# MVP - Plataforma para Vereador (multi-tenant)

## O que está incluído
- Login e sessão por usuário, vinculado a um tenant (vereador)
- Isolamento de dados por `tenant_id` centralizado em `includes/tenant.php`
- Agenda (privada + exibição opcional no portal público)
- Notícias (rascunho/publicado, aparecem no portal público)
- Solicitações/Demandas (com status: pendente, em análise, atendido)
- Base Eleitoral (CRM simples de contatos)
- Mapa de Demandas (Leaflet + OpenStreetMap, usando lat/long manual)
- Portal público por vereador via `/portal/index.php?slug=NOME_DO_VEREADOR`
- Ofícios com IA (texto livre → OpenAI gera rascunho; revisão humana obrigatória
  antes de considerar pronto; limite mensal de gerações por tenant configurável
  em `IA_LIMITE_MENSAL` para conter custo — compartilhado com o módulo de
  Matérias, já que a chave de API é única e o custo é seu)
- Matérias legislativas com IA (Moção, Requerimento, Indicação, Projeto de Lei —
  categorias fixas + descrição livre; mesmo fluxo de rascunho/revisão dos
  ofícios; a IA é instruída a sinalizar no texto quando o pedido parecer fora
  da competência do Legislativo municipal, mas isso é mitigação, não garantia —
  **não há aviso reforçado de "revisar com assessoria jurídica" na interface,
  por decisão explícita sua; considero isso um risco maior que no módulo de
  ofícios, registrado aqui para não ser esquecido**)
- Encurtador de links com contagem de cliques (`r.php` na raiz do projeto,
  público, sem exigir login — cada acesso incrementa o contador e redireciona).
  **Limitação importante:** o link gerado usa o domínio/subpasta atual
  (ex: `atendefacil.online/vereador/r.php?c=xxxx`), não é um domínio curto
  dedicado. Para um link realmente curto (estilo bit.ly), seria necessário
  registrar um domínio próprio e apontar para este sistema — isso é decisão
  e configuração fora do código.
- Banner do portal público (imagem de capa larga atrás do cabeçalho, com a
  logo circular sobreposta) — configurável em Configurações do Gabinete,
  junto com a logo.
- Página inicial pública de apresentação (`portal/index.php?slug=X`): hero
  com foto e botão de WhatsApp, biografia, cards de "Bandeiras de Atuação"
  (gerenciados em Bandeiras no painel admin), banner de call-to-action e
  rodapé com redes sociais. O Banner cadastrado em Configurações agora
  também é usado como imagem de fundo do hero e da seção final "Fale com
  o Gabinete" (antes só aparecia no mural de notícias). Há um terceiro
  campo de foto (`foto_cta_path`) opcional para a seção final.
- Formulário público de solicitação do cidadão (`portal/solicitar.php?slug=X`):
  sem exigir login, insere direto na tabela de Solicitações do gabinete
  (aparece automaticamente no painel admin). Tem um campo honeypot simples
  contra spam de robô, mas **não tem CAPTCHA nem limite de taxa por IP** —
  para volume real de tráfego público, isso pode precisar de reforço.
- O mural de notícias/agenda que antes ficava em `portal/index.php` foi
  movido para `portal/noticias.php?slug=X` (a página inicial nova ficou
  com `index.php`, e tem um link "Ver notícias e agenda" apontando pra lá).
- Notícias exigem upload de imagem (PNG/JPG, salva em `uploads/noticias/`)
  e são exibidas em carrossel no mural público — setas de navegação,
  indicadores (bolinhas), autoplay a cada 6s (pausa ao passar o mouse) e
  suporte a arrastar o dedo no celular.
- Galeria de fotos (gerenciada em Galeria no painel admin, múltiplos
  uploads com legenda opcional) exibida em grade na página inicial pública.
- Endereço do gabinete exibido como mapa embutido do Google (sem precisar
  de chave de API — usa o link público `google.com/maps?q=...&output=embed`)
  em vez de texto puro, na caixa "Contato do gabinete".
- Cores personalizáveis por vereador: em Configurações, 3 seletores de cor
  (Principal, Ação/botões, Destaque) sobrescrevem a paleta padrão em todas
  as páginas públicas (`portal/index.php`, `portal/noticias.php`,
  `portal/solicitar.php`). O tom mais claro usado em degradês é gerado
  automaticamente a partir da cor Principal — não precisa cadastrar uma
  quarta cor pra isso. **O painel admin (`/admin/...`) continua com a
  paleta fixa, cores personalizadas afetam só as páginas públicas.**
- Seções "Fale com o Gabinete" (CTA) e rodapé agora têm checkbox próprio
  em Configurações pra aparecer ou não.
