Introducción al Diseño de una API de Gestión de SLA y Escalaciones con FastAPI: Patrones Prácticos para no Retrasar la Atención de Consultas
Resumen
- La gestión de SLA consiste en definir claramente “hasta cuándo debe darse la primera respuesta” y “hasta cuándo debe resolverse” una consulta o ticket, para evitar incumplimientos de plazo.
- En FastAPI, conviene diseñarlo como una API que calcula plazos SLA al crear tickets y permite buscar, notificar y escalar tickets próximos a vencer o ya vencidos. Así se estabiliza la operación de soporte.
- Lo importante es no tratar el SLA como una simple columna de fecha. Es más seguro encapsularlo en la capa de servicio como una “regla de negocio” que considera prioridad, plan, horario laboral, responsable, estado y días no laborables.
- La escalación es el mecanismo para elevar un caso a responsables superiores u otros equipos cuando se supera un plazo. Si se combinan notificaciones, cambio de responsable, cambio de prioridad y logs de auditoría, el diseño resiste mejor la operación real.
- Este artículo organiza paso a paso modelos de datos, cálculo de plazos, API de búsqueda, notificaciones, logs de auditoría y estrategia de pruebas para crear una API de gestión de SLA y escalaciones con FastAPI.
Quién se Beneficia al Leerlo
Personas en Desarrollo Individual o Aprendizaje
Está dirigido a quienes, después de crear una función de consultas, empiezan a sentir que “las consultas sin responder se entierran” o “no sé cuál atender primero”.
Al principio parece suficiente que un ticket tenga solo status, pero cuando aumentan las consultas se vuelve necesario gestionar plazos y prioridades. Este artículo explica cómo introducir el concepto de SLA de forma pequeña.
Ingenieros Backend de Equipos Pequeños
Está dirigido a equipos donde ya hay varias personas de CS y empiezan a necesitar responsables, prioridades, vencimientos y avisos a superiores.
Podrás ordenar cómo expresar reglas como “responder urgent rápidamente”, “priorizar clientes Enterprise” o “avisar al manager si no hay respuesta en 24 horas” en la capa de servicio o jobs de FastAPI.
Equipos SaaS y Startups
Está dirigido a equipos donde el SLA impacta directamente en contratos de cliente y calidad de soporte.
Tiempo de atención, tiempo de primera respuesta, tiempo de resolución, tasa de escalación y tasa de incumplimiento influyen en satisfacción y churn. Al organizar SLA como API en FastAPI, es más fácil conectarlo con dashboards de CS, sistemas de notificación, auditoría y reportes.
Evaluación de Accesibilidad
- Se coloca al inicio un resumen y público objetivo para entender rápidamente el propósito del artículo.
- Términos como “SLA”, “escalación” y “plazo de primera respuesta” se explican brevemente al aparecer por primera vez.
- Los ejemplos de código se dividen en bloques cortos, cada uno con una sola responsabilidad.
- Solo siguiendo los encabezados se puede entender el flujo desde concepto hasta implementación, operación y pruebas.
- El nivel objetivo es equivalente a AA.
1. Qué es SLA: Proteger en Código la “Promesa” de Atención
SLA significa Service Level Agreement y representa el nivel de servicio acordado entre proveedor y usuario. En el contexto de atención de consultas, suele expresarse como promesas como:
- primera respuesta en 24 horas
- consultas urgent en 2 horas
- atención prioritaria para clientes Enterprise
- objetivo de resolución en 3 días hábiles
- excluir horas fuera del horario laboral del cálculo
El objetivo de gestionar SLA no es solo mostrar un plazo.
Lo realmente importante es evitar omisiones, detectar incumplimientos rápidamente y, si hace falta, elevar el caso a un responsable o equipo superior.
Por eso, la gestión de SLA necesita:
- cálculo de plazos
- detección de incumplimientos
- gestión de prioridad
- gestión de responsables
- notificaciones
- escalación
- logs de auditoría
- reportes
En FastAPI, es más fácil gestionarlo si no se escribe directamente en routers, sino en capa de servicio o funciones de política.
2. Qué es Escalación: Un Mecanismo para Elevar la Atención
La escalación consiste en elevar una consulta a un responsable superior u otro equipo cuando se cumplen ciertas condiciones.
Por ejemplo:
- se superó el plazo de primera respuesta
- se superó el plazo de resolución
- una consulta urgent sigue sin responsable
- el responsable no actualiza durante cierto tiempo
- el cliente está en plan Enterprise
- el mismo cliente envió varias consultas en poco tiempo
La escalación no es una simple notificación.
En operación real puede incluir acciones como:
- notificar al manager
- cambiar automáticamente el responsable
- subir la prioridad
- añadir una nota interna
- registrar una infracción de SLA
- dejar un log de auditoría
Es decir, la escalación es un proceso de negocio que visibiliza retrasos y conecta con la siguiente acción.
3. Modelo de Datos Básico: Añadir Información SLA al Ticket
Primero, añadimos campos relacionados con SLA al ticket de consulta.
from datetime import datetime
from pydantic import BaseModel
from typing import Literal
TicketStatus = Literal[
"open",
"pending",
"waiting_customer",
"resolved",
"closed",
]
TicketPriority = Literal["low", "normal", "high", "urgent"]
class TicketRead(BaseModel):
id: int
tenant_id: int | None = None
requester_email: str
subject: str
status: TicketStatus
priority: TicketPriority
assignee_id: int | None = None
first_response_due_at: datetime | None = None
resolution_due_at: datetime | None = None
first_responded_at: datetime | None = None
resolved_at: datetime | None = None
escalated: bool = False
created_at: datetime
updated_at: datetime
Los campos importantes son:
-
first_response_due_at
Plazo de primera respuesta -
resolution_due_at
Plazo de resolución -
first_responded_at
Hora real de la primera respuesta -
resolved_at
Hora real de resolución -
escalated
Indica si ya fue escalado
Al guardar tanto plazos como resultados reales, luego se pueden calcular tasa de cumplimiento SLA y tiempos de atención.
4. Definir Reglas SLA por Plan y Prioridad
El SLA no siempre es igual para todas las consultas.
Suele variar por prioridad o plan contratado.
Ejemplo:
from dataclasses import dataclass
from datetime import timedelta
@dataclass(frozen=True)
class SLARule:
first_response_within: timedelta
resolution_within: timedelta
SLA_RULES = {
("free", "normal"): SLARule(
first_response_within=timedelta(hours=48),
resolution_within=timedelta(days=7),
),
("pro", "normal"): SLARule(
first_response_within=timedelta(hours=24),
resolution_within=timedelta(days=3),
),
("enterprise", "normal"): SLARule(
first_response_within=timedelta(hours=8),
resolution_within=timedelta(days=2),
),
("enterprise", "urgent"): SLARule(
first_response_within=timedelta(hours=2),
resolution_within=timedelta(hours=12),
),
}
Aquí se decide el SLA por combinación de plan_code y priority.
En la práctica también pueden existir excepciones por contrato o SLA especiales.
Aun así, concentrar las reglas en un solo lugar facilita los cambios.
5. Crear un Servicio para Calcular Plazos SLA
Al crear un ticket, calculamos los plazos SLA.
from datetime import datetime, timezone
def resolve_sla_rule(plan_code: str, priority: str) -> SLARule:
return SLA_RULES.get(
(plan_code, priority),
SLA_RULES[("free", "normal")],
)
def calculate_sla_deadlines(
created_at: datetime,
plan_code: str,
priority: str,
) -> tuple[datetime, datetime]:
rule = resolve_sla_rule(plan_code, priority)
first_response_due_at = created_at + rule.first_response_within
resolution_due_at = created_at + rule.resolution_within
return first_response_due_at, resolution_due_at
Este ejemplo simplemente suma tiempo al momento actual.
Pero en la práctica puede ser necesario considerar horarios laborales y días festivos.
Puedes empezar con un cálculo simple, pero conviene asumir que más adelante podrían entrar:
- horario laboral
- fines de semana y festivos
- zona horaria por tenant
- SLA especial por plan
- fin de año o días de mantenimiento
Por eso se recomienda separar el cálculo de plazos en una capa de servicio y no escribirlo directamente en el router.
6. Establecer Plazos SLA al Crear una Consulta
En el servicio de creación de tickets, calculamos los plazos SLA.
from datetime import datetime, timezone
from pydantic import BaseModel
class TicketCreate(BaseModel):
requester_email: str
subject: str
body: str
priority: TicketPriority = "normal"
class TicketService:
def create_ticket(
self,
payload: TicketCreate,
tenant_plan: str,
) -> dict:
now = datetime.now(timezone.utc)
first_due, resolution_due = calculate_sla_deadlines(
created_at=now,
plan_code=tenant_plan,
priority=payload.priority,
)
ticket = {
"id": 1,
"requester_email": payload.requester_email,
"subject": payload.subject,
"status": "open",
"priority": payload.priority,
"first_response_due_at": first_due,
"resolution_due_at": resolution_due,
"created_at": now,
"updated_at": now,
}
return ticket
Así, en el momento de crear el ticket queda definido “hasta cuándo debe atenderse”.
Guardar el plazo al crear el ticket facilita búsquedas y notificaciones, en lugar de recalcularlo siempre después.
7. Registrar la Primera Respuesta
Para medir cumplimiento de SLA, se necesita la hora de primera respuesta.
Cuando el agente de CS responde por primera vez al cliente, se establece first_responded_at.
from datetime import datetime, timezone
class TicketReplyService:
def reply_to_ticket(
self,
ticket: dict,
agent_id: int,
body: str,
) -> dict:
now = datetime.now(timezone.utc)
# Registrar si todavía no hubo primera respuesta
if ticket.get("first_responded_at") is None:
ticket["first_responded_at"] = now
ticket["status"] = "waiting_customer"
ticket["updated_at"] = now
message = {
"ticket_id": ticket["id"],
"sender_type": "agent",
"sender_id": agent_id,
"body": body,
"created_at": now,
}
return message
Este proceso también debe estar en la capa de servicio, no en el router.
Así se protege la regla de primera respuesta aunque existan varias APIs de respuesta.
8. Crear Funciones para Detectar Incumplimientos de SLA
Convertimos la detección de vencimiento en funciones.
from datetime import datetime
def is_first_response_breached(ticket: dict, now: datetime) -> bool:
if ticket.get("first_responded_at") is not None:
return False
due_at = ticket.get("first_response_due_at")
return due_at is not None and now > due_at
def is_resolution_breached(ticket: dict, now: datetime) -> bool:
if ticket.get("resolved_at") is not None:
return False
if ticket.get("status") in {"resolved", "closed"}:
return False
due_at = ticket.get("resolution_due_at")
return due_at is not None and now > due_at
Conviene mirar por separado el SLA de primera respuesta y el SLA de resolución.
En soporte, “se respondió rápido pero se resolvió tarde” y “ni siquiera hubo primera respuesta” son estados con significados diferentes.
9. API de Búsqueda SLA: Encontrar Tickets Vencidos o Próximos a Vencer
En una pantalla de administración CS, hay que encontrar rápidamente tickets con incumplimiento SLA o próximos a vencer.
from fastapi import APIRouter, Query
from typing import Literal
router = APIRouter(prefix="/admin/tickets", tags=["admin-tickets"])
@router.get("/sla")
def list_sla_tickets(
kind: Literal["breached", "due_soon"] = Query(default="breached"),
target: Literal["first_response", "resolution"] = Query(default="first_response"),
limit: int = Query(default=50, ge=1, le=200),
):
return {
"items": [],
"meta": {
"kind": kind,
"target": target,
"limit": limit,
},
}
Una API dedicada facilita crear dashboards de CS o pantallas para managers.
Por ejemplo:
- tickets que superaron el plazo de primera respuesta
- tickets que superaron el plazo de resolución
- tickets urgent que vencen en menos de 2 horas
- tickets no atendidos de clientes Enterprise
- tickets sin responsable y próximos a vencer
La búsqueda SLA es tan importante que puede separarse de la búsqueda normal de tickets.
10. Definir Reglas de Escalación
Luego definimos cómo tratar tickets vencidos.
from dataclasses import dataclass
from datetime import timedelta
@dataclass(frozen=True)
class EscalationRule:
after_due: timedelta
notify_role: str
raise_priority: bool = False
assign_to_manager: bool = False
ESCALATION_RULES = {
"first_response": EscalationRule(
after_due=timedelta(minutes=0),
notify_role="support_manager",
raise_priority=True,
),
"resolution": EscalationRule(
after_due=timedelta(hours=1),
notify_role="support_manager",
raise_priority=True,
assign_to_manager=True,
),
}
En este ejemplo, cuando se supera el plazo de primera respuesta se notifica de inmediato, y una hora después de superar el plazo de resolución se asigna al manager.
En la práctica, las reglas de escalación también pueden variar por plan o prioridad.
11. Crear un Servicio de Escalación
El proceso de escalación busca tickets, actualiza estado si es necesario, notifica y deja logs de auditoría.
from datetime import datetime, timezone
class EscalationService:
def escalate_ticket(
self,
ticket: dict,
reason: str,
) -> dict:
now = datetime.now(timezone.utc)
ticket["escalated"] = True
ticket["updated_at"] = now
if ticket.get("priority") != "urgent":
ticket["priority"] = "high"
escalation_event = {
"ticket_id": ticket["id"],
"reason": reason,
"created_at": now,
}
return escalation_event
Este ejemplo está simplificado. En operación real se añaden procesos como:
- guardar historial de escalación
- dejar log de auditoría
- notificar al manager
- cambiar responsable
- destacarlo en el dashboard
12. Detectar Incumplimientos SLA con Jobs Periódicos
No basta con detectar incumplimientos solo cuando alguien llama a la API.
Hace falta un job que revise periódicamente.
Por ejemplo, cada 5 minutos:
- buscar tickets sin respuesta que superaron el plazo de primera respuesta
- buscar tickets no resueltos que superaron el plazo de resolución
- procesar los que aún no fueron escalados
- dejar notificaciones y logs de auditoría
FastAPI puede usar BackgroundTasks para tareas ligeras, pero para ejecución periódica y reintentos suele ser más natural introducir Celery o un scheduler.
def run_sla_check_job():
now = datetime.now(timezone.utc)
# En realidad se buscarían tickets objetivo en la DB
overdue_tickets = []
for ticket in overdue_tickets:
if is_first_response_breached(ticket, now):
EscalationService().escalate_ticket(
ticket,
reason="first_response_sla_breached",
)
La revisión SLA es muy importante operativamente, por lo que conviene guardar también logs y métricas de éxito, fallo y número de tickets procesados.
13. Diseño de Notificaciones: A Quién, Cuándo y Qué Avisar
La escalación debe pensarse junto con las notificaciones.
Los destinatarios varían según la operación.
- responsable del ticket
support_manager- canal Slack del equipo CS
- responsable dedicado de Enterprise
- equipo de desarrollo
- contabilidad o facturación
La notificación será más útil si incluye:
- ID del ticket
- asunto
- cliente o tenant
- prioridad
- plazo
- tiempo excedido
- responsable
- URL de administración
Pero en email o Slack no conviene incluir demasiados datos personales o información confidencial.
Lo más seguro es enlazar al panel de administración y mantener el texto del aviso al mínimo necesario.
14. Guardar Historial de Escalación
Las escalaciones deben guardarse como historial para poder rastrearlas luego.
from datetime import datetime
from pydantic import BaseModel
class EscalationEventRead(BaseModel):
id: int
ticket_id: int
reason: str
from_assignee_id: int | None = None
to_assignee_id: int | None = None
created_at: datetime
Ejemplos de razones:
first_response_sla_breachedresolution_sla_breachedurgent_unassignedmanual_escalationenterprise_customer
Con historial de escalación, el manager de CS puede entender por qué un ticket llegó a un nivel superior.
15. Preparar También una API de Escalación Manual
Además de la escalación automática, puede haber casos en los que un agente de CS quiera elevar manualmente un ticket.
from pydantic import BaseModel, Field
from fastapi import Depends, status
class ManualEscalationRequest(BaseModel):
reason: str = Field(..., min_length=1, max_length=1000)
@router.post("/{ticket_id}/escalate", status_code=status.HTTP_200_OK)
def escalate_ticket_manually(
ticket_id: int,
payload: ManualEscalationRequest,
admin=Depends(require_support),
):
return {
"ticket_id": ticket_id,
"escalated": True,
"reason": payload.reason,
"performed_by": admin.user_id,
}
En escalaciones manuales, se recomienda exigir siempre una razón.
Si más tarde no se entiende por qué se elevó un ticket, la operación se vuelve difícil.
16. Crear una API de Dashboard SLA
En operación SLA no solo importa la lista, sino también los agregados.
Es útil tener una API que devuelva indicadores como:
- número de tickets abiertos
- incumplimientos de primera respuesta
- incumplimientos de resolución
- tickets urgent abiertos
- tickets sin responsable
- promedio de tiempo de primera respuesta
- promedio de tiempo de resolución
@router.get("/sla/summary")
def get_sla_summary():
return {
"open_count": 42,
"first_response_breached_count": 3,
"resolution_breached_count": 5,
"urgent_open_count": 2,
"unassigned_count": 7,
"avg_first_response_minutes": 85,
"avg_resolution_hours": 36,
}
Con esta API, un manager de CS puede revisar la situación diaria o semanalmente.
Más adelante también puede enviarse a Grafana o herramientas BI como métricas.
17. Precauciones al Considerar Horario Laboral
Lo más difícil del SLA suele ser el cálculo con horarios laborales y días festivos.
Ejemplos:
- contar solo de lunes a viernes de 10:00 a 18:00
- excluir fines de semana y feriados
- zonas horarias distintas por tenant
- soporte 24 horas solo para Enterprise
- reglas especiales de fin de año
En estos casos, created_at + timedelta(hours=24) deja de ser correcto.
Puedes comenzar con una regla simple basada en UTC, pero si más adelante quieres incluir horario laboral, conviene abstraerlo así:
class BusinessCalendar:
def add_business_time(
self,
start_at: datetime,
duration_minutes: int,
timezone_name: str,
) -> datetime:
...
Al encerrar el cálculo de plazos en BusinessCalendar, será más fácil añadir festivos y horarios después.
18. Logs de Auditoría: Registrar Siempre Cambios SLA y Escalaciones
SLA y escalaciones afectan la calidad de soporte y explicaciones a clientes.
Por eso se recomienda auditar acciones como:
- cambio manual de plazo SLA
- cambio de prioridad
- escalación manual
- escalación automática
- cambio de responsable
- resolución de ticket
- reapertura de ticket
Ejemplo de log de auditoría:
def write_audit_log(
actor_id: int | None,
action: str,
resource_type: str,
resource_id: str,
detail: dict | None = None,
) -> None:
...
write_audit_log(
actor_id=admin.user_id,
action="ticket.escalate",
resource_type="ticket",
resource_id=str(ticket_id),
detail={"reason": payload.reason},
)
En escalaciones automáticas, es claro registrarlo con actor_id=None o actor_type="system".
19. Pensar También en la Experiencia del Cliente al Incumplir SLA
El SLA parece una métrica interna, pero también afecta la experiencia del cliente.
Conviene decidir qué mostrar al cliente cuando se supera un plazo.
Ejemplos:
- mostrar “Estamos revisando tu caso”
- enviar automáticamente un mensaje de disculpa por retraso
- notificar a un responsable dedicado solo para clientes Enterprise
- pedir una actualización de avance si la resolución tarda
Pero demasiados mensajes automáticos pueden ser contraproducentes.
Las notificaciones al cliente ante incumplimiento SLA deben decidirse cuidadosamente junto con el equipo CS.
20. Estrategia de Pruebas: Proteger Especialmente Cálculos y Transiciones
La gestión de SLA depende del tiempo, por lo que las pruebas son muy importantes.
Como mínimo, conviene cubrir:
- el plazo SLA de plan free se calcula correctamente
enterprise urgentgenera plazos cortos- si ya hubo primera respuesta, no hay incumplimiento de primera respuesta
- si no hubo respuesta y se superó el plazo, hay incumplimiento
- si está
resolved, no hay incumplimiento de resolución - no se escala dos veces un ticket ya escalado
- la escalación manual exige razón
- se guarda log de auditoría
- las cantidades del summary SLA son correctas
20.1 Ejemplo de Prueba de Detección de Plazo Vencido
from datetime import datetime, timedelta, timezone
def test_first_response_breached_when_due_passed():
now = datetime(2026, 1, 1, 12, 0, tzinfo=timezone.utc)
ticket = {
"first_responded_at": None,
"first_response_due_at": now - timedelta(minutes=1),
}
assert is_first_response_breached(ticket, now) is True
20.2 Probar También Casos que no Deben Incumplir
def test_first_response_not_breached_when_already_responded():
now = datetime(2026, 1, 1, 12, 0, tzinfo=timezone.utc)
ticket = {
"first_responded_at": now - timedelta(hours=1),
"first_response_due_at": now - timedelta(minutes=1),
}
assert is_first_response_breached(ticket, now) is False
En SLA es tan importante probar las condiciones de incumplimiento como las condiciones en las que no debe haber incumplimiento.
21. Errores Frecuentes
21.1 Recalcular Siempre el Plazo SLA en el Momento
Si las reglas cambian, también podrían cambiar accidentalmente los plazos de tickets antiguos.
Es más seguro guardar el plazo en el momento de creación.
21.2 Mezclar Primera Respuesta y Plazo de Resolución
Puede ocurrir que la primera respuesta sea rápida, pero la resolución sea lenta.
Se recomienda tener plazos y tiempos reales separados.
21.3 Resolver la Escalación Solo con Notificaciones
Si solo se notifica, queda poco historial y luego es difícil rastrear.
Guárdalo como evento de escalación.
21.4 Escalar Dos Veces
Un job periódico puede escalar el mismo ticket varias veces.
Protege la idempotencia con un flag escalated o historial de escalaciones.
21.5 Añadir Horario Laboral a Mano Después
El cálculo con horario laboral se vuelve complejo fácilmente.
Desde el inicio conviene acercarlo a una abstracción como BusinessCalendar.
22. Roadmap por Tipo de Lector
Personas en Desarrollo Individual o Aprendizaje
- Añadir
first_response_due_atal ticket - Crear pequeñas reglas SLA por prioridad
- Registrar la hora de primera respuesta
- Crear una función de detección de vencimiento
- Crear una API de lista de incumplimientos SLA
Ingenieros de Equipos Pequeños
- Revisar reglas SLA con el equipo CS
- Separar SLA de primera respuesta y resolución
- Detectar tickets vencidos con jobs periódicos
- Implementar escalación automática y manual
- Añadir logs de auditoría y notificaciones
Equipos SaaS y Startups
- Definir SLA por plan
- Organizar horario laboral, zonas horarias y festivos
- Crear API de resumen SLA y dashboard
- Guardar historial de escalación e integrarlo con notificaciones
- Convertir tasa de cumplimiento SLA, tiempo medio de primera respuesta y resolución en métricas
Enlaces de Referencia
- FastAPI
Conclusión
- La gestión de SLA es un mecanismo para proteger en código “hasta cuándo debe atenderse” una consulta.
- En FastAPI, conviene separar cálculo de plazos SLA, detección de incumplimientos y escalación en la capa de servicio, manteniendo routers ligeros.
- Los plazos de primera respuesta y resolución deben estar separados, y también conviene guardar los tiempos reales para calcular cumplimiento SLA y tiempos medios de atención.
- La escalación se vuelve más resistente en operación si se diseña no solo como notificación, sino junto con historial, auditoría, cambio de responsable y cambio de prioridad.
- Puedes comenzar con un cálculo simple basado en UTC. Lo importante es encerrar el cálculo de plazos en la capa de servicio para poder añadir más adelante horarios laborales y festivos.
Como siguientes artículos, encajan naturalmente “Introducción práctica a arquitectura dirigida por eventos con FastAPI” o “Diseño de una API de dashboard CS con FastAPI”.
