# Implementação Técnica — SEO Report (OpenSEO) ## Workflow ```mermaid graph TD A[Input: URL] --> B[whoami + list_projects] B --> C{Mercado correcto?} C -->|Não| C2[Passar locationCode 2620 + languageCode pt] C -->|Sim| D[GSC: dimensions query] C2 --> D D --> E[GSC: dimensions page] E --> F[Dimensionar crawl:
maxPages = páginas GSC + 25%] F --> G[run_site_audit] G --> H[poll get_audit_status] H -->|running| H H -->|completed| I[get_audit_issues por severidade] I --> J[Cruzar: páginas com procura
E com defeito] J --> K{Fases pagas contratadas?} K -->|Sim| L[get_keyword_metrics
get_backlinks_overview
get_serp_results] K -->|Não| M[Saltar fases pagas] L --> N[Processar] M --> N N --> O[Markdown] --> P[Google Doc] --> Q[Partilhar] ``` **O ponto crítico é o nó F.** Dimensionar o crawl a partir do GSC é o que distingue um relatório verdadeiro de um relatório limpo e falso. --- ## Sequência de chamadas ```javascript // 1. Contexto e mercado const me = await openseo.whoami(); const projs = await openseo.list_projects(); const projectId = projs[0].id; const MARKET = { locationCode: 2620, languageCode: 'pt' }; // Portugal // 2. Search Console — paginar até esgotar (grátis) async function gscAll(dimensions) { const rows = []; let startRow = 0; while (true) { const r = await openseo.get_search_console_performance({ projectId, dimensions, dateRange: 'last_3_months', rowLimit: 1000, startRow }); const parsed = parseRows(r.text); rows.push(...parsed); if (!r.text.split('\n')[0].includes('more available') || !parsed.length) break; startRow += 1000; } return rows; } const byQuery = await gscAll(['query']); const byPage = await gscAll(['page']); // 3. Dimensionar o crawl a partir da realidade, não de um default const maxPages = Math.ceil(byPage.length * 1.25); // 4. Crawl (grátis) — assíncrono, exige poll const audit = await openseo.run_site_audit({ projectId, url, maxPages, runLighthouse: true }); let status; do { await sleep(30_000); status = await openseo.get_audit_status({ projectId, auditId: audit.id }); } while (!status.text.includes('completed')); // 5. Issues por severidade (grátis) const critical = await openseo.get_audit_issues({ projectId, auditId: audit.id, severity: 'critical' }); const warning = await openseo.get_audit_issues({ projectId, auditId: audit.id, severity: 'warning' }); // 6. Fases pagas — so se contratadas if (options.paid) { const kw = await openseo.get_keyword_metrics({ projectId, keywords: strikingDistance(byQuery).map(r => r.key), ...MARKET }); const bl = await openseo.get_backlinks_overview({ projectId, target: domain }); } ``` --- ## Processamento — as funções que importam ```javascript // GSC anonimiza queries: totais por query < totais por página. // Para números globais usar SEMPRE a vista por página. const globals = { clicks: sum(byPage, 'clicks'), impressions: sum(byPage, 'impressions'), ctr: sum(byPage, 'clicks') / sum(byPage, 'impressions') }; // O GSC não filtra por posição — filtrar do lado do cliente. const strikingDistance = rows => rows .filter(r => r.position >= 4 && r.position <= 20 && r.impressions >= 25) .sort((a, b) => b.impressions - a.impressions); // Páginas com procura real enterradas fora das primeiras 4 páginas. const waste = rows => rows .filter(r => r.position > 40 && r.impressions >= 300) .sort((a, b) => b.impressions - a.impressions); // O sinal mais accionável: primeira página, CTR nulo. // Não é ranking — é título e meta description. const titleProblem = rows => rows .filter(r => r.position <= 10 && r.impressions >= 200 && r.ctr < 0.005); // Lixo nas queries do GSC (colagens do Google Ads, operadores site:). const isNoise = k => (k.includes('ativado') && k.includes('correspond')) || k.startsWith('-site:') || k.startsWith('=') || k.length > 90 || k.includes('0,00'); ``` --- ## Entrega ```javascript const doc = await googleWorkspace.docs_create_document({ title: `Relatório SEO — ${domain} — ${today}`, user_google_email: email }); await googleWorkspace.docs_append_text({ document_id: doc.id, text: markdown }); ``` --- ## Notas técnicas ### Requisitos - OpenSEO acessível (`whoami` responde) - Propriedade ligada ao Search Console - `google-workspace` MCP para o documento ### Tempos reais (medidos, descomplicar.pt, 07-2026) | Fase | Duração | |---|---| | GSC (2 dimensões, ~1700+474 linhas) | ~10s | | Crawl 600 páginas + Lighthouse 20 | ~4 min | | `get_audit_issues` | instantâneo | ### Limitações - **Issues sem URL:** `get_audit_issues` / `get_audit_pages` devolvem só contagens pela bridge MCP; `structuredContent.issues` não é entregue e `get_audit_pages` trunca em ~25 linhas. Usar `details.mcpMeta.url` para o link da UI. - **Sem medidor de créditos:** `whoami` não muda depois de chamadas pagas. Custos são estimativas. - **Lag do GSC:** últimos ~3 dias incompletos; datas em Pacific Time; máx. 16 meses. ### Erros comuns | Erro | Sintoma | Solução | |---|---|---| | `maxPages` por omissão (50) | Poucos issues, relatório "limpo" | Redimensionar pelo GSC, relançar | | Site fora do GSC | Fase GSC vazia | Gerar sem GSC, aviso destacado | | Mercado errado | Keywords em inglês/EUA | `locationCode: 2620`, `languageCode: "pt"` | | Cancelar o crawl cedo demais | Sem `auditId` utilizável | Fazer poll; 600 páginas ≈ 4 min | --- **Actualizado:** 30-07-2026 | Substitui a implementação baseada em Lighthouse MCP + SEO Tools API + Ahrefs