feat: Add controllers for gestion-flota, ordenes-transporte, tracking and viajes modules

- Added operadores.controller.ts for operator management
- Added unidades.controller.ts for fleet unit management
- Added ordenes-transporte.controller.ts for transport orders
- Added tracking.controller.ts for GPS tracking
- Added viajes controllers directory
- Updated index.ts exports for each module

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-01-25 14:30:49 -06:00
parent 23ee6ce90e
commit 6f0324f21a
9 changed files with 1574 additions and 2 deletions

View File

@ -1 +1,6 @@
/**
* Gestion Flota Controllers
*/
export { ProductsController, CategoriesController } from './products.controller';
export * from './unidades.controller';
export * from './operadores.controller';

View File

@ -0,0 +1,266 @@
import { Request, Response, NextFunction, Router } from 'express';
import { OperadoresService, CreateOperadorDto, UpdateOperadorDto } from '../services/operadores.service';
import { TipoLicencia, EstadoOperador } from '../entities';
export class OperadoresController {
public router: Router;
constructor(private readonly operadoresService: OperadoresService) {
this.router = Router();
this.initializeRoutes();
}
private initializeRoutes(): void {
// CRUD básico
this.router.get('/', this.findAll.bind(this));
this.router.get('/disponibles', this.getOperadoresDisponibles.bind(this));
this.router.get('/licencia-por-vencer', this.getOperadoresLicenciaPorVencer.bind(this));
this.router.get('/licencia-vencida', this.getOperadoresLicenciaVencida.bind(this));
this.router.get('/:id', this.findOne.bind(this));
this.router.get('/empleado/:numeroEmpleado', this.findByNumeroEmpleado.bind(this));
this.router.get('/licencia/:numeroLicencia', this.findByLicencia.bind(this));
this.router.post('/', this.create.bind(this));
this.router.patch('/:id', this.update.bind(this));
this.router.delete('/:id', this.delete.bind(this));
// Operaciones específicas
this.router.post('/:id/estado', this.cambiarEstado.bind(this));
}
private async findAll(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { search, estado, estados, tipoLicencia, sucursalId, limit, offset } = req.query;
const result = await this.operadoresService.findAll({
tenantId,
search: search as string,
estado: estado as EstadoOperador,
estados: estados ? (estados as string).split(',') as EstadoOperador[] : undefined,
tipoLicencia: tipoLicencia as TipoLicencia,
sucursalId: sucursalId as string,
limit: limit ? parseInt(limit as string, 10) : undefined,
offset: offset ? parseInt(offset as string, 10) : undefined,
});
res.json(result);
} catch (error) {
next(error);
}
}
private async findOne(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const operador = await this.operadoresService.findOne(id, tenantId);
if (!operador) {
res.status(404).json({ error: 'Operador no encontrado' });
return;
}
res.json({ data: operador });
} catch (error) {
next(error);
}
}
private async findByNumeroEmpleado(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { numeroEmpleado } = req.params;
const operador = await this.operadoresService.findByNumeroEmpleado(numeroEmpleado, tenantId);
if (!operador) {
res.status(404).json({ error: 'Operador no encontrado' });
return;
}
res.json({ data: operador });
} catch (error) {
next(error);
}
}
private async findByLicencia(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { numeroLicencia } = req.params;
const operador = await this.operadoresService.findByLicencia(numeroLicencia, tenantId);
if (!operador) {
res.status(404).json({ error: 'Operador no encontrado' });
return;
}
res.json({ data: operador });
} catch (error) {
next(error);
}
}
private async create(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const dto: CreateOperadorDto = req.body;
const operador = await this.operadoresService.create(tenantId, dto, userId);
res.status(201).json({ data: operador });
} catch (error) {
next(error);
}
}
private async update(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const dto: UpdateOperadorDto = req.body;
const operador = await this.operadoresService.update(id, tenantId, dto, userId);
if (!operador) {
res.status(404).json({ error: 'Operador no encontrado' });
return;
}
res.json({ data: operador });
} catch (error) {
next(error);
}
}
private async delete(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const deleted = await this.operadoresService.delete(id, tenantId);
if (!deleted) {
res.status(404).json({ error: 'Operador no encontrado' });
return;
}
res.status(204).send();
} catch (error) {
next(error);
}
}
private async cambiarEstado(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const { estado } = req.body;
if (!estado) {
res.status(400).json({ error: 'Estado es requerido' });
return;
}
const operador = await this.operadoresService.cambiarEstado(id, tenantId, estado, userId);
if (!operador) {
res.status(404).json({ error: 'Operador no encontrado' });
return;
}
res.json({ data: operador });
} catch (error) {
next(error);
}
}
private async getOperadoresDisponibles(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const operadores = await this.operadoresService.getOperadoresDisponibles(tenantId);
res.json({ data: operadores });
} catch (error) {
next(error);
}
}
private async getOperadoresLicenciaPorVencer(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { dias } = req.query;
const diasAntelacion = dias ? parseInt(dias as string, 10) : 30;
const operadores = await this.operadoresService.getOperadoresLicenciaPorVencer(tenantId, diasAntelacion);
res.json({ data: operadores });
} catch (error) {
next(error);
}
}
private async getOperadoresLicenciaVencida(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const operadores = await this.operadoresService.getOperadoresLicenciaVencida(tenantId);
res.json({ data: operadores });
} catch (error) {
next(error);
}
}
}

