Skip to content

Printed samples

A printed sample (muestra impresa) is the PDF representation of a DTE: the page a customer sees, with the red type box, the detail, the totals and the PDF417 stamp (the TED). The SII asks for printed samples during certification, and you will want the same PDF to send to your customers. dte-sdk/pdf renders one single-page PDF per document, following the SII's printed-samples manual, from the signed XML you already have.

The layout follows the SII printed-samples manual and is tested in CI, but the SII has not reviewed it yet. The SII reviews samples as part of certification, and that review has not happened for this SDK. Check what you print before you rely on it. See Status.

dte-sdk 0.8.0 is on npm, the latest release. It is not recommended for production yet: most areas have not met the real SII, and until 1.0.0 the API can change in any release. See Status.

Install#

dte-sdk/pdf needs two optional peer dependencies. They are not installed with dte-sdk, so the package stays small for everyone who does not print.

Shell
npm install pdf-lib bwip-js

pdf-lib draws the page and bwip-js draws the PDF417 stamp. Both are loaded the first time you render, never at import time. Without them, every render function throws a DteError with code DEPENDENCY_UNAVAILABLE, with details.module saying which one is missing ('pdf-lib' or 'bwip-js').

dte-sdk/pdf runs on Node only, because the batch functions read and write files. See Entry points.

Basic usage#

TypeScript
import { writeFileSync } from 'node:fs'
import { needsTransferableCopy, parsePrintableDocuments, renderPrintedSample } from 'dte-sdk/pdf'
 
// signedInvoice.xml is the signed <DTE> that issueDocument returned (or any EnvioDTE you hold)
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)
 
// The transferable copy adds the acknowledgement box and the CEDIBLE mark
if (needsTransferableCopy(doc)) {
  const copy = await renderPrintedSample(doc, { transferable: true })
  writeFileSync(`muestra_${doc.type}_${doc.folio}_cedible.pdf`, copy)
}

The input is XML, not a document object: the PDF is drawn from the bytes that were signed, so the stamp encodes exactly what the SII will check.

Read the XML: parsePrintableDocuments#

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

It accepts what you are likely to hold:

  • an EnvioDTE, EnvioDTEB, EnvioDTETraslado or EnvioBOLETA envelope,
  • a bare SetDTE,
  • a bare DTE, which is what SignedDocument.xml holds.

Any other root gives an empty list. XML that is not well-formed throws XML_PARSE_FAILED.

The TED is copied verbatim. The parser takes each <TED>…</TED> from the input text itself and matches it to its document by folio and type (by position when nothing matches), so the stamp carries the bytes that were signed. It does not rebuild the TED from parsed values.

A PrintableDocument holds what the page needs: type, folio (a string), issueDate, transferReason (guides), grossAmounts (line amounts include VAT), issuer (with branch), receiver, totals (net, exempt, vat, vatRate, total), globalAdjustments, items, references and tedXml. A string is empty and a number is undefined when the tag is absent. If IdDoc lacks the type, folio or date, the parser falls back to the TED.

Render one sample: renderPrintedSample#

TypeScript
const pdf = await renderPrintedSample(doc, options) // Promise<Uint8Array>
OptionDefaultWhat it does
siiOfficethe office of the issuer's communeThe SII office printed under the red type box. Not given, it is the office of the issuer's commune (CmnaOrigen), looked up with siiOfficeForCommune; S.I.I. - SANTIAGO CENTRO only for a commune the table lacks.
resolutionRes. Ex. SII N° 0 del {year}The resolution line printed under the stamp. The year comes from now. Use your real resolution number in production.
logonone{ bytes, format } with format 'png' or 'jpeg', drawn at the top left. A logo the library cannot embed is ignored.
transferablefalseDraw the transferable copy (acknowledgement box and CEDIBLE mark) when the document admits one.
nownew Date()The clock for the year of the default resolution.
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 returns the bytes of a one-page PDF. It throws a DteError with these codes:

CodeWhen
MISSING_FIELD (field: 'TipoDTE')The document type is 0: nothing to draw.
ENCODING_UNSUPPORTED_CHARThe TED holds a character above U+00FF, which cannot be one byte of ISO-8859-1.
DEPENDENCY_UNAVAILABLEpdf-lib or bwip-js cannot be loaded.
PDF_RENDER_FAILED (with type and folio)A library threw while drawing, for example on a character outside WinAnsi, the encoding of the standard Helvetica font the page uses.

A document with no <TED> is drawn without the stamp.

The transferable copy#

needsTransferableCopy(doc) says whether a document carries a transferable copy (copia cedible):

  • types 33, 34, 43 and 46 always,
  • type 52 (dispatch guide) only when the transfer is a sale: transferReason 1 or 9, with 1 assumed when it is absent, because the manual excludes transfers that are not sales,
  • never credit or debit notes, never receipts.

With transferable: true on such a document, the page gets the acknowledgement box (Acuse de Recibo) with its fields (name, RUT, date, place, signature) and the Ley 19.983 legend, and a CEDIBLE mark in the bottom right corner (CEDIBLE CON SU FACTURA on a guide). On a document that does not admit one, transferable: true changes nothing: you get the normal sample.

SII offices#

The legend under the type box is S.I.I. - {office}. When you do not pass siiOffice, the sample prints the office of the issuer's commune (the CmnaOrigen of the document): siiOfficeForCommune looks it up in SII_OFFICES, a table of 355 commune names in 40 offices that includes every official commune. It ignores case, surrounding spaces and accents, and gives null for a commune the table lacks; only then does the sample print S.I.I. - SANTIAGO CENTRO. Pass siiOffice to print another one.

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: the sample would print 'S.I.I. - SANTIAGO CENTRO'

