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