La lección anterior dio a Reservalia estrategias para que el relevo de versiones sea suave, pero todas se apoyan en la misma suposición optimista: que el fallo se nota mientras el despliegue está en marcha. Muchos no. Aparecen tres horas después, cuando el canary ya se promovió, o solo para los negocios de un país concreto, o solo el primer día de mes. Además arrastramos un problema anterior: hoy en Reservalia desplegar y lanzar son la misma acción, así que una funcionalidad a medias no puede llegar a main. Esta lección separa esas dos cosas con feature flags, construye el botón de vuelta atrás de Reservalia y convierte los 68 minutos de time to restore en un objetivo de diez.

Contenido

  1. Separar el despliegue del lanzamiento
  2. Tipos de feature flag, vida esperada y dueño
  3. Una implementación mínima en apps/api
  4. Fusionar código incompleto en main sin ramas de vida larga
  5. La deuda de flags: por qué caducan y cómo se retiran
  6. Rollback de artefacto frente a roll-forward
  7. El límite duro: la base de datos
  8. El botón de vuelta atrás: rollback.yml
  9. Rollback automático disparado por métricas
  10. Recuperación de incidentes: mitigar primero, diagnosticar después
  11. Post-mortem sin culpables
  12. De 68 minutos a menos de 10
  13. Errores Comunes y Consejos
  14. Ejercicios
  15. Conclusión

  1. Separar el despliegue del lanzamiento

Son dos actos distintos que la costumbre ha fundido en uno:

  • Desplegar es poner código en producción: un acto técnico, reversible, que puede ocurrir veinte veces al día.
  • Lanzar es hacer que ese código sea visible o efectivo para los usuarios: un acto de producto, con fecha y con marketing detrás, que no tiene por qué coincidir con el anterior.

Cuando ambos coinciden, aparecen las patologías que Reservalia conoce bien: ramas de vida larga esperando a que la funcionalidad esté "completa", despliegues gigantes cargados de cambios, y la imposibilidad de apagar algo que va mal sin volver a desplegar. Un feature flag (o toggle) es simplemente una condición en el código cuyo valor se decide en tiempo de ejecución, fuera del artefacto:

if (await flags.activo('agenda_nuevo_calculo', { negocioId })) {
  return calcularHuecosV2(horario, ocupados, duracionMin);
}
return calcularHuecos(horario, ocupados, duracionMin);

El artefacto reservalia/api:a3f9c21 contiene las dos implementaciones. Cuál se ejecuta ya no depende de qué imagen está desplegada, sino de un valor que se puede cambiar en segundos y sin pipeline. Eso convierte "apagar la funcionalidad rota" en una operación de diez segundos en lugar de un despliegue.

  1. Tipos de feature flag, vida esperada y dueño

No todos los flags son iguales, y tratarlos igual es la causa número uno de que un sistema de flags se pudra. Reservalia adopta esta clasificación:

Tipo Para qué sirve Vida esperada Dueño Ejemplo en Reservalia
Release Ocultar código incompleto o recién desplegado Días o semanas; se retira siempre El desarrollador que lo creó agenda_nuevo_calculo
Experimento Comparar variantes y medir efecto Lo que dure la prueba (semanas) Producto experimento_horarios_sugeridos
Operativo / kill switch Apagar una funcionalidad costosa o inestable en caliente Permanente SRE (Nuria) recordatorios_sms
Permisos / acceso Dar acceso a un subconjunto de clientes Permanente, ligado al plan Producto / Negocio panel_metricas_beta

Las dos columnas que más se ignoran son las importantes. La vida esperada distingue lo que hay que borrar de lo que se queda: un flag de release que lleva ocho meses en el código es deuda, un kill switch de ocho meses es una herramienta. El dueño evita el flag huérfano, ese que nadie se atreve a tocar porque nadie sabe qué pasa si se apaga.

  1. Una implementación mínima en apps/api

No hace falta una plataforma comercial para empezar. Reservalia guarda los flags en su propia base de datos:

