Skip to content

Test sets

The deterministic half of the certification. You give it the text the SII generated for your company and your CAFs and certificate; it gives you the signed envelopes and books the SII asks for. It sends nothing and waits for nothing: sending, declaring and waiting belong to the runner, which calls these same functions.

Use it on its own to see what a test set will issue before touching a portal, to issue a set by hand after the SII rejected it, or to build your own flow.

The parser was written without a real test set text at hand, and the tests use synthetic texts with invented data. Keep your first download (the runner saves it as set-texto.txt) as a fixture, and open an issue if the parser misreads a line. Nothing here has been run against the SII yet: see Status.

Basic usage#

TypeScript
import { readFileSync } from 'node:fs'
import { loadCertificateFromPkcs12, parseCaf } from 'dte-sdk'
import {
  parseTestSetText,
  planCertificationSets,
  createFolioAllocator,
  issueTestSet,
  issueSimulation,
  createCertificationSalesBook,
  createCertificationPurchaseBook,
  createCertificationGuideBook,
} from 'dte-sdk/portal'
 
const certificate = loadCertificateFromPkcs12(readFileSync('firma.pfx'), process.env.PFX_PASSWORD!)
 
// The text the SII generated for the applicant
const plan = planCertificationSets(parseTestSetText(readFileSync('set-texto.txt', 'utf8')))
plan.basic?.folios // { 33: 3, 61: 3, 56: 2 }: the folios the basic set needs, by document type
 
const issuer = {
  rut: '76543210-3',
  businessName: 'Comercial Andina SpA',
  businessActivity: 'Actividades de programacion informatica',
  address: 'Av. Los Aromos 1540',
  commune: 'Santiago',
  activityCode: 620200,
}
const receiver = {
  rut: '66666666-6',
  businessName: 'CLIENTE TEST',
  businessActivity: 'ACTIVIDADES VARIAS',
  address: 'AVENIDA PRINCIPAL 123',
  commune: 'Santiago',
  city: 'Santiago',
}
const resolution = { number: 0, date: '2026-01-15' }
 
// One allocator per set: it hands out the folios of the CAFs you give it
const cafs = ['caf-33.xml', 'caf-61.xml', 'caf-56.xml'].map((file) => parseCaf(readFileSync(file, 'utf8')))
const options = { issuer, receiver, certificate, folios: createFolioAllocator(cafs), resolution }
 
const basic = await issueTestSet(plan.basic!, options)
basic.cases.map((c) => [c.caseId, c.type, c.folio]) // [['2410011-1', 33, 1], …]
basic.envelope.xml // the signed EnvioDTE, ready to upload
 
const common = {
  period: '2026-11',
  issuerRut: issuer.rut,
  senderRut: certificate.rut!,
  resolution,
  certificate,
}
const salesBook = createCertificationSalesBook(basic, {
  ...common,
  receiver: { rut: receiver.rut, name: receiver.businessName },
})
const purchaseBook = createCertificationPurchaseBook(plan.purchaseBook!, common)
 
// The simulation reissues every case of the four sets, in one envelope, with CAFs other than the sets'
const freshCafs = ['caf-33-b.xml', 'caf-34.xml', 'caf-52.xml', 'caf-46.xml', 'caf-61-b.xml', 'caf-56-b.xml'].map((file) =>
  parseCaf(readFileSync(file, 'utf8')),
)
const simulation = await issueSimulation(plan, { ...options, folios: createFolioAllocator(freshCafs) })
simulation.envelope.xml

Each issued set and each book is a signed XML string plus the data around it. You upload them with uploadEnvelope and uploadBook, or let the runner do it.

parseTestSetText#

parseTestSetText(text) parses the text the SII generates for the applicant into TestSet[]. The text is the portal page with its tags turned into line breaks and its entities decoded, or a file holding the same (downloadTestSet already returns it that way). The function never throws: lines the grammar does not know are ignored, and a set of unknown kind is other.

Text
SET BASICO - NUMERO DE ATENCION: 2410011
==========================================
CASO 2410011-1
==========================================
DOCUMENTO                        FACTURA ELECTRONICA
 
