Arquitectura Híbrida 2.0: Cómo automaticé la inteligencia de mis Hubs de contenido

En el artículo anterior expliqué cómo rescaté mis mejores artículos del cementerio cronológico de WordPress usando Hugo para crear Hubs de conocimiento. El resultado fue una sección /temas/ rápida, limpia y bien organizada.

Pero había un problema que no mencioné: cada vez que publicaba un artículo nuevo, tenía que sentarme frente al ordenador, ejecutar el script manualmente y esperar a que Hugo compilara. Si estaba fuera de casa o solo con el móvil, los Hubs se quedaban desactualizados.

El contenido era dinámico. La arquitectura, demasiado estática.

La pregunta correcta

Podría haberlo resuelto con un webhook, con una GitHub Action o con cualquier servicio de CI/CD. Pero el objetivo siempre ha sido el contrario: menos dependencias externas y más control propio.

La pregunta no era «¿qué servicio uso?». «Era ¿cómo hago que el propio servidor sepa cuándo reconstruirse?».

El pegamento: Juredev Hubs Connector

Todo el sistema depende de un dato clave: saber a qué Hub pertenece cada artículo. Esa información no existe en WordPress por defecto, por lo que hay que crearla.

El plugin Juredev Hubs Connector resuelve esto con tres hooks de WordPress. El primero añade un selector en el editor lateral de cada post. El segundo guarda la elección como un metadato al publicar. El tercero inyecta automáticamente un banner al final del artículo con el enlace de vuelta al Hub.

El selector es un con los diez Hubs disponibles en el panel lateral del editor. Cuando guardo el post, el plugin escribe el slug elegido en wp_postmeta bajo la clave _juredev_selected_hub. Un dato minimalista que hace exactamente una sola cosa.

El banner que aparece al final de cada artículo usa rel="tag" en el enlace, un detalle semántico pequeño que refuerza la relación entre el artículo y su Hub para los crawlers:

$banner_html = '<p class="post-series">
    Tema Relacionado: <a href="' . esc_url( $hub_url ) . '" rel="tag">' . esc_html( $hub_name ) . '</a>
</p>';

Vale la pena mencionar que el plugin verifica tres condiciones antes de inyectar el banner: que sea una página de artículo individual, que esté dentro del loop principal y que sea la query principal. Sin esas comprobaciones, el banner podría aparecer en widgets, excerpts o cualquier otro lugar donde WordPress renderice contenido.

El cerebro del sistema: una query SQL

Antes de pensar en automatización, tuve que resolver un problema más fundamental: ¿qué datos necesita Hugo exactamente?

La respuesta está en SELECT-WP-POST.sql, una consulta que hace varias cosas a la vez. Extrae título, fecha, slug y resumen de cada artículo. Resuelve la imagen destacada navegando por la tabla de metadatos. Agrupa categorías y tags con GROUP_CONCAT. Y hace algo específico de esta arquitectura: lee el campo _juredev_selected_hub, el metadato que el plugin Juredev Hubs Connector escribe cuando asigno un artículo a un Hub desde el editor de WordPress.

(SELECT meta_value FROM wp_postmeta 
 WHERE post_id = p.ID 
 AND meta_key = '_juredev_selected_hub') AS hub_slug,

Esa línea es el puente entre los dos sistemas. Sin ella, Hugo no sabría qué artículos pertenecen a qué Hub.

Hay también un detalle de criterio editorial: los artículos etiquetados como Opinión están excluidos explícitamente. Los Hubs son colecciones técnicas. Las opiniones tienen su propio espacio en el feed cronológico. La query lo refleja:

AND p.ID NOT IN (
    SELECT tr.object_id FROM wp_term_relationships tr
    INNER JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
    INNER JOIN wp_terms t ON tt.term_id = t.term_id
    WHERE tt.taxonomy = 'post_tag' AND t.name = 'Opinión'
)

El exportador: PHP hablando directamente con la base de datos

El script export-wp-to-hugo.php no usa la API REST de WordPress. Se conecta directamente a la base de datos, ejecuta la query SQL y vuelca el resultado en data/posts.json, el archivo que Hugo usa para construir cada Hub.

La razón de ir directo a la DB en lugar de usar la API es simple: velocidad y control. No hay overhead de HTTP, no hay autenticación y no hay plugins que puedan interferir. El script lee, transforma y escribe:

$row['categorias'] = explode(', ', $row['categorias']);
$row['tags'] = explode(', ', $row['tags']);

Esas dos líneas convierten los strings de GROUP_CONCAT en arrays reales, que es el formato que espera Hugo en sus templates.

El vigilante: Bash + WP-CLI + Cron

Con el exportador listo, el siguiente paso fue hacer que el servidor decidiera cuándo ejecutarlo. La solución es un script bash que actúa como vigilante y corre cada 15 minutos vía cron:

*/15 * * * * /bin/bash /home/temasadmin/auto-deploy-hugo.sh

La lógica es deliberadamente simple. El script pregunta a WordPress cuál es el ID del último artículo publicado:

ID_ACTUAL=$(wp post list --post_type=post --post_status=publish \
  --posts_per_page=1 --field=ID \
  --path=$PATH_WP --skip-plugins --skip-themes --allow-root)

Lo compara con el último ID que registró en un archivo de texto plano. Si el número creció, hay contenido nuevo. Si no, no hace nada y el servidor queda en reposo. Sin polling innecesario, sin peticiones HTTP y sin dependencias externas.

Cuando detecta un ID nuevo, ejecuta la cadena completa:

/usr/bin/php export-wp-to-hugo.php    # Actualiza el JSON desde la DB
/usr/local/bin/hugo --gc --minify           # Recompila el sitio estático
/usr/bin/rsync -avzh --delete public/ $WEB_DIR/  # Sincroniza solo lo que cambió
chown -R www-data:www-data $WEB_DIR     # Ajusta permisos
echo $ID_ACTUAL > $LAST_ID_FILE         # Registra el nuevo ID

Todo queda registrado en un log con timestamp. Si algo falla, hay trazabilidad.

El flujo completo queda así:

flowchart TD
    WP[(WordPress DB)] -- Publicas un Post --> WP
    Cron[Cron cada 15min] -- Chequea ID --> WP
    Cron -- "ID nuevo detectado" --> Export[PHP Export]
    Export --> JSON[(posts.json)]
    JSON --> Hugo[Hugo Build]
    Hugo --> Deploy[rsync a /temas/]
    Deploy --> Live((Sitio Actualizado))
    

Lo que cambió en la práctica

Antes publicaba un artículo y tenía que volver al ordenador a actualizar los Hubs. Ahora publico desde donde sea, el móvil, una tablet, cualquier navegador, y en menos de 15 minutos los Hubs se actualizan solos, con el artículo correctamente asignado a su Hub, con sus metadatos completos y sin que yo haya tocado nada más.

La arquitectura híbrida no es solo separar el frontend del backend. Es crear un ecosistema donde cada herramienta hace exactamente lo que mejor sabe hacer: WordPress gestiona el contenido, PHP extrae los datos, Hugo construye las páginas y rsync sincroniza. Nadie hace el trabajo del otro.

Y cuando todo está bien separado, automatizarlo es trivial.

Tema Relacionado:

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.