This commit is contained in:
evenegas
2025-09-18 09:31:17 -06:00
11 changed files with 968 additions and 508 deletions
+1
View File
@@ -0,0 +1 @@
20.17.0
+429 -465
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -33,6 +33,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"convert-array-to-csv": "^2.0.0",
"csvtojson": "^2.0.10",
"googleapis": "^148.0.0",
"moment": "^2.30.1",
"mysql": "^2.18.1",
+1 -1
View File
@@ -39,7 +39,7 @@ export class AuthService {
}
async encriptar(password){
bcrypt.hashSync(password, Number(process.env.SALT_ROUNDS))
return bcrypt.hashSync(password, Number(process.env.SALT_ROUNDS))
}
async generarPassword(){
+27 -1
View File
@@ -1,10 +1,36 @@
import { Module } from '@nestjs/common';
import { CasoEspecialService } from './caso-especial.service';
import { CasoEspecialController } from './caso-especial.controller';
import { CasoEspecial } from './entities/caso-especial.entity';
import { Usuario } from 'src/usuario/entities/usuario.entity';
import { Carrera } from 'src/carrera/entities/carrera.entity';
import { Status } from 'src/status/entities/status.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ValidacionService } from 'src/helpers.services/validacion.service';
import { DriveService } from 'src/drive/drive.service';
import { gmail } from 'src/helpers.services/gmail.service';
import { Servicio } from 'src/servicio/entities/servicio.entity';
import { HttpModule } from '@nestjs/axios';
@Module({
imports: [
TypeOrmModule.forFeature([
CasoEspecial,
Usuario,
Carrera,
Status,
Servicio,
]),
HttpModule
// si ValidacionService viene de ah // si gmail y DriveService vienen de ahí
],
controllers: [CasoEspecialController],
providers: [CasoEspecialService],
providers: [
CasoEspecialService,
ValidacionService,
DriveService,
gmail,
],
})
export class CasoEspecialModule {}
@@ -1,9 +1,28 @@
import { Module } from '@nestjs/common';
import { CuestionarioAlumno2Service } from './cuestionario-alumno2.service';
import { CuestionarioAlumno2Controller } from './cuestionario-alumno2.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Servicio } from 'src/servicio/entities/servicio.entity';
import { CuestionarioAlumno2 } from './entities/cuestionario-alumno2.entity';
import { CuestionarioAlumno } from 'src/cuestionario-alumno/entities/cuestionario-alumno.entity';
import { ArchivoService } from 'src/helpers.services/archivo.service';
import { ValidacionService } from 'src/helpers.services/validacion.service';
@Module({
imports: [
TypeOrmModule.forFeature([
Servicio, // 👈 asegúrate de incluirlo aquí
CuestionarioAlumno2,
CuestionarioAlumno,
]),
// otros módulos si es necesario
],
controllers: [CuestionarioAlumno2Controller],
providers: [CuestionarioAlumno2Service],
providers: [
CuestionarioAlumno2Service,
ValidacionService,
ArchivoService
],
})
export class CuestionarioAlumno2Module {}
@@ -1,9 +1,27 @@
import { Module } from '@nestjs/common';
import { CuestionarioPrograma2Service } from './cuestionario-programa2.service';
import { CuestionarioPrograma2Controller } from './cuestionario-programa2.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CuestionarioPrograma } from 'src/cuestionario-programa/entities/cuestionario-programa.entity';
import { CuestionarioPrograma2 } from './entities/cuestionario-programa2.entity';
import { Servicio } from 'src/servicio/entities/servicio.entity';
import { ValidacionService } from 'src/helpers.services/validacion.service';
import { ArchivoService } from 'src/helpers.services/archivo.service';
@Module({
imports: [
TypeOrmModule.forFeature([
CuestionarioPrograma, // ✅ este es el que te está faltando
CuestionarioPrograma2,
Servicio,
]),
// módulos de ArchivoService, ValidacionService si aplican
],
controllers: [CuestionarioPrograma2Controller],
providers: [CuestionarioPrograma2Service],
providers: [
CuestionarioPrograma2Service,
ArchivoService,
ValidacionService,
],
})
export class CuestionarioPrograma2Module {}
@@ -98,15 +98,4 @@ export class CuestionarioPrograma2Service {
return this.archivoService.crearArchivo(filePath, convertArrayToCSV(data));
}
findOne(id: number) {
return `This action returns a #${id} cuestionarioPrograma2`;
}
update(id: number, updateCuestionarioPrograma2Dto: UpdateCuestionarioPrograma2Dto) {
return `This action updates a #${id} cuestionarioPrograma2`;
}
remove(id: number) {
return `This action removes a #${id} cuestionarioPrograma2`;
}
}
+2 -2
View File
@@ -37,14 +37,14 @@ export class ValidacionService {
return texto;
}
validarNumeroEntero(numero, campo, m = true) {
validarNumeroEntero(numero, campo, m: boolean = true) {
this.noHay(numero, campo, m);
if (typeof numero === 'number') numero = numero.toString();
if (!validator.isNumeric(numero, { no_symbols: true })) this.noValido(campo, m, 'no es un número entero válido');
return Number(numero);
}
validarCorreo(correo, campo = 'correo', m = true, length) {
validarCorreo(correo: any, length?: any, campo: string = 'correo', m: boolean = true) {
this.validacionBasicaStr(correo, campo, m, length);
if (!validator.isEmail(correo)) this.noValido(campo, m, 'no es un correo válido');
return correo;
+248 -13
View File
@@ -1,26 +1,261 @@
import { Injectable } from '@nestjs/common';
import { CreateProgramaDto } from './dto/create-programa.dto';
import { UpdateProgramaDto } from './dto/update-programa.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as csv from 'csvtojson';
import * as fs from 'fs';
import * as path from 'path';
import * as moment from 'moment';
import { Programa } from './entities/programa.entity';
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';
@Injectable()
export class ProgramaService {
create(createProgramaDto: CreateProgramaDto) {
return 'This action adds a new programa';
constructor(
@InjectRepository(Programa)
private programaRepo: Repository<Programa>,
@InjectRepository(Usuario)
private usuarioRepo: Repository<Usuario>,
private validacionService:ValidacionService,
private authService:AuthService,
private gmail:gmail,
) {}
async cargaMasiva(nombreArchivo: string): Promise<{ message: string }> {
const filePath = path.join('server/uploads', nombreArchivo);
const today = moment();
const programas = await csv().fromFile(filePath);
let mensaje = '';
for (let i = 0; i < programas.length; i++) {
const prog = programas[i];
mensaje += `Línea ${i + 2}\n`;
if (!prog.correo || !prog.nombre || !prog.institucion || !prog.dependencia || !prog.programa || !prog.clave) {
mensaje += 'Faltan campos requeridos. Línea omitida.\n\n';
continue;
}
try {
this.validacionService.validarCorreo(prog.correo);
} catch {
mensaje += 'Correo inválido. Línea omitida.\n\n';
continue;
}
const acatlan = prog.clave.substr(4, 7) === '-12/20-';
const activo = Number(prog.clave.substr(0, 4)) === today.year();
let responsable = await this.usuarioRepo.findOne({
where: {
usuario: prog.correo,
tipoUsuario: {
idTipoUsuario:2
}
},
});
if (!responsable) {
const password = await this.authService.encriptar(this.authService.generarPassword());
const correoInfo = enviarSec(password, prog.correo, prog.nombre);
responsable = await this.usuarioRepo.create({
usuario: prog.correo,
password: password,
nombre: prog.nombre,
activo: true,
tipoUsuario:{
idTipoUsuario: 2
},
});
const sendCorreoDto: SendCorreoDto = {
to: prog.correo,
subject: correoInfo.subject,
fecha_recibido: new Date(),
text: correoInfo.msj,
html: '',
adjuntos: undefined
};
if (!process.env.TOKEN_GMAIL) {
throw new Error('No existe el token');
}
try {
responsable = await this.usuarioRepo.save(responsable);
await this.gmail.send(sendCorreoDto, process.env.TOKEN_GMAIL);
mensaje += `Responsable creado con ID ${responsable.idUsuario} y correo ${responsable.usuario}\n`;
} catch (err) {
mensaje += `Error creando responsable: ${err.message}\n`;
continue;
}
}
if (!acatlan) {
const existente = await this.programaRepo.findOne({
where: { clavePrograma: prog.clave },
relations: ['usuario'],
});
if (!existente) {
try {
const nuevoPrograma = this.programaRepo.create({
institucion: prog.institucion,
dependencia: prog.dependencia,
programa: prog.programa,
clavePrograma: prog.clave,
activo,
usuario:{
idUsuario:responsable.idUsuario
}
});
const creado = await this.programaRepo.save(nuevoPrograma);
mensaje += `Programa ${creado.clavePrograma} asignado al usuario ${responsable.usuario}\n`;
} catch (err) {
mensaje += `Error creando programa: ${err.message}\n`;
}
} else {
mensaje += `Programa ya existe con clave ${existente.clavePrograma}. `;
if (existente.usuario?.usuario === prog.correo) {
mensaje += 'Ya pertenece a este responsable.\n';
} else {
mensaje += `Asignado a otro responsable: ${existente.usuario.usuario}\n`;
}
}
} else {
const existente = await this.programaRepo.findOne({
where: {
clavePrograma: prog.clave,
usuario:{
idUsuario: responsable.idUsuario
},
},
});
if (!existente) {
try {
const nuevoPrograma = this.programaRepo.create({
institucion: 'UNAM',
dependencia: 'FES ACATLAN',
programa: prog.programa,
clavePrograma: prog.clave,
activo,
acatlan,
usuario:{
idUsuario: responsable.idUsuario
},
});
const creado = await this.programaRepo.save(nuevoPrograma);
mensaje += `Programa ACATLÁN ${creado.clavePrograma} asignado a ${responsable.usuario}\n`;
} catch (err) {
mensaje += `Error creando programa: ${err.message}\n`;
}
} else {
mensaje += `Programa ACATLÁN ya pertenece a ${responsable.usuario}\n`;
}
}
mensaje += '\n';
}
fs.unlinkSync(filePath);
return {
message: 'Se subió correctamente el archivo CSV para la carga masiva.',
};
}
findAll() {
return `This action returns all programa`;
async programasAdmin(body: { idUsuario: number }) {
const idUsuario = this.validacionService.validarNumeroEntero(body.idUsuario, 'id usuario');
const usuario = await this.usuarioRepo.findOne({
where: { idUsuario },
});
if (!usuario) {
throw new Error('No existe este usuario.');
}
if (usuario.tipoUsuario.idTipoUsuario !== 2) {
throw new Error('No es un usuario de tipo responsable');
}
return this.programaRepo.find({
where: { usuario:{idUsuario} },
});
}
findOne(id: number) {
return `This action returns a #${id} programa`;
async programasResponsable(body: { idUsuario: number }) {
const idUsuario = this.validacionService.validarNumeroEntero(body.idUsuario, 'id usuario');
const usuario = await this.usuarioRepo.findOne({
where: { idUsuario },
});
if (!usuario) {
throw new Error('No existe este usuario.');
}
if (usuario.tipoUsuario.idTipoUsuario !== 2) {
throw new Error('No es un usuario de tipo responsable');
}
return this.programaRepo.find({
where: {
usuario:{idUsuario},
activo: true,
},
});
}
update(id: number, updateProgramaDto: UpdateProgramaDto) {
return `This action updates a #${id} programa`;
}
async reasignarProgramas(idUsuario:number,correoOtroResponsable:string){
const idusuario= this.validacionService.validarNumeroEntero(idUsuario,'id usuario')
const otroResponsable = this.validacionService.validarCorreo(
correoOtroResponsable
);
remove(id: number) {
return `This action removes a #${id} programa`;
let usuarioNuevo= await this.usuarioRepo.findOne({where:{idUsuario:idusuario}})
if (!usuarioNuevo) throw new Error('No existe este usuario.');
if (usuarioNuevo.tipoUsuario.idTipoUsuario != 2) throw new Error('No es un usuario de tipo responsable');
if (usuarioNuevo.usuario === correoOtroResponsable) throw new Error('Son el mismo usuario.');
let viejoResponsable=await this.usuarioRepo.findOne({where:{usuario:otroResponsable}})
if (!viejoResponsable) throw new Error('No existe este usuario.');
await this.programaRepo.update(
{ usuario:viejoResponsable },
{ usuario:usuarioNuevo }
);
// Eliminar usuario antiguo
await this.usuarioRepo.delete({idUsuario:viejoResponsable.idUsuario});
return {
message: `Se eliminó correctamente al usuario ${correoOtroResponsable} y se reasignaron sus programas.`,
}
}
}
+220 -13
View File
@@ -1,26 +1,233 @@
import { Injectable } from '@nestjs/common';
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { CreateServicioDto } from './dto/create-servicio.dto';
import { UpdateServicioDto } from './dto/update-servicio.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { 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 { SendCorreoDto } from 'src/helpers.services/dto/send-email.dto';
@Injectable()
export class ServicioService {
create(createServicioDto: CreateServicioDto) {
return 'This action adds a new servicio';
constructor(
@InjectRepository(Servicio)
private servicioRepo:Repository<Servicio>,
@InjectRepository(Usuario)
private usuarioRepo:Repository<Usuario>,
private validacionService:ValidacionService,
private gmailService:gmail
){}
async obtenerServicioAlumno(idUsuario: number) {
const usuario = await this.usuarioRepo.findOne({
where: { idUsuario },
});
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.programas', 'programa')
.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.nombre',
'status.idStatus',
'status.descripcion',
])
.getOne();
if (!servicio) {
throw new NotFoundException('No existe este servicio social.');
}
return servicio;
}
findAll() {
return `This action returns all 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.programas', 'programa')
.leftJoinAndSelect('programa.usuario', 'usuarioPrograma')
.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.nombre',
'status.idStatus',
'status.descripcion',
'programa.idPrograma',
'programa.institucion',
'programa.dependencia',
'programa.programa',
'programa.clavePrograma',
'programa.acatlan',
'usuarioPrograma.idUsuario',
'usuarioPrograma.usuario',
'usuarioPrograma.nombre',
])
.getOne();
if (!servicio) {
throw new NotFoundException('No existe este servicio social.');
}
return servicio;
}
findOne(id: number) {
return `This action returns a #${id} servicio`;
async cancelarServicio(id: number,) {
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
],
});
if (!servicio) {
throw new NotFoundException('No existe este Servicio Social.');
}
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: SendCorreoDto = {
to: servicio.correo,
subject: correoAlumno.subject,
fecha_recibido: new Date(),
text: correoAlumno.msj,
html: '',
adjuntos: undefined
};
const sendCorreoResponsableDto: SendCorreoDto = {
to: responsable.usuario,
subject: correoResponsable.subject,
fecha_recibido: new Date(),
text: correoResponsable.msj,
html: '',
adjuntos: undefined
};
if (!process.env.TOKEN_GMAIL) {
throw new Error('No existe el token');
}
if (servicio.correo) {
await this.gmailService.send(sendCorreoAlumnoDto, process.env.TOKEN_GMAIL);
}
if (responsable.usuario) {
await this.gmailService.send(sendCorreoResponsableDto,process.env.TOKEN_GMAIL);
}
// 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.',
};
}
update(id: number, updateServicioDto: UpdateServicioDto) {
return `This action updates a #${id} servicio`;
}
remove(id: number) {
return `This action removes a #${id} servicio`;
}
}