Sumário Executivo Técnico
Este documento constitui a Especificação Técnica Completa do Módulo de Licenciamento integrado ao sistema Pipeline da Vendemmia. O documento abrange toda a arquitetura de software, modelagem de banco de dados, APIs, segurança, infraestrutura e estratégias de evolução técnica.
Arquitetura atual: O sistema foi implementado como uma Single Page Application (SPA) em Vanilla JavaScript servida via GitHub Pages (CDN global), com backend 100% gerenciado pela plataforma Supabase (PostgreSQL + PostgREST + Auth + Storage). Esta escolha arquitetural prioriza velocidade de entrega, custo zero de infraestrutura e ausência de complexidade operacional — adequada para o estágio atual do produto.
- SPA em HTML/CSS/JS puro
- Chart.js 4.4.1 (visualizações)
- Leaflet.js 1.9.4 (mapas)
- jsPDF 2.5.1 + html2canvas 1.4.1
- Tabler Icons 2.44 (ícones)
- Google Fonts: Inter + JetBrains Mono
- PostgreSQL 17.6.1 (sa-east-1)
- PostgREST — REST API automática
- Supabase Auth (JWT)
- Supabase Storage (S3-compatible)
- Row Level Security (RLS)
- SDK JS: @supabase/supabase-js@2
- GitHub Pages (CDN Akamai)
- Supabase Cloud (AWS sa-east-1)
- GitHub Actions (CI/CD)
- BrasilAPI (CEP, geocodificação)
- ViaCEP (fallback de CEP)
- QR Server API (geração de QR Code)
1. Visão Geral Técnica Seção 1
1.1 Stack Tecnológica Detalhada
| Camada | Tecnologia | Versão | Propósito | CDN / Package |
|---|---|---|---|---|
| Linguagem | JavaScript (ES2022+) | — | Lógica de UI, manipulação DOM, state management | Nativo do browser |
| Markup / Styling | HTML5 + CSS3 | — | Estrutura e estilização — single-file architecture | — |
| Database client | @supabase/supabase-js | 2.x | Comunicação com Supabase (Auth, DB, Storage) | cdn.jsdelivr.net |
| Gráficos | Chart.js | 4.4.1 | Todos os gráficos do sistema (line, bar, doughnut) | cdnjs.cloudflare.com |
| Mapas | Leaflet.js | 1.9.4 | Mapa interativo do Brasil com marcadores e choropleth | unpkg.com |
| PDF generation | jsPDF | 2.5.1 | Geração de PDFs (Board Report + PDF individual do lead) | cdnjs.cloudflare.com |
| Canvas capture | html2canvas | 1.4.1 | Captura de HTML renderizado para conversão em PDF | cdnjs.cloudflare.com |
| Ícones | Tabler Icons (webfont) | 2.44.0 | Sistema de ícones SVG via CSS class | cdn.jsdelivr.net |
| Tipografia | Google Fonts | — | Inter (UI) + JetBrains Mono (code/dados) | fonts.googleapis.com |
| Database | PostgreSQL | 17.6.1 | Persistência de todos os dados do sistema | Supabase managed |
| Auth | Supabase Auth | — | JWT-based authentication com sessão persistente | Supabase managed |
| Storage | Supabase Storage | — | Armazenamento de arquivos (documentos, fotos, PDFs) | S3-compatible |
| GeoJSON (BR) | giuliano-macedo/geodata-br-states | — | Polígonos dos estados brasileiros para choropleth | raw.githubusercontent.com |
| CEP lookup | BrasilAPI | v2 | Geocodificação por CEP (retorna lat/lng) | brasilapi.com.br |
| CEP fallback | ViaCEP | — | Fallback de CEP quando BrasilAPI falha | viacep.com.br |
| QR Code | QR Server API | — | Geração de QR Code para link do portal | api.qrserver.com |
| Tile maps | CartoDB Voyager | — | Tiles do mapa (estradas, parques, relevo) | basemaps.cartocdn.com |
| Hosting | GitHub Pages | — | Hospedagem estática com CDN Akamai global | pipelinevendemmia.com.br |
| CI/CD | GitHub Actions | — | Deploy automático via git push para branch main | github.com |
1.2 Arquitetura Geral do Sistema
O sistema adota uma arquitetura JAMstack (JavaScript + APIs + Markup) com backend como serviço (BaaS). Esta abordagem elimina a necessidade de servidores de aplicação próprios, reduz a superfície de ataque e minimiza custos operacionais.
1.3 Decisões Arquiteturais e Justificativas
| Decisão | Alternativas consideradas | Escolha | Justificativa |
|---|---|---|---|
| Single-file SPA | React, Vue, Angular | Vanilla JS (index.html) | Zero dependências de build pipeline; deploy via git push; equipe sem expertise em frameworks; MVP rápido |
| Backend como serviço | Node.js + Express, .NET, FastAPI | Supabase (BaaS) | PostgreSQL gerenciado, Auth, Storage e API REST prontos; sem custo de servidor; SA-East-1 (baixa latência para o Brasil) |
| Autenticação | Auth0, Firebase Auth, JWT próprio | Supabase Auth | Integração nativa com o banco; RLS por usuário; sessão persistente; custo zero no tier atual |
| Controle de acesso | RBAC no backend, middleware | Whitelist de e-mail + RLS | Simplicidade para o estágio atual; RLS no PostgreSQL garante isolamento de dados; whitelist no frontend filtra acesso |
| Storage | AWS S3 direto, Cloudinary | Supabase Storage | Já incluído no Supabase; políticas RLS aplicáveis; SDK unificado; sem custo adicional |
| Hospedagem | Vercel, Netlify, AWS S3 | GitHub Pages | Integrado ao repositório existente; CDN Akamai global; domínio customizado; custo zero |
| Geração de PDF | Puppeteer server-side, wkhtmltopdf | jsPDF + html2canvas (client-side) | Zero infraestrutura de servidor; funciona offline; menor latência percebida pelo usuário |
| Mapas | Google Maps, Mapbox, Deck.gl | Leaflet.js + CartoDB | Open source; sem API key; tiles CartoDB gratuitos com qualidade visual adequada; leve (≈150KB) |
Limitação conhecida — single-file architecture: A ausência de bundler (Webpack/Vite) e de gerenciamento de módulos ES significa que todo o código JS está em um único arquivo index.html com aproximadamente 11.000+ linhas. Esta abordagem funciona para o estágio atual mas representa débito técnico que deve ser endereçado na evolução para arquitetura modular (ver Seção 12 na Fase 3).
2. Arquitetura do Sistema Seção 2
2.1 Frontend — Single Page Application (SPA)
O frontend é uma SPA implementada em um único arquivo HTML (index.html) servida via GitHub Pages no domínio pipelinevendemmia.com.br. Toda a lógica de UI, state management e comunicação com APIs está contida neste arquivo.
Estrutura lógica do frontend
// Organização lógica do código em index.html (~11.000+ linhas)
// ─────────────────────────────────────────────────────────
// [1] HEAD: CDNs carregados em ordem de dependência
Leaflet CSS → Tabler Icons CSS → jsPDF → html2canvas
→ Leaflet JS → Chart.js → @supabase/supabase-js
// [2] STYLE: CSS global (~700 linhas)
- Variáveis CSS (design tokens)
- Componentes reutilizáveis (cards, buttons, modals)
- Módulos específicos (lic-*, fin-*, kb-*)
// [3] HTML: Estrutura de telas (SPA)
- #login-overlay → tela de autenticação
- #main-system → container principal (hidden até login)
- header (nav global)
- #page-dashboard → tela Home/Pipeline
- #page-kanban → Kanban de oportunidades
- #page-financeiro → Financeiro
- #page-licenciamento → Módulo Licenciamento
- #lic-view-tabela → dashboard + tabela
- #lic-view-form → formulário 11 seções
- #lic-view-mapa → mapa interativo
- #lic-view-kanban → kanban de leads
- #lic-view-scorecard → scorecard KPIs
- #lic-view-auditoria → pipeline auditoria
- #lic-view-capacidade → painel capacidade
- #lic-view-disponibilidade → disponibilidade
- Modais globais (fixed position)
// [4] SCRIPT: JavaScript (~9.000+ linhas)
- Inicialização e autenticação
- Módulos do sistema principal (Pipeline, Financeiro...)
- Módulo Licenciamento (funções lic*, lc*, aud*, sc*...)
- State: baseLicLeads, _licPerfData, _audData, _licHistData
- Funções de renderização: renderizarLicenciamento()
- Funções de mapa: _licInicializarMapa(), _licAtualizarMarkers()
- Funções de scorecard: calcLicScore(), getLicTier()
- Funções de auditoria: audAvancarEtapa(), emitirCertificado()
- Funções de histórico: registrarLicHistorico()
- Funções de PDF: gerarLicPDF(), gerarBoardReport()
State Management
O sistema não utiliza biblioteca de state management. O estado é gerenciado via variáveis globais JavaScript com padrão de nomenclatura prefixada:
| Variável | Tipo | Propósito | Carga |
|---|---|---|---|
baseLicLeads | Array<Object> | Todos os leads de licenciamento | Supabase lic_leads |
_licPerfData | Object<id, Array> | KPIs por lead (indexado por lead_id) | Supabase lic_performance |
_audData | Object<id, Object> | Dados de auditoria por lead | Supabase lic_auditoria |
_licHistData | Object<id, Array> | Histórico de eventos por lead | Supabase lic_historico |
_licGeoJsonData | GeoJSON Object | Polígonos dos estados do Brasil | GitHub CDN (uma vez) |
_licMapPhotoCache | Object<id, URL> | Cache de URLs de fotos por lead | Supabase lic_anexos |
_licDCharts | Object<key, Chart> | Instâncias Chart.js para destroy antes de recriar | Runtime |
LIC_TIERS | Array<Object> | Configuração dos tiers (Platina/Ouro/Prata/Bronze) | Constante em memória |
LIC_AUDIT_STAGES | Array<Object> | Configuração das 6 etapas de auditoria | Constante em memória |
LIC_KPI_META | Object | Configuração dos 6 KPIs com thresholds | Constante em memória |
2.2 Backend — Supabase (BaaS)
O backend é inteiramente gerenciado pelo Supabase (projeto geojgcncmtbjebftosor, região sa-east-1 — São Paulo). O Supabase expõe automaticamente uma API REST via PostgREST sobre o PostgreSQL, com autenticação via JWT e controle de acesso por Row Level Security (RLS).
Componentes Supabase em uso
| Componente | Endpoint base | Função |
|---|---|---|
| PostgREST (REST API) | https://geojgcncmtbjebftosor.supabase.co/rest/v1/ | CRUD automático de todas as tabelas; filtros, ordenação e seleção de campos via query params |
| Auth | /auth/v1/ | Login com e-mail/senha, geração de JWT, refresh token, sessão persistente no localStorage |
| Storage | /storage/v1/ | Upload, download e listagem de arquivos; buckets com políticas RLS; URLs públicas |
| Realtime | wss:// | Não utilizado atualmente — disponível para evolução futura (live updates) |
Padrão de chamada à API
// Inicialização do cliente Supabase (chamada única no carregamento)
const SUPABASE_URL = "https://geojgcncmtbjebftosor.supabase.co";
const SUPABASE_KEY = "eyJ..."; // anon key (safe para expor no client)
const { createClient } = supabase;
const supabaseClient = createClient(SUPABASE_URL, SUPABASE_KEY);
// Padrão de leitura (SELECT)
const { data, error } = await supabaseClient
.from('lic_leads')
.select('*')
.eq('status', 'Convertido')
.order('created_at', { ascending: false });
// Padrão de escrita (INSERT)
const { data: novo, error } = await supabaseClient
.from('lic_leads')
.insert([{ razao_social: '...', status: 'Ativo' }])
.select('id')
.single();
// Padrão de atualização (UPDATE)
await supabaseClient
.from('lic_leads')
.update({ status: 'Convertido', updated_at: new Date().toISOString() })
.eq('id', leadId);
// Upload para Storage
await supabaseClient.storage
.from('lic-anexos')
.upload(path, file, { cacheControl: '3600', upsert: false });
2.3 Storage e Estrutura de Arquivos
O Supabase Storage é usado para persistir todos os arquivos do módulo Licenciamento no bucket lic-anexos. A estrutura de paths garante isolamento por lead e rastreabilidade por tipo.
Estrutura de paths no bucket lic-anexos
lic-anexos/
├── leads/
│ └── {lead_id}/
│ ├── contrato_social/
│ │ └── {timestamp}_{nome_arquivo} ← Contrato Social (PDF/DOC)
│ ├── foto_armazem_1/
│ │ └── {timestamp}_{nome_arquivo} ← Fotos do Armazém 1 (JPG/PNG)
│ ├── foto_armazem_2/
│ │ └── {timestamp}_{nome_arquivo} ← Fotos do Armazém 2
│ ├── certificado/
│ │ └── {timestamp}_{nome_arquivo} ← Certificações (PDF/JPG)
│ ├── relatorio_visita/
│ │ └── {timestamp}_{nome_arquivo} ← Relatório de visita técnica (PDF)
│ └── pdf_lead/
│ └── {timestamp}_lead_{id}_AAAA-MM.pdf ← PDF gerado do lead
Políticas de Storage (RLS em storage.objects)
| Política | Operação | Usuários | Condição |
|---|---|---|---|
| lic-anexos upload | INSERT | authenticated | bucket_id = 'lic-anexos' |
| lic-anexos anon upload | INSERT | anon (portal público) | bucket_id = 'lic-anexos' |
| lic-anexos leitura | SELECT | public | bucket_id = 'lic-anexos' |
| lic-anexos delete | DELETE | authenticated | bucket_id = 'lic-anexos' |
2.4 Autenticação e Sessão
Fluxo de autenticação
// 1. Login — Supabase Auth (e-mail + senha)
const { data, error } = await supabaseClient.auth
.signInWithPassword({ email, password });
// 2. JWT obtido → armazenado no localStorage pelo SDK
// Chave: 'sb-geojgcncmtbjebftosor-auth-token'
// 3. Todas as chamadas subsequentes incluem automaticamente
// o header: Authorization: Bearer {jwt_token}
// 4. Controle de acesso ao módulo Licenciamento
const LICENCIAMENTO_WHITELIST = [
'gppereira@vendemmia.com.br',
'jprado@vendemmia.com.br'
];
// Verificação via EMAIL_USUARIO_LOGADO após login
// 5. Sessão persistente: SDK recupera sessão do localStorage
// automaticamente no reload da página
// JWT expira em ~3600s; SDK faz refresh automático
2.5 Comunicação entre Componentes
| Origem | Destino | Protocolo | Formato |
|---|---|---|---|
| Browser (SPA) | Supabase PostgREST | HTTPS REST | JSON (application/json) |
| Browser (SPA) | Supabase Storage | HTTPS multipart | FormData (files) |
| Browser (SPA) | Supabase Auth | HTTPS REST | JSON |
| Browser (SPA) | BrasilAPI | HTTPS REST | JSON (GeoJSON v2) |
| Browser (SPA) | CartoDB CDN | HTTPS | PNG (map tiles 256×256) |
| Browser (SPA) | GitHub CDN | HTTPS | JSON (GeoJSON estados BR) |
| Browser (SPA) | QR Server API | HTTPS | PNG (imagem do QR Code) |
| Portal (portal.html) | Supabase PostgREST | HTTPS REST | JSON |
| Portal (portal.html) | Supabase Storage | HTTPS multipart | FormData |
| GitHub Actions | GitHub Pages | Git (HTTPS) | Static files |
3. Banco de Dados — PostgreSQL 17 Seção 3
3.1 Modelo Lógico de Dados
O módulo Licenciamento utiliza 5 tabelas próprias no banco Supabase, além de referenciar a tabela auth.users do Supabase Auth. Todas as tabelas têm Row Level Security (RLS) habilitado.
lic_leads (id PK) │ ├──── lic_auditoria (lead_id FK UNIQUE) — 1:1 por lead │ ├──── lic_performance (lead_id FK) — 1:N por lead (múltiplos períodos) │ ├──── lic_historico (lead_id FK) — 1:N por lead (linha do tempo) │ └──── lic_anexos (lead_id FK) — 1:N por lead (múltiplos arquivos)
3.2 Tabela: lic_leads
Tabela principal. Armazena todos os dados cadastrais de cada lead/licenciado da rede.
BIGSERIALTEXTTEXTTEXTINTEGERTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTNUMERICNUMERICTEXTTEXTJSONBNUMERICTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTNUMERICNUMERICTEXTTEXTTEXTTEXTTEXTDATEDATETEXTTEXTTEXTTEXTTEXTNUMERICTEXTTEXTTIMESTAMPTZTEXTTIMESTAMPTZTIMESTAMPTZ3.3 Tabela: lic_auditoria
BIGSERIALBIGINTTEXTJSONBJSONBJSONBTIMESTAMPTZTEXTTEXTTEXTTEXTTEXTDATEDATETEXTTEXTTIMESTAMPTZTIMESTAMPTZ3.4 Tabela: lic_performance
BIGSERIALBIGINTTEXTDATENUMERICNUMERICNUMERICNUMERICNUMERICNUMERICBOOLEANTEXTDATEDATETEXTTIMESTAMPTZ3.5 Tabela: lic_historico
BIGSERIALBIGINTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTIMESTAMPTZ3.6 Tabela: lic_anexos
BIGSERIALBIGINTTEXTTEXTTEXTTEXTBIGINTTEXTTIMESTAMPTZ3.7 Índices Recomendados
-- lic_leads: queries mais frequentes
CREATE INDEX idx_lic_leads_status ON lic_leads(status);
CREATE INDEX idx_lic_leads_uf ON lic_leads(uf);
CREATE INDEX idx_lic_leads_etapa ON lic_leads(etapa);
CREATE INDEX idx_lic_leads_created_at ON lic_leads(created_at DESC);
CREATE UNIQUE INDEX idx_lic_leads_form_token ON lic_leads(form_token)
WHERE form_token IS NOT NULL; -- partial unique index
-- lic_auditoria: lookup por lead
CREATE UNIQUE INDEX idx_lic_auditoria_lead ON lic_auditoria(lead_id);
CREATE INDEX idx_lic_auditoria_estagio ON lic_auditoria(estagio_atual);
CREATE INDEX idx_lic_auditoria_cert_val ON lic_auditoria(certificado_validade)
WHERE certificado_validade IS NOT NULL; -- para alertas de renovação
-- lic_performance: queries por lead + ordenação por período
CREATE INDEX idx_lic_perf_lead_periodo ON lic_performance(lead_id, periodo_ref DESC);
-- lic_historico: timeline ordenada por lead
CREATE INDEX idx_lic_hist_lead_created ON lic_historico(lead_id, created_at DESC);
-- lic_anexos: lookup por lead e tipo
CREATE INDEX idx_lic_anx_lead_tipo ON lic_anexos(lead_id, tipo);
3.8 Row Level Security (RLS)
Todas as tabelas do módulo Licenciamento têm RLS habilitado com política "Allow all" para usuários autenticados. O controle de acesso granular é feito na camada de aplicação (whitelist no frontend).
-- Padrão aplicado em todas as 5 tabelas
ALTER TABLE lic_leads ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow all lic_leads" ON lic_leads
FOR ALL USING (true) WITH CHECK (true);
-- Nota: política permissiva necessária pois o portal público
-- (usuário anônimo) precisa ler/escrever dados de leads via token.
-- Evolução: implementar RLS por token (ver Fase 3 - evolução técnica)
Débito técnico — RLS permissivo: A política atual USING (true) WITH CHECK (true) permite que qualquer usuário autenticado (ou anônimo, no caso do portal) leia e escreva qualquer registro. Isso é adequado para o MVP mas deve ser endurecido na evolução: restringir acesso anônimo apenas a registros cujo form_token corresponda ao token na requisição, e restringir autenticados ao tenant Vendemmia.