vendor/uvdesk/core-framework/Controller/TicketXHR.php line 529

Open in your IDE?
  1. <?php
  2. namespace Webkul\UVDesk\CoreFrameworkBundle\Controller;
  3. use Symfony\Component\HttpFoundation\Request;
  4. use Symfony\Component\HttpFoundation\Response;
  5. use Symfony\Component\HttpFoundation\JsonResponse;
  6. use Webkul\UVDesk\CoreFrameworkBundle\Entity as CoreFrameworkBundleEntities;
  7. use Webkul\UVDesk\CoreFrameworkBundle\Entity\SupportLabel;
  8. use Symfony\Component\EventDispatcher\GenericEvent;
  9. use Symfony\Bundle\FrameworkBundle\Controller\Controller;
  10. use Webkul\UVDesk\CoreFrameworkBundle\Workflow\Events as CoreWorkflowEvents;
  11. use Webkul\UVDesk\CoreFrameworkBundle\Form as CoreFrameworkBundleForms;
  12. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  13. use Webkul\UVDesk\CoreFrameworkBundle\DataProxies as CoreFrameworkBundleDataProxies;
  14. class TicketXHR extends Controller
  15. {
  16.     public function loadTicketXHR($ticketId)
  17.     {
  18.         $entityManager $this->getDoctrine()->getManager();
  19.         $request $this->container->get('request_stack')->getCurrentRequest();
  20.     }
  21.     public function bookmarkTicketXHR()
  22.     {
  23.         $entityManager $this->getDoctrine()->getManager();
  24.         $request $this->container->get('request_stack')->getCurrentRequest();
  25.         $requestContent json_decode($request->getContent(), true);
  26.         $ticket $entityManager->getRepository('UVDeskCoreFrameworkBundle:Ticket')->findOneById($requestContent['id']);
  27.         if (!empty($ticket)) {
  28.             $ticket->setIsStarred(!$ticket->getIsStarred());
  29.             $entityManager->persist($ticket);
  30.             $entityManager->flush();
  31.             return new Response(json_encode(['alertClass' => 'success']), 200, ['Content-Type' => 'application/json']);
  32.         }
  33.         return new Response(json_encode([]), 404, ['Content-Type' => 'application/json']);
  34.     }
  35.     public function ticketLabelXHR(Request $request)
  36.     {
  37.         $method $request->getMethod();
  38.         $content $request->getContent();
  39.         $em $this->getDoctrine()->getManager();
  40.         if($method == "POST") {
  41.             $data json_decode($contenttrue);
  42.             if($data['name'] != "") {
  43.                 $label = new SupportLabel();
  44.                 $label->setName($data['name']);
  45.                 if(isset($data['colorCode']))
  46.                     $label->setColorCode($data['colorCode']);
  47.                 $label->setUser($this->get('user.service')->getCurrentUser());
  48.                 $em->persist($label);
  49.                 $em->flush();
  50.                 $json['alertClass'] = 'success';
  51.                 $json['alertMessage'] = $this->get('translator')->trans('Success ! Label created successfully.');
  52.                 $json['label'] = json_encode([
  53.                     'id' => $label->getId(),
  54.                     'name' => $label->getName(),
  55.                     'colorCode' => $label->getColorCode(),
  56.                     'labelUser' => $label->getUser()->getId(),
  57.                 ]);
  58.             } else {
  59.                 $json['alertClass'] = 'danger';
  60.                 $json['alertMessage'] = $this->get('translator')->trans('Error ! Label name can not be blank.');
  61.             }
  62.         } elseif($method == "PUT") {
  63.             $data json_decode($contenttrue);
  64.             $label $em->getRepository('UVDeskCoreFrameworkBundle:SupportLabel')->findOneBy(array('id' => $request->attributes->get('ticketLabelId')));
  65.             if($label) {
  66.                 $label->setName($data['name']);
  67.                 if(!empty($data['colorCode'])) {
  68.                     $label->setColorCode($data['colorCode']);
  69.                 }
  70.                 $em->persist($label);
  71.                 $em->flush();
  72.                 $json['label'] = json_encode([
  73.                     'id' => $label->getId(),
  74.                     'name' => $label->getName(),
  75.                     'colorCode' => $label->getColorCode(),
  76.                     'labelUser' => $label->getUser()->getId(),
  77.                 ]);
  78.                 $json['alertClass'] = 'success';
  79.                 $json['alertMessage'] = $this->get('translator')->trans('Success ! Label updated successfully.');
  80.             } else {
  81.                 $json['alertClass'] = 'danger';
  82.                 $json['alertMessage'] = $this->get('translator')->trans('Error ! Invalid label id.');
  83.             }
  84.         } elseif($method == "DELETE") {
  85.             $label $em->getRepository('UVDeskCoreFrameworkBundle:SupportLabel')->findOneBy(array('id' => $request->attributes->get('ticketLabelId')));
  86.             if($label) {
  87.                 $em->remove($label);
  88.                 $em->flush();
  89.                 $json['alertClass'] = 'success';
  90.                 $json['alertMessage'] = $this->get('translator')->trans('Success ! Label removed successfully.');
  91.             } else {
  92.                 $json['alertClass'] = 'danger';
  93.                 $json['alertMessage'] = $this->get('translator')->trans('Error ! Invalid label id.');
  94.             }
  95.         }
  96.         return new Response(json_encode($json), 200, ['Content-Type' => 'application/json']);
  97.     }
  98.     public function updateTicketDetails(Request $request)
  99.     {
  100.         $ticketId $request->attributes->get('ticketId');
  101.         $entityManager $this->getDoctrine()->getManager();
  102.         $ticket $entityManager->getRepository('UVDeskCoreFrameworkBundle:Ticket')->find($ticketId);
  103.         if (!$ticket)
  104.             $this->noResultFound();
  105.         $error false;
  106.         $message '';
  107.         if ($request->request->get('subject') == '') {
  108.             $error true;
  109.             $message $this->get('translator')->trans('Error! Subject field is mandatory');
  110.         } elseif ($request->request->get('reply') == '') {
  111.             $error true;
  112.             $message $this->get('translator')->trans('Error! Reply field is mandatory');
  113.         }
  114.         if (!$error) {
  115.             $ticket->setSubject($request->request->get('subject'));
  116.             $createThread $this->get('ticket.service')->getCreateReply($ticket->getId(), false);
  117.             $createThread $entityManager->getRepository('UVDeskCoreFrameworkBundle:Thread')->find($createThread['id']);
  118.             $createThread->setMessage($request->request->get('reply'));
  119.             $entityManager->persist($createThread);
  120.             $entityManager->persist($ticket);
  121.             $entityManager->flush();
  122.             $this->addFlash('success'$this->get('translator')->trans('Success ! Ticket has been updated successfully.'));
  123.         } else {
  124.             $this->addFlash('warning'$message);
  125.         }
  126.         return $this->redirect($this->generateUrl('helpdesk_member_ticket', ['ticketId'=> $ticketId] ));
  127.     }
  128.     public function updateTicketAttributes($ticketId)
  129.     {
  130.         // @TODO: Ticket Voter
  131.         // $this->denyAccessUnlessGranted('VIEW', $ticket);
  132.         $entityManager $this->getDoctrine()->getManager();
  133.         $request $this->container->get('request_stack')->getCurrentRequest();
  134.         $requestContent $request->request->all() ?: json_decode($request->getContent(), true);
  135.         $ticketId =  $ticketId != $ticketId $requestContent['ticketId'];
  136.         $ticket $entityManager->getRepository('UVDeskCoreFrameworkBundle:Ticket')->findOneById($ticketId);
  137.         // Validate request integrity
  138.         if (empty($ticket)) {
  139.             $responseContent = [
  140.                 'alertClass' => 'danger',
  141.                 'alertMessage' => $this->get('translator')->trans('Unable to retrieve details for ticket #%ticketId%.', [
  142.                     '%ticketId%' => $ticketId,
  143.                 ]),
  144.             ];
  145.             return new Response(json_encode($responseContent), 200, ['Content-Type' => 'application/json']);
  146.         } else if (!isset($requestContent['attribute'])) {
  147.             $responseContent = [
  148.                 'alertClass' => 'danger',
  149.                 'alertMessage' => $this->get('translator')->trans('Insufficient details provided.'),
  150.             ];
  151.             return new Response(json_encode($responseContent), 400, ['Content-Type' => 'application/json']);
  152.         }
  153.         // Update attribute
  154.         switch ($requestContent['attribute']) {
  155.             case 'agent':
  156.         if($requestContent['value'] == '0'){
  157.                     $ticket->setAgent(null);
  158.                     $entityManager->persist($ticket);
  159.                     $entityManager->flush();
  160.                     // Trigger Agent Assign event
  161.                     $event = new GenericEvent(CoreWorkflowEvents\Ticket\Agent::getId(), [
  162.                         'entity' => $ticket,
  163.                     ]);
  164.                     $this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute'$event);
  165.                     return new Response(json_encode([
  166.                         'alertClass' => 'success',
  167.                         'alertMessage' => $this->get('translator')->trans('Ticket successfully unassigned', [
  168.                             '%agent%' => $agentDetails['name'],
  169.                         ]),
  170.                     ]), 200, ['Content-Type' => 'application/json']);
  171.                 }
  172.                 $agent $entityManager->getRepository('UVDeskCoreFrameworkBundle:User')->findOneById($requestContent['value']);
  173.                 if (empty($agent)) {
  174.                     // User does not exist
  175.                     return new Response(json_encode([
  176.                         'alertClass' => 'danger',
  177.                         'alertMessage' => $this->get('translator')->trans('Unable to retrieve agent details'),
  178.                     ]), 404, ['Content-Type' => 'application/json']);
  179.                 } else {
  180.                     // Check if an agent instance exists for the user
  181.                     $agentInstance $agent->getAgentInstance();
  182.                     if (empty($agentInstance)) {
  183.                         // Agent does not exist
  184.                         return new Response(json_encode([
  185.                             'alertClass' => 'danger',
  186.                             'alertMessage' => $this->get('translator')->trans('Unable to retrieve agent details'),
  187.                         ]), 404, ['Content-Type' => 'application/json']);
  188.                     }
  189.                 }
  190.                 $agentDetails $agentInstance->getPartialDetails();
  191.                 // Check if ticket is already assigned to the agent
  192.                 if ($ticket->getAgent() && $agent->getId() === $ticket->getAgent()->getId()) {
  193.                     return new Response(json_encode([
  194.                         'alertClass' => 'success',
  195.                         'alertMessage' => $this->get('translator')->trans('Ticket already assigned to %agent%', [
  196.                             '%agent%' => $agentDetails['name'],
  197.                         ]),
  198.                     ]), 200, ['Content-Type' => 'application/json']);
  199.                 } else {
  200.                     $ticket->setAgent($agent);
  201.                     $entityManager->persist($ticket);
  202.                     $entityManager->flush();
  203.                     // Trigger Agent Assign event
  204.                     $event = new GenericEvent(CoreWorkflowEvents\Ticket\Agent::getId(), [
  205.                         'entity' => $ticket,
  206.                     ]);
  207.                     $this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute'$event);
  208.                     return new Response(json_encode([
  209.                         'alertClass' => 'success',
  210.                         'alertMessage' => $this->get('translator')->trans('Ticket successfully assigned to %agent%', [
  211.                             '%agent%' => $agentDetails['name'],
  212.                         ]),
  213.                     ]), 200, ['Content-Type' => 'application/json']);
  214.                 }
  215.                 break;
  216.             case 'status':
  217.                 $ticketStatus $entityManager->getRepository('UVDeskCoreFrameworkBundle:TicketStatus')->findOneById((int) $requestContent['value']);
  218.                 if (empty($ticketStatus)) {
  219.                     // Selected ticket status does not exist
  220.                     return new Response(json_encode([
  221.                         'alertClass' => 'danger',
  222.                         'alertMessage' => $this->get('translator')->trans('Unable to retrieve status details'),
  223.                     ]), 404, ['Content-Type' => 'application/json']);
  224.                 }
  225.                 if ($ticketStatus->getId() === $ticket->getStatus()->getId()) {
  226.                     return new Response(json_encode([
  227.                         'alertClass' => 'success',
  228.                         'alertMessage' => $this->get('translator')->trans('Ticket status already set to %status%', [
  229.                             '%status%' => $ticketStatus->getDescription()
  230.                         ]),
  231.                     ]), 200, ['Content-Type' => 'application/json']);
  232.                 } else {
  233.                     $ticket->setStatus($ticketStatus);
  234.                     $entityManager->persist($ticket);
  235.                     $entityManager->flush();
  236.                     // Trigger ticket status event
  237.                     $event = new GenericEvent(CoreWorkflowEvents\Ticket\Status::getId(), [
  238.                         'entity' => $ticket,
  239.                     ]);
  240.                     $this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute'$event);
  241.                     
  242.                     if($ticketStatus->getDescription() == 'Closed' || $ticketStatus->getDescription() == 'Closed by autoclose'){
  243.                             $datosEncuesta file_get_contents($_ENV['EXTERNAL_PATH']."UserController.php?case=5&nroTicket=".$ticket->getId()."&idUsuario=".$ticket->getCustomer()->getId());
  244.                             $stringUrl base64_encode(rand(1000,10000)."#0#".$datosEncuesta);
  245.                             $stringUrl urldecode(str_replace("="""$stringUrl));    //elimino los "=" porque el base64 no los necesita
  246.                             $idioma explode('#'$datosEncuesta)[3];
  247.                             $link_encuesta "https://www.softguard.com/encuesta-soporte24/".$idioma."/?p=".$stringUrl;
  248.                             $mensaje 'Hemos finalizado la asistencia solicitada recientemente.
  249.                             Su opinión y comentario nos ayudará a mejorar nuestro servicio de soporte. Por favor, responda este breve cuestionario para que continuemos brindando la mejor atención que usted se merece, no tomará más de 3 minutos.
  250.                             Califique nuestro Centro de Soporte';
  251.                             //$apiKey = "AIzaSyBzVmrEVgd5Bb0r8bbgl3ZznsNuRGnO3RM"; // prod
  252.                             $apiKey "AIzaSyAenP2qCf3xrpbuLvzqzmsne0lJDewwP0w"// test
  253.                             $url 'https://www.googleapis.com/language/translate/v2?key=' $apiKey '&q=' rawurlencode($mensaje) . '&source=es&target='.$idioma;
  254.              
  255.                             $handle curl_init($url);
  256.                             curl_setopt($handleCURLOPT_RETURNTRANSFERtrue);     //We want the result to be saved into variable, not printed out
  257.                             $response curl_exec($handle); 
  258.                             $responseDecoded json_decode($responsetrue);                  
  259.                            
  260.                             if($idioma == 'es'){
  261.                     
  262.                                 $mensajeFinal $mensaje;
  263.                             }else{
  264.                     
  265.                                 $mensajeFinal =  $responseDecoded['data']['translations'][0]['translatedText'];
  266.                     
  267.                             }
  268.                         
  269.                             $invitacion_encuesta 'Hola '.$ticket->getCustomer()->getFirstName().'<br></br><br></br>'.$mensajeFinal;
  270.                             $message_id $this->get('email.service')->sendMail('Encuesta Softguard'$invitacion_encuesta' : <a href="' .$link_encuesta'">CALIFICAR LA ASISTENCIA</a> <br></br><br></br> SG Support Center '$ticket->getCustomer()->getEmail(), []);
  271.                             curl_close($handle);            
  272.                     }
  273.                     return new Response(json_encode([
  274.                         'alertClass' => 'success',
  275.                         'alertMessage' => $this->get('translator')->trans('Ticket status update to %status%', [
  276.                             '%status%' => $ticketStatus->getDescription()
  277.                         ]),
  278.                     ]), 200, ['Content-Type' => 'application/json']);
  279.                 }
  280.                 break;
  281.             case 'priority':
  282.                 // $this->isAuthorized('ROLE_AGENT_UPDATE_TICKET_PRIORITY');
  283.                 $ticketPriority $entityManager->getRepository('UVDeskCoreFrameworkBundle:TicketPriority')->findOneById($requestContent['value']);
  284.                 if (empty($ticketPriority)) {
  285.                     // Selected ticket priority does not exist
  286.                     return new Response(json_encode([
  287.                         'alertClass' => 'danger',
  288.                         'alertMessage' => $this->get('translator')->trans('Unable to retrieve priority details'),
  289.                     ]), 404, ['Content-Type' => 'application/json']);
  290.                 }
  291.                 if ($ticketPriority->getId() === $ticket->getPriority()->getId()) {
  292.                     return new Response(json_encode([
  293.                         'alertClass' => 'success',
  294.                         'alertMessage' => $this->get('translator')->trans('Ticket priority already set to %priority%', [
  295.                             '%priority%' => $ticketPriority->getDescription()
  296.                         ]),
  297.                     ]), 200, ['Content-Type' => 'application/json']);
  298.                 } else {
  299.                     $ticket->setPriority($ticketPriority);
  300.                     $entityManager->persist($ticket);
  301.                     $entityManager->flush();
  302.                     // Trigger ticket Priority event
  303.                     $event = new GenericEvent(CoreWorkflowEvents\Ticket\Priority::getId(), [
  304.                         'entity' => $ticket,
  305.                     ]);
  306.                     $this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute'$event);
  307.                     return new Response(json_encode([
  308.                         'alertClass' => 'success',
  309.                         'alertMessage' => $this->get('translator')->trans('Ticket priority updated to %priority%', [
  310.                             '%priority%' => $ticketPriority->getDescription()
  311.                         ]),
  312.                     ]), 200, ['Content-Type' => 'application/json']);
  313.                 }
  314.                 break;
  315.             case 'group':
  316.                 $supportGroup $entityManager->getRepository('UVDeskCoreFrameworkBundle:SupportGroup')->findOneById($requestContent['value']);
  317.                 if (empty($supportGroup)) {
  318.                     if ($requestContent['value'] == "") {
  319.                         if ($ticket->getSupportGroup() != null) {
  320.                             $ticket->setSupportGroup(null);
  321.                             $entityManager->persist($ticket);
  322.                             $entityManager->flush();
  323.                         }
  324.                         $responseCode 200;
  325.                         $response = [
  326.                             'alertClass' => 'success',
  327.                             'alertMessage' => $this->get('translator')->trans('Ticket support group updated successfully'),
  328.                         ];
  329.                     } else {
  330.                         $responseCode 404;
  331.                         $response = [
  332.                             'alertClass' => 'danger',
  333.                             'alertMessage' => $this->get('translator')->trans('Unable to retrieve support group details'),
  334.                         ];
  335.                     }
  336.                     return new Response(json_encode($response), $responseCode, ['Content-Type' => 'application/json']);;
  337.                 }
  338.                 if ($ticket->getSupportGroup() != null && $supportGroup->getId() === $ticket->getSupportGroup()->getId()) {
  339.                     return new Response(json_encode([
  340.                         'alertClass' => 'success',
  341.                         'alertMessage' => 'Ticket already assigned to support group ' $supportGroup->getName(),
  342.                     ]), 200, ['Content-Type' => 'application/json']);
  343.                 } else {
  344.                     $ticket->setSupportGroup($supportGroup);
  345.                     $entityManager->persist($ticket);
  346.                     $entityManager->flush();
  347.                     // Trigger Support group event
  348.                     $event = new GenericEvent(CoreWorkflowEvents\Ticket\Group::getId(), [
  349.                         'entity' => $ticket,
  350.                     ]);
  351.                     $this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute'$event);
  352.                     return new Response(json_encode([
  353.                         'alertClass' => 'success',
  354.                         'alertMessage' => 'Ticket assigned to support group ' $supportGroup->getName(),
  355.                     ]), 200, ['Content-Type' => 'application/json']);
  356.                 }
  357.                 break;
  358.             case 'team':
  359.                 $supportTeam $entityManager->getRepository('UVDeskCoreFrameworkBundle:SupportTeam')->findOneById($requestContent['value']);
  360.                 if (empty($supportTeam)) {
  361.                     if ($requestContent['value'] == "") {
  362.                         if ($ticket->getSupportTeam() != null) {
  363.                             $ticket->setSupportTeam(null);
  364.                             $entityManager->persist($ticket);
  365.                             $entityManager->flush();
  366.                         }
  367.                         $responseCode 200;
  368.                         $response = [
  369.                             'alertClass' => 'success',
  370.                             'alertMessage' => $this->get('translator')->trans('Ticket support team updated successfully'),
  371.                         ];
  372.                     } else {
  373.                         $responseCode 404;
  374.                         $response = [
  375.                             'alertClass' => 'danger',
  376.                             'alertMessage' => $this->get('translator')->trans('Unable to retrieve support team details'),
  377.                         ];
  378.                     }
  379.                     return new Response(json_encode($response), $responseCode, ['Content-Type' => 'application/json']);;
  380.                 }
  381.                 if ($ticket->getSupportTeam() != null && $supportTeam->getId() === $ticket->getSupportTeam()->getId()) {
  382.                     return new Response(json_encode([
  383.                         'alertClass' => 'success',
  384.                         'alertMessage' => 'Ticket already assigned to support team ' $supportTeam->getName(),
  385.                     ]), 200, ['Content-Type' => 'application/json']);
  386.                 } else {
  387.                     $ticket->setSupportTeam($supportTeam);
  388.                     $entityManager->persist($ticket);
  389.                     $entityManager->flush();
  390.                     // Trigger ticket delete event
  391.                     $event = new GenericEvent(CoreWorkflowEvents\Ticket\Team::getId(), [
  392.                         'entity' => $ticket,
  393.                     ]);
  394.                     $this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute'$event);
  395.                     return new Response(json_encode([
  396.                         'alertClass' => 'success',
  397.                         'alertMessage' => 'Ticket assigned to support team ' $supportTeam->getName(),
  398.                     ]), 200, ['Content-Type' => 'application/json']);
  399.                 }
  400.                 break;
  401.             case 'type':
  402.                 // $this->isAuthorized('ROLE_AGENT_UPDATE_TICKET_TYPE');
  403.                 $ticketType $entityManager->getRepository('UVDeskCoreFrameworkBundle:TicketType')->findOneById($requestContent['value']);
  404.                 if (empty($ticketType)) {
  405.                     // Selected ticket priority does not exist
  406.                     return new Response(json_encode([
  407.                         'alertClass' => 'danger',
  408.                         'alertMessage' => 'Unable to retrieve ticket type details',
  409.                     ]), 404, ['Content-Type' => 'application/json']);
  410.                 }
  411.                 if (null != $ticket->getType() && $ticketType->getId() === $ticket->getType()->getId()) {
  412.                     return new Response(json_encode([
  413.                         'alertClass' => 'success',
  414.                         'alertMessage' => 'Ticket type already set to ' $ticketType->getDescription(),
  415.                     ]), 200, ['Content-Type' => 'application/json']);
  416.                 } else {
  417.                     $ticket->setType($ticketType);
  418.                     $entityManager->persist($ticket);
  419.                     $entityManager->flush();
  420.                     // Trigger ticket delete event
  421.                     $event = new GenericEvent(CoreWorkflowEvents\Ticket\Type::getId(), [
  422.                         'entity' => $ticket,
  423.                     ]);
  424.                     $this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute'$event);
  425.                     return new Response(json_encode([
  426.                         'alertClass' => 'success',
  427.                         'alertMessage' => 'Ticket type updated to ' $ticketType->getDescription(),
  428.                     ]), 200, ['Content-Type' => 'application/json']);
  429.                 }
  430.                 break;
  431.             case 'label':
  432.                 $label $entityManager->getRepository('UVDeskCoreFrameworkBundle:SupportLabel')->find($requestContent['labelId']);
  433.                 if($label) {
  434.                     $ticket->removeSupportLabel($label);
  435.                     $entityManager->persist($ticket);
  436.                     $entityManager->flush();
  437.                     return new Response(json_encode([
  438.                         'alertClass' => 'success',
  439.                         'alertMessage' => $this->get('translator')->trans('Success ! Ticket to label removed successfully.'),
  440.                     ]), 200, ['Content-Type' => 'application/json']);
  441.                 }
  442.                 break;
  443.             default:
  444.                 break;
  445.         }
  446.         return new Response(json_encode([]), 400, ['Content-Type' => 'application/json']);
  447.     }
  448.     public function listTicketCollectionXHR(Request $request)
  449.     {
  450.         if ($request->isXmlHttpRequest()) {
  451.             $paginationResponse $this->get('ticket.service')->paginateMembersTicketCollection($request);
  452.             return new Response(json_encode($paginationResponse), 200, ['Content-Type' => 'application/json']);
  453.         }
  454.         return new Response(json_encode([]), 404, ['Content-Type' => 'application/json']);
  455.     }
  456.     public function updateTicketCollectionXHR(Request $request)
  457.     {
  458.         if ($request->isXmlHttpRequest()) {
  459.             $massResponse $this->get('ticket.service')->massXhrUpdate($request);
  460.             return new Response(json_encode($massResponse), 200, ['Content-Type' => 'application/json']);
  461.         }
  462.         return new Response(json_encode([]), 404);
  463.     }
  464.     public function loadTicketFilterOptionsXHR(Request $request)
  465.     {
  466.         return new Response(json_encode([]), 404);
  467.     }
  468.     public function saveTicketLabel(Request $request)
  469.     {
  470.         $entityManager $this->getDoctrine()->getManager();
  471.         $request $this->container->get('request_stack')->getCurrentRequest();
  472.         $requestContent json_decode($request->getContent(), true);
  473.         $ticket $entityManager->getRepository('UVDeskCoreFrameworkBundle:Ticket')->findOneById($requestContent['ticketId']);
  474.         if ('POST' == $request->getMethod()) {
  475.             $responseContent = [];
  476.             $user $this->get('user.service')->getSessionUser();
  477.             $supportLabel $entityManager->getRepository('UVDeskCoreFrameworkBundle:SupportLabel')->findOneBy([
  478.                 'user' => $user->getId(),
  479.                 'name' => $requestContent['name'],
  480.             ]);
  481.             if (empty($supportLabel)) {
  482.                 $supportLabel = new SupportLabel();
  483.                 $supportLabel->setName($requestContent['name']);
  484.                 $supportLabel->setUser($user);
  485.                 $entityManager->persist($supportLabel);
  486.                 $entityManager->flush();
  487.             }
  488.             $ticketLabelCollection $ticket->getSupportLabels()->toArray();
  489.             if (empty($ticketLabelCollection)) {
  490.                 $ticket->addSupportLabel($supportLabel);
  491.                 $entityManager->persist($ticket);
  492.                 $entityManager->flush();
  493.                 $responseContent['alertClass'] = 'success';
  494.                 $responseContent['alertMessage'] = $this->get('translator')->trans(
  495.                     'Label %label% added to ticket successfully', [
  496.                     '%label%' => $supportLabel->getName(),
  497.                 ]);
  498.             } else {
  499.                 $isLabelAlreadyAdded false;
  500.                 foreach ($ticketLabelCollection as $ticketLabel) {
  501.                     if ($supportLabel->getId() == $ticketLabel->getId()) {
  502.                         $isLabelAlreadyAdded true;
  503.                         break;
  504.                     }
  505.                 }
  506.                 if (false == $isLabelAlreadyAdded) {
  507.                     $ticket->addSupportLabel($supportLabel);
  508.                     $entityManager->persist($ticket);
  509.                     $entityManager->flush();
  510.                     $responseContent['alertClass'] = 'success';
  511.                     $responseContent['alertMessage'] = $this->get('translator')->trans(
  512.                         'Label %label% added to ticket successfully', [
  513.                         '%label%' => $supportLabel->getName(),
  514.                     ]);
  515.                 } else {
  516.                     $responseContent['alertClass'] = 'warning';
  517.                     $responseContent['alertMessage'] = $this->get('translator')->trans(
  518.                         'Label %label% already added to ticket', [
  519.                         '%label%' => $supportLabel->getName(),
  520.                     ]);
  521.                 }
  522.             }
  523.             $responseContent['label'] = [
  524.                 'id' => $supportLabel->getId(),
  525.                 'name' => $supportLabel->getName(),
  526.                 'color' => $supportLabel->getColorCode(),
  527.             ];
  528.             return new Response(json_encode($responseContent), 200, ['Content-Type' => 'application/json']);
  529.         }
  530.         return new Response(json_encode([]), 404, ['Content-Type' => 'application/json']);
  531.     }
  532.     public function getLabels($request null)
  533.     {
  534.         static $labels;
  535.         if (null !== $labels)
  536.             return $labels;
  537.         $qb $this->em->createQueryBuilder();
  538.         $qb->select('tl')->from('UVDeskCoreFrameworkBundle:TicketLabel''tl')
  539.             ->andwhere('tl.labelUser = :labelUserId')
  540.             ->andwhere('tl.company = :companyId')
  541.             ->setParameter('labelUserId'$this->getUser()->getId())
  542.             ->setParameter('companyId'$this->getCompany()->getId());
  543.         if($request) {
  544.             $qb->andwhere("tl.name LIKE :labelName");
  545.             $qb->setParameter('labelName''%'.urldecode($request->query->get('query')).'%');
  546.         }
  547.         return $labels $qb->getQuery()->getArrayResult();
  548.     }
  549.     public function loadTicketSearchFilterOptions(Request $request)
  550.     {
  551.         if (true === $request->isXmlHttpRequest()) {
  552.             switch ($request->query->get('type')) {
  553.                 case 'agent':
  554.                     $filtersResponse $this->get('user.service')->getAgentPartialDataCollection($request);
  555.                     break;
  556.                 case 'customer':
  557.                     $filtersResponse $this->get('user.service')->getCustomersPartial($request);
  558.                     break;
  559.                 case 'group':
  560.                     $filtersResponse $this->get('user.service')->getGroups($request);
  561.                     break;
  562.                 case 'team':
  563.                     $filtersResponse $this->get('user.service')->getSubGroups($request);
  564.                     break;
  565.                 case 'tag':
  566.                     $filtersResponse $this->get('ticket.service')->getTicketTags($request);
  567.                     break;
  568.                 case 'label':
  569.                     $searchTerm $request->query->get('query');
  570.                     $entityManager $this->getDoctrine()->getManager();
  571.                     $supportLabelQuery $entityManager->createQueryBuilder()->select('supportLabel')
  572.                         ->from('UVDeskCoreFrameworkBundle:SupportLabel''supportLabel')
  573.                         ->where('supportLabel.user = :user')->setParameter('user'$this->get('user.service')->getSessionUser());
  574.                     if (!empty($searchTerm)) {
  575.                         $supportLabelQuery->andWhere('supportLabel.name LIKE :labelName')->setParameter('labelName''%' urldecode($searchTerm) . '%');
  576.                     }
  577.                     $supportLabelCollection $supportLabelQuery->getQuery()->getArrayResult();
  578.                     return new Response(json_encode($supportLabelCollection), 200, ['Content-Type' => 'application/json']);
  579.                     break;
  580.                 default:
  581.                     break;
  582.             }
  583.         }
  584.         return new Response(json_encode([]), 404, ['Content-Type' => 'application/json']);
  585.     }
  586.     public function loadTicketCollectionSearchFilterOptionsXHR(Request $request)
  587.     {
  588.         $json = [];
  589.         if ($request->isXmlHttpRequest()) {
  590.             if ($request->query->get('type') == 'agent') {
  591.                 $json $this->get('user.service')->getAgentsPartialDetails($request);
  592.             } elseif ($request->query->get('type') == 'customer') {
  593.                 $json $this->get('user.service')->getCustomersPartial($request);
  594.             } elseif ($request->query->get('type') == 'group') {
  595.                 $json $this->get('user.service')->getGroups($request);
  596.             } elseif ($request->query->get('type') == 'team') {
  597.                 $json $this->get('user.service')->getSubGroups($request);
  598.             } elseif ($request->query->get('type') == 'tag') {
  599.                 $json $this->get('ticket.service')->getTicketTags($request);
  600.             } elseif ($request->query->get('type') == 'label') {
  601.                 $json $this->get('ticket.service')->getLabels($request);
  602.             }
  603.         }
  604.         return new Response(json_encode($json), 200, ['Content-Type' => 'application/json']);
  605.     }
  606.     public function listTicketTypeCollectionXHR(Request $request)
  607.     {
  608.         if (!$this->get('user.service')->isAccessAuthorized('ROLE_AGENT_MANAGE_TICKET_TYPE')) {
  609.             return $this->redirect($this->generateUrl('helpdesk_member_dashboard'));
  610.         }
  611.         if (true === $request->isXmlHttpRequest()) {
  612.             $paginationResponse $this->get('ticket.service')->paginateMembersTicketTypeCollection($request);
  613.             return new Response(json_encode($paginationResponse), 200, ['Content-Type' => 'application/json']);
  614.         }
  615.         return new Response(json_encode([]), 404, ['Content-Type' => 'application/json']);
  616.     }
  617.     public function removeTicketTypeXHR($typeIdRequest $request)
  618.     {
  619.         if (!$this->get('user.service')->isAccessAuthorized('ROLE_AGENT_MANAGE_TICKET_TYPE')) {
  620.             return $this->redirect($this->generateUrl('helpdesk_member_dashboard'));
  621.         }
  622.         $json = [];
  623.         if($request->getMethod() == "DELETE") {
  624.             $em $this->getDoctrine()->getManager();
  625.             $id $request->attributes->get('typeId');
  626.             $type $em->getRepository('UVDeskCoreFrameworkBundle:TicketType')->find($id);
  627.             // $this->get('event.manager')->trigger([
  628.             //             'event' => 'type.deleted',
  629.             //             'entity' => $type
  630.             //         ]);
  631.             $em->remove($type);
  632.             $em->flush();
  633.             $json['alertClass'] = 'success';
  634.             $json['alertMessage'] = $this->get('translator')->trans('Success ! Type removed successfully.');
  635.         }
  636.         $response = new Response(json_encode($json));
  637.         $response->headers->set('Content-Type''application/json');
  638.         return $response;
  639.     }
  640.     public function listTagCollectionXHR(Request $request)
  641.     {
  642.         if (!$this->get('user.service')->isAccessAuthorized('ROLE_AGENT_MANAGE_TAG')) {
  643.             return $this->redirect($this->generateUrl('helpdesk_member_dashboard'));
  644.         }
  645.         if (true === $request->isXmlHttpRequest()) {
  646.             $paginationResponse $this->get('ticket.service')->paginateMembersTagCollection($request);
  647.             return new Response(json_encode($paginationResponse), 200, ['Content-Type' => 'application/json']);
  648.         }
  649.         return new Response(json_encode([]), 404, ['Content-Type' => 'application/json']);
  650.     }
  651.     public function applyTicketPreparedResponseXHR(Request $request)
  652.     {
  653.         $id $request->attributes->get('id');
  654.         $ticketId $request->attributes->get('ticketId');
  655.         $ticket $this->getDoctrine()->getManager()->getRepository('UVDeskCoreFrameworkBundle:Ticket')->findOneById($ticketId);
  656.         $event = new GenericEvent($id, [
  657.             'entity' =>  $ticket
  658.         ]);
  659.         $this->get('event_dispatcher')->dispatch('uvdesk.automation.prepared_response.execute'$event);
  660.         $this->addFlash('success'$this->get('translator')->trans('Success ! Prepared Response applied successfully.'));
  661.         return $this->redirect($this->generateUrl('helpdesk_member_ticket',['ticketId' => $ticketId]));
  662.     }
  663.     public function loadTicketSavedReplies(Request $request)
  664.     {
  665.         $json = array();
  666.         $data $request->query->all();
  667.         if ($request->isXmlHttpRequest()) {
  668.             $json['message'] = $this->get('ticket.service')->getSavedReplyContent($data['id'],$data['ticketId']);
  669.         }
  670.         $response = new Response(json_encode($json));
  671.         return $response;
  672.     }
  673.     public function createTicketTagXHR(Request $request)
  674.     {
  675.         $json = [];
  676.         $content json_decode($request->getContent(), true);
  677.         $em $this->getDoctrine()->getManager();
  678.         $ticket $em->getRepository('UVDeskCoreFrameworkBundle:Ticket')->find($content['ticketId']);
  679.         if($request->getMethod() == "POST") {
  680.             $tag = new CoreFrameworkBundleEntities\Tag();
  681.             if ($content['name'] != "") {
  682.                 $checkTag $em->getRepository('UVDeskCoreFrameworkBundle:Tag')->findOneBy(array('name' => $content['name']));
  683.                 if(!$checkTag) {
  684.                     $tag->setName($content['name']);
  685.                     $em->persist($tag);
  686.                     $em->flush();
  687.                     //$json['tag'] = json_decode($this->objectSerializer($tag));
  688.                     $ticket->addSupportTag($tag);
  689.                 } else {
  690.                     //$json['tag'] = json_decode($this->objectSerializer($checkTag));
  691.                     $ticket->addSupportTag($checkTag);
  692.                 }
  693.                 $em->persist($ticket);
  694.                 $em->flush();
  695.                 $json['alertClass'] = 'success';
  696.                 $json['alertMessage'] = $this->get('translator')->trans('Success ! Tag added successfully.');
  697.             } else {
  698.                 $json['alertClass'] = 'danger';
  699.                 $json['alertMessage'] = $this->get('translator')->trans('Please enter tag name.');
  700.             }
  701.         } elseif($request->getMethod() == "DELETE") {
  702.             $tag $em->getRepository('UVDeskCoreFrameworkBundle:Tag')->findOneBy(array('id' => $request->attributes->get('id')));
  703.             if($tag) {
  704.                 $articles $em->getRepository('UVDeskSupportCenterBundle:ArticleTags')->findOneBy(array('tagId' => $tag->getId()));
  705.                 if($articles)
  706.                     foreach ($articles as $entry) {
  707.                         $em->remove($entry);
  708.                     }
  709.                 $ticket->removeSupportTag($tag);
  710.                 $em->persist($ticket);
  711.                 $em->flush();
  712.                 $json['alertClass'] = 'success';
  713.                 $json['alertMessage'] = $this->get('translator')->trans('Success ! Tag unassigned successfully.');
  714.             } else {
  715.                 $json['alertClass'] = 'danger';
  716.                 $json['alertMessage'] = $this->get('translator')->trans('Error ! Invalid tag.');
  717.             }
  718.         }
  719.         $response = new Response(json_encode($json));
  720.         $response->headers->set('Content-Type''application/json');
  721.         return $response;
  722.     }
  723.     public function getSearchFilterOptionsXhr(Request $request)
  724.     {
  725.         $json = [];
  726.         if ($request->isXmlHttpRequest()) {
  727.             if($request->query->get('type') == 'agent') {
  728.                 $json $this->get('user.service')->getAgentsPartialDetails($request);
  729.             } elseif($request->query->get('type') == 'customer') {
  730.                 $json $this->get('user.service')->getCustomersPartial($request);
  731.             } elseif($request->query->get('type') == 'group') {
  732.                 $json $this->get('user.service')->getSupportGroups($request);
  733.             } elseif($request->query->get('type') == 'team') {
  734.                 $json $this->get('user.service')->getSupportTeams($request);
  735.             } elseif($request->query->get('type') == 'tag') {
  736.                 $json $this->get('ticket.service')->getTicketTags($request);
  737.             } elseif($request->query->get('type') == 'label') {
  738.                 $json $this->get('ticket.service')->getLabels($request);
  739.             }
  740.         }
  741.         $response = new Response(json_encode($json));
  742.         $response->headers->set('Content-Type''application/json');
  743.         return $response;
  744.     }
  745.     public function updateCollaboratorXHR(Request $request)
  746.     {
  747.         $json = [];
  748.         $content json_decode($request->getContent(), true);
  749.         $em $this->getDoctrine()->getManager();
  750.         $ticket $em->getRepository('UVDeskCoreFrameworkBundle:Ticket')->find($content['ticketId']);
  751.         if($request->getMethod() == "POST") {
  752.             if($content['email'] == $ticket->getCustomer()->getEmail()) {
  753.                 $json['alertClass'] = 'danger';
  754.                 $json['alertMessage'] = $this->get('translator')->trans('Error ! Customer can not be added as collaborator.');
  755.             } else {
  756.                 $data = array(
  757.                     'from' => $content['email'],
  758.                     'firstName' => ($firstName ucfirst(current(explode('@'$content['email'])))),
  759.                     'lastName' => ' ',
  760.                     'role' => 4,
  761.                 );
  762.                 $supportRole $em->getRepository('UVDeskCoreFrameworkBundle:SupportRole')->findOneByCode('ROLE_CUSTOMER');
  763.                 $collaborator $this->get('user.service')->createUserInstance($data['from'], $data['firstName'], $supportRole$extras = ["active" => true]);
  764.                 $checkTicket $em->getRepository('UVDeskCoreFrameworkBundle:Ticket')->isTicketCollaborator($ticket$content['email']);
  765.                 if (!$checkTicket) {
  766.                     $ticket->addCollaborator($collaborator);
  767.                     $em->persist($ticket);
  768.                     $em->flush();
  769.                     $ticket->lastCollaborator $collaborator;
  770.                     if ($collaborator->getCustomerInstance())
  771.                         $json['collaborator'] = $collaborator->getCustomerInstance()->getPartialDetails();
  772.                     else
  773.                         $json['collaborator'] = $collaborator->getAgentInstance()->getPartialDetails();
  774.                     $event = new GenericEvent(CoreWorkflowEvents\Ticket\Collaborator::getId(), [
  775.                         'entity' => $ticket,
  776.                     ]);
  777.                     $this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute'$event);
  778.                     $json['alertClass'] = 'success';
  779.                     $json['alertMessage'] = $this->get('translator')->trans('Success ! Collaborator added successfully.');
  780.                 } else {
  781.                     $json['alertClass'] = 'danger';
  782.                     $message "Collaborator is already added.";
  783.                     $json['alertMessage'] = $this->get('translator')->trans('Error ! ' $message);
  784.                 }
  785.             }
  786.         } elseif($request->getMethod() == "DELETE") {
  787.             $collaborator $em->getRepository('UVDeskCoreFrameworkBundle:User')->findOneBy(array('id' => $request->attributes->get('id')));
  788.             if($collaborator) {
  789.                 $ticket->removeCollaborator($collaborator);
  790.                 $em->persist($ticket);
  791.                 $em->flush();
  792.                 $json['alertClass'] = 'success';
  793.                 $json['alertMessage'] = $this->get('translator')->trans('Success ! Collaborator removed successfully.');
  794.             } else {
  795.                 $json['alertClass'] = 'danger';
  796.                 $json['alertMessage'] = $this->get('translator')->trans('Error ! Invalid Collaborator.');
  797.             }
  798.         }
  799.         $response = new Response(json_encode($json));
  800.         $response->headers->set('Content-Type''application/json');
  801.         return $response;
  802.     }
  803.     // Apply quick Response action
  804.     public function getTicketQuickViewDetailsXhr(Request $request)
  805.     {
  806.         $json = [];
  807.         if ($request->isXmlHttpRequest()) {
  808.             $ticketId $request->query->get('ticketId');
  809.             $json $this->getDoctrine()->getRepository('UVDeskCoreFrameworkBundle:Ticket')->getTicketDetails($request->query,$this->container);
  810.         }
  811.         $response = new Response(json_encode($json));
  812.         $response->headers->set('Content-Type''application/json');
  813.         return $response;
  814.     }
  815.     public function updateTicketTagXHR(Request $request$tagId)
  816.     {
  817.         $content json_decode($request->getContent(), true);
  818.         $entityManager $this->getDoctrine()->getManager();
  819.         if (isset($content['name']) && $content['name'] != "") {
  820.             $checkTag $entityManager->getRepository('UVDeskCoreFrameworkBundle:Tag')->findOneBy(array('id' => $tagId));
  821.             if($checkTag) {
  822.                 $checkTag->setName($content['name']);
  823.                 $entityManager->persist($checkTag);
  824.                 $entityManager->flush();
  825.             }
  826.             $json['alertClass'] = 'success';
  827.             $json['alertMessage'] = $this->get('translator')->trans('Success ! Tag updated successfully.');
  828.         }
  829.         $response = new Response(json_encode($json));
  830.         $response->headers->set('Content-Type''application/json');
  831.         return $response;
  832.     }
  833.     public function removeTicketTagXHR($tagId)
  834.     {
  835.         $entityManager $this->getDoctrine()->getManager();
  836.         $checkTag $entityManager->getRepository('UVDeskCoreFrameworkBundle:Tag')->findOneBy(array('id' => $tagId));
  837.         if($checkTag) {
  838.             $entityManager->remove($checkTag);
  839.             $entityManager->flush();
  840.             $json['alertClass'] = 'success';
  841.             $json['alertMessage'] = $this->get('translator')->trans('Success ! Tag removed successfully.');
  842.         }
  843.         $response = new Response(json_encode($json));
  844.         $response->headers->set('Content-Type''application/json');
  845.         return $response;
  846.     }
  847.     /**
  848.      * Get overdue tickets grouped by status for the current agent (solo Pending y Resolved)
  849.      */
  850.     public function getAgentTicketAlertsXHR(Request $request)
  851.     {
  852.         $entityManager $this->getDoctrine()->getManager();
  853.         $activeUser $this->container->get('user.service')->getSessionUser();
  854.         if (!$activeUser) {
  855.             return new Response(json_encode(['overdue' => []]), 200, ['Content-Type' => 'application/json']);
  856.         }
  857.         $threeDaysAgo = new \DateTime();
  858.         $threeDaysAgo->modify('-3 days');
  859.         $qb $entityManager->createQueryBuilder();
  860.         $qb->select('status.description AS statusName, COUNT(t.id) AS count')
  861.             ->from('UVDeskCoreFrameworkBundle:Ticket''t')
  862.             ->leftJoin('t.status''status')
  863.             ->andWhere('t.agent = :agentId')
  864.             ->andWhere('t.updatedAt < :threeDaysAgo')
  865.             ->andWhere('status.id IN (:statusIds)')
  866.             ->andWhere('t.isTrashed = :isTrashed')
  867.             ->groupBy('status.description')
  868.             ->setParameter('agentId'$activeUser->getId())
  869.             ->setParameter('threeDaysAgo'$threeDaysAgo)
  870.             ->setParameter('statusIds', [24])
  871.             ->setParameter('isTrashed'false);
  872.         $results $qb->getQuery()->getResult();
  873.         $overdue = [];
  874.         foreach ($results as $row) {
  875.             $overdue[$row['statusName']] = (int)$row['count'];
  876.         }
  877.         $response = new Response(json_encode(['overdue' => $overdue]));
  878.         $response->headers->set('Content-Type''application/json');
  879.         return $response;
  880.     }
  881.     private function isKnowledgebaseActive()
  882.     {
  883.         $entityManager $this->getDoctrine()->getManager();
  884.         $website $entityManager->getRepository('UVDeskCoreFrameworkBundle:Website')->findOneByCode('knowledgebase');
  885.         if (!empty($website)) {
  886.             $knowledgebaseWebsite $entityManager->getRepository('UVDeskSupportCenterBundle:KnowledgebaseWebsite')->findOneBy(['website' => $website->getId(), 'status' => true]);
  887.             if (!empty($knowledgebaseWebsite) && true == $knowledgebaseWebsite->getIsActive()) {
  888.                 return true;
  889.             }
  890.         }
  891.         throw new \Symfony\Component\HttpKernel\Exception\NotFoundHttpException('Page Not Found');
  892.     }
  893.     /**
  894.      * @Route("/api/knowledgebase/documents", name="helpdesk_member_knowledgebase_documents_api", methods={"GET", "OPTIONS"})
  895.      */
  896.     public function getKnowledgebaseDocuments(Request $request)
  897.     {
  898.         $this->isKnowledgebaseActive();
  899.         
  900.         $entityManager $this->getDoctrine()->getManager();
  901.         $articleRepository $entityManager->getRepository('UVDeskSupportCenterBundle:Article');
  902.         
  903.         // Obtener todos los artículos activos
  904.         $query $entityManager->createQueryBuilder()
  905.             ->select('a')
  906.             ->from('UVDeskSupportCenterBundle:Article''a')
  907.             ->where('a.status = :status')
  908.             ->setParameter('status'1);
  909.             
  910.         // Ejecutar la consulta para obtener todos los artículos
  911.         $articles $query->getQuery()->getResult();
  912.         
  913.         // Formatear los resultados
  914.         $documents = [];
  915.         $searchQuery $request->query->get('search');
  916.         $searchPerformed = !empty($searchQuery);
  917.         
  918.         foreach ($articles as $article) {
  919.             // Obtener el contenido del artículo
  920.             $content $article->getContent();
  921.             
  922.             // Inicializar variables
  923.             $documentName 'TEC' $article->getId() . ' - ' $article->getName();
  924.             $documentUrl null;
  925.             $linkText '';
  926.             $includeDocument true;
  927.             
  928.             // Información de depuración
  929.             $contentDebug = [
  930.                 'articleId' => $article->getId(),
  931.                 'hasContent' => !empty($content),
  932.                 'contentLength' => !empty($content) ? strlen($content) : 0,
  933.             ];
  934.             
  935.             // Buscar enlaces .pdf en el contenido
  936.             if (!empty($content)) {
  937.                 // Vista previa del contenido
  938.                 $contentPreview substr($content0300) . (strlen($content) > 300 '...' '');
  939.                 $contentDebug['contentPreview'] = $contentPreview;
  940.                 
  941.                 // Contar menciones de PDF y enlaces
  942.                 $contentDebug['pdfMentions'] = substr_count(strtolower($content), '.pdf');
  943.                 $contentDebug['linkMentions'] = substr_count(strtolower($content), 'href=');
  944.                 
  945.                 // Buscar enlaces dentro de etiquetas <a href>
  946.                 preg_match_all('/<a\s+[^>]*href=["\']([^"\']+)["\'][^>]*>([^<]+)<\/a>/i'$content$matches);
  947.                 
  948.                 if (!empty($matches[1])) {
  949.                     // Filtrar solo los enlaces relevantes (PDF o softguardtv.com o otros recursos)
  950.                     $relevantLinks = [];
  951.                     $relevantTexts = [];
  952.                     
  953.                     foreach ($matches[1] as $index => $link) {
  954.                         // Aceptar si es un PDF o contiene softguardtv.com o knowledge
  955.                         if (preg_match('/\.pdf$/i'$link) || 
  956.                             strpos($link'softguardtv.com') !== false || 
  957.                             strpos($link'knowledge') !== false ||
  958.                             strpos($link'courses') !== false ||
  959.                             strpos($link'lectures') !== false) {
  960.                             $relevantLinks[] = $link;
  961.                             $relevantTexts[] = $matches[2][$index] ?? '';
  962.                         }
  963.                     }
  964.                     
  965.                     // Guardar enlaces y textos relevantes
  966.                     if (!empty($relevantLinks)) {
  967.                         $contentDebug['foundLinks'] = $relevantLinks;
  968.                         $contentDebug['linkTexts'] = $relevantTexts;
  969.                         
  970.                         // Usar el primer enlace encontrado
  971.                         $documentUrl $relevantLinks[0];
  972.                         $linkText = !empty($relevantTexts[0]) ? trim($relevantTexts[0]) : '';
  973.                         
  974.                         // Usar texto del enlace como nombre
  975.                         if (!empty($linkText)) {
  976.                             $documentName $linkText;
  977.                         }
  978.                     }
  979.                 } else {
  980.                     // Búsqueda alternativa para URLs
  981.                     preg_match_all('/(https?:\/\/[^\s"\'<>]+)/i'$content$altMatches);
  982.                     
  983.                     if (!empty($altMatches[0])) {
  984.                         // Filtrar enlaces relevantes
  985.                         $relevantLinks = [];
  986.                         foreach ($altMatches[0] as $link) {
  987.                             if (preg_match('/\.pdf$/i'$link) || 
  988.                                 strpos($link'softguardtv.com') !== false || 
  989.                                 strpos($link'knowledge') !== false ||
  990.                                 strpos($link'courses') !== false ||
  991.                                 strpos($link'lectures') !== false) {
  992.                                 $relevantLinks[] = $link;
  993.                             }
  994.                         }
  995.                         
  996.                         if (!empty($relevantLinks)) {
  997.                             $contentDebug['foundLinks'] = $relevantLinks;
  998.                             $documentUrl $relevantLinks[0];
  999.                         }
  1000.                     }
  1001.                 }
  1002.                 
  1003.                 // Si se está realizando una búsqueda, verificar si el término está en el contenido
  1004.                 if ($searchPerformed) {
  1005.                     // Buscar en el contenido completo (incluye texto y enlaces)
  1006.                     $includeDocument = (
  1007.                         stripos($content$searchQuery) !== false || 
  1008.                         stripos($documentName$searchQuery) !== false || 
  1009.                         (!empty($documentUrl) && stripos($documentUrl$searchQuery) !== false) ||
  1010.                         (!empty($linkText) && stripos($linkText$searchQuery) !== false)
  1011.                     );
  1012.                     
  1013.                     $contentDebug['searchPerformed'] = true;
  1014.                     $contentDebug['searchTerm'] = $searchQuery;
  1015.                     $contentDebug['matchFound'] = $includeDocument;
  1016.                 }
  1017.             }
  1018.             
  1019.             // Si no se encontró URL, usar formato estándar
  1020.             if (empty($documentUrl)) {
  1021.                 $baseDocumentName $article->getName();
  1022.                 $documentUrl 'https://softguard.com/knowledge/TEC' $article->getId() . '_' $baseDocumentName '.pdf';
  1023.                 $contentDebug['usingFallbackUrl'] = true;
  1024.             }
  1025.             
  1026.             // Solo incluir el documento si pasa los filtros de búsqueda
  1027.             if ($includeDocument) {
  1028.                 $documents[] = [
  1029.                     'id' => $article->getId(),
  1030.                     'name' => $documentName,
  1031.                     'url' => $documentUrl,
  1032.                     'views' => $article->getViewed(),
  1033.                     'dateAdded' => $article->getDateAdded() ? $article->getDateAdded()->format('Y-m-d H:i:s') : null,
  1034.                     'contentDebug' => $contentDebug
  1035.                 ];
  1036.             }
  1037.         }
  1038.         
  1039.         // Ordenar por popularidad (vistas)
  1040.         usort($documents, function($a$b) {
  1041.             return $b['views'] - $a['views'];
  1042.         });
  1043.         
  1044.         // Devolver respuesta JSON
  1045.         return new JsonResponse([
  1046.             'success' => true,
  1047.             'documents' => $documents,
  1048.             'totalFound' => count($documents),
  1049.             'searchQuery' => $searchQuery
  1050.         ]);
  1051.     }
  1052.     public function getTicketSuggestions(Request $request)
  1053.     {
  1054.         $ticketId $request->query->get('ticketId');
  1055.         
  1056.         if (empty($ticketId)) {
  1057.             return new JsonResponse([
  1058.                 'success' => false,
  1059.                 'message' => 'Se requiere ticketId'
  1060.             ], 400);
  1061.         }
  1062.         
  1063.         $entityManager $this->getDoctrine()->getManager();
  1064.         
  1065.         // Obtener el mensaje inicial del ticket actual
  1066.         $currentTicket $entityManager->getRepository('UVDeskCoreFrameworkBundle:Ticket')->find($ticketId);
  1067.         if (!$currentTicket) {
  1068.             return new JsonResponse([
  1069.                 'success' => false,
  1070.                 'message' => 'Ticket no encontrado'
  1071.             ], 404);
  1072.         }
  1073.         
  1074.         $initialThread $entityManager->createQueryBuilder()
  1075.             ->select('t')
  1076.             ->from('UVDeskCoreFrameworkBundle:Thread''t')
  1077.             ->where('t.ticket = :ticketId')
  1078.             ->andWhere('t.threadType = :threadType')
  1079.             ->andWhere('t.createdBy = :createdBy')
  1080.             ->setParameter('ticketId'$ticketId)
  1081.             ->setParameter('threadType''create')
  1082.             ->setParameter('createdBy''customer')
  1083.             ->orderBy('t.id''ASC')
  1084.             ->setMaxResults(1)
  1085.             ->getQuery()
  1086.             ->getOneOrNullResult();
  1087.         
  1088.         if (!$initialThread) {
  1089.             return new JsonResponse([
  1090.                 'success' => false,
  1091.                 'message' => 'No se encontró el mensaje inicial del cliente'
  1092.             ], 404);
  1093.         }
  1094.         
  1095.         $currentProblem strip_tags($initialThread->getMessage());
  1096.         $currentSubject $currentTicket->getSubject();
  1097.         
  1098.         // Extraer palabras dinámicas importantes
  1099.         $dynamicImportantWords $this->extractDynamicImportantWords($currentProblem$currentSubject);
  1100.         
  1101.         // Buscar tickets similares
  1102.         $similarTickets $this->findSimilarTickets($currentProblem$currentSubject$ticketId$entityManager);
  1103.         
  1104.         return new JsonResponse([
  1105.             'success' => true,
  1106.             'currentProblem' => $currentProblem,
  1107.             'currentSubject' => $currentSubject,
  1108.             'suggestions' => $similarTickets,
  1109.             'totalFound' => count($similarTickets),
  1110.             'debug' => [
  1111.                 'dynamicImportantWords' => $dynamicImportantWords,
  1112.                 'problemKeywords' => $this->extractKeywords($this->normalizeText($currentProblem)),
  1113.                 'subjectKeywords' => $this->extractKeywords($this->normalizeText($currentSubject)),
  1114.                 'totalTicketsSearched' => 500,
  1115.                 'ticketsWithReplies' => count($similarTickets)
  1116.             ]
  1117.         ]);
  1118.     }
  1119.     
  1120.     private function findSimilarTickets($currentProblem$currentSubject$excludeTicketId$entityManager)
  1121.     {
  1122.         $normalizedProblem $this->normalizeText($currentProblem);
  1123.         $normalizedSubject $this->normalizeText($currentSubject);
  1124.         $problemKeywords $this->extractKeywords($normalizedProblem);
  1125.         $subjectKeywords $this->extractKeywords($normalizedSubject);
  1126.         
  1127.         // Extraer palabras dinámicas importantes del ticket actual
  1128.         $dynamicImportantWords $this->extractDynamicImportantWords($currentProblem$currentSubject);
  1129.         
  1130.         $query $entityManager->createQueryBuilder()
  1131.             ->select('t, th, u')
  1132.             ->from('UVDeskCoreFrameworkBundle:Ticket''t')
  1133.             ->join('t.threads''th')
  1134.             ->join('th.user''u')
  1135.             ->where('t.id != :excludeId')
  1136.             ->andWhere('t.status IN (:statuses)')
  1137.             ->andWhere('th.threadType = :threadType')
  1138.             ->andWhere('th.createdBy = :createdBy')
  1139.             ->setParameter('excludeId'$excludeTicketId)
  1140.             ->setParameter('statuses', [345])
  1141.             ->setParameter('threadType''create')
  1142.             ->setParameter('createdBy''customer')
  1143.             ->orderBy('t.createdAt''DESC')
  1144.             ->setMaxResults(500)  // Aumentado de 200 a 500
  1145.             ->getQuery();
  1146.         $tickets $query->getResult();
  1147.         $similarTickets = [];
  1148.         foreach ($tickets as $ticket) {
  1149.             $initialThread null;
  1150.             foreach ($ticket->getThreads() as $thread) {
  1151.                 if ($thread->getThreadType() === 'create' && $thread->getCreatedBy() === 'customer') {
  1152.                     $initialThread $thread;
  1153.                     break;
  1154.                 }
  1155.             }
  1156.             if ($initialThread) {
  1157.                 $ticketProblem strip_tags($initialThread->getMessage());
  1158.                 $ticketSubject $ticket->getSubject();
  1159.                 $similarity $this->calculateIntelligentSimilarity(
  1160.                     $normalizedProblem
  1161.                     $normalizedSubject,
  1162.                     $this->normalizeText($ticketProblem),
  1163.                     $this->normalizeText($ticketSubject),
  1164.                     $problemKeywords,
  1165.                     $subjectKeywords,
  1166.                     $dynamicImportantWords
  1167.                 );
  1168.                 if ($similarity['total'] >= 15) {  // Reducido de 20 a 15
  1169.                     $agentReplies $entityManager->createQueryBuilder()
  1170.                         ->select('th, u')
  1171.                         ->from('UVDeskCoreFrameworkBundle:Thread''th')
  1172.                         ->join('th.user''u')
  1173.                         ->where('th.ticket = :ticketId')
  1174.                         ->andWhere('th.threadType = :threadType')
  1175.                         ->andWhere('th.createdBy = :createdBy')
  1176.                         ->andWhere('LENGTH(th.message) > 20')
  1177.                         ->setParameter('ticketId'$ticket->getId())
  1178.                         ->setParameter('threadType''reply')
  1179.                         ->setParameter('createdBy''agent')
  1180.                         ->orderBy('th.createdAt''ASC')
  1181.                         ->getQuery()
  1182.                         ->getResult();
  1183.                     $solutions = [];
  1184.                     foreach ($agentReplies as $reply) {
  1185.                         $solutions[] = [
  1186.                             'id' => $reply->getId(),
  1187.                             'text' => strip_tags($reply->getMessage()),
  1188.                             'agent' => $reply->getUser() ? $reply->getUser()->getFirstName() . ' ' $reply->getUser()->getLastName() : 'Agente',
  1189.                             'date' => $reply->getCreatedAt()->format('Y-m-d H:i:s'),
  1190.                             'length' => strlen(strip_tags($reply->getMessage()))
  1191.                         ];
  1192.                     }
  1193.                     if (!empty($solutions)) {
  1194.                         $similarTickets[] = [
  1195.                             'ticketId' => $ticket->getId(),
  1196.                             'subject' => $ticketSubject,
  1197.                             'problem' => $ticketProblem,
  1198.                             'similarity' => $similarity,
  1199.                             'solutions' => $solutions,
  1200.                             'status' => $ticket->getStatus()->getDescription(),
  1201.                             'createdAt' => $ticket->getCreatedAt()->format('Y-m-d H:i:s'),
  1202.                             'customer' => $initialThread->getUser() ? $initialThread->getUser()->getFirstName() . ' ' $initialThread->getUser()->getLastName() : 'Cliente'
  1203.                         ];
  1204.                     }
  1205.                 }
  1206.             }
  1207.         }
  1208.         usort($similarTickets, function($a$b) {
  1209.             return $b['similarity']['total'] <=> $a['similarity']['total'];
  1210.         });
  1211.         return array_slice($similarTickets08);
  1212.     }
  1213.     private function normalizeText($text)
  1214.     {
  1215.         $text strtolower($text);
  1216.         $text preg_replace('/[^\w\s]/'' '$text);
  1217.         $text preg_replace('/\s+/'' '$text);
  1218.         return trim($text);
  1219.     }
  1220.     private function extractKeywords($text)
  1221.     {
  1222.         $stopWords = [
  1223.             'el''la''de''que''y''a''en''un''es''se''no''te''lo''le''da''su''por''son''con''para''al''del''los''las''una''como''pero''sus''me''hasta''hay''donde''han''quien''estan''estado''desde''todo''nos''durante''todos''uno''les''ni''contra''otros''ese''eso''ante''ellos''e''esto''mi''antes''algunos''que''unos''yo''otro''otras''otra''el''tanto''esa''estos''mucho''quienes''nada''muchos''cual''poco''ella''estar''estas''algunas''algo''nosotros''este''esta''estos''estas''ese''esa''esos''esas''aquel''aquella''aquellos''aquellas''mi''tu''su''nuestro''vuestro''su''mio''tuyo''suyo''nuestro''vuestro''suyo''mios''tuyos''suyos''nuestros''vuestros''suyos''mia''tuya''suya''nuestra''vuestra''suya''mias''tuyas''suyas''nuestras''vuestras''suyas'
  1224.         ];
  1225.         $words explode(' '$text);
  1226.         $keywords = [];
  1227.         foreach ($words as $word) {
  1228.             $word trim($word);
  1229.             if (strlen($word) > && !in_array($word$stopWords)) {
  1230.                 $keywords[] = $word;
  1231.             }
  1232.         }
  1233.         $wordCount array_count_values($keywords);
  1234.         arsort($wordCount);
  1235.         return array_slice(array_keys($wordCount), 015);
  1236.     }
  1237.     private function calculateIntelligentSimilarity($problem1$subject1$problem2$subject2$keywords1$keywords2$dynamicImportantWords = [])
  1238.     {
  1239.         $scores = [];
  1240.         
  1241.         // 1. Similitud de texto del problema (peso reducido)
  1242.         similar_text($problem1$problem2$problemSimilarity);
  1243.         $scores['problem_text'] = $problemSimilarity;
  1244.         
  1245.         // 2. Similitud de subject (peso aumentado significativamente)
  1246.         similar_text($subject1$subject2$subjectSimilarity);
  1247.         $scores['subject_text'] = $subjectSimilarity;
  1248.         
  1249.         // 3. Coincidencia exacta de palabras clave (nuevo - peso alto)
  1250.         $subjectKeywords1 $this->extractKeywords($subject1);
  1251.         $subjectKeywords2 $this->extractKeywords($subject2);
  1252.         $problemKeywords1 $this->extractKeywords($problem1);
  1253.         $problemKeywords2 $this->extractKeywords($problem2);
  1254.         
  1255.         // Combinar keywords del subject y problema para el ticket actual
  1256.         $currentKeywords array_merge($subjectKeywords1$problemKeywords1);
  1257.         $currentKeywords array_unique($currentKeywords);
  1258.         
  1259.         // Combinar keywords del subject y problema para el ticket comparado
  1260.         $compareKeywords array_merge($subjectKeywords2$problemKeywords2);
  1261.         $compareKeywords array_unique($compareKeywords);
  1262.         
  1263.         // Calcular coincidencias exactas
  1264.         $exactMatches array_intersect($currentKeywords$compareKeywords);
  1265.         $exactMatchScore count($exactMatches) > ? (count($exactMatches) / max(count($currentKeywords), count($compareKeywords))) * 100 0;
  1266.         $scores['exact_keywords'] = $exactMatchScore;
  1267.         
  1268.         // 4. Coincidencia de keywords general (peso reducido)
  1269.         $keywordMatches array_intersect($keywords1$keywords2);
  1270.         $keywordScore count($keywordMatches) / max(count($keywords1), count($keywords2)) * 100;
  1271.         $scores['keywords'] = $keywordScore;
  1272.         
  1273.         // 5. Similitud por categorías
  1274.         $categoryScore $this->calculateCategorySimilarity($problem1 ' ' $subject1$problem2 ' ' $subject2);
  1275.         $scores['category'] = $categoryScore;
  1276.         
  1277.         // 6. Similitud técnica
  1278.         $technicalScore $this->calculateTechnicalSimilarity($problem1 ' ' $subject1$problem2 ' ' $subject2);
  1279.         $scores['technical'] = $technicalScore;
  1280.         
  1281.         // 7. Búsqueda de palabras específicas importantes (nuevo)
  1282.         $specificWordsScore $this->calculateSpecificWordsSimilarity($problem1 ' ' $subject1$problem2 ' ' $subject2);
  1283.         $scores['specific_words'] = $specificWordsScore;
  1284.         
  1285.         // 8. Coincidencia de palabras dinámicas importantes (nuevo - peso muy alto)
  1286.         $dynamicWordsScore $this->calculateDynamicWordsSimilarity($problem2 ' ' $subject2$dynamicImportantWords);
  1287.         $scores['dynamic_words'] = $dynamicWordsScore;
  1288.         
  1289.         // Nueva fórmula de peso que prioriza subject, palabras exactas y palabras dinámicas
  1290.         $total = (
  1291.             $scores['problem_text'] * 0.15 +      // Reducido de 0.20
  1292.             $scores['subject_text'] * 0.25 +      // Reducido de 0.30
  1293.             $scores['exact_keywords'] * 0.20 +    // Reducido de 0.25
  1294.             $scores['keywords'] * 0.10 +          // Mantenido
  1295.             $scores['category'] * 0.10 +          // Mantenido
  1296.             $scores['technical'] * 0.05 +         // Mantenido
  1297.             $scores['specific_words'] * 0.05 +    // Reducido de 0.05
  1298.             $scores['dynamic_words'] * 0.10       // Nuevo - peso alto para palabras dinámicas
  1299.         );
  1300.         
  1301.         $scores['total'] = round($total2);
  1302.         return $scores;
  1303.     }
  1304.     
  1305.     /**
  1306.      * Extrae palabras dinámicas importantes del ticket actual
  1307.      */
  1308.     private function extractDynamicImportantWords($problem$subject)
  1309.     {
  1310.         $importantWords = [];
  1311.         
  1312.         // Combinar problema y subject
  1313.         $combinedText $problem ' ' $subject;
  1314.         $normalizedText $this->normalizeText($combinedText);
  1315.         
  1316.         // Extraer todas las palabras
  1317.         $words explode(' '$normalizedText);
  1318.         $wordCount array_count_values($words);
  1319.         
  1320.         // Filtrar palabras por longitud y frecuencia
  1321.         $filteredWords = [];
  1322.         foreach ($wordCount as $word => $count) {
  1323.             $word trim($word);
  1324.             // Incluir palabras de 3+ caracteres que aparecen al menos 1 vez
  1325.             if (strlen($word) >= && $count >= 1) {
  1326.                 $filteredWords[$word] = $count;
  1327.             }
  1328.         }
  1329.         
  1330.         // Ordenar por frecuencia
  1331.         arsort($filteredWords);
  1332.         
  1333.         // Tomar las primeras 10 palabras más frecuentes
  1334.         $topWords array_slice(array_keys($filteredWords), 010);
  1335.         
  1336.         // Agregar palabras específicas que siempre son importantes
  1337.         $specificImportantWords = [
  1338.             'comando''comandos''programar''programacion''programa''programas',
  1339.             'script''scripts''codigo''codigos''funcion''funciones',
  1340.             'error''errores''falla''fallas''problema''problemas',
  1341.             'conexion''conectar''internet''red''wifi''network',
  1342.             'usuario''usuarios''password''contraseña''login''acceso',
  1343.             'datos''archivo''archivos''documento''documentos',
  1344.             'pago''pagos''factura''facturas''cobro''precio',
  1345.             'soporte''ayuda''asistencia''tecnico''servicio',
  1346.             'configuracion''ajuste''ajustes''parametro''parametros',
  1347.             'actualizacion''update''version''nuevo''nueva''cambio'
  1348.         ];
  1349.         
  1350.         // Buscar palabras específicas en el texto actual
  1351.         foreach ($specificImportantWords as $word) {
  1352.             if (stripos($combinedText$word) !== false) {
  1353.                 $importantWords[] = $word;
  1354.             }
  1355.         }
  1356.         
  1357.         // Agregar las palabras más frecuentes del texto actual
  1358.         $importantWords array_merge($importantWords$topWords);
  1359.         
  1360.         // Eliminar duplicados y retornar
  1361.         return array_unique($importantWords);
  1362.     }
  1363.     
  1364.     /**
  1365.      * Calcula la similitud basada en palabras dinámicas importantes
  1366.      */
  1367.     private function calculateDynamicWordsSimilarity($compareText$dynamicImportantWords)
  1368.     {
  1369.         if (empty($dynamicImportantWords)) {
  1370.             return 0;
  1371.         }
  1372.         
  1373.         $normalizedCompareText $this->normalizeText($compareText);
  1374.         $foundWords = [];
  1375.         
  1376.         // Buscar cada palabra importante en el texto a comparar
  1377.         foreach ($dynamicImportantWords as $word) {
  1378.             if (stripos($normalizedCompareText$word) !== false) {
  1379.                 $foundWords[] = $word;
  1380.             }
  1381.         }
  1382.         
  1383.         // Calcular score basado en la proporción de palabras encontradas
  1384.         $matchRatio count($foundWords) / count($dynamicImportantWords);
  1385.         
  1386.         // Dar bonus extra por coincidencias múltiples
  1387.         $bonus count($foundWords) * 15;
  1388.         
  1389.         $score = ($matchRatio 100) + $bonus;
  1390.         
  1391.         return min(100$score);
  1392.     }
  1393.     private function calculateCategorySimilarity($text1$text2)
  1394.     {
  1395.         $categories = [
  1396.             'error' => ['error''falla''problema''bug''crash''no funciona''no anda'],
  1397.             'conexion' => ['conexion''conectar''internet''red''wifi''network''online'],
  1398.             'usuario' => ['usuario''password''contraseña''login''acceso''autenticacion'],
  1399.             'datos' => ['datos''archivo''documento''informacion''base de datos''backup'],
  1400.             'pago' => ['pago''factura''cobro''precio''costo''tarifa''billing'],
  1401.             'soporte' => ['soporte''ayuda''asistencia''tecnico''servicio'],
  1402.             'configuracion' => ['configuracion''ajuste''parametro''setting''setup'],
  1403.             'actualizacion' => ['actualizacion''update''version''nuevo''cambio']
  1404.         ];
  1405.         $score 0;
  1406.         $totalCategories count($categories);
  1407.         foreach ($categories as $category => $keywords) {
  1408.             $text1HasCategory false;
  1409.             $text2HasCategory false;
  1410.             foreach ($keywords as $keyword) {
  1411.                 if (stripos($text1$keyword) !== false) {
  1412.                     $text1HasCategory true;
  1413.                 }
  1414.                 if (stripos($text2$keyword) !== false) {
  1415.                     $text2HasCategory true;
  1416.                 }
  1417.             }
  1418.             if ($text1HasCategory && $text2HasCategory) {
  1419.                 $score += 100;
  1420.             } elseif ($text1HasCategory || $text2HasCategory) {
  1421.                 $score += 0;
  1422.             }
  1423.         }
  1424.         return $score $totalCategories;
  1425.     }
  1426.     private function calculateTechnicalSimilarity($text1$text2)
  1427.     {
  1428.         $technicalTerms = [
  1429.             'api''database''server''client''protocol''ssl''https''http',
  1430.             'json''xml''rest''soap''authentication''authorization''token',
  1431.             'session''cookie''cache''memory''cpu''disk''storage''backup',
  1432.             'firewall''proxy''vpn''dns''ip''domain''subdomain''ssl',
  1433.             'certificate''encryption''decryption''hash''md5''sha''aes',
  1434.             'mysql''postgresql''mongodb''redis''elasticsearch''kafka',
  1435.             'docker''kubernetes''aws''azure''gcp''cloud''saas''paas'
  1436.         ];
  1437.         $text1Terms = [];
  1438.         $text2Terms = [];
  1439.         foreach ($technicalTerms as $term) {
  1440.             if (stripos($text1$term) !== false) {
  1441.                 $text1Terms[] = $term;
  1442.             }
  1443.             if (stripos($text2$term) !== false) {
  1444.                 $text2Terms[] = $term;
  1445.             }
  1446.         }
  1447.         if (empty($text1Terms) && empty($text2Terms)) {
  1448.             return 0;
  1449.         }
  1450.         $commonTerms array_intersect($text1Terms$text2Terms);
  1451.         $totalTerms array_unique(array_merge($text1Terms$text2Terms));
  1452.         return count($totalTerms) > ? (count($commonTerms) / count($totalTerms)) * 100 0;
  1453.     }
  1454.     private function calculateSpecificWordsSimilarity($text1$text2)
  1455.     {
  1456.         // Palabras específicas importantes que deben tener peso extra
  1457.         $specificWords = [
  1458.             // Comandos y programación
  1459.             'comando''comandos''programar''programacion''programa''programas',
  1460.             'script''scripts''codigo''codigos''funcion''funciones',
  1461.             'variable''variables''parametro''parametros''configuracion',
  1462.             
  1463.             // Errores y problemas
  1464.             'error''errores''falla''fallas''problema''problemas',
  1465.             'no funciona''no anda''no aparece''no puedo''no me deja',
  1466.             
  1467.             // Conexión y red
  1468.             'conexion''conectar''conecta''internet''red''wifi''network',
  1469.             'servidor''servidor''online''offline''timeout''timeout',
  1470.             
  1471.             // Usuarios y acceso
  1472.             'usuario''usuarios''password''contraseña''login''acceso',
  1473.             'autenticacion''autorizacion''sesion''perfil''cuenta',
  1474.             
  1475.             // Datos y archivos
  1476.             'datos''archivo''archivos''documento''documentos''informacion',
  1477.             'base de datos''backup''respaldo''guardar''guardado',
  1478.             
  1479.             // Pagos y facturación
  1480.             'pago''pagos''factura''facturas''cobro''precio''costo',
  1481.             'tarifa''billing''invoice''payment''charge',
  1482.             
  1483.             // Soporte y ayuda
  1484.             'soporte''ayuda''asistencia''tecnico''servicio''servicios',
  1485.             'manual''guia''tutorial''instrucciones''pasos',
  1486.             
  1487.             // Configuración y ajustes
  1488.             'configuracion''ajuste''ajustes''parametro''parametros',
  1489.             'setting''settings''setup''instalacion''instalar',
  1490.             
  1491.             // Actualizaciones y cambios
  1492.             'actualizacion''update''version''nuevo''nueva''cambio',
  1493.             'cambios''mejora''mejoras''nueva version''actualizar'
  1494.         ];
  1495.         
  1496.         $text1Words = [];
  1497.         $text2Words = [];
  1498.         
  1499.         // Buscar palabras específicas en ambos textos
  1500.         foreach ($specificWords as $word) {
  1501.             if (stripos($text1$word) !== false) {
  1502.                 $text1Words[] = $word;
  1503.             }
  1504.             if (stripos($text2$word) !== false) {
  1505.                 $text2Words[] = $word;
  1506.             }
  1507.         }
  1508.         
  1509.         // Si no hay palabras específicas en ninguno, retornar 0
  1510.         if (empty($text1Words) && empty($text2Words)) {
  1511.             return 0;
  1512.         }
  1513.         
  1514.         // Calcular coincidencias
  1515.         $commonWords array_intersect($text1Words$text2Words);
  1516.         $totalWords array_unique(array_merge($text1Words$text2Words));
  1517.         
  1518.         // Dar peso extra si hay coincidencias exactas
  1519.         $baseScore count($totalWords) > ? (count($commonWords) / count($totalWords)) * 100 0;
  1520.         
  1521.         // Bonus por coincidencias múltiples
  1522.         $bonus count($commonWords) * 10;
  1523.         
  1524.         return min(100$baseScore $bonus);
  1525.     }
  1526.     /**
  1527.      * Función auxiliar para convertir texto en un slug
  1528.      */
  1529.     private function slugify($text)
  1530.     {
  1531.         // Reemplazar espacios con guiones bajos
  1532.         $text str_replace(' ''_'$text);
  1533.         // Eliminar caracteres especiales
  1534.         $text preg_replace('/[^A-Za-z0-9\-_]/'''$text);
  1535.         // Convertir a minúsculas
  1536.         return strtolower($text);
  1537.     }
  1538.   
  1539.     /**
  1540.      * Devuelve la configuración de alertas de tickets desde archivo config/ticket_alerts.json
  1541.      */
  1542.     public function getTicketAlertConfigXHR(Request $request)
  1543.     {
  1544.         $kernel $this->get('kernel');
  1545.         $projectRoot $kernel->getProjectDir();
  1546.         $configFile $projectRoot '/config/ticket_alerts.json';
  1547.         $default = [
  1548.             'ticket_alert_interval' => 1800000,
  1549.             'ticket_alert_enabled' => true,
  1550.             'ticket_alert_days_threshold' => 3
  1551.         ];
  1552.         
  1553.         if (file_exists($configFile)) {
  1554.             $content file_get_contents($configFile);
  1555.             $params json_decode($contenttrue);
  1556.             
  1557.             $interval = isset($params['ticket_alert_interval']) ? (int)$params['ticket_alert_interval'] : $default['ticket_alert_interval'];
  1558.             $enabled = isset($params['ticket_alert_enabled']) ? (bool)$params['ticket_alert_enabled'] : $default['ticket_alert_enabled'];
  1559.             $daysThreshold = isset($params['ticket_alert_days_threshold']) ? (int)$params['ticket_alert_days_threshold'] : $default['ticket_alert_days_threshold'];
  1560.         } else {
  1561.             $interval $default['ticket_alert_interval'];
  1562.             $enabled $default['ticket_alert_enabled'];
  1563.             $daysThreshold $default['ticket_alert_days_threshold'];
  1564.         }
  1565.         $config = [
  1566.             'interval' => $interval,
  1567.             'enabled' => $enabled,
  1568.             'days_threshold' => $daysThreshold,
  1569.             'message' => '¡Hay tickets vencidos!'
  1570.         ];
  1571.         return new JsonResponse($config);
  1572.     }
  1573. }