<aside>

Contenido

  1. Introducción
  2. Stack tecnológico
  3. Arquitectura de Software
  4. Esquema / Diseño de Base de Datos
  5. Integración RabbitMQ
  6. ms-clientes → Microservicio (Cliente, Persona)
  7. ms-cuentas → Microservicio (Cuenta, Movimientos)
  8. Comunicación entre microservicios
  9. Seguridad
  10. Pruebas unitarias
  11. Pruebas de integración
  12. Buenas prácticas de desarrollo
  13. Documentación API
  14. Despliegue en Docker
  15. CI/CD → AWS EC2
  16. Probar solución

</aside>

<aside>

Decisiones clave

BigDecimal para dinero, nunca Double: Los doubles tienen errores de punto flotante (0.1 + 0.2 = 0.30000000000000004). En un sistema financiero eso es inaceptable. BigDecimal es preciso.

El tipo de movimiento se deduce del signo: Valor positivo = depósito, negativo = retiro. El consumidor solo manda "valor": 575 o "valor": -575. Es más simple y menos propenso a errores que mandar tipo + valor por separado.

F3 "Saldo no disponible": Se valida antes de aplicar el movimiento. Si saldoActual + valor < 0, lanza InsufficientBalanceException con el mensaje exacto que pide la prueba.

@Transactional en registrar: La actualización de saldo y el registro del movimiento son atómicos. Si falla uno, se revierte todo. Esto es crítco en operaciones financieras.

clienteId y clienteNombre en Cuenta: No es un FK real (son microservicios separados con BDs separadas). Es una referencia que se sincronizará vía RabbitMQ después.

Entity Cuenta

Entity Movimiento

Puntos clave:

Contra-asiento: Corrección de Movimientos

En un sistema financiero real, los movimientos son registros de auditoría inmutables. Nunca se modifica ni se elimina un movimiento existente. Si hay un error, se aplica un contra-asiento: se crea una reversión del movimiento original y un nuevo movimiento con el valor correcto. La prueba requiere CRU (Crear, Leer, Actualizar) para Movimiento. En lugar de implementar una actualización directa que sobreescriba el movimiento original (perdiendo el historial), se implementó el patrón de contra-asiento que mantiene la trazabilidad completa.

¿Cómo funciona?

Al hacer PATCH /api/v1/movimientos/{id} con un nuevo valor, el sistema:

  1. Busca el movimiento original (no lo modifica).
  2. Crea un movimiento de reversión con el valor opuesto (anula el original).
  3. Crea un nuevo movimiento con el valor correcto.
  4. Actualiza el saldo disponible de la cuenta.

¿Por qué no actualizar directamente? Si se modifica el movimiento original, en la base de datos solo queda el valor nuevo. Se pierde el registro de que alguna vez fue otro valor, cuándo se cambió y por qué. En auditoría financiera esto es inaceptable: un libro contable nunca borra ni modifica, solo agrega.

¿Qué pasa si se corrige un movimiento de reversión? Funciona igual. Cualquier movimiento que se corrija genera 2 movimientos nuevos (reversión + corrección). El historial solo crece, nunca se modifica.

Endpoint para obetener Reporte

Paginación

Es crítica. Si un cliente tiene miles de movimientos, retornar todo de golpe mata el performance. Pero para reportes, más que paginación clásica (page/size), conviene usar el rango de fechas como filtro natural.

Para el reporte no es necesario agregar paginación de Spring. El rango de fechas + límite de 365 días ya actúa como paginación natural. Un reporte de consulta completo, si lo paginamos, el resumen de totales estaría incompleto, sólo sumaríamos una página, no toto el periodo.

Donde sí tiene sentido agregar paginación, es en los endpoints de listado:

Aquí si podrían crecer sin control. Es un cambio rápido, se agrega Pageable al Controller.

Para esta prueba, no se aplica Pageable, y los endpoints de listado manejan pocos registros. Sabemos que estos endpoints son candidatos a paginación con Pagebale de Spring Data. No se implementa porqué el volumen actual no los justifica, pero es un cambio trivial si escala.

Resumen/Totales

Un reporte profesional no solo lista movimientos, inclue un resumen con totalizaciones.

Validación de fechas

Que fechaInicio no sea mayor a fechaFin, y que el rango no exceda un periodo razonable, en este caso, de 1 año.

Wrapper de respuesta

En lugar de retornar una lista plana, envolvemos en un objeto con metada.

Creación de Cueta con GET síncrono para obtener el nombre de Cliente | Flujo

El evento de RabbitMQ solo se dispara cuando creamos o actualizamos un cliente en ms-clientes.

Cuando se crea una cuenta nueva en ms-cuentas no hay nada que vaya a buscar el nombre del cliente en ese momento.

La solución que se implementó es que al crear un cuenta, ms-cuentas haga una llamanda REST a ms-clientes para obtener el nombre. Es una llamanda síncrona puntual solo en la creación, el resto se mantiene asíncrono con RabbitMQ.

¿Cómo funciona?

Al crear un cuenta, ms-cuentas hace un GET a **http://localhost:8081/api/v1/clientes/by-cliente-id/{clienteId} para obtener el nombre.** Si ms-clientes está caído, nno falla, el try/catch retorna null y la cuenta se crea sin nombre, después RabbitMQ lo sincroniza.

</aside>