Documentación técnica · Escenario A

System Design & Contrato API
«Creciendo Juntos Financiera»

Esta página documenta de forma visual la frontera HTTP entre la SPA Angular y el backend Spring Boot, y el diseño de sistema que la sostiene: arquitectura, reparto de responsabilidades, máquinas de estado, modelo de datos, catálogo de errores y reglas pendientes. Sustituye a un sistema Access en operación: un negocio, 6 usuarios, un solo consumidor del API.

Contrato v1.1.0 · 2026-09-02 system-design v1.2 Modelo de datos v1.1 Angular · Spring Boot · PostgreSQL
54rutas
68operaciones
70esquemas
18códigos de error
17pantallas
13reglas abiertas

01Las 17 pantallas en 7 módulos

Alcance cerrado en el Anexo A. El Anexo C incorporó las pantallas 5 y 7 (expediente digital y avales), desplazando la numeración del Anexo A §A.1. Esta numeración es la vigente en todo el contrato: cada endpoint indica a qué pantalla sostiene.

#PantallaMóduloOrigen
1Inicio de sesiónSeguridadAnexo A §A.1
2Gestión de usuariosSeguridadAnexo A §A.1
3Listado de clientesClientesAnexo A §A.1
4Ficha de clienteClientesAnexo A §A.1
5Expediente digital del clienteClientesNueva · Anexo C §C.1.1
6Alta de créditoCréditosAnexo A §A.1
7Avales del créditoCréditosNueva · Anexo C §C.1.2
8Tabla de amortizaciónCréditosAnexo A §A.1
9Historial del clienteCobranzaAnexo A §A.1
10Registro de pago de cuotaCobranzaAnexo A §A.1
11Abono parcialCobranzaAnexo A §A.1
12Abono a capitalCobranzaAnexo A §A.1
13Cuotas vencidasCobranzaAnexo A §A.1
14Próximos a vencerCobranzaAnexo A §A.1
15Ingresos y egresos (caja)CajaAnexo A §A.1
16Consultas por rango de fechasConsultasAnexo A §A.1
17Impresión de listados y recibosImpresiónAnexo A §A.1 · Anexo B §B.2

02Arquitectura de alto nivel

Sin caché distribuida, sin colas de mensajes, sin servicios adicionales. Con 6 usuarios y ~18 mil cuotas, cualquier pieza extra cuesta más de lo que resuelve.

NAVEGADOR — SPA Angular

Presentación (17 pantallas) · Dominio de UI: formateo, validación de captura, motor de amortización espejo (solo vista previa) · Capa de acceso: interfaz FinancieraApiMockApiService (semanas 1–2 y tests) / HttpApiService (desde la semana 3)

Spring Boot

API REST /api · Seguridad: autenticación + autorización por permiso · Reglas financieras: amortización, distribución de pagos, mora, saldos, caja · Expediente digital: recepción, validación y entrega autenticada de archivos · Reportes y renderizado de PDF / Excel · Migración Access → PostgreSQL (proceso aparte, una vez)

PostgreSQL — modelo v1.1

Metadatos del expediente · logotipo del negocio

Almacenamiento de archivos

Documentos del cliente · ubicación a criterio del backend, siempre con entrega autenticada

Decisiones transversales y sus contrapartidas

DecisiónSe ganaSe pagaCuándo revisarla
API sin versionado/api/…, nunca /api/v1/…URLs limpias, cero ceremoniaFrontend y backend se despliegan juntos siempreSi aparece una app móvil de cobradores — no antes
Estado de cuota derivado, no almacenadoNunca se desincroniza; sin proceso nocturnoSe calcula en cada consultaSi la cartera supera ~100 mil cuotas. Hoy son 18 mil
PDF en servidor (Anexo B §B.4)Papel y archivo idénticosEl frontend no puede generar PDF sin backendNo se revisa: requisito contractual
Archivos fuera de la base, metadatos dentroLa base no crece con binarios; respaldo simpleDos cosas que respaldar y desincronizarSi aparece un archivo huérfano en producción
Descarga autenticada por endpointEl expediente no se filtra por URLPrevisualización vía blob:Si los PDFs grandes se sienten lentos → URL firmada
Tablero en una sola llamadaUna petición, no seisUn endpoint que agrega seis consultasSi algún indicador se vuelve costoso, se separa
Sin caché ni colaMenos piezas que operarLos reportes pesados se recalculan cada vezSi un reporte anual supera 3 s
Un solo consumidor del APIContratos ajustados a las pantallasEndpoints específicos, poco reutilizablesSolo si aparece un segundo consumidor

Estrategia de integración — módulo por módulo

El frontend arranca contra una capa mock que implementa esta misma frontera y se conecta al backend real conforme los grupos de endpoints quedan estables. No hay una integración única al final.

Semana 1–2Todos los módulos contra mock
Semana 3Seguridad + Clientes → backend real · resto → mock
Semana 4+ Expediente digital y préstamos
Semana 5+ Avales y amortización
Semana 6–7+ Cobranza y pagos
Semana 8+ Caja
Semana 9+ Reportes, impresión y tablero
El expediente digital conviene conectarlo temprano: es lo único del sistema que sube archivos, y las sorpresas de multipart, límites de tamaño y CORS aparecen en la primera prueba real, no en la revisión de contrato.

03Reparto de responsabilidades

La línea que importa acordar: todo cálculo financiero con consecuencia contable vive en el backend. El frontend calcula únicamente para previsualizar mientras el usuario teclea, y nunca envía un resultado calculado por él como dato a persistir. En producción manda el backend siempre.

ResponsabilidadBackendFrontend
Generación del plan de cuotasAutoridadEspejo, solo para vista previa
Distribución de un pago sobre cuotasAutoridadMuestra lo que devuelve la previsualización
Cálculo de moraAutoridadSolo muestra el importe y la explicación
Estado de cada cuotaAutoridad (derivado)Solo pinta el badge
Saldos del préstamoAutoridadSolo muestra
Movimiento de caja de un pagoAutoridad (automático)No lo envía ni lo compone
Permisos efectivos del usuarioAutoridadOculta la UI según los recibe
Totales de reportes y del tableroAutoridadNo suma columnas en el cliente
Validación de tipo y tamaño de archivoAutoridad (revalida siempre)Primera línea, para no subir 40 MB en vano
Almacenamiento y entrega de archivosAutoridadSolo sube y descarga
Estado del expediente (completo / incompleto)Autoridad, si la regla abierta #13 lo exigeSolo muestra el distintivo
Formato de moneda, fecha y númeroAutoridad
Validación de captura (obligatorios, rangos, formato)Revalida siemprePrimera línea, para respuesta inmediata
Paginación, orden y filtrosAutoridadEnvía los parámetros
Estilos de impresión en pantallaAutoridad
Generación del archivo PDFAutoridad (Anexo B §B.4)Solo descarga
Motor de amortización duplicado: el frontend implementa la fórmula en TypeScript para que la vista previa responda al teclear. Dos implementaciones de la misma fórmula divergen: la mitigación acordada es un archivo amortizacion-casos.json con pares entrada → salida, versionado y consumido por los tests de ambos lados. Si divergen más de una vez, se elimina el motor local y la vista previa llama a POST /api/prestamos/simulacion con debounce.

04Máquinas de estado

Cinco máquinas relevantes para la interfaz. El frontend las pinta sin ambigüedad: los estados llegan ya resueltos desde el backend y nunca se recalculan en el cliente.

Cuota — derivado, nunca almacenado

Se resuelve al consultar; no requiere proceso nocturno. VENCIDA tiene precedencia sobre PARCIAL: una cuota vencida con abono parcial se pinta roja — el cobrador debe verla como pendiente de cobro.

PENDIENTE—abono menor→ PARCIAL—vence con saldo→ VENCIDA—pago del saldo→ PAGADA
PENDIENTE—pago total / vence y paga→ PAGADA
PAGADA—cancelación de pago→ PENDIENTE · PARCIAL · VENCIDA

Por cuota el frontend recibe, además: saldoPendiente, diasAtraso y moraAlDia — no puede derivarlos sin reimplementar la fórmula de mora.

Préstamo — la interfaz opera 4 de 8 estados

ACTIVO—último pago, saldo = 0→ LIQUIDADO
ACTIVO—cancelación (permiso ELIMINAR)→ CANCELADO

VENCIDO: lectura acordada (b) — condición derivada («tiene al menos una cuota vencida») sobre un préstamo ACTIVO, no un estado asignado. EN_REVISION, AUTORIZADO, RECHAZADO y REFINANCIADO existen en el modelo sin flujo (Anexo C §C.2).

Pago

