Files
mercadodevida/docs/HOWTO-monolith.md

11 KiB
Raw Permalink Blame History

HOWTO — operar MercadoDeVida en desarrollo y producción local

Esta guía levanta el monolito completo en una máquina y lo publica en la red local (LAN):

Servicio Puerto Descripción
Backend 3000 API Fastify, health y Swagger
Frontend 3003 Tienda principal para clientes
Admin 3004 Backoffice
Storefront 3005 Storefront SEO/ISR
PostgreSQL 5432 Base de datos de infraestructura
Redis 6379 Cache/infraestructura

El punto de entrada operativo es:

cd /ruta/al/repo/project
./scripts/monolith.sh <dev|prod> <comando>

Comandos disponibles:

start    instala, migra y levanta todos los servicios
restart  detiene los procesos gestionados y hace start completo
status   muestra PID, estado del proceso, HTTP y URL LAN
stop     detiene los cuatro procesos HTTP gestionados
logs     sigue todos los logs; Ctrl-C deja servicios ejecutándose
urls     imprime URLs locales y LAN sin cambiar procesos

Los PID y logs se guardan en project/.runtime/<modo>/ y no se versionan.


1. Requisitos

  • Node.js 22 o superior.
  • npm.
  • Docker Desktop/Engine con docker compose.
  • Puertos libres: 3000, 3003, 3004, 3005.
  • Para acceso desde otro dispositivo: ambos equipos en la misma LAN y firewall permitiendo conexiones entrantes a esos puertos.

Comprobar:

node --version
docker --version
docker compose version

El script crea project/.env sólo si falta, usando valores locales de desarrollo. Para una instalación real de producción, proporcionar un .env propio y secretos externos al repositorio.

Variables importantes del backend:

DATABASE_URL=postgres://...
REDIS_URL=redis://...
HOST=0.0.0.0
PORT=3000
COOKIE_SECURE=false

COOKIE_SECURE=false sólo es apropiado para HTTP dentro de una LAN de confianza. En producción pública debe usarse HTTPS y COOKIE_SECURE=true detrás de un reverse proxy.


2. Desarrollo

Levantar

cd project
./scripts/monolith.sh dev start

El comando comprueba puertos, levanta PostgreSQL/Redis, instala dependencias, aplica migraciones y arranca backend y las tres aplicaciones Next con binding LAN.

Reiniciar

./scripts/monolith.sh dev restart

Estado

./scripts/monolith.sh dev status

status muestra PID, proceso, HTTP y URL; devuelve código distinto de cero si algún servicio falla.

Logs

./scripts/monolith.sh dev logs

Logs individuales:

tail -F .runtime/dev/backend.log
tail -F .runtime/dev/admin.log
tail -F .runtime/dev/frontend.log
tail -F .runtime/dev/storefront.log

Detener

./scripts/monolith.sh dev stop

PostgreSQL y Redis continúan. Para detenerlos:

docker compose stop postgres redis

Para eliminar contenedores conservando volúmenes:

docker compose down

No usar docker compose down -v salvo que se quiera eliminar la base local.


3. Producción local/LAN

Este modo ejecuta builds optimizados y next start. Es adecuado para una máquina persistente en una LAN. No sustituye HTTPS, supervisor del sistema, backups ni reverse proxy de una producción pública.

Build + migraciones + levantar

cd project
./scripts/monolith.sh prod start

Usa npm ci, aplica migraciones, construye backend/admin/frontend/storefront y arranca todo en 0.0.0.0. Al finalizar ejecuta un smoke test post-arranque que verifica que ninguno de los servicios Next está sirviendo modo dev (sin HMR, sin react-refresh, sin banner (Turbopack)). Si detecta un dev server activo, aborta el arranque con un mensaje claro.

Redeploy / reinicio completo

cd project
./scripts/monolith.sh prod restart

