[ST-P2-001] feat: Add DTOs for Construction, Finance, and HSE modules

- Construction: 6 DTOs (create/update/response proyecto, fraccionamiento)
- Finance: 5 DTOs (create/update/response bank-account, accounting-entry)
- HSE: 7 DTOs (incidente, capacitacion, permiso-trabajo, epp-asignacion)

All DTOs use class-validator for validation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-02-03 07:38:54 -06:00
parent 06c48678b1
commit 7f32ee65b6
18 changed files with 1554 additions and 0 deletions

View File

@ -0,0 +1,78 @@
/**
* CreateFraccionamientoDto - DTO para crear fraccionamientos
*
* @module Construction
* @rf RF-MAI002-002
*/
import {
IsString,
IsNotEmpty,
IsOptional,
IsUUID,
IsInt,
IsNumber,
MaxLength,
MinLength,
Min,
} from 'class-validator';
export class CreateFraccionamientoDto {
/**
* ID del proyecto al que pertenece
*/
@IsUUID('4', { message: 'proyectoId debe ser un UUID valido' })
@IsNotEmpty({ message: 'El proyecto es requerido' })
proyectoId: string;
/**
* Codigo unico del fraccionamiento
* @example 'FRAC-001'
*/
@IsString()
@IsNotEmpty({ message: 'El codigo es requerido' })
@MinLength(3, { message: 'El codigo debe tener al menos 3 caracteres' })
@MaxLength(20, { message: 'El codigo no puede exceder 20 caracteres' })
codigo: string;
/**
* Nombre del fraccionamiento
* @example 'Las Palmas Etapa 1'
*/
@IsString()
@IsNotEmpty({ message: 'El nombre es requerido' })
@MinLength(3, { message: 'El nombre debe tener al menos 3 caracteres' })
@MaxLength(200, { message: 'El nombre no puede exceder 200 caracteres' })
nombre: string;
/**
* Descripcion del fraccionamiento
*/
@IsOptional()
@IsString()
descripcion?: string;
/**
* Numero total de manzanas
*/
@IsOptional()
@IsInt({ message: 'El numero de manzanas debe ser entero' })
@Min(0, { message: 'El numero de manzanas no puede ser negativo' })
numeroManzanas?: number;
/**
* Numero total de lotes
*/
@IsOptional()
@IsInt({ message: 'El numero de lotes debe ser entero' })
@Min(0, { message: 'El numero de lotes no puede ser negativo' })
numeroLotes?: number;
/**
* Superficie total en metros cuadrados
*/
@IsOptional()
@IsNumber({}, { message: 'La superficie debe ser un numero' })
@Min(0, { message: 'La superficie no puede ser negativa' })
superficieTotal?: number;
}

View File

@ -0,0 +1,93 @@
/**
* CreateProyectoDto - DTO para crear proyectos
*
* @module Construction
* @rf RF-MAI002-001
*/
import {
IsString,
IsNotEmpty,
IsOptional,
IsDateString,
IsEnum,
MaxLength,
MinLength,
} from 'class-validator';
import { EstadoProyecto } from '../entities/proyecto.entity';
export class CreateProyectoDto {
/**
* Codigo unico del proyecto
* @example 'PRY-001'
*/
@IsString()
@IsNotEmpty({ message: 'El codigo es requerido' })
@MinLength(3, { message: 'El codigo debe tener al menos 3 caracteres' })
@MaxLength(20, { message: 'El codigo no puede exceder 20 caracteres' })
codigo: string;
/**
* Nombre del proyecto
* @example 'Residencial Las Palmas Fase 1'
*/
@IsString()
@IsNotEmpty({ message: 'El nombre es requerido' })
@MinLength(3, { message: 'El nombre debe tener al menos 3 caracteres' })
@MaxLength(200, { message: 'El nombre no puede exceder 200 caracteres' })
nombre: string;
/**
* Descripcion detallada del proyecto
*/
@IsOptional()
@IsString()
descripcion?: string;
/**
* Direccion del proyecto
*/
@IsOptional()
@IsString()
direccion?: string;
/**
* Ciudad donde se ubica el proyecto
*/
@IsOptional()
@IsString()
@MaxLength(100)
ciudad?: string;
/**
* Estado/Provincia donde se ubica el proyecto
*/
@IsOptional()
@IsString()
@MaxLength(100)
estado?: string;
/**
* Fecha de inicio del proyecto (YYYY-MM-DD)
*/
@IsOptional()
@IsDateString({}, { message: 'Formato de fecha invalido (usar YYYY-MM-DD)' })
fechaInicio?: string;
/**
* Fecha estimada de finalizacion (YYYY-MM-DD)
*/
@IsOptional()
@IsDateString({}, { message: 'Formato de fecha invalido (usar YYYY-MM-DD)' })
fechaFinEstimada?: string;
/**
* Estado del proyecto
* @default 'activo'
*/
@IsOptional()
@IsEnum(['activo', 'pausado', 'completado', 'cancelado'], {
message: 'Estado invalido',
})
estadoProyecto?: EstadoProyecto;
}

