MÓDULO 18 · CI/CD + MONITORING EN PRODUCCIÓN · 30 MIN

El pipeline que no se puede saltar

Un workflow no protege nada por sí solo. Produce una señal —verde o rojo— y ahí termina su poder: un check en rojo y un merge son perfectamente compatibles hasta que alguien configura la regla de rama. La automatización es la mitad barata del trabajo; la mitad que de verdad cambia el comportamiento del equipo son cuatro casillas en Settings que no aparecen en ningún YAML.
01 — Las cinco capas
De la señal a la regla
CapaQué haceQué pasa sin ella
El workflowCorre lint, tests y buildNo hay señal
Status checkPublica el resultado en el PRLa señal existe, pero hay que ir a buscarla
Required status checkImpide el merge si está en rojoLa señal es una sugerencia
Require branches to be up to dateObliga a reejecutar sobre main actualVerde sobre una base que ya cambió
Do not allow bypassingAplica la regla también a los administradoresLa regla es para los demás

Las dos últimas filas son las que se olvidan, y no son adorno. La cuarta resuelve un fallo real: dos PRs pueden pasar los tests por separado y romper main al juntarse, porque ninguno de los dos se probó nunca contra el estado final. La quinta no resuelve un fallo técnico sino social — una regla que el admin puede saltarse es una regla que se salta el día que hay prisa, que es exactamente el día que importa.

02 — El detalle que rompe el montaje
El nombre del job es el nombre del check
jobs:
  lint:
    name: Lint          # ← esto es lo que sale en la lista de checks requeridos
    runs-on: ubuntu-latest

Si renombras el job, el required check que configuraste apunta a un nombre que ya no existe. Según cómo esté la regla, el PR se queda bloqueado para siempre esperando un check que nunca va a llegar, o —peor— deja de exigirse sin que nadie se entere. Es la avería más tonta de este montaje y también la más frecuente, porque el renombrado ocurre en el YAML y la regla vive en otro sitio.

// la protección de rama es configuración fuera del repositorio: no viaja en el fork ni en el clone
03 — Matrix
Varias combinaciones sin duplicar YAML
strategy:
  fail-fast: false
  matrix:
    os: [ubuntu-latest, windows-latest, macos-latest]
    node-version: [18, 20, 22]
    exclude:
      - os: windows-latest
        node-version: 18
ubuntu · 18corre
ubuntu · 20corre
ubuntu · 22corre
windows · 18excluida
windows · 20corre
windows · 22corre
macos · 18corre
macos · 20corre
macos · 22corre

Tres sistemas por tres versiones son nueve combinaciones; el exclude deja ocho jobs en paralelo. Y fail-fast: false es lo que hace útil el resultado: con el valor por defecto, el primer fallo cancela el resto de la matriz y te quedas sin saber si el problema es de Node 18 o de Windows — justo la pregunta para la que montaste la matriz.

04 — Velocidad y dependencias externas
Cache y services

Cache. La forma corta es cache: 'npm' dentro de setup-node. La manual da más control:

- uses: actions/cache@v4
  with:
    path: node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}

La clave se construye con el hash del fichero de bloqueo: si el lockfile no cambió, la clave es la misma y se restaura; si cambió, la clave es otra y se reinstala. Cachear por rama o por fecha —que es lo intuitivo— es la forma clásica de servir dependencias obsoletas a un pipeline que las declara actualizadas.

Services. Levantan contenedores junto al job para los tests de integración:

services:
  postgres:
    image: postgres:15
    ports: ['5432:5432']
    options: >-
      --health-cmd pg_isready
      --health-interval 10s
      --health-retries 5

El bloque options no es decorativo. Sin health check, el job arranca los tests antes de que Postgres acepte conexiones y falla de forma intermitente — el peor modo de fallo posible, porque se reejecuta, pasa, y el equipo aprende a reejecutar en vez de a leer.

05 — Concurrencia
No pagar por pipelines que ya no importan
concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

Alguien empuja tres commits seguidos a un PR. Sin esto corren tres pipelines completos y solo importa el resultado del último. Con esto, los dos primeros se cancelan en cuanto llega el siguiente.

Auditoría El enunciado del reto exige que «el pipeline falle si el coverage baja del 80 %» y entrega un jest.config.js cuya clave es coverageThresholds, en plural. ¿Es ese el nombre que lee Jest, o el que lee es coverageThreshold en singular? Conviene comprobarlo antes de dar el ejercicio por resuelto, porque la diferencia no es cosmética: una opción desconocida en la configuración de Jest no produce ningún error, se ignora en silencio. Si el nombre fuese el equivocado, el pipeline pasaría en verde con un 12 % de cobertura y el criterio que vale 15 puntos del reto no estaría comprobando absolutamente nada.
Para hacer
Cinco casillas, y luego intentar saltárselas

Sobre un repositorio tuyo, en este orden: lint automático en cada PR; tests en cada PR; los dos declarados como required status checks en main; require branches to be up to date; y do not allow bypassing, contigo incluido.

Cuando termines, la comprobación que vale: abre un PR con un test roto a propósito e intenta mergearlo. Si puedes, alguna de las cinco no está donde creías — y ahora sabes cuál mirar.