Skip to content

Commit 99c9a07

Browse files
committed
docs(runbook): runbook operativo del equipo
1 parent 92accd3 commit 99c9a07

1 file changed

Lines changed: 109 additions & 0 deletions

File tree

‎docs/RUNBOOK.md‎

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Runbook operativo — Seafood Intelligence OS (Pescamar)
2+
3+
Guía de operación para el equipo técnico de apoyo (no desarrollo diario). Todo lo descrito aquí está verificado contra el repositorio; ante duda, manda `db/README.md`, `PILOT_ACCEPTANCE.md` y este documento, en ese orden de autoridad de esquema.
4+
5+
## 1. Arquitectura en 5 líneas
6+
7+
1. Front-end: SPA Vite (React 19, entradas ES/EN) servida desde Vercel; enrutamiento SPA vía `vercel.json`.
8+
2. Back-end: ~111 funciones serverless en `api/*.ts` (TypeScript, Node) desplegadas como Vercel Functions; sesión por cookie firmada contra PostgreSQL.
9+
3. Datos: PostgreSQL 18 en Neon (proyecto `pescamar-control`, región aws-us-east-2, rama `main`), acceso único vía `DATABASE_URL`.
10+
4. Despliegue: cada push a `main` dispara CI (GitHub Actions, workflow `Quality`) y, en verde, Vercel despliega `main` automáticamente a Producción.
11+
5. Modelo operativo: capturar una vez → confirmar lo físico → automatizar lo repetitivo → escalar excepciones → decidir con evidencia. Ver `README.md` y `ROADMAP.md`.
12+
13+
## 2. Verificación de salud
14+
15+
Hay tres capas independientes. Verde total = las tres verdes. Ante rojo, alertar al responsable técnico.
16+
17+
**(a) Monitor externo de liveness.** Corre cada hora desde fuera de Vercel (a la hora en punto ~:17, America/Santiago) y alerta ante caída; usa el header de bypass de protección de despliegue configurado a nivel proyecto Vercel (`x-vercel-protection-bypass`, valor solo en el proyecto, nunca en el repo). Complemento interno: la app expone un cron horario en Vercel (`/api/cron-operational-health`, minuto :17, autenticado con `Authorization: Bearer $CRON_SECRET`) que mide salud operacional desde dentro de la plataforma. Replica humana del probe:
18+
19+
```bash
20+
curl -s https://<dominio-producción>/api/public-health
21+
# Esperado: HTTP 200 {"ok":true,"service":"pescamar","status":"live","healthVersion":"pescamar.public-health.v1","checkedAt":"..."}
22+
```
23+
24+
**(b) Vista en sitio `/admin/salud`** (solo admin, sin entrada de navegación; URL directa). Verde = panel "Build y entorno" con `Entorno: production` y `Commit` igual al SHA desplegado, y chip de liveness en **Operativa** (HTTP 200, latencia en ms, `checkedAt` fresco; se repolle cada 30 s con tu sesión).
25+
26+
**(c) Telemetría de pilotaje `/admin/pilotaje`** (solo admin) + gate de esquema. Verde = los cuatro paneles cargan sin banner de error (rutas 7/30 d, operadores activos 14 d, últimos eventos, feedback Seafood AI) y `/api/schema-preflight` reporta `pilotGate: pass`. Un `hold` del gate indica esquema no reconciliado (ver §3).
27+
28+
## 3. Aplicar migraciones
29+
30+
**No existe endpoint que aplique migraciones.** El flujo canónico (`db/README.md`): ejecutar los archivos de `db/migrations/` en orden ascendente por prefijo, con parada inmediata ante error:
31+
32+
```bash
33+
for migration in $(find db/migrations -maxdepth 1 -name '*.sql' | sort); do
34+
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f "$migration" || exit 1
35+
done
36+
```
37+
38+
En Neon, la práctica segura es crear una rama efímera desde `main`, aplicar ahí, verificar y borrarla; para producción, aplicar solo el archivo nuevo contra `main` (los archivos son idempotentes: `if not exists` / `on conflict do nothing`). Registros innegociables:
39+
40+
- `baseline` / `reconciled` / `applied` tienen semántica distinta; **nunca** inventar `applied_at`: `applied` exige timestamp real del momento de ejecución.
41+
- Cada migración nueva se registra sola vía el trailer `insert into schema_migrations ...` del archivo.
42+
- Después de aplicar: verificar `select migration_name,evidence_kind,applied_at from schema_migrations order by 1` y el `pilotGate` de `/api/schema-preflight` (debe pasar a `pass`).
43+
- CI protege el SQL de los endpoints de solo lectura con `scripts/endpoint-sql-contracts.mjs` (rama efímera + migraciones + consultas canónicas); un SQL inválido falla CI antes de desplegar, pero **aplicar la migración en Neon sigue siendo manual**.
44+
45+
## 4. CI/CD
46+
47+
Workflow `Quality` (`.github/workflows/quality.yml`), corre en cada push a `main` y en PRs hacia `main`, `feat/**` y `docs/**`:
48+
49+
1. `npm ci` → `npm run quality` (lint 0 warnings + typecheck + build).
50+
2. ~39 contratos en `scripts/*.mjs` (inventario de migraciones, contratos de UI, contrato SQL de endpoints contra Neon efímero).
51+
3. Playwright desktop + mobile (Chromium) con artefactos de evidencia adjuntos al run.
52+
53+
**Regla same-SHA:** un cambio solo se considera desplegado cuando CI está en `success` **y** Vercel reporta `Deployment has completed` **sobre el mismo SHA**. Comprobar: API de GitHub Actions (`actions/runs?branch=main`) y Deployments del repo.
54+
55+
**Rollback:** `git revert <sha> && git push` — Vercel despliega `main` automáticamente. Ojo: el rollback de código no revierte datos; las migraciones aplicadas en Neon no tienen rollback automático.
56+
57+
**Política de flakes:** hay specs conocidos como flaky ocasional (p. ej. rollforward, browser-smoke, supplier-reception-decision); CI reintenta una vez (`retries: 1`) y un flake que pasa en retry es aceptable. Si un test falla dos veces seguidas, tratarlo como fallo real e investigar **antes** de volver a pushear. Nunca dejar `main` rojo a propósito; un push rojo se corrige de inmediato o se revierte.
58+
59+
**Logs:** GitHub Actions → run → job `quality` (steps y artefactos `browser-release-evidence`). Logs de runtime de funciones: dashboard Vercel → proyecto → Logs.
60+
61+
## 5. Secretos y variables de entorno
62+
63+
Nombres exactos referenciados en el código/config. **Nunca** pegar valores en docs, tickets ni chats.
64+
65+
| Nombre | Dónde | Propósito | Rotación |
66+
|---|---|---|---|
67+
| `DATABASE_URL` | Vercel (producción) | Conexión única a Neon | Desde Neon (reset password de rol); actualizar en Vercel |
68+
| `ADMIN_SETUP_TOKEN` | Vercel | Activación del primer admin (`/api/bootstrap`); exige ≥24 chars | Rotar tras la activación inicial; luego altas solo con identidad admin individual |
69+
| `CRON_SECRET` | Vercel | Bearer del cron interno `/api/cron-operational-health` (minuto 17) | Anual o ante sospecha |
70+
| `OPENAI_API_KEY` | Vercel | Seafood AI (respuestas) | Según política del proveedor |
71+
| `OPENAI_VISION_MODEL` | Vercel | Modelo de visión usado por reception/Uni Vision provenance | Con el modelo |
72+
| `CEO_OPERATOR_EMAIL` | Vercel | Correo que identifica al operador CEO (experiencia ejecutiva); tiene default en código | Si cambia el CEO |
73+
| `PLANT_EXECUTION_WRITES_ENABLED` | Vercel | Kill-switch de escrituras Plant Execution (default activo solo en producción) | No aplica |
74+
| `COLD_SENSOR_INGEST_SECRET` | Vercel | Ingesta machine-to-machine de sensores de frío (fail-closed) | Anual o ante exposición |
75+
| `WHATSAPP_WEBHOOK_SECRET` | Vercel | Verificación del webhook de WhatsApp | Anual o ante exposición |
76+
| `VERCEL_GIT_COMMIT_SHA` | Vercel (automática) | SHA expuesto en `/api/status` (panel de despliegue) | Automática |
77+
| `NEON_API_KEY` | GitHub (secret, **pendiente**) | Contrato SQL de endpoints: crear/borrar ramas efímeras | Al configurar y anual |
78+
| `NEON_PROJECT_ID` | GitHub (variable, **pendiente**) | `icy-union-17389410` | Estable |
79+
80+
Notas: no existen `AI_GATEWAY_API_KEY` ni `AUTH_BYPASS` en el código — cualquier referencia a ellos es inventada. La protección de despliegue de Vercel (bypass header) se configura a nivel proyecto, fuera del repo.
81+
82+
## 6. Base de datos (Neon)
83+
84+
- Proyecto `pescamar-control` (aws-us-east-2, PostgreSQL 18), plan **free** al momento de escribir.
85+
- Plan free: **sin backups programados** y ventana de restauración a punto-en-el-tiempo (PITR) de **6 horas** (`history_retention`). La rama `main` **no está protegida** (admite push/reset directo).
86+
- Recomendación: subir el plan de Neon para tener backups programados y protección de rama `main`; hasta entonces, como práctica manual: **exportar antes de cualquier cambio riesgoso** (`pg_dump` o consulta de evidencia previa) y usar **ramas efímeras para experimentos** (nunca probar contra `main`).
87+
- `pg_stat_statements` está instalado (v1.12): para consultas lentas, revisar la consola de Neon (Monitoring / query insights) o `select * from pg_stat_statements order by total_exec_time desc limit 20;` desde el SQL editor.
88+
- Consumo/límites del plan en la consola de Neon → proyecto → Usage.
89+
90+
## 7. Incidentes — escalera de triage
91+
92+
**App caída / usuarios no entran:**
93+
- [ ] ¿El monitor externo alertó? Replicar el probe humano de §2(a): ¿HTTP 200 + `status: live`?
94+
- [ ] Abrir `/admin/salud` con sesión admin: ¿chip Operativa? ¿`Commit` = SHA esperado?
95+
- [ ] ¿CI y Vercel en verde sobre el mismo SHA? (Deployments del repo). Si el último deploy es sospechoso → `git revert <sha> && git push`.
96+
- [ ] ¿Caída solo de la API con SPA viva? Logs de funciones en Vercel → Logs; buscar errores 500 recientes por ruta.
97+
- [ ] ¿Base de datos? `/api/schema-preflight` (¿DATABASE_URL caída da 503), consola Neon → estado del proyecto.
98+
99+
**Datos / esquema:**
100+
- [ ] Verificar `schema_migrations` vs `db/migrations/` (deben coincidir exactamente; `db/README.md`).
101+
- [ ] Pérdida de datos: restauración solo dentro de la ventana PITR de 6 h (crear rama desde punto-en-el-tiempo en Neon y extraer; fuera de ventana, no hay copia). Por eso el export manual antes de cambios riesgosos.
102+
103+
**Comunicación:** registrar hora (America/Santiago), SHA afectado, y avisar al responsable técnico con lo verificado arriba (hechos, no hipótesis).
104+
105+
## 8. Gobernanza
106+
107+
- **UX congelado post-piloto** (veredicto y evidencia en `docs/POST-PILOT.md`): no se iteran superficies de producto visibles por ahora.
108+
- **Criterio de reactivación UX:** la fricción medida con evidencia en `/admin/pilotaje` (rutas visitadas, operadores activos, feedback Seafood AI) es la fuente que dispara la reactivación; cuando baje sostenidamente, se reactiva la iteración. La telemetría registra solo la ruta (nunca query strings) y el rol siempre se toma de la sesión en el servidor; no contiene PII.
109+
- Cambios de esquema: solo vía migraciones versionadas en `db/migrations/` (§3). Cambios de código: siempre con la cadena CI completa en verde (§4).

0 commit comments

Comments
 (0)