Saltar al contenido
dte-sdk

Conceptos básicos

Una introducción práctica al sistema de facturación electrónica de Chile: el vocabulario que encuentras en la documentación del SII y en la API de dte-sdk. No necesitas memorizarlo, pero saber qué es un CAF o un TED hace más fáciles de leer todos los códigos de error.

¿Qué es un DTE?#

Un DTE (Documento Tributario Electrónico) es un documento tributario (una factura, una boleta, una guía de despacho o una nota) emitido como un archivo XML firmado e informado al servicio de impuestos de Chile, el SII (Servicio de Impuestos Internos). El XML es el documento legal. Una copia impresa o en PDF es solo una representación de él y lleva un código de barras para que cualquiera pueda contrastarla con el original.

Un DTE se identifica por tres valores: el RUT del emisor, el tipo de documento y el folio. La combinación es única: el SII no acepta dos documentos que compartan los tres.

Tipos de documento#

El tipo es un código numérico (TipoDTE). dte-sdk soporta nueve. La clave en inglés es la de la constante DOCUMENT_TYPES, por ejemplo DOCUMENT_TYPES.invoice === 33.

CódigoNombre en el SIIClave en DOCUMENT_TYPESReceptor obligatorioCopia cedible
33Factura ElectrónicainvoiceSíSí
34Factura No Afecta o Exenta ElectrónicaexemptInvoiceSíSí
39Boleta ElectrónicareceiptNoNo
41Boleta Exenta ElectrónicaexemptReceiptNoNo
43Liquidación Factura ElectrónicasettlementInvoiceSíSí
46Factura de Compra ElectrónicapurchaseInvoiceSíSí
52Guía de Despacho ElectrónicadispatchGuideSíSí
56Nota de Débito ElectrónicadebitNoteSíNo
61Nota de Crédito ElectrónicacreditNoteSíNo

Qué significan las columnas en la práctica:

  • Receptor obligatorio. Todo salvo las boletas necesita un receptor identificado (requiresReceiver(type)). Una boleta sin receptor va al consumidor final, RUT 66666666-6.
  • Copia cedible. La copia impresa de 33, 34, 43, 46 y 52 lleva la marca CEDIBLE y el recuadro de acuse de recibo de la Ley 19.983 (isTransferable(type)).
  • Las boletas (39, 41) no llevan tasa de IVA, usan receiptReferences propias en vez de references y viajan en un sobre EnvioBOLETA (isReceipt(type)).
  • Los tipos exentos (34, 41) hacen exentas todas las líneas (isExemptType(type)). Cualquier otro tipo puede marcar líneas sueltas como exentas.
  • Las notas (56, 61) deben referenciar el documento que corrigen (isNote(type)).
  • Las liquidaciones factura (43) necesitan un settledDocumentType en cada línea.
  • Las facturas de compra (46) las emite el comprador y retienen el IVA por defecto.

documentTypeName(type) entrega el nombre oficial en español, y las variantes 'short' y 'printed' para las formas que usa el manual de muestras impresas del SII.

Folios y CAF#

El folio es el número del documento dentro de su tipo. No eliges los folios libremente: el SII los autoriza por rangos, por tipo de documento, mediante un CAF (Código de Autorización de Folios). Un CAF es un archivo XML que contiene:

  • el RUT del emisor y el tipo de documento que cubre,
  • el rango autorizado, por ejemplo los folios 1 a 100,
  • la fecha de autorización y la firma del propio SII sobre todo lo anterior,
  • un par de llaves RSA. La llave privada es la que timbra cada documento.

parseCaf() lee el archivo y devuelve un Caf: issuerRut, issuerName, documentType, range, authorizationDate, keyId y las llaves. availableFolios(caf) cuenta los folios que autoriza, y isFolioInRange(caf, folio) y assertFolioInRange(caf, folio) comprueban uno. Un keyId 100 marca un CAF del ambiente de certificación.

