21 KiB
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:
- Usuario abre orden de venta confirmada
- Usuario selecciona "Crear Factura" → "Anticipo"
- Sistema muestra wizard de anticipo
- Usuario selecciona "Porcentaje" e ingresa 30%
- Sistema calcula: $100,000 × 30% = $30,000
- Sistema crea línea de anticipo en SO
- Sistema crea factura de anticipo
- 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:
- Usuario abre orden de venta confirmada
- Usuario selecciona "Crear Factura" → "Anticipo"
- Usuario selecciona "Monto Fijo" e ingresa $25,000
- Sistema valida: $25,000 <= $100,000 ✓
- Sistema crea línea y factura de anticipo
- 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:
- Usuario abre orden de venta
- Usuario selecciona "Crear Factura" → "Factura Final"
- Sistema marca opción "Deducir anticipos" (por defecto)
- Sistema genera factura con:
- Líneas de productos: $100,000
- Sección "Anticipos": -$30,000
- Total: $70,000
- 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