Saltar al contenido
dte-sdk

Muestras impresas

Una muestra impresa es la representación en PDF de un DTE: la página que ve un cliente, con el recuadro rojo del tipo, el detalle, los totales y el timbre PDF417 (el TED). El SII pide muestras impresas durante la certificación, y querrás el mismo PDF para enviarlo a tus clientes. dte-sdk/pdf genera un PDF de una página por documento, siguiendo el manual de muestras impresas del SII, a partir del XML firmado que ya tienes.

La diagramación sigue el manual de muestras impresas del SII y está probada en el CI, pero el SII aún no la ha revisado. El SII revisa las muestras como parte de la certificación, y esa revisión todavía no ocurre para este SDK. Revisa lo que imprimes antes de depender de ello. Revisa el Estado.

La 0.8.0 de dte-sdk está en npm, la versión más reciente. Todavía no se recomienda para producción: la mayoría de las áreas no se ha probado contra el SII real, y hasta la 1.0.0 la API puede cambiar en cualquier versión. Revisa Estado.

Instalación#

dte-sdk/pdf necesita dos dependencias peer opcionales. No se instalan con dte-sdk, así el paquete se mantiene liviano para quien no imprime.

Shell
npm install pdf-lib bwip-js

pdf-lib dibuja la página y bwip-js dibuja el timbre PDF417. Ambas se cargan la primera vez que generas un PDF, nunca al importar. Sin ellas, toda función de generación lanza un DteError con código DEPENDENCY_UNAVAILABLE, y details.module indica cuál falta ('pdf-lib' o 'bwip-js').

dte-sdk/pdf funciona solo en Node, porque las funciones por lote leen y escriben archivos. Revisa Puntos de entrada.

Uso básico#

TypeScript
import { writeFileSync } from 'node:fs'
import { needsTransferableCopy, parsePrintableDocuments, renderPrintedSample } from 'dte-sdk/pdf'
 
// signedInvoice.xml es el <DTE> firmado que devolvió issueDocument (o cualquier EnvioDTE que tengas)
const [doc] = parsePrintableDocuments(signedInvoice.xml)
 
const pdf = await renderPrintedSample(doc, {
  siiOffice: 'S.I.I. - SANTIAGO ORIENTE',
  resolution: 'Res. Ex. SII N° 0 del 2026',
})
writeFileSync(`muestra_${doc.type}_${doc.folio}.pdf`, pdf)
 
// La copia cedible agrega el recuadro de acuse de recibo y la marca CEDIBLE
if (needsTransferableCopy(doc)) {
  const copy = await renderPrintedSample(doc, { transferable: true })
  writeFileSync(`muestra_${doc.type}_${doc.folio}_cedible.pdf`, copy)
}

La entrada es XML, no un objeto documento: el PDF se dibuja a partir de los bytes que se firmaron, así el timbre codifica exactamente lo que el SII va a verificar.

Leer el XML: parsePrintableDocuments#

TypeScript
const documents = parsePrintableDocuments(xml) // readonly PrintableDocument[]

Acepta lo que probablemente tengas:

  • un sobre EnvioDTE, EnvioDTEB, EnvioDTETraslado o EnvioBOLETA,
  • un SetDTE suelto,
  • un DTE suelto, que es lo que trae SignedDocument.xml.

Cualquier otra raíz devuelve una lista vacía. Un XML mal formado lanza XML_PARSE_FAILED.

El TED se copia textual. El parser toma cada <TED>…</TED> del texto de entrada y lo asigna a su documento por folio y tipo (por posición si nada coincide), así el timbre lleva los bytes que se firmaron. No reconstruye el TED a partir de valores parseados.

Un PrintableDocument trae lo que necesita la página: type, folio (un string), issueDate, transferReason (guías), grossAmounts (los montos de las líneas incluyen IVA), issuer (con branch), receiver, totals (net, exempt, vat, vatRate, total), globalAdjustments, items, references y tedXml. Un string queda vacío y un número undefined cuando falta la etiqueta. Si IdDoc no trae el tipo, el folio o la fecha, el parser recurre al TED.

Generar una muestra: renderPrintedSample#

TypeScript
const pdf = await renderPrintedSample(doc, options) // Promise<Uint8Array>
OpciónPor defectoQué hace
siiOfficela oficina de la comuna del emisorLa oficina del SII impresa bajo el recuadro rojo del tipo. Si no la indicas, es la oficina de la comuna del emisor (CmnaOrigen), buscada con siiOfficeForCommune; S.I.I. - SANTIAGO CENTRO solo para una comuna que la tabla no tiene.
resolutionRes. Ex. SII N° 0 del {año}La línea de resolución impresa bajo el timbre. El año sale de now. En producción usa tu número de resolución real.
logoninguno{ bytes, format } con format 'png' o 'jpeg', dibujado arriba a la izquierda. Un logo que la librería no pueda incrustar se ignora.
transferablefalseDibujar la copia cedible (recuadro de acuse de recibo y marca CEDIBLE) cuando el documento la admite.
nownew Date()El reloj para el año de la resolución por defecto.
TypeScript
import { readFileSync } from 'node:fs'
 
