El problema que no resolví con IA
Cuando armé la máquina que escribe este blog solo, me quedó pendiente una pieza: cada post necesita una portada. El sistema ya llamaba a claude -p con --json-schema para generar título, slug, descripción SEO, tags y cuerpo en HTML. Lo obvio hubiera sido seguir la misma lógica y pedirle una imagen a algún generador — Gemini tiene uno bastante bueno, DALL-E también. Pero cuando me puse a pensarlo en frío, esa opción tenía tres problemas que no quería arrastrar a algo que corre solo, sin supervisión, todos los días.
Tres razones para no usar un generador de imágenes
Primero, costo: el panel ya reporta el total_cost_usd real de cada llamada a Claude, sin estimaciones. Agregar una llamada a una API de imágenes por cada post — publicado o no — significaba otro cargo variable que además no tiene forma de auditarse tan fácil como el conteo de tokens de un modelo de texto.
Segundo, confiabilidad: los generadores de imágenes son notoriamente malos escribiendo texto dentro de la imagen — letras deformadas, palabras cortadas, tildes que desaparecen. Y una portada de blog en español, con títulos que a veces superan los 60 caracteres, es básicamente un ejercicio de tipografía, no de arte generativo.
Tercero, y el que más pesó: determinismo. Todo lo demás en este servidor — desde los backups hasta los timers de systemd — está pensado para que, dado el mismo input, produzca el mismo output. Una imagen generada por un modelo es, por diseño, no reproducible. Eso está bien para contenido creativo, pero no para un componente de infraestructura que corre sin que nadie lo mire.
Lo que terminé construyendo
La solución fue mucho más aburrida y mucho más confiable: código puro. Cada portada es un SVG generado en el momento — un gradiente de fondo, un patrón de puntos superpuesto para textura, y el título del post renderizado como texto real, no como imagen rasterizada de otro lado.
La parte no trivial fue el tamaño de fuente. Un título corto como "IA y logs" se ve bien en 48px; uno largo como el de este mismo post se vería cortado o desbordado con el mismo valor fijo. Así que el generador calcula un ancho aproximado del texto renderizado y va reduciendo el tamaño de fuente en un loop hasta que el título completo entra en el ancho disponible del SVG, con un mínimo razonable antes de empezar a hacer wrap en dos líneas. Nada de heurísticas de IA para esto — es aritmética simple sobre el ancho promedio de carácter según el tamaño de fuente.
function ajustarFuente(titulo, anchoMax, fuenteInicial = 56, fuenteMin = 28) {
let fuente = fuenteInicial
while (fuente > fuenteMin && estimarAncho(titulo, fuente) > anchoMax) {
fuente -= 2
}
return fuente
}
El SVG resultante se convierte a PNG con rsvg-convert, una herramienta de línea de comandos liviana que ya vive en cualquier Debian sin instalar nada exótico. Todo el proceso — generar el SVG, calcular la fuente, convertir a PNG — corre en milisegundos, sin red, sin clave de API, sin posibilidad de que un servicio externo esté caído justo cuando toca publicar.
La lección que se aplica más allá de las portadas
Lo que más me sirvió de este ejercicio no fue la solución en sí, sino el criterio para llegar a ella: usar IA donde hace falta juicio y lenguaje — redactar el post, elegir el ángulo, resumir en 160 caracteres — y usar código determinista donde el problema es en realidad de formato y layout, aunque a simple vista "generar una imagen" suene como una tarea para un modelo.
Es el mismo criterio que uso para el resto del servidor: cuándo dejar que algo corra automático y cuándo conviene que una decisión sea explícita y reproducible. Una portada de blog es una decisión de bajo riesgo, pero el principio es el mismo que aplicaría en cualquier pipeline de producción: la IA es excelente resolviendo ambigüedad, pero cuando el problema tiene una solución algorítmica clara, agregar un modelo solo suma latencia, costo y una fuente más de fallas que no controlás.