Skip to content

Instantly share code, notes, and snippets.

@jluisflo
Created January 6, 2026 19:44
Show Gist options
  • Select an option

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

Select an option

Save jluisflo/4c3a8b658db6f865bb005d0cc059ce70 to your computer and use it in GitHub Desktop.
Fineract Business Events para Vencimientos y Mora de Préstamos

Fineract Business Events para Vencimientos y Mora

Resumen

Fineract emite eventos de negocio via Kafka/ActiveMQ para notificar cambios en el ciclo de vida de préstamos. Este documento detalla los eventos relevantes para:

  • Próximo a vencer: Recordatorios antes del vencimiento
  • Vencido: Alertas post-vencimiento
  • En mora: Clasificación y cambios de estado de morosidad

Eventos Disponibles

Evento Propósito Trigger
LoanRepaymentDueBusinessEvent N días antes del vencimiento COB Job
LoanRepaymentOverdueBusinessEvent N días después del vencimiento COB Job
LoanDelinquencyRangeChangeBusinessEvent Cambio en clasificación de mora COB Job / Transacciones

1. LoanRepaymentDueBusinessEvent

Propósito: Notificar que una cuota vencerá en N días.

Configuración

-- Habilitar evento (días de anticipación)
UPDATE c_configuration
SET enabled = true, value = 3  -- 3 días antes
WHERE name = 'days-before-repayment-is-due';

-- Habilitar emisión externa
UPDATE m_external_event_configuration
SET enabled = true
WHERE type = 'LoanRepaymentDueBusinessEvent';

Condiciones de Emisión

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

Payload (Avro Schema: LoanRepaymentDueDataV1)

{
  "loanId": 12345,
  "loanAccountNo": "000000012345",
  "loanExternalId": "EXT-001",
  "currency": { "code": "MXN", "decimalPlaces": 2 },
  "installment": {
    "installmentNumber": 3,
    "installmentDueDate": "2025-02-15",
    "principalAmountDue": 5000.00,
    "interestAmountDue": 250.00,
    "feeChargeAmountDue": 50.00,
    "penaltyChargeAmountDue": 0.00,
    "totalAmountDue": 5300.00
  },
  "pastDueAmount": {
    "totalAmount": 0.00,
    "principalAmount": 0.00,
    "interestAmount": 0.00,
    "feeAmount": 0.00,
    "penaltyAmount": 0.00
  }
}

Referencias de Código

Archivo Línea Descripción
CheckLoanRepaymentDueBusinessStep.java 79-84 Lógica de emisión
LoanRepaymentDueBusinessEvent.java 23-35 Definición del evento
LoanRepaymentDueDataV1.avsc - Schema Avro

2. LoanRepaymentOverdueBusinessEvent

Propósito: Notificar que una cuota lleva N días vencida sin pagar.

Configuración

-- Habilitar evento (días después del vencimiento)
UPDATE c_configuration
SET enabled = true, value = 1  -- 1 día después
WHERE name = 'days-after-repayment-is-overdue';

-- Habilitar emisión externa
UPDATE m_external_event_configuration
SET enabled = true
WHERE type = 'LoanRepaymentOverdueBusinessEvent';

Condiciones de Emisión

dueDate + N días == businessDate
AND installment.totalOutstanding > 0

Payload

Mismo schema que LoanRepaymentDueBusinessEvent (LoanRepaymentDueDataV1).

Referencias de Código

Archivo Línea Descripción
CheckLoanRepaymentOverdueBusinessStep.java 83-86 Lógica de emisión
LoanRepaymentOverdueBusinessEvent.java 23-35 Definición del evento

3. LoanDelinquencyRangeChangeBusinessEvent

Propósito: Notificar cuando un préstamo entra, sale o cambia de rango de morosidad.

Configuración

-- Habilitar emisión externa
UPDATE m_external_event_configuration
SET enabled = true
WHERE type = 'LoanDelinquencyRangeChangeBusinessEvent';

Nota: Los rangos de mora (Delinquency Buckets) se configuran en la UI de Fineract o via API.

Escenarios de Emisión

Escenario Descripción
Entra en mora Préstamo pasa de "sin mora" a un rango de delinquency
Sale de mora Préstamo paga todo y sale de delinquency
Cambio de rango Préstamo se mueve entre buckets (ej: 30-60 → 60-90 días)

Payload (Avro Schema: LoanAccountDelinquencyRangeDataV1)

{
  "loanId": 12345,
  "loanAccountNo": "000000012345",
  "loanExternalId": "EXT-001",
  "delinquencyRange": {
    "id": 2,
    "classification": "MORA_31_60",
    "minimumAgeDays": 31,
    "maximumAgeDays": 60
  },
  "currency": { "code": "MXN", "decimalPlaces": 2 },
  "amount": {
    "principalAmount": 10000.00,
    "interestAmount": 750.00,
    "feeAmount": 100.00,
    "penaltyAmount": 200.00,
    "totalAmount": 11050.00
  },
  "delinquentDate": "2024-12-01",
  "charges": [...],
  "installmentDelinquencyBuckets": [...]
}

Detección de Escenarios por Payload

Campo Valor Interpretación
delinquencyRange null Sin mora - Préstamo al corriente
delinquencyRange objeto En mora - Ver clasificación
amount.totalAmount 0 Deuda saldada
delinquentDate fecha Inicio de la mora
maximumAgeDays null Bucket final (mora más severa)

Algoritmo de Detección

