{
  "name": "Vertrieb: Visitenkarte → KI-Erkennung → HubSpot CRM + LinkedIn-Einladung",
  "nodes": [
    {
      "id": "webhook-1",
      "name": "Visitenkarte empfangen",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [250, 300],
      "webhookId": "visitenkarte-scan",
      "parameters": {
        "httpMethod": "POST",
        "path": "visitenkarte-scan",
        "responseMode": "lastNode",
        "options": {
          "binaryData": true,
          "rawBody": true
        }
      },
      "notes": "Empfängt das Foto der Visitenkarte per POST-Request (z.B. von einer Scan-App, Shortcut auf dem Handy, oder einem Formular). Sende das Bild als Binary im Body."
    },
    {
      "id": "openai-vision",
      "name": "KI liest Visitenkarte",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 2.1,
      "position": [500, 300],
      "parameters": {
        "resource": "image",
        "operation": "analyze",
        "modelId": {
          "__rl": true,
          "mode": "id",
          "value": "gpt-4o"
        },
        "text": "Du bist ein OCR-Experte für Visitenkarten. Extrahiere ALLE Informationen von dieser Visitenkarte.\n\nAntworte ausschließlich im folgenden JSON-Format (keine Markdown, kein Text drumherum):\n{\n  \"vorname\": \"\",\n  \"nachname\": \"\",\n  \"firma\": \"\",\n  \"position\": \"\",\n  \"email\": \"\",\n  \"telefon\": \"\",\n  \"mobil\": \"\",\n  \"website\": \"\",\n  \"adresse\": \"\",\n  \"linkedin_url\": \"\"\n}\n\nFalls ein Feld nicht lesbar ist, setze einen leeren String. Falls du eine LinkedIn-URL oder ein LinkedIn-Handle erkennst, trage es unter linkedin_url ein.",
        "options": {
          "maxTokens": 500,
          "temperature": 0.1
        }
      },
      "credentials": {
        "openAiApi": {
          "id": "",
          "name": "OpenAI API"
        }
      },
      "notes": "GPT-4o analysiert das Foto der Visitenkarte und extrahiert alle Kontaktdaten strukturiert als JSON."
    },
    {
      "id": "code-parse",
      "name": "Kontaktdaten parsen",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [750, 300],
      "parameters": {
        "jsCode": "// Parse the AI Vision response\nconst aiResponse = $input.first().json.message.content;\n\ntry {\n  let jsonStr = aiResponse;\n  const jsonMatch = aiResponse.match(/\\{[\\s\\S]*\\}/);\n  if (jsonMatch) {\n    jsonStr = jsonMatch[0];\n  }\n  \n  const contact = JSON.parse(jsonStr);\n  \n  // Build LinkedIn search URL as fallback if no direct URL found\n  let linkedinUrl = contact.linkedin_url || '';\n  let linkedinSearchUrl = '';\n  if (!linkedinUrl && contact.vorname && contact.nachname) {\n    linkedinSearchUrl = `https://www.linkedin.com/search/results/people/?keywords=${encodeURIComponent(contact.vorname + ' ' + contact.nachname + ' ' + (contact.firma || ''))}`;\n  }\n  \n  return [{\n    json: {\n      vorname: contact.vorname || '',\n      nachname: contact.nachname || '',\n      voller_name: `${contact.vorname || ''} ${contact.nachname || ''}`.trim(),\n      firma: contact.firma || '',\n      position: contact.position || '',\n      email: contact.email || '',\n      telefon: contact.telefon || '',\n      mobil: contact.mobil || '',\n      website: contact.website || '',\n      adresse: contact.adresse || '',\n      linkedin_url: linkedinUrl,\n      linkedin_search_url: linkedinSearchUrl,\n      hat_linkedin: linkedinUrl !== '',\n      erfasst_am: new Date().toISOString().split('T')[0],\n      quelle: 'Visitenkarten-Scan'\n    }\n  }];\n} catch (e) {\n  return [{\n    json: {\n      error: true,\n      raw_response: aiResponse,\n      message: 'Visitenkarte konnte nicht geparst werden: ' + e.message\n    }\n  }];\n}"
      },
      "notes": "Parst die KI-Antwort in saubere Kontaktfelder. Erstellt automatisch eine LinkedIn-Such-URL als Fallback, falls kein direkter LinkedIn-Link auf der Karte stand."
    },
    {
      "id": "if-error",
      "name": "Parse erfolgreich?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [1000, 300],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "check-error",
              "leftValue": "={{ $json.error }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "notTrue"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "notes": "Prüft ob das Parsen erfolgreich war. Bei Fehler wird eine Benachrichtigung gesendet statt falsche Daten ins CRM zu schreiben."
    },
    {
      "id": "hubspot-1",
      "name": "HubSpot Kontakt anlegen",
      "type": "n8n-nodes-base.hubspot",
      "typeVersion": 2.2,
      "position": [1300, 200],
      "parameters": {
        "resource": "contact",
        "operation": "upsert",
        "email": "={{ $json.email }}",
        "additionalFields": {
          "firstName": "={{ $json.vorname }}",
          "lastName": "={{ $json.nachname }}",
          "phone": "={{ $json.telefon }}",
          "mobilePhone": "={{ $json.mobil }}",
          "company": "={{ $json.firma }}",
          "jobTitle": "={{ $json.position }}",
          "website": "={{ $json.website }}",
          "address": "={{ $json.adresse }}",
          "lifecycleStage": "lead",
          "leadStatus": "NEW",
          "hs_lead_status": "NEW"
        }
      },
      "credentials": {
        "hubspotApi": {
          "id": "",
          "name": "HubSpot API"
        }
      },
      "notes": "Erstellt oder aktualisiert den Kontakt in HubSpot (Upsert basierend auf E-Mail). Setzt Lifecycle Stage auf 'Lead'."
    },
    {
      "id": "if-linkedin",
      "name": "LinkedIn vorhanden?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [1550, 200],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "check-linkedin",
              "leftValue": "={{ $('Kontaktdaten parsen').item.json.hat_linkedin }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "notes": "Prüft ob ein direkter LinkedIn-Link auf der Visitenkarte gefunden wurde."
    },
    {
      "id": "linkedin-connect",
      "name": "LinkedIn-Einladung vorbereiten",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 2.1,
      "position": [1800, 100],
      "parameters": {
        "resource": "text",
        "operation": "message",
        "modelId": {
          "__rl": true,
          "mode": "id",
          "value": "gpt-4o-mini"
        },
        "messages": {
          "values": [
            {
              "content": "=Schreibe eine kurze, persönliche LinkedIn-Einladungsnachricht (max. 280 Zeichen) für folgenden Kontakt:\n\nName: {{ $('Kontaktdaten parsen').item.json.voller_name }}\nFirma: {{ $('Kontaktdaten parsen').item.json.firma }}\nPosition: {{ $('Kontaktdaten parsen').item.json.position }}\n\nDie Nachricht soll:\n- Sich auf ein persönliches Treffen/Event beziehen (wir haben die Visitenkarte persönlich erhalten)\n- Professionell aber freundlich sein\n- Auf Deutsch sein\n- Einen konkreten Mehrwert für die Vernetzung nennen\n\nAntworte NUR mit der Nachricht, ohne Anführungszeichen oder Erklärungen."
            }
          ]
        },
        "options": {
          "temperature": 0.7,
          "maxTokens": 200
        }
      },
      "credentials": {
        "openAiApi": {
          "id": "",
          "name": "OpenAI API"
        }
      },
      "notes": "Generiert eine personalisierte LinkedIn-Einladungsnachricht basierend auf den Visitenkarten-Daten."
    },
    {
      "id": "linkedin-notify",
      "name": "LinkedIn-Aufgabe erstellen",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [2050, 100],
      "parameters": {
        "jsCode": "// Prepare LinkedIn connection task\nconst contact = $('Kontaktdaten parsen').first().json;\nconst einladungsText = $input.first().json.message.content;\n\nreturn [{\n  json: {\n    aufgabe: 'LinkedIn-Einladung senden',\n    kontakt: contact.voller_name,\n    firma: contact.firma,\n    linkedin_url: contact.linkedin_url,\n    einladungstext: einladungsText.substring(0, 280),\n    status: 'offen',\n    erstellt_am: new Date().toISOString()\n  }\n}];"
      },
      "notes": "Erstellt eine strukturierte Aufgabe mit dem vorformulierten Einladungstext. Dieser kann per Slack/E-Mail an den Vertriebler gesendet werden."
    },
    {
      "id": "linkedin-search-fallback",
      "name": "LinkedIn-Suche vorbereiten",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1800, 300],
      "parameters": {
        "jsCode": "// Fallback: Create LinkedIn search task when no direct URL found\nconst contact = $('Kontaktdaten parsen').first().json;\n\nreturn [{\n  json: {\n    aufgabe: 'LinkedIn-Profil suchen & verbinden',\n    kontakt: contact.voller_name,\n    firma: contact.firma,\n    linkedin_search_url: contact.linkedin_search_url,\n    hinweis: `Kein LinkedIn-Link auf der Visitenkarte gefunden. Bitte manuell suchen: ${contact.voller_name} bei ${contact.firma}`,\n    status: 'offen',\n    erstellt_am: new Date().toISOString()\n  }\n}];"
      },
      "notes": "Fallback wenn kein LinkedIn-Link auf der Visitenkarte war: Erstellt eine Such-URL und eine Aufgabe für den Vertriebler."
    },
    {
      "id": "slack-notify",
      "name": "Slack-Benachrichtigung",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [2300, 200],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "channel": {
          "__rl": true,
          "mode": "name",
          "value": "#vertrieb"
        },
        "text": "=:handshake: *Neuer Kontakt aus Visitenkarten-Scan*\n\n:bust_in_silhouette: *{{ $('Kontaktdaten parsen').item.json.voller_name }}*\n:briefcase: {{ $('Kontaktdaten parsen').item.json.position }} bei {{ $('Kontaktdaten parsen').item.json.firma }}\n:email: {{ $('Kontaktdaten parsen').item.json.email }}\n:phone: {{ $('Kontaktdaten parsen').item.json.telefon || $('Kontaktdaten parsen').item.json.mobil || 'k.A.' }}\n\n:white_check_mark: HubSpot-Kontakt angelegt/aktualisiert\n{{ $json.aufgabe ? ':link: LinkedIn: ' + ($json.linkedin_url || $json.linkedin_search_url || 'Bitte manuell suchen') : '' }}\n{{ $json.einladungstext ? ':speech_balloon: Vorgeschlagener Einladungstext:\\n>' + $json.einladungstext : '' }}",
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "id": "",
          "name": "Slack API"
        }
      },
      "notes": "Sendet eine Zusammenfassung an den #vertrieb Slack-Channel mit allen Kontaktdaten, CRM-Status und LinkedIn-Einladungstext. Kanal kann angepasst werden."
    },
    {
      "id": "error-notify",
      "name": "Fehler melden",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [1300, 450],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "channel": {
          "__rl": true,
          "mode": "name",
          "value": "#vertrieb"
        },
        "text": "=:warning: *Visitenkarten-Scan fehlgeschlagen*\n\nDie Visitenkarte konnte nicht automatisch erkannt werden.\nBitte manuell erfassen.\n\nFehler: {{ $json.message }}",
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "id": "",
          "name": "Slack API"
        }
      },
      "notes": "Benachrichtigt bei Fehlern, damit keine Visitenkarte verloren geht."
    }
  ],
  "connections": {
    "Visitenkarte empfangen": {
      "main": [
        [
          {
            "node": "KI liest Visitenkarte",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "KI liest Visitenkarte": {
      "main": [
        [
          {
            "node": "Kontaktdaten parsen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Kontaktdaten parsen": {
      "main": [
        [
          {
            "node": "Parse erfolgreich?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse erfolgreich?": {
      "main": [
        [
          {
            "node": "HubSpot Kontakt anlegen",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fehler melden",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot Kontakt anlegen": {
      "main": [
        [
          {
            "node": "LinkedIn vorhanden?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn vorhanden?": {
      "main": [
        [
          {
            "node": "LinkedIn-Einladung vorbereiten",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "LinkedIn-Suche vorbereiten",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn-Einladung vorbereiten": {
      "main": [
        [
          {
            "node": "LinkedIn-Aufgabe erstellen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn-Aufgabe erstellen": {
      "main": [
        [
          {
            "node": "Slack-Benachrichtigung",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn-Suche vorbereiten": {
      "main": [
        [
          {
            "node": "Slack-Benachrichtigung",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false,
  "tags": [
    {
      "name": "Vertrieb"
    },
    {
      "name": "Automatisierung"
    },
    {
      "name": "CRM"
    }
  ]
}
