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

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