const pdf = await renderPrintedSample(doc, {
  logo: { bytes: new Uint8Array(readFileSync('logo.png')), format: 'png' },
  resolution: 'Res. Ex. SII N° 0 del 2026',
})

renderPrintedSample devuelve los bytes de un PDF de una página. Lanza un DteError con estos códigos:

CódigoCuándo
MISSING_FIELD (field: 'TipoDTE')El tipo de documento es 0: no hay nada que dibujar.
ENCODING_UNSUPPORTED_CHAREl TED tiene un carácter sobre U+00FF, que no cabe en un byte de ISO-8859-1.
DEPENDENCY_UNAVAILABLENo se puede cargar pdf-lib o bwip-js.
PDF_RENDER_FAILED (con type y folio)Una librería lanzó al dibujar, por ejemplo ante un carácter fuera de WinAnsi, la codificación de la fuente Helvetica estándar que usa la página.

Un documento sin <TED> se dibuja sin timbre.

La copia cedible#

needsTransferableCopy(doc) dice si un documento lleva copia cedible:

  • los tipos 33, 34, 43 y 46 siempre,
  • el tipo 52 (guía de despacho) solo cuando el traslado es una venta: transferReason 1 o 9, asumiendo 1 cuando falta, porque el manual excluye los traslados que no son venta,
  • nunca las notas de crédito ni de débito, nunca las boletas.

Con transferable: true en un documento así, la página recibe el recuadro de acuse de recibo con sus campos (nombre, RUT, fecha, recinto, firma) y la leyenda de la Ley 19.983, y una marca CEDIBLE en la esquina inferior derecha (CEDIBLE CON SU FACTURA en una guía). En un documento que no la admite, transferable: true no cambia nada: obtienes la muestra normal.

Oficinas del SII#

La leyenda bajo el recuadro del tipo es S.I.I. - {oficina}. Si no indicas siiOffice, la muestra imprime la oficina de la comuna del emisor (la CmnaOrigen del documento): siiOfficeForCommune la busca en SII_OFFICES, una tabla de 355 nombres de comuna en 40 oficinas que incluye todas las comunas oficiales. Ignora mayúsculas, espacios alrededor y tildes, y da null para una comuna que la tabla no tiene; solo entonces la muestra imprime S.I.I. - SANTIAGO CENTRO. Indica siiOffice para imprimir otra.

TypeScript
import { siiOfficeForCommune } from 'dte-sdk/pdf'
 
siiOfficeForCommune('Ñuñoa') // 'S.I.I. - ÑUÑOA'
siiOfficeForCommune(' NUNOA ') // 'S.I.I. - ÑUÑOA'
siiOfficeForCommune('Atlantis') // null: la muestra imprimiría 'S.I.I. - SANTIAGO CENTRO'

Las firmas están en dte-sdk/pdf.

Solo el timbre: renderTedBarcode#

TypeScript
import { renderTedBarcode } from 'dte-sdk/pdf'
 
const png = await renderTedBarcode(doc.tedXml) // Promise<Uint8Array>, un PNG

Dibuja el símbolo PDF417 de un TED como PNG. El generador incrusta esta misma imagen bajo los totales; usa renderTedBarcode tú mismo cuando tu propia diagramación necesite el timbre.

El símbolo codifica los bytes firmados: el texto se entrega a la librería de códigos de barras como bytes, así una ñ llega al símbolo como el único byte ISO-8859-1 que se firmó, nunca como dos bytes UTF-8. Lanza MISSING_FIELD (field: 'TED') cuando el texto no tiene <TED, ENCODING_UNSUPPORTED_CHAR ante un code point sobre U+00FF y DEPENDENCY_UNAVAILABLE cuando no se puede cargar bwip-js.

Lotes para la certificación#

La certificación pide las muestras impresas de los documentos de los sets de pruebas y de la simulación, ordenadas en dos conjuntos. Tres funciones lo hacen, de menos a más uso de disco.

En memoria: renderPrintedSamples#

TypeScript
import { renderPrintedSamples } from 'dte-sdk/pdf'
 
const result = await renderPrintedSamples({
  envelopes: [
    { name: 'envio-set-basico.xml', xml: basicSetXml },
    { name: 'envio-simulacion.xml', xml: simulationXml },
  ],
  options: { siiOffice: 'S.I.I. - SANTIAGO ORIENTE' },
  transferable: true,
})
 
result.success // true cuando nada falló
result.documents // documentos parseados
result.tests // muestras del conjunto de pruebas
result.simulation // muestras del conjunto de simulación
result.samples // [{ name, set, source, type, folio, transferable, bytes }]
result.failures // [{ source, type?, folio?, transferable?, code?, reason }]

Genera cada documento de cada envío: una muestra por documento y, con transferable: true, una segunda por cada documento que admite copia cedible. Cada muestra se llama muestra_{tipo}_{folio}.pdf, con _cedible antes de la extensión en una copia cedible.

El conjunto sale del nombre del envío. Los envíos cuyo nombre contiene envio-set-basico.xml, envio-set-guia.xml, envio-set-exenta.xml o envio-set-compra.xml van al conjunto de pruebas (set: 'tests'). Todo lo demás va a la simulación (set: 'simulation'). Nombra tus envíos en consecuencia.

