From 6afc4cdae8873543d402841a4757943e085ce997 Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Thu, 5 Feb 2026 09:10:47 -0600 Subject: [PATCH 1/7] =?UTF-8?q?se=20cambi=C3=B3=20el=20la=20l=C7=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/excel/excel.service.ts | 46 ++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts index caa0823..3441db9 100644 --- a/src/excel/excel.service.ts +++ b/src/excel/excel.service.ts @@ -651,8 +651,7 @@ export class ExcelService { }); usuarios.push(usuario); - idsUsuarios.push(usuario.id_usuario) - + if (carrera) { const carreraUsuario = queryRunner.manager.create(CarreraUsuario, { usuario, @@ -735,7 +734,10 @@ export class ExcelService { // 1️⃣ Guardar usuarios primero — asigna IDs await queryRunner.manager.save(usuarios); - + idsUsuarios = usuarios.map(u => u.id_usuario); + idsUsuarios.push( + ...existentes.map(e => e.id_usuario) + ); // 2️⃣ Reconstruir relaciones con usuarios ya guardados // CarreraUsuario @@ -791,23 +793,29 @@ export class ExcelService { await queryRunner.manager.save(usuariosTiposFinal); await queryRunner.manager.save(serviciosActivosFinal); - - await this.servActivosRepo - .createQueryBuilder() - .update() - .set({ - Red: false, - Prestamos: false, - AT: false, - Correo: false, - RedStatus: 'Inactivo', - PrestamosStatus: 'Inactivo', - ATStatus: 'Inactivo', - CorreoStatus: 'Inactivo', - }) - .where('id_usuario NOT IN (:...ids)', { ids: idsUsuarios }) - .execute(); + await this.servActivosRepo + .createQueryBuilder('sa') + .leftJoin( + Usuario, + 'u', + 'u.id_usuario = sa.id_usuario AND u.id_usuario IN (:...ids)', + { ids: idsUsuarios }, + ) + .update() + .set({ + Red: false, + Prestamos: false, + AT: false, + Correo: false, + RedStatus: 'Inactivo', + PrestamosStatus: 'Inactivo', + ATStatus: 'Inactivo', + CorreoStatus: 'Inactivo', + }) + .where('u.id_usuario IS NULL') + .execute(); + return { From 08745e2a7e245201107374bd17d124435388c4eb Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Thu, 5 Feb 2026 10:44:38 -0600 Subject: [PATCH 2/7] =?UTF-8?q?se=20cambi=C3=B3=20la=20estructura?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/excel/excel.service.ts | 514 +++++++++++++++---------------------- 1 file changed, 203 insertions(+), 311 deletions(-) diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts index 3441db9..77a01f6 100644 --- a/src/excel/excel.service.ts +++ b/src/excel/excel.service.ts @@ -502,339 +502,231 @@ export class ExcelService { } async loadFile2(buffer: Buffer, user: string) { - const file = await this.parseFile(buffer); - const status = this.validateRows(file); - const errs = status.errors; - const rows = status.rowsGood; + 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 = 0; + let conteoTiposAt = 0; + let conteoTiposSolicita = 0; + let conteoTiposCorreo = 0; + let contador = 0; + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); - if (rows.length === 0) { - throw new BadRequestException( - 'No se encontraron filas válidas para cargar. ' + errs, - ); - } + let idsUsuariosFinales: number[] = []; - let conteoTiposRed: number = 0; - let conteoTiposAt: number = 0; - let conteoTiposSolicita: number = 0; - let conteoTiposCorreo: number = 0; + try { + /* ===================== MOVIMIENTO ===================== */ + const movimiento = await this.movimientoService.logger( + queryRunner, + 'LOAD', + 'LOADING', + 'Carga masiva de usuarios hecha por: ' + user, + 'CARGA MASIVA DE USUARIOS', + ); - const queryRunner = this.dataSource.createQueryRunner(); - await queryRunner.connect(); - await queryRunner.startTransaction(); + /* ===================== EXISTENTES ===================== */ + const cuentas = rows.map(r => r.cuenta).filter(Boolean); + const rfcs = rows.map(r => r.rfc).filter(Boolean); - try { - const movimiento = await this.movimientoService.logger( - queryRunner, - 'LOAD', - 'LOADING', - 'Caraga masiva de usuarios hecha por: ' + user, - `CARGA MASIVA DE USUARIOS`, - ); + const existentes = await queryRunner.manager.find(Usuario, { + where: [{ num_cuenta: In(cuentas) }, { rfc: In(rfcs) }], + select: ['id_usuario', 'num_cuenta', 'rfc'], + }); - // 🔹 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 setCuentas = new Set(existentes.map(u => u.num_cuenta)); + const setRfcs = new Set(existentes.map(u => u.rfc)); - const existentes = await queryRunner.manager.find(Usuario, { - where: [{ num_cuenta: In(cuentas) }, { rfc: In(rfcs) }], - select: ['num_cuenta', 'rfc', 'id_usuario'], + /* ===================== CATÁLOGOS ===================== */ + const generosUnicos = [...new Set(rows.map(r => r.sexo).filter(Boolean))]; + const clavesCarrera = [...new Set(rows.map(r => r.clave).filter(Boolean))]; + const nombresCarrera = [...new Set(rows.map(r => r.nomCarr).filter(Boolean))]; + const tiposUnicos = [...new Set(rows.map(r => r.tipo?.trim()).filter(Boolean))]; + + const [generos, carreras, tipos] = await Promise.all([ + queryRunner.manager.find(Genero, { where: { genero: In(generosUnicos) } }), + queryRunner.manager.find(Carrera, { + where: [{ clave: In(clavesCarrera) }, { carrera: In(nombresCarrera) }], + }), + queryRunner.manager.find(TipoUsuario, { + where: { tipo_usuario: In(tiposUnicos) }, + }), + ]); + + const mapGenero = new Map(generos.map(g => [g.genero, g])); + const mapCarreraClave = new Map(carreras.map(c => [c.clave, c])); + const mapCarreraNombre = new Map(carreras.map(c => [c.carrera, c])); + const mapTipo = new Map(tipos.map(t => [t.tipo_usuario, t])); + + /* ===================== ARRAYS ===================== */ + const usuarios: Usuario[] = []; + const carrerasUsuario: CarreraUsuario[] = []; + const usuariosTipos: UsuarioTipoUsuario[] = []; + const serviciosActivos: ServActivos[] = []; + + + for (const r of rows) { + if (setCuentas.has(r.cuenta) || (r.rfc && setRfcs.has(r.rfc))) { + const u = existentes.find( + e => e.num_cuenta === r.cuenta || e.rfc === r.rfc, + ); + if (u) idsUsuariosFinales.push(u.id_usuario); + contador++; + continue; + } + + if (!r.tipo || !r.nombres || !r.apellidopa || !r.apellidoma) { + errs.push(`Fila con cuenta ${r.cuenta} tiene campos faltantes`); + contador++; + continue; + } + + /* ---------- Género ---------- */ + let genero = mapGenero.get(r.sexo); + if (!genero) { + genero = queryRunner.manager.create(Genero, { genero: r.sexo }); + mapGenero.set(r.sexo, genero); + } + + /* ---------- Carrera ---------- */ + let carrera = + (r.clave && mapCarreraClave.get(r.clave)) || + (r.nomCarr && mapCarreraNombre.get(r.nomCarr)); + + if (!carrera && (r.clave || r.nomCarr)) { + carrera = queryRunner.manager.create(Carrera, { + clave: r.clave ?? '', + carrera: r.nomCarr?.slice(0, 100) ?? '', + }); + mapCarreraClave.set(carrera.clave, carrera); + mapCarreraNombre.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 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: /^\d{4}$/.test(r.gen?.toString()) + ? Number(r.gen) + : null, + genero, + movimiento, }); - const setCuentas = new Set(existentes.map((u) => u.num_cuenta)); - const setRfcs = new Set(existentes.map((u) => u.rfc)); + usuarios.push(usuario); - // 🔹 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; - let idsUsuarios: number[] =[] - for (const r of rows) { - if (setCuentas.has(r.cuenta) || (r.rfc && setRfcs.has(r.rfc))) { - const use= existentes.find( u => u.num_cuenta === r.cuenta || u.rfc === r.rfc) - if(use?.id_usuario) - idsUsuarios.push(use?.id_usuario) - 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 (carrera) { + carrerasUsuario.push( + queryRunner.manager.create(CarreraUsuario, { usuario, carrera }), ); + } - // Servicios activos - const servData: Partial = { + usuariosTipos.push( + queryRunner.manager.create(UsuarioTipoUsuario, { usuario, - RedStatus: 'Inactivo', - ATStatus: 'Inactivo', - CorreoStatus: 'Inactivo', - PrestamosStatus: 'Inactivo', - }; + tipoUsuario: tipo, + }), + ); - 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; - } + const sa = queryRunner.manager.create(ServActivos, { usuario }); - serviciosActivos.push( - queryRunner.manager.create(ServActivos, servData), - ); + switch (r.tipo.trim()) { + case 'Diplomado': + sa.Correo = true; conteoTiposCorreo++; break; + case 'Extra Largo': + sa.Correo = true; sa.Prestamos = true; + conteoTiposCorreo++; conteoTiposSolicita++; break; + case 'Servicio Social': + sa.Correo = true; sa.AT = true; sa.Red = true; + conteoTiposCorreo++; conteoTiposAt++; conteoTiposRed++; break; + case 'Idiomas R (UNAM)': + case 'Idiomas Sabatino': + sa.Correo = true; sa.Red = true; + conteoTiposCorreo++; conteoTiposRed++; break; + default: + sa.Correo = true; sa.AT = true; sa.Red = true; sa.Prestamos = true; + conteoTiposCorreo++; conteoTiposAt++; conteoTiposRed++; conteoTiposSolicita++; } - // 🔹 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); - idsUsuarios = usuarios.map(u => u.id_usuario); - idsUsuarios.push( - ...existentes.map(e => e.id_usuario) - ); - // 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); - - await this.servActivosRepo - .createQueryBuilder('sa') - .leftJoin( - Usuario, - 'u', - 'u.id_usuario = sa.id_usuario AND u.id_usuario IN (:...ids)', - { ids: idsUsuarios }, - ) - .update() - .set({ - Red: false, - Prestamos: false, - AT: false, - Correo: false, - RedStatus: 'Inactivo', - PrestamosStatus: 'Inactivo', - ATStatus: 'Inactivo', - CorreoStatus: 'Inactivo', - }) - .where('u.id_usuario IS NULL') - .execute(); - - - - - 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(); + serviciosActivos.push(sa); } + + /* ===================== GUARDADOS ===================== */ + await queryRunner.manager.save([...mapGenero.values()]); + await queryRunner.manager.save([...mapCarreraClave.values()]); + await queryRunner.manager.save([...mapTipo.values()]); + + await queryRunner.manager.save(usuarios, { chunk: 500 }); + idsUsuariosFinales.push(...usuarios.map(u => u.id_usuario)); + + await queryRunner.manager.save(carrerasUsuario, { chunk: 500 }); + await queryRunner.manager.save(usuariosTipos, { chunk: 500 }); + await queryRunner.manager.save(serviciosActivos, { chunk: 500 }); + + await queryRunner.commitTransaction(); + } catch (e) { + await queryRunner.rollbackTransaction(); + throw e; + } finally { + await queryRunner.release(); } + /* ===================== UPDATE FUERA ===================== */ + await this.servActivosRepo + .createQueryBuilder('sa') + .leftJoin( + Usuario, + 'u', + 'u.id_usuario = sa.id_usuario AND u.id_usuario IN (:...ids)', + { ids: idsUsuariosFinales }, + ) + .update() + .set({ + Red: false, + Prestamos: false, + AT: false, + Correo: false, + RedStatus: 'Inactivo', + PrestamosStatus: 'Inactivo', + ATStatus: 'Inactivo', + CorreoStatus: 'Inactivo', + }) + .where('u.id_usuario IS NULL') + .execute(); + + return { + inserted: rows.length - contador, + errors: errs, + conteoTiposAt, + conteoTiposCorreo, + conteoTiposRed, + conteoTiposSolicita, + }; +} + + // async generateCsv(): Promise { // const users = await this.usuarioRepo.find({ // relations: ['genero', 'carreraUsuarios'], // si quieres más info From c5722bfe024a82f65945c51c2d82023c20ea6aaf Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Thu, 5 Feb 2026 11:00:37 -0600 Subject: [PATCH 3/7] =?UTF-8?q?se=20cambi=C3=B3=20la=20estructura?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/excel/excel.service.ts | 43 ++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts index 77a01f6..52a1e3f 100644 --- a/src/excel/excel.service.ts +++ b/src/excel/excel.service.ts @@ -695,26 +695,29 @@ export class ExcelService { /* ===================== UPDATE FUERA ===================== */ await this.servActivosRepo - .createQueryBuilder('sa') - .leftJoin( - Usuario, - 'u', - 'u.id_usuario = sa.id_usuario AND u.id_usuario IN (:...ids)', - { ids: idsUsuariosFinales }, - ) - .update() - .set({ - Red: false, - Prestamos: false, - AT: false, - Correo: false, - RedStatus: 'Inactivo', - PrestamosStatus: 'Inactivo', - ATStatus: 'Inactivo', - CorreoStatus: 'Inactivo', - }) - .where('u.id_usuario IS NULL') - .execute(); + .createQueryBuilder() + .update(ServActivos) + .set({ + Red: false, + Prestamos: false, + AT: false, + Correo: false, + RedStatus: 'Inactivo', + PrestamosStatus: 'Inactivo', + ATStatus: 'Inactivo', + CorreoStatus: 'Inactivo', + }) + .where( + `NOT EXISTS ( + SELECT 1 + FROM usuario u + WHERE u.id_usuario = servActivos.id_usuario + AND u.id_usuario IN (:...ids) + )`, + { ids: idsUsuariosFinales }, + ) + .execute(); + return { inserted: rows.length - contador, From 9ad749782f8df1921ce352b4e6a3f2b54da9717b Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Thu, 5 Feb 2026 11:31:21 -0600 Subject: [PATCH 4/7] =?UTF-8?q?se=20cambi=C3=B3=20la=20estructura?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/excel/excel.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts index 52a1e3f..0d3468c 100644 --- a/src/excel/excel.service.ts +++ b/src/excel/excel.service.ts @@ -696,7 +696,7 @@ export class ExcelService { /* ===================== UPDATE FUERA ===================== */ await this.servActivosRepo .createQueryBuilder() - .update(ServActivos) + .update() .set({ Red: false, Prestamos: false, @@ -708,7 +708,7 @@ export class ExcelService { CorreoStatus: 'Inactivo', }) .where( - `NOT EXISTS ( + `NOT EXISTS ( SELECT 1 FROM usuario u WHERE u.id_usuario = servActivos.id_usuario @@ -719,6 +719,7 @@ export class ExcelService { .execute(); + return { inserted: rows.length - contador, errors: errs, From 8104fd35ff1f72345679efbe458644729bb46202 Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Thu, 5 Feb 2026 11:52:35 -0600 Subject: [PATCH 5/7] =?UTF-8?q?se=20cambi=C3=B3=20la=20estructura?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/excel/excel.service.ts | 516 ++++++++++++++++++++++--------------- 1 file changed, 313 insertions(+), 203 deletions(-) diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts index 0d3468c..d68d0b3 100644 --- a/src/excel/excel.service.ts +++ b/src/excel/excel.service.ts @@ -502,200 +502,327 @@ export class ExcelService { } async loadFile2(buffer: Buffer, user: string) { - const file = await this.parseFile(buffer); - const status = this.validateRows(file); - const errs = status.errors; - const rows = status.rowsGood; + 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 = 0; - let conteoTiposAt = 0; - let conteoTiposSolicita = 0; - let conteoTiposCorreo = 0; - let contador = 0; - const queryRunner = this.dataSource.createQueryRunner(); - await queryRunner.connect(); - await queryRunner.startTransaction(); - let idsUsuariosFinales: number[] = []; - - try { - /* ===================== MOVIMIENTO ===================== */ - const movimiento = await this.movimientoService.logger( - queryRunner, - 'LOAD', - 'LOADING', - 'Carga masiva de usuarios hecha por: ' + user, - 'CARGA MASIVA DE USUARIOS', - ); - - /* ===================== 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: ['id_usuario', 'num_cuenta', 'rfc'], - }); - - const setCuentas = new Set(existentes.map(u => u.num_cuenta)); - const setRfcs = new Set(existentes.map(u => u.rfc)); - - /* ===================== CATÁLOGOS ===================== */ - const generosUnicos = [...new Set(rows.map(r => r.sexo).filter(Boolean))]; - const clavesCarrera = [...new Set(rows.map(r => r.clave).filter(Boolean))]; - const nombresCarrera = [...new Set(rows.map(r => r.nomCarr).filter(Boolean))]; - const tiposUnicos = [...new Set(rows.map(r => r.tipo?.trim()).filter(Boolean))]; - - const [generos, carreras, tipos] = await Promise.all([ - queryRunner.manager.find(Genero, { where: { genero: In(generosUnicos) } }), - queryRunner.manager.find(Carrera, { - where: [{ clave: In(clavesCarrera) }, { carrera: In(nombresCarrera) }], - }), - queryRunner.manager.find(TipoUsuario, { - where: { tipo_usuario: In(tiposUnicos) }, - }), - ]); - - const mapGenero = new Map(generos.map(g => [g.genero, g])); - const mapCarreraClave = new Map(carreras.map(c => [c.clave, c])); - const mapCarreraNombre = new Map(carreras.map(c => [c.carrera, c])); - const mapTipo = new Map(tipos.map(t => [t.tipo_usuario, t])); - - /* ===================== ARRAYS ===================== */ - const usuarios: Usuario[] = []; - const carrerasUsuario: CarreraUsuario[] = []; - const usuariosTipos: UsuarioTipoUsuario[] = []; - const serviciosActivos: ServActivos[] = []; - - - for (const r of rows) { - if (setCuentas.has(r.cuenta) || (r.rfc && setRfcs.has(r.rfc))) { - const u = existentes.find( - e => e.num_cuenta === r.cuenta || e.rfc === r.rfc, - ); - if (u) idsUsuariosFinales.push(u.id_usuario); - contador++; - continue; - } - - if (!r.tipo || !r.nombres || !r.apellidopa || !r.apellidoma) { - errs.push(`Fila con cuenta ${r.cuenta} tiene campos faltantes`); - contador++; - continue; - } - - /* ---------- Género ---------- */ - let genero = mapGenero.get(r.sexo); - if (!genero) { - genero = queryRunner.manager.create(Genero, { genero: r.sexo }); - mapGenero.set(r.sexo, genero); - } - - /* ---------- Carrera ---------- */ - let carrera = - (r.clave && mapCarreraClave.get(r.clave)) || - (r.nomCarr && mapCarreraNombre.get(r.nomCarr)); - - if (!carrera && (r.clave || r.nomCarr)) { - carrera = queryRunner.manager.create(Carrera, { - clave: r.clave ?? '', - carrera: r.nomCarr?.slice(0, 100) ?? '', - }); - mapCarreraClave.set(carrera.clave, carrera); - mapCarreraNombre.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 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: /^\d{4}$/.test(r.gen?.toString()) - ? Number(r.gen) - : null, - genero, - movimiento, - }); - - usuarios.push(usuario); - - if (carrera) { - carrerasUsuario.push( - queryRunner.manager.create(CarreraUsuario, { usuario, carrera }), - ); - } - - 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, ); - - const sa = queryRunner.manager.create(ServActivos, { usuario }); - - switch (r.tipo.trim()) { - case 'Diplomado': - sa.Correo = true; conteoTiposCorreo++; break; - case 'Extra Largo': - sa.Correo = true; sa.Prestamos = true; - conteoTiposCorreo++; conteoTiposSolicita++; break; - case 'Servicio Social': - sa.Correo = true; sa.AT = true; sa.Red = true; - conteoTiposCorreo++; conteoTiposAt++; conteoTiposRed++; break; - case 'Idiomas R (UNAM)': - case 'Idiomas Sabatino': - sa.Correo = true; sa.Red = true; - conteoTiposCorreo++; conteoTiposRed++; break; - default: - sa.Correo = true; sa.AT = true; sa.Red = true; sa.Prestamos = true; - conteoTiposCorreo++; conteoTiposAt++; conteoTiposRed++; conteoTiposSolicita++; - } - - serviciosActivos.push(sa); } - /* ===================== GUARDADOS ===================== */ - 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, { chunk: 500 }); - idsUsuariosFinales.push(...usuarios.map(u => u.id_usuario)); + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + let idsUsuarios: number[] =[] + try { + const movimiento = await this.movimientoService.logger( + queryRunner, + 'LOAD', + 'LOADING', + 'Caraga masiva de usuarios hecha por: ' + user, + `CARGA MASIVA DE USUARIOS`, + ); - await queryRunner.manager.save(carrerasUsuario, { chunk: 500 }); - await queryRunner.manager.save(usuariosTipos, { chunk: 500 }); - await queryRunner.manager.save(serviciosActivos, { chunk: 500 }); + // 🔹 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); - await queryRunner.commitTransaction(); - } catch (e) { - await queryRunner.rollbackTransaction(); - throw e; - } finally { - await queryRunner.release(); - } + const existentes = await queryRunner.manager.find(Usuario, { + where: [{ num_cuenta: In(cuentas) }, { rfc: In(rfcs) }], + select: ['num_cuenta', 'rfc', 'id_usuario'], + }); - /* ===================== UPDATE FUERA ===================== */ - await this.servActivosRepo + 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))) { + const use= existentes.find( u => u.num_cuenta === r.cuenta || u.rfc === r.rfc) + if(use?.id_usuario) + idsUsuarios.push(use?.id_usuario) + 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); + idsUsuarios = usuarios.map(u => u.id_usuario); + idsUsuarios.push( + ...existentes.map(e => e.id_usuario) + ); + // 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(); + + const subQuery = await this.servActivosRepo.manager .createQueryBuilder() + .select('1') + .from(Usuario, 'u') + .where('u.id_usuario = sa.id_usuario') + .andWhere('u.id_usuario IN (:...ids)') + .getQuery(); + +await this.servActivosRepo + .createQueryBuilder('sa') .update() .set({ Red: false, @@ -707,29 +834,12 @@ export class ExcelService { ATStatus: 'Inactivo', CorreoStatus: 'Inactivo', }) - .where( - `NOT EXISTS ( - SELECT 1 - FROM usuario u - WHERE u.id_usuario = servActivos.id_usuario - AND u.id_usuario IN (:...ids) - )`, - { ids: idsUsuariosFinales }, - ) + .where(`NOT EXISTS (${subQuery})`) + .setParameters({ ids: idsUsuarios }) .execute(); - - - return { - inserted: rows.length - contador, - errors: errs, - conteoTiposAt, - conteoTiposCorreo, - conteoTiposRed, - conteoTiposSolicita, - }; -} - + } + } // async generateCsv(): Promise { // const users = await this.usuarioRepo.find({ From 656126919b2d3520965afe2ee7e20c57bc6f6384 Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Thu, 5 Feb 2026 11:58:59 -0600 Subject: [PATCH 6/7] =?UTF-8?q?se=20cambi=C3=B3=20la=20estructura?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/excel/excel.service.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts index d68d0b3..d21a1c4 100644 --- a/src/excel/excel.service.ts +++ b/src/excel/excel.service.ts @@ -813,16 +813,8 @@ export class ExcelService { } finally { await queryRunner.release(); - const subQuery = await this.servActivosRepo.manager + await this.servActivosRepo .createQueryBuilder() - .select('1') - .from(Usuario, 'u') - .where('u.id_usuario = sa.id_usuario') - .andWhere('u.id_usuario IN (:...ids)') - .getQuery(); - -await this.servActivosRepo - .createQueryBuilder('sa') .update() .set({ Red: false, @@ -834,10 +826,10 @@ await this.servActivosRepo ATStatus: 'Inactivo', CorreoStatus: 'Inactivo', }) - .where(`NOT EXISTS (${subQuery})`) - .setParameters({ ids: idsUsuarios }) + .where('id_usuario NOT IN (SELECT id_usuario FROM usuario)') .execute(); + } } From a288c37531de1611be6e58c2b97e4733724b228d Mon Sep 17 00:00:00 2001 From: IO420 <320154041@pcpuma.acatlan.unam.mx> Date: Thu, 5 Feb 2026 13:13:09 -0600 Subject: [PATCH 7/7] =?UTF-8?q?se=20cambi=C3=B3=20el=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/excel/excel.service.ts | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts index d21a1c4..15c84e3 100644 --- a/src/excel/excel.service.ts +++ b/src/excel/excel.service.ts @@ -813,21 +813,23 @@ export class ExcelService { } finally { await queryRunner.release(); - await this.servActivosRepo - .createQueryBuilder() - .update() - .set({ - Red: false, - Prestamos: false, - AT: false, - Correo: false, - RedStatus: 'Inactivo', - PrestamosStatus: 'Inactivo', - ATStatus: 'Inactivo', - CorreoStatus: 'Inactivo', - }) - .where('id_usuario NOT IN (SELECT id_usuario FROM usuario)') - .execute(); + await this.servActivosRepo + .createQueryBuilder() + .update() + .set({ + Red: false, + Prestamos: false, + AT: false, + Correo: false, + RedStatus: 'Inactivo', + PrestamosStatus: 'Inactivo', + ATStatus: 'Inactivo', + CorreoStatus: 'Inactivo', + }) + .where('id_usuario NOT IN (:...ids)', { ids: idsUsuarios }) + .execute(); + + }