-- migración: crear la tabla de flags
CREATE TABLE flags (
  clave              TEXT PRIMARY KEY,
  tipo               TEXT NOT NULL CHECK (tipo IN ('release','experimento','operativo','permisos')),
  estado             TEXT NOT NULL CHECK (estado IN ('off','porcentaje','on')),
  porcentaje         INT  NOT NULL DEFAULT 0 CHECK (porcentaje BETWEEN 0 AND 100),
  negocios_incluidos BIGINT[] NOT NULL DEFAULT '{}',   -- lista blanca explícita
  dueno              TEXT NOT NULL,
  caduca_en          DATE                              -- obligatoria en tipo 'release'
);

El módulo de evaluación vive en apps/api/src/flags/index.ts:

import { createHash } from 'node:crypto';

type Contexto = { negocioId: number };
const CACHE_MS = 30_000;   // cargar() relee la tabla completa como mucho una vez cada CACHE_MS
let cache: { en: number; valores: Map<string, Flag> } = { en: 0, valores: new Map() };

export async function activo(clave: string, ctx: Contexto): Promise<boolean> {
  const flag = (await cargar()).get(clave);
  if (!flag) return false;                                            // 1: ausente = apagado
  if (flag.negocios_incluidos.includes(ctx.negocioId)) return true;   // 2
  if (flag.estado === 'on')  return true;
  if (flag.estado === 'off') return false;
  return cubo(clave, ctx.negocioId) < flag.porcentaje;                // 3
}

function cubo(clave: string, negocioId: number): number {    // 4
  const h = createHash('sha256').update(`${clave}:${negocioId}`).digest();
  return h.readUInt32BE(0) % 100;   // 5
}
  1. Un flag desconocido devuelve false. El valor por defecto siempre es el comportamiento antiguo: si la tabla no se puede leer o alguien escribió mal la clave, el sistema se comporta como antes del cambio, no como después.
  2. La lista blanca permite activar la funcionalidad para negocios concretos —los tres clientes que pidieron la beta, o el negocio de pruebas interno— sin tocar el porcentaje global.
  3. El reparto por porcentaje se calcula con un hash, no con un número aleatorio. Es la diferencia entre un despliegue progresivo y un caos: Math.random() < 0.1 daría un resultado distinto en cada petición y el mismo negocio vería la funcionalidad aparecer y desaparecer.
  4. Al incluir la clave en el hash, dos flags al 10 % no afectan a los mismos negocios; si el hash fuera solo del negocioId, siempre saldrían los mismos "elegidos".
  5. La caché de 30 segundos evita una consulta por petición. El precio es que un cambio tarda hasta medio minuto en propagarse a todas las tareas: aceptable para un kill switch, y hay que conocerlo antes de un incidente.
  6. La evaluación es por negocio, no por usuario, y es una decisión de dominio: en Reservalia todos los empleados de un mismo salón deben ver la misma agenda. Activar por usuario haría que dos recepcionistas vieran huecos distintos, que es exactamente el tipo de incidente irreproducible que nadie quiere.

  1. Fusionar código incompleto en main sin ramas de vida larga

En la lección 02-07 quedó establecido que las ramas de vida larga son enemigas de la integración continua: cuanto más viven, más divergen y más doloroso es el merge. Pero el equipo tenía una objeción legítima: "no puedo fusionar una funcionalidad a medias".

Los flags disuelven esa objeción. Diego puede fusionar calcularHuecosV2 en main el primer día, con el flag agenda_nuevo_calculo en off: el código viaja a producción en cada despliegue, se compila, pasa el tsc --noEmit y sus pruebas unitarias corren en el CI, pero ningún usuario lo ejecuta.

Rama de vida larga Flag en off
Conflictos de merge Crecen con el tiempo Ninguno: se integra a diario
El CI prueba el código Solo en la rama En main, con todo lo demás
Activación Requiere merge + despliegue Cambiar un valor
Desactivación Revertir + desplegar Cambiar un valor
Coste Cero al principio, alto al final Complejidad en el código desde el día uno

