diff --git a/package-lock.json b/package-lock.json index d47efe7..406975b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,11 @@ "@nestjs/core": "^11.0.1", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^11.0.1", + "@nestjs/swagger": "^11.1.0", "@nestjs/typeorm": "^11.0.0", + "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "mysql2": "^3.14.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "typeorm": "^0.3.21" @@ -1906,6 +1909,12 @@ "node": ">=8" } }, + "node_modules/@microsoft/tsdoc": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", + "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", + "license": "MIT" + }, "node_modules/@napi-rs/nice": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz", @@ -2495,6 +2504,39 @@ "tslib": "^2.1.0" } }, + "node_modules/@nestjs/swagger": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.1.0.tgz", + "integrity": "sha512-+GQ+q1ASTBvGi0DYHukWi8NVVVLszedwLLqHdLRnJh8rjokt8YTDb7roImvT/YMmYgPvaWBv/4JYdZH4FueLPQ==", + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.15.1", + "@nestjs/mapped-types": "2.1.0", + "js-yaml": "4.1.0", + "lodash": "4.17.21", + "path-to-regexp": "8.2.0", + "swagger-ui-dist": "5.20.1" + }, + "peerDependencies": { + "@fastify/static": "^8.0.0", + "@nestjs/common": "^11.0.1", + "@nestjs/core": "^11.0.1", + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/testing": { "version": "11.0.12", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.0.12.tgz", @@ -2613,6 +2655,13 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -4115,7 +4164,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-timsort": { @@ -4146,6 +4194,15 @@ "dev": true, "license": "MIT" }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/b4a": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", @@ -4746,6 +4803,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", @@ -5248,6 +5311,15 @@ "node": ">=0.4.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -6499,6 +6571,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -7077,6 +7158,12 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -7912,7 +7999,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -8125,6 +8211,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/long": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz", + "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==", + "license": "Apache-2.0" + }, "node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -8148,6 +8240,21 @@ "yallist": "^3.0.2" } }, + "node_modules/lru.min": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -8464,6 +8571,59 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/mysql2": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.0.tgz", + "integrity": "sha512-8eMhmG6gt/hRkU1G+8KlGOdQi2w+CgtNoD1ksXZq9gQfkfDsX4LHaBwTe1SY0Imx//t2iZA03DFnyYKPinxSRw==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -9637,6 +9797,11 @@ "node": ">= 0.6" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -9883,6 +10048,15 @@ "node": ">=14" } }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -10220,6 +10394,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-ui-dist": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.1.tgz", + "integrity": "sha512-qBPCis2w8nP4US7SvUxdJD3OwKcqiWeZmjN2VWhq2v+ESZEXOP/7n4DeiOiiZcGYTKMHAHUUrroHaTsjUWTEGw==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", diff --git a/package.json b/package.json index b3ae839..398da10 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,11 @@ "@nestjs/core": "^11.0.1", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^11.0.1", + "@nestjs/swagger": "^11.1.0", "@nestjs/typeorm": "^11.0.0", + "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "mysql2": "^3.14.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "typeorm": "^0.3.21" diff --git a/src/app.module.ts b/src/app.module.ts index 3d16193..0959dd8 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -15,6 +15,7 @@ import { CuestionarioRespondidoModule } from './cuestionario_respondido/cuestion import { RespuestaParticipanteAbiertaModule } from './respuesta_participante_abierta/respuesta_participante_abierta.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule } from '@nestjs/config'; +import { ParticipanteModule } from './participante/participante.module'; @Module({ @@ -33,6 +34,7 @@ import { ConfigModule } from '@nestjs/config'; autoLoadEntities: true, // Carga automáticamente las entidades }), + ParticipanteModule, CuestionarioModule, TipoCuestionarioModule, CuestionarioSeccionModule, diff --git a/src/cuestionario/cuestionario.controller.ts b/src/cuestionario/cuestionario.controller.ts index 82adea6..14f333a 100644 --- a/src/cuestionario/cuestionario.controller.ts +++ b/src/cuestionario/cuestionario.controller.ts @@ -1,34 +1,216 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Post, + Body, + Get, + Param, + Put, + Delete, + HttpStatus, + HttpException, + Query, +} from '@nestjs/common'; import { CuestionarioService } from './cuestionario.service'; import { CreateCuestionarioDto } from './dto/create-cuestionario.dto'; import { UpdateCuestionarioDto } from './dto/update-cuestionario.dto'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiParam, + ApiBody, + ApiQuery, +} from '@nestjs/swagger'; +import { SeccionExistenteCuestionarioDto } from './dto/seccion-existente-cuestionario.dto'; -@Controller('cuestionario') +@ApiTags('Cuestionarios') +@Controller('cuestionarios') export class CuestionarioController { constructor(private readonly cuestionarioService: CuestionarioService) {} @Post() - create(@Body() createCuestionarioDto: CreateCuestionarioDto) { - return this.cuestionarioService.create(createCuestionarioDto); + @ApiOperation({ summary: 'Crear un nuevo cuestionario' }) + @ApiBody({ type: CreateCuestionarioDto }) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Cuestionario creado exitosamente con sus secciones y preguntas', + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Datos de entrada inválidos', + }) + async create(@Body() createCuestionarioDto: CreateCuestionarioDto) { + try { + return await this.cuestionarioService.create(createCuestionarioDto); + } catch (error) { + throw new HttpException( + { + status: HttpStatus.BAD_REQUEST, + error: error.message, + }, + HttpStatus.BAD_REQUEST, + ); + } } @Get() - findAll() { + @ApiOperation({ summary: 'Obtener todos los cuestionarios' }) + @ApiQuery({ + name: 'tipo', + required: false, + description: 'Filtrar por tipo de cuestionario', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Lista de cuestionarios obtenida exitosamente', + }) + async findAll(@Query('tipo') tipoCuestionarioId?: number) { + if (tipoCuestionarioId) { + return this.cuestionarioService.findAllByTipo(tipoCuestionarioId); + } return this.cuestionarioService.findAll(); } @Get(':id') - findOne(@Param('id') id: string) { - return this.cuestionarioService.findOne(+id); + @ApiOperation({ summary: 'Obtener un cuestionario específico con todas sus relaciones' }) + @ApiParam({ name: 'id', description: 'ID del cuestionario' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Cuestionario encontrado con todas sus secciones y preguntas', + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Cuestionario no encontrado', + }) + async findOne(@Param('id') id: string) { + const cuestionario = await this.cuestionarioService.findOneWithRelations(+id); + if (!cuestionario) { + throw new HttpException( + 'Cuestionario no encontrado', + HttpStatus.NOT_FOUND, + ); + } + return cuestionario; } - @Patch(':id') - update(@Param('id') id: string, @Body() updateCuestionarioDto: UpdateCuestionarioDto) { - return this.cuestionarioService.update(+id, updateCuestionarioDto); + @Put(':id') + @ApiOperation({ summary: 'Actualizar los metadatos de un cuestionario' }) + @ApiParam({ name: 'id', description: 'ID del cuestionario a actualizar' }) + @ApiBody({ type: UpdateCuestionarioDto }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Cuestionario actualizado exitosamente', + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Cuestionario no encontrado', + }) + async update( + @Param('id') id: string, + @Body() updateCuestionarioDto: UpdateCuestionarioDto, + ) { + try { + return await this.cuestionarioService.update(+id, updateCuestionarioDto); + } catch (error) { + throw new HttpException( + { + status: HttpStatus.BAD_REQUEST, + error: error.message, + }, + HttpStatus.BAD_REQUEST, + ); + } } @Delete(':id') - remove(@Param('id') id: string) { - return this.cuestionarioService.remove(+id); + @ApiOperation({ summary: 'Eliminar un cuestionario y todas sus relaciones' }) + @ApiParam({ name: 'id', description: 'ID del cuestionario a eliminar' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Cuestionario eliminado exitosamente', + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Cuestionario no encontrado', + }) + async remove(@Param('id') id: string) { + try { + return await this.cuestionarioService.remove(+id); + } catch (error) { + throw new HttpException( + { + status: HttpStatus.BAD_REQUEST, + error: error.message, + }, + HttpStatus.BAD_REQUEST, + ); + } } -} + + @Post(':id/secciones') + @ApiOperation({ summary: 'Agregar una sección existente al cuestionario' }) + @ApiParam({ name: 'id', description: 'ID del cuestionario' }) + @ApiBody({ type: SeccionExistenteCuestionarioDto }) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Sección agregada al cuestionario exitosamente', + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Cuestionario o sección no encontrada', + }) + async agregarSeccion( + @Param('id') cuestionarioId: string, + @Body() seccionDto: SeccionExistenteCuestionarioDto, + ) { + try { + return await this.cuestionarioService.agregarSeccionExistente( + +cuestionarioId, + seccionDto.id_seccion, + seccionDto.posicion, + ); + } catch (error) { + throw new HttpException( + { + status: error.status || HttpStatus.BAD_REQUEST, + error: error.message, + }, + error.status || HttpStatus.BAD_REQUEST, + ); + } + } + + @Delete(':cuestionarioId/secciones/:seccionId') + @ApiOperation({ summary: 'Remover una sección del cuestionario' }) + @ApiParam({ name: 'cuestionarioId', description: 'ID del cuestionario' }) + @ApiParam({ name: 'seccionId', description: 'ID de la sección a remover' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Sección removida del cuestionario exitosamente', + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Relación no encontrada', + }) + async removerSeccion( + @Param('cuestionarioId') cuestionarioId: string, + @Param('seccionId') seccionId: string, + ) { + try { + return await this.cuestionarioService.eliminarSeccionDeCuestionario( + +cuestionarioId, + +seccionId, + ); + } catch (error) { + throw new HttpException( + { + status: error.status || HttpStatus.BAD_REQUEST, + error: error.message, + }, + error.status || HttpStatus.BAD_REQUEST, + ); + } + } + + +} \ No newline at end of file diff --git a/src/cuestionario/cuestionario.module.ts b/src/cuestionario/cuestionario.module.ts index 478b3d6..523c318 100644 --- a/src/cuestionario/cuestionario.module.ts +++ b/src/cuestionario/cuestionario.module.ts @@ -1,9 +1,20 @@ import { Module } from '@nestjs/common'; -import { CuestionarioService } from './cuestionario.service'; -import { CuestionarioController } from './cuestionario.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CuestionarioController } from 'src/cuestionario/cuestionario.controller'; +import { CuestionarioService } from 'src/cuestionario/cuestionario.service'; +import { Cuestionario } from 'src/cuestionario/entities/cuestionario.entity'; +import { CuestionarioSeccion } from 'src/cuestionario_seccion/entities/cuestionario_seccion.entity'; +import { SeccionModule } from 'src/seccion/seccion.module'; +import { TipoCuestionario } from 'src/tipo_cuestionario/entities/tipo_cuestionario.entity'; + @Module({ + imports: [ + TypeOrmModule.forFeature([Cuestionario, TipoCuestionario, CuestionarioSeccion]), + SeccionModule, + ], controllers: [CuestionarioController], providers: [CuestionarioService], + exports: [CuestionarioService], }) -export class CuestionarioModule {} +export class CuestionarioModule {} \ No newline at end of file diff --git a/src/cuestionario/cuestionario.service.ts b/src/cuestionario/cuestionario.service.ts index 6a8f5ac..474425a 100644 --- a/src/cuestionario/cuestionario.service.ts +++ b/src/cuestionario/cuestionario.service.ts @@ -1,26 +1,221 @@ -import { Injectable } from '@nestjs/common'; -import { CreateCuestionarioDto } from './dto/create-cuestionario.dto'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Cuestionario } from './entities/cuestionario.entity'; +import { SeccionService } from '../seccion/seccion.service'; +import { CreateCuestionarioDto, SeccionEnCuestionarioDto } from './dto/create-cuestionario.dto'; import { UpdateCuestionarioDto } from './dto/update-cuestionario.dto'; +import { Seccion } from '../seccion/entities/seccion.entity'; +import { CuestionarioSeccion } from 'src/cuestionario_seccion/entities/cuestionario_seccion.entity'; +import { TipoCuestionario } from 'src/tipo_cuestionario/entities/tipo_cuestionario.entity'; @Injectable() export class CuestionarioService { - create(createCuestionarioDto: CreateCuestionarioDto) { - return 'This action adds a new cuestionario'; + async findAllByTipo(tipoCuestionarioId: number): Promise { + return this.cuestionarioRepository.find({ + where: { tipo_cuestionario: { id_tipo_cuestionario: tipoCuestionarioId } }, + relations: ['tipo_cuestionario', 'cuestionarioSecciones'], + }); + } + + obtenerResumen(arg0: number) { + throw new Error('Method not implemented.'); + } + constructor( + @InjectRepository(Cuestionario) + private readonly cuestionarioRepository: Repository, + @InjectRepository(TipoCuestionario) + private readonly tipoCuestionarioRepository: Repository, + @InjectRepository(CuestionarioSeccion) + private readonly cuestionarioSeccionRepository: Repository, + private readonly seccionService: SeccionService, + ) {} + + async create(createCuestionarioDto: CreateCuestionarioDto) { + // Verificar tipo de cuestionario + const tipoCuestionario = await this.tipoCuestionarioRepository.findOne({ + where: { id_tipo_cuestionario: createCuestionarioDto.id_tipo_cuestionario }, + }); + + if (!tipoCuestionario) { + throw new NotFoundException('Tipo de cuestionario no encontrado'); + } + + // Crear cuestionario base + const cuestionario = this.cuestionarioRepository.create({ + nombre_form: createCuestionarioDto.nombre_form, + descripcion: createCuestionarioDto.descripcion, + editable: createCuestionarioDto.editable ?? true, + fecha_inicio: createCuestionarioDto.fecha_inicio, + fecha_fin: createCuestionarioDto.fecha_fin, + tipo_cuestionario: tipoCuestionario, + contador_secciones: createCuestionarioDto.secciones?.length || 0, + }); + + const cuestionarioGuardado = await this.cuestionarioRepository.save(cuestionario); + + // Procesar secciones si existen + if (createCuestionarioDto.secciones && createCuestionarioDto.secciones.length > 0) { + await this.agregarSeccionesACuestionario( + cuestionarioGuardado.id_cuestionario, + createCuestionarioDto.secciones, + ); + } + + return this.findOneWithRelations(cuestionarioGuardado.id_cuestionario); } - findAll() { - return `This action returns all cuestionario`; + private async agregarSeccionesACuestionario( + cuestionarioId: number, + secciones: SeccionEnCuestionarioDto[], + ) { + await Promise.all( + secciones.map(async (seccionDto) => { + // Crear la sección usando el servicio existente + const seccionCreada = await this.seccionService.create(seccionDto.seccion); + + // Crear la relación cuestionario-sección + const cuestionarioSeccion = this.cuestionarioSeccionRepository.create({ + posicion: seccionDto.posicion, + cuestionario: { id_cuestionario: cuestionarioId }, + seccion: { id_seccion: seccionCreada?.id_seccion }, + }); + + await this.cuestionarioSeccionRepository.save(cuestionarioSeccion); + }) + ); } - findOne(id: number) { - return `This action returns a #${id} cuestionario`; + async findOneWithRelations(id: number) { + return this.cuestionarioRepository.findOne({ + where: { id_cuestionario: id }, + relations: [ + 'tipo_cuestionario', + 'cuestionarioSecciones', + 'cuestionarioSecciones.seccion', + 'cuestionarioSecciones.seccion.seccionPreguntas', + 'cuestionarioSecciones.seccion.seccionPreguntas.pregunta', + 'cuestionarioSecciones.seccion.seccionPreguntas.pregunta.tipo_pregunta', + 'cuestionarioSecciones.seccion.seccionPreguntas.pregunta.opciones', + 'cuestionarioSecciones.seccion.seccionPreguntas.pregunta.opciones.opcion', + ], + }); } - update(id: number, updateCuestionarioDto: UpdateCuestionarioDto) { - return `This action updates a #${id} cuestionario`; + async findAll() { + return this.cuestionarioRepository.find({ + relations: ['tipo_cuestionario'], + }); } - remove(id: number) { - return `This action removes a #${id} cuestionario`; + async update(id: number, updateCuestionarioDto: UpdateCuestionarioDto) { + const cuestionario = await this.cuestionarioRepository.findOne({ + where: { id_cuestionario: id }, + }); + + if (!cuestionario) { + throw new NotFoundException(`Cuestionario con ID ${id} no encontrado`); + } + + // Actualizar campos básicos + if (updateCuestionarioDto.nombre_form !== undefined) { + cuestionario.nombre_form = updateCuestionarioDto.nombre_form; + } + + if (updateCuestionarioDto.descripcion !== undefined) { + cuestionario.descripcion = updateCuestionarioDto.descripcion; + } + + if (updateCuestionarioDto.editable !== undefined) { + cuestionario.editable = updateCuestionarioDto.editable; + } + + if (updateCuestionarioDto.fecha_inicio !== undefined) { + cuestionario.fecha_inicio = updateCuestionarioDto.fecha_inicio; + } + + if (updateCuestionarioDto.fecha_fin !== undefined) { + cuestionario.fecha_fin = updateCuestionarioDto.fecha_fin; + } + + return this.cuestionarioRepository.save(cuestionario); } -} + + async remove(id: number) { + // Eliminar relaciones cuestionario-sección primero + await this.cuestionarioSeccionRepository.delete({ + cuestionario: { id_cuestionario: id }, + }); + + // Luego eliminar el cuestionario + const result = await this.cuestionarioRepository.delete(id); + + if (result.affected === 0) { + throw new NotFoundException(`Cuestionario con ID ${id} no encontrado`); + } + + return { affected: result.affected }; + } + + async agregarSeccionExistente( + cuestionarioId: number, + seccionId: number, + posicion: number, + ) { + // Verificar que existan ambos elementos + const cuestionario = await this.cuestionarioRepository.findOne({ + where: { id_cuestionario: cuestionarioId }, + }); + + if (!cuestionario) { + throw new NotFoundException(`Cuestionario con ID ${cuestionarioId} no encontrado`); + } + + const seccion = await this.seccionService.findOneWithRelations(seccionId); + if (!seccion) { + throw new NotFoundException(`Sección con ID ${seccionId} no encontrada`); + } + + // Crear la relación + const cuestionarioSeccion = this.cuestionarioSeccionRepository.create({ + posicion, + cuestionario: { id_cuestionario: cuestionarioId }, + seccion: { id_seccion: seccionId }, + }); + + await this.cuestionarioSeccionRepository.save(cuestionarioSeccion); + + // Actualizar contador de secciones + await this.cuestionarioRepository.increment( + { id_cuestionario: cuestionarioId }, + 'contador_secciones', + 1, + ); + + return this.findOneWithRelations(cuestionarioId); + } + + async eliminarSeccionDeCuestionario(cuestionarioId: number, seccionId: number) { + const relacion = await this.cuestionarioSeccionRepository.findOne({ + where: { + cuestionario: { id_cuestionario: cuestionarioId }, + seccion: { id_seccion: seccionId }, + }, + }); + + if (!relacion) { + throw new NotFoundException('La sección no está asociada a este cuestionario'); + } + + await this.cuestionarioSeccionRepository.remove(relacion); + + // Actualizar contador de secciones + await this.cuestionarioRepository.decrement( + { id_cuestionario: cuestionarioId }, + 'contador_secciones', + 1, + ); + + return { success: true }; + } +} \ No newline at end of file diff --git a/src/cuestionario/dto/create-cuestionario.dto.ts b/src/cuestionario/dto/create-cuestionario.dto.ts index d752739..5268c31 100644 --- a/src/cuestionario/dto/create-cuestionario.dto.ts +++ b/src/cuestionario/dto/create-cuestionario.dto.ts @@ -1 +1,46 @@ -export class CreateCuestionarioDto {} +import { IsNotEmpty, IsString, IsBoolean, IsDateString, IsNumber, IsOptional, IsArray, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; +import { CreateSeccionDto } from 'src/seccion/dto/create-seccion.dto'; + +export class CreateCuestionarioDto { + @IsNotEmpty() + @IsString() + nombre_form: string; + + @IsOptional() + @IsString() + descripcion?: string; + + @IsOptional() + @IsBoolean() + editable?: boolean; + + @IsNotEmpty() + @IsDateString() + fecha_inicio: Date; + + @IsNotEmpty() + @IsDateString() + fecha_fin: Date; + + @IsNotEmpty() + @IsNumber() + id_tipo_cuestionario: number; + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => SeccionEnCuestionarioDto) + secciones?: SeccionEnCuestionarioDto[]; +} + +export class SeccionEnCuestionarioDto { + @IsNotEmpty() + @IsNumber() + posicion: number; + + @IsNotEmpty() + @ValidateNested() + @Type(() => CreateSeccionDto) + seccion: CreateSeccionDto; +} \ No newline at end of file diff --git a/src/cuestionario/dto/seccion-existente-cuestionario.dto.ts b/src/cuestionario/dto/seccion-existente-cuestionario.dto.ts new file mode 100644 index 0000000..3a677ee --- /dev/null +++ b/src/cuestionario/dto/seccion-existente-cuestionario.dto.ts @@ -0,0 +1,22 @@ +import { IsNotEmpty, IsNumber, Min } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class SeccionExistenteCuestionarioDto { + @ApiProperty({ + example: 1, + description: 'Posición de la sección dentro del cuestionario', + }) + @IsNotEmpty() + @IsNumber() + @Min(1) + posicion: number; + + @ApiProperty({ + example: 5, + description: 'ID de la sección existente a agregar', + }) + @IsNotEmpty() + @IsNumber() + @Min(1) + id_seccion: number; +} \ No newline at end of file diff --git a/src/cuestionario/dto/update-cuestionario.dto.ts b/src/cuestionario/dto/update-cuestionario.dto.ts index f275e73..2c214ec 100644 --- a/src/cuestionario/dto/update-cuestionario.dto.ts +++ b/src/cuestionario/dto/update-cuestionario.dto.ts @@ -1,4 +1,23 @@ -import { PartialType } from '@nestjs/mapped-types'; -import { CreateCuestionarioDto } from './create-cuestionario.dto'; +import { IsOptional, IsString, IsBoolean, IsDateString } from 'class-validator'; -export class UpdateCuestionarioDto extends PartialType(CreateCuestionarioDto) {} +export class UpdateCuestionarioDto { + @IsOptional() + @IsString() + nombre_form?: string; + + @IsOptional() + @IsString() + descripcion?: string; + + @IsOptional() + @IsBoolean() + editable?: boolean; + + @IsOptional() + @IsDateString() + fecha_inicio?: Date; + + @IsOptional() + @IsDateString() + fecha_fin?: Date; +} \ No newline at end of file diff --git a/src/cuestionario/entities/cuestionario.entity.ts b/src/cuestionario/entities/cuestionario.entity.ts index f6add87..4c56921 100644 --- a/src/cuestionario/entities/cuestionario.entity.ts +++ b/src/cuestionario/entities/cuestionario.entity.ts @@ -1,6 +1,7 @@ +import { CuestionarioRespondido } from 'src/cuestionario_respondido/entities/cuestionario_respondido.entity'; import { CuestionarioSeccion } from 'src/cuestionario_seccion/entities/cuestionario_seccion.entity'; import { TipoCuestionario } from 'src/tipo_cuestionario/entities/tipo_cuestionario.entity'; -import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToMany, ManyToOne } from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToMany, ManyToOne, JoinColumn } from 'typeorm'; @@ -27,18 +28,22 @@ export class Cuestionario { @Column({ type: 'datetime', nullable: true }) fecha_inicio: Date; + @OneToMany(() => CuestionarioRespondido, cuestionarioRespondido => cuestionarioRespondido.cuestionario) + cuestionariosRespondidos: CuestionarioRespondido[]; - @OneToMany(() => CuestionarioSeccion,(cuestionario_seccion) => cuestionario_seccion.id_cuestionario) + + @OneToMany(() => CuestionarioSeccion,(cuestionario_seccion) => cuestionario_seccion.seccion) + @JoinColumn() cuestionarioSeccion:CuestionarioSeccion[] - @OneToMany(()=> Cuestionario, (cuestionario) => cuestionario.id_cuestionario) - @Column({ type: 'int' }) - id_cuestionario_original: number; + @OneToMany(()=> Cuestionario, (cuestionario) => cuestionario.cuestionario_original) + cuestionarioOriginal: Cuestionario[]; - @OneToMany(()=> TipoCuestionario, (tipo_cuestionario) => tipo_cuestionario.id_tipo_cuestionario ) - @Column({ type: 'int' }) - id_tipo_cuestionario: number; + @ManyToOne(() => TipoCuestionario, (tipo) => tipo.cuestionarios) + @JoinColumn({ name: 'id_tipo_cuestionario' }) // Clave foránea en la base de datos + tipo_cuestionario: TipoCuestionario; - @ManyToOne(()=> Cuestionario, (cuestionario) => cuestionario.id_cuestionario) - Cuestionario: Cuestionario[]; + @ManyToOne(()=> Cuestionario, (cuestionario) => cuestionario.cuestionarioOriginal) + @JoinColumn({ name: 'id_cuestionario_original' }) + cuestionario_original: Cuestionario; } diff --git a/src/cuestionario_respondido/cuestionario_respondido.module.ts b/src/cuestionario_respondido/cuestionario_respondido.module.ts index 49b24eb..69119bb 100644 --- a/src/cuestionario_respondido/cuestionario_respondido.module.ts +++ b/src/cuestionario_respondido/cuestionario_respondido.module.ts @@ -1,9 +1,14 @@ import { Module } from '@nestjs/common'; import { CuestionarioRespondidoService } from './cuestionario_respondido.service'; import { CuestionarioRespondidoController } from './cuestionario_respondido.controller'; +import { CuestionarioRespondido } from './entities/cuestionario_respondido.entity'; +import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ - controllers: [CuestionarioRespondidoController], - providers: [CuestionarioRespondidoService], + + imports: [TypeOrmModule.forFeature([CuestionarioRespondido])], + controllers: [CuestionarioRespondidoController], + providers: [CuestionarioRespondidoService], + exports:[CuestionarioRespondidoService, TypeOrmModule] }) export class CuestionarioRespondidoModule {} diff --git a/src/cuestionario_respondido/entities/cuestionario_respondido.entity.ts b/src/cuestionario_respondido/entities/cuestionario_respondido.entity.ts index b1a3e5f..ce5a8ef 100644 --- a/src/cuestionario_respondido/entities/cuestionario_respondido.entity.ts +++ b/src/cuestionario_respondido/entities/cuestionario_respondido.entity.ts @@ -3,6 +3,7 @@ import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColum import { Cuestionario } from 'src/cuestionario/entities/cuestionario.entity'; import { RespuestaParticipanteCerrada } from 'src/respuesta_participante_cerrada/entities/respuesta_participante_cerrada.entity'; import { RespuestaParticipanteAbierta } from 'src/respuesta_participante_abierta/entities/respuesta_participante_abierta.entity'; +import { Participante } from 'src/participante/participante.entity'; @Entity('cuestionario_respondido') export class CuestionarioRespondido { @@ -13,20 +14,20 @@ export class CuestionarioRespondido { fechaRespuesta: Date; // Relación con Participante - @ManyToOne(() => Participante, participante => participante.cuestionariosRespondidos) + @ManyToOne(() => Participante, participante => participante.cuestionarioRespondidos) @JoinColumn({ name: 'id_participante' }) - id_participante: Participante; + participante: Participante; // Relación con Cuestionario - @ManyToOne(() => Cuestionario, cuestionario => cuestionario.id_cuestionario) + @ManyToOne(() => Cuestionario, cuestionario => cuestionario.cuestionariosRespondidos) @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[]; } \ No newline at end of file diff --git a/src/cuestionario_seccion/cuestionario_seccion.module.ts b/src/cuestionario_seccion/cuestionario_seccion.module.ts index d3635ed..75bd8fd 100644 --- a/src/cuestionario_seccion/cuestionario_seccion.module.ts +++ b/src/cuestionario_seccion/cuestionario_seccion.module.ts @@ -1,9 +1,13 @@ 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'; @Module({ - controllers: [CuestionarioSeccionController], - providers: [CuestionarioSeccionService], + imports: [TypeOrmModule.forFeature([CuestionarioSeccion])], + controllers: [CuestionarioSeccionController], + providers: [CuestionarioSeccionService], + exports:[CuestionarioSeccionService, TypeOrmModule] }) export class CuestionarioSeccionModule {} diff --git a/src/cuestionario_seccion/entities/cuestionario_seccion.entity.ts b/src/cuestionario_seccion/entities/cuestionario_seccion.entity.ts index 7bd9b70..b167615 100644 --- a/src/cuestionario_seccion/entities/cuestionario_seccion.entity.ts +++ b/src/cuestionario_seccion/entities/cuestionario_seccion.entity.ts @@ -1,4 +1,4 @@ -import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'; import { Cuestionario } from '../../cuestionario/entities/cuestionario.entity'; // Asegúrate de importar la entidad Cuestionario import { Seccion } from 'src/seccion/entities/seccion.entity'; @@ -11,11 +11,11 @@ export class CuestionarioSeccion { posicion: number; - @ManyToOne(() => Cuestionario, (cuestionario) => cuestionario.id_cuestionario) - @Column({ type: 'int' }) - id_cuestionario: number; + @ManyToOne(() => Cuestionario, (cuestionario) => cuestionario.cuestionarioSeccion) + @JoinColumn({name:'id_cuestionario'}) + cuestionario: Cuestionario; - @ManyToOne(() => Seccion, (seccion) => seccion.id_seccion) - @Column({type:'int'}) - id_seccion:number; + @ManyToOne(() => Seccion, (seccion) => seccion.cuestionarioSeccion) + @JoinColumn({name:'id_seccion'}) + seccion:Seccion; } diff --git a/src/opcion/entities/opcion.entity.ts b/src/opcion/entities/opcion.entity.ts index 3e2f2c4..386f1cd 100644 --- a/src/opcion/entities/opcion.entity.ts +++ b/src/opcion/entities/opcion.entity.ts @@ -9,7 +9,7 @@ export class Opcion { @Column({ type: String, nullable: false, length: 200, default: '' }) opcion: string; - @OneToMany(() => PreguntaOpcion, (preguntaOpcion) => preguntaOpcion.id_opcion) + @OneToMany(() => PreguntaOpcion, (preguntaOpcion) => preguntaOpcion.opcion) Preguntas: PreguntaOpcion[]; diff --git a/src/opcion/opcion.module.ts b/src/opcion/opcion.module.ts index c3533d3..b93ac82 100644 --- a/src/opcion/opcion.module.ts +++ b/src/opcion/opcion.module.ts @@ -5,15 +5,18 @@ import { PreguntaModule } from 'src/pregunta/pregunta.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Opcion } from './entities/opcion.entity'; import { TipoPregunta } from 'src/tipo_pregunta/entities/tipo_pregunta.entity'; +import { PreguntaOpcion } from 'src/pregunta_opcion/entities/pregunta_opcion.entity'; +import { Pregunta } from 'src/pregunta/entities/pregunta.entity'; @Module({ imports: [ - TypeOrmModule.forFeature([Opcion]), + TypeOrmModule.forFeature([Opcion, Pregunta, PreguntaOpcion]), PreguntaModule, TipoPregunta ], controllers: [OpcionController], providers: [OpcionService], - exports: [OpcionService], + exports: [OpcionService, TypeOrmModule], }) + export class OpcionModule {} diff --git a/src/opcion/opcion.service.ts b/src/opcion/opcion.service.ts index 590b32e..01ed1a4 100644 --- a/src/opcion/opcion.service.ts +++ b/src/opcion/opcion.service.ts @@ -2,12 +2,12 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Opcion } from './entities/opcion.entity'; - import { Pregunta } from '../pregunta/entities/pregunta.entity'; -import { PreguntaOpcion } from 'src/pregunta_opcion/entities/pregunta_opcion.entity'; import { CreateOpcionDto } from './dto/create-opcion.dto'; +import { PreguntaOpcion } from 'src/pregunta_opcion/entities/pregunta_opcion.entity'; import { UpdateOpcionDto } from './dto/update-opcion.dto'; + @Injectable() export class OpcionService { constructor( @@ -30,7 +30,7 @@ export class OpcionService { throw new NotFoundException('Pregunta no encontrada'); } - if (pregunta.id_tipo_pregunta.tipo_pregunta !== 'Cerrada') { + if (pregunta.tipo_pregunta.tipo_pregunta !== 'Cerrada') { throw new Error('Solo se pueden agregar opciones a preguntas cerradas'); } @@ -43,8 +43,8 @@ export class OpcionService { // Crear la relación entre pregunta y opción const preguntaOpcion = this.preguntaOpcionRepository.create({ - id_pregunta: { id_pregunta: preguntaId }, - id_opcion: savedOpcion, + pregunta: { id_pregunta: preguntaId }, + opcion: savedOpcion, posicion: createOpcionDto.posicion || 0, // Asignar posición }); @@ -62,17 +62,17 @@ export class OpcionService { async findAllByPregunta(preguntaId: number) { return this.preguntaOpcionRepository.find({ - where: { id_pregunta: { id_pregunta: preguntaId } }, + where: { pregunta: { id_pregunta: preguntaId } }, relations: ['opcion'], order: { posicion: 'ASC' }, }); } - async findOne(preguntaId: number, OpcionId: number) { + async findOne(preguntaId: number, id: number) { const preguntaOpcion = await this.preguntaOpcionRepository.findOne({ where: { - id_pregunta: { id_pregunta: preguntaId }, - id_opcion: { id_opcion: OpcionId }, + pregunta: { id_pregunta: preguntaId }, + opcion: { id_opcion: id }, }, relations: ['opcion'], }); @@ -81,7 +81,7 @@ export class OpcionService { throw new NotFoundException('Opción no encontrada para esta pregunta'); } - return preguntaOpcion.id_opcion; + return preguntaOpcion.opcion; } async update( @@ -92,8 +92,8 @@ export class OpcionService { // Verificar que la relación exista const preguntaOpcion = await this.preguntaOpcionRepository.findOne({ where: { - id_pregunta: { id_pregunta: preguntaId }, - id_opcion: { id_opcion: id }, + pregunta: { id_pregunta: preguntaId }, + opcion: { id_opcion: id }, }, relations: ['opcion'], }); @@ -125,8 +125,8 @@ export class OpcionService { // Verificar que la relación exista const preguntaOpcion = await this.preguntaOpcionRepository.findOne({ where: { - id_pregunta: { id_pregunta: preguntaId }, - id_opcion: { id_opcion: id }, + pregunta: { id_pregunta: preguntaId }, + opcion: { id_opcion: id }, }, }); @@ -139,7 +139,7 @@ export class OpcionService { // Eliminar la opción (si no está siendo usada en otras preguntas) const count = await this.preguntaOpcionRepository.count({ - where: { id_opcion: { id_opcion: id } }, + where: { opcion: { id_opcion: id } }, }); if (count === 0) { diff --git a/src/participante/participante.entity.ts b/src/participante/participante.entity.ts index 1bd1fd0..a46cd2e 100644 --- a/src/participante/participante.entity.ts +++ b/src/participante/participante.entity.ts @@ -1,3 +1,4 @@ +import { CuestionarioRespondido } from "src/cuestionario_respondido/entities/cuestionario_respondido.entity"; import { ParticipanteEvento } from "src/participante_evento/participante_evento.entity"; import { TipoUser } from "src/tipo_user/tipo_user.entity"; import { Column, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; @@ -13,6 +14,10 @@ export class Participante { @Column() id_tipo_user: number + + @OneToMany(() => CuestionarioRespondido, cuestionarioRespondido => cuestionarioRespondido.participante ) + cuestionarioRespondidos:CuestionarioRespondido[]; + //Relacion con tipo usuario @ManyToOne(() => TipoUser, tipoUser => tipoUser.participante) @JoinColumn({ name: 'participante_id' }) //nombre de la relacion diff --git a/src/participante/participante.module.ts b/src/participante/participante.module.ts index 912cd0c..41a0dd0 100644 --- a/src/participante/participante.module.ts +++ b/src/participante/participante.module.ts @@ -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 {} diff --git a/src/participante/participante.service.ts b/src/participante/participante.service.ts index 9eed97e..983dad0 100644 --- a/src/participante/participante.service.ts +++ b/src/participante/participante.service.ts @@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Participante } from './participante.entity'; import { Repository } from 'typeorm'; import { CreateParticipanteDto } from './dto/create-participante.dto'; -import { UpdateAdminDto } from 'src/admin/dto/update.admin.dto'; + import { UpdateParticipanteDto } from './dto/update.participante.dto'; @Injectable() diff --git a/src/participante_evento/participante_evento.module.ts b/src/participante_evento/participante_evento.module.ts index 6febf0a..d6b8665 100644 --- a/src/participante_evento/participante_evento.module.ts +++ b/src/participante_evento/participante_evento.module.ts @@ -8,6 +8,7 @@ import { Evento } from 'src/evento/evento.entity'; @Module({ imports: [TypeOrmModule.forFeature([ParticipanteEvento]), Evento], controllers: [ParticipanteEventoController], - providers: [ParticipanteEventoService] + providers: [ParticipanteEventoService], + exports:[TypeOrmModule,ParticipanteEventoService] }) export class ParticipanteEventoModule {} diff --git a/src/pregunta/dto/create-pregunta.dto.ts b/src/pregunta/dto/create-pregunta.dto.ts index ee5d15c..d518c5c 100644 --- a/src/pregunta/dto/create-pregunta.dto.ts +++ b/src/pregunta/dto/create-pregunta.dto.ts @@ -1 +1,23 @@ -export class CreatePreguntaDto {} +import { IsNotEmpty, IsString, IsBoolean, IsNumber, IsOptional, IsArray, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; +import { CreateOpcionDto } from '../../opcion/dto/create-opcion.dto'; + +export class CreatePreguntaDto { + @IsNotEmpty() + @IsString() + pregunta: string; + + @IsOptional() + @IsBoolean() + obligatoria?: boolean; + + @IsNotEmpty() + @IsNumber() + id_tipo_pregunta: number; + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CreateOpcionDto) + opciones?: CreateOpcionDto[]; +} \ No newline at end of file diff --git a/src/pregunta/dto/update-pregunta.dto.ts b/src/pregunta/dto/update-pregunta.dto.ts index 2883c39..565ae14 100644 --- a/src/pregunta/dto/update-pregunta.dto.ts +++ b/src/pregunta/dto/update-pregunta.dto.ts @@ -1,4 +1,15 @@ -import { PartialType } from '@nestjs/mapped-types'; -import { CreatePreguntaDto } from './create-pregunta.dto'; +import { IsOptional, IsString, IsBoolean, IsNumber } from 'class-validator'; -export class UpdatePreguntaDto extends PartialType(CreatePreguntaDto) {} +export class UpdatePreguntaDto { + @IsOptional() + @IsString() + pregunta?: string; + + @IsOptional() + @IsBoolean() + obligatoria?: boolean; + + @IsOptional() + @IsNumber() + id_tipo_pregunta?: number; +} \ No newline at end of file diff --git a/src/pregunta/entities/pregunta.entity.ts b/src/pregunta/entities/pregunta.entity.ts index d4c6671..6404d8d 100644 --- a/src/pregunta/entities/pregunta.entity.ts +++ b/src/pregunta/entities/pregunta.entity.ts @@ -1,33 +1,38 @@ import { PreguntaOpcion } from 'src/pregunta_opcion/entities/pregunta_opcion.entity'; +import { RespuestaParticipanteAbierta } from 'src/respuesta_participante_abierta/entities/respuesta_participante_abierta.entity'; import { SeccionPregunta } from 'src/seccion_pregunta/entities/seccion_pregunta.entity'; import { TipoPregunta } from 'src/tipo_pregunta/entities/tipo_pregunta.entity'; import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColumn } from 'typeorm'; -@Entity() +@Entity('pregunta') export class Pregunta { - @PrimaryGeneratedColumn() + @PrimaryGeneratedColumn({ name: 'id_pregunta' }) id_pregunta: number; - @Column() + @Column({ type: 'text' }) pregunta: string; - @Column({ default: 0 }) + @Column({ name: 'contador_opcion', default: 0 }) contador_opcion: number; @Column({ default: false }) obligatoria: boolean; - @ManyToOne(() => TipoPregunta, tipo => tipo.preguntas) + // Relación con TipoPregunta + @ManyToOne(() => TipoPregunta, tipoPregunta => tipoPregunta.preguntas) @JoinColumn({ name: 'id_tipo_pregunta' }) - id_tipo_pregunta: TipoPregunta; + tipo_pregunta: TipoPregunta; - @Column({ nullable: true }) - id_opcion_dependiente: number; - - @OneToMany(() => SeccionPregunta, seccionPregunta => seccionPregunta.id_pregunta) - seccionPreguntas: SeccionPregunta[]; - - @OneToMany(() => PreguntaOpcion, preguntaOpcion => preguntaOpcion.id_pregunta) + // Relación con PreguntaOpcion (tabla intermedia para opciones) + @OneToMany(() => PreguntaOpcion, preguntaOpcion => preguntaOpcion.pregunta) opciones: PreguntaOpcion[]; + + // Relación con SeccionPregunta (tabla intermedia para secciones) + @OneToMany(() => SeccionPregunta, seccionPregunta => seccionPregunta.pregunta) + seccionesPregunta: SeccionPregunta[]; + + // Relación con RespuestasAbiertas + @OneToMany(() => RespuestaParticipanteAbierta, respuestaAbierta => respuestaAbierta.pregunta) + respuestasAbiertas: RespuestaParticipanteAbierta[]; } \ No newline at end of file diff --git a/src/pregunta/pregunta.controller.ts b/src/pregunta/pregunta.controller.ts index c5672b4..f3a57f0 100644 --- a/src/pregunta/pregunta.controller.ts +++ b/src/pregunta/pregunta.controller.ts @@ -1,34 +1,110 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Post, + Body, + Get, + Param, + Put, + Delete, + HttpStatus, + HttpException, + Query +} from '@nestjs/common'; import { PreguntaService } from './pregunta.service'; +import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger'; import { CreatePreguntaDto } from './dto/create-pregunta.dto'; import { UpdatePreguntaDto } from './dto/update-pregunta.dto'; -@Controller('pregunta') +@ApiTags('Preguntas') +@Controller('preguntas') export class PreguntaController { constructor(private readonly preguntaService: PreguntaService) {} @Post() - create(@Body() createPreguntaDto: CreatePreguntaDto) { - return this.preguntaService.create(createPreguntaDto); + @ApiOperation({ summary: 'Crear una nueva pregunta' }) + @ApiResponse({ status: 201, description: 'Pregunta creada exitosamente' }) + @ApiResponse({ status: 400, description: 'Datos de entrada inválidos' }) + @ApiResponse({ status: 404, description: 'Tipo de pregunta no encontrado' }) + async create(@Body() createPreguntaDto: CreatePreguntaDto) { + try { + return await this.preguntaService.create(createPreguntaDto); + } catch (error) { + throw new HttpException({ + status: HttpStatus.BAD_REQUEST, + error: error.message, + }, HttpStatus.BAD_REQUEST); + } } @Get() - findAll() { + @ApiOperation({ summary: 'Obtener todas las preguntas' }) + @ApiQuery({ name: 'tipo', required: false, description: 'Filtrar por tipo de pregunta' }) + @ApiResponse({ status: 200, description: 'Lista de preguntas' }) + async findAll(@Query('tipo') tipoPreguntaId?: number) { + if (tipoPreguntaId) { + return this.preguntaService.findAllByTipo(tipoPreguntaId); + } return this.preguntaService.findAll(); } @Get(':id') - findOne(@Param('id') id: string) { - return this.preguntaService.findOne(+id); + @ApiOperation({ summary: 'Obtener una pregunta específica' }) + @ApiParam({ name: 'id', description: 'ID de la pregunta' }) + @ApiResponse({ status: 200, description: 'Pregunta encontrada' }) + @ApiResponse({ status: 404, description: 'Pregunta no encontrada' }) + async findOne(@Param('id') id: string) { + const pregunta = await this.preguntaService.findOneWithRelations(+id); + if (!pregunta) { + throw new HttpException('Pregunta no encontrada', HttpStatus.NOT_FOUND); + } + return pregunta; } - @Patch(':id') - update(@Param('id') id: string, @Body() updatePreguntaDto: UpdatePreguntaDto) { - return this.preguntaService.update(+id, updatePreguntaDto); + @Put(':id') + @ApiOperation({ summary: 'Actualizar una pregunta' }) + @ApiParam({ name: 'id', description: 'ID de la pregunta a actualizar' }) + @ApiResponse({ status: 200, description: 'Pregunta actualizada' }) + @ApiResponse({ status: 404, description: 'Pregunta no encontrada' }) + async update( + @Param('id') id: string, + @Body() updatePreguntaDto: UpdatePreguntaDto, + ) { + try { + return await this.preguntaService.update(+id, updatePreguntaDto); + } catch (error) { + throw new HttpException({ + status: HttpStatus.BAD_REQUEST, + error: error.message, + }, HttpStatus.BAD_REQUEST); + } } @Delete(':id') - remove(@Param('id') id: string) { - return this.preguntaService.remove(+id); + @ApiOperation({ summary: 'Eliminar una pregunta' }) + @ApiParam({ name: 'id', description: 'ID de la pregunta a eliminar' }) + @ApiResponse({ status: 200, description: 'Pregunta eliminada' }) + @ApiResponse({ status: 404, description: 'Pregunta no encontrada' }) + async remove(@Param('id') id: string) { + try { + return await this.preguntaService.remove(+id); + } catch (error) { + throw new HttpException({ + status: HttpStatus.BAD_REQUEST, + error: error.message, + }, HttpStatus.BAD_REQUEST); + } } -} + + @Get(':id/opciones') + @ApiOperation({ summary: 'Obtener opciones de una pregunta' }) + @ApiParam({ name: 'id', description: 'ID de la pregunta' }) + @ApiResponse({ status: 200, description: 'Opciones de la pregunta' }) + @ApiResponse({ status: 404, description: 'Pregunta no encontrada' }) + async getOpciones(@Param('id') id: string) { + const pregunta = await this.preguntaService.findOneWithRelations(+id); + if (!pregunta) { + throw new HttpException('Pregunta no encontrada', HttpStatus.NOT_FOUND); + } + return pregunta.opciones; + } +} \ No newline at end of file diff --git a/src/pregunta/pregunta.module.ts b/src/pregunta/pregunta.module.ts index 905ec29..910f916 100644 --- a/src/pregunta/pregunta.module.ts +++ b/src/pregunta/pregunta.module.ts @@ -1,9 +1,19 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Pregunta } from './entities/pregunta.entity'; import { PreguntaService } from './pregunta.service'; import { PreguntaController } from './pregunta.controller'; +import { OpcionModule } from '../opcion/opcion.module'; +import { PreguntaOpcion } from 'src/pregunta_opcion/entities/pregunta_opcion.entity'; +import { TipoPregunta } from 'src/tipo_pregunta/entities/tipo_pregunta.entity'; @Module({ + imports: [ + TypeOrmModule.forFeature([Pregunta, TipoPregunta, PreguntaOpcion]), + OpcionModule, + ], controllers: [PreguntaController], providers: [PreguntaService], + exports: [PreguntaService], }) -export class PreguntaModule {} +export class PreguntaModule {} \ No newline at end of file diff --git a/src/pregunta/pregunta.service.ts b/src/pregunta/pregunta.service.ts index b196cc8..bc2604c 100644 --- a/src/pregunta/pregunta.service.ts +++ b/src/pregunta/pregunta.service.ts @@ -1,26 +1,157 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Pregunta } from './entities/pregunta.entity'; +import { OpcionService } from '../opcion/opcion.service'; import { CreatePreguntaDto } from './dto/create-pregunta.dto'; -import { UpdatePreguntaDto } from './dto/update-pregunta.dto'; +import { CreateOpcionDto } from '../opcion/dto/create-opcion.dto'; +import { TipoPregunta } from 'src/tipo_pregunta/entities/tipo_pregunta.entity'; @Injectable() export class PreguntaService { - create(createPreguntaDto: CreatePreguntaDto) { - return 'This action adds a new pregunta'; + + constructor( + @InjectRepository(Pregunta) + private readonly preguntaRepository: Repository, + @InjectRepository(TipoPregunta) + private readonly tipoPreguntaRepository: Repository, + private readonly opcionService: OpcionService, + ) {} + + async create(createPreguntaDto: CreatePreguntaDto) { + // Verificar que el tipo de pregunta exista + + //Se espera que solo exista el ripo de pregunta abierta y cerrada + const tipoPregunta = await this.tipoPreguntaRepository.findOne({ + where: { id_tipo: createPreguntaDto.id_tipo_pregunta }, + }); + + if (!tipoPregunta) { + throw new NotFoundException( + `Tipo de pregunta con ID ${createPreguntaDto.id_tipo_pregunta} no encontrado`, + ); + } + + // Crear la pregunta base + const pregunta = this.preguntaRepository.create({ + pregunta: createPreguntaDto.pregunta, + obligatoria: createPreguntaDto.obligatoria || false, + tipo_pregunta: tipoPregunta, + }); + + const preguntaGuardada = await this.preguntaRepository.save(pregunta); + + // Si es pregunta cerrada y tiene opciones, crearlas + if ( + tipoPregunta.tipo_pregunta === 'Cerrada' && + createPreguntaDto.opciones && + createPreguntaDto.opciones.length > 0 + ) { + await this.agregarOpcionesAPregunta( + preguntaGuardada.id_pregunta, + createPreguntaDto.opciones, + ); + + // Actualizar el contador de opciones + await this.preguntaRepository.update( + { id_pregunta: preguntaGuardada.id_pregunta }, + { contador_opcion: createPreguntaDto.opciones.length }, + ); + } + + return this.findOneWithRelations(preguntaGuardada.id_pregunta); + } + + private async agregarOpcionesAPregunta( + preguntaId: number, + opciones: CreateOpcionDto[], + ) { + for (const opcionDto of opciones) { + await this.opcionService.create(preguntaId, opcionDto); + } + } + + async findOneWithRelations(id: number) { + return this.preguntaRepository.findOne({ + where: { id_pregunta: id }, + relations: [ + 'tipo_pregunta', + 'opciones', + 'opciones.opcion', + 'seccionesPregunta', + 'seccionesPregunta.seccion', + ], + }); + } + + async findAllByTipo(tipoPreguntaId: number) { + return this.preguntaRepository.find({ + where: { tipo_pregunta: { id_tipo: tipoPreguntaId } }, + relations: ['opciones', 'opciones.opcion'], + }); + } + + async remove(id: number) { + const pregunta = await this.preguntaRepository.findOne({ + where: { id_pregunta: id }, + relations: ['opciones'], + }); + + if (!pregunta) { + throw new NotFoundException(`Pregunta con ID ${id} no encontrada`); + } + + // Eliminar opciones primero si existen + if (pregunta.opciones && pregunta.opciones.length > 0) { + for (const opcion of pregunta.opciones) { + await this.opcionService.remove(id, opcion.id_pregunta_opcion); + } + } + + // Eliminar la pregunta + await this.preguntaRepository.delete(id); + + return { message: 'Pregunta eliminada correctamente' }; + } + + async update(id: number, updatePreguntaDto: any) { + const pregunta = await this.preguntaRepository.findOne({ + where: { id_pregunta: id }, + }); + + if (!pregunta) { + throw new NotFoundException(`Pregunta con ID ${id} no encontrada`); + } + + // Actualizar campos básicos + if (updatePreguntaDto.pregunta !== undefined) { + pregunta.pregunta = updatePreguntaDto.pregunta; + } + + if (updatePreguntaDto.obligatoria !== undefined) { + pregunta.obligatoria = updatePreguntaDto.obligatoria; + } + + // Si se cambia el tipo de pregunta + if (updatePreguntaDto.id_tipo_pregunta !== undefined) { + const tipoPregunta = await this.tipoPreguntaRepository.findOne({ + where: { id_tipo: updatePreguntaDto.id_tipo_pregunta }, + }); + + if (!tipoPregunta) { + throw new NotFoundException( + `Tipo de pregunta con ID ${updatePreguntaDto.id_tipo_pregunta} no encontrado`, + ); + } + + pregunta.tipo_pregunta = tipoPregunta; + } + + return this.preguntaRepository.save(pregunta); } findAll() { - return `This action returns all pregunta`; + return this.preguntaRepository.find(); } - findOne(id: number) { - return `This action returns a #${id} pregunta`; - } - - update(id: number, updatePreguntaDto: UpdatePreguntaDto) { - return `This action updates a #${id} pregunta`; - } - - remove(id: number) { - return `This action removes a #${id} pregunta`; - } -} +} \ No newline at end of file diff --git a/src/pregunta_opcion/entities/pregunta_opcion.entity.ts b/src/pregunta_opcion/entities/pregunta_opcion.entity.ts index 8cbd89c..22b125f 100644 --- a/src/pregunta_opcion/entities/pregunta_opcion.entity.ts +++ b/src/pregunta_opcion/entities/pregunta_opcion.entity.ts @@ -1,25 +1,31 @@ import { Opcion } from 'src/opcion/entities/opcion.entity'; import { Pregunta } from 'src/pregunta/entities/pregunta.entity'; import { RespuestaParticipanteCerrada } from 'src/respuesta_participante_cerrada/entities/respuesta_participante_cerrada.entity'; -import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany } from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColumn } from 'typeorm'; -@Entity() +@Entity('pregunta_opcion') export class PreguntaOpcion { - @PrimaryGeneratedColumn() + @PrimaryGeneratedColumn({ name: 'id_pregunta_opcion' }) id_pregunta_opcion: number; @Column() posicion: number; - @ManyToOne(() => Pregunta, pregunta => pregunta.id_pregunta) - @Column() - id_pregunta: Pregunta; + // Relación con Pregunta + @ManyToOne(() => Pregunta, pregunta => pregunta.opciones) + @JoinColumn({ name: 'id_pregunta' }) + pregunta: Pregunta; - @ManyToOne(() => Opcion, opcion => opcion.id_opcion) - @Column() - id_opcion: Opcion; + // Relación con Opcion + @ManyToOne(() => Opcion, opcion => opcion.Preguntas) + @JoinColumn({ name: 'id_opcion' }) + opcion: Opcion; - @OneToMany(()=> RespuestaParticipanteCerrada, respuestaParticipanteCerrada=>respuestaParticipanteCerrada.id_pregunta_opcion) - respuestaParticipanteCerrada:RespuestaParticipanteCerrada[]; + // Nueva relación con RespuestaParticipanteCerrada + @OneToMany( + () => RespuestaParticipanteCerrada, + respuestaCerrada => respuestaCerrada.preguntaOpcion + ) + respuestasCerradas: RespuestaParticipanteCerrada[]; } \ No newline at end of file diff --git a/src/pregunta_opcion/pregunta_opcion.module.ts b/src/pregunta_opcion/pregunta_opcion.module.ts index 6eff7d3..5703832 100644 --- a/src/pregunta_opcion/pregunta_opcion.module.ts +++ b/src/pregunta_opcion/pregunta_opcion.module.ts @@ -1,9 +1,13 @@ import { Module } from '@nestjs/common'; import { PreguntaOpcionService } from './pregunta_opcion.service'; import { PreguntaOpcionController } from './pregunta_opcion.controller'; +import { PreguntaOpcion } from './entities/pregunta_opcion.entity'; +import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ - controllers: [PreguntaOpcionController], - providers: [PreguntaOpcionService], + imports: [TypeOrmModule.forFeature([PreguntaOpcion])], + controllers: [PreguntaOpcionController], + providers: [PreguntaOpcionService], + exports:[PreguntaOpcionService, TypeOrmModule] }) export class PreguntaOpcionModule {} diff --git a/src/respuesta_participante_abierta/entities/respuesta_participante_abierta.entity.ts b/src/respuesta_participante_abierta/entities/respuesta_participante_abierta.entity.ts index 1990bee..d278ff4 100644 --- a/src/respuesta_participante_abierta/entities/respuesta_participante_abierta.entity.ts +++ b/src/respuesta_participante_abierta/entities/respuesta_participante_abierta.entity.ts @@ -2,29 +2,45 @@ import { CuestionarioRespondido } from 'src/cuestionario_respondido/entities/cue import { Pregunta } from 'src/pregunta/entities/pregunta.entity'; import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'; - @Entity('respuesta_participante_abierta') export class RespuestaParticipanteAbierta { - @PrimaryGeneratedColumn() - idRespuestaParticipanteAbierta: number; + @PrimaryGeneratedColumn({ + name: 'id_respuesta_participante_abierta', + type: 'integer' + }) + id: number; - @Column({ type: 'text' }) + @Column({ + type: 'text', + nullable: false + }) respuesta: string; // Relación con CuestionarioRespondido @ManyToOne( () => CuestionarioRespondido, - cuestionarioRespondido => cuestionarioRespondido.idCuestionarioRespondido + (cuestionarioRespondido) => cuestionarioRespondido.respuestasAbiertas ) - @JoinColumn({ name: 'id_cuestionario_respondido' }) - id_cuestionario_respondido: CuestionarioRespondido; + @JoinColumn({ + name: 'id_cuestionario_respondido' + }) + cuestionarioRespondido: CuestionarioRespondido; + // Relación con Pregunta @ManyToOne( () => Pregunta, - pregunta => pregunta.id_pregunta + (pregunta) => pregunta.respuestasAbiertas ) - @JoinColumn({ name: 'id_pregunta' }) - id_pregunta: Pregunta; - + @JoinColumn({ + name: 'id_pregunta' + }) + pregunta: Pregunta; + /* Campo adicional para tracking + @Column({ + name: 'fecha_respuesta', + type: 'timestamp', + default: () => 'CURRENT_TIMESTAMP' + }) + fechaRespuesta: Date;*/ } \ No newline at end of file diff --git a/src/respuesta_participante_abierta/respuesta_participante_abierta.module.ts b/src/respuesta_participante_abierta/respuesta_participante_abierta.module.ts index 125f0d5..9e7cce1 100644 --- a/src/respuesta_participante_abierta/respuesta_participante_abierta.module.ts +++ b/src/respuesta_participante_abierta/respuesta_participante_abierta.module.ts @@ -1,9 +1,15 @@ import { Module } from '@nestjs/common'; import { RespuestaParticipanteAbiertaService } from './respuesta_participante_abierta.service'; import { RespuestaParticipanteAbiertaController } from './respuesta_participante_abierta.controller'; +import { RespuestaParticipanteAbierta } from './entities/respuesta_participante_abierta.entity'; +import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ - controllers: [RespuestaParticipanteAbiertaController], - providers: [RespuestaParticipanteAbiertaService], + imports: [ + TypeOrmModule.forFeature([RespuestaParticipanteAbierta]), + ], + controllers: [RespuestaParticipanteAbiertaController], + providers: [RespuestaParticipanteAbiertaService], + exports: [RespuestaParticipanteAbiertaService, TypeOrmModule], }) export class RespuestaParticipanteAbiertaModule {} diff --git a/src/respuesta_participante_cerrada/entities/respuesta_participante_cerrada.entity.ts b/src/respuesta_participante_cerrada/entities/respuesta_participante_cerrada.entity.ts index ba96744..6613d41 100644 --- a/src/respuesta_participante_cerrada/entities/respuesta_participante_cerrada.entity.ts +++ b/src/respuesta_participante_cerrada/entities/respuesta_participante_cerrada.entity.ts @@ -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.respuestasCerradas) @JoinColumn({ name: 'id_pregunta_opcion' }) - id_pregunta_opcion: PreguntaOpcion; + preguntaOpcion: PreguntaOpcion; } \ No newline at end of file diff --git a/src/respuesta_participante_cerrada/respuesta_participante_cerrada.module.ts b/src/respuesta_participante_cerrada/respuesta_participante_cerrada.module.ts index 5fdf968..1231294 100644 --- a/src/respuesta_participante_cerrada/respuesta_participante_cerrada.module.ts +++ b/src/respuesta_participante_cerrada/respuesta_participante_cerrada.module.ts @@ -1,9 +1,13 @@ import { Module } from '@nestjs/common'; import { RespuestaParticipanteCerradaService } from './respuesta_participante_cerrada.service'; import { RespuestaParticipanteCerradaController } from './respuesta_participante_cerrada.controller'; +import { RespuestaParticipanteCerrada } from './entities/respuesta_participante_cerrada.entity'; +import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ - controllers: [RespuestaParticipanteCerradaController], - providers: [RespuestaParticipanteCerradaService], + imports: [TypeOrmModule.forFeature([RespuestaParticipanteCerrada])], + controllers: [RespuestaParticipanteCerradaController], + providers: [RespuestaParticipanteCerradaService], + exports:[RespuestaParticipanteCerradaService, TypeOrmModule] }) export class RespuestaParticipanteCerradaModule {} diff --git a/src/seccion/dto/create-seccion.dto.ts b/src/seccion/dto/create-seccion.dto.ts index 4753a9c..ca220cb 100644 --- a/src/seccion/dto/create-seccion.dto.ts +++ b/src/seccion/dto/create-seccion.dto.ts @@ -1 +1,30 @@ -export class CreateSeccionDto {} +import { IsNotEmpty, IsString, IsOptional, IsArray, ValidateNested, IsInt } from 'class-validator'; +import { Type } from 'class-transformer'; +import { CreatePreguntaDto } from '../../pregunta/dto/create-pregunta.dto'; + +export class CreateSeccionDto { + @IsNotEmpty() + @IsString() + titulo: string; + + @IsOptional() + @IsString() + descripcion?: string; + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => PreguntaEnSeccionDto) + preguntas?: PreguntaEnSeccionDto[]; +} + +export class PreguntaEnSeccionDto { + @IsNotEmpty() + @IsInt() + posicion: number; + + @IsNotEmpty() + @ValidateNested() + @Type(() => CreatePreguntaDto) + pregunta: CreatePreguntaDto; +} \ No newline at end of file diff --git a/src/seccion/entities/seccion.entity.ts b/src/seccion/entities/seccion.entity.ts index ee2701d..8a4818b 100644 --- a/src/seccion/entities/seccion.entity.ts +++ b/src/seccion/entities/seccion.entity.ts @@ -16,9 +16,9 @@ export class Seccion { @Column({ type: 'text', nullable: true }) titulo: string; - @OneToMany(() => CuestionarioSeccion,(cuestionario_seccion) => cuestionario_seccion.id_seccion ) - seccion:CuestionarioSeccion[]; + @OneToMany(() => CuestionarioSeccion,(cuestionario_seccion) => cuestionario_seccion.seccion ) + cuestionarioSeccion:CuestionarioSeccion[]; - @OneToMany(()=> SeccionPregunta, (seccion_pregunta)=> seccion_pregunta.id_seccion) + @OneToMany(()=> SeccionPregunta, (seccion_pregunta)=> seccion_pregunta.seccion) seccion_pregunta:SeccionPregunta[]; } diff --git a/src/seccion/seccion.controller.ts b/src/seccion/seccion.controller.ts index f3e3dcf..cce2cee 100644 --- a/src/seccion/seccion.controller.ts +++ b/src/seccion/seccion.controller.ts @@ -1,34 +1,166 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Post, + Body, + Get, + Param, + Put, + Delete, + HttpStatus, + HttpException, + Query, +} from '@nestjs/common'; import { SeccionService } from './seccion.service'; -import { CreateSeccionDto } from './dto/create-seccion.dto'; +import { CreateSeccionDto, PreguntaEnSeccionDto } from './dto/create-seccion.dto'; import { UpdateSeccionDto } from './dto/update-seccion.dto'; +import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiBody } from '@nestjs/swagger'; -@Controller('seccion') +@ApiTags('Secciones') +@Controller('secciones') export class SeccionController { constructor(private readonly seccionService: SeccionService) {} @Post() - create(@Body() createSeccionDto: CreateSeccionDto) { - return this.seccionService.create(createSeccionDto); + @ApiOperation({ summary: 'Crear una nueva sección con preguntas' }) + @ApiBody({ type: CreateSeccionDto }) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Sección creada exitosamente con sus preguntas' + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Datos de entrada inválidos' + }) + async create(@Body() createSeccionDto: CreateSeccionDto) { + try { + return await this.seccionService.create(createSeccionDto); + } catch (error) { + throw new HttpException({ + status: HttpStatus.BAD_REQUEST, + error: error.message, + }, HttpStatus.BAD_REQUEST); + } } @Get() - findAll() { + @ApiOperation({ summary: 'Obtener todas las secciones' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Lista de secciones obtenida exitosamente' + }) + async findAll() { return this.seccionService.findAll(); } @Get(':id') - findOne(@Param('id') id: string) { - return this.seccionService.findOne(+id); + @ApiOperation({ summary: 'Obtener una sección específica con sus preguntas' }) + @ApiParam({ name: 'id', description: 'ID de la sección' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Sección encontrada con todas sus relaciones' + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Sección no encontrada' + }) + async findOne(@Param('id') id: string) { + const seccion = await this.seccionService.findOneWithRelations(+id); + if (!seccion) { + throw new HttpException('Sección no encontrada', HttpStatus.NOT_FOUND); + } + return seccion; } - @Patch(':id') - update(@Param('id') id: string, @Body() updateSeccionDto: UpdateSeccionDto) { - return this.seccionService.update(+id, updateSeccionDto); + @Put(':id') + @ApiOperation({ summary: 'Actualizar los metadatos de una sección' }) + @ApiParam({ name: 'id', description: 'ID de la sección a actualizar' }) + @ApiBody({ type: UpdateSeccionDto }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Sección actualizada exitosamente' + }) + async update( + @Param('id') id: string, + @Body() updateSeccionDto: UpdateSeccionDto, + ) { + try { + return await this.seccionService.update(+id, updateSeccionDto); + } catch (error) { + throw new HttpException({ + status: HttpStatus.BAD_REQUEST, + error: error.message, + }, HttpStatus.BAD_REQUEST); + } } @Delete(':id') - remove(@Param('id') id: string) { - return this.seccionService.remove(+id); + @ApiOperation({ summary: 'Eliminar una sección y sus relaciones' }) + @ApiParam({ name: 'id', description: 'ID de la sección a eliminar' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Sección eliminada exitosamente' + }) + async remove(@Param('id') id: string) { + try { + return await this.seccionService.remove(+id); + } catch (error) { + throw new HttpException({ + status: HttpStatus.BAD_REQUEST, + error: error.message, + }, HttpStatus.BAD_REQUEST); + } } -} + + @Post(':id/preguntas') + @ApiOperation({ summary: 'Agregar una nueva pregunta a la sección' }) + @ApiParam({ name: 'id', description: 'ID de la sección' }) + @ApiBody({ type: PreguntaEnSeccionDto }) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Pregunta agregada a la sección exitosamente' + }) + async agregarPregunta( + @Param('id') seccionId: string, + @Body() preguntaDto: PreguntaEnSeccionDto, + ) { + try { + return await this.seccionService.agregarPreguntaExistente( + +seccionId, + preguntaDto.pregunta.id_tipo_pregunta, + preguntaDto.posicion, + ); + } catch (error) { + throw new HttpException({ + status: error.status || HttpStatus.BAD_REQUEST, + error: error.message, + }, error.status || HttpStatus.BAD_REQUEST); + } + } + + @Delete(':seccionId/preguntas/:preguntaId') + @ApiOperation({ summary: 'Remover una pregunta de la sección' }) + @ApiParam({ name: 'seccionId', description: 'ID de la sección' }) + @ApiParam({ name: 'preguntaId', description: 'ID de la pregunta a remover' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Pregunta removida de la sección exitosamente' + }) + + + async removerPregunta( + @Param('seccionId') seccionId: string, + @Param('preguntaId') preguntaId: string, + ) { + try { + return await this.seccionService.eliminarPreguntaDeSeccion( + +seccionId, + +preguntaId, + ); + } catch (error) { + throw new HttpException({ + status: error.status || HttpStatus.BAD_REQUEST, + error: error.message, + }, error.status || HttpStatus.BAD_REQUEST); + } + } +} \ No newline at end of file diff --git a/src/seccion/seccion.module.ts b/src/seccion/seccion.module.ts index a3b53de..a8dbf73 100644 --- a/src/seccion/seccion.module.ts +++ b/src/seccion/seccion.module.ts @@ -1,9 +1,18 @@ import { Module } from '@nestjs/common'; import { SeccionService } from './seccion.service'; import { SeccionController } from './seccion.controller'; +import { Seccion } from './entities/seccion.entity'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Pregunta } from 'src/pregunta/entities/pregunta.entity'; +import { SeccionPregunta } from 'src/seccion_pregunta/entities/seccion_pregunta.entity'; +import { PreguntaModule } from 'src/pregunta/pregunta.module'; @Module({ - controllers: [SeccionController], - providers: [SeccionService], + imports: [ + TypeOrmModule.forFeature([Seccion, SeccionPregunta]), + PreguntaModule], + controllers: [SeccionController], + providers: [SeccionService], + exports:[TypeOrmModule,SeccionService] }) export class SeccionModule {} diff --git a/src/seccion/seccion.service.ts b/src/seccion/seccion.service.ts index 789091b..3755d64 100644 --- a/src/seccion/seccion.service.ts +++ b/src/seccion/seccion.service.ts @@ -1,26 +1,177 @@ -import { Injectable } from '@nestjs/common'; -import { CreateSeccionDto } from './dto/create-seccion.dto'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DeleteResult, Repository } from 'typeorm'; +import { Seccion } from './entities/seccion.entity'; +import { PreguntaService } from '../pregunta/pregunta.service'; +import { CreateSeccionDto, PreguntaEnSeccionDto } from './dto/create-seccion.dto'; +import { Pregunta } from '../pregunta/entities/pregunta.entity'; +import { SeccionPregunta } from 'src/seccion_pregunta/entities/seccion_pregunta.entity'; import { UpdateSeccionDto } from './dto/update-seccion.dto'; @Injectable() export class SeccionService { - create(createSeccionDto: CreateSeccionDto) { - return 'This action adds a new seccion'; + constructor( + @InjectRepository(Seccion) + private readonly seccionRepository: Repository, + @InjectRepository(SeccionPregunta) + private readonly seccionPreguntaRepository: Repository, + private readonly preguntaService: PreguntaService, + ) {} + + async create(createSeccionDto: CreateSeccionDto) { + // Crear la sección base + const seccion = this.seccionRepository.create({ + titulo: createSeccionDto.titulo, + descripcion: createSeccionDto.descripcion, + contador_pregunta: createSeccionDto.preguntas?.length || 0, + }); + + const seccionGuardada = await this.seccionRepository.save(seccion); + + // Si hay preguntas, crearlas y asociarlas + if (createSeccionDto.preguntas && createSeccionDto.preguntas.length > 0) { + await this.agregarPreguntasASeccion( + seccionGuardada.id_seccion, + createSeccionDto.preguntas, + ); + } + + return this.findOneWithRelations(seccionGuardada.id_seccion); } - findAll() { - return `This action returns all seccion`; + private async agregarPreguntasASeccion( + seccionId: number, + preguntas: PreguntaEnSeccionDto[], // Tipo actualizado + ) { + for (const { posicion, pregunta } of preguntas) { + const preguntaCreada = await this.preguntaService.create(pregunta); + + const seccionPregunta = this.seccionPreguntaRepository.create({ + posicion, + seccion: { id_seccion: seccionId }, + pregunta: { id_pregunta: preguntaCreada?.id_pregunta }, + }); + + await this.seccionPreguntaRepository.save(seccionPregunta); + } } - findOne(id: number) { - return `This action returns a #${id} seccion`; + async findOneWithRelations(id: number) { + return this.seccionRepository.findOne({ + where: { id_seccion: id }, + relations: [ + 'seccionPreguntas', + 'seccionPreguntas.pregunta', + 'seccionPreguntas.pregunta.tipoPregunta', + 'seccionPreguntas.pregunta.opciones', + 'seccionPreguntas.pregunta.opciones.opcion', + ], + }); } - update(id: number, updateSeccionDto: UpdateSeccionDto) { - return `This action updates a #${id} seccion`; + async agregarPreguntaExistente( + seccionId: number, + preguntaId: number, + posicion: number, + ) { + // Verificar que existan ambos elementos + const seccion = await this.seccionRepository.findOne({ + where: { id_seccion: seccionId }, + }); + if (!seccion) { + throw new NotFoundException(`Sección con ID ${seccionId} no encontrada`); + } + + const pregunta = await this.preguntaService.findOneWithRelations(preguntaId); + if (!pregunta) { + throw new NotFoundException(`Pregunta con ID ${preguntaId} no encontrada`); + } + + // Crear la relación + const seccionPregunta = this.seccionPreguntaRepository.create({ + posicion, + seccion: { id_seccion: seccionId }, + pregunta: { id_pregunta: preguntaId }, + }); + + await this.seccionPreguntaRepository.save(seccionPregunta); + + // Actualizar contador de preguntas + await this.seccionRepository.increment( + { id_seccion: seccionId }, + 'contador_pregunta', + 1, + ); + + return this.findOneWithRelations(seccionId); } - remove(id: number) { - return `This action removes a #${id} seccion`; + async eliminarPreguntaDeSeccion(seccionId: number, preguntaId: number) { + const relacion = await this.seccionPreguntaRepository.findOne({ + where: { + seccion: { id_seccion: seccionId }, + pregunta: { id_pregunta: preguntaId }, + }, + }); + + if (!relacion) { + throw new NotFoundException( + 'La pregunta no está asociada a esta sección', + ); + } + + await this.seccionPreguntaRepository.remove(relacion); + + // Actualizar contador de preguntas + await this.seccionRepository.decrement( + { id_seccion: seccionId }, + 'contador_pregunta', + 1, + ); + + return { success: true }; } -} + + async findAll(): Promise { + return this.seccionRepository.find({ + relations: ['seccionPreguntas', 'seccionPreguntas.pregunta'], + }); + } + + async update(id: number, updateSeccionDto: UpdateSeccionDto): Promise { + const seccion = await this.seccionRepository.findOne({ + where: { id_seccion: id } + }); + + if (!seccion) { + throw new NotFoundException(`Sección con ID ${id} no encontrada`); + } + + if (updateSeccionDto.titulo !== undefined) { + seccion.titulo = updateSeccionDto.titulo; + } + + if (updateSeccionDto.descripcion !== undefined) { + seccion.descripcion = updateSeccionDto.descripcion; + } + + return this.seccionRepository.save(seccion); + } + + async remove(id: number): Promise { + // Primero eliminamos las relaciones con preguntas (esto sigue siendo asíncrono) + this.seccionPreguntaRepository.delete({ seccion: { id_seccion: id } }) + .catch(err => { + throw new Error(`Error al eliminar relaciones: ${err.message}`); + }); + + // Eliminación sincrónica usando delete (devuelve DeleteResult directamente) + const result = this.seccionRepository.delete(id); + + if ((await result).affected === 0) { + throw new NotFoundException(`Sección con ID ${id} no encontrada`); + } + + return result; + } +} \ No newline at end of file diff --git a/src/seccion_pregunta/entities/seccion_pregunta.entity.ts b/src/seccion_pregunta/entities/seccion_pregunta.entity.ts index e476f4f..d6303e1 100644 --- a/src/seccion_pregunta/entities/seccion_pregunta.entity.ts +++ b/src/seccion_pregunta/entities/seccion_pregunta.entity.ts @@ -1,21 +1,20 @@ import { Pregunta } from 'src/pregunta/entities/pregunta.entity'; import { Seccion } from 'src/seccion/entities/seccion.entity'; -import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'; - -@Entity('seccion_pregunta') +@Entity() export class SeccionPregunta { @PrimaryGeneratedColumn() id_seccion_pregunta: number; - @Column({ type: 'tinyint' }) + @Column() posicion: number; - @ManyToOne(() => Pregunta, (pregunta) => pregunta.id_pregunta) - @Column({ type: 'int' }) - id_pregunta: number; + @ManyToOne(() => Seccion, seccion => seccion.seccion_pregunta) + @JoinColumn({name:'id_seccion'}) + seccion: Seccion; - @ManyToOne(() => Seccion, (seccion) => seccion.id_seccion) - @Column({ type: 'int' }) - id_seccion: number; -} + @ManyToOne(() => Pregunta, pregunta => pregunta.seccionesPregunta) + @JoinColumn({name:'id_pregunta'}) + pregunta: Pregunta; +} \ No newline at end of file diff --git a/src/seccion_pregunta/seccion_pregunta.module.ts b/src/seccion_pregunta/seccion_pregunta.module.ts index d911c60..a844f9c 100644 --- a/src/seccion_pregunta/seccion_pregunta.module.ts +++ b/src/seccion_pregunta/seccion_pregunta.module.ts @@ -1,9 +1,13 @@ import { Module } from '@nestjs/common'; import { SeccionPreguntaService } from './seccion_pregunta.service'; import { SeccionPreguntaController } from './seccion_pregunta.controller'; +import { SeccionPregunta } from './entities/seccion_pregunta.entity'; +import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ + imports: [TypeOrmModule.forFeature([SeccionPregunta])], controllers: [SeccionPreguntaController], providers: [SeccionPreguntaService], + exports:[SeccionPreguntaService, TypeOrmModule] }) export class SeccionPreguntaModule {} diff --git a/src/tipo_cuestionario/entities/tipo_cuestionario.entity.ts b/src/tipo_cuestionario/entities/tipo_cuestionario.entity.ts index 3a2ccf5..b891d3e 100644 --- a/src/tipo_cuestionario/entities/tipo_cuestionario.entity.ts +++ b/src/tipo_cuestionario/entities/tipo_cuestionario.entity.ts @@ -1,5 +1,5 @@ import { Cuestionario } from 'src/cuestionario/entities/cuestionario.entity'; -import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, ManyToOne } from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, ManyToOne, OneToMany } from 'typeorm'; @Entity('tipo_cuestionario') export class TipoCuestionario { @@ -9,6 +9,6 @@ export class TipoCuestionario { @Column({ type: 'varchar', length: 30 }) tipo_cuestionario: string; - @ManyToOne(()=> Cuestionario,(cuestionario)=> cuestionario.id_tipo_cuestionario) - cuestionario:Cuestionario[]; + @OneToMany(()=> Cuestionario,(cuestionario)=> cuestionario.tipoCuestionario) + cuestionarios:Cuestionario[]; } diff --git a/src/tipo_cuestionario/tipo_cuestionario.module.ts b/src/tipo_cuestionario/tipo_cuestionario.module.ts index 00cf32e..bb34ef9 100644 --- a/src/tipo_cuestionario/tipo_cuestionario.module.ts +++ b/src/tipo_cuestionario/tipo_cuestionario.module.ts @@ -1,9 +1,13 @@ import { Module } from '@nestjs/common'; import { TipoCuestionarioService } from './tipo_cuestionario.service'; import { TipoCuestionarioController } from './tipo_cuestionario.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { TipoCuestionario } from './entities/tipo_cuestionario.entity'; @Module({ + imports: [TypeOrmModule.forFeature([TipoCuestionario])], controllers: [TipoCuestionarioController], providers: [TipoCuestionarioService], + exports:[TipoCuestionarioService, TypeOrmModule] }) export class TipoCuestionarioModule {} diff --git a/src/tipo_pregunta/dto/create-tipo_pregunta.dto.ts b/src/tipo_pregunta/dto/create-tipo_pregunta.dto.ts index 5cd85c9..5579234 100644 --- a/src/tipo_pregunta/dto/create-tipo_pregunta.dto.ts +++ b/src/tipo_pregunta/dto/create-tipo_pregunta.dto.ts @@ -1 +1,9 @@ -export class CreateTipoPreguntaDto {} +import { IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator'; + +export class CreateTipoPreguntaDto { + @IsNotEmpty() + @IsString() + @MinLength(3) + @MaxLength(50) + tipo_pregunta: string; +} \ No newline at end of file diff --git a/src/tipo_pregunta/entities/tipo_pregunta.entity.ts b/src/tipo_pregunta/entities/tipo_pregunta.entity.ts index 178c993..16a657d 100644 --- a/src/tipo_pregunta/entities/tipo_pregunta.entity.ts +++ b/src/tipo_pregunta/entities/tipo_pregunta.entity.ts @@ -9,6 +9,6 @@ export class TipoPregunta { @Column() tipo_pregunta: string; - @OneToMany(() => Pregunta, pregunta => pregunta.id_tipo_pregunta) + @OneToMany(() => Pregunta, pregunta => pregunta.tipo_pregunta) preguntas: Pregunta[]; } \ No newline at end of file diff --git a/src/tipo_pregunta/tipo_pregunta.controller.ts b/src/tipo_pregunta/tipo_pregunta.controller.ts index 9e664b0..a4bd982 100644 --- a/src/tipo_pregunta/tipo_pregunta.controller.ts +++ b/src/tipo_pregunta/tipo_pregunta.controller.ts @@ -1,6 +1,32 @@ -import { Controller } from "@nestjs/common"; +import { Controller, Post, Body, HttpStatus, HttpException } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger'; +import { CreateTipoPreguntaDto } from './dto/create-tipo_pregunta.dto'; +import { TipoPreguntaService } from './tipo_pregunta.service'; -@Controller('tipo_pregunta') -export class TipoPreguntaController{ - +@ApiTags('Tipos de Pregunta') +@Controller('tipos-pregunta') +export class TipoPreguntaController { + constructor(private readonly tipoPreguntaService: TipoPreguntaService) {} + + @Post() + @ApiOperation({ summary: 'Crear un nuevo tipo de pregunta' }) + @ApiBody({ type: CreateTipoPreguntaDto }) + @ApiResponse({ + status: 201, + description: 'Tipo de pregunta creado exitosamente' + }) + @ApiResponse({ + status: 409, + description: 'El tipo de pregunta ya existe' + }) + async create(@Body() createTipoPreguntaDto: CreateTipoPreguntaDto) { + try { + return await this.tipoPreguntaService.create(createTipoPreguntaDto); + } catch (error) { + throw new HttpException({ + status: error.status || HttpStatus.BAD_REQUEST, + error: error.message, + }, error.status || HttpStatus.BAD_REQUEST); + } + } } \ No newline at end of file diff --git a/src/tipo_pregunta/tipo_pregunta.module.ts b/src/tipo_pregunta/tipo_pregunta.module.ts index 9418617..61b6f21 100644 --- a/src/tipo_pregunta/tipo_pregunta.module.ts +++ b/src/tipo_pregunta/tipo_pregunta.module.ts @@ -1,10 +1,16 @@ import { Module } from '@nestjs/common'; -import { TipoPreguntaService } from './tipo_pregunta.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { TipoPregunta } from './entities/tipo_pregunta.entity'; import { TipoPreguntaController } from './tipo_pregunta.controller'; +import { TipoPreguntaService } from './tipo_pregunta.service'; + @Module({ + imports: [ + TypeOrmModule.forFeature([TipoPregunta]), + ], controllers: [TipoPreguntaController], providers: [TipoPreguntaService], - exports:[TipoPreguntaService] + exports: [TipoPreguntaService, TypeOrmModule], }) -export class TipoPreguntaModule {} +export class TipoPreguntaModule {} \ No newline at end of file diff --git a/src/tipo_pregunta/tipo_pregunta.service.ts b/src/tipo_pregunta/tipo_pregunta.service.ts index 2c27e8b..a8a8f59 100644 --- a/src/tipo_pregunta/tipo_pregunta.service.ts +++ b/src/tipo_pregunta/tipo_pregunta.service.ts @@ -1,16 +1,34 @@ -import { Injectable } from "@nestjs/common"; +import { Injectable, ConflictException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { TipoPregunta } from "./entities/tipo_pregunta.entity"; -import { TipoCuestionario } from "src/tipo_cuestionario/entities/tipo_cuestionario.entity"; -import { Repository } from "typeorm"; +import { Repository } from 'typeorm'; +import { TipoPregunta } from './entities/tipo_pregunta.entity'; +import { CreateTipoPreguntaDto } from './dto/create-tipo_pregunta.dto'; @Injectable() -export class TipoPreguntaService{ - constructor( - @InjectRepository(TipoCuestionario) - private readonly tipoCuestionarioRepository: Repository - ){} +export class TipoPreguntaService { + constructor( + @InjectRepository(TipoPregunta) + private readonly tipoPreguntaRepository: Repository, + ) {} - + async create(createTipoPreguntaDto: CreateTipoPreguntaDto): Promise { + // Verificar si ya existe un tipo con el mismo nombre + const tipoExistente = await this.tipoPreguntaRepository.findOne({ + where: { tipo_pregunta: createTipoPreguntaDto.tipo_pregunta }, + }); + + if (tipoExistente) { + throw new ConflictException( + `El tipo de pregunta '${createTipoPreguntaDto.tipo_pregunta}' ya existe`, + ); + } + + // Crear y guardar el nuevo tipo + const nuevoTipo = this.tipoPreguntaRepository.create({ + tipo_pregunta: createTipoPreguntaDto.tipo_pregunta, + }); + + return await this.tipoPreguntaRepository.save(nuevoTipo); + } } \ No newline at end of file