eithan #7
+18
@@ -0,0 +1,18 @@
|
||||
# Etapa de desarrollo para ejecutar en watch mode
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
# Asegurarse de compilar el proyecto
|
||||
RUN npm run build
|
||||
|
||||
# Puerto expuesto
|
||||
EXPOSE 4200
|
||||
|
||||
# Comando para iniciar en modo desarrollo
|
||||
CMD ["npm", "run", "start:dev"]
|
||||
@@ -0,0 +1,33 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb:latest
|
||||
container_name: formularios_db
|
||||
ports:
|
||||
- "4201:3306"
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: mypass
|
||||
MARIADB_DATABASE: formularios_test
|
||||
volumes:
|
||||
- mariadb_data:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: formularios_api
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- .:/app
|
||||
- /app/node_modules
|
||||
ports:
|
||||
- "4200:4200"
|
||||
depends_on:
|
||||
- mariadb
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
mariadb_data:
|
||||
Generated
+45
-8
@@ -16,6 +16,8 @@
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/swagger": "^11.1.0",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"axios": "^1.8.4",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"mysql2": "^3.14.0",
|
||||
@@ -4191,7 +4193,6 @@
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aws-ssl-profiles": {
|
||||
@@ -4203,6 +4204,17 @@
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.8.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/b4a": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
|
||||
@@ -4803,6 +4815,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/class-transformer": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
||||
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/class-validator": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz",
|
||||
@@ -4968,7 +4986,6 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
@@ -5299,7 +5316,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
@@ -5543,7 +5559,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
@@ -6321,6 +6336,26 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||
@@ -6434,7 +6469,6 @@
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
@@ -6460,7 +6494,6 @@
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -6470,7 +6503,6 @@
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
@@ -6824,7 +6856,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
@@ -9262,6 +9293,12 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/swagger": "^11.1.0",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"axios": "^1.8.4",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"mysql2": "^3.14.0",
|
||||
|
||||
+9
-4
@@ -29,7 +29,7 @@ import { ParticipanteEventoModule } from './participante_evento/participante_eve
|
||||
imports: [
|
||||
ConfigModule.forRoot(),
|
||||
TypeOrmModule.forRoot({
|
||||
/*
|
||||
|
||||
type: 'mysql',
|
||||
host: process.env.db_host, //process.env.db_host
|
||||
username: process.env.db_username,
|
||||
@@ -40,16 +40,21 @@ import { ParticipanteEventoModule } from './participante_evento/participante_eve
|
||||
dropSchema: true, // elimina la base de datos
|
||||
// logging: true, // Habilita los logs para depuración
|
||||
autoLoadEntities: true, // Carga automáticamente las entidades
|
||||
*/
|
||||
|
||||
//esta es mi base de datos local
|
||||
|
||||
/*
|
||||
type: 'mysql',
|
||||
host: 'localhost',
|
||||
port: 3306, //3306
|
||||
username: 'root',
|
||||
password: 'admin', //admin
|
||||
database: 'cidwa', //nestdb
|
||||
entities: [__dirname + '/**/*.entity{.ts,.js}'],
|
||||
synchronize: true,
|
||||
*/
|
||||
// entities: [__dirname + '/**/*.entity{.ts,.js}'],
|
||||
// synchronize: true,
|
||||
|
||||
|
||||
//extra
|
||||
retryAttempts: 10, // Intentos para reconectar
|
||||
retryDelay: 3000, // Tiempo entre reintentos
|
||||
|
||||
@@ -2,32 +2,46 @@ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/commo
|
||||
import { CuestionarioService } from './cuestionario.service';
|
||||
import { CreateCuestionarioDto } from './dto/create-cuestionario.dto';
|
||||
import { UpdateCuestionarioDto } from './dto/update-cuestionario.dto';
|
||||
import { CuestionarioApiDocumentation } from './cuestionario.documentation';
|
||||
import { ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';
|
||||
|
||||
@Controller('cuestionario')
|
||||
@CuestionarioApiDocumentation.ApiController
|
||||
export class CuestionarioController {
|
||||
constructor(private readonly cuestionarioService: CuestionarioService) {}
|
||||
|
||||
@Post()
|
||||
@CuestionarioApiDocumentation.ApiCreate
|
||||
create(@Body() createCuestionarioDto: CreateCuestionarioDto) {
|
||||
return this.cuestionarioService.create(createCuestionarioDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@CuestionarioApiDocumentation.ApiGetAll
|
||||
findAll() {
|
||||
return this.cuestionarioService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@CuestionarioApiDocumentation.ApiGetOne
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.cuestionarioService.findOne(+id);
|
||||
}
|
||||
|
||||
@Get(':id/formulario')
|
||||
@CuestionarioApiDocumentation.ApiGetFormulario
|
||||
findFormulario(@Param('id') id: string) {
|
||||
return this.cuestionarioService.findCompleto(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@CuestionarioApiDocumentation.ApiUpdate
|
||||
update(@Param('id') id: string, @Body() updateCuestionarioDto: UpdateCuestionarioDto) {
|
||||
return this.cuestionarioService.update(+id, updateCuestionarioDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@CuestionarioApiDocumentation.ApiRemove
|
||||
remove(@Param('id') id: string) {
|
||||
return this.cuestionarioService.remove(+id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
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';
|
||||
|
||||
export class CuestionarioApiDocumentation {
|
||||
// Decoradores para toda la clase del controlador
|
||||
static ApiController = ApiTags('Cuestionarios');
|
||||
|
||||
// Documentación para obtener el formulario completo
|
||||
static ApiGetFormulario = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Obtener formulario completo',
|
||||
description: 'Devuelve el cuestionario completo con la misma estructura que se usa para crearlo'
|
||||
}),
|
||||
ApiParam({
|
||||
name: 'id',
|
||||
description: 'ID del cuestionario',
|
||||
required: true,
|
||||
type: 'number',
|
||||
example: 1
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Formulario completo con todas sus secciones, preguntas y opciones',
|
||||
type: FormularioDto,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: getSchemaPath(FormularioDto) },
|
||||
example: feriaSexualidad
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 404, description: 'Cuestionario no encontrado' })
|
||||
);
|
||||
|
||||
// Documentación para crear un cuestionario
|
||||
static ApiCreate = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Crear un nuevo cuestionario',
|
||||
description: 'Crea un nuevo cuestionario con secciones, preguntas y opciones'
|
||||
}),
|
||||
ApiBody({
|
||||
description: 'Datos del cuestionario a crear',
|
||||
type: FormularioDto,
|
||||
examples: {
|
||||
feriaSexualidad: {
|
||||
summary: 'Ejemplo de cuestionario para feria de sexualidad',
|
||||
description: 'Cuestionario con secciones, preguntas y opciones para feria de sexualidad',
|
||||
value: feriaSexualidad
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 201,
|
||||
description: 'Cuestionario creado correctamente',
|
||||
schema: {
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
cuestionario: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_cuestionario: { type: 'number', example: 1 },
|
||||
nombre_form: { type: 'string', example: 'Registro para la Feria de la Sexualidad - FES Acatlán' },
|
||||
descripcion: { type: 'string' },
|
||||
contador_secciones: { type: 'number', example: 1 },
|
||||
editable: { type: 'boolean', example: true },
|
||||
fecha_fin: { type: 'string', format: 'date-time' },
|
||||
fecha_inicio: { type: 'string', format: 'date-time' },
|
||||
id_tipo_cuestionario: { type: 'number', example: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 400, description: 'Datos del cuestionario inválidos' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para obtener todos los cuestionarios
|
||||
static ApiGetAll = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Obtener todos los cuestionarios',
|
||||
description: 'Retorna una lista de todos los cuestionarios registrados'
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Lista de cuestionarios obtenida correctamente',
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_cuestionario: { type: 'number', example: 1 },
|
||||
nombre_form: { type: 'string', example: 'Registro para la Feria de la Sexualidad - FES Acatlán' },
|
||||
descripcion: { type: 'string' },
|
||||
contador_secciones: { type: 'number', example: 1 },
|
||||
editable: { type: 'boolean', example: true },
|
||||
fecha_fin: { type: 'string', format: 'date-time' },
|
||||
fecha_inicio: { type: 'string', format: 'date-time' },
|
||||
id_tipo_cuestionario: { type: 'number', example: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para obtener un cuestionario por ID
|
||||
static ApiGetOne = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Obtener un cuestionario por ID',
|
||||
description: 'Retorna un cuestionario específico por su ID'
|
||||
}),
|
||||
ApiParam({
|
||||
name: 'id',
|
||||
description: 'ID del cuestionario',
|
||||
required: true,
|
||||
type: 'number',
|
||||
example: 1
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Cuestionario obtenido correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_cuestionario: { type: 'number', example: 1 },
|
||||
nombre_form: { type: 'string', example: 'Registro para la Feria de la Sexualidad - FES Acatlán' },
|
||||
descripcion: { type: 'string' },
|
||||
contador_secciones: { type: 'number', example: 1 },
|
||||
editable: { type: 'boolean', example: true },
|
||||
fecha_fin: { type: 'string', format: 'date-time' },
|
||||
fecha_inicio: { type: 'string', format: 'date-time' },
|
||||
id_tipo_cuestionario: { type: 'number', example: 1 }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 404, description: 'Cuestionario no encontrado' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para actualizar un cuestionario
|
||||
static ApiUpdate = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Actualizar un cuestionario',
|
||||
description: 'Actualiza los datos de un cuestionario existente'
|
||||
}),
|
||||
ApiParam({
|
||||
name: 'id',
|
||||
description: 'ID del cuestionario a actualizar',
|
||||
required: true,
|
||||
type: 'number',
|
||||
example: 1
|
||||
}),
|
||||
ApiBody({
|
||||
description: 'Datos a actualizar del cuestionario',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
nombre_form: { type: 'string', example: 'Nombre actualizado del formulario' },
|
||||
descripcion: { type: 'string', example: 'Descripción actualizada' },
|
||||
fecha_inicio: { type: 'string', format: 'date-time' },
|
||||
fecha_fin: { type: 'string', format: 'date-time' },
|
||||
editable: { type: 'boolean', example: true }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Cuestionario actualizado correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
affected: { type: 'number', example: 1 }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 400, description: 'Datos de actualización inválidos' }),
|
||||
ApiResponse({ status: 404, description: 'Cuestionario no encontrado' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para eliminar un cuestionario
|
||||
static ApiRemove = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Eliminar un cuestionario',
|
||||
description: 'Elimina permanentemente un cuestionario por su ID'
|
||||
}),
|
||||
ApiParam({
|
||||
name: 'id',
|
||||
description: 'ID del cuestionario a eliminar',
|
||||
required: true,
|
||||
type: 'number',
|
||||
example: 1
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Cuestionario eliminado correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
affected: { type: 'number', example: 1 }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 404, description: 'Cuestionario no encontrado' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,33 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CuestionarioService } from './cuestionario.service';
|
||||
import { CuestionarioController } from './cuestionario.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Cuestionario } from './entities/cuestionario.entity';
|
||||
import { Seccion } from '../seccion/entities/seccion.entity';
|
||||
import { CuestionarioSeccion } from '../cuestionario_seccion/entities/cuestionario_seccion.entity';
|
||||
import { Pregunta } from '../pregunta/entities/pregunta.entity';
|
||||
import { SeccionPregunta } from '../seccion_pregunta/entities/seccion_pregunta.entity';
|
||||
import { Opcion } from '../opcion/entities/opcion.entity';
|
||||
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
|
||||
import { TipoPregunta } from '../tipo_pregunta/entities/tipo_pregunta.entity';
|
||||
import { TipoCuestionario } from '../tipo_cuestionario/entities/tipo_cuestionario.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
Cuestionario,
|
||||
Seccion,
|
||||
CuestionarioSeccion,
|
||||
Pregunta,
|
||||
SeccionPregunta,
|
||||
Opcion,
|
||||
PreguntaOpcion,
|
||||
TipoPregunta,
|
||||
TipoCuestionario
|
||||
])
|
||||
],
|
||||
controllers: [CuestionarioController],
|
||||
providers: [CuestionarioService],
|
||||
exports: [TypeOrmModule, CuestionarioService]
|
||||
})
|
||||
export class CuestionarioModule {}
|
||||
|
||||
@@ -1,26 +1,311 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource, In } from 'typeorm';
|
||||
import { CreateCuestionarioDto } from './dto/create-cuestionario.dto';
|
||||
import { UpdateCuestionarioDto } from './dto/update-cuestionario.dto';
|
||||
import { Cuestionario } from './entities/cuestionario.entity';
|
||||
import { Seccion } from '../seccion/entities/seccion.entity';
|
||||
import { CuestionarioSeccion } from '../cuestionario_seccion/entities/cuestionario_seccion.entity';
|
||||
import { Pregunta } from '../pregunta/entities/pregunta.entity';
|
||||
import { SeccionPregunta } from '../seccion_pregunta/entities/seccion_pregunta.entity';
|
||||
import { Opcion } from '../opcion/entities/opcion.entity';
|
||||
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
|
||||
import { TipoPregunta } from '../tipo_pregunta/entities/tipo_pregunta.entity';
|
||||
import { TipoCuestionario } from '../tipo_cuestionario/entities/tipo_cuestionario.entity';
|
||||
|
||||
@Injectable()
|
||||
export class CuestionarioService {
|
||||
create(createCuestionarioDto: CreateCuestionarioDto) {
|
||||
return 'This action adds a new cuestionario';
|
||||
constructor(
|
||||
@InjectRepository(Cuestionario)
|
||||
private cuestionarioRepository: Repository<Cuestionario>,
|
||||
@InjectRepository(Seccion)
|
||||
private seccionRepository: Repository<Seccion>,
|
||||
@InjectRepository(CuestionarioSeccion)
|
||||
private cuestionarioSeccionRepository: Repository<CuestionarioSeccion>,
|
||||
@InjectRepository(Pregunta)
|
||||
private preguntaRepository: Repository<Pregunta>,
|
||||
@InjectRepository(SeccionPregunta)
|
||||
private seccionPreguntaRepository: Repository<SeccionPregunta>,
|
||||
@InjectRepository(Opcion)
|
||||
private opcionRepository: Repository<Opcion>,
|
||||
@InjectRepository(PreguntaOpcion)
|
||||
private preguntaOpcionRepository: Repository<PreguntaOpcion>,
|
||||
@InjectRepository(TipoPregunta)
|
||||
private tipoPreguntaRepository: Repository<TipoPregunta>,
|
||||
private dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
async create(createCuestionarioDto: CreateCuestionarioDto) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
// 1. Crear cuestionario
|
||||
const cuestionario = new Cuestionario();
|
||||
cuestionario.nombre_form = createCuestionarioDto.nombre_form;
|
||||
cuestionario.descripcion = createCuestionarioDto.descripcion;
|
||||
cuestionario.fecha_inicio = createCuestionarioDto.fecha_inicio ? new Date(createCuestionarioDto.fecha_inicio) : undefined;
|
||||
cuestionario.fecha_fin = createCuestionarioDto.fecha_fin ? new Date(createCuestionarioDto.fecha_fin) : undefined;
|
||||
cuestionario.id_tipo_cuestionario = createCuestionarioDto.id_tipo_cuestionario;
|
||||
cuestionario.contador_secciones = createCuestionarioDto.secciones?.length || 0;
|
||||
cuestionario.editable = true;
|
||||
|
||||
const savedCuestionario = await queryRunner.manager.save(cuestionario);
|
||||
|
||||
// 2. Crear secciones y vincularlas al cuestionario
|
||||
if (createCuestionarioDto.secciones && createCuestionarioDto.secciones.length > 0) {
|
||||
for (let i = 0; i < createCuestionarioDto.secciones.length; i++) {
|
||||
const seccionDto = createCuestionarioDto.secciones[i];
|
||||
|
||||
// Crear sección
|
||||
const seccion = new Seccion();
|
||||
seccion.titulo = seccionDto.titulo;
|
||||
seccion.descripcion = seccionDto.descripcion;
|
||||
seccion.contador_pregunta = seccionDto.preguntas?.length || 0;
|
||||
|
||||
const savedSeccion = await queryRunner.manager.save(seccion);
|
||||
|
||||
// Vincular sección con cuestionario
|
||||
const cuestionarioSeccion = new CuestionarioSeccion();
|
||||
cuestionarioSeccion.id_cuestionario = savedCuestionario.id_cuestionario;
|
||||
cuestionarioSeccion.id_seccion = savedSeccion.id_seccion;
|
||||
cuestionarioSeccion.posicion = i + 1;
|
||||
|
||||
await queryRunner.manager.save(cuestionarioSeccion);
|
||||
|
||||
// 3. Crear preguntas y vincularlas a la sección
|
||||
if (seccionDto.preguntas && seccionDto.preguntas.length > 0) {
|
||||
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 },
|
||||
});
|
||||
|
||||
if (!tipoPregunta) {
|
||||
throw new Error(`Tipo de pregunta '${preguntaDto.tipo}' no encontrado`);
|
||||
}
|
||||
|
||||
// Crear pregunta
|
||||
const pregunta = new Pregunta();
|
||||
pregunta.pregunta = preguntaDto.titulo;
|
||||
pregunta.obligatoria = preguntaDto.obligatoria || false;
|
||||
pregunta.id_tipo_pregunta = tipoPregunta.id_tipo;
|
||||
pregunta.id_opcion_dependiente = preguntaDto.id_opcion_dependiente || undefined;
|
||||
pregunta.contador_opcion = preguntaDto.opciones?.length || 0;
|
||||
|
||||
const savedPregunta = await queryRunner.manager.save(pregunta);
|
||||
|
||||
// Vincular pregunta con sección
|
||||
const seccionPregunta = new SeccionPregunta();
|
||||
seccionPregunta.id_seccion = savedSeccion.id_seccion;
|
||||
seccionPregunta.id_pregunta = savedPregunta.id_pregunta;
|
||||
seccionPregunta.posicion = j + 1;
|
||||
|
||||
await queryRunner.manager.save(seccionPregunta);
|
||||
|
||||
// 4. Crear opciones para preguntas de tipo multiple/radio
|
||||
if (preguntaDto.opciones && preguntaDto.opciones.length > 0) {
|
||||
for (let k = 0; k < preguntaDto.opciones.length; k++) {
|
||||
const opcionDto = preguntaDto.opciones[k];
|
||||
|
||||
// Crear opción
|
||||
const opcion = new Opcion();
|
||||
opcion.opcion = opcionDto.valor;
|
||||
|
||||
const savedOpcion = await queryRunner.manager.save(opcion);
|
||||
|
||||
// Vincular opción con pregunta
|
||||
const preguntaOpcion = new PreguntaOpcion();
|
||||
preguntaOpcion.id_pregunta = savedPregunta.id_pregunta;
|
||||
// Utilizamos la opción como objeto, no como ID
|
||||
preguntaOpcion.opcion = savedOpcion;
|
||||
preguntaOpcion.posicion = k + 1;
|
||||
|
||||
await queryRunner.manager.save(preguntaOpcion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
cuestionario: savedCuestionario,
|
||||
};
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return `This action returns all cuestionario`;
|
||||
return this.cuestionarioRepository.find();
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return `This action returns a #${id} cuestionario`;
|
||||
async findOne(id: number) {
|
||||
const cuestionario = await this.cuestionarioRepository.findOne({
|
||||
where: { id_cuestionario: id }
|
||||
});
|
||||
|
||||
if (!cuestionario) {
|
||||
throw new NotFoundException(`Cuestionario con ID ${id} no encontrado`);
|
||||
}
|
||||
|
||||
return cuestionario;
|
||||
}
|
||||
|
||||
async findCompleto(id: number) {
|
||||
// Obtener el cuestionario básico
|
||||
const cuestionario = await this.cuestionarioRepository.findOne({
|
||||
where: { id_cuestionario: id }
|
||||
});
|
||||
|
||||
if (!cuestionario) {
|
||||
throw new NotFoundException(`Cuestionario con ID ${id} no encontrado`);
|
||||
}
|
||||
|
||||
// Para fines de demostración, usar valores hardcodeados
|
||||
// para el tipo de cuestionario según get_formulario_feria.ts
|
||||
const tipoCuestionario = {
|
||||
id_tipo_cuestionario: 1,
|
||||
tipo_cuestionario: 'Encuesta'
|
||||
};
|
||||
|
||||
// Obtener las relaciones cuestionario-seccion
|
||||
const cuestionarioSecciones = await this.cuestionarioSeccionRepository.find({
|
||||
where: { id_cuestionario: id },
|
||||
order: { posicion: 'ASC' }
|
||||
});
|
||||
|
||||
// Obtener todas las secciones
|
||||
const seccionIds = cuestionarioSecciones.map(cs => cs.id_seccion);
|
||||
const secciones = await this.seccionRepository.find({
|
||||
where: { id_seccion: In(seccionIds) }
|
||||
});
|
||||
|
||||
const seccionesFormateadas = await Promise.all(
|
||||
cuestionarioSecciones.map(async (cs) => {
|
||||
const seccion = secciones.find(s => s.id_seccion === cs.id_seccion);
|
||||
|
||||
if (!seccion) return null;
|
||||
|
||||
// Obtener relaciones sección-pregunta
|
||||
const seccionPreguntas = await this.seccionPreguntaRepository.find({
|
||||
where: { id_seccion: seccion.id_seccion },
|
||||
order: { posicion: 'ASC' }
|
||||
});
|
||||
|
||||
// Obtener preguntas
|
||||
const preguntaIds = seccionPreguntas.map(sp => sp.id_pregunta);
|
||||
const preguntas = await this.preguntaRepository.find({
|
||||
where: { id_pregunta: In(preguntaIds) }
|
||||
});
|
||||
|
||||
// Formatear cada pregunta según el formato requerido
|
||||
const preguntasFormateadas = await Promise.all(
|
||||
seccionPreguntas.map(async (sp) => {
|
||||
const pregunta = preguntas.find(p => p.id_pregunta === sp.id_pregunta);
|
||||
|
||||
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] ||
|
||||
{ id_tipo: pregunta.id_tipo_pregunta, tipo_pregunta: 'Desconocido' };
|
||||
|
||||
// Obtener opciones para preguntas de tipo multiple/radio
|
||||
const preguntaOpciones = await this.preguntaOpcionRepository.find({
|
||||
where: { id_pregunta: pregunta.id_pregunta },
|
||||
relations: ['opcion'],
|
||||
order: { posicion: 'ASC' }
|
||||
});
|
||||
|
||||
// Formatear las opciones según el formato requerido
|
||||
const opcionesFormateadas = preguntaOpciones.map(po => ({
|
||||
id_pregunta_opcion: po.id_pregunta_opcion,
|
||||
posicion: po.posicion,
|
||||
id_opcion: po.opcion.id_opcion,
|
||||
opcion: {
|
||||
id_opcion: po.opcion.id_opcion,
|
||||
opcion: po.opcion.opcion
|
||||
}
|
||||
}));
|
||||
|
||||
// Construir objeto de pregunta según el formato requerido
|
||||
return {
|
||||
id_seccion_pregunta: sp.id_seccion_pregunta,
|
||||
posicion: sp.posicion,
|
||||
pregunta: {
|
||||
id_pregunta: pregunta.id_pregunta,
|
||||
pregunta: pregunta.pregunta,
|
||||
contador_opcion: pregunta.contador_opcion,
|
||||
obligatoria: pregunta.obligatoria,
|
||||
id_tipo_pregunta: pregunta.id_tipo_pregunta,
|
||||
id_opcion_dependiente: pregunta.id_opcion_dependiente,
|
||||
tipo_pregunta: {
|
||||
id_tipo: tipoPregunta.id_tipo,
|
||||
tipo_pregunta: tipoPregunta.tipo_pregunta
|
||||
},
|
||||
opciones: opcionesFormateadas
|
||||
}
|
||||
};
|
||||
})
|
||||
).then(results => results.filter(p => p !== null));
|
||||
|
||||
// Construir objeto de sección según el formato requerido
|
||||
return {
|
||||
id_cuestionario_seccion: cs.id_cuestionario_seccion,
|
||||
posicion: cs.posicion,
|
||||
seccion: {
|
||||
id_seccion: seccion.id_seccion,
|
||||
contador_pregunta: seccion.contador_pregunta,
|
||||
descripcion: seccion.descripcion,
|
||||
titulo: seccion.titulo
|
||||
},
|
||||
preguntas: preguntasFormateadas
|
||||
};
|
||||
})
|
||||
).then(results => results.filter(s => s !== null));
|
||||
|
||||
// Construir objeto final respetando el formato de get_formulario_feria.ts
|
||||
return {
|
||||
tipo_cuestionario: {
|
||||
id_tipo_cuestionario: tipoCuestionario.id_tipo_cuestionario,
|
||||
tipo_cuestionario: tipoCuestionario.tipo_cuestionario
|
||||
},
|
||||
cuestionario: {
|
||||
id_cuestionario: cuestionario.id_cuestionario,
|
||||
nombre_form: cuestionario.nombre_form,
|
||||
contador_secciones: cuestionario.contador_secciones,
|
||||
descripcion: cuestionario.descripcion,
|
||||
editable: cuestionario.editable,
|
||||
fecha_inicio: cuestionario.fecha_inicio ? cuestionario.fecha_inicio.toISOString() : null,
|
||||
fecha_fin: cuestionario.fecha_fin ? cuestionario.fecha_fin.toISOString() : null,
|
||||
id_cuestionario_original: cuestionario.id_cuestionario_original,
|
||||
id_tipo_cuestionario: cuestionario.id_tipo_cuestionario,
|
||||
secciones: seccionesFormateadas
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
update(id: number, updateCuestionarioDto: UpdateCuestionarioDto) {
|
||||
return `This action updates a #${id} cuestionario`;
|
||||
return this.cuestionarioRepository.update(id, updateCuestionarioDto);
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} cuestionario`;
|
||||
return this.cuestionarioRepository.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,31 @@
|
||||
export class CreateCuestionarioDto {}
|
||||
import { CreateSeccionDto } from '../../seccion/dto/create-seccion.dto';
|
||||
import { IsArray, IsDateString, IsNotEmpty, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class CreateCuestionarioDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
nombre_form: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
descripcion?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
fecha_inicio?: Date;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
fecha_fin?: Date;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
id_tipo_cuestionario: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateSeccionDto)
|
||||
secciones?: CreateSeccionDto[];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
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[];
|
||||
}
|
||||
|
||||
export class FormularioDto {
|
||||
@ApiProperty({
|
||||
description: 'Nombre del formulario',
|
||||
example: 'Registro para la Feria de la Sexualidad - FES Acatlán'
|
||||
})
|
||||
nombre_form: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Descripción del formulario',
|
||||
example: 'La Feria de la Sexualidad es un espacio seguro e informativo...'
|
||||
})
|
||||
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[];
|
||||
}
|
||||
@@ -16,29 +16,27 @@ export class Cuestionario {
|
||||
contador_secciones: number;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
descripcion: string;
|
||||
descripcion?: string;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
editable: boolean;
|
||||
|
||||
@Column({ type: 'datetime', nullable: true })
|
||||
fecha_fin: Date;
|
||||
fecha_fin?: Date;
|
||||
|
||||
@Column({ type: 'datetime', nullable: true })
|
||||
fecha_inicio: Date;
|
||||
fecha_inicio?: Date;
|
||||
|
||||
|
||||
@OneToMany(() => CuestionarioSeccion,(cuestionario_seccion) => cuestionario_seccion.id_cuestionario)
|
||||
cuestionarioSeccion:CuestionarioSeccion[]
|
||||
|
||||
@OneToMany(()=> Cuestionario, (cuestionario) => cuestionario.id_cuestionario)
|
||||
@Column({ type: 'int' })
|
||||
@Column({ type: 'int', nullable: true })
|
||||
id_cuestionario_original: number;
|
||||
|
||||
@OneToMany(()=> TipoCuestionario, (tipo_cuestionario) => tipo_cuestionario.id_tipo_cuestionario )
|
||||
@Column({ type: 'int' })
|
||||
id_tipo_cuestionario: number;
|
||||
|
||||
@ManyToOne(()=> Cuestionario, (cuestionario) => cuestionario.id_cuestionario)
|
||||
Cuestionario: Cuestionario[];
|
||||
@ManyToOne(()=> Cuestionario)
|
||||
cuestionarioOriginal: Cuestionario;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CuestionarioRespondidoService } from './cuestionario_respondido.service';
|
||||
import { CuestionarioRespondidoController } from './cuestionario_respondido.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { CuestionarioRespondido } from './entities/cuestionario_respondido.entity';
|
||||
import { CuestionarioModule } from '../cuestionario/cuestionario.module';
|
||||
import { ParticipanteModule } from '../participante/participante.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([CuestionarioRespondido]),
|
||||
CuestionarioModule,
|
||||
ParticipanteModule
|
||||
],
|
||||
controllers: [CuestionarioRespondidoController],
|
||||
providers: [CuestionarioRespondidoService],
|
||||
exports: [TypeOrmModule]
|
||||
})
|
||||
export class CuestionarioRespondidoModule {}
|
||||
|
||||
@@ -16,18 +16,18 @@ export class CuestionarioRespondido {
|
||||
// Relación con Participante
|
||||
@ManyToOne(() => Participante, participante => participante.cuestionariosRespondidos)
|
||||
@JoinColumn({ name: 'id_participante' })
|
||||
id_participante: Participante;
|
||||
participante: Participante;
|
||||
|
||||
// Relación con Cuestionario
|
||||
@ManyToOne(() => Cuestionario, cuestionario => cuestionario.id_cuestionario)
|
||||
@ManyToOne(() => Cuestionario)
|
||||
@JoinColumn({ name: 'id_cuestionario' })
|
||||
id_cuestionario: Cuestionario;
|
||||
cuestionario: Cuestionario;
|
||||
|
||||
// Relación con RespuestasCerradas
|
||||
@OneToMany(() => RespuestaParticipanteCerrada, respuestaCerrada => respuestaCerrada.id_cuestionario_respondido)
|
||||
@OneToMany(() => RespuestaParticipanteCerrada, respuestaCerrada => respuestaCerrada.cuestionarioRespondido)
|
||||
respuestasCerradas: RespuestaParticipanteCerrada[];
|
||||
|
||||
// Relación con RespuestasAbiertas
|
||||
@OneToMany(() => RespuestaParticipanteAbierta, respuestaAbierta => respuestaAbierta.id_cuestionario_respondido)
|
||||
@OneToMany(() => RespuestaParticipanteAbierta, respuestaAbierta => respuestaAbierta.cuestionarioRespondido)
|
||||
respuestasAbiertas: RespuestaParticipanteAbierta[];
|
||||
}
|
||||
@@ -1,9 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CuestionarioSeccionService } from './cuestionario_seccion.service';
|
||||
import { CuestionarioSeccionController } from './cuestionario_seccion.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { CuestionarioSeccion } from './entities/cuestionario_seccion.entity';
|
||||
import { CuestionarioModule } from '../cuestionario/cuestionario.module';
|
||||
import { SeccionModule } from '../seccion/seccion.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([CuestionarioSeccion]),
|
||||
CuestionarioModule,
|
||||
SeccionModule
|
||||
],
|
||||
controllers: [CuestionarioSeccionController],
|
||||
providers: [CuestionarioSeccionService],
|
||||
exports: [TypeOrmModule]
|
||||
})
|
||||
export class CuestionarioSeccionModule {}
|
||||
|
||||
@@ -11,11 +11,15 @@ export class CuestionarioSeccion {
|
||||
posicion: number;
|
||||
|
||||
|
||||
@ManyToOne(() => Cuestionario, (cuestionario) => cuestionario.id_cuestionario)
|
||||
@ManyToOne(() => Cuestionario, (cuestionario) => cuestionario.cuestionarioSeccion)
|
||||
cuestionario: Cuestionario;
|
||||
|
||||
@Column({ type: 'int' })
|
||||
id_cuestionario: number;
|
||||
|
||||
@ManyToOne(() => Seccion, (seccion) => seccion.id_seccion)
|
||||
@ManyToOne(() => Seccion, (seccion) => seccion.seccion)
|
||||
seccion: Seccion;
|
||||
|
||||
@Column({type:'int'})
|
||||
id_seccion:number;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { INestApplication } from '@nestjs/common';
|
||||
@Module({})
|
||||
export class DocsModule {
|
||||
static setupSwagger(app: INestApplication) {
|
||||
const document = SwaggerModule.createDocument(app, swaggerConfig);
|
||||
SwaggerModule.setup('api-docs', app, document);
|
||||
const document = SwaggerModule.createDocument(app, swaggerConfig);
|
||||
SwaggerModule.setup('api-docs', app, document);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
import { DocumentBuilder } from '@nestjs/swagger';
|
||||
|
||||
export const swaggerConfig = new DocumentBuilder()
|
||||
.setTitle('Sistema de Registro de Usuarios y Eventos')
|
||||
.setDescription('API para gestionar usuarios, eventos, asistencia y códigos QR')
|
||||
.setTitle('Sistema de Formularios y Eventos CIDWA')
|
||||
.setDescription(`
|
||||
API para la gestión de formularios, cuestionarios, eventos y asistencias.
|
||||
|
||||
## Funcionalidades principales
|
||||
|
||||
- **Cuestionarios**: Creación y gestión de formularios con secciones y preguntas
|
||||
- **Eventos**: Registro y control de eventos
|
||||
- **Participantes**: Gestión de usuarios y participantes
|
||||
- **Asistencia**: Control de asistencia a eventos
|
||||
- **QR**: Generación y validación de códigos QR
|
||||
|
||||
## Ejemplos de uso
|
||||
|
||||
La API permite crear cuestionarios complejos con secciones y diferentes tipos de preguntas.
|
||||
Consulte la documentación de cada endpoint para ver ejemplos específicos.
|
||||
`)
|
||||
.setVersion('1.0')
|
||||
.addTag('Cuestionarios')
|
||||
.addTag('Secciones')
|
||||
.addTag('Preguntas')
|
||||
.addTag('Opciones')
|
||||
.addTag('Usuarios')
|
||||
.addTag('Eventos')
|
||||
.addTag('Asistencia')
|
||||
.addTag('QR')
|
||||
.addTag('QR')
|
||||
.addBearerAuth()
|
||||
.build();
|
||||
|
||||
+46
-4
@@ -2,15 +2,57 @@ import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import 'dotenv/config';
|
||||
import { DocsModule } from './docs/docs.module';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { TipoPregunta } from './tipo_pregunta/entities/tipo_pregunta.entity';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
//await app.listen(process.env.PORT ?? 3000);
|
||||
|
||||
// Configuración de validación global
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true, // Elimina propiedades no decoradas
|
||||
forbidNonWhitelisted: true, // Arroja error si hay propiedades no decoradas
|
||||
transform: true, // Transforma los datos recibidos al tipo definido en el DTO
|
||||
})
|
||||
);
|
||||
|
||||
// CORS configuration
|
||||
app.enableCors({
|
||||
origin: '*',
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
});
|
||||
|
||||
// Swagger setup
|
||||
DocsModule.setupSwagger(app);
|
||||
|
||||
await app.listen(3000);
|
||||
console.log('API en ejecución en http://localhost:3000');
|
||||
console.log('Swagger disponible en http://localhost:3000/api-docs');
|
||||
// Seed TipoPregunta data
|
||||
const dataSource = app.get(DataSource);
|
||||
const tiposPreguntaRepository = dataSource.getRepository(TipoPregunta);
|
||||
|
||||
// Check if data exists
|
||||
const count = await tiposPreguntaRepository.count();
|
||||
|
||||
if (count === 0) {
|
||||
// Create default tipos de pregunta
|
||||
const tiposPregunta = [
|
||||
{ tipo_pregunta: 'Texto' },
|
||||
{ tipo_pregunta: 'Numero' },
|
||||
{ tipo_pregunta: 'Radio' },
|
||||
{ tipo_pregunta: 'Multiple' },
|
||||
{ tipo_pregunta: 'Abierto' },
|
||||
{ tipo_pregunta: 'Fecha' },
|
||||
];
|
||||
|
||||
await tiposPreguntaRepository.save(tiposPregunta);
|
||||
console.log('Tipos de pregunta creados');
|
||||
}
|
||||
|
||||
// Start the server
|
||||
await app.listen(process.env.PORT ?? 4200);
|
||||
console.log('API en ejecución en http://localhost:4200');
|
||||
console.log('Swagger disponible en http://localhost:4200/api-docs');
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
@@ -9,8 +9,8 @@ export class Opcion {
|
||||
@Column({ type: String, nullable: false, length: 200, default: '' })
|
||||
opcion: string;
|
||||
|
||||
@OneToMany(() => PreguntaOpcion, (preguntaOpcion) => preguntaOpcion.id_opcion)
|
||||
Preguntas: PreguntaOpcion[];
|
||||
@OneToMany(() => PreguntaOpcion, (preguntaOpcion) => preguntaOpcion.opcion)
|
||||
preguntasOpciones: PreguntaOpcion[];
|
||||
|
||||
|
||||
}
|
||||
@@ -2,32 +2,39 @@ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/commo
|
||||
import { OpcionService } from './opcion.service';
|
||||
import { CreateOpcionDto } from './dto/create-opcion.dto';
|
||||
import { UpdateOpcionDto } from './dto/update-opcion.dto';
|
||||
import { OpcionApiDocumentation } from './opcion.documentation';
|
||||
|
||||
@Controller('opcion')
|
||||
@OpcionApiDocumentation.ApiController
|
||||
export class OpcionController {
|
||||
constructor(private readonly opcionService: OpcionService) {}
|
||||
|
||||
@Post()
|
||||
@OpcionApiDocumentation.ApiCreate
|
||||
create(@Body() createOpcionDto: CreateOpcionDto) {
|
||||
return this.opcionService.create(createOpcionDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@OpcionApiDocumentation.ApiGetAll
|
||||
findAll() {
|
||||
return this.opcionService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@OpcionApiDocumentation.ApiGetOne
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.opcionService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@OpcionApiDocumentation.ApiUpdate
|
||||
update(@Param('id') id: string, @Body() updateOpcionDto: UpdateOpcionDto) {
|
||||
return this.opcionService.update(+id, updateOpcionDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@OpcionApiDocumentation.ApiRemove
|
||||
remove(@Param('id') id: string) {
|
||||
return this.opcionService.remove(+id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { applyDecorators } from '@nestjs/common';
|
||||
|
||||
export class OpcionApiDocumentation {
|
||||
// Decoradores para toda la clase del controlador
|
||||
static ApiController = ApiTags('Opciones');
|
||||
|
||||
// Documentación para crear una opción
|
||||
static ApiCreate = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Crear una nueva opción',
|
||||
description: 'Crea una nueva opción para una pregunta'
|
||||
}),
|
||||
ApiBody({
|
||||
description: 'Datos de la opción a crear',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['opcion'],
|
||||
properties: {
|
||||
opcion: { type: 'string', example: 'Si' }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 201,
|
||||
description: 'Opción creada correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_opcion: { type: 'number', example: 1 },
|
||||
opcion: { type: 'string', example: 'Si' }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 400, description: 'Datos de la opción inválidos' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para obtener todas las opciones
|
||||
static ApiGetAll = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Obtener todas las opciones',
|
||||
description: 'Retorna una lista de todas las opciones registradas'
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Lista de opciones obtenida correctamente',
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_opcion: { type: 'number', example: 1 },
|
||||
opcion: { type: 'string', example: 'Si' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para obtener una opción por ID
|
||||
static ApiGetOne = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Obtener una opción por ID',
|
||||
description: 'Retorna una opción específica por su ID'
|
||||
}),
|
||||
ApiParam({
|
||||
name: 'id',
|
||||
description: 'ID de la opción',
|
||||
required: true,
|
||||
type: 'number',
|
||||
example: 1
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Opción obtenida correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_opcion: { type: 'number', example: 1 },
|
||||
opcion: { type: 'string', example: 'Si' }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 404, description: 'Opción no encontrada' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para actualizar una opción
|
||||
static ApiUpdate = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Actualizar una opción',
|
||||
description: 'Actualiza los datos de una opción existente'
|
||||
}),
|
||||
ApiParam({
|
||||
name: 'id',
|
||||
description: 'ID de la opción a actualizar',
|
||||
required: true,
|
||||
type: 'number',
|
||||
example: 1
|
||||
}),
|
||||
ApiBody({
|
||||
description: 'Datos a actualizar de la opción',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
opcion: { type: 'string', example: 'Opción actualizada' }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Opción actualizada correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
affected: { type: 'number', example: 1 }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 400, description: 'Datos de actualización inválidos' }),
|
||||
ApiResponse({ status: 404, description: 'Opción no encontrada' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para eliminar una opción
|
||||
static ApiRemove = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Eliminar una opción',
|
||||
description: 'Elimina permanentemente una opción por su ID'
|
||||
}),
|
||||
ApiParam({
|
||||
name: 'id',
|
||||
description: 'ID de la opción a eliminar',
|
||||
required: true,
|
||||
type: 'number',
|
||||
example: 1
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Opción eliminada correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
affected: { type: 'number', example: 1 }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 404, description: 'Opción no encontrada' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,6 @@ import { Opcion } from './entities/opcion.entity';
|
||||
imports: [TypeOrmModule.forFeature([Opcion])], // Importa el repositorio de Opcion
|
||||
controllers: [OpcionController],
|
||||
providers: [OpcionService],
|
||||
exports: [OpcionService]
|
||||
exports: [OpcionService, TypeOrmModule]
|
||||
})
|
||||
export class OpcionModule {}
|
||||
|
||||
@@ -39,7 +39,7 @@ export class Participante {
|
||||
|
||||
|
||||
|
||||
@OneToMany(() => CuestionarioRespondido, cuestionario => cuestionario.id_participante)
|
||||
@OneToMany(() => CuestionarioRespondido, cuestionario => cuestionario.participante)
|
||||
cuestionariosRespondidos: CuestionarioRespondido[];
|
||||
|
||||
@OneToMany(() => ParticipanteEvento, participanteEvento => participanteEvento.participante)
|
||||
|
||||
@@ -8,6 +8,6 @@ import { Participante } from './participante.entity';
|
||||
imports: [TypeOrmModule.forFeature([Participante])],
|
||||
controllers: [ParticipanteController],
|
||||
providers: [ParticipanteService],
|
||||
exports: [ParticipanteService],
|
||||
exports: [ParticipanteService, TypeOrmModule],
|
||||
})
|
||||
export class ParticipanteModule {}
|
||||
|
||||
@@ -1 +1,36 @@
|
||||
export class CreatePreguntaDto {}
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator';
|
||||
|
||||
export class OpcionDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
valor: string;
|
||||
}
|
||||
|
||||
export class CreatePreguntaDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
titulo: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
obligatoria?: boolean;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
tipo: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
contador_opcion?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
id_opcion_dependiente?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => OpcionDto)
|
||||
opciones?: OpcionDto[];
|
||||
}
|
||||
|
||||
@@ -20,14 +20,17 @@ export class Pregunta {
|
||||
|
||||
@ManyToOne(() => TipoPregunta, tipo => tipo.preguntas)
|
||||
@JoinColumn({ name: 'id_tipo_pregunta' })
|
||||
id_tipo_pregunta: TipoPregunta;
|
||||
tipoPregunta: TipoPregunta;
|
||||
|
||||
@Column()
|
||||
id_tipo_pregunta: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
id_opcion_dependiente: number;
|
||||
@Column({ type: 'int', nullable: true })
|
||||
id_opcion_dependiente?: number;
|
||||
|
||||
@OneToMany(() => SeccionPregunta, seccionPregunta => seccionPregunta.id_pregunta)
|
||||
@OneToMany(() => SeccionPregunta, seccionPregunta => seccionPregunta.pregunta)
|
||||
seccionPreguntas: SeccionPregunta[];
|
||||
|
||||
@OneToMany(() => PreguntaOpcion, preguntaOpcion => preguntaOpcion.id_pregunta)
|
||||
@OneToMany(() => PreguntaOpcion, preguntaOpcion => preguntaOpcion.pregunta)
|
||||
opciones: PreguntaOpcion[];
|
||||
}
|
||||
@@ -2,32 +2,39 @@ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/commo
|
||||
import { PreguntaService } from './pregunta.service';
|
||||
import { CreatePreguntaDto } from './dto/create-pregunta.dto';
|
||||
import { UpdatePreguntaDto } from './dto/update-pregunta.dto';
|
||||
import { PreguntaApiDocumentation } from './pregunta.documentation';
|
||||
|
||||
@Controller('pregunta')
|
||||
@PreguntaApiDocumentation.ApiController
|
||||
export class PreguntaController {
|
||||
constructor(private readonly preguntaService: PreguntaService) {}
|
||||
|
||||
@Post()
|
||||
@PreguntaApiDocumentation.ApiCreate
|
||||
create(@Body() createPreguntaDto: CreatePreguntaDto) {
|
||||
return this.preguntaService.create(createPreguntaDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@PreguntaApiDocumentation.ApiGetAll
|
||||
findAll() {
|
||||
return this.preguntaService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@PreguntaApiDocumentation.ApiGetOne
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.preguntaService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@PreguntaApiDocumentation.ApiUpdate
|
||||
update(@Param('id') id: string, @Body() updatePreguntaDto: UpdatePreguntaDto) {
|
||||
return this.preguntaService.update(+id, updatePreguntaDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@PreguntaApiDocumentation.ApiRemove
|
||||
remove(@Param('id') id: string) {
|
||||
return this.preguntaService.remove(+id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { applyDecorators } from '@nestjs/common';
|
||||
|
||||
export class PreguntaApiDocumentation {
|
||||
// Decoradores para toda la clase del controlador
|
||||
static ApiController = ApiTags('Preguntas');
|
||||
|
||||
// Documentación para crear una pregunta
|
||||
static ApiCreate = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Crear una nueva pregunta',
|
||||
description: 'Crea una nueva pregunta para una sección'
|
||||
}),
|
||||
ApiBody({
|
||||
description: 'Datos de la pregunta a crear',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['titulo', 'tipo'],
|
||||
properties: {
|
||||
titulo: { type: 'string', example: '¿Eres parte de la comunidad?' },
|
||||
obligatoria: { type: 'boolean', example: true },
|
||||
tipo: { type: 'string', example: 'Multiple' },
|
||||
contador_opcion: { type: 'number', example: 0 },
|
||||
id_opcion_dependiente: { type: 'number', example: null },
|
||||
opciones: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
valor: { type: 'string', example: 'Si' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 201,
|
||||
description: 'Pregunta creada correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_pregunta: { type: 'number', example: 1 },
|
||||
pregunta: { type: 'string', example: '¿Eres parte de la comunidad?' },
|
||||
obligatoria: { type: 'boolean', example: true },
|
||||
contador_opcion: { type: 'number', example: 0 },
|
||||
id_tipo_pregunta: { type: 'number', example: 1 },
|
||||
id_opcion_dependiente: { type: 'number', example: null }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 400, description: 'Datos de la pregunta inválidos' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para obtener todas las preguntas
|
||||
static ApiGetAll = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Obtener todas las preguntas',
|
||||
description: 'Retorna una lista de todas las preguntas registradas'
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Lista de preguntas obtenida correctamente',
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_pregunta: { type: 'number', example: 1 },
|
||||
pregunta: { type: 'string', example: '¿Eres parte de la comunidad?' },
|
||||
obligatoria: { type: 'boolean', example: true },
|
||||
contador_opcion: { type: 'number', example: 0 },
|
||||
id_tipo_pregunta: { type: 'number', example: 1 },
|
||||
id_opcion_dependiente: { type: 'number', example: null }
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para obtener una pregunta por ID
|
||||
static ApiGetOne = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Obtener una pregunta por ID',
|
||||
description: 'Retorna una pregunta específica por su ID'
|
||||
}),
|
||||
ApiParam({
|
||||
name: 'id',
|
||||
description: 'ID de la pregunta',
|
||||
required: true,
|
||||
type: 'number',
|
||||
example: 1
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Pregunta obtenida correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_pregunta: { type: 'number', example: 1 },
|
||||
pregunta: { type: 'string', example: '¿Eres parte de la comunidad?' },
|
||||
obligatoria: { type: 'boolean', example: true },
|
||||
contador_opcion: { type: 'number', example: 0 },
|
||||
id_tipo_pregunta: { type: 'number', example: 1 },
|
||||
id_opcion_dependiente: { type: 'number', example: null },
|
||||
opciones: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_opcion: { type: 'number', example: 1 },
|
||||
opcion: { type: 'string', example: 'Si' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 404, description: 'Pregunta no encontrada' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para actualizar una pregunta
|
||||
static ApiUpdate = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Actualizar una pregunta',
|
||||
description: 'Actualiza los datos de una pregunta existente'
|
||||
}),
|
||||
ApiParam({
|
||||
name: 'id',
|
||||
description: 'ID de la pregunta a actualizar',
|
||||
required: true,
|
||||
type: 'number',
|
||||
example: 1
|
||||
}),
|
||||
ApiBody({
|
||||
description: 'Datos a actualizar de la pregunta',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pregunta: { type: 'string', example: 'Pregunta actualizada' },
|
||||
obligatoria: { type: 'boolean', example: false }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Pregunta actualizada correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
affected: { type: 'number', example: 1 }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 400, description: 'Datos de actualización inválidos' }),
|
||||
ApiResponse({ status: 404, description: 'Pregunta no encontrada' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para eliminar una pregunta
|
||||
static ApiRemove = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Eliminar una pregunta',
|
||||
description: 'Elimina permanentemente una pregunta por su ID'
|
||||
}),
|
||||
ApiParam({
|
||||
name: 'id',
|
||||
description: 'ID de la pregunta a eliminar',
|
||||
required: true,
|
||||
type: 'number',
|
||||
example: 1
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Pregunta eliminada correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
affected: { type: 'number', example: 1 }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 404, description: 'Pregunta no encontrada' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,25 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PreguntaService } from './pregunta.service';
|
||||
import { PreguntaController } from './pregunta.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Pregunta } from './entities/pregunta.entity';
|
||||
import { TipoPreguntaModule } from '../tipo_pregunta/tipo_pregunta.module';
|
||||
import { TipoPregunta } from '../tipo_pregunta/entities/tipo_pregunta.entity';
|
||||
import { Opcion } from '../opcion/entities/opcion.entity';
|
||||
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
Pregunta,
|
||||
TipoPregunta,
|
||||
Opcion,
|
||||
PreguntaOpcion
|
||||
]),
|
||||
TipoPreguntaModule
|
||||
],
|
||||
controllers: [PreguntaController],
|
||||
providers: [PreguntaService],
|
||||
exports: [TypeOrmModule, PreguntaService]
|
||||
})
|
||||
export class PreguntaModule {}
|
||||
|
||||
@@ -1,26 +1,254 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreatePreguntaDto } from './dto/create-pregunta.dto';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import { CreatePreguntaDto, OpcionDto } from './dto/create-pregunta.dto';
|
||||
import { UpdatePreguntaDto } from './dto/update-pregunta.dto';
|
||||
import { Pregunta } from './entities/pregunta.entity';
|
||||
import { TipoPregunta } from '../tipo_pregunta/entities/tipo_pregunta.entity';
|
||||
import { Opcion } from '../opcion/entities/opcion.entity';
|
||||
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
|
||||
|
||||
@Injectable()
|
||||
export class PreguntaService {
|
||||
create(createPreguntaDto: CreatePreguntaDto) {
|
||||
return 'This action adds a new pregunta';
|
||||
constructor(
|
||||
@InjectRepository(Pregunta)
|
||||
private preguntaRepository: Repository<Pregunta>,
|
||||
@InjectRepository(TipoPregunta)
|
||||
private tipoPreguntaRepository: Repository<TipoPregunta>,
|
||||
@InjectRepository(Opcion)
|
||||
private opcionRepository: Repository<Opcion>,
|
||||
@InjectRepository(PreguntaOpcion)
|
||||
private preguntaOpcionRepository: Repository<PreguntaOpcion>,
|
||||
private dataSource: DataSource
|
||||
) {}
|
||||
|
||||
async create(createPreguntaDto: CreatePreguntaDto) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
// Obtener el tipo de pregunta por su nombre
|
||||
const tipoPregunta = await this.tipoPreguntaRepository.findOne({
|
||||
where: { tipo_pregunta: createPreguntaDto.tipo }
|
||||
});
|
||||
|
||||
if (!tipoPregunta) {
|
||||
throw new Error(`Tipo de pregunta '${createPreguntaDto.tipo}' no encontrado`);
|
||||
}
|
||||
|
||||
// Crear pregunta
|
||||
const pregunta = new Pregunta();
|
||||
pregunta.pregunta = createPreguntaDto.titulo;
|
||||
pregunta.obligatoria = createPreguntaDto.obligatoria || false;
|
||||
pregunta.id_tipo_pregunta = tipoPregunta.id_tipo;
|
||||
pregunta.id_opcion_dependiente = createPreguntaDto.id_opcion_dependiente || undefined;
|
||||
pregunta.contador_opcion = createPreguntaDto.opciones?.length || 0;
|
||||
|
||||
const savedPregunta = await queryRunner.manager.save(pregunta);
|
||||
|
||||
// Crear opciones para preguntas de tipo multiple/radio
|
||||
if (createPreguntaDto.opciones && createPreguntaDto.opciones.length > 0) {
|
||||
await this.createOpciones(
|
||||
queryRunner,
|
||||
savedPregunta.id_pregunta,
|
||||
createPreguntaDto.opciones
|
||||
);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
pregunta: savedPregunta
|
||||
};
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return `This action returns all pregunta`;
|
||||
private async createOpciones(
|
||||
queryRunner: any,
|
||||
preguntaId: number,
|
||||
opciones: OpcionDto[]
|
||||
) {
|
||||
for (let i = 0; i < opciones.length; i++) {
|
||||
const opcionDto = opciones[i];
|
||||
|
||||
// Crear opción
|
||||
const opcion = new Opcion();
|
||||
opcion.opcion = opcionDto.valor;
|
||||
|
||||
const savedOpcion = await queryRunner.manager.save(opcion);
|
||||
|
||||
// Vincular opción con pregunta
|
||||
const preguntaOpcion = new PreguntaOpcion();
|
||||
preguntaOpcion.id_pregunta = preguntaId;
|
||||
preguntaOpcion.opcion = savedOpcion;
|
||||
preguntaOpcion.posicion = i + 1;
|
||||
|
||||
await queryRunner.manager.save(preguntaOpcion);
|
||||
}
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return `This action returns a #${id} pregunta`;
|
||||
async findAll() {
|
||||
return this.preguntaRepository.find();
|
||||
}
|
||||
|
||||
update(id: number, updatePreguntaDto: UpdatePreguntaDto) {
|
||||
return `This action updates a #${id} pregunta`;
|
||||
async findOne(id: number) {
|
||||
const pregunta = await this.preguntaRepository.findOne({
|
||||
where: { id_pregunta: id }
|
||||
});
|
||||
|
||||
if (!pregunta) {
|
||||
throw new NotFoundException(`Pregunta con ID ${id} no encontrada`);
|
||||
}
|
||||
|
||||
return pregunta;
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} pregunta`;
|
||||
async findWithOptions(id: number) {
|
||||
const pregunta = await this.preguntaRepository.findOne({
|
||||
where: { id_pregunta: id },
|
||||
relations: ['tipoPregunta']
|
||||
});
|
||||
|
||||
if (!pregunta) {
|
||||
throw new NotFoundException(`Pregunta con ID ${id} no encontrada`);
|
||||
}
|
||||
|
||||
// Obtener las opciones para esta pregunta
|
||||
const preguntaOpciones = await this.preguntaOpcionRepository.find({
|
||||
where: { id_pregunta: id },
|
||||
relations: ['opcion'],
|
||||
order: { posicion: 'ASC' }
|
||||
});
|
||||
|
||||
const opciones = preguntaOpciones.map(po => ({
|
||||
id: po.opcion.id_opcion,
|
||||
valor: po.opcion.opcion,
|
||||
posicion: po.posicion
|
||||
}));
|
||||
|
||||
return {
|
||||
...pregunta,
|
||||
tipo: pregunta.tipoPregunta?.tipo_pregunta,
|
||||
opciones
|
||||
};
|
||||
}
|
||||
|
||||
async update(id: number, updatePreguntaDto: UpdatePreguntaDto) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const pregunta = await this.preguntaRepository.findOne({
|
||||
where: { id_pregunta: id }
|
||||
});
|
||||
|
||||
if (!pregunta) {
|
||||
throw new NotFoundException(`Pregunta con ID ${id} no encontrada`);
|
||||
}
|
||||
|
||||
// Actualizar los campos básicos de la pregunta
|
||||
if (updatePreguntaDto.titulo) pregunta.pregunta = updatePreguntaDto.titulo;
|
||||
if (updatePreguntaDto.obligatoria !== undefined) pregunta.obligatoria = updatePreguntaDto.obligatoria;
|
||||
if (updatePreguntaDto.id_opcion_dependiente !== undefined) pregunta.id_opcion_dependiente = updatePreguntaDto.id_opcion_dependiente;
|
||||
|
||||
// Si se proporciona un nuevo tipo de pregunta, actualizarlo
|
||||
if (updatePreguntaDto.tipo) {
|
||||
const tipoPregunta = await this.tipoPreguntaRepository.findOne({
|
||||
where: { tipo_pregunta: updatePreguntaDto.tipo }
|
||||
});
|
||||
|
||||
if (!tipoPregunta) {
|
||||
throw new Error(`Tipo de pregunta '${updatePreguntaDto.tipo}' no encontrado`);
|
||||
}
|
||||
|
||||
pregunta.id_tipo_pregunta = tipoPregunta.id_tipo;
|
||||
}
|
||||
|
||||
// Actualizar la pregunta
|
||||
const updatedPregunta = await queryRunner.manager.save(pregunta);
|
||||
|
||||
// Si se proporcionan nuevas opciones, actualizarlas
|
||||
if (updatePreguntaDto.opciones && updatePreguntaDto.opciones.length > 0) {
|
||||
// Eliminar las opciones existentes
|
||||
const existingOptions = await this.preguntaOpcionRepository.find({
|
||||
where: { id_pregunta: id }
|
||||
});
|
||||
|
||||
for (const option of existingOptions) {
|
||||
await queryRunner.manager.remove(option);
|
||||
}
|
||||
|
||||
// Crear las nuevas opciones
|
||||
await this.createOpciones(
|
||||
queryRunner,
|
||||
id,
|
||||
updatePreguntaDto.opciones
|
||||
);
|
||||
|
||||
// Actualizar el contador de opciones
|
||||
updatedPregunta.contador_opcion = updatePreguntaDto.opciones.length;
|
||||
await queryRunner.manager.save(updatedPregunta);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
pregunta: updatedPregunta
|
||||
};
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const pregunta = await this.preguntaRepository.findOne({
|
||||
where: { id_pregunta: id }
|
||||
});
|
||||
|
||||
if (!pregunta) {
|
||||
throw new NotFoundException(`Pregunta con ID ${id} no encontrada`);
|
||||
}
|
||||
|
||||
// Eliminar las relaciones con opciones
|
||||
const preguntaOpciones = await this.preguntaOpcionRepository.find({
|
||||
where: { id_pregunta: id }
|
||||
});
|
||||
|
||||
for (const po of preguntaOpciones) {
|
||||
await queryRunner.manager.remove(po);
|
||||
}
|
||||
|
||||
// Eliminar la pregunta
|
||||
await queryRunner.manager.remove(pregunta);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Pregunta con ID ${id} eliminada correctamente`
|
||||
};
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,15 @@ export class PreguntaOpcion {
|
||||
@Column()
|
||||
posicion: number;
|
||||
|
||||
@ManyToOne(() => Pregunta, pregunta => pregunta.id_pregunta)
|
||||
//@Column()
|
||||
id_pregunta: Pregunta;
|
||||
@ManyToOne(() => Pregunta)
|
||||
pregunta: Pregunta;
|
||||
|
||||
@Column()
|
||||
id_pregunta: number;
|
||||
|
||||
@ManyToOne(() => Opcion, opcion => opcion.id_opcion)
|
||||
//@Column()
|
||||
id_opcion: Opcion;
|
||||
@ManyToOne(() => Opcion, opcion => opcion.preguntasOpciones)
|
||||
opcion: Opcion;
|
||||
|
||||
@OneToMany(()=> RespuestaParticipanteCerrada, respuestaParticipanteCerrada=>respuestaParticipanteCerrada.id_pregunta_opcion)
|
||||
@OneToMany(()=> RespuestaParticipanteCerrada, respuestaParticipanteCerrada=>respuestaParticipanteCerrada.preguntaOpcion)
|
||||
respuestaParticipanteCerrada:RespuestaParticipanteCerrada[];
|
||||
}
|
||||
@@ -1,9 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PreguntaOpcionService } from './pregunta_opcion.service';
|
||||
import { PreguntaOpcionController } from './pregunta_opcion.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { PreguntaOpcion } from './entities/pregunta_opcion.entity';
|
||||
import { OpcionModule } from '../opcion/opcion.module';
|
||||
import { PreguntaModule } from '../pregunta/pregunta.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([PreguntaOpcion]),
|
||||
OpcionModule,
|
||||
PreguntaModule
|
||||
],
|
||||
controllers: [PreguntaOpcionController],
|
||||
providers: [PreguntaOpcionService],
|
||||
exports: [TypeOrmModule]
|
||||
})
|
||||
export class PreguntaOpcionModule {}
|
||||
|
||||
+4
-7
@@ -14,17 +14,14 @@ export class RespuestaParticipanteAbierta {
|
||||
// Relación con CuestionarioRespondido
|
||||
@ManyToOne(
|
||||
() => CuestionarioRespondido,
|
||||
cuestionarioRespondido => cuestionarioRespondido.idCuestionarioRespondido
|
||||
cuestionarioRespondido => cuestionarioRespondido.respuestasAbiertas
|
||||
)
|
||||
@JoinColumn({ name: 'id_cuestionario_respondido' })
|
||||
id_cuestionario_respondido: CuestionarioRespondido;
|
||||
cuestionarioRespondido: CuestionarioRespondido;
|
||||
|
||||
@ManyToOne(
|
||||
() => Pregunta,
|
||||
pregunta => pregunta.id_pregunta
|
||||
)
|
||||
@ManyToOne(() => Pregunta)
|
||||
@JoinColumn({ name: 'id_pregunta' })
|
||||
id_pregunta: Pregunta;
|
||||
pregunta: Pregunta;
|
||||
|
||||
|
||||
}
|
||||
@@ -1,9 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { RespuestaParticipanteAbiertaService } from './respuesta_participante_abierta.service';
|
||||
import { RespuestaParticipanteAbiertaController } from './respuesta_participante_abierta.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { RespuestaParticipanteAbierta } from './entities/respuesta_participante_abierta.entity';
|
||||
import { CuestionarioRespondidoModule } from '../cuestionario_respondido/cuestionario_respondido.module';
|
||||
import { PreguntaModule } from '../pregunta/pregunta.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([RespuestaParticipanteAbierta]),
|
||||
CuestionarioRespondidoModule,
|
||||
PreguntaModule
|
||||
],
|
||||
controllers: [RespuestaParticipanteAbiertaController],
|
||||
providers: [RespuestaParticipanteAbiertaService],
|
||||
exports: [TypeOrmModule]
|
||||
})
|
||||
export class RespuestaParticipanteAbiertaModule {}
|
||||
|
||||
+3
-3
@@ -10,11 +10,11 @@ export class RespuestaParticipanteCerrada {
|
||||
|
||||
@ManyToOne(() => CuestionarioRespondido, cuestionarioRespondido => cuestionarioRespondido.respuestasCerradas)
|
||||
@JoinColumn({ name: 'id_cuestionario_respondido' })
|
||||
id_cuestionario_respondido: CuestionarioRespondido;
|
||||
cuestionarioRespondido: CuestionarioRespondido;
|
||||
|
||||
@ManyToOne(() => PreguntaOpcion, preguntaOpcion => preguntaOpcion.id_pregunta_opcion)
|
||||
@ManyToOne(() => PreguntaOpcion, preguntaOpcion => preguntaOpcion.respuestaParticipanteCerrada)
|
||||
@JoinColumn({ name: 'id_pregunta_opcion' })
|
||||
id_pregunta_opcion: PreguntaOpcion;
|
||||
preguntaOpcion: PreguntaOpcion;
|
||||
|
||||
|
||||
}
|
||||
@@ -1,9 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { RespuestaParticipanteCerradaService } from './respuesta_participante_cerrada.service';
|
||||
import { RespuestaParticipanteCerradaController } from './respuesta_participante_cerrada.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { RespuestaParticipanteCerrada } from './entities/respuesta_participante_cerrada.entity';
|
||||
import { PreguntaOpcionModule } from '../pregunta_opcion/pregunta_opcion.module';
|
||||
import { CuestionarioRespondidoModule } from '../cuestionario_respondido/cuestionario_respondido.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([RespuestaParticipanteCerrada]),
|
||||
PreguntaOpcionModule,
|
||||
CuestionarioRespondidoModule
|
||||
],
|
||||
controllers: [RespuestaParticipanteCerradaController],
|
||||
providers: [RespuestaParticipanteCerradaService],
|
||||
exports: [TypeOrmModule]
|
||||
})
|
||||
export class RespuestaParticipanteCerradaModule {}
|
||||
|
||||
@@ -1 +1,23 @@
|
||||
export class CreateSeccionDto {}
|
||||
import { CreatePreguntaDto } from '../../pregunta/dto/create-pregunta.dto';
|
||||
import { IsArray, IsNotEmpty, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class CreateSeccionDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
titulo: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
descripcion?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
contador_pregunta?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreatePreguntaDto)
|
||||
preguntas?: CreatePreguntaDto[];
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export class Seccion {
|
||||
contador_pregunta: number;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
descripcion: string;
|
||||
descripcion?: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
titulo: string;
|
||||
@@ -19,6 +19,6 @@ export class Seccion {
|
||||
@OneToMany(() => CuestionarioSeccion,(cuestionario_seccion) => cuestionario_seccion.id_seccion )
|
||||
seccion:CuestionarioSeccion[];
|
||||
|
||||
@OneToMany(()=> SeccionPregunta, (seccion_pregunta)=> seccion_pregunta.id_seccion)
|
||||
seccion_pregunta:SeccionPregunta[];
|
||||
@OneToMany(()=> SeccionPregunta, (seccion_pregunta)=> seccion_pregunta.seccion)
|
||||
seccionPreguntas:SeccionPregunta[];
|
||||
}
|
||||
|
||||
@@ -2,32 +2,39 @@ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/commo
|
||||
import { SeccionService } from './seccion.service';
|
||||
import { CreateSeccionDto } from './dto/create-seccion.dto';
|
||||
import { UpdateSeccionDto } from './dto/update-seccion.dto';
|
||||
import { SeccionApiDocumentation } from './seccion.documentation';
|
||||
|
||||
@Controller('seccion')
|
||||
@SeccionApiDocumentation.ApiController
|
||||
export class SeccionController {
|
||||
constructor(private readonly seccionService: SeccionService) {}
|
||||
|
||||
@Post()
|
||||
@SeccionApiDocumentation.ApiCreate
|
||||
create(@Body() createSeccionDto: CreateSeccionDto) {
|
||||
return this.seccionService.create(createSeccionDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@SeccionApiDocumentation.ApiGetAll
|
||||
findAll() {
|
||||
return this.seccionService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@SeccionApiDocumentation.ApiGetOne
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.seccionService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@SeccionApiDocumentation.ApiUpdate
|
||||
update(@Param('id') id: string, @Body() updateSeccionDto: UpdateSeccionDto) {
|
||||
return this.seccionService.update(+id, updateSeccionDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@SeccionApiDocumentation.ApiRemove
|
||||
remove(@Param('id') id: string) {
|
||||
return this.seccionService.remove(+id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { applyDecorators } from '@nestjs/common';
|
||||
|
||||
export class SeccionApiDocumentation {
|
||||
// Decoradores para toda la clase del controlador
|
||||
static ApiController = ApiTags('Secciones');
|
||||
|
||||
// Documentación para crear una sección
|
||||
static ApiCreate = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Crear una nueva sección',
|
||||
description: 'Crea una nueva sección para un cuestionario'
|
||||
}),
|
||||
ApiBody({
|
||||
description: 'Datos de la sección a crear',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['titulo'],
|
||||
properties: {
|
||||
titulo: { type: 'string', example: 'Información Personal' },
|
||||
descripcion: {
|
||||
type: 'string',
|
||||
example: 'Proporciona tus datos personales para poder contactarte'
|
||||
},
|
||||
contador_pregunta: { type: 'number', example: 0 },
|
||||
preguntas: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
titulo: { type: 'string', example: '¿Eres parte de la comunidad?' },
|
||||
obligatoria: { type: 'boolean', example: true },
|
||||
tipo: { type: 'string', example: 'Multiple' },
|
||||
opciones: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
valor: { type: 'string', example: 'Si' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 201,
|
||||
description: 'Sección creada correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_seccion: { type: 'number', example: 1 },
|
||||
titulo: { type: 'string', example: 'Información Personal' },
|
||||
descripcion: { type: 'string' },
|
||||
contador_pregunta: { type: 'number', example: 0 }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 400, description: 'Datos de la sección inválidos' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para obtener todas las secciones
|
||||
static ApiGetAll = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Obtener todas las secciones',
|
||||
description: 'Retorna una lista de todas las secciones registradas'
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Lista de secciones obtenida correctamente',
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_seccion: { type: 'number', example: 1 },
|
||||
titulo: { type: 'string', example: 'Información Personal' },
|
||||
descripcion: { type: 'string' },
|
||||
contador_pregunta: { type: 'number', example: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para obtener una sección por ID
|
||||
static ApiGetOne = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Obtener una sección por ID',
|
||||
description: 'Retorna una sección específica por su ID'
|
||||
}),
|
||||
ApiParam({
|
||||
name: 'id',
|
||||
description: 'ID de la sección',
|
||||
required: true,
|
||||
type: 'number',
|
||||
example: 1
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Sección obtenida correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_seccion: { type: 'number', example: 1 },
|
||||
titulo: { type: 'string', example: 'Información Personal' },
|
||||
descripcion: { type: 'string' },
|
||||
contador_pregunta: { type: 'number', example: 0 }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 404, description: 'Sección no encontrada' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para actualizar una sección
|
||||
static ApiUpdate = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Actualizar una sección',
|
||||
description: 'Actualiza los datos de una sección existente'
|
||||
}),
|
||||
ApiParam({
|
||||
name: 'id',
|
||||
description: 'ID de la sección a actualizar',
|
||||
required: true,
|
||||
type: 'number',
|
||||
example: 1
|
||||
}),
|
||||
ApiBody({
|
||||
description: 'Datos a actualizar de la sección',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
titulo: { type: 'string', example: 'Título actualizado' },
|
||||
descripcion: { type: 'string', example: 'Descripción actualizada' }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Sección actualizada correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
affected: { type: 'number', example: 1 }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 400, description: 'Datos de actualización inválidos' }),
|
||||
ApiResponse({ status: 404, description: 'Sección no encontrada' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
|
||||
// Documentación para eliminar una sección
|
||||
static ApiRemove = applyDecorators(
|
||||
ApiOperation({
|
||||
summary: 'Eliminar una sección',
|
||||
description: 'Elimina permanentemente una sección por su ID'
|
||||
}),
|
||||
ApiParam({
|
||||
name: 'id',
|
||||
description: 'ID de la sección a eliminar',
|
||||
required: true,
|
||||
type: 'number',
|
||||
example: 1
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Sección eliminada correctamente',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
affected: { type: 'number', example: 1 }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({ status: 404, description: 'Sección no encontrada' }),
|
||||
ApiResponse({ status: 500, description: 'Error interno del servidor' })
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,27 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SeccionService } from './seccion.service';
|
||||
import { SeccionController } from './seccion.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Seccion } from './entities/seccion.entity';
|
||||
import { Pregunta } from '../pregunta/entities/pregunta.entity';
|
||||
import { SeccionPregunta } from '../seccion_pregunta/entities/seccion_pregunta.entity';
|
||||
import { TipoPregunta } from '../tipo_pregunta/entities/tipo_pregunta.entity';
|
||||
import { Opcion } from '../opcion/entities/opcion.entity';
|
||||
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
Seccion,
|
||||
Pregunta,
|
||||
SeccionPregunta,
|
||||
TipoPregunta,
|
||||
Opcion,
|
||||
PreguntaOpcion
|
||||
])
|
||||
],
|
||||
controllers: [SeccionController],
|
||||
providers: [SeccionService],
|
||||
exports: [TypeOrmModule, SeccionService]
|
||||
})
|
||||
export class SeccionModule {}
|
||||
|
||||
+190
-11
@@ -1,26 +1,205 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import { CreateSeccionDto } from './dto/create-seccion.dto';
|
||||
import { UpdateSeccionDto } from './dto/update-seccion.dto';
|
||||
import { Seccion } from './entities/seccion.entity';
|
||||
import { Pregunta } from '../pregunta/entities/pregunta.entity';
|
||||
import { SeccionPregunta } from '../seccion_pregunta/entities/seccion_pregunta.entity';
|
||||
import { TipoPregunta } from '../tipo_pregunta/entities/tipo_pregunta.entity';
|
||||
import { Opcion } from '../opcion/entities/opcion.entity';
|
||||
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SeccionService {
|
||||
create(createSeccionDto: CreateSeccionDto) {
|
||||
return 'This action adds a new seccion';
|
||||
constructor(
|
||||
@InjectRepository(Seccion)
|
||||
private seccionRepository: Repository<Seccion>,
|
||||
@InjectRepository(Pregunta)
|
||||
private preguntaRepository: Repository<Pregunta>,
|
||||
@InjectRepository(SeccionPregunta)
|
||||
private seccionPreguntaRepository: Repository<SeccionPregunta>,
|
||||
@InjectRepository(TipoPregunta)
|
||||
private tipoPreguntaRepository: Repository<TipoPregunta>,
|
||||
@InjectRepository(Opcion)
|
||||
private opcionRepository: Repository<Opcion>,
|
||||
@InjectRepository(PreguntaOpcion)
|
||||
private preguntaOpcionRepository: Repository<PreguntaOpcion>,
|
||||
private dataSource: DataSource
|
||||
) {}
|
||||
|
||||
async create(createSeccionDto: CreateSeccionDto) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
// 1. Crear sección
|
||||
const seccion = new Seccion();
|
||||
seccion.titulo = createSeccionDto.titulo;
|
||||
seccion.descripcion = createSeccionDto.descripcion;
|
||||
seccion.contador_pregunta = createSeccionDto.preguntas?.length || 0;
|
||||
|
||||
const savedSeccion = await queryRunner.manager.save(seccion);
|
||||
|
||||
// 2. Crear preguntas y vincularlas a la sección
|
||||
if (createSeccionDto.preguntas && createSeccionDto.preguntas.length > 0) {
|
||||
for (let i = 0; i < createSeccionDto.preguntas.length; i++) {
|
||||
const preguntaDto = createSeccionDto.preguntas[i];
|
||||
|
||||
// Obtener el tipo de pregunta por su nombre
|
||||
const tipoPregunta = await queryRunner.manager.findOne(TipoPregunta, {
|
||||
where: { tipo_pregunta: preguntaDto.tipo },
|
||||
});
|
||||
|
||||
if (!tipoPregunta) {
|
||||
throw new Error(`Tipo de pregunta '${preguntaDto.tipo}' no encontrado`);
|
||||
}
|
||||
|
||||
// Crear pregunta
|
||||
const pregunta = new Pregunta();
|
||||
pregunta.pregunta = preguntaDto.titulo;
|
||||
pregunta.obligatoria = preguntaDto.obligatoria || false;
|
||||
pregunta.id_tipo_pregunta = tipoPregunta.id_tipo;
|
||||
pregunta.id_opcion_dependiente = preguntaDto.id_opcion_dependiente || undefined;
|
||||
pregunta.contador_opcion = preguntaDto.opciones?.length || 0;
|
||||
|
||||
const savedPregunta = await queryRunner.manager.save(pregunta);
|
||||
|
||||
// Vincular pregunta con sección
|
||||
const seccionPregunta = new SeccionPregunta();
|
||||
seccionPregunta.id_seccion = savedSeccion.id_seccion;
|
||||
seccionPregunta.id_pregunta = savedPregunta.id_pregunta;
|
||||
seccionPregunta.posicion = i + 1;
|
||||
|
||||
await queryRunner.manager.save(seccionPregunta);
|
||||
|
||||
// Crear opciones para preguntas de tipo multiple/radio
|
||||
if (preguntaDto.opciones && preguntaDto.opciones.length > 0) {
|
||||
for (let j = 0; j < preguntaDto.opciones.length; j++) {
|
||||
const opcionDto = preguntaDto.opciones[j];
|
||||
|
||||
// Crear opción
|
||||
const opcion = new Opcion();
|
||||
opcion.opcion = opcionDto.valor;
|
||||
|
||||
const savedOpcion = await queryRunner.manager.save(opcion);
|
||||
|
||||
// Vincular opción con pregunta
|
||||
const preguntaOpcion = new PreguntaOpcion();
|
||||
preguntaOpcion.id_pregunta = savedPregunta.id_pregunta;
|
||||
preguntaOpcion.opcion = savedOpcion;
|
||||
preguntaOpcion.posicion = j + 1;
|
||||
|
||||
await queryRunner.manager.save(preguntaOpcion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
seccion: savedSeccion,
|
||||
};
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return `This action returns all seccion`;
|
||||
async findAll() {
|
||||
return this.seccionRepository.find();
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return `This action returns a #${id} seccion`;
|
||||
async findOne(id: number) {
|
||||
const seccion = await this.seccionRepository.findOne({
|
||||
where: { id_seccion: id }
|
||||
});
|
||||
|
||||
if (!seccion) {
|
||||
throw new NotFoundException(`Sección con ID ${id} no encontrada`);
|
||||
}
|
||||
|
||||
return seccion;
|
||||
}
|
||||
|
||||
update(id: number, updateSeccionDto: UpdateSeccionDto) {
|
||||
return `This action updates a #${id} seccion`;
|
||||
async findWithPreguntas(id: number) {
|
||||
const seccion = await this.seccionRepository.findOne({
|
||||
where: { id_seccion: id }
|
||||
});
|
||||
|
||||
if (!seccion) {
|
||||
throw new NotFoundException(`Sección con ID ${id} no encontrada`);
|
||||
}
|
||||
|
||||
// Obtener las relaciones seccion-pregunta para esta sección
|
||||
const seccionPreguntas = await this.seccionPreguntaRepository.find({
|
||||
where: { id_seccion: id },
|
||||
relations: ['pregunta']
|
||||
});
|
||||
|
||||
// Extraer y organizar las preguntas
|
||||
const preguntas = await Promise.all(
|
||||
seccionPreguntas.map(async (sp) => {
|
||||
const pregunta = await this.preguntaRepository.findOne({
|
||||
where: { id_pregunta: sp.id_pregunta }
|
||||
});
|
||||
|
||||
// Obtener las opciones para preguntas de tipo multiple/radio
|
||||
const preguntaOpciones = await this.preguntaOpcionRepository.find({
|
||||
where: { id_pregunta: sp.id_pregunta },
|
||||
relations: ['opcion'],
|
||||
order: { posicion: 'ASC' }
|
||||
});
|
||||
|
||||
const opciones = preguntaOpciones.map(po => po.opcion);
|
||||
|
||||
return {
|
||||
...pregunta,
|
||||
posicion: sp.posicion,
|
||||
opciones
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Ordenar las preguntas por posición
|
||||
preguntas.sort((a, b) => a.posicion - b.posicion);
|
||||
|
||||
return {
|
||||
...seccion,
|
||||
preguntas
|
||||
};
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} seccion`;
|
||||
async update(id: number, updateSeccionDto: UpdateSeccionDto) {
|
||||
const seccion = await this.seccionRepository.findOne({
|
||||
where: { id_seccion: id }
|
||||
});
|
||||
|
||||
if (!seccion) {
|
||||
throw new NotFoundException(`Sección con ID ${id} no encontrada`);
|
||||
}
|
||||
|
||||
// Actualizar solo los campos proporcionados
|
||||
if (updateSeccionDto.titulo) seccion.titulo = updateSeccionDto.titulo;
|
||||
if (updateSeccionDto.descripcion !== undefined) seccion.descripcion = updateSeccionDto.descripcion;
|
||||
|
||||
return this.seccionRepository.save(seccion);
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const seccion = await this.seccionRepository.findOne({
|
||||
where: { id_seccion: id }
|
||||
});
|
||||
|
||||
if (!seccion) {
|
||||
throw new NotFoundException(`Sección con ID ${id} no encontrada`);
|
||||
}
|
||||
|
||||
return this.seccionRepository.remove(seccion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,15 @@ export class SeccionPregunta {
|
||||
@Column({ type: 'tinyint' })
|
||||
posicion: number;
|
||||
|
||||
@ManyToOne(() => Pregunta, (pregunta) => pregunta.id_pregunta)
|
||||
@ManyToOne(() => Pregunta, (pregunta) => pregunta.seccionPreguntas)
|
||||
pregunta: Pregunta;
|
||||
|
||||
@Column({ type: 'int' })
|
||||
id_pregunta: number;
|
||||
|
||||
@ManyToOne(() => Seccion, (seccion) => seccion.id_seccion)
|
||||
@ManyToOne(() => Seccion, (seccion) => seccion.seccionPreguntas)
|
||||
seccion: Seccion;
|
||||
|
||||
@Column({ type: 'int' })
|
||||
id_seccion: number;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SeccionPreguntaService } from './seccion_pregunta.service';
|
||||
import { SeccionPreguntaController } from './seccion_pregunta.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { SeccionPregunta } from './entities/seccion_pregunta.entity';
|
||||
import { PreguntaModule } from '../pregunta/pregunta.module';
|
||||
import { SeccionModule } from '../seccion/seccion.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([SeccionPregunta]),
|
||||
PreguntaModule,
|
||||
SeccionModule
|
||||
],
|
||||
controllers: [SeccionPreguntaController],
|
||||
providers: [SeccionPreguntaService],
|
||||
exports: [TypeOrmModule]
|
||||
})
|
||||
export class SeccionPreguntaModule {}
|
||||
|
||||
@@ -9,6 +9,6 @@ export class TipoPregunta {
|
||||
@Column()
|
||||
tipo_pregunta: string;
|
||||
|
||||
@OneToMany(() => Pregunta, pregunta => pregunta.id_tipo_pregunta)
|
||||
@OneToMany(() => Pregunta, pregunta => pregunta.tipoPregunta)
|
||||
preguntas: Pregunta[];
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TipoPreguntaService } from './tipo_pregunta.service';
|
||||
import { TipoPreguntaController } from './tipo_pregunta.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { TipoPregunta } from './entities/tipo_pregunta.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([TipoPregunta])],
|
||||
controllers: [TipoPreguntaController],
|
||||
providers: [TipoPreguntaService],
|
||||
exports: [TypeOrmModule]
|
||||
})
|
||||
export class TipoPreguntaModule {}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
export const feriaSexualidad = {
|
||||
nombre_form: 'Registro para la Feria de la Sexualidad - FES Acatlán',
|
||||
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.',
|
||||
fecha_inicio: '2025-03-07T00:00:01',
|
||||
fecha_fin: '2025-03-18T23:59:59',
|
||||
id_tipo_cuestionario: 1,
|
||||
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: 'Multiple',
|
||||
opciones: [{ valor: 'Si' }, { valor: 'No' }],
|
||||
obligatoria: true,
|
||||
},
|
||||
{
|
||||
titulo: 'Numero de cuenta',
|
||||
obligatoria: true,
|
||||
tipo: 'Numero',
|
||||
},
|
||||
{
|
||||
titulo: 'Nombre(s)',
|
||||
obligatoria: true,
|
||||
tipo: 'Texto',
|
||||
},
|
||||
{
|
||||
titulo: 'Apellidos',
|
||||
obligatoria: true,
|
||||
tipo: 'Texto',
|
||||
},
|
||||
{
|
||||
titulo: 'genero',
|
||||
obligatoria: true,
|
||||
tipo: 'Radio',
|
||||
opciones: [
|
||||
{ valor: 'Masculino' },
|
||||
{ valor: 'Femenino' },
|
||||
{ valor: 'Prefiero no decirlo' },
|
||||
],
|
||||
},
|
||||
{
|
||||
titulo: 'Institución de procedencia',
|
||||
tipo: 'Abierto',
|
||||
obligatoria: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,197 @@
|
||||
export const cuestionario_feria = {
|
||||
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: 'Preguntas generales',
|
||||
titulo: 'General',
|
||||
},
|
||||
preguntas: [
|
||||
{
|
||||
id_seccion_pregunta: 1000,
|
||||
posicion: 1,
|
||||
pregunta: {
|
||||
id_pregunta: 10000,
|
||||
pregunta: '¿Cómo calificaría nuestro servicio?',
|
||||
contador_opcion: 4,
|
||||
obligatoria: true,
|
||||
id_tipo_pregunta: 1,
|
||||
id_opcion_dependiente: null,
|
||||
tipo_pregunta: {
|
||||
id_tipo: 1,
|
||||
tipo_pregunta: 'Cerrada',
|
||||
},
|
||||
opciones: [
|
||||
{
|
||||
id_pregunta_opcion: 50000,
|
||||
posicion: 1,
|
||||
id_opcion: 90000,
|
||||
opcion: {
|
||||
id_opcion: 90000,
|
||||
opcion: 'Excelente',
|
||||
},
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50001,
|
||||
posicion: 2,
|
||||
id_opcion: 90001,
|
||||
opcion: {
|
||||
id_opcion: 90001,
|
||||
opcion: 'Bueno',
|
||||
},
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50002,
|
||||
posicion: 3,
|
||||
id_opcion: 90002,
|
||||
opcion: {
|
||||
id_opcion: 90002,
|
||||
opcion: 'Regular',
|
||||
},
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50003,
|
||||
posicion: 4,
|
||||
id_opcion: 90003,
|
||||
opcion: {
|
||||
id_opcion: 90003,
|
||||
opcion: 'Malo',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id_seccion_pregunta: 1001,
|
||||
posicion: 2,
|
||||
pregunta: {
|
||||
id_pregunta: 10001,
|
||||
pregunta: '¿Recomendaría nuestro servicio?',
|
||||
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: 50004,
|
||||
posicion: 1,
|
||||
id_opcion: 90004,
|
||||
opcion: {
|
||||
id_opcion: 90004,
|
||||
opcion: 'Sí',
|
||||
},
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50005,
|
||||
posicion: 2,
|
||||
id_opcion: 90005,
|
||||
opcion: {
|
||||
id_opcion: 90005,
|
||||
opcion: 'No',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id_cuestionario_seccion: 2,
|
||||
posicion: 2,
|
||||
seccion: {
|
||||
id_seccion: 101,
|
||||
contador_pregunta: 1,
|
||||
descripcion: 'Preguntas adicionales para conocer más detalles',
|
||||
titulo: 'Adicionales',
|
||||
},
|
||||
preguntas: [
|
||||
{
|
||||
id_seccion_pregunta: 1002,
|
||||
posicion: 1,
|
||||
pregunta: {
|
||||
id_pregunta: 10002,
|
||||
pregunta: '¿Qué mejorarías en nuestro servicio?',
|
||||
contador_opcion: 0,
|
||||
obligatoria: false,
|
||||
id_tipo_pregunta: 2,
|
||||
id_opcion_dependiente: null,
|
||||
tipo_pregunta: {
|
||||
id_tipo: 2,
|
||||
tipo_pregunta: 'Abierta',
|
||||
},
|
||||
opciones: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id_seccion_pregunta: 1003,
|
||||
posicion: 3,
|
||||
pregunta: {
|
||||
id_pregunta: 10003,
|
||||
pregunta: '¿Qué aspectos del servicio fueron de tu agrado?',
|
||||
contador_opcion: 3,
|
||||
obligatoria: false,
|
||||
id_tipo_pregunta: 3, // Puedes usar este ID para distinguir múltiples
|
||||
id_opcion_dependiente: null,
|
||||
tipo_pregunta: {
|
||||
id_tipo: 3,
|
||||
tipo_pregunta: 'Multiple',
|
||||
},
|
||||
opciones: [
|
||||
{
|
||||
id_pregunta_opcion: 50006,
|
||||
posicion: 1,
|
||||
id_opcion: 90006,
|
||||
opcion: {
|
||||
id_opcion: 90006,
|
||||
opcion: 'Rapidez',
|
||||
},
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50007,
|
||||
posicion: 2,
|
||||
id_opcion: 90007,
|
||||
opcion: {
|
||||
id_opcion: 90007,
|
||||
opcion: 'Atención al cliente',
|
||||
},
|
||||
},
|
||||
{
|
||||
id_pregunta_opcion: 50008,
|
||||
posicion: 3,
|
||||
id_opcion: 90008,
|
||||
opcion: {
|
||||
id_opcion: 90008,
|
||||
opcion: 'Facilidad de uso',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
// Script para cargar un formulario desde un archivo JSON
|
||||
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const API_URL = 'http://localhost:4200';
|
||||
|
||||
async function loadFormFromFile(filePath) {
|
||||
try {
|
||||
// Leer el archivo JSON
|
||||
const formData = require(filePath);
|
||||
|
||||
console.log('Enviando datos del formulario al servidor...');
|
||||
console.log('Datos:', JSON.stringify(formData, null, 2));
|
||||
|
||||
// Enviar los datos al endpoint
|
||||
const response = await axios.post(`${API_URL}/cuestionario`, formData);
|
||||
|
||||
console.log('Formulario creado exitosamente:');
|
||||
console.log(JSON.stringify(response.data, null, 2));
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error al crear el formulario:');
|
||||
if (error.response) {
|
||||
console.error('Status:', error.response.status);
|
||||
console.error('Data:', JSON.stringify(error.response.data, null, 2));
|
||||
} else {
|
||||
console.error(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Verificar si se proporcionó un argumento de archivo
|
||||
const args = process.argv.slice(2);
|
||||
const filePath = args[0] || './crear_formulario_feria';
|
||||
|
||||
// Ejecutar la función
|
||||
loadFormFromFile(filePath)
|
||||
.then(() => console.log('Proceso completado'))
|
||||
.catch(() => {
|
||||
console.log('Proceso fallido');
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
const comunidad = {
|
||||
id_formulario: 1,
|
||||
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' },
|
||||
],
|
||||
fecha_envio: '2025-04-02T13:45:00',
|
||||
};
|
||||
|
||||
const externos = {
|
||||
id_formulario: 1,
|
||||
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' }, // genero
|
||||
{ id_pregunta: 107, valor: 'UAM Azcapotzalco' }, // Institución de procedencia
|
||||
],
|
||||
fecha_envio: '2025-04-02T13:45:00',
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { feriaSexualidad } from './crear_formulario_feria';
|
||||
import axios from 'axios';
|
||||
|
||||
// Asumiendo que el servidor está corriendo en localhost:4200
|
||||
const API_URL = 'http://localhost:4200';
|
||||
|
||||
async function crearFormulario() {
|
||||
try {
|
||||
const response = await axios.post(`${API_URL}/cuestionario`, feriaSexualidad);
|
||||
console.log('Formulario creado exitosamente:');
|
||||
console.log(JSON.stringify(response.data, null, 2));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error al crear el formulario:');
|
||||
if (error.response) {
|
||||
console.error('Status:', error.response.status);
|
||||
console.error('Data:', error.response.data);
|
||||
} else {
|
||||
console.error(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Ejecutar la función
|
||||
crearFormulario()
|
||||
.then(() => console.log('Proceso completado'))
|
||||
.catch(() => console.log('Proceso fallido'));
|
||||
Reference in New Issue
Block a user