# 🏗️ Arquitetura - Busco Rumo

## Visão Geral do Sistema

A plataforma foi **dividida em dois dashboards** com objetivos distintos, mais um
**hub** de entrada. Os três são páginas estáticas independentes que compartilham
o mesmo `cities_data.js` e o mesmo `manual.html`.

> **Reposicionamento de portfólio (jul/2026):** o Busco Rumo passa a ser
> tratado como **produto solo** (ferramenta séria de decisão de
> mudança/investimento, sem gamificação na própria UI). Dicas Outdoor e
> ExploreBR passam a ser tratados como **família conectada** (conta única,
> badges/ranking compartilhados; candidatos a wrap nativo via Capacitor pra
> loja de app — fora do escopo atual). O elo de dados entre o check-in
> gamificado do ExploreBR (quiz de 5 categorias) e a "Avaliação como
> morador" do Busco Rumo é **mantido**: nota agregada por cidade continua
> alimentando o scorecard do Busco Rumo. Razão de cada decisão e sequência
> de execução em **POSICIONAMENTO.md** — antes de mexer em auth/schema
> compartilhado entre os três apps, ler esse doc primeiro.

> **Modelo de três camadas (2026-07-28):** os três apps não são só telas
> compartilhando dataset — cada um cumpre um papel diferente na jornada do
> usuário, já descrita em fluxo em **POSICIONAMENTO.md** ("Decidir → Explorar
> → Guardar"), aqui nomeada formalmente:
> - **Busco Rumo = camada de decisão** (mudança/investimento — onde decidir).
> - **Dicas Outdoor = camada de exploração** (desejo-primeiro — o que fazer,
>   que destino/roteiro combina com o que a pessoa quer viver).
> - **ExploreBR = camada de memória geográfica** (check-in, coleção, registro
>   do que já foi vivido — não decide nem recomenda, só guarda e devolve).
>
> **Busco Rumo ↔ Dicas Outdoor funcionam em caminhos de mão dupla:** a nota
> agregada por cidade (coletada via check-in gamificado do ExploreBR) alimenta
> o scorecard do Busco Rumo (elo já travado em POSICIONAMENTO.md); e a lógica
> territorial e os fatos confiáveis do Busco Rumo (`politicaTerritorialInterior`,
> `recommendCities`) são a mesma referência que qualquer recomendação no
> Outdoor deve usar — nunca uma segunda lógica de recomendação (mesmo
> princípio já registrado para a Bússola Essencial, ver seção própria abaixo).
> ExploreBR fica fora dessa mão dupla por design: alimenta as outras duas
> camadas com memória, mas não consome decisão nem exploração de volta.

```
┌──────────────────────────────────────────────────────────────────┐
│  HUB  ../index.html  (G:\Meu Drive\Ideias Holding\index.html)     │
│  Landing com cards p/ todos os dashboards (Busco Rumo, Outdoor,    │
│  Treino Diário, Patrimônio, Investimentos · Leilões/Usucapião 🔜)  │
└──────────────┬───────────────────────────────┬───────────────────┘
               ↓                                ↓
┌──────────────────────────────┐  ┌──────────────────────────────────┐
│ index.html                   │  │ outdoor/index.html               │
│ ONDE MORAR BRASIL            │  │ O QUE FAZER OUTDOOR              │
│ Qualidade de vida · Negócio  │  │ Esporte · Turismo · Patrimônio   │
│ ├─ Sidebar: filtros+personas │  │ ├─ Abas: Descobrir/Experiências/ │
│ ├─ Filtro por Estado (nav)   │  │ │        Agenda                   │
│ ├─ Grid de cards             │  │ ├─ Sidebar: Atividade/Patrim./   │
│ ├─ Scorecard: F1/F2/F3, 7 abas│ │ │   Destino/Clima (sem Quem viaja)│
│ │  F1=macro,hospit.,dash     │  │ ├─ Grid + marks (Fav/Fui/Quero) │
│ │  F2=negócio,comparativo    │  │ ├─ Modal rápido + avaliação 1–5 │
│ │  F3=prospecção(multi-imóvel│  │                                  │
│ │     ),ROI                  │  │                                  │
│ └─ Modal + Análise IA        │  │ └─ Experiências: 84 rotas (6 cat)│
│                              │  └──────────────────────────────────┘
└──────────────┬───────────────┘                │
               ↓ (read only)                     ↓ (../cities_data.js)
        ┌────────────────────────────────────────────────┐
        │ cities_data.js (~4MB) — fonte única            │
        │ var CITIES_DB = [{id, nome, uf, ...}, ...]    │
        ├────────────────────────────────────────────────┤
        │ manual.html → MANUAL_USUARIO*.md (4 idiomas)   │
        │ outdoor/manual.html → MANUAL_OUTDOOR*.md (6)   │
        └────────────────────────────────────────────────┘
```

> **Split (jun/2026):** Busco Rumo perdeu as abas Esporte/Turismo do scorecard e
> os filtros/tags outdoor (MTur, UNESCO, PARNA, Caminho da Fé, Estrada Real), que
> migraram para `outdoor/`. O outdoor renomeou o "Scorecard" para **Ficha do
> Destino** e ganhou marcadores **Favorito / Já fui / Quero ir** (chave
> `project_marks` no localStorage). Detalhes no CHANGELOG.
>
> **Reorg. outdoor (jun/2026 — v2.10):** a **Ficha do Destino** deixou de ser aba
> fixa (agora abre via card → "Ver Ficha do Destino →"); a barra de nav passou a
> ter **Descobrir · Experiências · Agenda**. Nova aba **🧭 Experiências**: base
> curada de **84 rotas** em 6 categorias (PARNA, Peregrinação, Rede de Trilhas,
> Estrada Real, Trilhas SP, Ciclorrotas SP) — `const EXPERIENCIAS=[…]` no
> `outdoor/index.html`, render por `expRender()`/`expFiltTipo()`. A sidebar perdeu
> "Quem viaja" e "Riscos climáticos". Link-irmão (`.topnav-sister`) entre os dois
> dashboards movido para baixo do título.
>
> **Remoção da Ficha do Destino (jun/2026 — v2.12):** a **Ficha do Destino**
> (scorecard embarcado no outdoor) foi **removida por completo** do
> `outdoor/index.html` — saíram o `view-scorecard`, a PONTE de dados
> (`OMID/CITY_META/ECON/CURATED_OMIDS`), `openScorecard`/`loadCity`/`renderCmp` e
> 322 chaves i18n `sc_*`/`cmp_*`. A nav ficou **Descobrir · Experiências · Agenda**.
> Os cards/modal agora trazem marcadores **Fav / Já fui / Quero ir / Quero ir de
> Novo** e os cards de Experiências/Agenda ganharam campo **Wikiloc** (rota
> realizada). **O scorecard do Busco Rumo (`index.html`) permanece intacto.**
> Resultado: −1975 linhas no `outdoor/index.html`.
>
> **Redesenho do Scorecard Fase 1 (jul/2026 — v2.23.0):** a Fase 1 deixou de
> misturar avaliação de negócio com encaixe pessoal — agora fecha na
> pergunta-síntese **"Eu viveria aqui para sempre?"** (campo `viveria_sempre`,
> nota 1–5 + `nota-viveria` livre), conduzida na lente de um consultor sênior de
> relocation. `Fase 1` = Macro (`CRIT.macro`, 9 critérios) + Hospitalidade
> (`SILVIA_CRIT`) + Dashboard (Ranking por `viveria_sempre`). `Fase 2` ganhou a
> aba Negócio inteira (antes Fase 1) + os blocos Custo do m²/Mercado
> imobiliário/Operação e gestão (antes dentro da Macro). `Clima`, `Altitude`
> (campo `alt`, novo) e `Âncora` (via `computeAnchor()`, haversine local por IDH
> vizinho) passaram a vir pré-preenchidos para as **5.570 cidades** do banco, não
> só as 12 curadas. Novo `scripts/enriquecer_altitude.py` no pipeline ETL (ver
> abaixo). Botão `buildCityLivePrompt()` monta o resumo da Fase 1 para pedir
> leitura ao Claude. Detalhes no CHANGELOG.
>
> **Scorecard em 3 fases + esporte preenchível + multi-imóvel (jul/2026 — v2.23.1→v2.23.3):**
> a barra de abas passou a ter três grupos: **FASE 1** (Macro · Hospitalidade ·
> Dashboard — passada macro por muitas cidades) · **FASE 2** (Negócio ·
> Comparativo — pesquisa de campo das sobreviventes) · **FASE 3** (Prospecção
> Imobiliária · ROI — 1-2 finalistas: imóvel + modelo de negócio + investimento).
> A **Prospecção** virou multi-imóvel: `d.imoveis[]` (cada um com link do
> anúncio, água/posição/energia-gás-lixo/notas via `curImovel()`) + tabela
> comparativa; `calcScore()` elimina a cidade só quando todos os imóveis estão
> sem fonte hídrica. A seção **Potencial esportivo/aventura** da Macro é
> **preenchível** (`renderEsporteSection()`/`toggleEsporteConf()`): tags editáveis
> da taxonomia do app Outdoor (`ESPORTE_LABEL` sincronizado com `SPORTS_LABEL_PT`
> de `outdoor/index.html`), semeadas de `c.esportes` e salvas em `esportes_conf`.
> `Clima`/`Altitude` viraram **fixos** (só leitura, sempre de `cities_data.js` —
> corrige bug de "sumir" após autosave); `Âncora` deixou de ter campo editável
> (usa só o profile card + `getAnchorInfo()`). Dashboard alinhado às abas reais:
> heatmap Macro/Mercado/Negócio/Hospitalidade. Código morto do split Outdoor
> (`toggleSport`/`setGuide`/`setIntensity`/`toggleS`/`toggleAtr`) removido.

        ↓ (regeneração + curadoria)
┌─────────────────────────────────────────────────────────┐
│        Python ETL + Curadoria (Offline)                  │
│  ETL (6):  gerar_cidades → geocode_ibge_malha (1b, lat/lon│
│            centroide de área via malha IBGE) →            │
│            aplicar_coordenada_bc250 (1c, sede urbana real │
│            via BC250 2025, corrige erro de dezenas de km  │
│            do centroide em municípios grandes/rurais) →   │
│            enriquecer_mc_munic →                          │
│            enriquecer_desmatamento → calcular_cresc_ibge │
│            → estimar_crescimento_todas → melhorar_clima  │
│            → enriquecer_altitude (6b, API Open-Elevation)│
│  CURADORIA (15 fix_*, v13→v24): praia/turística,         │
│            esportes, Picos, rafting, PARNAs, Wikiloc,    │
│            MTur v17→v23 (A-E), calor extremo, patch p.l. │
└─────────────────────────────────────────────────────────┘
```
> Os 15 passos `fix_*` rodam **por último, na ordem das versões v13→v24**, e devem
> ser TODOS reaplicados sempre que `gerar_cidades.py` regenerar a base. Alguns
> (`enriquecer_desmatamento`, `fix_mtur_v17..v23`) dependem de arquivos-fonte
> `*.xlsx` **não versionados**. `enriquecer_altitude` (passo 6b) também precisa ser
> rerodado após qualquer regeneração — bate numa API pública (Open-Elevation/SRTM) por
> `lat/lon`, não faz parte da curadoria `fix_*`. `aplicar_coordenada_bc250` (passo 1c)
> também precisa ser rerodado após regeneração — sem ele, `lat/lon` volta a ser o
> centroide de área (menos preciso) em vez da sede urbana oficial; o dado antigo fica
> preservado em `lat_centroide_ibge`/`lon_centroide_ibge` para auditoria. Pipeline
> completo, ordem e dependências em **MANUTENCAO.md**.

## Arquitetura AI-native (como este projeto é mantido)

**Decisão (2026-07-28):** este projeto é desenvolvido e mantido em colaboração
contínua com agentes de IA (não só como ferramenta de apoio pontual), e a
documentação existe para tornar isso sustentável — cada decisão precisa ser
recuperável por um agente sem contexto de conversa anterior. Isso já era
prática de fato (ver o padrão "Decisão (data)" repetido neste arquivo e em
CHANGELOG.md); passa a ser princípio explícito:

- **Decisão datada, não código morto silencioso.** Quando um pedido vira
  "projeto futuro" em vez de implementação imediata (ex.: Bússola Essencial,
  rearquitetura do Outdoor abaixo), a decisão e a razão ficam escritas aqui —
  nunca uma flag/schema/tela pela metade esperando retomada.
- **Fonte de verdade única por tema.** `cities_data.js` para dado, este
  arquivo para arquitetura, `CHANGELOG.md` para histórico datado,
  `POSICIONAMENTO.md` para estratégia de marca/produto — um agente entrando
  no meio do projeto deve conseguir reconstruir contexto lendo esses arquivos,
  sem depender de memória de conversa.
- **Dado sem proveniência é rotulado, nunca apagado silenciosamente nem
  apresentado como oficial** (ver Dívida de Verificação, logo abaixo) — um
  agente que não sabe a origem de um campo tem que conseguir descobrir isso
  lendo a documentação, não inferindo.
- **Histórico nunca é substituído, só acrescentado.** Atualizações de
  documentação consolidam informação nova nos arquivos principais existentes
  (este arquivo, CHANGELOG.md) preservando o que já foi decidido — nunca
  reescrevem ou apagam uma decisão anterior para "simplificar".

### Dívida de Verificação (registro consolidado)

**Decisão (2026-07-28):** os itens abaixo já estavam decididos/suspensos em
seções espalhadas deste arquivo e do CHANGELOG (Fase 2 — confiança de dados,
2026-07-27); passam a ter um registro único para não precisar garimpar em
vários lugares para saber "que dado aqui não é 100% verificado". Nenhum item
muda de status por entrar nesta lista — é consolidação, não nova decisão.

| Campo/área | Status | Por quê | Onde reativar |
|---|---|---|---|
| `det_homicidios`/`mtur_seguranca` | Suspenso, morto (nunca reaproveitado) | Sem fonte oficial com proveniência — substituído por `seguranca_*` (Atlas da Violência 2026), não reativado | Nunca — campo legado permanece só pra histórico |
| IDH-E (`estimar_idh()`) | Indicador interno estimado, nunca oficial | Não é IDHM do IBGE; precisa sempre do nome completo + rótulo de indicador derivado | Fase de dados socioeconômicos (`ec_idhm` oficial) |
| `crescimento_pct` (2022→2025) | Fora de qualquer score | É comparação censitária, não fato definitivo | Sem ETA — permanece fora de score por decisão de produto |
| Score `legacy.compatMap` ("XX% combina") | `authoritative:false` | Teto artificial de 99%/piso de 30%, lógica antiga | Fase de scoring do Busco Rumo |
| Boa Esperança do Norte (MT, 5101837) | Fora do catálogo | Só código/nome/população 2025 conhecidos, sem pipeline completo | Quando atravessar o pipeline sem campo inventado (`dados/cobertura_pendente.json`) |
| IDEB de ~300 municípios pequenos | Fallback da capital do estado | Sem dado municipal próprio no INEP | Ver princípio de fallback estadual, seção de segurança abaixo — mesmo padrão a generalizar |
| PIB municipal (exceto SP/RJ/MG) | Estimado proporcional à população | IBGE só publica granular pra esses 3 estados | Sem ETA |

## Stack Técnico

### Frontend (100% Client-side)
- **HTML5**: Estrutura semântica
- **CSS3**: Grid, Flexbox, Media Queries, CSS Variables
- **JavaScript Vanilla**: Sem frameworks (jQuery, React, Vue)
  - Manipulação DOM direto
  - Event listeners para filtros
  - Renderização dinâmica de grid
  - Integração com Claude API (modal IA)

**Por que Vanilla JS?**
- Aplicação é simples (não precisa de reatividade complexa)
- Reduz bundle size (~0KB de deps)
- Melhor performance em renderização
- Fácil debugging

### Backend (Python - Offline)
Scripts que rodamlocalmente para regenerar `cities_data.js`:
- **requests**: Chamadas a APIs IBGE/INEP
- **json**: Parse de dados
- Sem banco de dados (JSON como source of truth)

### Data Sources
1. **IBGE API** (localidades)
   - GET https://servicodados.ibge.gov.br/api/v1/localidades/municipios
   - Retorna: ID, nome, estado, latitude, longitude

2. **Dados Cadastrais** (Censo 2022)
   - População, área, densidade demográfica
   - Buscado por município

3. **IDH** (PNUD)
   - Obtido de bases IBGE/Censo
   - ~5500 municípios cobertos

4. **IDEB** (INEP)
   - Educação (2021)
   - Nem todos os municípios têm dados

5. **PIB** (IBGE)
   - PIB per capita por estado (2021)
   - Estimado por município proporcional à população

6. **MapBiomas Desmatamento**
   - Arquivo Excel: `mapbiomas_desmat_municipio.xlsx`
   - Processado para JSON: `desmat_por_municipio.json`

## Fluxo de Filtros

```javascript
// 1. Usuário interage (click, change)
→ evento dispara onchange="render()"

// 2. render() lê checkboxes e sliders
const CHECKED = getChecked()
const tipoF = CHECKED.filter(v => TIPOS_VALS.includes(v))
const idhMin = document.querySelector('[name=idh-range]').value

// 3. Filtra CITIES_DB em memória
let list = CITIES.filter(c => {
  if (c.idh < idhMin) return false
  if (tipoF.length && !tipoF.some(t => c.tipo.includes(t))) return false
  if (biomaF.length && !biomaF.includes(c.bioma)) return false
  // ... mais 5 filtros
  return true
})

// 4. Ordena (score, IDH, IDEB, PIB, pop, nome)
list.sort((a,b) => calcScore(b) - calcScore(a))

// 5. Renderiza grid HTML
grid.innerHTML = list.map(c => `<div class="card">${cardHTML(c)}</div>`).join('')
```

**Performance**: ~5500 cidades filtradas + ordenadas em <100ms (O(n log n))

## Sistema de Scoring

### Fórmula Base
```
score = IDH×40 + IDEB×28 + PIB×18 + Natureza×7 + Turismo×7
        ↓        ↓        ↓       ↓             ↓
       pesos    (todos max 100pts, depois normalizado)
```

### Normalização de Componentes
```javascript
s_idh  = (c.idh - 0.50) / 0.37       // IDH: 0.50–0.87 → 0–1
s_ideb = (c.ideb - 3) / 7            // IDEB: 3–10 → 0–1
s_pib  = c.pib / 80000               // PIB: 0–80k → 0–1
s_nat  = c.tipo.includes('montanha'||'praia') ? 1 : 0.3
s_tur  = c.tipo.includes('turistica') ? 1 : 0.2
```

### Bonus por Personas
Cada persona aplica multiplicadores:

| Persona | IDH | IDEB | PIB | Natureza | Turismo |
|---------|-----|------|-----|----------|---------|
| Aposentado | 1.2 | 0.4 | 0.4 | 0.8 | — |
| Remote | 0.8 | 0.4 | 1.5 | 0.6 | — |
| Empreendedor | 0.7 | 0.3 | 1.5 | — | 2.0 |
| Cap→Interior | 0.9 | 0.8 | 0.6 | 1.2 | — |
| Interior→Cap | 1.4 | 1.0 | 1.5 | 0.3 | — |
| Família | 1.0 | 2.0 | 0.8 | 0.5 | — |
| Solo | 1.0 | 0.5 | 0.9 | 1.0 | — |
| Segurança | 2.0 | 1.0 | 0.6 | 0.4 | — |
| Natureza | 0.6 | 0.3 | 0.4 | 2.5 | — |

```javascript
let bonus = 0
activePersonas.forEach(p => {
  const w = PERSONA_W[p]
  bonus += w.idh * s_idh * 5    // cada fator × 5
  bonus += w.ideb * s_ideb * 5
  // ...
})
finalScore = Math.min(99, baseScore + bonus / numPersonas)
```

**Objetivo**: Personas não dominam, mas rebalanceiam as prioridades

## Estrutura de Dados (cities_data.js)

```javascript
window.CITIES_DB = [
  {
    // Identidade
    id: "goncalves_mg",
    nome: "Gonçalves",
    uf: "MG",
    
    // Localização
    regiao: "Sudeste",
    bioma: "mata_atlantica",
    clima: "frio",
    
    // População
    pop: 1100,
    
    // Indicadores
    idh: 0.695,
    ideb: 5.2,
    pib: 15000,
    temp: 14,  // média anual
    temp_min: 8, temp_max: 24,  // usados no pré-preenchimento de Clima do Scorecard
    alt: 1100, alt_fonte: "Open-Elevation (SRTM)",  // altitude (m) — todas as 5.570 cidades
    
    // Risco ambiental (uso interno do filtro Qualidade do Ar; sem UI própria)
    mc: "neutro",
    desmat_ha_ano: 0,
    
    // Classificações
    tipo: ["montanha", "turistica", "interior"],
    esportes: ["trekking", "escalada", "cicloturismo"],
    
    // Notas (insight do card, 4 idiomas — gerado por scripts/gen_notas.py)
    nota: "Município serrano no sul da Mantiqueira, ~1.100m de altitude...",
    nota_en: "...", nota_es: "...", nota_zh: "..."
  },
  // ... 5500+ cidades
]
```

## Ciclo de Atualização de Dados

### Mensal (1 tarde por semana)
```
Quinta 14h-17h
│
├─ Check: Há novos dados IBGE?
│  └─ python scripts/gerar_cidades.py
│     ├─ GET /api/localidades → ~5570 municípios
│     ├─ GET /api/municipios/{id} → pop, IDH, dados Censo
│     └─ OUTPUT: cities_data.js (versão nova)
│
├─ Check: Há novo desmatamento MapBiomas?
│  └─ python scripts/enriquecer_desmatamento.py
│     ├─ READ: mapbiomas_desmat_municipio.xlsx
│     ├─ PARSE: ha/ano por município
│     └─ MERGE: em cities_data.js
│
├─ QA: Validação
│  ├─ Conferir scores de 10 cidades aleatórias
│  ├─ Testar grid com 1000+ cidades
│  ├─ Abrir 5 modais sem lag
│  └─ Mobile: testar em iPhone 6
│
└─ Deploy: Commit + reload do navegador
```

## Responsividade

### Breakpoints
```css
/* Desktop: 2 colunas + sidebar (270px) */
@media (min-width: 1200px) {
  .layout { grid-template-columns: 270px 1fr; }
  .grid { grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); }
}

