Se agregaron funciones y cambios en los dto

This commit is contained in:
2025-10-16 14:04:30 -06:00
parent cdf3b761e4
commit 09fda8af12
16 changed files with 1470 additions and 296 deletions
+6 -5
View File
@@ -14,6 +14,7 @@ import { gmail } from 'src/helpers.services/gmail.service';
import { Servicio } from 'src/servicio/entities/servicio.entity';
import { Usuario } from 'src/usuario/entities/usuario.entity';
import { Like, Not, Repository } from 'typeorm';
import * as argon2 from 'argon2';
@Injectable()
export class AuthService {
@@ -80,15 +81,15 @@ export class AuthService {
}
async comparar(password, dbPassword) {
if (!bcrypt.compareSync(password, dbPassword)) {
return false;
} else {
if (await argon2.verify(password, dbPassword)) {
return true;
} else {
return false;
}
}
async encriptar(password) {
return bcrypt.hashSync(password, Number(process.env.SALT_ROUNDS));
return await argon2.hash(password);
}
async generarPassword() {
@@ -107,7 +108,7 @@ export class AuthService {
async login(usuario: string, password: string) {
const user = await this.userRepo.findOne({ where: { usuario } });
if (!user) throw new UnauthorizedException('No existe este usuario.');
const match = await bcrypt.compare(password, user.password);
const match = await this.comparar(password, user.password);
if (!match) throw new UnauthorizedException('Credenciales inválidas');
if (!user.activo) throw new Error('Este usuario no esta activo.');
const token = this.jwtCreate(
@@ -1,73 +1,72 @@
import {
IsEmail,
IsNotEmpty,
IsNumberString,
IsOptional,
IsString,
Length,
IsDateString,
IsNumber,
IsIn,
IsInt,
Matches,
} from 'class-validator';
export class CreateCasoEspecialDto {
@IsNumber()
idUsuario: number;
@IsNumber()
@IsInt()
idCarrera: number;
@IsNumber()
@Length(1, 11)
idStatus: number;
@IsString()
@Length(9, 9)
@Matches(/^\d+$/, { message: 'solo puede contener números' })
numeroCuenta: string;
@IsEmail()
correo: string;
@IsString()
@Length(1, 3)
@Matches(/^\d+$/, { message: 'solo puede contener números' })
creditos : string;
@IsString()
@Length(1, 15)
@Matches(/^\d+$/, { message: 'solo puede contener números' })
telefono: string;
@IsDateString()
fechaInicio: Date;
@IsDateString()
fechaFin: Date;
@IsDateString()
fechaNacimiento: Date;
@IsString()
@Length(1, 200)
direccion: string;
@IsOptional()
@IsString()
@Length(1, 200)
institucion?: string;
@IsOptional()
@IsString()
@Length(1, 200)
dependencia?: string;
@IsOptional()
@IsNumber()
@Length(1, 1)
motivo?: string;
}
IsEmail,
IsNotEmpty,
IsNumberString,
IsOptional,
IsString,
Length,
IsDateString,
IsNumber,
IsIn,
IsInt,
Matches,
} from 'class-validator';
export class CreateCasoEspecialDto {
@IsNumber()
idUsuario: number;
@IsNumber()
@IsInt()
idCarrera: number;
@IsNumber()
@Length(1, 11)
idStatus: number;
@IsString()
@Length(9, 9)
@Matches(/^\d+$/, { message: 'solo puede contener números' })
numeroCuenta: string;
@IsEmail()
correo: string;
@IsString()
@Length(1, 3)
@Matches(/^\d+$/, { message: 'solo puede contener números' })
creditos: string;
@IsString()
@Length(1, 15)
@Matches(/^\d+$/, { message: 'solo puede contener números' })
telefono: string;
@IsDateString()
fechaInicio: Date;
@IsDateString()
fechaFin: Date;
@IsDateString()
fechaNacimiento: Date;
@IsString()
@Length(1, 200)
direccion: string;
@IsOptional()
@IsString()
@Length(1, 200)
institucion?: string;
@IsOptional()
@IsString()
@Length(1, 200)
dependencia?: string;
@IsOptional() // Permite que no se envíe
@IsString() // Debe ser string si se envía
@Length(1, 1)
motivo?: string;
}
@@ -1,51 +1,51 @@
import {
IsOptional,
IsString,
IsEmail,
Length,
IsDateString,
IsNumber,
} from 'class-validator';
export class UpdateCasoEspecialDto {
@IsOptional()
@IsEmail()
correo?: string;
@IsOptional()
@IsDateString()
fechaInicio?: Date;
@IsOptional()
@IsDateString()
fechaFin?: Date;
@IsOptional()
@IsDateString()
fechaNacimiento?: Date;
@IsOptional()
@IsString()
@Length(1, 200)
direccion?: string;
@IsOptional()
@IsString()
@Length(1, 15)
telefono?: string;
@IsOptional()
@IsString()
@Length(1, 200)
institucion?: string;
@IsOptional()
@IsString()
@Length(1, 200)
dependencia?: string;
@IsOptional()
@IsNumber()
motivo?: number;
}
IsOptional,
IsString,
IsEmail,
Length,
IsDateString,
IsNumber,
} from 'class-validator';
export class UpdateCasoEspecialDto {
@IsOptional()
@IsEmail()
correo?: string;
@IsOptional()
@IsDateString()
fechaInicio?: Date;
@IsOptional()
@IsDateString()
fechaFin?: Date;
@IsOptional()
@IsDateString()
fechaNacimiento?: Date;
@IsOptional()
@IsString()
@Length(1, 200)
direccion?: string;
@IsOptional()
@IsString()
@Length(1, 15)
telefono?: string;
@IsOptional()
@IsString()
@Length(1, 200)
institucion?: string;
@IsOptional()
@IsString()
@Length(1, 200)
dependencia?: string;
@IsOptional() // Permite que no se envíe
@IsString() // Debe ser string si se envía
@Length(1, 1)
motivo?: string;
}
@@ -8,7 +8,7 @@ import { CuestionarioAlumno2 } from './entities/cuestionario-alumno2.entity';
import { messageByStatus } from 'src/messageByStatus';
import { ArchivoService } from 'src/helpers.services/archivo.service';
import { ValidacionService } from 'src/helpers.services/validacion.service';
const { convertArrayToCSV } = require('convert-array-to-csv');
import { convertArrayToCSV } from 'convert-array-to-csv';
@Injectable()
export class CuestionarioAlumno2Service {
@@ -8,7 +8,7 @@ import { ArchivoService } from 'src/helpers.services/archivo.service';
import { ValidacionService } from 'src/helpers.services/validacion.service';
import { Servicio } from 'src/servicio/entities/servicio.entity';
import { messageByStatus } from 'src/messageByStatus';
const { convertArrayToCSV } = require('convert-array-to-csv');
import { convertArrayToCSV } from 'convert-array-to-csv';
@Injectable()
export class CuestionarioPrograma2Service {
+33 -14
View File
@@ -1,27 +1,46 @@
import { IsString, IsEmail, IsDate, IsOptional } from 'class-validator';
import {
IsString,
IsArray,
IsOptional,
IsISO8601,
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
export class SendCorreoDto {
@IsEmail()
class AdjuntoDto {
@IsString()
filename: string;
@IsString()
content: string;
@IsString()
encoding: string;
@IsOptional()
@IsString()
cid?: string;
}
export class SendMailDto {
@IsString()
to: string;
@IsString()
@IsOptional()
subject?: string;
subject: string;
@IsString()
@IsOptional()
text?: string;
text: string;
@IsString()
@IsOptional()
html?: string;
html: string;
@IsString()
@IsOptional()
fileName?: string;
@IsISO8601()
fecha_recibido: string;
@IsString()
@IsOptional()
filePath?: string;
@IsArray()
@ValidateNested({ each: true })
@Type(() => AdjuntoDto)
adjuntos?: AdjuntoDto[];
}
+27 -86
View File
@@ -1,93 +1,34 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import * as nodemailer from 'nodemailer';
import { SendCorreoDto } from './dto/send-email.dto';
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import axios from 'axios';
import { SendMailDto } from './dto/send-email.dto';
@Injectable()
export class gmail {
private readonly baseUrl = process.env.MAIL_URL;
private readonly token = process.env.MAIL_TOKEN; // idealmente: process.env.MAIL_TOKEN
async sendMail(sendEmail: SendCorreoDto) {
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
type: 'OAuth2',
user: process.env.EMAIL_USER, // tu correo de Gmail
clientId: process.env.CLIENT_ID, // tu Client ID de OAuth2
clientSecret: process.env.CLIENT_SECRET, // tu Client Secret de OAuth2
refreshToken: process.env.REFRESH_TOKEN, // tu Refresh Token de OAuth2
//accessToken: sistem.accessToken, // opcional
},
pool: true,
maxConnections: 1,
maxMessages: 100,
rateDelta: 2000,
rateLimit: 1
});
const mailOptions = {
from: process.env.EMAIL_USER, // tu correo de Gmail
to: sendEmail.to,
subject: sendEmail.subject,
text: sendEmail.text,
html: sendEmail.html,
attachments: [
{
filename: '',
path: '', // ruta local
async enviarCorreo(data: SendMailDto): Promise<any> {
try {
if (!this.baseUrl || !this.token) {
throw new HttpException(
'Configuración de correo no encontrada',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const response = await axios.post(this.baseUrl, data, {
headers: {
'Content-Type': 'application/json',
accept: '*/*',
token: this.token,
},
{
filename: '',
path: '', // adjuntar desde URL
},
{
filename: '',
content: '', // adjunto generado en memoria
},
],
});
};
console.log(mailOptions);
let resMail = await transporter.sendMail(mailOptions);
if (!resMail) throw new Error("")
const statusTexto = resMail.accepted.length > 0 ? "Enviado" : "Fallido";
if (!resMail) {
throw new Error("fallo")
}
return (statusTexto);
return response.data;
} catch (error: any) {
throw new HttpException(
error.response?.data || 'Error al enviar correo',
error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}
}
+105 -20
View File
@@ -1,34 +1,119 @@
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import {
Controller,
Post,
Put,
Get,
Body,
Query,
UploadedFile,
UseGuards,
UseInterceptors,
BadRequestException,
Param,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname } from 'path';
import * as fs from 'fs';
import { AuthGuard } from '@nestjs/passport';
import { ProgramaService } from './programa.service';
import { CreateProgramaDto } from './dto/create-programa.dto';
import { UpdateProgramaDto } from './dto/update-programa.dto';
import { IsEmail, isEmail } from 'class-validator';
class Email {
@IsEmail()
correo: string;
}
@Controller('programa')
export class ProgramaController {
constructor(private readonly programaService: ProgramaService) {}
@Post()
create(@Body() createProgramaDto: CreateProgramaDto) {
return this.programaService.create(createProgramaDto);
// ==================== POST /carga_masiva ====================
@Post('carga_masiva')
@UseGuards(AuthGuard('jwt'))
@UseInterceptors(
FileInterceptor('csv', {
storage: diskStorage({
destination: './server/uploads',
filename: (req, file, cb) => {
const uniqueName = `${Date.now()}${extname(file.originalname)}`;
cb(null, uniqueName);
},
}),
}),
)
async cargaMasiva(@UploadedFile() file: Express.Multer.File) {
if (!file) {
throw new BadRequestException(
'No se envio un archivo csv para la carga masiva.',
);
}
try {
const data = await this.programaService.cargaMasiva(file.filename);
return {
statusCode: 201,
message: 'Carga masiva realizada con éxito',
data,
};
} catch (err) {
// Eliminar archivo si ocurre error
if (file && file.filename) {
fs.unlinkSync(`./server/uploads/${file.filename}`);
}
throw new BadRequestException(err.message);
}
}
@Get()
findAll() {
return this.programaService.findAll();
// ==================== PUT /reasignar_programas ====================
@Put('reasignar_programas/:idUsuario')
@UseGuards(AuthGuard('jwt'))
async reasignarProgramas(
@Body() email: Email,
@Param('idUsuario') idUsuario: number,
) {
try {
const data = await this.programaService.reasignarProgramas(
idUsuario,
email.correo,
);
return {
statusCode: 200,
message: 'Programas reasignados con éxito',
data,
};
} catch (err) {
throw new BadRequestException(err.message);
}
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.programaService.findOne(+id);
// ==================== GET /programas_admin ====================
@Get('programas_admin/:idUsuario')
@UseGuards(AuthGuard('jwt'))
async programasAdmin(@Param('idUsuario') query: number) {
try {
const data = await this.programaService.programasAdmin(query);
return {
statusCode: 200,
data,
};
} catch (err) {
throw new BadRequestException(err.message);
}
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateProgramaDto: UpdateProgramaDto) {
return this.programaService.update(+id, updateProgramaDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.programaService.remove(+id);
// ==================== GET /programas_responsable ====================
@Get('programas_responsable/:idUsuario')
@UseGuards(AuthGuard('jwt'))
async programasResponsable(@Param(`idUsuario`) idUsuario: number) {
try {
const data = await this.programaService.programasResponsable(idUsuario);
return {
statusCode: 200,
data,
};
} catch (err) {
throw new BadRequestException(err.message);
}
}
}
+4 -4
View File
@@ -179,9 +179,9 @@ export class ProgramaService {
};
}
async programasAdmin(body: { idUsuario: number }) {
async programasAdmin(idUser: number) {
const idUsuario = this.validacionService.validarNumeroEntero(
body.idUsuario,
idUser,
'id usuario',
);
@@ -201,9 +201,9 @@ export class ProgramaService {
});
}
async programasResponsable(body: { idUsuario: number }) {
async programasResponsable(idUser: number) {
const idUsuario = this.validacionService.validarNumeroEntero(
body.idUsuario,
idUser,
'id usuario',
);
+42 -1
View File
@@ -1 +1,42 @@
export class CreateServicioDto {}
import {
IsNumber,
IsOptional,
IsString,
IsDateString,
IsEmail,
IsDate,
} from 'class-validator';
export class CrearServicioDto {
@IsNumber()
idUsuario: number;
@IsNumber()
idPrograma: number;
@IsNumber()
idCarrera: number;
@IsString()
numeroCuenta: string;
@IsNumber()
creditos: number;
@IsEmail()
correo: string;
@IsDate()
fechaInicio: Date;
@IsDate()
fechaFin: Date;
@IsOptional()
@IsString()
programaInterno?: string;
@IsOptional()
@IsString()
profesor?: string;
}
+26
View File
@@ -0,0 +1,26 @@
import {
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
MaxLength,
} from 'class-validator';
export class RegistroValidadoDto {
@IsNumber()
@IsNotEmpty()
idServicio: number;
@IsOptional()
fechaNacimiento?: Date;
@IsOptional()
@IsString()
@MaxLength(15)
telefono?: string;
@IsOptional()
@IsString()
@MaxLength(200)
direccion?: string;
}
+19
View File
@@ -0,0 +1,19 @@
import { IsOptional, IsNumber, IsString } from 'class-validator';
export class ServiciosAdminDto {
@IsOptional()
@IsNumber()
pagina?: number;
@IsOptional()
@IsNumber()
idStatus?: number;
@IsOptional()
@IsString()
nombre?: string;
@IsOptional()
@IsString()
numeroCuenta?: string;
}
+15 -1
View File
@@ -1,9 +1,23 @@
import { Module } from '@nestjs/common';
import { ServicioService } from './servicio.service';
import { ServicioController } from './servicio.controller';
import { DriveService } from 'src/drive/drive.service';
import { ValidacionService } from 'src/helpers.services/validacion.service';
import { gmail } from 'src/helpers.services/gmail.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Servicio } from './entities/servicio.entity';
import { Usuario } from 'src/usuario/entities/usuario.entity';
import { ArchivoService } from 'src/helpers.services/archivo.service';
@Module({
imports: [TypeOrmModule.forFeature([Servicio, Usuario])],
controllers: [ServicioController],
providers: [ServicioService],
providers: [
ServicioService,
DriveService,
ValidacionService,
gmail,
ArchivoService,
],
})
export class ServicioModule {}
File diff suppressed because it is too large Load Diff