src/Controller/WorkingHoursController.php line 363

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