View File

@ -0,0 +1,301 @@
import { Request, Response, NextFunction, Router } from 'express';
import { UnidadesService, CreateUnidadDto, UpdateUnidadDto } from '../services/unidades.service';
import { TipoUnidad, EstadoUnidad } from '../entities';
export class UnidadesController {
public router: Router;
constructor(private readonly unidadesService: UnidadesService) {
this.router = Router();
this.initializeRoutes();
}
private initializeRoutes(): void {
// CRUD básico
this.router.get('/', this.findAll.bind(this));
this.router.get('/disponibles', this.getUnidadesDisponibles.bind(this));
this.router.get('/seguro-por-vencer', this.getUnidadesSeguroPorVencer.bind(this));
this.router.get('/verificacion-por-vencer', this.getUnidadesVerificacionPorVencer.bind(this));
this.router.get('/:id', this.findOne.bind(this));
this.router.get('/numero-economico/:numeroEconomico', this.findByNumeroEconomico.bind(this));
this.router.get('/placas/:placas', this.findByPlacas.bind(this));
this.router.post('/', this.create.bind(this));
this.router.patch('/:id', this.update.bind(this));
this.router.delete('/:id', this.delete.bind(this));
// Operaciones específicas
this.router.post('/:id/estado', this.cambiarEstado.bind(this));
this.router.post('/:id/odometro', this.actualizarOdometro.bind(this));
}
private async findAll(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { search, tipo, estado, estados, sucursalId, limit, offset } = req.query;
const result = await this.unidadesService.findAll({
tenantId,
search: search as string,
tipo: tipo as TipoUnidad,
estado: estado as EstadoUnidad,
estados: estados ? (estados as string).split(',') as EstadoUnidad[] : undefined,
sucursalId: sucursalId as string,
limit: limit ? parseInt(limit as string, 10) : undefined,
offset: offset ? parseInt(offset as string, 10) : undefined,
});
res.json(result);
} catch (error) {
next(error);
}
}
private async findOne(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const unidad = await this.unidadesService.findOne(id, tenantId);
if (!unidad) {
res.status(404).json({ error: 'Unidad no encontrada' });
return;
}
res.json({ data: unidad });
} catch (error) {
next(error);
}
}
private async findByNumeroEconomico(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { numeroEconomico } = req.params;
const unidad = await this.unidadesService.findByNumeroEconomico(numeroEconomico, tenantId);
if (!unidad) {
res.status(404).json({ error: 'Unidad no encontrada' });
return;
}
res.json({ data: unidad });
} catch (error) {
next(error);
}
}
private async findByPlacas(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { placas } = req.params;
const unidad = await this.unidadesService.findByPlacas(placas, tenantId);
if (!unidad) {
res.status(404).json({ error: 'Unidad no encontrada' });
return;
}
res.json({ data: unidad });
} catch (error) {
next(error);
}
}
private async create(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const dto: CreateUnidadDto = req.body;
const unidad = await this.unidadesService.create(tenantId, dto, userId);
res.status(201).json({ data: unidad });
} catch (error) {
next(error);
}
}
private async update(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const dto: UpdateUnidadDto = req.body;
const unidad = await this.unidadesService.update(id, tenantId, dto, userId);
if (!unidad) {
res.status(404).json({ error: 'Unidad no encontrada' });
return;
}
res.json({ data: unidad });
} catch (error) {
next(error);
}
}
private async delete(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const deleted = await this.unidadesService.delete(id, tenantId);
if (!deleted) {
res.status(404).json({ error: 'Unidad no encontrada' });
return;
}
res.status(204).send();
} catch (error) {
next(error);
}
}
private async cambiarEstado(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const { estado } = req.body;
if (!estado) {
res.status(400).json({ error: 'Estado es requerido' });
return;
}
const unidad = await this.unidadesService.cambiarEstado(id, tenantId, estado, userId);
if (!unidad) {
res.status(404).json({ error: 'Unidad no encontrada' });
return;
}
res.json({ data: unidad });
} catch (error) {
next(error);
}
}
private async actualizarOdometro(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const { odometro } = req.body;
if (odometro === undefined) {
res.status(400).json({ error: 'Odómetro es requerido' });
return;
}
const unidad = await this.unidadesService.actualizarOdometro(id, tenantId, odometro, userId);
if (!unidad) {
res.status(404).json({ error: 'Unidad no encontrada' });
return;
}
res.json({ data: unidad });
} catch (error) {
next(error);
}
}
private async getUnidadesDisponibles(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { tipo } = req.query;
const unidades = await this.unidadesService.getUnidadesDisponibles(tenantId, tipo as TipoUnidad);
res.json({ data: unidades });
} catch (error) {
next(error);
}
}
private async getUnidadesSeguroPorVencer(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { dias } = req.query;
const diasAntelacion = dias ? parseInt(dias as string, 10) : 30;
const unidades = await this.unidadesService.getUnidadesSeguroPorVencer(tenantId, diasAntelacion);
res.json({ data: unidades });
} catch (error) {
next(error);
}
}
private async getUnidadesVerificacionPorVencer(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { dias } = req.query;
const diasAntelacion = dias ? parseInt(dias as string, 10) : 30;
const unidades = await this.unidadesService.getUnidadesVerificacionPorVencer(tenantId, diasAntelacion);
res.json({ data: unidades });
} catch (error) {
next(error);
}
}
}

