nexusflex-mcp 3.77.0 → 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.
@@ -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.77.0",
4
+ "version": "3.78.0",
5
5
  "author": {
6
6
  "name": "Nexus Flex",
7
7
  "url": "https://nexusflex.com.ar"
package/docs.mjs CHANGED
@@ -268,8 +268,8 @@ const TOOL_DOCS = {
268
268
  productos_nodo: { que: "Cat\xE1logo del dep\xF3sito del nodo (WMS).", uso: "cliente opcional.", ej: "productos_nodo.", no: "Requiere WMS activo." },
269
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." },
270
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)." },
271
- 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." },
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, 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)." },
273
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." },
274
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." },
275
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.77.0",
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,9 @@ 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.77.0"; // bumpear junto con package.json + backend/services/mcp-remote.ts
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.",
61
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.",
62
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.",
63
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.",
@@ -174,6 +175,10 @@ const zNum = () => z.union([z.number(), z.string()]).optional();
174
175
  // cross-nodo). Se manda al backend como `logisticaId`. Ver sugerencia MCP #20.
175
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).";
176
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).");
177
182
 
178
183
  const q = (params) => {
179
184
  const s = Object.entries(params).filter(([, v]) => v != null && v !== "").map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&");
@@ -1423,6 +1428,7 @@ if (ALLOW_WRITE) {
1423
1428
  dni: z.string().optional(),
1424
1429
  direccion: z.string().optional(),
1425
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),
1426
1432
  nodo: zNodo(),
1427
1433
  },
1428
1434
  }, async ({ nodo, ...rest }) => {
@@ -1448,6 +1454,7 @@ if (ALLOW_WRITE) {
1448
1454
  dni: z.string().optional(),
1449
1455
  direccion: z.string().optional(),
1450
1456
  mensajeroNombre: z.string().optional().describe("Nombre con el que cobra si también reparte"),
1457
+ horarioCorte: zHorarioCorte(true),
1451
1458
  activo: z.boolean().optional(),
1452
1459
  nodo: zNodo(),
1453
1460
  },