Puntos de entrada
dte-sdk es un solo paquete con cuatro puntos de entrada. La separación sigue una regla: cada entrada declara lo que necesita del entorno de ejecución. La entrada raíz no necesita más que APIs estándar, así que corre en cualquier parte. Las demás necesitan Node, y dte-sdk/pdf además necesita dos paquetes opcionales. Esta página dice qué contiene cada entrada, cómo importarla y por qué están separadas.
Las cuatro entradas#
| Importa desde | Qué contiene | Dónde corre |
|---|---|---|
dte-sdk | Documentos, timbres, firmas, sobres, libros, la verificación de documentos recibidos, el cliente de los servicios web del SII sobre fetch y el registro público de emisores autorizados | Node, Bun, Deno; se empaqueta para el navegador sin configuración |
dte-sdk/node | createNodeHttpsTransport, createDirectoryCapture, createDirectoryUploadArchive, createDirectoryTokenCache, createDirectoryFolioRegistry, findCafFiles, findLatestCafFile, resolveCafFile, cafFileScope | Node |
dte-sdk/portal | Portal del SII, folios y solicitud de CAF, el registro de folios, registro de compras y ventas, respaldo MIPYME, verificación de actividades y certificación: sets, ejecutor, intercambio, envío de las muestras y la postulación de boletas | Node |
dte-sdk/pdf | Muestras impresas y la oficina del SII de cada comuna (SII_OFFICES, siiOfficeForCommune) | Node, con pdf-lib y bwip-js instalados |
DteError e isDteError vienen de dte-sdk y reconocen lo que lanza cualquier entrada.
Cómo importar#
// Documentos, timbres, firmas, sobres, libros y el cliente de los servicios web del SII
import { issueDocument, createEnvelope, signEnvelope, createSiiClient, uploadEnvelope } from 'dte-sdk'
// Adaptadores que necesitan Node: un transporte HTTPS propio, archivos en disco y la búsqueda de archivos CAF
import {
createNodeHttpsTransport,
createDirectoryCapture,
createDirectoryUploadArchive,
createDirectoryTokenCache,
resolveCafFile,
} from 'dte-sdk/node'
// El portal del SII, los folios, el registro y la certificación
import { createPortalClient, queryFolioQuota, requestCaf } from 'dte-sdk/portal'
// Muestras impresas
import { renderPrintedSample } from 'dte-sdk/pdf'
// Una sola clase de error para todas
import { isDteError } from 'dte-sdk'Las entradas se combinan. Un servicio Node típico que envía documentos al SII y guarda registro de lo enviado importa de dos de ellas:
import { createSiiClient } from 'dte-sdk'
import { createDirectoryCapture, createDirectoryUploadArchive } from 'dte-sdk/node'
const sii = createSiiClient({
environment: 'certification',
certificate,
companyRut: '76543210-3',
capture: createDirectoryCapture({ dir: './capture' }), // cada intercambio HTTP, con los datos sensibles tachados
archive: createDirectoryUploadArchive({ dir: './uploads' }), // los bytes exactos de cada envío
})El cliente mismo está en la entrada raíz; en dte-sdk/node solo están los adaptadores que tocan el sistema de archivos o node:https.
La entrada raíz: dte-sdk#
Aquí vive todo lo que es cálculo o protocolo: el modelo de dominio, la creación de documentos y los totales, el manejo de CAF y certificados, el TED, XMLDSig, los sobres, los tres libros del SII, los catálogos de estados y de reclamos y el cliente de los servicios web del SII. El cliente habla por fetch por defecto, detrás de un puerto de transporte inyectable. También contiene tres cosas que no necesitan certificado: el registro público de emisores autorizados (queryAuthorizedIssuer), receiverAnswer, la función pura que aplica la regla de los ocho días de la Ley 19.983 a los eventos de reclamo de un documento, y verifyReceivedDocuments, que revisa la firma, el timbre y el CAF de los documentos que envía un proveedor con las llaves con que el SII firma cada CAF (SII_CAF_KEYS, y cafEnvironment nombra el ambiente de cada una).
La entrada raíz nunca importa un módulo node:. Eso tiene dos consecuencias:
- Corre en Node, Bun y Deno, y el CI importa cada entrada en Bun y Deno contra la compilación real.
- Se empaqueta para el navegador sin configuración. esbuild, webpack, Vite y rollup construyen un consumidor de la entrada raíz sin polyfills ni ajustes de
external. Eso la hace usable en un paso de compilación, un worker o un front end que, por ejemplo, calcule totales o valide un documento.
Que un módulo se empaquete para el navegador no hace del navegador el lugar adecuado para timbrar documentos. Timbrar necesita la llave privada del CAF y firmar necesita la llave privada del certificado; ambas son secretos que pertenecen a un servidor que controlas.
dte-sdk/node: los adaptadores de Node#
Son los adaptadores que la raíz no puede contener porque necesitan Node:
createNodeHttpsTransport(): un transporte HTTP sobrenode:https. Úsalo para una autoridad certificadora propia (tls.ca, que se suma a las raíces públicas que trae Node, no las reemplaza) o, solo si no queda otra, los ajustes TLS antiguos. La verificación TLS queda activa por defecto.createDirectoryCapture({ dir }): escribe cada intercambio HTTP en un directorio, con los datos sensibles tachados (tokens, semillas, cookies, llaves privadas), para tickets de soporte del SII.createDirectoryUploadArchive({ dir }): conserva los bytes exactos de cada envío que el SII respondió, con su track id y la respuesta del SII.createDirectoryTokenCache({ dir }): una caché de tokens en disco, para eltokenCachedel cliente del SII y elsessionCachedel cliente del portal, así un reinicio reutiliza el token o la sesión. Un archivo por clave, legible solo por su dueño; ver Seguridad.createDirectoryFolioRegistry({ dir }): un registro de folios en disco (el registro en memoria está endte-sdk/portal).findCafFiles,findLatestCafFileyresolveCafFile: buscan en un directorio los archivos CAF de un tipo y una empresa, del más nuevo al más viejo, y eligen el que aún tiene folios libres;cafFileScopeda el alcance de un archivo en el registro, el que devuelveresolveCafFile. Verdte-sdk/node.
Cargan sus módulos node: de forma perezosa, en el primer uso, nunca al importar. Importar dte-sdk/node no evalúa ningún módulo node:, lo que además se comprueba en las pruebas, así que el import en sí es seguro donde sea que corra.
dte-sdk/portal: el portal del SII#
El sitio web del SII autenticado con certificado, leído como HTML, más todo lo construido sobre él: datos de la empresa, cupo de folios, solicitud de CAF, anulación y recuperación, el registro de folios, el registro de compras y ventas, el respaldo MIPYME, la verificación de actividades y los sets y el ejecutor de certificación (incluidos el intercambio, el envío de las muestras impresas y la postulación de boletas). Necesita Node, porque fetch no puede presentar un certificado de cliente. Revisa Portal del SII y folios y Certificación.
dte-sdk/pdf: muestras impresas#
El PDF de cada documento según el manual del SII, con su timbre PDF417. Dibuja con pdf-lib y bwip-js, que son dependencias peer opcionales: no entran en tu instalación a menos que las pidas y se cargan en el primer uso.
npm install pdf-lib bwip-jsSin ellas, las funciones de render lanzan un DteError con código DEPENDENCY_UNAVAILABLE, y details.module nombra el paquete que no se pudo cargar:
import { isDteError } from 'dte-sdk'
import { renderPrintedSample, parsePrintableDocuments } from 'dte-sdk/pdf'
try {
const [doc] = parsePrintableDocuments(signed.xml)
const pdf = await renderPrintedSample(doc)
} catch (error) {
if (isDteError(error) && error.code === 'DEPENDENCY_UNAVAILABLE') {
console.error(`Install ${String(error.details.module)} to print samples`)
}
throw error
}También contiene la oficina del SII de cada comuna, que imprime el encabezado: SII_OFFICES y siiOfficeForCommune.
Las funciones por lote de esta entrada leen y escriben archivos, una razón más para que sea solo Node. Solo dte-sdk/pdf nombra las dependencias peer opcionales: nada más del paquete las carga. Revisa Muestras impresas.
Por qué están separadas#
- La entrada raíz se empaqueta para el navegador sin configuración. Con los adaptadores de Node dentro, esbuild y webpack fallaban ante el
import('node:…')dinámico, importaras lo que importaras. Ahora esbuild, webpack, Vite y rollup la construyen sin problema. - Un solo
DteErrorpara todas. La compilación comparte código entre entradas como chunks de módulos ES, y un chunk compartido conserva una sola copia de la clase de error. Por eso lo que lanza cualquier entrada pasainstanceofeisDteErrorde la raíz, lo que se comprueba en Node, Bun y Deno. - Una instalación más pequeña.
pdf-libybwip-jseran la mayor parte del tamaño instalado del paquete. Solodte-sdk/pdflos nombra, así que pueden ser dependencias peer opcionales sin romper el bundler de nadie más. - Cada entrada declara lo que necesita. La raíz ya no ofrece funciones solo de Node a un proyecto que apunta a un navegador.
Un consumidor con bundler obtiene casi los mismos bytes de cualquiera de las dos formas: el tree-shaking ya descartaba lo que no importaba, y node-forge, una dependencia, es la mayor parte del resto.
Solo módulos ES#
El paquete entrega módulos ES, sin compilación CommonJS. Necesita Node ≥ 22.12, la versión desde la cual require() puede cargar un módulo ES. Un proyecto CommonJS carga cada entrada con require('dte-sdk'), require('dte-sdk/node') y así sucesivamente, y obtiene el mismo módulo que entrega un import.
Jest en su modo CommonJS por defecto no puede hacer require() de un módulo ES. Ejecuta Jest en modo ESM o haz que transforme este paquete con transformIgnorePatterns.
En TypeScript, las entradas con subruta se resuelven con "moduleResolution": "nodenext", "bundler" y "node10": el paquete declara exports y typesVersions. Revisa Instalación.
Presupuestos de tamaño#
El tamaño del bundle es un presupuesto que se exige en CI con size-limit. Cada límite es minificado más brotli, de la entrada y los chunks que alcanza, sin contar las dependencias del paquete (node-forge, @xmldom/xmldom, fast-xml-parser, rut.ts, pdf-lib, bwip-js). La última columna es lo que midió size-limit para la compilación de la 0.8.0:
| Entrada | Presupuesto (min + brotli) | Medido en la 0.8.0 |
|---|---|---|
dte-sdk | 36 kB | 34,33 kB |
dte-sdk/node | 8 kB | 7,09 kB |
dte-sdk/portal | 76 kB | 74,90 kB |
dte-sdk/pdf | 11 kB | 10,93 kB |
Una compilación que supera un presupuesto hace fallar el CI, y subir un presupuesto es una decisión deliberada. La verificación de documentos recibidos llevó dte-sdk de 31,54 kB en la 0.3.0 a 33,87 kB en la 0.4.0, por encima de su presupuesto de 32 kB, que pasó a 34 kB. Las correcciones de la 0.7.0 lo llevaron a 34,36 kB, por encima de su presupuesto de 34 kB, que pasó a 36 kB: el chequeo de que las dos llaves de un CAF sean par, sus referencias de carácter leídas como las lee el DOM, la profundidad que se permite a todo DOM y el codResp de la respuesta del receptor. El catálogo de los códigos de documento del registro llevó dte-sdk/portal de 72,28 kB en la 0.1.0 a 72,99 kB en la 0.2.0, la verificación de actividades a 73,82 kB en la 0.3.0, los chequeos más estrictos de la 0.4.0 (la firma, y el bloque <CAF> que guarda parseCaf) a 73,88 kB, y devolver los CAF que una solicitud por tandas ya tiene, con su revisión de la cantidad, a 73,95 kB en la 0.5.0, todavía bajo su presupuesto. Devolver lo que los otros flujos de folios ya tienen cuando falla un paso posterior (la cuenta de una anulación, los CAF que descargó una reobtención, la memoria de una solicitud exacta que anuló), con el error original registrado, lo llevó a 74,33 kB en la 0.6.0, por encima de su presupuesto de 74 kB, que pasó a 76 kB. El parseCaf más estricto, que corre la búsqueda de archivos CAF, llevó dte-sdk/node de 6,34 kB a 6,39 kB en la 0.4.0, y el registro de folios escrito solo para su dueño a 6,41 kB en la 0.5.0. Leer un archivo CAF según su declaración XML lo llevó a 7,09 kB en la 0.7.0, con dte-sdk/portal en 74,90 kB y dte-sdk/pdf en 10,93 kB, todos bajo sus presupuestos. Leer un documento pagado al contado, con el código 27 del registro, dejó dte-sdk en 34,33 kB en la 0.8.0, y las otras entradas no se movieron. Como las dependencias quedan fuera, lo que llega a tu bundle es mayor: node-forge es la parte más grande.
Qué protege la separación#
La API pública de cada entrada está listada en una prueba (tests/public-api.test.ts), de modo que una función no puede pasar de una entrada a otra por accidente. Las pruebas de humo sobre el paquete compilado comprueban que cargar una entrada no evalúa ningún módulo node:, que la raíz no alcanza ninguno, que solo dte-sdk/pdf nombra las dependencias peer opcionales, que los errores cruzan entre entradas, que la raíz se empaqueta para el navegador y que los consumidores con resolución nodenext, bundler y node10 obtienen tipos reales.