From cea7375e3b6018dcdc99ed67c802dceece21a416 Mon Sep 17 00:00:00 2001 From: Emilio Date: Tue, 14 Oct 2025 13:54:38 -0600 Subject: [PATCH] =?UTF-8?q?Se=20agreg=C3=B3=20env=C3=ADo=20de=20correos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/excel/excel.controller.ts | 273 ++++----- src/excel/excel.service.ts | 919 ++++++++++++++++--------------- src/usuarios/usuarios.service.ts | 383 +++++++------ 3 files changed, 845 insertions(+), 730 deletions(-) diff --git a/src/excel/excel.controller.ts b/src/excel/excel.controller.ts index 8b759f5..acd6f1f 100644 --- a/src/excel/excel.controller.ts +++ b/src/excel/excel.controller.ts @@ -1,5 +1,21 @@ // 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, + 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'; @@ -20,61 +36,75 @@ 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); console.log('Datos del request:', req.user); - if (req.user.origen != 'LOAD' && req.user.origen!='EXTERNO') { - throw new Error('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, req.user.email); + 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') + throw new Error('No se pudo realizar la carga del usuario'); } console.log('Alta de usuario:', alta); - await this.movimientoService.updateStatus(alta.movId, 'SUCCESS') + 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}`) + 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.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.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}`) + + 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 }; } @@ -89,20 +119,22 @@ export class ExcelController { 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,req.user.email); + const 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: '

No se pudieron subir datos.

', + text: 'No se pudieron subir datos.', + html: '', }); throw new Error('No se pudo procesar el archivo.'); } @@ -111,70 +143,72 @@ 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: `

Se han insertado ${result.inserted} registros.

-

Errores encontrados:

