Volver al inicio

Sin Anestesia

Automatización de contenido para un programa periodístico

Rol
AI Engineer & Full-Stack Developer
Estado
En producción

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

pipelinetext
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 ──► publicado
Cada etapa es idempotente: si Bull reintenta, no vuelve a pagar la transcripción.

Decisiones

01

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 const
02

No 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.

03

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.

04

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
}
05

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

El principio de confirmación humana, visible: la IA propone título, descripción y un copy distinto para cada red, pero nada sale hasta que el periodista pulsa «Aprobar y publicar».
Cada emisión entra como un link y sale como N clips listos para revisar.
El perfil de tono es configurable por el usuario y es el campo que más influye en la calidad de todo lo que escribe la app.
La agenda del programa: entrevistas concertadas y publicaciones previstas.
Acceso privado: la aplicación se construyó para una redacción concreta. La paleta sale del logo del programa, no de una plantilla.

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

HabilidadEvidencia
Salidas estructuradas y fiablesTool-use forzado + normalizeInput con tres modos de fallo reales cubiertos
Gestión de costosRoster de dos modelos · prompt recortado 20 % con medición · benchmark de proveedor con umbrales previos
Mitigación de alucinacionesResolución contra la base de datos · sanitizeExternalNames · prompts que premian el "no sé"
Robustez en producciónPipeline idempotente · fallback a heurística · reintentos con backoff · colas separadas
Criterio de productoConfirmación humana antes de escribir o publicar · tono configurable por el usuario
Ingeniería de promptsRol + criterios priorizados + reglas duras + restricciones de formato en el esquema

Stack

  • NestJS
  • TypeScript
  • MongoDB
  • Redis + Bull
  • ffmpeg (libass)
  • yt-dlp
  • Next.js
  • React
  • Tailwind
  • Claude Sonnet 5
  • Claude Haiku 4.5
  • Whisper
  • YouTube Data API v3
  • TikTok Content Posting API
  • Meta Graph
  • Docker Compose