FactuGES_ERP/docs/catalogs/payment-terms.md
david cf07de4836 feat: add proforma customer change evaluation utility
- Implemented `evaluateProformaCustomerChange` function to assess changes in customer details for proforma invoices.
- Introduced types for customer change details, blockers, field differences, and fiscal targets.
- Added utility functions for normalizing text and formatting recipient information.

test: add unit tests for customer payment resolver

- Created tests for `CustomerPaymentResolver` to validate payment term retrieval and handling of inactive terms.
- Ensured proper behavior when clearing payment terms and handling non-existent terms.

test: add unit tests for customer default payment term resolver

- Developed tests for `CustomerDefaultPaymentTermResolver` to verify default payment term resolution logic.
- Included scenarios for creating customers with and without explicit payment term IDs.

feat: implement customer default payment term resolver

- Created `CustomerDefaultPaymentTermResolver` to manage default payment term resolution during customer creation.
- Integrated with company finder and payment resolver services.

test: add unit tests for create customer use case

- Implemented tests for `CreateCustomerUseCase` to ensure correct handling of payment term IDs during customer creation.
- Validated behavior when no explicit payment term ID is provided.

test: add unit tests for update customer use case

- Developed tests for `UpdateCustomerUseCase` to verify payment term handling during customer updates.
- Ensured that inactive payment terms are not assigned and that explicit null values are respected.

feat: add dependency injection for customer company services

- Implemented DI for customer company dependencies to facilitate service resolution.

test: add tests for customer create data utility

- Created tests for `buildCustomerCreateData` to validate payment term handling based on user input.
2026-08-17 11:25:34 +02:00

10 KiB

Plazos de pago

Objetivo

Este documento resume cómo funciona Payment Terms en el ERP y qué reglas debe respetar cada módulo.

La cadena funcional final es:

Company.defaultPaymentTermId
        ↓ valor inicial
Customer.paymentTermId
        ↓ valor inicial
Proforma.paymentTermId
        ↓ snapshot al emitir
IssuedInvoice.paymentTerm

Modelo general

PaymentTerm define una plantilla de vencimientos.

Sus dueRules describen la distribución teórica del plazo, por ejemplo:

  • 100% a 30 días
  • 50% contado y 50% a 30 días

Estas reglas no crean todavía vencimientos reales, cuotas monetarias ni estado de cobro. Eso queda fuera de alcance hasta un futuro módulo de pagos y cobros.

Referencia viva y snapshot

Entidad Modelo
Company referencia viva
Customer referencia viva
Proforma referencia viva
Issued Invoice snapshot histórico

La diferencia es simple:

  • Company, Customer y Proforma siguen usando configuración viva.
  • Issued Invoice debe conservar exactamente lo que se emitió.

Empresa

Company tiene:

defaultPaymentTermId: string | null

Es una referencia viva al catálogo de Payment Terms.

Reglas:

  • puede ser null;
  • puede apuntar a un PaymentTerm activo de la misma empresa;
  • no puede apuntar a otra empresa;
  • isSystem está permitido.

Cambiar el default de Company:

  • no modifica Customers existentes;
  • no modifica Proformas existentes.

Solo afecta a futuros valores iniciales.

Desactivación del default

Si un PaymentTerm está configurado como Company.defaultPaymentTermId, no puede desactivarse.

Antes hay que:

  • cambiar el default;
  • o limpiarlo.

Esto evita que la empresa conserve un default que ya no podría reutilizar en nuevas asignaciones.

Clientes

Customer tiene:

paymentTermId: string | null

Es una referencia viva propia.

Create

Semántica:

  • campo omitido o undefined -> aplicar Company.defaultPaymentTermId;
  • null explícito -> crear el Customer sin plazo;
  • id -> usar ese PaymentTerm si existe, pertenece a la misma empresa y está activo.

Una vez creado el Customer, su paymentTermId pasa a ser propio.

Cambiar después el default de Company no modifica ese Customer.

Update

Semántica:

  • undefined -> no cambiar;
  • null -> limpiar;
  • id -> asignar si es activo y de la misma empresa.

Inactivos

Si un PaymentTerm ya asignado al Customer se desactiva después:

  • el Customer puede conservarlo;
  • no puede volver a seleccionarse como nueva asignación.