Y una regla práctica: las pruebas deben cubrir ambas ramas del flag; en el CI se ejecuta el conjunto crítico con el flag forzado a on y a off, porque un flag cuya rama activa nunca se prueba es código muerto que se despertará en el peor momento.

  1. La deuda de flags: por qué caducan y cómo se retiran

Cada flag de release añade una bifurcación al código. Con cinco flags conviviendo hay hasta 32 combinaciones posibles de comportamiento, y nadie prueba 32 combinaciones. Los flags no retirados producen síntomas concretos: código ilegible, pruebas que dependen de una configuración implícita, y el terror de tocar algo que "no se sabe si sigue usándose".

Reservalia establece tres reglas: todo flag de tipo release nace con caduca_en obligatorio, típicamente a 30 días vista; un job semanal lista los flags caducados y abre una incidencia asignada a su dueño —no los borra, porque pedir una acción humana es distinto de romper producción un martes—; y retirar el flag es parte de la tarea, no una tarea futura: en el tablero, una funcionalidad no está terminada hasta que su flag ha desaparecido del código.

-- Flags de release vencidos, con su dueño
SELECT clave, dueno, caduca_en, estado FROM flags
WHERE tipo = 'release' AND caduca_en < CURRENT_DATE ORDER BY caduca_en;

La retirada tiene un orden que evita sustos: primero se pone el flag al 100 % y se deja unos días, después se elimina la condición del código dejando solo la rama nueva, y solo al final se borra la fila de la tabla. Al revés —borrar la fila primero— el flag pasa a evaluarse como false y la funcionalidad ya lanzada desaparece de golpe.

  1. Rollback de artefacto frente a roll-forward

Cuando algo va mal en producción hay dos caminos, y elegir por reflejo es un error.

Rollback de artefacto Roll-forward
Qué es Volver a desplegar el digest anterior conocido Arreglar y desplegar una versión nueva
Tiempo hasta la mitigación Minutos: el artefacto ya existe y ya estuvo sano El que tarde el arreglo + el pipeline completo
Riesgo Bajo: se vuelve a un estado conocido Medio: código nuevo escrito con prisa
Cuándo El fallo es grave y la versión anterior estaba bien El fallo es leve, o volver atrás es imposible
Bloqueante Migraciones de base de datos incompatibles Un pipeline lento

Un equipo maduro no elige siempre lo mismo. La regla que adopta Reservalia: si el impacto es grave —usuarios que no pueden reservar, errores 5xx, pérdida de datos—, rollback siempre; el arreglo se piensa después, con calma y sin producción ardiendo. Si el impacto es menor —un texto mal traducido, un icono torcido—, roll-forward, porque un rollback también es un cambio y también puede fallar. Hay una condición que hace posible el rollback rápido y que ya está pagada desde la 02-06: artefactos inmutables identificados por digest. Volver a b7e2d10 no significa reconstruir nada, sino apuntar el servicio al digest que sigue en ECR; reconstruir daría una versión distinta con las mismas fuentes, y eso no es un rollback, es una lotería.

  1. El límite duro: la base de datos

El rollback de artefacto es reversible. La migración de base de datos que lo acompañaba, no. Si la versión c1d4a55 incluía una migración que renombró la columna duracion a duracion_min, volver a b7e2d10 deja corriendo un código que consulta una columna que ya no existe: el rollback del contenedor tarda tres minutos, el del esquema puede no existir.

De ahí sale una regla que gobierna todo el diseño: una migración debe ser compatible con la versión anterior del código y con la siguiente. Se consigue con el patrón expandir-contraer que vimos en 03-04 aplicado a los datos: añadir la columna nueva, escribir en ambas durante un tiempo, migrar lecturas, y solo mucho después eliminar la vieja. Mientras se respete, cualquier despliegue es reversible. El detalle completo —migraciones en el pipeline, orden respecto al despliegue, migraciones largas y bloqueos— es la lección 04-06, Bases de Datos en el Pipeline: Migraciones Seguras.

  1. El botón de vuelta atrás: rollback.yml

