🧪 Problem-Driven Systems Lab

👤 Caso 12 — Punto único de conocimiento y riesgo operacional#

Estado Stacks Categoría

[!IMPORTANT] 📖 Ver Análisis Técnico Senior de esta solución (PHP)

Este documento es un resumen ejecutivo. La evidencia de ingeniería, los algoritmos y la remediación profunda viven en el link de arriba y en comparison.md.


🔍 Qué problema representa#

Una persona, módulo o procedimiento concentra tanto conocimiento que el sistema se vuelve frágil ante ausencias o rotación. El clásico bus factor = 1: si la persona X se va de vacaciones, hay procesos que nadie sabe ejecutar; si renuncia, hay riesgo operativo serio.

No es solo problema humano — es problema arquitectónico, documental y operativo. El código sin documentación implícita "lo entiende solo quien lo escribió". El runbook que vive en la cabeza de una persona no es runbook, es memoria tribal. La continuidad operacional depende de externalizar ese conocimiento.


⚠️ Síntomas típicos#

  • Nadie sabe bien cómo funciona una parte crítica
  • Cambios dependen de una sola persona
  • Falta de documentación y de procedimientos claros
  • Incidentes se resuelven por memoria, no por método

🧩 Causas frecuentes#

  • Conocimiento no externalizado (vive en la cabeza, no en el repo)
  • Código acoplado y poco explicativo (nombres genéricos, lógica implícita)
  • Procesos manuales sin estandarización
  • Baja cultura de documentación y postmortems

🔬 Estrategia de diagnóstico#

  • Mapear zonas de dependencia humana o técnica
  • Revisar runbooks, ADRs, READMEs y procedimientos (¿existen? ¿están al día?)
  • Detectar pasos que solo una persona ejecuta
  • Analizar historial de incidentes y cuellos de soporte

💡 Opciones de solución#

  • Crear documentación accionable y runbooks (no solo descriptivos, ejecutables)
  • Reducir acoplamientos y modularizar donde aporte
  • Formalizar despliegues, soporte y troubleshooting
  • Hacer shadowing, pairing y transferencia explícita — no solo escribir, transferir

🗺️ Diagrama — Bus factor: dependencia humana vs runbook codificado#

text
  Legacy: acceso ciego                       Distributed: Optional<T> + chaining seguro

  scenario: owner_absent                      scenario: owner_absent
        │                                            │
        ▼                                            ▼
   ┌──────────────────────┐               ┌────────────────────────────────┐
   │ Owner owner =        │               │ Optional<Owner> ownerOpt =     │
   │   pickOwner(scenario)│               │   pickOwner(scenario)          │
   │  → null              │               │  → Optional.empty()            │
   └─────────┬────────────┘               └─────────────┬──────────────────┘
             │                                          │
             ▼                                          ▼
   ┌──────────────────────┐               ┌────────────────────────────────┐
   │ owner.runbook()      │               │ ownerOpt.map(o -> o.runbook()  │
   │  → NullPointerException│              │              .get(runbookKey))│
   └─────────┬────────────┘               │  .orElse(null)                 │
             ▼                              └─────────────┬──────────────────┘
   ╔═════════════════════╗                              │
   ║   CRASH             ║                              ▼
   ║   mttr = 120 min    ║                  ┌────────────────────────────────┐
   ║   bus_factor = 1    ║                  │ if script == null: usa runbook │
   ╚═════════════════════╝                  │ compartido por equipo          │
                                            └─────────────┬──────────────────┘
                                                          ▼
                                              ╔═════════════════════════════╗
                                              ║ degradacion controlada      ║
                                              ║ mttr = 35-50 min            ║
                                              ║ bus_factor = N (compartido) ║
                                              ╚═════════════════════════════╝

  /share-knowledge sube coverage +15 y bus_factor +1 cada vez.
  El tipo Optional<Owner> obliga a manejar el caso vacio: el lenguaje
  codifica el runbook que el legacy "olvido escribir".

🏗️ Implementación actual#

✅ PHP 8#

incident-legacy falla con owner ausente; incident-distributed chequea y degrada controlado. /share-knowledge sube cobertura. knowledge/state y diagnostics/summary exponen mttr_min, blocker_count, handoff_quality, bus_factor_min. Ver php/README.md. Modo aislado: puerto 8112.

Python 3.12#

Misma lógica con Optional-like via try/except + diccionario de owners. Ver python/README.md. Modo aislado: puerto 8312. Hub: http://localhost:8200/12/.

Node.js 22#

Optional chaining (a?.b?.c ?? default) como runbook codificado en el lenguajedistributed evita el crash que sufre legacy con acceso ciego a estructuras anidadas. share-knowledge sube coverage y baja mttr_min de forma medible. Ver node/README.md. Modo aislado: puerto 8212. Hub: http://localhost:8300/12/.

Java 21#