See dte-sdk/pdf for the signatures.

The stamp alone: renderTedBarcode#

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

It draws the PDF417 symbol of a TED as a PNG. The renderer embeds the same image under the totals; use renderTedBarcode yourself when your own layout needs the stamp.

The symbol encodes the signed bytes: the text is handed to the barcode library as bytes, so an ñ reaches the symbol as the one ISO-8859-1 byte that was signed, never as two UTF-8 bytes. It throws MISSING_FIELD (field: 'TED') when the text has no <TED, ENCODING_UNSUPPORTED_CHAR for a code point above U+00FF, and DEPENDENCY_UNAVAILABLE when bwip-js cannot be loaded.

Batches for certification#

The certification asks for the printed samples of the documents in the test sets and in the simulation, organized in two sets. Three functions do that, from least to most disk.

In memory: 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 when nothing failed
result.documents // documents parsed
result.tests // samples in the test set
result.simulation // samples in the simulation set
result.samples // [{ name, set, source, type, folio, transferable, bytes }]
result.failures // [{ source, type?, folio?, transferable?, code?, reason }]

It renders every document of every envelope: one sample per document and, with transferable: true, a second one for each document that admits a transferable copy. Each sample is named muestra_{type}_{folio}.pdf, with _cedible before the extension on a transferable copy.

The set comes from the envelope's name. Envelopes whose name contains envio-set-basico.xml, envio-set-guia.xml, envio-set-exenta.xml or envio-set-compra.xml go to the test set (set: 'tests'). Everything else goes to the simulation (set: 'simulation'). Name your envelopes accordingly.

Failures come back as data. The batch never throws for a bad document:

  • An envelope that does not parse is skipped and reported (code is the error's code, such as XML_PARSE_FAILED).
  • A document whose folio is not a run of digits is reported with code: 'INVALID_FOLIO'. The folio becomes part of the file name, and an XML from elsewhere could carry .. or a path separator in <Folio> to name a file outside the set's folder.
  • A document that fails to draw is reported with the code of the error (PDF_RENDER_FAILED, ENCODING_UNSUPPORTED_CHAR, DEPENDENCY_UNAVAILABLE).

The batch goes on after each failure. Check result.success and result.failures.

On disk: findEnvelopeFiles, writePrintedSamples, readPrintedSamples#

TypeScript
import { findEnvelopeFiles, readPrintedSamples, writePrintedSamples } from 'dte-sdk/pdf'
 
// The envelope files of a certification run
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
 
// Back from disk, for the certification runner
const samples = await readPrintedSamples('./certification/samples')
  • findEnvelopeFiles(path) returns the file itself, or every .xml under a directory (recursively) whose name contains envio-set-, envio-simulacion or envioboleta. That skips the responses and receipt envelopes of the exchange, which carry no DTE. A path that does not exist gives an empty list.
  • writePrintedSamples({ xmlFiles, outDir, options?, logoPath?, transferable? }) renders the samples of every file into {outDir}/SET-PRUEBAS and {outDir}/SET-SIMULACION, creating both. It returns the same counts as renderPrintedSamples, with the samples as { name, set, source, type, folio, transferable, path, size } and no bytes. A file that cannot be read, parsed or rendered is a failure entry and the rest goes on. A sample whose name would land outside the folder of its set is refused with INVALID_FOLIO and written nowhere. logoPath is read once, replaces options.logo, and must end in .png, .jpg or .jpeg; a missing file or another extension means no logo.
  • readPrintedSamples(dir) returns every .pdf under a directory, recursively, in name order at every level, so SET-PRUEBAS comes before SET-SIMULACION. Each is { fileName, bytes }, ready for the printed-samples step of the certification. A directory without PDFs throws MISSING_FIELD (field: 'samples', with the dir).

findEnvelopeFiles, writePrintedSamples and readPrintedSamples throw TRANSPORT_UNAVAILABLE if node:fs cannot be loaded.

The certification runner takes the result of readPrintedSamples as its samples option and uploads them to the SII's samples portal. See Exchange and printed samples.

The layout#

The page follows the SII printed-samples manual. None of it is a parameter: the layout is the SII's.

  • Page. 21.5 cm wide and as tall as its content, between 11 and 33 cm, with 0.5 cm margins. Content that does not fit in 33 cm is not drawn, so a very long detail can be cut off at the bottom.
  • Header. The issuer on the left (name, business activity, address, branch) with the logo, and on the right the red type box: 5.5 cm wide, with the issuer's RUT, the name of the document type and the folio. The SII office is printed under it: the one you pass as siiOffice or, by default, the office of the issuer's commune (see SII offices).
  • Body. The issue date, the receiver box, the transfer reason of a dispatch guide, a table of references to other documents when there are any, the detail table, and the totals (net, exempt, VAT with its rate, total, and global discounts or surcharges).
  • Acknowledgement. On the transferable copy, the acknowledgement box with its Ley 19.983 legend, and the CEDIBLE mark.
  • Stamp. The PDF417 symbol of the TED, 5 to 9 cm wide and 2 to 4 cm high, 2 cm from the left margin, with three legends under it: Timbre Electrónico SII, the resolution line and Verifique documento: www.sii.cl.

What is not printed yet#

  • Retentions.
  • The commission block of settlement invoices.
  • A stamp raster above 300 DPI.

If you issue documents that need these, check the PDF against the manual yourself.

Next steps#