# 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<Buffer>

// Image-to-image (para variaciones desde referencia)
generateVariation(referenceImageBuffer: Buffer, prompt: string, options?): Promise<Buffer>

// Storage
saveImage(slug, personajeId, assetId, buffer): Promise<string>  // returns URL
loadImage(slug, personajeId, assetId): Promise<Buffer | null>
```

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