View File

@ -0,0 +1,14 @@
/**
* Construction DTOs - Barrel Export
*
* @module Construction
*/
// Proyecto
export * from './create-proyecto.dto';
export * from './update-proyecto.dto';
export * from './proyecto-response.dto';
// Fraccionamiento
export * from './create-fraccionamiento.dto';
export * from './update-fraccionamiento.dto';

View File

@ -0,0 +1,70 @@
/**
* ProyectoResponseDto - DTO de respuesta para proyectos
*
* @module Construction
* @rf RF-MAI002-001
*/
import { Expose, Type } from 'class-transformer';
/**
* DTO de respuesta para proyectos.
* Usar con class-transformer para serializar respuestas.
*/
export class ProyectoResponseDto {
/** ID unico del proyecto */
@Expose()
id: string;
/** ID del tenant */
@Expose()
tenantId: string;
/** Codigo unico del proyecto */
@Expose()
codigo: string;
/** Nombre del proyecto */
@Expose()
nombre: string;
/** Descripcion del proyecto */
@Expose()
descripcion?: string;
/** Direccion del proyecto */
@Expose()
direccion?: string;
/** Ciudad del proyecto */
@Expose()
ciudad?: string;
/** Estado/Provincia del proyecto */
@Expose()
estado?: string;
/** Fecha de inicio */
@Expose()
@Type(() => Date)
fechaInicio?: Date;
/** Fecha estimada de fin */
@Expose()
@Type(() => Date)
fechaFinEstimada?: Date;
/** Estado del proyecto */
@Expose()
estadoProyecto: string;
/** Fecha de creacion */
@Expose()
@Type(() => Date)
createdAt: Date;
/** Fecha de ultima actualizacion */
@Expose()
@Type(() => Date)
updatedAt: Date;
}

View File

@ -0,0 +1,52 @@
/**
* UpdateFraccionamientoDto - DTO para actualizar fraccionamientos
*
* @module Construction
*/
import {
IsString,
IsOptional,
IsInt,
IsNumber,
MaxLength,
MinLength,
Min,
} from 'class-validator';
/**
* DTO para actualizar un fraccionamiento.
* El proyectoId no se puede cambiar.
*/
export class UpdateFraccionamientoDto {
@IsOptional()
@IsString()
@MinLength(3, { message: 'El codigo debe tener al menos 3 caracteres' })
@MaxLength(20, { message: 'El codigo no puede exceder 20 caracteres' })
codigo?: string;
@IsOptional()
@IsString()
@MinLength(3, { message: 'El nombre debe tener al menos 3 caracteres' })
@MaxLength(200, { message: 'El nombre no puede exceder 200 caracteres' })
nombre?: string;
@IsOptional()
@IsString()
descripcion?: string;
@IsOptional()
@IsInt({ message: 'El numero de manzanas debe ser entero' })
@Min(0, { message: 'El numero de manzanas no puede ser negativo' })
numeroManzanas?: number;
@IsOptional()
@IsInt({ message: 'El numero de lotes debe ser entero' })
@Min(0, { message: 'El numero de lotes no puede ser negativo' })
numeroLotes?: number;
@IsOptional()
@IsNumber({}, { message: 'La superficie debe ser un numero' })
@Min(0, { message: 'La superficie no puede ser negativa' })
superficieTotal?: number;
}

View File

