Skip to content

Exchange and samples

After the simulation, the certification asks for two more things. The exchange: the SII sends your company a set of documents addressed to it, and you answer with three signed files. The printed samples: the PDF of every document the sets and the simulation issued, uploaded as a review the SII's automatic validator checks. runCertification does both; this page covers how, and the functions beneath if you chain them by hand.

Nothing here has been run against the two portals yet. Every wire and wording is still unproven; the exchange answers validate against the official schemas in the tests, and the printed samples have not been reviewed by the SII. The first enrolled run should keep every artefact the store writes under intercambio/ and muestras/ as fixtures. See Status.

Basic usage#

TypeScript
import { readFile } from 'node:fs/promises'
import { runCertification, createExchangeAnswers, EXCHANGE_SET_ARTEFACT } from 'dte-sdk/portal'
import { readPrintedSamples, writePrintedSamples, findEnvelopeFiles } from 'dte-sdk/pdf'
 
// After the simulation the run downloads the exchange set from the exchange portal, answers it and confirms it.
// Then the samples are rendered from the envelopes the store kept and uploaded to the samples portal.
const samplesDir = './certification/samples'
await writePrintedSamples({
  xmlFiles: await findEnvelopeFiles('./certification'),
  outDir: samplesDir,
  transferable: true,
})
const run = await runCertification(deps, { samples: () => readPrintedSamples(samplesDir) })
// run.stopped: 'compliance' once the samples are submitted, or 'rejected' with the SII's reasons in run.detail
 
// The set could not be downloaded: run.stopped is 'exchange' and run.detail names intercambio/set-intercambio.xml.
// Save the set by hand from the exchange portal, then either pass it...
await runCertification(deps, { exchangeSetXml: await readFile('set-intercambio.xml', 'utf8') })
// ...or put it in that artefact of the store and run again
await deps.store.writeArtefact(EXCHANGE_SET_ARTEFACT, await readFile('set-intercambio.xml', 'utf8'))
 
// The answers alone, for a set in hand
const answers = createExchangeAnswers(setXml, {
  companyRut: '76543210-3',
  certificate,
  contact: { name: 'Contacto', mail: 'contacto@example.test' },
})
answers.reception.fileName // 'respuesta-recepcion-envio.xml'
answers.reception.xml // signed, one line, UTF-8, no XML declaration

The exchange#

The set#

The exchange set is the SII's set of documents addressed to the company, an EnvioDTE. parseExchangeSet(setXml) reads it loosely, because the SII's own test companies do not always have valid RUTs and the set is not under your control:

  • The XML is not checked for well-formedness, and tags are read by their full name, so an envelope with a namespace prefix is no envelope. The root is EnvioDTE, EnvioDTEB, EnvioDTETraslado or EnvioBOLETA.
  • The set id is the ID attribute, else an ID child, else SetDTE. The cover's issuer, sender and receiver RUTs are read; per document, its type (TipoDTE or TpoDoc), folio, issue date, issuer and receiver (the cover's when the document names none) and total.
  • A number that does not parse is 0 (1e3 reads as 1000), a missing date is null, and a missing RUT is empty. RUTs are not validated: only their dots are removed and their check letter is upper-cased.
  • Only a plain Documento is read, or one nested inside a second DTE; any other kind reads as empty.
  • It throws XML_PARSE_FAILED when the parser gives up or there is no envelope root, and ENVELOPE_EMPTY when the set holds no document. Nothing else fails.

The three answers#

createExchangeAnswers(setXml, { companyRut, certificate, contact?, now? }) builds and signs the three answers the certification expects. Each is one line of UTF-8 with no XML declaration, and each travels under the file name the portal's upload expects (EXCHANGE_ANSWER_FILES):

KeyFileContent
receptionrespuesta-recepcion-envio.xmlThe reception response: the envelope received, and each document received when its receiver is your company, otherwise marked with the SII's "error en RUT receptor"
receiptsenvio-recibos.xmlThe receipts of Ley 19.983: one Recibo per document, each signed by the certificate holder, inside a signed EnvioRecibos
approvalrespuesta-aprobacion-comercial.xmlThe commercial approval: each document accepted when its receiver is your company, otherwise rejected with CodRchDsc -1

They validate against the official schemas in the tests. The ids carry the clock in milliseconds, and every timestamp is Chile's local time. The contact name is sanitized; its phone number appears in the receipts only. The result also carries set, the exchange set as read.