Optional<T> + map/flatMap/orElse como runbook codificado en el sistema de tipos. El crash legacy no es falla de Java — es falla de no usar las herramientas que Java ya ofrece. record Owner/Incident inmutables; AtomicInteger para coverage y bus_factor. Ver java/README.md. Modo aislado: puerto 8412. Hub: http://localhost:8400/12/.

.NET 8#

Operadores ?. (null-conditional) + ?? (null-coalescing) con Nullable Reference Types habilitado (<Nullable>enable</Nullable>) — el compilador advierte sobre desreferencias de Owner? sin chequear. El crash legacy no es falla de .NET — es falla de no usar las herramientas que C# moderno ya ofrece. record Owner/Incident inmutables; Interlocked.Add para coverage y bus_factor. Ver dotnet/README.md. Modo aislado: puerto 8512. Hub: http://localhost:8500/12/.


⚖️ Trade-offs#

  • Documentar bien toma tiempo sostenido — no es una semana, es un hábito
  • Modularizar sin criterio puede fragmentar de más
  • La transferencia de conocimiento requiere hábito, no solo archivos — los archivos viejos engañan

💼 Valor de negocio#

Reduce riesgo organizacional, mejora continuidad y hace al producto más sostenible a largo plazo. El indicador honesto: ¿qué pasa cuando la persona clave toma 2 semanas de vacaciones? Si la respuesta es "todo bien", el bus factor subió. Si la respuesta es "esperamos a que vuelva", la deuda sigue. Lab demuestra esa diferencia con métricas.


🛠️ Stacks disponibles#

StackEstado
🐘 PHP 8OPERATIVO (runbook checks + share-knowledge)
🐍 Python 3.12OPERATIVO (try/except + dict owners)
🟢 Node.js 22OPERATIVO (optional chaining ?. como runbook codificado)
☕ Java 21OPERATIVO (Optional<T> + map/flatMap/orElse + record types)
🔵 .NET 8OPERATIVO (?. + ?? con Nullable Reference Types + record types)

🚀 Cómo levantar#

Modo hub (recomendado):

bash
docker compose -f compose.root.yml    up -d --build && curl http://localhost:8100/12/health   # PHP
docker compose -f compose.python.yml  up -d --build && curl http://localhost:8200/12/health   # Python
docker compose -f compose.nodejs.yml  up -d --build && curl http://localhost:8300/12/health   # Node
docker compose -f compose.java.yml    up -d --build && curl http://localhost:8400/12/health   # Java

Demostrar el bus factor (ejemplo Java):

bash
# Legacy: owner ausente → NullPointerException → crashed, mttr 120 min
curl "http://localhost:8400/12/incident-legacy?scenario=owner_absent&runbook=db_failover"

# Distributed: Optional<T> + orElse → degradacion controlada, mttr 35-50 min
curl "http://localhost:8400/12/incident-distributed?scenario=owner_absent&runbook=db_failover"

# Compartir conocimiento sube cobertura y bus_factor
curl "http://localhost:8400/12/share-knowledge?owner=bob&runbook=db_failover"
curl http://localhost:8400/12/diagnostics/summary    # coverage 45, bus_factor 2

# Nuevo incidente con owner_absent: ahora hay alterno → mttr aún más bajo
curl "http://localhost:8400/12/incident-distributed?scenario=owner_absent&runbook=db_failover"

📚 Lectura recomendada#

DocumentoQué cubre
comparison.mdComparativa multi-stack con runbook codificado por lenguaje
docs/postmortem.mdPostmortem del incidente que motivó el caso
docs/context.mdPor qué el bus factor no es solo problema de RR.HH.
docs/symptoms.mdCómo se ve la dependencia tribal en el oncall
docs/root-causes.mdLas 4 causas más frecuentes
docs/solution-options.mdDocumentación accionable, shadowing, modularización
docs/trade-offs.mdLo que cuesta sostener la cultura
docs/business-value.mdImpacto en continuidad y rotación

📁 Estructura del caso#

12-single-point-of-knowledge-and-operational-risk/
├── README.md                    ← este archivo
├── comparison.md                ← comparativa multi-stack
├── compose.compare.yml          ← los 7 stacks juntos
├── docs/                        ← análisis + postmortem
├── shared/                      ← assets compartidos
├── 🐘 php/                      ← `OPERATIVO` — runbook checks + share-knowledge
├── 🐍 python/                   ← `OPERATIVO` — try/except + dict owners
├── 🟢 node/                     ← `OPERATIVO` — optional chaining como runbook
├── ☕ java/                     ← `OPERATIVO` — Optional<T> + record types
├── 🔵 dotnet/                   ← `OPERATIVO` — ?. + ?? con Nullable Reference Types + record types
├── 🐹 go/                   ← `OPERATIVO` — comma-ok + recover()
└── 🦀 rust/                   ← `OPERATIVO` — Option<T> + ? ; omitir None no compila

Ver esta carpeta en GitHub ↗