El módulo Collections es la traducción operativa del ciclo financiero: ejecuta la cobranza de las ventas, registra cada pago (en caja o en línea), genera las cuotas conforme se cobran, aplica el reparto interés/capital definido por Financing, calcula y cobra la mora por atraso, maneja pagos parciales y cuotas equivalentes, y liquida la venta cuando todos los saldos llegan a cero. Es la interfaz entre el dinero real que entra y el estado contable de cada plan de Sales.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/DevCarlosRodriguez/Documentacion-Arccnova-2.0/llms.txt
Use this file to discover all available pages before exploring further.
Collections ejecuta el cobro; Financing calcula la matemática. Estos dos módulos son independientes. Los webhooks de pago en línea (Stripe) desembocan en el mismo motor de cuotas que el cobro en caja — las reglas son idénticas en ambos canales.
Modelo de dominio
| Entidad | Significado | Campos clave |
|---|---|---|
| Collection (Cobro) | El acto de cobrar: total esperado y total recibido. | Folio, FinancingTypeId, SaleId, ClientId, AmountReceived, TotalToPay, Change, Arrears_Amount, CollectionStatusId, IsMultiplePayment, PaymentDate |
| MultipleInstallments (Cuota) | La cuota generada de un plan. | InstallmentNumber, SaleId, FinancingTypeId, PaymentAmount, IsPaymentCompleted, LastRemainingAmout, InterestPayment, PricipalPayment, ExtraPrincipalPayment, MissingPayment, RealInterestPayment, RealPrincipalPayment, LastRemainingInterest, BalanceWithInterest, NextPaymentDate, GracePeriodDueDate |
| CollectionInstallment | Tabla puente cobro ↔ cuota: cuánto de un cobro fue a una cuota. | Amount, InterestPaid, PrincipalPaid, ExtraPrincipal |
| ConceptCollection | Conceptos cobrados junto a la cuota; la mora es un concepto. | ConceptId, Amount, IsCharged, EquivelentCharge, IsDelinquentPayer |
| MultiplePayment | Un cobro puede cubrirse con varias formas de pago. | Método, monto, tipo de cambio, referencia |
| VoucherImage | Comprobantes adjuntos del cobro. | Referencia al almacenamiento en nube (Files) |
Collection agrupa las formas de pago (MultiplePayment) y los conceptos (ConceptCollection, incluida la mora). La cuota (MultipleInstallments) vive por plan de la venta. La tabla puente CollectionInstallment registra exactamente cuánto de ese cobro fue a interés, a capital y a abono extra de cada cuota.
Los 10 invariantes del motor
Estas reglas son inviolables y se validan en código en cada operación de cobranza:Ver los 10 invariantes completos
Ver los 10 invariantes completos
| # | Invariante |
|---|---|
| 1 | Las cuotas nunca se crean anticipadas. La cuota N nace al cobrarse; la N+1 se genera cuando se cobra la N. Las 81 cuotas de un plan no existen hasta pagarse. |
| 2 | Sin brincos. Si la cuota actual no está liquidada (incluida su mora), no se avanza a la siguiente. |
| 3 | Solo se puede eliminar la última cuota generada. Nunca una cuota que tenga cuotas posteriores. |
| 4 | De una cuota pagada solo se puede actualizar el método de pago, validando que no exista cuota posterior. |
| 5 | Los comprobantes adjuntos sí se pueden actualizar (subir o eliminar) en cualquier momento. |
| 6 | No se cobra una venta liquidada. IsSettled = true o estado Liquidado = error. |
| 7 | No se aplica abono extra a capital al cubrir dos cuotas en el mismo cobro. El extra solo aplica cuando InstallmentEquivalent = 1. |
| 8 | No se pagan parcialidades en un cobro de cuota equivalente a dos. Si InstallmentEquivalent > 1 y el monto recibido es menor al total, el sistema devuelve error. |
| 9 | Pago incompleto = parcialidad. La cuota queda IsPaymentCompleted = false y no avanza hasta cubrir el faltante más su mora. |
| 10 | Solo se cobra sobre ventas en estado Conforme. |
Flujos
Calcular el próximo cobro — ValidateNextCollection
Antes de registrar un pago, el sistema determina qué cobrar:
Cargar el plan
Se identifica el plan por venta y tipo de financiamiento. Si
IsSettled = true, error: la venta ya está liquidada.Determinar la cuota actual
- No existe ninguna cuota → cuota número 1; fecha base =
PaymentFirstDate(o su gracia si aplica). - Existe cuota completa → próxima es N+1; monto = saldo + interés remanente, con tope al
PaymentByPeriod(así la última cuota cobra el saldo exacto sin superar el importe periódico). - Existe cuota incompleta → sigue en N; monto =
MissingPayment.
Calcular mora (si aplica)
Si la fecha actual supera el último día permitido para pagar y el plan tiene porcentaje de mora
> 0:- Se calculan los periodos vencidos (sección Mora más abajo).
- Se restan los periodos ya cobrados (
IsDelinquentPayer/EquivelentCharge) para no duplicar. - Se agrega el concepto de mora al cobro propuesto.
Registrar un cobro — SaveCollectionBySale
Folio de recibo
Si el cobro es identificado, se incrementa el correlativo del tipo de recibo (
ReceiptType.Correlative++). Este folio es único por proyecto y tipo de recibo.Insertar el cobro
Se graba el
Collection con TotalToPay, AmountReceived, cambio (Change) y mora (Arrears_Amount).Procesar formas de pago y conceptos
Se insertan o actualizan los registros de
MultiplePayment y ConceptCollection (incluida la mora si corresponde).Ajuste del monto al financiamiento:Procesar la cuota por cada venta
- Si
InstallmentEquivalent > 1yAmountReceived < TotalToPay→ error (invariante #8). - Si existe cuota N incompleta →
UpdateInstallmentsPartialPayment(pago parcial). - Si no existe cuota N →
SaveInstallmentsPayment(genera nueva cuota).
Subir comprobantes
Los archivos adjuntos se suben al servicio de almacenamiento; en BD solo se guarda la referencia.
Pago parcial — UpdateInstallmentsPartialPayment
Se invoca cuando ya existe una cuota N incompleta y llega un pago adicional sobre ella:
- Solo sobre ventas Conforme.
- Si hay extra a capital y
InstallmentEquivalent = 1: el extra reduce el saldo. - Se acumula:
PaymentAmount += PaymentByPeriod. MissingPayment = PaymentByPeriod_plan − PaymentAmount.- Si
MissingPayment = 0: la cuota se completa,NextPaymentDateavanza a la cuota N+1 y se calculaGracePeriodDueDate. - Si
MissingPayment > 0: la cuota sigue incompleta y no avanza. - Se reparte interés/capital y, si el saldo llega a ≤ 0, se liquida el plan y se recalcula el estado de la venta (
SettleSale).
Generar cuota N — SaveInstallmentsPayment
Validaciones previas:
- Saldo
> 0. - El número de cuota es exactamente el siguiente consecutivo.
- La cuota N no existe ya.
- La venta está en estado Conforme.
| Escenario | Comportamiento |
|---|---|
Pago completo (pago ≥ PaymentByPeriod) | IsPaymentCompleted = true; se calcula NextPaymentDate (formula de Financing §4.4) y GracePeriodDueDate; el saldo nuevo se persiste. |
Pago parcial (pago < PaymentByPeriod) | Si pago < InterestPayment: todo va a interés, el capital queda en 0 y el saldo no cambia. Si pago ≥ InterestPayment: el resto va a capital. NextPaymentDate no avanza; la gracia queda la del plan. |
Extra a capital (solo si Equivalent = 1) | Si extra > saldo restante → error. El extra reduce el saldo y eventualmente el número de cuotas. |
| Saldo ≤ 0 | NextPaymentDate = null, GracePeriodDueDate = null, el plan se marca IsSettled = true. Se llama a SettleSale: si todos los planes están liquidados, la venta pasa a estado Liquidado y el asesor puede descargar la hoja de liquidación. |
Mora
El cálculo de mora sigue estas reglas canónicas (C23 segunda ronda):Condición de mora
La mora se activa cuando
hoy > fecha_límite. La fecha límite es la fecha de vencimiento de la cuota más los días de gracia (si el plan tiene GracePeriod > 0). Sin gracia, la fecha límite es la de vencimiento normal.Penalidad por periodo
Periodos vencidos
Al pasar la fecha límite hay 1 periodo vencido. Cada periodo completo adicional de la modalidad suma otro. Ejemplo: cuota vence el 11/05, hoy es 11/06 → 1 periodo (la siguiente cuota vence justo el 11/06); al pasar esa fecha → 2 periodos.
Cargo de mora
ConceptCollection con IsDelinquentPayer = true y EquivelentCharge = n_periodos_cobrados, evitando duplicar mora.Días de gracia y festivos
Los días de gracia se cuentan en días hábiles de la inmobiliaria: lunes a sábado, saltando domingos y festivos (catálogo de días festivos del tenant). El sábado sí cuenta como día hábil. Ejemplo cerrado de la fuente:Cuota vence sábado 15 → 3 días de gracia → lunes 17 es festivo →
Se saltan: domingo 16 y lunes 17 → cuentan: 18, 19, 20
Último día para pagar sin mora: día 20. La mora corre desde el día 21.
Cobros no identificados
Un cobro no identificado ocurre cuando llega un depósito (por ejemplo por WhatsApp) y la inmobiliaria no sabe a qué cliente corresponde:- El pago se registra como cobro no identificado y aparece en la alerta de pendientes.
- La persona responsable lo identifica: selecciona el tipo de financiamiento, busca al cliente y distribuye el monto entre sus ventas/cuotas.
- El monto distribuido debe ser exactamente igual al monto recibido (
faltante = 0). - Al confirmar, el cobro cambia a estado Identificado y queda ligado a las cuotas, igual que si se hubiera capturado por el flujo normal.
El contador y la lista de cobros no identificados se actualizan en tiempo real mediante SSE sin recargar la página (decisión 19, RT-34). Este evento genera notificación por sistema y por correo electrónico (T05).
Eliminación autorizada de cobros
La eliminación de cobros es una acción protegida (C30, RT-09 a RT-11):- Requiere el permiso ELIMINAR COBROS más la contraseña de acción, o ser CEO / Admin.
- Se debe ingresar un motivo de eliminación.
| Escenario | Comportamiento |
|---|---|
| Eliminar el pago de una cuota | Solo si no hay cuotas posteriores. Soft-delete de la cuota y la relación CollectionInstallment; se eliminan las formas de pago del cobro. |
| Eliminar un cobro completo | Por cada cuota relacionada, valida que no exista N+1. Si la cuota había liquidado el plan, revierte la liquidación (la venta vuelve a Conforme). Si la cuota tiene más de un cobro, revierte los montos parciales y la cuota sigue viva. Si no, soft-delete de la cuota. Luego soft-delete de conceptos, pagos y cobro. |
Liquidación
CuandoSettleSale detecta que todos los planes de la venta tienen IsSettled = true y LastRemainingAmout ≤ 0:
- La venta transiciona automáticamente a estado Liquidado.
- El asesor puede descargar la hoja de liquidación (documento legal generado en back, RT-30).
- A partir de este punto, Collections rechaza nuevos cobros sobre esa venta (invariante #6).
Matriz de permisos
| Recurso | Acciones estándar | Acciones protegidas |
|---|---|---|
| Cobros | Leer, Crear, Actualizar | Eliminar cobros (contraseña de acción) |
| Créditos | Leer, Cambiar fecha de pago | — |
| Conceptos de mora | — | Editar, Eliminar (C33) |
Puntos abiertos
Decisiones pendientes
Decisiones pendientes
- Tabla de cuotas materializada vs. bajo demanda — El v1 genera cada cuota al cobrarla. El v2 puede materializar la tabla completa al crear el plan. Las reglas de este módulo aplican igual en ambos diseños. Decide el arquitecto (Flag 2 del cierre).
- Cobro no identificado sin reclamar — La fuente indica que el pago “queda en la alerta” sin caducidad definida. Política de cierre o archivo de pagos huérfanos pendiente de definición (C29).
- Rescisión de contrato por impago — Es una decisión administrativa de la inmobiliaria, fuera del sistema. El sistema solo aporta la carta de cobranza extrajudicial (módulo Reports, C47).
-
Autorización de
EDITAR_CONCEPTO_POR_MORA/ELIMINAR_CONCEPTO_POR_MORA— Los casos exactos en que se solicita el código de autorización para operar sobre conceptos de mora están pendientes de confirmar (C33).