nexusflex-mcp 3.75.0 → 3.76.1
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/.claude-plugin/plugin.json +2 -2
- package/api.mjs +9 -0
- package/docs.mjs +28 -5
- package/package.json +1 -1
- package/server.mjs +78 -49
- package/skills/alta-inicial-nodo/SKILL.md +37 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexusflex",
|
|
3
|
-
"description": "Nexus Flex en Claude: el MCP (altas, consultas, control por foto; nunca mueve dinero) y las skills (control por foto) en un solo instalable.",
|
|
4
|
-
"version": "3.
|
|
3
|
+
"description": "Nexus Flex en Claude: el MCP (altas, consultas, control por foto; nunca mueve dinero) y las skills (control por foto, alta inicial del nodo) en un solo instalable.",
|
|
4
|
+
"version": "3.76.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Nexus Flex",
|
|
7
7
|
"url": "https://nexusflex.com.ar"
|
package/api.mjs
CHANGED
|
@@ -32,6 +32,7 @@ const RUTAS_PROHIBIDAS = [
|
|
|
32
32
|
/^\/balance/i, /^\/reportes/i, /^\/programacion/i, /^\/recuento/i, /^\/whatsapp/i,
|
|
33
33
|
/^\/logisticas\/[^/]*\/cargo/i, /^\/logisticas\/saldos/i, /^\/logisticas\/facturar/i,
|
|
34
34
|
/^\/logisticas\/costo/i, /\/facturacion/i,
|
|
35
|
+
/^\/afiliados\/comisiones\/liquidar/i, // registra el pago al afiliado: es mover plata (dueño 02/10)
|
|
35
36
|
];
|
|
36
37
|
|
|
37
38
|
/** Timeout de red por llamada (ms). Un backend colgado no debe colgar el tool. */
|
|
@@ -58,8 +59,16 @@ const OPERACIONES_OK = [
|
|
|
58
59
|
{ metodo: "POST", ruta: /^\/afip\/preparar-cliente\/?$/i },
|
|
59
60
|
];
|
|
60
61
|
|
|
62
|
+
// Lecturas habilitadas aunque el path empiece como uno de dinero: GET /cuentas-vinculadas solo
|
|
63
|
+
// dice qué tiendas (ML/TN/TNG) tiene conectadas un cliente/nodo, sin saldos (mismo carve-out que
|
|
64
|
+
// RUTAS_MCP_LECTURA_OK en backend/src/middleware/auth.ts; el ancla exige "-vinculadas", no abre /cuentas).
|
|
65
|
+
const LECTURAS_OK = [
|
|
66
|
+
/^\/cuentas-vinculadas\/?$/i,
|
|
67
|
+
];
|
|
68
|
+
|
|
61
69
|
function guard(method, path) {
|
|
62
70
|
const clean = path.split("?")[0];
|
|
71
|
+
if (String(method).toUpperCase() === "GET" && LECTURAS_OK.some((re) => re.test(clean))) return;
|
|
63
72
|
if (OPERACIONES_OK.some((o) => o.metodo === String(method).toUpperCase() && o.ruta.test(clean))) return;
|
|
64
73
|
if (RUTAS_PROHIBIDAS.some((re) => re.test(clean))) {
|
|
65
74
|
throw new Error(`Ruta bloqueada por política del MCP (endpoints de dinero deshabilitados): ${clean}`);
|
package/docs.mjs
CHANGED
|
@@ -33,9 +33,9 @@ const TOPICS = {
|
|
|
33
33
|
"concilia caja, no imputa cobros/haberes en cuentas corrientes. Hay una denylist",
|
|
34
34
|
"de dinero (mcpMoneyGuard) heredada del backend. Las \xFAnicas escrituras 'con",
|
|
35
35
|
"olor a plata' son configuraciones de TARIFA (colecta_configurar, precio_",
|
|
36
|
-
"actualizar, afiliacion_crear) o el REGISTRO de
|
|
37
|
-
"
|
|
38
|
-
"
|
|
36
|
+
"actualizar, afiliacion_crear) o el REGISTRO de un precio acordado (ruta_adjudicar).",
|
|
37
|
+
"Liquidar/pagar comisiones de afiliados NO se hace por MCP (se hace en la app).",
|
|
38
|
+
"Ver tema facturacion_marketplace."
|
|
39
39
|
].join("\n"),
|
|
40
40
|
aislamiento: [
|
|
41
41
|
"AISLAMIENTO ENTRE NODOS",
|
|
@@ -154,12 +154,13 @@ const GROUPS = [
|
|
|
154
154
|
{ titulo: "Colecta y zonas (staff)", tools: ["colecta_ver", "colecta_pendientes", "colecta_historial", "colecta_configurar", "colecta_fija", "colecta_asignar", "colecta_desasignar", "zonas_reparto", "zona_barrios", "zonas_simetria", "asignar_mensajero_zona", "asignar_nodo_zona", "envios_por_zona", "procesar_zona", "rechazar_zona", "grupos_tarifas", "nodo_alias_poner", "zona_dejar", "nodo_link_confirmacion", "zonas_grupo", "zona_mover_metazona", "zona_componer_grupo", "grupo_tarifa_set", "retiros_cargar"] },
|
|
155
155
|
{ titulo: "Rendiciones y reclamos", tools: ["rendiciones", "rendicion_revertir", "reclamos_listar", "reclamo_responder"] },
|
|
156
156
|
{ titulo: "Marketplace de rutas", tools: ["rutas_publicas", "mis_rutas", "ruta_ofertas", "ruta_publicar", "ruta_ofertar", "ruta_adjudicar"] },
|
|
157
|
-
{ titulo: "Afiliados", tools: ["afiliacion_listar", "comisiones_afiliado_ver", "afiliado_crear", "afiliacion_crear", "afiliacion_editar"
|
|
157
|
+
{ titulo: "Afiliados", tools: ["afiliacion_listar", "comisiones_afiliado_ver", "afiliado_crear", "afiliacion_crear", "afiliacion_editar"] },
|
|
158
158
|
{ titulo: "M\xE9tricas / KPIs", tools: ["kpi_nodo", "kpi_red", "metricas_por_tipo", "top_productos"] },
|
|
159
159
|
{ titulo: "WMS del nodo", tools: ["stock_nodo", "productos_nodo", "producto_crear"] },
|
|
160
160
|
{ titulo: "Gesti\xF3n del nodo", tools: ["clientes_del_nodo", "cliente_crear", "cliente_editar", "sucursales_cliente", "sucursal_perfil", "precios_ver", "precio_actualizar", "cuentas_vinculadas", "generar_enlace_vinculacion", "facturacion_estado", "facturacion_preparar_cliente"] },
|
|
161
161
|
{ titulo: "Marketing / atribuci\xF3n", tools: ["marketing_config_ver", "marketing_config_guardar"] },
|
|
162
162
|
{ titulo: "Liquidaciones", tools: ["liquidacion_buscar", "liquidacion_detalle", "liquidaciones_sin_recibir", "liquidacion_excluir_envio", "liquidacion_reincluir_envio", "liquidacion_cobro_parcial", "cuenta_semanal_nodos"] },
|
|
163
|
+
{ titulo: "Alta inicial del nodo (migraci\xF3n)", tools: ["alta_nodo_columnas", "alta_nodo_previsualizar", "alta_nodo_confirmar"] },
|
|
163
164
|
{ titulo: "Usuarios / choferes", tools: ["chofer_listar", "chofer_crear", "chofer_editar", "usuario_editar", "operador_crear", "cliente_generar_usuario", "cliente_resetear_clave", "usuario_habilitar_reparto"] },
|
|
164
165
|
{ titulo: "Administraci\xF3n (red)", tools: ["nodos_listar", "nodo_crear", "usuario_habilitar_rol", "grupo_crear", "logo_subir", "sugerencias_listar", "cierre_semanal_links", "nodo_renombrar", "grupo_miembros", "grupo_sumar_nodo", "grupo_expulsar_nodo", "grupo_admin_permiso"] }
|
|
165
166
|
];
|
|
@@ -258,7 +259,6 @@ const TOOL_DOCS = {
|
|
|
258
259
|
afiliado_crear: { que: "Alta de un afiliado (quien trae volumen nuevo a la red).", uso: "nombre + tipo (nodo|mensajero|externo); refId = id del nodo/mensajero (null si externo).", ej: "afiliado_crear nombre:'Juan Ref' tipo:externo.", no: "No crea la afiliaci\xF3n/comisi\xF3n (afiliacion_crear); no toca dinero." },
|
|
259
260
|
afiliacion_crear: { que: "Vincula un afiliado con una entidad referida (nodo o cliente) y su comisi\xF3n RECURRENTE por env\xEDo.", uso: "afiliado + entidadTipo (nodo|cliente) + entidad + tipoComision (porcentaje|montoFijo) + valorComision; fechaExpiracion opcional. Una entidad = un afiliado activo.", ej: "afiliacion_crear afiliado:'Juan Ref' entidadTipo:cliente entidad:'Distri Sur' tipoComision:montoFijo valorComision:50.", no: "No paga (comision_liquidar); es config, no mueve dinero." },
|
|
260
261
|
afiliacion_editar: { que: "Edita una afiliaci\xF3n: valorComision, fechaExpiracion (renovar/extender) y/o activa.", uso: "id + los campos a cambiar.", ej: "afiliacion_editar id:5 activa:false \u2192 la das de baja.", no: "No toca dinero." },
|
|
261
|
-
comision_liquidar: { que: "Marca comisiones devengadas como LIQUIDADAS (registra el pago al afiliado).", uso: "ids = lista de comisiones (de comisiones_afiliado_ver). Requiere permiso de finanzas. Scopeado a tu nodo.", ej: "comision_liquidar ids:[10,11,12].", no: "No calcula comisiones (se devengan solas por env\xEDo); no factura." },
|
|
262
262
|
kpi_nodo: { que: "Tablero del nodo: P&L, top clientes, ca\xEDdas.", uso: "desde/hasta opcionales; nodo solo para admin.", ej: "kpi_nodo desde:2026-08-01 \u2192 resultado del mes.", no: "No baja a un vendedor puntual; solo tu nodo (salvo admin)." },
|
|
263
263
|
kpi_red: { que: "KPIs globales de la red.", uso: "Sin par\xE1metros. Solo admin global.", ej: "kpi_red \u2192 panorama SaaS de toda la red.", no: "No desglosa por nodo (us\xE1 kpi_nodo con nodo)." },
|
|
264
264
|
metricas_por_tipo: { que: "M\xE9tricas por TIPO de env\xEDo (flex/tienda/manual): total, entregados, tasa y % entregado antes de las 21hs. Para admin desglosa por log\xEDstica.", uso: "desde/hasta YYYY-MM-DD (default 30 d\xEDas); nodo solo admin.", ej: "metricas_por_tipo \u2192 ver si los Flex se entregan a tiempo.", no: "No es por cliente." },
|
|
@@ -286,6 +286,9 @@ const TOOL_DOCS = {
|
|
|
286
286
|
usuario_editar: { que: "Corrige nombre, apellido, tel\xE9fono y/o email de un usuario de tu nodo.", uso: "id + los campos a cambiar. Si el usuario todav\xEDa no tiene nombre y apellido separados, pas\xE1 los dos. Requiere permiso 'usuarios'.", ej: "usuario_editar id:7 nombre:'Sol' apellido:'Mart\xEDnez'.", no: "No toca rol, permisos ni clave. No cambia el nombre con el que se le paga al cadete." },
|
|
287
287
|
operador_crear: { que: "Da de alta un OPERADOR (staff que administra el nodo: recibe/escanea/despacha, liquida, etc.) en tu nodo, con una CLAVE TEMPORAL que devuelve el server.", uso: "nombre + apellido + email + telefono; permisos opcional (default 'logistica'): gestion, finanzas, mensajeros, reportes, logistica, wms, precios, usuarios. El admin global elige el nodo con `nodo`. Requiere permiso 'usuarios'. Pasale la clave; la cambia al primer ingreso.", ej: "operador_crear nombre:'Ana' apellido:'G\xF3mez' email:'ana@nodo.com' telefono:'11...' permisos:['logistica','gestion'].", no: "No es un vendedor (cliente_generar_usuario) ni un chofer (chofer_crear); no otorga admin de nodo (usuario_habilitar_rol); Claude nunca inventa la clave; no toca dinero." },
|
|
288
288
|
cliente_generar_usuario: { que: "Le crea las credenciales de login a un cliente/vendedor YA EXISTENTE de tu nodo (para que entre a su portal) y devuelve una CLAVE TEMPORAL.", uso: "cliente (nombre o id, de clientes_del_nodo) + email para loguear + nombre y apellido de la PERSONA que entra (obligatorios; no el de la tienda); telefono opcional. Requiere permiso 'usuarios'. Admin global: `nodo` para elegir/acotar el nodo. Si el cliente ya tiene usuario, avisa (no lo pisa).", ej: "cliente_generar_usuario cliente:'Distri Sur' email:'ventas@distrisur.com' nombre:'Juan' apellido:'P\xE9rez'.", no: "No da de alta el PERFIL del cliente (eso es cliente_crear); Claude nunca inventa la clave; no toca dinero." },
|
|
289
|
+
alta_nodo_columnas: { que: "Las columnas del alta inicial de tu nodo (equipo, vendedores, saldos con nodos): clave, obligatoria, opciones de los desplegables y notas.", uso: "Sin argumentos. Solo el due\xF1o del nodo. Leelo antes de armar las filas de alta_nodo_previsualizar.", ej: "alta_nodo_columnas \u2192 equipo[{clave:'tipo', opciones:['Due\xF1o del nodo','Operador','Chofer']}\u2026].", no: "No escribe nada." },
|
|
290
|
+
alta_nodo_previsualizar: { que: "Arma y VALIDA el alta inicial de tu nodo (equipo, vendedores con precios, saldos de apertura) sin escribir nada: devuelve las listas de precios que se arman, los saldos, los errores por hoja y fila y avisos.", uso: "equipo / vendedores / saldosNodos = listas de objetos con las claves de alta_nodo_columnas (o el encabezado de la planilla); desplegables con el texto (\xABS\xED\xBB, \xABChofer\xBB, \xABNos debe\xBB). Fila del error = posici\xF3n en la lista. Solo el due\xF1o, siempre sobre su nodo.", ej: "alta_nodo_previsualizar vendedores:[{tienda:'Fumshop', nom:'Juan', ape:'P\xE9rez', email:'juan@x.com', tel:'1123456789', dni:'30111222', buscar:'S\xED', dirret:'Mitre 100', locret:'Mor\xF3n', cercana:3500, media:4200, lejana:5000, muylejana:6500, coniva:'S\xED', frec:'Semanal', fact:'No', saldo:28500, saldoSentido:'Nos debe'}].", no: "No crea nada (eso es alta_nodo_confirmar); no edita el nodo (logo, direcci\xF3n y Mercado Libre van en Datos del nodo); no crea otro nodo." },
|
|
291
|
+
alta_nodo_confirmar: { que: "Carga el alta inicial de tu nodo: usuarios con CLAVE PROVISORIA (las devuelve), vendedores con sus listas de precios y los saldos iniciales de apertura.", uso: "Las mismas filas que previsualizaste y el due\xF1o aprob\xF3. Todo o nada: con un error no carga. Requiere escritura habilitada. Pasale a cada uno su clave (la cambia al entrar).", ej: "alta_nodo_confirmar equipo:[\u2026] vendedores:[\u2026] \u2192 credenciales[{email, password}].", no: "No es un pago ni un cobro (el saldo inicial es la apertura de la cuenta); los saldos con otros nodos los confirma ese nodo (si todav\xEDa no tiene cuenta en Nexus Flex, quedan tomados como v\xE1lidos); no pisa usuarios existentes (email usado = error); Claude nunca inventa las claves." },
|
|
289
292
|
cliente_resetear_clave: { que: "Genera una CLAVE TEMPORAL NUEVA para un cliente/vendedor de tu nodo que YA tiene usuario pero perdi\xF3 el acceso.", uso: "cliente (nombre o id, de clientes_del_nodo). Requiere permiso 'usuarios'. Admin global: `nodo` para elegir/acotar el nodo.", ej: "cliente_resetear_clave cliente:'Distri Sur' \u2192 nueva clave temporal.", no: "Si el cliente todav\xEDa no tiene usuario, avisa y sugiere cliente_generar_usuario; no crea uno nuevo; no toca dinero." },
|
|
290
293
|
usuario_habilitar_reparto: { que: "Marca a un operador/admin de tu nodo como TAMBI\xC9N mensajero (puede escanear, autoasignarse y entregar).", uso: "id + activo (default true).", ej: "usuario_habilitar_reparto id:4 \u2192 ese comisionista ya puede repartir.", no: "No toca credenciales." },
|
|
291
294
|
nodos_listar: { que: "Todas las log\xEDsticas (nodos) de la red.", uso: "Sin par\xE1metros. Solo admin global.", ej: "nodos_listar \u2192 id/nombre de cada nodo.", no: "No los crea (nodo_crear)." },
|
|
@@ -450,6 +453,26 @@ const CONTROL_POR_FOTO = [
|
|
|
450
453
|
" ABSORBER este historial (conciliar el provisorio) o descartarlo \u2014 no bloquea nada."
|
|
451
454
|
].join("\n");
|
|
452
455
|
const FLUJOS = {
|
|
456
|
+
alta_nodo: [
|
|
457
|
+
"FLUJO \u2014 ALTA INICIAL DE UN NODO (migraci\xF3n desde su sistema anterior)",
|
|
458
|
+
"Para el DUE\xD1O de un nodo reci\xE9n creado. Es su software (marca blanca): trae a su gente, sus",
|
|
459
|
+
"vendedores con lo que les cobra y lo que se deb\xEDan, sin pedirle nada a Nexus Flex.",
|
|
460
|
+
"",
|
|
461
|
+
"1) Pedile lo que YA tenga, en cualquier formato: la planilla de su sistema anterior, una lista,",
|
|
462
|
+
" un chat, una foto de un cuaderno. No le hagas completar la planilla de cero.",
|
|
463
|
+
"2) `alta_nodo_columnas` y arm\xE1 las filas: equipo (due\xF1os, operadores con permisos, choferes con",
|
|
464
|
+
" el nombre con el que se les paga), vendedores (titular, DNI, WhatsApp, email, colecta, los",
|
|
465
|
+
" 4 precios, IVA incluido, factura) y saldos de apertura (monto + \xABNos debe\xBB/\xABLe debemos\xBB; con",
|
|
466
|
+
" otro nodo, tambi\xE9n el grupo en el que trabajan juntos: lo confirma ese nodo, o vale si no tiene cuenta).",
|
|
467
|
+
" Lo que falte y sea obligatorio, PREGUNTALO; no inventes emails, DNI ni precios.",
|
|
468
|
+
"3) `alta_nodo_previsualizar` hasta 0 errores. Mostrale el resumen: cu\xE1ntos usuarios, qu\xE9 listas",
|
|
469
|
+
" de precios se arman (mismos 4 precios = una lista) y los saldos. Que confirme.",
|
|
470
|
+
"4) `alta_nodo_confirmar` \u2192 pasale las claves provisorias a cada uno.",
|
|
471
|
+
"5) Lo que sigue va en la app, \xAB\u{1F3E2} Datos del nodo\xBB (tarjeta Primeros pasos): logo, direcci\xF3n del",
|
|
472
|
+
" dep\xF3sito y vincular Mercado Libre como Mensajer\xEDa Flex. Manual: nexusflex.com.ar/manual-nodos",
|
|
473
|
+
"Si prefiere hacerlo a mano: en Datos del nodo \u2192 \u{1F4E5} Alta inicial baja la planilla de su nodo,",
|
|
474
|
+
"la completa y la sube \xE9l mismo (mismo resultado)."
|
|
475
|
+
].join("\n"),
|
|
453
476
|
alta_cliente: [
|
|
454
477
|
"FLUJO \u2014 ALTA DE CLIENTE (vendedor) COMPLETA",
|
|
455
478
|
"Gui\xE1 paso a paso; NO lo des por terminado hasta el paso 4.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexusflex-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.76.1",
|
|
4
4
|
"description": "MCP de Nexus Flex: operá tu nodo/cuenta desde un asistente de IA (altas de clientes, stock, KPIs). Login por autorización web (device-flow). NO toca dinero.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/server.mjs
CHANGED
|
@@ -56,8 +56,10 @@ const ALLOW_PRECIOS = truthy(process.env.NEXUSFLEX_MCP_ALLOW_PRECIOS);
|
|
|
56
56
|
|
|
57
57
|
// ATADO CON: backend/src/services/mcp-remote.ts (MCP_VERSION + NOVEDADES + mismos tools, salvo SOLO_REMOTO)
|
|
58
58
|
// y mcp/package.json. Lo verifica backend/src/coherencia.test.ts. Ver CLAUDE.md → "Cosas que van juntas".
|
|
59
|
-
const MCP_VERSION = "3.
|
|
59
|
+
const MCP_VERSION = "3.76.1"; // bumpear junto con package.json + backend/services/mcp-remote.ts
|
|
60
60
|
const NOVEDADES = [
|
|
61
|
+
"3.76.1 — `comision_liquidar` sale del MCP (decisión del dueño): registrar el pago de comisiones a un afiliado es mover plata y el MCP nunca mueve plata. Se liquida desde la app (Afiliados), y la ruta quedó en la lista de dinero de los dos lados para que no vuelva a entrar por error.",
|
|
62
|
+
"3.76.0 — ALTA INICIAL DEL NODO (migración): el DUEÑO de un nodo carga de una vez su equipo (dueños, operadores, choferes), sus vendedores con los 4 precios (mismos precios = una lista) y los SALDOS de apertura que trae de su sistema anterior, en su propio nodo. `alta_nodo_columnas` (qué va en cada columna), `alta_nodo_previsualizar` (arma y valida sin escribir: errores por hoja y fila) y `alta_nodo_confirmar` (carga todo o nada y devuelve las claves provisorias). Armalo con lo que el dueño tenga: una planilla vieja, una lista, un chat. Los saldos con otros nodos se arreglan entre ellos: los confirma el otro nodo en su app (si todavía no tiene cuenta, valen). Es la misma pantalla Datos del nodo → 📥 Alta inicial (Excel), donde también se baja la planilla. No es un pago ni un cobro. Además: el MCP de la PC ya no le muestra a un cadete herramientas de staff (trabados, provisorios, afiliados), `cuentas_vinculadas` vuelve a andar en la PC (solo lectura), y un admin con nodo ya no ve herramientas de toda la red.",
|
|
61
63
|
"3.75.0 — CONTROL POR FOTO: ENVÍO PARTICULAR SIN NÚMERO. Una etiqueta que el vendedor imprimió él mismo (sin QR ni tracking) ya no se queda trabada en 'sin código': si leés la dirección, `control_foto_foto` acepta `direccion` (+ `localidad`, `destinatario`, `telefono`) con `cliente` o `nodo`, busca si ya está cargado a esa dirección (te avisa del posible duplicado) y, si no, lo da de alta con un tracking propio de Nexus Flex. La pantalla Control por foto tiene los mismos campos. No mueve dinero.",
|
|
62
64
|
"3.74.1 — CONTROL POR FOTO: un paquete que está el mismo día en DADOS y en RECIBIDOS es el MISMO paquete (te lo dieron y lo pasaste), no un duplicado. Vale cómo salió en DADOS; la foto de RECIBIDOS (el respaldo por si algo no se registró) solo se suma al envío. `control_foto_carpeta` procesa primero DADOS y después RECIBIDOS.",
|
|
63
65
|
"3.74.0 — RECLAMOS DE VENDEDORES: nuevo `reclamo_responder` (responder, cerrar o reabrir el reclamo que un vendedor dejó sobre su liquidación; la respuesta la lee en su portal). Se usa junto con `liquidacion_excluir_envio` / `liquidacion_reincluir_envio`: primero corregís el cobro, después contestás con el nuevo total. Es lo mismo que la pantalla Reclamos de vendedores. Además `liquidacion_cobro_parcial`: cuando el comprador pagó en destino MENOS que el flete (ej. zona media cobrada como cercana), lo cobrado se da por pagado y el envío sigue en la liquidación por la diferencia; con `montoCobrado` 0 se quita el ajuste. Y el cierre automático de un reclamo ya no responde con un solo envío y un total viejo: espera a que estén atendidos todos los envíos del reclamo. Además la app y el MCP validan que el reclamo sea de un cliente de TU nodo. Solo texto y estado: no mueve dinero. Requiere permiso finanzas.",
|
|
@@ -443,8 +445,9 @@ try {
|
|
|
443
445
|
|
|
444
446
|
const rol = me?.rol ?? "";
|
|
445
447
|
const permisos = Array.isArray(me?.permisos) ? me.permisos : [];
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
+
// Igual que backend/src/lib/auth.ts esGlobal: un rol "admin" CON nodo es admin de ESE nodo, no de la red.
|
|
449
|
+
const esGlobal = me?.esSuperoperador === true || (rol === "admin" && !me?.logisticaId);
|
|
450
|
+
const esAdminNodo = me?.esAdminNodo === true || (rol === "admin" && !!me?.logisticaId);
|
|
448
451
|
const isCliente = rol === "cliente";
|
|
449
452
|
const isStaff = rol === "operador" || rol === "admin";
|
|
450
453
|
const wmsActivo = me?.wmsActivo === true;
|
|
@@ -1184,52 +1187,50 @@ if (ALLOW_WRITE) {
|
|
|
1184
1187
|
description: "Devuelve una NARRATIVA del estado de un envío (por tracking) para responder '¿cuándo se entrega?': si está en camino, con qué mensajero, cuántos envíos lleva en la ruta, en qué posición va este y una ETA estimada (posición en la ruta × minutos por parada). Marca PROBLEMAS si los hay (nunca despachado, sin mensajero asignado, mensajero sin reportar ubicación hace rato). Es una estimación explicable, no una promesa exacta. Read-only, scopeado a tu nodo.",
|
|
1185
1188
|
inputSchema: { tracking: z.string().min(1).describe("Tracking del envío a consultar") },
|
|
1186
1189
|
}, async ({ tracking }) => run(() => api("GET", `/flujo/envio-estado?tracking=${encodeURIComponent(tracking)}`)));
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
inputSchema: { ids: z.array(z.number().int().positive()).min(1) },
|
|
1232
|
-
}, async ({ ids }) => run(() => api("POST", "/afiliados/comisiones/liquidar", { ids })));
|
|
1190
|
+
// Solo STAFF (igual que el conector remoto): un mensajero no ve trabados, provisorios ni afiliados.
|
|
1191
|
+
if (isStaff) {
|
|
1192
|
+
tool("envios_trabados", {
|
|
1193
|
+
title: "Envíos trabados del nodo (para destrabar / cerrar)",
|
|
1194
|
+
description: "Lista los envíos TRABADOS de tu nodo, con el motivo: 'estancado' (mucho tiempo en 'A retirar'/'En centro' sin avanzar, ~nunca despachado), 'sin_mensajero' (en el centro pero sin mensajero asignado), 'mensajero_detenido' (en camino pero el mensajero no reporta ubicación hace rato) o 'en_camino_sin_cerrar' (en camino hace +24h sin cerrarse: candidato a cerrar; confirmá con el usuario antes de marcarlo entregado). Ordenados por severidad. Es la misma detección que alimenta el aviso proactivo a los operativos y admins del nodo. Read-only.",
|
|
1195
|
+
inputSchema: {},
|
|
1196
|
+
}, async () => run(() => api("GET", "/flujo/envios-trabados")));
|
|
1197
|
+
tool("planilla_reporte", {
|
|
1198
|
+
title: "Reporte de la planilla ML (control por foto)",
|
|
1199
|
+
description: "Resumen de los envíos cargados por el método de planilla/control por foto (ml_manual) de tu nodo: total + desglose por CLIENTE, por ZONA y por ESTADO + suma de valor declarado y de monto a cobrar. Opcional: rango de fechas (desde/hasta, YYYY-MM-DD). Read-only.",
|
|
1200
|
+
inputSchema: { desde: zStr().describe("Desde (YYYY-MM-DD), opcional"), hasta: zStr().describe("Hasta (YYYY-MM-DD), opcional") },
|
|
1201
|
+
}, async ({ desde, hasta }) => run(() => api("GET", `/flujo/planilla-reporte${desde || hasta ? `?${new URLSearchParams({ ...(desde ? { desde } : {}), ...(hasta ? { hasta } : {}) }).toString()}` : ""}`)));
|
|
1202
|
+
tool("nodo_provisorio_crear", {
|
|
1203
|
+
title: "Crear un nodo PROVISORIO en un grupo (planilla ML)",
|
|
1204
|
+
description: "Crea al vuelo un nodo PROVISORIO (placeholder) dentro de un grupo logístico, para atribuirle el clearing cuando el nodo real todavía no está dado de alta. No tiene login hasta que se concilie con el nodo real. Solo en grupos a los que pertenece TU nodo (el admin global, en cualquiera). Reusa el clearing por grupo. No mueve dinero.",
|
|
1205
|
+
inputSchema: { nombre: z.string().min(1).describe("Nombre del nodo (ej. 'Nodo Avellaneda - provisorio')"), grupoId: z.number().int().positive().describe("Grupo logístico donde crearlo (de tus grupos)") },
|
|
1206
|
+
}, async (args) => run(() => api("POST", "/flujo/nodo-provisorio", args)));
|
|
1207
|
+
tool("provisorio_conciliar", {
|
|
1208
|
+
title: "Conciliar un nodo provisorio con el nodo real",
|
|
1209
|
+
description: "Cuando el nodo real YA está dado de alta, CONCILIA (fusiona) el provisorio: reatribuye sus envíos (origen/entrega) y su membresía de grupo al nodo real, y marca el provisorio como RESUELTO (queda desactivado). El clearing de esos envíos pasa a usar la tarifa del nodo real en el grupo. Aislamiento: como operador solo conciliás los provisorios que creó TU nodo y contra un nodo real que YA opera en ese grupo; si el nodo real todavía no está en el grupo hay que definirle la tarifa → eso lo hace el admin global con `tarifa`. No mueve dinero (reordena el clearing).",
|
|
1210
|
+
inputSchema: {
|
|
1211
|
+
provisorioId: z.number().int().positive().describe("Id del nodo provisorio a conciliar (lo devolvió `nodo_provisorio_crear`, o lo ves en `grupos_tarifas`)"),
|
|
1212
|
+
nodoReal: z.string().min(1).describe("Nodo real destino: nombre o id (nodo ya dado de alta)"),
|
|
1213
|
+
tarifa: z.object({ cercana: z.number().optional(), media: z.number().optional(), lejana: z.number().optional(), muyLejana: z.number().optional() }).optional().describe("Solo admin global y solo si el nodo real todavía NO opera en el grupo: tarifa (bandas) con la que se lo agrega."),
|
|
1214
|
+
modo: z.enum(["absorber", "desde_ahora"]).optional().describe("absorber (default) = el nodo real se queda con el HISTORIAL del provisorio (reatribuye sus envíos y su grupo). desde_ahora = NO toca el historial; el clearing arranca de acá en adelante."),
|
|
1215
|
+
},
|
|
1216
|
+
}, async (args) => run(() => api("POST", "/flujo/conciliar-provisorio", args)));
|
|
1217
|
+
tool("afiliado_crear", {
|
|
1218
|
+
title: "Crear afiliado",
|
|
1219
|
+
description: "Alta de un afiliado (quien trae volumen nuevo a la red). tipo: nodo|mensajero|externo; refId = id del nodo/mensajero (null si externo). Admin global o admin de nodo (scopeado a tu nodo). NO toca dinero.",
|
|
1220
|
+
inputSchema: { nombre: z.string().min(1), tipo: z.enum(["nodo", "mensajero", "externo"]), refId: z.number().int().optional(), telefono: z.string().optional(), email: z.string().optional() },
|
|
1221
|
+
}, async (args) => run(() => api("POST", "/afiliados", args)));
|
|
1222
|
+
tool("afiliacion_crear", {
|
|
1223
|
+
title: "Crear afiliación (comisión por envío)",
|
|
1224
|
+
description: "Vincula un afiliado con una entidad referida (nodo o cliente) y su comisión RECURRENTE por envío. tipoComision: porcentaje|montoFijo. `porcentaje` se calcula sobre el valorDeclarado del envío (MVP); `montoFijo` = monto por envío. fechaExpiracion opcional (null = no vence). Una entidad = un solo afiliado activo. NO toca dinero (es config).",
|
|
1225
|
+
inputSchema: { afiliado: z.string().min(1).describe("nombre o id"), entidadTipo: z.enum(["nodo", "cliente"]), entidad: z.string().min(1).describe("nombre o id del nodo/cliente referido"), tipoComision: z.enum(["porcentaje", "montoFijo"]), valorComision: z.number(), fechaExpiracion: z.string().optional().describe("YYYY-MM-DD") },
|
|
1226
|
+
}, async (args) => run(() => api("POST", "/afiliados/afiliaciones", args)));
|
|
1227
|
+
tool("afiliacion_editar", {
|
|
1228
|
+
title: "Editar afiliación",
|
|
1229
|
+
description: "Edita una afiliación: valorComision, fechaExpiracion (renovar/extender) y/o activa (des/reactivar). NO toca dinero.",
|
|
1230
|
+
inputSchema: { id: z.number().int().positive(), valorComision: z.number().optional(), fechaExpiracion: z.string().optional().describe("YYYY-MM-DD; vacío = quitar vencimiento"), activa: z.boolean().optional() },
|
|
1231
|
+
}, async ({ id, ...body }) => run(() => api("PUT", `/afiliados/afiliaciones/${id}`, body)));
|
|
1232
|
+
// `comision_liquidar` NO va en el MCP (dueño 02/10): registrar el pago al afiliado es mover
|
|
1233
|
+
// plata. Se liquida desde la app; el guard lo corta igual (RUTAS_PROHIBIDAS de api.mjs).
|
|
1233
1234
|
}
|
|
1234
1235
|
}
|
|
1235
1236
|
if (puede("usuarios")) {
|
|
@@ -1554,6 +1555,34 @@ if (ALLOW_PRECIOS && puede("precios")) {
|
|
|
1554
1555
|
}, async ({ nodo, ...rest }) => run(() => api("POST", "/precios/clientes/version", { ...rest, logisticaId: nodo })));
|
|
1555
1556
|
}
|
|
1556
1557
|
|
|
1558
|
+
// Alta inicial del nodo (3.76.0): el DUEÑO migra su situación real desde su sistema anterior —
|
|
1559
|
+
// equipo, vendedores con precios y saldos de apertura— en su propio nodo. Misma pantalla:
|
|
1560
|
+
// Datos del nodo → 📥 Alta inicial (Excel). El backend fuerza el nodo del token.
|
|
1561
|
+
if (isStaff && (esAdminNodo || rol === "admin")) {
|
|
1562
|
+
const zFilas = (que) => z.array(z.record(z.string(), z.unknown())).optional().describe(que + ". Cada fila = objeto con las CLAVES de alta_nodo_columnas (o el encabezado de la planilla); desplegables con el texto de la planilla («Sí», «Chofer», «Nos debe»…).");
|
|
1563
|
+
const altaSchema = {
|
|
1564
|
+
equipo: zFilas("Hoja 2: dueños, operadores y choferes (una fila por persona)"),
|
|
1565
|
+
vendedores: zFilas("Hoja 3: vendedores con datos, facturación, los 4 precios y su saldo inicial"),
|
|
1566
|
+
saldosNodos: zFilas("Hoja 4 (opcional): saldos con otros nodos de tu grupo"),
|
|
1567
|
+
};
|
|
1568
|
+
tool("alta_nodo_columnas", {
|
|
1569
|
+
title: "Alta inicial del nodo: columnas",
|
|
1570
|
+
description: "Las columnas del alta inicial de TU nodo (equipo, vendedores, saldos con nodos): clave, si es obligatoria, opciones de los desplegables y notas. Leelo ANTES de armar las filas de `alta_nodo_previsualizar`. Solo el dueño del nodo. Solo lectura.",
|
|
1571
|
+
inputSchema: {},
|
|
1572
|
+
}, async () => run(() => api("GET", "/mi-nodo/alta/columnas")));
|
|
1573
|
+
tool("alta_nodo_previsualizar", {
|
|
1574
|
+
title: "Alta inicial del nodo: previsualizar",
|
|
1575
|
+
description: "Arma el alta inicial de TU nodo con lo que el dueño ya tiene (una planilla vieja, una lista, un chat) SIN escribir nada: devuelve el equipo, los vendedores, las listas de precios que se van a armar (mismos 4 precios = una lista), los saldos iniciales, los ERRORES por hoja y fila (fila = posición en tu lista) y avisos. Corregí hasta que no haya errores y mostrale el resumen al dueño antes de `alta_nodo_confirmar`. Solo el dueño del nodo, siempre sobre su nodo. Es lo mismo que la pantalla Datos del nodo → Alta inicial.",
|
|
1576
|
+
inputSchema: altaSchema,
|
|
1577
|
+
}, async (args) => run(() => api("POST", "/mi-nodo/alta/previsualizar", args)));
|
|
1578
|
+
if (ALLOW_WRITE)
|
|
1579
|
+
tool("alta_nodo_confirmar", {
|
|
1580
|
+
title: "Alta inicial del nodo: confirmar",
|
|
1581
|
+
description: "Carga el alta inicial de TU nodo (las mismas filas que previsualizaste y el dueño aprobó): crea los usuarios con CLAVE PROVISORIA (te las devuelve: pasáselas a cada uno, la cambian al entrar), los vendedores con sus listas de precios y los SALDOS INICIALES de apertura (lo que el nodo traía de su sistema anterior: no es un pago ni un cobro). Con un solo error no carga nada. Los saldos con OTROS nodos los confirma el otro nodo en su app (Saldos iniciales); si todavía no tiene cuenta, quedan tomados como válidos. Cada saldo con otro nodo lleva el grupo en el que trabajan juntos (`grupo`; vacío = el que compartan). Emails ya usados = error (no pisa a nadie). Solo el dueño del nodo.",
|
|
1582
|
+
inputSchema: altaSchema,
|
|
1583
|
+
}, async (args) => run(() => api("POST", "/mi-nodo/alta/confirmar", args)));
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1557
1586
|
// Ayuda integrada — se registra al final para que el índice conozca TODOS los tools
|
|
1558
1587
|
// que este usuario tiene según su rol/permisos. Solo lectura de documentación.
|
|
1559
1588
|
tool("ayuda", {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: alta-inicial-nodo
|
|
3
|
+
description: Alta inicial de un nodo de Nexus Flex — migrar a la plataforma el equipo (dueños, operadores, choferes), los vendedores con sus precios y los saldos que el nodo trae de su sistema anterior. Usar cuando el dueño de un nodo dice "armame el alta inicial", "quiero cargar a mis vendedores/choferes", "pasar mis clientes a Nexus Flex", "migrar desde mi sistema" o suelta una planilla/lista de sus vendedores, choferes o saldos. Requiere el MCP de Nexus Flex con el usuario DUEÑO del nodo.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Alta inicial del nodo (Nexus Flex)
|
|
7
|
+
|
|
8
|
+
Nexus Flex es el software del nodo (marca blanca): el dueño trae su situación real sin pedirle nada a nadie. Vos le ahorrás completar la planilla: armás el alta con lo que ya tiene.
|
|
9
|
+
|
|
10
|
+
## 1. Juntar lo que tiene
|
|
11
|
+
|
|
12
|
+
Pedile lo que ya exista, en cualquier formato: la planilla de su sistema anterior, un Excel de clientes, una lista por WhatsApp, una foto de un cuaderno. **No le pidas que complete la planilla de cero.**
|
|
13
|
+
|
|
14
|
+
## 2. Armar las filas
|
|
15
|
+
|
|
16
|
+
Corré `alta_nodo_columnas` y armá tres listas con esas claves:
|
|
17
|
+
- **equipo**: dueños («Dueño del nodo»), operadores (con sus permisos Sí/No; sin ninguno = acceso completo) y choferes (nombre con el que se le paga, frecuencia, saldo).
|
|
18
|
+
- **vendedores**: tienda, titular (nombre, apellido, DNI), email y WhatsApp, si se lo busca (colecta) con la dirección de retiro, los 4 precios por zona, si el precio tiene IVA, frecuencia, factura (CUIT, razón social, IVA, domicilio fiscal) y su saldo.
|
|
19
|
+
- **saldosNodos** (opcional): lo que se debe con otros nodos, con el **grupo** en el que trabajan juntos (si comparten uno, podés dejarlo vacío).
|
|
20
|
+
|
|
21
|
+
Saldos: monto positivo + sentido. Vendedor «Nos debe» = envíos que no pagó; «Le debemos» = plata suya que tiene el nodo (cobros en destino). Chofer «Le debemos» = viajes sin pagar; «Nos debe» = cobros que no rindió.
|
|
22
|
+
|
|
23
|
+
**No inventes** emails, DNI, teléfonos ni precios: lo obligatorio que falte, preguntalo (de a varios juntos, en una tabla corta).
|
|
24
|
+
|
|
25
|
+
## 3. Previsualizar hasta que esté limpio
|
|
26
|
+
|
|
27
|
+
`alta_nodo_previsualizar` con las tres listas. Corregí cada error (hoja + fila = posición en tu lista) y repetí hasta **0 errores**. Mostrale al dueño un resumen: cuántos usuarios, qué listas de precios se arman (mismos 4 precios = una sola lista) y los saldos. Esperá su OK.
|
|
28
|
+
|
|
29
|
+
## 4. Confirmar y entregar los accesos
|
|
30
|
+
|
|
31
|
+
`alta_nodo_confirmar` con las mismas filas. Devuelve la **clave provisoria** de cada uno: dáselas en una tabla (nombre, email, clave) para que se las mande; cada uno la cambia al entrar. Los saldos con otros nodos los confirma el otro nodo desde su app (💰 Saldos iniciales); si todavía no tiene cuenta en Nexus Flex, quedan tomados como válidos: avisale.
|
|
32
|
+
|
|
33
|
+
## 5. Lo que sigue (en la app)
|
|
34
|
+
|
|
35
|
+
En **🏢 Datos del nodo** está la tarjeta **Primeros pasos**: subir el logo, la dirección del depósito y vincular Mercado Libre como Mensajería Flex. Manual con video: https://nexusflex.com.ar/manual-nodos/
|
|
36
|
+
|
|
37
|
+
Si prefiere hacerlo a mano: en Datos del nodo → 📥 Alta inicial baja la planilla de su nodo, la completa y la sube él mismo (mismo resultado).
|