src/Controller/HomeController.php line 483

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