diff --git a/package-lock.json b/package-lock.json index 09ae172..22a04fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,13 +28,14 @@ "moment": "^2.30.1", "multer": "^2.0.2", "mysql": "^2.18.1", + "mysql2": "^3.15.2", "node-domexception": "^2.0.2", "nodemailer": "^7.0.6", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", - "typeorm": "^0.3.26" + "typeorm": "^0.3.27" }, "devDependencies": { "@eslint/eslintrc": "^3.3.1", @@ -5209,6 +5210,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/axios": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", @@ -6337,6 +6347,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", @@ -7533,6 +7552,15 @@ "node": ">=18" } }, + "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", @@ -8199,6 +8227,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", @@ -9487,6 +9521,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "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", @@ -9510,6 +9550,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", @@ -9881,6 +9936,72 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/mysql2": { + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.2.tgz", + "integrity": "sha512-kFm5+jbwR5mC+lo+3Cy46eHiykWSpUtTLOH3GE+AR7GeLq8PgfJcvpMiyVWk9/O53DjQsqm6a3VOOfq7gYWFRg==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "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.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mysql2/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/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/napi-postinstall": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", @@ -11081,6 +11202,11 @@ "node": ">= 18" } }, + "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", @@ -12315,9 +12441,9 @@ "license": "MIT" }, "node_modules/typeorm": { - "version": "0.3.26", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.26.tgz", - "integrity": "sha512-o2RrBNn3lczx1qv4j+JliVMmtkPSqEGpG0UuZkt9tCfWkoXKu8MZnjvp2GjWPll1SehwemQw6xrbVRhmOglj8Q==", + "version": "0.3.27", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.27.tgz", + "integrity": "sha512-pNV1bn+1n8qEe8tUNsNdD8ejuPcMAg47u2lUGnbsajiNUr3p2Js1XLKQjBMH0yMRMDfdX8T+fIRejFmIwy9x4A==", "license": "MIT", "dependencies": { "@sqltools/formatter": "^1.2.5", @@ -12329,7 +12455,7 @@ "dedent": "^1.6.0", "dotenv": "^16.4.7", "glob": "^10.4.5", - "sha.js": "^2.4.11", + "sha.js": "^2.4.12", "sql-highlight": "^6.0.0", "tslib": "^2.8.1", "uuid": "^11.1.0", diff --git a/package.json b/package.json index 50e9227..ffcfb25 100644 --- a/package.json +++ b/package.json @@ -39,13 +39,14 @@ "moment": "^2.30.1", "multer": "^2.0.2", "mysql": "^2.18.1", + "mysql2": "^3.15.2", "node-domexception": "^2.0.2", "nodemailer": "^7.0.6", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", - "typeorm": "^0.3.26" + "typeorm": "^0.3.27" }, "devDependencies": { "@eslint/eslintrc": "^3.3.1", @@ -90,6 +91,9 @@ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", - "testEnvironment": "node" + "testEnvironment": "node", + "moduleNameMapper": { + "^src/(.*)$": "/$1" + } } } diff --git a/src/carrera/carrera.controller.ts b/src/carrera/carrera.controller.ts index 0f9a9ef..2f10861 100644 --- a/src/carrera/carrera.controller.ts +++ b/src/carrera/carrera.controller.ts @@ -1,4 +1,12 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, +} from '@nestjs/common'; import { CarreraService } from './carrera.service'; import { CreateCarreraDto } from './dto/create-carrera.dto'; import { UpdateCarreraDto } from './dto/update-carrera.dto'; diff --git a/src/caso-especial/caso-especial.controller.spec.ts b/src/caso-especial/caso-especial.controller.spec.ts new file mode 100644 index 0000000..6b96813 --- /dev/null +++ b/src/caso-especial/caso-especial.controller.spec.ts @@ -0,0 +1,121 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { CasoEspecialController } from './caso-especial.controller'; +import { CasoEspecialService } from './caso-especial.service'; +import { CreateCasoEspecialDto } from './dto/create-caso-especial.dto'; +import { UpdateCasoEspecialDto } from './dto/update-caso-especial.dto'; +import { FilterCasoEspecialDto } from './dto/filter-caso-especial.dto'; + +describe('CasoEspecialController', () => { + let controller: CasoEspecialController; + let service: CasoEspecialService; + + const mockService = { + create: jest.fn(), + findAllFiltered: jest.fn(), + admin: jest.fn(), + liberarCasoEspecial: jest.fn(), + updateCasoEspecial: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [CasoEspecialController], + providers: [ + { + provide: CasoEspecialService, + useValue: mockService, + }, + ], + }).compile(); + + controller = module.get(CasoEspecialController); + service = module.get(CasoEspecialService); + }); + + it('debería estar definido', () => { + expect(controller).toBeDefined(); + }); + + it('debería subir un archivo y crear un caso especial', async () => { + const dto: CreateCasoEspecialDto = { + idUsuario: 1, + idCarrera: 2, + idStatus: 1, + numeroCuenta: '123456789', + correo: 'alumno@pcpuma.com', + creditos: '10', + telefono: '5555555555', + fechaInicio: new Date().toISOString(), // formato ISO para IsDateString + fechaFin: new Date( + new Date().setMonth(new Date().getMonth() + 1), + ).toISOString(), + fechaNacimiento: '2000-01-01T00:00:00.000Z', + direccion: 'Direccion', + institucion: 'Instituton', + dependencia: 'Dependencia', + motivo: '1', + }; + const file = { filename: 'test-file.pdf' } as Express.Multer.File; + const result = { id: 1, ...dto, archivo: file.filename }; + + mockService.create.mockResolvedValue(result); + + const response = await controller.subirArchivo(file, dto); + expect(response.statusCode).toBe(201); + expect(response.data).toEqual(result); + expect(mockService.create).toHaveBeenCalledWith(dto, file.filename); + }); + + it('debería obtener servicios especiales filtrados', async () => { + const dto: FilterCasoEspecialDto = { + pagina: 1, + + idStatus: 2, + + nombre: 'alumnonombre', + + numeroCuenta: '123456789', + }; + const result = [{ id: 1, alumno: '123456' }]; + + mockService.findAllFiltered.mockResolvedValue(result); + + const response = await controller.getServiciosEspeciales(dto); + expect(response.statusCode).toBe(200); + expect(response.data).toEqual(result); + }); + + it('debería obtener un caso especial por ID', async () => { + const id = 1; + const result = { id, alumno: '123456789' }; + + mockService.admin.mockResolvedValue(result); + + const response = await controller.getCasosEspeciales(id); + expect(response.statusCode).toBe(200); + expect(response.data).toEqual(result); + }); + + it('debería liberar un caso especial', async () => { + const id = 1; + const result = { id, liberado: true }; + + mockService.liberarCasoEspecial.mockResolvedValue(result); + + const response = await controller.liberacion(id); + expect(response.statusCode).toBe(200); + expect(response.data).toEqual(result); + }); + + it('debería actualizar un caso especial', async () => { + const id = 1; + const dto: UpdateCasoEspecialDto = { motivo: 1 }; + const result = { id, ...dto }; + + mockService.updateCasoEspecial.mockResolvedValue(result); + + const response = await controller.update(dto, id); + expect(response.statusCode).toBe(200); + expect(response.data).toEqual(result); + }); +}); diff --git a/src/caso-especial/dto/create-caso-especial.dto.ts b/src/caso-especial/dto/create-caso-especial.dto.ts index 8dacdb4..f3fc70a 100644 --- a/src/caso-especial/dto/create-caso-especial.dto.ts +++ b/src/caso-especial/dto/create-caso-especial.dto.ts @@ -1,73 +1,72 @@ import { - IsEmail, - IsNotEmpty, - IsNumberString, - IsOptional, - IsString, - Length, - IsDateString, - IsNumber, - IsIn, - IsInt, - Matches, - } from 'class-validator'; - - export class CreateCasoEspecialDto { - @IsNumber() - - idUsuario: number; - - @IsNumber() - @IsInt() - idCarrera: number; - - @IsNumber() - @Length(1, 11) - idStatus: number; - - @IsString() - @Length(9, 9) - @Matches(/^\d+$/, { message: 'solo puede contener números' }) - numeroCuenta: string; - - @IsEmail() - correo: string; - - @IsString() - @Length(1, 3) - @Matches(/^\d+$/, { message: 'solo puede contener números' }) - creditos : string; - - @IsString() - @Length(1, 15) - @Matches(/^\d+$/, { message: 'solo puede contener números' }) - telefono: string; - - @IsDateString() - fechaInicio: Date; - - @IsDateString() - fechaFin: Date; - - @IsDateString() - fechaNacimiento: Date; - - @IsString() - @Length(1, 200) - direccion: string; - - @IsOptional() - @IsString() - @Length(1, 200) - institucion?: string; - - @IsOptional() - @IsString() - @Length(1, 200) - dependencia?: string; - - @IsOptional() - @IsNumber() - @Length(1, 1) - motivo?: string; - } \ No newline at end of file + IsEmail, + IsNotEmpty, + IsNumberString, + IsOptional, + IsString, + Length, + IsDateString, + IsNumber, + IsIn, + IsInt, + Matches, +} from 'class-validator'; + +export class CreateCasoEspecialDto { + @IsNumber() + idUsuario: number; + + @IsNumber() + @IsInt() + idCarrera: number; + + @IsNumber() + @Length(1, 11) + idStatus: number; + + @IsString() + @Length(9, 9) + @Matches(/^\d+$/, { message: 'solo puede contener números' }) + numeroCuenta: string; + + @IsEmail() + correo: string; + + @IsString() + @Length(1, 3) + @Matches(/^\d+$/, { message: 'solo puede contener números' }) + creditos: string; + + @IsString() + @Length(1, 15) + @Matches(/^\d+$/, { message: 'solo puede contener números' }) + telefono: string; + + @IsDateString() + fechaInicio: String; + + @IsDateString() + fechaFin: String; + + @IsDateString() + fechaNacimiento: String; + + @IsString() + @Length(1, 200) + direccion: string; + + @IsOptional() + @IsString() + @Length(1, 200) + institucion?: string; + + @IsOptional() + @IsString() + @Length(1, 200) + dependencia?: string; + + @IsOptional() + @IsNumber() + @Length(1, 1) + motivo?: string; +} diff --git a/src/servicio/servicio.controller.ts b/src/servicio/servicio.controller.ts index b1a964a..7c18800 100644 --- a/src/servicio/servicio.controller.ts +++ b/src/servicio/servicio.controller.ts @@ -1,4 +1,12 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, +} from '@nestjs/common'; import { ServicioService } from './servicio.service'; import { CreateServicioDto } from './dto/create-servicio.dto'; import { UpdateServicioDto } from './dto/update-servicio.dto'; @@ -23,7 +31,10 @@ export class ServicioController { } @Patch(':id') - update(@Param('id') id: string, @Body() updateServicioDto: UpdateServicioDto) { + update( + @Param('id') id: string, + @Body() updateServicioDto: UpdateServicioDto, + ) { return this.servicioService.update(+id, updateServicioDto); } diff --git a/tsconfig.json b/tsconfig.json index e4dbf2e..6117456 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,9 @@ "sourceMap": true, "outDir": "./dist", "baseUrl": "./", + "paths": { + "src/*": ["src/*"] + }, "incremental": true, "skipLibCheck": true, "strictNullChecks": true,