src/Controller/AdminController.php line 322

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Appointment;
  4. use App\Entity\User;
  5. use App\Repository\AppointmentRepository;
  6. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  7. use Symfony\Component\HttpFoundation\Response;
  8. use Symfony\Component\Routing\Annotation\Route;
  9. use App\Repository\UserRepository;
  10. use Knp\Component\Pager\PaginatorInterface;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use App\Repository\HorarioRepository;
  13. use Doctrine\ORM\EntityManagerInterface;
  14. use Symfony\Component\HttpFoundation\JsonResponse;
  15. use App\Form\AdminProfileType;
  16. use App\Form\ChangePasswordType;
  17. use App\Form\ChangeEmailFormType;
  18. use SebastianBergmann\Environment\Console;
  19. use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  20. use Symfony\Component\Form\FormError;
  21. /** 
  22. * @Route("/admin", name="admin")
  23. */
  24. class AdminController extends AbstractController
  25. {
  26.     private $userRepository;
  27.     public function __construct(UserRepository $userRepository)
  28.     {
  29.         $this->userRepository $userRepository;
  30.     }
  31.     
  32.     /** 
  33.     * @Route("", name="_index")
  34.     */
  35.     public function index(): Response
  36.     {
  37.         $this->denyAccessUnlessGranted('ROLE_ADMIN');
  38.              return $this->render('admin/dashboard.html.twig', [
  39.             'seccion' => 'dashboard',
  40.             'titulo_pagina' => 'Panel de Administración'
  41.         ]);
  42.     }
  43.     /**
  44.      * @Route("/resumen", name="_resumen", methods={"GET"})
  45.      */
  46.     public function resumen(
  47.         AppointmentRepository $citaRepo,
  48.         UserRepository $userRepo
  49.     ): JsonResponse {
  50.         // Fechas de referencia
  51.         $hoy = new \DateTime('today');
  52.         $inicioSemana = (clone $hoy)->modify('monday this week');
  53.         $inicioMes = (clone $hoy)->modify('first day of this month');
  54.         $inicioAnno = (clone $hoy)->modify('first day of january');
  55.         // Función auxiliar para contar citas por rango y estado
  56.         $contarCitas = function(\DateTime $inicio$estado null) use ($citaRepo) {
  57.             $fin = new \DateTime('tomorrow');
  58.             return $citaRepo->contarPorRango($inicio$fin$estado);
  59.         };
  60.         // Contar todos los usuarios
  61.         $contarUsuarios = function(\DateTime $inicio) use ($userRepo) {
  62.             $fin = new \DateTime('tomorrow');
  63.             return $userRepo->contarPorRango($inicio$fin);
  64.         };
  65.         // Contar proveedores filtrando por rol
  66.         $contarProveedores = function(\DateTime $inicio) use ($userRepo) {
  67.             $fin = new \DateTime('tomorrow');
  68.             return $userRepo->contarPorRango($inicio$fin'ROLE_PROVIDER');
  69.         };
  70.         $data = [
  71.             'citas' => [
  72.                 'hoy'    => $contarCitas($hoy),
  73.                 'semana' => $contarCitas($inicioSemana),
  74.                 'mes'    => $contarCitas($inicioMes),
  75.                 'anno'   => $contarCitas($inicioAnno),
  76.             ],
  77.             'usuarios' => [
  78.                 'hoy'    => $contarUsuarios($hoy),
  79.                 'semana' => $contarUsuarios($inicioSemana),
  80.                 'mes'    => $contarUsuarios($inicioMes),
  81.                 'anno'   => $contarUsuarios($inicioAnno),
  82.             ],
  83.             'proveedores' => [
  84.                 'hoy'    => $contarProveedores($hoy),
  85.                 'semana' => $contarProveedores($inicioSemana),
  86.                 'mes'    => $contarProveedores($inicioMes),
  87.                 'anno'   => $contarProveedores($inicioAnno),
  88.             ],
  89.             'completadas' => [
  90.                 'hoy'    => $contarCitas($hoy'completada'),
  91.                 'semana' => $contarCitas($inicioSemana'completada'),
  92.                 'mes'    => $contarCitas($inicioMes'completada'),
  93.                 'anno'   => $contarCitas($inicioAnno'completada'),
  94.             ],
  95.             'canceladas' => [
  96.                 'hoy'    => $contarCitas($hoy'cancelada'),
  97.                 'semana' => $contarCitas($inicioSemana'cancelada'),
  98.                 'mes'    => $contarCitas($inicioMes'cancelada'),
  99.                 'anno'   => $contarCitas($inicioAnno'cancelada'),
  100.             ],
  101.         ];
  102.         return $this->json($data);
  103.     }
  104.     /** 
  105.     * @Route("/users", name="_users")
  106.     */
  107.     public function users(Request $requestUserRepository $userRepositoryPaginatorInterface $paginator): Response
  108.     {
  109.         $this->denyAccessUnlessGranted('ROLE_ADMIN');
  110.           // Capturamos el texto de búsqueda desde la URL (?q=...)
  111.         $q $request->query->get('q');
  112.         // Creamos un QueryBuilder para poder filtrar
  113.         $queryBuilder $userRepository->createQueryBuilder('u');
  114.         if ($q) {
  115.             $queryBuilder
  116.                 ->andWhere('u.name LIKE :q OR u.lastName LIKE :q OR u.email LIKE :q OR u.phone LIKE :q OR u.address LIKE :q OR u.roles LIKE :q')
  117.                 ->setParameter('q'"%$q%");
  118.         }
  119.         // Paginamos el resultado
  120.         $pagination $paginator->paginate(
  121.             $queryBuilder// Query o QueryBuilder
  122.             $request->query->getInt('page'1), // Página actual
  123.             10// Elementos por página
  124.             [
  125.             'params' => [
  126.                 'q' => $q,
  127.                 'page' => $request->query->getInt('page'1)
  128.                 ]
  129.             ]
  130.         );
  131.         return $this->render('admin/user/index.html.twig', [
  132.             'users' => $pagination,
  133.             'q' => $q,
  134.             'seccion' => 'users',
  135.             'titulo_pagina' => 'Gestión de Usuarios'
  136.         ]);
  137.     }
  138.     /**
  139.      * @Route("/pacientes", name="_pacientes")
  140.      */
  141.     public function pacientes(Request $requestUserRepository $userRepositoryPaginatorInterface $paginator): Response
  142.     {
  143.         $q $request->query->get('q');
  144.         $providerId $request->query->get('provider');
  145.     
  146.         $queryBuilder $userRepository->createQueryBuilder('patient')
  147.             ->select('patient')
  148.             ->where('patient.roles LIKE :role')
  149.             ->setParameter('role''%ROLE_CLIENT%')
  150.             //->orderBy('patient.name', 'ASC');
  151.             ->orderBy('patient.id''DESC'); // ← CAMBIO: Ordenar por ID descendente
  152.     
  153.         if ($q) {
  154.             // Dividir la búsqueda en palabras individuales
  155.             $searchTerms explode(' 'trim($q));
  156.             
  157.             $orConditions = [];
  158.             $parameters = [];
  159.             
  160.             foreach ($searchTerms as $key => $term) {
  161.                 if (!empty($term)) {
  162.                     $paramName 'term_' $key;
  163.                     
  164.                     // Buscar en cada campo individualmente
  165.                     $orConditions[] = "patient.name LIKE :$paramName";
  166.                     $orConditions[] = "patient.lastName LIKE :$paramName";
  167.                     $orConditions[] = "patient.email LIKE :$paramName";
  168.                     $orConditions[] = "patient.phone LIKE :$paramName";
  169.                     $orConditions[] = "patient.address LIKE :$paramName";
  170.                     
  171.                     $parameters[$paramName] = '%' $term '%';
  172.                 }
  173.             }
  174.             
  175.             if (!empty($orConditions)) {
  176.                 $queryBuilder->andWhere(implode(' OR '$orConditions));
  177.                 foreach ($parameters as $paramName => $paramValue) {
  178.                     $queryBuilder->setParameter($paramName$paramValue);
  179.                 }
  180.             }
  181.         }
  182.     
  183.         // Filtro por proveedor - INCLUYENDO ROLE_ADMIN
  184.         if ($providerId) {
  185.             $queryBuilder
  186.                 ->innerJoin(
  187.                     'App\Entity\User'
  188.                     'provider'
  189.                     'WITH'
  190.                     'provider.id = :providerId AND (provider.roles LIKE :providerRole OR provider.roles LIKE :adminRole)'
  191.                 )
  192.                 ->innerJoin(
  193.                     'provider.patients'
  194.                     'patient_relation'
  195.                 )
  196.                 ->andWhere('patient_relation.id = patient.id')
  197.                 ->setParameter('providerId'$providerId)
  198.                 ->setParameter('providerRole''%ROLE_PROVIDER%')
  199.                 ->setParameter('adminRole''%ROLE_ADMIN%');
  200.         }
  201.     
  202.         $users $paginator->paginate(
  203.             $queryBuilder->getQuery(),
  204.             $request->query->getInt('page'1),
  205.             10
  206.         );
  207.     
  208.         // Obtener proveedores para cada paciente
  209.         $patientsWithProviders = [];
  210.         foreach ($users as $user) {
  211.             $provider $userRepository->findProviderForPatient($user->getId());
  212.             $patientsWithProviders[$user->getId()] = $provider;
  213.         }
  214.     
  215.         // Obtener lista de proveedores para el select - INCLUYENDO ADMINS
  216.         $providers $userRepository->createQueryBuilder('u')
  217.             ->where('u.roles LIKE :providerRole OR u.roles LIKE :adminRole')
  218.             ->setParameter('providerRole''%ROLE_PROVIDER%')
  219.             ->setParameter('adminRole''%ROLE_ADMIN%')
  220.             ->orderBy('u.name''ASC')
  221.             ->getQuery()
  222.             ->getResult();
  223.     
  224.         return $this->render('admin/user/pacientes.html.twig', [
  225.             'users' => $users,
  226.             'patientsWithProviders' => $patientsWithProviders,
  227.             'providers' => $providers,
  228.             'seccion' => 'pacientes',
  229.             'q' => $q,
  230.             'selectedProvider' => $providerId
  231.         ]);
  232.     }
  233.     
  234.     /**
  235.      * @Route("/proveedores", name="_proveedores")
  236.      */
  237.     public function proveedores(Request $requestUserRepository $userRepositoryPaginatorInterface $paginator): Response
  238.     {
  239.         $q $request->query->get('q');
  240.         
  241.         $queryBuilder $userRepository->createQueryBuilder('u')
  242.             ->where('u.roles LIKE :role_provider OR u.roles LIKE :role_admin')
  243.             ->setParameter('role_provider''%ROLE_PROVIDER%')
  244.             ->setParameter('role_admin''%ROLE_ADMIN%')
  245.             ->orderBy('u.id''DESC');
  246.     
  247.         if ($q) {
  248.             $searchTerm trim($q);
  249.             
  250.             $orConditions = [];
  251.             
  252.             // PRIMERO: Búsqueda por la cadena COMPLETA en todos los campos
  253.             $orConditions[] = "u.name LIKE :full_term";
  254.             $orConditions[] = "u.email LIKE :full_term";
  255.             $orConditions[] = "u.phone LIKE :full_term";
  256.             $orConditions[] = "u.address LIKE :full_term";
  257.             $queryBuilder->setParameter('full_term''%' $searchTerm '%');
  258.             
  259.             // SEGUNDO: Solo si el término tiene espacios, buscar por palabras individuales
  260.             if (strpos($searchTerm' ') !== false) {
  261.                 $searchTerms explode(' '$searchTerm);
  262.                 foreach ($searchTerms as $key => $term) {
  263.                     if (!empty($term) && strlen($term) > 1) { // Solo términos con más de 1 carácter
  264.                         $paramName 'term_' $key;
  265.                         $orConditions[] = "u.name LIKE :$paramName";
  266.                         $orConditions[] = "u.email LIKE :$paramName";
  267.                         $orConditions[] = "u.phone LIKE :$paramName";
  268.                         $orConditions[] = "u.address LIKE :$paramName";
  269.                         $queryBuilder->setParameter($paramName'%' $term '%');
  270.                     }
  271.                 }
  272.             }
  273.             
  274.             if (!empty($orConditions)) {
  275.                 $queryBuilder->andWhere(implode(' OR '$orConditions));
  276.             }
  277.         }
  278.     
  279.         $users $paginator->paginate(
  280.             $queryBuilder->getQuery(),
  281.             $request->query->getInt('page'1),
  282.             10
  283.         );
  284.     
  285.         $totalPacientesAsignados 0;
  286.         foreach ($users as $user) {
  287.             $totalPacientesAsignados += $user->getPatients()->count();
  288.         }
  289.     
  290.         return $this->render('admin/user/proveedores.html.twig', [
  291.             'users' => $users,
  292.             'seccion' => 'proveedores',
  293.             'totalPacientesAsignados' => $totalPacientesAsignados,
  294.             'q' => $q
  295.         ]);
  296.     }
  297.     /**
  298.      * @Route("/appointment", name="_appointment")
  299.      */
  300.     public function appointment(Request $requestEntityManagerInterface $emPaginatorInterface $paginator): Response
  301.     {
  302.         $this->denyAccessUnlessGranted('ROLE_ADMIN');
  303.     
  304.         $q $request->query->get('q''');
  305.         $providerId $request->query->get('provider''');
  306.         $status $request->query->get('status''');
  307.         $filter $request->query->get('filter'''); // Nuevo parámetro del submenú
  308.     
  309.         $qb $em->getRepository(Appointment::class)
  310.             ->createQueryBuilder('u')
  311.             ->leftJoin('u.horario''h')->addSelect('h')
  312.             ->leftJoin('u.patient''p')->addSelect('p')
  313.             ->leftJoin('u.provider''pr')->addSelect('pr');
  314.     
  315.         // Filtro del submenú (tiene prioridad sobre el filtro de status individual)
  316.         if ($filter) {
  317.             switch ($filter) {
  318.                 case 'confirmed':
  319.                     $qb->andWhere('u.status = :filterStatus')
  320.                        ->setParameter('filterStatus''confirmada');
  321.                     $activeFilter 'confirmada';
  322.                     break;
  323.                 case 'completed':
  324.                     $qb->andWhere('u.status IN (:completedStatus)')
  325.                        ->setParameter('completedStatus', ['completada''ausente']);
  326.                     $activeFilter 'completada';
  327.                     break;
  328.                 case 'cancelled':
  329.                     // Incluir ambos tipos de cancelación
  330.                     $qb->andWhere('u.status IN (:cancelledStatuses)')
  331.                        ->setParameter('cancelledStatuses', ['cancelada_paciente''cancelada_clinica']);
  332.                     $activeFilter 'cancelada';
  333.                     break;
  334.                 default:
  335.                     $activeFilter 'all';
  336.             }
  337.         } else {
  338.             $activeFilter 'all';
  339.             
  340.             // Filtro por estado individual (solo si no hay filtro del submenú)
  341.             if ($status) {
  342.                 $qb->andWhere('u.status = :status')
  343.                    ->setParameter('status'$status);
  344.             }
  345.         }
  346.     
  347.         // Filtro de búsqueda general
  348.         if ($q) {
  349.             // Dividir la búsqueda en palabras individuales
  350.             $searchTerms explode(' 'trim($q));
  351.             
  352.             $orConditions = [];
  353.             $parameters = [];
  354.             
  355.             foreach ($searchTerms as $key => $term) {
  356.                 if (!empty($term)) {
  357.                     $paramName 'term_' $key;
  358.                     
  359.                     // Buscar en cada campo individualmente
  360.                     $orConditions[] = "u.status LIKE :$paramName";
  361.                     $orConditions[] = "u.notes LIKE :$paramName";
  362.                     $orConditions[] = "p.name LIKE :$paramName";
  363.                     $orConditions[] = "p.lastName LIKE :$paramName";
  364.                     $orConditions[] = "pr.name LIKE :$paramName";
  365.                     $orConditions[] = "pr.lastName LIKE :$paramName";
  366.                     $orConditions[] = "h.fecha LIKE :$paramName";
  367.                     $orConditions[] = "h.hora LIKE :$paramName";
  368.                     $orConditions[] = "CONCAT(p.name, ' ', p.lastName) LIKE :$paramName";
  369.                     $orConditions[] = "CONCAT(pr.name, ' ', pr.lastName) LIKE :$paramName";
  370.                     
  371.                     $parameters[$paramName] = '%' $term '%';
  372.                 }
  373.             }
  374.             
  375.             if (!empty($orConditions)) {
  376.                 $qb->andWhere(implode(' OR '$orConditions));
  377.                 foreach ($parameters as $paramName => $paramValue) {
  378.                     $qb->setParameter($paramName$paramValue);
  379.                 }
  380.             }
  381.         }
  382.     
  383.         // Filtro por proveedor
  384.         if ($providerId) {
  385.             $qb->andWhere('pr.id = :providerId')
  386.                ->setParameter('providerId'$providerId);
  387.         }
  388.     
  389.         // Orden por fecha y hora descendente
  390.         $qb->orderBy('h.fecha''DESC')
  391.            ->addOrderBy('h.hora''DESC');
  392.     
  393.         // Obtener lista de proveedores para el select
  394.         $providers $em->getRepository(User::class)
  395.             ->createQueryBuilder('u')
  396.             ->where('u.roles LIKE :providerRole OR u.roles LIKE :adminRole')
  397.             ->setParameter('providerRole''%ROLE_PROVIDER%')
  398.             ->setParameter('adminRole''%ROLE_ADMIN%')
  399.             ->orderBy('u.name''ASC')
  400.             ->getQuery()
  401.             ->getResult();
  402.     
  403.         // Obtener estados únicos de las citas para el select
  404.         $statuses $em->getRepository(Appointment::class)
  405.             ->createQueryBuilder('a')
  406.             ->select('DISTINCT a.status')
  407.             ->where('a.status IS NOT NULL')
  408.             ->orderBy('a.status''ASC')
  409.             ->getQuery()
  410.             ->getResult();
  411.     
  412.         // Extraer solo los valores de status
  413.         $statusValues array_column($statuses'status');
  414.     
  415.         $appointments $qb->getQuery()->getResult();
  416.     
  417.         // Paginamos el resultado
  418.         $pagination $paginator->paginate(
  419.             $appointments
  420.             $request->query->getInt('page'1), // Página actual
  421.             10// Elementos por página
  422.             [
  423.                 'params' => [
  424.                     'q' => $q,
  425.                     'provider' => $providerId,
  426.                     'status' => $status,
  427.                     'filter' => $filter// Incluir el filtro en la paginación
  428.                     'page' => $request->query->getInt('page'1)
  429.                 ]
  430.             ]
  431.         );
  432.     
  433.         return $this->render('admin/appointment/index.html.twig', [
  434.             'appointments' => $pagination,
  435.             'q' => $q,
  436.             'providers' => $providers,
  437.             'statuses' => $statusValues,
  438.             'selectedProvider' => $providerId,
  439.             'selectedStatus' => $status,
  440.             'active_filter' => $activeFilter// Nuevo parámetro para el template
  441.             'seccion' => 'appointments',
  442.             'titulo_pagina' => 'Gestión de Citas'
  443.         ]);
  444.     }
  445.     /** 
  446.      * @Route("/horario", name="_horario")
  447.      */
  448.     public function horario(HorarioRepository $horarioRepositoryRequest $requestPaginatorInterface $paginator): Response
  449.     {
  450.         $this->denyAccessUnlessGranted('ROLE_ADMIN');
  451.         // Capturamos los parámetros de filtro desde la URL
  452.         $fecha $request->query->get('fecha');
  453.         $hora $request->query->get('hora');
  454.         $estado $request->query->get('estado');
  455.         // Creamos un QueryBuilder para poder filtrar
  456.         $queryBuilder $horarioRepository->createQueryBuilder('h')
  457.             ->orderBy('h.fecha''DESC')
  458.             ->addOrderBy('h.hora''ASC');
  459.         // Filtro por fecha específica
  460.         if ($fecha) {
  461.         try {
  462.             $fechaObj = new \DateTime($fecha);
  463.             $queryBuilder
  464.                 ->andWhere('h.fecha = :fecha')
  465.                 ->setParameter('fecha'$fechaObj->format('Y-m-d'));
  466.         } catch (\Exception $e) {
  467.             // Si la fecha no es válida, ignoramos el filtro
  468.         }
  469.     }
  470.     // Filtro por hora específica - SOLUCIÓN APLICADA
  471.     if ($hora) {
  472.         try {
  473.             $horaObj = new \DateTime($hora);
  474.             $queryBuilder
  475.                 ->andWhere('h.hora = :hora')
  476.                 ->setParameter('hora'$horaObj->format('H:i:s'));
  477.         } catch (\Exception $e) {
  478.             // Si la hora no es válida, ignoramos el filtro
  479.         }
  480.     }
  481.         // Filtro por estado
  482.         if ($estado) {
  483.             $queryBuilder
  484.                 ->andWhere('h.estado = :estado')
  485.                 ->setParameter('estado'$estado);
  486.         }
  487.         // Paginamos el resultado
  488.         $pagination $paginator->paginate(
  489.             $queryBuilder// QueryBuilder con filtros aplicados
  490.             $request->query->getInt('page'1), // Página actual
  491.             10// Elementos por página
  492.             [
  493.                 'params' => [
  494.                     'fecha' => $fecha,
  495.                     'hora' => $hora,
  496.                     'estado' => $estado,
  497.                     'page' => $request->query->getInt('page'1)
  498.                 ]
  499.             ]
  500.         );
  501.         // Obtener estadísticas para las tarjetas
  502.         $totalHorarios $horarioRepository->createQueryBuilder('h')
  503.             ->select('COUNT(h.id)')
  504.             ->getQuery()
  505.             ->getSingleScalarResult();
  506.         $horariosDisponibles $horarioRepository->createQueryBuilder('h')
  507.             ->select('COUNT(h.id)')
  508.             ->where('h.estado = :estado')
  509.             ->setParameter('estado''disponible')
  510.             ->getQuery()
  511.             ->getSingleScalarResult();
  512.         $horariosOcupados $horarioRepository->createQueryBuilder('h')
  513.             ->select('COUNT(h.id)')
  514.             ->where('h.estado = :estado')
  515.             ->setParameter('estado''ocupado')
  516.             ->getQuery()
  517.             ->getSingleScalarResult();
  518.         $horariosCerrados $horarioRepository->createQueryBuilder('h')
  519.             ->select('COUNT(h.id)')
  520.             ->where('h.estado = :estado')
  521.             ->setParameter('estado''cerrado')
  522.             ->getQuery()
  523.             ->getSingleScalarResult();
  524.         return $this->render('admin/horario/index.html.twig', [
  525.             'horarios' => $pagination,
  526.             'fecha' => $fecha,
  527.             'hora' => $hora,
  528.             'estado' => $estado,
  529.             'seccion' => 'horarios',
  530.             'titulo_pagina' => 'Gestión del Horario',
  531.             'total_horarios' => $totalHorarios,
  532.             'horarios_disponibles' => $horariosDisponibles,
  533.             'horarios_ocupados' => $horariosOcupados,
  534.             'horarios_cerrados' => $horariosCerrados
  535.         ]);
  536.     }
  537.      /**
  538.      * @Route("/profile", name="_profile", methods={"GET", "POST"})
  539.      */
  540.     public function profile(Request $requestEntityManagerInterface $entityManager): Response
  541.     {
  542.         $this->denyAccessUnlessGranted('ROLE_ADMIN');
  543.         
  544.         /** @var User $user */
  545.         $user $this->getUser();
  546.         
  547.         $form $this->createForm(AdminProfileType::class, $user);
  548.         $form->handleRequest($request);
  549.         
  550.         
  551.         if ($form->isSubmitted() && $form->isValid()) {
  552.             try {
  553.                 $entityManager->flush();
  554.                 
  555.                 $this->addFlash('success''Perfil actualizado correctamente.');
  556.                 return $this->redirectToRoute('admin_profile');
  557.             } catch (\Exception $e) {
  558.                 $this->addFlash('error''Error al actualizar el perfil: ' $e->getMessage());
  559.             }
  560.         }
  561.         
  562.         return $this->render('admin/profile/index.html.twig', [
  563.             'user' => $user,
  564.             'form' => $form->createView(),
  565.             'titulo_pagina' => 'Mi Perfil - Administrador'
  566.         ]);
  567.     }
  568.     /**
  569.      * @Route("/change-email", name="_profile_change_email", methods={"GET", "POST"})
  570.      */
  571.     public function changeEmail(
  572.         Request $request
  573.         EntityManagerInterface $entityManager
  574.         UserPasswordHasherInterface $passwordHasher
  575.     ): Response {
  576.         $this->denyAccessUnlessGranted('ROLE_ADMIN');
  577.         
  578.         /** @var User $user */
  579.         $user $this->getUser();
  580.         
  581.         $form $this->createForm(ChangeEmailFormType::class);
  582.         $form->handleRequest($request);
  583.         if ($form->isSubmitted() && $form->isValid()) {
  584.             $currentPassword $form->get('currentPassword')->getData();
  585.             $newEmail $form->get('newEmail')->getData();
  586.             $confirmEmail $form->get('confirmEmail')->getData();
  587.             
  588.             $hasErrors false;
  589.             
  590.             // Verificar contraseña actual
  591.             if (!$passwordHasher->isPasswordValid($user$currentPassword)) {
  592.                 $form->get('currentPassword')->addError(new FormError('La contraseña actual es incorrecta'));
  593.                 $hasErrors true;
  594.             }
  595.             
  596.             // Verificar que los emails coincidan
  597.             if ($newEmail !== $confirmEmail) {
  598.                 $form->get('confirmEmail')->addError(new FormError('Los correos electrónicos no coinciden'));
  599.                 $hasErrors true;
  600.             }
  601.             
  602.             // Verificar si el email ya existe
  603.             if (!$hasErrors) {
  604.                 $existingUser $entityManager->getRepository(User::class)->findOneBy(['email' => $newEmail]);
  605.                 if ($existingUser && $existingUser->getId() !== $user->getId()) {
  606.                     $form->get('newEmail')->addError(new FormError('Este email ya está en uso por otro usuario'));
  607.                     $hasErrors true;
  608.                 }
  609.             }
  610.             
  611.             if (!$hasErrors) {
  612.                 try {
  613.                     $user->setEmail($newEmail);
  614.                     $entityManager->flush();
  615.                     
  616.                     $this->addFlash('success''Email actualizado correctamente.');
  617.                     return $this->redirectToRoute('admin_profile');
  618.                 } catch (\Exception $e) {
  619.                     $this->addFlash('error''Error al actualizar el email: ' $e->getMessage());
  620.                 }
  621.             }
  622.         }
  623.         return $this->render('admin/profile/change_email.html.twig', [
  624.             'form' => $form->createView(),
  625.             'titulo_pagina' => 'Cambiar Correo Electrónico - Administrador'
  626.         ]);
  627.     }
  628.     /**
  629.      * @Route("/change-password", name="_profile_change_password", methods={"GET", "POST"})
  630.      */
  631.     public function changePassword(
  632.         Request $request,
  633.         UserPasswordHasherInterface $passwordHasher,
  634.         EntityManagerInterface $entityManager
  635.     ): Response {
  636.         $this->denyAccessUnlessGranted('ROLE_ADMIN');
  637.         /** @var User $user */
  638.         $user $this->getUser();
  639.         
  640.         $form $this->createForm(ChangePasswordType::class);
  641.         $form->handleRequest($request);
  642.         if ($form->isSubmitted() && $form->isValid()) {
  643.             $currentPassword $form->get('currentPassword')->getData();
  644.             $newPassword $form->get('newPassword')->getData();
  645.             // Verificar contraseña actual
  646.             if (!$passwordHasher->isPasswordValid($user$currentPassword)) {
  647.                 $form->get('currentPassword')->addError(
  648.                     new FormError('La contraseña actual es incorrecta')
  649.                 );
  650.             } else {
  651.                 try {
  652.                     // Hashear y guardar nueva contraseña
  653.                     $hashedPassword $passwordHasher->hashPassword($user$newPassword);
  654.                     $user->setPassword($hashedPassword);
  655.                     
  656.                     $entityManager->flush();
  657.                     $this->addFlash('success''Contraseña actualizada correctamente.');
  658.                     return $this->redirectToRoute('admin_profile');
  659.                 } catch (\Exception $e) {
  660.                     $this->addFlash('error''Error al actualizar la contraseña: ' $e->getMessage());
  661.                 }
  662.             }
  663.         }
  664.         return $this->render('admin/profile/change_password.html.twig', [
  665.             'form' => $form->createView(),
  666.             'titulo_pagina' => 'Cambiar Contraseña - Administrador'
  667.         ]);
  668.     }
  669. }