Quick create

El quick create no necesita pedir plazo de pago al usuario.

Si no envía un valor explícito, el backend aplica la misma regla que en el alta completa:

Company.defaultPaymentTermId

No debe haber reglas distintas entre:

  • create completo;
  • quick create;
  • API directa.

Proformas

Proforma.paymentTermId es una referencia viva.

No es un snapshot.

Mientras la Proforma no se haya emitido, cualquier cambio posterior en la definición del catálogo afecta a la lectura actual de ese plazo.

Create

Prioridad de valor inicial:

  1. Customer.paymentTermId
  2. Company.defaultPaymentTermId
  3. null

Reglas:

  • si el usuario selecciona manualmente otro plazo, ese valor prevalece;
  • null explícito significa “sin plazo de pago”;
  • una selección manual de null no debe reactivar después un default automático.

Update

Semántica:

  • undefined -> no cambiar;
  • null -> limpiar;
  • id -> nueva asignación válida.

Si la Proforma ya tenía un PaymentTerm que luego quedó inactivo:

  • puede conservarlo;
  • puede emitirse;
  • no pueden elegirse nuevos inactivos.

Cambio de Customer en una Proforma

Cambiar el Customer de una Proforma existente siempre requiere confirmación.

No basta con cambiar customerId. Antes hay que cargar el Customer completo y evaluar compatibilidad.

Moneda

Si:

newCustomer.currencyCode !== proforma.currencyCode

el cambio se bloquea.

No se convierte moneda ni se reinterpretan importes.

Idioma

Si:

newCustomer.languageCode !== proforma.languageCode

el cambio también se bloquea.

Idioma y moneda se consideran características estructurales del documento.

Estados del modal

Incompatible:

  • no puede confirmarse;
  • debe informarse del motivo.

Compatible:

  • informa qué condiciones se adaptarán;
  • informa qué valores se conservarán.

Plazo de pago al cambiar Customer

Política:

REFRESH_IF_NOT_OVERRIDDEN

Si el usuario no lo modificó manualmente:

newCustomer.paymentTermId
-> Company.defaultPaymentTermId
-> null

Si lo modificó manualmente:

  • se conserva el valor actual.

Forma de pago al cambiar Customer

La forma de pago usa la misma política:

REFRESH_IF_NOT_OVERRIDDEN

No se añade un default de Company para PaymentMethod porque esa regla no existe en este flujo.

Dirty y override manual

Hay dos conceptos distintos:

  • cambio pendiente de guardar;
  • override manual.

Ejemplo:

  • Customer A -> plazo 30 días
  • cambio a Customer B -> se adapta automáticamente a 60 días

Ese cambio:

  • debe guardarse;
  • no cuenta como override manual.

Si luego cambia a Customer C, puede volver a adaptarse.

En cambio, si el usuario selecciona manualmente “Contado”:

  • sí existe override manual;
  • futuros cambios de Customer no deben sobrescribir ese valor.

La misma explicación aplica a:

  • paymentTermId;
  • paymentMethodId.

Fiscalidad

La fiscalidad no se cambia silenciosamente.

Si el nuevo Customer tiene una configuración fiscal distinta:

  • el modal debe advertirlo;
  • el usuario debe confirmarlo;
  • al confirmar, se adapta la configuración fiscal;
  • se recalculan impuestos y totales con la infraestructura fiscal existente.

La existencia de líneas no bloquea por sí sola el cambio.

Recipient

Si el cambio de Customer se confirma, el recipient snapshot de la Proforma siempre se refresca con el nuevo Customer.

Nunca debe quedar un documento con:

customerId = Customer B
recipient = Customer A

El preview y el PDF usan ese snapshot documental.

Duplicación de Proformas

Duplicar una Proforma crea un documento nuevo. Por eso no puede reutilizar una nueva asignación inactiva solo porque el original la conservaba.

Original con Payment Term activo

  • se copia el mismo paymentTermId.

Original con Payment Term inactivo

No se copia como nueva asignación.

Se resuelve así:

  1. Customer.paymentTermId, solo si está activo;
  2. Company.defaultPaymentTermId, solo si está activo;
  3. null.