- `, + text: + 'No se pudieron subir datos.' + + `Se han insertado ${result.inserted} registros. + Errores encontrados: + ${result.errors.map((error) => `
  • ${error}
  • `).join('')}`, + html: '', }); - return result + return result; } - console.log(result.id_movimiento) + 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: - `

    La carga de datos se ha realizado con éxito. Se han insertado ${result.inserted} registros.

    + text: 'La carga de datos se ha realizado con éxito.', + html: `

    La carga de datos se ha realizado con éxito. Se han insertado ${result.inserted} registros.

    Usuarios At: ${result.conteoTiposAt}

    Usuarios Red: ${result.conteoTiposRed}

    Usuarios Solicita: ${result.conteoTiposSolicita}

    Usuarios Correo: ${result.conteoTiposCorreo}

    - `, + `, }); // 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.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.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.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`) + + 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 result; } catch (err) { - await this.movimientoService.log( - - 'LOAD', - 'FAILED', - err.message - ); + await this.movimientoService.log('LOAD', 'FAILED', err.message); throw err; } } @@ -190,42 +224,45 @@ export class ExcelController { 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.' }); + .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'; } @@ -235,12 +272,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.' }); @@ -249,24 +281,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.' }); @@ -275,51 +304,49 @@ 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.' }); + .json({ + statusCode: 403, + message: 'Origen no permitido para descarga.', + }); } - } //Agregarlo en su propio controller @Get('movimientos') -async getMovimientos( - @Request() req, - @Query('page') page: number, - @Query('limit') limit: number, -) { - return this.movimientoService.findAll(req.user.origen, page, limit); -} + 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( @@ -337,22 +364,14 @@ async getMovimientos( return { message: 'Servicios activados correctamente' }; } - @Get('cargaIndividual') - async cargarIndividual( - @Request() req) { + async cargarIndividual(@Request() req) { const origen = req.user.origen; // ahora sí existe return this.movimientoService.findAllCargaIndv(origen); - } // @Delete() // async borrar(){ // return await this.excelService.borrarUsers() // } - - - - - -} \ No newline at end of file +} diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts index 39e1951..1f6e50b 100644 --- a/src/excel/excel.service.ts +++ b/src/excel/excel.service.ts @@ -3,7 +3,13 @@ import { Injectable, BadRequestException, Logger } from '@nestjs/common'; import { Workbook } from 'exceljs'; import { DataSource, In, Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; -import { CarreraUsuario, TipoUsuario, Usuario, UsuariosDelSistema, UsuarioTipoUsuario } from '../entities/entities'; +import { + CarreraUsuario, + TipoUsuario, + Usuario, + UsuariosDelSistema, + UsuarioTipoUsuario, +} from '../entities/entities'; import { Genero } from '../entities/entities'; import { Carrera } from '../entities/entities'; import { MovimientoService } from 'src/movimiento/movimiento.service'; @@ -49,10 +55,10 @@ export class ExcelService { private readonly usuariosDelSistemaRepo: Repository, private readonly movimientoService: MovimientoService, private readonly mailService: MailService, - private readonly dataSource: DataSource + private readonly dataSource: DataSource, // … inyecta otros repositorios si los usarás - ) { } + ) {} /** Lee el buffer del Excel y devuelve un arreglo de filas tipadas */ private async parseFile(buffer: Buffer): Promise { @@ -119,7 +125,7 @@ export class ExcelService { const rowNum = i + 2; // por el encabezado // Campos que no pueden quedar vacíos - ['cuenta', 'tipo'].forEach(field => { + ['cuenta', 'tipo'].forEach((field) => { const raw = (r as any)[field]; const str = raw != null ? raw.toString().trim() : ''; if (!str) { @@ -129,13 +135,17 @@ export class ExcelService { }); // El número de cuenta debe ser de nueve dígitos para los alumnos - if (r.cuenta && !/^\d{9}$/.test(r.cuenta.toString().trim()) && r.tipo.trim() === 'Licenciatura') { - errors.push(`Fila ${rowNum}: cuenta "${r.cuenta}" debe tener 9 dígitos.`); + if ( + r.cuenta && + !/^\d{9}$/.test(r.cuenta.toString().trim()) && + r.tipo.trim() === 'Licenciatura' + ) { + errors.push( + `Fila ${rowNum}: cuenta "${r.cuenta}" debe tener 9 dígitos.`, + ); flagError = true; } - - // Duplicados de "cuenta" const cuentaStr = r.cuenta != null ? r.cuenta.toString().trim() : ''; if (seen.has(cuentaStr)) { @@ -145,20 +155,18 @@ export class ExcelService { seen.add(cuentaStr); } - - - const genStr = r.gen != null ? r.gen.toString().trim() : ''; if (genStr && !/^[0-9]{4}$/.test(genStr)) { errors.push(`Fila ${rowNum}: generación inválida "${genStr}".`); flagError = true; } - // Fecha en formato YYYYMMDD const fechaStr = r.fechnac != null ? r.fechnac.toString().trim() : ''; if (!/^[0-9]{8}$/.test(fechaStr)) { - errors.push(`Fila ${rowNum}: fecha de nacimiento inválida "${fechaStr}".`); + errors.push( + `Fila ${rowNum}: fecha de nacimiento inválida "${fechaStr}".`, + ); flagError = true; } @@ -170,21 +178,15 @@ export class ExcelService { } if (!flagError) { - rowsGood.push(r) + rowsGood.push(r); } else { flagError = false; // reset para la siguiente fila } - - - }); - - return { errors, rowsGood }; } - /** * Solo valida: retorna lista de errores (vacío = ok) */ @@ -197,251 +199,306 @@ export class ExcelService { /** * Valida y luego persiste usuarios y relaciones básicas */ - async loadFile(buffer: Buffer, user:string) { - const file = await this.parseFile(buffer); - const status = this.validateRows(file); - const errs = status.errors; - const rows = status.rowsGood; + async loadFile(buffer: Buffer, user: string) { + const file = await this.parseFile(buffer); + const status = this.validateRows(file); + const errs = status.errors; + const rows = status.rowsGood; - if (rows.length === 0) { - throw new BadRequestException( - 'No se encontraron filas válidas para cargar. ' + errs, - ); - } - - let conteoTiposRed: number=0; - let conteoTiposAt: number=0; - let conteoTiposSolicita: number=0; - let conteoTiposCorreo: number=0; - - const queryRunner = this.dataSource.createQueryRunner(); - await queryRunner.connect(); - await queryRunner.startTransaction(); - - try { - const movimiento = await this.movimientoService.logger( - queryRunner, - 'LOAD', - 'LOADING', - 'Caraga masiva de usuarios hecha por: ' + user, - `CARGA MASIVA DE USUARIOS`, - ); - - // 🔹 1. Pre-cargar cuentas/rfc ya existentes - const cuentas = rows.map((r) => r.cuenta).filter(Boolean); - const rfcs = rows.map((r) => r.rfc).filter(Boolean); - - const existentes = await queryRunner.manager.find(Usuario, { - where: [{ num_cuenta: In(cuentas) }, { rfc: In(rfcs) }], - select: ['num_cuenta', 'rfc'], - }); - - const setCuentas = new Set(existentes.map((u) => u.num_cuenta)); - const setRfcs = new Set(existentes.map((u) => u.rfc)); - - // 🔹 2. Pre-cargar catálogos - const generosUnicos = [...new Set(rows.map((r) => r.sexo).filter(Boolean))]; - const carrerasUnicasClave = [ - ...new Set(rows.map((r) => r.clave).filter(Boolean)), - ]; - const carrerasUnicasNombre = [ - ...new Set(rows.map((r) => r.nomCarr).filter(Boolean)), - ]; - const tiposUnicos = [...new Set(rows.map((r) => r.tipo?.trim()).filter(Boolean))]; - - const [generosExist, carrerasExist, tiposExist] = await Promise.all([ - queryRunner.manager.find(Genero, { where: { genero: In(generosUnicos) } }), - queryRunner.manager.find(Carrera, { - where: [{ clave: In(carrerasUnicasClave) }, { carrera: In(carrerasUnicasNombre) }], - }), - queryRunner.manager.find(TipoUsuario, { - where: { tipo_usuario: In(tiposUnicos) }, - }), - ]); - - const mapGenero = new Map(generosExist.map((g) => [g.genero, g])); - const mapCarreraClave = new Map(carrerasExist.map((c) => [c.clave, c])); - const mapCarreraNom = new Map(carrerasExist.map((c) => [c.carrera, c])); - const mapTipo = new Map(tiposExist.map((t) => [t.tipo_usuario, t])); - - // 🔹 3. Arreglos para inserciones masivas - const usuarios: Usuario[] = []; - const carrerasUsuario: CarreraUsuario[] = []; - const usuariosTipos: UsuarioTipoUsuario[] = []; - const serviciosActivos: ServActivos[] = []; - - let contador = 0; - - for (const r of rows) { - if (setCuentas.has(r.cuenta) || (r.rfc && setRfcs.has(r.rfc))) { - errs.push(`Cuenta ${r.cuenta} ya existe, se omite.`); - contador++; - continue; - } - - if (!r.tipo || !r.nombres || !r.apellidopa || !r.apellidoma) { - errs.push(`Fila con cuenta ${r.cuenta} tiene campos faltantes.`); - contador++; - continue; - } - - // Genero - let genero = mapGenero.get(r.sexo); - if (!genero) { - genero = queryRunner.manager.create(Genero, { genero: r.sexo }); - mapGenero.set(r.sexo, genero); - } - - // Carrera - let carrera: Carrera|undefined; - if (r.clave && mapCarreraClave.has(r.clave)) { - carrera = mapCarreraClave.get(r.clave); - } else if (r.nomCarr && mapCarreraNom.has(r.nomCarr)) { - carrera = mapCarreraNom.get(r.nomCarr); - } else if (r.clave || r.nomCarr) { - carrera = queryRunner.manager.create(Carrera, { - clave: r.clave ?? '', - carrera: r.nomCarr?.slice(0, 100) ?? '', - }); - mapCarreraClave.set(carrera.clave, carrera); - mapCarreraNom.set(carrera.carrera, carrera); - } - - // Tipo - let tipo = mapTipo.get(r.tipo.trim()); - if (!tipo) { - tipo = queryRunner.manager.create(TipoUsuario, { - tipo_usuario: r.tipo.trim(), - }); - mapTipo.set(r.tipo.trim(), tipo); - } - - const generacion = - /^[0-9]{4}$/.test(r.gen?.toString().trim()) ? - parseInt(r.gen.toString().trim(), 10) : null; - - const usuario = queryRunner.manager.create(Usuario, { - num_cuenta: r.cuenta, - nombre: r.nombres, - a_paterno: r.apellidopa, - a_materno: r.apellidoma, - rfc: r.rfc, - fecha_nacimiento: r.fechnac, - generacion, - genero, - movimiento, - }); - - usuarios.push(usuario); - - if (carrera) { - const carreraUsuario = queryRunner.manager.create(CarreraUsuario, { - usuario, - carrera, - }); - carrerasUsuario.push(carreraUsuario); - } - - usuariosTipos.push( - queryRunner.manager.create(UsuarioTipoUsuario, { - usuario, - tipoUsuario: tipo, - }), + if (rows.length === 0) { + throw new BadRequestException( + 'No se encontraron filas válidas para cargar. ' + errs, ); - - // Servicios activos - const servData: Partial = { - usuario, - RedStatus: 'Inactivo', - ATStatus: 'Inactivo', - CorreoStatus: 'Inactivo', - PrestamosStatus: 'Inactivo', - }; - - switch (r.tipo.trim()) { - case 'Diplomado': - Object.assign(servData, { Correo: true }); - conteoTiposCorreo++; - break; - case 'Extra Largo': - Object.assign(servData, { Correo: true, Prestamos: true }); - conteoTiposCorreo++; - conteoTiposSolicita++; - break; - case 'Servicio Social': - Object.assign(servData, { Correo: true, AT: true, Red: true }); - conteoTiposCorreo++; - conteoTiposAt++; - conteoTiposRed++; - break; - case 'Idiomas R (UNAM)': - case 'Idiomas Sabatino': - Object.assign(servData, { Correo: true, Red: true }); - conteoTiposCorreo++; - conteoTiposRed++; - break; - case 'Reinscrito': - case 'Posgrado': - case 'Intercambio UNAM': - case 'Movilidad': - case 'Ampliación de Conocimiento': - case 'Licenciatura': - case 'Profesor': - Object.assign(servData, { - Correo: true, - AT: true, - Red: true, - Prestamos: true, - }); - conteoTiposCorreo++; - conteoTiposRed++; - conteoTiposAt++; - conteoTiposSolicita++; - break; - case 'Trabajadores': - Object.assign(servData, { Red: true }); - conteoTiposRed++; - break; - } - - serviciosActivos.push(queryRunner.manager.create(ServActivos, servData)); } - // 🔹 4. Guardar en batch - await queryRunner.manager.save([...mapGenero.values()]); - await queryRunner.manager.save([...mapCarreraClave.values()]); - await queryRunner.manager.save([...mapTipo.values()]); + let conteoTiposRed: number = 0; + let conteoTiposAt: number = 0; + let conteoTiposSolicita: number = 0; + let conteoTiposCorreo: number = 0; - await queryRunner.manager.save(usuarios); - await queryRunner.manager.save(carrerasUsuario); - await queryRunner.manager.save(usuariosTipos); - await queryRunner.manager.save(serviciosActivos); + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); - await queryRunner.commitTransaction(); - + try { + const movimiento = await this.movimientoService.logger( + queryRunner, + 'LOAD', + 'LOADING', + 'Caraga masiva de usuarios hecha por: ' + user, + `CARGA MASIVA DE USUARIOS`, + ); - return { - inserted: rows.length - contador, - id_movimiento: movimiento.id_mov, - errors: errs, - conteoTiposAt, - conteoTiposCorreo, - conteoTiposRed, - conteoTiposSolicita, - }; - } catch (err) { - await queryRunner.rollbackTransaction(); - throw err; - } finally { - await queryRunner.release(); + // 🔹 1. Pre-cargar cuentas/rfc ya existentes + const cuentas = rows.map((r) => r.cuenta).filter(Boolean); + const rfcs = rows.map((r) => r.rfc).filter(Boolean); + + const existentes = await queryRunner.manager.find(Usuario, { + where: [{ num_cuenta: In(cuentas) }, { rfc: In(rfcs) }], + select: ['num_cuenta', 'rfc'], + }); + + const setCuentas = new Set(existentes.map((u) => u.num_cuenta)); + const setRfcs = new Set(existentes.map((u) => u.rfc)); + + // 🔹 2. Pre-cargar catálogos + const generosUnicos = [ + ...new Set(rows.map((r) => r.sexo).filter(Boolean)), + ]; + const carrerasUnicasClave = [ + ...new Set(rows.map((r) => r.clave).filter(Boolean)), + ]; + const carrerasUnicasNombre = [ + ...new Set(rows.map((r) => r.nomCarr).filter(Boolean)), + ]; + const tiposUnicos = [ + ...new Set(rows.map((r) => r.tipo?.trim()).filter(Boolean)), + ]; + + const [generosExist, carrerasExist, tiposExist] = await Promise.all([ + queryRunner.manager.find(Genero, { + where: { genero: In(generosUnicos) }, + }), + queryRunner.manager.find(Carrera, { + where: [ + { clave: In(carrerasUnicasClave) }, + { carrera: In(carrerasUnicasNombre) }, + ], + }), + queryRunner.manager.find(TipoUsuario, { + where: { tipo_usuario: In(tiposUnicos) }, + }), + ]); + + const mapGenero = new Map(generosExist.map((g) => [g.genero, g])); + const mapCarreraClave = new Map(carrerasExist.map((c) => [c.clave, c])); + const mapCarreraNom = new Map(carrerasExist.map((c) => [c.carrera, c])); + const mapTipo = new Map(tiposExist.map((t) => [t.tipo_usuario, t])); + + // 🔹 3. Arreglos para inserciones masivas + const usuarios: Usuario[] = []; + const carrerasUsuario: CarreraUsuario[] = []; + const usuariosTipos: UsuarioTipoUsuario[] = []; + const serviciosActivos: ServActivos[] = []; + + let contador = 0; + + for (const r of rows) { + if (setCuentas.has(r.cuenta) || (r.rfc && setRfcs.has(r.rfc))) { + errs.push(`Cuenta ${r.cuenta} ya existe, se omite.`); + contador++; + continue; + } + + if (!r.tipo || !r.nombres || !r.apellidopa || !r.apellidoma) { + errs.push(`Fila con cuenta ${r.cuenta} tiene campos faltantes.`); + contador++; + continue; + } + + // Genero + let genero = mapGenero.get(r.sexo); + if (!genero) { + genero = queryRunner.manager.create(Genero, { genero: r.sexo }); + mapGenero.set(r.sexo, genero); + } + + // Carrera + let carrera: Carrera | undefined; + if (r.clave && mapCarreraClave.has(r.clave)) { + carrera = mapCarreraClave.get(r.clave); + } else if (r.nomCarr && mapCarreraNom.has(r.nomCarr)) { + carrera = mapCarreraNom.get(r.nomCarr); + } else if (r.clave || r.nomCarr) { + carrera = queryRunner.manager.create(Carrera, { + clave: r.clave ?? '', + carrera: r.nomCarr?.slice(0, 100) ?? '', + }); + mapCarreraClave.set(carrera.clave, carrera); + mapCarreraNom.set(carrera.carrera, carrera); + } + + // Tipo + let tipo = mapTipo.get(r.tipo.trim()); + if (!tipo) { + tipo = queryRunner.manager.create(TipoUsuario, { + tipo_usuario: r.tipo.trim(), + }); + mapTipo.set(r.tipo.trim(), tipo); + } + + const generacion = /^[0-9]{4}$/.test(r.gen?.toString().trim()) + ? parseInt(r.gen.toString().trim(), 10) + : null; + + const usuario = queryRunner.manager.create(Usuario, { + num_cuenta: r.cuenta, + nombre: r.nombres, + a_paterno: r.apellidopa, + a_materno: r.apellidoma, + rfc: r.rfc, + fecha_nacimiento: r.fechnac, + generacion, + genero, + movimiento, + }); + + usuarios.push(usuario); + + if (carrera) { + const carreraUsuario = queryRunner.manager.create(CarreraUsuario, { + usuario, + carrera, + }); + carrerasUsuario.push(carreraUsuario); + } + + usuariosTipos.push( + queryRunner.manager.create(UsuarioTipoUsuario, { + usuario, + tipoUsuario: tipo, + }), + ); + + // Servicios activos + const servData: Partial = { + usuario, + RedStatus: 'Inactivo', + ATStatus: 'Inactivo', + CorreoStatus: 'Inactivo', + PrestamosStatus: 'Inactivo', + }; + + switch (r.tipo.trim()) { + case 'Diplomado': + Object.assign(servData, { Correo: true }); + conteoTiposCorreo++; + break; + case 'Extra Largo': + Object.assign(servData, { Correo: true, Prestamos: true }); + conteoTiposCorreo++; + conteoTiposSolicita++; + break; + case 'Servicio Social': + Object.assign(servData, { Correo: true, AT: true, Red: true }); + conteoTiposCorreo++; + conteoTiposAt++; + conteoTiposRed++; + break; + case 'Idiomas R (UNAM)': + case 'Idiomas Sabatino': + Object.assign(servData, { Correo: true, Red: true }); + conteoTiposCorreo++; + conteoTiposRed++; + break; + case 'Reinscrito': + case 'Posgrado': + case 'Intercambio UNAM': + case 'Movilidad': + case 'Ampliación de Conocimiento': + case 'Licenciatura': + case 'Profesor': + Object.assign(servData, { + Correo: true, + AT: true, + Red: true, + Prestamos: true, + }); + conteoTiposCorreo++; + conteoTiposRed++; + conteoTiposAt++; + conteoTiposSolicita++; + break; + case 'Trabajadores': + Object.assign(servData, { Red: true }); + conteoTiposRed++; + break; + } + + serviciosActivos.push( + queryRunner.manager.create(ServActivos, servData), + ); + } + + // 🔹 4. Guardar en batch (orden correcto) + await queryRunner.manager.save([...mapGenero.values()]); + await queryRunner.manager.save([...mapCarreraClave.values()]); + await queryRunner.manager.save([...mapTipo.values()]); + + // 1️⃣ Guardar usuarios primero — asigna IDs + await queryRunner.manager.save(usuarios); + + // 2️⃣ Reconstruir relaciones con usuarios ya guardados + + // CarreraUsuario + const carrerasUsuarioFinal: CarreraUsuario[] = []; + for (const cu of carrerasUsuario) { + const usuarioConId = usuarios.find( + (u) => u.num_cuenta === cu.usuario.num_cuenta, + ); + if (usuarioConId && cu.carrera) { + carrerasUsuarioFinal.push( + queryRunner.manager.create(CarreraUsuario, { + usuario: usuarioConId, + carrera: cu.carrera, + }), + ); + } + } + + // UsuarioTipoUsuario + const usuariosTiposFinal: UsuarioTipoUsuario[] = []; + for (const ut of usuariosTipos) { + const usuarioConId = usuarios.find( + (u) => u.num_cuenta === ut.usuario.num_cuenta, + ); + if (usuarioConId && ut.tipoUsuario) { + usuariosTiposFinal.push( + queryRunner.manager.create(UsuarioTipoUsuario, { + usuario: usuarioConId, + tipoUsuario: ut.tipoUsuario, + }), + ); + } + } + + // ServActivos + const serviciosActivosFinal: ServActivos[] = []; + for (const sa of serviciosActivos) { + const usuarioConId = usuarios.find( + (u) => u.num_cuenta === sa.usuario.num_cuenta, + ); + if (usuarioConId) { + serviciosActivosFinal.push( + queryRunner.manager.create(ServActivos, { + ...sa, + usuario: usuarioConId, + }), + ); + } + } + + // 3️⃣ Guardar relaciones dependientes en orden + await queryRunner.manager.save(carrerasUsuarioFinal); + await queryRunner.manager.save(usuariosTiposFinal); + await queryRunner.manager.save(serviciosActivosFinal); + + return { + inserted: rows.length - contador, + id_movimiento: movimiento.id_mov, + errors: errs, + conteoTiposAt, + conteoTiposCorreo, + conteoTiposRed, + conteoTiposSolicita, + }; + } catch (err) { + await queryRunner.rollbackTransaction(); + throw err; + } finally { + await queryRunner.release(); + } } -} - - - - - - // async generateCsv(): Promise { // const users = await this.usuarioRepo.find({ @@ -468,11 +525,7 @@ export class ExcelService { // return header + rows; // } - async generateCsvCorreo(): Promise { - - - const tipos = [ 'Diplomado', 'Extra Largo', @@ -511,44 +564,49 @@ export class ExcelService { ], }); - const header = [ - 'num_cuenta', 'nombre', 'a_paterno', 'a_materno', - 'carreras', 'rfc', 'fecha_nacimiento', 'generacion', 'tipos_usuario' - ].join(',') + '\n'; + const header = + [ + 'num_cuenta', + 'nombre', + 'a_paterno', + 'a_materno', + 'carreras', + 'rfc', + 'fecha_nacimiento', + 'generacion', + 'tipos_usuario', + ].join(',') + '\n'; - const rows = usuarios.map(u => { - const carreras = (u.carreraUsuarios ?? []) - .map(cu => cu.carrera?.carrera ?? '') - .join(' | '); + const rows = usuarios + .map((u) => { + const carreras = (u.carreraUsuarios ?? []) + .map((cu) => cu.carrera?.carrera ?? '') + .join(' | '); - const tiposUsuario = (u.usuarioTipos ?? []) - .map(utu => utu.tipoUsuario?.tipo_usuario ?? '') - .join(' | '); + const tiposUsuario = (u.usuarioTipos ?? []) + .map((utu) => utu.tipoUsuario?.tipo_usuario ?? '') + .join(' | '); - return [ - u.num_cuenta ?? '', - u.nombre ?? '', - u.a_paterno ?? '', - u.a_materno ?? '', - carreras, - u.rfc ?? '', - u.fecha_nacimiento ?? '', - u.generacion?.toString() ?? '', - tiposUsuario - ].join(','); - }).join('\n'); + return [ + u.num_cuenta ?? '', + u.nombre ?? '', + u.a_paterno ?? '', + u.a_materno ?? '', + carreras, + u.rfc ?? '', + u.fecha_nacimiento ?? '', + u.generacion?.toString() ?? '', + tiposUsuario, + ].join(','); + }) + .join('\n'); const tablaCompleta = header + rows; return tablaCompleta; - } async generateCsvSolicita(): Promise { - - - const tipos = [ - 'Extra Largo', 'Ampliación de Conocimiento', 'Movilidad', @@ -569,52 +627,56 @@ export class ExcelService { servActivo: { PrestamosStatus: 'Inactivo', // solo los que no tienen servicio activo Prestamos: true, // solo los que tienen el servicio de préstamos activo - } + }, }, relations: [ 'genero', - 'carreraUsuarios', // relación intermedia - 'carreraUsuarios.carrera', // carrera asociada a través de la intermedia + 'carreraUsuarios', // relación intermedia + 'carreraUsuarios.carrera', // carrera asociada a través de la intermedia 'usuarioTipos', 'usuarioTipos.tipoUsuario', ], - }); - - const header = [ - 'num_cuenta', 'nombre', 'a_paterno', 'a_materno', - 'carreras', 'rfc', 'tipos_usuario' - ].join(',') + '\n'; - const rows = usuarios.map(u => { - const carreras = (u.carreraUsuarios ?? []) - .map(cu => cu.carrera?.carrera ?? '') // obtenemos solo el nombre - .join(' | '); // une todas las carreras en un solo string + const header = + [ + 'num_cuenta', + 'nombre', + 'a_paterno', + 'a_materno', + 'carreras', + 'rfc', + 'tipos_usuario', + ].join(',') + '\n'; - const tiposUsuario = (u.usuarioTipos ?? []) - .map(utu => utu.tipoUsuario?.tipo_usuario ?? '') - .join(' | '); // une todos los tipos de usuario en un solo string + const rows = usuarios + .map((u) => { + const carreras = (u.carreraUsuarios ?? []) + .map((cu) => cu.carrera?.carrera ?? '') // obtenemos solo el nombre + .join(' | '); // une todas las carreras en un solo string - return [ - u.num_cuenta ?? '', - u.nombre ?? '', - u.a_paterno ?? '', - u.a_materno ?? '', - carreras ?? '', - u.rfc ?? '', - tiposUsuario - ].join(','); - }).join('\n'); + const tiposUsuario = (u.usuarioTipos ?? []) + .map((utu) => utu.tipoUsuario?.tipo_usuario ?? '') + .join(' | '); // une todos los tipos de usuario en un solo string + + return [ + u.num_cuenta ?? '', + u.nombre ?? '', + u.a_paterno ?? '', + u.a_materno ?? '', + carreras ?? '', + u.rfc ?? '', + tiposUsuario, + ].join(','); + }) + .join('\n'); const tablaCompleta = header + rows; - console.log("rows:",rows) + console.log('rows:', rows); return tablaCompleta; } async generateCsvRed(): Promise { - - - const tipos = [ 'Ampliación de Conocimiento', 'Servicio Social', @@ -639,36 +701,31 @@ export class ExcelService { }, servActivo: { RedStatus: 'Inactivo', - Red: true, // solo los que tienen el servicio de red - } + Red: true, // solo los que tienen el servicio de red + }, }, - relations: [ - 'usuarioTipos', - 'usuarioTipos.tipoUsuario', - ], - + relations: ['usuarioTipos', 'usuarioTipos.tipoUsuario'], }); + const header = + ['num_cuenta', 'fecha_nacimiento', 'contraseña', 'tipos_usuario'].join( + ',', + ) + '\n'; + const rows = usuarios + .map((u) => { + const tiposUsuario = (u.usuarioTipos ?? []) + .map((utu) => utu.tipoUsuario?.tipo_usuario ?? '') + .join(' | '); - - const header = [ - 'num_cuenta', 'fecha_nacimiento', 'contraseña', 'tipos_usuario' - ].join(',') + '\n'; - - const rows = usuarios.map(u => { - - const tiposUsuario = (u.usuarioTipos ?? []) - .map(utu => utu.tipoUsuario?.tipo_usuario ?? '') - .join(' | '); - - return [ - u.num_cuenta, - u.fecha_nacimiento, - u.contraseña ?? '', - tiposUsuario - ].join(','); - }).join('\n'); + return [ + u.num_cuenta, + u.fecha_nacimiento, + u.contraseña ?? '', + tiposUsuario, + ].join(','); + }) + .join('\n'); const tablaCompleta = header + rows; @@ -676,9 +733,6 @@ export class ExcelService { } async generateCsvAT(): Promise { - - - const tipos = [ 'Ampliación de Conocimiento', 'Servicio Social', @@ -699,55 +753,58 @@ export class ExcelService { }, servActivo: { ATStatus: 'Inactivo', - AT: true + AT: true, }, }, relations: [ 'genero', - 'carreraUsuarios', // relación intermedia - 'carreraUsuarios.carrera', // carrera asociada a través de la intermedia + 'carreraUsuarios', // relación intermedia + 'carreraUsuarios.carrera', // carrera asociada a través de la intermedia 'usuarioTipos', 'usuarioTipos.tipoUsuario', ], - }); + const header = + [ + 'num_cuenta', + 'nombre', + 'a_paterno', + 'a_materno', + 'carreras', + 'fecha_nacimiento', + 'generacion', + 'tipos_usuario', + ].join(',') + '\n'; + const rows = usuarios + .map((u) => { + const carreras = (u.carreraUsuarios ?? []) + .map((cu) => cu.carrera?.carrera ?? '') // obtenemos solo el nombre + .join(' | '); // une todas las carreras en un solo string + const tiposUsuario = (u.usuarioTipos ?? []) + .map((utu) => utu.tipoUsuario?.tipo_usuario ?? '') + .join(' | '); // une todos los tipos de usuario en un solo string - const header = [ - 'num_cuenta', 'nombre', 'a_paterno', 'a_materno', - 'carreras', 'fecha_nacimiento', 'generacion', 'tipos_usuario' - ].join(',') + '\n'; - - const rows = usuarios.map(u => { - const carreras = (u.carreraUsuarios ?? []) - .map(cu => cu.carrera?.carrera ?? '') // obtenemos solo el nombre - .join(' | '); // une todas las carreras en un solo string - - const tiposUsuario = (u.usuarioTipos ?? []) - .map(utu => utu.tipoUsuario?.tipo_usuario ?? '') - .join(' | '); // une todos los tipos de usuario en un solo string - - return [ - u.num_cuenta ?? '', - u.nombre ?? '', - u.a_paterno ?? '', - u.a_materno ?? '', - carreras ?? '', - u.fecha_nacimiento ?? '', - u.generacion?.toString() ?? '', - tiposUsuario - ].join(','); - }).join('\n'); + return [ + u.num_cuenta ?? '', + u.nombre ?? '', + u.a_paterno ?? '', + u.a_materno ?? '', + carreras ?? '', + u.fecha_nacimiento ?? '', + u.generacion?.toString() ?? '', + tiposUsuario, + ].join(','); + }) + .join('\n'); const tablaCompleta = header + rows; return tablaCompleta; } - - //Funcion que se le debe hacer su propio módulo async activarServicios(id_movimiento: number, origen: string): Promise { if (!['AT', 'SOLICITA', 'CORREO', 'RED'].includes(origen)) { @@ -759,7 +816,9 @@ export class ExcelService { }); if (!usuarios || usuarios.length === 0) { - throw new BadRequestException('No se encontró el usuario asociado al movimiento'); + throw new BadRequestException( + 'No se encontró el usuario asociado al movimiento', + ); } for (const usuario of usuarios) { @@ -788,73 +847,61 @@ export class ExcelService { } await this.servActivosRepo.save(servActivos); - } - } - async enviarCargaMasiva(): Promise { - const usuarios_red = await this.usuariosDelSistemaRepo.findOne({ where: { origen: { origen: 'RED' } } }); - const usuarios_at = await this.usuariosDelSistemaRepo.findOne({ where: { origen: { origen: 'AT' } } }); - const usuarios_correo = await this.usuariosDelSistemaRepo.findOne({ where: { origen: { origen: 'CORREO' } } }); - const usuarios_solicita = await this.usuariosDelSistemaRepo.findOne({ where: { origen: { origen: 'SOLICITA' } } }); - if (!usuarios_red || !usuarios_at || !usuarios_correo || !usuarios_solicita) { - throw new BadRequestException('No se encontraron usuarios para enviar correo'); - } - + async enviarCargaMasiva(data?: { + nuevos: number; + actualizados: number; + }): Promise { // Aquí puedes enviar el correo usnado MailService - await this.mailService.sendMail({ - to: usuarios_red.correo ?? '', - subject: 'Carga de Usuarios', - text: 'Se ha realizado una carga de usuarios en el sistema.', - html: '', - }); - await this.mailService.sendMail({ - to: usuarios_at.correo ?? '', - subject: 'Carga de Usuarios', - text: 'Se ha realizado una carga de usuarios en el sistema.', - html: '', - }); - await this.mailService.sendMail({ - to: usuarios_correo.correo ?? '', - subject: 'Carga de Usuarios', - text: 'Se ha realizado una carga de usuarios en el sistema.', - html: '', - }); - - + this.enviarInforme( + 'RED', + 'Carga de Usuarios', + `Se ha realizado una carga de usuarios en el sistema. Nuevos: ${data?.nuevos ?? 0}, Actualizados: ${data?.actualizados ?? 0}`, + ); + this.enviarInforme( + 'AT', + 'Carga de Usuarios', + `Se ha realizado una carga de usuarios en el sistema. Nuevos: ${data?.nuevos ?? 0}, Actualizados: ${data?.actualizados ?? 0}`, + ); + this.enviarInforme( + 'CORREO', + 'Carga de Usuarios', + `Se ha realizado una carga de usuarios en el sistema. Nuevos: ${data?.nuevos ?? 0}, Actualizados: ${data?.actualizados ?? 0}`, + ); + this.enviarInforme( + 'SOLICITA', + 'Carga de Usuarios', + `Se ha realizado una carga de usuarios en el sistema. Nuevos: ${data?.nuevos ?? 0}, Actualizados: ${data?.actualizados ?? 0}`, + ); console.log(`Correo enviados`); } - - - async enviarInforme(origen:string, subject:string ,text:string, html:string = "" ):Promise{ - - try{ - const user = await this.usuariosDelSistemaRepo.find({where:{origen:{origen}}}) - user.forEach(async(u)=>{ - await this.mailService.sendMail({ - to: u.correo ?? '', - subject, - text, - html, + async enviarInforme( + origen: string, + subject: string, + text: string, + html: string = '', + ): Promise { + try { + const user = await this.usuariosDelSistemaRepo.find({ + where: { origen: { origen } }, }); - }) - - - }catch(error){ - throw new BadRequestException('No se encontraron usuarios para enviar correo o no se pudo enviar el correo'); + user.forEach(async (u) => { + await this.mailService.sendMail({ + to: u.correo ?? '', + subject, + text, + html, + }); + }); + } catch (error) { + throw new BadRequestException( + 'No se encontraron usuarios para enviar correo o no se pudo enviar el correo', + ); } - - - } - - - - - - } diff --git a/src/usuarios/usuarios.service.ts b/src/usuarios/usuarios.service.ts index f360626..3c3b826 100644 --- a/src/usuarios/usuarios.service.ts +++ b/src/usuarios/usuarios.service.ts @@ -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,44 +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){ + async findByMov(id: number) { const usuarios = await this.usuarioRepository.find({ where: { - //movimiento:{id_mov:id}, + //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' - ], - + }, + }, + relations: ['usuarioTipos', 'usuarioTipos.tipoUsuario', 'servActivo'], }); - if(!usuarios)throw new Error("No existe suauarios relacionados a ese movimiento"); + 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) { @@ -109,44 +101,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'); @@ -158,10 +139,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; @@ -187,44 +167,51 @@ export class UsuariosService { const apiUrl = process.env.API_URL; if (!apiUrl) { - console.error("API_URL no está definida en las variables de entorno"); + console.error('API_URL no está definida en las variables de entorno'); 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); + console.error('Error 400:', errorText); return; } else if (response.status === 204) { - console.log("No se encontraron datos conforme a los criterios de búsqueda"); + console.log( + 'No se encontraron datos conforme a los criterios de búsqueda', + ); return; } - if (!response.ok) throw new Error("Error en la petición: " + response.statusText); + if (!response.ok) + throw new Error('Error en la petición: ' + response.statusText); const json: DatosRespuesta[] = await response.json(); let nuevos = 0; let actualizados = 0; - let existe = await this.tipoUsuarioRepository.findOne({ where: { tipo_usuario: 'Profesor' } }); + let existe = await this.tipoUsuarioRepository.findOne({ + where: { tipo_usuario: 'Profesor' }, + }); if (!existe) { - const tipoUsuario = this.tipoUsuarioRepository.create({ tipo_usuario: 'Profesor' }); + const tipoUsuario = this.tipoUsuarioRepository.create({ + tipo_usuario: 'Profesor', + }); existe = await this.tipoUsuarioRepository.save(tipoUsuario); } @@ -237,31 +224,40 @@ export class UsuariosService { const existente = await this.usuarioRepository.findOne({ where: { rfc: prof.rfc }, - relations: ['genero'] + relations: ['genero'], }); 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 } }); + const generoTexto = + prof.genero === 'M' ? 'Masculino' : 'Femenino'; + genero = await this.generoRepository.findOne({ + where: { genero: generoTexto }, + }); if (!genero) { - genero = await this.generoRepository.create({ genero: generoTexto }); + genero = await this.generoRepository.create({ + genero: generoTexto, + }); await this.generoRepository.save(genero); } } - let carrera = await this.carreraRepository.findOne({ where: { carrera: prof.nombreCarrera.trim() } }) + let carrera = await this.carreraRepository.findOne({ + where: { carrera: prof.nombreCarrera.trim() }, + }); if (!carrera) { - const nuevaCarrera = await this.carreraRepository.create({ carrera: prof.nombreCarrera.trim(), clave: '' }); + const nuevaCarrera = await this.carreraRepository.create({ + carrera: prof.nombreCarrera.trim(), + clave: '', + }); carrera = await this.carreraRepository.save(nuevaCarrera); } - - const nuevo = await this.usuarioRepository.create({ - num_cuenta: prof.numeroTrabajador == '' ? undefined : prof.numeroTrabajador, + num_cuenta: + prof.numeroTrabajador == '' ? undefined : prof.numeroTrabajador, nombre: prof.nombre, a_paterno: prof.apellidoPaterno, a_materno: prof.apellidoMaterno, @@ -269,7 +265,7 @@ export class UsuariosService { fecha_nacimiento: prof.fechaNacimiento, genero, - movimiento: mov + movimiento: mov, }); const savedUser = await this.usuarioRepository.save(nuevo); @@ -283,13 +279,19 @@ export class UsuariosService { AT: prof.numeroTrabajador !== '', Red: prof.numeroTrabajador !== '', Prestamos: prof.numeroTrabajador !== '', - Correo: true + Correo: true, }); await this.servActivosRepository.save(servActivo); - const userCarrera = await this.carreraUsuario.create({ carrera, usuario: savedUser }); - const usuarioTipo = await this.usuarioTipoUsuario.create({ tipoUsuario: existe, usuario: savedUser }); + const userCarrera = await this.carreraUsuario.create({ + carrera, + usuario: savedUser, + }); + const usuarioTipo = await this.usuarioTipoUsuario.create({ + tipoUsuario: existe, + usuario: savedUser, + }); await this.carreraUsuario.save(userCarrera); await this.usuarioTipoUsuario.save(usuarioTipo); @@ -298,67 +300,75 @@ export class UsuariosService { } else { let cambios = false; - if (existente.nombre !== prof.nombre) { existente.nombre = prof.nombre; - const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } }); - + 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); + console.warn( + 'No se encontró el servicio activo para el usuario:', + existente, + ); } 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 } } }); + 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); + 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 } } }); + 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); + console.warn( + 'No se encontró el servicio activo para el usuario:', + existente, + ); } cambios = true; @@ -366,31 +376,35 @@ export class UsuariosService { if (existente.rfc !== prof.rfc) { existente.rfc = prof.rfc; - const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } }); + 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); + 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 } } }); + const servActivo = await this.servActivosRepository.findOne({ + where: { usuario: { id_usuario: existente.id_usuario } }, + }); if (servActivo) { - - servActivo.Prestamos = true; servActivo.Correo = true; servActivo.PrestamosStatus = 'Inactivo'; @@ -398,16 +412,23 @@ export class UsuariosService { await this.servActivosRepository.save(servActivo); } else { - console.warn('No se encontró el servicio activo para el usuario:', existente); + console.warn( + 'No se encontró el servicio activo para el usuario:', + existente, + ); } cambios = true; } - if (prof.numeroTrabajador && existente.num_cuenta !== prof.numeroTrabajador) { + 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 } } }); + const servActivo = await this.servActivosRepository.findOne({ + where: { usuario: { id_usuario: existente.id_usuario } }, + }); if (servActivo) { servActivo.Red = true; servActivo.AT = true; @@ -420,7 +441,10 @@ export class UsuariosService { servActivo.RedStatus = 'Inactivo'; await this.servActivosRepository.save(servActivo); } else { - console.warn('No se encontró el servicio activo para el usuario:', existente); + console.warn( + 'No se encontró el servicio activo para el usuario:', + existente, + ); } cambios = true; } @@ -433,36 +457,51 @@ export class UsuariosService { } } - console.log(`Progreso -> Nuevos: ${nuevos}, Actualizados: ${actualizados}`); + console.log( + `Progreso -> Nuevos: ${nuevos}, Actualizados: ${actualizados}`, + ); } catch (innerError) { console.error('Error procesando profesor:', prof, innerError); } } - this.movimientoService.updateStatus(mov.id_mov, 'SUCCESS', 'Carga masiva de web Service completa: ' + `Nuevos: ${nuevos}, Actualizados: ${actualizados}`) + this.movimientoService.updateStatus( + mov.id_mov, + 'SUCCESS', + 'Carga masiva de web Service completa: ' + + `Nuevos: ${nuevos}, Actualizados: ${actualizados}`, + ); if (!process.env.EMAIL_USER) { - throw new Error() + throw new Error(); } 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: '

    ' - }) + text: + 'Carga masiva de web Service completa: ' + + `Nuevos: ${nuevos}, Actualizados: ${actualizados}`, + html: '', + }); - this.excelService.enviarCargaMasiva(); + 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 al consultar profesores desde el WebService', + error, + ); throw error; } } - - async cargaIndividual(usuario: CreateUsuarioDto, origen: string, user:string) { + async cargaIndividual( + usuario: CreateUsuarioDto, + origen: string, + user: string, + ) { console.log('Iniciando carga individual para usuario:', usuario); - const queryRunner = this.usuarioRepository.manager.connection.createQueryRunner(); + const queryRunner = + this.usuarioRepository.manager.connection.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); @@ -476,12 +515,20 @@ export class UsuariosService { 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'); + 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); @@ -489,24 +536,29 @@ export class UsuariosService { } let carrera; - if(usuario.carrera!=undefined){ - carrera = await queryRunner.manager.findOne(Carrera, { where: { carrera: usuario.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: ""}}) + carrera = await queryRunner.manager.findOne(Carrera, { + where: { carrera: '' }, + }); } - console.log(carrera) - }else{ - carrera= await queryRunner.manager.findOne(Carrera, {where:{carrera: ""}}) - if(!carrera){ - const nuevaCarrera = queryRunner.manager.create(Carrera, { carrera: "", clave: "" }); + console.log(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, @@ -521,21 +573,37 @@ export class UsuariosService { contraseña: usuario.contraseña ?? null, // Asegúrate de manejar la contraseña adecuadamente }); - console.log(nuevo) + console.log(nuevo); 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 @@ -558,17 +626,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; - + 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, @@ -577,7 +641,7 @@ export class UsuariosService { CorreoStatus: 'Inactivo', PrestamosStatus: 'Inactivo', }); - correo=true; + correo = true; await queryRunner.manager.save(servActivosDiplomado); break; @@ -590,10 +654,9 @@ export class UsuariosService { ATStatus: 'Inactivo', CorreoStatus: 'Inactivo', PrestamosStatus: 'Inactivo', - }); - solicita=true; - correo=true; + solicita = true; + correo = true; await queryRunner.manager.save(servActivosExtraLargo); break; @@ -608,14 +671,12 @@ export class UsuariosService { CorreoStatus: 'Inactivo', PrestamosStatus: 'Inactivo', }); - at=true; - red=true; - correo=true; + at = true; + red = true; + correo = true; await queryRunner.manager.save(servActivosServicio); break; - - case 'Idiomas R (UNAM)': case 'Idiomas Sabatino': const servActivosIdiomas = this.servActivosRepository.create({ @@ -627,15 +688,12 @@ export class UsuariosService { CorreoStatus: 'Inactivo', PrestamosStatus: 'Inactivo', }); - red=true; - correo=true; + red = true; + correo = true; await queryRunner.manager.save(servActivosIdiomas); break; - - - case 'Reinscrito': case 'Posgrado': case 'Intercambio UNAM': @@ -644,7 +702,6 @@ export class UsuariosService { case 'Licenciatura': case 'Profesor': const servActivos = this.servActivosRepository.create({ - Red: true, AT: true, Correo: true, @@ -655,43 +712,35 @@ export class UsuariosService { CorreoStatus: 'Inactivo', PrestamosStatus: 'Inactivo', }); - at=true; - red=true; - correo=true; - solicita=true; + 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; + red = true; await queryRunner.manager.save(servActivosTrabajadores); break; } - - console.log(savedUser) + console.log(savedUser); 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); + throw new Error('Error en la carga individual: ' + error.message); } finally { await queryRunner.release(); } } } - -