/* Tablet: 1 coluna, sidebar acima */
@media (max-width: 680px) {
  .layout { grid-template-columns: 1fr; }
  .sidebar { position: relative; border-bottom: 1px solid #e0e0da; }
  .hdr-stats { display: none; }
}
```

### Performance em Mobile
- Sidebar colapsável (compactado por padrão)
- Cards: 1 coluna em mobile, 2+ em tablet+
- Modal: Full-height, scrollável
- Sem animações complexas (reduz CPU)

## Integração com Claude API

### Quando: Modal IA
```javascript
// Clique em "Analisar com IA" → analisarIA()
async function analisarIA() {
  const city = cityModal
  const personas = [...activePersonas].join(' + ')
  
  // TODO: POST /api/analyze
  const response = await fetch('/api/analyze', {
    method: 'POST',
    body: JSON.stringify({ city, personas })
  })
  
  const analysis = await response.json()
  document.getElementById('m-ia').innerHTML = analysis.text
}
```

**Status**: Placeholder estático (pronto para API real)

## Segurança

### Input Validation
- Filtros: Whitelist de valores conhecidos (TIPOS_VALS, etc.)
- Sliders: Range mínimo/máximo garantido (min=0.5, max=0.86)
- Sem SQL (JSON em memória)
- XSS: Renderização com `textContent` (não `innerHTML`)

### Data Privacy
- Sem cookies
- Sem analytics
- Sem storage de preferências (filtros perdidos ao reload)
- HTTPS recomendado para análise IA

## Debugging

### Console útil
```javascript
// Ver toda base de dados
console.table(window.CITIES_DB)

// Encontrar uma cidade
window.CITIES_DB.find(c => c.nome.includes('São Paulo'))

// Score de uma cidade
calcScore(CITIES_DB.find(c => c.id === 'sao_paulo_sp'))

// Listar todas as personas ativas
console.log([...activePersonas])

// Forçar re-render
render()
```

### Performance Profiling
```javascript
// Medir tempo de filtro
console.time('filter+sort')
render()
console.timeEnd('filter+sort')

// DevTools > Performance > Record → scroll + filter
```

## Limitações Conhecidas

1. **Biomas por UF**: Cada estado tem 1 bioma único (simplificado)
   - MG: Mata Atlântica (não cobre Cerrado de ~40% do estado)
   - SP: Mata Atlântica (não cobre Cerrado do interior)

2. **PIB por município**: Estimado proporcional à pop (não real)
   - Apenas SP, RJ, MG têm PIB granular no IBGE

3. **IDEB fallback**: ~300 municípios pequenos usam IDEB da capital

4. **Desmatamento**: Apenas 11 municípios com dados brutos (MapBiomas)
   - Outros calculados por bioma/regiao

5. **Climate data**: Temperatura e clima são médias por estado (não por município)

## Roadmap

- [ ] Dados em tempo real (atualização automática IBGE)
- [ ] Gráficos (PIB histórico, desmatamento trend)
- [ ] Exportar favoritos (JSON/CSV)
- [ ] Integração com Claude API real (não mock)
- [x] Modo escuro (toggle ☀️/🌙, persistido em `localStorage('buscoTheme')`)
- [ ] PWA (offline mode)
- [x] Multi-idioma — PT/EN/ES/ZH (i18n inline no dash + manuais; ver [RUNBOOK_IDIOMAS.md](RUNBOOK_IDIOMAS.md))
- [ ] Fase de scoring do Busco Rumo: corrigir o teto artificial de 99%/piso de 30% em `legacy.compatMap` (score "XX% combina com você") — hoje marcado `authoritative:false`, não deve virar score definitivo até essa fase recalcular com lógica própria (ver `bussola.html`/`recommendCities`, correção 2026-07-27)
- [ ] Fase de dados socioeconômicos: anexar IDHM oficial por município (`ec_idhm` + fonte + ano) — hoje só existe o indicador estimado interno (IDH-E, `estimar_idh()` em `scripts/gerar_cidades.py`), nunca deve ser citado como oficial
- [x] Fase de segurança (2026-07-28): `seguranca_*` via Atlas da Violência 2026 (Ipea/FBSP), fonte oficial com proveniência, fallback estadual, tendência (queda/estável/alta) — reativa badge "Refúgio Seguro"/sort no Dicas Outdoor e dimensão `violencia_letal` no motor de compatibilidade do Busco Rumo. `det_homicidios`/`mtur_seguranca` continuam mortos, nunca reaproveitados (ver Fase 2 no CHANGELOG e seção própria abaixo)
- [x] Saneamento (2026-07-28): `saneamento_*` via SINISA 2024 (Ministério das Cidades), só no Busco Rumo (fator base sempre aplicado no score) — campo 100% novo, mesmo padrão de fallback estadual da segurança
- [x] Desemprego removido por completo do Dicas Outdoor (`desemprego_pct` deixou de ser lido — campo continua no schema, só não é mais consumido por esse app)
- [ ] Rearquitetura do Dicas Outdoor — separação de entidades (trilha/praia/ilha/templo/parque) + redesenho do score "82 QUALIDADE" (ver seção própria abaixo)
- [x] Registro formal do modelo de três camadas (Busco Rumo=decisão, Dicas Outdoor=exploração, ExploreBR=memória geográfica) e da arquitetura AI-native de manutenção do projeto (ver seções acima, 2026-07-28)
- [x] Generalizar o princípio de fallback estadual (hoje implícito só no IDEB): `seguranca_status`/`saneamento_status` seguem o mesmo enum `oficial|fallback_regional`, gravado direto pelos scripts de download (sem stage de proveniência separado)

### Bússola Essencial — onboarding do aplicativo (projeto futuro, fora do escopo web atual)

**Decisão (2026-07-27):** o item 3.6 da Fase 3 (versão reduzida da Bússola de
Mudança) foi retirado do escopo da versão web atual e não é mais pendência
de `bussola.html`. Passa a ser um projeto futuro, associado à etapa de
transformação do ecossistema em aplicativo Android/iOS. A versão completa
atual da Bússola permanece como experiência oficial na web — nenhum
questionário reduzido, modo Essencial/Completo, novo schema de respostas,
avatar alternativo, tela de transição ou feature flag foi criado em
preparação a isso (evita código morto para uma decisão que ainda depende
do desenho do aplicativo).

**Objetivo:** criar uma versão curta da Bússola para funcionar como
onboarding inicial do futuro aplicativo — poucas perguntas, uma primeira
direção, convite explícito para aprofundar a análise depois (na versão
completa ou no Busco Rumo).

**Pré-requisitos** (nenhum resolvido ainda):
- definição do produto pago do aplicativo
- arquitetura Android/iOS
- estratégia de conta e sincronização
- pesquisa com usuários
- medição do abandono do questionário completo
- definição de quais respostas são indispensáveis
- política de dados pessoais
- funcionamento offline
- continuidade entre celular e web
- revisão do papel de gênero, idade e avatar
- validação de que o resultado reduzido continua honesto
- integração com Busco Rumo e Dicas Outdoor

**Princípios obrigatórios quando este projeto for retomado:**
- não preencher perguntas omitidas com respostas neutras
- não apresentar resultado reduzido como diagnóstico completo
- mostrar confiança proporcional ao que foi respondido
- preservar a possibilidade de aprofundamento
- não transmitir gênero ou idade quando forem apenas visuais
- não forçar cidades quando não houver evidência suficiente
- compartilhar a mesma lógica territorial e os mesmos dados confiáveis dos
  produtos web (`politicaTerritorialInterior`, `recommendCities`, fatos
  compartilhados) — nunca uma segunda lógica de recomendação

### Dicas Outdoor — separação de entidades + redesenho do score (projeto futuro, fora do escopo dos fixes de 2026-07-28)

**Decisão (2026-07-28):** o pedido de tratar trilha/praia/ilha/templo/parque como
entidades próprias (card e detalhe dedicados por tipo, com badges e regras de
avaliação específicas por categoria) **não é mais pendência do card de cidade
atual** em `outdoor/index.html`. Vira projeto futuro de rearquitetura, com sessão
dedicada própria. Os problemas pontuais relatados no card de cidade (texto de
qualidade-de-vida vazando via `c.nota`, crescimento populacional exibido, escala
de avaliação de 5 estrelas, links "Manual"/"❓" soltos, praia/ilha sem link de
mapa exato) já foram corrigidos nesta rodada — ver CHANGELOG.md.

**O que continua igual, por decisão explícita:** hoje **não existe** card por
destino individual — tudo (inclusive praias, ilhas, trilhas) é exibido a partir
do card/modal da **cidade** (`render()` e `openModal()` em `outdoor/index.html`).
Essa limitação de modelo de dados permanece até a rearquitetura.

**Objetivo:** cada tipo de destino (trilha, praia, ilha, templo/patrimônio,
parque/cachoeira/pico) passa a ter modelo de dados, card e tela de detalhe
próprios — sem herdar campo nenhum pensado para "onde morar" (IDH, IDEB, PIB,
renda, desemprego, saúde municipal, crescimento populacional).

**Inclui obrigatoriamente:** redesenho do score "82 QUALIDADE" do card — hoje
`calcScore()` (`outdoor/index.html`) usa 68% do peso em IDH+IDEB+PIB (mais 14%
natureza/turismo), e o próprio texto do modal chama esse número de "score de
qualidade de vida" (`m_why_score`, ainda presente porque a seção `#m-score-sec`
fica sempre oculta no Outdoor — código morto herdado do Busco Rumo, nunca
chamado). Não é um ajuste de rótulo: exige definir o que "qualidade" deve
significar para um destino outdoor (natureza, infraestrutura, acesso, avaliação
de visitantes?) antes de recalcular pesos.