- Enquete pública (gerenciada em Enquetes no painel admin — só uma fica
  ativa por vez): visitante vota, resultado em barras aparece depois do
  voto. Controle de "só um voto por pessoa" é via cookie no navegador —
  **não é à prova de fraude** (limpar cookies ou votar de outro
  navegador/aparelho permite votar de novo). Para algo mais rigoroso
  seria necessário CPF ou outro identificador, o que não implementei.
- Widget de acessibilidade flutuante (canto inferior esquerdo, em todas
  as páginas públicas): aumentar/diminuir tamanho de texto e alternar
  alto contraste. É uma implementação própria simples — não segue
  necessariamente todas as diretrizes de acessibilidade (WCAG) que um
  site de órgão público pode ser obrigado a cumprir por lei; se isso for
  requisito legal formal, vale validar com um especialista em
  acessibilidade digital.
- Notificações push (via OneSignal, gerenciado em Notificações no painel
  admin): visitantes do site podem se inscrever para receber avisos, e o
  vereador dispara mensagens pra todos os inscritos. **Exige conta própria
  gratuita no onesignal.com** — não é algo que o sistema cria sozinho, o
  vereador (ou você) precisa gerar o App ID e a REST API Key lá e colar
  no painel. Sem essas chaves configuradas, nada relacionado a push
  aparece nem funciona nas páginas públicas.
- Matérias Aprovadas (`portal/materias.php?slug=X`): página pública listando
  Indicações, Requerimentos e Projetos de Lei que o vereador marcou como
  "publicada" no painel admin. **Só pode publicar matéria já revisada**
  (rascunho de IA não vai pro site sem revisão humana passar por cima
  antes). O link só aparece no menu se houver pelo menos uma matéria
  publicada.
- Trabalhos Realizados (aba própria no admin, separada de Matérias):
  catálogo simples de obras/entregas do mandato — título, descrição,
  bairro, foto obrigatória, data opcional. Publicado por padrão ao
  cadastrar (diferente de Matérias, que exige revisão manual antes de
  poder publicar). Aparece como grade de cards com foto na página
  inicial pública.
- Mapa de Eleitores (`admin/mapa_eleitores.php`): mapa com clusterização
  (Leaflet + plugin markercluster, carregado via CDN) mostrando eleitores
  por status de engajamento (Indeciso/Frio, Apoio provável/Morno, Voto
  certo/Quente), com filtro por bairro, status, líder e tag. Depende de
  latitude/longitude preenchidas manualmente na Base Eleitoral (mesma
  limitação já existente no Mapa de Demandas — sem geocodificação
  automática).
- Lideranças (aba própria): cadastro de apoiadores/lideranças com nome,
  telefone e bairro. Vincula-se ao eleitor via dropdown na Base Eleitoral
  (substituindo o antigo campo de texto livre). Excluir uma liderança
  desvincula os eleitores associados, mas não os exclui.
- Base Eleitoral agora tem edição (antes só tinha criar/excluir).
- Bairros (aba própria): cadastro de bairro com coordenada de referência
  (lat/long do centro do bairro, preenchida manualmente por você, sem
  geocodificação automática). Ao cadastrar eleitor ou solicitação, um
  dropdown "Bairro cadastrado" preenche automaticamente o nome do bairro
  e sugere a latitude/longitude — os campos continuam editáveis, pra
  ajustar caso o endereço exato seja diferente do centro do bairro.
- Categorias de Solicitação (aba própria): substitui o campo de texto
  livre em Solicitações por um dropdown das categorias cadastradas.
- Formulários de Agenda, Notícias, Solicitações, Base Eleitoral e
  Lideranças agora abrem em modal (botão "+ Novo...") em vez de ficar
  sempre visível na tela — deixa a tela principal só com a lista.
- Mapa de Lideranças (`admin/mapa_liderancas.php`): mesma lógica do Mapa
  de Eleitores, mas pra lideranças — útil pra identificar bairros sem
  nenhuma liderança cadastrada. **Se você já criou a tabela `liderancas`
  antes desta atualização** (sem coluna de coordenada), rode:
  ```sql
  ALTER TABLE liderancas ADD COLUMN latitude DECIMAL(10,7) NULL, ADD COLUMN longitude DECIMAL(10,7) NULL;
  ```
- Relatórios em PDF (`admin/relatorios.php`): gera PDF de Base Eleitoral,
  Solicitações, Agenda, Trabalhos ou Matérias, com filtro de período/status/
  bairro conforme o tipo. Reaproveita a mesma biblioteca FPDF já usada nos
  Ofícios — **também depende de `vendor/fpdf/fpdf.php` estar instalado**
  (mesmo requisito já documentado abaixo). Tabelas com muitas linhas quebram
  de página automaticamente, repetindo o cabeçalho da tabela em cada página.