ITEM                             CANTIDAD   PRECIO UNITARIO
Cajon de manzanas                130        2800
Relleno de pollo                 90         1700
--------------------------------------------------
CASO 2410011-4
DOCUMENTO                        NOTA DE CREDITO ELECTRONICA
REFERENCIA                       CASO 2410011-1
RAZON REFERENCIA                 DEVOLUCION DE MERCADERIAS
ITEM                             CANTIDAD
Cajon de manzanas                62

This is a synthetic fragment with invented data, not an SII text.

What it reads#

A TestSet has name, attentionNumber (the digits after NUMERO DE ATENCION), kind, cases, bookDocuments and proportionalityFactor. A TestCase has:

FieldSource line
id, setId, numberThe CASO <setId>-<n> header, for example 2410011-1
documentText, documentTypeDOCUMENTO; the type is detected from the text, or null
itemsThe item rows under ITEM
referenceText, referencedCaseREFERENCIA; the CASO n-m found in it, or null
reasonTextRAZON REFERENCIA
motive, transferByMOTIVO and TRASLADO POR (guides)
globalDiscountPercentDESCUENTO GLOBAL … n%
currency, exportFieldsMONEDA DE LA OPERACION and the export-only lines (parsed, never issued)
rawEvery line of the case after its header, trimmed

An item row is split on tabs or runs of two or more spaces. With four columns it is name, quantity, unit and price, or name, quantity, price and discount (10%); with three it is name, quantity and price; with two it is a name and a quantity, or, when the reason says MODIFICA MONTO, a name and a new price. A missing quantity is 1. A line is not an item when it holds a colon, when DOCUMENTO, REFERENCIA or RAZON opens it, or when ITEM, CANTIDAD or UNITARIO is in its first part (the header row).

These words, and EXENTO and ANULA further down, are read in capitals only, as the SII writes them. Matching is case-insensitive elsewhere, but not here: an item named "Razones varias" is an item, and the value of a two-part row under a reason "modifica monto" is a quantity, not a price.

Kinds of set#

The kind comes from the name after SET, first match wins (TEST_SET_KINDS):

The name containskind
LIBRO DE COMPRAS PARA EXENTOSexemptPurchaseBook
LIBRO DE COMPRASpurchaseBook
LIBRO DE VENTASsalesBook
LIBRO DE GUIASguideBook
GUIAguide
FACTURA EXENTA or NO AFECTAexempt
EXPORTACIONexport
LIQUIDACIONsettlement
FACTURA DE COMPRA or FACTURA COMPRApurchase
BASICObasic
anything elseother

In the purchase book sets, the supplier documents are not cases: each one (FACTURA ELECTRONICA 234, NOTA DE CREDITO 12…) becomes a TestBookDocument with its folio, exempt and net affected amounts, the observation line and three flags read from it: IVA USO COMUN, ENTREGA GRATUITA (non-recoverable VAT code 4) and RETENCION TOTAL. The FACTOR DE PROPORCIONALIDAD line gives the proportionality factor.

planCertificationSets#

planCertificationSets(sets) turns the parsed sets into a CertificationPlan: what each set issues, ready for issueTestSet, and the folios each document type needs. For each kind the last set of the text wins, and a kind the text lacks is absent from the plan.

Plan keyTypeIssues
basicBasicSetPlanInvoices 33, credit notes 61, debit notes 56
exemptExemptSetPlanInvoices 34, credit notes 61, debit notes 56; every line exempt
guideGuideSetPlanOne guide 52 per case
purchasePurchaseSetPlanOne purchase invoice 46, at most one 61 and one 56
purchaseBookPurchaseBookPlanSupplier documents of the purchase book
exemptPurchaseBookPurchaseBookPlanThe same, for exempt taxpayers

The sales book and guide book sets have no plan: those books come from the basic and guide sets once issued. Export and settlement sets are parsed but never issued, and unknown sets are left out.

Every plan carries its attentionNumber and, for the issuable sets, folios: how many documents of each type it will issue (SetFolios, a record from document type to count). The runner sums these to know how many folios to have. A PurchaseBookPlan carries documents and proportionalityFactor, which is 0.6 when the set gives none.

