Caso de estudio 03 · Banca

Transferencia rechazada. Fondos aún bloqueados.

Análisis funcional y técnico de un flujo crítico de transferencias SEPA.

Análisis funcionalBancaSEPAAPIsMicroserviciosAnálisis de causa raíz
01

Contexto de negocio

Una transferencia es un proceso de negocio, no una simple llamada a una API.

Permitir que los clientes envíen transferencias SEPA garantizando el control de saldo, los límites diarios, los controles de compliance y la trazabilidad completa.
INV-01

Nunca transferir más que los fondos disponibles.

INV-02

Nunca superar el límite diario de transferencias.

INV-03

Una transferencia rechazada nunca debe dejar fondos del cliente bloqueados.

INV-04

Los reintentos nunca deben crear efectos financieros duplicados.

INV-05

Toda transición de estado debe ser auditable.

02

Reglas bancarias

Las reglas financieras y del sistema gobiernan cada transición.

BR-01

El importe de la transferencia debe ser positivo.

BR-02

El saldo disponible debe cubrir la reserva.

BR-03

El importe diario acumulado debe mantenerse dentro del límite configurado.

BR-04

Se requiere una clave de idempotencia única en cada envío.

BR-05

Los fondos se reservan antes de completar la validación de compliance.

BR-06

Si compliance rechaza la transferencia, los fondos reservados deben liberarse.

Límite diario
Saliente10.000
Moneda
AdmitidaEUR
Efecto financiero
Exactamente
03

Arquitectura del sistema

Mapear cada servicio que participa en una transferencia.

01Mobile / Web
02Transfer API
03Limits
04Ledger
05Compliance / AML
06Payment Orchestrator
07SEPA Gateway

Ruta de recuperación asíncrona

01Event Bus
02Funds Release Consumer
03Ledger
04

Máquina de estados de la transferencia

Pensar en estados antes que en pantallas.

01DRAFT
02VALIDATING
03FUNDS_RESERVED
04COMPLIANCE_CHECK
05SUBMITTED
06SETTLED

Ruta de rechazo de compliance

01COMPLIANCE_CHECK
02REJECTED
03FUNDS_RELEASE_PENDING
04FUNDS_RELEASED
05

Flujo esperado

Un rechazo debe restaurar la disponibilidad financiera.

01Enviar transferencia
02Validar límite
03Reservar 8.750 €
04Revisión AML
05Rechazar transferencia
06Publicar evento
07Liberar reserva
08Restaurar 12.480 €
06

Incidencia

La transferencia se rechaza correctamente; el saldo del cliente, no.

Transferencia
REJECTED

TRF-908771

Ledger
FUNDS_RESERVED

8750,00 € aún reservados

Saldo disponible
INCORRECT

3730,00 €

La transferencia es rechazada por compliance, pero 8.750 € permanecen reservados y no disponibles para el cliente.
07

Hipótesis de investigación

Construir un mapa de fallos antes de abrir Postman.

  1. H01El servicio de Compliance rechazó la transferencia pero no publicó el evento de rechazo
  2. H02El evento se publicó pero no llegó a la cola de liberación de fondos
  3. H03El Funds Release Consumer rechazó el evento por una incompatibilidad de contrato
  4. H04El Ledger Service procesó la liberación pero devolvió un saldo desactualizado
  5. H05La reserva se duplicó y solo se liberó una de ellas
  6. H06Un reintento creó estados inconsistentes entre la transferencia y el ledger
08

Evidencia de API

El flujo síncrono de la transferencia se comporta según lo diseñado.

POST /api/v1/transfers · 202 Accepted
{
  "debtorAccountId": "ACC-ES-4410",
  "beneficiaryIban": "DE89370400440532013000",
  "amount": 8750,
  "currency": "EUR",
  "idempotencyKey": "idem-5e71-transfer-8750"
}
VALIDATING
GET /api/v1/transfers/TRF-908771 · 200 OK
{
  "transferId": "TRF-908771",
  "status": "REJECTED",
  "rejectionCode": "AML_REVIEW_FAILED",
  "fundsReleaseStatus": "PENDING"
}
Rechazo de negocio
El éxito HTTP y el éxito de negocio son cosas distintas.
09

