Skip to content

Instantly share code, notes, and snippets.

@jluisflo
Created January 14, 2026 19:34
Show Gist options
  • Select an option

  • Save jluisflo/de286b4b3852f66f2ac7cb1f69ab7e74 to your computer and use it in GitHub Desktop.

Select an option

Save jluisflo/de286b4b3852f66f2ac7cb1f69ab7e74 to your computer and use it in GitHub Desktop.
Guía: Simulación de Eventos de Vencimiento en Fineract (LoanRepaymentDueBusinessEvent / LoanRepaymentOverdueBusinessEvent)

Guía: Simulación de Eventos de Vencimiento en Fineract

Esta guía explica cómo simular los eventos LoanRepaymentDueBusinessEvent y LoanRepaymentOverdueBusinessEvent en un ambiente de desarrollo sin modificar la fecha del sistema.


Índice

  1. Resumen de Eventos
  2. Configuración del Producto
  3. Condiciones de Emisión
  4. Métodos de Simulación
  5. Comandos Útiles
  6. Reemisión de Eventos
  7. Troubleshooting

Resumen de Eventos

Evento Propósito Cuándo se Emite
LoanRepaymentDueBusinessEvent Notificar que una cuota está próxima a vencer N días ANTES del vencimiento
LoanRepaymentOverdueBusinessEvent Notificar que una cuota está vencida N días DESPUÉS del vencimiento

Ambos eventos se emiten durante la ejecución del COB (Close of Business) Job.


Configuración del Producto

Parámetros Relevantes

{
  "dueDaysForRepaymentEvent": 1,      // Días ANTES del vencimiento para emitir LoanRepaymentDueBusinessEvent
  "overDueDaysForRepaymentEvent": 1   // Días DESPUÉS del vencimiento para emitir LoanRepaymentOverdueBusinessEvent
}

Consultar Configuración del Producto

curl -s -X GET 'https://<FINERACT_URL>/fineract-provider/api/v1/loanproducts/<PRODUCT_ID>' \
  -H 'Content-Type: application/json' \
  -H 'Fineract-Platform-TenantId: default' \
  -u '<USER>:<PASSWORD>' | jq '{
    id,
    name,
    dueDaysForRepaymentEvent,
    overDueDaysForRepaymentEvent,
    numberOfRepayments,
    repaymentEvery,
    repaymentFrequencyType
  }'

Configuración Global (Alternativa)

Si el producto no tiene configuración específica, se usan los valores globales:

-- Días antes del vencimiento
SELECT * FROM c_configuration WHERE name = 'days-before-repayment-is-due';

-- Días después del vencimiento
SELECT * FROM c_configuration WHERE name = 'days-after-repayment-is-overdue';

Condiciones de Emisión

LoanRepaymentDueBusinessEvent

Se emite cuando TODAS las siguientes condiciones se cumplen:

dueDate - dueDaysForRepaymentEvent == businessDate
AND loan.status NOT IN (SUBMITTED, APPROVED, REJECTED, WITHDRAWN)
AND loan.totalOutstanding > 0
AND installment.totalOutstanding > 0

Archivo fuente: CheckLoanRepaymentDueBusinessStep.java:79-85

LoanRepaymentOverdueBusinessEvent

Se emite cuando TODAS las siguientes condiciones se cumplen:

dueDate + overDueDaysForRepaymentEvent == businessDate
AND installment.totalOutstanding > 0

Archivo fuente: CheckLoanRepaymentOverdueBusinessStep.java:83-86


Métodos de Simulación

Método 1: Crear Préstamo con Fechas Específicas (Recomendado)

Crear un préstamo con fecha de desembolso en el pasado para que la fecha de vencimiento coincida con la fórmula.

Ejemplo Práctico

Supuestos:

  • Hoy: 14 enero 2026
  • Producto: dueDaysForRepaymentEvent = 1, overDueDaysForRepaymentEvent = 1
  • Plazo primera cuota: 30 días
Evento Fecha Vencimiento Necesaria Fecha Desembolso
LoanRepaymentDueBusinessEvent 15 enero 2026 (hoy + 1) 16 diciembre 2025
LoanRepaymentOverdueBusinessEvent 13 enero 2026 (hoy - 1) 14 diciembre 2025

Fórmulas:

  • Due Event: desembolso = hoy + 1 - plazo_cuota
  • Overdue Event: desembolso = hoy - 1 - plazo_cuota

Método 2: Usar Business Date API

Fineract tiene un concepto de "Business Date" separado de la fecha del sistema.

Paso 1: Habilitar Business Date

curl -X PUT 'https://<FINERACT_URL>/fineract-provider/api/v1/configurations/enable_business_date' \
  -H 'Content-Type: application/json' \
  -H 'Fineract-Platform-TenantId: default' \
  -u '<USER>:<PASSWORD>' \
  -d '{"enabled": true}'

