Saltar al contenido
dte-sdk

Sets de pruebas

La mitad determinista de la certificación. Le entregas el texto que el SII generó para tu empresa y tus CAF y certificado; te devuelve los sobres y libros firmados que pide el SII. No envía nada ni espera nada: enviar, declarar y esperar le corresponden al ejecutor, que llama a estas mismas funciones.

Úsala sola para ver qué va a emitir un set de pruebas antes de tocar un portal, para emitir un set a mano después de que el SII lo rechazó, o para armar tu propio flujo.

El parser se escribió sin ningún texto real de set de pruebas a mano, y las pruebas usan textos sintéticos con datos inventados. Conserva tu primera descarga (el ejecutor la guarda como set-texto.txt) como fixture, y abre un issue si el parser lee mal alguna línea. Nada de esto se ha ejecutado todavía contra el SII: ver Estado.

Uso básico#

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!)
 
// El texto que el SII generó para el postulante
const plan = planCertificationSets(parseTestSetText(readFileSync('set-texto.txt', 'utf8')))
plan.basic?.folios // { 33: 3, 61: 3, 56: 2 }: los folios que necesita el set básico, por tipo de documento
 
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' }
 
// Un asignador por set: entrega los folios de los CAF que le das
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 // el EnvioDTE firmado, listo para subir
 
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)
 
// La simulación reemite cada caso de los cuatro sets, en un sobre, con CAF distintos de los de los 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

Cada set emitido y cada libro es un string XML firmado más los datos que lo rodean. Los subes con uploadEnvelope y uploadBook, o dejas que lo haga el ejecutor.

parseTestSetText#

parseTestSetText(text) parsea el texto que el SII genera para el postulante en TestSet[]. El texto es la página del portal con sus etiquetas convertidas en saltos de línea y sus entidades decodificadas, o un archivo con lo mismo (downloadTestSet ya lo devuelve así). La función nunca lanza: las líneas que la gramática no conoce se ignoran, y un set de tipo desconocido es 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

Es un fragmento sintético con datos inventados, no un texto del SII.

Qué lee#

Un TestSet tiene name, attentionNumber (los dígitos tras NUMERO DE ATENCION), kind, cases, bookDocuments y proportionalityFactor. Un TestCase tiene:

CampoLínea de origen
id, setId, numberLa cabecera CASO <setId>-<n>, por ejemplo 2410011-1
documentText, documentTypeDOCUMENTO; el tipo se detecta del texto, o es null
itemsLas filas de ítems bajo ITEM
referenceText, referencedCaseREFERENCIA; el CASO n-m que trae, o null
reasonTextRAZON REFERENCIA
motive, transferByMOTIVO y TRASLADO POR (guías)
globalDiscountPercentDESCUENTO GLOBAL … n%
currency, exportFieldsMONEDA DE LA OPERACION y las líneas propias de exportación (se parsean, nunca se emiten)
rawCada línea del caso tras su cabecera, recortada

Una fila de ítem se separa por tabuladores o por grupos de dos o más espacios. Con cuatro columnas es nombre, cantidad, unidad y precio, o nombre, cantidad, precio y descuento (10%); con tres es nombre, cantidad y precio; con dos es un nombre y una cantidad, o, cuando la razón dice MODIFICA MONTO, un nombre y un precio nuevo. Una cantidad ausente vale 1. Una línea no es un ítem cuando trae dos puntos, cuando la abre DOCUMENTO, REFERENCIA o RAZON, o cuando ITEM, CANTIDAD o UNITARIO está en su primera parte (la fila de encabezado).

Estas palabras, y EXENTO y ANULA más abajo, se leen solo en mayúsculas, como las escribe el SII. En lo demás la lectura no distingue mayúsculas, pero aquí sí: un ítem llamado "Razones varias" es un ítem, y el valor de una fila de dos partes bajo una razón "modifica monto" es una cantidad, no un precio.

Tipos de set#

El tipo sale del nombre que sigue a SET, gana la primera coincidencia (TEST_SET_KINDS):

