Skip to content

Plataforma técnica · Sevastopol

DJ 1879 View Island

Islands Sevastopol Declaraciones

DJ1879ViewIsland es la island anual que opera la Declaración Jurada 1879 sobre honorarios, directores y retenciones relacionadas. Lee del backend la cabecera + detalle por receptor del año activo, agrega los movimientos mensuales con factores de actualización y permite generar la DJ desde la base operacional y descargar el CSV con el formato del SII.

  • Directorysevastopol/src/components/islands/declaraciones/
    • DJ1879ViewIsland.tsx — componente único, internamente DJ1879Panel
    • DeclaracionesWorkspace.tsx — tokens visuales compartidos
  • Directorysevastopol/src/pages/api/declaraciones-juradas/1879/
    • index.ts — proxy raíz createProxy("/api/declaraciones-juradas/1879")
    • […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 (el month no aplica aquí — la DJ es anual).

Signals locales:

SignalTipoUso
headerDJ1879Header | nullCabecera de la DJ del año (null si no existe).
detalleDJ1879Detalle[]Líneas por receptor, 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 receptor.

La island bifurca según header():

HeaderVista renderizada
null (HTTP 404 del backend o sin generar)Estado vacío con CTA Generar DJ 1879.
ExisteDos WorkspaceSectionCard apiladas: Receptores informados + Resumen consolidado por mes.

El 404 no es un error — el handler lo trata como “DJ no existe aún”:

if (resp.status === 404) {
setHeader(null);
setDetalle([]);
return;
}
if (!resp.ok) throw new Error(`Error ${resp.status}`);

Filtro libre por RUT o nombre. Las columnas exponen los campos especiales del archivo SII en columnas independientes — no se suman entre sí:

ColumnaOrigenNotas
numero_correlativoAsignado por el generador.
rut_receptorRUT validado del receptor.
nombre_receptorNombre del prestador o director.Fallback "Desconocido" cuando viene null.
retencion_honorariosArtículo 42 N° 2 LIR.Actualizada al cierre.
retencion_directoresArtículo 48 LIR (S.A.).Actualizada al cierre.
monto_bruto_totalBruto anual del receptor.
retencion_prestamosRetención adicional 3% por préstamos solidarios.Columna independiente del SII.
meses_countmeses_con_renta.length.Derivado en cliente.
.map((d) => ({
...d,
meses_count: d.meses_con_renta?.length || 0,
nombre_receptor: d.nombre_receptor || "Desconocido",
total_retencion: d.retencion_honorarios + d.retencion_directores,
}));

La segunda tarjeta agrega los detalle_mensual de todos los receptores filtrados mes a mes. Es una vista comparativa histórico vs actualizado por mes:

const aggregatedMonthly = createMemo(() => {
const months: Record<number, any> = {};
displayRows().forEach((receptor) => {
receptor.detalle_mensual?.forEach((m) => {
if (!months[m.mes]) {
months[m.mes] = { ...m };
} else {
months[m.mes].monto_bruto += m.monto_bruto;
months[m.mes].retencion_sin_actualizar += m.retencion_sin_actualizar;
months[m.mes].retencion_actualizada += m.retencion_actualizada;
}
});
});
return Object.values(months).sort((a, b) => a.mes - b.mes);
});

Columnas del detalle mensual:

ColumnaSignificado
MesNombre corto (Ene, Feb, …).
BrutoBruto del mes (suma de receptores filtrados).
Ret. Hist.Retención original del mes en pesos nominales.
FactorFactor de actualización IPC/UF del SII para ese mes (3 decimales).
Ret. Act.Retención re-expresada al cierre del año tributario.

Modal de confirmación → POST /api/declaraciones-juradas/1879/generate con { año: activeYear() }. La respuesta trae totalReceptores y se muestra en el toast:

toast.push(`Generada exitosamente (${json.totalReceptores} 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 — la UI no advierte explícitamente.

Disponible sólo cuando existe header(). Genera un archivo con nombre DJ1879_{año_tributario}.csv desde un Blob:

const res = await authenticatedFetch(`${API_BASE}/${h.id}/csv?tenant_id=${tid}`);
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `DJ1879_${h.año_tributario}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);

El CSV viaja como binario por el proxy (createProxy reenvía Content-Type y el cuerpo crudo).

flowchart TB
  MOUNT["onMount<br/>tenantId ready"]
  CHGT["useActiveTenant onChange"]
  CHGP["useActivePeriod onChange"]
  LOAD["loadData(tid)<br/>GET /1879/anio/{year}"]
  S404{"status === 404?"}
  EMPTY["header=null<br/>detalle=[]"]
  OK["header=DJ1879Header<br/>detalle=DJ1879Detalle[]"]
  ROWS["displayRows<br/>(filter por searchTerm)"]
  AGG["aggregatedMonthly<br/>(suma por mes)"]
  T1["DataTable receptores"]
  T2["DataTable detalle mensual"]
  CSV["downloadCSV<br/>GET /1879/{id}/csv"]
  GEN["generate<br/>POST /1879/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/1879/anio/:año?tenant_id=...Cabecera + detalle del año. 404 se trata como vacío.
POST/api/declaraciones-juradas/1879/generate?tenant_id=...Genera/sobreescribe la DJ del año desde la base operacional.
GET/api/declaraciones-juradas/1879/: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/1879/index.ts
import { createProxy } from "@/lib/proxyUtils";
export const { GET, POST, PUT, DELETE } = createProxy(
"/api/declaraciones-juradas/1879",
);

Se exportan los cuatro métodos clásicos; el detalle del BFF está en BFF Proxy a Orchestrator.

ReglaMotivo
404 como estado vacíoEl backend no distingue “no existe aún” de error — la UI debe ofrecer la acción de generar.
Re-fetch al cambiar año, no al cambiar mesLa DJ es anual; el mes del período activo no influye.
Agregación mensual sobre filas filtradasEl “detalle mensual” refleja lo que el usuario está viendo; cambiar el search lo recalcula.
Cuadre con F29 usando históricosComparar actualizado contra F29 nominal produce diferencias artificiales por corrección monetaria.
Columnas independientes para campos especialesretencion_directores, monto_isla_pascua y retencion_prestamos no se suman a la retención estándar — viajan por separado al SII.
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.