Paso 2: Cambiar la Fecha de Negocio

curl -X POST 'https://<FINERACT_URL>/fineract-provider/api/v1/businessdate' \
  -H 'Content-Type: application/json' \
  -H 'Fineract-Platform-TenantId: default' \
  -u '<USER>:<PASSWORD>' \
  -d '{
    "type": "BUSINESS_DATE",
    "date": "15 January 2026",
    "dateFormat": "dd MMMM yyyy",
    "locale": "en"
  }'

Paso 3: Ejecutar Inline COB

curl -X POST 'https://<FINERACT_URL>/fineract-provider/api/v1/jobs/LOAN_COB/inline' \
  -H 'Content-Type: application/json' \
  -H 'Fineract-Platform-TenantId: default' \
  -u '<USER>:<PASSWORD>' \
  -d '{"loanIds": [<LOAN_ID>]}'

Paso 4: Restaurar (Opcional)

curl -X PUT 'https://<FINERACT_URL>/fineract-provider/api/v1/configurations/enable_business_date' \
  -H 'Content-Type: application/json' \
  -H 'Fineract-Platform-TenantId: default' \
  -u '<USER>:<PASSWORD>' \
  -d '{"enabled": false}'

Método 3: Ajustar Configuración de Días

Modificar temporalmente los días de anticipación/atraso para que coincidan con un préstamo existente.

# Para LoanRepaymentDueBusinessEvent
curl -X PUT 'https://<FINERACT_URL>/fineract-provider/api/v1/configurations/days-before-repayment-is-due' \
  -H 'Content-Type: application/json' \
  -H 'Fineract-Platform-TenantId: default' \
  -u '<USER>:<PASSWORD>' \
  -d '{"enabled": true, "value": <DIAS>}'

# Para LoanRepaymentOverdueBusinessEvent
curl -X PUT 'https://<FINERACT_URL>/fineract-provider/api/v1/configurations/days-after-repayment-is-overdue' \
  -H 'Content-Type: application/json' \
  -H 'Fineract-Platform-TenantId: default' \
  -u '<USER>:<PASSWORD>' \
  -d '{"enabled": true, "value": <DIAS>}'

Comandos Útiles

Ejecutar Inline COB

# Para un préstamo
curl -X POST 'https://<FINERACT_URL>/fineract-provider/api/v1/jobs/LOAN_COB/inline' \
  -H 'Content-Type: application/json' \
  -H 'Fineract-Platform-TenantId: default' \
  -u '<USER>:<PASSWORD>' \
  -d '{"loanIds": [849]}'

# Para múltiples préstamos
curl -X POST 'https://<FINERACT_URL>/fineract-provider/api/v1/jobs/LOAN_COB/inline' \
  -H 'Content-Type: application/json' \
  -H 'Fineract-Platform-TenantId: default' \
  -u '<USER>:<PASSWORD>' \
  -d '{"loanIds": [849, 850, 851]}'

Consultar Préstamo con Schedule

curl -s -X GET 'https://<FINERACT_URL>/fineract-provider/api/v1/loans/<LOAN_ID>?associations=repaymentSchedule' \
  -H 'Content-Type: application/json' \
  -H 'Fineract-Platform-TenantId: default' \
  -u '<USER>:<PASSWORD>' | jq '{
    id,
    status,
    timeline,
    periods: [.repaymentSchedule.periods[] | select(.period) | {
      period,
      dueDate,
      totalOutstandingForPeriod,
      complete
    }]
  }'

Consultar Business Date Actual

curl -s -X GET 'https://<FINERACT_URL>/fineract-provider/api/v1/businessdate' \
  -H 'Content-Type: application/json' \
  -H 'Fineract-Platform-TenantId: default' \
  -u '<USER>:<PASSWORD>'

Reemisión de Eventos

Si ya ejecutaste el COB y necesitas reemitir el evento para el mismo préstamo y fecha, debes modificar el campo last_closed_business_date en la base de datos.

¿Por qué?

El COB solo procesa préstamos donde:

last_closed_business_date < cob_business_date OR last_closed_business_date IS NULL

Pasos para Reemitir

1. Verificar Estado Actual

SELECT id, account_no, last_closed_business_date
FROM m_loan
WHERE id = <LOAN_ID>;

2. Retroceder la Fecha del COB

-- Retroceder un día
UPDATE m_loan
SET last_closed_business_date = DATE_SUB(last_closed_business_date, INTERVAL 1 DAY)
WHERE id = <LOAN_ID>;

-- O establecer una fecha específica
UPDATE m_loan
SET last_closed_business_date = '2026-01-13'
WHERE id = <LOAN_ID>;

-- O establecer NULL para forzar procesamiento completo
UPDATE m_loan
SET last_closed_business_date = NULL
WHERE id = <LOAN_ID>;

3. Ejecutar Inline COB

