{
  "name": "GeckoAPI · Mercado Livre → Postgres → Power BI",
  "nodes": [
    {
      "id": "Executar manualmente",
      "name": "Executar manualmente",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "parameters": {}
    },
    {
      "id": "Todo dia às 08h",
      "name": "Todo dia às 08h",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        220
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * *"
            }
          ]
        }
      },
      "disabled": true
    },
    {
      "id": "Configuração",
      "name": "Configuração",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        0
      ],
      "parameters": {
        "jsCode": "return [{json:{keyword:\"tinta suvinil\"}}];"
      }
    },
    {
      "id": "Consultar Mercado Livre",
      "name": "Consultar Mercado Livre",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        480,
        0
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.geckoapi.com.br/v1/extract",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({target: \"mercadolivre.com.br\", type: \"plp\", keyword: $(\"Configuração\").first().json.keyword, page: 1}) }}",
        "options": {
          "timeout": 90000,
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      }
    },
    {
      "id": "Falha transitória?",
      "name": "Falha transitória?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        720,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "retryable",
              "leftValue": "={{ [429,500,502,503,504].includes($json.statusCode) }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "Esperar antes de repetir",
      "name": "Esperar antes de repetir",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        960,
        -160
      ],
      "parameters": {
        "amount": "={{ Math.max(10, Number($json.headers?.[\"retry-after\"]) || ((Date.parse($json.headers?.[\"retry-after\"]) - Date.now()) / 1000) || 10) }}",
        "unit": "seconds"
      },
      "webhookId": "gecko-price-retry"
    },
    {
      "id": "Repetir uma vez",
      "name": "Repetir uma vez",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1200,
        -160
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.geckoapi.com.br/v1/extract",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({target: \"mercadolivre.com.br\", type: \"plp\", keyword: $(\"Configuração\").first().json.keyword, page: 1}) }}",
        "options": {
          "timeout": 90000,
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      }
    },
    {
      "id": "Validar e normalizar",
      "name": "Validar e normalizar",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        0
      ],
      "parameters": {
        "jsCode": "// n8n Code node: Run Once for All Items. This file is embedded in workflow.json.\nconst response = $input.first().json;\nconst status = response.statusCode;\nconst payload = response.body;\nif (status !== 200) {\n  throw new Error(`GeckoAPI HTTP ${status}: verifique a execução; corrija 4xx antes de repetir. Não grave preço zero em falhas.`);\n}\nif (!payload || typeof payload !== 'object' || !payload.executionId) {\n  throw new Error('Resposta sem executionId; nenhum snapshot gravado.');\n}\nconst config = $('Configuração').first().json;\nconst data = payload.data;\nconst notFound = payload.notFound === true && data === null;\nif (!notFound && (!data || !Array.isArray(data.items))) {\n  throw new Error('Contrato PLP inválido: data.items deve ser um array.');\n}\nconst capturedAt = data?.extractedAt;\nif (!notFound && (!capturedAt || !Number.isFinite(Date.parse(capturedAt)))) {\n  throw new Error('Horário de captura ausente ou inválido.');\n}\nconst nullableText = value => typeof value === 'string' && value.trim() ? value : null;\nconst numeric = value => typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null;\nconst snapshots = (data?.items ?? []).map(item => {\n  // Use URL and seller identity: a catalog page can show different sellers.\n  if (typeof item.url !== 'string' || !/^https:\\/\\/(?:[a-z0-9-]+\\.)?mercadolivre\\.com\\.br\\//i.test(item.url)) {\n    throw new Error('Item sem URL pública válida do Mercado Livre.');\n  }\n  if (item.currency !== 'BRL') throw new Error('Moeda inesperada: este kit compara apenas BRL.');\n  const sellerId = item.sellerId == null ? null : String(item.sellerId);\n  return {\n    source_url: item.url,\n    product_id: item.sku == null ? item.url : String(item.sku),\n    seller_key: sellerId ?? nullableText(item.sellerName) ?? 'unknown',\n    seller_name: nullableText(item.sellerName),\n    product_name: nullableText(item.name),\n    price: numeric(item.price),\n    currency: item.currency,\n    rating: numeric(item.aggregateRating?.rating),\n    review_count: numeric(item.aggregateRating?.reviewCount),\n  };\n});\nreturn [{json:{\n  run_id: String(payload.executionId),\n  request_id: payload.requestId ?? null,\n  query: config.keyword,\n  captured_at: capturedAt ?? new Date().toISOString(),\n  status: notFound ? 'not_found' : snapshots.length ? 'ok' : 'empty',\n  snapshots,\n}}];\n"
      }
    },
    {
      "id": "Salvar no Postgres",
      "name": "Salvar no Postgres",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        1680,
        0
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT gecko_monitor.ingest($1::jsonb) AS inserted;",
        "options": {
          "queryReplacement": "={{ [JSON.stringify($json)] }}"
        }
      }
    }
  ],
  "connections": {
    "Executar manualmente": {
      "main": [
        [
          {
            "node": "Configuração",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Todo dia às 08h": {
      "main": [
        [
          {
            "node": "Configuração",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuração": {
      "main": [
        [
          {
            "node": "Consultar Mercado Livre",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Consultar Mercado Livre": {
      "main": [
        [
          {
            "node": "Falha transitória?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Esperar antes de repetir": {
      "main": [
        [
          {
            "node": "Repetir uma vez",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Repetir uma vez": {
      "main": [
        [
          {
            "node": "Validar e normalizar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validar e normalizar": {
      "main": [
        [
          {
            "node": "Salvar no Postgres",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Falha transitória?": {
      "main": [
        [
          {
            "node": "Esperar antes de repetir",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Validar e normalizar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "America/Sao_Paulo",
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  }
}