Detiene únicamente PIDs registrados en .runtime/prod/; nunca usa pkill global. Si un puerto está ocupado por un proceso ajeno, falla y muestra el PID.

3.1 Errores comunes — jamás arranques dev en un host de producción

Regla de oro: en cualquier host expuesto al público (sea LAN, VPS o servidor de tienda), usa siempre ./scripts/monolith.sh prod. El modo dev está reservado para desarrollo local en 127.0.0.1.

Por qué next dev no es un atajo válido en producción

  • Expone internals: el bundle del cliente incluye react-refresh, __webpack_require__, el cliente HMR y los mensajes "Download the React DevTools". Cualquier visitante ve el código fuente sin minificar.
  • Hackeable: el endpoint /_next/hmr queda abierto y acepta WebSocket upgrades. Esto filtra nombres de archivos del servidor, permite lecturas no autenticadas del árbol de fuentes y rompe asunciones de seguridad (CSP, cookies Secure, CORS).
  • Rota el PWA: Chrome exige un manifest.webmanifest válido. next dev no genera el manifest correctamente en algunos setups y dispara errores CORS cuando un auth-proxy (Authelia) intenta proteger la ruta.
  • Rendimiento ~10× peor: Turbopack compila cada request bajo demanda y mantiene cachés en memoria que pueden llegar a GB. Sin monitorización, esto degrada el TPV hasta hacerlo inutilizable en horas punta.
  • Rompe el ciclo de release: los artefactos .next/ de dev no son válidos para next start. Si mezclas dev y start en el mismo árbol, los reinicios de prod fallan silenciosamente porque los PIDs no se registran.

Señales de que estás corriendo dev por accidente

  • El log del servicio dice ▲ Next.js 16.3.1 (Turbopack) — la coletilla (Turbopack) solo aparece en next dev.
  • La consola del navegador muestra intentos de WebSocket a /_next/hmr y mensajes [HMR] connected repetidos.
  • Verás Download the React DevTools al cargar cualquier página.
  • lsof -nP -iTCP:3002 -sTCP:LISTEN muestra un PID de node .../next/dist/bin/next dev en lugar de next start.

Runbook de recuperación rápida (si ya estás sirviendo dev en prod)

# 1) Identificar el proceso dev en :3002 (TPV) y :3001 (admin)
lsof -nP -iTCP:3002 -sTCP:LISTEN
lsof -nP -iTCP:3001 -sTCP:LISTEN

# 2) Detenerlo (sustituye <PID> por el del paso anterior)
kill -TERM <PID>

# 3) Confirmar que el puerto queda libre
lsof -nP -iTCP:3002 -sTCP:LISTEN  # debe devolver nada