@ -0,0 +1,66 @@
/**
* UpdateProyectoDto - DTO para actualizar proyectos
*
* @module Construction
* @rf RF-MAI002-001
*/
import {
IsString,
IsOptional,
IsDateString,
IsEnum,
MaxLength,
MinLength,
} from 'class-validator';
import { EstadoProyecto } from '../entities/proyecto.entity';
/**
* DTO para actualizar un proyecto existente.
* Todos los campos son opcionales.
*/
export class UpdateProyectoDto {
@IsOptional()
@IsString()
@MinLength(3, { message: 'El codigo debe tener al menos 3 caracteres' })
@MaxLength(20, { message: 'El codigo no puede exceder 20 caracteres' })
codigo?: string;
@IsOptional()
@IsString()
@MinLength(3, { message: 'El nombre debe tener al menos 3 caracteres' })
@MaxLength(200, { message: 'El nombre no puede exceder 200 caracteres' })
nombre?: string;
@IsOptional()
@IsString()
descripcion?: string;
@IsOptional()
@IsString()
direccion?: string;
@IsOptional()
@IsString()
@MaxLength(100)
ciudad?: string;
@IsOptional()
@IsString()
@MaxLength(100)
estado?: string;
@IsOptional()
@IsDateString({}, { message: 'Formato de fecha invalido (usar YYYY-MM-DD)' })
fechaInicio?: string;
@IsOptional()
@IsDateString({}, { message: 'Formato de fecha invalido (usar YYYY-MM-DD)' })
fechaFinEstimada?: string;
@IsOptional()
@IsEnum(['activo', 'pausado', 'completado', 'cancelado'], {
message: 'Estado invalido',
})
estadoProyecto?: EstadoProyecto;
}

View File

@ -0,0 +1,95 @@
/**
* BankAccountResponseDto - DTO de respuesta para cuentas bancarias
*
* @module Finance
*/
import { Expose, Type } from 'class-transformer';
/**
* DTO de respuesta para cuentas bancarias.
* Usar con class-transformer para serializar respuestas.
*/
export class BankAccountResponseDto {
/** ID unico de la cuenta */
@Expose()
id: string;
/** ID del tenant */
@Expose()
tenantId: string;
/** Nombre de la cuenta */
@Expose()
name: string;
/** Numero de cuenta */
@Expose()
accountNumber: string;
/** CLABE interbancaria */
@Expose()
clabe?: string;
/** Tipo de cuenta */
@Expose()
accountType: string;
/** Estado de la cuenta */
@Expose()
status: string;
/** Nombre del banco */
@Expose()
bankName: string;
/** Codigo del banco */
@Expose()
bankCode?: string;
/** Moneda */
@Expose()
currency: string;
/** Saldo inicial */
@Expose()
initialBalance: number;
/** Saldo actual */
@Expose()
currentBalance: number;
/** Saldo disponible */
@Expose()
availableBalance: number;
/** Limite de credito */
@Expose()
creditLimit?: number;
/** Es cuenta por defecto */
@Expose()
isDefault: boolean;
/** Permite pagos */
@Expose()
allowsPayments: boolean;
/** Permite cobros */
@Expose()
allowsCollections: boolean;
/** ID del proyecto asociado */
@Expose()
projectId?: string;
/** Fecha de creacion */
@Expose()
@Type(() => Date)
createdAt: Date;
/** Fecha de actualizacion */
@Expose()
@Type(() => Date)
updatedAt: Date;
}

View File

@ -0,0 +1,115 @@
/**
* CreateAccountingEntryDto - DTO para crear polizas contables
*
* @module Finance
* @rf RF-MAE014-002
*/
import {
IsString,
IsNotEmpty,
IsOptional,
IsUUID,
IsDateString,
IsNumber,
IsArray,
ValidateNested,
ArrayMinSize,
MaxLength,
} from 'class-validator';
import { Type } from 'class-transformer';
/**
* Linea de poliza contable
*/
export class AccountingEntryLineDto {
/**
* ID de la cuenta contable
*/
@IsUUID('4', { message: 'accountId debe ser un UUID valido' })
@IsNotEmpty()
accountId: string;
/**
* Monto al debe
* @default 0
*/
@IsNumber({}, { message: 'El monto debe ser un numero' })
debit: number;
/**
* Monto al haber
* @default 0
*/
@IsNumber({}, { message: 'El monto debe ser un numero' })
credit: number;
/**
* Descripcion de la linea
*/
@IsOptional()
@IsString()
description?: string;
/**
* Referencia (factura, cheque, etc.)
*/
@IsOptional()
@IsString()
@MaxLength(100)
reference?: string;
}
/**
* DTO para crear una poliza contable
*/
export class CreateAccountingEntryDto {
/**
* Fecha de la poliza (YYYY-MM-DD)
*/
@IsDateString({}, { message: 'Formato de fecha invalido' })
@IsNotEmpty({ message: 'La fecha es requerida' })
entryDate: string;
/**
* Tipo de poliza
* @example 'ingreso'
*/
@IsString()
@IsNotEmpty({ message: 'El tipo es requerido' })
@MaxLength(50)
entryType: string;
/**
* Descripcion de la poliza
* @example 'Pago de estimacion #15'
*/
@IsString()
@IsNotEmpty({ message: 'La descripcion es requerida' })
description: string;
/**
* Referencia externa
* @example 'EST-2026-015'
*/
@IsOptional()
@IsString()
@MaxLength(100)
reference?: string;
/**
* ID del proyecto relacionado
*/
@IsOptional()
@IsUUID('4', { message: 'projectId debe ser un UUID valido' })
projectId?: string;
/**
* Lineas de la poliza (minimo 2)
*/
@IsArray()
@ValidateNested({ each: true })
@ArrayMinSize(2, { message: 'La poliza debe tener al menos 2 lineas' })
@Type(() => AccountingEntryLineDto)
lines: AccountingEntryLineDto[];
}

