{ "name": "MagRicambi - Ricerca prezzi articolo", "nodes": [ { "parameters": { "content": "## MagRicambi — Ricerca prezzi v3 (solo Tavily)\n\n**Affidabile, senza LLM.** Estrae i prezzi dagli snippet web di Tavily.\n\n**Setup:**\n1. Credenziale Header Auth sul Webhook (`X-Webhook-Secret`)\n2. Credenziale Tavily (header `Authorization: Bearer tvly-...`)\n3. **Attiva** il workflow (toggle verde)\n4. Copia **Production URL** in `N8N_PRICE_SEARCH_WEBHOOK_URL`\n\n**Flusso:** Webhook → Tavily → Estrai prezzi → Respond OK\n\nRisponde **sempre** JSON valido per MagRicambi.", "height": 340, "width": 440 }, "id": "sticky-note-setup", "name": "Note Setup", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [-80, 60] }, { "parameters": { "httpMethod": "POST", "path": "magricambi-ricerca-prezzi", "responseMode": "responseNode", "authentication": "headerAuth", "options": {} }, "id": "webhook-magricambi", "name": "Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [240, 300], "webhookId": "magricambi-ricerca-prezzi", "credentials": { "httpHeaderAuth": { "id": "REPLACE_WITH_CREDENTIAL_ID", "name": "MagRicambi Webhook Secret" } } }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "cond-descrizione-not-empty", "leftValue": "={{ ($json.body?.descrizione ?? $json.descrizione ?? '').toString().trim() }}", "rightValue": "", "operator": { "type": "string", "operation": "notEmpty", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "id": "if-descrizione-valida", "name": "IF Descrizione valida", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [480, 300] }, { "parameters": { "jsCode": "const item = $input.first().json;\nconst body = item.body ?? item;\n\nconst descrizione = String(body.descrizione ?? '').trim();\nconst barcode = String(body.barcode ?? '').trim();\nconst codice = String(body.codice ?? '').trim();\n\nconst parts = [];\nif (barcode) parts.push(barcode);\nif (codice) parts.push(codice);\nif (descrizione) parts.push(descrizione);\nparts.push('prezzo acquisto ricambio industriale Italia EUR');\n\nreturn [{\n json: {\n descrizione,\n barcode: barcode || null,\n codice: codice || null,\n quantita: body.quantita ?? null,\n query: parts.join(' '),\n meta: body.meta ?? {},\n },\n}];" }, "id": "code-build-query", "name": "Build Query", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [720, 200] }, { "parameters": { "method": "POST", "url": "https://api.tavily.com/search", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "sendBody": true, "specifyBody": "json", "jsonBody": "={\n \"query\": {{ JSON.stringify($json.query) }},\n \"search_depth\": \"basic\",\n \"max_results\": 8,\n \"include_answer\": false\n}", "options": { "timeout": 20000 } }, "id": "http-tavily-search", "name": "Tavily Search", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [960, 200], "onError": "continueRegularOutput", "credentials": { "httpHeaderAuth": { "id": "REPLACE_WITH_CREDENTIAL_ID", "name": "Tavily API" } } }, { "parameters": { "jsCode": "const query = $('Build Query').first().json.query;\nconst descrizione = $('Build Query').first().json.descrizione;\n\nconst DISCLAIMER =\n \"Prezzi indicativi da fonti web: verificare su ordine fornitore prima dell'acquisto.\";\nconst TAVILY_ERROR_DISCLAIMER =\n DISCLAIMER + \" Ricerca web non disponibile al momento: riprovare tra poco.\";\n\nfunction hostnameFromUrl(url) {\n try {\n return new URL(url).hostname.replace(/^www\\./, '');\n } catch {\n return 'Sconosciuto';\n }\n}\n\nfunction extractPriceFromText(text) {\n const patterns = [\n /(?:€|EUR)\\s*(\\d{1,6}[.,]\\d{2})/i,\n /(\\d{1,6}[.,]\\d{2})\\s*(?:€|EUR)/i,\n ];\n for (const re of patterns) {\n const m = text.match(re);\n if (m) {\n const n = Number(m[1].replace(',', '.'));\n if (Number.isFinite(n) && n > 0 && n < 100000) return n;\n }\n }\n return null;\n}\n\nconst tavilyItem = $input.first().json;\nconst tavilyFailed = Boolean(tavilyItem?.error);\nconst results = Array.isArray(tavilyItem?.results) ? tavilyItem.results : [];\n\nconst offers = [];\nconst seenUrls = new Set();\n\nfor (const r of results) {\n const url = String(r.url ?? '').trim();\n if (!url || url === '#' || seenUrls.has(url)) continue;\n\n const text = `${r.title ?? ''} ${r.content ?? ''}`;\n const price = extractPriceFromText(text);\n if (price === null) continue;\n\n seenUrls.add(url);\n offers.push({\n vendor: hostnameFromUrl(url).slice(0, 120),\n title: String(r.title ?? descrizione).slice(0, 200),\n price,\n currency: 'EUR',\n url,\n confidence: 'medium',\n source: 'snippet',\n });\n\n if (offers.length >= 8) break;\n}\n\nreturn [{\n json: {\n query,\n searchedAt: new Date().toISOString(),\n offers,\n disclaimer: tavilyFailed && offers.length === 0 ? TAVILY_ERROR_DISCLAIMER : DISCLAIMER,\n source: 'tavily',\n },\n}];" }, "id": "code-extract-prices", "name": "Estrai Prezzi", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [1200, 200] }, { "parameters": { "respondWith": "json", "responseBody": "={{ $json }}", "options": { "responseCode": 200 } }, "id": "respond-ok", "name": "Respond OK", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [1440, 200] }, { "parameters": { "respondWith": "json", "responseBody": "={{ { \"error\": \"descrizione obbligatoria\" } }}", "options": { "responseCode": 400 } }, "id": "respond-errore-400", "name": "Respond Errore 400", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [720, 420] } ], "connections": { "Webhook": { "main": [ [ { "node": "IF Descrizione valida", "type": "main", "index": 0 } ] ] }, "IF Descrizione valida": { "main": [ [ { "node": "Build Query", "type": "main", "index": 0 } ], [ { "node": "Respond Errore 400", "type": "main", "index": 0 } ] ] }, "Build Query": { "main": [ [ { "node": "Tavily Search", "type": "main", "index": 0 } ] ] }, "Tavily Search": { "main": [ [ { "node": "Estrai Prezzi", "type": "main", "index": 0 } ] ] }, "Estrai Prezzi": { "main": [ [ { "node": "Respond OK", "type": "main", "index": 0 } ] ] } }, "pinData": {}, "settings": { "executionOrder": "v1", "saveManualExecutions": true, "callerPolicy": "workflowsFromSameOwner", "errorWorkflow": "" }, "staticData": null, "tags": [ { "name": "magricambi" }, { "name": "ricerca-prezzi" } ], "triggerCount": 1, "updatedAt": "2026-06-07T18:00:00.000Z", "versionId": "magricambi-ricerca-prezzi-v3-tavily", "meta": { "templateCredsSetupCompleted": false, "instanceId": "magricambi-local" }, "active": false }