Notes carry their referenceCode (CodRef), derived from the reason text: a reason that is empty or says ANULA is 1 (cancels), CORRIGE with GIRO or TEXTO is 2 (corrects text), DEVOLUCION or MODIFICA is 3 (corrects amounts), anything else is 1. A guide carries its transferReason (IndTraslado) from the motive, 1 when empty or unknown (TRASLADO between warehouses is 5, DEVOLUCION 7, CONSIGNACION 3, GRATUITA 4, export sale 9, a sale to be made 2, plain VENTA 1) and its dispatchType from TRASLADO POR: EMISOR is 2 (by the issuer), CLIENTE is 1.

createFolioAllocator#

createFolioAllocator(cafs, { usedFolios }) is a FolioSource over one or several parsed CAFs: one counter per CAF, the first CAF of the type with a folio left wins, a folio is never given twice, and the CAF returned is the one whose range holds the folio, so it stamps with its own key. When every CAF of the type is exhausted take throws FOLIO_OUT_OF_RANGE with { type }. usedFolios makes it start after the highest used folio of each CAF.

A FolioSource is just { take(type): Promise<{ caf, folio }> }. The allocator is in memory; the runner passes its own, backed by the folio registry so two runs never take the same folio. Implement your own if you keep folios elsewhere.

issueTestSet#

issueTestSet(plan, options) issues every document of one set plan and signs its envelope. It accepts the basic, exempt, guide and purchase plans.

OptionMeaning
issuerThe issuer, as for issueDocument
receiverThe customer every document names. Purchase invoices and internal transfers name the issuer instead
certificateThe signing certificate; its holder is the sender of the envelope
foliosA FolioSource
resolution{ number, date }; number 0 in certification
nowClock, read once per document and once for the envelope

It returns an IssuedSet: kind, attentionNumber, cases (each with caseId, type, folio and the signed document) and the signed envelope (EnvioDTE, with SetDTE/@ID DTE_SetDoc, the SII as receiver and the certificate holder as sender; the issuer when the certificate names none). Invoices (or guides) go first, then credit notes, then debit notes.

It throws MISSING_FIELD (invoices or guides on an empty plan, referencedCase on a note whose case was not issued before it), FOLIO_OUT_OF_RANGE from the folio source, and the usual document issuing errors.

How the documents are written#

The test sets follow the rules the SII's review expects, and a few details matter because the SII compares them:

  • The SET reference. Every document carries a first reference with document type SET, folio 1, the issue date and the reason CASO <id> (TEST_SET_REFERENCE_TYPE). A note adds a second reference to the document of the case it refers to, with the set's reference code and reason.
  • Returned goods at the original price. A note that corrects amounts (code 3) and gives items with no price takes the price, and the discount, of the item with the same name in the original document. A return in the exempt set takes every item of the original, exempt, at its original price.
  • Cancellations take the cancelled document's items. A basic-set credit note whose reason says ANULA (in capitals; a lower-case "anula" keeps the note's own items) takes the items of the document it cancels. In the exempt set a note always keeps its own items, written as exempt lines, even when its reason says ANULA.
  • The issuer as receiver of purchase invoices and notes, and of internal transfers (guides with transfer reason 5). Purchase documents carry the VAT withholding. An internal transfer totals 0 and keeps the VAT rate next to that zero total.
  • Notes that correct a text (code 2) get one line named after the reason, with a zero price. In the basic and purchase sets such a document totals 0 and carries no VAT rate; in the exempt set and in guides the rate stays next to the zero total. A basic-set note that ends up with no items gets a SIN MONTO line.
  • Exempt items in the basic set. An item of a basic invoice whose name has EXENTO is issued exempt. "Servicio exento", in lower case, is not.
  • Basic invoices carry TpoTranVenta 1 and the case's global discount, described as DESCUENTO GLOBAL ITEMES AFECTOS.
  • Every case of the guide set is a guide, whatever its text was read as, with its transfer reason and dispatch type.
  • A receiver's line of business is cut at 40 characters instead of being refused. The issuer's keeps its own length, except where the issuer is the receiver of its own documents.

How the dates, stamps and prices are written is also deliberate, and different from the simulation:

  • The issue date is the UTC day the set starts on. TSTED, TmstFirma and TmstFirmaEnv are the UTC instant with milliseconds and no zone. A run after 21:00 in Chile (20:00 in winter) therefore dates the documents with the next day.
  • Unit prices are written with six decimals on priced lines. Invoice and note lines with no price carry no price, no quantity and no unit.
  • A document whose lines add up to nothing has no VAT rate (see above).

