Modificaciones en la validacion del qr

This commit is contained in:
santiago
2025-10-01 14:16:19 -06:00
7 changed files with 100 additions and 40 deletions
+53 -15
View File
@@ -12,6 +12,8 @@ import {
UnprocessableEntityException,
UploadedFiles,
BadRequestException,
HttpStatus,
HttpException,
} from '@nestjs/common';
import { CuestionarioService } from './cuestionario.service';
import { CreateCuestionarioDto } from './dto/create-cuestionario.dto';
@@ -21,9 +23,9 @@ import { diskStorage } from 'multer';
import { extname } from 'path';
import { CuestionarioApiDocumentation } from './docs/cuestionario.documentation';
import { CreateEventoWithCuestionarioDto } from './dto/create-evento-cuestionario.dto';
import * as ExcelJS from 'exceljs';
import { TipoPreguntaEnum } from 'src/tipo_pregunta/entities/tipo_pregunta.entity';
import { TiposValidacion } from 'src/pregunta/entities/pregunta.entity';
import * as fs from 'fs';
import { NotFoundError } from 'rxjs';
import { error } from 'console';
@Controller('cuestionario')
@CuestionarioApiDocumentation.ApiController
@@ -70,20 +72,56 @@ export class CuestionarioController {
}));
}
@Post('carga-masiva')
@UseInterceptors(FileInterceptor('file'))
async cargaMasiva(@UploadedFile() file: Express.Multer.File) {
if (!file) {
throw new BadRequestException('No se ha subido ningún archivo');
}
@Post('carga-masiva')
@UseInterceptors(
FileInterceptor('file', {
storage: diskStorage({
destination: (req, file, cb) => {
const uploadPath = './uploads/excel';
// Crear la carpeta si no existe
fs.mkdirSync(uploadPath, { recursive: true });
cb(null, uploadPath);
},
filename: (req, file, cb) => {
// Guardar el archivo con timestamp + nombre original
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
const fileExt = extname(file.originalname); // obtener extensión
cb(null, `${uniqueSuffix}${fileExt}`);
},
}),
fileFilter: (req, file, cb) => {
// Solo permitir archivos .xlsx
if (file.mimetype === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
cb(null, true);
} else {
cb(new BadRequestException('Solo se permiten archivos .xlsx'), false);
}
},
}),
)
async cargaMasiva(@UploadedFile() file: Express.Multer.File) {
if (!file) {
throw new BadRequestException('No se ha subido ningún archivo');
}
const cuestionarios = await this.cargaMasiva(file);
console.log('Archivo guardado en:', file.path);
return {
message: 'Carga masiva exitosa',
total: cuestionarios.length,
data: cuestionarios,
};
// Leer el archivo con ExcelJS
// Aquí tu lógica para procesar las hojas del Excel
const cuestionarios = await this.cuestionarioService.cargarEventosDesdeExcel(file.path); // Ejemplo de arreglo de datos procesados
// ...
if (!cuestionarios.data) {
throw error;
}
return {
message: cuestionarios.message,
total: cuestionarios.data.length,
data: cuestionarios,
};
}
+3 -2
View File
@@ -96,6 +96,7 @@ export class CuestionarioService {
2: 'registro-comunidad-trabajadores',
3: 'registro-general',
};
let data: any[] = [];
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile(filePath);
@@ -193,11 +194,11 @@ export class CuestionarioService {
};
// Llamada a tu servicio para guardar en DB
await this.createCuestionarioEvento(createDto);
data.push(await this.createCuestionarioEvento(createDto));
}
}
return { message: 'Carga masiva completada' };
return { message: 'Carga masiva completada', data };
}
async create(createCuestionarioDto: CreateCuestionarioDto) {
@@ -112,11 +112,11 @@ export class CuestionarioRespondidoService {
if (cuestionario && cuestionario.evento) {
try {
// Generar token para el QR
qrToken = await this.qrTokenService.generateQrToken(
qrToken = this.qrTokenService.generateQrToken(
participante.id_participante,
cuestionario.evento.id_evento,
cuestionario.id_cuestionario,
cuestionario.fecha_fin
);
// Generar código QR con el token
@@ -337,11 +337,11 @@ export class CuestionarioRespondidoService {
}
// Generar token para el QR
qrToken = await this.qrTokenService.generateQrToken(
qrToken = this.qrTokenService.generateQrToken(
participante.id_participante,
cuestionario.evento.id_evento,
cuestionario.id_cuestionario,
cuestionario.fecha_fin
);
qrBuffer = await QRCode.toBuffer(qrToken);
@@ -191,11 +191,13 @@ export class ParticipanteEventoService {
const tokenData = await this.qrTokenService.validateQrToken(qrToken);
if (!tokenData) {
throw new HttpException(
'Token QR inválido o expirado',
HttpStatus.BAD_REQUEST,
);
return{
success: false,
message:
'El token ya caduco',
data: {}
}
}
const { id_participante, id_evento, id_cuestionario } = tokenData;
+28 -13
View File
@@ -5,6 +5,7 @@ import { InjectRepository } from '@nestjs/typeorm';
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';
@Injectable()
export class QrTokenService {
@@ -15,6 +16,8 @@ export class QrTokenService {
private readonly asistenciaRepository: Repository<Asistencia>,
@InjectRepository(Cuestionario)
private readonly cuestionarioRepository: Repository<Cuestionario>,
@InjectRepository(Evento)
private readonly eventoREpo: Repository<Evento>,
) {}
/**
@@ -24,11 +27,12 @@ export class QrTokenService {
* @param id_cuestionario ID del cuestionario
* @returns Token JWT codificado
*/
async generateQrToken(
generateQrToken(
id_participante: number,
id_evento: number,
id_cuestionario: number,
): Promise<string> {
fecha_fin: Date,
): string {
const payload = {
id_participante,
id_evento,
@@ -42,17 +46,10 @@ export class QrTokenService {
this.configService.get<string>('QR_JWT_SECRET') ||
this.configService.get<string>('JWT_SECRET', 'tu_clave_secreta');
const fecha_fin = await this.cuestionarioRepository.findOne({
where: { id_cuestionario },
select: ['fecha_fin'],
});
if (!fecha_fin) {
throw new UnauthorizedException('Cuestionario no encontrado');
}
// Convert fecha_fin to seconds from now for expiresIn
const expiresInSeconds = Math.floor((fecha_fin?.fecha_fin.getTime() - Date.now()) / 1000);
const expiresInSeconds = Math.floor((fecha_fin.getTime() - Date.now()) / 1000);
return this.jwtService.sign(payload, {
secret,
@@ -65,7 +62,6 @@ export class QrTokenService {
* @param token Token JWT a validar
* @returns Datos decodificados del token o null si es inválido
*/
async validateQrToken(token: string): Promise<{
id_participante
: number;
@@ -80,9 +76,28 @@ export class QrTokenService {
this.configService.get<string>('JWT_SECRET', 'tu_clave_secreta');
console.log(secret)
const decoded = this.jwtService.verify(token, { secret });
const fecha_fin2 = await this.eventoREpo.findOne({
where: { id_evento:decoded.id_evento },
});
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');
}
// Verificar que sea un token de tipo QR de asistencia
if (decoded.type !== 'qr_asistencia') {
return null;
+4 -1
View File
@@ -69,20 +69,23 @@ export class QrController {
};
}
@Get('generate-token/:idParticipante/:idEvento/:idCuestionario')
@Get('generate-token/:idParticipante/:idEvento/:idCuestionario/:fechaFin')
@ApiOperation({ summary: 'Genera un token JWT para QR de asistencia' })
@ApiParam({ name: 'idParticipante', description: 'ID del participante' })
@ApiParam({ name: 'idEvento', description: 'ID del evento' })
@ApiParam({ name: 'idCuestionario', description: 'ID del cuestionario' })
@ApiParam({ name: 'fechaFin', description: 'Fecha de fin de validez del token (ISO 8601)' })
async generateQrToken(
@Param('idParticipante', ParseIntPipe) idParticipante: number,
@Param('idEvento', ParseIntPipe) idEvento: number,
@Param('idCuestionario', ParseIntPipe) idCuestionario: number,
@Param('fechaFin') fechaFin: Date,
) {
const token = await this.qrTokenService.generateQrToken(
idParticipante,
idEvento,
idCuestionario,
fechaFin
);
return {
+2 -1
View File
@@ -8,9 +8,10 @@ import { AuthModule } from '../auth/auth.module';
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';
@Module({
imports: [TypeOrmModule.forFeature([Qr, Asistencia, Cuestionario]), AuthModule, ConfigModule],
imports: [TypeOrmModule.forFeature([Qr, Asistencia, Cuestionario, Evento]), AuthModule, ConfigModule],
controllers: [QrController],
providers: [QrService, QrTokenService],
exports: [QrTokenService],