<?php
namespace App\Controller;
use App\Entity\Embajadores;
use App\Entity\Agenda;
use App\Entity\ColegioParticipante;
use App\Entity\EmbajadorParticipante;
use App\Entity\EstadoAgenda;
use App\Entity\EstadoSolicitud;
use App\Entity\Solicitud;
use App\Entity\TipoAccion;
use App\Entity\TipoAgenda;
use App\Form\AgendaType;
use App\Form\Agenda2Type;
use App\Repository\AgendaRepository;
use App\Repository\ColegioParticipanteRepository;
use App\Repository\EmbajadorParticipanteRepository;
use App\Repository\SolicitudRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use App\Service\Mensajes;
use App\Entity\Evaluacioncolegio;
use App\Repository\EvaluacioncolegioRepository;
use App\Repository\EvaluacionembajadorRepository;
use App\Entity\Evaluacionembajador;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use App\Entity\Colegio;
use App\Entity\UnidadAcademicaPostulacion;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use App\Service\QRConfirmacionService;
use App\Service\MassEmailService;
use App\Service\EmailTemplateService;
use Psr\Log\LoggerInterface;
/**
* @Route("/agenda")
*/
class AgendaController extends AbstractController
{
/**
* @Route("/", name="agenda_index", methods={"GET"})
*/
public function index(AgendaRepository $agendaRepository): Response
{
$entityManager = $this->getDoctrine()->getManager();
// Buscar estados tanto en minúsculas como con mayúscula inicial
$estados_minusculas = array("agendada", "realizada", "cancelada");
$estados_mayusculas = array("Agendada", "Realizada", "Cancelada");
$estados = $entityManager->getRepository(EstadoAgenda::class)
->createQueryBuilder('e')
->where('e.nombre IN (:nombres)')
->setParameter('nombres', array_merge($estados_minusculas, $estados_mayusculas))
->getQuery()
->getResult();
// Usar QueryBuilder para consulta compleja
$agendas = $agendaRepository->createQueryBuilder('a')
->where('a.estadoAgenda IN (:estados)')
->andWhere('(a.archivada IS NULL OR a.archivada = :false)')
->setParameter('estados', $estados)
->setParameter('false', false)
->orderBy('a.id', 'DESC')
->getQuery()
->getResult();
return $this->render('agenda/index.html.twig', [
'agendas' => $agendas,
]);
}
/**
* @Route("/export/{anio}", name="agenda_export", methods={"GET"}, defaults={"anio"="todos"})
*/
public function exportAgenda(string $anio): Response
{
$this->denyAccessUnlessGranted('ROLE_AYUDANTE');
$conn = $this->getDoctrine()->getConnection();
$conn->executeStatement('SET NAMES utf8mb4');
$whereSql = '';
$params = [];
if ($anio !== 'todos') {
$whereSql = ' WHERE a.fecha BETWEEN :inicio AND :fin';
$params['inicio'] = $anio . '-01-01';
$params['fin'] = $anio . '-12-31';
}
// Agendas base (include encargado fallback fields)
$agendas = $conn->fetchAllAssociative(
'SELECT a.id, a.fecha, a.hasta, a.direccion, a.lugar, a.n_alumnos, a.curso, a.observacion,
a.taxi, a.horariotaxi, a.encargado AS encargado, a.telefonoencargado AS telefono_encargado,
ta.nombre AS tipo, ea.nombre AS estado,
s.nombre_encargado AS solicitante_nombre, s.correo_encargado AS solicitante_correo
FROM agenda a
LEFT JOIN tipo_agenda ta ON ta.id = a.tipo_agenda_id
LEFT JOIN estado_agenda ea ON ea.id = a.estado_agenda_id
LEFT JOIN solicitud s ON s.id = a.solicitud_id'
. $whereSql .
' ORDER BY a.fecha ASC',
$params
);
if (empty($agendas)) {
$this->addFlash('warning', 'No hay agendas para exportar.');
return $this->redirectToRoute('agenda_index');
}
$ids = array_column($agendas, 'id');
$placeholders = implode(',', array_fill(0, count($ids), '?'));
// Colegios por agenda
$colegiosRows = $conn->fetchAllAssociative(
"SELECT cp.agenda_id, c.nombre FROM colegio_participante cp
LEFT JOIN colegio c ON c.id = cp.colegio_id
WHERE cp.agenda_id IN ($placeholders)",
array_values($ids)
);
$colegiosByAgenda = [];
foreach ($colegiosRows as $r) {
$colegiosByAgenda[$r['agenda_id']][] = $r['nombre'];
}
// Embajadores por agenda
$embRows = $conn->fetchAllAssociative(
"SELECT ep.agenda_id, e.nombre FROM embajador_participante ep
LEFT JOIN embajadores e ON e.id = ep.embajadores_id
WHERE ep.agenda_id IN ($placeholders)",
array_values($ids)
);
$embByAgenda = [];
foreach ($embRows as $r) {
$embByAgenda[$r['agenda_id']][] = $r['nombre'];
}
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$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'];
foreach ($headers as $col => $h) {
$sheet->setCellValueByColumnAndRow($col + 1, 1, $h);
$sheet->getColumnDimensionByColumn($col + 1)->setWidth(22);
}
$sheet->getStyle('A1:Q1')->getFont()->setBold(true);
$row = 2;
// Safe date/time formatters
$formatDate = function ($val) {
if (empty($val)) {
return '';
}
try {
return (new \DateTime($val))->format('d/m/Y');
} catch (\Exception $e) {
return (string) $val;
}
};
$formatTime = function ($val) {
if (empty($val)) {
return '';
}
try {
return (new \DateTime($val))->format('H:i');
} catch (\Exception $e) {
return '';
}
};
foreach ($agendas as $a) {
$sheet->setCellValueByColumnAndRow(1, $row, $a['id']);
$sheet->setCellValueByColumnAndRow(2, $row, $formatDate($a['fecha'] ?? ''));
$sheet->setCellValueByColumnAndRow(3, $row, $formatTime($a['fecha'] ?? ''));
$sheet->setCellValueByColumnAndRow(4, $row, $formatTime($a['hasta'] ?? ''));
$sheet->setCellValueByColumnAndRow(5, $row, $a['tipo'] ?? '');
$sheet->setCellValueByColumnAndRow(6, $row, implode(', ', $colegiosByAgenda[$a['id']] ?? []));
// Prefer solicitud contact fields; fall back to agenda.encargado
$contactNombre = $a['solicitante_nombre'] ?? '';
if (empty($contactNombre) && !empty($a['encargado'])) {
$contactNombre = $a['encargado'];
}
$sheet->setCellValueByColumnAndRow(7, $row, $contactNombre);
$sheet->setCellValueByColumnAndRow(8, $row, $a['solicitante_correo'] ?? '');
$sheet->setCellValueByColumnAndRow(9, $row, $a['direccion'] ?? '');
$sheet->setCellValueByColumnAndRow(10, $row, $a['lugar'] ?? '');
$sheet->setCellValueByColumnAndRow(11, $row, $a['estado'] ?? '');
$sheet->setCellValueByColumnAndRow(12, $row, $a['n_alumnos'] ?? '');
$sheet->setCellValueByColumnAndRow(13, $row, $a['curso'] ?? '');
$sheet->setCellValueByColumnAndRow(14, $row, implode(', ', $embByAgenda[$a['id']] ?? []));
$sheet->setCellValueByColumnAndRow(15, $row, !empty($a['taxi']) ? 'Sí' : 'No');
$sheet->setCellValueByColumnAndRow(16, $row, $a['horariotaxi'] ?? '');
$sheet->setCellValueByColumnAndRow(17, $row, $a['observacion'] ?? '');
$row++;
}
$filename = 'agendas_' . ($anio === 'todos' ? 'todas' : $anio) . '.xlsx';
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
$response = new StreamedResponse(function () use ($writer) { $writer->save('php://output'); });
$response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
$response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"');
return $response;
}
/**
* @Route("/bulk-change-estado", name="agenda_bulk_change_estado", methods={"POST"})
*/
public function bulkChangeEstado(Request $request): Response
{
$entityManager = $this->getDoctrine()->getManager();
$agendaIds = $request->request->get('agenda_ids', []);
$nuevoEstadoNombre = $request->request->get('nuevo_estado');
if (empty($agendaIds) || empty($nuevoEstadoNombre)) {
$this->addFlash('error', 'No se seleccionaron agendas o no se especificó un estado.');
return $this->redirectToRoute('agenda_index');
}
// Buscar el estado por su nombre
$nuevoEstado = $entityManager->getRepository(EstadoAgenda::class)->findOneBy(['nombre' => $nuevoEstadoNombre]);
if (!$nuevoEstado) {
$this->addFlash('error', 'El estado seleccionado no es válido.');
return $this->redirectToRoute('agenda_index');
}
$contador = 0;
foreach ($agendaIds as $agendaId) {
$agenda = $entityManager->getRepository(Agenda::class)->find($agendaId);
if ($agenda) {
$agenda->setEstadoAgenda($nuevoEstado);
$entityManager->persist($agenda);
$contador++;
}
}
$entityManager->flush();
$this->addFlash('success', "Se actualizaron $contador agenda(s) al estado: $nuevoEstadoNombre");
return $this->redirectToRoute('agenda_index');
}
/**
* @Route("/convocatoria_semana", name="convocatoria_semana", methods={"GET"})
*/
public function convocatoria_semana(): Response
{
$entityManager = $this->getDoctrine()->getManager();
$hoy = new \DateTime('now');
$siete_dias_anteriores = clone $hoy;
$siete_dias_anteriores->modify('-7 days');
$repository = $entityManager->getRepository(Agenda::class);
$query = $repository->createQueryBuilder('a')
->where("a.fecha >= :siete_dias_anteriores")
->andWhere("a.fecha <= :hoy")
->setParameter('siete_dias_anteriores', $siete_dias_anteriores)
->setParameter('hoy', $hoy)
->getQuery();
$agendas_semana = $query->getResult();
return $this->render('agenda/agenda_semana.html.twig', [
'agendas' => $agendas_semana,
]);
}
/**
* @Route("/cierre", name="cierre", methods={"GET", "POST"})
*/
public function cierre(Mensajes $mensajes): Response
{
$entityManager = $this->getDoctrine()->getManager();
$actividades_cerradas_semana = $entityManager->getRepository(Agenda::class)->findByAbiertopostulacion(0);
$hoy = new \DateTime('now');
$seis_dias_siguintes = clone $hoy;
$seis_dias_siguintes->modify('+7 days');
$agendas = $entityManager->getRepository(Agenda::class)->createQueryBuilder('b')
->where('b.id IN (:ids)')
->andWhere('b.fecha BETWEEN :hoy AND :seis_dias_siguientes')
->setParameter('ids', $actividades_cerradas_semana)
->setParameter('hoy', $hoy)
->setParameter('seis_dias_siguientes', $seis_dias_siguintes)
->getQuery()
->getResult();
return $this->render('agenda/apertura_o_cierre.html.twig', [
'agendas' => $agendas,
'aperturaocierre' => 1
]);
}
/**
* @Route("/correo_cierre", name="correo_cierre", methods={"GET", "POST"})
*/
public function correo_cierre(Mensajes $mensajes): Response
{
$entityManager = $this->getDoctrine()->getManager();
$actividades_cerradas_semana = $entityManager->getRepository(Agenda::class)->findByAbiertopostulacion(0);
$hoy = new \DateTime('now');
$seis_dias_siguintes = clone $hoy;
$seis_dias_siguintes->modify('+7 days');
$agendas = $entityManager->getRepository(Agenda::class)->createQueryBuilder('b')
->where('b.id IN (:ids)')
->andWhere('b.fecha BETWEEN :hoy AND :seis_dias_siguientes')
->setParameter('ids', $actividades_cerradas_semana)
->setParameter('hoy', $hoy)
->setParameter('seis_dias_siguientes', $seis_dias_siguintes)
->getQuery()
->getResult();
$url = $this->redirectToRoute('actividades_cerradas_by_e')->getTargetUrl();
$embajadores = array();
foreach ($agendas as $agenda){
$embajador_participantes = $agenda->getEmbajadorParticipantes();
foreach ($embajador_participantes as $embajador_participante){
array_push($embajadores, $embajador_participante->getEmbajadores()->getId());
}
}
$embajadores_final = $entityManager->getRepository(Embajadores::class)->findById($embajadores);
foreach ($embajadores_final as $embajador){
$mensajes->enviar2Mensaje($embajador->getCorreo(), 'difusion@delfy.cl', 'Cierre actividad', 'Estimados embajadores y embajadoras: <br>
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>
Ferias Panoramia: <br>
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>
Ferias de colegios:<br>
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.
Tour: <br>
Recibirán un correo con las instrucciones para la actividad.<br>
Charla Presencial:<br>
Recibirán un correo con las instrucciones para la actividad.<br><br><br>
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>
Recuerden que a las Ferias Panoramia no llevan material por lo que no se asigna taxi y deben llegar directo al colegio.<br>
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>');
}
return $this->redirectToRoute('cierre');
}
/**
* @Route("/actividades_cerradas_by_e", name="actividades_cerradas_by_e", methods={"GET"})
*/
public function actividades_cerradas_by_e(Mensajes $mensajes): Response
{
$entityManager = $this->getDoctrine()->getManager();
$actividades_cerradas_semana = $entityManager->getRepository(Agenda::class)->findByAbiertopostulacion(0);
$hoy = new \DateTime('now');
$seis_dias_siguintes = clone $hoy;
$seis_dias_siguintes->modify('+7 days');
$agendas = $entityManager->getRepository(Agenda::class)->createQueryBuilder('b')
->where('b.id IN (:ids)')
->andWhere('b.fecha BETWEEN :hoy AND :seis_dias_siguientes')
->setParameter('ids', $actividades_cerradas_semana)
->setParameter('hoy', $hoy)
->setParameter('seis_dias_siguientes', $seis_dias_siguintes)
->getQuery()
->getResult();
return $this->render('agenda/aperturaocierre_e.html.twig', [
'agendas' => $agendas
]);
}
/**
* @Route("/apertura", name="apertura", methods={"GET", "POST"})
*/
public function apertura(Mensajes $mensajes): Response
{
$entityManager = $this->getDoctrine()->getManager();
$actividades_cerradas_semana = $entityManager->getRepository(Agenda::class)->findByAbiertopostulacion(1);
$hoy = new \DateTime('now');
$seis_dias_siguintes = clone $hoy;
$seis_dias_siguintes->modify('+7 days');
$agendas = $entityManager->getRepository(Agenda::class)->createQueryBuilder('b')
->where('b.id IN (:ids)')
->andWhere('b.fecha BETWEEN :hoy AND :seis_dias_siguientes')
->setParameter('ids', $actividades_cerradas_semana)
->setParameter('hoy', $hoy)
->setParameter('seis_dias_siguientes', $seis_dias_siguintes)
->getQuery()
->getResult();
return $this->render('agenda/apertura_o_cierre.html.twig', [
'agendas' => $agendas,
'aperturaocierre' => 0,
]);
}
/**
* @Route("/abiertas", name="agenda_index_abiertas", methods={"GET"})
*/
public function indexabiertas(AgendaRepository $agendaRepository): Response
{
$entityManager = $this->getDoctrine()->getManager();
return $this->render('agenda/index.html.twig', [
'agendas' => $agendaRepository->findBy(array("abiertopostulacion"=>1)),
]);
}
/**
* @Route("/estudiantes", name="agenda_index_estudiante", methods={"GET"})
* @IsGranted("ROLE_ESTUDIANTE")
*/
public function indexestudiantes(Request $request, AgendaRepository $agendaRepository): Response
{
$entityManager = $this->getDoctrine()->getManager();
$estados_aceptados = array ("agendada");
$estados = $entityManager->getRepository(EstadoAgenda::class)->findByNombre($estados_aceptados);
$embajador = $entityManager->getRepository(Embajadores::class)->findOneBy(['correo' => $this->getUser()->getUserIdentifier()]);
if (!$embajador || strtolower(trim($embajador->getEstado() ?? '')) === 'no vigente') {
return $this->render('agenda/novigente.html.twig');
}
// Si el admin habilitó la actualización de datos, redirigir al formulario SOLO al entrar por primera vez (login)
// El banner en la vista se encarga de recordarlo en visitas posteriores
if ($embajador && $embajador->isActualiza() && $request->query->get('desde') === 'login') {
return $this->redirectToRoute('app_embajadores_actualiza', ['id' => $embajador->getId()]);
}
return $this->render('agenda/indexpostulantes.html.twig', [
'embajador'=> $embajador,
'iduser'=> $embajador ? $embajador->getId() : 0,
'pendienteActualizacion' => $embajador && (bool)$embajador->isActualiza(),
'agendas' => $agendaRepository->findBy(array("estadoAgenda"=>$estados, "abiertopostulacion"=> 1)),
'user'=>$this->getUser(),
]);
}
/**
* @Route("/estudiantesconfirmado", name="agenda_index_estudiante_confirmadas", methods={"GET"})
*/
public function indexestudiantesconfirmadas(AgendaRepository $agendaRepository): Response
{
if (!$this->getUser()) {
return $this->redirectToRoute('app_login');
}
$entityManager = $this->getDoctrine()->getManager();
$embajador = $entityManager->getRepository(Embajadores::class)->findOneBy(['correo' => $this->getUser()->getUserIdentifier()]);
if (!$embajador) {
return $this->redirectToRoute('app_login');
}
$estados_aceptados = array ("agendada");
$ids = array();
$estados = $entityManager->getRepository(EstadoAgenda::class)->findByNombre($estados_aceptados);
$aceptadas = $entityManager->getRepository(EmbajadorParticipante::class)->findBy(array('estado'=>'seleccionado','embajadores'=>$embajador->getId()));
foreach ($aceptadas as $agenda) {
if($agenda->getAgenda()){
array_push($ids, $agenda->getAgenda()->getId());
}
}
// Usar QueryBuilder para consulta con array de IDs
if (!empty($ids)) {
$agendas = $agendaRepository->createQueryBuilder('a')
->where('a.estadoAgenda IN (:estados)')
->andWhere('a.id IN (:ids)')
->setParameter('estados', $estados)
->setParameter('ids', $ids)
->getQuery()
->getResult();
} else {
$agendas = [];
}
return $this->render('agenda/indexestudiantes.html.twig', [
'iduser' => $embajador->getId(),
'agendas' => $agendas,
'aceptadas' => $aceptadas,
'user' => $this->getUser(),
]);
}
/**
* @Route("/historicas", name="agenda_index_estudiante_historicas", methods={"GET"})
*/
public function indexhistoricas(AgendaRepository $agendaRepository): Response
{
// Verificar que el usuario esté autenticado
if (!$this->getUser()) {
return $this->redirectToRoute('app_login');
}
$entityManager = $this->getDoctrine()->getManager();
$embajador = $entityManager->getRepository(Embajadores::class)->findOneBy(['correo' => $this->getUser()->getUserIdentifier()]);
if (!$embajador) {
return $this->redirectToRoute('app_login');
}
$ids = array();
$aceptadas = $entityManager->getRepository(EmbajadorParticipante::class)->findBy(array('embajadores' => $embajador->getId()));
foreach ($aceptadas as $agenda) {
if( $agenda->getAgenda()){
array_push($ids, $agenda->getAgenda()->getId());
}
}
// Usar QueryBuilder para consulta con array de IDs
if (!empty($ids)) {
$agendas = $agendaRepository->createQueryBuilder('a')
->where('a.id IN (:ids)')
->setParameter('ids', $ids)
->getQuery()
->getResult();
} else {
$agendas = [];
}
return $this->render('agenda/indexestudiantes.html.twig', [
'iduser' => $embajador->getId(),
'agendas' => $agendas,
'aceptadas' => $aceptadas,
'user' => $this->getUser(),
]);
}
/**
* @Route("/new", name="agenda_new", methods={"GET", "POST"})
*/
public function new(Request $request, AgendaRepository $agendaRepository): Response
{
$agenda = new Agenda();
$form = $this->createForm(AgendaType::class, $agenda);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$agendaRepository->add($agenda, true);
return $this->redirectToRoute('colegioparticipante_newbyagenda', array('id' => $agenda->getId()));
}
return $this->renderForm('agenda/new.html.twig', [
'agenda' => $agenda,
'form' => $form,
]);
}
/**
* @Route("/calendario", name="agenda_calendario", methods={"GET", "POST"})
*/
public function calendario(Request $request, AgendaRepository $agendaRepository): Response
{
$em = $this->getDoctrine()->getManager();
$estados_aceptados = array ("agendada", "realizada");
$estados = $em->getRepository(EstadoAgenda::class)->findByNombre($estados_aceptados);
$agendas = $em->getRepository(Agenda::class)->findByEstadoAgenda($estados);
$form = $this->createForm(Agenda2Type::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$agenda = $form->get('observacion')->getData();
return $this->redirectToRoute('agenda_show', array('id' => $agenda));
}
return $this->renderForm('agenda/calendario.html.twig', [
'agendas' => $agendas,
'form' => $form,
]);
}
/**
* @Route("/mass-email", name="agenda_mass_email", methods={"GET", "POST"})
*/
public function massEmail(Request $request): Response
{
$entityManager = $this->getDoctrine()->getManager();
// Manejar tanto GET como POST, y tanto array como string
$agendaIdsRaw = $request->request->get('agenda_ids') ?? $request->query->get('agenda_ids');
if (is_array($agendaIdsRaw)) {
$agendaIds = $agendaIdsRaw;
} elseif (is_string($agendaIdsRaw)) {
$agendaIds = explode(',', $agendaIdsRaw);
} else {
$agendaIds = [];
}
// El estado puede venir de POST (al cambiar filtro) o GET (primera carga)
$estadoFiltro = $request->request->get('estado_filtro')
?? $request->query->get('estado_filtro')
?? 'seleccionado';
if (empty($agendaIds)) {
$this->addFlash('error', 'No se seleccionaron agendas.');
return $this->redirectToRoute('agenda_index');
}
// Recopilar embajadores filtrados por estado
$recipients = [];
$estadosDisponibles = [];
foreach ($agendaIds as $agendaId) {
$agenda = $entityManager->getRepository(Agenda::class)->find($agendaId);
if ($agenda) {
foreach ($agenda->getEmbajadorParticipantes() as $embajadorParticipante) {
// Recopilar estados disponibles
$estado = $embajadorParticipante->getEstado();
if ($estado && !isset($estadosDisponibles[$estado])) {
$estadosDisponibles[$estado] = 0;
}
if ($estado) {
$estadosDisponibles[$estado]++;
}
// Filtrar por estado seleccionado
if ($estadoFiltro !== 'todos' && $embajadorParticipante->getEstado() !== $estadoFiltro) {
continue;
}
// Saltar embajadores que ya tienen correo enviado
if ($embajadorParticipante->isEnviado()) {
continue;
}
$embajador = $embajadorParticipante->getEmbajadores();
if ($embajador && $embajador->getCorreo()) {
$recipients[] = [
'email' => $embajador->getCorreo(),
'nombre' => $embajador->getNombre(),
'agenda' => $agenda->getId(),
'estado' => $embajadorParticipante->getEstado()
];
}
}
}
}
// Eliminar duplicados por email
$uniqueRecipients = [];
foreach ($recipients as $recipient) {
$uniqueRecipients[$recipient['email']] = $recipient;
}
$recipients = array_values($uniqueRecipients);
if (empty($recipients)) {
$this->addFlash('warning', "No se encontraron destinatarios con estado '{$estadoFiltro}' en las agendas seleccionadas.");
return $this->redirectToRoute('agenda_index');
}
// Obtener templates disponibles
$templates = $entityManager->getRepository(\App\Entity\Correo::class)->findAll();
return $this->render('agenda/mass_email.html.twig', [
'recipients' => $recipients,
'agenda_ids' => implode(',', $agendaIds),
'templates' => $templates,
'estado_actual' => $estadoFiltro,
'estados_disponibles' => $estadosDisponibles
]);
}
/**
* @Route("/mass-email/send", name="agenda_mass_email_send", methods={"POST"})
*/
public function massEmailSend(Request $request, MassEmailService $massEmailService, LoggerInterface $logger): Response
{
try {
// Aumentar tiempo de ejecución y límite de memoria para envíos masivos
set_time_limit(600); // 10 minutos
ini_set('memory_limit', '512M');
$entityManager = $this->getDoctrine()->getManager();
$agendaIds = explode(',', $request->request->get('agenda_ids', ''));
$asuntoPersonalizado = $request->request->get('asunto');
$cuerpoPersonalizado = $request->request->get('cuerpo');
$delay = (int) $request->request->get('delay', 5);
$estadoFiltro = $request->request->get('estado_filtro_hidden', 'seleccionado');
$logger->info('DEBUG massEmailSend - Datos recibidos', [
'agenda_ids' => $agendaIds,
'asunto' => $asuntoPersonalizado,
'tiene_cuerpo' => !empty($cuerpoPersonalizado),
'estado_filtro' => $estadoFiltro
]);
if (empty($agendaIds) || empty($asuntoPersonalizado) || empty($cuerpoPersonalizado)) {
$this->addFlash('error', 'Datos incompletos. Debes completar asunto y contenido.');
return $this->redirectToRoute('agenda_index');
}
// Configurar delay
$massEmailService->setDelayBetweenBatches($delay);
// Recopilar embajadores con sus variables
$recipients = [];
foreach ($agendaIds as $agendaId) {
$agenda = $entityManager->getRepository(Agenda::class)->find($agendaId);
if ($agenda) {
$logger->info('DEBUG massEmailSend - Agenda encontrada', [
'agenda_id' => $agendaId,
'participantes_count' => count($agenda->getEmbajadorParticipantes())
]);
$tipoAgenda = $agenda->getTipoAgenda() ? $agenda->getTipoAgenda()->getNombre() : 'Actividad';
$fecha = $agenda->getFecha() ? $agenda->getFecha()->format('d/m/Y') : '';
$hora = $agenda->getFecha() ? $agenda->getFecha()->format('H:i') : '';
$lugar = $agenda->getLugar() ?? '';
foreach ($agenda->getEmbajadorParticipantes() as $embajadorParticipante) {
// Filtrar por estado
if ($estadoFiltro !== 'todos' && $embajadorParticipante->getEstado() !== $estadoFiltro) {
$logger->debug('DEBUG massEmailSend - Embajador filtrado por estado', [
'estado_embajador' => $embajadorParticipante->getEstado(),
'estado_filtro' => $estadoFiltro
]);
continue;
}
// Saltar embajadores que ya tienen correo enviado
if ($embajadorParticipante->isEnviado()) {
$logger->debug('DEBUG massEmailSend - Embajador ya tiene correo enviado', [
'embajador' => $embajadorParticipante->getEmbajadores() ? $embajadorParticipante->getEmbajadores()->getNombre() : 'NULL'
]);
continue;
}
$embajador = $embajadorParticipante->getEmbajadores();
$logger->info('DEBUG massEmailSend - Embajador incluido', [
'embajador' => $embajador ? $embajador->getNombre() : 'NULL',
'correo' => $embajador ? $embajador->getCorreo() : 'NULL',
'estado' => $embajadorParticipante->getEstado()
]);
if ($embajador && $embajador->getCorreo()) {
$recipients[] = [
'email' => $embajador->getCorreo(),
'asunto' => $asuntoPersonalizado,
'cuerpo' => $cuerpoPersonalizado,
'variables' => [
'nombre_embajador' => $embajador->getNombre(),
'tipo_evento' => $tipoAgenda,
'fecha_evento' => $fecha,
'hora_evento' => $hora,
'lugar_evento' => $lugar,
'direccion' => $lugar,
'observaciones' => '',
'monto_pago' => '0',
'rol' => 'Embajador',
'horas' => '0'
]
];
}
}
} else {
$logger->warning('DEBUG massEmailSend - Agenda NO encontrada', ['agenda_id' => $agendaId]);
}
}
$logger->info('DEBUG massEmailSend - Total recipients', ['count' => count($recipients)]);
// Eliminar duplicados
$uniqueRecipients = [];
foreach ($recipients as $recipient) {
$uniqueRecipients[$recipient['email']] = $recipient;
}
$recipients = array_values($uniqueRecipients);
// Validar que hay destinatarios
if (empty($recipients)) {
$this->addFlash('error', 'No se encontraron destinatarios con email válido en las agendas seleccionadas.');
return $this->redirectToRoute('agenda_index');
}
// Enviar emails masivos con contenido personalizado
$results = $massEmailService->sendMassEmailCustom($recipients);
// Mostrar resultados
if ($results['success'] > 0) {
$this->addFlash('success', sprintf(
'Se enviaron %d emails correctamente de %d total.',
$results['success'],
$results['total']
));
}
if ($results['failed'] > 0) {
$this->addFlash('warning', sprintf(
'Fallaron %d envíos. Revisa los logs para más detalles.',
$results['failed']
));
}
} catch (\Exception $e) {
$logger->error('Error en envío masivo de correos', [
'mensaje' => $e->getMessage(),
'archivo' => $e->getFile(),
'linea' => $e->getLine(),
'trace' => $e->getTraceAsString()
]);
$this->addFlash('error', 'Error al enviar correos: ' . $e->getMessage());
}
return $this->redirectToRoute('agenda_index');
}
/**
* @Route("/mass-email/preview", name="agenda_mass_email_preview", methods={"GET", "POST"})
*/
public function massEmailPreview(Request $request, EmailTemplateService $templateService): Response
{
$entityManager = $this->getDoctrine()->getManager();
// Obtener datos del formulario (POST) o query string (GET)
if ($request->isMethod('POST')) {
$asunto = $request->request->get('asunto');
$cuerpo = $request->request->get('cuerpo');
$agendaIds = explode(',', $request->request->get('agenda_ids', ''));
} else {
$templateCodigo = $request->query->get('template');
$agendaIds = explode(',', $request->query->get('agenda_ids', ''));
$template = $entityManager->getRepository(\App\Entity\Correo::class)
->findOneBy(['codigo' => $templateCodigo]);
if (!$template) {
return new Response('<div class="alert alert-danger">Template no encontrado</div>');
}
$asunto = $template->getAsunto();
$cuerpo = $template->getCuerpo();
}
// Obtener datos de ejemplo de la primera agenda
$agenda = null;
if (!empty($agendaIds) && $agendaIds[0]) {
$agenda = $entityManager->getRepository(Agenda::class)->find($agendaIds[0]);
}
$ejemploVariables = [
'nombre_embajador' => 'Juan Pérez (ejemplo)',
'tipo_evento' => $agenda && $agenda->getTipoAgenda() ? $agenda->getTipoAgenda()->getNombre() : 'Evento Ejemplo',
'fecha_evento' => $agenda && $agenda->getFecha() ? $agenda->getFecha()->format('d/m/Y') : date('d/m/Y'),
'hora_evento' => $agenda && $agenda->getFecha() ? $agenda->getFecha()->format('H:i') : '10:00',
'lugar_evento' => $agenda ? $agenda->getLugar() ?? 'Por definir' : 'Por definir',
'direccion' => $agenda ? $agenda->getLugar() ?? 'Por definir' : 'Por definir',
'observaciones' => 'Ejemplo de observaciones adicionales',
'monto_pago' => '50000',
'rol' => 'Embajador',
'horas' => '4'
];
// Reemplazar variables manualmente
$asuntoRenderizado = $asunto;
$cuerpoRenderizado = $cuerpo;
foreach ($ejemploVariables as $key => $value) {
$asuntoRenderizado = str_replace('{{' . $key . '}}', $value, $asuntoRenderizado);
$asuntoRenderizado = str_replace('{' . $key . '}', $value, $asuntoRenderizado);
$cuerpoRenderizado = str_replace('{{' . $key . '}}', $value, $cuerpoRenderizado);
$cuerpoRenderizado = str_replace('{' . $key . '}', $value, $cuerpoRenderizado);
}
$html = sprintf('
<div class="row">
<div class="col-12">
<div class="alert alert-info mb-4">
<i class="la la-info-circle"></i> <strong>Vista Previa con Datos de Ejemplo</strong>
<p class="mb-0 mt-2">Cada destinatario recibirá el email personalizado con sus propios datos.</p>
</div>
<div class="card mb-3">
<div class="card-header bg-light">
<strong><i class="la la-tag"></i> Asunto del Email:</strong>
</div>
<div class="card-body">
<h5 class="mb-0">%s</h5>
</div>
</div>
<div class="card">
<div class="card-header bg-light">
<strong><i class="la la-file-text"></i> Contenido del Email:</strong>
</div>
<div class="card-body" style="background: #f9f9f9; padding: 30px; line-height: 1.8;">
%s
</div>
</div>
</div>
</div>
', htmlspecialchars($asuntoRenderizado), $cuerpoRenderizado);
return new Response($html);
}
/**
* @Route("/mass-email/variables", name="agenda_mass_email_variables", methods={"GET"})
*/
public function massEmailVariables(Request $request): Response
{
$entityManager = $this->getDoctrine()->getManager();
$templateCodigo = $request->query->get('template');
$template = $entityManager->getRepository(\App\Entity\Correo::class)
->findOneBy(['codigo' => $templateCodigo]);
if (!$template) {
return $this->json(['error' => 'Template no encontrado', 'variables' => []], 404);
}
// Extraer variables del contenido del template
// Buscar tanto {variable} como {{variable}}
$contenido = $template->getCuerpo();
preg_match_all('/\{\{?([a-z_]+)\}?\}/', $contenido, $matches);
$variables = array_unique($matches[1]);
return $this->json(['variables' => array_values($variables)]);
}
/**
* @Route("/{id}", name="agenda_show", methods={"GET", "POST"}, requirements={"id"="\d+"})
*/
public function show(Mensajes $mensajes, Agenda $agenda, Request $request): Response
{
$entityManager = $this->getDoctrine()->getManager();
$colegios = $entityManager->getRepository(ColegioParticipante::class)->findByAgenda($agenda->getId());
$embajadores = $entityManager->getRepository(EmbajadorParticipante::class)->findByAgenda($agenda->getId());
$uaPostulaciones = $entityManager->getRepository(UnidadAcademicaPostulacion::class)->findBy(['agenda' => $agenda]);
$form = $this->createFormBuilder()->getForm();
$form ->add("cuerpo", TextareaType::class, ['required'=>true,]);
$cuerpo = "";
if($agenda->getSolicitud()){
$cuerpo = "Hola,<br><br>
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>
Cualquier duda puede escribir a colegios@uc.cl.<br><br>
Saludos cordiales";
}
$form->get('cuerpo')->setData($cuerpo);
if ($request->isMethod('POST')) {
$form->handleRequest($request);
$data = $form->getData();
$agenda->getSolicitud()->setCuerpo($data["cuerpo"]);
$entityManager->flush();
$mensajes->enviar2Mensaje($agenda->getSolicitud()->getCorreoEncargado(),"vivian.avalos@uc.cl", "Solicitud actividad UC ingresada correctamente", $agenda->getSolicitud()->getCuerpo());
}
return $this->render('agenda/show.html.twig', [
'agenda' => $agenda,
'colegios' => $colegios,
'embajadores' => $embajadores,
'ua_postulaciones' => $uaPostulaciones,
'form' => $form->createView(),
]);
}
/**
* @Route("/{id}/edit", name="agenda_edit", methods={"GET", "POST"})
*/
public function edit(Request $request, Agenda $agenda, AgendaRepository $agendaRepository, Mensajes $mensajes): Response
{
$entityManager = $this->getDoctrine()->getManager();
$originalFecha = $agenda->getFecha();
// Normalizar roles si es un array (compatibilidad con datos antiguos)
if (is_array($agenda->getRoles())) {
$agenda->setRoles(implode(', ', $agenda->getRoles()));
}
$form = $this->createForm(AgendaType::class, $agenda);
$form->handleRequest($request);
$colegios = $entityManager->getRepository(ColegioParticipante::class)->findByAgenda($agenda->getId());
$embajadores = $entityManager->getRepository(EmbajadorParticipante::class)->findByAgenda($agenda->getId());
// Obtener todos los roles únicos de todos los eventos
$rolesUnicos = $this->getRolesUnicos($entityManager);
if ($form->isSubmitted() && $form->isValid()) {
$agendaRepository->add($agenda, true);
if ($agenda->getFecha() != $originalFecha) {
$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');
// Enviar correo a los embajadores participantes
foreach ($embajadores as $embajador) {
$mensajes->enviar2Mensaje($embajador->getEmbajadores()->getCorreo(), null, "Modificación de fecha de actividad", $nuevoMensaje);
}
$mensajes->enviar2Mensaje("migueleduardo.hernandez@gmail.com", null, "Modificación de fecha de actividad", $nuevoMensaje);
$mensajes->enviar2Mensaje("cigutierrez@uc.cl", null, "Modificación de fecha de actividad", $nuevoMensaje);
}
if ($agenda->getEstadoAgenda() == "eliminada") {
return $this->redirectToRoute('agenda_index');
}
return $this->redirectToRoute('colegioparticipante_newbyagenda', ['id' => $agenda->getId()]);
}
return $this->renderForm('agenda/edit.html.twig', [
'agenda' => $agenda,
'form' => $form,
'colegios' => $colegios,
'embajadores' => $embajadores,
]);
}
/**
* Obtiene todos los roles únicos de todos los eventos
*/
private function getRolesUnicos($entityManager): array
{
$connection = $entityManager->getConnection();
$sql = "SELECT DISTINCT unnest(string_to_array(roles, ',')) as rol FROM agenda WHERE roles IS NOT NULL ORDER BY rol";
try {
$result = $connection->executeQuery($sql)->fetchAllAssociative();
$rolesUnicos = [];
foreach ($result as $row) {
$rolCompleto = trim($row['rol']);
if (!empty($rolCompleto)) {
// Extraer solo el nombre del rol (antes de los dos puntos)
$partes = explode(':', $rolCompleto);
$nombreRol = trim($partes[0]);
// Evitar duplicados
if (!in_array($nombreRol, $rolesUnicos) && !empty($nombreRol)) {
$rolesUnicos[] = $nombreRol;
}
}
}
sort($rolesUnicos);
return $rolesUnicos;
} catch (\Exception $e) {
return [];
}
}
/**
* @Route("/{id}", name="app_agenda_delete", methods={"POST"}, requirements={"id"="\d+"})
*/
public function delete(Request $request, Agenda $agenda, AgendaRepository $agendaRepository): Response
{
if ($this->isCsrfTokenValid('delete'.$agenda->getId(), $request->request->get('_token'))) {
$agendaRepository->remove($agenda, true);
}
return $this->redirectToRoute('app_agenda_index', [], Response::HTTP_SEE_OTHER);
}
/**
* @Route("/calendario/get_events", name="calendario_events")
*/
public function eventsAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$sesiones = $em->getRepository('AppBundle:Agenda')->findAll();
$events = array();
$aux2;
$aux2="";
foreach ($sesiones as $event) {
$aux = $event->getId().'-'.$event->getLugar();
$e = array();
$e['id'] = $event->getId();
$e['title'] = $aux;
$e['color'] = '#5867dd';
$e['eventColor'] = 'white';
$e['url'] = $this->generateUrl('agenda_show', array('id' => $event->getId()));
$e['start'] = date_format($event->getFecha(), 'Y-m-d');
array_push($events, $e);
}
$response = new JsonResponse($events);
return $response;
}
/**
* @Route("/newbysol/{id}", name="agenda_newbysol", methods={"GET", "POST"})
*/
public function newbysol($id, AgendaRepository $agendaRepository, ColegioParticipanteRepository $colegioParticipanteRepository, SolicitudRepository $solicitudRepository): Response
{
$entityManager = $this->getDoctrine()->getManager();
$sol = $entityManager->getRepository(Solicitud::class)->findOneById($id);
// Idempotencia: verificar AMBAS FKs para evitar duplicados aunque una esté desfasada.
// FK autoritativa: agenda.solicitud_id (leída vía $sol->getAgendas())
if ($sol->getAgendas()->count() > 0) {
return $this->redirectToRoute('agenda_edit', ['id' => $sol->getAgendas()->first()->getId()]);
}
// Fallback: verificar solicitud.agenda_id por si no se ha sincronizado aún
if ($sol->getAgenda()) {
return $this->redirectToRoute('agenda_edit', ['id' => $sol->getAgenda()->getId()]);
}
// Bloquear creación de agenda si la solicitud está rechazada o cancelada
$estadoNombreActual = $sol->getEstado() ? strtolower($sol->getEstado()->getNombre()) : null;
if (in_array($estadoNombreActual, ['rechazada', 'cancelada'])) {
$this->addFlash('error', 'No se puede crear una agenda para una solicitud con estado "' . $sol->getEstado()->getNombre() . '".');
return $this->redirectToRoute('app_solicitud_edit', ['id' => $sol->getId()]);
}
$estado = $entityManager->getRepository(EstadoAgenda::class)->findOneByNombre("agendada");
// Mapeo de tipo de solicitud → TipoAgenda (case-insensitive via trim/lower)
$tipoNombre = trim($sol->getTipo() ?? '');
$tipo = null;
if (strcasecmp($tipoNombre, 'Tour') === 0) {
$tipo = $entityManager->getRepository(TipoAgenda::class)->findOneByNombre("Tour");
} elseif (strcasecmp($tipoNombre, 'Feria') === 0) {
$tipo = $entityManager->getRepository(TipoAgenda::class)->findOneByNombre("Feria");
} elseif (strcasecmp($tipoNombre, 'Charla') === 0) {
$tipo = $entityManager->getRepository(TipoAgenda::class)->findOneByNombre("Charla");
} elseif (strcasecmp($tipoNombre, 'Campus abierto') === 0) {
$tipo = $entityManager->getRepository(TipoAgenda::class)->findOneByNombre("Campus abierto");
}
// Si el tipo no se resuelve, queda null (tipoAgenda no asignado)
// Buscar estado "agendada" de solicitud (case-insensitive)
$estadosol = $entityManager->getRepository(EstadoSolicitud::class)->createQueryBuilder('e')
->where('LOWER(e.nombre) = :nombre')
->setParameter('nombre', 'agendada')
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
$agenda = new Agenda();
$agenda->setFecha($sol->getHorario());
$agenda->setNAlumnos($sol->getCantidadAlumnos());
$agenda->setEstadoAgenda($estado);
$agenda->setEncargado($sol->getNombreEncargado());
$agenda->setTelefonoencargado($sol->getTelefonoEncargado());
if($sol->getColegio()){
$agenda->setComuna($sol->getColegio()->getComuna());
}
$agenda->setComentarioFinal("creado desde solicitud");
$agenda->setTipoAgenda($tipo);
$agenda->setSolicitud($sol);
$agenda->setCurso($sol->getCursos());
if($sol->getCampus()){
$agenda->setLugar($sol->getCampus());
$agenda->setDireccion($sol->getCampus());
}
if($sol->getDireccion()){
$agenda->setDireccion($sol->getDireccion());
}
if($sol->getTipo()=="Feria"){
$agenda->setDireccion($sol->getDireccion());
}
$agenda->setObservacion($sol->getComentario());
$agendaRepository->add($agenda, true);
$colegioParticipante = new ColegioParticipante();
$colegioParticipante->setAgenda($agenda);
$colegioParticipante->setColegio($sol->getColegio());
$colegioParticipanteRepository->add($colegioParticipante, true);
if ($estadosol) {
$sol->setEstado($estadosol);
}
$sol->setAgenda($agenda);
$solicitudRepository->add($sol, true);
return $this->redirectToRoute('agenda_edit', array(
'id' => $agenda->getId(),
));
}
/**
* @Route("/{id}/toggle-postulacion", name="agenda_toggle_postulacion", methods={"POST"})
*/
public function togglePostulacion(Agenda $agenda, Request $request): Response
{
if (!$this->isCsrfTokenValid('toggle_postulacion_' . $agenda->getId(), $request->request->get('_token'))) {
return new \Symfony\Component\HttpFoundation\JsonResponse(['error' => 'Token inválido'], 403);
}
$entityManager = $this->getDoctrine()->getManager();
$nuevoEstado = !$agenda->isAbiertopostulacion();
$agenda->setAbiertopostulacion($nuevoEstado);
$entityManager->flush();
return new \Symfony\Component\HttpFoundation\JsonResponse([
'success' => true,
'abiertopostulacion' => $nuevoEstado,
]);
}
/**
* @Route("/{id}/cerrar_convocatoria", name="cerrar_convocatoria", methods={"GET", "POST"})
*/
public function cerrar_convocatoria(Agenda $agenda): Response
{
$entityManager = $this->getDoctrine()->getManager();
$agenda->setAbiertopostulacion(0);
$entityManager->persist($agenda);
$entityManager->flush();
$embajador_participantes = $entityManager->getRepository(EmbajadorParticipante::class)->findBy(array('agenda' =>$agenda,
'estado' =>'pendiente seleccion'));
foreach ($embajador_participantes as $embajador){
if ($embajador->getEstado() == 'pendiente seleccion'){
$embajador->setEstado('no seleccionado');
$entityManager->persist($embajador);
$entityManager->flush();
}
}
return $this->redirectToRoute('agenda_show', array(
'id' => $agenda->getId(),
));
}
/**
* @Route("/{id}/cerraragenda", name="agenda_cerraragenda", methods={"GET", "POST"})
*/
public function cerraragenda(Agenda $agenda): Response
{
$entityManager = $this->getDoctrine()->getManager();
$estado = $entityManager->getRepository(EstadoAgenda::class)->findOneByNombre("realizada");
$agenda->setEstadoAgenda($estado);
$entityManager->flush();
$this->addFlash('success', 'Actividad marcada como realizada. Ahora puedes enviar las encuestas por separado.');
return $this->redirectToRoute('agenda_show', array('id' => $agenda->getId()));
}
/**
* @Route("/{id}/cancelar-con-postulantes", name="agenda_cancelar_con_postulantes", methods={"POST"}, requirements={"id"="\d+"})
*/
public function cancelarConPostulantes(Request $request, Agenda $agenda, EmbajadorParticipanteRepository $embajadorParticipanteRepository): Response
{
if (!$this->isCsrfTokenValid('cancelar-postulantes'.$agenda->getId(), $request->request->get('_token'))) {
$this->addFlash('error', 'Token CSRF inválido.');
return $this->redirectToRoute('agenda_show', array('id' => $agenda->getId()));
}
$entityManager = $this->getDoctrine()->getManager();
$estadoCancelada = $entityManager->getRepository(EstadoAgenda::class)->findOneBy(['nombre' => 'cancelada'])
?? $entityManager->getRepository(EstadoAgenda::class)->findOneBy(['nombre' => 'Cancelada']);
$postulantes = $embajadorParticipanteRepository->findBy(array('agenda' => $agenda));
$eliminados = 0;
foreach ($postulantes as $postulante) {
$embajadorParticipanteRepository->remove($postulante, true);
$eliminados++;
}
if ($estadoCancelada) {
$agenda->setEstadoAgenda($estadoCancelada);
}
$entityManager->flush();
$this->addFlash('success', sprintf(
'Actividad cancelada y %d embajador(es) postulante(s) eliminado(s).',
$eliminados
));
return $this->redirectToRoute('agenda_show', array('id' => $agenda->getId()));
}
/**
* @Route("/{id}/cerraragendaorientador", name="agenda_cerraragendaorientador", methods={"GET", "POST"})
*/
public function enviaragendaorientador( Mensajes $mensajes, Agenda $agenda, EvaluacioncolegioRepository $evaluacioncolegioRepository ): Response
{
$entityManager = $this->getDoctrine()->getManager();
if($agenda->getSolicitud()){
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$charactersLength = strlen($characters);
$randomString = '';
$maxAttempts = 10;
do {
$randomString = '';
for ($i = 0; $i < 6; $i++) {
$randomString .= $characters[random_int(0, $charactersLength - 1)];
}
$exists = null !== $evaluacioncolegioRepository->findOneBy(['hash' => $randomString]);
} while ($exists && $maxAttempts-- > 0);
if ($exists) {
// Espacio agotado: fallback a un hash criptográficamente aleatorio
// para garantizar unicidad sin colisiones.
$randomString = bin2hex(random_bytes(8));
}
$evaluacion = new Evaluacioncolegio();
$evaluacion->setHash($randomString);
$evaluacion->setAgenda($agenda);
$evaluacion->setCorreo($agenda->getSolicitud()->getCorreoEncargado());
$evaluacion->setCargo($agenda->getSolicitud()->getCargoEncargado());
$evaluacion->setNombre($agenda->getSolicitud()->getNombreEncargado());
$evaluacioncolegioRepository->add($evaluacion, true);
$encuestaUrl = $this->generateUrl('app_evaluacioncolegio_edit', ['hash' => $evaluacion->getHash()], UrlGeneratorInterface::ABSOLUTE_URL);
$reporteLinks = '';
$colegio = $agenda->getSolicitud()->getColegio();
if ($colegio) {
foreach ($colegio->getColegiodetalles() as $detalle) {
$reporteUrl = $this->generateUrl('app_colegio_reporte2', ['id' => $detalle->getId()], UrlGeneratorInterface::ABSOLUTE_URL);
$reporteLinks .= "<br><a href=\"".$reporteUrl."\">Informe de rendimiento — ".$detalle->getNombre()."</a>";
}
}
$msj=
"Hola,<br><br>
Gracias por participar en la actividad ".$agenda->getTipoAgenda()->getNombre().", el día ".date_format($agenda->getFecha(),"d/m/Y").",
<br><br>
Por favor ayudanos contestando una encuesta en el siguiente link: ".$encuestaUrl."
".('' !== $reporteLinks ? "<br><br>Además, puedes descargar tu(s) informe(s) de rendimiento desde el siguiente enlace:".$reporteLinks : '')."
";
$mensajes->enviar2Mensaje($agenda->getSolicitud()->getCorreoEncargado(),"vivian.avalos@uc.cl", "Evaluación de actividad", $msj);
$this->addFlash('success', 'Encuesta enviada al colegio correctamente.');
} else {
$this->addFlash('warning', 'Esta agenda no tiene solicitud asociada, no se pudo enviar la encuesta al colegio.');
}
$entityManager->flush();
return $this->redirectToRoute('agenda_show', array('id' => $agenda->getId()));
}
/**
* @Route("/{id}/enviar-encuesta-embajadores", name="agenda_enviar_encuesta_embajadores", methods={"GET", "POST"})
*/
public function enviarEncuestaEmbajadores( Mensajes $mensajes, Agenda $agenda, EvaluacionembajadorRepository $evaluacionembajadorRepository ): Response
{
$entityManager = $this->getDoctrine()->getManager();
$embajadores = $entityManager->getRepository(EmbajadorParticipante::class)->findBy(array('estado'=>'seleccionado','agenda'=>$agenda));
if (empty($embajadores)) {
$this->addFlash('warning', 'No hay embajadores seleccionados en esta actividad.');
return $this->redirectToRoute('agenda_show', array('id' => $agenda->getId()));
}
foreach ($embajadores as $embajador) {
$evaluacionembajador = new Evaluacionembajador();
$evaluacionembajador->setEmbajador($embajador->getEmbajadores());
$evaluacionembajador->setAgenda($agenda);
$evaluacionembajador->setContestada(0);
$evaluacionembajadorRepository->add($evaluacionembajador, true);
$msj2=
"Hola,<br><br>
Gracias por participar en la actividad ".$agenda->getTipoAgenda()->getNombre().", el día ".date_format($agenda->getFecha(),"d/m/Y").",
<br><br>
Por favor ayudanos contestando una encuesta en la plataforma
";
$mensajes->enviar2Mensaje($embajador->getEmbajadores()->getCorreo(),"vivian.avalos@uc.cl", "Evaluación de actividad", $msj2);
}
$agenda->setEncuestaenviada(1);
$entityManager->flush();
$this->addFlash('success', 'Encuestas enviadas a ' . count($embajadores) . ' embajador(es) correctamente.');
return $this->redirectToRoute('agenda_show', array('id' => $agenda->getId()));
}
/**
* @Route("cargar_excel", name="agenda_cargar_excel", methods={"GET", "POST"})
*/
public function cargarExcel(Request $request): Response
{
$em = $this->getDoctrine()->getManager();
$agendasCreadas = [];
if ($request->isMethod('POST')) {
$fechaImportacion = (new \DateTime())->format('Y-m-d');
$codigoImportacion = strtoupper(substr(bin2hex(random_bytes(3)), 0, 5));
/** @var UploadedFile $file */
$file = $request->files->get('excel');
if ($file && $file->getClientOriginalExtension() === 'xlsx') {
$spreadsheet = IOFactory::load($file->getPathname());
$sheet = $spreadsheet->getActiveSheet();
$agendaActual = null;
foreach ($sheet->toArray(null, true, true, true) as $index => $row) {
if ($index === 1 || empty($row['C'])) continue;
// Si hay nueva fecha => nueva agenda
if (!empty($row['A'])) {
// FECHA base
if (is_numeric($row['A'])) {
$baseDate = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($row['A']);
} else {
$baseDate = \DateTime::createFromFormat('d-M', $row['A']) ??
\DateTime::createFromFormat('d/m/Y', $row['A']) ??
\DateTime::createFromFormat('d-m-Y', $row['A']);
if ($baseDate && $baseDate->format('Y') === '1970') {
$baseDate->setDate(2025, $baseDate->format('m'), $baseDate->format('d'));
}
}
// Horario: "10:00 - 13:00"
$horario = $row['B'] ?? null;
$horaInicio = $horaFin = null;
if ($horario && strpos($horario, '-') !== false) {
[$horaInicio, $horaFin] = array_map('trim', explode('-', $horario));
}
// Fecha inicio y hasta
$fecha = clone $baseDate;
if ($horaInicio) {
[$h, $m] = explode(':', $horaInicio);
$fecha->setTime((int)$h, (int)$m);
}
$hasta = clone $baseDate;
if ($horaFin) {
[$h2, $m2] = explode(':', $horaFin);
$hasta->setTime((int)$h2, (int)$m2);
}
// Crear agenda
$agendaActual = new Agenda();
$agendaActual->setFecha($fecha);
$agendaActual->setHasta($hasta);
$agendaActual->setMonto(19700);
// $agendaActual->setHorariotaxi($horario);
$agendaActual->setDireccion($row['H'] ?? null);
$agendaActual->setObservacion('panoramia');
$agendaActual->setComentarioFinal("Importada $fechaImportacion - $codigoImportacion");
$agendaActual->setCurso('1º-4º');
$agendaActual->setComuna($row['E'] ?? null);
$agendaActual->setLugar($row['F'] ?? null);
$tipoAgenda = $em->getRepository(\App\Entity\TipoAgenda::class)->find(13);
$estadoAgenda = $em->getRepository(\App\Entity\EstadoAgenda::class)->find(1);
$agendaActual->setTipoAgenda($tipoAgenda);
$agendaActual->setEstadoAgenda($estadoAgenda);
$em->persist($agendaActual);
$em->flush(); // para tener el ID inmediatamente
$agendasCreadas[] = [
'id' => $agendaActual->getId(),
'lugar' => $agendaActual->getLugar(),
'comuna' => $agendaActual->getComuna(),
'fecha' => $agendaActual->getFecha()->format('Y-m-d H:i'),
'hasta' => $agendaActual->getHasta()->format('H:i'),
'codigo' => $codigoImportacion,
];
}
// Colegio
$rbd = $row['C'];
$nombre = $row['F'] ?? null;
$direccion = $row['H'] ?? null;
$contacto = $row['I'] ?? null;
$fono = $row['J'] ?? null;
$email = $row['K'] ?? null;
$colegio = $em->getRepository(Colegio::class)->findOneBy(['id' => $rbd]);
if (!$colegio) {
$colegio = new Colegio();
$colegio->setRbd($rbd);
$colegio->setNombre($nombre);
$colegio->setDireccion($direccion);
$em->persist($colegio);
$em->flush();
}
if ($agendaActual && $colegio) {
$cp = new ColegioParticipante();
$cp->setAgenda($agendaActual);
$cp->setColegio($colegio);
$cp->setObservacion($row['O'] ?? '');
$em->persist($cp);
}
}
$em->flush();
return $this->render('agenda/cargar-excel-resumen.html.twig', [
'agendas' => $agendasCreadas,
]);
}
}
return $this->render('agenda/cargar-excel.html.twig');
}
/**
* @Route("/unidad-academica/aceptar/{id}", name="unidad_academica_aceptar", methods={"POST"})
*/
public function aceptarUnidadAcademica(Request $request, UnidadAcademicaPostulacion $postulacion, Mensajes $mensajes): Response
{
$motivo = $request->request->get('motivo');
$postulacion->setAceptada(true);
$this->getDoctrine()->getManager()->flush();
$mensaje = "Su postulación ha sido aceptada. Motivo: " . $motivo;
$mensajes->enviar2Mensaje($postulacion->getUnidadAcademica(), null, "Postulación aceptada", $mensaje);
return $this->redirectToRoute('agenda_show', ['id' => $postulacion->getAgenda()->getId()]);
}
/**
* @Route("/unidad-academica/rechazar/{id}", name="unidad_academica_rechazar", methods={"POST"})
*/
public function rechazarUnidadAcademica(Request $request, UnidadAcademicaPostulacion $postulacion, Mensajes $mensajes): Response
{
$motivo = $request->request->get('motivo');
$postulacion->setEstado('rechazado');
$this->getDoctrine()->getManager()->flush();
$mensaje = "Su postulación ha sido rechazada. Motivo: " . $motivo;
$mensajes->enviar2Mensaje($postulacion->getUnidadAcademica(), null, "Postulación rechazada", $mensaje);
return $this->redirectToRoute('agenda_show', ['id' => $postulacion->getAgenda()->getId()]);
}
/**
* Confirma asistencia mediante código QR
*
* @Route("/confirmacion/{codigoQr}", name="agenda_confirmacion_qr", methods={"GET"})
*/
public function confirmarQR(string $codigoQr, QRConfirmacionService $qrService): Response
{
try {
$resultado = $qrService->confirmarAsistencia($codigoQr);
if ($resultado['confirmado']) {
$this->addFlash('success', sprintf(
'¡Asistencia confirmada exitosamente! %s - %s',
$resultado['tipo'],
$resultado['agenda']
));
return $this->render('agenda/confirmacion_resultado.html.twig', [
'exito' => true,
'resultado' => $resultado,
'mensaje' => 'Asistencia confirmada exitosamente'
]);
} else {
$this->addFlash('warning', $resultado['mensaje']);
return $this->render('agenda/confirmacion_resultado.html.twig', [
'exito' => false,
'resultado' => $resultado,
'mensaje' => $resultado['mensaje']
]);
}
} catch (\Exception $e) {
$this->addFlash('error', 'Error al confirmar asistencia: ' . $e->getMessage());
return $this->render('agenda/confirmacion_resultado.html.twig', [
'exito' => false,
'resultado' => null,
'mensaje' => 'Código QR no válido o no encontrado'
]);
}
}
/**
* Página de escaneo QR
*
* @Route("/escanear-qr", name="agenda_escanear_qr", methods={"GET"})
*/
public function escanearQR(): Response
{
return $this->render('agenda/escanear_qr.html.twig');
}
}