From 95d4fa506cf5fcaac1cbaed413bfd1791ddcda2d Mon Sep 17 00:00:00 2001 From: evenegas Date: Wed, 2 Jul 2025 10:19:00 -0600 Subject: [PATCH] se cambio la descarga de a csv y subida de archivos --- src/excel/excel.controller.ts | 26 +-- src/excel/excel.service.ts | 390 +++++++++++++++------------------- 2 files changed, 184 insertions(+), 232 deletions(-) diff --git a/src/excel/excel.controller.ts b/src/excel/excel.controller.ts index 0268c2f..ca9bcf3 100644 --- a/src/excel/excel.controller.ts +++ b/src/excel/excel.controller.ts @@ -148,13 +148,13 @@ export class ExcelController { origen, 'SUCCESS', - undefined, + 'descarga de csv', `size=${csv.length}` ); return res .status(HttpStatus.OK) - .header('Content-Type', 'text/tab-separated-values') - .header('Content-Disposition', 'attachment; filename="usuarios_solicita.tsv"') + .header('Content-Type', 'text/csv') + .header('Content-Disposition', 'attachment; filename="usuarios_solicita.csv"') .send(csv); } catch (err) { await this.movimientoService.log( @@ -166,20 +166,20 @@ export class ExcelController { .status(HttpStatus.FORBIDDEN) .json({ statusCode: 403, message: 'Origen no permitido para descarga.' }); } - } else if (origen == 'CORREO') { + } else if (origen == 'CORREO' || origen == 'LOAD') { try { const csv = await this.excelService.generateCsvCorreo(); await this.movimientoService.log( origen, 'SUCCESS', - undefined, + 'descarga de csv', `size=${csv.length}` ); return res .status(HttpStatus.OK) - .header('Content-Type', 'text/tab-separated-values') - .header('Content-Disposition', 'attachment; filename="usuarios.tsv"') + .header('Content-Type', 'text/csv') + .header('Content-Disposition', 'attachment; filename="usuarios_correo.csv"') .send(csv); } catch (err) { await this.movimientoService.log( @@ -199,13 +199,13 @@ export class ExcelController { origen, 'SUCCESS', - undefined, + 'descarga de csv', `size=${csv.length}` ); return res .status(HttpStatus.OK) - .header('Content-Type', 'text/tab-separated-values') - .header('Content-Disposition', 'attachment; filename="usuarios_at.tsv"') + .header('Content-Type', 'text/csv') + .header('Content-Disposition', 'attachment; filename="usuarios_AT.csv"') .send(csv); } catch (err) { await this.movimientoService.log( @@ -225,13 +225,13 @@ export class ExcelController { origen, 'SUCCESS', - undefined, + 'descarga de csv', `size=${csv.length}` ); return res .status(HttpStatus.OK) - .header('Content-Type', 'text/tab-separated-values') - .header('Content-Disposition', 'attachment; filename="usuarios_red.tsv"') + .header('Content-Type', 'text/csv') + .header('Content-Disposition', 'attachment; filename="usuarios_red.csv"') .send(csv); } catch (err) { await this.movimientoService.log( diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts index e536350..db8ab59 100644 --- a/src/excel/excel.service.ts +++ b/src/excel/excel.service.ts @@ -1,7 +1,7 @@ // src/excel/excel.service.ts import { Injectable, BadRequestException, Logger } from '@nestjs/common'; import { Workbook } from 'exceljs'; -import { In, Repository } from 'typeorm'; +import { DataSource, In, Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; import { CarreraUsuario, TipoUsuario, Usuario, UsuariosDelSistema, UsuarioTipoUsuario } from '../entities/entities'; import { Genero } from '../entities/entities'; @@ -48,7 +48,8 @@ export class ExcelService { @InjectRepository(UsuariosDelSistema) private readonly usuariosDelSistemaRepo: Repository, private readonly movimientoService: MovimientoService, - private readonly mailService: MailService + private readonly mailService: MailService, + private readonly dataSource: DataSource // … inyecta otros repositorios si los usarás ) { } @@ -118,7 +119,7 @@ export class ExcelService { const rowNum = i + 2; // por el encabezado // Campos que no pueden quedar vacíos - ['cuenta', 'nombreCompleto', 'tipo'].forEach(field => { + ['cuenta', 'tipo'].forEach(field => { const raw = (r as any)[field]; const str = raw != null ? raw.toString().trim() : ''; if (!str) { @@ -144,7 +145,7 @@ export class ExcelService { seen.add(cuentaStr); } - // Numérico en cuenta y generación + const genStr = r.gen != null ? r.gen.toString().trim() : ''; @@ -201,215 +202,166 @@ export class ExcelService { const status = this.validateRows(file); const errs = status.errors; const rows = status.rowsGood; - let carga - if (rows.length == 0) { + + if (rows.length === 0) { throw new BadRequestException('No se encontraron filas válidas para cargar. ' + errs); } + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); - const movimiento = await this.movimientoService.log( + try { + const movimiento = await this.movimientoService.logger( + queryRunner, + 'LOAD', + 'LOADING', + undefined, + 'CARGA MASIVA DE USUARIOS' + ); - 'LOAD', - 'LOADING', - undefined, - `CARGA MASIVA DE USUARIOS`, - true, // flag para indicar que es una carga masiva - ); + let contador = 0; + for (const r of rows) { + const whereCond: Array<{ num_cuenta?: string; rfc?: string }> = [{ num_cuenta: r.cuenta }]; + if (r.rfc) { + whereCond.push({ rfc: r.rfc }); + } - let contador: number = 0; - - for (const r of rows) { - //0.1 - const userExists = await this.usuarioRepo.findOne({ - where: { num_cuenta: r.cuenta }, - }); - - if (userExists) { - errs.push(`Cuenta ${r.cuenta} ya existe, se omite.`); - contador++; - continue; // si ya existe, no lo insertamos - } - - - // 1) Género (crea o reutiliza) - let genero = await this.generoRepo.findOne({ where: { genero: r.sexo } }); - if (!genero) { - genero = this.generoRepo.create({ genero: r.sexo }); - await this.generoRepo.save(genero); - } - - // 2) Carrera (por clave) - let carrera = await this.carreraRepo.findOne({ - where: { clave: r.clave }, - }); - if (!carrera) { - carrera = this.carreraRepo.create({ - clave: r.clave, - carrera: r.nomCarr.slice(0, 100), + const userExists = await queryRunner.manager.findOne(Usuario, { + where: whereCond, }); - await this.carreraRepo.save(carrera); - } - let tipo = await this.tipoUsuarioRepo.findOne({ - where: { tipo_usuario: r.tipo.trim() }, - }); - if (!tipo) { - tipo = this.tipoUsuarioRepo.create({ - tipo_usuario: r.tipo.trim(), + + + if (userExists) { + 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; + } + + let genero = await queryRunner.manager.findOne(Genero, { where: { genero: r.sexo } }); + if (!genero) { + genero = queryRunner.manager.create(Genero, { genero: r.sexo }); + await queryRunner.manager.save(genero); + } + + let carrera + if (r.clave || r.nomCarr) { + carrera = await queryRunner.manager.findOne(Carrera, { where: { clave: r.clave } }); + if (!carrera) { + carrera = queryRunner.manager.create(Carrera, { + clave: r.clave, + carrera: r.nomCarr.slice(0, 100), + }); + await queryRunner.manager.save(carrera); + } + + } else { + carrera = null; + } + + let tipo = await queryRunner.manager.findOne(TipoUsuario, { + where: { tipo_usuario: r.tipo.trim() }, }); - await this.tipoUsuarioRepo.save(tipo); + if (!tipo) { + tipo = queryRunner.manager.create(TipoUsuario, { tipo_usuario: r.tipo.trim() }); + await queryRunner.manager.save(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.trim().split(' ')[0], + a_paterno: r.apellidopa, + a_materno: r.apellidoma, + rfc: r.rfc, + fecha_nacimiento: r.fechnac, + generacion, + genero, + movimiento, + }); + const user = await queryRunner.manager.save(usuario); + + if (carrera) { + const carreraUsuario = queryRunner.manager.create(CarreraUsuario, { + usuario: user, + carrera, + }); + await queryRunner.manager.save(carreraUsuario); + } + + const usuarioTipo = queryRunner.manager.create(UsuarioTipoUsuario, { + usuario: user, + tipoUsuario: tipo, + }); + await queryRunner.manager.save(usuarioTipo); + + const servData = { + usuario: user, + RedStatus: 'Inactivo', + ATStatus: 'Inactivo', + CorreoStatus: 'Inactivo', + PrestamosStatus: 'Inactivo', + }; + + switch (r.tipo.trim()) { + case 'Diplomado': + Object.assign(servData, { Correo: true }); + break; + case 'Extra Largo': + Object.assign(servData, { Correo: true, Prestamos: true }); + break; + case 'Servicio Social': + Object.assign(servData, { Correo: true, AT: true, Red: true }); + break; + case 'Idiomas R (UNAM)': + case 'Idiomas Sabatino': + Object.assign(servData, { Correo: true, Red: true }); + 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 }); + break; + case 'Trabajadores': + Object.assign(servData, { Red: true }); + break; + } + + const serv = queryRunner.manager.create(ServActivos, servData); + await queryRunner.manager.save(serv); } - const generacion = /^[0-9]{4}$/.test(r.gen?.toString().trim()) - ? parseInt(r.gen.toString().trim(), 10) - : null; - - - // 3) Usuario - const usuario = this.usuarioRepo.create({ - num_cuenta: r.cuenta, - nombre: r.nombres.trim().split(' ')[0], // ajusta si quieres - a_paterno: r.apellidopa, - a_materno: r.apellidoma, - rfc: r.rfc, - fecha_nacimiento: r.fechnac, - generacion: generacion, - genero, - movimiento: { id_mov: movimiento.id_mov }, // Relación con movimiento - - }); - const user = await this.usuarioRepo.save(usuario); - - - - // 4) Relacion usuario–carrxera (tabla intermedia) - const carreraUsuario = this.carreraUsuarioRepo.create({ - usuario, - carrera, - }); - await this.carreraUsuarioRepo.save(carreraUsuario); - - // 5) Tipo de usuario - - const usuarioTipo = this.usuarioTipoUsuarioRepo.create({ - usuario, - tipoUsuario: tipo, - }); - await this.usuarioTipoUsuarioRepo.save(usuarioTipo); - - // 6) ServActivos (si es necesario) - switch (r.tipo.trim()) { - - case 'Diplomado': - const servActivosDiplomado = this.servActivosRepo.create({ - Correo: true, - usuario: user, - RedStatus: 'Inactivo', - ATStatus: 'Inactivo', - CorreoStatus: 'Inactivo', - PrestamosStatus: 'Inactivo', - }); - await this.servActivosRepo.save(servActivosDiplomado); - - break; - - case 'Extra Largo': - const servActivosExtraLargo = this.servActivosRepo.create({ - Prestamos: true, - Correo: true, - usuario: user, - RedStatus: 'Inactivo', - ATStatus: 'Inactivo', - CorreoStatus: 'Inactivo', - PrestamosStatus: 'Inactivo', - }); - await this.servActivosRepo.save(servActivosExtraLargo); - break; - - case 'Servicio Social': - const servActivosServicio = this.servActivosRepo.create({ - Red: true, - AT: true, - Correo: true, - usuario: user, - RedStatus: 'Inactivo', - ATStatus: 'Inactivo', - CorreoStatus: 'Inactivo', - PrestamosStatus: 'Inactivo', - }); - await this.servActivosRepo.save(servActivosServicio); - break; - - - - case 'Idiomas R (UNAM)': - case 'Idiomas Sabatino': - const servActivosIdiomas = this.servActivosRepo.create({ - Red: true, - Correo: true, - usuario: user, - RedStatus: 'Inactivo', - ATStatus: 'Inactivo', - CorreoStatus: 'Inactivo', - PrestamosStatus: 'Inactivo', - }); - await this.servActivosRepo.save(servActivosIdiomas); - - break; - - - - - case 'Reinscrito': - case 'Posgrado': - case 'Intercambio UNAM': - case 'Movilidad': - case 'Ampliación de Conocimiento': - case 'Licenciatura': - case 'Profesor': - const servActivos = this.servActivosRepo.create({ - - Red: true, - AT: true, - Correo: true, - Prestamos: true, - usuario: user, - RedStatus: 'Inactivo', - ATStatus: 'Inactivo', - CorreoStatus: 'Inactivo', - PrestamosStatus: 'Inactivo', - }); - await this.servActivosRepo.save(servActivos); - - break; - - - - case 'Trabajadores': - const servActivosTrabajadores = this.servActivosRepo.create({ - Red: true, - usuario: user, - RedStatus: 'Inactivo', - - }); - await this.servActivosRepo.save(servActivosTrabajadores); - break; - } - - + await queryRunner.commitTransaction(); + return { inserted: rows.length - contador, id_movimiento: movimiento.id_mov, errors: errs }; + } catch (err) { + await queryRunner.rollbackTransaction(); + throw err; + } finally { + await queryRunner.release(); } - - - - return { inserted: rows.length - contador, id_movimiento: movimiento.id_mov, errors: errs }; } + // async generateCsv(): Promise { // const users = await this.usuarioRepo.find({ // relations: ['genero', 'carreraUsuarios'], // si quieres más info @@ -483,7 +435,7 @@ export class ExcelService { const header = [ 'num_cuenta', 'nombre', 'a_paterno', 'a_materno', 'carreras', 'rfc', 'fecha_nacimiento', 'generacion' - ].join('\t') + '\n'; + ].join(',') + '\n'; const rows = usuarios.map(u => { const carreras = (u.carreraUsuarios ?? []) @@ -491,15 +443,15 @@ export class ExcelService { .join(', '); // une todas las carreras en un solo string return [ - u.num_cuenta, - u.nombre, - u.a_paterno, - u.a_materno, - carreras, + u.num_cuenta ?? '', + u.nombre ?? '', + u.a_paterno ?? '', + u.a_materno ?? '', + carreras ?? '', u.rfc ?? '', - u.fecha_nacimiento, + u.fecha_nacimiento ?? '', u.generacion?.toString() ?? '' - ].join('\t'); + ].join(','); }).join('\n'); const tablaCompleta = header + rows; @@ -565,7 +517,7 @@ export class ExcelService { const header = [ 'num_cuenta', 'nombre', 'a_paterno', 'a_materno', 'carreras', 'rfc' - ].join('\t') + '\n'; + ].join(',') + '\n'; const rows = usuarios.map(u => { const carreras = (u.carreraUsuarios ?? []) @@ -573,13 +525,13 @@ export class ExcelService { .join(', '); // une todas las carreras en un solo string return [ - u.num_cuenta, - u.nombre, - u.a_paterno, - u.a_materno, - carreras, + u.num_cuenta ?? '', + u.nombre ?? '', + u.a_paterno ?? '', + u.a_materno ?? '', + carreras ?? '', u.rfc ?? '', - ].join('\t'); + ].join(','); }).join('\n'); const tablaCompleta = header + rows; @@ -630,13 +582,13 @@ export class ExcelService { const header = [ 'num_cuenta', 'fecha_nacimiento' - ].join('\t') + '\n'; + ].join(',') + '\n'; const rows = usuarios.map(u => { return [ u.num_cuenta, u.fecha_nacimiento, - ].join('\t'); + ].join(','); }).join('\n'); const tablaCompleta = header + rows; @@ -686,22 +638,22 @@ export class ExcelService { const header = [ 'num_cuenta', 'nombre', 'a_paterno', 'a_materno', 'carreras', 'fecha_nacimiento', 'generacion' - ].join('\t') + '\n'; + ].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 + .join(','); // une todas las carreras en un solo string return [ - u.num_cuenta, - u.nombre, - u.a_paterno, - u.a_materno, - carreras, - u.fecha_nacimiento, + u.num_cuenta ?? '', + u.nombre ?? '', + u.a_paterno ?? '', + u.a_materno ?? '', + carreras ?? '', + u.fecha_nacimiento ?? '', u.generacion?.toString() ?? '' - ].join('\t'); + ].join(','); }).join('\n'); const tablaCompleta = header + rows;