Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 605227aeba | |||
| 3411737e6d | |||
| 3a7f8d3647 | |||
| 2a1a3bc5c0 | |||
| 6114fa2583 |
@@ -1,18 +0,0 @@
|
||||
API_PORT = 4000
|
||||
JWT_SECRET = "your_jwt_secret"
|
||||
JWT_EXPIRES_IN = "1h"
|
||||
DB_HOST = localhost
|
||||
DB_PORT = 32769
|
||||
DB_USER = root
|
||||
DB_PASS = root
|
||||
DB_NAME = carga_test
|
||||
DB_SYNC = true
|
||||
//api
|
||||
API_URL = //URL web service
|
||||
|
||||
|
||||
//Gmail configuration, llaves de acceso
|
||||
EMAIL_USER =
|
||||
CLIENT_ID =
|
||||
CLIENT_SECRET =
|
||||
REFRESH_TOKEN =
|
||||
@@ -1,17 +1,14 @@
|
||||
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
|
||||
|
||||
Generated
+594
-770
File diff suppressed because it is too large
Load Diff
+2
-5
@@ -8,7 +8,7 @@
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "source ~/.nvm/nvm.sh && nvm use 22.10.0 && npm install && nest start",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
@@ -26,7 +26,6 @@
|
||||
"@nestjs/jwt": "^11.0.0",
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-express": "^11.1.3",
|
||||
"@nestjs/schedule": "^6.0.0",
|
||||
"@nestjs/swagger": "^11.2.0",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
@@ -36,13 +35,11 @@
|
||||
"exceljs": "^4.4.0",
|
||||
"multer": "^2.0.1",
|
||||
"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.26"
|
||||
"typeorm": "^0.3.24"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
|
||||
+2
-9
@@ -10,14 +10,10 @@ import { ExcelModule } from './excel/excel.module';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { MovimientoModule } from './movimiento/movimiento.module';
|
||||
import { UsuariosModule } from './usuarios/usuarios.module';
|
||||
import { MailService } from './mail/mail.service';
|
||||
import { MailModule } from './mail/mail.module';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ScheduleModule.forRoot(),
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
@@ -31,7 +27,6 @@ import { ScheduleModule } from '@nestjs/schedule';
|
||||
// no prints en claro en prod:
|
||||
password: config.get('DB_PASS', ''),
|
||||
database: config.get('DB_NAME', 'test'),
|
||||
dropSchema: false, // solo para desarrollo
|
||||
};
|
||||
Logger.log('[DB CONFIG] ' + JSON.stringify({
|
||||
...dbConfig,
|
||||
@@ -44,10 +39,9 @@ import { ScheduleModule } from '@nestjs/schedule';
|
||||
|
||||
logging: ['error', 'warn', 'info', 'query', 'schema'],
|
||||
logger: 'advanced-console',
|
||||
|
||||
retryAttempts: 5,
|
||||
retryDelay: 2000,
|
||||
|
||||
dropSchema: true, // solo para desarrollo
|
||||
};
|
||||
},
|
||||
}),
|
||||
@@ -57,8 +51,7 @@ import { ScheduleModule } from '@nestjs/schedule';
|
||||
ExcelModule,
|
||||
AuthModule,
|
||||
MovimientoModule,
|
||||
UsuariosModule,
|
||||
MailModule
|
||||
UsuariosModule
|
||||
|
||||
],
|
||||
controllers: [AppController],
|
||||
|
||||
@@ -1,36 +1,14 @@
|
||||
import { AuthDocumentation } from './auth.documentation';
|
||||
import { Controller, Post, Body, UseGuards, Request, Get, Req, Res } from '@nestjs/common';
|
||||
import { Controller, Post, Body, UseGuards, Request, Get } 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()
|
||||
@@ -59,83 +37,12 @@ export class AuthController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Post('register')
|
||||
@AuthDocumentation.register() // ver siguiente sección
|
||||
async register(@Body() dto: RegisterDto) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export class AuthDocumentation {
|
||||
properties: {
|
||||
email: { type: 'string', format: 'email', example: 'nuevo@dominio.com' },
|
||||
password: { type: 'string', example: 'Password123' },
|
||||
origen: { type: 'string', example: 'RED', description: 'Origen del usuario' }
|
||||
origen: { type: 'string', example: 'redes', description: 'Origen del usuario' }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -91,43 +91,7 @@ export class AuthDocumentation {
|
||||
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.' }),
|
||||
);
|
||||
}
|
||||
|
||||
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' }
|
||||
origen: { type: 'string', example: 'redes' }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ 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: [
|
||||
@@ -23,7 +22,7 @@ import { GoogleStrategy } from './google.strategy';
|
||||
}),
|
||||
}),
|
||||
],
|
||||
providers: [AuthService, JwtStrategy, GoogleStrategy],
|
||||
providers: [AuthService, JwtStrategy],
|
||||
controllers: [AuthController],
|
||||
})
|
||||
export class AuthModule { }
|
||||
|
||||
@@ -6,8 +6,6 @@ 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 {
|
||||
@@ -41,23 +39,8 @@ 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
|
||||
@@ -92,93 +75,4 @@ 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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// dto/set-password.dto.ts
|
||||
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
|
||||
|
||||
export class SetPasswordDto {
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@MinLength(6)
|
||||
password: string;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { IsEmail, IsString, MinLength, MaxLength, IsOptional, IsNumber } from 'class-validator';
|
||||
|
||||
export class WhiteDto {
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(60)
|
||||
origen: string;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,6 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
|
||||
async validate(payload: any) {
|
||||
// Devuelve lo que estará disponible en Request.user
|
||||
return { userId: payload.sub, email: payload.email, origen: payload.tipo };
|
||||
return { userId: payload.sub, email: payload.email, origen: payload.tipo, tipo: payload.tipo, };
|
||||
}
|
||||
}
|
||||
|
||||
+131
-163
@@ -1,14 +1,4 @@
|
||||
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 } from 'typeorm';
|
||||
|
||||
@Entity({ name: 'origen' })
|
||||
export class Origen {
|
||||
@@ -18,22 +8,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[];
|
||||
}
|
||||
|
||||
@@ -48,10 +38,134 @@ export class Carrera {
|
||||
@Column({ type: 'varchar', length: 6 })
|
||||
clave: string;
|
||||
|
||||
@OneToMany(() => CarreraUsuario, (cu) => cu.carrera)
|
||||
@OneToMany(() => CarreraUsuario, cu => cu.carrera)
|
||||
carreraUsuarios: CarreraUsuario[];
|
||||
}
|
||||
|
||||
@Entity({ name: 'usuario' })
|
||||
export class Usuario {
|
||||
@PrimaryGeneratedColumn({ name: 'id_usuario', type: 'int' })
|
||||
id_usuario: number;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 9,
|
||||
unique: true,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
num_cuenta: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 60 })
|
||||
nombre: string;
|
||||
|
||||
@Column({ name: 'a_paterno', type: 'varchar', length: 60 })
|
||||
a_paterno: string;
|
||||
|
||||
@Column({ name: 'a_materno', type: 'varchar', length: 60 })
|
||||
a_materno: string;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 15,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
rfc: string | null;
|
||||
|
||||
@Column({ name: 'fecha_nacimiento', type: 'varchar', nullable: true, length: 8 })
|
||||
fecha_nacimiento: string | null;
|
||||
|
||||
@Column({ type: 'int', nullable: true, })
|
||||
generacion: number | null;
|
||||
|
||||
@ManyToOne(() => Genero, genero => genero.usuarios, { nullable: true })
|
||||
@JoinColumn({ name: 'id_genero' })
|
||||
genero: Genero | null;
|
||||
|
||||
|
||||
@OneToMany(() => CarreraUsuario, cu => cu.usuario)
|
||||
carreraUsuarios: CarreraUsuario[];
|
||||
|
||||
@OneToMany(() => UsuarioTipoUsuario, utu => utu.usuario)
|
||||
usuarioTipos: UsuarioTipoUsuario[];
|
||||
}
|
||||
@Entity({ name: 'carrera_usuario' })
|
||||
export class CarreraUsuario {
|
||||
@PrimaryGeneratedColumn({ name: 'id_carrera_usuario', type: 'int' })
|
||||
id_carrera_usuario: number;
|
||||
|
||||
@ManyToOne(() => Carrera, carrera => carrera.carreraUsuarios)
|
||||
@JoinColumn({ name: 'id_carrera' })
|
||||
carrera: Carrera;
|
||||
|
||||
@ManyToOne(() => Usuario, usuario => usuario.carreraUsuarios)
|
||||
@JoinColumn({ name: 'id_usuario' })
|
||||
usuario: Usuario;
|
||||
}
|
||||
|
||||
@Entity({ name: 'usuariosDelSistema' })
|
||||
export class UsuariosDelSistema {
|
||||
@PrimaryGeneratedColumn({ name: 'id_usuario', type: 'int' })
|
||||
id_usuario: string;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 100, // ajusta longitud según tu necesidad
|
||||
unique: true,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
correo: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 60 })
|
||||
contraseña: string;
|
||||
|
||||
@ManyToOne(() => Origen, origen => origen.usuariosDelSistema)
|
||||
@JoinColumn({ name: 'id_origen' })
|
||||
origen: Origen;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Entity({ name: 'equipo' })
|
||||
export class Equipo {
|
||||
@PrimaryGeneratedColumn({ name: 'id_equipo', type: 'int' })
|
||||
id_equipo: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 10 })
|
||||
equipo: string;
|
||||
|
||||
@Column({ name: 'numero_serie', type: 'varchar', length: 30 })
|
||||
numero_serie: string;
|
||||
}
|
||||
|
||||
@Entity({ name: 'tipo_usuario' })
|
||||
export class TipoUsuario {
|
||||
@PrimaryGeneratedColumn({ name: 'id_tipo_usuario', type: 'int' })
|
||||
id_tipo_usuario: number;
|
||||
|
||||
@Column({ name: 'tipo_usuario', type: 'varchar', length: 20 })
|
||||
tipo_usuario: string;
|
||||
|
||||
@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)
|
||||
@JoinColumn({ name: 'id_tipo_usuario' })
|
||||
tipoUsuario: TipoUsuario;
|
||||
|
||||
@ManyToOne(() => Usuario, usuario => usuario.usuarioTipos)
|
||||
@JoinColumn({ name: 'id_usuario' })
|
||||
usuario: Usuario;
|
||||
}
|
||||
|
||||
@Entity({ name: 'movimiento' })
|
||||
export class Movimiento {
|
||||
@PrimaryGeneratedColumn({ name: 'id_mov', type: 'int' })
|
||||
@@ -80,154 +194,8 @@ export class Movimiento {
|
||||
})
|
||||
reporte?: string;
|
||||
|
||||
@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' })
|
||||
id_usuario: number;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
unique: true,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
num_cuenta: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 60 })
|
||||
nombre: string;
|
||||
|
||||
@Column({ name: 'a_paterno', type: 'varchar', length: 60 })
|
||||
a_paterno: string;
|
||||
|
||||
@Column({ name: 'a_materno', type: 'varchar', length: 60 })
|
||||
a_materno: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20, nullable: true, default: null })
|
||||
contraseña: string | null;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 15,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
rfc: string | null;
|
||||
|
||||
@Column({
|
||||
name: 'fecha_nacimiento',
|
||||
type: 'varchar',
|
||||
nullable: true,
|
||||
length: 8,
|
||||
})
|
||||
fecha_nacimiento: string | null;
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
generacion: number | null;
|
||||
|
||||
@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)
|
||||
carreraUsuarios: CarreraUsuario[];
|
||||
|
||||
@OneToMany(() => UsuarioTipoUsuario, (utu) => utu.usuario)
|
||||
usuarioTipos: UsuarioTipoUsuario[];
|
||||
|
||||
@ManyToOne(() => Movimiento, (movimiento) => movimiento.usuario)
|
||||
@JoinColumn({ name: 'id_movimiento' })
|
||||
movimiento: Movimiento;
|
||||
|
||||
@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)
|
||||
@JoinColumn({ name: 'id_carrera' })
|
||||
carrera: Carrera;
|
||||
|
||||
@ManyToOne(() => Usuario, (usuario) => usuario.carreraUsuarios)
|
||||
@JoinColumn({ name: 'id_usuario' })
|
||||
usuario: Usuario;
|
||||
}
|
||||
|
||||
@Entity({ name: 'usuariosDelSistema' })
|
||||
export class UsuariosDelSistema {
|
||||
@PrimaryGeneratedColumn({ name: 'id_usuario', type: 'int' })
|
||||
id_usuario: string;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 100, // ajusta longitud según tu necesidad
|
||||
unique: true,
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
correo: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 60, default: null })
|
||||
contraseña: string;
|
||||
|
||||
@ManyToOne(() => Origen, (origen) => origen.usuariosDelSistema)
|
||||
@JoinColumn({ name: 'id_origen' })
|
||||
origen: Origen;
|
||||
}
|
||||
|
||||
@Entity({ name: 'equipo' })
|
||||
export class Equipo {
|
||||
@PrimaryGeneratedColumn({ name: 'id_equipo', type: 'int' })
|
||||
id_equipo: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 10 })
|
||||
equipo: string;
|
||||
|
||||
@Column({ name: 'numero_serie', type: 'varchar', length: 30 })
|
||||
numero_serie: string;
|
||||
}
|
||||
|
||||
@Entity({ name: 'tipo_usuario' })
|
||||
export class TipoUsuario {
|
||||
@PrimaryGeneratedColumn({ name: 'id_tipo_usuario', type: 'int' })
|
||||
id_tipo_usuario: number;
|
||||
|
||||
@Column({ name: 'tipo_usuario', type: 'varchar', length: 30 })
|
||||
tipo_usuario: string;
|
||||
|
||||
@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)
|
||||
@JoinColumn({ name: 'id_tipo_usuario' })
|
||||
tipoUsuario: TipoUsuario;
|
||||
|
||||
@ManyToOne(() => Usuario, (usuario) => usuario.usuarioTipos)
|
||||
@JoinColumn({ name: 'id_usuario' })
|
||||
usuario: Usuario;
|
||||
}
|
||||
|
||||
export { ServActivos };
|
||||
|
||||
+78
-326
@@ -1,21 +1,5 @@
|
||||
// src/excel/excel.controller.ts
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
Get,
|
||||
UseGuards,
|
||||
Request,
|
||||
Res,
|
||||
UploadedFile,
|
||||
UseInterceptors,
|
||||
HttpStatus,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Body,
|
||||
BadRequestException,
|
||||
Delete,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { Controller, Post, Get, UseGuards, Request, Res, UploadedFile, UseInterceptors, HttpStatus, ForbiddenException } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
@@ -23,9 +7,6 @@ import { Response } from 'express';
|
||||
import { ExcelDocumentation } from './excel.documentation';
|
||||
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 { UsuariosService } from 'src/usuarios/usuarios.service';
|
||||
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
@ApiBearerAuth('bearer')
|
||||
@@ -34,345 +15,116 @@ export class ExcelController {
|
||||
constructor(
|
||||
private readonly excelService: ExcelService,
|
||||
private readonly movimientoService: MovimientoService,
|
||||
private readonly mailService: MailService,
|
||||
private readonly usuarioService: UsuariosService, // Asegúrate de importar el servicio de correo
|
||||
) {}
|
||||
) { }
|
||||
|
||||
@Post('carga')
|
||||
async cargaUsuario(@Request() req, @Body() usuario: CreateUsuarioDto) {
|
||||
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,
|
||||
req.user.email,
|
||||
);
|
||||
if (!alta) {
|
||||
throw new Error('No se pudo realizar la carga del usuario');
|
||||
}
|
||||
await this.movimientoService.updateStatus(alta.movId, 'SUCCESS');
|
||||
// excel.controller.ts
|
||||
|
||||
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}`,
|
||||
);
|
||||
}
|
||||
@Post('verify')
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
@ExcelDocumentation.verifyExcel()
|
||||
async verifyExcel(
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@Request() req
|
||||
) {
|
||||
const { userId, tipo } = req.user; // obtenemos ambos
|
||||
const errors = await this.excelService.validateFile(file.buffer);
|
||||
|
||||
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 status = await this.excelService.validateFile(file.buffer);
|
||||
const errors = status.errors;
|
||||
await this.movimientoService.log(
|
||||
'VERIFY',
|
||||
userId, // el ID
|
||||
tipo, // aquí va tu fuente dinámica
|
||||
errors.length ? 'FAILED' : 'SUCCESS',
|
||||
errors.join('; '),
|
||||
errors.join('; ')
|
||||
);
|
||||
|
||||
return { valid: errors.length === 0, errors };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Post('load')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
@ExcelDocumentation.loadExcel()
|
||||
async loadExcel(
|
||||
@Request() req,
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
@ExcelDocumentation.loadExcel()
|
||||
async loadExcel(
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@Query('function') funcion?: number,
|
||||
) {
|
||||
const origen = req.user.origen; // ahora sí existe
|
||||
|
||||
if (origen != 'LOAD') {
|
||||
@Request() req,
|
||||
) {
|
||||
const { userId, tipo } = req.user; // userId es number, tipo tu fuente
|
||||
if (tipo !== 'LOAD') {
|
||||
await this.movimientoService.log(
|
||||
origen,
|
||||
userId,
|
||||
tipo,
|
||||
'FAILED',
|
||||
'Origen no permitido para carga',
|
||||
);
|
||||
throw new Error('Origen no permitido para carga.');
|
||||
throw new ForbiddenException('Origen no permitido para carga.');
|
||||
}
|
||||
|
||||
try {
|
||||
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: '',
|
||||
});
|
||||
throw new Error('No se pudo procesar el archivo.');
|
||||
}
|
||||
|
||||
if (result.inserted == 0) {
|
||||
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.' +
|
||||
`Se han insertado ${result.inserted} registros.
|
||||
Errores encontrados:
|
||||
${result.errors.map((error) => `<li>${error}</li>`).join('')}`,
|
||||
html: '',
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
await this.movimientoService.updateStatus(
|
||||
result.id_movimiento,
|
||||
const { inserted } = await this.excelService.loadFile(file.buffer);
|
||||
await this.movimientoService.log(
|
||||
userId,
|
||||
tipo,
|
||||
'SUCCESS',
|
||||
`inserted=${result.inserted}`,
|
||||
undefined,
|
||||
`inserted=${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>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();
|
||||
|
||||
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`,
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
return { inserted };
|
||||
} catch (err) {
|
||||
await this.movimientoService.log('LOAD', 'FAILED', err.message);
|
||||
await this.movimientoService.log(
|
||||
userId,
|
||||
tipo,
|
||||
'FAILED',
|
||||
err.message,
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Get('download')
|
||||
@ExcelDocumentation.downloadExcel()
|
||||
async downloadData(@Request() req, @Res() res: Response) {
|
||||
const origen = req.user.origen; // ahora sí existe
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
@ApiBearerAuth('bearer')
|
||||
@ExcelDocumentation.downloadExcel()
|
||||
async downloadData(@Request() req, @Res() res: Response) {
|
||||
const { userId, tipo } = req.user;
|
||||
|
||||
if (origen == 'SOLICITA') {
|
||||
try {
|
||||
const csv = await this.excelService.generateCsvSolicita();
|
||||
await this.movimientoService.log(
|
||||
origen,
|
||||
'SUCCESS',
|
||||
'descarga de csv',
|
||||
`size=${csv.length}`,
|
||||
);
|
||||
// Mapeo: tipo → [método de ExcelService, sufijo de filename]
|
||||
const generators: Record<string, [() => Promise<string>, string]> = {
|
||||
SOLICITA: [() => this.excelService.generateCsvSolicita(), 'solicita'],
|
||||
CORREO: [() => this.excelService.generateCsvCorreo(), 'correo'],
|
||||
AT: [() => this.excelService.generateCsvAT(), 'at'],
|
||||
RED: [() => this.excelService.generateCsvRed(), 'red'],
|
||||
};
|
||||
|
||||
const entry = generators[tipo];
|
||||
if (!entry) {
|
||||
await this.movimientoService.log(userId, tipo, 'FAILED', 'Origen no permitido para descarga');
|
||||
return res
|
||||
.status(HttpStatus.OK)
|
||||
.header('Content-Type', 'text/csv')
|
||||
.header(
|
||||
'Content-Disposition',
|
||||
'attachment; filename="usuarios_solicita.csv"',
|
||||
)
|
||||
.send(csv);
|
||||
} catch (err) {
|
||||
await this.movimientoService.log(
|
||||
origen,
|
||||
'FAILED',
|
||||
'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}`,
|
||||
);
|
||||
let filename;
|
||||
if (origen == 'CORREO') {
|
||||
filename = 'usuarios_correo.csv';
|
||||
} else {
|
||||
filename = 'usuarios.csv';
|
||||
.status(HttpStatus.FORBIDDEN)
|
||||
.json({ statusCode: 403, message: 'Origen no permitido para descarga.' });
|
||||
}
|
||||
|
||||
const [generateCsv, suffix] = entry;
|
||||
try {
|
||||
const csv = await generateCsv();
|
||||
await this.movimientoService.log(userId, tipo, 'SUCCESS', undefined, `size=${csv.length}`);
|
||||
return res
|
||||
.status(HttpStatus.OK)
|
||||
.header('Content-Type', 'text/csv')
|
||||
.header('Content-Disposition', `attachment; filename="${filename}"`)
|
||||
.header('Content-Type', 'text/tab-separated-values')
|
||||
.header('Content-Disposition', `attachment; filename="usuarios_${suffix}.tsv"`)
|
||||
.send(csv);
|
||||
} catch (err) {
|
||||
await this.movimientoService.log(origen, 'FAILED', err.message);
|
||||
await this.movimientoService.log(userId, tipo, 'FAILED', err.message);
|
||||
return res
|
||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.json({ statusCode: 500, message: 'Error generando descarga.' });
|
||||
}
|
||||
} else if (origen == 'AT') {
|
||||
try {
|
||||
const csv = await this.excelService.generateCsvAT();
|
||||
await this.movimientoService.log(
|
||||
origen,
|
||||
'SUCCESS',
|
||||
'descarga de csv',
|
||||
`size=${csv.length}`,
|
||||
);
|
||||
return res
|
||||
.status(HttpStatus.OK)
|
||||
.header('Content-Type', 'text/csv')
|
||||
.header(
|
||||
'Content-Disposition',
|
||||
'attachment; filename="usuarios_AT.csv"',
|
||||
)
|
||||
.send(csv);
|
||||
} catch (err) {
|
||||
await this.movimientoService.log(origen, 'FAILED', err.message);
|
||||
return res
|
||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.json({ statusCode: 500, message: 'Error generando descarga.' });
|
||||
}
|
||||
} else if (origen == 'RED') {
|
||||
try {
|
||||
const csv = await this.excelService.generateCsvRed();
|
||||
await this.movimientoService.log(
|
||||
origen,
|
||||
'SUCCESS',
|
||||
'descarga de csv',
|
||||
`size=${csv.length}`,
|
||||
);
|
||||
return res
|
||||
.status(HttpStatus.OK)
|
||||
.header('Content-Type', 'text/csv')
|
||||
.header(
|
||||
'Content-Disposition',
|
||||
'attachment; filename="usuarios_red.csv"',
|
||||
)
|
||||
.send(csv);
|
||||
} catch (err) {
|
||||
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',
|
||||
);
|
||||
return res.status(HttpStatus.FORBIDDEN).json({
|
||||
statusCode: 403,
|
||||
message: 'Origen no permitido para descarga.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//Agregarlo en su propio controller
|
||||
@Get('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')
|
||||
async activarServicios(
|
||||
@Param('id_movimiento', ParseIntPipe) id_movimiento: number,
|
||||
@Request() req,
|
||||
): Promise<{ message: string }> {
|
||||
const origen = req.user.origen;
|
||||
|
||||
if (!origen) {
|
||||
throw new Error('No se encontró origen en el JWT');
|
||||
}
|
||||
|
||||
await this.excelService.activarServicios(id_movimiento, origen);
|
||||
|
||||
return { message: 'Servicios activados correctamente' };
|
||||
}
|
||||
|
||||
@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()
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,55 +1,16 @@
|
||||
import { applyDecorators } from '@nestjs/common';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiConsumes,
|
||||
ApiBody,
|
||||
ApiResponse,
|
||||
ApiBearerAuth,
|
||||
ApiQuery,
|
||||
} from '@nestjs/swagger';
|
||||
import { ApiTags, ApiOperation, ApiConsumes, ApiBody, ApiResponse } from '@nestjs/swagger';
|
||||
|
||||
export class ExcelDocumentation {
|
||||
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',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorators Swagger para el endpoint POST /excel/verify
|
||||
*/
|
||||
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({
|
||||
@@ -59,10 +20,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,
|
||||
@@ -74,18 +35,12 @@ export class ExcelDocumentation {
|
||||
errors: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
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.',
|
||||
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.' })
|
||||
);
|
||||
}
|
||||
|
||||
@@ -97,8 +52,7 @@ 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({
|
||||
@@ -108,10 +62,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,
|
||||
@@ -119,9 +73,9 @@ export class ExcelDocumentation {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
inserted: { type: 'number', example: 3 },
|
||||
},
|
||||
},
|
||||
inserted: { type: 'number', example: 3 }
|
||||
}
|
||||
}
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 400,
|
||||
@@ -132,58 +86,52 @@ 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',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,21 +2,15 @@ import { Module } from '@nestjs/common';
|
||||
import { ExcelService } from './excel.service';
|
||||
import { ExcelController } from './excel.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Usuario, Genero, Carrera, CarreraUsuario, UsuarioTipoUsuario, TipoUsuario, UsuariosDelSistema } from '../entities/entities';
|
||||
import { Usuario, Genero, Carrera, CarreraUsuario, UsuarioTipoUsuario, TipoUsuario } from '../entities/entities';
|
||||
import { MovimientoModule } from 'src/movimiento/movimiento.module';
|
||||
import { ServActivos } from 'src/usuarios/entities/servActivos.entitie';
|
||||
import { MailModule } from 'src/mail/mail.module';
|
||||
import { UsuariosService } from 'src/usuarios/usuarios.service';
|
||||
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Usuario, Genero, Carrera, CarreraUsuario, UsuarioTipoUsuario, TipoUsuario, ServActivos, UsuariosDelSistema]),
|
||||
MovimientoModule,
|
||||
MailModule,
|
||||
|
||||
TypeOrmModule.forFeature([Usuario, Genero, Carrera, CarreraUsuario, UsuarioTipoUsuario, TipoUsuario]),
|
||||
MovimientoModule
|
||||
],
|
||||
providers: [ExcelService, UsuariosService],
|
||||
providers: [ExcelService],
|
||||
controllers: [ExcelController],
|
||||
})
|
||||
export class ExcelModule { }
|
||||
|
||||
+324
-949
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
||||
import { IsString, IsEmail, IsDate, IsOptional } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class SendCorreoDto {
|
||||
@IsEmail()
|
||||
to: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
subject: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
text: string;
|
||||
|
||||
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
html: string;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// src/excel/excel.controller.ts
|
||||
import { Controller, Post, Get, UseGuards, Request, Res, UploadedFile, UseInterceptors, HttpStatus, Param, ParseIntPipe, Body } from '@nestjs/common';
|
||||
import { SendCorreoDto } from './dto/send-email.dto';
|
||||
import { MailService } from './mail.service';
|
||||
|
||||
|
||||
@Controller('mail')
|
||||
export class MailController {
|
||||
constructor(
|
||||
private readonly mailService: MailService
|
||||
) { }
|
||||
|
||||
@Post('send')
|
||||
async sendMail(@Body() body: SendCorreoDto) {
|
||||
const result =
|
||||
await this.mailService.sendMail(body)
|
||||
.then((value) => {
|
||||
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new Error(err)
|
||||
})
|
||||
|
||||
|
||||
// sistem
|
||||
return result;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// src/movimiento/movimiento.module.ts
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MailService } from './mail.service';
|
||||
import { MailController } from './mail.controller';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [MailController],
|
||||
providers: [MailService],
|
||||
exports: [MailService],
|
||||
})
|
||||
export class MailModule { }
|
||||
@@ -1,75 +0,0 @@
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import * as nodemailer from 'nodemailer';
|
||||
import { SendCorreoDto } from 'src/mail/dto/send-email.dto';
|
||||
|
||||
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class MailService {
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+1
-12
@@ -1,23 +1,12 @@
|
||||
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({
|
||||
origin: [process.env.FRONTEND_URL], // solo frontend permitido
|
||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||
credentials: true,
|
||||
});
|
||||
app.enableCors();
|
||||
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// src/movimiento/movimiento.controller.ts
|
||||
import { Controller, Get, UseGuards } from '@nestjs/common';
|
||||
import { MovimientoService } from './movimiento.service';
|
||||
import { Movimiento } from '../entities/entities';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { MovimientoDocumentation } from './movimiento.documentation';
|
||||
|
||||
@ApiTags('Movimientos')
|
||||
@ApiBearerAuth('bearer')
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
@Controller('movimientos')
|
||||
export class MovimientoController {
|
||||
constructor(private readonly movimientoService: MovimientoService) {}
|
||||
|
||||
@Get()
|
||||
@MovimientoDocumentation.findAllmov()
|
||||
@ApiOperation({ summary: 'Listar movimientos', description: 'Obtiene todos los registros de movimientos con usuario y origen.' })
|
||||
@ApiResponse({ status: 200, description: 'Listado de movimientos', type: Movimiento, isArray: true })
|
||||
async findAll(): Promise<Movimiento[]> {
|
||||
return this.movimientoService.findAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { applyDecorators } from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { Movimiento } from '../entities/entities';
|
||||
|
||||
export class MovimientoDocumentation {
|
||||
/**
|
||||
* Decorators Swagger para el endpoint GET /movimientos
|
||||
*/
|
||||
static findAllmov() {
|
||||
return applyDecorators(
|
||||
ApiTags('Movimientos'),
|
||||
ApiBearerAuth('bearer'),
|
||||
ApiOperation({
|
||||
summary: 'Listar todos los movimientos',
|
||||
description: 'Devuelve el histórico completo de movimientos, incluyendo usuario y origen.'
|
||||
}),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
description: 'Listado de movimientos exitoso.',
|
||||
type: Movimiento,
|
||||
isArray: true
|
||||
}),
|
||||
ApiResponse({ status: 401, description: 'No autorizado.' }),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { MovimientoService } from './movimiento.service';
|
||||
import { Movimiento, Origen, Usuario } from '../entities/entities';
|
||||
import { Movimiento, Origen } from '../entities/entities';
|
||||
import { MovimientoController } from './movimiento.controller';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Movimiento, Origen, Usuario])],
|
||||
imports: [TypeOrmModule.forFeature([Movimiento, Origen])],
|
||||
providers: [MovimientoService],
|
||||
controllers: [MovimientoController],
|
||||
|
||||
exports: [MovimientoService],
|
||||
})
|
||||
export class MovimientoModule { }
|
||||
export class MovimientoModule {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { QueryRunner, Repository } from 'typeorm';
|
||||
import { Movimiento, Usuario } from '../entities/entities';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Movimiento } from '../entities/entities';
|
||||
import { Origen } from '../entities/entities';
|
||||
|
||||
@Injectable()
|
||||
@@ -11,273 +11,42 @@ export class MovimientoService {
|
||||
private readonly movRepo: Repository<Movimiento>,
|
||||
@InjectRepository(Origen)
|
||||
private readonly origenRepo: Repository<Origen>,
|
||||
@InjectRepository(Usuario)
|
||||
private readonly usuarioRepo: Repository<Usuario>,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
/** Crea un registro de movimiento */
|
||||
async log(
|
||||
origen: string,
|
||||
usuarioId: number,
|
||||
fuente: string,
|
||||
status: string,
|
||||
observaciones?: string,
|
||||
reporte?: string,
|
||||
flag = false,
|
||||
): Promise<Movimiento> {
|
||||
) {
|
||||
// 1) Obtén el origen o falla si no existe
|
||||
const origin = await this.origenRepo.findOne({ where: { origen } });
|
||||
let origenRepo = origin;
|
||||
if (!origin) {
|
||||
const newOrigin = this.origenRepo.create({ origen });
|
||||
origenRepo = await this.origenRepo.save(newOrigin);
|
||||
}
|
||||
if (!origenRepo) {
|
||||
throw new Error(`No se pudo obtener o crear el origen: ${origen}`);
|
||||
}
|
||||
const origin = await this.origenRepo.findOne({ where: { origen:fuente } });
|
||||
if (!origin) throw new Error(`Origen desconocido: ${fuente}`);
|
||||
|
||||
// 2) Crea el objeto movimiento
|
||||
const movimiento = this.movRepo.create({
|
||||
// 2) Inserta directamente usando los campos de FK
|
||||
await this.movRepo.insert({
|
||||
fecha_mov: new Date(),
|
||||
status,
|
||||
observaciones,
|
||||
reporte,
|
||||
observaciones, // aquí usas el valor que vino como parámetro
|
||||
reporte, // idem
|
||||
flag,
|
||||
origen: { id_origen: origenRepo.id_origen },
|
||||
});
|
||||
|
||||
// 3) Guarda y devuelve el ID
|
||||
const saved = await this.movRepo.save(movimiento);
|
||||
return saved;
|
||||
}
|
||||
|
||||
async logger(
|
||||
queryRunner: QueryRunner,
|
||||
origenNombre: string,
|
||||
status: string,
|
||||
observaciones?: string,
|
||||
reporte?: string,
|
||||
): Promise<Movimiento> {
|
||||
const origenEntidad = await queryRunner.manager.findOne(Origen, {
|
||||
where: { origen: origenNombre },
|
||||
});
|
||||
|
||||
if (!origenEntidad) {
|
||||
throw new Error(`Origen no encontrado: ${origenNombre}`);
|
||||
}
|
||||
|
||||
const nuevoMov = queryRunner.manager.create(Movimiento, {
|
||||
fecha_mov: new Date(),
|
||||
status,
|
||||
observaciones,
|
||||
reporte,
|
||||
origen: origenEntidad,
|
||||
flag: false,
|
||||
});
|
||||
|
||||
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 },
|
||||
});
|
||||
|
||||
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
|
||||
|
||||
await this.movRepo.save(movimiento);
|
||||
}
|
||||
|
||||
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',
|
||||
CORREO: 'CorreoStatus',
|
||||
SOLICITA: 'PrestamosStatus',
|
||||
} as const;
|
||||
|
||||
const FieldMap = {
|
||||
AT: 'AT',
|
||||
RED: 'Red',
|
||||
CORREO: 'Correo',
|
||||
SOLICITA: 'Prestamos',
|
||||
} as const;
|
||||
|
||||
const field = statusFieldMap[origen as keyof typeof statusFieldMap];
|
||||
const origenField = FieldMap[origen as keyof typeof FieldMap];
|
||||
|
||||
if (!field) {
|
||||
const movimientos = await this.movRepo.find({
|
||||
where: {
|
||||
origen: { origen: 'LOAD' },
|
||||
status: 'SUCCESS',
|
||||
reporte: 'CARGA MASIVA DE USUARIOS',
|
||||
},
|
||||
});
|
||||
|
||||
const mov = await this.movRepo.find({
|
||||
where: {
|
||||
origen: { origen: 'SISTEMA' },
|
||||
status: 'SUCCESS',
|
||||
reporte: 'CARGA MASIVA WEB SERVICE',
|
||||
},
|
||||
});
|
||||
|
||||
const mov2 = await this.movRepo.find({
|
||||
where: {
|
||||
reporte: 'CARGA INDIVIDUAL DE USUARIOS',
|
||||
status: 'SUCCESS',
|
||||
},
|
||||
});
|
||||
|
||||
const todosLosMovimientos = [...movimientos, ...mov, ...mov2];
|
||||
return { move: todosLosMovimientos, button: false };
|
||||
}
|
||||
|
||||
const movimientos = await this.movRepo.find({
|
||||
where: {
|
||||
origen: { origen: 'LOAD' },
|
||||
reporte: 'CARGA MASIVA DE USUARIOS',
|
||||
status: 'SUCCESS',
|
||||
usuario: {
|
||||
activo: true,
|
||||
servActivo: {
|
||||
[origenField]: true,
|
||||
|
||||
[field]: 'Inactivo',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const mov = await this.movRepo.find({
|
||||
where: {
|
||||
origen: { origen: 'SISTEMA' },
|
||||
reporte: 'CARGA MASIVA WEB SERVICE',
|
||||
status: 'SUCCESS',
|
||||
usuario: {
|
||||
activo: true,
|
||||
servActivo: {
|
||||
[origenField]: true,
|
||||
[field]: 'Inactivo',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const mov2 = await this.movRepo.find({
|
||||
where: {
|
||||
reporte: 'CARGA INDIVIDUAL DE USUARIOS',
|
||||
status: 'SUCCESS',
|
||||
usuario: {
|
||||
activo: true,
|
||||
servActivo: {
|
||||
[origenField]: true,
|
||||
[field]: 'Inactivo',
|
||||
},
|
||||
},
|
||||
},
|
||||
relations: ['usuario'],
|
||||
});
|
||||
const todosLosMovimientos = [...movimientos, ...mov, ...mov2];
|
||||
|
||||
return { move: todosLosMovimientos, button: true };
|
||||
}
|
||||
|
||||
async findAllCargaIndv(origen): Promise<Usuario[]> {
|
||||
const statusFieldMap = {
|
||||
AT: 'ATStatus',
|
||||
RED: 'RedStatus',
|
||||
CORREO: 'CorreoStatus',
|
||||
SOLICITA: 'PrestamosStatus',
|
||||
} as const;
|
||||
|
||||
const FieldMap = {
|
||||
AT: 'AT',
|
||||
RED: 'Red',
|
||||
CORREO: 'Correo',
|
||||
SOLICITA: 'Prestamos',
|
||||
} as const;
|
||||
|
||||
const field = statusFieldMap[origen as keyof typeof statusFieldMap];
|
||||
|
||||
const origenField = FieldMap[origen as keyof typeof FieldMap];
|
||||
|
||||
if (!field) {
|
||||
throw new Error(`Origen desconocido: ${origen}`);
|
||||
}
|
||||
|
||||
const usuarios = await this.usuarioRepo.find({
|
||||
where: {
|
||||
activo: true,
|
||||
movimiento: {
|
||||
reporte: 'CARGA INDIVIDUAL DE USUARIOS',
|
||||
},
|
||||
|
||||
servActivo: {
|
||||
[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',
|
||||
},
|
||||
origen: { id_origen: origin.id_origen },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** Devuelve todos los movimientos con usuario y origen */
|
||||
async findAll(): Promise<Movimiento[]> {
|
||||
return this.movRepo.find({
|
||||
relations: [ 'origen'],
|
||||
order: { fecha_mov: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,53 +1 @@
|
||||
import { IsNumber, IsOptional, IsString, Length } from "class-validator";
|
||||
|
||||
export class CreateUsuarioDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
num_cuenta?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
usuario?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
carrera?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
contraseña?: string;
|
||||
|
||||
@IsString()
|
||||
tipo_usuario: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
clave_carrera?: string;
|
||||
|
||||
@IsString()
|
||||
nombre: string;
|
||||
|
||||
@IsString()
|
||||
a_paterno: string;
|
||||
|
||||
@IsString()
|
||||
a_materno: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Length(10, 13)
|
||||
rfc?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Length(8, 8)
|
||||
fecha_nacimiento?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
generacion?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
genero?: string;
|
||||
}
|
||||
export class CreateUsuarioDto {}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { Usuario } from "src/entities/entities";
|
||||
import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity({ name: 'servActivos' })
|
||||
export class ServActivos {
|
||||
@PrimaryGeneratedColumn({ name: 'id_servActivos' })
|
||||
id_servActivos: number;
|
||||
|
||||
@OneToOne(() => Usuario, (usuario) => usuario.servActivo, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'id_usuario' })
|
||||
usuario: Usuario;
|
||||
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
Red: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
Prestamos: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
AT: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
Correo: boolean;
|
||||
|
||||
@Column({ type: 'varchar', length: 40, nullable: true })
|
||||
RedStatus: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 40, nullable: true })
|
||||
PrestamosStatus: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 40, nullable: true })
|
||||
ATStatus: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 40, nullable: true })
|
||||
CorreoStatus: string;
|
||||
|
||||
|
||||
}
|
||||
@@ -12,23 +12,10 @@ export class UsuariosController {
|
||||
|
||||
|
||||
|
||||
@Post('users')
|
||||
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) {
|
||||
return this.usuariosService.findOneStatus(noCuenta);
|
||||
return this.usuariosService.findOne(noCuenta);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// @UseGuards(AuthGuard('jwt'))
|
||||
// @ApiBearerAuth('bearer')
|
||||
// @Post()
|
||||
|
||||
@@ -2,20 +2,15 @@ import { Module } from '@nestjs/common';
|
||||
import { UsuariosService } from './usuarios.service';
|
||||
import { UsuariosController } from './usuarios.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Carrera, CarreraUsuario, Genero, TipoUsuario, Usuario, UsuariosDelSistema, UsuarioTipoUsuario } from 'src/entities/entities';
|
||||
import { Carrera, CarreraUsuario, Genero, Usuario } from 'src/entities/entities';
|
||||
import { MovimientoModule } from 'src/movimiento/movimiento.module';
|
||||
import { ServActivos } from './entities/servActivos.entitie';
|
||||
import { MailService } from 'src/mail/mail.service';
|
||||
import { ExcelService } from 'src/excel/excel.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Usuario, Genero, Carrera, CarreraUsuario, ServActivos, TipoUsuario, UsuarioTipoUsuario, UsuariosDelSistema]),
|
||||
TypeOrmModule.forFeature([Usuario, Genero, Carrera, CarreraUsuario]),
|
||||
MovimientoModule
|
||||
|
||||
],
|
||||
controllers: [UsuariosController],
|
||||
providers: [UsuariosService, MailService, ExcelService],
|
||||
exports: [TypeOrmModule.forFeature([ServActivos])],
|
||||
providers: [UsuariosService],
|
||||
})
|
||||
export class UsuariosModule { }
|
||||
|
||||
@@ -1,628 +1,59 @@
|
||||
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { Injectable, 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 { CarreraUsuario, Usuario } from 'src/entities/entities';
|
||||
import { Repository } from 'typeorm';
|
||||
import { ServActivos } from './entities/servActivos.entitie';
|
||||
import { MovimientoService } from 'src/movimiento/movimiento.service';
|
||||
import { MailService } from 'src/mail/mail.service';
|
||||
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);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Usuario)
|
||||
private readonly usuarioRepository: Repository<Usuario>,
|
||||
@InjectRepository(Carrera)
|
||||
private readonly carreraRepository: Repository<Carrera>,
|
||||
@InjectRepository(ServActivos)
|
||||
private readonly servActivosRepository: Repository<ServActivos>,
|
||||
@InjectRepository(TipoUsuario)
|
||||
private readonly tipoUsuarioRepository: Repository<TipoUsuario>,
|
||||
@InjectRepository(Genero)
|
||||
private readonly generoRepository: Repository<Genero>,
|
||||
@InjectRepository(CarreraUsuario)
|
||||
private readonly carreraUsuario: Repository<CarreraUsuario>,
|
||||
@InjectRepository(UsuarioTipoUsuario)
|
||||
private readonly usuarioTipoUsuario: Repository<UsuarioTipoUsuario>,
|
||||
private readonly movimientoService: MovimientoService,
|
||||
private readonly mailService: MailService,
|
||||
private readonly excelService: ExcelService, // Asegúrate de importar el servicio de Excel
|
||||
) {}
|
||||
private readonly carreraUsuarioRepository: Repository<CarreraUsuario>,
|
||||
) { }
|
||||
|
||||
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) {
|
||||
async findOne(noCuenta: string) {
|
||||
const usuario = await this.usuarioRepository.findOne({
|
||||
relations: [
|
||||
'genero',
|
||||
'carreraUsuarios',
|
||||
'carreraUsuarios.carrera',
|
||||
],
|
||||
where: { num_cuenta: noCuenta },
|
||||
|
||||
});
|
||||
|
||||
if (!usuario) {
|
||||
throw new NotFoundException('Usuario no encontrado');
|
||||
}
|
||||
|
||||
const servActivo = await this.servActivosRepository.findOne({
|
||||
where: { usuario: { activo: true, id_usuario: usuario.id_usuario } },
|
||||
});
|
||||
|
||||
if (!servActivo) {
|
||||
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: {
|
||||
servicio: string;
|
||||
status: string;
|
||||
fecha_activacion: string;
|
||||
}[] = [];
|
||||
|
||||
const mapServicio = (nombre: string, estado: string | undefined) => {
|
||||
if (!estado) return;
|
||||
|
||||
if (estado.startsWith('Activo')) {
|
||||
const fechaCompleta = estado.substring(7); // "2025-06-25T14:26:21.427Z"
|
||||
const fechaSolo = fechaCompleta.substring(0, 10); // "2025-06-25"
|
||||
resultado.push({
|
||||
servicio: nombre,
|
||||
status: 'Activo',
|
||||
fecha_activacion: fechaSolo,
|
||||
});
|
||||
} else {
|
||||
resultado.push({
|
||||
servicio: nombre,
|
||||
status: 'Inactivo',
|
||||
fecha_activacion: '',
|
||||
});
|
||||
}
|
||||
// Transformar a formato plano
|
||||
const resultadoPlano = {
|
||||
num_cuenta: usuario.num_cuenta,
|
||||
nombre: usuario.nombre,
|
||||
a_paterno: usuario.a_paterno,
|
||||
a_materno: usuario.a_materno,
|
||||
fecha_nacimiento: usuario.fecha_nacimiento,
|
||||
generacion: usuario.generacion,
|
||||
genero: usuario.genero?.genero ?? '',
|
||||
carreras: (usuario.carreraUsuarios ?? [])
|
||||
.map(cu => cu.carrera?.carrera)
|
||||
.filter(Boolean)
|
||||
.join(', '), // todas las carreras como string plano
|
||||
};
|
||||
|
||||
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);
|
||||
return [resultadoPlano]; // importante: frontend espera array
|
||||
|
||||
if (resultado.length === 0) {
|
||||
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');
|
||||
await this.enviarSolicitud(); // tu función de importación
|
||||
}
|
||||
|
||||
async enviarSolicitud() {
|
||||
const mov = await this.movimientoService.log(
|
||||
'SISTEMA',
|
||||
'LOADING',
|
||||
'Enviando solicitud de consulta masiva de profesores al WebService',
|
||||
'CARGA MASIVA WEB SERVICE',
|
||||
);
|
||||
|
||||
interface DatosEntrada {
|
||||
Nombre: string;
|
||||
ApellidoPaterno: string;
|
||||
ApellidoMaterno: string;
|
||||
NumeroTrabajador: string;
|
||||
RFC: string;
|
||||
}
|
||||
|
||||
interface DatosRespuesta {
|
||||
numeroTrabajador: string;
|
||||
nombre: string;
|
||||
apellidoPaterno: string;
|
||||
apellidoMaterno: string;
|
||||
nombreCarrera: string;
|
||||
fechaNacimiento: string;
|
||||
rfc: string;
|
||||
genero: string;
|
||||
}
|
||||
|
||||
const apiUrl = process.env.API_URL;
|
||||
if (!apiUrl) return;
|
||||
|
||||
const entrada: DatosEntrada = {
|
||||
Nombre: '',
|
||||
ApellidoPaterno: '',
|
||||
ApellidoMaterno: '',
|
||||
NumeroTrabajador: '',
|
||||
RFC: '',
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(entrada),
|
||||
});
|
||||
|
||||
if (!response.ok) return;
|
||||
|
||||
const profesores: DatosRespuesta[] = await response.json();
|
||||
|
||||
let nuevos = 0;
|
||||
let actualizados = 0;
|
||||
|
||||
/* ==========================
|
||||
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' }),
|
||||
);
|
||||
}
|
||||
|
||||
/* ==========================
|
||||
CACHE DE CARRERAS
|
||||
========================== */
|
||||
const carreraCache = new Map<string, any>();
|
||||
|
||||
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 (!carrera) {
|
||||
carrera = await this.carreraRepository.save(
|
||||
this.carreraRepository.create({
|
||||
carrera: nombreCarrera,
|
||||
clave: '',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
carreraCache.set(nombreCarrera, carrera);
|
||||
}
|
||||
|
||||
/* ==========================
|
||||
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 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,
|
||||
}),
|
||||
);
|
||||
|
||||
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',
|
||||
}),
|
||||
);
|
||||
|
||||
await this.carreraUsuario.save(
|
||||
this.carreraUsuario.create({
|
||||
usuario: nuevoUsuario,
|
||||
carrera,
|
||||
}),
|
||||
);
|
||||
|
||||
await this.usuarioTipoUsuario.save(
|
||||
this.usuarioTipoUsuario.create({
|
||||
usuario: nuevoUsuario,
|
||||
tipoUsuario: tipoProfesor,
|
||||
}),
|
||||
);
|
||||
|
||||
nuevos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ==========================
|
||||
USUARIO EXISTENTE
|
||||
========================== */
|
||||
let cambios = false;
|
||||
|
||||
const servActivo = await this.servActivosRepository.findOne({
|
||||
where: { usuario: { id_usuario: usuario.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 (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;
|
||||
}
|
||||
|
||||
if (usuario.fecha_nacimiento !== prof.fechaNacimiento) {
|
||||
usuario.fecha_nacimiento = prof.fechaNacimiento;
|
||||
activarServicios();
|
||||
cambios = true;
|
||||
}
|
||||
|
||||
if (
|
||||
prof.numeroTrabajador &&
|
||||
usuario.num_cuenta !== prof.numeroTrabajador
|
||||
) {
|
||||
usuario.num_cuenta = prof.numeroTrabajador;
|
||||
activarServicios();
|
||||
cambios = true;
|
||||
}
|
||||
|
||||
if (cambios) {
|
||||
usuario.movimiento = mov;
|
||||
await this.usuarioRepository.save(usuario);
|
||||
if (servActivo) await this.servActivosRepository.save(servActivo);
|
||||
actualizados++;
|
||||
}
|
||||
}
|
||||
|
||||
await this.movimientoService.updateStatus(
|
||||
mov.id_mov,
|
||||
'SUCCESS',
|
||||
`Carga masiva finalizada. Nuevos: ${nuevos}, Actualizados: ${actualizados}`,
|
||||
);
|
||||
|
||||
return `Importación finalizada. Nuevos: ${nuevos}, Actualizados: ${actualizados}`;
|
||||
} catch (error) {
|
||||
this.logger.error('Error en carga masiva de profesores', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async cargaIndividual(
|
||||
usuario: CreateUsuarioDto,
|
||||
origen: string,
|
||||
user: string,
|
||||
) {
|
||||
const queryRunner =
|
||||
this.usuarioRepository.manager.connection.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const existente = await queryRunner.manager.findOne(Usuario, {
|
||||
where: { num_cuenta: usuario.num_cuenta ?? usuario.usuario },
|
||||
relations: ['genero'],
|
||||
});
|
||||
|
||||
if (existente) {
|
||||
throw new Error('Ya existe este usuario');
|
||||
}
|
||||
|
||||
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 },
|
||||
});
|
||||
if (!genero) {
|
||||
genero = queryRunner.manager.create(Genero, { genero: generoTexto });
|
||||
await queryRunner.manager.save(genero);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
nombre: usuario.nombre,
|
||||
a_paterno: usuario.a_paterno,
|
||||
a_materno: usuario.a_materno,
|
||||
rfc: usuario.rfc ?? null,
|
||||
fecha_nacimiento: usuario.fecha_nacimiento,
|
||||
generacion: usuario.generacion ?? null,
|
||||
genero,
|
||||
movimiento: mov,
|
||||
contraseña: usuario.contraseña ?? null, // Asegúrate de manejar la contraseña adecuadamente
|
||||
});
|
||||
|
||||
const savedUser = await queryRunner.manager.save(nuevo);
|
||||
|
||||
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',
|
||||
];
|
||||
|
||||
if (!FieldMap.includes(usuario.tipo_usuario)) {
|
||||
throw new Error(
|
||||
`Tipo de usuario no permitido: ${usuario.tipo_usuario}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Verifica si el tipo de usuario ya existe
|
||||
let tipoUsuario = await queryRunner.manager.findOne(TipoUsuario, {
|
||||
where: { tipo_usuario: usuario.tipo_usuario },
|
||||
});
|
||||
|
||||
// Si no existe, lo crea y guarda
|
||||
if (!tipoUsuario) {
|
||||
tipoUsuario = queryRunner.manager.create(TipoUsuario, {
|
||||
tipo_usuario: usuario.tipo_usuario,
|
||||
});
|
||||
tipoUsuario = await queryRunner.manager.save(tipoUsuario);
|
||||
}
|
||||
|
||||
// Luego crea la relación con el usuario ya guardado
|
||||
const usuarioTipo = queryRunner.manager.create(UsuarioTipoUsuario, {
|
||||
tipoUsuario,
|
||||
usuario: savedUser,
|
||||
});
|
||||
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,
|
||||
RedStatus: 'Inactivo',
|
||||
ATStatus: 'Inactivo',
|
||||
CorreoStatus: 'Inactivo',
|
||||
PrestamosStatus: 'Inactivo',
|
||||
});
|
||||
correo = true;
|
||||
await queryRunner.manager.save(servActivosDiplomado);
|
||||
break;
|
||||
|
||||
case 'Extra Largo':
|
||||
const servActivosExtraLargo = this.servActivosRepository.create({
|
||||
Prestamos: true,
|
||||
Correo: true,
|
||||
usuario: savedUser,
|
||||
RedStatus: 'Inactivo',
|
||||
ATStatus: 'Inactivo',
|
||||
CorreoStatus: 'Inactivo',
|
||||
PrestamosStatus: 'Inactivo',
|
||||
});
|
||||
solicita = true;
|
||||
correo = true;
|
||||
await queryRunner.manager.save(servActivosExtraLargo);
|
||||
break;
|
||||
|
||||
case 'Servicio Social':
|
||||
const servActivosServicio = this.servActivosRepository.create({
|
||||
Red: true,
|
||||
AT: true,
|
||||
Correo: true,
|
||||
usuario: savedUser,
|
||||
RedStatus: 'Inactivo',
|
||||
ATStatus: 'Inactivo',
|
||||
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({
|
||||
Red: true,
|
||||
Correo: true,
|
||||
usuario: savedUser,
|
||||
RedStatus: 'Inactivo',
|
||||
ATStatus: 'Inactivo',
|
||||
CorreoStatus: 'Inactivo',
|
||||
PrestamosStatus: 'Inactivo',
|
||||
});
|
||||
red = true;
|
||||
correo = true;
|
||||
await queryRunner.manager.save(servActivosIdiomas);
|
||||
|
||||
break;
|
||||
|
||||
case 'Reinscrito':
|
||||
case 'Posgrado':
|
||||
case 'Intercambio UNAM':
|
||||
case 'Movilidad':
|
||||
case 'Ampliación de Conocimiento':
|
||||
case 'Licenciatura':
|
||||
case 'Profesor':
|
||||
const servActivos = this.servActivosRepository.create({
|
||||
Red: true,
|
||||
AT: true,
|
||||
Correo: true,
|
||||
Prestamos: true,
|
||||
usuario: savedUser,
|
||||
RedStatus: 'Inactivo',
|
||||
ATStatus: 'Inactivo',
|
||||
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;
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return { saved: savedUser, movId: mov.id_mov, at, correo, red, solicita };
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw new Error('Error en la carga individual: ' + error.message);
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user