src/Controller/WorkingHoursController.php line 285

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' => $this->getDayOfWeekInSpanish($localTime->format('N')),
  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->getWorkingDaysArray($workingDays),
  189.                 'workingDaysText' => $this->getWorkingDaysText($workingDays),
  190.                 'supportGroupId' => $group['supportGroupId'],
  191.                 'agents' => $countryAgents
  192.             ];
  193.         }
  194.         return new JsonResponse($status);
  195.     }
  196.     
  197.     private function isCountryInRegion($country$regionName): bool
  198.     {
  199.         // Mapeo de países a posibles nombres de regiones
  200.         $countryRegionMap = [
  201.             'Casa Central' => ['casa central''argentina''buenos aires''córdoba''rosario'],
  202.             'Colombia' => ['colombia''bogotá''medellín''cali''barranquilla'],
  203.             'Mexico' => ['mexico''méxico''ciudad de mexico''cdmx''guadalajara''monterrey'],
  204.             'Brasil' => ['brasil''brazil''sao paulo''rio de janeiro''brasilia'],
  205.             'España' => ['españa''spain''madrid''barcelona''valencia''sevilla'],
  206.             'Peru' => ['peru''perú''lima''arequipa''trujillo'],
  207.             'Chile' => ['chile''santiago''valparaíso''concepción'],
  208.             'Venezuela' => ['venezuela''caracas''maracaibo''valencia'],
  209.             'Costa Rica' => ['costa rica''san josé''cartago''alajuela']
  210.         ];
  211.         
  212.         if (isset($countryRegionMap[$country])) {
  213.             foreach ($countryRegionMap[$country] as $regionKeyword) {
  214.                 if (stripos($regionName$regionKeyword) !== false) {
  215.                     return true;
  216.                 }
  217.             }
  218.         }
  219.         
  220.         return false;
  221.     }
  222.     
  223.     private function getWorkingDaysText($workingDays): string
  224.     {
  225.         $days = [];
  226.         $dayNames = [
  227.             => 'Lunes',
  228.             => 'Martes'
  229.             => 'Miércoles',
  230.             => 'Jueves',
  231.             => 'Viernes',
  232.             => 'Sábado',
  233.             => 'Domingo'
  234.         ];
  235.         
  236.         foreach ($workingDays as $dayNum => $isWorking) {
  237.             if ($isWorking) {
  238.                 $days[] = $dayNames[$dayNum];
  239.             }
  240.         }
  241.         
  242.         if (empty($days)) {
  243.             return 'No hay días laborales configurados';
  244.         }
  245.         
  246.         if (count($days) === 7) {
  247.             return 'Todos los días';
  248.         }
  249.         
  250.         if (count($days) === && $workingDays[1] && $workingDays[2] && $workingDays[3] && $workingDays[4] && $workingDays[5]) {
  251.             return 'Lunes a Viernes';
  252.         }
  253.         
  254.         return implode(', '$days);
  255.     }
  256.     private function getWorkingDaysArray($workingDays): array
  257.     {
  258.         $days = [];
  259.         $dayNames = [
  260.             => 'Lunes',
  261.             => 'Martes'
  262.             => 'Miércoles',
  263.             => 'Jueves',
  264.             => 'Viernes',
  265.             => 'Sábado',
  266.             => 'Domingo'
  267.         ];
  268.         
  269.         foreach ($workingDays as $dayNum => $isWorking) {
  270.             if ($isWorking) {
  271.                 $days[] = $dayNames[$dayNum];
  272.             }
  273.         }
  274.         
  275.         return $days;
  276.     }
  277.     public function getRecentTicketsAction(): JsonResponse
  278.     {
  279.         $entityManager $this->getDoctrine()->getManager();
  280.         
  281.         // Forzar refresh - cerrar y reabrir la conexión para evitar cache
  282.         $entityManager->close();
  283.         $entityManager $this->getDoctrine()->getManager();
  284.         $connection $entityManager->getConnection();
  285.         
  286.         // Forzar que no use cache de transacciones
  287.         $connection->executeQuery('SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
  288.         
  289.         // Query nativo MySQL para obtener los últimos 5 tickets
  290.         $sql "
  291.             SELECT 
  292.                 t.id as ticketId,
  293.                 t.subject,
  294.                 s.code as status,
  295.                 p.code as priority,
  296.                 t.created_at as createdAt,
  297.                 t.updated_at as updatedAt,
  298.                 c.id as customerId,
  299.                 c.first_name as customerFirstName,
  300.                 c.last_name as customerLastName,
  301.                 c.email as customerEmail,
  302.                 c.idOR as idor,
  303.                 sg.name as supportGroupName,
  304.                 sg.description as supportGroupDescription,
  305.                 a.id as agentId,
  306.                 a.first_name as agentFirstName,
  307.                 a.last_name as agentLastName,
  308.                 a.email as agentEmail
  309.             FROM uv_ticket t
  310.             JOIN uv_user c ON t.customer_id = c.id
  311.             LEFT JOIN uv_ticket_status s ON t.status_id = s.id
  312.             LEFT JOIN uv_ticket_priority p ON t.priority_id = p.id
  313.             LEFT JOIN uv_support_group sg ON c.idOR = sg.id
  314.             LEFT JOIN uv_user a ON t.agent_id = a.id
  315.             WHERE t.is_trashed = 0
  316.             ORDER BY t.created_at DESC
  317.             LIMIT 5
  318.         ";
  319.         
  320.         $stmt $connection->prepare($sql);
  321.         $stmt->execute();
  322.         $tickets $stmt->fetchAll(\PDO::FETCH_ASSOC);
  323.         
  324.         // Formatear los datos para la respuesta
  325.         $formattedTickets = [];
  326.         foreach ($tickets as $ticket) {
  327.             $formattedTickets[] = [
  328.                 'ticketId' => $ticket['ticketId'],
  329.                 'subject' => $ticket['subject'],
  330.                 'status' => $ticket['status'],
  331.                 'priority' => $ticket['priority'],
  332.                 'createdAt' => $ticket['createdAt'],
  333.                 'updatedAt' => $ticket['updatedAt'],
  334.                 'customer' => [
  335.                     'id' => $ticket['customerId'],
  336.                     'name' => $ticket['customerFirstName'] . ' ' $ticket['customerLastName'],
  337.                     'email' => $ticket['customerEmail'],
  338.                     'idor' => $ticket['idor']
  339.                 ],
  340.                 'region' => [
  341.                     'name' => $ticket['supportGroupName'] ?: 'Sin región asignada',
  342.                     'description' => $ticket['supportGroupDescription'] ?: ''
  343.                 ],
  344.                 'agent' => $ticket['agentId'] ? [
  345.                     'id' => $ticket['agentId'],
  346.                     'name' => $ticket['agentFirstName'] . ' ' $ticket['agentLastName'],
  347.                     'email' => $ticket['agentEmail']
  348.                 ] : null
  349.             ];
  350.         }
  351.         
  352.         return new JsonResponse($formattedTickets);
  353.     }
  354.     public function getAgentsAction(): JsonResponse
  355.     {
  356.         $entityManager $this->getDoctrine()->getManager();
  357.         
  358.         // Forzar refresh - cerrar y reabrir la conexión para evitar cache
  359.         $entityManager->close();
  360.         $entityManager $this->getDoctrine()->getManager();
  361.         $connection $entityManager->getConnection();
  362.         
  363.         // Forzar que no use cache de transacciones
  364.         $connection->executeQuery('SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
  365.         
  366.         $sql "
  367.             SELECT 
  368.                 a.id as agentId,
  369.                 a.first_name as firstName,
  370.                 a.last_name as lastName,
  371.                 a.email,
  372.                 a.is_enabled as isEnabled,
  373.                 a.lastactivity,
  374.                 a.idOR as idor,
  375.                 sg.name as supportGroupName,
  376.                 sg.description as supportGroupDescription,
  377.                 sr.code as supportRoleCode
  378.             FROM uv_user a
  379.             JOIN uv_user_instance ui ON a.id = ui.user_id
  380.             JOIN uv_support_role sr ON ui.supportRole_id = sr.id
  381.             LEFT JOIN uv_support_group sg ON a.idOR = sg.id
  382.             WHERE a.is_enabled = 1
  383.             AND sr.code = 'ROLE_AGENT'
  384.             ORDER BY a.first_name, a.last_name
  385.         ";
  386.         
  387.         $stmt $connection->prepare($sql);
  388.         $stmt->execute();
  389.         $agents $stmt->fetchAll(\PDO::FETCH_ASSOC);
  390.         
  391.         $formattedAgents = [];
  392.         foreach ($agents as $agent) {
  393.             $formattedAgents[] = [
  394.                 'id' => $agent['agentId'],
  395.                 'name' => $agent['firstName'] . ' ' $agent['lastName'],
  396.                 'email' => $agent['email'],
  397.                 'isEnabled' => $agent['isEnabled'],
  398.                 'lastActivity' => $agent['lastactivity'] ?: 'N/A',
  399.                 'idor' => $agent['idor'],
  400.                 'role' => $agent['supportRoleCode'],
  401.                 'region' => [
  402.                     'name' => $agent['supportGroupName'] ?: 'Sin región asignada',
  403.                     'description' => $agent['supportGroupDescription'] ?: 'No hay descripción disponible'
  404.                 ]
  405.             ];
  406.         }
  407.         
  408.         return new JsonResponse($formattedAgents);
  409.     }
  410.     
  411.     public function getActiveOfficeAgentsAction(): JsonResponse
  412.     {
  413.         $entityManager $this->getDoctrine()->getManager();
  414.         
  415.         // Forzar refresh - cerrar y reabrir la conexión para evitar cache
  416.         $entityManager->close();
  417.         $entityManager $this->getDoctrine()->getManager();
  418.         $connection $entityManager->getConnection();
  419.         
  420.         // Forzar que no use cache de transacciones
  421.         $connection->executeQuery('SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
  422.         
  423.         // Primero obtener las oficinas que están abiertas actualmente
  424.         $sql "
  425.             SELECT 
  426.                 sg.id as supportGroupId,
  427.                 sg.name as supportGroupName,
  428.                 sg.description as supportGroupDescription,
  429.                 wh.timezone,
  430.                 wh.start_time,
  431.                 wh.end_time,
  432.                 wh.monday,
  433.                 wh.tuesday,
  434.                 wh.wednesday,
  435.                 wh.thursday,
  436.                 wh.friday,
  437.                 wh.saturday,
  438.                 wh.sunday,
  439.                 wh.is_active
  440.             FROM uv_support_group sg
  441.             LEFT JOIN uv_working_hours wh ON sg.id = wh.support_group_id
  442.             WHERE sg.is_active = 1
  443.             AND (sg.name LIKE 'OR %' OR sg.name = 'Casa Central')
  444.         ";
  445.         
  446.         $stmt $connection->prepare($sql);
  447.         $stmt->execute();
  448.         $supportGroups $stmt->fetchAll(\PDO::FETCH_ASSOC);
  449.         
  450.         // Determinar qué oficinas están abiertas
  451.         $openOffices = [];
  452.         foreach ($supportGroups as $group) {
  453.             // Si no hay configuración de horarios, usar valores por defecto
  454.             if (!$group['timezone']) {
  455.                 $group['timezone'] = 'UTC';
  456.                 $group['start_time'] = '09:00:00';
  457.                 $group['end_time'] = '18:00:00';
  458.                 $group['monday'] = 1;
  459.                 $group['tuesday'] = 1;
  460.                 $group['wednesday'] = 1;
  461.                 $group['thursday'] = 1;
  462.                 $group['friday'] = 1;
  463.                 $group['saturday'] = 0;
  464.                 $group['sunday'] = 0;
  465.             }
  466.             
  467.             $localTime = new \DateTime('now', new \DateTimeZone($group['timezone']));
  468.             $dayOfWeek = (int)$localTime->format('N'); // 1 = Monday, 7 = Sunday
  469.             $hour = (int)$localTime->format('G'); // 0-23 hour format
  470.             $minute = (int)$localTime->format('i');
  471.             $currentTime $hour 60 $minute// Convertir a minutos para comparación
  472.             
  473.             // Convertir horarios de inicio y fin a minutos
  474.             $startTimeParts explode(':'$group['start_time']);
  475.             $endTimeParts explode(':'$group['end_time']);
  476.             $startTimeMinutes = (int)$startTimeParts[0] * 60 + (int)$startTimeParts[1];
  477.             $endTimeMinutes = (int)$endTimeParts[0] * 60 + (int)$endTimeParts[1];
  478.             
  479.             // Verificar si es día laboral
  480.             $workingDays = [
  481.                 => $group['monday'],
  482.                 => $group['tuesday'],
  483.                 => $group['wednesday'],
  484.                 => $group['thursday'],
  485.                 => $group['friday'],
  486.                 => $group['saturday'],
  487.                 => $group['sunday']
  488.             ];
  489.             
  490.             $isWorkingDay $workingDays[$dayOfWeek] == 1;
  491.             
  492.             // Verificar si está dentro del horario laboral
  493.             $isWorkingHours $isWorkingDay && $currentTime >= $startTimeMinutes && $currentTime $endTimeMinutes;
  494.             
  495.             if ($isWorkingHours) {
  496.                 $openOffices[] = $group['supportGroupId'];
  497.             }
  498.         }
  499.         
  500.         // Si no hay oficinas abiertas, devolver array vacío
  501.         if (empty($openOffices)) {
  502.             return new JsonResponse([]);
  503.         }
  504.         
  505.         // Obtener agentes solo de las oficinas abiertas
  506.         $placeholders str_repeat('?,'count($openOffices) - 1) . '?';
  507.         $sql "
  508.             SELECT 
  509.                 a.id as agentId,
  510.                 a.first_name as firstName,
  511.                 a.last_name as lastName,
  512.                 a.email,
  513.                 a.is_enabled as isEnabled,
  514.                 a.lastactivity,
  515.                 a.idOR as idor,
  516.                 sg.name as supportGroupName,
  517.                 sg.description as supportGroupDescription,
  518.                 sr.code as supportRoleCode
  519.             FROM uv_user a
  520.             JOIN uv_user_instance ui ON a.id = ui.user_id
  521.             JOIN uv_support_role sr ON ui.supportRole_id = sr.id
  522.             LEFT JOIN uv_support_group sg ON a.idOR = sg.id
  523.             WHERE a.is_enabled = 1
  524.             AND sr.code = 'ROLE_AGENT'
  525.             AND a.idOR IN ($placeholders)
  526.             ORDER BY a.first_name, a.last_name
  527.         ";
  528.         
  529.         $stmt $connection->prepare($sql);
  530.         $stmt->execute($openOffices);
  531.         $agents $stmt->fetchAll(\PDO::FETCH_ASSOC);
  532.         
  533.         $formattedAgents = [];
  534.         foreach ($agents as $agent) {
  535.             $formattedAgents[] = [
  536.                 'id' => $agent['agentId'],
  537.                 'name' => $agent['firstName'] . ' ' $agent['lastName'],
  538.                 'email' => $agent['email'],
  539.                 'isEnabled' => $agent['isEnabled'],
  540.                 'lastActivity' => $agent['lastactivity'] ?: 'N/A',
  541.                 'idor' => $agent['idor'],
  542.                 'role' => $agent['supportRoleCode'],
  543.                 'region' => [
  544.                     'name' => $agent['supportGroupName'] ?: 'Sin región asignada',
  545.                     'description' => $agent['supportGroupDescription'] ?: 'No hay descripción disponible'
  546.                 ]
  547.             ];
  548.         }
  549.         
  550.         return new JsonResponse($formattedAgents);
  551.     }
  552.     
  553.     public function assignAgentAction(Request $request): JsonResponse
  554.     {
  555.         try {
  556.             $data json_decode($request->getContent(), true);
  557.             
  558.             if (!isset($data['ticketId']) || !isset($data['agentId'])) {
  559.                 return new JsonResponse([
  560.                     'success' => false,
  561.                     'message' => 'Se requieren ticketId y agentId'
  562.                 ], 400);
  563.             }
  564.             
  565.             $ticketId = (int) $data['ticketId'];
  566.             $agentId = (int) $data['agentId'];
  567.             
  568.             $entityManager $this->getDoctrine()->getManager();
  569.             
  570.             // Forzar refresh - cerrar y reabrir la conexión para evitar cache
  571.             $entityManager->close();
  572.             $entityManager $this->getDoctrine()->getManager();
  573.             $connection $entityManager->getConnection();
  574.             
  575.             // Forzar que no use cache de transacciones
  576.             $connection->executeQuery('SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
  577.             
  578.             // Verificar que el ticket existe usando query nativo
  579.             $sql "SELECT id FROM uv_ticket WHERE id = ?";
  580.             $stmt $connection->prepare($sql);
  581.             $stmt->execute([$ticketId]);
  582.             $ticket $stmt->fetch(\PDO::FETCH_ASSOC);
  583.             
  584.             if (!$ticket) {
  585.                 return new JsonResponse([
  586.                     'success' => false,
  587.                     'message' => 'El ticket especificado no existe'
  588.                 ], 404);
  589.             }
  590.             
  591.             // Verificar que el agente existe y está activo
  592.             $sql "SELECT id, first_name, last_name, is_enabled FROM uv_user WHERE id = ?";
  593.             $stmt $connection->prepare($sql);
  594.             $stmt->execute([$agentId]);
  595.             $agent $stmt->fetch(\PDO::FETCH_ASSOC);
  596.             
  597.             if (!$agent) {
  598.                 return new JsonResponse([
  599.                     'success' => false,
  600.                     'message' => 'El agente especificado no existe'
  601.                 ], 404);
  602.             }
  603.             
  604.             if (!$agent['is_enabled']) {
  605.                 return new JsonResponse([
  606.                     'success' => false,
  607.                     'message' => 'El agente especificado no está activo'
  608.                 ], 400);
  609.             }
  610.             
  611.             // Verificar que el usuario tiene rol de agente
  612.             $sql "
  613.                 SELECT sr.code 
  614.                 FROM uv_user_instance ui
  615.                 JOIN uv_support_role sr ON ui.supportRole_id = sr.id
  616.                 WHERE ui.user_id = ?
  617.             ";
  618.             $stmt $connection->prepare($sql);
  619.             $stmt->execute([$agentId]);
  620.             $role $stmt->fetch(\PDO::FETCH_ASSOC);
  621.             
  622.             if (!$role || $role['code'] !== 'ROLE_AGENT') {
  623.                 return new JsonResponse([
  624.                     'success' => false,
  625.                     'message' => 'El usuario especificado no tiene rol de agente'
  626.                 ], 400);
  627.             }
  628.             
  629.             // Asignar el agente al ticket usando query nativo
  630.             $sql "UPDATE uv_ticket SET agent_id = ? WHERE id = ?";
  631.             $stmt $connection->prepare($sql);
  632.             $stmt->execute([$agentId$ticketId]);
  633.             
  634.             return new JsonResponse([
  635.                 'success' => true,
  636.                 'message' => 'Ticket asignado exitosamente al agente',
  637.                 'data' => [
  638.                     'ticketId' => $ticketId,
  639.                     'agentId' => $agentId,
  640.                     'agentName' => $agent['first_name'] . ' ' $agent['last_name']
  641.                 ]
  642.             ]);
  643.             
  644.         } catch (\Exception $e) {
  645.             return new JsonResponse([
  646.                 'success' => false,
  647.                 'message' => 'Error interno del servidor: ' $e->getMessage()
  648.             ], 500);
  649.         }
  650.     }
  651.     
  652.     public function updateWorkingHoursAction(Request $request): JsonResponse
  653.     {
  654.         try {
  655.             $data json_decode($request->getContent(), true);
  656.             
  657.             if (!isset($data['supportGroupId']) || !isset($data['timezone']) || 
  658.                 !isset($data['startTime']) || !isset($data['endTime'])) {
  659.                 return new JsonResponse([
  660.                     'success' => false,
  661.                     'message' => 'Se requieren todos los campos obligatorios'
  662.                 ], 400);
  663.             }
  664.             
  665.             $supportGroupId = (int) $data['supportGroupId'];
  666.             $timezone $data['timezone'];
  667.             $startTime $data['startTime'];
  668.             $endTime $data['endTime'];
  669.             $monday = isset($data['monday']) ? (int) $data['monday'] : 1;
  670.             $tuesday = isset($data['tuesday']) ? (int) $data['tuesday'] : 1;
  671.             $wednesday = isset($data['wednesday']) ? (int) $data['wednesday'] : 1;
  672.             $thursday = isset($data['thursday']) ? (int) $data['thursday'] : 1;
  673.             $friday = isset($data['friday']) ? (int) $data['friday'] : 1;
  674.             $saturday = isset($data['saturday']) ? (int) $data['saturday'] : 0;
  675.             $sunday = isset($data['sunday']) ? (int) $data['sunday'] : 0;
  676.             
  677.             $entityManager $this->getDoctrine()->getManager();
  678.             
  679.             // Forzar refresh - cerrar y reabrir la conexión para evitar cache
  680.             $entityManager->close();
  681.             $entityManager $this->getDoctrine()->getManager();
  682.             $connection $entityManager->getConnection();
  683.             
  684.             // Forzar que no use cache de transacciones
  685.             $connection->executeQuery('SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
  686.             
  687.             // Verificar que el grupo de soporte existe
  688.             $sql "SELECT id FROM uv_support_group WHERE id = ? AND is_active = 1";
  689.             $stmt $connection->prepare($sql);
  690.             $stmt->execute([$supportGroupId]);
  691.             $supportGroup $stmt->fetch(\PDO::FETCH_ASSOC);
  692.             
  693.             if (!$supportGroup) {
  694.                 return new JsonResponse([
  695.                     'success' => false,
  696.                     'message' => 'El grupo de soporte especificado no existe o no está activo'
  697.                 ], 404);
  698.             }
  699.             
  700.             // Verificar si ya existe configuración de horarios para este grupo
  701.             $sql "SELECT id FROM uv_working_hours WHERE support_group_id = ?";
  702.             $stmt $connection->prepare($sql);
  703.             $stmt->execute([$supportGroupId]);
  704.             $existingHours $stmt->fetch(\PDO::FETCH_ASSOC);
  705.             
  706.             if ($existingHours) {
  707.                 // Actualizar horarios existentes
  708.                 $sql "
  709.                     UPDATE uv_working_hours 
  710.                     SET timezone = ?, start_time = ?, end_time = ?, 
  711.                         monday = ?, tuesday = ?, wednesday = ?, thursday = ?, 
  712.                         friday = ?, saturday = ?, sunday = ?, updated_at = CURRENT_TIMESTAMP
  713.                     WHERE support_group_id = ?
  714.                 ";
  715.                 $stmt $connection->prepare($sql);
  716.                 $stmt->execute([
  717.                     $timezone$startTime$endTime,
  718.                     $monday$tuesday$wednesday$thursday$friday$saturday$sunday,
  719.                     $supportGroupId
  720.                 ]);
  721.             } else {
  722.                 // Crear nueva configuración de horarios
  723.                 $sql "
  724.                     INSERT INTO uv_working_hours 
  725.                     (support_group_id, timezone, start_time, end_time, 
  726.                      monday, tuesday, wednesday, thursday, friday, saturday, sunday)
  727.                     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  728.                 ";
  729.                 $stmt $connection->prepare($sql);
  730.                 $stmt->execute([
  731.                     $supportGroupId$timezone$startTime$endTime,
  732.                     $monday$tuesday$wednesday$thursday$friday$saturday$sunday
  733.                 ]);
  734.             }
  735.             
  736.             return new JsonResponse([
  737.                 'success' => true,
  738.                 'message' => 'Horarios laborales actualizados exitosamente',
  739.                 'data' => [
  740.                     'supportGroupId' => $supportGroupId,
  741.                     'timezone' => $timezone,
  742.                     'startTime' => $startTime,
  743.                     'endTime' => $endTime,
  744.                     'workingDays' => [
  745.                         'monday' => $monday,
  746.                         'tuesday' => $tuesday,
  747.                         'wednesday' => $wednesday,
  748.                         'thursday' => $thursday,
  749.                         'friday' => $friday,
  750.                         'saturday' => $saturday,
  751.                         'sunday' => $sunday
  752.                     ]
  753.                 ]
  754.             ]);
  755.             
  756.         } catch (\Exception $e) {
  757.             return new JsonResponse([
  758.                 'success' => false,
  759.                 'message' => 'Error interno del servidor: ' $e->getMessage()
  760.             ], 500);
  761.         }
  762.     }
  763.     /**
  764.      * Convierte el número del día de la semana a nombre en español
  765.      * @param int $dayNumber 1 = Lunes, 7 = Domingo
  766.      * @return string
  767.      */
  768.     private function getDayOfWeekInSpanish($dayNumber)
  769.     {
  770.         $days = [
  771.             => 'Lunes',
  772.             => 'Martes'
  773.             => 'Miércoles',
  774.             => 'Jueves',
  775.             => 'Viernes',
  776.             => 'Sábado',
  777.             => 'Domingo'
  778.         ];
  779.         
  780.         return $days[$dayNumber] ?? 'Desconocido';
  781.     }
  782. }