Al terminar esta lección
- Diseñar una migración expand/contract que siga funcionando con la versión anterior durante el rollout.
- Separar el deploy de la exposición con feature flags que tengan un default seguro, un dueño y una fecha de retiro.
- Escribir healthchecks de liveness y readiness que describan el estado real del sistema y no mientan.
- Diseñar señales de observabilidad a partir de preguntas operativas concretas, sin loguear datos sensibles.
- Diseñar una sesión de validación con usuarios que pueda refutar la solución.
11. Integrar, desplegar y demostrar sin convertir producción en el test final
El trabajo no termina cuando CI queda verde. Un release real cambia un sistema que ya tiene usuarios, datos históricos e integraciones que no controlamos. Cuando desplegamos, aparecen tres tiempos que los tests de la rama no muestran: el tiempo en que conviven la versión vieja y la nueva, el tiempo en que los datos de ayer tienen que seguir siendo válidos con el modelo de mañana, y el tiempo en que alguien tiene que decidir si seguimos o volvemos atrás con la información que tiene.
Esta lección trata esos tres tiempos. Vamos a usar Nexo B2B como caso, pero el criterio aplica a cualquier sistema con datos reales. Empezamos por la migración, porque es donde más releases se rompen. Seguimos con los flags que separan deploy de exposición, con los healthchecks y la observabilidad que permiten reconstruir una operación, y cerramos con la demo y la sesión de validación, que son la última forma de comprobar si entendimos el problema.
11.1 Migraciones: expandir, migrar, contraer
El problema que resuelve
Una migración no es solo el comando que aplica el SQL. El riesgo aparece en la ventana entre dos estados: mientras el nuevo deploy reemplaza instancias, algunas réplicas siguen corriendo el código anterior y escriben en la misma base. Si la migración elimina algo que la versión vieja usa, o exige algo que la versión vieja no envía, la base queda incompatible con una parte de la flota durante minutos u horas.
El segundo riesgo es el volumen. Una migración que parece instantánea en el repositorio puede bloquear una tabla de cientos de miles de filas durante varios minutos en producción, porque toma un lock exclusivo y reescribe datos. Por eso una migración profesional se mide con datos parecidos a los reales y se diseña para ser interrumpida sin dejar la base a medio camino.
Definición operativa
Expand/contract es un patrón de cambio en cuatro fases que mantiene compatibles las versiones N y N+1 durante toda la transición:
- Expand. Se agrega lo nuevo sin quitar ni renombrar nada. Las columnas nuevas son nullable, los índices se crean sin bloquear escrituras y ninguna versión existente depende de lo que se agregó.
- Migrar. La versión N+1 escribe en el formato viejo y en el nuevo (dual write) y lee el nuevo cuando existe, con fallback al viejo. En paralelo se completan los datos históricos con un backfill por lotes, con checkpoint y conteo.
- Switch. Cuando la evidencia dice que todas las filas tienen el dato nuevo, la aplicación deja de depender del formato viejo. Ningún lector nuevo usa la columna vieja.
- Contract. Solo después de un gate explícito se aplican las restricciones definitivas (NOT NULL, constraint validado) y se elimina lo viejo. Esta fase es irreversible sin restaurar un backup, así que tiene su propio go/no-go.
El detalle que más se olvida es que el contract no es una fase técnica: es una decisión sobre quién todavía depende de lo viejo. Se decide con telemetría, no con la fecha del calendario.
Ejemplo en Nexo: la intención de pedido necesita un hash
En el release 1, un pedido se identifica por su requestId (ADR-005). La versión 1 guardaba además un payload_md5 para detectar reintentos con contenido distinto. Eso tiene un problema: MD5 no es el fingerprint canónico que define ADR-006 (SHA-256 sobre cliente, depósito y líneas normalizadas, ignorando tracing y requestId). Hay que agregar request_hash y dejar de depender del campo viejo, sin cortar pedidos mientras tanto.
La primera fase es esta migración. No tiene NOT NULL, no renombra y no elimina nada:
-- Fase EXPAND. Solo agrega: no elimina, no renombra, no exige valores.
-- La versión 1 (que escribe payload_md5) sigue sirviendo tráfico durante el rollout.
SET lock_timeout = '3s'; -- si no consigue el lock en 3 s, falla en vez de frenar escrituras
ALTER TABLE idempotency_keys
ADD COLUMN IF NOT EXISTS request_hash text; -- nullable: las filas históricas quedan en NULL
ALTER TABLE order_lines
ADD COLUMN IF NOT EXISTS price_source text; -- origen del precio aplicado (RN-03), nullable
El índice por request_hash no va en esta migración. Según la herramienta de migraciones, CREATE INDEX CONCURRENTLY puede no ejecutarse dentro de una transacción, y el dry-run tiene que confirmarlo. Lo tratamos como paso operativo separado, con su propio registro.
Contraejemplo: el deploy de una sola línea
El antipatrón es escribir una migración que haga todo junto:
-- Antipatrón: no aplicar en un solo release
ALTER TABLE idempotency_keys ADD COLUMN request_hash text NOT NULL;
ALTER TABLE idempotency_keys DROP COLUMN payload_md5;
Parece prolijo y corto. En el deploy aparecen dos fallas. La primera: las instancias de la versión 1 todavía insertan filas sin request_hash, así que sus escrituras fallan con una violación de NOT NULL durante el rollout. La segunda: las filas históricas no tienen hash y el ADD COLUMN ... NOT NULL sin valor por defecto no puede aplicarse sobre ellas. En el mejor caso la migración falla y el deploy se corta; en el peor, una parte de los clientes ve errores 500 mientras otra parte no.
Cómo se verifica una migración
Una migración se verifica con cuatro pruebas, en este orden:
- Dry-run contra una copia con volumen real. No alcanza con la base de tests con tres filas. Medí cuánto tarda y qué locks toma. Si el
lock_timeoutde 3 segundos dispara, la migración todavía no está lista. - Compatibilidad N-1. Corré la suite de la versión anterior contra el schema ya expandido. Si la versión vieja falla, el expand no es compatible.
- Conteos antes y después. Cada fase del backfill tiene una consulta que reproduce el número esperado. Sin conteo, no hay evidencia de que la migración hizo lo que dijo.
- Rollback del código con el schema expandido. Desplegá la versión anterior sobre el schema nuevo y verificá que atiende pedidos. Esta prueba demuestra que el expand realmente permite volver atrás.
Una tabla de decisiones ayuda a clasificar los cambios antes de escribir la migración. Probá tu criterio con el ejercicio siguiente.
Clasificá cada cambio según lo que rompe durante el rollout
Elegí la categoría según si la versión anterior sigue funcionando mientras la nueva ya está desplegada.
Agregar la columna request_hash como nullable, sin valor por defecto.
Completar request_hash en todas las filas históricas con un único UPDATE sobre la tabla completa.
Agregar request_hash como NOT NULL en el mismo deploy en que se agrega la columna.
Eliminar payload_md5 mientras todavía hay instancias de la versión 1 activas.
Eliminar payload_md5 después de verificar que no hay instancias v1 y que el backfill terminó.
Renombrar unit_price_cents a price_cents en el mismo release que lo usa.
Con la clasificación hecha, el orden de las fases también importa. Armá la secuencia del cambio de hash. Las acciones están mezcladas.
Ordená la secuencia segura del cambio de hash
Usá las flechas para dejar las fases en el orden en que deben ejecutarse. Después comprobá.
- Posición 1 de 5: Verificar con conteos que no quedan filas migrables sin hash y que no hay instancias de la versión 1 activas.
- Posición 2 de 5: Desplegar la versión que escribe payload_md5 y request_hash, y lee request_hash cuando existe.
- Posición 3 de 5: Aplicar la migración 0007 (contract): constraint NOT VALID, VALIDATE, SET NOT NULL y DROP de payload_md5.
- Posición 4 de 5: Desplegar la migración 0005 (expand): columnas nullable, sin tocar datos existentes.
- Posición 5 de 5: Ejecutar el backfill por lotes sobre los pedidos con depósito asignado y registrar los que no pueden migrarse.
Feature flags: el deploy no es la exposición
El problema que resuelve
Un deploy pone código en producción. Una activación decide qué comportamiento ven los usuarios. Si las dos cosas están acopladas, cada cambio riesgoso obliga a elegir entre desplegar sin control o esperar a la próxima ventana para no exponer algo que todavía no se validó. Un feature flag separa ambas decisiones: el código queda desplegado apagado y se activa cuando hay evidencia.
Definición operativa
Un feature flag en un sistema de negocio tiene cuatro propiedades que hay que declarar, no solo un booleano en el código:
- Default seguro. Si el flag no está configurado, el sistema se comporta como antes del cambio.
- Alcance por entorno. Puede estar encendido en staging y apagado en producción, o activado solo para un grupo.
- Dueño y motivo. Alguien responde por el flag y dice qué decisión habilita.
- Fecha de retiro. Un flag que sobrevive a su release se convierte en una rama muerta que nadie entiende.
Ejemplo en Nexo: el registro de flags del release 1
El release 1 incluye cuatro flags. Los nombres son parte del contrato operativo, así que se documentan junto con el código:
orders.request_hash_idempotency: cambia la lectura del hash en la verificación de reintentos. Default apagado. Se activa primero en staging y después en producción, por porcentaje de tráfico.orders.price_source_snapshot: guarda el origen del precio en cada línea. Default apagado; se activa cuando el backfill deprice_sourcetermina.fiscal.emission.enabled: la emisión de comprobante (CAE) es integración posterior, OUT del primer release. Default apagado, y el guard de abajo impide activarlo en producción.reservation.soft_hold.enabled: reserva blanda para pedidos pendientes de autorización. Default apagado. No tiene TTL definido: RN-07 sigue BLOCKED, así que el flag no puede activarse hasta que exista una decisión, aunque el código esté listo.
// Registro único de flags. Ausente = default seguro. Valor inválido = el proceso no arranca.
export const FLAG_DEFAULTS: Record<string, boolean> = {
"orders.request_hash_idempotency": false,
"orders.price_source_snapshot": false,
"fiscal.emission.enabled": false,
"reservation.soft_hold.enabled": false,
};
const TRUE_VALUES = new Set(["1", "true", "on"]);
const FALSE_VALUES = new Set(["0", "false", "off"]);
// FLAG_ORDERS_REQUEST_HASH_IDEMPOTENCY, por ejemplo.
function envKey(flag: string): string {
return "FF_" + flag.toUpperCase().replace(/\./g, "_");
}
export function readFlags(env: NodeJS.ProcessEnv = process.env): Record<string, boolean> {
const out: Record<string, boolean> = { ...FLAG_DEFAULTS };
for (const name of Object.keys(FLAG_DEFAULTS)) {
const raw = env[envKey(name)];
if (raw === undefined) continue; // ausente: queda el default
const normalized = raw.trim().toLowerCase();
if (!TRUE_VALUES.has(normalized) && !FALSE_VALUES.has(normalized)) {
// Fallar al arrancar es mejor que interpretar "ture" como false en silencio.
throw new Error("Flag " + name + ": valor inválido");
}
out[name] = TRUE_VALUES.has(normalized);
}
// Guard del release 1: la emisión fiscal no se activa en producción.
if (out["fiscal.emission.enabled"] && env.NEXO_ENV === "production") {
throw new Error("fiscal.emission.enabled no puede activarse en producción en el release 1");
}
return out;
}
Fijate en tres decisiones del código. El valor inválido corta el arranque, porque un typo en una variable de entorno no debería desactivar silenciosamente una protección. El default vive en un solo lugar. Y el guard de producción convierte una restricción de alcance en una condición que el proceso verifica, no en una nota en el README.
Contraejemplo: el flag que cambia el significado de los datos
El antipatrón más peligroso es un flag que cambia qué formato se escribe, sin plan de migración. Si orders.request_hash_idempotency se activa en producción y la mitad de las instancias escribe hash y la otra mitad no, la verificación de reintentos empieza a tomar decisiones distintas según el nodo que atienda. El síntoma es un reintento que devuelve conflicto en un nodo y el mismo pedido en otro. La causa es un flag usado como switch de escritura sin que la fase expand estuviera completa. La corrección: el flag controla la lectura, la escritura dual está siempre encendida desde la fase migrar, y el flag se activa solo cuando el backfill terminó.
Otros dos antipatrones aparecen con frecuencia. El flag olvidado: lleva un año encendido en todos los entornos y el código que protege ya no tiene la versión apagada para probar. Y el flag disperso: se consulta en nueve archivos con condiciones distintas. Si el flag no tiene un único punto de lectura, nadie puede saber qué hace cuando se activa.
Cómo se verifica un flag
Un flag se verifica con dos pruebas. La primera confirma que con todos los flags en default el comportamiento es idéntico al anterior al cambio; un snapshot de las respuestas de la API en esa configuración alcanza. La segunda confirma que cada flag, encendido, produce el resultado esperado en el entorno donde se permite. Para el guard fiscal, la prueba es que el proceso no arranca con NEXO_ENV=production y el flag encendido.
Healthchecks: liveness y readiness
El problema que resuelve
Un contenedor en estado “running” no significa que el sistema pueda operar. La aplicación puede estar viva pero sin conexión a la base, con un schema anterior al que necesita o con un worker de outbox detenido hace veinte minutos. Si el balanceador manda tráfico a esa instancia, los vendedores ven errores aunque el dashboard del orquestador esté verde.
Los healthchecks tienen dos preguntas distintas, y responderlas con el mismo endpoint produce problemas en los dos sentidos.
- Liveness: ¿el proceso está vivo y no se trabó? No consulta dependencias. Si falla, el orquestador reinicia el proceso.
- Readiness: ¿puede atender tráfico ahora? Consulta lo que el proceso necesita para responder bien. Si falla, el balanceador deja de mandarle tráfico, pero no lo reinicia.
Ejemplo en Nexo: readiness que valida lo que importa
El readiness del release 1 verifica tres cosas: la base responde, el schema tiene al menos la versión que esta app necesita y el lag del outbox está dentro del umbral. No llama al ERP ni al proveedor fiscal. Esa decisión es la más importante del archivo.
import type { Pool } from "pg";
// 5 = 0005_expand_request_hash. Esta app funciona con esa versión o más.
export const REQUIRED_SCHEMA_VERSION = 5;
export const MAX_OUTBOX_LAG_SECONDS = 300;
export type Check = { name: string; ok: boolean; detail?: string };
export async function readiness(db: Pool): Promise<{ status: number; checks: Check[] }> {
const checks: Check[] = [];
try {
await db.query("select 1");
checks.push({ name: "db", ok: true });
} catch {
// No devolvemos el mensaje del error: puede contener datos de conexión.
checks.push({ name: "db", ok: false, detail: "sin conexión" });
return { status: 503, checks };
}
const schema = await db.query("select coalesce(max(version), 0) as v from schema_migrations");
const version = Number(schema.rows[0].v);
checks.push({
name: "schema",
ok: version >= REQUIRED_SCHEMA_VERSION,
detail: "version " + version + " de " + REQUIRED_SCHEMA_VERSION + " requerida",
});
const outbox = await db.query(
"select coalesce(extract(epoch from now() - min(created_at)), 0) as lag from outbox where processed_at is null"
);
const lag = Number(outbox.rows[0].lag);
checks.push({
name: "outbox",
ok: lag <= MAX_OUTBOX_LAG_SECONDS,
detail: "lag " + Math.round(lag) + " s",
});
const ok = checks.every((c) => c.ok);
return { status: ok ? 200 : 503, checks };
}
La regla de schema es la que conecta esta lección con la anterior. Si el deploy de la versión 5 se despliega antes de que la migración 0005 se aplique, las instancias nuevas no reciben tráfico. Y el check se diseña para funcionar también en el expand: la versión 4 sigue siendo válida con el schema 5, porque el expand no le quita nada.
Contraejemplo: readiness que llama al ERP
El antipatrón es incluir en readiness una llamada al ERP para “verificar la integración”. Cuando el ERP demora, todas las instancias fallan el readiness al mismo tiempo, el balanceador deja el servicio sin backends y la caída pasa de ser una integración lenta a una caída total de Nexo. Un sistema que depende de un proveedor externo para responder su propia salud se cae con el proveedor. La integración se monitorea con métricas de latencia y lag, no con el readiness.
Cómo se verifica un healthcheck
Un healthcheck se prueba con fallas inyectadas. Bajá la base y verificá que readiness devuelve 503 y liveness sigue en 200. Dejá el worker detenido hasta superar el umbral y verificá que readiness deja de anunciar la instancia. Hacé lenta la respuesta del ERP simulado y verificá que readiness no cambia. Si alguna de estas pruebas no da el resultado esperado, el healthcheck está describiendo otra cosa que lo que el equipo cree.
Observabilidad: preguntas antes que logs
El problema que resuelve
Después de un incidente, la pregunta no es “qué pasó en el servidor” sino “por qué este pedido quedó pendiente”, “cuánto tardó la autorización” o “qué actor autorizó la excepción”. Si el sistema no conserva las señales para responder esas preguntas, el equipo reconstruye el incidente desde memoria, capturas de pantalla y el relato del vendedor.
El error opuesto es loguear todo. Un console.log(req.body) en el endpoint de pedidos imprime precios, identificadores de clientes y, a veces, datos personales. Cada línea extra aumenta el costo y el riesgo, y el log deja de ser útil porque nadie lo lee.
Definición operativa
Antes de instrumentar, escribí las preguntas que la operación va a hacer. Cada pregunta define una señal, y cada señal tiene un responsable de mantenerla:
- Correlation ID. Sigue una ejecución técnica de punta a punta: entra por HTTP, pasa por el caso de uso, llega al worker. Es útil, pero no reemplaza los IDs del dominio.
- ID de dominio.
requestId,orderId,reservationId. Permiten reconstruir la intención de negocio aunque cambie la infraestructura. - Evento de auditoría. Registra quién hizo qué, sobre qué pedido, con qué regla y con qué resultado. Es un registro de negocio, no un log de depuración, y se conserva por el período que fija NFR-02.
- Métrica. Cuenta o mide algo que la operación necesita vigilar: rechazos por crédito, conflictos por carrera de stock, duración p95, lag del outbox.
Un log de ejemplo del release 1 muestra qué incluye y qué excluye:
{"ts":"2026-10-12T10:31:07.412-03:00","level":"info","event":"order.create.finished","release":"r1.4.2","schema":5,"request_id":"req-48172-a1","order_id":"O-20261012-0391","actor":"vendedor:V-07","cu":"CU-01","result":"pending_authorization","rule":"RN-01","duration_ms":412}
El registro no incluye el precio, las líneas ni el nombre del cliente. Alcanza para responder “¿por qué quedó pendiente?” (la regla RN-01 y el resultado), “¿cuánto tardó?” (la duración) y “¿quién lo intentó?” (el actor). Si un dato no ayuda a responder una pregunta de la operación, no se loguea.
Ejemplo: las preguntas que justifican cada señal
Las cinco preguntas del release 1 y la señal que las responde:
- ¿Por qué quedó pendiente este pedido? Evento
order.status_changedconreasony la regla aplicada. - ¿Un request se procesó dos veces? Métrica
idempotent_replays_totalmás conflictos registrados porrequest_id. - ¿Cuánto tardó la autorización de crédito? Evento con
started_atydecided_aten el caso de uso de autorización. - ¿Qué pedidos siguen esperando al depósito? Métrica
outbox_oldest_pending_age_seconds, con alerta si supera el umbral acordado. - ¿Qué actor autorizó la excepción? Evento de auditoría con actor, motivo obligatorio y pedido, sin el detalle de precios.
Contraejemplo: la alerta sin pregunta
Una alerta de “CPU mayor a 80 por ciento” no responde ninguna de las preguntas de la operación. Puede dispararse sin impacto para el vendedor, o no dispararse cuando el worker de outbox se detuvo y la CPU está tranquila. Una alerta útil nace de una pregunta: “¿el handoff a preparación se está atrasando?”. Esa pregunta se mide con el lag del outbox, y la alerta dice qué hacer cuando suena.
Cómo se verifica la observabilidad
La prueba es reconstruir un incidente real o inyectado usando solo las señales. Se crea un pedido de prueba, se fuerza un reintento con el mismo requestId y se intenta responder desde los logs y las métricas: qué pasó, cuántas veces, quién lo originó. Si el equipo necesita abrir la base de datos para responder, la señal no alcanza. Si necesita leer el payload completo, el log contiene de más.
Validación con usuarios: diseñar una sesión que pueda refutarnos
El problema que resuelve
Una demo donde el equipo opera y el usuario mira favorece el asentimiento. Las personas tienden a confirmar lo que ven, sobre todo cuando el que habla es quien construyó la solución. Además, un recorrido preparado usa siempre los datos que la solución resuelve bien. Si la demo no puede fallar, no puede enseñarnos nada que no sepamos.
Una sesión de validación cambia el rol. El usuario opera, el equipo observa, y la sesión está diseñada para descubrir si el problema que resolvimos es el problema real.
Definición operativa
Una sesión de validación útil tiene cuatro piezas que se preparan antes:
- Tareas con objetivo, sin pasos. “Cargá un pedido para este cliente con lo que pidió por WhatsApp” en lugar de “hacé clic en Nuevo pedido, después en agregar línea”. Los pasos le quitan al usuario el momento en que dudaría.
- Datos con casos difíciles. Un cliente en el límite de crédito, un SKU que cambió de código, un acuerdo sin fecha. Son los casos donde la solución puede equivocarse, y el usuario conoce las respuestas mejor que nosotros.
- Preguntas que buscan episodios. “¿Cuándo fue la última vez que te pasó?” sirve más que “¿te gusta?”. Los episodios revelan el proceso real.
- Un observador que no interviene. Anota dónde duda, qué dato busca afuera, qué palabra usa y dónde la expectativa no coincide con la solución.
Comparemos dos diseños de la misma sesión para ver qué cambia:
Dos diseños de la misma sesión de validación
El equipo muestra el pedido normal, luego el de autorización y después la última unidad. El vendedor mira. Al final se pregunta “¿qué te pareció?”. La respuesta esperable es “muy bien”, y el equipo cierra con una lista de próximos pasos.
El vendedor recibe el pedido de WhatsApp y el cliente DEMO-CLI-03, que está en el límite. Tiene que resolverlo solo. El observador anota que busca el saldo en una planilla que el sistema no muestra, que llama a la palabra “reserva” lo que el sistema llama “pendiente”, y que duda antes de confirmar un pedido que sabe que el cliente no puede pagar. Esas tres notas son el resultado de la sesión.
Contraejemplo: validar con quien ya está convencido
El antipatrón es validar con tres personas del equipo que conocen el sistema, o con el responsable que aprobó el alcance. Esas personas completan las tareas porque saben qué quiere el sistema, y su aprobación confirma la solución en lugar de probarla. La validación necesita personas que usen el proceso todos los días y que no hayan participado del diseño.
Otro antipatrón es la sesión sin dudas. Si el observador no anota ninguna duda, buscar afuera ni palabras distintas, el diseño tuvo demasiada guía. Revisá cuántos pasos dictaste antes de concluir que el usuario lo resolvió.
Cómo se verifica la sesión de validación
Una sesión está bien diseñada si al final podés contestar tres preguntas. ¿Qué tarea salió distinta de lo que esperábamos? ¿Qué dato buscó el usuario fuera del sistema? ¿Qué decisión de la solución quedó refutada o al menos cuestionada? Si la respuesta a las tres es “ninguna”, la sesión no tuvo la capacidad de refutar nada, y el resultado no sirve para decidir el go/no-go.
Gates: qué tiene que ser verdad antes de desplegar
Un pipeline de entrega convierte cada condición en una barrera verificable. El diagrama muestra el recorrido del release 1. Cada gate tiene un criterio observable y un responsable; si el criterio no se cumple, el cambio vuelve al paso anterior, sin excepciones silenciosas.
Lo importante del diagrama es el orden. La migración se ensaya sobre una copia antes de llegar a staging, y staging se ensaya con fallos antes de que alguien decida. El gate humano aparece una sola vez, antes de producción, y su criterio está escrito en el runbook, no en la cabeza de quien aprueba.
Errores frecuentes
- Síntoma: después del deploy, errores 500 solo en algunas instancias durante unos minutos. Causa: la migración eliminó o restringió una columna que la versión anterior seguía usando. Corrección: separar expand y contract, y verificar la compatibilidad N-1 antes del deploy.
- Síntoma: la migración tarda horas o bloquea la tabla de pedidos en el dry-run. Causa: un UPDATE masivo o un índice sin concurrencia sobre una tabla histórica. Corrección: backfill por lotes con checkpoint, y medir el lock con un
lock_timeoutexplícito. - Síntoma: un flag activado produce resultados distintos según el nodo que atienda. Causa: el flag controla la escritura en lugar de la lectura, con la fase migrar incompleta. Corrección: escritura dual siempre activa y el flag solo decide la lectura.
- Síntoma: el balanceador saca todas las instancias cuando el ERP se pone lento. Causa: el readiness depende de un sistema externo. Corrección: readiness solo valida dependencias propias, y las integraciones se miden con latencia y lag.
- Síntoma: el equipo tarda horas en explicar un pedido pendiente. Causa: no hay evento de decisión con regla y resultado, solo logs de infraestructura. Corrección: instrumentar a partir de preguntas operativas concretas.
Para llevarte
- Un release tiene tres tiempos: la convivencia de versiones, la validez de los datos históricos y la decisión de seguir o volver atrás.
- Expand/contract mantiene compatibles N y N+1. El contract se decide con telemetría y conteos, no con el calendario.
- Un feature flag tiene default seguro, alcance por entorno, dueño y fecha de retiro. Separa el deploy de la exposición.
- Liveness reinicia; readiness quita tráfico. Ninguno de los dos debe depender de un proveedor externo.
- Loguear es elegir preguntas. Cada señal responde a una pregunta de la operación y excluye datos que no la responden.
- La demo que puede refutar tiene tareas sin pasos, datos difíciles y usuarios que no participaron del diseño.
Comprobá tu criterio
Querés que request_hash sea NOT NULL en una tabla con datos históricos, y la versión actual todavía inserta filas sin ese campo. ¿Qué secuencia es segura?
Vocabulario del módulo
Glosario de integración y operación
Frente
Expand/contract
IA EN ESTA FASE
Qué puede hacer el agente: generar un borrador de runbook de deploy, comparar dos migraciones contra el mismo schema, revisar logs contra IDs de trazabilidad y proponer escenarios de fallo que no se nos ocurrieron.
Qué no debe decidir: declarar que un deploy es seguro sin ejecutarlo, decidir que un warning operativo es aceptable, ocultar una incompatibilidad para completar la demo o elegir el default de un flag que afecta datos.
Contexto mínimo útil: plan de release, migraciones con su fase, lista de flags con defaults, señales de observabilidad y los NFR de latencia y recuperación.
Cómo verificar: dry-runs sobre copia, pruebas de compatibilidad N-1, smoke tests en staging y observación real después del deploy. La salida del agente es una hipótesis que se valida con esas pruebas.
Prompt de ejemplo:
Sos un revisor de migraciones de PostgreSQL para Nexo B2B.
Te paso la migración 0005 y el schema actual de idempotency_keys.
Respondé en este formato:
1. Operaciones que toman locks exclusivos, con el nombre de la tabla.
2. Cambios que rompen la versión 4 de la aplicación durante el rollout.
3. Filas que podrían violar el nuevo schema, con la consulta que las encuentra.
4. Lo que NO podés afirmar sin ejecutar el dry-run en una copia.
No propongas cambios en el código de la aplicación.
Al revisar la salida, comprobá cada punto contra el dry-run. Si el agente dice que una operación es “instantánea” sin medirla, es una afirmación a verificar, no un dato.
¿Cómo valoras el contenido técnico de esta lección?
Tu feedback nos ayuda a mejorar los laboratorios prácticos de Eje Z.