Documentos
Un documento parte como datos planos que describes en inglés legible (issuer.businessName, items[0].unitPrice) y termina como el XML que espera el SII. createDocument es el primer paso: valida lo que le entregas, normaliza RUT y textos, numera las líneas, calcula los totales y devuelve un Document. issueDocument ejecuta todo el flujo (crear, timbrar, firmar) en una sola llamada. buildDocumentXml y el codec (toSiiDocument, fromSiiDocument) convierten el modelo a etiquetas del SII y de vuelta.
Todo lo de esta página vive en la entrada dte-sdk y corre en Node, Bun y Deno.
Los documentos, timbres, firmas y sobres están probados contra datos sintéticos y contra los esquemas oficiales, pero todavía no se han enviado al SII real. Mira Estado para saber qué ha pasado por el SII real y qué no.
Crear un documento#
import { createDocument } from 'dte-sdk'
const document = createDocument({
type: 33,
folio: 1,
issuer: {
rut: '76543210-3',
businessName: 'Comercial Andina SpA',
businessActivity: 'Software',
address: 'Av. Los Aromos 1540',
commune: 'Santiago',
activityCode: 620200,
},
receiver: {
rut: '12.345.678-5',
businessName: 'Comercializadora Rapel Ltda',
businessActivity: 'Comercio',
address: 'Pasaje Los Olmos 88',
commune: 'Providencia',
},
items: [
{ name: 'Plan anual', quantity: 1, unitPrice: 100000 },
{ name: 'Horas de consultoría', quantity: 3, unitPrice: 15000, discountPercent: 10 },
],
})
document.id // 'DTE_T33F1'
document.header.receiver.rut // '12345678-5'
document.items[1] // { lineNumber: 2, name: 'Horas de consultoría', quantity: 3, unitPrice: 15000, discountPercent: 10, discountAmount: 4500, amount: 40500 }
document.header.totals // { netAmount: 140500, vatRate: 19, vatAmount: 26695, totalAmount: 167195 }createDocument es pura: la misma entrada da la misma salida, nunca modifica su argumento y no toca red, archivos ni secretos. Aquí el folio no se compara con ningún CAF; eso ocurre al timbrar el documento (mira Timbre).
Emitir en una sola llamada#
issueDocument recibe un DocumentInput (o un Document que ya creaste), un CAF parseado y un certificado, y devuelve un documento firmado:
import { readFileSync } from 'node:fs'
import { issueDocument, loadCertificateFromPkcs12, parseCaf } from 'dte-sdk'
const certificate = loadCertificateFromPkcs12(readFileSync('firma.pfx'), process.env.PFX_PASSWORD!)
const caf = parseCaf(readFileSync('caf-33.xml', 'utf8'))
const signed = issueDocument({ document: input, caf, certificate })
signed.xml // <DTE> firmado con su TED, sin declaración XML
signed.ted // el timbre electrónico
signed.signedAt // hora de Chile de la firma| Opción | Significado |
|---|---|
document | Un DocumentInput (pasa por createDocument) o un Document ya creado. |
caf | El CAF parseado que cubre el tipo, el emisor y el folio. |
certificate | El certificado del emisor que firma el documento. |
now | Reloj para la fecha de emisión, TSTED y TmstFirma. Los tests lo usan para tener una salida determinista. |
checkCafExpiry | Chequear el vencimiento del CAF (por defecto true). Mira Timbre. |
timestamp | TSTED y TmstFirma exactamente como se entregan, en vez de la hora de Chile de now. |
El resultado es un SignedDocument: un Document más ted, xml, signedAt y signature. Los pasos que ejecuta son los documentados en Timbre y Firma; llámalos por separado cuando necesites detenerte a la mitad (para inspeccionar el TED, por ejemplo).
Tipos de documento#
El SDK soporta nueve códigos TipoDTE. DOCUMENT_TYPES los nombra en inglés, DOCUMENT_TYPE_VALUES los lista en orden ascendente e isDocumentType(value) es una guarda de tipo.
| Código | Clave de DOCUMENT_TYPES | Nombre oficial (documentTypeName) | Boleta | Exento | Nota | Exige receptor | Transferible |
|---|---|---|---|---|---|---|---|
| 33 | invoice | Factura Electrónica | no | no | no | sí | sí |
| 34 | exemptInvoice | Factura Exenta Electrónica | no | sí | no | sí | sí |
| 39 | receipt | Boleta Electrónica | sí | no | no | no | no |
| 41 | exemptReceipt | Boleta Exenta Electrónica | sí | sí | no | no | no |
| 43 | settlementInvoice | Liquidación Factura Electrónica | no | no | no | sí | sí |
| 46 | purchaseInvoice | Factura de Compra Electrónica | no | no | no | sí | sí |
| 52 | dispatchGuide | Guía de Despacho Electrónica | no | no | no | sí | sí |
| 56 | debitNote | Nota de Débito Electrónica | no | no | sí | sí | no |
| 61 | creditNote | Nota de Crédito Electrónica | no | no | sí | sí | no |
Las columnas son las respuestas de los helpers del mismo nombre:
| Helper | true para | Qué significa |
|---|---|---|
isReceipt(type) | 39, 41 | Boletas: no exigen receptor identificado, no llevan TasaIVA, tienen su propia forma de referencia y se envían por REST. |
isExemptType(type) | 34, 41 | Todas las líneas son exentas. |
isNote(type) | 56, 61 | Notas de débito y de crédito, que el SII espera que referencien otro documento. |
requiresReceiver(type) | todo salvo 39 y 41 | El tipo exige un receptor identificado. |
isTransferable(type) | 33, 34, 43, 46, 52 | El documento impreso lleva copia cedible y el recuadro de acuse de recibo de la Ley 19.983. |
documentTypeName(type, variant) devuelve el nombre oficial en español. La variante es 'long' (por defecto, como en la tabla), 'short' (mayúsculas sin tildes: NOTA CREDITO) o 'printed' (mayúsculas, como lo escribe el manual de muestras impresas del SII: NOTA DE CRÉDITO ELECTRÓNICA).
createDocument no obliga a que una nota lleve una referencia. El SII espera
que toda nota de débito o de crédito referencie el documento que afecta, así
que agrega references tú mismo (mira Notas de crédito y de débito).
El modelo DocumentInput#
DocumentInput es lo que le entregas a createDocument. Los campos obligatorios van en negrita en la primera tabla; todo lo demás es opcional. Los campos nombrados con etiquetas del SII en las tablas siguientes son las etiquetas que escribe el codec (TipoDTE, MntTotal…), así puedes seguir un campo desde tu entrada hasta el XML.
| Campo | Tipo | Significado |
|---|---|---|
type | DocumentType | TipoDTE: uno de 33, 34, 39, 41, 43, 46, 52, 56, 61. |
folio | number | Folio: un entero positivo autorizado por un CAF. |
issueDate | string | FchEmis, AAAA-MM-DD (también se aceptan DD-MM-AAAA y DD/MM/AAAA). Por defecto, hoy en Chile. |
issuer | IssuerInput | Quién emite el documento. |
receiver | ReceiverInput | Quién lo recibe. Obligatorio en todos los tipos salvo 39 y 41. |
items | LineItemInput[] | Las líneas de detalle. Al menos una. |
references | ReferenceInput[] | Referencias a otros documentos (todos los tipos salvo boletas). |
receiptReferences | ReceiptReferenceInput[] | Referencias de una boleta (solo 39 y 41). |
globalAdjustments | GlobalAdjustmentInput[] | Descuentos y recargos globales (DscRcgGlobal). |
subtotals | SubtotalInfo[] | Subtotales informativos (SubTotInfo), escritos tal como vienen. |
commissions | CommissionLineInput[] | Líneas de comisión de una liquidación factura (43). |
transport | TransportInput | Datos de transporte, para guías de despacho (52). |
options | HeaderOptions | Campos opcionales de IdDoc: forma de pago, vencimiento, tipos de despacho y de traslado… |
pricesIncludeVat | boolean | Los precios unitarios ya incluyen IVA (MntBruto=1). |
withholdVat | boolean | Retener todo el IVA (ImptoReten 15). true por defecto en facturas de compra (46). |
totals | Totals | Totales explícitos (avanzado). Se calculan desde las líneas si se omiten. |
Emisor#
El emisor es la empresa que emite el documento (Emisor). El RUT puede venir en cualquier forma (76.543.210-3, 76543210-3, 765432103); rut.ts lo valida y el documento lo guarda como 76543210-3.
| Campo | Obligatorio | Etiqueta SII | Límite | Notas |
|---|---|---|---|---|
rut | sí | RUTEmisor | Validado con rut.ts. | |
businessName | sí | RznSoc (RznSocEmisor en boletas) | 100 | Razón social. |
businessActivity | sí | GiroEmis (GiroEmisor en boletas) | 80 | Giro comercial. |
address | sí | DirOrigen | 70 | |
commune | sí | CmnaOrigen | 20 | |
city | no | CiudadOrigen | 20 | Por defecto, la comuna. |
activityCode | no | Acteco | Código de actividad económica del SII (un número). | |
phone | no | Telefono | No se escribe en boletas. | |
email | no | CorreoEmisor | No se escribe en boletas. | |
branch | no | Sucursal | 20 | Nombre de la sucursal. |
siiBranchCode | no | CdgSIISucur | Código de sucursal inscrito en el SII. | |
salespersonCode | no | CdgVendedor | 60 | No se escribe en boletas. |
Receptor#
| Campo | Obligatorio | Etiqueta SII | Límite | Notas |
|---|---|---|---|---|
rut | sí (si se entrega un receptor) | RUTRecep | Validado con rut.ts. | |
businessName | sí (si se entrega un receptor) | RznSocRecep | 100 | |
address | sí, salvo en boletas | DirRecep | 70 | |
commune | sí, salvo en boletas | CmnaRecep | 20 | |
businessActivity | no | GiroRecep | 40 | No se escribe en boletas. |
internalCode | no | CdgIntRecep | 20 | |
contact | no | Contacto | 80 | |
email | no | CorreoRecep | No se escribe en boletas. | |
city | no | CiudadRecep | 20 | |
postalAddress | no | DirPostal | 70 | No se escribe en boletas. |
postalCommune | no | CmnaPostal | 20 | No se escribe en boletas. |
postalCity | no | CiudadPostal | 20 | No se escribe en boletas. |
En una boleta (39, 41) puedes omitir receiver. El documento lleva entonces al consumidor final genérico: RUT 66666666-6, nombre Consumidor Final, dirección Sin Direccion, comuna Santiago. Un receptor que sí entregues en una boleta se conserva tal cual, solo con lo que escribiste (su nombre sigue siendo obligatorio).
Ítems#
Cada elemento de items es un LineItemInput. El SDK numera las líneas desde 1 (NroLinDet) y calcula amount (MontoItem) salvo que lo entregues.
| Campo | Etiqueta SII | Límite | Significado |
|---|---|---|---|
name (obligatorio) | NmbItem | 80 | Nombre del ítem. |
description | DscItem | 1000 | Descripción. |
quantity | QtyItem | Cantidad. Cuenta como 1 en el monto cuando se omite, pero no se escribe. | |
unit | UnmdItem | 4 | Unidad de medida. |
unitPrice | PrcItem | Precio unitario, neto o con IVA según pricesIncludeVat. Cuenta como 0 cuando se omite. | |
unitPriceDecimals | PrcItem | Decimales con que se escribe el precio, un entero de 0 a 6. Sin él, el número se escribe tal cual. | |
discountPercent | DescuentoPct | Descuento de la línea en porcentaje. El SDK también escribe DescuentoMonto. | |
surchargePercent | RecargoPct | Recargo de la línea en porcentaje. El SDK también escribe RecargoMonto. | |
exempt | IndExe | Atajo para exemptIndicator: 1. | |
exemptIndicator | IndExe | 1 exento, 2 no facturable, 3 garantía por depósito de envases, 4 no constituye venta, 5 ítem a rebajar, 6 no facturable negativo (EXEMPT_INDICATORS). | |
codes | CdgItem | Hasta 5 códigos: { type: 'INT1', value: 'T-100' }. | |
additionalTaxCodes | CodImpAdic | Hasta 2 códigos de impuesto adicional (números). | |
settledDocumentType | TpoDocLiq | 3 | Tipo del documento liquidado. Obligatorio en cada línea de una liquidación factura (43). |
referenceQuantity | QtyRef | Cantidad de referencia. | |
referenceUnit | UnmdRef | 4 | Unidad de referencia. |
referencePrice | PrcRef | Precio de referencia. | |
amount | MontoItem | Monto explícito de la línea. Omite el cálculo cantidad por precio. |
Cómo se calcula el monto de una línea (redondeo por línea, descuentos, recargos) está en Totales.
Un ítem es exento cuando pones exempt: true, fijas un exemptIndicator, o cuando el tipo de documento mismo es exento (34 y 41 dan IndExe=1 a cada línea salvo que fijes otro indicador). El SDK no revisa el largo de codes ni de additionalTaxCodes: se escriben como los entregas.
Los precios con decimales necesitan unitPriceDecimals, porque el monto siempre es en pesos enteros pero el SII lee PrcItem como está escrito:
{ name: 'Tornillo', quantity: 3, unit: 'UN', unitPrice: 12.5, unitPriceDecimals: 2 }
// PrcItem se escribe como 12.50, MontoItem como 38 (round(3 × 12.5) = round(37.5))Notas de crédito y de débito con referencias#
references vincula el documento con otros. En las notas de crédito y de débito (61, 56) es la forma de decir qué documento anulas o corriges.
import { createDocument, REFERENCE_CODES } from 'dte-sdk'
const creditNote = createDocument({
type: 61,
folio: 1,
issuer,
receiver,
items: [{ name: 'Plan anual', quantity: 1, unitPrice: 100000 }],
references: [
{
documentType: 33,
folio: 1,
date: '2026-11-01',
code: REFERENCE_CODES.cancels,
reason: 'Anula factura',
},
],
})
creditNote.references
// [{ lineNumber: 1, documentType: 33, folio: '1', date: '2026-11-01', code: 1, reason: 'Anula factura' }]
creditNote.header.totals // { netAmount: 100000, vatRate: 19, vatAmount: 19000, totalAmount: 119000 }| Campo | Etiqueta SII | Límite | Significado |
|---|---|---|---|
documentType | TpoDocRef | Tipo del documento referenciado (un número), o el string 'SET' en los sets de pruebas de certificación. | |
folio | FolioRef | Folio del documento referenciado (número o string; se guarda como string). | |
date | FchRef | Fecha del documento referenciado, AAAA-MM-DD (o DD-MM-AAAA). | |
code | CodRef | REFERENCE_CODES: cancels (1, anula), correctsText (2, corrige texto), correctsAmounts (3, corrige montos). | |
reason | RazonRef | 90 | Razón de la referencia. |
global | IndGlobal | Una referencia global (IndGlobal=1). | |
otherRut | RUTOtr | RUT de otro contribuyente, validado con rut.ts. |
Una nota de crédito que no da derecho a descontar débito fiscal de IVA fija options: { noDebitReduction: 1 } (IndNoRebaja).
Referencias de una boleta#
Las boletas tienen su propio esquema para Referencia: sin fecha, sin indicador global y sin RUT de otro contribuyente, y CodRef es texto libre propio de la empresa. Por eso una boleta usa receiptReferences en vez de references, y las dos nunca se mezclan: references en una boleta, o receiptReferences en cualquier otro tipo, lanza INVALID_OPTION con details.field indicando el campo.
const receipt = createDocument({
type: 39,
folio: 5,
issuer,
items: [{ name: 'Café', unitPrice: 1500 }],
pricesIncludeVat: true,
receiptReferences: [{ code: 'SET', reason: 'CASO-1' }],
})
receipt.receiptReferences // [{ lineNumber: 1, code: 'SET', reason: 'CASO-1' }]| Campo | Etiqueta SII | Límite | Significado |
|---|---|---|---|
code | CodRef | 18 | El código de referencia propio de la empresa; SET en el set de pruebas de certificación. |
reason | RazonRef | 90 | Razón. |
documentType | TpoDocRef | 3 | Tipo del documento referenciado (número o string, se guarda como string). |
folio | FolioRef | 18 | Folio del documento referenciado (número o string, se guarda como string). |
Los cuatro son opcionales, y los textos vacíos se descartan.
Descuentos y recargos globales#
globalAdjustments son descuentos o recargos sobre todo el documento (DscRcgGlobal):
globalAdjustments: [
{ kind: 'discount', valueType: 'percent', value: 10, description: 'Descuento cliente' },
]| Campo | Etiqueta SII | Límite | Significado |
|---|---|---|---|
kind | TpoMov | 'discount' (D, descuento) o 'surcharge' (R, recargo). | |
valueType | TpoValor | 'percent' (%) o 'amount' ($). | |
value | ValorDR | El valor, en la unidad de valueType. | |
description | GlosaDR | 45 | Glosa. |
exemptIndicator | IndExeDR | 1 aplica el ajuste al monto exento, 2 al no facturable. Omítelo para el monto afecto. |
Los totales los consideran; mira Totales.
Subtotales#
subtotals es una lista de SubtotalInfo (SubTotInfo) que el SDK escribe tal como viene, sin calcular nada: number (NroSTI), totalAmount (ValSubtotSTI) y, opcionalmente, description (GlosaSTI), order (OrdenSTI), netAmount, vatAmount, additionalTaxAmount, exemptAmount y las lines (LineasDeta) que incluye.
Comisiones (liquidación factura 43)#
Una liquidación factura (43) la emite un agente por cuenta de un mandante. Cada línea necesita settledDocumentType, y commissions lleva las líneas de comisión y otros cargos (Comisiones):
const settlement = createDocument({
type: 43,
folio: 1,
issuer,
receiver,
items: [{ name: 'Venta liquidada', unitPrice: 10000, settledDocumentType: '33' }],
commissions: [
{ kind: 'commission', description: 'Comisión 10%', rate: 10, netValue: 1000, vatValue: 190 },
],
})| Campo | Etiqueta SII | Límite | Significado |
|---|---|---|---|
kind | TipoMovim | 'commission' (C, comisión) u 'otherCharge' (O, otro cargo). | |
description | Glosa | 60 | Glosa (obligatoria). |
rate | TasaComision | Tasa de la comisión en porcentaje. | |
netValue | ValComNeto | Valor neto. | |
exemptValue | ValComExe | Valor exento. | |
vatValue | ValComIVA | IVA de la comisión. |
calculateTotals no deriva los totales de comisiones, el IVA propio (IVAProp) ni el IVA de terceros (IVATerc) de una liquidación factura. Calcúlalos tú y entrégalos en totals (mira Totales explícitos). El elemento firmado de una liquidación factura es Liquidacion, no Documento.
Transporte (guía de despacho 52)#
Una guía de despacho (52) dice qué se mueve, hacia dónde y por qué. Dos opciones del encabezado dan el motivo y quién despacha (options.transferType, options.dispatchType), y transport lleva el vehículo, el transportista, el chofer y el destino:
import { createDocument, DISPATCH_TYPES, TRANSFER_TYPES } from 'dte-sdk'
const guide = createDocument({
type: 52,
folio: 1,
issuer,
receiver,
items: [{ name: 'Pallet', quantity: 4, unit: 'UN', unitPrice: 25000 }],
options: {
transferType: TRANSFER_TYPES.sale,
dispatchType: DISPATCH_TYPES.byIssuerToReceiver,
},
transport: {
licensePlate: 'ABCD12',
carrierRut: '77654321-7',
driver: { rut: '10000004-0', name: 'Juan Perez' },
destinationAddress: 'Pasaje Los Olmos 88',
destinationCommune: 'Providencia',
},
})
guide.header.options // { transferType: 1, dispatchType: 2 }
guide.header.transport.driver // { rut: '10000004-0', name: 'Juan Perez' }
guide.header.totals // { netAmount: 100000, vatRate: 19, vatAmount: 19000, totalAmount: 119000 }Campo de transport | Etiqueta SII | Límite | Significado |
|---|---|---|---|
licensePlate | Patente | 8 | Patente del vehículo. |
carrierRut | RUTTrans | RUT del transportista, validado con rut.ts. | |
driver.rut | RUTChofer | RUT del chofer, validado con rut.ts. | |
driver.name | NombreChofer | 30 | Nombre del chofer (obligatorio cuando se entrega driver). |
destinationAddress | DirDest | 70 | Dirección de destino. |
destinationCommune | CmnaDest | 20 | Comuna de destino. |
destinationCity | CiudadDest | 20 | Ciudad de destino. |
TRANSFER_TYPES (IndTraslado): sale 1 (venta), pendingSale 2 (ventas por efectuar), consignment 3 (consignación), freeDelivery 4 (entrega gratuita), internalTransfer 5 (traslado interno), otherNonSale 6 (otros no venta), return 7 (devolución), exportTransfer 8 (traslado para exportación), exportSale 9 (venta para exportación). DISPATCH_TYPES (TipoDespacho): byReceiver 1 (por cuenta del receptor), byIssuerToReceiver 2 (por cuenta del emisor a instalaciones del receptor), byIssuerToOther 3 (por cuenta del emisor a otras instalaciones).
Una guía de un traslado sin precio puede omitir unitPrice: sus totales son { vatRate: 19, totalAmount: 0 }.
Opciones del encabezado#
options contiene los campos opcionales de IdDoc. Solo se escriben los que fijas, en el orden del esquema. La mayoría no las necesitarás nunca; las útiles son paymentTerms, dueDate, payments, transferType y dispatchType.
import { createDocument, PAYMENT_TERMS } from 'dte-sdk'
createDocument({
type: 33,
folio: 3,
issuer,
receiver,
items: [{ name: 'Plan anual', unitPrice: 100000 }],
options: {
paymentTerms: PAYMENT_TERMS.credit,
dueDate: '2026-12-15',
payments: [{ date: '2026-12-15', amount: 119000, note: 'Pago único' }],
},
})| Opción | Etiqueta SII | Significado |
|---|---|---|
noDebitReduction | IndNoRebaja | Nota de crédito sin derecho a descontar débito (1). |
dispatchType | TipoDespacho | DISPATCH_TYPES (1 a 3). |
transferType | IndTraslado | TRANSFER_TYPES (1 a 9). |
printFormat | TpoImpresion | 'N' normal o 'T' ticket. |
serviceIndicator | IndServicio | SERVICE_INDICATORS: 1 servicios periódicos, 2 servicios periódicos domiciliarios, 3 ventas y servicios, 4 espectáculos. Por defecto 3 en boletas. |
grossAmounts | MntBruto | Los montos de las líneas incluyen IVA (1). Lo fija pricesIncludeVat por ti. |
purchaseTransactionType | TpoTranCompra | Tipo de transacción de compra (1 a 7). |
saleTransactionType | TpoTranVenta | Tipo de transacción de venta (1 a 4). |
paymentTerms | FmaPago | PAYMENT_TERMS: cash 1 (contado), credit 2 (crédito), free 3 (sin costo). |
exportPaymentTerms | FmaPagExp | Forma de pago de exportación. |
settlementDate | FchCancel | Fecha de cancelación. |
settledAmount | MntCancel | Monto cancelado. |
outstandingBalance | SaldoInsol | Saldo insoluto. |
payments | MntPagos | Cuotas: { date, amount, note? } (FchPago, MntPago, GlosaPagos). |
billingPeriod | PeriodoDesde, PeriodoHasta | { from, to }. |
paymentMedium | MedioPago | Medio de pago. |
paymentAccountType | TpoCtaPago | Tipo de cuenta de pago. |
paymentAccountNumber | NumCtaPago | Número de cuenta de pago. |
paymentBank | BcoPago | Banco de pago. |
paymentTermsCode | TermPagoCdg | Código de términos de pago. |
paymentTermsNote | TermPagoGlosa | Glosa de términos de pago. |
paymentTermsDays | TermPagoDias | Días de los términos de pago. |
dueDate | FchVenc | Fecha de vencimiento. |
netAmountsIndicator | IndMntNeto | Indicador de montos netos. |
El SDK escribe las opciones como las entregas, sin validarlas ni medirlas. Las fechas dentro de options no se normalizan: escríbelas como AAAA-MM-DD.
Precios con IVA incluido#
Cuando tus precios ya incluyen IVA, indícalo con pricesIncludeVat: true. El SDK deriva entonces el neto y el IVA desde el monto bruto y, en todos los tipos salvo boletas, escribe MntBruto=1 (options.grossAmounts). Las boletas no llevan MntBruto, pero los totales se derivan igual:
const receipt = createDocument({
type: 39,
folio: 1,
issuer,
items: [{ name: 'Café', quantity: 2, unitPrice: 1500 }],
pricesIncludeVat: true,
})
receipt.header.receiver // { rut: '66666666-6', businessName: 'Consumidor Final', address: 'Sin Direccion', commune: 'Santiago' }
receipt.header.options // { serviceIndicator: 3 }
receipt.header.totals // { netAmount: 2521, vatAmount: 479, totalAmount: 3000 }El SDK fija grossAmounts automáticamente solo cuando el documento tiene monto afecto: una factura solo con líneas exentas y pricesIncludeVat no lleva MntBruto.
Retención (factura de compra 46)#
Una factura de compra (46) la emite el comprador, que retiene el IVA en vez de pagárselo al vendedor. El emisor es el comprador; el receptor es el vendedor. Por defecto se retiene todo el IVA:
const purchase = createDocument({
type: 46,
folio: 1,
issuer,
receiver: {
rut: '77654321-7',
businessName: 'Distribuidora Maipo Ltda',
businessActivity: 'Reciclaje',
address: 'Camino Lo Boza 410',
commune: 'Pudahuel',
},
items: [{ name: 'Chatarra', quantity: 10, unitPrice: 1000 }],
})
purchase.header.totals
// {
// netAmount: 10000,
// vatRate: 19,
// vatAmount: 1900,
// withholdings: [{ taxCode: 15, rate: 19, amount: 1900 }],
// totalAmount: 10000,
// }totalAmount deja fuera el IVA retenido. Pasa withholdVat: false para emitir una 46 sin retención, y withholdVat: true en una nota de débito o de crédito que afecta una factura de compra, para que la nota retenga el IVA de la misma forma. En cualquier otro tipo withholdVat es false por defecto.
Totales explícitos#
totals reemplaza los totales calculados por el objeto que entregues (un Totals). Úsalo cuando necesites montos que el SDK no deriva (totales de comisiones de una liquidación factura, IVAProp, IVATerc, MontoPeriodo, SaldoAnterior, VlrPagar) o una tasa de IVA distinta de 19. El SDK confía en él: no lo contrasta con las líneas. Mira Totales para los campos.
Qué valida#
createDocument falla temprano en vez de dejar que el SII rechace después el sobre completo (el SII rechaza un sobre cuando uno de sus documentos falla el esquema). Entre otras cosas:
- Revisa el tipo (
INVALID_DOCUMENT_TYPE) y el folio, un entero positivo (INVALID_FOLIO). - Toma la fecha de emisión como viene o la de hoy en Chile, y exige que sea una fecha real (
INVALID_DATE). - Normaliza cada RUT con rut.ts (
INVALID_RUT, condetails.fieldcomoissuer.rutoreferences[0].otherRut). Se valida el dígito verificador; el valor nunca se copia al mensaje del error. - Sanitiza los textos libres: quita apóstrofes y comillas dobles (rompen las validaciones de firma del SII), convierte guiones y puntos suspensivos tipográficos a ASCII, elimina todo lo que está fuera de ISO-8859-1 (emoji, comillas curvas) y recorta espacios.
"Premium" — grandequeda comoPremium - grande. - Exige los largos del esquema sobre el texto ya sanitizado (
FIELD_TOO_LONG). - Exige lo que el tipo necesita: el receptor (
MISSING_FIELD,details.field: 'receiver'), su dirección y comuna en todos los tipos salvo boletas, al menos un ítem, el nombre del ítem, la razón social, el giro, la dirección y la comuna del emisor, ysettledDocumentTypeen cada línea de una 43. - Rechaza números no finitos en los montos (
INVALID_AMOUNT) y ununitPriceDecimalsfuera de 0 a 6 (INVALID_OPTION). - Numera ítems, referencias, ajustes y comisiones desde 1.
- Calcula los montos de línea y los totales (mira Totales), aplica los defaults de boleta y fija
MntBruto.
| Código | Cuándo |
|---|---|
INVALID_DOCUMENT_TYPE | type no es uno de los nueve códigos soportados. |
INVALID_FOLIO | folio no es un entero positivo. |
INVALID_DATE | Una fecha (issueDate, la date de una referencia) no es una fecha de calendario real. |
INVALID_RUT | Un RUT no pasa rut.ts. details.field dice cuál. |
MISSING_FIELD | Falta un campo obligatorio o está vacío. details.field es su ruta, como items[0].name. |
FIELD_TOO_LONG | Un texto sanitizado supera su límite. details trae field (la etiqueta del SII), maxLength, length y value. |
INVALID_AMOUNT | Una cantidad, precio, porcentaje o monto no es un número finito. |
INVALID_OPTION | references en una boleta, receiptReferences en otro tipo, o un unitPriceDecimals inválido. |
import { createDocument, isDteError } from 'dte-sdk'
try {
createDocument({ ...input, items: [{ name: 'x'.repeat(81), unitPrice: 1 }] })
} catch (error) {
if (isDteError(error) && error.code === 'FIELD_TOO_LONG') {
error.message // 'Field exceeds the maximum length allowed by the SII schema (field=NmbItem, maxLength=80, length=81)'
error.details // { field: 'NmbItem', maxLength: 80, length: 81, value: 'xxxxxxxx…' }
}
}Los mensajes nunca incluyen RUT, nombres ni texto de ítems; el valor ofensivo vive en details. Si registras details en un log, ten presente que puede contener datos personales. Mira Errores.
Límites de campos#
FIELD_LIMITS es la tabla de valores xs:maxLength que createDocument exige, por etiqueta del SII:
| Etiqueta | Máx | Etiqueta | Máx |
|---|---|---|---|
RznSoc | 100 | NmbItem | 80 |
GiroEmis | 80 | DscItem | 1000 |
DirOrigen | 70 | UnmdItem, UnmdRef | 4 |
CmnaOrigen, CiudadOrigen, Sucursal | 20 | TpoDocLiq | 3 |
CdgVendedor | 60 | RazonRef | 90 |
RznSocRecep | 100 | CodRef, FolioRef | 18 |
GiroRecep | 40 | TpoDocRef | 3 |
Contacto | 80 | GlosaDR | 45 |
DirRecep, DirPostal | 70 | Glosa | 60 |
CmnaRecep, CiudadRecep | 20 | Patente | 8 |
CmnaPostal, CiudadPostal | 20 | NombreChofer | 30 |
CdgIntRecep | 20 | DirDest | 70 |
CmnaDest, CiudadDest | 20 |
FIELD_LIMITS también lista CorreoRecep (80) y GlosaSTI (40), pero hoy createDocument no revisa el correo del receptor ni la glosa de los subtotales. Los campos que no son texto libre (teléfono, correo, activityCode, codes, options, subtotals) se escriben como los entregas.
Fecha de emisión y reloj#
issueDate acepta AAAA-MM-DD, DD-MM-AAAA y DD/MM/AAAA (como muestran las fechas el portal y las resoluciones del SII) y se guarda como AAAA-MM-DD. Cuando la omites, el SDK usa la fecha de hoy en Chile (America/Santiago, con horario de verano), nunca la zona horaria del proceso. Importa en un servidor en UTC: a las 23:30 en Chile del 15 de noviembre, la fecha UTC ya es el 16.
Toda función que depende de la hora recibe el reloj como opción, así los tests son deterministas:
const document = createDocument(input, { now: new Date('2026-11-15T18:30:00Z') })
document.header.issueDate // '2026-11-15'issueDocument recibe el mismo now y se lo pasa al timbre y a la firma, así un solo now fija FchEmis, TSTED y TmstFirma a la vez. timestamp reemplaza las horas del timbre y de la firma por un string que tú elijas (AAAA-MM-DDTHH:MM:SS).
Renderizar el XML#
buildDocumentXml(document, options?) renderiza el XML <DTE> sin firma:
import { buildDocumentXml, documentElementName } from 'dte-sdk'
const xml = buildDocumentXml(document)
documentElementName(33) // 'Documento'
documentElementName(43) // 'Liquidacion'| Opción | Significado |
|---|---|
ted | El Ted a incluir (textual, desde stampDocument). |
signedAt | Valor de TmstFirma. |
Lo que hace:
- Declara los namespaces en el raíz
DTE(http://www.sii.cl/SiiDteyxsi), así el digest de la firma los hereda. - Pone el atributo
ID(DTE_T33F1) en el elemento firmado:Documento, oLiquidacionen una liquidación factura (43).documentElementName(type)devuelve ese nombre. - Escribe las secciones en el orden del esquema (
Encabezado,Detalle,SubTotInfo,DscRcgGlobal,Referencia,Comisiones,TED) y omite las vacías. - Pone un salto de línea después de cada una de esas secciones. El SII rechaza cualquier línea de más de unos 4.090 caracteres (
CHR-00002), así que una boleta con muchas líneas fallaría de otro modo. Los saltos quedan dentro del elemento firmado y el digest los cubre. - Escapa los valores de texto como entidades XML.
Rara vez lo llamas tú: signDocument lo llama por ti. Sirve para mirar lo que se va a firmar o para archivar un borrador.
El codec: etiquetas del SII y el modelo en inglés#
El modelo en inglés y las etiquetas del SII son dos formas de los mismos datos. toSiiDocument convierte un Document a la estructura con las etiquetas oficiales en orden de esquema (SiiDocumento: Encabezado.IdDoc.TipoDTE, Totales.MntTotal…), y fromSiiDocument la convierte de vuelta:
import { fromSiiDocument, toSiiDocument } from 'dte-sdk'
const sii = toSiiDocument(document)
sii.Encabezado.Totales.MntTotal // 167195
sii.Detalle[0].NmbItem // 'Plan anual'
fromSiiDocument(sii) // otra vez un DocumenttoSiiDocument es el único lugar que conoce los nombres de las etiquetas; buildDocumentXml siempre renderiza desde su salida. fromSiiDocument es para los DTE que recibes y para interoperar: valida cada RUT con rut.ts al entrar (INVALID_RUT). Una boleta convierte sus referencias hacia y desde receiptReferences. Una cosa no sobrevive al viaje de ida y vuelta: unitPriceDecimals, porque un 12.50 escrito se lee de vuelta como el número 12.5.
Siguientes pasos#
- Totales: cómo se calculan los montos de línea, el IVA, las líneas exentas y la retención.
- Timbre: pon el TED en el documento con un CAF.
- Firma: carga el certificado y firma.
- Ejemplos: factura, nota de crédito y boleta.