diff --git a/src/modules/construction/dto/create-fraccionamiento.dto.ts b/src/modules/construction/dto/create-fraccionamiento.dto.ts new file mode 100644 index 0000000..16420bf --- /dev/null +++ b/src/modules/construction/dto/create-fraccionamiento.dto.ts @@ -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; +} diff --git a/src/modules/construction/dto/create-proyecto.dto.ts b/src/modules/construction/dto/create-proyecto.dto.ts new file mode 100644 index 0000000..2a4e433 --- /dev/null +++ b/src/modules/construction/dto/create-proyecto.dto.ts @@ -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; +} diff --git a/src/modules/construction/dto/index.ts b/src/modules/construction/dto/index.ts new file mode 100644 index 0000000..f1dd3bf --- /dev/null +++ b/src/modules/construction/dto/index.ts @@ -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'; diff --git a/src/modules/construction/dto/proyecto-response.dto.ts b/src/modules/construction/dto/proyecto-response.dto.ts new file mode 100644 index 0000000..0be7bd9 --- /dev/null +++ b/src/modules/construction/dto/proyecto-response.dto.ts @@ -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; +} diff --git a/src/modules/construction/dto/update-fraccionamiento.dto.ts b/src/modules/construction/dto/update-fraccionamiento.dto.ts new file mode 100644 index 0000000..0bb5c2d --- /dev/null +++ b/src/modules/construction/dto/update-fraccionamiento.dto.ts @@ -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; +} diff --git a/src/modules/construction/dto/update-proyecto.dto.ts b/src/modules/construction/dto/update-proyecto.dto.ts new file mode 100644 index 0000000..60f54a6 --- /dev/null +++ b/src/modules/construction/dto/update-proyecto.dto.ts @@ -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; +} diff --git a/src/modules/finance/dto/bank-account-response.dto.ts b/src/modules/finance/dto/bank-account-response.dto.ts new file mode 100644 index 0000000..87db5ae --- /dev/null +++ b/src/modules/finance/dto/bank-account-response.dto.ts @@ -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; +} diff --git a/src/modules/finance/dto/create-accounting-entry.dto.ts b/src/modules/finance/dto/create-accounting-entry.dto.ts new file mode 100644 index 0000000..7b10dcf --- /dev/null +++ b/src/modules/finance/dto/create-accounting-entry.dto.ts @@ -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[]; +} diff --git a/src/modules/finance/dto/create-bank-account.dto.ts b/src/modules/finance/dto/create-bank-account.dto.ts new file mode 100644 index 0000000..1c1e67f --- /dev/null +++ b/src/modules/finance/dto/create-bank-account.dto.ts @@ -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; +} diff --git a/src/modules/finance/dto/index.ts b/src/modules/finance/dto/index.ts new file mode 100644 index 0000000..afe6078 --- /dev/null +++ b/src/modules/finance/dto/index.ts @@ -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'; diff --git a/src/modules/finance/dto/update-bank-account.dto.ts b/src/modules/finance/dto/update-bank-account.dto.ts new file mode 100644 index 0000000..0fe85fc --- /dev/null +++ b/src/modules/finance/dto/update-bank-account.dto.ts @@ -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; +} diff --git a/src/modules/hse/dto/create-capacitacion.dto.ts b/src/modules/hse/dto/create-capacitacion.dto.ts new file mode 100644 index 0000000..c130508 --- /dev/null +++ b/src/modules/hse/dto/create-capacitacion.dto.ts @@ -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; +} diff --git a/src/modules/hse/dto/create-epp-asignacion.dto.ts b/src/modules/hse/dto/create-epp-asignacion.dto.ts new file mode 100644 index 0000000..3c285c0 --- /dev/null +++ b/src/modules/hse/dto/create-epp-asignacion.dto.ts @@ -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; +} diff --git a/src/modules/hse/dto/create-incidente.dto.ts b/src/modules/hse/dto/create-incidente.dto.ts new file mode 100644 index 0000000..6739a1f --- /dev/null +++ b/src/modules/hse/dto/create-incidente.dto.ts @@ -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[]; +} diff --git a/src/modules/hse/dto/create-permiso-trabajo.dto.ts b/src/modules/hse/dto/create-permiso-trabajo.dto.ts new file mode 100644 index 0000000..9c1330b --- /dev/null +++ b/src/modules/hse/dto/create-permiso-trabajo.dto.ts @@ -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[]; +} diff --git a/src/modules/hse/dto/incidente-response.dto.ts b/src/modules/hse/dto/incidente-response.dto.ts new file mode 100644 index 0000000..16ff1e4 --- /dev/null +++ b/src/modules/hse/dto/incidente-response.dto.ts @@ -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; +} diff --git a/src/modules/hse/dto/index.ts b/src/modules/hse/dto/index.ts new file mode 100644 index 0000000..70edfcf --- /dev/null +++ b/src/modules/hse/dto/index.ts @@ -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'; diff --git a/src/modules/hse/dto/update-incidente.dto.ts b/src/modules/hse/dto/update-incidente.dto.ts new file mode 100644 index 0000000..ee35131 --- /dev/null +++ b/src/modules/hse/dto/update-incidente.dto.ts @@ -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[]; +}