@devlas/dte-sii 2.19.1 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DTE.js +18 -2
- package/dte-sii.d.ts +6 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/utils/index.js +2 -0
- package/utils/sanitize.js +36 -0
package/DTE.js
CHANGED
|
@@ -12,6 +12,7 @@ const { DOMParser } = require('@xmldom/xmldom');
|
|
|
12
12
|
const {
|
|
13
13
|
sanitizeSiiText,
|
|
14
14
|
sanitizeTedText,
|
|
15
|
+
assertLargoMaximo,
|
|
15
16
|
formatBase64InXml,
|
|
16
17
|
normalizeEmisor,
|
|
17
18
|
normalizeReceptor,
|
|
@@ -22,6 +23,21 @@ const {
|
|
|
22
23
|
} = require('./utils');
|
|
23
24
|
const { serializeNode, escapeAttr, escapeText, buildSignedInfo, buildSignature } = require('./utils/c14n');
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Sanitiza y valida el nombre de un ítem antes de que entre al XML.
|
|
28
|
+
*
|
|
29
|
+
* Lanza si supera los 80 caracteres que admite `NmbItem` en el XSD del SII — el SII
|
|
30
|
+
* rechaza el SOBRE COMPLETO cuando uno solo de sus documentos falla el schema (medido el
|
|
31
|
+
* 2026-09-01: 3 boletas cayeron juntas por un ítem de 83 caracteres, 2 de ellas con datos
|
|
32
|
+
* válidos). Mejor lanzar acá, con el campo y el valor, que dejar que el SII lo rechace
|
|
33
|
+
* tres pasos después con "Error en Schema" sin decir cuál documento ni por qué.
|
|
34
|
+
*/
|
|
35
|
+
function sanitizeNmbItem(nombre) {
|
|
36
|
+
const valor = sanitizeSiiText(nombre);
|
|
37
|
+
assertLargoMaximo(valor, 80, 'NmbItem');
|
|
38
|
+
return valor;
|
|
39
|
+
}
|
|
40
|
+
|
|
25
41
|
// ============================================
|
|
26
42
|
// CONSTANTES
|
|
27
43
|
// ============================================
|
|
@@ -140,7 +156,7 @@ class DTE {
|
|
|
140
156
|
const det = {
|
|
141
157
|
NroLinDet: idx + 1,
|
|
142
158
|
...(esItemExento ? { IndExe: 1 } : {}),
|
|
143
|
-
NmbItem:
|
|
159
|
+
NmbItem: sanitizeNmbItem(item.NmbItem),
|
|
144
160
|
QtyItem: qty,
|
|
145
161
|
...(item.UnmdItem ? { UnmdItem: item.UnmdItem } : {}),
|
|
146
162
|
PrcItem: prc,
|
|
@@ -247,7 +263,7 @@ class DTE {
|
|
|
247
263
|
_buildDetalle(det) {
|
|
248
264
|
return (Array.isArray(det) ? det : [det]).map(item => ({
|
|
249
265
|
...item,
|
|
250
|
-
NmbItem:
|
|
266
|
+
NmbItem: sanitizeNmbItem(item.NmbItem),
|
|
251
267
|
...(item.DscItem ? { DscItem: sanitizeSiiText(item.DscItem) } : {}),
|
|
252
268
|
}));
|
|
253
269
|
}
|
package/dte-sii.d.ts
CHANGED
|
@@ -888,6 +888,11 @@ export function sanitizeSiiText(text: string): string;
|
|
|
888
888
|
*/
|
|
889
889
|
export function sanitizeTedText(text: string): string;
|
|
890
890
|
export function truncateText(text: string, maxLen: number, preserveWords?: boolean): string;
|
|
891
|
+
/**
|
|
892
|
+
* Lanza si `text` supera `maxLength` caracteres — para campos con `xs:maxLength` fijo en
|
|
893
|
+
* el XSD (ej. NmbItem: 80). Se valida el texto YA sanitizado, el que efectivamente va al XML.
|
|
894
|
+
*/
|
|
895
|
+
export function assertLargoMaximo(text: string, maxLength: number, campo: string): void;
|
|
891
896
|
export function sanitizeGiroRecep(giro: string): string;
|
|
892
897
|
export function sanitizeRazonSocial(razonSocial: string): string;
|
|
893
898
|
export function sanitizeNombreItem(nombre: string): string;
|
|
@@ -1032,6 +1037,7 @@ export const utils: {
|
|
|
1032
1037
|
sanitizeSiiText: typeof sanitizeSiiText;
|
|
1033
1038
|
sanitizeTedText: typeof sanitizeTedText;
|
|
1034
1039
|
truncateText: typeof truncateText;
|
|
1040
|
+
assertLargoMaximo: typeof assertLargoMaximo;
|
|
1035
1041
|
sanitizeGiroRecep: typeof sanitizeGiroRecep;
|
|
1036
1042
|
sanitizeRazonSocial: typeof sanitizeRazonSocial;
|
|
1037
1043
|
sanitizeNombreItem: typeof sanitizeNombreItem;
|
package/index.js
CHANGED
|
@@ -46,6 +46,7 @@ const {
|
|
|
46
46
|
sanitizeSiiText,
|
|
47
47
|
sanitizeTedText,
|
|
48
48
|
truncateText,
|
|
49
|
+
assertLargoMaximo,
|
|
49
50
|
sanitizeGiroRecep,
|
|
50
51
|
sanitizeRazonSocial,
|
|
51
52
|
sanitizeNombreItem,
|
|
@@ -260,6 +261,7 @@ module.exports = {
|
|
|
260
261
|
sanitizeSiiText,
|
|
261
262
|
sanitizeTedText,
|
|
262
263
|
truncateText,
|
|
264
|
+
assertLargoMaximo,
|
|
263
265
|
sanitizeGiroRecep,
|
|
264
266
|
sanitizeRazonSocial,
|
|
265
267
|
sanitizeNombreItem,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devlas/dte-sii",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.20.0",
|
|
4
4
|
"description": "Facturación y boletas electrónicas para el SII de Chile. Genera, timbra, firma y envía DTEs, libros electrónicos y automatiza la certificación.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "dte-sii.d.ts",
|
package/utils/index.js
CHANGED
|
@@ -54,6 +54,7 @@ const {
|
|
|
54
54
|
sanitizeSiiText,
|
|
55
55
|
sanitizeTedText,
|
|
56
56
|
truncateText,
|
|
57
|
+
assertLargoMaximo,
|
|
57
58
|
sanitizeGiroRecep,
|
|
58
59
|
sanitizeRazonSocial,
|
|
59
60
|
sanitizeNombreItem,
|
|
@@ -179,6 +180,7 @@ module.exports = {
|
|
|
179
180
|
sanitizeSiiText,
|
|
180
181
|
sanitizeTedText,
|
|
181
182
|
truncateText,
|
|
183
|
+
assertLargoMaximo,
|
|
182
184
|
sanitizeGiroRecep,
|
|
183
185
|
sanitizeRazonSocial,
|
|
184
186
|
sanitizeNombreItem,
|
package/utils/sanitize.js
CHANGED
|
@@ -118,6 +118,41 @@ function truncateText(text, maxLen, preserveWords = false) {
|
|
|
118
118
|
return sanitized.substring(0, maxLen);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Lanza si `text` supera `maxLength` caracteres — para campos con `xs:maxLength` fijo en
|
|
123
|
+
* el XSD del SII (ej. NmbItem: 80).
|
|
124
|
+
*
|
|
125
|
+
* ── Por qué lanza y no trunca ─────────────────────────────────────────────────
|
|
126
|
+
* Esta librería ya tenía `sanitizeNombreItem`/`sanitizeRazonSocial`/etc. (más abajo),
|
|
127
|
+
* que truncan en silencio — pero nunca se conectaron a los campos que serializa `DTE.js`
|
|
128
|
+
* (`sanitizeSiiText` corre sola ahí, sin ningún control de largo). El resultado real:
|
|
129
|
+
* un `NmbItem` de 83 caracteres pasaba intacto y el SII rechazaba recién al validar el
|
|
130
|
+
* XML — y **rechaza el sobre completo**, no solo ese documento (medido el 2026-09-01,
|
|
131
|
+
* 3 boletas de un mismo envío cayeron juntas, 2 de ellas con datos válidos).
|
|
132
|
+
*
|
|
133
|
+
* Truncar en una librería de serialización es una decisión de negocio disfrazada de
|
|
134
|
+
* detalle técnico: recorta lo que el cliente ve impreso en su documento tributario, y
|
|
135
|
+
* hacerlo en silencio esconde el dato malo en vez de exponerlo. Esta librería conoce el
|
|
136
|
+
* contrato del XSD — le corresponde avisar temprano, con un mensaje que diga campo,
|
|
137
|
+
* límite y valor, para que el consumidor decida qué hacer (corregir el dato, truncar con
|
|
138
|
+
* su propia política, etc.) en vez de que el SII lo rechace tres pasos después con un
|
|
139
|
+
* error genérico de schema.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} text - Texto YA sanitizado (post `sanitizeSiiText`) — se valida el que
|
|
142
|
+
* efectivamente va al XML, no el original.
|
|
143
|
+
* @param {number} maxLength
|
|
144
|
+
* @param {string} campo - Nombre del elemento XSD, para el mensaje de error.
|
|
145
|
+
* @throws {Error} si `text.length > maxLength`
|
|
146
|
+
*/
|
|
147
|
+
function assertLargoMaximo(text, maxLength, campo) {
|
|
148
|
+
const valor = String(text ?? '');
|
|
149
|
+
if (valor.length > maxLength) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`${campo} excede el largo maximo del XSD (${maxLength} caracteres, tiene ${valor.length}): "${valor}"`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
121
156
|
/**
|
|
122
157
|
* Sanitizar giro para receptor (máximo 40 caracteres)
|
|
123
158
|
*
|
|
@@ -184,6 +219,7 @@ module.exports = {
|
|
|
184
219
|
sanitizeSiiText,
|
|
185
220
|
sanitizeTedText,
|
|
186
221
|
truncateText,
|
|
222
|
+
assertLargoMaximo,
|
|
187
223
|
sanitizeGiroRecep,
|
|
188
224
|
sanitizeRazonSocial,
|
|
189
225
|
sanitizeNombreItem,
|