nexusflex-mcp 3.74.1 → 3.76.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 +2 -2
- package/api.mjs +8 -0
- package/docs.mjs +28 -2
- package/package.json +1 -1
- package/server.mjs +88 -50
- package/skills/alta-inicial-nodo/SKILL.md +37 -0
- package/skills/control-por-foto/SKILL.md +2 -1
|
@@ -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.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Nexus Flex",
|
|
7
7
|
"url": "https://nexusflex.com.ar"
|
package/api.mjs
CHANGED
|
@@ -58,8 +58,16 @@ const OPERACIONES_OK = [
|
|
|
58
58
|
{ metodo: "POST", ruta: /^\/afip\/preparar-cliente\/?$/i },
|
|
59
59
|
];
|
|
60
60
|
|
|
61
|
+
// Lecturas habilitadas aunque el path empiece como uno de dinero: GET /cuentas-vinculadas solo
|
|
62
|
+
// dice qué tiendas (ML/TN/TNG) tiene conectadas un cliente/nodo, sin saldos (mismo carve-out que
|
|
63
|
+
// RUTAS_MCP_LECTURA_OK en backend/src/middleware/auth.ts; el ancla exige "-vinculadas", no abre /cuentas).
|
|
64
|
+
const LECTURAS_OK = [
|
|
65
|
+
/^\/cuentas-vinculadas\/?$/i,
|
|
66
|
+
];
|
|
67
|
+
|
|
61
68
|
function guard(method, path) {
|
|
62
69
|
const clean = path.split("?")[0];
|
|
70
|
+
if (String(method).toUpperCase() === "GET" && LECTURAS_OK.some((re) => re.test(clean))) return;
|
|
63
71
|
if (OPERACIONES_OK.some((o) => o.metodo === String(method).toUpperCase() && o.ruta.test(clean))) return;
|
|
64
72
|
if (RUTAS_PROHIBIDAS.some((re) => re.test(clean))) {
|
|
65
73
|
throw new Error(`Ruta bloqueada por política del MCP (endpoints de dinero deshabilitados): ${clean}`);
|
package/docs.mjs
CHANGED
|
@@ -160,6 +160,7 @@ const GROUPS = [
|
|
|
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
|
];
|
|
@@ -217,7 +218,7 @@ const TOOL_DOCS = {
|
|
|
217
218
|
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." },
|
|
218
219
|
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." },
|
|
219
220
|
control_foto_carpeta: { que: "Procesa la CARPETA de un d\xEDa (o de varios) del control por foto, desde tu PC: cada foto bajo DADOS/\u2026 o RECIBIDOS/\u2026 con su carpeta y el d\xEDa que sale del nombre de la carpeta.", uso: "ruta: la carpeta del d\xEDa (o la que contiene varios d\xEDas). Primero SIN `aplicar` (simula) \u2192 mostr\xE1 el resumen y resolv\xE9 las preguntas (cuentas de ML desconocidas, zonas con varios responsables, duplicados) \u2192 despu\xE9s `aplicar:true`. Reanudable: lo ya procesado no se repite. Deja _control-foto-<d\xEDa>.json y copia lo dudoso a _sin_identificar.", ej: "control_foto_carpeta ruta:'Lunes 14-9-2026' \u2192 312 fotos: 280 ok, 25 preguntas (3 cuentas de ML desconocidas\u2026). Despu\xE9s, aplicar:true.", no: "Solo en el MCP local (npx): el conector remoto no ve tu disco. Solo lee adentro de la carpeta del control por foto (rutas relativas a ella). No mueve dinero; no descomprime .zip (avisa)." },
|
|
220
|
-
control_foto_foto: { que: "Procesa UNA foto de etiqueta del control por foto: QR o IA \u2192 env\xEDo sin duplicar \u2192 asignaci\xF3n seg\xFAn la carpeta \u2192 Entregado en el d\xEDa real \u2192 foto en el historial.", uso: "ruta (MCP local) o imagen en base64 (remoto) + carpeta ('DADOS/Bonorino', 'RECIBIDOS/Matanza/Gerardo'\u2026) + fecha. Sirve para CONTESTAR una pregunta: repet\xED la foto con `nodoEntrega` (qui\xE9n la llev\xF3) o `cliente` (de qui\xE9n es la cuenta de ML). `simular:true` = solo mirar.", ej: "control_foto_foto ruta:'Lunes 14-9-2026/RECIBIDOS/Matanza/Gerardo/IMG_1.jpg' carpeta:'RECIBIDOS/Matanza/Gerardo' fecha:'14/09/2026' cliente:306", no: "No mueve dinero; no toca env\xEDos de afuera de tu red (tu nodo + los de tus grupos); no reasigna lo que ya entr\xF3 en un cierre semanal congelado." },
|
|
221
|
+
control_foto_foto: { que: "Procesa UNA foto de etiqueta del control por foto: QR o IA \u2192 env\xEDo sin duplicar \u2192 asignaci\xF3n seg\xFAn la carpeta \u2192 Entregado en el d\xEDa real \u2192 foto en el historial.", uso: "ruta (MCP local) o imagen en base64 (remoto) + carpeta ('DADOS/Bonorino', 'RECIBIDOS/Matanza/Gerardo'\u2026) + fecha. Sirve para CONTESTAR una pregunta: repet\xED la foto con `nodoEntrega` (qui\xE9n la llev\xF3) o `cliente` (de qui\xE9n es la cuenta de ML). Etiqueta SIN n\xFAmero (env\xEDo particular, la imprimi\xF3 el vendedor): pas\xE1 `direccion` (+ `localidad`, `destinatario`, `telefono`) y `cliente` o `nodo` \u2014 busca si ya est\xE1 cargado a esa direcci\xF3n y, si no, lo da de alta con un tracking propio de Nexus Flex. `simular:true` = solo mirar.", ej: "control_foto_foto ruta:'Lunes 14-9-2026/RECIBIDOS/Matanza/Gerardo/IMG_1.jpg' carpeta:'RECIBIDOS/Matanza/Gerardo' fecha:'14/09/2026' cliente:306", no: "No mueve dinero; no toca env\xEDos de afuera de tu red (tu nodo + los de tus grupos); no reasigna lo que ya entr\xF3 en un cierre semanal congelado." },
|
|
221
222
|
envio_entregar: { que: "Marca como ENTREGADO uno o varios env\xEDos que YA est\xE1n cargados, por tracking \u2014 y con `fecha`, el d\xEDa REAL en que se entreg\xF3.", uso: "trackings: ['TN-123', 'NFABC123'] + fecha (opcional, dd/mm/aaaa). Usalo cuando no est\xE1s cargando desde una etiqueta (para eso est\xE1 `envio_desde_etiqueta_ml` con `avanzarA`). Sin `fecha` el historial dice que se entreg\xF3 ahora, que es mentira cuando la tanda se controla por foto d\xEDas despu\xE9s; sobre un env\xEDo que YA figura entregado, `fecha` CORRIGE el cierre ya registrado (vuelve en `fechaCorregida`). Entrega solo con los datos (sin foto ni firma). Scopeado a tu nodo.", ej: "envio_entregar trackings:['TN-2057600163'] fecha:'02/09/2026' \u2192 lo cierra con el d\xEDa en que sali\xF3 y sella la log\xEDstica de entrega para el clearing.", no: "No mueve dinero; `fecha` toca el evento 'Entregado' del historial, NO la semana en que se liquida (eso es envio_editar_fecha); no pide foto ni firma (para eso est\xE1 la entrega del cadete en la PWA); no revierte una entrega." },
|
|
222
223
|
envio_procesar: { que: "Marca un env\xEDo como PROCESADO / recibido en el centro de distribuci\xF3n (queda 'En centro de distribuci\xF3n') por su tracking, sin escanear.", uso: "tracking del env\xEDo. Scopeado a tu nodo/grupo (solo proces\xE1s lo que te corresponde). Es la misma acci\xF3n 'procesar' del esc\xE1ner.", ej: "envio_procesar tracking:'NFABC123' \u2192 lo marca recibido en el centro.", no: "No mueve dinero (registra el tramo de clearing del grupo, como procesar_zona); no lo entrega ni lo asigna a un cadete." },
|
|
223
224
|
envio_desde_etiqueta_ml: { que: "Registra un env\xEDo a partir de lo que VOS (Claude) le\xEDste de la foto de una etiqueta de Mercado Libre.", uso: "Le\xE9 la etiqueta: mlShipmentId + (si pod\xE9s) mlQr crudo, mlSenderId del vendedor y el destino. El vendedor se mapea por mlSenderId (cuenta vinculada) o `cliente` por nombre. Una llamada por etiqueta. Si la planilla es de d\xEDas anteriores, pas\xE1 `fecha` (solo staff, dd/mm/aaaa): fecha el env\xEDo Y toda la cadena de `avanzarA` (cada estado con su hora de ESE d\xEDa: Colectado 9, En centro 12, En camino 15, Entregado 18). Sin eso todo queda con la hora del control y se liquida en la semana equivocada. Si el env\xEDo YA estaba cargado tambi\xE9n le mueve la fecha, salvo que ese per\xEDodo ya est\xE9 liquidado (ah\xED rechaza nombrando la liquidaci\xF3n).", ej: "envio_desde_etiqueta_ml mlSenderId:'123456' mlShipmentId:'44000...' destinatario:'Juan' direccion:'...' localidad:'Avellaneda'.", no: "No mueve dinero; si no reconoce el vendedor por mlSenderId ten\xE9s que pasar `cliente`." },
|
|
@@ -286,6 +287,9 @@ const TOOL_DOCS = {
|
|
|
286
287
|
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
288
|
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
289
|
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." },
|
|
290
|
+
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." },
|
|
291
|
+
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." },
|
|
292
|
+
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
293
|
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
294
|
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
295
|
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)." },
|
|
@@ -386,7 +390,9 @@ const CONTROL_POR_FOTO = [
|
|
|
386
390
|
" o que el nodo due\xF1o vincule/aprenda esa cuenta y volv\xE9s a correr la carpeta.",
|
|
387
391
|
" \u2022 responsable (zona con varios responsables o sin ninguno) \u2192 pregunt\xE1 qui\xE9n lo llev\xF3 y",
|
|
388
392
|
" repet\xED esa foto con `nodoEntrega`.",
|
|
389
|
-
" \u2022
|
|
393
|
+
" \u2022 sin_codigo (etiqueta sin n\xFAmero = env\xEDo particular del vendedor) \u2192 le\xE9 la direcci\xF3n de la foto y repet\xED con",
|
|
394
|
+
" `control_foto_foto \u2026 direccion:<\u2026> localidad:<\u2026> cliente:<id>` (busca por direcci\xF3n; si no est\xE1, lo carga con tracking propio).",
|
|
395
|
+
" \u2022 duplicado / no_ml / fuera_de_red \u2192 se revisan a mano (la foto qued\xF3 copiada",
|
|
390
396
|
" en <d\xEDa>/_sin_identificar).",
|
|
391
397
|
" \u2022 erroresDeCarpeta \u2192 una carpeta que no coincide con un grupo o cadete tuyo: renombrala.",
|
|
392
398
|
"",
|
|
@@ -448,6 +454,26 @@ const CONTROL_POR_FOTO = [
|
|
|
448
454
|
" ABSORBER este historial (conciliar el provisorio) o descartarlo \u2014 no bloquea nada."
|
|
449
455
|
].join("\n");
|
|
450
456
|
const FLUJOS = {
|
|
457
|
+
alta_nodo: [
|
|
458
|
+
"FLUJO \u2014 ALTA INICIAL DE UN NODO (migraci\xF3n desde su sistema anterior)",
|
|
459
|
+
"Para el DUE\xD1O de un nodo reci\xE9n creado. Es su software (marca blanca): trae a su gente, sus",
|
|
460
|
+
"vendedores con lo que les cobra y lo que se deb\xEDan, sin pedirle nada a Nexus Flex.",
|
|
461
|
+
"",
|
|
462
|
+
"1) Pedile lo que YA tenga, en cualquier formato: la planilla de su sistema anterior, una lista,",
|
|
463
|
+
" un chat, una foto de un cuaderno. No le hagas completar la planilla de cero.",
|
|
464
|
+
"2) `alta_nodo_columnas` y arm\xE1 las filas: equipo (due\xF1os, operadores con permisos, choferes con",
|
|
465
|
+
" el nombre con el que se les paga), vendedores (titular, DNI, WhatsApp, email, colecta, los",
|
|
466
|
+
" 4 precios, IVA incluido, factura) y saldos de apertura (monto + \xABNos debe\xBB/\xABLe debemos\xBB; con",
|
|
467
|
+
" otro nodo, tambi\xE9n el grupo en el que trabajan juntos: lo confirma ese nodo, o vale si no tiene cuenta).",
|
|
468
|
+
" Lo que falte y sea obligatorio, PREGUNTALO; no inventes emails, DNI ni precios.",
|
|
469
|
+
"3) `alta_nodo_previsualizar` hasta 0 errores. Mostrale el resumen: cu\xE1ntos usuarios, qu\xE9 listas",
|
|
470
|
+
" de precios se arman (mismos 4 precios = una lista) y los saldos. Que confirme.",
|
|
471
|
+
"4) `alta_nodo_confirmar` \u2192 pasale las claves provisorias a cada uno.",
|
|
472
|
+
"5) Lo que sigue va en la app, \xAB\u{1F3E2} Datos del nodo\xBB (tarjeta Primeros pasos): logo, direcci\xF3n del",
|
|
473
|
+
" dep\xF3sito y vincular Mercado Libre como Mensajer\xEDa Flex. Manual: nexusflex.com.ar/manual-nodos",
|
|
474
|
+
"Si prefiere hacerlo a mano: en Datos del nodo \u2192 \u{1F4E5} Alta inicial baja la planilla de su nodo,",
|
|
475
|
+
"la completa y la sube \xE9l mismo (mismo resultado)."
|
|
476
|
+
].join("\n"),
|
|
451
477
|
alta_cliente: [
|
|
452
478
|
"FLUJO \u2014 ALTA DE CLIENTE (vendedor) COMPLETA",
|
|
453
479
|
"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.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.76.0"; // bumpear junto con package.json + backend/services/mcp-remote.ts
|
|
60
60
|
const NOVEDADES = [
|
|
61
|
+
"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
|
+
"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.",
|
|
61
63
|
"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.",
|
|
62
64
|
"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.",
|
|
63
65
|
"3.73.0 — CONTROL POR FOTO MÁS SEGURO: el MCP de la PC solo puede leer y crear adentro de UNA carpeta, la que elegís al instalar el plugin de Nexus Flex (si no elegís ninguna: Documentos\\Control por foto, que se crea sola). `control_foto_preparar`, `control_foto_carpeta` y `control_foto_foto` toman las rutas desde ahí (podés pasar solo 'Lunes 14-9-2026') y rechazan cualquier ruta de afuera o un acceso directo que apunte afuera. `control_foto_estructura` te dice cuál es la carpeta. No mueve dinero.",
|
|
@@ -335,6 +337,10 @@ function registrarControlFoto() {
|
|
|
335
337
|
simular: z.boolean().optional(),
|
|
336
338
|
nodoEntrega: zStr().describe("Respuesta: nodo (nombre o id) que lo llevó, cuando la zona tiene varios responsables"),
|
|
337
339
|
cliente: zStr().describe("Respuesta: id del cliente dueño de una cuenta de ML desconocida"),
|
|
340
|
+
direccion: zStr().describe("Envío PARTICULAR sin número (etiqueta propia del vendedor, no se lee QR ni tracking): la dirección que leés de la foto. Busca si ya está cargado (misma dirección) y, si no, lo da de alta con un tracking propio de Nexus Flex"),
|
|
341
|
+
localidad: zStr().describe("Envío particular: localidad"),
|
|
342
|
+
destinatario: zStr().describe("Envío particular: destinatario (opcional)"),
|
|
343
|
+
telefono: zStr().describe("Envío particular: teléfono (opcional)"),
|
|
338
344
|
nodo: zNodo(),
|
|
339
345
|
},
|
|
340
346
|
}, async (a) => run(async () => {
|
|
@@ -345,7 +351,7 @@ function registrarControlFoto() {
|
|
|
345
351
|
if (permitida.error) return permitida.error;
|
|
346
352
|
const foto = permitida.ruta;
|
|
347
353
|
if (!fs.existsSync(foto) || !EXT_FOTO.test(foto)) return { ok: false, status: 400, data: { message: `No encuentro la foto ${a.ruta}.` } };
|
|
348
|
-
return api("POST", "/control-foto/foto", { imagen: fs.readFileSync(foto).toString("base64"), carpeta: a.carpeta, fecha: a.fecha, simular: a.simular === true, nodoEntrega: a.nodoEntrega, cliente: a.cliente, nombreArchivo: path.basename(foto), nodo: a.nodo });
|
|
354
|
+
return api("POST", "/control-foto/foto", { imagen: fs.readFileSync(foto).toString("base64"), carpeta: a.carpeta, fecha: a.fecha, simular: a.simular === true, nodoEntrega: a.nodoEntrega, cliente: a.cliente, nombreArchivo: path.basename(foto), direccion: a.direccion, localidad: a.localidad, destinatario: a.destinatario, telefono: a.telefono, nodo: a.nodo });
|
|
349
355
|
}));
|
|
350
356
|
|
|
351
357
|
tool("control_foto_carpeta", {
|
|
@@ -438,8 +444,9 @@ try {
|
|
|
438
444
|
|
|
439
445
|
const rol = me?.rol ?? "";
|
|
440
446
|
const permisos = Array.isArray(me?.permisos) ? me.permisos : [];
|
|
441
|
-
|
|
442
|
-
const
|
|
447
|
+
// Igual que backend/src/lib/auth.ts esGlobal: un rol "admin" CON nodo es admin de ESE nodo, no de la red.
|
|
448
|
+
const esGlobal = me?.esSuperoperador === true || (rol === "admin" && !me?.logisticaId);
|
|
449
|
+
const esAdminNodo = me?.esAdminNodo === true || (rol === "admin" && !!me?.logisticaId);
|
|
443
450
|
const isCliente = rol === "cliente";
|
|
444
451
|
const isStaff = rol === "operador" || rol === "admin";
|
|
445
452
|
const wmsActivo = me?.wmsActivo === true;
|
|
@@ -1179,52 +1186,55 @@ if (ALLOW_WRITE) {
|
|
|
1179
1186
|
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.",
|
|
1180
1187
|
inputSchema: { tracking: z.string().min(1).describe("Tracking del envío a consultar") },
|
|
1181
1188
|
}, async ({ tracking }) => run(() => api("GET", `/flujo/envio-estado?tracking=${encodeURIComponent(tracking)}`)));
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
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
|
-
|
|
1189
|
+
// Solo STAFF (igual que el conector remoto): un mensajero no ve trabados, provisorios ni afiliados.
|
|
1190
|
+
if (isStaff) {
|
|
1191
|
+
tool("envios_trabados", {
|
|
1192
|
+
title: "Envíos trabados del nodo (para destrabar / cerrar)",
|
|
1193
|
+
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.",
|
|
1194
|
+
inputSchema: {},
|
|
1195
|
+
}, async () => run(() => api("GET", "/flujo/envios-trabados")));
|
|
1196
|
+
tool("planilla_reporte", {
|
|
1197
|
+
title: "Reporte de la planilla ML (control por foto)",
|
|
1198
|
+
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.",
|
|
1199
|
+
inputSchema: { desde: zStr().describe("Desde (YYYY-MM-DD), opcional"), hasta: zStr().describe("Hasta (YYYY-MM-DD), opcional") },
|
|
1200
|
+
}, async ({ desde, hasta }) => run(() => api("GET", `/flujo/planilla-reporte${desde || hasta ? `?${new URLSearchParams({ ...(desde ? { desde } : {}), ...(hasta ? { hasta } : {}) }).toString()}` : ""}`)));
|
|
1201
|
+
tool("nodo_provisorio_crear", {
|
|
1202
|
+
title: "Crear un nodo PROVISORIO en un grupo (planilla ML)",
|
|
1203
|
+
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.",
|
|
1204
|
+
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)") },
|
|
1205
|
+
}, async (args) => run(() => api("POST", "/flujo/nodo-provisorio", args)));
|
|
1206
|
+
tool("provisorio_conciliar", {
|
|
1207
|
+
title: "Conciliar un nodo provisorio con el nodo real",
|
|
1208
|
+
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).",
|
|
1209
|
+
inputSchema: {
|
|
1210
|
+
provisorioId: z.number().int().positive().describe("Id del nodo provisorio a conciliar (lo devolvió `nodo_provisorio_crear`, o lo ves en `grupos_tarifas`)"),
|
|
1211
|
+
nodoReal: z.string().min(1).describe("Nodo real destino: nombre o id (nodo ya dado de alta)"),
|
|
1212
|
+
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."),
|
|
1213
|
+
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."),
|
|
1214
|
+
},
|
|
1215
|
+
}, async (args) => run(() => api("POST", "/flujo/conciliar-provisorio", args)));
|
|
1216
|
+
tool("afiliado_crear", {
|
|
1217
|
+
title: "Crear afiliado",
|
|
1218
|
+
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.",
|
|
1219
|
+
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() },
|
|
1220
|
+
}, async (args) => run(() => api("POST", "/afiliados", args)));
|
|
1221
|
+
tool("afiliacion_crear", {
|
|
1222
|
+
title: "Crear afiliación (comisión por envío)",
|
|
1223
|
+
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).",
|
|
1224
|
+
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") },
|
|
1225
|
+
}, async (args) => run(() => api("POST", "/afiliados/afiliaciones", args)));
|
|
1226
|
+
tool("afiliacion_editar", {
|
|
1227
|
+
title: "Editar afiliación",
|
|
1228
|
+
description: "Edita una afiliación: valorComision, fechaExpiracion (renovar/extender) y/o activa (des/reactivar). NO toca dinero.",
|
|
1229
|
+
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
|
+
}, 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
|
+
}
|
|
1228
1238
|
}
|
|
1229
1239
|
}
|
|
1230
1240
|
if (puede("usuarios")) {
|
|
@@ -1549,6 +1559,34 @@ if (ALLOW_PRECIOS && puede("precios")) {
|
|
|
1549
1559
|
}, async ({ nodo, ...rest }) => run(() => api("POST", "/precios/clientes/version", { ...rest, logisticaId: nodo })));
|
|
1550
1560
|
}
|
|
1551
1561
|
|
|
1562
|
+
// Alta inicial del nodo (3.76.0): el DUEÑO migra su situación real desde su sistema anterior —
|
|
1563
|
+
// equipo, vendedores con precios y saldos de apertura— en su propio nodo. Misma pantalla:
|
|
1564
|
+
// Datos del nodo → 📥 Alta inicial (Excel). El backend fuerza el nodo del token.
|
|
1565
|
+
if (isStaff && (esAdminNodo || rol === "admin")) {
|
|
1566
|
+
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»…).");
|
|
1567
|
+
const altaSchema = {
|
|
1568
|
+
equipo: zFilas("Hoja 2: dueños, operadores y choferes (una fila por persona)"),
|
|
1569
|
+
vendedores: zFilas("Hoja 3: vendedores con datos, facturación, los 4 precios y su saldo inicial"),
|
|
1570
|
+
saldosNodos: zFilas("Hoja 4 (opcional): saldos con otros nodos de tu grupo"),
|
|
1571
|
+
};
|
|
1572
|
+
tool("alta_nodo_columnas", {
|
|
1573
|
+
title: "Alta inicial del nodo: columnas",
|
|
1574
|
+
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.",
|
|
1575
|
+
inputSchema: {},
|
|
1576
|
+
}, async () => run(() => api("GET", "/mi-nodo/alta/columnas")));
|
|
1577
|
+
tool("alta_nodo_previsualizar", {
|
|
1578
|
+
title: "Alta inicial del nodo: previsualizar",
|
|
1579
|
+
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.",
|
|
1580
|
+
inputSchema: altaSchema,
|
|
1581
|
+
}, async (args) => run(() => api("POST", "/mi-nodo/alta/previsualizar", args)));
|
|
1582
|
+
if (ALLOW_WRITE)
|
|
1583
|
+
tool("alta_nodo_confirmar", {
|
|
1584
|
+
title: "Alta inicial del nodo: confirmar",
|
|
1585
|
+
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.",
|
|
1586
|
+
inputSchema: altaSchema,
|
|
1587
|
+
}, async (args) => run(() => api("POST", "/mi-nodo/alta/confirmar", args)));
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1552
1590
|
// Ayuda integrada — se registra al final para que el índice conozca TODOS los tools
|
|
1553
1591
|
// que este usuario tiene según su rol/permisos. Solo lectura de documentación.
|
|
1554
1592
|
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).
|
|
@@ -45,7 +45,8 @@ Mostrá un resumen corto por día: fotos, ok, altas nuevas, preguntas y errores
|
|
|
45
45
|
|
|
46
46
|
- **cuentasMLDesconocidas**: preguntá de qué nodo y cliente es cada cuenta. Mostrá el vendedor, la marca a mano y la foto de ejemplo. Con el id del cliente: `control_foto_foto ruta:<foto> carpeta:<…> fecha:<…> cliente:<id>`. Si es un vendedor de otro nodo, puede vincular o aprender la cuenta ese nodo, y después se vuelve a correr la carpeta.
|
|
47
47
|
- **responsable** (la zona tiene varios responsables o ninguno): preguntá quién lo llevó y repetí la foto con `nodoEntrega`.
|
|
48
|
-
- **
|
|
48
|
+
- **sin_codigo** (la etiqueta no tiene número: es un envío particular que imprimió el vendedor): leé la dirección de la foto (y localidad/destinatario si se ven) y repetí `control_foto_foto ruta:<foto> carpeta:<…> fecha:<…> direccion:<…> localidad:<…>` con `cliente:<id>` o `nodo:<…>` (el nodo que te lo dio, queda sin vendedor). Busca por dirección si ya está cargado; si no, lo carga con un tracking propio de Nexus Flex. Si la dirección no se lee, no la inventes: va a revisión manual.
|
|
49
|
+
- **duplicado / no_ml / fuera_de_red**: van a revisión manual. La foto quedó copiada en `<día>/_sin_identificar`.
|
|
49
50
|
- **erroresDeCarpeta**: una carpeta no coincide con un grupo o cadete del nodo. Que la renombren (con `control_foto_estructura` a la vista).
|
|
50
51
|
|
|
51
52
|
**Más cómodo:** al aplicar, lo dudoso se sube solo a la app, en **Logística → 📷 Control por foto**. Ahí cada foto se ve grande y se elige:
|