Nova Uptime
Guíasincident-responseescalationon-call

Alertas de email personalizadas y escalados: enrutamiento avanzado de incidentes

Diseña flujos de escalado que avisen a la persona adecuada en el momento adecuado. Guía sobre enrutamiento de alertas, integración on-call y políticas de.

SN
Sumit Nova Uptime
28 de febrero de 2026 · 9 min read
Share:

El problema del escalado#

Son las 3 de la mañana. Tu web se cae. Necesitas que alguien responda de inmediato.

Sin escalado:

  • La alerta salta al canal de Slack on-call
  • Nadie la ve (todo el mundo durmiendo, notificaciones de Slack apagadas)
  • 45 minutos después, las quejas de los clientes despiertan a alguien
  • MTTR: 45 minutos

Con escalado inteligente:

  • La alerta salta en Slack (prioridad baja)
  • Si no hay confirmación en 2 minutos → SMS al primario on-call
  • Si no hay respuesta en 5 minutos → llamada al backup on-call
  • MTTR: 5 minutos

Esta guía te muestra cómo construir flujos de escalado inteligentes.

Entender la severidad del incidente#

Lo primero, clasifica los incidentes por severidad:

Nivel 1: Crítico (avisar inmediatamente)#

  • Web de producción caída (impacto en ingresos)
  • Fallo en la pasarela de pago
  • API devolviendo errores 5xx
  • Replicación de base de datos caída

Acciones:

  • Slack #critical-incidents (siempre monitorizado)
  • SMS al primario on-call
  • Llamada telefónica si no hay respuesta por SMS
  • Creación automática de ticket en Jira
  • Actualización de la status page

Nivel 2: Aviso (notificar en horario laboral)#

  • Degradación de tiempos de respuesta
  • Errores en servicios no críticos
  • Problemas de deliverability de email
  • Lentitud en queries de base de datos

Acciones:

  • Slack #alerts (consultado en horario laboral)
  • Creación de ticket en Jira
  • Digest por email al final del día

Nivel 3: Información (solo log)#

  • Caducidad de dominio en 30 días
  • Caducidad de SSL en 90 días
  • Informe de tendencias semanal
  • Umbral de métrica no crítica

Acciones:

  • Digest semanal por email
  • Notificación en dashboard (sin avisar a nadie)

Construir tu política de escalado#

Paso 1: Define las rotaciones on-call#

Crea una rotación que muestre quién está on-call y cuándo:

Lunes-Viernes 9:00 - 17:00: Alice (primaria), Bob (backup)
Lunes-Viernes 17:00 - 9:00: Charlie (primario), Diana (backup)
Sábado-Domingo 24 h: Eve (primaria), Frank (backup)
Festivos: George (on-call todo el día)

Paso 2: Define los tiempos de escalado#

T+0:    La alerta salta en Slack
T+2min: Sin confirmación → SMS al primario
T+5min: Sin respuesta → llamada al primario
T+10min: Sin respuesta → SMS al backup
T+15min: Todavía sin respuesta → avisar al equipo completo

Paso 3: Implementa la lógica de escalado#

En Nova Uptime (si está soportado):

  1. Configuración del dominio → Alertas
  2. Establece severidad: Crítica
  3. Configura el escalado:
    • Paso 1: Slack #critical-incidents
    • Paso 2 (2 min): SMS al on-call
    • Paso 3 (5 min): llamada telefónica
    • Paso 4 (10 min): todo el equipo

Vía Webhook + sistema personalizado:

async function handleCriticalIncident({ domain, detectedAt }) {
  const oncall = await getOnCallEngineer(new Date());

  // Step 1: Immediate Slack alert
  const slackMessage = await postToSlack({
    channel: '#critical-incidents',
    text: `🚨 CRITICAL: ${domain} is down`,
    blocks: [
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `@${oncall.slackHandle}: ${domain} is down. Acknowledge with reaction.`
        }
      }
    ]
  });

  // Step 2: Wait 2 minutes for acknowledgment
  const acknowledged = await waitForAcknowledgment(slackMessage, 2 * 60 * 1000);

  if (!acknowledged) {
    // Step 2: Send SMS
    await sendSMS({
      to: oncall.phone,
      message: `CRITICAL: ${domain} down. Reply OK to acknowledge.`
    });
  }

  // Step 3: Wait 5 minutes total
  const smsAcknowledged = await waitForSMS(oncall.phone, 5 * 60 * 1000);

  if (!smsAcknowledged) {
    // Step 3: Phone call
    await makePhoneCall({
      to: oncall.phone,
      message: `Critical incident. Website ${domain} is down. Press 1 to acknowledge.`
    });
  }

  // ... Continue escalation chain
}

Avanzado: enrutamiento inteligente de alertas#

Patrón 1: Enrutar por hora del día#

Distintas personas on-call en distintos momentos.

