[
  {
    "updatedAt": "2026-10-31T19:58:13.000Z",
    "createdAt": "2026-10-31T19:58:11.945Z",
    "id": "leadIntakeDemo01",
    "name": "Flux leads PME (démo Memo Labs)",
    "description": null,
    "active": true,
    "isArchived": false,
    "nodes": [
      {
        "parameters": {
          "httpMethod": "POST",
          "path": "lead",
          "responseMode": "responseNode",
          "options": {}
        },
        "name": "Formulaire (Typeform ou Tally)",
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "position": [
          0,
          300
        ],
        "webhookId": "5d6f0a52-8c1e-4f7b-9a30-2b6e1c4d7f01",
        "id": "3201733f-9eef-47ed-92d4-240b73157e0a"
      },
      {
        "parameters": {
          "jsCode": "// Les seuls réglages qui changent d'un client à l'autre. Aucun secret ici : les clés d'API\n// vivent dans les identifiants chiffrés de n8n, jamais dans le workflow exporté.\nconst cfg = {\n  base_url: 'http://fakes-partenaire-automation:8080', // faux services de la démo\n  hot_score: 60,               // seuil « lead chaud » : on propose des créneaux\n  sales_channel: '#ventes',\n  ops_channel: '#ops-alertes',\n};\nreturn $input.all().map((item) => ({ json: { cfg, raw: item.json.body ?? item.json } }));"
        },
        "name": "Réglages du client",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          220,
          300
        ],
        "id": "193fd13f-22e5-4323-9f85-28aa11e34840"
      },
      {
        "parameters": {
          "jsCode": "// Accepte les deux formes de webhook les plus courantes chez les PME : Typeform et Tally.\n// Sort un lead normalisé et la liste des erreurs. Aucune donnée n'est écrite avant ce contrôle.\nconst raw = $input.first().json.raw || {};\nconst out = { source: null, submission_id: null, lead: {}, errors: [] };\nconst set = (k, v) => { if (v !== undefined && v !== null && v !== '') out.lead[k] = v; };\n\nif (raw.form_response) {\n  // Typeform : les réponses sont repérées par la « ref » du champ, stable même si le libellé change.\n  out.source = 'typeform';\n  out.submission_id = raw.form_response.token || raw.event_id || null;\n  for (const a of raw.form_response.answers || []) {\n    const ref = a.field && a.field.ref;\n    const v = a[a.type] && typeof a[a.type] === 'object' && a.type === 'choice' ? a.choice.label : a[a.type];\n    set(ref, v);\n  }\n} else if (raw.data && Array.isArray(raw.data.fields)) {\n  // Tally : les champs arrivent avec leur libellé, on les rapproche par mots-clés.\n  out.source = 'tally';\n  out.submission_id = raw.data.submissionId || raw.data.responseId || raw.eventId || null;\n  const map = [[/nom/i, 'name'], [/e-?mail/i, 'email'], [/t[ée]l[ée]phone/i, 'phone'],\n    [/entreprise|soci[ée]t[ée]/i, 'company'], [/budget/i, 'budget_chf'], [/demande|message/i, 'message'],\n    [/accepte|consent/i, 'consent']];\n  for (const f of raw.data.fields) {\n    const hit = map.find(([re]) => re.test(f.label || ''));\n    if (hit) set(hit[1], Array.isArray(f.value) ? f.value.length > 0 : f.value);\n  }\n} else {\n  out.errors.push('format de formulaire inconnu (ni Typeform ni Tally)');\n}\n\nconst L = out.lead;\nif (typeof L.name === 'string') L.name = L.name.trim().replace(/\\s+/g, ' ');\nif (typeof L.email === 'string') L.email = L.email.trim().toLowerCase();\nif (typeof L.phone === 'string') {\n  let p = L.phone.replace(/[\\s.()-]/g, '');\n  if (/^0\\d{9}$/.test(p)) p = '+41' + p.slice(1);   // numéro suisse saisi sans indicatif\n  L.phone = p;\n}\nif (L.budget_chf !== undefined) L.budget_chf = Number(String(L.budget_chf).replace(/[^\\d.]/g, '')) || 0;\n\nif (out.source) {\n  if (!out.submission_id) out.errors.push('identifiant de soumission absent');\n  if (!L.name || L.name.length < 2) out.errors.push('nom manquant');\n  if (!L.email) out.errors.push('email manquant');\n  else if (!/^[^\\s@]+@[^\\s@]+\\.[a-z]{2,}$/i.test(L.email)) out.errors.push('email invalide : ' + L.email);\n  if (L.phone && !/^\\+\\d{8,15}$/.test(L.phone)) out.errors.push('téléphone invalide : ' + L.phone);\n  if (L.consent !== true) out.errors.push('consentement au recontact absent');\n}\nout.valid = out.errors.length === 0;\nreturn [{ json: out }];"
        },
        "name": "Normaliser et valider",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          440,
          300
        ],
        "id": "905ab961-0a22-48f2-a38a-9a2cd4758be7"
      },
      {
        "parameters": {
          "conditions": {
            "options": {
              "caseSensitive": true,
              "leftValue": "",
              "typeValidation": "loose"
            },
            "conditions": [
              {
                "id": "Formulaire-valide-?",
                "leftValue": "={{ $json.valid }}",
                "rightValue": "",
                "operator": {
                  "type": "boolean",
                  "operation": "true",
                  "singleValue": true
                }
              }
            ],
            "combinator": "and"
          },
          "options": {}
        },
        "name": "Formulaire valide ?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2,
        "position": [
          660,
          300
        ],
        "id": "e77c988c-122f-4c13-b747-2ddff8d16d24"
      },
      {
        "parameters": {
          "jsCode": "const v = $('Normaliser et valider').first().json;\nconst text = `Formulaire rejeté (${v.source || 'source inconnue'}, soumission ${v.submission_id || 'sans identifiant'}) : `\n  + v.errors.join(' ; ') + '. Rien n\\'a été écrit dans le CRM.';\nreturn [{ json: { text, key: 'reject:' + (v.submission_id || Date.now()) } }];"
        },
        "name": "Préparer le rejet",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          880,
          520
        ],
        "id": "72df2dbc-346a-446a-a905-ff999fb25a28"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "={{ $('Réglages du client').first().json.cfg.base_url }}/slack/post",
          "options": {
            "timeout": 5000
          },
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Idempotency-Key",
                "value": "={{ $json.key }}"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ channel: $('Réglages du client').first().json.cfg.ops_channel, text: $json.text }) }}"
        },
        "name": "Slack : formulaire rejeté (simulé)",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          1100,
          520
        ],
        "retryOnFail": true,
        "maxTries": 3,
        "waitBetweenTries": 1000,
        "id": "98dfa841-94d7-46b0-8a54-58796b1878b4"
      },
      {
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ JSON.stringify({ status: 'rejected', errors: $('Normaliser et valider').first().json.errors }) }}",
          "options": {
            "responseCode": 422
          }
        },
        "name": "Répondre 422",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1.1,
        "position": [
          1320,
          520
        ],
        "id": "ea16cb9d-f4ca-4ee0-a639-65c2481662d9"
      },
      {
        "parameters": {
          "method": "GET",
          "url": "={{ $('Réglages du client').first().json.cfg.base_url }}/crm/contacts",
          "options": {
            "timeout": 5000
          },
          "sendQuery": true,
          "queryParameters": {
            "parameters": [
              {
                "name": "email",
                "value": "={{ $json.lead.email }}"
              }
            ]
          }
        },
        "name": "CRM : chercher le contact (simulé)",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          880,
          300
        ],
        "retryOnFail": true,
        "maxTries": 3,
        "waitBetweenTries": 1000,
        "id": "37329285-3bff-40aa-818b-1094ff972396"
      },
      {
        "parameters": {
          "jsCode": "// Doublon ou rejeu ? On compare l'email normalisé et l'identifiant de soumission.\n// - rejeu : la même soumission revient (renvoi du webhook, rejeu après panne). Chaque étape aval\n//   porte une clé d'idempotence, donc rejouer ne crée aucun second effet.\n// - doublon : la même personne renvoie le formulaire. On met le contact à jour, sans second email.\nconst v = $('Normaliser et valider').first().json;\nconst r = $input.first().json;\nconst subs = r.found ? (r.contact.submissions || []) : [];\nconst isReplay = subs.includes(v.submission_id);\nconst isDuplicate = r.found === true && subs[0] !== v.submission_id;\nreturn [{ json: { ...v, crm_contact_id: r.found ? r.contact.id : null, isReplay, isDuplicate,\n  // Rejeu : la qualification de cette soumission. Doublon : celle du contact, si elle vient du\n  // modèle (une relance « avez-vous reçu ma demande ? » ne doit pas déclasser un lead chaud).\n  known_qualification: isReplay ? ((r.contact.qualifications || {})[v.submission_id] || null)\n    : (isDuplicate && r.contact.qualification && r.contact.qualification.status === 'auto' ? r.contact.qualification : null) } }];"
        },
        "name": "Doublon ?",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1100,
          300
        ],
        "id": "5b3ad4d4-958f-4b66-9dcd-8b1b888dc7c2"
      },
      {
        "parameters": {
          "conditions": {
            "options": {
              "caseSensitive": true,
              "leftValue": "",
              "typeValidation": "loose"
            },
            "conditions": [
              {
                "id": "Qualification-déjà-enregistrée-?",
                "leftValue": "={{ !!$json.known_qualification }}",
                "rightValue": "",
                "operator": {
                  "type": "boolean",
                  "operation": "true",
                  "singleValue": true
                }
              }
            ],
            "combinator": "and"
          },
          "options": {}
        },
        "name": "Qualification déjà enregistrée ?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2,
        "position": [
          1320,
          300
        ],
        "id": "e3b18aee-b60b-48ac-bbae-d544f68fcc51"
      },
      {
        "parameters": {
          "jsCode": "// Rejeu ou doublon : la qualification est déjà dans le CRM, on ne repaie pas un appel au modèle.\nconst d = $input.first().json;\nreturn [{ json: { ...d, qualification: { ...d.known_qualification, reused: true } } }];"
        },
        "name": "Reprendre la qualification",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1540,
          140
        ],
        "id": "9143e897-d4b3-435a-b907-14945c0f7666"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "={{ $('Réglages du client').first().json.cfg.base_url }}/llm/qualify",
          "options": {
            "timeout": 4000
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ model: 'simulated-qualifier', messages: [{ role: 'system', content: 'Qualifie ce lead. Réponds uniquement en JSON : {score: entier 0 à 100, category: chaud|tiède|froid, reason: 280 caractères max}.' }, { role: 'user', content: JSON.stringify($json.lead) }], lead: $json.lead }) }}"
        },
        "name": "IA : qualifier le lead (simulé)",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          1540,
          380
        ],
        "retryOnFail": true,
        "maxTries": 2,
        "waitBetweenTries": 500,
        "onError": "continueRegularOutput",
        "id": "2a180b61-b8a5-42cb-9175-ffbaa8476011"
      },
      {
        "parameters": {
          "jsCode": "// Les contrôles obligatoires sont dans le code, pas dans le prompt : une réponse du modèle qui\n// sort du contrat (score hors bornes, catégorie inventée, JSON illisible, délai dépassé) ne passe\n// jamais. Le lead n'est pas perdu pour autant : il part en qualification manuelle.\nconst d = $('Doublon ?').first().json;\nconst cfg = $('Réglages du client').first().json.cfg;\nconst r = $input.first().json;\nlet q = null;\nlet problem = null;\nif (r.error) {\n  const m = typeof r.error === 'string' ? r.error : (r.error.message || JSON.stringify(r.error));\n  problem = /timeout|aborted|ECONNABORTED|timed out/i.test(m) ? 'modèle trop lent (délai de 4 s dépassé)' : 'modèle indisponible : ' + m;\n} else {\n  try { q = JSON.parse(r.choices[0].message.content); } catch (e) { problem = 'réponse du modèle illisible'; }\n}\nif (q) {\n  const cats = { chaud: [cfg.hot_score, 100], 'tiède': [40, cfg.hot_score - 1], froid: [0, 39] };\n  const bad = [];\n  if (!Number.isInteger(q.score) || q.score < 0 || q.score > 100) bad.push('score hors bornes (' + q.score + ')');\n  if (!cats[q.category]) bad.push('catégorie inconnue (' + q.category + ')');\n  else if (Number.isInteger(q.score) && (q.score < cats[q.category][0] || q.score > cats[q.category][1])) bad.push('catégorie incohérente avec le score');\n  if (typeof q.reason !== 'string' || q.reason.length > 280) bad.push('motif absent ou trop long');\n  if (bad.length) { problem = 'réponse du modèle hors contrat : ' + bad.join(', '); q = null; }\n}\nconst qualification = q\n  ? { score: q.score, category: q.category, reason: q.reason, status: 'auto' }\n  : { score: null, category: 'à qualifier', reason: problem, status: 'manuelle' };\nreturn [{ json: { ...d, qualification } }];"
        },
        "name": "Contrôler la qualification",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1760,
          380
        ],
        "id": "15ea9245-e122-49e7-8528-f1169882259b"
      },
      {
        "parameters": {
          "method": "PUT",
          "url": "={{ $('Réglages du client').first().json.cfg.base_url }}/crm/contacts",
          "options": {
            "timeout": 5000
          },
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Idempotency-Key",
                "value": "={{ $json.submission_id }}"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ email: $json.lead.email, name: $json.lead.name, phone: $json.lead.phone, company: $json.lead.company, message: $json.lead.message, source: $json.source, submission_id: $json.submission_id, qualification: $json.qualification, tags: [$json.source, $json.qualification.status === 'manuelle' ? 'qualification-manuelle' : 'qualifie-auto', $json.qualification.category] }) }}"
        },
        "name": "CRM : créer ou mettre à jour (simulé)",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          1980,
          300
        ],
        "retryOnFail": true,
        "maxTries": 3,
        "waitBetweenTries": 2000,
        "id": "d03bc501-bc7a-4436-86e1-a910b476bd2e"
      },
      {
        "parameters": {
          "jsCode": "// Choisit la suite et prépare les textes. Les nœuds HTTP qui suivent ne font qu'envoyer.\nconst pick = (n) => { try { return $(n).first().json; } catch (e) { return null; } };\nconst d = pick('Contrôler la qualification') || pick('Reprendre la qualification');\nconst cfg = $('Réglages du client').first().json.cfg;\nconst crm = $input.first().json;\nconst q = d.qualification;\nconst hot = q.status === 'auto' && q.score >= cfg.hot_score;\nconst who = `${d.lead.name}${d.lead.company ? ' (' + d.lead.company + ')' : ''}`;\nlet route;\nlet text;\nif (d.isDuplicate) {\n  route = 'doublon';\n  text = `Doublon : ${who} a renvoyé le formulaire. Contact ${crm.contact.id} mis à jour, pas de second email au prospect. Nouveau message : « ${d.lead.message || ''} »`;\n} else if (q.status === 'manuelle') {\n  route = 'accuse';\n  text = `Nouveau lead à qualifier à la main : ${who}, contact ${crm.contact.id}. Motif : ${q.reason}. Accusé de réception envoyé.`;\n} else if (hot) {\n  route = 'creneaux';\n  const why = q.reason ? q.reason.charAt(0).toUpperCase() + q.reason.slice(1) + '. ' : '';\n  text = `Lead chaud (${q.score}/100) : ${who}, contact ${crm.contact.id}. ${why}Trois créneaux proposés par email.`;\n} else {\n  route = 'accuse';\n  text = `Nouveau lead ${q.category} (${q.score}/100) : ${who}, contact ${crm.contact.id}. Accusé de réception envoyé.`;\n}\nreturn [{ json: { ...d, crm_action: crm.action, crm_contact_id: crm.contact.id, route,\n  isNew: !d.isDuplicate, hot: route === 'creneaux', slack_text: text } }];"
        },
        "name": "Décider de la suite",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2200,
          300
        ],
        "id": "5757c1ae-f6c6-4e04-a710-1c74490b1b16"
      },
      {
        "parameters": {
          "conditions": {
            "options": {
              "caseSensitive": true,
              "leftValue": "",
              "typeValidation": "loose"
            },
            "conditions": [
              {
                "id": "Nouveau-contact-?",
                "leftValue": "={{ $json.isNew }}",
                "rightValue": "",
                "operator": {
                  "type": "boolean",
                  "operation": "true",
                  "singleValue": true
                }
              }
            ],
            "combinator": "and"
          },
          "options": {}
        },
        "name": "Nouveau contact ?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2,
        "position": [
          2420,
          300
        ],
        "id": "91a57d31-c485-4243-9cac-28b7dc3aef3d"
      },
      {
        "parameters": {
          "conditions": {
            "options": {
              "caseSensitive": true,
              "leftValue": "",
              "typeValidation": "loose"
            },
            "conditions": [
              {
                "id": "Lead-chaud-?",
                "leftValue": "={{ $json.hot }}",
                "rightValue": "",
                "operator": {
                  "type": "boolean",
                  "operation": "true",
                  "singleValue": true
                }
              }
            ],
            "combinator": "and"
          },
          "options": {}
        },
        "name": "Lead chaud ?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2,
        "position": [
          2640,
          200
        ],
        "id": "46d7ed03-6e94-421d-8346-f5a23044d60c"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "={{ $('Réglages du client').first().json.cfg.base_url }}/calendar/slots",
          "options": {
            "timeout": 5000
          },
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Idempotency-Key",
                "value": "={{ $json.submission_id + ':slots' }}"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ email: $json.lead.email, duration_min: 30 }) }}"
        },
        "name": "Agenda : proposer 3 créneaux (simulé)",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          2860,
          100
        ],
        "retryOnFail": true,
        "maxTries": 3,
        "waitBetweenTries": 1000,
        "id": "90184b5e-864e-4473-b287-6cdb3ce76b56"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "={{ $('Réglages du client').first().json.cfg.base_url }}/mail/send",
          "options": {
            "timeout": 5000
          },
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Idempotency-Key",
                "value": "={{ $('Décider de la suite').first().json.submission_id + ':mail' }}"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ to: $('Décider de la suite').first().json.lead.email, subject: 'Votre demande : trois créneaux pour en parler', template: 'rdv', slots: $json.proposal.slots }) }}"
        },
        "name": "Email : proposition de rendez-vous (simulé)",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          3080,
          100
        ],
        "retryOnFail": true,
        "maxTries": 3,
        "waitBetweenTries": 1000,
        "id": "256a3c9b-e079-4c9c-a24a-05c1429314a4"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "={{ $('Réglages du client').first().json.cfg.base_url }}/mail/send",
          "options": {
            "timeout": 5000
          },
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Idempotency-Key",
                "value": "={{ $json.submission_id + ':mail' }}"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ to: $json.lead.email, subject: 'Nous avons bien reçu votre demande', template: 'accuse' }) }}"
        },
        "name": "Email : accusé de réception (simulé)",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          2860,
          300
        ],
        "retryOnFail": true,
        "maxTries": 3,
        "waitBetweenTries": 1000,
        "id": "6aece4e6-c2e5-440c-995b-ede48a044655"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "={{ $('Réglages du client').first().json.cfg.base_url }}/slack/post",
          "options": {
            "timeout": 5000
          },
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Idempotency-Key",
                "value": "={{ $('Décider de la suite').first().json.submission_id + ':slack' }}"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ channel: $('Réglages du client').first().json.cfg.sales_channel, text: $('Décider de la suite').first().json.slack_text }) }}"
        },
        "name": "Slack : alerter l'équipe (simulé)",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          3300,
          300
        ],
        "retryOnFail": true,
        "maxTries": 3,
        "waitBetweenTries": 1000,
        "id": "7c1d3d5f-233b-44a2-a021-ae892490e69e"
      },
      {
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ JSON.stringify({ status: 'ok', submission_id: $('Décider de la suite').first().json.submission_id, crm_action: $('Décider de la suite').first().json.crm_action, contact: $('Décider de la suite').first().json.crm_contact_id, route: $('Décider de la suite').first().json.route, qualification: $('Décider de la suite').first().json.qualification }) }}",
          "options": {
            "responseCode": 200
          }
        },
        "name": "Répondre 200",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1.1,
        "position": [
          3520,
          300
        ],
        "id": "58ec610e-7d32-4034-9e83-7807c6d79c8c"
      }
    ],
    "connections": {
      "Formulaire (Typeform ou Tally)": {
        "main": [
          [
            {
              "node": "Réglages du client",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Réglages du client": {
        "main": [
          [
            {
              "node": "Normaliser et valider",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Normaliser et valider": {
        "main": [
          [
            {
              "node": "Formulaire valide ?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Préparer le rejet": {
        "main": [
          [
            {
              "node": "Slack : formulaire rejeté (simulé)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Slack : formulaire rejeté (simulé)": {
        "main": [
          [
            {
              "node": "Répondre 422",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "CRM : chercher le contact (simulé)": {
        "main": [
          [
            {
              "node": "Doublon ?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Doublon ?": {
        "main": [
          [
            {
              "node": "Qualification déjà enregistrée ?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "IA : qualifier le lead (simulé)": {
        "main": [
          [
            {
              "node": "Contrôler la qualification",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Contrôler la qualification": {
        "main": [
          [
            {
              "node": "CRM : créer ou mettre à jour (simulé)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Reprendre la qualification": {
        "main": [
          [
            {
              "node": "CRM : créer ou mettre à jour (simulé)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "CRM : créer ou mettre à jour (simulé)": {
        "main": [
          [
            {
              "node": "Décider de la suite",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Décider de la suite": {
        "main": [
          [
            {
              "node": "Nouveau contact ?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Agenda : proposer 3 créneaux (simulé)": {
        "main": [
          [
            {
              "node": "Email : proposition de rendez-vous (simulé)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Email : proposition de rendez-vous (simulé)": {
        "main": [
          [
            {
              "node": "Slack : alerter l'équipe (simulé)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Email : accusé de réception (simulé)": {
        "main": [
          [
            {
              "node": "Slack : alerter l'équipe (simulé)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Slack : alerter l'équipe (simulé)": {
        "main": [
          [
            {
              "node": "Répondre 200",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Formulaire valide ?": {
        "main": [
          [
            {
              "node": "CRM : chercher le contact (simulé)",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Préparer le rejet",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Qualification déjà enregistrée ?": {
        "main": [
          [
            {
              "node": "Reprendre la qualification",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "IA : qualifier le lead (simulé)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Nouveau contact ?": {
        "main": [
          [
            {
              "node": "Lead chaud ?",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Slack : alerter l'équipe (simulé)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Lead chaud ?": {
        "main": [
          [
            {
              "node": "Agenda : proposer 3 créneaux (simulé)",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Email : accusé de réception (simulé)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "settings": {
      "executionOrder": "v1",
      "errorWorkflow": "errorAlertDemo01",
      "timezone": "Europe/Zurich",
      "saveDataSuccessExecution": "all",
      "saveDataErrorExecution": "all",
      "saveManualExecutions": true,
      "callerPolicy": "workflowsFromSameOwner"
    },
    "staticData": null,
    "meta": null,
    "nodeGroups": [],
    "pinData": {},
    "versionId": "4c746b16-11ee-48e3-adca-f224f4659b45",
    "activeVersionId": "4c746b16-11ee-48e3-adca-f224f4659b45",
    "versionCounter": 1,
    "triggerCount": 1,
    "sourceWorkflowId": null,
    "tags": [],
    "shared": [
      {
        "updatedAt": "2026-10-31T19:58:11.947Z",
        "createdAt": "2026-10-31T19:58:11.947Z",
        "role": "workflow:owner",
        "workflowId": "leadIntakeDemo01",
        "projectId": "7LTUHtgnonN2RDaB",
        "project": {
          "updatedAt": "2026-10-31T19:58:08.601Z",
          "createdAt": "2026-10-31T19:58:08.601Z",
          "id": "7LTUHtgnonN2RDaB",
          "name": "Unnamed Project",
          "type": "personal",
          "icon": null,
          "description": null,
          "customTelemetryTags": [],
          "creatorId": "a4795032-97a2-4039-8298-4e00747bfdf7"
        }
      }
    ],
    "versionMetadata": {
      "name": null,
      "description": null
    }
  }
]