Plataforma técnica · Sevastopol
Prestamos Island
Islands Sevastopol Finanzas
Propósito
Section titled “Propósito”PrestamosIsland opera el portafolio de préstamos del tenant: alta/edición/eliminación, generación de la tabla de amortización, registro de pagos por cuota con enlace opcional a un movimiento bancario. La island no calcula la amortización en cliente — sólo dispara la generación en Orchestrator y lista las cuotas resultantes.
Ubicación
Section titled “Ubicación”Directorysevastopol/src/components/islands/financieros/
- PrestamosIsland.tsx — componente único, internamente
PrestamosPanel - FinancierosWorkspace.tsx — tokens compartidos
- PrestamosIsland.tsx — componente único, internamente
Directorysevastopol/src/lib/hooks/
- useActiveTenant.ts
useActivePeriod no se consume — los préstamos y cuotas viven a través de varios períodos contables; el filtro por fecha está fuera del scope de la island.
Composición
Section titled “Composición”Dos pestañas controladas por activeTab:
| Pestaña | Función | Acción primaria |
|---|---|---|
prestamos | Portafolio con búsqueda + filtro de estado. | Nuevo préstamo / Editar / Eliminar / Generar amortización / Ver. |
amortizacion | Cronograma del préstamo seleccionado. | Registrar pago de cuota pendiente. |
La pestaña amortizacion requiere selectedPrestamo; si está vacío pinta un estado vacío con instrucción de seleccionar desde el portafolio.
Hooks y estado global
Section titled “Hooks y estado global”const { tenantId } = useActiveTenant(() => { void Promise.all([loadBancos(), loadPrestamos()]); setCuotas([]); setSelectedPrestamo(null);});Cambiar tenant dispara la recarga de bancos+préstamos en paralelo y limpia el préstamo seleccionado.
Signals locales:
| Signal | Tipo | Uso |
|---|---|---|
activeTab | "prestamos" | "amortizacion" | Pestaña visible. |
bancos | Banco[] | Catálogo del tenant para el <select> del modal. |
prestamos | Prestamo[] | Portafolio completo. |
cuotas | AmortizacionCuota[] | Cronograma del préstamo seleccionado. |
selectedPrestamo | Prestamo | null | Préstamo cuya amortización está cargada. |
editingPrestamo | Prestamo | null | Distingue modal de alta vs edición. |
form | objeto plano | Estado del modal de alta/edición. |
pagoForm | { cuotaId, fecha_pago, monto_pagado, movimiento_id } | Estado del modal de pago. |
filtroTextoPrest / filtroEstadoPrest | string | Filtros locales de la pestaña portafolio. |
Filtros del portafolio
Section titled “Filtros del portafolio”Los dos filtros se aplican en cliente sobre prestamos():
const filtradosPrestamos = createMemo(() => { let list = prestamos(); const query = filtroTextoPrest().toLowerCase(); if (query) { list = list.filter((item) => item.nombre_prestamo.toLowerCase().includes(query) || (item.nombre_banco ?? "").toLowerCase().includes(query), ); } if (filtroEstadoPrest()) list = list.filter((item) => item.estado === filtroEstadoPrest()); return list;});| Filtro | Tipo | Opciones |
|---|---|---|
| Texto | <input> | Sub-string sobre nombre_prestamo y nombre_banco. |
| Estado | WorkspaceSegmentedControl | Todos, VIGENTE, CANCELADO, VENCIDO. |
Modal de alta/edición
Section titled “Modal de alta/edición”15 campos. Los marcados con * son obligatorios; el resto son opcionales y se omiten del payload cuando vienen vacíos:
| Campo | Tipo | Obligatorio | Notas |
|---|---|---|---|
banco_id | <select> | Sí | Catálogo bancos del tenant. |
nombre_prestamo | <input> | Sí | — |
numero_operacion | <input> | No | Sólo en alta — no se carga al editar. |
tipo_prestamo | <select> | Sí | HIPOTECARIO, CONSUMO, COMERCIAL, LEASING, OTRO. Default COMERCIAL. |
monto_original | <input type=number> | Sí | Parseado con parseFloat. |
moneda | constante | — | Fijo CLP en el formulario actual. |
tasa_interes | <input type=number step=0.01> | Sí | % anual; parseFloat. |
tasa_tipo | <select> | — | FIJA o VARIABLE. Default FIJA. |
fecha_inicio | <input type=date> | Sí | — |
fecha_vencimiento | <input type=date> | Sí | — |
num_cuotas | <input type=number> | Sí | parseInt. |
periodicidad | <select> | — | MENSUAL, TRIMESTRAL, SEMESTRAL, ANUAL. |
cuenta_pasivo_codigo | <input> | No | Código contable del pasivo. |
cuenta_gasto_interes | <input> | No | Código contable del gasto financiero. |
observaciones | <textarea> | No | — |
const payload: Record<string, unknown> = { /* campos obligatorios */ };if (current.numero_operacion) payload.numero_operacion = current.numero_operacion;if (current.cuenta_pasivo_codigo) payload.cuenta_pasivo_codigo = current.cuenta_pasivo_codigo;if (current.cuenta_gasto_interes) payload.cuenta_gasto_interes = current.cuenta_gasto_interes;if (current.observaciones) payload.observaciones = current.observaciones;No hay validación de cliente — todos los campos se aceptan y el backend rechaza si faltan obligatorios.
Generación de amortización
Section titled “Generación de amortización”Acción inline desde la fila del portafolio (icono Database). Es destructiva sobre cuotas pendientes:
if (!confirm("¿Regenerar tabla de amortización? Se eliminarán las cuotas existentes no pagadas.")) return;const res = await authenticatedFetch(`${API}/prestamos/${prestamoId}/amortizacion${tq()}`, { method: "POST", body: JSON.stringify({}),});Si el préstamo seleccionado coincide con el regenerado, recarga las cuotas inmediatamente para reflejar el cambio:
if (selectedPrestamo()?.id === prestamoId) void loadCuotas(prestamoId);Las cuotas PAGADAS sobreviven a la regeneración (eso lo gestiona el backend); las PENDIENTES se reemplazan.
Tabla de amortización
Section titled “Tabla de amortización”10 columnas. Sólo las cuotas PENDIENTE exponen la acción de registrar pago:
| Columna | Render |
|---|---|
| N° | numero_cuota |
| Vencimiento | fmtDate(fecha_vencimiento) |
| Capital | fmtMonto(capital) |
| Interés | fmtMonto(interes) |
| Total | fmtMonto(total_cuota) |
| Cap. pendiente | fmtMonto(capital_pendiente) o — |
| Estado | PENDIENTE | PAGADA (y eventualmente otros) |
| F. pago | fmtDate(fecha_pago) o — |
| Monto pagado | fmtMonto(monto_pagado) o — |
| (acción) | <TableAction icon=Status> sólo si estado === "PENDIENTE". |
Modal de pago
Section titled “Modal de pago”3 campos. El monto se pre-rellena con total_cuota para registrar el caso normal sin escribir:
setPagoForm({ cuotaId: item.id, fecha_pago: new Date().toISOString().slice(0, 10), // hoy monto_pagado: String(item.total_cuota), // pre-rellenado movimiento_id: "",});| Campo | Obligatorio | Uso |
|---|---|---|
fecha_pago | Sí | Default hoy. |
monto_pagado | Sí | Default = total_cuota. Permite pago parcial o mayor (intereses moratorios). |
movimiento_id | No | UUID de un movimiento bancario ya cargado por la cartola para enlazar el pago a la contraparte bancaria. |
Tras éxito, recarga las cuotas del préstamo seleccionado.
Métricas en pills
Section titled “Métricas en pills”Cuatro contadores en el header se actualizan en vivo:
const prestamosVigentes = createMemo(() => prestamos().filter(i => i.estado === "VIGENTE").length);const montoVisible = createMemo(() => filtradosPrestamos().reduce((s, i) => s + Number(i.monto_original), 0));const cuotasPendientes = createMemo(() => cuotas().filter(i => i.estado === "PENDIENTE").length);const cuotasPagadas = createMemo(() => cuotas().filter(i => i.estado === "PAGADA").length);Monto visible suma sólo lo filtrado — el operador ve el impacto de cada filtro sin abrir un detalle.
Endpoints consumidos
Section titled “Endpoints consumidos”| Método | Ruta | Operación |
|---|---|---|
GET | /api/financieros/bancos?tenant_id=... | Catálogo de bancos. |
GET | /api/financieros/prestamos?tenant_id=... | Lista del portafolio. |
POST | /api/financieros/prestamos?tenant_id=... | Alta. |
PUT | /api/financieros/prestamos/:id?tenant_id=... | Edición. |
DELETE | /api/financieros/prestamos/:id?tenant_id=... | Eliminación. |
GET | /api/financieros/prestamos/:id/amortizacion?tenant_id=... | Cuotas del préstamo. |
POST | /api/financieros/prestamos/:id/amortizacion?tenant_id=... body {} | Genera/regenera la tabla. |
PUT | /api/financieros/prestamos/:id/amortizacion/:cuotaId/pagar?tenant_id=... | Registra pago. |
tenant_id viaja como query param en todas las llamadas.
Reglas de UI
Section titled “Reglas de UI”| Regla | Motivo |
|---|---|
useActivePeriod no se consume | Los préstamos cruzan varios períodos; filtrar por mes/año oscurecería el portafolio. |
| Filtros de texto+estado en cliente | El portafolio es bajo volumen por tenant. |
| Modal con campos opcionales omitidos del payload | El backend asigna defaults; mandar strings vacíos confundiría la validación. |
| Confirmar antes de regenerar amortización | Las cuotas PENDIENTE se borran y se reconstruyen. |
Cambio de tenant limpia selectedPrestamo y cuotas | El cronograma del tenant anterior no aplica. |
Pago pre-rellenado con total_cuota y fecha de hoy | Acelera el caso normal; el operador sólo edita en pagos atípicos. |
movimiento_id opcional | Permite registrar pagos sin cartola aún cargada; la conciliación queda como tarea posterior. |
Sólo cuotas PENDIENTE exponen acción de pago | Evita re-pagos accidentales sobre cuotas ya cerradas. |
| Botón “Generar amortización” en cada fila, no global | El operador debe decidir préstamo por préstamo; no hay “regenerar todas”. |