El blog que se escribe solo (pero no se publica solo)
Llevaba varios posts publicados en este blog y me di cuenta de un patrón: la parte que más tiempo me tomaba no era decidir el tema, era sentarme a escribir. Entre el trabajo en Renta 4 y el resto de proyectos en este mismo servidor, el blog terminaba siendo lo primero que se postergaba. Así que hice lo que suelo hacer con cualquier tarea repetitiva de este servidor: automatizarla. La diferencia es que esta vez la automatización tenía que escribir prosa coherente, no solo mover archivos.
El corazón del sistema: claude -p con --json-schema
El panel de administración es una aplicación Node.js chica, sin frameworks pesados, que vive junto al resto de servicios Docker de este servidor. Cuando pido un post nuevo, no llama a una API de LLM directamente: invoca la CLI de Claude Code (claude -p) como un subproceso, pasándole el prompt y el flag --json-schema para forzar que la respuesta venga estructurada: título, slug, descripción SEO, tags y el cuerpo en HTML, todo en un único objeto validable.
Usar la CLI en lugar de la API directa fue una decisión práctica: ya tenía Claude Code instalado y autenticado en el servidor para otras tareas de administración, así que reutilizar esa misma vía evitó manejar credenciales de API por separado. El schema forzado es lo que hace que el resto del pipeline (guardar en base de datos, generar la portada, mostrar la vista previa) pueda tratar la respuesta como datos, no como texto libre que hay que parsear con expresiones regulares y rezar.
Cada llamada devuelve también total_cost_usd, el costo real de esa ejecución específica, y el panel lo muestra tal cual junto al borrador. No es una estimación ni un promedio: es el número real de esa llamada. Me pareció importante no inventar esa cifra — si el sistema va a jactarse de ser automático, que al menos sea honesto con lo que cuesta.
Las portadas: nada de bancos de imágenes
Para las imágenes de portada no llamo a ninguna API de generación de imágenes. Cada portada es un SVG armado por código: un gradiente de fondo, un patrón de puntos superpuesto, y el título del post ajustado a un tamaño de fuente dinámico para que nunca se corte, sin importar si el título tiene ocho palabras o veinte caracteres. Ese SVG se convierte a PNG con rsvg-convert al momento de generar el post. Es la misma lógica que aplico en el resto del servidor: cuando construí el mini-dashboard de monitoreo en vez de usar la interfaz oficial de Netdata, la razón era la misma — prefiero una pieza chica y controlada por mí que depender de un servicio externo para algo que puedo resolver con código simple.
Nada se publica sin que yo lo apruebe
Cada borrador que genera el sistema queda en una cola esperando revisión manual. El panel me muestra el HTML renderizado, la portada generada, los tags y el costo de la llamada, y desde ahí decido: publicar, editar o descartar. No hay ningún cron que empuje contenido a producción sin que yo lo vea antes. Me tomó la misma filosofía que aplico en seguridad de este servidor — como conté en el post sobre por qué un firewall por sí solo no basta, la protección real vive donde realmente importa. Acá, el punto de control real no es técnico, es humano: yo apretando "publicar".
El bug real: duplicar un borrador que ya existía
El detalle más interesante de construir esto no fue el prompt ni el schema, fue un bug de lógica bastante tonto en retrospectiva. Al principio, el sistema evitaba repetir tema comparando contra los posts ya publicados. El problema es que un borrador puede quedar días esperando aprobación — y en ese tiempo, el generador seguía proponiendo temas nuevos sin saber que ya existía un borrador casi idéntico durmiendo en la cola. Un día generó un post que era prácticamente el mismo ángulo que otro borrador de una semana antes, todavía sin publicar.
La corrección fue simple una vez identificada: la lista de "temas que no repetir" tiene que incluir también los borradores pendientes, no solo lo publicado. Eso sí, los enlaces internos que el sistema genera dentro de cada post siguen apuntando exclusivamente a contenido ya publicado de verdad — nunca a un borrador que podría no ver la luz nunca. Es una distinción chica pero importante: evitar duplicar temas requiere ver todo el pipeline, pero enlazar contenido requiere ver solo lo que es permanente.
Por qué vale la pena contar esto
Este sistema no reemplaza el criterio editorial, lo mueve más adelante en el proceso: en vez de decidir qué escribir, decido qué aprobar. Y como con casi todo lo que he armado en este servidor, el valor no estuvo en la parte de IA en sí, sino en la ingeniería alrededor — el schema que fuerza estructura, el costo que no se esconde, y un bug de estado que solo aparece cuando el sistema convive con el tiempo real de una cola de aprobación.