# 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-06-19.

---

## 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`.

---

## 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`** não validado com dado real — calibrar `extractClientText` quando
  houver exemplo.
- **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).

---

## 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.
