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

60 KiB

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)

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 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:

# 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

# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: automatizase-portal

2. ConfigMap

# 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

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

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

# 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

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

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

# 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):

# 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

// 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

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

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

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

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

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
  • 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

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

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

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

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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

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

// 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

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

# 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

# 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):

# 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):

# 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):

# 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):

# 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

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

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

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

        E2E (Playwright)
        /              \
       /  Integration   \
      /                  \
     /____________________\
    Frontend Unit  Backend Unit
    (Vitest+RTL)   (Vitest)

Organização de Testes

Testes Frontend

tests/
├── unit/
│   ├── components/
│   ├── hooks/
│   └── services/
└── integration/

Testes Backend

tests/
├── api/
│   ├── whatsapp/
│   └── google-calendar/
└── lib/

Testes E2E

e2e/
├── auth.spec.ts
├── whatsapp-management.spec.ts
└── google-calendar-oauth.spec.ts

Exemplos de Testes

Teste de Componente Frontend

// 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

// 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

// 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

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

interface ApiError {
  error: {
    code: string;
    message: string;
    details?: Record<string, any>;
    timestamp: string;
    requestId: string;
  };
}

Tratamento de Erros Frontend

// 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

// 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:

# 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! 🚀