Plataforma técnica · Sevastopol
Capital View Island
Islands Sevastopol Administración
Propósito
Section titled “Propósito”CapitalViewIsland mantiene los movimientos de capital del tenant: aportes iniciales, aumentos, reducciones, capitalización de utilidades, revalorizaciones y ajustes. Cada movimiento atraviesa el workflow PENDIENTE_APROBACION → ACTIVO (con ANULADO como cierre). La aprobación ejecuta un asiento contable y emite un evento al outbox de Orchestrator.
Ubicación
Section titled “Ubicación”Directorysevastopol/src/components/islands/admin/
- CapitalViewIsland.tsx — componente único, internamente
CapitalView
- CapitalViewIsland.tsx — componente único, internamente
Estado
Section titled “Estado”const { tenantId } = useActiveTenant((id) => { void fetchData(id); void fetchLegalRepresentatives(id); void fetchChartOfAccounts(id);});const [entries, setEntries] = createSignal<CapitalEntry[]>([]);const [legalReps, setLegalReps] = createSignal<LegalRep[]>([]);const [chartAccounts, setChartAccounts] = createSignal<Account[]>([]);Cambiar tenant carga 3 datasets en paralelo porque el modal de capital necesita los tres: cuentas para validar, representantes para el socio, movimientos para listar.
Signals adicionales:
| Signal | Tipo | Uso |
|---|---|---|
qEstado / qTipo | string | Filtros locales. |
showModal / editingId / formData | edición | Modal alta/edición. |
isSubmitting / isLoading | bloqueos | UI. |
Tipos de movimiento
Section titled “Tipos de movimiento”const TIPO_CAPITAL_OPTIONS = [ { id: "APORTE_INICIAL", nombre: "Aporte Inicial" }, { id: "AUMENTO_CAPITAL", nombre: "Aumento Capital" }, { id: "REDUCCION_CAPITAL", nombre: "Reducción Capital" }, { id: "CAPITALIZACION_UTILIDADES", nombre: "Capitalización Utilidades" }, { id: "REVALORIZACION", nombre: "Revalorización" }, { id: "AJUSTE_CAPITAL", nombre: "Ajuste Capital" },];| Tipo | Signo en stats | Cuenta default |
|---|---|---|
APORTE_INICIAL | + | 2301001 (capital aportado) |
AUMENTO_CAPITAL | + | 2301001 |
REDUCCION_CAPITAL | − | 2301001 |
CAPITALIZACION_UTILIDADES | + | configurable |
REVALORIZACION | + | configurable |
AJUSTE_CAPITAL | + | configurable |
REDUCCION_CAPITAL es la única que resta en el total de capital — el cálculo totalCapital aplica el signo:
totalCapital: activeList.reduce((sum, e) => { const sign = e.tipo_capital === "REDUCCION_CAPITAL" ? -1 : 1; return sum + e.monto * sign;}, 0),Estados del workflow
Section titled “Estados del workflow”const ESTADO_OPTIONS = [ { id: "ACTIVO", nombre: "Activo" }, { id: "ANULADO", nombre: "Anulado" }, { id: "PENDIENTE_APROBACION", nombre: "Pendiente Aprobación" },];| Estado | Significado | Cuenta para el total |
|---|---|---|
PENDIENTE_APROBACION | Borrador, sin asiento contable. | No |
ACTIVO | Aprobado, con asiento generado. | Sí |
ANULADO | Movimiento reversado/cancelado. | No |
Aprobación en dos pasos
Section titled “Aprobación en dos pasos”El backend tiene un CHECK CONSTRAINT chk_capital_aprobacion que rechaza insertar/actualizar directamente a ACTIVO. El frontend resuelve esto haciendo dos llamadas secuenciales:
const shouldApprove = requestedEstado === "ACTIVO" && (!isEditing || currentEntry?.estado === "PENDIENTE_APROBACION");
const payloadToSave = { ...payload, // Guardar primero en PENDIENTE y luego aprobar para cumplir chk_capital_aprobacion. estado: shouldApprove ? "PENDIENTE_APROBACION" : requestedEstado,};
// Paso 1: POST/PUT con estado PENDIENTE_APROBACIONconst r = await authenticatedFetch(url, { method, body: JSON.stringify(payloadToSave) });const saved = await r.json();
// Paso 2: si el usuario eligió ACTIVO, aprobarif (shouldApprove) { const targetId = isEditing ? String(editingId()) : String(saved?.id || ""); const approveResp = await authenticatedFetch( `${API_BASE}/${targetId}/approve?tenant_id=${tenantId()}`, { method: "POST" }, );}El paso 2 es el que dispara la contabilización en el backend y el evento capital:movimiento al outbox.
Validación de anulación
Section titled “Validación de anulación”ANULADO sólo se permite sobre registros ya aprobados:
if (requestedEstado === "ANULADO" && (!isEditing || currentEntry?.estado === "PENDIENTE_APROBACION")) { toast.push("No se puede dejar en Anulado antes de estar aprobado", "error"); return;}Anular un borrador no tiene sentido — se elimina.
Identidad del socio
Section titled “Identidad del socio”El modal incluye un selector de representante legal. La lógica fuerza que socio_id siga al representante seleccionado:
const selectedRepresentativeId = payload.representante_legal_id || null;payload.representante_legal_id = selectedRepresentativeId;payload.socio_id = selectedRepresentativeId; // En este flujo, socio_id refleja el representante seleccionado.Las opciones se ordenan por apellido + nombre, con etiqueta Nombres Apellidos (RUT) [No vigente] si aplica:
const legalRepOptions = createMemo(() => [...legalReps()] .sort((a, b) => `${a.apellidos} ${a.nombres}`.localeCompare(`${b.apellidos} ${b.nombres}`)) .map(rep => ({ id: rep.id, nombre: `${rep.nombres} ${rep.apellidos}${rep.rut ? ` (${rep.rut})` : ""}${rep.vigente ? "" : " [No vigente]"}`, })),);Cuenta contable y contrapartida
Section titled “Cuenta contable y contrapartida”El movimiento exige cuenta_contable_codigo (default 2301001 en el formulario). Opcionalmente puede indicar cuenta_contrapartida_codigo — sino el backend usa la contrapartida por defecto (capital pagado vs banco/caja).
Ambas se validan contra el plan cargado en chartAccounts. La validación es frontend-only (existencia); el backend revalida.
Métricas en pills
Section titled “Métricas en pills”| Pill | Cálculo |
|---|---|
| Total | entries.length |
| Activos | filter(estado === "ACTIVO").length |
| Pendientes | filter(estado === "PENDIENTE_APROBACION").length |
| Total Capital | Suma firmada de monto solo para ACTIVO, con REDUCCION_CAPITAL en negativo. |
Orden de la lista
Section titled “Orden de la lista”return list.sort((a, b) => new Date(b.fecha_movimiento).getTime() - new Date(a.fecha_movimiento).getTime(),);Más recientes primero — el operador suele revisar el último movimiento ingresado.
Endpoints consumidos
Section titled “Endpoints consumidos”| Método | Ruta | Operación |
|---|---|---|
GET | /api/admin/capital?tenant_id=... | Lista de movimientos. |
POST | /api/admin/capital?tenant_id=... | Alta como PENDIENTE_APROBACION. |
PUT | /api/admin/capital/:id?tenant_id=... | Edición (preserva estado salvo aprobación). |
POST | /api/admin/capital/:id/approve?tenant_id=... | Aprobación — dispara asiento y evento capital:movimiento. |
DELETE | /api/admin/capital/:id?tenant_id=... | Eliminación (rechazada si el movimiento tiene asientos aprobados). |
GET | /api/admin/representatives?tenant_id=... | Catálogo de socios. |
GET | /api/admin/chart-of-accounts?tenant_id=... | Catálogo de cuentas para validar. |
Reglas de UI
Section titled “Reglas de UI”| Regla | Motivo |
|---|---|
| Aprobación en dos pasos | El CHECK CONSTRAINT chk_capital_aprobacion impide setear ACTIVO directo; la UI emula la transición esperada. |
Anular solo a partir de ACTIVO | Anular un borrador no tiene reverso contable — se elimina. |
REDUCCION_CAPITAL con signo negativo en stats | Es el único tipo que resta del capital; mostrar +monto sería engañoso. |
socio_id = representante_legal_id | El flujo actual no distingue socio de representante; mantener la simetría evita ambigüedad. |
Default cuenta 2301001 | Cuenta de capital aportado del plan estándar; el operador puede sobreescribir. |
| Tres fetches en paralelo al cambiar tenant | Cuentas, reps y movimientos son independientes; serializar es latencia visible. |
| Ordenar por fecha descendente | Las decisiones recientes pesan más en este dominio. |
| Confirmación en eliminación | El backend rechaza si tiene asientos; el confirm evita el viaje y aclara la consecuencia. |
Etiqueta del rep con [No vigente] | El operador puede asociar a un rep histórico para cuadres antiguos; el sufijo evita confusión. |