Microservicio de gestión de pagos construido con Spring Boot 4 y PostgreSQL. Responde a los requerimientos de la prueba técnica.
Instancia AWS [inactive] — disponible de forma temporal para evaluacion.
| Recurso | URL / Valor |
|---|---|
| Health | xxx/health |
| Pagos CR141 (PAID, 3 pagos) | xxx/payments/CR141 |
| Pagos CR001 (ACTIVE, parciales) | xxx/payments/CR001 |
| API Key demo | abcdefghijklmnopqrstuvwxyz0123456789abcdefgh |
| Repositorio | https://github.com/Daespinosag/kata-koa-api |
- Prerequisitos
- Puesta en marcha
- Configuración de Postman
- API
- Seguridad — API Key
- Base de datos y SQL
- Arquitectura
- Tests
- CI/CD — Deploy a EC2
- Caso de producción
- Uso de herramientas de IA
| Herramienta | Versión mínima | Notas |
|---|---|---|
| Java | 17 | Solo para desarrollo local (Opción B) |
| Maven | incluido (./mvnw) |
Wrapper incluido en el repo |
| Docker | 24+ | Requerido para Opción A |
| Docker Compose | v2 (plugin) | docker compose (sin guión) |
| Postman | cualquier versión | Para pruebas manuales |
No se necesita instalar Postgres — Docker lo levanta automáticamente.
git clone https://github.com/Daespinosag/kata-koa-api.git
cd kata-koa-api/koa-demo
docker compose upPrimer arranque: descarga imágenes y compila la app (~3 min). Los siguientes arranques usan caché.
Cuando aparezca Started KoaDemoApplication, la app está lista:
curl http://localhost:8080/health
# → OKAPI key para pruebas (incluida como default en compose.yaml):
dev:abcdefghijklmnopqrstuvwxyz0123456789abcdefgh
La clave es abcdefghijklmnopqrstuvwxyz0123456789abcdefgh (el prefijo dev: es el clientId).
curl -H "X-API-Key: abcdefghijklmnopqrstuvwxyz0123456789abcdefgh" \
http://localhost:8080/payments/CR001Los seeds de desarrollo (200 créditos, 298 pagos) se aplican automáticamente via Flyway.
Requiere Docker para la base de datos; Spring Boot lo levanta automáticamente. Asume que ya clonaste el repo.
cd kata-koa-api/koa-demo
export API_KEYS='dev:abcdefghijklmnopqrstuvwxyz0123456789abcdefgh'
./mvnw spring-boot:run -Dspring-boot.run.profiles=devSpring Boot detecta compose-dev.yaml (solo Postgres, sin el contenedor de la app) y lo levanta solo.
Hot reload: Spring DevTools está activo en el perfil dev. Al compilar con ./mvnw compile o desde el IDE (IntelliJ: Build → Build Project, VS Code: guardar), la app se reinicia en ~2 s sin detener Postgres.
El repositorio incluye una colección y un entorno listos para importar:
| Archivo | Ruta |
|---|---|
| Colección | .postman/koa-demo.postman_collection.json |
| Entorno | .postman/koa-demo.postman_environment.json |
Pasos:
- Abrir Postman → Import
- Importar ambos archivos
- Seleccionar el entorno koa-demo en el selector superior derecho
- Ajustar las variables según la tabla de abajo
Variables del entorno:
| Variable | Valor por defecto | Descripción | Cómo obtenerla |
|---|---|---|---|
baseUrl |
http://localhost:8080 |
URL base de la API. Cambiar a http://3.144.197.59:8080 para apuntar a la instancia AWS |
URL del servidor donde corre la app |
apiKey |
abcdefghijklmnopqrstuvwxyz0123456789abcdefgh |
Clave de autenticación para el header X-API-Key |
En local: valor de API_KEYS exportado antes de arrancar. En Docker: default en compose.yaml línea API_KEYS. En AWS: la clave demo ya está activa |
creditId |
CR001 |
Número de crédito usado en las peticiones GET /payments/{creditId} |
Cualquier crédito de los seeds: CR001–CR140 (ACTIVE), CR141–CR180 (PAID), CR181–CR200 (CANCELLED) |
Todos los endpoints están documentados en la colección con ejemplos de request, response y casos de error.
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
POST |
/payments |
API Key | Registrar un pago |
GET |
/payments/{numeroCredito} |
API Key | Listar pagos de un crédito |
GET |
/health |
No | Health check |
{
"numeroCredito": "CR001",
"valor": 500000,
"fecha": "2026-02-18"
}Respuestas:
| Código | Causa |
|---|---|
201 Created |
Pago registrado |
409 Conflict |
Pago duplicado (numeroCredito + fecha + valor) |
400 Bad Request |
Campo inválido o faltante |
404 Not Found |
Crédito no existe |
422 Unprocessable Entity |
Regla de negocio violada (crédito cancelado, sobrepago, etc.) |
401 Unauthorized |
API Key ausente o inválida |
Retorna pagos ordenados por fecha ascendente. Soporta paginación por cursor (?after=<cursor>&limit=<n>).
{
"numeroCredito": "CR001",
"items": [
{
"paymentId": "...",
"numeroCredito": "CR001",
"valor": 500000.0,
"fecha": "2026-02-18",
"paidAt": "2026-02-18T17:00:00Z",
"registeredAt": "2026-04-11T..."
}
],
"limit": 50,
"nextCursor": null
}Los errores siguen el estándar RFC 9457 Problem Details. Ver docs/spec/09-error-handling.md.
- Header requerido:
X-API-Key - Las claves se configuran mediante la variable de entorno
API_KEYS(formato:clientId:clave, múltiples separadas por|) - No hay secretos hardcodeados en el código
/healthestá excluido de autenticación
# Ejemplo con clave demo
curl -H "X-API-Key: abcdefghijklmnopqrstuvwxyz0123456789abcdefgh" \
http://localhost:8080/payments/CR001
# Sin clave → 401
curl http://localhost:8080/payments/CR001Rate limiting: 30 req/min por IP (pre-auth) y 100 req/min por cliente autenticado.
Ver docs/spec/08-security-api-key.md para detalles.
El schema completo está en koa-demo/src/main/resources/db/schema.sql. Flyway lo aplica como migración V1__schema.sql.
Tablas:
credits— crédito otorgado (aggregate root). Estados:ACTIVE,PAID,CANCELLEDpayments— pagos registrados contra un crédito. Append-only
Idempotencia: restricción UNIQUE (credit_id, payment_date, amount) — un pago duplicado retorna 409 sin insertar un registro nuevo.
Total pagado por numero_credito:
SELECT credit_id,
SUM(amount) AS total_pagado
FROM payments
WHERE credit_id = :creditId
GROUP BY credit_id;Top 5 créditos con mayor pago en un rango de fechas:
Implementado en PostgresPaymentRepository#topCreditsByPaidAtSumInRange.
SELECT credit_id,
SUM(amount) AS total
FROM payments
WHERE paid_at >= :from
AND paid_at < :to
GROUP BY credit_id
ORDER BY total DESC
LIMIT 5;| Índice | Columnas | Propósito |
|---|---|---|
idx_payments_credit_paid_at_id |
(credit_id, paid_at, payment_id) INCLUDE (amount, currency, payment_date, ...) |
GET /payments/{id} con paginación keyset — index-only scan |
idx_payments_paid_at_credit_amount |
(paid_at) INCLUDE (credit_id, amount) |
Agregación por rango de fechas para top-N |
Sin índice en paid_at la consulta top-N haría sequential scan sobre toda la tabla. Ver análisis completo en docs/spec/07-persistence-and-schema.md.
El proyecto sigue arquitectura hexagonal (ports & adapters) dentro de un único módulo Maven.
┌─────────────────────────────────────────────────────────────┐
│ HTTP Client │
└────────────────────────────┬────────────────────────────────┘
│ X-API-Key
▼
┌─────────────────────────────────────────────────────────────┐
│ Infrastructure — Entrada │
│ ┌──────────────────┐ ┌────────────────────────────────┐ │
│ │ ApiKeyAuth Filter│ │ IpRateLimit / ClientRateLimit │ │
│ └────────┬─────────┘ └───────────────┬────────────────┘ │
│ │ │ │
│ ┌────────▼────────────────────────────▼────────────────┐ │
│ │ Controllers (REST) │ │
│ │ POST /payments │ GET /payments/{id} │ GET /health│ │
│ └────────┬─────────────────────┬───────────────────────┘ │
└───────────│─────────────────────│─────────────────────────── ┘
│ │
┌───────────▼─────────────────────▼─────────────────────────── ┐
│ Application (casos de uso) │
│ RegisterPaymentCommandHandler FindPaymentsByCreditHandler │
│ │ │ │
│ ┌────────▼──────────────────────────────▼────────────────┐ │
│ │ Domain (reglas de negocio) │ │
│ │ Credit (FSM) · Payment · Money (COP) · PaidAt │ │
│ └────────┬──────────────────────────────┬───────────────┘ │
└───────────│──────────────────────────────│───────────────────┘
│ puerto (interface) │
┌───────────▼──────────────────────────────▼───────────────────┐
│ Infrastructure — Salida │
│ PostgresCreditRepository │ PostgresPaymentRepository │
│ (JDBC — sin JPA) │
│ │ │
│ ┌───────▼────────┐ │
│ │ PostgreSQL │ │
│ │ credits │ │
│ │ payments │ │
│ │ UNIQUE idempot.│ │
│ └────────────────┘ │
└───────────────────────────────────────────────────────────────┘
Estructura de paquetes:
payments/
credit/
domain/ ← Aggregate Credit, reglas de negocio, puertos
infrastructure/
persistence/ ← PostgresCreditRepository (JDBC)
payment/
domain/ ← Aggregate Payment, value objects
application/
register/ ← RegisterPaymentCommandHandler
find_by_credit/← FindPaymentsByCreditQueryHandler
infrastructure/
persistence/ ← PostgresPaymentRepository (JDBC)
rest/ ← Controllers (entrada HTTP)
shared/
domain/ ← Money, PaidAt, Clock, EventBus
infrastructure/
security/ ← ApiKeyAuthenticationFilter, rate limiting
errors/ ← GlobalExceptionHandler, Problem Details
Decisiones clave:
- JDBC sin JPA — control explícito del SQL, sin lazy loading inesperado (ADR-0002)
Moneycomo value object COP-only — evita errores de divisa (ADR-0003)- Idempotencia por constraint DB, no por lógica de aplicación (ADR-0007)
- Errores en RFC 9457 Problem Details (ADR-0005)
# Todos los tests (desde koa-demo/)
./mvnw test
# Solo tests de integración (requieren Docker para Testcontainers)
./mvnw test -Dgroups="integration"
# Excluir integración (solo unitarios)
./mvnw test -DexcludedGroups="integration"Los tests de integración usan Testcontainers — levantan Postgres automáticamente, sin configuración adicional.
Cobertura: 21 clases de test que cubren dominio, aplicación, persistencia, API (MockMvc + REST Docs) y arquitectura hexagonal (ArchUnit).
La aplicación está desplegada en AWS EC2 (us-east-2) y accesible en:
http://3.144.197.59:8080
La instancia estara disponible de forma temporal para evaluacion.
El pipeline se dispara automáticamente con un tag v*:
git tag v1.0.0
git push origin v1.0.0Flujo:
- Tests contra Postgres real (GitHub Actions service container)
- Build imagen Docker
- SCP imagen al EC2
docker compose upen el servidor- Healthcheck con reintentos
Ver .github/workflows/deploy.yml.
Secrets requeridos en GitHub → Settings → Secrets:
| Secret | Valor |
|---|---|
EC2_HOST |
IP pública del EC2 |
EC2_SSH_KEY |
Contenido del .pem |
- Logs estructurados (
/actuator/logso Loki/CloudWatch): buscar eltraceIddel error 500 y reconstruir el stack completo sin exponer al cliente. - Pool de conexiones (
/actuator/metrics/hikaricp.connections.active): si el pool se agota bajo carga, las peticiones siguientes fallan. Solución: ajustarmaximum-pool-sizeo revisar transacciones largas que no liberan. - Timeouts de BD: un query lento (>30 s) puede disparar un
SocketTimeoutExceptionque elGlobalExceptionHandlerno mapea a 400/409 → cae a 500. Revisarstatement-timeouten Postgres. - Optimistic lock contention (
OptimisticLockException): dos peticiones concurrentes al mismo crédito. El handler retorna 409, pero si el retry está mal configurado puede escalar. - OOM / GC pause: revisar métricas de heap (
/actuator/metrics/jvm.memory.used). Con-XX:MaxRAMPercentage=75en el contenedor es poco probable, pero un leak en caché de rate-limit podría manifestarse.
EXPLAIN ANALYZEsobre las queries reportadas como lentas. Los índicesidx_payments_credit_paid_at_ideidx_payments_paid_at_credit_amountcubren los accesos más frecuentes; verificar que el planner los use.- Autovacuum: tabla
paymentses append-only pero el índice de idempotencia se actualiza en cada INSERT. Revisarpg_stat_user_tables— sin_dead_tupcrece, autovacuum puede estar rezagado. - Connection pool:
pg_stat_activitymuestra conexiones activas. Si hay muchas enidle in transaction, hay transacciones que no se cierran correctamente. - Paginación: reemplazar
OFFSETpor keyset pagination (ya implementado con cursor enGET /payments/{id}) para evitar full-scan en páginas profundas. - Particionado por
paid_at: a futuro, si la tabla supera decenas de millones de filas, particionar por mes permitepartition pruningen las queries de rango.
| Métrica | Por qué |
|---|---|
http.server.requests (p99 por endpoint) |
Latencia percibida por el cliente |
payments.registered.count vs payments.duplicate.count |
Ratio de idempotencia — spike de duplicados indica reintento masivo del cliente |
hikaricp.connections.pending |
Presión sobre el pool de BD |
jvm.memory.used / jvm.gc.pause |
Salud de la JVM |
process.cpu.usage |
Detección de CPU-bound bajo carga |
Tasa de errores 5xx / 4xx |
SLA contractual; alerta si 5xx > 0.1% |
db.statement p99 (Micrometer JDBC) |
Queries lentas antes de que impacten al usuario |
Exportados via /actuator/prometheus → Prometheus + Grafana. Ver docs/spec/10-observability.md.
| Herramienta | Rol en el proyecto |
|---|---|
| Claude Code (Anthropic) | Orquestador principal — arquitectura, implementación, infraestructura |
| Cursor | Editor con asistencia contextual para refactoring fino y revisión de código |
El desarrollo siguió un flujo de múltiples agentes especializados coordinados por Claude Code:
Developer
│
▼
┌─────────────────────────────────────────────┐
│ Claude Code (orquestador) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │koa-arch │ │koa-coder │ │koa-tester│ │
│ │Diseño │→ │Impl. │→ │MockMvc + │ │
│ │hexagonal │ │JDBC/API │ │REST Docs │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ │
│ │koa-sec │ │koa-review│ │
│ │API Key + │ │Auditoría │ │
│ │rate limit│ │calidad │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────┘
│
▼ código generado
Cursor
│ revisión + ajustes finos
▼
Developer valida
│ ./mvnw test + Postman
▼
git commit → CI/CD
Etapas del flujo:
- Especificación — Claude Code +
koa-architect: análisis de la prueba técnica, definición de bounded contexts, ADRs y decisiones de diseño (docs/spec/) - Implementación —
koa-coder: generación de domain objects, use cases, controllers, JDBC repositories y configuraciones Spring - Testing —
koa-tester: tests unitarios (dominio), integración (Testcontainers), MockMvc + REST Docs snippets, ArchUnit - Seguridad —
koa-security:ApiKeyAuthenticationFilter, rate limiting Bucket4j, Problem Details RFC 9457 - Revisión — Cursor: revisión contextual del diff, refactoring fino, naming consistency
- Infraestructura — Claude Code:
Dockerfile,compose.yaml, GitHub Actions CI/CD, deploy EC2
Validación del código generado:
- Revisión manual de cada archivo antes del commit
- Tests de integración contra Postgres real (no mocks):
PaymentsApiIT,TopCreditsByPaidAtRangeRepositoryIT HexagonalArchitectureTest(ArchUnit) — las capas no se violan aunque el código sea generado- Pipeline CI bloquea el deploy si algún test falla
- Casos edge verificados manualmente con Postman: idempotencia, crédito cancelado, sobrepago, rate limiting
La IA elimina fricción en la escritura mecánica. El diseño, las decisiones de arquitectura y la validación fueron responsabilidad del desarrollador.