# Introducción a SII SDK > Qué es SII SDK, qué hace, qué no hace y el modelo mental de la librería: Resultado, texto literal del SII y tu sistema decide cuándo llamar. Fuente: https://sii.devlas.cl/docs/introduccion # Introducción a SII SDK SII SDK es una librería TypeScript de Devlas SpA (paquetes `@devlas-cl/*`, versión publicada 0.6.0) para emitir y recibir documentos tributarios electrónicos ante el SII de Chile. Se instala como dependencia y corre dentro del sistema del integrador; no es un servicio alojado ni consulta servidores de Devlas al ejecutarse. Esta página resume qué hace, qué deja en manos de tu sistema y las reglas que siguen todas sus funciones. SII SDK no es un producto del Servicio de Impuestos Internos ni está afiliado a él. ## Qué hace - Valida los datos de un documento contra las reglas del SII y los largos del XSD, sin recortar nada en silencio. - Calcula montos (neto, IVA, exento, descuentos, impuestos adicionales), arma el XML en el orden del esquema, lo timbra con el CAF, lo firma y lo valida contra el XSD oficial antes de enviarlo. - Se autentica (semilla y token), envía (boletas 39 y 41 por la API REST del SII; facturas, notas y guías por `DTEUpload`) y consulta estados. - Pide, reobtiene y anula folios (CAF) y los repone con `autoCaf` (paquete `@devlas-cl/folios`). - Lee el Registro de Compras y Ventas (RCV), responde el intercambio entre contribuyentes y registra aceptación o reclamo. - Recorre la certificación ante el SII con un motor por etapas (paquete `@devlas-cl/certificacion`) y verifica la habilitación en producción. - Genera la representación impresa (ticket térmico y PDF) con el timbre PDF417. ## Qué no hace (y qué hace tu sistema) | La librería no... | Qué hace tu sistema | |---|---| | Persiste nada: ni folios, ni sesiones, ni CAF, ni el XML emitido, ni el estado de un envío. | Guarda el XML firmado (la ley obliga a conservarlo 6 años), el track id (como texto) y el estado. Folios, sesiones y CAF entran por los puertos `AlmacenCaf` y `AlmacenSesion`, que implementas tú. | | Planifica: no hay cola, temporizadores ni reintentos de negocio. | Decide cuándo llamar. La librería responde "qué hacer ahora" con funciones puras: `siguienteConsultaDeEstado`, `siguienteAccionDeEnvio`, `decidirReenvio`, `autoCaf`, `avanzarCorrida`. | | Agrupa boletas por tiempo. | Cierra el grupo cuando `siguienteAccionDeEnvio` responde `armar_sobres`; la librería arma el sobre de hasta 500 boletas. | | Recorta o corrige datos: un giro de 41 caracteres es un error, no se corta a 40. | Corrige el dato. | | Lee tu correo. | Entrega el adjunto `EnvioDTE` o las glosas del correo del SII a las funciones de intercambio y de `protocolo`. | | Cifra tus secretos. | Cifra en reposo el `.pfx`, su clave, el CAF (trae una llave privada) y las cookies de sesión. | | Decide tu negocio (medios de pago, inventario, qué se documenta en una venta mixta). | Lo resuelve antes de llamar. | | Llama a servidores de Devlas al ejecutarse. | Nada. Un servidor caído de Devlas no puede impedir emitir. | | Tiene estado global. | Pasa la configuración y los puertos a cada llamada. Varias instancias y réplicas no se pisan. | ## Modelo mental ### 1. Resultado en vez de excepciones Toda operación devuelve `Resultado`: ```ts import type { ErrorSii, Resultado } from '@devlas-cl/comun' // type Resultado = { readonly ok: true; readonly valor: T } | { readonly ok: false; readonly error: E } declare const r: Resultado<{ trackId: string }, ErrorSii> if (!r.ok) { r.error.codigo // CodigoError: catálogo cerrado ('VALIDACION', 'TRANSPORTE', 'LIMITE_SESIONES', ..., 'DESCONOCIDO') r.error.accion // 'reintentar' | 'esperar' | 'corregir' | 'tramitar' | 'escalar' r.error.mensajeSii // texto LITERAL del SII, o null si el error nace en la librería } else { r.valor.trackId } ``` - Nada lanza por datos tuyos ni del SII. Solo lanza un argumento mal formado en la llamada (error de programación). - El patrón de uso es `if (!r.ok) return r`. - Decide por `codigo`, `codigoSii` y `accion`, nunca comparando subcadenas de `mensajeSii`. - El estado de un sobre no es un error: `consultarEstadoEnvio` devuelve `ok: true` incluso con veredicto `rechazado`. ### 2. El texto del SII llega literal `mensajeSii` es el texto exacto del SII, decodificado del transporte (juego de caracteres y entidades HTML) y sin traducir ni recortar. Un mensaje que el catálogo no reconoce llega como `DESCONOCIDO` con su texto intacto y acción `escalar`. Para mostrar un error a una persona, `mensajeParaUsuario(error)` de `@devlas-cl/comun` devuelve `{ titulo, queHacer, detalle, nivel, reintentable }` en español neutro, con el literal del SII aparte en `detalle`. ### 3. El ambiente es explícito `Ambiente = 'certificacion' | 'produccion'`. Son registros distintos del SII: el folio, la resolución, el RCV y las sesiones de uno no existen en el otro. Se pasa en la configuración de cada llamada (`ConfigToken.ambiente`, `ConfigPortal.ambiente`) y en la clave de folios (`ClaveCaf.ambiente`). ### 4. El reloj es un parámetro Las funciones puras reciben `ahora: Date`; las llamadas reciben un `Reloj` (`{ ahora: () => Date }`). Ningún paquete usa `Date.now` ni `process.env` por su cuenta. La fecha tributaria se calcula en hora de Chile con `fechaTributaria(instante)` de `@devlas-cl/dominio`, nunca con `toISOString()`: entre las 20:00 y las 24:00 de Chile la fecha UTC ya es la del día siguiente y el SII responde `FECHA NO CORRESPONDE AL ENVIO`. ### 5. Cuatro puertos Lo que varía entre instalaciones entra por puertos que implementa tu sistema (la librería trae versiones en memoria para pruebas): | Puerto | Contrato (de `@devlas-cl/comun`) | Para qué | |---|---|---| | `Http` | `{ solicitar(solicitud: SolicitudHttp): Promise }` | Llegar al SII. No sigue redirecciones, no guarda cookies, no reintenta y entrega el cuerpo en bytes. | | `AlmacenSesion` + `Bloqueos` | `obtener/guardar/borrar` y `adquirir/renovar/liberar` | La sesión del portal y el token, compartidos entre réplicas, y quién tiene el turno. | | `AlmacenCaf` | `guardarCaf/reservarFolio/liberarFolio/consultarCaf` | Los folios. `reservarFolio` debe ser atómico entre réplicas. | | `Registro` | `{ emitir(evento: EventoRegistro): void }` | Eventos y errores (por ejemplo `emitir.envio_incierto`, `folios.bajos`). No promete entrega. | ### 6. Una sola sesión por RUT El SII cuenta las sesiones autenticadas por RUT y bloquea al superar el cupo (`01.01.215.500.720`, código `LIMITE_SESIONES`, acción `esperar`, al menos 35 minutos). Cerrar sesión no libera el cupo. Por eso las llamadas al portal comparten una sesión mediante `AlmacenSesion` y `Bloqueos`. Nunca reintentes un `LIMITE_SESIONES`: cada intento abre otra sesión. ## Tres niveles de uso | Nivel | Paquetes | Qué es | |---|---|---| | Piezas | `comun`, `dominio`, `xml`, `protocolo`, `impresion` | Funciones puras: sin red, sin disco, sin reloj del sistema. Validar un RUT, calcular totales, armar y firmar un DTE, leer una respuesta del SII, dibujar el PDF417. | | Llamadas | `sii` | Una operación con el SII por el puerto `Http`. Hace una cosa y termina: `emitir`, `enviarBoletas`, `enviarDte`, `consultarEstadoEnvio`, `resumenRcv`. | | Soluciones | `soluciones`, `folios`, `certificacion` | Recetas que juntan lo anterior: `habilitar`, `diagnosticarEmisor`, `autoCaf`, el motor de certificación. | Dependencias en un solo sentido: `comun` <- `dominio` <- `xml` <- `protocolo` <- `sii` <- `soluciones` <- `folios` <- `certificacion`. `impresion` usa solo `dominio` y `xml`. `emitir` (de `@devlas-cl/sii`) compone validar, armar, timbrar, firmar, validar el XSD y enviar en una llamada. Cada paso también se usa suelto (`prepararDte`, `prepararSobre`, `enviarBoletas`, `enviarDte`) cuando necesitas intervenir entre pasos o agrupar boletas. ## Requisitos - Node.js 24 o superior. Solo ESM. - `comun`, `dominio`, `impresion` y el catálogo de errores corren también en el navegador, Electron y Capacitor. La firma, los certificados y la red al SII son solo de servidor. - Un certificado digital (`.pfx` y su clave) del contribuyente o de un usuario autorizado, y una empresa certificada ante el SII para emitir en producción. ## Páginas de esta documentación - `docs/instalacion`: registro privado, `.npmrc`, token y paquetes por nivel. - `docs/primeros-pasos`: emitir una boleta 39 y consultar su estado. - `docs/autocaf`: CAF, `AlmacenCaf`, pedir, reobtener, anular y `autoCaf`. - `docs/estado`: `consultarEstadoEnvio`, códigos del SII y `siguienteConsultaDeEstado`. - `docs/certificacion`: motor `avanzarCorrida`, etapas, esperas y habilitación. - `docs/tiers`: niveles, paquetes y licencia. - `docs/referencia-api`: firmas de las funciones públicas y catálogo de `ErrorSii`. - `docs/errores`: errores comunes y cómo resolverlos. --- # Niveles y paquetes > Niveles de SII SDK (Libre, Facturas, AutoCAF, Certificador), precios anuales en UF más IVA, paquetes de cada nivel, licencia, soporte y qué pasa al no renovar. Fuente: https://sii.devlas.cl/docs/tiers # Niveles, paquetes y licencia SII SDK tiene cuatro niveles acumulativos: cada uno incluye todo lo del anterior y el precio publicado es el total, no un adicional. Precios anuales en UF, más IVA, al valor de la UF del día de pago; no cambian según la cantidad de clientes finales que atiendas con tu sistema. Esta página dice qué entrega cada nivel, qué paquetes trae y cómo funciona la licencia. ## Niveles | Nivel | Precio | Incluye | |---|---|---| | **Libre** | Gratis, con registro y contrato | Boleta electrónica afecta (39) y exenta (41). Validación, cálculo de montos, timbre, firma, envío y consulta de estado. Mensajes de error en español con el texto literal del SII aparte. Ticket térmico y PDF con timbre PDF417. | | **Facturas** | 16 UF al año, más IVA | Todo Libre, más: factura afecta (33) y exenta (34), notas de crédito (61) y de débito (56), guía de despacho (52), libros electrónicos de ventas y compras, intercambio entre contribuyentes y Registro de Compras y Ventas (RCV). | | **AutoCAF** | 60 UF al año, más IVA | Todo Facturas, más: pedir CAF por el portal del SII sin pasos manuales, reposición de folios antes de que se agoten con `autoCaf`, y reobtener un CAF ya autorizado ante un bloqueo de timbraje antes de anular. | | **Certificador** | 100 UF al año, más IVA | Todo AutoCAF, más: motor de certificación de factura y de boleta (una etapa por llamada), armado de sets de prueba, libros y muestras impresas, y verificación de habilitación en producción. | Quien pasa de AutoCAF a Certificador paga la diferencia: 40 UF más IVA. ## Paquetes de cada nivel | Paquete | Libre | Facturas | AutoCAF | Certificador | Contenido | |---|---|---|---|---|---| | `comun` | Sí | Sí | Sí | Sí | `Resultado`, `ErrorSii`, puertos (`Http`, `AlmacenSesion`, `Bloqueos`, `AlmacenCaf`, `Registro`) y `mensajeParaUsuario` | | `dominio` | Sí | Sí | Sí | Sí | Modelo canónico del documento, RUT, fecha tributaria en hora de Chile y cálculo de montos | | `xml` | Sí | Sí | Sí | Sí | Armar, timbrar, firmar y validar contra el XSD: `prepararDte`, `prepararSobre`, `prevalidarEmision`, `leerCaf` | | `protocolo` | Sí | Sí | Sí | Sí | Lectores puros de respuestas del SII, catálogo de errores, veredictos y políticas de consulta y envío | | `sii` | Sí | Sí | Sí | Sí | Llamadas al SII: `emitir`, `enviarBoletas`, `enviarDte`, `consultarEstadoEnvio`, `consultarEstadoDte`, RCV y reclamos | | `soluciones` | Sí | Sí | Sí | Sí | Recetas: `habilitar`, `diagnosticarEmisor`, `resumirFoliosCaf` | | `impresion` | Sí | Sí | Sí | Sí | Ticket térmico, PDF en rollo u hoja y timbre PDF417 | | `folios` | No | No | Sí | Sí | `autoCaf`, `pedirCaf`, `reobtenerCaf`, `anularFolios`, `consultarTopeFolios`, `llamadasFoliosDeSii` | | `certificacion` | No | No | No | Sí | Motor de certificación (`avanzarCorrida`, `crearPasosSii`), sets, libros, muestras y habilitación | - Libre y Facturas usan los mismos paquetes: lo que cambia es lo que el contrato permite emitir (Libre: solo 39 y 41). - AutoCAF suma `folios`; Certificador suma `certificacion`. `soluciones` e `impresion` los reciben todos los niveles. - Dependencias en un solo sentido: `comun`, `dominio`, `xml`, `protocolo`, `sii`, `soluciones`, `folios`, `certificacion`. `impresion` usa solo `dominio` y `xml`. - Todos los paquetes llevan el mismo número de versión. Instalación: `docs/instalacion`. ## Licencia - **Registro con contrato, también en Libre.** Se verifica tu RUT y tu correo, y recibes un token personal atado a tu RUT para instalar los paquetes de tu nivel. Hoy el alta se hace a mano y el contrato está en revisión; la forma de empezar es crear una cuenta en el sitio. - **Anual.** Cada periodo dura 12 meses. Devlas avisa 30 días antes del vencimiento. Se renueva cuando quieras al precio anual vigente, sin pagar el tiempo no renovado. - **Sin cobro por cliente final.** La licencia cubre tu operación, incluida la prestación de servicios a tus propios clientes: por ejemplo un POS o un SaaS multitenant que emite para cada comercio. Pagas el precio de tu nivel, sin cobro por cliente. - **Redistribuir está prohibido.** No se permite redistribuir, sublicenciar ni entregar el código del SDK a terceros, ni incluirlo en un producto que instalan tus clientes. Se entrega compilado, sin código fuente. No hay contrato ni precio de redistribución. - **Emitir no depende de la licencia.** La librería no consulta servidores de Devlas al ejecutarse. Si no renuevas, sigues emitiendo con la última versión que recibiste mientras tu licencia estuvo vigente (capacidades de Libre y Facturas), sin corte y con el mismo alcance. Pierdes actualizaciones, soporte y acceso al registro con tu token. Sin actualizaciones la librería puede dejar de funcionar bien cuando el SII cambia sus reglas. - **Integración a tu sistema:** servicio aparte, se cotiza según el caso. ## Soporte | Nivel | Soporte | |---|---| | Libre | Por correo | | Facturas, AutoCAF, Certificador | Directo por WhatsApp y correo, durante el año de licencia (incluye actualizaciones) | Devlas adapta la librería a los cambios del SII dentro de lo razonable, sin plazo garantizado, y no puede garantizar que el SII acepte cada documento ni que sus servicios estén disponibles. ## Próxima versión: llave de licencia en AutoCAF y Certificador Lo siguiente está decidido y desarrollado, pero **aún no está publicado**: la versión 0.6.0 no verifica ninguna licencia. - `@devlas-cl/folios` (AutoCAF) y `@devlas-cl/certificacion` (Certificador) funcionarán solo con una licencia vigente firmada por Devlas. Libre y Facturas (`comun`, `dominio`, `xml`, `protocolo`, `sii`, `soluciones`, `impresion`, `contabilidad`) no llevan licencia. - La licencia es una línea de texto que empieza con `SIISDK1.`. Se entrega en la variable de entorno `SII_SDK_LICENCIA` o en el campo `licencia` de la configuración (`ConfigPortalLicenciada` y contexto de `autoCaf`); la configuración manda sobre la variable. - Se verifica sin red, al **usar** cada operación con el SII, no al importar. Está a nombre del licenciatario, no del RUT operado: un integrador opera muchas empresas con una sola licencia. - Desde 30 días antes del vencimiento deja el evento `licencia.por_vencer`. Tras vencer hay 14 días de gracia con el aviso `licencia.en_gracia` en cada uso; desde el día 15 la operación responde `LICENCIA_VENCIDA`. - Errores nuevos (todos `accion: 'corregir'`, `mensajeSii: null`, no reintentables): `LICENCIA_AUSENTE`, `LICENCIA_INVALIDA`, `LICENCIA_VENCIDA`, `LICENCIA_TIER_INSUFICIENTE`. `esErrorDeLicencia(error)` los reconoce y `verificarLicencia(fuente, 'autocaf' | 'certificador')` comprueba la licencia al arrancar. - Los CAF ya obtenidos, los documentos emitidos y las certificaciones logradas no se ven afectados; se puede seguir pidiendo folios por los medios del SII. Al renovar se recibe una llave nueva. - Para migrar existe un modo de transición explícito (`SII_SDK_LICENCIA_TRANSICION=1` o `licenciaTransicion: true`) que solo tolera la **ausencia** de licencia, con aviso, y termina el 2027-01-31. --- # Instalación > Instalar los paquetes @devlas-cl de SII SDK desde GitHub Packages con un token clásico, un .npmrc y la etiqueta de versión del licenciatario. Fuente: https://sii.devlas.cl/docs/instalacion # Instalación Los paquetes `@devlas-cl/*` son privados y se distribuyen por el registro npm de GitHub Packages (`https://npm.pkg.github.com`), no por npmjs.com. Cada licenciatario recibe su propia copia con un sufijo de versión y una etiqueta propia, y el acceso queda ligado a su nivel (también el nivel Libre, gratis con registro). Requiere Node.js 24 o superior; los paquetes son solo ESM. ## Requisitos previos - Devlas aprueba tu registro y da acceso de lectura a los paquetes de tu nivel. Sin ese acceso la instalación responde 403 o 404. - Al aprobarlo, Devlas entrega la **etiqueta** de versión (por ejemplo `c7f3a1b2c`) que usarás en cada instalación. - Node.js 24 o superior (`engines.node >=24`). ## Pasos ### 1. Token personal clásico GitHub Packages no acepta tokens fine-grained. Crea un token clásico (GitHub: Settings, Developer settings, Personal access tokens, Tokens (classic)) con el permiso `read:packages` y nada más, con vencimiento (recomendado: 90 días). Si la cuenta pertenece a una organización con SSO, autoriza el token para esa organización. ### 2. Token fuera del repositorio ```sh export NODE_AUTH_TOKEN='ghp_...' # nunca en un archivo versionado ``` ### 3. `.npmrc` en la raíz del proyecto Se versiona, porque no contiene el token (lo lee de la variable): ```ini @devlas-cl:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN} ``` ### 4. Instalar con tu etiqueta Usa la misma etiqueta en todos los paquetes `@devlas-cl`. Núcleo para emitir boletas, facturas y notas: ```sh pnpm add @devlas-cl/comun@ @devlas-cl/dominio@ @devlas-cl/xml@ @devlas-cl/protocolo@ @devlas-cl/sii@ ``` ## Paquetes según el nivel Los niveles son acumulativos. Paquetes publicados y a qué nivel pertenecen: | Paquete | Nivel que lo entrega | Contenido | |---|---|---| | `@devlas-cl/comun` | Todos | `Resultado`, `ErrorSii`, catálogo de códigos, puertos, `mensajeParaUsuario` | | `@devlas-cl/dominio` | Todos | `Documento`, RUT, `fechaTributaria`, cálculo de montos, vigencia del CAF | | `@devlas-cl/xml` | Todos | `prepararDte`, `prepararSobre`, `prevalidarEmision`, `leerCaf`, `abrirCertificado`, XSD | | `@devlas-cl/protocolo` | Todos | Lectores de respuestas del SII, catálogo, veredictos, políticas de consulta y envío | | `@devlas-cl/sii` | Todos | Llamadas al SII: `emitir`, `enviarBoletas`, `enviarDte`, `consultarEstadoEnvio`, RCV, reclamos | | `@devlas-cl/soluciones` | Todos | `habilitar`, `diagnosticarEmisor`, `resumirFoliosCaf` | | `@devlas-cl/impresion` | Todos | Ticket térmico, PDF y timbre PDF417 | | `@devlas-cl/folios` | AutoCAF y Certificador | `autoCaf`, `pedirCaf`, `reobtenerCaf`, `anularFolios`, `consultarTopeFolios`, `llamadasFoliosDeSii` | | `@devlas-cl/certificacion` | Certificador | `avanzarCorrida`, `crearPasosSii`, sets, libros, muestras | `@devlas-cl/contabilidad` (IVA, PPM, reajuste, interés penal, multas) también se publica, sin nivel declarado. `@devlas-cl/pruebas` (falsos y pruebas de contrato) no se publica. Opcionales en cualquier nivel (recetas e impresión): ```sh pnpm add @devlas-cl/soluciones@ @devlas-cl/impresion@ ``` AutoCAF suma `folios` (depende de `soluciones`): ```sh pnpm add @devlas-cl/soluciones@ @devlas-cl/folios@ ``` Certificador suma `certificacion` (depende de `folios`, `soluciones` e `impresion`): ```sh pnpm add @devlas-cl/soluciones@ @devlas-cl/impresion@ @devlas-cl/folios@ @devlas-cl/certificacion@ ``` ## Versiones y actualizaciones - Tu versión es la del lanzamiento con un sufijo tuyo (por ejemplo `0.6.0-c7f3a1b2c`) y la etiqueta es ese sufijo. - Para actualizar, vuelve a instalar con la misma etiqueta: Devlas la mueve a cada versión nueva. - Un rango como `^0.6.0` no sirve: las versiones con sufijo no entran en rangos de otra versión. - Todos los paquetes llevan el mismo número de versión. No hay matriz de compatibilidad: instala todos con la misma etiqueta. - Las dependencias internas entre paquetes quedan fijadas a la versión exacta de tu copia. ## Integración continua Guarda el token clásico como secreto del pipeline (por ejemplo `DEVLAS_PACKAGES_TOKEN`) y pásalo como `NODE_AUTH_TOKEN` al paso de instalación. El `GITHUB_TOKEN` de otro repositorio no sirve para leer estos paquetes. Usa un token propio del pipeline. ```yaml - uses: actions/setup-node@v4 with: registry-url: https://npm.pkg.github.com scope: '@devlas-cl' - run: pnpm install --frozen-lockfile env: NODE_AUTH_TOKEN: ${{ secrets.DEVLAS_PACKAGES_TOKEN }} ``` ## Rotación y vencimiento del token - Antes de que venza, crea uno nuevo, actualiza la variable o el secreto y borra el anterior. - Token vencido o revocado: las instalaciones nuevas fallan con 401 o 403. Lo ya instalado en `node_modules` sigue funcionando; solo dejas de poder instalar o actualizar. - Token filtrado: revócalo de inmediato en GitHub y crea otro. ## Solo ESM Los paquetes no se cargan con `require()`. Desde CommonJS usa `import()` dinámico dentro de una función `async`. En TypeScript con CommonJS necesitas `moduleResolution` `node16` o `nodenext`. ```ts // Desde CommonJS, dentro de una función async: const { abrirCertificado } = await import('@devlas-cl/xml') ``` ## Contenido de cada paquete publicado Cada tarball trae solo `dist` (JavaScript compilado con tipos), `package.json`, `README.md`, `COMMERCIAL.md` y `THIRD-PARTY-NOTICES.md`. Sin código fuente, pruebas ni mapas. --- # Primeros pasos: emitir una boleta > Configurar ConfigToken, cargar el CAF, armar un Documento tipo 39 con todos sus campos obligatorios, emitir con emitir, leer el resultado y consultar el estado. Fuente: https://sii.devlas.cl/docs/primeros-pasos # Primeros pasos: emitir una boleta electrónica (tipo 39) Objetivo: generar una boleta 39, enviarla al SII y saber si la aceptó. `emitir` de `@devlas-cl/sii` hace todo el camino en una llamada: reserva el folio en `AlmacenCaf`, valida, calcula montos, arma el XML, lo timbra, lo firma, lo valida contra el XSD, arma y firma el sobre y lo sube (boletas 39 y 41 por la API REST; el resto por `DTEUpload`). Nivel: Libre. Una boleta 39 emitida con `emitir` fue aceptada por el SII en el ambiente de certificación, con `TSTED`, `TmstFirma` y `TmstFirmaEnv` en hora de Chile. ## Qué necesitas | Qué | Cómo se obtiene | Dónde se usa | |---|---|---| | Certificado digital (`.pfx` y su clave) del titular autorizado | Lo tramita el contribuyente. `abrirCertificado(pfx, clave)` lo lee; la clave nunca se registra. | Firmar el DTE, el sobre y la semilla | | Folios (un CAF) del tipo 39 | Un CAF por tipo, ambiente y RUT emisor. Lo descargas del portal del SII o lo pide `autoCaf` (nivel AutoCAF). | `AlmacenCaf` | | La resolución del ambiente (`fchResol`, `nroResol`) | De la ficha de la empresa (`consultarEmpresa` devuelve `fechaResolucion` y `numeroResolucion`). En certificación `nroResol` es 0. Nunca un valor por defecto: mezclar ambientes da `RCT` (rechazo en carátula). | `caratula` | | Los puertos `Http`, `AlmacenCaf`, `Registro` y, opcional, `AlmacenSesion` para tokens | Los implementa tu sistema | `ConfigToken` y `emitir` | ## 1. Configuración (`ConfigToken`) Las llamadas con token (envío, estado, reclamos) reciben `ConfigToken = ConfigBase & { rutTitular, firmador, almacenTokens?, guardaDeAmbiente? }`, donde `ConfigBase = { http, reloj, ambiente, registro?, esperar?, reintentos?, conservarCrudo? }`. ```ts import { readFile } from 'node:fs/promises' import type { AlmacenCaf, AlmacenSesion, Ambiente, Http, Registro } from '@devlas-cl/comun' import { type ConfigToken, crearFirmadorSemilla } from '@devlas-cl/sii' import { abrirCertificado } from '@devlas-cl/xml' // Implementaciones de los puertos en tu sistema: declare const http: Http declare const almacenCaf: AlmacenCaf // reservarFolio atómico entre réplicas declare const almacenTokens: AlmacenSesion // opcional: reutiliza el token entre llamadas y réplicas declare const registro: Registro // opcional: eventos y errores const ambiente: Ambiente = 'certificacion' // El .pfx del titular y su clave. La librería nunca registra la clave. const cert = abrirCertificado(await readFile('certificado.pfx'), process.env.CLAVE_PFX ?? '') if (!cert.ok) throw new Error(cert.error.codigo) const certificado = cert.valor // certificado.rutTitular: RUT de la persona titular const configToken: ConfigToken = { http, reloj: { ahora: () => new Date() }, ambiente, rutTitular: certificado.rutTitular, // quien firma y envía firmador: crearFirmadorSemilla(certificado), // firma la semilla para obtener el token almacenTokens, registro, } ``` ## 2. Cargar el CAF en el almacén Si no usas AutoCAF, guardas tú el archivo que descargaste del SII. Guarda el **texto original completo** (con la llave privada), no `caf.valor.xml` (que es solo el bloque `` y no sirve para timbrar). Lee el archivo como ISO-8859-1. ```ts import { readFile } from 'node:fs/promises' import type { AlmacenCaf, ClaveCaf } from '@devlas-cl/comun' import { leerCaf } from '@devlas-cl/xml' declare const almacenCaf: AlmacenCaf const clave: ClaveCaf = { rutEmisor: '7654321-6', tipoDte: 39, ambiente: 'certificacion' } const archivoCaf = await readFile('caf-39.xml', 'latin1') const caf = leerCaf(archivoCaf) if (!caf.ok) throw new Error(caf.error.codigo) const { folioDesde, folioHasta, vencimiento } = caf.valor // vencimiento: null en 39 y 41 (no vencen) const ranura = await almacenCaf.guardarCaf(clave, { xml: archivoCaf, // texto COMPLETO: cífralo en reposo desde: folioDesde, hasta: folioHasta, ...(vencimiento === null ? {} : { venceEn: vencimiento.getTime() }), }) // 'activa' | 'siguiente' ``` Si ya emitiste folios de ese rango con otro sistema, pasa `{ ultimoFolioUsado: n }` como tercer argumento de `guardarCaf`. ## 3. El documento `Documento` de `@devlas-cl/dominio` usa el vocabulario del SII. Campos obligatorios por tipo: `tipo`, `fechaEmision` (AAAA-MM-DD), `emisor`, `base`, `lineas`. En la boleta, `indServicio` es obligatorio por validación aunque el tipo lo declare opcional. `Emisor` exige `rut`, `razonSocial`, `giro`, `direccion` y `comuna`. `Linea` exige `nombre` y `cantidad`; `precioUnitario` es el precio por unidad. ```ts import { type Documento, fechaTributaria } from '@devlas-cl/dominio' const boleta: Documento = { tipo: 39, fechaEmision: fechaTributaria(new Date()), // AAAA-MM-DD en hora de Chile, nunca toISOString() emisor: { rut: '7654321-6', razonSocial: 'Comercial de Prueba SpA', giro: 'Venta al por menor', direccion: 'Calle Uno 100', comuna: 'Santiago', }, base: 'bruto', // los precios incluyen IVA (la boleta va siempre en bruto) indServicio: 3, // 1 y 2 servicios periódicos, 3 venta y servicios, 4 espectáculos lineas: [ { nombre: 'Café de grano 250 g', cantidad: 2, precioUnitario: 4990 }, { nombre: 'Galleta de avena', cantidad: 1, precioUnitario: 1500 }, ], } ``` Reglas de la boleta que la librería aplica por ti: - Sin `receptor`, se declara al consumidor final `66666666-6`. Un RUT real es obligatorio si el total supera 135 UF, y entonces también `medioPago`. - La boleta lleva el detalle en bruto y no declara `TasaIVA`, `MntBruto` ni `DscRcgGlobal`; un descuento global se reparte en las líneas. - Largos del XSD sin recortar: un `nombre` (`NmbItem`) de más de 80 caracteres es un error `VALIDACION` con campo, límite y largo recibido. El excedente va en `descripcion` (`DscItem`, hasta 1000). - En la boleta exenta (41), cada línea lleva `exento: true`; una línea sin esa marca es el error local `LINEA_AFECTA_EN_BOLETA_EXENTA`. - Una boleta de total cero es advertencia: el SII la acepta con reparo y el folio se gasta. ## 4. Revisar antes de cobrar (opcional) `prevalidarEmision` es pura (sin red). Devuelve hallazgos con `codigo`, `nivel` (`error`, `advertencia`, `info`), `campo`, `mensaje`, `accion` y `fuente`, sin gastar folio: ```ts import type { Documento } from '@devlas-cl/dominio' import { prevalidarEmision } from '@devlas-cl/xml' declare const boleta: Documento const hallazgos = prevalidarEmision(boleta, { ahora: new Date() }) const bloqueantes = hallazgos.filter((h) => h.nivel === 'error') if (bloqueantes.length > 0) { // no cobrar todavía: mostrar h.mensaje y h.accion } ``` `ContextoPrevalidacion` acepta además `folio`, `caf` (un `CafPublico`), `valorUf` (para el umbral de 135 UF), `rutEnvia` y `rutTitular`. ## 5. Emitir Firma: `emitir(cfg: ConfigToken, entrada: EntradaEmitir): Promise>`, con `EntradaEmitir = { documento, certificado, caratula: { fchResol, nroResol, rutReceptor?, tmstFirmaEnv? }, almacenCaf, declaraImpuestoAdicional? }` y `DteEmitido = { trackId: string, envio, dte: DtePreparado, sobre: Sobre }`. ```ts import { type ErrorSii, mensajeParaUsuario } from '@devlas-cl/comun' import type { AlmacenCaf } from '@devlas-cl/comun' import type { Documento } from '@devlas-cl/dominio' import { type ConfigToken, emitir } from '@devlas-cl/sii' import type { Certificado } from '@devlas-cl/xml' declare const configToken: ConfigToken declare const certificado: Certificado declare const almacenCaf: AlmacenCaf declare const boleta: Documento const sinFolios = (e: ErrorSii): boolean => e.codigo === 'VALIDACION' && (e.causa as { regla?: string } | undefined)?.regla === 'sin_folios' const r = await emitir(configToken, { documento: boleta, // sin folio: lo entrega el almacén certificado, caratula: { fchResol: '2026-10-07', nroResol: 0 }, // resolución del ambiente (en certificación, 0) almacenCaf, }) if (!r.ok) { if (sinFolios(r.error)) { // no quedan folios vigentes: llamar a autoCaf o encolar el documento } const m = mensajeParaUsuario(r.error) // { titulo, queHacer, detalle, nivel, reintentable } console.error(m.titulo, m.queHacer, m.detalle) // detalle: texto literal del SII o null } else { const { trackId, dte } = r.valor // dte.folio, dte.xml (DTE firmado), dte.ted (para el PDF417), dte.plan.Totales.MntTotal, dte.advertencias // Tu sistema guarda dte.xml (6 años) y trackId COMO TEXTO (hasta 15 dígitos en boletas). } ``` Qué pasa con el folio: - Si algo falla **antes** de que el sobre pueda llegar al SII (documento inválido, CAF, XSD, token, red sin que nada llegara), el folio vuelve al almacén. - Si el envío **pudo** llegar (tiempo agotado, corte, 5xx o rechazo del SII), el folio queda consumido, el error se devuelve tal cual y **no se reenvía**. Consulta el estado antes de decidir. Ese caso deja el evento `emitir.envio_incierto` en el `Registro`, con tipo y folio. - Sin folios devuelve `VALIDACION` con `causa.regla === 'sin_folios'`. ## 6. Consultar el estado El track id solo dice que el sobre **subió**. La boleta puede quedar rechazada después: consulta siempre. Una boleta se consulta por el canal `rest`; facturas, notas, guías y libros por `soap`. ```ts import { siguienteConsultaDeEstado, veredictoFinalDeEnvio } from '@devlas-cl/protocolo' import { type ConfigToken, consultarEstadoEnvio } from '@devlas-cl/sii' declare const configToken: ConfigToken declare const trackId: string declare const enviadoEn: Date declare const consultasHechas: number const estado = await consultarEstadoEnvio(configToken, { canal: 'rest', trackId, rutEmisor: '7654321-6' }) if (estado.ok) { // estado.valor: { canal, trackId, codigo, glosa, veredicto, porTipo, numeroAtencion, fechaRecepcion, detalleReparosRechazos } const final = veredictoFinalDeEnvio(estado.valor) // final.veredicto: 'en_proceso' | 'aceptado' | 'aceptado_con_reparos' | 'parcial' | 'rechazado' | 'desconocido' } const siguiente = siguienteConsultaDeEstado( { enviadoEn, consultasHechas, ultimoCodigo: estado.ok ? estado.valor.codigo : null }, new Date(), ) // { accion: 'consultar', en, esperaMs } | { accion: 'cerrar', veredicto } | { accion: 'dejar_de_consultar' } ``` - `EPR` (veredicto `procesado`) es del sobre, no del documento: usa `veredictoFinalDeEnvio` para sumar los conteos de `porTipo`. - Aceptado con reparos vale como aceptado. Rechazado nunca fue válido: corrige la causa y emite con folio nuevo. - Tiempos medidos en certificación: la boleta tuvo veredicto en menos de 2 minutos. Consulta desde los 60 segundos. Detalle de códigos, canales y reenvío tras un corte: `docs/estado`. ## Lo que pones tú `http`, `almacenCaf`, `almacenTokens` y `registro` son tus implementaciones de los puertos. El contrato de `Http` es una función `solicitar(solicitud)` que no sigue redirecciones, no guarda cookies ni reintenta, envía las cabeceras en el orden dado y entrega el cuerpo en bytes tal como llegó. `AlmacenCaf` debe reservar folios de forma atómica entre réplicas (por ejemplo con Postgres). > Pendiente en esta documentación: la guía completa de los puertos (con adaptadores de referencia para `node:https`, Redis y Postgres y sus pruebas de contrato) existe en la documentación del producto y aún no está publicada aquí. Pídela al crear tu cuenta. ## La otra puerta: piezas sueltas Para intervenir entre pasos o agrupar hasta 500 boletas en un sobre: `almacenCaf.reservarFolio(clave)`, `leerCaf`, `prepararDte` y `prepararSobre` de `@devlas-cl/xml`, y `enviarBoletas` de `@devlas-cl/sii`. Regla de oro: un folio se libera (`almacenCaf.liberarFolio(clave, folio, reserva)`) solo si algo falló **antes** de enviar. ```ts import type { AlmacenCaf, ClaveCaf } from '@devlas-cl/comun' import type { Documento } from '@devlas-cl/dominio' import { type ConfigToken, enviarBoletas } from '@devlas-cl/sii' import { type Certificado, leerCaf, prepararDte, prepararSobre } from '@devlas-cl/xml' declare const configToken: ConfigToken declare const almacenCaf: AlmacenCaf declare const certificado: Certificado declare const boleta: Documento declare const clave: ClaveCaf const ahora = configToken.reloj.ahora() const reservado = await almacenCaf.reservarFolio(clave) if (reservado === null) throw new Error('sin folios vigentes') const caf = leerCaf(reservado.caf.xml) const dte = caf.ok ? prepararDte({ documento: boleta, folio: reservado.folio, caf: caf.valor, certificado, ahora }) : caf if (!dte.ok) { await almacenCaf.liberarFolio(clave, reservado.folio, reservado.reserva) // el SII no vio este folio } else { const sobre = prepararSobre({ documentos: [dte.valor], caratula: { fchResol: '2026-10-07', nroResol: 0 }, certificado, ahora }) if (!sobre.ok) { await almacenCaf.liberarFolio(clave, reservado.folio, reservado.reserva) } else { // Desde aquí el folio queda consumido aunque el envío falle. const envio = await enviarBoletas(configToken, { sobre: sobre.valor.xml, rutEmisor: boleta.emisor.rut, rutEnvia: configToken.rutTitular, }) } } ``` `prepararSobre` elige `EnvioBOLETA` si el primer documento es boleta y `EnvioDTE` si no; mezclar familias o emisores es un error antes de firmar. Cuándo cerrar un grupo de boletas lo responde `siguienteAccionDeEnvio` de `@devlas-cl/protocolo` (por omisión espera 5 minutos desde la más antigua; la norma exige que cada boleta llegue al SII en máximo una hora). --- # Usar con agentes y MCP > Conecta Claude Code, Cursor u otro agente al servidor MCP de documentación de SII SDK: herramientas disponibles, llaves de API y ejemplo de configuración. Fuente: https://sii.devlas.cl/docs/usar-con-agentes # Usar con agentes y MCP El portal expone la documentación a agentes de código mediante un servidor MCP de solo lectura. Un agente puede listar páginas, leerlas, buscar texto y consultar símbolos de la API sin que copies nada a mano. ## Conectar Claude Code Sin llave el agente solo ve las páginas públicas: ```bash claude mcp add --transport http sii-docs https://sii.devlas.cl/mcp ``` Con una llave de cuenta activa ve lo que permite su nivel. La llave la crea Devlas desde el panel de administración y se muestra una sola vez: ```bash claude mcp add --transport http sii-docs https://sii.devlas.cl/mcp \ --header "Authorization: Bearer TU_LLAVE" ``` ## Herramientas - `listar_paginas`: lista las páginas que tu acceso permite ver. - `leer_pagina`: devuelve una página en Markdown a partir de su slug. - `buscar`: búsqueda de texto completo en español; devuelve fragmentos con su slug. - `reglas_para_agentes`: el contenido de agents.md. - `simbolo_api`: busca una función o tipo exportado en la referencia de la API (requiere cuenta activa). ## Qué se registra Cada llamada deja un evento con la herramienta, los argumentos y un resumen del resultado, asociado a tu llave. Hay un límite de 60 llamadas por minuto por llave. ## Alternativas sin MCP - Índice para modelos: https://sii.devlas.cl/llms.txt - Todo en un archivo: https://sii.devlas.cl/llms-full.txt - Cada página en Markdown: agrega `.md` a su dirección. --- # Reglas para agentes de código > Reglas que un agente de código debe seguir al integrar SII SDK. Se puede copiar al AGENTS.md o CLAUDE.md del proyecto. Fuente: https://sii.devlas.cl/docs/agents # SII SDK (Devlas): reglas para agentes de código Este proyecto usa SII SDK, la librería TypeScript de Devlas para emitir documentos tributarios electrónicos ante el SII de Chile. Antes de escribir código que la use, lee la documentación en Markdown. ## Dónde leer - Toda la documentación en un archivo: https://sii.devlas.cl/llms-full.txt - Índice: https://sii.devlas.cl/llms.txt - Servidor MCP de documentación: https://sii.devlas.cl/mcp {{PAGINAS}} ## Reglas 1. Node.js 24 o superior, solo ESM. Los paquetes son @devlas-cl/* desde GitHub Packages; usa el .npmrc del proyecto y el token en la variable NODE_AUTH_TOKEN, nunca en el repositorio. 2. Instala todos los paquetes @devlas-cl con la misma etiqueta de versión. 3. Las operaciones devuelven Resultado y no lanzan por datos del usuario ni del SII. Revisa siempre `ok` antes de usar `valor`. 4. El texto del SII llega literal en el error. No lo reinterpretes ni lo recortes; muéstralo o guárdalo tal cual. 5. La librería no guarda datos ni agenda tareas. Tu sistema decide dónde viven los CAF, el XML firmado y el estado de cada envío, y cuándo volver a consultar. 6. Folios: ante un bloqueo, reobtener lo ya autorizado antes de pedir más; anular folios es irreversible y es el último recurso. 7. No inventes funciones ni campos: usa solo lo que aparece en la referencia de la API. 8. No redistribuyas la librería ni su código a terceros. ## Contacto WhatsApp +56 9 7226 9035, correo ti@devlas.cl.