events-division #34

Merged
jalvarado merged 2 commits from events-division into develop 2025-08-19 16:28:26 +00:00
5 changed files with 393 additions and 9 deletions
Showing only changes of commit 94002a3e50 - Show all commits
+3
View File
@@ -0,0 +1,3 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
@@ -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,
};
}
+307
View File
@@ -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 `
<div
style="
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 650px;
margin: 0 auto;
padding: 20px;
border-radius: 16px;
"
>
<!-- Header del correo -->
<div style="text-align: center">
<h1>¡Registro Confirmado!</h1>
</div>
<!-- Ticket Container -->
<div
style="
background: white;
border-radius: 12px;
overflow: hidden;
background: #fefefe;
position: relative;
"
>
<!-- Ticket Header -->
<div
style="
background: linear-gradient(90deg, #003d79 0%, #0056b3 100%);
color: white;
padding: 20px;
position: relative;
"
>
<h2 style="margin: 0 0 8px 0; font-size: 24px; font-weight: bold">
🎓 ${nombreEvento || 'Evento Especial'}
</h2>
<p style="margin: 0; opacity: 0.9; font-size: 14px">
TICKET DE ENTRADA • ENTRADA GRATUITA
</p>
</div>
<!-- Ticket Body -->
<div style="padding: 30px 20px">
<div style="display: flex; gap: 30px; align-items: center">
<!-- QR Code Section -->
<div
style="
flex: 1;
display: flex;
justify-content: center;
align-items: center;
"
>
<div
style="
background: #f8f9fa;
border: 2px dashed #dee2e6;
border-radius: 8px;
padding: 20px;
display: inline-block;
text-align: center;
"
>
<img
src="cid:qrCode"
alt="Código QR de Entrada"
style="
width: 180px;
height: 180px;
display: block;
margin: 0 auto;
"
/>
<p
style="
margin: 12px 0 0 0;
font-size: 12px;
color: #6c757d;
font-weight: 600;
"
>
ESCANEA PARA REGISTRAR ASISTENCIA
</p>
</div>
</div>
<!-- Ticket Info Section -->
<div style="flex: 1.2">
<div style="margin-bottom: 24px">
<h3
style="
color: #003d79;
margin: 0 0 16px 0;
font-size: 18px;
border-bottom: 2px solid #e9ecef;
padding-bottom: 8px;
"
>
📋 Detalles de tu registro
</h3>
<div>
<div style="margin-bottom: 12px">
<p
style="
margin: 0;
font-size: 12px;
color: #6c757d;
text-transform: uppercase;
font-weight: 600;
"
>
Participante
</p>
<p
style="
margin: 2px 0 0 0;
font-size: 15px;
color: #333;
font-weight: 500;
"
>
${correo}
</p>
</div>
<div style="margin-bottom: 12px">
<p
style="
margin: 0;
font-size: 12px;
color: #6c757d;
text-transform: uppercase;
font-weight: 600;
"
>
Formulario
</p>
<p
style="
margin: 2px 0 0 0;
font-size: 15px;
color: #333;
font-weight: 500;
"
>
${nombreForm}
</p>
</div>
<div style="margin-bottom: 12px">
<p
style="
margin: 0;
font-size: 12px;
color: #6c757d;
text-transform: uppercase;
font-weight: 600;
"
>
Fecha de Registro
</p>
<p
style="
margin: 2px 0 0 0;
font-size: 15px;
color: #333;
font-weight: 500;
"
>
${fechaRegistro}
</p>
</div>
<div style="margin-bottom: 12px">
<p
style="
margin: 0;
font-size: 12px;
color: #6c757d;
text-transform: uppercase;
font-weight: 600;
"
>
🕒 Horario del Evento
</p>
<p
style="
margin: 2px 0 0 0;
font-size: 15px;
color: #003d79;
font-weight: 600;
"
>
${horarioEvento}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Ticket Footer -->
<div
style="
background: #f8f9fa;
padding: 16px 20px;
border-top: 2px dashed #dee2e6;
"
>
<div
style="
display: flex;
justify-content: space-between;
align-items: center;
"
>
<div>
<p style="margin: 0; font-size: 12px; color: #6c757d">
<strong>IMPORTANTE:</strong> Presenta este código QR en la mesa
de registro
</p>
<p style="margin: 4px 0 0 0; font-size: 11px; color: #6c757d">
Disponible en formato digital o impreso
</p>
</div>
<div style="text-align: right">
<p
style="
margin: 0;
font-size: 14px;
color: #003d79;
font-weight: bold;
"
>
ENTRADA VÁLIDA
</p>
<p style="margin: 2px 0 0 0; font-size: 11px; color: #28a745">
✓ Verificado
</p>
</div>
</div>
</div>
</div>
<!-- Instrucciones adicionales -->
<div
style="
margin-top: 20px;
background: rgba(255, 255, 255, 0.95);
border-radius: 8px;
padding: 16px;
"
>
<h4 style="color: #003d79; margin: 0 0 12px 0; font-size: 16px">
📝 Instrucciones importantes:
</h4>
<ul
style="
margin: 0;
padding-left: 20px;
color: #333;
font-size: 14px;
line-height: 1.6;
"
>
<li>Llega 15 minutos antes del inicio del evento</li>
<li>
Presenta este QR en la mesa de registro para validar tu asistencia
</li>
<li>Tu constancia será otorgada al finalizar el evento</li>
<li>Para dudas, acude al módulo de información durante el evento</li>
</ul>
</div>
<!-- Footer del correo -->
<div style="text-align: center; margin-top: 20px">
<p style="color: rgba(255, 255, 255, 0.8); font-size: 13px; margin: 0">
Este mensaje fue enviado automáticamente. Por favor, no respondas a
este correo.
</p>
</div>
</div>
`;
}
+1 -6
View File
@@ -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 {
+9 -1
View File
@@ -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) {