View File

@ -2,6 +2,9 @@ import { Request, Response, NextFunction, Router } from 'express';
import { SalesService } from '../services';
import { CreateQuotationDto, UpdateQuotationDto, CreateSalesOrderDto, UpdateSalesOrderDto } from '../dto';
// Export transport-specific controller
export * from './ordenes-transporte.controller';
export class QuotationsController {
public router: Router;
constructor(private readonly salesService: SalesService) {

View File

@ -0,0 +1,332 @@
import { Request, Response, NextFunction, Router } from 'express';
import { OrdenesTransporteService, CreateOrdenTransporteDto, UpdateOrdenTransporteDto } from '../services/ordenes-transporte.service';
import { EstadoOrdenTransporte, ModalidadServicio } from '../entities';
export class OrdenesTransporteController {
public router: Router;
constructor(private readonly otService: OrdenesTransporteService) {
this.router = Router();
this.initializeRoutes();
}
private initializeRoutes(): void {
// CRUD básico
this.router.get('/', this.findAll.bind(this));
this.router.get('/pendientes', this.getOtsPendientes.bind(this));
this.router.get('/programacion', this.getOtsParaProgramacion.bind(this));
this.router.get('/cliente/:clienteId', this.getOtsCliente.bind(this));
this.router.get('/:id', this.findOne.bind(this));
this.router.get('/numero/:numeroOt', this.findByNumeroOt.bind(this));
this.router.post('/', this.create.bind(this));
this.router.patch('/:id', this.update.bind(this));
// Operaciones de estado
this.router.post('/:id/estado', this.cambiarEstado.bind(this));
this.router.post('/:id/confirmar', this.confirmar.bind(this));
this.router.post('/:id/asignar', this.asignar.bind(this));
this.router.post('/:id/cancelar', this.cancelar.bind(this));
}
private async findAll(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const {
search,
estado,
estados,
clienteId,
modalidad,
fechaDesde,
fechaHasta,
limit,
offset,
} = req.query;
const result = await this.otService.findAll({
tenantId,
search: search as string,
estado: estado as EstadoOrdenTransporte,
estados: estados ? (estados as string).split(',') as EstadoOrdenTransporte[] : undefined,
clienteId: clienteId as string,
modalidad: modalidad as ModalidadServicio,
fechaDesde: fechaDesde ? new Date(fechaDesde as string) : undefined,
fechaHasta: fechaHasta ? new Date(fechaHasta as string) : undefined,
limit: limit ? parseInt(limit as string, 10) : undefined,
offset: offset ? parseInt(offset as string, 10) : undefined,
});
res.json(result);
} catch (error) {
next(error);
}
}
private async findOne(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const ot = await this.otService.findOne(id, tenantId);
if (!ot) {
res.status(404).json({ error: 'Orden de transporte no encontrada' });
return;
}
res.json({ data: ot });
} catch (error) {
next(error);
}
}
private async findByNumeroOt(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { numeroOt } = req.params;
const ot = await this.otService.findByNumeroOt(numeroOt, tenantId);
if (!ot) {
res.status(404).json({ error: 'Orden de transporte no encontrada' });
return;
}
res.json({ data: ot });
} catch (error) {
next(error);
}
}
private async create(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const dto: CreateOrdenTransporteDto = req.body;
const ot = await this.otService.create(tenantId, dto, userId);
res.status(201).json({ data: ot });
} catch (error) {
next(error);
}
}
private async update(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const dto: UpdateOrdenTransporteDto = req.body;
const ot = await this.otService.update(id, tenantId, dto, userId);
if (!ot) {
res.status(404).json({ error: 'Orden de transporte no encontrada' });
return;
}
res.json({ data: ot });
} catch (error) {
next(error);
}
}
private async cambiarEstado(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const { estado } = req.body;
if (!estado) {
res.status(400).json({ error: 'Estado es requerido' });
return;
}
const ot = await this.otService.cambiarEstado(id, tenantId, estado, userId);
if (!ot) {
res.status(404).json({ error: 'Orden de transporte no encontrada' });
return;
}
res.json({ data: ot });
} catch (error) {
next(error);
}
}
private async confirmar(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const ot = await this.otService.confirmar(id, tenantId, userId);
if (!ot) {
res.status(404).json({ error: 'Orden de transporte no encontrada' });
return;
}
res.json({ data: ot });
} catch (error) {
next(error);
}
}
private async asignar(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const { viajeId } = req.body;
if (!viajeId) {
res.status(400).json({ error: 'viajeId es requerido' });
return;
}
const ot = await this.otService.asignar(id, tenantId, viajeId, userId);
if (!ot) {
res.status(404).json({ error: 'Orden de transporte no encontrada' });
return;
}
res.json({ data: ot });
} catch (error) {
next(error);
}
}
private async cancelar(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const { motivo } = req.body;
if (!motivo) {
res.status(400).json({ error: 'Motivo de cancelación es requerido' });
return;
}
const ot = await this.otService.cancelar(id, tenantId, motivo, userId);
if (!ot) {
res.status(404).json({ error: 'Orden de transporte no encontrada' });
return;
}
res.json({ data: ot });
} catch (error) {
next(error);
}
}
private async getOtsPendientes(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const ots = await this.otService.getOtsPendientes(tenantId);
res.json({ data: ots });
} catch (error) {
next(error);
}
}
private async getOtsCliente(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { clienteId } = req.params;
const { limit } = req.query;
const ots = await this.otService.getOtsCliente(
clienteId,
tenantId,
limit ? parseInt(limit as string, 10) : undefined
);
res.json({ data: ots });
} catch (error) {
next(error);
}
}
private async getOtsParaProgramacion(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { fecha } = req.query;
if (!fecha) {
res.status(400).json({ error: 'Fecha es requerida' });
return;
}
const ots = await this.otService.getOtsParaProgramacion(tenantId, new Date(fecha as string));
res.json({ data: ots });
} catch (error) {
next(error);
}
}
}