El CAF contiene una llave privada. Quien tenga el archivo puede timbrar documentos a tu nombre. Mantenlo fuera del control de versiones, de los logs y de los reportes de errores, y entrega a cada servicio solo los CAF que necesita. Revisa Seguridad.

Vencimiento. Los CAF de facturas y notas (tipos 33, 46, 56 y 61) vencen seis meses después de su fecha de autorización, a medianoche de Chile continental (UTC-4). Los demás tipos no vencen. cafExpiresAt(caf) entrega el instante (o null) e isCafExpired(caf) indica si ya venció o si vencerá dentro de un margen de un día por defecto (marginMs), porque el SII rechaza todo documento timbrado con un CAF vencido y un sobre puede llegar al SII después de haber sido timbrado. stampDocument() y issueDocument() lo comprueban por ti.

Los folios son racionados y no se reutilizan. Cada folio puede ir en un solo documento. El SII puede limitar cuántos folios puede tener una empresa por tipo, y los folios sin usar cuentan contra ese límite. La entrada dte-sdk/portal consulta el cupo, solicita CAF, anula y recupera folios, y mantiene un registro local de qué folios de cada CAF están reservados, enviados o anulados (Portal del SII y folios).

El TED#

El TED (Timbre Electrónico) es el timbre que prueba que el SII autorizó el folio. Contiene un elemento DD con los datos clave del documento (RUT del emisor, tipo, folio, fecha de emisión, RUT y nombre del receptor, monto total, el nombre del primer ítem, el CAF tal como lo emitió el SII y la hora del timbre), seguido de una firma de esa cadena exacta hecha con la llave privada del CAF.

stampDocument() arma la cadena DD y la firma (SHA1withRSA sobre los bytes ISO-8859-1). verifyTed() verifica un TED contra la llave pública del CAF. El nombre del receptor y el del primer ítem entran al TED convertidos a ASCII y cortados a 40 caracteres, como exige el esquema.

En la copia impresa, el mismo TED es el código de barras PDF417 que cualquiera puede escanear para contrastar el documento. La entrada dte-sdk/pdf lo dibuja a partir de los bytes firmados (Muestras impresas).

Certificados y XMLDSig#

Tu certificado digital es la identidad de la persona que firma por la empresa, y su llave privada produce la firma electrónica XML (XMLDSig, envuelta, XML canónico 1.0, RSA-SHA1) que valida el SII. Se firman dos cosas: cada <DTE> (signDocument()) y el SetDTE de cada sobre (signEnvelope()).

Un Certificate son datos planos. Lleva las llaves, la cadena en PEM, el nombre común, las fechas de vigencia y el RUT del titular, que el SDK lee del propio certificado (certificate.rut, o null si no lo tiene). Cárgalo desde un archivo PKCS#12 (loadCertificateFromPkcs12) o desde PEM (loadCertificateFromPem); isCertificateExpired() y daysUntilExpiry() ayudan a vigilar su vigencia.

El certificado y su clave son secretos. La clave va en tu entorno (por ejemplo process.env.PFX_PASSWORD), nunca en el código fuente.

El titular del certificado debe estar autorizado por la empresa ante el SII. Esa persona es el senderRut de la carátula del sobre (RutEnvia). Una firma válida prueba que el documento está íntegro y firmado con el certificado incrustado; no prueba que ese certificado pertenezca al firmante esperado. Eso es otra comprobación.

Sobres#

El SII no recibe un documento suelto: recibe un sobre con uno o más documentos firmados y una carátula.

Sobrekind en createEnvelope()Contiene
EnvioDTE'dte'Facturas, guías, notas y los demás tipos que no son boletas
EnvioBOLETA'receipt'Boletas (39, 41)

