Notas

El sábado más denso: editor, ingesta y un DNS que se apagó

Veinte commits: dashboard propio en el admin, botones de IA, cola de ingesta

3 min de lectura

El sábado más denso del proyecto

Veinte commits (cada punto guardado del código, con su mensaje). Fase 5 casi entera, el arranque de la 6, y un DNS que se apagó en mitad de un build (el proceso que compila toda la web). Va por orden. Si te has perdido, uno de los capítulos anteriores del diario es El migrador ya habla con el WordPress de producción.

Un dashboard propio

Vista /admin propia: cinco tarjetas (publicados del mes, pendientes, planes LaIA, suscriptores, consumo LLM) y tres accesos directos. Que se vea requirió arreglar el build dos veces: el importMap de Payload —el gestor de contenidos nuevo— se genera solo y no se commitea; sin él, las vistas custom no se registran. Y payload-types.ts está en el gitignore (la lista de archivos que Git ignora), así que el build del servidor fallaba con colecciones nuevas hasta añadir la generación al proceso.

Encima, un bug de Payload 3.89: pasar el objeto locale como clientProp a una vista client revienta la serialización RSC (el formato con el que React pasa los datos del servidor al navegador) con un Functions cannot be passed directly to Client Components. Solución: un wrapper sin use client que recibe la vista y monta el dashboard sin props.

Botones de IA e ingesta con puerta

Botones de IA contextuales en título, copdet, metaDescription y alt de cuatro colecciones, con live preview en las mismas.

La cola de ingesta (T5.6): lo que genera el agente ya no se publica solo. Llega con estatFlux=revisio, publicar es solo de admin y descartar pide motivo. En el endpoint del agente, cuatro correcciones del día: se busca por el índice HMAC de la API key (una huella de la clave, no la clave entera), no por la clave cifrada; los campos estructurados no admiten null (un $push sobre null revienta); las imágenes de revisión van a una tabla hija, porque Postgres (la base de datos) no guarda arrays en jsonb; y un cast para que el tipo de drafts no exija un flag que no usamos.

El fantasma número 418

El error de hidratación —el servidor pinta una cosa y el navegador esperaba otra— volvió: un <a> dentro de un <a> en la home, y el lang del admin. Y las vistas de edición daban 500: el árbol de dependencias tenía una versión antigua de un paquete de Payload (3.45 instalada, 3.89 exigida) y los deep imports de AiAssist la tapaban. Fuera versión fantasma, vistas vivas.

El DNS se muere en mitad del build

En mitad del build, el DNS de la máquina cayó y los builds se quedaban colgados pidiendo fuentes de Google. El DNS es la libreta de direcciones que traduce nombres en direcciones; sin él, nada. Dos parches: pre-resolver los dominios en /etc/hosts, y DNS público (8.8.8.8, 1.1.1.1) en el servicio de la app. A la media hora, todo revertido: cuando el MagicDNS de Tailscale (la red privada del servidor) vuelve, el parche es ruido. Queda un script de recovery y una lección: si payload migrate encuentra marcadores batch = -1, pide confirmación interactiva y para el deploy (la publicación al servidor). El deploy limpia esas filas antes de migrar.

Y ya de noche, publicidad

Primera fase 6: motor de zonas con rotación por peso, tracking propio de impresiones y clics (/ads/imp, /ads/click), y panel de estadísticas con página /publicitat. El detalle del día: el filtro de campañas vigentes comparaba con CURRENT_DATE, que en Postgres es UTC (la hora mundial). Una campaña vigente a las 00:30 de aquí no salía. Ahora compara con (now() AT TIME ZONE 'Europe/Madrid')::date.

Qué queda

Fase 5 casi cerrada. Fase 6, encendida. Y el servidor, con el DNS vivo de nuevo.