25 Commits

Author SHA1 Message Date
IO 07c1e1651a now can calculate sancion 2026-05-18 14:55:34 -05:00
IO 61fb91c7ec modified conections 2026-04-22 14:42:18 -05:00
IO 46ad8e88ce modified connection 2026-04-22 13:54:54 -05:00
IO 94641514ad delete guard 2026-04-16 16:25:07 -05:00
IO f334a0f9e2 added timeout 2026-04-14 19:35:21 -05:00
IO ac009d0688 added package 2026-04-14 19:21:02 -05:00
IO 8b6186ff6e delete spaces 2026-04-13 12:11:14 -05:00
IO 0d9e82f527 Merge branch 'Axel' of repositorio.acatlan.unam.mx:IO/api-AT 2026-04-06 14:42:00 -05:00
IO 65bdb47c00 Merge branch 'Axel' of repositorio.acatlan.unam.mx:IO/api-AT 2026-03-26 19:06:07 -05:00
IO 73e0ede7ed package 2026-03-25 20:48:08 -05:00
IO 645c4ed6c1 fixed modified machines' programs and search programs 2026-03-20 17:51:12 -05:00
IO 4bd78578a7 pool limited 2026-03-19 15:56:59 -05:00
IO 5f5295dcb4 fixed format 2026-03-18 18:28:23 -05:00
IO 70e1e2d7bf fixed orderBy and added roles 2026-03-17 12:07:26 -05:00
IO 2071be5a65 Merge branch 'Axel' of repositorio.acatlan.unam.mx:IO/api-AT into Lino 2026-03-10 17:52:14 -05:00
IO 0cf4fc5d3c fixed package 2026-03-10 16:54:33 -05:00
IO 9d07eab337 Merge branch 'master' of repositorio.acatlan.unam.mx:IO/api-AT into Lino 2026-03-10 16:34:49 -05:00
IO af2052111c created active-directory 2026-03-10 16:32:50 -05:00
frcarlos 3bb95a3d50 @Patch idactivo 2026-03-05 14:41:19 -05:00
IO e28d534edf new ep usoWhitout 2026-03-04 18:19:36 -06:00
frcarlos 81ad881d6a merge 2026-03-04 14:20:47 -05:00
IO 54d878c145 now the component login dont give access to inactive 2026-03-04 11:16:14 -06:00
IO 04290f4097 merge axel 2026-03-03 17:41:06 -06:00
IO 22431dd73e Merge branch 'Axel' of repositorio.acatlan.unam.mx:IO/api-AT 2026-03-03 17:37:43 -06:00
IO 9ddff920fb npm audit fix 2026-03-03 17:37:36 -06:00
22 changed files with 697 additions and 593 deletions
+347 -557
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -30,6 +30,7 @@
"@nestjs/typeorm": "^11.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"ldapts": "^8.1.7",
"mysql2": "^3.14.4",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ActiveDirectoryController } from './active-directory.controller';
import { ActiveDirectoryService } from './active-directory.service';
describe('ActiveDirectoryController', () => {
let controller: ActiveDirectoryController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ActiveDirectoryController],
providers: [ActiveDirectoryService],
}).compile();
controller = module.get<ActiveDirectoryController>(ActiveDirectoryController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
@@ -0,0 +1,28 @@
import { Controller, Post, Body, Param } from '@nestjs/common';
import { ActiveDirectoryService } from './active-directory.service';
@Controller('active-directory')
export class ActiveDirectoryController {
constructor(private readonly adService: ActiveDirectoryService) {}
@Post('crear/:cuenta')
async crearUsuario(@Param('cuenta') cuenta: string) {
await this.adService.createUser(cuenta);
return { message: `Usuario ${cuenta} creado en AD` };
}
@Post('reset/:cuenta')
async resetPassword(@Param('cuenta') cuenta: string) {
await this.adService.resetPassword(cuenta);
return { message: `Contraseña de ${cuenta} reseteada` };
}
@Post('cambiar-password/:cuenta')
async cambiarPassword(
@Param('cuenta') cuenta: string,
@Body('password') password?: string,
) {
await this.adService.changePassword(cuenta, password);
return { message: `Contraseña de ${cuenta} actualizada` };
}
}
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ActiveDirectoryService } from './active-directory.service';
import { ActiveDirectoryController } from './active-directory.controller';
@Module({
imports: [ConfigModule],
providers: [ActiveDirectoryService],
controllers: [ActiveDirectoryController],
exports: [ActiveDirectoryService],
})
export class ActiveDirectoryModule {}
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ActiveDirectoryService } from './active-directory.service';
describe('ActiveDirectoryService', () => {
let service: ActiveDirectoryService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ActiveDirectoryService],
}).compile();
service = module.get<ActiveDirectoryService>(ActiveDirectoryService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
@@ -0,0 +1,124 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Client, Attribute, Change } from 'ldapts';
@Injectable()
export class ActiveDirectoryService {
private readonly logger = new Logger(ActiveDirectoryService.name);
constructor(private configService: ConfigService) { }
private createClient(): Client {
return new Client({
url: this.configService.get<string>('AD_URL')!,
tlsOptions: { rejectUnauthorized: false },
});
}
async createUser(cuenta: string): Promise<void> {
const client = this.createClient();
const bindDN = this.configService.get<string>('AD_BIND_DN')!;
const bindPass = this.configService.get<string>('AD_BIND_PASS')!;
const baseDN = this.configService.get<string>('AD_BASE_DN')!;
try {
await client.bind(bindDN, bindPass);
const userDN = `CN=${cuenta},${baseDN}`;
await client.add(userDN, {
objectClass: ['top', 'person', 'organizationalPerson', 'user'],
sAMAccountName: cuenta,
cn: cuenta,
description: 'Creado desde el Sitio',
userAccountControl: '32',
});
const encodedPassword = Buffer.from(`"${cuenta}"`, 'utf16le');
await client.modify(userDN, [
new Change({
operation: 'replace',
modification: new Attribute({ type: 'unicodePwd', values: [encodedPassword] }),
}),
]);
await client.modify(userDN, [
new Change({
operation: 'replace',
modification: new Attribute({ type: 'pwdLastSet', values: ['0'] }),
}),
]);
await client.modify(userDN, [
new Change({
operation: 'replace',
modification: new Attribute({ type: 'userAccountControl', values: ['512'] }),
}),
]);
this.logger.log(`Usuario ${cuenta} creado exitosamente en AD`);
} catch (error) {
this.logger.error(`Error al crear usuario ${cuenta} en AD`, error);
throw error;
} finally {
await client.unbind();
}
}
async resetPassword(cuenta: string): Promise<void> {
const client = this.createClient();
const bindDN = this.configService.get<string>('AD_BIND_DN')!;
const bindPass = this.configService.get<string>('AD_BIND_PASS')!;
const baseDN = this.configService.get<string>('AD_BASE_DN')!;
try {
await client.bind(bindDN, bindPass);
const userDN = `CN=${cuenta},${baseDN}`;
await client.del(userDN);
this.logger.log(`Usuario ${cuenta} eliminado de AD`);
} catch (error) {
this.logger.warn(`No se pudo eliminar ${cuenta} de AD (puede no existir)`, error);
} finally {
await client.unbind();
}
await this.createUser(cuenta);
}
async changePassword(cuenta: string, newPass?: string): Promise<void> {
const client = this.createClient();
const bindDN = this.configService.get<string>('AD_BIND_DN')!;
const bindPass = this.configService.get<string>('AD_BIND_PASS')!;
const baseDN = this.configService.get<string>('AD_BASE_DN')!;
const password = newPass ?? cuenta;
try {
await client.bind(bindDN, bindPass);
const userDN = `CN=${cuenta},${baseDN}`;
const encodedPassword = Buffer.from(`"${password}"`, 'utf16le');
await client.modify(userDN, [
new Change({
operation: 'replace',
modification: new Attribute({ type: 'unicodePwd', values: [encodedPassword] }),
}),
]);
await client.modify(userDN, [
new Change({
operation: 'replace',
modification: new Attribute({ type: 'pwdLastSet', values: ['0'] }),
}),
]);
this.logger.log(`Contraseña de ${cuenta} actualizada`);
} catch (error) {
this.logger.error(`Error al cambiar contraseña de ${cuenta}`, error);
throw error;
} finally {
await client.unbind();
}
}
}
@@ -0,0 +1 @@
export class CreateActiveDirectoryDto {}
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateActiveDirectoryDto } from './create-active-directory.dto';
export class UpdateActiveDirectoryDto extends PartialType(CreateActiveDirectoryDto) {}
@@ -0,0 +1 @@
export class ActiveDirectory {}
+1 -2
View File
@@ -32,8 +32,7 @@ export class AlumnoController {
return this.alumnoService.findOneWhitSancion(+id);
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR,Role.ATENCION_USUARIOS,Role.SERVICIO_SOCIAL,Role.PCPUMA)
//delete guard to use in cedetec
@Get('sancion/:id')
findOne(@Param('id') id: number) {
return this.alumnoService.findOne(+id);
+18 -1
View File
@@ -54,9 +54,26 @@ export class AlumnoSancionService {
const alusancion = await this.alumnosancionRepository.find({
where: { alumno: { id_cuenta } },
relations: ['sancion'],
});
return { student, alusancion };
const now = new Date();
const sancionesActivas = alusancion.filter((item) => {
const fechaInicio = new Date(item.fecha_inicio);
const fechaFin = new Date(fechaInicio);
fechaFin.setDate(
fechaFin.getDate() + item.sancion.duracion * 7,
);
return fechaFin >= now;
});
return {
student,
alusancion: sancionesActivas,
};
}
async removeByStudent(id_cuenta: number): Promise<{ message: string }> {
+10 -1
View File
@@ -43,6 +43,7 @@ import { BitacoraModule } from './bitacora/bitacora.module';
import { Bitacora } from './bitacora/entities/bitacora.entity';
import { MensajeModule } from './mensaje/mensaje.module';
import { Mensaje } from './mensaje/entities/mensaje.entity';
import { ActiveDirectoryModule } from './active-directory/active-directory.module';
import { CostoModule } from './costo/costo.module';
import { Costo } from './costo/entities/costo.entity';
@@ -85,6 +86,13 @@ import { Costo } from './costo/entities/costo.entity';
Costo,
],
synchronize: false, //Never change to true in production!
extra: {
connectionLimit: 5,
waitForConnections: true,
idleTimeout: 60000,
connectTimeout: 10000,
},
}),
}),
UserModule,
@@ -107,9 +115,10 @@ import { Costo } from './costo/entities/costo.entity';
BitacoraModule,
MensajeModule,
CostoModule,
ActiveDirectoryModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
export class AppModule { }
//IO
@@ -25,16 +25,17 @@ export class DetalleServicioController {
return this.detalleServicioService.findByDateRange(data.desde, data.hasta);
}
@Get('totales-por-periodo')
async getTotalesPorPeriodo(
@Query('periodoInicio') periodoInicio?: string,
@Query('periodoFin') periodoFin?: string,
@Query('servicioId') servicioId?: string,
) {
const inicio = periodoInicio ? parseInt(periodoInicio) : undefined;
const fin = periodoFin ? parseInt(periodoFin) : undefined;
const servicio = servicioId ? parseInt(servicioId) : undefined;
return this.detalleServicioService.getTotalesPorServicioYPeriodo(inicio, fin, servicio);
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR)
@Get('totales-por-periodo')
async getTotalesPorPeriodo(
@Query('periodoInicio') periodoInicio?: string,
@Query('periodoFin') periodoFin?: string,
@Query('servicioId') servicioId?: string,
) {
const inicio = periodoInicio ? parseInt(periodoInicio) : undefined;
const fin = periodoFin ? parseInt(periodoFin) : undefined;
const servicio = servicioId ? parseInt(servicioId) : undefined;
return this.detalleServicioService.getTotalesPorServicioYPeriodo(inicio, fin, servicio);
}
}
+2 -2
View File
@@ -51,7 +51,7 @@ export class EquipoService {
.innerJoin('equipo.plataforma', 'p')
.innerJoin('equipo.areaUbicacion', 'a')
.where('equipo.activo = 1')
.orderBy('equipo.ubicacion', 'ASC')
.orderBy('equipo.ubicacion + 0', 'ASC')
.getRawMany();
}
@@ -108,7 +108,7 @@ export class EquipoService {
return `equipo.id_equipo NOT IN ${subQuery}`;
})
.orderBy('equipo.ubicacion', 'ASC')
.orderBy('equipo.ubicacion + 0', 'ASC')
.getMany();
+5 -4
View File
@@ -10,10 +10,11 @@ async function bootstrap() {
app.useGlobalPipes(new ValidationPipe());
app.enableCors(
// {
// origin: configService.get<string>('Front_URL'),
// }
);
// {
// origin: configService.get<string>('Front_URL'),
// }
);
app.enableShutdownHooks();
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
+12 -5
View File
@@ -11,26 +11,33 @@ export class MesaController {
constructor(private readonly mesaService: MesaService) { }
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR,Role.ATENCION_USUARIOS,Role.SERVICIO_SOCIAL)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR, Role.ATENCION_USUARIOS, Role.SERVICIO_SOCIAL)
@Get()
async findAll() {
return this.mesaService.findAll();
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR,Role.ATENCION_USUARIOS,Role.SERVICIO_SOCIAL)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR, Role.ATENCION_USUARIOS, Role.SERVICIO_SOCIAL)
@Get('activo')
async findAllActivo() {
return this.mesaService.findAllActivo();
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR,Role.ATENCION_USUARIOS,Role.SERVICIO_SOCIAL)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR, Role.ATENCION_USUARIOS, Role.SERVICIO_SOCIAL)
@Get('uso')
async findActiveMesas() {
return this.mesaService.findActiveMesas();
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR, Role.ATENCION_USUARIOS, Role.SERVICIO_SOCIAL)
@Get('usoWhitout')
async findActiveMesasWhitoudOcupado() {
return this.mesaService.findActiveMesasWhitoudOcupado();
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR)
@Patch(':id')
@@ -42,7 +49,7 @@ export class MesaController {
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR,Role.ATENCION_USUARIOS,Role.SERVICIO_SOCIAL)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR, Role.ATENCION_USUARIOS, Role.SERVICIO_SOCIAL)
@Patch(':id/activo')
toggleActivo(@Param('id') id: number) {
return this.mesaService.toggleActivo(+id);
+23
View File
@@ -142,4 +142,27 @@ export class MesaService {
.orderBy('mesa.id_mesa', 'ASC')
.getRawMany();
}
findActiveMesasWhitoudOcupado() {
return this.mesaRepository
.createQueryBuilder('mesa')
.select([
'mesa.id_mesa AS id_mesa',
])
.where('mesa.activo = 1')
.andWhere(`
NOT EXISTS (
SELECT 1
FROM bitacora_mesa bm
WHERE bm.id_mesa = mesa.id_mesa
AND TIMESTAMPDIFF(
SECOND,
NOW(),
DATE_ADD(bm.tiempo_entrada, INTERVAL bm.tiempo_asignado MINUTE)
) > 0
)
`)
.orderBy('mesa.id_mesa', 'ASC')
.getRawMany();
}
}
@@ -13,13 +13,12 @@ export class ProgramaEquipoController {
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR)
@Get('programas/:id')
findAllProgramByNumber(@Param('id') id: number) {
return this.programaEquipoService.findAllProgramByNumber(+id);
async findAllProgramByNumber(@Param('id') id: number) {
return await this.programaEquipoService.findAllProgramByNumber(+id);
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR)
@Get('ubicacion/:id')
findAllProgramByUbicacion(@Param('id') id: string) {
return this.programaEquipoService.findAllProgramByUbication(id);
@@ -28,7 +27,7 @@ export class ProgramaEquipoController {
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR)
@Get(':id')
findOne(@Param('id') id: string) {
findOne(@Param('id') id: number) {
return this.programaEquipoService.findOne(id);
}
@@ -21,9 +21,9 @@ export class ProgramaEquipoService {
return this.programaEquipoRepository.find();
}
async findOne(ubicacion: string) {
async findOne(id_equipo: number) {
const equipo = await this.programaEquipoRepository.find({
where: { equipo: { ubicacion } },
where: { equipo: { id_equipo } },
});
if (equipo.length === 0) {
+17 -1
View File
@@ -6,6 +6,8 @@ import {
UseGuards,
Res,
Req,
Param,
Patch,
} from '@nestjs/common';
import type { Response } from 'express';
import { UserService } from './user.service';
@@ -19,6 +21,13 @@ import { Role } from 'src/role.enum';
export class UserController {
constructor(private readonly userService: UserService) { }
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR)
@Get('/switch/:id')
async getactive(@Param('id') id: number) {
return this.userService.getactive(id);
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR)
@Get()
@@ -28,11 +37,18 @@ export class UserController {
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR)
@Get('/perfil')
@Get('/perfil')
async getAllPerfil() {
return this.userService.getAllPerfil();
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMINISTRADOR, Role.SUPER_ADMINISTRADOR)
@Patch(':id/activo')
toggleActivo(@Param('id') id: number) {
return this.userService.toggleActivo(id);
}
@Post()
async Login(@Body() data: Login, @Res() res: Response) {
return this.userService.Login(data, res);
+35 -2
View File
@@ -19,7 +19,36 @@ export class UserService {
@InjectRepository(Perfil)
private perfilRepository: Repository<Perfil>,
private jwtService: JwtService,
) {}
) { }
async toggleActivo(id: number) {
const usuario = await this.userRepository.findOne({
where: { id_usuario: id }
});
if (!usuario) {
throw new NotFoundException("no se encontro usuario")
}
usuario.activo = usuario.activo === 1 ? 0 : 1;
return this.userRepository.save(usuario);
}
async getactive(id: number) {
const user = await this.userRepository.findOne({
where: { id_usuario: id },
});
if (!user) {
throw new NotFoundException('Usuario no encontrado');
}
user.activo = user.activo === 1 ? 0 : 1;
return this.userRepository.save(user);
}
async findOneByNameandPassword(data: Login): Promise<User> {
const { usuario, password } = data;
@@ -28,6 +57,10 @@ export class UserService {
where: { usuario, password },
});
if (user?.activo === 0) {
throw new NotFoundException(`El usuario se encuentra desactivado`);
}
if (!user) {
throw new NotFoundException(`El usuario o la contraseña es incorrecta`);
}
@@ -111,7 +144,7 @@ export class UserService {
}
async getAll() {
return this.userRepository.find();
return this.userRepository.find({select:['id_usuario','nombre','usuario','perfil','activo','password']});
}
async getAllPerfil() {