Las fallas vuelven como datos. El lote nunca lanza por un documento malo:

  • Un envío que no se puede parsear se omite y se informa (code es el código del error, como XML_PARSE_FAILED).
  • Un documento cuyo folio no es solo dígitos se informa con code: 'INVALID_FOLIO'. El folio pasa a ser parte del nombre del archivo, y un XML de otra procedencia podría traer .. o un separador de ruta en <Folio> para nombrar un archivo fuera de la carpeta de su conjunto.
  • Un documento que falla al dibujarse se informa con el código del error (PDF_RENDER_FAILED, ENCODING_UNSUPPORTED_CHAR, DEPENDENCY_UNAVAILABLE).

El lote sigue después de cada falla. Revisa result.success y result.failures.

En disco: findEnvelopeFiles, writePrintedSamples, readPrintedSamples#

TypeScript
import { findEnvelopeFiles, readPrintedSamples, writePrintedSamples } from 'dte-sdk/pdf'
 
// Los archivos de envío de una corrida de certificación
const files = await findEnvelopeFiles('./certification/envelopes')
 
const written = await writePrintedSamples({
  xmlFiles: files,
  outDir: './certification/samples',
  logoPath: './logo.png',
  transferable: true,
})
// ./certification/samples/SET-PRUEBAS/muestra_33_1.pdf
// ./certification/samples/SET-SIMULACION/muestra_33_7.pdf
 
// De vuelta desde el disco, para el runner de certificación
const samples = await readPrintedSamples('./certification/samples')
  • findEnvelopeFiles(path) devuelve el archivo mismo, o todo .xml bajo un directorio (recursivo) cuyo nombre contenga envio-set-, envio-simulacion o envioboleta. Así se saltan las respuestas y los envíos de recibos del intercambio, que no traen DTE. Una ruta que no existe devuelve una lista vacía.
  • writePrintedSamples({ xmlFiles, outDir, options?, logoPath?, transferable? }) genera las muestras de cada archivo en {outDir}/SET-PRUEBAS y {outDir}/SET-SIMULACION, creando ambas carpetas. Devuelve los mismos conteos que renderPrintedSamples, con las muestras como { name, set, source, type, folio, transferable, path, size } y sin bytes. Un archivo que no se puede leer, parsear o dibujar es una entrada de falla y el resto sigue. Una muestra cuyo nombre caería fuera de la carpeta de su conjunto se rechaza con INVALID_FOLIO y no se escribe en ninguna parte. logoPath se lee una vez, reemplaza a options.logo y debe terminar en .png, .jpg o .jpeg; un archivo que falta u otra extensión significa sin logo.
  • readPrintedSamples(dir) devuelve todo .pdf bajo un directorio, recursivo, en orden de nombre en cada nivel, así SET-PRUEBAS va antes que SET-SIMULACION. Cada uno es { fileName, bytes }, listo para el paso de muestras impresas de la certificación. Un directorio sin PDF lanza MISSING_FIELD (field: 'samples', con el dir).

findEnvelopeFiles, writePrintedSamples y readPrintedSamples lanzan TRANSPORT_UNAVAILABLE si no se puede cargar node:fs.

El runner de certificación recibe el resultado de readPrintedSamples como su opción samples y las sube al portal de muestras del SII. Revisa Intercambio y muestras impresas.

La diagramación#

La página sigue el manual de muestras impresas del SII. Nada de esto es un parámetro: la diagramación es la del SII.

  • Página. 21,5 cm de ancho y tan alta como su contenido, entre 11 y 33 cm, con márgenes de 0,5 cm. El contenido que no cabe en 33 cm no se dibuja, así que un detalle muy largo puede quedar cortado al final.
  • Encabezado. El emisor a la izquierda (nombre, giro, dirección, sucursal) con el logo, y a la derecha el recuadro rojo del tipo: de 5,5 cm de ancho, con el RUT del emisor, el nombre del tipo de documento y el folio. Bajo él se imprime la oficina del SII.
  • Cuerpo. La fecha de emisión, el recuadro del receptor, el tipo de traslado de una guía de despacho, una tabla de referencias a otros documentos cuando las hay, la tabla de detalle y los totales (neto, exento, IVA con su tasa, total, y descuentos o recargos globales).
  • Acuse de recibo. En la copia cedible, el recuadro de acuse de recibo con su leyenda de la Ley 19.983, y la marca CEDIBLE.
  • Timbre. El símbolo PDF417 del TED, de 5 a 9 cm de ancho y de 2 a 4 cm de alto, a 2 cm del margen izquierdo, con tres leyendas debajo: Timbre Electrónico SII, la línea de resolución y Verifique documento: www.sii.cl.

Lo que aún no se imprime#

  • Retenciones.
  • El bloque de comisiones de las facturas de liquidación.
  • Una trama del timbre sobre 300 DPI.

Si emites documentos que necesitan estos elementos, revisa tú mismo el PDF contra el manual.

Próximos pasos#