Refactor cuestionario module to support event creation with associated questionnaire, including new DTO and enhanced error handling.

This commit is contained in:
miguel
2025-06-23 18:58:20 -06:00
parent e20a381ba6
commit 551cc3e3b2
6 changed files with 146 additions and 104 deletions
+2 -1
View File
@@ -20,6 +20,7 @@ import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer'; import { diskStorage } from 'multer';
import { extname } from 'path'; import { extname } from 'path';
import { CuestionarioApiDocumentation } from './docs/cuestionario.documentation'; import { CuestionarioApiDocumentation } from './docs/cuestionario.documentation';
import { CreateEventoWithCuestionarioDto } from './dto/create-evento-cuestionario.dto';
@Controller('cuestionario') @Controller('cuestionario')
@CuestionarioApiDocumentation.ApiController @CuestionarioApiDocumentation.ApiController
@@ -35,7 +36,7 @@ export class CuestionarioController {
@Post('withEvento') @Post('withEvento')
@CuestionarioApiDocumentation.ApiCreateWithEvento @CuestionarioApiDocumentation.ApiCreateWithEvento
createCuestionarioEvento( createCuestionarioEvento(
@Body() createCuestionarioDto: CreateCuestionarioEventoDto, @Body() createCuestionarioDto: CreateEventoWithCuestionarioDto,
) { ) {
return this.cuestionarioService.createCuestionarioEvento( return this.cuestionarioService.createCuestionarioEvento(
createCuestionarioDto, createCuestionarioDto,
+53 -55
View File
@@ -1,4 +1,8 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import {
Injectable,
InternalServerErrorException,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource, In } from 'typeorm'; import { Repository, DataSource, In } from 'typeorm';
import { import {
@@ -19,6 +23,7 @@ import {
} from '../tipo_pregunta/entities/tipo_pregunta.entity'; } from '../tipo_pregunta/entities/tipo_pregunta.entity';
import { Evento } from '../evento/entities/evento.entity'; import { Evento } from '../evento/entities/evento.entity';
import { EventoService } from '../evento/evento.service'; import { EventoService } from '../evento/evento.service';
import { CreateEventoWithCuestionarioDto } from './dto/create-evento-cuestionario.dto';
@Injectable() @Injectable()
export class CuestionarioService { export class CuestionarioService {
@@ -165,80 +170,72 @@ export class CuestionarioService {
return { return {
success: true, success: true,
message: 'Cuestionario creado exitosamente', message: 'Cuestionario creado exitosamente',
cuestionario: savedCuestionario, id_cuestionario: savedCuestionario.id_cuestionario,
id_evento: savedCuestionario.id_evento,
}; };
} catch (error) { } catch (error) {
await queryRunner.rollbackTransaction(); await queryRunner.rollbackTransaction();
throw error; throw new InternalServerErrorException(
'Error al crear el cuestionario y evento',
error.message,
);
} finally { } finally {
await queryRunner.release(); await queryRunner.release();
} }
} }
async createCuestionarioEvento( async createCuestionarioEvento(
createCuestionarioDto: CreateCuestionarioEventoDto, createCuestionarioDto: CreateEventoWithCuestionarioDto,
) { ) {
const { evento, cuestionario } = createCuestionarioDto;
const queryRunner = this.dataSource.createQueryRunner(); const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect(); await queryRunner.connect();
await queryRunner.startTransaction(); await queryRunner.startTransaction();
try { try {
// Verificar si se especificó un evento y búscarlo/crearlo // Buscar si ya existe un evento con el nombre proporcionado
let idEvento: number | undefined = undefined; let eventoExistente = await this.eventoRepository.findOne({
where: { nombre_evento: evento.nombre_evento },
});
if (createCuestionarioDto.evento) { // Si el evento no existe, crearlo
// Buscar si el evento ya existe if (!eventoExistente) {
let evento = await this.eventoRepository.findOne({ eventoExistente = queryRunner.manager.create(Evento, evento);
where: { nombre_evento: createCuestionarioDto.evento }, await queryRunner.manager.save(eventoExistente);
}); console.log(`Creando nuevo evento: ${evento.nombre_evento}`);
// Si no existe, crearlo
if (!evento) {
const nuevoEvento = {
nombre_evento: createCuestionarioDto.evento,
tipo_evento: 'Predeterminado',
fecha_inicio: createCuestionarioDto.fecha_inicio || new Date(),
fecha_fin:
createCuestionarioDto.fecha_fin ||
new Date(Date.now() + 86400000), // 1 día después si no hay fecha
};
evento = await queryRunner.manager.save(Evento, nuevoEvento);
} else {
console.log(
`Evento encontrado: ${evento.nombre_evento}, ID: ${evento.id_evento}`,
);
}
idEvento = evento.id_evento;
} }
// 1. Crear cuestionario // Crear el cuestionario asociado al evento
const cuestionario = new Cuestionario(); const cuestionarioEntity = queryRunner.manager.create(Cuestionario, {
cuestionario.nombre_form = createCuestionarioDto.nombre_form; nombre_form: cuestionario.nombre_form,
cuestionario.descripcion = createCuestionarioDto.descripcion; descripcion: cuestionario.descripcion,
cuestionario.fecha_inicio = new Date(createCuestionarioDto.fecha_inicio); fecha_inicio: new Date(cuestionario.fecha_inicio),
cuestionario.fecha_fin = new Date(createCuestionarioDto.fecha_fin); fecha_fin: new Date(cuestionario.fecha_fin),
cuestionario.id_tipo_cuestionario = id_tipo_cuestionario: cuestionario.id_tipo_cuestionario,
createCuestionarioDto.id_tipo_cuestionario; contador_secciones: cuestionario.secciones?.length || 0,
cuestionario.contador_secciones = editable: true,
createCuestionarioDto.secciones?.length || 0; id_evento: eventoExistente.id_evento, // Asociar el evento creado
cuestionario.editable = true; cupo_maximo: cuestionario.cupo_maximo,
});
const savedCuestionario =
await queryRunner.manager.save(cuestionarioEntity);
// Asociar el cuestionario con el evento si existe console.log(
if (idEvento !== undefined) { `Cuestionario creado: ${savedCuestionario.nombre_form}, ID: ${savedCuestionario.id_cuestionario}`,
cuestionario.id_evento = idEvento; );
}
const savedCuestionario = await queryRunner.manager.save(cuestionario); // Crear las secciones del cuestionario
// 2. Crear secciones y vincularlas al cuestionario
if ( if (
createCuestionarioDto.secciones && createCuestionarioDto.cuestionario.secciones &&
createCuestionarioDto.secciones.length > 0 createCuestionarioDto.cuestionario.secciones.length > 0
) { ) {
for (let i = 0; i < createCuestionarioDto.secciones.length; i++) { for (
const seccionDto = createCuestionarioDto.secciones[i]; let i = 0;
i < createCuestionarioDto.cuestionario.secciones.length;
i++
) {
const seccionDto = createCuestionarioDto.cuestionario.secciones[i];
// Crear sección // Crear sección
const seccion = new Seccion(); const seccion = new Seccion();
@@ -335,10 +332,11 @@ export class CuestionarioService {
} }
await queryRunner.commitTransaction(); await queryRunner.commitTransaction();
return { return {
success: true, success: true,
cuestionario: savedCuestionario, message: 'Cuestionario y evento creados exitosamente',
id_cuestionario: savedCuestionario.id_cuestionario,
id_evento: eventoExistente.id_evento,
}; };
} catch (error) { } catch (error) {
await queryRunner.rollbackTransaction(); await queryRunner.rollbackTransaction();
@@ -13,7 +13,7 @@ import {
CreateCuestionarioDto, CreateCuestionarioDto,
CreateCuestionarioEventoDto, CreateCuestionarioEventoDto,
} from '../dto/create-cuestionario.dto'; } from '../dto/create-cuestionario.dto';
import { ejemploCuestionarioAlumno } from './cuestionario.examples'; import { ejemploCreateEventoWithCuestionario, ejemploCuestionarioAlumno } from './cuestionario.examples';
export class CuestionarioApiDocumentation { export class CuestionarioApiDocumentation {
// Decoradores para toda la clase del controlador // Decoradores para toda la clase del controlador
@@ -47,18 +47,6 @@ export class CuestionarioApiDocumentation {
type: 'number', type: 'number',
example: 1, example: 1,
}), }),
/* ApiResponse({
status: 200,
description:
'Formulario completo con todas sus secciones, preguntas y opciones. Las preguntas incluyen el atributo "validacion" si tienen reglas de validación específicas (como "correo", "cuenta_alumno", "telefono", "nombre", etc.)',
type: FormularioDto,
content: {
'application/json': {
schema: { $ref: getSchemaPath(FormularioDto) },
example: feriaSexualidad,
},
},
}), */
ApiResponse({ status: 404, description: 'Cuestionario no encontrado' }), ApiResponse({ status: 404, description: 'Cuestionario no encontrado' }),
); );
@@ -79,39 +67,7 @@ export class CuestionarioApiDocumentation {
ApiBody({ ApiBody({
type: CreateCuestionarioEventoDto, type: CreateCuestionarioEventoDto,
examples: { examples: {
ejemplo_con_evento_nuevo: { ejemplo_con_evento_nuevo: ejemploCreateEventoWithCuestionario,
summary: 'Cuestionario que crea evento automáticamente',
value: {
nombre_form: 'Registro de participantes',
descripcion: 'Formulario para inscripción de asistentes.',
evento: 'Foro Juvenil de Tecnología 2025',
fecha_inicio: '2025-07-20T10:00:00',
fecha_fin: '2025-07-20T17:00:00',
id_tipo_cuestionario: 2,
secciones: [
{
titulo: 'Datos generales',
descripcion: 'Recopilación de información personal',
preguntas: [
{
titulo: '¿Cuál es tu edad?',
tipo: 'Abierta',
obligatoria: true,
},
{
titulo: '¿Cómo te enteraste del evento?',
tipo: 'Multiple',
opciones: [
{ valor: 'Redes sociales' },
{ valor: 'Correo electrónico' },
{ valor: 'Por un amigo' },
],
},
],
},
],
},
},
}, },
}), }),
); );
@@ -1,5 +1,85 @@
import { ejemploFeriaSexualidad } from 'src/evento/docs/evento.examples';
import { TiposValidacion } from 'src/pregunta/entities/pregunta.entity'; import { TiposValidacion } from 'src/pregunta/entities/pregunta.entity';
export const ejemploCreateEventoWithCuestionario = {
summary: 'Crear un evento con cuestionario asociado',
value: {
evento: ejemploFeriaSexualidad.value,
cuestionario: {
nombre_form: 'Registro',
descripcion:
'Con este formulario estaras reservando tu lugar en el evento. Por favor, completa todos los campos obligatorios.',
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',
descripcion:
'Proporciona tus datos personales para poder contactarte y enviarte información relevante sobre la Feria de la Sexualidad.',
preguntas: [
{
titulo: '¿Eres parte de la comunidad de la FES Acatlán?',
tipo: 'Cerrada',
opciones: [{ valor: 'Si' }, { valor: 'No' }],
obligatoria: true,
validacion: TiposValidacion.COMUNIDAD_ALUMNO,
},
{
titulo: 'Numero de cuenta',
tipo: 'Abierta (Respuesta corta)',
obligatoria: false,
validacion: TiposValidacion.CUENTA_ALUMNO,
},
{
titulo: 'Correo electrónico',
tipo: 'Abierta (Respuesta corta)',
obligatoria: true,
validacion: TiposValidacion.CORREO,
},
{
titulo: 'Nombre(s)',
tipo: 'Abierta (Respuesta corta)',
obligatoria: true,
validacion: TiposValidacion.NOMBRE,
},
{
titulo: 'Apellidos',
tipo: 'Abierta (Respuesta corta)',
obligatoria: true,
validacion: TiposValidacion.APELLIDOS,
},
{
titulo: 'Género',
tipo: 'Cerrada',
obligatoria: true,
validacion: TiposValidacion.GENERO,
opciones: [
{ valor: 'Masculino' },
{ valor: 'Femenino' },
{ valor: 'Prefiero no decirlo' },
],
},
{
titulo: 'Institución de procedencia',
tipo: 'Abierta (Respuesta corta)',
obligatoria: true,
validacion: TiposValidacion.INSTITUCION,
},
{
titulo: 'Carrera',
tipo: 'Abierta (Respuesta corta)',
obligatoria: true,
validacion: TiposValidacion.CARRERA,
},
],
},
],
},
},
};
export const ejemploCuestionarioAlumno = { export const ejemploCuestionarioAlumno = {
summary: summary:
'Cuestionario para el registro de asistencia a un evento para comunidad estudiantil', 'Cuestionario para el registro de asistencia a un evento para comunidad estudiantil',
@@ -0,0 +1,7 @@
import { CreateEventoDto } from 'src/evento/dto/create-evento.dto';
import { CreateCuestionarioDto, CreateCuestionarioEventoDto } from './create-cuestionario.dto';
export interface CreateEventoWithCuestionarioDto {
evento: CreateEventoDto;
cuestionario: CreateCuestionarioEventoDto;
}
+2 -2
View File
@@ -54,8 +54,8 @@ export const createMainDbConfig = async (
retryDelay: 3000, retryDelay: 3000,
connectTimeout: 30000, connectTimeout: 30000,
synchronize: configService.get<boolean>('SYNCHRONIZE') || false, synchronize: configService.get<string>('SYNCHRONIZE') === 'true',
dropSchema: configService.get<boolean>('DROP_SCHEMA') || false, dropSchema: configService.get<string>('DROP_SCHEMA') === 'true',
}); });
export const createAlumnosDbConfig = async ( export const createAlumnosDbConfig = async (