Sin Anestesia
Automatización de contenido para un programa periodístico
El problema
Un periodista con un programa diario tenía dos cuellos de botella manuales. Cada emisión dura una o dos horas: recortar los momentos virales, ponerlos en vertical, subtitularlos, escribir el copy de cada red y publicarlos era medio día de trabajo por programa. Y para conseguir invitados leía las noticias del día y cruzaba de memoria contra un Excel de contactos.
Arquitectura
link YouTube
└─ yt-dlp ──────────► video fuente (mp4 ≤1080p)
└─ ffmpeg ───────► audio mono 16 kHz 16 kbps
└─ Whisper ───► transcripción con timestamps por palabra
└─ Claude Sonnet 5 ──► N momentos clip-eables (inicio, fin, gancho)
└─ ffmpeg ────────► corte + fondo 9:16 + subtítulos ASS quemados
└─ Claude Sonnet 5 ──► títulos, hashtags y caption por red
└─ YouTube / TikTok / Meta ──► publicadoDecisiones
Roster de modelos por tarea, no un modelo para todo
Calidad donde el resultado es el producto (highlights, copys, mensajes) y velocidad/costo donde es extracción estructurada (mapear columnas, cruzar titulares con una lista). Bajar el matching de noticias a Haiku no degrada nada perceptible y corre cada 2 horas por cron: es donde el ahorro compone.
export const AI_MODELS = {
quality: 'claude-sonnet-5', // salida que ve el usuario
fast: 'claude-haiku-4-5-20251001', // extracción barata
} as constNo confiar en los números que devuelve el modelo
Los startSec/endSec de Claude se tratan como propuestas, no como verdad: cada borde se ajusta al segmento real más cercano con 3 s de tolerancia, se recorta a la duración máxima, se descartan clips demasiado cortos y los que se solapan con el anterior aunque el prompt lo prohíba. Esta capa es la que permite simplificar el prompt: el modelo aporta el juicio editorial, el código impone las restricciones duras.
Defensa contra salidas malformadas reales
El modelo casi siempre devuelve input ya parseado, pero a veces serializa el objeto entero —o solo un campo interno— como string JSON, ocasionalmente envuelto en un fence de markdown. Sin normalizar, Mongoose recibe un string donde espera un array y revienta con CastError en producción. normalizeInput() cubre los tres casos observados. No es teoría defensiva: cada rama corresponde a un fallo real del pipeline.
Detalles que solo se aprenden implementando
Sonnet 5 usa adaptive thinking por defecto, y eso entra en conflicto con tool_choice forzado. El cliente lo detecta y desactiva thinking solo para los modelos que lo necesitan. Está comentado en el código para que el siguiente que lo lea no lo "arregle".
if (model.startsWith('claude-sonnet-5')) {
request.thinking = { type: 'disabled' } // choca con tool_choice forzado
}Degradación elegante: la IA como mejora, no como dependencia
En el import de contactos el mapeo de columnas lo propone Haiku. Si la llamada falla —sin API key, rate limit, timeout— cae a una tabla de heurísticas regex y el import sigue funcionando, solo con un mapeo peor que el usuario revisa igual antes de confirmar.
El producto
Cifras
- 7 400
- líneas de TypeScript (94 archivos)Sin Anestesia §1
- 14
- pantallas en el frontendSin Anestesia §1
- 7
- usos distintos de IA en el sistemaSin Anestesia §3
- −20 %
- de prompt recortado, medido con count_tokensSin Anestesia §4.5
- 4
- contenedores en producciónSin Anestesia §1
- 72 %
- del gasto de IA era transcripciónSin Anestesia §4.9
Lo que demuestra
| Habilidad | Evidencia |
|---|---|
| Salidas estructuradas y fiables | Tool-use forzado + normalizeInput con tres modos de fallo reales cubiertos |
| Gestión de costos | Roster de dos modelos · prompt recortado 20 % con medición · benchmark de proveedor con umbrales previos |
| Mitigación de alucinaciones | Resolución contra la base de datos · sanitizeExternalNames · prompts que premian el "no sé" |
| Robustez en producción | Pipeline idempotente · fallback a heurística · reintentos con backoff · colas separadas |
| Criterio de producto | Confirmación humana antes de escribir o publicar · tono configurable por el usuario |
| Ingeniería de prompts | Rol + criterios priorizados + reglas duras + restricciones de formato en el esquema |