564 lines
14 KiB
Markdown
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*
|