cuestionario contestado, respuestas guardadas
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags, getSchemaPath } from '@nestjs/swagger';
|
||||
import { feriaSexualidad } from '../../utils/crear_formulario_feria';
|
||||
import { applyDecorators } from '@nestjs/common';
|
||||
import { FormularioDto } from './dto/formulario.schema';
|
||||
import { FormularioDto, FormularioFeriaSchema } from './dto/formulario.schema';
|
||||
|
||||
export class CuestionarioApiDocumentation {
|
||||
// Decoradores para toda la clase del controlador
|
||||
|
||||
@@ -79,15 +79,22 @@ export class CuestionarioService {
|
||||
for (let j = 0; j < seccionDto.preguntas.length; j++) {
|
||||
const preguntaDto = seccionDto.preguntas[j];
|
||||
|
||||
// Obtener el tipo de pregunta por su nombre
|
||||
const tipoPregunta = await queryRunner.manager.findOne(TipoPregunta, {
|
||||
where: { tipo_pregunta: preguntaDto.tipo },
|
||||
});
|
||||
// Mapear directamente el tipo de pregunta a su ID correspondiente
|
||||
let idTipoPregunta = 7; // Valor por defecto (desconocido)
|
||||
|
||||
if (!tipoPregunta) {
|
||||
throw new Error(`Tipo de pregunta '${preguntaDto.tipo}' no encontrado`);
|
||||
// Mapeamos el tipo de pregunta a su ID correspondiente
|
||||
if (preguntaDto.tipo === 'Cerrada') {
|
||||
idTipoPregunta = 1;
|
||||
} else if (preguntaDto.tipo === 'Abierta') {
|
||||
idTipoPregunta = 2;
|
||||
} else if (preguntaDto.tipo === 'Multiple') {
|
||||
idTipoPregunta = 3;
|
||||
} else {
|
||||
console.warn(`Tipo de pregunta no reconocido: ${preguntaDto.tipo}. Se asignará como desconocido.`);
|
||||
}
|
||||
|
||||
const tipoPregunta = { id_tipo: idTipoPregunta, tipo_pregunta: preguntaDto.tipo };
|
||||
|
||||
// Crear pregunta
|
||||
const pregunta = new Pregunta();
|
||||
pregunta.pregunta = preguntaDto.titulo;
|
||||
@@ -179,6 +186,13 @@ export class CuestionarioService {
|
||||
tipo_cuestionario: 'Encuesta'
|
||||
};
|
||||
|
||||
// Mapeador de ID de tipo de pregunta a nombres conocidos
|
||||
const tiposPreguntaMap = {
|
||||
1: { id_tipo: 1, tipo_pregunta: 'Cerrada' },
|
||||
2: { id_tipo: 2, tipo_pregunta: 'Abierta' },
|
||||
3: { id_tipo: 3, tipo_pregunta: 'Multiple' }
|
||||
};
|
||||
|
||||
// Obtener las relaciones cuestionario-seccion
|
||||
const cuestionarioSecciones = await this.cuestionarioSeccionRepository.find({
|
||||
where: { id_cuestionario: id },
|
||||
@@ -216,14 +230,8 @@ export class CuestionarioService {
|
||||
|
||||
if (!pregunta) return null;
|
||||
|
||||
// Usar un mapeo hardcodeado para tipos de pregunta
|
||||
const tiposPregunta = {
|
||||
1: { id_tipo: 1, tipo_pregunta: 'Cerrada' },
|
||||
2: { id_tipo: 2, tipo_pregunta: 'Abierta' },
|
||||
3: { id_tipo: 3, tipo_pregunta: 'Multiple' }
|
||||
};
|
||||
|
||||
const tipoPregunta = tiposPregunta[pregunta.id_tipo_pregunta] ||
|
||||
// Usar el mapeo de tipos de pregunta definido arriba
|
||||
const tipoPregunta = tiposPreguntaMap[pregunta.id_tipo_pregunta] ||
|
||||
{ id_tipo: pregunta.id_tipo_pregunta, tipo_pregunta: 'Desconocido' };
|
||||
|
||||
// Obtener opciones para preguntas de tipo multiple/radio
|
||||
|
||||
@@ -1,94 +1,198 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { TipoPreguntaEnum } from '../../pregunta/dto/create-pregunta.dto';
|
||||
|
||||
export class OpcionDto {
|
||||
@ApiProperty({
|
||||
description: 'Valor de la opción',
|
||||
example: 'Si'
|
||||
})
|
||||
valor: string;
|
||||
}
|
||||
|
||||
export class PreguntaDto {
|
||||
@ApiProperty({
|
||||
description: 'Título de la pregunta',
|
||||
example: '¿Eres parte de la comunidad de la FES Acatlán?'
|
||||
})
|
||||
titulo: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Indica si la pregunta es obligatoria',
|
||||
example: true
|
||||
})
|
||||
obligatoria: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Tipo de pregunta (Texto, Numero, Multiple, Radio, Abierto, Fecha)',
|
||||
example: 'Multiple'
|
||||
})
|
||||
tipo: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Lista de opciones para preguntas de tipo Multiple o Radio',
|
||||
type: [OpcionDto],
|
||||
required: false
|
||||
})
|
||||
opciones?: OpcionDto[];
|
||||
}
|
||||
|
||||
export class SeccionDto {
|
||||
@ApiProperty({
|
||||
description: 'Título de la sección',
|
||||
example: 'Información Personal'
|
||||
})
|
||||
titulo: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Descripción de la sección',
|
||||
example: 'Proporciona tus datos personales para poder contactarte.'
|
||||
})
|
||||
descripcion?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Lista de preguntas que conforman la sección',
|
||||
type: [PreguntaDto]
|
||||
})
|
||||
preguntas: PreguntaDto[];
|
||||
}
|
||||
// Ejemplo de respuesta de cuestionario completo
|
||||
export const FormularioFeriaSchema = {
|
||||
tipo_cuestionario: {
|
||||
id_tipo_cuestionario: 1,
|
||||
tipo_cuestionario: 'Encuesta'
|
||||
},
|
||||
cuestionario: {
|
||||
id_cuestionario: 10,
|
||||
nombre_form: 'Registro para la Feria de la Sexualidad - Comunidad FES Acatlán',
|
||||
contador_secciones: 2,
|
||||
descripcion: 'La Feria de la Sexualidad es un espacio seguro e informativo dirigido a toda la comunidad universitaria. Este registro nos ayudará a organizar mejor las actividades, talleres y charlas, así como conocer tus intereses. Por favor, completa el siguiente formulario para confirmar tu participación.',
|
||||
editable: true,
|
||||
fecha_inicio: '2025-03-26T00:00:00',
|
||||
fecha_fin: '2025-03-31T23:59:59',
|
||||
id_cuestionario_original: null,
|
||||
id_tipo_cuestionario: 1,
|
||||
secciones: [
|
||||
{
|
||||
id_cuestionario_seccion: 1,
|
||||
posicion: 1,
|
||||
seccion: {
|
||||
id_seccion: 100,
|
||||
contador_pregunta: 2,
|
||||
descripcion: 'Información básica',
|
||||
titulo: 'Datos personales'
|
||||
},
|
||||
preguntas: [
|
||||
{
|
||||
id_seccion_pregunta: 1000,
|
||||
posicion: 1,
|
||||
pregunta: {
|
||||
id_pregunta: 101,
|
||||
pregunta: '¿Eres parte de la comunidad de la FES Acatlán?',
|
||||
contador_opcion: 2,
|
||||
obligatoria: true,
|
||||
id_tipo_pregunta: 1,
|
||||
id_opcion_dependiente: null,
|
||||
tipo_pregunta: {
|
||||
id_tipo: 1,
|
||||
tipo_pregunta: 'Cerrada'
|
||||
},
|
||||
opciones: [
|
||||
{
|
||||
id_pregunta_opcion: 50001,
|
||||
posicion: 1,
|
||||
id_opcion: 90001,
|
||||
opcion: {
|
||||
id_opcion: 90001,
|
||||
opcion: 'Sí'
|
||||
}
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50002,
|
||||
posicion: 2,
|
||||
id_opcion: 90002,
|
||||
opcion: {
|
||||
id_opcion: 90002,
|
||||
opcion: 'No'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id_seccion_pregunta: 1001,
|
||||
posicion: 2,
|
||||
pregunta: {
|
||||
id_pregunta: 102,
|
||||
pregunta: 'Número de cuenta (solo para estudiantes de la FES Acatlán)',
|
||||
contador_opcion: 0,
|
||||
obligatoria: false,
|
||||
id_tipo_pregunta: 2,
|
||||
id_opcion_dependiente: 90001, // Dependiente de la respuesta "Sí" en la pregunta anterior
|
||||
tipo_pregunta: {
|
||||
id_tipo: 2,
|
||||
tipo_pregunta: 'Abierta'
|
||||
},
|
||||
opciones: []
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id_cuestionario_seccion: 2,
|
||||
posicion: 2,
|
||||
seccion: {
|
||||
id_seccion: 101,
|
||||
contador_pregunta: 2,
|
||||
descripcion: 'Información sobre tus intereses',
|
||||
titulo: 'Intereses'
|
||||
},
|
||||
preguntas: [
|
||||
{
|
||||
id_seccion_pregunta: 1003,
|
||||
posicion: 1,
|
||||
pregunta: {
|
||||
id_pregunta: 106,
|
||||
pregunta: '¿Qué temas te interesan para la Feria de la Sexualidad?',
|
||||
contador_opcion: 5,
|
||||
obligatoria: true,
|
||||
id_tipo_pregunta: 3,
|
||||
id_opcion_dependiente: null,
|
||||
tipo_pregunta: {
|
||||
id_tipo: 3,
|
||||
tipo_pregunta: 'Multiple'
|
||||
},
|
||||
opciones: [
|
||||
{
|
||||
id_pregunta_opcion: 50010,
|
||||
posicion: 1,
|
||||
id_opcion: 90010,
|
||||
opcion: {
|
||||
id_opcion: 90010,
|
||||
opcion: 'Educación sexual'
|
||||
}
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50011,
|
||||
posicion: 2,
|
||||
id_opcion: 90011,
|
||||
opcion: {
|
||||
id_opcion: 90011,
|
||||
opcion: 'Prevención de ITS'
|
||||
}
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50012,
|
||||
posicion: 3,
|
||||
id_opcion: 90012,
|
||||
opcion: {
|
||||
id_opcion: 90012,
|
||||
opcion: 'Salud reproductiva'
|
||||
}
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50013,
|
||||
posicion: 4,
|
||||
id_opcion: 90013,
|
||||
opcion: {
|
||||
id_opcion: 90013,
|
||||
opcion: 'Diversidad sexual y de género'
|
||||
}
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50014,
|
||||
posicion: 5,
|
||||
id_opcion: 90014,
|
||||
opcion: {
|
||||
id_opcion: 90014,
|
||||
opcion: 'Relaciones saludables'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id_seccion_pregunta: 1004,
|
||||
posicion: 2,
|
||||
pregunta: {
|
||||
id_pregunta: 107,
|
||||
pregunta: '¿Algún otro tema que te gustaría que se abordara?',
|
||||
contador_opcion: 0,
|
||||
obligatoria: false,
|
||||
id_tipo_pregunta: 2,
|
||||
id_opcion_dependiente: null,
|
||||
tipo_pregunta: {
|
||||
id_tipo: 2,
|
||||
tipo_pregunta: 'Abierta'
|
||||
},
|
||||
opciones: []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// Ejemplo de tipo FormularioDto para usar en la API
|
||||
export class FormularioDto {
|
||||
@ApiProperty({
|
||||
description: 'Nombre del formulario',
|
||||
example: 'Registro para la Feria de la Sexualidad - FES Acatlán'
|
||||
description: 'Tipo de cuestionario',
|
||||
example: {
|
||||
id_tipo_cuestionario: 1,
|
||||
tipo_cuestionario: 'Encuesta'
|
||||
}
|
||||
})
|
||||
nombre_form: string;
|
||||
tipo_cuestionario: any;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Descripción del formulario',
|
||||
example: 'La Feria de la Sexualidad es un espacio seguro e informativo...'
|
||||
description: 'Información completa del cuestionario',
|
||||
example: FormularioFeriaSchema.cuestionario
|
||||
})
|
||||
descripcion?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Fecha de inicio del formulario',
|
||||
example: '2025-03-07T00:00:01'
|
||||
})
|
||||
fecha_inicio?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Fecha de fin del formulario',
|
||||
example: '2025-03-18T23:59:59'
|
||||
})
|
||||
fecha_fin?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'ID del tipo de cuestionario',
|
||||
example: 1
|
||||
})
|
||||
id_tipo_cuestionario: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Secciones que conforman el formulario',
|
||||
type: [SeccionDto]
|
||||
})
|
||||
secciones: SeccionDto[];
|
||||
cuestionario: any;
|
||||
}
|
||||
@@ -2,7 +2,11 @@ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/commo
|
||||
import { CuestionarioRespondidoService } from './cuestionario_respondido.service';
|
||||
import { CreateCuestionarioRespondidoDto } from './dto/create-cuestionario_respondido.dto';
|
||||
import { UpdateCuestionarioRespondidoDto } from './dto/update-cuestionario_respondido.dto';
|
||||
import { SubmitRespuestasDto } from './dto/submit-respuestas.dto';
|
||||
import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { CuestionarioRespondidoApiDocumentation } from './cuestionario_respondido.documentation';
|
||||
|
||||
@ApiTags('Cuestionario Respondido')
|
||||
@Controller('cuestionario-respondido')
|
||||
export class CuestionarioRespondidoController {
|
||||
constructor(private readonly cuestionarioRespondidoService: CuestionarioRespondidoService) {}
|
||||
@@ -12,6 +16,22 @@ export class CuestionarioRespondidoController {
|
||||
return this.cuestionarioRespondidoService.create(createCuestionarioRespondidoDto);
|
||||
}
|
||||
|
||||
@Post('submit')
|
||||
@CuestionarioRespondidoApiDocumentation.ApiSubmitRespuestas
|
||||
@ApiBody({
|
||||
type: SubmitRespuestasDto,
|
||||
examples: {
|
||||
comunidad: CuestionarioRespondidoApiDocumentation.ApiSubmitRespuestasExamples.communityExample,
|
||||
externos: CuestionarioRespondidoApiDocumentation.ApiSubmitRespuestasExamples.externalExample
|
||||
}
|
||||
})
|
||||
@CuestionarioRespondidoApiDocumentation.ApiSubmitResponse201
|
||||
@CuestionarioRespondidoApiDocumentation.ApiSubmitResponse400
|
||||
@CuestionarioRespondidoApiDocumentation.ApiSubmitResponse404
|
||||
submitRespuestas(@Body() submitRespuestasDto: SubmitRespuestasDto) {
|
||||
return this.cuestionarioRespondidoService.submitRespuestas(submitRespuestasDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.cuestionarioRespondidoService.findAll();
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
export class CuestionarioRespondidoApiDocumentation {
|
||||
static ApiController = ApiTags('Cuestionario Respondido');
|
||||
|
||||
static ApiSubmitRespuestas = ApiOperation({
|
||||
summary: 'Enviar respuestas a un cuestionario',
|
||||
description: 'Registra las respuestas de un participante a un cuestionario. Crea un registro de participante si no existe uno con ese correo.'
|
||||
});
|
||||
|
||||
static ApiSubmitResponse201 = ApiResponse({
|
||||
status: 201,
|
||||
description: 'Respuestas guardadas correctamente',
|
||||
schema: {
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: 'Respuestas guardadas correctamente' },
|
||||
cuestionarioRespondidoId: { type: 'number', example: 1 }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
static ApiSubmitResponse400 = ApiResponse({
|
||||
status: 400,
|
||||
description: 'Datos inválidos en la solicitud',
|
||||
schema: {
|
||||
properties: {
|
||||
statusCode: { type: 'number', example: 400 },
|
||||
message: { type: 'string', example: 'No se encontró la opción \'Opción inválida\' para la pregunta 101' },
|
||||
error: { type: 'string', example: 'Bad Request' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
static ApiSubmitResponse404 = ApiResponse({
|
||||
status: 404,
|
||||
description: 'Cuestionario o pregunta no encontrada',
|
||||
schema: {
|
||||
properties: {
|
||||
statusCode: { type: 'number', example: 404 },
|
||||
message: { type: 'string', example: 'Cuestionario con ID 999 no encontrado' },
|
||||
error: { type: 'string', example: 'Not Found' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
static ApiSubmitRespuestasExamples = {
|
||||
communityExample: {
|
||||
summary: 'Ejemplo de respuesta para un miembro de la comunidad',
|
||||
value: {
|
||||
id_formulario: 1,
|
||||
correo: 'estudiante@acatlan.unam.mx',
|
||||
respuestas: [
|
||||
{ id_pregunta: 101, valor: 'Si' }, // ¿Eres parte de la comunidad de la FES Acatlán?
|
||||
{ id_pregunta: 102, valor: '123456789' }, // Numero de cuenta
|
||||
{ id_pregunta: 106, valor: 'Prefiero no decirlo' } // Género
|
||||
],
|
||||
fecha_envio: '2025-04-02T13:45:00'
|
||||
},
|
||||
description: 'Datos de un estudiante de la FES Acatlán que proporciona su número de cuenta.'
|
||||
},
|
||||
externalExample: {
|
||||
summary: 'Ejemplo de respuesta para un participante externo',
|
||||
value: {
|
||||
id_formulario: 1,
|
||||
correo: 'externo@ejemplo.com',
|
||||
respuestas: [
|
||||
{ id_pregunta: 101, valor: 'No' }, // ¿Eres parte de la comunidad de la FES Acatlán?
|
||||
{ id_pregunta: 103, valor: 'Juan Carlos' }, // Nombre(s)
|
||||
{ id_pregunta: 104, valor: 'Ramírez López' }, // Apellidos
|
||||
{ id_pregunta: 106, valor: 'Prefiero no decirlo' }, // Género
|
||||
{ id_pregunta: 107, valor: 'UAM Azcapotzalco' } // Institución de procedencia
|
||||
],
|
||||
fecha_envio: '2025-04-02T13:45:00'
|
||||
},
|
||||
description: 'Datos de un participante externo que proporciona su nombre e institución de procedencia.'
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -5,10 +5,26 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { CuestionarioRespondido } from './entities/cuestionario_respondido.entity';
|
||||
import { CuestionarioModule } from '../cuestionario/cuestionario.module';
|
||||
import { ParticipanteModule } from '../participante/participante.module';
|
||||
import { Participante } from '../participante/participante.entity';
|
||||
import { Cuestionario } from '../cuestionario/entities/cuestionario.entity';
|
||||
import { Pregunta } from '../pregunta/entities/pregunta.entity';
|
||||
import { RespuestaParticipanteAbierta } from '../respuesta_participante_abierta/entities/respuesta_participante_abierta.entity';
|
||||
import { RespuestaParticipanteCerrada } from '../respuesta_participante_cerrada/entities/respuesta_participante_cerrada.entity';
|
||||
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
|
||||
import { Opcion } from '../opcion/entities/opcion.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([CuestionarioRespondido]),
|
||||
TypeOrmModule.forFeature([
|
||||
CuestionarioRespondido,
|
||||
Participante,
|
||||
Cuestionario,
|
||||
Pregunta,
|
||||
RespuestaParticipanteAbierta,
|
||||
RespuestaParticipanteCerrada,
|
||||
PreguntaOpcion,
|
||||
Opcion
|
||||
]),
|
||||
CuestionarioModule,
|
||||
ParticipanteModule
|
||||
],
|
||||
|
||||
@@ -1,19 +1,181 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import { CreateCuestionarioRespondidoDto } from './dto/create-cuestionario_respondido.dto';
|
||||
import { UpdateCuestionarioRespondidoDto } from './dto/update-cuestionario_respondido.dto';
|
||||
import { SubmitRespuestasDto } from './dto/submit-respuestas.dto';
|
||||
import { CuestionarioRespondido } from './entities/cuestionario_respondido.entity';
|
||||
import { Participante } from '../participante/participante.entity';
|
||||
import { Cuestionario } from '../cuestionario/entities/cuestionario.entity';
|
||||
import { Pregunta } from '../pregunta/entities/pregunta.entity';
|
||||
import { RespuestaParticipanteAbierta } from '../respuesta_participante_abierta/entities/respuesta_participante_abierta.entity';
|
||||
import { RespuestaParticipanteCerrada } from '../respuesta_participante_cerrada/entities/respuesta_participante_cerrada.entity';
|
||||
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
|
||||
import { Opcion } from '../opcion/entities/opcion.entity';
|
||||
|
||||
@Injectable()
|
||||
export class CuestionarioRespondidoService {
|
||||
constructor(
|
||||
@InjectRepository(CuestionarioRespondido)
|
||||
private cuestionarioRespondidoRepository: Repository<CuestionarioRespondido>,
|
||||
@InjectRepository(Participante)
|
||||
private participanteRepository: Repository<Participante>,
|
||||
@InjectRepository(Cuestionario)
|
||||
private cuestionarioRepository: Repository<Cuestionario>,
|
||||
@InjectRepository(Pregunta)
|
||||
private preguntaRepository: Repository<Pregunta>,
|
||||
@InjectRepository(RespuestaParticipanteAbierta)
|
||||
private respuestaAbiertaRepository: Repository<RespuestaParticipanteAbierta>,
|
||||
@InjectRepository(RespuestaParticipanteCerrada)
|
||||
private respuestaCerradaRepository: Repository<RespuestaParticipanteCerrada>,
|
||||
@InjectRepository(PreguntaOpcion)
|
||||
private preguntaOpcionRepository: Repository<PreguntaOpcion>,
|
||||
@InjectRepository(Opcion)
|
||||
private opcionRepository: Repository<Opcion>,
|
||||
private dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
create(createCuestionarioRespondidoDto: CreateCuestionarioRespondidoDto) {
|
||||
return 'This action adds a new cuestionarioRespondido';
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return `This action returns all cuestionarioRespondido`;
|
||||
async submitRespuestas(submitDto: SubmitRespuestasDto) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
// 1. Verificar que el cuestionario existe
|
||||
const cuestionario = await this.cuestionarioRepository.findOne({
|
||||
where: { id_cuestionario: submitDto.id_formulario }
|
||||
});
|
||||
|
||||
if (!cuestionario) {
|
||||
throw new NotFoundException(`Cuestionario con ID ${submitDto.id_formulario} no encontrado`);
|
||||
}
|
||||
|
||||
// 2. Buscar o crear participante
|
||||
let participante = await this.participanteRepository.findOne({
|
||||
where: { correo: submitDto.correo }
|
||||
});
|
||||
|
||||
if (!participante) {
|
||||
participante = new Participante();
|
||||
participante.correo = submitDto.correo;
|
||||
participante.id_tipo_user = 1; // Usar un tipo de usuario por defecto
|
||||
participante = await queryRunner.manager.save(participante);
|
||||
}
|
||||
|
||||
// 3. Crear un nuevo registro de cuestionario respondido
|
||||
const cuestionarioRespondido = new CuestionarioRespondido();
|
||||
cuestionarioRespondido.cuestionario = cuestionario;
|
||||
cuestionarioRespondido.participante = participante;
|
||||
cuestionarioRespondido.fechaRespuesta = submitDto.fecha_envio ?
|
||||
new Date(submitDto.fecha_envio) : new Date();
|
||||
|
||||
const savedCuestionarioRespondido = await queryRunner.manager.save(cuestionarioRespondido);
|
||||
|
||||
// 4. Procesar cada respuesta
|
||||
for (const respuestaDto of submitDto.respuestas) {
|
||||
// Buscar la pregunta
|
||||
const pregunta = await this.preguntaRepository.findOne({
|
||||
where: { id_pregunta: respuestaDto.id_pregunta },
|
||||
relations: ['tipoPregunta']
|
||||
});
|
||||
|
||||
if (!pregunta) {
|
||||
throw new NotFoundException(`Pregunta con ID ${respuestaDto.id_pregunta} no encontrada`);
|
||||
}
|
||||
|
||||
// Determinar tipo de pregunta por ID
|
||||
const esPreguntaAbierta = pregunta.id_tipo_pregunta === 2;
|
||||
|
||||
// O determinar tipo de pregunta por su nombre si existe
|
||||
const esPreguntaTipoAbierta = pregunta.tipoPregunta &&
|
||||
pregunta.tipoPregunta.tipo_pregunta === 'Abierta';
|
||||
|
||||
// Determinar tipo de pregunta y guardar respuesta adecuadamente
|
||||
if (esPreguntaAbierta || esPreguntaTipoAbierta) {
|
||||
// Respuesta abierta (texto)
|
||||
const respuestaAbierta = new RespuestaParticipanteAbierta();
|
||||
respuestaAbierta.pregunta = pregunta;
|
||||
respuestaAbierta.respuesta = respuestaDto.valor;
|
||||
respuestaAbierta.cuestionarioRespondido = savedCuestionarioRespondido;
|
||||
|
||||
await queryRunner.manager.save(respuestaAbierta);
|
||||
} else {
|
||||
// Respuesta cerrada (opción)
|
||||
// Buscar la opción correspondiente
|
||||
const opciones = await this.opcionRepository.find({
|
||||
where: { opcion: respuestaDto.valor }
|
||||
});
|
||||
|
||||
if (opciones.length === 0) {
|
||||
throw new BadRequestException(`No se encontró la opción '${respuestaDto.valor}' para la pregunta ${respuestaDto.id_pregunta}`);
|
||||
}
|
||||
|
||||
// Buscar la relación entre pregunta y opción
|
||||
const preguntaOpciones = await this.preguntaOpcionRepository
|
||||
.createQueryBuilder('po')
|
||||
.innerJoinAndSelect('po.opcion', 'opcion')
|
||||
.where('po.id_pregunta = :preguntaId', { preguntaId: pregunta.id_pregunta })
|
||||
.andWhere('opcion.id_opcion = :opcionId', { opcionId: opciones[0].id_opcion })
|
||||
.getMany();
|
||||
|
||||
if (preguntaOpciones.length === 0) {
|
||||
throw new BadRequestException(`La opción '${respuestaDto.valor}' no está asociada a la pregunta ${respuestaDto.id_pregunta}`);
|
||||
}
|
||||
|
||||
// Guardar respuesta cerrada
|
||||
const respuestaCerrada = new RespuestaParticipanteCerrada();
|
||||
respuestaCerrada.preguntaOpcion = preguntaOpciones[0];
|
||||
respuestaCerrada.cuestionarioRespondido = savedCuestionarioRespondido;
|
||||
|
||||
await queryRunner.manager.save(respuestaCerrada);
|
||||
}
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Respuestas guardadas correctamente',
|
||||
cuestionarioRespondidoId: savedCuestionarioRespondido.idCuestionarioRespondido
|
||||
};
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return `This action returns a #${id} cuestionarioRespondido`;
|
||||
findAll() {
|
||||
return this.cuestionarioRespondidoRepository.find({
|
||||
relations: ['participante', 'cuestionario', 'respuestasAbiertas', 'respuestasCerradas']
|
||||
});
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const cuestionarioRespondido = await this.cuestionarioRespondidoRepository.findOne({
|
||||
where: { idCuestionarioRespondido: id },
|
||||
relations: [
|
||||
'participante',
|
||||
'cuestionario',
|
||||
'respuestasAbiertas',
|
||||
'respuestasAbiertas.pregunta',
|
||||
'respuestasCerradas',
|
||||
'respuestasCerradas.preguntaOpcion',
|
||||
'respuestasCerradas.preguntaOpcion.opcion',
|
||||
'respuestasCerradas.preguntaOpcion.pregunta'
|
||||
]
|
||||
});
|
||||
|
||||
if (!cuestionarioRespondido) {
|
||||
throw new NotFoundException(`Cuestionario respondido con ID ${id} no encontrado`);
|
||||
}
|
||||
|
||||
return cuestionarioRespondido;
|
||||
}
|
||||
|
||||
update(id: number, updateCuestionarioRespondidoDto: UpdateCuestionarioRespondidoDto) {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsArray, IsDateString, IsNotEmpty, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class RespuestaDto {
|
||||
@ApiProperty({
|
||||
description: 'ID de la pregunta respondida',
|
||||
example: 101
|
||||
})
|
||||
@IsNumber()
|
||||
id_pregunta: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Valor de la respuesta',
|
||||
example: 'Si'
|
||||
})
|
||||
@IsString()
|
||||
valor: string;
|
||||
}
|
||||
|
||||
export class SubmitRespuestasDto {
|
||||
@ApiProperty({
|
||||
description: 'ID del formulario a responder',
|
||||
example: 1
|
||||
})
|
||||
@IsNumber()
|
||||
id_formulario: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Correo electrónico del participante',
|
||||
example: 'usuario@ejemplo.com'
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
correo: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Array de respuestas a las preguntas',
|
||||
type: [RespuestaDto],
|
||||
examples: [
|
||||
{
|
||||
id_pregunta: 101,
|
||||
valor: 'Si'
|
||||
},
|
||||
{
|
||||
id_pregunta: 102,
|
||||
valor: '123456789'
|
||||
}
|
||||
]
|
||||
})
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => RespuestaDto)
|
||||
respuestas: RespuestaDto[];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Fecha y hora del envío',
|
||||
example: '2025-04-02T13:45:00'
|
||||
})
|
||||
@IsDateString()
|
||||
@IsOptional()
|
||||
fecha_envio?: string;
|
||||
}
|
||||
@@ -1,33 +1,93 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator';
|
||||
import { IsArray, IsBoolean, IsEnum, IsNotEmpty, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export enum TipoPreguntaEnum {
|
||||
CERRADA = 'Cerrada',
|
||||
ABIERTA = 'Abierta',
|
||||
MULTIPLE = 'Multiple'
|
||||
}
|
||||
|
||||
export class OpcionDto {
|
||||
@ApiProperty({
|
||||
description: 'Valor de la opción',
|
||||
example: 'Sí'
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
valor: string;
|
||||
}
|
||||
|
||||
export class CreatePreguntaDto {
|
||||
@ApiProperty({
|
||||
description: 'Título o texto de la pregunta',
|
||||
example: '¿Eres parte de la comunidad de la FES Acatlán?'
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
titulo: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Indica si la pregunta es obligatoria',
|
||||
example: true,
|
||||
default: false
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
obligatoria?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Tipo de pregunta',
|
||||
enum: Object.values(TipoPreguntaEnum),
|
||||
enumName: 'TipoPreguntaEnum',
|
||||
example: TipoPreguntaEnum.CERRADA,
|
||||
examples: {
|
||||
'cerrada': {
|
||||
summary: 'Pregunta de opción única',
|
||||
value: TipoPreguntaEnum.CERRADA
|
||||
},
|
||||
'abierta': {
|
||||
summary: 'Pregunta de texto libre',
|
||||
value: TipoPreguntaEnum.ABIERTA
|
||||
},
|
||||
'multiple': {
|
||||
summary: 'Pregunta de selección múltiple',
|
||||
value: TipoPreguntaEnum.MULTIPLE
|
||||
}
|
||||
}
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@IsEnum(TipoPreguntaEnum, {
|
||||
message: 'El tipo debe ser uno de los siguientes valores: Cerrada, Abierta, Multiple'
|
||||
})
|
||||
tipo: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Contador de opciones (se calcula automáticamente)',
|
||||
required: false
|
||||
})
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
contador_opcion?: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'ID de la opción de la que depende esta pregunta (para preguntas condicionadas)',
|
||||
required: false,
|
||||
example: null
|
||||
})
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
id_opcion_dependiente?: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Lista de opciones para preguntas de tipo Cerrada o Multiple',
|
||||
type: [OpcionDto],
|
||||
required: false,
|
||||
examples: [
|
||||
{ valor: 'Sí' },
|
||||
{ valor: 'No' }
|
||||
]
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { applyDecorators } from '@nestjs/common';
|
||||
import { TipoPreguntaEnum } from './dto/create-pregunta.dto';
|
||||
|
||||
export class PreguntaApiDocumentation {
|
||||
// Decoradores para toda la clase del controlador
|
||||
@@ -19,7 +20,12 @@ export class PreguntaApiDocumentation {
|
||||
properties: {
|
||||
titulo: { type: 'string', example: '¿Eres parte de la comunidad?' },
|
||||
obligatoria: { type: 'boolean', example: true },
|
||||
tipo: { type: 'string', example: 'Multiple' },
|
||||
tipo: {
|
||||
type: 'string',
|
||||
enum: Object.values(TipoPreguntaEnum),
|
||||
description: 'Tipo de pregunta: Cerrada (opción única), Abierta (texto libre), Multiple (varias opciones)',
|
||||
example: TipoPreguntaEnum.CERRADA
|
||||
},
|
||||
contador_opcion: { type: 'number', example: 0 },
|
||||
id_opcion_dependiente: { type: 'number', example: null },
|
||||
opciones: {
|
||||
@@ -32,6 +38,41 @@ export class PreguntaApiDocumentation {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
examples: {
|
||||
cerrada: {
|
||||
summary: 'Pregunta de opción única',
|
||||
value: {
|
||||
titulo: '¿Eres parte de la comunidad de la FES Acatlán?',
|
||||
obligatoria: true,
|
||||
tipo: TipoPreguntaEnum.CERRADA,
|
||||
opciones: [
|
||||
{ valor: 'Sí' },
|
||||
{ valor: 'No' }
|
||||
]
|
||||
}
|
||||
},
|
||||
abierta: {
|
||||
summary: 'Pregunta de texto libre',
|
||||
value: {
|
||||
titulo: '¿Qué mejorarías en nuestro servicio?',
|
||||
obligatoria: false,
|
||||
tipo: TipoPreguntaEnum.ABIERTA
|
||||
}
|
||||
},
|
||||
multiple: {
|
||||
summary: 'Pregunta de selección múltiple',
|
||||
value: {
|
||||
titulo: '¿Qué aspectos del servicio fueron de tu agrado?',
|
||||
obligatoria: false,
|
||||
tipo: TipoPreguntaEnum.MULTIPLE,
|
||||
opciones: [
|
||||
{ valor: 'Rapidez' },
|
||||
{ valor: 'Atención al cliente' },
|
||||
{ valor: 'Facilidad de uso' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({
|
||||
|
||||
@@ -8,6 +8,6 @@ import { TipoPregunta } from './entities/tipo_pregunta.entity';
|
||||
imports: [TypeOrmModule.forFeature([TipoPregunta])],
|
||||
controllers: [TipoPreguntaController],
|
||||
providers: [TipoPreguntaService],
|
||||
exports: [TypeOrmModule]
|
||||
exports: [TipoPreguntaService, TypeOrmModule]
|
||||
})
|
||||
export class TipoPreguntaModule {}
|
||||
|
||||
@@ -1,6 +1,59 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { CreateTipoPreguntaDto } from './dto/create-tipo_pregunta.dto';
|
||||
import { UpdateTipoPreguntaDto } from './dto/update-tipo_pregunta.dto';
|
||||
import { TipoPregunta } from './entities/tipo_pregunta.entity';
|
||||
|
||||
@Injectable()
|
||||
export class TipoPreguntaService {
|
||||
// Métodos del servicio
|
||||
export class TipoPreguntaService implements OnModuleInit {
|
||||
constructor(
|
||||
@InjectRepository(TipoPregunta)
|
||||
private tipoPreguntaRepository: Repository<TipoPregunta>,
|
||||
) {}
|
||||
|
||||
async onModuleInit() {
|
||||
await this.seedTipoPreguntas();
|
||||
}
|
||||
|
||||
private async seedTipoPreguntas() {
|
||||
const defaultTipos = [
|
||||
{ tipo_pregunta: 'Cerrada' },
|
||||
{ tipo_pregunta: 'Abierta' },
|
||||
{ tipo_pregunta: 'Multiple' },
|
||||
{ tipo_pregunta: 'Texto' }
|
||||
];
|
||||
|
||||
for (const tipo of defaultTipos) {
|
||||
const existingTipo = await this.tipoPreguntaRepository.findOne({
|
||||
where: { tipo_pregunta: tipo.tipo_pregunta },
|
||||
});
|
||||
|
||||
if (!existingTipo) {
|
||||
await this.tipoPreguntaRepository.save(tipo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
create(createTipoPreguntaDto: CreateTipoPreguntaDto) {
|
||||
return this.tipoPreguntaRepository.save(createTipoPreguntaDto);
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return this.tipoPreguntaRepository.find();
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return this.tipoPreguntaRepository.findOne({
|
||||
where: { id_tipo: id }
|
||||
});
|
||||
}
|
||||
|
||||
update(id: number, updateTipoPreguntaDto: UpdateTipoPreguntaDto) {
|
||||
return this.tipoPreguntaRepository.update(id, updateTipoPreguntaDto);
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return this.tipoPreguntaRepository.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,29 +13,29 @@ export const feriaSexualidad = {
|
||||
preguntas: [
|
||||
{
|
||||
titulo: '¿Eres parte de la comunidad de la FES Acatlán?',
|
||||
tipo: 'Multiple',
|
||||
tipo: 'Cerrada',
|
||||
opciones: [{ valor: 'Si' }, { valor: 'No' }],
|
||||
obligatoria: true,
|
||||
},
|
||||
{
|
||||
titulo: 'Numero de cuenta',
|
||||
obligatoria: true,
|
||||
tipo: 'Numero',
|
||||
tipo: 'Abierta',
|
||||
},
|
||||
{
|
||||
titulo: 'Nombre(s)',
|
||||
obligatoria: true,
|
||||
tipo: 'Texto',
|
||||
tipo: 'Abierta',
|
||||
},
|
||||
{
|
||||
titulo: 'Apellidos',
|
||||
obligatoria: true,
|
||||
tipo: 'Texto',
|
||||
tipo: 'Abierta',
|
||||
},
|
||||
{
|
||||
titulo: 'genero',
|
||||
titulo: 'Género',
|
||||
obligatoria: true,
|
||||
tipo: 'Radio',
|
||||
tipo: 'Cerrada',
|
||||
opciones: [
|
||||
{ valor: 'Masculino' },
|
||||
{ valor: 'Femenino' },
|
||||
@@ -44,7 +44,7 @@ export const feriaSexualidad = {
|
||||
},
|
||||
{
|
||||
titulo: 'Institución de procedencia',
|
||||
tipo: 'Abierto',
|
||||
tipo: 'Abierta',
|
||||
obligatoria: true,
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user