El SetDTE del sobre tiene un id (generado, o el tuyo con setId), una carátula (Carátula) y los documentos. La carátula dice quién es el emisor (issuerRut), quién envía (senderRut), quién recibe (receiverRut, el SII por defecto y siempre el SII en las boletas), el número y la fecha de la resolución bajo la cual la empresa emite (resolutionNumber es 0 en certificación), una marca de tiempo de firma y el conteo de documentos por tipo (SubTotDTE, calculado por ti). signEnvelope() firma el SetDTE y encodeLatin1(envelope.xml) entrega los bytes para subir.

Ambientes#

El SII opera dos ambientes de sus servicios web, y cada cliente se crea para uno de ellos.

environmentNombre de hostAPI REST de boletasUso
'certification'maullin.sii.clapicert.sii.cl y pangal.sii.clPruebas y proceso de certificación
'production'palena.sii.clapi.sii.cl y rahue.sii.clDocumentos reales con efecto legal

Los folios, los CAF y la resolución son propios de cada ambiente: un CAF de certificación lleva keyId 100 y los CAF de producción se obtienen por separado. El portal tiene sus propias páginas para cada ambiente, y createPortalClient() recibe la misma opción environment.

Los servicios web y el portal del SII#

El SDK habla con el SII por dos tipos de superficie distintos, y la diferencia importa:

  • Los servicios web son las interfaces de máquina del SII: endpoints SOAP y CGI (semilla, token, DTEUpload, consultas de estado y el registro de aceptación y reclamos) y la API REST de boletas. Te autenticas firmando una semilla con tu certificado para obtener un token. El SDK se encarga de eso. El cliente está en la entrada raíz, dte-sdk, y corre sobre fetch (Servicios web del SII).
  • El portal es el sitio web del SII, tras un ingreso con tu certificado. Es HTML, sin API: el SDK ingresa, lee las páginas y envía formularios como lo haría un navegador. Contiene lo que los servicios web no tienen: los datos de la empresa, el cupo de folios y la solicitud de CAF, el registro de compras y ventas, el respaldo MIPYME y el proceso de certificación. Vive en dte-sdk/portal, necesita Node (fetch no puede presentar un certificado de cliente) y depende de la forma de unas páginas que el SII puede cambiar (Portal del SII y folios).

El certificado debe estar habilitado antes como método de ingreso en sii.cl, un paso manual; si no, el ingreso al portal lanza SII_PORTAL_CERT_NOT_ENABLED.

Track id y estados de envío#

Cuando el SII recibe un envío responde con un track id (TrackID), solo dígitos. Luego valida el archivo de forma asíncrona: esquema, firma, carátula y cada documento. Consultas su estado con el track id, una vez (queryUploadStatus()) o sondeando (waitForUploadStatus()).

El estado trae un code del SII y el SDK lo clasifica:

statusSignificadoEjemplos de códigos del SII
pendingRecibido, aún en validaciónREC, SOK, FOK, PRD, CRT
acceptedProcesadoEPR, LOK (libro)
acceptedWithObjectionsAceptado con observaciones (reparos)RPR
rejectedRechazadoRCH, RCO, RSC, LNC, LRH
queryErrorFalló el servicio de consulta; reintenta más tarde-1 a -14
unknownUn código que el SDK no conoce

RLV ("rechazado, error en documento") es un rechazo en el canal SOAP y un éxito en el canal REST de boletas. Las respuestas de negocio son datos, no excepciones: un sobre rechazado devuelve un estado con status: 'rejected', mientras que un problema del intercambio mismo (un envío rechazado, un timeout) lanza un DteError. Las funciones classifyUploadStatus() y uploadStatusMessage() exponen el mismo catálogo.

Libros y RCV#

Además de documentos, el SII pide libros, archivos XML que armas, firmas y subes con uploadBook() o uploadFolioConsumption():

LibroFunciónQué es
Libro de compras y ventas (IECV, LibroCompraVenta)createPurchaseSalesBook()Los documentos de un período, por operación
Libro de guías de despacho (LibroGuia)createGuideBook()Las guías de un período
Reporte de consumo de folios (RCOF, ConsumoFolios)createFolioConsumption()Los folios usados por día (hoy Resumen de Ventas Diarias)