**Pré-requisitos** (nenhum resolvido ainda):
- schema de dados por tipo de entidade (campos obrigatórios/opcionais por
  categoria: distância/duração/dificuldade para trilha; acesso/maré/autorização
  para praia e ilha; horário/reserva para templo e parque)
- decisão de produto sobre o que compõe o score outdoor e seus pesos
- fonte e proveniência de cada novo campo (sem inventar valor onde não existir)
- cobertura de testes automatizados para o Outdoor (hoje `scripts/test_battery.py`
  cobre Busco Rumo/i18n; renderização de card/modal do Outdoor não tem asserção
  automatizada, só checklist manual em `TESTES.md`)

**Princípios obrigatórios quando este projeto for retomado:**
- nenhum campo de moradia (IDH/IDEB/PIB/renda/desemprego/saúde municipal)
  aparece em card ou detalhe de destino outdoor
- nenhum badge aparece sem dado real por trás (sem inventar valor)
- todo link de mapa aponta para coordenada exata do destino, nunca só a cidade
- toda avaliação exibida diz se é pessoal, editorial ou de visitantes — nunca
  estrela sem explicar o critério
- nenhum ícone ou rótulo ("?", "Manual" etc.) sem legenda visível

### Fase de segurança + saneamento — CONCLUÍDA (2026-07-28)

