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] =?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({