El nombre contienekind
LIBRO DE COMPRAS PARA EXENTOSexemptPurchaseBook
LIBRO DE COMPRASpurchaseBook
LIBRO DE VENTASsalesBook
LIBRO DE GUIASguideBook
GUIAguide
FACTURA EXENTA o NO AFECTAexempt
EXPORTACIONexport
LIQUIDACIONsettlement
FACTURA DE COMPRA o FACTURA COMPRApurchase
BASICObasic
cualquier otroother

En los sets de libro de compras, los documentos de proveedor no son casos: cada uno (FACTURA ELECTRONICA 234, NOTA DE CREDITO 12…) pasa a ser un TestBookDocument con su folio, los montos exento y afecto neto, la línea de observación y tres indicadores leídos de ella: IVA USO COMUN, ENTREGA GRATUITA (código de IVA no recuperable 4) y RETENCION TOTAL. La línea FACTOR DE PROPORCIONALIDAD entrega el factor de proporcionalidad.

planCertificationSets#

planCertificationSets(sets) convierte los sets parseados en un CertificationPlan: lo que emite cada set, listo para issueTestSet, y los folios que necesita cada tipo de documento. Por cada tipo gana el último set del texto, y un tipo que el texto no trae queda ausente del plan.

Clave del planTipoEmite
basicBasicSetPlanFacturas 33, notas de crédito 61, notas de débito 56
exemptExemptSetPlanFacturas 34, notas de crédito 61, notas de débito 56; toda línea exenta
guideGuideSetPlanUna guía 52 por caso
purchasePurchaseSetPlanUna factura de compra 46, a lo más una 61 y una 56
purchaseBookPurchaseBookPlanDocumentos de proveedor del libro de compras
exemptPurchaseBookPurchaseBookPlanLo mismo, para contribuyentes exentos

Los sets de libro de ventas y de libro de guías no tienen plan: esos libros salen de los sets básico y de guías ya emitidos. Los sets de exportación y de liquidación se parsean pero nunca se emiten, y los sets desconocidos se dejan fuera.

Cada plan lleva su attentionNumber y, en los sets emitibles, folios: cuántos documentos de cada tipo va a emitir (SetFolios, un record de tipo de documento a cantidad). El ejecutor los suma para saber cuántos folios debe tener. Un PurchaseBookPlan lleva documents y proportionalityFactor, que vale 0,6 cuando el set no trae ninguno.

Las notas llevan su referenceCode (CodRef), derivado del texto de la razón: una razón vacía o que dice ANULA es 1 (anula), CORRIGE con GIRO o TEXTO es 2 (corrige texto), DEVOLUCION o MODIFICA es 3 (corrige montos), cualquier otra cosa es 1. Una guía lleva su transferReason (IndTraslado) según el motivo, 1 cuando está vacío o no se reconoce (TRASLADO entre bodegas es 5, DEVOLUCION 7, CONSIGNACION 3, GRATUITA 4, venta de exportación 9, venta por efectuar 2, VENTA simple 1) y su dispatchType según TRASLADO POR: EMISOR es 2 (por cuenta del emisor), CLIENTE es 1.

createFolioAllocator#

createFolioAllocator(cafs, { usedFolios }) es un FolioSource sobre uno o varios CAF ya parseados: un contador por CAF, gana el primer CAF del tipo con un folio libre, un folio nunca se entrega dos veces, y el CAF devuelto es el que contiene el folio en su rango, así timbra con su propia llave. Cuando todos los CAF del tipo se agotan, take lanza FOLIO_OUT_OF_RANGE con { type }. usedFolios hace que parta después del mayor folio usado de cada CAF.

Un FolioSource es solo { take(type): Promise<{ caf, folio }> }. El asignador vive en memoria; el ejecutor pasa uno propio, respaldado por el registro de folios, para que dos corridas nunca tomen el mismo folio. Implementa el tuyo si guardas los folios en otra parte.

issueTestSet#

issueTestSet(plan, options) emite cada documento del plan de un set y firma su sobre. Acepta los planes básico, exento, de guías y de compra.

OpciónSignificado
issuerEl emisor, como en issueDocument
receiverEl cliente que nombra cada documento. Las facturas de compra y los traslados internos nombran al emisor
certificateEl certificado de firma; su titular es quien envía el sobre
foliosUn FolioSource
resolution{ number, date }; número 0 en certificación
nowReloj, leído una vez por documento y una vez para el sobre