View File

@ -0,0 +1,215 @@
/**
* CreateBankAccountDto - DTO para crear cuentas bancarias
*
* @module Finance
* @rf RF-MAE014-001
*/
import {
IsString,
IsNotEmpty,
IsOptional,
IsUUID,
IsEnum,
IsNumber,
IsBoolean,
MaxLength,
MinLength,
Matches,
Min,
IsObject,
} from 'class-validator';
import { BankAccountType, BankAccountStatus } from '../entities/bank-account.entity';
export class CreateBankAccountDto {
/**
* Nombre descriptivo de la cuenta
* @example 'Cuenta Principal Banamex'
*/
@IsString()
@IsNotEmpty({ message: 'El nombre es requerido' })
@MinLength(3, { message: 'El nombre debe tener al menos 3 caracteres' })
@MaxLength(100, { message: 'El nombre no puede exceder 100 caracteres' })
name: string;
/**
* Numero de cuenta bancaria
* @example '1234567890'
*/
@IsString()
@IsNotEmpty({ message: 'El numero de cuenta es requerido' })
@MaxLength(50, { message: 'El numero de cuenta no puede exceder 50 caracteres' })
accountNumber: string;
/**
* CLABE interbancaria (18 digitos)
* @example '012345678901234567'
*/
@IsOptional()
@IsString()
@Matches(/^\d{18}$/, { message: 'La CLABE debe tener exactamente 18 digitos' })
clabe?: string;
/**
* Tipo de cuenta
* @default 'checking'
*/
@IsOptional()
@IsEnum(['checking', 'savings', 'investment', 'credit_line', 'other'], {
message: 'Tipo de cuenta invalido',
})
accountType?: BankAccountType;
/**
* Estado de la cuenta
* @default 'active'
*/
@IsOptional()
@IsEnum(['active', 'inactive', 'blocked', 'closed'], {
message: 'Estado invalido',
})
status?: BankAccountStatus;
/**
* Nombre del banco
* @example 'Banamex'
*/
@IsString()
@IsNotEmpty({ message: 'El nombre del banco es requerido' })
@MaxLength(100, { message: 'El nombre del banco no puede exceder 100 caracteres' })
bankName: string;
/**
* Codigo del banco
* @example '002'
*/
@IsOptional()
@IsString()
@MaxLength(10)
bankCode?: string;
/**
* Nombre de la sucursal
*/
@IsOptional()
@IsString()
@MaxLength(100)
branchName?: string;
/**
* Codigo de la sucursal
*/
@IsOptional()
@IsString()
@MaxLength(20)
branchCode?: string;
/**
* Moneda de la cuenta (ISO 4217)
* @default 'MXN'
*/
@IsOptional()
@IsString()
@Matches(/^[A-Z]{3}$/, { message: 'La moneda debe ser un codigo ISO de 3 letras' })
currency?: string;
/**
* Saldo inicial de la cuenta
* @default 0
*/
@IsOptional()
@IsNumber({}, { message: 'El saldo inicial debe ser un numero' })
initialBalance?: number;
/**
* Limite de credito (para lineas de credito)
*/
@IsOptional()
@IsNumber({}, { message: 'El limite de credito debe ser un numero' })
@Min(0, { message: 'El limite de credito no puede ser negativo' })
creditLimit?: number;
/**
* Saldo minimo requerido
*/
@IsOptional()
@IsNumber({}, { message: 'El saldo minimo debe ser un numero' })
@Min(0, { message: 'El saldo minimo no puede ser negativo' })
minimumBalance?: number;
/**
* ID del proyecto asociado (si es cuenta especifica)
*/
@IsOptional()
@IsUUID('4', { message: 'projectId debe ser un UUID valido' })
projectId?: string;
/**
* ID de la cuenta contable vinculada
*/
@IsOptional()
@IsUUID('4', { message: 'ledgerAccountId debe ser un UUID valido' })
ledgerAccountId?: string;
/**
* Nombre del contacto en el banco
*/
@IsOptional()
@IsString()
@MaxLength(255)
bankContactName?: string;
/**
* Telefono del contacto en el banco
*/
@IsOptional()
@IsString()
@MaxLength(50)
bankContactPhone?: string;
/**
* Email del contacto en el banco
*/
@IsOptional()
@IsString()
@MaxLength(255)
bankContactEmail?: string;
/**
* Indica si es la cuenta por defecto
* @default false
*/
@IsOptional()
@IsBoolean()
isDefault?: boolean;
/**
* Permite realizar pagos
* @default true
*/
@IsOptional()
@IsBoolean()
allowsPayments?: boolean;
/**
* Permite recibir cobros
* @default true
*/
@IsOptional()
@IsBoolean()
allowsCollections?: boolean;
/**
* Notas adicionales
*/
@IsOptional()
@IsString()
notes?: string;
/**
* Metadatos adicionales (JSON)
*/
@IsOptional()
@IsObject()
metadata?: Record<string, any>;
}