Evidencia de eventos

La recuperación depende de un contrato asíncrono.

transfer.rejected · evt-trf-908771-r1
{
  "eventType": "transfer.rejected",
  "eventVersion": "2.0",
  "eventId": "evt-trf-908771-r1",
  "timestamp": "2026-09-11T15:18:42.413Z",
  "data": {
    "transferId": "TRF-908771",
    "debtorAccountId": "ACC-ES-4410",
    "amount": 8750,
    "currency": "EUR",
    "rejectionCode": "AML_REVIEW_FAILED",
    "releaseFunds": true
  }
}
Funds Release Consumer
Versión soportada
1.0
Versión recibida
2.0
Rechazado

Unsupported eventVersion: 2.0; expected 1.0

10

Análisis de logs

Seguir el estado de la transferencia y el estado del dinero de forma independiente.

transfer-processing · traza de un entorno similar a producción
TRANSFER_APIPOST /transfers accepted id=TRF-908771 idempotencyKey=idem-5e71-transfer-8750
LIMITSDaily limit check PASS projectedTotal=8750.00 EUR
LEDGERFunds reserved amount=8750.00 availableBalance=3730.00
COMPLIANCEAML screening started transferId=TRF-908771
COMPLIANCETransfer rejected code=AML_REVIEW_FAILED
EVENT_BUSPublished transfer.rejected version=2.0 eventId=evt-trf-908771-r1
SQSDelivered event to funds-release-queue
FUNDS_RELEASEReceived event evt-trf-908771-r1
FUNDS_RELEASEERROR Unsupported eventVersion=2.0 expected=1.0
FUNDS_RELEASEMessage moved to funds-release-dlq
BALANCE_APIGET /accounts/ACC-ES-4410/balance available=3730.00 reserved=8750.00
11

Análisis del ledger

El saldo contable y el saldo disponible no son el mismo concepto.

Saldo contable
Registrado12.480 €
Importe reservado
Activo8.750 €
Saldo disponible
Utilizable3.730 €

Una reserva reduce lo que el cliente puede usar sin cambiar el saldo contable registrado.

12

Validación de datos

Comprobar la hipótesis a nivel de datos

El flujo de la API y de los eventos indicaba que la transferencia había llegado a un estado de rechazo, pero el saldo disponible del cliente sugería que la reserva de fondos original seguía activa.

En este punto, la investigación va más allá de la respuesta del servicio y comprueba si los datos persistidos reflejan el estado de negocio esperado.

El objetivo no es consultar la base de datos en cada prueba, sino usar SQL cuando los datos del backend pueden confirmar o descartar una hipótesis concreta de la investigación.

1. Confirmar el estado de la transferencia y la reserva

SQL
SELECT
    t.transfer_id,
    t.status AS transfer_status,
    t.amount,
    r.status AS reservation_status,
    r.reserved_amount
FROM transfers t
LEFT JOIN fund_reservations r
    ON r.transfer_id = t.transfer_id
WHERE t.transfer_id = 'TRF-908771';
Resultado esperado de la investigación
transfer_status    = REJECTED
reservation_status = ACTIVE
reserved_amount    = 8750.00

Esto confirma la inconsistencia: la transacción de negocio está rechazada, pero la reserva de fondos sigue activa.

2. Verificar el impacto en el saldo del cliente

SQL
SELECT
    account_id,
    ledger_balance,
    reserved_amount,
    available_balance
FROM account_balances
WHERE account_id = 'ACC-ES-4410';
Resultado esperado
ledger_balance    = 12480.00
reserved_amount   = 8750.00
available_balance = 3730.00

El saldo contable no ha disminuido, pero la reserva activa sigue reduciendo el importe disponible para el cliente.