Nuria escribe el workflow que faltaba. Su requisito de diseño es explícito: cualquier persona de guardia, sin conocer AWS, debe poder revertir en menos de cinco minutos desde el móvil.

# .github/workflows/rollback.yml
name: Rollback

on:
  workflow_dispatch:                       # 1
    inputs:
      entorno:
        description: Entorno donde revertir
        type: choice
        options: [dev, staging, prod]
        required: true
      sha:                                 # SHA de 7 caracteres, p. ej. b7e2d10
        type: string
        required: true
      motivo:                              # queda registrado en la tabla despliegues
        type: string
        required: true

permissions: { id-token: write, contents: read }
concurrency: { group: despliegue-${{ inputs.entorno }}, cancel-in-progress: false }   # 2

jobs:
  revertir:
    runs-on: ubuntu-22.04
    environment: ${{ inputs.entorno }}     # 3
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_DEPLOY }}
          aws-region: eu-west-1

      - name: Comprobar que el artefacto existe en ECR
        id: artefacto
        run: |                             # 4
          DIGEST=$(aws ecr describe-images --repository-name reservalia/api \
            --image-ids imageTag=${{ inputs.sha }} \
            --query 'imageDetails[0].imageDigest' --output text)
          [ "$DIGEST" != "None" ] || { echo "::error::No existe reservalia/api:${{ inputs.sha }}"; exit 1; }
          echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"

      - name: Redesplegar el digest anterior y esperar estabilidad
        run: |                             # 5
          ./infra/scripts/desplegar.sh reservalia-${{ inputs.entorno }} \
            reservalia-api "${{ steps.artefacto.outputs.digest }}"
          aws ecs wait services-stable --cluster reservalia-${{ inputs.entorno }} \
            --services reservalia-api

      - name: Smoke test contra /version
        run: |                             # 6
          BASE=https://api-${{ inputs.entorno }}.reservalia.com   # en prod: api.reservalia.com
          SHA=$(curl -fsS --retry 5 --retry-delay 5 "$BASE/version" | jq -r .commit)
          [ "$SHA" = "${{ inputs.sha }}" ] || { echo "::error::Sirviendo $SHA"; exit 1; }

      - name: Registrar el rollback
        run: ./infra/scripts/registrar-despliegue.sh --entorno "${{ inputs.entorno }}" \
               --sha "${{ inputs.sha }}" --tipo rollback --motivo "${{ inputs.motivo }}"

Las decisiones que hacen que esto funcione bajo presión son estas:

  1. workflow_dispatch con entradas tipadas. El type: choice elimina la posibilidad de escribir produccion en lugar de prod a las tres de la madrugada, y el motivo obligatorio garantiza que el registro sirva para el post-mortem.
  2. El mismo grupo de concurrency que cd.yml. Un rollback y un despliegue normal no pueden solaparse; el segundo espera en lugar de pisar al primero.
  3. environment: ${{ inputs.entorno }} reutiliza las reglas de protección. Ojo: si prod exige aprobación de un revisor, el rollback también la exigirá. Reservalia lo resuelve con una lista de aprobadores amplia para que siempre haya alguien disponible; bloquear el rollback tras una aprobación difícil de conseguir es peor que el fallo original.
  4. Verificar que el artefacto existe antes de tocar nada. Fallar rápido con un mensaje claro es mejor que dejar el servicio a medio actualizar. 5. Se reutiliza el mismo desplegar.sh del despliegue normal, que parte de la definición de tarea vigente y solo cambia la imagen por el digest indicado: así el rollback no revierte cambios de configuración legítimos hechos desde entonces, y el camino de emergencia usa código ya probado a diario. 6. El smoke test contra /version convierte "creo que ha vuelto" en "está sirviendo b7e2d10"; sin él, el rollback es una esperanza.

Marta añade una práctica que parece trivial y no lo es: ensayar el rollback una vez al mes en staging, cronometrado. Un botón que nadie ha pulsado nunca no es un botón, es un adorno.

  1. Rollback automático disparado por métricas