View File

@ -0,0 +1,13 @@
/**
* Finance DTOs - Barrel Export
*
* @module Finance
*/
// Bank Account
export * from './create-bank-account.dto';
export * from './update-bank-account.dto';
export * from './bank-account-response.dto';
// Accounting Entry
export * from './create-accounting-entry.dto';

View File

@ -0,0 +1,116 @@
/**
* UpdateBankAccountDto - DTO para actualizar cuentas bancarias
*
* @module Finance
*/
import {
IsString,
IsOptional,
IsUUID,
IsEnum,
IsNumber,
IsBoolean,
MaxLength,
MinLength,
Min,
IsObject,
} from 'class-validator';
import { BankAccountType, BankAccountStatus } from '../entities/bank-account.entity';
/**
* DTO para actualizar una cuenta bancaria.
* accountNumber y clabe no se pueden cambiar.
*/
export class UpdateBankAccountDto {
@IsOptional()
@IsString()
@MinLength(3, { message: 'El nombre debe tener al menos 3 caracteres' })
@MaxLength(100, { message: 'El nombre no puede exceder 100 caracteres' })
name?: string;
@IsOptional()
@IsEnum(['checking', 'savings', 'investment', 'credit_line', 'other'], {
message: 'Tipo de cuenta invalido',
})
accountType?: BankAccountType;
@IsOptional()
@IsEnum(['active', 'inactive', 'blocked', 'closed'], {
message: 'Estado invalido',
})
status?: BankAccountStatus;
@IsOptional()
@IsString()
@MaxLength(100)
bankName?: string;
@IsOptional()
@IsString()
@MaxLength(10)
bankCode?: string;
@IsOptional()
@IsString()
@MaxLength(100)
branchName?: string;
@IsOptional()
@IsString()
@MaxLength(20)
branchCode?: string;
@IsOptional()
@IsNumber({}, { message: 'El limite de credito debe ser un numero' })
@Min(0, { message: 'El limite de credito no puede ser negativo' })
creditLimit?: number;
@IsOptional()
@IsNumber({}, { message: 'El saldo minimo debe ser un numero' })
@Min(0, { message: 'El saldo minimo no puede ser negativo' })
minimumBalance?: number;
@IsOptional()
@IsUUID('4', { message: 'projectId debe ser un UUID valido' })
projectId?: string;
@IsOptional()
@IsUUID('4', { message: 'ledgerAccountId debe ser un UUID valido' })
ledgerAccountId?: string;
@IsOptional()
@IsString()
@MaxLength(255)
bankContactName?: string;
@IsOptional()
@IsString()
@MaxLength(50)
bankContactPhone?: string;
@IsOptional()
@IsString()
@MaxLength(255)
bankContactEmail?: string;
@IsOptional()
@IsBoolean()
isDefault?: boolean;
@IsOptional()
@IsBoolean()
allowsPayments?: boolean;
@IsOptional()
@IsBoolean()
allowsCollections?: boolean;
@IsOptional()
@IsString()
notes?: string;
@IsOptional()
@IsObject()
metadata?: Record<string, any>;
}

View File