3. Comprobar si el evento de liberación se procesó

SQL
SELECT
    e.event_type,
    e.event_version,
    e.processing_status,
    e.created_at
FROM integration_events e
WHERE e.transfer_id = 'TRF-908771'
ORDER BY e.created_at;
Resultado de ejemplo
transfer.created  | 1.0 | PROCESSED
funds.reserved    | 1.0 | PROCESSED
transfer.rejected | 2.0 | FAILED

Los datos respaldan la hipótesis del flujo de eventos: el evento de rechazo existe, pero su procesamiento posterior no se completó correctamente.

4. Detectar el patrón global

SQL
SELECT
    COUNT(*) AS affected_transfers,
    SUM(r.reserved_amount) AS total_funds_still_reserved
FROM transfers t
JOIN fund_reservations r
    ON r.transfer_id = t.transfer_id
WHERE t.status = 'REJECTED'
  AND r.status = 'ACTIVE';

Esta consulta cambia la pregunta de “¿ha fallado una transferencia?” a “¿es esto una inconsistencia de estado sistémica y repetible?”.

SQL se utiliza aquí como evidencia de investigación: cada consulta existe para validar una hipótesis concreta sobre estado, dinero o procesamiento de eventos.

13

Causa raíz

El servicio de liberación de fondos no puede consumir el evento de rechazo.

01Rechazo de Compliance ✓
02Transferencia REJECTED ✓
03Publicación del evento ✓
04Entrega en la cola ✓
05Funds Release Consumer ✕
06Liberación de fondos — no ejecutada
07Saldo disponible — incorrecto
El productor publica transfer.rejected v2.0, mientras que el Funds Release Consumer solo admite v1.0. El mensaje termina en la DLQ y 8.750 € permanecen reservados.
14

Impacto en negocio

Una incompatibilidad técnica de contrato se convierte en un problema con el dinero del cliente.

ClienteLos fondos permanecen bloqueados
SaldoEl importe disponible es incorrecto
OperacionesSe requiere conciliación manual
SoporteSe requiere escalado y explicación al cliente
RiesgoPosibles intentos de transferencia duplicados
ConfianzaEl cliente no puede acceder a su dinero
15

Defecto

Traducir la evidencia del sistema distribuido a riesgo bancario.

IDBNK-4412
TítuloLos fondos reservados no se liberan tras el rechazo de compliance debido a una versión incompatible del evento transfer.rejected
SeveridadCrítica
EsperadoTransferencia REJECTED → fondos reservados liberados → saldo disponible restaurado.
ActualTransferencia REJECTED → el evento termina en la DLQ → 8.750 € permanecen reservados.
Impacto en el clienteEl cliente no puede acceder a un dinero que debería haberse liberado tras el rechazo.
Impacto operativoPuede ser necesaria la conciliación manual, el escalado a soporte y el reprocesado de la DLQ.
16

Estrategia de regresión

Convertir la incidencia en una cobertura de calidad duradera.

Pruebas de contrato

Verificar la compatibilidad entre productor y consumidor de transfer.rejected antes del despliegue.

Pruebas de integración

Reserva → rechazo → evento → consumo → liberación de fondos.

Pruebas de estados

Validar las transiciones válidas de la transferencia y de la liberación de fondos.

Pruebas de idempotencia

Las peticiones y los eventos duplicados crean un único efecto financiero.

Pruebas de concurrencia

Las reservas simultáneas no pueden hacer que el saldo disponible quede por debajo de cero.

Monitorización

Generar alertas ante el crecimiento de la DLQ, reservas bloqueadas y descuadres de conciliación.

Colección de Postman
PassPOST /transfers · 202 Accepted
PassGET /transfers/TRF-908771 · REJECTED
FailGET /accounts/ACC-ES-4410/balance · reserved 8750
PassRetry · same Idempotency-Key
17

UAT

Validar el resultado para el cliente y la operación, no solo los servicios.