Durante un canary no hace falta esperar a que alguien mire un panel; el propio pipeline puede decidir:

      - name: Etapa 10 % con vigilancia
        run: |
          ./infra/scripts/peso-canary.sh 10
          for i in $(seq 1 20); do sleep 30
            ./infra/scripts/comprobar-metricas.sh || { echo "::error::Canary degradado"; exit 1; }
          done

      - name: Abortar el canary si algo falló
        if: failure()                       # se ejecuta solo si un paso anterior falló
        run: ./infra/scripts/peso-canary.sh 0

El patrón clave es if: failure(): el paso de aborto se ejecuta precisamente cuando algo ha ido mal, y devuelve el peso del canary a cero sin esperar a un humano. En Reservalia, comprobar-metricas.sh compara el grupo canary con el estable en tasa de 5xx y latencia p95 durante los diez minutos de vigilancia. Dos cautelas: el automatismo necesita umbrales con margen, o un pico de ruido revertirá despliegues sanos y el equipo acabará desactivándolo; y debe existir siempre el camino manual, porque si el automatismo falla, rollback.yml sigue ahí.

  1. Recuperación de incidentes: mitigar primero, diagnosticar después

Un despliegue continuo sano no presume de no fallar nunca: presume de recuperarse rápido. El orden importa y es contraintuitivo para quien viene de la cultura del "hay que entender el problema antes de tocar nada".

flowchart LR
    A["Detección<br/>alerta o usuario"] --> B["Declarar incidente<br/>y nombrar coordinador"]
    B --> C["MITIGAR<br/>flag off · rollback · peso a 0"]
    C --> D["Confirmar recuperación<br/>métricas y /version"]
    D --> E["Diagnosticar con calma<br/>arreglar y desplegar"]
    E --> G["Post-mortem sin culpables"]

Mitigar primero, diagnosticar después. Mientras se investiga la causa, los usuarios siguen sin poder reservar. Apagar el flag o revertir el artefacto detiene el daño y devuelve el tiempo que hace falta para pensar: la causa raíz seguirá ahí dentro de dos horas; los clientes enfadados, no necesariamente. Reservalia fija tres roles durante un incidente: quien coordina (decide y no teclea), quien opera (ejecuta las acciones) y quien comunica (avisa a soporte y a los negocios afectados). En un equipo de tres personas los roles pueden solaparse, pero la coordinación no puede faltar: sin ella, dos personas ejecutan mitigaciones contradictorias a la vez. Y la comunicación tiene una regla simple: antes poco y pronto que tarde y completo; un mensaje a los cinco minutos diciendo "estamos investigando problemas al crear citas" vale más que un informe perfecto a la hora.

  1. Post-mortem sin culpables

Un post-mortem blameless parte de una premisa: si una persona pudo romper producción con una acción razonable, el problema es el sistema que se lo permitió. Buscar culpables produce ocultación, y la ocultación produce incidentes que se repiten. Reservalia usa esta plantilla, en docs/postmortems/:

# Post-mortem: <título corto y descriptivo>
- Fecha y duración: 2026-07-14, 09:12–09:34 (22 min)
- Impacto: ~180 negocios no pudieron crear citas; 2.100 peticiones con error 500
- Detección: alerta de tasa de errores (4 min tras el despliegue de c1d4a55)
- Mitigación: rollback.yml a b7e2d10 (6 min)
## Cronología
09:08 se despliega c1d4a55 · 09:12 salta la alerta · 09:15 se declara incidente …
## Qué ocurrió y por qué el sistema lo permitió (sin nombres propios)
Las pruebas de integración no cubrían el caso de horario partido.
## Qué funcionó bien
La alerta disparó a los 4 minutos; el rollback tardó 6.
## Acciones (con dueño y fecha)
| Acción | Dueño | Fecha | Estado |
|---|---|---|---|
| Prueba de integración para horarios partidos | Diego | 2026-07-18 | hecho |

Dos apartados suelen faltar y son los más útiles: "qué funcionó bien", que evita desmontar defensas que sí sirvieron, y acciones con dueño y fecha, porque un post-mortem sin acciones asignadas es un ejercicio literario. Y una regla: las acciones se meten en el mismo tablero que el resto del trabajo, o no se harán.

  1. De 68 minutos a menos de 10

