From de24bdeec00d21a448363acca4d182ac46fd2c8f Mon Sep 17 00:00:00 2001 From: Emilio Date: Tue, 21 Oct 2025 14:54:31 -0600 Subject: [PATCH] =?UTF-8?q?Se=20corrigi=C3=B3=20envio=20de=20correos=20y?= =?UTF-8?q?=20creacion=20de=20controller=20servicio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/auth/auth.controller.ts | 39 +++ src/auth/auth.service.ts | 254 ------------------ src/helpers.services/dto/send-email.dto.ts | 8 +- src/programa/programa.service.ts | 8 +- src/servicio/dto/update-servicio.dto.ts | 6 +- src/servicio/servicio.controller.ts | 160 +++++++++-- src/servicio/servicio.service.ts | 297 ++++++++++++++++----- src/status/status.controller.ts | 35 +-- src/status/status.service.ts | 22 +- src/usuario/usuario.controller.ts | 35 +-- src/usuario/usuario.service.ts | 277 ++++++++++++++++++- 11 files changed, 709 insertions(+), 432 deletions(-) create mode 100644 src/auth/auth.controller.ts diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts new file mode 100644 index 0000000..e8356d6 --- /dev/null +++ b/src/auth/auth.controller.ts @@ -0,0 +1,39 @@ +import { + Controller, + Post, + Body, + Get, + Query, + UnauthorizedException, +} from '@nestjs/common'; +import { AuthService } from './auth.service'; + +@Controller('auth') +export class AuthController { + constructor(private readonly authService: AuthService) {} + + // ===================== LOGIN ===================== + @Post('login') + async login( + @Body('usuario') usuario: string, + @Body('password') password: string, + ) { + try { + const token = await this.authService.login(usuario, password); + return token; // { access_token: '...' } + } catch (error) { + throw new UnauthorizedException(error.message); + } + } + + // ===================== VERIFICAR TOKEN ===================== + @Get('verify') + async verify(@Query('token') token: string) { + try { + const payload = await this.authService.jwtVerificar(token); + return { valid: true, payload }; + } catch (error) { + throw new UnauthorizedException(error.message); + } + } +} diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 7f3ab81..af5e9cc 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -18,44 +18,11 @@ import * as argon2 from 'argon2'; @Injectable() export class AuthService { - private readonly carreras: Record = { - 'LIC. EN ACTUARIA': 64, - 'LIC. EN ARQUITECTURA': 70, - 'LIC. EN CIENCIAS POLITICAS Y ADMON PUB': 67, - 'LIC. EN CIENCIAS POLITICAS Y ADMON.PUBL.': 67, - 'LIC. EN COMUNICACION': 67, - 'LIC. EN DERECHO': 68, - 'LIC. EN DERECHO (SUA)': 67, - 'LIC. EN DISEÑO GRAFICO': 70, - 'LIC. EN ECONOMIA': 66, - 'LIC. EN ENSEÑANZA DE INGLES': 69, - 'LIC. EN FILOSOFIA': 70, - 'LIC. EN HISTORIA': 70, - 'LIC. EN INGENIERIA CIVIL': 70, - 'LIC. EN LENGUA Y LITERATURA HISPANICAS': 70, - 'LIC. EN MAT. APLICADAS Y COMPUTACION': 66, - 'LIC. EN MATEMATICAS APLICADAS Y COMP.': 66, - 'LIC. EN PEDAGOGÍA': 70, - 'LIC. EN PERIODISMO Y COMUNICACION COL.': 70, - 'LIC. EN RELACIONES INTERNACIONALES': 70, - 'LIC. EN RELACIONES INTERNACIONALES (SUA)': 70, - 'LIC. EN SOCIOLOGIA': 68, - 'LIC. ENSEÑANZA DE ALEMÁN (LENG. EXTRANJS': 70, - 'LIC. ENSEÑANZA DE ESPAÑOL(LENG. EXTRANJ)': 70, - 'LIC. ENSEÑANZA DE INGLÉS(LENG. EXTRANJE)': 70, - 'LIC. ENSEÑANZA DE ITALIANO(LENG. EXTRANJ': 70, - }; - constructor( private readonly jwtService: JwtService, @InjectRepository(Usuario) private readonly userRepo: Repository, - @InjectRepository(Carrera) - private readonly carreraRepo: Repository, - @InjectRepository(Servicio) - private readonly servicioRepo: Repository, - private readonly gmail: gmail, ) {} async jwtVerificar(token: string) { @@ -117,225 +84,4 @@ export class AuthService { ); return token; } - - async escolares(numeroDeCuenta: string) { - let response; - try { - response = await axios.post( - `${process.env.ESCOLARES}${numeroDeCuenta}`, - { password: process.env.ESCOLARES_PASS }, - - { - headers: { - 'Content-Type': 'application/json', // Tipo de contenido - Authorization: `Bearer ${process.env.API_TOKEN}`, // Header de auth - }, - }, - ); - } catch (error) { - throw new UnauthorizedException( - 'No se pudo conectar con el servicio de escolares', - ); - } - - if ( - !response.data.nombre || - !response.data.carrconst || - !response.data.avance - ) { - throw new Error( - 'El alumno no cumple con los requisitos para realizar el Servicio Social. Si cree que esto es erróneo comunícate al Departamento de Servicio Social y Bolsa de Trabajo.', - ); - } - interface AlumnoDTO { - nombre: string; - creditos: number; - carrera: string; - idCarrera?: number; - } - - let alumno: AlumnoDTO = { - nombre: response.data.nombre.trim(), - creditos: response.data.avance, - carrera: response.data.carrconst.trim(), - }; - - if (alumno.creditos < this.carreras[alumno.carrera]) { - throw new Error('Este alumno no cuenta con los créditos necesarios.'); - } - while (alumno.nombre.search('‘') != -1 && alumno.nombre.search('Ã') != -1) { - alumno.nombre = alumno.nombre.replace('Ã', 'Ñ'); - alumno.nombre = alumno.nombre.replace('‘', ''); - } - - let carrera = await this.carreraRepo.findOne({ - where: { carrera: alumno.carrera }, - }); - - if (!carrera) { - let carr = this.carreraRepo.create({ carrera: alumno.carrera }); - carrera = await this.carreraRepo.save(carr); - } - - alumno.idCarrera = carrera.idCarrera; - - let alum = await this.userRepo.findOne({ - where: { usuario: numeroDeCuenta }, - }); - if (!alum) { - let nuevoUsuario = this.userRepo.create({ - usuario: numeroDeCuenta, - nombre: alumno.nombre, - activo: true, - tipoUsuario: { idTipoUsuario: 3 }, - }); - alum = await this.userRepo.save(nuevoUsuario); - } - - return { ...alumno, idUsuario: alum.idUsuario }; - } - - async newPasswordAlumno(idServicio: number) { - const password = this.generarPassword(); - - // Buscar el servicio junto con el usuario - const servicio = await this.servicioRepo.findOne({ - where: { idServicio }, - relations: ['usuario'], // asegura que traiga la relación - }); - - if (!servicio) { - throw new NotFoundException('No existe este Servicio Social.'); - } - - let usuario = servicio.usuario; - - if (usuario.tipoUsuario.idTipoUsuario !== 3) { - throw new BadRequestException('Este usuario no es de tipo alumno.'); - } - - // Preparar correo - let correo = preRegistro(password, usuario.nombre); - - // Enviar correo - await this.gmail.sendMail({ - subject: correo.subject, - to: servicio.correo, - text: correo.msj, - }); - - // Actualizar contraseña en la DB - usuario.password = await this.encriptar(password); - await this.userRepo.save(usuario); - - return { - message: 'Se envió un correo con una contraseña nueva al alumno.', - }; - } - - async newPasswordResponsable(idUsuario: number) { - const password = this.generarPassword(); - - // Buscar usuario - const usuario = await this.userRepo.findOne({ where: { idUsuario } }); - - if (!usuario) { - throw new NotFoundException('No existe este Usuario.'); - } - - if (usuario.tipoUsuario.idTipoUsuario !== 2) { - throw new BadRequestException('Este usuario no es de tipo responsable.'); - } - - // Preparar correo - const correo = enviarSec(password, usuario.usuario, usuario.nombre); - - // Enviar correo - await this.gmail.sendMail({ - subject: correo.subject, - to: usuario.usuario, - text: correo.msj, - }); - - // Actualizar contraseña en la DB - usuario.password = await this.encriptar(password); - await this.userRepo.save(usuario); - - return { - message: 'Se envió un correo con una contraseña nueva al responsable.', - }; - } - - async findResponsable(idUsuario: number) { - return await this.userRepo.findOne({ where: { idUsuario } }); - } - - async findResponsables(pagina: number, nombre: string, correo: string) { - const [responsables, count] = await this.userRepo.findAndCount({ - where: { - usuario: Like(`%${correo}%`), - nombre: Like(`%${nombre}%`), - tipoUsuario: { idTipoUsuario: 2 }, - }, - select: ['idUsuario', 'usuario', 'nombre', 'activo'], - take: 25, - skip: 25 * (pagina - 1), - }); - - return { count: count, responsables: responsables }; - } - - async actualizarResponsable( - idUsuario: number, - correo?: string, - nombre?: string, - ) { - const dataUpdate: Partial = {}; - - // Buscar responsable - const responsable = await this.userRepo.findOne({ where: { idUsuario } }); - if (!responsable) { - throw new NotFoundException( - 'No existe este responsable en la base de datos.', - ); - } - - if (responsable.tipoUsuario.idTipoUsuario !== 2) { - throw new BadRequestException( - 'Este usuario no es un responsable de programa.', - ); - } - - if (correo) { - const yaUsado = await this.userRepo.findOne({ - where: { usuario: correo, idUsuario: Not(idUsuario) }, - }); - - if (yaUsado) { - throw new BadRequestException( - 'No se puede asignar este correo a esta cuenta porque está siendo usado por otro responsable.', - ); - } - - dataUpdate.usuario = correo; - } - - // Validar nombre si se envía - if (nombre) { - dataUpdate.nombre = nombre; - } - - // Verificar que haya algo para actualizar - if (Object.keys(dataUpdate).length === 0) { - throw new BadRequestException('No se ha enviado nada para actualizar.'); - } - - // Actualizar en la base - await this.userRepo.update(idUsuario, dataUpdate); - - return { - message: - 'Se actualizó la información de este responsable de programas correctamente.', - }; - } } diff --git a/src/helpers.services/dto/send-email.dto.ts b/src/helpers.services/dto/send-email.dto.ts index b9cd4bf..0723018 100644 --- a/src/helpers.services/dto/send-email.dto.ts +++ b/src/helpers.services/dto/send-email.dto.ts @@ -4,6 +4,7 @@ import { IsOptional, IsISO8601, ValidateNested, + IsDate, } from 'class-validator'; import { Type } from 'class-transformer'; @@ -33,10 +34,11 @@ export class SendMailDto { text: string; @IsString() - html: string; + @IsOptional() + html?: string; - @IsISO8601() - fecha_recibido: string; + @IsDate() + fecha_recibido: Date; @IsOptional() @IsArray() diff --git a/src/programa/programa.service.ts b/src/programa/programa.service.ts index a2f5e77..5dfaa3e 100644 --- a/src/programa/programa.service.ts +++ b/src/programa/programa.service.ts @@ -12,7 +12,7 @@ import { Usuario } from 'src/usuario/entities/usuario.entity'; import { ValidacionService } from 'src/helpers.services/validacion.service'; import { AuthService } from 'src/auth/auth.service'; import { gmail } from 'src/helpers.services/gmail.service'; -import { SendCorreoDto } from 'src/helpers.services/dto/send-email.dto'; +import { SendMailDto } from 'src/helpers.services/dto/send-email.dto'; @Injectable() export class ProgramaService { @@ -83,11 +83,11 @@ export class ProgramaService { }, }); - const sendCorreoDto: SendCorreoDto = { + const sendCorreoDto: SendMailDto = { to: prog.correo, subject: correoInfo.subject, text: correoInfo.msj, - html: '', + fecha_recibido: new Date(), }; if (!process.env.TOKEN_GMAIL) { @@ -97,7 +97,7 @@ export class ProgramaService { try { responsable = await this.usuarioRepo.save(responsable); - await this.gmail.sendMail(sendCorreoDto); + await this.gmail.enviarCorreo(sendCorreoDto); mensaje += `Responsable creado con ID ${responsable.idUsuario} y correo ${responsable.usuario}\n`; } catch (err) { mensaje += `Error creando responsable: ${err.message}\n`; diff --git a/src/servicio/dto/update-servicio.dto.ts b/src/servicio/dto/update-servicio.dto.ts index 2a4eeb1..061c606 100644 --- a/src/servicio/dto/update-servicio.dto.ts +++ b/src/servicio/dto/update-servicio.dto.ts @@ -19,15 +19,15 @@ export class UpdateServicioDto { @IsOptional() @IsDateString({}, { message: 'fechaInicio debe ser una fecha válida' }) - fechaInicio?: string; + fechaInicio?: Date; @IsOptional() @IsDateString({}, { message: 'fechaFin debe ser una fecha válida' }) - fechaFin?: string; + fechaFin?: Date; @IsOptional() @IsDateString({}, { message: 'fechaNacimiento debe ser una fecha válida' }) - fechaNacimiento?: string; + fechaNacimiento?: Date; @IsOptional() @IsString({ message: 'direccion debe ser texto' }) diff --git a/src/servicio/servicio.controller.ts b/src/servicio/servicio.controller.ts index b1a964a..9fa7f12 100644 --- a/src/servicio/servicio.controller.ts +++ b/src/servicio/servicio.controller.ts @@ -1,34 +1,158 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Put, + Body, + Query, + UploadedFile, + UploadedFiles, + UseGuards, + UseInterceptors, + Res, + ParseIntPipe, +} from '@nestjs/common'; +import { + FileInterceptor, + FileFieldsInterceptor, +} from '@nestjs/platform-express'; import { ServicioService } from './servicio.service'; -import { CreateServicioDto } from './dto/create-servicio.dto'; -import { UpdateServicioDto } from './dto/update-servicio.dto'; +import { AuthGuard } from '@nestjs/passport'; +import { CrearServicioDto } from './dto/create-servicio.dto'; +import { Response } from 'express'; @Controller('servicio') export class ServicioController { constructor(private readonly servicioService: ServicioService) {} - @Post() - create(@Body() createServicioDto: CreateServicioDto) { - return this.servicioService.create(createServicioDto); + // ------------------ POST ------------------ + + @Post('nuevo') + @UseGuards(AuthGuard('jwt')) + @UseInterceptors(FileInterceptor('cartaAceptacion')) + async crearServicio( + @UploadedFile() file: Express.Multer.File, + @Body('alumno') alumnoJson: string, + ) { + const dto: CrearServicioDto = JSON.parse(alumnoJson); + return await this.servicioService.registrarNuevoServicio(dto, file); } - @Get() - findAll() { - return this.servicioService.findAll(); + // ------------------ GET ------------------ + + @Get('admin') + @UseGuards(AuthGuard('jwt')) + async obtenerAdmin(@Query() query: any) { + return this.servicioService.obtenerDetalleServicio(query.idServicio); } - @Get(':id') - findOne(@Param('id') id: string) { - return this.servicioService.findOne(+id); + @Get('alumno') + @UseGuards(AuthGuard('jwt')) + async obtenerAlumno(@Query('idUsuario', ParseIntPipe) idUsuario: number) { + return this.servicioService.obtenerServicioAlumno(idUsuario); } - @Patch(':id') - update(@Param('id') id: string, @Body() updateServicioDto: UpdateServicioDto) { - return this.servicioService.update(+id, updateServicioDto); + @Get('gustavo_baz_prada') + @UseGuards(AuthGuard('jwt')) + async gustavoBaz( + @Query('year', ParseIntPipe) year: number, + @Res() res: Response, + ) { + const path = await this.servicioService.generarGustavoBazPrada(year); + return res.download(path); } - @Delete(':id') - remove(@Param('id') id: string) { - return this.servicioService.remove(+id); + @Get('reporte') + @UseGuards(AuthGuard('jwt')) + async generarReporte(@Query() query: any, @Res() res: Response) { + const path = await this.servicioService.generarGustavoBazPrada(query.year); + return res.download(path); + } + + // ------------------ PUT ------------------ + + @Put('cancelar') + @UseGuards(AuthGuard('jwt')) + async cancelar(@Body() body: { idServicio: number; mensaje: string }) { + return this.servicioService.cancelarServicio(body); + } + + @Put('carta_aceptacion') + @UseGuards(AuthGuard('jwt')) + @UseInterceptors(FileInterceptor('cartaAceptacion')) + async subirCartaAceptacion( + @UploadedFile() file: Express.Multer.File, + @Body('data') dataJson: string, + ) { + const body = JSON.parse(dataJson); + return this.servicioService.cartaTermino(body.idServicio, file); + } + + @Put('carta_termino') + @UseGuards(AuthGuard('jwt')) + @UseInterceptors(FileInterceptor('cartaTermino')) + async subirCartaTermino( + @UploadedFile() file: Express.Multer.File, + @Body('data') dataJson: string, + ) { + const body = JSON.parse(dataJson); + return this.servicioService.subirCartaTermino(body.idServicio, file); + } + + @Put('informe_global') + @UseGuards(AuthGuard('jwt')) + @UseInterceptors(FileInterceptor('informeGlobal')) + async subirInformeGlobal( + @UploadedFile() file: Express.Multer.File, + @Body('data') dataJson: string, + ) { + const body = JSON.parse(dataJson); + return this.servicioService.subirInformeGlobal(body.idServicio, file); + } + + @Put('liberacion') + @UseGuards(AuthGuard('jwt')) + async liberarServicio( + @Body('idServicio', ParseIntPipe) idServicio: number, + @Body('vistoBuenoAcatlan') vistoBuenoAcatlan: boolean, + ) { + return this.servicioService.liberarServicio(idServicio, vistoBuenoAcatlan); + } + + @Put('rechazar_aceptacion') + @UseGuards(AuthGuard('jwt')) + async rechazarAceptacion( + @Body() body: { idServicio: number; mensaje: string }, + ) { + return this.servicioService.rechazar(body.idServicio, body.mensaje); + } + + @Put('rechazar_informe') + @UseGuards(AuthGuard('jwt')) + async rechazarInforme(@Body() body: { idServicio: number; mensaje: string }) { + return this.servicioService.rechazarInforme(body.idServicio, body.mensaje); + } + + @Put('rechazar_termino') + @UseGuards(AuthGuard('jwt')) + async rechazarTermino(@Body() body: { idServicio: number; mensaje: string }) { + return this.servicioService.rechazarTermino(body.idServicio, body.mensaje); + } + + @Put('update') + @UseGuards(AuthGuard('jwt')) + @UseInterceptors( + FileFieldsInterceptor([ + { name: 'cartaAceptacion', maxCount: 1 }, + { name: 'cartaTermino', maxCount: 1 }, + { name: 'informeGlobal', maxCount: 1 }, + ]), + ) + async actualizarServicio( + @UploadedFiles() files: Record, + @Body('data') dataJson: string, + ) { + const data = JSON.parse(dataJson); + return this.servicioService.update(data, files); } } diff --git a/src/servicio/servicio.service.ts b/src/servicio/servicio.service.ts index 1623225..31a139b 100644 --- a/src/servicio/servicio.service.ts +++ b/src/servicio/servicio.service.ts @@ -11,7 +11,6 @@ import { Servicio } from './entities/servicio.entity'; import { Usuario } from 'src/usuario/entities/usuario.entity'; import { ValidacionService } from 'src/helpers.services/validacion.service'; import { gmail } from 'src/helpers.services/gmail.service'; -import { SendCorreoDto } from 'src/helpers.services/dto/send-email.dto'; import { DriveService } from 'src/drive/drive.service'; import moment from 'moment'; import { ArchivoService } from 'src/helpers.services/archivo.service'; @@ -22,6 +21,8 @@ import { AuthService } from 'src/auth/auth.service'; import { RegistroValidadoDto } from './dto/registro-validado.dto'; import * as fs from 'fs'; import { ServiciosAdminDto } from './dto/servicios-admin.dto'; +import { ServiciosResponsableDto } from './dto/servicios-responsable.dto'; +import { SendMailDto } from 'src/helpers.services/dto/send-email.dto'; @Injectable() export class ServicioService { @@ -211,7 +212,7 @@ export class ServicioService { const correoAlumno = canceladoAlumno(mensaje, alumno.nombre); const correoResponsable = canceladoResponsable(mensaje, alumno.nombre); - const sendCorreoAlumnoDto: SendCorreoDto = { + const sendCorreoAlumnoDto: SendMailDto = { to: servicio.correo, subject: correoAlumno.subject, fecha_recibido: new Date(), @@ -220,7 +221,7 @@ export class ServicioService { adjuntos: undefined, }; - const sendCorreoResponsableDto: SendCorreoDto = { + const sendCorreoResponsableDto: SendMailDto = { to: responsable.usuario, subject: correoResponsable.subject, fecha_recibido: new Date(), @@ -234,17 +235,11 @@ export class ServicioService { } if (servicio.correo) { - await this.gmailService.send( - sendCorreoAlumnoDto, - process.env.TOKEN_GMAIL, - ); + await this.gmailService.enviarCorreo(sendCorreoAlumnoDto); } if (responsable.usuario) { - await this.gmailService.send( - sendCorreoResponsableDto, - process.env.TOKEN_GMAIL, - ); + await this.gmailService.enviarCorreo(sendCorreoResponsableDto); } // Desactivar al usuario (alumno) @@ -588,16 +583,12 @@ export class ServicioService { const correoAlumno = terminoValidado(servicio.usuario.nombre); if (servicio.correo) { - await this.gmailService.sendMail( - { - to: servicio.correo, - subject: correoAlumno.subject, - text: correoAlumno.msj, - html: '', - fecha_recibido: new Date(), - }, - process.env.TOKEN_GMAIL, - ); + await this.gmailService.enviarCorreo({ + to: servicio.correo, + subject: correoAlumno.subject, + text: correoAlumno.msj, + fecha_recibido: new Date(), + }); } break; @@ -763,11 +754,12 @@ export class ServicioService { emailResponsable = servicio.programa.usuario.usuario; // correo del responsable // Enviar correo al alumno - await this.gmailService.enviarCorreo( - correoAlumno.subject, - servicio.correo, - correoAlumno.msj, - ); + await this.gmailService.enviarCorreo({ + subject: correoAlumno.subject, + to: servicio.correo, + text: correoAlumno.msj, + fecha_recibido: new Date(), + }); break; case 2: @@ -796,11 +788,12 @@ export class ServicioService { } // Enviar correo al responsable - await this.gmailService.enviarCorreo( - correoResponsable.subject, - emailResponsable, - correoResponsable.msj, - ); + await this.gmailService.enviarCorreo({ + subject: correoResponsable.subject, + to: emailResponsable, + text: correoResponsable.msj, + fecha_recibido: new Date(), + }); // Actualizar estado del servicio await this.servicioRepo.update(idServicio, { @@ -836,11 +829,12 @@ export class ServicioService { ); emailResponsable = servicio.programa.usuario.usuario; // correo responsable - await this.gmailService.enviarCorreo( - correoAlumno.subject, - servicio.correo, - correoAlumno.msj, - ); + await this.gmailService.enviarCorreo({ + subject: correoAlumno.subject, + to: servicio.correo, + text: correoAlumno.msj, + fecha_recibido: new Date(), + }); break; } @@ -874,11 +868,12 @@ export class ServicioService { } // Enviar correo al responsable - await this.gmailService.enviarCorreo( - correoResponsable.subject, - emailResponsable, - correoResponsable.msj, - ); + await this.gmailService.enviarCorreo({ + subject: correoResponsable.subject, + to: emailResponsable, + text: correoResponsable.msj, + fecha_recibido: new Date(), + }); // Actualizar estado del servicio await this.servicioRepo.update(idServicio, { @@ -916,11 +911,12 @@ export class ServicioService { ); emailResponsable = servicio.programa.usuario.usuario; - await this.gmailService.enviarCorreo( - correoAlumno.subject, - servicio.correo, - correoAlumno.msj, - ); + await this.gmailService.enviarCorreo({ + subject: correoAlumno.subject, + to: servicio.correo, + text: correoAlumno.msj, + fecha_recibido: new Date(), + }); break; } @@ -954,11 +950,12 @@ export class ServicioService { } // Enviar correo al responsable - await this.gmailService.enviarCorreo( - correoResponsable.subject, - emailResponsable, - correoResponsable.msj, - ); + await this.gmailService.enviarCorreo({ + subject: correoResponsable.subject, + to: emailResponsable, + text: correoResponsable.msj, + fecha_recibido: new Date(), + }); // Actualizar el servicio await this.servicioRepo.update(idServicio, { @@ -988,11 +985,12 @@ export class ServicioService { case 1: correoData = preRegistro(password, servicio.usuario.nombre); idUsuario = servicio.usuario.idUsuario; - await this.gmailService.enviarCorreo( - correoData.subject, - servicio.correo, - correoData.msj, - ); + await this.gmailService.enviarCorreo({ + subject: correoData.subject, + to: servicio.correo, + text: correoData.msj, + fecha_recibido: new Date(), + }); break; case 2: @@ -1061,11 +1059,12 @@ export class ServicioService { servicio.usuario.nombre, ); emailResponsable = servicio.programa.usuario.usuario; - await this.gmailService.enviarCorreo( - correoAlumno.subject, - servicio.correo, - correoAlumno.msj, - ); + await this.gmailService.enviarCorreo({ + subject: correoAlumno.subject, + to: servicio.correo, + text: correoAlumno.msj, + fecha_recibido: new Date(), + }); break; case 1: @@ -1097,11 +1096,12 @@ export class ServicioService { throw new BadRequestException('Id status no válido.'); } - await this.gmailService.enviarCorreo( - correoResponsable.subject, - emailResponsable, - correoResponsable.msj, - ); + await this.gmailService.enviarCorreo({ + subject: correoResponsable.subject, + to: emailResponsable, + text: correoResponsable.msj, + fecha_recibido: new Date(), + }); // Determinar estatus según modo const tempStatus = process.env.MODE === 'pruebas' ? 4 : 3; @@ -1202,4 +1202,169 @@ export class ServicioService { return { count, serviciosAdmin: servicios }; } + + async obtenerServiciosResponsable(dto: ServiciosResponsableDto) { + const { + idUsuario, + idStatus, + nombre = '', + numeroCuenta = '', + pagina = 1, + } = dto; + + // 1️⃣ Validar que el usuario exista y sea responsable + const responsable = await this.usuarioRepo.findOne({ + where: { idUsuario }, + }); + if (!responsable) throw new NotFoundException('No existe este usuario.'); + if (responsable.tipoUsuario.idTipoUsuario !== 2) + throw new BadRequestException('No es un usuario tipo responsable.'); + + // 2️⃣ Crear el query builder + const query = this.servicioRepo + .createQueryBuilder('servicio') + .innerJoinAndSelect('servicio.usuario', 'usuario') + .innerJoinAndSelect('servicio.programa', 'programa') + .innerJoinAndSelect('servicio.carrera', 'carrera') + .innerJoinAndSelect('servicio.status', 'status') + .where('programa.idUsuario = :idUsuario', { idUsuario }) + .andWhere('usuario.usuario LIKE :numeroCuenta', { + numeroCuenta: `%${numeroCuenta}%`, + }) + .andWhere('usuario.nombre LIKE :nombre', { nombre: `%${nombre}%` }); + + // 3️⃣ Filtro de estatus + if (idStatus) { + query.andWhere('status.idStatus = :idStatus', { idStatus }); + } else { + query.andWhere('status.idStatus != :status', { status: 10 }); + } + + // 4️⃣ Orden, paginación y selección de campos + query + .select([ + 'servicio.idServicio', + 'servicio.fechaInicio', + 'servicio.fechaFin', + 'servicio.cartaTermino', + 'servicio.createdAt', + 'servicio.idCuestionarioPrograma', + 'servicio.idCuestionarioPrograma2', + 'usuario.idUsuario', + 'usuario.usuario', + 'usuario.nombre', + 'carrera.idCarrera', + 'carrera.carrera', + 'status.idStatus', + 'status.status', + ]) + .orderBy('servicio.updatedAt', 'DESC') + .skip(25 * (pagina - 1)) + .take(25); + + // 5️⃣ Ejecutar y obtener datos + total + const [rows, count] = await query.getManyAndCount(); + + // 6️⃣ Retornar resultado en el mismo formato que antes + return { count, serviciosResponsable: rows }; + } + + async update( + body: UpdateServicioDto, + files: Record, + ) { + const idServicio = body.idServicio; + + const servicio = await this.servicioRepo.findOne({ where: { idServicio } }); + if (!servicio) + throw new NotFoundException( + 'Este Servicio Social no existe en la base de datos.', + ); + + let dataUpdate: Partial = {}; + + // ---- 📁 Subida de archivos a Drive ---- + try { + if (files?.['cartaAceptacion']?.[0]?.filename) { + { + const result = await this.driveService.uploadFile( + `./server/uploads/${files['cartaAceptacion'][0].filename}`, + `Carta_Aceptacion.pdf`, + 'application/pdf', + servicio.carpeta, + ); + dataUpdate.cartaAceptacion = result === null ? undefined : result; + } + } + } catch (err) {} + + try { + if (files?.['cartaTermino']?.[0]?.filename) { + { + const result = await this.driveService.uploadFile( + `./server/uploads/${files['cartaTermino'][0].filename}`, + `Carta_Termino.pdf`, + 'application/pdf', + servicio.carpeta, + ); + dataUpdate.cartaTermino = result === null ? undefined : result; + } + } + } catch (err) {} + + try { + if (files?.['informeGlobal']?.[0]?.filename) { + const result = await this.driveService.uploadFile( + `./server/uploads/${files['informeGlobal'][0].filename}`, + `Informe_Global.pdf`, + 'application/pdf', + servicio.carpeta, + ); + dataUpdate.informeGlobal = result === null ? undefined : result; + } + } catch (err) {} + + // ---- 📆 Validaciones de campos del body ---- + if (body.correo) dataUpdate.correo = body.correo; + if (body.fechaInicio) dataUpdate.fechaInicio = body.fechaInicio; + if (body.fechaFin) dataUpdate.fechaFin = body.fechaFin; + if (body.fechaNacimiento) dataUpdate.fechaNacimiento = body.fechaNacimiento; + if (body.direccion) + dataUpdate.direccion = this.validacionService.validarAlfanumerico( + body.direccion, + 'dirección', + false, + 200, + ); + if (body.telefono) + dataUpdate.telefono = this.validacionService.validarNumero( + body.telefono, + 'teléfono', + true, + 15, + ); + + if (this.validacionService.validarObjetoVacio(dataUpdate)) { + throw new BadRequestException( + 'No se envió nada para actualizar este Servicio Social.', + ); + } + + // ---- 🧱 Actualización en la base ---- + await this.servicioRepo + .createQueryBuilder() + .update(Servicio) + .set(dataUpdate) + .where('idServicio = :idServicio', { idServicio }) + .execute(); + + // ---- ✅ Validar si se requiere "pre-término" ---- + if (dataUpdate.cartaTermino || dataUpdate.informeGlobal) { + await this.validacionService.validarPreTermino(idServicio); + } + + return { + message: 'Se guardaron correctamente los cambios de este servicio.', + }; + } } diff --git a/src/status/status.controller.ts b/src/status/status.controller.ts index a6ed1ab..22170ad 100644 --- a/src/status/status.controller.ts +++ b/src/status/status.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 { StatusService } from './status.service'; import { CreateStatusDto } from './dto/create-status.dto'; import { UpdateStatusDto } from './dto/update-status.dto'; @@ -6,29 +14,4 @@ import { UpdateStatusDto } from './dto/update-status.dto'; @Controller('status') export class StatusController { constructor(private readonly statusService: StatusService) {} - - @Post() - create(@Body() createStatusDto: CreateStatusDto) { - return this.statusService.create(createStatusDto); - } - - @Get() - findAll() { - return this.statusService.findAll(); - } - - @Get(':id') - findOne(@Param('id') id: string) { - return this.statusService.findOne(+id); - } - - @Patch(':id') - update(@Param('id') id: string, @Body() updateStatusDto: UpdateStatusDto) { - return this.statusService.update(+id, updateStatusDto); - } - - @Delete(':id') - remove(@Param('id') id: string) { - return this.statusService.remove(+id); - } } diff --git a/src/status/status.service.ts b/src/status/status.service.ts index 143b7f1..3a7031d 100644 --- a/src/status/status.service.ts +++ b/src/status/status.service.ts @@ -3,24 +3,4 @@ import { CreateStatusDto } from './dto/create-status.dto'; import { UpdateStatusDto } from './dto/update-status.dto'; @Injectable() -export class StatusService { - create(createStatusDto: CreateStatusDto) { - return 'This action adds a new status'; - } - - findAll() { - return `This action returns all status`; - } - - findOne(id: number) { - return `This action returns a #${id} status`; - } - - update(id: number, updateStatusDto: UpdateStatusDto) { - return `This action updates a #${id} status`; - } - - remove(id: number) { - return `This action removes a #${id} status`; - } -} +export class StatusService {} diff --git a/src/usuario/usuario.controller.ts b/src/usuario/usuario.controller.ts index 5b3b392..dc4327c 100644 --- a/src/usuario/usuario.controller.ts +++ b/src/usuario/usuario.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 { UsuarioService } from './usuario.service'; import { CreateUsuarioDto } from './dto/create-usuario.dto'; import { UpdateUsuarioDto } from './dto/update-usuario.dto'; @@ -6,29 +14,4 @@ import { UpdateUsuarioDto } from './dto/update-usuario.dto'; @Controller('usuario') export class UsuarioController { constructor(private readonly usuarioService: UsuarioService) {} - - @Post() - create(@Body() createUsuarioDto: CreateUsuarioDto) { - return this.usuarioService.create(createUsuarioDto); - } - - @Get() - findAll() { - return this.usuarioService.findAll(); - } - - @Get(':id') - findOne(@Param('id') id: string) { - return this.usuarioService.findOne(+id); - } - - @Patch(':id') - update(@Param('id') id: string, @Body() updateUsuarioDto: UpdateUsuarioDto) { - return this.usuarioService.update(+id, updateUsuarioDto); - } - - @Delete(':id') - remove(@Param('id') id: string) { - return this.usuarioService.remove(+id); - } } diff --git a/src/usuario/usuario.service.ts b/src/usuario/usuario.service.ts index 878b91d..e23520c 100644 --- a/src/usuario/usuario.service.ts +++ b/src/usuario/usuario.service.ts @@ -1,26 +1,281 @@ -import { Injectable } from '@nestjs/common'; +import { + BadRequestException, + Injectable, + NotFoundException, + UnauthorizedException, +} from '@nestjs/common'; import { CreateUsuarioDto } from './dto/create-usuario.dto'; import { UpdateUsuarioDto } from './dto/update-usuario.dto'; +import axios from 'axios'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Usuario } from './entities/usuario.entity'; +import { Like, Not, Repository } from 'typeorm'; +import { Carrera } from 'src/carrera/entities/carrera.entity'; +import { Servicio } from 'src/servicio/entities/servicio.entity'; +import { gmail } from 'src/helpers.services/gmail.service'; +import { AuthService } from 'src/auth/auth.service'; @Injectable() export class UsuarioService { - create(createUsuarioDto: CreateUsuarioDto) { - return 'This action adds a new usuario'; + private readonly carreras: Record = { + 'LIC. EN ACTUARIA': 64, + 'LIC. EN ARQUITECTURA': 70, + 'LIC. EN CIENCIAS POLITICAS Y ADMON PUB': 67, + 'LIC. EN CIENCIAS POLITICAS Y ADMON.PUBL.': 67, + 'LIC. EN COMUNICACION': 67, + 'LIC. EN DERECHO': 68, + 'LIC. EN DERECHO (SUA)': 67, + 'LIC. EN DISEÑO GRAFICO': 70, + 'LIC. EN ECONOMIA': 66, + 'LIC. EN ENSEÑANZA DE INGLES': 69, + 'LIC. EN FILOSOFIA': 70, + 'LIC. EN HISTORIA': 70, + 'LIC. EN INGENIERIA CIVIL': 70, + 'LIC. EN LENGUA Y LITERATURA HISPANICAS': 70, + 'LIC. EN MAT. APLICADAS Y COMPUTACION': 66, + 'LIC. EN MATEMATICAS APLICADAS Y COMP.': 66, + 'LIC. EN PEDAGOGÍA': 70, + 'LIC. EN PERIODISMO Y COMUNICACION COL.': 70, + 'LIC. EN RELACIONES INTERNACIONALES': 70, + 'LIC. EN RELACIONES INTERNACIONALES (SUA)': 70, + 'LIC. EN SOCIOLOGIA': 68, + 'LIC. ENSEÑANZA DE ALEMÁN (LENG. EXTRANJS': 70, + 'LIC. ENSEÑANZA DE ESPAÑOL(LENG. EXTRANJ)': 70, + 'LIC. ENSEÑANZA DE INGLÉS(LENG. EXTRANJE)': 70, + 'LIC. ENSEÑANZA DE ITALIANO(LENG. EXTRANJ': 70, + }; + + constructor( + @InjectRepository(Usuario) + private readonly userRepo: Repository, + @InjectRepository(Carrera) + private readonly carreraRepo: Repository, + @InjectRepository(Servicio) + private readonly servicioRepo: Repository, + private readonly gmail: gmail, + private readonly authService: AuthService, + ) {} + + async escolares(numeroDeCuenta: string) { + let response; + try { + response = await axios.post( + `${process.env.ESCOLARES}${numeroDeCuenta}`, + { password: process.env.ESCOLARES_PASS }, + + { + headers: { + 'Content-Type': 'application/json', // Tipo de contenido + Authorization: `Bearer ${process.env.API_TOKEN}`, // Header de auth + }, + }, + ); + } catch (error) { + throw new UnauthorizedException( + 'No se pudo conectar con el servicio de escolares', + ); + } + + if ( + !response.data.nombre || + !response.data.carrconst || + !response.data.avance + ) { + throw new Error( + 'El alumno no cumple con los requisitos para realizar el Servicio Social. Si cree que esto es erróneo comunícate al Departamento de Servicio Social y Bolsa de Trabajo.', + ); + } + interface AlumnoDTO { + nombre: string; + creditos: number; + carrera: string; + idCarrera?: number; + } + + let alumno: AlumnoDTO = { + nombre: response.data.nombre.trim(), + creditos: response.data.avance, + carrera: response.data.carrconst.trim(), + }; + + if (alumno.creditos < this.carreras[alumno.carrera]) { + throw new Error('Este alumno no cuenta con los créditos necesarios.'); + } + while (alumno.nombre.search('‘') != -1 && alumno.nombre.search('Ã') != -1) { + alumno.nombre = alumno.nombre.replace('Ã', 'Ñ'); + alumno.nombre = alumno.nombre.replace('‘', ''); + } + + let carrera = await this.carreraRepo.findOne({ + where: { carrera: alumno.carrera }, + }); + + if (!carrera) { + let carr = this.carreraRepo.create({ carrera: alumno.carrera }); + carrera = await this.carreraRepo.save(carr); + } + + alumno.idCarrera = carrera.idCarrera; + + let alum = await this.userRepo.findOne({ + where: { usuario: numeroDeCuenta }, + }); + if (!alum) { + let nuevoUsuario = this.userRepo.create({ + usuario: numeroDeCuenta, + nombre: alumno.nombre, + activo: true, + tipoUsuario: { idTipoUsuario: 3 }, + }); + alum = await this.userRepo.save(nuevoUsuario); + } + + return { ...alumno, idUsuario: alum.idUsuario }; } - findAll() { - return `This action returns all usuario`; + async newPasswordAlumno(idServicio: number) { + const password = this.authService.generarPassword(); + + // Buscar el servicio junto con el usuario + const servicio = await this.servicioRepo.findOne({ + where: { idServicio }, + relations: ['usuario'], // asegura que traiga la relación + }); + + if (!servicio) { + throw new NotFoundException('No existe este Servicio Social.'); + } + + let usuario = servicio.usuario; + + if (usuario.tipoUsuario.idTipoUsuario !== 3) { + throw new BadRequestException('Este usuario no es de tipo alumno.'); + } + + // Preparar correo + let correo = preRegistro(password, usuario.nombre); + + // Enviar correo + await this.gmail.enviarCorreo({ + subject: correo.subject, + to: servicio.correo, + text: correo.msj, + fecha_recibido: new Date(), + }); + + // Actualizar contraseña en la DB + usuario.password = await this.authService.encriptar(password); + await this.userRepo.save(usuario); + + return { + message: 'Se envió un correo con una contraseña nueva al alumno.', + }; } - findOne(id: number) { - return `This action returns a #${id} usuario`; + async newPasswordResponsable(idUsuario: number) { + const password = this.authService.generarPassword(); + + // Buscar usuario + const usuario = await this.userRepo.findOne({ where: { idUsuario } }); + + if (!usuario) { + throw new NotFoundException('No existe este Usuario.'); + } + + if (usuario.tipoUsuario.idTipoUsuario !== 2) { + throw new BadRequestException('Este usuario no es de tipo responsable.'); + } + + // Preparar correo + const correo = enviarSec(password, usuario.usuario, usuario.nombre); + + // Enviar correo + await this.gmail.enviarCorreo({ + subject: correo.subject, + to: usuario.usuario, + text: correo.msj, + fecha_recibido: new Date(), + }); + + // Actualizar contraseña en la DB + usuario.password = await this.authService.encriptar(password); + await this.userRepo.save(usuario); + + return { + message: 'Se envió un correo con una contraseña nueva al responsable.', + }; } - update(id: number, updateUsuarioDto: UpdateUsuarioDto) { - return `This action updates a #${id} usuario`; + async findResponsable(idUsuario: number) { + return await this.userRepo.findOne({ where: { idUsuario } }); } - remove(id: number) { - return `This action removes a #${id} usuario`; + async findResponsables(pagina: number, nombre: string, correo: string) { + const [responsables, count] = await this.userRepo.findAndCount({ + where: { + usuario: Like(`%${correo}%`), + nombre: Like(`%${nombre}%`), + tipoUsuario: { idTipoUsuario: 2 }, + }, + select: ['idUsuario', 'usuario', 'nombre', 'activo'], + take: 25, + skip: 25 * (pagina - 1), + }); + + return { count: count, responsables: responsables }; + } + + async actualizarResponsable( + idUsuario: number, + correo?: string, + nombre?: string, + ) { + const dataUpdate: Partial = {}; + + // Buscar responsable + const responsable = await this.userRepo.findOne({ where: { idUsuario } }); + if (!responsable) { + throw new NotFoundException( + 'No existe este responsable en la base de datos.', + ); + } + + if (responsable.tipoUsuario.idTipoUsuario !== 2) { + throw new BadRequestException( + 'Este usuario no es un responsable de programa.', + ); + } + + if (correo) { + const yaUsado = await this.userRepo.findOne({ + where: { usuario: correo, idUsuario: Not(idUsuario) }, + }); + + if (yaUsado) { + throw new BadRequestException( + 'No se puede asignar este correo a esta cuenta porque está siendo usado por otro responsable.', + ); + } + + dataUpdate.usuario = correo; + } + + // Validar nombre si se envía + if (nombre) { + dataUpdate.nombre = nombre; + } + + // Verificar que haya algo para actualizar + if (Object.keys(dataUpdate).length === 0) { + throw new BadRequestException('No se ha enviado nada para actualizar.'); + } + + // Actualizar en la base + await this.userRepo.update(idUsuario, dataUpdate); + + return { + message: + 'Se actualizó la información de este responsable de programas correctamente.', + }; } }