# Story Studio — Contexto para Claude Code ## Qué es este proyecto **Story Studio** es una aplicación Next.js (v16, App Router, Turbopack) para crear series animadas infantiles de forma asistida por IA. El flujo de trabajo es un wizard de 3 pasos: 1. **Idea Motriz** — Concepto, estructura narrativa, valores, protagonistas 2. **Personajes** — Fichas completas (especie, apariencia física, personalidad, utilería visual…) 3. **Capítulos** — Guión conductor → Escaleta (desglose de planos) → Prompts de imagen → Assets de personaje Los datos se persisten como archivos markdown + JSON en `projects/{slug}/`. --- ## Stack técnico | Capa | Tecnología | |------|------------| | Framework | Next.js 16.1.6 (App Router, `use()` para params) | | React | 19.2.3 | | Estilos | TailwindCSS 4 + PostCSS | | Iconos | Lucide React | | LLM (texto) | OpenAI GPT-4o (`openai` SDK) | | LLM (imágenes) | Google Gemini / Nano Banana Pro (`@google/genai` SDK) | | Datos | Archivos en disco (`projects/` dir), sin base de datos | ### Variables de entorno (`.env.local`) ``` OPENAI_API_KEY=sk-... GEMINI_API_KEY=AI... ``` > **IMPORTANTE**: El `.env.local` DEBE estar en UTF-8 sin BOM. Si se guarda como UTF-16 LE (bytes iniciales `255,254`), Next.js no puede parsear las variables. --- ## Estructura de archivos ``` story-studio/ ├── src/ │ ├── app/ │ │ ├── page.tsx # Home — lista de proyectos │ │ ├── project/[slug]/ │ │ │ ├── page.tsx # Wizard principal (3 pasos) │ │ │ └── capitulo/[numero]/page.tsx # Editor de capítulo │ │ └── api/projects/[slug]/ │ │ ├── route.ts # CRUD proyecto │ │ ├── idea-motriz/route.ts # Generar/refinar idea │ │ ├── personajes/ │ │ │ ├── route.ts # CRUD personajes │ │ │ └── [id]/ │ │ │ ├── assets/route.ts # ★ Character assets API │ │ │ └── images/[assetId]/route.ts # Servir imágenes desde disco │ │ └── capitulos/ │ │ ├── route.ts │ │ └── [numero]/ │ │ ├── guion/route.ts │ │ ├── escaleta/route.ts │ │ └── prompts/route.ts │ ├── components/ │ │ ├── project/ │ │ │ ├── CharacterAssetsPanel.tsx # ★ Panel de assets con workflow │ │ │ ├── PersonajesView.tsx │ │ │ ├── PersonajeEditor.tsx │ │ │ ├── IdeaMotrizView.tsx │ │ │ └── IdeaMotrizEditor.tsx │ │ ├── ui/ (Button, Card, Input, Select, Toast) │ │ └── wizard/ (WizardStepper, GenerationPanel) │ ├── hooks/ │ │ └── useProjectData.ts # Hook principal del wizard │ ├── lib/ │ │ ├── file-service.ts # Lectura/escritura de archivos de proyecto │ │ ├── llm-service.ts # OpenAI: generación de texto (ideas, personajes, guiones, prompts) │ │ ├── image-service.ts # ★ Gemini: generación de imágenes (text2img + img2img) │ │ └── prompts.ts # Todos los system prompts para el LLM │ └── types/ │ └── project.ts # Todos los tipos TypeScript ├── projects/ # Datos persistidos en disco │ └── {slug}/ │ ├── project.md │ ├── idea-motriz.md │ ├── personajes.md │ ├── personajes/{id}/ │ │ ├── assets.json # CharacterAssetsData │ │ └── images/{assetId}.png # Imágenes generadas │ └── capitulos/{numero}/ │ ├── capitulo.md │ ├── guion-conductor.md │ ├── escaleta.md │ ├── escaleta-planos.json # Planos estructurados │ └── prompts.md └── package.json ``` --- ## Sistema de Character Assets (última feature implementada) ### Flujo de trabajo (4 pasos en UI) 1. **Generar prompts** → OpenAI GPT-4o genera 9 prompts (3 T-poses + 6 emociones) basándose en la ficha del personaje 2. **Generar imagen base** → Gemini text-to-image genera la imagen canónica (T-pose frontal). El prompt es editable antes de generar. 3. **Fijar como referencia** → El usuario revisa y "bloquea" la imagen base como referencia canónica 4. **Generar variaciones** → Gemini image-to-image genera el resto de poses/emociones usando la imagen base como referencia ### API: `/api/projects/[slug]/personajes/[id]/assets` **GET** → Devuelve `CharacterAssetsData { assets: CharacterAsset[], baseLocked: boolean }` **POST** con `action`: | Acción | Descripción | |--------|-------------| | `generate-prompts` | LLM genera prompts de texto para 9 assets | | `generate-base-image` | Text-to-image con Gemini (acepta `prompt` override) | | `lock-base` | Fija la imagen base como referencia | | `generate-variation` | Img-to-img de un asset individual (requiere `assetId`) | | `generate-all-variations` | Genera todas las variaciones pendientes secuencialmente | ### Servicio de imágenes (`image-service.ts`) ```typescript // Text-to-image (para imagen base) generateImage(prompt: string, options?: { aspectRatio?: string }): Promise // Image-to-image (para variaciones desde referencia) generateVariation(referenceImageBuffer: Buffer, prompt: string, options?): Promise // Storage saveImage(slug, personajeId, assetId, buffer): Promise // returns URL loadImage(slug, personajeId, assetId): Promise ``` Usa modelo `gemini-2.5-flash-preview-image-generation` con `responseModalities: ['TEXT', 'IMAGE']`. ### Tipos clave (`types/project.ts`) ```typescript interface CharacterAsset { id: string; // "{personajeId}-{type}-{variant}" type: 'tpose' | 'emotion' | 'pose'; variant: string; // 'front'|'side'|'back' o 'alegria'|'tristeza'|etc. label: string; prompt: string; imageUrl?: string; // "/api/projects/{slug}/personajes/{id}/images/{assetId}" isBase?: boolean; // true for canonical reference status: 'pending' | 'prompt_ready' | 'generating' | 'generated'; } interface CharacterAssetsData { assets: CharacterAsset[]; baseLocked: boolean; } ``` ### UI (`CharacterAssetsPanel.tsx`) - Modal a pantalla completa con stepper visual (4 pasos) - Sección de imagen base con preview, editor de prompt, botones generar/regenerar/fijar - Grid de variaciones (T-poses y emociones) con generación individual o masiva - Se abre desde `PersonajesView.tsx` → botón "Assets" por personaje --- ## Convenciones del proyecto - **Next.js App Router**: Todos los params son `Promise<>` y se resuelven con `await params` - **API Routes**: `NextRequest` / `NextResponse`, patrón RESTful con acciones via `action` en body POST - **Archivos de datos**: Markdown con frontmatter YAML (via `gray-matter`) + JSON auxiliar - **Estilo UI**: TailwindCSS 4, diseño minimalista con paleta `stone`, acentos `indigo` y `emerald` - **Componentes UI**: `Button` (variantes: primary/secondary/outline/ghost/danger), `Card`, `Input`/`Textarea`, `Select`, `Toast` - **Sin base de datos**: Todo se lee/escribe en `projects/` con `fs/promises` --- ## Estado actual - ✅ Wizard completo: idea motriz → personajes → capítulos - ✅ Generación de guión conductor y escaleta - ✅ Prompts de imagen por plano (lee planos desde JSON server-side) - ✅ Character assets: prompts generados por LLM - ✅ Character assets: generación de imagen base con Gemini (text-to-image) - ✅ Character assets: flujo lock-base + variaciones con referencia (img-to-image) - ⏳ **Pendiente**: Probar flujo completo de generación de imágenes (requiere `GEMINI_API_KEY` configurada) - ⏳ **Pendiente**: Las imágenes de los prompts de planos (escaleta) aún no se generan con Gemini - ⏳ **Pendiente**: Integrar assets de personaje con la composición final en Remotion --- ## Proyecto hermano: Remotion (`../vibecoding/`) Existe un proyecto Remotion en `../vibecoding/` que es donde se componen los videos finales. Usa las imágenes generadas aquí. Su `package.json` incluye `remotion 4.0.409`, `openai`, `dotenv`, y `tailwindcss 4.0.0`. --- ## Bugs resueltos recientemente 1. **0 prompts generados**: Los `planos` no se guardaban como JSON separado → ahora se guardan en `escaleta-planos.json` 2. **OPENAI_API_KEY no leída**: El `.env.local` estaba en UTF-16 LE con BOM → convertido a UTF-8 sin BOM 3. **Import dinámico rompía env vars con Turbopack**: Cambiado a import estático en API routes