Merge branch 'develop'

This commit is contained in:
Emilio-11
2026-02-10 11:56:44 -06:00
5 changed files with 220 additions and 167 deletions
+45 -40
View File
@@ -1,5 +1,14 @@
import { ServActivos } from 'src/usuarios/entities/servActivos.entitie';
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColumn, PrimaryColumn, OneToOne } from 'typeorm';
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
OneToMany,
JoinColumn,
PrimaryColumn,
OneToOne,
} from 'typeorm';
@Entity({ name: 'origen' })
export class Origen {
@@ -9,22 +18,22 @@ export class Origen {
@Column({ type: 'varchar', length: 30 })
origen: string;
@OneToMany(() => Movimiento, movimiento => movimiento.origen)
@OneToMany(() => Movimiento, (movimiento) => movimiento.origen)
movimientos: Movimiento[];
@OneToMany(() => UsuariosDelSistema, uds => uds.origen)
@OneToMany(() => UsuariosDelSistema, (uds) => uds.origen)
usuariosDelSistema: UsuariosDelSistema[];
}
@Entity({ name: 'genero' })
export class Genero {
@PrimaryGeneratedColumn({ name: 'id_genero', type: 'int', })
@PrimaryGeneratedColumn({ name: 'id_genero', type: 'int' })
id_genero: number;
@Column({ type: 'varchar', length: 20, nullable: true })
genero: string;
@OneToMany(() => Usuario, usuario => usuario.genero)
@OneToMany(() => Usuario, (usuario) => usuario.genero)
usuarios: Usuario[];
}
@@ -39,7 +48,7 @@ export class Carrera {
@Column({ type: 'varchar', length: 6 })
clave: string;
@OneToMany(() => CarreraUsuario, cu => cu.carrera)
@OneToMany(() => CarreraUsuario, (cu) => cu.carrera)
carreraUsuarios: CarreraUsuario[];
}
@@ -57,7 +66,7 @@ export class Movimiento {
@Column({
type: 'varchar',
length: 200,
nullable: true, // permite NULL
nullable: true, // permite NULL
})
observaciones?: string;
@@ -67,20 +76,18 @@ export class Movimiento {
@Column({
type: 'varchar',
length: 200,
nullable: true, // permite NULL
nullable: true, // permite NULL
})
reporte?: string;
@OneToMany(() => Usuario, usuario => usuario.movimiento)
@OneToMany(() => Usuario, (usuario) => usuario.movimiento)
usuario: Usuario[];
@ManyToOne(() => Origen, origen => origen.movimientos)
@ManyToOne(() => Origen, (origen) => origen.movimientos)
@JoinColumn({ name: 'id_origen' })
origen: Origen;
}
@Entity({ name: 'usuario' })
export class Usuario {
@PrimaryGeneratedColumn({ name: 'id_usuario', type: 'int' })
@@ -114,43 +121,51 @@ export class Usuario {
})
rfc: string | null;
@Column({ name: 'fecha_nacimiento', type: 'varchar', nullable: true, length: 8 })
@Column({
name: 'fecha_nacimiento',
type: 'varchar',
nullable: true,
length: 8,
})
fecha_nacimiento: string | null;
@Column({ type: 'int', nullable: true, })
@Column({ type: 'int', nullable: true })
generacion: number | null;
@ManyToOne(() => Genero, genero => genero.usuarios, { nullable: true })
@Column({
type: 'bit',
default: () => '1',
})
activo: boolean;
@ManyToOne(() => Genero, (genero) => genero.usuarios, { nullable: true })
@JoinColumn({ name: 'id_genero' })
genero: Genero | null;
@OneToMany(() => CarreraUsuario, cu => cu.usuario)
@OneToMany(() => CarreraUsuario, (cu) => cu.usuario)
carreraUsuarios: CarreraUsuario[];
@OneToMany(() => UsuarioTipoUsuario, utu => utu.usuario)
@OneToMany(() => UsuarioTipoUsuario, (utu) => utu.usuario)
usuarioTipos: UsuarioTipoUsuario[];
@ManyToOne(() => Movimiento, movimiento => movimiento.usuario)
@ManyToOne(() => Movimiento, (movimiento) => movimiento.usuario)
@JoinColumn({ name: 'id_movimiento' })
movimiento: Movimiento;
@OneToOne(() => ServActivos, (servActivo) => servActivo.usuario,)
@OneToOne(() => ServActivos, (servActivo) => servActivo.usuario)
servActivo: ServActivos;
}
@Entity({ name: 'carrera_usuario' })
export class CarreraUsuario {
@PrimaryGeneratedColumn({ name: 'id_carrera_usuario', type: 'int' })
id_carrera_usuario: number;
@ManyToOne(() => Carrera, carrera => carrera.carreraUsuarios)
@ManyToOne(() => Carrera, (carrera) => carrera.carreraUsuarios)
@JoinColumn({ name: 'id_carrera' })
carrera: Carrera;
@ManyToOne(() => Usuario, usuario => usuario.carreraUsuarios)
@ManyToOne(() => Usuario, (usuario) => usuario.carreraUsuarios)
@JoinColumn({ name: 'id_usuario' })
usuario: Usuario;
}
@@ -162,23 +177,21 @@ export class UsuariosDelSistema {
@Column({
type: 'varchar',
length: 100, // ajusta longitud según tu necesidad
length: 100, // ajusta longitud según tu necesidad
unique: true,
nullable: true,
default: null,
})
correo: string | null;
@Column({ type: 'varchar', length: 60, default:null })
@Column({ type: 'varchar', length: 60, default: null })
contraseña: string;
@ManyToOne(() => Origen, origen => origen.usuariosDelSistema)
@ManyToOne(() => Origen, (origen) => origen.usuariosDelSistema)
@JoinColumn({ name: 'id_origen' })
origen: Origen;
}
@Entity({ name: 'equipo' })
export class Equipo {
@PrimaryGeneratedColumn({ name: 'id_equipo', type: 'int' })
@@ -199,30 +212,22 @@ export class TipoUsuario {
@Column({ name: 'tipo_usuario', type: 'varchar', length: 30 })
tipo_usuario: string;
@OneToMany(() => UsuarioTipoUsuario, utu => utu.tipoUsuario)
@OneToMany(() => UsuarioTipoUsuario, (utu) => utu.tipoUsuario)
usuariosTipos: UsuarioTipoUsuario[];
}
@Entity({ name: 'usuario_tipo_usuario' })
export class UsuarioTipoUsuario {
@PrimaryGeneratedColumn({ name: 'id_user_tipo_usuario', type: 'int' })
id_user_tipo_usuario: number;
@ManyToOne(() => TipoUsuario, tipo => tipo.usuariosTipos)
@ManyToOne(() => TipoUsuario, (tipo) => tipo.usuariosTipos)
@JoinColumn({ name: 'id_tipo_usuario' })
tipoUsuario: TipoUsuario;
@ManyToOne(() => Usuario, usuario => usuario.usuarioTipos)
@ManyToOne(() => Usuario, (usuario) => usuario.usuarioTipos)
@JoinColumn({ name: 'id_usuario' })
usuario: Usuario;
}
export { ServActivos };
+1 -1
View File
@@ -25,7 +25,6 @@ import { ExcelService } from './excel.service';
import { MovimientoService } from '../movimiento/movimiento.service';
import { MailService } from 'src/mail/mail.service';
import { CreateUsuarioDto } from 'src/usuarios/dto/create-usuario.dto';
import { Origen } from 'src/entities/entities';
import { UsuariosService } from 'src/usuarios/usuarios.service';
@UseGuards(AuthGuard('jwt'))
@@ -362,6 +361,7 @@ export class ExcelController {
return { message: 'Servicios activados correctamente' };
}
@ExcelDocumentation.servAct()
@Get('serv_act')
async serv() {
return await this.excelService.buscaAct();
+21
View File
@@ -165,4 +165,25 @@ export class ExcelDocumentation {
ApiResponse({ status: 500, description: 'Error al generar descarga' }),
);
}
static servAct() {
return applyDecorators(
ApiTags('Excel'),
ApiOperation({
summary: 'Obtener servicios activos',
description:
'Devuelve la lista de servicios activos registrados en el sistema',
}),
ApiResponse({
status: 200,
description: 'Lista de servicios activos',
// type: ServActivoDto, // si tienes DTO
// isArray: true,
}),
ApiResponse({
status: 401,
description: 'No autorizado',
}),
);
}
}
+149 -126
View File
@@ -1,7 +1,7 @@
// src/excel/excel.service.ts
import { Injectable, BadRequestException, Logger } from '@nestjs/common';
import { Workbook } from 'exceljs';
import { DataSource, In, Repository } from 'typeorm';
import { DataSource, DeepPartial, In, Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import {
CarreraUsuario,
@@ -220,6 +220,8 @@ export class ExcelService {
await queryRunner.connect();
await queryRunner.startTransaction();
const cuentasAReactivar: string[] = [];
try {
const movimiento = await this.movimientoService.logger(
queryRunner,
@@ -235,6 +237,7 @@ export class ExcelService {
const existentes = await queryRunner.manager.find(Usuario, {
where: [{ num_cuenta: In(cuentas) }, { rfc: In(rfcs) }],
select: ['num_cuenta', 'rfc'],
});
@@ -275,7 +278,6 @@ export class ExcelService {
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 usuariosConRow: { usuario: Usuario; row: any }[] = [];
@@ -284,12 +286,21 @@ export class ExcelService {
for (const r of rows) {
if (setCuentas.has(r.cuenta) || (r.rfc && setRfcs.has(r.rfc))) {
cuentasAReactivar.push(r.cuenta);
errs.push(`Cuenta ${r.cuenta} ya existe, se omite.`);
contador++;
continue;
}
if (!r.tipo || !r.nombres || !r.apellidopa || !r.apellidoma) {
if (
!r.tipo ||
!r.nombres ||
!r.apellidopa ||
!r.apellidoma ||
!r.cuenta ||
!r.sexo
) {
errs.push(`Fila con cuenta ${r.cuenta} tiene campos faltantes.`);
contador++;
continue;
@@ -303,18 +314,33 @@ export class ExcelService {
}
// Carrera
const clave = r.clave?.trim().toUpperCase() || null;
const nombre = r.nomCarr?.trim().slice(0, 100).toUpperCase() || null;
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) {
// 1. Buscar por clave (prioridad)
if (clave && mapCarreraClave.has(clave)) {
carrera = mapCarreraClave.get(clave);
// 2. Buscar por nombre
} else if (nombre && mapCarreraNom.has(nombre)) {
carrera = mapCarreraNom.get(nombre);
// 3. Crear nueva solo si hay datos válidos
} else if (clave || nombre) {
carrera = queryRunner.manager.create(Carrera, {
clave: r.clave ?? '',
carrera: r.nomCarr?.slice(0, 100) ?? '',
});
mapCarreraClave.set(carrera.clave, carrera);
mapCarreraNom.set(carrera.carrera, carrera);
clave,
carrera: nombre,
} as DeepPartial<Carrera>);
if (clave) {
mapCarreraClave.set(clave, carrera);
}
if (nombre) {
mapCarreraNom.set(nombre, carrera);
}
}
// Tipo
@@ -467,6 +493,13 @@ export class ExcelService {
await queryRunner.rollbackTransaction();
throw err;
} finally {
if (cuentasAReactivar.length > 0) {
await queryRunner.manager.update(
Usuario,
{ num_cuenta: In(cuentasAReactivar) },
{ activo: true },
);
}
await queryRunner.release();
}
}
@@ -547,14 +580,12 @@ export class ExcelService {
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;
const usuariosConRow: { usuario: Usuario; row: any }[] = [];
for (const r of rows) {
if (setCuentas.has(r.cuenta) || (r.rfc && setRfcs.has(r.rfc))) {
const use = existentes.find(
@@ -579,18 +610,33 @@ export class ExcelService {
}
// Carrera
const clave = r.clave?.trim().toUpperCase() || null;
const nombre = r.nomCarr?.trim().slice(0, 100).toUpperCase() || null;
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) {
// 1. Buscar por clave (prioridad)
if (clave && mapCarreraClave.has(clave)) {
carrera = mapCarreraClave.get(clave);
// 2. Buscar por nombre
} else if (nombre && mapCarreraNom.has(nombre)) {
carrera = mapCarreraNom.get(nombre);
// 3. Crear nueva solo si hay datos válidos
} else if (clave || nombre) {
carrera = queryRunner.manager.create(Carrera, {
clave: r.clave ?? '',
carrera: r.nomCarr?.slice(0, 100) ?? '',
});
mapCarreraClave.set(carrera.clave, carrera);
mapCarreraNom.set(carrera.carrera, carrera);
clave,
carrera: nombre,
} as DeepPartial<Carrera>);
if (clave) {
mapCarreraClave.set(clave, carrera);
}
if (nombre) {
mapCarreraNom.set(nombre, carrera);
}
}
// Tipo
@@ -619,50 +665,86 @@ export class ExcelService {
});
usuarios.push(usuario);
usuariosConRow.push({ usuario, row: r });
}
await queryRunner.manager.save([...mapGenero.values()]);
await queryRunner.manager.save([...mapCarreraClave.values()]);
await queryRunner.manager.save([...mapTipo.values()]);
await queryRunner.manager.save(usuarios);
idsUsuarios = usuarios.map((u) => u.id_usuario);
idsUsuarios.push(...existentes.map((e) => e.id_usuario));
const carrerasUsuarioFinal: CarreraUsuario[] = [];
for (const { usuario, row } of usuariosConRow) {
const carrera =
(row.clave && mapCarreraClave.get(row.clave)) ||
(row.nomCarr && mapCarreraNom.get(row.nomCarr));
if (carrera) {
const carreraUsuario = queryRunner.manager.create(CarreraUsuario, {
usuario,
carrera,
});
carrerasUsuario.push(carreraUsuario);
carrerasUsuarioFinal.push(
queryRunner.manager.create(CarreraUsuario, {
usuario,
carrera,
}),
);
}
}
usuariosTipos.push(
await queryRunner.manager.save(carrerasUsuarioFinal);
const usuariosTiposFinal: UsuarioTipoUsuario[] = [];
for (const { usuario, row } of usuariosConRow) {
const tipo = mapTipo.get(row.tipo.trim());
if (!tipo) continue;
usuariosTiposFinal.push(
queryRunner.manager.create(UsuarioTipoUsuario, {
usuario,
tipoUsuario: tipo,
}),
);
}
// Servicios activos
const servData: Partial<ServActivos> = {
await queryRunner.manager.save(usuariosTiposFinal);
const serviciosActivosFinal: ServActivos[] = [];
for (const { usuario, row } of usuariosConRow) {
const serv = queryRunner.manager.create(ServActivos, {
usuario,
Red: false,
AT: false,
Correo: false,
Prestamos: false,
RedStatus: 'Inactivo',
ATStatus: 'Inactivo',
CorreoStatus: 'Inactivo',
PrestamosStatus: 'Inactivo',
};
});
switch (r.tipo.trim()) {
switch (row.tipo.trim()) {
case 'Diplomado':
Object.assign(servData, { Correo: true });
Object.assign(serv, { Correo: true });
conteoTiposCorreo++;
break;
case 'Extra Largo':
Object.assign(servData, { Correo: true, Prestamos: true });
Object.assign(serv, { Correo: true, Prestamos: true });
conteoTiposCorreo++;
conteoTiposSolicita++;
break;
case 'Servicio Social':
Object.assign(servData, { Correo: true, AT: true, Red: true });
Object.assign(serv, { Correo: true, AT: true, Red: true });
conteoTiposCorreo++;
conteoTiposAt++;
conteoTiposRed++;
break;
case 'Idiomas R (UNAM)':
case 'Idiomas Sabatino':
Object.assign(servData, { Correo: true, Red: true });
Object.assign(serv, { Correo: true, Red: true });
conteoTiposCorreo++;
conteoTiposRed++;
break;
@@ -673,7 +755,7 @@ export class ExcelService {
case 'Ampliación de Conocimiento':
case 'Licenciatura':
case 'Profesor':
Object.assign(servData, {
Object.assign(serv, {
Correo: true,
AT: true,
Red: true,
@@ -685,79 +767,16 @@ export class ExcelService {
conteoTiposSolicita++;
break;
case 'Trabajadores':
Object.assign(servData, { Red: true });
Object.assign(serv, { Red: true });
conteoTiposRed++;
break;
}
serviciosActivos.push(
queryRunner.manager.create(ServActivos, servData),
);
serviciosActivosFinal.push(serv);
}
// 🔹 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 queryRunner.commitTransaction();
return {
inserted: rows.length - contador,
@@ -772,20 +791,19 @@ export class ExcelService {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.manager.update(
Usuario,
{ id_usuario: In(idsUsuarios) },
{ activo: true },
);
await queryRunner.release();
await this.servActivosRepo
await this.usuarioRepo
.createQueryBuilder()
.update()
.set({
Red: false,
Prestamos: false,
AT: false,
Correo: false,
RedStatus: 'Inactivo',
PrestamosStatus: 'Inactivo',
ATStatus: 'Inactivo',
CorreoStatus: 'Inactivo',
activo: false,
})
.where('id_usuario NOT IN (:...ids)', { ids: idsUsuarios })
.execute();
@@ -837,6 +855,7 @@ export class ExcelService {
const usuarios = await this.usuarioRepo.find({
where: {
activo: true,
usuarioTipos: {
tipoUsuario: {
tipo_usuario: In(tipos),
@@ -911,6 +930,7 @@ export class ExcelService {
const usuarios = await this.usuarioRepo.find({
where: {
activo: true,
usuarioTipos: {
tipoUsuario: {
tipo_usuario: In(tipos),
@@ -985,6 +1005,7 @@ export class ExcelService {
const usuarios = await this.usuarioRepo.find({
where: {
activo: true,
usuarioTipos: {
tipoUsuario: {
tipo_usuario: In(tipos),
@@ -1037,6 +1058,7 @@ export class ExcelService {
const usuarios = await this.usuarioRepo.find({
where: {
activo: true,
usuarioTipos: {
tipoUsuario: {
tipo_usuario: In(tipos),
@@ -1103,7 +1125,10 @@ export class ExcelService {
}
const usuarios = await this.usuarioRepo.find({
where: { movimiento: { id_mov: id_movimiento } },
where: {
activo: true,
movimiento: { id_mov: id_movimiento },
},
});
if (!usuarios || usuarios.length === 0) {
@@ -1195,13 +1220,11 @@ export class ExcelService {
}
async buscaAct() {
return await this.servActivosRepo.find({
where: [
{ AT: true },
{ Correo: true },
{ Prestamos: true },
{ Red: true },
],
});
const count = await this.usuarioRepo
.createQueryBuilder('sa')
.where('sa.activo = :activo', { activo: true })
.getCount();
return count;
}
}
+4
View File
@@ -181,6 +181,7 @@ export class MovimientoService {
reporte: 'CARGA MASIVA DE USUARIOS',
status: 'SUCCESS',
usuario: {
activo: true,
servActivo: {
[origenField]: true,
@@ -195,6 +196,7 @@ export class MovimientoService {
reporte: 'CARGA MASIVA WEB SERVICE',
status: 'SUCCESS',
usuario: {
activo: true,
servActivo: {
[origenField]: true,
[field]: 'Inactivo',
@@ -207,6 +209,7 @@ export class MovimientoService {
reporte: 'CARGA INDIVIDUAL DE USUARIOS',
status: 'SUCCESS',
usuario: {
activo: true,
servActivo: {
[origenField]: true,
[field]: 'Inactivo',
@@ -245,6 +248,7 @@ export class MovimientoService {
const usuarios = await this.usuarioRepo.find({
where: {
activo: true,
movimiento: {
reporte: 'CARGA INDIVIDUAL DE USUARIOS',
},