**Decisão (2026-07-28):** `det_homicidios`/`mtur_seguranca`/"Refúgio Seguro"
continuam **suspensos e mortos** (Fase 2, 2026-07-27) — nunca foram
reaproveitados. A reativação usa campos **novos**: `seguranca_*` (Atlas da
Violência 2026) e `saneamento_*` (SINISA 2024, campo 100% novo).

**Fontes usadas:**
- **Atlas da Violência 2026** (Ipea/FBSP) — série municipal de homicídios,
  API pública `ipea.gov.br/dados-api/series-values/20/{4,3}` (município e
  estado). Cobertura direta: 5.530/5.570 municípios (99,3%); os outros 40 com
  fallback estadual (mesma API, nível estado).
- **SINISA 2024** (Ministério das Cidades) — consolidado por página pública
  do Instituto Água e Saneamento (`aguaesaneamento.org.br`), um índice por
  município (água+esgoto+resíduos).
- **Anuário Brasileiro de Segurança Pública (FBSP)** e o PDF do relatório
  completo do Atlas 2026 foram avaliados como fonte, mas o dado realmente
  usado é a série numérica da API acima (o relatório é análise textual, não
  dataset municipal baixável) — citado aqui pra registro, não é lido pelo
  pipeline.

**Fallback estadual (generalizado, mesmo princípio do IDEB):** quando o
município não tem série própria, usa-se o dado do **estado** (nunca valor
municipal inventado). Ambos os campos usam o enum `seguranca_status`/
`saneamento_status` = `'oficial'|'fallback_regional'`, gravado direto pelos
scripts de download (`scripts/baixar_seguranca_atlas_violencia.py`,
`scripts/baixar_saneamento_sinisa.py`) — sem precisar de um stage de
proveniência separado, ao contrário do IDEB/PIB legados. O fallback aparece
marcado como tal na UI (caveat em `bussola.html`, nunca disfarçado de dado
municipal direto).

