@openfactura/cli 0.1.0 → 0.2.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/README.md +67 -5
- package/dist/index.js +517 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ npx @openfactura/cli --help
|
|
|
21
21
|
|
|
22
22
|
## Inicio de sesión
|
|
23
23
|
|
|
24
|
-
Cree una clave de API en el panel web (
|
|
24
|
+
Cree una clave de API en el panel web (Desarrolladores → Nueva clave). Las claves `of_test_…` trabajan contra
|
|
25
25
|
el ambiente de pruebas del MH y las `of_live_…` contra producción.
|
|
26
26
|
|
|
27
27
|
```bash
|
|
@@ -220,7 +220,45 @@ openfactura dte invalidar <codigo> --tipo 1 --reemplazo <codigo-del-dte-que-lo-c
|
|
|
220
220
|
```
|
|
221
221
|
|
|
222
222
|
`--tipo`: `1` error en la información (requiere `--reemplazo`), `2` rescindir la operación, `3` otro
|
|
223
|
-
(requiere `--motivo` y `--reemplazo`). La invalidación es irreversible.
|
|
223
|
+
(requiere `--motivo` y `--reemplazo`). La invalidación es irreversible. El mismo comando invalida un
|
|
224
|
+
evento de retorno u operaciones especiales si recibe su código. Un DTE con un retorno vigente no se
|
|
225
|
+
invalida hasta invalidar antes el retorno.
|
|
226
|
+
|
|
227
|
+
#### Retorno (evento 18)
|
|
228
|
+
|
|
229
|
+
Devolución de bienes, reembolso o envases sobre una Factura (01), Factura de Exportación (11) o Sujeto
|
|
230
|
+
Excluido (14) procesados, sin invalidar el documento. Plazo: 3 meses desde la fecha del documento.
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
echo '{ "items": [{ "descripcion": "Silla", "cantidad": 1, "precioUni": 22.6 }] }' \
|
|
234
|
+
| openfactura dte retorno 0862382B-0BB8-42A0-BB6B-B42DCCDF1EA4 -
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`precioUni` va como en el documento original: en Factura con IVA, en 11 y 14 sin IVA. Como `dte emitir`,
|
|
238
|
+
lleva `Idempotency-Key` y pide confirmación con una clave `of_live_…`.
|
|
239
|
+
|
|
240
|
+
### Eventos
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
openfactura eventos operaciones-especiales simplificadas.json # evento 17
|
|
244
|
+
openfactura eventos list --tipo retorno
|
|
245
|
+
openfactura eventos get <codigo>
|
|
246
|
+
openfactura eventos json <codigo> -o retorno.json
|
|
247
|
+
openfactura eventos pdf <codigo> -o retorno.pdf
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
El archivo de operaciones especiales informa facturas simplificadas (`"tipoDocumento": "02"`, por rangos)
|
|
251
|
+
o comprobantes de control interno (`"97"`):
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"tipoDocumento": "02",
|
|
256
|
+
"fecha": "2026-10-31",
|
|
257
|
+
"items": [
|
|
258
|
+
{ "descripcion": "Facturas simplificadas", "docDel": "1", "docAl": "25", "precioUni": 10 }
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
```
|
|
224
262
|
|
|
225
263
|
### Contingencia
|
|
226
264
|
|
|
@@ -228,9 +266,30 @@ openfactura dte invalidar <codigo> --tipo 1 --reemplazo <codigo-del-dte-que-lo-c
|
|
|
228
266
|
openfactura contingencia transmitir
|
|
229
267
|
```
|
|
230
268
|
|
|
269
|
+
### Webhooks
|
|
270
|
+
|
|
271
|
+
Su sistema recibe un `POST` firmado cuando un documento cambia de estado. La clave necesita el permiso
|
|
272
|
+
`webhooks:manage`.
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
openfactura webhooks add https://erp.su-empresa.sv/hooks/openfactura \
|
|
276
|
+
--eventos dte.procesado,dte.rechazado --descripcion "ERP" # sin --eventos: todos
|
|
277
|
+
openfactura webhooks list
|
|
278
|
+
openfactura webhooks test <id> # envía webhook.prueba; salida 1 si no se entrega
|
|
279
|
+
openfactura webhooks deliveries <id> # últimas 50 entregas, con intentos y errores
|
|
280
|
+
openfactura webhooks rotate <id> # secreto nuevo; el anterior deja de firmar
|
|
281
|
+
openfactura webhooks remove <id>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`add` y `rotate` muestran el secreto de firma (`whsec_…`) una sola vez. Eventos: `dte.procesado`,
|
|
285
|
+
`dte.rechazado`, `dte.contingencia`, `dte.invalidado`, `retorno.procesado`,
|
|
286
|
+
`operaciones_especiales.procesado`. Cómo verificar la cabecera `OpenFactura-Signature`: sección
|
|
287
|
+
«Webhooks» de `docs/api.md` en el repositorio del servidor.
|
|
288
|
+
|
|
231
289
|
### Confirmación en producción
|
|
232
290
|
|
|
233
|
-
Con una clave `of_live_…`, `dte emitir`, `dte
|
|
291
|
+
Con una clave `of_live_…`, `dte emitir`, `dte retorno`, `eventos operaciones-especiales`,
|
|
292
|
+
`dte invalidar` y `contingencia transmitir` piden confirmación
|
|
234
293
|
porque tienen efecto fiscal. Use `--yes` para omitirla. Si no hay una terminal interactiva (un script,
|
|
235
294
|
una tubería, `dte emitir -`), la CLI no pregunta: exige `--yes` y termina con salida `2` si falta.
|
|
236
295
|
|
|
@@ -274,10 +333,13 @@ clientes que no pueden enviar un encabezado HTTP fijo al servidor MCP remoto (`/
|
|
|
274
333
|
herramientas que el remoto y las ejecuta llamando a la API REST con su clave:
|
|
275
334
|
|
|
276
335
|
`emisor_obtener`, `catalogo_buscar`, `clientes_buscar`, `cliente_crear`, `productos_buscar`,
|
|
277
|
-
`producto_crear`, `dte_validar`, `dte_emitir`, `dte_listar`, `dte_obtener`, `dte_invalidar
|
|
336
|
+
`producto_crear`, `dte_validar`, `dte_emitir`, `dte_listar`, `dte_obtener`, `dte_invalidar`,
|
|
337
|
+
`dte_retorno`, `operaciones_especiales_informar`, `eventos_listar`, `evento_obtener`,
|
|
338
|
+
`webhooks_listar`, `webhook_crear`, `webhook_eliminar`, `webhook_probar`.
|
|
278
339
|
|
|
279
340
|
Cada herramienta exige el mismo permiso que su ruta REST, así que la clave decide qué puede hacer el
|
|
280
|
-
asistente. Empiece con una clave `of_test_…`; `dte_emitir`
|
|
341
|
+
asistente. Empiece con una clave `of_test_…`; `dte_emitir`, `dte_retorno`, `operaciones_especiales_informar` y
|
|
342
|
+
`dte_invalidar` tienen efecto fiscal con una
|
|
281
343
|
clave `of_live_…`, y aquí no hay confirmación de la CLI: la da usted en su cliente MCP al aprobar la
|
|
282
344
|
llamada.
|
|
283
345
|
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { parseArgs } from "util";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@openfactura/cli",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.2.0",
|
|
10
10
|
description: "Cliente de l\xEDnea de comandos y servidor MCP (stdio) de Open Factura: facturaci\xF3n electr\xF3nica de El Salvador (DTE).",
|
|
11
11
|
license: "MIT",
|
|
12
12
|
repository: {
|
|
@@ -257,6 +257,56 @@ var invalidacion = z.object({
|
|
|
257
257
|
solicitante: persona.optional(),
|
|
258
258
|
responsable: persona.optional()
|
|
259
259
|
});
|
|
260
|
+
var fecha = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Use el formato AAAA-MM-DD");
|
|
261
|
+
var retorno = z.object({
|
|
262
|
+
items: z.array(
|
|
263
|
+
z.object({
|
|
264
|
+
descripcion: texto(1e3),
|
|
265
|
+
cantidad: z.number().positive(),
|
|
266
|
+
precioUni: z.number().nonnegative(),
|
|
267
|
+
tipoItem: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]).optional(),
|
|
268
|
+
codigo: opcional(25),
|
|
269
|
+
uniMedida: z.number().int().min(1).max(99).optional(),
|
|
270
|
+
descuento: z.number().nonnegative().optional(),
|
|
271
|
+
tipoVenta: z.enum(["gravada", "exenta", "noSujeta"]).optional(),
|
|
272
|
+
seguro: z.number().nonnegative().optional(),
|
|
273
|
+
flete: z.number().nonnegative().optional()
|
|
274
|
+
})
|
|
275
|
+
).min(1).max(2e3).describe(
|
|
276
|
+
"Bienes que se retornan. precioUni: en Factura (01) CON IVA, como en la factura; en Exportaci\xF3n (11) y Sujeto Excluido (14) SIN IVA."
|
|
277
|
+
)
|
|
278
|
+
});
|
|
279
|
+
var operacionesEspeciales = z.object({
|
|
280
|
+
tipoDocumento: z.enum(["02", "97"]).describe("02 factura de venta simplificada (por rangos), 97 comprobante de control interno"),
|
|
281
|
+
fecha: fecha.optional().describe("Fecha del evento; define el periodo informado"),
|
|
282
|
+
items: z.array(
|
|
283
|
+
z.object({
|
|
284
|
+
descripcion: texto(1500),
|
|
285
|
+
precioUni: z.number().nonnegative().describe("SIN IVA"),
|
|
286
|
+
cantidad: z.number().int().positive().optional(),
|
|
287
|
+
numDocumento: opcional(36),
|
|
288
|
+
docDel: z.string().trim().min(1).max(36).optional(),
|
|
289
|
+
docAl: z.string().trim().min(1).max(36).optional(),
|
|
290
|
+
fechaEmision: fecha.optional(),
|
|
291
|
+
tipoVenta: z.enum(["gravada", "exenta", "noSujeta"]).optional(),
|
|
292
|
+
codigoGeneracionRef: z.string().length(36).optional()
|
|
293
|
+
})
|
|
294
|
+
).min(1).max(2e3)
|
|
295
|
+
});
|
|
296
|
+
var EVENTOS_WEBHOOK = [
|
|
297
|
+
"dte.procesado",
|
|
298
|
+
"dte.rechazado",
|
|
299
|
+
"dte.contingencia",
|
|
300
|
+
"dte.invalidado",
|
|
301
|
+
"retorno.procesado",
|
|
302
|
+
"operaciones_especiales.procesado"
|
|
303
|
+
];
|
|
304
|
+
var webhook = z.object({
|
|
305
|
+
url: z.string().max(2e3).describe("URL https:// p\xFAblica que recibir\xE1 los POST firmados"),
|
|
306
|
+
descripcion: opcional(200),
|
|
307
|
+
eventos: z.array(z.enum(EVENTOS_WEBHOOK)).min(1).describe("Eventos a los que se suscribe"),
|
|
308
|
+
activo: z.boolean().optional()
|
|
309
|
+
});
|
|
260
310
|
|
|
261
311
|
// src/http.ts
|
|
262
312
|
var TIEMPO_LIMITE_MS = 6e4;
|
|
@@ -522,7 +572,7 @@ function crearServidorMcp(api, version) {
|
|
|
522
572
|
"dte_invalidar",
|
|
523
573
|
{
|
|
524
574
|
title: "Invalidar un DTE",
|
|
525
|
-
description: "Transmite el evento de invalidaci\xF3n de un DTE procesado. Es irreversible y tiene efecto fiscal: confirme con el usuario. tipoAnulacion: 1 error en la informaci\xF3n (requiere codigoGeneracionReemplazo), 2 rescindir la operaci\xF3n, 3 otro (requiere motivo y reemplazo).",
|
|
575
|
+
description: "Transmite el evento de invalidaci\xF3n de un DTE procesado, o de un evento de retorno u operaciones especiales (use su c\xF3digo de generaci\xF3n). Un DTE con retorno vigente exige invalidar antes el retorno. Es irreversible y tiene efecto fiscal: confirme con el usuario. tipoAnulacion: 1 error en la informaci\xF3n (requiere codigoGeneracionReemplazo), 2 rescindir la operaci\xF3n, 3 otro (requiere motivo y reemplazo).",
|
|
526
576
|
inputSchema: z2.object({ codigoGeneracion: z2.string(), ...invalidacion.shape }),
|
|
527
577
|
annotations: {
|
|
528
578
|
readOnlyHint: false,
|
|
@@ -535,6 +585,122 @@ function crearServidorMcp(api, version) {
|
|
|
535
585
|
() => api.pedir("POST", `/dte/${ruta(codigoGeneracion)}/invalidar`, { cuerpo: entrada })
|
|
536
586
|
)
|
|
537
587
|
);
|
|
588
|
+
servidor.registerTool(
|
|
589
|
+
"dte_retorno",
|
|
590
|
+
{
|
|
591
|
+
title: "Registrar un retorno (evento 18)",
|
|
592
|
+
description: "Transmite un Evento de Retorno sobre una Factura (01), Factura de Exportaci\xF3n (11) o Sujeto Excluido (14) procesados: devoluci\xF3n de bienes, reembolso o envases, sin invalidar el documento. Plazo: 3 meses desde la fecha del DTE. La suma de retornos no puede superar el DTE. Tiene efecto fiscal: confirme con el usuario. Para un Cr\xE9dito Fiscal use una Nota de Cr\xE9dito (05).",
|
|
593
|
+
inputSchema: z2.object({
|
|
594
|
+
codigoGeneracion: z2.string().describe("C\xF3digo de generaci\xF3n del DTE original"),
|
|
595
|
+
...retorno.shape,
|
|
596
|
+
idempotencyKey: z2.string().min(8).max(200).describe(
|
|
597
|
+
"Identificador \xFAnico de este intento. Repetirlo devuelve el mismo resultado sin transmitir dos veces."
|
|
598
|
+
)
|
|
599
|
+
}),
|
|
600
|
+
annotations: {
|
|
601
|
+
readOnlyHint: false,
|
|
602
|
+
destructiveHint: false,
|
|
603
|
+
idempotentHint: true,
|
|
604
|
+
openWorldHint: true
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
({ codigoGeneracion, idempotencyKey, ...entrada }) => responder(
|
|
608
|
+
() => api.pedir("POST", `/dte/${ruta(codigoGeneracion)}/retorno`, {
|
|
609
|
+
cuerpo: entrada,
|
|
610
|
+
headers: { "Idempotency-Key": idempotencyKey }
|
|
611
|
+
})
|
|
612
|
+
)
|
|
613
|
+
);
|
|
614
|
+
servidor.registerTool(
|
|
615
|
+
"operaciones_especiales_informar",
|
|
616
|
+
{
|
|
617
|
+
title: "Informar operaciones especiales (evento 17)",
|
|
618
|
+
description: "Transmite un Evento de Operaciones Especiales: informa al MH las facturas simplificadas (02, siempre por rangos docDel\u2013docAl) o los comprobantes de control interno (97, uno a uno con numDocumento o por rangos, sin mezclar) de un periodo. precioUni SIN IVA. Los documentos anulados van en un evento aparte con codigoGeneracionRef. Tiene efecto fiscal: confirme con el usuario.",
|
|
619
|
+
inputSchema: z2.object({
|
|
620
|
+
...operacionesEspeciales.shape,
|
|
621
|
+
idempotencyKey: z2.string().min(8).max(200).describe(
|
|
622
|
+
"Identificador \xFAnico de este intento. Repetirlo devuelve el mismo resultado sin transmitir dos veces."
|
|
623
|
+
)
|
|
624
|
+
}),
|
|
625
|
+
annotations: {
|
|
626
|
+
readOnlyHint: false,
|
|
627
|
+
destructiveHint: false,
|
|
628
|
+
idempotentHint: true,
|
|
629
|
+
openWorldHint: true
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
({ idempotencyKey, ...entrada }) => responder(
|
|
633
|
+
() => api.pedir("POST", "/eventos/operaciones-especiales", {
|
|
634
|
+
cuerpo: entrada,
|
|
635
|
+
headers: { "Idempotency-Key": idempotencyKey }
|
|
636
|
+
})
|
|
637
|
+
)
|
|
638
|
+
);
|
|
639
|
+
servidor.registerTool(
|
|
640
|
+
"eventos_listar",
|
|
641
|
+
{
|
|
642
|
+
title: "Listar eventos",
|
|
643
|
+
description: "Eventos de retorno y de operaciones especiales del ambiente activo (los 50 m\xE1s recientes).",
|
|
644
|
+
inputSchema: z2.object({ tipo: z2.enum(["retorno", "operaciones_especiales"]).optional() }),
|
|
645
|
+
annotations: lectura
|
|
646
|
+
},
|
|
647
|
+
({ tipo }) => responder(() => api.pedir("GET", "/eventos", { query: { tipo } }))
|
|
648
|
+
);
|
|
649
|
+
servidor.registerTool(
|
|
650
|
+
"evento_obtener",
|
|
651
|
+
{
|
|
652
|
+
title: "Ver un evento",
|
|
653
|
+
description: "Detalle de un evento por c\xF3digo de generaci\xF3n: estado, sello, JSON oficial y respuesta del MH.",
|
|
654
|
+
inputSchema: z2.object({ codigoGeneracion: z2.string() }),
|
|
655
|
+
annotations: lectura
|
|
656
|
+
},
|
|
657
|
+
({ codigoGeneracion }) => responder(() => api.pedir("GET", `/eventos/${ruta(codigoGeneracion)}`))
|
|
658
|
+
);
|
|
659
|
+
servidor.registerTool(
|
|
660
|
+
"webhooks_listar",
|
|
661
|
+
{
|
|
662
|
+
title: "Listar webhooks",
|
|
663
|
+
description: "Webhooks de la organizaci\xF3n: URL, eventos suscritos y si est\xE1n activos. Nunca devuelve el secreto de firma.",
|
|
664
|
+
inputSchema: z2.object({}),
|
|
665
|
+
annotations: lectura
|
|
666
|
+
},
|
|
667
|
+
() => responder(() => api.pedir("GET", "/webhooks"))
|
|
668
|
+
);
|
|
669
|
+
servidor.registerTool(
|
|
670
|
+
"webhook_crear",
|
|
671
|
+
{
|
|
672
|
+
title: "Crear webhook",
|
|
673
|
+
description: `Registra una URL https:// que recibir\xE1 un POST firmado cuando cambie el estado de un documento. Eventos: ${EVENTOS_WEBHOOK.join(", ")}. Devuelve el secreto de firma (whsec_\u2026) UNA sola vez: entr\xE9gueselo al usuario para que verifique la firma OpenFactura-Signature.`,
|
|
674
|
+
inputSchema: webhook,
|
|
675
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
676
|
+
},
|
|
677
|
+
(datos) => responder(() => api.pedir("POST", "/webhooks", { cuerpo: datos }))
|
|
678
|
+
);
|
|
679
|
+
servidor.registerTool(
|
|
680
|
+
"webhook_eliminar",
|
|
681
|
+
{
|
|
682
|
+
title: "Eliminar webhook",
|
|
683
|
+
description: "Elimina un webhook y su historial de entregas. Deja de recibir notificaciones de inmediato. Confirme con el usuario.",
|
|
684
|
+
inputSchema: z2.object({ id: z2.string() }),
|
|
685
|
+
annotations: {
|
|
686
|
+
readOnlyHint: false,
|
|
687
|
+
destructiveHint: true,
|
|
688
|
+
idempotentHint: true,
|
|
689
|
+
openWorldHint: false
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
({ id }) => responder(() => api.pedir("DELETE", `/webhooks/${ruta(id)}`))
|
|
693
|
+
);
|
|
694
|
+
servidor.registerTool(
|
|
695
|
+
"webhook_probar",
|
|
696
|
+
{
|
|
697
|
+
title: "Probar webhook",
|
|
698
|
+
description: "Env\xEDa ya un evento webhook.prueba firmado a la URL del webhook y devuelve si respondi\xF3 2xx, su estado HTTP o el error.",
|
|
699
|
+
inputSchema: z2.object({ id: z2.string() }),
|
|
700
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
701
|
+
},
|
|
702
|
+
({ id }) => responder(() => api.pedir("POST", `/webhooks/${ruta(id)}/probar`))
|
|
703
|
+
);
|
|
538
704
|
return servidor;
|
|
539
705
|
}
|
|
540
706
|
function servirMcp(api, version) {
|
|
@@ -579,7 +745,7 @@ var siNo = (valor) => valor ? "s\xED" : "no";
|
|
|
579
745
|
// src/comandos.ts
|
|
580
746
|
var nombreTipo = (codigo) => `${codigo} ${TIPOS_DTE[String(codigo)] ?? ""}`.trim();
|
|
581
747
|
var nombreAmbiente = (codigo) => `${codigo} ${NOMBRE_AMBIENTE[String(codigo)] ?? ""}`.trim();
|
|
582
|
-
var
|
|
748
|
+
var fecha2 = (ms) => typeof ms === "number" ? new Date(ms).toISOString().replace("T", " ").slice(0, 19) : ms;
|
|
583
749
|
var segmento = (texto2) => encodeURIComponent(texto2);
|
|
584
750
|
function mostrar(ctx, datos, legible) {
|
|
585
751
|
ctx.ent.stdout(ctx.json ? JSON.stringify(datos, null, 2) : legible());
|
|
@@ -959,36 +1125,45 @@ async function dteValidar(ctx) {
|
|
|
959
1125
|
});
|
|
960
1126
|
return problemas.length ? SALIDA.error : SALIDA.ok;
|
|
961
1127
|
}
|
|
962
|
-
async function
|
|
963
|
-
const origen = ctx.args[0];
|
|
964
|
-
const cuerpo = await leerEmision(ctx, origen);
|
|
1128
|
+
async function enviarUnaVez(ctx, ruta2, cuerpo, qu\u00E9, reintento, stdin = false) {
|
|
965
1129
|
const api = ctx.api();
|
|
966
1130
|
const clave = ctx.op("idempotency-key") ?? randomUUID();
|
|
967
1131
|
if (clave.length < 1 || clave.length > 200)
|
|
968
1132
|
throw new ErrorUso("--idempotency-key debe tener entre 1 y 200 caracteres.");
|
|
969
|
-
await confirmar(ctx,
|
|
1133
|
+
await confirmar(ctx, qu\u00E9, stdin);
|
|
970
1134
|
ctx.ent.stderr(`Idempotency-Key: ${clave}`);
|
|
971
|
-
let respuesta;
|
|
972
1135
|
try {
|
|
973
|
-
respuesta = await api.pedir("POST",
|
|
1136
|
+
const respuesta = await api.pedir("POST", ruta2, {
|
|
974
1137
|
cuerpo,
|
|
975
1138
|
headers: { "Idempotency-Key": clave }
|
|
976
1139
|
});
|
|
1140
|
+
if (respuesta.headers.get("Idempotent-Replayed") === "true") {
|
|
1141
|
+
ctx.ent.stderr(
|
|
1142
|
+
"Respuesta repetida: esta Idempotency-Key ya se hab\xEDa usado; no se transmiti\xF3 de nuevo."
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
return respuesta.datos;
|
|
977
1146
|
} catch (error) {
|
|
978
1147
|
if (error instanceof ErrorCli && (error.code === "sin_conexion" || error.code === "tiempo_agotado")) {
|
|
979
1148
|
ctx.ent.stderr(
|
|
980
|
-
`No se sabe si
|
|
981
|
-
openfactura
|
|
1149
|
+
`No se sabe si se transmiti\xF3. Reintente con la misma clave para no duplicarlo:
|
|
1150
|
+
openfactura ${reintento} --idempotency-key ${clave}`
|
|
982
1151
|
);
|
|
983
1152
|
}
|
|
984
1153
|
throw error;
|
|
985
1154
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1155
|
+
}
|
|
1156
|
+
async function dteEmitir(ctx) {
|
|
1157
|
+
const origen = ctx.args[0];
|
|
1158
|
+
const cuerpo = await leerEmision(ctx, origen);
|
|
1159
|
+
const doc = await enviarUnaVez(
|
|
1160
|
+
ctx,
|
|
1161
|
+
"/dte",
|
|
1162
|
+
cuerpo,
|
|
1163
|
+
`emitir un DTE (${nombreTipo(cuerpo.tipoDte)})`,
|
|
1164
|
+
`dte emitir ${origen}`,
|
|
1165
|
+
origen === "-"
|
|
1166
|
+
);
|
|
992
1167
|
if (doc.estado === "contingencia") {
|
|
993
1168
|
ctx.ent.stderr(
|
|
994
1169
|
"El MH no respondi\xF3: el DTE qued\xF3 firmado en contingencia. Transm\xEDtalo con `openfactura contingencia transmitir`."
|
|
@@ -1073,7 +1248,7 @@ async function dteGet(ctx) {
|
|
|
1073
1248
|
eventos.map((e) => [
|
|
1074
1249
|
e.tipo,
|
|
1075
1250
|
e.estado,
|
|
1076
|
-
|
|
1251
|
+
fecha2(e.creado),
|
|
1077
1252
|
e.codigoGeneracion,
|
|
1078
1253
|
e.selloRecibido
|
|
1079
1254
|
])
|
|
@@ -1129,6 +1304,117 @@ async function dteInvalidar(ctx) {
|
|
|
1129
1304
|
${fichaDocumento(datos)}`);
|
|
1130
1305
|
return void 0;
|
|
1131
1306
|
}
|
|
1307
|
+
var TIPOS_EVENTO = ["retorno", "operaciones_especiales"];
|
|
1308
|
+
function fichaEvento(e) {
|
|
1309
|
+
return fichas([
|
|
1310
|
+
[
|
|
1311
|
+
"Evento",
|
|
1312
|
+
e.tipoEvento === "18" ? "18 Retorno" : e.tipoEvento === "17" ? "17 Operaciones especiales" : e.tipo
|
|
1313
|
+
],
|
|
1314
|
+
["Estado", e.estado],
|
|
1315
|
+
["C\xF3digo de generaci\xF3n", e.codigoGeneracion],
|
|
1316
|
+
["Ambiente", nombreAmbiente(e.ambiente)],
|
|
1317
|
+
["Sello recibido", e.selloRecibido]
|
|
1318
|
+
]);
|
|
1319
|
+
}
|
|
1320
|
+
async function leerObjeto(ctx, origen, forma) {
|
|
1321
|
+
const cuerpo = await leerJson(ctx, origen);
|
|
1322
|
+
if (!cuerpo || typeof cuerpo !== "object" || Array.isArray(cuerpo) || !("items" in cuerpo)) {
|
|
1323
|
+
throw new ErrorUso(`El archivo debe ser un objeto ${forma}`);
|
|
1324
|
+
}
|
|
1325
|
+
return cuerpo;
|
|
1326
|
+
}
|
|
1327
|
+
async function dteRetorno(ctx) {
|
|
1328
|
+
const [codigo, origen] = ctx.args;
|
|
1329
|
+
const cuerpo = await leerObjeto(
|
|
1330
|
+
ctx,
|
|
1331
|
+
origen,
|
|
1332
|
+
'{ "items": [{ "descripcion", "cantidad", "precioUni" }] }'
|
|
1333
|
+
);
|
|
1334
|
+
const evento = await enviarUnaVez(
|
|
1335
|
+
ctx,
|
|
1336
|
+
`/dte/${segmento(codigo)}/retorno`,
|
|
1337
|
+
cuerpo,
|
|
1338
|
+
`registrar un retorno sobre el DTE ${codigo}`,
|
|
1339
|
+
`dte retorno ${codigo} ${origen}`,
|
|
1340
|
+
origen === "-"
|
|
1341
|
+
);
|
|
1342
|
+
mostrar(ctx, evento, () => `Retorno registrado.
|
|
1343
|
+
${fichaEvento(evento)}`);
|
|
1344
|
+
return void 0;
|
|
1345
|
+
}
|
|
1346
|
+
async function eventosOperacionesEspeciales(ctx) {
|
|
1347
|
+
const origen = ctx.args[0];
|
|
1348
|
+
const cuerpo = await leerObjeto(
|
|
1349
|
+
ctx,
|
|
1350
|
+
origen,
|
|
1351
|
+
'{ "tipoDocumento": "02" | "97", "fecha"?, "items": [{ "descripcion", "precioUni", "docDel", "docAl" }] }'
|
|
1352
|
+
);
|
|
1353
|
+
const evento = await enviarUnaVez(
|
|
1354
|
+
ctx,
|
|
1355
|
+
"/eventos/operaciones-especiales",
|
|
1356
|
+
cuerpo,
|
|
1357
|
+
"informar operaciones especiales",
|
|
1358
|
+
`eventos operaciones-especiales ${origen}`,
|
|
1359
|
+
origen === "-"
|
|
1360
|
+
);
|
|
1361
|
+
mostrar(ctx, evento, () => `Operaciones especiales informadas.
|
|
1362
|
+
${fichaEvento(evento)}`);
|
|
1363
|
+
return void 0;
|
|
1364
|
+
}
|
|
1365
|
+
async function eventosList(ctx) {
|
|
1366
|
+
const tipo = ctx.op("tipo");
|
|
1367
|
+
if (tipo !== void 0 && !TIPOS_EVENTO.includes(tipo)) {
|
|
1368
|
+
throw new ErrorUso(`--tipo debe ser uno de: ${TIPOS_EVENTO.join(", ")}`);
|
|
1369
|
+
}
|
|
1370
|
+
const { datos } = await ctx.api().pedir("GET", "/eventos", { query: { tipo } });
|
|
1371
|
+
mostrar(
|
|
1372
|
+
ctx,
|
|
1373
|
+
datos,
|
|
1374
|
+
() => datos.length ? tabla(
|
|
1375
|
+
["creado", "evento", "estado", "c\xF3digo de generaci\xF3n"],
|
|
1376
|
+
datos.map((e) => [
|
|
1377
|
+
new Date(e.creado).toISOString().slice(0, 10),
|
|
1378
|
+
e.tipoEvento,
|
|
1379
|
+
e.estado,
|
|
1380
|
+
e.codigoGeneracion
|
|
1381
|
+
])
|
|
1382
|
+
) : "No hay eventos."
|
|
1383
|
+
);
|
|
1384
|
+
return void 0;
|
|
1385
|
+
}
|
|
1386
|
+
async function eventosGet(ctx) {
|
|
1387
|
+
const { datos } = await ctx.api().pedir("GET", `/eventos/${segmento(ctx.args[0])}`);
|
|
1388
|
+
mostrar(ctx, datos, () => fichaEvento(datos));
|
|
1389
|
+
return void 0;
|
|
1390
|
+
}
|
|
1391
|
+
async function eventosJson(ctx) {
|
|
1392
|
+
const codigo = ctx.args[0];
|
|
1393
|
+
const { datos } = await ctx.api().pedir("GET", `/eventos/${segmento(codigo)}/json`);
|
|
1394
|
+
const texto2 = JSON.stringify(datos, null, 2);
|
|
1395
|
+
const salida = ctx.op("output");
|
|
1396
|
+
if (salida === void 0) {
|
|
1397
|
+
ctx.ent.stdout(texto2);
|
|
1398
|
+
return void 0;
|
|
1399
|
+
}
|
|
1400
|
+
const destino = resolve(ctx.ent.cwd, salida);
|
|
1401
|
+
await writeFile(destino, `${texto2}
|
|
1402
|
+
`);
|
|
1403
|
+
mostrar(ctx, { archivo: destino }, () => `JSON guardado en ${destino}`);
|
|
1404
|
+
return void 0;
|
|
1405
|
+
}
|
|
1406
|
+
async function eventosPdf(ctx) {
|
|
1407
|
+
const codigo = ctx.args[0];
|
|
1408
|
+
const bytes = await ctx.api().descargar(`/eventos/${segmento(codigo)}/pdf`);
|
|
1409
|
+
const destino = resolve(ctx.ent.cwd, ctx.op("output") ?? `${basename(codigo)}.pdf`);
|
|
1410
|
+
await writeFile(destino, bytes);
|
|
1411
|
+
mostrar(
|
|
1412
|
+
ctx,
|
|
1413
|
+
{ archivo: destino, bytes: bytes.length },
|
|
1414
|
+
() => `PDF guardado en ${destino} (${bytes.length} bytes)`
|
|
1415
|
+
);
|
|
1416
|
+
return void 0;
|
|
1417
|
+
}
|
|
1132
1418
|
async function contingenciaTransmitir(ctx) {
|
|
1133
1419
|
const api = ctx.api();
|
|
1134
1420
|
await confirmar(ctx, "transmitir los DTE en contingencia");
|
|
@@ -1150,6 +1436,113 @@ async function contingenciaTransmitir(ctx) {
|
|
|
1150
1436
|
});
|
|
1151
1437
|
return void 0;
|
|
1152
1438
|
}
|
|
1439
|
+
function fichaWebhook(w) {
|
|
1440
|
+
return fichas([
|
|
1441
|
+
["Id", w.id],
|
|
1442
|
+
["URL", w.url],
|
|
1443
|
+
["Descripci\xF3n", w.descripcion],
|
|
1444
|
+
["Eventos", Array.isArray(w.eventos) ? w.eventos.join(", ") : null],
|
|
1445
|
+
["Activo", siNo(w.activo)]
|
|
1446
|
+
]);
|
|
1447
|
+
}
|
|
1448
|
+
function mostrarSecreto(ctx, w, titulo) {
|
|
1449
|
+
mostrar(
|
|
1450
|
+
ctx,
|
|
1451
|
+
w,
|
|
1452
|
+
() => [
|
|
1453
|
+
titulo,
|
|
1454
|
+
fichaWebhook(w),
|
|
1455
|
+
"",
|
|
1456
|
+
`Secreto de firma: ${w.secreto}`,
|
|
1457
|
+
"Gu\xE1rdelo ahora: no se vuelve a mostrar. Verifique con \xE9l la cabecera OpenFactura-Signature."
|
|
1458
|
+
].join("\n")
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
async function webhooksList(ctx) {
|
|
1462
|
+
const { datos } = await ctx.api().pedir("GET", "/webhooks");
|
|
1463
|
+
mostrar(
|
|
1464
|
+
ctx,
|
|
1465
|
+
datos,
|
|
1466
|
+
() => datos.length ? tabla(
|
|
1467
|
+
["id", "url", "eventos", "activo", "descripci\xF3n"],
|
|
1468
|
+
datos.map((w) => [
|
|
1469
|
+
w.id,
|
|
1470
|
+
w.url,
|
|
1471
|
+
w.eventos.join(", "),
|
|
1472
|
+
siNo(w.activo),
|
|
1473
|
+
w.descripcion
|
|
1474
|
+
])
|
|
1475
|
+
) : "No hay webhooks."
|
|
1476
|
+
);
|
|
1477
|
+
return void 0;
|
|
1478
|
+
}
|
|
1479
|
+
async function webhooksAdd(ctx) {
|
|
1480
|
+
const texto2 = ctx.op("eventos");
|
|
1481
|
+
const eventos = texto2 ? texto2.split(",").map((e) => e.trim()).filter(Boolean) : [...EVENTOS_WEBHOOK];
|
|
1482
|
+
const desconocido = eventos.find((e) => !EVENTOS_WEBHOOK.includes(e));
|
|
1483
|
+
if (desconocido || !eventos.length) {
|
|
1484
|
+
throw new ErrorUso(`--eventos acepta, separados por coma: ${EVENTOS_WEBHOOK.join(", ")}`);
|
|
1485
|
+
}
|
|
1486
|
+
const cuerpo = { url: ctx.args[0], eventos };
|
|
1487
|
+
if (ctx.op("descripcion") !== void 0) cuerpo.descripcion = ctx.op("descripcion");
|
|
1488
|
+
const { datos } = await ctx.api().pedir("POST", "/webhooks", { cuerpo });
|
|
1489
|
+
mostrarSecreto(ctx, datos, "Webhook creado.");
|
|
1490
|
+
return void 0;
|
|
1491
|
+
}
|
|
1492
|
+
async function webhooksRemove(ctx) {
|
|
1493
|
+
const id = ctx.args[0];
|
|
1494
|
+
const { datos } = await ctx.api().pedir("DELETE", `/webhooks/${segmento(id)}`);
|
|
1495
|
+
mostrar(ctx, datos, () => `Webhook ${id} eliminado.`);
|
|
1496
|
+
return void 0;
|
|
1497
|
+
}
|
|
1498
|
+
async function webhooksTest(ctx) {
|
|
1499
|
+
const id = ctx.args[0];
|
|
1500
|
+
const { datos } = await ctx.api().pedir("POST", `/webhooks/${segmento(id)}/probar`);
|
|
1501
|
+
mostrar(
|
|
1502
|
+
ctx,
|
|
1503
|
+
datos,
|
|
1504
|
+
() => datos.entregado ? `Entregado: el destino respondi\xF3 HTTP ${datos.status}.` : `No se entreg\xF3: ${datos.error ?? "error desconocido"}.`
|
|
1505
|
+
);
|
|
1506
|
+
return datos.entregado ? SALIDA.ok : SALIDA.error;
|
|
1507
|
+
}
|
|
1508
|
+
async function webhooksDeliveries(ctx) {
|
|
1509
|
+
const id = ctx.args[0];
|
|
1510
|
+
const { datos } = await ctx.api().pedir("GET", `/webhooks/${segmento(id)}/entregas`);
|
|
1511
|
+
mostrar(
|
|
1512
|
+
ctx,
|
|
1513
|
+
datos,
|
|
1514
|
+
() => datos.length ? tabla(
|
|
1515
|
+
[
|
|
1516
|
+
"creado (UTC)",
|
|
1517
|
+
"evento",
|
|
1518
|
+
"estado",
|
|
1519
|
+
"intentos",
|
|
1520
|
+
"http",
|
|
1521
|
+
"error",
|
|
1522
|
+
"pr\xF3ximo intento",
|
|
1523
|
+
"id"
|
|
1524
|
+
],
|
|
1525
|
+
datos.map((e) => [
|
|
1526
|
+
fecha2(e.creado),
|
|
1527
|
+
e.evento,
|
|
1528
|
+
e.estado,
|
|
1529
|
+
e.intentos,
|
|
1530
|
+
e.ultimoStatus,
|
|
1531
|
+
e.ultimoError,
|
|
1532
|
+
fecha2(e.proximoIntento),
|
|
1533
|
+
e.id
|
|
1534
|
+
]),
|
|
1535
|
+
[3]
|
|
1536
|
+
) : "Este webhook todav\xEDa no tiene entregas."
|
|
1537
|
+
);
|
|
1538
|
+
return void 0;
|
|
1539
|
+
}
|
|
1540
|
+
async function webhooksRotate(ctx) {
|
|
1541
|
+
const id = ctx.args[0];
|
|
1542
|
+
const { datos } = await ctx.api().pedir("POST", `/webhooks/${segmento(id)}/rotar-secreto`);
|
|
1543
|
+
mostrarSecreto(ctx, datos, "Secreto rotado: el anterior ya no firma.");
|
|
1544
|
+
return void 0;
|
|
1545
|
+
}
|
|
1153
1546
|
async function mcp(ctx) {
|
|
1154
1547
|
const api = ctx.api();
|
|
1155
1548
|
ctx.ent.stderr(
|
|
@@ -1162,7 +1555,9 @@ var GRUPOS = {
|
|
|
1162
1555
|
clientes: "Clientes guardados",
|
|
1163
1556
|
productos: "Productos y servicios guardados",
|
|
1164
1557
|
dte: "Documentos tributarios electr\xF3nicos",
|
|
1165
|
-
|
|
1558
|
+
eventos: "Eventos de retorno (18) y de operaciones especiales (17)",
|
|
1559
|
+
contingencia: "Documentos emitidos en contingencia",
|
|
1560
|
+
webhooks: "Notificaciones firmadas a su sistema cuando cambia un documento"
|
|
1166
1561
|
};
|
|
1167
1562
|
var COMANDOS = [
|
|
1168
1563
|
{
|
|
@@ -1288,11 +1683,59 @@ var COMANDOS = [
|
|
|
1288
1683
|
{
|
|
1289
1684
|
nombre: "dte invalidar",
|
|
1290
1685
|
uso: "dte invalidar <codigoGeneracion> --tipo <1|2|3> [--motivo <texto>] [--reemplazo <codigo>] [--yes]",
|
|
1291
|
-
resumen: "Invalida un DTE procesado (irreversible)",
|
|
1686
|
+
resumen: "Invalida un DTE procesado, o un evento de retorno u operaciones especiales (irreversible)",
|
|
1292
1687
|
opciones: ["tipo", "motivo", "reemplazo", "yes"],
|
|
1293
1688
|
args: [1, 1],
|
|
1294
1689
|
accion: dteInvalidar
|
|
1295
1690
|
},
|
|
1691
|
+
{
|
|
1692
|
+
nombre: "dte retorno",
|
|
1693
|
+
uso: "dte retorno <codigoGeneracion> <archivo.json | -> [--idempotency-key <k>] [--yes]",
|
|
1694
|
+
resumen: "Registra un retorno (evento 18) sobre una Factura, Exportaci\xF3n o Sujeto Excluido",
|
|
1695
|
+
opciones: ["idempotency-key", "yes"],
|
|
1696
|
+
args: [2, 2],
|
|
1697
|
+
accion: dteRetorno
|
|
1698
|
+
},
|
|
1699
|
+
{
|
|
1700
|
+
nombre: "eventos operaciones-especiales",
|
|
1701
|
+
uso: "eventos operaciones-especiales <archivo.json | -> [--idempotency-key <k>] [--yes]",
|
|
1702
|
+
resumen: "Informa facturas simplificadas o comprobantes de control interno (evento 17)",
|
|
1703
|
+
opciones: ["idempotency-key", "yes"],
|
|
1704
|
+
args: [1, 1],
|
|
1705
|
+
accion: eventosOperacionesEspeciales
|
|
1706
|
+
},
|
|
1707
|
+
{
|
|
1708
|
+
nombre: "eventos list",
|
|
1709
|
+
uso: "eventos list [--tipo retorno|operaciones_especiales]",
|
|
1710
|
+
resumen: "Lista los eventos de retorno y operaciones especiales",
|
|
1711
|
+
opciones: ["tipo"],
|
|
1712
|
+
args: [0, 0],
|
|
1713
|
+
accion: eventosList
|
|
1714
|
+
},
|
|
1715
|
+
{
|
|
1716
|
+
nombre: "eventos get",
|
|
1717
|
+
uso: "eventos get <codigoGeneracion>",
|
|
1718
|
+
resumen: "Muestra un evento",
|
|
1719
|
+
opciones: [],
|
|
1720
|
+
args: [1, 1],
|
|
1721
|
+
accion: eventosGet
|
|
1722
|
+
},
|
|
1723
|
+
{
|
|
1724
|
+
nombre: "eventos json",
|
|
1725
|
+
uso: "eventos json <codigoGeneracion> [-o <archivo.json>]",
|
|
1726
|
+
resumen: "Descarga el archivo JSON oficial del evento",
|
|
1727
|
+
opciones: ["output"],
|
|
1728
|
+
args: [1, 1],
|
|
1729
|
+
accion: eventosJson
|
|
1730
|
+
},
|
|
1731
|
+
{
|
|
1732
|
+
nombre: "eventos pdf",
|
|
1733
|
+
uso: "eventos pdf <codigoGeneracion> [-o <archivo.pdf>]",
|
|
1734
|
+
resumen: "Descarga la representaci\xF3n gr\xE1fica del evento",
|
|
1735
|
+
opciones: ["output"],
|
|
1736
|
+
args: [1, 1],
|
|
1737
|
+
accion: eventosPdf
|
|
1738
|
+
},
|
|
1296
1739
|
{
|
|
1297
1740
|
nombre: "contingencia transmitir",
|
|
1298
1741
|
uso: "contingencia transmitir [--yes]",
|
|
@@ -1301,6 +1744,54 @@ var COMANDOS = [
|
|
|
1301
1744
|
args: [0, 0],
|
|
1302
1745
|
accion: contingenciaTransmitir
|
|
1303
1746
|
},
|
|
1747
|
+
{
|
|
1748
|
+
nombre: "webhooks list",
|
|
1749
|
+
uso: "webhooks list",
|
|
1750
|
+
resumen: "Lista los webhooks (sin su secreto)",
|
|
1751
|
+
opciones: [],
|
|
1752
|
+
args: [0, 0],
|
|
1753
|
+
accion: webhooksList
|
|
1754
|
+
},
|
|
1755
|
+
{
|
|
1756
|
+
nombre: "webhooks add",
|
|
1757
|
+
uso: "webhooks add <https://url> [--eventos <e1,e2>] [--descripcion <texto>]",
|
|
1758
|
+
resumen: "Crea un webhook y muestra su secreto de firma una sola vez",
|
|
1759
|
+
opciones: ["eventos", "descripcion"],
|
|
1760
|
+
args: [1, 1],
|
|
1761
|
+
accion: webhooksAdd
|
|
1762
|
+
},
|
|
1763
|
+
{
|
|
1764
|
+
nombre: "webhooks remove",
|
|
1765
|
+
uso: "webhooks remove <id>",
|
|
1766
|
+
resumen: "Elimina un webhook y su historial de entregas",
|
|
1767
|
+
opciones: [],
|
|
1768
|
+
args: [1, 1],
|
|
1769
|
+
accion: webhooksRemove
|
|
1770
|
+
},
|
|
1771
|
+
{
|
|
1772
|
+
nombre: "webhooks test",
|
|
1773
|
+
uso: "webhooks test <id>",
|
|
1774
|
+
resumen: "Env\xEDa un evento webhook.prueba (salida 1 si no se entrega)",
|
|
1775
|
+
opciones: [],
|
|
1776
|
+
args: [1, 1],
|
|
1777
|
+
accion: webhooksTest
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
nombre: "webhooks deliveries",
|
|
1781
|
+
uso: "webhooks deliveries <id>",
|
|
1782
|
+
resumen: "\xDAltimas 50 entregas de un webhook",
|
|
1783
|
+
opciones: [],
|
|
1784
|
+
args: [1, 1],
|
|
1785
|
+
accion: webhooksDeliveries
|
|
1786
|
+
},
|
|
1787
|
+
{
|
|
1788
|
+
nombre: "webhooks rotate",
|
|
1789
|
+
uso: "webhooks rotate <id>",
|
|
1790
|
+
resumen: "Genera un secreto de firma nuevo y lo muestra una vez",
|
|
1791
|
+
opciones: [],
|
|
1792
|
+
args: [1, 1],
|
|
1793
|
+
accion: webhooksRotate
|
|
1794
|
+
},
|
|
1304
1795
|
{
|
|
1305
1796
|
nombre: "mcp",
|
|
1306
1797
|
uso: "mcp",
|
|
@@ -1337,7 +1828,7 @@ var OPCIONES = {
|
|
|
1337
1828
|
},
|
|
1338
1829
|
tipo: {
|
|
1339
1830
|
type: "string",
|
|
1340
|
-
ayuda: "Tipo de DTE (01, 03\u2026)
|
|
1831
|
+
ayuda: "Tipo de DTE (01, 03\u2026); al invalidar, tipo de anulaci\xF3n (1, 2, 3); en eventos, retorno | operaciones_especiales"
|
|
1341
1832
|
},
|
|
1342
1833
|
estado: {
|
|
1343
1834
|
type: "string",
|
|
@@ -1365,7 +1856,11 @@ var OPCIONES = {
|
|
|
1365
1856
|
telefono: { type: "string", ayuda: "Tel\xE9fono" },
|
|
1366
1857
|
correo: { type: "string", ayuda: "Correo electr\xF3nico" },
|
|
1367
1858
|
codigo: { type: "string", ayuda: "C\xF3digo del producto" },
|
|
1368
|
-
descripcion: { type: "string", ayuda: "Descripci\xF3n del producto" },
|
|
1859
|
+
descripcion: { type: "string", ayuda: "Descripci\xF3n (del producto o del webhook)" },
|
|
1860
|
+
eventos: {
|
|
1861
|
+
type: "string",
|
|
1862
|
+
ayuda: "Eventos del webhook separados por coma (por defecto todos): dte.procesado, dte.rechazado, dte.contingencia, dte.invalidado, retorno.procesado, operaciones_especiales.procesado"
|
|
1863
|
+
},
|
|
1369
1864
|
precio: { type: "string", ayuda: "Precio unitario SIN IVA" },
|
|
1370
1865
|
"tipo-item": {
|
|
1371
1866
|
type: "string",
|
package/package.json
CHANGED