nexusflex-mcp 3.76.1 → 3.78.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/.claude-plugin/plugin.json +1 -1
- package/docs.mjs +4 -3
- package/package.json +1 -1
- package/server.mjs +27 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexusflex",
|
|
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.
|
|
4
|
+
"version": "3.78.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Nexus Flex",
|
|
7
7
|
"url": "https://nexusflex.com.ar"
|
package/docs.mjs
CHANGED
|
@@ -150,7 +150,7 @@ const GROUPS = [
|
|
|
150
150
|
{ titulo: "Vendedor \u2014 mi stock y n\xFAmeros", tools: ["mi_stock", "mi_disponible", "mi_rentabilidad", "mis_productos", "mis_top_productos", "mis_kpis"] },
|
|
151
151
|
{ titulo: "Mensajero", tools: ["mi_ruta", "mis_colectas"] },
|
|
152
152
|
{ titulo: "Control por foto (staff)", tools: ["control_foto_preparar", "control_foto_estructura", "control_foto_carpeta", "control_foto_foto"] },
|
|
153
|
-
{ titulo: "Env\xEDos (staff)", tools: ["envio_cargar", "envio_desde_etiqueta_ml", "envio_procesar", "envio_entregar", "envio_estado", "envios_trabados", "planilla_reporte", "envio_asignar_grupo", "nodo_provisorio_crear", "provisorio_conciliar", "envio_reasignar_cliente", "envio_mensajero_externo", "envio_editar_zona", "envio_editar_fecha", "cobro_corregir", "sin_vendedor", "asignar_sin_vendedor", "envio_completar_ciclo", "envio_pago_mensajero", "envio_corregir_estado", "envio_asignar_mensajero"] },
|
|
153
|
+
{ titulo: "Env\xEDos (staff)", tools: ["envio_cargar", "envio_cotizar", "envio_desde_etiqueta_ml", "envio_procesar", "envio_entregar", "envio_estado", "envios_trabados", "planilla_reporte", "envio_asignar_grupo", "nodo_provisorio_crear", "provisorio_conciliar", "envio_reasignar_cliente", "envio_mensajero_externo", "envio_editar_zona", "envio_editar_fecha", "cobro_corregir", "sin_vendedor", "asignar_sin_vendedor", "envio_completar_ciclo", "envio_pago_mensajero", "envio_corregir_estado", "envio_asignar_mensajero"] },
|
|
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"] },
|
|
@@ -214,6 +214,7 @@ const TOOL_DOCS = {
|
|
|
214
214
|
mis_kpis: { que: "Tus m\xE9tricas + benchmark an\xF3nimo de tu nodo.", uso: "desde/hasta opcionales.", ej: "mis_kpis \u2192 tu tasa de entrega vs. el promedio del nodo.", no: "No revela datos de otros vendedores (el benchmark es an\xF3nimo)." },
|
|
215
215
|
mi_ruta: { que: "Tus entregas asignadas: las paradas de tu ruta del d\xEDa.", uso: "Sin par\xE1metros.", ej: "mi_ruta \u2192 orden de reparto de hoy.", no: "No marca entregas (eso se hace escaneando en la app)." },
|
|
216
216
|
mis_colectas: { que: "Las colectas/retiros que ten\xE9s asignados.", uso: "Sin par\xE1metros.", ej: "mis_colectas \u2192 a qu\xE9 vendedores ten\xE9s que ir a buscar.", no: "No las completa/confirma desde ac\xE1." },
|
|
217
|
+
envio_cotizar: { que: "Cotiza UN env\xEDo antes de cargarlo: zona + precio con la lista del cliente, el mismo que sale despu\xE9s en la liquidaci\xF3n.", uso: "direccion y/o localidad y/o cp (al menos uno). Staff con permiso precios pasa `cliente` (id de su nodo); el vendedor no (siempre \xE9l). sucursalId opcional si el cliente cotiza por sucursal. Con direcci\xF3n ubica la puerta y la zona sale del mapa, como al cobrar.", ej: 'envio_cotizar localidad:"Ramos Mej\xEDa" \u2192 Zona de Media Distancia, $5.490 (lista A).', no: "No carga el env\xEDo (envio_cargar) ni inventa precios: zona sin resolver = precio null con motivo. No aplica descuentos por volumen ni recargos (los suma la liquidaci\xF3n). No mueve dinero." },
|
|
217
218
|
envio_cargar: { que: "Registra un env\xEDo nuevo (queda 'A retirar') y devuelve tracking + link de etiqueta + link para subir foto.", uso: "Obligatorios: destinatario, telefono, direccion, localidad. Staff pasa `cliente` (nombre del vendedor de su nodo); el vendedor no. montoCobro = cobro contra entrega (opcional). `fecha` (solo staff, dd/mm/aaaa) si el env\xEDo NO es de hoy: es la que define en qu\xE9 semana se liquida; no se admite futura.", ej: "Staff: envio_cargar cliente:'Distri Sur' destinatario:'Ana' telefono:'11...' direccion:'Belgrano 100' localidad:'Lan\xFAs' montoCobro:15000.", no: "No mueve dinero (montoCobro es el cobro a destino, no un asiento); no imprime, devuelve el link. Un vendedor/cadete NO puede pasar `fecha` (mover\xEDa sus paquetes de per\xEDodo de liquidaci\xF3n)." },
|
|
218
219
|
control_foto_preparar: { que: "Crea en la PC las carpetas del control por foto de uno o varios d\xEDas, con los nombres EXACTOS de tus grupos y cadetes (DADOS/<grupo>, DADOS/<cadete>, RECIBIDOS/<cadete>, RECIBIDOS/<grupo>) + un LEEME.txt.", uso: "Cuando el usuario dice 'hagamos el control por foto del d\xEDa tal': fechas ('14/09/2026', '14/09 a 19/09', '14/09, 16/09'). Se arman adentro de la carpeta del control por foto (la que eligi\xF3 al instalar el plugin; por defecto Documentos\\Control por foto); `ruta` es solo una subcarpeta opcional. Decile d\xF3nde quedaron y que suelte las fotos; despu\xE9s `control_foto_carpeta`.", ej: "control_foto_preparar fechas:'14/09 a 19/09' \u2192 6 d\xEDas con DADOS/Bonorino, DADOS/Portela, DADOS/Gerardo Soveron\u2026", no: "Solo en el MCP local (npx). No crea ni lee nada afuera de la carpeta del control; no pisa carpetas que ya existen; no procesa fotos." },
|
|
219
220
|
control_foto_estructura: { que: "C\xF3mo se arman las carpetas del control por foto para TU nodo: tus grupos log\xEDsticos y qui\xE9nes reparten (los nombres que valen en DADOS/\u2026 y RECIBIDOS/\u2026).", uso: "Sin argumentos (el admin global pasa `nodo`). Llamalo antes del primer control para explicarle la estructura al usuario, o cuando una carpeta no se reconoce.", ej: "control_foto_estructura \u2192 grupos: Bonorino, Portela, Z\xE1rate Campana \xB7 cadetes: Gerardo Soveron, Miguel Punturirero\u2026", no: "No procesa fotos ni cambia nada." },
|
|
@@ -267,8 +268,8 @@ const TOOL_DOCS = {
|
|
|
267
268
|
productos_nodo: { que: "Cat\xE1logo del dep\xF3sito del nodo (WMS).", uso: "cliente opcional.", ej: "productos_nodo.", no: "Requiere WMS activo." },
|
|
268
269
|
producto_crear: { que: "Alta de producto en el dep\xF3sito (WMS).", uso: "nombre + sku/codigoBarra/peso/volumen opcionales; staff pasa idCliente due\xF1o. Admin global: `nodo` para elegir/acotar el nodo.", ej: "producto_crear nombre:'Remera M' sku:'REM-M' idCliente:'130'.", no: "No mueve dinero; requiere WMS activo." },
|
|
269
270
|
clientes_del_nodo: { que: "Clientes/vendedores del nodo con su lista de precio.", uso: "Sin par\xE1metros. Scopeado a tu nodo.", ej: "clientes_del_nodo \u2192 ver a qui\xE9n factur\xE1s y con qu\xE9 lista.", no: "No los crea/edita (cliente_crear/editar)." },
|
|
270
|
-
cliente_crear: { que: "Alta de cliente/vendedor en tu nodo.", uso: "nombre obligatorio; telefono/dni/direccion/idLista opcionales. Admin global: `nodo` para elegir/acotar el nodo.", ej: "cliente_crear nombre:'Nueva Distri' idLista:'ID12'.", no: "No toca dinero; no cruza nodos." },
|
|
271
|
-
cliente_editar: { que: "Edita un cliente de tu nodo cambiando SOLO los campos que pas\xE1s (lista de precio, tel\xE9fono, email con el que entra, DNI, direcci\xF3n, nombre de pago, activo).", uso: 'id + los campos a cambiar. Lo que no pas\xE1s queda igual; "" vac\xEDa el campo. `email` = el del usuario del cliente (requiere permiso de usuarios). Admin global: `nodo` mueve el cliente a ese nodo.', ej: "cliente_editar id:130 idLista:'ID12' \xB7 cliente_editar id:50 email:'nuevo@mail.com'.", no: "No toca dinero ni la clave (cliente_resetear_clave)." },
|
|
271
|
+
cliente_crear: { que: "Alta de cliente/vendedor en tu nodo.", uso: "nombre obligatorio; telefono/dni/direccion/idLista/horarioCorte (HH:MM, corte de la colecta) opcionales. Admin global: `nodo` para elegir/acotar el nodo.", ej: "cliente_crear nombre:'Nueva Distri' idLista:'ID12'.", no: "No toca dinero; no cruza nodos." },
|
|
272
|
+
cliente_editar: { que: "Edita un cliente de tu nodo cambiando SOLO los campos que pas\xE1s (lista de precio, tel\xE9fono, email con el que entra, DNI, direcci\xF3n, nombre de pago, horario de corte, activo).", uso: 'id + los campos a cambiar. Lo que no pas\xE1s queda igual; "" vac\xEDa el campo. `email` = el del usuario del cliente (requiere permiso de usuarios). `horarioCorte` = HH:MM (corte de la colecta). Admin global: `nodo` mueve el cliente a ese nodo.', ej: "cliente_editar id:130 idLista:'ID12' \xB7 cliente_editar id:50 email:'nuevo@mail.com' \xB7 cliente_editar id:50 horarioCorte:'13:00'.", no: "No toca dinero ni la clave (cliente_resetear_clave)." },
|
|
272
273
|
sucursales_cliente: { que: "Lista las sucursales (puntos de retiro) de un cliente de tu nodo con su id, nombre, direcci\xF3n y perfil de zona.", uso: "cliente (id, de clientes_del_nodo). Requiere permiso 'gestion'.", ej: "sucursales_cliente cliente:130 \u2192 ver las sucursales y sus perfiles.", no: "No las crea/edita (eso lo hace el vendedor en su portal); solo el PERFIL lo setea el staff con sucursal_perfil." },
|
|
273
274
|
sucursal_perfil: { que: "Setea (o limpia) el perfil de zona de UNA sucursal: la liquidaci\xF3n cotiza los env\xEDos que salen de ah\xED seg\xFAn SU distancia, no la del cliente.", uso: "sucursalId (de sucursales_cliente) + perfilZona (vac\xEDo = hereda el del cliente). Requiere permiso 'gestion'. Config de staff (el vendedor NO lo toca).", ej: "sucursal_perfil sucursalId:12 perfilZona:'MORENO'.", no: "No mueve dinero, pero afecta el tramo/precio; no crea la sucursal." },
|
|
274
275
|
precios_ver: { que: "Listas de precios por zona de tu nodo.", uso: "Sin par\xE1metros. Requiere permiso 'precios'.", ej: "precios_ver \u2192 ver tarifas por zona.", no: "No las edita (precio_actualizar)." },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexusflex-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.78.0",
|
|
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.78.0"; // bumpear junto con package.json + backend/services/mcp-remote.ts
|
|
60
60
|
const NOVEDADES = [
|
|
61
|
+
"3.78.0 — HORARIO DE CORTE DEL CLIENTE (sugerencia #57): `cliente_crear` y `cliente_editar` aceptan `horarioCorte` (HH:MM, ej. 13:00), la hora de corte de la colecta del cliente. En `cliente_editar` sigue siendo parcial: si no lo pasás queda como está y vacío (\"\") lo borra. Es el mismo campo Horario de corte de la ficha del cliente en la app (antes por MCP solo se podía en `sucursal_guardar`). No mueve dinero.",
|
|
62
|
+
"3.77.0 — COTIZADOR DE ENVÍOS (sugerencia #68): `envio_cotizar` te dice, ANTES de cargar un envío que no es de Mercado Libre, la ZONA y el PRECIO que paga ese cliente con SU lista — el mismo que después sale en la liquidación (usa la misma lógica, no un cálculo aparte). Con la dirección y/o la localidad y/o el CP; con la dirección ubica la puerta y la zona sale del mapa, como al cobrar. Sirve para que el vendedor le cobre el envío al comprador por adelantado. Vendedor: para vos; staff con permiso precios: pasá `cliente` de tu nodo. Si la zona no se resuelve devuelve precio null con el motivo (nunca un precio inventado). Es la pantalla Cotizar de la app, con el botón Cargar este envío. Solo lectura: no mueve dinero.",
|
|
61
63
|
"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
64
|
"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.",
|
|
63
65
|
"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.",
|
|
@@ -173,6 +175,10 @@ const zNum = () => z.union([z.number(), z.string()]).optional();
|
|
|
173
175
|
// cross-nodo). Se manda al backend como `logisticaId`. Ver sugerencia MCP #20.
|
|
174
176
|
const NODO_OVERRIDE_DESC = "Solo admin global: nodo (id) sobre el que operás. El operador de nodo lo ignora (siempre opera en el suyo).";
|
|
175
177
|
const zNodo = () => z.number().int().positive().optional().describe(NODO_OVERRIDE_DESC);
|
|
178
|
+
// Horario de corte del cliente (HH:MM 24 h). Al editar, "" lo vacía (PATCH parcial). Sugerencia #57.
|
|
179
|
+
const zHorarioCorte = (vaciable) =>
|
|
180
|
+
z.string().trim().regex(vaciable ? /^(([01]\d|2[0-3]):[0-5]\d)?$/ : /^([01]\d|2[0-3]):[0-5]\d$/, "Horario de corte en formato HH:MM (ej. 13:00).").optional()
|
|
181
|
+
.describe(vaciable ? "Horario de corte de la colecta, HH:MM (ej. 13:00). \"\" lo borra." : "Horario de corte de la colecta, HH:MM (ej. 13:00).");
|
|
176
182
|
|
|
177
183
|
const q = (params) => {
|
|
178
184
|
const s = Object.entries(params).filter(([, v]) => v != null && v !== "").map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&");
|
|
@@ -758,6 +764,24 @@ if (isCliente || isStaff || rol === "mensajero") {
|
|
|
758
764
|
}, async () => run(() => api("GET", "/flujo/rendiciones")));
|
|
759
765
|
}
|
|
760
766
|
|
|
767
|
+
// Cotizador (sugerencia #68): zona + precio de UN envío con la lista del cliente, el mismo de la
|
|
768
|
+
// liquidación. Vendedor → /portal/cotizar; staff con permiso precios → /precios/cotizar. Lectura.
|
|
769
|
+
if (isCliente || puede("precios")) {
|
|
770
|
+
tool("envio_cotizar", {
|
|
771
|
+
title: "Cotizar un envío (zona + precio)",
|
|
772
|
+
description: "Cotiza UN envío (fuera de Mercado Libre) ANTES de cargarlo, para cobrarle el envío al comprador por adelantado: con la dirección y/o la localidad y/o el código postal devuelve la ZONA y el PRECIO que paga ese cliente con SU lista de precios — el mismo que después sale en la liquidación (misma lógica, sin cálculo aparte). Con la dirección ubica el número de puerta y, si lo encuentra, la zona sale del mapa (igual que al cobrar). Vendedor: siempre para vos. Staff (permiso precios): pasá `cliente` (de `clientes_del_nodo`, solo de tu nodo). Si la zona no se puede resolver devuelve `precio: null` con el `motivo` — nunca inventes un precio. Es precio de lista para un envío: los descuentos por volumen y los recargos (doble/triple, después de hora) los aplica la liquidación (ver `notas`). Solo lectura: no carga nada ni mueve dinero. Para cargarlo después: `envio_cargar` (o el botón Cargar este envío de la pantalla Cotizar).",
|
|
773
|
+
inputSchema: {
|
|
774
|
+
direccion: z.string().optional().describe("Calle y número (ej. 'Av. Corrientes 1234')"),
|
|
775
|
+
localidad: z.string().optional().describe("Localidad / barrio del destino (ej. 'Ramos Mejía')"),
|
|
776
|
+
cp: z.string().optional().describe("Código postal del destino (ej. '1406')"),
|
|
777
|
+
...(isCliente ? {} : { cliente: z.string().min(1).describe("Id del cliente de tu nodo (de `clientes_del_nodo`)") }),
|
|
778
|
+
sucursalId: z.number().int().positive().optional().describe("Sucursal de origen del cliente, si cotiza distinto por sucursal (de `sucursales_cliente` / `mis_sucursales`)"),
|
|
779
|
+
},
|
|
780
|
+
}, async (a) => run(() => api("POST", isCliente ? "/portal/cotizar" : "/precios/cotizar", {
|
|
781
|
+
direccion: a.direccion, localidad: a.localidad, cp: a.cp, sucursalId: a.sucursalId, ...(isCliente ? {} : { cliente: a.cliente }),
|
|
782
|
+
})));
|
|
783
|
+
}
|
|
784
|
+
|
|
761
785
|
// Reclamos de clientes ligados a liquidaciones (vendedor: lo suyo; operador: su nodo).
|
|
762
786
|
if (isCliente || isStaff) {
|
|
763
787
|
tool("reclamos_listar", {
|
|
@@ -1404,6 +1428,7 @@ if (ALLOW_WRITE) {
|
|
|
1404
1428
|
dni: z.string().optional(),
|
|
1405
1429
|
direccion: z.string().optional(),
|
|
1406
1430
|
idLista: z.string().optional().describe("ID de la lista de precios a asignar (ej. 'B'). Consultá 'clientes_del_nodo' / 'precios_ver'. Si falta, el alta queda incompleta."),
|
|
1431
|
+
horarioCorte: zHorarioCorte(false),
|
|
1407
1432
|
nodo: zNodo(),
|
|
1408
1433
|
},
|
|
1409
1434
|
}, async ({ nodo, ...rest }) => {
|
|
@@ -1429,6 +1454,7 @@ if (ALLOW_WRITE) {
|
|
|
1429
1454
|
dni: z.string().optional(),
|
|
1430
1455
|
direccion: z.string().optional(),
|
|
1431
1456
|
mensajeroNombre: z.string().optional().describe("Nombre con el que cobra si también reparte"),
|
|
1457
|
+
horarioCorte: zHorarioCorte(true),
|
|
1432
1458
|
activo: z.boolean().optional(),
|
|
1433
1459
|
nodo: zNodo(),
|
|
1434
1460
|
},
|