**Tendência, não só valor estático:** `seguranca_tendencia`
(`'queda'|'estavel'|'alta'`) compara a média dos últimos 3 anos disponíveis
com os 3 anteriores — a badge "Refúgio Seguro" do Dicas Outdoor exige
`tendencia !== 'alta'` além de taxa baixa, nunca ativa só por um valor
pontual (ver `outdoor/index.html`, `isRefugioSeguro`).

**Onde entra no produto:**
- **Busco Rumo**: fator base sempre aplicado em `recommendCities()`
  (`bussola.html` — toda cidade recebe bônus/penalidade, com ou sem a tag
  "Segurança" escolhida) + dimensão `violencia_letal` no motor de
  compatibilidade (`index.html`, `compatScoreDetalhado`/`COMPAT_DIMENSIONS`).
  Saneamento só entra como fator base (sem persona própria).
- **Dicas Outdoor**: badge "Refúgio Seguro"/"Guardião da Segurança" e
  ordenação por segurança (dropdown). Saneamento **não** aparece aqui —
  escopo do usuário foi só Busco Rumo pra esse campo.
- Desemprego foi removido por completo do Dicas Outdoor no mesmo trabalho
  (badge/sort não tinham relação com segurança/saneamento, mas o usuário
  pediu a limpeza junto).