View File

@ -1,5 +1,4 @@
/**
* Tracking Controllers
*/
// TODO: Implement controllers
// - tracking.controller.ts
export * from './tracking.controller';

View File

@ -0,0 +1,323 @@
import { Request, Response, NextFunction, Router } from 'express';
import { TrackingService, CreateEventoDto, CreateGeocercaDto } from '../services/tracking.service';
import { TipoEventoTracking, TipoGeocerca } from '../entities';
export class TrackingController {
public router: Router;
constructor(private readonly trackingService: TrackingService) {
this.router = Router();
this.initializeRoutes();
}
private initializeRoutes(): void {
// Eventos/Posiciones
this.router.get('/eventos', this.findEventos.bind(this));
this.router.get('/posiciones-actuales', this.getPosicionesActuales.bind(this));
this.router.get('/unidad/:unidadId/ultima-posicion', this.getUltimaPosicion.bind(this));
this.router.get('/unidad/:unidadId/historial', this.getHistorialPosiciones.bind(this));
this.router.get('/viaje/:viajeId/ruta', this.getRutaViaje.bind(this));
this.router.get('/viaje/:viajeId/eventos', this.getEventosViaje.bind(this));
this.router.post('/eventos', this.registrarEvento.bind(this));
this.router.post('/posicion', this.registrarPosicion.bind(this));
// Geocercas
this.router.get('/geocercas', this.findAllGeocercas.bind(this));
this.router.get('/geocercas/:id', this.findGeocerca.bind(this));
this.router.post('/geocercas', this.createGeocerca.bind(this));
this.router.patch('/geocercas/:id', this.updateGeocerca.bind(this));
this.router.delete('/geocercas/:id', this.deleteGeocerca.bind(this));
}
// ==================== Eventos/Posiciones ====================
private async findEventos(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { unidadId, viajeId, tipoEvento, fechaDesde, fechaHasta, limit, offset } = req.query;
const result = await this.trackingService.findEventos({
tenantId,
unidadId: unidadId as string,
viajeId: viajeId as string,
tipoEvento: tipoEvento as TipoEventoTracking,
fechaDesde: fechaDesde ? new Date(fechaDesde as string) : undefined,
fechaHasta: fechaHasta ? new Date(fechaHasta as string) : undefined,
limit: limit ? parseInt(limit as string, 10) : undefined,
offset: offset ? parseInt(offset as string, 10) : undefined,
});
res.json(result);
} catch (error) {
next(error);
}
}
private async registrarEvento(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const dto: CreateEventoDto = req.body;
const evento = await this.trackingService.registrarEvento(tenantId, dto);
res.status(201).json({ data: evento });
} catch (error) {
next(error);
}
}
private async registrarPosicion(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { unidadId, latitud, longitud, velocidad, rumbo, odometro, viajeId, operadorId } = req.body;
if (!unidadId || latitud === undefined || longitud === undefined) {
res.status(400).json({ error: 'unidadId, latitud y longitud son requeridos' });
return;
}
const evento = await this.trackingService.registrarPosicion(tenantId, unidadId, {
latitud,
longitud,
velocidad,
rumbo,
odometro,
viajeId,
operadorId,
});
res.status(201).json({ data: evento });
} catch (error) {
next(error);
}
}
private async getUltimaPosicion(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { unidadId } = req.params;
const posicion = await this.trackingService.getUltimaPosicion(unidadId, tenantId);
if (!posicion) {
res.status(404).json({ error: 'No hay posiciones registradas para esta unidad' });
return;
}
res.json({ data: posicion });
} catch (error) {
next(error);
}
}
private async getHistorialPosiciones(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { unidadId } = req.params;
const { fechaDesde, fechaHasta } = req.query;
if (!fechaDesde || !fechaHasta) {
res.status(400).json({ error: 'fechaDesde y fechaHasta son requeridos' });
return;
}
const posiciones = await this.trackingService.getHistorialPosiciones(
unidadId,
tenantId,
new Date(fechaDesde as string),
new Date(fechaHasta as string)
);
res.json({ data: posiciones });
} catch (error) {
next(error);
}
}
private async getRutaViaje(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { viajeId } = req.params;
const ruta = await this.trackingService.getRutaViaje(viajeId, tenantId);
res.json({ data: ruta });
} catch (error) {
next(error);
}
}
private async getEventosViaje(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { viajeId } = req.params;
const eventos = await this.trackingService.getEventosViaje(viajeId, tenantId);
res.json({ data: eventos });
} catch (error) {
next(error);
}
}
private async getPosicionesActuales(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { unidadIds } = req.query;
const ids = unidadIds ? (unidadIds as string).split(',') : undefined;
const posiciones = await this.trackingService.getPosicionesActuales(tenantId, ids);
res.json({ data: posiciones });
} catch (error) {
next(error);
}
}
// ==================== Geocercas ====================
private async findAllGeocercas(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { tipo, activa, clienteId, limit, offset } = req.query;
const result = await this.trackingService.findAllGeocercas({
tenantId,
tipo: tipo as TipoGeocerca,
activa: activa ? activa === 'true' : undefined,
clienteId: clienteId as string,
limit: limit ? parseInt(limit as string, 10) : undefined,
offset: offset ? parseInt(offset as string, 10) : undefined,
});
res.json(result);
} catch (error) {
next(error);
}
}
private async findGeocerca(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const geocerca = await this.trackingService.findGeocerca(id, tenantId);
if (!geocerca) {
res.status(404).json({ error: 'Geocerca no encontrada' });
return;
}
res.json({ data: geocerca });
} catch (error) {
next(error);
}
}
private async createGeocerca(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const dto: CreateGeocercaDto = req.body;
const geocerca = await this.trackingService.createGeocerca(tenantId, dto, userId);
res.status(201).json({ data: geocerca });
} catch (error) {
next(error);
}
}
private async updateGeocerca(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const dto: Partial<CreateGeocercaDto> = req.body;
const geocerca = await this.trackingService.updateGeocerca(id, tenantId, dto, userId);
if (!geocerca) {
res.status(404).json({ error: 'Geocerca no encontrada' });
return;
}
res.json({ data: geocerca });
} catch (error) {
next(error);
}
}
private async deleteGeocerca(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const deleted = await this.trackingService.deleteGeocerca(id, tenantId);
if (!deleted) {
res.status(404).json({ error: 'Geocerca no encontrada' });
return;
}
res.status(204).send();
} catch (error) {
next(error);
}
}
}

