214 lines
11 KiB
Markdown
214 lines
11 KiB
Markdown
# 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. |