Desenvolupament web
El dia que producció no va perdonar: dashboard, editor i estadístiques
Migració del dashboard a React, documentació d'arquitectura i tres errors encadenats en producció: el 502 del proxy, l'editor que no muntava i les estadístiques inabastables.

El dashboard, pantalla a pantalla
Avui he tancat la migració del dashboard a React seguint la decisió que havia deixat anotada al matí: pantalla a pantalla, sense reescriptura global i sense tocar la web pública.
Primer el llistat d’articles: tenia més de dues-centes línies de JavaScript imperatiu que seleccionaven nodes per ID i reconstruïen files a mà. Ara és una única illa de React amb els mateixos estils de sempre. Després els formularis de compte, després el resum —unes 450 línies més de script que movien KPIs, gràfics i esborranys— i finalment l’editor. A l’editor, Milkdown (1,1 MB) va ara en un import() dinàmic: la pàgina baixa un chunk de 7 KB i la llibreria només es descarrega quan es munta l’editor.
Abans de desplegar he passat les cinc illes per les regles de rendiment de Vercel. Gairebé tot ja complia; l’arranjament real ha estat un: els formatejadors de dates es creaven a cada render i ara viuen a nivell de mòdul.
La documentació que faltava
He escrit docs/architecture.md: els tres contenidors i les seves xarxes, el flux d’Alembic amb ordres reals, receptes de backup amb pg_dump i la política d’índexs de pgvector. Cada dada contrastada amb les fonts. No he creat l’índex HNSW: amb el volum actual no el justifica, i el document recull el disparador per quan toqui.
Producció: tres errors encadenats
El desplegament ha acabat amb els tests en verd i totes les rutes responent 200. I llavors ha arribat producció.
Primer, 502 a /api: nginx resol el proxy_pass una sola vegada en carregar la configuració i guarda la IP a la cau. En recrear el contenidor de l’API va canviar d’IP i el proxy seguia apuntant a la vella. Un nginx -s reload ho arregla a l’instant; la solució definitiva és fer servir una variable a la location, que es resol a cada petició.
Segon, l’editor no es muntava: el build no emetia un CSS que el manifest sí referenciava (bug conegut de rolldown-vite amb imports dinàmics de CSS) i el precarregador de Vite, en fallar aquell fitxer, descartava l’import() sencer. Per això els temes de l’editor van ara com a import estàtic i només el JavaScript de Milkdown queda diferit.
Tercer, les estadístiques donaven 500: el router del servidor filtra la sortida cap al rang de Cloudflare on resolia analytics.escandell.cat. He fixat el domini a una IP assolible amb extra_hosts al compose i el flux complet torna a funcionar. Sostre conegut: si Cloudflare reassigna la IP, el pin caldrà actualitzar-lo.
La lliçó del dia: un 502 en producció no sempre és del codi que acabes de desplegar. Aquí hi havia tres capes encadenades i només una era nova.
Quan l’agent es passa
El meu agent de codi, buscant la causa de les estadístiques, va inventar dues sortides sense consultar i una apuntava a un contenidor d’un altre projecte del servidor. L’he corregit i hem deixat la regla clara: si la causa és ambiental, aturar-se, explicar i preguntar abans d’improvisar.
Fase 8: design system de veritat
Tailwind v4 integrat només a l’administrador — la web pública no rep ni una línia. Tokens de color en un únic bloc, quatre primitives (botó, input, panel i badge d’estat) i una pàgina d’Estils dins d’Ajustos per veure-ho tot viu. Primer aprenentatge del sistema: el CSS antic reinicia tots els marges, així que en components nous l’espaiat tipogràfic es declara sempre explícit.
Fase 9, el primer consumidor
La pàgina d’Articles ja fa servir les primitives: chips i botons migrats. Millora silenciosa: eliminar ara es mostra en vermell destructiu; abans era un botó idèntic als altres. Filtres i cercador esperen tenir un segon consumidor.