APLICADO—cancelación (ventana de tiempo, regla abierta #7)→ CANCELADO→ terminal, no se reactiva

La cancelación revierte aplicaciones, recalcula cuotas, ajusta saldos y anula o compensa el movimiento de caja — en una sola transacción. El pago cancelado se conserva visible en el historial, marcado, con fecha y usuario de cancelación: requisito de auditoría.

Usuario

INACTIVO ACTIVO
ACTIVO—primer acceso tras migración→ ACTIVO · requiereCambioPassword

activo es estado, no permiso. Un usuario inactivo recibe 403 USUARIO_INACTIVO en el login, con mensaje distinto al de credenciales incorrectas.

Documento del expediente

VIGENTE—reemplazo→ SUSTITUIDO
VIGENTE—eliminación (permiso ELIMINAR)→ ELIMINADO

Baja lógica (activo = false), nunca borrado físico. El reemplazo conserva el documento anterior (acuerdo #24): reemplazaAId permite ofrecer «Ver versiones anteriores» en la pantalla 5.

Expediente completo — sujeto a la regla abierta #13

SIN_DOCUMENTOS INCOMPLETO COMPLETO

Solo existe si el cliente decide que el expediente incompleto bloquea el alta de crédito. Mientras no se resuelva, el frontend maqueta la advertencia, que es lo reversible.

05Modelo de datos v1.1

El modelo es del backend y tiene prioridad sobre los anexos cuando algo no coincida. El detalle completo entidad por entidad está en el documento Modelo de Datos Financiero Reconciliado.

Entidades de primer nivel

EmpresaIdentidad del negocio: nombre, lema, RFC, logotipo para los formatos impresos
ConfiguracionDías de anticipación del aviso de vencimiento (hoy: 3)
UsuarioAutenticación; password con BCrypt; requiereCambioPassword
Role / PermisoRoles agrupando los 5 permisos heredados del Access
UsuarioRole / RolePermisoAsignaciones rol ↔ usuario y permiso ↔ rol
UsuarioPermisoExcepciones puntuales por usuario; solo se expone si el cliente lo confirma
RutaCatálogo administrable de rutas de cobranza en campo
ClienteFicha única; nombreCompleto íntegro; la ruta vive aquí, no en el préstamo
ClienteDocumentoExpediente digital; baja lógica; reemplazo con historial
AvalEl aval avala un crédito, no a una persona; 0..n por préstamo
PrestamoSnapshot de condiciones: tasa, periodicidad, esquema y cuotas congelados al alta
CuotaRenglón de amortización; estado derivado, saldos y pagos acumulados
PagoEl dinero recibido; puede cubrir varias cuotas y dejar saldo a favor
PagoAplicacionDetalle de distribución: cuánto de cada pago fue a capital, interés y mora
MovimientoEstado de cuenta del cliente — no confundir con la caja
MovimientoCajaEfectivo real del negocio; un pago genera uno solo, con utilidad = interés + mora

Relaciones principales

Empresa ── Configuracion

Usuario ── UsuarioRole ── Role ── RolePermiso ── Permiso
   └────── UsuarioPermiso (excepciones opcionales)

Ruta ── Cliente ── ClienteDocumento
            └── Prestamo ── Aval
                    ├── Cuota ── PagoAplicacion ── Pago
                    ├── Movimiento
                    └── MovimientoCaja (cuando el movimiento de caja está relacionado)

Cinco decisiones del modelo que la interfaz hace visibles

Pago separado de Cuota

Un pago puede cubrir varias cuotas y dejar saldo a favor. El panel de cobro captura un monto recibido y muestra cómo se reparte — un cambio de mentalidad respecto del Access.

PagoAplicacion como detalle

Es lo que permite que el recibo impreso diga exactamente cuánto fue a capital, interés y mora. Sin eso, el recibo sería un total opaco.

Movimiento ≠ MovimientoCaja

El estado de cuenta del cliente y la caja del negocio son dos contabilidades: dos pantallas (9 y 15) que no se suman entre sí.

ClienteDocumento como tabla

Un cliente puede tener tres comprobantes de ingresos sin que nadie toque el esquema — lista con carga múltiple, no casillas fijas.

Aval colgado de Prestamo

Si el mismo avalista respalda dos créditos del mismo cliente, son dos registros. La pantalla 7 vive dentro del crédito.

Snapshot de condiciones

Tasa, periodicidad, esquema y número de cuotas quedan congelados en el crédito: cambiar tasas del negocio no altera créditos históricos.

Campos derivados que el frontend espera recibir calculados

Ninguno se deriva en el cliente. Llegan ya resueltos en cada respuesta.

RecursoCampoDefinición
CuotaPENDIENTE · PARCIAL · VENCIDA · PAGADA — derivado (§3.1)
totalCuota − (capitalPagado + interesPagado + moratorioPagado)
Fórmula del Anexo A §A.4; 0 si no aplica
Mora acumulada a la fecha de consulta, sin persistir
saldoPendiente + moraAlDia — el total del panel de cobro
PrestamoEl «5 de 12» que aparece en toda la interfaz
Conteo, para el distintivo de riesgo
Fecha de la siguiente cuota no pagada
Booleano derivado, lectura (b) de VENCIDO
Conteo para el distintivo, sin traer la lista
false en los 24 créditos migrados sin movimientos
ClienteLa columna que hace visible la separación cliente/crédito
Conteo
Suma de saldos de sus créditos activos
Junto a rutaId, para no resolver el catálogo en cada fila
Documentos vigentes del expediente
Solo si la regla abierta #13 lo hace necesario
ClienteDocumentoPara mostrar «2.4 MB» sin descargar el archivo
Ruta relativa del endpoint de descarga, armada por el backend. rutaArchivo no debe salir jamás
Quién lo subió — expediente que revisan varias personas
MovimientoCajaINGRESO · EGRESO · AJUSTE, derivada del prefijo del tipo
Trazabilidad cuando el movimiento nació de un pago
Determina si la fila es editable en la pantalla 15

Presente en el modelo, sin superficie en esta entrega

Anexo C §C.2: la presencia de una estructura en la base de datos no implica funcionalidad contratada.

Elemento del modeloEstado en la entrega
Campo creado y nullable. Sin flujo de refinanciamiento
Definidos. La interfaz opera los cuatro de §3.2
Para migrar créditos históricos. No se ofrecen en el alta
Tabla creada. Se expone solo si el cliente lo confirma
Valor definido. Sin operación que lo genere
Anexo C §C.3 prevé además cinco temas de revisión futura — refinanciamiento, módulo de ahorro, catálogo de productos, flujo de solicitud/autorización e interés sobre saldos insolutos — cada uno incorporable después sin rehacer lo ya construido. Es previsión, no backlog.

06Convenciones de la frontera

Reglas vinculantes de representación. El versionado no existe por decisión de diseño: un solo consumidor desplegado junto al backend; cualquier cambio incompatible obliga a desplegar ambos juntos.

TemaRegla
Prefijo y recursos/api · plural, español, minúsculas: /api/clientes, /api/prestamos (no /creditos)
Campos JSONcamelCase, igual en Java y TypeScript
EnumsTexto, MAYÚSCULAS, español — exactamente los valores del modelo v1.1
ImportesNúmero JSON con 2 decimales (1058.33), nunca cadena; el backend calcula con BigDecimal
TasasFracción por periodo: 0.0225 = 2.25 %
Fechas de negocioaaaa-mm-dd, sin hora ni zona — evita el desplazamiento de un día
Marcas de tiempoISO-8601 con offset: 2026-09-02T14:30:00-06:00
NulosCampo presente con null, nunca campo ausente
Paginación?page=0&size=50{ contenido, pagina, tamano, totalElementos, totalPaginas } — siempre de servidor
Orden?orden=campo,asc
ErroresSobre { codigo, mensaje, campos? } con el HTTP que corresponda — ver catálogo
Archivosmultipart/form-data, un archivo por petición; tamaños en bytes

07Seguridad

Autenticación

JWT en Authorization: Bearer <token>. Sin refresh token: con 6 usuarios y jornada de oficina, un token de vigencia larga y relogin manual basta. Al recargar la página el frontend llama GET /api/auth/sesion en vez de confiar en lo que guardó.

Permisos efectivos

Los 5 permisos del Anexo A §A.2 agrupados en roles. Llegan ya resueltos en el login — rol más excepciones de UsuarioPermiso, aplanados en códigos. El frontend no reconstruye la precedencia.

Ocultar no es autorizar

Cada endpoint valida el permiso por su cuenta. Si el frontend falla y muestra una acción indebida, la respuesta correcta es 403 SIN_PERMISO — no que la operación pase.

Contraseñas

Hash irreversible (BCrypt), nunca de vuelta al cliente. Las 6 migradas estaban en texto plano: requiereCambioPassword llega en el login y el frontend bloquea la navegación hasta el cambio.

Documentos: descarga autenticada

Nunca una URL pública adivinable. Como <img src> y <iframe src> no envían el encabezado Authorization, el frontend descarga los bytes y los convierte en blob:. Content-Disposition: attachment con ?descarga=true, inline para previsualizar.

Validación en dos líneas

Tipo y tamaño de archivo se validan también en el navegador para no subir 40 MB en vano, pero el servidor revalida siempre: la validación del cliente es cortesía, no control.

08Idempotencia en las operaciones de dinero

Un doble clic, una reconexión o un reintento del navegador no deben producir dos pagos: es el riesgo más caro del sistema, porque un pago duplicado ensucia cuotas, saldos y caja a la vez.

POST /api/prestamos/{id}/pagos
Clave-Idempotencia: 7f9c24e5-1b3d-4a2f-9e8c-05d1a6b7c3f9   ← UUID del frontend
  • Si llega una clave ya procesada, se devuelve el resultado original en lugar de crear un registro nuevo.
  • Una clave por operación, no por pantalla. IdempotenciaOperacion.hashSolicitud detecta la misma clave con cuerpo distinto: si el operador corrige el monto tras un fallo, es una operación nueva y genera clave nueva.
  • expiraEn: ventana de retención propuesta de 24 horas — debe superar con holgura el tiempo de un reintento tras perder la red.
  • Ante clave repetida con cuerpo distinto se propone 409 CONFLICTO_CONCURRENCIA con mensaje explícito, no un 400 genérico.
  • Fuera del mecanismo: la subida de documentos — un documento duplicado es molesto pero inofensivo, y el operador lo borra.

09Flujos clave

Los recorridos que concentran el riesgo. Cada uno indica dónde vive cada responsabilidad.

Alta de crédito con vista previa — pantalla 6

1. Motor espejo TS pinta el plan al teclear (respuesta instantánea, sin red) · 2. POST /api/prestamos/simulacion con debounce de 400 ms — si difieren, manda el backend y la discrepancia se registra en consola, nunca se muestra al usuario · 3. POST /api/prestamos + Clave-Idempotencia: una transacción que crea el Prestamo con snapshot, genera todas las Cuota, registra el Movimiento de otorgamiento y el MovimientoCaja EGRESO_PRESTAMO_OTORGADO · 4. Los avales se capturan después (pantalla 7): Aval.prestamoId exige que el préstamo exista.

Registro de un pago — pantallas 10 y 11

1. POST …/pagos/previsualizacion muestra qué cuotas se liquidan y cuánto va a mora, interés y capital — sin persistir · 2. POST …/pagos + Clave-Idempotencia: una sola transacción — crea Pago, distribuye, crea PagoAplicacion, acumula en Cuota, actualiza saldos, registra Movimiento, registra un único MovimientoCaja con utilidad = interés + mora cobrados, e incrementa el saldo a favor · 3. La respuesta trae la distribución final: con ella se pintan historial y recibo sin segunda consulta. Si difiere de la previsualización, manda la respuesta y se avisa del cambio. El abono parcial no es otro endpoint: es el mismo pago con monto menor.

Cancelación de un pago

Confirmación explícita (acción destructiva) → POST /api/pagos/{id}/cancelacion + idempotencia: revierte PagoAplicacion, recalcula cuotas, ajusta saldos, anula o compensa el movimiento de caja y marca CANCELADO. Si la ventana de tiempo pasó: 409 CANCELACION_NO_PERMITIDA con el motivo — nunca un 403 genérico.

Cobranza en ruta e impresión — pantallas 13, 14 y 17

GET /api/cobranza/vencidas es la lista de trabajo del cobrador · GET /api/impresion/{formato} trae datos + identidad del negocio para la vista previa con @media print · el PDF lo genera el backend con la misma plantilla que se imprime (Anexo B §B.4): archivo y papel idénticos.

10Referencia de la API

68 operaciones en 54 rutas, agrupadas en 11 etiquetas. Desplegable estilo Scalar/Postman: cada operación muestra descripción, parámetros, cuerpo y respuestas esperadas — incluidos los errores del catálogo.

Servidor local · http://localhost:8080 QA · https://qa.creciendojuntos.local (pendiente) Bearer JWT — todo salvo /api/auth/login Clave-Idempotencia en POST de dinero

Autenticación

3 operaciones

Inicio de sesión, revalidación y cambio de contraseña. Pantalla 1.

POST/api/auth/loginInicia sesión y devuelve token y permisos efectivos
iniciarSesionpública · sin token

permisos viene ya resuelto: rol más excepciones de UsuarioPermiso, aplanado en una lista de códigos. El frontend no reconstruye la precedencia entre rol y excepción (system-design §2.4).

Cuerpo de la petición req

application/json  → PeticionLogin

Respuestas

200

Sesión iniciada

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
401

CREDENCIALES_INVALIDAS · «Usuario o contraseña incorrectos»

CREDENCIALES_INVALIDAS
403

USUARIO_INACTIVO · Mensaje distinto al de credenciales: el operador necesita distinguir «me equivoqué de contraseña» de «me deshabilitaron».

USUARIO_INACTIVO
GET/api/auth/sesionRevalida la sesión y devuelve permisos vigentes
obtenerSesionVigente

El frontend lo llama al recargar la página en lugar de confiar en lo que guardó: si al usuario le cambiaron permisos o lo desactivaron, se entera de inmediato (system-design §6.1).

Respuestas

200

Sesión vigente

401

TOKEN_EXPIRADO · El frontend redirige a login conservando la ruta de retorno

TOKEN_EXPIRADO
POST/api/auth/passwordCambia la contraseña del usuario autenticado
cambiarPassword

Obligatorio en el primer acceso tras la migración (Anexo A §A.2). Mientras requiereCambioPassword sea true, el frontend bloquea la navegación.

Cuerpo de la petición req

application/json  → PeticionCambioPassword

Respuestas

204

Contraseña actualizada

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
401

TOKEN_EXPIRADO · El frontend redirige a login conservando la ruta de retorno

TOKEN_EXPIRADO

Usuarios y seguridad

8 operaciones

Usuarios, roles y permisos. Pantalla 2 · Anexo A §A.2.

GET/api/usuariosLista los usuarios del sistema
listarUsuarios

Parámetros

NombreEnTipoDescripción
pagequeryintegeropc

Índice de página, base 0

sizequeryintegeropc
ordenquerystringopc

Campo y dirección: fechaVencimiento,asc

Respuestas

200

Página de usuarios

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
POST/api/usuariosDa de alta un usuario
crearUsuario

Cuerpo de la petición req

application/json  → PeticionUsuario

Respuestas

201

Usuario creado

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
PUT/api/usuarios/{id}Edita un usuario
actualizarUsuario

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

application/json  → PeticionUsuario

Respuestas

200

Usuario actualizado

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
PATCH/api/usuarios/{id}/estadoActiva o desactiva un usuario
cambiarEstadoUsuario

activo es estado del usuario, no permiso (Anexo A §A.2). Un usuario inactivo recibe 403 USUARIO_INACTIVO al intentar iniciar sesión, con un mensaje distinto al de credenciales incorrectas.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

application/json  → object

Respuestas

200

Estado actualizado

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
PUT/api/usuarios/{id}/rolesReemplaza los roles asignados a un usuario
asignarRolesUsuario

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

application/json  → object

Respuestas

200

Roles actualizados

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
PUT/api/usuarios/{id}/permisosDefine excepciones de permiso para un usuario
definirExcepcionesPermiso

Solo se expone en la interfaz si el cliente confirma que lo necesita (Anexo A §A.2, punto abierto #5; Anexo C §C.2). La tabla UsuarioPermiso existe en el modelo; la pantalla puede no existir.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

application/json  → object

Respuestas

200

Excepciones actualizadas

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
GET/api/rolesCatálogo de roles
listarRoles

Respuestas

200

Roles

GET/api/permisosCatálogo de los 5 permisos del Anexo A §A.2
listarPermisos

Respuestas

200

Permisos

Catálogos

9 operaciones

Rutas y enumeraciones servidas por el backend para no duplicarlas en el frontend.

GET/api/rutasCatálogo de rutas de cobranza
listarRutas

En el sistema Access la ruta es texto libre; aquí es catálogo administrable (Anexo A §A.5).

REGLA ABIERTA #10 — falta confirmar si GRUPOS, QUINCENAL y LIQUIDACION son rutas equivalentes a las numeradas o clasificaciones de otra naturaleza. Si no son rutas, el filtro de la pantalla 3 necesita una dimensión más.

Parámetros

NombreEnTipoDescripción
soloActivasquerybooleanopc

Respuestas

200

Rutas

POST/api/rutasCrea una ruta
crearRuta

Cuerpo de la petición req

application/json  → PeticionRuta

Respuestas

201

Ruta creada

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
PUT/api/rutas/{id}Edita una ruta
actualizarRuta

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

application/json  → PeticionRuta

Respuestas

200

Ruta actualizada

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
PATCH/api/rutas/{id}/estadoActiva o desactiva una ruta
cambiarEstadoRuta

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

application/json  → object

Respuestas

200

Estado actualizado

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
409

La ruta tiene créditos asignados y no puede desactivarse

GET/api/catalogos/periodicidadesPeriodicidades, acotadas por ámbito
listarPeriodicidades

ambito=ALTA devuelve las tres que se ofrecen al registrar un crédito nuevo (SEMANAL, QUINCENAL, MENSUAL). ambito=TODAS devuelve las seis del modelo, necesarias para mostrar créditos históricos migrados (Anexo A §A.3).

Se pide al servidor en lugar de mantener una copia en el frontend, para que la regla no quede quemada en una lista que alguien olvide actualizar.

Parámetros

NombreEnTipoDescripción
ambitoquerystringopc

Respuestas

200

Periodicidades

GET/api/catalogos/tipos-pagoTipos de pago
listarTiposPago

En la operación real de campo prácticamente todo es EFECTIVO; el frontend lo deja preseleccionado.

Respuestas

200

Tipos de pago

GET/api/catalogos/tipos-movimiento-cajaTipos de movimiento de caja, con su naturaleza y si son manuales
listarTiposMovimientoCaja

Respuestas

200

Tipos de movimiento de caja

GET/api/catalogos/tipos-documentoTipos de documento admitidos en el expediente
listarTiposDocumento

Coinciden exactamente con los «tipos admitidos» del Anexo C §C.1.1.

REGLA ABIERTA #13 — si el expediente incompleto bloquea el alta de crédito, hay que marcar cuáles de estos son obligatorios. El campo obligatorio está previsto para eso y hoy puede venir siempre en false.

Respuestas

200

Tipos de documento

GET/api/catalogos/tipos-identificacionTipos de identificación
listarTiposIdentificacion

[ABIERTO — acuerdo #17 del Apéndice 2] El modelo v1.1 declara TipoIdentificacion como tipo pero no enumera sus valores. Propuesta del frontend: INE, PASAPORTE, LICENCIA, CEDULA_PROFESIONAL, OTRO.

Respuestas

200

Tipos de identificación

Clientes

6 operaciones

Pantallas 3 y 4.

GET/api/clientesListado de clientes con búsqueda, filtro y paginación
listarClientes

busqueda es un solo parámetro que cubre nombre y número de identificación: es como el operador busca en la práctica, sin elegir antes por qué campo (system-design §5.2).

Parámetros

NombreEnTipoDescripción
busquedaquerystringopc

Texto libre sobre nombre completo y número de identificación

rutaIdqueryinteger · int64opc
estadoqueryClienteStatusopc
pagequeryintegeropc

Índice de página, base 0

sizequeryintegeropc
ordenquerystringopc

Campo y dirección: fechaVencimiento,asc

Respuestas

200

Página de clientes

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
POST/api/clientesDa de alta un cliente
crearCliente

Cuerpo de la petición req

application/json  → PeticionCliente

Respuestas

201

Cliente creado

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
GET/api/clientes/{id}Ficha completa de un cliente
obtenerCliente

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Respuestas

200

Cliente

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
PUT/api/clientes/{id}Edita un cliente
actualizarCliente

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

application/json  → PeticionCliente

Respuestas

200

Cliente actualizado

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
GET/api/clientes/{id}/prestamosCréditos de un cliente
listarPrestamosDeCliente

Un cliente puede tener varios créditos sin duplicar su ficha. Es la separación cliente/crédito del Anexo A §A.8, y la columna que la hace visible en la interfaz.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Respuestas

200

Créditos del cliente

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
GET/api/clientes/{id}/estado-cuentaEstado de cuenta del cliente (entidad Movimiento)
obtenerEstadoCuentaCliente

No confundir con la caja del negocio. Movimiento es el estado de cuenta del cliente; MovimientoCaja es el efectivo de la financiera. Son dos contabilidades distintas y en la interfaz son dos pantallas que no se suman entre sí (modelo v1.1 §2.17 y §2.18).

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req
desdequerystring · dateopc
hastaquerystring · dateopc
pagequeryintegeropc

Índice de página, base 0

sizequeryintegeropc

Respuestas

200

Movimientos del estado de cuenta

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO

Expediente digital

8 operaciones

Pantalla 5 · Anexo C §C.1.1. Único grupo que sube archivos.

GET/api/clientes/{id}/documentosDocumentos del expediente de un cliente
listarDocumentosCliente

Por omisión devuelve solo los vigentes (activo = true). incluirInactivos=true devuelve además los sustituidos y los eliminados lógicamente, para poder ofrecer un historial de versiones del documento (system-design §3.5, acuerdo #24).

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req
tipoDocumentoqueryTipoDocumentoopc
incluirInactivosquerybooleanopc

Respuestas

200

Documentos

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
POST/api/clientes/{id}/documentosSube un documento al expediente
subirDocumentoClientemultipart

Un archivo por petición. Si el operador selecciona cinco, el frontend hace cinco llamadas con progreso independiente: es más simple del lado del backend y permite reintentar solo la que falló (system-design §5.2, acuerdo #21).

La validación de tipo y tamaño se hace también en el navegador para no subir 40 MB en vano, pero el servidor debe revalidar: la validación del cliente es cortesía, no control.

CRÍTICO (acuerdos #22 y #23): el límite de spring.servlet.multipart debe coincidir con el de la REGLA ABIERTA #12, y el rechazo por tamaño debe devolver 413 ARCHIVO_MUY_GRANDE con el sobre { codigo, mensaje }. Si sale como página HTML del contenedor, el usuario ve un error incomprensible.

Esta operación queda fuera del mecanismo de idempotencia: un documento duplicado es molesto pero inofensivo, y el operador lo borra (§5.4).

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

multipart/form-data  → object

Respuestas

201

Documento cargado

400

ARCHIVO_VACIO · 0 bytes o multipart mal formado

ARCHIVO_VACIO
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
413

ARCHIVO_MUY_GRANDE · Debe salir con el sobre { codigo, mensaje } (acuerdo #23). Si Spring rechaza el multipart por exceder el límite del contenedor, la respuesta suele salir con cuerpo HTML y sin sobre, y el usuario ve un error incomprensible.

ARCHIVO_MUY_GRANDE
415

FORMATO_NO_ADMITIDO · La respuesta nombra los formatos válidos

FORMATO_NO_ADMITIDO
GET/api/documentos/{id}Metadatos de un documento
obtenerDocumento

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Respuestas

200

Documento

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
PUT/api/documentos/{id}Reclasifica un documento sin cambiar el archivo
actualizarDocumento

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

application/json  → object

Respuestas

200

Documento actualizado

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
DELETE/api/documentos/{id}Baja lógica de un documento
eliminarDocumento

Requiere permiso ELIMINAR. Es baja lógica (activo = false), no borrado físico: el campo activo del modelo v1.1 existe justamente para eso.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Respuestas

204

Documento dado de baja

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
GET/api/documentos/{id}/contenidoDescarga o previsualiza el archivo, con la sesión validada
obtenerContenidoDocumento

Nunca una URL pública adivinable (RNF-07). Este endpoint valida sesión y permiso en cada llamada, aunque la lista ya se haya cargado: el token puede haber expirado entre una cosa y otra.

Como <img src> y <iframe src> no envían el encabezado Authorization, el frontend descarga los bytes y los convierte en blob: desde TypeScript (opción (a) de system-design §2.4). Si en pruebas resulta pesado para archivos de varios MB, se evaluará una URL firmada de vida corta.

Content-Disposition: attachment con el nombre original si descarga=true; inline en caso contrario.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req
descargaquerybooleanopc

Respuestas

200

Bytes del archivo

401

TOKEN_EXPIRADO · El frontend redirige a login conservando la ruta de retorno

TOKEN_EXPIRADO
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
POST/api/documentos/{id}/reemplazoReemplaza el archivo de un documento
reemplazarDocumentomultipart

Acuerdo #24: el documento anterior se conserva inactivo, no se sustituye sin rastro. Aprovecha el campo activo del modelo y permite ofrecer un historial de versiones en la pantalla 5.

La respuesta devuelve el documento nuevo; el anterior queda accesible con ?incluirInactivos=true.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

multipart/form-data  → object

Respuestas

201

Documento reemplazado

400

ARCHIVO_VACIO · 0 bytes o multipart mal formado

ARCHIVO_VACIO
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
413

ARCHIVO_MUY_GRANDE · Debe salir con el sobre { codigo, mensaje } (acuerdo #23). Si Spring rechaza el multipart por exceder el límite del contenedor, la respuesta suele salir con cuerpo HTML y sin sobre, y el usuario ve un error incomprensible.

ARCHIVO_MUY_GRANDE
415

FORMATO_NO_ADMITIDO · La respuesta nombra los formatos válidos

FORMATO_NO_ADMITIDO
GET/api/clientes/{id}/expediente/estadoEstado de completitud del expediente
obtenerEstadoExpediente

REGLA ABIERTA #13 — solo tiene sentido si el cliente decide que un expediente incompleto BLOQUEA el alta de crédito. Si se resuelve como «solo advertir», este endpoint se retira y basta con totalDocumentos en la ficha del cliente.

Mientras no se resuelva, el frontend maqueta la advertencia, que es lo reversible: convertir un aviso en un bloqueo es una línea; quitar un bloqueo mal puesto genera una discusión con el cliente.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Respuestas

200

Estado del expediente

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO

Préstamos y amortización

7 operaciones

Pantallas 6 y 8.

POST/api/prestamos/simulacionSimula el plan de pagos sin persistir nada
simularAmortizacion

Sostiene la vista previa en vivo del alta de crédito (pantalla 6) y es además el oráculo contra el que el frontend valida su motor espejo en TypeScript (system-design §2.2).

El frontend lo llama con debounce de 400 ms mientras el operador teclea, y pinta primero su cálculo local para respetar RNF-04 (< 200 ms). Si difieren, manda el backend y la discrepancia se registra en consola, nunca se le muestra al usuario como conflicto.

Fórmula documentada para CAPITAL_MAS_INTERES (Anexo A §A.3): capitalPorCuota = capital / numeroCuotas · interesPorCuota = capital × tasaInteres · totalCuota = capitalPorCuota + interesPorCuota. Interés simple sobre capital original, no sobre saldo insoluto.

Fechas: SEMANAL +7 días · QUINCENAL +15 días · MENSUAL REGLA ABIERTA #8 (+30 días como en Access, o mes calendario).

REGLA ABIERTA #3 — para SOLO_INTERES falta definir cómo y cuándo se recupera el capital. Afecta a 6 créditos históricos y la vista previa no puede mostrar el plan hasta cerrarlo.

Cuerpo de la petición req

application/json  → PeticionSimulacion

Respuestas

200

Plan simulado

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
409

MONTO_INVALIDO · Monto menor o igual a cero, o que excede lo permitido

MONTO_INVALIDO
GET/api/prestamosListado de créditos
listarPrestamos

Parámetros

NombreEnTipoDescripción
clienteIdqueryinteger · int64opc
rutaIdqueryinteger · int64opc
statusqueryPrestamoStatusopc
pagequeryintegeropc

Índice de página, base 0

sizequeryintegeropc
ordenquerystringopc

Campo y dirección: fechaVencimiento,asc

Respuestas

200

Página de créditos

POST/api/prestamosRegistra el crédito y genera el plan de pagos completo
crearPrestamoClave-Idempotencia

En una sola transacción: crea el Prestamo con el snapshot de condiciones (tasa, periodicidad, esquema y número de cuotas quedan congelados en el crédito), genera todas las Cuota, registra el Movimiento de otorgamiento y el MovimientoCaja de egreso (system-design §6.3).

Requiere Clave-Idempotencia (§5.4).

REGLA ABIERTA #13 — si se resuelve como bloqueo, este endpoint debe revalidar el expediente y devolver 409 EXPEDIENTE_INCOMPLETO con la lista de tipos faltantes en campos[].

Los avales NO se capturan aquí. Aval.prestamoId exige que el préstamo exista primero, así que el flujo es en dos pasos (acuerdo #25). Si el backend prefiere recibirlos como arreglo anidado, funciona igual y ahorra una pantalla intermedia, pero el alta se vuelve una transacción más grande.

Parámetros

NombreEnTipoDescripción
Clave-Idempotenciaheaderstring · uuidreq

UUID generado por el frontend. Si llega una clave ya procesada, se devuelve el resultado original en lugar de crear un registro nuevo (§5.4).

Un doble clic, una reconexión o un reintento del navegador no deben producir dos pagos: un pago duplicado ensucia cuotas, saldos y caja a la vez.

Implementado en el backend como la entidad IdempotenciaOperacion (clave única, hashSolicitud, httpStatus, respuestaJson, expiraEn). Dos consecuencias que el frontend necesita acordadas:

  • hashSolicitud implica que la misma clave con un cuerpo distinto es un caso detectable. Regla del frontend: una clave por operación, no por pantalla. Si el operador corrige el monto tras un fallo, es una operación nueva y genera clave nueva; solo el reintento del mismo envío reutiliza la clave. Falta acordar qué responde el servidor ante clave repetida con cuerpo distinto — se propone 409 CONFLICTO_CONCURRENCIA con mensaje explícito, no un 400 genérico.
  • expiraEn implica una ventana de retención. Falta acordar su duración. Debe superar con holgura el tiempo que un operador tarda en reintentar tras perder la red; se propone 24 horas.

Cuerpo de la petición req

application/json  → PeticionPrestamo

Respuestas

201

Crédito creado con su plan de pagos

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
409

EXPEDIENTE_INCOMPLETO (REGLA ABIERTA #13). Los tipos faltantes viajan en campos[] para que el frontend pueda listarlos y ofrecer ir al expediente.

EXPEDIENTE_INCOMPLETO
GET/api/prestamos/{id}Ficha de un crédito
obtenerPrestamo

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Respuestas

200

Crédito

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
GET/api/prestamos/{id}/amortizacionTabla de amortización con estado, saldo, atraso y mora al día
obtenerAmortizacion

El estado de cada cuota es derivado, nunca almacenado (modelo v1.1 §2.14). Llega ya resuelto; el frontend solo pinta el badge.

VENCIDA tiene precedencia sobre PARCIAL (acuerdo #15): una cuota vencida con abono parcial se pinta roja, porque el cobrador necesita verla como pendiente de cobro, no como avanzada.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req
estadoqueryEstadoCuotaopc

Filtra por estado derivado. Sostiene las pestañas Todas / Pendientes / Vencidas

Respuestas

200

Plan de pagos

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
409

PRESTAMO_SIN_PLAN · Crédito migrado sin cuotas generadas. Son 24 casos conocidos (Anexo A §A.9). El frontend muestra un aviso explicativo y no ofrece cobro.

PRESTAMO_SIN_PLAN
GET/api/prestamos/{id}/historialCuotas y pagos aplicados, para la pantalla 9
obtenerHistorialPrestamo

Los pagos cancelados siguen apareciendo, marcados como tales, con su fecha de cancelación y el usuario que la hizo. Es requisito de auditoría (system-design §3.3): un pago cancelado no desaparece.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Respuestas

200

Historial

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
PATCH/api/prestamos/{id}/statusCambia el estado del crédito (cancelación)
cambiarStatusPrestamo

Requiere permiso ELIMINAR. La interfaz de esta entrega opera únicamente ACTIVO, LIQUIDADO, VENCIDO y CANCELADO; los demás valores del enum existen en el modelo sin flujo asociado (Anexo C §C.2).

Acuerdo #13 pendiente: si VENCIDO es un estado real que alguien asigna o una condición derivada («tiene al menos una cuota vencida»). El frontend maquetó la segunda lectura, que es lo que hace el Access en la práctica.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

application/json  → object

Respuestas

200

Estado actualizado

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO

Avales

4 operaciones

Pantalla 7 · Anexo C §C.1.2.

GET/api/prestamos/{id}/avalesAvales de un crédito
listarAvales

El aval avala un crédito, no a una persona. Si el mismo avalista respalda dos créditos del mismo cliente, son dos registros. Por eso la pantalla 7 vive dentro del crédito y no dentro de la ficha del cliente (system-design §4.4).

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Respuestas

200

Avales

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
POST/api/prestamos/{id}/avalesAgrega un aval al crédito
crearAval

Un crédito admite cero, uno o varios avales; ni el contrato ni la pantalla imponen un máximo. Si el negocio quiere un tope, es una validación de servidor y un mensaje, no un cambio de estructura.

Acuerdo #26: se pueden agregar y editar avales con el crédito ya ACTIVO, porque los datos de contacto cambian y hay que poder corregirlos.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

application/json  → PeticionAval

Respuestas

201

Aval creado

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
PUT/api/avales/{id}Edita un aval
actualizarAval

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

application/json  → PeticionAval

Respuestas

200

Aval actualizado

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
DELETE/api/avales/{id}Elimina un aval
eliminarAval

Requiere permiso ELIMINAR.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Respuestas

204

Aval eliminado

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO

Cobranza y pagos

8 operaciones

Pantallas 9 a 14. El grupo más delicado del contrato.

POST/api/prestamos/{id}/pagos/previsualizacionCalcula la distribución de un pago sin persistir nada
previsualizarPago

Es la pieza clave de la pantalla 10. El operador teclea el monto recibido y necesita ver, antes de confirmar, qué cuotas se liquidan y cuánto va a mora, interés y capital.

Sin este endpoint el frontend tendría que reimplementar la regla de distribución en TypeScript, que es exactamente lo que system-design §2.2 dice que no debe pasar. Si el backend prefiere resolverlo de otra forma, cualquier mecanismo que devuelva la distribución sin persistir sirve igual.

REGLA ABIERTA #2 — orden de aplicación. Se propone mora → interés → capital → saldo a favor, conforme a la práctica habitual del sector, pero debe confirmarlo el cliente.

REGLA ABIERTA #5condonarMora. Si la mora no es condonable por el operador, el campo se retira del contrato y de la pantalla.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

application/json  → PeticionPrevisualizacionPago

Respuestas

200

Distribución simulada

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
409

Conflicto de negocio al cobrar: MONTO_INVALIDO, PRESTAMO_LIQUIDADO, PRESTAMO_SIN_PLAN o CONFLICTO_CONCURRENCIA. Se presenta como aviso dentro del panel de cobro, que no se cierra.

CONFLICTO_CONCURRENCIA deja de ser hipotético: Prestamo, Cuota, Pago, MovimientoCaja y ClienteDocumento llevan @Version, y el plan del backend incluye «validar concurrencia de dos pagos simultáneos» en la semana 8. Con dos personas cobrando la misma ruta es un caso real, no de laboratorio. El frontend recarga el historial, vuelve a previsualizar y avisa de que la distribución cambió; nunca reintenta en silencio con la misma clave de idempotencia.

MONTO_INVALIDOPRESTAMO_LIQUIDADOPRESTAMO_SIN_PLANCONFLICTO_CONCURRENCIA
GET/api/prestamos/{id}/pagosPagos registrados sobre un crédito
listarPagosPrestamo

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req
pagequeryintegeropc

Índice de página, base 0

sizequeryintegeropc

Respuestas

200

Página de pagos

POST/api/prestamos/{id}/pagosRegistra un pago y lo distribuye sobre las cuotas
registrarPagoClave-Idempotencia

El flujo central del sistema. Es donde se cruzan cuotas, saldos, estado de cuenta y caja. Los 8 pasos van en una sola transacción (system-design §6.4): crea Pago, distribuye, crea PagoAplicacion, acumula en Cuota, actualiza saldos de Prestamo, registra Movimiento, registra un único MovimientoCaja de tipo INGRESO_PAGO_PRESTAMO con utilidad = interés + mora efectivamente cobrados, e incrementa el saldo a favor si hubo excedente.

Si algo falla en el paso del movimiento de caja, no puede quedar el pago aplicado sin movimiento: el cuadre de la caja del día es lo primero que el cliente revisa.

La respuesta trae la distribución final, no solo el pagoId: con eso el frontend pinta el historial actualizado y arma el recibo sin una segunda consulta. Si difiere de la previsualización —porque otro usuario cobró en medio— manda la respuesta, y el frontend avisa de que cambió.

No existe POST /api/cuotas/{id}/pago. El pago se registra contra el crédito, no contra una cuota, porque el modelo permite que cubra varias. El «abono parcial» de la pantalla 11 no es un endpoint distinto: es el mismo pago con un monto menor al total de la cuota. Una operación, dos pantallas.

Requiere Clave-Idempotencia. Un doble clic, una reconexión o un reintento del navegador no deben producir dos pagos: es el riesgo más caro del sistema.

REGLA ABIERTA #4 — si el abono acumulado alcanza el monto de la cuota, ¿se cierra sola o la cierra el operador? REGLA ABIERTA #6 — ¿el saldo a favor se aplica solo al siguiente vencimiento o se conserva hasta que el operador decida?

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req
Clave-Idempotenciaheaderstring · uuidreq

UUID generado por el frontend. Si llega una clave ya procesada, se devuelve el resultado original en lugar de crear un registro nuevo (§5.4).

Un doble clic, una reconexión o un reintento del navegador no deben producir dos pagos: un pago duplicado ensucia cuotas, saldos y caja a la vez.

Implementado en el backend como la entidad IdempotenciaOperacion (clave única, hashSolicitud, httpStatus, respuestaJson, expiraEn). Dos consecuencias que el frontend necesita acordadas:

  • hashSolicitud implica que la misma clave con un cuerpo distinto es un caso detectable. Regla del frontend: una clave por operación, no por pantalla. Si el operador corrige el monto tras un fallo, es una operación nueva y genera clave nueva; solo el reintento del mismo envío reutiliza la clave. Falta acordar qué responde el servidor ante clave repetida con cuerpo distinto — se propone 409 CONFLICTO_CONCURRENCIA con mensaje explícito, no un 400 genérico.
  • expiraEn implica una ventana de retención. Falta acordar su duración. Debe superar con holgura el tiempo que un operador tarda en reintentar tras perder la red; se propone 24 horas.

Cuerpo de la petición req

application/json  → PeticionPago

Respuestas

201

Pago registrado y aplicado

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
409

Conflicto de negocio al cobrar: MONTO_INVALIDO, PRESTAMO_LIQUIDADO, PRESTAMO_SIN_PLAN o CONFLICTO_CONCURRENCIA. Se presenta como aviso dentro del panel de cobro, que no se cierra.

CONFLICTO_CONCURRENCIA deja de ser hipotético: Prestamo, Cuota, Pago, MovimientoCaja y ClienteDocumento llevan @Version, y el plan del backend incluye «validar concurrencia de dos pagos simultáneos» en la semana 8. Con dos personas cobrando la misma ruta es un caso real, no de laboratorio. El frontend recarga el historial, vuelve a previsualizar y avisa de que la distribución cambió; nunca reintenta en silencio con la misma clave de idempotencia.

MONTO_INVALIDOPRESTAMO_LIQUIDADOPRESTAMO_SIN_PLANCONFLICTO_CONCURRENCIA
GET/api/pagos/{id}Detalle de un pago con sus aplicaciones
obtenerPago

PagoAplicacion es lo que permite que el recibo impreso diga exactamente cuánto fue a capital, interés y mora. Sin ese detalle, el recibo sería un total opaco (system-design §4.4).

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Respuestas

200

Pago con detalle

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
POST/api/pagos/{id}/cancelacionCancela un pago registrado por error
cancelarPagoClave-Idempotencia

En una sola transacción: revierte las PagoAplicacion, recalcula las cuotas afectadas, ajusta los saldos del crédito, anula o compensa el movimiento de caja y marca el pago como CANCELADO.

El pago cancelado se conserva y sigue visible en el historial: es requisito de auditoría.

REGLA ABIERTA #7 — qué perfil puede cancelar y con qué límite de tiempo. Si la ventana ya pasó, se espera 409 CANCELACION_NO_PERMITIDA con un mensaje que explique el motivo — no un 403 genérico. El operador necesita entender por qué no puede.

Si la reversión genera un movimiento de caja compensatorio en lugar de anular el original, hay que avisarlo: cambia lo que muestra la pantalla 15.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req
Clave-Idempotenciaheaderstring · uuidreq

UUID generado por el frontend. Si llega una clave ya procesada, se devuelve el resultado original en lugar de crear un registro nuevo (§5.4).

Un doble clic, una reconexión o un reintento del navegador no deben producir dos pagos: un pago duplicado ensucia cuotas, saldos y caja a la vez.

Implementado en el backend como la entidad IdempotenciaOperacion (clave única, hashSolicitud, httpStatus, respuestaJson, expiraEn). Dos consecuencias que el frontend necesita acordadas:

  • hashSolicitud implica que la misma clave con un cuerpo distinto es un caso detectable. Regla del frontend: una clave por operación, no por pantalla. Si el operador corrige el monto tras un fallo, es una operación nueva y genera clave nueva; solo el reintento del mismo envío reutiliza la clave. Falta acordar qué responde el servidor ante clave repetida con cuerpo distinto — se propone 409 CONFLICTO_CONCURRENCIA con mensaje explícito, no un 400 genérico.
  • expiraEn implica una ventana de retención. Falta acordar su duración. Debe superar con holgura el tiempo que un operador tarda en reintentar tras perder la red; se propone 24 horas.

Cuerpo de la petición req

application/json  → object

Respuestas

200

Pago cancelado

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
409

CANCELACION_NO_PERMITIDA o PAGO_YA_CANCELADO. Debe explicar el motivo —no un 403 genérico—: el operador necesita entender por qué no puede.

CANCELACION_NO_PERMITIDAPAGO_YA_CANCELADO
POST/api/prestamos/{id}/abonos-capitalAbono directo al capital, fuera del plan de cuotas
registrarAbonoCapitalClave-Idempotencia

REGLA ABIERTA #1 — BLOQUEANTE. Determina el comportamiento del motor de amortización y debe cerrarse antes de aprobar el Anexo A. Las tres opciones están en el enum aplicacion:

  • REDUCE_CUOTAS — reduce el número de cuotas restantes, manteniendo el monto
  • REDUCE_MONTO — reduce el monto de las cuotas restantes, manteniendo el número
  • SOLO_CAJA — se registra en caja sin alterar el plan de pagos vigente

Dato del sistema actual: existen 5 movimientos de caja con el concepto Abona capital <cliente>, lo que confirma que la operación genera un registro en caja, pero no es concluyente respecto de su efecto sobre el plan de pagos.

Cuando el cliente elija, queda un valor y los otros dos se retiran del contrato y de la pantalla 12.

La respuesta devuelve el plan resultante para que la pantalla pueda mostrar el antes y el después.

Requiere Clave-Idempotencia.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req
Clave-Idempotenciaheaderstring · uuidreq

UUID generado por el frontend. Si llega una clave ya procesada, se devuelve el resultado original en lugar de crear un registro nuevo (§5.4).

Un doble clic, una reconexión o un reintento del navegador no deben producir dos pagos: un pago duplicado ensucia cuotas, saldos y caja a la vez.

Implementado en el backend como la entidad IdempotenciaOperacion (clave única, hashSolicitud, httpStatus, respuestaJson, expiraEn). Dos consecuencias que el frontend necesita acordadas:

  • hashSolicitud implica que la misma clave con un cuerpo distinto es un caso detectable. Regla del frontend: una clave por operación, no por pantalla. Si el operador corrige el monto tras un fallo, es una operación nueva y genera clave nueva; solo el reintento del mismo envío reutiliza la clave. Falta acordar qué responde el servidor ante clave repetida con cuerpo distinto — se propone 409 CONFLICTO_CONCURRENCIA con mensaje explícito, no un 400 genérico.
  • expiraEn implica una ventana de retención. Falta acordar su duración. Debe superar con holgura el tiempo que un operador tarda en reintentar tras perder la red; se propone 24 horas.

Cuerpo de la petición req

application/json  → PeticionAbonoCapital

Respuestas

201

Abono a capital registrado

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
409

Conflicto de negocio al cobrar: MONTO_INVALIDO, PRESTAMO_LIQUIDADO, PRESTAMO_SIN_PLAN o CONFLICTO_CONCURRENCIA. Se presenta como aviso dentro del panel de cobro, que no se cierra.

CONFLICTO_CONCURRENCIA deja de ser hipotético: Prestamo, Cuota, Pago, MovimientoCaja y ClienteDocumento llevan @Version, y el plan del backend incluye «validar concurrencia de dos pagos simultáneos» en la semana 8. Con dos personas cobrando la misma ruta es un caso real, no de laboratorio. El frontend recarga el historial, vuelve a previsualizar y avisa de que la distribución cambió; nunca reintenta en silencio con la misma clave de idempotencia.

MONTO_INVALIDOPRESTAMO_LIQUIDADOPRESTAMO_SIN_PLANCONFLICTO_CONCURRENCIA
GET/api/cobranza/vencidasCuotas con fecha de vencimiento cumplida y saldo pendiente
listarCuotasVencidas

Pantalla 13 y base del formato impreso «Listado de ruta». Es la pantalla estrella del sistema y la lista de trabajo del cobrador.

Parámetros

NombreEnTipoDescripción
rutaIdqueryinteger · int64opc
fechaquerystring · dateopc

Fecha de corte. Por omisión, hoy

clienteIdqueryinteger · int64opc
pagequeryintegeropc

Índice de página, base 0

sizequeryintegeropc
ordenquerystringopc

Campo y dirección: fechaVencimiento,asc

Respuestas

200

Cuotas vencidas

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
GET/api/cobranza/proximasCuotas por vencer dentro de los días configurados
listarCuotasProximas

Pantalla 14. El valor por omisión de dias es Configuracion.diasAnticipacionAviso (hoy 3, Anexo A §A.7). El frontend lo muestra como cualquier otro filtro: visible y editable, nunca oculto.

Los recordatorios se derivan de las cuotas; no existe una tabla de recordatorios (modelo v1.1 §1.7).

Parámetros

NombreEnTipoDescripción
rutaIdqueryinteger · int64opc
diasqueryintegeropc

Días de anticipación. Por omisión, el valor de Configuracion

pagequeryintegeropc

Índice de página, base 0

sizequeryintegeropc
ordenquerystringopc

Campo y dirección: fechaVencimiento,asc

Respuestas

200

Cuotas próximas a vencer

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO

Caja

4 operaciones

Pantalla 15 · Anexo A §A.4.

GET/api/caja/movimientosMovimientos de caja del negocio
listarMovimientosCaja

naturaleza viene derivada del prefijo de tipoMovimiento, para que el frontend no tenga que parsear el nombre del enum para saber el signo (system-design §4.2).

Cuando el movimiento nació de un pago, la respuesta trae clienteNombre y prestamoId: es la trazabilidad que el Access no tenía, porque allí el nombre viajaba concatenado dentro del texto del concepto.

Parámetros

NombreEnTipoDescripción
desdequerystring · dateopc
hastaquerystring · dateopc
tipoqueryTipoMovimientoCajaopc
soloManualesquerybooleanopc

Solo los movimientos con generadoPorSistema = false

pagequeryintegeropc

Índice de página, base 0

sizequeryintegeropc
ordenquerystringopc

Campo y dirección: fechaVencimiento,asc

Respuestas

200

Página de movimientos de caja

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
POST/api/caja/movimientosRegistra un movimiento de caja manual
crearMovimientoCajaClave-Idempotencia

Solo movimientos manuales: aportaciones de capital, retiros y gastos operativos. El frontend no crea los movimientos derivados de pagos ni de altas de crédito: esos los genera el backend como efecto de la operación (Anexo A §A.4).

REGLA ABIERTA #9 — dónde se registran hoy los gastos operativos del negocio. En los 9 meses de la base entregada no existe ningún gasto operativo capturado, y eso define qué significa «utilidad» en 2 de los 6 indicadores del tablero.

Requiere Clave-Idempotencia.

Parámetros

NombreEnTipoDescripción
Clave-Idempotenciaheaderstring · uuidreq

UUID generado por el frontend. Si llega una clave ya procesada, se devuelve el resultado original en lugar de crear un registro nuevo (§5.4).

Un doble clic, una reconexión o un reintento del navegador no deben producir dos pagos: un pago duplicado ensucia cuotas, saldos y caja a la vez.

Implementado en el backend como la entidad IdempotenciaOperacion (clave única, hashSolicitud, httpStatus, respuestaJson, expiraEn). Dos consecuencias que el frontend necesita acordadas:

  • hashSolicitud implica que la misma clave con un cuerpo distinto es un caso detectable. Regla del frontend: una clave por operación, no por pantalla. Si el operador corrige el monto tras un fallo, es una operación nueva y genera clave nueva; solo el reintento del mismo envío reutiliza la clave. Falta acordar qué responde el servidor ante clave repetida con cuerpo distinto — se propone 409 CONFLICTO_CONCURRENCIA con mensaje explícito, no un 400 genérico.
  • expiraEn implica una ventana de retención. Falta acordar su duración. Debe superar con holgura el tiempo que un operador tarda en reintentar tras perder la red; se propone 24 horas.

Cuerpo de la petición req

application/json  → PeticionMovimientoCaja

Respuestas

201

Movimiento registrado

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
PUT/api/caja/movimientos/{id}Edita un movimiento de caja manual
actualizarMovimientoCaja

Solo si generadoPorSistema = false. Un movimiento generado por un pago o por un alta de crédito no es editable: se corrige cancelando la operación que lo originó. Se espera 409 si se intenta.

Parámetros

NombreEnTipoDescripción
idpathinteger · int64req

Cuerpo de la petición req

application/json  → PeticionMovimientoCaja

Respuestas

200

Movimiento actualizado

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
409

El movimiento fue generado por el sistema y no es editable

GET/api/caja/resumenTotales de caja del periodo
obtenerResumenCaja

Parámetros

NombreEnTipoDescripción
desdequerystring · dateopc
hastaquerystring · dateopc

Respuestas

200

Resumen de caja

Reportes e impresión

5 operaciones

Anexo B §B.1 (9 reportes) y §B.2 (7 formatos). Pantallas 16 y 17.

GET/api/reportes/{clave}Datos de un reporte, para pintar en pantalla
obtenerReporte

Un solo par de endpoints cubre los 9 reportes: cada reporte nuevo es configuración, no código, de los dos lados.

Los filtros aplicables dependen de clave y están en el Anexo B §B.1. Se envían como parámetros de consulta libres; el backend valida los que aplican a cada reporte y responde 400 VALIDACION con el nombre del filtro si alguno no corresponde.

Los totales los calcula el backend y vienen en totales: el frontend no suma columnas (system-design §2.2).

Parámetros

NombreEnTipoDescripción
clavepathstringreq

Las 9 claves del Anexo B §B.1

desdequerystring · dateopc
hastaquerystring · dateopc
clienteIdqueryinteger · int64opc
prestamoIdqueryinteger · int64opc
rutaIdqueryinteger · int64opc
anioqueryintegeropc
mesqueryintegeropc
pagequeryintegeropc

Índice de página, base 0

sizequeryintegeropc

Respuestas

200

Datos del reporte

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
GET/api/reportes/{clave}/pdfReporte en PDF, generado en el servidor
obtenerReportePdf

Requisito contractual (Anexo B §B.4): el PDF se genera en el servidor a partir de la misma plantilla que se imprime, de modo que el archivo descargado y el documento en papel son idénticos. No es preferencia.

Parámetros

NombreEnTipoDescripción
clavepathstringreq

Las 9 claves del Anexo B §B.1

desdequerystring · dateopc
hastaquerystring · dateopc
clienteIdqueryinteger · int64opc
prestamoIdqueryinteger · int64opc
rutaIdqueryinteger · int64opc
anioqueryintegeropc

Respuestas

200

Documento PDF

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
GET/api/reportes/{clave}/excelReporte en Excel
obtenerReporteExcel

Parámetros

NombreEnTipoDescripción
clavepathstringreq

Las 9 claves del Anexo B §B.1

desdequerystring · dateopc
hastaquerystring · dateopc
clienteIdqueryinteger · int64opc
prestamoIdqueryinteger · int64opc
rutaIdqueryinteger · int64opc
anioqueryintegeropc

Respuestas

200

Libro de Excel

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
GET/api/impresion/{formato}Datos de un formato de cobranza, para la vista previa en pantalla
obtenerDatosImpresion

La respuesta incluye la identidad del negocio (nombre, lema y logotipo de Datos del negocio, Anexo A Módulo 7), porque encabeza todos los formatos.

El frontend renderiza la vista previa con @media print para que se vea al instante; el archivo lo genera el backend (Anexo B §B.4).

Parámetros

NombreEnTipoDescripción
formatopathstringreq

Los 7 formatos del Anexo B §B.2

rutaIdqueryinteger · int64opc
fechaquerystring · dateopc
prestamoIdqueryinteger · int64opc
cuotaIdqueryinteger · int64opc
pagoIdqueryinteger · int64opc

Respuestas

200

Datos del formato

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO
GET/api/impresion/{formato}/pdfFormato de cobranza en PDF
obtenerImpresionPdf

REGLA ABIERTA #11 — falta confirmar si los tickets se imprimen en impresora térmica de rollo o en hoja carta. El parámetro presentacion soporta ambos desde el primer día; la confirmación decide cuál es el valor por omisión, no si se construye.

Parámetros

NombreEnTipoDescripción
formatopathstringreq

Los 7 formatos del Anexo B §B.2

presentacionquerystringopc
rutaIdqueryinteger · int64opc
fechaquerystring · dateopc
prestamoIdqueryinteger · int64opc
cuotaIdqueryinteger · int64opc
pagoIdqueryinteger · int64opc

Respuestas

200

Documento PDF

404

NO_ENCONTRADO · Estado vacío con retorno al listado

NO_ENCONTRADO

Tablero y configuración

6 operaciones

Anexo B §B.3 y Módulo 7 del Anexo A.

GET/api/dashboardLos 6 indicadores del Anexo B §B.3, en una sola llamada
obtenerTablero

Una petición, no seis. Seis peticiones en paralelo para pintar seis tarjetas es la clase de decisión que se paga en percepción de lentitud desde el primer día (system-design §5.2).

Definiciones exactas del Anexo B §B.3:

  • totalEfectivo — suma algebraica de todos los movimientos de caja
  • carteraActiva — saldo pendiente de todos los créditos con cuotas por cobrar
  • cobranzaPeriodo — pagos recibidos en el periodo seleccionado
  • cuotasVencidas — número e importe de las cuotas vencidas con saldo
  • proyeccionMes — cuotas cuyo vencimiento cae en el mes seleccionado
  • utilidadMes — intereses y mora efectivamente cobrados

Advertencia que el frontend rotula en la interfaz: «Utilidad del mes» no es ingresos menos gastos y «Total en efectivo» no descuenta gastos operativos, porque el negocio no los captura (Anexo A §A.9). Dos de los seis indicadores cambian de significado si se resuelve la REGLA ABIERTA #9. El tablero no es un estado de resultados y así se rotula.

Parámetros

NombreEnTipoDescripción
anioqueryintegeropc
mesqueryintegeropc
rutaIdqueryinteger · int64opc

Respuestas

200

Indicadores del tablero

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
GET/api/configuracionConfiguración operativa
obtenerConfiguracion

Respuestas

200

Configuración

PUT/api/configuracionActualiza la configuración operativa
actualizarConfiguracion

Cuerpo de la petición req

application/json  → Configuracion

Respuestas

200

Configuración actualizada

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
GET/api/empresaDatos del negocio usados en los formatos impresos
obtenerEmpresa

Respuestas

200

Empresa

PUT/api/empresaActualiza los datos del negocio
actualizarEmpresa

Cuerpo de la petición req

application/json  → PeticionEmpresa

Respuestas

200

Empresa actualizada

400

VALIDACION · El nombre en campos[].campo debe coincidir con el del cuerpo de la petición: así el frontend marca el control automáticamente, sin mantener un mapa de traducción.

VALIDACION
403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
POST/api/empresa/logoSube el logotipo del negocio
subirLogoEmpresamultipart

Es la única subida de archivo fuera del expediente digital. Se resuelve como caso único, no como componente del sistema de diseño (DS §10).

El logotipo debe imprimirse en monocromo a 12 mm de altura máxima en la hoja térmica; si no hay uno apto, los formatos usan solo el nombre (DS §6.3).

Cuerpo de la petición req

multipart/form-data  → object

Respuestas

201

Logotipo actualizado

403

SIN_PERMISO · Ocultar un botón no es autorizar. Cada endpoint valida el permiso por su cuenta; si el frontend falla y muestra una acción indebida, la respuesta correcta es este 403, no que la operación pase.

SIN_PERMISO
413

ARCHIVO_MUY_GRANDE · Debe salir con el sobre { codigo, mensaje } (acuerdo #23). Si Spring rechaza el multipart por exceder el límite del contenedor, la respuesta suele salir con cuerpo HTML y sin sobre, y el usuario ve un error incomprensible.

ARCHIVO_MUY_GRANDE
415

FORMATO_NO_ADMITIDO · La respuesta nombra los formatos válidos

FORMATO_NO_ADMITIDO

11Catálogo de errores esperados

18 códigos estables. Todo error viaja en el mismo sobre; el frontend decide el texto visible a partir del codigo con un diccionario único. Si llega un código desconocido, muestra el mensaje y registra el código.

{
  "codigo": "VALIDACION",
  "mensaje": "Revisa los datos capturados",
  "campos": [ { "campo": "capital", "mensaje": "Debe ser mayor que cero" } ]
}
Contrato del nombre del campo: el campo en campos[] debe coincidir con el del cuerpo de la petición — así el frontend marca el control automáticamente, sin mapa de traducción. Y para EXPEDIENTE_INCOMPLETO, los tipos faltantes viajan en campos[] para poder listarlos y ofrecer ir al expediente.
Caso especial de archivos: si Spring rechaza un multipart por exceder el límite del contenedor, la respuesta suele salir como página HTML sin el sobre. Debe capturarse para devolver 413 ARCHIVO_MUY_GRANDE con el mismo formato que todo lo demás — y el límite del contenedor debe coincidir con el acordado en la regla abierta #12.
CódigoHTTPCuándo ocurreQué hace el frontend
CREDENCIALES_INVALIDAS 401

Usuario o contraseña incorrectos

Mensaje: «Usuario o contraseña incorrectos»

USUARIO_INACTIVO 403

Usuario deshabilitado

Mensaje distinto: «Usuario deshabilitado. Contacta al administrador» — el operador distingue «me equivoqué» de «me deshabilitaron»

TOKEN_EXPIRADO 401

Sesión vencida

Redirige a login conservando la ruta de retorno

PASSWORD_REQUERIDO 403

Falta el cambio obligatorio de contraseña

Fuerza la pantalla de cambio

SIN_PERMISO 403

Permiso insuficiente — ocultar no es autorizar

«No tienes permiso para esta acción»

NO_ENCONTRADO 404

Recurso inexistente

Estado vacío con retorno al listado

VALIDACION 400

Campos inválidos

Marca cada campo con campos[].campo y campos[].mensaje

MONTO_INVALIDO 409

Monto ≤ 0, o que excede lo permitido

Marca el campo de importe

PRESTAMO_SIN_PLAN 409

Crédito migrado sin cuotas generadas — 24 casos conocidos (Anexo A §A.9)

Aviso explicativo, sin ofrecer cobro

PRESTAMO_LIQUIDADO 409

Cobro sobre préstamo sin saldo

«Este crédito ya está liquidado»

PAGO_YA_CANCELADO 409

Doble cancelación

Refresca y avisa

CANCELACION_NO_PERMITIDA 409

Fuera de la ventana de tiempo (regla abierta #7)

Explica el motivo — nunca un 403 genérico

ARCHIVO_MUY_GRANDE 413

Supera el límite de la regla abierta #12

«El archivo supera el máximo de N MB». Debe salir con el sobre, no como HTML del contenedor

FORMATO_NO_ADMITIDO 415

Extensión o MIME fuera de los permitidos

Nombra los formatos válidos

ARCHIVO_VACIO 400

0 bytes o multipart mal formado

Pide volver a seleccionar

EXPEDIENTE_INCOMPLETO 409

Alta de crédito sin documentos obligatorios (regla abierta #13)

Lista los tipos faltantes en campos[] y ofrece ir al expediente

CONFLICTO_CONCURRENCIA 409

El recurso cambió desde que se cargó — @Version en Prestamo, Cuota, Pago, MovimientoCaja y ClienteDocumento

Recarga, vuelve a previsualizar y avisa. Nunca reintentar en silencio con la misma clave de idempotencia

ERROR_INTERNO 500

Cualquier otro fallo

Mensaje genérico + opción de reintentar

12Enumeraciones

Los valores que viajan por la API en MAYÚSCULAS, tal cual. Los marcados como abiertos dependen de una decisión del cliente (ver reglas abiertas); cuando se cierren, se retira lo que sobre.

Periodicidad

DIARIOSEMANALQUINCENALMENSUALTRIMESTRALANUAL

Días que suma: DIARIO +1 · SEMANAL +7 · QUINCENAL +15 · MENSUAL +30 o mes calendario (REGLA ABIERTA #8)

TipoCalculo

CAPITAL_MAS_INTERESSOLO_INTERES

CAPITAL_MAS_INTERES (533 créditos) · SOLO_INTERES (6 créditos). SALDOS_INSOLUTOS queda fuera del alcance (Anexo C §C.3).

PrestamoStatus

EN_REVISIONAUTORIZADORECHAZADOACTIVOLIQUIDADOVENCIDOREFINANCIADOCANCELADO

La interfaz de esta entrega opera únicamente ACTIVO, LIQUIDADO, VENCIDO y CANCELADO. EN_REVISION, AUTORIZADO, RECHAZADO y REFINANCIADO existen en el modelo sin flujo asociado (Anexo C §C.2).

PagoStatus

APLICADOCANCELADO

TipoPago

EFECTIVOTRANSFERENCIADEPOSITOOTRO

EstadoCuota

PENDIENTEPARCIALVENCIDAPAGADA

Derivado, nunca almacenado (modelo v1.1 §2.14). VENCIDA tiene precedencia sobre PARCIAL: una cuota vencida con abono se pinta roja, porque el cobrador necesita verla como pendiente de cobro (acuerdo #15).

ClienteStatus

ACTIVOINACTIVO

CERRADO (acuerdo #18). No estaba enumerado en el modelo v1.1; el backend lo implementa así en ClienteStatus.java

TipoIdentificacion

INEPASAPORTELICENCIACEDULA_PROFESIONALOTRO

CERRADO (acuerdo #17). No estaba enumerado en el modelo v1.1; el backend lo implementa así en TipoIdentificacion.java

TipoDocumento

IDENTIFICACION_FRENTEIDENTIFICACION_ATRASCOMPROBANTE_DOMICILIOCOMPROBANTE_INGRESOSCURPOTRO

Coinciden exactamente con los tipos admitidos del Anexo C §C.1.1

EstadoExpediente

SIN_DOCUMENTOSINCOMPLETOCOMPLETO

[ABIERTO] Solo si la REGLA ABIERTA #13 se resuelve como bloqueo

TipoMovimiento

OTORGAMIENTO_PRESTAMOPAGOINTERESMORATORIOAJUSTEREFINANCIAMIENTOCANCELACION

Estado de cuenta del cliente (pantalla 9). No confundir con la caja del negocio. REFINANCIAMIENTO está definido sin operación que lo genere (C.2).

TipoMovimientoCaja

INGRESO_PAGO_PRESTAMOINGRESO_CAPITALINGRESO_OTROEGRESO_PRESTAMO_OTORGADOEGRESO_GASTO_OPERATIVOEGRESO_RETIRO_CAPITALEGRESO_OTROAJUSTE_INGRESOAJUSTE_EGRESO

Caja del negocio.

CERRADO (acuerdo #16). El Anexo A §A.4 clasifica el alta de crédito como EGRESO_PRESTAMO_OTORGADO, valor que no existía en el enum del modelo v1.1. Son 2,107 movimientos, el 14 % de la caja. El backend lo incorporó en TipoMovimientoCaja.java y su plan de 8 semanas lo lista como corrección recomendada de la semana 1. El filtro de la pantalla 15 y la agrupación del reporte 5 del Anexo B conservan la categoría.

NaturalezaMovimientoCaja

INGRESOEGRESOAJUSTE

Campo derivado del prefijo de tipoMovimiento. Existe para que el frontend no tenga que parsear el nombre del enum para saber el signo.

AplicacionAbonoCapital

REDUCE_CUOTASREDUCE_MONTOSOLO_CAJA

REGLA ABIERTA #1 — bloqueante. Cuando el cliente elija, quedan uno y se retiran los otros dos

Presentacion

CARTATERMICO_58TERMICO_80

Parámetro de GET /api/impresion/{formato}/pdf. Cuál es el valor por omisión depende de la REGLA ABIERTA #11 (Anexo B §B.2).

AmbitoPeriodicidad

ALTATODAS

Parámetro de GET /api/catalogos/periodicidades: ALTA devuelve las tres del alta; TODAS, las seis del modelo para créditos históricos.

13Esquemas del contrato

Los 70 esquemas de financiera-api-v1.1.0.yaml. Los campos requeridos van marcados; las descripciones conservan las referencias a anexos, reglas y acuerdos, enlazadas.

Errorobject
CampoTipoDescripción
CodigoErrorreq
stringreq

Respaldo en español operativo. El texto que ve el usuario lo decide el frontend a partir del codigo, con un diccionario único. Si llega un código desconocido se muestra este mensaje y se registra el código.

array<object>opc
CodigoErrorenum

Los 18 códigos estables de system-design §5.3

CREDENCIALES_INVALIDASUSUARIO_INACTIVOTOKEN_EXPIRADOPASSWORD_REQUERIDOSIN_PERMISONO_ENCONTRADOVALIDACIONMONTO_INVALIDOPRESTAMO_SIN_PLANPRESTAMO_LIQUIDADOPAGO_YA_CANCELADOCANCELACION_NO_PERMITIDAARCHIVO_MUY_GRANDEFORMATO_NO_ADMITIDOARCHIVO_VACIOEXPEDIENTE_INCOMPLETOCONFLICTO_CONCURRENCIAERROR_INTERNO
Paginaobject
CampoTipoDescripción
integerreq
integerreq
integerreq
integerreq
Periodicidadenum

Días que suma: DIARIO +1 · SEMANAL +7 · QUINCENAL +15 · MENSUAL +30 o mes calendario (REGLA ABIERTA #8)

DIARIOSEMANALQUINCENALMENSUALTRIMESTRALANUAL
TipoCalculoenum

CAPITAL_MAS_INTERES (533 créditos) · SOLO_INTERES (6 créditos). SALDOS_INSOLUTOS queda fuera del alcance (Anexo C §C.3).

CAPITAL_MAS_INTERESSOLO_INTERES
PrestamoStatusenum

La interfaz de esta entrega opera únicamente ACTIVO, LIQUIDADO, VENCIDO y CANCELADO. EN_REVISION, AUTORIZADO, RECHAZADO y REFINANCIADO existen en el modelo sin flujo asociado (Anexo C §C.2).

EN_REVISIONAUTORIZADORECHAZADOACTIVOLIQUIDADOVENCIDOREFINANCIADOCANCELADO
PagoStatusenum
APLICADOCANCELADO
TipoPagoenum
EFECTIVOTRANSFERENCIADEPOSITOOTRO
EstadoCuotaenum

Derivado, nunca almacenado (modelo v1.1 §2.14). VENCIDA tiene precedencia sobre PARCIAL: una cuota vencida con abono se pinta roja, porque el cobrador necesita verla como pendiente de cobro (acuerdo #15).

PENDIENTEPARCIALVENCIDAPAGADA
ClienteStatusenum

CERRADO (acuerdo #18). No estaba enumerado en el modelo v1.1; el backend lo implementa así en ClienteStatus.java

ACTIVOINACTIVO
TipoIdentificacionenum

CERRADO (acuerdo #17). No estaba enumerado en el modelo v1.1; el backend lo implementa así en TipoIdentificacion.java

INEPASAPORTELICENCIACEDULA_PROFESIONALOTRO
TipoDocumentoenum

Coinciden exactamente con los tipos admitidos del Anexo C §C.1.1

IDENTIFICACION_FRENTEIDENTIFICACION_ATRASCOMPROBANTE_DOMICILIOCOMPROBANTE_INGRESOSCURPOTRO
EstadoExpedienteenum

[ABIERTO] Solo si la REGLA ABIERTA #13 se resuelve como bloqueo

SIN_DOCUMENTOSINCOMPLETOCOMPLETO
TipoMovimientoenum

Estado de cuenta del cliente (pantalla 9). No confundir con la caja del negocio. REFINANCIAMIENTO está definido sin operación que lo genere (C.2).

OTORGAMIENTO_PRESTAMOPAGOINTERESMORATORIOAJUSTEREFINANCIAMIENTOCANCELACION
TipoMovimientoCajaenum

Caja del negocio.

CERRADO (acuerdo #16). El Anexo A §A.4 clasifica el alta de crédito como EGRESO_PRESTAMO_OTORGADO, valor que no existía en el enum del modelo v1.1. Son 2,107 movimientos, el 14 % de la caja. El backend lo incorporó en TipoMovimientoCaja.java y su plan de 8 semanas lo lista como corrección recomendada de la semana 1. El filtro de la pantalla 15 y la agrupación del reporte 5 del Anexo B conservan la categoría.

INGRESO_PAGO_PRESTAMOINGRESO_CAPITALINGRESO_OTROEGRESO_PRESTAMO_OTORGADOEGRESO_GASTO_OPERATIVOEGRESO_RETIRO_CAPITALEGRESO_OTROAJUSTE_INGRESOAJUSTE_EGRESO
NaturalezaMovimientoCajaenum

Campo derivado del prefijo de tipoMovimiento. Existe para que el frontend no tenga que parsear el nombre del enum para saber el signo.

INGRESOEGRESOAJUSTE
AplicacionAbonoCapitalenum

REGLA ABIERTA #1 — bloqueante. Cuando el cliente elija, quedan uno y se retiran los otros dos

REDUCE_CUOTASREDUCE_MONTOSOLO_CAJA
PeticionLoginobject
CampoTipoDescripción
stringreq
string · passwordreq
PeticionCambioPasswordobject
CampoTipoDescripción
string · passwordreq
string · passwordreq
RespuestaSesionobject
CampoTipoDescripción
stringreq
string · date-timereq

ISO-8601 con offset

booleanreq

Obligatorio en el primer acceso tras la migración: las contraseñas estaban en texto plano y se cifran con BCrypt (Anexo A §A.2). Mientras sea true, el frontend bloquea la navegación.

objectreq
array<string>req
array<string>req

Ya resuelto: rol más excepciones de UsuarioPermiso, aplanado. El frontend no reconstruye la precedencia entre rol y excepción.

Empresaopc
Usuarioobject
CampoTipoDescripción
integer · int64req
stringreq
string · nullopc
booleanreq

Estado del usuario, no un permiso (Anexo A §A.2)

booleanopc

Campo persistido (Usuario.java). Se pone en true para los 6 usuarios migrados, cuyas contraseñas estaban en texto plano y se cifran con BCrypt (Anexo A §A.2). La pantalla 2 lo muestra como distintivo y permite forzarlo.

array<Role>opc
array<string>opc
string · date-timeopc
string · nullopc
PeticionUsuarioobject
CampoTipoDescripción
stringreq
stringopc
string · nullopc

Solo en el alta. Nunca viaja de vuelta en ninguna respuesta

booleanreq
array<integer · int64>req
Roleobject
CampoTipoDescripción
integer · int64req
stringreq
string · nullopc
Permisoobject
CampoTipoDescripción
integer · int64req
stringreq

Los 5 del Anexo A §A.2. ELIMINAR cubre también la baja de un documento del expediente y la de un aval.

Punto abierto (Ap.1 §2): los 5 permisos vienen del Access, que no tenía expediente ni avales. Con el Anexo C hay dos superficies nuevas sin permiso propio. El frontend asumió la opción (a): ver bajo LISTAR_CLIENTES, modificar bajo EDITAR_HISTORIAL. Consecuencia a mirar de frente: un cobrador podría ver identificaciones y comprobantes de domicilio de todos los clientes. Si eso preocupa al negocio, hacen falta GESTIONAR_EXPEDIENTE y GESTIONAR_AVALES. Lo decide el cliente, no el equipo técnico.

string · nullopc
Rutaobject
CampoTipoDescripción
integer · int64req
stringreq
string · nullopc
booleanreq
integer · nullopc

Para poder avisar antes de desactivar una ruta con cartera

PeticionRutaobject
CampoTipoDescripción
stringreq
stringreq

Obligatoria. Ruta.descripcion es nullable = false en Ruta.java

OpcionTipoMovimientoCajaobjeto compuesto

Compone y extiende: OpcionCatalogo

CampoTipoDescripción
NaturalezaMovimientoCajareq
booleanreq

Si false, el tipo solo lo genera el sistema y no se ofrece en el alta manual

OpcionTipoDocumentoobjeto compuesto

Compone y extiende: OpcionCatalogo

CampoTipoDescripción
booleanopc

REGLA ABIERTA #13. Hoy puede venir siempre en false

ClienteResumenobject
CampoTipoDescripción
integer · int64req
stringreq

Se migra íntegro en un solo campo. No se separa automáticamente en nombre y apellidos: hacerlo sobre cientos de nombres capturados libremente produce errores (Anexo A §A.8).

string · nullopc
string · nullopc
integer · nullopc
string · nullopc

Junto al rutaId, para no resolver el catálogo en cada fila

ClienteStatusreq
integeropc

Columna que hace visible la separación cliente/crédito del Anexo A §A.8

integeropc
numberopc

Suma de saldos de sus créditos activos

integeropc

Documentos vigentes del expediente

EstadoExpediente | nullopc

Solo si la REGLA ABIERTA #13 lo hace necesario

booleanopc

Registro migrado con incidencia de calidad conocida: sin nombre (14 casos), sin fecha de préstamo, forma de pago o ruta (18–20 casos), o cliente marcador de movimientos huérfanos (60 casos). Anexo A §A.9.

ABIERTO — acuerdo #30. Cliente.java no tiene este campo. Sin él, los 14 clientes sin nombre y los 60 movimientos huérfanos se ven como filas defectuosas en lugar de como pendientes de revisión identificados, y el cliente no tiene forma de encontrarlos para corregirlos.

Clienteobjeto compuesto

Compone y extiende: ClienteResumen

CampoTipoDescripción
string · nullopc
string · nullopc
string · nullopc
string · nullopc
TipoIdentificacion | nullopc
string · nullopc

ABIERTO — acuerdo #28. Ver la nota de direccion

string · nullopc

ABIERTO — acuerdo #28. Cliente.java no tiene telefono, direccion ni notas, pero el Anexo A §A.1 define la pantalla 4 como «Datos personales, documento de identificación, contacto, dirección, ruta y notas». Los tres son alcance contratado.

Llama la atención que Aval.java tiene telefono y direccion: el avalista es localizable y el deudor no. Para una financiera de cobranza en campo, es al revés.

string · nullopc

ABIERTO — acuerdo #28. Ver la nota de direccion

string · date-timeopc
string · nullopc
PeticionClienteobject
CampoTipoDescripción
stringreq
stringopc
stringopc
stringopc
stringopc
string · nullopc
TipoIdentificacion | nullopc
stringopc
stringopc
stringopc
stringopc
integer · nullopc
ClienteStatusopc
ClienteDocumentoobject
CampoTipoDescripción
integer · int64req
integer · int64req
TipoDocumentoreq
stringreq
string · nullopc
string · nullopc
integeropc

Para mostrar «2.4 MB» en la lista sin descargar el archivo

string · date-timereq
string · nullopc

Quién lo subió. Útil en un expediente que revisan varias personas

booleanreq

false = sustituido por un reemplazo o dado de baja lógica

integer · nullopc

Documento al que este reemplaza. Corresponde a la FK reemplaza_a_id de ClienteDocumento.java y confirma el acuerdo #24: el reemplazo conserva el anterior inactivo en lugar de sustituirlo sin rastro.

Es lo que permite que la pantalla 5 ofrezca «Ver versiones anteriores» recorriendo la cadena, en lugar de mostrar una lista plana de inactivos.

integer · nullopc

Control de concurrencia optimista (@Version). Ver acuerdo #27

stringreq

Ruta relativa del endpoint de descarga, armada por el backend. El frontend no concatena rutas: si mañana el archivo se mueve a otro almacenamiento, no toca nada.

rutaArchivo del modelo NO debe salir en la respuesta: es una ruta interna del servidor y no le sirve de nada al navegador.

EstadoExpedienteRespuestaobject
CampoTipoDescripción
EstadoExpedientereq
array<TipoDocumento>req
Avalobject
CampoTipoDescripción
integer · int64req
integer · int64req
stringreq

Un solo campo, igual que en Cliente y por la misma razón

string · nullopc
string · nullopc
string · nullopc
TipoIdentificacion | nullopc
string · nullopc
string · date-timereq
string · nullopc
PeticionAvalobject
CampoTipoDescripción
stringreq
stringopc
stringopc
stringopc
TipoIdentificacion | nullopc
stringopc
PeticionSimulacionobject
CampoTipoDescripción
numberreq
numberreq

Fracción por periodo, no porcentaje. 0.0225 = 2.25 %

numberopc

De 557 créditos, uno solo tiene tasa de mora distinta de cero (0.01), y solo 17 de 17,927 movimientos registran mora aplicada. La funcionalidad se implementa completa, pero en la práctica el negocio no la usa.

integerreq
Periodicidadreq
TipoCalculoreq
string · datereq
ResumenSimulacionobject
CampoTipoDescripción
numberreq
numberreq
numberreq
numberreq
numberreq
CuotaSimuladaobject
CampoTipoDescripción
integerreq
string · datereq
numberreq
numberreq
numberreq

La última cuota absorbe el ajuste de redondeo (p. ej. 1,058.37 frente a 1,058.33)

RespuestaSimulacionobject
CampoTipoDescripción
ResumenSimulacionreq
array<CuotaSimulada>req
PeticionPrestamoobject

Sin rutaId. Prestamo.java no tiene relación con Ruta: la ruta vive en Cliente. En Access cliente y crédito eran el mismo registro, así que la ruta parecía del crédito; al separarlos (Anexo A §A.8), la ruta quedó donde corresponde — es la zona de cobranza de una persona, no una condición del préstamo. Los créditos de un cliente comparten su ruta.

PrestamoResumen.rutaId y rutaNombre viajan igualmente en las respuestas, derivados del cliente, para no obligar a una consulta extra por fila. Y GET /api/prestamos?rutaId= filtra por la ruta del cliente.

Consecuencia a vigilar: si la regla abierta #10 concluye que GRUPOS, QUINCENAL y LIQUIDACION no son rutas de cobranza sino clasificaciones del crédito, esta decisión se cae y hace falta una dimensión propia en Prestamo. Es la razón por la que esa regla no es tan menor como parece.

CampoTipoDescripción
integer · int64req
string · nullopc
string · nullopc
string · datereq
numberreq
numberreq
numberopc
integerreq
Periodicidadreq
TipoCalculoreq
stringopc
PrestamoResumenobject
CampoTipoDescripción
integer · int64req
integer · int64req
string · nullopc
integer · nullopc
string · nullopc
string · nullopc
numberreq
numberopc
numberopc
integerreq
Periodicidadreq
TipoCalculoreq
PrestamoStatusreq
numberreq
numberopc
numberopc
numberopc
numberopc
integeropc

Junto con numeroCuotas, sostiene el «5 de 12» que aparece en toda la interfaz

integeropc

Conteo, para el distintivo de riesgo

string · nullopc

Fecha de la siguiente cuota no pagada

booleanopc

Derivado: «tiene al menos una cuota vencida». Es la lectura (b) del acuerdo #13: un crédito con cuotas vencidas sigue siendo ACTIVO; la mora es una condición de sus cuotas, no un cambio de estado del crédito.

integeropc

Conteo, para mostrar el distintivo en la ficha sin traer la lista

booleanopc

false en los 24 créditos migrados sin movimientos (Anexo A §A.9)

integer · nullopc

Control de concurrencia optimista (@Version). Ver acuerdo #27

Prestamoobjeto compuesto

Compone y extiende: PrestamoResumen

CampoTipoDescripción
string · nullopc
string · nullopc
string · nullopc
string · nullopc
numberopc
numberopc
integer · nullopc

Campo creado y nullable. Sin flujo de refinanciamiento (Anexo C §C.2)

string · nullopc
string · date-timeopc
string · nullopc
Cuotaobject
CampoTipoDescripción
integer · int64req
integer · int64req
integerreq
string · datereq
numberopc
numberopc
numberreq
numberopc
numberopc
numberopc
numberopc
string · nullopc
EstadoCuotareq

Derivado por el backend. El frontend solo pinta la marca

numberreq

totalCuota − (capitalPagado + interesPagado + moratorioPagado)

integeropc

Fórmula del Anexo A §A.4: si la cuota está pagada, fechaMovimiento − fechaPago; si está pendiente, fechaActual − fechaPago. Si el resultado es negativo, 0.

numberopc

Mora acumulada a la fecha de consulta, sin persistir. CAPITAL_MAS_INTERES: retornoCapital × tasaMora × diasAtraso. SOLO_INTERES: valorInteres × tasaMora × diasAtraso.

numberopc

saldoPendiente + moraAlDia. Es lo que el panel de cobro muestra como total

integer · nullopc

Control de concurrencia optimista (@Version). Ver acuerdo #27

RespuestaAmortizacionobject
CampoTipoDescripción
PrestamoResumenreq
array<Cuota>req
objectopc
RespuestaHistorialobject
CampoTipoDescripción
PrestamoResumenreq
array<Cuota>req
array<PagoDetalle>req

Incluye los pagos cancelados, marcados como tales. Requisito de auditoría

CuotaCobranzaobjeto compuesto

Compone y extiende: Cuota

CampoTipoDescripción
integer · int64req
stringreq
integer · nullopc
string · nullopc
string · nullopc
PaginaCuotasCobranzaobjeto compuesto

Compone y extiende: Pagina

CampoTipoDescripción
array<CuotaCobranza>req
objectreq

Totales de la consulta completa, no de la página. Es lo que se muestra al pie de la tabla («23 cuotas vencidas · 41,320.00») y lo que el frontend nunca calcula sumando columnas.

PeticionPrevisualizacionPagoobject
CampoTipoDescripción
string · datereq
numberreq
booleanopc

REGLA ABIERTA #5. Si la mora no es condonable, el campo se retira

PeticionPagoobjeto compuesto

Compone y extiende: PeticionPrevisualizacionPago

CampoTipoDescripción
TipoPagoreq
stringopc
stringopc
AplicacionCuotaobject
CampoTipoDescripción
integer · int64req
integerreq
numberreq
numberreq
numberreq
numberreq
EstadoCuotareq
TotalesDistribucionobject
CampoTipoDescripción
numberreq
numberreq
numberreq
numberreq
RespuestaDistribucionPagoobject
CampoTipoDescripción
array<AplicacionCuota>req
TotalesDistribucionreq
numberreq
RespuestaPagoRegistradoobjeto compuesto

Compone y extiende: RespuestaDistribucionPago

CampoTipoDescripción
integer · int64req
integer · int64req

Un pago genera un solo movimiento de caja, por el monto total recibido, con utilidad = interés + mora efectivamente cobrados. No se genera un movimiento aparte por la ganancia, para no duplicar el ingreso (Anexo A §A.4, modelo v1.1 §2.18).

booleanopc

Si true, el frontend prensa el sello LIQUIDADO sobre la tabla de amortización. Ocurre una vez por crédito en toda su vida.

Pagoobject
CampoTipoDescripción
integer · int64req
integer · int64req
integer · int64req
string · nullopc
string · date-timereq
numberreq
TipoPagoreq
string · nullopc
string · nullopc
integer · int64opc
string · nullopc
PagoStatusreq
string · date-timeopc
string · nullopc
string · nullopc
string · nullopc
integer · nullopc

Control de concurrencia optimista (@Version). Ver acuerdo #27

PagoAplicacionobject
CampoTipoDescripción
integer · int64req
integer · int64req
integer · int64req
integeropc
numberreq
numberreq
numberreq
numberopc
string · date-timeopc
PagoDetalleobjeto compuesto

Compone y extiende: Pago

CampoTipoDescripción
array<PagoAplicacion>opc

Es lo que permite que el recibo impreso diga exactamente cuánto fue a capital, interés y mora. Sin esto, el recibo sería un total opaco.

integer · nullopc
PeticionAbonoCapitalobject
CampoTipoDescripción
string · datereq
numberreq
AplicacionAbonoCapitalreq
stringopc
RespuestaAbonoCapitalobject
CampoTipoDescripción
integer · nullopc
integer · int64req
numberreq
array<Cuota>opc

Plan de pagos tras el abono, para que la pantalla 12 muestre el antes y el después

Movimientoobject
CampoTipoDescripción
integer · int64req
integer · int64req
integer · nullopc
TipoMovimientoreq
numberreq
numberreq
numberreq
stringopc
string · nullopc
string · date-timereq
integer · nullopc
MovimientoCajaobject
CampoTipoDescripción
integer · int64req
string · date-timereq
TipoMovimientoCajareq
NaturalezaMovimientoCajareq
stringreq
numberreq
numberopc

En un pago, interés + mora efectivamente cobrados. Se muestra siempre atenuada, nunca destacada: no es un ingreso adicional, es un desglose del mismo importe. Resaltarla induce a leerla como un segundo ingreso, que es justo el error que la regla de utilidad del Anexo A §A.4 quiere evitar.

integer · nullopc
integer · nullopc
string · nullopc

Trazabilidad que el Access no tenía: allí el nombre viajaba dentro del texto del concepto

booleanreq

Determina si la fila es editable desde la pantalla 15

integer · nullopc
string · nullopc
string · date-timeopc
booleanopc

Movimiento migrado con importe de escala anómala (9 casos de hasta 9 dígitos, muy por encima del volumen de cartera). No afectan los saldos de los créditos, pero se marcan para revisión del cliente (Anexo A §A.9).

ABIERTO — acuerdo #30. MovimientoCaja.java no tiene este campo. El Anexo A §A.9 sí exige que estos 9 registros queden «marcados para revisión del cliente». Si el backend no lo añade, el frontend no puede señalarlos y el compromiso del anexo queda sin superficie.

integer · nullopc

Control de concurrencia optimista (@Version). Ver acuerdo #27

PeticionMovimientoCajaobject
CampoTipoDescripción
string · datereq
TipoMovimientoCajareq

Solo se admiten los tipos marcados como manuales en el catálogo

stringreq
numberreq
stringopc
ResumenCajaobject
CampoTipoDescripción
numberreq
numberreq
numberreq
numberreq
RespuestaReporteobject
CampoTipoDescripción
stringreq
stringreq
objectopc

Los filtros en claro, para imprimirlos como texto en la cabecera del formato

array<object>req
array<object>req
objectopc

Calculados por el backend. El frontend no suma columnas

integer · nullopc
integer · nullopc
integer · nullopc
integer · nullopc
RespuestaImpresionobject
CampoTipoDescripción
stringreq
stringreq
string · nullopc

Ruta y fecha, cliente, o crédito

Empresareq
objectopc
array<object>opc
array<object>req
objectopc
string · date-timeopc

Va al pie de todo formato junto con el usuario que imprimió. No es decorativo: un listado de ruta que circula en papel sin decir cuándo se generó induce a cobrar sobre datos viejos.

stringopc
RespuestaTableroobject
CampoTipoDescripción
objectopc
numberreq
numberreq
numberreq
objectreq
numberreq
numberreq

No es ingresos menos gastos. Es la ganancia financiera cobrada: intereses y mora efectivamente recibidos. No descuenta gastos operativos, porque en los 9 meses de la base entregada no existe ningún gasto operativo capturado (Anexo A §A.9, Anexo B §B.3).

Configuracionobject
CampoTipoDescripción
integerreq

Días de anticipación del aviso de vencimiento. Valor actual del negocio: 3.

Atención a la siembra: Configuracion.java declara @Builder.Default private Integer diasAnticipacionAviso = 0. Un 0 hace que «Próximos a vencer» salga vacía y parezca rota. El dato migrado desde la tabla Recordatorio de Access debe ser 3; conviene verificarlo en el seed inicial y no dejarlo al default de la entidad. Sustituye la idea de persistir recordatorios individuales: los avisos se derivan de las cuotas (modelo v1.1 §1.7 y §2.2).

string · nullopc
Empresaobject
CampoTipoDescripción
integer · nullopc
stringreq
string · nullopc

ABIERTO — acuerdo #29. Empresa.java tiene nombre, rfc, logo, logo2 y logo3, pero no lema. El Anexo A Módulo 7 define Datos del negocio como «Nombre, lema y logotipo utilizados en los formatos impresos», y el encabezado de los 7 formatos del Anexo B §B.2 lo incluye.

Sin este campo, el encabezado impreso queda incompleto respecto de lo aprobado. Alternativa si no se añade: reutilizar uno de los tres logo*, que es peor.

string · nullopc
string · nullopc

Ruta del endpoint que sirve el logotipo. Encabeza todos los formatos impresos

booleanopc
PeticionEmpresaobject
CampoTipoDescripción
stringreq
stringopc
stringopc
PrestamoConPlanobject
CampoTipoDescripción
Prestamoreq
array<Cuota>req

El plan definitivo generado por el backend. En producción manda éste siempre: si no coincide con el que la vista previa mostró, el que se guarda y se muestra es el del backend, sin excepción.

integer · nullopc

14Reglas abiertas — pendientes del cliente

Consolidación de los puntos [POR CONFIRMAR] de los anexos y del modelo v1.1, ordenadas por lo que impiden construir. Ninguna bloquea el arranque: donde la regla está abierta, el contrato la expone como enum o campo opcional. Criterio: se maqueta lo reversible — convertir un aviso en bloqueo es una línea; quitar un bloqueo mal puesto genera una discusión con el cliente.

#Regla abiertaBloqueaImpacto en el frontend
1Abono a capital: reduce cuotas / reduce monto / solo cajaPOST /api/prestamos/{id}/abonos-capitalBloqueante La pantalla 12 está maquetada con las 3 variantes; aplicacion es enum mientras tanto
2Orden de aplicación del pago (se propone mora → interés → capital → saldo a favor)POST /api/prestamos/{id}/pagos y su previsualizaciónEl desglose sale del backend; si cambia el orden, cambian los números, no la pantalla
3Esquema SOLO_INTERÉS: cómo y cuándo se recupera el capitalPOST /api/prestamos/simulacionAfecta 6 créditos históricos; la vista previa no puede mostrar el plan hasta cerrarlo
4Abono parcial completado: ¿la cuota se cierra sola o la cierra el operador?Distribución del pagoCambia si la pantalla 11 necesita una acción explícita de cierre
5Mora: automática o condonable por el operadorPOST …/pagos (campo condonarMora)El campo está en el contrato; si no es condonable, se retira
6Saldo a favor: se aplica solo al siguiente vencimiento o se conservaDistribución del pagoCambia si hay que mostrar y accionar el saldo a favor en el historial
7Reverso de pagos: quién puede y con qué límite de tiempoPOST /api/pagos/{id}/cancelacionDefine si la acción se oculta por permiso, por antigüedad, o ambas
8Mensual: ¿+30 días o mes calendario?Generación de cuotasCosmético para el frontend, crítico para la conciliación con el Access
9Gastos operativos: dónde se registran hoyGET /api/dashboardDefine qué significa «utilidad» en 2 de los 6 indicadores
10Rutas GRUPOS / QUINCENAL / LIQUIDACIÓN: ¿rutas o clasificaciones?Catálogo de rutas · y la ruta en ClienteSi no son rutas, el filtro de la pantalla 3 necesita una dimensión más — y la regla sube de importancia
11Formato del ticket: rollo térmico o cartaGET /api/impresion/{formato}/pdfDefine el valor por omisión de presentacion y 4 de los 7 formatos
12Tamaño máximo y formatos del expediente (propuesto: PDF, JPG, PNG hasta 10 MB)POST /api/clientes/{id}/documentosDefine la validación previa y el mensaje al usuario; cerrar antes de la pantalla 5
13¿El expediente incompleto impide el alta de crédito o solo advierte?POST /api/prestamos y la pantalla 6Alto Si bloquea, hay que definir qué documentos son obligatorios

15Acuerdos frontend–backend (Apéndice 2)

Los 26 acuerdos a cerrar entre los dos equipos, ordenados por lo que bloquean. Los cerrados con los modelos JPA del backend quedan confirmados.

Frontera general

#PuntoPropuesta
1Nombres de campo en JSONcamelCasecerrado
2Nombre del recurso de préstamos/api/prestamoscerrado
3Endpoint de previsualización de pagoQue exista; sostiene la pantalla 10
4Endpoint de simulación de amortizaciónQue exista; sostiene la pantalla 6
5Campos derivados en las respuestasLa lista de §05 · campos derivados
6Permisos efectivos resueltos en el loginLista plana de códigos
7Catálogo de códigos de errorLa tabla de §11, o la del backend si prefiere otra
8Mecanismo de idempotenciaEncabezado Clave-Idempotencia + entidad IdempotenciaOperacioncerrado
9Refresh tokenSin refresh: token largo y relogin manual
10Claves de reportes y formatosLa lista del contrato (9 claves, 7 formatos)
11Aviso de endpoints estables por móduloUn mensaje por grupo, aunque los datos estén incompletos
12Casos de prueba compartidos de amortizaciónamortizacion-casos.json en el repositorio

Modelo y estados

#PuntoPropuesta
13Naturaleza del estado VENCIDOCondición derivada, no estado asignado — cerrado
14¿Varios préstamos activos por cliente?Sí, por el Anexo A §A.8
15Precedencia VENCIDA sobre PARCIALUn solo campo estado, con VENCIDA ganando
16EGRESO_PRESTAMO_OTORGADO no existía en el enumAgregado — cerrado. Son 2,107 movimientos, el 14 % de la caja
17Valores de TipoIdentificacionINE, PASAPORTE, LICENCIA, CEDULA_PROFESIONAL, OTROcerrado
18Valores de ClienteStatusACTIVO, INACTIVOcerrado

Expediente digital y avales — nuevos con el Anexo C

#PuntoPropuesta
19Entrega de archivos al navegadorEndpoint autenticado + blob:. Si los PDFs pesan, URL firmada
20urlContenido armada por el backendSin exponer rutaArchivo
21Un archivo por peticiónmultipart/form-data; cinco archivos son cinco llamadas con progreso independiente
22Límite del contenedor = regla abierta #12Configurar Spring al mismo valor acordado con el cliente
23Error de tamaño con el sobre { codigo, mensaje }Capturar el rechazo del contenedor → 413 ARCHIVO_MUY_GRANDE
24¿El reemplazo conserva el documento anterior?Sí, inactivo — cerrado (reemplazaAId)
25¿Avales dentro del POST /api/prestamos o después?Después, en dos pasos: Aval.prestamoId exige el préstamo existente
26¿Avales editables con el préstamo activo?Sí; los datos de contacto cambian y hay que poder corregirlos

Cerrados y abiertos con los modelos JPA del backend

#PuntoEstado
27Control de concurrencia optimista: @Version en Prestamo, Cuota, Pago, MovimientoCaja y ClienteDocumentocerradoversion expuesto en las 5 entidades
28Cliente.telefono, direccion y notas — los Anexos los exigen (pantalla 4) y el JPA no los tiene; Aval sí tiene teléfono y direcciónabierto — el contrato ya los expone
29Empresa.lema — encabeza los 7 formatos del Anexo B §B.2 y el JPA no lo tieneabierto — el contrato ya lo expone
30marcadoParaRevision en Cliente y MovimientoCaja — para los 14 clientes sin nombre, los 60 movimientos huérfanos y los 9 importes anómalos del Anexo A §A.9abierto — el contrato ya lo expone
Lo que hay que preguntarle al cliente, no resolver entre nosotros: regla abierta #13 (¿bloquea el expediente incompleto? ¿qué tipos son obligatorios?), regla abierta #12 (tamaño y formatos), y los permisos del expediente (Ap.1 §2): con los 5 permisos actuales, un cobrador puede ver identificaciones de todos los clientes. Si eso no es aceptable, hacen falta GESTIONAR_EXPEDIENTE y GESTIONAR_AVALES. Lo decide el cliente.

16Anexos y documentos fuente

Cada referencia de esta página — «Anexo A §A.4», «modelo v1.1 §2.14», «REGLA ABIERTA #n» — es un enlace al punto exacto del documento correspondiente. Orden de prioridad cuando dos documentos discrepan: Modelo v1.1 > system-design > Anexos A/B/C > design system > arquitectura.

Anexo A — Inventario de alcance v1.3 · 17 pantallas · migración · reglas de pago
  • A.1 Módulos y pantallas · A.2 Perfiles y permisos
  • A.3 Esquemas de amortización · A.4 Reglas de pago y mora
  • A.8 Tablas a migrar · A.9 Volumen y calidad de los datos
Ver Anexo A
Anexo B — Reportes y formatos v1.1 · 9 reportes · 7 formatos · tablero
  • B.1 Reportes analíticos (pantalla, PDF, Excel)
  • B.2 Formatos de impresión de cobranza
  • B.3 Los 6 indicadores del tablero, definidos con exactitud
Ver Anexo B
Anexo C — Alcance complementario y exclusiones v1.1 · expediente digital · avales · exclusiones
  • C.1 Expediente digital (C.1.1) y avales (C.1.2) — aprobados
  • C.2 Entidades preparadas sin funcionalidad contratada
  • C.3 Fuera de alcance explícito, incorporable sin rehacer
Ver Anexo C
Modelo de Datos Financiero Reconciliado v1.1 · 20 entidades · enums · flujo de pago
  • §2 Las entidades, una por una · §3 Enums recomendados
  • §5 Flujo de un pago (8 pasos)
  • §11 Reglas que todavía deben cerrarse con el cliente
Ver el modelo →

Archivos fuente en el repositorio