src/Controller/HomeController.php line 307

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Configuration;
  4. use App\Entity\Space;
  5. use App\Entity\User;
  6. use App\Entity\WidgetNotes;
  7. use App\Entity\HtFile;
  8. use App\MDS\AvexpressBundle\Entity\AveFiles;
  9. use App\Entity\SettingsCompany;
  10. use App\MDS\VenuesBundle\Entity\ReservationInvoice;
  11. use App\Form\WidgetNotesType;
  12. use App\MDS\VenuesBundle\Entity\Reservation;
  13. use App\MDS\VenuesBundle\Entity\ReservationLoungeDetails;
  14. use App\MDS\VenuesBundle\Entity\ReservationLoungeSimple;
  15. use App\MDS\VenuesBundle\Entity\ReservationVisit;
  16. use App\Service\CalendarService;
  17. use App\Service\UserNotificationService;
  18. use Doctrine\ORM\EntityManagerInterface;
  19. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  20. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  21. use Symfony\Component\Routing\Annotation\Route;
  22. use Symfony\Component\HttpFoundation\Request;
  23. use Symfony\Component\HttpFoundation\Response;
  24. use Symfony\Component\HttpFoundation\Session\Session;
  25. use Symfony\Component\HttpFoundation\JsonResponse;
  26. use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
  27. use Symfony\Contracts\Translation\TranslatorInterface;
  28. use Google_Client;
  29. use Google_Service_Calendar;
  30. use Doctrine\ORM\Query;
  31. use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  32. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  33. use Swift_Mailer;
  34. use Swift_Message;
  35. use Swift_SmtpTransport;
  36. class HomeController extends AbstractController
  37. {
  38.     private $googleCalendar;
  39.     private $translator;
  40.     private $userNotificationService;
  41.     private SessionInterface $session;
  42.     public function __construct(TranslatorInterface $translatorUserNotificationService $userNotificationServiceSessionInterface $session)
  43.     {
  44.         $redirectUri 'https://' . ($_SERVER['HTTP_HOST'] ?? '') . '/calendar/token';
  45.         $client = new Google_Client();
  46.         $client->setApplicationName('Google Calendar API');
  47.         $client->setClientId('YOUR_GOOGLE_CLIENT_ID');
  48.         $client->setClientSecret('YOUR_GOOGLE_CLIENT_SECRET');
  49.         $client->setRedirectUri($redirectUri);
  50.         $client->addScope(Google_Service_Calendar::CALENDAR);
  51.         $guzzle = new \GuzzleHttp\Client(['curl' => [CURLOPT_SSL_VERIFYPEER => false]]);
  52.         $client->setHttpClient($guzzle);
  53.         $this->googleCalendar $client;
  54.         $this->translator $translator;
  55.         $this->userNotificationService $userNotificationService;
  56.         $this->session $session;
  57.     }
  58.     /**
  59.      * Portal principal con los 3 accesos (ManteVenues, ManteCatering, ManteAV)
  60.      * @Route("/inicio", name="ruta_inicio")
  61.      */
  62.     public function inicioAction(AuthenticationUtils $authenticationUtilsEntityManagerInterface $em): Response
  63.     {
  64.         $configuration $em->getRepository(Configuration::class)->findOneBy([]);
  65.         return $this->render('login/login.html.twig', [
  66.             'last_username' => $authenticationUtils->getLastUsername(),
  67.             'error' => $authenticationUtils->getLastAuthenticationError(),
  68.             'configuration' => $configuration,
  69.         ]);
  70.     }
  71.    
  72. /**
  73.      * @Route("/", name="homepage")
  74.      */
  75.     public function index(EntityManagerInterface $emRequest $request): Response
  76.     {
  77.         $user $this->getUser();
  78.         if (!$user) {
  79.             return $this->redirectToRoute('ruta_inicio');
  80.         }
  81.         // Recuperamos el tipo de acceso actual (Venues, Catering o AV) de la sesión
  82.         $accessType $this->session->get('current_access_type') ?? 'venues';
  83.         $tz = new \DateTimeZone('Europe/Madrid');
  84.         $from = (new \DateTimeImmutable('today'$tz))->setTime(000);
  85.         $to $from->setTime(235959);
  86.         $agendaMix = [];
  87.         $formattedReservations = [];
  88.         $formattedVisits = [];
  89.         if ($accessType === 'venues') {
  90.             // 1. FILTRADO POR EQUIPO "LEGENDS VIEW"
  91.             $isLegendTeam = (strtoupper(trim($user->getTeam() ?? '')) === 'LEGENDS VIEW');
  92.             $lSpace null;
  93.             if ($isLegendTeam) {
  94.                 $lSpace $em->getRepository(Space::class)->createQueryBuilder('s')
  95.                     ->where('s.name LIKE :n')->setParameter('n''%LEGEND%')
  96.                     ->setMaxResults(1)->getQuery()->getOneOrNullResult();
  97.             }
  98.             /** --- LÓGICA Módulo VENUES (Reservas y Visitas) --- */
  99.             $qbR $em->createQueryBuilder()
  100.                 ->select('r.id''r.title''r.dateStart''r.dateEnd''lounge.name as loungeName''rls.type''r.status')
  101.                 ->from(Reservation::class, 'r')
  102.                 ->leftJoin(ReservationLoungeSimple::class, 'rls''WITH''rls.idReservation = r.id')
  103.                 ->leftJoin(ReservationLoungeDetails::class, 'lounge''WITH''lounge.id = rls.idLounge')
  104.                 ->where('r.dateStart <= :to AND COALESCE(r.dateEnd, r.dateStart) >= :from')
  105.                 ->andWhere('r.status <> :deleted')
  106.                 ->setParameter('from'$from)
  107.                 ->setParameter('to'$to)
  108.                 ->setParameter('deleted''Deleted')
  109.                 ->orderBy('r.dateStart''ASC');
  110.             if ($isLegendTeam && $lSpace) {
  111.                 $qbR->andWhere('lounge.space = :sid')->setParameter('sid'$lSpace->getId());
  112.             }
  113.             $rawRows $qbR->getQuery()->getArrayResult();
  114.             $reservationsMap = [];
  115.             foreach ($rawRows as $row) {
  116.                 $id $row['id'];
  117.                 $type $row['type'];
  118.                 $key $id . ($type '_' $type '');
  119.                 if (!isset($reservationsMap[$key])) {
  120.                     $prefix $type '[' strtoupper($type) . '] ' '';
  121.                     $reservationsMap[$key] = [
  122.                         'id' => $id,
  123.                         'title' => $prefix . ($row['title'] ?? 'Reserva'),
  124.                         'dateStart' => $row['dateStart'],
  125.                         'rooms' => [],
  126.                         'tipo' => 'RESERVA',
  127.                         'status' => $row['status'],
  128.                         'url' => $this->generateUrl('reservations_venues_edit_simple', ['id' => $id])
  129.                     ];
  130.                 }
  131.                 if ($row['loungeName']) {
  132.                     $reservationsMap[$key]['rooms'][] = $row['loungeName'];
  133.                 }
  134.             }
  135.             $rIds array_unique(array_column($reservationsMap'id'));
  136.             $invoiceMap = [];
  137.             if (!empty($rIds)) {
  138.                 $invoices $em->getRepository(ReservationInvoice::class)
  139.                     ->findBy(['reservationId' => $rIds], ['id' => 'DESC']);
  140.                 foreach ($invoices as $inv) {
  141.                     if (!isset($invoiceMap[$inv->getReservationId()])) {
  142.                         $invoiceMap[$inv->getReservationId()] = $inv->getId();
  143.                     }
  144.                 }
  145.             }
  146.             foreach ($reservationsMap as $res) {
  147.                 $res['room'] = implode(', 'array_unique($res['rooms']));
  148.                 $res['invoiceUrl'] = isset($invoiceMap[$res['id']]) 
  149.                     ? $this->generateUrl('reservations_invoiceorproforma_print', ['type' => 'I''id' => $invoiceMap[$res['id']]]) 
  150.                     : null;
  151.                 $res['showUrl'] = $this->generateUrl('reservations_venues_show_simple', ['id' => $res['id']]);
  152.                 $formattedReservations[] = $res;
  153.             }
  154.             // VISITAS
  155.             $qbV $em->getRepository(ReservationVisit::class)->createQueryBuilder('v')
  156.                 ->where('v.dateStart <= :to AND COALESCE(v.dateEnd, v.dateStart) >= :from')
  157.                 ->setParameter('from'$from)
  158.                 ->setParameter('to'$to)
  159.                 ->orderBy('v.dateStart''ASC');
  160.             if ($isLegendTeam && $lSpace) {
  161.                 $qbV->andWhere('v.space = :sid')->setParameter('sid'$lSpace->getId());
  162.             }
  163.             $visitEntities $qbV->getQuery()->getResult();
  164.             $loungeRepo $em->getRepository(ReservationLoungeDetails::class);
  165.             foreach ($visitEntities as $visit) {
  166.                 $lName '-';
  167.                 if ($visit->getIdLoungeDetails()) {
  168.                     $lounge $loungeRepo->find($visit->getIdLoungeDetails());
  169.                     $lName $lounge $lounge->getName() : '-';
  170.                 }
  171.                 $formattedVisits[] = [
  172.                     'id' => $visit->getId(),
  173.                     'title' => ($visit->getTitle() ?? 'Sin título'),
  174.                     'dateStart' => $visit->getDateStart(),
  175.                     'room' => $lName,
  176.                     'tipo' => 'VISITA',
  177.                     'url' => $this->generateUrl('venues_edit_visit', ['id' => $visit->getId()])
  178.                 ];
  179.             }
  180.             $agendaMix array_merge($formattedReservations$formattedVisits);
  181.             usort($agendaMix, fn ($a$b) => $a['dateStart'] <=> $b['dateStart']);
  182.         } else if ($accessType === 'catering') {
  183.             /** --- LÓGICA Módulo CATERING (Optimizado) --- */
  184.             // 1. Eventos de Catering
  185.             $htFiles $em->getRepository(HtFile::class)->createQueryBuilder('h')
  186.                 ->where('h.dateStart <= :to AND COALESCE(h.dateEnd, h.dateStart) >= :from')
  187.                 ->setParameter('from'$from)
  188.                 ->setParameter('to'$to)
  189.                 ->orderBy('h.dateStart''ASC')
  190.                 ->getQuery()->getResult();
  191.             $htEventsFormatted = [];
  192.             foreach ($htFiles as $ht) {
  193.                 $htEventsFormatted[] = [
  194.                     'id' => $ht->getId(),
  195.                     'title' => '[CATERING] ' . ($ht->getTitle() ?? 'Sin título'),
  196.                     'dateStart' => $ht->getDateStart(),
  197.                     'room' => 'Servicio Catering',
  198.                     'tipo' => 'RESERVA',
  199.                     'url' => $this->generateUrl('app_ht_file_edit', ['id' => $ht->getId()])
  200.                 ];
  201.             }
  202.             // 2. Montajes/Desmontajes de Venues (Llamada optimizada al repositorio)
  203.             $venueRows $em->getRepository(Reservation::class)->findMontajesForCatering($from$to);
  204.             
  205.             $venueEventsFormatted = [];
  206.             foreach ($venueRows as $row) {
  207.                 $venueEventsFormatted[] = [
  208.                     'id' => $row['id'],
  209.                     'title' => '[' strtoupper($row['type'] ?? 'TRABAJO') . '] ' . ($row['title'] ?? 'Reserva'),
  210.                     'dateStart' => $row['dateStart'],
  211.                     'room' => $row['loungeName'] ?? '-',
  212.                     'tipo' => 'RESERVA',
  213.                     'url' => $this->generateUrl('reservations_venues_edit_simple', ['id' => $row['id']])
  214.                 ];
  215.             }
  216.             $agendaMix array_merge($htEventsFormatted$venueEventsFormatted);
  217.             usort($agendaMix, fn ($a$b) => $a['dateStart'] <=> $b['dateStart']);
  218.             $formattedReservations $agendaMix;
  219.         } else if ($accessType === 'av') {
  220.             /** --- LÓGICA Módulo AV (Audiovisuales) --- */
  221.             $avFiles $em->getRepository(AveFiles::class)->createQueryBuilder('a')
  222.                 ->where('a.dateStart <= :to AND COALESCE(a.dateEnd, a.dateStart) >= :from')
  223.                 ->andWhere('a.status <> :deleted')
  224.                 ->setParameter('from'$from)
  225.                 ->setParameter('to'$to)
  226.                 ->setParameter('deleted''Deleted')
  227.                 ->orderBy('a.dateStart''ASC')
  228.                 ->getQuery()->getResult();
  229.             foreach ($avFiles as $av) {
  230.                 $formattedReservations[] = [
  231.                     'id' => $av->getId(),
  232.                     'title' => $av->getTitle() ?? 'Servicio AV',
  233.                     'dateStart' => $av->getDateStart(),
  234.                     'room' => 'Equipos AV',
  235.                     'tipo' => 'RESERVA',
  236.                     'url' => $this->generateUrl('ave_edit_file', ['id' => $av->getId()])
  237.                 ];
  238.             }
  239.             $agendaMix $formattedReservations;
  240.         }
  241.         $this->userNotificationService->checkRecentNotifications($user);
  242.         return $this->render('home/index.html.twig', [
  243.             'accessType'           => $accessType,
  244.             'agenda'               => $agendaMix,
  245.             'upcomingReservations' => $formattedReservations,
  246.             'upcomingVisits'       => $formattedVisits,
  247.             'notifications'        => $this->userNotificationService->getForModal($user)
  248.         ]);
  249.     }
  250.    
  251.     /**
  252.      * @Route("/calendar/catering/menu-testings", name="calendar_catering_menu_testings", methods={"GET"})
  253.      */
  254.     public function calendarCateringMenuTestings(Request $requestCalendarService $calendar): JsonResponse
  255.     {
  256.         $fromParam $request->query->get('start''first day of this month');
  257.         $toParam $request->query->get('end''last day of next month');
  258.         try {
  259.             $from = new \DateTimeImmutable($fromParam);
  260.             $to = new \DateTimeImmutable($toParam);
  261.         } catch (\Exception $e) {
  262.             return new JsonResponse(['error' => 'Formato de fecha inválido'], 400);
  263.         }
  264.         // Llamamos al método renombrado
  265.         return new JsonResponse($calendar->getMenuTestingsForCalendar($from$to));
  266.     }
  267.     /**
  268.      * @Route("/calendar/global/visits", name="calendar_global_visits", methods={"GET"})
  269.      */
  270.     public function globalVisits(Request $requestCalendarService $calendarEntityManagerInterface $em): JsonResponse
  271.     {
  272.         $user $this->getUser();
  273.         $spaceIds null;
  274.         if ($user && strtoupper(trim($user->getTeam() ?? '')) === 'LEGENDS VIEW') {
  275.             // Buscamos todos los espacios cuyo nombre sea o contenga 'LEGENDS VIEW'
  276.             $spaces $em->getRepository(Space::class)->createQueryBuilder('s')
  277.                 ->where('s.name LIKE :n')
  278.                 ->setParameter('n''%ESPACIO LEGENDS%')
  279.                 ->getQuery()
  280.                 ->getResult();
  281.             if (!empty($spaces)) {
  282.                 $spaceIds array_map(fn($s) => $s->getId(), $spaces);
  283.             } else {
  284.                 // Seguridad: Si no encuentra el espacio, ID inexistente para que no vea nada por error
  285.                 $spaceIds = [99999];
  286.             }
  287.         }
  288.         $from = new \DateTime($request->query->get('start'));
  289.         $to = new \DateTime($request->query->get('end'));
  290.         return new JsonResponse($calendar->getVisitsForCalendar($spaceIdsnull$from$to));
  291.     }
  292.     /**
  293.      * @Route("/calendar/global/reservations", name="calendar_global_reservations", methods={"GET"})
  294.      */
  295.     public function globalReservations(Request $requestCalendarService $calendarEntityManagerInterface $em): JsonResponse
  296.     {
  297.         $user $this->getUser();
  298.         $spaceId null;
  299.         if ($user && strtoupper(trim($user->getTeam() ?? '')) === 'LEGENDS VIEW') {
  300.             $lSpace $em->getRepository(Space::class)->createQueryBuilder('s')
  301.                 ->where('s.name LIKE :n')
  302.                 ->setParameter('n''%LEGEND%')
  303.                 ->setMaxResults(1)
  304.                 ->getQuery()
  305.                 ->getOneOrNullResult();
  306.             $spaceId $lSpace?->getId();
  307.         }
  308.         $from = new \DateTime($request->query->get('start'));
  309.         $to = new \DateTime($request->query->get('end'));
  310.         return new JsonResponse($calendar->getReservationsForCalendar($spaceIdnull$from$to));
  311.     }
  312.     /**
  313.      * Endpoint específico para que el perfil de Catering vea solo montajes y desmontajes.
  314.      * * @Route("/calendar/catering/reservations", name="calendar_catering_reservations", methods={"GET"})
  315.      */
  316.     public function cateringReservations(Request $requestCalendarService $calendar): JsonResponse
  317.     {
  318.         // Validamos las fechas de entrada que envía FullCalendar
  319.         $startParam $request->query->get('start');
  320.         $endParam $request->query->get('end');
  321.         if (!$startParam || !$endParam) {
  322.             return new JsonResponse(['error' => 'Missing date parameters'], 400);
  323.         }
  324.         try {
  325.             $from = new \DateTime($startParam);
  326.             $to = new \DateTime($endParam);
  327.         } catch (\Exception $e) {
  328.             return new JsonResponse(['error' => 'Invalid date format'], 400);
  329.         }
  330.         // Llamamos al nuevo método específico del servicio
  331.         $events $calendar->getCateringSetupsForCalendar($from$to);
  332.         return new JsonResponse($events);
  333.     }
  334.     /**
  335.      * @Route("/calendar/ht/events", name="calendar_ht_events", methods={"GET"})
  336.      */
  337.     public function calendarHtEvents(Request $requestCalendarService $calendar): JsonResponse
  338.     {
  339.         $from = new \DateTimeImmutable($request->query->get('start''first day of this month'));
  340.         $to = new \DateTimeImmutable($request->query->get('end''last day of next month'));
  341.         return new JsonResponse($calendar->getHtEvents($from$to));
  342.     }
  343.     /**
  344.      * @Route("/calendar/av/events", name="calendar_av_events", methods={"GET"})
  345.      */
  346.     public function calendarAvEvents(Request $requestCalendarService $calendar): JsonResponse
  347.     {
  348.         $from = new \DateTimeImmutable($request->query->get('start''first day of this month'));
  349.         $to = new \DateTimeImmutable($request->query->get('end''last day of next month'));
  350.         // 1. Sacamos los eventos propios de Audiovisuales
  351.         $avEvents $calendar->getAvEvents($from$to'av');
  352.         // 2. Sacamos los montajes y desmontajes asociados a Audiovisuales
  353.         $avSetups $calendar->getAvSetupsForCalendar($from$to'av');
  354.         // 3. Los unimos en un solo array
  355.         $allEvents array_merge($avEvents$avSetups);
  356.         return new JsonResponse($allEvents);
  357.     }
  358.     /**
  359.      * @Route("/ChangeLanguage/{lang}", name="change_language")
  360.      */
  361.     public function changeLanguage(Request $requeststring $lang): Response
  362.     {
  363.         $this->translator->setLocale($lang);
  364.         $request->getSession()->set('_locale'$lang);
  365.         return $this->redirect($request->headers->get('referer'));
  366.     }
  367.     /**
  368.      * @Route("/calendar-full", name="calendar_full")
  369.      */
  370.     public function calendarFull(Request $request): Response
  371.     {
  372.         $token $request->request->get('token');
  373.         $em $this->getDoctrine()->getManager();
  374.         $user $em->getRepository(User::class)->findOneByAccessKey($token);
  375.         if (!$user)
  376.             return $this->redirectToRoute('ruta_inicio');
  377.         $userId $user->getId();
  378.         $wnotes = new WidgetNotes();
  379.         $wnotes->setDateAt(new \DateTime());
  380.         $form $this->createForm(WidgetNotesType::class, $wnotes, [
  381.             'action' => $this->generateUrl('widget_notes_create'),
  382.             'method' => 'POST',
  383.         ]);
  384.         return $this->render('home/calendar-fullscreen.html.twig', [
  385.             'form' => $form->createView(),
  386.             'user' => $userId,
  387.             'token' => $token,
  388.         ]);
  389.     }
  390.     /**
  391.      * @Route("/external/calendar-reservation", name="calendar_external_reservation")
  392.      */
  393.     public function externalCalendar(): Response
  394.     {
  395.         return $this->render('home/calendar-reservation.html.twig');
  396.     }
  397.     /**
  398.      * Controlador para recuperar la contraseña desde el incio
  399.      * @Route("/olvide-mi-contrasena", name="forgot_password")
  400.      */
  401.     public function request(Request $requestEntityManagerInterface $em): Response
  402.     {
  403.         if ($request->isMethod('POST')) {
  404.             $email $request->request->get('email');
  405.             $user $em->getRepository(User::class)->findOneBy(['email' => $email]);
  406.             if ($user) {
  407.                 // Generar un token único
  408.                 $token bin2hex(random_bytes(32));
  409.                 $user->setResetToken($token);
  410.                 $em->flush();
  411.                 // Generar URL absoluta para el email
  412.                 $url $this->generateUrl('reset_password', ['token' => $token], UrlGeneratorInterface::ABSOLUTE_URL);
  413.                 // Enviar el email
  414.                 $data = array(
  415.                     'body' => '<p>Hola,</p><p>Para restablecer tu contraseña, haz clic en el siguiente enlace:</p><a href="'.$url.'">Restablecer mi contraseña</a>',
  416.                     'firm' => null,
  417.                 );
  418.                 $transporter = new Swift_SmtpTransport();
  419.                 $transporter->setHost('smtp.gmail.com')
  420.                     ->setEncryption('ssl'//ssl / tls
  421.                     ->setPort(465// 465 / 587
  422.                     ->setUsername('desarrollo@develup.solutions')
  423.                     ->setPassword('utvh hzoi wfdo ztjs');
  424.                 $mailer = new Swift_Mailer($transporter);
  425.                 $correo = new Swift_Message();
  426.                 $correo->setSubject('Recuperación de contraseña - Plataforma MANTE')
  427.                     ->setSender('desarrollo@develup.solutions')
  428.                     ->setTo($email)
  429.                     ->setBody(
  430.                         $this->renderView(
  431.                             'mail/structure-mail.html.twig',
  432.                             array('data' => $data)
  433.                         ),
  434.                         'text/html'
  435.                     )
  436.                 ;
  437.                 $mailer->send($correo);
  438.             }
  439.             // Por seguridad, siempre mostramos el mismo mensaje exista o no el correo
  440.             $this->addFlash('success''Si el correo existe en nuestro sistema, te hemos enviado un enlace para restablecer tu contraseña.');
  441.             return $this->redirectToRoute('login');
  442.         }
  443.         return $this->render('inicio/request.html.twig');
  444.     }
  445.     /**
  446.      * Controlador para resetear contraseña desde el incio
  447.      * @Route("/restablecer-contrasena/{token}", name="reset_password")
  448.      */
  449.     public function reset(string $tokenRequest $requestEntityManagerInterface $emUserPasswordHasherInterface $passwordHasher): Response
  450.     {
  451.         // Buscar al usuario por el token
  452.         $user $em->getRepository(User::class)->findOneBy(['resetToken' => $token]);
  453.         if (!$user) {
  454.             $this->addFlash('danger''El enlace de recuperación es inválido o ha expirado.');
  455.             return $this->redirectToRoute('login');
  456.         }
  457.         if ($request->isMethod('POST')) {
  458.             $password $request->request->get('password');
  459.             $repeatPassword $request->request->get('repeat_password');
  460.             if ($password === $repeatPassword && strlen($password) >= 6) {
  461.                 // Hashear la nueva contraseña
  462.                 $hashedPassword $passwordHasher->hashPassword($user$password);
  463.                 $user->setPassword($hashedPassword);
  464.                 // Limpiar el token para que no se pueda volver a usar
  465.                 $user->setResetToken(null);
  466.                 $em->flush();
  467.                 $this->addFlash('success''Tu contraseña ha sido cambiada correctamente. Ya puedes iniciar sesión.');
  468.                 return $this->redirectToRoute('login');
  469.             } else {
  470.                 $this->addFlash('danger''Las contraseñas no coinciden o son muy cortas.');
  471.             }
  472.         }
  473.         return $this->render('inicio/reset.html.twig', [
  474.             'token' => $token
  475.         ]);
  476.     }
  477. }