src/Aviatur/ParkBundle/Controller/ParkAvailabilityController.php line 67

Open in your IDE?
  1. <?php
  2. namespace Aviatur\ParkBundle\Controller;
  3. use Aviatur\TwigBundle\Services\TwigFolder;
  4. use Doctrine\Persistence\ManagerRegistry;
  5. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  6. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  7. use Symfony\Component\HttpFoundation\JsonResponse;
  8. use Symfony\Component\HttpFoundation\Request;
  9. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  10. use Aviatur\GeneralBundle\Services\AviaturLogSave;
  11. use Aviatur\GeneralBundle\Services\ExceptionLog;
  12. use Aviatur\GeneralBundle\Services\AviaturWebService;
  13. use Aviatur\GeneralBundle\Services\AviaturRestService;
  14. use Aviatur\GeneralBundle\Services\AviaturErrorHandler;
  15. use Aviatur\GeneralBundle\Services\AviaturChangeCoin;
  16. use Aviatur\ParkBundle\Models\ParkAvailModel;
  17. use Aviatur\ParkBundle\Models\ParkSearchModel;
  18. use Aviatur\ParkBundle\Services\ListParkService;
  19. use Aviatur\ParkBundle\Services\LegacyTicketParkService;
  20. use Aviatur\ParkBundle\Services\SendVoucherEmailParkService;
  21. use Aviatur\AgencyBundle\Entity\Agency;
  22. class ParkAvailabilityController extends AbstractController
  23. {
  24.     /**
  25.      * @var ManagerRegistry
  26.      */
  27.     protected ManagerRegistry $managerRegistry;
  28.     /**
  29.      * @var AviaturRestService
  30.      */
  31.     private $aviaturRestService;
  32.     /**
  33.      * @var SessionInterface
  34.      */
  35.     protected $session;
  36.     /**
  37.      * @var Agency|object|null
  38.      */
  39.     protected $agency;
  40.     protected static $exceptionLog;
  41.     private $em;
  42.     private $listParkService;
  43.     private $legacyTicketParkService;
  44.     private $sendVoucherEmailParkService;
  45.     public function __construct(ManagerRegistry $managerRegistrySessionInterface $sessionAviaturRestService $aviaturRestServiceExceptionLog $exceptionLogListParkService $listParkServiceLegacyTicketParkService $legacyTicketParkServiceSendVoucherEmailParkService $sendVoucherEmailParkService)
  46.     {
  47.         self::$exceptionLog $exceptionLog;
  48.         $this->em $managerRegistry->getManager();
  49.         $this->aviaturRestService $aviaturRestService;
  50.         $this->session $session;
  51.         $this->agency $this->em->getRepository(Agency::class)->find($session->get('agencyId'));
  52.         $this->listParkService $listParkService;
  53.         $this->legacyTicketParkService $legacyTicketParkService;
  54.         $this->sendVoucherEmailParkService $sendVoucherEmailParkService;
  55.     }
  56.     public function searchAction()
  57.     {
  58.         return $this->redirect(
  59.             $this->generateUrl(
  60.                 'aviatur_search_parks',
  61.                 []
  62.             )
  63.         );
  64.     }
  65.     // Función vista de disponibilidad
  66.     public function availabilityAction(Request $requestAviaturErrorHandler $aviaturErrorHandlerParameterBagInterface $parameterBagTwigFolder $twigFolder)
  67.     {
  68.         $params $request->attributes->get("_route_params");
  69.         // Listado de variables
  70.         $namePark $params["park"];
  71.         $nameSede $params["sede"];
  72.         $date1 $params["datepark"];
  73.         $now = new \DateTime();
  74.         // Inicializar el mensaje de error
  75.         $redirectTitle "Recomendación Automática";
  76.         $redirectText "";
  77.         // VALIDACIÓN DE FECHA: Comprobamos si la fecha es válida
  78.         $dateIn = \DateTime::createFromFormat("Y-m-d"$date1);
  79.         if (!$dateIn || $dateIn->format("Y-m-d") !== $date1 || $dateIn <= $now) {
  80.             // Si la fecha no es válida se asigna la fecha dateIn
  81.             $dateIn $now->modify("+1 day");
  82.             $redirectTitle "Se requiere seleccionar una fecha con 24 horas de antelación.";
  83.             $redirectText .= "La búsqueda se ha ajustado automáticamente a la fecha más cercana permitida. \n";
  84.         }
  85.         // VALIDACIÓN DE ADULTOS
  86.         $passangerTickets $params["passenger"];
  87.         list($adults$children) = explode("-"$passangerTickets);
  88.         if (intval($adults) <= 0) {
  89.             $passangerTickets "1-" $children// Si no hay adultos, se asigna 1 adulto
  90.             $redirectTitle "Es necesario al menos un adulto.";
  91.             $redirectText .= "La búsqueda se ha ajustado automáticamente para incluir 1 adulto.";
  92.         }
  93.         // Si hay un mensaje de error
  94.         if ($redirectText) {
  95.             // Configurar el modelo de búsqueda
  96.             $parkSearchModel = new ParkSearchModel();
  97.             $parkSearchModel->setNamePark($namePark);
  98.             $parkSearchModel->setNameSedePark($nameSede);
  99.             $parkSearchModel->setDatePark($dateIn->format("Y-m-d"));
  100.             $parkSearchModel->setPassengerPark($passangerTickets);
  101.             $parkSearchAvail $parkSearchModel->getData();
  102.             // Redirigir con el mensaje de error
  103.             return $this->redirect($aviaturErrorHandler->errorRedirectNoEmail($this->generateUrl('aviatur_park_avaliability'$parkSearchAvail), $redirectTitle$redirectText));
  104.         }
  105.         // Validación de la existencia del parque y sede
  106.         $availabilityParameters $this->em->getRepository(\Aviatur\ParkBundle\Entity\ParkSedes::class)->findAvailabilityParameters($this->agency$namePark$nameSede);
  107.         
  108.         // Si no se encuentra el parque o la sede
  109.         if (empty($availabilityParameters)) {
  110.             $titleError "La ubicación seleccionada no está disponible.";
  111.             $textError "Ha sido redirigido al home para que pueda seguir explorando opciones.";
  112.             // Redirigir al Home de parques con el mensaje de error
  113.             return $this->redirect($aviaturErrorHandler->errorRedirectNoEmail($this->generateUrl('aviatur_search_parks', []), $titleError$textError));
  114.         }
  115.         // Obtener parques y sedes disponibles
  116.         $parksAndSedes $this->listParkService->getParksAndSedes($this->agency);
  117.         $agencyFolder $twigFolder->twigFlux();
  118.         // Renderizar la vista con los datos
  119.         return $this->render($twigFolder->twigExists(sprintf('@AviaturTwig/%s/Park/parkAvailability_index.html.twig'$agencyFolder)), [
  120.             "listParks" => $parksAndSedes["parks"],
  121.             "listSedesPark" => $parksAndSedes["sedes"]
  122.         ]);
  123.     }
  124.     // Función disponibilidad de los tickets del parque
  125.     public function getAvailableTicketsAction(Request $requestAviaturLogSave $logSaveAviaturWebService $aviaturWebServiceAviaturErrorHandler $aviaturErrorHandlerParameterBagInterface $parameterBagAviaturChangeCoin $aviaturChangeCoin)
  126.     {
  127.         // Generación del transactionId
  128.         $transactionIdSessionName $parameterBag->get('transaction_id_session_name');
  129.         $session $this->session;
  130.         $makeLogin true;
  131.         if ($request->query->has('transactionMulti')) {
  132.             $transactionId base64_decode($request->query->get('transactionMulti'));
  133.             $session->set($transactionIdSessionName$transactionId);
  134.             $makeLogin false;
  135.         }
  136.         //generacion del transactionId
  137.         if ($makeLogin) {
  138.             $transactionIdResponse $aviaturWebService->loginService('SERVICIO_MPT''dummy|http://www.aviatur.com.co/dummy/', []);
  139.             if ('error' == $transactionIdResponse || is_array($transactionIdResponse)) {
  140.                 $aviaturErrorHandler->errorRedirect('''Error MPA''No se creo Login!');
  141.                 return (new JsonResponse())->setData([
  142.                     'tickets' => [],
  143.                     'error' => true,
  144.                     "message" => [
  145.                         "title" => "No se encontró disponibilidad en este momento.",
  146.                         "text" => "Estamos experimentando dificultades técnicas en este momento."
  147.                     ]
  148.                 ]);
  149.             }
  150.             $transactionId = (string) $transactionIdResponse;
  151.             $session->set($transactionIdSessionName$transactionId);
  152.         }
  153.         
  154.         // Parametros RQ
  155.         $params json_decode($request->getContent(), true);
  156.         // VALIDACIÓN DE ADULTOS
  157.         $adultsTickets = (int) $params['adults'];
  158.         $childrenTickets = (int) $params['children'];
  159.         if (($adultsTickets $childrenTickets) > 9) {
  160.             return (new JsonResponse())->setData([
  161.                 "tickets" => [],
  162.                 "error" => true,
  163.                 "message" => [
  164.                     "title" => "No se encontró disponibilidad en este momento.",
  165.                     "text" => "Para más de 9 pasajeros, contáctenos para brindarle la mejor opción o modifique la búsqueda."
  166.                 ]
  167.             ]);
  168.         }
  169.         // Obtener datos de BD para el RQ
  170.         $availabilityParameters $this->em->getRepository(\Aviatur\ParkBundle\Entity\ParkSedes::class)->findAvailabilityParameters($this->agency$params['park'], $params['sede']);
  171.         $availableTickets = [];
  172.         if (isset($availabilityParameters) && count($availabilityParameters) > 0) {
  173.             $date1 $params['datepark'];
  174.             $date = new \DateTime($date1);
  175.             $date->modify('+1 day');
  176.             $date2 $date->format('Y-m-d');
  177.             $parkAvailModel = new ParkAvailModel();
  178.             $parkAvailModel->setRequestKind($availabilityParameters['requestKind']);
  179.             $parkAvailModel->setStartDate($date1);
  180.             $parkAvailModel->setEndDate($date2);
  181.             $parkAvailModel->setEnvironmentCode($availabilityParameters['environmentCode']);
  182.             $parkRQ $parkAvailModel->getData();
  183.             
  184.             $logSave->logSave(json_encode($parkRQ), 'ParkAvail'$availabilityParameters['providerIdentifier'] . '_RQ'$transactionId);
  185.             $response $this->aviaturRestService->callRestWebService($availabilityParameters['wsUrl'], 'dispo'$parkRQ);
  186.             $logSave->logSave(json_encode($response), 'ParkAvail''RS'$transactionId); 
  187.             // Guardar en sessión el RS de disponibilidad encodeada
  188.             $ajaxParametersParksAvail base64_encode(json_encode($response));
  189.             $session->set($transactionId.'[park][ticketsAvailability]'$ajaxParametersParksAvail);
  190.             // Verificar que el response tenga los datos necesarios
  191.             if (!empty($response) && $response["succeeded"] && !empty($response["productCatalogs"])) {
  192.                 // Obtener TRM
  193.                 $trmData $aviaturChangeCoin->getExchangeRate('USD''COP''TRM');
  194.                 $trmDataValue $trmData['OfficialExchangeRate'];
  195.                 // Mapear tickets RS
  196.                 $availableTickets $this->getAvailableTickets($response$params$trmDataValue$availabilityParameters['deliveryMethod']);
  197.             }
  198.         } 
  199.         return new JsonResponse([
  200.             "transactionId" => $transactionId,
  201.             "tickets" => $availableTickets,
  202.             "error" => empty($availableTickets),
  203.             "message" => [
  204.                 "title" => empty($availableTickets) ? "No se encontró disponibilidad en este momento." "",
  205.                 "text" => empty($availableTickets) ? "Por favor, intente nuevamente más tarde o contáctenos para obtener más información." ""
  206.             ]
  207.         ]);
  208.     }
  209.     // Obtener tipo de pasajero y calcular el precio del ticket
  210.     private function processTicketForPassengerType($ticket$params$markupPark$trm$adultsTickets$childrenTickets)
  211.     {
  212.         // Determinar el tipo de pasajero (Adulto, Niño, No importa la edad)
  213.         $typePassengerTicket = !empty($ticket['ageValue']) ? $ticket['ageValue'] : $ticket['ageQualifies'] ?? null;
  214.         if (is_array($typePassengerTicket) && !empty($typePassengerTicket)) {
  215.             $typePassengerTicket $typePassengerTicket[0]['ageType'];
  216.         }
  217.         // Definir cantidad de tickets y precios iniciales
  218.         $quantityTickets 0;
  219.         $priceTicketAdult 0;
  220.         $priceTicketChild 0;
  221.         // Obtener el precio base y la moneda
  222.         $amountAfterTaxTicket $ticket['price']['amountAfterTax'];
  223.         $decimalPlacesTicket $ticket['price']['decimalPlaces'];
  224.         $currencyTicket 'COP';
  225.         // Actualizamos la moneda si es necesario
  226.         if ($trm == 1) {
  227.             $currencyTicket $ticket['price']['currency'];
  228.         }
  229.         // Precio del ticket en cop con trm y markup
  230.         $priceTicketCOP $this->calculateTicketPrice($amountAfterTaxTicket$decimalPlacesTicket$markupPark$trm);
  231.         // Calcular precios basados en el tipo de pasajero
  232.         switch ($typePassengerTicket) {
  233.             case 'Adult':
  234.             case '10 Años en adelante':
  235.                 $quantityTickets $adultsTickets;
  236.                 $priceTicketAdult $priceTicketCOP $quantityTickets;
  237.                 break;
  238.             case 'Child':
  239.             case 'Entre 3 y 9 años':
  240.                 $quantityTickets $childrenTickets;
  241.                 $priceTicketChild $priceTicketCOP $quantityTickets;
  242.                 break;
  243.             case 'No importa la edad':
  244.                 $quantityTickets $adultsTickets $childrenTickets;
  245.                 $priceTicketAdult $priceTicketCOP $adultsTickets;
  246.                 $priceTicketChild $priceTicketCOP $childrenTickets;
  247.                 break;
  248.         }
  249.         return [$priceTicketAdult$priceTicketChild$quantityTickets$currencyTicket];
  250.     }
  251.     // Mapeo de tickets disponibles
  252.     private function getAvailableTickets($response$params$trm$deliveryMethodSelect)
  253.     {
  254.         $allTickets $response["productCatalogs"];
  255.         $adultsTickets = (int) $params['adults'];
  256.         $childrenTickets = (int) $params['children'];
  257.         $quantityTotalTickets $adultsTickets $childrenTickets;
  258.         $groupedTickets = [];
  259.         // Obtener el porcentaje de markup del parque
  260.         $markupPark $this->em->getRepository(\Aviatur\ParkBundle\Entity\ParkMarkup::class)->getMarkupPark($params['park'], $params['sede']);
  261.         $markupPark = empty($markupPark) ? $markupPark;
  262.         // Procesar cada ticket
  263.         foreach ($allTickets as $ticket) {
  264.             // Filtrar tickets no deseados
  265.             if (
  266.                 ($params['park'] == "Universal" && stripos(strtolower($ticket['productName']), 'vip') !== false) || 
  267.                 ($params['park'] == "Universal" && stripos(strtolower($ticket['productName']), 'express') !== false) || 
  268.                 ($params['park'] == "Universal" && stripos(strtolower($ticket['salesProgramName']), 'express') !== false) ||
  269.                 (!empty($ticket["date"]) && !str_contains($ticket["date"], $params["datepark"])) ||
  270.                 (!empty($ticket["price"]) && !empty($ticket["price"]["amountAfterTax"]) && $ticket["price"]["amountAfterTax"] == 0) ||
  271.                 (!empty($ticket["allowedDeliveryMethods"]) && !in_array($deliveryMethodSelect$ticket["allowedDeliveryMethods"])) 
  272.             ) {
  273.                 continue;
  274.             }
  275.             // Tickets especiales
  276.             $isSpecial false;
  277.             if (
  278.                 ($params['park'] == "Disney" && isset($ticket['productId'][0]) && strtoupper($ticket['productId'][0]) == 'S'
  279.             ) {
  280.                 $isSpecial true;
  281.             }
  282.             // Eliminar "Adult" o "Child" del nombre del ticket para agruparlos
  283.             $baseName preg_replace('/(Adult|Child|Ad |Ch )/'''$ticket['productName']);
  284.             $baseName trim($baseName);
  285.             // Inicializar ticket agrupado si no existe
  286.             if (!isset($groupedTickets[$baseName])) {
  287.                 $groupedTickets[$baseName] = [
  288.                     'nameTicket' => $baseName,
  289.                     'numberOfDays' => $ticket['numberOfDays'],
  290.                     'salesProgramId' => $ticket['salesProgramId'],
  291.                     'quantityTotalTickets' => $quantityTotalTickets,
  292.                     'priceTicket' => [
  293.                         'priceAdultTicket' => 0,
  294.                         'priceChildTicket' => 0,
  295.                         'priceTotalTicket' => 0,
  296.                         'currency' => 'COP'
  297.                     ],
  298.                     'findEvent' => $ticket['capacity'],
  299.                     'isSpecial' => $isSpecial,
  300.                     'detailTicket' => [],
  301.                     'tickets' => []
  302.                 ];
  303.             }
  304.             // Llamada al método para procesar el ticket según el tipo de pasajero
  305.             list($priceTicketAdult$priceTicketChild$quantityTickets$currencyTicket) = $this->processTicketForPassengerType($ticket$params$markupPark$trm$adultsTickets$childrenTickets);
  306.             
  307.             // Actualizamos los precios si es necesario
  308.             if (
  309.                 ($groupedTickets[$baseName]['priceTicket']['priceAdultTicket'] != && $groupedTickets[$baseName]['priceTicket']['priceAdultTicket'] < $priceTicketAdult) ||
  310.                 ($groupedTickets[$baseName]['priceTicket']['priceChildTicket'] != && $groupedTickets[$baseName]['priceTicket']['priceChildTicket'] < $priceTicketChild)
  311.             ) {
  312.                 continue;
  313.             }
  314.             $groupedTickets[$baseName]['priceTicket']['priceAdultTicket'] = $priceTicketAdult != $priceTicketAdult $groupedTickets[$baseName]['priceTicket']['priceAdultTicket'];
  315.             $groupedTickets[$baseName]['priceTicket']['priceChildTicket'] = $priceTicketChild != $priceTicketChild $groupedTickets[$baseName]['priceTicket']['priceChildTicket'];
  316.             $groupedTickets[$baseName]['priceTicket']['currency'] = $currencyTicket;
  317.             // Agregar detalles del ticket
  318.             $ticketDetails = !empty($ticket['aditionalData']) ? $ticket['aditionalData'] : $ticket['detailTicket'];
  319.             if (!empty($ticketDetails)) {
  320.                 $groupedTickets[$baseName]['detailTicket'] = $this->processTicketDetails($ticketDetails);
  321.             }
  322.             // Agregar el producto al grupo correspondiente
  323.             if ($quantityTickets 0) {
  324.                 $groupedTickets[$baseName]['priceTicket']['priceTotalTicket'] = $groupedTickets[$baseName]['priceTicket']['priceAdultTicket'] + $groupedTickets[$baseName]['priceTicket']['priceChildTicket'];
  325.                 $groupedTickets[$baseName]['tickets'][] = $ticket['productId'];
  326.             }
  327.         }
  328.         // Verificamos si $groupedTickets no está vacío. Ordenamos 'priceTicket' de menor a mayor
  329.         if (!empty($groupedTickets)) {
  330.             usort($groupedTickets, function($a$b) {
  331.                 return $a['priceTicket']['priceTotalTicket'] <=> $b['priceTicket']['priceTotalTicket'];
  332.             });
  333.             // Eliminar los elementos que cumplen las condiciones especificadas
  334.             foreach ($groupedTickets as $key => $ticket) {
  335.                 if (($adultsTickets != && $ticket['priceTicket']['priceAdultTicket'] == 0) || 
  336.                     ($childrenTickets != && $ticket['priceTicket']['priceChildTicket'] == 0)) {
  337.                     unset($groupedTickets[$key]);
  338.                 }
  339.             }
  340.             $availabilityParameters $this->em->getRepository(\Aviatur\ParkBundle\Entity\ParkSedes::class)->findAvailabilityParameters($this->agency$params['park'], $params['sede']);
  341.             // Mapear información legal de los tickets, politicas.
  342.             $groupedTickets array_values($groupedTickets);
  343.             foreach ($groupedTickets as $key => $ticket) {
  344.                 // Extraer número de parques si existe "X-Park"
  345.                 $parkNumber "";
  346.                 if (preg_match('/(\d+)-Park/'$ticket['nameTicket'], $matches)) {
  347.                     $parkNumber $matches[1];
  348.                 }
  349.                 // Validar si es Park to Park y lista de parques permitidos
  350.                 $aditionalData $ticket["detailTicket"];
  351.                 $isParkToPark $this->sendVoucherEmailParkService->isParkToPark($aditionalData);
  352.                 $parksVisitList $this->getParksVisitList($aditionalData);
  353.                 // Conector de listado de parques para text voucher
  354.                 $conectorText $isParkToPark "<strong>AND</strong>" "<strong>OR</strong>";
  355.                 $nameParksVisitList "";
  356.                 $countParks count($parksVisitList);
  357.                 foreach ($parksVisitList as $i => $park) {
  358.                     $nameParksVisitList .= $park;
  359.                     if ($i $countParks 1) {
  360.                         $nameParksVisitList .= $conectorText ";
  361.                     }
  362.                 }
  363.                 // Agregar políticas legacy y reemplazar valores
  364.                 $legacyTicketPark $this->legacyTicketParkService->getLegacyTicketPark($availabilityParameters["parkSedesId"], $ticket['salesProgramId'], $ticket['nameTicket'], $ticket['numberOfDays'], $parkNumber);
  365.                 if(empty($legacyTicketPark)){
  366.                     unset($groupedTickets[$key]);
  367.                 }else{
  368.                     $legacyTicketPark str_replace("{{productName}}"$ticket["nameTicket"], $legacyTicketPark);
  369.                     $legacyTicketPark str_replace("{{nameParksVisitList}}"$nameParksVisitList$legacyTicketPark);
  370.                     $groupedTickets[$key]['legacyTicketPark'] = $legacyTicketPark;
  371.                 }
  372.             }
  373.         }
  374.         
  375.         return $groupedTickets;
  376.     }
  377.     // Calcular el precio del ticket con markup en COP
  378.     private function calculateTicketPrice($priceTicket$decimalPlacesTicket$markupPark$trm)
  379.     {
  380.         $priceTicket $priceTicket * (/ (100 $markupPark)) * 100
  381.         $priceTicket round($priceTicket, (int) $decimalPlacesTicket); //Redondeando en USD
  382.         $priceTicket $priceTicket $trm;
  383.         return round($priceTicket, (int) $decimalPlacesTicket); //Redondeando en COP
  384.     }
  385.     // Función auxiliar para procesar los detalles del ticket
  386.     private function processTicketDetails($infodetailTicket)
  387.     {
  388.         $arrayDetailTicket = [];
  389.         // Si tenemos additionValues y additionType es "ValidPark", lo procesamos - DISNEY
  390.         $validParkItems array_filter($infodetailTicket, function ($item) {
  391.             return isset($item['additionType']) && $item['additionType'] == "ValidPark";
  392.         });
  393.         if (!empty($validParkItems)) {
  394.             $validParkItems reset($validParkItems);
  395.             $newItem = [
  396.                 'titleDetailPark' => 'Parques que puedes visitar:',
  397.                 'itemsDetailPark' => array_map(function ($subitem) {
  398.                     return $subitem['name'] ?? '';
  399.                 }, $validParkItems['additionValues'])
  400.             ];
  401.             $arrayDetailTicket[] = $newItem;
  402.             $arrayDetailTicket[] = [
  403.                 'titleDetailPark' => 'No se requiere reservar los parques.',
  404.                 'itemsDetailPark' => []
  405.             ];
  406.         } else {
  407.             // Si no hay "ValidPark", agregamos los títulos con itemsDetailPark vacíos - UNIVERSAL
  408.             foreach ($infodetailTicket as $item) {
  409.                 $arrayDetailTicket[] = [
  410.                     'titleDetailPark' => $item,
  411.                     'itemsDetailPark' => []
  412.                 ];
  413.             }
  414.         }
  415.         return $arrayDetailTicket;
  416.     }
  417.     // Función para obtener la lista de parques del ticket
  418.     private function getParksVisitList(array $aditionalData): array {
  419.         $parksVisitList = [];
  420.         foreach ($aditionalData as $item) {
  421.             $text is_array($item) && isset($item['titleDetailPark']) ? $item['titleDetailPark'] : (is_string($item) ? $item '');
  422.             if (stripos($text'Posibilidad de entrar a') !== false) {
  423.                 $parts explode(':'$text2);
  424.                 if (isset($parts[1])) {
  425.                     $parks explode(','$parts[1]);
  426.                     $parks array_map('trim'$parks);
  427.                     $parksVisitList array_merge($parksVisitList$parks);
  428.                 }
  429.             }
  430.         }
  431.         return array_filter(array_unique($parksVisitList), fn($p) => $p !== '');
  432.     }
  433. }