UAT-01

Transferencia válida por debajo del límite diario

Fondos reservados una sola vez; la transferencia avanza a SUBMITTED.

UAT-02

La transferencia supera el saldo disponible

Rechazada antes de la reserva; el saldo no cambia.

UAT-03

La transferencia supera el límite diario

Rechazada con un motivo claro; sin efecto financiero.

UAT-04

Compliance rechaza después de la reserva

Transferencia REJECTED y reserva liberada por completo.

UAT-05

Envío duplicado con la misma clave de idempotencia

Se devuelve la misma transferencia; sin reserva duplicada.

UAT-06

El evento de rechazo se entrega dos veces

Los fondos se liberan exactamente una vez.

UAT-07

Consumidor no disponible temporalmente

El evento se reintenta de forma segura; la reserva se libera finalmente.

UAT-08

El mensaje llega a la DLQ

Se genera una alerta y existe una ruta operativa de reprocesado.

Evidencia Gherkin
Feature: SEPA transfer validation and funds release
  As a retail banking customer
  I want transfers and rejected transfers to update my available balance correctly
  So that my money is never duplicated, overdrawn or blocked incorrectly

  Scenario: Compliance rejects after reservation
    Given 8750 EUR has been reserved
    When compliance rejects the transfer
    And a compatible transfer.rejected event is processed
    Then the transfer should be REJECTED
    And the reservation should be released
    And the available balance should return to 12480 EUR

  Scenario: Funds-release consumer cannot process contract version
    Given a transfer.rejected event with eventVersion 2.0
    And the consumer supports only eventVersion 1.0
    When the event is consumed
    Then the event should be rejected
    And the message should be sent to a Dead Letter Queue
    And an operational alert should be raised
18

Análisis de riesgos

Los sistemas financieros fallan en los bordes entre estados válidos.

EscenarioModo de falloRiesgoControl esperado
Envío duplicadoDos clics del usuario crean dos intentos de transferenciaHIGHLa clave de idempotencia debe devolver la transferencia original en lugar de crear otra
Transferencias simultáneasDos transferencias válidas superan conjuntamente el saldo disponibleCRITICALLa reserva debe ser atómica sobre el saldo disponible
Rechazo de complianceTransferencia rechazada después de la reserva de fondosCRITICALLos fondos reservados deben liberarse exactamente una vez
Timeout del proveedorLa pasarela SEPA acepta la petición pero la respuesta HTTP se pierdeHIGHConciliar usando la referencia del proveedor antes de reintentar
Evento fuera de ordenEl evento de liberación llega antes que la actualización del estado localHIGHEl consumidor debe gestionar o reintentar de forma segura el desfase temporal de estado
Evento duplicadoEl mismo evento de rechazo se entrega dos vecesHIGHLa liberación de fondos debe ser idempotente
Acumulación en la DLQEl consumidor rechaza eventos de negocio válidosCRITICALSe requiere monitorización y un procedimiento de reprocesado
Lectura desactualizadaEl ledger es correcto pero la aplicación muestra el saldo antiguoMEDIUMDeben definirse la consistencia de lectura y la invalidación de caché
Controles de recuperaciónReintentosDLQConciliaciónAuditabilidadOrdenación de eventosEventos duplicados
19

Estrategia de pruebas

Decidir dónde hay que demostrar la calidad

No todos los riesgos requieren el mismo tipo de prueba.

En un flujo bancario crítico, la estrategia debe conectar el riesgo de negocio con el nivel en el que el comportamiento puede validarse con mayor eficacia.

