<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\UserType;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use App\Entity\Appointment;
use Symfony\Component\HttpFoundation\JsonResponse;
use App\Form\ProveedorType;
use App\Service\ProviderNotifier;
class UserController extends AbstractController
{
private $passwordHasher;
public function __construct(UserPasswordHasherInterface $passwordHasher)
{
$this->passwordHasher = $passwordHasher;
}
/*****************************************************************
* PACIENTES - ADMIN
*****************************************************************/
/**
* @Route("/admin/pacientes/nuevo", name="admin_paciente_new", methods={"GET", "POST"})
*/
public function nuevoPaciente(Request $request, UserRepository $userRepository, EntityManagerInterface $entityManager): Response
{
$user = new User();
$form = $this->createForm(UserType::class, $user, [
'validation_groups' => ['Default'] // Solo validaciones básicas, sin password
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// Generar contraseña aleatoria
$randomPassword = $this->generateRandomPassword();
$user->setPassword($this->passwordHasher->hashPassword($user, $randomPassword));
// Asignar rol de paciente por defecto
$user->setRoles(['ROLE_CLIENT']);
try {
if ($this->isGranted('ROLE_ADMIN')) {
// Buscar al admin por rol, ordenando por ID descendente para obtener el último
$admin = $entityManager->getRepository(User::class)
->createQueryBuilder('u')
->andWhere('u.roles LIKE :role')
->setParameter('role', '%"ROLE_ADMIN"%')
->orderBy('u.id', 'DESC') // Ordenar por ID descendente
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
if ($admin) {
$admin->addPatient($user);
$entityManager->persist($admin);
}
} elseif ($this->isGranted('ROLE_PROVIDER')) {
/** @var User $provider */
$provider = $this->getUser();
$provider->addPatient($user);
$entityManager->persist($provider);
}
$userRepository->add($user, true);
// Mostrar la contraseña generada al administrador
$this->addFlash('success', 'Paciente creado correctamente.');
return $this->redirectToRoute('admin_pacientes');
} catch (UniqueConstraintViolationException $e) {
$this->addFlash('error', 'Ya existe una cuenta con este correo electrónico.');
}
}
return $this->render('admin/user/form.html.twig', [
'user' => $user,
'form' => $form->createView(),
'modo' => 'new',
'tipo' => 'paciente',
'seccion' => 'pacientes',
'titulo_pagina' => 'Nuevo Paciente'
]);
}
/**
* Genera una contraseña aleatoria segura
*/
private function generateRandomPassword(int $length = 12): string
{
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*';
$charactersLength = strlen($characters);
$randomPassword = '';
for ($i = 0; $i < $length; $i++) {
$randomPassword .= $characters[random_int(0, $charactersLength - 1)];
}
return $randomPassword;
}
/**
* @Route("/admin/pacientes/editar/{id}", name="admin_paciente_edit", methods={"GET", "POST"})
*/
public function editarPaciente(Request $request, User $user, UserRepository $userRepository, EntityManagerInterface $entityManager): Response
{
// Verificar que el usuario sea un paciente
if (!in_array('ROLE_CLIENT', $user->getRoles())) {
$this->addFlash('error', 'El usuario no es un paciente.');
return $this->redirectToRoute('admin_pacientes');
}
// Guardar el email original para verificar cambios
$emailOriginal = $user->getEmail();
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$randomPassword = $this->generateRandomPassword();
$user->setPassword($this->passwordHasher->hashPassword($user, $randomPassword));
try {
// Si el usuario actual es admin, actualizar la relación con el último admin
if ($this->isGranted('ROLE_ADMIN')) {
// Buscar al admin por rol, ordenando por ID descendente para obtener el último
$admin = $entityManager->getRepository(User::class)
->createQueryBuilder('u')
->andWhere('u.roles LIKE :role')
->setParameter('role', '%"ROLE_ADMIN"%')
->orderBy('u.id', 'DESC') // Ordenar por ID descendente
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
if ($admin) {
// Remover de todos los admins anteriores
$allAdmins = $entityManager->getRepository(User::class)
->createQueryBuilder('u')
->andWhere('u.roles LIKE :role')
->setParameter('role', '%"ROLE_ADMIN"%')
->getQuery()
->getResult();
foreach ($allAdmins as $previousAdmin) {
$previousAdmin->removePatient($user);
$entityManager->persist($previousAdmin);
}
// Agregar al último admin
$admin->addPatient($user);
$entityManager->persist($admin);
}
}
$userRepository->add($user, true);
$this->addFlash('success', 'Paciente actualizado correctamente.');
return $this->redirectToRoute('admin_pacientes');
} catch (UniqueConstraintViolationException $e) {
$this->addFlash('error', 'Ya existe una cuenta con este correo electrónico.');
} catch (\Exception $e) {
$this->addFlash('error', 'Error al actualizar el paciente: ' . $e->getMessage());
}
}
return $this->render('admin/user/form.html.twig', [
'user' => $user,
'form' => $form->createView(),
'modo' => 'edit',
'tipo' => 'paciente',
'seccion' => 'pacientes',
'titulo_pagina' => 'Editar Paciente'
]);
}
/**
* @Route("/admin/paciente/{id}/check-citas", name="admin_paciente_check_citas", methods={"GET"})
*/
public function checkCitasPaciente(User $paciente, EntityManagerInterface $entityManager): JsonResponse
{
$citasCount = $entityManager->getRepository(Appointment::class)
->createQueryBuilder('a')
->select('COUNT(a.id)')
->where('a.patient = :patient')
->setParameter('patient', $paciente)
->getQuery()
->getSingleScalarResult();
return $this->json([
'hasCitas' => $citasCount > 0,
'citasCount' => $citasCount
]);
}
/**
* @Route("/admin/pacientes/eliminar/{id}", name="admin_paciente_delete", methods={"POST"})
*/
public function eliminarPaciente(User $paciente, EntityManagerInterface $entityManager): Response
{
// Verificar si tiene citas
$citasCount = $entityManager->getRepository(Appointment::class)
->createQueryBuilder('a')
->select('COUNT(a.id)')
->where('a.patient = :patient')
->setParameter('patient', $paciente)
->getQuery()
->getSingleScalarResult();
try {
// Si tiene citas, eliminarlas primero
if ($citasCount > 0) {
$appointments = $entityManager->getRepository(Appointment::class)
->createQueryBuilder('a')
->where('a.patient = :patient')
->setParameter('patient', $paciente)
->getQuery()
->getResult();
foreach ($appointments as $appointment) {
$entityManager->remove($appointment);
}
}
// Buscar todos los proveedores que tienen a este paciente asignado
$providers = $entityManager->getRepository(User::class)
->createQueryBuilder('u')
->innerJoin('u.patients', 'p')
->where('p = :patient')
->setParameter('patient', $paciente)
->getQuery()
->getResult();
// Remover al paciente de la lista de pacientes de cada proveedor
foreach ($providers as $provider) {
$provider->removePatient($paciente); // Necesitas este método en tu entidad User
}
// Finalmente eliminar el usuario
$entityManager->remove($paciente);
$entityManager->flush();
if ($citasCount > 0) {
$this->addFlash('warning', "Paciente eliminado junto con {$citasCount} cita(s) asociada(s)");
} else {
$this->addFlash('success', 'Paciente eliminado correctamente');
}
} catch (\Exception $e) {
$this->addFlash('error', 'Error al eliminar el paciente: ' . $e->getMessage());
}
return $this->redirectToRoute('admin_pacientes');
}
/*****************************************************************
* PROVEEDORES - ADMIN
*****************************************************************/
/**
* @Route("/admin/proveedores/nuevo", name="admin_proveedor_new", methods={"GET", "POST"})
*/
public function nuevoProveedor(
Request $request,
UserRepository $userRepository,
EntityManagerInterface $entityManager,
ProviderNotifier $providerNotifier
): Response
{
$user = new User();
// Para proveedores, establece un lastName por defecto
$user->setLastName('Clínica');
$form = $this->createForm(ProveedorType::class, $user, [
'es_nuevo' => true,
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// Verificar que plainPassword no sea null
if (!$user->getPlainPassword()) {
$this->addFlash('error', 'La contraseña es obligatoria.');
return $this->render('admin/user/proveedor_form.html.twig', [
'user' => $user,
'form' => $form->createView(),
'modo' => 'new',
'tipo' => 'proveedor',
'seccion' => 'proveedores',
'titulo_pagina' => 'Nuevo Proveedor'
]);
}
$user->setPassword($this->passwordHasher->hashPassword($user, $user->getPlainPassword()));
// Asegurar que el lastName esté establecido para proveedores
if (empty($user->getLastName())) {
$user->setLastName('Clínica');
}
// Asignar rol de proveedor por defecto
$user->setRoles(['ROLE_PROVIDER']);
$user->setEntityType('clinica'); // Marcar como clínica
try {
if ($this->isGranted('ROLE_ADMIN')) {
// Buscar al admin por rol, ordenando por ID descendente para obtener el último
$admin = $entityManager->getRepository(User::class)
->createQueryBuilder('u')
->andWhere('u.roles LIKE :role')
->setParameter('role', '%"ROLE_ADMIN"%')
->orderBy('u.id', 'DESC') // ← ORDENAR POR ID DESCENDENTE
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
if ($admin) {
$admin->addPatient($user);
$entityManager->persist($admin);
}
} elseif ($this->isGranted('ROLE_PROVIDER')) {
/** @var User $provider */
$provider = $this->getUser();
$provider->addPatient($user);
$entityManager->persist($provider);
}
$userRepository->add($user, true);
// ✅ CORRECTO: Usar el servicio inyectado
$language = $request->getSession()->get('_locale', 'es');
$providerNotifier->sendProviderNotification($user, 'create', $language);
$this->addFlash('success', 'Proveedor creado correctamente.');
return $this->redirectToRoute('admin_proveedores');
} catch (UniqueConstraintViolationException $e) {
$this->addFlash('error', 'Ya existe una cuenta con este correo electrónico.');
} catch (\Exception $e) {
$this->addFlash('error', 'Error al crear el proveedor: ' . $e->getMessage());
}
}
return $this->render('admin/user/proveedor_form.html.twig', [
'user' => $user,
'form' => $form->createView(),
'modo' => 'new',
'tipo' => 'proveedor',
'seccion' => 'proveedores',
'titulo_pagina' => 'Nuevo Proveedor'
]);
}
/**
* @Route("/admin/proveedores/editar/{id}", name="admin_proveedor_edit", methods={"GET", "POST"})
*/
public function editarProveedor(Request $request, User $user, UserRepository $userRepository, EntityManagerInterface $entityManager): Response
{
// Verificar que el usuario sea un proveedor
if (!in_array('ROLE_PROVIDER', $user->getRoles())) {
$this->addFlash('error', 'El usuario no es un proveedor.');
return $this->redirectToRoute('admin_proveedores');
}
// Para proveedores, establece un lastName por defecto si está vacío
if (empty($user->getLastName())) {
$user->setLastName('Clínica');
}
$form = $this->createForm(ProveedorType::class, $user, [
'es_nuevo' => false,
]);
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) {
// Si se cambió la contraseña, hashearla
if ($user->getPlainPassword()) {
$hashedPassword = $this->passwordHasher->hashPassword($user, $user->getPlainPassword());
$user->setPassword($hashedPassword);
}
try {
// Si el usuario actual es admin, actualizar la relación con el último admin
if ($this->isGranted('ROLE_ADMIN')) {
// Buscar al admin por rol, ordenando por ID descendente para obtener el último
$admin = $entityManager->getRepository(User::class)
->createQueryBuilder('u')
->andWhere('u.roles LIKE :role')
->setParameter('role', '%"ROLE_ADMIN"%')
->orderBy('u.id', 'DESC') // ← ORDENAR POR ID DESCENDENTE
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
if ($admin) {
// Remover de todos los admins anteriores
$allAdmins = $entityManager->getRepository(User::class)
->createQueryBuilder('u')
->andWhere('u.roles LIKE :role')
->setParameter('role', '%"ROLE_ADMIN"%')
->getQuery()
->getResult();
foreach ($allAdmins as $previousAdmin) {
$previousAdmin->removePatient($user);
$entityManager->persist($previousAdmin);
}
// Agregar al último admin
$admin->addPatient($user);
$entityManager->persist($admin);
}
}
$entityManager->persist($user);
dump("aki1 - Persist realizado");
$entityManager->flush();
dump("aki2 - Flush exitoso");
$this->addFlash('success', 'Proveedor actualizado correctamente.');
return $this->redirectToRoute('admin_proveedores');
} catch (\Doctrine\DBAL\Exception\DriverException $e) {
dump('Error de driver:', $e->getMessage());
$this->addFlash('error', 'Error de base de datos: ' . $e->getMessage());
} catch (\Doctrine\DBAL\Exception\ConstraintViolationException $e) {
dump('Error de constraint:', $e->getMessage());
$this->addFlash('error', 'Error de restricción: ' . $e->getMessage());
} catch (\Doctrine\ORM\ORMException $e) {
dump('Error ORM:', $e->getMessage());
$this->addFlash('error', 'Error ORM: ' . $e->getMessage());
} catch (UniqueConstraintViolationException $e) {
$this->addFlash('error', 'Ya existe una cuenta con este correo electrónico.');
} catch (\Exception $e) {
dump('Error general:', $e->getMessage(), $e->getTraceAsString());
$this->addFlash('error', 'Error al actualizar el proveedor: ' . $e->getMessage());
}
} else {
$this->addFlash('error', 'Por favor, corrige los errores en el formulario.');
}
}
return $this->render('admin/user/proveedor_form.html.twig', [
'user' => $user,
'form' => $form->createView(),
'modo' => 'edit',
'tipo' => 'proveedor',
'seccion' => 'proveedores',
'titulo_pagina' => 'Editar Proveedor'
]);
}
/**
* @Route("/admin/proveedores/eliminar/{id}", name="admin_proveedor_delete", methods={"POST"})
*/
public function eliminarProveedor(User $proveedor, EntityManagerInterface $entityManager): Response
{
// Verificar si tiene citas
$citasCount = $entityManager->getRepository(Appointment::class)
->createQueryBuilder('a')
->select('COUNT(a.id)')
->where('a.patient = :patient')
->setParameter('patient', $proveedor)
->getQuery()
->getSingleScalarResult();
try {
// Si tiene citas, eliminarlas primero
if ($citasCount > 0) {
$appointments = $entityManager->getRepository(Appointment::class)
->createQueryBuilder('a')
->where('a.patient = :patient')
->setParameter('patient', $proveedor)
->getQuery()
->getResult();
foreach ($appointments as $appointment) {
$entityManager->remove($appointment);
}
}
// Buscar todos los proveedores que tienen a este proveedor asignado
$providers = $entityManager->getRepository(User::class)
->createQueryBuilder('u')
->innerJoin('u.patients', 'p')
->where('p = :patient')
->setParameter('patient', $proveedor)
->getQuery()
->getResult();
// Remover al proveedor de la lista de proveedors de cada proveedor
foreach ($providers as $provider) {
$provider->removePatient($proveedor); // Necesitas este método en tu entidad User
}
// Finalmente eliminar el usuario
$entityManager->remove($proveedor);
$entityManager->flush();
if ($citasCount > 0) {
$this->addFlash('warning', "Proveedor eliminado junto con {$citasCount} cita(s) asociada(s)");
} else {
$this->addFlash('success', 'Proveedor eliminado correctamente');
}
} catch (\Exception $e) {
$this->addFlash('error', 'Error al eliminar el proveedor: ' . $e->getMessage());
}
return $this->redirectToRoute('admin_proveedores');
}
/*****************************************************************
* PACIENTES - PROVEEDOR
*****************************************************************/
/**
* @Route("/proveedor/pacientes/nuevo", name="provider_patient_new", methods={"GET", "POST"})
*/
public function newPatient(Request $request, UserRepository $userRepository, EntityManagerInterface $entityManager): Response
{
$this->denyAccessUnlessGranted('ROLE_PROVIDER');
$user = new User();
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$randomPassword = $this->generateRandomPassword();
$user->setPassword($this->passwordHasher->hashPassword($user, $randomPassword));
// Asignar rol de paciente por defecto
$user->setRoles(['ROLE_CLIENT']);
try {
/** @var User $provider */
$provider = $this->getUser();
$provider->addPatient($user);
$entityManager->persist($provider);
$userRepository->add($user, true);
$this->addFlash('success', 'Paciente creado correctamente.');
return $this->redirectToRoute('provider_patients');
} catch (UniqueConstraintViolationException $e) {
$this->addFlash('error', 'Ya existe una cuenta con este correo electrónico.');
}
}
return $this->render('provider/patients/form.html.twig', [
'user' => $user,
'form' => $form->createView(),
'modo' => 'new',
'seccion' => 'patients'
]);
}
/**
* @Route("/proveedor/pacientes/editar/{id}", name="provider_patient_edit", methods={"GET", "POST"})
*/
public function editPatient(Request $request, User $user, UserRepository $userRepository): Response
{
$this->denyAccessUnlessGranted('ROLE_PROVIDER');
/** @var User $provider */
$provider = $this->getUser();
// Verificar que el paciente pertenece a este proveedor
if (!$provider->getPatients()->contains($user)) {
throw $this->createAccessDeniedException('No tienes permisos para editar este paciente.');
}
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
try {
$randomPassword = $this->generateRandomPassword();
$user->setPassword($this->passwordHasher->hashPassword($user, $randomPassword));
$userRepository->add($user, true);
$this->addFlash('success', 'Paciente actualizado correctamente.');
return $this->redirectToRoute('provider_patients');
} catch (UniqueConstraintViolationException $e) {
$this->addFlash('error', 'Ya existe una cuenta con este correo electrónico.');
} catch (\Exception $e) {
$this->addFlash('error', 'Error al actualizar el paciente.');
}
}
return $this->render('provider/patients/form.html.twig', [
'user' => $user,
'form' => $form->createView(),
'modo' => 'edit',
'seccion' => 'patients'
]);
}
/**
* @Route("/proveedor/pacientes/eliminar/{id}", name="provider_patient_delete", methods={"POST"})
*/
public function deletePatient(Request $request, User $patient, EntityManagerInterface $entityManager): Response
{
$this->denyAccessUnlessGranted('ROLE_PROVIDER');
/** @var User $provider */
$provider = $this->getUser();
// Verificar que el paciente pertenece a este proveedor
if (!$provider->getPatients()->contains($patient)) {
throw $this->createAccessDeniedException('No tienes permisos para eliminar este paciente.');
}
// Verificar el token CSRF para seguridad
if (!$this->isCsrfTokenValid('delete-patient-'.$patient->getId(), $request->request->get('_token'))) {
$this->addFlash('error', 'Token de seguridad inválido.');
return $this->redirectToRoute('provider_patients');
}
try {
// Eliminar el paciente
$entityManager->remove($patient);
$entityManager->flush();
$this->addFlash('success', 'Paciente eliminado correctamente.');
} catch (\Exception $e) {
$this->addFlash('error', 'Error al eliminar el paciente.');
}
return $this->redirectToRoute('provider_patients');
}
}