# Mudanças Painel ↔ Gerador XML (xmlcolibex)

Documento de referência para atualizar o script/gerador que produz os arquivos estáticos em `https://colibex.pro/xml/{hash}.xml`.

**Data:** junho/2026  
**Projetos:** `colibexpainel` (PHP) + `xmlcolibex` (Go)

---

## 1. Visão geral

Foram feitas **duas frentes** de mudança:

| Frente | Objetivo |
|--------|----------|
| **A. Regeneração por alteração no painel** | Qualquer mudança que o cliente faz no XML no painel deve disparar nova geração do arquivo estático, **no mesmo dia**. |
| **B. Feed automático (modo global)** | Novos XMLs podem usar filtros dinâmicos (estado, cidade, transação, categoria) em vez de marcar imóveis um a um. Integrações antigas continuam em modo manual. |

O gerador Go (`gerador-xml`) é a fonte de verdade para os XML em disco. O painel **não** chama o gerador diretamente; ele apenas atualiza o banco. O cron no servidor detecta o que precisa regerar.

---

## 2. Banco de dados (obrigatório antes do deploy)

Migration: `colibexpainel/sql/migrations/20260612_xml_feed_global_filtros.sql`

### 2.1 Coluna `xml_config.modo_feed`

```sql
modo_feed ENUM('manual','global') NOT NULL DEFAULT 'manual'
```

| Valor | Significado |
|-------|-------------|
| `manual` | Comportamento legado: imóveis vêm de `xml_imoveis` + flags `send_all*`. **Todas as integrações existentes.** |
| `global` | Feed automático: imóveis filtrados por `xml_config_filtros`. **XMLs novos no painel.** |

### 2.2 Tabela `xml_config_filtros`

```sql
PRIMARY KEY (id_xml_config, escopo, dimensao, valor)
```

| Campo | Valores |
|-------|---------|
| `escopo` | `todos`, `caixa`, `particular` |
| `dimensao` | `estado`, `cidade`, `transacao`, `categoria` |
| `valor` | Texto do filtro. Cidade salva como `Cidade::UF` (ex.: `Campinas::SP`) |

**Regra importante:** sem linhas para um escopo/dimensão = **sem restrição** (equivale a “tudo marcado” no painel).

### 2.3 Campo já existente: `xml_config.data_alteracao`

Usado como **sinal de regeneração**. O painel atualiza em:

- Autosave (salvar configurações)
- Marcar/desmarcar imóveis
- Filtros globais (engrenagem)
- Destaque, lote, flags `send_all*`
- Criar/editar integração

---

## 3. Frente A — Quando regenerar o XML

### 3.1 Como era (ANTES)

```
┌─────────────────────────────────────────────────────────┐
│ Decisão baseada APENAS no mtime de xml/{hash}.xml       │
├─────────────────────────────────────────────────────────┤
│ Arquivo não existe        → GERA                         │
│ Arquivo de dia anterior   → GERA                         │
│ Arquivo gerado HOJE       → IGNORA (mesmo se painel mudou)│
└─────────────────────────────────────────────────────────┘

executar-novos (cron/minuto): só feeds SEM arquivo
executar-tudo (cron/diário): novos + desatualizados (dia anterior)
```

`data_alteracao` só aparecia no `<PublishDate>` do XML, **não** influenciava regeneração.

### 3.2 Como ficou (AGORA)

```
┌─────────────────────────────────────────────────────────┐
│ Decisão: mtime do arquivo + xml_config.data_alteracao   │
├─────────────────────────────────────────────────────────┤
│ Arquivo não existe                              → GERA  │
│ data_alteracao > mtime do arquivo               → GERA  │
│ Arquivo de dia anterior (e config não mudou)    → GERA  │
│ Gerado hoje E data_alteracao ≤ mtime            → IGNORA│
└─────────────────────────────────────────────────────────┘
```

**Comparação:** `data_alteracao` e mtime truncados ao segundo, no fuso `GO_XML_TZ` (padrão `America/Sao_Paulo`).

### 3.3 Prioridades na fila (código: `internal/geracao/diaria.go`)

| Prioridade | Constante | Significado |
|------------|-----------|-------------|
| 0 | `PriGerarSemArquivo` | Sem `{hash}.xml` |
| 1 | `PriGerarConfigAlterada` | Painel alterou após último XML |
| 2 | `PriGerarDesatualizado` | XML de dia calendário anterior |
| 3 | `PriIgnorarHoje` | Atualizado hoje, config estável |

