# PROJETO — Monitor de Conversas iSUPER (memória viva)

> Documento-mestre do projeto. Objetivo: não perder o "porquê" das decisões.
> Última atualização: 2026-09-02.

---

## 1. O que é

Dashboard em **PHP + MySQL (auth) + Google Sheets (dados)** que audita conversas de
atendimento de um **provedor de internet (telecom)**. Servido em
`monitordeconversas.isuper.com.br` (cPanel). É um **rebuild em PHP** de um projeto original
feito no Lovable (React) — `monitordeconversas.lovable.app`.

- Front: PHP server-rendered + Tailwind (CDN) + Chart.js (CDN).
- Auth: sessão PHP + MySQL (`users`), CSRF no login.
- **Dados de atendimento NÃO vêm do MySQL** — vêm de **planilhas do Google Sheets**
  (uma por contexto: Comercial, Suporte, Financeiro, Retenção), lidas em runtime e cacheadas.
- IA: **Google Gemini** (free tier, `gemini-2.5-flash`).

### Estrutura de pastas
```
projeto-php-local/
├── public/            # docroot (index, dashboard, vendedor-detail, keywords, api/)
├── src/
│   ├── bootstrap.php  # carrega .env, autoload PSR-4 App\, sessão, headers segurança, requireAuth()
│   ├── Database.php   # PDO MySQL (usado só p/ auth; tabela atendimentos está ÓRFÃ)
│   ├── Models/        # User, Vendedor (DB — Vendedor está órfão), Atendimento (órfão)
│   └── Services/      # GoogleSheetsService, AiInsightsService, IntentService
├── config/            # .env, .env.production (gitignored), keywords.json
└── db/                # schema.sql, test_data.php (mock), seed.php
versoes/               # snapshots versionados (v1.0 ... v1.7), cada um com INFO.md
```

---

## 2. Modelo de dados (campos do Google Sheets)

Cada linha = 1 atendimento avaliado. Campos disponíveis (nomes reais das colunas):
`Atendimento, Abertura, Encerramento, Duracao, Atendente, Nome (cliente), Nota Final,
Avaliacao, Causa, Codigo, Conversa, Topico, Fluxo, EstiMark, TipoCli, Ponto Negativo`.

Notas importantes:
- **`Avaliacao`** é multilinha `chave=valor` com critérios e justificativas, ex.:
  `cordialidade=5\njustificativa_cordialidade=...\nnota_final=5`. Os critérios **variam por
  contexto** (Comercial: conexao/conhecer/envolver/converter/encantar; etc.).
- **`Conversa`** = texto bruto do chat (usado pela Análise de Intenção). **Formato de quem
  fala ainda NÃO validado com dado real** (heurística "Nome: texto" com fallback).
- **`Nota Final`** extraída via `preg_match('/(\d+\.?\d*)/', ...)` em vários pontos.
- **`Duracao`** aceita `HH:MM:SS` ou número (minutos).
- **NÃO existe** coluna de resultado de negócio (comprou/cancelou) — ver §6 (Fase 2).

`GoogleSheetsService` mapeia colunas por aproximação (`getColumnMapping`) e todos os métodos
aceitam um array `$filters` com `contexto, from_date, to_date, vendedor, causas`.

---

## 3. Configuração / Deploy (cPanel)

- `bootstrap.php` escolhe o arquivo de ambiente por `getenv('APP_ENV')`: se `production`
  carrega `config/.env.production`, senão `config/.env`. **No servidor, garantir
  `APP_ENV=production`** (é o mecanismo que faz a v1.3+ funcionar).
- **Chave Gemini:** `GEMINI_API_KEY` (+ `GEMINI_MODEL`). Lida via `getenv` **com fallback**
  que lê direto `config/.env.production`/`.env` — isso resolveu o bug "Diagnóstico não
  configurado" quando o servidor não exporta `APP_ENV` (PHP-FPM/getenv). Chave gratuita:
  https://aistudio.google.com/apikey. Enviar no header `X-goog-api-key`.
- `.gitignore` ignora `config/.env*`, `logs/`, `db/test_data.php`, `db/seed.php`.
  **`config/keywords.json` NÃO é ignorado** (deve ser deployado).
- Deploy = subir a estrutura por FTP mantendo `public/` como docroot e `src/config/db` fora
  do docroot. Snapshots em `versoes/` espelham isso (entregamos pasta, **sem zip**).

---

## 4. Funcionalidades por versão (o "diário")