Devuelve un IssuedSet: kind, attentionNumber, cases (cada uno con caseId, type, folio y el document firmado) y el envelope firmado (EnvioDTE, con SetDTE/@ID DTE_SetDoc, el SII como receptor y el titular del certificado como enviador; el emisor cuando el certificado no nombra a nadie). Primero van las facturas (o guías), luego las notas de crédito, luego las de débito.

Lanza MISSING_FIELD (invoices o guides en un plan vacío, referencedCase en una nota cuyo caso no se emitió antes), FOLIO_OUT_OF_RANGE desde la fuente de folios, y los errores habituales de emisión de documentos.

Cómo se escriben los documentos#

Los sets de pruebas siguen las reglas que espera la revisión del SII, y algunos detalles importan porque el SII los compara:

  • La referencia SET. Cada documento lleva una primera referencia con tipo de documento SET, folio 1, la fecha de emisión y la razón CASO <id> (TEST_SET_REFERENCE_TYPE). Una nota agrega una segunda referencia al documento del caso al que refiere, con el código y la razón de referencia del set.
  • Devoluciones al precio original. Una nota que corrige montos (código 3) y trae ítems sin precio toma el precio, y el descuento, del ítem del mismo nombre en el documento original. Una devolución en el set exento toma todos los ítems del original, exentos, a su precio original.
  • Las anulaciones toman los ítems del documento anulado. Una nota de crédito del set básico cuya razón dice ANULA (en mayúsculas; un "anula" en minúsculas deja a la nota con sus propios ítems) toma los ítems del documento que anula. En el set exento una nota siempre conserva sus propios ítems, escritos como líneas exentas, aunque su razón diga ANULA.
  • El emisor como receptor de las facturas y notas de compra, y de los traslados internos (guías con tipo de traslado 5). Los documentos de compra llevan la retención de IVA. Un traslado interno totaliza 0 y mantiene la tasa de IVA junto a ese total cero.
  • Las notas que corrigen un texto (código 2) llevan una línea con el nombre de la razón y precio cero. En los sets básico y de compra, un documento así totaliza 0 y no lleva tasa de IVA; en el set exento y en las guías la tasa se mantiene junto al total cero. Una nota del set básico que termina sin ítems lleva una línea SIN MONTO.
  • Ítems exentos en el set básico. Un ítem de una factura básica cuyo nombre trae EXENTO se emite exento. "Servicio exento", en minúsculas, no lo es.
  • Las facturas del set básico llevan TpoTranVenta 1 y el descuento global del caso, con la glosa DESCUENTO GLOBAL ITEMES AFECTOS.
  • Todo caso del set de guías es una guía, sea cual sea el texto con que se leyó, con su tipo de traslado y su tipo de despacho.
  • El giro de un receptor se corta en 40 caracteres en vez de rechazarse. El del emisor conserva su propio largo, salvo cuando el emisor es el receptor de sus propios documentos.

También es deliberada la forma de escribir fechas, marcas de tiempo y precios, y es distinta de la simulación:

  • La fecha de emisión es el día UTC en que parte el set. TSTED, TmstFirma y TmstFirmaEnv son el instante UTC con milisegundos y sin zona. Una corrida después de las 21:00 en Chile (20:00 en invierno) fecha entonces los documentos con el día siguiente.
  • Los precios unitarios se escriben con seis decimales en las líneas con precio. Las líneas de facturas y notas sin precio no llevan precio, cantidad ni unidad.
  • Un documento cuyas líneas suman cero no lleva tasa de IVA (ver arriba).

issueSimulation#

issueSimulation(plan, options) emite de nuevo cada caso de los cuatro sets, con folios nuevos, en el orden básico, exento, guías y compra, y firma un solo sobre con todos. La fuente de folios debería traer CAF distintos de los de los sets. Recibe las mismas opciones que issueTestSet y devuelve un IssuedSimulation: cases, envelope, types (tipos de documento en orden de aparición) y fewerThanRecommended (true con menos de los diez documentos que pide el SII).

Lanza MISSING_FIELD (cases) cuando el plan no tiene ningún caso.

