Saltar al contenido
dte-sdk

Inicio rápido

Esta guía emite una factura electrónica tipo 33 de punta a punta: crear el documento, timbrarlo con un CAF, firmarlo con tu certificado, enviarlo al ambiente de certificación del SII y revisar el resultado.

dte-sdk@0.0.2 es una versión placeholder que solo exporta hello(). Esta guía muestra cómo funcionará la API planificada para la 1.0; los detalles pueden cambiar antes del lanzamiento.

Vas a necesitar un CAF para el tipo 33 y un certificado digital. ¿No conoces esos términos? Lee primero Conceptos clave. ¿Aún no lo instalas? Revisa Instalación.

Pruébalo primero#

No necesitas CAF ni certificado para ver cómo se arma un documento. Edita las líneas de abajo y mira cómo se actualizan los totales y el XML:

Pruébalo — arma un documento

RUT del receptor válidotrue
totals.net$120.000
totals.exempt$0
totals.vat (19%)$22.800
totals.total$142.800

Vista previa de toXml() — sin firma ni TED

<DTE version="1.0">
  <Documento ID="F1024T33">
    <Encabezado>
      <IdDoc>
        <TipoDTE>33</TipoDTE>
        <Folio>1024</Folio>
        <FchEmis>2026-10-30</FchEmis>
      </IdDoc>
      <Emisor>
        <RUTEmisor>76123456-0</RUTEmisor>
        <RznSoc>Arrow Software SpA</RznSoc>
      </Emisor>
      <Receptor>
        <RUTRecep>77654321-7</RUTRecep>
      </Receptor>
      <Totales>
        <MntNeto>120000</MntNeto>
        <TasaIVA>19</TasaIVA>
        <IVA>22800</IVA>
        <MntTotal>142800</MntTotal>
      </Totales>
    </Encabezado>
    <Detalle>
      <NroLinDet>1</NroLinDet>
      <NmbItem>Licencia anual</NmbItem>
      <QtyItem>1</QtyItem>
      <PrcItem>120000</PrcItem>
      <MontoItem>120000</MontoItem>
    </Detalle>
  </Documento>
</DTE>

Los cinco pasos#

1

Crea el documento

createDte() valida la entrada (dígitos verificadores de los RUT, montos, campos obligatorios) y calcula los totales. Lanza un DteError en vez de devolver un documento que el SII rechazaría.

TypeScript
import { createDte } from 'dte-sdk'
 
const invoice = createDte({
  type: 33,
  folio: 1024,
  issuer: {
    rut: '76.123.456-0',
    businessName: 'Arrow Software SpA',
    activity: 'Desarrollo de software',
    acteco: 620200,
    address: 'Av. Providencia 1234',
    commune: 'Providencia',
  },
  receiver: {
    rut: '77.654.321-7',
    businessName: 'Comercial Los Andes Ltda.',
    activity: 'Comercio al por menor',
    address: 'Huérfanos 1055',
    commune: 'Santiago',
  },
  items: [{ name: 'Licencia anual', quantity: 1, unitPrice: 120000 }],
})
 
invoice.totals // { net: 120000, exempt: 0, vatRate: 19, vat: 22800, total: 142800 }
2

Tímbralo con el CAF

stampDte() genera el TED, el timbre electrónico, con la llave privada de tu CAF. El folio 1024 está dentro del rango 1001–2000 del CAF, así que el timbraje funciona; un folio fuera de él lanza FOLIO_OUT_OF_RANGE.

TypeScript
import { readFile } from 'node:fs/promises'
import { parseCaf, stampDte } from 'dte-sdk'
 
// The SII delivers CAF files encoded as ISO-8859-1.
const caf = parseCaf(await readFile('./caf/33.xml', 'latin1'))
caf.range // { from: 1001, to: 2000 }
 
const stamped = stampDte(invoice, caf)
stamped.ted    // '<TED version="1.0">…</TED>'
stamped.pdf417 // payload for the barcode on the printed version

dte-sdk no genera PDF: pasa stamped.pdf417 a la librería de códigos de barra que ya uses para la versión impresa.

