Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/tidy-dtes-sign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@emisso/sii": minor
---

Implement `buildDteXml`, `applyTimbre` and `signDte` for DTE types 33 (Factura Electrónica) and 39 (Boleta Electrónica).

- `buildDteXml`: builds the `<DTE>` / `<Documento ID="F{folio}T{tipo}">` XML with type-specific element names (RznSoc/GiroEmis/Acteco for 33; RznSocEmisor/GiroEmisor for 39), derives MntNeto/IVA from MntTotal when omitted (IVA as exact remainder, never a second rounding), validates receiver requirements for facturas, and treats line amounts as GROSS: for type 33 lines are converted to net with the rounding difference absorbed into the last affected line so the detail sums to MntNeto exactly.
- `applyTimbre`: builds a real TED signed RSA-SHA1 with the CAF private key over the compact `<DD>` block, embedding the original `<CAF>` block verbatim. Signature changed to `(xml, caf: FolioRange)` and `FolioRangeSchema` gained an optional `cafXml` field, since SII's `<FRMA>` signature inside the CAF cannot be reconstructed from parsed fields.
- `signDte`: XMLDSig over the `<Documento>` (Reference by ID + C14N transform), inserted as sibling of `<Documento>` inside `<DTE>`; must run after `applyTimbre`.
- `DteDocumentSchema` gained an optional `indServicio` (1|2|3) emitted only for boletas and only when explicitly set. Timestamps (TSTED/TmstFirma) use America/Santiago wall time.
21 changes: 15 additions & 6 deletions examples/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
import {
authenticate,
buildDteXml,
applyTimbre,
signDte,
uploadDte,
queryUploadStatus,
loadCafFromFile,
loadConfigFromEnv,
type DteDocument,
} from "@emisso/sii";
Expand Down Expand Up @@ -47,10 +49,12 @@ async function main() {
},
items: [
{
// Line amounts are GROSS (IVA-included) — buildDteXml converts them
// to net for facturas so the detail matches MntNeto exactly.
nombre: "Servicio de Consultoría en Software",
cantidad: 10,
precioUnitario: 50000,
montoItem: 500000,
precioUnitario: 59500,
montoItem: 595000,
},
],
montoNeto: 500000,
Expand All @@ -62,15 +66,20 @@ async function main() {
const xml = await buildDteXml(document);
console.log("DTE XML built successfully");

// 5. Sign the DTE
const signedXml = await signDte(xml, config.certPath, config.certPassword);
// 5. Stamp with the CAF timbre (TED) — must happen BEFORE signing
const caf = await loadCafFromFile("./caf-33.xml");
const stampedXml = await applyTimbre(xml, caf);
console.log("DTE stamped with TED");

// 6. Sign the DTE
const signedXml = await signDte(stampedXml, config.certPath, config.certPassword);
console.log("DTE signed successfully");

// 6. Upload to SII
// 7. Upload to SII
const uploadResponse = await uploadDte(signedXml, token, config);
console.log("DTE uploaded, trackId:", uploadResponse.trackId);

// 7. Check status (poll after a few seconds in real usage)
// 8. Check status (poll after a few seconds in real usage)
const status = await queryUploadStatus(uploadResponse.trackId, token, config);
console.log("DTE status:", status.status, "-", status.glosa);
}
Expand Down
Loading