{
  "version": "1.0.0",
  "exported_at": "2026-06-02T03:05:00.000Z",
  "project": {
    "name": "Propiedades Post Details Scraper",
    "description": "Scrapes detailed property information from propiedades.com detail-page URLs, including image URLs, price, description, address/name, property ID, bedrooms, bathrooms, parking, floors, age, built area, garden size, and amenities/services. Navigation uses a multi-URL loop: edit the Navigate block's urls array to add property detail URLs; each URL appends one row to propiedades_detalles_scraper_final.csv. Propiedades may serve anti-bot Challenge Validation with a hidden body; this template waits for html without requiring visibility, records challenge status, and extracts full details when the real property page is available.",
    "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": 240,
      "config": {
        "urls": [
          "https://propiedades.com/inmuebles//casa-en-renta-via-polaris-912-cumbres-antares-nuevo_leon-25268809#area=monterrey&tipos=renta&orden=&pagina=1&paginas=355&pos=1",
          "https://propiedades.com/inmuebles/casa-en-venta-no-reeleccion-villas-de-la-alianza-nuevo_leon-25189472#area=monterrey&tipos=venta&orden=&pagina=1&paginas=936&pos=2",
          "https://propiedades.com/inmuebles/casa-en-renta-mariano-arista-701-la-noria-mexico-27936673#area=estado-de-mexico&tipos=residencial-renta&pos=1"
        ],
        "color": "bg-[#4589ff]"
      }
    },
    {
      "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": 240,
      "config": {
        "timeout": 30,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 840,
      "position_y": 240,
      "config": {
        "duration": 5,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1200,
      "position_y": 240,
      "config": {
        "selector": "html",
        "timeout": 30,
        "visible": false,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1560,
      "position_y": 240,
      "config": {
        "rowSelector": "html",
        "fileName": "propiedades_detalles_scraper_final.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "scrape_status",
            "selector": "(() => { const challenge=!!document.querySelector('#sec-if-container,#sec-cpt-if,iframe[title*=Challenge i],input[name=\"verify-url\"]') || /Challenge Validation/i.test(document.title||''); return challenge ? 'blocked_by_challenge_validation' : 'loaded_property_page'; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "nombre",
            "selector": "(() => { const clean=s=>String(s||'').replace(/\\s+/g,' ').trim(); const objs=[]; document.querySelectorAll('script[type=\"application/ld+json\"],script#__NEXT_DATA__').forEach(sc=>{try{objs.push(JSON.parse(sc.textContent||''))}catch(e){}}); let found=''; const walk=o=>{ if(!o||found)return; if(Array.isArray(o)){o.forEach(walk);return;} if(typeof o==='object'){ for(const [k,v] of Object.entries(o)){ if(/^(name|title|titulo)$/i.test(k)&&typeof v==='string'&&clean(v)&&!/Challenge/i.test(v)){found=clean(v);return;} walk(v); } } }; objs.forEach(walk); if(found)return found; for(const sel of ['h1','[data-testid*=\"title\" i]','[class*=\"title\" i]','[class*=\"titulo\" i]']){const el=document.querySelector(sel); const v=clean(el&&el.innerText); if(v&&!/^\\$/.test(v)&&!/Challenge/i.test(v))return v;} const slug=(location.pathname.split('/').filter(Boolean).pop()||'').replace(/-\\d+\\/?$/,''); return slug ? slug.split('-').map(w=>w? w[0].toUpperCase()+w.slice(1):'').join(' ') : clean(document.title); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "direccion",
            "selector": "(() => { const clean=s=>String(s||'').replace(/\\s+/g,' ').trim(); const objs=[]; document.querySelectorAll('script[type=\"application/ld+json\"],script#__NEXT_DATA__').forEach(sc=>{try{objs.push(JSON.parse(sc.textContent||''))}catch(e){}}); let found=''; const walk=o=>{ if(!o||found)return; if(Array.isArray(o)){o.forEach(walk);return;} if(typeof o==='object'){ for(const [k,v] of Object.entries(o)){ if(/address|direccion|ubicacion|location/i.test(k)){ if(typeof v==='string'&&clean(v)){found=clean(v);return;} if(v&&typeof v==='object'){const parts=[v.streetAddress,v.addressLocality,v.addressRegion,v.postalCode].filter(Boolean).map(clean); if(parts.length){found=parts.join(', ');return;}} } walk(v); } } }; objs.forEach(walk); if(found)return found; for(const sel of ['[data-testid*=\"address\" i]','[class*=\"address\" i]','[class*=\"direccion\" i]','[class*=\"ubicacion\" i]']){const el=document.querySelector(sel); const v=clean(el&&el.innerText); if(v)return v;} const h=clean((document.querySelector('h1')||{}).innerText); if(/Colonia|C\\.P\\.|CP\\./i.test(h)){const p=h.split(',').map(clean); if(p.length>1)return p.slice(1).join(', ');} const lines=(document.body.innerText||'').split(/\\n+/).map(clean).filter(Boolean); const line=lines.find(l=>/(Colonia|Mcpio\\.|Municipio|CP\\.|C\\.P\\.|Nuevo Le[oó]n|M[eé]xico|Jalisco|CDMX)/i.test(l)&&!/Nuestros productos|Blog|Empresa/i.test(l)); return line||''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "pagina_url",
            "selector": "location.href",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "precio",
            "selector": "(() => { const clean=s=>String(s||'').replace(/\\s+/g,' ').trim(); const values=[]; const add=v=>{ const raw=clean(v); if(!raw)return; const m=raw.match(/\\$?\\s?([\\d,.]+)\\s*(?:MXN|MN|M\\.N\\.)?/i); if(!m)return; const num=parseFloat(m[1].replace(/,/g,'')); if(!isFinite(num)||num<=0)return; values.push(/^\\$/.test(raw)?raw.match(/\\$\\s?[\\d,.]+\\s*(?:MXN|MN|M\\.N\\.)?/i)?.[0]||raw:'$'+m[1]+' MXN'); }; document.querySelectorAll('[class*=\"price\" i],[class*=\"precio\" i],[data-testid*=\"price\" i],h1,h2,p,span,div').forEach(el=>{ const t=clean(el.innerText); if(t&&t.length<160&&/\\$\\s?[\\d,.]+/.test(t)) add(t); }); document.querySelectorAll('meta[property=\"product:price:amount\"],meta[property=\"og:price:amount\"],meta[name*=\"price\" i],meta[property*=\"price\" i]').forEach(m=>add(m.getAttribute('content'))); const objs=[]; document.querySelectorAll('script[type=\"application/ld+json\"],script#__NEXT_DATA__').forEach(sc=>{try{objs.push(JSON.parse(sc.textContent||''))}catch(e){}}); const walk=o=>{ if(!o)return; if(Array.isArray(o)){o.forEach(walk);return;} if(typeof o==='object'){ for(const [k,v] of Object.entries(o)){ if(/price|precio|amount|renta|venta/i.test(k)&&(typeof v==='string'||typeof v==='number')) add(v); walk(v); } } }; objs.forEach(walk); const body=document.body.innerText||''; Array.from(body.matchAll(/\\$\\s?[\\d,.]+\\s*(?:MXN|MN|M\\.N\\.)?/ig)).forEach(m=>add(m[0])); return Array.from(new Set(values)).sort((a,b)=>b.replace(/\\D/g,'').length-a.replace(/\\D/g,'').length)[0]||''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "descripcion",
            "selector": "(() => { const clean=s=>String(s||'').replace(/\\n{3,}/g,'\\n\\n').trim(); const objs=[]; document.querySelectorAll('script[type=\"application/ld+json\"],script#__NEXT_DATA__').forEach(sc=>{try{objs.push(JSON.parse(sc.textContent||''))}catch(e){}}); let found=''; const walk=o=>{ if(!o||found)return; if(Array.isArray(o)){o.forEach(walk);return;} if(typeof o==='object'){ for(const [k,v] of Object.entries(o)){ if(/description|descripcion/i.test(k)&&typeof v==='string'&&clean(v).length>25&&!/Nuestros productos|Blog|Empresa/i.test(v)){found=clean(v);return;} walk(v); } } }; objs.forEach(walk); if(found)return found; for(const sel of ['[data-testid*=\"description\" i]','[class*=\"description\" i]','[class*=\"descripcion\" i]','#description','#descripcion']){const el=document.querySelector(sel); const v=clean(el&&el.innerText); if(v.length>30&&!/Nuestros productos|Blog|Empresa/i.test(v))return v;} const t=(document.body.innerText||'').replace(/\\r/g,''); const m=t.match(/Descripci[oó]n\\s*([\\s\\S]{20,2500}?)(?:Caracter[ií]sticas|Amenidades|Servicios|Ubicaci[oó]n|Mapa|Contactar|Anunciante|Propiedades similares|Nuestros productos|$)/i); return m ? clean(m[1]) : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "id_del_inmueble",
            "selector": "(() => { const urlId=(location.pathname.match(/-(\\d+)\\/?$/)||[])[1]; if(urlId)return urlId; const m=(document.body.innerText||'').match(/(?:ID(?: del inmueble)?|Folio)\\D{0,20}(\\d{5,})/i); return m?m[1]:''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "recamaras",
            "selector": "(() => { const text=document.body.innerText||''; const lines=text.split(/\\n+/).map(s=>s.trim()).filter(Boolean); const good=s=>{const m=String(s||'').match(/\\b\\d+(?:\\.\\d+)?\\b/); if(!m)return''; const n=parseFloat(m[0]); return n>=0&&n<=20?m[0]:''}; function jsonFind(re){let out=''; const walk=o=>{if(!o||out)return; if(Array.isArray(o)){o.forEach(walk);return;} if(typeof o==='object'){for(const [k,v] of Object.entries(o)){if(re.test(k)&&(typeof v==='string'||typeof v==='number')){const g=good(v); if(g){out=g;return;}} walk(v);}}}; document.querySelectorAll('script[type=\"application/ld+json\"],script#__NEXT_DATA__').forEach(sc=>{try{walk(JSON.parse(sc.textContent||''))}catch(e){}}); return out;} const j=jsonFind(/rec[aá]m|habitac|bedroom/i); if(j)return j; for(let i=0;i<lines.length;i++){if(/Rec[aá]maras?|Habitaciones?/i.test(lines[i])){for(const x of [lines[i],lines[i-1],lines[i+1],lines[i-2],lines[i+2]].filter(Boolean)){const g=good(x); if(g)return g;}}} return ((text.match(/(\\d+)\\s*(?:rec[aá]maras?|habitaciones?)/i)||[])[1]||''); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "banos",
            "selector": "(() => { const text=document.body.innerText||''; const lines=text.split(/\\n+/).map(s=>s.trim()).filter(Boolean); const good=s=>{const m=String(s||'').match(/\\b\\d+(?:\\.\\d+)?\\b/); if(!m)return''; const n=parseFloat(m[0]); return n>=0&&n<=20?m[0]:''}; function jsonFind(re){let out=''; const walk=o=>{if(!o||out)return; if(Array.isArray(o)){o.forEach(walk);return;} if(typeof o==='object'){for(const [k,v] of Object.entries(o)){if(re.test(k)&&(typeof v==='string'||typeof v==='number')){const g=good(v); if(g){out=g;return;}} walk(v);}}}; document.querySelectorAll('script[type=\"application/ld+json\"],script#__NEXT_DATA__').forEach(sc=>{try{walk(JSON.parse(sc.textContent||''))}catch(e){}}); return out;} const j=jsonFind(/ba[nñ]o|bath/i); if(j)return j; for(let i=0;i<lines.length;i++){if(/Ba[ñn]os?/i.test(lines[i])){for(const x of [lines[i],lines[i-1],lines[i+1],lines[i-2],lines[i+2]].filter(Boolean)){if(!/m\\s*(2|²)|CP|C\\.P\\./i.test(x)){const g=good(x); if(g)return g;}}}} return ((text.match(/(\\d+(?:\\.\\d+)?)\\s*ba[ñn]os?/i)||[])[1]||''); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "estacionamientos",
            "selector": "(() => { const text=document.body.innerText||''; const lines=text.split(/\\n+/).map(s=>s.trim()).filter(Boolean); const good=s=>{const m=String(s||'').match(/\\b\\d+\\b/); if(!m)return''; const n=parseInt(m[0]); return n>=0&&n<=20?m[0]:''}; function jsonFind(re){let out=''; const walk=o=>{if(!o||out)return; if(Array.isArray(o)){o.forEach(walk);return;} if(typeof o==='object'){for(const [k,v] of Object.entries(o)){if(re.test(k)&&(typeof v==='string'||typeof v==='number')){const g=good(v); if(g){out=g;return;}} walk(v);}}}; document.querySelectorAll('script[type=\"application/ld+json\"],script#__NEXT_DATA__').forEach(sc=>{try{walk(JSON.parse(sc.textContent||''))}catch(e){}}); return out;} const j=jsonFind(/estacion|cajon|garage|parking/i); if(j)return j; for(let i=0;i<lines.length;i++){if(/Estacionamientos?|Cajones?|Cochera|Garage|Autos?/i.test(lines[i])){for(const x of [lines[i],lines[i-1],lines[i+1],lines[i-2],lines[i+2]].filter(Boolean)){const g=good(x); if(g)return g;}}} return ((text.match(/(\\d+)\\s*(?:estacionamientos?|cajones?|autos?)/i)||[])[1]||''); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "no_de_pisos",
            "selector": "(() => { const text=document.body.innerText||''; const lines=text.split(/\\n+/).map(s=>s.trim()).filter(Boolean); const good=s=>{const m=String(s||'').match(/\\b\\d+\\b/); if(!m)return''; const n=parseInt(m[0]); return n>=0&&n<=80?m[0]:''}; for(let i=0;i<lines.length;i++){if(/N[úu]mero de pisos|No\\.? de pisos|Pisos?|Niveles?/i.test(lines[i])){for(const x of [lines[i],lines[i-1],lines[i+1]].filter(Boolean)){const g=good(x); if(g)return g;}}} return ((text.match(/(\\d+)\\s*(?:niveles?|pisos?)/i)||[])[1]||''); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "edad_del_inmueble",
            "selector": "(() => { const text=document.body.innerText||''; const lines=text.split(/\\n+/).map(s=>s.trim()).filter(Boolean); for(let i=0;i<lines.length;i++){if(/Edad del inmueble|Antig[uü]edad|Estado/i.test(lines[i])){for(const x of [lines[i],lines[i-1],lines[i+1]].filter(Boolean)){const m=x.match(/Nuevo|N\\/D|\\d+\\s*a[ñn]os?/i); if(m)return m[0];}}} const m=text.match(/(?:Edad del inmueble|Antig[uü]edad|Estado)\\D{0,40}(Nuevo|N\\/D|\\d+\\s*a[ñn]os?)/i); return m?m[1]:''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "area_construida",
            "selector": "(() => { const text=document.body.innerText||''; const lines=text.split(/\\n+/).map(s=>s.trim()).filter(Boolean); function findNear(re){for(let i=0;i<lines.length;i++){if(re.test(lines[i])){for(const x of [lines[i],lines[i-1],lines[i+1],lines[i-2],lines[i+2]].filter(Boolean)){if(x.length<80){const m=x.match(/[\\d,.]+\\s*m(?:2|²)/i); if(m)return m[0];}}}} return '';} const near=findNear(/[ÁA]rea construida|Construcci[oó]n|Construidos?/i); if(near)return near; const m=text.match(/(?:[ÁA]rea construida|Construcci[oó]n|Construidos?)\\D{0,50}([\\d,.]+\\s*m(?:2|²))/i); return m?m[1]:''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tamano_del_jardin",
            "selector": "(() => { const text=document.body.innerText||''; const lines=text.split(/\\n+/).map(s=>s.trim()).filter(Boolean); function findNear(re){for(let i=0;i<lines.length;i++){if(re.test(lines[i])){for(const x of [lines[i],lines[i-1],lines[i+1],lines[i-2],lines[i+2]].filter(Boolean)){if(x.length<80){const m=x.match(/[\\d,.]+\\s*m(?:2|²)/i); if(m)return m[0];}}}} return '';} const near=findNear(/Tama[ñn]o del jard[ií]n|Jard[ií]n|Patio/i); if(near)return near; const m=text.match(/(?:Tama[ñn]o del jard[ií]n|Jard[ií]n|Patio)\\D{0,50}([\\d,.]+\\s*m(?:2|²))/i); return m?m[1]:''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "amenidades_servicios",
            "selector": "(() => { const clean=s=>String(s||'').replace(/\\n{3,}/g,'\\n\\n').replace(/([a-záéíóúñ])([A-ZÁÉÍÓÚÑ])/g,'$1 | $2').trim(); const bad=s=>/Nuestros productos|Publicar gratis|Ingresar|Somos la comunidad|Empresa\\s+Acerca|Prensa|Blog/i.test(s); const heads=Array.from(document.querySelectorAll('h2,h3,h4,strong,b')).filter(h=>/Amenidades|Servicios/i.test(h.innerText||'')); for(const h of heads){const parts=[]; let n=h.nextElementSibling; for(let k=0;n&&k<5;k++,n=n.nextElementSibling){const v=clean(n.innerText); if(v&&!bad(v))parts.push(v);} const out=clean(parts.join(' | ')); if(out&&out.length<1500)return out;} const t=(document.body.innerText||'').replace(/\\r/g,''); const m=t.match(/(?:Amenidades|Servicios)\\s*([\\s\\S]{5,1200}?)(?:Ubicaci[oó]n|Mapa|Contactar|Anunciante|Propiedades similares|Nuestros productos|$)/i); const out=m?clean(m[1]):''; return out&&!bad(out)?out:''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "imagen_url",
            "selector": "(() => { const urls=[]; const add=u=>{ if(!u)return; String(u).split(',').forEach(part=>{ let x=part.trim().split(/\\s+/)[0].replace(/\\\\u002F/g,'/').replace(/\\\\\\//g,'/'); if(!x||x.startsWith('data:'))return; try{urls.push(new URL(x,location.href).href)}catch(e){} }); }; document.querySelectorAll('img,source').forEach(el=>{[el.currentSrc,el.src,el.getAttribute('src'),el.getAttribute('data-src'),el.getAttribute('srcset')].forEach(add);}); document.querySelectorAll('meta[property=\"og:image\"],meta[name=\"twitter:image\"],meta[itemprop=\"image\"]').forEach(m=>add(m.getAttribute('content'))); const walk=o=>{ if(!o)return; if(typeof o==='string'){ if(/(jpg|jpeg|png|webp|_next\\/image|s3\\.amazonaws|propiedadescom)/i.test(o)) add(o); return; } if(Array.isArray(o)){o.forEach(walk);return;} if(typeof o==='object'){ for(const [k,v] of Object.entries(o)){ if(/image|imagen|photo|foto|thumbnail|url/i.test(k)) walk(v); else if(typeof v==='object') walk(v); } } }; document.querySelectorAll('script[type=\"application/ld+json\"],script#__NEXT_DATA__').forEach(sc=>{try{walk(JSON.parse(sc.textContent||''))}catch(e){}}); document.querySelectorAll('script').forEach(sc=>{const normalized=(sc.textContent||'').replace(/\\\\u002F/g,'/').replace(/\\\\\\//g,'/'); const re=/https?:\\/\\/[^\"'\\s<>]+?(?:jpg|jpeg|png|webp)(?:[^\"'\\s<>]*)?/ig; let m; while((m=re.exec(normalized))) add(m[0]);}); return Array.from(new Set(urls)).filter(u=>/(propiedades|s3\\.amazonaws|_next\\/image|jpg|jpeg|png|webp)/i.test(u)).join(' | '); })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 1920,
      "position_y": 240,
      "config": {
        "color": "bg-[#ff832b]"
      }
    }
  ],
  "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": "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": "loop-continue-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 48,
      "position_y": 136,
      "width": 1400,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-element-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 1488,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 1848,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes detailed property information from propiedades.com detail-page URLs, including image URLs, price, description, address/name, property ID, bedrooms, bathrooms, parking, floors, age, built area, garden size, and amenities/services. Navigation uses a multi-URL loop: edit the Navigate block's urls array to add property detail URLs; each URL appends one row to propiedades_detalles_scraper_final.csv. Propiedades may serve anti-bot Challenge Validation with a hidden body; this template waits for html without requiring visibility, records challenge status, and extracts full details when the real property page is available.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (scrape_status, nombre, direccion, pagina_url, precio). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 1760,
      "position_y": 220,
      "width": 340,
      "height": 131,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-loop-continue-1",
      "element_type": "note",
      "title": "Note: Loop Continue",
      "content": "Loop Continue advances a multi-URL or multi-text loop. Place at the end of the loop body with a clear back-edge to the loop start.",
      "color": "#ee5396",
      "position_x": 2120,
      "position_y": 220,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}