""" Script para generar datos de prueba de SLA Management. Crea tickets con diferentes estados de SLA para probar el dashboard. """ import asyncio import sys from pathlib import Path from datetime import datetime, timedelta, timezone import uuid import random # Agregar el directorio raíz al path sys.path.insert(0, str(Path(__file__).parent.parent)) from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from app.core.database import AsyncSessionLocal from app.models.ticket import Ticket, TicketStatus, TicketPriority from app.models.category import Category from app.models.user import User from app.models.tenant import Tenant from app.models.system import System async def generate_sla_test_data(): """Genera tickets de prueba con diferentes estados de SLA.""" async with AsyncSessionLocal() as db: # Obtener tenant y usuarios tenant_result = await db.execute(select(Tenant).limit(1)) tenant = tenant_result.scalar_one_or_none() if not tenant: print("❌ No se encontró ningún tenant. Ejecuta las migraciones primero.") return # Obtener usuarios users_result = await db.execute( select(User).where(User.tenant_id == tenant.id).limit(5) ) users = list(users_result.scalars().all()) if not users: print("❌ No se encontraron usuarios. Crea usuarios primero.") return creator = users[0] agents = users if len(users) > 1 else [creator] # Obtener o crear categorías categories_result = await db.execute( select(Category).where(Category.tenant_id == tenant.id) ) categories = list(categories_result.scalars().all()) if not categories: print("📁 Creando categorías de prueba...") category_data = [ {"name": "Soporte Técnico", "sla_response_hours": 2, "sla_resolution_hours": 24, "color": "#3B82F6"}, {"name": "Facturación", "sla_response_hours": 4, "sla_resolution_hours": 48, "color": "#10B981"}, {"name": "Incidente Crítico", "sla_response_hours": 1, "sla_resolution_hours": 8, "color": "#EF4444"}, {"name": "Consulta General", "sla_response_hours": 8, "sla_resolution_hours": 72, "color": "#6B7280"}, ] for cat_data in category_data: category = Category( id=uuid.uuid4(), tenant_id=tenant.id, name=cat_data["name"], description=f"Categoría de {cat_data['name']}", color=cat_data["color"], sla_response_hours=cat_data["sla_response_hours"], sla_resolution_hours=cat_data["sla_resolution_hours"], is_active=True ) db.add(category) categories.append(category) await db.commit() print(f" ✓ Creadas {len(categories)} categorías") # Obtener o crear sistemas afectados systems_result = await db.execute( select(System).where(System.tenant_id == tenant.id) ) systems = list(systems_result.scalars().all()) if not systems: print("🖥️ Creando sistemas de prueba...") system_names = ["Portal Web", "API REST", "Base de Datos", "Sistema de Pagos"] for sys_name in system_names: system = System( id=uuid.uuid4(), tenant_id=tenant.id, name=sys_name, description=f"Sistema {sys_name}", is_active=True ) db.add(system) systems.append(system) await db.commit() print(f" ✓ Creados {len(systems)} sistemas") print(f"✅ Usando tenant: {tenant.name}") print(f"✅ Usuarios disponibles: {len(users)}") print(f"✅ Categorías disponibles: {len(categories)}") print() now = datetime.now(timezone.utc) tickets_created = 0 # Función auxiliar para crear ticket def create_ticket( subject: str, description: str, priority: TicketPriority, status: TicketStatus, category: Category, created_hours_ago: int, first_response_hours_after: int = None, resolved_hours_after: int = None, assigned: bool = True ): nonlocal tickets_created ticket_id = uuid.uuid4() created_at = now - timedelta(hours=created_hours_ago) # Calcular SLA deadlines basados en la categoría (sin timezone para la BD) sla_response_due = (created_at + timedelta(hours=category.sla_response_hours)).replace(tzinfo=None) sla_resolution_due = (created_at + timedelta(hours=category.sla_resolution_hours)).replace(tzinfo=None) # Primera respuesta (si aplica) first_response_at = None if first_response_hours_after is not None: first_response_at = (created_at + timedelta(hours=first_response_hours_after)).replace(tzinfo=None) # Resolución (si aplica) resolved_at = None if resolved_hours_after is not None: resolved_at = (created_at + timedelta(hours=resolved_hours_after)).replace(tzinfo=None) ticket = Ticket( id=ticket_id, tenant_id=tenant.id, ticket_number=f"TKT-{1000 + tickets_created}", subject=subject, description=description, status=status, priority=priority, created_by=creator.id, assigned_to=random.choice(agents).id if assigned else None, category_id=category.id, affected_system_id=random.choice(systems).id if systems else None, sla_response_due=sla_response_due, sla_resolution_due=sla_resolution_due, first_response_at=first_response_at, resolved_at=resolved_at, created_at=created_at, updated_at=resolved_at or first_response_at or created_at ) db.add(ticket) tickets_created += 1 return ticket # ============================================ # 1. TICKETS CUMPLIENDO SLA RESPONSE (Verde) # ============================================ print("✅ Generando tickets CUMPLIENDO Response SLA...") for i in range(15): category = random.choice(categories) priority = random.choice([TicketPriority.LOW, TicketPriority.MEDIUM, TicketPriority.HIGH]) # Creado hace X horas, respondido ANTES del deadline created_hours_ago = random.randint(24, 120) response_time = random.uniform(0.5, category.sla_response_hours * 0.7) # 70% del SLA status = random.choice([TicketStatus.IN_PROGRESS, TicketStatus.WAITING_CUSTOMER]) create_ticket( subject=f"Ticket con respuesta a tiempo #{i+1}", description=f"Este ticket fue respondido dentro del SLA de {category.name}", priority=priority, status=status, category=category, created_hours_ago=created_hours_ago, first_response_hours_after=response_time, assigned=True ) print(f" ✓ Creados 15 tickets cumpliendo Response SLA") # ============================================ # 2. TICKETS VIOLANDO SLA RESPONSE (Rojo) # ============================================ print("🔴 Generando tickets VIOLANDO Response SLA...") for i in range(8): category = random.choice(categories) priority = random.choice([TicketPriority.HIGH, TicketPriority.URGENT]) # Creado hace más tiempo que el SLA, SIN respuesta created_hours_ago = category.sla_response_hours + random.randint(1, 10) create_ticket( subject=f"Ticket SIN respuesta - VIOLACIÓN #{i+1}", description=f"Este ticket lleva {created_hours_ago}h sin respuesta (SLA: {category.sla_response_hours}h)", priority=priority, status=random.choice([TicketStatus.NEW, TicketStatus.TRIAGE]), category=category, created_hours_ago=created_hours_ago, first_response_hours_after=None, # Sin respuesta! assigned=random.choice([True, False]) ) print(f" ✓ Creados 8 tickets VIOLANDO Response SLA") # ============================================ # 3. TICKETS EN RIESGO Response (Amarillo) # ============================================ print("⚠️ Generando tickets EN RIESGO Response SLA...") for i in range(10): category = random.choice(categories) priority = random.choice([TicketPriority.MEDIUM, TicketPriority.HIGH, TicketPriority.URGENT]) # Creado hace tiempo, cerca del deadline (80-95% consumido) sla_hours = category.sla_response_hours time_consumed = random.uniform(0.8, 0.95) * sla_hours created_hours_ago = time_consumed create_ticket( subject=f"Ticket cerca de vencer respuesta #{i+1}", description=f"Este ticket está al {int(time_consumed/sla_hours*100)}% del SLA de respuesta", priority=priority, status=random.choice([TicketStatus.TRIAGE, TicketStatus.NEW]), category=category, created_hours_ago=created_hours_ago, first_response_hours_after=None, # Aún sin respuesta assigned=True ) print(f" ✓ Creados 10 tickets EN RIESGO Response SLA") # ============================================ # 4. TICKETS CUMPLIENDO SLA RESOLUTION # ============================================ print("✅ Generando tickets CUMPLIENDO Resolution SLA...") for i in range(20): category = random.choice(categories) priority = random.choice([TicketPriority.LOW, TicketPriority.MEDIUM, TicketPriority.HIGH]) # Creado, respondido y resuelto dentro del SLA created_hours_ago = random.randint(72, 240) response_time = random.uniform(1, category.sla_response_hours * 0.5) resolution_time = random.uniform( response_time + 1, category.sla_resolution_hours * 0.8 ) create_ticket( subject=f"Ticket resuelto a tiempo #{i+1}", description=f"Este ticket fue resuelto dentro del SLA de {category.name}", priority=priority, status=random.choice([TicketStatus.RESOLVED, TicketStatus.CLOSED]), category=category, created_hours_ago=created_hours_ago, first_response_hours_after=response_time, resolved_hours_after=resolution_time, assigned=True ) print(f" ✓ Creados 20 tickets cumpliendo Resolution SLA") # ============================================ # 5. TICKETS VIOLANDO SLA RESOLUTION # ============================================ print("🔴 Generando tickets VIOLANDO Resolution SLA...") for i in range(6): category = random.choice(categories) priority = random.choice([TicketPriority.HIGH, TicketPriority.URGENT]) # Creado hace más del SLA de resolución, con respuesta pero sin resolver created_hours_ago = category.sla_resolution_hours + random.randint(5, 48) response_time = random.uniform(1, category.sla_response_hours * 0.5) create_ticket( subject=f"Ticket sin resolver - VIOLACIÓN #{i+1}", description=f"Ticket lleva {created_hours_ago}h sin resolver (SLA: {category.sla_resolution_hours}h)", priority=priority, status=random.choice([TicketStatus.IN_PROGRESS, TicketStatus.WAITING_CUSTOMER]), category=category, created_hours_ago=created_hours_ago, first_response_hours_after=response_time, resolved_hours_after=None, # Sin resolver! assigned=True ) print(f" ✓ Creados 6 tickets VIOLANDO Resolution SLA") # ============================================ # 6. TICKETS EN RIESGO Resolution # ============================================ print("⚠️ Generando tickets EN RIESGO Resolution SLA...") for i in range(12): category = random.choice(categories) priority = random.choice([TicketPriority.MEDIUM, TicketPriority.HIGH]) # Con respuesta, cerca del deadline de resolución sla_hours = category.sla_resolution_hours time_consumed = random.uniform(0.75, 0.95) * sla_hours created_hours_ago = time_consumed response_time = random.uniform(0.5, category.sla_response_hours * 0.5) create_ticket( subject=f"Ticket cerca de vencer resolución #{i+1}", description=f"Este ticket está al {int(time_consumed/sla_hours*100)}% del SLA de resolución", priority=priority, status=TicketStatus.IN_PROGRESS, category=category, created_hours_ago=created_hours_ago, first_response_hours_after=response_time, resolved_hours_after=None, assigned=True ) print(f" ✓ Creados 12 tickets EN RIESGO Resolution SLA") # Guardar todos los tickets await db.commit() print() print("=" * 70) print("✅ GENERACIÓN DE DATOS SLA COMPLETADA") print(f" Total de tickets creados: {tickets_created}") print() print("📊 Distribución esperada:") print(" ✅ Response cumplidos: 15 tickets") print(" 🔴 Response violados: 8 tickets") print(" ⚠️ Response en riesgo: 10 tickets") print(" ✅ Resolution cumplidos: 20 tickets") print(" 🔴 Resolution violados: 6 tickets") print(" ⚠️ Resolution en riesgo: 12 tickets") print() print("🌐 Ve los resultados en:") print(" Dashboard SLA: http://localhost:3001/sla") print("=" * 70) async def cleanup_sla_test_data(): """Elimina tickets de prueba.""" async with AsyncSessionLocal() as db: tenant_result = await db.execute(select(Tenant).limit(1)) tenant = tenant_result.scalar_one_or_none() if not tenant: print("❌ No se encontró ningún tenant.") return # Eliminar tickets que empiezan con TKT- result = await db.execute( select(Ticket).where( Ticket.tenant_id == tenant.id, Ticket.ticket_number.like('TKT-%') ) ) tickets = result.scalars().all() if not tickets: print("ℹ️ No hay tickets de prueba para eliminar.") return for ticket in tickets: await db.delete(ticket) await db.commit() print(f"✅ Eliminados {len(tickets)} tickets de prueba") if __name__ == "__main__": import sys if len(sys.argv) > 1 and sys.argv[1] == "cleanup": print("🧹 Limpiando datos de prueba de SLA...") print() asyncio.run(cleanup_sla_test_data()) else: print("🚀 Generando datos de prueba para SLA Management...") print() asyncio.run(generate_sla_test_data()) print() print("💡 Para limpiar estos datos de prueba, ejecuta:") print(" python scripts/generate_sla_test_data.py cleanup")