245 lines
11 KiB
Markdown
245 lines
11 KiB
Markdown
# Super Admin, Multi-tenant & Clube de Benefícios — Plano de Implementação
|
|
|
|
## Visão Geral
|
|
|
|
Transformar o sistema em uma plataforma multi-tenant SaaS. Cada empresa tem identidade visual, domínio, usuários e rifas isolados. Um Super Admin gerencia todas as empresas. As páginas públicas migram de Bootstrap/HTML para React + Tailwind em uma única entrega que inclui o Clube de Benefícios (Odonto + Médico).
|
|
|
|
---
|
|
|
|
## Decisões de Arquitetura
|
|
|
|
- **Detecção de tenant**: subdomínio (`alemao.app.com`, `ariane.app.com`) + fallback header `X-Tenant-Slug`
|
|
- **Branding**: armazenado no DB (`tenant_config`), injetado via `/api/tenant/config` como CSS variables
|
|
- **Auth do cliente**: CPF+telefone → **email + senha** (bcrypt). Legados mantêm fallback temporário.
|
|
- **Bootstrap removido**: zero reescrita em Bootstrap — próxima versão das páginas públicas já é React
|
|
- **customer-app**: SPA React unificada com landing, login, sorteio, clube e dashboard do cliente
|
|
- **Super Admin**: SPA React separada em `/superadmin`
|
|
- **tenant_id**: todas as tabelas (existentes e novas do clube) recebem `tenant_id` na mesma migration
|
|
|
|
---
|
|
|
|
## Sequência de Execução
|
|
|
|
```
|
|
9A → 9B → 10A → 10B → 10C
|
|
│ └─► 10F (relatórios, após 10B+10C)
|
|
├─► 9E (super admin, paralelo com 10A+10B+10C)
|
|
└─► 9F (admin multi-tenant, paralelo com 10A+10B+10C)
|
|
|
|
9D + 9C + 10D + 10E ← FUSÃO: customer-app React unificado
|
|
(após 9B + 10B + 10C estarem com backend pronto)
|
|
```
|
|
|
|
> 10D (slides Bootstrap) REMOVIDA — Bootstrap não recebe mais desenvolvimento.
|
|
> 9C (multi-ticket) e 10E (carteirinha) são implementados dentro do customer-app React.
|
|
|
|
---
|
|
|
|
## Fase 9A — Fundação Multi-tenant (Backend) ← ATUAL
|
|
|
|
### Banco de dados
|
|
- [x] Criar tabela `tenants` — `id, slug, name, domain, active, created_at`
|
|
- [x] Criar tabela `tenant_config` — `tenant_id, primary_color, secondary_color, logo_url, favicon_url, contact_email, contact_phone, app_url, asaas_key`
|
|
- [x] Adicionar coluna `tenant_id` em `users`
|
|
- [x] Adicionar coluna `tenant_id` em `raffles`
|
|
- [x] Adicionar coluna `tenant_id` em `orders`
|
|
- [x] Adicionar coluna `tenant_id` em `tickets`
|
|
- [x] Adicionar coluna `tenant_id` em `promotions`
|
|
- [x] Adicionar coluna `tenant_id` em `whatsapp_logs`
|
|
- [x] Criar tabelas do clube já com `tenant_id`: `benefit_clubs`, `club_plans`, `club_members`, `club_dependents`, `club_payments`, `club_partners`
|
|
- [x] Inserir tenant padrão `alemao`
|
|
- [x] Popular `tenant_id = 1` em todos os registros existentes
|
|
- [x] Criar índices em `tenant_id` em todas as tabelas
|
|
|
|
### Middleware de tenant
|
|
- [x] Criar `middleware/tenant.js` — detecta por subdomínio e header `X-Tenant-Slug`; anexa `req.tenant`
|
|
- [x] Fallback: hostname sem tenant → responde com `req.tenant` do slug `alemao` (ambiente local)
|
|
- [x] Criar `middleware/tenantAdmin.js` — verifica que admin pertence ao tenant correto
|
|
|
|
### Isolamento de dados
|
|
- [x] Atualizar models `User`, `Raffle`, `Order`, `Ticket`, `Promotion` para filtrar por `tenant_id`
|
|
- [x] Atualizar `analyticsController` com filtro de tenant
|
|
- [x] Atualizar `whatsappService` com `tenant_id` nos logs
|
|
|
|
### API pública
|
|
- [x] `GET /api/tenant/config` — retorna branding do tenant detectado (público)
|
|
|
|
---
|
|
|
|
## Fase 9B — Autenticação do Cliente: Email + Senha
|
|
|
|
### Backend
|
|
- [x] Adicionar colunas `email VARCHAR(255)`, `password_hash VARCHAR(255)` em `users`
|
|
- [x] Atualizar `POST /api/register` — aceitar `nome, email, cpf, telefone, password`
|
|
- [x] Atualizar `POST /api/login` — email+senha; fallback CPF+telefone para legados
|
|
- [x] Criar `POST /api/auth/forgot-password` — token temporário, envia WhatsApp
|
|
- [x] Criar `POST /api/auth/reset-password` — valida token, atualiza hash
|
|
- [x] Criar tabela `password_reset_tokens` — `user_id, token, expires_at, used`
|
|
- [x] Atualizar JWT payload — incluir `tenant_id`
|
|
- [x] `verifyUserToken` — validar `tenant_id` do token
|
|
|
|
### Migração de legados
|
|
- [x] Login CPF+telefone continua funcionando se `password_hash IS NULL`
|
|
- [x] `POST /api/auth/migrate` — legado informa CPF+telefone+novo email+senha
|
|
|
|
---
|
|
|
|
## Fase 10A — Clube de Benefícios: Backend
|
|
|
|
> Tabelas já criadas na 9A com `tenant_id`. Esta fase implementa a lógica de negócio.
|
|
|
|
### Models
|
|
- [x] Criar `models/BenefitClub.js` — CRUD clubes e planos
|
|
- [x] Criar `models/ClubMember.js` — findByCpf, create, updateStatus, listByClub
|
|
- [x] Criar `models/ClubPayment.js` — create, findByMember, listOverdue
|
|
- [x] Criar `models/ClubPartner.js` — CRUD parceiros por clube
|
|
|
|
### Asaas — Assinaturas recorrentes
|
|
- [x] Criar `services/clubAsaasService.js`
|
|
- [x] `createCustomer(cpf, nome, email, telefone)` → `asaas_customer_id`
|
|
- [x] `createSubscription(customerId, planPrice, description)` → `asaas_subscription_id` + primeiro PIX
|
|
- [x] `cancelSubscription(subscriptionId)`
|
|
- [x] `suspendSubscription(subscriptionId)`
|
|
- [x] `listPayments(subscriptionId)` → histórico
|
|
- [x] Webhook `POST /api/webhooks/asaas/club` — `PAYMENT_RECEIVED`, `PAYMENT_OVERDUE`, `PAYMENT_DELETED`
|
|
|
|
### API pública
|
|
- [x] `GET /api/clubs` — clubes ativos do tenant com planos
|
|
- [x] `GET /api/clubs/:slug` — detalhe + parceiros + benefícios
|
|
- [x] `POST /api/clubs/:slug/subscribe` — adesão: cria membro + assinatura Asaas + retorna PIX
|
|
- [x] `GET /api/clubs/card/:cpf` — carteirinha digital (dados não-sensíveis, público)
|
|
- [x] `POST /api/clubs/member/cancel` — cancelamento pelo cliente
|
|
|
|
---
|
|
|
|
## Fase 10B — Clube Odonto
|
|
|
|
### Backend
|
|
- [x] `GET /api/clubs/odonto/partners` — clínicas/dentistas parceiros
|
|
- [x] `GET /api/clubs/odonto/validate/:cpf` — validação pública para recepções de clínicas
|
|
- [x] Worker: membros com pagamento +5 dias em atraso → `status = suspended`
|
|
- [x] Worker: reativar após pagamento de mensalidade em atraso
|
|
|
|
### WhatsApp — Templates
|
|
- [x] `club_welcome` — boas-vindas + link carteirinha
|
|
- [x] `club_payment_reminder` — lembrete 3 dias antes do vencimento
|
|
- [x] `club_payment_confirmed` — confirmação + validade
|
|
- [x] `club_payment_overdue` — inadimplência + link pagar
|
|
- [x] `club_suspended` — plano suspenso
|
|
- [x] `club_canceled` — confirmação de cancelamento
|
|
- [x] Adicionar `club_payment_confirmed` e `club_suspended` em `CRITICAL_TYPES`
|
|
- [x] Criar `services/clubNotificationService.js`
|
|
|
|
### Admin (admin-app)
|
|
- [x] Criar `ClubOdonto.tsx` com KPIs: membros ativos, MRR, inadimplentes, cancelamentos
|
|
- [x] Lista de membros com status, plano, próxima cobrança, botão detalhes
|
|
- [x] Lista de inadimplentes com "Reenviar cobrança WhatsApp"
|
|
- [x] Modal membro: histórico pagamentos, dependentes, ações suspender/cancelar/reativar
|
|
- [x] CRUD parceiros (clínicas)
|
|
- [x] Gráfico evolução de membros (Recharts AreaChart)
|
|
- [x] Registrar rota `/clubs/odonto` no `App.tsx`
|
|
|
|
---
|
|
|
|
## Fase 10C — Clube Médico
|
|
|
|
### Backend
|
|
- [x] Criar clube `medico` com planos Individual e Família
|
|
- [x] `GET /api/clubs/medico/partners` — parceiros por especialidade
|
|
- [x] `GET /api/clubs/medico/validate/:cpf`
|
|
- [x] Template WhatsApp `club_medico_partner_new` — novo parceiro na rede
|
|
|
|
### Admin (admin-app)
|
|
- [x] Criar `ClubMedico.tsx` (componentes reutilizados de `ClubOdonto.tsx`)
|
|
- [x] Filtro de parceiros por especialidade (clínico geral, pediatria, ginecologia, etc.)
|
|
- [x] Registrar rota `/clubs/medico` no `App.tsx`
|
|
|
|
---
|
|
|
|
## Fase 10F — Relatórios do Clube (admin-app)
|
|
|
|
- [x] Criar `ClubReports.tsx`
|
|
- [x] MRR (Monthly Recurring Revenue) — gráfico de linha por mês
|
|
- [x] Churn rate mensal (cancelamentos / total início do mês)
|
|
- [x] Inadimplência — % membros com pagamento em atraso
|
|
- [x] Crescimento — novos vs cancelamentos por mês
|
|
- [x] Distribuição de planos — Individual vs Família por clube (PieChart)
|
|
- [x] Export CSV: membros ativos filtrado por clube/plano/status
|
|
- [x] Registrar rota `/clubs/reports` no `App.tsx`
|
|
|
|
---
|
|
|
|
## Fase 9E — Super Admin (SPA React)
|
|
|
|
### Backend
|
|
- [x] Criar tabela `super_admins` — `id, email, password_hash, name, created_at`
|
|
- [x] Criar `middleware/superAuth.js` — JWT secret separado
|
|
- [x] `POST /superadmin/api/login`
|
|
- [x] `GET /superadmin/api/tenants`
|
|
- [x] `POST /superadmin/api/tenants`
|
|
- [x] `PUT /superadmin/api/tenants/:id`
|
|
- [x] `DELETE /superadmin/api/tenants/:id` — soft delete
|
|
- [x] `GET /superadmin/api/tenants/:id/stats` — receita, usuários, tickets, membros clube
|
|
- [x] `POST /superadmin/api/tenants/:id/admins`
|
|
- [x] Servir `superadmin-app` em `GET /superadmin*`
|
|
|
|
### superadmin-app (React + Tailwind)
|
|
- [x] Setup: Vite + React 18 + TS + Tailwind, output → `public/superadmin/`
|
|
- [x] `LoginPage` — email + senha
|
|
- [x] `Dashboard` — totais globais por empresa
|
|
- [x] `TenantsPage` — tabela empresas, status, receita, membros clube
|
|
- [x] `TenantDetailPage` — stats + formulário branding (logo, cores, domínio)
|
|
- [x] `BrandingEditor` — preview live das cores/logo
|
|
- [x] `TenantAdminsPage` — cria/lista admins por empresa
|
|
|
|
---
|
|
|
|
## Fase 9F — Admin React Multi-tenant
|
|
|
|
- [x] `tenant_id` no JWT do admin (`cloud_token`)
|
|
- [x] Header do admin-app exibe nome e logo do tenant
|
|
- [x] Settings: aba "Identidade Visual" — edita `tenant_config`
|
|
- [x] Settings: `APP_URL` e `asaas_key` por tenant (não mais variáveis globais de ambiente)
|
|
|
|
---
|
|
|
|
## Fase 9D+9C+10D+10E — customer-app React (Entrega Unificada)
|
|
|
|
> Uma única SPA React substitui todas as páginas Bootstrap e entrega todas as funcionalidades do cliente de uma vez.
|
|
|
|
### Setup
|
|
- [x] Criar `customer-app/` — Vite + React 18 + TS + Tailwind
|
|
- [x] Build output → `sistema-nuvem/public/app/`
|
|
- [x] Adicionar `lucide-react`, `axios`, `swiper` (para hero slider)
|
|
- [x] Hook `useTenant()` — carrega `/api/tenant/config`, aplica CSS variables
|
|
- [x] Componente `<TenantProvider>` wrappa o app
|
|
|
|
### Páginas
|
|
- [x] `LandingPage` — hero slider 3 slides (promoções, odonto, médico), seção promoções, seção sorteio, seção clube odonto, seção clube médico
|
|
- [x] `LoginPage` — email+senha, tabs entrar/cadastrar, link "esqueci minha senha"
|
|
- [x] `RafflePage` — grid de números multi-seleção, contador de selecionados + valor, modal checkout 3 etapas (resumo → login/cadastro inline → PIX)
|
|
- [x] `DashboardPage` — meus tickets, opt-in WhatsApp, carteirinha clube
|
|
- [x] `CarteirinhaPage` — `/carteirinha/:token` pública, exibe `<MemberCard>` sem login
|
|
- [x] `ClubSubscribePage` — modal de adesão ao clube (escolha plano → dados → PIX)
|
|
- [x] `BlockedPage` — substitui `bloqueado.html`
|
|
|
|
### Remoção do Bootstrap
|
|
- [x] Remover `bootstrap.min.css` e `bootstrap-icons.min.css` de `public/css/`
|
|
- [x] Remover todos os HTML de `views/landing/` (rotas redirecionadas para `/app/*`)
|
|
|
|
---
|
|
|
|
## Stack Final
|
|
|
|
| Camada | Tecnologia |
|
|
|--------|-----------|
|
|
| Backend | Node.js + Express |
|
|
| Admin | React 18 + Tailwind + Recharts (existente) |
|
|
| Customer app | React 18 + Tailwind (novo — substitui Bootstrap) |
|
|
| Super Admin | React 18 + Tailwind (novo) |
|
|
| CSS | Tailwind — zero Bootstrap |
|
|
| Ícones | Lucide React — zero Bootstrap Icons |
|
|
| DB | PostgreSQL + tabelas tenant + clube |
|
|
| Pagamentos recorrentes | Asaas Subscriptions API |
|
|
| Notificações | WhatsApp via BullMQ (existente + novos templates) |
|