{
  "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
}