curl -X POST 'https://<FINERACT_URL>/fineract-provider/api/v1/jobs/LOAN_COB/inline' \
  -H 'Content-Type: application/json' \
  -H 'Fineract-Platform-TenantId: default' \
  -u '<USER>:<PASSWORD>' \
  -d '{"loanIds": [<LOAN_ID>]}'

Troubleshooting

El evento no se emite

Verificar:

  1. Configuración del producto:

    curl ... /loanproducts/<ID> | jq '{dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent}'
  2. Fechas del préstamo:

    • La fecha de vencimiento debe coincidir con la fórmula
    • El préstamo debe estar en estado ACTIVE
    • La cuota debe tener saldo pendiente (totalOutstanding > 0)
  3. Eventos externos habilitados:

    SELECT * FROM m_external_event_configuration
    WHERE type IN ('LoanRepaymentDueBusinessEvent', 'LoanRepaymentOverdueBusinessEvent');
  4. Business Steps configurados:

    SELECT * FROM m_batch_business_steps
    WHERE job_name = 'LOAN_CLOSE_OF_BUSINESS'
    AND step_name IN ('CHECK_LOAN_REPAYMENT_DUE', 'CHECK_LOAN_REPAYMENT_OVERDUE');

El COB no procesa el préstamo

Verificar last_closed_business_date:

SELECT id, last_closed_business_date FROM m_loan WHERE id = <LOAN_ID>;

Si ya está actualizada a la fecha actual, retrocederla como se indica en Reemisión de Eventos.

Verificar si hay locks

SELECT * FROM m_loan_account_locks WHERE loan_id = <LOAN_ID>;

Si hay un lock, puede ser necesario eliminarlo:

DELETE FROM m_loan_account_locks WHERE loan_id = <LOAN_ID>;

Diagrama de Flujo

                         ┌──────────────────────┐
                         │  COB Job / Inline    │
                         │  LOAN_CLOSE_OF_BUSINESS│
                         └──────────┬───────────┘
                                    │
                    ┌───────────────┴───────────────┐
                    │                               │
                    ▼                               ▼
        ┌─────────────────────┐         ┌─────────────────────┐
        │ CHECK_REPAYMENT_DUE │         │CHECK_REPAYMENT_OVERDUE│
        │                     │         │                     │
        │ dueDate - N = today │         │ dueDate + N = today │
        └──────────┬──────────┘         └──────────┬──────────┘
                   │                               │
                   ▼                               ▼
        ┌─────────────────────┐         ┌─────────────────────┐
        │LoanRepaymentDue     │         │LoanRepaymentOverdue │
        │BusinessEvent        │         │BusinessEvent        │
        └──────────┬──────────┘         └──────────┬──────────┘
                   │                               │
                   └───────────────┬───────────────┘
                                   │
                                   ▼
                         ┌──────────────────────┐
                         │   ActiveMQ / Kafka   │
                         └──────────────────────┘

Referencias

Archivos de Código

Archivo Descripción
CheckLoanRepaymentDueBusinessStep.java Lógica de emisión del evento Due
CheckLoanRepaymentOverdueBusinessStep.java Lógica de emisión del evento Overdue
InlineLoanCOBExecutorServiceImpl.java Servicio de ejecución del Inline COB
AbstractLoanItemProcessor.java Procesador que actualiza lastClosedBusinessDate
Loan.java Entidad con el campo lastClosedBusinessDate

Tablas de Base de Datos

Tabla Descripción
m_loan Préstamos (contiene last_closed_business_date)
m_loan_repayment_schedule Calendario de pagos
c_configuration Configuración global
m_external_event_configuration Habilitación de eventos externos
m_batch_business_steps Steps del COB configurados
m_loan_account_locks Locks de préstamos durante COB

Ejemplo Completo

Escenario

  • Fecha actual: 14 enero 2026
  • Producto ID: 8
  • Configuración: dueDaysForRepaymentEvent = 1, overDueDaysForRepaymentEvent = 1
  • Plazo primera cuota: 30 días

Para Emitir LoanRepaymentDueBusinessEvent

  1. Crear préstamo con desembolso el 16 diciembre 2025
  2. Primera cuota vence el 15 enero 2026
  3. El evento se emite el 14 enero 2026 (hoy)
# Ejecutar COB
curl -X POST 'https://credits-fineract-dev.dev.n1co.dev/fineract-provider/api/v1/jobs/LOAN_COB/inline' \
  -H 'Content-Type: application/json' \
  -H 'Fineract-Platform-TenantId: default' \
  -u 'jluis:N1cocreditos$2025' \
  -d '{"loanIds": [<NEW_LOAN_ID>]}'

Para Emitir LoanRepaymentOverdueBusinessEvent

  1. Crear préstamo con desembolso el 14 diciembre 2025
  2. Primera cuota vence el 13 enero 2026
  3. El evento se emite el 14 enero 2026 (hoy)

Última actualización: Enero 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment