nexusflex-mcp 3.76.0 → 3.77.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.76.0",
4
+ "version": "3.77.0",
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. */
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 una obligaci\xF3n (comision_liquidar",
37
- "marca comisiones ya devengadas como pagadas; ruta_adjudicar deja el precio",
38
- "acordado). Ver tema facturacion_marketplace."
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",
@@ -150,11 +150,11 @@ 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"] },
157
- { titulo: "Afiliados", tools: ["afiliacion_listar", "comisiones_afiliado_ver", "afiliado_crear", "afiliacion_crear", "afiliacion_editar", "comision_liquidar"] },
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"] },
@@ -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." },
@@ -259,7 +260,6 @@ const TOOL_DOCS = {
259
260
  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." },
260
261
  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." },
261
262
  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." },
262
- 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." },
263
263
  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)." },
264
264
  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)." },
265
265
  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." },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexusflex-mcp",
3
- "version": "3.76.0",
3
+ "version": "3.77.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.76.0"; // bumpear junto con package.json + backend/services/mcp-remote.ts
59
+ const MCP_VERSION = "3.77.0"; // bumpear junto con package.json + backend/services/mcp-remote.ts
60
60
  const NOVEDADES = [
61
+ "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
+ "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.",
61
63
  "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.",
62
64
  "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.",
63
65
  "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.",
@@ -757,6 +759,24 @@ if (isCliente || isStaff || rol === "mensajero") {
757
759
  }, async () => run(() => api("GET", "/flujo/rendiciones")));
758
760
  }
759
761
 
762
+ // Cotizador (sugerencia #68): zona + precio de UN envío con la lista del cliente, el mismo de la
763
+ // liquidación. Vendedor → /portal/cotizar; staff con permiso precios → /precios/cotizar. Lectura.
764
+ if (isCliente || puede("precios")) {
765
+ tool("envio_cotizar", {
766
+ title: "Cotizar un envío (zona + precio)",
767
+ 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).",
768
+ inputSchema: {
769
+ direccion: z.string().optional().describe("Calle y número (ej. 'Av. Corrientes 1234')"),
770
+ localidad: z.string().optional().describe("Localidad / barrio del destino (ej. 'Ramos Mejía')"),
771
+ cp: z.string().optional().describe("Código postal del destino (ej. '1406')"),
772
+ ...(isCliente ? {} : { cliente: z.string().min(1).describe("Id del cliente de tu nodo (de `clientes_del_nodo`)") }),
773
+ sucursalId: z.number().int().positive().optional().describe("Sucursal de origen del cliente, si cotiza distinto por sucursal (de `sucursales_cliente` / `mis_sucursales`)"),
774
+ },
775
+ }, async (a) => run(() => api("POST", isCliente ? "/portal/cotizar" : "/precios/cotizar", {
776
+ direccion: a.direccion, localidad: a.localidad, cp: a.cp, sucursalId: a.sucursalId, ...(isCliente ? {} : { cliente: a.cliente }),
777
+ })));
778
+ }
779
+
760
780
  // Reclamos de clientes ligados a liquidaciones (vendedor: lo suyo; operador: su nodo).
761
781
  if (isCliente || isStaff) {
762
782
  tool("reclamos_listar", {
@@ -1228,13 +1248,8 @@ if (ALLOW_WRITE) {
1228
1248
  description: "Edita una afiliación: valorComision, fechaExpiracion (renovar/extender) y/o activa (des/reactivar). NO toca dinero.",
1229
1249
  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() },
1230
1250
  }, async ({ id, ...body }) => run(() => api("PUT", `/afiliados/afiliaciones/${id}`, body)));
1231
- if (esGlobal || esAdminNodo || permisos.includes("finanzas")) { // igual que el gate del backend
1232
- tool("comision_liquidar", {
1233
- title: "Liquidar comisiones de afiliado (registra pago)",
1234
- description: "Marca comisiones devengadas como LIQUIDADAS (registra el pago al afiliado). `ids` = lista de comisiones (de comisiones_afiliado_ver). Requiere permiso de finanzas. Scopeado a tu nodo.",
1235
- inputSchema: { ids: z.array(z.number().int().positive()).min(1) },
1236
- }, async ({ ids }) => run(() => api("POST", "/afiliados/comisiones/liquidar", { ids })));
1237
- }
1251
+ // `comision_liquidar` NO va en el MCP (dueño 02/10): registrar el pago al afiliado es mover
1252
+ // plata. Se liquida desde la app; el guard lo corta igual (RUTAS_PROHIBIDAS de api.mjs).
1238
1253
  }
1239
1254
  }
1240
1255
  if (puede("usuarios")) {