La simulación se escribe distinto de los sets de pruebas:

  • Sin referencia SET. Una nota lleva una referencia al documento al que refiere, cuando ese caso está en la simulación.
  • La fecha es el día local del proceso. FchEmis es la fecha local, TSTED y TmstFirma son el instante UTC al segundo, el id del sobre es la hora local, y solo TmstFirmaEnv es hora de Chile. Corre el proceso con TZ=America/Santiago.
  • Los precios unitarios son números planos, no seis decimales.
  • Una guía lleva el tipo de traslado y no lleva TpoDespacho. Un traslado interno nombra al emisor como receptor.
  • El descuento global no lleva glosa.
  • Una nota sin ítems lleva una línea con el nombre de su razón. Un ítem de nota de compra sin precio toma el precio del ítem del mismo nombre del original, o 1.
  • Un documento cuyas líneas suman cero totaliza 0 sin tasa de IVA; la guía la conserva.
  • Las partes llevan una lista fija de campos: lo demás que le des al emisor o al receptor (teléfono, correo, sucursal, contacto) queda fuera de los documentos.

Los libros#

Las tres funciones arman y firman los libros. Todas reciben un CertificationBookInput:

CampoSignificado
periodPeríodo tributario AAAA-MM. El ejecutor lo elige contra el historial del portal
issuerRut, senderRutLa empresa y quien envía autorizado, normalmente el titular del certificado
resolution{ number, date }
certificateEl certificado de firma
nowReloj para el id del libro y la hora de la firma
submissiontotal (por defecto) o adjustment, para un período que el SII ya tiene
previousTotalsTotales del período de los envíos anteriores, arrastrados a un ajuste del libro de ventas o de compras; el libro de guías los ignora
  • createCertificationSalesBook(set, input) recibe el set básico emitido (o { kind: 'basic', cases }, como los tiene una corrida retomada) y receiver: { rut, name }. Solo entran las facturas y notas del set básico, con fecha 15 del período, el cliente como contraparte, todo monto presente (0 si falta) y la tasa de IVA 19 en cada línea. creditNoteSign es positive (por defecto) o negative. Lanza INVALID_OPTION (campo set) para cualquier otro set.
  • createCertificationPurchaseBook(plan, input) recibe un PurchaseBookPlan (purchaseBook o exemptPurchaseBook). Una línea por documento de proveedor, con el RUT del propio SII y un nombre de relleno como contraparte (CERTIFICATION_COUNTERPARTY, dato personal de nadie), IVA al 19 % del monto afecto, IVA de uso común, IVA no recuperable en entregas gratuitas, la retención de una factura de compra, y el folio referido de una nota tomado de su observación. En el libro para contribuyentes exentos, IVA no recuperable en toda línea afecta, la factura exenta en papel como tipo 32, y notas que heredan los montos del documento que refieren. La tasa de retención de IVA viaja como fracción (0.19), mientras que toda otra tasa es un porcentaje. Las filas de resumen llevan el crédito de IVA de uso común, redondeado documento por documento y luego sumado, también entre las filas de envíos anteriores.
  • createCertificationGuideBook(set, input) recibe el set de guías emitido (o { kind: 'guide', cases }) y receiver. Por posición, la segunda guía se marca modified y la tercera voided (escrita como anulada, total 0 y nada más), salvo que caseFlags diga otra cosa. Una venta lleva el cliente, el neto, la tasa de IVA y el IVA; toda guía su total. notificationFolio es 3 por defecto. Un ajuste marca solo la guía modificada.

Los libros de ventas y de compras son libros mensuales enviados como envío total. El libro de guías es un libro especial. Los tres devuelven un libro firmado con su xml (los de ventas y compras traen además su periodTotals).

Catálogos#

ExportQué contiene
TEST_SET_KINDSLos tipos de set, por su nombre en inglés
TEST_SET_PORTAL_NAMESLos nombres con que el portal lista los sets y libros (SET BASICO, SET GUIA DE DESPACHO, LIBRO DE VENTAS…)
TEST_SET_REFERENCE_TYPESET, el tipo de documento de la primera referencia de todo documento de un set
TEST_SET_ENVELOPE_IDDTE_SetDoc, el SetDTE/@ID de los sobres del set de pruebas
CERTIFICATION_COUNTERPARTYLa contraparte escrita en los libros de compras de certificación

Próximos pasos#