{
  "name": "My workflow",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "`search`",
        "responseMode": "lastNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        4240,
        8576
      ],
      "id": "bcbd7779-5f36-43c2-b189-6048ef13f5bf",
      "name": "Webhook"
    },
    {
      "parameters": {
        "errorMessage": "400 Simple Error "
      },
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        4912,
        8624
      ],
      "id": "18a14323-8769-466f-95a7-dde5bb307f0b",
      "name": "Stop and Error"
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nconst keywords = items[0].json.keywords;\nconst area = items[0].json.area || '';\nconst region = items[0].json.region || '';\nconst searchId = items[0].json.searchId;\n\n// Arrays de t\u00e9rminos que mejoran la b\u00fasqueda de contactos\nconst contactTerms = [\n  'email contacto',\n  'directorio profesionales',\n  'perfil profesional',\n  'curriculum vitae'\n];\n\nconst queries = [];\n\n// Generar m\u00faltiples queries con diferentes enfoques\ncontactTerms.forEach(term => {\n  queries.push({\n    query: `${keywords} ${region} ${term} site:.cl OR site:linkedin.com OR site:researchgate.net`,\n    searchTerm: keywords,\n    area: area,\n    region: region,\n    searchId: searchId,\n    queryType: term\n  });\n});\n\n// Query espec\u00edfica para colegios profesionales\nif (area && area.toLowerCase().includes('salud')) {\n  queries.push({\n    query: `${keywords} ${region} site:colegiomedicochile.cl OR site:minsal.cl contacto`,\n    searchTerm: keywords,\n    area: area,\n    region: region,\n    searchId: searchId,\n    queryType: 'colegio_profesional'\n  });\n}\n\nreturn queries.map(q => ({ json: q }));"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4912,
        8432
      ],
      "id": "34fc3a5b-e66a-4949-9bef-c19305fdf730",
      "name": "Build Search Query"
    },
    {
      "parameters": {
        "url": "https://serpapi.com/search",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "q",
              "value": "={{$json.query}}"
            },
            {
              "name": "api_key",
              "value": "d401a6726f70ad6a019aafe5a4a4496171d4e50becb4f7ec1047346d4c8b1036"
            },
            {
              "name": "location",
              "value": "chile"
            },
            {
              "name": "gl",
              "value": "cl"
            },
            {
              "name": "hl",
              "value": "es"
            }
          ]
        },
        "options": {
          "timeout": 10000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        5136,
        8432
      ],
      "id": "55246d27-a97b-46ca-9960-7b4c639e827d",
      "name": "Google Custom Search API",
      "retryOnFail": true,
      "waitBetweenTries": 2000
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 3
          },
          "conditions": [
            {
              "id": "aa00dba6-61ac-4f88-9dc2-8d9d2793170d",
              "leftValue": "={{$json.keywords}}",
              "rightValue": "undefined",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            },
            {
              "id": "d82c94fd-6e96-4ee7-b2ca-8fbb5d96ab43",
              "leftValue": "={{$json.keywords.length}}",
              "rightValue": 3,
              "operator": {
                "type": "number",
                "operation": "gte"
              }
            },
            {
              "id": "f1e94d69-83b8-4c3a-8ea0-0df622386d07",
              "leftValue": "={{$json.searchId}}",
              "rightValue": 0,
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        4688,
        8576
      ],
      "id": "403711b2-b561-467d-9204-bc9edf739c40",
      "name": "IF Data is Valid",
      "alwaysOutputData": false
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nconst body = items[0].json.body;\n\nreturn [{\n  json: {\n    searchId: body.search_id,\n    keywords: body.keywords,\n    area: body.area,\n    region: body.region,\n    timestamp: new Date().toISOString(),\n    startTime: Date.now()\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4464,
        8576
      ],
      "id": "31b91048-124b-439b-ae0a-3e9d8d45175e",
      "name": "Data Structure"
    },
    {
      "parameters": {
        "jsCode": "// ==================== PARSER MEJORADO - AUTO-DETECT ESTRUCTURA ====================\n\ntry {\n  let inputData = $input.all();\n  \n  // Si inputData est\u00e1 vac\u00edo o no tiene items, intentar extraer del primer objeto\n  if (!inputData || inputData.length === 0) {\n    return [{ json: { items: [] } }];\n  }\n\n  // DEBUG: Ver qu\u00e9 estructura tiene el input\n  const firstItem = inputData[0];\n  let itemsToProcess = [];\n\n  // Intenta detectar d\u00f3nde est\u00e1n realmente los datos\n  if (firstItem.json && Array.isArray(firstItem.json)) {\n    // Si json es directamente un array\n    itemsToProcess = firstItem.json;\n  } else if (firstItem.json && Array.isArray(firstItem.json.items)) {\n    // Si json tiene un campo \"items\"\n    itemsToProcess = firstItem.json.items;\n  } else if (firstItem.json && Array.isArray(firstItem.json.organic_results)) {\n    // Si es de SerpAPI directamente\n    itemsToProcess = firstItem.json.organic_results;\n  } else if (Array.isArray(firstItem.json)) {\n    // Intenta directamente\n    itemsToProcess = firstItem.json;\n  } else {\n    // Si firstItem.json es un objeto individual\n    itemsToProcess = inputData.map(item => item.json);\n  }\n\n  // Si a\u00fan est\u00e1 vac\u00edo\n  if (!itemsToProcess || itemsToProcess.length === 0) {\n    return [{ json: { items: [], debug: \"No se encontraron datos para procesar\" } }];\n  }\n\n  // ==================== MAPEO REGIONES CHILE ====================\n  const regionKeywords = {\n    \"Arica y Parinacota\": [\"arica y parinacota\", \"arica\", \"parinacota\"],\n    \"Tarapac\u00e1\": [\"tarapac\u00e1\", \"tarapaca\", \"iquique\"],\n    \"Antofagasta\": [\"antofagasta\"],\n    \"Atacama\": [\"atacama\", \"copiap\u00f3\", \"copiapo\"],\n    \"Coquimbo\": [\"coquimbo\", \"la serena\"],\n    \"Valpara\u00edso\": [\"valpara\u00edso\", \"valparaiso\", \"vi\u00f1a\", \"quilpu\u00e9\", \"conc\u00f3n\"],\n    \"Santiago\": [\"santiago\", \"metropolitana\", \"rm\", \"\u00f1u\u00f1oa\"],\n    \"O'Higgins\": [\"o'higgins\", \"ohiggins\", \"rancagua\"],\n    \"Maule\": [\"maule\", \"talca\"],\n    \"\u00d1uble\": [\"\u00f1uble\", \"nuble\", \"chill\u00e1n\", \"chillan\"],\n    \"Biob\u00edo\": [\"biob\u00edo\", \"biobio\", \"b\u00edo b\u00edo\", \"bio bio\", \"concepci\u00f3n\", \"concepcion\"],\n    \"La Araucan\u00eda\": [\"la araucan\u00eda\", \"la araucania\", \"araucan\u00eda\", \"araucania\", \"temuco\"],\n    \"Los R\u00edos\": [\"los r\u00edos\", \"los rios\", \"valdivia\"],\n    \"Los Lagos\": [\"los lagos\", \"puerto montt\"],\n    \"Ays\u00e9n\": [\"ays\u00e9n\", \"aysen\", \"coihaique\", \"coyhaique\"],\n    \"Magallanes\": [\"magallanes\", \"punta arenas\"],\n  };\n\n  const results = [];\n\n  // Procesar cada item\n  for (let idx = 0; idx < itemsToProcess.length; idx++) {\n    const item = itemsToProcess[idx];\n\n    // Validar datos b\u00e1sicos\n    if (!item.link && !item.title) {\n      continue;\n    }\n\n    // Detectar regi\u00f3n\n    let regionInferred = \"Sin especificar\";\n    const searchText = ((item.snippet || \"\") + \" \" + (item.title || \"\") + \" \" + (item.link || \"\")).toLowerCase();\n\n    for (const [region, regionKeywordsList] of Object.entries(regionKeywords)) {\n      let found = false;\n      for (const keyword of regionKeywordsList) {\n        if (searchText.includes(keyword.toLowerCase())) {\n          regionInferred = region;\n          found = true;\n          break;\n        }\n      }\n      if (found) break;\n    }\n\n    // Construir resultado\n    results.push({\n      metadata: {\n        title: item.title || null,\n        link: item.link || null,\n        snippet: item.snippet || null,\n        source: item.source || null,\n        position: item.position || null,\n      },\n      region: regionInferred,\n      search_id: null,\n      keywords: null,\n      processed: false,\n      html_content: null,\n      contact_data: null,\n    });\n  }\n\n  return [{ json: { items: results, count: results.length } }];\n\n} catch (err) {\n  let errorMsg = \"Unknown error\";\n  if (typeof err === 'string') {\n    errorMsg = err;\n  } else if (err && typeof err === 'object' && 'message' in err) {\n    errorMsg = String(err.message);\n  }\n  return [{ json: { items: [], error: errorMsg } }];\n}"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        5360,
        8432
      ],
      "id": "f2fbe179-3b03-4054-9895-ef9cb14c8711",
      "name": "Parser1"
    },
    {
      "parameters": {
        "jsCode": "try {\n  // Obtener todos los items\n  const input = $input.first().json;\n  let items = [];\n  \n  // Si viene con estructura de Parser (items array), procesar todos\n  if (input.items && Array.isArray(input.items) && input.items.length > 0) {\n    items = input.items;\n  } else if (input.metadata) {\n    // Si ya es un item individual\n    items = [input];\n  } else {\n    throw new Error(\"Invalid input structure: no metadata found\");\n  }\n  \n  // Procesar cada item\n  return items.map(item => {\n    try {\n      const metadata = item.metadata;\n      const link = metadata.link.toLowerCase();\n      const title = metadata.title.toLowerCase();\n\n  // ==================== PATRONES A IGNORAR ====================\n  const ignorePatterns = {\n    // Marketplaces de servicios\n    marketplace: [\n      \"cronoshare.cl\",\n      \"starofservice.cl\",\n      \"uber.com\",\n      \"airbnb.com\",\n      \"fiverr.com\",\n      \"upwork.com\",\n      \"freelancer.com\",\n    ],\n    // Bolsas de empleo\n    jobsites: [\n      \"computrabajo.cl\",\n      \"linkedin.com/jobs\",\n      \"trabajos.com\",\n      \"buscojob.com\",\n      \"indeed.com\",\n      \"infojobs.com\",\n    ],\n    // Instituciones educativas\n    education: [\n      \"culinary.cl\",\n      \".edu.cl\",\n      \"universidad\",\n      \"escuela\",\n      \"curso\",\n      \"clase en l\u00ednea\",\n      \"diplomado\",\n    ],\n    // Otros no deseados\n    other: [\n      \"google.com\",\n      \"facebook.com\",\n      \"instagram.com\",\n      \"tiktok.com\",\n      \"youtube.com\",\n      \"wikipedia.org\",\n      \"wix.com\",\n      \"squarespace.com\",\n    ],\n  };\n\n  // ==================== CLASIFICACI\u00d3N ====================\n\n  let shouldIgnore = false;\n  let ignoreReason = null;\n\n  // Verificar si es oferta de trabajo\n  if (title.includes(\"oferta\") || title.includes(\"trabajo\") || title.includes(\"empleo\")) {\n    if (!link.includes(\"linkedin.com\")) { // LinkedIn profiles s\u00ed son v\u00e1lidos\n      shouldIgnore = true;\n      ignoreReason = \"job_listing\";\n    }\n  }\n\n  // Verificar patrones a ignorar\n  for (const [category, patterns] of Object.entries(ignorePatterns)) {\n    for (const pattern of patterns) {\n      if (link.includes(pattern.toLowerCase())) {\n        shouldIgnore = true;\n        ignoreReason = category;\n        break;\n      }\n    }\n    if (shouldIgnore) break;\n  }\n\n  // ==================== VALIDACI\u00d3N POSITIVA ====================\n  // Si pas\u00f3 los filtros negativos, verificar que tenga datos de profesional\n\n  const validSources = [\n    \"linkedin.com\", // LinkedIn profiles\n    \"superprof.cl\", // Directorio profesionales\n    \"doctoralia.cl\", // M\u00e9dicos\n    \"topdoctors.es\", // M\u00e9dicos\n    \".cl\", // Sitios chilenos generales\n  ];\n\n  let isValidSource = false;\n  for (const source of validSources) {\n    if (link.includes(source)) {\n      isValidSource = true;\n      break;\n    }\n  }\n\n  // Si no es marketplace/bolsa/escuela Y es fuente v\u00e1lida\n  shouldIgnore = shouldIgnore || !isValidSource;\n\n  // ==================== RESULTADO ====================\n\n      return {\n        json: {\n          ...item,\n          classification: {\n            should_process: !shouldIgnore,\n            ignore_reason: ignoreReason,\n            is_linkedin: link.includes(\"linkedin.com\"),\n            source_type: ignoreReason || \"professional\",\n          },\n        },\n      };\n    } catch (itemErr) {\n      return {\n        json: {\n          ...item,\n          classification: {\n            should_process: false,\n            ignore_reason: \"error\",\n            error: String(itemErr.message || itemErr),\n          },\n        },\n      };\n    }\n  });\n\n} catch (err) {\n  // Error general\n  let errorMsg = \"Unknown error\";\n  if (typeof err === 'string') {\n    errorMsg = err;\n  } else if (err && typeof err === 'object' && 'message' in err) {\n    errorMsg = String(err.message);\n  } else {\n    errorMsg = String(err);\n  }\n  \n  return [{ json: { \n    classification: {\n      should_process: false,\n      ignore_reason: \"error\",\n      error: errorMsg,\n    }\n  }}];\n}"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        5584,
        8432
      ],
      "id": "43286d55-6c54-4367-9c0e-dad43d509988",
      "name": "Link Classifier"
    },
    {
      "parameters": {
        "jsCode": "try {\n  // Procesar TODOS los items que vienen del IF node\n  const allItems = $input.all();\n  \n  return allItems.map(itemWrapper => {\n    const item = itemWrapper.json;\n    const metadata = item.metadata;\n    const title = metadata.title;\n    const snippet = metadata.snippet;\n    const link = metadata.link;\n\n  // ==================== EXTRACCI\u00d3N NOMBRE ====================\n  // Formato t\u00edpico LinkedIn: \"Nombre Apellido - Cargo, experiencia...\"\n  \n  let name = null;\n  const nameMatch = title.match(/^([A-Za-z\u00e1\u00e9\u00ed\u00f3\u00fa\u00c1\u00c9\u00cd\u00d3\u00da\u00f1\u00d1\\s]+?)(?:\\s*-|$)/);\n  if (nameMatch) {\n    name = nameMatch[1].trim();\n    // Limpiar si a\u00fan tiene ruido\n    if (name.length < 3 || name.toLowerCase().includes(\"error\")) {\n      name = null;\n    }\n  }\n\n  // ==================== EXTRACCI\u00d3N CARGO ====================\n  // Buscar despu\u00e9s del primer gui\u00f3n\n  let position = null;\n  const posMatch = title.match(/\\s*-\\s*([^,]+)/);\n  if (posMatch) {\n    position = posMatch[1].trim();\n    // Tomar solo la primera parte (antes de a\u00f1os de experiencia)\n    position = position.split(/[,\u00b7]/)[0].trim();\n    // Limitar a 50 caracteres\n    if (position.length > 50) {\n      position = position.split(/\\s+/).slice(0, 3).join(\" \");\n    }\n  }\n\n  // ==================== EMAIL & TEL\u00c9FONO ====================\n  const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\n  const emails = snippet.match(emailRegex) || [];\n  const email = emails.length > 0 ? emails[0] : null;\n\n  const phoneRegex = /(\\+?56|0)?[\\s]?9[\\s]?[\\d]{4}[\\s]?[\\d]{4}|(\\+?56|0)?[\\s]?2[\\s]?[\\d]{4}[\\s]?[\\d]{4}/g;\n  const phones = snippet.match(phoneRegex) || [];\n  const phone = phones.length > 0 ? phones[0] : null;\n\n  // ==================== VALIDACI\u00d3N ====================\n  // is_valid: true solo si tiene NOMBRE + (CARGO O EMAIL O TEL\u00c9FONO)\n  const isValid = !!(name && (position || email || phone));\n  \n  // validation_score ser\u00e1 calculado en el Backend seg\u00fan criterios finales\n  // Aqu\u00ed solo guardamos la informaci\u00f3n extra\u00edda\n    const validationScore = isValid ? 1 : 0; // Score temporal, Backend lo recalcula\n\n    return {\n      json: {\n        ...item,\n        processed: true,\n        contact_data: {\n          name: name || \"Desconocido\",\n          email: email,\n          phone: phone,\n          position: position,\n          organization: \"LinkedIn\",\n          region: item.region,\n          source_url: link,\n          source_type: \"LinkedIn\",\n          is_valid: isValid,\n          validation_score: validationScore,\n        },\n      },\n    };\n  });\n\n} catch (err) {\n  // Error general\n  let errorMsg = \"Unknown error\";\n  if (typeof err === 'string') {\n    errorMsg = err;\n  } else if (err && typeof err === 'object' && 'message' in err) {\n    errorMsg = String(err.message);\n  } else {\n    errorMsg = String(err);\n  }\n  \n  return [{ json: { \n    contact_data: {\n      is_valid: false,\n      validation_score: 0,\n      scraping_source: \"error\",\n      scraping_error: errorMsg,\n    }\n  }}];\n}"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6928,
        8336
      ],
      "id": "ccd244f5-26bf-4fe7-88a7-e3ba4d9e2fb5",
      "name": "Code in JavaScript6"
    },
    {
      "parameters": {
        "url": "={{ $json.metadata.link }}",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
            },
            {
              "name": "Accept",
              "value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
            },
            {
              "name": "Accept-Language",
              "value": "es-ES,es;q=0.9"
            },
            {
              "name": "Referer",
              "value": "https://www.google.com/"
            }
          ]
        },
        "options": {
          "allowUnauthorizedCerts": false,
          "response": {
            "response": {
              "responseFormat": "text"
            }
          },
          "timeout": 10000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        6256,
        8624
      ],
      "id": "564b16c8-fabb-4764-9741-96ce032e2a09",
      "name": "HTTP Request3",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "// Combina respuesta HTTP con datos originales\n// Mantiene estructura completa aunque HTTP falle\n// Detecta correctamente cuando HTTP devuelve error\n\ntry {\n  const allItems = $input.all();\n  \n  return allItems.map(itemWrapper => {\n    const item = itemWrapper.json;\n    \n    // Detectar si fue error (tiene status error) o \u00e9xito\n    let htmlContent = null;\n    let statusCode = null;\n    let fetchError = null;\n\n    // ==================== DETECTAR ESTRUCTURA DE RESPUESTA ====================\n    \n    // Si viene con error (HTTP Request con Continue on Error)\n    if (item.error) {\n      statusCode = item.error.status || item.error.statusCode || 403;\n      fetchError = item.error.message || \"HTTP Error\";\n      htmlContent = null;\n    }\n    // Si viene con statusCode y body\n    else if (item.statusCode === 200 && item.body) {\n      htmlContent = item.body;\n      statusCode = 200;\n      fetchError = null;\n    }\n    // Si viene con error status\n    else if (item.statusCode === 403) {\n      statusCode = 403;\n      fetchError = \"CAPTCHA/Blocked (403)\";\n      htmlContent = null;\n    }\n    else if (item.statusCode >= 500) {\n      statusCode = item.statusCode;\n      fetchError = `Server error (${statusCode})`;\n      htmlContent = null;\n    }\n    else if (item.statusCode) {\n      statusCode = item.statusCode;\n      fetchError = `HTTP ${statusCode}`;\n      htmlContent = null;\n    }\n    else {\n      fetchError = \"Unknown error\";\n      statusCode = null;\n    }\n\n    // ==================== CONSTRUIR RESPUESTA LIMPIA ====================\n    // Preservar TODO del item original EXCEPTO el campo \"error\"\n    // (destructuring elimina error, spread incluye todo lo dem\u00e1s)\n    const { error, ...cleanItem } = item;\n\n    return {\n      json: {\n        ...cleanItem,\n        html_fetch: {\n          success: htmlContent ? true : false,\n          status_code: statusCode,\n          error: fetchError,\n          html_content: htmlContent,\n        },\n      },\n    };\n  });\n\n} catch (err) {\n  let errorMsg = \"Unknown error\";\n  if (typeof err === 'string') {\n    errorMsg = err;\n  } else if (err && typeof err === 'object' && 'message' in err) {\n    errorMsg = String(err.message);\n  } else {\n    errorMsg = String(err);\n  }\n\n  return [{ json: {\n    html_fetch: {\n      success: false,\n      error: errorMsg,\n      html_content: null,\n      status_code: null,\n    }\n  }}];\n}"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6704,
        8528
      ],
      "id": "7f605f0f-8d2b-4123-b968-ec42ed9e2a91",
      "name": "Combine Data"
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        6480,
        8528
      ],
      "id": "69ecf90d-92c0-4425-b53f-e407e79e407e",
      "name": "Prepare Data"
    },
    {
      "parameters": {
        "jsCode": "try {\n  const allItems = $input.all();\n  \n  return allItems.map(itemWrapper => {\n    const item = itemWrapper.json;\n    const metadata = item.metadata;\n    \n    // Obtener HTML de la estructura de 3B-1B\n    let htmlContent = null;\n    let source = \"snippet\";\n    let extractionNotes = \"\";\n\n    // Verificar estructura html_fetch limpia de 3B-1B\n    if (item.html_fetch) {\n      if (item.html_fetch.success && item.html_fetch.html_content) {\n        htmlContent = item.html_fetch.html_content;\n        source = \"html\";\n        extractionNotes = \"HTML obtenido exitosamente\";\n      } else if (item.html_fetch.error) {\n        extractionNotes = `HTTP ${item.html_fetch.status_code}: ${item.html_fetch.error} - usando snippet`;\n      } else {\n        extractionNotes = \"Ning\u00fan HTML disponible - usando snippet\";\n      }\n    } else {\n      extractionNotes = \"Sin html_fetch - usando snippet\";\n    }\n\n    // Usar HTML si lo tenemos, sino snippet\n    const textToSearch = htmlContent || metadata.snippet || \"\";\n\n    const genericNameTerms = new Set([\n      \"contacto\", \"inicio\", \"nombre empresa\", \"empresa\", \"servicios\", \"servicio\",\n      \"masajes\", \"masajes providencia\", \"masajes corporales\", \"home\", \"about\"\n    ]);\n\n    const toTitleCase = (value) => value\n      .split(\" \")\n      .filter(Boolean)\n      .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n      .join(\" \" );\n\n    const cleanCandidate = (value) => (value || \"\")\n      .replace(/\\s*[-|\u2022]\\s*.*/, \"\")\n      .replace(/[\\d()]/g, \"\")\n      .replace(/\\s+/g, \" \")\n      .trim();\n\n    const isGenericCandidate = (value) => {\n      const normalized = (value || \"\").toLowerCase().trim();\n      if (!normalized || normalized.length < 3) return true;\n      if (genericNameTerms.has(normalized)) return true;\n      if (/^(contacto|inicio|servicios?|home|masajes?)$/.test(normalized)) return true;\n      return false;\n    };\n\n    const domainToName = (domainValue) => toTitleCase(\n      (domainValue || \"\")\n        .replace(/^https?:\\/\\//i, \"\")\n        .replace(/^www\\./i, \"\")\n        .split(\"/\")[0]\n        .replace(/\\.(cl|com|net|org|io|app|co|es)$/i, \"\")\n        .replace(/[-_]+/g, \" \")\n        .trim()\n    );\n\n    const normalizeOrganization = (sourceValue, linkValue, titleValue) => {\n      let org = (sourceValue || \"\").replace(/^LinkedIn\\s*\u00b7\\s*/i, \"\").trim();\n\n      if (!org || org.includes(\".\")) {\n        try {\n          const host = new URL(linkValue || \"\").hostname;\n          if (host) {\n            org = domainToName(host);\n          }\n        } catch (_) {}\n      } else {\n        org = cleanCandidate(org);\n      }\n\n      if ((!org || isGenericCandidate(org)) && titleValue) {\n        const titleOrg = cleanCandidate(titleValue);\n        if (!isGenericCandidate(titleOrg)) {\n          org = toTitleCase(titleOrg);\n        }\n      }\n\n      return org && !isGenericCandidate(org) ? org : null;\n    };\n\n    // ==================== NOMBRE ====================\n    let name = cleanCandidate(metadata.title);\n    const personMatch = (metadata.title || \"\").match(/\\b([A-Z\u00c1\u00c9\u00cd\u00d3\u00da\u00d1][a-z\u00e1\u00e9\u00ed\u00f3\u00fa\u00f1]+(?:\\s+[A-Z\u00c1\u00c9\u00cd\u00d3\u00da\u00d1][a-z\u00e1\u00e9\u00ed\u00f3\u00fa\u00f1]+){1,3})\\b/);\n    if (personMatch) {\n      name = personMatch[1].trim();\n    }\n    if (isGenericCandidate(name)) {\n      name = null;\n    }\n\n    // ==================== CARGO ====================\n    let position = null;\n    const positionKeywords = [\n      \"chef\", \"cocinero\", \"pastelero\", \"panadero\", \"sommelier\",\n      \"profesor\", \"instructor\", \"tutor\", \"capacitador\", \"consultor\",\n      \"m\u00e9dico\", \"doctor\", \"ingeniero\", \"arquitecto\", \"abogado\", \"contador\",\n      \"electricista\", \"plomero\", \"carpintero\", \"pintor\", \"mec\u00e1nico\",\n      \"gerente\", \"director\", \"administrador\", \"coordinador\", \"supervisor\",\n      \"dise\u00f1ador\", \"desarrollador\", \"programador\", \"analista\", \"t\u00e9cnico\",\n    ];\n\n    for (const keyword of positionKeywords) {\n      const regex = new RegExp(`\\\\b${keyword}\\\\b`, \"i\");\n      if (regex.test(textToSearch)) {\n        position = keyword.charAt(0).toUpperCase() + keyword.slice(1);\n        break;\n      }\n    }\n\n    // ==================== EMAIL ====================\n    const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\n    const emails = textToSearch.match(emailRegex) || [];\n    const email = emails.length > 0 ? emails[0] : null;\n\n    // ==================== TEL\u00c9FONO ====================\n    const phoneRegex = /(\\+?56|0)?[\\s]?9[\\s]?[\\d]{4}[\\s]?[\\d]{4}|(\\+?56|0)?[\\s]?2[\\s]?[\\d]{4}[\\s]?[\\d]{4}/g;\n    const phones = textToSearch.match(phoneRegex) || [];\n    let phone = phones.length > 0 ? phones[0] : null;\n\n    if (phone) {\n      phone = phone.replace(/[\\s\\-().]/g, \"\");\n      if (!phone.startsWith(\"+56\")) {\n        if (phone.startsWith(\"0\")) {\n          phone = \"+56\" + phone.substring(1);\n        } else if (!phone.startsWith(\"56\")) {\n          phone = \"+56\" + phone;\n        } else {\n          phone = \"+\" + phone;\n        }\n      }\n    }\n\n    // ==================== ORGANIZACI\u00d3N ====================\n    let organization = normalizeOrganization(metadata.source, metadata.link, metadata.title);\n\n    if (!name && organization) {\n      name = organization;\n    }\n\n    // ==================== VALIDACI\u00d3N ====================\n    // Para Superprof: requiere NOMBRE + (EMAIL O TEL\u00c9FONO)\n    // Si no hay email/tel\u00e9fono en snippet \u2192 is_valid = false\n    const isValid = !!(name && (email || phone));\n    const validationScore = isValid ? 1 : 0;\n\n    return {\n      json: {\n        ...item,\n        processed: true,\n        contact_data: {\n          name: name || \"Desconocido\",\n          email: email,\n          phone: phone,\n          position: position,\n          organization: organization,\n          region: item.region,\n          source_url: metadata.link,\n          source_type: metadata.source,\n          is_valid: isValid,\n          validation_score: validationScore,\n        },\n        extraction_info: {\n          source_used: source,\n          notes: extractionNotes,\n          http_status: item.html_fetch?.status_code || null,\n        },\n      },\n    };\n  });\n\n} catch (err) {\n  let errorMsg = \"Unknown error\";\n  if (typeof err === 'string') {\n    errorMsg = err;\n  } else if (err && typeof err === 'object' && 'message' in err) {\n    errorMsg = String(err.message);\n  } else {\n    errorMsg = String(err);\n  }\n  \n  return [{ json: { \n    contact_data: {\n      is_valid: false,\n      validation_score: 0,\n      error: errorMsg,\n    }\n  }}];\n}"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6928,
        8528
      ],
      "id": "03ee11a1-43c2-433e-88e8-d3fa8445c2d4",
      "name": "Code in JavaScript8"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 3
          },
          "conditions": [
            {
              "id": "2cd8af3f-f9dd-4dfb-971d-dea61bd52d7a",
              "leftValue": "={{ !!$json.contact_data }}",
              "rightValue": "true",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        7824,
        8384
      ],
      "id": "9632a364-8bc5-433c-a727-959dd8f4ee66",
      "name": "Valid?"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        7152,
        8384
      ],
      "id": "b7e78d2c-ff2b-42e9-8f30-1716ce3742c7",
      "name": "Merge Branches"
    },
    {
      "parameters": {
        "jsCode": "const genericTerms = new Set(['contacto','inicio','empresa','servicio','servicios','home','about','nosotros','quienes somos','sitio oficial']);\nconst companyTerms = new Set(['spa','ltda','limitada','sa','s.a','eirl','sociedad','empresa','group','grupo','holding','consultora','consultores','clinica','cl\u00ednica']);\nconst connectorWords = new Set(['de','del','la','las','los','y']);\n\nconst clean = (value) => (value || '').toString().replace(/\\s+/g, ' ').replace(/\\s*[-|\u2022]\\s*.*/, '').trim();\nconst titleCase = (value) => clean(value).split(' ').filter(Boolean).map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');\nconst isGeneric = (value) => {\n  const v = clean(value).toLowerCase();\n  return !v || v.length < 3 || genericTerms.has(v) || /^(contacto|inicio|servicios?|home|masajes?)$/.test(v);\n};\nconst fromUrl = (rawUrl) => {\n  try {\n    const host = new URL(rawUrl || '').hostname.replace(/^www\\./i, '');\n    return titleCase(host.replace(/\\.(cl|com|net|org|io|app|co|es)$/i, '').replace(/[-_]+/g, ' '));\n  } catch (_) {\n    return null;\n  }\n};\nconst isLikelyPerson = (value) => {\n  const candidate = clean(value);\n  if (isGeneric(candidate) || /\\d/.test(candidate)) return false;\n  const words = candidate.split(/\\s+/).filter(Boolean);\n  const core = words.filter(w => !connectorWords.has(w.toLowerCase()));\n  if (core.length < 2 || core.length > 4) return false;\n  return core.every(w => /^[A-Z\u00c1\u00c9\u00cd\u00d3\u00da\u00d1][a-z\u00e1\u00e9\u00ed\u00f3\u00fa\u00f1'\u2019-]+$/.test(w) && !companyTerms.has(w.toLowerCase().replace(/\\./g, '')));\n};\n\nreturn $input.all().map((itemWrapper) => {\n  const item = itemWrapper.json;\n  const contact = item.contact_data || {};\n  const title = clean(item.metadata?.title || item.title || '');\n  const source = clean(item.metadata?.source || contact.source_type || '');\n  const url = contact.source_url || item.metadata?.link || '';\n\n  let name = clean(contact.name);\n  let organization = clean(contact.organization);\n\n  if (!isLikelyPerson(name)) {\n    const match = title.match(/\\b([A-Z\u00c1\u00c9\u00cd\u00d3\u00da\u00d1][a-z\u00e1\u00e9\u00ed\u00f3\u00fa\u00f1]+(?:\\s+[A-Z\u00c1\u00c9\u00cd\u00d3\u00da\u00d1][a-z\u00e1\u00e9\u00ed\u00f3\u00fa\u00f1]+){1,3})\\b/);\n    const titlePerson = match ? clean(match[1]) : '';\n    name = isLikelyPerson(titlePerson) ? titlePerson : null;\n  }\n\n  if (!organization || isGeneric(organization) || isLikelyPerson(organization) || organization.includes('.')) {\n    if (source && !isGeneric(source) && !isLikelyPerson(source) && !source.includes('.')) {\n      organization = titleCase(source);\n    }\n  }\n\n  if (!organization || isGeneric(organization) || isLikelyPerson(organization)) {\n    const domainOrg = fromUrl(url);\n    if (domainOrg && !isGeneric(domainOrg) && !isLikelyPerson(domainOrg)) {\n      organization = domainOrg;\n    }\n  }\n\n  if (!name && organization) {\n    name = organization;\n  }\n\n  let score = Number(contact.validation_score);\n  const isValid = !!contact.is_valid;\n  if (!Number.isFinite(score)) score = isValid ? 1 : 0.3;\n  if (!isValid && score < 0.3) score = 0.3;\n\n  contact.name = name || contact.name || organization || 'Desconocido';\n  contact.organization = organization || null;\n  contact.is_valid = isValid;\n  contact.validation_score = score;\n\n  item.contact_data = contact;\n  return { json: item };\n});"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        7376,
        8384
      ],
      "id": "4de8e1e3-4938-4461-a02e-6d535a9cbd94",
      "name": "Contact Final Validation"
    },
    {
      "parameters": {
        "jsCode": "const regionRules = [\n  { region: 'Arica y Parinacota', keys: ['arica y parinacota','arica','parinacota'] },\n  { region: 'Tarapac\u00e1', keys: ['tarapac\u00e1','tarapaca','iquique'] },\n  { region: 'Antofagasta', keys: ['antofagasta'] },\n  { region: 'Atacama', keys: ['atacama','copiap\u00f3','copiapo'] },\n  { region: 'Coquimbo', keys: ['coquimbo','la serena'] },\n  { region: 'Valpara\u00edso', keys: ['valpara\u00edso','valparaiso','vi\u00f1a del mar','vina del mar','quilpu\u00e9','quilpue','conc\u00f3n','concon'] },\n  { region: 'Santiago', keys: ['santiago','metropolitana','rm','providencia','las condes','\u00f1u\u00f1oa','nunoa'] },\n  { region: \"O'Higgins\", keys: [\"o'higgins\",'ohiggins','rancagua'] },\n  { region: 'Maule', keys: ['maule','talca'] },\n  { region: '\u00d1uble', keys: ['\u00f1uble','nuble','chill\u00e1n','chillan'] },\n  { region: 'Biob\u00edo', keys: ['biob\u00edo','biobio','b\u00edo b\u00edo','bio bio','concepci\u00f3n','concepcion'] },\n  { region: 'La Araucan\u00eda', keys: ['la araucan\u00eda','la araucania','araucan\u00eda','araucania','temuco'] },\n  { region: 'Los R\u00edos', keys: ['los r\u00edos','los rios','valdivia'] },\n  { region: 'Los Lagos', keys: ['los lagos','puerto montt'] },\n  { region: 'Ays\u00e9n', keys: ['ays\u00e9n','aysen','coihaique','coyhaique'] },\n  { region: 'Magallanes', keys: ['magallanes','punta arenas'] }\n];\n\nreturn $input.all().map((itemWrapper) => {\n  const item = itemWrapper.json;\n  const contact = item.contact_data || {};\n  const text = [\n    contact.source_url || '',\n    contact.organization || '',\n    item.metadata?.title || '',\n    item.metadata?.snippet || '',\n    item.snippet || ''\n  ].join(' ').toLowerCase();\n\n  let inferred = null;\n  for (const rule of regionRules) {\n    if (rule.keys.some(key => text.includes(key))) {\n      inferred = rule.region;\n      break;\n    }\n  }\n\n  if (inferred) {\n    contact.region = inferred;\n    item.region = inferred;\n  }\n\n  item.contact_data = contact;\n  return { json: item };\n});"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        7600,
        8384
      ],
      "id": "e0052a9f-9eda-4a5f-a215-1d4e4da592f1",
      "name": "Region Correction"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=http://backend:8081/api/v1/searches/{{ Number($(\"Webhook\").first().json.body.search_id) }}/callback",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-N8N-API-KEY",
              "value": "=Unab.2026"
            }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "search_id",
              "value": "={{$json.search_id}}"
            },
            {
              "name": "status",
              "value": "={{$json.status}}"
            },
            {
              "name": "results_count",
              "value": "={{$json.results_count}}"
            },
            {
              "name": "duplicates_count",
              "value": "={{$json.duplicates_count}}"
            },
            {
              "name": "execution_time_ms",
              "value": "={{$json.execution_time_ms}}"
            },
            {
              "name": "avg_validation_score",
              "value": "={{$json.avg_validation_score}}"
            },
            {
              "name": "stats",
              "value": "={{$json.stats}}"
            },
            {
              "name": "failed_searches",
              "value": "={{$json.failed_searches}}"
            },
            {
              "name": "high_quality_contacts",
              "value": "={{$json.high_quality_contacts}}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        8496,
        8384
      ],
      "id": "f8e58d8f-3c14-4d37-aa5d-8718e8a78604",
      "name": "Notify Backend"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SET @sid = CAST('{{ \n  $items(\"Webhook\")[0].json.search_id \n  || $items(\"Webhook\")[0].json.body?.search_id \n  || 0 \n}}' AS UNSIGNED);\n\nINSERT INTO contacts\n(name, email, phone, position, organization, region, source_url, source_type, research_lines, is_valid, validation_score)\nVALUES\n(\n  '{{$json.contact_data.name.replace(/'/g, \"''\")}}',\n  {{$json.contact_data.email ? `'${$json.contact_data.email.replace(/'/g, \"''\")}'` : 'NULL'}},\n  {{$json.contact_data.phone ? `'${$json.contact_data.phone.replace(/'/g, \"''\")}'` : 'NULL'}},\n  {{$json.contact_data.position ? `'${$json.contact_data.position.replace(/'/g, \"''\")}'` : 'NULL'}},\n  {{$json.contact_data.organization ? `'${$json.contact_data.organization.replace(/'/g, \"''\")}'` : 'NULL'}},\n  {{$json.contact_data.region ? `'${$json.contact_data.region.replace(/'/g, \"''\")}'` : 'NULL'}},\n  '{{$json.contact_data.source_url.replace(/'/g, \"''\")}}',\n  {{$json.contact_data.source_type ? `'${$json.contact_data.source_type.replace(/'/g, \"''\")}'` : 'NULL'}},\n  NULL,\n  1,\n  {{ Number($json.contact_data.validation_score || 1) }}\n)\nON DUPLICATE KEY UPDATE\n  id = LAST_INSERT_ID(id),\n  updated_at = CURRENT_TIMESTAMP;\n\nSET @cid = LAST_INSERT_ID();\n\nINSERT INTO search_results (search_id, contact_id, relevance_score)\nSELECT\n  @sid,\n  @cid,\n  COALESCE((SELECT validation_score FROM contacts WHERE id = @cid), 1)\nWHERE @sid > 0\nON DUPLICATE KEY UPDATE\n  relevance_score = VALUES(relevance_score);\n\nSELECT @sid AS search_id, @cid AS contact_id;",
        "options": {}
      },
      "type": "n8n-nodes-base.mySql",
      "typeVersion": 2.5,
      "position": [
        8048,
        8384
      ],
      "id": "fe5726ea-8a31-4e10-979a-606d1e84235c",
      "name": "Insert Valid Contacts",
      "credentials": {
        "mySql": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const webhookData = $(\"Webhook\").first().json;\nconst inserted = $(\"Insert Valid Contacts\").all().length;\n\n// En Webhook de n8n normalmente viene en body\nconst rawSearchId = webhookData?.body?.search_id ?? webhookData?.search_id;\n\nconst searchId = Number(rawSearchId);\nif (!Number.isInteger(searchId) || searchId <= 0) {\n  throw new Error(`search_id inv\u00e1lido. Valor recibido: ${JSON.stringify(rawSearchId)}`);\n}\n\nreturn [{\n  json: {\n    search_id: searchId,\n    status: \"completed\",\n    results_count: inserted,\n    duplicates_count: 0,\n    execution_time_ms: 0,\n    avg_validation_score: 1,\n    stats: {},\n    failed_searches: 0,\n    high_quality_contacts: inserted\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        8272,
        8384
      ],
      "id": "061530b4-cf1d-4db0-a195-43285ad7f416",
      "name": "Build Callback"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 3
          },
          "conditions": [
            {
              "id": "f44c22a0-7f19-420e-a27b-3ebf11cc17e3",
              "leftValue": "={{ $json.classification.should_process }}",
              "rightValue": "true",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        5808,
        8432
      ],
      "id": "1b15e3f3-014e-4aae-a4cd-1a76bb9770ae",
      "name": "Data Valid?"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 3
          },
          "conditions": [
            {
              "id": "523301ab-f2f3-4d0b-9cdc-0528dd2b1d6d",
              "leftValue": "={{ $json.classification.is_linkedin }}",
              "rightValue": "true",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        6032,
        8432
      ],
      "id": "ac59c7a5-7502-400c-a0d1-3c115e270b07",
      "name": "Linkedin?"
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Data Structure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Search Query": {
      "main": [
        [
          {
            "node": "Google Custom Search API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Custom Search API": {
      "main": [
        [
          {
            "node": "Parser1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Data is Valid": {
      "main": [
        [
          {
            "node": "Build Search Query",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Stop and Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Structure": {
      "main": [
        [
          {
            "node": "IF Data is Valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parser1": {
      "main": [
        [
          {
            "node": "Link Classifier",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Link Classifier": {
      "main": [
        [
          {
            "node": "Data Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request3": {
      "main": [
        [
          {
            "node": "Prepare Data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Prepare Data": {
      "main": [
        [
          {
            "node": "Combine Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Data": {
      "main": [
        [
          {
            "node": "Code in JavaScript8",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript8": {
      "main": [
        [
          {
            "node": "Merge Branches",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Code in JavaScript6": {
      "main": [
        [
          {
            "node": "Merge Branches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Valid?": {
      "main": [
        [
          {
            "node": "Insert Valid Contacts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Branches": {
      "main": [
        [
          {
            "node": "Contact Final Validation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Contact Final Validation": {
      "main": [
        [
          {
            "node": "Region Correction",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Region Correction": {
      "main": [
        [
          {
            "node": "Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Valid Contacts": {
      "main": [
        [
          {
            "node": "Build Callback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Callback": {
      "main": [
        [
          {
            "node": "Notify Backend",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Valid?": {
      "main": [
        [
          {
            "node": "Linkedin?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Linkedin?": {
      "main": [
        [
          {
            "node": "Code in JavaScript6",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "HTTP Request3",
            "type": "main",
            "index": 0
          },
          {
            "node": "Prepare Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "availableInMCP": false
  },
  "versionId": "56043686-1c91-4f2c-bfe6-71dfe9240ca5",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "Mng5-VRYT5d6W3iWV0o6H",
  "tags": []
}