# 05 · Solución de problemas

Guía de fallas frecuentes, en formato **síntoma → causa → arreglo**. Cada caso trae el
texto del error tal como aparece en consola, para que lo puedas buscar con la vista.

Antes que nada, corré el diagnóstico. No imprime ninguna key: solo dice qué está y qué falta.

```bash
npm run check
# o, si guardás las keys en un archivo .env:
node --env-file=.env scripts/check-setup.mjs
```

Una salida sana se ve así:

```
=== Herramientas ===
✓ Node.js v20.x.x
✓ ffmpeg: ffmpeg version ...
✓ ffprobe: ffprobe version ...
✓ npx (para HyperFrames): 10.x.x

=== API keys (Keychain o variables de entorno) ===
✓ kie — Kie.ai (imágenes GPT + video I2V Grok)
✓ elevenlabs — ElevenLabs (voz TTS + efectos de sonido)
✓ suno — Suno via sunoapi.org (música)
• gemini — Google Gemini (respaldo opcional de imágenes) (sin configurar, opcional)
```

`gemini` con `•` (amarillo) es normal: es opcional. Solo `✗` (rojo) bloquea.

---

## Tabla rápida (síntoma → sección)

| Lo que ves | Andá a |
|---|---|
| `Falta la key "kie" (...)` o `✗ kie` en `npm run check` | [1. Key faltante o mal guardada](#1-key-faltante-o-mal-guardada) |
| `Kie.ai no responde — revisá tu key o tu conexión` | [2. Kie.ai no responde](#2-kieai-no-responde) |
| `Kie falló: ...` justo al empezar un trabajo | [3. Saldo insuficiente en Kie](#3-saldo-insuficiente-en-kie) |
| Los subtítulos salen en inglés | [4. Whisper traduce al inglés](#4-whisper-traduce-al-inglés) |
| `ffmpeg falló` / `no encontrado` en `npm run check` | [5. ffmpeg o ffprobe no encontrado](#5-ffmpeg-o-ffprobe-no-encontrado) |
| `Música BLOQUEADA: "..." parece tener VOCES` | [6. Música bloqueada por la guarda anti-voces](#6-música-bloqueada-por-la-guarda-anti-voces) |
| `hyperframes ... falló`, o la primera corrida se cuelga | [7. npx / HyperFrames lento o falla la primera vez](#7-npx--hyperframes-lento-o-falla-la-primera-vez) |
| `descarga falló: HTTP 403/404` al animar una imagen vieja | [8. La URL de imagen subida a Kie expiró](#8-la-url-de-imagen-subida-a-kie-expiró) |
| La IA escribe el texto con errores dentro de la ilustración | [9. La IA escribe mal el español dentro de la imagen](#9-la-ia-escribe-mal-el-español-dentro-de-la-imagen) |
| Los subtítulos van adelantados o atrasados respecto a la voz | [10. Video y voz desincronizados](#10-video-y-voz-desincronizados) |

---

## 1. Key faltante o mal guardada

**Síntoma**

```
Falta la key "kie" (Kie.ai (imágenes GPT + video I2V Grok)).
  Opción A (recomendada en Mac): guardala en el Keychain:
    security add-generic-password -a editor -s "editor:kie-ai-key" -w
  Opción B (cualquier sistema): exportá la variable de entorno:
    export KIE_API_KEY="tu-key"
  Ver docs/02-configurar-keys.md
```

O en `npm run check` aparece `✗ kie — ... (sin configurar)`.

**Causa**

El código resuelve cada key en este orden: **1)** variable de entorno; **2)** Keychain de macOS.
Si las dos faltan (o el valor quedó vacío, o el nombre de la entrada no coincide exactamente),
el runner se detiene antes de gastar créditos. Nombres exactos por servicio:

| Key | Variable de entorno | Cuenta Keychain | Servicio Keychain |
|---|---|---|---|
| kie | `KIE_API_KEY` | `editor` | `editor:kie-ai-key` |
| elevenlabs | `ELEVENLABS_API_KEY` | `editor` | `editor:elevenlabs-key` |
| suno | `SUNO_API_KEY` | `editor` | `editor:sunoapi-org-key` |
| gemini (opcional) | `GEMINI_API_KEY` | `editor` | `editor:gemini-key` |

**Arreglo**

En Mac, guardá la key en el Keychain (te pide el valor de forma interactiva, no queda en el historial del shell):

```bash
security add-generic-password -a editor -s "editor:kie-ai-key" -w
```

En cualquier sistema, usá un archivo `.env` (Node >= 20.6 lo lee con `--env-file`):

```bash
cp .env.example .env
# pegá tus keys reales en .env (ese archivo está en .gitignore, no se sube)
node --env-file=.env src/runners/imagen.mjs --prompt "..." --out ./a.png
```

Notas que ahorran tiempo:

- El Keychain **solo existe en macOS**. En Linux o Windows, usá variables de entorno o `.env`.
- Si guardaste la key con otro nombre de servicio, borrá la entrada y volvé a crearla con el nombre exacto de la tabla.
- Verificá que la key quedó (sin verla):

```bash
security find-generic-password -a editor -s "editor:kie-ai-key" >/dev/null && echo "kie OK en Keychain"
```

---

## 2. Kie.ai no responde

**Síntoma**

```
Kie.ai no responde — revisá tu key o tu conexión
```

Aparece al empezar `imagen` o `animar`, antes de crear la tarea.

**Causa**

Antes de gastar créditos, el runner hace un chequeo de salud contra `GET /chat/credit` con un
límite de 5 segundos. Si ese chequeo no responde `200`, frena. Las causas típicas: sin internet,
la key es válida pero quedó revocada o con un carácter de más, o Kie está caído en ese momento.

**Arreglo**

Probá el endpoint a mano. Debería devolverte un número de créditos:

```bash
# con variable de entorno
curl -sS https://api.kie.ai/api/v1/chat/credit \
  -H "Authorization: Bearer $KIE_API_KEY"

# en Mac, leyendo la key del Keychain
curl -sS https://api.kie.ai/api/v1/chat/credit \
  -H "Authorization: Bearer $(security find-generic-password -a editor -s 'editor:kie-ai-key' -w)"
```

- Si devuelve un número → tu key y tu red están bien; reintentá el runner (pudo ser un bache pasajero).
- Si devuelve `401` / `403` → la key está mal; regenerala en https://kie.ai y volvé a guardarla (sección 1).
- Si no devuelve nada o da timeout → revisá tu conexión, VPN o proxy.

> Mientras una tarea ya está en curso, un bache de red no la mata: el cliente reporta el estado
> interno `network-retry` y sigue intentando hasta el timeout (15 min por defecto).

---

## 3. Saldo insuficiente en Kie

**Síntoma**

```
Kie falló: <mensaje del servidor>
```

Suele caer apenas arranca un trabajo de imagen o video, sin llegar a producir el archivo.

**Causa**

No hay créditos suficientes para el modelo elegido. Costo por trabajo (créditos Kie):

| Paso | Modelo | Slug | Costo |
|---|---|---|---|
| Imagen | `gpt-image` (default) | `gpt-image-2-text-to-image` | 6 cr |
| Imagen | `nano-banana` | `google/nano-banana` | 4 cr |
| Video I2V | `grok` (default) | `grok-imagine/image-to-video` | 18 cr |
| Video I2V | `wan` | `wan/2-6-flash-image-to-video` | 30 cr |

**Arreglo**

Mirá tu saldo antes de lanzar un lote:

```bash
curl -sS https://api.kie.ai/api/v1/chat/credit \
  -H "Authorization: Bearer $KIE_API_KEY"
```

Calculá lo que vas a gastar (escenas × costo del modelo) y dejá un margen. Si no alcanza,
recargá en https://kie.ai. Para abaratar pruebas:

- Imagen con `--model nano-banana` (4 cr en vez de 6). Ojo: escribe mal el texto en español (ver sección 9).
- Avanzá por etapas con `--until` para no animar de más mientras ajustás los prompts de imagen:

```bash
node src/runners/pipeline.mjs --project ejemplo/proyecto-demo --until imagen
```

> Los runners de imagen y video imprimen el saldo antes y el gasto real después
> (`saldo N cr` / `gasto M cr`), así llevás la cuenta sin salir de la consola.

---

## 4. Whisper traduce al inglés

**Síntoma**

La voz está en español pero `transcript.json` / los subtítulos salen en inglés. Puede aparecer este aviso:

```
⚠ modelo "small.en" es solo inglés y traduciría tu audio en es. Usá --model small.
```

**Causa**

Los modelos de Whisper que terminan en `.en` (`small.en`, `medium.en`, `base.en`…) son
**solo inglés**: con audio en español lo fuerzan al inglés. **Regla dura: nunca uses un modelo
`.en` con audio que no sea inglés.**

**Arreglo**

Usá un modelo multilingüe (los que **no** terminan en `.en`) y declará el idioma:

```bash
node src/runners/transcribir.mjs --audio ./assets/voz.mp3 --language es --model small
```

`small` y `es` son los valores por defecto del runner, así que esto alcanza:

```bash
node src/runners/transcribir.mjs --audio ./assets/voz.mp3
```

Si ya generaste subtítulos en inglés por error, volvé a correr `transcribir` con el modelo
correcto: regenera `transcript.json`, `words.json` y `groups.json` encima de los viejos.

> El runner **avisa** pero no se detiene si insistís con un modelo `.en`. El aviso es tu señal
> para cancelar y repetir bien.

---

## 5. ffmpeg o ffprobe no encontrado

**Síntoma**

En `npm run check`:

```
✗ ffmpeg: no encontrado
✗ ffprobe: no encontrado
```

O al animar / normalizar / mezclar:

```
ffmpeg falló:
...
```

**Causa**

El editor llama a `ffmpeg` y `ffprobe` directo desde el `PATH` del sistema (los usan la
normalización de clips, la medición de duración y la mezcla final). Si no están instalados o no
están en el `PATH`, esos pasos fallan. `ffprobe` viene junto con `ffmpeg`: si falta uno, suele
faltar el otro.

**Arreglo**

Instalalos según tu sistema:

```bash
# macOS (Homebrew)
brew install ffmpeg

# Debian / Ubuntu
sudo apt-get update && sudo apt-get install -y ffmpeg

# Windows (winget)
winget install Gyan.FFmpeg
```

Confirmá que el sistema los encuentra:

```bash
ffmpeg -version
ffprobe -version
```

Si los instalaste pero la consola sigue sin verlos, abrí una terminal nueva (para recargar el
`PATH`) y volvé a correr `npm run check`.

---

## 6. Música bloqueada por la guarda anti-voces

**Síntoma**

```
Música BLOQUEADA: "./assets/musica-track0.mp3" parece tener VOCES (exceso centro/lado = 8.3 dB ≥ 6.5).
La música de fondo debe ser instrumental para no competir con la narración.
Generala con generarMusica({ instrumental: true }) o elegí otro track.
```

Pasa en el paso de mezcla (`mezclar`), no en el de render.

**Causa**

La mezcla mide cuánto contenido hay centrado en la banda de la voz humana (250–3500 Hz). La voz
cantada vive ahí, igual que la narración. Si ese exceso llega a 6.5 dB o más, la guarda asume que
el track trae voces y frena: una pista con voz tapa la narración. El umbral protege el resultado
final.

**Arreglo**

Generá la música como instrumental. El runner de música ya lo hace por defecto (instrumental
salvo que pidas `--withVocals`):

```bash
node src/runners/musica.mjs --prompt "tech minimal, calmo, cinematic" --out ./assets
```

Después mezclá con el track instrumental:

```bash
node src/runners/mezclar.mjs --video ./render.mp4 --music ./assets/musica-track0.mp3 --out ./final.mp4
```

- Si vino de un pipeline donde pediste `--withVocals`, sacá ese flag y regenerá la música.
- Si la música es de otra fuente y tiene voces, cambiá de track: no hay forma de pasar la guarda
  por CLI, y es a propósito.
- Si la narración queda baja frente al fondo, bajá el volumen de la música (default `0.12`):

```bash
node src/runners/mezclar.mjs --video ./render.mp4 --music ./fondo.mp3 --out ./final.mp4 --volume 0.08
```

---

## 7. npx / HyperFrames lento o falla la primera vez

**Síntoma**

La primera transcripción o el primer render se quedan minutos sin avanzar, o cortan con:

```
hyperframes transcribe falló (¿está instalado node/npx? ver docs/01-instalar.md)
```

```
hyperframes render falló
```

**Causa**

El editor no trae HyperFrames instalado: lo baja **on-demand** con `npx --yes hyperframes`. La
primera corrida descarga el paquete (y, en transcripción, el modelo de Whisper). Eso tarda y
necesita internet. Si la descarga se corta a la mitad, la corrida falla.

**Arreglo**

- Dale tiempo a la primera corrida y no la canceles. Las siguientes usan la caché y arrancan rápido.
- Confirmá que tenés Node y npx:

```bash
node --version   # debe ser >= 18
npx --version
```

- Si falló a media descarga, reintentá: `npx` retoma con lo que ya tiene en caché.
- La versión de HyperFrames del render queda fijada en el `package.json` de la carpeta de la
  composición (`composicion/`), no se decide al vuelo. Si tocás esa versión, el primer render
  con la nueva vuelve a descargar.
- Render dentro de la carpeta correcta (la que tiene `index.html`):

```bash
node src/runners/render.mjs --dir ./ejemplo/proyecto-demo/composicion
```

> Si el render avisa `validate con avisos`, no es un error: sigue de largo con el render igual.

---

## 8. La URL de imagen subida a Kie expiró

**Síntoma**

Al animar reutilizando una URL guardada de antes:

```
descarga falló: HTTP 403
```

o `HTTP 404`, sobre la `remoteUrl` de una imagen de hace días.

**Causa**

Cuando animás un archivo local (`--image`), el runner sube la imagen a Kie y obtiene una **URL
pública temporal**. Esas URLs se borran a los **~3 días**. Si guardaste esa URL y la reusás
después, ya no existe.

**Arreglo**

No reutilices URLs viejas: animá desde el archivo local y dejá que el runner vuelva a subirlo
(genera una URL fresca cada vez):

```bash
node src/runners/animar.mjs --image ./assets/escena-1.png --out ./assets/clip-1.mp4
```

Solo usá `--image-url` con una URL **recién** generada (por ejemplo, la `remoteUrl` que imprime
`imagen.mjs` en la misma sesión). Si perdiste el PNG de origen, regenerá la imagen primero:

```bash
node src/runners/imagen.mjs --prompt "..." --out ./assets/escena-1.png
```

---

## 9. La IA escribe mal el español dentro de la imagen

**Síntoma**

El texto incrustado en la ilustración sale con faltas, letras cambiadas o palabras inventadas
(más notorio con `nano-banana`).

**Causa**

Los modelos de imagen no deletrean bien el español dentro del dibujo. `nano-banana` es el más
flojo en esto; `gpt-image` falla menos con texto, pero tampoco es confiable para frases.

**Arreglo**

No pongas el texto **dentro** de la ilustración. Generá la imagen limpia (sin letras) y poné el
texto como **subtítulo** en la composición, sincronizado con la voz. Esa capa de subtítulos sale
de transcribir la narración (sección 4) y la arma HyperFrames en el build:

```bash
node src/runners/voz.mjs --file ./guion.txt --out ./assets/voz.mp3
node src/runners/transcribir.mjs --audio ./assets/voz.mp3
```

- Para imagen, dejá `gpt-image` (el default) cuando el dibujo sí necesite algún rótulo corto;
  `nano-banana` solo si no hay texto y querés ahorrar 2 cr.
- En el prompt de imagen, pedí explícitamente que **no** incluya texto, para que el modelo no
  intente escribir.

---

## 10. Video y voz desincronizados

**Síntoma**

Los subtítulos van adelantados o atrasados respecto a lo que se escucha, o la voz se oye doble /
con eco en el archivo final.

**Causa**

Los tiempos de los subtítulos (`words.json`, `groups.json`) se calculan **a partir del mismo
`voz.mp3`** que entra al render. Si cambiás la voz (regenerás `voz.mp3`, recortás silencios,
cambiás de voz) pero **no** volvés a transcribir, los subtítulos quedan calzados sobre la voz
vieja. La voz doble suele venir de mezclar dos pistas de audio que no son la misma.

**Arreglo**

Cada vez que toques la voz, transcribí de nuevo **antes** de hacer el build y el render:

```bash
node src/runners/voz.mjs --file ./guion.txt --out ./assets/voz.mp3
node src/runners/transcribir.mjs --audio ./assets/voz.mp3      # regenera words.json + groups.json
node src/runners/render.mjs --dir ./ejemplo/proyecto-demo/composicion
```

- Mantené un solo `voz.mp3` por proyecto: el que se transcribe y el que entra al render tienen
  que ser el mismo archivo.
- Si recortaste o editaste la voz por fuera, esa versión editada es la que tenés que transcribir.
- El orden del flujo importa: **voz → transcribir → build → render → mezcla**. Saltarte la
  transcripción tras cambiar la voz es la causa más común del desfase.

---

## Si nada de esto encaja

1. Corré `npm run check` y resolvé primero cualquier `✗`.
2. Volvé a leer el error completo de la consola: los runners explican qué falta y cómo arreglarlo.
3. Aislá el paso que falla corriéndolo solo (`imagen`, `animar`, `voz`, `transcribir`, `musica`,
   `render`, `mezclar`) en vez del `pipeline` entero.
4. Repasá `docs/01-instalar.md`, `docs/02-configurar-keys.md` y `docs/04-referencia-modelos.md`.
