Refactor cuestionario module to support event creation with associated questionnaire, including new DTO and enhanced error handling.
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
|||||||
Reference in New Issue
Block a user