niiko-cli 0.1.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 +39 -0
- package/dist/niiko.js +1263 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# niiko-cli
|
|
2
|
+
|
|
3
|
+
La API de acciones de niiko desde la terminal. **Generada** del mismo plan que los SDK, la referencia y el
|
|
4
|
+
servidor MCP (plan `9a8b80d285ff`): una acción nueva aparece al regenerar, y este repositorio no se edita a mano.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm i -g niiko-cli
|
|
8
|
+
niiko login --key nk_… # guarda la llave (0600) en ~/.config/niiko/config.json
|
|
9
|
+
niiko actions # las acciones abiertas, con su propósito
|
|
10
|
+
niiko describe miira.lead_create
|
|
11
|
+
niiko run miira.lead_create --input '{ … }'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
La salida es siempre JSON. El desenlace va en el **código de salida**: `0` hecho · `2` negada (el motivo, con
|
|
15
|
+
nombre, va en la salida) · `3` esperando una firma humana · `1` error de la CLI o del transporte. Así
|
|
16
|
+
`niiko run … && siguiente` hace lo correcto sin parsear nada.
|
|
17
|
+
|
|
18
|
+
La llave también se lee de `NIIKO_API_KEY` (y la URL de `NIIKO_URL`), para scripts que no deban tocar la
|
|
19
|
+
configuración de la persona. Cada `run` manda una `Idempotency-Key` (o la que le pases con
|
|
20
|
+
`--idempotency-key`): reintentar lo mismo no duplica el efecto.
|
|
21
|
+
|
|
22
|
+
## Las acciones de hoy
|
|
23
|
+
|
|
24
|
+
- `miira.lead_create` — Crea un lead nuevo en el CRM del workspace a partir de sus datos de contacto. Si ya existe uno que encaja, no lo duplica: contesta `ambiguous` con los candidatos.
|
|
25
|
+
- `crm.call_logged` — Anota en la ficha de un cliente, dicho por su nombre, lo que se habló en una llamada; opcionalmente deja creado el seguimiento con su fecha. No lee nada ni llama a nadie.
|
|
26
|
+
- `crm.owner_assigned` — Cambia de quién es un cliente, diciendo el nombre del cliente y el nombre (o correo) del miembro del equipo. Si alguno de los dos es ambiguo, se niega con la lista.
|
|
27
|
+
- `crm.stage_moved` — Mueve el negocio abierto de un cliente a otra etapa del pipeline, diciendo el nombre del cliente y el de la etapa. No crea negocios: sin uno abierto se niega, y con varios se niega con la lista.
|
|
28
|
+
- `kiipu.invoice_proposed` — Deja preparada una factura como BORRADOR para un cliente dicho por su nombre, con sus líneas e impuestos. NO la emite, NO la numera y NO cuenta como deuda: una persona la revisa y la emite en Kiipu. No crea el cliente si no existe.
|
|
29
|
+
- `crm.task_created` — Crea un recordatorio (tarea con fecha y hora) sobre un cliente dicho por su nombre. No anota una llamada: para eso está crm.call_logged. No crea el cliente si no existe.
|
|
30
|
+
- `crm.deal_created` — Abre un negocio nuevo en el pipeline para un cliente dicho por su nombre, con título, valor opcional en USD, etapa opcional (por nombre; sin ella, la primera) y responsable opcional. No comprueba si ya tiene otros abiertos: devuelve cuántos quedan para que se vea un duplicado. No lo gana ni lo pierde: eso es crm.stage_moved.
|
|
31
|
+
- `crm.note_added` — Guarda una nota en la ficha de un cliente dicho por su nombre: algo que hay que saber la próxima vez, sin llamada ni fecha. Para una llamada está crm.call_logged; para un recordatorio con fecha, crm.task_created.
|
|
32
|
+
- `crm.contact_added` — Añade una persona (nombre, y opcionalmente correo, teléfono y cargo) a la ficha de un cliente dicho por su nombre. No la hace contacto principal ni crea el cliente. Si ya había alguien con ese correo o teléfono, lo dice en la respuesta pero no lo impide.
|
|
33
|
+
- `miira.broadcast_quoted` — Presupuesta mandar el MISMO mensaje de WhatsApp a varios clientes dichos por su nombre (hasta 50). NO envía nada: dice, por cliente, si le llega el texto tal cual (ventana de 24 h abierta, gratis), si hace falta una plantilla aprobada y cuánto cuesta, o por qué no se le puede escribir. Devuelve un presupuesto firmado que vale 15 minutos; para enviar, llama a miira.broadcast_sent con él. Enséñale el presupuesto a la persona antes.
|
|
34
|
+
- `miira.broadcast_sent` — Envía la difusión de WhatsApp presupuestada por miira.broadcast_quoted, exactamente a quienes y como dijo el presupuesto. Si algo cambió (ventana, consentimiento, tarifa) se niega con el presupuesto nuevo para confirmarlo otra vez. Cuesta dinero cuando hay plantillas: no lo llames sin que la persona haya visto el coste.
|
|
35
|
+
- `kiipu.draft_voided` — Anula un BORRADOR de factura (uno creado con kiipu.invoice_proposed y todavía no emitido), por su id o por el nombre del cliente si es su único borrador. No anula facturas emitidas: eso es de una persona en Kiipu.
|
|
36
|
+
- `kiipu.payment_reported` — Deja en la cola de aprobación de Kiipu el aviso de que un cliente (por su nombre) pagó cierto monto de una factura abierta. NO aplica el pago ni toca saldos: una persona lo revisa contra el banco y lo aplica. Si el cliente tiene varias facturas abiertas hay que decir el número.
|
|
37
|
+
|
|
38
|
+
Hacen falta **dos** permisos para cada una: una llave con su alcance, y que el dueño del workspace la haya
|
|
39
|
+
encendido. Referencia completa: https://developers.niiko.org
|
package/dist/niiko.js
ADDED
|
@@ -0,0 +1,1263 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// GENERADO POR `tooling/build-cli.ts` del monorepo de niiko — NO SE EDITA A MANO.
|
|
3
|
+
// Plan `9a8b80d285ff`. Una acción nueva aparece aquí al regenerar; ninguna se añade editando este archivo.
|
|
4
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
/** El catálogo, tal como estaba en el plan `9a8b80d285ff`. */
|
|
8
|
+
const CATALOGO = [
|
|
9
|
+
{
|
|
10
|
+
"accion": "miira.lead_create",
|
|
11
|
+
"version": 1,
|
|
12
|
+
"alcance": "miira.lead_create@1",
|
|
13
|
+
"proposito": "Crea un lead nuevo en el CRM del workspace a partir de sus datos de contacto. Si ya existe uno que encaja, no lo duplica: contesta `ambiguous` con los candidatos.",
|
|
14
|
+
"motivos": [
|
|
15
|
+
"honeypot",
|
|
16
|
+
"invalid_email",
|
|
17
|
+
"disposable_email",
|
|
18
|
+
"invalid_identity"
|
|
19
|
+
],
|
|
20
|
+
"entrada": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {
|
|
23
|
+
"submissionId": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"format": "uuid"
|
|
26
|
+
},
|
|
27
|
+
"source": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"enum": [
|
|
30
|
+
"web_form",
|
|
31
|
+
"referral",
|
|
32
|
+
"api",
|
|
33
|
+
"import",
|
|
34
|
+
"event",
|
|
35
|
+
"other"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"name": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"maxLength": 256
|
|
41
|
+
},
|
|
42
|
+
"email": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"format": "email",
|
|
45
|
+
"maxLength": 320
|
|
46
|
+
},
|
|
47
|
+
"phone": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"maxLength": 40
|
|
50
|
+
},
|
|
51
|
+
"fields": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"additionalProperties": true
|
|
54
|
+
},
|
|
55
|
+
"consent": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"properties": {
|
|
58
|
+
"email": {
|
|
59
|
+
"type": "boolean"
|
|
60
|
+
},
|
|
61
|
+
"whatsapp": {
|
|
62
|
+
"type": "boolean"
|
|
63
|
+
},
|
|
64
|
+
"source": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"enum": [
|
|
67
|
+
"api",
|
|
68
|
+
"web_form",
|
|
69
|
+
"verbal",
|
|
70
|
+
"llamada",
|
|
71
|
+
"visita",
|
|
72
|
+
"imported"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"required": [
|
|
77
|
+
"source"
|
|
78
|
+
],
|
|
79
|
+
"additionalProperties": true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"required": [
|
|
83
|
+
"submissionId",
|
|
84
|
+
"source"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"salida": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"properties": {
|
|
90
|
+
"outcome": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"enum": [
|
|
93
|
+
"created",
|
|
94
|
+
"existing",
|
|
95
|
+
"ambiguous",
|
|
96
|
+
"rejected"
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
"clientId": {
|
|
100
|
+
"anyOf": [
|
|
101
|
+
{
|
|
102
|
+
"type": "string"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"type": "null"
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
"reason": {
|
|
110
|
+
"anyOf": [
|
|
111
|
+
{
|
|
112
|
+
"type": "string",
|
|
113
|
+
"enum": [
|
|
114
|
+
"honeypot",
|
|
115
|
+
"invalid_email",
|
|
116
|
+
"disposable_email",
|
|
117
|
+
"invalid_identity"
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"type": "null"
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"required": [
|
|
127
|
+
"outcome",
|
|
128
|
+
"clientId",
|
|
129
|
+
"reason"
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"accion": "crm.call_logged",
|
|
135
|
+
"version": 1,
|
|
136
|
+
"alcance": "crm.call_logged@1",
|
|
137
|
+
"proposito": "Anota en la ficha de un cliente, dicho por su nombre, lo que se habló en una llamada; opcionalmente deja creado el seguimiento con su fecha. No lee nada ni llama a nadie.",
|
|
138
|
+
"motivos": [
|
|
139
|
+
"cliente_no_encontrado",
|
|
140
|
+
"cliente_ambiguo",
|
|
141
|
+
"demasiados_clientes",
|
|
142
|
+
"sin_permiso"
|
|
143
|
+
],
|
|
144
|
+
"entrada": {
|
|
145
|
+
"type": "object",
|
|
146
|
+
"properties": {
|
|
147
|
+
"client": {
|
|
148
|
+
"type": "string",
|
|
149
|
+
"minLength": 1,
|
|
150
|
+
"maxLength": 200
|
|
151
|
+
},
|
|
152
|
+
"summary": {
|
|
153
|
+
"type": "string",
|
|
154
|
+
"minLength": 1,
|
|
155
|
+
"maxLength": 4000
|
|
156
|
+
},
|
|
157
|
+
"followUp": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"properties": {
|
|
160
|
+
"what": {
|
|
161
|
+
"type": "string",
|
|
162
|
+
"minLength": 1,
|
|
163
|
+
"maxLength": 500
|
|
164
|
+
},
|
|
165
|
+
"dueAt": {
|
|
166
|
+
"type": "string",
|
|
167
|
+
"format": "date-time"
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"required": [
|
|
171
|
+
"dueAt"
|
|
172
|
+
],
|
|
173
|
+
"additionalProperties": true
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"required": [
|
|
177
|
+
"client",
|
|
178
|
+
"summary"
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
"salida": {
|
|
182
|
+
"type": "object",
|
|
183
|
+
"properties": {
|
|
184
|
+
"clientId": {
|
|
185
|
+
"type": "string"
|
|
186
|
+
},
|
|
187
|
+
"clientName": {
|
|
188
|
+
"type": "string"
|
|
189
|
+
},
|
|
190
|
+
"activityId": {
|
|
191
|
+
"type": "string"
|
|
192
|
+
},
|
|
193
|
+
"followUpId": {
|
|
194
|
+
"anyOf": [
|
|
195
|
+
{
|
|
196
|
+
"type": "string"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"type": "null"
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
"required": [
|
|
205
|
+
"clientId",
|
|
206
|
+
"clientName",
|
|
207
|
+
"activityId",
|
|
208
|
+
"followUpId"
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"accion": "crm.owner_assigned",
|
|
214
|
+
"version": 1,
|
|
215
|
+
"alcance": "crm.owner_assigned@1",
|
|
216
|
+
"proposito": "Cambia de quién es un cliente, diciendo el nombre del cliente y el nombre (o correo) del miembro del equipo. Si alguno de los dos es ambiguo, se niega con la lista.",
|
|
217
|
+
"motivos": [
|
|
218
|
+
"cliente_no_encontrado",
|
|
219
|
+
"cliente_ambiguo",
|
|
220
|
+
"demasiados_clientes",
|
|
221
|
+
"persona_no_encontrada",
|
|
222
|
+
"persona_ambigua",
|
|
223
|
+
"sin_permiso"
|
|
224
|
+
],
|
|
225
|
+
"entrada": {
|
|
226
|
+
"type": "object",
|
|
227
|
+
"properties": {
|
|
228
|
+
"client": {
|
|
229
|
+
"type": "string",
|
|
230
|
+
"minLength": 1,
|
|
231
|
+
"maxLength": 200
|
|
232
|
+
},
|
|
233
|
+
"owner": {
|
|
234
|
+
"type": "string",
|
|
235
|
+
"minLength": 1,
|
|
236
|
+
"maxLength": 200
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
"required": [
|
|
240
|
+
"client",
|
|
241
|
+
"owner"
|
|
242
|
+
]
|
|
243
|
+
},
|
|
244
|
+
"salida": {
|
|
245
|
+
"type": "object",
|
|
246
|
+
"properties": {
|
|
247
|
+
"clientId": {
|
|
248
|
+
"type": "string"
|
|
249
|
+
},
|
|
250
|
+
"clientName": {
|
|
251
|
+
"type": "string"
|
|
252
|
+
},
|
|
253
|
+
"ownerUserId": {
|
|
254
|
+
"type": "string"
|
|
255
|
+
},
|
|
256
|
+
"ownerName": {
|
|
257
|
+
"type": "string"
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
"required": [
|
|
261
|
+
"clientId",
|
|
262
|
+
"clientName",
|
|
263
|
+
"ownerUserId",
|
|
264
|
+
"ownerName"
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"accion": "crm.stage_moved",
|
|
270
|
+
"version": 1,
|
|
271
|
+
"alcance": "crm.stage_moved@1",
|
|
272
|
+
"proposito": "Mueve el negocio abierto de un cliente a otra etapa del pipeline, diciendo el nombre del cliente y el de la etapa. No crea negocios: sin uno abierto se niega, y con varios se niega con la lista.",
|
|
273
|
+
"motivos": [
|
|
274
|
+
"cliente_no_encontrado",
|
|
275
|
+
"cliente_ambiguo",
|
|
276
|
+
"demasiados_clientes",
|
|
277
|
+
"sin_negocio_abierto",
|
|
278
|
+
"varios_negocios",
|
|
279
|
+
"etapa_no_encontrada",
|
|
280
|
+
"negocio_cerrado",
|
|
281
|
+
"ya_en_esa_etapa",
|
|
282
|
+
"sin_permiso"
|
|
283
|
+
],
|
|
284
|
+
"entrada": {
|
|
285
|
+
"type": "object",
|
|
286
|
+
"properties": {
|
|
287
|
+
"client": {
|
|
288
|
+
"type": "string",
|
|
289
|
+
"minLength": 1,
|
|
290
|
+
"maxLength": 200
|
|
291
|
+
},
|
|
292
|
+
"stage": {
|
|
293
|
+
"type": "string",
|
|
294
|
+
"minLength": 1,
|
|
295
|
+
"maxLength": 120
|
|
296
|
+
},
|
|
297
|
+
"lostReason": {
|
|
298
|
+
"type": "string",
|
|
299
|
+
"maxLength": 500
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
"required": [
|
|
303
|
+
"client",
|
|
304
|
+
"stage"
|
|
305
|
+
]
|
|
306
|
+
},
|
|
307
|
+
"salida": {
|
|
308
|
+
"type": "object",
|
|
309
|
+
"properties": {
|
|
310
|
+
"dealId": {
|
|
311
|
+
"type": "string"
|
|
312
|
+
},
|
|
313
|
+
"clientId": {
|
|
314
|
+
"type": "string"
|
|
315
|
+
},
|
|
316
|
+
"clientName": {
|
|
317
|
+
"type": "string"
|
|
318
|
+
},
|
|
319
|
+
"stageName": {
|
|
320
|
+
"type": "string"
|
|
321
|
+
},
|
|
322
|
+
"won": {
|
|
323
|
+
"type": "boolean"
|
|
324
|
+
},
|
|
325
|
+
"firstWon": {
|
|
326
|
+
"type": "boolean"
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
"required": [
|
|
330
|
+
"dealId",
|
|
331
|
+
"clientId",
|
|
332
|
+
"clientName",
|
|
333
|
+
"stageName",
|
|
334
|
+
"won",
|
|
335
|
+
"firstWon"
|
|
336
|
+
]
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
"accion": "kiipu.invoice_proposed",
|
|
341
|
+
"version": 1,
|
|
342
|
+
"alcance": "kiipu.invoice_proposed@1",
|
|
343
|
+
"proposito": "Deja preparada una factura como BORRADOR para un cliente dicho por su nombre, con sus líneas e impuestos. NO la emite, NO la numera y NO cuenta como deuda: una persona la revisa y la emite en Kiipu. No crea el cliente si no existe.",
|
|
344
|
+
"motivos": [
|
|
345
|
+
"cliente_no_encontrado",
|
|
346
|
+
"cliente_ambiguo",
|
|
347
|
+
"demasiados_clientes",
|
|
348
|
+
"sin_permiso"
|
|
349
|
+
],
|
|
350
|
+
"entrada": {
|
|
351
|
+
"type": "object",
|
|
352
|
+
"properties": {
|
|
353
|
+
"client": {
|
|
354
|
+
"type": "string",
|
|
355
|
+
"minLength": 1,
|
|
356
|
+
"maxLength": 200
|
|
357
|
+
},
|
|
358
|
+
"lines": {
|
|
359
|
+
"type": "array",
|
|
360
|
+
"items": {
|
|
361
|
+
"type": "object",
|
|
362
|
+
"properties": {
|
|
363
|
+
"concept": {
|
|
364
|
+
"type": "string",
|
|
365
|
+
"minLength": 1,
|
|
366
|
+
"maxLength": 500
|
|
367
|
+
},
|
|
368
|
+
"quantity": {
|
|
369
|
+
"type": "string",
|
|
370
|
+
"pattern": "^(?!0+(?:\\.0+)?$)\\d+(?:\\.\\d{1,3})?$"
|
|
371
|
+
},
|
|
372
|
+
"unitPriceUsd": {
|
|
373
|
+
"type": "string",
|
|
374
|
+
"pattern": "^\\d+(\\.\\d{1,2})?$"
|
|
375
|
+
},
|
|
376
|
+
"tax": {
|
|
377
|
+
"type": "string",
|
|
378
|
+
"enum": [
|
|
379
|
+
"exempt",
|
|
380
|
+
"itbms_7",
|
|
381
|
+
"itbms_10",
|
|
382
|
+
"itbms_15"
|
|
383
|
+
],
|
|
384
|
+
"default": "exempt"
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
"required": [
|
|
388
|
+
"concept",
|
|
389
|
+
"quantity",
|
|
390
|
+
"unitPriceUsd"
|
|
391
|
+
],
|
|
392
|
+
"additionalProperties": true
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
"dueAt": {
|
|
396
|
+
"type": "string",
|
|
397
|
+
"format": "date-time"
|
|
398
|
+
},
|
|
399
|
+
"series": {
|
|
400
|
+
"type": "string",
|
|
401
|
+
"pattern": "^[A-Za-z0-9-]{1,12}$"
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
"required": [
|
|
405
|
+
"client",
|
|
406
|
+
"lines"
|
|
407
|
+
]
|
|
408
|
+
},
|
|
409
|
+
"salida": {
|
|
410
|
+
"type": "object",
|
|
411
|
+
"properties": {
|
|
412
|
+
"invoiceId": {
|
|
413
|
+
"type": "string"
|
|
414
|
+
},
|
|
415
|
+
"clientId": {
|
|
416
|
+
"type": "string"
|
|
417
|
+
},
|
|
418
|
+
"clientName": {
|
|
419
|
+
"type": "string"
|
|
420
|
+
},
|
|
421
|
+
"status": {
|
|
422
|
+
"const": "draft"
|
|
423
|
+
},
|
|
424
|
+
"subtotalUsd": {
|
|
425
|
+
"type": "string"
|
|
426
|
+
},
|
|
427
|
+
"taxUsd": {
|
|
428
|
+
"type": "string"
|
|
429
|
+
},
|
|
430
|
+
"totalUsd": {
|
|
431
|
+
"type": "string"
|
|
432
|
+
},
|
|
433
|
+
"dueAt": {
|
|
434
|
+
"type": "string"
|
|
435
|
+
},
|
|
436
|
+
"series": {
|
|
437
|
+
"type": "string"
|
|
438
|
+
},
|
|
439
|
+
"issueAt": {
|
|
440
|
+
"const": "/kiipu/facturas"
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
"required": [
|
|
444
|
+
"invoiceId",
|
|
445
|
+
"clientId",
|
|
446
|
+
"clientName",
|
|
447
|
+
"status",
|
|
448
|
+
"subtotalUsd",
|
|
449
|
+
"taxUsd",
|
|
450
|
+
"totalUsd",
|
|
451
|
+
"dueAt",
|
|
452
|
+
"series",
|
|
453
|
+
"issueAt"
|
|
454
|
+
]
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
"accion": "crm.task_created",
|
|
459
|
+
"version": 1,
|
|
460
|
+
"alcance": "crm.task_created@1",
|
|
461
|
+
"proposito": "Crea un recordatorio (tarea con fecha y hora) sobre un cliente dicho por su nombre. No anota una llamada: para eso está crm.call_logged. No crea el cliente si no existe.",
|
|
462
|
+
"motivos": [
|
|
463
|
+
"cliente_no_encontrado",
|
|
464
|
+
"cliente_ambiguo",
|
|
465
|
+
"demasiados_clientes",
|
|
466
|
+
"sin_permiso"
|
|
467
|
+
],
|
|
468
|
+
"entrada": {
|
|
469
|
+
"type": "object",
|
|
470
|
+
"properties": {
|
|
471
|
+
"client": {
|
|
472
|
+
"type": "string",
|
|
473
|
+
"minLength": 1,
|
|
474
|
+
"maxLength": 200
|
|
475
|
+
},
|
|
476
|
+
"what": {
|
|
477
|
+
"type": "string",
|
|
478
|
+
"minLength": 1,
|
|
479
|
+
"maxLength": 500
|
|
480
|
+
},
|
|
481
|
+
"dueAt": {
|
|
482
|
+
"type": "string",
|
|
483
|
+
"format": "date-time"
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
"required": [
|
|
487
|
+
"client",
|
|
488
|
+
"what",
|
|
489
|
+
"dueAt"
|
|
490
|
+
]
|
|
491
|
+
},
|
|
492
|
+
"salida": {
|
|
493
|
+
"type": "object",
|
|
494
|
+
"properties": {
|
|
495
|
+
"clientId": {
|
|
496
|
+
"type": "string"
|
|
497
|
+
},
|
|
498
|
+
"clientName": {
|
|
499
|
+
"type": "string"
|
|
500
|
+
},
|
|
501
|
+
"taskId": {
|
|
502
|
+
"type": "string"
|
|
503
|
+
},
|
|
504
|
+
"dueAt": {
|
|
505
|
+
"type": "string"
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
"required": [
|
|
509
|
+
"clientId",
|
|
510
|
+
"clientName",
|
|
511
|
+
"taskId",
|
|
512
|
+
"dueAt"
|
|
513
|
+
]
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
"accion": "crm.deal_created",
|
|
518
|
+
"version": 1,
|
|
519
|
+
"alcance": "crm.deal_created@1",
|
|
520
|
+
"proposito": "Abre un negocio nuevo en el pipeline para un cliente dicho por su nombre, con título, valor opcional en USD, etapa opcional (por nombre; sin ella, la primera) y responsable opcional. No comprueba si ya tiene otros abiertos: devuelve cuántos quedan para que se vea un duplicado. No lo gana ni lo pierde: eso es crm.stage_moved.",
|
|
521
|
+
"motivos": [
|
|
522
|
+
"cliente_no_encontrado",
|
|
523
|
+
"cliente_ambiguo",
|
|
524
|
+
"demasiados_clientes",
|
|
525
|
+
"etapa_no_encontrada",
|
|
526
|
+
"etapa_cerrada",
|
|
527
|
+
"persona_no_encontrada",
|
|
528
|
+
"persona_ambigua",
|
|
529
|
+
"sin_permiso"
|
|
530
|
+
],
|
|
531
|
+
"entrada": {
|
|
532
|
+
"type": "object",
|
|
533
|
+
"properties": {
|
|
534
|
+
"client": {
|
|
535
|
+
"type": "string",
|
|
536
|
+
"minLength": 1,
|
|
537
|
+
"maxLength": 200
|
|
538
|
+
},
|
|
539
|
+
"title": {
|
|
540
|
+
"type": "string",
|
|
541
|
+
"minLength": 1,
|
|
542
|
+
"maxLength": 200
|
|
543
|
+
},
|
|
544
|
+
"valueUsd": {
|
|
545
|
+
"type": "string",
|
|
546
|
+
"pattern": "^\\d+(\\.\\d{1,2})?$"
|
|
547
|
+
},
|
|
548
|
+
"stage": {
|
|
549
|
+
"type": "string",
|
|
550
|
+
"minLength": 1,
|
|
551
|
+
"maxLength": 120
|
|
552
|
+
},
|
|
553
|
+
"owner": {
|
|
554
|
+
"type": "string",
|
|
555
|
+
"minLength": 1,
|
|
556
|
+
"maxLength": 200
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
"required": [
|
|
560
|
+
"client",
|
|
561
|
+
"title"
|
|
562
|
+
]
|
|
563
|
+
},
|
|
564
|
+
"salida": {
|
|
565
|
+
"type": "object",
|
|
566
|
+
"properties": {
|
|
567
|
+
"dealId": {
|
|
568
|
+
"type": "string"
|
|
569
|
+
},
|
|
570
|
+
"clientId": {
|
|
571
|
+
"type": "string"
|
|
572
|
+
},
|
|
573
|
+
"clientName": {
|
|
574
|
+
"type": "string"
|
|
575
|
+
},
|
|
576
|
+
"title": {
|
|
577
|
+
"type": "string"
|
|
578
|
+
},
|
|
579
|
+
"valueUsd": {
|
|
580
|
+
"anyOf": [
|
|
581
|
+
{
|
|
582
|
+
"type": "string"
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
"type": "null"
|
|
586
|
+
}
|
|
587
|
+
]
|
|
588
|
+
},
|
|
589
|
+
"stageName": {
|
|
590
|
+
"type": "string"
|
|
591
|
+
},
|
|
592
|
+
"ownerName": {
|
|
593
|
+
"anyOf": [
|
|
594
|
+
{
|
|
595
|
+
"type": "string"
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
"type": "null"
|
|
599
|
+
}
|
|
600
|
+
]
|
|
601
|
+
},
|
|
602
|
+
"openDeals": {
|
|
603
|
+
"type": "integer",
|
|
604
|
+
"minimum": 1
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
"required": [
|
|
608
|
+
"dealId",
|
|
609
|
+
"clientId",
|
|
610
|
+
"clientName",
|
|
611
|
+
"title",
|
|
612
|
+
"valueUsd",
|
|
613
|
+
"stageName",
|
|
614
|
+
"ownerName",
|
|
615
|
+
"openDeals"
|
|
616
|
+
]
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
"accion": "crm.note_added",
|
|
621
|
+
"version": 1,
|
|
622
|
+
"alcance": "crm.note_added@1",
|
|
623
|
+
"proposito": "Guarda una nota en la ficha de un cliente dicho por su nombre: algo que hay que saber la próxima vez, sin llamada ni fecha. Para una llamada está crm.call_logged; para un recordatorio con fecha, crm.task_created.",
|
|
624
|
+
"motivos": [
|
|
625
|
+
"cliente_no_encontrado",
|
|
626
|
+
"cliente_ambiguo",
|
|
627
|
+
"demasiados_clientes",
|
|
628
|
+
"sin_permiso"
|
|
629
|
+
],
|
|
630
|
+
"entrada": {
|
|
631
|
+
"type": "object",
|
|
632
|
+
"properties": {
|
|
633
|
+
"client": {
|
|
634
|
+
"type": "string",
|
|
635
|
+
"minLength": 1,
|
|
636
|
+
"maxLength": 200
|
|
637
|
+
},
|
|
638
|
+
"note": {
|
|
639
|
+
"type": "string",
|
|
640
|
+
"minLength": 1,
|
|
641
|
+
"maxLength": 2000
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
"required": [
|
|
645
|
+
"client",
|
|
646
|
+
"note"
|
|
647
|
+
]
|
|
648
|
+
},
|
|
649
|
+
"salida": {
|
|
650
|
+
"type": "object",
|
|
651
|
+
"properties": {
|
|
652
|
+
"clientId": {
|
|
653
|
+
"type": "string"
|
|
654
|
+
},
|
|
655
|
+
"clientName": {
|
|
656
|
+
"type": "string"
|
|
657
|
+
},
|
|
658
|
+
"noteId": {
|
|
659
|
+
"type": "string"
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
"required": [
|
|
663
|
+
"clientId",
|
|
664
|
+
"clientName",
|
|
665
|
+
"noteId"
|
|
666
|
+
]
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
"accion": "crm.contact_added",
|
|
671
|
+
"version": 1,
|
|
672
|
+
"alcance": "crm.contact_added@1",
|
|
673
|
+
"proposito": "Añade una persona (nombre, y opcionalmente correo, teléfono y cargo) a la ficha de un cliente dicho por su nombre. No la hace contacto principal ni crea el cliente. Si ya había alguien con ese correo o teléfono, lo dice en la respuesta pero no lo impide.",
|
|
674
|
+
"motivos": [
|
|
675
|
+
"cliente_no_encontrado",
|
|
676
|
+
"cliente_ambiguo",
|
|
677
|
+
"demasiados_clientes",
|
|
678
|
+
"sin_permiso"
|
|
679
|
+
],
|
|
680
|
+
"entrada": {
|
|
681
|
+
"type": "object",
|
|
682
|
+
"properties": {
|
|
683
|
+
"client": {
|
|
684
|
+
"type": "string",
|
|
685
|
+
"minLength": 1,
|
|
686
|
+
"maxLength": 200
|
|
687
|
+
},
|
|
688
|
+
"name": {
|
|
689
|
+
"type": "string",
|
|
690
|
+
"minLength": 1,
|
|
691
|
+
"maxLength": 200
|
|
692
|
+
},
|
|
693
|
+
"email": {
|
|
694
|
+
"type": "string",
|
|
695
|
+
"format": "email",
|
|
696
|
+
"maxLength": 320
|
|
697
|
+
},
|
|
698
|
+
"phone": {
|
|
699
|
+
"type": "string",
|
|
700
|
+
"minLength": 5,
|
|
701
|
+
"maxLength": 40
|
|
702
|
+
},
|
|
703
|
+
"role": {
|
|
704
|
+
"type": "string",
|
|
705
|
+
"minLength": 1,
|
|
706
|
+
"maxLength": 120
|
|
707
|
+
}
|
|
708
|
+
},
|
|
709
|
+
"required": [
|
|
710
|
+
"client",
|
|
711
|
+
"name"
|
|
712
|
+
]
|
|
713
|
+
},
|
|
714
|
+
"salida": {
|
|
715
|
+
"type": "object",
|
|
716
|
+
"properties": {
|
|
717
|
+
"clientId": {
|
|
718
|
+
"type": "string"
|
|
719
|
+
},
|
|
720
|
+
"clientName": {
|
|
721
|
+
"type": "string"
|
|
722
|
+
},
|
|
723
|
+
"contactId": {
|
|
724
|
+
"type": "string"
|
|
725
|
+
},
|
|
726
|
+
"possibleDuplicate": {
|
|
727
|
+
"type": "boolean"
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
"required": [
|
|
731
|
+
"clientId",
|
|
732
|
+
"clientName",
|
|
733
|
+
"contactId",
|
|
734
|
+
"possibleDuplicate"
|
|
735
|
+
]
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
"accion": "miira.broadcast_quoted",
|
|
740
|
+
"version": 1,
|
|
741
|
+
"alcance": "miira.broadcast_quoted@1",
|
|
742
|
+
"proposito": "Presupuesta mandar el MISMO mensaje de WhatsApp a varios clientes dichos por su nombre (hasta 50). NO envía nada: dice, por cliente, si le llega el texto tal cual (ventana de 24 h abierta, gratis), si hace falta una plantilla aprobada y cuánto cuesta, o por qué no se le puede escribir. Devuelve un presupuesto firmado que vale 15 minutos; para enviar, llama a miira.broadcast_sent con él. Enséñale el presupuesto a la persona antes.",
|
|
743
|
+
"motivos": [
|
|
744
|
+
"plantilla_invalida",
|
|
745
|
+
"tarifas_vencidas",
|
|
746
|
+
"sin_permiso"
|
|
747
|
+
],
|
|
748
|
+
"entrada": {
|
|
749
|
+
"type": "object",
|
|
750
|
+
"properties": {
|
|
751
|
+
"clients": {
|
|
752
|
+
"type": "array",
|
|
753
|
+
"items": {
|
|
754
|
+
"type": "string",
|
|
755
|
+
"minLength": 1,
|
|
756
|
+
"maxLength": 200
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
"message": {
|
|
760
|
+
"type": "string",
|
|
761
|
+
"minLength": 1,
|
|
762
|
+
"maxLength": 4096
|
|
763
|
+
},
|
|
764
|
+
"template": {
|
|
765
|
+
"type": "string",
|
|
766
|
+
"minLength": 1,
|
|
767
|
+
"maxLength": 512
|
|
768
|
+
},
|
|
769
|
+
"templateValues": {
|
|
770
|
+
"type": "array",
|
|
771
|
+
"items": {
|
|
772
|
+
"type": "string",
|
|
773
|
+
"maxLength": 1024
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
"required": [
|
|
778
|
+
"clients",
|
|
779
|
+
"message"
|
|
780
|
+
]
|
|
781
|
+
},
|
|
782
|
+
"salida": {
|
|
783
|
+
"type": "object",
|
|
784
|
+
"properties": {
|
|
785
|
+
"rows": {
|
|
786
|
+
"type": "array",
|
|
787
|
+
"items": {
|
|
788
|
+
"type": "object",
|
|
789
|
+
"properties": {
|
|
790
|
+
"client": {
|
|
791
|
+
"type": "string"
|
|
792
|
+
},
|
|
793
|
+
"clientId": {
|
|
794
|
+
"anyOf": [
|
|
795
|
+
{
|
|
796
|
+
"type": "string"
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
"type": "null"
|
|
800
|
+
}
|
|
801
|
+
]
|
|
802
|
+
},
|
|
803
|
+
"clientName": {
|
|
804
|
+
"anyOf": [
|
|
805
|
+
{
|
|
806
|
+
"type": "string"
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
"type": "null"
|
|
810
|
+
}
|
|
811
|
+
]
|
|
812
|
+
},
|
|
813
|
+
"outcome": {
|
|
814
|
+
"type": "string",
|
|
815
|
+
"enum": [
|
|
816
|
+
"free_text",
|
|
817
|
+
"template",
|
|
818
|
+
"needs_template",
|
|
819
|
+
"no_phone",
|
|
820
|
+
"suppressed",
|
|
821
|
+
"consent_revoked",
|
|
822
|
+
"no_marketing_consent",
|
|
823
|
+
"quiet_hours",
|
|
824
|
+
"not_found",
|
|
825
|
+
"ambiguous",
|
|
826
|
+
"too_many"
|
|
827
|
+
]
|
|
828
|
+
},
|
|
829
|
+
"costUsd": {
|
|
830
|
+
"anyOf": [
|
|
831
|
+
{
|
|
832
|
+
"type": "string"
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
"type": "null"
|
|
836
|
+
}
|
|
837
|
+
]
|
|
838
|
+
},
|
|
839
|
+
"candidates": {
|
|
840
|
+
"type": "array",
|
|
841
|
+
"items": {
|
|
842
|
+
"type": "string"
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
"required": [
|
|
847
|
+
"client",
|
|
848
|
+
"clientId",
|
|
849
|
+
"clientName",
|
|
850
|
+
"outcome",
|
|
851
|
+
"costUsd"
|
|
852
|
+
],
|
|
853
|
+
"additionalProperties": true
|
|
854
|
+
}
|
|
855
|
+
},
|
|
856
|
+
"totals": {
|
|
857
|
+
"type": "object",
|
|
858
|
+
"properties": {
|
|
859
|
+
"freeText": {
|
|
860
|
+
"type": "integer"
|
|
861
|
+
},
|
|
862
|
+
"template": {
|
|
863
|
+
"type": "integer"
|
|
864
|
+
},
|
|
865
|
+
"skipped": {
|
|
866
|
+
"type": "integer"
|
|
867
|
+
},
|
|
868
|
+
"costUsd": {
|
|
869
|
+
"type": "string"
|
|
870
|
+
}
|
|
871
|
+
},
|
|
872
|
+
"required": [
|
|
873
|
+
"freeText",
|
|
874
|
+
"template",
|
|
875
|
+
"skipped",
|
|
876
|
+
"costUsd"
|
|
877
|
+
],
|
|
878
|
+
"additionalProperties": true
|
|
879
|
+
},
|
|
880
|
+
"quote": {
|
|
881
|
+
"anyOf": [
|
|
882
|
+
{
|
|
883
|
+
"type": "string"
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
"type": "null"
|
|
887
|
+
}
|
|
888
|
+
]
|
|
889
|
+
},
|
|
890
|
+
"expiresAt": {
|
|
891
|
+
"anyOf": [
|
|
892
|
+
{
|
|
893
|
+
"type": "string"
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
"type": "null"
|
|
897
|
+
}
|
|
898
|
+
]
|
|
899
|
+
}
|
|
900
|
+
},
|
|
901
|
+
"required": [
|
|
902
|
+
"rows",
|
|
903
|
+
"totals",
|
|
904
|
+
"quote",
|
|
905
|
+
"expiresAt"
|
|
906
|
+
]
|
|
907
|
+
}
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
"accion": "miira.broadcast_sent",
|
|
911
|
+
"version": 1,
|
|
912
|
+
"alcance": "miira.broadcast_sent@1",
|
|
913
|
+
"proposito": "Envía la difusión de WhatsApp presupuestada por miira.broadcast_quoted, exactamente a quienes y como dijo el presupuesto. Si algo cambió (ventana, consentimiento, tarifa) se niega con el presupuesto nuevo para confirmarlo otra vez. Cuesta dinero cuando hay plantillas: no lo llames sin que la persona haya visto el coste.",
|
|
914
|
+
"motivos": [
|
|
915
|
+
"presupuesto_invalido",
|
|
916
|
+
"presupuesto_vencido",
|
|
917
|
+
"presupuesto_cambiado",
|
|
918
|
+
"plantilla_invalida",
|
|
919
|
+
"tarifas_vencidas",
|
|
920
|
+
"sin_permiso"
|
|
921
|
+
],
|
|
922
|
+
"entrada": {
|
|
923
|
+
"type": "object",
|
|
924
|
+
"properties": {
|
|
925
|
+
"quote": {
|
|
926
|
+
"type": "string",
|
|
927
|
+
"minLength": 1,
|
|
928
|
+
"maxLength": 65536
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
"required": [
|
|
932
|
+
"quote"
|
|
933
|
+
]
|
|
934
|
+
},
|
|
935
|
+
"salida": {
|
|
936
|
+
"type": "object",
|
|
937
|
+
"properties": {
|
|
938
|
+
"queued": {
|
|
939
|
+
"type": "array",
|
|
940
|
+
"items": {
|
|
941
|
+
"type": "object",
|
|
942
|
+
"properties": {
|
|
943
|
+
"clientId": {
|
|
944
|
+
"type": "string"
|
|
945
|
+
},
|
|
946
|
+
"clientName": {
|
|
947
|
+
"type": "string"
|
|
948
|
+
},
|
|
949
|
+
"mode": {
|
|
950
|
+
"type": "string",
|
|
951
|
+
"enum": [
|
|
952
|
+
"free_text",
|
|
953
|
+
"template"
|
|
954
|
+
]
|
|
955
|
+
},
|
|
956
|
+
"outboxId": {
|
|
957
|
+
"type": "string"
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
"required": [
|
|
961
|
+
"clientId",
|
|
962
|
+
"clientName",
|
|
963
|
+
"mode",
|
|
964
|
+
"outboxId"
|
|
965
|
+
],
|
|
966
|
+
"additionalProperties": true
|
|
967
|
+
}
|
|
968
|
+
},
|
|
969
|
+
"costUsd": {
|
|
970
|
+
"type": "string"
|
|
971
|
+
}
|
|
972
|
+
},
|
|
973
|
+
"required": [
|
|
974
|
+
"queued",
|
|
975
|
+
"costUsd"
|
|
976
|
+
]
|
|
977
|
+
}
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
"accion": "kiipu.draft_voided",
|
|
981
|
+
"version": 1,
|
|
982
|
+
"alcance": "kiipu.draft_voided@1",
|
|
983
|
+
"proposito": "Anula un BORRADOR de factura (uno creado con kiipu.invoice_proposed y todavía no emitido), por su id o por el nombre del cliente si es su único borrador. No anula facturas emitidas: eso es de una persona en Kiipu.",
|
|
984
|
+
"motivos": [
|
|
985
|
+
"cliente_no_encontrado",
|
|
986
|
+
"cliente_ambiguo",
|
|
987
|
+
"demasiados_clientes",
|
|
988
|
+
"borrador_no_encontrado",
|
|
989
|
+
"varios_borradores",
|
|
990
|
+
"no_es_borrador",
|
|
991
|
+
"sin_permiso"
|
|
992
|
+
],
|
|
993
|
+
"entrada": {
|
|
994
|
+
"type": "object",
|
|
995
|
+
"properties": {
|
|
996
|
+
"invoiceId": {
|
|
997
|
+
"type": "string",
|
|
998
|
+
"format": "uuid"
|
|
999
|
+
},
|
|
1000
|
+
"client": {
|
|
1001
|
+
"type": "string",
|
|
1002
|
+
"minLength": 1,
|
|
1003
|
+
"maxLength": 200
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
1006
|
+
"required": []
|
|
1007
|
+
},
|
|
1008
|
+
"salida": {
|
|
1009
|
+
"type": "object",
|
|
1010
|
+
"properties": {
|
|
1011
|
+
"invoiceId": {
|
|
1012
|
+
"type": "string"
|
|
1013
|
+
},
|
|
1014
|
+
"clientId": {
|
|
1015
|
+
"type": "string"
|
|
1016
|
+
},
|
|
1017
|
+
"clientName": {
|
|
1018
|
+
"type": "string"
|
|
1019
|
+
},
|
|
1020
|
+
"totalUsd": {
|
|
1021
|
+
"type": "string"
|
|
1022
|
+
},
|
|
1023
|
+
"status": {
|
|
1024
|
+
"const": "void"
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
"required": [
|
|
1028
|
+
"invoiceId",
|
|
1029
|
+
"clientId",
|
|
1030
|
+
"clientName",
|
|
1031
|
+
"totalUsd",
|
|
1032
|
+
"status"
|
|
1033
|
+
]
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
"accion": "kiipu.payment_reported",
|
|
1038
|
+
"version": 1,
|
|
1039
|
+
"alcance": "kiipu.payment_reported@1",
|
|
1040
|
+
"proposito": "Deja en la cola de aprobación de Kiipu el aviso de que un cliente (por su nombre) pagó cierto monto de una factura abierta. NO aplica el pago ni toca saldos: una persona lo revisa contra el banco y lo aplica. Si el cliente tiene varias facturas abiertas hay que decir el número.",
|
|
1041
|
+
"motivos": [
|
|
1042
|
+
"cliente_no_encontrado",
|
|
1043
|
+
"cliente_ambiguo",
|
|
1044
|
+
"demasiados_clientes",
|
|
1045
|
+
"sin_factura_abierta",
|
|
1046
|
+
"varias_facturas",
|
|
1047
|
+
"factura_no_encontrada",
|
|
1048
|
+
"sin_permiso"
|
|
1049
|
+
],
|
|
1050
|
+
"entrada": {
|
|
1051
|
+
"type": "object",
|
|
1052
|
+
"properties": {
|
|
1053
|
+
"client": {
|
|
1054
|
+
"type": "string",
|
|
1055
|
+
"minLength": 1,
|
|
1056
|
+
"maxLength": 200
|
|
1057
|
+
},
|
|
1058
|
+
"amountUsd": {
|
|
1059
|
+
"type": "string",
|
|
1060
|
+
"pattern": "^\\d+(\\.\\d{1,2})?$"
|
|
1061
|
+
},
|
|
1062
|
+
"invoice": {
|
|
1063
|
+
"type": "string",
|
|
1064
|
+
"minLength": 1,
|
|
1065
|
+
"maxLength": 40
|
|
1066
|
+
}
|
|
1067
|
+
},
|
|
1068
|
+
"required": [
|
|
1069
|
+
"client",
|
|
1070
|
+
"amountUsd"
|
|
1071
|
+
]
|
|
1072
|
+
},
|
|
1073
|
+
"salida": {
|
|
1074
|
+
"type": "object",
|
|
1075
|
+
"properties": {
|
|
1076
|
+
"submissionId": {
|
|
1077
|
+
"type": "string"
|
|
1078
|
+
},
|
|
1079
|
+
"clientId": {
|
|
1080
|
+
"type": "string"
|
|
1081
|
+
},
|
|
1082
|
+
"clientName": {
|
|
1083
|
+
"type": "string"
|
|
1084
|
+
},
|
|
1085
|
+
"invoiceId": {
|
|
1086
|
+
"type": "string"
|
|
1087
|
+
},
|
|
1088
|
+
"invoiceNumber": {
|
|
1089
|
+
"type": "string"
|
|
1090
|
+
},
|
|
1091
|
+
"outstandingUsd": {
|
|
1092
|
+
"type": "string"
|
|
1093
|
+
},
|
|
1094
|
+
"declaredUsd": {
|
|
1095
|
+
"type": "string"
|
|
1096
|
+
},
|
|
1097
|
+
"status": {
|
|
1098
|
+
"const": "received"
|
|
1099
|
+
},
|
|
1100
|
+
"reviewAt": {
|
|
1101
|
+
"const": "/kiipu/aprobaciones"
|
|
1102
|
+
}
|
|
1103
|
+
},
|
|
1104
|
+
"required": [
|
|
1105
|
+
"submissionId",
|
|
1106
|
+
"clientId",
|
|
1107
|
+
"clientName",
|
|
1108
|
+
"invoiceId",
|
|
1109
|
+
"invoiceNumber",
|
|
1110
|
+
"outstandingUsd",
|
|
1111
|
+
"declaredUsd",
|
|
1112
|
+
"status",
|
|
1113
|
+
"reviewAt"
|
|
1114
|
+
]
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
];
|
|
1118
|
+
const RUTA_CONFIG = join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "niiko", "config.json");
|
|
1119
|
+
function leerConfig() {
|
|
1120
|
+
if (!existsSync(RUTA_CONFIG))
|
|
1121
|
+
return null;
|
|
1122
|
+
try {
|
|
1123
|
+
const c = JSON.parse(readFileSync(RUTA_CONFIG, "utf8"));
|
|
1124
|
+
return typeof c.url === "string" && typeof c.apiKey === "string" ? { url: c.url, apiKey: c.apiKey } : null;
|
|
1125
|
+
}
|
|
1126
|
+
catch {
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
/** La llave: `--key`, o `NIIKO_API_KEY`, o la guardada. En ese orden, para que un script pueda mandar sin tocar
|
|
1131
|
+
* la configuración de la persona. Nunca se imprime. */
|
|
1132
|
+
function credenciales(args) {
|
|
1133
|
+
const guardada = leerConfig();
|
|
1134
|
+
const url = args.get("url") ?? process.env.NIIKO_URL ?? guardada?.url ?? "https://niiko.org";
|
|
1135
|
+
const apiKey = args.get("key") ?? process.env.NIIKO_API_KEY ?? guardada?.apiKey;
|
|
1136
|
+
return apiKey ? { url: url.replace(/\/+$/, ""), apiKey } : null;
|
|
1137
|
+
}
|
|
1138
|
+
function salir(codigo, cuerpo) {
|
|
1139
|
+
process.stdout.write(JSON.stringify(cuerpo, null, 2) + "\n");
|
|
1140
|
+
process.exit(codigo);
|
|
1141
|
+
}
|
|
1142
|
+
/** `--a b` y `--a=b`. Lo que no empieza por `--` es posicional. */
|
|
1143
|
+
function parsear(argv) {
|
|
1144
|
+
const posicionales = [];
|
|
1145
|
+
const args = new Map();
|
|
1146
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1147
|
+
const a = argv[i];
|
|
1148
|
+
if (a.startsWith("--")) {
|
|
1149
|
+
const [k, v] = a.slice(2).split("=", 2);
|
|
1150
|
+
if (v !== undefined)
|
|
1151
|
+
args.set(k, v);
|
|
1152
|
+
else if (i + 1 < argv.length && !argv[i + 1].startsWith("--"))
|
|
1153
|
+
args.set(k, argv[++i]);
|
|
1154
|
+
else
|
|
1155
|
+
args.set(k, "");
|
|
1156
|
+
}
|
|
1157
|
+
else
|
|
1158
|
+
posicionales.push(a);
|
|
1159
|
+
}
|
|
1160
|
+
return { posicionales, args };
|
|
1161
|
+
}
|
|
1162
|
+
function leerEntrada(args) {
|
|
1163
|
+
const crudo = args.get("input");
|
|
1164
|
+
let texto;
|
|
1165
|
+
if (crudo === undefined) {
|
|
1166
|
+
if (process.stdin.isTTY)
|
|
1167
|
+
salir(1, { error: "falta la entrada: --input '{…}', --input @archivo, o por stdin" });
|
|
1168
|
+
texto = readFileSync(0, "utf8");
|
|
1169
|
+
}
|
|
1170
|
+
else if (crudo.startsWith("@"))
|
|
1171
|
+
texto = readFileSync(crudo.slice(1), "utf8");
|
|
1172
|
+
else
|
|
1173
|
+
texto = crudo;
|
|
1174
|
+
try {
|
|
1175
|
+
return JSON.parse(texto);
|
|
1176
|
+
}
|
|
1177
|
+
catch {
|
|
1178
|
+
return salir(1, { error: "la entrada no es JSON válido" });
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
function uuid() {
|
|
1182
|
+
return globalThis.crypto.randomUUID();
|
|
1183
|
+
}
|
|
1184
|
+
const AYUDA = `niiko — la API de acciones de niiko desde la terminal (plan 9a8b80d285ff)
|
|
1185
|
+
|
|
1186
|
+
niiko login --url https://niiko.org --key nk_… guarda la llave en ${RUTA_CONFIG}
|
|
1187
|
+
niiko actions las acciones abiertas, con su propósito
|
|
1188
|
+
niiko describe <accion> entrada, salida y motivos de una acción
|
|
1189
|
+
niiko run <accion> --input '{…}' ejerce una acción (también --input @archivo o stdin)
|
|
1190
|
+
[--idempotency-key K] [--key nk_…] [--url …]
|
|
1191
|
+
|
|
1192
|
+
Códigos de salida de \`run\`: 0 hecho · 2 negada (el motivo va en la salida) · 3 esperando una firma · 1 error.
|
|
1193
|
+
La llave también se lee de NIIKO_API_KEY y la URL de NIIKO_URL.`;
|
|
1194
|
+
async function main() {
|
|
1195
|
+
const { posicionales, args } = parsear(process.argv.slice(2));
|
|
1196
|
+
const cmd = posicionales[0];
|
|
1197
|
+
if (!cmd || cmd === "help" || args.has("help")) {
|
|
1198
|
+
process.stdout.write(AYUDA + "\n");
|
|
1199
|
+
process.exit(0);
|
|
1200
|
+
}
|
|
1201
|
+
if (cmd === "login") {
|
|
1202
|
+
const apiKey = args.get("key") ?? process.env.NIIKO_API_KEY;
|
|
1203
|
+
if (!apiKey)
|
|
1204
|
+
salir(1, { error: "falta --key nk_… (o NIIKO_API_KEY)" });
|
|
1205
|
+
const url = (args.get("url") ?? process.env.NIIKO_URL ?? "https://niiko.org").replace(/\/+$/, "");
|
|
1206
|
+
mkdirSync(join(RUTA_CONFIG, ".."), { recursive: true });
|
|
1207
|
+
writeFileSync(RUTA_CONFIG, JSON.stringify({ url, apiKey }, null, 2) + "\n", { mode: 0o600 });
|
|
1208
|
+
// Se dice DÓNDE quedó y con qué prefijo, nunca la llave entera: la terminal es un historial.
|
|
1209
|
+
salir(0, { guardado: RUTA_CONFIG, url, key: apiKey.slice(0, 7) + "…" });
|
|
1210
|
+
}
|
|
1211
|
+
if (cmd === "actions") {
|
|
1212
|
+
salir(0, CATALOGO.map((a) => ({ accion: a.accion, version: a.version, alcance: a.alcance, proposito: a.proposito })));
|
|
1213
|
+
}
|
|
1214
|
+
if (cmd === "describe") {
|
|
1215
|
+
const id = posicionales[1];
|
|
1216
|
+
const a = CATALOGO.find((x) => x.accion === id);
|
|
1217
|
+
if (!a)
|
|
1218
|
+
salir(1, { error: `no existe la acción \`${id}\``, disponibles: CATALOGO.map((x) => x.accion) });
|
|
1219
|
+
salir(0, a);
|
|
1220
|
+
}
|
|
1221
|
+
if (cmd === "run") {
|
|
1222
|
+
const id = posicionales[1];
|
|
1223
|
+
const a = CATALOGO.find((x) => x.accion === id);
|
|
1224
|
+
if (!a)
|
|
1225
|
+
salir(1, { error: `no existe la acción \`${id}\``, disponibles: CATALOGO.map((x) => x.accion) });
|
|
1226
|
+
const cred = credenciales(args);
|
|
1227
|
+
if (!cred)
|
|
1228
|
+
salir(1, { error: "sin llave: niiko login --key nk_…, o NIIKO_API_KEY, o --key" });
|
|
1229
|
+
const entrada = leerEntrada(args);
|
|
1230
|
+
const clave = args.get("idempotency-key") ?? uuid();
|
|
1231
|
+
let r;
|
|
1232
|
+
try {
|
|
1233
|
+
r = await fetch(`${cred.url}/api/v1/actions/${a.accion}`, {
|
|
1234
|
+
method: "POST",
|
|
1235
|
+
headers: {
|
|
1236
|
+
Authorization: `Bearer ${cred.apiKey}`,
|
|
1237
|
+
"Content-Type": "application/json",
|
|
1238
|
+
// Siempre con clave: sin ella un reintento por timeout duplica el efecto, y el servidor lo exige.
|
|
1239
|
+
"Idempotency-Key": clave,
|
|
1240
|
+
"User-Agent": "niiko-cli/9a8b80d285ff",
|
|
1241
|
+
},
|
|
1242
|
+
body: JSON.stringify(entrada),
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
catch (e) {
|
|
1246
|
+
salir(1, { error: "no se pudo conectar", detalle: String(e), url: cred.url });
|
|
1247
|
+
}
|
|
1248
|
+
const cuerpo = await r.json().catch(() => null);
|
|
1249
|
+
const status = cuerpo?.status;
|
|
1250
|
+
// La llave de idempotencia viaja en la salida aunque el servidor no la eche: es lo que permite reintentar
|
|
1251
|
+
// EXACTAMENTE lo mismo si la terminal murió antes de leer la respuesta.
|
|
1252
|
+
const salida = typeof cuerpo === "object" && cuerpo !== null ? { ...cuerpo, idempotencyKey: cuerpo.idempotencyKey ?? clave } : { http: r.status, cuerpo };
|
|
1253
|
+
if (status === "done")
|
|
1254
|
+
salir(0, salida);
|
|
1255
|
+
if (status === "pending_approval")
|
|
1256
|
+
salir(3, salida);
|
|
1257
|
+
if (status === "refused")
|
|
1258
|
+
salir(2, salida);
|
|
1259
|
+
salir(1, { error: `respuesta inesperada (HTTP ${r.status})`, ...salida });
|
|
1260
|
+
}
|
|
1261
|
+
salir(1, { error: `comando desconocido: ${cmd}`, ayuda: "niiko help" });
|
|
1262
|
+
}
|
|
1263
|
+
main().catch((e) => salir(1, { error: String(e) }));
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "niiko-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "La API de acciones de niiko desde la terminal. Generada del mismo plan que los SDK.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"niiko": "./dist/niiko.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=20"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"prepublishOnly": "tsc"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"typescript": "^5.6.0",
|
|
23
|
+
"@types/node": "^22.10.0"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/vorluno/niiko-cli"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://developers.niiko.org"
|
|
30
|
+
}
|