From 551cc3e3b24a8d99322fc5f2c623f687bf2feb55 Mon Sep 17 00:00:00 2001 From: miguel Date: Mon, 23 Jun 2025 18:58:20 -0600 Subject: [PATCH] Refactor cuestionario module to support event creation with associated questionnaire, including new DTO and enhanced error handling. --- src/cuestionario/cuestionario.controller.ts | 3 +- src/cuestionario/cuestionario.service.ts | 108 +++++++++--------- .../docs/cuestionario.documentation.ts | 48 +------- .../docs/cuestionario.examples.ts | 80 +++++++++++++ .../dto/create-evento-cuestionario.dto.ts | 7 ++ src/db/typeorm.config.ts | 4 +- 6 files changed, 146 insertions(+), 104 deletions(-) create mode 100644 src/cuestionario/dto/create-evento-cuestionario.dto.ts diff --git a/src/cuestionario/cuestionario.controller.ts b/src/cuestionario/cuestionario.controller.ts index ad6c5b4..2aaaa57 100644 --- a/src/cuestionario/cuestionario.controller.ts +++ b/src/cuestionario/cuestionario.controller.ts @@ -20,6 +20,7 @@ import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { extname } from 'path'; import { CuestionarioApiDocumentation } from './docs/cuestionario.documentation'; +import { CreateEventoWithCuestionarioDto } from './dto/create-evento-cuestionario.dto'; @Controller('cuestionario') @CuestionarioApiDocumentation.ApiController @@ -35,7 +36,7 @@ export class CuestionarioController { @Post('withEvento') @CuestionarioApiDocumentation.ApiCreateWithEvento createCuestionarioEvento( - @Body() createCuestionarioDto: CreateCuestionarioEventoDto, + @Body() createCuestionarioDto: CreateEventoWithCuestionarioDto, ) { return this.cuestionarioService.createCuestionarioEvento( createCuestionarioDto, diff --git a/src/cuestionario/cuestionario.service.ts b/src/cuestionario/cuestionario.service.ts index 505ebac..c3a8aa1 100644 --- a/src/cuestionario/cuestionario.service.ts +++ b/src/cuestionario/cuestionario.service.ts @@ -1,4 +1,8 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { + Injectable, + InternalServerErrorException, + NotFoundException, +} from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, DataSource, In } from 'typeorm'; import { @@ -19,6 +23,7 @@ import { } from '../tipo_pregunta/entities/tipo_pregunta.entity'; import { Evento } from '../evento/entities/evento.entity'; import { EventoService } from '../evento/evento.service'; +import { CreateEventoWithCuestionarioDto } from './dto/create-evento-cuestionario.dto'; @Injectable() export class CuestionarioService { @@ -165,80 +170,72 @@ export class CuestionarioService { return { success: true, message: 'Cuestionario creado exitosamente', - cuestionario: savedCuestionario, + id_cuestionario: savedCuestionario.id_cuestionario, + id_evento: savedCuestionario.id_evento, }; } catch (error) { await queryRunner.rollbackTransaction(); - throw error; + throw new InternalServerErrorException( + 'Error al crear el cuestionario y evento', + error.message, + ); } finally { await queryRunner.release(); } } async createCuestionarioEvento( - createCuestionarioDto: CreateCuestionarioEventoDto, + createCuestionarioDto: CreateEventoWithCuestionarioDto, ) { + const { evento, cuestionario } = createCuestionarioDto; + const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { - // Verificar si se especificó un evento y búscarlo/crearlo - let idEvento: number | undefined = undefined; + // Buscar si ya existe un evento con el nombre proporcionado + let eventoExistente = await this.eventoRepository.findOne({ + where: { nombre_evento: evento.nombre_evento }, + }); - if (createCuestionarioDto.evento) { - // Buscar si el evento ya existe - let evento = await this.eventoRepository.findOne({ - where: { nombre_evento: createCuestionarioDto.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; + // Si el evento no existe, crearlo + if (!eventoExistente) { + eventoExistente = queryRunner.manager.create(Evento, evento); + await queryRunner.manager.save(eventoExistente); + console.log(`Creando nuevo evento: ${evento.nombre_evento}`); } - // 1. Crear cuestionario - const cuestionario = new Cuestionario(); - cuestionario.nombre_form = createCuestionarioDto.nombre_form; - cuestionario.descripcion = createCuestionarioDto.descripcion; - cuestionario.fecha_inicio = new Date(createCuestionarioDto.fecha_inicio); - cuestionario.fecha_fin = new Date(createCuestionarioDto.fecha_fin); - cuestionario.id_tipo_cuestionario = - createCuestionarioDto.id_tipo_cuestionario; - cuestionario.contador_secciones = - createCuestionarioDto.secciones?.length || 0; - cuestionario.editable = true; + // Crear el cuestionario asociado al evento + const cuestionarioEntity = queryRunner.manager.create(Cuestionario, { + nombre_form: cuestionario.nombre_form, + descripcion: cuestionario.descripcion, + fecha_inicio: new Date(cuestionario.fecha_inicio), + fecha_fin: new Date(cuestionario.fecha_fin), + id_tipo_cuestionario: cuestionario.id_tipo_cuestionario, + contador_secciones: cuestionario.secciones?.length || 0, + editable: true, + id_evento: eventoExistente.id_evento, // Asociar el evento creado + cupo_maximo: cuestionario.cupo_maximo, + }); + const savedCuestionario = + await queryRunner.manager.save(cuestionarioEntity); - // Asociar el cuestionario con el evento si existe - if (idEvento !== undefined) { - cuestionario.id_evento = idEvento; - } + console.log( + `Cuestionario creado: ${savedCuestionario.nombre_form}, ID: ${savedCuestionario.id_cuestionario}`, + ); - const savedCuestionario = await queryRunner.manager.save(cuestionario); - - // 2. Crear secciones y vincularlas al cuestionario + // Crear las secciones del cuestionario if ( - createCuestionarioDto.secciones && - createCuestionarioDto.secciones.length > 0 + createCuestionarioDto.cuestionario.secciones && + createCuestionarioDto.cuestionario.secciones.length > 0 ) { - for (let i = 0; i < createCuestionarioDto.secciones.length; i++) { - const seccionDto = createCuestionarioDto.secciones[i]; + for ( + let i = 0; + i < createCuestionarioDto.cuestionario.secciones.length; + i++ + ) { + const seccionDto = createCuestionarioDto.cuestionario.secciones[i]; // Crear sección const seccion = new Seccion(); @@ -335,10 +332,11 @@ export class CuestionarioService { } await queryRunner.commitTransaction(); - return { success: true, - cuestionario: savedCuestionario, + message: 'Cuestionario y evento creados exitosamente', + id_cuestionario: savedCuestionario.id_cuestionario, + id_evento: eventoExistente.id_evento, }; } catch (error) { await queryRunner.rollbackTransaction(); diff --git a/src/cuestionario/docs/cuestionario.documentation.ts b/src/cuestionario/docs/cuestionario.documentation.ts index b4a8550..6d1b45a 100644 --- a/src/cuestionario/docs/cuestionario.documentation.ts +++ b/src/cuestionario/docs/cuestionario.documentation.ts @@ -13,7 +13,7 @@ import { CreateCuestionarioDto, CreateCuestionarioEventoDto, } from '../dto/create-cuestionario.dto'; -import { ejemploCuestionarioAlumno } from './cuestionario.examples'; +import { ejemploCreateEventoWithCuestionario, ejemploCuestionarioAlumno } from './cuestionario.examples'; export class CuestionarioApiDocumentation { // Decoradores para toda la clase del controlador @@ -47,18 +47,6 @@ export class CuestionarioApiDocumentation { type: 'number', 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' }), ); @@ -79,39 +67,7 @@ export class CuestionarioApiDocumentation { ApiBody({ type: CreateCuestionarioEventoDto, examples: { - ejemplo_con_evento_nuevo: { - 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' }, - ], - }, - ], - }, - ], - }, - }, + ejemplo_con_evento_nuevo: ejemploCreateEventoWithCuestionario, }, }), ); diff --git a/src/cuestionario/docs/cuestionario.examples.ts b/src/cuestionario/docs/cuestionario.examples.ts index 3f43863..1f06a18 100644 --- a/src/cuestionario/docs/cuestionario.examples.ts +++ b/src/cuestionario/docs/cuestionario.examples.ts @@ -1,5 +1,85 @@ +import { ejemploFeriaSexualidad } from 'src/evento/docs/evento.examples'; 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 = { summary: 'Cuestionario para el registro de asistencia a un evento para comunidad estudiantil', diff --git a/src/cuestionario/dto/create-evento-cuestionario.dto.ts b/src/cuestionario/dto/create-evento-cuestionario.dto.ts new file mode 100644 index 0000000..2985fcf --- /dev/null +++ b/src/cuestionario/dto/create-evento-cuestionario.dto.ts @@ -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; +} diff --git a/src/db/typeorm.config.ts b/src/db/typeorm.config.ts index 7164f7e..1ae25ac 100644 --- a/src/db/typeorm.config.ts +++ b/src/db/typeorm.config.ts @@ -54,8 +54,8 @@ export const createMainDbConfig = async ( retryDelay: 3000, connectTimeout: 30000, - synchronize: configService.get('SYNCHRONIZE') || false, - dropSchema: configService.get('DROP_SCHEMA') || false, + synchronize: configService.get('SYNCHRONIZE') === 'true', + dropSchema: configService.get('DROP_SCHEMA') === 'true', }); export const createAlumnosDbConfig = async (