Plataforma técnica · Sevastopol
Libro Banco Island
Islands Sevastopol Finanzas
Propósito
Section titled “Propósito”LibroBancoIsland es la vista contable de la cuenta bancaria y el centro de resolución de diferencias de conciliación. Reúne cinco pestañas:
- Libro — movimientos del banco con sus categorías y cuentas contables.
- Categorías — CRUD del catálogo de categorías de movimiento.
- Diferencias — conciliaciones del período con estado
DIFERENCIAoPARCIAL, agrupadas por documento. - Compensaciones — workspace para cruzar una venta con diferencia negativa contra facturas de proveedor del mismo emisor (resolución cruzada cliente↔proveedor).
- Resoluciones — historial de resoluciones aplicadas en el período.
Es la island de destino del botón “Abrir Libro de banco” en el paso incobrables de CicloContableIsland.
Ubicación
Section titled “Ubicación”Directorysevastopol/src/components/islands/financieros/
- LibroBancoIsland.tsx — componente único, internamente
LibroBancoPanel - FinancierosWorkspace.tsx — tokens compartidos
- LibroBancoIsland.tsx — componente único, internamente
Directorysevastopol/src/lib/hooks/
- useActiveTenant.ts
- useActivePeriod.ts
Hooks y estado global
Section titled “Hooks y estado global”const { tenantId } = useActiveTenant((_id) => { void loadBancos(); setRows([]); setCategorias([]); setDiferencias([]); setIncobrableEditRow(null); resetCompensacionWorkspace();});const { period } = useActivePeriod();Cambiar tenant vacía todo y recarga catálogos. Cambiar período no re-fetcha automáticamente — el operador debe pulsar Cargar período (la operación toca 1-3 endpoints según la pestaña).
useActiveTenant carga 3 catálogos en paralelo: bancos, categorías-movimiento y plan de cuentas imputables (/api/admin/chart-of-accounts). El plan se filtra a es_imputable === true para los selectores.
Composición
Section titled “Composición”const [activeTab, setActiveTab] = createSignal< "libro" | "categorias" | "diferencias" | "compensaciones" | "resoluciones">("libro");
function switchTab(tab) { setActiveTab(tab); if ((tab === "diferencias" || tab === "compensaciones") && filtBanco()) { void loadDiferencias(); } if (tab === "resoluciones") { void loadResoluciones(); }}Pestañas que disparan fetch al activarse:
| Pestaña | Carga al activar | Endpoint |
|---|---|---|
libro | No (botón explícito). | — |
categorias | No (catálogo ya cargado). | — |
diferencias | Sí (si hay banco). | GET /conciliacion/resumen?bancoId&anio&mes&acumulado=true |
compensaciones | Sí (si hay banco). | igual a diferencias |
resoluciones | Sí (si hay año+mes). | GET /resoluciones-conciliacion?anio&mes |
Pestaña Libro
Section titled “Pestaña Libro”Tabla del libro de banco — un movimiento por fila con su categoría asignada, código contable, cargo/abono y conteo de documentos vinculados.
const params = new URLSearchParams({ bancoId: filtBanco(), anio, mes });const res = await authenticatedFetch(`${API}/libro-banco${buildQuery(params)}`);Columnas:
| Columna | Origen | Notas |
|---|---|---|
| Fecha | fmtDate(fecha_movimiento) | — |
| Tipo | tipo_movimiento | CARGO o ABONO. |
| Descripción / Glosa | concat. | — |
| Categoría | categoria_codigo · categoria_nombre | — si null. |
| Cta. Contable | cuenta_contable_codigo | — |
| Cargo / Abono | monto_cargo / monto_abono | Columnas separadas. |
| Estado | estado | PENDIENTE | CONCILIADO | CONCILIADO_PARCIAL. |
| Vínculos | documentos_vinculados | Conteo de docs en la conciliación. |
Sin acciones inline en esta tabla — es read-only. La edición de movimientos ocurre en ConciliacionBancariaIsland.
Pestaña Categorías
Section titled “Pestaña Categorías”CRUD de CategoriaMovimiento con tres modales (nueva, editar, eliminar). Cada categoría puede mapear a una cuenta contable del plan.
| Campo | Obligatorio | Notas |
|---|---|---|
codigo | Sólo en alta | Inmutable post-creación. |
nombre | Sí | — |
aplica_a | Sí | CARGO, ABONO, AMBOS. |
descripcion | No | — |
cuenta_contable_codigo | No | Selector contra cuentas (imputables, ordenadas numéricamente). |
orden | No | Default 99. Define orden en selectores del libro. |
El selector de cuenta usa un memo derivado del plan:
const cuentasOptions = createMemo(() => [...cuentas()] .sort((a, b) => a.codigo.localeCompare(b.codigo, undefined, { numeric: true })) .map((c) => ({ id: c.codigo, nombre: `${c.codigo} · ${c.nombre}` })),);Pestaña Diferencias
Section titled “Pestaña Diferencias”Tabla de conciliaciones con estado IN ('DIFERENCIA', 'PARCIAL') y diferencia negativa significativa (la venta cobró menos de lo facturado). Se cargan con acumulado=true — incluye diferencias arrastradas de períodos anteriores no resueltas.
Cada fila expone dos acciones de resolución según el tipo de documento:
Para ventas/boletas con diferencia (incobrables). Pide dos cuentas (gasto + contraactivo) con defaults 3502003 y 1104002:
function openIncobrableCastigo(row) { setIncobrableEditRow(row); setIncobrableCuentaGasto(row.cuenta_gasto_codigo ?? "3502003"); setIncobrableCuentaContraactivo(row.cuenta_contraactivo_codigo ?? "1104002");}El POST contabiliza el castigo:
POST /api/financieros/conciliacion/:id/castigo-estimacion{ anio, mes, cuenta_gasto_codigo, cuenta_contraactivo_codigo }Reversa disponible vía PUT /castigo-estimacion/reversar (con confirmación explícita).
Estados visibles en la fila:
castigo_estimacion_estado | Significado |
|---|---|
null | No se ha contabilizado castigo. |
CONTABILIZADO | Castigo activo, expone botón Reversar. |
REVERSADO | Castigo deshecho — la diferencia vuelve a estar abierta. |
canCompensarProveedor valida tres condiciones:
function canCompensarProveedor(dif) { return ( ["VENTA", "BOLETA"].includes(String(dif.tipo_documento ?? "")) && (dif.estado === "DIFERENCIA" || dif.estado === "PARCIAL") && Number(dif.diferencia ?? 0) < -0.01 );}Click en Compensar con proveedor llama openCompensacionProveedor(documento_id) que cambia a la pestaña Compensaciones y carga el workspace.
Pestaña Compensaciones
Section titled “Pestaña Compensaciones”Workspace específico para resolver una diferencia cruzando documentos. La idea contable: una venta cobró 100; si el mismo emisor tiene una factura de proveedor de $20 pendiente, se compensa contra esa factura en vez de castigar.
const [res, resAplicadas] = await Promise.all([ authenticatedFetch(`${API}/conciliacion/documento/:id/compensacion-proveedor?anio&mes`), authenticatedFetch(`${API}/conciliacion/documento/:id/compensaciones-proveedor`),]);La primera respuesta trae { documento, documentos_proveedor[] } — el detalle del documento cliente y los proveedores aplicables. La segunda trae las compensaciones ya aplicadas.
Selección y monto sugerido
Section titled “Selección y monto sugerido”Toggle por documento proveedor; al chequear se calcula un monto sugerido conservador:
function suggestedCompensacionMonto(detalle, doc, maxDisponible) { const monto = Math.min( Math.abs(Number(detalle.diferencia_pendiente ?? 0)), Math.max(Number(maxDisponible ?? Infinity), 0), Number(doc.monto_pendiente ?? 0), ); return monto > 0 ? String(Number(monto.toFixed(2))) : "";}El monto se acota por:
- La diferencia pendiente del documento cliente.
- El máximo todavía disponible (descontando otros seleccionados).
- El pendiente del documento proveedor.
updateCompensacionDocumentoMonto re-normaliza al modificar manualmente — nunca permite asignar más allá del disponible o del pendiente del proveedor.
Aplicación
Section titled “Aplicación”const res = await authenticatedFetch( `${API}/conciliacion/documento/${detalle.documento_cliente_id}/compensaciones-proveedor${tq()}`, { method: "POST", body: JSON.stringify({ anio, mes, documentos: documentos.map(item => ({ documento_id: item.documento_id, monto: Number(parseFloat(item.monto).toFixed(2)), })), observaciones: compensacionObservacion().trim() || null, }), },);Tras aplicar: recarga diferencias y refresca el detalle de compensación (modo silent para no toastar de nuevo).
Reversa
Section titled “Reversa”Botón en cada compensación aplicada → DELETE /conciliacion/compensacion-proveedor/:resolucionId. Pide confirmación y recarga ambos lados del workspace.
Pestaña Resoluciones
Section titled “Pestaña Resoluciones”Historial de todas las resoluciones del período (castigo + compensación). Una fila por resolución con cliente↔proveedor, monto, observaciones, autor y fecha.
const params = new URLSearchParams({ anio: String(qAnio), mes: String(qMes) });const res = await authenticatedFetch(`${API}/resoluciones-conciliacion${buildQuery(params)}`);Acción única: Reversar (mismo endpoint que la reversa de compensación — comparten infraestructura). Pide confirmación que advierte explícitamente sobre el recálculo:
¿Reversar esta resolución? Se recalcularán los estados de conciliación y documentos afectados.Endpoints consumidos
Section titled “Endpoints consumidos”| Método | Ruta | Pestaña |
|---|---|---|
GET | /api/financieros/bancos | (carga inicial) |
GET | /api/financieros/categorias-movimiento | (carga inicial) |
GET | /api/admin/chart-of-accounts | (carga inicial, filtra es_imputable) |
GET | /api/financieros/libro-banco?bancoId&anio&mes | libro |
POST / PUT / DELETE | /api/financieros/categorias-movimiento[/:id] | categorias |
GET | /api/financieros/conciliacion/resumen?bancoId&anio&mes&acumulado=true | diferencias, compensaciones |
POST | /api/financieros/conciliacion/:id/castigo-estimacion | diferencias |
PUT | /api/financieros/conciliacion/:id/castigo-estimacion/reversar | diferencias |
GET | /api/financieros/conciliacion/documento/:id/compensacion-proveedor?anio&mes | compensaciones |
GET | /api/financieros/conciliacion/documento/:id/compensaciones-proveedor | compensaciones |
POST | /api/financieros/conciliacion/documento/:id/compensaciones-proveedor | compensaciones |
DELETE | /api/financieros/conciliacion/compensacion-proveedor/:id | compensaciones, resoluciones |
GET | /api/financieros/resoluciones-conciliacion?anio&mes | resoluciones |
Todas usan tenant_id como query param.
Reglas de UI
Section titled “Reglas de UI”| Regla | Motivo |
|---|---|
Diferencias se cargan con acumulado=true | Las diferencias arrastradas de períodos anteriores siguen pendientes hasta resolverse; ocultarlas crearía falsa sensación de cierre. |
Castigo expone cuentas por defecto 3502003 / 1104002 | Plan estándar; el operador puede sobreescribir para casos no-estándar. |
Compensación sólo para VENTA o BOLETA con diferencia < 0 | Una compensación tiene sentido cuando el cliente cobró menos; cuando cobró más es un anticipo. |
| Monto sugerido = mínimo de 3 cotas | Evita over-allocate de la diferencia pendiente, del disponible y del pendiente del proveedor. |
| Re-normaliza monto al editar | Mantener la invariante: la suma de asignaciones ≤ diferencia pendiente, y cada monto ≤ pendiente del proveedor. |
Switch a compensaciones precarga el documento clickeado | El operador viene desde diferencias; cargar otra vez sería redundante. |
| Reversa de compensación es el mismo endpoint que reversa de resolución | Ambas son tipo_resolucion = COMPENSACION_PROVEEDOR en backend. |
| Confirmación explícita en castigo, compensación, resoluciones | Tres acciones contables irreversibles desde la UI. |
| Cargas en paralelo en carga inicial | Bancos, categorías y plan son independientes; serializar agrega latencia visible. |
| Libro sin acciones inline | La edición es responsabilidad de ConciliacionBancariaIsland; mantener la separación de roles entre vistas. |