# Workflow: Pagos Anticipados (Down Payments) **Código:** WF-SAL-001 **Versión:** 1.0 **Fecha:** 2025-12-08 **Módulo:** MGN-007 (Ventas Básico) **Referencia:** Odoo sale_advance_payment_inv --- ## 1. Resumen ### 1.1 Propósito Los pagos anticipados (down payments) permiten facturar un porcentaje o monto fijo de una orden de venta antes de la entrega completa. Son esenciales para: - Proyectos de construcción (anticipos por fase) - Ventas de alto valor (garantía de compromiso) - Servicios profesionales (retainer fees) ### 1.2 Tipos de Anticipos | Tipo | Descripción | Ejemplo | |------|-------------|---------| | **Porcentaje** | % del monto total de la orden | 30% de $100,000 = $30,000 | | **Monto Fijo** | Cantidad exacta en moneda | $25,000 de cualquier orden | | **Por Líneas** | Basado en entregas parciales | Entregas facturables | ### 1.3 Flujo General ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ ORDEN VENTA │────▶│ ANTICIPO 30% │────▶│ FACTURA FINAL │ │ Total: $100K │ │ Factura: $30K │ │ $100K - $30K │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ ▼ Saldo: $70K ``` --- ## 2. Modelo de Datos ### 2.1 Campos en Línea de Orden de Venta ```sql ALTER TABLE core_sales.sale_order_lines ADD COLUMN IF NOT EXISTS is_downpayment BOOLEAN NOT NULL DEFAULT FALSE, amount_invoiced DECIMAL(15,2) NOT NULL DEFAULT 0, amount_to_invoice DECIMAL(15,2) GENERATED ALWAYS AS ( CASE WHEN is_downpayment THEN 0 ELSE price_subtotal - amount_invoiced END ) STORED; COMMENT ON COLUMN core_sales.sale_order_lines.is_downpayment IS 'Indica si la línea es un pago anticipado'; COMMENT ON COLUMN core_sales.sale_order_lines.amount_invoiced IS 'Monto ya facturado de esta línea'; COMMENT ON COLUMN core_sales.sale_order_lines.amount_to_invoice IS 'Monto pendiente de facturar'; ``` ### 2.2 Campos en Línea de Factura ```sql ALTER TABLE core_financial.invoice_lines ADD COLUMN IF NOT EXISTS is_downpayment BOOLEAN NOT NULL DEFAULT FALSE, sale_line_id UUID REFERENCES core_sales.sale_order_lines(id); COMMENT ON COLUMN core_financial.invoice_lines.is_downpayment IS 'Indica si la línea es un pago anticipado'; ``` ### 2.3 Campos en Orden de Venta ```sql ALTER TABLE core_sales.sale_orders ADD COLUMN IF NOT EXISTS amount_downpayment DECIMAL(15,2) NOT NULL DEFAULT 0, amount_to_invoice DECIMAL(15,2) GENERATED ALWAYS AS ( amount_total - amount_downpayment ) STORED; COMMENT ON COLUMN core_sales.sale_orders.amount_downpayment IS 'Suma de anticipos facturados y confirmados'; ``` ### 2.4 Configuración de Cuenta Contable ```sql -- En categoría de producto ALTER TABLE core_inventory.product_categories ADD COLUMN IF NOT EXISTS property_account_downpayment_id UUID REFERENCES core_financial.accounts(id); COMMENT ON COLUMN core_inventory.product_categories.property_account_downpayment_id IS 'Cuenta contable para registrar anticipos (pasivo diferido)'; ``` --- ## 3. Diagrama de Flujo ``` ┌─────────────────────────────────────────────────────────────────────┐ │ ORDEN DE VENTA CONFIRMADA │ │ state = 'sale' | amount_total = $100,000 │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ CREAR ANTICIPO (Wizard) │ │ Tipo: Porcentaje (30%) o Monto Fijo ($30,000) │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ CREAR LÍNEA DE ANTICIPO EN SO │ │ is_downpayment = true | display_type = 'line_section' │ │ price_unit = $30,000 | product_qty = 1 │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ CREAR FACTURA DE ANTICIPO │ │ type = 'out_invoice' | amount_total = $30,000 │ │ line.is_downpayment = true │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ CONFIRMAR FACTURA DE ANTICIPO │ │ Asiento contable: │ │ Débito: Cuentas por Cobrar $30,000 │ │ Crédito: Anticipo de Clientes $30,000 │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ REGISTRAR PAGO DEL ANTICIPO │ │ Débito: Banco $30,000 │ │ Crédito: Cuentas por Cobrar $30,000 │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ ENTREGAR PRODUCTOS/SERVICIOS (Picking completado) │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ CREAR FACTURA FINAL (final=true) │ │ Incluye deducción automática de anticipos │ │ │ │ Líneas de producto: $100,000 │ │ (-) Deducción de anticipos: -$30,000 │ │ ────────────────────────────────────────────────────────── │ │ TOTAL A COBRAR: $70,000 │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ CONFIRMAR FACTURA FINAL │ │ Asiento contable: │ │ Débito: Cuentas por Cobrar $70,000 │ │ Débito: Anticipo de Clientes $30,000 (reversión) │ │ Crédito: Ingresos por Ventas $100,000 │ └─────────────────────────────────────────────────────────────────────┘ ``` --- ## 4. Reglas de Negocio ### 4.1 Validaciones de Creación | ID | Regla | Descripción | Acción | |----|-------|-------------|--------| | RN-DP-001 | Monto positivo | El anticipo debe ser > 0 | Error | | RN-DP-002 | No exceder total | Anticipo <= monto pendiente | Warning | | RN-DP-003 | SO confirmada | Solo crear anticipos en SO confirmada | Error | | RN-DP-004 | Porcentaje válido | Entre 0 y 100% | Error | ### 4.2 Cálculo de Montos ```typescript interface DownPaymentCalculation { type: 'percentage' | 'fixed'; value: number; // Porcentaje (0-100) o monto fijo orderTotal: number; // Total de la orden previousDownpayments: number; // Anticipos previos facturados } function calculateDownPayment(calc: DownPaymentCalculation): number { const availableAmount = calc.orderTotal - calc.previousDownpayments; let downPaymentAmount: number; if (calc.type === 'percentage') { downPaymentAmount = (calc.orderTotal * calc.value) / 100; } else { downPaymentAmount = calc.value; } // Validar que no exceda lo disponible if (downPaymentAmount > availableAmount) { console.warn(`Anticipo (${downPaymentAmount}) excede disponible (${availableAmount})`); } return Math.min(downPaymentAmount, availableAmount); } ``` ### 4.3 Manejo de Impuestos ```typescript interface TaxHandling { // Impuestos proporcionales (IVA %) percentageTaxes: Tax[]; // Impuestos fijos (monto por unidad) fixedTaxes: Tax[]; } function distributeDownPaymentTaxes( amount: number, orderLines: SaleOrderLine[] ): DownPaymentLine[] { const result: DownPaymentLine[] = []; // Agrupar líneas por combinación de impuestos const taxGroups = groupByTaxes(orderLines); for (const [taxes, lines] of taxGroups) { // Calcular proporción de este grupo const groupTotal = sumPriceSubtotal(lines); const orderTotal = sumPriceSubtotal(orderLines); const ratio = groupTotal / orderTotal; // Calcular monto de anticipo para este grupo const groupAmount = amount * ratio; // Separar impuestos fijos (se crean como líneas separadas) const percentTaxes = taxes.filter(t => t.amountType !== 'fixed'); const fixedTaxes = taxes.filter(t => t.amountType === 'fixed'); // Crear línea de anticipo con impuestos proporcionales result.push({ amount: groupAmount, taxes: percentTaxes, isDownpayment: true, }); // Crear líneas separadas para impuestos fijos for (const fixedTax of fixedTaxes) { result.push({ amount: fixedTax.amount * ratio, taxes: [], isDownpayment: true, description: `Tax: ${fixedTax.name}`, }); } } return result; } ``` --- ## 5. Contabilidad de Anticipos ### 5.1 Asientos Contables **Factura de Anticipo (30% de $100,000):** ``` FECHA: 2025-01-15 DESCRIPCIÓN: Anticipo 30% - Orden OV-2025-001 Débito: 1200 Cuentas por Cobrar $34,800 ($30,000 + IVA) Crédito: 2100 Anticipos de Clientes $30,000 Crédito: 2110 IVA por Pagar $4,800 ``` **Pago del Anticipo:** ``` FECHA: 2025-01-20 DESCRIPCIÓN: Pago anticipo - Factura FA-2025-001 Débito: 1000 Banco $34,800 Crédito: 1200 Cuentas por Cobrar $34,800 ``` **Factura Final (con deducción):** ``` FECHA: 2025-03-01 DESCRIPCIÓN: Factura final - Orden OV-2025-001 Débito: 1200 Cuentas por Cobrar $81,200 ($70,000 + IVA) Débito: 2100 Anticipos de Clientes $30,000 (reversión) Crédito: 4000 Ingresos por Ventas $100,000 Crédito: 2110 IVA por Pagar $11,200 (IVA neto) ``` ### 5.2 Cuentas Contables | Cuenta | Tipo | Descripción | |--------|------|-------------| | 2100 Anticipos de Clientes | Pasivo | Obligación de entregar producto/servicio | | 4000 Ingresos por Ventas | Ingreso | Se reconoce al entregar | | 1200 Cuentas por Cobrar | Activo | Derecho de cobro | --- ## 6. Casos de Uso ### 6.1 UC-DP-001: Crear Anticipo por Porcentaje **Actor:** Vendedor **Precondición:** Orden de venta confirmada **Flujo Principal:** 1. Usuario abre orden de venta confirmada 2. Usuario selecciona "Crear Factura" → "Anticipo" 3. Sistema muestra wizard de anticipo 4. Usuario selecciona "Porcentaje" e ingresa 30% 5. Sistema calcula: $100,000 × 30% = $30,000 6. Sistema crea línea de anticipo en SO 7. Sistema crea factura de anticipo 8. Usuario confirma factura **Postcondición:** - amount_downpayment = $30,000 - amount_to_invoice = $70,000 ### 6.2 UC-DP-002: Crear Anticipo por Monto Fijo **Actor:** Vendedor **Precondición:** Orden de venta confirmada **Flujo Principal:** 1. Usuario abre orden de venta confirmada 2. Usuario selecciona "Crear Factura" → "Anticipo" 3. Usuario selecciona "Monto Fijo" e ingresa $25,000 4. Sistema valida: $25,000 <= $100,000 ✓ 5. Sistema crea línea y factura de anticipo 6. Usuario confirma factura ### 6.3 UC-DP-003: Factura Final con Deducción **Actor:** Vendedor **Precondición:** Anticipo facturado y pagado, productos entregados **Flujo Principal:** 1. Usuario abre orden de venta 2. Usuario selecciona "Crear Factura" → "Factura Final" 3. Sistema marca opción "Deducir anticipos" (por defecto) 4. Sistema genera factura con: - Líneas de productos: $100,000 - Sección "Anticipos": -$30,000 - Total: $70,000 5. Usuario confirma factura final ### 6.4 UC-DP-004: Múltiples Anticipos **Escenario:** Proyecto de construcción con 3 fases ``` Fase 1: Anticipo inicial (30%) - Monto: $30,000 - Factura: FA-001 Fase 2: Anticipo intermedio (40%) - Monto: $40,000 - Factura: FA-002 Fase 3: Factura final (30%) - Total orden: $100,000 - Anticipos: -$70,000 - Saldo: $30,000 - Factura: FA-003 ``` --- ## 7. API REST ### 7.1 Endpoints ```yaml # Crear anticipo POST /api/v1/sale-orders/{id}/create-downpayment Authorization: Bearer {token} Request: { "type": "percentage", # 'percentage' | 'fixed' "value": 30, # Porcentaje o monto "deduct_from_final": true } Response: { "success": true, "data": { "invoice_id": "uuid", "amount": 30000.00, "tax_amount": 4800.00, "total": 34800.00, "so_line_id": "uuid" } } # Obtener estado de anticipos GET /api/v1/sale-orders/{id}/downpayments Authorization: Bearer {token} Response: { "order_total": 100000.00, "amount_downpayment": 30000.00, "amount_to_invoice": 70000.00, "downpayments": [ { "invoice_id": "uuid", "invoice_number": "FA-2025-001", "amount": 30000.00, "status": "paid", "created_at": "2025-01-15" } ] } # Crear factura final POST /api/v1/sale-orders/{id}/create-final-invoice Authorization: Bearer {token} Request: { "deduct_downpayments": true, "consolidated_billing": true } Response: { "success": true, "data": { "invoice_id": "uuid", "product_total": 100000.00, "downpayment_deduction": -30000.00, "net_total": 70000.00, "tax_amount": 11200.00, "grand_total": 81200.00 } } # Cancelar anticipo DELETE /api/v1/invoices/{id} Authorization: Bearer {token} # Nota: Al cancelar factura de anticipo, se elimina la línea de SO correspondiente ``` ### 7.2 Permisos | Endpoint | Permiso Requerido | |----------|-------------------| | POST create-downpayment | `sales:invoice` | | GET downpayments | `sales:read` | | POST create-final-invoice | `sales:invoice` | | DELETE invoice (anticipo) | `sales:invoice`, `accounting:cancel` | --- ## 8. Integración con INFONAVIT ### 8.1 Escenario Específico En proyectos INFONAVIT, los pagos se estructuran por fases: ``` PROYECTO DE VIVIENDA: $1,000,000 MXN Fase 1 - Escrituración (30%): $300,000 ├── Anticipo al firmar contrato └── Factura FA-001 Fase 2 - Avance 50% (50%): $500,000 ├── Al alcanzar 50% de avance físico └── Factura FA-002 Fase 3 - Entrega (20%): $200,000 ├── Al entregar vivienda terminada └── Factura FA-003 (final con deducciones) ``` ### 8.2 Configuración Recomendada ```typescript const infonavitConfig = { // Porcentajes por fase phases: [ { name: 'Escrituración', percentage: 30 }, { name: 'Avance 50%', percentage: 50 }, { name: 'Entrega', percentage: 20 }, ], // Cuenta de anticipos específica downpaymentAccountCode: '2100.01', // Anticipos INFONAVIT // Validaciones adicionales requirePhysicalProgress: true, // Requiere % avance para fase 2 requireDeliveryConfirmation: true, // Requiere entrega para fase 3 }; ``` --- ## 9. Manejo de Errores y Edge Cases ### 9.1 Cancelación de Anticipo ```typescript async function cancelDownpaymentInvoice(invoiceId: string): Promise { const invoice = await this.findById(invoiceId); // Verificar que es factura de anticipo const hasDownpaymentLines = invoice.lines.some(l => l.isDownpayment); if (!hasDownpaymentLines) { throw new Error('La factura no contiene anticipos'); } // Verificar que no hay factura final const saleOrderId = invoice.lines[0].saleLineId?.orderId; const finalInvoice = await this.findFinalInvoice(saleOrderId); if (finalInvoice) { throw new Error('No se puede cancelar anticipo con factura final existente'); } // Cancelar factura await this.cancelInvoice(invoiceId); // Eliminar líneas de anticipo de la SO await this.saleOrderLineService.deleteDownpaymentLines(saleOrderId, invoiceId); // Recalcular amount_downpayment await this.saleOrderService.recalculateDownpayments(saleOrderId); } ``` ### 9.2 Cambio de Monto de Orden Si el monto de la orden cambia después de crear anticipos: ```typescript function validateOrderChange( order: SaleOrder, newTotal: number ): ValidationResult { const totalDownpayments = order.amountDownpayment; if (newTotal < totalDownpayments) { return { valid: false, error: `El nuevo total (${newTotal}) es menor que los anticipos existentes (${totalDownpayments})` }; } return { valid: true }; } ``` --- ## 10. Reportes ### 10.1 Reporte de Anticipos Pendientes ```sql SELECT so.name AS order_number, so.partner_id, p.name AS customer_name, so.amount_total AS order_total, so.amount_downpayment, so.amount_to_invoice AS pending_amount, (so.amount_downpayment / so.amount_total * 100) AS downpayment_percent FROM core_sales.sale_orders so JOIN core_catalogs.partners p ON p.id = so.partner_id WHERE so.state IN ('sale', 'done') AND so.amount_to_invoice > 0 ORDER BY so.date_order DESC; ``` ### 10.2 KPIs de Anticipos | KPI | Fórmula | Meta | |-----|---------|------| | Tasa de cobranza de anticipos | Anticipos cobrados / Anticipos facturados | > 95% | | Tiempo promedio de cobro | Días desde factura hasta pago | < 15 días | | Anticipos por fase | Distribución por tipo de fase | Según política | --- ## 11. Referencias - **Odoo sale_make_invoice_advance.py:** Wizard de creación - **Odoo sale_order.py:** _create_invoices() - **MGN-007:** Módulo Ventas Básico - **MGN-004:** Módulo Financiero (facturas) - **WF-FIN-001:** Cierre de Período Contable --- **Documento creado por:** Requirements-Analyst **Fecha:** 2025-12-08