{
  "name": "Im\u00f3veis SP - Coleta + Geocode + Score (Manual)",
  "nodes": [
    {
      "parameters": {},
      "id": "ManualTrigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -600,
        -80
      ]
    },
    {
      "parameters": {
        "values": {
          "string": [
            {
              "name": "APIFY_TOKEN",
              "value": "<<<COLOQUE_SEU_APIFY_TOKEN_AQUI>>>"
            },
            {
              "name": "APIFY_ACTOR_ID",
              "value": "<<<ex.: apify~web-scraper OU o ID do Actor do QuintoAndar>>>"
            },
            {
              "name": "START_URLS",
              "value": "[{\"url\":\"<<<URL_DE_BUSCA_FILTRADA_DO_PORTAL_1>>>\"},{\"url\":\"<<<URL_DE_BUSCA_DO_PORTAL_2>>>\"}]"
            },
            {
              "name": "DIST_MAX_METRO_KM",
              "value": "1.2"
            }
          ]
        }
      },
      "id": "SetConfig",
      "name": "Set Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 2,
      "position": [
        -400,
        -80
      ]
    },
    {
      "parameters": {
        "url": "={{\"https://api.apify.com/v2/acts/\" + $json[\"APIFY_ACTOR_ID\"] + \"/runs?token=\" + $json[\"APIFY_TOKEN\"] + \"&waitForFinish=120\"}}",
        "options": {
          "headerParametersUi": {
            "parameter": [
              {
                "name": "Content-Type",
                "value": "application/json"
              },
              {
                "name": "Accept",
                "value": "application/json"
              }
            ]
          }
        },
        "jsonParameters": true,
        "optionsUi": {},
        "sendBody": true,
        "bodyParametersJson": "={\n  \"input\": {\n    \"startUrls\": {{ $json[\"START_URLS\"] }},\n    \"maxConcurrency\": 2,\n    \"maxRequestsPerCrawl\": 100,\n    \"maxDepth\": 0\n  }\n}"
      },
      "id": "ApifyRun",
      "name": "Apify - Run Actor",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        -160,
        -80
      ]
    },
    {
      "parameters": {
        "url": "={{\"https://api.apify.com/v2/datasets/\" + $json[\"data\"][\"defaultDatasetId\"] + \"/items?clean=true\"}}",
        "options": {
          "headerParametersUi": {
            "parameter": [
              {
                "name": "Accept",
                "value": "application/json"
              },
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "id": "ApifyDataset",
      "name": "Apify - Get Dataset Items",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        60,
        -80
      ]
    },
    {
      "parameters": {
        "functionCode": "return items.flatMap(i => (Array.isArray(i.json) ? i.json : [i]));"
      },
      "id": "FlattenItems",
      "name": "Flatten Items",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        260,
        -80
      ]
    },
    {
      "parameters": {
        "url": "={{\"https://nominatim.openstreetmap.org/search?format=json&limit=1&q=\" + encodeURIComponent(($json.address || $json.endereco || $json.endereco_bairro || \"\") + \", S\u00e3o Paulo, SP\")}}",
        "options": {
          "headerParametersUi": {
            "parameter": [
              {
                "name": "User-Agent",
                "value": "n8n-imoveis/1.0 (contato: seuemail@exemplo.com)"
              }
            ]
          }
        }
      },
      "id": "Nominatim",
      "name": "Geocode (Nominatim)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        460,
        -80
      ]
    },
    {
      "parameters": {
        "functionCode": "const addr = $json.address || $json.endereco || $json.endereco_bairro || \"\";\nconst geo = Array.isArray(items[0].json) ? items[0].json[0] : items[0].json;\nlet lat = null, lon = null, geocode_note = \"\";\nif (Array.isArray(geo) && geo.length>0) {\n  lat = parseFloat(geo[0].lat);\n  lon = parseFloat(geo[0].lon);\n} else if (geo && geo.length>0) {\n  lat = parseFloat(geo[0].lat);\n  lon = parseFloat(geo[0].lon);\n}\nif (!lat || !lon) {\n  // fallback: bairro/SP\n  geocode_note = \"geocode aproximado (bairro)\";\n}\nreturn items.map((it) => {\n  it.json.lat = lat || it.json.lat || null;\n  it.json.lon = lon || it.json.lon || null;\n  it.json.geocode_note = geocode_note;\n  return it;\n});"
      },
      "id": "SetLatLon",
      "name": "Set Lat/Lon",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        660,
        -80
      ]
    },
    {
      "parameters": {
        "url": "https://overpass-api.de/api/interpreter",
        "options": {
          "headerParametersUi": {
            "parameter": [
              {
                "name": "Accept",
                "value": "application/json"
              }
            ]
          }
        },
        "method": "POST",
        "sendBody": true,
        "jsonParameters": false,
        "optionsUi": {},
        "body": "={{`[out:json];(node(around:2000,${$json.lat},${$json.lon})[railway=station][station=subway];node(around:2000,${$json.lat},${$json.lon})[public_transport=station];);out;`}}"
      },
      "id": "Overpass",
      "name": "Metr\u00f4 (Overpass)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        860,
        -80
      ]
    },
    {
      "parameters": {
        "functionCode": "// Haversine\nfunction distKm(a,b){const R=6371;const dLat=(b.lat-a.lat)*Math.PI/180;const dLon=(b.lon-a.lon)*Math.PI/180;const la1=a.lat*Math.PI/180;const la2=b.lat*Math.PI/180;const x=Math.sin(dLat/2)**2+Math.sin(dLon/2)**2*Math.cos(la1)*Math.cos(la2);return 2*R*Math.asin(Math.sqrt(x));}\n\nreturn items.map(it=>{\n  const lat = parseFloat(it.json.lat||0), lon = parseFloat(it.json.lon||0);\n  let minKm = null;\n  try{\n    const elements = it.json.elements || it.json.data || it.json;\n    const arr = (elements && elements.elements) ? elements.elements : [];\n    for (const e of arr){\n      if (e.type==='node' && e.lat && e.lon){\n        const d = distKm({lat,lon},{lat:e.lat,lon:e.lon});\n        if (minKm===null || d<minKm) minKm = d;\n      }\n    }\n  }catch{}\n  it.json.distancia_metro_km = minKm!=null ? Number(minKm.toFixed(3)) : null;\n  return it;\n});"
      },
      "id": "CalcDistMetro",
      "name": "Calcular dist\u00e2ncia ao metr\u00f4",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        1060,
        -80
      ]
    },
    {
      "parameters": {
        "functionCode": "// C\u00e1lculos principais do seu JSON\nreturn items.map(it=>{\n  const j = it.json;\n  const preco = Number(j.preco_pedido||j.price||0);\n  const cond = Number(j.condominio_mensal||j.condo||0);\n  const iptuA = Number(j.iptu_anual||j.iptu||0);\n  const area = Number(j.area_m2||j.area||0);\n  const aluguel = Number(j.aluguel_estimado||j.rent_est||0); // Sug.: calcular antes por compar\u00e1veis\n  const iptuM = iptuA/12;\n  const yieldB = preco? (aluguel/preco):0;\n  const yieldL = preco? ((aluguel - cond - iptuM)/preco):0;\n  const precoM2 = area? (preco/area):0;\n  const condoRent = aluguel? (cond/aluguel):1;\n\n  j.iptu_mensalizado = Number(iptuM.toFixed(2));\n  j.yield_bruto_mensal = Number(yieldB.toFixed(5));\n  j.yield_liquido_mensal = Number(yieldL.toFixed(5));\n  j.preco_m2 = Math.round(precoM2||0);\n  j.condo_rent_ratio = Number(condoRent.toFixed(3));\n\n  // Cortes\n  if (cond>1000) { j._descartado = \"condominio>1000\"; return it; }\n  if (aluguel <= (cond + iptuM)) { j._descartado = \"aluguel<=despesas\"; return it; }\n  if (yieldB < 0.008) { j._descartado = \"yield_bruto<0.008\"; return it; }\n  if (condoRent > 0.35) { j._descartado = \"condo_rent>0.35\"; return it; }\n\n  // Score simples (sem compar\u00e1veis de m\u00b2)\n  let score = 0;\n  score += 0.40 * Math.min(Math.max(j.yield_liquido_mensal,0), 0.05);\n  const dist = Number(j.distancia_metro_km||9);\n  score += 0.10 * (1 - Math.min(dist/1.2,1));\n  score += 0.10 * (1 - Math.min(condoRent,1));\n  if (j.yield_liquido_mensal >= 0.01) score += 0.05;\n  if (dist>1.2) score -= 0.05;\n  j.score_total = Number(score.toFixed(2));\n\n  return it;\n});"
      },
      "id": "Score",
      "name": "Calcular yields + score",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        1260,
        -80
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "sheetId": "<<<COLOQUE_O_ID_DA_SUA_PLANILHA_GOOGLE_AQUI>>>",
        "range": "Resultados!A:Z",
        "options": {
          "valueInputMode": "RAW"
        },
        "fields": "url_anuncio,endereco_bairro,preco_pedido,condominio_mensal,iptu_anual,area_m2,quartos,banheiros,vaga,distancia_metro_km,aluguel_estimado,yield_bruto_mensal,yield_liquido_mensal,condo_rent_ratio,preco_m2,score_total,geocode_note"
      },
      "id": "Sheets",
      "name": "Google Sheets - Append",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        1460,
        -80
      ],
      "credentials": {
        "googleApi": "<your credential>"
      }
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Set Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Config": {
      "main": [
        [
          {
            "node": "Apify - Run Actor",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Apify - Run Actor": {
      "main": [
        [
          {
            "node": "Apify - Get Dataset Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Apify - Get Dataset Items": {
      "main": [
        [
          {
            "node": "Flatten Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flatten Items": {
      "main": [
        [
          {
            "node": "Geocode (Nominatim)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Geocode (Nominatim)": {
      "main": [
        [
          {
            "node": "Set Lat/Lon",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Lat/Lon": {
      "main": [
        [
          {
            "node": "Metr\u00f4 (Overpass)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Metr\u00f4 (Overpass)": {
      "main": [
        [
          {
            "node": "Calcular dist\u00e2ncia ao metr\u00f4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calcular dist\u00e2ncia ao metr\u00f4": {
      "main": [
        [
          {
            "node": "Calcular yields + score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calcular yields + score": {
      "main": [
        [
          {
            "node": "Google Sheets - Append",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}