src/Controller/AgendaController.php line 481

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Embajadores;
  4. use App\Entity\Agenda;
  5. use App\Entity\ColegioParticipante;
  6. use App\Entity\EmbajadorParticipante;
  7. use App\Entity\EstadoAgenda;
  8. use App\Entity\EstadoSolicitud;
  9. use App\Entity\Solicitud;
  10. use App\Entity\TipoAccion;
  11. use App\Entity\TipoAgenda;
  12. use App\Form\AgendaType;
  13. use App\Form\Agenda2Type;
  14. use App\Repository\AgendaRepository;
  15. use App\Repository\ColegioParticipanteRepository;
  16. use App\Repository\EmbajadorParticipanteRepository;
  17. use App\Repository\SolicitudRepository;
  18. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  19. use Symfony\Component\Form\Extension\Core\Type\TextareaType;
  20. use Symfony\Component\HttpFoundation\Request;
  21. use Symfony\Component\HttpFoundation\Response;
  22. use Symfony\Component\Routing\Annotation\Route;
  23. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  24. use App\Service\Mensajes;
  25. use App\Entity\Evaluacioncolegio;
  26. use App\Repository\EvaluacioncolegioRepository;
  27. use App\Repository\EvaluacionembajadorRepository;
  28. use App\Entity\Evaluacionembajador;
  29. use PhpOffice\PhpSpreadsheet\IOFactory;
  30. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  31. use Symfony\Component\HttpFoundation\StreamedResponse;
  32. use Symfony\Component\HttpFoundation\File\UploadedFile;
  33. use App\Entity\Colegio;
  34. use App\Entity\UnidadAcademicaPostulacion;
  35. use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
  36. use App\Service\QRConfirmacionService;
  37. use App\Service\MassEmailService;
  38. use App\Service\EmailTemplateService;
  39. use Psr\Log\LoggerInterface;
  40. /**
  41.  * @Route("/agenda")
  42.  */
  43. class AgendaController extends AbstractController
  44. {
  45.     /**
  46.      * @Route("/", name="agenda_index", methods={"GET"})
  47.      */
  48.     public function index(AgendaRepository $agendaRepository): Response
  49.     {
  50.         $entityManager $this->getDoctrine()->getManager();
  51.         // Buscar estados tanto en minúsculas como con mayúscula inicial
  52.         $estados_minusculas = array("agendada""realizada""cancelada");
  53.         $estados_mayusculas = array("Agendada""Realizada""Cancelada");
  54.         
  55.         $estados $entityManager->getRepository(EstadoAgenda::class)
  56.             ->createQueryBuilder('e')
  57.             ->where('e.nombre IN (:nombres)')
  58.             ->setParameter('nombres'array_merge($estados_minusculas$estados_mayusculas))
  59.             ->getQuery()
  60.             ->getResult();
  61.         // Usar QueryBuilder para consulta compleja
  62.         $agendas $agendaRepository->createQueryBuilder('a')
  63.             ->where('a.estadoAgenda IN (:estados)')
  64.             ->andWhere('(a.archivada IS NULL OR a.archivada = :false)')
  65.             ->setParameter('estados'$estados)
  66.             ->setParameter('false'false)
  67.             ->orderBy('a.id''DESC')
  68.             ->getQuery()
  69.             ->getResult();
  70.         return $this->render('agenda/index.html.twig', [
  71.             'agendas' => $agendas,
  72.         ]);
  73.     }
  74.     /**
  75.      * @Route("/export/{anio}", name="agenda_export", methods={"GET"}, defaults={"anio"="todos"})
  76.      */
  77.     public function exportAgenda(string $anio): Response
  78.     {
  79.         $this->denyAccessUnlessGranted('ROLE_AYUDANTE');
  80.         $conn $this->getDoctrine()->getConnection();
  81.         $conn->executeStatement('SET NAMES utf8mb4');
  82.         $whereSql '';
  83.         $params = [];
  84.         if ($anio !== 'todos') {
  85.             $whereSql ' WHERE a.fecha BETWEEN :inicio AND :fin';
  86.             $params['inicio'] = $anio '-01-01';
  87.             $params['fin']    = $anio '-12-31';
  88.         }
  89.         // Agendas base (include encargado fallback fields)
  90.         $agendas $conn->fetchAllAssociative(
  91.                 'SELECT a.id, a.fecha, a.hasta, a.direccion, a.lugar, a.n_alumnos, a.curso, a.observacion,
  92.                     a.taxi, a.horariotaxi, a.encargado AS encargado, a.telefonoencargado AS telefono_encargado,
  93.                     ta.nombre AS tipo, ea.nombre AS estado,
  94.                     s.nombre_encargado AS solicitante_nombre, s.correo_encargado AS solicitante_correo
  95.              FROM agenda a
  96.              LEFT JOIN tipo_agenda ta ON ta.id = a.tipo_agenda_id
  97.              LEFT JOIN estado_agenda ea ON ea.id = a.estado_agenda_id
  98.              LEFT JOIN solicitud s ON s.id = a.solicitud_id'
  99.             $whereSql .
  100.             ' ORDER BY a.fecha ASC',
  101.             $params
  102.         );
  103.         if (empty($agendas)) {
  104.             $this->addFlash('warning''No hay agendas para exportar.');
  105.             return $this->redirectToRoute('agenda_index');
  106.         }
  107.         $ids array_column($agendas'id');
  108.         $placeholders implode(','array_fill(0count($ids), '?'));
  109.         // Colegios por agenda
  110.         $colegiosRows $conn->fetchAllAssociative(
  111.             "SELECT cp.agenda_id, c.nombre FROM colegio_participante cp
  112.              LEFT JOIN colegio c ON c.id = cp.colegio_id
  113.              WHERE cp.agenda_id IN ($placeholders)",
  114.             array_values($ids)
  115.         );
  116.         $colegiosByAgenda = [];
  117.         foreach ($colegiosRows as $r) {
  118.             $colegiosByAgenda[$r['agenda_id']][] = $r['nombre'];
  119.         }
  120.         // Embajadores por agenda
  121.         $embRows $conn->fetchAllAssociative(
  122.             "SELECT ep.agenda_id, e.nombre FROM embajador_participante ep
  123.              LEFT JOIN embajadores e ON e.id = ep.embajadores_id
  124.              WHERE ep.agenda_id IN ($placeholders)",
  125.             array_values($ids)
  126.         );
  127.         $embByAgenda = [];
  128.         foreach ($embRows as $r) {
  129.             $embByAgenda[$r['agenda_id']][] = $r['nombre'];
  130.         }
  131.         $spreadsheet = new Spreadsheet();
  132.         $sheet $spreadsheet->getActiveSheet();
  133.         $headers = ['ID''Fecha''Hora inicio''Hora fin''Tipo Actividad''Colegio(s)''Contacto nombre''Contacto correo''Dirección''Lugar''Estado''N° Alumnos''Curso''Embajadores''Taxi''Horario Taxi''Observación'];
  134.         foreach ($headers as $col => $h) {
  135.             $sheet->setCellValueByColumnAndRow($col 11$h);
  136.             $sheet->getColumnDimensionByColumn($col 1)->setWidth(22);
  137.         }
  138.         $sheet->getStyle('A1:Q1')->getFont()->setBold(true);
  139.         $row 2;
  140.         // Safe date/time formatters
  141.         $formatDate = function ($val) {
  142.             if (empty($val)) {
  143.                 return '';
  144.             }
  145.             try {
  146.                 return (new \DateTime($val))->format('d/m/Y');
  147.             } catch (\Exception $e) {
  148.                 return (string) $val;
  149.             }
  150.         };
  151.         $formatTime = function ($val) {
  152.             if (empty($val)) {
  153.                 return '';
  154.             }
  155.             try {
  156.                 return (new \DateTime($val))->format('H:i');
  157.             } catch (\Exception $e) {
  158.                 return '';
  159.             }
  160.         };
  161.         foreach ($agendas as $a) {
  162.             $sheet->setCellValueByColumnAndRow(1,  $row$a['id']);
  163.             $sheet->setCellValueByColumnAndRow(2,  $row$formatDate($a['fecha'] ?? ''));
  164.             $sheet->setCellValueByColumnAndRow(3,  $row$formatTime($a['fecha'] ?? ''));
  165.             $sheet->setCellValueByColumnAndRow(4,  $row$formatTime($a['hasta'] ?? ''));
  166.             $sheet->setCellValueByColumnAndRow(5,  $row$a['tipo'] ?? '');
  167.             $sheet->setCellValueByColumnAndRow(6,  $rowimplode(', '$colegiosByAgenda[$a['id']] ?? []));
  168.             // Prefer solicitud contact fields; fall back to agenda.encargado
  169.             $contactNombre $a['solicitante_nombre'] ?? '';
  170.             if (empty($contactNombre) && !empty($a['encargado'])) {
  171.                 $contactNombre $a['encargado'];
  172.             }
  173.             $sheet->setCellValueByColumnAndRow(7,  $row$contactNombre);
  174.             $sheet->setCellValueByColumnAndRow(8,  $row$a['solicitante_correo'] ?? '');
  175.             $sheet->setCellValueByColumnAndRow(9,  $row$a['direccion'] ?? '');
  176.             $sheet->setCellValueByColumnAndRow(10$row$a['lugar'] ?? '');
  177.             $sheet->setCellValueByColumnAndRow(11$row$a['estado'] ?? '');
  178.             $sheet->setCellValueByColumnAndRow(12$row$a['n_alumnos'] ?? '');
  179.             $sheet->setCellValueByColumnAndRow(13$row$a['curso'] ?? '');
  180.             $sheet->setCellValueByColumnAndRow(14$rowimplode(', '$embByAgenda[$a['id']] ?? []));
  181.             $sheet->setCellValueByColumnAndRow(15$row, !empty($a['taxi']) ? 'Sí' 'No');
  182.             $sheet->setCellValueByColumnAndRow(16$row$a['horariotaxi'] ?? '');
  183.             $sheet->setCellValueByColumnAndRow(17$row$a['observacion'] ?? '');
  184.             $row++;
  185.         }
  186.         $filename 'agendas_' . ($anio === 'todos' 'todas' $anio) . '.xlsx';
  187.         $writer IOFactory::createWriter($spreadsheet'Xlsx');
  188.         $response = new StreamedResponse(function () use ($writer) { $writer->save('php://output'); });
  189.         $response->headers->set('Content-Type''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
  190.         $response->headers->set('Content-Disposition''attachment; filename="' $filename '"');
  191.         return $response;
  192.     }
  193.     /**
  194.      * @Route("/bulk-change-estado", name="agenda_bulk_change_estado", methods={"POST"})
  195.      */
  196.     public function bulkChangeEstado(Request $request): Response
  197.     {
  198.         $entityManager $this->getDoctrine()->getManager();
  199.         
  200.         $agendaIds $request->request->get('agenda_ids', []);
  201.         $nuevoEstadoNombre $request->request->get('nuevo_estado');
  202.         
  203.         if (empty($agendaIds) || empty($nuevoEstadoNombre)) {
  204.             $this->addFlash('error''No se seleccionaron agendas o no se especificó un estado.');
  205.             return $this->redirectToRoute('agenda_index');
  206.         }
  207.         
  208.         // Buscar el estado por su nombre
  209.         $nuevoEstado $entityManager->getRepository(EstadoAgenda::class)->findOneBy(['nombre' => $nuevoEstadoNombre]);
  210.         
  211.         if (!$nuevoEstado) {
  212.             $this->addFlash('error''El estado seleccionado no es válido.');
  213.             return $this->redirectToRoute('agenda_index');
  214.         }
  215.         
  216.         $contador 0;
  217.         foreach ($agendaIds as $agendaId) {
  218.             $agenda $entityManager->getRepository(Agenda::class)->find($agendaId);
  219.             if ($agenda) {
  220.                 $agenda->setEstadoAgenda($nuevoEstado);
  221.                 $entityManager->persist($agenda);
  222.                 $contador++;
  223.             }
  224.         }
  225.         
  226.         $entityManager->flush();
  227.         
  228.         $this->addFlash('success'"Se actualizaron $contador agenda(s) al estado: $nuevoEstadoNombre");
  229.         
  230.         return $this->redirectToRoute('agenda_index');
  231.     }
  232.     /**
  233.      * @Route("/convocatoria_semana", name="convocatoria_semana", methods={"GET"})
  234.      */
  235.     public function convocatoria_semana(): Response
  236.     {
  237.         $entityManager $this->getDoctrine()->getManager();
  238.         $hoy = new \DateTime('now');
  239.         $siete_dias_anteriores = clone $hoy;
  240.         $siete_dias_anteriores->modify('-7 days');
  241.         $repository $entityManager->getRepository(Agenda::class);
  242.         $query $repository->createQueryBuilder('a')
  243.         ->where("a.fecha >= :siete_dias_anteriores")
  244.         ->andWhere("a.fecha <= :hoy")
  245.         ->setParameter('siete_dias_anteriores'$siete_dias_anteriores)
  246.         ->setParameter('hoy'$hoy)
  247.         ->getQuery();
  248.         $agendas_semana $query->getResult();
  249.         return $this->render('agenda/agenda_semana.html.twig', [
  250.             'agendas' => $agendas_semana,
  251.         ]);
  252.     }
  253.     /**
  254.      * @Route("/cierre", name="cierre", methods={"GET", "POST"})
  255.      */
  256.     public function cierre(Mensajes $mensajes): Response
  257.     {
  258.         $entityManager $this->getDoctrine()->getManager();
  259.         $actividades_cerradas_semana $entityManager->getRepository(Agenda::class)->findByAbiertopostulacion(0);
  260.         
  261.         $hoy = new \DateTime('now');
  262.         $seis_dias_siguintes = clone $hoy;
  263.         $seis_dias_siguintes->modify('+7 days');
  264.      
  265.         $agendas $entityManager->getRepository(Agenda::class)->createQueryBuilder('b')
  266.         ->where('b.id IN (:ids)')
  267.         ->andWhere('b.fecha BETWEEN :hoy AND :seis_dias_siguientes')
  268.         ->setParameter('ids'$actividades_cerradas_semana)
  269.         ->setParameter('hoy'$hoy)
  270.         ->setParameter('seis_dias_siguientes'$seis_dias_siguintes)
  271.         ->getQuery()
  272.         ->getResult();
  273.         
  274.         return $this->render('agenda/apertura_o_cierre.html.twig', [
  275.             'agendas' => $agendas,
  276.             'aperturaocierre' => 1
  277.         ]);
  278.     }
  279.     /**
  280.      * @Route("/correo_cierre", name="correo_cierre", methods={"GET", "POST"})
  281.      */
  282.     public function correo_cierre(Mensajes $mensajes): Response
  283.     {
  284.         $entityManager $this->getDoctrine()->getManager();
  285.         $actividades_cerradas_semana $entityManager->getRepository(Agenda::class)->findByAbiertopostulacion(0);
  286.         
  287.         $hoy = new \DateTime('now');
  288.         $seis_dias_siguintes = clone $hoy;
  289.         $seis_dias_siguintes->modify('+7 days');
  290.      
  291.         $agendas $entityManager->getRepository(Agenda::class)->createQueryBuilder('b')
  292.         ->where('b.id IN (:ids)')
  293.         ->andWhere('b.fecha BETWEEN :hoy AND :seis_dias_siguientes')
  294.         ->setParameter('ids'$actividades_cerradas_semana)
  295.         ->setParameter('hoy'$hoy)
  296.         ->setParameter('seis_dias_siguientes'$seis_dias_siguintes)
  297.         ->getQuery()
  298.         ->getResult();
  299.         $url $this->redirectToRoute('actividades_cerradas_by_e')->getTargetUrl();
  300.         $embajadores = array();
  301.         foreach ($agendas as $agenda){
  302.             $embajador_participantes $agenda->getEmbajadorParticipantes();
  303.             foreach ($embajador_participantes as $embajador_participante){
  304.                 array_push($embajadores$embajador_participante->getEmbajadores()->getId());
  305.             }
  306.             
  307.         }
  308.         $embajadores_final $entityManager->getRepository(Embajadores::class)->findById($embajadores);
  309.         foreach ($embajadores_final as $embajador){
  310.             $mensajes->enviar2Mensaje($embajador->getCorreo(), 'difusion@delfy.cl''Cierre actividad''Estimados embajadores y embajadoras: <br> 
  311.             Junto con saludar y esperando se encuentren bien, les escribo para enviar las asignaciones de la próxima semana, les dejo algunas indicaciones específicas de cada actividad: <br><br><br>
  312.                 
  313.                   
  314.                 
  315.             Ferias Panoramia: <br>
  316.             Deben llegar por cuenta propia, 15 minutos antes del inicio del evento, organizar el material en las mesas y una vez finalizado el evento, dejar guardado el material dentro de la caja que se encuentra en la feria, además, deben notificar cuánto material queda disponible antes de retirarse del lugar. <br><br><br>
  317.                 
  318.                  
  319.                 
  320.             Ferias de colegios:<br>
  321.                 
  322.             Las y los embajadores deben retirar el material en  nuestra oficina ubicada en el Hall universitario de casa central 15 minutos antes de la hora de salida del taxi, en el caso de que el retiro sea antes de las 8:30 am, deben hablar con los guardias de la entrada para solicitar la apertura del hall y trasladarse con el material hasta el taxi, los guardias estarán enterados de la situación y se les copiará en el correo. La salida es por el centro de extensión y el taxi los esperara en Alameda con Lira.
  323.                 
  324.                  
  325.                 
  326.             Tour: <br>
  327.                 
  328.             Recibirán un correo con las instrucciones para la actividad.<br>
  329.                 
  330.             Charla Presencial:<br>
  331.                 
  332.             Recibirán un correo con las instrucciones para la actividad.<br><br><br>
  333.                 
  334.                  
  335.                 
  336.                  
  337.                 
  338.             Es importante destacar que para TODAS las actividades antes mencionadas, deben asistir con su cortaviento UC y buena presentación personal, si no cuentan con él, pueden venir a retirarlo a nuestra oficina (previa coordinación de día y horario). Para finalizar, solicitamos que envíen vía WhatsApp a Valentina Díaz o a Natalia Peña fotos/videos/boomerangs/reels (puede ser cualquiera de estas opciones) de cada evento con el fin de poder visibilizar nuestro trabajo en redes sociales.<br><br><br>
  339.                 
  340.                  
  341.                 
  342.             Recuerden que a las Ferias Panoramia no llevan material por lo que no se asigna taxi y deben llegar directo al colegio.<br>
  343.                 
  344.             Para ver las asignaciones debes entrar a la plataforma, hacer click en "Agenda general actividades" y luego en "Actividades cerradas". Ingresa a la plataforma <a href=difusionuc.eurekatech.io>AQUÍ</a>');
  345.         }
  346.         return $this->redirectToRoute('cierre');
  347.     }
  348.     /**
  349.      * @Route("/actividades_cerradas_by_e", name="actividades_cerradas_by_e", methods={"GET"})
  350.      */
  351.     public function actividades_cerradas_by_e(Mensajes $mensajes): Response
  352.     {
  353.         $entityManager $this->getDoctrine()->getManager();
  354.         $actividades_cerradas_semana $entityManager->getRepository(Agenda::class)->findByAbiertopostulacion(0);
  355.         
  356.         $hoy = new \DateTime('now');
  357.         $seis_dias_siguintes = clone $hoy;
  358.         $seis_dias_siguintes->modify('+7 days');
  359.      
  360.         $agendas $entityManager->getRepository(Agenda::class)->createQueryBuilder('b')
  361.         ->where('b.id IN (:ids)')
  362.         ->andWhere('b.fecha BETWEEN :hoy AND :seis_dias_siguientes')
  363.         ->setParameter('ids'$actividades_cerradas_semana)
  364.         ->setParameter('hoy'$hoy)
  365.         ->setParameter('seis_dias_siguientes'$seis_dias_siguintes)
  366.         ->getQuery()
  367.         ->getResult();
  368.         
  369.         return $this->render('agenda/aperturaocierre_e.html.twig', [
  370.             'agendas' => $agendas
  371.         ]);
  372.     }
  373.     /**
  374.      * @Route("/apertura", name="apertura", methods={"GET", "POST"})
  375.      */
  376.     public function apertura(Mensajes $mensajes): Response
  377.     {
  378.         $entityManager $this->getDoctrine()->getManager();
  379.         $actividades_cerradas_semana $entityManager->getRepository(Agenda::class)->findByAbiertopostulacion(1);
  380.         
  381.         $hoy = new \DateTime('now');
  382.         $seis_dias_siguintes = clone $hoy;
  383.         $seis_dias_siguintes->modify('+7 days');
  384.      
  385.         $agendas $entityManager->getRepository(Agenda::class)->createQueryBuilder('b')
  386.         ->where('b.id IN (:ids)')
  387.         ->andWhere('b.fecha BETWEEN :hoy AND :seis_dias_siguientes')
  388.         ->setParameter('ids'$actividades_cerradas_semana)
  389.         ->setParameter('hoy'$hoy)
  390.         ->setParameter('seis_dias_siguientes'$seis_dias_siguintes)
  391.         ->getQuery()
  392.         ->getResult();
  393.         return $this->render('agenda/apertura_o_cierre.html.twig', [
  394.             'agendas' => $agendas,
  395.             'aperturaocierre' => 0,
  396.         ]);
  397.     }
  398.     /**
  399.      * @Route("/abiertas", name="agenda_index_abiertas", methods={"GET"})
  400.      */
  401.     public function indexabiertas(AgendaRepository $agendaRepository): Response
  402.     {
  403.         $entityManager $this->getDoctrine()->getManager();
  404.         return $this->render('agenda/index.html.twig', [
  405.             'agendas' => $agendaRepository->findBy(array("abiertopostulacion"=>1)),
  406.         ]);
  407.     }
  408.     /**
  409.      * @Route("/estudiantes", name="agenda_index_estudiante", methods={"GET"})
  410.      * @IsGranted("ROLE_ESTUDIANTE")
  411.      */
  412.     public function indexestudiantes(Request $requestAgendaRepository $agendaRepository): Response
  413.     {
  414.         $entityManager $this->getDoctrine()->getManager();
  415.         $estados_aceptados = array ("agendada");
  416.         $estados $entityManager->getRepository(EstadoAgenda::class)->findByNombre($estados_aceptados);
  417.         $embajador $entityManager->getRepository(Embajadores::class)->findOneBy(['correo' => $this->getUser()->getUserIdentifier()]);
  418.         if (!$embajador || strtolower(trim($embajador->getEstado() ?? '')) === 'no vigente') {
  419.             return $this->render('agenda/novigente.html.twig');
  420.         }
  421.         // Si el admin habilitó la actualización de datos, redirigir al formulario SOLO al entrar por primera vez (login)
  422.         // El banner en la vista se encarga de recordarlo en visitas posteriores
  423.         if ($embajador && $embajador->isActualiza() && $request->query->get('desde') === 'login') {
  424.             return $this->redirectToRoute('app_embajadores_actualiza', ['id' => $embajador->getId()]);
  425.         }
  426.         return $this->render('agenda/indexpostulantes.html.twig', [
  427.             'embajador'=> $embajador,
  428.             'iduser'=> $embajador $embajador->getId() : 0,
  429.             'pendienteActualizacion' => $embajador && (bool)$embajador->isActualiza(),
  430.             'agendas' => $agendaRepository->findBy(array("estadoAgenda"=>$estados"abiertopostulacion"=> 1)),
  431.             'user'=>$this->getUser(),
  432.         ]);
  433.     }
  434.     /**
  435.      * @Route("/estudiantesconfirmado", name="agenda_index_estudiante_confirmadas", methods={"GET"})
  436.      */
  437.     public function indexestudiantesconfirmadas(AgendaRepository $agendaRepository): Response
  438.     {
  439.         if (!$this->getUser()) {
  440.             return $this->redirectToRoute('app_login');
  441.         }
  442.         $entityManager $this->getDoctrine()->getManager();
  443.         $embajador $entityManager->getRepository(Embajadores::class)->findOneBy(['correo' => $this->getUser()->getUserIdentifier()]);
  444.         if (!$embajador) {
  445.             return $this->redirectToRoute('app_login');
  446.         }
  447.         $estados_aceptados = array ("agendada");
  448.         $ids = array();
  449.         $estados $entityManager->getRepository(EstadoAgenda::class)->findByNombre($estados_aceptados);
  450.         $aceptadas $entityManager->getRepository(EmbajadorParticipante::class)->findBy(array('estado'=>'seleccionado','embajadores'=>$embajador->getId()));
  451.         foreach ($aceptadas as $agenda) {
  452.             if($agenda->getAgenda()){
  453.                 array_push($ids$agenda->getAgenda()->getId());
  454.             }
  455.         }
  456.         // Usar QueryBuilder para consulta con array de IDs
  457.         if (!empty($ids)) {
  458.             $agendas $agendaRepository->createQueryBuilder('a')
  459.                 ->where('a.estadoAgenda IN (:estados)')
  460.                 ->andWhere('a.id IN (:ids)')
  461.                 ->setParameter('estados'$estados)
  462.                 ->setParameter('ids'$ids)
  463.                 ->getQuery()
  464.                 ->getResult();
  465.         } else {
  466.             $agendas = [];
  467.         }
  468.         return $this->render('agenda/indexestudiantes.html.twig', [
  469.             'iduser' => $embajador->getId(),
  470.             'agendas' => $agendas,
  471.             'aceptadas' => $aceptadas,
  472.             'user' => $this->getUser(),
  473.         ]);
  474.     }
  475.     /**
  476.      * @Route("/historicas", name="agenda_index_estudiante_historicas", methods={"GET"})
  477.      */
  478.     public function indexhistoricas(AgendaRepository $agendaRepository): Response
  479.     {
  480.         // Verificar que el usuario esté autenticado
  481.         if (!$this->getUser()) {
  482.             return $this->redirectToRoute('app_login');
  483.         }
  484.         $entityManager $this->getDoctrine()->getManager();
  485.         $embajador $entityManager->getRepository(Embajadores::class)->findOneBy(['correo' => $this->getUser()->getUserIdentifier()]);
  486.         if (!$embajador) {
  487.             return $this->redirectToRoute('app_login');
  488.         }
  489.         $ids = array();
  490.         $aceptadas $entityManager->getRepository(EmbajadorParticipante::class)->findBy(array('embajadores' => $embajador->getId()));
  491.         foreach ($aceptadas as $agenda) {
  492.             if( $agenda->getAgenda()){
  493.                 array_push($ids$agenda->getAgenda()->getId());
  494.             }
  495.         }
  496.         // Usar QueryBuilder para consulta con array de IDs
  497.         if (!empty($ids)) {
  498.             $agendas $agendaRepository->createQueryBuilder('a')
  499.                 ->where('a.id IN (:ids)')
  500.                 ->setParameter('ids'$ids)
  501.                 ->getQuery()
  502.                 ->getResult();
  503.         } else {
  504.             $agendas = [];
  505.         }
  506.         return $this->render('agenda/indexestudiantes.html.twig', [
  507.             'iduser' => $embajador->getId(),
  508.             'agendas' => $agendas,
  509.             'aceptadas' => $aceptadas,
  510.             'user' => $this->getUser(),
  511.         ]);
  512.     }
  513.     /**
  514.      * @Route("/new", name="agenda_new", methods={"GET", "POST"})
  515.      */
  516.     public function new(Request $requestAgendaRepository $agendaRepository): Response
  517.     {
  518.         $agenda = new Agenda();
  519.         $form $this->createForm(AgendaType::class, $agenda);
  520.         $form->handleRequest($request);
  521.         if ($form->isSubmitted() && $form->isValid()) {
  522.             $agendaRepository->add($agendatrue);
  523.             return $this->redirectToRoute('colegioparticipante_newbyagenda', array('id' => $agenda->getId()));
  524.         }
  525.         return $this->renderForm('agenda/new.html.twig', [
  526.             'agenda' => $agenda,
  527.             'form' => $form,
  528.         ]);
  529.     }
  530.     /**
  531.      * @Route("/calendario", name="agenda_calendario", methods={"GET", "POST"})
  532.      */
  533.     public function calendario(Request $requestAgendaRepository $agendaRepository): Response
  534.     {
  535.         $em $this->getDoctrine()->getManager();
  536.         $estados_aceptados = array ("agendada""realizada");
  537.         $estados $em->getRepository(EstadoAgenda::class)->findByNombre($estados_aceptados);
  538.         $agendas $em->getRepository(Agenda::class)->findByEstadoAgenda($estados);
  539.         $form $this->createForm(Agenda2Type::class);
  540.         $form->handleRequest($request);
  541.         if ($form->isSubmitted() && $form->isValid()) {
  542.             $agenda $form->get('observacion')->getData();
  543.             return $this->redirectToRoute('agenda_show', array('id' => $agenda));
  544.         }
  545.         return $this->renderForm('agenda/calendario.html.twig', [
  546.             'agendas' => $agendas,
  547.             'form' => $form,
  548.         ]);
  549.     }
  550.     /**
  551.      * @Route("/mass-email", name="agenda_mass_email", methods={"GET", "POST"})
  552.      */
  553.     public function massEmail(Request $request): Response
  554.     {
  555.         $entityManager $this->getDoctrine()->getManager();
  556.         
  557.         // Manejar tanto GET como POST, y tanto array como string
  558.         $agendaIdsRaw $request->request->get('agenda_ids') ?? $request->query->get('agenda_ids');
  559.         
  560.         if (is_array($agendaIdsRaw)) {
  561.             $agendaIds $agendaIdsRaw;
  562.         } elseif (is_string($agendaIdsRaw)) {
  563.             $agendaIds explode(','$agendaIdsRaw);
  564.         } else {
  565.             $agendaIds = [];
  566.         }
  567.         
  568.         // El estado puede venir de POST (al cambiar filtro) o GET (primera carga)
  569.         $estadoFiltro $request->request->get('estado_filtro'
  570.                        ?? $request->query->get('estado_filtro'
  571.                        ?? 'seleccionado';
  572.         
  573.         if (empty($agendaIds)) {
  574.             $this->addFlash('error''No se seleccionaron agendas.');
  575.             return $this->redirectToRoute('agenda_index');
  576.         }
  577.         // Recopilar embajadores filtrados por estado
  578.         $recipients = [];
  579.         $estadosDisponibles = [];
  580.         
  581.         foreach ($agendaIds as $agendaId) {
  582.             $agenda $entityManager->getRepository(Agenda::class)->find($agendaId);
  583.             if ($agenda) {
  584.                 foreach ($agenda->getEmbajadorParticipantes() as $embajadorParticipante) {
  585.                     // Recopilar estados disponibles
  586.                     $estado $embajadorParticipante->getEstado();
  587.                     if ($estado && !isset($estadosDisponibles[$estado])) {
  588.                         $estadosDisponibles[$estado] = 0;
  589.                     }
  590.                     if ($estado) {
  591.                         $estadosDisponibles[$estado]++;
  592.                     }
  593.                     
  594.                     // Filtrar por estado seleccionado
  595.                     if ($estadoFiltro !== 'todos' && $embajadorParticipante->getEstado() !== $estadoFiltro) {
  596.                         continue;
  597.                     }
  598.                     
  599.                     // Saltar embajadores que ya tienen correo enviado
  600.                     if ($embajadorParticipante->isEnviado()) {
  601.                         continue;
  602.                     }
  603.                     
  604.                     $embajador $embajadorParticipante->getEmbajadores();
  605.                     if ($embajador && $embajador->getCorreo()) {
  606.                         $recipients[] = [
  607.                             'email' => $embajador->getCorreo(),
  608.                             'nombre' => $embajador->getNombre(),
  609.                             'agenda' => $agenda->getId(),
  610.                             'estado' => $embajadorParticipante->getEstado()
  611.                         ];
  612.                     }
  613.                 }
  614.             }
  615.         }
  616.         // Eliminar duplicados por email
  617.         $uniqueRecipients = [];
  618.         foreach ($recipients as $recipient) {
  619.             $uniqueRecipients[$recipient['email']] = $recipient;
  620.         }
  621.         $recipients array_values($uniqueRecipients);
  622.         if (empty($recipients)) {
  623.             $this->addFlash('warning'"No se encontraron destinatarios con estado '{$estadoFiltro}' en las agendas seleccionadas.");
  624.             return $this->redirectToRoute('agenda_index');
  625.         }
  626.         // Obtener templates disponibles
  627.         $templates $entityManager->getRepository(\App\Entity\Correo::class)->findAll();
  628.         return $this->render('agenda/mass_email.html.twig', [
  629.             'recipients' => $recipients,
  630.             'agenda_ids' => implode(','$agendaIds),
  631.             'templates' => $templates,
  632.             'estado_actual' => $estadoFiltro,
  633.             'estados_disponibles' => $estadosDisponibles
  634.         ]);
  635.     }
  636.     /**
  637.      * @Route("/mass-email/send", name="agenda_mass_email_send", methods={"POST"})
  638.      */
  639.     public function massEmailSend(Request $requestMassEmailService $massEmailServiceLoggerInterface $logger): Response
  640.     {
  641.         try {
  642.             // Aumentar tiempo de ejecución y límite de memoria para envíos masivos
  643.             set_time_limit(600); // 10 minutos
  644.             ini_set('memory_limit''512M');
  645.             
  646.             $entityManager $this->getDoctrine()->getManager();
  647.             
  648.             $agendaIds explode(','$request->request->get('agenda_ids'''));
  649.             $asuntoPersonalizado $request->request->get('asunto');
  650.             $cuerpoPersonalizado $request->request->get('cuerpo');
  651.             $delay = (int) $request->request->get('delay'5);
  652.             $estadoFiltro $request->request->get('estado_filtro_hidden''seleccionado');
  653.             $logger->info('DEBUG massEmailSend - Datos recibidos', [
  654.                 'agenda_ids' => $agendaIds
  655.                 'asunto' => $asuntoPersonalizado,
  656.                 'tiene_cuerpo' => !empty($cuerpoPersonalizado),
  657.                 'estado_filtro' => $estadoFiltro
  658.             ]);
  659.             if (empty($agendaIds) || empty($asuntoPersonalizado) || empty($cuerpoPersonalizado)) {
  660.                 $this->addFlash('error''Datos incompletos. Debes completar asunto y contenido.');
  661.                 return $this->redirectToRoute('agenda_index');
  662.             }
  663.             // Configurar delay
  664.             $massEmailService->setDelayBetweenBatches($delay);
  665.         // Recopilar embajadores con sus variables
  666.         $recipients = [];
  667.         foreach ($agendaIds as $agendaId) {
  668.             $agenda $entityManager->getRepository(Agenda::class)->find($agendaId);
  669.             if ($agenda) {
  670.                 $logger->info('DEBUG massEmailSend - Agenda encontrada', [
  671.                     'agenda_id' => $agendaId
  672.                     'participantes_count' => count($agenda->getEmbajadorParticipantes())
  673.                 ]);
  674.                 
  675.                 $tipoAgenda $agenda->getTipoAgenda() ? $agenda->getTipoAgenda()->getNombre() : 'Actividad';
  676.                 $fecha $agenda->getFecha() ? $agenda->getFecha()->format('d/m/Y') : '';
  677.                 $hora $agenda->getFecha() ? $agenda->getFecha()->format('H:i') : '';
  678.                 $lugar $agenda->getLugar() ?? '';
  679.                 foreach ($agenda->getEmbajadorParticipantes() as $embajadorParticipante) {
  680.                     // Filtrar por estado
  681.                     if ($estadoFiltro !== 'todos' && $embajadorParticipante->getEstado() !== $estadoFiltro) {
  682.                         $logger->debug('DEBUG massEmailSend - Embajador filtrado por estado', [
  683.                             'estado_embajador' => $embajadorParticipante->getEstado(),
  684.                             'estado_filtro' => $estadoFiltro
  685.                         ]);
  686.                         continue;
  687.                     }
  688.                     
  689.                     // Saltar embajadores que ya tienen correo enviado
  690.                     if ($embajadorParticipante->isEnviado()) {
  691.                         $logger->debug('DEBUG massEmailSend - Embajador ya tiene correo enviado', [
  692.                             'embajador' => $embajadorParticipante->getEmbajadores() ? $embajadorParticipante->getEmbajadores()->getNombre() : 'NULL'
  693.                         ]);
  694.                         continue;
  695.                     }
  696.                     
  697.                     $embajador $embajadorParticipante->getEmbajadores();
  698.                     $logger->info('DEBUG massEmailSend - Embajador incluido', [
  699.                         'embajador' => $embajador $embajador->getNombre() : 'NULL',
  700.                         'correo' => $embajador $embajador->getCorreo() : 'NULL',
  701.                         'estado' => $embajadorParticipante->getEstado()
  702.                     ]);
  703.                     
  704.                     if ($embajador && $embajador->getCorreo()) {
  705.                         $recipients[] = [
  706.                             'email' => $embajador->getCorreo(),
  707.                             'asunto' => $asuntoPersonalizado,
  708.                             'cuerpo' => $cuerpoPersonalizado,
  709.                             'variables' => [
  710.                                 'nombre_embajador' => $embajador->getNombre(),
  711.                                 'tipo_evento' => $tipoAgenda,
  712.                                 'fecha_evento' => $fecha,
  713.                                 'hora_evento' => $hora,
  714.                                 'lugar_evento' => $lugar,
  715.                                 'direccion' => $lugar,
  716.                                 'observaciones' => '',
  717.                                 'monto_pago' => '0',
  718.                                 'rol' => 'Embajador',
  719.                                 'horas' => '0'
  720.                             ]
  721.                         ];
  722.                     }
  723.                 }
  724.             } else {
  725.                 $logger->warning('DEBUG massEmailSend - Agenda NO encontrada', ['agenda_id' => $agendaId]);
  726.             }
  727.         }
  728.         
  729.         $logger->info('DEBUG massEmailSend - Total recipients', ['count' => count($recipients)]);
  730.         // Eliminar duplicados
  731.         $uniqueRecipients = [];
  732.         foreach ($recipients as $recipient) {
  733.             $uniqueRecipients[$recipient['email']] = $recipient;
  734.         }
  735.         $recipients array_values($uniqueRecipients);
  736.         // Validar que hay destinatarios
  737.         if (empty($recipients)) {
  738.             $this->addFlash('error''No se encontraron destinatarios con email válido en las agendas seleccionadas.');
  739.             return $this->redirectToRoute('agenda_index');
  740.         }
  741.         // Enviar emails masivos con contenido personalizado
  742.         $results $massEmailService->sendMassEmailCustom($recipients);
  743.         // Mostrar resultados
  744.         if ($results['success'] > 0) {
  745.             $this->addFlash('success'sprintf(
  746.                 'Se enviaron %d emails correctamente de %d total.',
  747.                 $results['success'],
  748.                 $results['total']
  749.             ));
  750.         }
  751.         if ($results['failed'] > 0) {
  752.             $this->addFlash('warning'sprintf(
  753.                 'Fallaron %d envíos. Revisa los logs para más detalles.',
  754.                 $results['failed']
  755.             ));
  756.         }
  757.         } catch (\Exception $e) {
  758.             $logger->error('Error en envío masivo de correos', [
  759.                 'mensaje' => $e->getMessage(),
  760.                 'archivo' => $e->getFile(),
  761.                 'linea' => $e->getLine(),
  762.                 'trace' => $e->getTraceAsString()
  763.             ]);
  764.             
  765.             $this->addFlash('error''Error al enviar correos: ' $e->getMessage());
  766.         }
  767.         return $this->redirectToRoute('agenda_index');
  768.     }
  769.     /**
  770.      * @Route("/mass-email/preview", name="agenda_mass_email_preview", methods={"GET", "POST"})
  771.      */
  772.     public function massEmailPreview(Request $requestEmailTemplateService $templateService): Response
  773.     {
  774.         $entityManager $this->getDoctrine()->getManager();
  775.         
  776.         // Obtener datos del formulario (POST) o query string (GET)
  777.         if ($request->isMethod('POST')) {
  778.             $asunto $request->request->get('asunto');
  779.             $cuerpo $request->request->get('cuerpo');
  780.             $agendaIds explode(','$request->request->get('agenda_ids'''));
  781.         } else {
  782.             $templateCodigo $request->query->get('template');
  783.             $agendaIds explode(','$request->query->get('agenda_ids'''));
  784.             
  785.             $template $entityManager->getRepository(\App\Entity\Correo::class)
  786.                 ->findOneBy(['codigo' => $templateCodigo]);
  787.             if (!$template) {
  788.                 return new Response('<div class="alert alert-danger">Template no encontrado</div>');
  789.             }
  790.             
  791.             $asunto $template->getAsunto();
  792.             $cuerpo $template->getCuerpo();
  793.         }
  794.         // Obtener datos de ejemplo de la primera agenda
  795.         $agenda null;
  796.         if (!empty($agendaIds) && $agendaIds[0]) {
  797.             $agenda $entityManager->getRepository(Agenda::class)->find($agendaIds[0]);
  798.         }
  799.         $ejemploVariables = [
  800.             'nombre_embajador' => 'Juan Pérez (ejemplo)',
  801.             'tipo_evento' => $agenda && $agenda->getTipoAgenda() ? $agenda->getTipoAgenda()->getNombre() : 'Evento Ejemplo',
  802.             'fecha_evento' => $agenda && $agenda->getFecha() ? $agenda->getFecha()->format('d/m/Y') : date('d/m/Y'),
  803.             'hora_evento' => $agenda && $agenda->getFecha() ? $agenda->getFecha()->format('H:i') : '10:00',
  804.             'lugar_evento' => $agenda $agenda->getLugar() ?? 'Por definir' 'Por definir',
  805.             'direccion' => $agenda $agenda->getLugar() ?? 'Por definir' 'Por definir',
  806.             'observaciones' => 'Ejemplo de observaciones adicionales',
  807.             'monto_pago' => '50000',
  808.             'rol' => 'Embajador',
  809.             'horas' => '4'
  810.         ];
  811.         // Reemplazar variables manualmente
  812.         $asuntoRenderizado $asunto;
  813.         $cuerpoRenderizado $cuerpo;
  814.         
  815.         foreach ($ejemploVariables as $key => $value) {
  816.             $asuntoRenderizado str_replace('{{' $key '}}'$value$asuntoRenderizado);
  817.             $asuntoRenderizado str_replace('{' $key '}'$value$asuntoRenderizado);
  818.             $cuerpoRenderizado str_replace('{{' $key '}}'$value$cuerpoRenderizado);
  819.             $cuerpoRenderizado str_replace('{' $key '}'$value$cuerpoRenderizado);
  820.         }
  821.         $html sprintf('
  822.             <div class="row">
  823.                 <div class="col-12">
  824.                     <div class="alert alert-info mb-4">
  825.                         <i class="la la-info-circle"></i> <strong>Vista Previa con Datos de Ejemplo</strong>
  826.                         <p class="mb-0 mt-2">Cada destinatario recibirá el email personalizado con sus propios datos.</p>
  827.                     </div>
  828.                     
  829.                     <div class="card mb-3">
  830.                         <div class="card-header bg-light">
  831.                             <strong><i class="la la-tag"></i> Asunto del Email:</strong>
  832.                         </div>
  833.                         <div class="card-body">
  834.                             <h5 class="mb-0">%s</h5>
  835.                         </div>
  836.                     </div>
  837.                     
  838.                     <div class="card">
  839.                         <div class="card-header bg-light">
  840.                             <strong><i class="la la-file-text"></i> Contenido del Email:</strong>
  841.                         </div>
  842.                         <div class="card-body" style="background: #f9f9f9; padding: 30px; line-height: 1.8;">
  843.                             %s
  844.                         </div>
  845.                     </div>
  846.                 </div>
  847.             </div>
  848.         'htmlspecialchars($asuntoRenderizado), $cuerpoRenderizado);
  849.         return new Response($html);
  850.     }
  851.     /**
  852.      * @Route("/mass-email/variables", name="agenda_mass_email_variables", methods={"GET"})
  853.      */
  854.     public function massEmailVariables(Request $request): Response
  855.     {
  856.         $entityManager $this->getDoctrine()->getManager();
  857.         $templateCodigo $request->query->get('template');
  858.         $template $entityManager->getRepository(\App\Entity\Correo::class)
  859.             ->findOneBy(['codigo' => $templateCodigo]);
  860.         if (!$template) {
  861.             return $this->json(['error' => 'Template no encontrado''variables' => []], 404);
  862.         }
  863.         // Extraer variables del contenido del template
  864.         // Buscar tanto {variable} como {{variable}}
  865.         $contenido $template->getCuerpo();
  866.         preg_match_all('/\{\{?([a-z_]+)\}?\}/'$contenido$matches);
  867.         $variables array_unique($matches[1]);
  868.         return $this->json(['variables' => array_values($variables)]);
  869.     }
  870.     /**
  871.      * @Route("/{id}", name="agenda_show", methods={"GET", "POST"}, requirements={"id"="\d+"})
  872.      */
  873.     public function show(Mensajes $mensajesAgenda $agendaRequest $request): Response
  874.     {
  875.         $entityManager $this->getDoctrine()->getManager();
  876.         $colegios $entityManager->getRepository(ColegioParticipante::class)->findByAgenda($agenda->getId());
  877.         $embajadores $entityManager->getRepository(EmbajadorParticipante::class)->findByAgenda($agenda->getId());
  878.         $uaPostulaciones $entityManager->getRepository(UnidadAcademicaPostulacion::class)->findBy(['agenda' => $agenda]);
  879.         $form $this->createFormBuilder()->getForm();
  880.         $form ->add("cuerpo"TextareaType::class, ['required'=>true,]);
  881.         $cuerpo "";
  882.         if($agenda->getSolicitud()){
  883.             $cuerpo "Hola,<br><br> 
  884. Junto con saludar, le comentamos que su solicitud de ".$agenda->getSolicitud()->getTipo().", quedó agendada para la fecha ".date_format($agenda->getFecha(),"d/m/Y")." de ".date_format($agenda->getFecha(),"H:i")." hasta ".date_format($agenda->getHasta(),"H:i").". Próximos a la fecha enviaremos un correo de confirmación, también solicitamos que cualquier cambio sea informado vía correo.<br><br>
  885. Cualquier duda puede escribir a colegios@uc.cl.<br><br>
  886. Saludos cordiales";
  887.         }
  888.         $form->get('cuerpo')->setData($cuerpo);
  889.         if ($request->isMethod('POST')) {
  890.             $form->handleRequest($request);
  891.             $data $form->getData();
  892.             $agenda->getSolicitud()->setCuerpo($data["cuerpo"]);
  893.             $entityManager->flush();
  894.             $mensajes->enviar2Mensaje($agenda->getSolicitud()->getCorreoEncargado(),"vivian.avalos@uc.cl""Solicitud actividad UC ingresada correctamente"$agenda->getSolicitud()->getCuerpo());
  895.         }
  896.         return $this->render('agenda/show.html.twig', [
  897.             'agenda' => $agenda,
  898.             'colegios' => $colegios,
  899.             'embajadores' => $embajadores,
  900.             'ua_postulaciones' => $uaPostulaciones,
  901.             'form' => $form->createView(),
  902.         ]);
  903.     }
  904.     /**
  905.      * @Route("/{id}/edit", name="agenda_edit", methods={"GET", "POST"})
  906.      */
  907.     public function edit(Request $requestAgenda $agendaAgendaRepository $agendaRepositoryMensajes $mensajes): Response
  908.     {
  909.         $entityManager $this->getDoctrine()->getManager();
  910.         $originalFecha $agenda->getFecha();
  911.         
  912.         // Normalizar roles si es un array (compatibilidad con datos antiguos)
  913.         if (is_array($agenda->getRoles())) {
  914.             $agenda->setRoles(implode(', '$agenda->getRoles()));
  915.         }
  916.         $form $this->createForm(AgendaType::class, $agenda);
  917.         $form->handleRequest($request);
  918.         $colegios $entityManager->getRepository(ColegioParticipante::class)->findByAgenda($agenda->getId());
  919.         $embajadores $entityManager->getRepository(EmbajadorParticipante::class)->findByAgenda($agenda->getId());
  920.         // Obtener todos los roles únicos de todos los eventos
  921.         $rolesUnicos $this->getRolesUnicos($entityManager);
  922.         if ($form->isSubmitted() && $form->isValid()) {
  923.             
  924.             $agendaRepository->add($agendatrue);
  925.               if ($agenda->getFecha() != $originalFecha) {
  926.                 $nuevoMensaje "La actividad con id: " $agenda->getId() . " para el día " $originalFecha->format('d/m/Y') . ", en el lugar " $agenda->getLugar() . ", se modificó la fecha para el día " $agenda->getFecha()->format('d/m/Y');
  927.                 // Enviar correo a los embajadores participantes
  928.                 foreach ($embajadores as $embajador) {
  929.                     $mensajes->enviar2Mensaje($embajador->getEmbajadores()->getCorreo(), null"Modificación de fecha de actividad"$nuevoMensaje);
  930.                 }
  931.                 $mensajes->enviar2Mensaje("migueleduardo.hernandez@gmail.com"null"Modificación de fecha de actividad"$nuevoMensaje);
  932.                 $mensajes->enviar2Mensaje("cigutierrez@uc.cl"null"Modificación de fecha de actividad"$nuevoMensaje);
  933.             }
  934.             if ($agenda->getEstadoAgenda() == "eliminada") {
  935.                 return $this->redirectToRoute('agenda_index');
  936.             }
  937.           
  938.             return $this->redirectToRoute('colegioparticipante_newbyagenda', ['id' => $agenda->getId()]);
  939.         }
  940.         return $this->renderForm('agenda/edit.html.twig', [
  941.             'agenda' => $agenda,
  942.             'form' => $form,
  943.             'colegios' => $colegios,
  944.             'embajadores' => $embajadores,
  945.         ]);
  946.     }
  947.     
  948.     /**
  949.      * Obtiene todos los roles únicos de todos los eventos
  950.      */
  951.     private function getRolesUnicos($entityManager): array
  952.     {
  953.         $connection $entityManager->getConnection();
  954.         $sql "SELECT DISTINCT unnest(string_to_array(roles, ',')) as rol FROM agenda WHERE roles IS NOT NULL ORDER BY rol";
  955.         
  956.         try {
  957.             $result $connection->executeQuery($sql)->fetchAllAssociative();
  958.             $rolesUnicos = [];
  959.             
  960.             foreach ($result as $row) {
  961.                 $rolCompleto trim($row['rol']);
  962.                 if (!empty($rolCompleto)) {
  963.                     // Extraer solo el nombre del rol (antes de los dos puntos)
  964.                     $partes explode(':'$rolCompleto);
  965.                     $nombreRol trim($partes[0]);
  966.                     
  967.                     // Evitar duplicados
  968.                     if (!in_array($nombreRol$rolesUnicos) && !empty($nombreRol)) {
  969.                         $rolesUnicos[] = $nombreRol;
  970.                     }
  971.                 }
  972.             }
  973.             
  974.             sort($rolesUnicos);
  975.             return $rolesUnicos;
  976.         } catch (\Exception $e) {
  977.             return [];
  978.         }
  979.     }
  980.     /**
  981.      * @Route("/{id}", name="app_agenda_delete", methods={"POST"}, requirements={"id"="\d+"})
  982.      */
  983.     public function delete(Request $requestAgenda $agendaAgendaRepository $agendaRepository): Response
  984.     {
  985.         if ($this->isCsrfTokenValid('delete'.$agenda->getId(), $request->request->get('_token'))) {
  986.             $agendaRepository->remove($agendatrue);
  987.         }
  988.         return $this->redirectToRoute('app_agenda_index', [], Response::HTTP_SEE_OTHER);
  989.     }
  990.     /**
  991.      * @Route("/calendario/get_events", name="calendario_events")
  992.      */
  993.     public function eventsAction(Request $request)
  994.     {
  995.         $em $this->getDoctrine()->getManager();
  996.         $sesiones $em->getRepository('AppBundle:Agenda')->findAll();
  997.         $events = array();
  998.         $aux2;
  999.         $aux2="";
  1000.         foreach ($sesiones as $event) {
  1001.           
  1002.             $aux =  $event->getId().'-'.$event->getLugar();
  1003.             $e = array();
  1004.             $e['id'] = $event->getId();
  1005.             $e['title'] = $aux;
  1006.             $e['color'] = '#5867dd';
  1007.             $e['eventColor'] = 'white';
  1008.             $e['url'] = $this->generateUrl('agenda_show', array('id' => $event->getId()));
  1009.             $e['start'] = date_format($event->getFecha(), 'Y-m-d');
  1010.             array_push($events$e);
  1011.             
  1012.         }
  1013.         $response = new JsonResponse($events);
  1014.         return $response;
  1015.     }
  1016.     /**
  1017.      * @Route("/newbysol/{id}", name="agenda_newbysol", methods={"GET", "POST"})
  1018.      */
  1019.     public function newbysol($idAgendaRepository $agendaRepositoryColegioParticipanteRepository $colegioParticipanteRepositorySolicitudRepository $solicitudRepository): Response
  1020.     {
  1021.         $entityManager $this->getDoctrine()->getManager();
  1022.         $sol $entityManager->getRepository(Solicitud::class)->findOneById($id);
  1023.         // Idempotencia: verificar AMBAS FKs para evitar duplicados aunque una esté desfasada.
  1024.         // FK autoritativa: agenda.solicitud_id (leída vía $sol->getAgendas())
  1025.         if ($sol->getAgendas()->count() > 0) {
  1026.             return $this->redirectToRoute('agenda_edit', ['id' => $sol->getAgendas()->first()->getId()]);
  1027.         }
  1028.         // Fallback: verificar solicitud.agenda_id por si no se ha sincronizado aún
  1029.         if ($sol->getAgenda()) {
  1030.             return $this->redirectToRoute('agenda_edit', ['id' => $sol->getAgenda()->getId()]);
  1031.         }
  1032.         // Bloquear creación de agenda si la solicitud está rechazada o cancelada
  1033.         $estadoNombreActual $sol->getEstado() ? strtolower($sol->getEstado()->getNombre()) : null;
  1034.         if (in_array($estadoNombreActual, ['rechazada''cancelada'])) {
  1035.             $this->addFlash('error''No se puede crear una agenda para una solicitud con estado "' $sol->getEstado()->getNombre() . '".');
  1036.             return $this->redirectToRoute('app_solicitud_edit', ['id' => $sol->getId()]);
  1037.         }
  1038.         $estado $entityManager->getRepository(EstadoAgenda::class)->findOneByNombre("agendada");
  1039.         // Mapeo de tipo de solicitud → TipoAgenda (case-insensitive via trim/lower)
  1040.         $tipoNombre trim($sol->getTipo() ?? '');
  1041.         $tipo null;
  1042.         if (strcasecmp($tipoNombre'Tour') === 0) {
  1043.             $tipo $entityManager->getRepository(TipoAgenda::class)->findOneByNombre("Tour");
  1044.         } elseif (strcasecmp($tipoNombre'Feria') === 0) {
  1045.             $tipo $entityManager->getRepository(TipoAgenda::class)->findOneByNombre("Feria");
  1046.         } elseif (strcasecmp($tipoNombre'Charla') === 0) {
  1047.             $tipo $entityManager->getRepository(TipoAgenda::class)->findOneByNombre("Charla");
  1048.         } elseif (strcasecmp($tipoNombre'Campus abierto') === 0) {
  1049.             $tipo $entityManager->getRepository(TipoAgenda::class)->findOneByNombre("Campus abierto");
  1050.         }
  1051.         // Si el tipo no se resuelve, queda null (tipoAgenda no asignado)
  1052.         // Buscar estado "agendada" de solicitud (case-insensitive)
  1053.         $estadosol $entityManager->getRepository(EstadoSolicitud::class)->createQueryBuilder('e')
  1054.             ->where('LOWER(e.nombre) = :nombre')
  1055.             ->setParameter('nombre''agendada')
  1056.             ->setMaxResults(1)
  1057.             ->getQuery()
  1058.             ->getOneOrNullResult();
  1059.         $agenda = new Agenda();
  1060.         $agenda->setFecha($sol->getHorario());
  1061.         $agenda->setNAlumnos($sol->getCantidadAlumnos());
  1062.         $agenda->setEstadoAgenda($estado);
  1063.         $agenda->setEncargado($sol->getNombreEncargado());
  1064.         $agenda->setTelefonoencargado($sol->getTelefonoEncargado());
  1065.         if($sol->getColegio()){
  1066.             $agenda->setComuna($sol->getColegio()->getComuna());
  1067.         }
  1068.         $agenda->setComentarioFinal("creado desde solicitud");
  1069.         $agenda->setTipoAgenda($tipo);
  1070.         $agenda->setSolicitud($sol);
  1071.         $agenda->setCurso($sol->getCursos());
  1072.         if($sol->getCampus()){
  1073.             $agenda->setLugar($sol->getCampus());
  1074.             $agenda->setDireccion($sol->getCampus());
  1075.         }
  1076.         if($sol->getDireccion()){
  1077.             $agenda->setDireccion($sol->getDireccion());
  1078.         }
  1079.         if($sol->getTipo()=="Feria"){
  1080.             $agenda->setDireccion($sol->getDireccion());
  1081.         }
  1082.         $agenda->setObservacion($sol->getComentario());
  1083.         $agendaRepository->add($agendatrue);
  1084.         $colegioParticipante = new ColegioParticipante();
  1085.         $colegioParticipante->setAgenda($agenda);
  1086.         $colegioParticipante->setColegio($sol->getColegio());
  1087.         $colegioParticipanteRepository->add($colegioParticipantetrue);
  1088.         if ($estadosol) {
  1089.             $sol->setEstado($estadosol);
  1090.         }
  1091.         $sol->setAgenda($agenda);
  1092.         $solicitudRepository->add($soltrue);
  1093.         return $this->redirectToRoute('agenda_edit', array(
  1094.             'id' => $agenda->getId(),
  1095.         ));
  1096.     }
  1097.     /**
  1098.      * @Route("/{id}/toggle-postulacion", name="agenda_toggle_postulacion", methods={"POST"})
  1099.      */
  1100.     public function togglePostulacion(Agenda $agendaRequest $request): Response
  1101.     {
  1102.         if (!$this->isCsrfTokenValid('toggle_postulacion_' $agenda->getId(), $request->request->get('_token'))) {
  1103.             return new \Symfony\Component\HttpFoundation\JsonResponse(['error' => 'Token inválido'], 403);
  1104.         }
  1105.         $entityManager $this->getDoctrine()->getManager();
  1106.         $nuevoEstado = !$agenda->isAbiertopostulacion();
  1107.         $agenda->setAbiertopostulacion($nuevoEstado);
  1108.         $entityManager->flush();
  1109.         return new \Symfony\Component\HttpFoundation\JsonResponse([
  1110.             'success' => true,
  1111.             'abiertopostulacion' => $nuevoEstado,
  1112.         ]);
  1113.     }
  1114.     /**
  1115.      * @Route("/{id}/cerrar_convocatoria", name="cerrar_convocatoria", methods={"GET", "POST"})
  1116.      */
  1117.     public function cerrar_convocatoria(Agenda $agenda): Response
  1118.     {
  1119.         $entityManager $this->getDoctrine()->getManager();
  1120.         $agenda->setAbiertopostulacion(0);
  1121.         $entityManager->persist($agenda);
  1122.         $entityManager->flush();
  1123.         $embajador_participantes $entityManager->getRepository(EmbajadorParticipante::class)->findBy(array('agenda' =>$agenda
  1124.         'estado' =>'pendiente seleccion'));
  1125.         foreach ($embajador_participantes as $embajador){
  1126.             if ($embajador->getEstado() == 'pendiente seleccion'){
  1127.                 $embajador->setEstado('no seleccionado');
  1128.                 $entityManager->persist($embajador);
  1129.                 $entityManager->flush();
  1130.             }
  1131.         }
  1132.         return $this->redirectToRoute('agenda_show', array(
  1133.             'id' => $agenda->getId(),
  1134.         ));
  1135.     }
  1136.     /**
  1137.      * @Route("/{id}/cerraragenda", name="agenda_cerraragenda", methods={"GET", "POST"})
  1138.      */
  1139.     public function cerraragenda(Agenda $agenda): Response
  1140.     {
  1141.         $entityManager $this->getDoctrine()->getManager();
  1142.         $estado $entityManager->getRepository(EstadoAgenda::class)->findOneByNombre("realizada");
  1143.         $agenda->setEstadoAgenda($estado);
  1144.         $entityManager->flush();
  1145.         $this->addFlash('success''Actividad marcada como realizada. Ahora puedes enviar las encuestas por separado.');
  1146.         return $this->redirectToRoute('agenda_show', array('id' => $agenda->getId()));
  1147.     }
  1148.     /**
  1149.      * @Route("/{id}/cancelar-con-postulantes", name="agenda_cancelar_con_postulantes", methods={"POST"}, requirements={"id"="\d+"})
  1150.      */
  1151.     public function cancelarConPostulantes(Request $requestAgenda $agendaEmbajadorParticipanteRepository $embajadorParticipanteRepository): Response
  1152.     {
  1153.         if (!$this->isCsrfTokenValid('cancelar-postulantes'.$agenda->getId(), $request->request->get('_token'))) {
  1154.             $this->addFlash('error''Token CSRF inválido.');
  1155.             return $this->redirectToRoute('agenda_show', array('id' => $agenda->getId()));
  1156.         }
  1157.         $entityManager $this->getDoctrine()->getManager();
  1158.         $estadoCancelada $entityManager->getRepository(EstadoAgenda::class)->findOneBy(['nombre' => 'cancelada'])
  1159.             ?? $entityManager->getRepository(EstadoAgenda::class)->findOneBy(['nombre' => 'Cancelada']);
  1160.         $postulantes $embajadorParticipanteRepository->findBy(array('agenda' => $agenda));
  1161.         $eliminados 0;
  1162.         foreach ($postulantes as $postulante) {
  1163.             $embajadorParticipanteRepository->remove($postulantetrue);
  1164.             $eliminados++;
  1165.         }
  1166.         if ($estadoCancelada) {
  1167.             $agenda->setEstadoAgenda($estadoCancelada);
  1168.         }
  1169.         $entityManager->flush();
  1170.         $this->addFlash('success'sprintf(
  1171.             'Actividad cancelada y %d embajador(es) postulante(s) eliminado(s).',
  1172.             $eliminados
  1173.         ));
  1174.         return $this->redirectToRoute('agenda_show', array('id' => $agenda->getId()));
  1175.     }
  1176.     /**
  1177.      * @Route("/{id}/cerraragendaorientador", name="agenda_cerraragendaorientador", methods={"GET", "POST"})
  1178.      */
  1179.     public function enviaragendaorientadorMensajes $mensajesAgenda $agendaEvaluacioncolegioRepository $evaluacioncolegioRepository ): Response
  1180.     {
  1181.         $entityManager $this->getDoctrine()->getManager();
  1182.         if($agenda->getSolicitud()){
  1183.             $characters 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  1184.             $charactersLength strlen($characters);
  1185.             $randomString '';
  1186.             $maxAttempts 10;
  1187.             do {
  1188.                 $randomString '';
  1189.                 for ($i 0$i 6$i++) {
  1190.                     $randomString .= $characters[random_int(0$charactersLength 1)];
  1191.                 }
  1192.                 $exists null !== $evaluacioncolegioRepository->findOneBy(['hash' => $randomString]);
  1193.             } while ($exists && $maxAttempts-- > 0);
  1194.             if ($exists) {
  1195.                 // Espacio agotado: fallback a un hash criptográficamente aleatorio
  1196.                 // para garantizar unicidad sin colisiones.
  1197.                 $randomString bin2hex(random_bytes(8));
  1198.             }
  1199.             $evaluacion = new Evaluacioncolegio();
  1200.             $evaluacion->setHash($randomString);
  1201.             $evaluacion->setAgenda($agenda);
  1202.             $evaluacion->setCorreo($agenda->getSolicitud()->getCorreoEncargado());
  1203.             $evaluacion->setCargo($agenda->getSolicitud()->getCargoEncargado());
  1204.             $evaluacion->setNombre($agenda->getSolicitud()->getNombreEncargado());
  1205.             $evaluacioncolegioRepository->add($evaluaciontrue);
  1206.             $encuestaUrl $this->generateUrl('app_evaluacioncolegio_edit', ['hash' => $evaluacion->getHash()], UrlGeneratorInterface::ABSOLUTE_URL);
  1207.             $reporteLinks '';
  1208.             $colegio $agenda->getSolicitud()->getColegio();
  1209.             if ($colegio) {
  1210.                 foreach ($colegio->getColegiodetalles() as $detalle) {
  1211.                     $reporteUrl $this->generateUrl('app_colegio_reporte2', ['id' => $detalle->getId()], UrlGeneratorInterface::ABSOLUTE_URL);
  1212.                     $reporteLinks .= "<br><a href=\"".$reporteUrl."\">Informe de rendimiento &mdash; ".$detalle->getNombre()."</a>";
  1213.                 }
  1214.             }
  1215.             $msj=
  1216.                 "Hola,<br><br>
  1217.  Gracias por participar en la actividad ".$agenda->getTipoAgenda()->getNombre().", el día ".date_format($agenda->getFecha(),"d/m/Y").",
  1218. <br><br>
  1219.  Por favor ayudanos contestando una encuesta en el siguiente link: ".$encuestaUrl."
  1220.  ".('' !== $reporteLinks "<br><br>Además, puedes descargar tu(s) informe(s) de rendimiento desde el siguiente enlace:".$reporteLinks '')."
  1221.  ";
  1222.             $mensajes->enviar2Mensaje($agenda->getSolicitud()->getCorreoEncargado(),"vivian.avalos@uc.cl""Evaluación de actividad"$msj);
  1223.             $this->addFlash('success''Encuesta enviada al colegio correctamente.');
  1224.         } else {
  1225.             $this->addFlash('warning''Esta agenda no tiene solicitud asociada, no se pudo enviar la encuesta al colegio.');
  1226.         }
  1227.         $entityManager->flush();
  1228.         return $this->redirectToRoute('agenda_show', array('id' => $agenda->getId()));
  1229.     }
  1230.     /**
  1231.      * @Route("/{id}/enviar-encuesta-embajadores", name="agenda_enviar_encuesta_embajadores", methods={"GET", "POST"})
  1232.      */
  1233.     public function enviarEncuestaEmbajadoresMensajes $mensajesAgenda $agendaEvaluacionembajadorRepository $evaluacionembajadorRepository ): Response
  1234.     {
  1235.         $entityManager $this->getDoctrine()->getManager();
  1236.         $embajadores $entityManager->getRepository(EmbajadorParticipante::class)->findBy(array('estado'=>'seleccionado','agenda'=>$agenda));
  1237.         if (empty($embajadores)) {
  1238.             $this->addFlash('warning''No hay embajadores seleccionados en esta actividad.');
  1239.             return $this->redirectToRoute('agenda_show', array('id' => $agenda->getId()));
  1240.         }
  1241.         foreach ($embajadores as $embajador) {
  1242.             $evaluacionembajador = new Evaluacionembajador();
  1243.             $evaluacionembajador->setEmbajador($embajador->getEmbajadores());
  1244.             $evaluacionembajador->setAgenda($agenda);
  1245.             $evaluacionembajador->setContestada(0);
  1246.             $evaluacionembajadorRepository->add($evaluacionembajadortrue);
  1247.             $msj2=
  1248.                 "Hola,<br><br>
  1249.  
  1250.  Gracias por participar en la actividad ".$agenda->getTipoAgenda()->getNombre().", el día ".date_format($agenda->getFecha(),"d/m/Y").", 
  1251.  
  1252. <br><br>
  1253.  
  1254.  Por favor ayudanos contestando una encuesta en la plataforma
  1255.  ";
  1256.             $mensajes->enviar2Mensaje($embajador->getEmbajadores()->getCorreo(),"vivian.avalos@uc.cl""Evaluación de actividad"$msj2);
  1257.         }
  1258.         $agenda->setEncuestaenviada(1);
  1259.         $entityManager->flush();
  1260.         $this->addFlash('success''Encuestas enviadas a ' count($embajadores) . ' embajador(es) correctamente.');
  1261.         return $this->redirectToRoute('agenda_show', array('id' => $agenda->getId()));
  1262.     }
  1263.     /**
  1264.      * @Route("cargar_excel", name="agenda_cargar_excel", methods={"GET", "POST"})
  1265.      */
  1266.     public function cargarExcel(Request $request): Response
  1267.     {
  1268.         $em $this->getDoctrine()->getManager();
  1269.         $agendasCreadas = [];
  1270.         if ($request->isMethod('POST')) {
  1271.             $fechaImportacion = (new \DateTime())->format('Y-m-d');
  1272.             $codigoImportacion strtoupper(substr(bin2hex(random_bytes(3)), 05));
  1273.             /** @var UploadedFile $file */
  1274.             $file $request->files->get('excel');
  1275.             if ($file && $file->getClientOriginalExtension() === 'xlsx') {
  1276.                 $spreadsheet IOFactory::load($file->getPathname());
  1277.                 $sheet $spreadsheet->getActiveSheet();
  1278.                 $agendaActual null;
  1279.                 foreach ($sheet->toArray(nulltruetruetrue) as $index => $row) {
  1280.                     if ($index === || empty($row['C'])) continue;
  1281.                     // Si hay nueva fecha => nueva agenda
  1282.                     if (!empty($row['A'])) {
  1283.                         // FECHA base
  1284.                         if (is_numeric($row['A'])) {
  1285.                             $baseDate = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($row['A']);
  1286.                         } else {
  1287.                             $baseDate = \DateTime::createFromFormat('d-M'$row['A']) ??
  1288.                                 \DateTime::createFromFormat('d/m/Y'$row['A']) ??
  1289.                                 \DateTime::createFromFormat('d-m-Y'$row['A']);
  1290.                             if ($baseDate && $baseDate->format('Y') === '1970') {
  1291.                                 $baseDate->setDate(2025$baseDate->format('m'), $baseDate->format('d'));
  1292.                             }
  1293.                         }
  1294.                         // Horario: "10:00 - 13:00"
  1295.                         $horario $row['B'] ?? null;
  1296.                         $horaInicio $horaFin null;
  1297.                         if ($horario && strpos($horario'-') !== false) {
  1298.                             [$horaInicio$horaFin] = array_map('trim'explode('-'$horario));
  1299.                         }
  1300.                         // Fecha inicio y hasta
  1301.                         $fecha = clone $baseDate;
  1302.                         if ($horaInicio) {
  1303.                             [$h$m] = explode(':'$horaInicio);
  1304.                             $fecha->setTime((int)$h, (int)$m);
  1305.                         }
  1306.                         $hasta = clone $baseDate;
  1307.                         if ($horaFin) {
  1308.                             [$h2$m2] = explode(':'$horaFin);
  1309.                             $hasta->setTime((int)$h2, (int)$m2);
  1310.                         }
  1311.                         // Crear agenda
  1312.                         $agendaActual = new Agenda();
  1313.                         $agendaActual->setFecha($fecha);
  1314.                         $agendaActual->setHasta($hasta);
  1315.                         $agendaActual->setMonto(19700);
  1316.                        // $agendaActual->setHorariotaxi($horario);
  1317.                         $agendaActual->setDireccion($row['H'] ?? null);
  1318.                         $agendaActual->setObservacion('panoramia');
  1319.                         $agendaActual->setComentarioFinal("Importada $fechaImportacion - $codigoImportacion");
  1320.                         $agendaActual->setCurso('1º-4º');
  1321.                         $agendaActual->setComuna($row['E'] ?? null);
  1322.                         $agendaActual->setLugar($row['F'] ?? null);
  1323.                         $tipoAgenda $em->getRepository(\App\Entity\TipoAgenda::class)->find(13);
  1324.                         $estadoAgenda $em->getRepository(\App\Entity\EstadoAgenda::class)->find(1);
  1325.                         $agendaActual->setTipoAgenda($tipoAgenda);
  1326.                         $agendaActual->setEstadoAgenda($estadoAgenda);
  1327.                         $em->persist($agendaActual);
  1328.                         $em->flush(); // para tener el ID inmediatamente
  1329.                         $agendasCreadas[] = [
  1330.                             'id' => $agendaActual->getId(),
  1331.                             'lugar' => $agendaActual->getLugar(),
  1332.                             'comuna' => $agendaActual->getComuna(),
  1333.                             'fecha' => $agendaActual->getFecha()->format('Y-m-d H:i'),
  1334.                             'hasta' => $agendaActual->getHasta()->format('H:i'),
  1335.                             'codigo' => $codigoImportacion,
  1336.                         ];
  1337.                     }
  1338.                     // Colegio
  1339.                     $rbd $row['C'];
  1340.                     $nombre $row['F'] ?? null;
  1341.                     $direccion $row['H'] ?? null;
  1342.                     $contacto $row['I'] ?? null;
  1343.                     $fono $row['J'] ?? null;
  1344.                     $email $row['K'] ?? null;
  1345.                     $colegio $em->getRepository(Colegio::class)->findOneBy(['id' => $rbd]);
  1346.                     if (!$colegio) {
  1347.                         $colegio = new Colegio();
  1348.                         $colegio->setRbd($rbd);
  1349.                         $colegio->setNombre($nombre);
  1350.                         $colegio->setDireccion($direccion);
  1351.                         $em->persist($colegio);
  1352.                         $em->flush();
  1353.                     }
  1354.                     if ($agendaActual && $colegio) {
  1355.                         $cp = new ColegioParticipante();
  1356.                         $cp->setAgenda($agendaActual);
  1357.                         $cp->setColegio($colegio);
  1358.                         $cp->setObservacion($row['O'] ?? '');
  1359.                         $em->persist($cp);
  1360.                     }
  1361.                 }
  1362.                 $em->flush();
  1363.                 return $this->render('agenda/cargar-excel-resumen.html.twig', [
  1364.                     'agendas' => $agendasCreadas,
  1365.                 ]);
  1366.             }
  1367.         }
  1368.         return $this->render('agenda/cargar-excel.html.twig');
  1369.     }
  1370.     /**
  1371.      * @Route("/unidad-academica/aceptar/{id}", name="unidad_academica_aceptar", methods={"POST"})
  1372.      */
  1373.     public function aceptarUnidadAcademica(Request $requestUnidadAcademicaPostulacion $postulacionMensajes $mensajes): Response
  1374.     {
  1375.         $motivo $request->request->get('motivo');
  1376.         $postulacion->setAceptada(true);
  1377.         $this->getDoctrine()->getManager()->flush();
  1378.         $mensaje "Su postulación ha sido aceptada. Motivo: " $motivo;
  1379.         $mensajes->enviar2Mensaje($postulacion->getUnidadAcademica(), null"Postulación aceptada"$mensaje);
  1380.         return $this->redirectToRoute('agenda_show', ['id' => $postulacion->getAgenda()->getId()]);
  1381.     }
  1382.     /**
  1383.      * @Route("/unidad-academica/rechazar/{id}", name="unidad_academica_rechazar", methods={"POST"})
  1384.      */
  1385.     public function rechazarUnidadAcademica(Request $requestUnidadAcademicaPostulacion $postulacionMensajes $mensajes): Response
  1386.     {
  1387.         $motivo $request->request->get('motivo');
  1388.         $postulacion->setEstado('rechazado');
  1389.         $this->getDoctrine()->getManager()->flush();
  1390.         $mensaje "Su postulación ha sido rechazada. Motivo: " $motivo;
  1391.         $mensajes->enviar2Mensaje($postulacion->getUnidadAcademica(), null"Postulación rechazada"$mensaje);
  1392.         return $this->redirectToRoute('agenda_show', ['id' => $postulacion->getAgenda()->getId()]);
  1393.     }
  1394.     /**
  1395.      * Confirma asistencia mediante código QR
  1396.      * 
  1397.      * @Route("/confirmacion/{codigoQr}", name="agenda_confirmacion_qr", methods={"GET"})
  1398.      */
  1399.     public function confirmarQR(string $codigoQrQRConfirmacionService $qrService): Response
  1400.     {
  1401.         try {
  1402.             $resultado $qrService->confirmarAsistencia($codigoQr);
  1403.             
  1404.             if ($resultado['confirmado']) {
  1405.                 $this->addFlash('success'sprintf(
  1406.                     '¡Asistencia confirmada exitosamente! %s - %s',
  1407.                     $resultado['tipo'],
  1408.                     $resultado['agenda']
  1409.                 ));
  1410.                 
  1411.                 return $this->render('agenda/confirmacion_resultado.html.twig', [
  1412.                     'exito' => true,
  1413.                     'resultado' => $resultado,
  1414.                     'mensaje' => 'Asistencia confirmada exitosamente'
  1415.                 ]);
  1416.             } else {
  1417.                 $this->addFlash('warning'$resultado['mensaje']);
  1418.                 
  1419.                 return $this->render('agenda/confirmacion_resultado.html.twig', [
  1420.                     'exito' => false,
  1421.                     'resultado' => $resultado,
  1422.                     'mensaje' => $resultado['mensaje']
  1423.                 ]);
  1424.             }
  1425.         } catch (\Exception $e) {
  1426.             $this->addFlash('error''Error al confirmar asistencia: ' $e->getMessage());
  1427.             
  1428.             return $this->render('agenda/confirmacion_resultado.html.twig', [
  1429.                 'exito' => false,
  1430.                 'resultado' => null,
  1431.                 'mensaje' => 'Código QR no válido o no encontrado'
  1432.             ]);
  1433.         }
  1434.     }
  1435.     /**
  1436.      * Página de escaneo QR
  1437.      * 
  1438.      * @Route("/escanear-qr", name="agenda_escanear_qr", methods={"GET"})
  1439.      */
  1440.     public function escanearQR(): Response
  1441.     {
  1442.         return $this->render('agenda/escanear_qr.html.twig');
  1443.     }
  1444. }