Plataforma técnica · Sevastopol
Declaraciones View Island
Islands Sevastopol Declaraciones
Propósito
Section titled “Propósito”DeclaracionesViewIsland es la island que gestiona el Formulario 29 mensual desde Sevastopol: lista los F29 del tenant filtrados por el período activo, abre un detalle con líneas tributarias, expone el workflow BORRADOR → VALIDADO → DECLARADO → ANULADO y soporta dos acciones contables sensibles — baja administrativa de remanente SII y regeneración anual de asientos.
Ubicación
Section titled “Ubicación”Directorysevastopol/src/components/islands/declaraciones/
- DeclaracionesViewIsland.tsx — componente único, internamente
DeclaracionesPanel - DeclaracionesWorkspace.tsx — tokens visuales compartidos por las tres islands
- DeclaracionesViewIsland.tsx — componente único, internamente
Directorysevastopol/src/services/declaraciones/
- DeclaracionesService.ts — cliente HTTP (list, getById, generate, updateStatus, delete, ajuste baja, regenerar asientos)
Directorysevastopol/src/pages/api/declaraciones/f29/
- index.ts — proxy raíz
createProxy("/api/declaraciones/f29") - […all].ts — proxy catch-all
- index.ts — proxy raíz
Directorysevastopol/src/lib/hooks/
- useActiveTenant.ts
- useActivePeriod.ts
Composición
Section titled “Composición”La island es una página con cuatro superficies que comparten el mismo tenant y período:
| Superficie | Función | Visible cuando |
|---|---|---|
| Tabla de F29 | Lista filtrada por período + búsqueda libre. | Siempre. |
| Modal Generar | Crea un borrador para un periodo YYYY-MM. | Click en “Nueva declaración”. |
| Modal Detalle | Cabecera + tabla de líneas con sintetizados. | Click en el icono de ver fila. |
| Modal Baja remanente | Formulario controlado con validaciones de monto/motivo. | Click en “Baja remanente SII” desde el detalle, si aplica. |
| Modal Editar estado | Cambia el estado del workflow. | Click en el icono de editar fila. |
Hooks y estado global
Section titled “Hooks y estado global”const { tenantId } = useActiveTenant((id) => void loadData());const { period } = useActivePeriod();| Hook | Aporta | Reactividad |
|---|---|---|
useActiveTenant | UUID del tenant. | El callback re-fetcha la lista al cambiar tenant. |
useActivePeriod | { year, month } global. | No re-fetcha — el filtro se aplica en cliente sobre la lista ya cargada. |
El filtro de período es derivado en cliente porque la lista trae hasta 1000 F29 del tenant en una sola llamada:
const filteredItems = createMemo(() => { let result = items(); const activeYear = period().year ?? 0; const activeMonth = period().month ?? 0; if (activeYear > 0) { if (activeMonth > 0) { const p = `${activeYear}-${pad2(activeMonth)}`; result = result.filter((i) => i.periodo === p); } else { const pStart = `${activeYear}-`; result = result.filter((i) => i.periodo.startsWith(pStart)); } } // … búsqueda libre por periodo, RUT o monto return result;});Signals principales:
| Signal | Tipo | Uso |
|---|---|---|
items | F29Header[] | Lista cruda del tenant (limit=1000, offset=0). |
selectedDetail | F29Full | null | Detalle abierto del F29 actual. |
q | string | Búsqueda libre. |
isModalOpen / isDetailOpen / isAjusteOpen / isEditOpen | boolean | Estado de los 4 modales. |
genPeriod | string (YYYY-MM) | Período del modal de generación. |
editStatus | string | Selección del modal de cambio de estado. |
ajusteActual / ajusteMonto / ajusteMotivo / ajusteReferenciaSii / ajusteMongoNoteId | varias | Formulario controlado del modal de baja remanente. |
isGenerating / isLoadingAjuste / isSavingAjuste / isDeletingAjuste / isRegenerating | boolean | Bloqueos de UI por acción en curso. |
Estados del F29
Section titled “Estados del F29”El selector del modal de edición lista los cuatro estados del workflow. Cada estado habilita acciones distintas:
| Estado | Editable | Baja remanente | Cuenta para cierre |
|---|---|---|---|
BORRADOR | Sí | Sí (si hay remanente positivo) | No |
VALIDADO | Sí | Sí | No |
DECLARADO | No | No | Sí — habilita la regeneración de asientos |
ANULADO | No | No | No |
const statusOptions = [ { id: "BORRADOR", nombre: "Borrador" }, { id: "VALIDADO", nombre: "Validada" }, { id: "DECLARADO", nombre: "Declarada" }, { id: "ANULADO", nombre: "Anulada" },];El badge de la tabla usa StatusBadge con type="payroll" — mismo lenguaje visual que liquidaciones, no específico de F29.
Detalle: líneas derivadas
Section titled “Detalle: líneas derivadas”El modal de detalle pinta las líneas del F29 con dos transformaciones en cliente:
Línea 62 sintetizada (PPM por pagar)
Section titled “Línea 62 sintetizada (PPM por pagar)”Si el F29 no trae una línea 62 y sí trae una línea 88 (PPM) con monto distinto de cero, la UI agrega una línea 62 derivada para visibilidad contable:
const ppmPorPagar: F29Detail = { ...ppmLine, codigo_linea: "62", descripcion_linea: "PPM - Por pagar",};return [...details, ppmPorPagar].sort(/* por código numérico */);Esta línea es vista contable, no se envía al SII como dato adicional.
Línea 89B (baja remanente)
Section titled “Línea 89B (baja remanente)”Cuando existe un ajuste de baja, el detalle incluye una línea con codigo_linea = "89B". La UI la reconoce con isBajaRemanenteLine(row) y pinta el badge BAJA SII al lado de la descripción.
function isBajaRemanenteLine(row: F29Detail): boolean { return String(row.codigo_linea).toUpperCase() === "89B";}Baja administrativa de remanente
Section titled “Baja administrativa de remanente”El botón Baja remanente SII aparece dentro del modal de detalle bajo dos condiciones combinadas (canManageAjusteBaja):
const canManageAjusteBaja = createMemo(() => { const f29 = selectedDetail(); if (!f29) return false; if (Number(f29.remanente_anterior) <= 0 && !hasAjusteBaja()) return false; return ["BORRADOR", "VALIDADO"].includes(f29.estado);});El label del botón cambia: Editar baja remanente si ya existe, Baja remanente SII si es nueva. Cuando ya existe, aparece además un botón secundario Eliminar baja que llama a eliminarAjusteBajaRemanente(id, tenant) y restituye el remanente.
Validaciones del formulario
Section titled “Validaciones del formulario”Cinco reglas duras antes de enviar:
if (monto <= 0) → "Ingrese un monto mayor que cero"if (monto > ajusteEditableRemanente()) → "El monto excede el remanente disponible"if (motivo.length < 5) → "El motivo debe tener al menos 5 caracteres"if (motivo.length > 1000) → "El motivo no puede superar 1000 caracteres"if (referenciaSii.length > 100) → "La referencia SII no puede superar 100 caracteres"if (mongoNoteId.length > 50) → "La nota Mongo no puede superar 50 caracteres"ajusteEditableRemanente() es el remanente disponible incluyendo el ajuste actual si se está editando — permite reasignar sin perder el saldo del ajuste previo:
function ajusteEditableRemanente(): number { const f29 = selectedDetail(); if (!f29) return 0; return Number(f29.remanente_anterior) + Number(ajusteActual()?.monto ?? 0);}Preview del impacto
Section titled “Preview del impacto”El modal muestra en vivo el remanente después del ajuste computado con Math.max(0, ajusteEditableRemanente() - ajusteMontoNumber()), y un recordatorio del asiento contable que generará el backend:
Asiento contable: D 3701040 IVA no Recuperable / H 1108002 IVA Crédito FiscalTras guardar, setSelectedDetail(updated) reemplaza el detalle en memoria con la respuesta del backend (que ya incluye la línea 89B sintetizada) y loadData() refresca la tabla principal.
Regeneración anual de asientos
Section titled “Regeneración anual de asientos”Botón secundario en la cabecera (Regenerar asientos {año}). Sólo se monta si el período activo tiene año. Pide confirmación explícita:
if (!confirm( `Regenerar asientos F29 del año ${anio}?\nSe eliminarán y reinsertarán a partir de los F29 en estado DECLARADO.`,)) return;La acción llama a DeclaracionesService.regenerarAsientosAnual(anio, tenant) y reporta ${res.anio} regenerados (${res.filas} filas). Es destructiva sobre el lado contabilidad — sólo debe usarse cuando cambia el mapeo de cuentas o tras correcciones masivas.
Flujo de carga y acciones
Section titled “Flujo de carga y acciones”flowchart TB
MOUNT["onMount<br/>tenantId ready"]
CHGT["useActiveTenant onChange"]
LOAD["loadData()<br/>DeclaracionesService.list({limit:1000})"]
ST["setItems(F29Header[])"]
FILT["filteredItems<br/>(period + q)"]
TABLE["DataTable F29"]
OPEN["openDetail(row)"]
GETBYID["DeclaracionesService.getById(id, tenant)"]
SD["setSelectedDetail(F29Full)"]
DTABLE["detailTableRows<br/>+ Línea 62 derivada<br/>+ marcador 89B"]
AJ["openAjusteModal"]
AJGET["getAjusteBajaRemanente"]
AJSAVE["registrarAjusteBajaRemanente"]
AJDEL["eliminarAjusteBajaRemanente"]
GEN["openNewModal → generate"]
EDIT["openEdit → updateStatus"]
DEL["handleDelete → delete"]
REGEN["handleRegenerarAsientos<br/>regenerarAsientosAnual"]
MOUNT --> LOAD
CHGT --> LOAD
LOAD --> ST --> FILT --> TABLE
TABLE --> OPEN --> GETBYID --> SD --> DTABLE
DTABLE --> AJ --> AJGET
AJ --> AJSAVE --> SD
AJ --> AJDEL --> SD
GEN --> LOAD
EDIT --> LOAD
DEL --> LOAD
REGEN -.no recarga.-> TABLE Endpoints consumidos
Section titled “Endpoints consumidos”Todos van por DeclaracionesService (cliente HTTP en sevastopol/src/services/declaraciones/), que a su vez delega en /api/declaraciones/f29 (proxy).
| Método | Ruta lógica | Operación |
|---|---|---|
GET | /api/declaraciones/f29?limit=1000&offset=0 | Lista de cabeceras del tenant. |
GET | /api/declaraciones/f29/:id | F29 completo con detalle de líneas. |
POST | /api/declaraciones/f29/generate | Genera borrador desde la base operacional. |
PUT | /api/declaraciones/f29/:id/status | Cambia el estado del workflow. |
DELETE | /api/declaraciones/f29/:id | Elimina (sólo no-DECLARADO). |
GET | /api/declaraciones/f29/:id/ajuste-baja-remanente | Lee el ajuste actual si existe. |
POST | /api/declaraciones/f29/:id/ajuste-baja-remanente | Crea o reemplaza el ajuste. |
DELETE | /api/declaraciones/f29/:id/ajuste-baja-remanente | Elimina el ajuste y restituye remanente. |
POST | /api/declaraciones/f29/regenerar-asientos/:anio | Reconstruye los asientos del año. |
tenant_id viaja como query param en cada llamada.
Proxy local
Section titled “Proxy local”import { createProxy } from "@/lib/proxyUtils";export const { GET, POST, PUT, DELETE, PATCH } = createProxy( "/api/declaraciones/f29",);A diferencia del proxy de ciclo-contable (read-only), aquí se exportan todos los métodos porque el dominio incluye creación, actualización y eliminación. El detalle del BFF está en BFF Proxy a Orchestrator.
Reglas de UI
Section titled “Reglas de UI”| Regla | Motivo |
|---|---|
Lista completa en una llamada (limit=1000) | Evita paginación; el F29 es bajo volumen por tenant/año. El filtro de período se aplica en cliente. |
| Confirmar antes de regenerar asientos | La operación es destructiva sobre declaraciones.asientos_f29. |
| Confirmar antes de eliminar | Se prefiere ANULADO sobre DELETE para mantener trazabilidad. |
Bloquear baja en DECLARADO/ANULADO | El ajuste contable de baja sólo procede sobre borrador o validado. |
monto ≤ remanente disponible | Una baja no puede exceder el remanente acumulado más el monto del ajuste actual (si se está editando). |
motivo ≥ 5 caracteres | Trazabilidad mínima para el auditor — no se admite “ok” como justificación. |
Reemplazar selectedDetail con la respuesta del save | El backend devuelve el F29 ya con la línea 89B sintetizada, evita un fetch extra. |
| Línea 62 derivada en cliente, no SII | El sintetizado es vista contable; el F29 enviado al SII conserva sólo la línea 88. |
Hardcoded rut = "6000431-5" como fallback | TODO conocido — el RUT debería resolverse desde el tenant; ver línea 137 del archivo fuente. |