Dashboard-Automatizase/docs/architecture.md
2025-10-05 21:17:43 -03:00

2130 lines
60 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AutomatizaSE Portal - Documento de Arquitetura Full-Stack
## Introdução
Este documento define a arquitetura completa full-stack do **AutomatizaSE Portal**, incluindo sistemas backend, implementação frontend e suas integrações. Serve como fonte única de verdade para desenvolvimento orientado por IA, garantindo consistência em toda a stack tecnológica.
Esta abordagem unificada combina o que tradicionalmente seriam documentos separados de arquitetura backend e frontend, otimizando o processo de desenvolvimento para aplicações fullstack modernas onde essas preocupações estão cada vez mais interligadas.
### Starter Template ou Projeto Existente
**NextJS 14+ App Router** - Projeto greenfield baseado no template oficial do NextJS com App Router, TailwindCSS e TypeScript. Aproveita infraestrutura Supabase existente da AutomatizaSE (auth unificado, database PostgreSQL).
**Constraints:**
- Schema `portal` separado do schema `public` existente no Supabase
- SMTP da AutomatizaSE já configurado no Supabase para recuperação de senha
- EvolutionAPI existente deve ser integrada via REST API
- OAuth do Google Calendar delegado ao n8n (workflow existente)
### Change Log
| Date | Version | Description | Author |
|------------|---------|--------------------------------------------------|--------------------|
| 2025-10-05 | 1.0 | Versão inicial da arquitetura | Winston (Architect)|
| 2025-10-05 | 1.1 | Atualizado para Docker + K8s + ArgoCD (não Vercel) | Winston (Architect)|
---
## Arquitetura de Alto Nível
### Resumo Técnico
O AutomatizaSE Portal é uma **POC simples** com NextJS 14+ App Router containerizado em **Docker**, deployado em **Kubernetes** usando manifests básicos. Utiliza **Supabase** como backend completo (auth, database PostgreSQL, recuperação de senha). O frontend NextJS renderiza páginas SSR e se comunica com API Routes que orquestram chamadas à EvolutionAPI (REST) e Supabase. OAuth do Google Calendar é delegado ao n8n. A aplicação roda em container Docker, exposta via Nginx Ingress. **Arquitetura minimalista para deploy rápido em poucas horas.**
### Escolha de Plataforma e Infraestrutura
**Plataforma:** Kubernetes + Docker + Supabase **(POC Simplificada)**
**Componentes Essenciais:**
- **Docker:** Containerização da aplicação NextJS
- **Kubernetes:** Orquestração básica (deployment + service + ingress)
- **Nginx Ingress:** Roteamento HTTP para o portal
- **Supabase:** Auth + PostgreSQL (schema `portal`) + SMTP
- **EvolutionAPI:** Integração REST WhatsApp (externo)
- **n8n:** OAuth Google Calendar (externo)
**Topologia Simples:**
- **Namespace:** `automatizase-portal`
- **Replicas:** 1 pod (POC, sem HA)
- **Ingress:** HTTP via Nginx Ingress, domínio `portal.automatizase.com` (HTTPS opcional)
- **Recursos:** Limits mínimos (256Mi RAM, 100m CPU)
**Justificativa:**
- **Simplicidade:** Setup rápido em poucas horas
- **Mínimo viável:** Apenas o necessário para rodar NextJS em K8s
- **Supabase gerenciado:** Reduz complexidade de backend
### Estrutura do Repositório
**Estrutura:** Single NextJS App (monorepo não necessário para POC)
**Ferramenta Monorepo:** N/A
**Organização de Pacotes:**
- Projeto NextJS único contendo frontend (`/app`, `/components`) e API routes (`/app/api`)
- Shared types em `/types` (TypeScript interfaces compartilhadas entre frontend e backend)
- Libs utilitárias em `/lib` (Supabase client, EvolutionAPI client, helpers)
**Justificativa:** Para POC simplificada, monorepo com múltiplos packages adiciona complexidade desnecessária. Single app NextJS oferece estrutura clara e setup rápido.
### Diagrama de Arquitetura de Alto Nível (POC Simplificada)
```mermaid
graph TB
User[Usuário<br/>Mobile/Desktop] -->|HTTP| Ingress[Nginx Ingress<br/>portal.automatizase.com]
subgraph K8s["Kubernetes Cluster"]
Ingress -->|Route| Service[Service<br/>ClusterIP:80]
Service --> Pod[Pod<br/>NextJS Container<br/>porta 3000]
Pod -->|Auth + DB| Supabase
Pod -->|API Calls| EvolutionAPI
Pod -->|OAuth Redirect| n8n
end
subgraph External["Serviços Externos"]
Supabase[(Supabase<br/>Auth + PostgreSQL)]
EvolutionAPI[EvolutionAPI<br/>WhatsApp]
n8n[n8n<br/>Google Calendar OAuth]
end
Supabase -->|SMTP| Email[SMTP]
n8n -->|Callback| Service
style User fill:#4A90E2
style Ingress fill:#009639
style Service fill:#326CE5
style Pod fill:#4A90E2
style Supabase fill:#3ECF8E
style EvolutionAPI fill:#25D366
style n8n fill:#FF6D5A
```
**Fluxo Simplificado:**
1. Usuário acessa `portal.automatizase.com`
2. Nginx Ingress roteia para Service K8s (porta 80)
3. Service encaminha para Pod NextJS (porta 3000)
4. NextJS se comunica com Supabase (auth/DB) e EvolutionAPI (WhatsApp)
### Padrões Arquiteturais (Simplificados para POC)
- **Containerized App:** NextJS em Docker, deploy via K8s manifests simples
- **Component-Based UI:** Componentes React com TailwindCSS (Shadcn/ui)
- **API Routes (BFF):** `/app/api/*` centralizam comunicação com EvolutionAPI e Supabase
- **OAuth Delegation:** Google Calendar via n8n (evita gerenciar credenciais)
- **Config via Env Vars:** Secrets e configs via K8s ConfigMap/Secret
---
## Containerização e Orquestração
### Dockerfile (Simplificado para POC)
```dockerfile
# Dockerfile simples para NextJS
FROM node:18-alpine
WORKDIR /app
# Copiar package files
COPY package*.json ./
# Install dependencies
RUN npm ci
# Copiar código
COPY . .
# Build Next.js
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
# Expor porta 3000
EXPOSE 3000
# Start NextJS
CMD ["npm", "start"]
```
**Notas para POC:**
- Build simples sem multi-stage (mais rápido para POC)
- Porta 3000 (padrão NextJS)
- Comando `npm start` roda servidor production
**Build Rápido:**
```bash
# Build
docker build -t portal:latest .
# Test local (opcional)
docker run -p 3000:3000 --env-file .env.local portal:latest
# Tag e push para registry
docker tag portal:latest registry.automatizase.com/portal:latest
docker push registry.automatizase.com/portal:latest
```
---
### Kubernetes Manifests (POC Mínima)
#### 1. Namespace
```yaml
# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: automatizase-portal
```
---
#### 2. ConfigMap
```yaml
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: portal-config
namespace: automatizase-portal
data:
NODE_ENV: "production"
NEXT_PUBLIC_SITE_URL: "http://portal.automatizase.com"
NEXT_PUBLIC_SUPABASE_URL: "https://xxxxx.supabase.co"
```
---
#### 3. Secret
```yaml
# k8s/secret.yaml
# ATENÇÃO: NÃO commitar este arquivo! Criar manualmente no cluster
apiVersion: v1
kind: Secret
metadata:
name: portal-secrets
namespace: automatizase-portal
type: Opaque
stringData:
NEXT_PUBLIC_SUPABASE_ANON_KEY: "eyJhbGci..."
SUPABASE_SERVICE_ROLE_KEY: "eyJhbGci..."
EVOLUTION_API_URL: "https://evolution.automatizase.com"
EVOLUTION_API_KEY: "your-key"
EVOLUTION_INSTANCE_NAMES: "instance1,instance2"
N8N_OAUTH_URL: "https://n8n.automatizase.com/webhook/google-oauth"
```
**Criar secret:**
```bash
kubectl create secret generic portal-secrets \
--from-literal=NEXT_PUBLIC_SUPABASE_ANON_KEY='...' \
--from-literal=SUPABASE_SERVICE_ROLE_KEY='...' \
--from-literal=EVOLUTION_API_URL='...' \
--from-literal=EVOLUTION_API_KEY='...' \
--from-literal=EVOLUTION_INSTANCE_NAMES='...' \
--from-literal=N8N_OAUTH_URL='...' \
-n automatizase-portal
```
---
#### 4. Deployment
```yaml
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: portal
namespace: automatizase-portal
spec:
replicas: 1 # POC: 1 replica suficiente
selector:
matchLabels:
app: portal
template:
metadata:
labels:
app: portal
spec:
containers:
- name: nextjs
image: registry.automatizase.com/portal:latest
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: portal-config
- secretRef:
name: portal-secrets
resources:
requests:
memory: "128Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "200m"
```
---
#### 5. Service
```yaml
# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
name: portal-service
namespace: automatizase-portal
spec:
type: ClusterIP
selector:
app: portal
ports:
- port: 80
targetPort: 3000
```
---
#### 6. Ingress (Nginx)
```yaml
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: portal-ingress
namespace: automatizase-portal
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: portal.automatizase.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: portal-service
port:
number: 80
```
---
### Deploy Rápido (POC)
**Deploy Manual via kubectl:**
```bash
# 1. Criar namespace
kubectl apply -f k8s/namespace.yaml
# 2. Criar ConfigMap
kubectl apply -f k8s/configmap.yaml
# 3. Criar Secret (manualmente com valores reais)
kubectl create secret generic portal-secrets \
--from-literal=NEXT_PUBLIC_SUPABASE_ANON_KEY='seu-anon-key' \
--from-literal=SUPABASE_SERVICE_ROLE_KEY='seu-service-role-key' \
--from-literal=EVOLUTION_API_URL='https://evolution.automatizase.com' \
--from-literal=EVOLUTION_API_KEY='sua-api-key' \
--from-literal=EVOLUTION_INSTANCE_NAMES='instance1,instance2' \
--from-literal=N8N_OAUTH_URL='https://n8n.automatizase.com/webhook/google-oauth' \
-n automatizase-portal
# 4. Deploy app
kubectl apply -f k8s/deployment.yaml
# 5. Criar Service
kubectl apply -f k8s/service.yaml
# 6. Criar Ingress
kubectl apply -f k8s/ingress.yaml
# 7. Verificar
kubectl get pods -n automatizase-portal
kubectl get svc -n automatizase-portal
kubectl get ingress -n automatizase-portal
# 8. Ver logs
kubectl logs -f deployment/portal -n automatizase-portal
```
**Deploy Completo (1 comando):**
```bash
# Aplicar todos manifests de uma vez
kubectl apply -f k8s/
# Aguardar pod ficar ready
kubectl wait --for=condition=ready pod -l app=portal -n automatizase-portal --timeout=120s
```
---
## Tech Stack
| Categoria | Tecnologia | Versão | Propósito | Justificativa |
|-------------------------|------------------|--------------|----------------------------------------------|-------------------------------------------------------------------------------|
| Containerização | Docker | 24+ | Build e runtime de containers | Reprodutibilidade, portabilidade |
| Orquestração | Kubernetes | 1.28+ | Deploy e orquestração | Padrão de mercado, self-healing |
| Ingress Controller | Nginx Ingress | Latest | Roteamento HTTP | Simples, direto ao ponto |
| Linguagem Frontend | TypeScript | 5.3+ | Código frontend type-safe | Type safety reduz bugs, melhora DX com autocomplete |
| Framework Frontend | Next.js | 14.2+ | Framework React SSR | App Router moderno, API Routes integradas, standalone output para containers |
| Biblioteca UI | Shadcn/ui | Latest | Componentes React acessíveis | TailwindCSS-based, customizável, tema escuro nativo, WCAG AA |
| Gerenciamento de Estado | React Hooks | (built-in) | Estado local e context | Simplicidade para POC, Context API para auth global |
| Linguagem Backend | TypeScript | 5.3+ | API Routes type-safe | Shared types com frontend, consistência de linguagem |
| Framework Backend | Next.js API Routes| 14.2+ | Endpoints API | Zero config, stateless (K8s-friendly) |
| Estilo de API | REST | - | HTTP JSON APIs | Simplicidade, EvolutionAPI é REST, sem overhead de GraphQL |
| Banco de Dados | PostgreSQL (Supabase)| 15+ | Database relacional | Schema `portal` isolado, RLS nativo, Supabase gerenciado |
| Cache | Nginx (Ingress) | - | Static assets caching | Cache de assets via Nginx, Redis futuro para app cache |
| Armazenamento de Arquivos| N/A | - | Não requerido na POC | Apenas QR codes (base64 inline) |
| Autenticação | Supabase Auth | Latest | Email/senha, recuperação senha | Auth unificado existente, SMTP configurado, RLS integrado |
| Testes Frontend | Vitest + React Testing Library | Latest | Unit tests componentes | Fast, Vite-based, melhor que Jest |
| Testes Backend | Vitest | Latest | Unit tests API Routes | Mesma ferramenta que frontend, mocking fácil |
| Testes E2E | Playwright | Latest | Testes end-to-end | Multi-browser, fast, trace viewer excelente |
| Build Tool | Turbopack (Next) | (built-in) | Fast refresh e builds | Next.js 14+ default |
| Bundler | Webpack (Next) | (built-in) | Production builds | Next.js production bundler |
| CI/CD | Manual (kubectl) | - | Deploy manual | POC: kubectl apply, sem CI/CD complexo |
| Monitoramento | kubectl logs | - | Logs básicos | POC: logs via kubectl, sem Prometheus/Grafana |
| Framework CSS | TailwindCSS | 3.4+ | Utility-first CSS | Tema escuro fácil, design system AutomatizaSE, Shadcn/ui compatible |
---
## Modelos de Dados
### User
**Propósito:** Representa usuário autenticado do portal (gerenciado pelo Supabase Auth, tabela `auth.users`)
**Atributos Principais:**
- `id`: UUID - ID único do usuário (primary key Supabase Auth)
- `email`: string - Email do usuário (usado para login)
- `created_at`: timestamp - Data de criação da conta
- `updated_at`: timestamp - Última atualização
#### Interface TypeScript
```typescript
// Baseado em Supabase Auth User
interface User {
id: string; // UUID
email: string;
created_at: string; // ISO timestamp
updated_at: string; // ISO timestamp
}
```
#### Relacionamentos
- One-to-One com `UserSettings`
- One-to-Many com `GoogleCalendarIntegration`
---
### UserSettings
**Propósito:** Configurações e preferências do usuário específicas do portal
**Atributos Principais:**
- `user_id`: UUID - FK para `auth.users.id`
- `created_at`: timestamp - Data de criação
- `updated_at`: timestamp - Última atualização
#### Interface TypeScript
```typescript
interface UserSettings {
user_id: string; // UUID, FK to auth.users
created_at: string;
updated_at: string;
}
```
#### Relacionamentos
- Belongs-to `User` (auth.users)
---
### GoogleCalendarIntegration
**Propósito:** Armazena status de autorização OAuth do Google Calendar por usuário
**Atributos Principais:**
- `id`: UUID - Primary key
- `user_id`: UUID - FK para `auth.users.id`
- `is_connected`: boolean - Status de conexão
- `connected_email`: string | null - Email da conta Google autorizada
- `connected_at`: timestamp | null - Data da última autorização
- `created_at`: timestamp - Data de criação
- `updated_at`: timestamp - Última atualização
#### Interface TypeScript
```typescript
interface GoogleCalendarIntegration {
id: string; // UUID
user_id: string; // UUID, FK to auth.users
is_connected: boolean;
connected_email: string | null;
connected_at: string | null; // ISO timestamp
created_at: string;
updated_at: string;
}
```
#### Relacionamentos
- Belongs-to `User` (auth.users)
---
### WhatsAppInstance
**Propósito:** Modelo virtual (não armazenado no DB) representando instância EvolutionAPI
**Atributos Principais:**
- `instanceName`: string - Nome da instância
- `status`: 'connected' | 'disconnected' - Status de conexão
- `qrCode`: string | null - Base64 do QR code (quando gerado)
#### Interface TypeScript
```typescript
interface WhatsAppInstance {
instanceName: string;
status: 'connected' | 'disconnected';
qrCode?: string | null; // Base64 image
}
```
#### Relacionamentos
- Não persistido no Supabase (dados efêmeros da EvolutionAPI)
---
## Especificação da API
### REST API
```yaml
openapi: 3.0.0
info:
title: AutomatizaSE Portal API
version: 1.0.0
description: API interna do portal (NextJS API Routes)
servers:
- url: https://portal.automatizase.com/api
description: Production
- url: http://localhost:3000/api
description: Development
paths:
/api/whatsapp/instances:
get:
summary: Lista todas as instâncias WhatsApp
security:
- SupabaseAuth: []
responses:
'200':
description: Lista de instâncias com status
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/WhatsAppInstance'
/api/whatsapp/instances/{instanceName}/qrcode:
post:
summary: Gera QR code de conexão
parameters:
- name: instanceName
in: path
required: true
schema:
type: string
security:
- SupabaseAuth: []
responses:
'200':
description: QR code gerado
content:
application/json:
schema:
type: object
properties:
qrCode:
type: string
/api/whatsapp/instances/{instanceName}/disconnect:
post:
summary: Desconecta instância WhatsApp
parameters:
- name: instanceName
in: path
required: true
schema:
type: string
security:
- SupabaseAuth: []
responses:
'200':
description: Instância desconectada
/api/google-calendar/status:
get:
summary: Status de conexão do Google Calendar
security:
- SupabaseAuth: []
responses:
'200':
description: Status da integração
content:
application/json:
schema:
$ref: '#/components/schemas/GoogleCalendarStatus'
/api/google-calendar/callback:
get:
summary: Callback OAuth do n8n
parameters:
- name: success
in: query
schema:
type: boolean
- name: email
in: query
schema:
type: string
security:
- SupabaseAuth: []
responses:
'302':
description: Redirect para dashboard
components:
schemas:
WhatsAppInstance:
type: object
properties:
instanceName:
type: string
status:
type: string
enum: [connected, disconnected]
qrCode:
type: string
nullable: true
GoogleCalendarStatus:
type: object
properties:
is_connected:
type: boolean
connected_email:
type: string
nullable: true
connected_at:
type: string
format: date-time
nullable: true
securitySchemes:
SupabaseAuth:
type: http
scheme: bearer
bearerFormat: JWT
```
---
## Componentes
### NextJS Frontend App
**Responsabilidade:** Renderiza UI do portal, gerencia estado da aplicação
**Interfaces Principais:**
- Pages: `/login`, `/reset-password`, `/dashboard`
- Components: `WhatsAppInstanceCard`, `GoogleCalendarCard`, `Header`, `Footer`
- Hooks: `useAuth`, `useWhatsAppInstances`, `useGoogleCalendar`
**Dependências:** Supabase Auth Client, API Routes, Shadcn/ui
**Tech Stack:** Next.js 14, React 18, TypeScript, TailwindCSS, Shadcn/ui
---
### NextJS API Routes (BFF)
**Responsabilidade:** Orquestra chamadas à EvolutionAPI e Supabase, protege API keys
**Interfaces Principais:**
- `/api/whatsapp/instances` - Lista instâncias
- `/api/whatsapp/instances/[name]/qrcode` - Gera QR code
- `/api/whatsapp/instances/[name]/disconnect` - Desconecta
- `/api/google-calendar/status` - Status OAuth
- `/api/google-calendar/callback` - Callback OAuth
**Dependências:** EvolutionAPI client, Supabase client
**Tech Stack:** Next.js API Routes, TypeScript, Vercel Edge Functions
---
### Supabase Backend
**Responsabilidade:** Autenticação, autorização, persistência de dados
**Interfaces Principais:**
- Supabase Auth API (login, logout, resetPassword)
- PostgreSQL schema `portal`
- Row Level Security policies
**Dependências:** PostgreSQL 15+, SMTP AutomatizaSE
**Tech Stack:** Supabase (Auth + Database), PostgreSQL
---
### Diagrama de Componentes
```mermaid
graph TB
subgraph "Frontend (Vercel)"
Pages[Pages<br/>/login /dashboard]
Components[Components<br/>Cards, Modals]
Hooks[Custom Hooks<br/>useAuth useAPI]
Pages --> Components
Pages --> Hooks
Components --> Hooks
end
subgraph "Backend (API Routes)"
WhatsAppAPI[WhatsApp Routes]
CalendarAPI[Calendar Routes]
AuthMW[Auth Middleware]
WhatsAppAPI --> AuthMW
CalendarAPI --> AuthMW
end
subgraph "External Services"
Evolution[EvolutionAPI]
Supabase[Supabase]
n8n[n8n OAuth]
end
Hooks --> WhatsAppAPI
Hooks --> CalendarAPI
Hooks --> Supabase
WhatsAppAPI --> Evolution
CalendarAPI --> Supabase
AuthMW --> Supabase
Pages --> n8n
n8n --> CalendarAPI
style Pages fill:#4A90E2
style Components fill:#4A90E2
style WhatsAppAPI fill:#000
style CalendarAPI fill:#000
style Evolution fill:#25D366
style Supabase fill:#3ECF8E
style n8n fill:#FF6D5A
```
---
## APIs Externas
### EvolutionAPI
- **Propósito:** Gerenciar instâncias WhatsApp
- **Documentação:** [EvolutionAPI Docs](https://doc.evolution-api.com/)
- **Base URL:** `{EVOLUTION_API_URL}` (env var)
- **Autenticação:** Bearer token `{EVOLUTION_API_KEY}`
- **Rate Limits:** Não documentado
**Endpoints Principais:**
- `GET /instance/fetchInstances` - Listar instâncias (verificar docs)
- `GET /instance/connect/{instanceName}` - Gerar QR code (verificar docs)
- `DELETE /instance/logout/{instanceName}` - Desconectar (verificar docs)
**Notas de Integração:**
- **Investigação necessária:** Confirmar endpoints exatos na documentação oficial
- **Error handling:** Implementar retry com backoff, timeout 10s
- **Validação:** Verificar que instâncias em `EVOLUTION_INSTANCE_NAMES` existem
---
### Google Calendar OAuth (via n8n)
- **Propósito:** Autorizar integração com Google Calendar
- **Documentação:** Workflow n8n interno
- **Base URL:** `{N8N_OAUTH_URL}` (env var)
- **Autenticação:** N/A (n8n gerencia)
- **Rate Limits:** N/A
**Endpoints:**
- `GET {N8N_OAUTH_URL}?user_id={userId}&redirect_uri={callbackURL}` - Inicia OAuth
**Notas de Integração:**
- **Redirect flow:** Portal → n8n → Google → n8n → Portal callback
- **Callback:** `/api/google-calendar/callback?success=true&email={email}` ou `?error={msg}`
- **Reconectar:** Mesmo fluxo atualiza email conectado
---
## Workflows Principais
### Login Flow
```mermaid
sequenceDiagram
actor User
participant LoginPage
participant SupabaseAuth
participant Dashboard
User->>LoginPage: Acessa /login
LoginPage->>User: Exibe formulário
User->>LoginPage: Preenche email/senha
LoginPage->>SupabaseAuth: signInWithPassword()
alt Sucesso
SupabaseAuth->>LoginPage: JWT token
LoginPage->>Dashboard: Redirect /dashboard
Dashboard->>User: Exibe dashboard
else Erro
SupabaseAuth->>LoginPage: Error 401
LoginPage->>User: "Email ou senha inválidos"
end
```
---
### WhatsApp QR Code Flow
```mermaid
sequenceDiagram
actor User
participant Dashboard
participant API
participant EvolutionAPI
participant Modal
User->>Dashboard: Clica "Gerar QR Code"
Dashboard->>API: POST /api/.../qrcode
API->>EvolutionAPI: GET /instance/connect/{name}
alt Sucesso
EvolutionAPI->>API: QR code (base64)
API->>Dashboard: { qrCode: "..." }
Dashboard->>Modal: Exibe QR code
Modal->>User: Modal com QR
Note over User,EvolutionAPI: Polling a cada 5s
EvolutionAPI->>API: Status "connected"
API->>Dashboard: Status atualizado
Dashboard->>User: Badge verde
else Erro
EvolutionAPI->>API: Error
API->>Dashboard: Error response
Dashboard->>User: Toast erro
end
```
---
### Google Calendar OAuth Flow
```mermaid
sequenceDiagram
actor User
participant Dashboard
participant n8n
participant Google
participant Callback
participant Supabase
User->>Dashboard: Clica "Conectar Calendar"
Dashboard->>n8n: Redirect {N8N_OAUTH_URL}
n8n->>Google: Redirect OAuth
User->>Google: Autoriza
Google->>n8n: Callback com code
n8n->>n8n: Troca code por token
n8n->>Callback: Redirect /callback?success=true&email={email}
Callback->>Supabase: UPDATE google_calendar_integration
Supabase->>Callback: Success
Callback->>Dashboard: Redirect /dashboard
Dashboard->>User: Toast "Conectado!" + Badge verde
```
---
## Schema do Banco de Dados
### Schema: `portal`
```sql
-- Schema portal
CREATE SCHEMA IF NOT EXISTS portal;
-- Tabela: portal.user_settings
CREATE TABLE portal.user_settings (
user_id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Trigger para auto-update de updated_at
CREATE OR REPLACE FUNCTION portal.update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_user_settings_updated_at
BEFORE UPDATE ON portal.user_settings
FOR EACH ROW
EXECUTE FUNCTION portal.update_updated_at_column();
-- RLS: Usuário só vê próprias configurações
ALTER TABLE portal.user_settings ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own settings"
ON portal.user_settings FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can update own settings"
ON portal.user_settings FOR UPDATE
USING (auth.uid() = user_id);
-- Tabela: portal.google_calendar_integration
CREATE TABLE portal.google_calendar_integration (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
is_connected BOOLEAN DEFAULT FALSE,
connected_email VARCHAR(255),
connected_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id)
);
CREATE TRIGGER update_google_calendar_integration_updated_at
BEFORE UPDATE ON portal.google_calendar_integration
FOR EACH ROW
EXECUTE FUNCTION portal.update_updated_at_column();
-- RLS
ALTER TABLE portal.google_calendar_integration ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own google calendar integration"
ON portal.google_calendar_integration FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own google calendar integration"
ON portal.google_calendar_integration FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own google calendar integration"
ON portal.google_calendar_integration FOR UPDATE
USING (auth.uid() = user_id);
-- Indexes
CREATE INDEX idx_user_settings_user_id ON portal.user_settings(user_id);
CREATE INDEX idx_google_calendar_integration_user_id ON portal.google_calendar_integration(user_id);
CREATE INDEX idx_google_calendar_integration_is_connected ON portal.google_calendar_integration(is_connected);
```
---
## Arquitetura Frontend
### Arquitetura de Componentes
#### Organização de Componentes
```
src/
├── components/
│ ├── ui/ # Shadcn/ui components
│ ├── layout/
│ │ ├── Header.tsx
│ │ ├── Footer.tsx
│ │ └── DashboardLayout.tsx
│ ├── whatsapp/
│ │ ├── WhatsAppInstanceCard.tsx
│ │ ├── WhatsAppInstanceList.tsx
│ │ └── QRCodeModal.tsx
│ └── google-calendar/
│ └── GoogleCalendarCard.tsx
```
#### Template de Componente
```typescript
// components/whatsapp/WhatsAppInstanceCard.tsx
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { WhatsAppInstance } from '@/types/whatsapp';
interface WhatsAppInstanceCardProps {
instance: WhatsAppInstance;
onGenerateQRCode: (instanceName: string) => void;
onDisconnect: (instanceName: string) => void;
isLoading?: boolean;
}
export function WhatsAppInstanceCard({
instance,
onGenerateQRCode,
onDisconnect,
isLoading = false,
}: WhatsAppInstanceCardProps) {
const isConnected = instance.status === 'connected';
return (
<Card className="bg-gray-800 border-gray-700">
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span className="text-white">{instance.instanceName}</span>
<Badge variant={isConnected ? 'success' : 'destructive'}>
{isConnected ? 'Conectado' : 'Desconectado'}
</Badge>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{!isConnected && (
<Button
onClick={() => onGenerateQRCode(instance.instanceName)}
disabled={isLoading}
className="w-full bg-blue-600 hover:bg-blue-700"
>
Gerar QR Code
</Button>
)}
{isConnected && (
<Button
onClick={() => onDisconnect(instance.instanceName)}
disabled={isLoading}
variant="outline"
className="w-full"
>
Desconectar
</Button>
)}
</CardContent>
</Card>
);
}
```
### Gerenciamento de Estado
#### Estrutura de Estado
```typescript
// stores/auth.ts
import { createContext, useContext, useState, useEffect } from 'react';
import { User } from '@supabase/supabase-js';
import { supabase } from '@/lib/supabase';
interface AuthContextType {
user: User | null;
loading: boolean;
signOut: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null);
setLoading(false);
});
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null);
});
return () => subscription.unsubscribe();
}, []);
const signOut = async () => {
await supabase.auth.signOut();
setUser(null);
};
return (
<AuthContext.Provider value={{ user, loading, signOut }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
}
```
#### Padrões de Gerenciamento de Estado
- **Global auth state:** React Context (`AuthContext`) para sessão
- **Component-local state:** `useState` para UI state
- **Server state:** Custom hooks com `fetch` e polling
- **Form state:** `useState` ou React Hook Form
### Arquitetura de Roteamento
#### Organização de Rotas
```
app/
├── (auth)/
│ ├── login/page.tsx
│ └── reset-password/page.tsx
├── (dashboard)/
│ ├── layout.tsx # Protected layout
│ └── dashboard/page.tsx
├── api/
│ ├── whatsapp/
│ └── google-calendar/
└── layout.tsx # Root layout
```
#### Padrão de Rota Protegida
```typescript
// app/(dashboard)/layout.tsx
'use client';
import { useAuth } from '@/stores/auth';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { DashboardLayout } from '@/components/layout/DashboardLayout';
export default function ProtectedLayout({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!loading && !user) {
router.push('/login');
}
}, [user, loading, router]);
if (loading) return <div>Loading...</div>;
if (!user) return null;
return <DashboardLayout>{children}</DashboardLayout>;
}
```
### Camada de Serviços Frontend
#### Setup do Cliente API
```typescript
// lib/api-client.ts
import { supabase } from '@/lib/supabase';
interface FetchOptions extends RequestInit {
requireAuth?: boolean;
}
export async function apiClient<T>(
endpoint: string,
options: FetchOptions = {}
): Promise<T> {
const { requireAuth = true, ...fetchOptions } = options;
const headers: HeadersInit = {
'Content-Type': 'application/json',
...fetchOptions.headers,
};
if (requireAuth) {
const { data: { session } } = await supabase.auth.getSession();
if (!session) throw new Error('Unauthorized');
headers['Authorization'] = `Bearer ${session.access_token}`;
}
const res = await fetch(`${process.env.NEXT_PUBLIC_SITE_URL}${endpoint}`, {
...fetchOptions,
headers,
});
if (!res.ok) {
const errorData = await res.json().catch(() => ({ error: { message: 'Unknown error' } }));
throw new Error(errorData.error?.message || `Request failed: ${res.status}`);
}
return res.json();
}
```
#### Exemplo de Serviço
```typescript
// services/whatsapp.service.ts
import { apiClient } from '@/lib/api-client';
import { WhatsAppInstance } from '@/types/whatsapp';
export const whatsappService = {
async getInstances(): Promise<WhatsAppInstance[]> {
return apiClient<WhatsAppInstance[]>('/api/whatsapp/instances');
},
async generateQRCode(instanceName: string): Promise<{ qrCode: string }> {
return apiClient<{ qrCode: string }>(`/api/whatsapp/instances/${instanceName}/qrcode`, {
method: 'POST',
});
},
async disconnectInstance(instanceName: string): Promise<{ success: boolean }> {
return apiClient(`/api/whatsapp/instances/${instanceName}/disconnect`, {
method: 'POST',
});
},
};
```
---
## Arquitetura Backend
### Arquitetura de Serviços (Serverless)
#### Organização de Funções
```
app/api/
├── whatsapp/
│ └── instances/
│ ├── route.ts
│ └── [name]/
│ ├── qrcode/route.ts
│ └── disconnect/route.ts
└── google-calendar/
├── status/route.ts
└── callback/route.ts
```
#### Template de Função
```typescript
// app/api/whatsapp/instances/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { evolutionAPI } from '@/lib/evolutionapi';
export async function GET(request: NextRequest) {
try {
// 1. Authenticate
const supabase = createRouteHandlerClient({ cookies });
const { data: { session } } = await supabase.auth.getSession();
if (!session) {
return NextResponse.json(
{ error: { code: 'UNAUTHORIZED', message: 'Not authenticated' } },
{ status: 401 }
);
}
// 2. Parse instance names
const instanceNames = process.env.EVOLUTION_INSTANCE_NAMES?.split(',') || [];
// 3. Fetch status from EvolutionAPI
const instances = await Promise.all(
instanceNames.map(async (name) => {
try {
const status = await evolutionAPI.getInstanceStatus(name.trim());
return { instanceName: name.trim(), status };
} catch (error) {
console.error(`Failed to get status for ${name}:`, error);
return { instanceName: name.trim(), status: 'disconnected' as const };
}
})
);
return NextResponse.json(instances);
} catch (error) {
console.error('GET /api/whatsapp/instances error:', error);
return NextResponse.json(
{ error: { code: 'INTERNAL_ERROR', message: 'Failed to fetch instances' } },
{ status: 500 }
);
}
}
```
### Arquitetura de Banco de Dados
#### Design do Schema
*Veja seção "Schema do Banco de Dados" acima*
#### Camada de Acesso a Dados
```typescript
// lib/db/google-calendar.repository.ts
import { createClient } from '@supabase/supabase-js';
import { GoogleCalendarIntegration } from '@/types/google-calendar';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
export const googleCalendarRepository = {
async getByUserId(userId: string): Promise<GoogleCalendarIntegration | null> {
const { data, error } = await supabase
.from('portal.google_calendar_integration')
.select('*')
.eq('user_id', userId)
.single();
if (error && error.code !== 'PGRST116') throw error;
return data;
},
async upsert(userId: string, email: string): Promise<GoogleCalendarIntegration> {
const { data, error } = await supabase
.from('portal.google_calendar_integration')
.upsert({
user_id: userId,
is_connected: true,
connected_email: email,
connected_at: new Date().toISOString(),
})
.select()
.single();
if (error) throw error;
return data;
},
};
```
### Autenticação e Autorização
#### Fluxo de Auth
```mermaid
sequenceDiagram
actor User
participant Frontend
participant SupabaseAuth
participant APIRoute
participant DB
User->>Frontend: Login
Frontend->>SupabaseAuth: signInWithPassword()
SupabaseAuth->>Frontend: JWT token
Frontend->>Frontend: Armazena token
User->>Frontend: Acessa /dashboard
Frontend->>APIRoute: GET /api/... (Bearer JWT)
APIRoute->>SupabaseAuth: Valida JWT
SupabaseAuth->>APIRoute: Valid session
APIRoute->>DB: Query com RLS
DB->>APIRoute: Filtered data
APIRoute->>Frontend: Response
```
#### Middleware/Guards
```typescript
// lib/auth/auth-middleware.ts
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { NextRequest, NextResponse } from 'next/server';
export async function requireAuth(request: NextRequest) {
const supabase = createRouteHandlerClient({ cookies });
const { data: { session } } = await supabase.auth.getSession();
if (!session) {
return {
error: NextResponse.json(
{ error: { code: 'UNAUTHORIZED', message: 'Authentication required' } },
{ status: 401 }
),
session: null,
};
}
return { error: null, session };
}
```
---
## Estrutura Unificada do Projeto
```plaintext
dashboard-promova/
├── app/
│ ├── (auth)/
│ │ ├── login/page.tsx
│ │ └── reset-password/page.tsx
│ ├── (dashboard)/
│ │ ├── layout.tsx
│ │ └── dashboard/page.tsx
│ ├── api/
│ │ ├── health/ # Health check para K8s probes
│ │ │ └── route.ts
│ │ ├── whatsapp/
│ │ │ └── instances/
│ │ │ ├── route.ts
│ │ │ └── [name]/
│ │ │ ├── qrcode/route.ts
│ │ │ └── disconnect/route.ts
│ │ └── google-calendar/
│ │ ├── status/route.ts
│ │ └── callback/route.ts
│ └── layout.tsx
├── components/
│ ├── ui/
│ ├── layout/
│ ├── whatsapp/
│ └── google-calendar/
├── lib/
│ ├── supabase.ts
│ ├── evolutionapi.ts
│ ├── api-client.ts
│ ├── auth/
│ └── db/
├── types/
│ ├── whatsapp.ts
│ ├── google-calendar.ts
│ └── api.ts
├── hooks/
│ ├── useAuth.ts
│ └── useWhatsAppInstances.ts
├── services/
│ ├── whatsapp.service.ts
│ └── google-calendar.service.ts
├── styles/
│ └── globals.css
├── public/
├── docs/
│ ├── prd.md
│ ├── architecture.md
│ └── prd/ # PRD fragmentado (shards)
├── k8s/ # Kubernetes manifests
│ ├── namespace.yaml
│ ├── configmap.yaml
│ ├── secret.yaml # (gitignored ou encrypted)
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── hpa.yaml # (opcional)
│ └── kustomization.yaml # (opcional, para overlays dev/staging/prod)
├── argocd/ # ArgoCD Application definitions
│ └── application.yaml
├── .github/
│ └── workflows/
│ ├── ci.yaml # CI: lint, test, build
│ └── docker-build.yaml # Build e push Docker image
├── .env.local.example
├── .dockerignore
├── Dockerfile # Multi-stage Docker build
├── next.config.js
├── tailwind.config.js
├── tsconfig.json
├── package.json
└── README.md
```
**Novos Arquivos/Pastas:**
- **`app/api/health/route.ts`:** Endpoint de health check para liveness/readiness probes K8s
- **`k8s/`:** Manifests Kubernetes (namespace, deployment, service, ingress, etc.)
- **`argocd/`:** ArgoCD Application definition para GitOps
- **`.github/workflows/`:** CI/CD pipelines (GitHub Actions para CI, ArgoCD para CD)
- **`Dockerfile`:** Multi-stage build para produção
- **`.dockerignore`:** Excluir arquivos desnecessários do build context
---
## Workflow de Desenvolvimento
### Setup de Desenvolvimento Local
#### Pré-requisitos
```bash
# Node.js 18+ e npm
node --version # >= 18
npm --version # >= 9
# Docker (para build de imagens e teste local)
docker --version # >= 24
# kubectl (para interagir com cluster K8s)
kubectl version --client # >= 1.28
# (Opcional) k9s para gerenciamento visual do cluster
k9s version
```
#### Setup Inicial
```bash
# Clone repository
git clone https://github.com/automatizase/dashboard-promova.git
cd dashboard-promova
# Install dependencies
npm install
# Copy environment template
cp .env.local.example .env.local
# Edit .env.local com credenciais
# nano .env.local
# Start development (local, sem Docker)
npm run dev
```
#### Comandos de Desenvolvimento
**Desenvolvimento Local (sem Docker):**
```bash
# Start dev server
npm run dev
# Run tests
npm run test
# Run E2E tests
npm run test:e2e
# Lint
npm run lint
# Build production
npm run build
# Start production
npm run start
```
**Desenvolvimento com Docker (simula produção):**
```bash
# Build Docker image
docker build -t portal:dev .
# Run container local
docker run -p 3000:3000 \
--env-file .env.local \
portal:dev
# Acessar: http://localhost:3000
# Stop container
docker ps # Ver CONTAINER ID
docker stop <CONTAINER_ID>
```
**Build e Push para Registry (CI/CD):**
```bash
# Build com tag de versão
docker build -t registry.automatizase.com/portal:1.0.0 .
# Login no registry privado
docker login registry.automatizase.com
# Push para registry
docker push registry.automatizase.com/portal:1.0.0
# Tag como latest
docker tag registry.automatizase.com/portal:1.0.0 registry.automatizase.com/portal:latest
docker push registry.automatizase.com/portal:latest
```
**Deploy para Kubernetes (via kubectl manual, ou ArgoCD automatizado):**
```bash
# Aplicar todos manifests K8s
kubectl apply -f k8s/
# Verificar pods
kubectl get pods -n automatizase-portal
# Ver logs
kubectl logs -f deployment/portal-deployment -n automatizase-portal
# Port-forward para testar local
kubectl port-forward svc/portal-service 3000:80 -n automatizase-portal
# Rollback (se algo der errado)
kubectl rollout undo deployment/portal-deployment -n automatizase-portal
# Verificar status do Ingress
kubectl get ingress -n automatizase-portal
```
### Configuração de Ambiente
#### Variáveis de Ambiente Necessárias
```bash
# Frontend (.env.local)
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci...
# Backend
SUPABASE_SERVICE_ROLE_KEY=eyJhbGci...
EVOLUTION_API_URL=https://evolution.automatizase.com
EVOLUTION_API_KEY=your-key
EVOLUTION_INSTANCE_NAMES=instance1,instance2,instance3
N8N_OAUTH_URL=https://n8n.automatizase.com/webhook/google-oauth
```
---
## Arquitetura de Deploy
### Estratégia de Deploy
**Containerização:**
- **Build Tool:** Docker multi-stage build
- **Base Image:** node:18-alpine (otimizado para produção)
- **Output:** Container image com NextJS standalone build
- **Registry:** Registry privado (registry.automatizase.com) ou Docker Hub
**Orquestração Kubernetes:**
- **Platform:** Kubernetes cluster (self-hosted ou cloud)
- **Namespace:** `automatizase-portal`
- **Replicas:** 2+ pods (alta disponibilidade via HPA)
- **Service:** ClusterIP (internal)
- **Ingress:** Nginx Ingress Controller (HTTPS via Cert-Manager)
**GitOps Deployment:**
- **Tool:** ArgoCD
- **Source:** Git repository (`k8s/` folder)
- **Sync:** Automatizado (self-heal + prune)
- **Rollback:** Via ArgoCD UI ou kubectl
### Pipeline CI/CD
**CI (Continuous Integration) - GitHub Actions:**
```yaml
# .github/workflows/ci.yaml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Type check
run: npx tsc --noEmit
- name: Run unit tests
run: npm run test
- name: Build application
run: npm run build
env:
# Env vars necessárias para build (apenas públicas)
NEXT_PUBLIC_SITE_URL: https://portal.automatizase.com
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
```
**CD (Continuous Delivery) - Docker Build & Push:**
```yaml
# .github/workflows/docker-build.yaml
name: Docker Build and Push
on:
push:
branches: [main]
tags:
- 'v*' # Trigger on version tags (v1.0.0)
env:
REGISTRY: registry.automatizase.com
IMAGE_NAME: portal
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
NEXT_PUBLIC_SITE_URL=https://portal.automatizase.com
- name: Output image tag
run: echo "Image pushed: ${{ steps.meta.outputs.tags }}"
```
**CD (GitOps) - ArgoCD:**
ArgoCD monitora automaticamente o repositório Git (`k8s/` folder) e sincroniza mudanças para o cluster. Quando novo commit é pushado em `main`:
1. GitHub Actions executa CI (testes, lint, build)
2. Se CI passa, GitHub Actions builda e pusha Docker image para registry
3. Developer atualiza `k8s/deployment.yaml` com nova tag de imagem:
```yaml
image: registry.automatizase.com/portal:v1.0.1 # Atualizar aqui
```
4. Commit e push para `main`
5. ArgoCD detecta mudança no Git, atualiza deployment no cluster automaticamente
6. Kubernetes faz rolling update (zero downtime)
**Fluxo Completo:**
```
Developer push → CI tests → Build Docker → Push to registry →
Update k8s/deployment.yaml tag → Push to Git → ArgoCD sync → K8s rolling update
```
### Ambientes
| Ambiente | Frontend URL | Backend URL | K8s Namespace | ArgoCD App | Propósito |
|-------------|----------------------------------------|-----------------------------------------------|------------------------|-----------------|----------------------|
| Development | http://localhost:3000 | http://localhost:3000/api | - | - | Dev local (npm/docker)|
| Staging | https://staging.portal.automatizase.com| https://staging.portal.automatizase.com/api | automatizase-portal-staging | portal-staging | Pre-production K8s |
| Production | https://portal.automatizase.com | https://portal.automatizase.com/api | automatizase-portal | portal-prod | Live K8s |
**Notas:**
- **Development:** Roda localmente (npm dev ou Docker), sem K8s
- **Staging (opcional):** Cluster K8s separado ou namespace separado, mesma infra
- **Production:** Cluster K8s produção, Ingress com TLS, 2+ replicas, HPA ativo
---
## Segurança e Performance
### Requisitos de Segurança
**Segurança Frontend:**
- **CSP Headers:** Configurado em `next.config.js`
- **XSS Prevention:** React auto-escapes, validação de input
- **Secure Storage:** Tokens em httpOnly cookies
**Segurança Backend:**
- **Input Validation:** Validar params, sanitize input
- **Rate Limiting:** Vercel built-in ou Upstash
- **CORS Policy:** Next.js default same-origin
**Segurança de Autenticação:**
- **Token Storage:** httpOnly cookies via Supabase Auth Helpers
- **Session Management:** Supabase gerencia refresh automático
- **Password Policy:** Min 8 caracteres (configurar Supabase)
### Otimização de Performance
**Performance Frontend:**
- **Bundle Size Target:** < 200KB initial bundle
- **Loading Strategy:** SSR para pages, lazy load modals
- **Caching Strategy:** Vercel Edge Cache, SWR para hooks
**Performance Backend:**
- **Response Time Target:** < 500ms P95
- **Database Optimization:** Indexes em `user_id`
- **Caching Strategy:** Redis futuro para cache de status
---
## Estratégia de Testes
### Pirâmide de Testes
```plaintext
E2E (Playwright)
/ \
/ Integration \
/ \
/____________________\
Frontend Unit Backend Unit
(Vitest+RTL) (Vitest)
```
### Organização de Testes
#### Testes Frontend
```plaintext
tests/
├── unit/
│ ├── components/
│ ├── hooks/
│ └── services/
└── integration/
```
#### Testes Backend
```plaintext
tests/
├── api/
│ ├── whatsapp/
│ └── google-calendar/
└── lib/
```
#### Testes E2E
```plaintext
e2e/
├── auth.spec.ts
├── whatsapp-management.spec.ts
└── google-calendar-oauth.spec.ts
```
### Exemplos de Testes
#### Teste de Componente Frontend
```typescript
// tests/unit/components/WhatsAppInstanceCard.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { WhatsAppInstanceCard } from '@/components/whatsapp/WhatsAppInstanceCard';
describe('WhatsAppInstanceCard', () => {
it('renders instance name and status', () => {
const instance = { instanceName: 'test', status: 'connected' as const };
render(
<WhatsAppInstanceCard
instance={instance}
onGenerateQRCode={vi.fn()}
onDisconnect={vi.fn()}
/>
);
expect(screen.getByText('test')).toBeInTheDocument();
expect(screen.getByText('Conectado')).toBeInTheDocument();
});
});
```
#### Teste de API Backend
```typescript
// tests/api/whatsapp/instances.test.ts
import { describe, it, expect, vi } from 'vitest';
import { GET } from '@/app/api/whatsapp/instances/route';
describe('GET /api/whatsapp/instances', () => {
it('returns instances with status', async () => {
const response = await GET(new Request('http://localhost/api/whatsapp/instances'));
const data = await response.json();
expect(response.status).toBe(200);
expect(Array.isArray(data)).toBe(true);
});
});
```
#### Teste E2E
```typescript
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
test('user can login', async ({ page }) => {
await page.goto('/login');
await page.fill('input[type="email"]', 'user@example.com');
await page.fill('input[type="password"]', 'password');
await page.click('button:has-text("Entrar")');
await expect(page).toHaveURL('/dashboard');
});
```
---
## Padrões de Código
### Regras Críticas Fullstack
- **Type Sharing:** Definir types em `/types` e importar
- **API Calls:** Usar service layer, não fetch direto
- **Environment Variables:** Acessar via `process.env`, nunca hardcode
- **Error Handling:** Todos API Routes com try/catch
- **State Updates:** Nunca mutar state diretamente
- **Auth Middleware:** Sempre validar auth em API Routes
- **Tailwind Classes:** Priorizar utility classes
### Convenções de Nomenclatura
| Elemento | Frontend | Backend | Exemplo |
|-----------------|--------------------------|----------------------|---------------------------------|
| Components | PascalCase | - | `WhatsAppInstanceCard.tsx` |
| Hooks | camelCase com 'use' | - | `useAuth.ts` |
| API Routes | - | kebab-case | `/api/whatsapp/instances` |
| Database Tables | - | snake_case | `user_settings` |
| Types | PascalCase | PascalCase | `WhatsAppInstance` |
| Functions | camelCase | camelCase | `generateQRCode()` |
---
## Estratégia de Tratamento de Erros
### Fluxo de Erros
```mermaid
sequenceDiagram
actor User
participant Frontend
participant API
participant External
User->>Frontend: Ação
Frontend->>API: Request
API->>External: Request externo
alt Sucesso
External->>API: Success
API->>Frontend: Success
Frontend->>User: Feedback sucesso
else Erro
External->>API: Error
API->>API: Catch + Log
API->>Frontend: Erro formatado
Frontend->>User: Toast erro
end
```
### Formato de Resposta de Erro
```typescript
interface ApiError {
error: {
code: string;
message: string;
details?: Record<string, any>;
timestamp: string;
requestId: string;
};
}
```
### Tratamento de Erros Frontend
```typescript
// lib/error-handler.ts
export function handleApiError(error: unknown): string {
if (error instanceof ApiClientError) {
const messages: Record<string, string> = {
'UNAUTHORIZED': 'Sessão expirada. Faça login novamente.',
'INSTANCE_NOT_FOUND': 'Instância não encontrada.',
};
return messages[error.code] || error.message;
}
return 'Erro inesperado. Tente novamente.';
}
```
### Tratamento de Erros Backend
```typescript
// lib/error-handler.ts
export function handleRouteError(error: unknown, requestId: string): NextResponse {
console.error('[API Error]', { error, requestId });
if (error instanceof AppError) {
return NextResponse.json(
{
error: {
code: error.code,
message: error.message,
timestamp: new Date().toISOString(),
requestId,
},
},
{ status: error.statusCode }
);
}
return NextResponse.json(
{
error: {
code: 'INTERNAL_ERROR',
message: 'Erro interno do servidor.',
timestamp: new Date().toISOString(),
requestId,
},
},
{ status: 500 }
);
}
```
---
## Monitoramento (POC Simplificada)
### Logs Básicos
**kubectl logs:**
```bash
# Ver logs do pod
kubectl logs -f deployment/portal -n automatizase-portal
# Logs das últimas 100 linhas
kubectl logs --tail=100 deployment/portal -n automatizase-portal
# Logs com timestamp
kubectl logs --timestamps deployment/portal -n automatizase-portal
```
**console.log no NextJS:**
- Logs vão para stdout do container
- Acessíveis via `kubectl logs`
- Suficiente para POC
**Métricas Básicas:**
- Status do pod: `kubectl get pods -n automatizase-portal`
- Recursos usados: `kubectl top pods -n automatizase-portal`
- Eventos: `kubectl get events -n automatizase-portal`
---
## Relatório de Resultados do Checklist
### Resumo Executivo
**Completude da Arquitetura:** 95%
**Prontidão para Desenvolvimento:** **PRONTA** - Arquitetura completa, detalhada e pronta para desenvolvimento orientado por IA.
**Gaps Críticos:**
- EvolutionAPI endpoints exatos não confirmados - **REQUER INVESTIGAÇÃO**
- n8n OAuth callback contract não documentado - **VERIFICAR COM EQUIPE**
### Recomendações
**Ações Imediatas:**
1. **Investigar EvolutionAPI Endpoints**
- Consultar documentação oficial
- Confirmar rotas exatas
- Testar com Postman
2. **Documentar n8n OAuth Callback**
- Confirmar query params de callback
- Testar fluxo OAuth end-to-end
3. **Setup Ambiente de Desenvolvimento**
- Configurar `.env.local`
- Executar migrations do schema `portal`
- Verificar conectividade
**Próximos Passos:**
1. Completar investigações de dependências externas
2. Setup ambiente de desenvolvimento
3. Passar para **Dev Agent** para implementação
4. Seguir estrutura de projeto definida
### Decisão Final
**PRONTA PARA DESENVOLVIMENTO**
A arquitetura está completa e detalhada. Gaps identificadas são investigáveis rapidamente e não bloqueiam início do trabalho.
**Pontos Fortes (POC Simplificada):**
- **Simples e rápida:** Docker + K8s básico, deploy em poucas horas
- **Stack moderna:** Next.js 14 + Supabase + Docker + K8s
- **Mínimo viável:** Apenas o necessário para rodar em K8s
- **NextJS + Supabase:** Frontend e backend conforme PRD
- **Nginx Ingress:** Roteamento HTTP simples
**Checklist Rápido:**
1. Dockerfile simples (sem multi-stage)
2. K8s manifests mínimos (namespace, deployment, service, ingress)
3. ConfigMap e Secret para env vars
4. 1 replica POC (não precisa HA)
5. Nginx Ingress porta 80
6. Deploy via `kubectl apply -f k8s/`
7. Confirmar endpoints EvolutionAPI
8. Testar flow OAuth com n8n
9. Criar secrets com valores reais
---
**Pronto para desenvolvimento! 🚀**