Files
Iris_back/src/servicio/servicio.service.ts
T
2026-01-16 09:58:40 -06:00

1657 lines
48 KiB
TypeScript

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<Servicio>,
@InjectRepository(Usuario)
private usuarioRepo: Repository<Usuario>,
@InjectRepository(Programa)
private programaRepo: Repository<Programa>,
@InjectRepository(Carrera)
private carreraRepo: Repository<Carrera>,
@InjectRepository(Status)
private statusRepo: Repository<Status>,
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<string> {
// 🔹 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<string> {
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<string, Express.Multer.File[]>,
) {
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<Servicio> = {};
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<void> {
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")
}
}