Reglas adicionales:

  • si el Customer tiene un término inactivo, se ignora;
  • si la Company tiene por datos legacy un default inactivo, no se usa;
  • la duplicación no falla por esa inconsistencia legacy.

Feedback de duplicación

Si el plazo original estaba inactivo y hubo que sustituirlo o eliminarlo, el usuario recibe feedback después de duplicar.

La respuesta expone metadata funcional equivalente a:

payment_term_adaptation:
  null
  o
  {
    source: "inactive_original",
    resolution: "customer" | "company" | "none"
  }

La intención de esa metadata es explicar si el sistema:

  • conservó el mismo plazo;
  • usó el del Customer;
  • usó el default de Company;
  • o creó la nueva Proforma sin plazo.

Facturas emitidas

Issued Invoice no mantiene una referencia viva para interpretar el plazo.

Al emitir una Proforma, el sistema guarda un snapshot histórico.

Shape conceptual:

paymentTerm = {
  sourceId,
  name,
  description,
  dueRules
}

Qué se conserva

  • payment_term_id para trazabilidad;
  • payment_term_name como snapshot;
  • payment_term_description como snapshot;
  • payment_term_snapshot_json con las reglas congeladas.

Los porcentajes conservan precisión escalada. No se convierten a float para persistir el histórico.

Inmutabilidad histórica

Ejemplo:

  • una factura se emite con “30 días” y 100% a 30 días;
  • después el catálogo cambia a “30 / 60 días” con 50% / 50%.

La factura emitida sigue mostrando:

  • “30 días”;
  • 100% a 30 días.

El detail y el reporting de Issued Invoice no consultan el catálogo vivo para reconstruir ese plazo.

Payment Term inactivo en emisión

Si una Proforma ya tenía asignado un PaymentTerm y después se desactiva:

  • la Proforma puede emitirse.

La inactividad impide nuevas asignaciones. No invalida una referencia ya existente en la Proforma.

Referencia perdida en emisión

Si la Proforma tiene paymentTermId pero al emitir no puede resolverse dentro de la misma empresa:

  • la emisión falla.

No se crea una factura sin snapshot de forma silenciosa y tampoco se usa un fallback nuevo desde Customer o Company.

Activos e inactivos

Contexto Puede conservar inactivo Puede asignar inactivo
Company default NO NO
Customer SÍ NO
Proforma SÍ NO
Issued Invoice snapshot histórico N/A

Nota:

Company.defaultPaymentTermId no puede quedar inactivo porque la desactivación se bloquea.

Aislamiento por empresa

Toda nueva asignación o resolución debe quedar limitada a la empresa activa.

Aplica a:

  • Company default;
  • Customer create;
  • Customer update;
  • Proforma create;
  • Proforma update;
  • emisión;
  • duplicación.

isSystem

isSystem afecta a la gestión del catálogo.

No impide usar un PaymentTerm en:

  • Company default;
  • Customer;
  • Proforma;
  • emisión

si el término es válido para esa operación.

Reporting

En reporting:

  • Proforma usa el PaymentTerm vivo;
  • Issued Invoice usa el snapshot histórico.

Además, para seleccionar templates FastReport:

es-ES -> es

Qué no está incluido todavía

Este feature no implementa todavía:

  • dueDate real;
  • calendario de cobros;
  • cuotas monetarias;
  • estado pagado o pendiente;
  • pagos parciales;
  • conciliación.

No debe confundirse dueRules de PaymentTerm con vencimientos reales de una factura.

Ejemplos funcionales

Ejemplo 1: Customer sin plazo

  • Company default = 30 días
  • Customer create no envía paymentTermId

Resultado:

  • el Customer nace con 30 días.

Ejemplo 2: Customer sin plazo explícito

  • Company default = 30 días
  • Customer create envía paymentTermId = null

Resultado:

  • el Customer nace sin plazo.

Ejemplo 3: Proforma con override manual

  • Customer A = 30 días
  • cambio a Customer B = 60 días
  • el sistema adapta automáticamente a 60 días
  • el usuario elige manualmente “Contado”
  • cambio a Customer C

Resultado:

  • la Proforma conserva “Contado”.