## Se o visual sumir depois de atualizar os arquivos
O CSS de todo o sistema vive em UM arquivo só (`assets/css/style.css`). Se
você atualizar só um módulo PHP e esquecer de subir esse arquivo também
(ou o navegador estiver com cache antigo dele), a página carrega sem nenhum
estilo — parece "quebrado" mas não é. Sempre: (1) suba o `style.css` mais
recente junto com qualquer atualização, (2) force recarregamento sem cache
no navegador (Ctrl+Shift+R) antes de concluir que tem bug.
- Exportação de ofício em PDF com timbre: logo no cabeçalho, endereço/telefone/
  e-mail no rodapé, aviso visível em vermelho quando o PDF é de um rascunho
  ainda não revisado por humano
- Configurações do Gabinete: upload de logo (PNG/JPG), telefone e endereço

## Instalar o FPDF (necessário para os PDFs de ofício)
Baixe a biblioteca (arquivo único, sem Composer) em https://www.fpdf.org/
e coloque em `vendor/fpdf/fpdf.php` dentro do projeto. Não incluí o arquivo
da biblioteca aqui porque é código de terceiros — baixe direto do site oficial.

## O que NÃO está incluído (limitações conhecidas, não bugs)
- **Geocodificação automática de endereço.** Lat/long são inseridos manualmente
  nos formulários de Solicitações e Eleitores. Para automatizar, seria necessário
  integrar a Nominatim (gratuito, mas com limite de uso e qualidade variável em
  cidades do interior) ou uma API paga (Google Geocoding).
- **WhatsApp.** Você decidiu usar biblioteca não-oficial (ex: Baileys). Isso
  roda como serviço Node.js separado, não dentro deste PHP. Precisa de:
  um processo Node persistente conectado à sessão do WhatsApp do vereador,
  fila de envio com delay humanizado (para reduzir risco de banimento), e
  endpoint HTTP interno que o PHP chama para disparar mensagens. Não construí
  isso ainda porque é um projeto técnico separado, com riscos operacionais
  próprios (queda de sessão, necessidade de reconexão via QR code).
- **Cadastro de tenant e primeiro usuário via painel.** Hoje isso é feito
  direto no banco (veja abaixo). Não existe tela de "criar novo vereador
  cliente" — precisa ser construída quando você tiver processo de onboarding
  definido.
- **Recuperação de senha, 2FA, rate limiting de login.** Segurança básica
  apenas (senha com hash bcrypt). Não é nível de produção para dado sensível
  de eleitor em escala.
- **Subdomínio por vereador.** O portal está em `/portal/index.php?slug=X`,
  não em `vereador.suaplataforma.com.br`. Para subdomínio real, é preciso
  configurar DNS wildcard na sua VM e ajustar o roteamento — não fiz isso
  aqui porque depende do seu domínio e provedor de DNS.