issueSimulation#

issueSimulation(plan, options) issues every case of the four sets again, with fresh folios, in the order basic, exempt, guide and purchase, and signs one envelope with all of them. The folio source should hold CAFs other than the sets'. It takes the same options as issueTestSet and returns an IssuedSimulation: cases, envelope, types (document types in order of first appearance) and fewerThanRecommended (true with fewer than the ten documents the SII asks for).

It throws MISSING_FIELD (cases) when the plan has no case at all.

The simulation is written differently from the test sets:

  • No SET reference. A note carries one reference to the document it refers to, when that case is in the simulation.
  • The date is the local day of the process. FchEmis is the local date, TSTED and TmstFirma are the UTC instant to the second, the envelope id is the local time, and only TmstFirmaEnv is Chile time. Run the process with TZ=America/Santiago.
  • Unit prices are plain numbers, not six decimals.
  • A guide carries the transfer reason and no TpoDespacho. An internal transfer names the issuer as receiver.
  • The global discount has no description.
  • A note with no items gets one line named after its reason. A purchase note item with no price takes the price of the same-named item of the original, or 1.
  • A document whose lines add up to nothing totals 0 with no VAT rate; a guide keeps it.
  • The parties carry a fixed list of fields: whatever else you give the issuer or the receiver (phone, e-mail, branch, contact) stays out of the documents.

The books#

The three functions build and sign the books. All take a CertificationBookInput:

FieldMeaning
periodTax period YYYY-MM. The runner chooses it against the portal's history
issuerRut, senderRutThe company and the authorized sender, normally the certificate holder
resolution{ number, date }
certificateThe signing certificate
nowClock for the book id and the signature time
submissiontotal (default) or adjustment, for a period the SII already holds
previousTotalsPeriod totals of the earlier submissions, carried into an adjustment of the sales or purchase book; the guide book ignores them
  • createCertificationSalesBook(set, input) takes the issued basic set (or { kind: 'basic', cases }, as a resumed run holds them) and receiver: { rut, name }. Only the basic set's invoices and notes go in, dated the 15th of the period, with the customer as counterparty, every amount present (0 when absent) and the 19 VAT rate on every line. creditNoteSign is positive (default) or negative. It throws INVALID_OPTION (field set) for any other set.
  • createCertificationPurchaseBook(plan, input) takes a PurchaseBookPlan (purchaseBook or exemptPurchaseBook). One line per supplier document, with the SII's own RUT and a placeholder name as counterparty (CERTIFICATION_COUNTERPARTY, nobody's personal data), VAT at 19 % of the affected amount, common-use VAT, non-recoverable VAT for free deliveries, the withholding of a purchase invoice, and the referenced folio of a note taken from its observation. For the exempt taxpayers' book, non-recoverable VAT on every affected line, the paper exempt invoice as type 32, and notes that inherit the amounts of the document they reference. The VAT withholding rate travels as a fraction (0.19) while every other rate is a percentage. The summary rows carry the credit of common-use VAT, rounded document by document and then summed, also across the rows of earlier submissions.
  • createCertificationGuideBook(set, input) takes the issued guide set (or { kind: 'guide', cases }) and receiver. By position, the second guide is flagged modified and the third voided (written as annulled, total 0 and nothing else), unless caseFlags says otherwise. A sale carries the customer, the net, the VAT rate and the VAT; every guide its total. notificationFolio is 3 by default. An adjustment marks only the modified guide.

Sales and purchase books are monthly books sent as a total submission. The guide book is a special book. All three return a signed book with its xml (the sales and purchase books also its periodTotals).

Catalogs#

ExportWhat it holds
TEST_SET_KINDSThe kinds of set, by their English name
TEST_SET_PORTAL_NAMESThe names the portal lists the sets and books under (SET BASICO, SET GUIA DE DESPACHO, LIBRO DE VENTAS…)
TEST_SET_REFERENCE_TYPESET, the document type of the first reference of every set document
TEST_SET_ENVELOPE_IDDTE_SetDoc, the SetDTE/@ID of the test set envelopes
CERTIFICATION_COUNTERPARTYThe counterparty written in the certification purchase books

Next steps#