It throws the codes of parseExchangeSet, and INVALID_RUT (field companyRut).

The exchange step#

runExchange(deps, state, { setXml? }) is the exchange stage. In order:

  1. The set. The one you gave (setXml, which is saved as the artefact), else the one kept from an earlier run, else it is downloaded from the exchange portal and kept as intercambio/set-intercambio.xml.
  2. The answers are generated and kept as artefacts under intercambio/.
  3. One stage on the exchange portal (one warm-up): the user is validated; if the company is in the exchange stage, the three answers are uploaded in order, and the exchange is confirmed. Every portal answer is kept under intercambio/.
  4. The state records the exchange and moves the stage to printedSamples.
OutcomeMeaning
doneThe answers were uploaded and the exchange confirmed
alreadyDoneThe state says it was confirmed, or the portal says the exchange was completed (state P90)
setUnavailableThe set could not be downloaded. detail names the artefact to fill by hand
notInExchangeThe portal says the company is not at the exchange stage. detail is its sentence

A set that cannot be downloaded, for any reason, does not throw: it stops the run with exchange and asks for the set by hand. The page that came back, when there is one, is kept as intercambio/pfe-download-error.html. Save the set into the EXCHANGE_SET_ARTEFACT artefact (or pass exchangeSetXml) and run again.

The answers are generated and uploaded again on every run that reaches the portal; only the set is kept.

The exchange portal operations#

