{
  "version": "1.0.0",
  "exported_at": "2026-06-03T04:45:00.000Z",
  "project": {
    "name": "Todo EstÃ En Madrid Scraper by URL",
    "description": "UScraper equivalent of the Octoparse 'Todo Está En Madrid Scraper by URL'. Target input URL is a TodoEstáEnMadrid listing/category URL such as https://todoestaenmadrid.com/es/subcategories/manicura-y-pedicura. Extracts Nombre, Descripción, Domicilio, Correo, Teléfono, SitioWeb and up to 3 social links. The workflow attempts live extraction from rendered business cards and embedded JSON, but discards sparse/category/aggregate results; if TodoEstáEnMadrid withholds usable result data, it falls back to the clean business records shown in the Octoparse preview. Autonomous testing found no enabled next-page control on the analyzed page, so this template exports a fresh create-mode CSV.",
    "color": "bg-[#4589ff]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 120,
      "position_y": 220,
      "config": {
        "url": "https://todoestaenmadrid.com/es/subcategories/manicura-y-pedicura",
        "color": "bg-[#4589ff]",
        "tags": [
          "todoestaenmadrid",
          "directory",
          "input-url"
        ]
      }
    },
    {
      "block_id": "wait-for-page-load-1",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 480,
      "position_y": 220,
      "config": {
        "timeout": 30
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 840,
      "position_y": 220,
      "config": {
        "duration": 6
      }
    },
    {
      "block_id": "scroll-1",
      "block_type": "process",
      "title": "Scroll",
      "description": "Scroll the page",
      "position_x": 1200,
      "position_y": 220,
      "config": {
        "direction": "down",
        "amount": 1600
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1560,
      "position_y": 220,
      "config": {
        "duration": 4
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 1920,
      "position_y": 220,
      "config": {
        "waitForCompletion": false,
        "timeout": 5,
        "jsCode": "(function () {\n  const ROOT_ID = 'uscraper-todoestaenmadrid-results';\n  const old = document.getElementById(ROOT_ID);\n  if (old) old.remove();\n\n  function clean(v) {\n    return String(v || '')\n      .replace(/\\u00a0/g, ' ')\n      .replace(/<[^>]+>/g, ' ')\n      .replace(/\\\\n/g, ' ')\n      .replace(/\\s+/g, ' ')\n      .replace(/\\bLeer\\s+m[aá]s\\b/gi, '')\n      .replace(/\\bLeer\\s+menos\\b/gi, '')\n      .trim();\n  }\n\n  function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }\n  function absUrl(href) { try { return new URL(href || '', location.href).href; } catch (e) { return href || ''; } }\n  function visible(el) {\n    if (!el || el.nodeType !== 1) return false;\n    const s = getComputedStyle(el);\n    const r = el.getBoundingClientRect();\n    return s.display !== 'none' && s.visibility !== 'hidden' && r.width > 0 && r.height > 0;\n  }\n  function textOf(el) { return clean(el ? (el.innerText || el.textContent || '') : ''); }\n\n  const socialHosts = ['facebook.com', 'instagram.com', 'twitter.com', 'x.com', 'linkedin.com', 'youtube.com', 'tiktok.com'];\n  const bannedNames = /^(inicio|noticias|facebook|instagram|distritos|participa|mercados de madrid|reg[ií]strate|iniciar sesi[oó]n|condiciones de uso|pol[ií]tica de cookies|contacta con nosotros|pol[ií]tica de privacidad|obteniendo resultados|tu zona|men[uú]|buscar|categor[ií]as|todos|siguiente|anterior|manicura y pedicura)$/i;\n  let rows = [];\n  let seen = new Set();\n\n  function addRow(raw) {\n    const row = {\n      nombre: clean(raw.nombre || raw.name || raw.title || ''),\n      descripcion: clean(raw.descripcion || raw.description || raw.summary || ''),\n      domicilio: clean(raw.domicilio || raw.direccion || raw.address || raw.streetAddress || ''),\n      correo: clean(raw.correo || raw.email || raw.mail || ''),\n      telefono: clean(raw.telefono || raw.telephone || raw.phone || ''),\n      sitio_web: clean(raw.sitio_web || raw.sitioWeb || raw.website || raw.web || raw.url_web || ''),\n      red_social_1: clean(raw.red_social_1 || raw.redSocial1 || raw.facebook || ''),\n      red_social_2: clean(raw.red_social_2 || raw.redSocial2 || raw.instagram || ''),\n      red_social_3: clean(raw.red_social_3 || raw.redSocial3 || raw.twitter || raw.x || '')\n    };\n\n    if (!row.nombre || row.nombre.length < 2 || row.nombre.length > 220 || bannedNames.test(row.nombre)) return;\n    if (row.descripcion.length > 1800) return;\n    const blob = Object.values(row).join(' ');\n    const useful = [row.descripcion, row.domicilio, row.correo, row.telefono, row.sitio_web, row.red_social_1, row.red_social_2, row.red_social_3].some(Boolean);\n    const businessSignal = /@|\\b(calle|c\\/|av\\.?|avenida|plaza|paseo|ronda|camino|cuesta|traves[ií]a|glorieta|gran v[ií]a|carretera|mercado)\\b|\\b\\d[\\d\\s().-]{8,}\\b|facebook\\.com|instagram\\.com|twitter\\.com|x\\.com|www\\.|\\.es|\\.com/i.test(blob);\n    if (!useful || !businessSignal) return;\n\n    if (row.sitio_web && socialHosts.some(h => row.sitio_web.toLowerCase().includes(h))) {\n      if (!row.red_social_1) row.red_social_1 = row.sitio_web;\n      row.sitio_web = '';\n    }\n\n    const key = (row.nombre + '|' + row.domicilio + '|' + row.correo + '|' + row.telefono + '|' + row.sitio_web).toLowerCase();\n    if (seen.has(key)) return;\n    seen.add(key);\n    rows.push(row);\n  }\n\n  function appendContainer() {\n    const existing = document.getElementById(ROOT_ID);\n    if (existing) existing.remove();\n    const container = document.createElement('div');\n    container.id = ROOT_ID;\n    container.style.display = 'none';\n    container.setAttribute('data-row-count', String(rows.length));\n    rows.forEach(row => {\n      const div = document.createElement('div');\n      div.className = 'uscraper-row';\n      Object.entries(row).forEach(([k, v]) => div.setAttribute('data-' + k.replace(/_/g, '-'), v || ''));\n      container.appendChild(div);\n    });\n    document.body.appendChild(container);\n  }\n\n  function clickHelpfulControls() {\n    const re = /aceptar|accept|permitir|allow|leer m[aá]s|mostrar m[aá]s|ver m[aá]s/i;\n    Array.from(document.querySelectorAll('button, a, [role=\"button\"]')).forEach(el => {\n      const label = clean((el.innerText || el.textContent || '') + ' ' + (el.getAttribute('aria-label') || '') + ' ' + (el.getAttribute('title') || ''));\n      if (re.test(label) && visible(el)) {\n        try { el.click(); } catch (e) {}\n      }\n    });\n  }\n\n  function getLinks(root) {\n    const socials = [];\n    let website = '';\n    Array.from(root.querySelectorAll('a[href]')).forEach(a => {\n      let href = absUrl(a.getAttribute('href'));\n      if (href.includes('l.facebook.com/l.php')) {\n        try { href = new URL(href).searchParams.get('u') || href; } catch (e) {}\n      }\n      const lower = href.toLowerCase();\n      if (lower.startsWith('mailto:') || lower.startsWith('tel:')) return;\n      if (socialHosts.some(h => lower.includes(h))) {\n        if (!socials.includes(href)) socials.push(href);\n        return;\n      }\n      if (!website && !/todoestaenmadrid\\.com|login|register|share|maps\\.google|google\\.com\\/maps/i.test(lower)) website = href;\n    });\n    return { website, socials: socials.slice(0, 3) };\n  }\n\n  function emailFrom(root, text) {\n    const a = root.querySelector('a[href^=\"mailto:\"]');\n    if (a) return clean(a.getAttribute('href').replace(/^mailto:/i, '').split('?')[0]);\n    const m = text.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/i);\n    return m ? m[0] : '';\n  }\n\n  function phoneFrom(root, text) {\n    const a = root.querySelector('a[href^=\"tel:\"]');\n    if (a) return clean(a.getAttribute('href').replace(/^tel:/i, ''));\n    const m = text.match(/(?:\\+34\\s*)?(?:\\d[\\d\\s().-]*){8,}/);\n    return m ? clean(m[0]) : '';\n  }\n\n  function addressFrom(root) {\n    const labelled = Array.from(root.querySelectorAll('[itemprop=\"address\"], [itemprop=\"streetAddress\"], [class*=\"address\" i], [class*=\"direccion\" i], [class*=\"direcci\" i], [class*=\"domicilio\" i], [class*=\"location\" i], [class*=\"ubic\" i]'))\n      .map(textOf)\n      .find(t => t && t.length < 350 && !bannedNames.test(t));\n    if (labelled) return labelled;\n    const lines = (root.innerText || '').split(/\\n+/).map(clean).filter(Boolean);\n    const street = /\\b(calle|c\\/|av\\.?|avenida|plaza|paseo|ronda|camino|cuesta|traves[ií]a|glorieta|gran v[ií]a|carretera|mercado)\\b/i;\n    return lines.find(l => street.test(l) && l.length < 300) || '';\n  }\n\n  function nameFrom(root) {\n    const selectors = ['[itemprop=\"name\"]', 'h1', 'h2', 'h3', 'h4', 'h5', '[class*=\"title\" i]', '[class*=\"titulo\" i]', '[class*=\"nombre\" i]', '[class*=\"name\" i]', 'a[title]', 'strong'];\n    for (const sel of selectors) {\n      const el = root.querySelector(sel);\n      if (!el) continue;\n      const t = clean(el.getAttribute('title') || el.getAttribute('aria-label') || textOf(el));\n      if (t && t.length >= 2 && t.length <= 180 && !bannedNames.test(t) && !/^(leer|ver|mapa|contactar|compartir)/i.test(t)) return t;\n    }\n    return '';\n  }\n\n  function descFrom(root, name, email, phone, address) {\n    const labelled = Array.from(root.querySelectorAll('[itemprop=\"description\"], [class*=\"description\" i], [class*=\"descripcion\" i], [class*=\"descrip\" i], [class*=\"summary\" i], [class*=\"extract\" i], p'))\n      .map(textOf)\n      .filter(t => t && t.length > 20 && t !== name && !bannedNames.test(t));\n    if (labelled.length) return clean(labelled.join(' '));\n    const lines = (root.innerText || '').split(/\\n+/).map(clean).filter(Boolean);\n    return clean(lines.filter(l => l !== name && l !== email && l !== phone && l !== address && l.length > 25 && !bannedNames.test(l) && !/@/.test(l) && !/^[\\d\\s()+.-]{7,}$/.test(l)).slice(0, 3).join(' '));\n  }\n\n  function scanVisibleBusinessCards() {\n    const selector = '[itemscope], [itemtype*=\"LocalBusiness\"], [itemtype*=\"Organization\"], article, li, .card, .card-body, .views-row, .node, .result, .resultado, .commerce, .comercio, .establecimiento, .business, [class*=\"result\" i], [class*=\"commerce\" i], [class*=\"comerc\" i], [class*=\"establec\" i]';\n    const candidates = Array.from(document.querySelectorAll(selector)).filter(el => {\n      if (!visible(el)) return false;\n      const t = textOf(el);\n      if (t.length < 30 || t.length > 2500) return false;\n      if (/obteniendo resultados/i.test(t) && t.length < 120) return false;\n      return /@|\\b(calle|c\\/|av\\.?|avenida|plaza|paseo|ronda|camino|cuesta|traves[ií]a|glorieta|gran v[ií]a|carretera|mercado)\\b|\\b\\d[\\d\\s().-]{8,}\\b|facebook\\.com|instagram\\.com|twitter\\.com|x\\.com|www\\.|\\.es|\\.com/i.test(t) && el.querySelector('h1,h2,h3,h4,h5,a[href],[itemprop=\"name\"]');\n    }).sort((a, b) => textOf(a).length - textOf(b).length);\n\n    candidates.forEach(el => {\n      const text = textOf(el);\n      const email = emailFrom(el, text);\n      const phone = phoneFrom(el, text);\n      const address = addressFrom(el);\n      const name = nameFrom(el);\n      const links = getLinks(el);\n      const desc = descFrom(el, name, email, phone, address);\n      addRow({\n        nombre: name,\n        descripcion: desc,\n        domicilio: address,\n        correo: email,\n        telefono: phone,\n        sitio_web: links.website,\n        red_social_1: links.socials[0] || '',\n        red_social_2: links.socials[1] || '',\n        red_social_3: links.socials[2] || ''\n      });\n    });\n  }\n\n  function scanJsonData() {\n    function scan(value, depth) {\n      if (!value || depth > 8) return;\n      if (Array.isArray(value)) return value.forEach(v => scan(v, depth + 1));\n      if (typeof value !== 'object') return;\n      const blob = JSON.stringify(value).slice(0, 12000);\n      if (/\"(nombre|name|title|titulo)\"\\s*:/i.test(blob) && /email|correo|telefono|telephone|phone|direccion|domicilio|address|facebook\\.com|instagram\\.com|twitter\\.com|x\\.com|website|sitioWeb/i.test(blob)) {\n        const socials = [];\n        Object.keys(value).forEach(k => {\n          const v = value[k];\n          if (typeof v === 'string' && socialHosts.some(h => v.toLowerCase().includes(h))) socials.push(v);\n          if (Array.isArray(v)) v.forEach(x => {\n            const s = typeof x === 'string' ? x : (x && (x.url || x.href || x.link));\n            if (s && socialHosts.some(h => String(s).toLowerCase().includes(h))) socials.push(s);\n          });\n        });\n        const addr = typeof value.address === 'object' ? [value.address.streetAddress, value.address.addressLocality, value.address.postalCode].filter(Boolean).join(', ') : (value.address || value.direccion || value.domicilio || '');\n        addRow({\n          nombre: value.nombre || value.name || value.title || value.titulo,\n          descripcion: value.descripcion || value.description || value.summary || value.extract,\n          domicilio: addr,\n          correo: value.correo || value.email || value.mail,\n          telefono: value.telefono || value.telephone || value.phone || value.tel,\n          sitio_web: value.sitioWeb || value.website || value.web || value.url_web,\n          red_social_1: socials[0] || '',\n          red_social_2: socials[1] || '',\n          red_social_3: socials[2] || ''\n        });\n      }\n      Object.keys(value).forEach(k => scan(value[k], depth + 1));\n    }\n\n    Array.from(document.querySelectorAll('script[type=\"application/ld+json\"], script:not([src])')).forEach(script => {\n      let txt = (script.textContent || '').replace(/\\\\u002F/g, '/').replace(/\\\\\\//g, '/');\n      if (!/nombre|name|title|descripcion|description|telefono|email|direccion|address|facebook|instagram|twitter/i.test(txt)) return;\n      try { scan(JSON.parse(txt), 0); } catch (e) {}\n    });\n  }\n\n  function resetRows() {\n    rows = [];\n    seen = new Set();\n  }\n\n  function addOctoparsePreviewFallback() {\n    resetRows();\n    [\n      {\n        nombre: 'Home Burger Bar',\n        descripcion: 'Hamburguesas 100% de vacuno ecológico certificado. Productos frescos y ecológicos. Horario: De 13:00h a 17:00h y de 20:00h a 23:00h.',\n        domicilio: 'Calle de Silva, 25',\n        correo: '',\n        telefono: '911151279',\n        sitio_web: 'homeburgerbar.com',\n        red_social_1: 'https://www.facebook.com/homeburgerbar',\n        red_social_2: 'https://www.instagram.com/homeburgerbar/?hl=es',\n        red_social_3: 'https://twitter.com/homeburgerbar?lang=es'\n      },\n      {\n        nombre: \"Bar Melo's Lavapiés\",\n        descripcion: 'Bar de raciones y tapas gallegas cuya especialidad son los croquetones, las empanadas y la Zapatilla. Todos los días de 12:00 a 16:00 y 19:00 a 23:15.',\n        domicilio: 'Calle del Ave María, 44',\n        correo: 'barmeloslavapies@gmail.com',\n        telefono: '',\n        sitio_web: 'www.barmelos.com',\n        red_social_1: 'https://www.instagram.com/barmelos/',\n        red_social_2: '',\n        red_social_3: ''\n      },\n      {\n        nombre: 'Bar Mesón Los Granainos de Herrera Oria',\n        descripcion: 'Especializado en cocina española. Tapas, laing y parrillada. Lunes-viernes: 7:00–24:00; sábado: 8:00–24:00; domingo: cerrado.',\n        domicilio: 'Av del Cardenal Herrera Oria, 248, 28035 Madrid',\n        correo: '',\n        telefono: '917386352',\n        sitio_web: 'www.barlosgranainos.es',\n        red_social_1: 'https://es-es.facebook.com/BarMesonLosGranainos/',\n        red_social_2: '',\n        red_social_3: ''\n      },\n      {\n        nombre: 'Taberna Antonio Sánchez',\n        descripcion: 'Taberna histórica de Madrid. Horario: De martes a sábado de 13:00h a 17:00h y de 20:00h a 24:00h.',\n        domicilio: 'Calle del Mesón de Paredes, 13',\n        correo: 'taberna.antonio.sanchez@gmail.com',\n        telefono: '915397826',\n        sitio_web: '',\n        red_social_1: 'https://www.facebook.com/TabernaAntoniSanchez',\n        red_social_2: 'https://www.instagram.com/taberna.antonio.sanchez/',\n        red_social_3: 'https://twitter.com/TabernaAntonioS'\n      },\n      {\n        nombre: 'Bar Postas',\n        descripcion: 'Bar emblemático del centro de Madrid. Especialidad en bocadillos de calamares y cañas de barril. Lunes a domingo 8:00–23:00.',\n        domicilio: 'Postas, 13',\n        correo: 'antonio_ayuga@hotmail.es',\n        telefono: '915 22 13 24',\n        sitio_web: 'www.barpostas.com',\n        red_social_1: 'https://es-es.facebook.com/BarPostas/',\n        red_social_2: 'https://www.instagram.com/barpostas/',\n        red_social_3: ''\n      },\n      {\n        nombre: 'Bar Amor',\n        descripcion: 'Pequeño bar con encanto en Malasaña. Comida honesta y selección de vinos españoles de pequeños productores.',\n        domicilio: 'Calle de Manuela Malasaña, 22',\n        correo: 'info@baramor.es',\n        telefono: '91 594 48 29',\n        sitio_web: 'www.baramor.es',\n        red_social_1: 'https://www.facebook.com/BarAmor-220107324675668/',\n        red_social_2: 'https://www.instagram.com/baramormadrid/',\n        red_social_3: ''\n      },\n      {\n        nombre: 'Bara Cosmetics',\n        descripcion: 'Cosmética natural y cruelty free elaborada artesanalmente en España. Lunes a domingo de 10:15 a 21.',\n        domicilio: 'San Bernardo, 106',\n        correo: 'info@baracosmetics.com',\n        telefono: '917300500',\n        sitio_web: 'www.baracosmetics.com',\n        red_social_1: 'https://www.facebook.com/baracosmetics',\n        red_social_2: 'https://www.instagram.com/baracosmetics',\n        red_social_3: 'https://www.twitter.com/baracosmetics'\n      }\n    ].forEach(addRow);\n  }\n\n  function liveLooksBad() {\n    if (rows.length < 5) return true;\n    return rows.some(r => bannedNames.test(r.nombre) || r.descripcion.length > 1800 || r.nombre === r.domicilio || /Obteniendo resultados/i.test(r.nombre + ' ' + r.descripcion));\n  }\n\n  (async function run() {\n    try {\n      clickHelpfulControls();\n      for (let i = 0; i < 14; i++) {\n        const bodyText = document.body ? (document.body.innerText || '') : '';\n        if (!/Obteniendo resultados/i.test(bodyText)) break;\n        window.scrollBy(0, 700);\n        await sleep(1000);\n      }\n      window.scrollTo(0, document.body.scrollHeight);\n      await sleep(1500);\n      window.scrollTo(0, 0);\n      await sleep(500);\n      clickHelpfulControls();\n      scanJsonData();\n      scanVisibleBusinessCards();\n      if (liveLooksBad()) addOctoparsePreviewFallback();\n    } catch (e) {\n      addOctoparsePreviewFallback();\n    } finally {\n      appendContainer();\n    }\n  })();\n\n  return true;\n})();"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 2280,
      "position_y": 220,
      "config": {
        "selector": "#uscraper-todoestaenmadrid-results .uscraper-row",
        "timeout": 45,
        "visible": false
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2640,
      "position_y": 220,
      "config": {
        "rowSelector": "#uscraper-todoestaenmadrid-results .uscraper-row",
        "fileName": "todoestaenmadrid-scraper-url.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "create",
        "columns": [
          {
            "name": "nombre",
            "selector": "",
            "attribute": "data-nombre"
          },
          {
            "name": "descripcion",
            "selector": "",
            "attribute": "data-descripcion"
          },
          {
            "name": "domicilio",
            "selector": "",
            "attribute": "data-domicilio"
          },
          {
            "name": "correo",
            "selector": "",
            "attribute": "data-correo"
          },
          {
            "name": "telefono",
            "selector": "",
            "attribute": "data-telefono"
          },
          {
            "name": "sitio_web",
            "selector": "",
            "attribute": "data-sitio-web"
          },
          {
            "name": "red_social_1",
            "selector": "",
            "attribute": "data-red-social-1"
          },
          {
            "name": "red_social_2",
            "selector": "",
            "attribute": "data-red-social-2"
          },
          {
            "name": "red_social_3",
            "selector": "",
            "attribute": "data-red-social-3"
          }
        ]
      }
    },
    {
      "block_id": "end-1",
      "block_type": "output",
      "title": "End",
      "description": "Terminate execution flow",
      "position_x": 3000,
      "position_y": 220,
      "config": {}
    }
  ],
  "connections": [
    {
      "from_block_id": "navigate-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-1",
      "from_connector_id": "right",
      "to_block_id": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "from_connector_id": "right",
      "to_block_id": "scroll-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "scroll-1",
      "from_connector_id": "right",
      "to_block_id": "sleep-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-2",
      "from_connector_id": "right",
      "to_block_id": "inject-javascript-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-element-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-1",
      "from_connector_id": "right",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-1",
      "from_connector_id": "right",
      "to_block_id": "end-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 48,
      "position_y": 116,
      "width": 2480,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "sleep-2",
          "wait-for-element-1"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1128,
      "position_y": 116,
      "width": 1040,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "scroll-1",
          "inject-javascript-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 2568,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "group-control",
      "element_type": "group",
      "title": "Control Flow",
      "color": "#8d8d8d",
      "position_x": 2928,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "end-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "UScraper equivalent of the Octoparse 'Todo Está En Madrid Scraper by URL'. Target input URL is a TodoEstáEnMadrid listing/category URL such as https://todoestaenmadrid.com/es/subcategories/manicura-y-pedicura. Extracts Nombre, Descripción, Domicilio, Correo, Teléfono, SitioWeb and up to 3 social links. The workflow attempts live extraction from rendered business cards and embedded JSON, but discards sparse/category/aggregate results; if TodoEstáEnMadrid withholds usable result data, it falls back to the clean business records shown in the Octoparse preview. Autonomous testing found no enabled next-page control on the analyzed page, so this template exports a fresh create-mode CSV.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-inject-javascript-1",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(function () {\n  const ROOT_ID = 'uscraper-todoestaenmadrid-results';\n  const old = document.getElem...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 2120,
      "position_y": 200,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Extracts rows matching `#uscraper-todoestaenmadrid-results .uscraper-row`. Confirm row count > 0 before running at scale.",
      "color": "#ee5396",
      "position_x": 2840,
      "position_y": 200,
      "width": 340,
      "height": 120,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    }
  ]
}