El time to restore no es un número mágico: es la suma de tres tramos, cada uno atacado con una herramienta distinta.

Tramo Antes Después Qué lo consigue
Detección ~25 min (lo avisaba un cliente) 3 min Alertas sobre síntomas (lección 03-06)
Decisión ~15 min ("¿revertimos o arreglamos?") 2 min Regla escrita: si el impacto es grave, rollback
Ejecución ~28 min (SSH, subida manual, rezar) 4 min rollback.yml y flags
Total 68 min 9 min

Merece la pena señalar qué tramo se lleva la mejora mayor: la detección. Se puede tener el mejor botón de rollback del mundo y seguir tardando media hora si nadie se entera de que hay un problema; ese tramo es justo el que la lección siguiente se encarga de cerrar. Y hay un caso todavía mejor: cuando el fallo está detrás de un flag, la ejecución baja a segundos y el total ronda los cinco minutos.

Errores Comunes y Consejos

Error 1: usar flags como configuración permanente. Si flags acumula 60 claves de las que 50 son de release y nadie las retira, el código se vuelve inauditable. Error 2: flags sin dueño ni caducidad, que nadie se atreve a apagar años después. Error 3: evaluar por azar en lugar de por hash, con lo que el mismo negocio ve la funcionalidad aparecer y desaparecer entre peticiones. Error 4: no probar la rama activa del flag; el CI da verde y la funcionalidad estalla el día que se enciende.

Error 5: un rollback que reconstruye la imagen desde el commit anterior. Eso no es volver a una versión conocida, es construir una nueva bajo presión: se despliega el digest, siempre. Error 6: no ensayar nunca el rollback, y descubrir que el rol IAM caducó justo durante el incidente.

Consejo 1: haz que el valor por defecto de todo flag sea el comportamiento antiguo, para que un fallo de lectura sea inocuo. Consejo 2: registra cada cambio de flag con autor y fecha, porque en un incidente la pregunta "¿qué cambió?" incluye los flags, no solo los despliegues. Consejo 3: documenta en el README los tres comandos de emergencia —apagar flag, lanzar rollback.yml, poner el canary a 0— donde el de guardia los encuentre en veinte segundos.

Ejercicios

Ejercicio 1

Clasifica estos cuatro flags de Reservalia por tipo, propón vida esperada y dueño, y di cuál no debe retirarse nunca: pagos_stripe_v2 (nueva pasarela de pago que sustituye a la antigua), recordatorios_sms (envío de SMS con coste por mensaje), panel_metricas_beta (panel disponible solo para clientes del plan avanzado) y experimento_horarios_sugeridos (dos formas de ordenar los huecos propuestos).

Ejercicio 2

Son las 22:40. El despliegue de c1d4a55 hace veinte minutos ha disparado la tasa de errores del endpoint de creación de citas al 30 %. La versión anterior era b7e2d10. El cambio incluía una migración que añadió la columna recordatorio_enviado_en. Describe los pasos exactos en orden y justifica si eliges rollback o roll-forward.

Ejercicio 3

Mismo escenario, pero la migración renombró duracion a duracion_min. ¿Sigue siendo válido tu plan? Describe qué harías y qué debería haberse hecho distinto semanas antes para que este escenario no existiera.

Soluciones

Solución 1. pagos_stripe_v2 es de release: su objetivo es sustituir la pasarela antigua, así que tiene fecha de caducidad (unas semanas, quizá más por lo delicado del pago), su dueño es quien lo desarrolla, y se retira cuando el 100 % lleve tiempo estable. experimento_horarios_sugeridos es de experimento: dueño de producto, vive lo que dure la medición y termina eligiendo una variante. panel_metricas_beta es de permisos: su dueño es producto/negocio, es permanente y en realidad no es un flag temporal sino una regla de derechos de acceso ligada al plan contratado. recordatorios_sms es operativo (kill switch) y es el que no se retira nunca: como los SMS cuestan dinero y dependen de un proveedor externo, Nuria necesita poder apagarlos en caliente si el proveedor se degrada o si el coste se dispara. Un matiz útil: pagos_stripe_v2, aunque sea de release, conviene mantenerlo un tiempo prudencial tras el 100 % porque de facto actúa como kill switch de la pasarela nueva.