@ -0,0 +1,105 @@
/**
* CreateCapacitacionDto - DTO para crear capacitaciones
*
* @module HSE
* @rf RF-MAA017-002
*/
import {
IsString,
IsNotEmpty,
IsOptional,
IsUUID,
IsEnum,
IsInt,
IsBoolean,
MaxLength,
Min,
} from 'class-validator';
/**
* DTO para crear una capacitacion HSE
*/
export class CreateCapacitacionDto {
/**
* Nombre de la capacitacion
* @example 'Trabajo en Alturas NOM-009'
*/
@IsString()
@IsNotEmpty({ message: 'El nombre es requerido' })
@MaxLength(200)
nombre: string;
/**
* Descripcion de la capacitacion
*/
@IsOptional()
@IsString()
descripcion?: string;
/**
* Tipo de capacitacion
* @example 'normativa'
*/
@IsEnum(['induccion', 'especifica', 'normativa', 'reciclaje', 'emergencia'], {
message: 'Tipo de capacitacion invalido',
})
@IsNotEmpty()
tipo: string;
/**
* ID de la norma STPS relacionada
*/
@IsOptional()
@IsUUID('4')
normaStpsId?: string;
/**
* Duracion en horas
* @example 8
*/
@IsInt({ message: 'La duracion debe ser un numero entero' })
@Min(1, { message: 'La duracion minima es 1 hora' })
duracionHoras: number;
/**
* Vigencia en meses (0 = sin vencimiento)
* @default 0
*/
@IsOptional()
@IsInt()
@Min(0)
vigenciaMeses?: number;
/**
* Requiere evaluacion
* @default false
*/
@IsOptional()
@IsBoolean()
requiereEvaluacion?: boolean;
/**
* Calificacion minima para aprobar (0-100)
* @example 80
*/
@IsOptional()
@IsInt()
@Min(0)
calificacionMinima?: number;
/**
* Emite constancia DC-3
* @default false
*/
@IsOptional()
@IsBoolean()
emiteDc3?: boolean;
/**
* ID del instructor por defecto
*/
@IsOptional()
@IsUUID('4')
instructorDefaultId?: string;
}

View File

@ -0,0 +1,91 @@
/**
* CreateEppAsignacionDto - DTO para asignar EPP a empleados
*
* @module HSE
* @rf RF-MAA017-004
*/
import {
IsString,
IsNotEmpty,
IsOptional,
IsUUID,
IsDateString,
IsInt,
MaxLength,
Min,
} from 'class-validator';
/**
* DTO para asignar EPP a un empleado
*/
export class CreateEppAsignacionDto {
/**
* ID del empleado que recibe el EPP
*/
@IsUUID('4', { message: 'empleadoId debe ser un UUID valido' })
@IsNotEmpty({ message: 'El empleado es requerido' })
empleadoId: string;
/**
* ID del articulo EPP del catalogo
*/
@IsUUID('4', { message: 'eppCatalogoId debe ser un UUID valido' })
@IsNotEmpty({ message: 'El articulo EPP es requerido' })
eppCatalogoId: string;
/**
* ID del fraccionamiento/obra
*/
@IsUUID('4', { message: 'fraccionamientoId debe ser un UUID valido' })
@IsNotEmpty({ message: 'El fraccionamiento es requerido' })
fraccionamientoId: string;
/**
* Cantidad asignada
* @example 1
*/
@IsInt({ message: 'La cantidad debe ser un numero entero' })
@Min(1, { message: 'La cantidad minima es 1' })
cantidad: number;
/**
* Fecha de asignacion (YYYY-MM-DD)
* @example '2026-02-03'
*/
@IsDateString({}, { message: 'Formato de fecha invalido' })
@IsNotEmpty({ message: 'La fecha de asignacion es requerida' })
fechaAsignacion: string;
/**
* Fecha de vencimiento del EPP (YYYY-MM-DD)
* @example '2027-02-03'
*/
@IsOptional()
@IsDateString({}, { message: 'Formato de fecha invalido' })
fechaVencimiento?: string;
/**
* Talla del EPP (si aplica)
* @example 'M'
*/
@IsOptional()
@IsString()
@MaxLength(10)
talla?: string;
/**
* Numero de serie o lote
*/
@IsOptional()
@IsString()
@MaxLength(50)
numeroSerie?: string;
/**
* Observaciones de la asignacion
*/
@IsOptional()
@IsString()
observaciones?: string;
}

View File