function detectScenario(event, previousRangeId) {
  const range = event.delinquencyRange;
  const total = event.amount.totalAmount;

  // Salió de mora
  if (range === null && total === 0) {
    return "OUT_OF_DELINQUENCY";
  }

  // Entró en mora
  if (range !== null && previousRangeId === null) {
    return "INTO_DELINQUENCY";
  }

  // Cambió de rango
  if (range !== null && previousRangeId !== null && range.id !== previousRangeId) {
    return range.minimumAgeDays > previousMinDays
      ? "DELINQUENCY_WORSENED"
      : "DELINQUENCY_IMPROVED";
  }

  return "NO_CHANGE";
}

Referencias de Código

Archivo Línea Descripción
SetLoanDelinquencyTagsBusinessStep.java 46-130 COB Step que evalúa delinquency
DelinquencyWritePlatformServiceHelper.java 89-139 Lógica de cambio de tags
LoanDelinquencyRangeChangeBusinessEventSerializer.java 74-119 Serialización del evento
LoanAccountDelinquencyRangeDataV1.avsc - Schema Avro

Estructura del Mensaje Kafka

Todos los eventos se envuelven en MessageV1:

{
  "id": 1234567890,
  "source": "fineract",
  "type": "LoanDelinquencyRangeChangeBusinessEvent",
  "category": "Loan",
  "createdAt": "2025-01-06T14:30:00",
  "businessDate": "2025-01-06",
  "tenantId": "default",
  "idempotencyKey": "uuid-unique-key",
  "dataschema": "org.apache.fineract.avro.loan.v1.LoanAccountDelinquencyRangeDataV1",
  "data": "<avro-bytes>"
}

Configuración Completa

1. Habilitar Eventos Externos

UPDATE m_external_event_configuration SET enabled = true WHERE type IN (
  'LoanRepaymentDueBusinessEvent',
  'LoanRepaymentOverdueBusinessEvent',
  'LoanDelinquencyRangeChangeBusinessEvent'
);

2. Configurar Días de Anticipación/Atraso

UPDATE c_configuration SET enabled = true, value = 3
WHERE name = 'days-before-repayment-is-due';

UPDATE c_configuration SET enabled = true, value = 1
WHERE name = 'days-after-repayment-is-overdue';

3. Verificar COB Steps

Los steps están pre-configurados en m_batch_business_steps:

Job Step Función
LOAN_CLOSE_OF_BUSINESS CHECK_LOAN_REPAYMENT_DUE Verifica próximos vencimientos
LOAN_CLOSE_OF_BUSINESS CHECK_LOAN_REPAYMENT_OVERDUE Verifica cuotas vencidas
LOAN_CLOSE_OF_BUSINESS LOAN_DELINQUENCY_CLASSIFICATION Clasifica mora

Diagrama de Flujo

                         ┌──────────────────────┐
                         │  COB Job (Nightly)   │
                         │ LOAN_CLOSE_OF_BUSINESS│
                         └──────────┬───────────┘
                                    │
          ┌─────────────────────────┼─────────────────────────┐
          │                         │                         │
          ▼                         ▼                         ▼
┌─────────────────────┐  ┌─────────────────────┐  ┌─────────────────────┐
│ CHECK_REPAYMENT_DUE │  │CHECK_REPAYMENT_OVERDUE│ │DELINQUENCY_CLASSIF. │
│                     │  │                     │  │                     │
│ dueDate - N = today │  │ dueDate + N = today │  │ Evalúa días vencidos│
└──────────┬──────────┘  └──────────┬──────────┘  └──────────┬──────────┘
           │                        │                        │
           ▼                        ▼                        ▼
┌─────────────────────┐  ┌─────────────────────┐  ┌─────────────────────┐
│LoanRepaymentDue     │  │LoanRepaymentOverdue │  │LoanDelinquencyRange │
│BusinessEvent        │  │BusinessEvent        │  │ChangeBusinessEvent  │
└──────────┬──────────┘  └──────────┬──────────┘  └──────────┬──────────┘
           │                        │                        │
           └────────────────────────┴────────────────────────┘
                                    │
                                    ▼
                         ┌──────────────────────┐
                         │   Kafka / ActiveMQ   │
                         └──────────────────────┘

Consumo en Sistema Externo

Deserialización

Usar la librería de schemas Avro de Fineract:

<dependency>
  <groupId>org.apache.fineract</groupId>
  <artifactId>fineract-avro-schemas</artifactId>
  <version>0.0.1114-a1b6f15</version>
</dependency>

Identificadores para Correlación

Campo Uso
loanExternalId ID externo configurado en el préstamo
loanAccountNo Número de cuenta Fineract
loanId ID interno de Fineract

Archivos de Referencia

fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/
├── repayment/
│   ├── LoanRepaymentDueBusinessEvent.java
│   └── LoanRepaymentOverdueBusinessEvent.java
└── LoanDelinquencyRangeChangeBusinessEvent.java

fineract-provider/src/main/java/org/apache/fineract/cob/loan/
├── CheckLoanRepaymentDueBusinessStep.java
├── CheckLoanRepaymentOverdueBusinessStep.java
└── SetLoanDelinquencyTagsBusinessStep.java

fineract-avro-schemas/src/main/avro/loan/v1/
├── LoanRepaymentDueDataV1.avsc
├── LoanAccountDelinquencyRangeDataV1.avsc
├── DelinquencyRangeDataV1.avsc
└── RepaymentDueDataV1.avsc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment