erp-core/docs/04-modelado/workflows/WORKFLOW-PAGOS-ANTICIPADOS.md

21 KiB
Raw Permalink Blame History

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

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

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

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

-- 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

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

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

# 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

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

async function cancelDownpaymentInvoice(invoiceId: string): Promise<void> {
  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:

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

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