From 94002a3e5046e06c124e67eb0739d3b714e48aea Mon Sep 17 00:00:00 2001 From: miguel Date: Tue, 12 Aug 2025 20:00:18 -0600 Subject: [PATCH] Enhance email functionality by adding QR code generation and resend logic for participants who have already responded to the questionnaire. Update email template for event ticket confirmation with detailed registration information. --- .vscode/settings.json | 3 + .../cuestionario_respondido.service.ts | 75 ++++- src/emails/registro-evento-ticket.ts | 307 ++++++++++++++++++ src/evento/entities/evento.entity.ts | 7 +- src/evento/evento.service.ts | 10 +- 5 files changed, 393 insertions(+), 9 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/emails/registro-evento-ticket.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..65a1965 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode" +} diff --git a/src/cuestionario_respondido/cuestionario_respondido.service.ts b/src/cuestionario_respondido/cuestionario_respondido.service.ts index ab64aee..fdc1a1e 100644 --- a/src/cuestionario_respondido/cuestionario_respondido.service.ts +++ b/src/cuestionario_respondido/cuestionario_respondido.service.ts @@ -101,9 +101,80 @@ export class CuestionarioRespondidoService { ); if (cuestionarioRespondidoExistente) { await queryRunner.rollbackTransaction(); + + // Aunque ya haya respondido, enviamos el correo nuevamente + if (cuestionario && cuestionario.evento) { + try { + // Generar datos para el QR + const datosQR = { + id_participante: participante.id_participante, + id_evento: cuestionario.evento.id_evento, + id_cuestionario: cuestionario.id_cuestionario, + }; + + // Generar código QR + const qrJsonData = JSON.stringify(datosQR); + const qrBuffer = await QRCode.toBuffer(qrJsonData); + + // Enviar correo con el QR + const urlApiCorreos = process.env.url_api_correos; + + if (urlApiCorreos) { + await axios.post( + `${urlApiCorreos}/mail/send`, + { + to: participante.correo, + subject: `Evento: ${cuestionario.evento?.nombre_evento}`, + fecha_recibido: new Date().toISOString(), + html: generarHtmlCorreoAsistencia({ + nombreForm: cuestionario.nombre_form, + nombreEvento: cuestionario.evento?.nombre_evento, + correo: participante.correo, + fechaRegistro: new Date().toLocaleString(), + fechaInicioEvento: cuestionario.evento?.fecha_inicio + ? new Date( + cuestionario.evento.fecha_inicio, + ).toLocaleString() + : undefined, + fechaFinEvento: cuestionario.evento?.fecha_fin + ? new Date(cuestionario.evento.fecha_fin).toLocaleString() + : undefined, + }), + adjuntos: [ + { + filename: 'qr.png', + content: qrBuffer.toString('base64'), + encoding: 'base64', + cid: 'qrCode', + }, + ], + }, + { + headers: { + token: process.env.SYSTEM_TOKEN_API_CORREOS, + }, + }, + ); + + console.log( + `Correo reenviado exitosamente a ${participante.correo}`, + ); + } + } catch (error) { + console.error('Error al reenviar correo:', error); + } + } + return { - success: false, - message: `El participante con correo ${submitDto.correo} ya ha respondido el cuestionario ${cuestionario.nombre_form}.`, + success: true, + message: `El participante con correo ${submitDto.correo} ya había respondido el cuestionario ${cuestionario.nombre_form}. Se ha reenviado el correo con el código QR.`, + datos_qr: cuestionario.evento + ? { + id_participante: participante.id_participante, + id_evento: cuestionario.evento.id_evento, + id_cuestionario: cuestionario.id_cuestionario, + } + : undefined, }; } diff --git a/src/emails/registro-evento-ticket.ts b/src/emails/registro-evento-ticket.ts new file mode 100644 index 0000000..19a5814 --- /dev/null +++ b/src/emails/registro-evento-ticket.ts @@ -0,0 +1,307 @@ +export function generarHtmlCorreoAsistenciaTicket({ + nombreForm, + nombreEvento, + correo, + fechaRegistro, + fechaInicioEvento, + fechaFinEvento, +}: { + nombreForm: string; + nombreEvento: string; + correo: string; + fechaRegistro: string; + fechaInicioEvento?: string; + fechaFinEvento?: string; +}) { + const horarioEvento = + fechaInicioEvento && fechaFinEvento + ? `${fechaInicioEvento} al ${fechaFinEvento}` + : 'Por confirmar'; + + return ` +
+ +
+

¡Registro Confirmado!

+
+ + +
+ +
+

+ 🎓 ${nombreEvento || 'Evento Especial'} +

+

+ TICKET DE ENTRADA • ENTRADA GRATUITA +

+
+ + +
+
+ +
+
+ Código QR de Entrada +

+ ESCANEA PARA REGISTRAR ASISTENCIA +

+
+
+ + +
+
+

+ 📋 Detalles de tu registro +

+ +
+
+

+ Participante +

+

+ ${correo} +

+
+ +
+

+ Formulario +

+

+ ${nombreForm} +

+
+ +
+

+ Fecha de Registro +

+

+ ${fechaRegistro} +

+
+ +
+

+ 🕒 Horario del Evento +

+

+ ${horarioEvento} +

+
+
+
+
+
+
+ + +
+
+
+

+ IMPORTANTE: Presenta este código QR en la mesa + de registro +

+

+ Disponible en formato digital o impreso +

+
+
+

+ ENTRADA VÁLIDA +

+

+ ✓ Verificado +

+
+
+
+
+ + +
+

+ 📝 Instrucciones importantes: +

+
    +
  • Llega 15 minutos antes del inicio del evento
  • +
  • + Presenta este QR en la mesa de registro para validar tu asistencia +
  • +
  • Tu constancia será otorgada al finalizar el evento
  • +
  • Para dudas, acude al módulo de información durante el evento
  • +
+
+ + +
+

+ Este mensaje fue enviado automáticamente. Por favor, no respondas a + este correo. +

+
+
+ `; +} diff --git a/src/evento/entities/evento.entity.ts b/src/evento/entities/evento.entity.ts index a86530e..d297f7e 100644 --- a/src/evento/entities/evento.entity.ts +++ b/src/evento/entities/evento.entity.ts @@ -1,10 +1,5 @@ import { Cuestionario } from 'src/cuestionario/entities/cuestionario.entity'; -import { - Column, - Entity, - OneToMany, - PrimaryGeneratedColumn, -} from 'typeorm'; +import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class Evento { diff --git a/src/evento/evento.service.ts b/src/evento/evento.service.ts index 9b86f9b..b7edb73 100644 --- a/src/evento/evento.service.ts +++ b/src/evento/evento.service.ts @@ -225,11 +225,19 @@ export class EventoService { async getEventosActivos() { const now = new Date(); - return this.eventoRepository.find({ + const eventos = await this.eventoRepository.find({ where: { fecha_fin: MoreThan(now), }, + relations: ['cuestionarios'], }); + + return eventos.map((evento) => ({ + ...evento, + total_cuestionarios: evento.cuestionarios + ? evento.cuestionarios.length + : 0, + })); } async getCuestionarioEvento(id_evento: number, id_cuestionario: number) {