@ -0,0 +1,142 @@
/**
* CreateIncidenteDto - DTO para crear incidentes de seguridad
*
* @module HSE
* @rf RF-MAA017-001
*/
import {
IsString,
IsNotEmpty,
IsOptional,
IsUUID,
IsDateString,
IsEnum,
IsArray,
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
/**
* DTO para persona involucrada en el incidente
*/
export class InvolucradoDto {
/**
* ID del empleado involucrado
*/
@IsUUID('4', { message: 'empleadoId debe ser un UUID valido' })
@IsNotEmpty()
empleadoId: string;
/**
* Tipo de involucramiento
*/
@IsEnum(['lesionado', 'testigo', 'responsable'], {
message: 'Tipo de involucramiento invalido',
})
tipoInvolucramiento: string;
/**
* Descripcion de lesiones (si aplica)
*/
@IsOptional()
@IsString()
descripcionLesiones?: string;
}
/**
* DTO para crear un incidente de seguridad
*/
export class CreateIncidenteDto {
/**
* Fecha y hora del incidente (ISO 8601)
* @example '2026-02-03T14:30:00Z'
*/
@IsDateString({}, { message: 'Formato de fecha invalido' })
@IsNotEmpty({ message: 'La fecha y hora son requeridas' })
fechaHora: string;
/**
* ID del fraccionamiento/obra donde ocurrio
*/
@IsUUID('4', { message: 'fraccionamientoId debe ser un UUID valido' })
@IsNotEmpty({ message: 'El fraccionamiento es requerido' })
fraccionamientoId: string;
/**
* Descripcion de la ubicacion especifica
* @example 'Zona de excavacion, frente 3'
*/
@IsOptional()
@IsString()
ubicacionDescripcion?: string;
/**
* Coordenadas geograficas (formato WKT)
* @example 'POINT(-100.3899 25.6866)'
*/
@IsOptional()
@IsString()
ubicacionGeo?: string;
/**
* Tipo de incidente
*/
@IsEnum(['accidente', 'incidente', 'casi_accidente'], {
message: 'Tipo de incidente invalido',
})
@IsNotEmpty({ message: 'El tipo es requerido' })
tipo: string;
/**
* Gravedad del incidente
*/
@IsEnum(['leve', 'moderado', 'grave', 'fatal'], {
message: 'Gravedad invalida',
})
@IsNotEmpty({ message: 'La gravedad es requerida' })
gravedad: string;
/**
* Descripcion detallada del incidente
* @example 'Trabajador sufrio corte menor en mano izquierda al manipular lamina'
*/
@IsString()
@IsNotEmpty({ message: 'La descripcion es requerida' })
descripcion: string;
/**
* Causa inmediata identificada
* @example 'Falta de uso de guantes de proteccion'
*/
@IsOptional()
@IsString()
causaInmediata?: string;
/**
* Causa basica/raiz identificada
* @example 'Falta de capacitacion en manejo de materiales'
*/
@IsOptional()
@IsString()
causaBasica?: string;
/**
* Estado inicial del incidente
* @default 'abierto'
*/
@IsOptional()
@IsEnum(['abierto', 'en_investigacion', 'cerrado'], {
message: 'Estado invalido',
})
estado?: string;
/**
* Lista de personas involucradas
*/
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => InvolucradoDto)
involucrados?: InvolucradoDto[];
}

View File

@ -0,0 +1,104 @@
/**
* CreatePermisoTrabajoDto - DTO para crear permisos de trabajo
*
* @module HSE
* @rf RF-MAA017-003
*/
import {
IsString,
IsNotEmpty,
IsOptional,
IsUUID,
IsDateString,
IsArray,
MaxLength,
} from 'class-validator';
/**
* DTO para crear un permiso de trabajo
*/
export class CreatePermisoTrabajoDto {
/**
* ID del tipo de permiso de trabajo
*/
@IsUUID('4', { message: 'tipoPermisoId debe ser un UUID valido' })
@IsNotEmpty({ message: 'El tipo de permiso es requerido' })
tipoPermisoId: string;
/**
* ID del fraccionamiento/obra
*/
@IsUUID('4', { message: 'fraccionamientoId debe ser un UUID valido' })
@IsNotEmpty({ message: 'El fraccionamiento es requerido' })
fraccionamientoId: string;
/**
* Descripcion del trabajo a realizar
* @example 'Soldadura de estructura metalica en nivel 3'
*/
@IsString()
@IsNotEmpty({ message: 'La descripcion del trabajo es requerida' })
descripcionTrabajo: string;
/**
* Ubicacion especifica del trabajo
* @example 'Torre A, Nivel 3, Zona Norte'
*/
@IsString()
@IsNotEmpty({ message: 'La ubicacion es requerida' })
@MaxLength(200)
ubicacion: string;
/**
* Fecha y hora de inicio del permiso (ISO 8601)
* @example '2026-02-03T08:00:00Z'
*/
@IsDateString({}, { message: 'Formato de fecha invalido' })
@IsNotEmpty({ message: 'La fecha de inicio es requerida' })
fechaInicio: string;
/**
* Fecha y hora de fin del permiso (ISO 8601)
* @example '2026-02-03T18:00:00Z'
*/
@IsDateString({}, { message: 'Formato de fecha invalido' })
@IsNotEmpty({ message: 'La fecha de fin es requerida' })
fechaFin: string;
/**
* ID del responsable del trabajo
*/
@IsUUID('4', { message: 'responsableId debe ser un UUID valido' })
@IsNotEmpty({ message: 'El responsable es requerido' })
responsableId: string;
/**
* Riesgos identificados
*/
@IsOptional()
@IsString()
riesgosIdentificados?: string;
/**
* Medidas de control a implementar
*/
@IsOptional()
@IsString()
medidasControl?: string;
/**
* EPP requerido para el trabajo
*/
@IsOptional()
@IsString()
eppRequerido?: string;
/**
* IDs del personal autorizado
*/
@IsOptional()
@IsArray()
@IsUUID('4', { each: true, message: 'Cada ID debe ser un UUID valido' })
personalAutorizado?: string[];
}