### 3.4 Comandos do gerador

| Comando | Comportamento atualizado |
|---------|--------------------------|
| `executar-novos` | **Cron a cada minuto.** Gera prioridade 0 **e** 1 (sem XML + alterados no painel). Não reprocessa feeds “só velhos de ontem” (isso fica no diário). |
| `executar-tudo` | Incremental completo: prioridades 0, 1 e 2. |
| `executar-tudo --force` | Regenera todos, ignora política. |
| `executar-feed --hash=XXX` | Um feed, sempre regenera. |

**Crontab recomendado (dedicado — resposta em ~1 minuto):**

```cron
* * * * * .../bin/gerador-xml executar-novos >> .../cron-xml-novos.log 2>&1
0 3 * * * .../bin/gerador-xml executar-tudo >> .../cron-xml-diario.log 2>&1
```

**Alternativa: reaproveitar crons existentes (sem cron a cada minuto):**

Se você já roda scripts Go no servidor (ex.: `run_go_sync_cron.sh`, `run_score_cron.sh`), pode chamar o gerador **dentro deles** em vez de criar linha nova no crontab. Ver seção **10** abaixo.

### 3.5 Fluxo completo (alteração no painel)

```mermaid
sequenceDiagram
    participant Cliente
    participant Painel
    participant MySQL
    participant Cron
    participant Gerador
    participant Disco

    Cliente->>Painel: Salva XML / filtros / imóveis
    Painel->>MySQL: data_alteracao = NOW()
    Cron->>Gerador: executar-novos (1/min)
    Gerador->>MySQL: Lista feeds ativos + data_alteracao
    Gerador->>Disco: Stat xml/{hash}.xml (mtime)
    alt data_alteracao > mtime
        Gerador->>Disco: Regenera {hash}.xml
    else já atualizado
        Gerador-->>Cron: Ignora
    end
```

### 3.6 Implementação no Go (arquivos alterados)

- `internal/geracao/diaria.go` — `DecidirGeracao`, `PrioridadeGeracao`, `configAlteradaAposXML`
- `internal/geracao/registro.go` — `InfoPrecisaGerar` recebe `dataAlteracao`
- `internal/pipeline/executar_tudo.go` — passa `feed.DataAlteracao` para decisão e filas
- `internal/consulta/feeds.go` — já lê `data_alteracao` na query

---

## 4. Frente B — Modo feed global (exportação de imóveis)

### 4.1 Como era (ANTES)

O gerador Go **sempre** usava lógica manual:

1. Buscar IDs em `xml_imoveis`
2. Montar cláusula com `send_all`, `send_all_caixa`, `send_all_particular` e `IN (ids)`
3. Aplicar filtro legado `estado_xml` quando preenchido

Não lia `modo_feed` nem `xml_config_filtros`.

### 4.2 Como ficou (AGORA)

```
SE modo_feed = 'global':
    IGNORAR xml_imoveis
    Montar WHERE dinâmico a partir de xml_config_filtros
    IGNORAR estado_xml legado
    Respeitar send_all / send_all_caixa / send_all_particular como ESCOPOS

SE modo_feed = 'manual' (default):
    Comportamento idêntico ao anterior
```

### 4.3 Escopos ativos (`send_all*`)

| Flags | Escopos usados na exportação |
|-------|------------------------------|
| `send_all = 1` | só `todos` |
| `send_all_caixa = 1` | `caixa` |
| `send_all_particular = 1` | `particular` |
| caixa + particular (sem send_all) | OR entre os dois blocos |
| nenhum | `AND 1=0` (feed vazio) |

Cada escopo pode ter filtros próprios em `xml_config_filtros`.

### 4.4 Montagem da cláusula SQL (espelho do PHP)

Para cada escopo ativo:

1. **ref_caixa:** `caixa` → `ref_caixa <> ''`; `particular` → `ref_caixa = ''`; `todos` → sem filtro
2. **estado:** `AND i.estado IN (...)` se houver valores salvos
3. **transacao:** `AND i.transacao IN (...)`
4. **categoria:** `AND categorias.nome IN (...)` (JOIN já existe)
5. **cidade:** `Cidade::UF` → `(TRIM(i.cidade)=X AND TRIM(i.estado)=Y)`; nome simples → `IN (...)`

Múltiplos escopos → `(bloco_caixa) OR (bloco_particular)`.

**Limite:** se `limite_xml >= 1`, aplica `LIMIT N` (sem `ORDER BY RAND()` no global — igual ao PHP `montarExportacaoGlobal`).

