51 Commits

Author SHA1 Message Date
Emilio-11 9a6a1edae9 Merge branch 'develop' 2026-02-20 08:28:43 -06:00
Emilio-11 f653d12ff9 se agrego una transformaciòn de datos 2026-02-20 08:27:35 -06:00
Emilio-11 9f0379198d Merge remote-tracking branch 'origin/develop' 2026-02-17 10:08:22 -06:00
Emilio-11 dde68bbee1 se normalizaron los n.cuenta a string 2026-02-17 10:07:22 -06:00
evenegas 8a079bdf51 Merge remote-tracking branch 'origin/develop' 2026-02-10 15:13:50 -06:00
Emilio-11 f33977c2f6 se corrigieron cambios 2026-02-10 12:43:57 -06:00
Emilio-11 c79c2e8fc5 se corrigieron cambios 2026-02-10 12:11:29 -06:00
Emilio-11 1fd5320c4b Merge branch 'develop' 2026-02-10 11:56:44 -06:00
Emilio-11 9ae678e7f4 se corrigieron cambios 2026-02-10 11:56:19 -06:00
Emilio-11 1b0ac5828d se agrego nuevo campo para poder activar o desactivar campos 2026-02-10 11:12:42 -06:00
Emilio-11 c328383ec0 se agrego nuevo campo para poder activar o desactivar campos 2026-02-10 11:10:46 -06:00
Emilio-11 2216a70055 Merge branch 'develop' 2026-02-06 13:54:01 -06:00
Emilio-11 2e7d01552d se cambio función 2026-02-06 13:49:36 -06:00
IO a4afb5294d Merge remote-tracking branch 'origin/develop' 2026-02-05 13:38:43 -06:00
IO a288c37531 se cambió el update 2026-02-05 13:13:09 -06:00
IO 656126919b se cambió la estructura 2026-02-05 11:58:59 -06:00
IO 8104fd35ff se cambió la estructura 2026-02-05 11:52:35 -06:00
IO 9ad749782f se cambió la estructura 2026-02-05 11:31:21 -06:00
IO c5722bfe02 se cambió la estructura 2026-02-05 11:00:37 -06:00
IO 08745e2a7e se cambió la estructura 2026-02-05 10:44:38 -06:00
IO 6afc4cdae8 se cambió el la lǵ 2026-02-05 09:10:47 -06:00
IO 05544efc02 Merge remote-tracking branch 'origin/develop' 2026-02-04 15:12:59 -06:00
IO c1283ae9fa se modifico endpoint de cargar 2026-02-04 13:50:47 -06:00
IO 4f0ef219ec se modifico endpoint de cargar 2026-02-04 13:21:47 -06:00
IO 2c91c9cb34 se modifico endpoint de cargar 2026-02-04 13:12:59 -06:00
IO 104f67d07e se modifico endpoint de cargar 2026-02-04 12:53:27 -06:00
evenegas 75c66e6e1d se actualizo funcion 2026-01-22 11:13:44 -06:00
evenegas ea0dc8aba0 se actualizo la funcion de carga masiva y se quitaron console logs 2026-01-09 12:58:08 -06:00
evenegas bdd7b1dd9a se actualizo la funcion de carga masiva 2026-01-09 12:54:22 -06:00
evenegas cea7375e3b Se agregó envío de correos 2025-10-14 13:54:38 -06:00
evenegas e2850c20a3 se agregaron cambios 2025-09-12 14:18:08 -06:00
evenegas c9ce5d6e3a se agregaron cambios 2025-09-12 12:51:32 -06:00
evenegas 91a1fa5f51 se agregaron cambios 2025-09-12 12:50:57 -06:00
evenegas 9d15183601 se agregaron cambios de seguridad 2025-09-12 12:44:55 -06:00
evenegas 55e64da38d se agregaron cambios de seguridad 2025-09-12 12:38:15 -06:00
evenegas 8c6502d82c Se quitò documentación 2025-09-11 10:19:35 -06:00
evenegas 8143b91714 Se agragaron cambios para los envios de correos 2025-09-11 10:04:16 -06:00
evenegas d34f308ab8 Se agregó mov 2025-09-10 09:39:43 -06:00
evenegas c742fa114d Se agregaron cambios en la parte de carrera 2025-09-09 11:01:57 -06:00
evenegas 6e12e580e1 Se agregarón usuarios Externos en Carga Individual 2025-09-09 09:52:05 -06:00
evenegas d9550f1bf1 Se agregarò envìo de correos 2025-09-05 11:05:30 -06:00
evenegas 784b8b1205 se agregò google 2025-09-03 12:34:31 -06:00
evenegas 0ae79f833e se agregaron cambios del inicio de sesiòn con google 2025-09-03 08:44:21 -06:00
evenegas d8d2154cbe Se agregó el inicio de sesion de google 2025-09-02 13:58:37 -06:00
evenegas 8ad6db15dc nombre 2025-08-08 13:32:10 -06:00
evenegas 8a35c6d2ae Se agregaron modificaciones 2025-08-08 12:15:42 -06:00
evenegas 3206b4b824 Se agregaron modificaciones 2025-08-08 12:13:09 -06:00
evenegas 5ee90345f5 Merge remote-tracking branch 'origin/develop' 2025-07-31 14:17:14 -06:00
evenegas 46d889fbfb se hicieron cambios 2025-07-04 12:10:52 -06:00
evenegas 3cbb1536fc Merge branch 'Emilio' 2025-07-04 11:30:55 -06:00
stern 6ff1526650 Modificación a script start para uso de NodeJS 22 2025-07-04 11:20:43 -06:00
21 changed files with 2491 additions and 1381 deletions
+6 -3
View File
@@ -1,14 +1,17 @@
version: '3.9'
services:
database:
image: mariadb:latest
container_name: carga_db
restart: always
environment:
- MARIADB_ROOT_PASSWORD=root
- MARIADB_DATABASE=carga_test
- MARIADB_USER=miusuario
- MARIADB_PASSWORD=root
env_file:
- ../.env
ports:
- "${DB_PORT}:3306"
- '${DB_PORT}:3306'
volumes:
- mariadb_data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
+719 -595
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -8,7 +8,7 @@
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start": "source ~/.nvm/nvm.sh && nvm use 22.10.0 && npm install && nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
@@ -38,10 +38,11 @@
"mysql2": "^3.14.1",
"nodemailer": "^7.0.3",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"typeorm": "^0.3.24"
"typeorm": "^0.3.26"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
+94 -1
View File
@@ -1,14 +1,36 @@
import { AuthDocumentation } from './auth.documentation';
import { Controller, Post, Body, UseGuards, Request, Get } from '@nestjs/common';
import { Controller, Post, Body, UseGuards, Request, Get, Req, Res } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { AuthGuard } from '@nestjs/passport';
import { RegisterDto } from './dto/register.dto';
import { ApiBearerAuth } from '@nestjs/swagger';
import { SetPasswordDto } from './dto/createPassword.dto';
import { Response } from 'express';
import { WhiteDto } from './dto/whiteList.dto';
import { GoogleAuthGuard } from './google-auth.guard';
interface Update{
id:string;
nombreNuevo:string;
}
@Controller()
export class AuthController {
constructor(private readonly authService: AuthService) { }
//Comentar en producciòn
@Post('update')
async update(@Body() user:Update) {
return this.authService.updateCorreo(user.id,user.nombreNuevo);
}
@Get('findAll')
async findAll(){
this.authService.all()
}
@Post('login')
@AuthDocumentation.login()
@@ -34,6 +56,8 @@ export class AuthController {
};
}
@@ -43,6 +67,75 @@ export class AuthController {
return this.authService.register(dto);
}
@Post('white-list')
@AuthDocumentation.white()
async newRegister(@Body() dto:WhiteDto){
return this.authService.white(dto)
}
//Inicio de sesión con GOOGLE
@Get('google')
@UseGuards(AuthGuard('google'))
async googleLogin() {
return { msg: 'Redirigiendo a Google' };
}
// Callback de Google
// @Get('google/callback')
// @UseGuards(AuthGuard('google'))
// async googleCallback(@Req() req) {
// const user = req.user;
// if (user.needsPassword) {
// return {
// status: 'NEEDS_PASSWORD',
// email: user.correo,
// message: 'El usuario debe crear una contraseña antes de continuar',
// };
// }
// return this.authService.login(user); // genera JWT
// }
// auth.controller.ts
@Get('google/callback')
@UseGuards(GoogleAuthGuard)
async googleCallback(@Req() req, @Res() res: Response) {
const user = req.user;
if (user.needsPassword) {
return res.redirect(
`${process.env.FRONTEND_URL}/password?email=${user.correo}`,
);
}
const jwt = await this.authService.login(user);
return res.redirect(
`${process.env.FRONTEND_URL}/oauth-callback?token=${jwt.access_token}`,
);
}
//Inicio de sesión con GOOGLE
//Set-contraseña
// auth.controller.ts
@Post('set-password')
async setPassword(@Body() dto: SetPasswordDto) {
return this.authService.setPassword(dto);
}
}
+36
View File
@@ -101,4 +101,40 @@ export class AuthDocumentation {
);
}
static white() {
return applyDecorators(
ApiTags('Auth'),
ApiOperation({
summary: 'Registrar usuario',
description: 'Crea un nuevo usuario con correo y origen para una white list.'
}),
ApiConsumes('application/json'),
ApiBody({
schema: {
type: 'object',
properties: {
email: { type: 'string', format: 'email', example: 'nuevo@dominio.com' },
origen: { type: 'string', example: 'RED', description: 'Origen del usuario' }
}
}
}),
ApiResponse({
status: 201,
description: 'Usuario creado correctamente',
schema: {
type: 'object',
properties: {
id_usuario: { type: 'number', example: 1 },
correo: { type: 'string', example: 'nuevo@dominio.com' },
origen: { type: 'string', example: 'RED' }
}
}
}),
ApiResponse({ status: 409, description: 'El correo ya está registrado.' }),
);
}
}
+2 -1
View File
@@ -8,6 +8,7 @@ import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
import { Origen, UsuariosDelSistema } from '../entities/entities';
import { TypeOrmModule } from '@nestjs/typeorm';
import { GoogleStrategy } from './google.strategy';
@Module({
imports: [
@@ -22,7 +23,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
}),
}),
],
providers: [AuthService, JwtStrategy],
providers: [AuthService, JwtStrategy, GoogleStrategy],
controllers: [AuthController],
})
export class AuthModule { }
+106
View File
@@ -6,6 +6,8 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Origen, UsuariosDelSistema } from '../entities/entities';
import * as bcrypt from 'bcrypt';
import { RegisterDto } from './dto/register.dto';
import { SetPasswordDto } from './dto/createPassword.dto';
import { WhiteDto } from './dto/whiteList.dto';
@Injectable()
export class AuthService {
@@ -39,8 +41,23 @@ export class AuthService {
};
}
async updateCorreo(id: string, nombreNuevo: string) {
const usuarios = await this.userRepo.findOne({ where: { id_usuario: id } })
if (!usuarios) { throw new Error("usuaro no existe") }
usuarios.correo = nombreNuevo;
return await this.userRepo.save(usuarios);
}
async all() {
const usuarios = await this.userRepo.find()
}
async register(dto: RegisterDto) {
// 1) Verificar duplicado
@@ -75,4 +92,93 @@ export class AuthService {
const { contraseña, ...result } = saved;
return result;
}
async white(dto: WhiteDto) {
// 1) Verificar duplicado
const exists = await this.userRepo.findOne({ where: { correo: dto.email } });
if (exists) {
throw new ConflictException('El correo ya está registrado');
}
let origen = await this.origenRepo.findOne({ where: { origen: dto.origen } });
if (!origen) {
origen = this.origenRepo.create({ origen: dto.origen });
origen = await this.origenRepo.save(origen);
}
// 3) Crear entidad Usuario
const user = this.userRepo.create({
correo: dto.email,
contraseña: undefined,
origen: origen,
});
// 4) Guardar en BD
const saved = await this.userRepo.save(user);
// 5) Devolver sin contraseña
const { contraseña, ...result } = saved;
return result;
}
async validateGoogleUser(email: string) {
const user = await this.userRepo.findOne({ where: { correo: email }, relations: ['origen'] });
if (!user) {
// No permitir crear cuenta
return null;
}
if (!user.contraseña) {
// Usuario existe pero nunca puso contraseña → permitirle definirla
return {
...user,
needsPassword: true,
};
}
// Usuario con contraseña → login normal
return user;
}
//Poner contraseñas
// auth.service.ts
async setPassword(dto: SetPasswordDto) {
const user = await this.userRepo.findOne({
where: { correo: dto.email },
relations: ['origen'],
});
if (!user) {
throw new UnauthorizedException('Usuario no encontrado');
}
if (user.contraseña) {
throw new ConflictException('Este usuario ya tiene contraseña');
}
// Hash contraseña nueva
const hash = await bcrypt.hash(dto.password, 10);
user.contraseña = hash;
const updated = await this.userRepo.save(user);
// Retornar con JWT inmediato (login automático)
const payload = { sub: updated.id_usuario, email: updated.correo, tipo: updated.origen.origen };
return {
access_token: this.jwtService.sign(payload),
};
}
}
+11
View File
@@ -0,0 +1,11 @@
// dto/set-password.dto.ts
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
export class SetPasswordDto {
@IsEmail()
email: string;
@IsNotEmpty()
@MinLength(6)
password: string;
}
+10
View File
@@ -0,0 +1,10 @@
import { IsEmail, IsString, MinLength, MaxLength, IsOptional, IsNumber } from 'class-validator';
export class WhiteDto {
@IsEmail()
email: string;
@IsString()
@MaxLength(60)
origen: string;
}
+17
View File
@@ -0,0 +1,17 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class GoogleAuthGuard extends AuthGuard('google') {
handleRequest(err, user, info, context) {
const res = context.switchToHttp().getResponse();
if (err) {
// redirige directamente y corta la ejecución
res.redirect(`${process.env.FRONTEND_URL}?error=oauth_failed`);
}
return user; // si existe, sigue normalmente
}
}
+44
View File
@@ -0,0 +1,44 @@
// google.strategy.ts
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { ConfigService } from '@nestjs/config';
import { AuthService } from './auth.service';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(
private readonly authService: AuthService,
private readonly config: ConfigService,
) {
super({
clientID: config.get<string>('GOOGLE_CLIENT_ID'),
clientSecret: config.get<string>('GOOGLE_CLIENT_SECRET'),
callbackURL: config.get<string>('GOOGLE_CALLBACK_URL'),
scope: ['email', 'profile'],
failureRedirect: `${process.env.FRONTEND_URL}?error=oauth_failed`,
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: any,
done: VerifyCallback,
): Promise<any> {
try{
const { emails } = profile;
const email = emails[0].value;
const user = await this.authService.validateGoogleUser(email);
if (!user) {
return done(true, false);
}
return done(false, user);
}catch(err){
return done(true, false);
}
}
}
+46 -41
View File
@@ -1,5 +1,14 @@
import { ServActivos } from 'src/usuarios/entities/servActivos.entitie';
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColumn, PrimaryColumn, OneToOne } from 'typeorm';
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
OneToMany,
JoinColumn,
PrimaryColumn,
OneToOne,
} from 'typeorm';
@Entity({ name: 'origen' })
export class Origen {
@@ -9,22 +18,22 @@ export class Origen {
@Column({ type: 'varchar', length: 30 })
origen: string;
@OneToMany(() => Movimiento, movimiento => movimiento.origen)
@OneToMany(() => Movimiento, (movimiento) => movimiento.origen)
movimientos: Movimiento[];
@OneToMany(() => UsuariosDelSistema, uds => uds.origen)
@OneToMany(() => UsuariosDelSistema, (uds) => uds.origen)
usuariosDelSistema: UsuariosDelSistema[];
}
@Entity({ name: 'genero' })
export class Genero {
@PrimaryGeneratedColumn({ name: 'id_genero', type: 'int', })
@PrimaryGeneratedColumn({ name: 'id_genero', type: 'int' })
id_genero: number;
@Column({ type: 'varchar', length: 20, nullable: true })
genero: string;
@OneToMany(() => Usuario, usuario => usuario.genero)
@OneToMany(() => Usuario, (usuario) => usuario.genero)
usuarios: Usuario[];
}
@@ -39,7 +48,7 @@ export class Carrera {
@Column({ type: 'varchar', length: 6 })
clave: string;
@OneToMany(() => CarreraUsuario, cu => cu.carrera)
@OneToMany(() => CarreraUsuario, (cu) => cu.carrera)
carreraUsuarios: CarreraUsuario[];
}
@@ -57,7 +66,7 @@ export class Movimiento {
@Column({
type: 'varchar',
length: 200,
nullable: true, // permite NULL
nullable: true, // permite NULL
})
observaciones?: string;
@@ -67,20 +76,18 @@ export class Movimiento {
@Column({
type: 'varchar',
length: 200,
nullable: true, // permite NULL
nullable: true, // permite NULL
})
reporte?: string;
@OneToMany(() => Usuario, usuario => usuario.movimiento)
@OneToMany(() => Usuario, (usuario) => usuario.movimiento)
usuario: Usuario[];
@ManyToOne(() => Origen, origen => origen.movimientos)
@ManyToOne(() => Origen, (origen) => origen.movimientos)
@JoinColumn({ name: 'id_origen' })
origen: Origen;
}
@Entity({ name: 'usuario' })
export class Usuario {
@PrimaryGeneratedColumn({ name: 'id_usuario', type: 'int' })
@@ -114,43 +121,51 @@ export class Usuario {
})
rfc: string | null;
@Column({ name: 'fecha_nacimiento', type: 'varchar', nullable: true, length: 8 })
@Column({
name: 'fecha_nacimiento',
type: 'varchar',
nullable: true,
length: 8,
})
fecha_nacimiento: string | null;
@Column({ type: 'int', nullable: true, })
@Column({ type: 'int', nullable: true })
generacion: number | null;
@ManyToOne(() => Genero, genero => genero.usuarios, { nullable: true })
@Column({
type: 'bit',
default: () => '1',
})
activo: boolean;
@ManyToOne(() => Genero, (genero) => genero.usuarios, { nullable: true })
@JoinColumn({ name: 'id_genero' })
genero: Genero | null;
@OneToMany(() => CarreraUsuario, cu => cu.usuario)
@OneToMany(() => CarreraUsuario, (cu) => cu.usuario)
carreraUsuarios: CarreraUsuario[];
@OneToMany(() => UsuarioTipoUsuario, utu => utu.usuario)
@OneToMany(() => UsuarioTipoUsuario, (utu) => utu.usuario)
usuarioTipos: UsuarioTipoUsuario[];
@ManyToOne(() => Movimiento, movimiento => movimiento.usuario)
@ManyToOne(() => Movimiento, (movimiento) => movimiento.usuario)
@JoinColumn({ name: 'id_movimiento' })
movimiento: Movimiento;
@OneToOne(() => ServActivos, (servActivo) => servActivo.usuario,)
@OneToOne(() => ServActivos, (servActivo) => servActivo.usuario)
servActivo: ServActivos;
}
@Entity({ name: 'carrera_usuario' })
export class CarreraUsuario {
@PrimaryGeneratedColumn({ name: 'id_carrera_usuario', type: 'int' })
id_carrera_usuario: number;
@ManyToOne(() => Carrera, carrera => carrera.carreraUsuarios)
@ManyToOne(() => Carrera, (carrera) => carrera.carreraUsuarios)
@JoinColumn({ name: 'id_carrera' })
carrera: Carrera;
@ManyToOne(() => Usuario, usuario => usuario.carreraUsuarios)
@ManyToOne(() => Usuario, (usuario) => usuario.carreraUsuarios)
@JoinColumn({ name: 'id_usuario' })
usuario: Usuario;
}
@@ -162,23 +177,21 @@ export class UsuariosDelSistema {
@Column({
type: 'varchar',
length: 100, // ajusta longitud según tu necesidad
length: 100, // ajusta longitud según tu necesidad
unique: true,
nullable: true,
default: null,
})
correo: string | null;
@Column({ type: 'varchar', length: 60 })
@Column({ type: 'varchar', length: 60, default: null })
contraseña: string;
@ManyToOne(() => Origen, origen => origen.usuariosDelSistema)
@ManyToOne(() => Origen, (origen) => origen.usuariosDelSistema)
@JoinColumn({ name: 'id_origen' })
origen: Origen;
}
@Entity({ name: 'equipo' })
export class Equipo {
@PrimaryGeneratedColumn({ name: 'id_equipo', type: 'int' })
@@ -196,33 +209,25 @@ export class TipoUsuario {
@PrimaryGeneratedColumn({ name: 'id_tipo_usuario', type: 'int' })
id_tipo_usuario: number;
@Column({ name: 'tipo_usuario', type: 'varchar', length: 20 })
@Column({ name: 'tipo_usuario', type: 'varchar', length: 30 })
tipo_usuario: string;
@OneToMany(() => UsuarioTipoUsuario, utu => utu.tipoUsuario)
@OneToMany(() => UsuarioTipoUsuario, (utu) => utu.tipoUsuario)
usuariosTipos: UsuarioTipoUsuario[];
}
@Entity({ name: 'usuario_tipo_usuario' })
export class UsuarioTipoUsuario {
@PrimaryGeneratedColumn({ name: 'id_user_tipo_usuario', type: 'int' })
id_user_tipo_usuario: number;
@ManyToOne(() => TipoUsuario, tipo => tipo.usuariosTipos)
@ManyToOne(() => TipoUsuario, (tipo) => tipo.usuariosTipos)
@JoinColumn({ name: 'id_tipo_usuario' })
tipoUsuario: TipoUsuario;
@ManyToOne(() => Usuario, usuario => usuario.usuarioTipos)
@ManyToOne(() => Usuario, (usuario) => usuario.usuarioTipos)
@JoinColumn({ name: 'id_usuario' })
usuario: Usuario;
}
export { ServActivos };
+179 -108
View File
@@ -1,5 +1,21 @@
// src/excel/excel.controller.ts
import { Controller, Post, Get, UseGuards, Request, Res, UploadedFile, UseInterceptors, HttpStatus, Param, ParseIntPipe, Body, BadRequestException } from '@nestjs/common';
import {
Controller,
Post,
Get,
UseGuards,
Request,
Res,
UploadedFile,
UseInterceptors,
HttpStatus,
Param,
ParseIntPipe,
Body,
BadRequestException,
Delete,
Query,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiBearerAuth } from '@nestjs/swagger';
import { FileInterceptor } from '@nestjs/platform-express';
@@ -9,7 +25,6 @@ import { ExcelService } from './excel.service';
import { MovimientoService } from '../movimiento/movimiento.service';
import { MailService } from 'src/mail/mail.service';
import { CreateUsuarioDto } from 'src/usuarios/dto/create-usuario.dto';
import { Origen } from 'src/entities/entities';
import { UsuariosService } from 'src/usuarios/usuarios.service';
@UseGuards(AuthGuard('jwt'))
@@ -20,46 +35,72 @@ export class ExcelController {
private readonly excelService: ExcelService,
private readonly movimientoService: MovimientoService,
private readonly mailService: MailService,
private readonly usuarioService: UsuariosService // Asegúrate de importar el servicio de correo
) { }
private readonly usuarioService: UsuariosService, // Asegúrate de importar el servicio de correo
) {}
@Post('carga')
async cargaUsuario(@Request() req, @Body() usuario: CreateUsuarioDto) {
console.log('Usuario recibido:', usuario);
if (req.user.origen != 'LOAD') {
throw new BadRequestException('Origen no permitido para carga')
if (req.user.origen != 'LOAD' && req.user.origen != 'EXTERNO') {
throw new Error('Origen no permitido para carga');
}
const alta = await this.usuarioService.cargaIndividual(usuario, req.user.origen)
console.log('Alta de usuario:', alta);
await this.movimientoService.updateStatus(alta.movId, 'SUCCESS')
this.mailService.sendMail({
to: req.user.email, // Asegúrate de que el usuario tenga un email
subject: 'Carga de datos exitosa',
text: "La carga de datos se ha realizado con éxito.",
html: `<p>La carga de datos se ha realizado con éxito. ` + alta,
});
await this.excelService.enviarCargaMasiva();
const alta = await this.usuarioService.cargaIndividual(
usuario,
req.user.origen,
req.user.email,
);
if (!alta) {
throw new Error('No se pudo realizar la carga del usuario');
}
await this.movimientoService.updateStatus(alta.movId, 'SUCCESS');
await this.excelService.enviarInforme(
'LOAD',
'Carga Individual de datos en Servicios PCpuma',
`Se ha hecho una carga individual en el sistema por el usuario ${req.user.email}`,
);
if (alta.at) {
await this.excelService.enviarInforme(
'AT',
'Carga Individual de datos en Servicios PCpuma',
`Se ha hecho una carga individual en el sistema de ${usuario.tipo_usuario}`,
);
}
if (alta.red) {
await this.excelService.enviarInforme(
'RED',
'Carga Individual de datos en Servicios PCpuma',
`Se ha hecho una carga individual en el sistema de ${usuario.tipo_usuario}`,
);
}
if (alta.solicita) {
await this.excelService.enviarInforme(
'SOLICITA',
'Carga Individual de datos en Servicios PCpuma',
`Se ha hecho una carga individual en el sistema de ${usuario.tipo_usuario}`,
);
}
if (alta.correo) {
await this.excelService.enviarInforme(
'CORREO',
'Carga Individual de datos en Servicios PCpuma',
`Se ha hecho una carga individual en el sistema de ${usuario.tipo_usuario}`,
);
}
}
@Post('verify')
@UseInterceptors(FileInterceptor('file'))
@ExcelDocumentation.verifyExcel()
async verifyExcel(@UploadedFile() file: Express.Multer.File, @Request() req) {
const userId: number = req.user.userId; // ahora sí existe
const userId: number = req.user.userId; // ahora sí existe
const status = await this.excelService.validateFile(file.buffer);
const errors = status.errors;
await this.movimientoService.log(
'VERIFY',
errors.length ? 'FAILED' : 'SUCCESS',
errors.join('; ')
errors.join('; '),
);
return { valid: errors.length === 0, errors };
}
@@ -67,25 +108,36 @@ export class ExcelController {
@Post('load')
@UseInterceptors(FileInterceptor('file'))
@ExcelDocumentation.loadExcel()
async loadExcel(@Request() req, @UploadedFile() file: Express.Multer.File) {
async loadExcel(
@Request() req,
@UploadedFile() file: Express.Multer.File,
@Query('function') funcion?: number,
) {
const origen = req.user.origen; // ahora sí existe
if (origen != 'LOAD') {
await this.movimientoService.log(
origen,
'FAILED',
'Origen no permitido para carga'
'Origen no permitido para carga',
);
throw new Error('Origen no permitido para carga.');
}
try {
const result = await this.excelService.loadFile(file.buffer);
let result;
if (funcion == 2) {
result = await this.excelService.loadFile2(file.buffer, req.user.email);
} else {
result = await this.excelService.loadFile(file.buffer, req.user.email);
}
//Actualizar el estado de los movimientos
if (!result) {
this.mailService.sendMail({
to: req.user.email, // Asegúrate de que el usuario tenga un email
subject: 'Carga de datos Fallida',
text: "No se pudieron subir datos.",
html: '<p>No se pudieron subir datos.</p>',
text: 'No se pudieron subir datos.',
html: '',
});
throw new Error('No se pudo procesar el archivo.');
}
@@ -94,43 +146,71 @@ export class ExcelController {
this.mailService.sendMail({
to: req.user.email, // Asegúrate de que el usuario tenga un email
subject: 'Carga de datos Fallida',
text: "No se pudieron subir datos.",
html: `<p> Se han insertado ${result.inserted} registros.</p>
<p>Errores encontrados:</p>
<ul>${result.errors.map(error => `<li>${error}</li>`).join('')}</ul>`,
text:
'No se pudieron subir datos.' +
`Se han insertado ${result.inserted} registros.
Errores encontrados:
${result.errors.map((error) => `<li>${error}</li>`).join('')}`,
html: '',
});
return result
return result;
}
console.log(result.id_movimiento)
await this.movimientoService.updateStatus(
result.id_movimiento,
'SUCCESS',
`inserted=${result.inserted}`
`inserted=${result.inserted}`,
);
this.mailService.sendMail({
to: req.user.email, // Asegúrate de que el usuario tenga un email
subject: 'Carga de datos exitosa',
text: "La carga de datos se ha realizado con éxito.",
html: `<p>La carga de datos se ha realizado con éxito. Se han insertado ${result.inserted} registros.</p>
<p>Errores encontrados:</p>
<ul>${result.errors.map(error => `<li>${error}</li>`).join('')}</ul>`,
text: 'La carga de datos se ha realizado con éxito.',
html: `<p> La carga de datos se ha realizado con éxito. Se han insertado ${result.inserted} registros.</p>
<p>Usuarios At: ${result.conteoTiposAt}</p>
<p>Usuarios Red: ${result.conteoTiposRed}</p>
<p>Usuarios Solicita: ${result.conteoTiposSolicita}</p>
<p>Usuarios Correo: ${result.conteoTiposCorreo}</p>
<ul>${result.errors.map((error) => `<li>${error}</li>`).join('')}</ul>`,
});
await this.excelService.enviarCargaMasiva();
// await this.excelService.enviarCargaMasiva();
if (result.conteoTiposAt > 0) {
await this.excelService.enviarInforme(
'AT',
'Carga de datos en Servicios PCpuma',
`Se ha hecho una carga en el sistema de \n ${result.conteoTiposAt} usarios`,
);
}
if (result.conteoTiposRed > 0) {
await this.excelService.enviarInforme(
'RED',
'Carga de datos en Servicios PCpuma',
`Se ha hecho una carga en el sistema \n ${result.conteoTiposRed} usarios`,
);
}
return result
if (result.conteoTiposSolicita > 0) {
await this.excelService.enviarInforme(
'SOLICITA',
'Carga de datos en Servicios PCpuma',
`Se ha hecho una carga en el sistema \n ${result.conteoTiposSolicita} usarios`,
);
}
if (result.conteoTiposCorreo > 0) {
await this.excelService.enviarInforme(
'CORREO',
'Carga de datos en Servicios PCpuma',
`Se ha hecho una carga en el sistema \n ${result.conteoTiposCorreo} usarios`,
);
}
return result;
} catch (err) {
await this.movimientoService.log(
'LOAD',
'FAILED',
err.message
);
await this.movimientoService.log('LOAD', 'FAILED', err.message);
throw err;
}
}
@@ -139,49 +219,48 @@ export class ExcelController {
@ExcelDocumentation.downloadExcel()
async downloadData(@Request() req, @Res() res: Response) {
const origen = req.user.origen; // ahora sí existe
console.log('Origen de descarga:', origen);
console.log('Usuario:', req.user);
if (origen == 'SOLICITA') {
try {
const csv = await this.excelService.generateCsvSolicita();
await this.movimientoService.log(
origen,
'SUCCESS',
'descarga de csv',
`size=${csv.length}`
`size=${csv.length}`,
);
return res
.status(HttpStatus.OK)
.header('Content-Type', 'text/csv')
.header('Content-Disposition', 'attachment; filename="usuarios_solicita.csv"')
.header(
'Content-Disposition',
'attachment; filename="usuarios_solicita.csv"',
)
.send(csv);
} catch (err) {
await this.movimientoService.log(
origen,
'FAILED',
'Origen no permitido para descarga'
'Origen no permitido para descarga',
);
return res
.status(HttpStatus.FORBIDDEN)
.json({ statusCode: 403, message: 'Origen no permitido para descarga.' });
return res.status(HttpStatus.FORBIDDEN).json({
statusCode: 403,
message: 'Origen no permitido para descarga.',
});
}
} else if (origen == 'CORREO' || origen == 'LOAD') {
try {
const csv = await this.excelService.generateCsvCorreo();
await this.movimientoService.log(
origen,
'SUCCESS',
'descarga de csv',
`size=${csv.length}`
`size=${csv.length}`,
);
let filename
let filename;
if (origen == 'CORREO') {
filename = 'usuarios_correo.csv';
}
else {
} else {
filename = 'usuarios.csv';
}
@@ -191,12 +270,7 @@ export class ExcelController {
.header('Content-Disposition', `attachment; filename="${filename}"`)
.send(csv);
} catch (err) {
await this.movimientoService.log(
origen,
'FAILED',
err.message
);
await this.movimientoService.log(origen, 'FAILED', err.message);
return res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ statusCode: 500, message: 'Error generando descarga.' });
@@ -205,24 +279,21 @@ export class ExcelController {
try {
const csv = await this.excelService.generateCsvAT();
await this.movimientoService.log(
origen,
'SUCCESS',
'descarga de csv',
`size=${csv.length}`
`size=${csv.length}`,
);
return res
.status(HttpStatus.OK)
.header('Content-Type', 'text/csv')
.header('Content-Disposition', 'attachment; filename="usuarios_AT.csv"')
.header(
'Content-Disposition',
'attachment; filename="usuarios_AT.csv"',
)
.send(csv);
} catch (err) {
await this.movimientoService.log(
origen,
'FAILED',
err.message
);
await this.movimientoService.log(origen, 'FAILED', err.message);
return res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ statusCode: 500, message: 'Error generando descarga.' });
@@ -231,48 +302,47 @@ export class ExcelController {
try {
const csv = await this.excelService.generateCsvRed();
await this.movimientoService.log(
origen,
'SUCCESS',
'descarga de csv',
`size=${csv.length}`
`size=${csv.length}`,
);
return res
.status(HttpStatus.OK)
.header('Content-Type', 'text/csv')
.header('Content-Disposition', 'attachment; filename="usuarios_red.csv"')
.header(
'Content-Disposition',
'attachment; filename="usuarios_red.csv"',
)
.send(csv);
} catch (err) {
await this.movimientoService.log(
origen,
'FAILED',
err.message
);
await this.movimientoService.log(origen, 'FAILED', err.message);
return res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ statusCode: 500, message: 'Error generando descarga.' });
}
} else {
await this.movimientoService.log(
origen,
'FAILED',
'Origen no permitido para descarga'
'Origen no permitido para descarga',
);
return res
.status(HttpStatus.FORBIDDEN)
.json({ statusCode: 403, message: 'Origen no permitido para descarga.' });
return res.status(HttpStatus.FORBIDDEN).json({
statusCode: 403,
message: 'Origen no permitido para descarga.',
});
}
}
//Agregarlo en su propio controller
@Get('movimientos')
async getMovimientos(@Request() req) {
const movimientos = await this.movimientoService.findAll(req.user.origen);
return movimientos;
@ExcelDocumentation.getMovimientos()
async getMovimientos(
@Request() req,
@Query('page') page: number,
@Query('limit') limit: number,
) {
return this.movimientoService.findAll(req.user.origen, page, limit);
}
@Post('activar-servicios/:id_movimiento')
@@ -291,17 +361,18 @@ export class ExcelController {
return { message: 'Servicios activados correctamente' };
}
@Get('cargaIndividual')
async cargarIndividual(
@Request() req) {
const origen = req.user.origen; // ahora sí existe
return this.movimientoService.findAllCargaIndv(origen);
@ExcelDocumentation.servAct()
@Get('serv_act')
async serv() {
return await this.excelService.buscaAct();
}
}
@Post('mov/:id_movimiento')
async MOV(@Param('id_movimiento', ParseIntPipe) id_movimiento: number) {
return await this.movimientoService.findAllWithRelations(id_movimiento);
}
// @Delete()
// async borrar(){
// return await this.excelService.borrarUsers()
// }
}
+101 -49
View File
@@ -1,16 +1,55 @@
import { applyDecorators } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiConsumes, ApiBody, ApiResponse } from '@nestjs/swagger';
import {
ApiTags,
ApiOperation,
ApiConsumes,
ApiBody,
ApiResponse,
ApiBearerAuth,
ApiQuery,
} from '@nestjs/swagger';
export class ExcelDocumentation {
/**
* Decorators Swagger para el endpoint POST /excel/verify
*/
static getMovimientos() {
return applyDecorators(
ApiBearerAuth(),
ApiOperation({
summary: 'Obtener movimientos',
description:
'Obtiene un listado paginado de movimientos según el origen del usuario autenticado.',
}),
ApiQuery({
name: 'page',
required: false,
type: Number,
example: 1,
description: 'Número de página',
}),
ApiQuery({
name: 'limit',
required: false,
type: Number,
example: 10,
description: 'Cantidad de registros por página',
}),
ApiResponse({
status: 200,
description: 'Listado de movimientos obtenido correctamente',
}),
ApiResponse({
status: 401,
description: 'No autorizado',
}),
);
}
static verifyExcel() {
return applyDecorators(
ApiTags('Excel'),
ApiOperation({
summary: 'Verificar archivo Excel',
description: 'Valida duplicados, filas en blanco y formato básico de los datos sin realizar carga en la base.'
description:
'Valida duplicados, filas en blanco y formato básico de los datos sin realizar carga en la base.',
}),
ApiConsumes('multipart/form-data'),
ApiBody({
@@ -20,10 +59,10 @@ export class ExcelDocumentation {
file: {
type: 'string',
format: 'binary',
description: 'Archivo Excel (.xlsx) con usuarios a validar'
}
}
}
description: 'Archivo Excel (.xlsx) con usuarios a validar',
},
},
},
}),
ApiResponse({
status: 200,
@@ -35,12 +74,18 @@ export class ExcelDocumentation {
errors: {
type: 'array',
items: { type: 'string' },
example: ['Fila 2: cuenta duplicada "42515101".', 'Fila 3: fecha de nacimiento inválida "1988051".']
}
}
}
example: [
'Fila 2: cuenta duplicada "42515101".',
'Fila 3: fecha de nacimiento inválida "1988051".',
],
},
},
},
}),
ApiResponse({
status: 400,
description: 'No se recibió archivo o formato inválido.',
}),
ApiResponse({ status: 400, description: 'No se recibió archivo o formato inválido.' })
);
}
@@ -52,7 +97,8 @@ export class ExcelDocumentation {
ApiTags('Excel'),
ApiOperation({
summary: 'Cargar archivo Excel',
description: 'Valida y persiste los datos en la base de datos. Retorna el número de registros insertados.'
description:
'Valida y persiste los datos en la base de datos. Retorna el número de registros insertados.',
}),
ApiConsumes('multipart/form-data'),
ApiBody({
@@ -62,10 +108,10 @@ export class ExcelDocumentation {
file: {
type: 'string',
format: 'binary',
description: 'Archivo Excel (.xlsx) con usuarios a cargar'
}
}
}
description: 'Archivo Excel (.xlsx) con usuarios a cargar',
},
},
},
}),
ApiResponse({
status: 201,
@@ -73,9 +119,9 @@ export class ExcelDocumentation {
schema: {
type: 'object',
properties: {
inserted: { type: 'number', example: 3 }
}
}
inserted: { type: 'number', example: 3 },
},
},
}),
ApiResponse({
status: 400,
@@ -86,52 +132,58 @@ export class ExcelDocumentation {
errors: {
type: 'array',
items: { type: 'string' },
example: ['Fila 5: rfc contiene caracteres inválidos.']
}
}
}
})
example: ['Fila 5: rfc contiene caracteres inválidos.'],
},
},
},
}),
);
}
static downloadExcel() {
return applyDecorators(
ApiTags('Excel'),
//ApiBearerAuth('bearer'),
ApiOperation({
summary: 'Descargar usuarios',
description: 'Exporta todos los usuarios en un archivo TSV (o CSV) y registra el movimiento.'
description:
'Exporta todos los usuarios en un archivo TSV (o CSV) y registra el movimiento.',
}),
ApiResponse({
status: 200,
description: 'TSV generado correctamente',
content: {
'text/tab-separated-values': {
schema: { type: 'string', example: 'num_cuenta\\tnombre\\t...\\n...' }
}
}
schema: {
type: 'string',
example: 'num_cuenta\\tnombre\\t...\\n...',
},
},
},
}),
ApiResponse({ status: 401, description: 'No autorizado' }),
ApiResponse({ status: 500, description: 'Error al generar descarga' }),
);
}
static servAct() {
return applyDecorators(
ApiTags('Excel'),
ApiOperation({
summary: 'Obtener servicios activos',
description:
'Devuelve la lista de servicios activos registrados en el sistema',
}),
ApiResponse({
status: 200,
description: 'Lista de servicios activos',
// type: ServActivoDto, // si tienes DTO
// isArray: true,
}),
ApiResponse({
status: 401,
description: 'No autorizado',
}),
);
}
}
+742 -253
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -15,11 +15,10 @@ export class MailController {
const result =
await this.mailService.sendMail(body)
.then((value) => {
console.log("Se mandaron los correos", value);
})
.catch((err) => {
console.log(err)
throw new Error(err)
})
-1
View File
@@ -50,7 +50,6 @@ export class MailService {
};
console.log(mailOptions);
+12 -1
View File
@@ -1,12 +1,23 @@
import * as crypto from "crypto";
//(global as any).crypto = crypto;
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { GlobalExceptionFilter } from './helpers/exception.filter';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({ exposedHeaders: ['Content-Disposition'] });
app.enableCors({
origin: [process.env.FRONTEND_URL], // solo frontend permitido
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
});
app.useGlobalPipes(
new ValidationPipe({
+67 -38
View File
@@ -13,7 +13,7 @@ export class MovimientoService {
private readonly origenRepo: Repository<Origen>,
@InjectRepository(Usuario)
private readonly usuarioRepo: Repository<Usuario>,
) { }
) {}
/** Crea un registro de movimiento */
async log(
@@ -54,7 +54,7 @@ export class MovimientoService {
origenNombre: string,
status: string,
observaciones?: string,
reporte?: string
reporte?: string,
): Promise<Movimiento> {
const origenEntidad = await queryRunner.manager.findOne(Origen, {
where: { origen: origenNombre },
@@ -76,17 +76,21 @@ export class MovimientoService {
return await queryRunner.manager.save(nuevoMov);
}
/** Actualiza el status de un movimiento existente */
async updateStatus(id_movimiento: number, nuevoStatus: string, observaciones?: string): Promise<void> {
const movimiento = await this.movRepo.findOne({ where: { id_mov: id_movimiento } });
async updateStatus(
id_movimiento: number,
nuevoStatus: string,
observaciones?: string,
): Promise<void> {
const movimiento = await this.movRepo.findOne({
where: { id_mov: id_movimiento },
});
if (!movimiento) {
throw new Error(`Movimiento con ID ${id_movimiento} no encontrado`);
}
console.log('movimiento' + movimiento);
movimiento.status = nuevoStatus;
movimiento.observaciones = observaciones || movimiento.observaciones; // opcional: actualizar observaciones
movimiento.fecha_mov = new Date(); // opcional: actualizar fecha
@@ -94,7 +98,38 @@ export class MovimientoService {
await this.movRepo.save(movimiento);
}
async findAll(origen: string): Promise<{ move: any[]; button: boolean }> {
async findAll(
origen: string,
page: number = 1,
limit: number = 10,
): Promise<{
move: any[];
button: boolean;
total?: number;
lastPage?: number;
}> {
if (origen === 'LOAD') {
const [movimientos, total] = await this.movRepo.findAndCount({
where: {
origen: { origen: 'LOAD' },
status: 'SUCCESS',
reporte: 'CARGA MASIVA DE USUARIOS',
},
skip: (page - 1) * limit,
take: limit,
order: { fecha_mov: 'DESC' }, // opcional
});
// si quieres incluir también mov y mov2 en la misma paginación:
// const todosLosMovimientos = [...movimientos, ...mov, ...mov2]; (y luego slice)
return {
move: movimientos,
button: false,
total,
lastPage: Math.ceil(total / limit),
};
}
const statusFieldMap = {
AT: 'ATStatus',
RED: 'RedStatus',
@@ -109,8 +144,6 @@ export class MovimientoService {
SOLICITA: 'Prestamos',
} as const;
const field = statusFieldMap[origen as keyof typeof statusFieldMap];
const origenField = FieldMap[origen as keyof typeof FieldMap];
@@ -120,7 +153,6 @@ export class MovimientoService {
origen: { origen: 'LOAD' },
status: 'SUCCESS',
reporte: 'CARGA MASIVA DE USUARIOS',
},
});
@@ -129,26 +161,17 @@ export class MovimientoService {
origen: { origen: 'SISTEMA' },
status: 'SUCCESS',
reporte: 'CARGA MASIVA WEB SERVICE',
},
});
const mov2 = await this.movRepo.find({
where: {
reporte: 'CARGA INDIVIDUAL DE USUARIOS',
status: 'SUCCESS',
},
});
console.log('movimientos:', mov2)
const todosLosMovimientos = [...movimientos, ...mov, ...mov2];
console.log(todosLosMovimientos)
return { move: todosLosMovimientos, button: false };
}
@@ -158,10 +181,11 @@ export class MovimientoService {
reporte: 'CARGA MASIVA DE USUARIOS',
status: 'SUCCESS',
usuario: {
activo: true,
servActivo: {
[origenField]: true,
[field]: 'Inactivo'
[field]: 'Inactivo',
},
},
},
@@ -172,22 +196,23 @@ export class MovimientoService {
reporte: 'CARGA MASIVA WEB SERVICE',
status: 'SUCCESS',
usuario: {
activo: true,
servActivo: {
[origenField]: true,
[field]: 'Inactivo'
[field]: 'Inactivo',
},
},
},
});
const mov2 = await this.movRepo.find({
where: {
reporte: 'CARGA INDIVIDUAL DE USUARIOS',
status: 'SUCCESS',
usuario: {
activo: true,
servActivo: {
[origenField]: true,
[field]: 'Inactivo'
[field]: 'Inactivo',
},
},
},
@@ -199,7 +224,6 @@ export class MovimientoService {
}
async findAllCargaIndv(origen): Promise<Usuario[]> {
const statusFieldMap = {
AT: 'ATStatus',
RED: 'RedStatus',
@@ -214,10 +238,6 @@ export class MovimientoService {
SOLICITA: 'Prestamos',
} as const;
const field = statusFieldMap[origen as keyof typeof statusFieldMap];
const origenField = FieldMap[origen as keyof typeof FieldMap];
@@ -228,8 +248,8 @@ export class MovimientoService {
const usuarios = await this.usuarioRepo.find({
where: {
activo: true,
movimiento: {
reporte: 'CARGA INDIVIDUAL DE USUARIOS',
},
@@ -237,18 +257,27 @@ export class MovimientoService {
[field]: 'Inactivo',
[origenField]: true,
},
},
});
return usuarios;
}
async findAllWithRelations(id: number): Promise<Movimiento[]> {
return await this.movRepo.find({
where: { id_mov: id },
relations: [
'usuario',
'origen',
'usuario.servActivo',
'usuario.carreraUsuarios',
'usuario.carreraUsuarios.carrera',
'usuario.usuarioTipos',
'usuario.usuarioTipos.tipoUsuario',
],
order: {
fecha_mov: 'DESC',
},
});
}
}
+5
View File
@@ -16,6 +16,11 @@ export class UsuariosController {
finsdAll() {
return this.usuariosService.enviarSolicitud();
}
@Get("mov/:move")
findByMov(@Param('move') move:number){
return this.usuariosService.findByMov(move);
}
@Get(':noCuenta')
findOne(@Param('noCuenta') noCuenta: string) {
+290 -286
View File
@@ -2,7 +2,14 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { CreateUsuarioDto } from './dto/create-usuario.dto';
import { UpdateUsuarioDto } from './dto/update-usuario.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Carrera, CarreraUsuario, Genero, TipoUsuario, Usuario, UsuarioTipoUsuario } from 'src/entities/entities';
import {
Carrera,
CarreraUsuario,
Genero,
TipoUsuario,
Usuario,
UsuarioTipoUsuario,
} from 'src/entities/entities';
import { Repository } from 'typeorm';
import { ServActivos } from './entities/servActivos.entitie';
import { MovimientoService } from 'src/movimiento/movimiento.service';
@@ -11,9 +18,6 @@ import { error } from 'console';
import { Cron } from '@nestjs/schedule';
import { ExcelService } from 'src/excel/excel.service';
@Injectable()
export class UsuariosService {
private readonly logger = new Logger(UsuariosService.name);
@@ -36,17 +40,32 @@ export class UsuariosService {
private readonly movimientoService: MovimientoService,
private readonly mailService: MailService,
private readonly excelService: ExcelService, // Asegúrate de importar el servicio de Excel
) { }
) {}
altaIndividual(createUsuarioDto: CreateUsuarioDto) {
return 'This action adds a new usuario';
}
async findByMov(id: number) {
const usuarios = await this.usuarioRepository.find({
where: {
//movimiento:{id_mov:id},
servActivo: {
PrestamosStatus: 'Inactivo', // solo los que no tienen servicio activo
Prestamos: true, // solo los que tienen el servicio de préstamos activo
},
},
relations: ['usuarioTipos', 'usuarioTipos.tipoUsuario', 'servActivo'],
});
if (!usuarios)
throw new Error('No existe suauarios relacionados a ese movimiento');
return usuarios;
}
async findOneStatus(noCuenta: string) {
const usuario = await this.usuarioRepository.findOne({
where: { num_cuenta: noCuenta },
});
if (!usuario) {
@@ -54,11 +73,20 @@ export class UsuariosService {
}
const servActivo = await this.servActivosRepository.findOne({
where: { usuario: { id_usuario: usuario.id_usuario } },
where: { usuario: { activo: true, id_usuario: usuario.id_usuario } },
});
if (!servActivo) {
throw new NotFoundException('Servicios no encontrados para el usuario');
return {
usuario: `${usuario.nombre} ${usuario.a_paterno} ${usuario.a_materno}`,
resultado: [
{
servicio: 'Ninguno',
status: 'El usuario no está activo este semestre',
fecha_activacion: null,
},
],
};
}
const resultado: {
@@ -82,44 +110,33 @@ export class UsuariosService {
resultado.push({
servicio: nombre,
status: 'Inactivo',
fecha_activacion: "",
fecha_activacion: '',
});
}
};
await mapServicio('Red', servActivo.RedStatus);
await mapServicio('Correo', servActivo.CorreoStatus);
await mapServicio('Préstamo de equipo PC Puma', servActivo.PrestamosStatus);
await mapServicio('Préstamo de equipo en CEDETEC', servActivo.ATStatus);
if (resultado.length === 0) {
return [{
servicio: 'Ninguno',
status: 'No hay servicios activos para este usuario',
fecha_activacion: null,
}];
return [
{
servicio: 'Ninguno',
status: 'No hay servicios activos para este usuario',
fecha_activacion: null,
},
];
}
return { resultado, usuario: usuario.nombre };
}
update(id: number, updateUsuarioDto: UpdateUsuarioDto) {
return `This action updates a #${id} usuario`;
}
@Cron('0 0 0 10,25 * *')
async tareaMensual() {
this.logger.log('Ejecutando consulta mensual de profesores');
@@ -131,10 +148,9 @@ export class UsuariosService {
'SISTEMA',
'LOADING',
'Enviando solicitud de consulta masiva de profesores al WebService',
'CARGA MASIVA WEB SERVICE'
'CARGA MASIVA WEB SERVICE',
);
interface DatosEntrada {
Nombre: string;
ApellidoPaterno: string;
@@ -152,289 +168,238 @@ export class UsuariosService {
fechaNacimiento: string;
rfc: string;
genero: string;
correoPersonal: string;
nombramiento: string;
unidadResponsable: string;
curp: string;
}
const apiUrl = process.env.API_URL;
if (!apiUrl) {
console.error("API_URL no está definida en las variables de entorno");
return;
}
if (!apiUrl) return;
const entrada: DatosEntrada = {
Nombre: "",
ApellidoPaterno: "",
ApellidoMaterno: "",
NumeroTrabajador: "",
RFC: ""
Nombre: '',
ApellidoPaterno: '',
ApellidoMaterno: '',
NumeroTrabajador: '',
RFC: '',
};
try {
const response = await fetch(apiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(entrada)
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(entrada),
});
if (response.status === 400) {
const errorText = await response.text();
console.error("Error 400:", errorText);
return;
} else if (response.status === 204) {
console.log("No se encontraron datos conforme a los criterios de búsqueda");
return;
}
if (!response.ok) return;
if (!response.ok) throw new Error("Error en la petición: " + response.statusText);
const json: DatosRespuesta[] = await response.json();
const profesores: DatosRespuesta[] = await response.json();
let nuevos = 0;
let actualizados = 0;
let existe = await this.tipoUsuarioRepository.findOne({ where: { tipo_usuario: 'Profesor' } });
if (!existe) {
const tipoUsuario = this.tipoUsuarioRepository.create({ tipo_usuario: 'Profesor' });
existe = await this.tipoUsuarioRepository.save(tipoUsuario);
/* ==========================
TIPO USUARIO PROFESOR
========================== */
let tipoProfesor = await this.tipoUsuarioRepository.findOne({
where: { tipo_usuario: 'Profesor' },
});
if (!tipoProfesor) {
tipoProfesor = await this.tipoUsuarioRepository.save(
this.tipoUsuarioRepository.create({ tipo_usuario: 'Profesor' }),
);
}
for (const prof of json) {
try {
if (!prof.rfc || !prof.nombre) {
console.warn('Profesor con datos incompletos detectado:', prof);
continue;
}
/* ==========================
CACHE DE CARRERAS
========================== */
const carreraCache = new Map<string, any>();
const existente = await this.usuarioRepository.findOne({
where: { rfc: prof.rfc },
relations: ['genero']
for (const prof of profesores) {
if (!prof.rfc || !prof.nombre) continue;
const usuario = await this.usuarioRepository.findOne({
where: { rfc: prof.rfc },
relations: ['genero'],
});
/* ==========================
NORMALIZAR CARRERA
========================== */
const nombreCarrera = prof.nombreCarrera?.trim().toUpperCase();
let carrera = carreraCache.get(nombreCarrera);
if (!carrera && nombreCarrera) {
carrera = await this.carreraRepository.findOne({
where: { carrera: nombreCarrera },
});
if (!existente) {
let genero: Genero | null = null;
if (prof.genero === 'M' || prof.genero === 'F') {
const generoTexto = prof.genero === 'M' ? 'Masculino' : 'Femenino';
genero = await this.generoRepository.findOne({ where: { genero: generoTexto } });
if (!genero) {
genero = await this.generoRepository.create({ genero: generoTexto });
await this.generoRepository.save(genero);
}
}
if (!carrera) {
carrera = await this.carreraRepository.save(
this.carreraRepository.create({
carrera: nombreCarrera,
clave: '',
}),
);
}
let carrera = await this.carreraRepository.findOne({ where: { carrera: prof.nombreCarrera.trim() } })
carreraCache.set(nombreCarrera, carrera);
}
if (!carrera) {
const nuevaCarrera = await this.carreraRepository.create({ carrera: prof.nombreCarrera.trim(), clave: '' });
carrera = await this.carreraRepository.save(nuevaCarrera);
}
/* ==========================
USUARIO NUEVO
========================== */
if (!usuario) {
let genero;
if (prof.genero === 'M' || prof.genero === 'F') {
const generoTexto = prof.genero === 'M' ? 'Masculino' : 'Femenino';
genero =
(await this.generoRepository.findOne({
where: { genero: generoTexto },
})) ??
(await this.generoRepository.save(
this.generoRepository.create({ genero: generoTexto }),
));
}
const nuevo = await this.usuarioRepository.create({
num_cuenta: prof.numeroTrabajador == '' ? undefined : prof.numeroTrabajador,
const nuevoUsuario = await this.usuarioRepository.save(
this.usuarioRepository.create({
num_cuenta: prof.numeroTrabajador || undefined,
nombre: prof.nombre,
a_paterno: prof.apellidoPaterno,
a_materno: prof.apellidoMaterno,
rfc: prof.rfc,
fecha_nacimiento: prof.fechaNacimiento,
genero,
movimiento: mov,
}),
);
movimiento: mov
});
const savedUser = await this.usuarioRepository.save(nuevo);
const servActivo = await this.servActivosRepository.create({
await this.servActivosRepository.save(
this.servActivosRepository.create({
usuario: nuevoUsuario,
AT: !!prof.numeroTrabajador,
Red: !!prof.numeroTrabajador,
Prestamos: !!prof.numeroTrabajador,
Correo: true,
ATStatus: 'Inactivo',
RedStatus: 'Inactivo',
PrestamosStatus: 'Inactivo',
CorreoStatus: 'Inactivo',
usuario: savedUser,
AT: prof.numeroTrabajador !== '',
Red: prof.numeroTrabajador !== '',
Prestamos: prof.numeroTrabajador !== '',
Correo: true
});
}),
);
await this.servActivosRepository.save(servActivo);
await this.carreraUsuario.save(
this.carreraUsuario.create({
usuario: nuevoUsuario,
carrera,
}),
);
const userCarrera = await this.carreraUsuario.create({ carrera, usuario: savedUser });
const usuarioTipo = await this.usuarioTipoUsuario.create({ tipoUsuario: existe, usuario: savedUser });
await this.usuarioTipoUsuario.save(
this.usuarioTipoUsuario.create({
usuario: nuevoUsuario,
tipoUsuario: tipoProfesor,
}),
);
await this.carreraUsuario.save(userCarrera);
await this.usuarioTipoUsuario.save(usuarioTipo);
nuevos++;
continue;
}
nuevos++;
} else {
let cambios = false;
/* ==========================
USUARIO EXISTENTE
========================== */
let cambios = false;
const servActivo = await this.servActivosRepository.findOne({
where: { usuario: { id_usuario: usuario.id_usuario } },
});
if (existente.nombre !== prof.nombre) {
existente.nombre = prof.nombre;
const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } });
const activarServicios = () => {
if (!servActivo) return;
servActivo.AT = true;
servActivo.Red = true;
servActivo.Prestamos = true;
servActivo.Correo = true;
servActivo.ATStatus = 'Inactivo';
servActivo.RedStatus = 'Inactivo';
servActivo.PrestamosStatus = 'Inactivo';
servActivo.CorreoStatus = 'Inactivo';
};
if (usuario.nombre !== prof.nombre) {
usuario.nombre = prof.nombre;
activarServicios();
cambios = true;
}
if (servActivo) {
servActivo.Red = true;
servActivo.AT = true;
servActivo.Prestamos = true;
if (usuario.a_paterno !== prof.apellidoPaterno) {
usuario.a_paterno = prof.apellidoPaterno;
activarServicios();
cambios = true;
}
if (usuario.a_materno !== prof.apellidoMaterno) {
usuario.a_materno = prof.apellidoMaterno;
activarServicios();
cambios = true;
}
servActivo.ATStatus = 'Inactivo';
servActivo.PrestamosStatus = 'Inactivo';
servActivo.RedStatus = 'Inactivo';
await this.servActivosRepository.save(servActivo);
} else {
console.warn('No se encontró el servicio activo para el usuario:', existente);
}
cambios = true;
}
if (usuario.fecha_nacimiento !== prof.fechaNacimiento) {
usuario.fecha_nacimiento = prof.fechaNacimiento;
activarServicios();
cambios = true;
}
if (existente.a_paterno !== prof.apellidoPaterno) {
existente.a_paterno = prof.apellidoPaterno;
const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } });
if (
prof.numeroTrabajador &&
usuario.num_cuenta !== prof.numeroTrabajador
) {
usuario.num_cuenta = prof.numeroTrabajador;
activarServicios();
cambios = true;
}
if (servActivo) {
servActivo.Red = true;
servActivo.AT = true;
servActivo.Prestamos = true;
servActivo.ATStatus = 'Inactivo';
servActivo.PrestamosStatus = 'Inactivo';
servActivo.RedStatus = 'Inactivo';
await this.servActivosRepository.save(servActivo);
} else {
console.warn('No se encontró el servicio activo para el usuario:', existente);
}
cambios = true;
}
if (existente.a_materno !== prof.apellidoMaterno) {
existente.a_materno = prof.apellidoMaterno;
const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } });
if (servActivo) {
servActivo.Red = true;
servActivo.AT = true;
servActivo.Prestamos = true;
servActivo.ATStatus = 'Inactivo';
servActivo.PrestamosStatus = 'Inactivo';
servActivo.RedStatus = 'Inactivo';
await this.servActivosRepository.save(servActivo);
} else {
console.warn('No se encontró el servicio activo para el usuario:', existente);
}
cambios = true;
}
if (existente.rfc !== prof.rfc) {
existente.rfc = prof.rfc;
const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } });
if (servActivo) {
servActivo.Red = true;
servActivo.Prestamos = true;
servActivo.PrestamosStatus = 'Inactivo';
servActivo.RedStatus = 'Inactivo';
await this.servActivosRepository.save(servActivo);
} else {
console.warn('No se encontró el servicio activo para el usuario:', existente);
}
cambios = true;
}
if (existente.fecha_nacimiento !== prof.fechaNacimiento) {
existente.fecha_nacimiento = prof.fechaNacimiento;
const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } });
if (servActivo) {
servActivo.Prestamos = true;
servActivo.Correo = true;
servActivo.PrestamosStatus = 'Inactivo';
servActivo.CorreoStatus = 'Inactivo';
await this.servActivosRepository.save(servActivo);
} else {
console.warn('No se encontró el servicio activo para el usuario:', existente);
}
cambios = true;
}
if (prof.numeroTrabajador && existente.num_cuenta !== prof.numeroTrabajador) {
existente.num_cuenta = prof.numeroTrabajador;
const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } });
if (servActivo) {
servActivo.Red = true;
servActivo.AT = true;
servActivo.Correo = true;
servActivo.Prestamos = true;
servActivo.CorreoStatus = 'Inactivo';
servActivo.ATStatus = 'Inactivo';
servActivo.PrestamosStatus = 'Inactivo';
servActivo.RedStatus = 'Inactivo';
await this.servActivosRepository.save(servActivo);
} else {
console.warn('No se encontró el servicio activo para el usuario:', existente);
}
cambios = true;
}
if (cambios) {
existente.movimiento = mov;
await this.usuarioRepository.save(existente);
actualizados++;
}
}
console.log(`Progreso -> Nuevos: ${nuevos}, Actualizados: ${actualizados}`);
} catch (innerError) {
console.error('Error procesando profesor:', prof, innerError);
if (cambios) {
usuario.movimiento = mov;
await this.usuarioRepository.save(usuario);
if (servActivo) await this.servActivosRepository.save(servActivo);
actualizados++;
}
}
this.movimientoService.updateStatus(mov.id_mov, 'SUCCESS', 'Carga masiva de web Service completa: ' + `Nuevos: ${nuevos}, Actualizados: ${actualizados}`)
await this.movimientoService.updateStatus(
mov.id_mov,
'SUCCESS',
`Carga masiva finalizada. Nuevos: ${nuevos}, Actualizados: ${actualizados}`,
);
if (!process.env.EMAIL_USER) {
throw new Error()
throw new Error();
}
this.mailService.sendMail({
await this.mailService.sendMail({
to: process.env.EMAIL_USER,
subject: 'Carga de datos exitosa',
text: 'Carga masiva de web Service completa: ' + `Nuevos: ${nuevos}, Actualizados: ${actualizados}`,
html: '<p></p>'
})
this.excelService.enviarCargaMasiva();
text:
'Carga masiva de web Service completa: ' +
`Nuevos: ${nuevos}, Actualizados: ${actualizados}`,
html: '',
});
await this.excelService.enviarCargaMasiva({ nuevos, actualizados });
return `Importación finalizada. Nuevos: ${nuevos}, Actualizados: ${actualizados}`;
} catch (error) {
this.logger.error('Error al consultar profesores desde el WebService', error);
this.logger.error('Error en carga masiva de profesores', error);
throw error;
}
}
async cargaIndividual(usuario: CreateUsuarioDto, origen: string) {
const queryRunner = this.usuarioRepository.manager.connection.createQueryRunner();
async cargaIndividual(
usuario: CreateUsuarioDto,
origen: string,
user: string,
) {
const queryRunner =
this.usuarioRepository.manager.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
@@ -448,24 +413,49 @@ export class UsuariosService {
throw new Error('Ya existe este usuario');
}
const mov = await this.movimientoService.logger(queryRunner, origen, 'LOADING', undefined, 'CARGA INDIVIDUAL DE USUARIOS');
const mov = await this.movimientoService.logger(
queryRunner,
origen,
'LOADING',
'la carga se hizo por: ' + user,
'CARGA INDIVIDUAL DE USUARIOS',
);
let genero: Genero | null = null;
if (usuario.genero === 'M' || usuario.genero === 'F') {
const generoTexto = usuario.genero === 'M' ? 'Masculino' : 'Femenino';
genero = await queryRunner.manager.findOne(Genero, { where: { genero: generoTexto } });
genero = await queryRunner.manager.findOne(Genero, {
where: { genero: generoTexto },
});
if (!genero) {
genero = queryRunner.manager.create(Genero, { genero: generoTexto });
await queryRunner.manager.save(genero);
}
}
let carrera = await queryRunner.manager.findOne(Carrera, { where: { carrera: usuario.carrera } });
if (!carrera) {
const nuevaCarrera = queryRunner.manager.create(Carrera, { carrera: usuario.carrera, clave: usuario.clave_carrera });
carrera = await queryRunner.manager.save(nuevaCarrera);
}
let carrera;
if (usuario.carrera != undefined) {
carrera = await queryRunner.manager.findOne(Carrera, {
where: { carrera: usuario.carrera },
});
if (!carrera) {
carrera = await queryRunner.manager.findOne(Carrera, {
where: { carrera: '' },
});
}
} else {
carrera = await queryRunner.manager.findOne(Carrera, {
where: { carrera: '' },
});
if (!carrera) {
const nuevaCarrera = queryRunner.manager.create(Carrera, {
carrera: '',
clave: '',
});
carrera = await queryRunner.manager.save(nuevaCarrera);
}
}
const nuevo = queryRunner.manager.create(Usuario, {
num_cuenta: usuario.num_cuenta ?? usuario.usuario,
@@ -482,16 +472,33 @@ export class UsuariosService {
const savedUser = await queryRunner.manager.save(nuevo);
const userCarrera = queryRunner.manager.create(CarreraUsuario, { carrera, usuario: savedUser });
const userCarrera = queryRunner.manager.create(CarreraUsuario, {
carrera,
usuario: savedUser,
});
await queryRunner.manager.save(userCarrera);
const FieldMap = [
'Diplomado', 'Extra Largo', 'Servicio Social', 'Idiomas R (UNAM)', 'Idiomas Sabatino',
'Reinscrito', 'Posgrado', 'Intercambio UNAM', 'Movilidad', 'Ampliación de Conocimiento',
'Licenciatura', 'Profesor', 'Trabajadores', 'Caso especial',
'Diplomado',
'Extra Largo',
'Servicio Social',
'Idiomas R (UNAM)',
'Idiomas Sabatino',
'Reinscrito',
'Posgrado',
'Intercambio UNAM',
'Movilidad',
'Ampliación de Conocimiento',
'Licenciatura',
'Profesor',
'Trabajadores',
'Caso especial',
];
if (!FieldMap.includes(usuario.tipo_usuario)) {
throw new Error(`Tipo de usuario no permitido: ${usuario.tipo_usuario}`);
throw new Error(
`Tipo de usuario no permitido: ${usuario.tipo_usuario}`,
);
}
// Verifica si el tipo de usuario ya existe
@@ -514,14 +521,13 @@ export class UsuariosService {
});
await queryRunner.manager.save(usuarioTipo);
let at: boolean = false;
let correo: boolean = false;
let red: boolean = false;
let solicita: boolean = false;
switch (usuario.tipo_usuario.trim()) {
case 'Diplomado':
const servActivosDiplomado = this.servActivosRepository.create({
Correo: true,
usuario: savedUser,
@@ -530,8 +536,8 @@ export class UsuariosService {
CorreoStatus: 'Inactivo',
PrestamosStatus: 'Inactivo',
});
correo = true;
await queryRunner.manager.save(servActivosDiplomado);
break;
case 'Extra Largo':
@@ -543,8 +549,9 @@ export class UsuariosService {
ATStatus: 'Inactivo',
CorreoStatus: 'Inactivo',
PrestamosStatus: 'Inactivo',
});
solicita = true;
correo = true;
await queryRunner.manager.save(servActivosExtraLargo);
break;
@@ -559,11 +566,12 @@ export class UsuariosService {
CorreoStatus: 'Inactivo',
PrestamosStatus: 'Inactivo',
});
at = true;
red = true;
correo = true;
await queryRunner.manager.save(servActivosServicio);
break;
case 'Idiomas R (UNAM)':
case 'Idiomas Sabatino':
const servActivosIdiomas = this.servActivosRepository.create({
@@ -575,13 +583,12 @@ export class UsuariosService {
CorreoStatus: 'Inactivo',
PrestamosStatus: 'Inactivo',
});
red = true;
correo = true;
await queryRunner.manager.save(servActivosIdiomas);
break;
case 'Reinscrito':
case 'Posgrado':
case 'Intercambio UNAM':
@@ -590,7 +597,6 @@ export class UsuariosService {
case 'Licenciatura':
case 'Profesor':
const servActivos = this.servActivosRepository.create({
Red: true,
AT: true,
Correo: true,
@@ -601,36 +607,34 @@ export class UsuariosService {
CorreoStatus: 'Inactivo',
PrestamosStatus: 'Inactivo',
});
at = true;
red = true;
correo = true;
solicita = true;
await queryRunner.manager.save(servActivos);
break;
case 'Caso especial':
case 'Trabajadores':
const servActivosTrabajadores = this.servActivosRepository.create({
Red: true,
usuario: savedUser,
RedStatus: 'Inactivo',
});
red = true;
await queryRunner.manager.save(servActivosTrabajadores);
break;
}
console.log(savedUser)
await queryRunner.commitTransaction();
return { saved: savedUser, movId: mov.id_mov };
return { saved: savedUser, movId: mov.id_mov, at, correo, red, solicita };
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
throw new Error('Error en la carga individual: ' + error.message);
} finally {
await queryRunner.release();
}
}
}