Kodekanox Studio: plataforma donde creadores organizan prompts, personajes y escenas para producir anime con IA — con seguridad y cobro que sostienen un negocio real, no un demo.
01 · Contexto y problema
Kodekanox Studio es una plataforma donde creadores organizan prompts, personajes y escenas para producir contenido estilo anime con IA — la Studio automatiza el trabajo pesado para que el creador se enfoque solo en crear.
Es un SaaS pago de verdad: Next.js 16 con Turbopack y App Router (Server Components + Server Actions por defecto), Supabase como backend (Postgres, Auth, Storage), Stripe para cobros, Resend para email transaccional, Upstash Redis para rate-limit y reCAPTCHA contra bots.
El desafío central: vender acceso por suscripción a una herramienta de IA manteniendo a los proveedores de IA invisibles en la UI (marca neutra), con seguridad en profundidad — y garantizando que solo un pago verificado, nunca un redirect del navegador, otorgue acceso.
Restricciones
02 · Decisiones técnicas
Acceso otorgado solo por el webhook de Stripe
Problema
El redirect del navegador tras el checkout es falsificable; no puede ser la fuente de verdad para otorgar acceso a un producto pago.
Opciones
Otorgar acceso en el redirect del checkout · polling del estado de la sesión · webhook firmado de Stripe como única fuente
Decisión ✓
El webhook de Stripe (checkout.session.completed y afines) es la ÚNICA fuente que otorga acceso, con upsert idempotente por owner_id; studio_subscriptions es read-only para el usuario — solo el service-role escribe status/plan.
Trade-off
Hay un pequeño delay entre pagar y ver el acceso activo (espera al webhook), pero es la única forma de que nadie se autoconceda una suscripción manipulando el cliente.
Seguridad en profundidad para archivos privados
Problema
La plataforma guarda y sirve archivos privados de creadores; un bypass de RLS o un bucket mal configurado expone contenido pago de otra persona.
Opciones
Buckets públicos con URLs ofuscadas · RLS solo en Postgres · RLS en tablas y Storage + URLs firmadas + verificación de propiedad antes del service-role
Decisión ✓
RLS en tablas y en Storage, buckets privados, URLs firmadas de 60s, subida en 3 pasos (firma → PUT directo a Storage → confirmación con verificación de magic bytes), y el service-role solo actúa después de verificar la propiedad del recurso.
Trade-off
Más pasos en el flujo de subida (tres viajes en vez de uno), a cambio de que ninguna URL quede válida más de 60s y que un bypass de RLS nunca sea el camino por defecto.
Un proxy único como muralla (idioma, subdominio, MFA)
Problema
Necesitaba un único punto que resolviera idioma, gateara rutas (/creator, /admin), separara subdominios (app vs admin) y exigiera MFA del admin — sin duplicar esa lógica en cada ruta.
Opciones
Chequeo por página · middleware clásico · src/proxy.ts como muralla única (Next.js 16)
Decisión ✓
src/proxy.ts concentra todo: resuelve idioma por cookie/geo, gatea /creator y /admin, separa kodekanox.com de admin.kodekanox.com y exige MFA en el admin. Es intencional que viva en src/ — en la raíz simplemente no corre, y todos esos gates desaparecen en silencio.
Trade-off
Se vuelve un punto único de falla si se rompe, así que quedó documentado en AGENTS.md como algo que nunca se toca sin leer antes los docs locales de Next.js 16 (la versión trajo breaking changes).
Arquitectura
03 · Subida de archivo privado: firma, PUT directo y confirmación por magic bytes
Guardar y servir archivos de usuario en un producto pago no puede confiar en la extensión o el mimetype que declara el cliente — y no debería pasar el peso del archivo por el servidor de la app sin necesidad.
El flujo quedó en 3 pasos: el cliente pide una URL firmada (el servidor verifica propiedad y cuota antes de firmar), el cliente sube el archivo directo a Storage (el app-server nunca ve los bytes), y recién después el cliente confirma — momento en que el servidor verifica la firma real del archivo (magic bytes) antes de marcarlo válido.
Esto se suma a una cuota de storage única en GB por plan (no por tipo de archivo) — decisión directa: un video pesa como 4 imágenes, así que cobrar por tipo dejaría que el costo corra por delante del ingreso del plan.
Resultado: ningún bucket público, ninguna URL firmada sobrevive más de 60s, y ningún archivo es confiable solo por la extensión que lleva.
04 · Seguridad y proceso antes de builds sensibles
Revisión de seguridad formal + test de intrusión A/B antes de mergear features sensibles (pago, acceso) — un hábito interno antes de tocar esas áreas, no una auditoría externa puntual.
Flujo de git disciplinado: rama por feature, commitlint con scope-enum, pre-push bloqueando push directo a main, y merges en orden cuando hay dependencia entre PRs.
Marca neutra por decisión de producto: ninguna mención a proveedores de IA de terceros en la UI — el stack de herramientas es secreto del negocio, no solo estética.
Regla dura de ingeniería, nacida de un bug real: los valores runtime nunca se exportan desde módulos "use server" (solo funciones async) — un .map is not a function en producción fue lo que hizo esa regla permanente.
Demo interactiva
Próximamente: ejecuta el código y míralo correr en vivo, sin instalar nada.
Editor en vivo (DartPad) — próximamente
04 · Resultados · antes / después
05 · Retrospectiva
Definiría desde el día 1 que los valores runtime nunca salen de módulos "use server": el bug real que originó esa regla costó un tiempo de debug que se podía evitar.
Cambiaría el login por navegación dura más temprano: la carrera de cookies con redirect() solo apareció probando el flujo completo, desde el checkout hasta el primer acceso — no en aislamiento.
Mantendría el webhook de Stripe como única fuente de acceso desde la primera versión: es tentador confiar en el redirect del checkout porque 'funciona en el happy path', pero es justo el camino que alguien malicioso intentaría falsificar primero.