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#
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.xmlCada 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.
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 62Es 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:
| Campo | Línea de origen |
|---|---|
id, setId, number | La cabecera CASO <setId>-<n>, por ejemplo 2410011-1 |
documentText, documentType | DOCUMENTO; el tipo se detecta del texto, o es null |
items | Las filas de ítems bajo ITEM |
referenceText, referencedCase | REFERENCIA; el CASO n-m que trae, o null |
reasonText | RAZON REFERENCIA |
motive, transferBy | MOTIVO y TRASLADO POR (guías) |
globalDiscountPercent | DESCUENTO GLOBAL … n% |
currency, exportFields | MONEDA DE LA OPERACION y las líneas propias de exportación (se parsean, nunca se emiten) |
raw | Cada 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 contiene | kind |
|---|---|
LIBRO DE COMPRAS PARA EXENTOS | exemptPurchaseBook |
LIBRO DE COMPRAS | purchaseBook |
LIBRO DE VENTAS | salesBook |
LIBRO DE GUIAS | guideBook |
GUIA | guide |
FACTURA EXENTA o NO AFECTA | exempt |
EXPORTACION | export |
LIQUIDACION | settlement |
FACTURA DE COMPRA o FACTURA COMPRA | purchase |
BASICO | basic |
| cualquier otro | other |
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 plan | Tipo | Emite |
|---|---|---|
basic | BasicSetPlan | Facturas 33, notas de crédito 61, notas de débito 56 |
exempt | ExemptSetPlan | Facturas 34, notas de crédito 61, notas de débito 56; toda línea exenta |
guide | GuideSetPlan | Una guía 52 por caso |
purchase | PurchaseSetPlan | Una factura de compra 46, a lo más una 61 y una 56 |
purchaseBook | PurchaseBookPlan | Documentos de proveedor del libro de compras |
exemptPurchaseBook | PurchaseBookPlan | Lo 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ón | Significado |
|---|---|
issuer | El emisor, como en issueDocument |
receiver | El cliente que nombra cada documento. Las facturas de compra y los traslados internos nombran al emisor |
certificate | El certificado de firma; su titular es quien envía el sobre |
folios | Un FolioSource |
resolution | { number, date }; número 0 en certificación |
now | Reloj, 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 documentoSET, folio 1, la fecha de emisión y la razónCASO <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 digaANULA. - 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
EXENTOse emite exento. "Servicio exento", en minúsculas, no lo es. - Las facturas del set básico llevan
TpoTranVenta1 y el descuento global del caso, con la glosaDESCUENTO 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,TmstFirmayTmstFirmaEnvson 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.
FchEmises la fecha local,TSTEDyTmstFirmason el instante UTC al segundo, el id del sobre es la hora local, y soloTmstFirmaEnves hora de Chile. Corre el proceso conTZ=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:
| Campo | Significado |
|---|---|
period | Período tributario AAAA-MM. El ejecutor lo elige contra el historial del portal |
issuerRut, senderRut | La empresa y quien envía autorizado, normalmente el titular del certificado |
resolution | { number, date } |
certificate | El certificado de firma |
now | Reloj para el id del libro y la hora de la firma |
submission | total (por defecto) o adjustment, para un período que el SII ya tiene |
previousTotals | Totales 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) yreceiver: { 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.creditNoteSignespositive(por defecto) onegative. LanzaINVALID_OPTION(camposet) para cualquier otro set.createCertificationPurchaseBook(plan, input)recibe unPurchaseBookPlan(purchaseBookoexemptPurchaseBook). 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 }) yreceiver. Por posición, la segunda guía se marcamodifiedy la terceravoided(escrita como anulada, total 0 y nada más), salvo quecaseFlagsdiga otra cosa. Una venta lleva el cliente, el neto, la tasa de IVA y el IVA; toda guía su total.notificationFolioes 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#
| Export | Qué contiene |
|---|---|
TEST_SET_KINDS | Los tipos de set, por su nombre en inglés |
TEST_SET_PORTAL_NAMES | Los nombres con que el portal lista los sets y libros (SET BASICO, SET GUIA DE DESPACHO, LIBRO DE VENTAS…) |
TEST_SET_REFERENCE_TYPE | SET, el tipo de documento de la primera referencia de todo documento de un set |
TEST_SET_ENVELOPE_ID | DTE_SetDoc, el SetDTE/@ID de los sobres del set de pruebas |
CERTIFICATION_COUNTERPARTY | La contraparte escrita en los libros de compras de certificación |
Próximos pasos#
- Envía y declara lo que emitiste con el ejecutor.
- Genera los PDF que pide el SII en Muestras impresas.
- Los libros tal como los modela el SDK: Libros.