events-division #34
Vendored
+3
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user