import { TipoDocumentoFiscal } from '../value-objects/FiscalEnums.js'; import { ResolucionAgotadaException } from '../exceptions/ValidacionFiscalException.js'; export interface ResolucionDianProps { id?: string; tenantId?: string; resolutionNumber: string; prefix: string; fromNumber: bigint; toNumber: bigint; currentConsecutive: bigint; technicalKey: string; startDate: Date; endDate: Date; isActive?: boolean; documentType?: TipoDocumentoFiscal; } export class ResolucionDian { public readonly id?: string; public readonly tenantId?: string; public readonly resolutionNumber: string; public readonly prefix: string; public readonly fromNumber: bigint; public readonly toNumber: bigint; private _currentConsecutive: bigint; public readonly technicalKey: string; public readonly startDate: Date; public readonly endDate: Date; public readonly isActive: boolean; public readonly documentType: TipoDocumentoFiscal; constructor(props: ResolucionDianProps) { this.id = props.id; this.tenantId = props.tenantId; this.resolutionNumber = props.resolutionNumber.trim(); this.prefix = props.prefix.trim().toUpperCase(); this.fromNumber = BigInt(props.fromNumber); this.toNumber = BigInt(props.toNumber); this._currentConsecutive = BigInt(props.currentConsecutive); this.technicalKey = props.technicalKey.trim(); this.startDate = props.startDate; this.endDate = props.endDate; this.isActive = props.isActive ?? true; this.documentType = props.documentType || TipoDocumentoFiscal.FACTURA_VENTA; this.validarInvariantes(); } private validarInvariantes(): void { if (this.fromNumber > this.toNumber) { throw new Error('El rango inicial no puede ser mayor que el rango final de la resolución.'); } } public get currentConsecutive(): bigint { return this._currentConsecutive; } public estaVigente(fechaReferencia: Date = new Date()): boolean { if (!this.isActive) return false; return fechaReferencia >= this.startDate && fechaReferencia <= this.endDate; } public tieneNumerosDisponibles(): boolean { return this._currentConsecutive < this.toNumber; } public obtenerSiguienteConsecutivo(): bigint { if (!this.estaVigente()) { throw new ResolucionAgotadaException( `La resolución ${this.resolutionNumber} (Prefijo: ${this.prefix}) ha expirado o se encuentra inactiva.` ); } if (!this.tieneNumerosDisponibles()) { throw new ResolucionAgotadaException( `Rango de numeración agotado para la resolución ${this.resolutionNumber} (Prefijo: ${this.prefix}). Rango límite: ${this.toNumber}` ); } this._currentConsecutive += 1n; return this._currentConsecutive; } public formatearNumeroCompleto(consecutivo: bigint): string { return this.prefix ? `${this.prefix}-${consecutivo}` : `${consecutivo}`; } public porcentajeConsumido(): number { const total = Number(this.toNumber - this.fromNumber + 1n); if (total <= 0) return 100; const usados = Number(this._currentConsecutive - this.fromNumber + 1n); const porcentaje = Math.min(100, Math.max(0, (usados / total) * 100)); return Math.round(porcentaje * 10) / 10; } public diasRestantesVigencia(ahora: Date = new Date()): number { const diffMs = this.endDate.getTime() - ahora.getTime(); return Math.max(0, Math.ceil(diffMs / (1000 * 60 * 60 * 24))); } }