# 4) Limpiar runtime stale (incluye dev huérfano)
rm -f project/.runtime/dev/*.pid

# 5) Arrancar prod (con smoke test post-arranque)
cd project && ./scripts/monolith.sh prod start

# 6) Confirmar: el log dirá "▲ Next.js 16.3.1" SIN "(Turbopack)"
tail project/.runtime/prod/tpv.log
# debe terminar con la línea de Ready y NADA de WebSocket /_next/hmr

Si el smoke test falla igualmente, abre un ticket: hay otro proceso (posiblemente externo al script) ocupando el puerto y monolith.sh no puede broad-kill por seguridad.

Estado, logs y stop

./scripts/monolith.sh prod status
./scripts/monolith.sh prod logs
./scripts/monolith.sh prod stop
./scripts/monolith.sh prod urls

Logs individuales:

tail -F .runtime/prod/backend.log
tail -F .runtime/prod/admin.log
tail -F .runtime/prod/frontend.log
tail -F .runtime/prod/storefront.log

4. Acceso desde otro dispositivo de la LAN

IP detectada actualmente:

192.168.18.93

URLs:

  • Tienda principal: http://192.168.18.93:3003/
  • Backoffice: http://192.168.18.93:3004/
  • Storefront SEO: http://192.168.18.93:3005/
  • Backend health: http://192.168.18.93:3000/health
  • Swagger/OpenAPI: http://192.168.18.93:3000/docs

La IP DHCP puede cambiar. Consultar la actual:

./scripts/monolith.sh prod urls

Forzar una IP concreta:

LAN_IP=192.168.18.93 ./scripts/monolith.sh prod restart

Diagnóstico desde el host:

curl -i http://127.0.0.1:3000/health
curl -I http://192.168.18.93:3003/

Desde el otro dispositivo:

curl -i http://192.168.18.93:3000/health

Si local funciona pero LAN no:

  1. confirmar status con HTTP 200;
  2. confirmar que ambos dispositivos están en la misma red y no una guest aislada;
  3. permitir conexiones entrantes de Node/Docker en el firewall;
  4. comprobar que el router no tiene client isolation;
  5. inspeccionar listeners:
lsof -nP -iTCP -sTCP:LISTEN | grep -E ':(3000|3003|3004|3005)\b'

5. Infraestructura y base de datos

docker ps
docker compose ps
docker exec mdv-dev-postgres pg_isready -U mdv -d mercadodevida
npm run db:status

Aplicar migraciones manualmente:

node --env-file-if-exists=.env node_modules/node-pg-migrate/bin/node-pg-migrate.js up --migrations-dir migrations

Logs:

docker logs -f mdv-dev-postgres
docker logs -f mdv-dev-redis

6. Validación antes/después del deploy

Desde la raíz:

./scripts/verify.sh

Desde project/:

npm run lint
npm run lint:boundaries
npm run typecheck
npm test
npm run build
(cd apps/admin && npm run lint && npm run typecheck && npm run build)
(cd frontend && npm run lint && npm run build)
(cd storefront && npm run lint && npm run typecheck && npm run build)

Smoke test:

./scripts/monolith.sh prod status
curl -fsS http://127.0.0.1:3000/health
curl -fsS -o /dev/null http://127.0.0.1:3003/
curl -fsS -o /dev/null http://127.0.0.1:3004/
curl -fsS -o /dev/null http://127.0.0.1:3005/

Comprobaciones adicionales anti-dev (TPV-DEV-IN-PROD):

# 1) El log del TPV NO debe contener "(Turbopack)"
! grep -F '(Turbopack)' project/.runtime/prod/tpv.log

# 2) /_next/hmr debe devolver 404 o 426 (no 200/101/405)
code=$(curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:3002/_next/hmr)
[[ "$code" == "404" || "$code" == "426" ]]

# 3) El HTML inicial NO debe contener marcadores de dev
! curl -fsS http://127.0.0.1:3002/ | grep -qE '/__next_hmr|react-refresh|Download the React DevTools'

Repetir para :3001 (admin), :3003 (frontend) y :3004 (storefront) si también están en producción. Si cualquiera falla, sigue el runbook de §3.1 antes de continuar.


7. Problemas frecuentes

Puerto ocupado por proceso no gestionado

lsof -nP -iTCP:3000 -sTCP:LISTEN
ps -p <PID> -o pid,ppid,command

Detenerlo explícitamente y repetir. El script no mata procesos no registrados.

Backend no arranca

tail -100 .runtime/prod/backend.log
docker exec mdv-dev-postgres pg_isready -U mdv -d mercadodevida

Revisar DATABASE_URL en .env.

Un frontend usa localhost desde otro dispositivo

LAN_IP=<IP_DEL_HOST> ./scripts/monolith.sh prod restart

NEXT_PUBLIC_API_URL se incorpora durante el build, por lo que cambiar IP requiere reconstruir producción.

Cambiar puertos

BACKEND_PORT=3100 FRONTEND_PORT=3103 ADMIN_PORT=3104 STOREFRONT_PORT=3105 \
  ./scripts/monolith.sh prod restart

Usar los mismos overrides en todos los comandos posteriores.