- **v1.0** Refatoração completa (base PHP).
- **v1.1** Conversa original na avaliação detalhada.
- **v1.2** Modo TV (contraste) + Painel de Alertas + auto-reload.
- **v1.3** Fix fetch Google Sheets para planilhas grandes (4000+ linhas).
- **v1.4** **Seção "Qualidade e Diagnóstico"**: card **Diagnóstico de IA** ("Revelar Padrões
  Ocultos", Gemini, análise em 4 blocos) + **Pareto de detração** (causas das notas 1–3) +
  **Correlação Tempo×Qualidade**. Endpoint `public/api/insights.php` + `AiInsightsService`.
- **v1.5** Página do atendente reconstruída para ler do Sheets (substituiu versão MySQL órfã).
- **v1.6** **Página do atendente = dashboard em "modo individual"**
  (`dashboard.php?atendente=X&individual=1`): tudo igual ao dashboard, **sem o ranking**.
  `vendedor-detail.php` virou **redirect** pra esse modo. Cards do ranking são clicáveis.
- **v1.6.1** **Score de Sentimento** corrigido: era fixo "100%"/contagem; agora é a **média
  qualitativa ponderada (0–5)** das dimensões do `Avaliacao` (via `getCompetencias`).
- **v1.7** **Análise de Intenção por Palavras-Chave (Fase 1)** — ver §5.
- **v1.8** **Análise de Intenção em página dedicada** (`keywords.php`) — ver §5.
- **v1.9** **Novo contexto Retenção** — 4ª área ao lado de Comercial/Suporte/Financeiro, com
  planilha própria (`RETENCAO_SHEET_ID`/`RETENCAO_SHEET_GID`).
- **v2.0** **Redesign visual completo** (skill `impeccable`) — novo sistema de tokens
  (`public/css/design-tokens.css`) aplicado em `dashboard.php`, `keywords.php`, `index.php`,
  `avaliacao-detalhada.php` e `public/api/atualizar-banco-atendimentos.php`. Modo TV passou de
  `!important` por classe Tailwind pra reatribuição de tokens (`body.modo-tv`). Ver
  `versoes/v2.0-redesign-visual/INFO.md`.
- **v2.1** **4 melhorias de UX** (segunda opinião via `ui-ux-pro-max`): breadcrumb de navegação
  no modo Individual, seleção em massa + exportar CSV + skeleton loading no Banco de
  Atendimentos, realce de anomalia (dia com nota <20% da média do período) no gráfico
  Performance por Dia, indicador "ao vivo" pulsante no painel de Alertas. Ver
  `versoes/v2.1-melhorias-ux/INFO.md`.
- **v2.2** **Seletor de período estilo Meta Ads + comparação de dois períodos** — novo
  `public/js/date-range-picker.js` (presets + calendário duplo + "Usados recentemente" via
  localStorage), substitui os campos De/Até em `dashboard.php` e `keywords.php`. No dashboard,
  o checkbox "Comparar" é **funcional**: KPIs ganham selo de variação vs período anterior e o
  gráfico Performance por Dia sobrepõe uma 2ª linha — sem tocar `GoogleSheetsService` (o mesmo
  `$data` já buscado é refiltrado 2x, cada método já chama `filterData()` internamente). Ver
  `versoes/v2.2-seletor-datas/INFO.md`.
- **v2.3** **Revisão e correção dos filtros** (auditoria completa do caminho período/atendente/
  causa, do formulário até `filterData()`). Principais:
  - **Dropdowns de Atendente e Causa passam a usar só o contexto** (antes eram escopados pelo
    período → no padrão "Hoje" do Modo Monitor nasciam vazios e não dava pra filtrar nada).
  - **Modo Monitor continua abrindo em "Hoje"** (decisão v2.2 preservada), mas quando não há
    atendimento hoje e não veio `from_date` na URL, alarga automático pros últimos 7 dias com
    aviso no topo — em vez de mostrar a tela zerada.
  - **`filterData()` normaliza (trim) os dois lados** das comparações de atendente e causa
    (antes o valor cru da planilha era comparado contra a lista, também crua → item com espaço
    sobrando não casava) e usa um parser de data único (`parseRowDate()`) que aceita dia/mês
    com 1 ou 2 dígitos. Loga aviso quando um filtro é pedido mas a coluna não foi detectada
    (antes o filtro era silenciosamente ignorado).
  - **`IntentService::extractClientText()` calibrado pro formato real do campo `Conversa`**
    (`[timestamp] whatsapp / número / Nome : texto` p/ cliente; `AnyCom LiveChat` / `AnyCom BOT`
    p/ atendimento). Antes nenhum rótulo era reconhecido e a análise lia a conversa inteira
    (cliente + atendente + bot).
  - **Timezone fixo** `America/Sao_Paulo` no `bootstrap.php` (via `APP_TIMEZONE`) — sem isso o
    "hoje" do Modo Monitor virava o dia seguinte no fim da tarde (servidor em UTC).
  - Menores: `getVendedores`/`getCausas` fazem trim + dedupe + pulam a linha "Total Geral";
    cards do ranking preservam o filtro de causa ao entrar no modo individual; regex de data do
    heatmap aceita 1–2 dígitos.
  Ver `versoes/v2.3-fix-filtros/INFO.md`.

---

## 5. Análise de Intenção por Palavras-Chave (o "coração" novo)

### Ideia
Palavras revelam intenção/urgência. Em telecom: "**preciso** de internet" (necessidade, intenção
alta) ≠ "**queria** um orçamento" (desejo, intenção baixa). Queremos (1) rastrear palavras
ponderadas e (2) — futuro — descobrir, com base em dados, quais palavras levam a um resultado.

### Metodologias (mapa mental, decidido em sessão de "grill")
- **Saliência (sem rótulo):** keyword spotting/léxico (a nossa lista), TF-IDF.
- **Intenção/urgência linguística:** modalidade (necessidade "preciso" > desejo "quero" >
  condicional "queria"); léxicos afetivos (LIWC/VADER).
- **Importância preditiva (com rótulo) = Fase 2:** log-odds com prior de Dirichlet (padrão-ouro
  p/ "palavras que separam grupo A de B"), PMI, qui-quadrado, regressão logística/Lasso, SHAP.
- **Fonte da tag:** léxico determinístico vs LLM (Gemini) vs híbrido.

### Decisões tomadas (com o usuário)
1. **3 modos selecionáveis** (`?avaliacao=lista|hibrido|ia`), **padrão = lista determinística**.
2. **Texto analisado:** apenas **falas do cliente**, conversa inteira (início simples).
3. **Lista:** por **categoria** + **peso definido pelo usuário (0–3)**, exibindo
   **"Sugestão de peso pela IA = X"** (Gemini sugere; usuário decide).
4. **Gestão por tela** no painel (CRUD).
5. **Anti-vazamento (Fase 2):** descontar/sinalizar palavras que só repetem o desfecho
   (ex.: "cancelar" numa conversa de cancelamento) — descrevem, não preveem.

### Implementação (v1.7)
- `config/keywords.json` — `{categorias:{COD:{rotulo,cor}}, palavras:[{categoria,palavra,
  peso_usuario,peso_sugerido_ia}]}`. Semente: COMPRA, CANCELAMENTO, URGENCIA, SUPORTE.
- `src/Services/IntentService.php`:
  - `extractClientText($conversa,$cliente,$atendente)` — mantém só falas do cliente (heurística
    de rótulo, fallback p/ texto inteiro).
  - `analyze($texto,$cfg)` — soma pesos por categoria, **trata negação** ("não/nunca/sem/jamais"
    antes da palavra anula a contagem). Retorna `score, por_categoria, hits`.
  - `aiSuggestWeight($palavra,$categoria)` — Gemini sugere peso 0–3.
  - Lê a chave Gemini com o mesmo fallback do `AiInsightsService`.
- `public/api/keywords.php` — `action=list|save|suggest`.
- `public/keywords.php` — **página dedicada do tema** (desde 2026-06-19): no topo, bloco de
  introdução (Tese / Método / Como interpretar); **barra de filtros própria** (contexto + período
  + atendente, reaproveitando `getVendedores`, preservando `?avaliacao`); a seção
  **"Análise de Intenção"** (peso por categoria + top palavras coloridas pela cor da categoria +
  score médio + seletor de modo) com **indicador de cobertura** ("Analisados N de TOTAL · X sem
  conversa" + aviso de amostra pequena quando N<30); abaixo, a **gestão de palavras** e a
  **gestão de categorias** (criar/renomear/recolorir/remover, com remoção bloqueada se houver
  palavras associadas — persistida pelo mesmo `action=save`). Ainda herda filtros do dashboard via
  querystring e tem link de volta preservando o recorte.
- `public/dashboard.php` — **botão "Intenção"** na navbar (monitor) que leva à página dedicada
  carregando o mesmo recorte de filtros. O card antigo de Análise de Intenção foi **movido** do
  dashboard para `keywords.php` (decisão do usuário: feature com casa própria).

### Estado / limitações conhecidas
- **Modos Híbrido/IA**: selecionáveis, mas a análise semântica por conversa via LLM ainda é
  placeholder (custo/latência de N chamadas). Evolução: 1 chamada por amostragem. O modo
  **lista** está completo.
- **Formato do `Conversa`**: validado com dado real da Comercial em 2026-09-02 e
  `extractClientText()` calibrado pra ele (v2.3). Exemplo real:
  `[2026-08-31 12:02:07] whatsapp / 5544999200769 / Henrique : texto` (cliente) /
  `[…] AnyCom LiveChat / <Atendente>: …` e `[…] AnyCom BOT : …` (atendimento). Falta confirmar
  se Suporte/Financeiro/Retenção usam o mesmo formato.
- **Permissão de escrita** em `config/keywords.json` no servidor (a tela grava nele).

---

## 6. Fase 2 — a parte de maior valor (ainda NÃO construída)

**Conclusão central:** o valor ("a palavra X leva a 70% dos cancelamentos") não é problema de
NLP — é ter o **gabarito do resultado**. A ponderação de palavras é a parte fácil.

- **Plano:** o usuário vai **adicionar uma coluna de resultado na planilha do sistema**
  (ex.: `Resultado` = Comprou/Não comprou ou Cancelou/Não cancelou). Como a planilha vem do
  CRM, o gabarito é real e automático — basta o `GoogleSheetsService` ler a coluna.
- **Depois:** correlação palavra/tag → resultado (taxa por resultado + **log-odds** simples),
  com **flag anti-vazamento**. Exige volume (centenas+) e atenção a desbalanceamento de classe.
- **Cuidado de causalidade:** medir intenção pela **abertura** do cliente é o ideal p/ prever;
  começamos simples (conversa inteira, só cliente) e refinamos depois.

### Índice de confiabilidade (desenho decidido em 2026-06-19)
Motivação: o peso manual pode estar errado (ex.: "preciso"=3 e "quero"=1, mas ambos convertem).
Os dados devem corrigir o palpite. Decisões:
- **Dois indicadores SEPARADOS** (não um número só):
  1. **Taxa de conversão** = `P(resultado | palavra presente)` ("dos que disseram X, Y% converteram").
  2. **Confiança (anti-sorte)** = selo **Alta / Média / Baixa + nº de casos (N)**; qui-quadrado /
     tamanho de amostra rodam por baixo e viram 3 níveis (evita "100% de 2 casos"). p-valor/N
     podem aparecer no tooltip.
- **Seletor de nível** (mesma UX dos outros seletores): **palavra / categoria / conversa / feature inteiro**.
- **Recalibração de peso (payoff):** comparar a taxa empírica com o `peso_usuario` e **sugerir
  ajuste** do peso (fecha o ciclo com a tela de gestão / `peso_sugerido_ia`).
- **Nuance a aplicar:** a taxa só é informativa **comparada à taxa-base** (lift): "X converte 78%
  vs média geral 50%". Sem isso, palavras comuns parecem fortes.
- **Pré-requisito:** depende 100% da coluna de resultado (gabarito). É Fase 2.

---

## 7. Dívidas técnicas / pontos de atenção
- `App\Models\Vendedor`, `Atendimento` e `api/export.php` são **DB-based e órfãos** (a tabela
  MySQL `atendimentos` não é alimentada em produção). Não remover sem checar; a página do
  atendente já não depende deles. O botão "Exportar CSV" foi removido da página do atendente.
- `db/test_data.php` (mock) usa `Avaliacao` numa linha com `&` (diferente do real, que é
  multilinha) — por isso radar/competências/score de sentimento ficam vazios localmente; em
  produção populam.
- IA: custo por chamada (Diagnóstico, sugestão de peso, futuros modos semânticos).
- **Auto-detecção de coluna** (`GoogleSheetsService::autoDetectColumns`): conferida OK só na
  **Comercial** (data→`Abertura`, atendente→`Atendente`, causa→`Causa`). Se errar num contexto
  novo, o filtro correspondente é ignorado — desde a v2.3 isso vira um `error_log`, não um
  silêncio, mas ainda falta verificar Suporte/Financeiro/Retenção ao vivo.
- **Nomes de coluna fixos** ("Abertura", "Causa", "Nota Final", "Duracao") ainda aparecem
  hardcoded em vários helpers de `dashboard.php` (heatmap, Pareto, correlação tempo×nota) e em
  `api/atualizar-banco-atendimentos.php` — funcionam na Comercial, podem falhar em contexto com
  cabeçalho diferente. Passar pelo `getColumnMapping` é dívida aberta (v2.3 só ajustou a regex
  de data do heatmap).
- `keywords.php`: o checkbox "Comparar" do seletor de datas continua **inerte** (só o dashboard
  compara períodos). "Desmarcar todas as causas" no dashboard equivale a "todas marcadas"
  (comportamento mantido — decisão de produto, não alterado na v2.3).
- `fgetcsv(..., '\\')` em `GoogleSheetsService::parseCSV` usa `\` como escape (footgun conhecido
  do PHP, depreciado no 8.4) — não alterado na v2.3 por ser mudança de parsing sem teste local.

---

## 8. Como rodar local
```
cd projeto-php-local/public && php -S localhost:8000
```
Login valida contra MySQL (hashes reais) — local sem DB seedado não loga; usar servidor real
ou seedar o banco. `php -l <arquivo>` para checar sintaxe.
