Módulo 8: Proyecto — Sistema de Alertas Programadas

Paso 4: Escalación con HITL

Descripción de la cápsula

Las alertas críticas requieren acknowledgement (ack) humano. Si nadie ack en 15 minutos, escalar al manager. Esto requiere 2 workflows adicionales: [ALERTS-ACK] (recibe acks) y [ALERTS-ESCALATE] (chequea pendientes).


Workflow [ALERTS-ACK]

Trigger

Webhook que recibe ack del oncall (desde el botón de Slack o link de email).

Webhook:
- Method: POST
- Path: /alerts-ack
- Auth: Header X-Ack-Secret

Procesamiento

[Webhook]
   │
[Set: extraer alert_id del payload]
   │
[Sheet: actualizar 'alerts_pending_ack']
- Find row con alert_id
- Set ack_at = $now
- Set ack_by = $json.body.user (si está)
   │
[Slack: confirmar al oncall: "Alert {alert_id} ack-eada"]

Link en notificación original

En el Slack message original al canal #alerts-critical (cápsula 05), incluir botón Block Kit:

{
  "type": "button",
  "text": { "type": "plain_text", "text": "✅ Acknowledge" },
  "url": "https://your-n8n/webhook/alerts-ack?alert_id={{ alert_id }}"
}

Click → GET con alert_id → workflow [ALERTS-ACK] recibe → marca como acknowledged.

Para Slack interactive realmente requiere webhook POST con payload, no GET. Implementación simplificada acá; en producción usar Slack interactivity con Request URL.


Workflow [ALERTS-ESCALATE]

Trigger

Schedule cada 5 minutos
Cron: */5 * * * *
TZ: America/Mexico_City

Procesamiento

[Schedule]
   │
[Sheet: leer 'alerts_pending_ack']
- Filter: ack_at is empty (sin ack) AND created_at > 15 min ago
   │
[IF: hay alertas sin ack > 15 min?]
└─ TRUE → escalar

Escalación

[SplitInBatches batch=1]
   │
[Slack: a #manager-alerts]
"🚨 ALERTA SIN ACK - Manager attention required
Alert ID: {alert_id}
Métrica: {metric}
Tiempo sin ack: 15+ minutos
Detalles: ..."
   │
[Sheet: actualizar alerts_pending_ack]
- Set escalated_at = $now
   │
   └─→ loop back

Verificar no escalar 2 veces

Si la escalación corre cada 5 min, podría re-escalar la misma alert. Filter para evitar:

[Sheet: filter 'alerts_pending_ack']
- ack_at is empty
- AND created_at > 15 min ago
- AND escalated_at is empty  ← clave

Solo escalar las que aún no se escalaron.


Workflow [ALERTS-MAIN] actualizado

En la rama "crítica" (cápsula 05), después de notificar:

[Slack #alerts-critical, Email, etc. en paralelo]
   │
[Sheet append 'alerts_pending_ack']
- alert_id: {{ $now.toMillis() }} + random
- metric
- severity: 'critical'
- created_at: $now
- ack_at: empty
- escalated_at: empty

Cada crítica se trackea para posible escalación.


Lifecycle de una alerta crítica

T+0:     Alert detectada
         → Slack notification
         → Email a oncall
         → Sheet 'alerts_pending_ack' append
T+15:    [ALERTS-ESCALATE] chequea
         → Si ack_at empty → escalar a manager
         → Sheet update escalated_at
T+30:    Manager debería responder
         (siguiente escalación si configuras: a director, etc.)

Workflow para "alert resolved"

Opcional pero útil: workflow [ALERTS-RESOLVED] para cerrar alertas cuando el problema se resuelve.

[Webhook /alerts-resolved]
   │
[Sheet update: resolved_at = $now]
   │
[Slack: "Alert X resuelta"]

Test de la cápsula

Test 1: Crítica con ack rápido

  • Disparar alerta crítica
  • Verificar Slack llega
  • Hacer POST al webhook ack inmediatamente
  • Verificar Sheet 'alerts_pending_ack' tiene ack_at
  • 15 min después: escalation NO debería disparar

Test 2: Crítica sin ack

  • Disparar alerta crítica
  • NO hacer ack
  • Esperar 15-20 min
  • [ALERTS-ESCALATE] debería:
    • Detectar la alert sin ack
    • Mandar Slack al manager
    • Marcar escalated_at

Test 3: Múltiples críticas

  • Generar 3 críticas
  • Ack 1, dejar 2 sin ack
  • Escalación debería escalar las 2 sin ack

Validación

  • [ALERTS-ACK] recibe webhook y actualiza Sheet
  • [ALERTS-ESCALATE] corre cada 5 min y escala correctamente
  • No escala 2 veces (filter de escalated_at)
  • Slack message principal tiene botón ack funcional
  • Auditoría completa en Sheet

Lo que sigue: Paso 5 — resilience completo del sistema.


Creado: Mayo 11, 2026 Versión: 1.0