Plataforma técnica · Sevastopol
DJ 1887 View Island
Islands Sevastopol Declaraciones
Propósito
Section titled “Propósito”DJ1887ViewIsland es la island anual que opera la Declaración Jurada 1887 sobre sueldos, Impuesto Único de Segunda Categoría y rentas del artículo 42 N° 1 LIR. Lee del backend la cabecera + detalle por trabajador del año activo, agrega los movimientos mensuales con factores de actualización monetaria, mapea códigos de jornada del SII y permite generar la DJ desde las liquidaciones del año y descargar el CSV.
Ubicación
Section titled “Ubicación”Directorysevastopol/src/components/islands/declaraciones/
- DJ1887ViewIsland.tsx — componente único, internamente
DJ1887Panel - DeclaracionesWorkspace.tsx — tokens visuales compartidos
- DJ1887ViewIsland.tsx — componente único, internamente
Directorysevastopol/src/pages/api/declaraciones-juradas/1887/
- index.ts — proxy raíz
createProxy("/api/declaraciones-juradas/1887") - […all].ts — proxy catch-all
- index.ts — proxy raíz
Directorysevastopol/src/lib/hooks/
- useActiveTenant.ts
- useActivePeriod.ts
Hooks y estado global
Section titled “Hooks y estado global”const { tenantId } = useActiveTenant((id) => void loadData(id));const { period } = useActivePeriod(() => { if (tenantId()) loadData(tenantId());});const activeYear = () => period().year ?? new Date().getFullYear();| Hook | Aporta | Reactividad |
|---|---|---|
useActiveTenant | UUID del tenant. | Re-fetcha al cambiar tenant. |
useActivePeriod | { year } global. | Re-fetcha al cambiar año (mes ignorado — la DJ es anual). |
Signals locales:
| Signal | Tipo | Uso |
|---|---|---|
header | DJ1887Header | null | Cabecera de la DJ del año (null si no existe). |
detalle | DJ1887Detalle[] | Líneas por trabajador, con detalle_mensual anidado. |
loading | boolean | Bloqueo de UI durante el fetch. |
processing | boolean | Bloqueo durante el POST /generate. |
genModalOpen | boolean | Modal de confirmación de generación. |
searchTerm | string | Filtro libre por RUT o nombre del trabajador. |
Modos de vista
Section titled “Modos de vista”La island bifurca según header():
| Header | Vista renderizada |
|---|---|
null (HTTP 404 o sin generar) | Estado vacío con CTA Generar DJ 1887. |
| Existe | Dos WorkspaceSectionCard: Trabajadores informados + Resumen consolidado por mes. |
El 404 se trata como “DJ no generada aún”, no como error:
if (resp.status === 404) { setHeader(null); setDetalle([]); return;}Códigos de jornada
Section titled “Códigos de jornada”El sistema mapea las siglas SII a etiquetas legibles antes de renderizar la columna Jornada:
const JORNADAS_MAP: Record<string, string> = { A: "Agrícola Completa", B: "Agrícola Parcial", C: "No Agrícola Completa", P: "Parcial", G: "Gobierno",};| Código | Significado SII | Etiqueta en UI |
|---|---|---|
A | Trabajador agrícola jornada completa. | Agrícola Completa |
B | Trabajador agrícola jornada parcial. | Agrícola Parcial |
C | Trabajador no agrícola jornada completa. | No Agrícola Completa |
P | Trabajador no agrícola jornada parcial. | Parcial |
G | Autoridad de gobierno (art. 52 bis LIR). | Gobierno |
Códigos desconocidos pasan sin transformar — la columna conserva el original.
Tabla de trabajadores
Section titled “Tabla de trabajadores”Filtro libre por RUT o nombre. Las columnas separan histórico y actualizado, y exponen los campos especiales de la DJ:
| Columna | Origen | Notas |
|---|---|---|
numero_correlativo | Asignado por el generador. | — |
rut_trabajador | RUT validado. | — |
nombre_trabajador | Nombre. | Fallback "Desconocido" si viene null. |
renta_neta_pagada | Renta neta actualizada al cierre. | Re-expresión IPC/UF. |
iusc_retenido | IUSC actualizado al cierre. | Re-expresión IPC/UF. |
renta_no_gravada_act | Suma derivada de detalle_mensual.no_renta_actualizada. | Calculada en cliente. |
renta_neta_sin_actualizar | Renta histórica (pesos nominales del mes). | Base de cuadre con F29. |
iusc_sin_actualizar | IUSC histórico. | Base de cuadre con F29. |
leyes_sociales | Cotizaciones + mutual + seguros del empleador. | — |
tipo_jornada_label | Sigla traducida vía JORNADAS_MAP. | — |
horas_semanales | Horas según contrato. | — |
meses_count | meses_pagados.length. | Derivado en cliente. |
.map((d) => { const noGravadaAct = (d.detalle_mensual || []).reduce( (sum, m) => sum + (Number(m.no_renta_actualizada) || 0), 0, ); return { ...d, renta_no_gravada_act: Math.round(noGravadaAct), meses_count: d.meses_pagados?.length || 0, tipo_jornada_label: JORNADAS_MAP[d.tipo_jornada] || d.tipo_jornada, nombre_trabajador: d.nombre_trabajador || "Desconocido", };});Detalle mensual consolidado
Section titled “Detalle mensual consolidado”La segunda tarjeta agrega detalle_mensual de todos los trabajadores filtrados mes a mes — pareja histórico vs actualizado por concepto:
const aggregatedMonthly = createMemo(() => { const months: Record<number, any> = {}; displayRows().forEach((worker) => { worker.detalle_mensual?.forEach((m) => { if (!months[m.mes]) { months[m.mes] = { ...m }; } else { months[m.mes].renta_sin_actualizar += m.renta_sin_actualizar; months[m.mes].renta_actualizada += m.renta_actualizada; months[m.mes].iusc_sin_actualizar += m.iusc_sin_actualizar; months[m.mes].iusc_actualizado += m.iusc_actualizado; months[m.mes].no_renta_sin_actualizar += m.no_renta_sin_actualizar; months[m.mes].no_renta_actualizada += m.no_renta_actualizada; } }); }); return Object.values(months).sort((a, b) => a.mes - b.mes);});Columnas del detalle mensual (cada concepto en pareja):
| Columna | Significado |
|---|---|
| Mes | Nombre corto (Ene, Feb, …). |
| Renta Hist. / Renta Act. | Renta nominal del mes / re-expresada al cierre. |
| Factor | Factor de actualización del SII (3 decimales). |
| IUSC Hist. / IUSC Act. | IUSC nominal / actualizado. |
| No Grav. Hist. / No Grav. Act. | Rentas no gravadas nominales / actualizadas. |
Totales en pills (InlineMetricPill): No renta act. (total actualizado en el período visible) y No renta hist. (total nominal):
const totals = createMemo(() => { let noRentaHist = 0; let noRentaAct = 0; detalle().forEach((worker) => { worker.detalle_mensual?.forEach((m) => { noRentaHist += m.no_renta_sin_actualizar || 0; noRentaAct += m.no_renta_actualizada || 0; }); }); return { noRentaHist, noRentaAct };});Acciones
Section titled “Acciones”Generar DJ 1887
Section titled “Generar DJ 1887”Modal de confirmación → POST /api/declaraciones-juradas/1887/generate con { año: activeYear() }. La respuesta trae totalTrabajadores:
toast.push(`Generada exitosamente (${json.totalTrabajadores} registros)`, "ok");Tras éxito el modal se cierra y loadData(tid) refresca la vista. Si ya existe DJ para el año, el backend la sobreescribe sin advertencia explícita en la UI.
Descargar CSV
Section titled “Descargar CSV”Disponible sólo cuando existe header(). Genera el archivo con nombre DJ1887_{año_tributario}.csv desde un Blob, vía el mismo patrón que DJ 1879:
const res = await authenticatedFetch(`${API_BASE}/${h.id}/csv?tenant_id=${tid}`);const blob = await res.blob();const a = document.createElement("a");a.href = URL.createObjectURL(blob);a.download = `DJ1887_${h.año_tributario}.csv`;a.click();Flujo de carga
Section titled “Flujo de carga”flowchart TB
MOUNT["onMount<br/>tenantId ready"]
CHGT["useActiveTenant onChange"]
CHGP["useActivePeriod onChange"]
LOAD["loadData(tid)<br/>GET /1887/anio/{year}"]
S404{"status === 404?"}
EMPTY["header=null<br/>detalle=[]"]
OK["header=DJ1887Header<br/>detalle=DJ1887Detalle[]"]
ROWS["displayRows<br/>(filter + jornada label + no_grav_act)"]
AGG["aggregatedMonthly<br/>(suma 6 conceptos por mes)"]
T1["DataTable trabajadores"]
T2["DataTable detalle mensual"]
CSV["downloadCSV<br/>GET /1887/{id}/csv"]
GEN["generate<br/>POST /1887/generate"]
MOUNT --> LOAD
CHGT --> LOAD
CHGP --> LOAD
LOAD --> S404
S404 -- sí --> EMPTY
S404 -- no --> OK
OK --> ROWS --> T1
ROWS --> AGG --> T2
T1 --> CSV
T2 --> CSV
GEN --> LOAD Endpoints consumidos
Section titled “Endpoints consumidos”| Método | Ruta | Operación |
|---|---|---|
GET | /api/declaraciones-juradas/1887/anio/:año?tenant_id=... | Cabecera + detalle del año. 404 se trata como vacío. |
POST | /api/declaraciones-juradas/1887/generate?tenant_id=... | Genera/sobreescribe la DJ desde las liquidaciones aprobadas. |
GET | /api/declaraciones-juradas/1887/:id/csv?tenant_id=... | Descarga el archivo en formato SII. |
Las llamadas usan authenticatedFetch directo, no un cliente Service envuelto.
Proxy local
Section titled “Proxy local”import { createProxy } from "@/lib/proxyUtils";export const { GET, POST, PUT, DELETE } = createProxy( "/api/declaraciones-juradas/1887",);Exporta los cuatro métodos clásicos. Detalle del BFF en BFF Proxy a Orchestrator.
Reglas de UI
Section titled “Reglas de UI”| Regla | Motivo |
|---|---|
404 como estado vacío | El backend no distingue “no existe aún” de error; la UI ofrece la acción de generar. |
| Re-fetch al cambiar año, no al cambiar mes | La DJ es anual. |
| Traducir códigos de jornada en cliente | Mostrar A, B, C cruda obliga al operador a recordar las siglas; el mapa local es estable. |
| Cuadre con F29 usando históricos | Comparar IUSC actualizado contra F29 nominal produce diferencias por corrección monetaria que no son errores reales. |
| Agregación mensual sobre filas filtradas | El detalle mensual refleja lo que el usuario está viendo. |
| Sobrescritura silenciosa en generate | El backend reemplaza la DJ del año sin advertir; la UI debería confirmar antes (TODO). |
Descarga deshabilitada sin header() | Sin DJ generada no hay CSV que descargar. |
no_renta_gravada_act derivada en cliente | El backend devuelve detalle mensual, no el total actualizado anual; la UI suma para mostrarlo en la tabla. |