# 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”.