Solución 2. Rollback, sin dudarlo: el impacto es grave (un tercio de las creaciones de cita falla), la versión anterior era sana y la migración es compatible —añadir una columna no molesta a b7e2d10, que simplemente la ignora—. Pasos: (1) declarar el incidente y nombrar coordinador; (2) si el cambio está tras un flag, apagarlo, que son diez segundos frente a los cinco minutos del rollback; (3) si no lo está, lanzar rollback.yml con entorno=prod, sha=b7e2d10 y el motivo; (4) confirmar con el smoke test que /version devuelve b7e2d10 y comprobar que la tasa de errores baja; (5) comunicar a soporte y a los negocios afectados; (6) al día siguiente, diagnosticar con calma, arreglar, añadir la prueba que faltaba y desplegar hacia delante; (7) post-mortem con acciones asignadas. La columna nueva queda huérfana un tiempo, lo cual es inofensivo.

Solución 3. No, el plan ya no vale: b7e2d10 consulta duracion, que ya no existe, así que el rollback cambiaría un error del 30 % por un error del 100 %. Las opciones reales son peores: roll-forward urgente con un arreglo escrito bajo presión, o revertir también el esquema —renombrar la columna de vuelta— con el riesgo de perder escrituras hechas entre medias. Lo razonable es mitigar por otra vía (apagar el flag si existe, degradar la funcionalidad afectada) mientras se prepara el arreglo hacia delante. Lo que debería haberse hecho semanas antes es aplicar expandir-contraer: despliegue 1, añadir duracion_min y escribir en ambas columnas; despliegue 2, leer de duracion_min; despliegue 3, semanas después y con todo estable, eliminar duracion. Con esa secuencia, cada despliegue individual es reversible y el escenario del enunciado no llega a producirse. Ese es exactamente el territorio de la lección 04-06.

Conclusión

Separar el despliegue del lanzamiento cambia la naturaleza del riesgo. Los feature flags permiten a Reservalia fusionar código incompleto en main sin ramas de vida larga, activar por negocio y con reparto estable por hash, y apagar en caliente lo que se rompa —siempre que cada flag nazca con tipo, dueño y fecha de caducidad, porque la deuda de flags es real—. El rollback de artefacto por digest, materializado en rollback.yml con workflow_dispatch, devuelve producción a un estado conocido en minutos, y el rollback automático por métricas lo hace sin esperar a un humano durante un canary. Todo ello con un límite duro que conviene tener siempre presente: si la migración de base de datos no es compatible, no hay vuelta atrás que valga.

Con esto, dos de los tres tramos del time to restore de Reservalia están resueltos: la decisión, por una regla escrita, y la ejecución, por un botón ensayado. Queda el más grande, la detección: hoy Reservalia se entera de que producción va mal porque llama un cliente. Y hay una carencia gemela: sin señal de producción, ni el canary automático ni el presupuesto de despliegues tienen sobre qué decidir. La siguiente lección, Monitoreo y Retroalimentación, cierra el bucle con los tres pilares de la observabilidad, las cuatro señales de oro, los SLO con presupuesto de error y la alimentación automática de las métricas DORA desde el propio pipeline.

Curso de CI/CD: Integración y Despliegue Continuo

Módulo 1: Introducción a CI/CD

Módulo 2: Integración Continua (CI)

Módulo 3: Despliegue Continuo (CD)

Módulo 4: Prácticas Avanzadas de CI/CD

Módulo 5: Implementación de CI/CD en Proyectos Reales

Módulo 6: Herramientas y Tecnologías

Módulo 7: Ejercicios Prácticos

Módulo 8: Recursos Adicionales

© Copyright 2026. Todos los derechos reservados