All take the PortalClient, an input with an optional companyRut (the client's by default) and an optional stage, and optional call overrides.

OperationWhat it does
downloadExchangeSetA warm-up, then the download with the company in the query and an empty multipart body. Returns { xml }. The download does not change the application. Throws SII_PORTAL_UNEXPECTED_PAGE (with the redacted page in details.page) when the answer is no set
validateExchangeUserValidates the user with the company and the certificate holder. Returns inExchange, state, completed, message, the hashes used and the raw body. //OK is not required: the answer says whether the company stands in the exchange stage
uploadExchangeAnswersUploads reception, receipts and approval, in that order, each as one multipart file part. A refused page stops the sequence (SII_PORTAL_REJECTED with exchangeUploadRefused and the file); anything not refused counts as uploaded, because the success page has never been recorded
confirmExchangeSends the set validation (its answer is kept, not judged), then three confirmations (follow-up ids 40, 41 and 43), one per uploaded answer, each of which must answer //OK. The three confirmations are what moves the application to the printed samples
withExchangeStageOpens a stage for you to chain the operations by hand (see The portals)

Printed samples#

Writing the PDFs#

writePrintedSamples (from dte-sdk/pdf) renders every document of the envelopes the run kept into SET-PRUEBAS and SET-SIMULACION folders, and readPrintedSamples(dir) reads every .pdf under a directory, the test set folder first, as PrintedSampleFile[] (fileName, bytes). findEnvelopeFiles picks the set and simulation envelopes out of the store directory. The PDF itself is described in Printed samples.

The samples step#

uploadPrintedSamples(deps, state, { samples }) runs the whole stage on the samples portal as one stage with one warm-up: reading, creating, every PDF, submitting, reading again, and the detail. It throws MISSING_FIELD (samples) when you give no PDF. In order:

  1. Read the review. A sentence with no state means the application is not at the printed samples stage (or no test set was requested): outcome blocked, with the portal's sentence.
  2. A review already sent (any state other than INGRESO, RECHAZADO or APROBADO) ends here as alreadySubmitted, and the stage moves to compliance.
  3. Resume or create. A review left in INGRESO (a run cut off while uploading), with a readable id and token, is resumed: every PDF is uploaded again. Otherwise a new review is created. The portal only creates one when there is none or the current one is RECHAZADO or APROBADO.
  4. Upload every PDF in order, saving each name in the state as it goes.
  5. Submit the review and read its state again. A read that fails is logged and leaves the state unknown, because the review was sent anyway.
OutcomeMeaning
submittedThe review was sent. detail is its state, or submitted when it could not be read. The stage moves to compliance
alreadySubmittedA review was already sent. detail is its state
blockedThe portal says the application is not at this stage
rejectedThe SII rejected the review at once. detail carries the SII's reasons (see below)

A review the SII rejects comes back as rejected, with the state saved. The step then reads how the SII filed each PDF (readSamplesDetail) and summarizes it in one sentence: how many documents it recognised as the test set, with their cases, and how many as the simulation, by document type. A set whose documents the SII filed as simulation was not the envelope the SII registered for that set.

The review states the portal names are INGRESO, POR REVISAR, EN REVISION, ENVIADO AL SII, APROBADO and RECHAZADO. Every portal answer is kept under muestras/.

The samples portal operations#

OperationWhat it does
readSamplesReviewReads the company's review: its state, a blocked sentence when the portal names no state, the review id and token, and when it was sent. By default any HTTP status but 200 is unexpected; anyStatus: true reads whatever came, as the step does after a submission
createSamplesReviewCreates a review with the company as sender and provider. Throws SII_PORTAL_REJECTED (samplesReviewRefused) with the SII's sentence
uploadSampleUploads one PDF (revisionId, fileName, bytes). HTTP 200 is success. A 5xx is retried, up to four attempts in all, waiting 2, 4 and 6 s (a 503 on one file once left a review in INGRESO). A 4xx is refused at once (sampleUploadRefused). A name holding a CR, an LF or a double quote throws INVALID_OPTION
submitSamplesReviewSubmits a review by its token. The answer is the same whether the SII accepts or rejects the samples: read the state afterwards
readSamplesDetailReads how the SII filed each PDF of a rejected review. A failure is logged and gives an empty detail: the rejection stands without it
withSamplesStageOpens a stage for you to chain the operations by hand

The GWT-RPC portals#

The exchange portal and the samples portal are web applications that talk GWT-RPC. The SDK includes the codec and the stage mechanics, because the portals have no other API.

One stage, one warm-up. The run enters each portal once per stage of work: once for the download of the exchange set, once for the validation, the three uploads and the confirmation, and once for the whole of the printed samples. A stage makes sure of the session (a cached one is checked and replaced when dead), warms the portal up with a request to its base (following one redirect), and takes the hashes once. The cookies the warm-up sets live only for that stage: they neither join the shared session nor travel to another portal. withExchangeStage and withSamplesStage open a stage for a caller that chains the operations by hand (pass it as stage in their input); alone, each operation opens its own.

These calls are sent once and wait as long as it takes. No retry (but for a PDF upload the portal answers with a 5xx) and no time-out of their own, so a portal that does not answer holds the call. Pass timeoutMs in the call overrides to bound them. The answers are read as UTF-8. Inside a stage each call reads its own answer whatever its status: the client neither throws on a 502, 503 or 504 nor looks for the firewall's page there, so an error on the user validation or on the set validation does not stop the exchange. Each portal receives only the headers of its stage (a browser-like User-Agent, Accept and keep-alive, and for the samples portal the origin and referer of the portal itself) plus what each call names.

The hashes. A GWT portal requires two hashes on every call: the permutation (X-GWT-Permutation) and the serialization policy. The SII changes both when it redeploys a portal. The exchange portal is called with the pair known to work (EXCHANGE_PORTAL.known), never looked up. The samples portal's hashes are discovered once on the way in, from the module's script, with the known pair as the fallback when anything fails. Pass gwt: { permutation, policy } in the input of any operation to override either: it wins over the known pair and over any lookup.

The codec#

FunctionWhat it does
gwtBody(call)The request body of a GwtCall (moduleBase, policy, service, method, parameters): the protocol header, the string table, then the body, every item followed by a pipe. Parameters are strings, boxed integers and longs, booleans, nulls, or objects with their fields in serialization order
gwtAnswer(body)Reads an answer: ok (the body starts with //OK), the string table, the first message that reads as a sentence to the user, and the longs (every single-quoted token decoded)
encodeGwtLong(value)A number in the GWT base 64 alphabet, most significant digit first (264872 is BAqo)
decodeGwtLong(token)The number a token holds, or null when a character is not in the alphabet
discoverGwtHashes(context, input)Looks the hashes of a portal up from its module script: the known pair stands when its permutation is listed, otherwise the first one listed is the permutation and its cache page gives the policy. Any failure keeps the known pair. The stage uses it; you rarely need it

You need the codec only to call a GWT method the SDK does not wrap. The portal operations above already do.

Catalogs#

ExportWhat it holds
EXCHANGE_PORTAL, SAMPLES_PORTALA GwtPortal each: the module base, RPC service path and interface, module name, and the known hash pair
EXCHANGE_ANSWER_KEYSreception, receipts, approval, in upload order
EXCHANGE_ANSWER_FILESThe file name of each answer
EXCHANGE_SET_ARTEFACTintercambio/set-intercambio.xml, the artefact that holds the exchange set

Next steps#