- mesma regra de proveniência da Dívida de Verificação: dado sem origem
  rastreável não entra no produto

### Fase de Qualidade — integração de segurança + saneamento ao score "Qualidade" (2026-07-28)

**Auditoria feita antes de implementar:** os 3 apps têm scores DIFERENTES,
sem sobreposição:
1. `calcScore()`/`scoreComponents()` — "Qualidade" (0–100), duplicado em
   `index.html` e `outdoor/index.html`, mostrado em TODO card de cidade
   (`card_qualidade`). Não lia segurança/saneamento antes desta fase.
2. `compatScoreDetalhado()`/`COMPAT_DIMENSIONS` — "Compatibilidade"
   (`card_compat`, só em `index.html`), preferência PESSOAL que só pontua
   quando o usuário ativa uma persona. Já tinha `violencia_letal`
   (segurança) desde a reativação anterior desta mesma fase.
3. `recommendCities()` (`bussola.html`) — motor de recomendação da
   Bússola, sem número "Qualidade"; segurança/saneamento já entravam como
   fator base sempre aplicado.

**Decisão:** integrar segurança+saneamento na "Qualidade" (score 1) — o
que faltava — sem tocar nos scores 2 e 3, que já tratavam o assunto em
suas próprias camadas. Ver fórmula completa, pesos e normalização em
`DADOS.md` ("Qualidade: fórmula final..."). Resumo:

- **Busco Rumo**: 7 componentes (IDH-E 34, IDEB 24, PIB 15, Natureza 6,
  Turismo 6, **Segurança pública 8**, **Saneamento 7**) = 100.
- **Dicas Outdoor**: 6 componentes, sem saneamento (fora de escopo pra
  este app) — IDH-E 37, IDEB 26, PIB 16, Natureza 6, Turismo 6,
  **Segurança pública 9** = 100.
- Fallback estadual (`*_status==='fallback_regional'`) entra no cálculo
  mas AMORTECIDO (50% da distância até o neutro 0.5) — nunca em pé de
  igualdade com dado oficial, nunca zerado. Marcado como "(estadual)" no
  breakdown e com confiança rebaixada na proveniência.
- `PERSONA_W.seguranca` (bônus de "Qualidade" quando a persona "Segurança"
  está ativa) foi CORRIGIDO: usava IDH-E/IDEB/PIB/Natureza como proxy de
  criminalidade (o erro que a Fase 2 já proibia no texto) — agora bonifica
  só o componente `s_seguranca` real, sem duplicar peso.
- `proveniencia_shared.js` ganhou entradas `seguranca`/`saneamento` no
  `REGISTRY`. **Atualização 2026-07-29**: só o modal do Busco Rumo
  (`index.html`) chama `ProveShared.render('seguranca'/'saneamento', ...)`
  — o do Dicas Outdoor parou de chamar (ver subseção de UI abaixo).
