Add contador field to EventoUsuario and update related services for handling user data #40
@@ -31,4 +31,7 @@ export class EventoUsuario {
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
identificador: string;
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
contador: number | null;
|
||||
}
|
||||
|
||||
@@ -44,59 +44,78 @@ export class EventoUsuarioService {
|
||||
|
||||
async bulkInsert(
|
||||
id_evento: number,
|
||||
alumnos: string[],
|
||||
trabajadores: string[],
|
||||
alumnos: { identificador: string; contador: number | null }[],
|
||||
trabajadores: { identificador: string; contador: number | null }[],
|
||||
): Promise<{ insertados: number; omitidos: number; errores: string[] }> {
|
||||
console.log(id_evento, alumnos, trabajadores);
|
||||
let insertados = 0;
|
||||
let omitidos = 0;
|
||||
const errores: string[] = [];
|
||||
|
||||
for (const cuenta of alumnos) {
|
||||
for (const alumno of alumnos) {
|
||||
try {
|
||||
const result = await this.eventoUsuarioRepo
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(EventoUsuario)
|
||||
.values({
|
||||
const existing = await this.eventoUsuarioRepo.findOne({
|
||||
where: {
|
||||
id_evento,
|
||||
tipo_usuario: TipoUsuarioEnum.ALUMNO,
|
||||
identificador: cuenta.toUpperCase(),
|
||||
})
|
||||
.orIgnore()
|
||||
.execute();
|
||||
if (result.raw?.affectedRows > 0) {
|
||||
insertados++;
|
||||
identificador: alumno.identificador.toUpperCase(),
|
||||
},
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
if (existing.contador !== alumno.contador) {
|
||||
await this.eventoUsuarioRepo.update(existing.id_evento_usuario, {
|
||||
contador: alumno.contador,
|
||||
});
|
||||
insertados++;
|
||||
} else {
|
||||
omitidos++;
|
||||
}
|
||||
} else {
|
||||
omitidos++;
|
||||
await this.eventoUsuarioRepo.insert({
|
||||
id_evento,
|
||||
tipo_usuario: TipoUsuarioEnum.ALUMNO,
|
||||
identificador: alumno.identificador.toUpperCase(),
|
||||
contador: alumno.contador,
|
||||
});
|
||||
insertados++;
|
||||
}
|
||||
} catch {
|
||||
omitidos++;
|
||||
errores.push(`Error al insertar cuenta ${cuenta}`);
|
||||
errores.push(`Error al insertar cuenta ${alumno.identificador}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const rfc of trabajadores) {
|
||||
for (const trabajador of trabajadores) {
|
||||
try {
|
||||
const result = await this.eventoUsuarioRepo
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(EventoUsuario)
|
||||
.values({
|
||||
const existing = await this.eventoUsuarioRepo.findOne({
|
||||
where: {
|
||||
id_evento,
|
||||
tipo_usuario: TipoUsuarioEnum.TRABAJADOR,
|
||||
identificador: rfc.toUpperCase(),
|
||||
})
|
||||
.orIgnore()
|
||||
.execute();
|
||||
if (result.raw?.affectedRows > 0) {
|
||||
insertados++;
|
||||
identificador: trabajador.identificador.toUpperCase(),
|
||||
},
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
if (existing.contador !== trabajador.contador) {
|
||||
await this.eventoUsuarioRepo.update(existing.id_evento_usuario, {
|
||||
contador: trabajador.contador,
|
||||
});
|
||||
insertados++;
|
||||
} else {
|
||||
omitidos++;
|
||||
}
|
||||
} else {
|
||||
omitidos++;
|
||||
await this.eventoUsuarioRepo.insert({
|
||||
id_evento,
|
||||
tipo_usuario: TipoUsuarioEnum.TRABAJADOR,
|
||||
identificador: trabajador.identificador.toUpperCase(),
|
||||
contador: trabajador.contador,
|
||||
});
|
||||
insertados++;
|
||||
}
|
||||
} catch {
|
||||
omitidos++;
|
||||
errores.push(`Error al insertar RFC ${rfc}`);
|
||||
errores.push(`Error al insertar RFC ${trabajador.identificador}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,27 +136,41 @@ export class EventoUsuarioService {
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
const data: any[] = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName]);
|
||||
|
||||
const alumnosValidos: string[] = [];
|
||||
const trabajadoresValidos: string[] = [];
|
||||
const alumnosValidos: { identificador: string; contador: number | null }[] =
|
||||
[];
|
||||
const trabajadoresValidos: {
|
||||
identificador: string;
|
||||
contador: number | null;
|
||||
}[] = [];
|
||||
let omitidos = 0;
|
||||
const errores: string[] = [];
|
||||
|
||||
for (const row of data) {
|
||||
const cuentaRaw = row['cuenta'];
|
||||
const rfcRaw = row['rfc'];
|
||||
const contadorRaw = row['contador'];
|
||||
const contador =
|
||||
contadorRaw !== undefined &&
|
||||
contadorRaw !== null &&
|
||||
contadorRaw !== '' &&
|
||||
!isNaN(Number(contadorRaw))
|
||||
? Number(contadorRaw)
|
||||
: null;
|
||||
|
||||
if (cuentaRaw !== undefined && cuentaRaw !== null && cuentaRaw !== '') {
|
||||
const cuenta = String(cuentaRaw).trim();
|
||||
const alumno = await this.alumnoRepo.findOne({
|
||||
where: { id_ncuenta: parseInt(cuenta, 10) },
|
||||
});
|
||||
console.log('Alumno encontrado:', alumno);
|
||||
if (!alumno) {
|
||||
omitidos++;
|
||||
errores.push(`Cuenta no encontrada en el sistema: ${cuenta}`);
|
||||
continue;
|
||||
}
|
||||
alumnosValidos.push(alumno.id_ncuenta.toString());
|
||||
alumnosValidos.push({
|
||||
identificador: alumno.id_ncuenta.toString(),
|
||||
contador,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -151,7 +184,7 @@ export class EventoUsuarioService {
|
||||
errores.push(`RFC no encontrado en el sistema: ${rfc}`);
|
||||
continue;
|
||||
}
|
||||
trabajadoresValidos.push(trabajador.rfc);
|
||||
trabajadoresValidos.push({ identificador: trabajador.rfc, contador });
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -261,11 +261,26 @@ export class ParticipanteEventoController {
|
||||
@ApiOperation({
|
||||
summary: 'Retornala info del token QR',
|
||||
})
|
||||
@ApiBody({
|
||||
description: 'Token JWT generado por el código QR',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
token: { type: 'string' },
|
||||
},
|
||||
example: {
|
||||
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
||||
},
|
||||
},
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Token válido, información decodificada retornada',
|
||||
})
|
||||
@ApiResponse({ status: 400, description: 'Token inválido o expirado' })
|
||||
@Post('decode-token')
|
||||
decodeToken(@Body() body: { token: string }) {
|
||||
return this.participanteEventoService.validToken(
|
||||
body.token,
|
||||
);
|
||||
return this.participanteEventoService.validToken(body.token);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
|
||||
@@ -7,10 +7,13 @@ import { Evento } from 'src/evento/entities/evento.entity';
|
||||
import { Participante } from 'src/participante/entities/participante.entity';
|
||||
import { CuestionarioModule } from 'src/cuestionario/cuestionario.module';
|
||||
import { QrModule } from 'src/qr/qr.module';
|
||||
import { EventoUsuario } from 'src/evento_usuario/entities/evento_usuario.entity';
|
||||
import { RespuestaParticipanteAbierta } from 'src/respuesta_participante_abierta/entities/respuesta_participante_abierta.entity';
|
||||
import { Cuestionario } from 'src/cuestionario/entities/cuestionario.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([ParticipanteEvento, Evento, Participante]),
|
||||
TypeOrmModule.forFeature([ParticipanteEvento, Evento, Participante, Cuestionario, EventoUsuario, RespuestaParticipanteAbierta]),
|
||||
CuestionarioModule,
|
||||
QrModule,
|
||||
],
|
||||
|
||||
@@ -8,6 +8,10 @@ import { Evento } from 'src/evento/entities/evento.entity';
|
||||
import { Participante } from 'src/participante/entities/participante.entity';
|
||||
import { CuestionarioService } from 'src/cuestionario/cuestionario.service';
|
||||
import { QrTokenService } from 'src/qr/qr-token.service';
|
||||
import { EventoUsuario } from 'src/evento_usuario/entities/evento_usuario.entity';
|
||||
import { RespuestaParticipanteAbierta } from 'src/respuesta_participante_abierta/entities/respuesta_participante_abierta.entity';
|
||||
import { TiposValidacion } from 'src/pregunta/entities/pregunta.entity';
|
||||
import { Cuestionario } from 'src/cuestionario/entities/cuestionario.entity';
|
||||
|
||||
@Injectable()
|
||||
export class ParticipanteEventoService {
|
||||
@@ -17,6 +21,12 @@ export class ParticipanteEventoService {
|
||||
@InjectRepository(Evento) private eventoRepository: Repository<Evento>,
|
||||
@InjectRepository(Participante)
|
||||
private participanteRepository: Repository<Participante>,
|
||||
@InjectRepository(Cuestionario)
|
||||
private cuestionarioRepository: Repository<Cuestionario>,
|
||||
@InjectRepository(EventoUsuario)
|
||||
private eventoUsuarioRepository: Repository<EventoUsuario>,
|
||||
@InjectRepository(RespuestaParticipanteAbierta)
|
||||
private respuestaAbiertaRepository: Repository<RespuestaParticipanteAbierta>,
|
||||
private readonly cuestionarioService: CuestionarioService,
|
||||
private readonly qrTokenService: QrTokenService,
|
||||
) {}
|
||||
@@ -178,7 +188,55 @@ export class ParticipanteEventoService {
|
||||
|
||||
participante_eventoFound.fecha_asistencia = new Date();
|
||||
participante_eventoFound.asistio = true;
|
||||
return this.participanteEventoRepository.save(participante_eventoFound);
|
||||
const resultado =
|
||||
await this.participanteEventoRepository.save(participante_eventoFound);
|
||||
|
||||
const cuestionario = await this.cuestionarioRepository.findOne({
|
||||
where: { id_cuestionario },
|
||||
});
|
||||
|
||||
let contador: number | null | undefined = undefined;
|
||||
|
||||
if (cuestionario?.id_evento) {
|
||||
const tieneListaRestringida =
|
||||
(await this.eventoUsuarioRepository.count({
|
||||
where: { id_evento: cuestionario.id_evento },
|
||||
})) > 0;
|
||||
|
||||
if (tieneListaRestringida) {
|
||||
const respuesta =
|
||||
(await this.respuestaAbiertaRepository.findOne({
|
||||
where: {
|
||||
cuestionarioRespondido: {
|
||||
participante: { id_participante },
|
||||
cuestionario: { id_cuestionario },
|
||||
},
|
||||
pregunta: { validacion: TiposValidacion.CUENTA_ALUMNO },
|
||||
},
|
||||
relations: ['pregunta', 'cuestionarioRespondido'],
|
||||
})) ??
|
||||
(await this.respuestaAbiertaRepository.findOne({
|
||||
where: {
|
||||
cuestionarioRespondido: {
|
||||
participante: { id_participante },
|
||||
cuestionario: { id_cuestionario },
|
||||
},
|
||||
pregunta: { validacion: TiposValidacion.RFC },
|
||||
},
|
||||
relations: ['pregunta', 'cuestionarioRespondido'],
|
||||
}));
|
||||
|
||||
if (respuesta) {
|
||||
const identificador = respuesta.respuesta.trim().toUpperCase();
|
||||
const eventoUsuario = await this.eventoUsuarioRepository.findOne({
|
||||
where: { id_evento: cuestionario.id_evento, identificador },
|
||||
});
|
||||
contador = eventoUsuario?.contador ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { ...resultado, contador };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,7 +257,7 @@ export class ParticipanteEventoService {
|
||||
}
|
||||
}
|
||||
|
||||
const { id_participante, id_evento, id_cuestionario } = tokenData;
|
||||
const { id_participante, id_evento, id_cuestionario, contador } = tokenData;
|
||||
|
||||
// Verificar que el participante existe
|
||||
const participante = await this.participanteRepository.findOne({
|
||||
@@ -239,6 +297,7 @@ export class ParticipanteEventoService {
|
||||
participante: participante.correo,
|
||||
fecha_asistencia_previa: participanteEvento.fecha_asistencia,
|
||||
fecha_actual: new Date(),
|
||||
contador,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -250,7 +309,6 @@ export class ParticipanteEventoService {
|
||||
const resultado =
|
||||
await this.participanteEventoRepository.save(participanteEvento);
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Asistencia registrada exitosamente',
|
||||
@@ -259,6 +317,7 @@ export class ParticipanteEventoService {
|
||||
fecha_asistencia: resultado.fecha_asistencia,
|
||||
id_evento,
|
||||
id_cuestionario,
|
||||
contador,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
+101
-39
@@ -6,6 +6,9 @@ import { Repository } from 'typeorm';
|
||||
import { Asistencia } from 'src/asistencia/entities/asistencia.entity';
|
||||
import { Cuestionario } from 'src/cuestionario/entities/cuestionario.entity';
|
||||
import { Evento } from 'src/evento/entities/evento.entity';
|
||||
import { EventoUsuario } from 'src/evento_usuario/entities/evento_usuario.entity';
|
||||
import { RespuestaParticipanteAbierta } from 'src/respuesta_participante_abierta/entities/respuesta_participante_abierta.entity';
|
||||
import { TiposValidacion } from 'src/pregunta/entities/pregunta.entity';
|
||||
|
||||
@Injectable()
|
||||
export class QrTokenService {
|
||||
@@ -13,11 +16,15 @@ export class QrTokenService {
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService,
|
||||
@InjectRepository(Asistencia)
|
||||
private readonly asistenciaRepository: Repository<Asistencia>,
|
||||
private readonly asistenciaRepository: Repository<Asistencia>,
|
||||
@InjectRepository(Cuestionario)
|
||||
private readonly cuestionarioRepository: Repository<Cuestionario>,
|
||||
private readonly cuestionarioRepository: Repository<Cuestionario>,
|
||||
@InjectRepository(Evento)
|
||||
private readonly eventoREpo: Repository<Evento>,
|
||||
private readonly eventoREpo: Repository<Evento>,
|
||||
@InjectRepository(EventoUsuario)
|
||||
private readonly eventoUsuarioRepo: Repository<EventoUsuario>,
|
||||
@InjectRepository(RespuestaParticipanteAbierta)
|
||||
private readonly respuestaAbiertaRepo: Repository<RespuestaParticipanteAbierta>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -46,12 +53,16 @@ export class QrTokenService {
|
||||
this.configService.get<string>('QR_JWT_SECRET') ||
|
||||
this.configService.get<string>('JWT_SECRET', 'tu_clave_secreta');
|
||||
|
||||
|
||||
console.log("fecha de caducidad:",fecha_fin);
|
||||
console.log('fecha de caducidad:', fecha_fin);
|
||||
// Convert fecha_fin to seconds from now for expiresIn
|
||||
const expiresInSeconds = Math.floor((fecha_fin.getTime() - Date.now()) / 1000);
|
||||
console.log("expiresInSeconds:",expiresInSeconds);
|
||||
console.log("Caducidad token: ", Math.floor((fecha_fin.getTime() - Date.now()) / 1000));
|
||||
const expiresInSeconds = Math.floor(
|
||||
(fecha_fin.getTime() - Date.now()) / 1000,
|
||||
);
|
||||
console.log('expiresInSeconds:', expiresInSeconds);
|
||||
console.log(
|
||||
'Caducidad token: ',
|
||||
Math.floor((fecha_fin.getTime() - Date.now()) / 1000),
|
||||
);
|
||||
|
||||
return this.jwtService.sign(payload, {
|
||||
secret,
|
||||
@@ -65,60 +76,111 @@ export class QrTokenService {
|
||||
* @returns Datos decodificados del token o null si es inválido
|
||||
*/
|
||||
async validateQrToken(token: string): Promise<{
|
||||
id_participante
|
||||
: number;
|
||||
id_participante: number;
|
||||
id_evento: number;
|
||||
id_cuestionario: number;
|
||||
type: string;
|
||||
iat: number;
|
||||
exp?: number;
|
||||
} | null >{
|
||||
|
||||
contador?: number | null;
|
||||
} | null> {
|
||||
try {
|
||||
const secret =
|
||||
this.configService.get<string>('QR_JWT_SECRET') ||
|
||||
this.configService.get<string>('JWT_SECRET', 'tu_clave_secreta');
|
||||
|
||||
console.log(secret)
|
||||
|
||||
console.log(secret);
|
||||
|
||||
const decoded = this.jwtService.verify(token, { secret });
|
||||
|
||||
//console.log("fecha fin:", fecha_fin2.fecha_fin);
|
||||
console.log("este es el token", decoded);
|
||||
console.log('este es el token', decoded);
|
||||
|
||||
const fecha_fin2 = await this.eventoREpo.findOne({
|
||||
where: { id_evento:decoded.id_evento },
|
||||
const evento = await this.eventoREpo.findOne({
|
||||
where: { id_evento: decoded.id_evento },
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
if (!evento) {
|
||||
throw new UnauthorizedException('Evento no encontrado');
|
||||
}
|
||||
const fechaFinEvento = new Date(evento.fecha_fin);
|
||||
const now = new Date();
|
||||
if (fechaFinEvento.getTime() < now.getTime()) {
|
||||
throw new Error('El evento ha finalizado');
|
||||
}
|
||||
|
||||
if (!fecha_fin2) {
|
||||
throw new UnauthorizedException('Evento no encontrado');
|
||||
}
|
||||
const fechaFinEvento = new Date(fecha_fin2.fecha_fin); // UTC
|
||||
const now = new Date();
|
||||
console.log(fechaFinEvento.getTime())
|
||||
console.log(now.getTime)
|
||||
if (fechaFinEvento.getTime() < now.getTime()) {
|
||||
throw new Error('El evento ha finalizado');
|
||||
}
|
||||
if (decoded.type !== 'qr_asistencia') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verificar que sea un token de tipo QR de asistencia
|
||||
if (decoded.type !== 'qr_asistencia') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
const result: {
|
||||
id_participante: number;
|
||||
id_evento: number;
|
||||
id_cuestionario: number;
|
||||
type: string;
|
||||
iat: number;
|
||||
contador?: number | null;
|
||||
} = {
|
||||
id_participante: decoded.id_participante,
|
||||
id_evento: decoded.id_evento,
|
||||
id_cuestionario: decoded.id_cuestionario,
|
||||
type: decoded.type,
|
||||
iat: decoded.iat,
|
||||
};
|
||||
|
||||
console.log({ result });
|
||||
|
||||
const tieneListaRestringida =
|
||||
(await this.eventoUsuarioRepo.count({
|
||||
where: { id_evento: decoded.id_evento },
|
||||
})) > 0;
|
||||
|
||||
if (tieneListaRestringida) {
|
||||
const respuesta = await this.respuestaAbiertaRepo.findOne({
|
||||
where: {
|
||||
cuestionarioRespondido: {
|
||||
participante: { id_participante: decoded.id_participante },
|
||||
cuestionario: { id_cuestionario: decoded.id_cuestionario },
|
||||
},
|
||||
pregunta: {
|
||||
validacion: TiposValidacion.CUENTA_ALUMNO,
|
||||
},
|
||||
},
|
||||
relations: ['pregunta', 'cuestionarioRespondido'],
|
||||
});
|
||||
|
||||
const respuestaRfc = respuesta
|
||||
? null
|
||||
: await this.respuestaAbiertaRepo.findOne({
|
||||
where: {
|
||||
cuestionarioRespondido: {
|
||||
participante: { id_participante: decoded.id_participante },
|
||||
cuestionario: { id_cuestionario: decoded.id_cuestionario },
|
||||
},
|
||||
pregunta: {
|
||||
validacion: TiposValidacion.RFC,
|
||||
},
|
||||
},
|
||||
relations: ['pregunta', 'cuestionarioRespondido'],
|
||||
});
|
||||
|
||||
const identificadorRespuesta = respuesta ?? respuestaRfc;
|
||||
|
||||
if (identificadorRespuesta) {
|
||||
const identificador = identificadorRespuesta.respuesta
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
|
||||
const eventoUsuario = await this.eventoUsuarioRepo.findOne({
|
||||
where: { id_evento: decoded.id_evento, identificador },
|
||||
});
|
||||
|
||||
result.contador = eventoUsuario?.contador ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error validando token QR:', error.message);
|
||||
console.error('Error validando token QR:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -9,9 +9,11 @@ import { ConfigModule } from '@nestjs/config';
|
||||
import { Asistencia } from 'src/asistencia/entities/asistencia.entity';
|
||||
import { Cuestionario } from 'src/cuestionario/entities/cuestionario.entity';
|
||||
import { Evento } from 'src/evento/entities/evento.entity';
|
||||
import { EventoUsuario } from 'src/evento_usuario/entities/evento_usuario.entity';
|
||||
import { RespuestaParticipanteAbierta } from 'src/respuesta_participante_abierta/entities/respuesta_participante_abierta.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Qr, Asistencia, Cuestionario, Evento]), AuthModule, ConfigModule],
|
||||
imports: [TypeOrmModule.forFeature([Qr, Asistencia, Cuestionario, Evento, EventoUsuario, RespuestaParticipanteAbierta]), AuthModule, ConfigModule],
|
||||
controllers: [QrController],
|
||||
providers: [QrService, QrTokenService],
|
||||
exports: [QrTokenService],
|
||||
|
||||
Reference in New Issue
Block a user