495 lines
10 KiB
Markdown
495 lines
10 KiB
Markdown
|
|
# 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:
|
||
|
|
|
||
|
|
```text
|
||
|
|
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:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
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:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
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:
|
||
|
|
|
||
|
|
```text
|
||
|
|
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:
|
||
|
|
|
||
|
|
```text
|
||
|
|
newCustomer.currencyCode !== proforma.currencyCode
|
||
|
|
```
|
||
|
|
|
||
|
|
el cambio se bloquea.
|
||
|
|
|
||
|
|
No se convierte moneda ni se reinterpretan importes.
|
||
|
|
|
||
|
|
### Idioma
|
||
|
|
|
||
|
|
Si:
|
||
|
|
|
||
|
|
```text
|
||
|
|
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:
|
||
|
|
|
||
|
|
```text
|
||
|
|
REFRESH_IF_NOT_OVERRIDDEN
|
||
|
|
```
|
||
|
|
|
||
|
|
Si el usuario no lo modificó manualmente:
|
||
|
|
|
||
|
|
```text
|
||
|
|
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:
|
||
|
|
|
||
|
|
```text
|
||
|
|
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:
|
||
|
|
|
||
|
|
```text
|
||
|
|
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:
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
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:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
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:
|
||
|
|
|
||
|
|
```text
|
||
|
|
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”.
|