Commits

El día que producción no perdonó: dashboard, editor y estadísticas

Migración del dashboard a React, documentación de arquitectura y tres errores

El dashboard, pantalla a pantalla

Hoy he cerrado la migración del dashboard (el panel de control con las métricas del día) a React (la librería con la que se construye la interfaz) siguiendo la decisión que dejé anotada por la mañana: pantalla a pantalla, sin reescritura global y sin tocar la web pública. El panel llegaba ya encarrilado desde el día anterior (Del Resumen al editor: el panel toma forma).

Primero el listado de artículos: tenía más de doscientas líneas de JavaScript imperativo (el código que dice paso a paso cómo mover cada pieza) que seleccionaban nodos por ID y reconstruían filas a mano. Ahora es una única isla de React (un trozo de página que se gestiona solo) con los mismos estilos de siempre. Después los formularios de cuenta, luego el resumen —unas 450 líneas más de script que movían KPIs, gráficos y borradores— y por último el editor. En el editor, Milkdown (1,1 MB) va ahora en un import() dinámico: la página baja un chunk de 7 KB y la librería solo se descarga cuando se monta el editor.

Antes de desplegar pasé las cinco islas por las reglas de rendimiento de Vercel. Casi todo ya cumplía; el arreglo real fue uno: los formateadores de fechas se creaban en cada render y ahora viven a nivel de módulo.

La documentación que faltaba

He escrito docs/architecture.md: los tres contenedores (cada uno, un piso independiente para una parte de la aplicación) y sus redes, el flujo de Alembic con comandos reales, recetas de backup con pg_dump y la política de índices de pgvector. Cada dato contrastado contra las fuentes. No he creado el índice HNSW: con el volumen actual no lo justifica, y el documento recoge el disparador para cuando toque.

Producción: tres fallos encadenados

El despliegue acabó con los tests en verde y todas las rutas respondiendo 200. Y entonces llegó producción: tres fallos encadenados, como eslabones de una cadena; solo el último era nuevo.

Primero, 502 en /api: nginx (el servidor que reparte las peticiones) resuelve el proxy_pass una sola vez al cargar la configuración y cachea la IP. Al recrear el contenedor de la API cambió de IP y el proxy siguió apuntando a la vieja. Un nginx -s reload lo arregla al momento; la solución definitiva es usar una variable en la location, que se resuelve en cada petición.

Segundo, el editor no montaba: el build no emitía un CSS que el manifest sí referenciaba (bug conocido de rolldown-vite con imports dinámicos de CSS) y el precargador de Vite, al fallar ese fichero, descartaba el import() entero. Por eso los temas del editor van ahora como import estático y solo el JavaScript de Milkdown queda diferido.

Tercero, las estadísticas daban 500: el router del servidor filtra la salida hacia el rango de Cloudflare donde resolvía analytics.escandell.cat. He fijado el dominio a una IP alcanzable con extra_hosts en el compose y el flujo completo vuelve a funcionar. Techo conocido: si Cloudflare reasigna la IP, el pin habrá que actualizarlo.

La lección del día: un 502 en producción no siempre es del código que acabas de desplegar. Aquí hubo tres capas encadenadas y solo una era nueva.

Cuando el agente se pasa

Mi agente de código, buscando la causa de las estadísticas, inventó dos salidas sin consultar y una apuntaba a un contenedor de otro proyecto del servidor. Lo he corregido y hemos dejado la regla clara: si la causa es ambiental (del entorno, no del código), parar, explicar y preguntar antes de improvisar.

Fase 8: design system de verdad

Tailwind v4 integrado solo en el administrador — la web pública no recibe ni una línea. Tokens de color en un único bloque, cuatro primitivas (las piezas básicas: botón, input, panel y badge de estado) y una página de Estilos dentro de Ajustes para verlo todo vivo. Primer aprendizaje del sistema: el CSS antiguo resetea todos los márgenes, así que en componentes nuevos el espaciado tipográfico se declara siempre explícito.

Fase 9, el primer consumidor

La página de Artículos ya usa las primitivas: chips (las etiquetas pequeñas) y botones migrados. Mejora silenciosa: eliminar ahora se muestra en rojo destructivo; antes era un botón idéntico a los demás. Filtros y buscador esperan a tener un segundo consumidor.

Otras entradas del diario