### 4.5 Implementação no Go (arquivos novos/alterados)

- `internal/consulta/tipos.go` — campo `ModoFeed`
- `internal/consulta/feeds.go` — `SELECT ... modo_feed`
- `internal/consulta/filtros_globais.go` — lógica de filtros (novo)
- `internal/consulta/imoveis_portal.go` — modo global antes dos casos especiais de portal
- `internal/consulta/imoveis.go` — `MontarClausulaFeed` inalterado para manual

**Nota:** em modo global, portais com lógica especial (Chave na Mão, ImóvelWeb vínculo) usam a mesma cláusula global — igual aos `Portal_*.php` que passaram a usar `xmlMontarExportacaoComModoFeed`.

### 4.6 Sync automático de imóveis (`SyncXmlImoveisSendAll`)

No painel PHP, o comando que popula `xml_imoveis` em massa **só roda** se `modo_feed = 'manual'`. Feeds globais não devem depender de `xml_imoveis`.

---

## 5. Checklist de deploy do gerador

1. **Banco:** migration `20260612_xml_feed_global_filtros.sql` aplicada (produção já confirmada).
2. **Atualizar o CÓDIGO no servidor** (só `build.sh` não basta se o fonte estiver velho):

```bash
# Opção A — servidor já é clone git:
cd /home/colibex/public_html
git pull origin main
ls internal/consulta/filtros_globais.go   # deve existir
grep modo_feed internal/consulta/feeds.go # deve aparecer

# Opção B — do Mac (rsync + build):
cd /caminho/xmlcolibex
SERVIDOR=root@IP_DO_SERVIDOR ./scripts/deploy-servidor.sh
```

3. **Compilar no servidor Linux** (não copiar binário do Mac):

```bash
cd /home/colibex/public_html
bash ./scripts/build.sh
./bin/gerador-xml versao
# deve: gerador-xml 20260612-data-alteracao-modo-global
```

3. **Testar diagnóstico:**

```bash
./bin/gerador-xml executar-novos --dry-run
./bin/gerador-xml executar-feed --hash=HASH_DO_CLIENTE
```

4. **Verificar log:** mensagens `feeds alterados no painel` na fila do `executar-novos`.

5. **Painel PHP/JS:** deploy das alterações em `Xml.php`, helpers e tela de administração XML.

---

## 6. Tabela resumo: antes × depois

| Aspecto | Antes | Depois |
|---------|-------|--------|
| Sinal de “precisa regerar” | Só mtime do `.xml` | mtime **ou** `data_alteracao > mtime` |
| Cron `executar-novos` | Só feeds sem arquivo | Sem arquivo **+** alterados no painel |
| Alteração no painel no mesmo dia | Ignorada até amanhã | Regerada em ~1 minuto (cron) |
| `data_alteracao` | Só `<PublishDate>` | PublishDate **+** gatilho de regeneração |
| `modo_feed` | Não existia no gerador | `manual` / `global` |
| Seleção de imóveis (global) | N/A | `xml_config_filtros` |
| Seleção de imóveis (manual) | `xml_imoveis` | Igual (sem mudança) |
| `estado_xml` | Filtro fixo | Ignorado em modo global |
| XML novo no painel | — | Default `modo_feed = global` |
| XML existente | — | Permanece `manual` |

---

## 7. Casos de teste sugeridos

### Regeneração

1. Gerar XML de um feed (`executar-feed --hash=X`).
2. No painel, alterar nome ou toggle e salvar (autosave).
3. Rodar `executar-novos --dry-run` → feed deve aparecer como “alterado no painel”.
4. Rodar `executar-novos` → arquivo regerado; `data_alteracao` no XML atualizado.

### Modo global

1. Criar XML novo (default global).
2. Marcar só SP no escopo `todos`.
3. Salvar filtros → `data_alteracao` atualiza.
4. Gerar feed → XML contém só imóveis ativos em SP.
5. Feed manual antigo → continua usando `xml_imoveis` sem ler filtros.

---

## 8. Referência rápida de arquivos

### colibexpainel

| Arquivo | Papel |
|---------|-------|
| `sql/migrations/20260612_xml_feed_global_filtros.sql` | Schema |
| `app/Helpers/XmlFeedFiltrosHelper.php` | Filtros globais (referência PHP) |
| `app/Controllers/Xml.php` | API + `touchXmlConfigUltimaAlteracao` |
| `app/Controllers/Controller.php` | `xmlMontarExportacaoComModoFeed` |

