Add cupo_maximo field and related validations to cuestionario and evento modules
This commit is contained in:
@@ -32,7 +32,7 @@ export class CuestionarioController {
|
||||
return this.cuestionarioService.create(createCuestionarioDto);
|
||||
}
|
||||
|
||||
@Post('evento')
|
||||
@Post('withEvento')
|
||||
@CuestionarioApiDocumentation.ApiCreateWithEvento
|
||||
createCuestionarioEvento(
|
||||
@Body() createCuestionarioDto: CreateCuestionarioEventoDto,
|
||||
|
||||
@@ -66,6 +66,7 @@ export class CuestionarioService {
|
||||
createCuestionarioDto.secciones?.length || 0;
|
||||
cuestionario.editable = true;
|
||||
cuestionario.id_evento = createCuestionarioDto.id_evento;
|
||||
cuestionario.cupo_maximo = createCuestionarioDto.cupo_maximo;
|
||||
|
||||
const savedCuestionario = await queryRunner.manager.save(cuestionario);
|
||||
|
||||
@@ -203,9 +204,6 @@ export class CuestionarioService {
|
||||
};
|
||||
|
||||
evento = await queryRunner.manager.save(Evento, nuevoEvento);
|
||||
console.log(
|
||||
`Evento creado: ${evento.nombre_evento}, ID: ${evento.id_evento}`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`Evento encontrado: ${evento.nombre_evento}, ID: ${evento.id_evento}`,
|
||||
|
||||
@@ -11,6 +11,7 @@ export const ejemploCuestionarioAlumno = {
|
||||
fecha_inicio: '2025-08-01T08:00:00',
|
||||
fecha_fin: '2025-08-10T23:59:59',
|
||||
id_tipo_cuestionario: 1,
|
||||
cupo_maximo: 100,
|
||||
secciones: [
|
||||
{
|
||||
titulo: 'Información Personal',
|
||||
@@ -88,6 +89,7 @@ export const ejemploCuestionarioTrabajador = {
|
||||
fecha_inicio: '2025-08-01T08:00:00',
|
||||
fecha_fin: '2025-08-10T23:59:59',
|
||||
id_tipo_cuestionario: 2,
|
||||
cupo_maximo: 100,
|
||||
secciones: [
|
||||
{
|
||||
titulo: 'Información Personal',
|
||||
|
||||
@@ -63,6 +63,15 @@ export class CreateCuestionarioDto {
|
||||
@IsNumber()
|
||||
id_tipo_cuestionario: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Cupo máximo de participantes',
|
||||
example: 100,
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
cupo_maximo?: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Nombre del evento asociado',
|
||||
example: 'Feria de la Sexualidad',
|
||||
|
||||
@@ -10,6 +10,7 @@ import { CuestionarioSeccion } from 'src/cuestionario_seccion/entities/cuestiona
|
||||
import { Evento } from 'src/evento/entities/evento.entity';
|
||||
import { ParticipanteEvento } from 'src/participante_evento/entities/participante_evento.entity';
|
||||
import { TipoCuestionario } from 'src/tipo_cuestionario/entities/tipo_cuestionario.entity';
|
||||
import { CuestionarioRespondido } from 'src/cuestionario_respondido/entities/cuestionario_respondido.entity';
|
||||
|
||||
@Entity('cuestionario')
|
||||
export class Cuestionario {
|
||||
@@ -37,6 +38,9 @@ export class Cuestionario {
|
||||
@Column({ type: 'datetime' })
|
||||
fecha_fin: Date;
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
cupo_maximo?: number;
|
||||
|
||||
@Column({ type: 'int' })
|
||||
id_tipo_cuestionario: number;
|
||||
|
||||
@@ -69,4 +73,7 @@ export class Cuestionario {
|
||||
(participanteEvento) => participanteEvento.cuestionario,
|
||||
)
|
||||
inscripciones: ParticipanteEvento[];
|
||||
|
||||
@OneToMany(() => CuestionarioRespondido, (cr) => cr.cuestionario)
|
||||
cuestionariosRespondidos: CuestionarioRespondido[];
|
||||
}
|
||||
|
||||
@@ -56,6 +56,27 @@ export class CuestionarioRespondidoService {
|
||||
submitDto.id_cuestionario,
|
||||
);
|
||||
|
||||
//1.1 Validar que no se haya rebasado el cupo máximo
|
||||
if (
|
||||
cuestionario.cupo_maximo !== null &&
|
||||
cuestionario.cupo_maximo !== undefined
|
||||
) {
|
||||
const totalRespondidos =
|
||||
await this.cuestionarioRespondidoRepository.count({
|
||||
where: {
|
||||
cuestionario: { id_cuestionario: submitDto.id_cuestionario },
|
||||
},
|
||||
});
|
||||
|
||||
if (totalRespondidos >= cuestionario.cupo_maximo) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
return {
|
||||
success: false,
|
||||
message: `El cupo máximo de ${cuestionario.cupo_maximo} participantes ha sido alcanzado para el cuestionario ${cuestionario.nombre_form}.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Buscar o crear participante
|
||||
let participante = await this.participanteRepository.findOne({
|
||||
where: { correo: submitDto.correo },
|
||||
@@ -551,7 +572,7 @@ export class CuestionarioRespondidoService {
|
||||
return csv;
|
||||
} */
|
||||
|
||||
async generarReporteRespuestas(idCuestionario: number): Promise<string> {
|
||||
async generarReporteRespuestas(idCuestionario: number): Promise<string> {
|
||||
// Buscar el cuestionario para validar que existe
|
||||
const cuestionario = await this.cuestionarioRepository.findOne({
|
||||
where: { id_cuestionario: idCuestionario },
|
||||
|
||||
@@ -48,14 +48,14 @@ export const createMainDbConfig = async (
|
||||
SeccionPregunta,
|
||||
TipoCuestionario,
|
||||
TipoPregunta,
|
||||
TipoUser
|
||||
TipoUser,
|
||||
],
|
||||
retryAttempts: 5,
|
||||
retryDelay: 3000,
|
||||
connectTimeout: 30000,
|
||||
|
||||
synchronize: false,
|
||||
dropSchema: false,
|
||||
synchronize: configService.get<boolean>('SYNCHRONIZE') || false,
|
||||
dropSchema: configService.get<boolean>('DROP_SCHEMA') || false,
|
||||
});
|
||||
|
||||
export const createAlumnosDbConfig = async (
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Expose, Type } from 'class-transformer';
|
||||
import { TipoCuestionarioOutputDto } from 'src/tipo_cuestionario/dto/outputs.dto';
|
||||
|
||||
export class CuestionarioConCupoDto {
|
||||
@Expose()
|
||||
id_cuestionario: number;
|
||||
|
||||
@Expose()
|
||||
nombre_form: string;
|
||||
|
||||
@Expose()
|
||||
descripcion?: string;
|
||||
|
||||
@Expose()
|
||||
banner: string;
|
||||
|
||||
@Expose()
|
||||
fecha_inicio: Date;
|
||||
|
||||
@Expose()
|
||||
fecha_fin: Date;
|
||||
|
||||
@Expose()
|
||||
cupo_maximo: number | null;
|
||||
|
||||
@Expose()
|
||||
cupos_usados: number;
|
||||
|
||||
@Expose()
|
||||
cupos_disponibles: number | null;
|
||||
|
||||
@Expose()
|
||||
id_evento: number;
|
||||
|
||||
@Expose()
|
||||
@Type(() => TipoCuestionarioOutputDto)
|
||||
tipoCuestionario: TipoCuestionarioOutputDto;
|
||||
}
|
||||
|
||||
export class EventoConCuestionariosDto {
|
||||
@Expose()
|
||||
id_evento: number;
|
||||
|
||||
@Expose()
|
||||
tipo_evento: string;
|
||||
|
||||
@Expose()
|
||||
nombre_evento: string;
|
||||
|
||||
@Expose()
|
||||
descripcion_evento: string;
|
||||
|
||||
@Expose()
|
||||
fecha_inicio: Date;
|
||||
|
||||
@Expose()
|
||||
fecha_fin: Date;
|
||||
|
||||
@Expose()
|
||||
asistencias: any;
|
||||
|
||||
@Expose()
|
||||
banner: string | null;
|
||||
|
||||
@Expose()
|
||||
@Type(() => CuestionarioConCupoDto) // <- Esto es lo que faltaba
|
||||
cuestionarios: CuestionarioConCupoDto[];
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import { CreateEventoDto } from './dto/create-evento.dto';
|
||||
import { UpdateEventoDto } from './dto/update.evento.dto';
|
||||
import { join } from 'path';
|
||||
import { existsSync, unlinkSync } from 'fs';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { EventoConCuestionariosDto } from './dto/outpus.dto';
|
||||
|
||||
@Injectable()
|
||||
export class EventoService {
|
||||
@@ -71,16 +73,36 @@ export class EventoService {
|
||||
}
|
||||
|
||||
async getCuestionariosEvento(id_evento: number) {
|
||||
const evento = await this.eventoRepository.findOne({
|
||||
where: { id_evento },
|
||||
relations: ['cuestionarios'], // solo los cuestionarios, sin secciones
|
||||
const eventos = await this.eventoRepository.findOne({
|
||||
where: {
|
||||
id_evento: id_evento,
|
||||
},
|
||||
relations: ['cuestionarios', 'cuestionarios.cuestionariosRespondidos'],
|
||||
});
|
||||
|
||||
if (!evento) {
|
||||
if (!eventos) {
|
||||
throw new NotFoundException(`Evento con ID ${id_evento} no encontrado.`);
|
||||
}
|
||||
|
||||
return evento;
|
||||
const eventoConCupos = {
|
||||
...eventos,
|
||||
cuestionarios: eventos.cuestionarios.map((cuest) => {
|
||||
const cupoMaximo = cuest.cupo_maximo ?? null;
|
||||
const usados = cuest.cuestionariosRespondidos?.length || 0;
|
||||
const disponibles =
|
||||
cupoMaximo !== null ? Math.max(0, cupoMaximo - usados) : null;
|
||||
|
||||
return {
|
||||
...cuest,
|
||||
cupos_usados: usados,
|
||||
cupos_disponibles: disponibles,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
return plainToInstance(EventoConCuestionariosDto, eventoConCupos, {
|
||||
excludeExtraneousValues: true,
|
||||
});
|
||||
}
|
||||
|
||||
async getEvento(id_evento: number) {
|
||||
@@ -96,10 +118,30 @@ export class EventoService {
|
||||
where: {
|
||||
fecha_fin: MoreThan(new Date()),
|
||||
},
|
||||
relations: ['cuestionarios'], // solo los cuestionarios, sin secciones
|
||||
relations: ['cuestionarios', 'cuestionarios.cuestionariosRespondidos'],
|
||||
});
|
||||
|
||||
return eventos;
|
||||
const eventosConCupos = eventos.map((evento) => {
|
||||
return {
|
||||
...evento,
|
||||
cuestionarios: evento.cuestionarios.map((cuest) => {
|
||||
const cupoMaximo = cuest.cupo_maximo ?? null;
|
||||
const usados = cuest.cuestionariosRespondidos?.length || 0;
|
||||
const disponibles =
|
||||
cupoMaximo !== null ? Math.max(0, cupoMaximo - usados) : null;
|
||||
|
||||
return {
|
||||
...cuest,
|
||||
cupos_usados: usados,
|
||||
cupos_disponibles: disponibles,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
return plainToInstance(EventoConCuestionariosDto, eventosConCupos, {
|
||||
excludeExtraneousValues: true,
|
||||
});
|
||||
}
|
||||
|
||||
async getEventoOrFail(id_evento: number): Promise<Evento> {
|
||||
|
||||
@@ -14,7 +14,6 @@ async function bootstrap() {
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true, // Elimina propiedades no decoradas
|
||||
forbidNonWhitelisted: true, // Arroja error si hay propiedades no decoradas
|
||||
transform: true, // Transforma los datos recibidos al tipo definido en el DTO
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Expose } from 'class-transformer';
|
||||
|
||||
export class TipoCuestionarioOutputDto {
|
||||
@Expose()
|
||||
id_tipo_cuestionario: number;
|
||||
|
||||
@Expose()
|
||||
tipo_cuestionario: string;
|
||||
}
|
||||
Reference in New Issue
Block a user