3

Fírmalo con tu certificado

loadCertificate() lee el archivo .pfx y su contraseña una sola vez; signDte() agrega la firma XMLDSig.

TypeScript
import { readFile } from 'node:fs/promises'
import { loadCertificate, signDte } from 'dte-sdk'
 
const certificate = await loadCertificate(
  await readFile('./certs/signer.pfx'),
  process.env.DTE_CERT_PASSWORD!,
)
 
const signed = await signDte(stamped, certificate)
signed.xml      // the signed document, ready to send
signed.signedAt // ISO 8601 timestamp
4

Envíalo al SII

sendToSii() envuelve el documento en un EnvioDTE firmado, se autentica con el certificado y lo sube. senderRut es el RUT del titular del certificado, no el de la empresa. En el ambiente de certificación el número de resolución es 0.

TypeScript
import { sendToSii } from 'dte-sdk'
 
const { trackId } = await sendToSii(signed, {
  certificate,
  environment: 'certification',
  senderRut: '15.234.567-4',
  resolution: { number: 0, date: '2026-09-01' },
})
 
trackId // '0234567890'
5

Revisa el resultado

El SII valida los envíos de forma asíncrona, así que un track id recién creado suele responder received primero. Vuelve a consultar después de una breve espera hasta que quede en accepted, accepted_with_objections o rejected.

TypeScript
import { getStatus } from 'dte-sdk'
 
const status = await getStatus(trackId, {
  certificate,
  environment: 'certification',
  issuerRut: '76.123.456-0',
})
 
status.state     // 'accepted'
status.documents // [{ type: 33, folio: 1024, state: 'accepted' }]

Todo junto#

El mismo flujo en un solo script, con consulta periódica y manejo de errores:

TypeScript
import { readFile } from 'node:fs/promises'
import {
  createDte, parseCaf, stampDte, loadCertificate,
  signDte, sendToSii, getStatus, DteError,
} from 'dte-sdk'
 
const environment = 'certification'
const issuerRut = '76.123.456-0'
 
async function issueInvoice() {
  const caf = parseCaf(await readFile('./caf/33.xml', 'latin1'))
  const certificate = await loadCertificate(
    await readFile('./certs/signer.pfx'),
    process.env.DTE_CERT_PASSWORD!,
  )
 
  const invoice = createDte({
    type: 33,
    folio: 1024,
    issuer: {
      rut: issuerRut,
      businessName: 'Arrow Software SpA',
      activity: 'Desarrollo de software',
      acteco: 620200,
      address: 'Av. Providencia 1234',
      commune: 'Providencia',
    },
    receiver: {
      rut: '77.654.321-7',
      businessName: 'Comercial Los Andes Ltda.',
      activity: 'Comercio al por menor',
      address: 'Huérfanos 1055',
      commune: 'Santiago',
    },
    items: [{ name: 'Licencia anual', quantity: 1, unitPrice: 120000 }],
  })
 
  const signed = await signDte(stampDte(invoice, caf), certificate)
 
  const { trackId } = await sendToSii(signed, {
    certificate,
    environment,
    senderRut: '15.234.567-4',
    resolution: { number: 0, date: '2026-09-01' },
  })
 
  // The SII answers asynchronously: poll with a growing delay.
  for (let attempt = 1; attempt <= 10; attempt++) {
    const status = await getStatus(trackId, { certificate, environment, issuerRut })
    if (status.state !== 'received') return status
    await new Promise((resolve) => setTimeout(resolve, attempt * 5000))
  }
  throw new Error(`Still processing, check track id ${trackId} later`)
}
 
try {
  const status = await issueInvoice()
  console.log(status.state) // 'accepted'
} catch (error) {
  if (error instanceof DteError) {
    console.error(error.code) // e.g. 'CAF_EXPIRED', never a RUT or an amount
  }
  throw error
}

Regla práctica: mantén el CAF y el certificado en el servidor, pasa cada tipo de documento nuevo por certification antes de cambiar environment a 'production', y guarda el track id junto al folio para poder volver a consultar el estado más tarde.

A dónde ir ahora#