import { BadRequestException, Injectable, InternalServerErrorException, NotFoundException, } from '@nestjs/common'; import { CrearServicioDto } from './dto/create-servicio.dto'; import { UpdateServicioDto } from './dto/update-servicio.dto'; import { InjectRepository } from '@nestjs/typeorm'; import { Between, In, Not, Repository } from 'typeorm'; 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 { DriveService } from 'src/drive/drive.service'; import * as moment from 'moment'; import { ArchivoService } from 'src/helpers.services/archivo.service'; import { convertArrayToCSV } from 'convert-array-to-csv'; import { Programa } from 'src/programa/entities/programa.entity'; import { Carrera } from 'src/carrera/entities/carrera.entity'; 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'; import { Status } from 'src/status/entities/status.entity'; import { Cron } from '@nestjs/schedule'; const { canceladoAlumno, canceladoResponsable, terminoValidado, preRegistro, preRegistroRechazadoAlumno, preRegistroRechazadoResponsable, terminoRechazadoAlumno, terminoRechazadoResponsable, registroValidadoAlumno, registroValidadoResponsable, avisoTerminoAlumno, avisoTerminoResponsable, } = require('./../helpers.services/msjCorreos'); @Injectable() export class ServicioService { constructor( @InjectRepository(Servicio) private servicioRepo: Repository, @InjectRepository(Usuario) private usuarioRepo: Repository, @InjectRepository(Programa) private programaRepo: Repository, @InjectRepository(Carrera) private carreraRepo: Repository, @InjectRepository(Status) private statusRepo: Repository, private validacionService: ValidacionService, private driveService: DriveService, private gmailService: gmail, private archivoService: ArchivoService, private authService: AuthService, ) { } async obtenerServicioAlumno(idUsuario: number) { const usuario = await this.usuarioRepo.findOne({ where: { idUsuario }, relations: ['tipoUsuario'], }); if (!usuario) { throw new NotFoundException('No existe este usuario.'); } if (usuario.tipoUsuario.idTipoUsuario !== 3) { throw new NotFoundException('No es un usuario de tipo alumno.'); } const servicio = await this.servicioRepo .createQueryBuilder('servicio') .leftJoinAndSelect('servicio.carrera', 'carrera') .leftJoinAndSelect('servicio.status', 'status') .leftJoinAndSelect('servicio.programa', 'programa') .leftJoinAndSelect('servicio.cuestionarioAlumno2', 'ca2') .leftJoinAndSelect('servicio.cuestionarioAlumno', 'ca') .where('servicio.idUsuario = :idUsuario', { idUsuario }) .andWhere('servicio.idStatus != :status', { status: 10 }) .select([ 'servicio.idServicio', 'servicio.creditos', 'servicio.correo', 'servicio.telefono', 'servicio.direccion', 'servicio.fechaInicio', 'servicio.fechaFin', 'servicio.fechaLiberacion', 'servicio.fechaNacimiento', 'servicio.informeGlobal', 'servicio.programaInterno', 'servicio.profesor', 'servicio.createdAt', 'servicio.idCuestionarioAlumno', 'servicio.idCuestionarioAlumno2', 'programa.idPrograma', 'programa.institucion', 'programa.dependencia', 'programa.programa', 'programa.clavePrograma', 'carrera.idCarrera', 'carrera.carrera', 'status.idStatus', 'status.status', 'ca2.idCuestionarioAlumno2', 'ca.idCuestionarioAlumno', ]) .getOne(); if (!servicio) { throw new NotFoundException('No existe este servicio social.'); } return servicio; } async obtenerDetalleServicio(idServicio: number) { const servicio = await this.servicioRepo .createQueryBuilder('servicio') .leftJoinAndSelect('servicio.usuario', 'usuario') .leftJoinAndSelect('usuario.tipoUsuario', 'tipoUsuario') .leftJoinAndSelect('servicio.carrera', 'carrera') .leftJoinAndSelect('servicio.status', 'status') .leftJoinAndSelect('servicio.programa', 'programa') .leftJoinAndSelect('programa.usuario', 'usuarioPrograma') .leftJoinAndSelect('servicio.cuestionarioAlumno2', 'ca2') .leftJoinAndSelect('servicio.cuestionarioPrograma2', 'cp2') .leftJoinAndSelect('servicio.cuestionarioPrograma', 'cp') .leftJoinAndSelect('servicio.cuestionarioAlumno', 'ca') .where('servicio.idServicio = :idServicio', { idServicio }) .select([ 'servicio.idServicio', 'servicio.creditos', 'servicio.correo', 'servicio.telefono', 'servicio.direccion', 'servicio.fechaInicio', 'servicio.fechaFin', 'servicio.fechaLiberacion', 'servicio.fechaNacimiento', 'servicio.cartaAceptacion', 'servicio.cartaTermino', 'servicio.informeGlobal', 'servicio.programaInterno', 'servicio.profesor', 'servicio.vistoBuenoAcatlan', 'servicio.createdAt', 'servicio.idCuestionarioAlumno', 'servicio.idCuestionarioAlumno2', 'servicio.idCuestionarioPrograma', 'servicio.idCuestionarioPrograma2', 'usuario.idUsuario', 'usuario.usuario', 'usuario.nombre', 'tipoUsuario.idTipoUsuario', 'carrera.idCarrera', 'carrera.carrera', 'status.idStatus', 'status.status', 'programa.idPrograma', 'programa.institucion', 'programa.dependencia', 'programa.programa', 'programa.clavePrograma', 'programa.acatlan', 'usuarioPrograma.idUsuario', 'usuarioPrograma.usuario', 'usuarioPrograma.nombre', 'ca2.idCuestionarioAlumno2', 'ca.idCuestionarioAlumno', 'cp2.idCuestionarioPrograma2', 'cp.idCuestionarioPrograma' ]) .getOne(); if (!servicio) { throw new NotFoundException('No existe este servicio social.'); } return servicio; } async cancelarServicio(body: { idServicio: number; mensaje: string }) { const idServicio = this.validacionService.validarNumeroEntero( body.idServicio, 'id servicio', ); const mensaje = this.validacionService.validarAlfanumerico( body.mensaje, 'mensaje', true, 800, ); const servicio = await this.servicioRepo.findOne({ where: { idServicio }, relations: [ 'usuario', // alumno 'programa', 'programa.usuario', // responsable 'status', // Falto agregar la relacion de status ], }); if (!servicio) { throw new NotFoundException('No existe este Servicio Social.'); } // Agregamos una validación para el status extra if (!servicio.status) { throw new BadRequestException('El Servicio Social no tiene un estatus válido.'); } if (servicio.status.idStatus === 10) { throw new BadRequestException('Este Servicio Social ya fue cancelado.'); } if (servicio.status.idStatus === 6) { throw new BadRequestException( 'Este Servicio Social ya fue finalizado, no se puede cancelar.', ); } const alumno = servicio.usuario; const responsable = servicio.programa.usuario; const correoAlumno = canceladoAlumno(mensaje, alumno.nombre); const correoResponsable = canceladoResponsable(mensaje, alumno.nombre); const sendCorreoAlumnoDto: SendMailDto = { to: servicio.correo, subject: correoAlumno.subject, fecha_recibido: new Date(), text: correoAlumno.msj, html: '', adjuntos: undefined, }; const sendCorreoResponsableDto: SendMailDto = { to: responsable.usuario, subject: correoResponsable.subject, fecha_recibido: new Date(), text: correoResponsable.msj, html: '', adjuntos: undefined, }; if (servicio.correo) { await this.gmailService.enviarCorreo(sendCorreoAlumnoDto); } if (responsable.usuario) { await this.gmailService.enviarCorreo(sendCorreoResponsableDto); } // Desactivar al usuario (alumno) await this.usuarioRepo.update(alumno.idUsuario, { activo: false, password: null, }); // Cambiar estado del servicio await this.servicioRepo.update(idServicio, { status: { idStatus: 10 } }); return { message: 'Se canceló correctamente este Servicio Social.', }; } async cartaTermino(idServ: number, file: string) { const idServicio = this.validacionService.validarNumeroEntero( idServ, 'id servicio', ); const path = `./server/uploads/${this.validacionService.validacionBasicaStr( file, 'archivo', true, 1000, )}`; const servicio = await this.servicioRepo.findOne({ where: { idServicio }, relations: ['status'], }); console.log('Servicio encontrado:', servicio); if (!servicio) { throw new NotFoundException('Este servicio no existe.'); } if (servicio.cartaAceptacion) { throw new BadRequestException('Ya se subió la Carta de Aceptación.'); } // Aquí validas los estados igual que antes switch (servicio.status.idStatus) { case 7: break; case 1: throw new BadRequestException( 'Esta función solo puede usarse cuando el estatus del Servicio Social sea 7.', ); case 2: case 3: case 4: case 5: throw new BadRequestException( 'Este Servicio Social ya pasó la fase de subir la Carta de Aceptación.', ); case 6: throw new BadRequestException('Este Servicio Social ya finalizó.'); case 8: case 9: throw new BadRequestException( 'Este Servicio Social se encuentra rechazado. No se puede avanzar hasta que se corrija lo necesario.', ); case 10: throw new BadRequestException( 'Este Servicio Social fue cancelado. Comunícate con COESI para solucionar tu problema.', ); default: throw new BadRequestException('Id status no válido.'); } // Subir archivo al drive const uploadPath = await this.driveService.uploadFile( path, 'Carta_Aceptacion.pdf', 'application/pdf', servicio.carpeta, ); if (!uploadPath) { throw new BadRequestException( 'No se pudo subir el archivo, intenta más tarde.', ); } // Actualizar servicio await this.servicioRepo.update(idServicio, { cartaAceptacion: uploadPath, status: { idStatus: 1 }, }); return { message: 'Se subió la Carta de Aceptación correctamente.', }; } async subirCartaTermino( idServicioParam: number, archivo: Express.Multer.File, ): Promise<{ message: string }> { const idServicio: number = this.validacionService.validarNumeroEntero(idServicioParam, 'id servicio'); if (!archivo) { throw new BadRequestException('No se recibió el archivo'); } if (archivo.mimetype !== 'application/pdf') { throw new BadRequestException('El archivo debe ser PDF'); } const rutaArchivo = `./server/uploads/${archivo.filename}`; const servicio = await this.servicioRepo.findOne({ where: { idServicio }, relations: ['status'], }); if (!servicio) { throw new NotFoundException('Este servicio no existe.'); } if (!servicio.status?.idStatus) { throw new BadRequestException('El servicio no tiene un status válido.'); } if (servicio.cartaTermino) { throw new BadRequestException('Ya se subió la Carta de Termino.'); } let archivoSubido: string; switch (servicio.status.idStatus) { case 4: case 8: try { archivoSubido = await this.driveService.uploadFile( rutaArchivo, 'Carta_Termino.pdf', 'application/pdf', servicio.carpeta, ); } catch (err) { if (fs.existsSync(rutaArchivo)) fs.unlinkSync(rutaArchivo); throw err; } break; case 1: case 2: case 3: throw new BadRequestException( 'Aún no se puede subir la Carta de Termino a este Servicio Social.', ); case 5: throw new BadRequestException( 'Este Servicio Social ya pasó la fase de subir la Carta de Termino.', ); case 6: throw new BadRequestException('Este Servicio Social ya finalizó.'); case 7: case 9: throw new BadRequestException( 'Este Servicio Social se encuentra rechazado.', ); case 10: throw new BadRequestException( 'Este Servicio Social está cancelado.', ); default: throw new BadRequestException('Id status no válido.'); } if (!archivoSubido) { throw new InternalServerErrorException('Error al subir archivo.'); } await this.servicioRepo.update(idServicio, { cartaTermino: archivoSubido, }); const resultadoPreTermino = await this.validacionService.validarPreTermino(idServicio); return { message: `Se subió la Carta de Termino correctamente. ${resultadoPreTermino}`, }; } async generarGustavoBazPrada(yearParam: number): Promise { // 🔹 Validar año const year: number = this.validacionService.validarNumero( String(yearParam), 'año', true, 4, ); // Los transformo a string para hacer la consulta //const inicio = moment(`${year}-01-31`).toDate(); //const fin = moment(`${year + 1}-01-31`).toDate(); const inicio = moment(`${year}-01-31`).format('YYYY-MM-DD'); const fin = moment(`${year + 1}-01-31`).format('YYYY-MM-DD'); const path: string = `server/uploads/${year}_gustavo_baz_prada.csv`; const data: any[] = []; // 🔹 Buscar servicios liberados en el año const servicios: Servicio[] = await this.servicioRepo.find({ where: { fechaLiberacion: Between(inicio, fin), }, relations: ['usuario', 'carrera'], order: { fechaLiberacion: 'ASC' }, }); // 🔹 Formatear datos servicios.forEach((servicio) => { data.push({ numeroCuenta: servicio.usuario.usuario, nombre: servicio.usuario.nombre, licenciatura: servicio.carrera.carrera, correo: servicio.correo, fechaLiberacion: this.archivoService.crearDDMMAAAA( servicio.fechaLiberacion, ), }); }); // 🔹 Eliminar archivo existente y crear CSV try { await this.archivoService.eliminarArchivo(path); } catch (err) { console.log('Error eliminando archivo:', err); } await this.archivoService.crearArchivo(path, convertArrayToCSV(data)); return path; } async subirInformeGlobal( idServicioParam: number, archivo: Express.Multer.File, ): Promise<{ message: string }> { // 🔹 Validar idServicio const idServicio: number = this.validacionService.validarNumeroEntero( idServicioParam, 'id servicio', ); // 🔹 Validar archivo y generar path local const rutaArchivo: string = `./server/uploads/${this.validacionService.validacionBasicaStr( archivo.filename, 'archivo', true, 1000, )}`; // 🔹 Buscar servicio const servicio = await this.servicioRepo.findOne({ where: { idServicio }, relations: ['status'], }); if (!servicio) { throw new NotFoundException('Este servicio no existe.'); } if (servicio.informeGlobal) { throw new BadRequestException('Ya se subió el Informe Global.'); } // 🔹 Validar status y subir archivo si corresponde let archivoSubido; switch (servicio.status.idStatus) { case 4: case 9: archivoSubido = await this.driveService.uploadFile( rutaArchivo, 'Informe_Global.pdf', 'application/pdf', servicio.carpeta, ); break; case 1: case 2: case 3: throw new BadRequestException( 'Aún no se puede subir el Informe Global a este Servicio Social.', ); case 5: throw new BadRequestException( 'Este Servicio Social ya pasó la fase de subir el Informe Global.', ); case 6: throw new BadRequestException('Este Servicio Social ya finalizó.'); case 7: case 8: throw new BadRequestException( 'Este Servicio Social se encuentra rechazado. No se puede avanzar hasta que se corrija lo necesario.', ); case 10: throw new BadRequestException( 'Este Servicio Social está cancelado. Comunícate con COESI para solucionar tu problema.', ); default: throw new BadRequestException('Id status no válido.'); } // 🔹 Actualizar el servicio con el informe global await this.servicioRepo.update(idServicio, { informeGlobal: archivoSubido, }); // 🔹 Ejecutar validaciones adicionales (pre-termino) const resultadoPreTermino = await this.validacionService.validarPreTermino(idServicio); // 🔹 Retornar mensaje return { message: `Se subió el Informe Global correctamente. ${resultadoPreTermino}`, }; } async liberarServicio( idServicioParam: number, vistoBuenoAcatlan: boolean = false, ): Promise<{ message: string }> { // 🔹 Validar idServicio const idServicio: number = this.validacionService.validarNumeroEntero( idServicioParam, 'id servicio', ); // 🔹 Buscar servicio con relaciones const servicio = await this.servicioRepo.findOne({ where: { idServicio }, relations: ['usuario', 'programa', 'status'], }); if (!servicio) { throw new NotFoundException('No existe este Servicio Social.'); } // 🔹 Validación para programas Acatlán if (servicio.programa.acatlan && !vistoBuenoAcatlan) { throw new BadRequestException( 'El programa de este Servicio Social es Acatlán Contigo y no se envió la validación correspondiente.', ); } // 🔹 Enviar correo según status switch (servicio.status.idStatus) { case 5: const correoAlumno = terminoValidado(servicio.usuario.nombre); if (servicio.correo) { await this.gmailService.enviarCorreo({ to: servicio.correo, subject: correoAlumno.subject, text: correoAlumno.msj, fecha_recibido: new Date(), }); } break; case 1: case 2: case 3: case 4: throw new BadRequestException( 'Este Servicio Social aún no puede pasar a Liberación.', ); case 6: throw new BadRequestException( 'Este Servicio Social ya se encuentra en Liberación.', ); case 7: case 8: case 9: throw new BadRequestException( 'Este Servicio Social se encuentra rechazado. No se puede aceptar hasta que se corrija lo necesario.', ); case 10: throw new BadRequestException('Este Servicio Social está cancelado.'); default: throw new BadRequestException('Id status no válido.'); } // 🔹 Actualizar el servicio await this.servicioRepo.update(idServicio, { status: { idStatus: 6 }, fechaLiberacion: moment().format('YYYY-MM-DD'), // lo mismo pasamos a string antes como toDate vistoBuenoAcatlan: vistoBuenoAcatlan ? true : false, }); return { message: 'Se cambió de estatus correctamente y se envió un correo al alumno informándole de su liberación.', }; } async registrarNuevoServicio( dto: CrearServicioDto, file: Express.Multer.File, ): Promise<{ message: string }> { // 🔹 Validaciones // Agregue una validacion para el archivo if (!file) { throw new BadRequestException('No se ha proporcionado un archivo.'); } /* Codigo anterior const archivoPath = `./server/uploads/${this.validacionService.validacionBasicaStr(file.filename, 'archivo', true, 1000)}`; */ // Hacemos una validacion en el nombre const nombreArchivo = `./server/uploads/${this.validacionService.validacionBasicaStr(file.filename, 'archivo', true, 1000)}`; const archivoPath = nombreArchivo; // Validacion de tipo de archivo if (file.mimetype !== 'application/pdf') { throw new BadRequestException('La carta debe ser un archivo PDF.'); } // Validacion de tamanio if (file.size > 5 * 1024 * 1024) { throw new BadRequestException('La carta debe ser menor a 5MB.'); } const progInterno = dto.programaInterno ? this.validacionService.validarAlfanumerico( dto.programaInterno, 'programa interno', true, 250, ) : ''; const prof = dto.profesor ? this.validacionService.validarTexto(dto.profesor, 'profesor', true, 50) : ''; // 🔹 Validar Usuario const usuario = await this.usuarioRepo.findOne({ where: { idUsuario: dto.idUsuario }, relations: ['tipoUsuario'], }); if (!usuario) throw new NotFoundException('Este alumno no existe en la db.'); if (usuario.tipoUsuario.idTipoUsuario !== 3) throw new BadRequestException('Este usuario no es de tipo Alumno.'); // 🔹 Validar Programa const programa = await this.programaRepo.findOne({ where: { idPrograma: dto.idPrograma }, }); if (!programa) throw new NotFoundException('Este programa no existe en la db.'); // 🔹 Validar Carrera const carrera = await this.carreraRepo.findOne({ where: { idCarrera: dto.idCarrera }, }); if (!carrera) throw new NotFoundException('Esta carrera no existe en la db.'); // 🔹 Verificar Servicio activo const servicioExistente = await this.servicioRepo.findOne({ where: { usuario: { idUsuario: dto.idUsuario }, status: { idStatus: Not(10) }, }, }); if (servicioExistente) throw new BadRequestException( 'Este alumno ya tiene un Servicio Social activo.', ); // 🔹 Crear Servicio const nuevoServicio = this.servicioRepo.create({ usuario, programa, carrera, creditos: dto.creditos.toString(), correo: dto.correo, fechaInicio: dto.fechaInicio, fechaFin: dto.fechaFin, carpeta: '', cartaAceptacion: '', profesor: prof, programaInterno: progInterno, status: { idStatus: 1 }, }); await this.servicioRepo.save(nuevoServicio); // 🔹 Crear carpeta y subir carta de aceptación const carpeta = await this.driveService.mkDir(dto.numeroCuenta); const cartaPath = await this.driveService.uploadFile( archivoPath, 'Carta_Aceptacion.pdf', 'application/pdf', carpeta, ); if (!carpeta || !cartaPath) { throw new BadRequestException( 'No se pudo crear la carpeta o subir la carta, intenta más tarde.', ); } // 🔹 Actualizar servicio con carpeta y carta await this.servicioRepo.update(nuevoServicio.idServicio, { carpeta, cartaAceptacion: cartaPath, }); return { message: 'Se pre-registró correctamente a este alumno.' }; } async rechazar( idServicio: number, mensaje: string, ): Promise<{ message: string }> { let correoResponsable: any; let correoAlumno: any; let emailResponsable = ''; const servicio = await this.servicioRepo.findOne({ where: { idServicio }, relations: ['usuario', 'programa', 'programa.usuario', 'status'], }); if (!servicio) { throw new NotFoundException('No existe este Servicio Social.'); } const statusId = servicio.status.idStatus; switch (statusId) { case 1: correoAlumno = preRegistroRechazadoAlumno( mensaje, servicio.usuario.nombre, ); correoResponsable = preRegistroRechazadoResponsable( mensaje, servicio.usuario.nombre, ); emailResponsable = servicio.programa.usuario.usuario; // correo alumno await this.gmailService.enviarCorreo({ subject: correoAlumno.subject, to: servicio.correo, text: correoAlumno.msj, fecha_recibido: new Date(), }); break; case 2: case 3: case 4: case 5: case 8: case 9: throw new BadRequestException( 'No se puede rechazar la Carta de Aceptación en este punto del Servicio Social.', ); case 6: throw new BadRequestException('Este Servicio Social ya finalizó.'); case 7: throw new BadRequestException( 'Este Servicio Social ya tiene la Carta de Aceptación rechazada.', ); case 10: throw new BadRequestException('Este Servicio Social fue cancelado.'); default: throw new BadRequestException('Id status no válido.'); } // correo responsable await this.gmailService.enviarCorreo({ subject: correoResponsable.subject, to: emailResponsable, text: correoResponsable.msj, fecha_recibido: new Date(), }); const statusRechazado = await this.statusRepo.findOneBy({ idStatus: 7 }); if (!statusRechazado) { throw new NotFoundException('El estatus de rechazado no existe.'); } await this.driveService.deleteFile(servicio.cartaAceptacion) await this.servicioRepo.update(idServicio, { status: statusRechazado, cartaAceptacion: '', }); return { message: 'Se rechazó correctamente la Carta de Aceptación.', }; } async rechazarInforme( idServicio: number, mensaje: string, ): Promise<{ message: string }> { let correoResponsable: any; let correoAlumno: any; let emailResponsable = ''; const servicio = await this.servicioRepo.findOne({ where: { idServicio }, relations: ['usuario', 'programa', 'programa.usuario', 'status'], }); if (!servicio) { throw new NotFoundException('No existe este Servicio Social.'); } switch (servicio.status.idStatus) { case 5: correoAlumno = terminoRechazadoAlumno( mensaje, servicio.usuario.nombre, ); correoResponsable = terminoRechazadoResponsable( mensaje, servicio.usuario.nombre, ); emailResponsable = servicio.programa.usuario.usuario; await this.gmailService.enviarCorreo({ subject: correoAlumno.subject, to: servicio.correo, text: correoAlumno.msj, fecha_recibido: new Date(), }); break; case 1: case 2: case 3: case 4: throw new BadRequestException( 'Aún no se puede rechazar el Informe Global.', ); case 6: throw new BadRequestException('Este Servicio Social ya finalizó.'); case 7: case 8: throw new BadRequestException( 'No se puede rechazar el Informe Global en este punto del Servicio Social.', ); case 9: throw new BadRequestException( 'Este Servicio Social ya tiene el Informe Global rechazado.', ); case 10: throw new BadRequestException('Este Servicio Social fue cancelado.'); default: throw new BadRequestException('Id status no válido.'); } await this.gmailService.enviarCorreo({ subject: correoResponsable.subject, to: emailResponsable, text: correoResponsable.msj, fecha_recibido: new Date(), }); if (!servicio.informeGlobal) { throw new Error("no hay Informe Global") } await this.driveService.deleteFile(servicio.informeGlobal) await this.servicioRepo.update(idServicio, { status: { idStatus: 9 }, informeGlobal: '', }); return { message: 'Se rechazó correctamente el Informe Global.', }; } async rechazarTermino( idServicio: number, mensaje: string, ): Promise<{ message: string }> { let correoResponsable: any; let correoAlumno: any; let emailResponsable = ''; const servicio = await this.servicioRepo.findOne({ where: { idServicio }, relations: ['usuario', 'programa', 'programa.usuario', 'status'], }); if (!servicio) { throw new NotFoundException('No existe este Servicio Social.'); } switch (servicio.status.idStatus) { case 5: correoAlumno = terminoRechazadoAlumno( mensaje, servicio.usuario.nombre, ); correoResponsable = terminoRechazadoResponsable( mensaje, servicio.usuario.nombre, ); emailResponsable = servicio.programa.usuario.usuario; await this.gmailService.enviarCorreo({ subject: correoAlumno.subject, to: servicio.correo, text: correoAlumno.msj, fecha_recibido: new Date(), }); break; case 1: case 2: case 3: case 4: throw new BadRequestException( 'Aún no se puede rechazar la Carta de Término.', ); case 6: throw new BadRequestException('Este Servicio Social ya finalizó.'); case 7: case 9: throw new BadRequestException( 'No se puede rechazar la Carta de Término en este punto del Servicio Social.', ); case 8: throw new BadRequestException( 'Este Servicio Social ya tiene la Carta de Término rechazada.', ); case 10: throw new BadRequestException('Este Servicio Social fue cancelado.'); default: throw new BadRequestException('Id status no válido.'); } await this.gmailService.enviarCorreo({ subject: correoResponsable.subject, to: emailResponsable, text: correoResponsable.msj, fecha_recibido: new Date(), }); if (!servicio.cartaTermino) { throw new Error("No hay Carta Termino") } await this.driveService.deleteFile(servicio.cartaTermino) await this.servicioRepo.update(idServicio, { status: { idStatus: 8 }, cartaTermino: '', }); return { message: 'Se rechazó correctamente la Carta de Término.', }; } async registro(idServicio: number): Promise<{ message: string }> { let password = await this.authService.generarPassword(); if (process.env.MODE === 'pruebas') password = 'qwertyui'; const servicio = await this.servicioRepo.findOne({ where: { idServicio }, relations: ['usuario', 'status'], }); if (!servicio) throw new NotFoundException('No existe este Servicio Social.'); let correoData: any; let idUsuario: number; switch (servicio.status.idStatus) { case 1: correoData = preRegistro(password, servicio.usuario.nombre); idUsuario = servicio.usuario.idUsuario; await this.gmailService.enviarCorreo({ subject: correoData.subject, to: servicio.correo, text: correoData.msj, fecha_recibido: new Date(), }); break; case 2: throw new BadRequestException( 'Este Servicio Social ya se encuentra en Registro.', ); case 3: case 4: case 5: throw new BadRequestException( 'Este Servicio Social ya paso por el Registro.', ); case 6: throw new BadRequestException('Este Servicio Social ya finalizó.'); case 7: case 8: case 9: throw new BadRequestException( 'Este Servicio Social se encuentra rechazado. No se puede aceptar hasta que se corriga lo necesario.', ); case 10: throw new BadRequestException('Este Servicio Social fue cancelado.'); default: throw new BadRequestException('Id status no válido.'); } // Actualizar contraseña del alumno y activarlo await this.usuarioRepo.update(idUsuario, { password: await this.authService.encriptar(password), activo: true, }); // Cambiar estatus del Servicio Social await this.servicioRepo.update(idServicio, { status: { idStatus: 2 } }); return { message: 'Se cambió de estatus correctamente y se envió un correo al alumno con sus credenciales.', }; } async registroValidado( dto: RegistroValidadoDto, ): Promise<{ message: string }> { const idServicio = dto.idServicio; const fechaNacimiento = dto.fechaNacimiento; const telefono = dto.telefono; const direccion = dto.direccion; const servicio = await this.servicioRepo.findOne({ where: { idServicio }, relations: ['usuario', 'programa', 'programa.usuario', 'status'], }); if (!servicio) throw new NotFoundException('No existe este Servicio Social.'); let correoAlumno: any; let correoResponsable: any; let emailResponsable: string; // const sendCorreoResponsableDto: SendMailDto = { // to: correoResponsable, // subject: correoResponsable.subject, // fecha_recibido: new Date(), // text: correoResponsable.msj, // html: '', // adjuntos: undefined, // }; // const sendCorreoAlumnoDto: SendMailDto = { // to: servicio.correo, // subject: correoAlumno.subject, // fecha_recibido: new Date(), // text: correoAlumno.msj, // html: '', // adjuntos: undefined, // }; switch (servicio.status.idStatus) { case 2: // correoAlumno = registroValidadoAlumno(servicio.usuario.nombre); // correoResponsable = registroValidadoResponsable( // servicio.usuario.nombre, // ); // emailResponsable = servicio.programa.usuario.usuario; // await this.gmailService.enviarCorreo({ // subject: correoAlumno.subject, // to: servicio.correo, // text: correoAlumno.msj, // fecha_recibido: new Date(), // }); // break; correoAlumno = registroValidadoAlumno(servicio.usuario.nombre); correoResponsable = registroValidadoResponsable( servicio.usuario.nombre, ); emailResponsable = servicio.programa.usuario.usuario; await this.gmailService.enviarCorreo({ subject: correoAlumno.subject, to: servicio.correo, text: correoAlumno.msj, fecha_recibido: new Date(), }); break; case 1: throw new BadRequestException( 'Este Servicio Social aún no puede pasar a Registro Validado.', ); case 3: throw new BadRequestException( 'Este servicio ya se encuentra en Registro Validado.', ); case 4: case 5: throw new BadRequestException( 'Este Servicio Social ya pasó por el Registro Validado.', ); case 6: throw new BadRequestException('Este Servicio Social ya finalizó.'); case 7: case 8: case 9: throw new BadRequestException( 'Este Servicio Social se encuentra rechazado. Comunícate con COESI.', ); case 10: throw new BadRequestException( 'Este Servicio Social fue cancelado. Comunícate con COESI.', ); default: throw new BadRequestException('Id status no válido.'); } if (!correoResponsable || !emailResponsable) { throw new InternalServerErrorException( 'Error al generar correo para el responsable', ); } await this.gmailService.enviarCorreo({ subject: correoResponsable.subject, to: emailResponsable, text: correoResponsable.msj, fecha_recibido: new Date(), }); // 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; await this.servicioRepo.update(idServicio, { status: { idStatus: tempStatus }, direccion, telefono, fechaNacimiento, }); return { message: 'Haz terminado el registro de tu Servicio Social correctamente.', }; } // Pasamos el string antes tenia Date async generarReporte(inicio: string, fin: string): Promise { const path = `server/uploads/reporte.csv`; const data: any[] = []; const servicios = await this.servicioRepo.find({ where: { fechaInicio: Between(inicio, fin), }, relations: ['usuario', 'programa', 'carrera', 'status'], order: { fechaInicio: 'ASC' }, }); servicios.forEach((s) => { data.push({ numeroCuenta: s.usuario.usuario, nombre: s.usuario.nombre, licenciatura: s.carrera.carrera, correo: s.correo, fechaInicio: this.archivoService.crearDDMMAAAA(s.fechaInicio), fechaFin: this.archivoService.crearDDMMAAAA(s.fechaFin), fechaLiberacion: s.fechaLiberacion ? this.archivoService.crearDDMMAAAA(s.fechaLiberacion) : '', institucion: s.programa.institucion, dependencia: s.programa.dependencia, programa: s.programa.programa, profesor: s.profesor, clave: s.programa.clavePrograma, status: s.status.status, }); }); // Eliminar archivo existente si existe if (fs.existsSync(path)) { fs.unlinkSync(path); } // Crear CSV fs.writeFileSync(path, convertArrayToCSV(data)); return path; } async obtenerServiciosAdmin(dto: ServiciosAdminDto) { const pagina = dto.pagina ? dto.pagina : 1; const limite = 25; // limite de paginacion const whereStatus = dto.idStatus ? { idStatus: dto.idStatus } : {}; const nombre = dto.nombre ? dto.nombre : ''; const numeroCuenta = dto.numeroCuenta ? dto.numeroCuenta : ''; const query = this.servicioRepo .createQueryBuilder('servicio') .leftJoinAndSelect('servicio.usuario', 'usuario') .leftJoinAndSelect('servicio.carrera', 'carrera') .leftJoinAndSelect('servicio.status', 'status') //.leftJoinAndSelect('servicio.cuestionarioAlumno2', 'ca2') //.leftJoinAndSelect('servicio.cuestionarioPrograma2', 'cp2') //.leftJoinAndSelect('servicio.cuestionarioPrograma', 'cp') //.leftJoinAndSelect('servicio.cuestionarioAlumno', 'ca') .select([ 'servicio.idServicio', 'servicio.fechaInicio', 'servicio.fechaFin', 'servicio.createdAt', 'servicio.updatedAt', 'usuario.idUsuario', 'usuario.usuario', 'usuario.nombre', 'carrera.idCarrera', 'carrera.carrera', 'status.idStatus', 'status.status', // 'ca2.idCuestionarioAlumno2', // 'ca.idCuestionarioAlumno', // 'cp2.idCuestionarioPrograma2', // 'cp.idCuestionarioPrograma' ]) .orderBy('servicio.updatedAt', 'DESC') .skip(limite * (pagina - 1)) .take(limite); if (whereStatus?.idStatus) { query.andWhere('status.idStatus = :status', { status: whereStatus.idStatus, }); } if (nombre) { query.andWhere('usuario.nombre LIKE :nombre', { nombre: `%${nombre}%` }); } if (numeroCuenta) { query.andWhere('usuario.usuario LIKE :numeroCuenta', { numeroCuenta: `%${numeroCuenta}%`, }); } const [servicios, count] = await query.getManyAndCount(); return { count, serviciosAdmin: servicios, paginacion: { pagina, limite, total: count, paginas: Math.ceil(count / limite), }, }; } 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 }, relations: ['tipoUsuario'], }); 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') .leftJoinAndSelect('usuario.tipoUsuario', 'tipoUsuario') .leftJoinAndSelect('servicio.cuestionarioPrograma2', 'cuestionarioPrograma2') // Agregue esta linea para tener la relacion y poder jalar la infromacion .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', 'cuestionarioPrograma2.idCuestionarioPrograma2', // Agrgue esta linea para tener la infromacion de la ralacion ]) .orderBy('servicio.createdAt', 'DESC') // Cambiamos el orderBy anteriormente orderBy('servicio.updatedAt', 'DESC') .skip(25 * (pagina - 1)) .take(25); // 5️⃣ Ejecutar y obtener datos + total const [rows, count] = await query.getManyAndCount(); // Agregue una validacion para el cuestionario de responsable const serviciosResponsable = rows.map((servicio) => ({ ...servicio, cuestionarioCompletado: Boolean(servicio.cuestionarioPrograma2), })); // Agregamos temporalmente para verificar que funcione console.log( serviciosResponsable.map(s => ({ idServicio: s.idServicio, cuestionarioCompleto: s.cuestionarioCompletado, })) ) // 6️⃣ Retornar resultado en el mismo formato que antes // return { count, serviciosResponsable: rows }; return { count, serviciosResponsable }; } async update( body: UpdateServicioDto, files: Record, ) { const idServicio = body.idServicio; const servicio = await this.servicioRepo.findOne({ where: { idServicio }, relations: ['status'] }); if (!servicio) throw new NotFoundException( 'Este Servicio Social no existe en la base de datos.', ); let dataUpdate: Partial = {}; 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; } if (servicio.status.idStatus === 7) { dataUpdate.status = { idStatus: 1 } as any; } } } 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) { } if (body.correo) dataUpdate.correo = body.correo; 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 (body.fechaInicio) dataUpdate.fechaInicio = moment(body.fechaInicio).format('YYYY-MM-DD'); if (body.fechaFin) dataUpdate.fechaFin = moment(body.fechaFin).format('YYYY-MM-DD'); if (body.fechaNacimiento) dataUpdate.fechaNacimiento = moment(body.fechaNacimiento).format('YYYY-MM-DD'); if (this.validacionService.validarObjetoVacio(dataUpdate)) { throw new BadRequestException( 'No se envió nada para actualizar este Servicio Social.', ); } await this.servicioRepo .createQueryBuilder() .update(Servicio) .set(dataUpdate) .where('idServicio = :idServicio', { idServicio }) .execute(); if (dataUpdate.cartaTermino || dataUpdate.informeGlobal) { await this.validacionService.validarPreTermino(idServicio); } return { message: 'Se guardaron correctamente los cambios de este servicio.', }; } async obtenerTodosLosServicios() { return await this.servicioRepo.find({ relations: ['usuario', 'carrera', 'status', 'programa'], take: 10, order: { idServicio: 'DESC', // Ordenar por el más reciente primero }, }); } @Cron('*/5 * * * *') async diario(): Promise { const now = moment().startOf('day'); let mensaje = ''; const servicios = await this.servicioRepo.find({ where: { idServicio: In([19067, 19070]), status: { idStatus: 3 } }, relations: [ 'status', 'usuario', 'programa', 'programa.usuario', ], }); for (const servicio of servicios) { const fechaFin = moment(servicio.fechaFin).startOf('day'); const diferencia = fechaFin.diff(now, 'days'); console.log("diferencia:", diferencia) // ===== 7 días antes ===== if (diferencia === 7) { const correoAlumno = avisoTerminoAlumno( servicio.usuario.nombre, ); const correoResponsable = avisoTerminoResponsable( servicio.usuario.nombre, ); mensaje += `Se envió correo al alumno ${servicio.usuario.usuario} ` + `con id ${servicio.usuario.idUsuario} al correo ${servicio.correo} ` + `y al responsable ${servicio.programa.usuario.nombre} ` + `con id ${servicio.programa.usuario.idUsuario} ` + `y correo ${servicio.programa.usuario.usuario}\n\n`; await this.gmailService.enviarCorreo({ subject: correoAlumno.subject, to: servicio.correo, text: correoAlumno.msj, fecha_recibido: new Date(), }); await this.gmailService.enviarCorreo({ subject: correoResponsable.subject, to: servicio.programa.usuario.usuario, text: correoResponsable.msj, fecha_recibido: new Date(), }); } // ===== Servicio vencido ===== else if (diferencia <= 0) { mensaje += `El servicio con id ${servicio.idServicio} ` + `del alumno ${servicio.usuario.usuario} ` + `con id ${servicio.usuario.idUsuario} ` + `pasó al status 4\n\n`; await this.servicioRepo.update( { idServicio: servicio.idServicio }, { status: { idStatus: 4 } }, ); } } // ===== Reporte ===== console.log("se realizo carga masiva") } }