Reestructuración pedida por el cliente tras revisar la primera versión. Conversión: - Formulario de 14 campos a 4: nombre, correo, teléfono y mensaje. La clasificación de riesgo ya no se pregunta, se deduce del contexto de la página y viaja oculta. Menos fricción, misma riqueza para el análisis. - El inicio deja de bifurcar y empuja al catálogo: 36 llamados desembocan ahí, y las rutas por industria y escenario pasan a ser la forma de entrar al catálogo ya filtrado, en lugar de competir como destino. - Catálogo rehecho como tienda: barra lateral con filtros, búsqueda que acepta expresiones regulares, títulos que abren la ficha, un solo botón primario por tarjeta y el comparador detrás de un desplegable. - Ficha de producto orientada al cierre: bloque en lenguaje llano sobre si el equipo corresponde al caso, y "Cotizar este equipo" con el formulario precargado. Se retiraron los enlaces que sacaban del embudo a leer normas. - Todos los llamados se unifican en "Recibir cotización". - Widget de WhatsApp: nombre, teléfono, correo y mensaje libre. El texto que escribe la persona es el que viaja a la conversación. Doble audiencia: - 13 escenarios con la iconografía original de FSPM traducen un lugar reconocible a clases de fuego y productos, para quien no domina la terminología. La capa técnica se conserva para búsqueda. - Héroe con fotografía en todas las landings. Correcciones: - El contenedor moría al arrancar porque el CLI de Prisma no podía escribir sus motores; el chown ahora incluye /app/prisma-cli. - Iconos repetidos entre escenarios. El set original no tiene icono de cocina, así que ese escenario usa un marcador tipográfico explícito. - FIREMIKS no aparecía en ningún escenario. - Los ids de campo del formulario colisionaban con dos instancias por página. - Se documentó el despliegue, que faltaba por completo. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
6.3 KiB
Despliegue
Sitio de FSPM con micro CRM. Un solo contenedor: Next.js en modo standalone y
SQLite en un volumen. No hay base de datos externa.
Variables de entorno
| Variable | Requerida | Qué pasa si falta |
|---|---|---|
DATABASE_URL |
Sí | Prisma no arranca. En el contenedor ya viene fijada a file:/app/data/fspm.db; no la cambies salvo que muevas el volumen. |
AUTH_SECRET |
Sí | El arranque falla con un error explícito. Firma las cookies de sesión del panel. |
NEXT_PUBLIC_SITE_URL |
Sí, en construcción | Las URL canónicas, el sitemap y Open Graph apuntan al dominio equivocado. Ver la advertencia de abajo. |
NEXT_PUBLIC_WHATSAPP |
Sí, en construcción | El widget de WhatsApp abre una conversación con un número vacío. |
DEMO_ACCESO_RAPIDO |
No | Con cualquier valor distinto de false, el acceso de un clic por perfil está activo en /admin/login. |
NODE_ENV |
No | Ya viene en production dentro de la imagen. |
Advertencia sobre las variables NEXT_PUBLIC_*
Next las sustituye literalmente durante next build, no las lee al arrancar.
Cambiar NEXT_PUBLIC_SITE_URL o NEXT_PUBLIC_WHATSAPP en el entorno del
contenedor no tiene ningún efecto: hay que reconstruir la imagen pasándolas
como --build-arg. Es el error más fácil de cometer aquí, y se manifiesta como
un sitemap con el dominio incorrecto y un widget de WhatsApp que no marca a nadie.
docker build \
--build-arg NEXT_PUBLIC_SITE_URL=https://fspm-demo.urieljareth.org \
--build-arg NEXT_PUBLIC_WHATSAPP=525540348395 \
-t fspm-web:demo .
Generar un AUTH_SECRET
node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"
Uno por entorno. No reutilices el de desarrollo: cualquiera que lo tenga puede firmar una cookie de sesión válida y entrar al panel como administrador.
El volumen — la parte frágil
Si no montas /app/data, cada redespliegue borra los leads capturados.
La base de demostración se hornea dentro de la imagen durante la construcción. Al arrancar, el punto de entrada la copia al volumen solo si el volumen está vacío; si ya hay una base, no la toca. Ese es el mecanismo que permite que los leads que llegan por el formulario y por el widget sobrevivan a una nueva versión de la imagen.
Sin volumen, el contenedor sigue funcionando —arranca con los datos sembrados—, así que la falla es silenciosa: nadie se entera hasta que un lead real desaparece.
En Coolify: Storages → Add → Volume Mount, con destino /app/data.
Verifícalo después del primer despliegue con:
docker inspect <contenedor> --format '{{json .Mounts}}'
Local con Docker
docker build -t fspm-web:demo .
docker run -d --name fspm-demo -p 3100:3000 \
-v fspm-datos:/app/data \
-e AUTH_SECRET="<secreto generado>" \
fspm-web:demo
curl -s http://127.0.0.1:3100/api/salud
/api/salud consulta la base, así que un volumen no escribible se reporta como
contenedor enfermo en lugar de pasar por sano.
Coolify
Los endpoints POST /applications/* de la API devuelven 404 en esta instancia
(verificado en 4.1.2 y de nuevo en 4.3.12), así que la aplicación se crea desde
la interfaz web y solo el despliegue se dispara por API.
- Crea la aplicación apuntando al repositorio de Gitea, rama
main, tipo Dockerfile. - Fija el dominio, para que Coolify no asigne uno aleatorio y devuelva 503.
- Carga las variables de entorno. Las
NEXT_PUBLIC_*van también como argumentos de construcción. - Monta el volumen en
/app/data. - Despliega y vigila el registro.
Despliegues posteriores:
curl -H "Authorization: Bearer $COOLIFY_TOKEN" \
"$COOLIFY_API_URL/deploy?uuid=<uuid de la aplicación>"
Verificación posterior al despliegue
Hazla en este orden; cada punto descarta una clase distinta de falla.
BASE=https://fspm-demo.urieljareth.org
curl -s $BASE/api/salud # {"ok":true,...}
curl -s $BASE/robots.txt | grep admin # debe prohibir /admin
curl -s $BASE/sitemap.xml | grep -c admin # debe dar 0
curl -sI $BASE/icon | grep -i content-type # image/*
curl -sI $BASE/opengraph-image | grep -i content-type
curl -s $BASE/sitemap.xml | grep -o 'https://[^<]*' | head -3 # ¿el dominio correcto?
Después, a mano:
- Envía el formulario del inicio y confirma que devuelve folio.
- Abre el widget de WhatsApp, envíalo y revisa que el enlace lleve el prefijo
[DEMO], el folio y el mensaje que escribiste. - Entra a
/admin/login—no está enlazado desde el sitio, es intencional— y usa el acceso rápido para ver el panel desde los dos roles. - Confirma que la oportunidad de los pasos 1 y 2 aparece en el panel con su campaña y su producto de interés.
- Redespliega y vuelve a entrar al panel: los leads de los pasos 1 y 2 deben seguir ahí. Si desaparecieron, el volumen no está montado.
Antes de enseñarlo a un cliente
AUTH_SECRETdistinto del de desarrollo.DEMO_ACCESO_RAPIDO=falsesi la demo queda publicada sin acompañamiento: con el acceso rápido activo, cualquiera que llegue a/admin/loginentra como administrador.- Los datos del panel son ficticios y los precios de los pedidos son inventados. Conviene decirlo en voz alta antes de abrir la pantalla de pedidos.
Notas del entorno de desarrollo
- El repositorio vive en una carpeta sincronizada a la nube. El cliente de
sincronización mueve archivos de
.nexta media compilación ynpm run buildfalla conCannot find module './NNNN.js'aunque el código esté correcto. Está reproducido. La construcción dentro de Docker no se ve afectada, porque copia el contexto y compila en el contenedor. Para verificar en local, usa la imagen, nonext build. next.config.tsaceptaNEXT_DIST_DIRpara mover el directorio de salida, pero Next lo resuelve relativo a la raíz del proyecto y no acepta rutas absolutas, así que no resuelve el problema anterior por sí solo.- El
chowndel Dockerfile incluye/app/prisma-clia propósito: al arrancar,prisma db pushreescribe sus motores dentro de su propionode_modules, y sin permiso el contenedor muere conCan't write to /app/prisma-cli/node_modules/@prisma/engines. Se detectó con el contenedor ya construido, nunca durante el build.