erp-construccion/orchestration/directivas/DIRECTIVA-ESTIMACIONES.md

564 lines
14 KiB
Markdown

# Directiva: Estimaciones y Facturación
## Metadatos
| Campo | Valor |
|-------|-------|
| **ID** | DIR-CONST-003 |
| **Versión** | 1.0.0 |
| **Ámbito** | Vertical Construcción |
| **Módulo** | MAI-008 |
| **Estado** | Activa |
## Propósito
Define las reglas y patrones para el proceso de estimaciones de obra, aplicación de anticipos, retenciones y generación de documentos de cobro.
## Alcance
Esta directiva aplica a:
- Estimaciones a clientes
- Estimaciones a subcontratistas
- Anticipos y amortizaciones
- Retenciones (fondo de garantía, IMSS, etc.)
- Generación de reportes y documentos
## Tipos de Estimación
```yaml
tipos:
CLIENTE:
descripcion: Cobro al cliente/desarrollador
flujo: Contratista → Cliente
SUBCONTRATISTA:
descripcion: Pago a subcontratistas
flujo: Subcontratista → Contratista
DESTAJO:
descripcion: Pago a cuadrillas por obra ejecutada
flujo: Trabajadores → Contratista
```
## Reglas Obligatorias
### 1. Estructura de Estimación
```typescript
interface EstimationEntity {
id: string;
tenantId: string;
projectId: string;
// Identificación
number: number; // Número consecutivo
code: string; // EST-{PROJ}-{NNN}
type: EstimationType; // CLIENTE | SUBCONTRATISTA | DESTAJO
period: EstimationPeriod; // Período de corte
// Relaciones
contractId: string | null; // Contrato origen
subcontractId: string | null; // Subcontrato (si aplica)
// Fechas
cutoffDate: Date; // Fecha de corte
createdAt: Date;
submittedAt: Date | null;
approvedAt: Date | null;
// Estado
status: EstimationStatus;
// Montos (todos calculados)
grossAmount: number; // Importe bruto
previousAmount: number; // Estimado anteriormente
currentAmount: number; // Este período
// Deducciones
advanceAmortization: number; // Amortización de anticipo
retentionGuarantee: number; // Fondo de garantía (5-10%)
retentionImss: number; // Retención IMSS (5%)
retentionIsr: number; // Retención ISR (1.25%)
otherDeductions: number; // Otras deducciones
// Neto
netAmount: number; // Importe neto a pagar
// IVA
subtotal: number;
iva: number; // IVA 16%
total: number; // Total con IVA
// Aprobaciones
preparedBy: string;
reviewedBy: string | null;
approvedBy: string | null;
}
type EstimationType = 'CLIENTE' | 'SUBCONTRATISTA' | 'DESTAJO';
type EstimationStatus =
| 'BORRADOR'
| 'EN_REVISION'
| 'OBSERVACIONES'
| 'APROBADA'
| 'RECHAZADA'
| 'FACTURADA'
| 'PAGADA'
| 'CANCELADA';
```
### 2. Detalle de Estimación
```typescript
interface EstimationDetail {
id: string;
tenantId: string;
estimationId: string;
// Concepto
budgetItemId: string; // Concepto de presupuesto
budgetItemCode: string; // Código (01.03.015)
description: string;
unitOfMeasure: string;
// Cantidades
contractedQuantity: number; // Cantidad contratada
previousQuantity: number; // Estimado anterior
currentQuantity: number; // Este período
accumulatedQuantity: number; // Acumulado
remainingQuantity: number; // Pendiente
// Precios
unitPrice: number;
previousAmount: number;
currentAmount: number;
accumulatedAmount: number;
// % de avance
progressPercentage: number;
}
```
### 3. Anticipos y Amortización
```yaml
anticipo:
porcentaje_maximo: 30%
amortizacion:
metodo: PROPORCIONAL
formula: "(monto_estimacion / monto_contrato) * monto_anticipo"
reglas:
- Anticipo se registra al inicio del contrato
- Amortización automática en cada estimación
- No puede exceder el anticipo otorgado
- Saldo de anticipo visible en cada estimación
```
```typescript
interface AdvancePayment {
id: string;
tenantId: string;
contractId: string;
// Monto
amount: number;
percentage: number; // % sobre contrato
// Estado
paidDate: Date;
status: AdvanceStatus;
// Amortización
amortizedAmount: number;
pendingAmount: number;
}
// Cálculo de amortización
function calculateAmortization(
estimationAmount: number,
contractAmount: number,
advanceAmount: number,
alreadyAmortized: number
): number {
const proportionalAmortization =
(estimationAmount / contractAmount) * advanceAmount;
const pendingAdvance = advanceAmount - alreadyAmortized;
// No puede exceder el pendiente
return Math.min(proportionalAmortization, pendingAdvance);
}
```
### 4. Retenciones
```yaml
retenciones:
fondo_garantia:
porcentaje: 5% - 10%
descripcion: Garantía de vicios ocultos
liberacion: 6-12 meses después de finiquito
imss:
porcentaje: 5%
descripcion: Retención IMSS subcontratistas
aplica_a: SUBCONTRATISTA
isr:
porcentaje: 1.25%
descripcion: Retención ISR por servicios
aplica_a: SUBCONTRATISTA
otros:
- Penalizaciones
- Daños a terceros
- Cargos por atraso
```
```typescript
interface RetentionConfig {
tenantId: string;
contractId: string;
// Fondo de garantía
guaranteeFundPercentage: number;
guaranteeFundAccumulated: number;
guaranteeFundReleaseDate: Date | null;
// IMSS (solo subcontratos)
imssRetentionPercentage: number;
// ISR (solo subcontratos)
isrRetentionPercentage: number;
// Otros
otherRetentions: OtherRetention[];
}
// Cálculo de retenciones
function calculateRetentions(
amount: number,
config: RetentionConfig,
type: EstimationType
): RetentionBreakdown {
const guaranteeFund = amount * (config.guaranteeFundPercentage / 100);
let imss = 0;
let isr = 0;
if (type === 'SUBCONTRATISTA') {
imss = amount * (config.imssRetentionPercentage / 100);
isr = amount * (config.isrRetentionPercentage / 100);
}
return {
guaranteeFund,
imss,
isr,
total: guaranteeFund + imss + isr
};
}
```
### 5. Workflow de Aprobación
```yaml
workflow:
estados:
BORRADOR:
acciones: [editar, eliminar, enviar_revision]
permisos: [preparador]
EN_REVISION:
acciones: [aprobar, rechazar, devolver_observaciones]
permisos: [revisor, supervisor]
OBSERVACIONES:
acciones: [editar, enviar_revision]
permisos: [preparador]
APROBADA:
acciones: [generar_factura, cancelar]
permisos: [autorizador]
FACTURADA:
acciones: [registrar_pago]
permisos: [tesoreria]
PAGADA:
acciones: [] # Estado final
permisos: []
niveles_aprobacion:
- nivel: 1
monto_maximo: 100000
aprobador: supervisor_obra
- nivel: 2
monto_maximo: 500000
aprobador: gerente_proyecto
- nivel: 3
monto_maximo: null # Sin límite
aprobador: director_operaciones
```
## Patrones de Implementación
### Schema de Base de Datos
```sql
-- Schema de estimaciones
CREATE SCHEMA IF NOT EXISTS financial_management;
-- Estimaciones
CREATE TABLE financial_management.estimations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID NOT NULL,
project_id UUID NOT NULL,
-- Identificación
number INTEGER NOT NULL,
code VARCHAR(50) NOT NULL,
type VARCHAR(20) NOT NULL,
-- Relaciones
contract_id UUID,
subcontract_id UUID,
-- Período
period_start DATE NOT NULL,
period_end DATE NOT NULL,
cutoff_date DATE NOT NULL,
-- Estado
status VARCHAR(30) NOT NULL DEFAULT 'BORRADOR',
-- Montos
gross_amount DECIMAL(15,2) DEFAULT 0,
previous_amount DECIMAL(15,2) DEFAULT 0,
current_amount DECIMAL(15,2) DEFAULT 0,
-- Deducciones
advance_amortization DECIMAL(15,2) DEFAULT 0,
retention_guarantee DECIMAL(15,2) DEFAULT 0,
retention_imss DECIMAL(15,2) DEFAULT 0,
retention_isr DECIMAL(15,2) DEFAULT 0,
other_deductions DECIMAL(15,2) DEFAULT 0,
-- Totales
net_amount DECIMAL(15,2) DEFAULT 0,
subtotal DECIMAL(15,2) DEFAULT 0,
iva DECIMAL(15,2) DEFAULT 0,
total DECIMAL(15,2) DEFAULT 0,
-- Fechas de flujo
submitted_at TIMESTAMPTZ,
approved_at TIMESTAMPTZ,
invoiced_at TIMESTAMPTZ,
paid_at TIMESTAMPTZ,
-- Responsables
prepared_by UUID NOT NULL,
reviewed_by UUID,
approved_by UUID,
-- Auditoría
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, project_id, number, type)
);
-- Detalle de estimación
CREATE TABLE financial_management.estimation_details (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID NOT NULL,
estimation_id UUID NOT NULL REFERENCES financial_management.estimations(id),
-- Concepto
budget_item_id UUID NOT NULL,
budget_item_code VARCHAR(50) NOT NULL,
description TEXT NOT NULL,
unit_of_measure VARCHAR(20),
-- Cantidades
contracted_quantity DECIMAL(15,4) DEFAULT 0,
previous_quantity DECIMAL(15,4) DEFAULT 0,
current_quantity DECIMAL(15,4) DEFAULT 0,
accumulated_quantity DECIMAL(15,4) DEFAULT 0,
-- Precios
unit_price DECIMAL(15,4) NOT NULL,
previous_amount DECIMAL(15,2) DEFAULT 0,
current_amount DECIMAL(15,2) DEFAULT 0,
accumulated_amount DECIMAL(15,2) DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Índices
CREATE INDEX idx_estimations_tenant ON financial_management.estimations(tenant_id);
CREATE INDEX idx_estimations_project ON financial_management.estimations(project_id);
CREATE INDEX idx_estimations_status ON financial_management.estimations(status);
CREATE INDEX idx_estimation_details_estimation ON financial_management.estimation_details(estimation_id);
-- RLS
ALTER TABLE financial_management.estimations ENABLE ROW LEVEL SECURITY;
ALTER TABLE financial_management.estimation_details ENABLE ROW LEVEL SECURITY;
CREATE POLICY "tenant_isolation" ON financial_management.estimations
USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
CREATE POLICY "tenant_isolation" ON financial_management.estimation_details
USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
```
### Servicio de Estimaciones
```typescript
@Injectable()
export class EstimationService {
constructor(
@InjectRepository(EstimationEntity)
private estimationRepo: Repository<EstimationEntity>,
@InjectRepository(EstimationDetailEntity)
private detailRepo: Repository<EstimationDetailEntity>,
private advanceService: AdvancePaymentService,
private retentionService: RetentionService,
) {}
async createEstimation(
dto: CreateEstimationDto,
tenantId: string,
userId: string
): Promise<EstimationEntity> {
// Generar número consecutivo
const lastNumber = await this.getLastEstimationNumber(
dto.projectId,
dto.type,
tenantId
);
// Calcular montos
const amounts = await this.calculateAmounts(dto, tenantId);
// Calcular deducciones
const deductions = await this.calculateDeductions(
amounts.currentAmount,
dto.contractId,
dto.type,
tenantId
);
const estimation = this.estimationRepo.create({
...dto,
tenantId,
number: lastNumber + 1,
code: this.generateCode(dto.projectId, lastNumber + 1),
...amounts,
...deductions,
netAmount: amounts.currentAmount - deductions.totalDeductions,
subtotal: amounts.currentAmount - deductions.totalDeductions,
iva: (amounts.currentAmount - deductions.totalDeductions) * 0.16,
total: (amounts.currentAmount - deductions.totalDeductions) * 1.16,
preparedBy: userId,
status: 'BORRADOR'
});
return this.estimationRepo.save(estimation);
}
private async calculateDeductions(
amount: number,
contractId: string,
type: EstimationType,
tenantId: string
): Promise<DeductionsResult> {
// Obtener configuración de retenciones
const config = await this.retentionService.getConfig(contractId, tenantId);
// Calcular amortización de anticipo
const amortization = await this.advanceService.calculateAmortization(
amount,
contractId,
tenantId
);
// Calcular retenciones
const retentions = calculateRetentions(amount, config, type);
return {
advanceAmortization: amortization,
retentionGuarantee: retentions.guaranteeFund,
retentionImss: retentions.imss,
retentionIsr: retentions.isr,
otherDeductions: 0,
totalDeductions: amortization + retentions.total
};
}
async submitForReview(
id: string,
tenantId: string,
userId: string
): Promise<EstimationEntity> {
const estimation = await this.findOne(id, tenantId);
if (estimation.status !== 'BORRADOR' && estimation.status !== 'OBSERVACIONES') {
throw new BadRequestException('Solo borradores pueden enviarse a revisión');
}
estimation.status = 'EN_REVISION';
estimation.submittedAt = new Date();
return this.estimationRepo.save(estimation);
}
}
```
## Documentos Generados
```yaml
documentos:
estimacion_pdf:
contenido:
- Datos del proyecto y contrato
- Período de estimación
- Tabla de conceptos con cantidades
- Resumen de montos
- Desglose de deducciones
- Firmas de elaboró, revisó, autorizó
caratula:
contenido:
- Resumen ejecutivo
- Comparativo vs presupuesto
- Gráfica de avance
reporte_acumulado:
contenido:
- Histórico de estimaciones
- Avance acumulado por concepto
- Proyección de cierre
```
## Validaciones Pre-Commit
- [ ] Cálculo correcto de amortización de anticipos
- [ ] Retenciones calculadas según configuración
- [ ] Workflow de aprobación implementado
- [ ] Números consecutivos sin duplicados
- [ ] RLS habilitado en todas las tablas
- [ ] Validación de cantidades vs presupuesto
## Referencias
- Documentación: `/docs/01-fase-alcance-inicial/MAI-008-estimaciones-facturacion/`
- Especificaciones: `/docs/01-fase-alcance-inicial/MAI-008-estimaciones-facturacion/especificaciones/`
- Core directivas: `../../erp-core/orchestration/directivas/`
---
*Directiva específica de Vertical Construcción*