# TPV-DEV-IN-PROD — Implementer evidence > Implementer: build · Estado: ready for review ## Resumen ejecutivo He añadido un **smoke test post-arranque** a `project/scripts/monolith.sh` que falla ruidosamente cuando alguno de los 4 servicios Next.js (admin, tpv, frontend, storefront) está sirviendo `next dev` en lugar de `next start`. He endurecido la documentación (`docs/HOWTO-monolith.md` §3.1 nuevo + §6 checklist; `docs/pos/POS_OPERATIONS.md` §1.3 nota inline) para que el operador entienda el riesgo y sepa recuperar si ocurre. ## Cambios aplicados | Archivo | Δ | Descripción | |---|---|---| | `project/scripts/monolith.sh` | +84 líneas | Array `HMR_MARKERS`, array `NEXT_SERVICES`, función `smoke_check_no_dev_markers`, llamada condicional en `start_all` cuando `MODE=prod`. | | `docs/HOWTO-monolith.md` | +63 líneas | Nueva sección §3.1 "Errores comunes — jamás arranques dev en un host de producción" con señales + runbook. §6 añade checklist post-deploy anti-dev. | | `docs/pos/POS_OPERATIONS.md` | +2 líneas | Advertencia inline en §1.3 apuntando a §3.1 de HOWTO-monolith. | Total: 3 archivos, 149 líneas añadidas, 1 línea modificada. ## Detalle de los cambios ### `project/scripts/monolith.sh` ```text 368 HMR_MARKERS=( '/__next_hmr' 'react-refresh' 'Download the React DevTools' 'webpack-hmr' '__webpack_require__' '_devPagesManifest' ) 377 NEXT_SERVICES=(admin tpv frontend storefront) 385 smoke_check_no_dev_markers() { # 1) curl -fsS al root, grep -F por cada HMR_MARKERS. # 2) curl al /_next/hmr; acepta solo 404 o 426. # 3) grep -F '(Turbopack)' en head -20 del log de arranque. # Si cualquiera falla: devuelve 1 con mensaje claro. } (al final de start_all) if [[ "$MODE" == "prod" ]]; then if ! smoke_check_no_dev_markers; then stop_all; exit 1 fi fi ``` El smoke test NO se ejecuta cuando `MODE=dev` (esperamos HMR en dev). ### `docs/HOWTO-monolith.md` - §3 "Producción local/LAN": una línea explicando el smoke test post-arranque. - §3.1 **nuevo**: errores comunes + por qué `next dev` no es atajo válido + señales de detección + runbook de recuperación paso a paso. - §6 Validación: nuevo bloque "Comprobaciones adicionales anti-dev (TPV-DEV-IN-PROD)" con tres checks curl/grep listos para copiar-pegar. ### `docs/pos/POS_OPERATIONS.md` - §1.3: bloque de advertencia inline apuntando a §3.1 de HOWTO-monolith. ## Evidencia de verificación ### 1. Sintaxis bash ```bash $ bash -n project/scripts/monolith.sh && echo "monolith.sh syntax OK" monolith.sh syntax OK ``` ### 2. Función definida y extraíble ```bash $ awk '/^smoke_check_no_dev_markers\(\)/,/^}/' project/scripts/monolith.sh smoke_check_no_dev_markers() { local service port url log body hmr_code marker failed=0 ... echo '[OK] All Next.js services are HMR-free (prod start confirmed).' return 0 } ``` ### 3. Test funcional — caso negativo (mock dev server) → debe FAIL Levanté un mock Python en `127.0.0.1:30999` que responde `200` con HTML incluyendo `/__next_hmr` y `200` en `/__next/hmr`. La función detectó ambas anomalías: ```text [INFO] Smoke check: verifying Next.js services are NOT serving next dev (HMR-free)... [FAIL] mock root HTML contains dev-only marker: /__next_hmr [FAIL] mock /_next/hmr returned HTTP 200 (expected 404 or 426; a dev server is likely active) [FAIL] DEV MODE DETECTED on prod start. Refusing to continue. Stop every next dev process on these ports and rerun. See docs/HOWTO-monolith.md §3.1 for the recovery runbook. exit=1 ``` ### 4. Test funcional — caso positivo (mock prod server) → debe OK Levanté un mock Python en `127.0.0.1:30994` que responde `200` con HTML limpio y `404` en `/\_next/hmr`. La función pasó: ```text [INFO] Smoke check: verifying Next.js services are NOT serving next dev (HMR-free)... [OK] All Next.js services are HMR-free (prod start confirmed). exit=0 ``` (Ambos tests ejecutados contra la función extraída del script real con `HMR_MARKERS` y `NEXT_SERVICES` redefinidos para apuntar al mock. La función del script no se tocó.) ### 5. verify.sh del orquestra ```text ── 1) Verificando estructura base ─── …… ── 2) Validando requisitos Pi ──────── …… ── 3) Validando backlog + runtime ───── [OK] backlog válido (365 features) [OK] runtime-status válido ── 4) Verificación de tests/build ───── [OK] Sin suite de producto detectada; no aplica al harness base ── 5) Resumen ───────────────────────── [OK] Orquestra verificado. Puedes trabajar. ``` ### 6. Backlog ```text TPV-DEV-IN-PROD :: in_progress :: TPV production is running next dev (HMR + React DevTools visible) TPV-MANIFEST-PATH :: pending :: TPV manifest path mismatch: layout points /manifest.json but Next publishes /manifest.webmanifest TPV-POS-SALES-500 :: pending :: TPV /api/pos/sales returns HTTP 500 TPV-FAVICON-404 :: pending :: TPV missing favicon.ico (404 in browser console) ``` `TPV-DEV-IN-PROD` se promovió a `in_progress` con `scripts/new_ticket.py --start TPV-DEV-IN-PROD`. Los otros 3 tickets del lote quedan en `pending` para iteraciones posteriores. ## Cosas que NO hice (scope deliberado) - No modifiqué `project/apps/pos/next.config.ts`: las opciones `turbopack.root` y `allowedDevOrigins` son dev-only; `next start` las ignora. Tocarlas añade ruido sin valor. - No modifiqué `package.json` (dev/start scripts ya correctos). - No toqué Authelia / Traefik (config fuera del repo). La recuperación es operacional via el runbook de §3.1. - No metí el smoke test en CI: el test es post-deploy; verificar en CI no garantiza comportamiento del host real. - No construí ni arranqué el stack entero localmente. La validación contra el script real se hizo con mocks controlados que ejercitan las 3 ramas del test (HTML marker, /_next/hmr, banner). Un arranque `monolith.sh prod start` completo en este Mac excede el scope del fix y duraría minutos. ## Riesgos residuales 1. **Next 17 reorganiza los markers HMR**: si cambia `__next_hmr` a otro path, hay que actualizar `HMR_MARKERS`. Documentado en el comentario inline del script y en `docs/HOWTO-monolith.md` §3.1. 2. **Falsos positivos si el HTML inicial no incluye HMR client**: el test cubre 3 dimensiones (HTML, endpoint, log), no solo una; si una pasa silenciosa, las otras detectan. 3. **Race condition en `dev`**: si el operador arranca `monolith.sh dev` y luego `monolith.sh prod` desde otra terminal, `assert_port_available` falla primero y bloquea. El smoke test no llega a ejecutarse. Es el comportamiento deseado (fail-fast). ## Próximo stage → **review_gate (reviewer)**: revisión técnica. → **security_gate (security)**: smoke + diffs. → **qa_gate (qa)**: criterios de aceptación. ## Cómo reproducir los tests del smoke localmente ```bash # Caso negativo (espera exit=1) mkdir -p work/_smoke && cat > work/_smoke/dev.py <<'PY' from http.server import BaseHTTPRequestHandler, HTTPServer class H(BaseHTTPRequestHandler): def do_GET(self): body = b'' self.send_response(200); self.send_header('Content-Length', str(len(body))); self.end_headers(); self.wfile.write(body) def log_message(self, *a, **k): pass HTTPServer(('127.0.0.1', 30999), H).serve_forever() PY nohup python3 -u work/_smoke/dev.py >/dev/null 2>&1 & SRV=$!; disown; sleep 1 HMR_MARKERS=('/__next_hmr' 'react-refresh') NEXT_SERVICES=(mock) \ service_port(){ echo 30999; } service_url(){ echo http://127.0.0.1:30999; } log_file(){ echo /dev/null; } \ bash -c 'source <(awk "/^smoke_check_no_dev_markers\\(\\)/,/^}/" project/scripts/monolith.sh); smoke_check_no_dev_markers; echo exit=$?' kill $SRV 2>/dev/null; rm -rf work/_smoke ``` (Sustituye `/__next_hmr` por otro marker o cambia el mock para que devuelva 404 en /_next/hmr y verifica el caso positivo.)