Files
service_manager/backend/app/api/schemas/sla.py
icamarillo 75726d915f v1.7.0 - Fix: Corregido error 500 en SLA Dashboard
- Fix error de sintaxis SQL en cálculo de tickets 'at risk'
- Fix error de timezone (offset-naive vs offset-aware datetimes)
- Implementado sistema completo de SLA Management
- Agregados endpoints: /sla/dashboard, /sla/violations, /sla/at-risk
- Creadas vistas frontend para dashboard, violaciones y tickets en riesgo
- Actualizado sistema de Celery para monitoreo automático de SLAs
- Mejorada configuración de categorías con tiempos SLA personalizables
- Corregidos problemas de proxy en configuración de Vite
- Agregado troubleshooting guide en README

Archivos principales modificados:
- backend/app/api/v1/endpoints/sla.py (nuevo)
- backend/app/api/schemas/sla.py (nuevo)
- frontend-internal/src/routes/sla/ (nuevo módulo completo)
- workers/app/tasks/sla_tasks.py (queries async mejoradas)

Documentación: docs/changelog-2026-02-17.md
2026-02-17 08:22:32 -07:00

254 lines
6.3 KiB
Python

"""
SLA Schemas - ServiceManagerWeb
Schemas para el sistema de gestión de SLAs
"""
from pydantic import BaseModel, ConfigDict
from typing import Optional, List, Dict, Any
from datetime import datetime
from enum import Enum
import uuid
class SLATypeEnum(str, Enum):
"""Tipos de SLA"""
RESPONSE = "response"
RESOLUTION = "resolution"
class SLAStatusEnum(str, Enum):
"""Estados de cumplimiento SLA"""
MET = "met" # Cumplido
VIOLATED = "violated" # Violado
AT_RISK = "at_risk" # En riesgo (80%+ del tiempo)
PENDING = "pending" # Pendiente (ticket aún abierto)
# ===================================
# DASHBOARD SCHEMAS
# ===================================
class SLAComplianceMetrics(BaseModel):
"""Métricas de cumplimiento SLA"""
target_hours: int
met_count: int
violated_count: int
at_risk_count: int
total_count: int
compliance_percentage: float
avg_time_hours: Optional[float] = None
class SLADashboardResponse(BaseModel):
"""Response del dashboard principal de SLA"""
tenant_id: uuid.UUID
period_start: datetime
period_end: datetime
generated_at: datetime
# Métricas generales
response_sla: SLAComplianceMetrics
resolution_sla: SLAComplianceMetrics
# Contadores rápidos
active_violations: int
at_risk_tickets: int
total_tickets_period: int
# Breakdown por categoría (top 5)
by_category: List[Dict[str, Any]]
# Breakdown por prioridad
by_priority: Dict[str, Dict[str, float]]
# Tendencias (comparación con período anterior)
trends: Dict[str, str]
model_config = ConfigDict(from_attributes=True)
# ===================================
# VIOLATIONS SCHEMAS
# ===================================
class TicketBasicInfo(BaseModel):
"""Información básica del ticket"""
id: uuid.UUID
ticket_number: str
subject: str
priority: str
status: str
class UserBasicInfo(BaseModel):
"""Información básica del usuario"""
id: uuid.UUID
first_name: str
last_name: str
email: str
class CategoryBasicInfo(BaseModel):
"""Información básica de categoría"""
id: uuid.UUID
name: str
sla_response_hours: int
sla_resolution_hours: int
class SLAViolationResponse(BaseModel):
"""Detalle de una violación SLA"""
ticket: TicketBasicInfo
category: Optional[CategoryBasicInfo] = None
created_by: UserBasicInfo
assigned_to: Optional[UserBasicInfo] = None
sla_type: SLATypeEnum
sla_due_at: datetime
violated_at: datetime
hours_overdue: float
# Contexto adicional
first_response_at: Optional[datetime] = None
resolved_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class SLAViolationsListResponse(BaseModel):
"""Lista paginada de violaciones"""
violations: List[SLAViolationResponse]
total: int
page: int
per_page: int
total_pages: int
# ===================================
# TICKETS AT RISK
# ===================================
class SLATicketAtRisk(BaseModel):
"""Ticket que está en riesgo de violar SLA"""
ticket: TicketBasicInfo
category: Optional[CategoryBasicInfo] = None
assigned_to: Optional[UserBasicInfo] = None
sla_type: SLATypeEnum
sla_due_at: datetime
time_remaining_hours: float
risk_percentage: float # 0-100, qué % del tiempo ha pasado
model_config = ConfigDict(from_attributes=True)
class SLAAtRiskListResponse(BaseModel):
"""Lista de tickets en riesgo"""
tickets: List[SLATicketAtRisk]
total: int
# ===================================
# METRICS & REPORTS SCHEMAS
# ===================================
class SLAMetricsByCategory(BaseModel):
"""Métricas SLA por categoría"""
category_id: uuid.UUID
category_name: str
response_sla_compliance: float
resolution_sla_compliance: float
total_tickets: int
response_violations: int
resolution_violations: int
avg_response_time_hours: Optional[float]
avg_resolution_time_hours: Optional[float]
class SLAMetricsByAgent(BaseModel):
"""Métricas SLA por agente"""
agent_id: uuid.UUID
agent_name: str
tickets_assigned: int
response_sla_met: int
resolution_sla_met: int
response_compliance: float
resolution_compliance: float
avg_response_time_hours: Optional[float]
avg_resolution_time_hours: Optional[float]
class SLAMetricsByPriority(BaseModel):
"""Métricas SLA por prioridad"""
priority: str
total_tickets: int
response_sla_compliance: float
resolution_sla_compliance: float
avg_response_time_hours: Optional[float]
avg_resolution_time_hours: Optional[float]
class SLADetailedMetricsResponse(BaseModel):
"""Response de métricas detalladas"""
tenant_id: uuid.UUID
date_from: datetime
date_to: datetime
group_by: str # 'category', 'agent', 'priority'
by_category: Optional[List[SLAMetricsByCategory]] = None
by_agent: Optional[List[SLAMetricsByAgent]] = None
by_priority: Optional[List[SLAMetricsByPriority]] = None
generated_at: datetime
model_config = ConfigDict(from_attributes=True)
# ===================================
# HISTORICAL TRENDS
# ===================================
class SLADailyTrend(BaseModel):
"""Tendencia diaria de SLA"""
date: str # YYYY-MM-DD
response_compliance: float
resolution_compliance: float
total_tickets: int
violations: int
class SLATrendsResponse(BaseModel):
"""Response de tendencias históricas"""
tenant_id: uuid.UUID
days: int
daily_trends: List[SLADailyTrend]
# Promedios del período
avg_response_compliance: float
avg_resolution_compliance: float
total_tickets: int
total_violations: int
model_config = ConfigDict(from_attributes=True)
# ===================================
# CONFIGURATION
# ===================================
class SLAConfigByCategoryResponse(BaseModel):
"""Configuración SLA por categoría"""
category_id: uuid.UUID
category_name: str
sla_response_hours: int
sla_resolution_hours: int
warning_threshold_percentage: int # % del tiempo para alertar
is_active: bool
class SLAConfigListResponse(BaseModel):
"""Lista de configuraciones SLA"""
tenant_id: uuid.UUID
categories: List[SLAConfigByCategoryResponse]