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 { 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,
+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 { 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();
@@ -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,
},
}),
);
@@ -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',
@@ -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,
connectTimeout: 30000,
synchronize: configService.get<boolean>('SYNCHRONIZE') || false,
dropSchema: configService.get<boolean>('DROP_SCHEMA') || false,
synchronize: configService.get<string>('SYNCHRONIZE') === 'true',
dropSchema: configService.get<string>('DROP_SCHEMA') === 'true',
});
export const createAlumnosDbConfig = async (