View File

@ -0,0 +1,4 @@
/**
* Viajes Controllers
*/
export * from './viajes.controller';

View File

@ -0,0 +1,339 @@
import { Request, Response, NextFunction, Router } from 'express';
import { ViajesService, CreateViajeDto, UpdateViajeDto } from '../services/viajes.service';
import { EstadoViaje } from '../entities';
export class ViajesController {
public router: Router;
constructor(private readonly viajesService: ViajesService) {
this.router = Router();
this.initializeRoutes();
}
private initializeRoutes(): void {
// CRUD básico
this.router.get('/', this.findAll.bind(this));
this.router.get('/en-progreso', this.getViajesEnProgreso.bind(this));
this.router.get('/operador/:operadorId', this.getViajesOperador.bind(this));
this.router.get('/unidad/:unidadId', this.getViajesUnidad.bind(this));
this.router.get('/:id', this.findOne.bind(this));
this.router.get('/numero/:numeroViaje', this.findByNumero.bind(this));
this.router.post('/', this.create.bind(this));
this.router.patch('/:id', this.update.bind(this));
// Operaciones de estado
this.router.post('/:id/estado', this.cambiarEstado.bind(this));
this.router.post('/:id/despachar', this.despachar.bind(this));
this.router.post('/:id/iniciar-transito', this.iniciarTransito.bind(this));
this.router.post('/:id/registrar-llegada', this.registrarLlegada.bind(this));
this.router.post('/:id/registrar-entrega', this.registrarEntrega.bind(this));
}
private async findAll(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const {
search,
estado,
estados,
unidadId,
operadorId,
clienteId,
fechaDesde,
fechaHasta,
limit,
offset,
} = req.query;
const result = await this.viajesService.findAll({
tenantId,
search: search as string,
estado: estado as EstadoViaje,
estados: estados ? (estados as string).split(',') as EstadoViaje[] : undefined,
unidadId: unidadId as string,
operadorId: operadorId as string,
clienteId: clienteId as string,
fechaDesde: fechaDesde ? new Date(fechaDesde as string) : undefined,
fechaHasta: fechaHasta ? new Date(fechaHasta as string) : undefined,
limit: limit ? parseInt(limit as string, 10) : undefined,
offset: offset ? parseInt(offset as string, 10) : undefined,
});
res.json(result);
} catch (error) {
next(error);
}
}
private async findOne(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const viaje = await this.viajesService.findOne(id, tenantId);
if (!viaje) {
res.status(404).json({ error: 'Viaje no encontrado' });
return;
}
res.json({ data: viaje });
} catch (error) {
next(error);
}
}
private async findByNumero(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { numeroViaje } = req.params;
const viaje = await this.viajesService.findByNumero(numeroViaje, tenantId);
if (!viaje) {
res.status(404).json({ error: 'Viaje no encontrado' });
return;
}
res.json({ data: viaje });
} catch (error) {
next(error);
}
}
private async create(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const dto: CreateViajeDto = req.body;
const viaje = await this.viajesService.create(tenantId, dto, userId);
res.status(201).json({ data: viaje });
} catch (error) {
next(error);
}
}
private async update(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const dto: UpdateViajeDto = req.body;
const viaje = await this.viajesService.update(id, tenantId, dto, userId);
if (!viaje) {
res.status(404).json({ error: 'Viaje no encontrado' });
return;
}
res.json({ data: viaje });
} catch (error) {
next(error);
}
}
private async cambiarEstado(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const { estado } = req.body;
if (!estado) {
res.status(400).json({ error: 'Estado es requerido' });
return;
}
const viaje = await this.viajesService.cambiarEstado(id, tenantId, estado, userId);
if (!viaje) {
res.status(404).json({ error: 'Viaje no encontrado' });
return;
}
res.json({ data: viaje });
} catch (error) {
next(error);
}
}
private async despachar(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const { kmInicial, sellos } = req.body;
const viaje = await this.viajesService.despachar(id, tenantId, { kmInicial, sellos }, userId);
if (!viaje) {
res.status(404).json({ error: 'Viaje no encontrado' });
return;
}
res.json({ data: viaje });
} catch (error) {
next(error);
}
}
private async iniciarTransito(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const viaje = await this.viajesService.iniciarTransito(id, tenantId, userId);
if (!viaje) {
res.status(404).json({ error: 'Viaje no encontrado' });
return;
}
res.json({ data: viaje });
} catch (error) {
next(error);
}
}
private async registrarLlegada(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const { kmFinal } = req.body;
const viaje = await this.viajesService.registrarLlegada(id, tenantId, { kmFinal }, userId);
if (!viaje) {
res.status(404).json({ error: 'Viaje no encontrado' });
return;
}
res.json({ data: viaje });
} catch (error) {
next(error);
}
}
private async registrarEntrega(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
const userId = req.headers['x-user-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { id } = req.params;
const { kmFinal, observaciones } = req.body;
const viaje = await this.viajesService.registrarEntrega(id, tenantId, { kmFinal, observaciones }, userId);
if (!viaje) {
res.status(404).json({ error: 'Viaje no encontrado' });
return;
}
res.json({ data: viaje });
} catch (error) {
next(error);
}
}
private async getViajesEnProgreso(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const viajes = await this.viajesService.getViajesEnProgreso(tenantId);
res.json({ data: viajes });
} catch (error) {
next(error);
}
}
private async getViajesOperador(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { operadorId } = req.params;
const viajes = await this.viajesService.getViajesOperador(operadorId, tenantId);
res.json({ data: viajes });
} catch (error) {
next(error);
}
}
private async getViajesUnidad(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
res.status(400).json({ error: 'Tenant ID is required' });
return;
}
const { unidadId } = req.params;
const viajes = await this.viajesService.getViajesUnidad(unidadId, tenantId);
res.json({ data: viajes });
} catch (error) {
next(error);
}
}
}