Skip to content

Plataforma técnica · Sevastopol

Config Contable View Island

Islands Sevastopol Administración

ConfigContableViewIsland mantiene el wiring contable del tenant: qué cuenta usar por tipo de documento SII y qué débito/crédito por concepto operacional. Dos pestañas CRUD comparten el catálogo del plan de cuentas y se cargan en paralelo.

  • Directorysevastopol/src/components/islands/admin/
    • ConfigContableViewIsland.tsx — componente único, internamente ConfigContableView

Dos pestañas controladas por tab:

PestañaTabla SQLIdentidadAcciones
cuentasconfig_cuentas_contablestipo_documento (VENTA, BOLETA, COMPRA)Alta / Editar / Eliminar
conceptosconfig_conceptos_contablesconcepto_codigo (string libre del catálogo de conceptos)Alta / Editar / Eliminar
async function loadAll(tid: string) {
const [rCuentas, rConceptos, rPlan] = await Promise.all([
authenticatedFetch(`${API_BASE}/cuentas?tenant_id=${tid}`),
authenticatedFetch(`${API_BASE}/conceptos?tenant_id=${tid}`),
authenticatedFetch(`${PLAN_API}?tenant_id=${tid}`),
]);
if (!rCuentas.ok || !rConceptos.ok || !rPlan.ok) {
throw new Error("Error cargando configuración contable");
}
setCuentas(await rCuentas.json());
setConceptos(await rConceptos.json());
setPlanCuentas(await rPlan.json());
}

El plan de cuentas alimenta los selectores de ambas pestañas. Un solo fallo en cualquiera lanza error — la UI no carga parcialmente.

const planOptions = createMemo(() =>
planCuentas().map((p) => ({ id: p.codigo, nombre: `${p.codigo} — ${p.nombre}` })),
);
const cuentaLookup = createMemo(() => {
const map = new Map<string, string>();
for (const p of planCuentas()) map.set(p.codigo, p.nombre);
return map;
});
  • planOptions: opciones del <Select> con etiqueta código — nombre.
  • cuentaLookup: mapa código → nombre para mostrar nombres legibles en la tabla sin re-buscar.

Una fila por tipo de documento SII. El tipo_documento es la primary key (no hay UUID): editar reemplaza el registro completo.

const url = editing
? `${API_BASE}/cuentas/${editing.tipo_documento}?tenant_id=${tid}`
: `${API_BASE}/cuentas?tenant_id=${tid}`;
const method = editing ? "PUT" : "POST";
const body = editing
? { cuenta_contable_codigo: f.cuenta_contable_codigo }
: f;

Al editar, el body solo manda cuenta_contable_codigo — el tipo es inmutable (es la identidad del registro y va en la URL). En la creación se manda todo el form.

Opciones de tipo: VENTA, BOLETA, COMPRA (constantes hardcodeadas, no enum del backend).

Una fila por concepto operacional. La identidad es concepto_codigo. Cada concepto requiere dos cuentas: débito y crédito.

CampoObligatorioNotas
concepto_codigoSólo en altaString libre. Inmutable post-creación.
cuenta_debito_codigoSelector contra planOptions.
cuenta_credito_codigoSelector contra planOptions.

La validación es simple — los tres campos no vacíos:

if (!f.concepto_codigo || !f.cuenta_debito_codigo || !f.cuenta_credito_codigo) {
toast.push("Completa todos los campos", "error");
return;
}

El backend valida que ambas cuentas existan en el plan y que el concepto_codigo exista en operaciones_sii.conceptos_operaciones.

MétodoRutaOperación
GET/api/admin/config-contable/cuentas?tenant_idLista de wiring por documento.
POST/api/admin/config-contable/cuentas?tenant_idAlta de wiring.
PUT/api/admin/config-contable/cuentas/:tipo_documento?tenant_idReemplaza cuenta del tipo.
DELETE/api/admin/config-contable/cuentas/:tipo_documento?tenant_idElimina wiring.
GET/api/admin/config-contable/conceptos?tenant_idLista de conceptos.
POST/api/admin/config-contable/conceptos?tenant_idAlta.
PUT/api/admin/config-contable/conceptos/:concepto_codigo?tenant_idActualiza débito/crédito.
DELETE/api/admin/config-contable/conceptos/:concepto_codigo?tenant_idElimina.
GET/api/admin/chart-of-accounts?tenant_idPlan de cuentas para los selectores.
ReglaMotivo
Identidad por código natural (no UUID)El tipo_documento y el concepto_codigo son referenciados por código en SQL; usar UUID requeriría joins innecesarios.
Carga en paraleloLos 3 endpoints son independientes; serializar es latencia visible.
Edición solo del campo no-identidadEl tipo/código de un registro nunca cambia — si hace falta, se elimina y se crea otro.
Confirmación destructiva en DELETEEliminar wiring rompe la contabilización del próximo asiento que use ese tipo/concepto.
Selector con etiqueta código — nombreAcelera la búsqueda visual; los códigos solos son crípticos para conceptos contables.
Sin recarga entre pestañasEl cambio de pestaña usa los datos ya en memoria; ambas pestañas trabajan sobre el mismo snapshot del plan.
Toast unificado con `err.error