- `bussola.html` não ganhou um segundo score — decisão documentada em
  comentário no próprio arquivo (busca por "DECISÃO ARQUITETURAL" no
  bloco de `recommendCities`).
- Nenhuma mudança em `det_homicidios`/`mtur_seguranca`/desemprego —
  continuam mortos/removidos, confirmado por teste.

### UI dos cards — segurança/saneamento visíveis, layout consistente (2026-07-29)

Depois de entrar no cálculo, os dois campos precisavam aparecer na
interface — e o card interno do Dicas Outdoor precisava de mais espaço
pras atividades outdoor (seu conteúdo principal), não pra indicadores que
já pertencem ao Busco Rumo.

- **Busco Rumo (`index.html`)**: bioma saiu da linha isolada (`.c-bio`) e
  virou chip (🗺️) adjacente ao chip de Cobertura vegetal (🌳) na faixa de
  sinais do card (`_sig`) — mesma faixa, ordem fixa. Chips de **Segurança
  pública** (🛡️) e **Saneamento** (🚰) entraram na mesma faixa, com `sev`
  (`ok`/`warn`/`bad`) computado pelos MESMOS limiares do score de
  Qualidade (`<=10`/`<=25`/`>25` para taxa de homicídios; `>=70`/`>=40`
  para índice de saneamento) — cor do chip e peso no score nunca
  divergem. Limite de chips exibidos (`_sig.slice`) subiu de 6 para 8.
- **Dicas Outdoor (`outdoor/index.html`)**: ganhou o mesmo indicador de
  Segurança (🛡️, com cor por severidade e `*` de fallback) na linha
  `.c-bio` do card, ao lado de bioma/cobertura. Saneamento continua fora
  daqui (fora de escopo deste app).
- **Alinhamento do rodapé do card (Dicas Outdoor)**: `.busco-card` virou
  `display:flex;flex-direction:column` e `.card-marks` ganhou
  `margin-top:auto` — a fileira de botões (Fav/Já fui/Quero ir/de Novo)
  sempre fica na base do card, alinhada entre cards com quantidades
  diferentes de tags/trilhas na mesma linha da grade (CSS Grid já
  equaliza a altura dos cards de uma linha; o flex empurra os botões pro
  fim dessa altura).
- **Modal interno do Dicas Outdoor simplificado**: removidos IDH-E,
  Crescimento populacional e Segurança pública do `#m-provenance`
  (deixa de chamar `ProveShared.render` pra esses 3) e o box "Segurança:
  Indisponível" órfão em `#m-mets` (ficou lá desde a Fase 2, nunca
  atualizado quando `seguranca_*` foi reativado) — esse conteúdo
  estrutural é do Busco Rumo; o card interno do Outdoor foca em
  atividades outdoor. **Os dados continuam intactos**: `seguranca_*`
  segue alimentando `calcScore`, `isRefugioSeguro` e o sort — só parou de
  ocupar espaço visual nessa seção específica do modal.

---

**Last updated**: 2026-07-29
