Satélite Transversal Mapa avanzado

Matriz transversal: riesgo → control → prueba → evidencia → señal → runbook

El ciclo completo de la serie en una tabla: 22 riesgos de negocio con su control arquitectónico, la prueba que lo demuestra, la evidencia en CI, la señal en producción y el runbook. Una columna vacía es un riesgo sin controlar.

Fecha: 2026-07-10. Dominio ficticio: Nexo Finanzas. Ningún número es una medición.

Este documento existe porque el prompt maestro pide un ciclo cerrado y los ocho capítulos lo recorren por separado. Acá está entero, en una tabla, para que se pueda auditar de una sola lectura.

Diagrama: matriz-riesgo-control-evidencia (1)

Cómo se usa

  1. Un riesgo sin control es una preocupación. Si la columna “control” está vacía, no tenés nada.
  2. Un control sin prueba es una intención. Si la columna “prueba” está vacía, no sabés si el control funciona.
  3. Una prueba sin señal en producción es una esperanza. Los tests no corren en producción. Si la columna “señal” está vacía, el día que el control falle nadie se va a enterar.
  4. Una señal sin runbook es una alarma que despierta a alguien que no sabe qué hacer.

La fila está completa cuando las seis columnas lo están. Esa es la definición de “riesgo controlado” que esta serie sostiene.


La matriz

Ordenada por prioridad. Los cinco primeros son los que definen el portfolio: si no los demostrás, no demostraste nada.

#Riesgo de negocioControl arquitectónicoPrueba que lo demuestraEvidencia en CISeñal en producciónRunbook
R-1Una transferencia se debita dos veces porque el cliente reintentó tras un timeoutClave de idempotencia con reclamo atómico (UNIQUE, insert-first)concurrenciaMismaClaveProduceUnaEjecucion (con repetición)Reporte de test con riskId: R-1Duplicados por clave de negocio > 0reconciliation-break
R-2El dinero se descuadra: un débito sin su créditoLedger de doble entrada en una transacción localINV-1: suma de asientos por transferencia = 0Invariantes SQL ejecutadas en CIAlerta sobre INV-1 en producciónreconciliation-break
R-3La transferencia se guarda y el evento nunca se publica: pérdida silenciosaTransactional outboxoutboxSePublicaTrasReiniciarElPublisherTest de integraciónAntigüedad del pendiente más viejo (no la cantidad)outbox-backlog
R-4Se publica un evento de una transferencia revertida: hecho inventadoOutbox (imposible por construcción)rollbackNoDejaEventoEnOutboxTest de componenteINV-3 / HUERFANO_EN_LEDGERreconciliation-break
R-5El broker reentrega y el consumidor acredita dos vecesInbox idempotente UNIQUE(eventId), misma tx que el efectoentregaDuplicadaProduceUnSoloEfecto (con repetición)Test de integraciónDuplicados por clave de negocioreplay-dead-letter-events
R-6Un log o un response filtra un documento, un correo o un tokenContrato de telemetría; mensaje de log constante y campos declaradoslogsYResponsesNoContienenPatronesProhibidos (camino de error)El test corre en CI y reporta ubicación, nunca el valorHallazgos de PII por tipo, por semana
R-7Se despliega un artefacto que nadie construyó en este pipelineFirma keyless + gate con --certificate-identity + lectura de la provenanceimagenFirmadaPorIdentidadNoAutorizadaEsRechazadaAttestation + firma adjuntas al releaseFirmas en Rekor de identidades fuera de la allowlistdesactivar-politica-admision
R-8Una persona ficticia transfiere desde una cuenta ajena (BOLA)Autorización por objeto en cada accesotransferirDesdeCuentaAjenaDevuelve403o404Test de seguridad en CITasa de 403/404 por cuenta
R-9Un mensaje envenenado bloquea la partición o se reintenta al infinitoClasificación de errores + DLQ + backoff con jittererrorPermanenteVaADlqAlPrimerIntentoTest de integraciónProfundidad de la DLQreplay-dead-letter-events
R-10Un cambio de schema rompe a un consumidor que no sabías que existíaCompatibilidad forward + registro de consumidores + métrica de consumoconsumidorViejoConSchemaNuevoFallaDeFormaDetectableGate bloqueante de compatibilidad en el PRConsumo por canal y versión de schema
R-11Un cambio malo llega al 100 % de los usuarios antes de que nadie mire una señalDeployment ≠ release; canary con guardrails contra baselineSmoke post-deploy sobre código desplegado y apagadoHipótesis de release versionadaGuardrail de negocio (completitud), no solo CPUdisable-feature
R-12El rollback se ejecuta por primera vez durante un incidenteKill switch ejecutable por on-call, sin aprobaciónTest del camino degradado de cada kill switchdegraded_behavior + tested_in en el registro de flagsDistribución de evaluaciones del flagdisable-feature (con tabla de ensayos)
R-13Un flag de release se vuelve permanente y su combinatoria intestableRegistro de flags con expires obligatoriolaConfiguracionRealDeProduccionPasaElJourneyJob que abre ticket ante flags vencidos (no rompe el build)Flags vencidos, en lista pública
R-14El reporte y la API cuentan historias distintas y nadie puede explicar por quéADR de source of truth + tolerancias con razón escritaLas cinco invariantes, nombradas por riesgoInvariantes en CI contra el dataset sintético% reconciliado sin meta: cualquier valor < 100 % es un ticketreconciliation-break
R-15El pipeline está detenido y todos los datos son perfectamente consistentes y viejosMétrica de freshness por dataset— (no se prueba: se monitorea)SLO de freshness declaradonow() - max(occurred_at) independiente de la reconciliaciónbackfill
R-16Un backfill duplica dinero porque el pipeline no es idempotenteUPSERT sobre clave natural, restricción en el schema (no exists() en la app)BackfillIdempotenteTest: ejecutar dos veces, compararTest en CISilenciamiento de reconciliación con TTLbackfill
R-17Una decisión de riesgo de hace tres meses no se puede reproducir ni explicarruleSetVersion + featureSnapshot congelado + reasonCodedecisionHistoricaSeReproduceConSuSnapshotbacktestReport obligatorio en el manifiesto del rulesetDecisiones sin reasonCode = 0rule-rollback
R-18Un ruleset nuevo duplica la carga de revisión humana y se descubre el lunesShadow mode → canary → rolloutBacktest con impacto operativo en casos por cada 100.000Reporte de backtest en evidence/% enviado a revisión como guardrail bloqueanterule-rollback
R-19Un umbral absoluto envejece con la inflación y la cola crece sin que nadie toque el códigoUmbrales relativos donde es posible; si no, fecha de recalibraciónDistribución de reasonCode por semana (la señal de drift más barata)rule-rollback
R-20Un test flaky consume horas de ingeniería que ninguna factura muestraEspera por condición observable; prohibición de sleepgrep -r 'Thread.sleep' falla el buildflakyHistory viaja con cada resultadoMinutos desperdiciados por flakiness
R-21Una excepción de vulnerabilidad aceptada nunca se revisaRegistro con owner y expires_at; VEX con justificacióncheck-expired-exceptions.shÚnico gate bloqueante recomendado del capítulo 02Excepciones vencidas > 0
R-22Una plataforma rompe tres suites un martes con un cambio “menor”En un componente de test, todo cambio que altere el resultado de un test existente es MAJORSuite de contrato del componenteCOMPATIBILITY.md con guarantees y not_guaranteedFallos de plataforma vs de productodeprecations/policy