async function getOnCallEngineer(timestamp) {
  const hour = new Date(timestamp).getHours();
  const dayOfWeek = new Date(timestamp).getDay();

  // Business hours (9 AM - 5 PM weekdays)
  if (dayOfWeek >= 1 && dayOfWeek <= 5 && hour >= 9 && hour < 17) {
    return {
      name: 'Alice',
      slackHandle: 'alice',
      phone: '+1-555-0100',
      email: 'alice@company.com'
    };
  }

  // After hours (weekdays)
  if (dayOfWeek >= 1 && dayOfWeek <= 5 && (hour < 9 || hour >= 17)) {
    return {
      name: 'Charlie',
      slackHandle: 'charlie',
      phone: '+1-555-0102',
      email: 'charlie@company.com'
    };
  }

  // Weekend
  if (dayOfWeek === 0 || dayOfWeek === 6) {
    return {
      name: 'Eve',
      slackHandle: 'eve',
      phone: '+1-555-0104',
      email: 'eve@company.com'
    };
  }
}

Patrón 2: Enrutar por dominio#

Cada equipo es dueño de dominios distintos.

async function getTeamForDomain(domain) {
  // Engineering team owns api.*, backend.*
  if (domain.startsWith('api.') || domain.startsWith('backend.')) {
    return 'engineering';
  }

  // Infrastructure team owns server, monitoring, infra
  if (domain.includes('server') || domain.includes('monitor')) {
    return 'infrastructure';
  }

  // Support team owns customer-facing domains
  if (domain.startsWith('support.') || domain.startsWith('customer.')) {
    return 'support';
  }

  // Default: DevOps
  return 'devops';
}

async function handleIncident({ domain, severity }) {
  const team = await getTeamForDomain(domain);
  const oncall = await getOnCallEngineer(team, new Date());

  if (severity === 'critical') {
    await escalate(oncall);
  }
}

Patrón 3: Enrutar por tipo de incidente#

Distinta especialidad para distintos tipos de fallo.

async function getExpertForIncident(domain, incidentType) {
  if (incidentType === 'database_down') {
    // Page database expert
    return await getOnCallExpert('database');
  } else if (incidentType === 'api_errors') {
    // Page API lead
    return await getOnCallExpert('backend');
  } else if (incidentType === 'email_delivery_failing') {
    // Page email ops
    return await getOnCallExpert('email');
  } else if (incidentType === 'ssl_expired') {
    // Page security
    return await getOnCallExpert('security');
  }

  // Default: on-call engineer
  return await getOnCallEngineer(new Date());
}

Patrón 4: Escalado condicional#

Distintas rutas de escalado según las propiedades del incidente.

async function escalateIncident({ domain, severity, duration }) {
  const oncall = await getOnCallEngineer(new Date());

  if (severity === 'critical' && duration > 5 * 60 * 1000) {
    // Critical for >5 minutes: Aggressive escalation
    await Promise.all([
      postSlack({ channel: '#critical-incidents', text: 'CRITICAL ESCALATION' }),
      sendSMS(oncall.phone),
      makePhoneCall(oncall.phone),
      pageBackup(oncall.backup)
    ]);
  } else if (severity === 'critical') {
    // Critical but recent: Gentle escalation
    await Promise.all([
      postSlack({ channel: '#critical-incidents' }),
      sendSMS(oncall.phone)
    ]);
  } else if (severity === 'warning') {
    // Just log
    await postSlack({ channel: '#alerts' });
  }
}

Integración con PagerDuty#

Para una gestión sofisticada del on-call, intégrate con PagerDuty:

const PagerDutyClient = require('pagerduty');

async function pageOnCallVia PagerDuty(domain, severity) {
  const client = new PagerDutyClient({
    token: process.env.PAGERDUTY_TOKEN
  });

  // Create incident
  const incident = await client.incidents.create({
    type: 'incident_reference',
    incident: {
      type: 'incident',
      title: `${domain} is down`,
      body: {
        type: 'incident_body',
        description: `Website ${domain} is down. Response: Down. Severity: ${severity}`
      },
      urgency: severity === 'critical' ? 'high' : 'low',
      service: {
        id: process.env.PAGERDUTY_SERVICE_ID,
        type: 'service_reference'
      }
    }
  });

  console.log(`Created PagerDuty incident: ${incident.id}`);
}

Confirmación y traspaso#

Patrones de confirmación#

El ingeniero on-call tiene que confirmar el incidente:

// Via Slack reaction
async function waitForSlackAcknowledgment(messageId, maxWait) {
  const startTime = Date.now();

  while (Date.now() - startTime < maxWait) {
    // Poll for reactions
    const reactions = await getSlackMessageReactions(messageId);

    if (reactions.includes('white_check_mark')) {
      return true; // Acknowledged
    }

    await sleep(10 * 1000); // Check every 10 seconds
  }

  return false; // Not acknowledged within max wait
}

