src/Controller/WorkingHoursController.php line 262

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  4. use Symfony\Component\HttpFoundation\Request;
  5. use Symfony\Component\HttpFoundation\Response;
  6. use Symfony\Component\HttpFoundation\JsonResponse;
  7. use Symfony\Component\Routing\Annotation\Route;
  8. class WorkingHoursController extends AbstractController
  9. {
  10.     public function workingHoursAction(): Response
  11.     {
  12.         return $this->render('reports/working-hours.html.twig');
  13.     }
  14.     public function getWorkingHoursStatusAction(): JsonResponse
  15.     {
  16.         $entityManager $this->getDoctrine()->getManager();
  17.         
  18.         // Forzar refresh - cerrar y reabrir la conexión para evitar cache
  19.         $entityManager->close();
  20.         $entityManager $this->getDoctrine()->getManager();
  21.         $connection $entityManager->getConnection();
  22.         
  23.         // Forzar que no use cache de transacciones
  24.         $connection->executeQuery('SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
  25.         
  26.         // Obtener países y horarios desde la tabla uv_support_group y uv_working_hours
  27.         $sql "
  28.             SELECT 
  29.                 sg.id as supportGroupId,
  30.                 sg.name as supportGroupName,
  31.                 sg.description as supportGroupDescription,
  32.                 wh.timezone,
  33.                 wh.start_time,
  34.                 wh.end_time,
  35.                 wh.monday,
  36.                 wh.tuesday,
  37.                 wh.wednesday,
  38.                 wh.thursday,
  39.                 wh.friday,
  40.                 wh.saturday,
  41.                 wh.sunday,
  42.                 wh.is_active
  43.             FROM uv_support_group sg
  44.             LEFT JOIN uv_working_hours wh ON sg.id = wh.support_group_id
  45.             WHERE sg.is_active = 1
  46.             AND (sg.name LIKE 'OR %' OR sg.name = 'Casa Central')
  47.             ORDER BY 
  48.                 CASE 
  49.                     WHEN sg.name LIKE 'OR %' THEN 1
  50.                     WHEN sg.name = 'Casa Central' THEN 2
  51.                     ELSE 3
  52.                 END,
  53.                 sg.name
  54.         ";
  55.         
  56.         $stmt $connection->prepare($sql);
  57.         $stmt->execute();
  58.         $supportGroups $stmt->fetchAll(\PDO::FETCH_ASSOC);
  59.         
  60.         // Obtener agentes activos agrupados por región
  61.         $sql "
  62.             SELECT 
  63.                 a.id as agentId,
  64.                 a.first_name as firstName,
  65.                 a.last_name as lastName,
  66.                 a.email,
  67.                 a.is_enabled as isEnabled,
  68.                 a.lastactivity,
  69.                 a.idOR as idor,
  70.                 sg.name as supportGroupName,
  71.                 sg.description as supportGroupDescription,
  72.                 sr.code as supportRoleCode
  73.             FROM uv_user a
  74.             JOIN uv_user_instance ui ON a.id = ui.user_id
  75.             JOIN uv_support_role sr ON ui.supportRole_id = sr.id
  76.             LEFT JOIN uv_support_group sg ON a.idOR = sg.id
  77.             WHERE a.is_enabled = 1
  78.             AND sr.code = 'ROLE_AGENT'
  79.             ORDER BY a.first_name, a.last_name
  80.         ";
  81.         
  82.         $stmt $connection->prepare($sql);
  83.         $stmt->execute();
  84.         $agents $stmt->fetchAll(\PDO::FETCH_ASSOC);
  85.         
  86.         // Agrupar agentes por región
  87.         $agentsByRegion = [];
  88.         foreach ($agents as $agent) {
  89.             $regionName $agent['supportGroupName'] ?: 'Sin región asignada';
  90.             if (!isset($agentsByRegion[$regionName])) {
  91.                 $agentsByRegion[$regionName] = [];
  92.             }
  93.             $agentsByRegion[$regionName][] = [
  94.                 'id' => $agent['agentId'],
  95.                 'name' => $agent['firstName'] . ' ' $agent['lastName'],
  96.                 'email' => $agent['email'],
  97.                 'isEnabled' => $agent['isEnabled'],
  98.                 'lastActivity' => $agent['lastactivity'] ? $agent['lastactivity'] : 'N/A',
  99.                 'idor' => $agent['idor'],
  100.                 'role' => $agent['supportRoleCode']
  101.             ];
  102.         }
  103.         
  104.         $status = [];
  105.         foreach ($supportGroups as $group) {
  106.             // Extraer nombre del país del nombre del grupo
  107.             if ($group['supportGroupName'] === 'Casa Central') {
  108.                 $countryName 'Casa Central';
  109.             } else {
  110.                 // Para oficinas regionales: "OR Colombia" -> "Colombia"
  111.                 $countryName trim(str_replace('OR'''$group['supportGroupName']));
  112.             }
  113.             
  114.             // Si no hay configuración de horarios, usar valores por defecto
  115.             if (!$group['timezone']) {
  116.                 $group['timezone'] = 'UTC';
  117.                 $group['start_time'] = '09:00:00';
  118.                 $group['end_time'] = '18:00:00';
  119.                 $group['monday'] = 1;
  120.                 $group['tuesday'] = 1;
  121.                 $group['wednesday'] = 1;
  122.                 $group['thursday'] = 1;
  123.                 $group['friday'] = 1;
  124.                 $group['saturday'] = 0;
  125.                 $group['sunday'] = 0;
  126.             }
  127.             
  128.             $localTime = new \DateTime('now', new \DateTimeZone($group['timezone']));
  129.             $dayOfWeek = (int)$localTime->format('N'); // 1 = Monday, 7 = Sunday
  130.             $hour = (int)$localTime->format('G'); // 0-23 hour format
  131.             $minute = (int)$localTime->format('i');
  132.             $currentTime $hour 60 $minute// Convertir a minutos para comparación
  133.             
  134.             // Convertir horarios de inicio y fin a minutos
  135.             $startTimeParts explode(':'$group['start_time']);
  136.             $endTimeParts explode(':'$group['end_time']);
  137.             $startTimeMinutes = (int)$startTimeParts[0] * 60 + (int)$startTimeParts[1];
  138.             $endTimeMinutes = (int)$endTimeParts[0] * 60 + (int)$endTimeParts[1];
  139.             
  140.             // Verificar si es día laboral
  141.             $workingDays = [
  142.                 => $group['monday'],
  143.                 => $group['tuesday'],
  144.                 => $group['wednesday'],
  145.                 => $group['thursday'],
  146.                 => $group['friday'],
  147.                 => $group['saturday'],
  148.                 => $group['sunday']
  149.             ];
  150.             
  151.             $isWorkingDay $workingDays[$dayOfWeek] == 1;
  152.             
  153.             // Verificar si está dentro del horario laboral
  154.             $isWorkingHours $isWorkingDay && $currentTime >= $startTimeMinutes && $currentTime $endTimeMinutes;
  155.             
  156.             // Determinar estado
  157.             if ($isWorkingDay && $isWorkingHours) {
  158.                 $statusCode 'open';
  159.                 $statusText 'Abierto';
  160.                 $statusColor 'green';
  161.             } else {
  162.                 $statusCode 'closed';
  163.                 $statusText 'Cerrado';
  164.                 $statusColor 'red';
  165.             }
  166.             
  167.             // Buscar agentes para este país
  168.             $countryAgents = [];
  169.             foreach ($agentsByRegion as $regionName => $regionAgents) {
  170.                 if (stripos($regionName$countryName) !== false || 
  171.                     stripos($countryName$regionName) !== false ||
  172.                     $this->isCountryInRegion($countryName$regionName)) {
  173.                     $countryAgents array_merge($countryAgents$regionAgents);
  174.                 }
  175.             }
  176.             $status[$countryName] = [
  177.                 'country' => $countryName,
  178.                 'timezone' => $group['timezone'],
  179.                 'localTime' => $localTime->format('H:i:s'),
  180.                 'localDate' => $localTime->format('Y-m-d'),
  181.                 'dayOfWeek' => $localTime->format('l'),
  182.                 'isWorkingDay' => $isWorkingDay,
  183.                 'isWorkingHours' => $isWorkingHours,
  184.                 'status' => $statusCode,
  185.                 'statusText' => $statusText,
  186.                 'statusColor' => $statusColor,
  187.                 'workingHours' => $group['start_time'] . ' - ' $group['end_time'],
  188.                 'workingDays' => $this->getWorkingDaysText($workingDays),
  189.                 'supportGroupId' => $group['supportGroupId'],
  190.                 'agents' => $countryAgents
  191.             ];
  192.         }
  193.         return new JsonResponse($status);
  194.     }
  195.     
  196.     private function isCountryInRegion($country$regionName): bool
  197.     {
  198.         // Mapeo de países a posibles nombres de regiones
  199.         $countryRegionMap = [
  200.             'Casa Central' => ['casa central''argentina''buenos aires''córdoba''rosario'],
  201.             'Colombia' => ['colombia''bogotá''medellín''cali''barranquilla'],
  202.             'Mexico' => ['mexico''méxico''ciudad de mexico''cdmx''guadalajara''monterrey'],
  203.             'Brasil' => ['brasil''brazil''sao paulo''rio de janeiro''brasilia'],
  204.             'España' => ['españa''spain''madrid''barcelona''valencia''sevilla'],
  205.             'Peru' => ['peru''perú''lima''arequipa''trujillo'],
  206.             'Chile' => ['chile''santiago''valparaíso''concepción'],
  207.             'Venezuela' => ['venezuela''caracas''maracaibo''valencia'],
  208.             'Costa Rica' => ['costa rica''san josé''cartago''alajuela']
  209.         ];
  210.         
  211.         if (isset($countryRegionMap[$country])) {
  212.             foreach ($countryRegionMap[$country] as $regionKeyword) {
  213.                 if (stripos($regionName$regionKeyword) !== false) {
  214.                     return true;
  215.                 }
  216.             }
  217.         }
  218.         
  219.         return false;
  220.     }
  221.     
  222.     private function getWorkingDaysText($workingDays): string
  223.     {
  224.         $days = [];
  225.         $dayNames = [
  226.             => 'Lunes',
  227.             => 'Martes'
  228.             => 'Miércoles',
  229.             => 'Jueves',
  230.             => 'Viernes',
  231.             => 'Sábado',
  232.             => 'Domingo'
  233.         ];
  234.         
  235.         foreach ($workingDays as $dayNum => $isWorking) {
  236.             if ($isWorking) {
  237.                 $days[] = $dayNames[$dayNum];
  238.             }
  239.         }
  240.         
  241.         if (empty($days)) {
  242.             return 'No hay días laborales configurados';
  243.         }
  244.         
  245.         if (count($days) === 7) {
  246.             return 'Todos los días';
  247.         }
  248.         
  249.         if (count($days) === && $workingDays[1] && $workingDays[2] && $workingDays[3] && $workingDays[4] && $workingDays[5]) {
  250.             return 'Lunes a Viernes';
  251.         }
  252.         
  253.         return implode(', '$days);
  254.     }
  255.     public function getRecentTicketsAction(): JsonResponse
  256.     {
  257.         $entityManager $this->getDoctrine()->getManager();
  258.         
  259.         // Forzar refresh - cerrar y reabrir la conexión para evitar cache
  260.         $entityManager->close();
  261.         $entityManager $this->getDoctrine()->getManager();
  262.         $connection $entityManager->getConnection();
  263.         
  264.         // Forzar que no use cache de transacciones
  265.         $connection->executeQuery('SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
  266.         
  267.         // Query nativo MySQL para obtener los últimos 5 tickets
  268.         $sql "
  269.             SELECT 
  270.                 t.id as ticketId,
  271.                 t.subject,
  272.                 s.code as status,
  273.                 p.code as priority,
  274.                 t.created_at as createdAt,
  275.                 t.updated_at as updatedAt,
  276.                 c.id as customerId,
  277.                 c.first_name as customerFirstName,
  278.                 c.last_name as customerLastName,
  279.                 c.email as customerEmail,
  280.                 c.idOR as idor,
  281.                 sg.name as supportGroupName,
  282.                 sg.description as supportGroupDescription,
  283.                 a.id as agentId,
  284.                 a.first_name as agentFirstName,
  285.                 a.last_name as agentLastName,
  286.                 a.email as agentEmail
  287.             FROM uv_ticket t
  288.             JOIN uv_user c ON t.customer_id = c.id
  289.             LEFT JOIN uv_ticket_status s ON t.status_id = s.id
  290.             LEFT JOIN uv_ticket_priority p ON t.priority_id = p.id
  291.             LEFT JOIN uv_support_group sg ON c.idOR = sg.id
  292.             LEFT JOIN uv_user a ON t.agent_id = a.id
  293.             WHERE t.is_trashed = 0
  294.             ORDER BY t.created_at DESC
  295.             LIMIT 5
  296.         ";
  297.         
  298.         $stmt $connection->prepare($sql);
  299.         $stmt->execute();
  300.         $tickets $stmt->fetchAll(\PDO::FETCH_ASSOC);
  301.         
  302.         // Formatear los datos para la respuesta
  303.         $formattedTickets = [];
  304.         foreach ($tickets as $ticket) {
  305.             $formattedTickets[] = [
  306.                 'ticketId' => $ticket['ticketId'],
  307.                 'subject' => $ticket['subject'],
  308.                 'status' => $ticket['status'],
  309.                 'priority' => $ticket['priority'],
  310.                 'createdAt' => $ticket['createdAt'],
  311.                 'updatedAt' => $ticket['updatedAt'],
  312.                 'customer' => [
  313.                     'id' => $ticket['customerId'],
  314.                     'name' => $ticket['customerFirstName'] . ' ' $ticket['customerLastName'],
  315.                     'email' => $ticket['customerEmail'],
  316.                     'idor' => $ticket['idor']
  317.                 ],
  318.                 'region' => [
  319.                     'name' => $ticket['supportGroupName'] ?: 'Sin región asignada',
  320.                     'description' => $ticket['supportGroupDescription'] ?: ''
  321.                 ],
  322.                 'agent' => $ticket['agentId'] ? [
  323.                     'id' => $ticket['agentId'],
  324.                     'name' => $ticket['agentFirstName'] . ' ' $ticket['agentLastName'],
  325.                     'email' => $ticket['agentEmail']
  326.                 ] : null
  327.             ];
  328.         }
  329.         
  330.         return new JsonResponse($formattedTickets);
  331.     }
  332.     public function getAgentsAction(): JsonResponse
  333.     {
  334.         $entityManager $this->getDoctrine()->getManager();
  335.         
  336.         // Forzar refresh - cerrar y reabrir la conexión para evitar cache
  337.         $entityManager->close();
  338.         $entityManager $this->getDoctrine()->getManager();
  339.         $connection $entityManager->getConnection();
  340.         
  341.         // Forzar que no use cache de transacciones
  342.         $connection->executeQuery('SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
  343.         
  344.         $sql "
  345.             SELECT 
  346.                 a.id as agentId,
  347.                 a.first_name as firstName,
  348.                 a.last_name as lastName,
  349.                 a.email,
  350.                 a.is_enabled as isEnabled,
  351.                 a.lastactivity,
  352.                 a.idOR as idor,
  353.                 sg.name as supportGroupName,
  354.                 sg.description as supportGroupDescription,
  355.                 sr.code as supportRoleCode
  356.             FROM uv_user a
  357.             JOIN uv_user_instance ui ON a.id = ui.user_id
  358.             JOIN uv_support_role sr ON ui.supportRole_id = sr.id
  359.             LEFT JOIN uv_support_group sg ON a.idOR = sg.id
  360.             WHERE a.is_enabled = 1
  361.             AND sr.code = 'ROLE_AGENT'
  362.             ORDER BY a.first_name, a.last_name
  363.         ";
  364.         
  365.         $stmt $connection->prepare($sql);
  366.         $stmt->execute();
  367.         $agents $stmt->fetchAll(\PDO::FETCH_ASSOC);
  368.         
  369.         $formattedAgents = [];
  370.         foreach ($agents as $agent) {
  371.             $formattedAgents[] = [
  372.                 'id' => $agent['agentId'],
  373.                 'name' => $agent['firstName'] . ' ' $agent['lastName'],
  374.                 'email' => $agent['email'],
  375.                 'isEnabled' => $agent['isEnabled'],
  376.                 'lastActivity' => $agent['lastactivity'] ?: 'N/A',
  377.                 'idor' => $agent['idor'],
  378.                 'role' => $agent['supportRoleCode'],
  379.                 'region' => [
  380.                     'name' => $agent['supportGroupName'] ?: 'Sin región asignada',
  381.                     'description' => $agent['supportGroupDescription'] ?: 'No hay descripción disponible'
  382.                 ]
  383.             ];
  384.         }
  385.         
  386.         return new JsonResponse($formattedAgents);
  387.     }
  388.     
  389.     public function getActiveOfficeAgentsAction(): JsonResponse
  390.     {
  391.         $entityManager $this->getDoctrine()->getManager();
  392.         
  393.         // Forzar refresh - cerrar y reabrir la conexión para evitar cache
  394.         $entityManager->close();
  395.         $entityManager $this->getDoctrine()->getManager();
  396.         $connection $entityManager->getConnection();
  397.         
  398.         // Forzar que no use cache de transacciones
  399.         $connection->executeQuery('SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
  400.         
  401.         // Primero obtener las oficinas que están abiertas actualmente
  402.         $sql "
  403.             SELECT 
  404.                 sg.id as supportGroupId,
  405.                 sg.name as supportGroupName,
  406.                 sg.description as supportGroupDescription,
  407.                 wh.timezone,
  408.                 wh.start_time,
  409.                 wh.end_time,
  410.                 wh.monday,
  411.                 wh.tuesday,
  412.                 wh.wednesday,
  413.                 wh.thursday,
  414.                 wh.friday,
  415.                 wh.saturday,
  416.                 wh.sunday,
  417.                 wh.is_active
  418.             FROM uv_support_group sg
  419.             LEFT JOIN uv_working_hours wh ON sg.id = wh.support_group_id
  420.             WHERE sg.is_active = 1
  421.             AND (sg.name LIKE 'OR %' OR sg.name = 'Casa Central')
  422.         ";
  423.         
  424.         $stmt $connection->prepare($sql);
  425.         $stmt->execute();
  426.         $supportGroups $stmt->fetchAll(\PDO::FETCH_ASSOC);
  427.         
  428.         // Determinar qué oficinas están abiertas
  429.         $openOffices = [];
  430.         foreach ($supportGroups as $group) {
  431.             // Si no hay configuración de horarios, usar valores por defecto
  432.             if (!$group['timezone']) {
  433.                 $group['timezone'] = 'UTC';
  434.                 $group['start_time'] = '09:00:00';
  435.                 $group['end_time'] = '18:00:00';
  436.                 $group['monday'] = 1;
  437.                 $group['tuesday'] = 1;
  438.                 $group['wednesday'] = 1;
  439.                 $group['thursday'] = 1;
  440.                 $group['friday'] = 1;
  441.                 $group['saturday'] = 0;
  442.                 $group['sunday'] = 0;
  443.             }
  444.             
  445.             $localTime = new \DateTime('now', new \DateTimeZone($group['timezone']));
  446.             $dayOfWeek = (int)$localTime->format('N'); // 1 = Monday, 7 = Sunday
  447.             $hour = (int)$localTime->format('G'); // 0-23 hour format
  448.             $minute = (int)$localTime->format('i');
  449.             $currentTime $hour 60 $minute// Convertir a minutos para comparación
  450.             
  451.             // Convertir horarios de inicio y fin a minutos
  452.             $startTimeParts explode(':'$group['start_time']);
  453.             $endTimeParts explode(':'$group['end_time']);
  454.             $startTimeMinutes = (int)$startTimeParts[0] * 60 + (int)$startTimeParts[1];
  455.             $endTimeMinutes = (int)$endTimeParts[0] * 60 + (int)$endTimeParts[1];
  456.             
  457.             // Verificar si es día laboral
  458.             $workingDays = [
  459.                 => $group['monday'],
  460.                 => $group['tuesday'],
  461.                 => $group['wednesday'],
  462.                 => $group['thursday'],
  463.                 => $group['friday'],
  464.                 => $group['saturday'],
  465.                 => $group['sunday']
  466.             ];
  467.             
  468.             $isWorkingDay $workingDays[$dayOfWeek] == 1;
  469.             
  470.             // Verificar si está dentro del horario laboral
  471.             $isWorkingHours $isWorkingDay && $currentTime >= $startTimeMinutes && $currentTime $endTimeMinutes;
  472.             
  473.             if ($isWorkingHours) {
  474.                 $openOffices[] = $group['supportGroupId'];
  475.             }
  476.         }
  477.         
  478.         // Si no hay oficinas abiertas, devolver array vacío
  479.         if (empty($openOffices)) {
  480.             return new JsonResponse([]);
  481.         }
  482.         
  483.         // Obtener agentes solo de las oficinas abiertas
  484.         $placeholders str_repeat('?,'count($openOffices) - 1) . '?';
  485.         $sql "
  486.             SELECT 
  487.                 a.id as agentId,
  488.                 a.first_name as firstName,
  489.                 a.last_name as lastName,
  490.                 a.email,
  491.                 a.is_enabled as isEnabled,
  492.                 a.lastactivity,
  493.                 a.idOR as idor,
  494.                 sg.name as supportGroupName,
  495.                 sg.description as supportGroupDescription,
  496.                 sr.code as supportRoleCode
  497.             FROM uv_user a
  498.             JOIN uv_user_instance ui ON a.id = ui.user_id
  499.             JOIN uv_support_role sr ON ui.supportRole_id = sr.id
  500.             LEFT JOIN uv_support_group sg ON a.idOR = sg.id
  501.             WHERE a.is_enabled = 1
  502.             AND sr.code = 'ROLE_AGENT'
  503.             AND a.idOR IN ($placeholders)
  504.             ORDER BY a.first_name, a.last_name
  505.         ";
  506.         
  507.         $stmt $connection->prepare($sql);
  508.         $stmt->execute($openOffices);
  509.         $agents $stmt->fetchAll(\PDO::FETCH_ASSOC);
  510.         
  511.         $formattedAgents = [];
  512.         foreach ($agents as $agent) {
  513.             $formattedAgents[] = [
  514.                 'id' => $agent['agentId'],
  515.                 'name' => $agent['firstName'] . ' ' $agent['lastName'],
  516.                 'email' => $agent['email'],
  517.                 'isEnabled' => $agent['isEnabled'],
  518.                 'lastActivity' => $agent['lastactivity'] ?: 'N/A',
  519.                 'idor' => $agent['idor'],
  520.                 'role' => $agent['supportRoleCode'],
  521.                 'region' => [
  522.                     'name' => $agent['supportGroupName'] ?: 'Sin región asignada',
  523.                     'description' => $agent['supportGroupDescription'] ?: 'No hay descripción disponible'
  524.                 ]
  525.             ];
  526.         }
  527.         
  528.         return new JsonResponse($formattedAgents);
  529.     }
  530.     
  531.     public function assignAgentAction(Request $request): JsonResponse
  532.     {
  533.         try {
  534.             $data json_decode($request->getContent(), true);
  535.             
  536.             if (!isset($data['ticketId']) || !isset($data['agentId'])) {
  537.                 return new JsonResponse([
  538.                     'success' => false,
  539.                     'message' => 'Se requieren ticketId y agentId'
  540.                 ], 400);
  541.             }
  542.             
  543.             $ticketId = (int) $data['ticketId'];
  544.             $agentId = (int) $data['agentId'];
  545.             
  546.             $entityManager $this->getDoctrine()->getManager();
  547.             
  548.             // Forzar refresh - cerrar y reabrir la conexión para evitar cache
  549.             $entityManager->close();
  550.             $entityManager $this->getDoctrine()->getManager();
  551.             $connection $entityManager->getConnection();
  552.             
  553.             // Forzar que no use cache de transacciones
  554.             $connection->executeQuery('SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
  555.             
  556.             // Verificar que el ticket existe usando query nativo
  557.             $sql "SELECT id FROM uv_ticket WHERE id = ?";
  558.             $stmt $connection->prepare($sql);
  559.             $stmt->execute([$ticketId]);
  560.             $ticket $stmt->fetch(\PDO::FETCH_ASSOC);
  561.             
  562.             if (!$ticket) {
  563.                 return new JsonResponse([
  564.                     'success' => false,
  565.                     'message' => 'El ticket especificado no existe'
  566.                 ], 404);
  567.             }
  568.             
  569.             // Verificar que el agente existe y está activo
  570.             $sql "SELECT id, first_name, last_name, is_enabled FROM uv_user WHERE id = ?";
  571.             $stmt $connection->prepare($sql);
  572.             $stmt->execute([$agentId]);
  573.             $agent $stmt->fetch(\PDO::FETCH_ASSOC);
  574.             
  575.             if (!$agent) {
  576.                 return new JsonResponse([
  577.                     'success' => false,
  578.                     'message' => 'El agente especificado no existe'
  579.                 ], 404);
  580.             }
  581.             
  582.             if (!$agent['is_enabled']) {
  583.                 return new JsonResponse([
  584.                     'success' => false,
  585.                     'message' => 'El agente especificado no está activo'
  586.                 ], 400);
  587.             }
  588.             
  589.             // Verificar que el usuario tiene rol de agente
  590.             $sql "
  591.                 SELECT sr.code 
  592.                 FROM uv_user_instance ui
  593.                 JOIN uv_support_role sr ON ui.supportRole_id = sr.id
  594.                 WHERE ui.user_id = ?
  595.             ";
  596.             $stmt $connection->prepare($sql);
  597.             $stmt->execute([$agentId]);
  598.             $role $stmt->fetch(\PDO::FETCH_ASSOC);
  599.             
  600.             if (!$role || $role['code'] !== 'ROLE_AGENT') {
  601.                 return new JsonResponse([
  602.                     'success' => false,
  603.                     'message' => 'El usuario especificado no tiene rol de agente'
  604.                 ], 400);
  605.             }
  606.             
  607.             // Asignar el agente al ticket usando query nativo
  608.             $sql "UPDATE uv_ticket SET agent_id = ? WHERE id = ?";
  609.             $stmt $connection->prepare($sql);
  610.             $stmt->execute([$agentId$ticketId]);
  611.             
  612.             return new JsonResponse([
  613.                 'success' => true,
  614.                 'message' => 'Ticket asignado exitosamente al agente',
  615.                 'data' => [
  616.                     'ticketId' => $ticketId,
  617.                     'agentId' => $agentId,
  618.                     'agentName' => $agent['first_name'] . ' ' $agent['last_name']
  619.                 ]
  620.             ]);
  621.             
  622.         } catch (\Exception $e) {
  623.             return new JsonResponse([
  624.                 'success' => false,
  625.                 'message' => 'Error interno del servidor: ' $e->getMessage()
  626.             ], 500);
  627.         }
  628.     }
  629.     
  630.     public function updateWorkingHoursAction(Request $request): JsonResponse
  631.     {
  632.         try {
  633.             $data json_decode($request->getContent(), true);
  634.             
  635.             if (!isset($data['supportGroupId']) || !isset($data['timezone']) || 
  636.                 !isset($data['startTime']) || !isset($data['endTime'])) {
  637.                 return new JsonResponse([
  638.                     'success' => false,
  639.                     'message' => 'Se requieren todos los campos obligatorios'
  640.                 ], 400);
  641.             }
  642.             
  643.             $supportGroupId = (int) $data['supportGroupId'];
  644.             $timezone $data['timezone'];
  645.             $startTime $data['startTime'];
  646.             $endTime $data['endTime'];
  647.             $monday = isset($data['monday']) ? (int) $data['monday'] : 1;
  648.             $tuesday = isset($data['tuesday']) ? (int) $data['tuesday'] : 1;
  649.             $wednesday = isset($data['wednesday']) ? (int) $data['wednesday'] : 1;
  650.             $thursday = isset($data['thursday']) ? (int) $data['thursday'] : 1;
  651.             $friday = isset($data['friday']) ? (int) $data['friday'] : 1;
  652.             $saturday = isset($data['saturday']) ? (int) $data['saturday'] : 0;
  653.             $sunday = isset($data['sunday']) ? (int) $data['sunday'] : 0;
  654.             
  655.             $entityManager $this->getDoctrine()->getManager();
  656.             
  657.             // Forzar refresh - cerrar y reabrir la conexión para evitar cache
  658.             $entityManager->close();
  659.             $entityManager $this->getDoctrine()->getManager();
  660.             $connection $entityManager->getConnection();
  661.             
  662.             // Forzar que no use cache de transacciones
  663.             $connection->executeQuery('SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
  664.             
  665.             // Verificar que el grupo de soporte existe
  666.             $sql "SELECT id FROM uv_support_group WHERE id = ? AND is_active = 1";
  667.             $stmt $connection->prepare($sql);
  668.             $stmt->execute([$supportGroupId]);
  669.             $supportGroup $stmt->fetch(\PDO::FETCH_ASSOC);
  670.             
  671.             if (!$supportGroup) {
  672.                 return new JsonResponse([
  673.                     'success' => false,
  674.                     'message' => 'El grupo de soporte especificado no existe o no está activo'
  675.                 ], 404);
  676.             }
  677.             
  678.             // Verificar si ya existe configuración de horarios para este grupo
  679.             $sql "SELECT id FROM uv_working_hours WHERE support_group_id = ?";
  680.             $stmt $connection->prepare($sql);
  681.             $stmt->execute([$supportGroupId]);
  682.             $existingHours $stmt->fetch(\PDO::FETCH_ASSOC);
  683.             
  684.             if ($existingHours) {
  685.                 // Actualizar horarios existentes
  686.                 $sql "
  687.                     UPDATE uv_working_hours 
  688.                     SET timezone = ?, start_time = ?, end_time = ?, 
  689.                         monday = ?, tuesday = ?, wednesday = ?, thursday = ?, 
  690.                         friday = ?, saturday = ?, sunday = ?, updated_at = CURRENT_TIMESTAMP
  691.                     WHERE support_group_id = ?
  692.                 ";
  693.                 $stmt $connection->prepare($sql);
  694.                 $stmt->execute([
  695.                     $timezone$startTime$endTime,
  696.                     $monday$tuesday$wednesday$thursday$friday$saturday$sunday,
  697.                     $supportGroupId
  698.                 ]);
  699.             } else {
  700.                 // Crear nueva configuración de horarios
  701.                 $sql "
  702.                     INSERT INTO uv_working_hours 
  703.                     (support_group_id, timezone, start_time, end_time, 
  704.                      monday, tuesday, wednesday, thursday, friday, saturday, sunday)
  705.                     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  706.                 ";
  707.                 $stmt $connection->prepare($sql);
  708.                 $stmt->execute([
  709.                     $supportGroupId$timezone$startTime$endTime,
  710.                     $monday$tuesday$wednesday$thursday$friday$saturday$sunday
  711.                 ]);
  712.             }
  713.             
  714.             return new JsonResponse([
  715.                 'success' => true,
  716.                 'message' => 'Horarios laborales actualizados exitosamente',
  717.                 'data' => [
  718.                     'supportGroupId' => $supportGroupId,
  719.                     'timezone' => $timezone,
  720.                     'startTime' => $startTime,
  721.                     'endTime' => $endTime,
  722.                     'workingDays' => [
  723.                         'monday' => $monday,
  724.                         'tuesday' => $tuesday,
  725.                         'wednesday' => $wednesday,
  726.                         'thursday' => $thursday,
  727.                         'friday' => $friday,
  728.                         'saturday' => $saturday,
  729.                         'sunday' => $sunday
  730.                     ]
  731.                 ]
  732.             ]);
  733.             
  734.         } catch (\Exception $e) {
  735.             return new JsonResponse([
  736.                 'success' => false,
  737.                 'message' => 'Error interno del servidor: ' $e->getMessage()
  738.             ], 500);
  739.         }
  740.     }
  741. }