Las seis invariantes, y qué fila las protege

Del capstone. Una invariante es una afirmación que debe ser verdadera siempre.

InvarianteFilas que la protegen
1. Una Idempotency-Key no crea dos transferenciasR-1
2. Débitos y créditos permanecen balanceadosR-2, R-14
3. Un evento se procesa como máximo una vez a nivel de efecto de negocioR-3, R-4, R-5, R-9, R-16
4. Toda decisión de regla tiene versión y reason codeR-17, R-19
5. La reconciliación puede explicar las diferenciasR-14, R-15
6. Logs y trazas no contienen secretos ni PIIR-6

Regla de alcance del capstone: si una capacidad no protege una de estas seis invariantes ni una fila de la matriz, no entra en el MVP. Va al backlog con justificación.


Las filas donde la señal de producción es la única defensa

Miralas de nuevo: R-15 y R-19 no tienen prueba automatizada, y no es un descuido.

  • R-15 (pipeline detenido). Un pipeline que no corre pasa todas las validaciones de datos que sí corren. No hay test que detecte la ausencia de ejecución. La única defensa es una señal de freshness independiente de la reconciliación —porque si la reconciliación también está detenida, tampoco alerta.
  • R-19 (drift de umbral). Nada cambió en el código. Ningún test puede fallar. La única defensa es observar la distribución de reasonCode a lo largo del tiempo.

Estas dos filas son la razón por la que la columna “señal en producción” existe en esta matriz. Un Quality Engineer que solo piensa en tests deja estos dos riesgos completamente descubiertos, y ambos son silenciosos.


Las filas donde el control es no hacer algo

  • R-6: el control más eficaz contra la fuga de datos es no recolectar el dato. Un dato que no existe no se filtra, no se retiene y no aparece en un incidente.
  • R-13: el control contra la deuda de flags es borrar el flag y el camino viejo, no gestionarlos mejor.
  • R-20: el control contra el costo del flakiness es arreglar el flakiness, no optimizar el pipeline que lo ejecuta.

Los tres son ejemplos del mismo principio: la capacidad más barata de operar es la que no construiste.


Cómo se llena esta matriz en tu proyecto

  1. Escribí los riesgos en términos de lo que pierde el negocio, no de lo que falta técnicamente.
    • “No hay tests de idempotencia.” (Eso es el gap.)
    • “Una transferencia podría debitarse dos veces si el cliente reintenta tras un timeout.”
  2. Priorizá por Impacto × Probabilidad, no por Impacto × Facilidad de arreglo. La facilidad decide el orden de ejecución, no la prioridad.
  3. La columna “prueba” solo acepta un nombre de test que exista. "Hay tests" no es una entrada válida.
  4. La columna “señal” te va a estar vacía en la mitad de las filas la primera vez. Ese es el hallazgo más valioso del ejercicio.

Plantilla en blanco: 09-relevamiento-de-un-proyecto-existente/artefactos/matrices-de-diagnostico.md.


Fuentes

Cada fila se desarrolla en su capítulo, con sus fuentes primarias. Ver el índice de la serie y la verificación de fuentes.

Relacionados en el blog

esc
↑↓ navegar abrir