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.
npm install pdf-lib bwip-jspdf-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#
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#
const documents = parsePrintableDocuments(xml) // readonly PrintableDocument[]Acepta lo que probablemente tengas:
- un sobre
EnvioDTE,EnvioDTEB,EnvioDTETrasladooEnvioBOLETA, - un
SetDTEsuelto, - un
DTEsuelto, que es lo que traeSignedDocument.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#
const pdf = await renderPrintedSample(doc, options) // Promise<Uint8Array>| Opción | Por defecto | Qué hace |
|---|---|---|
siiOffice | la oficina de la comuna del emisor | La 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. |
resolution | Res. 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. |
logo | ninguno | { bytes, format } con format 'png' o 'jpeg', dibujado arriba a la izquierda. Un logo que la librería no pueda incrustar se ignora. |
transferable | false | Dibujar la copia cedible (recuadro de acuse de recibo y marca CEDIBLE) cuando el documento la admite. |
now | new Date() | El reloj para el año de la resolución por defecto. |
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ódigo | Cuándo |
|---|---|
MISSING_FIELD (field: 'TipoDTE') | El tipo de documento es 0: no hay nada que dibujar. |
ENCODING_UNSUPPORTED_CHAR | El TED tiene un carácter sobre U+00FF, que no cabe en un byte de ISO-8859-1. |
DEPENDENCY_UNAVAILABLE | No 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:
transferReason1 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.
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#
import { renderTedBarcode } from 'dte-sdk/pdf'
const png = await renderTedBarcode(doc.tedXml) // Promise<Uint8Array>, un PNGDibuja 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#
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 (
codees el código del error, comoXML_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#
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.xmlbajo un directorio (recursivo) cuyo nombre contengaenvio-set-,envio-simulacionoenvioboleta. 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-PRUEBASy{outDir}/SET-SIMULACION, creando ambas carpetas. Devuelve los mismos conteos querenderPrintedSamples, 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 conINVALID_FOLIOy no se escribe en ninguna parte.logoPathse lee una vez, reemplaza aoptions.logoy debe terminar en.png,.jpgo.jpeg; un archivo que falta u otra extensión significa sin logo.readPrintedSamples(dir)devuelve todo.pdfbajo un directorio, recursivo, en orden de nombre en cada nivel, asíSET-PRUEBASva antes queSET-SIMULACION. Cada uno es{ fileName, bytes }, listo para el paso de muestras impresas de la certificación. Un directorio sin PDF lanzaMISSING_FIELD(field: 'samples', con eldir).
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 yVerifique 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#
- Intercambio y muestras impresas: dónde sube la certificación estos PDF.
- Runner de certificación: el runner que recibe las muestras como opción.
- Timbre: cómo se construye el TED que codifica el símbolo PDF417.
- Referencia de la API de
dte-sdk/pdf: cada firma y cada tipo.