Skip to content

Plataforma técnica · Sevastopol

DJ 1887 View Island

Islands Sevastopol Declaraciones

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.

  • Directorysevastopol/src/components/islands/declaraciones/
    • DJ1887ViewIsland.tsx — componente único, internamente DJ1887Panel
    • DeclaracionesWorkspace.tsx — tokens visuales compartidos
  • Directorysevastopol/src/pages/api/declaraciones-juradas/1887/
    • index.ts — proxy raíz createProxy("/api/declaraciones-juradas/1887")
    • […all].ts — proxy catch-all
  • Directorysevastopol/src/lib/hooks/
    • useActiveTenant.ts
    • useActivePeriod.ts
const { tenantId } = useActiveTenant((id) => void loadData(id));
const { period } = useActivePeriod(() => {
if (tenantId()) loadData(tenantId());
});
const activeYear = () => period().year ?? new Date().getFullYear();
HookAportaReactividad
useActiveTenantUUID del tenant.Re-fetcha al cambiar tenant.
useActivePeriod{ year } global.Re-fetcha al cambiar año (mes ignorado — la DJ es anual).

Signals locales:

SignalTipoUso
headerDJ1887Header | nullCabecera de la DJ del año (null si no existe).
detalleDJ1887Detalle[]Líneas por trabajador, con detalle_mensual anidado.
loadingbooleanBloqueo de UI durante el fetch.
processingbooleanBloqueo durante el POST /generate.
genModalOpenbooleanModal de confirmación de generación.
searchTermstringFiltro libre por RUT o nombre del trabajador.

La island bifurca según header():

HeaderVista renderizada
null (HTTP 404 o sin generar)Estado vacío con CTA Generar DJ 1887.
ExisteDos 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;
}

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ódigoSignificado SIIEtiqueta en UI
ATrabajador agrícola jornada completa.Agrícola Completa
BTrabajador agrícola jornada parcial.Agrícola Parcial
CTrabajador no agrícola jornada completa.No Agrícola Completa
PTrabajador no agrícola jornada parcial.Parcial
GAutoridad de gobierno (art. 52 bis LIR).Gobierno

Códigos desconocidos pasan sin transformar — la columna conserva el original.

Filtro libre por RUT o nombre. Las columnas separan histórico y actualizado, y exponen los campos especiales de la DJ:

ColumnaOrigenNotas
numero_correlativoAsignado por el generador.
rut_trabajadorRUT validado.
nombre_trabajadorNombre.Fallback "Desconocido" si viene null.
renta_neta_pagadaRenta neta actualizada al cierre.Re-expresión IPC/UF.
iusc_retenidoIUSC actualizado al cierre.Re-expresión IPC/UF.
renta_no_gravada_actSuma derivada de detalle_mensual.no_renta_actualizada.Calculada en cliente.
renta_neta_sin_actualizarRenta histórica (pesos nominales del mes).Base de cuadre con F29.
iusc_sin_actualizarIUSC histórico.Base de cuadre con F29.
leyes_socialesCotizaciones + mutual + seguros del empleador.
tipo_jornada_labelSigla traducida vía JORNADAS_MAP.
horas_semanalesHoras según contrato.
meses_countmeses_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",
};
});

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):

ColumnaSignificado
MesNombre corto (Ene, Feb, …).
Renta Hist. / Renta Act.Renta nominal del mes / re-expresada al cierre.
FactorFactor 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 };
});

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.

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();
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
MétodoRutaOperació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.

sevastopol/src/pages/api/declaraciones-juradas/1887/index.ts
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.

ReglaMotivo
404 como estado vacíoEl 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 mesLa DJ es anual.
Traducir códigos de jornada en clienteMostrar A, B, C cruda obliga al operador a recordar las siglas; el mapa local es estable.
Cuadre con F29 usando históricosComparar IUSC actualizado contra F29 nominal produce diferencias por corrección monetaria que no son errores reales.
Agregación mensual sobre filas filtradasEl detalle mensual refleja lo que el usuario está viendo.
Sobrescritura silenciosa en generateEl 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 clienteEl backend devuelve detalle mensual, no el total actualizado anual; la UI suma para mostrarlo en la tabla.