View File

@ -0,0 +1,101 @@
/**
* IncidenteResponseDto - DTO de respuesta para incidentes
*
* @module HSE
*/
import { Expose, Type } from 'class-transformer';
/**
* DTO de respuesta para persona involucrada
*/
export class InvolucradoResponseDto {
@Expose()
id: string;
@Expose()
empleadoId: string;
@Expose()
empleadoNombre?: string;
@Expose()
tipoInvolucramiento: string;
@Expose()
descripcionLesiones?: string;
}
/**
* DTO de respuesta para incidentes.
* Usar con class-transformer para serializar respuestas.
*/
export class IncidenteResponseDto {
/** ID del incidente */
@Expose()
id: string;
/** ID del tenant */
@Expose()
tenantId: string;
/** Folio del incidente */
@Expose()
folio: string;
/** Fecha y hora del incidente */
@Expose()
@Type(() => Date)
fechaHora: Date;
/** ID del fraccionamiento */
@Expose()
fraccionamientoId: string;
/** Nombre del fraccionamiento */
@Expose()
fraccionamientoNombre?: string;
/** Ubicacion descriptiva */
@Expose()
ubicacionDescripcion?: string;
/** Tipo de incidente */
@Expose()
tipo: string;
/** Gravedad */
@Expose()
gravedad: string;
/** Descripcion del incidente */
@Expose()
descripcion: string;
/** Causa inmediata */
@Expose()
causaInmediata?: string;
/** Causa basica */
@Expose()
causaBasica?: string;
/** Estado actual */
@Expose()
estado: string;
/** Involucrados */
@Expose()
@Type(() => InvolucradoResponseDto)
involucrados?: InvolucradoResponseDto[];
/** Fecha de creacion */
@Expose()
@Type(() => Date)
createdAt: Date;
/** Fecha de actualizacion */
@Expose()
@Type(() => Date)
updatedAt: Date;
}

View File

@ -0,0 +1,19 @@
/**
* HSE DTOs - Barrel Export
*
* @module HSE
*/
// Incidente
export * from './create-incidente.dto';
export * from './update-incidente.dto';
export * from './incidente-response.dto';
// Capacitacion
export * from './create-capacitacion.dto';
// Permiso de Trabajo
export * from './create-permiso-trabajo.dto';
// EPP Asignacion
export * from './create-epp-asignacion.dto';

View File

@ -0,0 +1,65 @@
/**
* UpdateIncidenteDto - DTO para actualizar incidentes
*
* @module HSE
*/
import {
IsString,
IsOptional,
IsEnum,
IsArray,
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
import { InvolucradoDto } from './create-incidente.dto';
/**
* DTO para actualizar un incidente.
* fraccionamientoId y fechaHora no se pueden cambiar.
*/
export class UpdateIncidenteDto {
@IsOptional()
@IsString()
ubicacionDescripcion?: string;
@IsOptional()
@IsString()
ubicacionGeo?: string;
@IsOptional()
@IsEnum(['accidente', 'incidente', 'casi_accidente'], {
message: 'Tipo de incidente invalido',
})
tipo?: string;
@IsOptional()
@IsEnum(['leve', 'moderado', 'grave', 'fatal'], {
message: 'Gravedad invalida',
})
gravedad?: string;
@IsOptional()
@IsString()
descripcion?: string;
@IsOptional()
@IsString()
causaInmediata?: string;
@IsOptional()
@IsString()
causaBasica?: string;
@IsOptional()
@IsEnum(['abierto', 'en_investigacion', 'cerrado'], {
message: 'Estado invalido',
})
estado?: string;
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => InvolucradoDto)
involucrados?: InvolucradoDto[];
}