### xmlcolibex

| Arquivo | Papel |
|---------|-------|
| `internal/geracao/diaria.go` | Política de regeneração |
| `internal/pipeline/executar_tudo.go` | Pipeline e filas |
| `internal/consulta/filtros_globais.go` | Exportação modo global |
| `internal/consulta/feeds.go` | Query de feeds |
| `cmd/gerador-xml/main.go` | CLI |

---

## 10. Integrar nos crons que você já tem

Exemplo real de crontab:

```cron
0 20 * * * /home/colimob/public_html/run_go_sync_cron.sh >> .../cron_go_sync.log 2>&1
0 7,20 * * * /home/colimob/public_html/run_score_cron.sh >> .../cron_score.log 2>&1
```

### Qual comando usar em cada script?

| Comando | O que processa | Peso |
|---------|----------------|------|
| `executar-novos` | Feeds **sem** XML + **alterados no painel** (`data_alteracao > mtime`) | Leve — ideal para encaixar em cron existente |
| `executar-tudo` | Acima + feeds com XML de **dia anterior** | Mais pesado — 1×/dia basta |

Para “cliente mexeu no XML hoje”, **`executar-novos` já basta**. Não precisa de cron a cada minuto se você aceita esperar até a próxima execução (7h ou 20h).

### Onde colocar (recomendação)

| Script | Horário | Sugestão |
|--------|---------|----------|
| `run_go_sync_cron.sh` | 20h | **Principal:** adicionar geração XML aqui (nome sugere sync Go) |
| `run_score_cron.sh` | **7h** | `executar-novos` — pega alterações feitas após as 20h do dia anterior |
| `run_score_cron.sh` | 20h | **Não duplicar** se `run_go_sync` já rodou `executar-novos`/`executar-tudo` no mesmo minuto |

### Snippet para colar no final do script

Ajuste `COLIBEX_XML_ROOT` se a raiz do projeto for outra:

```bash
# --- XML Colibex: novos + alterados no painel ---
export COLIBEX_XML_ENV="${COLIBEX_XML_ENV:-server}"
export COLIBEX_XML_ROOT="${COLIBEX_XML_ROOT:-/home/colimob/public_html}"
cd "$COLIBEX_XML_ROOT" || exit 1

if [[ -x ./bin/gerador-xml ]]; then
  echo "[$(date '+%F %T')] gerador-xml executar-novos (pendentes + alterados)"
  ./bin/gerador-xml executar-novos
else
  echo "[$(date '+%F %T')] AVISO: bin/gerador-xml não encontrado — pular geração XML"
fi
```

No **`run_go_sync_cron.sh` às 20h**, se quiser também atualizar feeds de dias anteriores na mesma janela, troque a última linha por:

```bash
./bin/gerador-xml executar-tudo
```

(ou rode `executar-novos` e depois `executar-tudo` — redundante; prefira só `executar-tudo` 1×/dia à noite).

### Atraso máximo sem cron por minuto

| Cliente salva às… | Próxima regen com 7h+20h | Próxima regen só 20h |
|-------------------|--------------------------|----------------------|
| 21h | 7h do dia seguinte (~10h) | 20h do dia seguinte (~23h) |
| 10h | 20h do mesmo dia (~10h) | 20h do mesmo dia (~10h) |

### Evitar rodar duas vezes às 20h

Às 20:00 os dois crons disparam juntos. Coloque a geração XML **só em um** deles (de preferência `run_go_sync_cron.sh`) para não processar a mesma fila em paralelo.

### Validar antes de produção

```bash
cd /home/colimob/public_html
./bin/gerador-xml executar-novos --dry-run
```

Lista feeds que seriam gerados (sem XML + alterados no painel) sem gravar arquivos.

---

## 9. Perguntas frequentes

**O painel precisa chamar o gerador?**  
Não. Basta atualizar `data_alteracao`; o cron detecta.

**E se `data_alteracao` for NULL ou `0000-00-00`?**  
O gerador usa `data_cadastro` como fallback na query e trata timestamp inválido como “sem sinal de alteração” (comportamento legado por mtime).

**Cliente alterou mas XML não regerou?**  
Verificar: cron rodando, binário recompilado, `data_alteracao` no banco > mtime do arquivo, `status=1` e master `Ativo`.

**Feed global sem filtros salvos?**  
Exporta todos os imóveis ativos do escopo (todos/caixa/particular conforme flags).