## Setup local/VM
0.5. **Se você já tinha o banco criado antes desta atualização**, rode este
   ALTER para adicionar as colunas novas sem perder dados existentes:
   ```sql
   ALTER TABLE tenants
     ADD COLUMN foto_hero_path VARCHAR(255),
     ADD COLUMN bio TEXT,
     ADD COLUMN whatsapp VARCHAR(30),
     ADD COLUMN instagram_url VARCHAR(255),
     ADD COLUMN facebook_url VARCHAR(255),
     ADD COLUMN foto_bio_path VARCHAR(255),
     ADD COLUMN foto_cta_path VARCHAR(255),
     ADD COLUMN foto_trajetoria_path VARCHAR(255),
     ADD COLUMN trajetoria TEXT,
     ADD COLUMN cor_primaria VARCHAR(7) DEFAULT '#1c2b4a',
     ADD COLUMN cor_secundaria VARCHAR(7) DEFAULT '#1f7a5c',
     ADD COLUMN cor_destaque VARCHAR(7) DEFAULT '#c1502e',
     ADD COLUMN mostrar_rodape TINYINT(1) DEFAULT 1,
     ADD COLUMN mostrar_cta TINYINT(1) DEFAULT 1,
     ADD COLUMN onesignal_app_id VARCHAR(100),
     ADD COLUMN onesignal_api_key VARCHAR(255);

   ALTER TABLE materias ADD COLUMN publicada TINYINT(1) DEFAULT 0;

   ALTER TABLE eleitores
     ADD COLUMN status_voto ENUM('indeciso','provavel','certo') DEFAULT 'indeciso',
     ADD COLUMN lider VARCHAR(150),
     ADD COLUMN lideranca_id INT NULL,
     ADD COLUMN tags VARCHAR(255);

   CREATE TABLE liderancas (
       id INT AUTO_INCREMENT PRIMARY KEY,
       tenant_id INT NOT NULL,
       nome VARCHAR(150) NOT NULL,
       telefone VARCHAR(30),
       bairro VARCHAR(150),
       latitude DECIMAL(10,7) NULL,
       longitude DECIMAL(10,7) NULL,
       criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
       FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
   ) ENGINE=InnoDB;

   CREATE TABLE categorias (
       id INT AUTO_INCREMENT PRIMARY KEY,
       tenant_id INT NOT NULL,
       nome VARCHAR(100) NOT NULL,
       criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
       FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
   ) ENGINE=InnoDB;

   CREATE TABLE categorias (
       id INT AUTO_INCREMENT PRIMARY KEY,
       tenant_id INT NOT NULL,
       nome VARCHAR(100) NOT NULL,
       criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
       FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
   ) ENGINE=InnoDB;

   CREATE TABLE bairros (
       id INT AUTO_INCREMENT PRIMARY KEY,
       tenant_id INT NOT NULL,
       nome VARCHAR(150) NOT NULL,
       latitude DECIMAL(10,7) NULL,
       longitude DECIMAL(10,7) NULL,
       criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
       FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
   ) ENGINE=InnoDB;

   CREATE TABLE causas (
       id INT AUTO_INCREMENT PRIMARY KEY,
       tenant_id INT NOT NULL,
       icone VARCHAR(10) DEFAULT '📌',
       titulo VARCHAR(100) NOT NULL,
       descricao VARCHAR(300),
       ordem INT DEFAULT 0,
       criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
       FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
   ) ENGINE=InnoDB;

   CREATE TABLE galeria_fotos (
       id INT AUTO_INCREMENT PRIMARY KEY,
       tenant_id INT NOT NULL,
       imagem_path VARCHAR(255) NOT NULL,
       legenda VARCHAR(200),
       ordem INT DEFAULT 0,
       criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
       FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
   ) ENGINE=InnoDB;

   CREATE TABLE enquetes (
       id INT AUTO_INCREMENT PRIMARY KEY,
       tenant_id INT NOT NULL,
       pergunta VARCHAR(255) NOT NULL,
       ativa TINYINT(1) DEFAULT 1,
       criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
       FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
   ) ENGINE=InnoDB;

   CREATE TABLE enquete_opcoes (
       id INT AUTO_INCREMENT PRIMARY KEY,
       enquete_id INT NOT NULL,
       texto VARCHAR(150) NOT NULL,
       votos INT DEFAULT 0,
       FOREIGN KEY (enquete_id) REFERENCES enquetes(id) ON DELETE CASCADE
   ) ENGINE=InnoDB;

   CREATE TABLE trabalhos (
       id INT AUTO_INCREMENT PRIMARY KEY,
       tenant_id INT NOT NULL,
       titulo VARCHAR(150) NOT NULL,
       descricao VARCHAR(400),
       local_bairro VARCHAR(150),
       imagem_path VARCHAR(255) NOT NULL,
       data_realizacao DATE NULL,
       publicado TINYINT(1) DEFAULT 1,
       ordem INT DEFAULT 0,
       criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
       FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
   ) ENGINE=InnoDB;
   ```
   Se estiver criando o banco do zero, ignore isso — o `schema.sql` já
   inclui tudo.
0. **Importante:** edite `APP_BASE` em `config.php` conforme onde o sistema
   ficar hospedado. Se for `seudominio.com/admin/...` (raiz), deixe `''`.
   Se for `seudominio.com/vereador/admin/...` (subpasta), coloque `'/vereador'`
   (sem barra no final). Todo o sistema usa esse valor pra montar links e
   redirecionamentos — sem isso configurado certo, CSS não carrega e os
   links do menu apontam pro lugar errado.
1. Importe `schema.sql` no MariaDB:
   ```
   mysql -u root -p < schema.sql
   ```
2. Edite `config.php` com usuário/senha do banco e sua `OPENAI_API_KEY`
   (necessária apenas se for usar o módulo de Ofícios com IA).
3. Crie o primeiro usuário admin manualmente (gere o hash com PHP):
   ```
   php -r "echo password_hash('sua_senha', PASSWORD_BCRYPT);"
   ```
   Depois insira no banco:
   ```sql
   INSERT INTO usuarios (tenant_id, nome, email, senha_hash, papel)
   VALUES (1, 'Seu Nome', 'seu@email.com', 'HASH_GERADO_ACIMA', 'admin');
   ```
4. Aponte o document root do Apache/Nginx para a pasta do projeto.
5. Acesse `/admin/login.php`.

## Próximos passos sugeridos (na ordem que eu recomendaria)
1. Testar o fluxo completo com dados reais de 1 vereador (o "cliente aguardando").
2. Decidir se geocodificação manual é aceitável para o cliente ou se
   inviabiliza o módulo de mapa na prática.
3. Só depois disso, investir tempo na integração de WhatsApp — é o módulo
   de maior risco técnico e operacional do pacote.