// Via SMS
async function waitForSmsAcknowledgment(phone, maxWait) {
  const startTime = Date.now();

  while (Date.now() - startTime < maxWait) {
    // Poll SMS responses
    const responses = await getSmsResponses(phone);

    if (responses.some(m => m.text.toUpperCase().includes('OK'))) {
      return true; // Acknowledged
    }

    await sleep(5 * 1000); // Check every 5 seconds
  }

  return false; // Not acknowledged
}

Traspaso entre equipos#

Cuando un ingeniero on-call necesita pasar el testigo a otro:

async function handoffIncident(incident, fromEngineer, toEngineer) {
  // Update incident
  incident.assignedTo = toEngineer;
  incident.handoffAt = new Date();
  await incident.save();

  // Notify both
  await sendMessage({
    to: fromEngineer.slack,
    text: `Handing off ${incident.domain} to ${toEngineer.name}`
  });

  await sendMessage({
    to: toEngineer.slack,
    text: `Taking over incident: ${incident.domain}. See: ${incident.dashboard}`
  });

  // Update status page
  await updateStatusPage({
    message: `Working with ${toEngineer.name}'s team on investigation`
  });
}

Medir la eficacia del escalado#

Mide las métricas de escalado:

async function analyzeEscalationMetrics() {
  const incidents = await Incident.find({
    createdAfter: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) // Last 30 days
  });

  const metrics = {
    totalIncidents: incidents.length,
    avgTimeToAcknowledgment: calculateAvg(
      incidents.map(i => i.acknowledgedAt - i.alertedAt)
    ),
    avgTimeToEscalation: calculateAvg(
      incidents.map(i => i.escalatedAt - i.alertedAt)
    ),
    escalationRate: incidents.filter(i => i.escalatedAt).length / incidents.length,
    avgMTTR: calculateAvg(
      incidents.map(i => i.resolvedAt - i.alertedAt)
    )
  };

  console.log('Escalation Metrics (Last 30 days):');
  console.log(`Total Incidents: ${metrics.totalIncidents}`);
  console.log(`Avg Time to Acknowledgment: ${metrics.avgTimeToAcknowledgment / 60}m`);
  console.log(`Escalation Rate: ${(metrics.escalationRate * 100).toFixed(1)}%`);
  console.log(`Avg MTTR: ${metrics.avgMTTR / 60}m`);
}

Errores comunes en el escalado#

Error 1: fatiga de alertas que lleva a ignorarlas#

Problema: demasiadas alertas → el equipo deja de responder → se pierden los problemas reales

Solución: usa umbrales estrictos. Avisa solo en incidentes realmente críticos.

Error 2: escalar de forma demasiado agresiva#

Problema: avisar a todo el equipo en cada incidente → burnout → la gente se va

Solución: escala de forma gradual. Dale al primario 5 minutos antes de avisar al backup.

Error 3: escalar demasiado despacio#

Problema: un incidente crítico pasa desapercibido durante 30 minutos → impacto enorme

Solución: para incidentes críticos, escala en 2-5 minutos.

Error 4: sin proceso de traspaso#

Problema: el on-call primario no sabía que le estaban pasando el incidente → caos

Solución: comunica los traspasos de forma explícita por Slack/email.

Resumen: checklist de configuración del escalado#

  • ✅ Define los niveles de severidad de incidente (Crítico/Aviso/Info)
  • ✅ Crea un calendario de rotación on-call
  • ✅ Define los tiempos de escalado (2 min → 5 min → 10 min → todos)
  • ✅ Configura los canales de escalado (Slack → SMS → llamada → todo el equipo)
  • ✅ Configura el enrutamiento por hora del día
  • ✅ Enruta por dominio/equipo responsable
  • ✅ Enruta por tipo de incidente (base de datos vs API vs email)
  • ✅ Intégrate con PagerDuty (si aplica)
  • ✅ Configura los mecanismos de confirmación (reacción en Slack, respuesta SMS)
  • ✅ Define los procedimientos de traspaso
  • ✅ Mide las métricas de escalado
  • ✅ Revisión mensual de la eficacia del escalado

Empieza hoy#

Empieza simple: solo Slack + SMS. Añade complejidad (PagerDuty, enrutamiento condicional) según vaya creciendo el volumen de incidentes.

Documenta tu política de escalado en la wiki del equipo. Compártela con todo el equipo. Pruébala cada trimestre para asegurarte de que todo sigue funcionando.

Tu política de escalado es la diferencia entre "incidente resuelto en 5 minutos" y "caída de 3 horas". Invierte en hacerlo bien.

Monitor Your Website Before It Goes Down

Get uptime monitoring, SSL tracking, domain expiry alerts, and email health checks. Free plan — no credit card required.

Start Monitoring Free

Artículos relacionados