P4 · Tercer sprint: entrega continua

Publicado: semana 11 · Entrega: semana 14 (demo del sprint 3) · En equipo · Tag: p4

En el sprint 3 el producto sale a producción, y sale solo: cada pull request pasa por un pipeline que impide integrar código roto, y cada merge a main se despliega sin pasos a mano. El equipo además observa su aplicación en producción (/health y logs estructurados), cuida sus dependencias y aprende de un incidente con un postmortem.

Cubre las semanas 12 (integración continua, build, dependencias y cadena de suministro), 13 (contenedores, entornos, observabilidad, incidentes y postmortems) y 14 (desarrollo con agentes). La revisión de seguridad con OWASP Top 10 viene en P5, después de la semana 15. La revisión de P4 vuelve a correr las de P1 a P3.

1. Qué entregan

Archivo Qué es
.github/workflows/*.yml El pipeline de GitHub Actions
Dockerfile (y .dockerignore) La imagen de producción
GET /health Un endpoint de salud
Logs estructurados Una línea JSON por evento en stdout
README.md, ## Despliegue La URL de producción y cómo se despliega
docs/postmortems/AAAA-MM-DD-tema.md Al menos un postmortem
docs/dependencias.md y package-lock.json La política de dependencias y el lockfile
docs/sprints/sprint-3.md Planificación, revisión (demo) y retrospectiva del sprint 3

El pipeline (.github/workflows/)

Uno o más workflows de GitHub Actions que:

  1. Se disparan con pull_request y corren un job con npm run check (el mismo de P3: tipos, linter, pruebas con cobertura) y con timeout-minutes: 10: un proceso colgado no falla, y sin este límite GitHub espera 360 minutos (semana 12). Si su revisión de verdad tarda más, súbanlo hasta 30 como máximo. Activen en GitHub la regla de protección de main que exige ese check en verde para el merge (Require status checks to pass). En un repositorio privado, la protección de ramas necesita GitHub Pro, gratis con GitHub Education, o una organización con GitHub Team; sin ella, el equipo no integra un pull request con el pipeline en rojo, y el docente lo mira en la historia de GitHub Actions.
  2. En cada push a main despliegan, en un job cuyo nombre o id dice deploy (o despliegue) y que tiene needs: con el job de revisión: no se despliega lo que no pasó. Si el mismo workflow también corre en pull_request, el job de deploy lleva if: github.event_name == 'push' && github.ref == 'refs/heads/main', o desplegaría cada pull request.

En GitHub Actions no está el contenedor del curso: el job instala las herramientas con npm ci (por eso las versiones de desarrollo van en package.json y en el lockfile, ver Dependencias) y usa actions/setup-node con node-version: 26.

Dockerfile

  • Imagen base node:26 (por ejemplo node:26-trixie-slim), como el contenedor del curso.
  • Copia solo lo que hace falta para correr (src/, public/, package.json); nunca .env. Un .dockerignore excluye al menos node_modules y .git.
  • Corre como un usuario que no es root (USER node).
  • Termina con CMD (o ENTRYPOINT) que levanta la aplicación, que lee PORT y DB_PATH como en P2. Si los datos tienen que sobrevivir a un despliegue, van en un volumen.

La revisión no construye la imagen (no hay Docker dentro del contenedor del curso): la construyen en el pipeline y en su computadora con docker build -t app . && docker run -p 3000:3000 app.

GET /health

Responde 200 con un JSON que tiene un campo status ({"status": "ok", ...}) cuando la aplicación puede atender: idealmente revisa también la base de datos y responde 503 si no responde. Lo usan el hosting, el pipeline (prueba de humo después del deploy) y un monitor externo. Agréguenlo a docs/api/openapi.yaml.

Logs estructurados

La aplicación escribe en stdout una línea JSON por evento, con al menos la hora (time o timestamp), el nivel (level) y el mensaje (msg o message), más los campos que sirvan (método, ruta, código de estado, duración):

{"time":"2026-06-16T19:25:04.022Z","level":"info","msg":"request","method":"POST","path":"/api/orders","status":409,"durationMs":6}

Sin datos personales ni secretos en los logs (ni tokens, ni contraseñas, ni el celular de un cliente).

README.md: ## Despliegue

La URL https de producción (la revisión intenta URL/health), dónde está desplegada, cómo llega un cambio a producción, cómo se vuelve a la versión anterior (rollback), dónde están los logs y, si hay base de datos, cómo se respalda.

Dónde desplegar lo deciden ustedes en un ADR. Los planes gratuitos cambian seguido: revisen en el momento qué ofrece cada servicio (contenedores, disco persistente, si la aplicación se apaga sin tráfico, si pide tarjeta) y si la universidad tiene un servidor para proyectos. Si el plan gratuito no guarda archivos entre despliegues, SQLite pierde los datos: es una decisión de diseño, no un detalle.

docs/postmortems/

Al menos un postmortem sin culpables, como en la semana 13, en AAAA-MM-DD-tema.md. Puede ser de un incidente real del sprint (lo más probable: algo se rompe en producción) o de un game day: un incidente que el equipo provoca a propósito en un entorno controlado (apagar la base de datos, llenar el disco, desplegar una versión rota) para ver cómo responde. Al inicio dice Tipo: incidente real o Tipo: game day, y tiene estas secciones:

  • ## Resumen: qué pasó, en tres frases.
  • ## Impacto: a quién afectó, cuánto tiempo, cuántos pedidos o usuarios, con datos (de los logs).
  • ## Línea de tiempo: con horas, desde que empezó hasta que se cerró, incluida la detección.
  • ## Causa raíz: por qué pasó y por qué el proceso (pruebas, revisión, monitoreo) no lo detectó antes.
  • ## Acciones: qué se cambia, quién y para cuándo, con su estado.

Dependencias: docs/dependencias.md y package-lock.json

  • package-lock.json está en el repositorio y corresponde a package.json (si agregan, quitan o cambian una versión, regeneran el lockfile en el contenedor con npm install --package-lock-only, que necesita red). Las herramientas de desarrollo que usa el pipeline (TypeScript, ESLint, typescript-eslint, fast-check, @types/node) van en devDependencies, con las mismas versiones del contenedor del curso.
  • docs/dependencias.md con estas secciones:
    • ## Cómo elegimos una dependencia: las preguntas que se responden antes de agregar un paquete (¿lo resuelve Node?, ¿quién lo mantiene?, ¿existe de verdad con ese nombre?, ¿licencia?).
    • ## Versiones y lockfile: versiones exactas o rangos, y cómo se instala en el pipeline (npm ci).
    • ## Auditoría: cuándo y cómo se corre npm audit y qué se hace con lo que encuentra.

Sin vulnerabilidades altas ni críticas en las dependencias de producción (npm audit --omit=dev). Nunca suban .env ni llaves: la revisión los busca en los archivos del repositorio.

docs/sprints/sprint-3.md

Como los anteriores (## Planificación, ## Revisión, ## Retrospectiva). La demo de la semana 14 es con la aplicación en producción.

2. Revisión automática

Dentro del contenedor del curso, desde la carpeta del repositorio del equipo, clonado dentro de la carpeta del curso (P1, sección 1):

cd /work/equipo-nombre
make -f /work/projects/p4-entrega-continua/Makefile check

Corre las reglas de P1 a P3 y estas:

Regla Qué revisa
P4-WORKFLOW-PR Un workflow con pull_request cuyo job corre npm run check (o make check), con timeout-minutes entre 1 y 30
P4-WORKFLOW-DEPLOY Un job deploy con needs: en un workflow que corre en push a main; si ese workflow también corre en pull_request, el job tiene un if: que lo limita a main
P4-DOCKERFILE Dockerfile con FROM, CMD o ENTRYPOINT y sin copiar .env; avisa si la imagen no es node:26, si corre como root o si falta .dockerignore
P4-HEALTH Con la aplicación levantada (como en P2-ESQUELETO), GET /health responde 200 con JSON y un campo status
P4-LOGS La aplicación escribe en stdout al menos una línea JSON con hora, nivel y mensaje
P4-URL ## Despliegue del README tiene una URL https; si hay red, que URL/health responda (si no responde: aviso)
P4-POSTMORTEM Al menos un postmortem con su tipo y sus cinco secciones
P4-DEPENDENCIAS docs/dependencias.md con sus tres secciones
P4-LOCKFILE package-lock.json (versión 2 o más) con las mismas dependencias y versiones que package.json
P4-AUDIT Con red: npm audit --omit=dev sin vulnerabilidades altas ni críticas (sin red: aviso)
P4-SECRETOS Ningún .env (salvo .env.example) ni llave privada en los archivos que sigue git
P4-SPRINT-3 docs/sprints/sprint-3.md con sus tres secciones y al menos una historia que existe

3. Cómo entregar

Con el tag p4, como en P1. La demo del sprint 3 es en la semana 14, con la URL de producción.

4. Consejos y errores comunes

  • Dejar el pipeline para la última semana. Es lo primero del sprint: desde que existe, cada pull request se revisa solo. Un pipeline que nunca frenó nada tampoco se probó: abran un pull request con una prueba rota y vean que no se puede integrar.
  • Desplegar a mano “por esta vez”. Si hay un paso manual, documenten por qué y automatícenlo en el sprint siguiente. Lo que llega a producción tiene que ser lo que está en main.
  • Secretos en el repositorio. Un token en el YAML o un .env en un commit quedan en la historia aunque se borren después: hay que cambiarlos. Usen los secrets de GitHub y las variables del hosting.
  • /health que siempre dice ok. Si la base de datos no responde y /health dice 200, el monitor no sirve. Que revise lo que la aplicación necesita para atender.
  • Un postmortem que busca culpables o se queda en “fue un error humano”. La pregunta útil es por qué el sistema permitió el error y por qué no se detectó antes.
  • Con IA. Un agente escribe un workflow o un Dockerfile en segundos, y a veces con acciones de terceros que no conocen, versiones que no existen o permisos de más. Lean cada línea, fijen las versiones, y no instalen un paquete sugerido sin buscarlo en npm (los nombres inventados son un ataque conocido de cadena de suministro).