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
- Separar el despliegue del lanzamiento
- Tipos de feature flag, vida esperada y dueño
- Una implementación mínima en
apps/api - Fusionar código incompleto en
mainsin ramas de vida larga - La deuda de flags: por qué caducan y cómo se retiran
- Rollback de artefacto frente a roll-forward
- El límite duro: la base de datos
- El botón de vuelta atrás:
rollback.yml - Rollback automático disparado por métricas
- Recuperación de incidentes: mitigar primero, diagnosticar después
- Post-mortem sin culpables
- De 68 minutos a menos de 10
- Errores Comunes y Consejos
- Ejercicios
- Conclusión
- 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.
- 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.
- Una implementación mínima en
apps/api
apps/apiNo 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
}- 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. - 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.
- 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.1daría un resultado distinto en cada petición y el mismo negocio vería la funcionalidad aparecer y desaparecer. - 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". - 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.
- 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.
- Fusionar código incompleto en
main sin ramas de vida larga
main sin ramas de vida largaEn 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.
- 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.
- 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.
- 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.
- El botón de vuelta atrás:
rollback.yml
rollback.ymlNuria 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:
workflow_dispatchcon entradas tipadas. Eltype: choiceelimina la posibilidad de escribirproduccionen lugar deproda las tres de la madrugada, y elmotivoobligatorio garantiza que el registro sirva para el post-mortem.- El mismo grupo de
concurrencyquecd.yml. Un rollback y un despliegue normal no pueden solaparse; el segundo espera en lugar de pisar al primero. environment: ${{ inputs.entorno }}reutiliza las reglas de protección. Ojo: siprodexige 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.- 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.shdel 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/versionconvierte "creo que ha vuelto" en "está sirviendob7e2d10"; 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.
- 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 0El 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í.
- 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.
- 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.
- 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
- Conceptos Básicos de CI/CD
- Beneficios de CI/CD
- Herramientas Populares de CI/CD
- El Proyecto del Curso: la Aplicación que Vamos a Automatizar
- Métricas DORA: Cómo se Mide la Entrega de Software
Módulo 2: Integración Continua (CI)
- Introducción a la Integración Continua
- Configuración de un Entorno de CI
- Automatización de la Construcción
- Pruebas Automatizadas
- Calidad de Código y Análisis Estático
- Artefactos, Versionado y Promoción
- Integración con Control de Versiones
Módulo 3: Despliegue Continuo (CD)
- Introducción al Despliegue Continuo
- Automatización del Despliegue
- Infraestructura como Código y Entornos Reproducibles
- Estrategias de Despliegue
- Feature Flags, Rollback y Recuperación ante Fallos
- Monitoreo y Retroalimentación
Módulo 4: Prácticas Avanzadas de CI/CD
- Pipelines de CI/CD
- Gestión de Dependencias
- Seguridad en CI/CD
- Escalabilidad y Rendimiento
- Pipeline as Code: Plantillas, Reutilización y Pruebas del Pipeline
- Bases de Datos en el Pipeline: Migraciones Seguras
Módulo 5: Implementación de CI/CD en Proyectos Reales
- Caso de Estudio: Proyecto Web
- Caso de Estudio: Aplicación Móvil
- Caso de Estudio: Microservicios
- Caso de Estudio: Modernizar un Proyecto Legacy
Módulo 6: Herramientas y Tecnologías
- Jenkins
- GitLab CI/CD
- CircleCI
- Travis CI
- Docker y Kubernetes
- GitHub Actions a Fondo
- Comparativa y Criterios para Elegir Herramienta
Módulo 7: Ejercicios Prácticos
- Ejercicio 1: Configuración de un Pipeline Básico
- Ejercicio 2: Integración de Pruebas Automatizadas
- Ejercicio 3: Despliegue en un Entorno de Producción
- Ejercicio 4: Monitoreo y Retroalimentación
- Ejercicio 5: Endurecer el Pipeline con Seguridad y Secretos
- Proyecto Final: Pipeline Completo de Extremo a Extremo
