Skip to content

Plataforma técnica · Sevastopol

Bancos Island

Islands Sevastopol Finanzas

BancosIsland mantiene el maestro de cuentas bancarias del tenant y sus categorías. Pinta dos tablas CRUD, cada una con su modal. El saldo bancario que renderiza es efectivo — suma saldo inicial registrado más los aportes de capital asociados — y se calcula al cierre del mes activo (fecha_hasta).

  • Directorysevastopol/src/components/islands/financieros/
    • BancosIsland.tsx — componente único, internamente BancosPanel
    • FinancierosWorkspace.tsx — tokens compartidos
  • Directorysevastopol/src/lib/hooks/
    • useActiveTenant.ts
    • useActivePeriod.ts

Dos pestañas controladas por activeTab:

PestañaFunciónModal
bancosCatálogo de cuentas bancarias del tenant.Alta/edición con autocompletado desde catálogo local.
categoriasCategorías que agrupan los bancos por tipo (Banco, Préstamo, Línea Crédito, Inversión, Otro).Alta/edición con validación de cuenta contable padre.
const { tenantId } = useActiveTenant((id) => {
void Promise.all([loadBancos(), loadCategorias(), loadCuentasContables(id)]);
});
const { period } = useActivePeriod((p) => {
if (p.year && p.month) setFechaHasta(lastDayStr(p.year, p.month));
void loadBancos();
});
HookAportaReactividad
useActiveTenantUUID del tenant.Carga en paralelo bancos, categorías y plan de cuentas.
useActivePeriod{ year, month }.Mueve fechaHasta al último día del mes activo y recarga bancos para que el saldo refleje ese corte.
function lastDayStr(year: number, month: number): string {
const d = new Date(year, month, 0).getDate();
return `${year}-${String(month).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
}

Signals locales:

SignalTipoUso
activeTab"bancos" | "categorias"Pestaña visible.
fechaHastastring (YYYY-MM-DD)Corte para saldo_actual.
bancosBanco[]Catálogo del tenant.
categoriasCategoriaBancaria[]Catálogo (incluye inactivas vía todas=true).
cuentasContablesCuentaContableLite[]Plan de cuentas imputables del tenant, para los selectores.
filtroBanco / filtroCategoriastringFiltros locales.
showBancoModal / showCategoriaModalbooleanApertura de modales.
editingBanco / editingCategoriaobjeto o nullDistingue alta vs edición.
bancoForm / categoriaFormobjetos planosEstado del formulario activo.
bancoCatalogoCodigostringSelección del catálogo local de bancos chilenos.

La columna Saldo Inicial renderiza un valor compuesto:

const efectivo = r.saldo_inicial_efectivo ?? r.saldo_inicial ?? 0;
const aportes = Number(r.capital_aportes_total ?? 0);
const registrado = Number(r.saldo_inicial ?? 0);
const tooltip = aportes > 0
? `Registrado: ${fmt(registrado)} + ${r.capital_aportes_count} aporte(s) de capital: ${fmt(aportes)}`
: "Solo saldo inicial registrado (sin aportes de capital)";

Cuando hay aportes (aportes > 0), aparece un sufijo (NC) en azul indicando cuántos aportes de capital componen el efectivo. La fórmula es registrado + aportes, y el tooltip explica la composición.

Indicador visualSignificado
Sin sufijosaldo_inicial_efectivo = saldo_inicial — no hay aportes.
(3C) en azulEl efectivo incluye 3 aportes de capital adicionales al saldo registrado.

El modal de alta de banco incluye un selector pre-poblado con 10 bancos chilenos:

BancoRUTCódigo
BancoEstado97.030.000-7012
Banco de Chile97.004.000-5001
Banco Santander97.036.000-K037
Banco BCI97.006.000-6016
Scotiabank97.018.000-1014
Banco Itaú76.000.248-0039
Banco BICE97.080.000-K028
Banco Security97.053.000-2049
Banco Falabella97.951.000-8051
Banco Consorcio99.500.000-9055

Seleccionar uno autocompleta nombre, rut_banco y codigo_banco en el formulario:

function applyBancoCatalogo(codigo: string) {
const banco = BANCOS_CATALOGO.find((item) => item.codigo_banco === codigo);
if (!banco) return;
setBancoForm((prev) => ({
...prev,
nombre: banco.nombre,
rut_banco: banco.rut_banco,
codigo_banco: banco.codigo_banco,
}));
}

El catálogo no cubre todos los bancos del SBIF; basta no usar el autocompletado y escribir manualmente para los faltantes.

CampoTipoObligatorioNotas
Catálogo bancos<select>NoAutocompleta nombre/RUT/código.
nombre<input>
rut_banco<input>NoFormato XX.XXX.XXX-Y.
codigo_banco<input>No3 dígitos del SBIF.
categoria_id<select>Lista de categorías del tenant.
numero_cuenta<input>
tipo_cuenta<select>CTA_CTE, AHORRO, VISTA, CAJA, OTRO.
moneda<input>Default CLP, normalizado a uppercase.
cuenta_contable_codigo<select>NoHeredada de la categoría seleccionada al crear; editable.
saldo_inicial<input type=number>Default 0.
fecha_saldo_inicial<input type=date>NoOpcional.
observaciones<textarea>No
function onBancoCategoriaChange(categoriaId: string) {
const categoria = categorias().find((c) => String(c.id) === categoriaId);
setBancoForm((prev) => ({
...prev,
categoria_id: categoriaId,
cuenta_contable_codigo: editingBanco()
? prev.cuenta_contable_codigo
: (categoria?.cuenta_contable_codigo ?? ""),
}));
}

Herencia de cuenta contable: al crear un banco, cambiar la categoría sobrescribe cuenta_contable_codigo con el valor de la categoría. Al editar un banco existente, el cambio de categoría no sobrescribe — preserva la cuenta del banco para no romper la trazabilidad contable.

CampoTipoObligatorioNotas
codigo<input>Sólo en altaInmutable después de creada; normalizado a uppercase.
nombre<input>
tipo<select>BANCO, PRESTAMO, LINEA_CREDITO, INVERSION, OTRO.
cuenta_contable_codigo<select>CondicionalObligatorio si el tipo está en el mapa de padres permitidos.
descripcion<textarea>No
activo<checkbox>Default true.

Cada tipo restringe qué cuenta contable es válida como padre:

const CUENTAS_PADRE_POR_TIPO: Record<string, string[]> = {
BANCO: ["1101200"],
INVERSION: ["1102000"],
LINEA_CREDITO: ["2101000", "210100"],
PRESTAMO: ["2101100", "2201000"],
};
TipoPadre permitidoSignificado
BANCO1101200Bancos (activo corriente).
INVERSION1102000Inversiones (activo corriente).
LINEA_CREDITO2101000, 210100Pasivo corriente — créditos rotativos.
PRESTAMO2101100, 2201000Pasivo corriente / no corriente.
OTRO(sin restricción)Sin validación.

El selector de cuenta contable se filtra contra estos padres usando el plan de cuentas (cuentasContables) cargado desde /api/admin/chart-of-accounts.

if (padresPermitidos.length > 0 && !f.cuenta_contable_codigo.trim()) {
toast.error("Seleccione una cuenta contable para este tipo de categoría");
return;
}
AcciónEfecto colateral
Eliminar bancoRecarga sólo bancos. El backend rechaza si tiene movimientos.
Eliminar categoríaRecarga categorías y bancos en paralelo — los bancos enlazados quedan con nombre_categoria = null.
MétodoRutaOperación
GET/api/financieros/bancos?fecha_hasta&tenant_idLista del catálogo con saldo a fecha.
POST/api/financieros/bancos?tenant_idAlta.
PUT/api/financieros/bancos/:id?tenant_idEdición.
DELETE/api/financieros/bancos/:id?tenant_idEliminación.
GET/api/financieros/categorias?todas=true&tenant_idLista incluyendo inactivas.
POST/api/financieros/categorias?tenant_idAlta.
PUT/api/financieros/categorias/:id?tenant_idEdición.
DELETE/api/financieros/categorias/:id?tenant_idEliminación.
GET/api/admin/chart-of-accounts?tenant_idPlan de cuentas imputables.
ReglaMotivo
fechaHasta se sigue al período activoEl saldo bancario es siempre al corte del mes contable, no a hoy.
Carga en paralelo al cambiar tenantBancos, categorías y plan de cuentas son independientes; serializar agrega latencia visible.
Categoría hereda cuenta contable sólo en altaCambiar categoría de un banco existente no debe alterar su cuenta contable — eso rompería el libro mayor.
codigo de categoría inmutable post-creaciónEl código es referenciado por reglas externas; cambiarlo desincronizaría reportes.
Catálogo local de bancos chilenosAcelera el alta del 80% de los casos sin necesidad de un endpoint extra.
Saldo inicial muestra efectivo + tooltip detalladoEl operador necesita explicar la composición al auditor sin abrir un detalle.
Filtros locales por nombre / código / númeroEl catálogo es pequeño (menos de 50 cuentas por tenant); paginar no aporta.
Errores con fallback a error.message ?? error.errorEl backend a veces retorna {message}, a veces {error}; la UI lee ambos.
Eliminar categoría refresca bancosLos bancos pueden quedar huérfanos visualmente (nombre_categoria = null); el refresh es necesario para reflejarlo.