erp-construccion-backend-v2/src/modules/hse/controllers/indicador.controller.ts
Adrian Flores Cortes c8a01c5f14 fix(hr): Fix PaginatedResult format in employee and puesto services
- Remove meta wrapper from PaginatedResult returns in findWithFilters and findAll
- Return flat format with total, page, limit, totalPages at top level
- Align with base.service.ts interface definition
- Controllers already updated to access flat pagination structure
2026-01-25 14:52:53 -06:00

377 lines
12 KiB
TypeScript

/**
* IndicadorController - Controller de indicadores HSE
*
* Endpoints REST para gestión de indicadores y reportes HSE.
*
* @module HSE
*/
import { Router, Request, Response, NextFunction } from 'express';
import { DataSource } from 'typeorm';
import {
IndicadorService,
CreateIndicadorDto,
IndicadorFilters,
} from '../services/indicador.service';
import { AuthMiddleware } from '../../auth/middleware/auth.middleware';
import { AuthService } from '../../auth/services/auth.service';
import { IndicadorConfig } from '../entities/indicador-config.entity';
import { IndicadorMetaObra } from '../entities/indicador-meta-obra.entity';
import { IndicadorValor } from '../entities/indicador-valor.entity';
import { HorasTrabajadas } from '../entities/horas-trabajadas.entity';
import { DiasSinAccidente } from '../entities/dias-sin-accidente.entity';
import { ReporteProgramado } from '../entities/reporte-programado.entity';
import { AlertaIndicador } from '../entities/alerta-indicador.entity';
import { User } from '../../core/entities/user.entity';
import { Tenant } from '../../core/entities/tenant.entity';
import { RefreshToken } from '../../auth/entities/refresh-token.entity';
/**
* Service context for multi-tenant operations
*/
interface ServiceContext {
tenantId: string;
userId?: string;
}
/**
* Crear router de indicadores
*/
export function createIndicadorController(dataSource: DataSource): Router {
const router = Router();
// Repositorios
const indicadorRepository = dataSource.getRepository(IndicadorConfig);
const metaRepository = dataSource.getRepository(IndicadorMetaObra);
const valorRepository = dataSource.getRepository(IndicadorValor);
const horasRepository = dataSource.getRepository(HorasTrabajadas);
const diasRepository = dataSource.getRepository(DiasSinAccidente);
const reporteRepository = dataSource.getRepository(ReporteProgramado);
const alertaRepository = dataSource.getRepository(AlertaIndicador);
const userRepository = dataSource.getRepository(User);
const tenantRepository = dataSource.getRepository(Tenant);
const refreshTokenRepository = dataSource.getRepository(RefreshToken);
// Servicios
const indicadorService = new IndicadorService(
indicadorRepository,
metaRepository,
valorRepository,
horasRepository,
diasRepository,
reporteRepository,
alertaRepository
);
const authService = new AuthService(userRepository, tenantRepository, refreshTokenRepository as any);
const authMiddleware = new AuthMiddleware(authService, dataSource);
// Helper para crear contexto de servicio
const getContext = (req: Request): ServiceContext => {
if (!req.tenantId) {
throw new Error('Tenant ID is required');
}
return {
tenantId: req.tenantId,
userId: req.user?.sub,
};
};
// ========== Configuración de Indicadores ==========
/**
* GET /indicadores
* Listar indicadores con filtros
*/
router.get('/', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const tenantId = req.tenantId;
if (!tenantId) {
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
return;
}
const page = parseInt(req.query.page as string) || 1;
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
const filters: IndicadorFilters = {
tipo: req.query.tipo as any,
activo: req.query.activo === 'true' ? true : req.query.activo === 'false' ? false : undefined,
search: req.query.search as string,
};
const result = await indicadorService.findIndicadores(getContext(req), filters, page, limit);
res.status(200).json({
success: true,
data: result.data,
pagination: {
total: result.total,
page: result.page,
limit: result.limit,
totalPages: result.totalPages,
},
});
} catch (error) {
next(error);
}
});
/**
* GET /indicadores/stats
* Obtener estadísticas de indicadores
*/
router.get('/stats', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const tenantId = req.tenantId;
if (!tenantId) {
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
return;
}
const stats = await indicadorService.getStats(getContext(req));
res.status(200).json({ success: true, data: stats });
} catch (error) {
next(error);
}
});
/**
* GET /indicadores/:id
* Obtener indicador por ID
*/
router.get('/:id', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const indicador = await indicadorService.findIndicadorById(getContext(req), req.params.id);
if (!indicador) {
res.status(404).json({ error: 'Not Found', message: 'Indicator not found' });
return;
}
res.status(200).json({ success: true, data: indicador });
} catch (error) {
next(error);
}
});
/**
* POST /indicadores
* Crear indicador
*/
router.post('/', authMiddleware.authenticate, authMiddleware.authorize('admin'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const tenantId = req.tenantId;
if (!tenantId) {
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
return;
}
const dto: CreateIndicadorDto = req.body;
if (!dto.codigo || !dto.nombre || !dto.tipo) {
res.status(400).json({
error: 'Bad Request',
message: 'codigo, nombre and tipo are required',
});
return;
}
const indicador = await indicadorService.createIndicador(getContext(req), dto);
res.status(201).json({ success: true, data: indicador });
} catch (error) {
next(error);
}
});
/**
* PATCH /indicadores/:id
* Actualizar indicador
*/
router.patch('/:id', authMiddleware.authenticate, authMiddleware.authorize('admin'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const dto: Partial<CreateIndicadorDto> = req.body;
const indicador = await indicadorService.updateIndicador(getContext(req), req.params.id, dto);
if (!indicador) {
res.status(404).json({ error: 'Not Found', message: 'Indicator not found' });
return;
}
res.status(200).json({ success: true, data: indicador });
} catch (error) {
next(error);
}
});
/**
* POST /indicadores/:id/toggle
* Activar/desactivar indicador
*/
router.post('/:id/toggle', authMiddleware.authenticate, authMiddleware.authorize('admin'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const indicador = await indicadorService.toggleIndicadorActivo(getContext(req), req.params.id);
if (!indicador) {
res.status(404).json({ error: 'Not Found', message: 'Indicator not found' });
return;
}
res.status(200).json({
success: true,
data: indicador,
message: indicador.activo ? 'Indicator activated' : 'Indicator deactivated',
});
} catch (error) {
next(error);
}
});
// ========== Metas por Obra ==========
/**
* GET /indicadores/metas
* Listar metas por obra
*/
router.get('/metas/list', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const fraccionamientoId = req.query.fraccionamientoId as string;
const indicadorId = req.query.indicadorId as string;
const metas = await indicadorService.findMetas(getContext(req), fraccionamientoId, indicadorId);
res.status(200).json({ success: true, data: metas });
} catch (error) {
next(error);
}
});
// ========== Valores de Indicadores ==========
/**
* GET /indicadores/:id/valores
* Listar valores de un indicador
*/
router.get('/:id/valores', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const tenantId = req.tenantId;
if (!tenantId) {
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
return;
}
const page = parseInt(req.query.page as string) || 1;
const limit = Math.min(parseInt(req.query.limit as string) || 50, 200);
const fraccionamientoId = req.query.fraccionamientoId as string;
const result = await indicadorService.findValores(
getContext(req),
req.params.id,
fraccionamientoId,
page,
limit
);
res.status(200).json({
success: true,
data: result.data,
pagination: {
total: result.total,
page: result.page,
limit: result.limit,
totalPages: result.totalPages,
},
});
} catch (error) {
next(error);
}
});
// ========== Horas Trabajadas ==========
/**
* GET /indicadores/horas-trabajadas/:fraccionamientoId
* Obtener horas trabajadas por obra
*/
router.get('/horas-trabajadas/:fraccionamientoId', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const year = req.query.year ? parseInt(req.query.year as string) : undefined;
const horas = await indicadorService.findHorasTrabajadas(
getContext(req),
req.params.fraccionamientoId,
year
);
res.status(200).json({ success: true, data: horas });
} catch (error) {
next(error);
}
});
// ========== Días Sin Accidente ==========
/**
* GET /indicadores/dias-sin-accidente/:fraccionamientoId
* Obtener días sin accidente por obra
*/
router.get('/dias-sin-accidente/:fraccionamientoId', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const dias = await indicadorService.findDiasSinAccidente(
getContext(req),
req.params.fraccionamientoId
);
res.status(200).json({ success: true, data: dias });
} catch (error) {
next(error);
}
});
// ========== Reportes Programados ==========
/**
* GET /indicadores/reportes
* Listar reportes programados
*/
router.get('/reportes/list', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const reportes = await indicadorService.findReportes(getContext(req));
res.status(200).json({ success: true, data: reportes });
} catch (error) {
next(error);
}
});
// ========== Alertas ==========
/**
* GET /indicadores/alertas
* Listar alertas de indicadores
*/
router.get('/alertas/list', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const tenantId = req.tenantId;
if (!tenantId) {
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
return;
}
const page = parseInt(req.query.page as string) || 1;
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
const fraccionamientoId = req.query.fraccionamientoId as string;
const result = await indicadorService.findAlertas(getContext(req), fraccionamientoId, page, limit);
res.status(200).json({
success: true,
data: result.data,
pagination: {
total: result.total,
page: result.page,
limit: result.limit,
totalPages: result.totalPages,
},
});
} catch (error) {
next(error);
}
});
return router;
}
export default createIndicadorController;