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
| 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 |
Propósito: Notificar que una cuota vencerá en N días.
-- 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';dueDate - N días == businessDate
AND loan.status NOT IN (SUBMITTED, APPROVED, REJECTED, WITHDRAWN)
AND loan.totalOutstanding > 0
AND installment.totalOutstanding > 0
{
"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
}
}| 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 |
Propósito: Notificar que una cuota lleva N días vencida sin pagar.
-- 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';dueDate + N días == businessDate
AND installment.totalOutstanding > 0
Mismo schema que LoanRepaymentDueBusinessEvent (LoanRepaymentDueDataV1).
| Archivo | Línea | Descripción |
|---|---|---|
CheckLoanRepaymentOverdueBusinessStep.java |
83-86 | Lógica de emisión |
LoanRepaymentOverdueBusinessEvent.java |
23-35 | Definición del evento |
Propósito: Notificar cuando un préstamo entra, sale o cambia de rango de morosidad.
-- 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.
| 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) |
{
"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": [...]
}| 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) |
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";
}| 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 |
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>"
}UPDATE m_external_event_configuration SET enabled = true WHERE type IN (
'LoanRepaymentDueBusinessEvent',
'LoanRepaymentOverdueBusinessEvent',
'LoanDelinquencyRangeChangeBusinessEvent'
);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';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 |
┌──────────────────────┐
│ 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 │
└──────────────────────┘
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>| Campo | Uso |
|---|---|
loanExternalId |
ID externo configurado en el préstamo |
loanAccountNo |
Número de cuenta Fineract |
loanId |
ID interno de Fineract |
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