El SDK calcula los resúmenes de cada libro a partir de sus líneas. Hoy su uso es acotado. El RCV (Registro de Compras y Ventas), el registro que el SII arma con los documentos que recibe, reemplazó al libro de compras y ventas en producción desde el período 2017-08. El libro de guías se envía solo cuando el SII lo pide y el reporte de consumo de folios se usa en la certificación de boletas. Los libros siguen siendo parte del set de pruebas de certificación. El RCV mismo se lee por el portal (listPeriodDocuments(), fetchRegisterSummary()), junto con el respaldo MIPYME que descarga el XML de los documentos emitidos y recibidos (Libros, Registro de compras y ventas).

Certificación#

Antes de que una empresa pueda emitir documentos electrónicos, el SII le pide demostrar que puede hacerlo: es el proceso de certificación (postulación y certificación), que se hace en el ambiente de pruebas del SII. A grandes rasgos, el SII entrega a la empresa un set de pruebas de casos; la empresa emite esos documentos, los envía, arma y envía sus libros, emite una simulación, responde un set de intercambio con las respuestas de recepción, de recibos y de aprobación comercial, entrega muestras impresas de sus documentos y firma la declaración de cumplimiento, la declaración jurada del representante legal, que no se puede deshacer. Las boletas tienen una postulación propia.

dte-sdk/portal trae las piezas deterministas (leer el set de pruebas, emitirlo, los libros, las respuestas de intercambio) y un ejecutor reanudable para el lado del portal. Nada de eso se ha ejecutado todavía contra el SII real: revisa el Estado del proyecto y Certificación.

El modelo de dominio y las etiquetas del SII#

El modelo del SDK está en inglés y es legible: totalAmount, issuer.businessName, items[].unitPrice. El XML del SII usa sus propias etiquetas (MntTotal, RznSoc, PrcItem). toSiiDocument() convierte un Document a la estructura del SII con las etiquetas oficiales en el orden del esquema, y es el único lugar que conoce los nombres de las etiquetas: el XML siempre se renderiza desde su salida. fromSiiDocument() hace el camino inverso, para documentos recibidos e interoperabilidad. Los libros tienen el mismo par (toSiiPurchaseSalesBook(), toSiiGuideBook(), toSiiFolioConsumption()).

RUT#

Todo RUT lo valida y normaliza rut.ts; el SDK no reimplementa nada. toSiiRut(value) acepta entrada con puntos, con guion o compacta y devuelve la forma que exigen los esquemas del SII: sin puntos, con guion y con K mayúscula, por ejemplo 12345678-5. Un RUT inválido lanza INVALID_RUT y el valor nunca aparece en el mensaje. isSiiRut() y splitSiiRut() completan el conjunto, y SiiRut es un tipo con marca, de modo que una función que pide uno no puede recibir un string sin validar.

Texto y codificación#

El SII lee ISO-8859-1 (Latin-1), no UTF-8. El SDK sanea los campos de texto (sanitizeText()), exige los largos máximos del esquema (FIELD_LIMITS, FIELD_TOO_LONG) y encodeLatin1() lanza ENCODING_UNSUPPORTED_CHAR ante un carácter sobre U+00FF en vez de truncarlo. Los envíos suben bytes Latin-1 y exigen que el XML abra con una declaración que nombre ISO-8859-1, como lo hacen los sobres y libros que arma el SDK.

Hora y determinismo#

El SII trabaja en hora de Chile. El SDK calcula fechas y marcas de tiempo en hora de Chile con Intl, sea cual sea la zona horaria de tu proceso. Toda función que depende de "ahora" acepta un { now: Date } inyectado, y el cliente del SII recibe una función now y una sleep, de modo que las pruebas pueden ser deterministas y rápidas.

Siguientes pasos#