fix(tpv-dev-in-prod): tPV production is running next dev (HMR + React DevTools visible)

This commit is contained in:
Deploy
2026-08-27 23:13:10 +02:00
parent 42b0290dd2
commit 7ed78f96eb
13 changed files with 1144 additions and 244 deletions

View File

@@ -0,0 +1,214 @@
# TPV-DEV-IN-PROD — Diseño técnico
> Arquitecto: design · Estado: ready for build
## 1 · Diagnóstico (root cause)
El host de producción `tpv-mv.rikrdo.com` está sirviendo el TPV con `next dev`
(Turbopack) en lugar de `next start`. La app funciona superficialmente, pero
expone internals de desarrollo y rompe la experiencia PWA.
### 1.1 Evidencia
| Fuente | Hallazgo |
|---|---|
| Consola del navegador (`tpv-mv.rikrdo.com`) | `[HMR] connected` repetido, `web-socket.ts:50 WebSocket … /_next/hmr` fallando, "Download the React DevTools". Solo `next dev` emite HMR. |
| `project/.runtime/dev/tpv.log` | `▲ Next.js 16.3.1 (Turbopack)` ← banner exclusivo de `next dev`. 324 líneas con tráfico real. |
| `project/.runtime/prod/tpv.log` | `▲ Next.js 16.3.1` (sin Turbopack) → arranque correcto de `next start`, **5 líneas, sin PID asociado, sin tráfico posterior**. Proceso prod nunca se mantuvo. |
| `project/apps/pos/.next/BUILD_ID` | Existe (`UjpwEJ1_CRR9xKXBHOcBu`, `26 ago 23:25`). El build de prod existe; el problema es de arranque, no de build. |
| `project/apps/pos/package.json` | `dev: next dev --port 3002`, `start: next start --port 3002`. Definición correcta. |
| `project/scripts/monolith.sh` | Distingue `dev:tpv` (Turbopack) vs `prod:tpv` (no Turbopack). Lógica correcta. |
| `project/apps/pos/next.config.ts` | Tiene `turbopack: { root: __dirname }` y `allowedDevOrigins` (incluye `tpv-mv.rikrdo.com`). Estas opciones son **dev-only**; su presencia confirma que la config se diseñó pensando en dev. |
| `git log` reciente | No hay commits que toquen `monolith.sh`, `next.config.ts` o el arranque. El bug no vino de un cambio de código. |
### 1.2 Cadena causal
1. Operador del host remoto ejecutó `monolith.sh dev start` (o equivalente:
`cd project/apps/pos && npm run dev`) en lugar de `monolith.sh prod start`.
2. El proceso `next dev` se ató al puerto `3002` (único en el host).
3. Traefik enruta `tpv-mv.rikrdo.com → :3002` → recibe la app de TPV en modo dev.
4. El proceso `next start` (prod) que pudo haber existido antes fue desplazado,
o nunca se arrancó, o murió silenciosamente.
5. Resultado: prod domain sirve dev internals; HMR, React DevTools y WebSocket
a `/_next/hmr` quedan expuestos al público.
### 1.3 Por qué pasó
- El script `monolith.sh` y la doc (`HOWTO-monolith.md`) ya dicen "prod = `next start`"
y "dev = `next dev`". El **conocimiento está correcto**.
- No existe un **smoke test post-deploy** que verifique que el proceso activo es
`next start`, no `next dev`. Un operador que arranca con `dev` por confusión o
Shortcut no recibe señal hasta que aparece un bug raro (HMR, favicon, manifest).
- No hay una **advertencia explícita** que diga "dev mode en host público =
expones dev internals y rompes PWA". Las consecuencias están implícitas.
## 2 · Enfoque del fix
Refuerzo de la guardarraíl operacional en **3 capas**, todas centradas en
detectar el caso "dev corriendo donde debería ir prod" en el momento del
despliegue, no horas después en la consola del cliente.
### Capa A — Smoke test obligatorio post `monolith.sh prod start`
Añadir una función `smoke_check_no_dev_markers()` que, tras `wait_http`
para los 4 servicios Next.js (admin, tpv, frontend, storefront — backend no usa
Next), ejecute:
1. `curl -fs http://127.0.0.1:$PORT/ | grep -F ...` — grep **negativo** sobre la
respuesta HTML inicial. Falla si encuentra cualquiera de:
- `/__next_hmr`
- `react-refresh`
- `Download the React DevTools`
- `webpack-hmr`
- `__webpack_require__`
- `/_next/static/chunks/_devPagesManifest`
2. `curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:$PORT/_next/hmr`
esperar HTTP 404 o 426 en el endpoint HMR. Cualquier otro código (200, 101,
405) indica que un dev server está escuchando.
3. `grep -F '(Turbopack)' "$log"` — el log de arranque del proceso no debe
contener el sufijo `(Turbopack)`, exclusivo de `next dev`.
La función se invoca desde `start_all` después del loop `spawn_service` solo
cuando `MODE == "prod"`. Si cualquiera falla: `exit 1`, mensaje claro
"DEV MODE DETECTED ON PROD START — refusing to continue", y se eliminan los
PIDs que `monolith.sh` acaba de registrar (deja el sistema en estado limpio).
**Por qué aquí:** captura el bug en el momento del deploy, antes de que
Traefik apunte tráfico de usuarios a la instancia incorrecta.
### Capa B — Documentación endurecida
- `docs/HOWTO-monolith.md`: añadir sección **"§3.1 Errores comunes — jamás
arranques `dev` en un host de producción"** con:
- Por qué `next dev` no es un atajo válido: expone HMR, React DevTools, source
maps y rutas internas; consume ~10× memoria; no compila para prod; rompe
PWA manifest y cookies Secure assumptions.
- Comando correcto: `./scripts/monolith.sh prod start` (o `prod restart`).
- Señales de que estás en dev por accidente: el log dirá `(Turbopack)` y
verás intentos de WebSocket a `/_next/hmr` en la consola del navegador.
- `docs/pos/POS_OPERATIONS.md` §1.3 (POS app deployment): añadir una línea
inline "**Nunca** `npm run dev` ni `monolith.sh dev` en el host público —
usar siempre `monolith.sh prod`."
- `docs/HOWTO-monolith.md` §6 (Validación antes/después del deploy): añadir el
smoke test al checklist post-deploy.
### Capa C — Runbook de remediación (operacional, no código)
Añadir a `docs/HOWTO-monolith.md` un mini-runbook **"Si ya estás sirviendo dev
en prod, recuperación rápida"**:
```bash
# 1) Identificar el proceso dev en :3002
lsof -nP -iTCP:3002 -sTCP:LISTEN
# 2) Detenerlo
kill -TERM <PID>
# 3) Limpiar runtime stale (incluye dev huérfano)
rm -f project/.runtime/dev/*.pid
# 4) Arrancar prod
cd project && ./scripts/monolith.sh prod start
# 5) Confirmar: el log dirá "▲ Next.js 16.3.1" SIN "(Turbopack)"
# y el smoke test post-start debe pasar.
tail project/.runtime/prod/tpv.log
```
## 3 · Alcance (scope)
**Dentro:**
- `project/scripts/monolith.sh` — añadir `smoke_check_no_dev_markers` +
invocación en `start_all` cuando `MODE == "prod"`.
- `docs/HOWTO-monolith.md` — §3.1 + actualización de §6.
- `docs/pos/POS_OPERATIONS.md` — nota inline en §1.3.
- `work/artifacts/TPV-DEV-IN-PROD/implementer.md` con evidencia de los
comandos y outputs.
**Fuera (deliberado):**
- No se modifica `next.config.ts`: `turbopack.root` y `allowedDevOrigins` son
opciones dev-only que `next start` ignora sin warning. Tocarlas añade ruido
sin valor; el fix correcto es operacional.
- No se modifica `package.json` (dev/start scripts ya correctos).
- No se toca Authelia / Traefik (config fuera del repo). La capa C deja claro
que la recuperación es operacional.
- No se mete un check en CI: el smoke test es post-deploy; verificar en CI
no garantiza el comportamiento del host real.
## 4 · Decisiones de diseño y por qué
- **Smoke test basado en `grep -F` sobre HTML, no en introspección del proceso.**
Es black-box: no depende de flags internas de Next. Funciona aunque la versión
de Next cambie. Si Next 17 reorganiza los markers HMR, hay que actualizar el
patrón — pero el coste de un grep es despreciable y el acierto es evidente.
- **Test cubre los 4 servicios Next, no solo TPV.** El bug es sistémico:
cualquier servicio Next del repo es susceptible. Limitar al TPV dejaría
una bomba de tiempo en admin/frontend/storefront.
- **El smoke test falla ruidosamente (`exit 1`) en lugar de warn.** Warn se
ignora; un fail del deploy obliga a reaccionar. El runbook de remediación
está a 1 scroll.
- **No se automatiza el "kill dev before prod"** más allá de lo que ya hace
`clear_stale_deployments`. Automatizar un kill "inteligente" de procesos
ajenos es peligroso (false positives en puertos compartidos). Mejor detectar
y abortar.
- **Capa C (runbook) es código-en-documentos, no automatización.** Mantenerlo
como doc evita código que pueda fallar cuando el operador lo necesita.
## 5 · Acceptance criteria
1. `bash -n project/scripts/monolith.sh` → exit 0 (sintaxis válida).
2. `./scripts/monolith.sh dev start` arranca en dev y **no** ejecuta el smoke
test (es dev, HMR es esperado).
3. `./scripts/monolith.sh prod start` arranca en prod y **ejecuta** el smoke
test; pasa si los 4 servicios Next se sirvieron con `next start`.
4. Forzar `npm run dev` (en lugar del script) en el host y luego intentar
`monolith.sh prod start` → el smoke test debe **fallar** con mensaje claro.
5. `docs/HOWTO-monolith.md` contiene §3.1 con la sección de error común y el
runbook de recuperación.
6. `docs/pos/POS_OPERATIONS.md` §1.3 contiene la advertencia inline.
7. `./scripts/verify.sh` → exit 0.
8. `work/artifacts/TPV-DEV-IN-PROD/implementer.md` documenta los comandos
ejecutados y los outputs observados.
## 6 · Verificación esperada
- `bash -n project/scripts/monolith.sh` → exit 0.
- Búsqueda de los markers HMR en el HTML renderizado por un dev local:
`curl -fs http://127.0.0.1:3002/ | grep -E '/__next_hmr|react-refresh'`
→ matches (sanity check del test).
- Smoke test sobre prod local (si hay tiempo): `monolith.sh prod start`
grep -F en `/` → no matches → PASS.
- `./scripts/verify.sh` → exit 0.
## 7 · Riesgos y mitigaciones
| Riesgo | Mitigación |
|---|---|
| Next 17 reorganiza markers HMR y rompe el grep | Comentario explícito en el script listando los markers y la fuente. Si cambia, hay que actualizar el patrón — pero el cambio será visible en cualquier deploy. |
| El smoke test genera falsos positivos si el HTML inicial aún no incluye el HMR client (timing) | Reintento: si el primer `curl` no encuentra el marker pero el proceso es `next dev`, el segundo marker (`/_next/hmr` 404/426) lo detecta. Doble cobertura. |
| Operador preocupado por tiempo extra en `monolith.sh prod start` | El smoke test son 4 curls + 4 greps = <500ms. Despreciable comparado con el build. |
| Cambio en `monolith.sh` rompe otros features que dependen de su output | Smoke test solo afecta a exit code; los logs siguen imprimiendo lo mismo. `monolith.sh status`/`stop`/`logs`/`urls` no se tocan. |
## 8 · Resumen para el implementer
1. Editar `project/scripts/monolith.sh`:
- Añadir función `smoke_check_no_dev_markers()` antes de `start_all`.
- Definir array `NEXT_SERVICES=(admin tpv frontend storefront)` y array
`HMR_MARKERS=('/__next_hmr' 'react-refresh' 'Download the React DevTools'
'webpack-hmr' '__webpack_require__' '_devPagesManifest')`.
- En `start_all`, tras el loop `spawn_service`, agregar:
`[[ "$MODE" == "prod" ]] && smoke_check_no_dev_markers`.
- Si falla: `echo "[FAIL] ..." >&2; stop_all; exit 1`.
2. Editar `docs/HOWTO-monolith.md`:
- Añadir §3.1 "Errores comunes jamás arranques `dev` en un host de
producción" + runbook de recuperación.
- En §6, añadir bullet "Smoke test post-start verde (incluye verificación
HMR-free)".
3. Editar `docs/pos/POS_OPERATIONS.md` §1.3: añadir una línea de advertencia.
4. Ejecutar `bash -n project/scripts/monolith.sh` (sintaxis) y
`./scripts/verify.sh` (full).
5. Documentar todo en `work/artifacts/TPV-DEV-IN-PROD/implementer.md`.
## 9 · Próximo stage
**build (implementer)**: ejecutar §8.