RiesgoNivel principal de validaciónPor quéPrioridad
El importe de la transferencia supera el límite diarioAPI / servicioLa regla de negocio puede validarse de forma directa y determinista, sin depender de la UI.Alta
Los fondos no se reservan antes de complianceIntegración + datosRequiere validar la orquestación y el estado financiero persistido.Crítica
Una transferencia rechazada mantiene los fondos reservadosIntegración + base de datos + E2EEl riesgo abarca el estado de negocio, el procesamiento asíncrono y el saldo visible para el cliente.Crítica
Envío duplicado de la transferenciaAPI + integraciónLa idempotencia debe validarse en el límite de la transacción y en el procesamiento posterior.Crítica
Eventos fuera de orden o incompatiblesContrato + integraciónEl fallo ocurre entre servicios, no en la UI.Crítica
El cliente ve un estado final incorrectoE2EEl recorrido completo del usuario debe reflejar correctamente el estado financiero final.Alta

Decisiones de cobertura

UI

Validar: inicio de la transferencia, estado visible para el usuario, mensajes de validación, presentación del saldo y del estado final.

No depender solo de la UI para: orquestación, entrega de eventos, persistencia de reservas, idempotencia.

API / servicio

Validar: reglas de negocio, límites, respuestas de validación, cambios de estado de la transacción, peticiones duplicadas, gestión de errores.

Integración / contrato

Validar: comportamiento entre servicios, compatibilidad del esquema de eventos, versiones de eventos, procesamiento asíncrono, reintentos, comportamiento de la DLQ.

Base de datos / datos

Validar: estado persistido de la transferencia, reservas activas y liberadas, consistencia de saldos, condiciones de conciliación, combinaciones de estado inesperadas.

End-to-end

Validar: recorridos críticos del cliente, consistencia del estado financiero, recuperación de transacciones rechazadas o fallidas, resultado final visible para el usuario.

Condiciones de entrada y salida

Condiciones de entrada

  • Reglas de negocio comprendidas
  • Integraciones críticas disponibles
  • Datos de prueba preparados
  • Contratos de eventos conocidos
  • Entorno lo bastante estable para una ejecución significativa

Condiciones de salida

  • Ningún defecto crítico abierto en el ciclo de vida de la transferencia
  • Ningún escenario conocido en el que una transferencia REJECTED pueda dejar fondos reservados
  • Los escenarios críticos de API, integración y E2E pasan
  • Comportamiento de duplicados y reintentos validado
  • El estado financiero cuadra correctamente tras un rechazo o fallo
  • Ningún mensaje sin explicación en la DLQ del flujo crítico

En este flujo, la decisión de release se define por la consistencia del estado de negocio, no simplemente por el porcentaje de casos de prueba que pasan.

20

Resultado del análisis

Del fallo de una transacción al riesgo sistémico

Invariante de negocio crítica identificada

Una transferencia rechazada nunca debe dejar los fondos del cliente no disponibles.

Inconsistencia financiera rastreada

La transferencia llegó a REJECTED, mientras 8.750 € permanecían reservados y seguían reduciendo el saldo disponible del cliente.

Causa raíz aislada

El evento transfer.rejected se produjo con el contrato v2.0, mientras que el Funds Release Consumer solo admitía v1.0.

Propagación del fallo comprendida

El análisis conectó la incompatibilidad del evento con la DLQ, la operación de liberación de fondos no ejecutada y la inconsistencia resultante en el ledger.

Riesgo sistémico identificado

El problema no se limitaba a una transacción: cualquier transferencia rechazada que siguiera la misma ruta de eventos podía dejar fondos reservados.

Controles de calidad definidos

La validación de contratos, la conciliación, la monitorización de la DLQ, la idempotencia, las pruebas de concurrencia y la regresión end-to-end se identificaron como controles para el flujo crítico.

Conclusión

El objetivo no era solo encontrar dónde falló la transacción, sino entender por qué el sistema permitió que existiera un estado de negocio no válido.

21

Conclusión

En banca, la calidad incluye la integridad del dinero, el estado y la recuperación.

La pregunta crítica no es solo “¿respondió correctamente la API?”. Es “¿siguen siendo consistentes el estado de la transferencia, el estado del ledger, las reglas de negocio y el saldo visible para el cliente después de cada resultado posible?”.