Compare commits

..

1 Commits

Author SHA1 Message Date
Claude dde0fa661a
docs: relatório de sessão 2026-03-18
Cobre: correções TS App.tsx, guia Coolify, análise Superset BI,
comparativo auditoria vs desenvolvimento (52 itens, 44% concluído)
e backlog priorizado das demandas pendentes.

https://claude.ai/code/session_01DinH3VcgbAv1d9MqnNxzdb
2026-03-18 13:12:22 +00:00
5122 changed files with 3076 additions and 668712 deletions

View File

@ -1,24 +0,0 @@
# aider chat started at 2026-03-23 12:27:25
> You can skip this check with --no-gitignore
> Add .aider* to .gitignore (recommended)? (Y)es/(N)o [Yes]: y
> Added .aider* to .gitignore
> /home/ubuntu/.local/bin/aider --message liste os arquivos principais do projeto em uma linha cada
> Warning: ollama/deepseek-r1:14b expects these environment variables
> - OLLAMA_API_BASE: Not set
> Warning for ollama/deepseek-r1:14b: Unknown context window size and costs, using sane defaults.
> You can skip this check with --no-show-model-warnings
> https://aider.chat/docs/llms/warnings.html
> Open documentation url for more info? (Y)es/(N)o/(D)on't ask again [Yes]: y
> Aider v0.86.2
> Model: ollama/deepseek-r1:14b with diff edit format
> Git repo: .git with 5,113 files
> Warning: For large repos, consider using --subtree-only and .aiderignore
> See: https://aider.chat/docs/faq.html#can-i-use-aider-in-a-large-mono-repo
> Repo-map: using 1024 tokens, auto refresh
#### liste os arquivos principais do projeto em uma linha cada
> Initial repo scan can be slow in larger repos, but only happens once.
> litellm.APIConnectionError: OllamaException - [Errno 111] Connection refused
> Retrying in 0.2 seconds...

View File

@ -1,6 +0,0 @@
# 2026-03-23 12:27:36.139575
+liste os arquivos principais do projeto em uma linha cada
# 2026-03-23 12:27:36.139669
+liste os arquivos principais do projeto em uma linha cada

View File

@ -1,25 +0,0 @@
metabase/
attached_assets/
.git/
.cache/
.upm/
.config/
.local/
.pythonlibs/
uploads/
plus/
whatsapp-sessions/
migrations/
docs/
scripts/
server/python/
python-service/
*.tar.gz
*.zip
*.sql
*.log
*.docx
plus_backup_*/
RELATORIO_TECNICO_RETAIL.md
MAPA_GERAL_RETAIL.md
PLANO_EVOLUCAO_ARCADIA.docx

View File

@ -1,47 +0,0 @@
node_modules
dist
.git
*.zip
*.log
.env
.replit
replit.nix
.cache
.config
.upm
# MetaSet - Apache Superset source code (não necessário para build do app principal)
server/bi/metaset/superset-src/
server/bi/metaset/venv/
# Python cache
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.so
# Logs e dados
data/
logs/
*.log
# IDE
.vscode/
.idea/
*.swp
*.swo
# Testes
.pytest_cache/
.playwright-mcp/
coverage/
# Docker
.docker/
# Outros - exceto attached_assets que são necessários
*.md
!attached_assets/
docs/

17
.env
View File

@ -1,17 +0,0 @@
PGPASSWORD=afefb610090de1646944b537f3054715ad5755665c90036c2bcd258c9ab52dd8
SESSION_SECRET=092a26dcbebd6f7f5e7b9e020bdc4d4e966286c9780ef72e3426f437eb8d608b
SSO_SECRET=fbb87d1c5a14a6623d4e3dcc2b679bd68c46e59afb48dd6abd42b95cac945856
LITELLM_API_KEY=e396b827cbeb5e77d85cce4eb9da0521114f57a8e799f7aa8febad08d776c07e
WEBUI_SECRET_KEY=76c7fc044f9e8d6e5eb60b67c82832b67e84739da4827ce1fa7c101ae1a055df
DOMAIN=suite.onboardbi.com.br
OLLAMA_BASE_URL=http://ollama-ia1upsekrad96at5hq97e4qa:11434
# ── Superset BI ───────────────────────────────────────────────
SUPERSET_SECRET_KEY=421274b5ba360778a5398d528116a45ea962d1197e3dfc99f36a372ff1025a63
SUPERSET_ADMIN_USERNAME=admin
SUPERSET_ADMIN_PASSWORD=arcadia1337
SUPERSET_ADMIN_EMAIL=engenharia@ometas.com.br
COMPOSE_PROFILES=bi
SUPERSET_HOST=superset
SUPERSET_PORT=8088

View File

@ -27,11 +27,7 @@ CONTABIL_PYTHON_URL=http://localhost:8003
BI_PYTHON_URL=http://localhost:8004
AUTOMATION_PYTHON_URL=http://localhost:8005
FISCO_PYTHON_URL=http://localhost:8002
# ── Embeddings / Learning Service ──────────────────────────────────────────────
# IMPORTANTE: Usar porta 8000 (não 8001) - o serviço escuta na porta 8000
# Em Docker: http://arcadia-prod-embeddings-1:8000
# Em dev local: http://localhost:8000
PYTHON_SERVICE_URL=http://localhost:8000
PYTHON_SERVICE_URL=http://localhost:8001
# ── IA — OpenAI ───────────────────────────────────────────────────────────────
# Deixe vazio se usar apenas Ollama (soberania total)
@ -53,8 +49,13 @@ AI_INTEGRATIONS_OPENAI_API_KEY=arcadia-internal
# Se Ollama está em outro servidor: OLLAMA_BASE_URL=http://IP_DO_SERVIDOR:11434
OLLAMA_BASE_URL=http://localhost:11434
# ── IA — LLMFit (modelos fine-tuned locais — habilitar quando disponível) ─────
# LLMFit turbocharge: modelos treinados com dados do seu negócio
# Deixe vazio para desabilitar (LiteLLM cai para Ollama automaticamente)
LLMFIT_BASE_URL=
# ── IA — Providers externos (opt-in — soberania: dados não saem sem configurar)
# Deixe vazio para operação 100% soberana (apenas Ollama)
# Deixe vazio para operação 100% soberana (apenas Ollama + LLMFit)
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
GROQ_API_KEY=

12
.gitignore vendored
View File

@ -1,6 +1,9 @@
node_modules/
dist/
.DS_Store
.env
.env.local
.env.production
# Replit specific
.cache/
@ -61,12 +64,3 @@ attached_assets/
# Package lock (regenerated on install)
package-lock.json
.claude/
# MetaSet - Apache Superset fork (não versionar código fonte completo)
server/bi/metaset/superset-src/
server/bi/metaset/venv/
server/bi/metaset/superset
# Backups gerados durante deploy
docker-compose.yml.backup.*
server/kernel/config/services.json.backup.*

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "server/modules/openclaw"]
path = server/modules/openclaw
url = https://github.com/miaoxworld/OpenClawInstaller.git

View File

@ -1,26 +0,0 @@
- generic [ref=e2]:
- region "Notifications (F8)":
- list
- generic [ref=e5]:
- generic [ref=e6]:
- img "Arcadia" [ref=e7]
- heading "Arcádia Suite" [level=1] [ref=e8]
- generic [ref=e9]:
- tablist [ref=e10]:
- tab "Login" [selected] [ref=e11]
- tab "Criar Conta" [ref=e12]
- tabpanel "Login" [ref=e13]:
- generic [ref=e14]:
- generic [ref=e15]:
- generic [ref=e16]: Bem-vindo de volta
- generic [ref=e17]: Entre com suas credenciais para acessar o sistema
- generic [ref=e19]:
- generic [ref=e20]:
- text: Usuário
- textbox "Usuário" [ref=e21]:
- /placeholder: Digite seu usuário
- generic [ref=e22]:
- text: Senha
- textbox "Senha" [ref=e23]:
- /placeholder: Digite sua senha
- button "Entrar" [ref=e24]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,34 +0,0 @@
- generic [ref=e2]:
- region "Notifications (F8)":
- list [ref=e26]:
- status [ref=e27]:
- generic [ref=e28]:
- generic [ref=e29]: Login failed
- generic [ref=e30]: Invalid username or password
- button [ref=e31]:
- img [ref=e32]
- generic [ref=e5]:
- generic [ref=e6]:
- img "Arcadia" [ref=e7]
- heading "Arcádia Suite" [level=1] [ref=e8]
- generic [ref=e9]:
- tablist [ref=e10]:
- tab "Login" [selected] [ref=e11]
- tab "Criar Conta" [ref=e12]
- tabpanel "Login" [ref=e13]:
- generic [ref=e14]:
- generic [ref=e15]:
- generic [ref=e16]: Bem-vindo de volta
- generic [ref=e17]: Entre com suas credenciais para acessar o sistema
- generic [ref=e19]:
- generic [ref=e20]:
- text: Usuário
- textbox "Usuário" [ref=e21]:
- /placeholder: Digite seu usuário
- text: admin
- generic [ref=e22]:
- text: Senha
- textbox "Senha" [ref=e23]:
- /placeholder: Digite sua senha
- text: admin
- button "Entrar" [ref=e24]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@ -1,28 +0,0 @@
- generic [ref=e2]:
- region "Notifications (F8)":
- list
- generic [ref=e5]:
- generic [ref=e6]:
- img "Arcadia" [ref=e7]
- heading "Arcádia Suite" [level=1] [ref=e8]
- generic [ref=e9]:
- tablist [ref=e10]:
- tab "Login" [selected] [ref=e11]
- tab "Criar Conta" [ref=e12]
- tabpanel "Login" [ref=e13]:
- generic [ref=e14]:
- generic [ref=e15]:
- generic [ref=e16]: Bem-vindo de volta
- generic [ref=e17]: Entre com suas credenciais para acessar o sistema
- generic [ref=e19]:
- generic [ref=e20]:
- text: Usuário
- textbox "Usuário" [ref=e21]:
- /placeholder: Digite seu usuário
- text: admin
- generic [ref=e22]:
- text: Senha
- textbox "Senha" [ref=e23]:
- /placeholder: Digite sua senha
- text: admin
- button "Entrar" [ref=e24]

View File

@ -1,30 +0,0 @@
- generic [ref=e2]:
- region "Notifications (F8)":
- list
- generic [ref=e5]:
- generic [ref=e6]:
- img "Arcadia" [ref=e7]
- heading "Arcádia Suite" [level=1] [ref=e8]
- generic [ref=e9]:
- tablist [ref=e10]:
- tab "Login" [selected] [ref=e11]
- tab "Criar Conta" [ref=e12]
- tabpanel "Login" [ref=e13]:
- generic [ref=e14]:
- generic [ref=e15]:
- generic [ref=e16]: Bem-vindo de volta
- generic [ref=e17]: Entre com suas credenciais para acessar o sistema
- generic [ref=e19]:
- generic [ref=e20]:
- text: Usuário
- textbox "Usuário" [ref=e21]:
- /placeholder: Digite seu usuário
- text: admin
- generic [ref=e22]:
- text: Senha
- textbox "Senha" [ref=e23]:
- /placeholder: Digite sua senha
- text: admin
- button "Entrando..." [disabled]:
- img
- text: Entrando...

View File

@ -1,28 +0,0 @@
- generic [ref=e2]:
- region "Notifications (F8)":
- list
- generic [ref=e5]:
- generic [ref=e6]:
- img "Arcadia" [ref=e7]
- heading "Arcádia Suite" [level=1] [ref=e8]
- generic [ref=e9]:
- tablist [ref=e10]:
- tab "Login" [selected] [ref=e11]
- tab "Criar Conta" [ref=e12]
- tabpanel "Login" [ref=e13]:
- generic [ref=e14]:
- generic [ref=e15]:
- generic [ref=e16]: Bem-vindo de volta
- generic [ref=e17]: Entre com suas credenciais para acessar o sistema
- generic [ref=e19]:
- generic [ref=e20]:
- text: Usuário
- textbox "Usuário" [ref=e21]:
- /placeholder: Digite seu usuário
- text: admin
- generic [ref=e22]:
- text: Senha
- textbox "Senha" [ref=e23]:
- /placeholder: Digite sua senha
- text: admin
- button "Entrar" [ref=e24]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Binary file not shown.

View File

@ -20,7 +20,7 @@ server/
client/ # 66 páginas React
shared/schema.ts # Schema do banco (7317 linhas, Drizzle ORM)
docker/
litellm-config.yaml # Roteamento de LLMs (TIER 1: Ollama local, TIER 2: externos)
litellm-config.yaml # Roteamento de LLMs (TIER 1: LLMFit, TIER 2: Ollama, TIER 3: externos)
```
## Arquitetura de IA
@ -29,8 +29,9 @@ Manus / Agents / Embeddings
│ AI_INTEGRATIONS_OPENAI_BASE_URL
LiteLLM :4000 (gateway unificado, loga tudo no banco)
├──► Ollama :11434 (TIER 1 — local, padrão)
└──► OpenAI/Anthropic/Groq (TIER 2 — opt-in, só se API key configurada)
├──► LLMFit (TIER 1 — fine-tuned, soberano) [slot pronto, comentado]
├──► Ollama :11434 (TIER 2 — local, padrão)
└──► OpenAI/Anthropic/Groq (TIER 3 — opt-in, só se API key configurada)
```
**Variáveis chave do Manus:**
@ -44,46 +45,9 @@ AI_INTEGRATIONS_OPENAI_API_KEY=${LITELLM_API_KEY}
- `PLANO_EVOLUCAO_ARCADIA.md` — roadmap e evolução
- `MAPA_SISTEMA_ARCADIA.md` — mapa do sistema
## Git e Deploy
### Remote canônico
- **Remote principal:** `gitea``http://10.0.1.7:3000/arcadia-admin/arcadiasuite` (Gitea interno)
- **Branch de produção:** `main`
- **Push de deploy:** `git push gitea Servidor:main`
> O Coolify monitora o Gitea (`https://git.onboardbi.com.br/arcadia-admin/arcadiasuite`, branch `main`).
> Push para `gitea` → Coolify detecta → build automático → rolling deploy.
> O remote `origin` (GitHub) é backup secundário — push opcional para manter sincronizado.
> O desenvolvimento local ocorre na branch `Servidor` — fazer push para `main` no Gitea ao deployar.
### Deploy em produção
**Fluxo normal (CI/CD automático):**
```bash
cd /opt/arcadia_merged
git push gitea Servidor:main
```
O Coolify faz o build e deploy automaticamente (~3-5 min).
**Deploy manual (emergência):**
```bash
cd /opt/arcadia_merged
docker compose -f docker-compose.prod.yml build app
docker compose -f docker-compose.prod.yml up -d app
```
- Reconstrói só o container `app` (~3-5 min)
- Banco, Redis, MiroFlow, Python services **não são afetados**
- Downtime: ~30 segundos durante a troca do container
### Serviços em execução (não mexer sem necessidade)
```
arcadia-prod-app-1 porta 5000 → suite.onboardbi.com.br (Traefik)
arcadia-prod-db-1 PostgreSQL (dados em volume Docker)
arcadia-prod-redis-1 Redis
arcadia-prod-miroflow-1 MiroFlow agente
arcadia-prod-litellm-1 LiteLLM gateway
arcadia-prod-superset-1 Superset BI
arcadia-prod-{contabil,bi,automation,fisco,embeddings}-1 Python services
```
## Branch de desenvolvimento
Sempre commitar em: `claude/analyze-project-0mXjP`
Push: `git push -u origin claude/analyze-project-0mXjP`
## O que está implementado
- ✅ Manus (agente autônomo, 30+ ferramentas)
@ -95,11 +59,11 @@ arcadia-prod-{contabil,bi,automation,fisco,embeddings}-1 Python services
- ✅ Docker dev + prod, LiteLLM gateway
## O que ainda falta
- ❌ LLMFit: slot pronto em `litellm-config.yaml`, só habilitar quando disponível
- ❌ Testes automatizados / CI-CD
- ❌ Monitoramento (APM, Sentry, métricas)
- ❌ Multi-tenancy completo
- ❌ Rate limiting em todos os endpoints (parcial)
- ❌ Fine-tuning de modelos locais (dados do Arcádia)
## Comandos úteis
```bash
@ -121,6 +85,7 @@ npm run build
```
SESSION_SECRET, SSO_SECRET # gerar strings seguras em prod
AI_INTEGRATIONS_OPENAI_BASE_URL # aponta para LiteLLM
LLMFIT_BASE_URL # LLMFit quando disponível
OLLAMA_BASE_URL # Ollama host ou container
OPENAI_API_KEY # opcional (soberania: deixar vazio)
```

View File

@ -122,6 +122,7 @@ AUTOMATION_PYTHON_URL=http://automation:8005
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
GROQ_API_KEY=
LLMFIT_BASE_URL= # habilitar quando LLMFit estiver disponível
```
### Superset — profile `bi`
@ -255,11 +256,12 @@ Configure backups automáticos no Coolify em **Project → Backups**.
## Stack de IA (LiteLLM + Ollama)
### Dois tiers configurados em `docker/litellm-config.yaml`
### Três tiers configurados em `docker/litellm-config.yaml`
```
TIER 1 — Ollama (local, padrão) → llama3.3, qwen2.5-coder, nomic-embed-text
TIER 2 — Externos (opt-in) → OpenAI, Anthropic, Groq (apenas se API key definida)
TIER 1 — LLMFit (fine-tuned, soberano) → slot pronto, habilitar via LLMFIT_BASE_URL
TIER 2 — Ollama (local, padrão) → llama3.3, qwen2.5-coder, nomic-embed-text
TIER 3 — Externos (opt-in) → OpenAI, Anthropic, Groq (apenas se API key definida)
```
### Baixar modelos Ollama após o primeiro deploy
@ -270,6 +272,12 @@ docker exec arcadia-ollama ollama pull qwen2.5-coder:7b
docker exec arcadia-ollama ollama pull nomic-embed-text
```
### Habilitar LLMFit (quando disponível)
1. Defina `LLMFIT_BASE_URL=http://seu-llmfit:porta`
2. Descomente o bloco TIER 1 em `docker/litellm-config.yaml`
3. Redeploy do serviço `litellm`
---
## Pós-deploy
@ -298,40 +306,6 @@ curl -H "Authorization: Bearer $LITELLM_API_KEY" \
---
## Otimização de Build (.dockerignore)
O arquivo `.dockerignore` é crítico para builds rápidos. Ele evita que arquivos desnecessários sejam enviados para o Docker daemon.
### Estrutura atual otimizada
```
# Excluídos (reduzem contexto de build)
node_modules/ ← Instalados no container
dist/ ← Gerado no build
git/ ← Não necessário
server/bi/metaset/superset-src/ ← 3.7GB de código fonte Superset
server/bi/metaset/venv/ ← Python venv local
__pycache__/ ← Cache Python
.pytest_cache/ ← Cache de testes
.playwright-mcp/ ← Screenshots de teste
docs/ ← Documentação Markdown
*.md ← Arquivos Markdown
# INCLUÍDOS (necessários para build)
attached_assets/ ← Assets de branding (ícones, logos)
```
### Resultado da otimização
| Métrica | Antes | Depois | Melhoria |
|---------|-------|--------|----------|
| Contexto de build | ~5GB | ~93MB | **98% menor** |
| Tempo de transferência | ~2-3 min | ~5-10s | **~90% mais rápido** |
> ⚠️ **Atenção**: Ao adicionar novos diretórios ao `.dockerignore`, verifique se não há assets necessários para o build (como imagens importadas no frontend).
---
## Checklist Final
### Antes do deploy
@ -341,7 +315,6 @@ attached_assets/ ← Assets de branding (ícones, logos)
- [ ] `SESSION_SECRET` e `SSO_SECRET` gerados com `openssl rand -hex 32`
- [ ] Senhas do banco definidas (nunca reutilizar senhas de dev)
- [ ] `DOCKER_MODE=true` definido
- [ ] `.dockerignore` revisado (especialmente após adicionar novos diretórios grandes)
### No Coolify

Binary file not shown.

View File

@ -1,33 +1,33 @@
# ─── Stage 1: Build ───────────────────────────────────────────────────────────
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json* ./
RUN NODE_ENV=development npm install --legacy-peer-deps
COPY package*.json ./
RUN npm ci --ignore-scripts
COPY . .
RUN npm run build
# ─── Stage 2: Production ──────────────────────────────────────────────────────
FROM node:20-alpine AS runner
RUN apk add --no-cache p7zip
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
COPY --from=builder /app/migrations ./migrations
COPY --from=builder /app/drizzle.config.ts ./drizzle.config.ts
COPY --from=builder /app/shared ./shared
COPY --from=builder /app/tsconfig.json ./tsconfig.json
ENV NODE_ENV=production
ENV PORT=5000
# Deps de produção apenas
COPY package*.json ./
RUN npm ci --omit=dev --ignore-scripts
# Artefatos do build
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/shared ./shared
COPY --from=builder /app/migrations ./migrations
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD wget -qO- http://127.0.0.1:5000/ || exit 1
CMD wget -qO- http://localhost:5000/api/health || exit 1
CMD ["node", "dist/index.cjs"]

View File

@ -1,24 +0,0 @@
FROM python:3.11-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Instalar MiroFlow e suas dependências direto do submodule
COPY server/modules/miroflow/ ./server/modules/miroflow/
RUN pip install --no-cache-dir -e ./server/modules/miroflow/
# Copiar o service script
COPY server/python/miroflow_service.py ./server/python/miroflow_service.py
ENV MIROFLOW_PORT=8006
ENV OLLAMA_BASE_URL=http://host.docker.internal:11434
EXPOSE 8006
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8006/health')" || exit 1
CMD ["python", "server/python/miroflow_service.py"]

View File

@ -1,6 +1,6 @@
# Integração de IA — Ollama no Servidor
# Integração de IA — Ollama + LLMFit no Servidor
Guia para conectar o Arcádia Suite à IA local em produção.
Guia para conectar o Arcádia Suite às IAs locais em produção.
---
@ -13,11 +13,11 @@ Arcádia Suite (Manus, Agents, Embeddings)
LiteLLM (porta 4000) — gateway único
├──► Ollama (modelos open source locais) [TIER 1 — padrão]
└──► OpenAI/Anthropic/Groq (opt-in) [TIER 2 — externo]
├──► LLMFit (seus modelos fine-tuned) [TIER 1 — prioridade]
└──► Ollama (modelos open source locais) [TIER 2 — padrão/fallback]
```
Nenhum serviço do Arcádia chama Ollama diretamente.
Nenhum serviço do Arcádia chama Ollama ou LLMFit diretamente.
Tudo passa pelo LiteLLM — que roteia, loga e faz fallback automaticamente.
---
@ -89,6 +89,66 @@ docker exec -it $(docker ps -qf "name=ollama") ollama pull deepseek-r1:7b
---
## Configuração do LLMFit
### 1. Pré-requisito
O LLMFit deve expor uma API compatível com OpenAI (formato `/v1/chat/completions`).
Verifique se está respondendo:
```bash
curl http://IP_DO_LLMFIT:PORTA/v1/models
```
### 2. Configurar variável no Coolify
```
LLMFIT_BASE_URL=http://IP_DO_LLMFIT:PORTA
```
### 3. Ativar no LiteLLM config
Edite `docker/litellm-config.yaml` e **descomente** o bloco TIER 1:
```yaml
model_list:
# TIER 1 — LLMFit (fine-tuned, prioridade máxima)
- model_name: arcadia-finetuned
litellm_params:
model: openai/NOME_DO_SEU_MODELO # substitua pelo nome real
api_base: os.environ/LLMFIT_BASE_URL
api_key: llmfit-internal
# Modelo de embeddings fine-tuned (se disponível)
- model_name: arcadia-embed
litellm_params:
model: openai/NOME_DO_MODELO_EMBED
api_base: os.environ/LLMFIT_BASE_URL
api_key: llmfit-internal
```
### 4. Definir LLMFit como modelo padrão do Arcádia
No mesmo arquivo, atualize o `arcadia-default`:
```yaml
- model_name: arcadia-default
litellm_params:
model: openai/NOME_DO_SEU_MODELO
api_base: os.environ/LLMFIT_BASE_URL
api_key: llmfit-internal
model_info:
fallbacks: ["llama3.3"] # cai para Ollama se LLMFit falhar
```
### 5. Reiniciar o LiteLLM para aplicar
```bash
docker compose -f docker-compose.prod.yml restart litellm
```
---
## Variáveis de ambiente — resumo completo
Configure todas no Coolify antes do deploy:
@ -114,6 +174,9 @@ AI_INTEGRATIONS_OPENAI_API_KEY=${LITELLM_API_KEY}
# Ollama em container: http://ollama:11434
OLLAMA_BASE_URL=http://host-gateway:11434
# ── LLMFit (deixar vazio até estar disponível) ────────────────────────────────
LLMFIT_BASE_URL=
# ── Providers externos (deixar vazio para soberania total) ───────────────────
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
@ -139,7 +202,7 @@ curl http://localhost:4000/v1/chat/completions \
### 2. Testar Manus via interface
Acesse `https://seudominio.com.br` → abra o Manus → envie uma mensagem simples.
O Manus deve responder via Ollama.
O Manus deve responder via Ollama (ou LLMFit se configurado).
### 3. Ver logs em tempo real
@ -190,6 +253,10 @@ docker exec $(docker ps -qf "name=ollama") ollama pull nomic-embed-text
→ O modelo referenciado em `litellm-config.yaml` não foi baixado no Ollama.
→ Execute `ollama pull NOME_DO_MODELO`
**LLMFit não está sendo chamado**
→ Confirme que `LLMFIT_BASE_URL` está definido e o serviço está respondendo.
→ Reinicie o LiteLLM após alterar o config: `docker compose restart litellm`
**Ollama no host não é alcançado de dentro do Docker**
→ Tente `OLLAMA_BASE_URL=http://172.17.0.1:11434` (IP padrão do docker0)
→ Ou use o IP real da interface de rede: `ip addr show` para descobrir

View File

@ -46,27 +46,15 @@
## Mapa de Portas
| Porta | Serviço | Tecnologia | Observação |
|-------|---------|-----------|------------|
| 5000 | API Principal + Frontend | Express.js + React | Exposta publicamente |
| 5001 | **Arcadia Kernel** | Node.js/Express | Dashboard interno (mesmo container) |
| 8002 | Motor Fiscal (Fisco) | FastAPI (Python) | Via Docker network |
| 8003 | Motor Contábil | FastAPI (Python) | Via Docker network |
| 8004 | Motor BI (Insights) | FastAPI (Python) | Via Docker network |
| 8005 | Motor Automação | FastAPI (Python) | Via Docker network |
| 8006 | Motor Comunicação | Node.js/Express | Via Docker network |
| 8080 | Arcádia Plus (Laravel) | PHP/Laravel | Via Docker network |
### Sobre o Kernel (Porta 5001)
O **Arcadia Kernel** é o sistema operacional nativo que roda **dentro do mesmo container** da aplicação principal (porta 5000). Ele não é acessível externamente, mas fornece:
- **Service Registry**: Catálogo central de todos os serviços
- **Discovery Automático**: Detecta serviços via Coolify API, Docker, DNS
- **Health Monitoring**: Verifica saúde dos serviços periodicamente
- **Casa de Máquinas**: Acessa o Registry via memória (não HTTP)
A Casa de Máquinas (na porta 5000) consulta o Kernel via **acesso direto à memória** (`global.arcadiaKernelRegistry`), não via HTTP.
| Porta | Serviço | Tecnologia |
|-------|---------|-----------|
| 5000 | API Principal + Frontend | Express.js + React |
| 8002 | Motor Fiscal (Fisco) | FastAPI (Python) |
| 8003 | Motor Contábil | FastAPI (Python) |
| 8004 | Motor BI (Insights) | FastAPI (Python) |
| 8005 | Motor Automação | FastAPI (Python) |
| 8006 | Motor Comunicação | Node.js/Express |
| 8080 | Arcádia Plus (Laravel) | PHP/Laravel |
---

787
PLANO_BI_SUPERSET.md Normal file
View File

@ -0,0 +1,787 @@
# PLANO BI — Substituição Metabase → Apache Superset
## Análise Real + Plano de Migração
### Versão 1.0 — Março 2026
---
## 1. DIAGNÓSTICO — ESTADO ATUAL
### 1.1 O que foi descoberto no código
O documento do Replit referenciava **Metabase** (Java, :8088) como o pilar visual do BI. Ao analisar o código real, a situação é diferente:
| Componente | Nome no doc | Nome real no código | Status |
|---|---|---|---|
| Plataforma BI Visual | "Metabase" | **"MetaSet"** (alias) | Metabase por baixo |
| Proxy | `/metabase/*` | `server/metabase/proxy.ts` | Funcional |
| Auth | Metabase session | `server/metaset/routes.ts` (autologin) | Funcional |
| Client | MetabaseClient | `server/metaset/client.ts` | Funcional |
| UI | Advanced tab iframe | `/api/bi/metaset/autologin``/metabase/` | Funcional |
| **Superset** | Não mencionado | **`docker-compose.yml` linha 165** | **JÁ CONFIGURADO** |
**Descoberta crítica:** O Apache Superset **já está no docker-compose.yml**:
```yaml
superset:
image: apache/superset:4.1.0
restart: unless-stopped
environment:
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY:-superset-secret-change-in-prod}
DATABASE_URL: postgresql://arcadia:arcadia123@db:5432/arcadia_superset
ports:
- "8088:8088"
depends_on:
db:
condition: service_healthy
profiles: [bi]
```
O Superset está configurado, mas **não integrado ao gateway Node.js**. O que falta é a ponte.
### 1.2 Arquivos que precisam mudar vs o que fica igual
**Mudam:**
```
server/metabase/proxy.ts → substituir por server/superset/proxy.ts
server/metaset/routes.ts → substituir por server/superset/routes.ts
server/metaset/client.ts → substituir por server/superset/client.ts
client/src/pages/BiWorkspace.tsx (Advanced tab apenas)
docker-compose.yml (Superset config + init)
docker-compose.prod.yml (adicionar Superset)
.env.example (novas variáveis)
```
**NÃO mudam (ficam exatamente iguais):**
```
server/python/bi_engine.py ← Motor Python :8004 continua intacto
server/bi/routes.ts ← CRUD de datasets/charts/dashboards
server/bi/upload.ts ← ETL pipeline
server/bi/staging.ts ← Staging
server/bi/engine-proxy.ts ← Proxy para BI Engine Python
client/src/pages/BiWorkspace.tsx (7 de 8 tabs ficam iguais)
Todas as tabelas do banco (bi_datasets, bi_charts, etc.)
```
---
## 2. POR QUE SUPERSET É MELHOR PARA O CASO
### 2.1 Metabase vs Superset
| Critério | Metabase | Apache Superset |
|---|---|---|
| Runtime | Java (JVM, 500MB+ RAM) | Python/Flask (mais leve) |
| Embedding | Iframe com cookie de sessão | **Guest Token JWT** (seguro, sem credenciais) |
| "Qualquer tela" | Iframe estático, limitado | **`@superset-ui/embedded-sdk`** — componente React nativo |
| REST API | Limitada e não documentada | **REST API v1 completa e documentada** |
| Consistência de stack | Java (fora do padrão) | Python (mesmo stack dos motores existentes) |
| Docker image | `metabase/metabase` ~700MB | `apache/superset` ~400MB |
| SQL Lab | Básico | **Poderoso** (autocomplete, histórico, salvar queries) |
| Alertas | Básico | **Avançado** (webhooks, Slack, email) |
| Tipos de chart | ~20 | **50+** (incluindo mapas, sunburst, funnel) |
| Banco de metadados | H2/Postgres separado | **Mesmo PostgreSQL** da Arcádia |
### 2.2 A feature que o usuário pediu: "funcionar em qualquer tela"
O Superset resolve isso com **Guest Token + Embedded SDK**:
```
Fluxo atual (Metabase):
BiWorkspace → iframe → /metabase/ → autologin com cookie
❌ Só funciona na tab Advanced do BiWorkspace
❌ Cookie expira, precisa re-autenticar
❌ Não embeda em outras páginas facilmente
Fluxo novo (Superset):
QUALQUER página React → <EmbeddedDashboard dashboardId="xxx" />
✅ Funciona no Dashboard principal (/)
✅ Funciona no SOE (/soe tab Financeiro)
✅ Funciona no /contabil
✅ Funciona no /financeiro
✅ Funciona em qualquer tela do Arcádia
✅ Guest Token renovado automaticamente (sem re-login)
```
---
## 3. ARQUITETURA COM SUPERSET
### 3.1 Diagrama atualizado
```
┌─────────────────────────────────────────────────────────────────────┐
│ USUÁRIO / FRONTEND │
│ │
│ BiWorkspace.tsx (8 tabs — 7 iguais, Advanced → Superset) │
│ + QUALQUER outra página pode embedar um dashboard Superset │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ <SupersetDashboard dashboardId="finance-overview" /> │ │
│ │ Componente React reutilizável em QUALQUER rota │ │
│ └────────────────────────────────────────────────────────────┘ │
└─────────────────────────┬────────────────────────────────────────────┘
┌───────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌────────────────┐ ┌──────────────────────┐
│ API BI Node │ │ BI Engine Py │ │ Superset (Python) │
│ /api/bi/* │ │ /api/bi-engine │ │ /superset/* │
│ (sem mudança)│ │ :8004 (igual) │ │ :8088 │
│ │ │ │ │ │
│ + novo: │ │ │ │ REST API v1 │
│ /api/superset│ │ │ │ Guest Token JWT │
│ /guest-token │ │ │ │ SQL Lab │
│ │ │ │ │ 50+ chart types │
└──────────────┘ └────────────────┘ └──────────┬───────────┘
┌──────────────────────┐
│ PostgreSQL │
│ arcadia (dados SOE) │
│ arcadia_superset │
│ (metadados Superset) │
└──────────────────────┘
```
### 3.2 Dois bancos PostgreSQL
```
Banco: arcadia → Dados do SOE (persons, products, sales_orders, etc.)
Superset conecta aqui em READ-ONLY para criar charts
Banco: arcadia_superset → Metadados do Superset (dashboards, queries, users)
Superset usa internamente
```
Ambos no mesmo servidor PostgreSQL, bancos separados. O docker-compose.yml já referencia `arcadia_superset`.
---
## 4. O QUE CONSTRUIR — Passo a Passo
### 4.1 Configuração do Superset (docker-compose.yml)
O container já existe. Faltam: init script, volumes, configuração de embedding.
```yaml
# docker-compose.yml — seção superset (expandida)
superset:
image: apache/superset:4.1.0
restart: unless-stopped
environment:
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY:-superset-secret-change-in-prod}
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD:-arcadia123}@db:5432/arcadia_superset
SUPERSET_WEBSERVER_PORT: 8088
PYTHONPATH: /app/pythonpath
# Para embedding funcionar:
FEATURE_FLAGS: '{"EMBEDDED_SUPERSET": true, "ENABLE_TEMPLATE_PROCESSING": true}'
ports:
- "8088:8088"
volumes:
- ./docker/superset/superset_config.py:/app/pythonpath/superset_config.py
- ./docker/superset/init.sh:/app/docker/init.sh
- superset_home:/app/superset_home
depends_on:
db:
condition: service_healthy
command: ["/bin/bash", "/app/docker/init.sh"]
profiles: [bi]
networks:
- arcadia
```
### 4.2 Arquivo de Configuração Superset
```python
# docker/superset/superset_config.py
import os
# Chave secreta (mesma do .env)
SECRET_KEY = os.environ.get("SUPERSET_SECRET_KEY", "change-in-production")
# Banco de metadados do Superset
SQLALCHEMY_DATABASE_URI = os.environ.get(
"DATABASE_URL",
"postgresql://arcadia:arcadia123@db:5432/arcadia_superset"
)
# CORS — permite o gateway Arcádia (:5000) chamar a API
ENABLE_CORS = True
CORS_OPTIONS = {
"supports_credentials": True,
"allow_headers": ["*"],
"resources": {r"/api/*": {"origins": "*"}},
}
# Embedding habilitado
FEATURE_FLAGS = {
"EMBEDDED_SUPERSET": True,
"ENABLE_TEMPLATE_PROCESSING": True,
"ALERT_REPORTS": True, # Alertas automáticos
"DRILL_TO_DETAIL": True, # Drill-down em charts
}
# Timeout de queries (30s por default, aumentar para análises pesadas)
SUPERSET_WEBSERVER_TIMEOUT = 300
# Cache (Redis se disponível, senão in-memory)
CACHE_CONFIG = {
"CACHE_TYPE": "SimpleCache",
"CACHE_DEFAULT_TIMEOUT": 300, # 5 min
}
# Tema/branding Arcádia (logo, cores)
APP_NAME = "Arcádia Insights"
APP_ICON = "/static/arcadia-logo.png"
```
### 4.3 Init Script do Superset
```bash
#!/bin/bash
# docker/superset/init.sh — roda ao iniciar o container
set -e
echo "[Superset Init] Iniciando configuração..."
# Aguarda PostgreSQL
until psql "${DATABASE_URL}" -c "SELECT 1" > /dev/null 2>&1; do
echo "[Superset Init] Aguardando PostgreSQL..."
sleep 2
done
# Cria banco arcadia_superset se não existir
psql "postgresql://${PGUSER:-arcadia}:${PGPASSWORD:-arcadia123}@db:5432/postgres" \
-c "CREATE DATABASE arcadia_superset OWNER arcadia;" 2>/dev/null || true
# Inicializa banco do Superset
superset db upgrade
# Cria usuário admin (só se não existir)
superset fab create-admin \
--username "${SUPERSET_ADMIN_USER:-admin}" \
--firstname "Arcádia" \
--lastname "Admin" \
--email "${SUPERSET_ADMIN_EMAIL:-admin@arcadia.app}" \
--password "${SUPERSET_ADMIN_PASSWORD:-arcadia2026}" 2>/dev/null || true
# Carrega exemplos (opcional, comentar em prod)
# superset load_examples
# Inicializa roles e permissões
superset init
# Registra conexão com o banco Arcádia (dados do SOE)
python - <<'PYEOF'
from superset import create_app
from superset.extensions import db
from superset.models.core import Database
import os
app = create_app()
with app.app_context():
existing = db.session.query(Database).filter_by(
database_name="Arcádia Suite"
).first()
if not existing:
arcadia_db = Database(
database_name="Arcádia Suite",
sqlalchemy_uri=os.environ.get(
"ARCADIA_DATABASE_URL",
"postgresql://arcadia:arcadia123@db:5432/arcadia"
),
expose_in_sqllab=True,
allow_run_async=True,
allow_csv_upload=False,
)
db.session.add(arcadia_db)
db.session.commit()
print("[Superset Init] Banco 'Arcádia Suite' registrado!")
else:
print("[Superset Init] Banco 'Arcádia Suite' já existe.")
PYEOF
echo "[Superset Init] Iniciando servidor..."
gunicorn \
--bind "0.0.0.0:${SUPERSET_WEBSERVER_PORT:-8088}" \
--access-logfile "-" \
--error-logfile "-" \
--workers 4 \
--timeout 120 \
--limit-request-line 0 \
--limit-request-field_size 0 \
"superset.app:create_app()"
```
### 4.4 Proxy Superset (substitui server/metabase/proxy.ts)
```typescript
// server/superset/proxy.ts
import { Express, Response } from "express";
import { createProxyMiddleware } from "http-proxy-middleware";
const SUPERSET_HOST = process.env.SUPERSET_HOST || "localhost";
const SUPERSET_PORT = parseInt(process.env.SUPERSET_PORT || "8088", 10);
const SUPERSET_TIMEOUT = 60000;
export function setupSupersetProxy(app: Express): void {
const target = `http://${SUPERSET_HOST}:${SUPERSET_PORT}`;
const supersetProxy = createProxyMiddleware({
target,
changeOrigin: true,
timeout: SUPERSET_TIMEOUT,
proxyTimeout: SUPERSET_TIMEOUT,
pathRewrite: { "^/superset": "" },
on: {
error: (err, _req, res) => {
console.error("[Superset Proxy] Error:", err.message);
if (res && typeof (res as Response).status === "function") {
(res as Response).status(502).json({
error: "Superset indisponível",
message: "O Superset está iniciando. Tente novamente em alguns segundos.",
target,
});
}
},
proxyRes: (proxyRes) => {
const location = proxyRes.headers["location"];
if (location && typeof location === "string" && location.startsWith("/")) {
proxyRes.headers["location"] = `/superset${location}`;
}
},
},
});
app.use("/superset", supersetProxy);
console.log(`[Superset Proxy] Configurado -> /superset/* => ${target}`);
}
```
### 4.5 Rotas Superset — Guest Token para Embedding
Esta é a peça-chave para "funcionar em qualquer tela":
```typescript
// server/superset/routes.ts
import type { Express, Request, Response } from "express";
const SUPERSET_HOST = process.env.SUPERSET_HOST || "localhost";
const SUPERSET_PORT = parseInt(process.env.SUPERSET_PORT || "8088", 10);
const SUPERSET_URL = `http://${SUPERSET_HOST}:${SUPERSET_PORT}`;
const SUPERSET_ADMIN_USER = process.env.SUPERSET_ADMIN_USER || "admin";
const SUPERSET_ADMIN_PASS = process.env.SUPERSET_ADMIN_PASSWORD || "arcadia2026";
// Cache do token de serviço (expira em 50 min)
let serviceToken: string | null = null;
let serviceTokenExpiry = 0;
async function getServiceToken(): Promise<string> {
if (serviceToken && Date.now() < serviceTokenExpiry) return serviceToken;
const resp = await fetch(`${SUPERSET_URL}/api/v1/security/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username: SUPERSET_ADMIN_USER,
password: SUPERSET_ADMIN_PASS,
provider: "db",
refresh: true,
}),
});
if (!resp.ok) throw new Error("Falha ao autenticar no Superset");
const data = await resp.json();
serviceToken = data.access_token;
serviceTokenExpiry = Date.now() + 50 * 60 * 1000; // 50 min
return serviceToken!;
}
export function registerSupersetRoutes(app: Express): void {
// Guest Token — para embedding em qualquer tela
// O frontend chama este endpoint para obter um token temporário
// e renderiza o dashboard via @superset-ui/embedded-sdk
app.post("/api/superset/guest-token", async (req: Request, res: Response) => {
try {
if (!req.isAuthenticated()) return res.status(401).json({ error: "Not authenticated" });
const { dashboardId } = req.body;
if (!dashboardId) return res.status(400).json({ error: "dashboardId obrigatório" });
const token = await getServiceToken();
// Solicita guest token ao Superset
const guestResp = await fetch(`${SUPERSET_URL}/api/v1/security/guest_token/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
body: JSON.stringify({
user: {
username: `arcadia_${req.user?.id}`,
first_name: req.user?.name?.split(" ")[0] || "User",
last_name: req.user?.name?.split(" ")[1] || "",
},
resources: [{ type: "dashboard", id: dashboardId }],
rls: [], // Row Level Security (futuro: por tenantId)
}),
});
if (!guestResp.ok) {
const err = await guestResp.text();
return res.status(502).json({ error: "Falha ao gerar guest token", detail: err });
}
const { token: guestToken } = await guestResp.json();
res.json({ token: guestToken, supersetUrl: "/superset" });
} catch (err: any) {
res.status(502).json({ error: err.message });
}
});
// Lista dashboards disponíveis (para o seletor na UI)
app.get("/api/superset/dashboards", async (req: Request, res: Response) => {
try {
if (!req.isAuthenticated()) return res.status(401).json({ error: "Not authenticated" });
const token = await getServiceToken();
const resp = await fetch(`${SUPERSET_URL}/api/v1/dashboard/?q=(order_column:changed_on_delta_humanized,order_direction:desc)`, {
headers: { "Authorization": `Bearer ${token}` },
});
if (!resp.ok) return res.status(502).json({ error: "Superset indisponível" });
const data = await resp.json();
res.json(data.result || []);
} catch (err: any) {
res.status(502).json({ error: err.message });
}
});
// Health check
app.get("/api/superset/health", async (_req: Request, res: Response) => {
try {
const resp = await fetch(`${SUPERSET_URL}/health`);
res.json({ online: resp.ok, url: SUPERSET_URL });
} catch {
res.json({ online: false, url: SUPERSET_URL });
}
});
}
```
### 4.6 Componente React — SupersetDashboard (reutilizável em QUALQUER tela)
```typescript
// client/src/components/SupersetDashboard.tsx
import { useEffect, useRef, useState } from "react";
import { embedDashboard } from "@superset-ui/embedded-sdk";
interface SupersetDashboardProps {
dashboardId: string;
height?: string | number;
className?: string;
}
export function SupersetDashboard({
dashboardId,
height = "calc(100vh - 300px)",
className = "",
}: SupersetDashboardProps) {
const containerRef = useRef<HTMLDivElement>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!containerRef.current) return;
let mounted = true;
setLoading(true);
setError(null);
embedDashboard({
id: dashboardId,
supersetDomain: window.location.origin + "/superset",
mountPoint: containerRef.current,
// Busca guest token do gateway Arcádia
fetchGuestToken: async () => {
const resp = await fetch("/api/superset/guest-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ dashboardId }),
credentials: "include",
});
if (!resp.ok) throw new Error("Falha ao obter token");
const { token } = await resp.json();
return token;
},
dashboardUiConfig: {
hideTitle: true, // Título próprio no Arcádia
hideChartControls: false, // Manter controles de chart
filters: {
visible: true, // Mostrar filtros
expanded: false,
},
},
}).then(() => {
if (mounted) setLoading(false);
}).catch((err) => {
if (mounted) {
setError(err.message);
setLoading(false);
}
});
return () => { mounted = false; };
}, [dashboardId]);
if (error) {
return (
<div className="flex items-center justify-center h-48 text-red-500 text-sm">
Superset indisponível: {error}
</div>
);
}
return (
<div className={`relative ${className}`} style={{ height }}>
{loading && (
<div className="absolute inset-0 flex items-center justify-center bg-white/80 z-10">
<div className="text-center text-[#1f334d]">
<div className="w-8 h-8 border-4 border-[#c89b3c] border-t-transparent rounded-full animate-spin mx-auto mb-2" />
<p className="text-sm">Carregando Arcádia Insights...</p>
</div>
</div>
)}
<div ref={containerRef} className="w-full h-full" />
</div>
);
}
```
**Uso em QUALQUER página:**
```tsx
// Exemplo: na página /financeiro
import { SupersetDashboard } from "@/components/SupersetDashboard";
// Dashboard financeiro embeddado diretamente no módulo Financeiro
<SupersetDashboard dashboardId="financial-overview" height={500} />
// Exemplo: na página /contabil
<SupersetDashboard dashboardId="dre-mensal" />
// Exemplo: no Dashboard principal (/)
<SupersetDashboard dashboardId="executive-summary" height={400} />
```
### 4.7 BiWorkspace.tsx — Advanced tab (única mudança na UI)
```tsx
// Substituir a tab Advanced (linhas 2918-2945)
<TabsContent value="advanced" className="mt-0">
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h2 className="text-lg font-semibold text-[#1f334d]">Arcádia Insights — Apache Superset</h2>
<p className="text-sm text-gray-500">
SQL Lab avançado, 50+ tipos de gráfico, dashboards interativos
</p>
</div>
<a
href="/superset"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-4 py-2 bg-[#1f334d] text-white rounded-lg hover:bg-[#2a4466] transition-colors text-sm"
>
<ArrowRight className="w-4 h-4" /> Abrir em Nova Aba
</a>
</div>
{/* Dashboard selecionável */}
<SupersetDashboardSelector />
</div>
</TabsContent>
```
---
## 5. TABELAS E VARIÁVEIS
### 5.1 Nenhuma tabela nova no schema Arcádia
O Superset usa seu próprio banco (`arcadia_superset`) para metadados internos. As tabelas do Arcádia não mudam.
### 5.2 Novas variáveis de ambiente
```bash
# .env — adicionar/substituir:
# Superset (substitui METABASE_*)
SUPERSET_HOST=superset # nome do container Docker
SUPERSET_PORT=8088
SUPERSET_SECRET_KEY= # gerar: openssl rand -hex 32
SUPERSET_ADMIN_USER=admin
SUPERSET_ADMIN_EMAIL=admin@arcadia.app
SUPERSET_ADMIN_PASSWORD= # senha forte em prod
# URL do banco Arcádia para o Superset acessar (read-only ideal)
ARCADIA_DATABASE_URL=postgresql://arcadia_ro:pass@db:5432/arcadia
# Remover (não são mais necessários):
# METABASE_HOST
# METABASE_PORT
# METASET_ADMIN_EMAIL
# METASET_ADMIN_PASSWORD
```
---
## 6. ROADMAP — Fases de Implementação
### Fase 1 — Infraestrutura (2-3 dias)
```
[ ] Criar docker/superset/superset_config.py
[ ] Criar docker/superset/init.sh
[ ] Atualizar docker-compose.yml (expandir seção superset existente)
[ ] Adicionar volumes: superset_home no docker-compose
[ ] Testar: docker compose --profile bi up superset
[ ] Verificar: banco arcadia_superset criado, admin funciona, :8088 acessível
```
### Fase 2 — Gateway (1-2 dias)
```
[ ] Criar server/superset/proxy.ts (substitui server/metabase/proxy.ts)
[ ] Criar server/superset/routes.ts (guest token, lista dashboards, health)
[ ] Registrar no server/routes.ts: setupSupersetProxy() + registerSupersetRoutes()
[ ] Remover/comentar setupMetabaseProxy() e registerMetaSetRoutes()
[ ] Testar: GET /api/superset/health → { online: true }
[ ] Testar: POST /api/superset/guest-token → token JWT
```
### Fase 3 — Frontend (2-3 dias)
```
[ ] npm install @superset-ui/embedded-sdk
[ ] Criar client/src/components/SupersetDashboard.tsx
[ ] Atualizar BiWorkspace.tsx Advanced tab (substituir iframe MetaSet)
[ ] Testar: dashboard embeddado na Advanced tab
[ ] Criar 3 dashboards base no Superset:
- executive-summary (KPIs gerais)
- financial-overview (Financeiro)
- dre-mensal (DRE)
```
### Fase 4 — Embedding em outras telas (1 semana)
```
[ ] Embedar SupersetDashboard no /financeiro (tab Análise)
[ ] Embedar SupersetDashboard no /contabil (tab DRE/Balanço)
[ ] Embedar SupersetDashboard no SOE (tab Dashboard)
[ ] Embedar SupersetDashboard no Home / Dashboard principal
[ ] RLS por tenantId (Row Level Security nos dashboards)
```
### Fase 5 — Migração de dashboards (conforme necessário)
```
[ ] Recriar no Superset os dashboards que existiam no MetaSet (se houver)
[ ] Configurar alertas automáticos (Superset Alerts & Reports)
[ ] Criar dashboards para cada domínio do SOE:
- Vendas, Compras, Fiscal, Pessoas, Estoque
```
---
## 7. DIAGRAMA BI ATUALIZADO (completo)
```
┌────────────────────────────────────────────────────────────────────┐
│ ARCÁDIA BI STACK v2 │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ FRONTEND REACT — Pode usar em QUALQUER rota │ │
│ │ │ │
│ │ BiWorkspace.tsx → 8 tabs (7 iguais + Advanced=Superset) │ │
│ │ <SupersetDashboard> → componente reutilizável │ │
│ │ Guest Token API → /api/superset/guest-token │ │
│ └────────────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────┼──────────────────────┐ │
│ │ │ │ │
│ ┌──────▼──────┐ ┌────────▼───────┐ ┌─────────▼──────────┐ │
│ │ API BI │ │ BI Engine │ │ Apache Superset │ │
│ │ (Node.js) │ │ Python:8004 │ │ Python:8088 │ │
│ │ sem mudança│ │ sem mudança │ │ │ │
│ │ │ │ │ │ SQL Lab │ │
│ │ CRUD │ │ SQL+Charts │ │ 50+ chart types │ │
│ │ Upload/ETL │ │ Micro-BI │ │ Guest Token (embed) │ │
│ │ Staging │ │ Análise Pandas │ │ Alerts & Reports │ │
│ │ Backups │ │ Cache 5min │ │ REST API v1 │ │
│ └─────┬───────┘ └────────┬───────┘ └─────────┬───────────┘ │
│ │ │ │ │
│ └──────────────────────┼─────────────────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ PostgreSQL │ │
│ │ │ │
│ │ arcadia │ ← Dados SOE (read-only) │
│ │ arcadia_superset │ ← Metadados Superset │
│ └────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ CIENTISTA (Python) — IA/ML: análise, padrões, insights │ │
│ │ ASSISTENTE BI (GPT) — chat sobre dados │ │
│ │ (ambos sem mudança) │ │
│ └──────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────┘
```
---
## 8. RESUMO — O que muda, o que fica
| Componente | Ação | Esforço |
|---|---|---|
| `server/metabase/proxy.ts` | Substituir por `server/superset/proxy.ts` | 30 min |
| `server/metaset/routes.ts` | Substituir por `server/superset/routes.ts` | 2h |
| `server/metaset/client.ts` | Remover (lógica vai para routes.ts) | 5 min |
| `docker-compose.yml` Superset | Expandir configuração existente | 1h |
| `docker/superset/superset_config.py` | Criar (novo arquivo) | 30 min |
| `docker/superset/init.sh` | Criar (novo arquivo) | 1h |
| `BiWorkspace.tsx` Advanced tab | Substituir iframe MetaSet | 1h |
| `SupersetDashboard.tsx` | Criar componente (novo) | 2h |
| `npm install @superset-ui/embedded-sdk` | Instalar dependência | 5 min |
| BI Engine Python (:8004) | **Não muda nada** | — |
| API BI Node /api/bi/* | **Não muda nada** | — |
| Upload/ETL/Staging | **Não muda nada** | — |
| 7 tabs do BiWorkspace | **Não muda nada** | — |
| Tabelas do banco | **Não muda nada** | — |
**Total estimado: 1-2 dias de implementação real** (a infra já está 80% pronta).
---
## 9. VANTAGEM ESTRATÉGICA
> O Apache Superset não é apenas "trocar Metabase".
>
> É a virada de chave que permite que o BI deixe de ser uma página separada
> e vire um **componente vivo** que aparece **dentro de cada módulo do Arcádia**:
>
> - O usuário está no módulo Financeiro → vê o dashboard financeiro ali mesmo
> - O usuário está no módulo Contábil → vê o DRE ali mesmo
> - O usuário está no SOE → vê os KPIs do negócio ali mesmo
>
> Sem abrir nova aba. Sem sair do contexto. Sem re-autenticar.
> O Superset renderiza no lugar exato onde o usuário está.
---
*PLANO_BI_SUPERSET.md — Arcádia Suite v3.0*
*Gerado em: 2026-03-16 — Baseado na análise do MAPA_BI_ARCADIA.md (Replit) + código real*

Binary file not shown.

733
PLANO_SOE_CENTRAL.md Normal file
View File

@ -0,0 +1,733 @@
# PLANO SOE CENTRAL — Arcádia Suite
## Orquestração Inteligente: Plus + ERPNext invisíveis, Central visível
### Versão 1.0 — Março 2026
---
## 1. ONDE ESTAMOS (Orientação Geral)
### O Projeto nasceu no Replit como…
- Uma plataforma de **escritório empresarial com IA central** (Manus Agent)
- Com proxy para o **Arcádia Plus** (Laravel/PHP, porta 8080) — ERP fiscal completo
- Com integração ao **ERPNext/Frappe** via API
- Stack principal: **React 18 + Express.js + PostgreSQL + FastAPI (Python)**
### O que JÁ existe e funciona:
| Componente | Status | Onde vive |
|---|---|---|
| Manus Agent (IA, 30+ tools) | ✅ Funcionando | `server/manus/` |
| Arcádia Plus (Laravel ERP) | ✅ Funcionando | `plus/` → porta 8080 |
| SSO Plus ↔ Suite | ✅ Funcionando | `server/plus/` |
| Motor Fiscal (nfelib) | ✅ Funcionando | `server/python/` → porta 8002 |
| Motor Contábil | ✅ Funcionando | `server/python/` → porta 8003 |
| Motor BI | ✅ Funcionando | `server/python/` → porta 8004 |
| Motor Automação | ✅ Funcionando | `server/python/` → porta 8005 |
| Motor Comunicação (Comm) | ✅ Funcionando | porta 8006 |
| Dev Center XOS (6 agentes) | ✅ Funcionando | `server/blackboard/` |
| LiteLLM Gateway | ✅ Configurado | porta 4000 |
| CRM, WhatsApp, Chat | ✅ Funcionando | `server/crm/`, `server/whatsapp/` |
| ERPNext integração | ⚠️ Parcial | `server/erp/routes.ts` |
### O que FALTA para o plano atual:
| Componente | Status |
|---|---|
| **SOE Central (Regras Unificadas)** | ❌ Não existe — este é o plano |
| Fiscal rules centralizadas (acima do Plus) | ❌ Dispersas no Plus |
| ERPNext rules centralizadas | ❌ Não existe camada de abstração |
| Contábil Avançado (lançamentos automáticos) | ❌ Motor existe, regras não |
| Financeiro Avançado (previsão, DRE automático) | ❌ Motor existe, regras não |
| Testes automatizados / CI-CD | ❌ |
| Monitoramento (APM, Sentry) | ❌ |
---
## 2. O PROBLEMA QUE VAMOS RESOLVER
### Situação Atual (problemática)
```
Usuário → Arcádia Suite
├── /plus → acessa Laravel diretamente (usuário VÊ o Plus)
├── /erp → acessa ERPNext diretamente (usuário VÊ o ERPNext)
└── Regras fiscais ficam dentro do Plus
Regras contábeis ficam no Motor 8003
Regras ERP ficam no ERPNext
→ Fragmentado, sem governança central
```
### Situação Desejada (SOE Central)
```
Usuário → Arcádia Suite (SOE)
▼ [Central de Regras SOE]
├── Fiscal: regras unificadas (CFOP, NCM, tributação, NF-e, CT-e...)
├── ERP: regras de negócio (pedidos, estoque, compras...)
├── Contábil: regras de lançamentos, plano de contas, DRE...
└── Financeiro: regras de fluxo, provisões, conciliação...
▼ [Execução invisível]
├── Plus → emite documentos, registra no banco
└── ERPNext → executa operações, registra no banco
O usuário NUNCA VÊ o Plus nem o ERPNext.
Só vê a Central SOE do Arcádia.
```
---
## 3. ARQUITETURA SOE CENTRAL
### 3.1 Diagrama Completo
```
┌─────────────────────────────────────────────────────────────────────────┐
│ ARCÁDIA SUITE (Frontend React) │
│ /financeiro /contabil /fisco /erp /people /production /bi │
│ Usuário nunca acessa /plus ou ERPNext diretamente │
└───────────────────────────────┬─────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ SOE — SISTEMA OPERACIONAL EMPRESARIAL (Nova Camada) │
│ Express.js / porta 5000 / server/soe/ │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ CENTRAL FISCAL │ │ CENTRAL ERP │ │
│ │ server/soe/fiscal/ │ │ server/soe/erp/ │ │
│ │ │ │ │ │
│ │ • Regras NCM/CFOP │ │ • Regras de pedidos │ │
│ │ • Políticas tribut. │ │ • Regras de estoque │ │
│ │ • Emissão NF-e/NFC-e │ │ • Regras de compras │ │
│ │ • Cancelamento │ │ • Regras de pessoas │ │
│ │ • SPED/SINTEGRA │ │ • Integrações ext. │ │
│ └──────────┬───────────┘ └──────────┬─────────────┘ │
│ │ │ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ CENTRAL CONTÁBIL │ │ CENTRAL FINANCEIRO │ │
│ │ server/soe/contabil/│ │ server/soe/financ/ │ │
│ │ │ │ │ │
│ │ • Plano de contas │ │ • Fluxo de caixa │ │
│ │ • Regras de lanç. │ │ • Contas a P/R │ │
│ │ • DRE automático │ │ • Conciliação │ │
│ │ • Balancete │ │ • Previsão (IA) │ │
│ │ • Fechamento │ │ • Impostos │ │
│ └──────────┬───────────┘ └──────────┬─────────────┘ │
│ │ │ │
│ └────────────┬─────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ SOE RULE ENGINE (Núcleo) │ │
│ │ server/soe/rule-engine.ts │ │
│ │ │ │
│ │ • Resolução de conflitos │ │
│ │ • Aplicação de políticas XOS │ │
│ │ • Audit trail de todas regras │ │
│ │ • Manus consulta este engine │ │
│ └─────────────────┬───────────────┘ │
└────────────────────────────┼────────────────────────────────────────────┘
┌──────────────┼───────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌──────────────┐ ┌──────────────────┐
│ ARCÁDIA PLUS │ │ ERPNEXT │ │ MOTORES PYTHON │
│ Laravel :8080 │ │ Frappe : │ │ 8002 Fiscal │
│ (invisível) │ │ (invisível) │ │ 8003 Contábil │
│ │ │ │ │ 8004 BI │
│ Emite docs │ │ Executa ERP │ │ 8005 Automação │
│ fiscais │ │ operacional │ │ │
└─────────────────┘ └──────────────┘ └──────────────────┘
```
---
## 4. CENTRAL FISCAL — Detalhamento
### 4.1 O que ela resolve
O Plus tem **376 controllers** e **1.546 rotas** — tudo fiscal/operacional. A Central Fiscal **não substitui** o Plus, ela **orquestra** o Plus com regras definidas no SOE.
### 4.2 Regras que vão para a Central (tiradas do Plus)
```typescript
// server/soe/fiscal/rules.ts — Exemplo de estrutura
interface FiscalRule {
id: string
nome: string
trigger: 'pre_emissao' | 'pos_emissao' | 'cancelamento' | 'validacao'
condicao: FiscalCondition
acao: FiscalAction
prioridade: number
ativo: boolean
}
// Exemplos de regras:
const REGRAS_FISCAIS: FiscalRule[] = [
{
id: 'NFE_CFOP_INTERESTADUAL',
nome: 'Ajusta CFOP para operação interestadual',
trigger: 'pre_emissao',
condicao: { uf_emitente: '!=', uf_destinatario: true },
acao: { ajustar_cfop: 'interestadual' },
prioridade: 1,
ativo: true
},
{
id: 'NFE_SIMPLES_NACIONAL',
nome: 'Aplica tributação Simples Nacional',
trigger: 'pre_emissao',
condicao: { regime_tributario: 'simples_nacional' },
acao: { aplicar_cst: 'tabela_simples', remover_pis_cofins: true },
prioridade: 2,
ativo: true
},
{
id: 'NFE_VALIDACAO_NCM',
nome: 'Valida NCM obrigatório para NF-e',
trigger: 'validacao',
condicao: { tipo_documento: 'nfe' },
acao: { validar_campo: 'ncm', obrigatorio: true },
prioridade: 1,
ativo: true
}
]
```
### 4.3 Fluxo de Emissão via SOE Central
```
Usuário clica "Emitir NF-e" no Arcádia
POST /api/soe/fiscal/emitir-nfe
SOE Rule Engine: aplica REGRAS_FISCAIS (pre_emissao)
• Verifica regime tributário
• Aplica CFOP correto
• Calcula impostos (ICMS, PIS, COFINS, IPI)
• Valida NCM obrigatório
• Adiciona dados da empresa emitente
SOE envia para Plus via API interna (invisível)
POST http://plus:8080/api/nfe/emitir
+ token SSO automático
Plus emite via Cloud-DFE → SEFAZ
SOE recebe retorno
• Registra no banco SOE (tabela soe_fiscal_eventos)
• Dispara lançamento contábil automático
• Notifica via WebSocket o frontend
Usuário vê resultado no Arcádia (nunca soube do Plus)
```
### 4.4 Tabelas SOE para Fiscal
```sql
-- Tabelas a criar no schema Arcádia (PostgreSQL)
CREATE TABLE soe_fiscal_regras (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nome VARCHAR(200) NOT NULL,
trigger VARCHAR(50) NOT NULL,
condicao JSONB NOT NULL,
acao JSONB NOT NULL,
prioridade INTEGER DEFAULT 10,
ativo BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE soe_fiscal_eventos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tipo VARCHAR(50) NOT NULL, -- 'emissao_nfe', 'cancelamento', etc.
empresa_id INTEGER NOT NULL,
documento_chave VARCHAR(100),
regras_aplicadas JSONB, -- quais regras foram aplicadas
payload_enviado JSONB, -- o que foi enviado ao Plus
payload_retorno JSONB, -- o que o Plus/SEFAZ respondeu
status VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE soe_fiscal_configuracoes (
empresa_id INTEGER PRIMARY KEY,
regime_tributario VARCHAR(50), -- simples, lucro_presumido, lucro_real
uf VARCHAR(2),
cnpj VARCHAR(18),
certificado_tipo VARCHAR(20),
aliquotas JSONB, -- alíquotas padrão por operação
cfops_padrao JSONB, -- CFOPs padrão por tipo de operação
created_at TIMESTAMP DEFAULT NOW()
);
```
---
## 5. CENTRAL ERP (ERPNext/Frappe) — Detalhamento
### 5.1 O que ela resolve
O ERPNext tem **APIs complexas**. A Central ERP abstrai tudo isso em operações simples que o Arcádia entende.
### 5.2 Adapter Layer ERPNext
```typescript
// server/soe/erp/frappe-adapter.ts
interface ERPAdapter {
// Produtos
criarProduto(data: ProdutoSOE): Promise<ERPProduto>
buscarProduto(codigo: string): Promise<ProdutoSOE>
atualizarEstoque(movimentacao: MovimentacaoEstoque): Promise<void>
// Pedidos
criarPedidoVenda(pedido: PedidoSOE): Promise<ERPPedido>
aprovarPedido(id: string): Promise<void>
faturarPedido(id: string): Promise<ERPFatura>
// Financeiro
criarLancamento(lancamento: LancamentoSOE): Promise<ERPLancamento>
buscarPlanoContas(): Promise<ContaContabil[]>
// Compras
criarPedidoCompra(data: PedidoCompraSOE): Promise<ERPPedidoCompra>
receberMercadoria(data: RecebimentoSOE): Promise<void>
}
// As regras ficam no SOE, o adapter só traduz
class FrappeAdapter implements ERPAdapter {
private baseUrl: string
private apiKey: string
async criarPedidoVenda(pedido: PedidoSOE): Promise<ERPPedido> {
// Traduz do modelo SOE para o modelo Frappe
const frappePedido = this.traduzirParaFrappe(pedido)
// Envia para ERPNext (invisível para o usuário)
const response = await fetch(`${this.baseUrl}/api/resource/Sales Order`, {
method: 'POST',
headers: { 'Authorization': `token ${this.apiKey}` },
body: JSON.stringify(frappePedido)
})
// Traduz retorno de volta para modelo SOE
return this.traduzirDeVoltaSOE(await response.json())
}
}
```
### 5.3 Regras de Negócio ERP no SOE
```typescript
// server/soe/erp/rules.ts
const REGRAS_ERP = [
{
id: 'PEDIDO_APROVACAO_AUTOMATICA',
nome: 'Aprovação automática até R$ 5.000',
trigger: 'pre_aprovacao_pedido',
condicao: { valor_total: { menor_que: 5000 } },
acao: { aprovar_automaticamente: true }
},
{
id: 'ESTOQUE_RESERVA_VENDA',
nome: 'Reservar estoque ao confirmar venda',
trigger: 'pos_confirmacao_venda',
condicao: { status: 'confirmado' },
acao: { reservar_estoque: true, gerar_separacao: true }
},
{
id: 'NF_AUTOMATICA_FATURAMENTO',
nome: 'Emite NF-e automaticamente ao faturar',
trigger: 'pos_faturamento',
condicao: { tipo_cliente: ['pj', 'pf_contribuinte'] },
acao: { emitir_nfe: true, via_soe_fiscal: true }
}
]
```
---
## 6. CENTRAL CONTÁBIL AVANÇADA
### 6.1 Motor Atual (porta 8003) + Regras SOE
O Motor Contábil Python já faz DRE e Balancete. O que falta é **alimentação automática**.
### 6.2 Lançamentos Automáticos (grande salto)
```typescript
// server/soe/contabil/auto-lancamentos.ts
interface RegrasLancamentoAuto {
gatilho: string
debito: string // Conta contábil
credito: string // Conta contábil
historico: string
formula: string // Como calcular o valor
}
const LANCAMENTOS_AUTOMATICOS: RegrasLancamentoAuto[] = [
// Quando emite NF-e de venda
{
gatilho: 'emissao_nfe_venda',
debito: '1.1.3.01', // Clientes
credito: '3.1.1.01', // Receita Bruta de Vendas
historico: 'Venda conforme NF-e {numero}',
formula: 'valor_total_nota'
},
{
gatilho: 'emissao_nfe_venda',
debito: '3.1.2.01', // Dedução de Receita (ICMS)
credito: '2.1.2.01', // ICMS a Recolher
historico: 'ICMS s/ Venda NF-e {numero}',
formula: 'valor_icms'
},
// Quando paga conta
{
gatilho: 'pagamento_conta',
debito: '2.1.1.{categoria}', // Conta a Pagar específica
credito: '1.1.1.01', // Banco/Caixa
historico: 'Pagamento: {descricao}',
formula: 'valor_pago'
},
// Quando recebe pagamento
{
gatilho: 'recebimento',
debito: '1.1.1.01', // Banco/Caixa
credito: '1.1.3.01', // Clientes
historico: 'Recebimento: {descricao}',
formula: 'valor_recebido'
},
// Compra de mercadoria
{
gatilho: 'entrada_nfe_compra',
debito: '1.1.4.01', // Estoque
credito: '2.1.1.01', // Fornecedores
historico: 'Compra conforme NF-e {numero} / {fornecedor}',
formula: 'valor_mercadorias'
}
]
```
### 6.3 DRE Automático via SOE
```
Usuário pede DRE do mês
SOE Contábil consulta:
1. Todos lançamentos do período (tabela soe_lancamentos)
2. Agrupa por conta contábil
3. Aplica estrutura DRE configurada
4. Envia para Motor Python 8003 para cálculos
5. Retorna DRE formatado
Usuário vê DRE no /contabil do Arcádia
(Nunca soube que teve lançamentos automáticos de NF-e, pagamentos, etc.)
```
---
## 7. CENTRAL FINANCEIRO AVANÇADO
### 7.1 Regras Financeiras no SOE
```typescript
// server/soe/financeiro/rules.ts
const REGRAS_FINANCEIRO = [
{
id: 'BOLETO_AUTO_VENCIMENTO',
nome: 'Gerar boleto automático para vendas a prazo',
trigger: 'pos_confirmacao_venda_prazo',
acao: { gerar_boleto: true, via: 'asaas', vencimento: '+{prazo}_dias' }
},
{
id: 'ALERTA_VENCIMENTO_3DIAS',
nome: 'Alerta de vencimento 3 dias antes',
trigger: 'cron_diario',
condicao: { vencimento_em: 3 },
acao: { notificar: ['whatsapp', 'email'], via_manus: true }
},
{
id: 'PROVISAO_IMPOSTOS',
nome: 'Provisão mensal de impostos',
trigger: 'cron_mensal',
acao: { calcular_provisao: ['icms', 'pis', 'cofins', 'csll', 'irpj'], registrar_lancamento: true }
},
{
id: 'CONCILIACAO_AUTO',
nome: 'Conciliação bancária automática via OFX',
trigger: 'importacao_ofx',
acao: { tentar_conciliar: true, confianca_minima: 0.85, pendentes_para_revisao: true }
}
]
```
---
## 8. MANUS AGENT + SOE CENTRAL
### 8.1 O Manus consulta a Central
```
Pergunta: "Qual foi o resultado do mês passado?"
Manus → SOE Central
• GET /api/soe/contabil/dre?periodo=2026-02
• GET /api/soe/financeiro/fluxo?periodo=2026-02
• GET /api/soe/fiscal/resumo?periodo=2026-02
SOE agrega dados dos motores (Python) + Plus + ERPNext
Manus recebe contexto completo e responde:
"Faturamento: R$ 120.000
Deduções (impostos): R$ 18.000
Receita Líquida: R$ 102.000
Custo das mercadorias: R$ 65.000
Lucro Bruto: R$ 37.000
..."
```
### 8.2 Tools do Manus para o SOE
```typescript
// Novas tools do Manus que consultam o SOE Central
{
name: 'consultar_situacao_fiscal',
description: 'Consulta situação fiscal da empresa (NFs pendentes, impostos, SPED)',
endpoint: 'GET /api/soe/fiscal/situacao'
},
{
name: 'emitir_documento_fiscal',
description: 'Emite NF-e, NFC-e, CT-e ou MDF-e',
endpoint: 'POST /api/soe/fiscal/emitir'
},
{
name: 'consultar_dre',
description: 'Gera DRE de qualquer período',
endpoint: 'GET /api/soe/contabil/dre'
},
{
name: 'consultar_fluxo_caixa',
description: 'Consulta fluxo de caixa e previsões',
endpoint: 'GET /api/soe/financeiro/fluxo'
},
{
name: 'registrar_lancamento_contabil',
description: 'Registra lançamento contábil manualmente',
endpoint: 'POST /api/soe/contabil/lancamento'
}
```
---
## 9. ESTRUTURA DE PASTAS A CRIAR
```
server/
└── soe/ ← Nova pasta (Central SOE)
├── index.ts ← Router principal do SOE
├── rule-engine.ts ← Motor de regras central
├── fiscal/
│ ├── routes.ts ← GET/POST /api/soe/fiscal/*
│ ├── rules.ts ← Regras fiscais (CFOP, NCM, tributação)
│ ├── emissao.ts ← Orquestração de emissão (→ Plus)
│ ├── plus-adapter.ts ← Adaptador para o Plus (invisível)
│ └── sefaz-adapter.ts ← Comunicação direta SEFAZ (via Python 8002)
├── erp/
│ ├── routes.ts ← GET/POST /api/soe/erp/*
│ ├── rules.ts ← Regras de negócio ERP
│ ├── frappe-adapter.ts ← Adaptador ERPNext (invisível)
│ └── plus-erp-adapter.ts ← Adaptador Plus-ERP (para dados de venda)
├── contabil/
│ ├── routes.ts ← GET/POST /api/soe/contabil/*
│ ├── rules.ts ← Regras contábeis
│ ├── auto-lancamentos.ts ← Lançamentos automáticos
│ ├── plano-contas.ts ← Plano de contas SOE
│ └── python-adapter.ts ← Adaptador Motor Python 8003
├── financeiro/
│ ├── routes.ts ← GET/POST /api/soe/financeiro/*
│ ├── rules.ts ← Regras financeiras
│ ├── previsao.ts ← Previsão com IA
│ └── conciliacao.ts ← Conciliação bancária
└── shared/
├── types.ts ← Tipos SOE compartilhados
├── audit.ts ← Registro de todas as operações
└── event-bus.ts ← Eventos entre Centrais
```
---
## 10. TABELAS DO BANCO A CRIAR (Drizzle ORM)
```typescript
// shared/schema.ts — Adicionar:
// Configuração da empresa no SOE
export const soeEmpresaConfig = pgTable('soe_empresa_config', {
id: serial('id').primaryKey(),
empresaId: integer('empresa_id').notNull(),
regimeTributario: varchar('regime_tributario', { length: 50 }), // simples, presumido, real
uf: varchar('uf', { length: 2 }),
cnpj: varchar('cnpj', { length: 18 }),
planoContasId: integer('plano_contas_id'),
configFiscal: jsonb('config_fiscal'), // alíquotas, CFOPs padrão
configErp: jsonb('config_erp'), // qual ERP: plus, erpnext, ambos
createdAt: timestamp('created_at').defaultNow(),
})
// Regras configuráveis (editáveis pelo usuário)
export const soeRegras = pgTable('soe_regras', {
id: uuid('id').primaryKey().defaultRandom(),
empresaId: integer('empresa_id'), // null = regra global
dominio: varchar('dominio', { length: 50 }).notNull(), // 'fiscal', 'erp', 'contabil', 'financeiro'
nome: varchar('nome', { length: 200 }).notNull(),
trigger: varchar('trigger', { length: 100 }).notNull(),
condicao: jsonb('condicao').notNull(),
acao: jsonb('acao').notNull(),
prioridade: integer('prioridade').default(10),
ativo: boolean('ativo').default(true),
createdAt: timestamp('created_at').defaultNow(),
})
// Log de todas as operações SOE (audit trail)
export const soeEventos = pgTable('soe_eventos', {
id: uuid('id').primaryKey().defaultRandom(),
empresaId: integer('empresa_id').notNull(),
dominio: varchar('dominio', { length: 50 }).notNull(),
tipo: varchar('tipo', { length: 100 }).notNull(),
regraIds: jsonb('regra_ids'), // regras aplicadas
payloadEntrada: jsonb('payload_entrada'),
payloadSaida: jsonb('payload_saida'),
erp: varchar('erp', { length: 50 }), // 'plus', 'erpnext', 'python_8003', etc.
status: varchar('status', { length: 50 }).notNull(),
duracao: integer('duracao'), // ms
createdAt: timestamp('created_at').defaultNow(),
})
// Lançamentos contábeis centralizados
export const soeLancamentos = pgTable('soe_lancamentos', {
id: uuid('id').primaryKey().defaultRandom(),
empresaId: integer('empresa_id').notNull(),
data: date('data').notNull(),
contaDebito: varchar('conta_debito', { length: 30 }).notNull(),
contaCredito: varchar('conta_credito', { length: 30 }).notNull(),
valor: numeric('valor', { precision: 15, scale: 2 }).notNull(),
historico: text('historico').notNull(),
origem: varchar('origem', { length: 100 }), // 'emissao_nfe', 'pagamento', etc.
origemId: varchar('origem_id', { length: 100 }),
period: varchar('period', { length: 7 }), // '2026-03'
createdAt: timestamp('created_at').defaultNow(),
})
```
---
## 11. ROADMAP DE IMPLEMENTAÇÃO
### Fase 1 — Fundação (23 semanas)
```
[ ] 1. Criar server/soe/ com estrutura básica
[ ] 2. Criar tabelas: soe_empresa_config, soe_regras, soe_eventos, soe_lancamentos
[ ] 3. Implementar Plus Adapter (encapsula chamadas ao Laravel)
[ ] 4. Implementar Frappe Adapter (encapsula chamadas ao ERPNext)
[ ] 5. Endpoint GET /api/soe/status (health check de todos os motores)
[ ] 6. Migrar /api/fisco/* para passar pelo SOE
```
### Fase 2 — Central Fiscal (23 semanas)
```
[ ] 7. Implementar Rule Engine para fiscal
[ ] 8. Criar regras fiscais base (NCM, CFOP, tributação por regime)
[ ] 9. POST /api/soe/fiscal/emitir-nfe → Plus (invisível)
[ ] 10. POST /api/soe/fiscal/cancelar-nfe → Plus (invisível)
[ ] 11. GET /api/soe/fiscal/situacao → agrega Plus + Python 8002
[ ] 12. Atualizar frontend /fisco para usar /api/soe/fiscal/*
```
### Fase 3 — Central Contábil (2 semanas)
```
[ ] 13. Implementar auto-lancamentos para eventos fiscais
[ ] 14. Integrar Motor Python 8003 via SOE
[ ] 15. GET /api/soe/contabil/dre → resultado automático
[ ] 16. GET /api/soe/contabil/balancete → atualizado em tempo real
[ ] 17. Atualizar frontend /contabil para usar /api/soe/contabil/*
```
### Fase 4 — Central Financeiro (2 semanas)
```
[ ] 18. Regras de boleto automático (via Asaas)
[ ] 19. Alertas de vencimento (via Manus/WhatsApp)
[ ] 20. Conciliação bancária semi-automática
[ ] 21. Previsão financeira com IA (Motor Python 8003 + Manus)
[ ] 22. Atualizar frontend /financeiro para usar /api/soe/financeiro/*
```
### Fase 5 — Manus + SOE (1 semana)
```
[ ] 23. Registrar novas tools Manus apontando para SOE
[ ] 24. Manus usa SOE para responder perguntas de negócio
[ ] 25. Dashboard SOE: visão geral de todos os eventos
```
### Fase 6 — Central ERP (3 semanas)
```
[ ] 26. Frappe Adapter completo (produtos, pedidos, estoque, compras)
[ ] 27. Regras de negócio ERP no SOE
[ ] 28. Sincronização bidirecional SOE ↔ ERPNext (via eventos)
[ ] 29. Atualizar /erp para usar /api/soe/erp/*
```
---
## 12. RESULTADO FINAL — O QUE O USUÁRIO EXPERIMENTA
```
Antes (fragmentado):
Usuário → /plus → vê Laravel diretamente
Usuário → /erp → vê ERPNext diretamente
Regras fiscais só no Plus
Contabilidade manual
Financeiro sem automação
Depois (SOE Central):
Usuário → /fisco → emite NF-e, Plus executa invisível
Usuário → /contabil → vê DRE gerado automaticamente
Usuário → /financeiro → vê fluxo, previsões, alertas automáticos
Usuário → Manus → pergunta "qual meu resultado?" → resposta completa
Plus e ERPNext: INVISÍVEIS, executando em background
Regras: CENTRALIZADAS no SOE, editáveis
Lançamentos: AUTOMÁTICOS baseados em eventos (NF-e, pagamentos, etc.)
Auditoria: TOTAL de todas as operações
```
---
## 13. PRINCÍPIO ORIENTADOR
> **"O usuário não sabe o que está por trás. Ele só sabe que funciona."**
>
> O Arcádia Suite é o **Sistema Operacional** da empresa.
> O Plus e o ERPNext são **drivers de hardware** — poderosos, mas invisíveis.
> O SOE Central é o **kernel** que os orquestra com regras e inteligência.
---
*PLANO_SOE_CENTRAL.md — Arcádia Suite v3.0*
*Gerado em: 2026-03-16*

167
SESSAO_2026-03-18.md Normal file
View File

@ -0,0 +1,167 @@
# Sessão de Desenvolvimento — 2026-03-18
**Branch:** `claude/analyze-project-0mXjP`
**Commits desta sessão:** `8d14fc0`, `20a0237`
**Contexto:** Continuação do ciclo iniciado em 2026-03-13 (auditoria A-Z + desenvolvimento das fases XOS)
---
## 1. O que foi feito nesta sessão
### 1.1 Correção de erros TypeScript críticos no `App.tsx`
Três classes de erros impediam o build limpo do frontend:
| Erro | Arquivo | Correção |
|---|---|---|
| `Cannot find module '@/contexts/SoeMotorContext'` | `App.tsx:8` | Criado `client/src/contexts/SoeMotorContext.tsx` |
| `Cannot find module '@/pages/SOE'` | `App.tsx:39` | Criado `client/src/pages/SOE.tsx` |
| `LazyExoticComponent` não atribuível a `() => Element` (20x) | `protected-route.tsx` | Tipo alterado para `React.ComponentType<any>` |
**Arquivos criados/modificados:**
- `client/src/contexts/SoeMotorContext.tsx` — context global com `SoeMotorProvider` e hook `useSoeMotor`
- `client/src/pages/SOE.tsx` — página SOE (Sistema de Operações Empresariais), re-export do módulo Plus/ERP
- `client/src/lib/protected-route.tsx` — tipo do prop `component` corrigido
**Resultado:** `npx tsc --noEmit` zerou todos os erros do `App.tsx`. Erros remanescentes são pré-existentes em outros arquivos e não relacionados às correções.
---
### 1.2 Guia de deploy no Coolify (`DEPLOY_COOLIFY.md`)
Documento completo de 359 linhas cobrindo:
- **Arquitetura de serviços** — mapa visual de todos os containers e redes
- **Variáveis de ambiente** — obrigatórias e opcionais por módulo (Core, IA, Superset, Plus, ERPNext)
- **Profiles Docker** — como habilitar `ai`, `bi`, `plus`, `erpnext` via `COMPOSE_PROFILES`
- **Sequência de inicialização** — ordem garantida pelos `depends_on` e healthchecks
- **Traefik + HTTPS** — labels prontas para Let's Encrypt via Coolify
- **Volumes e persistência** — tabela de criticidade + comandos de backup PostgreSQL
- **Stack de IA** — 3 tiers LiteLLM (LLMFit → Ollama → externos), como baixar modelos, como habilitar LLMFit
- **Pós-deploy** — migrations, verificação de saúde
- **Checklist final** — antes, durante e após o deploy
- **Comandos de referência rápida**
---
### 1.3 Relatório de comparação Auditoria vs Desenvolvimento
Análise completa cruzando o `ARCADIA_AUDIT_EXECUCAO.md` (auditoria de 13/Mar) com todos os 18 commits do branch:
**Resultado geral: 44% concluído, 17% parcial, 38% pendente**
| Sprint | ✅ | ⚠️ | ❌ |
|---|---|---|---|
| S — Segurança (10 itens) | 7 | 3 | 0 |
| 0 — Deploy (6 itens) | 2 | 1 | 3 |
| 1 — Inteligência (6 itens) | 2 | 2 | 2 |
| 2 — App Store (4 itens) | 3 | 0 | 1 |
| 3 — Compass IA (4 itens) | 1 | 1 | 2 |
| 4 — ERP Real (6 itens) | 0 | 0 | 6 |
| 5 — Soberania IA (4 itens) | 3 | 1 | 0 |
| 6 — Qualidade (7 itens) | 3 | 0 | 4 |
| 7 — ERPNext (5 itens) | 2 | 1 | 2 |
---
## 2. Estado do Superset BI (análise técnica)
Levantamento completo da integração Apache Superset:
**Funcionando:**
- Gateway Node.js com 4 endpoints (`/guest-token`, `/dashboards`, `/health`, proxy `/superset/*`)
- Guest Token JWT via service account com cache de 50 min
- Componente React `<SupersetDashboard>` reutilizável com fallback iframe
- Docker: init automático, banco `arcadia` registrado somente leitura, admin criado
- Feature flags: embed, drill-to-detail, cross-filters, alerts, templates Jinja
**Pendente / stub:**
- RLS por tenant — `rls: []` vazio, todos veem todos os dados
- Nenhum dashboard pré-criado — criação manual pelo usuário
- Cache Redis — usando `SimpleCache` em memória
- SSO/OAuth — auth local apenas
---
## 3. Contexto acumulado do ciclo completo (desde 13/Mar)
### O que foi construído no branch (18 commits, 71 arquivos, +9.399 linhas)
| Área | Principais entregas |
|---|---|
| **Segurança** | Auth em todas as rotas XOS, CORS Python corrigido, credentials removidas, timeout+retry, approval guard Manus |
| **XOS CRM** | Fases 14 completas: automações, SLA, horário comercial, campanhas, Socket.IO tempo real |
| **Novas páginas React** | `XosSupervisor`, `XosReports`, `XosProtocols` |
| **SOE Rule Engine** | Motor de regras contábil/fiscal + importador ERPNext |
| **Superset BI** | Integração completa embed + proxy + guest token |
| **Infra Docker** | `docker-compose.prod.yml` com profiles: `ai`, `bi`, `plus`, `erpnext` |
| **IA Soberania** | LiteLLM 3 tiers + Ollama + slot LLMFit pronto |
| **Qualidade** | Rate limiting, logging estruturado (`server/logger.ts`), paginação |
| **App Store** | `GET /api/marketplace/my-apps` + AppCenter por subscription |
| **Docs** | `DEPLOY_COOLIFY.md`, `INTEGRACAO_IA.md`, `PLANO_BI_SUPERSET.md`, `PLANO_SOE_CENTRAL_v2.md` |
---
## 4. Demandas prioritárias pendentes
### Críticas (bloqueia produção estável)
1. **LMS e Quality** — auth ainda incompleta (SEC-02, SEC-03)
- `server/lms/routes.ts` — 8 rotas ainda públicas
- `server/quality/routes.ts` — ~50 rotas revisão insuficiente
2. **WhatsApp auto-reply** — configuração ainda em `Map` na memória
- Reiniciar servidor = perda total da configuração sem aviso
- Persistir em tabela `whatsapp_auto_reply_config` (migration já existe)
3. **`pg_dump` do Replit** — exportar banco antes de encerrar o plano
- Risco de perda de dados de produção atual
### Altas (produto incompleto)
4. **Ciclo de aprendizado Manus** — embeddings não são populados automaticamente
- Busca semântica retorna vazio, sistema não aprende das interações
5. **Páginas placeholder**`Agent.tsx`, `CentralApis.tsx`, `ApiHub.tsx`
- Três das páginas mais estratégicas ainda sem implementação
6. **Sprint 4 inteiro — Integrações ERP reais**
- Adaptadores Omie e TOTVS: zero implementado
- Todos os conectores da Central de API ainda mockados
- Estrutura fragmentada em 4 lugares (`server/erpnext/`, `server/crm/frappe-service.ts`, `server/api-central/`, `server/migration/`)
### Médias (qualidade de engenharia)
7. **Error boundaries globais no React** — sem captura de erros em cascata
8. **Testes automatizados** — zero no projeto inteiro
9. **Connection pooling Python** — nova conexão DB por request nos 6 microserviços
10. **Consolidar sistemas de comunicação** — legacy (`whatsapp_*`, `chat_*`) e moderno (`comm_*`, `xosConversations`) duplicados
11. **36 tabelas sem insert schema** — risco de runtime errors no Drizzle
12. **40+ tabelas sem `createdAt`/`updatedAt`** — auditoria e ordenação impossíveis
### Decisões arquiteturais abertas
| # | Decisão |
|---|---|
| D1 | BI padrão: Metabase (existente) vs Superset (integrado)? |
| D2 | LMS: tabelas dinâmicas vs schema.ts fixo? |
| D3 | Sistema de comunicação canônico: legacy vs moderno vs XOS? |
| D4 | Marketplace público ou autenticado (hoje público — intencional)? |
---
## 5. Próxima sessão recomendada
**Sequência sugerida por impacto/risco:**
```
1. Corrigir auth LMS + Quality (23h) → fecha todas as vulnerabilidades SEC
2. Persistir WhatsApp auto-reply no banco (1h) → estabilidade em produção
3. Fechar ciclo embeddings Manus (2h) → IA começa a aprender de verdade
4. Implementar Agent.tsx com UI real (34h) → página mais estratégica
5. Adaptador Omie — primeira integração ERP real (46h)
```
---
*Sessão registrada em 2026-03-18 | Branch: `claude/analyze-project-0mXjP`*

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

View File

@ -1,5 +1,5 @@
import { Switch, Route, useLocation } from "wouter";
import { lazy, Suspense, useEffect, useState } from "react";
import { lazy, Suspense, useEffect } from "react";
import { queryClient } from "./lib/queryClient";
import { QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "@/components/ui/toaster";
@ -9,8 +9,6 @@ import { SoeMotorProvider } from "@/contexts/SoeMotorContext";
import { ProtectedRoute } from "@/lib/protected-route";
import { CommandPalette } from "@/components/CommandPalette";
import { KnowledgeCollectorInit } from "@/components/KnowledgeCollectorInit";
import { OpenClawWidget } from "@/components/openclaw/OpenClawWidget";
import { ErrorBoundary } from "@/components/ErrorBoundary";
import NotFound from "@/pages/not-found";
import AuthPage from "@/pages/auth-page";
@ -73,56 +71,13 @@ const XosSupervisor = lazy(() => import("@/pages/XosSupervisor"));
const XosReports = lazy(() => import("@/pages/XosReports"));
const XosProtocols = lazy(() => import("@/pages/XosProtocols"));
function LoadingFallback() {
const [showSlowLoading, setShowSlowLoading] = useState(false);
const [showError, setShowError] = useState(false);
useEffect(() => {
// Show "slow loading" message after 3 seconds
const slowTimer = setTimeout(() => setShowSlowLoading(true), 3000);
// Show error hint after 10 seconds
const errorTimer = setTimeout(() => setShowError(true), 10000);
return () => {
clearTimeout(slowTimer);
clearTimeout(errorTimer);
};
}, []);
return (
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh", flexDirection: "column" }}>
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh" }}>
<div style={{ textAlign: "center" }}>
<div style={{ width: 40, height: 40, border: "3px solid #e5e7eb", borderTopColor: "#3b82f6", borderRadius: "50%", animation: "spin 1s linear infinite", margin: "0 auto" }} />
<p style={{ marginTop: 16, color: "#6b7280" }}>Carregando...</p>
{showSlowLoading && (
<p style={{ marginTop: 8, color: "#9ca3af", fontSize: 14 }}>
Isso está demorando mais que o esperado...
</p>
)}
{showError && (
<div style={{ marginTop: 16, padding: 12, backgroundColor: "#fef3c7", borderRadius: 6, maxWidth: 300 }}>
<p style={{ color: "#92400e", fontSize: 13, margin: 0 }}>
A página pode ter um erro. Tente recarregar ou volte para a página anterior.
</p>
<button
onClick={() => window.location.reload()}
style={{
marginTop: 8,
padding: "6px 12px",
backgroundColor: "#f59e0b",
color: "white",
border: "none",
borderRadius: 4,
cursor: "pointer",
fontSize: 13
}}
>
Recarregar Página
</button>
</div>
)}
</div>
<style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
</div>
@ -131,74 +86,72 @@ function LoadingFallback() {
function Router() {
return (
<ErrorBoundary>
<Suspense fallback={<LoadingFallback />}>
<Switch>
<ProtectedRoute path="/" component={Cockpit} />
<ProtectedRoute path="/agent" component={Agent} />
<ProtectedRoute path="/admin" component={Admin} />
<ProtectedRoute path="/chat" component={Chat} />
<ProtectedRoute path="/whatsapp" component={WhatsApp} />
<ProtectedRoute path="/comunicacao" component={XosInbox} />
<ProtectedRoute path="/automations" component={Automations} />
<ProtectedRoute path="/insights" component={BiWorkspace} />
<ProtectedRoute path="/compass" component={ProcessCompass} />
<ProtectedRoute path="/crm" component={Crm} />
<ProtectedRoute path="/production" component={Production} />
<ProtectedRoute path="/support" component={Support} />
<ProtectedRoute path="/valuation" component={Valuation} />
<ProtectedRoute path="/canvas" component={Canvas} />
<ProtectedRoute path="/ide" component={IDE} />
<ProtectedRoute path="/scientist" component={Scientist} />
<ProtectedRoute path="/knowledge" component={Knowledge} />
<ProtectedRoute path="/central-apis" component={CentralApis} />
<ProtectedRoute path="/api-tester" component={ApiTesterPage} />
<ProtectedRoute path="/api-hub" component={ApiHub} />
<ProtectedRoute path="/fisco" component={Fisco} />
<ProtectedRoute path="/people" component={People} />
<ProtectedRoute path="/contabil" component={Contabil} />
<ProtectedRoute path="/soe" component={SOE} />
<ProtectedRoute path="/erp" component={SOE} />
<ProtectedRoute path="/financeiro" component={Financeiro} />
<ProtectedRoute path="/communities" component={Communities} />
<ProtectedRoute path="/quality" component={QualityModule} />
<ProtectedRoute path="/commercial-env" component={CommercialEnv} />
<ProtectedRoute path="/field-ops" component={FieldOperations} />
<ProtectedRoute path="/technical" component={TechnicalModule} />
<ProtectedRoute path="/suppliers" component={SuppliersPortal} />
<ProtectedRoute path="/nps" component={NPSSurvey} />
<ProtectedRoute path="/engineering" component={EngineeringHub} />
<ProtectedRoute path="/development" component={DevelopmentModule} />
<ProtectedRoute path="/retail" component={ArcadiaRetail} />
<ProtectedRoute path="/plus" component={() => { const [, nav] = useLocation(); useEffect(() => nav("/soe"), []); return null; }} />
<ProtectedRoute path="/super-admin" component={SuperAdmin} />
<ProtectedRoute path="/marketplace" component={Marketplace} />
<ProtectedRoute path="/lms" component={LMS} />
<ProtectedRoute path="/apps" component={AppCenter} />
<ProtectedRoute path="/xos" component={XosCentral} />
<ProtectedRoute path="/xos/crm" component={XosCrm} />
<ProtectedRoute path="/xos/inbox" component={XosInbox} />
<ProtectedRoute path="/xos/tickets" component={XosTickets} />
<ProtectedRoute path="/xos/campaigns" component={XosCampaigns} />
<ProtectedRoute path="/xos/automations" component={XosAutomations} />
<ProtectedRoute path="/xos/sites" component={XosSites} />
<ProtectedRoute path="/xos/governance" component={XosGovernance} />
<ProtectedRoute path="/xos/pipeline" component={XosPipeline} />
<ProtectedRoute path="/xos/supervisor" component={XosSupervisor} />
<ProtectedRoute path="/xos/reports" component={XosReports} />
<ProtectedRoute path="/xos/protocols" component={XosProtocols} />
<Suspense fallback={<LoadingFallback />}>
<Switch>
<ProtectedRoute path="/" component={Cockpit} />
<ProtectedRoute path="/agent" component={Agent} />
<ProtectedRoute path="/admin" component={Admin} />
<ProtectedRoute path="/chat" component={Chat} />
<ProtectedRoute path="/whatsapp" component={WhatsApp} />
<ProtectedRoute path="/comunicacao" component={XosInbox} />
<ProtectedRoute path="/automations" component={Automations} />
<ProtectedRoute path="/insights" component={BiWorkspace} />
<ProtectedRoute path="/compass" component={ProcessCompass} />
<ProtectedRoute path="/crm" component={Crm} />
<ProtectedRoute path="/production" component={Production} />
<ProtectedRoute path="/support" component={Support} />
<ProtectedRoute path="/valuation" component={Valuation} />
<ProtectedRoute path="/canvas" component={Canvas} />
<ProtectedRoute path="/ide" component={IDE} />
<ProtectedRoute path="/scientist" component={Scientist} />
<ProtectedRoute path="/knowledge" component={Knowledge} />
<ProtectedRoute path="/central-apis" component={CentralApis} />
<ProtectedRoute path="/api-tester" component={ApiTesterPage} />
<ProtectedRoute path="/api-hub" component={ApiHub} />
<ProtectedRoute path="/fisco" component={Fisco} />
<ProtectedRoute path="/people" component={People} />
<ProtectedRoute path="/contabil" component={Contabil} />
<ProtectedRoute path="/soe" component={SOE} />
<ProtectedRoute path="/erp" component={SOE} />
<ProtectedRoute path="/financeiro" component={Financeiro} />
<ProtectedRoute path="/communities" component={Communities} />
<ProtectedRoute path="/quality" component={QualityModule} />
<ProtectedRoute path="/commercial-env" component={CommercialEnv} />
<ProtectedRoute path="/field-ops" component={FieldOperations} />
<ProtectedRoute path="/technical" component={TechnicalModule} />
<ProtectedRoute path="/suppliers" component={SuppliersPortal} />
<ProtectedRoute path="/nps" component={NPSSurvey} />
<ProtectedRoute path="/engineering" component={EngineeringHub} />
<ProtectedRoute path="/development" component={DevelopmentModule} />
<ProtectedRoute path="/retail" component={ArcadiaRetail} />
<ProtectedRoute path="/plus" component={() => { const [, nav] = useLocation(); useEffect(() => nav("/soe"), []); return null; }} />
<ProtectedRoute path="/super-admin" component={SuperAdmin} />
<ProtectedRoute path="/marketplace" component={Marketplace} />
<ProtectedRoute path="/lms" component={LMS} />
<ProtectedRoute path="/apps" component={AppCenter} />
<ProtectedRoute path="/xos" component={XosCentral} />
<ProtectedRoute path="/xos/crm" component={XosCrm} />
<ProtectedRoute path="/xos/inbox" component={XosInbox} />
<ProtectedRoute path="/xos/tickets" component={XosTickets} />
<ProtectedRoute path="/xos/campaigns" component={XosCampaigns} />
<ProtectedRoute path="/xos/automations" component={XosAutomations} />
<ProtectedRoute path="/xos/sites" component={XosSites} />
<ProtectedRoute path="/xos/governance" component={XosGovernance} />
<ProtectedRoute path="/xos/pipeline" component={XosPipeline} />
<ProtectedRoute path="/xos/supervisor" component={XosSupervisor} />
<ProtectedRoute path="/xos/reports" component={XosReports} />
<ProtectedRoute path="/xos/protocols" component={XosProtocols} />
<ProtectedRoute path="/doctype-builder" component={DocTypeBuilder} />
<ProtectedRoute path="/page-builder" component={PageBuilder} />
<ProtectedRoute path="/migration" component={Migration} />
<ProtectedRoute path="/dev-center" component={DevCenter} />
<ProtectedRoute path="/page/:id" component={WorkspacePage} />
<ProtectedRoute path="/app/:id" component={AppViewer} />
<Route path="/auth" component={AuthPage} />
<Route component={NotFound} />
</Switch>
</Suspense>
</ErrorBoundary>
<ProtectedRoute path="/doctype-builder" component={DocTypeBuilder} />
<ProtectedRoute path="/page-builder" component={PageBuilder} />
<ProtectedRoute path="/migration" component={Migration} />
<ProtectedRoute path="/dev-center" component={DevCenter} />
<ProtectedRoute path="/page/:id" component={WorkspacePage} />
<ProtectedRoute path="/app/:id" component={AppViewer} />
<Route path="/auth" component={AuthPage} />
<Route component={NotFound} />
</Switch>
</Suspense>
);
}
@ -211,7 +164,6 @@ function App() {
<KnowledgeCollectorInit />
<Toaster />
<CommandPalette />
<OpenClawWidget />
<Router />
</TooltipProvider>
</SoeMotorProvider>

View File

@ -91,7 +91,7 @@ function CompactNavigationBar() {
</div>
<span className="hidden md:inline">Insights</span>
</div>
<div
<div
className="flex items-center gap-1 hover:bg-muted px-2 py-1.5 rounded cursor-pointer transition-colors flex-shrink-0"
onClick={() => navigateTo("/compass", "Compass")}
data-testid="bookmark-compass"

View File

@ -1,146 +0,0 @@
import { lazy, Suspense } from "react";
interface ChartData {
type: "bar" | "line" | "pie" | "area";
title: string;
data: Record<string, any>[];
xKey: string;
yKeys: string[];
colors: string[];
}
// Lazy load do recharts para não pesar o bundle inicial
const RechartsComponents = lazy(() =>
import("recharts").then((module) => ({
default: function RechartsWrapper({ chart }: { chart: ChartData }) {
const {
BarChart,
Bar,
LineChart,
Line,
PieChart,
Pie,
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
Cell,
} = module;
const renderChart = () => {
switch (chart.type) {
case "bar":
return (
<BarChart data={chart.data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey={chart.xKey} />
<YAxis />
<Tooltip />
<Legend />
{chart.yKeys.map((key, index) => (
<Bar
key={key}
dataKey={key}
fill={chart.colors[index % chart.colors.length]}
/>
))}
</BarChart>
);
case "line":
return (
<LineChart data={chart.data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey={chart.xKey} />
<YAxis />
<Tooltip />
<Legend />
{chart.yKeys.map((key, index) => (
<Line
key={key}
type="monotone"
dataKey={key}
stroke={chart.colors[index % chart.colors.length]}
/>
))}
</LineChart>
);
case "area":
return (
<AreaChart data={chart.data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey={chart.xKey} />
<YAxis />
<Tooltip />
<Legend />
{chart.yKeys.map((key, index) => (
<Area
key={key}
type="monotone"
dataKey={key}
stroke={chart.colors[index % chart.colors.length]}
fill={chart.colors[index % chart.colors.length]}
fillOpacity={0.6}
/>
))}
</AreaChart>
);
case "pie":
return (
<PieChart>
<Tooltip />
<Legend />
<Pie
data={chart.data}
dataKey={chart.yKeys[0]}
nameKey={chart.xKey}
cx="50%"
cy="50%"
outerRadius={80}
label
>
{chart.data.map((_, index) => (
<Cell
key={`cell-${index}`}
fill={chart.colors[index % chart.colors.length]}
/>
))}
</Pie>
</PieChart>
);
default:
return null;
}
};
return (
<div className="w-full h-64">
<ResponsiveContainer width="100%" height="100%">
{renderChart()}
</ResponsiveContainer>
</div>
);
},
}))
);
interface ChartRendererProps {
chart: ChartData;
}
export function ChartRenderer({ chart }: ChartRendererProps) {
return (
<Suspense
fallback={
<div className="w-full h-64 flex items-center justify-center bg-muted/20 rounded">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
</div>
}
>
<RechartsComponents chart={chart} />
</Suspense>
);
}

View File

@ -1,90 +0,0 @@
import { Component, ReactNode } from "react";
import { AlertCircle, RefreshCw } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error("[ErrorBoundary] Erro capturado:", error);
console.error("[ErrorBoundary] Info:", errorInfo);
}
handleRetry = () => {
this.setState({ hasError: false, error: undefined });
window.location.reload();
};
handleGoHome = () => {
this.setState({ hasError: false, error: undefined });
window.location.href = "/";
};
render() {
if (this.state.hasError) {
// Fallback customizado ou padrão
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div className="min-h-screen flex items-center justify-center bg-slate-50 p-4">
<Card className="max-w-lg w-full">
<CardHeader className="text-center">
<div className="mx-auto w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mb-4">
<AlertCircle className="w-8 h-8 text-red-600" />
</div>
<CardTitle className="text-xl">Ops! Algo deu errado</CardTitle>
<CardDescription>
Não foi possível carregar esta página. O módulo pode estar incompleto ou com erro.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{this.state.error && (
<div className="bg-slate-100 p-3 rounded text-sm font-mono text-slate-600 overflow-auto max-h-32">
{this.state.error.message}
</div>
)}
<div className="flex gap-3 justify-center">
<Button
variant="outline"
onClick={this.handleGoHome}
>
Voltar ao Início
</Button>
<Button
onClick={this.handleRetry}
className="gap-2"
>
<RefreshCw className="w-4 h-4" />
Tentar Novamente
</Button>
</div>
</CardContent>
</Card>
</div>
);
}
return this.props.children;
}
}

View File

@ -1,277 +0,0 @@
import { useState } from "react";
import { useMutation } from "@tanstack/react-query";
import { apiRequest } from "@/lib/queryClient";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Textarea } from "@/components/ui/textarea";
import { Badge } from "@/components/ui/badge";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Brain, Loader2 } from "lucide-react";
interface AnalyzeRequest {
agent: "statistician" | "fiscal_auditor" | "researcher";
task: string;
context?: Record<string, unknown>;
}
interface AnalyzeResponse {
agent: string;
model: string;
result: string;
execution_id: string;
duration_ms: number;
}
const AGENTS = [
{
value: "statistician",
label: "Statistician",
model: "llama3.1:8b",
placeholder:
"Ex: Analise a distribuição de vendas por região no último trimestre",
},
{
value: "fiscal_auditor",
label: "Fiscal Auditor",
model: "llama3.1:8b",
placeholder:
"Ex: Verifique inconsistências nos registros NFe do CNPJ 12.345.678/0001-90",
},
{
value: "researcher",
label: "Researcher",
model: "llama3.1:8b",
placeholder:
"Ex: Quais fornecedores têm maior correlação com atrasos de entrega?",
},
] as const;
interface MiroFlowControlProps {
currentDashboardData?: Record<string, unknown>;
}
export function MiroFlowControl({
currentDashboardData,
}: MiroFlowControlProps) {
const [enabled, setEnabled] = useState(false);
const [selectedAgent, setSelectedAgent] = useState<
"statistician" | "fiscal_auditor" | "researcher"
>("statistician");
const [task, setTask] = useState("");
const mutation = useMutation({
mutationFn: async (request: AnalyzeRequest) => {
const response = await apiRequest("POST", "/api/miroflow/analyze", request);
return response.json() as Promise<AnalyzeResponse>;
},
});
const createDashboardMutation = useMutation({
mutationFn: async (dashboardData: {
dashboardTitle: string;
sqlQuery: string;
}) => {
const response = await apiRequest(
"POST",
"/api/superset/miroflow/create-dashboard",
{
...dashboardData,
analysisId: mutation.data?.execution_id,
agent: selectedAgent,
task,
insights: mutation.data?.result,
}
);
return response.json();
},
});
const handleAnalyze = () => {
if (!task.trim()) {
return;
}
mutation.mutate({
agent: selectedAgent,
task,
context: currentDashboardData,
});
};
const currentAgentConfig = AGENTS.find((a) => a.value === selectedAgent);
return (
<div className="space-y-6">
<Card className="bg-[#1f334d] border border-[#c89b3c]/20">
<CardHeader className="border-b border-[#c89b3c]/20 pb-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Brain className="w-6 h-6 text-[#c89b3c]" />
<CardTitle className="text-white">Modo Científico</CardTitle>
</div>
<button
onClick={() => setEnabled(!enabled)}
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-colors ${
enabled ? "bg-[#c89b3c]" : "bg-gray-600"
}`}
>
<span
className={`inline-block h-6 w-6 transform rounded-full bg-white transition-transform ${
enabled ? "translate-x-7" : "translate-x-1"
}`}
/>
</button>
</div>
</CardHeader>
{enabled && (
<CardContent className="space-y-4 pt-6">
{/* Agent Selection */}
<div>
<label className="block text-sm font-medium text-white mb-2">
Selecione o Agente
</label>
<Select value={selectedAgent} onValueChange={(value: any) => setSelectedAgent(value)}>
<SelectTrigger className="bg-[#2a4466] border border-[#c89b3c]/20 text-white">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-[#2a4466] border border-[#c89b3c]/20">
{AGENTS.map((agent) => (
<SelectItem key={agent.value} value={agent.value} className="text-white">
<div>
<div>{agent.label}</div>
<div className="text-xs text-gray-400">{agent.model}</div>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Task Input */}
<div>
<label className="block text-sm font-medium text-white mb-2">
Digite a Tarefa
</label>
<Textarea
placeholder={currentAgentConfig?.placeholder || "Digite sua tarefa..."}
value={task}
onChange={(e) => setTask(e.target.value)}
disabled={mutation.isPending}
className="bg-[#2a4466] border border-[#c89b3c]/20 text-white placeholder:text-gray-500 min-h-24"
/>
</div>
{/* Analyze Button */}
<Button
onClick={handleAnalyze}
disabled={!task.trim() || mutation.isPending}
className="w-full bg-[#c89b3c] hover:bg-[#d4a94a] text-[#1f334d] font-semibold"
>
{mutation.isPending ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Analisando...
</>
) : (
"Analisar"
)}
</Button>
{/* Error Display */}
{mutation.isError && (
<div className="rounded-lg p-4 bg-red-900/20 border border-red-500/50">
<Badge className="mb-2 bg-red-600 text-white">Erro</Badge>
<p className="text-sm text-red-100">
{mutation.error instanceof Error
? mutation.error.message
: "Erro ao processar a análise"}
</p>
</div>
)}
{/* Result Display */}
{mutation.isSuccess && (
<div className="space-y-3">
<div className="flex items-center gap-2 mb-2">
<Badge className="bg-green-600 text-white">Sucesso</Badge>
</div>
<div className="rounded-lg p-4 bg-[#2a4466] border border-[#c89b3c]/20">
<ScrollArea className="h-64 w-full pr-4">
<p className="text-white text-sm whitespace-pre-wrap">
{mutation.data?.result}
</p>
</ScrollArea>
</div>
{/* Metadata */}
<div className="grid grid-cols-3 gap-2 text-xs text-gray-400">
<div>
<span className="font-semibold text-[#c89b3c]">Modelo:</span>{" "}
{mutation.data?.model}
</div>
<div>
<span className="font-semibold text-[#c89b3c]">Tempo:</span>{" "}
{mutation.data?.duration_ms}ms
</div>
<div>
<span className="font-semibold text-[#c89b3c]">ID:</span>{" "}
{mutation.data?.execution_id?.slice(0, 8)}...
</div>
</div>
{/* Create Dashboard Button */}
<div className="pt-4 border-t border-[#c89b3c]/20">
<Button
onClick={() => {
const title = `Análise ${selectedAgent} - ${new Date().toLocaleDateString('pt-BR')}`;
createDashboardMutation.mutate({
dashboardTitle: title,
sqlQuery: `SELECT * FROM arcadia LIMIT 100`, // Placeholder - idealmente vem do MiroFlow
});
}}
disabled={createDashboardMutation.isPending}
className="w-full bg-[#8b7355] hover:bg-[#9d8568] text-white font-semibold"
>
{createDashboardMutation.isPending ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Criando Dashboard...
</>
) : (
"📊 Criar Dashboard no Superset"
)}
</Button>
{createDashboardMutation.isSuccess && (
<div className="mt-2 rounded-lg p-3 bg-green-900/20 border border-green-500/50">
<p className="text-xs text-green-100">
Dashboard criado! Atualizando...
</p>
</div>
)}
{createDashboardMutation.isError && (
<div className="mt-2 rounded-lg p-3 bg-red-900/20 border border-red-500/50">
<p className="text-xs text-red-100">
Erro: {createDashboardMutation.error instanceof Error ? createDashboardMutation.error.message : "Erro ao criar dashboard"}
</p>
</div>
)}
</div>
</div>
)}
</CardContent>
)}
</Card>
</div>
);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,245 +0,0 @@
/**
* OpenClawPanel Phase 4
*
* Painel de visualização de Skills Emergentes detectadas pelo PatternDetector.
* Exibido como aba na sidebar do /development.
*/
import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import {
Sparkles, CheckCircle, XCircle, ChevronDown, ChevronRight,
Loader2, Activity, Clock, BarChart2
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useToast } from "@/hooks/use-toast";
interface Suggestion {
id: string;
suggestedSkillName: string;
suggestedDescription: string | null;
estimatedAutomation: string | null;
confidence: string;
draftBody: string | null;
createdAt: string;
}
interface Pattern {
id: string;
actionType: string;
description: string | null;
frequency: number;
confidence: string;
firstSeenAt: string;
lastSeenAt: string;
status: string;
}
export default function OpenClawPanel() {
const [tab, setTab] = useState<"suggestions" | "patterns">("suggestions");
const [expanded, setExpanded] = useState<string | null>(null);
const { toast } = useToast();
const qc = useQueryClient();
const { data: sugData, isLoading: sugLoading } = useQuery<{ suggestions: Suggestion[]; total: number }>({
queryKey: ["/api/openclaw/suggestions"],
refetchInterval: 2 * 60 * 1000,
});
const { data: patData, isLoading: patLoading } = useQuery<{ patterns: Pattern[]; total: number }>({
queryKey: ["/api/openclaw/patterns"],
enabled: tab === "patterns",
});
const accept = useMutation({
mutationFn: async (id: string) => {
const res = await fetch(`/api/openclaw/suggestions/${id}/accept`, { method: "POST" });
if (!res.ok) throw new Error();
return res.json();
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ["/api/openclaw/suggestions"] });
qc.invalidateQueries({ queryKey: ["/api/skills"] });
toast({ title: "Skill publicada!" });
},
onError: () => toast({ title: "Erro ao aceitar", variant: "destructive" }),
});
const reject = useMutation({
mutationFn: async (id: string) => {
const res = await fetch(`/api/openclaw/suggestions/${id}/reject`, { method: "POST" });
if (!res.ok) throw new Error();
return res.json();
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ["/api/openclaw/suggestions"] });
toast({ title: "Sugestão rejeitada" });
},
onError: () => toast({ title: "Erro ao rejeitar", variant: "destructive" }),
});
const suggestions = sugData?.suggestions ?? [];
const patterns = patData?.patterns ?? [];
return (
<div className="h-full flex flex-col bg-gray-900 text-white">
{/* Header */}
<div className="px-5 py-4 border-b border-white/10 flex items-center gap-2">
<Sparkles className="w-4 h-4 text-yellow-400" />
<div>
<h2 className="text-sm font-semibold">Skills Emergentes</h2>
<p className="text-[11px] text-white/40">OpenClaw detecção automática de padrões</p>
</div>
</div>
{/* Tabs */}
<div className="flex border-b border-white/10 text-xs">
<button
onClick={() => setTab("suggestions")}
className={`px-4 py-2.5 flex items-center gap-1.5 transition-colors border-b-2 ${tab === "suggestions" ? "border-yellow-400 text-yellow-400" : "border-transparent text-white/50 hover:text-white"}`}
>
<Sparkles className="w-3 h-3" />
Sugestões
{suggestions.length > 0 && (
<span className="ml-1 rounded-full bg-yellow-400 text-black text-[9px] font-bold w-4 h-4 flex items-center justify-center">
{suggestions.length}
</span>
)}
</button>
<button
onClick={() => setTab("patterns")}
className={`px-4 py-2.5 flex items-center gap-1.5 transition-colors border-b-2 ${tab === "patterns" ? "border-yellow-400 text-yellow-400" : "border-transparent text-white/50 hover:text-white"}`}
>
<Activity className="w-3 h-3" />
Padrões
</button>
</div>
<ScrollArea className="flex-1">
{/* Sugestões */}
{tab === "suggestions" && (
<div>
{sugLoading && (
<div className="flex justify-center py-10">
<Loader2 className="w-5 h-5 animate-spin text-white/30" />
</div>
)}
{!sugLoading && suggestions.length === 0 && (
<div className="px-5 py-12 text-center">
<Sparkles className="w-8 h-8 text-white/10 mx-auto mb-3" />
<p className="text-sm text-white/30">Nenhuma sugestão pendente</p>
<p className="text-xs text-white/20 mt-1">O detector roda a cada hora</p>
</div>
)}
<div className="divide-y divide-white/5">
{suggestions.map((s) => {
const conf = Math.round(Number(s.confidence) * 100);
const isExp = expanded === s.id;
const isAccepting = accept.isPending && accept.variables === s.id;
const isRejecting = reject.isPending && reject.variables === s.id;
return (
<div key={s.id} className="px-4 py-4 space-y-2.5">
<div className="flex items-start gap-2">
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-white truncate">{s.suggestedSkillName}</p>
{s.suggestedDescription && (
<p className="text-xs text-white/50 mt-0.5 line-clamp-2">{s.suggestedDescription}</p>
)}
</div>
<Badge className="shrink-0 bg-yellow-400/10 text-yellow-400 border-yellow-400/20 text-[10px]">
{conf}%
</Badge>
</div>
{s.draftBody && (
<div>
<button
onClick={() => setExpanded(isExp ? null : s.id)}
className="flex items-center gap-1 text-[11px] text-white/40 hover:text-white/60 transition-colors"
>
{isExp ? <ChevronDown className="w-3 h-3" /> : <ChevronRight className="w-3 h-3" />}
{isExp ? "Ocultar body" : "Ver body gerado"}
</button>
{isExp && (
<pre className="mt-2 rounded bg-black/40 p-3 text-[11px] text-white/60 overflow-x-auto whitespace-pre-wrap font-mono max-h-40 border border-white/5">
{s.draftBody}
</pre>
)}
</div>
)}
<div className="flex gap-2">
<Button
size="sm"
className="h-7 text-xs bg-emerald-900/40 text-emerald-400 border border-emerald-700/40 hover:bg-emerald-900/60"
onClick={() => accept.mutate(s.id)}
disabled={isAccepting || isRejecting}
>
{isAccepting ? <Loader2 className="w-3 h-3 animate-spin" /> : <CheckCircle className="w-3 h-3" />}
<span className="ml-1">Aceitar</span>
</Button>
<Button
size="sm"
variant="ghost"
className="h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-900/20"
onClick={() => reject.mutate(s.id)}
disabled={isAccepting || isRejecting}
>
{isRejecting ? <Loader2 className="w-3 h-3 animate-spin" /> : <XCircle className="w-3 h-3" />}
<span className="ml-1">Rejeitar</span>
</Button>
</div>
</div>
);
})}
</div>
</div>
)}
{/* Padrões */}
{tab === "patterns" && (
<div>
{patLoading && (
<div className="flex justify-center py-10">
<Loader2 className="w-5 h-5 animate-spin text-white/30" />
</div>
)}
{!patLoading && patterns.length === 0 && (
<div className="px-5 py-12 text-center">
<BarChart2 className="w-8 h-8 text-white/10 mx-auto mb-3" />
<p className="text-sm text-white/30">Nenhum padrão detectado</p>
<p className="text-xs text-white/20 mt-1">Execute skills manualmente para gerar dados</p>
</div>
)}
<div className="divide-y divide-white/5">
{patterns.map((p) => {
const conf = Math.round(Number(p.confidence) * 100);
const last = new Date(p.lastSeenAt).toLocaleDateString("pt-BR");
return (
<div key={p.id} className="px-4 py-3 space-y-1">
<div className="flex items-center justify-between gap-2">
<p className="text-xs font-mono text-white/70 truncate">{p.actionType}</p>
<Badge className="shrink-0 bg-white/5 text-white/50 border-white/10 text-[10px]">
{conf}%
</Badge>
</div>
{p.description && (
<p className="text-[11px] text-white/40">{p.description}</p>
)}
<div className="flex items-center gap-3 text-[10px] text-white/30">
<span className="flex items-center gap-1"><Activity className="w-3 h-3" />{p.frequency}x</span>
<span className="flex items-center gap-1"><Clock className="w-3 h-3" />{last}</span>
</div>
</div>
);
})}
</div>
</div>
)}
</ScrollArea>
</div>
);
}

View File

@ -1,353 +0,0 @@
import { useState, useEffect, useCallback } from "react";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useToast } from "@/hooks/use-toast";
import {
RefreshCw, CheckCircle, XCircle, Loader2, Users, Package,
ShoppingCart, FileText, ExternalLink, Building2, Link2
} from "lucide-react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
interface Empresa {
id: number;
tenantId: number;
razaoSocial: string;
nomeFantasia: string | null;
cnpj: string;
tipo: string;
plusEmpresaId: number | null;
isActive: boolean;
}
interface SyncResult {
success: boolean;
created?: number;
updated?: number;
failed?: number;
errors?: string[];
message?: string;
}
interface PlusStatus {
connected: boolean;
url?: string;
message?: string;
}
export default function PlusSyncPanel({ tenantId }: { tenantId?: number }) {
const { toast } = useToast();
const [empresas, setEmpresas] = useState<Empresa[]>([]);
const [selectedEmpresa, setSelectedEmpresa] = useState<string>("");
const [plusStatus, setPlusStatus] = useState<PlusStatus | null>(null);
const [loading, setLoading] = useState(false);
const [syncingCustomers, setSyncingCustomers] = useState(false);
const [syncingProducts, setSyncingProducts] = useState(false);
const [syncingSales, setSyncingSales] = useState(false);
const [importingCustomers, setImportingCustomers] = useState(false);
const [importingProducts, setImportingProducts] = useState(false);
const [bindPlusId, setBindPlusId] = useState("");
const [lastResults, setLastResults] = useState<Record<string, SyncResult>>({});
const currentTenant = tenantId || 1;
const loadEmpresas = useCallback(async () => {
try {
const res = await fetch(`/api/retail/plus/empresas?tenantId=${currentTenant}`);
if (res.ok) {
const data = await res.json();
setEmpresas(data);
if (data.length > 0 && !selectedEmpresa) {
setSelectedEmpresa(data[0].id.toString());
}
}
} catch (error) {
console.error("Error loading empresas:", error);
}
}, [currentTenant, selectedEmpresa]);
const checkStatus = useCallback(async () => {
setLoading(true);
try {
const empresaId = selectedEmpresa ? `?empresaId=${selectedEmpresa}` : "";
const res = await fetch(`/api/retail/plus/status${empresaId}`);
if (res.ok) {
const data = await res.json();
setPlusStatus(data);
}
} catch (error) {
setPlusStatus({ connected: false, message: "Erro ao verificar conexão" });
} finally {
setLoading(false);
}
}, [selectedEmpresa]);
useEffect(() => {
loadEmpresas();
}, [loadEmpresas]);
useEffect(() => {
if (selectedEmpresa) checkStatus();
}, [selectedEmpresa, checkStatus]);
const handleSync = async (action: string, setLoading: (v: boolean) => void) => {
setLoading(true);
try {
const body: any = { tenantId: currentTenant };
if (selectedEmpresa) body.empresaId = parseInt(selectedEmpresa);
const res = await fetch(`/api/retail/plus/${action}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const data = await res.json();
setLastResults(prev => ({ ...prev, [action]: data }));
if (data.success) {
toast({ title: "Sincronização concluída", description: `Criados: ${data.created || 0}, Atualizados: ${data.updated || 0}` });
} else {
toast({ title: "Erro na sincronização", description: data.message || "Falha na operação", variant: "destructive" });
}
} catch (error) {
toast({ title: "Erro", description: "Falha na comunicação com o servidor", variant: "destructive" });
} finally {
setLoading(false);
}
};
const handleBindEmpresa = async () => {
if (!selectedEmpresa || !bindPlusId) return;
try {
const res = await fetch(`/api/retail/plus/empresas/${selectedEmpresa}/bind`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ plusEmpresaId: parseInt(bindPlusId) }),
});
if (res.ok) {
toast({ title: "Empresa vinculada", description: "Empresa vinculada ao Plus com sucesso" });
loadEmpresas();
setBindPlusId("");
}
} catch (error) {
toast({ title: "Erro", description: "Falha ao vincular empresa", variant: "destructive" });
}
};
const currentEmpresa = empresas.find(e => e.id.toString() === selectedEmpresa);
const SyncResultBadge = ({ result }: { result?: SyncResult }) => {
if (!result) return null;
return (
<div className="flex items-center gap-2 text-xs mt-1">
{result.success ? (
<Badge variant="outline" className="text-green-600 border-green-300">
<CheckCircle className="h-3 w-3 mr-1" />
OK: {result.created || 0} criados, {result.updated || 0} atualizados
</Badge>
) : (
<Badge variant="destructive" className="text-xs">
<XCircle className="h-3 w-3 mr-1" />
{result.message || "Falha"}
</Badge>
)}
</div>
);
};
return (
<div className="space-y-6" data-testid="plus-sync-panel">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-semibold">Integração Plus ERP</h3>
<p className="text-sm text-muted-foreground">Sincronização bidirecional com o Arcádia Plus (Laravel ERP)</p>
</div>
<div className="flex items-center gap-2">
{plusStatus && (
<Badge variant={plusStatus.connected ? "default" : "destructive"} data-testid="plus-connection-status">
{plusStatus.connected ? (
<><CheckCircle className="h-3 w-3 mr-1" /> Conectado</>
) : (
<><XCircle className="h-3 w-3 mr-1" /> Desconectado</>
)}
</Badge>
)}
<Button variant="outline" size="sm" onClick={checkStatus} disabled={loading} data-testid="btn-check-plus-status">
<RefreshCw className={`h-4 w-4 mr-1 ${loading ? "animate-spin" : ""}`} />
Testar Conexão
</Button>
</div>
</div>
<Card>
<CardHeader>
<CardTitle className="text-base flex items-center gap-2">
<Building2 className="h-4 w-4" />
Empresa Ativa
</CardTitle>
<CardDescription>Selecione a empresa para operações de sincronização</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-end gap-3">
<div className="flex-1">
<Label>Empresa</Label>
<Select value={selectedEmpresa} onValueChange={setSelectedEmpresa}>
<SelectTrigger data-testid="select-empresa-plus">
<SelectValue placeholder="Selecione uma empresa" />
</SelectTrigger>
<SelectContent>
{empresas.map(emp => (
<SelectItem key={emp.id} value={emp.id.toString()}>
{emp.nomeFantasia || emp.razaoSocial} ({emp.cnpj}) {emp.tipo === "matriz" ? "📍" : "🔗"}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
{currentEmpresa && (
<div className="rounded-lg border p-3 bg-muted/50 space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">{currentEmpresa.razaoSocial}</span>
<Badge variant="outline">{currentEmpresa.tipo === "matriz" ? "Matriz" : "Filial"}</Badge>
</div>
<div className="text-xs text-muted-foreground">CNPJ: {currentEmpresa.cnpj}</div>
<div className="flex items-center gap-2">
<Link2 className="h-3 w-3" />
<span className="text-xs">Plus ID: {currentEmpresa.plusEmpresaId || "Não vinculada"}</span>
{!currentEmpresa.plusEmpresaId && (
<div className="flex items-center gap-1 ml-2">
<Input
type="number"
placeholder="ID da empresa no Plus"
value={bindPlusId}
onChange={e => setBindPlusId(e.target.value)}
className="h-7 w-36 text-xs"
data-testid="input-bind-plus-id"
/>
<Button size="sm" variant="outline" className="h-7 text-xs" onClick={handleBindEmpresa} data-testid="btn-bind-empresa">
Vincular
</Button>
</div>
)}
</div>
</div>
)}
</CardContent>
</Card>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card>
<CardHeader>
<CardTitle className="text-base flex items-center gap-2">
<Users className="h-4 w-4" />
Clientes / Fornecedores
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
className="flex-1"
disabled={syncingCustomers}
onClick={() => handleSync("sync/customers", setSyncingCustomers)}
data-testid="btn-sync-customers-to-plus"
>
{syncingCustomers ? <Loader2 className="h-4 w-4 mr-1 animate-spin" /> : <ExternalLink className="h-4 w-4 mr-1" />}
Enviar Plus
</Button>
<Button
variant="outline"
size="sm"
className="flex-1"
disabled={importingCustomers}
onClick={() => handleSync("import/customers", setImportingCustomers)}
data-testid="btn-import-customers-from-plus"
>
{importingCustomers ? <Loader2 className="h-4 w-4 mr-1 animate-spin" /> : <ExternalLink className="h-4 w-4 mr-1 rotate-180" />}
Plus Importar
</Button>
</div>
<SyncResultBadge result={lastResults["sync/customers"] || lastResults["import/customers"]} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base flex items-center gap-2">
<Package className="h-4 w-4" />
Produtos / Estoque
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
className="flex-1"
disabled={importingProducts}
onClick={() => handleSync("import/products", setImportingProducts)}
data-testid="btn-import-products-from-plus"
>
{importingProducts ? <Loader2 className="h-4 w-4 mr-1 animate-spin" /> : <Package className="h-4 w-4 mr-1" />}
Plus Importar Produtos
</Button>
</div>
<SyncResultBadge result={lastResults["import/products"]} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base flex items-center gap-2">
<ShoppingCart className="h-4 w-4" />
Vendas PDV
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<p className="text-xs text-muted-foreground">
As vendas são sincronizadas automaticamente ao finalizar no PDV.
Use o botão abaixo para reenviar vendas pendentes.
</p>
<Button
variant="outline"
size="sm"
disabled={syncingSales}
onClick={() => {
toast({ title: "Em breve", description: "Sincronização em lote será implementada" });
}}
data-testid="btn-sync-pending-sales"
>
{syncingSales ? <Loader2 className="h-4 w-4 mr-1 animate-spin" /> : <RefreshCw className="h-4 w-4 mr-1" />}
Reenviar Pendentes
</Button>
<SyncResultBadge result={lastResults["sync/sales"]} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base flex items-center gap-2">
<FileText className="h-4 w-4" />
Documentos Fiscais
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<p className="text-xs text-muted-foreground">
Emissão de NF-e e NFC-e via Plus ERP. Selecione a venda na aba PDV
e use a opção "Emitir NF-e" para gerar o documento fiscal.
</p>
<Badge variant="outline" className="text-xs">
<FileText className="h-3 w-3 mr-1" />
Integrado via Plus API
</Badge>
</CardContent>
</Card>
</div>
</div>
);
}

View File

@ -1,131 +0,0 @@
/**
* OpenClawWidget.tsx
*
* Widget flutuante do OpenClaw aparece quando sugestões pendentes.
* Posicionado no canto inferior direito, fora das rotas.
* Abre o modal SkillSuggestion para confirmação.
*
* Fase 4: OpenClaw Sprint 2
*/
import { useState } from "react";
import { Sparkles, X, ChevronUp, ChevronDown } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { useAgentEmergence } from "@/hooks/useAgentEmergence";
import { SkillSuggestion } from "./SkillSuggestion";
import { useAuth } from "@/hooks/use-auth";
export function OpenClawWidget() {
const { user } = useAuth();
const {
suggestions,
activeSuggestion,
pendingCount,
confirmSkill,
rejectSkill,
openSuggestion,
closeSuggestion,
} = useAgentEmergence();
const [collapsed, setCollapsed] = useState(false);
const [dismissed, setDismissed] = useState(false);
// Não renderiza se não há usuário, não há sugestões, ou foi dispensado
if (!user || pendingCount === 0 || dismissed) return null;
return (
<>
{/* Widget flutuante */}
<div
className="fixed bottom-6 right-6 z-50 flex flex-col items-end gap-2"
role="complementary"
aria-label="OpenClaw - Sugestões de Skills"
>
{/* Painel expandido */}
{!collapsed && (
<div className="w-72 rounded-xl border bg-background shadow-lg overflow-hidden">
<div className="flex items-center justify-between px-3 py-2.5 border-b bg-violet-50 dark:bg-violet-900/20">
<div className="flex items-center gap-2">
<Sparkles className="h-4 w-4 text-violet-600 dark:text-violet-400" />
<span className="text-sm font-semibold text-violet-700 dark:text-violet-300">
OpenClaw
</span>
<Badge className="bg-violet-600 text-white text-xs px-1.5 py-0">
{pendingCount}
</Badge>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => setCollapsed(true)}
className="p-0.5 rounded hover:bg-violet-100 dark:hover:bg-violet-900/40 text-muted-foreground"
aria-label="Minimizar"
>
<ChevronDown className="h-3.5 w-3.5" />
</button>
<button
onClick={() => setDismissed(true)}
className="p-0.5 rounded hover:bg-violet-100 dark:hover:bg-violet-900/40 text-muted-foreground"
aria-label="Fechar"
>
<X className="h-3.5 w-3.5" />
</button>
</div>
</div>
<div className="p-3 space-y-2 max-h-64 overflow-y-auto">
<p className="text-xs text-muted-foreground">
Detectei padrões de uso que podem virar skills automáticas:
</p>
{suggestions.map((s) => (
<button
key={s.id}
onClick={() => openSuggestion(s)}
className="w-full text-left rounded-lg border p-2.5 hover:bg-muted/50 transition-colors space-y-1"
>
<div className="flex items-start justify-between gap-2">
<span className="text-sm font-medium leading-tight line-clamp-1">
{s.suggested_skill_name}
</span>
<Badge variant="outline" className="text-xs shrink-0">
{Math.round(s.confidence * 100)}%
</Badge>
</div>
<p className="text-xs text-muted-foreground line-clamp-2">
{s.suggested_description}
</p>
</button>
))}
</div>
</div>
)}
{/* Botão minimizado */}
{collapsed && (
<button
onClick={() => setCollapsed(false)}
className="flex items-center gap-2 rounded-full bg-violet-600 hover:bg-violet-700 text-white px-4 py-2.5 shadow-lg transition-colors"
aria-label="Expandir sugestões de skills"
>
<Sparkles className="h-4 w-4" />
<span className="text-sm font-medium">Skills sugeridas</span>
<Badge className="bg-white text-violet-700 text-xs px-1.5 py-0 ml-0.5">
{pendingCount}
</Badge>
<ChevronUp className="h-3.5 w-3.5 ml-0.5" />
</button>
)}
</div>
{/* Modal de detalhes */}
{activeSuggestion && (
<SkillSuggestion
suggestion={activeSuggestion}
onConfirm={confirmSkill}
onReject={rejectSkill}
onClose={closeSuggestion}
/>
)}
</>
);
}

View File

@ -1,101 +0,0 @@
/**
* SkillSuggestion.tsx
*
* Modal com preview da skill sugerida pelo OpenClaw.
* Apresenta padrão detectado, nome, descrição e preview de automação.
* Usuário pode confirmar ou rejeitar.
*
* Fase 4: OpenClaw Sprint 2
*/
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Sparkles, X, Check, Zap, BarChart2 } from "lucide-react";
import type { SkillSuggestionData } from "@/hooks/useAgentEmergence";
interface SkillSuggestionProps {
suggestion: SkillSuggestionData;
onConfirm: (id: string) => void;
onReject: (id: string) => void;
onClose: () => void;
}
export function SkillSuggestion({ suggestion, onConfirm, onReject, onClose }: SkillSuggestionProps) {
const confidencePct = Math.round(suggestion.confidence * 100);
return (
<Dialog open onOpenChange={onClose}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<div className="flex items-center gap-2">
<div className="p-1.5 rounded-md bg-violet-100 dark:bg-violet-900/30">
<Sparkles className="h-4 w-4 text-violet-600 dark:text-violet-400" />
</div>
<DialogTitle className="text-base">Nova skill detectada</DialogTitle>
</div>
</DialogHeader>
{/* Padrão detectado */}
<div className="rounded-lg border bg-muted/40 p-3 text-sm space-y-1">
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Padrão detectado</p>
<p className="font-medium">{suggestion.pattern.description || suggestion.pattern.action_type}</p>
<div className="flex items-center gap-3 text-muted-foreground text-xs mt-1">
<span className="flex items-center gap-1">
<BarChart2 className="h-3 w-3" />
{suggestion.pattern.frequency}x detectado
</span>
<span className="flex items-center gap-1">
<Zap className="h-3 w-3" />
Confiança: {confidencePct}%
</span>
</div>
</div>
{/* Skill sugerida */}
<div className="space-y-3">
<div>
<p className="text-xs font-medium text-muted-foreground mb-1">Nome da skill</p>
<div className="flex items-center gap-2">
<Badge variant="secondary" className="font-mono text-xs">
{suggestion.suggested_skill_name}
</Badge>
</div>
</div>
<div>
<p className="text-xs font-medium text-muted-foreground mb-1">Descrição</p>
<p className="text-sm">{suggestion.suggested_description}</p>
</div>
<div>
<p className="text-xs font-medium text-muted-foreground mb-1">O que será automatizado</p>
<div className="rounded-md border bg-muted/20 p-2.5 text-sm text-muted-foreground">
{suggestion.estimated_automation}
</div>
</div>
</div>
<DialogFooter className="gap-2 sm:gap-0">
<Button
variant="outline"
size="sm"
onClick={() => onReject(suggestion.id)}
className="gap-1.5"
>
<X className="h-3.5 w-3.5" />
Rejeitar
</Button>
<Button
size="sm"
onClick={() => onConfirm(suggestion.id)}
className="gap-1.5 bg-violet-600 hover:bg-violet-700"
>
<Check className="h-3.5 w-3.5" />
Criar skill
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@ -1,59 +0,0 @@
import { useQuery } from "@tanstack/react-query";
export type TenantFeatures = {
ide: boolean;
ideMode: 'none' | 'no-code' | 'low-code' | 'pro-code';
whatsapp: boolean;
whatsappSessions: number;
crm: boolean;
erp: boolean;
bi: boolean;
manus: boolean;
manusTools: string[];
centralApis: boolean;
centralApisManage: boolean;
comunidades: boolean;
maxChannels: number;
biblioteca: boolean;
bibliotecaPublish: boolean;
suporteN3: boolean;
retail: boolean;
plus: boolean;
fisco: boolean;
cockpit: boolean;
compass: boolean;
production: boolean;
support: boolean;
xosCrm: boolean;
};
const defaultFeatures: TenantFeatures = {
ide: true, ideMode: 'pro-code', whatsapp: false, whatsappSessions: 0,
crm: true, erp: true, bi: false, manus: true, manusTools: [],
centralApis: false, centralApisManage: false, comunidades: false,
maxChannels: 5, biblioteca: false, bibliotecaPublish: false,
suporteN3: false, retail: false, plus: false, fisco: false,
cockpit: false, compass: true, production: false, support: false, xosCrm: false
};
export function useTenantFeatures() {
const { data, isLoading } = useQuery({
queryKey: ["tenant-modules"],
queryFn: async () => {
const res = await fetch("/api/soe/tenant/modules", { credentials: "include" });
if (!res.ok) throw new Error("Failed to fetch tenant modules");
return res.json();
},
staleTime: 60000,
retry: 1
});
const features: TenantFeatures = { ...defaultFeatures, ...(data?.features || {}) };
return {
features,
isLoading,
plan: data?.plan as string | undefined,
isEnabled: (key: keyof TenantFeatures) => !!features[key],
};
}

View File

@ -1,143 +0,0 @@
/**
* useAgentEmergence.ts
*
* Hook para gerenciar sugestões de skills do OpenClaw.
* Escuta eventos Socket.IO e coordena confirmações/rejeições.
*
* Fase 4: OpenClaw Sprint 2
*/
import { useState, useEffect, useCallback } from "react";
import { useAuth } from "./use-auth";
export interface SkillSuggestionData {
id: string;
pattern: {
id: string;
action_type: string;
frequency: number;
confidence: number;
description: string;
};
suggested_skill_name: string;
suggested_description: string;
estimated_automation: string;
confidence: number;
created_at: string;
status: "pending" | "accepted" | "rejected";
}
interface UseAgentEmergenceReturn {
suggestions: SkillSuggestionData[];
activeSuggestion: SkillSuggestionData | null;
isOpen: boolean;
pendingCount: number;
confirmSkill: (suggestionId: string) => Promise<void>;
rejectSkill: (suggestionId: string) => Promise<void>;
openSuggestion: (suggestion: SkillSuggestionData) => void;
closeSuggestion: () => void;
dismissWidget: () => void;
}
export function useAgentEmergence(): UseAgentEmergenceReturn {
const { user } = useAuth();
const [suggestions, setSuggestions] = useState<SkillSuggestionData[]>([]);
const [activeSuggestion, setActiveSuggestion] = useState<SkillSuggestionData | null>(null);
const [isOpen, setIsOpen] = useState(false);
// Carrega sugestões pendentes do servidor
const fetchPendingSuggestions = useCallback(async () => {
if (!user) return;
try {
const res = await fetch("/api/openclaw/suggestions");
if (res.ok) {
const data = await res.json();
setSuggestions(data.suggestions || []);
}
} catch {
// silencioso — não bloqueia o usuário
}
}, [user]);
// Escuta eventos Socket.IO via window (padrão da suite)
useEffect(() => {
if (!user) return;
fetchPendingSuggestions();
const handleOpenClawEvent = (event: CustomEvent) => {
const suggestion: SkillSuggestionData = event.detail;
setSuggestions((prev) => {
const exists = prev.find((s) => s.id === suggestion.id);
if (exists) return prev;
return [suggestion, ...prev];
});
};
window.addEventListener("openclaw:suggestion" as any, handleOpenClawEvent);
return () => {
window.removeEventListener("openclaw:suggestion" as any, handleOpenClawEvent);
};
}, [user, fetchPendingSuggestions]);
const confirmSkill = useCallback(async (suggestionId: string) => {
try {
const res = await fetch("/api/openclaw/confirm-skill", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ suggestion_id: suggestionId, action: "confirm" }),
});
if (res.ok) {
setSuggestions((prev) => prev.filter((s) => s.id !== suggestionId));
setActiveSuggestion(null);
setIsOpen(false);
}
} catch {
// erro tratado na UI via toast externo
}
}, []);
const rejectSkill = useCallback(async (suggestionId: string) => {
try {
const res = await fetch("/api/openclaw/confirm-skill", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ suggestion_id: suggestionId, action: "reject" }),
});
if (res.ok) {
setSuggestions((prev) => prev.filter((s) => s.id !== suggestionId));
setActiveSuggestion(null);
setIsOpen(false);
}
} catch {
// silencioso
}
}, []);
const openSuggestion = useCallback((suggestion: SkillSuggestionData) => {
setActiveSuggestion(suggestion);
setIsOpen(true);
}, []);
const closeSuggestion = useCallback(() => {
setActiveSuggestion(null);
setIsOpen(false);
}, []);
const dismissWidget = useCallback(() => {
setIsOpen(false);
setActiveSuggestion(null);
}, []);
return {
suggestions,
activeSuggestion,
isOpen,
pendingCount: suggestions.length,
confirmSkill,
rejectSkill,
openSuggestion,
closeSuggestion,
dismissWidget,
};
}

View File

@ -1,142 +0,0 @@
// Utilitários de exportação com lazy loading dinâmico
// Reduz o bundle inicial do Agent.tsx
export async function exportToPDF(content: string, prompt?: string, chartContainer?: HTMLElement | null) {
const [{ jsPDF }, html2canvas] = await Promise.all([
import("jspdf"),
import("html2canvas").then(m => m.default)
]);
const doc = new jsPDF();
const pageWidth = doc.internal.pageSize.getWidth();
const margin = 20;
const maxWidth = pageWidth - margin * 2;
doc.setFontSize(16);
doc.setTextColor(31, 51, 77);
doc.text("Arcádia Agent - Resultado", margin, 20);
let currentY = 30;
if (prompt) {
doc.setFontSize(10);
doc.setTextColor(90, 108, 125);
doc.text("Tarefa:", margin, currentY);
currentY += 7;
const promptLines = doc.splitTextToSize(prompt, maxWidth);
doc.text(promptLines, margin, currentY);
currentY += promptLines.length * 5 + 10;
}
doc.setFontSize(12);
doc.setTextColor(31, 51, 77);
doc.text("Resposta:", margin, currentY);
currentY += 8;
const textContent = content.replace(/__CHART_DATA__[\s\S]*?__END_CHART_DATA__/g, "").trim();
doc.setFontSize(11);
const lines = doc.splitTextToSize(textContent, maxWidth);
doc.text(lines, margin, currentY);
currentY += lines.length * 5 + 10;
// Verifica se há dados de gráfico e captura
const chartDataMatch = content.match(/__CHART_DATA__([\s\S]*?)__END_CHART_DATA__/);
if (chartDataMatch && chartContainer) {
try {
const canvas = await html2canvas(chartContainer, {
backgroundColor: "#ffffff",
scale: 2,
});
const imgData = canvas.toDataURL("image/png");
const imgWidth = maxWidth;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
if (currentY + imgHeight > doc.internal.pageSize.getHeight() - margin) {
doc.addPage();
currentY = margin;
}
doc.addImage(imgData, "PNG", margin, currentY, imgWidth, imgHeight);
} catch (e) {
console.error("Error capturing chart:", e);
}
}
doc.save("arcadia-resultado.pdf");
}
export async function exportToWord(content: string, prompt?: string) {
const { Document, Packer, Paragraph, TextRun } = await import("docx");
const { saveAs } = await import("file-saver");
const paragraphs = [];
paragraphs.push(
new Paragraph({
children: [new TextRun({ text: "Arcádia Agent - Resultado", bold: true, size: 32 })],
})
);
if (prompt) {
paragraphs.push(
new Paragraph({ children: [] }),
new Paragraph({
children: [new TextRun({ text: "Tarefa: ", bold: true }), new TextRun({ text: prompt })],
})
);
}
paragraphs.push(
new Paragraph({ children: [] }),
new Paragraph({
children: [new TextRun({ text: "Resposta:", bold: true })],
})
);
content.split("\n").forEach((line) => {
paragraphs.push(new Paragraph({ children: [new TextRun({ text: line })] }));
});
const doc = new Document({
sections: [{ properties: {}, children: paragraphs }],
});
const blob = await Packer.toBlob(doc);
saveAs(blob, "arcadia-resultado.docx");
}
export async function exportToText(content: string, prompt?: string) {
const { saveAs } = await import("file-saver");
const textContent = prompt ? `Tarefa: ${prompt}\n\nResposta:\n${content}` : content;
const blob = new Blob([textContent], { type: "text/plain;charset=utf-8" });
saveAs(blob, "arcadia-resultado.txt");
}
export async function exportToCSV(content: string) {
const { saveAs } = await import("file-saver");
const lines = content.split("\n");
let csvContent = "";
for (const line of lines) {
if (line.includes("|")) {
const cells = line
.split("|")
.map((cell) => cell.trim())
.filter((cell) => cell && !cell.match(/^[-:]+$/));
if (cells.length > 0) {
csvContent += cells.map((cell) => `"${cell.replace(/"/g, """)}"`).join(",") + "\n";
}
} else if (line.includes("\t")) {
csvContent +=
line
.split("\t")
.map((cell) => `"${cell.trim().replace(/"/g, """)}"`)
.join(",") + "\n";
} else if (line.trim()) {
csvContent += `"${line.replace(/"/g, """)}"\n`;
}
}
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8" });
saveAs(blob, "arcadia-resultado.csv");
}

View File

@ -54,10 +54,13 @@ import {
} from "lucide-react";
import { CardDescription } from "@/components/ui/card";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { jsPDF } from "jspdf";
import { Document, Packer, Paragraph, TextRun } from "docx";
import { saveAs } from "file-saver";
import html2canvas from "html2canvas";
import { BarChart, Bar, LineChart, Line, PieChart, Pie, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, Cell } from "recharts";
import { ResultViewer } from "@/components/ResultViewer";
import DevHistory from "@/components/DevHistory";
import { ChartRenderer } from "@/components/ChartRenderer";
import { exportToPDF, exportToWord, exportToText, exportToCSV } from "@/lib/export-utils";
interface ChartData {
type: 'bar' | 'line' | 'pie' | 'area';

View File

@ -1,267 +0,0 @@
/**
* AutomationCenter Phase 5: Automation Fabric
*
* Visão unificada de todas as automações: Central + XOS.
* Rota: /automations-center
*/
import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { BrowserFrame } from "@/components/Browser/BrowserFrame";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Zap, Search, Play, Loader2, CheckCircle, XCircle,
Clock, Webhook, Bot, Calendar, Activity, Filter
} from "lucide-react";
import { useToast } from "@/hooks/use-toast";
interface UnifiedAutomation {
id: string;
sourceId: number;
source: "central" | "xos";
name: string;
description: string | null;
triggerType: string;
isActive: boolean;
executionCount: number;
lastExecutedAt: string | null;
createdAt: string;
}
const TRIGGER_ICONS: Record<string, React.ReactNode> = {
schedule: <Clock className="w-3.5 h-3.5" />,
webhook: <Webhook className="w-3.5 h-3.5" />,
agent: <Bot className="w-3.5 h-3.5" />,
event: <Activity className="w-3.5 h-3.5" />,
contact_created: <Zap className="w-3.5 h-3.5" />,
deal_stage_changed: <Zap className="w-3.5 h-3.5" />,
form_submitted: <Zap className="w-3.5 h-3.5" />,
};
const TRIGGER_LABEL: Record<string, string> = {
schedule: "Agendada",
webhook: "Webhook",
manual: "Manual",
event: "Evento",
agent: "Agente",
contact_created: "Novo Contato",
deal_stage_changed: "Mudança de Etapa",
form_submitted: "Formulário",
};
export default function AutomationCenter() {
const [search, setSearch] = useState("");
const [filterSource, setFilterSource] = useState<"all" | "central" | "xos">("all");
const [filterStatus, setFilterStatus] = useState<"all" | "active" | "inactive">("all");
const { toast } = useToast();
const qc = useQueryClient();
const { data, isLoading } = useQuery<{ automations: UnifiedAutomation[]; total: number }>({
queryKey: ["/api/automation-fabric/list"],
refetchInterval: 30_000,
});
const runMutation = useMutation({
mutationFn: async (id: string) => {
const res = await fetch(`/api/automation-fabric/${encodeURIComponent(id)}/run`, { method: "POST" });
if (!res.ok) throw new Error();
return res.json();
},
onSuccess: (_, id) => {
toast({ title: "Automação executada" });
qc.invalidateQueries({ queryKey: ["/api/automation-fabric/list"] });
},
onError: () => toast({ title: "Erro ao executar", variant: "destructive" }),
});
const toggleMutation = useMutation({
mutationFn: async ({ id, isActive }: { id: string; isActive: boolean }) => {
const res = await fetch(`/api/automation-fabric/${encodeURIComponent(id)}/toggle`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ isActive }),
});
if (!res.ok) throw new Error();
return res.json();
},
onSuccess: () => qc.invalidateQueries({ queryKey: ["/api/automation-fabric/list"] }),
onError: () => toast({ title: "Erro ao atualizar status", variant: "destructive" }),
});
const automations = data?.automations ?? [];
const filtered = automations.filter((a) => {
if (filterSource !== "all" && a.source !== filterSource) return false;
if (filterStatus === "active" && !a.isActive) return false;
if (filterStatus === "inactive" && a.isActive) return false;
if (search) {
const q = search.toLowerCase();
return (
a.name.toLowerCase().includes(q) ||
(a.description ?? "").toLowerCase().includes(q) ||
a.triggerType.toLowerCase().includes(q)
);
}
return true;
});
const activeCount = automations.filter((a) => a.isActive).length;
const centralCount = automations.filter((a) => a.source === "central").length;
const xosCount = automations.filter((a) => a.source === "xos").length;
return (
<BrowserFrame>
<div className="flex flex-col h-full bg-[#0a0f1a] text-white">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-white/10">
<div>
<h1 className="text-lg font-semibold flex items-center gap-2">
<Zap className="w-5 h-5 text-[#c89b3c]" />
Automation Center
</h1>
<p className="text-xs text-muted-foreground mt-0.5">
{automations.length} automações · {activeCount} ativas · Central ({centralCount}) + XOS ({xosCount})
</p>
</div>
</div>
{/* Filters */}
<div className="flex gap-2 p-3 border-b border-white/10 flex-wrap">
<div className="relative flex-1 min-w-48">
<Search className="absolute left-2.5 top-2.5 w-3.5 h-3.5 text-muted-foreground" />
<Input
placeholder="Buscar automações..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-8 h-8 text-sm bg-white/5 border-white/10"
/>
</div>
{/* Source filter */}
<div className="flex rounded-md border border-white/10 overflow-hidden">
{(["all", "central", "xos"] as const).map((s) => (
<button
key={s}
onClick={() => setFilterSource(s)}
className={`px-2.5 py-1.5 text-xs transition-colors ${
filterSource === s ? "bg-[#c89b3c] text-black font-medium" : "text-white/50 hover:text-white hover:bg-white/5"
}`}
>
{s === "all" ? "Todas" : s === "central" ? "Central" : "XOS"}
</button>
))}
</div>
{/* Status filter */}
<div className="flex rounded-md border border-white/10 overflow-hidden">
{(["all", "active", "inactive"] as const).map((s) => (
<button
key={s}
onClick={() => setFilterStatus(s)}
className={`px-2.5 py-1.5 text-xs transition-colors ${
filterStatus === s ? "bg-[#c89b3c] text-black font-medium" : "text-white/50 hover:text-white hover:bg-white/5"
}`}
>
{s === "all" ? "Todas" : s === "active" ? "Ativas" : "Inativas"}
</button>
))}
</div>
</div>
{/* List */}
<ScrollArea className="flex-1">
{isLoading && (
<div className="flex justify-center py-16">
<Loader2 className="w-6 h-6 animate-spin text-white/30" />
</div>
)}
{!isLoading && filtered.length === 0 && (
<div className="flex flex-col items-center justify-center py-20 text-white/30 gap-3">
<Filter className="w-10 h-10 opacity-30" />
<p className="text-sm">Nenhuma automação encontrada</p>
{search && <p className="text-xs">Tente outro filtro ou busca</p>}
</div>
)}
<div className="divide-y divide-white/5">
{filtered.map((a) => {
const isRunning = runMutation.isPending && runMutation.variables === a.id;
const isToggling = toggleMutation.isPending && toggleMutation.variables?.id === a.id;
const triggerIcon = TRIGGER_ICONS[a.triggerType] ?? <Zap className="w-3.5 h-3.5" />;
const triggerLabel = TRIGGER_LABEL[a.triggerType] ?? a.triggerType;
const lastRun = a.lastExecutedAt
? new Date(a.lastExecutedAt).toLocaleDateString("pt-BR")
: null;
return (
<div key={a.id} className="px-4 py-3 flex items-center gap-3 hover:bg-white/3 transition-colors">
{/* Icon */}
<div className={`w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 ${a.isActive ? "bg-[#c89b3c]/15 text-[#c89b3c]" : "bg-white/5 text-white/30"}`}>
{triggerIcon}
</div>
{/* Info */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="text-sm font-medium truncate">{a.name}</span>
<Badge
className={`text-[9px] px-1.5 py-0 shrink-0 ${
a.source === "central"
? "bg-blue-500/15 text-blue-400 border-blue-500/20"
: "bg-purple-500/15 text-purple-400 border-purple-500/20"
}`}
>
{a.source === "central" ? "Central" : "XOS"}
</Badge>
</div>
<div className="flex items-center gap-3 mt-0.5">
<span className="text-[11px] text-white/40 flex items-center gap-1">
{triggerIcon} {triggerLabel}
</span>
{a.executionCount > 0 && (
<span className="text-[11px] text-white/30">{a.executionCount}x</span>
)}
{lastRun && (
<span className="text-[11px] text-white/30 flex items-center gap-1">
<Calendar className="w-3 h-3" /> {lastRun}
</span>
)}
</div>
</div>
{/* Actions */}
<div className="flex items-center gap-2 shrink-0">
<Button
size="sm"
variant="ghost"
className="h-7 w-7 p-0 text-white/40 hover:text-white hover:bg-white/10"
onClick={() => runMutation.mutate(a.id)}
disabled={isRunning || !a.isActive}
title="Executar agora"
>
{isRunning ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Play className="w-3.5 h-3.5" />}
</Button>
<Switch
checked={a.isActive}
disabled={isToggling}
onCheckedChange={(v) => toggleMutation.mutate({ id: a.id, isActive: v })}
className="scale-75"
/>
</div>
</div>
);
})}
</div>
</ScrollArea>
</div>
</BrowserFrame>
);
}

View File

@ -50,7 +50,6 @@ import {
} from "lucide-react";
import { Textarea } from "@/components/ui/textarea";
import { SupersetDashboard } from "@/components/SupersetDashboard";
import { MiroFlowControl } from "@/components/MiroFlowControl";
import {
Dialog,
DialogContent,
@ -2958,9 +2957,6 @@ export default function BiWorkspace() {
<TabsTrigger value="staging" className="data-[state=active]:bg-[#c89b3c] data-[state=active]:text-[#1f334d] text-white/70">
<Layers className="w-4 h-4 mr-2" /> Staging
</TabsTrigger>
<TabsTrigger value="cientifico" className="data-[state=active]:bg-[#c89b3c] data-[state=active]:text-[#1f334d] text-white/70">
Científico
</TabsTrigger>
<TabsTrigger value="advanced" className="data-[state=active]:bg-[#c89b3c] data-[state=active]:text-[#1f334d] text-white/70">
<Settings className="w-4 h-4 mr-2" /> Insights
</TabsTrigger>
@ -2987,12 +2983,8 @@ export default function BiWorkspace() {
<TabsContent value="staging" className="mt-0">
<StagingTab />
</TabsContent>
<TabsContent value="cientifico" className="mt-0">
<MiroFlowControl />
</TabsContent>
<TabsContent value="advanced" className="mt-0">
<SupersetAdvancedTab />
</TabsContent>
</Tabs>
</div>

View File

@ -1,29 +0,0 @@
# Design Studio — Contexto para próximo agente
## O que é
Design Studio é a entrada na sidebar de `/development` (DevelopmentModule.tsx) que abre o `DevCenter.tsx` como painel interno.
## Estado atual (27/03/2026)
O ícone e a navegação estão implementados. O conteúdo renderizado é o `DevCenter.tsx` existente, que já possui 4 abas funcionais:
| Aba | O que faz |
|-----|-----------|
| **Design** | Editor de definições de agente (modos: Markdown, TypeScript, Visual-JSON) |
| **Assemble** | Agentes draft → Blackboard gera o código → polling de status |
| **Deploy** | Agentes prontos → deploy, re-montar |
| **Galeria** | Grid de agentes implantados, busca, executar, fork |
## O que FALTA (João tem ideias — perguntar antes de implementar)
- **Visual canvas drag-and-drop** para o modo "Visual" do editor (hoje é um Textarea com JSON manual)
- **Suporte a UML** no editor de definições
- O usuário João mencionou ter ideias específicas para o Design Studio — **confirmar com ele antes de implementar qualquer coisa nova aqui**
## Arquivos relacionados
- `client/src/pages/DevCenter.tsx` — componente principal renderizado pelo Design Studio
- `client/src/pages/DevelopmentModule.tsx` — sidebar onde o item foi adicionado (id: "designstudio", ícone: PenTool)
- `server/routes/agent-defs.ts` (ou similar) — API de definições de agentes usada pelo DevCenter
- `migrations/phase6_agent_defs.sql` — schema das tabelas de agent definitions

File diff suppressed because it is too large Load Diff

View File

@ -11,12 +11,12 @@ import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useToast } from "@/hooks/use-toast";
import {
import {
Code2, Database, Layout, GitBranch, BarChart3, FileText,
Plus, Settings, Play, Zap, Layers, Puzzle, Palette,
Terminal, Box, Workflow, PanelLeft, Table2, FormInput,
ListChecks, PieChart, LineChart, Gauge, Bot, Code, Trash2, Save,
Sparkles, Rocket, PenTool, Brain
Sparkles, Rocket
} from "lucide-react";
import { useLocation } from "wouter";
@ -25,12 +25,8 @@ import PageBuilder from "./PageBuilder";
import WorkflowBuilder from "./WorkflowBuilder";
import IDE from "./IDE";
import DevAgent from "@/components/lowcode/DevAgent";
import OpenClawPanel from "@/components/OpenClawPanel";
import AutomationCenter from "./AutomationCenter";
import DevCenter from "./DevCenter";
import { MiroFlowControl } from "@/components/MiroFlowControl";
type ActiveTool = "home" | "doctypes" | "pages" | "workflows" | "dashboards" | "reports" | "scripts" | "ide" | "agent" | "openclaw" | "automationcenter" | "designstudio" | "miroflow";
type ActiveTool = "home" | "doctypes" | "pages" | "workflows" | "dashboards" | "reports" | "scripts" | "ide" | "agent";
interface Dashboard {
id: number;
@ -254,51 +250,15 @@ export default function DevelopmentModule() {
count: 0,
category: "dev"
},
{
id: "agent" as ActiveTool,
name: "Manus AI",
{
id: "agent" as ActiveTool,
name: "Manus AI",
description: "Agente autônomo de IA",
icon: Bot,
icon: Bot,
color: "bg-violet-600",
count: 0,
category: "dev"
},
{
id: "openclaw" as ActiveTool,
name: "Skills Emergentes",
description: "Padrões detectados automaticamente",
icon: Sparkles,
color: "bg-yellow-500",
count: 0,
category: "dev"
},
{
id: "automationcenter" as ActiveTool,
name: "Automation Center",
description: "Automações unificadas Central + XOS",
icon: Workflow,
color: "bg-blue-600",
count: 0,
category: "dev"
},
{
id: "designstudio" as ActiveTool,
name: "Design Studio",
description: "Projete e monte agentes autônomos",
icon: PenTool,
color: "bg-purple-600",
count: 0,
category: "dev"
},
{
id: "miroflow" as ActiveTool,
name: "MiroFlow",
description: "Análises científicas e estatísticas",
icon: Brain,
color: "bg-cyan-500",
count: 0,
category: "scientific"
},
];
const [, navigate] = useLocation();
@ -711,8 +671,8 @@ export default function DevelopmentModule() {
);
return (
<div className="h-screen flex bg-gray-50">
<div className="w-16 bg-gray-900 flex flex-col items-center py-4 gap-2 overflow-y-auto h-full">
<div className="h-full flex bg-gray-50">
<div className="w-16 bg-gray-900 flex flex-col items-center py-4 gap-2">
<button
onClick={() => setActiveTool("home")}
className={`p-3 rounded-lg transition-colors ${activeTool === "home" ? "bg-blue-600" : "hover:bg-gray-700"}`}
@ -735,8 +695,8 @@ export default function DevelopmentModule() {
))}
</div>
<div className="flex-1 h-full overflow-hidden">
{activeTool === "home" && <div className="h-full overflow-y-auto">{renderHome()}</div>}
<div className="flex-1 overflow-hidden">
{activeTool === "home" && renderHome()}
{activeTool === "doctypes" && <DocTypeBuilder />}
{activeTool === "pages" && <PageBuilder />}
{activeTool === "workflows" && <WorkflowBuilder />}
@ -745,10 +705,6 @@ export default function DevelopmentModule() {
{activeTool === "scripts" && renderScriptEditor()}
{activeTool === "ide" && <IDE />}
{activeTool === "agent" && <DevAgent />}
{activeTool === "openclaw" && <OpenClawPanel />}
{activeTool === "automationcenter" && <AutomationCenter />}
{activeTool === "designstudio" && <DevCenter />}
{activeTool === "miroflow" && <MiroFlowControl />}
</div>
<Dialog open={showNewDashboardDialog} onOpenChange={setShowNewDashboardDialog}>

View File

@ -1,78 +0,0 @@
import React, { useState } from "react";
import { TradeInFiltersWidget } from "../components/retail-reports/TradeInFiltersWidget";
import { TradeInDetailsModal } from "../components/retail-reports/TradeInDetailsModal";
import { TradeInPrintButton } from "../components/retail-reports/TradeInPrintButton";
import { useQuery } from "@tanstack/react-query";
const fetchTradeIns = async (filters) => {
const query = new URLSearchParams(filters).toString();
const response = await fetch(`/api/modules/retail-reports/trade-ins?${query}`);
if (!response.ok) throw new Error("Failed to fetch trade-ins.");
return response.json();
};
export const RetailReportsPage: React.FC = () => {
const [filters, setFilters] = useState({});
const [selectedTradeIn, setSelectedTradeIn] = useState(null);
const { data: tradeIns, isLoading, error } = useQuery(["tradeIns", filters], () => fetchTradeIns(filters));
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">Retail Trade-In Reports</h1>
<TradeInFiltersWidget onApplyFilters={setFilters} data-testid="filters-widget" />
{isLoading && <p>Loading trade-ins...</p>}
{error && <p className="text-red-500">Failed to load trade-ins.</p>}
<table className="table-auto w-full border-collapse border border-gray-300 mt-4">
<thead>
<tr>
<th className="border border-gray-300 px-4 py-2">ID</th>
<th className="border border-gray-300 px-4 py-2">Seller</th>
<th className="border border-gray-300 px-4 py-2">Company</th>
<th className="border border-gray-300 px-4 py-2">Client</th>
<th className="border border-gray-300 px-4 py-2">Date</th>
<th className="border border-gray-300 px-4 py-2">Value</th>
<th className="border border-gray-300 px-4 py-2">Status</th>
<th className="border border-gray-300 px-4 py-2">Actions</th>
</tr>
</thead>
<tbody>
{tradeIns?.map((tradeIn) => (
<tr key={tradeIn.id}>
<td className="border border-gray-300 px-4 py-2">{tradeIn.id}</td>
<td className="border border-gray-300 px-4 py-2">{tradeIn.sellerId}</td>
<td className="border border-gray-300 px-4 py-2">{tradeIn.companyId}</td>
<td className="border border-gray-300 px-4 py-2">{tradeIn.clientId}</td>
<td className="border border-gray-300 px-4 py-2">{new Date(tradeIn.tradeInDate).toLocaleDateString()}</td>
<td className="border border-gray-300 px-4 py-2">{tradeIn.tradeInValue}</td>
<td className="border border-gray-300 px-4 py-2">{tradeIn.status}</td>
<td className="border border-gray-300 px-4 py-2">
<button
className="text-blue-500 hover:underline"
onClick={() => setSelectedTradeIn(tradeIn)}
data-testid="view-details-button"
>
View Details
</button>
</td>
</tr>
))}
</tbody>
</table>
<TradeInPrintButton tradeIns={tradeIns} data-testid="print-button" />
{selectedTradeIn && (
<TradeInDetailsModal
tradeIn={selectedTradeIn}
onClose={() => setSelectedTradeIn(null)}
data-testid="details-modal"
/>
)}
</div>
);
};
export default RetailReportsPage;

View File

@ -1,5 +1,4 @@
import { BrowserFrame } from "@/components/Browser/BrowserFrame";
import { MiroFlowControl } from "@/components/MiroFlowControl";
import { useState } from "react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
@ -201,10 +200,6 @@ export default function Scientist() {
<Lightbulb className="w-4 h-4 mr-2" />
Sugestões
</TabsTrigger>
<TabsTrigger value="scientific" className="data-[state=active]:bg-cyan-500">
<Brain className="w-4 h-4 mr-2" />
Científico
</TabsTrigger>
</TabsList>
<TabsContent value="knowledge" className="space-y-4">
@ -832,10 +827,6 @@ export default function Scientist() {
</Card>
)}
</TabsContent>
<TabsContent value="scientific" className="space-y-4">
<MiroFlowControl />
</TabsContent>
</Tabs>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useQuery } from "@tanstack/react-query";
import { Link } from "wouter";
import { BrowserFrame } from "@/components/Browser/BrowserFrame";
import {
@ -14,9 +14,6 @@ import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
interface XosStats {
total_contacts: number;
@ -70,48 +67,6 @@ interface Activity {
export default function XosCentral() {
const [activeTab, setActiveTab] = useState("dashboard");
const [isNewContactOpen, setIsNewContactOpen] = useState(false);
const [isNewActivityOpen, setIsNewActivityOpen] = useState(false);
const [newContact, setNewContact] = useState({ name: "", email: "", phone: "", company: "", position: "" });
const [newActivity, setNewActivity] = useState({ type: "task", title: "", due_at: "", priority: "medium" });
const queryClient = useQueryClient();
const createContactMutation = useMutation({
mutationFn: async (data: typeof newContact) => {
const res = await fetch("/api/xos/contacts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error("Erro ao criar contato");
return res.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["/api/xos/contacts"] });
queryClient.invalidateQueries({ queryKey: ["/api/xos/stats"] });
setIsNewContactOpen(false);
setNewContact({ name: "", email: "", phone: "", company: "", position: "" });
},
});
const createActivityMutation = useMutation({
mutationFn: async (data: typeof newActivity) => {
const res = await fetch("/api/xos/activities", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error("Erro ao criar atividade");
return res.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["/api/xos/activities"] });
queryClient.invalidateQueries({ queryKey: ["/api/xos/stats"] });
setIsNewActivityOpen(false);
setNewActivity({ type: "task", title: "", due_at: "", priority: "medium" });
},
});
const { data: stats } = useQuery<XosStats>({
queryKey: ["/api/xos/stats"],
@ -226,9 +181,9 @@ export default function XosCentral() {
</span>
)}
</Button>
<Button data-testid="button-new-contact" onClick={() => setIsNewContactOpen(true)}>
<Button data-testid="button-new-contact">
<PlusCircle className="h-4 w-4 mr-2" />
Novo Contato
Novo
</Button>
</div>
</div>
@ -455,7 +410,7 @@ export default function XosCentral() {
<Filter className="h-4 w-4 mr-2" />
Filtros
</Button>
<Button size="sm" onClick={() => setIsNewContactOpen(true)}>
<Button size="sm">
<PlusCircle className="h-4 w-4 mr-2" />
Novo Contato
</Button>
@ -527,7 +482,7 @@ export default function XosCentral() {
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>Atividades</CardTitle>
<Button size="sm" onClick={() => setIsNewActivityOpen(true)}>
<Button size="sm">
<PlusCircle className="h-4 w-4 mr-2" />
Nova Atividade
</Button>
@ -569,129 +524,6 @@ export default function XosCentral() {
</Tabs>
</main>
</div>
{/* Modal: Novo Contato */}
<Dialog open={isNewContactOpen} onOpenChange={setIsNewContactOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Novo Contato</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label>Nome *</Label>
<Input
value={newContact.name}
onChange={(e) => setNewContact({ ...newContact, name: e.target.value })}
placeholder="Nome do contato"
/>
</div>
<div className="space-y-2">
<Label>Email</Label>
<Input
type="email"
value={newContact.email}
onChange={(e) => setNewContact({ ...newContact, email: e.target.value })}
placeholder="email@exemplo.com"
/>
</div>
<div className="space-y-2">
<Label>Telefone</Label>
<Input
value={newContact.phone}
onChange={(e) => setNewContact({ ...newContact, phone: e.target.value })}
placeholder="(00) 00000-0000"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Empresa</Label>
<Input
value={newContact.company}
onChange={(e) => setNewContact({ ...newContact, company: e.target.value })}
placeholder="Empresa"
/>
</div>
<div className="space-y-2">
<Label>Cargo</Label>
<Input
value={newContact.position}
onChange={(e) => setNewContact({ ...newContact, position: e.target.value })}
placeholder="Cargo"
/>
</div>
</div>
<Button
className="w-full"
onClick={() => createContactMutation.mutate(newContact)}
disabled={!newContact.name || createContactMutation.isPending}
>
{createContactMutation.isPending ? "Salvando..." : "Criar Contato"}
</Button>
</div>
</DialogContent>
</Dialog>
{/* Modal: Nova Atividade */}
<Dialog open={isNewActivityOpen} onOpenChange={setIsNewActivityOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Nova Atividade</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label>Título *</Label>
<Input
value={newActivity.title}
onChange={(e) => setNewActivity({ ...newActivity, title: e.target.value })}
placeholder="Descrição da atividade"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Tipo</Label>
<Select value={newActivity.type} onValueChange={(v) => setNewActivity({ ...newActivity, type: v })}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="task">Tarefa</SelectItem>
<SelectItem value="call">Ligação</SelectItem>
<SelectItem value="email">E-mail</SelectItem>
<SelectItem value="meeting">Reunião</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>Prioridade</Label>
<Select value={newActivity.priority} onValueChange={(v) => setNewActivity({ ...newActivity, priority: v })}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="low">Baixa</SelectItem>
<SelectItem value="medium">Média</SelectItem>
<SelectItem value="high">Alta</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label>Data prevista</Label>
<Input
type="datetime-local"
value={newActivity.due_at}
onChange={(e) => setNewActivity({ ...newActivity, due_at: e.target.value })}
/>
</div>
<Button
className="w-full"
onClick={() => createActivityMutation.mutate(newActivity)}
disabled={!newActivity.title || createActivityMutation.isPending}
>
{createActivityMutation.isPending ? "Salvando..." : "Criar Atividade"}
</Button>
</div>
</DialogContent>
</Dialog>
</BrowserFrame>
);
}

View File

@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import browserIcon from "/favicon.png";
import browserIcon from "@assets/arcadia_branding/arcadia_suite_icon.png";
import { Loader2 } from "lucide-react";
export default function AuthPage() {

View File

@ -8,22 +8,10 @@ if (!process.env.DATABASE_URL) {
);
}
// CORREÇÃO: Usar Pool em vez de Client único para melhor performance
// e evitar queries travadas quando conexão falha
const pool = new pg.Pool({
const client = new pg.Client({
connectionString: process.env.DATABASE_URL,
max: 20, // máximo de conexões no pool
idleTimeoutMillis: 30000, // tempo máximo de inatividade
connectionTimeoutMillis: 5000, // timeout de conexão: 5s
statement_timeout: 10000, // timeout de query: 10s
});
// Log de erros do pool
pool.on('error', (err) => {
console.error('[DB Pool] Erro inesperado:', err.message);
});
client.connect();
// Exportar pool para uso no session store
export { pool };
export const db = drizzle({ client: pool, schema });
export const db = drizzle({ client, schema });

View File

@ -1,72 +0,0 @@
#!/bin/bash
# ==============================================
# Arcádia Suite - Script de Deploy para VPS
# ==============================================
set -e
echo "=========================================="
echo " Arcádia Suite - Deploy VPS"
echo "=========================================="
if ! command -v docker &> /dev/null; then
echo "Docker não encontrado. Instalando..."
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
echo "Docker instalado! Faça logout e login novamente para aplicar as permissões."
exit 1
fi
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
echo "Docker Compose não encontrado. Instalando..."
sudo apt-get update && sudo apt-get install -y docker-compose-plugin
fi
if [ ! -f .env ]; then
echo "Arquivo .env não encontrado. Criando a partir do .env.example..."
cp .env.example .env
echo ""
echo "IMPORTANTE: Edite o arquivo .env com suas configurações antes de continuar!"
echo " nano .env"
echo ""
echo "Depois execute este script novamente."
exit 1
fi
echo ""
echo "1/4 - Parando containers existentes..."
docker compose down 2>/dev/null || docker-compose down 2>/dev/null || true
echo ""
echo "2/5 - Construindo a aplicação..."
docker compose build --no-cache 2>/dev/null || docker-compose build --no-cache
echo ""
echo "3/5 - Iniciando o banco de dados..."
docker compose up -d db 2>/dev/null || docker-compose up -d db
echo "Aguardando banco ficar pronto..."
sleep 5
echo ""
echo "4/5 - Aplicando migrations do banco de dados..."
docker compose run --rm app npx drizzle-kit push 2>/dev/null || docker-compose run --rm app npx drizzle-kit push
echo ""
echo "5/5 - Iniciando a aplicação..."
docker compose up -d 2>/dev/null || docker-compose up -d
sleep 10
echo ""
echo "=========================================="
echo " Deploy concluído!"
echo "=========================================="
echo ""
echo " App: http://$(hostname -I | awk '{print $1}'):5000"
echo " DB: PostgreSQL na porta 5432"
echo ""
echo " Comandos úteis:"
echo " Ver logs: docker compose logs -f app"
echo " Parar tudo: docker compose down"
echo " Reiniciar: docker compose restart app"
echo " Status: docker compose ps"
echo ""

View File

@ -1,42 +0,0 @@
# ─── MiroFlow — Aplicação Coolify ─────────────────────────────────────────────
# Conecta aos serviços compartilhados (ollama) via network arcadia-internal
# Requer: docker-compose.shared.yml rodando
name: arcadia-miroflow
services:
# ── MiroFlow (Cientistas via Ollama) ───────────────────────────────────────────
miroflow:
build:
context: .
dockerfile: Dockerfile.miroflow
restart: always
environment:
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
MIROFLOW_PORT: 8006
MIROFLOW_RESEARCHER_MODEL: ${MIROFLOW_RESEARCHER_MODEL:-llama3.1:8b}
networks:
- arcadia-internal
- coolify
labels:
- "traefik.enable=true"
- "traefik.docker.network=coolify"
- "traefik.http.routers.miroflow.entrypoints=https"
- "traefik.http.routers.miroflow.rule=Host(`${MIROFLOW_DOMAIN:-miroflow.onboardbi.com.br}`)"
- "traefik.http.routers.miroflow.tls=true"
- "traefik.http.routers.miroflow.tls.certresolver=letsencrypt"
- "traefik.http.services.miroflow.loadbalancer.server.port=8006"
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8006/health')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
arcadia-internal:
external: true
name: arcadia-internal
coolify:
external: true

View File

@ -25,7 +25,6 @@ services:
retries: 10
networks:
- arcadia-internal
- coolify
# ── Redis ────────────────────────────────────────────────────────────────────
redis:
@ -36,7 +35,6 @@ services:
- redis_data:/data
networks:
- arcadia-internal
- coolify
# ── App principal ─────────────────────────────────────────────────────────
app:
@ -69,9 +67,6 @@ services:
PLUS_PORT: "8080"
PLUS_AUTO_START: "false" # container separado em prod
SSO_PLUS_BASE_URL: http://plus:8080
# ── MiroFlow ──────────────────────────────────────────────────────────
MIROFLOW_HOST: miroflow
MIROFLOW_PORT: "8006"
# ── Apache Superset ───────────────────────────────────────────────────
SUPERSET_HOST: superset
SUPERSET_ADMIN_USER: ${SUPERSET_ADMIN_USER:-admin}
@ -91,15 +86,12 @@ services:
condition: service_started
networks:
- arcadia-internal
- coolify
- arcadia-public
labels:
- "traefik.enable=true"
- "traefik.docker.network=coolify"
- "traefik.http.routers.arcadia.entrypoints=https"
- "traefik.http.routers.arcadia.rule=Host(`${DOMAIN}`)"
- "traefik.http.routers.arcadia.tls=true"
- "traefik.http.routers.arcadia.tls.certresolver=letsencrypt"
- "traefik.http.services.arcadia.loadbalancer.server.port=5000"
# ── Arcádia Plus (Laravel + MySQL) ──────────────────────────────────────────
# Perfil `plus` — suba com: docker compose --profile plus up
@ -147,6 +139,38 @@ services:
networks:
- arcadia-internal
# ── Apache Superset (BI) ─────────────────────────────────────────────────────
# Perfil `bi` — suba com: docker compose --profile bi up
superset:
image: apache/superset:4.1.0
restart: always
profiles: [bi]
environment:
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY}
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/arcadia_superset
ARCADIA_DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
SUPERSET_ADMIN_USER: ${SUPERSET_ADMIN_USER:-admin}
SUPERSET_ADMIN_EMAIL: ${SUPERSET_ADMIN_EMAIL:-admin@arcadia.app}
SUPERSET_ADMIN_PASSWORD: ${SUPERSET_ADMIN_PASSWORD}
SUPERSET_WEBSERVER_PORT: "8088"
PYTHONPATH: /app/pythonpath
volumes:
- ./docker/superset/superset_config.py:/app/pythonpath/superset_config.py:ro
- ./docker/superset/init.sh:/app/docker/init.sh:ro
- superset_home:/app/superset_home
depends_on:
db:
condition: service_healthy
command: ["/bin/bash", "/app/docker/init.sh"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8088/health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
networks:
- arcadia-internal
# ── ERPNext (Frappe Framework) ───────────────────────────────────────────────
# Perfil `erpnext` — suba com: docker compose --profile erpnext up
erpnext-db:
@ -273,9 +297,6 @@ services:
SERVICE_NAME: embeddings
SERVICE_PORT: 8001
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
LITELLM_BASE_URL: http://litellm:4000
LITELLM_API_KEY: ${LITELLM_API_KEY}
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
depends_on:
db:
condition: service_healthy
@ -283,7 +304,7 @@ services:
- arcadia-internal
# ── LiteLLM (gateway unificado de LLM — soberania dos dados) ─────────────────
# Roteia: Ollama (local) → externo (opt-in)
# Roteia: LLMFit (fine-tuned) → Ollama (local) → externo (opt-in)
litellm:
image: ghcr.io/berriai/litellm:main-latest
restart: always
@ -293,10 +314,12 @@ services:
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
LITELLM_MASTER_KEY: ${LITELLM_API_KEY}
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/litellm
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
# Ollama: se instalado no host use http://host-gateway:11434
# Se usar container Docker, mantém http://ollama:11434
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
# LLMFit: URL do serviço de modelos fine-tuned
LLMFIT_BASE_URL: ${LLMFIT_BASE_URL:-}
# Providers externos opcionais (soberania: só habilitados se configurados)
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
GROQ_API_KEY: ${GROQ_API_KEY:-}
@ -305,7 +328,6 @@ services:
condition: service_healthy
networks:
- arcadia-internal
- coolify
# ── Ollama (LLMs locais — soberania total) ────────────────────────────────────
# OPÇÃO A (padrão): Ollama como container Docker
@ -318,8 +340,10 @@ services:
- ollama_models:/root/.ollama
networks:
- arcadia-internal
# Remova 'profiles: [ai]' para ativar por padrão no deploy
profiles: [ai]
# ── Open WebUI (interface para Ollama) ───────────────────────────────
# ── Open WebUI (interface para Ollama + LLMFit) ───────────────────────────────
open-webui:
image: ghcr.io/open-webui/open-webui:main
restart: always
@ -335,7 +359,7 @@ services:
- litellm
networks:
- arcadia-internal
- coolify
- arcadia-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.webui.rule=Host(`ai.${DOMAIN}`)"
@ -344,85 +368,17 @@ services:
- "traefik.http.services.webui.loadbalancer.server.port=8080"
profiles: [ai]
# ── Superset BI ───────────────────────────────────────────────────────────────
superset:
build:
context: ./docker/superset
dockerfile: Dockerfile
restart: always
environment:
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY}
SQLALCHEMY_DATABASE_URI: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/superset
PYTHONPATH: /app/pythonpath
SUPERSET_ADMIN_USERNAME: ${SUPERSET_ADMIN_USERNAME:-admin}
SUPERSET_ADMIN_PASSWORD: ${SUPERSET_ADMIN_PASSWORD}
SUPERSET_ADMIN_EMAIL: ${SUPERSET_ADMIN_EMAIL:-admin@onboardbi.com.br}
ARCADIA_DATABASE_URL: postgresql+psycopg2://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
command: ["/bin/bash", "/app/docker/init.sh"]
volumes:
- ./docker/superset/superset_config.py:/app/pythonpath/superset_config.py:ro
- ./docker/superset/init.sh:/app/docker/init.sh:ro
- superset_home:/app/superset_home
depends_on:
db:
condition: service_healthy
networks:
- arcadia-internal
- coolify
labels:
- "traefik.enable=true"
- "traefik.docker.network=coolify"
- "traefik.http.routers.superset.entrypoints=https"
- "traefik.http.routers.superset.rule=Host(`${SUPERSET_DOMAIN:-bi.onboardbi.com.br}`)"
- "traefik.http.routers.superset.tls=true"
- "traefik.http.routers.superset.tls.certresolver=letsencrypt"
- "traefik.http.services.superset.loadbalancer.server.port=8088"
# ── MiroFlow (Cientistas via Ollama) ───────────────────────────────────────────
miroflow:
build:
context: .
dockerfile: Dockerfile.miroflow
restart: always
environment:
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
MIROFLOW_PORT: 8006
MIROFLOW_RESEARCHER_MODEL: ${MIROFLOW_RESEARCHER_MODEL:-llama3.1:8b}
depends_on:
ollama:
condition: service_started
networks:
- arcadia-internal
- coolify
labels:
- "traefik.enable=true"
- "traefik.docker.network=coolify"
- "traefik.http.routers.miroflow.entrypoints=https"
- "traefik.http.routers.miroflow.rule=Host(`${MIROFLOW_DOMAIN:-miroflow.onboardbi.com.br}`)"
- "traefik.http.routers.miroflow.tls=true"
- "traefik.http.routers.miroflow.tls.certresolver=letsencrypt"
- "traefik.http.services.miroflow.loadbalancer.server.port=8006"
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8006/health')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
arcadia-internal:
driver: bridge
coolify:
external: true
arcadia-public:
driver: bridge
volumes:
pgdata:
redis_data:
ollama_models:
open_webui_data:
plus_db:
plus_storage:
superset_home:

View File

@ -1,62 +0,0 @@
# ─── Arcádia — Infraestrutura Compartilhada ─────────────────────────────────
# Serviços base: db, redis, ollama
# Usado por: superset, miroflow, app, etc.
name: arcadia-shared
services:
# ── Banco de dados com pgvector ─────────────────────────────────────────────
db:
image: pgvector/pgvector:pg16
restart: always
environment:
POSTGRES_DB: ${PGDATABASE:-arcadia}
POSTGRES_USER: ${PGUSER:-arcadia}
POSTGRES_PASSWORD: ${PGPASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
- ./docker/init-pgvector.sql:/docker-entrypoint-initdb.d/01-pgvector.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${PGUSER:-arcadia}"]
interval: 10s
timeout: 5s
retries: 10
networks:
- arcadia-internal
# ── Redis ────────────────────────────────────────────────────────────────────
redis:
image: redis:7-alpine
restart: always
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
networks:
- arcadia-internal
# ── Ollama (LLMs locais — soberania total) ────────────────────────────────────
ollama:
image: ollama/ollama:latest
restart: always
volumes:
- ollama_models:/root/.ollama
networks:
- arcadia-internal
- coolify
networks:
arcadia-internal:
driver: bridge
name: arcadia-internal
coolify:
external: true
volumes:
pgdata:
external: true
name: arcadia-prod_pgdata
redis_data:
external: true
name: arcadia-prod_redis_data
ollama_models:

View File

@ -1,49 +0,0 @@
# ─── Superset BI — Aplicação Coolify ───────────────────────────────────────
# Conecta aos serviços compartilhados (db, redis) via network arcadia-internal
# Requer: docker-compose.shared.yml rodando
name: arcadia-superset
services:
# ── Superset BI ───────────────────────────────────────────────────────────────
superset:
image: arcadia-prod-superset:latest
restart: always
environment:
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/superset
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY}
SQLALCHEMY_DATABASE_URI: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/superset
PYTHONPATH: /app/pythonpath
SUPERSET_ADMIN_USERNAME: ${SUPERSET_ADMIN_USERNAME:-admin}
SUPERSET_ADMIN_PASSWORD: ${SUPERSET_ADMIN_PASSWORD}
SUPERSET_ADMIN_EMAIL: ${SUPERSET_ADMIN_EMAIL:-admin@onboardbi.com.br}
ARCADIA_DATABASE_URL: postgresql+psycopg2://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
command: ["/bin/bash", "/app/docker/init.sh"]
volumes:
- ./docker/superset/superset_config.py:/app/pythonpath/superset_config.py:ro
- ./docker/superset/init.sh:/app/docker/init.sh:ro
- superset_home:/app/superset_home
networks:
- arcadia-internal
- coolify
labels:
- "traefik.enable=true"
- "traefik.docker.network=coolify"
- "traefik.http.routers.superset.entrypoints=https"
- "traefik.http.routers.superset.rule=Host(`${SUPERSET_DOMAIN:-bi.onboardbi.com.br}`)"
- "traefik.http.routers.superset.tls=true"
- "traefik.http.routers.superset.tls.certresolver=letsencrypt"
- "traefik.http.services.superset.loadbalancer.server.port=8088"
networks:
arcadia-internal:
external: true
name: arcadia-internal
coolify:
external: true
volumes:
superset_home:
external: true
name: arcadia-prod_superset_home

View File

@ -29,6 +29,8 @@ services:
redis:
image: redis:7-alpine
restart: unless-stopped
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
@ -59,23 +61,12 @@ services:
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
LITELLM_BASE_URL: http://litellm:4000
LITELLM_API_KEY: ${LITELLM_API_KEY:-arcadia-internal}
# MetaSet BI
METASET_HOST: metaset
METASET_PORT: 8100
# Kernel Arcadia (Service Discovery)
KERNEL_ENABLED: "true"
KERNEL_AUTOSTART: "true"
COOLIFY_API_TOKEN: "${COOLIFY_API_TOKEN:-}"
ports:
- "5000:5000"
- "5001:5001" # Kernel Arcadia (Service Discovery)
volumes:
- .:/app
- /app/node_modules
- /app/dist
networks:
- arcadia
- coolify
depends_on:
db:
condition: service_healthy
@ -92,16 +83,6 @@ services:
context: .
dockerfile: Dockerfile.python
restart: unless-stopped
labels:
- "arcadia.discovery.enabled=true"
- "arcadia.name=Motor Contábil"
- "arcadia.type=python"
- "arcadia.category=contabil"
- "arcadia.port=8003"
- "arcadia.capabilities=lancamentos,dre,balancete,razao"
- "arcadia.requires_ia=false"
- "arcadia.health_path=/health"
- "arcadia.description=Contabilidade - Lançamentos, DRE, Balancete, Razão"
environment:
SERVICE_NAME: contabil
SERVICE_PORT: 8003
@ -119,17 +100,6 @@ services:
context: .
dockerfile: Dockerfile.python
restart: unless-stopped
labels:
- "arcadia.discovery.enabled=true"
- "arcadia.name=Motor BI"
- "arcadia.type=python"
- "arcadia.category=bi"
- "arcadia.port=8004"
- "arcadia.capabilities=sql,charts,microbi,cache"
- "arcadia.requires_ia=true"
- "arcadia.ia_policies=bi,lgpd"
- "arcadia.health_path=/health"
- "arcadia.description=Business Intelligence - SQL, Charts, Micro-BI, Cache"
environment:
SERVICE_NAME: bi
SERVICE_PORT: 8004
@ -147,17 +117,6 @@ services:
context: .
dockerfile: Dockerfile.python
restart: unless-stopped
labels:
- "arcadia.discovery.enabled=true"
- "arcadia.name=Motor Automação"
- "arcadia.type=python"
- "arcadia.category=automation"
- "arcadia.port=8005"
- "arcadia.capabilities=scheduler,eventbus,workflow"
- "arcadia.requires_ia=true"
- "arcadia.ia_policies=automation"
- "arcadia.health_path=/health"
- "arcadia.description=Scheduler, Event Bus, Workflow Executor"
environment:
SERVICE_NAME: automation
SERVICE_PORT: 8005
@ -175,17 +134,6 @@ services:
context: .
dockerfile: Dockerfile.python
restart: unless-stopped
labels:
- "arcadia.discovery.enabled=true"
- "arcadia.name=Motor Fiscal"
- "arcadia.type=python"
- "arcadia.category=fiscal"
- "arcadia.port=8002"
- "arcadia.capabilities=nfe,nfce,ncm,cfop,cest,sefaz"
- "arcadia.requires_ia=true"
- "arcadia.ia_policies=fiscal"
- "arcadia.health_path=/health"
- "arcadia.description=NF-e/NFC-e - NCMs, CFOPs, CESTs, SEFAZ"
environment:
SERVICE_NAME: fisco
SERVICE_PORT: 8002
@ -203,17 +151,6 @@ services:
context: .
dockerfile: Dockerfile.python
restart: unless-stopped
labels:
- "arcadia.discovery.enabled=true"
- "arcadia.name=Serviço Embeddings"
- "arcadia.type=python"
- "arcadia.category=ai"
- "arcadia.port=8001"
- "arcadia.capabilities=embeddings,vectorsearch"
- "arcadia.requires_ia=true"
- "arcadia.ia_policies=ai,lgpd"
- "arcadia.health_path=/health"
- "arcadia.description=Embeddings e Vector Search com pgvector"
environment:
SERVICE_NAME: embeddings
SERVICE_PORT: 8001
@ -224,106 +161,10 @@ services:
db:
condition: service_healthy
# ── MetaSet BI (Apache Superset Fork) ────────────────────────────────────────
metaset:
build:
context: ./server/bi/metaset
dockerfile: Dockerfile
restart: unless-stopped
labels:
- "arcadia.discovery.enabled=true"
- "arcadia.name=MetaSet BI"
- "arcadia.type=python"
- "arcadia.category=bi"
- "arcadia.port=8100"
- "arcadia.capabilities=dashboards,charts,sql_lab,datasets,embedding,rls"
- "arcadia.requires_ia=false"
- "arcadia.health_path=/health"
- "arcadia.description=MetaSet BI - Business Intelligence by ArcadiaSuite"
- "arcadia.version=4.1.0-arcadia"
- "traefik.enable=true"
- "traefik.docker.network=coolify"
- "traefik.http.routers.metaset.entrypoints=https"
- "traefik.http.routers.metaset.rule=Host(`bi.onboardbi.com.br`)"
- "traefik.http.routers.metaset.tls=true"
- "traefik.http.routers.metaset.tls.certresolver=letsencrypt"
- "traefik.http.services.metaset.loadbalancer.server.port=8100"
environment:
DATABASE_URL: postgresql://arcadia:arcadia123@db:5432/metaset_db
ARCADIA_DATABASE_URL: postgresql://arcadia:arcadia123@db:5432/arcadia
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY:-arcadia-superset-secret-change-in-prod}
METASET_ADMIN_USER: ${SUPERSET_ADMIN_USER:-admin}
METASET_ADMIN_EMAIL: ${SUPERSET_ADMIN_EMAIL:-admin@arcadia.app}
METASET_ADMIN_PASSWORD: ${SUPERSET_ADMIN_PASSWORD:-arcadia2026}
REDIS_URL: redis://redis:6379/1
PYTHONPATH: /app
FLASK_ENV: production
ports:
- "8100:8100"
volumes:
- metaset_home:/app/superset_home
- ./server/bi/metaset/branding:/app/superset/static/assets/images/branding:ro
- ./server/bi/metaset/superset_config.py:/app/superset_config.py:ro
extra_hosts:
- "db:10.0.10.2"
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8100/health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
# ── BI-API Gateway (Node.js) ─────────────────────────────────────────────────
bi-api:
build:
context: .
dockerfile: server/bi/Dockerfile
restart: unless-stopped
labels:
- "arcadia.discovery.enabled=true"
- "arcadia.name=BI API Gateway"
- "arcadia.type=node"
- "arcadia.category=bi"
- "arcadia.port=8004"
- "arcadia.capabilities=proxy,embed,api"
- "arcadia.requires_ia=false"
- "arcadia.health_path=/health"
- "arcadia.description=BI Gateway - Proxy para MetaSet e FDB-Bridge"
- "arcadia.version=1.0.0"
environment:
NODE_ENV: production
PORT: 8004
METASET_HOST: metaset
METASET_PORT: 8100
FDB_BRIDGE_HOST: fdb-bridge
FDB_BRIDGE_PORT: 8200
ports:
- "8004:8004"
depends_on:
metaset:
condition: service_healthy
# ── Apache Superset (BI avançado - LEGADO, será removido) ─────────────────────
# ── Apache Superset (BI avançado) ────────────────────────────────────────────
superset:
image: apache/superset:4.1.0
restart: unless-stopped
profiles: [bi]
labels:
- "arcadia.discovery.enabled=true"
- "arcadia.name=Apache Superset"
- "arcadia.type=java"
- "arcadia.category=data"
- "arcadia.port=8088"
- "arcadia.capabilities=dashboards,charts,sql_lab,datasets"
- "arcadia.requires_ia=false"
- "arcadia.health_path=/health"
- "arcadia.description=Apache Superset original (legado)"
- "arcadia.version=4.1.0"
environment:
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY:-superset-secret-change-in-prod}
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD:-arcadia123}@db:5432/arcadia_superset
@ -340,8 +181,6 @@ services:
- ./docker/superset/superset_config.py:/app/pythonpath/superset_config.py:ro
- ./docker/superset/init.sh:/app/docker/init.sh:ro
- superset_home:/app/superset_home
extra_hosts:
- "db:10.0.10.2"
depends_on:
db:
condition: service_healthy
@ -354,15 +193,34 @@ services:
start_period: 60s
networks:
- arcadia
profiles: [bi]
# ─────────────── PERFIL: ai (Soberania de IA) ────────────────────────────────
# ── Ollama (LLMs locais) ─────────────────────────────────────────────────────
ollama:
image: ollama/ollama:latest
restart: unless-stopped
volumes:
- ollama_models:/root/.ollama
ports:
- "11434:11434"
profiles: [ai]
# Para GPU NVIDIA: adicione `deploy.resources.reservations.devices`
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: 1
# capabilities: [gpu]
# ── Open WebUI (interface para devs/consultores) ─────────────────────────────
open-webui:
image: ghcr.io/open-webui/open-webui:main
restart: unless-stopped
environment:
OLLAMA_BASE_URL: http://ollama-ia1upsekrad96at5hq97e4qa:11434
OLLAMA_BASE_URL: http://ollama:11434
WEBUI_SECRET_KEY: ${WEBUI_SECRET_KEY:-webui-secret-change-in-prod}
DEFAULT_MODELS: llama3.3,qwen2.5-coder
ENABLE_RAG_WEB_SEARCH: "true"
@ -370,6 +228,8 @@ services:
- "3001:8080"
volumes:
- open_webui_data:/app/backend/data
depends_on:
- ollama
profiles: [ai]
# ── LiteLLM (proxy unificado de LLMs) ───────────────────────────────────────
@ -381,7 +241,7 @@ services:
command: ["--config", "/app/config.yaml", "--port", "4000", "--detailed_debug"]
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
OLLAMA_BASE_URL: http://ollama-ia1upsekrad96at5hq97e4qa:11434
OLLAMA_BASE_URL: http://ollama:11434
LITELLM_MASTER_KEY: ${LITELLM_API_KEY:-arcadia-internal}
ports:
- "4000:4000"
@ -413,30 +273,6 @@ services:
retries: 10
start_period: 30s
# ── Neo4j (Knowledge Graph) ──────────────────────────────────────────────────
neo4j:
image: neo4j:5.20-community
restart: unless-stopped
profiles: [agentic]
environment:
NEO4J_AUTH: neo4j/${NEO4J_PASSWORD:-arcadia123}
NEO4J_PLUGINS: '["apoc"]'
NEO4J_dbms_security_procedures_unrestricted: apoc.*
volumes:
- neo4j_data:/data
- neo4j_logs:/logs
ports:
- "7474:7474"
- "7687:7687"
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:7474 || exit 1"]
interval: 20s
timeout: 10s
retries: 5
start_period: 30s
networks:
- arcadia
# ── ERPNext (Frappe Framework + ERPNext app) ─────────────────────────────────
erpnext:
image: frappe/erpnext:version-15
@ -466,18 +302,13 @@ services:
volumes:
pgdata:
ollama_models:
open_webui_data:
superset_home:
metaset_home:
erpnext_db:
erpnext_sites:
erpnext_logs:
neo4j_data:
neo4j_logs:
networks:
arcadia:
driver: bridge
coolify:
external: true
name: coolify

View File

@ -3,18 +3,45 @@
#
# ESTRATÉGIA DE SOBERANIA DOS DADOS:
# ┌─────────────────────────────────────────────────────────────────────────┐
# │ TIER 1 (soberania total): Ollama — modelos open source no servidor │
# │ TIER 2 (opt-in): Providers externos — só com configuração explícita │
# │ TIER 1 (soberania total): LLMFit — modelos fine-tuned locais │
# │ TIER 2 (soberania total): Ollama — modelos open source no servidor │
# │ TIER 3 (opt-in): Providers externos — só com configuração explícita │
# └─────────────────────────────────────────────────────────────────────────┘
# O Manus, Autonomous Agents e todos os serviços chamam APENAS este proxy.
# Nunca chamam APIs externas diretamente.
model_list:
# ── TIER 1: Ollama (LLMs locais — soberania total) ───────────────────────────
- model_name: llama3.2
# ── TIER 1: LLMFit (modelos fine-tuned locais — máxima soberania) ────────────
# Descomente quando o LLMFit estiver rodando no servidor
# O LLMFit expõe API compatível com OpenAI — basta apontar a URL
#
# - model_name: arcadia-finetuned
# litellm_params:
# model: openai/arcadia-v1 # nome do modelo no LLMFit
# api_base: os.environ/LLMFIT_BASE_URL
# api_key: llmfit-internal
#
# - model_name: arcadia-embed
# litellm_params:
# model: openai/arcadia-embed-v1 # modelo de embeddings fine-tuned
# api_base: os.environ/LLMFIT_BASE_URL
# api_key: llmfit-internal
# ── TIER 2: Ollama (LLMs locais — soberania total) ───────────────────────────
- model_name: llama3.3
litellm_params:
model: ollama/llama3.2:3b
model: ollama/llama3.3
api_base: os.environ/OLLAMA_BASE_URL
- model_name: qwen2.5-coder
litellm_params:
model: ollama/qwen2.5-coder:7b
api_base: os.environ/OLLAMA_BASE_URL
- model_name: deepseek-r1
litellm_params:
model: ollama/deepseek-r1:7b
api_base: os.environ/OLLAMA_BASE_URL
- model_name: nomic-embed-text
@ -22,13 +49,7 @@ model_list:
model: ollama/nomic-embed-text
api_base: os.environ/OLLAMA_BASE_URL
# Alias OpenAI-compatible para embedding (Manus usa este nome por padrão)
- model_name: text-embedding-3-small
litellm_params:
model: ollama/nomic-embed-text
api_base: os.environ/OLLAMA_BASE_URL
# ── TIER 2: OpenAI (opt-in — só ativo se OPENAI_API_KEY configurado) ─────────
# ── TIER 3: OpenAI (opt-in — só ativo se OPENAI_API_KEY configurado) ─────────
- model_name: gpt-4o
litellm_params:
model: openai/gpt-4o
@ -39,35 +60,42 @@ model_list:
model: openai/gpt-4o-mini
api_key: os.environ/OPENAI_API_KEY
# ── TIER 2: Anthropic (opt-in — descomente para habilitar) ───────────────────
# ── TIER 3: Anthropic (opt-in — descomente para habilitar) ───────────────────
# - model_name: claude-sonnet
# litellm_params:
# model: anthropic/claude-sonnet-4-6
# api_key: os.environ/ANTHROPIC_API_KEY
# ── TIER 2: Groq (opt-in — inferência rápida sem dados persistidos) ──────────
# ── TIER 3: Groq (opt-in — inferência rápida sem dados persistidos) ──────────
# - model_name: groq-llama
# litellm_params:
# model: groq/llama-3.3-70b-versatile
# api_key: os.environ/GROQ_API_KEY
# ── Modelo padrão do Arcádia (Manus usa este) ─────────────────────────────────
# Prioridade: LLMFit → OpenAI (se configurado) → Ollama (sempre disponível)
# Para soberania total: remova o fallback para gpt-4o-mini
- model_name: arcadia-default
litellm_params:
model: ollama/llama3.2:3b
model: ollama/llama3.3
api_base: os.environ/OLLAMA_BASE_URL
model_info:
# fallbacks: ["gpt-4o-mini"] # descomente para habilitar fallback externo
router_settings:
routing_strategy: least-busy
fallbacks:
- {"gpt-4o": ["llama3.2"]}
- {"gpt-4o-mini": ["llama3.2"]}
- {"arcadia-default": ["llama3.2"]}
fallbacks:
- {"gpt-4o": ["llama3.3"]}
- {"gpt-4o-mini": ["llama3.3"]}
- {"arcadia-default": ["llama3.3"]}
litellm_settings:
drop_params: true
request_timeout: 120
set_verbose: false
# Loga todas as chamadas no banco — essencial para auditoria e soberania
success_callback: ["langfuse"]
failure_callback: ["langfuse"]
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY

View File

@ -1,5 +0,0 @@
FROM apache/superset:latest
USER root
RUN pip install psycopg2-binary && \
sed -i 's/include-system-site-packages = false/include-system-site-packages = true/' /app/.venv/pyvenv.cfg
USER superset

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@ -3,21 +3,20 @@
# Executado ao iniciar o container
set -e
SUPERSET_ADMIN_USER="${SUPERSET_ADMIN_USERNAME:-${SUPERSET_ADMIN_USER:-admin}}"
SUPERSET_ADMIN_USER="${SUPERSET_ADMIN_USER:-admin}"
SUPERSET_ADMIN_EMAIL="${SUPERSET_ADMIN_EMAIL:-admin@arcadia.app}"
SUPERSET_ADMIN_PASSWORD="${SUPERSET_ADMIN_PASSWORD:-arcadia2026}"
ARCADIA_DB_URL="${ARCADIA_DATABASE_URL:-postgresql://arcadia:arcadia123@db:5432/arcadia}"
echo "[Superset Init] Aguardando PostgreSQL..."
cat > /tmp/pgcheck.py << 'EOF'
until python -c "
import psycopg2, os, sys
url = os.environ.get('SQLALCHEMY_DATABASE_URI', 'postgresql://arcadia:arcadia123@db:5432/superset')
try:
url = os.environ.get('DATABASE_URL', 'postgresql://arcadia:arcadia123@db:5432/arcadia_superset')
psycopg2.connect(url)
except Exception:
sys.exit(1)
EOF
until python /tmp/pgcheck.py 2>/dev/null; do
sys.exit(0)
except: sys.exit(1)
" 2>/dev/null; do
sleep 2
done
echo "[Superset Init] PostgreSQL disponível!"

View File

@ -9,8 +9,8 @@ SECRET_KEY = os.environ.get("SUPERSET_SECRET_KEY", "change-in-production-use-ope
# ── Banco de metadados do Superset ────────────────────────────────────────────
SQLALCHEMY_DATABASE_URI = os.environ.get(
"SQLALCHEMY_DATABASE_URI",
os.environ.get("DATABASE_URL", "postgresql://arcadia:arcadia123@db:5432/superset")
"DATABASE_URL",
"postgresql://arcadia:arcadia123@db:5432/arcadia_superset"
)
# ── CORS — permite o gateway Arcádia (:5000) chamar a API ────────────────────

View File

@ -1,58 +0,0 @@
# ===========================================
# Arcádia Suite - Variáveis de Ambiente
# ===========================================
# Copie este arquivo para .env na raiz do projeto
# e preencha com seus valores
# -----------------------------
# Banco de Dados PostgreSQL
# -----------------------------
DATABASE_URL=postgresql://arcadia:SUA_SENHA_AQUI@localhost:5432/arcadia_suite
PGHOST=localhost
PGPORT=5432
PGUSER=arcadia
PGPASSWORD=SUA_SENHA_AQUI
PGDATABASE=arcadia_suite
# -----------------------------
# Ambiente
# -----------------------------
NODE_ENV=production
PORT=5000
# -----------------------------
# Sessão e Segurança
# -----------------------------
# Gere uma chave aleatória: openssl rand -base64 32
SESSION_SECRET=GERE_UMA_CHAVE_ALEATORIA_LONGA_AQUI
# -----------------------------
# ERPNext (opcional)
# -----------------------------
ERPNEXT_URL=https://seu-erpnext.com
ERPNEXT_API_KEY=sua_api_key
ERPNEXT_API_SECRET=seu_api_secret
# -----------------------------
# OpenAI (opcional - para IA)
# -----------------------------
OPENAI_API_KEY=sk-sua-chave-openai
# -----------------------------
# WhatsApp Baileys (opcional)
# -----------------------------
# As sessões são armazenadas automaticamente em ./baileys_sessions
# -----------------------------
# Fiscal - SEFAZ (opcional)
# -----------------------------
# Caminho para o certificado digital A1 (.pfx)
CERT_PATH=/path/to/certificado.pfx
CERT_PASSWORD=senha_do_certificado
SEFAZ_AMBIENTE=2 # 1=Produção, 2=Homologação
# -----------------------------
# ChromaDB (opcional)
# -----------------------------
CHROMA_HOST=localhost
CHROMA_PORT=8000

View File

@ -1,54 +0,0 @@
---
⚠️ **AVISO: DOCUMENTAÇÃO HISTÓRICA**
Este documento foi criado em **Março/2026** como planejamento estratégico.
Pode conter informações desatualizadas em relação ao sistema atual.
Para documentação técnica atualizada, consulte:
- \_SPEC_NAVBAR.md\_ (componentes UI)
- \_KERNEL_SPEC.md\_ (arquitetura do Kernel)
- Código-fonte em \_server/modules/\_
---
# Agente: Monitor de Skills
## Objetivo
Verificar as skills mais executadas nos últimos 7 dias e gerar um resumo com recomendações.
## Passos
1. **Consultar execuções recentes**
- Buscar em `/api/skills` todas as skills ativas do tenant
- Para cada skill, buscar `/api/skills/:id/executions?limit=50`
- Filtrar execuções dos últimos 7 dias
2. **Analisar padrões**
- Contar total de execuções por skill
- Identificar taxa de sucesso (status = "success" / total)
- Destacar skills com taxa de falha > 20%
3. **Gerar relatório**
- Listar top 5 skills mais executadas
- Alertar skills com problemas
- Sugerir skills candidatas a automação (executadas 3+ vezes por dia)
## Saída esperada
```
## Relatório de Skills — últimos 7 dias
### Top 5 mais usadas
1. [nome] — X execuções (Y% sucesso)
...
### Atenção
- [skill] com taxa de falha de Z%
### Candidatas ao OpenClaw
- [skill] — média de N exec/dia
```
## Parâmetros
- `tenant_id` — filtro de tenant (opcional)
- `dias` — janela de análise (padrão: 7)

View File

@ -1,203 +0,0 @@
# 🤖 Referência Rápida para Agentes - Arcádia Suite
> Guia de referência para desenvolvimento autônomo no projeto Arcádia Suite
---
## 📁 Documentação Técnica por Componente
### Navegação (Navbar)
- **Especificação Completa:** [`docs/SPEC_NAVBAR.md`](docs/SPEC_NAVBAR.md)
- **Arquivo Fonte:** `client/src/components/Browser/BrowserFrame.tsx`
- **Hook de Tracking:** `client/src/hooks/use-navigation-tracking.ts`
- **Error Boundary:** `client/src/components/ErrorBoundary.tsx` (tratamento de erros lazy loading)
### Banco de Dados (Schema)
- **Arquivo Principal:** `shared/schema.ts` (7.000+ linhas)
- **Tabelas Principais:**
- `users`, `profiles`, `roles` - Autenticação e RBAC
- `tenants`, `tenantUsers` - Multi-tenancy
- `workspacePages`, `pageBlocks` - Produtividade (estilo Notion)
- `conversations`, `messages` - Chat/Agente
- `whatsappSessions`, `whatsappMessages` - WhatsApp
- `crmPartners`, `crmContracts` - CRM
- `fiscalNcms`, `fiscalGruposTributacao` - Fiscal
- `retailStores`, `posSales`, `mobileDevices` - Retail
- `manusRuns`, `manusSteps` - Agente Manus
- `biDatasets`, `biCharts`, `biDashboards` - BI
### Backend (API Routes)
- **Registro de Rotas:** `server/routes.ts`
- **Autenticação:** `server/auth.ts`
- **Agente Manus:** `server/manus/service.ts` + `server/manus/tools.ts`
- **WhatsApp:** `server/whatsapp/routes.ts` + `server/whatsapp/service.ts`
- **CRM:** `server/crm/routes.ts`
- **Fiscal:** `server/fisco/routes.ts`
- **Retail:** `server/retail/routes.ts`
- **Blackboard (Agentes):** `server/blackboard/routes.ts`
### Frontend (Páginas)
- **Router:** `client/src/App.tsx` (inclui ErrorBoundary + Suspense)
- **Cockpit (Home):** `client/src/pages/Cockpit.tsx`
- **Agent:** `client/src/pages/Agent.tsx`
- **DevCenter:** `client/src/pages/DevCenter.tsx`
- **Crm:** `client/src/pages/Crm.tsx`
- **Fisco:** `client/src/pages/Fisco.tsx`
- **Retail:** `client/src/pages/ArcadiaRetail.tsx`
- **WhatsApp:** `client/src/pages/WhatsApp.tsx`
- **ErrorBoundary:** `client/src/components/ErrorBoundary.tsx`
---
## 🏗️ Arquitetura do Sistema
```
┌─────────────────────────────────────────────────────────────────┐
│ CAMADA DE APRESENTAÇÃO │
│ React 18 + TypeScript + Tailwind CSS + shadcn/ui │
│ 66 páginas / Interface tipo browser com abas │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ CAMADA DE ORQUESTRAÇÃO │
│ Express.js + Socket.IO + Manus Agent │
│ API REST + WebSocket em tempo real │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ CAMADA DE INTELIGÊNCIA │
│ FastAPI (Contábil 8003, BI 8004, Automação 8005) │
│ OpenAI GPT-4o + LiteLLM Gateway + Ollama │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ CAMADA DE DADOS │
│ PostgreSQL 16 + Drizzle ORM + pgvector │
│ Session Store + Multi-tenant │
└─────────────────────────────────────────────────────────────────┘
```
---
## 🛠️ Stack Tecnológico
| Camada | Tecnologia |
|--------|------------|
| **Frontend** | React 18, TypeScript, Tailwind CSS, shadcn/ui |
| **Backend** | Express.js, Socket.IO, Passport.js |
| **Banco** | PostgreSQL 16, Drizzle ORM, pgvector |
| **Python** | FastAPI (microserviços nas portas 8001-8005) |
| **IA** | OpenAI GPT-4o, LiteLLM, Ollama |
| **Deploy** | Docker Compose, Coolify, PM2 |
---
## 🔧 Comandos Úteis
```bash
# Desenvolvimento
npm run dev # Inicia servidor de desenvolvimento
npm run dev:client # Apenas frontend (porta 5000)
# Banco de dados
npm run db:push # Aplica migrations Drizzle
npm run db:apply-rls # Aplica políticas RLS
# Build
npm run build # Build para produção
npm run start # Inicia em produção
# Docker
docker compose up -d # Sobe todos os serviços
docker compose --profile ai up # Sobe com IA (Ollama + LiteLLM)
```
---
## 📋 Convenções de Código
### Nomenclatura
- **Componentes:** PascalCase (ex: `BrowserFrame.tsx`)
- **Hooks:** camelCase com prefixo `use` (ex: `useAuth.tsx`)
- **Utilitários:** camelCase (ex: `navigation-tracking.ts`)
- **Rotas API:** kebab-case (ex: `/api/whatsapp/connect`)
### Estrutura de Pastas
```
client/src/
components/ # Componentes reutilizáveis
pages/ # Páginas da aplicação
hooks/ # Custom hooks
lib/ # Utilitários e configurações
server/
[modulo]/ # Um diretório por módulo
routes.ts # Rotas da API
service.ts # Lógica de negócio
storage.ts # Acesso a dados
```
---
## 🧪 Test IDs
Sempre adicione `data-testid` em elementos interativos:
```tsx
<button
data-testid="button-submit"
onClick={handleSubmit}
>
Enviar
</button>
```
---
## 📚 Documentação Adicional
- **Documentação Técnica Completa:** [`DOCUMENTATION.md`](DOCUMENTATION.md)
- **Mapa do Sistema:** [`MAPA_SISTEMA_ARCADIA.md`](MAPA_SISTEMA_ARCADIA.md)
- **Contexto para Claude:** [`CLAUDE.md`](CLAUDE.md)
- **README:** [`README.md`](README.md)
---
## 🆘 Precisa de Ajuda?
1. Consulte a especificação técnica específica em `docs/`
2. Verifique exemplos similares no código existente
3. Siga os padrões já estabelecidos no projeto
---
*Última atualização: 2026-04-08*
---
## 🐛 Tratamento de Erros - Lazy Loading
### Problema Resolvido: Loading Infinito na Navegação
**Sintoma:** Ao clicar em itens da navbar, se a página tivesse erro (import quebrado, sintaxe inválida), o loading ficava infinito.
**Causa:** `React.lazy()` falha silenciosamente e `Suspense` não tem como capturar o erro.
**Solução:**
1. **ErrorBoundary** (`client/src/components/ErrorBoundary.tsx`):
- Captura erros de renderização, incluindo lazy load
- Mostra UI de fallback com botão de retry
2. **LoadingFallback com Timeout** (`client/src/App.tsx`):
- 3s: mensagem "Isso está demorando..."
- 10s: alerta amarelo com botão recarregar
3. **Integração:**
```tsx
<ErrorBoundary>
<Suspense fallback={<LoadingFallback />}>
<Switch>{/* rotas */}</Switch>
</Suspense>
</ErrorBoundary>
```

View File

@ -1,374 +0,0 @@
# AUDITORIA DO PLANO XOS - Arcádia Suite v1.0
**Data:** 06 de Fevereiro de 2026
**Projeto:** Arcádia Suite - Office Estratégico Empresarial
**Escopo:** Sistema Operacional de Desenvolvimento Autônomo (XOS) com Blackboard Architecture
---
## RESUMO EXECUTIVO
O plano XOS v1.0 foi executado em **4 fases** progressivas, transformando o Arcádia Suite de um sistema de automação empresarial em um **sistema operacional de desenvolvimento autônomo** com 6 agentes de IA especializados, governança de segurança, e pipeline autônomo de ponta a ponta.
| Indicador | Valor |
|---|---|
| Fases planejadas | 4 |
| Fases executadas | 4 (100%) |
| Agentes de IA criados | 6 |
| Tabelas de banco criadas | 31 (XOS) + 3 (Blackboard) = 34 novas tabelas |
| Linhas de código (XOS backend) | ~5.491 linhas |
| Linhas de código (XOS frontend) | ~988 linhas |
| APIs criadas | 22+ endpoints |
| Páginas UI criadas | 2 (Governance Dashboard + Pipeline UI) |
---
## FASE 1 - FUNDAÇÃO (Blackboard + Agentes Base)
**Status: CONCLUIDO**
### O que foi planejado:
- Criar arquitetura Blackboard centralizada para coordenação de agentes
- Implementar agentes base de IA para desenvolvimento autônomo
- Sistema de ferramentas (ToolManager) para interação com código
### O que foi executado:
#### 1.1 Blackboard Service (`server/blackboard/service.ts` - 476 linhas)
- Quadro negro centralizado para coordenação de tarefas entre agentes
- Criação, atribuição e monitoramento de tarefas
- Gerenciamento de artefatos (código gerado, especificações, relatórios)
- Logging estruturado por agente
#### 1.2 Base Agent (`server/blackboard/BaseBlackboardAgent.ts` - 189 linhas)
- Classe abstrata para todos os agentes de IA
- Loop thought-action-observation com OpenAI
- Polling automático para busca de tarefas pendentes
- Integração com governança para verificação de políticas
- Event emitter para comunicação entre agentes
#### 1.3 Context Indexer (`server/blackboard/ContextIndexer.ts` - 277 linhas)
- Indexação inteligente da estrutura do projeto
- Mapeamento de arquivos, imports e dependências
- Contexto técnico para agentes tomarem decisões informadas
#### 1.4 ToolManager (`server/autonomous/tools/ToolManager.ts` - 171 linhas)
- Registro e execução de ferramentas para agentes
- 10 ferramentas implementadas:
| Ferramenta | Arquivo | Linhas | Função |
|---|---|---|---|
| ReadFileTool | filesystem/ReadFileTool.ts | 66 | Leitura de arquivos do projeto |
| WriteFileTool | filesystem/WriteFileTool.ts | 88 | Escrita segura de arquivos |
| ListDirectoryTool | filesystem/ListDirectoryTool.ts | 85 | Listagem de diretórios |
| SearchCodeTool | filesystem/SearchCodeTool.ts | 92 | Busca por padrões no código |
| RunCommandTool | command/RunCommandTool.ts | 76 | Execução de comandos shell |
| TypeCheckTool | command/TypeCheckTool.ts | 70 | Validação TypeScript real |
| GitStatusTool | git/GitStatusTool.ts | 47 | Status do repositório |
| GitCommitTool | git/GitCommitTool.ts | 67 | Commits automáticos |
| AnalyzeRepoTool | github/AnalyzeRepoTool.ts | 41 | Análise de repos externos |
| ReadExternalFileTool | github/ReadExternalFileTool.ts | 41 | Leitura de repos GitHub |
| GitHubCommitTool | github/GitHubCommitTool.ts | 55 | Commits via GitHub API |
#### 1.5 Tabelas de Banco (Fase 1)
| Tabela | Função |
|---|---|
| `blackboard_tasks` | Tarefas do quadro negro (status, agente, prioridade, parent) |
| `blackboard_artifacts` | Artefatos gerados (código, specs, relatórios) |
| `blackboard_agent_logs` | Logs estruturados por agente |
---
## FASE 2 - AGENTES ESPECIALIZADOS + GOVERNANÇA
**Status: CONCLUIDO**
### O que foi planejado:
- Criar 5 agentes especializados com papéis distintos
- Implementar camada de governança com políticas de segurança
- Sistema de auditoria imutável
### O que foi executado:
#### 2.1 Agentes Especializados (5 agentes)
| Agente | Arquivo | Linhas | Papel |
|---|---|---|---|
| Architect | agents/ArchitectAgent.ts | 156 | Interpreta requisitos, cria specs técnicas, analisa estrutura existente |
| Generator | agents/GeneratorAgent.ts | 187 | Gera código TypeScript/React de alta qualidade seguindo padrões do projeto |
| Validator | agents/ValidatorAgent.ts | 232 | Valida código com TypeScript real (tsc --noEmit), análise de qualidade, score >= 60 para aprovar |
| Executor | agents/ExecutorAgent.ts | 178 | Prepara código para staging, verifica arquivos protegidos, NÃO aplica direto |
| Evolution | agents/EvolutionAgent.ts | 263 | Aprende com execuções passadas, documenta padrões, sugere melhorias |
#### 2.2 Governança de Segurança (`server/governance/service.ts` - 281 linhas)
**Motor de Políticas (Fail-Closed):**
- Avaliação de políticas antes de qualquer ação
- Se não há política explícita permitindo, a ação é BLOQUEADA
- Integrado automaticamente em BaseBlackboardAgent e ToolManager
**5 Políticas de Segurança:**
| # | Política | Regra |
|---|---|---|
| 1 | Proteção de arquivos críticos | Bloqueia escrita em server/routes.ts, server/index.ts, shared/schema.ts, etc. |
| 2 | Permissões de leitura | Permite leitura de qualquer arquivo do projeto |
| 3 | Bloqueio de comandos destrutivos | Bloqueia rm -rf, DROP TABLE, DELETE FROM, etc. |
| 4 | Aprovação humana para produção | Requer aprovação do usuário antes de deploy |
| 5 | Staging automático com threshold | Score de validação mínimo para ir para staging |
#### 2.3 Registros de Governança
| Componente | Tabela | Função |
|---|---|---|
| Contract Registry | `xos_contract_registry` | Contratos entre agentes (SLAs, dependências) |
| Tool Registry | `xos_tool_registry` | Registro de ferramentas disponíveis (auto-sync do ToolManager) |
| Skill Registry | `xos_skill_registry` | Habilidades aprendidas com tracking de uso |
| Policy Rules | `xos_policy_rules` | Regras de política de segurança |
| Audit Trail | `xos_audit_trail` | Trilha de auditoria imutável de todas as ações |
#### 2.4 API de Governança (`server/governance/routes.ts` - 266 linhas)
| Endpoint | Método | Função |
|---|---|---|
| `/api/governance/evaluate` | POST | Avaliar política para ação |
| `/api/governance/contracts` | GET/POST | Listar/criar contratos |
| `/api/governance/tools` | GET | Listar ferramentas registradas |
| `/api/governance/skills` | GET/POST | Listar/registrar habilidades |
| `/api/governance/policies` | GET/POST | Listar/criar políticas |
| `/api/governance/audit` | GET | Consultar trilha de auditoria |
| `/api/governance/dashboard` | GET | Dashboard com estatísticas |
---
## FASE 3 - JOB QUEUE + METRICS + RESEARCHER + DASHBOARD
**Status: CONCLUIDO**
### O que foi planejado:
- Job Queue PostgreSQL para processamento assíncrono
- Métricas de agentes para monitoramento
- 6o agente (Researcher) para análise de repositórios externos
- Dashboard visual de governança
### O que foi executado:
#### 3.1 Job Queue PostgreSQL (`server/governance/jobQueue.ts` - 252 linhas)
| Feature | Implementação |
|---|---|
| Fila de jobs | Tabela `xos_job_queue` com prioridade (0-10) |
| Claiming atômico | `UPDATE ... FOR UPDATE SKIP LOCKED` (previne race conditions) |
| Retry automático | Máximo de tentativas configurável por job |
| Dead-letter | Jobs que falharam todas tentativas vão para dead-letter |
| Handler registry | Registro de handlers por tipo de job |
| Processamento | Loop de polling com intervalo configurável |
#### 3.2 Métricas de Agentes
| Tabela | Campos | Função |
|---|---|---|
| `xos_agent_metrics` | agentName, tasksCompleted, tasksFailed, avgDuration, lastActive | Monitoramento de performance |
#### 3.3 Researcher Agent (`server/blackboard/agents/ResearcherAgent.ts` - 145 linhas)
- 6o agente especializado em pesquisa
- Analisa repositórios GitHub externos
- Pesquisa padrões de implementação e boas práticas
- Compara soluções e frameworks
- Gera relatórios de viabilidade técnica
#### 3.4 Governance Dashboard (`client/src/pages/XosGovernance.tsx` - 517 linhas)
**Rota:** `/xos/governance`
| Seção | Conteúdo |
|---|---|
| Stats Cards | Contratos, ferramentas, habilidades, políticas, auditorias, jobs |
| Audit Trail | Tabela com ações, decisões, agentes, timestamps |
| Policies | Lista de políticas de segurança ativas |
| Tools | Ferramentas registradas com descrição |
| Skills | Habilidades aprendidas com contagem de uso |
| Jobs | Fila de jobs com status e prioridade |
| Agent Monitoring | Métricas por agente (tarefas, falhas, duração média) |
#### 3.5 APIs Adicionais (Fase 3)
| Endpoint | Método | Função |
|---|---|---|
| `/api/governance/jobs` | GET | Listar jobs da fila |
| `/api/governance/jobs` | POST | Enfileirar novo job |
| `/api/governance/jobs/:id/cancel` | POST | Cancelar job |
| `/api/governance/jobs/:id/retry` | POST | Retentar job |
| `/api/governance/metrics` | GET | Métricas dos agentes |
---
## FASE 4 - PIPELINE AUTÔNOMO (Prompt-to-Production)
**Status: CONCLUIDO**
### O que foi planejado:
- Pipeline Orchestrator que encadeia todos os 6 agentes
- Fluxo: Prompt em Português -> Design -> Codegen -> Validação -> Staging -> Evolução
- Sistema de staging com revisão e aprovação do usuário
- Streaming em tempo real via SSE
- UI para submissão, acompanhamento e aprovação
### O que foi executado:
#### 4.1 Pipeline Orchestrator (`server/blackboard/PipelineOrchestrator.ts` - 350 linhas)
| Feature | Implementação |
|---|---|
| Criação de pipeline | Registra em `xos_dev_pipelines` + cria tarefa principal |
| Fases encadeadas | design -> codegen -> validation -> staging -> evolution |
| Monitoramento | Polling a cada 3s para verificar progresso de cada fase |
| Job handlers | Registra handlers para cada tipo de job (pipeline_*) |
| SSE Broadcasting | Emite eventos em tempo real para clientes conectados |
| Staging review | Pausa no staging para revisão humana antes de aplicar |
| Aplicação de código | Aplica mudanças aprovadas ao filesystem com backup |
| Proteção de arquivos | Verifica lista PROTECTED_FILES antes de aplicar |
**Fluxo completo do pipeline:**
```
1. Usuário envia prompt em português
2. DESIGN: ArchitectAgent analisa e cria especificação técnica
3. CODEGEN: GeneratorAgent gera código TypeScript/React
4. VALIDATION: ValidatorAgent executa tsc --noEmit real
5. STAGING: ExecutorAgent prepara para staging (NÃO aplica direto)
6. REVIEW: Usuário revisa código staged e aprova/rejeita
7. EVOLUTION: EvolutionAgent documenta aprendizados
```
#### 4.2 Tabelas de Banco (Fase 4)
| Tabela | Campos Principais | Função |
|---|---|---|
| `xos_dev_pipelines` | prompt, status, currentPhase, mainTaskId, phases (JSON), error | Rastreamento do pipeline |
| `xos_staging_changes` | pipelineId, filePath, content, action, status, reviewedBy | Código staged para revisão |
#### 4.3 Pipeline API (`server/blackboard/pipelineRoutes.ts` - 134 linhas)
| Endpoint | Método | Função |
|---|---|---|
| `/api/xos/pipeline` | POST | Criar e iniciar pipeline com prompt |
| `/api/xos/pipeline` | GET | Listar pipelines recentes |
| `/api/xos/pipeline/:id` | GET | Detalhes do pipeline (staging, tarefas, artefatos, logs) |
| `/api/xos/pipeline/:id/staging` | GET | Listar mudanças staged |
| `/api/xos/pipeline/:id/approve` | POST | Aprovar e aplicar código staged |
| `/api/xos/pipeline/:id/reject` | POST | Rejeitar código staged |
| `/api/xos/pipeline/:id/stream` | GET | SSE para eventos em tempo real |
#### 4.4 Pipeline UI (`client/src/pages/XosPipeline.tsx` - 471 linhas)
**Rota:** `/xos/pipeline`
| Componente | Funcionalidade |
|---|---|
| Formulário de prompt | Textarea com instruções em português + botão de envio |
| Timeline ao vivo | 5 fases com ícones, cores e status em tempo real |
| Staging review | Preview do código gerado com path dos arquivos |
| Approve/Reject | Botões para aprovar ou rejeitar código staged |
| Pipeline history | Lista de pipelines passados com status badges |
| SSE EventSource | Conexão em tempo real para atualizações |
---
## INVENTÁRIO COMPLETO DE ARTEFATOS
### Arquivos Backend (server/)
| Caminho | Linhas | Fase |
|---|---|---|
| `server/blackboard/BaseBlackboardAgent.ts` | 189 | 1 |
| `server/blackboard/service.ts` | 476 | 1 |
| `server/blackboard/ContextIndexer.ts` | 277 | 1 |
| `server/blackboard/routes.ts` | - | 1 |
| `server/blackboard/agents/index.ts` | 58 | 1/2 |
| `server/blackboard/agents/ArchitectAgent.ts` | 156 | 2 |
| `server/blackboard/agents/GeneratorAgent.ts` | 187 | 2 |
| `server/blackboard/agents/ValidatorAgent.ts` | 232 | 2 |
| `server/blackboard/agents/ExecutorAgent.ts` | 178 | 2 |
| `server/blackboard/agents/EvolutionAgent.ts` | 263 | 2 |
| `server/blackboard/agents/ResearcherAgent.ts` | 145 | 3 |
| `server/blackboard/PipelineOrchestrator.ts` | 350 | 4 |
| `server/blackboard/pipelineRoutes.ts` | 134 | 4 |
| `server/governance/service.ts` | 281 | 2 |
| `server/governance/routes.ts` | 266 | 2/3 |
| `server/governance/jobQueue.ts` | 252 | 3 |
| `server/autonomous/tools/ToolManager.ts` | 171 | 1 |
| `server/autonomous/tools/BaseTool.ts` | 82 | 1 |
| `server/autonomous/tools/**/*.ts` | ~750 | 1 |
| `server/autonomous/agents/**/*.ts` | ~1.003 | 1 |
| **TOTAL BACKEND XOS** | **~5.491** | |
### Arquivos Frontend (client/src/)
| Caminho | Linhas | Fase |
|---|---|---|
| `client/src/pages/XosGovernance.tsx` | 517 | 3 |
| `client/src/pages/XosPipeline.tsx` | 471 | 4 |
| **TOTAL FRONTEND XOS** | **988** | |
### Tabelas de Banco de Dados (34 novas tabelas)
| Tabela | Fase | Propósito |
|---|---|---|
| `blackboard_tasks` | 1 | Tarefas do quadro negro |
| `blackboard_artifacts` | 1 | Artefatos gerados |
| `blackboard_agent_logs` | 1 | Logs de agentes |
| `xos_contract_registry` | 2 | Contratos entre agentes |
| `xos_tool_registry` | 2 | Ferramentas registradas |
| `xos_skill_registry` | 2 | Habilidades aprendidas |
| `xos_policy_rules` | 2 | Políticas de segurança |
| `xos_audit_trail` | 2 | Trilha de auditoria |
| `xos_job_queue` | 3 | Fila de jobs assíncronos |
| `xos_agent_metrics` | 3 | Métricas de agentes |
| `xos_dev_pipelines` | 4 | Pipelines de desenvolvimento |
| `xos_staging_changes` | 4 | Código staged para revisão |
---
## DECISÕES ARQUITETURAIS REGISTRADAS
| # | Decisão | Justificativa |
|---|---|---|
| 1 | SSE em vez de Socket.IO para pipeline | Evita conflito com sockets existentes (WhatsApp, Chat) |
| 2 | FOR UPDATE SKIP LOCKED no job claiming | Previne race conditions com múltiplos workers |
| 3 | Fail-closed na governança | Segurança: ação sem política explícita = BLOQUEADA |
| 4 | Staging obrigatório antes de aplicar | Nunca auto-aplica código em produção |
| 5 | Arquivos protegidos (PROTECTED_FILES) | Lista de arquivos críticos que agentes não podem sobrescrever |
| 6 | Polling de 3s no pipeline monitor | Equilíbrio entre responsividade e carga no servidor |
| 7 | Score >= 60 para validação | Threshold mínimo para código passar pela validação |
| 8 | Prompts em português nos agentes | Sistema projetado para prompts de negócio em português brasileiro |
---
## VERIFICAÇÃO DE FUNCIONAMENTO
| Teste | Status | Evidência |
|---|---|---|
| API Pipeline retorna lista | OK | `GET /api/xos/pipeline` -> `{"success": true, "pipelines": [...]}` |
| Criação de pipeline funciona | OK | `POST /api/xos/pipeline` -> Pipeline #1 criado e rodando |
| Fases iniciam corretamente | OK | Design phase `status: "running"` após criação |
| SSE streaming configurado | OK | Endpoint `/api/xos/pipeline/:id/stream` implementado |
| Staging review configurado | OK | Endpoints approve/reject implementados |
| Governance dashboard funciona | OK | Página `/xos/governance` com 517 linhas de UI |
| Pipeline UI funciona | OK | Página `/xos/pipeline` com 471 linhas de UI |
| Todas as rotas registradas | OK | Verificado em server/routes.ts e client/src/App.tsx |
| 6 agentes rodando | OK | Architect, Generator, Validator, Executor, Evolution, Researcher |
| Job Queue operacional | OK | Atomic claiming com FOR UPDATE SKIP LOCKED |
---
## CONCLUSÃO
O plano XOS v1.0 foi executado com **100% de completude** em suas 4 fases:
- **Fase 1:** Fundação sólida com Blackboard architecture, 10 ferramentas e sistema de coordenação
- **Fase 2:** 5 agentes especializados + governança com 5 políticas de segurança fail-closed
- **Fase 3:** Job Queue PostgreSQL + Métricas + 6o agente Researcher + Dashboard visual
- **Fase 4:** Pipeline autônomo prompt-to-production com staging review e SSE streaming
O sistema está operacional e pronto para uso, com todas as salvaguardas de segurança ativas (staging obrigatório, arquivos protegidos, governança fail-closed, aprovação humana para produção).
---
*Documento gerado em 06/02/2026 como registro de auditoria do plano XOS v1.0 do Arcádia Suite.*

View File

@ -1,235 +0,0 @@
# Avaliação de Aderência - Impacto Geologia
## Arcádia Suite vs Requisitos do Book de Requisitos
### Data: Fevereiro 2026
-## 1. RESUMO EXECUTIVO
Este documento apresenta a análise de aderência entre os requisitos levantados no Book de Requisitos da Impacto Geologia e as funcionalidades atualmente implementadas na plataforma Arcádia Suite. A avaliação considera o estado atual do sistema, identificando o que já está pronto, o que está parcialmente implementado e o que precisa ser desenvolvido.
**Resultado Geral:**
| Status | Quantidade | % |
|--------|-----------|---|
| Implementado | 14 | 38% |
| Parcialmente Implementado | 10 | 27% |
| Não Implementado | 13 | 35% |
| **Total de Requisitos** | **37** | **100%** |
---
## 2. ANÁLISE POR MÓDULO
### 2.1 Módulo Administrativo Financeiro
| ID | Requisito | Status | Observação |
|----|-----------|--------|------------|
| RF-AF01 | Conciliação Bancária Automatizada | Parcial | Existe `finBankAccounts` e `finTransactions` para contas bancárias e transações, mas não há rotina de conciliação automática com extratos OFX/CSV |
| RF-AF02 | Fluxo de Pagamentos Controlado | Parcial | `finAccountsPayable` existe para contas a pagar, mas falta workflow de aprovação (programação → aprovação → execução) |
| RF-AF03 | Controle de Fluxo de Caixa Projetado | Parcial | `finCashFlowCategories` e transações existem, mas falta projeção automática de 90 dias com cenários |
| RF-AF04 | Gestão de Impostos e Obrigações | Implementado | Módulo fiscal completo: `fiscalNcms`, `fiscalCfops`, `fiscalCests`, `fiscalGruposTributacao`, `fiscalConfiguracoes`, `fiscalNotas` com integração Arcádia Plus |
| RF-AF05 | Prestação de Contas Digital | Parcial | `fieldExpenses` existe para despesas de campo, mas falta fluxo de aprovação digital e comprovantes fotográficos via mobile |
| - | Gestão de Ativos e Endividamento | Não Implementado | Não existe módulo de controle de ativos fixos nem dashboard de endividamento |
| - | Gestão de Cartões Corporativos | Não Implementado | Não há funcionalidade para tratar cartões como "contas correntes individuais" com controle de cargas/depósitos |
**Resumo Financeiro: 1 implementado, 4 parciais, 2 não implementados**
---
### 2.2 Módulo Comercial (Vendas e CRM)
| ID | Requisito | Status | Observação |
|----|-----------|--------|------------|
| RF-COM01 | Conversão Proposta → Projeto | Parcial | `crmProposals`, `crmProposalItems`, `crmOpportunities` existem, mas falta a automação de conversão proposta aprovada → criação automática de Projeto + Ordem de Venda |
| RF-COM02 | Gestão de Incentivos/Comissões | Implementado | `crmCommissionRules`, `crmCommissions` no CRM + `retailCommissionPlans`, `retailCommissionClosures` no Retail - sistema completo de comissões |
| RF-COM03 | Pesquisa de Satisfação (NPS) | Não Implementado | Campos de `satisfactionScore` existem em algumas tabelas, mas não há módulo dedicado de envio automático de pesquisa NPS pós-projeto |
**Resumo Comercial: 1 implementado, 1 parcial, 1 não implementado**
---
### 2.3 Módulo Operações e Projetos
| ID | Requisito | Status | Observação |
|----|-----------|--------|------------|
| RF-OP01 | Rastreabilidade por ID de Projeto | Implementado | `pcProjects`, `pcProjectMembers`, `pcProjectActivities`, `pcProjectTasks`, `pcProjectFiles`, `pcProjectHistory` - sistema completo de gestão de projetos com ID único e rastreabilidade |
| RF-OP02 | Gestão de Insumos de Campo | Parcial | Existe estoque (`retailWarehouseStock`, `retailStockMovements`) mas não há vinculação automática de consumo de materiais/equipamentos por projeto |
| RF-OP03 | Digitalização de Fichas de Campo | Parcial | `qualityFieldForms` existe para formulários digitais, mas falta os formulários específicos de geologia (Plano de Amostragem, PT, Monitoramento de Poços) e app mobile dedicado |
| RF-OP04 | Gestão de Pendências de Faturamento | Não Implementado | Não há sistema de monitoramento de eventos de campo que causem quebra de faturamento com alertas automáticos |
| - | Acompanhamento do Ciclo de Vida | Parcial | `pcProjectActivities` e `pcProjectHistory` registram atividades, mas falta pipeline visual desde pré-programação até entrega do relatório final |
| - | Custódia de Equipamentos | Não Implementado | Não existe módulo para controle de custódia de equipamentos por equipe de campo (DOC-010) |
| - | Programação de Equipes de Campo | Não Implementado | Não há funcionalidade de programação de equipes para mobilização de campo (DOC-008) |
| - | Programação de Relatórios | Não Implementado | Não existe módulo de programação e acompanhamento de relatórios técnicos (DOC-009) |
**Resumo Operações: 1 implementado, 3 parciais, 4 não implementados**
---
### 2.4 Módulo Qualidade e Serviços de Terceiros
| ID | Requisito | Status | Observação |
|----|-----------|--------|------------|
| RF-QC01 | Controle de Amostras e Laudos | Implementado | `qualitySamples`, `qualityLabReports` - sistema para rastreamento de amostras enviadas a laboratórios e laudos recebidos |
| RF-QC02 | Gestão de Documentos (QMS) | Implementado | `qualityDocuments`, `qualityDocumentRevisions` - controle de versão e lista mestra de documentos internos (FT-xx) |
| RF-QC03 | Controle de Não Conformidades (RNC) | Implementado | `qualityNonConformities` - registro e gestão de RNC, Ações Corretivas e Oportunidades de Melhoria |
| RF-QC04 | Homologação de Fornecedores (ISO 17025) | Parcial | `suppliers` e `purchaseOrders` existem, mas falta controle de certificações ISO 17025, bloqueio de compras de não-homologados e portal de fornecedores |
| - | Matriz de Treinamentos | Implementado | `qualityTrainingMatrix` - controle de treinamentos, participação e validade |
| - | Controle de Brancos | Não Implementado | Não existe funcionalidade específica para controle de brancos de laboratório (FT-58, FT-68) |
**Resumo Qualidade: 4 implementados, 1 parcial, 1 não implementado**
---
### 2.5 Módulo Fiscal e Tributário
| ID | Requisito | Status | Observação |
|----|-----------|--------|------------|
| RF-FT01 | Módulo Fiscal e Tributário | Implementado | Motor Fiscal completo (Arcádia Fisco): NCMs, CFOPs, CESTs, grupos tributação, certificados digitais, NF-e/NFC-e com integração SEFAZ via nfelib |
| - | Emissão de NF-e de Serviço | Parcial | Infraestrutura fiscal existe, mas NFS-e (Nota Fiscal de Serviço Eletrônica) requer integração com prefeituras, que varia por município - não implementado |
| - | Integração com Arcádia Plus | Implementado | `FiscalAdapter` com conexão via API para transmissão fiscal e emissão de documentos fiscais via Arcádia Plus |
**Resumo Fiscal: 2 implementados, 1 parcial, 0 não implementados**
---
### 2.6 Módulo Estoque e Logística
| ID | Requisito | Status | Observação |
|----|-----------|--------|------------|
| - | Gestão de Estoque | Implementado | `retailWarehouseStock`, `retailStockMovements`, `retailInventories`, `retailInventoryItems` - estoque completo com movimentações e inventário |
| - | Transferências entre Almoxarifados | Implementado | `retailStockTransfers`, `retailStockTransferItems`, `retailTransferSerials` - transferências com rastreio de seriais |
| - | Estoque de Segurança | Não Implementado | Não há funcionalidade de definição de estoque mínimo com alertas automáticos para itens com lead time > 90 dias |
| - | Controle de Km Rodado | Não Implementado | Não existe módulo para controle de custos de km rodado por veículo/equipe |
**Resumo Estoque: 2 implementados, 0 parciais, 2 não implementados**
---
### 2.7 Módulo Gestão de Pessoas (RH)
| ID | Requisito | Status | Observação |
|----|-----------|--------|------------|
| - | Cadastro de Colaboradores | Implementado | `peopleFuncionarios`, `peopleDependentes`, `peopleCargos`, `peopleDepartamentos` |
| - | Folha de Pagamento | Implementado | `peopleFolhaPagamento`, `peopleFolhaItens`, `peopleFolhaEventos`, `peopleEventosFolha` |
| - | Controle de Férias | Implementado | `peopleFerias` |
| - | Controle de Ponto | Implementado | `peoplePonto` |
| - | Benefícios | Implementado | `peopleBeneficios`, `peopleFuncionarioBeneficios` |
**Resumo RH: 5 implementados - módulo completo**
---
### 2.8 Módulo Contabilidade
| ID | Requisito | Status | Observação |
|----|-----------|--------|------------|
| - | Plano de Contas | Implementado | `contabilPlanoContas` |
| - | Centros de Custo | Implementado | `contabilCentrosCusto` - permite vincular custos por projeto |
| - | Lançamentos Contábeis | Implementado | `contabilLancamentos`, `contabilPartidas` |
| - | Períodos e Saldos | Implementado | `contabilPeriodos`, `contabilSaldos` |
**Resumo Contabilidade: 4 implementados - módulo completo**
---
## 3. REQUISITOS NÃO-FUNCIONAIS
| Requisito | Status | Observação |
|-----------|--------|------------|
| Usabilidade/Interface intuitiva | Implementado | Interface moderna React + Tailwind + shadcn/ui, estilo WhatsApp Web |
| Integração via APIs | Implementado | APIs REST completas, protocolos MCP/A2A implementados |
| Segurança e Governança | Implementado | Multi-tenant com tenant scoping, XOS Governance Layer, audit trail |
| Mobile | Não Implementado | Não há aplicativo móvel dedicado; interface web é responsiva mas não tem funcionalidades offline para campo |
| Framework FA (DocTypes customizados) | Implementado | Sistema ArcDocTypes com fields, layouts, pages, scripts e widgets |
---
## 4. PRIORIZAÇÃO - O QUE FALTA PARA FECHAR O PROJETO
### Prioridade ALTA (Essencial para entrega)
| # | Item | Esforço Estimado | Descrição |
|---|------|-----------------|-----------|
| 1 | Conversão Proposta → Projeto automática | 2-3 dias | Workflow que ao aprovar proposta cria automaticamente o Projeto e Ordem de Venda |
| 2 | Workflow de aprovação de pagamentos | 2-3 dias | Fluxo: programação → aprovação (Fran → Sidney) → execução de pagamentos |
| 3 | Conciliação bancária | 3-5 dias | Import de extratos bancários (OFX/CSV) e conciliação automática com transações |
| 4 | Vinculação Insumos ↔ Projetos | 2-3 dias | Associar consumo de materiais e equipamentos a cada projeto |
| 5 | Homologação de Fornecedores ISO 17025 | 3-4 dias | Status de homologação, certificações, bloqueio de compras para não-homologados |
| 6 | Gestão de Pendências de Faturamento | 2-3 dias | Alertas para eventos de campo que impedem faturamento |
### Prioridade MÉDIA (Importante para operação completa)
| # | Item | Esforço Estimado | Descrição |
|---|------|-----------------|-----------|
| 7 | Prestação de contas digital com aprovação | 2-3 dias | Upload de comprovantes, fluxo aprovação de despesas de campo |
| 8 | Projeção de fluxo de caixa 90 dias | 2-3 dias | Dashboard com projeção baseada em recebíveis e pagáveis programados |
| 9 | Formulários de campo específicos | 3-5 dias | Plano de Amostragem, PT, Monitoramento de Poços como DocTypes |
| 10 | Portal de Fornecedores | 3-5 dias | Área externa para fornecedores atualizarem documentos e certificações |
| 11 | NPS automático pós-projeto | 1-2 dias | Envio automático de pesquisa de satisfação ao concluir projeto |
| 12 | Programação de Equipes de Campo | 2-3 dias | Calendário de mobilização de equipes para projetos |
| 13 | Programação de Relatórios | 2 dias | Controle de prazos e status de relatórios técnicos |
| 14 | NFS-e (Nota Fiscal de Serviço) | 5-10 dias | Integração com webservices municipais (varia por prefeitura) |
### Prioridade BAIXA (Desejável / Fase 2)
| # | Item | Esforço Estimado | Descrição |
|---|------|-----------------|-----------|
| 15 | Gestão de Ativos Fixos | 3-5 dias | Cadastro, depreciação e controle de ativos da empresa |
| 16 | Dashboard de Endividamento | 1-2 dias | BI com visão consolidada de endividamento |
| 17 | Gestão de Cartões Corporativos | 2-3 dias | Tratar cartões como contas individuais com cargas |
| 18 | Estoque de Segurança com alertas | 1-2 dias | Definição de estoque mínimo e alertas automáticos |
| 19 | Controle de Km Rodado | 1-2 dias | Registro de km por veículo/equipe com cálculo de custos |
| 20 | Custódia de Equipamentos | 2-3 dias | Controle de equipamentos alocados por equipe de campo |
| 21 | Controle de Brancos de Laboratório | 1-2 dias | Registro e rastreamento de amostras branco |
| 22 | App Mobile para Campo | 15-20 dias | PWA ou app nativo para coleta de dados offline em campo |
| 23 | Contratos Guarda-Chuva | 2-3 dias | Gestão de contratos de fornecimento de longo prazo |
---
## 5. ESTIMATIVA TOTAL DE ESFORÇO
| Prioridade | Itens | Dias Estimados |
|------------|-------|---------------|
| Alta | 6 itens | 14-21 dias |
| Média | 8 itens | 19-33 dias |
| Baixa | 9 itens | 28-42 dias |
| **Total** | **23 itens** | **61-96 dias** |
### Recomendação de Fases:
**Fase 1 (MVP Impacto):** Prioridade Alta → ~3-4 semanas
- Entrega o mínimo necessário para operação integrada
**Fase 2 (Operação Completa):** Prioridade Média → ~4-5 semanas
- Completa todas as funcionalidades operacionais do dia a dia
**Fase 3 (Evolução):** Prioridade Baixa → ~5-7 semanas
- Funcionalidades avançadas e app mobile
---
## 6. O QUE JÁ ESTÁ PRONTO E FUNCIONANDO
A Arcádia Suite já possui uma base sólida implementada:
- **Contabilidade completa** (plano de contas, centros de custo, lançamentos, períodos)
- **Gestão de Pessoas completa** (funcionários, folha, férias, ponto, benefícios)
- **Motor Fiscal completo** (NCMs, CFOPs, CESTs, NF-e/NFC-e, certificados digitais)
- **CRM e Comercial** (leads, oportunidades, propostas, comissões)
- **Gestão de Projetos** (projetos, membros, tarefas, atividades, histórico)
- **Qualidade** (amostras, laudos, RNC, documentos, treinamentos)
- **Estoque** (warehouses, movimentações, transferências, inventário)
- **Financeiro básico** (contas bancárias, a pagar, a receber, transações)
- **Framework FA** (DocTypes customizados para fichas de campo)
- **Multi-tenant com governança** (segurança, audit trail, políticas)
- **Inteligência Artificial** (Manus agent, Dev Center, auto-programação)
- **Comunicação unificada** (WhatsApp, chat interno, email)
- **BI e Dashboards** (motor BI com análise de dados)
---
## 7. CONCLUSÃO
O sistema Arcádia Suite apresenta **aderência de ~65%** aos requisitos da Impacto Geologia, com os módulos estruturais (Contabilidade, RH, Fiscal, Qualidade) praticamente completos. Os principais gaps estão nos **fluxos de negócio específicos** (workflows de aprovação, conciliação bancária, conversão proposta→projeto) e nas **funcionalidades de campo** (formulários específicos de geologia, app mobile, programação de equipes).--
A recomendação é priorizar os 6 itens de alta prioridade para uma entrega funcional em **3-4 semanas**, seguida de evolução gradual nos itens médios e baixos.

View File

@ -1,116 +0,0 @@
# Dev Center — Fábrica de Agentes
**Rota:** `/development` → ícone "Design Studio"
**Componente:** `client/src/pages/DevCenter.tsx` (função `AgentFactoryTabs`)
**Backend:** `server/agent-defs/routes.ts` + `server/blackboard/`
---
## Visão Geral
O Dev Center é a fábrica de agentes do Arcádia Suite. Permite criar, montar, implantar e monitorar agentes de IA de forma visual, seguindo o pipeline:
```
Design → Assemble → Deploy → Galeria
```
Cada agente tem um ciclo de vida com 4 status:
| Status | Descrição |
|--------|-----------|
| `draft` | Rascunho criado, aguardando montagem |
| `assembling` | Sendo montado pelo Blackboard |
| `ready` | Montagem concluída, pronto para deploy |
| `deployed` | Implantado e disponível para execução |
---
## Abas
### Design
Cria e edita definições de agentes (`agent_defs`).
- **Editor de spec** — suporta 3 modos: `markdown`, `typescript`, `visual`
- Salva via `PATCH /api/agent-defs/:id`
- Após salvar, agente fica com status `draft` e aparece na aba Assemble
### Assemble (Linha de Montagem)
Lista agentes em `draft` ou `assembling` e aciona a montagem via Blackboard.
- Botão **Montar**`POST /api/agent-defs/:id/assemble`
- Cria uma `MainTask` no Blackboard com a spec do agente
- Cria subtask `architect` para projetar a estrutura
- **Painel de progresso** (expansível por Task ID):
- Polling a cada 3s em `/api/blackboard/task/:id`
- Mostra subtasks com ícones de status (✓ ✗ ⏳ ⏰) e `agentRole`
- Quando a task completa → status muda para `ready` (ou `draft` se falhou)
### Deploy (Orchestrate Center)
Gerencia agentes `ready` e `deployed`.
- **Stats** no topo: total de agentes / prontos p/ deploy / implantados
- **Deploy**`POST /api/agent-defs/:id/deploy` (só para `ready`)
- Incrementa `version`, muda status para `deployed`
- **Executar**`POST /api/agent-defs/:id/run` (só para `deployed`)
- Cria nova task no Blackboard para execução
- **Re-montar**`POST /api/agent-defs/:id/redraft`
- Volta para `draft` para corrigir e remontar
### Galeria
Grid de agentes `deployed` com busca por nome/descrição.
- Cada card mostra: nome, descrição, versão
- Botão **Executar** — dispara execução imediata
- Botão **Fork**`POST /api/agent-defs/:id/fork` — duplica como novo `draft`
- Botão **Info** (ícone) — dialog com spec completo, modo do editor e data de criação
---
## API Reference
| Método | Endpoint | Descrição |
|--------|----------|-----------|
| `GET` | `/api/agent-defs` | Lista todos os agentes do tenant |
| `POST` | `/api/agent-defs` | Cria novo agente (status: draft) |
| `PATCH` | `/api/agent-defs/:id` | Atualiza nome, descrição ou spec |
| `DELETE` | `/api/agent-defs/:id` | Remove agente |
| `POST` | `/api/agent-defs/:id/assemble` | Inicia montagem via Blackboard |
| `POST` | `/api/agent-defs/:id/deploy` | Implanta agente (ready → deployed) |
| `POST` | `/api/agent-defs/:id/run` | Executa agente implantado |
| `POST` | `/api/agent-defs/:id/fork` | Duplica como draft |
| `POST` | `/api/agent-defs/:id/redraft` | Volta para draft |
---
## Schema
Tabela: `arcadia_agent_defs`
```sql
id serial PRIMARY KEY
tenant_id varchar
user_id varchar REFERENCES users(id)
name varchar(200) NOT NULL
description text
spec jsonb NOT NULL DEFAULT '{}' -- { mode, content }
status varchar(30) DEFAULT 'draft'
version integer DEFAULT 1
last_task_id integer -- FK blackboard_tasks
created_at timestamp
updated_at timestamp
```
Migration: `migrations/phase6_agent_defs.sql`
---
## Integração com Blackboard
O Assemble usa `blackboardService.createMainTask()` + `createSubtask()`.
A spec do agente é passada como `content` da task, com metadata `{ agentDefId, source: "agent-factory" }`.
Quando a task do Blackboard completa ou falha, o polling no frontend (`useEffect` com `setInterval(3000)`) atualiza o status do agente via `PATCH /api/agent-defs/:id`.

View File

@ -1,620 +0,0 @@
# Arcadia Suite - Guia Completo de Hospedagem e Deploy
## Indice
1. [Requisitos do Servidor](#1-requisitos-do-servidor)
2. [Variaveis de Ambiente](#2-variaveis-de-ambiente)
3. [Instalacao](#3-instalacao)
4. [Banco de Dados](#4-banco-de-dados)
5. [Servicos Python](#5-servicos-python)
6. [Metabase (Arcadia Insights)](#6-metabase-arcadia-insights)
7. [Build e Producao](#7-build-e-producao)
8. [Configuracao do Servidor Web (Nginx)](#8-configuracao-do-servidor-web-nginx)
9. [Process Manager (PM2)](#9-process-manager-pm2)
10. [SSL/HTTPS](#10-sslhttps)
11. [Estrutura de Portas](#11-estrutura-de-portas)
12. [Arcadia Plus (Laravel)](#12-arcadia-plus-laravel)
13. [Manutencao e Backups](#13-manutencao-e-backups)
14. [Troubleshooting](#14-troubleshooting)
---
## 1. Requisitos do Servidor
### Minimo Recomendado
- **SO:** Ubuntu 22.04 LTS ou Debian 12
- **CPU:** 4 vCPUs
- **RAM:** 8 GB (16 GB recomendado)
- **Disco:** 50 GB SSD
- **Node.js:** v20.x ou superior
- **Python:** 3.11 ou superior
- **PostgreSQL:** 15 ou superior
- **Java:** OpenJDK 11+ (para Metabase)
- **PHP:** 8.1+ (para Arcadia Plus, opcional)
- **Composer:** 2.x (para Arcadia Plus, opcional)
### Instalacao de Dependencias (Ubuntu/Debian)
```bash
# Atualizar sistema
sudo apt update && sudo apt upgrade -y
# Node.js 20
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Python 3.11
sudo apt install -y python3.11 python3.11-venv python3-pip
# PostgreSQL 15
sudo apt install -y postgresql postgresql-contrib
# Java (para Metabase)
sudo apt install -y openjdk-11-jre-headless
# Ferramentas auxiliares
sudo apt install -y git nginx certbot python3-certbot-nginx build-essential
```
---
## 2. Variaveis de Ambiente
Crie um arquivo `.env` na raiz do projeto:
```env
# === BANCO DE DADOS ===
DATABASE_URL=postgresql://arcadia_user:SUA_SENHA_AQUI@localhost:5432/arcadia_suite
PGHOST=localhost
PGPORT=5432
PGUSER=arcadia_user
PGPASSWORD=SUA_SENHA_AQUI
PGDATABASE=arcadia_suite
# === OPENAI (IA) ===
OPENAI_API_KEY=sk-sua-chave-openai-aqui
# === ERPNext (opcional) ===
ERPNEXT_URL=https://seu-erpnext.com
ERPNEXT_API_KEY=sua-api-key
ERPNEXT_API_SECRET=seu-api-secret
# === GITHUB (opcional - para XOS Pipeline) ===
GITHUB_TOKEN=ghp_seu-token-github
# === PRODUCAO ===
NODE_ENV=production
PORT=5000
SESSION_SECRET=gere-uma-string-secreta-longa-e-aleatoria
# === METABASE ===
MB_DB_TYPE=postgres
MB_DB_DBNAME=arcadia_metabase
MB_DB_PORT=5432
MB_DB_USER=arcadia_user
MB_DB_PASS=SUA_SENHA_AQUI
MB_DB_HOST=localhost
MB_JETTY_PORT=8088
METABASE_PORT=8088
METASET_ADMIN_EMAIL=admin@arcadia.app
METASET_ADMIN_PASSWORD=SuaSenhaMetaSet
```
---
## 3. Instalacao
```bash
# Clonar o repositorio
git clone https://github.com/SEU_USUARIO/arcadia-suite.git
cd arcadia-suite
# Instalar dependencias Node.js
npm install
# Instalar dependencias Python
python3.11 -m venv .venv
source .venv/bin/activate
pip install -r python-service/requirements.txt
pip install fastapi uvicorn pandas numpy httpx pydantic nfelib lxml signxml \
cryptography zeep beautifulsoup4 psycopg2-binary matplotlib pymongo \
python-multipart python-docx pyopenssl
# Copiar arquivo de ambiente
cp .env.example .env
# Edite o .env com suas credenciais
nano .env
```
---
## 4. Banco de Dados
### Criar banco e usuario
```bash
sudo -u postgres psql
```
```sql
-- Criar usuario
CREATE USER arcadia_user WITH PASSWORD 'SUA_SENHA_SEGURA';
-- Criar banco principal
CREATE DATABASE arcadia_suite OWNER arcadia_user;
-- Criar banco do Metabase (separado)
CREATE DATABASE arcadia_metabase OWNER arcadia_user;
-- Permissoes
GRANT ALL PRIVILEGES ON DATABASE arcadia_suite TO arcadia_user;
GRANT ALL PRIVILEGES ON DATABASE arcadia_metabase TO arcadia_user;
\q
```
### Executar migracoes (Drizzle ORM)
```bash
# Sincronizar schema com o banco
npx drizzle-kit push
```
---
## 5. Servicos Python
O projeto utiliza servicos Python para funcionalidades especificas. Eles sao iniciados automaticamente pelo servidor Node.js, mas podem ser executados manualmente para testes:
| Servico | Porta | Funcao | Inicio Automatico |
|---------|-------|--------|--------------------|
| Contabil | 8003 | Motor contabil | Sim |
| BI Engine | 8004 | Business Intelligence | Sim |
| Automations | 8005 | Motor de automacoes | Sim |
| Scientist | 8001 | IA, embeddings, analise de dados | Opcional* |
| Fisco | 8002 | Motor fiscal NF-e/NFC-e | Opcional* |
| Communication | 8006 | Motor de comunicacao | Opcional* |
*Servicos opcionais podem ser ativados adicionando suas chamadas em `server/index.ts`.
Os servicos Contabil, BI e Automations sao iniciados automaticamente pelo `server/index.ts`.
---
## 6. Metabase (Arcadia Insights)
O Metabase e usado como motor de BI invisivel. Para instala-lo:
```bash
# Criar diretorio
mkdir -p metabase/plugins
# Baixar Metabase (versao 0.50.x recomendada)
wget https://downloads.metabase.com/v0.50.31/metabase.jar -O metabase/metabase.jar
# Testar execucao
java -jar metabase/metabase.jar &
```
O Metabase sera executado na porta 8088 e proxied pelo Node.js em `/api/bi/metaset/*` e `/metabase/`.
O script de inicio esta em `metabase/start-metabase.sh` e e gerenciado automaticamente pelo servidor Node.js.
---
## 7. Build e Producao
```bash
# Build do frontend (Vite) + backend
npm run build
# Iniciar em producao
npm run start
```
O comando `npm run start` executa `NODE_ENV=production node dist/index.cjs` na porta 5000.
---
## 8. Configuracao do Servidor Web (Nginx)
Crie o arquivo de configuracao:
```bash
sudo nano /etc/nginx/sites-available/arcadia-suite
```
```nginx
server {
listen 80;
server_name seu-dominio.com.br;
# Redirecionar HTTP para HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name seu-dominio.com.br;
# SSL (sera configurado pelo Certbot)
ssl_certificate /etc/letsencrypt/live/seu-dominio.com.br/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/seu-dominio.com.br/privkey.pem;
# Limites
client_max_body_size 50M;
# Proxy principal (Node.js na porta 5000)
location / {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
# WebSocket (Socket.IO)
location /socket.io/ {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400;
}
# Arcadia Plus (Laravel, opcional)
location /plus/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
```bash
# Ativar o site
sudo ln -s /etc/nginx/sites-available/arcadia-suite /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
---
## 9. Process Manager (PM2)
Use PM2 para manter o servidor rodando:
```bash
# Instalar PM2 globalmente
sudo npm install -g pm2
# Iniciar a aplicacao
pm2 start dist/index.cjs --name arcadia-suite --env production
# Salvar a lista de processos
pm2 save
# Configurar para iniciar no boot
pm2 startup
```
### Arquivo ecosystem (opcional)
Crie `ecosystem.config.cjs`:
```javascript
module.exports = {
apps: [
{
name: "arcadia-suite",
script: "dist/index.cjs",
env: {
NODE_ENV: "production",
PORT: 5000,
},
instances: 1,
max_memory_restart: "1G",
log_date_format: "YYYY-MM-DD HH:mm:ss",
error_file: "logs/error.log",
out_file: "logs/output.log",
},
{
name: "arcadia-metabase",
script: "java",
args: "-jar metabase/metabase.jar",
interpreter: "none",
env: {
MB_DB_TYPE: "postgres",
MB_DB_DBNAME: "arcadia_metabase",
MB_DB_PORT: "5432",
MB_DB_USER: "arcadia_user",
MB_DB_PASS: "SUA_SENHA_AQUI",
MB_DB_HOST: "localhost",
MB_JETTY_PORT: "8088",
},
max_memory_restart: "2G",
},
],
};
```
```bash
# Iniciar com ecosystem
pm2 start ecosystem.config.cjs
pm2 save
```
---
## 10. SSL/HTTPS
```bash
# Certificado gratuito com Let's Encrypt
sudo certbot --nginx -d seu-dominio.com.br
# Renovacao automatica (ja configurada pelo Certbot)
sudo certbot renew --dry-run
```
---
## 11. Estrutura de Portas
| Porta | Servico | Acesso |
|-------|---------|--------|
| 5000 | Node.js (Express + Vite) | Principal - proxied pelo Nginx |
| 8088 | Metabase (Arcadia Insights) | Interno - proxied via /api/bi/metaset e /metabase |
| 8003 | Python Contabil | Interno - inicio automatico |
| 8004 | Python BI Engine | Interno - inicio automatico |
| 8005 | Python Automations | Interno - inicio automatico |
| 8001 | Python Scientist | Interno - opcional |
| 8002 | Python Fisco | Interno - opcional |
| 8006 | Node Communication | Interno - opcional |
| 8080 | Arcadia Plus (Laravel) | Interno - proxied via /plus |
Apenas a porta 80 (HTTP) e 443 (HTTPS) devem estar expostas publicamente via Nginx.
---
## 12. Arcadia Plus (Laravel)
Se voce utiliza o modulo Plus (ERP Laravel):
```bash
cd plus
# Instalar dependencias PHP
composer install --no-dev --optimize-autoloader
# Configurar ambiente
cp .env.example .env
php artisan key:generate
# Configurar banco (usar schema separado no mesmo PostgreSQL)
# Edite plus/.env com as credenciais do banco
# Migracoes
php artisan migrate
# Otimizar para producao
php artisan config:cache
php artisan route:cache
php artisan view:cache
# Servir (porta 8080)
php artisan serve --host=0.0.0.0 --port=8080
```
---
## 13. Manutencao e Backups
### Backup do Banco de Dados
```bash
# Criar backup
pg_dump -U arcadia_user -h localhost arcadia_suite > backup_$(date +%Y%m%d_%H%M%S).sql
# Restaurar backup
psql -U arcadia_user -h localhost arcadia_suite < backup_20260209.sql
```
### Atualizar a Aplicacao
```bash
cd /caminho/para/arcadia-suite
# Parar a aplicacao
pm2 stop arcadia-suite
# Atualizar codigo
git pull origin main
# Reinstalar dependencias
npm install
# Rebuild
npm run build
# Migrar banco (se necessario)
npx drizzle-kit push
# Reiniciar
pm2 restart arcadia-suite
```
### Logs
```bash
# Ver logs em tempo real
pm2 logs arcadia-suite
# Ver logs do Nginx
sudo tail -f /var/log/nginx/error.log
```
---
## 14. Troubleshooting
### Erro: "Cannot find module"
```bash
npm install
npm run build
```
### Erro: "ECONNREFUSED" no banco
```bash
# Verificar se PostgreSQL esta rodando
sudo systemctl status postgresql
sudo systemctl start postgresql
```
### Erro: "Port already in use"
```bash
# Encontrar processo na porta
lsof -i :5000
kill -9 PID_DO_PROCESSO
```
### Servicos Python nao iniciam
```bash
# Verificar Python
python3.11 --version
# Verificar ambiente virtual
source .venv/bin/activate
pip list | grep fastapi
```
### Metabase nao inicia
```bash
# Verificar Java
java -version
# Verificar se metabase.jar existe
ls -la metabase/metabase.jar
# Executar manualmente para ver erros
java -jar metabase/metabase.jar
```
---
## Hospedagem em Cloud (Alternativas)
### VPS Recomendados
- **Hetzner:** A partir de EUR 4/mes (melhor custo-beneficio)
- **DigitalOcean:** A partir de USD 12/mes (4GB RAM)
- **Vultr:** A partir de USD 12/mes
- **AWS EC2:** t3.medium (sob demanda)
- **Contabo:** A partir de EUR 5/mes (alto recurso por preco)
### Deploy com Docker (Alternativa)
Se preferir usar Docker, crie um `Dockerfile`:
```dockerfile
FROM node:20-slim
WORKDIR /app
# Instalar Python e Java
RUN apt-get update && apt-get install -y \
python3.11 python3.11-venv python3-pip \
openjdk-11-jre-headless \
&& rm -rf /var/lib/apt/lists/*
# Copiar e instalar dependencias
COPY package.json ./
RUN npm install --production
# Instalar dependencias Python
COPY python-service/requirements.txt ./python-requirements.txt
RUN python3.11 -m venv /app/.venv && \
/app/.venv/bin/pip install -r python-requirements.txt
# Copiar codigo
COPY . .
# Build
RUN npm run build
EXPOSE 5000
CMD ["npm", "run", "start"]
```
```yaml
# docker-compose.yml
version: "3.8"
services:
app:
build: .
ports:
- "5000:5000"
environment:
- DATABASE_URL=postgresql://arcadia_user:senha@db:5432/arcadia_suite
- NODE_ENV=production
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_USER: arcadia_user
POSTGRES_PASSWORD: senha
POSTGRES_DB: arcadia_suite
volumes:
- pgdata:/var/lib/postgresql/data
metabase:
image: metabase/metabase:v0.50.31
ports:
- "8088:8088"
environment:
MB_DB_TYPE: postgres
MB_DB_DBNAME: arcadia_metabase
MB_DB_PORT: 5432
MB_DB_USER: arcadia_user
MB_DB_PASS: senha
MB_DB_HOST: db
MB_JETTY_PORT: 8088
volumes:
pgdata:
```
---
## Checklist de Deploy
- [ ] Servidor provisionado com requisitos minimos
- [ ] PostgreSQL instalado e configurado
- [ ] Node.js 20 instalado
- [ ] Python 3.11 instalado
- [ ] Repositorio clonado
- [ ] Dependencias instaladas (npm install)
- [ ] Dependencias Python instaladas
- [ ] Arquivo .env configurado com todas as variaveis
- [ ] Banco de dados criado e migracoes executadas
- [ ] Build de producao executado (npm run build)
- [ ] Metabase baixado e configurado
- [ ] PM2 configurado e aplicacao rodando
- [ ] Nginx configurado como reverse proxy
- [ ] SSL/HTTPS configurado com Certbot
- [ ] Firewall configurado (portas 80 e 443 abertas)
- [ ] Backup automatico do banco configurado
- [ ] Dominio apontando para o servidor
---
*Documento gerado em 09/02/2026 - Arcadia Suite v1.0*

View File

@ -1,252 +0,0 @@
---
⚠️ **AVISO: DOCUMENTAÇÃO HISTÓRICA**
Este documento foi criado em **Março/2026** como planejamento estratégico.
Pode conter informações desatualizadas em relação ao sistema atual.
Para documentação técnica atualizada, consulte:
- \_SPEC_NAVBAR.md\_ (componentes UI)
- \_KERNEL_SPEC.md\_ (arquitetura do Kernel)
- Código-fonte em \_server/modules/\_
---
# Índice: PLANEJAMENTO ESTRATÉGICO ARCÁDIA AGENTIC SUITE
**Documento:** `/home/ubuntu/# PLANEJAMENTO ESTRATÉGICO ARCÁDIA.txt`
**Versão:** 1.0 — Março 2026
**Estado:** Pronto para Implementação
---
## 📑 ESTRUTURA DO DOCUMENTO
### 1. **Executive Summary** (Seção 1)
- Visão: ERP tradicional → **Sistema Agêntico Orientado a Objetos**
- 5 decisões arquiteturais críticas (MiroFlow, OpenClaw, Superset, Skills POO, Automações)
### 2. **Arquitetura de Alto Nível** (Seção 2)
```
Interface Unificada (Chat + Dev Center + OpenClaw + BI)
Orquestradores (Manus + Blackboard + Automation Fabric)
Motores Especializados Embutidos (MiroFlow + OpenClaw + Skill Engine)
Infraestrutura (Knowledge Graph + PostgreSQL + Ollama/DeepSeek)
```
### 3. **Componentes Embutidos** (Seção 3+)
| Componente | Status | Local | Propósito |
|-----------|--------|-------|----------|
| **MiroFlow** | ✅ | `server/modules/miroflow/` | Motor científico, benchmarks, análise estatística |
| **OpenClaw** | 🔄 | `server/modules/openclaw/` + `client/src/components/openclaw/` | Detecção padrões, sugestão proativa, skills emergentes |
| **Skills Engine** | ✅ | `server/skills/` | POO: herança, composição, polimorfismo, versionamento |
| **Automation Fabric** | ⏳ | `server/modules/automation/` | Runtime unificado: Workflow, Rule, Agent, Schedule, Event |
| **Dev Center** | ⏳ | `client/src/pages/DevCenter.tsx` | Design → Assemble → Deploy de agentes |
| **Superset Integration** | ⏳ | `server/modules/superset/` | BI visual + MiroFlow bridge |
### 4. **Skills Engine POO** (Seção 4)
- ✅ Herança: `extends`, `isA`
- ✅ Composição: `dependencies`, `requires`
- ✅ Polimorfismo: `useMiroFlow`, `autoApprove`
- ✅ Versionamento Git-like (createVersion, listVersions, rollback, fork, diff)
- ✅ Marketplace / Biblioteca (import de skills públicas)
### 5. **Automation Fabric Unificada** (Seção 6)
- Runtime com suporte: Workflow, Rule, Agent, Schedule, Event
- Trigger-based (webhook, database, schedule, event)
- Integração com Blackboard para codegen
### 6. **Dev Center Completo** (Seção 8)
- **Design Studio**: UML, Visual, Markdown
- **Assemble Line**: Integração com Blackboard
- **Orchestrate Center**: Deploy, monitoramento
- **Galeria de Agentes**: Marketplace interno
### 7. **Infraestrutura** (Seção 9)
- PostgreSQL 16 + pgvector
- Neo4j (Knowledge Graph)
- Ollama (LLMs locais: deepseek-r1:32b/70b, llama3.1:8b/70b)
- Apache Superset (BI visual)
---
## 🗺️ ROADMAP E STATUS
### ✅ Fase 1: Fundação (Semanas 1-2)
**Status: CONCLUÍDA (2026-03-25)**
- Submodules MiroFlow + OpenClaw
- Tabelas skills, skill_executions
- Neo4j no Docker
- ReferenceParser/Resolver
- Superset + Ollama
**Entregável:** Infraestrutura base ✅
---
### ✅ Fase 2: Skills Engine (Semanas 3-4)
**Status: CONCLUÍDA (25/03/2026 - 15:04)**
- SkillEngine com herança/composição
- API REST CRUD + execução + histórico
- Editor Monaco (tabs Geral/Body/Params)
- Versionamento Git-like (VersionManager)
- Skill Marketplace / Biblioteca
**Entregável:** Skills criáveis, versionáveis, executáveis ✅
---
### ✅ Fase 3: MiroFlow Embutido (Semanas 5-6)
**Status: CONCLUÍDA PARCIAL (25/03/2026 - 15:04)**
**Implementado:**
- Adapt MiroFlow para Ollama local (llama3.2:3b / deepseek-r1:14b)
- Containerização em docker-compose.prod.yml
- Agentes especializados (Statistician, Auditor, Researcher)
- Proxy Node.js: `/api/miroflow/analyze`
- UI MiroFlowControl (tab Científico em /insights)
**Pendente:**
- [ ] MiroFlowBridge para Superset
- [ ] Integração com Arcádia Audit (imutabilidade)
- [ ] Dashboard de benchmarks no Dev Center
- [ ] **Tool SQL via Skills** (skill `db_query` como MCP tool)
**Entregável:** Análises científicas (ETA completo: 08/04/2026)
---
### 🔄 Fase 4: OpenClaw Embutido (Semanas 7-8)
**Status: SPRINT 1 COMPLETO (26/03/2026) | Sprints 2-3 Em Progresso**
#### ✅ Sprint 1: Backend (COMPLETO - 26/03)
- [x] PatternDetector.ts — detecção (3+ em 30d), confiança, Neo4j
- [x] OpenClawEngine.ts — orquestração, sugestões, scheduler, Socket.IO
- [x] SkillEmergence.ts — Blackboard codegen, validação, DRAFT storage
- [x] routes.ts — 10 endpoints REST
- [x] arcadia.config.yaml — pattern detection, suggestions, emergence
#### ⏳ Sprint 2: Frontend (PRÓXIMO - 28-29/03)
- [ ] OpenClawWidget.tsx — widget flutuante de sugestão
- [ ] SkillSuggestion.tsx — modal de sugestão
- [ ] useAgentEmergence.ts — hook de lógica
- [ ] Integração em App.tsx (layout principal)
#### ⏳ Sprint 3: Config + Testes (30-31/03)
- [ ] Schema database (detectedPatterns, skillSuggestions)
- [ ] Migration SQL (migrations/0004_openclaw_tables.sql)
- [ ] Testes unitários
- [ ] Documentação
**Fluxo:** Padrão → Detector → Engine → Sugestão → Widget → User → Blackboard → SkillEmergence → DRAFT → Dev Center → Publicada
**Entregável:** Skills emergentes (ETA: 08/04/2026)
---
### ⏳ Fase 5: Automation Fabric (Semanas 9-10)
**Status: PENDENTE**
- [ ] Unificar XOS/Automations + Central /automations
- [ ] 4 runtimes: Workflow, Rule, Agent, Schedule, Event
- [ ] UI única no Dev Center
- [ ] Migração dados existentes
- [ ] Testes carga/segurança
**Entregável:** Automações unificadas
---
### ⏳ Fase 6: Dev Center Completo (Semanas 11-12)
**Status: PENDENTE**
- [ ] Design Studio (UML, Visual, Markdown)
- [ ] Assemble Line (Blackboard integration)
- [ ] Orchestrate Center (deploy, monitoramento)
- [ ] Galeria de Agentes (marketplace interno)
- [ ] Documentação e treinamento
**Entregável:** Sistema completo operacional
---
## 🎯 DECISÕES CRÍTICAS (RESUMO)
| Decisão | Escolha | Justificativa |
|---------|---------|---------------|
| **MiroFlow** | **EMBUTIR** | Motor científico nativo, benchmarks, verificação multi-camada |
| **OpenClaw** | **EMBUTIR** | Widget interno, detecção contextual no Arcádia |
| **Superset** | **INTEGRAR** | BI visual mantido, bridge para MiroFlow |
| **Skills** | **POO** | Herança, composição, polimorfismo para reuso |
| **Automações** | **UNIFICAR** | Runtime único, múltiplos orquestradores |
| **Dev Center** | **FÁBRICA** | Design → Assemble → Deploy de agentes |
| **Multi-tenant** | **HIERARQUIA** | System → Tenant → Company → User skills |
---
## 📋 PRÓXIMO PASSO
### **Fase 4 Sprint 2: OpenClaw Frontend**
**ETA: 28-29/03/2026**
A Fase 4 Sprint 1 (backend) foi concluída em 26/03. Agora entrar na **Sprint 2 do frontend**, que consiste em:
#### Tarefas Sprint 2:
1. **OpenClawWidget.tsx** — Widget flutuante que:
- Escuta eventos de sugestões via Socket.IO
- Mostra notificação "Detectei padrão!"
- Abre SkillSuggestion modal ao ser clicado
2. **SkillSuggestion.tsx** — Modal que:
- Exibe padrão detectado (frequência, confiança)
- Preview do código da skill sugerida (gerado por Blackboard)
- Botões: Aprovar (→ DRAFT em Dev Center) ou Rejeitar
3. **useAgentEmergence.ts** — Hook que:
- Conecta a Socket.IO `/api/openclaw` events
- Gerencia estado de sugestões
- Sincroniza com OpenClawEngine backend
4. **Integração em App.tsx**:
- Importar OpenClawWidget
- Renderizar no layout principal (canto inferior direito)
- Garantir que não bloqueia interação com página
#### Localização dos arquivos:
```
client/src/
├── components/
│ └── openclaw/
│ ├── OpenClawWidget.tsx ← NOVO
│ ├── SkillSuggestion.tsx ← NOVO
│ └── ...
├── hooks/
│ └── useAgentEmergence.ts ← NOVO
└── App.tsx ← MODIFICAR (integração)
```
#### Integração com backend já pronto:
- PatternDetector.ts emite eventos via Socket.IO
- OpenClawEngine.ts orquestra sugestões
- SkillEmergence.ts gera código via Blackboard
- Todas as rotas REST já existem
**Próximo passo após Sprint 2:** Sprint 3 (Config + Testes), depois Fase 5 (Automation Fabric).
---
## 🔗 LINKS ESSENCIAIS
| Recurso | URL |
|---------|-----|
| **MiroFlow Docs** | https://github.com/MiroMindAI/MiroFlow/tree/main/docs |
| **MiroFlow Paper** | https://arxiv.org/abs/2602.22808 |
| **OpenClaw README** | https://github.com/miaoxworld/OpenClawInstaller/blob/main/README.md |
| **Superset Docs** | https://superset.apache.org/docs/intro |
| **Neo4j Docs** | https://neo4j.com/docs/ |
| **Ollama Docs** | https://github.com/ollama/ollama/blob/main/README.md |
---
**Pronto para começar Fase 4 Sprint 2?**

View File

@ -1,255 +0,0 @@
# Análise de Gap - Arcadia Kernel
**Data:** 09/04/2026
**Objetivo:** Comparar funcionalidades implementadas vs. sugeridas
---
## 🔴 Root Cause Analysis: Learning Service Timeouts (RESOLVIDO)
**Problema:** `PYTHON_SERVICE_URL` sendo resetado para porta 8001 em vez de 8000
**Causa Raiz Identificada:** O arquivo `.env.example` na linha 30 continha:
```bash
PYTHON_SERVICE_URL=http://localhost:8001 # ← ERRADO
```
Quando o Coolify recriava o container, ele usava o valor do `.env.example` como fallback, causando:
- Timeouts de 10s a cada 60s (ciclo do Learning Service)
- Degradação de performance do sistema
- Erros silenciosos no log
**Solução Aplicada (08/04/2026):** Corrigido `.env.example` para usar porta 8000 com comentário explicativo.
**Lição Aprendida:** O `.env.example` deve sempre estar sincronizado com a configuração de produção correta.
---
## 📊 Resumo das Funcionalidades
| Funcionalidade | Status | Implementação | Notas |
|----------------|--------|---------------|-------|
| **Service Discovery** | ✅ **Parcial** | `ServiceRegistry` + 4 providers | Docker, Coolify, XOS, Static |
| **Load Balancer** | ❌ **Não tem** | - | Não implementado |
| **Circuit Breaker** | ❌ **Não tem** | - | Não implementado |
| **Config Hot Reload** | ❌ **Não tem** | - | Requer restart do Kernel |
| **Resource Limits** | ⚠️ **Parcial** | Via `ProcessManager` | Limites básicos de processo |
| **Secrets Management** | ❌ **Não tem** | - | Variáveis em `.env` |
---
## ✅ O que Temos Implementado
### 1. Service Discovery (Parcial ✅)
**Arquivos:**
- `server/kernel/registry/ServiceRegistry.ts`
- `server/kernel/discovery/*`
**Providers Implementados:**
- `DockerDiscovery` - Descobre containers Docker
- `CoolifyDiscovery` - Integração com Coolify
- `XOSDiscovery` - Descobre serviços XOS
- `StaticDiscovery` - Configuração estática
**Funcionalidades:**
- ✅ Discovery automático (30s intervalo)
- ✅ Health checks (15s intervalo)
- ✅ Cleanup de serviços stale (5min)
- ✅ EventEmitter para mudanças
- ❌ **Falta:** Service mesh, load balancing entre instâncias
### 2. Health Monitor ✅
**Arquivo:** `server/kernel/core/HealthMonitor.ts`
**Funcionalidades:**
- ✅ Health checks HTTP periódicos
- ✅ Configurável (interval, timeout, retries)
- ✅ Eventos onHealthChange
- ✅ Tracking de falhas/sucessos consecutivos
### 3. Process Manager ✅
**Arquivo:** `server/kernel/core/ProcessManager.ts`
**Funcionalidades:**
- ✅ Gerenciamento de processos filhos
- ✅ Logs aggregator
- ✅ State tracking
- ⚠️ **Básico:** Resource limits ( apenas via SO)
### 4. Log Aggregator ✅
**Arquivo:** `server/kernel/core/LogAggregator.ts`
**Funcionalidades:**
- ✅ Centralização de logs
- ✅ Limite de linhas (configurável)
- ✅ Query/filter básico
### 5. Dashboard Web ✅
**Arquivo:** `server/kernel/dashboard/DashboardServer.ts`
**Funcionalidades:**
- ✅ Interface web (porta 5001)
- ✅ WebSocket para tempo real
- ✅ Visualização de serviços
---
## ❌ O que Falta Implementar
### 1. Load Balancer ❌
**Prioridade:** Alta
**Descrição:** Distribuir carga entre instâncias de serviços
**Casos de Uso:**
- Múltiplas instâncias do mesmo serviço
- Failover automático
- Distribuição por região
**Implementação Sugerida:**
```typescript
// server/kernel/balancer/LoadBalancer.ts
interface LoadBalancer {
selectInstance(serviceId: string): RegisteredService;
registerStrategy(strategy: BalanceStrategy): void;
}
```
### 2. Circuit Breaker ❌
**Prioridade:** Alta
**Descrição:** Parar de chamar serviço falho após N falhas
**Estados:**
- CLOSED: Funcionando normal
- OPEN: Circuito aberto (rejeita chamadas)
- HALF_OPEN: Testando se voltou
**Implementação Sugerida:**
```typescript
// server/kernel/circuit/CircuitBreaker.ts
interface CircuitBreaker {
call<T>(fn: () => Promise<T>): Promise<T>;
getState(): 'CLOSED' | 'OPEN' | 'HALF_OPEN';
}
```
### 3. Config Hot Reload ❌
**Prioridade:** Média
**Descrição:** Mudar configuração sem restartar Kernel
**Funcionalidades:**
- Watch de arquivo `services.json`
- Aplicar mudanças em runtime
- Notificar serviços afetados
**Implementação Sugerida:**
```typescript
// server/kernel/config/ConfigWatcher.ts
interface ConfigWatcher {
watch(path: string, onChange: (config: Config) => void): void;
}
```
### 4. Resource Limits (Melhorar) ⚠️
**Prioridade:** Média
**Status:** Básico via SO (ulimit)
**O que falta:**
- Limite de CPU por serviço (cgroups)
- Limite de memória por serviço
- Throttling de requisições
- Quotas por tenant
### 5. Secrets Management ❌
**Prioridade:** Alta
**Status:** Variáveis em `.env` (inseguro)
**Funcionalidades Necessárias:**
- Vault integrado (HashiCorp Vault style)
- Encryption at rest
- Rotacionamento automático
- Acesso por política (RBAC)
- Audit logging
**Implementação Sugerida:**
```typescript
// server/kernel/secrets/SecretVault.ts
interface SecretVault {
get(key: string): Promise<string>;
set(key: string, value: string): Promise<void>;
rotate(key: string): Promise<void>;
}
```
---
## 📋 Recomendações de Prioridade
### Fase 1 - Crítico (Segurança + Estabilidade)
1. **Secrets Management** - Dados sensíveis expostos em `.env`
2. **Circuit Breaker** - Evitar cascata de falhas
### Fase 2 - Performance
3. **Load Balancer** - Necessário para escalar
4. **Resource Limits** - Prevenir resource exhaustion
### Fase 3 - DX (Developer Experience)
5. **Config Hot Reload** - Evitar restart em produção
---
## 🔧 Notas Técnicas
### Service Discovery Atual
```
ServiceRegistry
├── DockerDiscovery (containers)
├── CoolifyDiscovery (apps Coolify)
├── XOSDiscovery (serviços XOS)
└── StaticDiscovery (config manual)
```
**Limitação:** Não faz load balancing entre múltiplas instâncias do mesmo serviço.
### Health Check Atual
- Interval: 15s
- Timeout: 5s
- Retries: 3
- **Falta:** Circuit breaker baseado em health
### Process Manager Atual
- Gerencia processos via Node.js spawn
- **Falta:** Isolamento real (containers), resource limits
---
## 📁 Arquivos Relacionados
| Componente | Arquivo |
|------------|---------|
| Entry Point | `server/kernel/index.ts` |
| Service Registry | `server/kernel/registry/ServiceRegistry.ts` |
| Health Monitor | `server/kernel/core/HealthMonitor.ts` |
| Process Manager | `server/kernel/core/ProcessManager.ts` |
| Discovery | `server/kernel/discovery/*.ts` |
| Dashboard | `server/kernel/dashboard/DashboardServer.ts` |
---
## 🎯 Próximos Passos Sugeridos
1. **Criar especificação técnica** para cada funcionalidade faltante
2. **Priorizar** baseado em necessidades de produção
3. **Implementar** gradualmente, começando por Secrets e Circuit Breaker
**Responsável:** Equipe de Platform/Infra
**Timeline:** 2-4 semanas por funcionalidade

View File

@ -1,365 +0,0 @@
# Especificação Técnica: Arcadia Kernel
> **Versão:** 3.3
> **Status:** ✅ Produção
> **Última atualização:** 2026-04-08
---
## 1. Visão Geral
O **Arcadia Kernel** é o sistema operacional nativo da ArcadiaSuite - um orquestrador de serviços em Node.js que unifica discovery, health monitoring e gerenciamento de processos.
### 1.1 Objetivos
- **Discovery**: Descobrir automaticamente todos os serviços do ecossistema
- **Orquestração**: Iniciar, parar e reiniciar serviços (processos e containers)
- **Observabilidade**: Health checks, logs e métricas centralizados
- **Integração**: Unificar visão da Casa de Máquinas com dados reais
### 1.2 Arquitetura
```
┌─────────────────────────────────────────────────────────────────────────┐
│ ARCADIA APPLICATION (5000) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ARCADIA KERNEL (5001) │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ │
│ │ │ Dashboard │ │ Process │ │ LogAggregator │ │ │
│ │ │ (Web UI) │ │ Manager │ │ │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └─────────────────────────┘ │ │
│ │ │ │ │ │
│ │ └────────────────┴────────────────┐ │ │
│ │ │ │ │ │
│ │ ┌───────────┴───────────┐ │ │ │
│ │ │ Service Registry │◄──┼──┐ │ │
│ │ │ (Unified Discovery) │ │ │ │ │
│ │ └───────────┬───────────┘ │ │ │ │
│ │ │ │ │ │ │
│ │ ┌─────────────────────┼───────────────┘ │ │ │
│ │ │ │ │ │ │
│ │ ┌──┴──┐ ┌──────────┐ ┌┴─────────┐ ┌────┴─────┐ ┌────────┐ │ │
│ │ │Static│ │ Coolify │ │ XOS │ │ Docker │ │ ... │ │ │
│ │ │Disc. │ │ Disc. │ │ Disc. │ │ Disc. │ │ │ │ │
│ │ └──┬──┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └───┬────┘ │ │
│ │ │ │ │ │ │ │ │
│ │ └──────────┴─────────────┴─────────────┴────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────┴──────────┐ │ │
│ │ │ UnifiedRegistry │ │ │
│ │ │ (Agregação + Dedu) │ │ │
│ │ └──────────┬──────────┘ │ │
│ └─────────────────────────┼─────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────┴──────────────────┐ │
│ │ global.arcadiaKernelRegistry │ │
│ │ (Acesso direto - mesmo processo) │ │
│ └──────────────────┬──────────────────┘ │
│ │ │
│ ┌───────────────────────┴───────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ Casa de │ │ /api/engine-room/* │ │
│ │ Máquinas │ │ (API Express) │ │
│ │ (React UI) │ │ │ │
│ └─────────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
Legenda:
───► HTTP/HTTPS ─ ─► Acesso direto (memória)
Porta 5000: App principal Porta 5001: Kernel Dashboard (interno)
```
---
## 2. Componentes
### 2.1 Core (ProcessManager, HealthMonitor, LogAggregator)
**Status:** ✅ Estável
- **ProcessManager**: Gerencia processos Node.js/Python locais
- **HealthMonitor**: Health checks periódicos
- **LogAggregator**: Coleta e armazena logs
### 2.2 Service Registry
**Status:** ✅ Implementado
Catálogo central de serviços com:
- Registro manual ou automático
- Health status tracking
- Capabilities e metadados
- Eventos (service:registered, service:health_changed, etc)
### 2.3 Discovery Providers
| Provider | Status | Descrição |
|----------|--------|-----------|
| `StaticDiscovery` | ✅ | DNS interno (nomes fixos) |
| `CoolifyDiscovery` | ✅ | API Coolify (produção) |
| `DockerDiscovery` | ✅ | Docker labels (fallback) |
| `XOSDiscovery` | ✅ | Banco XOS (filas, integrações) |
| `UnifiedRegistry` | ⏳ | Agregador (Semana 5) |
---
## 3. Discovery Providers
### 3.1 StaticDiscovery
Descobre serviços via nomes DNS fixos na rede Docker.
```typescript
// Ex: http://contabil:8003, http://bi:8004
```
### 3.2 CoolifyDiscovery
Consulta API Coolify para descobrir serviços em produção.
**Endpoints:**
- `GET /api/v1/services` - Docker Compose services
- `GET /api/v1/applications` - Aplicações
**Filtro:** `arcadia.discovery.enabled=true`
### 3.3 DockerDiscovery
Lê labels de containers Docker na rede local.
**Labels:**
- `arcadia.discovery.enabled=true`
- `arcadia.name=MetaSet BI`
- `arcadia.type=python`
- `arcadia.port=8100`
- `arcadia.capabilities=dashboards,charts,sql_lab`
### 3.4 XOSDiscovery
Consulta banco de dados para descobrir serviços XOS.
**Descobre:**
- Filas de atendimento (`xos_queues`)
- Integrações WhatsApp (`whatsapp_sessions`)
- Automações ativas (`xos_automations`)
---
## 4. API do Kernel
### 4.1 Endpoints Core
| Endpoint | Método | Descrição |
|----------|--------|-----------|
| `/health` | GET | Health check do Kernel |
| `/api/kernel/services` | GET | Lista serviços do ProcessManager |
| `/api/kernel/services/:id/start` | POST | Inicia serviço |
| `/api/kernel/services/:id/stop` | POST | Para serviço |
| `/api/kernel/services/:id/restart` | POST | Reinicia serviço |
| `/api/kernel/services/:id/logs` | GET | Logs do serviço |
### 4.2 Endpoints Registry
| Endpoint | Método | Descrição |
|----------|--------|-----------|
| `/api/registry/services` | GET | Lista serviços descobertos |
| `/api/registry/services/:id` | GET | Detalhes de um serviço |
| `/api/registry/by-category/:cat` | GET | Serviços por categoria |
| `/api/registry/by-capability/:cap` | GET | Serviços por capability |
| `/api/registry/stats` | GET | Estatísticas do registry |
| `/api/registry/discover` | POST | Força discovery manual |
### 4.3 Endpoints Casa de Máquinas
| Endpoint | Método | Descrição |
|----------|--------|-----------|
| `/api/engine-room/status` | GET | Status unificado (Registry + Presets) |
| `/api/engine-room/engine/:name/health` | GET | Health de um motor |
| `/api/engine-room/engine/:name/start` | POST | Inicia motor |
| `/api/engine-room/engine/:name/stop` | POST | Para motor |
| `/api/engine-room/engine/:name/restart` | POST | Reinicia motor |
| `/api/engine-room/engine/:name/logs` | GET | Logs do motor |
---
## 5. Integração Casa de Máquinas
### 5.1 Fluxo de Dados
```
Casa de Máquinas (React)
/api/engine-room/status
┌─────────────────────────────┐
│ fetchRegistryServices() │
│ │
│ PRIORIDADE 1: Registry │
│ Global (mesmo processo) │
│ ───────────────────────── │
│ (global.arcadiaKernel │
│ .serviceRegistry) │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Acesso │ │
│ │ direto │ │
│ │ (memória) │ │
│ └──────┬──────┘ │
│ │ │
│ Se falhar: │
│ ───────────────────────── │
│ PRIORIDADE 2: HTTP │
│ Fallback para porta 5001 │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ http.get │ │
│ │ 127.0.0.1 │ │
│ │ localhost │ │
│ └──────┬──────┘ │
└─────────┼───────────────────┘
Resposta JSON
Frontend Render
```
### 5.2 Implementação Atual
```typescript
// server/engine-room/routes.ts
async function fetchRegistryServicesWithRetry(): Promise<any[] | null> {
// PRIORIDADE 1: Registry global (mesmo processo Node.js)
const globalRegistry = (global as any).arcadiaKernelRegistry;
if (globalRegistry) {
const services = globalRegistry.getServices();
console.log(`[Engine Room] Usando Registry global: ${services.length} serviços`);
return services;
}
// PRIORIDADE 2: HTTP request para porta 5001 (fallback)
console.log('[Engine Room] Registry global não disponível, tentando HTTP...');
// ... retry com 127.0.0.1 e localhost
}
```
#### Vantagens do Registry Global
| Aspecto | HTTP (antigo) | Registry Global (novo) |
|---------|---------------|------------------------|
| Latência | ~10-50ms | ~0.1ms |
| Confiabilidade | Problemas com loopback Docker | 100% (mesmo processo) |
| Complexidade | Retry, timeout, hostname | Acesso direto |
| Failover | Necessário | Não aplicável |
#### Inicialização no server/index.ts
```typescript
// Inicia Kernel ANTES do servidor HTTP
const kernel = new ArcadiaKernel({
dashboard: { enabled: true, port: 5001, host: '0.0.0.0' },
autoStart: process.env.KERNEL_AUTOSTART === 'true',
});
await kernel.start();
// Exporta Registry globalmente
if (kernel.serviceRegistry) {
(global as any).arcadiaKernelRegistry = kernel.serviceRegistry;
}
// DEPOIS inicia servidor HTTP (porta 5000)
httpServer.listen({ port: 5000, host: "0.0.0.0" }, ...);
```
---
## 6. Configuração
### 6.1 Variáveis de Ambiente
```bash
# Kernel
KERNEL_ENABLED=true
KERNEL_AUTOSTART=true
# Coolify Discovery
COOLIFY_API_TOKEN=seu_token_aqui
# MetaSet
METASET_HOST=metaset
METASET_PORT=8100
# Modo Docker (desativa spawn de processos)
DOCKER_MODE=true
```
### 6.2 Docker Compose Labels
```yaml
services:
metaset:
labels:
- "arcadia.discovery.enabled=true"
- "arcadia.name=MetaSet BI"
- "arcadia.type=python"
- "arcadia.category=bi"
- "arcadia.port=8100"
- "arcadia.capabilities=dashboards,charts,sql_lab,rls,embedding"
```
---
## 7. Roadmap
### Semana 4 ✅ CONCLUÍDO
- [x] Service Registry
- [x] StaticDiscovery
- [x] CoolifyDiscovery
- [x] DockerDiscovery
- [x] Integração com Casa de Máquinas
### Semana 5 ✅ CONCLUÍDO
- [x] XOSDiscovery
- [x] Registry Global (acesso direto)
- [x] Integração Casa de Máquinas via memória
- [ ] UnifiedRegistry (agregador) - Opcional
### Semana 6 PLANEJADO
- [ ] Maestro IA (Guardião de IAs)
- [ ] Policy Engine
- [ ] Audit Trail
### Semana 7 PLANEJADO
- [ ] Integração completa Maestro
- [ ] Documentação P2P
---
## 8. Histórico de Mudanças
| Data | Versão | Mudança |
|------|--------|---------|
| 2026-04-06 | 3.0 | Service Registry + Discovery |
| 2026-04-07 | 3.1 | Integração Casa de Máquinas |
| 2026-04-08 | 3.2 | Coolify Discovery, XOS Discovery, correção MetaSet |
| 2026-04-08 | 3.3 | **Registry Global** - Casa de Máquinas usa acesso direto (memória) em vez de HTTP |
| 2026-04-08 | 3.4 | **MetaSet Consolidado** - Migração Metabase → Apache Superset 4.1.0 |
---
## 9. Referências
- `/server/kernel/` - Código fonte do Kernel
- `/server/kernel/registry/` - Service Registry
- `/server/kernel/discovery/` - Discovery Providers
- `/server/engine-room/` - Casa de Máquinas (backend)
- `/client/src/pages/Admin.tsx` - Casa de Máquinas (frontend)
- `/home/ubuntu/.kimi/kernel e tenants.md` - Planejamento executivo

View File

@ -1,885 +0,0 @@
# Arcádia Automações — Mapa Completo do Motor de Automação
> Mapa da arquitetura de automações da Arcádia Suite: Automation Engine
> (FastAPI :8005), Scheduler (Cron), Event Bus (Pub/Sub), Workflow
> Executor (multi-step), Service Node.js (fallback), XOS Automations
> (CRM), BPMN Viewer, e integração com Manus IA.
> Atualizado em: Março 2026
---
## 1. Visão Geral
O sistema de automações opera em **duas camadas** coordenadas: um motor Python (FastAPI) que executa workflows complexos com scheduler e event bus, e um serviço Node.js que gerencia o CRUD das automações e funciona como fallback de agendamento.
```
┌─────────────────────────────────────────────────────────────────────┐
│ ARCÁDIA AUTOMAÇÕES — STACK COMPLETO │
│ │
│ ┌─────────── Frontend ──────────────────────────────────────┐ │
│ │ │ │
│ │ Automations.tsx XosAutomations.tsx WorkflowBuilder │ │
│ │ (559 linhas) (350 linhas) (493 linhas) │ │
│ │ CRUD + Trigger CRM workflows Visual editor │ │
│ │ │ │
│ │ BpmnPage.tsx BpmnDiagram.tsx ProcessDiagram │ │
│ │ (12 linhas) (645 linhas) (184 linhas) │ │
│ │ BPMN viewer Diagrama visual Fluxos visuais │ │
│ └────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌─────────── Gateway (Node.js :5000) ───────────────────────┐ │
│ │ │ │ │
│ │ /api/automations/* ←───┤ CRUD + Execução (Drizzle) │ │
│ │ (routes.ts — 343L) │ │ │
│ │ │ │ │
│ │ /api/automation-engine/* ←┤ Proxy → FastAPI :8005 │ │
│ │ (engine-proxy.ts — 267L) │ │ │
│ │ │ │ │
│ │ AutomationService ←┤ Executor + Scheduler Node │ │
│ │ (service.ts — 209L) │ (polling 60s fallback) │ │
│ └────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌─────────── Motor Python (FastAPI :8005) ──────────────────┐ │
│ │ │ │ │
│ │ automation_engine.py (668 linhas) │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │
│ │ │ Scheduler│ │Event Bus │ │Workflow Executor │ │ │
│ │ │ (Cron) │ │(Pub/Sub) │ │(Multi-step) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ Entries │ │ Subscribe│ │ Condition │ │ │
│ │ │ Start/ │ │ Emit │ │ Action │ │ │
│ │ │ Stop │ │ History │ │ Delay │ │ │
│ │ │ 30s loop │ │ 500 max │ │ SQL Query │ │ │
│ │ │ │ │ │ │ HTTP Request │ │ │
│ │ │ │ │ │ │ Transform │ │ │
│ │ │ │ │ │ │ Notify │ │ │
│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────── PostgreSQL ────────────────────────────────────┐ │
│ │ automations, automation_actions, automation_logs, │ │
│ │ scheduled_tasks, xos_automations │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 2. Automation Engine (Python FastAPI — :8005)
O motor Python é o coração do sistema de automações. Roda como processo separado e expõe 3 subsistemas via REST:
### 2.1 — Event Bus (Barramento de Eventos)
```
┌──────────────────────────────────────────────────────────────────┐
│ EventBus — Pub/Sub em memória │
│ server/python/automation_engine.py:117 │
│ │
│ ┌─── Tipos de Eventos (EventType) ──────────────────────┐ │
│ │ │ │
│ │ record.created → Registro criado no banco │ │
│ │ record.updated → Registro atualizado │ │
│ │ record.deleted → Registro deletado │ │
│ │ schedule.fired → Scheduler disparou cron │ │
│ │ webhook.received → Webhook externo recebido │ │
│ │ threshold.reached → Limiar atingido │ │
│ │ agent.completed → Manus IA finalizou tarefa │ │
│ │ manual.trigger → Disparo manual do usuário │ │
│ │ system.event → Evento interno do sistema │ │
│ │ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── Operações ─────────────────────────────────────────┐ │
│ │ │ │
│ │ subscribe(event_type, handler_id, config) │ │
│ │ → Registra um handler para um tipo de evento │ │
│ │ │ │
│ │ unsubscribe(event_type, handler_id) │ │
│ │ → Remove assinatura │ │
│ │ │ │
│ │ emit(event_type, payload) │ │
│ │ → Emite evento, retorna handlers triggerados │ │
│ │ → Suporta wildcard "*" (ouve tudo) │ │
│ │ │ │
│ │ get_history(limit, event_type) │ │
│ │ → Últimos eventos (máx. 500 em memória) │ │
│ │ │ │
│ │ stats() │ │
│ │ → total_event_types, total_subscribers, │ │
│ │ history_size, event_types list │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Fluxo: │
│ │
│ Emissor ──emit("record.created", {entity: "lead"})──▶ EventBus │
│ │ │
│ ┌────────────────────────────────┤ │
│ ▼ ▼ │
│ Handler A Handler B │
│ (workflow X) (notificação) │
└──────────────────────────────────────────────────────────────────┘
```
### 2.2 — Scheduler (Agendador Cron)
```
┌──────────────────────────────────────────────────────────────────┐
│ Scheduler — Cron-based scheduler │
│ server/python/automation_engine.py:186 │
│ │
│ ┌─── CronExpression Parser ─────────────────────────────┐ │
│ │ │ │
│ │ Formato: "minuto hora dia mês dia_semana" │ │
│ │ Suporta: *, ranges (1-5), steps (*/5), lists (1,3,5) │ │
│ │ │ │
│ │ Exemplos: │ │
│ │ "0 9 * * 1-5" → Seg-Sex às 9h │ │
│ │ "*/15 * * * *" → A cada 15 minutos │ │
│ │ "0 0 1 * *" → 1º dia de cada mês à meia-noite │ │
│ │ │ │
│ │ matches(datetime) → bool │ │
│ │ next_run(from_dt) → datetime │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── SchedulerEntry ────────────────────────────────────┐ │
│ │ │ │
│ │ id: Identificador único │ │
│ │ name: Nome descritivo │ │
│ │ cron: Expressão cron (5 partes) │ │
│ │ automation_id: Link para automação (opcional) │ │
│ │ action: "trigger" (padrão) │ │
│ │ config: Configurações extras (JSON) │ │
│ │ is_active: Ativo/Inativo │ │
│ │ last_run: Última execução (ISO) │ │
│ │ next_run: Próxima execução (ISO) │ │
│ │ run_count: Contador de execuções │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── Loop de Execução ──────────────────────────────────┐ │
│ │ │ │
│ │ Thread daemon, check_interval = 30 segundos │ │
│ │ │ │
│ │ A cada 30s: │ │
│ │ 1. Varre todas as entries ativas │ │
│ │ 2. Para cada entry: cron.matches(agora)? │ │
│ │ 3. Se match: │ │
│ │ a. Atualiza last_run, run_count │ │
│ │ b. Calcula next_run │ │
│ │ c. event_bus.emit("schedule.fired", { │ │
│ │ scheduler_id, automation_id, name │ │
│ │ }) │ │
│ │ 4. Sleep(30s) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Inicia automaticamente no startup do FastAPI │
└──────────────────────────────────────────────────────────────────┘
```
### 2.3 — Workflow Executor (Executor Multi-Step)
```
┌──────────────────────────────────────────────────────────────────┐
│ WorkflowExecutor │
│ server/python/automation_engine.py:273 │
│ │
│ ┌─── WorkflowDefinition ────────────────────────────────┐ │
│ │ │ │
│ │ id: Identificador do workflow │ │
│ │ name: Nome descritivo │ │
│ │ steps: Lista de WorkflowStep │ │
│ │ trigger: Tipo de trigger (opcional) │ │
│ │ variables: Variáveis iniciais (Dict) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── WorkflowStep ─────────────────────────────────────┐ │
│ │ │ │
│ │ id: Identificador do step │ │
│ │ type: Tipo (ver abaixo) │ │
│ │ config: Configuração do step (Dict) │ │
│ │ on_success: Próximo step se OK (opcional) │ │
│ │ on_failure: Próximo step se falha (opcional) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── Tipos de Steps (WorkflowStepType) ─────────────────┐ │
│ │ │ │
│ │ condition → Avalia condição (field, operator, value)│ │
│ │ Operadores: ==, !=, >, <, >=, <=, │ │
│ │ contains, exists │ │
│ │ │ │
│ │ action → Executa ação: │ │
│ │ • log (registra mensagem) │ │
│ │ • set_variable (define variável) │ │
│ │ • emit_event (emite evento no bus) │ │
│ │ │ │
│ │ delay → Pausa execução (max 30s) │ │
│ │ │ │
│ │ query → Executa SELECT no PostgreSQL │ │
│ │ Conexão read-only, timeout 10s, │ │
│ │ limite 100 rows │ │
│ │ │ │
│ │ http → Requisição HTTP (GET/POST/PUT/DELETE) │ │
│ │ Timeout 10s, resposta truncada em 5KB │ │
│ │ │ │
│ │ transform → Transforma dados: │ │
│ │ • count (conta itens) │ │
│ │ • sum (soma campo numérico) │ │
│ │ • filter (filtra por campo/valor) │ │
│ │ │ │
│ │ notify → Envia notificação (message + channel) │ │
│ │ │ │
│ │ loop → Iteração (planejado) │ │
│ │ parallel → Execução paralela (planejado) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── Fluxo de Execução ────────────────────────────────┐ │
│ │ │ │
│ │ 1. Recebe workflow_id + trigger_data + variables │ │
│ │ 2. Cria execution com status "running" │ │
│ │ 3. Merge: workflow.variables + variables + trigger │ │
│ │ 4. Para cada step (sequencial): │ │
│ │ a. Executa _execute_step(step, variables) │ │
│ │ b. Se resultado tem "output" → merge em variables │ │
│ │ c. Registra resultado do step │ │
│ │ 5. Status final: "completed" ou "error" │ │
│ │ 6. Guarda em memória (máx. 200 execuções) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Segurança: │
│ • SQL: somente SELECT, read-only connection, timeout 10s │
│ • HTTP: timeout 10s, resposta truncada │
│ • Delay: máximo 30 segundos │
└──────────────────────────────────────────────────────────────────┘
```
---
## 3. Automation Service (Node.js — Fallback)
O serviço Node.js complementa o motor Python, fornecendo CRUD das automações e um scheduler de fallback:
```
┌──────────────────────────────────────────────────────────────────┐
│ AutomationService │
│ server/automations/service.ts (209 linhas) │
│ │
│ ┌─── Executor de Ações (Node.js) ──────────────────────┐ │
│ │ │ │
│ │ runAutomation(automationId, userId, triggerData) │ │
│ │ │ │
│ │ 1. Cria log com status "running" │ │
│ │ 2. Busca automation + actions (orderBy orderIndex) │ │
│ │ 3. Executa cada action sequencialmente: │ │
│ │ │ │
│ │ Tipos de ação suportados: │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────┐ │ │
│ │ │ agent_task → Dispara Manus IA │ │ │
│ │ │ manusService.run(prompt) │ │ │
│ │ │ Aguarda até 120s │ │ │
│ │ │ Polls a cada 2s │ │ │
│ │ ├────────────────────────────────────────────┤ │ │
│ │ │ send_notification→ Registra notificação │ │ │
│ │ ├────────────────────────────────────────────┤ │ │
│ │ │ erp_sync → Inicia sync com ERP │ │ │
│ │ ├────────────────────────────────────────────┤ │ │
│ │ │ generate_report → Gera relatório │ │ │
│ │ ├────────────────────────────────────────────┤ │ │
│ │ │ webhook → Chama URL externa │ │ │
│ │ │ POST + JSON payload │ │ │
│ │ ├────────────────────────────────────────────┤ │ │
│ │ │ send_email → Envia email │ │ │
│ │ ├────────────────────────────────────────────┤ │ │
│ │ │ send_whatsapp → Envia msg WhatsApp │ │ │
│ │ └────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 4. Atualiza log: "completed" ou "error" │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── Scheduler (Fallback) ──────────────────────────────┐ │
│ │ │ │
│ │ startScheduler() │ │
│ │ → setInterval(60000) — verifica a cada 60s │ │
│ │ │ │
│ │ checkScheduledTasks() │ │
│ │ → JOIN scheduled_tasks + automations │ │
│ │ → WHERE isActive = true AND nextRunAt <= agora │ │
│ │ → Executa runAutomation() para cada task due │ │
│ │ → Calcula nextRunAt baseado em intervalMinutes │ │
│ │ → Atualiza lastRunAt │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Integração com Manus IA: │
│ • action "agent_task" → manusService.run(userId, prompt) │
│ • Aguarda execução com polling (até 120s) │
│ • Verifica status: completed | stopped | error │
└──────────────────────────────────────────────────────────────────┘
```
---
## 4. Engine Proxy (Node.js → Python)
O proxy traduz chamadas `/api/automation-engine/*` para o FastAPI em `:8005`:
```
┌──────────────────────────────────────────────────────────────────┐
│ Engine Proxy │
│ server/automations/engine-proxy.ts (267 linhas) │
│ │
│ Config: │
│ AUTOMATION_ENGINE_HOST = localhost (ou env var) │
│ AUTOMATION_ENGINE_PORT = 8005 │
│ TIMEOUT = 30 segundos │
│ │
│ Tradução: │
│ │
│ /api/automation-engine/health → GET :8005/health │
│ /api/automation-engine/version → GET :8005/version │
│ /api/automation-engine/metrics → GET :8005/metrics │
│ │
│ /api/automation-engine/scheduler/ → :8005/scheduler/ │
│ entries (GET/POST/DELETE) │
│ start (POST) │
│ stop (POST) │
│ │
│ /api/automation-engine/events/ → :8005/events/ │
│ emit (POST) │
│ subscribe (POST) │
│ subscribers (GET) │
│ history (GET) │
│ types (GET) │
│ │
│ /api/automation-engine/workflows/ → :8005/workflows/ │
│ register (POST) │
│ list (GET) │
│ get/:id (GET) │
│ execute/:id (POST) │
│ executions/:id (GET) │
│ │
│ /api/automation-engine/executions → :8005/executions │
│ /api/automation-engine/cron/validate → :8005/cron/validate │
│ │
│ Se engine offline → retorna { status: "offline" } no /health │
│ Demais rotas → 502 Bad Gateway se timeout │
└──────────────────────────────────────────────────────────────────┘
```
---
## 5. XOS Automations (CRM Workflows)
Automações específicas do CRM com triggers baseados em eventos de negócio:
```
┌──────────────────────────────────────────────────────────────────┐
│ XOS Automations │
│ Tabela: xos_automations — shared/schema.ts:6593 │
│ UI: client/src/pages/XosAutomations.tsx (350 linhas) │
│ │
│ ┌─── Schema ────────────────────────────────────────────┐ │
│ │ │ │
│ │ id serial PK │ │
│ │ tenantId FK → tenants.id │ │
│ │ name varchar(200) NOT NULL │ │
│ │ description text │ │
│ │ triggerType varchar(50) NOT NULL │ │
│ │ triggerConfig JSONB { condições do trigger } │ │
│ │ actions JSONB [ { type, config } ] │ │
│ │ conditions JSONB [ { field, operator, value } ] │ │
│ │ isActive boolean (default true) │ │
│ │ executionCount integer (default 0) │ │
│ │ lastExecutedAt timestamp │ │
│ │ createdBy FK → users.id │ │
│ │ createdAt, updatedAt timestamp │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── Triggers CRM ─────────────────────────────────────┐ │
│ │ │ │
│ │ contact_created → Novo contato cadastrado │ │
│ │ deal_stage_changed → Deal mudou de estágio │ │
│ │ form_submitted → Formulário preenchido │ │
│ │ ticket_created → Ticket de suporte aberto │ │
│ │ deal_won → Negócio fechado (ganho) │ │
│ │ deal_lost → Negócio perdido │ │
│ │ message_received → Mensagem WhatsApp recebida │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── Ações CRM ────────────────────────────────────────┐ │
│ │ │ │
│ │ send_email → Email automático │ │
│ │ send_whatsapp → Mensagem WhatsApp │ │
│ │ create_task → Cria tarefa/atividade │ │
│ │ assign_agent → Atribui agente ao ticket │ │
│ │ update_field → Atualiza campo do registro │ │
│ │ move_deal_stage → Move deal no pipeline │ │
│ │ webhook → Chama URL externa │ │
│ │ notify_team → Notifica equipe │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Diferença vs automations genérica: │
│ • xos_automations → multi-tenant (tenantId) │
│ • xos_automations → triggers de negócio (CRM) │
│ • xos_automations → conditions inline (JSONB array) │
│ • automations → por usuário (userId) │
│ • automations → triggers genéricos (schedule/webhook/manual)│
└──────────────────────────────────────────────────────────────────┘
```
---
## 6. Banco de Dados — Tabelas
### 6.1 — automations (Automações Genéricas)
```
┌──────────────────────────────────────────────────────────────────┐
│ automations — shared/schema.ts:582 │
│ │
│ id serial PK │
│ userId FK → users.id (CASCADE) │
│ name text NOT NULL │
│ description text │
│ triggerType text NOT NULL │
│ "schedule" | "webhook" | "manual" | "event" │
│ triggerConfig text (JSON stringified) │
│ isActive "true" | "false" │
│ createdAt timestamp │
│ updatedAt timestamp │
└──────────────────────────────────────────────────────────────────┘
```
### 6.2 — automation_actions (Ações da Automação)
```
┌──────────────────────────────────────────────────────────────────┐
│ automation_actions — shared/schema.ts:594 │
│ │
│ id serial PK │
│ automationId FK → automations.id (CASCADE) │
│ orderIndex integer (ordem de execução) │
│ actionType text NOT NULL │
│ "agent_task" | "send_notification" | │
│ "erp_sync" | "generate_report" | │
│ "webhook" | "send_email" | "send_whatsapp" │
│ actionConfig text (JSON: url, method, to, prompt, etc.) │
│ conditionConfig text (JSON: condição para executar) │
└──────────────────────────────────────────────────────────────────┘
```
### 6.3 — automation_logs (Histórico de Execuções)
```
┌──────────────────────────────────────────────────────────────────┐
│ automation_logs — shared/schema.ts:603 │
│ │
│ id serial PK │
│ automationId FK → automations.id (CASCADE) │
│ status "running" | "completed" | "error" │
│ triggerData text (JSON: dados que dispararam) │
│ result text (resultado da execução) │
│ error text (mensagem de erro, se falhou) │
│ startedAt timestamp │
│ completedAt timestamp │
└──────────────────────────────────────────────────────────────────┘
```
### 6.4 — scheduled_tasks (Tarefas Agendadas)
```
┌──────────────────────────────────────────────────────────────────┐
│ scheduled_tasks — shared/schema.ts:614 │
│ │
│ id serial PK │
│ automationId FK → automations.id (CASCADE) │
│ cronExpression text ("0 9 * * 1-5") │
│ intervalMinutes integer (alternativa ao cron) │
│ nextRunAt timestamp (próxima execução) │
│ lastRunAt timestamp (última execução) │
│ isActive "true" | "false" │
│ │
│ Dois modos: │
│ 1. cronExpression → Parser Python (5 partes) │
│ 2. intervalMinutes → Simples: nextRunAt += interval │
└──────────────────────────────────────────────────────────────────┘
```
### 6.5 — xos_automations (CRM Automations)
```
┌──────────────────────────────────────────────────────────────────┐
│ xos_automations — shared/schema.ts:6593 │
│ │
│ id serial PK │
│ tenantId FK → tenants.id (CASCADE) │
│ name varchar(200) NOT NULL │
│ description text │
│ triggerType varchar(50) NOT NULL │
│ triggerConfig JSONB │
│ actions JSONB [ { type, config } ] │
│ conditions JSONB [ { field, operator, value } ] │
│ isActive boolean (default true) │
│ executionCount integer (default 0) │
│ lastExecutedAt timestamp │
│ createdBy FK → users.id │
│ createdAt, updatedAt timestamp │
└──────────────────────────────────────────────────────────────────┘
```
---
## 7. API REST Completa
### 7.1 — CRUD Automações (/api/automations/*)
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/automations` | Lista automações do usuário |
| GET | `/api/automations/:id` | Detalhe (com actions + schedule) |
| POST | `/api/automations` | Cria automação (+ actions + schedule) |
| PUT | `/api/automations/:id` | Atualiza automação completa |
| DELETE | `/api/automations/:id` | Exclui automação (cascade) |
| POST | `/api/automations/:id/run` | Executa manualmente |
| GET | `/api/automations/:id/logs` | Histórico de execuções (últimas 50) |
### 7.2 — Automation Engine (/api/automation-engine/*)
#### Saúde e Métricas
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/health` | Status (online/offline + stats) |
| GET | `/version` | Versão + capabilities |
| GET | `/metrics` | Métricas: scheduler, event_bus, workflows |
#### Scheduler
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/scheduler/entries` | Lista entradas cron |
| POST | `/scheduler/entries` | Adiciona entrada cron |
| DELETE | `/scheduler/entries/:id` | Remove entrada |
| POST | `/scheduler/start` | Inicia loop do scheduler |
| POST | `/scheduler/stop` | Para loop do scheduler |
#### Event Bus
| Método | Endpoint | Função |
|--------|----------|--------|
| POST | `/events/emit` | Emite evento |
| POST | `/events/subscribe` | Registra handler |
| GET | `/events/subscribers` | Lista assinantes |
| GET | `/events/history` | Histórico de eventos |
| GET | `/events/types` | Tipos de eventos disponíveis |
#### Workflows
| Método | Endpoint | Função |
|--------|----------|--------|
| POST | `/workflows/register` | Registra workflow (steps) |
| GET | `/workflows` | Lista workflows |
| GET | `/workflows/:id` | Detalhe do workflow |
| POST | `/workflows/:id/execute` | Executa workflow |
| GET | `/workflows/:id/executions` | Execuções do workflow |
| GET | `/executions` | Todas as execuções |
#### Cron
| Método | Endpoint | Função |
|--------|----------|--------|
| POST | `/cron/validate` | Valida expressão cron + próximas 5 execuções |
**Total: 27+ endpoints de automação**
---
## 8. Frontend — Interfaces
### 8.1 — Automations.tsx (Gestão Geral)
```
┌──────────────────────────────────────────────────────────────────┐
│ Automations.tsx — 559 linhas │
│ Rota: /automations │
│ │
│ Componentes: │
│ │
│ ┌── CreateAutomationDialog ─────────────────────────────┐ │
│ │ • Nome, descrição │ │
│ │ • Tipo de trigger: schedule | webhook | manual | event│ │
│ │ • Config do trigger (JSON) │ │
│ │ • Actions: lista de { actionType, actionConfig } │ │
│ │ • Schedule: cronExpression ou intervalMinutes │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌── AutomationCard ────────────────────────────────────┐ │
│ │ • Nome, tipo de trigger, status (ativo/inativo) │ │
│ │ • Botão: Executar agora │ │
│ │ • Botão: Ativar/Desativar │ │
│ │ • Botão: Excluir │ │
│ │ • Logs de execução (últimas 50) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Lista todas as automações do usuário logado │
└──────────────────────────────────────────────────────────────────┘
```
### 8.2 — XosAutomations.tsx (CRM Workflows)
```
┌──────────────────────────────────────────────────────────────────┐
│ XosAutomations.tsx — 350 linhas │
│ Rota: /xos/automations │
│ │
│ Foco em automações de CRM: │
│ • Triggers de negócio (contact_created, deal_stage_changed) │
│ • Ações comerciais (send_email, assign_agent, move_deal) │
│ • Condições inline (campo, operador, valor) │
│ • Contagem de execuções e última execução │
│ • Vinculado ao tenant (multi-tenant) │
└──────────────────────────────────────────────────────────────────┘
```
### 8.3 — WorkflowBuilder.tsx (Editor Visual)
```
┌──────────────────────────────────────────────────────────────────┐
│ WorkflowBuilder.tsx — 493 linhas │
│ Rota: /workflow-builder │
│ │
│ Editor visual de workflows: │
│ • Drag & drop de steps │
│ • Configuração de cada step (type + config) │
│ • Visualização do fluxo │
│ • Registro no Workflow Executor do engine │
│ • Execução e monitoramento │
└──────────────────────────────────────────────────────────────────┘
```
### 8.4 — BPMN (Diagramas de Processo)
```
┌──────────────────────────────────────────────────────────────────┐
│ BpmnDiagram.tsx — 645 linhas │
│ ProcessDiagram.tsx — 184 linhas │
│ BpmnPage.tsx — 12 linhas (wrapper) │
│ │
│ Visualização BPMN de processos: │
│ • Render de diagramas BPMN 2.0 │
│ • Nós: start, end, task, gateway, event │
│ • Conexões direcionais │
│ • Zoom e pan │
│ • Usado no Process Compass (Módulo Consultivo) │
└──────────────────────────────────────────────────────────────────┘
```
---
## 9. Fluxo de Execução Completo
### 9.1 — Automação Manual (Trigger: manual)
```
Usuário clica "Executar" na UI
POST /api/automations/:id/run
AutomationService.runAutomation()
├──▶ INSERT automation_logs (status: "running")
├──▶ SELECT automation_actions ORDER BY orderIndex
├──▶ Para cada action:
│ ├── agent_task → manusService.run() → GPT-4o
│ ├── webhook → fetch(url, POST, body)
│ ├── send_email → (placeholder)
│ ├── send_whatsapp → (placeholder)
│ ├── erp_sync → (placeholder)
│ └── generate_report → (placeholder)
└──▶ UPDATE automation_logs (status: "completed")
```
### 9.2 — Automação Agendada (Trigger: schedule)
```
┌───────────────────── Scheduler Python ──────────────────────┐
│ │
│ Thread daemon (30s loop) │
│ ↓ │
│ CronExpression.matches(agora)? → SIM │
│ ↓ │
│ event_bus.emit("schedule.fired", {automation_id: 42}) │
│ │
└──────────────────────────────────────────────────────────────┘
▼ (subscriber triggerado)
Handler executa ação configurada
┌───────────── OU ── Scheduler Node.js (Fallback) ────────────┐
│ │
│ setInterval(60000) → checkScheduledTasks() │
│ ↓ │
│ SELECT scheduled_tasks │
│ JOIN automations │
│ WHERE isActive AND nextRunAt <= NOW() │
│ ↓ │
│ AutomationService.runAutomation(id, userId) │
│ ↓ │
│ UPDATE nextRunAt = NOW() + intervalMinutes │
│ │
└──────────────────────────────────────────────────────────────┘
```
### 9.3 — Workflow Engine (Multi-Step)
```
POST /api/automation-engine/workflows/register
▼ Registra WorkflowDefinition com N steps
POST /api/automation-engine/workflows/:id/execute
WorkflowExecutor.execute(workflow_id, trigger_data, variables)
┌────────────── EXECUÇÃO SEQUENCIAL ──────────────────────────┐
│ │
│ Step 1: condition │
│ ├── field: "revenue" operator: ">" value: 10000 │
│ └── result: true → continua │
│ │
│ Step 2: query │
│ ├── sql: "SELECT COUNT(*) FROM crm_leads WHERE ..." │
│ └── output: { query_result: [{count: 42}] } │
│ │
│ Step 3: http │
│ ├── url: "https://api.slack.com/webhook" │
│ ├── method: "POST" │
│ └── body: { text: "42 novos leads!" } │
│ │
│ Step 4: transform │
│ ├── operation: "sum", source: "query_result" │
│ └── output: { sum: 42 } │
│ │
│ Step 5: notify │
│ ├── message: "Pipeline atualizado" │
│ └── channel: "system" │
│ │
│ → status: "completed", steps_completed: 5/5 │
└──────────────────────────────────────────────────────────────┘
```
---
## 10. Integração com Outros Sistemas
```
┌─────────────────────────────────────────────────────────────────────┐
│ INTEGRAÇÕES DA AUTOMAÇÃO │
│ │
│ ┌───────── Manus IA (GPT-4o) ─────────────────────────────┐ │
│ │ action: "agent_task" │ │
│ │ → manusService.run(userId, prompt) │ │
│ │ → Polling até 120s para conclusão │ │
│ │ → Retorna resultado do agente │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────── WhatsApp (Baileys) ──────────────────────────────┐ │
│ │ action: "send_whatsapp" │ │
│ │ trigger: "message_received" (xos_automations) │ │
│ │ → Integra via CommunicationService │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────── ERPNext/SOE ─────────────────────────────────────┐ │
│ │ action: "erp_sync" │ │
│ │ trigger: "record.created" / "record.updated" │ │
│ │ → Sincroniza entidades com motor SOE │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────── Webhooks Externos ───────────────────────────────┐ │
│ │ action: "webhook" │ │
│ │ trigger: "webhook.received" │ │
│ │ → HTTP POST/GET com payload JSON │ │
│ │ → Slack, Zapier, n8n, APIs externas │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────── Knowledge Graph ─────────────────────────────────┐ │
│ │ Eventos de automação alimentam o Knowledge Graph │ │
│ │ via comm_events → graph_nodes + graph_edges │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────── Casa de Máquinas ─────────────────────────────────┐ │
│ │ Monitora status do Automation Engine (:8005) │ │
│ │ GET /api/automation-engine/health │ │
│ │ Exibe: scheduler stats, event_bus stats, workflow stats │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 11. Arquivos-Chave
| Arquivo | Função | Linhas |
|---------|--------|--------|
| `server/python/automation_engine.py` | Motor FastAPI (Scheduler + EventBus + Workflows) | 668 |
| `server/automations/service.ts` | AutomationService (executor + scheduler fallback) | 209 |
| `server/automations/routes.ts` | CRUD API (/api/automations/*) | 343 |
| `server/automations/engine-proxy.ts` | Proxy Node → Python (:8005) | 267 |
| `shared/schema.ts` (L582-636) | 4 tabelas: automations, actions, logs, scheduled | 55 |
| `shared/schema.ts` (L6593-6608) | xos_automations (CRM workflows) | 16 |
| `client/src/pages/Automations.tsx` | Interface CRUD geral | 559 |
| `client/src/pages/XosAutomations.tsx` | Interface CRM automations | 350 |
| `client/src/pages/WorkflowBuilder.tsx` | Editor visual de workflows | 493 |
| `client/src/components/BpmnDiagram.tsx` | Diagrama BPMN | 645 |
| `client/src/components/ProcessDiagram.tsx` | Diagrama de processos | 184 |
---
## 12. Resumo Visual
```
┌─────────────────────────────────────────────────────────────────────┐
│ ARCÁDIA AUTOMAÇÕES STACK │
│ │
│ ┌─── Frontend ──────────────────────────────────────────────┐ │
│ │ Automations.tsx │ XosAutomations.tsx │ WorkflowBuilder │ │
│ │ BPMN Viewer │ ProcessDiagram │ │ │
│ └──────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────▼────────────────────────────────┐ │
│ │ Node.js Gateway (:5000) │ │
│ │ │ │
│ │ /api/automations/* → CRUD (Drizzle ORM) │ │
│ │ /api/automation-engine/* → Proxy para :8005 │ │
│ │ AutomationService → Executor + Scheduler 60s │ │
│ └──────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────▼────────────────────────────────┐ │
│ │ Python FastAPI (:8005) │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │
│ │ │ Scheduler│ │Event Bus │ │Workflow Executor │ │ │
│ │ │ 30s loop │ │ Pub/Sub │ │ 10 step types │ │ │
│ │ │ Cron 5pt │ │ 9 events │ │ SQL/HTTP/Logic │ │ │
│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ PostgreSQL: 5 tabelas │ │
│ │ automations, automation_actions, automation_logs, │ │
│ │ scheduled_tasks, xos_automations │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Integrações: Manus IA, WhatsApp, ERPNext, Webhooks, │ │
│ │ Knowledge Graph, Casa de Máquinas │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```

View File

@ -1,841 +0,0 @@
# Arcádia BI — Mapa de Business Intelligence
> Mapa completo da arquitetura de BI, incluindo Motor Python, MetaSet (Apache Superset),
> Cientista (IA), ETL/Staging, APIs e Frontend.
> Atualizado em: Abril 2026
---
## 1. Visão Geral
O BI da Arcádia Suite opera em **4 camadas complementares** que se combinam para oferecer analytics completo: desde queries SQL diretas até dashboards visuais no MetaSet (Apache Superset), passando por análise com IA e ingestão de dados externos.
```
┌─────────────────────────────────────────────────────────────────────┐
│ USUÁRIO / FRONTEND │
│ │
│ BiWorkspace.tsx (2.970 linhas) MetaSet (Embeddado) │
│ React + Recharts + Tailwind Proxy → :8088 │
│ 8 abas funcionais Dashboards avançados │
│ │
│ ┌──────────┬──────────┬──────────┬──────────┬──────────┐ │
│ │ Overview │DataSrc │ Upload │ Datasets │ Charts │ │
│ ├──────────┼──────────┼──────────┼──────────┼──────────┤ │
│ │ Backups │ Staging │ Advanced │ │ │ │
│ └──────────┴──────────┴──────────┴──────────┴──────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ 🤖 Assistente BI (IA) │ │
│ │ Chat com GPT-4o sobre dados │ │
│ │ Prompts rápidos + análise livre │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────┬────────────────────────────────────────────┘
┌───────────────┼───────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ API BI (Node) │ │BI Engine (Python)│ │ MetaSet (Python) │
│ /api/bi/* │ │ /api/bi-engine/* │ │ /bi/metaset/* │
│ │ │ │ │ │
│ CRUD: │ │ SQL Execution │ │ Dashboards │
│ - DataSources │ │ Chart Data Gen │ │ Perguntas/Queries │
│ - Datasets │ │ Micro-BI │ │ Alertas │
│ - Charts │ │ Análise Pandas │ │ Visualizações │
│ - Dashboards │ │ Cache (TTL 5min) │ │ Coleções │
│ - Backups │ │ Agregações │ │ Embedding │
│ - Upload/ETL │ │ Insights Auto │ │ │
│ - Staging │ │ │ │ │
│ - AI Analysis │ │ │ │ │
└────────┬────────┘ └────────┬─────────┘ └──────────┬──────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ PostgreSQL (Porta 5432) │
│ │
│ Tabelas de gestão BI: data_sources, bi_datasets, bi_charts, │
│ bi_dashboards, bi_dashboard_charts, backup_jobs, backup_artifacts, │
│ staged_tables, staging_mappings, migration_jobs │
│ │
│ Tabelas de negócio: persons, products, sales_orders, fin_*, │
│ crm_*, whatsapp_messages, valuation_*, etc. │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 2. Os 4 Pilares do BI
### Pilar 1 — Motor BI Python (FastAPI :8004)
O coração analítico. Processa SQL, gera dados para gráficos e fornece Micro-BI.
```
┌─────────────────────────────────────────────────────────────┐
│ BI ENGINE (Python 3.11) │
│ FastAPI + Pandas + NumPy │
│ Porta: 8004 │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ SEGURANÇA SQL │ │
│ │ │ │
│ │ ✓ Somente SELECT/WITH permitidos │ │
│ │ ✓ 17 keywords bloqueadas (DROP, DELETE, INSERT...) │ │
│ │ ✓ 7 padrões perigosos filtrados (pg_sleep, --, /*) │ │
│ │ ✓ Multi-statement bloqueado (múltiplos ;) │ │
│ │ ✓ Conexão read-only (autocommit) │ │
│ │ ✓ Timeout: 30 segundos por query │ │
│ │ ✓ Limite: 10.000 linhas por resultado │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ CACHE (QueryCache) │ │
│ │ │ │
│ │ • Tipo: In-memory (OrderedDict) │ │
│ │ • Max entries: 200 │ │
│ │ • TTL: 300 segundos (5 minutos) │ │
│ │ • Key: SHA-256 de (SQL + params) │ │
│ │ • Métricas: hits, misses, hit_rate (%) │ │
│ │ • Invalidação: por padrão ou total │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ CAPABILITIES │ │
│ │ │ │
│ │ • sql_query → Execução de SELECT arbitrário │ │
│ │ • chart_data → Geração de dados para gráficos │ │
│ │ • micro_bi → Métricas rápidas com comparação │ │
│ │ • analysis → Análise estatística com Pandas │ │
│ │ • aggregation → Agregações customizadas │ │
│ │ • cache → Cache inteligente de queries │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Pilar 2 — MetaSet (Apache Superset :8100)
Plataforma visual de BI baseada em Apache Superset 4.1.0, acessível via proxy reverso.
```
┌─────────────────────────────────────────────────────────────┐
│ METASET (SUPERSET) │
│ Porta: 8100 (via proxy /bi/metaset) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ PROXY (http-proxy-middleware) │ │
│ │ │ │
│ │ Gateway (:5000) → /bi/metaset/* → MetaSet (:8100) │ │
│ │ │ │
│ │ • pathRewrite: /bi/metaset → / │ │
│ │ • changeOrigin: true │ │
│ │ • timeout: 60 segundos │ │
│ │ • Reescrita de Location headers │ │
│ │ • Fallback: 502 "MetaSet indisponível" │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ FUNCIONALIDADES │ │
│ │ │ │
│ │ • Dashboards visuais (drag & drop) │ │
│ │ • Perguntas SQL / visual query builder │ │
│ │ • Alertas automáticos │ │
│ │ • Coleções organizadas │ │
│ │ • Embedding de dashboards │ │
│ │ • Filtros interativos │ │
│ │ • Exportação (CSV, Excel, PDF) │ │
│ │ • Agendamento de relatórios │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ CONEXÃO │ │
│ │ │ │
│ │ Banco: PostgreSQL (mesmo DATABASE_URL do Gateway) │ │
│ │ Acesso: Todas as tabelas public (read-only) │ │
│ │ Config: METABASE_HOST, METABASE_PORT │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Pilar 3 — API BI Node.js (Gateway :5000)
Camada de gestão e CRUD para objetos de BI.
```
┌─────────────────────────────────────────────────────────────┐
│ API BI (Node.js/Express) │
│ Registrada no Gateway (:5000) │
│ │
│ ┌── Gestão de Objetos BI ──────────────────────────────┐ │
│ │ │ │
│ │ Data Sources → Conexões com bancos externos │ │
│ │ Datasets → Conjuntos de dados (table/SQL/API) │ │
│ │ Charts → Gráficos salvos (config + dados) │ │
│ │ Dashboards → Painéis com layout de charts │ │
│ │ Backup Jobs → Jobs de backup programados │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌── ETL & Ingestão ────────────────────────────────────┐ │
│ │ │ │
│ │ Upload → Importação de arquivos │ │
│ │ Staging → Área de preparação para migração │ │
│ │ Migration → Jobs de migração de dados │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌── Tabelas Internas ──────────────────────────────────┐ │
│ │ │ │
│ │ Exploração de schema do PostgreSQL │ │
│ │ Criação de datasets a partir de tabelas internas │ │
│ │ Categorização automática (Sistema, CRM, BI, etc.) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌── Análise com IA ────────────────────────────────────┐ │
│ │ │ │
│ │ OpenAI GPT-4o → Responde perguntas sobre dados │ │
│ │ Prompts pré-definidos (Resumo, Anomalias, etc.) │ │
│ │ Formato JSON estruturado (answer + insights) │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Pilar 4 — Cientista (Python: Auto-Programação)
Módulo de IA para análise avançada e geração de código.
```
┌─────────────────────────────────────────────────────────────┐
│ CIENTISTA (Python) │
│ python-service/services/cientista.py │
│ │
│ ┌── Análise Estatística ───────────────────────────────┐ │
│ │ │ │
│ │ analyze_data() │ │
│ │ → Shape (rows × columns) │ │
│ │ → Tipos de dados por coluna │ │
│ │ → Valores ausentes │ │
│ │ → Estatísticas (describe): mean, std, min, max │ │
│ │ → Amostra dos dados (5 primeiros registros) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌── Detecção de Padrões ──────────────────────────────┐ │
│ │ │ │
│ │ detect_patterns() │ │
│ │ → Correlações fortes (> 0.7) entre colunas │ │
│ │ → Tendências (crescente/decrescente) via polyfit │ │
│ │ → Saída: [{ type, columns, value, description }] │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌── Insights Automáticos ─────────────────────────────┐ │
│ │ │ │
│ │ generate_insights() │ │
│ │ → Valores ausentes por coluna (% de missing) │ │
│ │ → Detecção de outliers (IQR × 1.5) │ │
│ │ → Valores dominantes (> 50% em categóricas) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌── Auto-Programação (ScientistModule) ────────────────┐ │
│ │ │ │
│ │ generate_analysis_code(description, goal) │ │
│ │ → Templates: aggregate, filter, predict, correlate │ │
│ │ → Gera código Python executável │ │
│ │ → Salva padrões aprendidos │ │
│ │ → Histórico de execuções │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
---
## 3. API REST Completa
### 3.1 — BI Engine Proxy (/api/bi-engine/*)
Proxy do Gateway para o Motor Python FastAPI (:8004):
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/bi-engine/health` | Status do motor (online/offline, banco, cache) |
| GET | `/api/bi-engine/version` | Versão e capabilities |
| GET | `/api/bi-engine/metrics` | Métricas (cache stats, limites) |
| GET | `/api/bi-engine/tables` | Lista todas as tabelas do PostgreSQL |
| GET | `/api/bi-engine/tables/:name/columns` | Schema de colunas de uma tabela |
| GET | `/api/bi-engine/tables/:name/preview` | Preview dos dados (limit padrão: 50) |
| GET | `/api/bi-engine/tables/:name/stats` | Estatísticas (row_count, column_count) |
| POST | `/api/bi-engine/query` | Executa SQL (somente SELECT/WITH) |
| POST | `/api/bi-engine/chart-data` | Gera dados formatados para gráficos |
| POST | `/api/bi-engine/micro-bi` | Micro-BI: métricas rápidas com comparação temporal |
| POST | `/api/bi-engine/analyze` | Análise de dados com Pandas |
| POST | `/api/bi-engine/aggregate` | Agregações customizadas |
| GET | `/api/bi-engine/cache/stats` | Estatísticas do cache |
| POST | `/api/bi-engine/cache/invalidate` | Invalida cache (por padrão ou total) |
### 3.2 — BI Management (/api/bi/*)
CRUD de objetos BI no Gateway Node.js:
#### Data Sources
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/bi/data-sources` | Lista fontes de dados do usuário |
| POST | `/api/bi/data-sources` | Cria fonte (PostgreSQL, MySQL, MongoDB, SQLite, Internal) |
| POST | `/api/bi/data-sources/:id/test` | Testa conexão |
| DELETE | `/api/bi/data-sources/:id` | Remove fonte |
#### Datasets
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/bi/datasets` | Lista datasets do usuário |
| POST | `/api/bi/datasets` | Cria dataset (table, SQL ou API) |
| POST | `/api/bi/datasets/:id/execute` | Executa dataset e retorna dados |
| DELETE | `/api/bi/datasets/:id` | Remove dataset |
#### Charts
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/bi/charts` | Lista gráficos do usuário |
| POST | `/api/bi/charts` | Cria gráfico (bar, line, pie, area, scatter, table, metric, donut) |
| DELETE | `/api/bi/charts/:id` | Remove gráfico |
#### Dashboards
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/bi/dashboards` | Lista dashboards do usuário |
| GET | `/api/bi/dashboards/:id` | Detalhe do dashboard + charts posicionados |
| POST | `/api/bi/dashboards` | Cria dashboard |
| POST | `/api/bi/dashboards/:id/charts` | Adiciona chart ao dashboard (com posição X/Y/W/H) |
| DELETE | `/api/bi/dashboards/:id` | Remove dashboard |
#### Tabelas Internas
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/bi/internal-tables` | Lista tabelas do sistema (com tamanho e categoria) |
| GET | `/api/bi/internal-tables/:name/schema` | Schema + preview + row count |
| POST | `/api/bi/internal-tables/:name/create-dataset` | Cria dataset a partir de tabela interna |
| POST | `/api/bi/query` | Query direta em tabela (simples, limite 1000) |
#### Backups
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/bi/backup-jobs` | Lista jobs de backup |
| POST | `/api/bi/backup-jobs` | Cria job (full, schema, data, incremental) |
| POST | `/api/bi/backup-jobs/:id/run` | Executa backup |
| DELETE | `/api/bi/backup-jobs/:id` | Remove job |
| GET | `/api/bi/backup-artifacts` | Lista artefatos gerados |
### 3.3 — Upload & ETL (/api/upload/*, /api/staging/*)
#### Upload de Dados
| Método | Endpoint | Função |
|--------|----------|--------|
| POST | `/api/upload/file` | Upload de arquivo (CSV, JSON, Excel, SQL, BSON, ZIP) |
| POST | `/api/upload/analyze` | Analisa arquivo e sugere schema |
Formatos suportados (até 200MB):
- `.csv`, `.txt` → Parser CSV inteligente (suporta aspas)
- `.json` → Parse direto de arrays/objetos
- `.xlsx`, `.xls` → Leitura via SheetJS (XLSX)
- `.sql` → Detecção de INSERTs e criação de datasets
- `.bson` → Parse via biblioteca BSON
- `.zip` → Extração e processamento de conteúdo
#### Staging (Área de Preparação)
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/staging/tables` | Lista tabelas staged |
| GET | `/api/staging/tables/:id` | Detalhe de tabela staged |
| GET | `/api/staging/tables/:id/data` | Dados da tabela staged |
| POST | `/api/staging/tables/:id/mappings` | Cria mapeamento de colunas |
| GET | `/api/staging/tables/:id/mappings` | Lista mapeamentos |
| POST | `/api/staging/tables/:id/migrate` | Executa migração para tabela destino |
| DELETE | `/api/staging/tables/:id` | Remove tabela staged |
### 3.4 — MetaSet Proxy (/metaset/*)
| Rota | Destino | Função |
|------|---------|--------|
| `/metaset/*` | `http://localhost:8088/*` | Proxy reverso completo para MetaSet |
---
## 4. Banco de Dados — Tabelas do BI
### Objetos BI
```
┌─────────────────────────────┐ ┌──────────────────────────────┐
│ data_sources │ │ bi_datasets │
│ │ │ │
│ id, userId, name, │ │ id, userId, name, description,│
│ type (postgresql|mysql| │ │ dataSourceId, queryType │
│ mongodb|sqlite|internal), │ │ (table|sql|api), tableName, │
│ host, port, database, │ │ sqlQuery, columns, filters, │
│ username, password, │ │ isPublic, createdAt, updatedAt│
│ connectionString, isActive, │ │ │
│ lastTestedAt, createdAt │ └───────────────┬───────────────┘
└──────────────────────────────┘ │ 1:N
┌─────────────────────────────────────────────────────────────────┐
│ bi_charts │
│ │
│ id, userId, name, datasetId, chartType (bar|line|pie|area| │
│ scatter|table|metric|donut), config (JSON), xAxis, yAxis, │
│ groupBy, aggregation, colors, createdAt, updatedAt │
└──────────────────────────┬──────────────────────────────────────┘
│ N:M (via bi_dashboard_charts)
┌─────────────────────────────────────────────────────────────────┐
│ bi_dashboards bi_dashboard_charts │
│ │
│ id, userId, name, id, dashboardId, │
│ description, layout (JSON), chartId, │
│ isPublic, createdAt, updatedAt positionX, positionY, │
│ width, height │
└─────────────────────────────────────────────────────────────────┘
```
### Backups
```
┌───────────────────────────────┐ ┌───────────────────────────────┐
│ backup_jobs │ │ backup_artifacts │
│ │ │ │
│ id, userId, name, │ │ id, backupJobId, filename, │
│ dataSourceId, backupType │ │ filePath, fileSize, status │
│ (full|schema|data|incremental),│ │ (running|completed|failed), │
│ includeSchema, includeTables, │ │ startedAt, completedAt, │
│ excludeTables, compressionType,│ │ errorMessage │
│ retentionDays, storageLocation,│ │ │
│ lastRunAt, isActive, createdAt │ │ │
└───────────────────────────────┘ └───────────────────────────────┘
```
### Staging & Migração
```
┌───────────────────────────────┐ ┌───────────────────────────────┐
│ staged_tables │ │ staging_mappings │
│ │ │ │
│ id, userId, name, │ │ id, stagedTableId, │
│ originalFilename, fileType, │ │ sourceColumn, targetTable, │
│ rowCount, columnCount, │ │ targetColumn, transformType, │
│ columns (JSON), sampleData, │ │ transformConfig, createdAt │
│ status, createdAt, updatedAt │ │ │
└───────────────────────────────┘ └───────────────────────────────┘
┌───────────────────────────────────────────────────────────────────┐
│ migration_jobs │
│ │
│ id, userId, name, stagedTableId, targetTable, status, │
│ (pending|running|completed|failed), totalRows, processedRows, │
│ errorLog, mappingConfig (JSON), createdAt, updatedAt │
└───────────────────────────────────────────────────────────────────┘
```
---
## 5. Frontend — BiWorkspace.tsx (2.970 linhas)
### Tabs
| Tab | Valor | Descrição |
|-----|-------|-----------|
| **Overview** | `overview` | KPIs resumo: DataSources, Datasets, Charts, Dashboards, Backups |
| **Data Sources** | `datasources` | CRUD de fontes externas (PostgreSQL, MySQL, MongoDB, SQLite) + tabelas internas do sistema |
| **Upload** | `upload` | Importação de arquivos (CSV, Excel, JSON, SQL, BSON, ZIP) com preview automático |
| **Datasets** | `datasets` | Conjuntos de dados criados a partir de tabelas ou SQL, com execução e preview |
| **Charts** | `charts` | Criação de gráficos (8 tipos) a partir de datasets, com visualização Recharts |
| **Backups** | `backups` | Jobs de backup com execução e artefatos gerados |
| **Staging** | `staging` | Área de preparação: mapeamento de colunas, migração para tabelas destino |
| **Advanced** | `advanced` | BI Engine (link para Motor Python), MetaSet (link para iframe), Assistente IA |
### Tipos de Gráficos Suportados (Recharts)
| Tipo | Componente | Uso |
|------|-----------|-----|
| `bar` | `BarChart + Bar` | Comparação entre categorias |
| `line` | `LineChart + Line` | Evolução temporal |
| `pie` | `PieChart + Pie + Cell` | Proporções (até 8 fatias) |
| `area` | `AreaChart + Area` | Tendências com preenchimento |
| `scatter` | (planejado) | Correlação entre variáveis |
| `table` | HTML Table | Dados tabulares |
| `metric` | Card KPI | Valor único com destaque |
| `donut` | `PieChart (innerRadius)` | Proporções com centro vazio |
### Assistente BI (IA)
```
┌─────────────────────────────────────────────────────────────┐
│ 🤖 Assistente BI │
│ │
│ • Chat conversacional com GPT-4o │
│ • Recebe dados do dataset ativo como contexto │
│ • Prompts rápidos pré-definidos: │
│ 📊 "Resumo Executivo" (principais métricas e insights) │
│ 🔍 "Anomalias" (outliers e desvios) │
│ 📈 "Tendências" (padrões temporais) │
│ 💡 "Recomendações" (ações sugeridas) │
│ • Formato de resposta: JSON { answer, insights[] } │
│ • Interface: chat bubble com histórico │
└─────────────────────────────────────────────────────────────┘
```
### Categorias de Tabelas Internas
O BI mapeia automaticamente as tabelas do sistema em categorias:
| Categoria | Tabelas |
|-----------|---------|
| **Sistema** | users, applications, roles |
| **Process Compass** | pc_clients, pc_projects, pc_tasks |
| **CRM** | crm_clients, crm_contracts, crm_partners, crm_opportunities, crm_leads, crm_messages |
| **BI** | bi_datasets, bi_charts |
| **Conhecimento** | knowledge_base |
| **Manus** | manus_runs, agent_tasks |
| **Comunicação** | whatsapp_messages, conversations |
| **Outros** | Todas as demais tabelas (valuation_*, fin_*, etc.) |
---
## 6. Motor BI Python — Endpoints Detalhados
### POST /query — Execução SQL
```json
{
"sql": "SELECT status, COUNT(*) as total FROM sales_orders GROUP BY status",
"params": {},
"limit": 1000,
"use_cache": true
}
```
Retorno:
```json
{
"data": [{"status": "confirmed", "total": 45}, ...],
"columns": [{"name": "status", "type": "1043"}, ...],
"row_count": 3,
"elapsed_ms": 12.5,
"cached": false
}
```
### POST /chart-data — Dados para Gráficos
```json
{
"table": "sales_orders",
"x_axis": "created_at",
"y_axis": "total_amount",
"aggregation": "sum",
"time_grain": "month",
"group_by": "status",
"filters": [{"column": "status", "operator": "!=", "value": "cancelled"}],
"limit": 12
}
```
Retorno:
```json
{
"labels": ["2026-01", "2026-02", "2026-03"],
"series": {
"confirmed": [{"label": "2026-01", "value": 15000}, ...],
"delivered": [{"label": "2026-01", "value": 12000}, ...]
},
"row_count": 6,
"elapsed_ms": 18.3,
"query": "SELECT DATE_TRUNC('month', created_at) AS label, status AS series, SUM(total_amount) AS value FROM sales_orders WHERE status != 'cancelled' GROUP BY ...",
"cached": false
}
```
### POST /micro-bi — Métricas Rápidas
```json
{
"table": "fin_transactions",
"metrics": ["count", "sum:amount", "avg:amount"],
"dimension": "category",
"period": "month",
"compare_previous": true
}
```
Retorno:
```json
{
"current": [
{"dimension": "vendas", "count": 120, "sum_amount": 45000, "avg_amount": 375}
],
"previous": [
{"dimension": "vendas", "count": 98, "sum_amount": 38000, "avg_amount": 387.76}
],
"comparison": {
"count": {"current": 120, "previous": 98, "change": 22, "change_pct": 22.4, "trend": "up"}
}
}
```
### POST /analyze — Análise com Pandas
```json
{
"data": [{"col1": 10, "col2": "A"}, ...],
"question": "Quais são os outliers?"
}
```
Retorno: estatísticas por coluna (min, max, mean, median, std, sum), insights automáticos, sugestões de gráficos.
### Granularidades Temporais (time_grain)
| Valor | Função SQL | Exemplo |
|-------|-----------|---------|
| `day` | `DATE_TRUNC('day', col)` | 2026-03-16 |
| `week` | `DATE_TRUNC('week', col)` | 2026-03-11 |
| `month` | `DATE_TRUNC('month', col)` | 2026-03-01 |
| `quarter` | `DATE_TRUNC('quarter', col)` | 2026-01-01 |
| `year` | `DATE_TRUNC('year', col)` | 2026-01-01 |
### Funções de Agregação
| Valor | SQL | Uso |
|-------|-----|-----|
| `sum` | `SUM(col)` | Totalizador |
| `avg` | `AVG(col)` | Média |
| `count` | `COUNT(*)` | Contagem |
| `min` | `MIN(col)` | Mínimo |
| `max` | `MAX(col)` | Máximo |
---
## 7. Pipeline ETL — Fluxo de Ingestão
```
Arquivo (CSV/Excel/JSON/SQL/BSON/ZIP)
┌──────────────────────────────────────┐
│ 1. UPLOAD (/api/upload/file) │
│ │
│ • Multer (disk storage) │
│ • Limite: 200 MB │
│ • Validação de extensão │
│ • Parse do conteúdo │
└──────────────────┬───────────────────┘
┌──────────────────────────────────────┐
│ 2. STAGING (staged_tables) │
│ │
│ • Armazena headers e sample data │
│ • Detecta tipos de colunas │
│ • Preview para o usuário │
└──────────────────┬───────────────────┘
┌──────────────────────────────────────┐
│ 3. MAPEAMENTO (staging_mappings) │
│ │
│ • sourceColumn → targetColumn │
│ • transformType (cast, rename, etc.)│
│ • Validação de compatibilidade │
└──────────────────┬───────────────────┘
┌──────────────────────────────────────┐
│ 4. MIGRAÇÃO (migration_jobs) │
│ │
│ • Cria tabela destino se necessário │
│ • INSERT em lotes │
│ • Tracking: total/processed rows │
│ • Status: pending → running → │
│ completed/failed │
│ • Error log para debugging │
└──────────────────┬───────────────────┘
┌──────────────────────────────────────┐
│ 5. DATASET (bi_datasets) │
│ │
│ • Dataset pronto para visualização │
│ • Pode criar charts e dashboards │
│ • Conecta com BI Engine e MetaSet │
└──────────────────────────────────────┘
```
---
## 8. Integração entre Pilares
```
┌──────────────────────────────────────────────────────────────────────┐
│ FLUXO DE DADOS │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Upload │────▶│ Staging │────▶│ Dataset │────▶│ Chart │ │
│ │ (ETL) │ │(Prepare) │ │ (Dados) │ │ (Visual) │ │
│ └──────────┘ └──────────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ ┌─────▼──────┐ ┌────▼─────┐ │
│ │ BI Engine │ │Dashboard │ │
│ │ (Análise) │ │(Painel) │ │
│ └─────┬──────┘ └──────────┘ │
│ │ │
│ ┌──────────┐ ┌─────▼──────┐ │
│ │ Tabelas │─────────────────────▶│ MetaSet │ │
│ │ Internas │ │ (Visual) │ │
│ │ (PG) │ └──────┬─────┘ │
│ └──────────┘ │ │
│ ┌─────▼──────┐ │
│ │ Cientista │ │
│ │ (IA/ML) │ │
│ └────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
```
### Como os pilares se conectam:
| De | Para | Como |
|----|------|------|
| Upload | Staging | Arquivo parseado → tabela staged |
| Staging | Dataset | Migração → dataset criado automaticamente |
| Tabela Interna | Dataset | `/api/bi/internal-tables/:name/create-dataset` |
| Dataset | BI Engine | SQL do dataset executado no Motor Python |
| Dataset | Chart | Chart referencia datasetId |
| Chart | Dashboard | `bi_dashboard_charts` com posição (X,Y,W,H) |
| Tabelas PG | MetaSet | Conexão direta ao PostgreSQL |
| Dataset | Assistente IA | Dados enviados como contexto ao GPT-4o |
| Dados | Cientista | `analyze_data()`, `detect_patterns()`, `generate_insights()` |
---
## 9. Configuração e Variáveis de Ambiente
| Variável | Default | Usado por | Descrição |
|----------|---------|-----------|-----------|
| `DATABASE_URL` | - | BI Engine, Gateway | Conexão PostgreSQL |
| `BI_ENGINE_HOST` | `localhost` | Engine Proxy | Host do motor Python |
| `BI_PORT` | `8004` | BI Engine, Proxy | Porta do motor Python |
| `BI_ENGINE_TIMEOUT` | `30000` | Engine Proxy | Timeout de proxy (ms) |
| `METASET_HOST` | `metaset` | MetaSet Proxy | Host do MetaSet |
| `METASET_PORT` | `8100` | MetaSet Proxy | Porta do MetaSet |
| `METASET_TIMEOUT` | `30000` | MetaSet Proxy | Timeout do proxy (ms) |
| `OPENAI_API_KEY` | - | Assistente BI | API key para GPT-4o |
---
## 10. Arquivos-Chave
| Arquivo | Camada | Função | Linhas |
|---------|--------|--------|--------|
| `server/python/bi_engine.py` | Motor Python | SQL, Charts, Micro-BI, Cache, Análise | ~650 |
| `server/bi/routes.ts` | API Node | CRUD de DataSources, Datasets, Charts, Dashboards, Backups | ~700 |
| `server/bi/engine-proxy.ts` | Proxy | Proxy Gateway → BI Engine Python (:8004) | ~200 |
| `server/bi/upload.ts` | ETL | Upload de arquivos (CSV, Excel, JSON, SQL, BSON, ZIP) | ~1.060 |
| `server/bi/staging.ts` | ETL | Staging, mapeamento, migração de dados | ~408 |
| `server/bi/metaset-client/routes.ts` | Proxy | Proxy Gateway → MetaSet (:8100) | ~215 |
| `client/src/pages/BiWorkspace.tsx` | Frontend | Interface principal do BI (8 tabs) | ~2.970 |
| `server/bi/metaset-client/index.ts` | Cliente | Cliente TypeScript para API Superset | ~375 |
| `client/src/pages/MetaSetProxyPage.tsx` | Frontend | Página do MetaSet embeddado | ~25 |
| `python-service/services/cientista.py` | IA/ML | Análise, padrões, insights, auto-programação | ~567 |
| `shared/schema.ts` | Schema | Tabelas BI (9 tabelas) | Parte do schema |
---
## 11. Deploy (Docker/Coolify)
### Container `arcadia-bi` (Motor Python)
```dockerfile
FROM python:3.11-slim
WORKDIR /app
RUN pip install fastapi uvicorn pydantic pandas numpy psycopg2-binary
COPY server/python/bi_engine.py ./main.py
EXPOSE 8004
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8004"]
```
### Container `metaset`
```yaml
metaset:
image: metaset/metaset:latest
restart: always
ports:
- "8088:3000"
environment:
MB_DB_TYPE: postgres
MB_DB_DBNAME: metaset
MB_DB_PORT: 5432
MB_DB_USER: ${PGUSER:-arcadia}
MB_DB_PASS: ${PGPASSWORD}
MB_DB_HOST: postgres
JAVA_TIMEZONE: America/Sao_Paulo
depends_on:
postgres:
condition: service_healthy
volumes:
- metaset-data:/metaset-data
networks:
- arcadia
```
### Variáveis no Gateway (Docker)
```yaml
gateway:
environment:
BI_ENGINE_HOST: bi
BI_PORT: 8004
METABASE_HOST: metaset
METABASE_PORT: 3000
```
---
## 12. Resumo Visual
```
┌─────────────────────────────────────────────────────────────────────┐
│ ARCÁDIA BI STACK │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ FRONTEND: BiWorkspace.tsx + Recharts + Assistente IA (GPT) │ │
│ └────────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────┼─────────────────────┐ │
│ │ │ │ │
│ ┌──────▼──────┐ ┌────────▼────────┐ ┌────────▼────────┐ │
│ │ API BI │ │ BI Engine │ │ MetaSet │ │
│ │ (Node.js) │ │ (Python:8004) │ │ (Java:8088) │ │
│ │ │ │ │ │ │ │
│ │ CRUD │ │ SQL + Charts │ │ Dashboards │ │
│ │ Upload/ETL │ │ Micro-BI │ │ Visual Builder │ │
│ │ Staging │ │ Análise Pandas │ │ Alertas │ │
│ │ Backups │ │ Cache (5min) │ │ Exportação │ │
│ └──────┬──────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ └────────────────────┼──────────────────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ PostgreSQL │ │
│ │ (Porta 5432) │ │
│ │ │ │
│ │ 9 tabelas BI + │ │
│ │ 100+ tabelas SOE │ │
│ └────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ CIENTISTA (Python) — Análise, Padrões, Insights, AutoCode │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```

View File

@ -1,834 +0,0 @@
# Arcádia CRM — Mapa Completo do Sistema de Relacionamento
> Mapa da arquitetura CRM nativa da Arcádia Suite: módulos, tabelas,
> APIs, comunicação unificada (WhatsApp/Email), motor de comissões,
> integração Frappe/ERPNext e frontend.
> Atualizado em: Março 2026
---
## 1. Visão Geral
O CRM da Arcádia Suite é **100% nativo** — construído internamente em Node.js + PostgreSQL com Drizzle ORM. Ele opera em **três camadas** que se complementam, unificadas por um Motor de Comunicação e alimentadas por IA (Manus/GPT-4o):
```
┌─────────────────────────────────────────────────────────────────────┐
│ CAMADA DE EXPERIÊNCIA │
│ │
│ ┌────────────────────────┐ ┌───────────────────────┐ │
│ │ Arcádia CRM (Crm.tsx) │ │ XOS CRM (XosCrm.tsx) │ │
│ │ 2.700 linhas │ │ 752 linhas │ │
│ │ 12 abas │ │ Kanban visual │ │
│ │ Gestão completa │ │ Deals + Pipeline │ │
│ └────────────┬───────────┘ └───────────┬───────────┘ │
│ │ │ │
│ └──────────┬────────────────┘ │
│ │ │
│ ┌───────────────────────▼───────────────────────────────────┐ │
│ │ WhatsApp.tsx — Interface de Atendimento │ │
│ │ Multi-sessão, tickets, IA auto-reply │ │
│ └───────────────────────────────────────────────────────────┘ │
└──────────────────────────┬───────────────────────────────────────────┘
┌──────────────────────────▼───────────────────────────────────────────┐
│ CAMADA DE ORQUESTRAÇÃO │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Routes │ │ Storage │ │ Comm. │ │Commission│ │ WA │ │
│ │ (1.592L) │ │ (708L) │ │ (289L) │ │ Engine │ │ Bridge │ │
│ │ │ │ │ │ │ │ (418L) │ │ (126L) │ │
│ │ 90+ │ │CrmStorage│ │ Connect/ │ │ Revenue │ │ Sessão │ │
│ │ endpoints│ │ class │ │ Send/ │ │ Schedule │ │ ↔ Canal │ │
│ │ │ │ │ │ Receive │ │ Comissões│ │ Eventos │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Frappe Svc │ │Google Calendar│ │ Knowledge │ │
│ │ ERPNext sync │ │ OAuth + Sync │ │ Graph Feed │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└──────────────────────────┬───────────────────────────────────────────┘
┌──────────────────────────▼───────────────────────────────────────────┐
│ CAMADA DE DADOS │
│ │
│ PostgreSQL — 30 tabelas CRM (crm_*) + 15 tabelas XOS (xos_*) │
│ + 8 tabelas Communication (comm_*) │
│ │
│ Drizzle ORM — Schema em shared/schema.ts │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 2. Os Três CRMs
### 2.1 — Arcádia CRM (Principal)
O CRM completo da plataforma. Gerencia todo o ciclo de relacionamento: da captação de leads até contratos e comissões.
```
┌─────────────────────────────────────────────────────────────┐
│ ARCÁDIA CRM │
│ client/src/pages/Crm.tsx (2.700 linhas) │
│ /api/crm/* (90+ endpoints) │
│ │
│ 12 Abas: │
│ ┌──────────┬──────────┬──────────┬──────────┐ │
│ │Dashboard │ Leads │ Opps │ Produtos │ │
│ ├──────────┼──────────┼──────────┼──────────┤ │
│ │Parceiros │ Clientes │Contratos │Mensagens │ │
│ ├──────────┼──────────┼──────────┼──────────┤ │
│ │Calendário│Integrações│Config │Multitenant│ │
│ └──────────┴──────────┴──────────┴──────────┘ │
│ │
│ Domínios: │
│ • Pipeline de Vendas (Leads → Opps → Propostas → Contratos)│
│ • Parceiros & Certificações │
│ • Produtos & Catálogo │
│ • Comunicação Multicanal (WhatsApp, Email) │
│ • Comissões & Revenue Schedule │
│ • Agenda (Google Calendar) │
│ • Integração Frappe/ERPNext │
│ • Multi-tenant │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 — XOS CRM (Kanban/Pipeline)
Visão simplificada e visual focada em gestão de deals com drag & drop.
```
┌─────────────────────────────────────────────────────────────┐
│ XOS CRM │
│ client/src/pages/XosCrm.tsx (752 linhas) │
│ │
│ Foco: │
│ • Kanban visual de deals │
│ • Pipeline com estágios customizáveis │
│ • Contatos & Empresas │
│ • Conversas & Tickets │
│ • Campanhas & Automações │
│ • Filas de atendimento │
│ │
│ Tabelas próprias: xos_contacts, xos_companies, │
│ xos_pipelines, xos_pipeline_stages, xos_deals, │
│ xos_conversations, xos_messages, xos_tickets, │
│ xos_campaigns, xos_automations, xos_activities, │
│ xos_queues, xos_queue_users, xos_internal_notes, │
│ xos_quick_messages │
└─────────────────────────────────────────────────────────────┘
```
### 2.3 — Plus CRM (Legado/PHP)
Módulo CRM dentro do Arcádia Plus (Laravel), focado em anotações e histórico.
```
┌─────────────────────────────────────────────────────────────┐
│ PLUS CRM (Legado) │
│ plus/app/Http/Controllers/CrmController │
│ │
│ • Anotações de clientes (crm_anotacoes) │
│ • Histórico de interações │
│ • Integrado ao ERP Plus (fiscal, estoque, financeiro) │
│ • Proxy reverso via /plus │
│ • Banco MySQL (separado do PostgreSQL) │
└─────────────────────────────────────────────────────────────┘
```
---
## 3. Ciclo de Vida do Cliente
```
┌────────┐ ┌────────┐ ┌────────────┐ ┌──────────┐ ┌──────────┐
│ LEAD │───▶│ OPP │───▶│ PROPOSTA │───▶│ CONTRATO │───▶│ CLIENTE │
│ │ │ │ │ │ │ │ │ │
│ Nome │ │ Valor │ │ Itens │ │ Receita │ │ Ativo │
│ Email │ │ Stage │ │ Preços │ │ Parcelas │ │ Suporte │
│ Fone │ │ Prob% │ │ Desconto │ │ Comissão │ │ Upsell │
│ Source │ │ Close │ │ Aprovação │ │ Marcos │ │ │
│ Status │ │ Owner │ │ PDF │ │ Duração │ │ │
└────┬───┘ └────┬───┘ └────────────┘ └──────────┘ └──────────┘
│ │
│ convert │ won/lost
│ │
▼ ▼
POST /leads/ POST /opportunities/
:id/convert :id/won
:id/lost
:id/bill
:id/open-project
```
### Fluxo detalhado:
| Etapa | Ação | Endpoint | Resultado |
|-------|------|----------|-----------|
| 1 | Captação | `POST /api/crm/leads` | Lead criado (status: new) |
| 2 | Qualificação | `PATCH /api/crm/leads/:id` | Lead atualizado (status: qualified) |
| 3 | Conversão | `POST /api/crm/leads/:id/convert` | Cria Opportunity + Client automaticamente |
| 4 | Negociação | `PATCH /api/crm/opportunities/:id/stage` | Move entre estágios do pipeline |
| 5 | Proposta | `POST /api/crm/proposals` | Proposta com itens e preços |
| 6 | Ganho | `POST /api/crm/opportunities/:id/won` | Marca como ganha |
| 7 | Contrato | `POST /api/crm/contracts` | Contrato com receita e marcos |
| 8 | Faturamento | `POST /api/crm/opportunities/:id/bill` | Gera faturamento |
| 9 | Projeto | `POST /api/crm/opportunities/:id/open-project` | Abre projeto no Process Compass |
| 10 | Perda | `POST /api/crm/opportunities/:id/lost` | Marca como perdida + motivo |
---
## 4. Comunicação Multicanal
```
┌─────────────────────────────────────────────────────────────────────┐
│ SISTEMA DE COMUNICAÇÃO │
│ │
│ ┌───────────┐ ┌────────────────┐ ┌──────────────────┐ │
│ │ WhatsApp │ │ Communication │ │ CRM Threads │ │
│ │ (Baileys) │───▶│ Service │───▶│ & Messages │ │
│ │ │ │ │ │ │ │
│ │ Multi- │ │ connectChannel │ │ crm_channels │ │
│ │ sessão │ │ sendMessage │ │ crm_threads │ │
│ │ QR Code │ │ receiveMessage │ │ crm_messages │ │
│ │ Auto-reply│ │ getOrCreate │ │ crm_quick_msgs │ │
│ │ (GPT-4o) │ │ Thread │ │ │ │
│ └─────┬─────┘ └────────────────┘ └──────────────────┘ │
│ │ │
│ ┌─────▼─────────────────────────────────────┐ │
│ │ WhatsApp Bridge │ │
│ │ │ │
│ │ Evento "message" → handleIncoming │ │
│ │ Evento "connected"→ updateChannelStatus │ │
│ │ Evento "qr" → storeQRCode │ │
│ │ Evento "disconnect"→ markDisconnected │ │
│ │ │ │
│ │ sessionChannelMap: Session ↔ CRM Channel │ │
│ └────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ Motor de Comunicação (:8006) │ │
│ │ │ │
│ │ Unifica: │ │
│ │ • CRM Threads (crm_threads) │ │
│ │ • XOS Conversations (xos_conversations) │ │
│ │ • WhatsApp Tickets (xos_tickets) │ │
│ │ │ │
│ │ Tabelas comm_*: │ │
│ │ comm_contacts, comm_threads, │ │
│ │ comm_messages, comm_channels, │ │
│ │ comm_queues, comm_quick_messages, │ │
│ │ comm_events → Knowledge Graph │ │
│ └────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
### Fluxo de mensagem WhatsApp:
```
Cliente envia mensagem no WhatsApp
Baileys (whatsappService) recebe
▼ emite evento "message"
WhatsApp Bridge captura
▼ mapeia session → channelId
CommunicationService.receiveMessage()
├──▶ getOrCreateThread() → cria/reutiliza thread
├──▶ crmMessages → INSERT (direction: "incoming")
├──▶ AI Auto-Reply? (GPT-4o-mini em horário comercial)
└──▶ Knowledge Graph → graph_nodes + graph_edges
```
### Canais suportados:
| Canal | Tecnologia | Status |
|-------|-----------|--------|
| **WhatsApp** | Baileys (multi-device) | Ativo |
| **Email** | SMTP + IMAP | Planejado |
| **SMS** | API (Twilio/etc.) | Futuro |
| **Telegram** | Bot API | Futuro |
| **Chat Interno** | Socket.IO | Ativo (Comunidades) |
---
## 5. Motor de Comissões
```
┌─────────────────────────────────────────────────────────────┐
│ COMMISSION ENGINE │
│ server/crm/commission-engine.ts (418L) │
│ │
│ ┌── Revenue Schedule ──────────────────────────────────┐ │
│ │ │ │
│ │ generateRevenueSchedule(contractId) │ │
│ │ │ │
│ │ • Mensal: projeta N parcelas (até 24 meses) │ │
│ │ • Anual: projeta N anos (até 10 anos) │ │
│ │ • Calcula datas de vencimento │ │
│ │ • Status: pending → paid → overdue │ │
│ │ │ │
│ │ Contrato R$ 5.000/mês × 12 meses: │ │
│ │ → 12 registros em crm_revenue_schedule │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌── Commission Rules ─────────────────────────────────┐ │
│ │ │ │
│ │ crm_commission_rules │ │
│ │ │ │
│ │ Regras baseadas em: │ │
│ │ • Tipo de receita: recurring / one-time │ │
│ │ • Cenário de venda: direct / partner │ │
│ │ • Mês do contrato (faixas) │ │
│ │ │ │
│ │ Exemplo: │ │
│ │ Meses 1-5 (aquisição): 15% sobre receita │ │
│ │ Meses 6-12 (retenção): 10% sobre receita │ │
│ │ Meses 13+ (maturidade): 5% sobre receita │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌── Cálculo de Comissões ─────────────────────────────┐ │
│ │ │ │
│ │ calculateCommissions(contractId) │ │
│ │ │ │
│ │ Para cada parcela da revenue schedule: │ │
│ │ 1. Identifica o mês do contrato │ │
│ │ 2. Busca regra aplicável (tipo + cenário + faixa) │ │
│ │ 3. Calcula valor = parcela × percentual │ │
│ │ 4. Cria registro em crm_commissions │ │
│ │ │ │
│ │ Status: pending → approved → paid │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
---
## 6. Integração Frappe/ERPNext
```
┌─────────────────────────────────────────────────────────────┐
│ FRAPPE SERVICE │
│ server/crm/frappe-service.ts │
│ │
│ Autenticação: token apiKey:apiSecret │
│ Base URL: configurável por conector │
│ │
│ Sync Bidirecional: │
│ │
│ Arcádia CRM ←──────────→ ERPNext/Frappe │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Entidade Arcádia DocType Frappe │ │
│ │ ─────────────────── ───────────────── │ │
│ │ crm_leads → Lead │ │
│ │ crm_opportunities→ Opportunity │ │
│ │ crm_products → Item │ │
│ │ crm_partners → Supplier (tipo: partner) │ │
│ │ crm_clients → Customer │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ Funcionalidades: │
│ • POST /frappe/connectors → Cria conector │
│ • POST /frappe/connectors/:id/test → Testa conexão │
│ • POST /frappe/connectors/:id/sync → Sincroniza dados │
│ • GET /frappe/connectors/:id/logs → Logs de sync │
│ • CRUD /frappe/connectors/:id/mappings → Mapeamentos │
│ │
│ Tabelas: crm_frappe_connectors, crm_frappe_mappings │
└─────────────────────────────────────────────────────────────┘
```
---
## 7. Google Calendar
```
┌─────────────────────────────────────────────────────────────┐
│ GOOGLE CALENDAR INTEGRATION │
│ server/crm/google-calendar.ts │
│ │
│ OAuth 2.0 Flow: │
│ GET /api/crm/google/auth → Inicia OAuth │
│ GET /api/crm/google/callback → Recebe token │
│ GET /api/crm/google/status → Status da conexão │
│ POST /api/crm/google/disconnect → Desconecta │
│ POST /api/crm/google/sync → Sincroniza eventos │
│ GET /api/crm/google/events → Lista eventos │
│ POST /api/crm/google/events → Cria evento │
│ │
│ Tabela: crm_google_tokens │
│ Tabela: crm_events (sincronizado) │
└─────────────────────────────────────────────────────────────┘
```
---
## 8. Banco de Dados — Todas as Tabelas
### 8.1 — Tabelas CRM (crm_*) — 30 tabelas
#### Entidades Principais
```
┌──────────────────────────┐ ┌──────────────────────────┐
│ crm_leads │ │ crm_clients │
│ │ │ │
│ id, tenantId, name, │ │ id, tenantId, name, │
│ email, phone, company, │ │ email, phone, company, │
│ source, status (new| │ │ type, address, city, │
│ contacted|qualified| │ │ state, notes, segment, │
│ proposal|won|lost), │ │ isActive, createdAt, │
│ notes, assignedTo, │ │ updatedAt │
│ estimatedValue, │ │ │
│ createdAt, updatedAt │ └──────────────────────────┘
└──────────────────────────┘
┌──────────────────────────┐ ┌──────────────────────────┐
│ crm_opportunities │ │ crm_partners │
│ │ │ │
│ id, tenantId, name, │ │ id, tenantId, companyName,│
│ clientId, value, │ │ contactName, email, phone,│
│ probability, stageId, │ │ type (reseller|referral| │
│ expectedCloseDate, │ │ integrator|consultant), │
│ status (open|won|lost), │ │ status (active|inactive| │
│ lostReason, assignedTo, │ │ suspended), tier (gold| │
│ notes, approvalStatus, │ │ silver|bronze|platinum), │
│ approvedBy, createdAt │ │ commissionRate, region, │
│ │ │ specializations, notes │
└──────────────────────────┘ └──────────────────────────┘
```
#### Pipeline e Propostas
```
┌───────────────────────────┐ ┌───────────────────────────┐
│ crm_pipeline_stages │ │ crm_proposals │
│ │ │ │
│ id, tenantId, name, │ │ id, tenantId, opportunityId│
│ order, color, isDefault, │ │ title, description, │
│ probability, createdAt │ │ totalValue, discount, │
│ │ │ validUntil, status (draft| │
│ │ │ sent|approved|rejected), │
│ │ │ approvedBy, notes, │
│ │ │ createdAt, updatedAt │
└───────────────────────────┘ └───────────────────────────┘
┌───────────────────────────┐
│ crm_proposal_items │
│ │
│ id, proposalId, productId, │
│ description, quantity, │
│ unitPrice, discount, │
│ totalPrice, createdAt │
└───────────────────────────┘
```
#### Contratos e Financeiro
```
┌───────────────────────────┐ ┌───────────────────────────┐
│ crm_contracts │ │ crm_revenue_schedule │
│ │ │ │
│ id, tenantId, name, │ │ id, contractId, month, │
│ clientId, partnerId, │ │ dueDate, value, status │
│ type (recurring|one_time), │ │ (pending|paid|overdue), │
│ totalValue, monthlyValue, │ │ paidDate, createdAt │
│ startDate, endDate, │ │ │
│ billingCycle (monthly| │ └───────────────────────────┘
│ yearly), status (draft| │
│ active|completed|cancelled)│ ┌───────────────────────────┐
│ autoRenew, notes, │ │ crm_contract_milestones │
│ createdAt, updatedAt │ │ │
└───────────────────────────┘ │ id, contractId, name, │
│ description, dueDate, │
┌───────────────────────────┐ │ status (pending|in_progress│
│ crm_commission_rules │ │ |completed|delayed), │
│ │ │ completedAt, notes │
│ id, tenantId, name, │ └───────────────────────────┘
│ revenueType (recurring| │
│ one_time), saleType │
│ (direct|partner), │
│ percentage, monthFrom, │
│ monthTo, isActive │
└───────────────────────────┘
┌───────────────────────────┐
│ crm_commissions │
│ │
│ id, tenantId, contractId, │
│ partnerId, userId, │
│ revenueScheduleId, month, │
│ baseValue, percentage, │
│ commissionValue, status │
│ (pending|approved|paid), │
│ paidDate, createdAt │
└───────────────────────────┘
```
#### Comunicação
```
┌───────────────────────────┐ ┌───────────────────────────┐
│ crm_channels │ │ crm_threads │
│ │ │ │
│ id, tenantId, name, │ │ id, tenantId, channelId, │
│ type (whatsapp|email|sms| │ │ contactPhone, contactName, │
│ telegram), status │ │ status (open|waiting| │
│ (connected|disconnected| │ │ resolved|closed), │
│ pending_qr), identifier, │ │ assignedTo, priority, │
│ qrCode, sessionData, │ │ subject, tags, unreadCount,│
│ lastConnectedAt, │ │ lastMessageAt, closedAt, │
│ createdAt, updatedAt │ │ createdAt, updatedAt │
└───────────────────────────┘ └───────────────────────────┘
┌───────────────────────────┐ ┌───────────────────────────┐
│ crm_messages │ │ crm_quick_messages │
│ │ │ │
│ id, threadId, direction │ │ id, tenantId, title, │
│ (incoming|outgoing), │ │ content, shortcut, │
│ content, contentType │ │ category, isActive, │
│ (text|image|audio|video| │ │ createdAt │
│ document), externalId, │ │ │
│ status (sent|delivered| │ │ Uso: /atalho → mensagem │
│ read|failed), senderName, │ │ rápida pré-definida │
│ metadata, createdAt │ │ │
└───────────────────────────┘ └───────────────────────────┘
```
#### Outros
```
┌───────────────────────────┐ ┌───────────────────────────┐
│ crm_products │ │ crm_opportunity_products │
│ │ │ │
│ id, tenantId, name, sku, │ │ id, opportunityId, │
│ description, category, │ │ productId, quantity, │
│ unitPrice, costPrice, │ │ unitPrice, discount, │
│ type (product|service| │ │ totalPrice │
│ subscription), isActive │ └───────────────────────────┘
└───────────────────────────┘
┌───────────────────────────┐ ┌───────────────────────────┐
│ crm_campaigns │ │ crm_campaign_contacts │
│ │ │ │
│ id, tenantId, name, type, │ │ id, campaignId, contactId, │
│ status, channelId, │ │ status (pending|sent| │
│ message, scheduledAt, │ │ delivered|failed|replied), │
│ sentCount, deliveredCount, │ │ sentAt, deliveredAt, │
│ failedCount, createdAt │ │ repliedAt │
└───────────────────────────┘ └───────────────────────────┘
┌───────────────────────────┐ ┌───────────────────────────┐
│ crm_events │ │ crm_google_tokens │
│ │ │ │
│ id, tenantId, title, │ │ id, userId, accessToken, │
│ description, type, │ │ refreshToken, expiresAt, │
│ startDate, endDate, │ │ calendarId, createdAt │
│ isAllDay, location, │ │ │
│ attendees, googleEventId, │ └───────────────────────────┘
│ color, reminders, │
│ createdBy, createdAt │ ┌───────────────────────────┐
│ │ │ crm_opportunity_registrations│
└───────────────────────────┘ │ │
│ id, opportunityId, partnerId,│
┌───────────────────────────┐ │ status (pending|approved| │
│ crm_partner_certifications │ │ rejected), registeredAt │
│ │ └───────────────────────────┘
│ id, partnerId, name, │
│ issuer, certDate, expDate, │ ┌───────────────────────────┐
│ status, documentUrl │ │ crm_partner_performance │
│ │ │ │
└───────────────────────────┘ │ id, partnerId, period, │
│ revenue, deals, activeContracts│
│ satisfaction, churnRate │
└───────────────────────────┘
```
### 8.2 — Tabelas XOS CRM (xos_*) — 15 tabelas
```
xos_contacts → Contatos unificados (nome, email, phone, company, tags)
xos_companies → Empresas (nome, CNPJ, segmento, tamanho, receita)
xos_pipelines → Pipelines customizáveis
xos_pipeline_stages → Estágios de pipeline (nome, ordem, cor, prob%)
xos_deals → Negócios/Deals (valor, estágio, expected close)
xos_conversations → Conversas (canal, status, prioridade)
xos_messages → Mensagens individuais
xos_tickets → Tickets de suporte (SLA, prioridade)
xos_campaigns → Campanhas de marketing
xos_automations → Regras de automação (trigger, action)
xos_activities → Atividades (calls, emails, meetings, tasks)
xos_queues → Filas de atendimento
xos_queue_users → Usuários nas filas
xos_internal_notes → Notas internas de deals/contatos
xos_quick_messages → Mensagens rápidas/templates
```
### 8.3 — Tabelas Comunicação Unificada (comm_*) — 8 tabelas
```
comm_channels → Canais unificados
comm_contacts → Contatos unificados (merge CRM + XOS + WA)
comm_threads → Threads unificadas
comm_messages → Mensagens unificadas
comm_queues → Filas unificadas
comm_queue_members → Membros das filas
comm_quick_messages → Templates unificados
comm_events → Eventos (alimentam Knowledge Graph)
```
---
## 9. API REST Completa (/api/crm/*)
### Parceiros
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/partners` | Lista parceiros |
| GET | `/partners/:id` | Detalhe do parceiro |
| POST | `/partners` | Cria parceiro |
| PATCH | `/partners/:id` | Atualiza parceiro |
| DELETE | `/partners/:id` | Remove parceiro |
| GET | `/partners/:id/certifications` | Certificações |
| POST | `/partners/:id/certifications` | Adiciona certificação |
| GET | `/partners/:id/performance` | Performance |
| POST | `/partners/:id/performance` | Registra performance |
| POST | `/partners/:id/convert-to-client` | Converte para cliente |
### Clientes
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/clients` | Lista clientes |
| GET | `/clients/:id` | Detalhe do cliente |
| POST | `/clients` | Cria cliente |
| PATCH | `/clients/:id` | Atualiza cliente |
| DELETE | `/clients/:id` | Remove cliente |
### Leads
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/leads` | Lista leads |
| GET | `/leads/:id` | Detalhe do lead |
| POST | `/leads` | Cria lead |
| PATCH | `/leads/:id` | Atualiza lead |
| DELETE | `/leads/:id` | Remove lead |
| POST | `/leads/:id/convert` | Converte para Opportunity + Client |
### Oportunidades
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/opportunities` | Lista oportunidades |
| GET | `/opportunities/:id` | Detalhe |
| POST | `/opportunities` | Cria oportunidade |
| PATCH | `/opportunities/:id` | Atualiza |
| DELETE | `/opportunities/:id` | Remove |
| PATCH | `/opportunities/:id/stage` | Muda estágio do pipeline |
| POST | `/opportunities/:id/won` | Marca como ganha |
| POST | `/opportunities/:id/lost` | Marca como perdida |
| POST | `/opportunities/:id/approve` | Aprova (governance) |
| POST | `/opportunities/:id/reject` | Rejeita |
| POST | `/opportunities/:id/open-project` | Abre projeto no Compass |
| POST | `/opportunities/:id/bill` | Gera faturamento |
| GET | `/opportunities/:id/products` | Produtos da opp |
| POST | `/opportunities/:id/products` | Adiciona produto |
| DELETE | `/opportunity-products/:id` | Remove produto |
| GET | `/opportunities/:id/proposals` | Propostas da opp |
### Propostas
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/proposals` | Lista propostas |
| GET | `/proposals/:id` | Detalhe |
| POST | `/proposals` | Cria proposta |
| PATCH | `/proposals/:id` | Atualiza |
| DELETE | `/proposals/:id` | Remove |
| GET | `/proposals/:id/items` | Itens da proposta |
| POST | `/proposals/:id/items` | Adiciona item |
| PATCH | `/proposal-items/:id` | Atualiza item |
| DELETE | `/proposal-items/:id` | Remove item |
### Contratos
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/contracts` | Lista contratos |
| GET | `/contracts/:id` | Detalhe |
| POST | `/contracts` | Cria contrato |
| PATCH | `/contracts/:id` | Atualiza |
| GET | `/contracts/:id/revenue` | Revenue schedule |
| GET | `/contracts/:id/milestones` | Marcos |
| POST | `/contracts/:id/milestones` | Adiciona marco |
| PATCH | `/milestones/:id` | Atualiza marco |
| DELETE | `/milestones/:id` | Remove marco |
| POST | `/contracts/:id/process-commissions` | Calcula comissões |
| POST | `/contracts/:id/extend-schedule` | Estende schedule |
| POST | `/contracts/extend-all` | Estende todos |
### Comissões
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/commission-rules` | Lista regras |
| POST | `/commission-rules` | Cria regra |
| POST | `/commission-rules/seed` | Seed regras padrão |
| GET | `/commissions` | Lista comissões |
| GET | `/commissions/summary` | Resumo financeiro |
| POST | `/commissions/:id/mark-paid` | Marca como paga |
### Comunicação
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/channels` | Lista canais |
| POST | `/channels` | Cria canal |
| PATCH | `/channels/:id` | Atualiza canal |
| POST | `/channels/:id/whatsapp/connect` | Conecta WhatsApp |
| POST | `/channels/whatsapp/new` | Novo canal WhatsApp |
| POST | `/channels/:id/whatsapp/disconnect` | Desconecta |
| GET | `/threads` | Lista threads |
| GET | `/threads/:id` | Detalhe da thread |
| POST | `/threads` | Cria thread |
| PATCH | `/threads/:id` | Atualiza thread |
| GET | `/threads/:id/messages` | Mensagens da thread |
| POST | `/threads/:id/messages` | Envia mensagem |
| POST | `/threads/:id/send` | Envia via WhatsApp |
| POST | `/threads/:id/read` | Marca como lida |
| POST | `/threads/:id/assign` | Atribui a agente |
| POST | `/threads/:id/close` | Fecha thread |
| POST | `/threads/:id/reopen` | Reabre thread |
| GET | `/threads/stats` | Estatísticas de threads |
| GET | `/quick-messages` | Mensagens rápidas |
| POST | `/quick-messages` | Cria mensagem rápida |
### Produtos
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/products` | Lista produtos |
| GET | `/products/:id` | Detalhe |
| POST | `/products` | Cria produto |
| PATCH | `/products/:id` | Atualiza |
| DELETE | `/products/:id` | Remove |
### Pipeline
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/pipeline-stages` | Lista estágios |
| POST | `/pipeline-stages` | Cria estágio |
| PATCH | `/pipeline-stages/:id` | Atualiza |
| DELETE | `/pipeline-stages/:id` | Remove |
### Eventos e Calendário
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/events` | Lista eventos |
| POST | `/events` | Cria evento |
| PATCH | `/events/:id` | Atualiza |
| PUT | `/events/:id` | Atualiza (full) |
| DELETE | `/events/:id` | Remove |
| GET | `/google/auth` | Inicia OAuth Google |
| GET | `/google/callback` | Callback OAuth |
| GET | `/google/status` | Status conexão |
| POST | `/google/disconnect` | Desconecta Google |
| POST | `/google/sync` | Sincroniza eventos |
| GET | `/google/events` | Eventos Google |
| POST | `/google/events` | Cria evento Google |
### Frappe/ERPNext
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/frappe/connectors` | Lista conectores |
| GET | `/frappe/connectors/:id` | Detalhe |
| POST | `/frappe/connectors` | Cria conector |
| PATCH | `/frappe/connectors/:id` | Atualiza |
| DELETE | `/frappe/connectors/:id` | Remove |
| POST | `/frappe/connectors/:id/test` | Testa conexão |
| POST | `/frappe/connectors/:id/sync` | Sincroniza dados |
| GET | `/frappe/connectors/:id/logs` | Logs de sync |
| GET | `/frappe/connectors/:id/mappings` | Mapeamentos |
| POST | `/frappe/connectors/:id/mappings` | Cria mapeamento |
### Estatísticas
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/stats` | KPIs gerais |
| GET | `/stats/sales` | Estatísticas de vendas |
**Total: 90+ endpoints**
---
## 10. Arquivos-Chave
| Arquivo | Função | Linhas |
|---------|--------|--------|
| `client/src/pages/Crm.tsx` | Interface principal (12 tabs) | 2.700 |
| `client/src/pages/XosCrm.tsx` | CRM Kanban/Pipeline | 752 |
| `client/src/pages/WhatsApp.tsx` | Interface WhatsApp | — |
| `server/crm/routes.ts` | API REST (90+ endpoints) | 1.592 |
| `server/crm/storage.ts` | CrmStorage class (Drizzle ORM) | 708 |
| `server/crm/communication.ts` | CommunicationService | 289 |
| `server/crm/commission-engine.ts` | CommissionEngine | 418 |
| `server/crm/whatsapp-bridge.ts` | WhatsAppBridge | 126 |
| `server/crm/frappe-service.ts` | Integração ERPNext | — |
| `server/crm/google-calendar.ts` | Google Calendar OAuth | — |
| `server/whatsapp/service.ts` | WhatsApp (Baileys) | — |
| `server/communication/engine.ts` | Motor Comunicação (:8006) | — |
| `shared/schema.ts` | 53 tabelas CRM/XOS/Comm | — |
---
## 11. Resumo Visual
```
┌─────────────────────────────────────────────────────────────────────┐
│ ARCÁDIA CRM STACK │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ FRONTEND │ │
│ │ Crm.tsx (12 tabs) + XosCrm.tsx (Kanban) + WhatsApp.tsx │ │
│ └────────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────▼────────────────────────────────┐ │
│ │ API: /api/crm/* — 90+ endpoints │ │
│ │ Router → CrmStorage → PostgreSQL │ │
│ └───┬─────────┬─────────┬──────────┬──────────┬──────────────┘ │
│ │ │ │ │ │ │
│ ┌───▼───┐ ┌───▼───┐ ┌───▼────┐ ┌───▼───┐ ┌───▼────────┐ │
│ │Comm │ │Comissão│ │WhatsApp│ │Frappe │ │Google Cal │ │
│ │Service│ │Engine │ │Bridge │ │Service│ │OAuth │ │
│ └───┬───┘ └───────┘ └───┬────┘ └───────┘ └────────────┘ │
│ │ │ │
│ │ ┌─────▼──────┐ │
│ │ │ Baileys │ │
│ │ │ WhatsApp │ │
│ │ │ Multi-Sess│ │
│ │ └─────┬──────┘ │
│ │ │ │
│ ┌───▼────────────────────▼──────────────────────────────────┐ │
│ │ Motor de Comunicação (:8006) │ │
│ │ Unifica: CRM + XOS + WhatsApp → comm_* tables │ │
│ │ Alimenta: Knowledge Graph + Manus IA │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ PostgreSQL: 53 tabelas (30 crm_* + 15 xos_* + 8 comm_*) │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```

View File

@ -1,734 +0,0 @@
# Arcádia Suite — Mapa de Microserviços para Coolify/Docker
> Documento de referência para containerização e deploy em Coolify.
> Atualizado em: Março 2026
---
## 1. Visão Geral da Arquitetura
```
┌──────────────────────────────┐
│ COOLIFY / TRAEFIK │
│ Reverse Proxy + TLS + DNS │
└──────────┬───────────────────┘
┌─────────────────────┼─────────────────────┐
│ │ │
┌────────▼────────┐ ┌────────▼────────┐ ┌─────────▼────────┐
│ arcadia-gateway │ │ arcadia-plus │ │ arcadia-frontend│
│ (Node.js:5000) │ │ (PHP/Nginx) │ │ (Nginx/Static) │
│ API + WebSocket │ │ :8080 │ │ :80 (React SPA) │
└────────┬────────┘ └────────┬────────┘ └──────────────────┘
│ │
┌─────────┼──────────────┬──────┘
│ │ │
┌───▼──┐ ┌───▼───┐ ┌────────▼────────┐
│Postgres│ │MySQL │ │ Redis (futuro) │
│ :5432 │ │ :3306 │ │ :6379 │
└───┬────┘ └───────┘ └─────────────────┘
│ (DATABASE_URL compartilhado)
┌───▼──────────────────────────────────────────────────────┐
│ MOTORES (FastAPI / Node) │
│ │
│ ┌─────────┐ ┌─────────┐ ┌──────┐ ┌──────┐ ┌──────────┐ │
│ │ Fisco │ │Contábil │ │ BI │ │Auto │ │ Comm │ │
│ │ :8002 │ │ :8003 │ │:8004 │ │:8005 │ │ :8006 │ │
│ │ Python │ │ Python │ │Python│ │Python│ │ Node/TS │ │
│ └─────────┘ └─────────┘ └──────┘ └──────┘ └──────────┘ │
└──────────────────────────────────────────────────────────┘
```
---
## 2. Inventário de Serviços (9 containers + 3 bancos)
### 2.1 — `arcadia-gateway` (Container Principal)
| Item | Valor |
|------|-------|
| **Tecnologia** | Node.js 20 + Express + Socket.IO |
| **Porta** | 5000 |
| **Entrada** | `server/index.ts` (build: `dist/index.cjs`) |
| **Banco** | PostgreSQL (Drizzle ORM) |
| **Responsabilidades** | API REST principal, autenticação/sessões, WebSocket (chat + WhatsApp), proxy para motores, Dev Center (6 agentes XOS), Manus IA (OpenAI), Knowledge Graph, Learning System, Governance, MCP/A2A protocols |
| **Variáveis obrigatórias** | `DATABASE_URL`, `PGHOST`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`, `SESSION_SECRET`, `OPENAI_API_KEY`, `GITHUB_TOKEN` |
| **Variáveis opcionais** | `ERPNEXT_URL`, `ERPNEXT_API_KEY`, `ERPNEXT_API_SECRET`, `GITHUB_OWNER`, `GITHUB_REPO` |
| **Healthcheck** | `GET /api/health` ou `GET /` |
| **Dockerfile existente** | `./Dockerfile` (node:20-alpine, multi-stage) |
```dockerfile
# Build: npm run build → dist/index.cjs
# Run: node dist/index.cjs
# Portas: 5000
```
### 2.2 — `arcadia-frontend` (SPA React)
| Item | Valor |
|------|-------|
| **Tecnologia** | React 18 + Vite + TypeScript + Tailwind + shadcn/ui |
| **Build** | `npm run build``dist/public/` |
| **Servir com** | Nginx (static files) ou incluir no Gateway |
| **Porta** | 80/443 (via Coolify/Traefik) |
> **Nota**: Atualmente o frontend é servido pelo Gateway (Express serve os assets estáticos). Para microsserviços, pode ser separado em um container Nginx leve ou mantido no Gateway.
**Opção A — Junto com Gateway** (recomendado para início):
O Dockerfile atual já faz isso. O Express serve o React build.
**Opção B — Separado** (para escalar independentemente):
```dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm install --legacy-peer-deps
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist/public /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
```
### 2.3 — `arcadia-plus` (ERP Laravel/PHP)
| Item | Valor |
|------|-------|
| **Tecnologia** | PHP 8.2 + Laravel + Nginx |
| **Porta** | 8080 (interno) → exposto via proxy `/plus` |
| **Banco** | MySQL 8.0 (separado do PostgreSQL) |
| **Entrada** | `plus/public/index.php` via `php artisan serve` ou PHP-FPM + Nginx |
| **Dockerfile existente** | `plus/Dockerfile` (php:8.2-fpm) |
| **docker-compose existente** | `plus/docker-compose.yml` (app + web + db) |
| **Dependências** | Composer, Node.js 18 (para assets) |
| **Variáveis obrigatórias** | `DB_HOST`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`, `MYSQL_ROOT_PASSWORD`, `APP_KEY`, `APP_URL` |
| **Módulos** | NF-e/NFC-e/CT-e/MDF-e, PDV, Menu Digital, Ordem de Serviço, Financeiro, Estoque, CRM |
**Containers necessários para o Plus**:
1. `arcadia-plus-app` — PHP-FPM (porta 9000 interna)
2. `arcadia-plus-web` — Nginx (porta 8080, proxy para PHP-FPM)
3. `arcadia-mysql` — MySQL 8.0 (porta 3306)
### 2.4 — `arcadia-fisco` (Motor Fiscal)
| Item | Valor |
|------|-------|
| **Tecnologia** | Python 3.11 + FastAPI + Uvicorn |
| **Porta** | 8002 |
| **Arquivo** | `server/python/fisco_service.py` |
| **Banco** | Nenhum direto (opera sobre XML) |
| **Dependências Python** | `fastapi`, `uvicorn`, `nfelib`, `signxml`, `lxml`, `zeep`, `cryptography`, `pydantic` |
| **Responsabilidades** | Geração XML NF-e/NFC-e, assinatura digital, comunicação SEFAZ, consulta/cancelamento/inutilização |
| **Variáveis** | `FISCO_PORT=8002` |
| **Healthcheck** | `GET /health` |
```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY server/python/fisco_service.py .
COPY requirements-fisco.txt .
RUN pip install -r requirements-fisco.txt
EXPOSE 8002
CMD ["uvicorn", "fisco_service:app", "--host", "0.0.0.0", "--port", "8002"]
```
### 2.5 — `arcadia-contabil` (Motor Contábil)
| Item | Valor |
|------|-------|
| **Tecnologia** | Python 3.11 + FastAPI |
| **Porta** | 8003 |
| **Arquivo** | `server/python/contabil_service.py` |
| **Banco** | Nenhum direto (recebe dados via POST) |
| **Dependências Python** | `fastapi`, `uvicorn`, `pydantic` |
| **Responsabilidades** | DRE, Balanço Patrimonial, Balancete, Validação de Lançamentos, SPED ECD/ECF, integração NF-e/folha |
| **Variáveis** | `CONTABIL_PORT=8003` |
| **Healthcheck** | `GET /health` |
### 2.6 — `arcadia-bi` (Motor BI/Analytics)
| Item | Valor |
|------|-------|
| **Tecnologia** | Python 3.11 + FastAPI + Pandas + NumPy |
| **Porta** | 8004 |
| **Arquivo** | `server/python/bi_engine.py` |
| **Banco** | PostgreSQL (via `psycopg2`, leitura direta) |
| **Dependências Python** | `fastapi`, `uvicorn`, `pandas`, `numpy`, `psycopg2-binary`, `pydantic` |
| **Responsabilidades** | Exploração de schema, execução SQL, geração de dados para gráficos, Micro-BI, análise de dados |
| **Variáveis** | `BI_PORT=8004`, `DATABASE_URL` |
| **Healthcheck** | `GET /health` |
### 2.7 — `arcadia-automation` (Motor de Automação)
| Item | Valor |
|------|-------|
| **Tecnologia** | Python 3.11 + FastAPI + APScheduler |
| **Porta** | 8005 |
| **Arquivo** | `server/python/automation_engine.py` |
| **Banco** | PostgreSQL (via `psycopg2`) |
| **Dependências Python** | `fastapi`, `uvicorn`, `psycopg2-binary`, `apscheduler`, `pydantic` |
| **Responsabilidades** | Scheduler (cron jobs), Event Bus, Workflow executor |
| **Variáveis** | `AUTOMATION_PORT=8005`, `DATABASE_URL` |
| **Healthcheck** | `GET /health` |
### 2.8 — `arcadia-communication` (Motor de Comunicação)
| Item | Valor |
|------|-------|
| **Tecnologia** | Node.js 20 + Express + TypeScript |
| **Porta** | 8006 |
| **Arquivo** | `server/communication/engine.ts` |
| **Banco** | PostgreSQL (via `@neondatabase/serverless`) |
| **Dependências Node** | `express`, `@neondatabase/serverless`, `ws` |
| **Responsabilidades** | Unifica contatos/threads/mensagens de WhatsApp, CRM, XOS. Canais, filas, mensagens rápidas, eventos |
| **Variáveis** | `COMMUNICATION_PORT=8006`, `DATABASE_URL` |
| **Healthcheck** | `GET /health` |
### 2.9 — `arcadia-people` (Motor RH — opcional/futuro)
| Item | Valor |
|------|-------|
| **Tecnologia** | Python 3.11 + FastAPI |
| **Porta** | 8007 (ajustar — conflito atual com BI na 8004) |
| **Arquivo** | `server/python/people_service.py` |
| **Banco** | Nenhum direto (cálculos puros) |
| **Dependências Python** | `fastapi`, `uvicorn`, `pydantic` |
| **Responsabilidades** | Cálculo INSS/IRRF, folha de pagamento, férias, rescisão, eSocial |
| **Variáveis** | `PEOPLE_PORT=8007` |
---
## 3. Bancos de Dados (3 containers)
### 3.1 — `arcadia-postgres`
| Item | Valor |
|------|-------|
| **Imagem** | `postgres:16-alpine` |
| **Porta** | 5432 |
| **Uso** | Gateway, BI Engine, Automation Engine, Communication Engine |
| **ORM** | Drizzle (Node.js) / psycopg2 (Python) |
| **Schema** | ~100+ tabelas (tenants, users, valuation_*, crm_*, comm_*, xos_*, etc.) |
| **Volume** | `/var/lib/postgresql/data` |
### 3.2 — `arcadia-mysql`
| Item | Valor |
|------|-------|
| **Imagem** | `mysql:8.0` |
| **Porta** | 3306 |
| **Uso** | Exclusivo do Plus (Laravel) |
| **Schema** | 260+ migrações Laravel (produtos, vendas, fiscal, estoque, etc.) |
| **Volume** | `/var/lib/mysql` |
### 3.3 — `arcadia-redis` (futuro/recomendado)
| Item | Valor |
|------|-------|
| **Imagem** | `redis:7-alpine` |
| **Porta** | 6379 |
| **Uso** | Cache de sessões, fila de jobs, pub/sub entre serviços |
| **Recomendação** | Substituir session store do Express (atualmente PostgreSQL) e usar como broker de eventos |
---
## 4. Mapa de Comunicação entre Serviços
```
┌─────────────────────────────────────────────────────────────────┐
│ arcadia-gateway (:5000) │
│ │
│ /api/* → Rotas internas (Express) │
│ /api/fisco/* → proxy → arcadia-fisco:8002 │
│ /api/contabil/* → proxy → arcadia-contabil:8003 │
│ /api/bi/* → proxy → arcadia-bi:8004 │
│ /api/automation/*→ proxy → arcadia-automation:8005 │
│ /api/comm/* → proxy → arcadia-communication:8006 │
│ /plus/* → proxy → arcadia-plus-web:8080 │
│ /metabase/* → proxy → metabase:8088 (se usar) │
│ │
│ WebSocket (Socket.IO) → Chat interno, WhatsApp real-time │
└─────────────────────────────────────────────────────────────────┘
```
### Dependências entre serviços:
| Serviço | Depende de | Comunicação |
|---------|-----------|-------------|
| Gateway | PostgreSQL, todos os motores | HTTP proxy, DB direto |
| Plus | MySQL, Gateway (SSO) | HTTP (proxy reverso) |
| Fisco | Nenhum banco | HTTP (recebe XMLs) |
| Contábil | Nenhum banco | HTTP (recebe dados via POST) |
| BI | PostgreSQL | HTTP + SQL direto no PG |
| Automation | PostgreSQL | HTTP + SQL direto no PG |
| Communication | PostgreSQL | HTTP + SQL direto no PG |
| People | Nenhum banco | HTTP (cálculos puros) |
---
## 5. Docker Compose Completo para Coolify
```yaml
# docker-compose.coolify.yml
version: "3.8"
services:
# ═══════════════════════════════════════════
# BANCOS DE DADOS
# ═══════════════════════════════════════════
postgres:
image: postgres:16-alpine
restart: always
environment:
POSTGRES_USER: ${PGUSER:-arcadia}
POSTGRES_PASSWORD: ${PGPASSWORD}
POSTGRES_DB: ${PGDATABASE:-arcadia}
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${PGUSER:-arcadia}"]
interval: 10s
timeout: 5s
retries: 5
networks:
- arcadia
mysql:
image: mysql:8.0
restart: always
command: >
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
environment:
MYSQL_DATABASE: ${PLUS_DB_DATABASE:-controlplus}
MYSQL_USER: ${PLUS_DB_USERNAME:-controlplus}
MYSQL_PASSWORD: ${PLUS_DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${PLUS_MYSQL_ROOT_PASSWORD}
volumes:
- mysqldata:/var/lib/mysql
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
networks:
- arcadia
redis:
image: redis:7-alpine
restart: always
ports:
- "6379:6379"
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
networks:
- arcadia
# ═══════════════════════════════════════════
# GATEWAY + FRONTEND (Node.js principal)
# ═══════════════════════════════════════════
gateway:
build:
context: .
dockerfile: Dockerfile
restart: always
depends_on:
postgres:
condition: service_healthy
ports:
- "5000:5000"
environment:
NODE_ENV: production
PORT: 5000
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@postgres:5432/${PGDATABASE:-arcadia}
PGHOST: postgres
PGUSER: ${PGUSER:-arcadia}
PGPASSWORD: ${PGPASSWORD}
PGDATABASE: ${PGDATABASE:-arcadia}
SESSION_SECRET: ${SESSION_SECRET}
OPENAI_API_KEY: ${OPENAI_API_KEY}
GITHUB_TOKEN: ${GITHUB_TOKEN}
ERPNEXT_URL: ${ERPNEXT_URL:-}
ERPNEXT_API_KEY: ${ERPNEXT_API_KEY:-}
ERPNEXT_API_SECRET: ${ERPNEXT_API_SECRET:-}
# URLs dos motores (comunicação interna Docker)
FISCO_URL: http://fisco:8002
CONTABIL_URL: http://contabil:8003
BI_URL: http://bi:8004
AUTOMATION_URL: http://automation:8005
COMMUNICATION_URL: http://communication:8006
PLUS_URL: http://plus-web:8080
REDIS_URL: redis://redis:6379
volumes:
- uploads:/app/uploads
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:5000/"]
interval: 30s
timeout: 10s
start_period: 40s
networks:
- arcadia
# ═══════════════════════════════════════════
# MOTORES PYTHON (FastAPI)
# ═══════════════════════════════════════════
fisco:
build:
context: .
dockerfile: docker/fisco.Dockerfile
restart: always
ports:
- "8002:8002"
environment:
FISCO_PORT: 8002
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8002/health"]
interval: 30s
networks:
- arcadia
contabil:
build:
context: .
dockerfile: docker/contabil.Dockerfile
restart: always
ports:
- "8003:8003"
environment:
CONTABIL_PORT: 8003
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8003/health"]
interval: 30s
networks:
- arcadia
bi:
build:
context: .
dockerfile: docker/bi.Dockerfile
restart: always
depends_on:
postgres:
condition: service_healthy
ports:
- "8004:8004"
environment:
BI_PORT: 8004
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@postgres:5432/${PGDATABASE:-arcadia}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8004/health"]
interval: 30s
networks:
- arcadia
automation:
build:
context: .
dockerfile: docker/automation.Dockerfile
restart: always
depends_on:
postgres:
condition: service_healthy
ports:
- "8005:8005"
environment:
AUTOMATION_PORT: 8005
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@postgres:5432/${PGDATABASE:-arcadia}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8005/health"]
interval: 30s
networks:
- arcadia
communication:
build:
context: .
dockerfile: docker/communication.Dockerfile
restart: always
depends_on:
postgres:
condition: service_healthy
ports:
- "8006:8006"
environment:
COMMUNICATION_PORT: 8006
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@postgres:5432/${PGDATABASE:-arcadia}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8006/health"]
interval: 30s
networks:
- arcadia
# ═══════════════════════════════════════════
# ARCÁDIA PLUS (Laravel/PHP)
# ═══════════════════════════════════════════
plus-app:
build:
context: ./plus
dockerfile: Dockerfile
restart: always
depends_on:
mysql:
condition: service_healthy
env_file:
- ./plus/.env
volumes:
- ./plus:/var/www/html:cached
- plus-vendor:/var/www/html/vendor
networks:
- arcadia
plus-web:
image: nginx:1.25-alpine
restart: always
depends_on:
- plus-app
ports:
- "8080:80"
volumes:
- ./plus:/var/www/html:cached
- ./plus/docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
networks:
- arcadia
volumes:
pgdata:
mysqldata:
redisdata:
uploads:
plus-vendor:
networks:
arcadia:
driver: bridge
```
---
## 6. Variáveis de Ambiente (.env para Coolify)
```env
# ══════════════════════════════════════
# POSTGRESQL (Principal)
# ══════════════════════════════════════
PGUSER=arcadia
PGPASSWORD=SuaSenhaSegura123!
PGDATABASE=arcadia
DATABASE_URL=postgresql://arcadia:SuaSenhaSegura123!@postgres:5432/arcadia
# ══════════════════════════════════════
# GATEWAY (Node.js)
# ══════════════════════════════════════
NODE_ENV=production
PORT=5000
SESSION_SECRET=sua-session-secret-aqui
# ══════════════════════════════════════
# INTELIGÊNCIA ARTIFICIAL
# ══════════════════════════════════════
OPENAI_API_KEY=sk-...
# ══════════════════════════════════════
# GITHUB
# ══════════════════════════════════════
GITHUB_TOKEN=ghp_...
GITHUB_OWNER=jonaspachecoometas
GITHUB_REPO=arcadiasuite
# ══════════════════════════════════════
# ERPNEXT (opcional)
# ══════════════════════════════════════
ERPNEXT_URL=https://seu-erpnext.com
ERPNEXT_API_KEY=
ERPNEXT_API_SECRET=
# ══════════════════════════════════════
# MYSQL (Plus/Laravel)
# ══════════════════════════════════════
PLUS_DB_DATABASE=controlplus
PLUS_DB_USERNAME=controlplus
PLUS_DB_PASSWORD=SenhaMySQLSegura!
PLUS_MYSQL_ROOT_PASSWORD=RootSenha123!
# ══════════════════════════════════════
# REDIS
# ══════════════════════════════════════
REDIS_URL=redis://redis:6379
```
---
## 7. Dockerfiles dos Motores (a criar em `docker/`)
### `docker/fisco.Dockerfile`
```dockerfile
FROM python:3.11-slim
WORKDIR /app
RUN pip install fastapi uvicorn pydantic cryptography lxml zeep signxml
# nfelib se disponível: RUN pip install nfelib
COPY server/python/fisco_service.py ./main.py
EXPOSE 8002
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8002"]
```
### `docker/contabil.Dockerfile`
```dockerfile
FROM python:3.11-slim
WORKDIR /app
RUN pip install fastapi uvicorn pydantic
COPY server/python/contabil_service.py ./main.py
EXPOSE 8003
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8003"]
```
### `docker/bi.Dockerfile`
```dockerfile
FROM python:3.11-slim
WORKDIR /app
RUN pip install fastapi uvicorn pydantic pandas numpy psycopg2-binary
COPY server/python/bi_engine.py ./main.py
EXPOSE 8004
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8004"]
```
### `docker/automation.Dockerfile`
```dockerfile
FROM python:3.11-slim
WORKDIR /app
RUN pip install fastapi uvicorn pydantic psycopg2-binary apscheduler
COPY server/python/automation_engine.py ./main.py
EXPOSE 8005
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8005"]
```
### `docker/communication.Dockerfile`
```dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm install --legacy-peer-deps --production
COPY server/communication/ ./server/communication/
COPY shared/ ./shared/
COPY tsconfig.json ./
EXPOSE 8006
CMD ["npx", "tsx", "server/communication/engine.ts"]
```
---
## 8. Ordem de Deploy no Coolify
### Fase 1 — Infraestrutura (subir primeiro)
1. `arcadia-postgres` — Banco principal
2. `arcadia-mysql` — Banco do Plus
3. `arcadia-redis` — Cache e filas
### Fase 2 — Motores Independentes (podem subir em paralelo)
4. `arcadia-fisco` — Sem dependência de banco
5. `arcadia-contabil` — Sem dependência de banco
6. `arcadia-bi` — Depende do PostgreSQL
7. `arcadia-automation` — Depende do PostgreSQL
8. `arcadia-communication` — Depende do PostgreSQL
### Fase 3 — Aplicações Principais
9. `arcadia-gateway` — Depende de PostgreSQL + todos os motores
10. `arcadia-plus-app` + `arcadia-plus-web` — Depende do MySQL
### Fase 4 — Migrações (após tudo no ar)
```bash
# PostgreSQL (Drizzle)
docker exec arcadia-gateway npx drizzle-kit push
# MySQL (Laravel)
docker exec arcadia-plus-app php artisan migrate --force
docker exec arcadia-plus-app php artisan db:seed --force
```
---
## 9. Ajustes Necessários no Código
### 9.1 — Gateway: URLs dos motores precisam ser configuráveis
Atualmente o código usa `localhost:800X`. Para Docker, precisa usar nomes de serviço:
| Arquivo | Linha | De | Para |
|---------|-------|----|------|
| `server/contabil/routes.ts` | proxy | `localhost:8003` | `process.env.CONTABIL_URL` |
| `server/fisco/routes.ts` | proxy | `localhost:8002` | `process.env.FISCO_URL` |
| `server/bi/proxy.ts` | proxy | `localhost:8004` | `process.env.BI_URL` |
| `server/python/launcher.ts` | spawn | hardcoded | Remover spawns no modo Docker |
| `server/plus/proxy.ts` | proxy | `localhost:8080` | `process.env.PLUS_URL` |
| `server/communication/proxy.ts` | proxy | `localhost:8006` | `process.env.COMMUNICATION_URL` |
### 9.2 — Desabilitar spawns de subprocessos em produção
O Gateway atualmente inicia os motores Python como subprocessos. No Docker cada motor roda em seu próprio container. Adicionar flag:
```typescript
// server/index.ts
if (process.env.DOCKER_MODE !== 'true') {
// spawn python services
startManagedServices();
}
```
### 9.3 — Conflito de portas
| Serviço | Porta atual | Conflito | Solução |
|---------|------------|----------|---------|
| People | 8004 | BI Engine | Mudar para 8007 |
| BI Analysis | 8003 | Contábil | Mesclar no BI Engine (8004) |
---
## 10. Checklist de Migração para Coolify
- [ ] Criar pasta `docker/` com Dockerfiles dos motores
- [ ] Criar `docker-compose.coolify.yml` (baseado na seção 5)
- [ ] Criar `.env.production` com todas as variáveis
- [ ] Ajustar proxies do Gateway para usar variáveis de ambiente
- [ ] Adicionar flag `DOCKER_MODE=true` para desabilitar spawns
- [ ] Resolver conflitos de porta (People → 8007)
- [ ] Testar build de cada Dockerfile isoladamente
- [ ] Configurar Coolify com Docker Compose
- [ ] Rodar migrações PostgreSQL (`drizzle-kit push`)
- [ ] Rodar migrações MySQL (`php artisan migrate`)
- [ ] Configurar domínio e TLS no Coolify/Traefik
- [ ] Configurar volumes persistentes (pgdata, mysqldata, uploads)
- [ ] Testar SSO do Plus via proxy
- [ ] Verificar WebSocket (Socket.IO) funciona via Traefik
- [ ] Configurar healthchecks em todos os serviços
---
## 11. Resumo Visual Rápido
| # | Container | Porta | Tech | Banco | Prioridade |
|---|-----------|-------|------|-------|-----------|
| 1 | postgres | 5432 | PostgreSQL 16 | - | Essencial |
| 2 | mysql | 3306 | MySQL 8.0 | - | Para Plus |
| 3 | redis | 6379 | Redis 7 | - | Recomendado |
| 4 | gateway | 5000 | Node.js 20 | PG | Essencial |
| 5 | fisco | 8002 | Python 3.11 | - | Módulo |
| 6 | contabil | 8003 | Python 3.11 | - | Módulo |
| 7 | bi | 8004 | Python 3.11 | PG | Módulo |
| 8 | automation | 8005 | Python 3.11 | PG | Módulo |
| 9 | communication | 8006 | Node.js 20 | PG | Módulo |
| 10 | plus-app | 9000 | PHP 8.2 FPM | MySQL | Módulo |
| 11 | plus-web | 8080 | Nginx 1.25 | - | Módulo |
| 12 | people | 8007 | Python 3.11 | - | Futuro |
**Total: 12 containers (3 bancos + 1 gateway + 8 motores)**

View File

@ -1,746 +0,0 @@
# Arcádia SOE — Sistema Operacional Empresarial
## Mapa Completo para Deploy em Servidor Dedicado
> Mapa da rota `/soe`: todos os arquivos do módulo, domínios de negócio,
> padrão de motor (adapter), tabelas PostgreSQL, APIs REST e dependências
> externas. Atualizado em: Março 2026.
---
## 1. O que é o SOE
O **Arcádia SOE (Sistema Operacional Empresarial)** é o **kernel central** da Arcádia Suite — o sistema que concentra todos os domínios canônicos de negócio. Enquanto apps como Varejo, Engenharia e Food Service adicionam experiências verticais, o SOE é a camada horizontal que todos compartilham.
**Princípio central:** O SOE **pensa e governa**; os motores (Plus, ERPNext) **executam e registram**.
```
┌─────────────────────────────────────────────────────────────────────┐
│ ARCÁDIA SOE — VISÃO GERAL │
│ │
│ URL: /soe │
│ API: /api/soe/* (alias backward-compatible → /api/erp/*) │
│ │
│ ┌──────── Domínios Canônicos ────────────────────────────────┐ │
│ │ │ │
│ │ Pessoas │ Produtos │ Vendas │ Compras │ Estoque │ │
│ │ Fiscal │ Financ. │ CRM │ Projetos │ Qualidade │ │
│ │ Governa. │ RH │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────── Motores (Adapters) ────────────────────────────────┐ │
│ │ │ │
│ │ motor = "plus" → Arcádia Plus (Laravel :8080) │ │
│ │ motor = "erpnext" → ERPNext/Frappe (externo) │ │
│ │ motor = nativo → PostgreSQL local (default fallback) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────── Apps que estendem o SOE ───────────────────────────┐ │
│ │ Varejo (Retail) │ Engenharia │ Food Service │ │
│ │ Assistência Tec │ Educação │ (outros segmentos) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 2. Arquivos do Módulo SOE — Lista Completa para Export
### 2.1 — Frontend (React/TypeScript)
| Arquivo | Função | Linhas |
|---------|--------|--------|
| `client/src/pages/SOE.tsx` | **Página principal** do SOE — 9 abas, CRUD completo | 2.414 |
| `client/src/pages/ERP.tsx` | Legacy ERP — mantido para referência histórica | 2.197 |
| `client/src/contexts/SoeMotorContext.tsx` | **Context do Motor** — gerencia plus/erpnext via localStorage | 63 |
| `client/src/contexts/ErpProfileContext.tsx` | Alias legacy do SoeMotorContext (backward compat.) | 52 |
### 2.2 — Backend (Node.js/TypeScript)
| Arquivo | Função | Linhas |
|---------|--------|--------|
| `server/erp/routes.ts` | **Rotas SOE core** — /api/erp/* (= /api/soe/*) | 1.343 |
| `server/erp/index.ts` | Setup do módulo + alias /api/soe → /api/erp | 210 |
| `server/retail/routes.ts` | **Rotas do Varejo** — /api/retail/* | 5.218 |
| `server/retail/index.ts` | Setup do módulo retail | — |
| `server/retail/sync-service.ts` | Sincronização retail ↔ Plus | — |
| `server/retail/plus-sync.ts` | Ponte retail → Arcádia Plus | — |
| `server/plus/client.ts` | **Cliente HTTP** para Arcádia Plus (:8080) | 376 |
| `server/plus/routes.ts` | Rotas de integração Plus | — |
| `server/plus/launcher.ts` | Iniciador do serviço Plus | — |
| `server/plus/proxy.ts` | Proxy transparente → Plus (:8080) | — |
| `server/plus/sso.ts` | Single Sign-On entre SOE e Plus | — |
| `server/erpnext/routes.ts` | Rotas de integração ERPNext | — |
| `server/erpnext/index.ts` | Setup do módulo ERPNext | — |
### 2.3 — Schema PostgreSQL (Drizzle ORM)
| Arquivo | Tabelas relacionadas ao SOE |
|---------|----------------------------|
| `shared/schema.ts` (L348) | `erp_connections` |
| `shared/schema.ts` (L3303) | `erp_segments`, `erp_config` |
| `shared/schema.ts` (L33673517) | `customers`, `suppliers`, `products`, `sales_orders`, `sales_order_items`, `purchase_orders`, `purchase_order_items` |
| `shared/schema.ts` (L50825617) | Tabelas retail (ver seção 5) |
| `shared/schema.ts` (L6204) | `persons`, `person_roles` |
| `shared/schema.ts` (L4372) | `fin_payment_methods`, `fin_payment_plans` |
---
## 3. Arquitetura de Arquivos
```
workspace/
├── client/src/
│ ├── pages/
│ │ ├── SOE.tsx ← Página principal (/soe)
│ │ └── ERP.tsx ← Legacy (mantido)
│ │
│ └── contexts/
│ ├── SoeMotorContext.tsx ← Motor context (plus | erpnext)
│ └── ErpProfileContext.tsx ← Alias backward-compat
├── server/
│ ├── erp/
│ │ ├── routes.ts ← /api/erp/* (= /api/soe/*)
│ │ └── index.ts ← Setup + alias soe→erp
│ │
│ ├── retail/
│ │ ├── routes.ts ← /api/retail/*
│ │ ├── index.ts
│ │ ├── sync-service.ts
│ │ └── plus-sync.ts
│ │
│ ├── plus/
│ │ ├── client.ts ← HTTP client → Plus :8080
│ │ ├── routes.ts
│ │ ├── proxy.ts
│ │ ├── launcher.ts
│ │ └── sso.ts
│ │
│ └── erpnext/
│ ├── routes.ts
│ └── index.ts
└── shared/
└── schema.ts ← Tabelas SOE + Retail
```
---
## 4. Motor de Execução (Adapter Pattern)
O SOE usa um **padrão de adapter** para rotear operações para o motor correto:
```
┌─────────────────────────────────────────────────────────────────────┐
│ SOEOMOTOR — ADAPTER PATTERN │
│ │
│ SoeMotorContext.tsx │
│ │
│ export type SoeMotor = "plus" | "erpnext" │
│ │
│ Persistência: localStorage["arcadia_soe_motor"] │
│ │
│ getApiUrl(localPath, plusPath, erpnextPath?) │
│ │
│ ┌──────── motor = "plus" ────────────────────────────────────┐ │
│ │ → /plus/api{plusPath} │ │
│ │ → Passa pelo proxy transparente do Plus │ │
│ │ → Arcádia Plus (Laravel) em :8080 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────── motor = "erpnext" ─────────────────────────────────┐ │
│ │ → erpnextPath ou localPath │ │
│ │ → ERPNext API (ERPNEXT_URL env var) │ │
│ │ → Autenticado via ERPNEXT_API_KEY + ERPNEXT_API_SECRET │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────── motor = nativo (fallback) ─────────────────────────┐ │
│ │ → localPath (/api/erp/*) │ │
│ │ → PostgreSQL local via Drizzle ORM │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ Seleção na UI: SOE.tsx → Config tab → Dropdown de motor │
│ Plus ativo: isERPNextConnected verifica /api/erpnext/connections │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 5. Domínios e Abas do SOE
```
┌─────────────────────────────────────────────────────────────────────┐
│ SOE.tsx — 9 ABAS PRINCIPAIS │
│ │
│ ┌── Tab: dashboard ──────────────────────────────────────────┐ │
│ │ • Stats: clientes, produtos, pedidos, total vendas │ │
│ │ • Cards com indicadores-chave do negócio │ │
│ │ • Acesso rápido às seções │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌── Tab: persons (Pessoas) ───────────────────────────────────┐ │
│ │ • Clientes, Fornecedores, Funcionários, Técnicos, Parceiros│ │
│ │ • Busca + filtro por papel (role) │ │
│ │ • Campos: nome, CPF/CNPJ, email, telefone, endereço │ │
│ │ • Tabelas: persons + person_roles │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌── Tab: products (Produtos) ─────────────────────────────────┐ │
│ │ • Cadastro de produtos/serviços │ │
│ │ • Rastreamento: IMEI, Número de Série, sem rastreamento │ │
│ │ • Grupos de produto, unidades (UN/KG/M/L/CX/PC) │ │
│ │ • Estoque por produto, devices batch import │ │
│ │ • Tabela: products + mobile_devices │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌── Tab: sales (Vendas) ──────────────────────────────────────┐ │
│ │ • Pedidos de venda │ │
│ │ • Status: draft → confirmed → delivered │ │
│ │ • Geração de NF-e a partir do pedido │ │
│ │ • Formas de pagamento: Dinheiro/Cartão/PIX/Boleto/Prazo │ │
│ │ • Tabelas: sales_orders + sales_order_items │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌── Tab: purchases (Compras) ─────────────────────────────────┐ │
│ │ • Pedidos de compra │ │
│ │ • Associados a fornecedores │ │
│ │ • Tabelas: purchase_orders + purchase_order_items │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌── Tab: financial (Financeiro) ──────────────────────────────┐ │
│ │ • Visão financeira consolidada │ │
│ │ • Planos de pagamento e parcelamento │ │
│ │ • Tabelas: fin_payment_methods + fin_payment_plans │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌── Tab: crm ─────────────────────────────────────────────────┐ │
│ │ • Módulo CRM inline no SOE │ │
│ │ • Integrado via useTenantFeatures (feature: crm) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌── Tab: sync (Sincronização) ────────────────────────────────┐ │
│ │ • Status dos motores (Plus/ERPNext) │ │
│ │ • Módulos ativos/inativos do tenant │ │
│ │ • Botões de sync manual por entidade │ │
│ │ • Indica se Plus está online (:8080) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌── Tab: config (Configuração) ───────────────────────────────┐ │
│ │ • Dados da empresa: razão social, CNPJ, regime tributário │ │
│ │ • Inscrição estadual/municipal │ │
│ │ • Segmento de negócio │ │
│ │ • Seletor de motor: Plus ou ERPNext │ │
│ │ • Tabela: erp_config + erp_segments │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 6. Tabelas PostgreSQL — SOE Core
### 6.1 — erp_connections (Conexões com ERPs externos)
```
erp_connections
├── id serial PK
├── userId FK → users.id
├── erpType "erpnext" | "plus" | "omie" | etc.
├── name Nome da conexão
├── url URL do ERP
├── apiKey Chave de API
├── apiSecret Secret
├── isActive boolean
└── createdAt, updatedAt
```
### 6.2 — erp_segments (Segmentos de Negócio)
```
erp_segments
├── id serial PK
├── name Nome (Varejo, Serviços, Indústria, etc.)
├── description Descrição
├── icon Ícone
└── createdAt
```
### 6.3 — erp_config (Configuração da Empresa)
```
erp_config
├── id serial PK
├── userId FK → users.id
├── companyName Razão Social
├── tradeName Nome Fantasia
├── taxId CNPJ
├── taxRegime simples | lucro_presumido | lucro_real
├── stateRegistration Inscrição Estadual
├── cityRegistration Inscrição Municipal
├── segmentId FK → erp_segments.id
├── address Endereço completo
└── createdAt, updatedAt
```
### 6.4 — customers (Clientes)
```
customers
├── id serial PK
├── userId FK → users.id
├── code Código interno
├── name Nome
├── email E-mail
├── phone Telefone
├── document CPF/CNPJ
├── type company | individual
├── address Endereço
└── createdAt, updatedAt
```
### 6.5 — suppliers (Fornecedores)
```
suppliers
├── id serial PK
├── userId FK → users.id
├── code Código interno
├── name Nome/Razão Social
├── email, phone
├── document CNPJ/CPF
└── createdAt, updatedAt
```
### 6.6 — products (Produtos)
```
products
├── id serial PK
├── userId FK → users.id
├── code Código/SKU
├── name Nome do produto
├── description
├── price Decimal(10,2)
├── costPrice Preço de custo
├── unit UN | KG | M | L | CX | PC
├── stock Estoque atual
├── minStock Estoque mínimo
├── groupId Grupo de produto
├── requiresSerialTracking boolean
├── trackingType none | imei | serial
└── createdAt, updatedAt
```
### 6.7 — sales_orders + sales_order_items
```
sales_orders
├── id serial PK
├── userId FK → users.id
├── customerId FK → customers.id
├── orderNumber Número do pedido
├── status draft | confirmed | delivered | cancelled
├── total Decimal(10,2)
├── paymentMethod dinheiro | cartao_credito | pix | boleto | prazo
└── createdAt, updatedAt
sales_order_items
├── id serial PK
├── orderId FK → sales_orders.id
├── productId FK → products.id
├── quantity integer
├── unitPrice Decimal(10,2)
└── total Decimal(10,2)
```
### 6.8 — purchase_orders + purchase_order_items
```
purchase_orders
├── id serial PK
├── userId FK → users.id
├── supplierId FK → suppliers.id
├── orderNumber
├── status draft | confirmed | received | cancelled
├── total Decimal(10,2)
└── createdAt, updatedAt
purchase_order_items
├── id serial PK
├── orderId FK → purchase_orders.id
├── productId FK → products.id
├── quantity, unitPrice, total
```
### 6.9 — persons + person_roles (Cadastro Unificado)
```
persons
├── id serial PK
├── tenantId FK → tenants.id
├── fullName
├── cpfCnpj
├── email, phone
├── address, city, state, zipCode
├── type company | individual
└── notes, createdAt, updatedAt
person_roles
├── id serial PK
├── personId FK → persons.id
├── role customer | supplier | employee | technician | partner
└── isActive
```
---
## 7. Tabelas PostgreSQL — Módulo Varejo (Retail)
### 7.1 — Estrutura Operacional
```
retail_stores → Lojas físicas
retail_warehouses → Depósitos/Almoxarifados
retail_payment_methods → Formas de pagamento
retail_sellers → Vendedores
retail_commission_plans → Planos de comissão
retail_seller_goals → Metas por vendedor
retail_store_goals → Metas por loja
```
### 7.2 — Estoque
```
stock_transfers → Transferências entre depósitos
stock_transfer_items → Itens da transferência
```
### 7.3 — Dispositivos Móveis (Eletrônicos)
```
mobile_devices
├── id serial PK
├── productId FK → products.id
├── imei varchar (único)
├── imei2 varchar (chip duplo)
├── serial Número de série
├── status em_estoque | vendido | locado | devolucao | avaliacao
├── color, storage, ram
├── purchasePrice, salePrice
└── supplierId, createdAt
device_evaluations → Avaliação de aparelhos usados
device_history → Histórico completo do dispositivo
```
### 7.4 — PDV (Ponto de Venda)
```
pos_sessions → Sessões de caixa (abertura/fechamento)
pos_sales → Vendas PDV
pos_sale_items → Itens das vendas PDV
```
### 7.5 — Planos de Pagamento
```
payment_plans → Planos parcelados
payment_plan_installments → Parcelas
lease_payments → Pagamentos de locação
```
---
## 8. API REST Completa (/api/soe/* = /api/erp/*)
> O alias `/api/soe/*` é convertido automaticamente para `/api/erp/*`
> no middleware do `server/erp/index.ts`.
### 8.1 — Conexões ERP
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/soe/connections` | Lista conexões do usuário |
| POST | `/api/soe/connections` | Cria nova conexão |
| POST | `/api/soe/connections/:id/test` | Testa conectividade |
| DELETE | `/api/soe/connections/:id` | Remove conexão |
### 8.2 — Tarefas ERP (Sync Jobs)
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/soe/tasks` | Lista tarefas de sync |
| POST | `/api/soe/tasks` | Cria nova tarefa |
| POST | `/api/soe/tasks/:id/execute` | Executa tarefa manualmente |
| DELETE | `/api/soe/tasks/:id` | Remove tarefa |
| GET | `/api/soe/tasks/:id/executions` | Histórico de execuções |
### 8.3 — Clientes
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/soe/customers` | Lista clientes (paginado) |
| POST | `/api/soe/customers` | Cria cliente |
| PUT | `/api/soe/customers/:id` | Atualiza cliente |
| DELETE | `/api/soe/customers/:id` | Exclui cliente |
### 8.4 — Fornecedores
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/soe/suppliers` | Lista fornecedores |
| POST | `/api/soe/suppliers` | Cria fornecedor |
| PUT | `/api/soe/suppliers/:id` | Atualiza |
| DELETE | `/api/soe/suppliers/:id` | Exclui |
### 8.5 — Pessoas (Cadastro Unificado)
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/soe/persons` | Lista com filtro por role/search |
| GET | `/api/soe/persons/:id` | Detalhe + roles |
| POST | `/api/soe/persons` | Cria pessoa + roles |
| PUT | `/api/soe/persons/:id` | Atualiza pessoa + roles |
| DELETE | `/api/soe/persons/:id` | Exclui pessoa (cascade roles) |
### 8.6 — Produtos
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/soe/products` | Lista produtos |
| POST | `/api/soe/products` | Cria produto |
| PUT | `/api/soe/products/:id` | Atualiza |
| DELETE | `/api/soe/products/:id` | Exclui |
| GET | `/api/soe/products/:id/devices` | Dispositivos do produto |
| POST | `/api/soe/products/:id/devices/batch` | Import batch de IMEIs |
| GET | `/api/soe/products/:id/stock-count` | Contagem de estoque |
### 8.7 — Pedidos de Venda
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/soe/sales-orders` | Lista pedidos |
| POST | `/api/soe/sales-orders` | Cria pedido (com itens) |
| POST | `/api/soe/sales-orders/:id/confirm` | Confirma pedido |
| POST | `/api/soe/sales-orders/:id/generate-nfe` | Gera NF-e |
| DELETE | `/api/soe/sales-orders/:id` | Cancela/Exclui |
### 8.8 — Pedidos de Compra
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/soe/purchase-orders` | Lista pedidos |
| POST | `/api/soe/purchase-orders` | Cria pedido |
| DELETE | `/api/soe/purchase-orders/:id` | Exclui |
### 8.9 — Dados e Configuração
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/soe/stats` | Dashboard — totais de clientes/produtos/pedidos/receita |
| GET | `/api/soe/data/:connectionId/:dataType` | Dados via conexão ERP |
| GET | `/api/soe/segments` | Lista segmentos |
| POST | `/api/soe/segments` | Cria segmento |
| POST | `/api/soe/segments/seed` | Popula segmentos padrão |
| GET | `/api/soe/config` | Configuração da empresa |
| POST | `/api/soe/config` | Salva configuração |
### 8.10 — Módulos do Tenant
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/api/soe/tenant/modules` | Módulos ativos do tenant atual |
| PUT | `/api/soe/tenant/modules` | Ativa/desativa módulos |
---
## 9. API REST — Módulo Varejo (/api/retail/*)
### 9.1 — Estrutura Operacional
| Módulo | Endpoints | Operações |
|--------|-----------|-----------|
| Activity Feed | `/activity-feed` | GET, mark-read |
| Payment Methods | `/payment-methods` | CRUD |
| Sellers | `/sellers` | CRUD |
| Commission Plans | `/commission-plans` | CRUD |
| Price Tables | `/price-tables` | CRUD |
| Promotions | `/promotions` | CRUD |
| Product Types | `/product-types` | CRUD + detail |
| Stores | `/stores` | CRU |
| Warehouses | `/warehouses` | CRUD |
### 9.2 — Estoque
| Módulo | Endpoints | Operações |
|--------|-----------|-----------|
| Warehouse Stock | `/warehouse-stock` | GET list + summary |
| Stock Movements | `/stock-movements` | GET + POST |
| Product Serials | `/product-serials` | CRUD |
| Stock Transfers | `/stock-transfers` | CRUD + status |
| Stock Alerts | `/stock-alerts` | GET |
| Inventories | `/inventories` | CRUD + count + apply |
### 9.3 — Dispositivos (Mobile/Eletrônicos)
| Módulo | Endpoints | Operações |
|--------|-----------|-----------|
| Devices | `/devices` | CRUD + IMEI lookup |
| Evaluations | `/evaluations` | CRUD + approve + reject |
### 9.4 — Serviços
| Módulo | Endpoints | Operações |
|--------|-----------|-----------|
| Service Orders | `/service-orders` | CRUD + complete-preparation + items |
| Warranties | `/warranties` | CRUD + check IMEI + claim |
### 9.5 — PDV
| Módulo | Endpoints | Operações |
|--------|-----------|-----------|
| POS Sessions | `/pos-sessions` | GET + open + close |
| Cash Movements | `/cash-movements` | GET + POST |
### 9.6 — Relatórios
| Endpoint | Descrição |
|----------|-----------|
| `/reports/os-by-status` | OS agrupadas por status |
| `/reports/os-by-technician` | OS agrupadas por técnico |
**Total: ~120 endpoints no módulo SOE (Core + Retail)**
---
## 10. Dependências Externas do SOE
```
┌─────────────────────────────────────────────────────────────────────┐
│ DEPENDÊNCIAS DO SOE │
│ │
│ ┌──── Arcádia Plus (Laravel) ────────────────────────────────┐ │
│ │ Host: :8080 (mesmo servidor) ou externo │ │
│ │ Env: PLUS_URL (se externo) │ │
│ │ Proxy: /plus/* → :8080 │ │
│ │ SSO: token compartilhado via plus/sso.ts │ │
│ │ Sync: retail/plus-sync.ts + retail/sync-service.ts │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──── ERPNext/Frappe ─────────────────────────────────────────┐ │
│ │ Host: ERPNEXT_URL (env var no servidor) │ │
│ │ Auth: ERPNEXT_API_KEY + ERPNEXT_API_SECRET │ │
│ │ Entidades: Customer, Supplier, Item, Sales Order │ │
│ │ Rotas: server/erpnext/routes.ts → /api/erpnext/* │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──── Arcádia Fisco (:8002) ──────────────────────────────────┐ │
│ │ Geração de NF-e a partir do pedido de venda │ │
│ │ POST /api/soe/sales-orders/:id/generate-nfe │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──── PostgreSQL ─────────────────────────────────────────────┐ │
│ │ DATABASE_URL env var │ │
│ │ Motor padrão (nativo) sem Plus/ERPNext │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 11. Variáveis de Ambiente Necessárias para Deploy
```bash
# PostgreSQL (obrigatório)
DATABASE_URL=postgresql://user:pass@host:5432/arcadia
# ERPNext (opcional — apenas se motor = "erpnext")
ERPNEXT_URL=https://seu-erpnext.com
ERPNEXT_API_KEY=sua-chave
ERPNEXT_API_SECRET=seu-secret
# Arcádia Plus (opcional — apenas se motor = "plus")
PLUS_URL=http://localhost:8080 # ou URL externa do Plus
# Fisco (para geração de NF-e via SOE)
FISCO_URL=http://localhost:8002 # ou URL externa do Fisco
```
---
## 12. Checklist de Export para Deploy Manual
### Frontend
- [ ] `client/src/pages/SOE.tsx`
- [ ] `client/src/pages/ERP.tsx`
- [ ] `client/src/contexts/SoeMotorContext.tsx`
- [ ] `client/src/contexts/ErpProfileContext.tsx`
### Backend
- [ ] `server/erp/routes.ts`
- [ ] `server/erp/index.ts`
- [ ] `server/retail/routes.ts`
- [ ] `server/retail/index.ts`
- [ ] `server/retail/sync-service.ts`
- [ ] `server/retail/plus-sync.ts`
- [ ] `server/plus/client.ts`
- [ ] `server/plus/routes.ts`
- [ ] `server/plus/proxy.ts`
- [ ] `server/plus/launcher.ts`
- [ ] `server/plus/sso.ts`
- [ ] `server/erpnext/routes.ts`
- [ ] `server/erpnext/index.ts`
### Schema e Banco
- [ ] `shared/schema.ts` (tabelas: L348, L33033517, L50825617, L6204)
- [ ] Rodar `drizzle-kit push` no servidor para criar as tabelas
- [ ] Verificar variável `DATABASE_URL` no servidor
### Configuração do Servidor
- [ ] `.env` com `DATABASE_URL` (obrigatório)
- [ ] `.env` com `ERPNEXT_*` (se usar motor ERPNext)
- [ ] `.env` com `PLUS_URL` (se usar motor Plus)
- [ ] Confirmar que `:8080` (Plus) está acessível se necessário
---
## 13. Resumo Visual do Módulo
```
┌─────────────────────────────────────────────────────────────────────┐
│ ARCÁDIA SOE — STACK COMPLETO │
│ │
│ URL /soe → SOE.tsx (2.414L) │
│ │
│ 9 Abas: Dashboard │ Pessoas │ Produtos │ Vendas │ Compras │
│ Financeiro │ CRM │ Sync │ Config │
│ │
│ ┌─── Motor (Adapter) ────────────────────────────────────────┐ │
│ │ localStorage["arcadia_soe_motor"] = "plus" | "erpnext" │ │
│ │ │ │
│ │ plus → /plus/api/* → Arcádia Plus :8080 (Laravel) │ │
│ │ erpnext → ERPNext API → ERPNEXT_URL externo │ │
│ │ nativo → /api/soe/* → PostgreSQL local (Drizzle) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── Backend ────────────────────────────────────────────────┐ │
│ │ /api/soe/* → /api/erp/* (alias automático) │ │
│ │ server/erp/routes.ts (1.343L) → 35+ endpoints core │ │
│ │ server/retail/routes.ts (5.218L) → 85+ endpoints varejo │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── Banco de Dados ─────────────────────────────────────────┐ │
│ │ Core: customers, suppliers, products, sales_orders, │ │
│ │ purchase_orders, persons, erp_config, erp_segments │ │
│ │ │ │
│ │ Retail: retail_stores, retail_warehouses, mobile_devices, │ │
│ │ device_evaluations, pos_sessions, pos_sales, │ │
│ │ stock_transfers, payment_plans (20+ tabelas) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── Integrações ────────────────────────────────────────────┐ │
│ │ Arcádia Plus :8080 │ ERPNext/Frappe │ Fisco :8002 │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```

View File

@ -1,812 +0,0 @@
# Arcádia Tenants — Mapa Completo da Gestão Multi-Tenant
> Mapa da arquitetura multi-tenant da Arcádia Suite: hierarquia
> Master/Parceiro/Cliente, sistema de planos, ativação de módulos,
> empresas (Matriz/Filiais), comissões de parceiros, controle de acesso,
> e middleware de isolamento.
> Atualizado em: Março 2026
---
## 1. Visão Geral
A Arcádia Suite opera em modelo **multi-tenant hierárquico** com três níveis. Cada tenant possui um plano que define quais módulos da plataforma estão habilitados, e pode conter múltiplas empresas (CNPJ) para operações fiscais.
```
┌─────────────────────────────────────────────────────────────────────┐
│ HIERARQUIA DE TENANTS │
│ │
│ ┌──────────────┐ │
│ │ MASTER │ ← Operador da plataforma │
│ │ │ ← Vê tudo, controla tudo │
│ │ tenantType: │ ← Pode criar parceiros │
│ │ "master" │ ← Pode excluir tenants │
│ └──────┬───────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ PARCEIRO A │ │ PARCEIRO B │ │ PARCEIRO C │ │
│ │ │ │ │ │ │ │
│ │ tenantType: │ │ Consultorias │ │ Integradores │ │
│ │ "partner" │ │ Revendas │ │ Franquias │ │
│ │ │ │ │ │ │ │
│ │ partnerCode: │ │ comissão: % │ │ plano: │ │
│ │ "PARC-001" │ │ sobre clientes│ │partner_pro │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ┌────┴────┐ ┌───┴───┐ ┌───┴───┐ │
│ ▼ ▼ ▼ ▼ ▼ ▼ │
│ ┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐ │
│ │Cliente1││Cliente2││Cliente3││Cliente4││Cliente5││Cliente6│ │
│ │ ││ ││ ││ ││ ││ │ │
│ │tenant ││tenant ││tenant ││tenant ││tenant ││tenant │ │
│ │Type: ││Type: ││Type: ││Type: ││Type: ││Type: │ │
│ │"client"││"client"││"client"││"client"││"client"││"client"│ │
│ │ ││ ││ ││ ││ ││ │ │
│ │parent: ││parent: ││parent: ││parent: ││parent: ││parent: │ │
│ │Parc.A ││Parc.A ││Parc.B ││Parc.B ││Parc.C ││Parc.C │ │
│ │ ││ ││ ││ ││ ││ │ │
│ │Plano: ││Plano: ││Plano: ││Plano: ││Plano: ││Plano: │ │
│ │starter ││pro ││free ││enterpr.││starter ││pro │ │
│ └────────┘└────────┘└────────┘└────────┘└────────┘└────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
### Visibilidade por tipo:
| Tipo | Vê | Cria | Exclui |
|------|-----|------|--------|
| **Master** | Todos os tenants | Parceiros e Clientes | Qualquer (exceto master) |
| **Partner** | Seu tenant + seus clientes | Apenas clientes vinculados | Nenhum |
| **Client** | Apenas seu próprio tenant | Nenhum | Nenhum |
---
## 2. Banco de Dados — Tabelas de Tenant
### 2.1 — tenants (Tabela Principal)
```
┌──────────────────────────────────────────────────────────────────┐
│ tenants │
│ Tabela: "tenants" — shared/schema.ts:849 │
│ │
│ ┌─────────── Identificação ──────────────────┐ │
│ │ id serial PK │ │
│ │ name text NOT NULL │ │
│ │ slug text UNIQUE │ │
│ │ email text │ │
│ │ phone text │ │
│ │ logoUrl text │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────── Hierarquia ─────────────────────┐ │
│ │ tenantType "master" | "partner" | │ │
│ │ "client" │ │
│ │ parentTenantId integer (self-ref FK) │ │
│ │ partnerCode text (código único parceiro) │ │
│ │ commissionRate numeric(5,2) (% comissão) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────── Plano e Limites ────────────────┐ │
│ │ plan "free" | "starter" | "pro" │ │
│ │ | "enterprise" | │ │
│ │ "partner_starter" | │ │
│ │ "partner_pro" │ │
│ │ status "active" | "trial" | │ │
│ │ "suspended" | "cancelled" │ │
│ │ maxUsers integer (default 5) │ │
│ │ maxStorageMb integer (default 1000) │ │
│ │ features JSONB → TenantFeatures │ ← Módulos │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────── Billing ────────────────────────┐ │
│ │ billingEmail text │ │
│ │ trialEndsAt timestamp │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────── Dados Empresariais ─────────────┐ │
│ │ cnpj text │ │
│ │ tradeName text │ │
│ │ address text │ │
│ │ city, state text │ │
│ │ segment text │ │
│ │ notes, source text │ │
│ │ commercialContact / commercialPhone │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────── Metadata ───────────────────────┐ │
│ │ settings text (JSON config) │ │
│ │ createdAt timestamp │ │
│ │ updatedAt timestamp │ │
│ └─────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
```
### 2.2 — TenantFeatures (JSONB — Sistema de Módulos)
O campo `features` na tabela `tenants` é um JSONB que controla **quais módulos da Arcádia estão habilitados** para cada tenant. O tipo é definido em `shared/schema.ts:821` e espelhado no frontend em `client/src/hooks/use-tenant-features.ts:3`.
```
┌──────────────────────────────────────────────────────────────────┐
│ TenantFeatures (JSONB) │
│ │
│ ┌─────────── Módulos Booleanos ──────────────┐ │
│ │ │ │
│ │ ide → IDE integrada │ │
│ │ whatsapp → WhatsApp multi-sessão │ │
│ │ crm → CRM completo │ │
│ │ erp → SOE/ERP │ │
│ │ bi → Business Intelligence │ │
│ │ manus → IA Manus (GPT-4o) │ │
│ │ centralApis → APIs centrais │ │
│ │ centralApisManage → Gerenciar APIs │ │
│ │ comunidades → Chat interno │ │
│ │ biblioteca → Biblioteca de conhecimento │ │
│ │ bibliotecaPublish → Publicar na biblioteca │ │
│ │ suporteN3 → Suporte nível 3 │ │
│ │ retail → Módulo Varejo │ │
│ │ plus → ERP Plus (Laravel) │ │
│ │ fisco → Motor Fiscal │ │
│ │ cockpit → Cockpit executivo │ │
│ │ compass → Process Compass │ │
│ │ production → Módulo Produção │ │
│ │ support → Módulo Suporte │ │
│ │ xosCrm → XOS CRM (Kanban) │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────── Configurações Numéricas ────────┐ │
│ │ │ │
│ │ whatsappSessions → Nº sessões WA (0-N) │ │
│ │ maxChannels → Nº canais permitidos │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────── Configurações de Texto ─────────┐ │
│ │ │ │
│ │ ideMode → "none" | "no-code" | │ │
│ │ "low-code" | "pro-code" │ │
│ │ manusTools[] → Lista de tools habilitadas │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
```
### 2.3 — tenant_empresas (Matriz/Filiais)
Cada tenant pode ter **múltiplas empresas** (CNPJs) cadastradas — essencial para operações fiscais (NF-e/NFC-e) por filial.
```
┌──────────────────────────────────────────────────────────────────┐
│ tenant_empresas │
│ Tabela: "tenant_empresas" — shared/schema.ts:889 │
│ │
│ ┌─────────── Identificação ──────────────────┐ │
│ │ id serial PK │ │
│ │ tenantId FK → tenants.id (CASCADE) │ │
│ │ razaoSocial text NOT NULL │ │
│ │ nomeFantasia text │ │
│ │ cnpj text NOT NULL │ │
│ │ ie text (inscrição estadual) │ │
│ │ im text (inscrição municipal) │ │
│ │ email, phone text │ │
│ │ tipo "matriz" | "filial" │ │
│ │ status "active" | "inactive" │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────── Endereço ───────────────────────┐ │
│ │ cep, logradouro, numero, complemento, │ │
│ │ bairro, cidade, uf, codigoIbge │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────── Fiscal ─────────────────────────┐ │
│ │ regimeTributario "simples" | "presumido" │ │
│ │ | "real" │ │
│ │ certificadoDigitalId FK certificado │ │
│ │ ambienteFiscal "producao" | "homologacao" │ │
│ │ serieNfe integer (default 1) │ │
│ │ serieNfce integer (default 1) │ │
│ │ plusEmpresaId integer (link ERP Plus) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────── Metadata ───────────────────────┐ │
│ │ createdAt, updatedAt timestamp │ │
│ └─────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
```
**Uso típico:**
```
Tenant "Empresa ABC" (id: 5)
├── Empresa Matriz: "ABC Comércio LTDA" (CNPJ: 12.345.678/0001-00)
│ regime: simples, ambiente: producao, série NF-e: 1
├── Filial SP: "ABC Comércio SP" (CNPJ: 12.345.678/0002-81)
│ regime: simples, ambiente: producao, série NF-e: 2
└── Filial RJ: "ABC Comércio RJ" (CNPJ: 12.345.678/0003-62)
regime: simples, ambiente: homologacao, série NF-e: 1
```
### 2.4 — tenant_users (Membership)
```
┌──────────────────────────────────────────────────────────────────┐
│ tenant_users │
│ Tabela: "tenant_users" — shared/schema.ts:923 │
│ │
│ id serial PK │
│ tenantId FK → tenants.id (CASCADE) │
│ userId FK → users.id (CASCADE) │
│ role "owner" | "admin" | "member" │
│ isOwner "true" | "false" │
│ createdAt timestamp │
│ │
│ → Link N:N entre users e tenants │
│ → Um usuário pode pertencer a múltiplos tenants │
│ → Cada tenant tem um owner + admins + members │
└──────────────────────────────────────────────────────────────────┘
```
### 2.5 — tenant_plans (Planos Disponíveis)
```
┌──────────────────────────────────────────────────────────────────┐
│ tenant_plans │
│ Tabela: "tenant_plans" — shared/schema.ts:933 │
│ │
│ id serial PK │
│ code text UNIQUE ("free", "starter", "pro", │
│ "enterprise", "partner_starter", "partner_pro") │
│ name text NOT NULL │
│ description text │
│ tenantType "master" | "partner" | "client" │
│ maxUsers integer (default 5) │
│ maxStorageMb integer (default 1000) │
│ features JSONB → TenantFeatures │
│ monthlyPrice integer (centavos) │
│ yearlyPrice integer (centavos) │
│ trialDays integer (default 14) │
│ isActive "true" | "false" │
│ sortOrder integer │
│ createdAt timestamp │
└──────────────────────────────────────────────────────────────────┘
```
### 2.6 — partner_clients (Relacionamento Parceiro↔Cliente)
```
┌──────────────────────────────────────────────────────────────────┐
│ partner_clients │
│ Tabela: "partner_clients" — shared/schema.ts:951 │
│ │
│ id serial PK │
│ partnerId FK → tenants.id (CASCADE) │
│ clientId FK → tenants.id (CASCADE) │
│ commissionRate numeric(5,2) — Override do rate do parceiro │
│ status "active" | "suspended" | "ended" │
│ notes text │
│ startedAt timestamp │
│ endedAt timestamp │
└──────────────────────────────────────────────────────────────────┘
```
### 2.7 — partner_commissions (Comissões dos Parceiros)
```
┌──────────────────────────────────────────────────────────────────┐
│ partner_commissions │
│ Tabela: "partner_commissions" — shared/schema.ts:963 │
│ │
│ id serial PK │
│ partnerId FK → tenants.id (CASCADE) │
│ clientId FK → tenants.id (CASCADE) │
│ referenceMonth text ("2026-01", "2026-02", ...) │
│ clientPlanCode text (código do plano do cliente) │
│ clientPlanValue integer (valor do plano em centavos) │
│ commissionRate numeric(5,2) NOT NULL │
│ commissionValue integer NOT NULL (valor em centavos) │
│ status "pending" | "approved" | "paid" | "cancelled" │
│ approvedAt timestamp │
│ paidAt timestamp │
│ paymentReference text │
│ createdAt timestamp │
│ │
│ Fluxo: pending → approved → paid │
│ │
│ Exemplo: │
│ Parceiro A tem Cliente X no plano "pro" (R$ 299/mês) │
│ commissionRate: 20% │
│ commissionValue: 5980 centavos (R$ 59,80) │
└──────────────────────────────────────────────────────────────────┘
```
---
## 3. Planos Pré-definidos (Seed)
A rota `POST /api/admin/plans/seed` cria os 6 planos padrão:
```
┌─────────────────────────────────────────────────────────────────────┐
│ PLANOS PARA CLIENTES │
├────────────┬──────────┬──────────┬──────────────────────────────────┤
│ Plano │ Preço/mês│ Usuários │ Módulos │
├────────────┼──────────┼──────────┼──────────────────────────────────┤
│ Gratuito │ R$ 0 │ 2 │ erp, crm │
│ (free) │ │ │ │
├────────────┼──────────┼──────────┼──────────────────────────────────┤
│ Starter │ R$ 99 │ 5 │ erp, crm, bi, fisco, │
│ │ │ │ whatsapp, compass │
├────────────┼──────────┼──────────┼──────────────────────────────────┤
│ Pro │ R$ 299 │ 15 │ erp, crm, bi, fisco, retail, │
│ │ │ │ plus, whatsapp, manus, cockpit, │
│ │ │ │ compass, support, biblioteca │
├────────────┼──────────┼──────────┼──────────────────────────────────┤
│ Enterprise │ R$ 999 │ 100 │ TODOS os módulos │
│ │ │ 50GB │ (erp, crm, bi, fisco, retail, │
│ │ │ │ plus, whatsapp, manus, ide, │
│ │ │ │ cockpit, compass, production, │
│ │ │ │ support, xosCrm, centralApis, │
│ │ │ │ comunidades, biblioteca) │
└────────────┴──────────┴──────────┴──────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ PLANOS PARA PARCEIROS │
├──────────────────┬──────────┬──────────┬────────────────────────────┤
│ Plano │ Preço/mês│ Usuários │ Módulos │
├──────────────────┼──────────┼──────────┼────────────────────────────┤
│ Parceiro Starter │ R$ 199 │ 10 │ erp, crm, bi, fisco, │
│ │ │ │ retail, whatsapp, compass, │
│ │ │ │ support │
├──────────────────┼──────────┼──────────┼────────────────────────────┤
│ Parceiro Pro │ R$ 499 │ 50 │ TODOS os módulos │
│ │ │ 25GB │ (igual Enterprise) │
└──────────────────┴──────────┴──────────┴────────────────────────────┘
```
---
## 4. Sistema de Papéis (Roles)
A Arcádia opera com **dois níveis de papéis** que se complementam:
```
┌─────────────────────────────────────────────────────────────────────┐
│ SISTEMA DUPLO DE ROLES │
│ │
│ ┌─────────── Papel do Sistema (users.role) ──────────────────┐ │
│ │ │ │
│ │ "master" → Superadmin, acesso total, bypass de tenant │ │
│ │ "admin" → Administrador, acessa /api/admin/* │ │
│ │ "user" → Usuário padrão, sem acesso admin │ │
│ │ │ │
│ │ Verificado no middleware global de admin: │ │
│ │ if (role !== "admin" && role !== "master") → 403 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────── Papel no Tenant (tenant_users.role) ────────────┐ │
│ │ │ │
│ │ "owner" → Dono do tenant, criador, full control │ │
│ │ "admin" → Administrador do tenant │ │
│ │ "member" → Membro comum, acesso operacional │ │
│ │ │ │
│ │ Usado para scoping de dados e permissões intra-tenant │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ Combinação no login (getEnrichedUser): │
│ user.role + user.tenantId + user.tenantType + user.tenantRole │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 5. Controle de Acesso — Helpers
O backend implementa dois helpers essenciais em `server/admin/routes.ts`:
```
┌─────────────────────────────────────────────────────────────────────┐
│ getAllowedTenantIds(user) │
│ │
│ Retorna os IDs de tenants que o usuário pode acessar: │
│ │
│ • master → null (acesso total, sem filtro) │
│ • partner → [seu tenantId, ...clientIds vinculados] │
│ • client → [apenas seu tenantId] │
│ │
│ Usado em: GET /tenants, GET /partner-clients, GET /commissions, │
│ GET /empresas, GET /tenants/hierarchy │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ canAccessTenant(user, tenantId) │
│ │
│ Verifica se o usuário pode operar sobre um tenant específico: │
│ │
│ • master → true (sempre) │
│ • partner → true se tenantId ∈ getAllowedTenantIds() │
│ • client → true apenas se tenantId === user.tenantId │
│ │
│ Usado em: PATCH /tenants/:id, DELETE /empresas/:id, │
│ POST /empresas, etc. │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 6. Middleware de Módulos (requireModule)
O middleware `requireModule` em `server/retail/routes.ts:25` bloqueia endpoints quando um módulo não está ativo para o tenant:
```
┌─────────────────────────────────────────────────────────────────────┐
│ requireModule(moduleKey: keyof TenantFeatures) │
│ │
│ Fluxo: │
│ 1. Identifica tenantId do usuário logado │
│ 2. Busca tenant no banco │
│ 3. Verifica features[moduleKey] === true │
│ 4. Se false → 403 com mensagem: │
│ { │
│ error: "Módulo 'X' não está ativo para este tenant", │
│ moduleKey: "plus", │
│ action: "Ative o módulo em Admin → Módulos" │
│ } │
│ │
│ Exemplo de uso: │
│ router.post("/plus/sync/customers", │
│ requireModule("plus"), ← Bloqueia se Plus não está ativo │
│ async (req, res) => { ... } │
│ ); │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 7. Hook Frontend (useTenantFeatures)
O hook `useTenantFeatures()` em `client/src/hooks/use-tenant-features.ts` fornece acesso às features do tenant no frontend:
```
┌─────────────────────────────────────────────────────────────────────┐
│ useTenantFeatures() │
│ │
│ Fonte: GET /api/soe/tenant/modules │
│ Cache: 60 segundos (staleTime: 60000) │
│ │
│ Retorna: │
│ { │
│ features: TenantFeatures, ← Objeto completo de features │
│ isLoading: boolean, │
│ plan: string, ← Código do plano atual │
│ isEnabled: (key) => bool, ← Verificação rápida │
│ } │
│ │
│ Defaults (quando não logado / sem tenant): │
│ ide: true, crm: true, erp: true, manus: true, compass: true │
│ Todos os demais: false │
│ │
│ Uso no componente: │
│ const { isEnabled } = useTenantFeatures(); │
│ if (isEnabled("bi")) { /* mostra menu BI */ } │
│ if (isEnabled("retail")) { /* mostra módulo varejo */ } │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 8. Interface de Administração
### 8.1 — Admin.tsx (Página Principal)
```
┌─────────────────────────────────────────────────────────────────────┐
│ Admin.tsx — client/src/pages/Admin.tsx (2.635 linhas) │
│ Rota: /admin │
│ │
│ 6 Abas: │
│ ┌─────────────┬──────────┬────────────┬───────────┬──────────────┐ │
│ │ Conexões │ Tarefas │ Knowledge │ Bibliotecas│ Módulos │ │
│ │ │ │ │ │ │ │
│ │ WhatsApp │ Dev │ Knowledge │ Conteúdo │ Ativação │ │
│ │ ERPNext │ Center │ Graph │ Docs │ por tenant │ │
│ │ APIs │ Pipeline │ Q&A │ Templates │ Toggle on/ │ │
│ │ │ │ │ │ off módulos │ │
│ └─────────────┴──────────┴────────────┴───────────┴──────────────┘ │
│ ┌─────────────┐ │
│ │Casa de │ │
│ │Máquinas │ │
│ │ │ │
│ │ Status dos │ │
│ │ motores │ │
│ │ (:8002-8006)│ │
│ └─────────────┘ │
│ │
│ Aba "Módulos" renderiza: <MultiTenantSection />
└─────────────────────────────────────────────────────────────────────┘
```
### 8.2 — MultiTenantSection (Gestão Completa)
```
┌─────────────────────────────────────────────────────────────────────┐
│ MultiTenantSection.tsx (1.486 linhas) │
│ client/src/components/MultiTenantSection.tsx │
│ │
│ 4 Abas internas: │
│ │
│ ┌─ Tenants ──────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Lista de tenants com: │ │
│ │ • Nome, tipo (Master/Parceiro/Cliente) │ │
│ │ • Plano atual e status │ │
│ │ • Nº de filhos (childCount) │ │
│ │ • Tenant pai (parentTenant) │ │
│ │ • Ações: Editar, Ativar/Desativar módulos │ │
│ │ │ │
│ │ Formulário de criação: │ │
│ │ • Nome, email, phone │ │
│ │ • Tipo (master/partner/client) │ │
│ │ • Plano (seleciona do tenant_plans) │ │
│ │ • Tenant pai (para hierarquia) │ │
│ │ • Features auto-preenchidas pelo plano selecionado │ │
│ │ • Toggle individual de cada módulo │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Planos ───────────────────────────────────────────────────┐ │
│ │ │ │
│ │ CRUD de planos com: │ │
│ │ • Código, nome, descrição │ │
│ │ • Tipo de tenant (client/partner) │ │
│ │ • Preço mensal e anual (centavos) │ │
│ │ • Max usuários e storage │ │
│ │ • Features (toggle de cada módulo) │ │
│ │ • Botão "Seed" para criar planos padrão │ │
│ │ • Botão "Propagar" para aplicar features do plano │ │
│ │ a todos os tenants que usam esse plano │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Comissões ────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Lista de comissões de parceiros: │ │
│ │ • Parceiro, Cliente, Mês referência │ │
│ │ • Valor do plano, taxa de comissão, valor da comissão │ │
│ │ • Status (pending → approved → paid) │ │
│ │ • Ações: Aprovar, Marcar como paga │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Empresas ─────────────────────────────────────────────────┐ │
│ │ │ │
│ │ EmpresasSubSection: │ │
│ │ • Lista de empresas (Matriz/Filiais) por tenant │ │
│ │ • CNPJ, Razão Social, Nome Fantasia │ │
│ │ • IE, IM, Regime Tributário │ │
│ │ • Endereço completo (CEP, logradouro, cidade, UF) │ │
│ │ • Config Fiscal (ambiente, série NF-e, série NFC-e) │ │
│ │ • Link com ERP Plus (plusEmpresaId) │ │
│ │ • Ações: Criar, Editar, Excluir │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 9. API REST Completa (/api/admin/*)
### Tenants
| Método | Endpoint | Função | Acesso |
|--------|----------|--------|--------|
| GET | `/tenants` | Lista tenants (filtrado por acesso) | master/partner/client |
| POST | `/tenants` | Cria tenant (resolve features do plano) | master/admin |
| PATCH | `/tenants/:id` | Atualiza tenant (canAccessTenant) | master/partner |
| DELETE | `/tenants/:id` | Exclui tenant (proteção master) | somente master |
| GET | `/tenants/hierarchy` | Árvore hierárquica de tenants | master/partner |
| GET | `/tenants/stats` | Estatísticas (total, por tipo, por status) | master/admin |
### Planos
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/plans` | Lista todos os planos |
| POST | `/plans` | Cria novo plano |
| PATCH | `/plans/:id` | Atualiza plano |
| DELETE | `/plans/:id` | Exclui plano |
| POST | `/plans/:id/propagate` | Propaga features do plano para todos os tenants que o usam |
| POST | `/plans/seed` | Cria 6 planos padrão |
### Parceiro-Cliente
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/partner-clients` | Lista relacionamentos parceiro↔cliente |
| POST | `/partner-clients` | Cria vínculo parceiro↔cliente |
### Comissões de Parceiros
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/commissions` | Lista comissões (filtro por partnerId, status) |
| PATCH | `/commissions/:id/approve` | Aprova comissão |
| PATCH | `/commissions/:id/pay` | Marca como paga |
### Empresas (Matriz/Filiais)
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/empresas` | Lista empresas (filtro por tenantId + acesso) |
| GET | `/empresas/:id` | Detalhe da empresa |
| POST | `/empresas` | Cria empresa (verifica canAccessTenant) |
| PATCH | `/empresas/:id` | Atualiza empresa |
| DELETE | `/empresas/:id` | Exclui empresa |
### Outros Admin
| Método | Endpoint | Função |
|--------|----------|--------|
| GET | `/profiles` | Lista perfis de usuários |
| POST | `/profiles` | Cria perfil |
| PATCH | `/profiles/:id` | Atualiza perfil |
| DELETE | `/profiles/:id` | Exclui perfil |
| GET | `/users` | Lista usuários (com tenant info) |
| PATCH | `/users/:id` | Atualiza usuário |
| PATCH | `/users/:id/status` | Altera status do usuário |
| PATCH | `/partners/:id/status` | Altera status de parceiro |
| PATCH | `/clients/:id/status` | Altera status de cliente |
| GET | `/stats` | Estatísticas gerais (tenants, users, leads) |
| GET | `/libraries` | Lista bibliotecas de conteúdo |
**Total: 33 endpoints de administração**
---
## 10. Fluxo Completo — Onboarding de Tenant
```
┌──────────┐ ┌────────────┐ ┌──────────────┐ ┌──────────────┐
│ 1. CRIAR │───▶│ 2. PLANO │───▶│ 3. FEATURES │───▶│ 4. EMPRESAS │
│ TENANT │ │ │ │ │ │ │
│ │ │ Seleciona │ │ Auto-resolve │ │ Cadastra │
│ POST │ │ plano │ │ do plano ou │ │ Matriz │
│ /admin/ │ │ (free, │ │ toggle │ │ + Filiais │
│ tenants │ │ starter, │ │ individual │ │ │
│ │ │ pro, etc) │ │ de módulos │ │ CNPJ, IE, │
│ name, │ │ │ │ │ │ endereço, │
│ email, │ │ maxUsers │ │ ide: true │ │ regime │
│ type │ │ maxStorage │ │ crm: true │ │ tributário, │
│ │ │ preço │ │ bi: false │ │ certificado │
│ │ │ │ │ ... │ │ digital │
└──────────┘ └────────────┘ └──────────────┘ └──────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐
│ 5. VINCULAR │ │ 6. COMISSÕES │ │ 7. USUÁRIOS │
│ PARCEIRO │ │ │ │ │
│ (se partner) │ │ Automáticas │ │ Convida users │
│ │ │ mensais │ │ tenant_users │
│ POST /admin/ │ │ baseadas no │ │ roles: owner/admin/ │
│ partner- │ │ plano do │ │ member │
│ clients │ │ cliente │ │ │
│ │ │ │ │ Scoping automático │
│ partnerId │ │ pending → │ │ de todos os dados │
│ clientId │ │ approved → │ │ por tenantId │
│ commission% │ │ paid │ │ │
└──────────────┘ └──────────────┘ └──────────────────────────┘
```
---
## 11. Propagação de Plano
Quando um plano é atualizado, a feature de **propagação** atualiza todos os tenants que usam esse plano:
```
┌─────────────────────────────────────────────────────────────────────┐
│ POST /api/admin/plans/:id/propagate │
│ │
│ 1. Busca o plano por ID │
│ 2. Pega features, maxUsers, maxStorageMb do plano │
│ 3. Atualiza TODOS os tenants onde plan === plan.code: │
│ UPDATE tenants SET │
│ features = plan.features, │
│ maxUsers = plan.maxUsers, │
│ maxStorageMb = plan.maxStorageMb │
│ WHERE plan = 'pro' │
│ 4. Retorna: { propagated: 15, planCode: "pro" } │
│ │
│ Exemplo: │
│ Plano "pro" ganha módulo "production" → │
│ Propagar → 15 tenants "pro" recebem production: true │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 12. Isolamento de Dados
Toda query de dados respeita o escopo do tenant:
```
┌─────────────────────────────────────────────────────────────────────┐
│ ISOLAMENTO POR tenantId │
│ │
│ Padrão em todas as queries: │
│ │
│ const tenantId = (req.user as any).tenantId; │
│ const data = await db.select() │
│ .from(tabela) │
│ .where(eq(tabela.tenantId, tenantId)); │
│ │
│ Tabelas com tenantId: │
│ • tenants (hierarquia) • crm_channels │
│ • tenant_empresas • crm_threads │
│ • crm_leads • crm_events │
│ • crm_clients • crm_campaigns │
│ • crm_opportunities • crm_products │
│ • crm_partners • crm_contracts │
│ • crm_pipeline_stages • crm_commission_rules │
│ • crm_proposals • crm_quick_messages │
│ • retail_activity_feed • valuation_* │
│ │
│ Resultado: Dados do Tenant A NUNCA são vistos pelo Tenant B │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 13. Arquivos-Chave
| Arquivo | Função | Linhas |
|---------|--------|--------|
| `shared/schema.ts` (L821-977) | TenantFeatures type + 6 tabelas tenant | ~160 |
| `server/admin/routes.ts` | API admin (33 endpoints) | 876 |
| `client/src/pages/Admin.tsx` | Página admin (6 abas) | 2.635 |
| `client/src/components/MultiTenantSection.tsx` | Gestão multi-tenant (4 sub-abas) | 1.486 |
| `client/src/hooks/use-tenant-features.ts` | Hook de features no frontend | 59 |
| `server/retail/routes.ts` (L25) | Middleware requireModule | ~25 |
| `server/storage.ts` (getEnrichedUser) | Enriquece user com tenant info | — |
---
## 14. Resumo Visual
```
┌─────────────────────────────────────────────────────────────────────┐
│ ARCÁDIA MULTI-TENANT STACK │
│ │
│ ┌───── Autenticação ──────────────────────────────────────────┐ │
│ │ Login → getEnrichedUser → user + tenantId + tenantType │ │
│ │ + tenantRole + features │ │
│ └─────────────────────────────────────────┬───────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────▼───────────────────┐ │
│ │ Middleware │ │
│ │ ┌──────────┐ ┌─────────────┐ ┌────────────────────┐ │ │
│ │ │requireAuth│ │requireModule │ │getAllowedTenantIds │ │ │
│ │ │role check │ │feature check │ │canAccessTenant │ │ │
│ │ └──────────┘ └─────────────┘ └────────────────────┘ │ │
│ └─────────────────────────────────────────┬───────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────▼───────────────────┐ │
│ │ API /api/admin/* │ │
│ │ 33 endpoints: Tenants, Plans, Empresas, Comissões │ │
│ └─────────────────────────────────────────┬───────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────▼───────────────────┐ │
│ │ PostgreSQL │ │
│ │ 6 tabelas: tenants, tenant_users, tenant_plans, │ │
│ │ tenant_empresas, partner_clients, partner_commissions │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───── Frontend ──────────────────────────────────────────────┐ │
│ │ Admin.tsx (6 abas) → MultiTenantSection (4 sub-abas) │ │
│ │ useTenantFeatures() hook → GET /api/soe/tenant/modules │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```

View File

@ -1,292 +0,0 @@
# PLANO BI — MetaSet (Apache Superset)
## Arquitetura Consolidada
### Versão 2.0 — Abril 2026
---
## 1. STATUS — CONSOLIDAÇÃO CONCLUÍDA ✅
### 1.1 O que foi realizado
A migração do Metabase para o Apache Superset foi **concluída** em abril de 2026.
| Componente | Status | Localização |
|---|---|---|
| **MetaSet (Apache Superset 4.1.0)** | ✅ Implementado | `/server/bi/metaset/` |
| **Cliente TypeScript** | ✅ Implementado | `/server/bi/metaset-client/` |
| **Rotas Express** | ✅ Implementado | `/server/bi/metaset-client/routes.ts` |
| **Código legado Metabase** | ✅ Backup | `/server/metaset/backup/` |
| **Integração Kernel** | ✅ Ativa | Porta 8100 |
| **Documentação** | ✅ Atualizada | `/server/metaset/README.md` |
### 1.2 Arquitetura Consolidada
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ ARCÁDIA SUITE (Porta 5000) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ BI-API GATEWAY (Porta 8004) │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────────┐ │ │
│ │ │ /bi/api/* │ │/bi/metaset/*│ │ /bi/fdb/* │ │ │
│ │ │ (nativo) │ │ (proxy) │ │ (proxy) │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └─────────────┬───────────────┘ │ │
│ │ └─────────────────┴───────────────────────┘ │ │
│ └─────────────────────────────┬────────────────────────────────────────┘ │
└────────────────────────────────┼────────────────────────────────────────────┘
┌───────────────────────┴───────────────────────┐
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ METASET BI (Porta 8100)│ │ FDB-BRIDGE (8200) │
│ Apache Superset 4.1.0 │ │ Sync Firebird → PG │
│ ┌─────────────────┐ │ │ - Change Data Capture │
│ │ superset-src/ │ │ │ - Real-time sync │
│ │ - Dashboards │ │ │ - Tenant isolation │
│ │ - SQL Lab │ │ │ │
│ │ - Charts │ │ │ │
│ │ - RLS │ │ │ │
│ └─────────────────┘ │ │ │
└─────────────────────────┘ └─────────────────────────┘
┌─────────────────────────┐
│ PostgreSQL + Redis │
│ (metaset_db + cache) │
└─────────────────────────┘
```
---
## 2. ESTRUTURA DE ARQUIVOS
### 2.1 Implementação Atual (Apache Superset)
```
/server/bi/metaset/
├── main.py # FastAPI wrapper (Porta 8100)
├── run.py # Entry point Gunicorn
├── superset_config.py # Configuração Superset
├── security_manager.py # ArcadiaSecurityManager (JWT + RLS)
├── Dockerfile # Container MetaSet
├── init.sh # Script de inicialização
├── requirements.txt # Dependências Python
├── superset-src/ # Código fonte Apache Superset 4.1.0
├── config/ # Configurações adicionais
├── branding/ # Assets de branding
├── security/ # Módulos de segurança
├── extensions/ # Extensões
└── templates/ # Templates customizados
/server/bi/metaset-client/
├── index.ts # Cliente TypeScript completo
└── routes.ts # Rotas Express /api/bi/metaset/*
```
### 2.2 Código Legado (Backup)
```
/server/metaset/backup/
├── client.ts # Cliente Metabase (legado)
└── routes.ts # Rotas Metabase (legado)
```
---
## 3. API ENDPOINTS
### 3.1 BI-API Gateway (Porta 8004)
| Endpoint | Método | Descrição |
|----------|--------|-----------|
| `/api/bi/metaset/health` | GET | Health check |
| `/api/bi/metaset/databases` | GET/POST | Lista/Cria databases |
| `/api/bi/metaset/databases/:id/sync` | POST | Sincroniza schema |
| `/api/bi/metaset/databases/:id/tables` | GET | Lista tabelas |
| `/api/bi/metaset/tables/:id` | GET | Metadata da tabela |
| `/api/bi/metaset/query` | POST | Executa SQL |
| `/api/bi/metaset/charts` | GET/POST | Lista/Cria charts |
| `/api/bi/metaset/charts/:id` | GET/DELETE | Obtém/Deleta chart |
| `/api/bi/metaset/dashboards` | GET/POST | Lista/Cria dashboards |
| `/api/bi/metaset/dashboards/:id` | GET/PUT/DELETE | CRUD dashboard |
| `/api/bi/metaset/guest-token` | POST | Cria guest token |
| `/api/bi/metaset/embed/dashboard/:id` | GET | URL de embed |
| `/api/bi/metaset/suggest/:table` | GET | Sugestões de análise |
### 3.2 MetaSet Direto (Porta 8100)
API nativa do Apache Superset:
```
GET /health
GET /api/v1/dashboard/
GET /api/v1/dashboard/:id
POST /api/v1/dashboard/
GET /api/v1/chart/
GET /api/v1/dataset/
POST /api/v1/security/guest_token/
GET /api/v1/database/
GET /sqllab/
```
---
## 4. CONFIGURAÇÃO
### 4.1 Variáveis de Ambiente
```bash
# MetaSet Connection
METASET_HOST=metaset
METASET_PORT=8100
# Database
DATABASE_URL=postgresql://arcadia:arcadia123@db:5432/metaset_db
ARCADIA_DATABASE_URL=postgresql://arcadia:arcadia123@db:5432/arcadia
# Redis (Cache)
REDIS_URL=redis://redis:6379/1
# Security
METASET_SECRET_KEY=change-in-production
JWT_SECRET_KEY=arcadia-jwt-secret
# Admin
METASET_ADMIN_USER=admin
METASET_ADMIN_EMAIL=admin@arcadia.app
METASET_ADMIN_PASSWORD=metaset2026
```
### 4.2 Feature Flags
```python
FEATURE_FLAGS = {
"EMBEDDED_SUPERSET": True, # Embedding de dashboards
"GUEST_EMBEDDING_ENABLED": True, # Guest tokens
"DASHBOARD_RBAC": True, # Controle de acesso
"SQLLAB_BACKEND_PERSISTENCE": True, # Persistência SQL Lab
"DRILL_BY": True, # Drill-down
"DASHBOARD_CROSS_FILTERS": True, # Filtros cruzados
}
```
---
## 5. SEGURANÇA
### 5.1 ArcadiaSecurityManager
- Extrai JWT do header `Authorization: Bearer <token>`
- Identifica tenant do payload JWT (`tenant_id`)
- Aplica RLS automaticamente via `SET LOCAL app.current_tenant`
- Mapeia roles: superadmin→Admin, admin→Alpha, analyst→Gamma, viewer→Public
### 5.2 Row Level Security (RLS)
```sql
-- Política aplicada automaticamente
SET LOCAL app.current_tenant = <tenant_id>;
-- Views filtram por tenant
CREATE VIEW tenant_data AS
SELECT * FROM data
WHERE tenant_id = current_setting('app.current_tenant')::int;
```
---
## 6. CLIENTE TYPESCRIPT
### 6.1 Uso Básico
```typescript
import { metasetClient } from "./server/bi/metaset-client";
// Health check
const health = await metasetClient.isHealthy();
// Listar dashboards
const dashboards = await metasetClient.listDashboards();
// Executar query
const result = await metasetClient.executeSql(1, "SELECT * FROM sales LIMIT 100");
// Criar dashboard
const dashboard = await metasetClient.createDashboard({
name: "Vendas Q1 2026",
description: "Dashboard de vendas"
});
// Guest token para embedding
const token = await metasetClient.createGuestToken({
resources: [{ type: "dashboard", id: "1" }],
rls: [{ dataset: 1, clause: "tenant_id = 123" }]
});
```
### 6.2 Ferramentas do Manus (Agente IA)
| Ferramenta | Descrição |
|------------|-----------|
| `metaset.query` | Executa SQL |
| `metaset.list_tables` | Lista tabelas |
| `metaset.table_fields` | Campos da tabela |
| `metaset.create_chart` | Cria gráfico |
| `metaset.list_charts` | Lista gráficos |
| `metaset.create_dashboard` | Cria dashboard |
| `metaset.list_dashboards` | Lista dashboards |
| `metaset.suggest_analysis` | Sugere análises |
| `metaset.sync_database` | Sincroniza schema |
| `metaset.health` | Verifica status |
---
## 7. DEPLOYMENT
### 7.1 Docker Compose
```yaml
services:
metaset:
build: ./server/bi/metaset
ports:
- "8100:8100"
environment:
- DATABASE_URL=postgresql://arcadia:arcadia123@db:5432/metaset_db
- REDIS_URL=redis://redis:6379/1
labels:
- "arcadia.discovery.enabled=true"
- "arcadia.name=MetaSet BI"
- "arcadia.type=python"
- "arcadia.port=8100"
- "arcadia.capabilities=dashboards,charts,sql_lab,rls,embedding"
```
### 7.2 Inicialização
```bash
# Via docker compose
docker compose up metaset
# Ou diretamente
cd server/bi/metaset
python run.py --host 0.0.0.0 --port 8100 --workers 2
```
---
## 8. HISTÓRICO DE MUDANÇAS
| Data | Versão | Mudança |
|------|--------|---------|
| 2026-03 | 1.0 | Plano inicial de migração Metabase → Superset |
| 2026-04-08 | 2.0 | **Consolidação concluída** - Cliente TypeScript, rotas, documentação |
---
## 9. REFERÊNCIAS
- `/server/metaset/README.md` - Documentação completa do MetaSet
- `/server/bi/metaset-client/index.ts` - Cliente TypeScript
- `/server/bi/metaset-client/routes.ts` - Rotas Express
- [Apache Superset Docs](https://superset.apache.org/docs/)
- [Superset API Reference](https://superset.apache.org/docs/api/)

View File

@ -1,326 +0,0 @@
---
⚠️ **AVISO: DOCUMENTAÇÃO HISTÓRICA**
Este documento foi criado em **Março/2026** como planejamento estratégico.
Pode conter informações desatualizadas em relação ao sistema atual.
Para documentação técnica atualizada, consulte:
- \_SPEC_NAVBAR.md\_ (componentes UI)
- \_KERNEL_SPEC.md\_ (arquitetura do Kernel)
- Código-fonte em \_server/modules/\_
---
# PLANO DE IMPLEMENTAÇÃO: ARCÁDIA AGENTIC SUITE
**Versão:** 1.0 | **Baseado em:** Planejamento Estratégico Arcádia v1.0 (Março 2026)
**Rastreamento:** Ações marcadas com `[DATA | HORA | AUTOR | DETALHE]`
---
## LEGENDA DE STATUS
```
[ ] Pendente
[~] Em andamento
[x] Concluído
[!] Bloqueado / Atenção
[?] Requer decisão antes de executar
```
---
## ALERTAS DE CONFLITO COM SISTEMA EXISTENTE
> Itens marcados com **(CONFLITO POTENCIAL)** podem sobrescrever ou interferir
> com configurações já existentes no Arcádia. CONFIRMAR antes de executar.
| Item | Conflito | Observação |
|------|----------|------------|
| `docker-compose.yml` | **(CONFLITO POTENCIAL)** | Pode já existir com serviços configurados |
| Apache Superset | **(CONFLITO POTENCIAL)** | RLS e dashboards já configurados — nova instância pode perder dados |
| Tabelas `skills`, `automations` | **(CONFLITO POTENCIAL)** | Verificar se já existem no schema atual |
| XOS/Automations | **(CONFLITO POTENCIAL)** | Unificação na Fase 5 altera o runtime existente |
| `/automations` (Central) | **(CONFLITO POTENCIAL)** | Será migrado/substituído na Fase 5 |
---
## FASE 1 — FUNDAÇÃO (Semanas 1-2)
**Entregável:** Infraestrutura base funcionando
### 1.1 Submodules / Clone
- [ ] Clonar MiroFlow como submodule em `server/modules/miroflow/`
```bash
git submodule add https://github.com/MiroMindAI/MiroFlow.git server/modules/miroflow
```
- [ ] Clonar OpenClaw como submodule em `server/modules/openclaw/`
```bash
git submodule add https://github.com/miaoxworld/OpenClawInstaller.git server/modules/openclaw
```
- [ ] Inicializar submodules
```bash
git submodule update --init --recursive
```
### 1.2 Banco de Dados — Novas Tabelas
> **(CONFLITO POTENCIAL)** — Verificar se tabelas já existem antes de criar
- [ ] Criar tabela `skills` no PostgreSQL
- [ ] Criar tabela `automations` no PostgreSQL
- [ ] Criar tabela `skill_executions` no PostgreSQL
- [ ] Criar migration e executar `npm run db:migrate`
### 1.3 Knowledge Graph (Neo4j) — Novos Nós
- [ ] Estender KG com nó: `Skill`
- [ ] Estender KG com nó: `Automation`
- [ ] Estender KG com nó: `Execution`
### 1.4 Reference System
- [ ] Implementar `ReferenceParser``server/modules/skills/ReferenceParser.ts`
- [ ] Implementar `ReferenceResolver``server/modules/skills/ReferenceResolver.ts`
### 1.5 Docker Compose / Infraestrutura
> **(CONFLITO POTENCIAL)** — `docker-compose.yml` pode já existir.
> Confirmar com João antes de sobrescrever.
- [?] Verificar se `docker-compose.yml` já existe no projeto
- [?] Se existir: mesclar serviços (Neo4j, Ollama, Superset) sem sobrescrever configs atuais
- [ ] Garantir serviços no compose: PostgreSQL, Neo4j, Ollama, Superset, Arcádia Suite
---
## FASE 2 — SKILLS ENGINE (Semanas 3-4)
**Entregável:** Skills criáveis e executáveis
### 2.1 Backend — Skill Engine
- [ ] Implementar `SkillEngine.ts``server/modules/skills/SkillEngine.ts`
- Suporte a herança (`extends`)
- Suporte a composição (`dependencies`)
- Suporte a polimorfismo (`execute()`)
- [ ] Implementar `SkillRepository.ts` — CRUD de skills no banco
### 2.2 Sistema de Namespaces
- [ ] Hierarquia: `system``tenant``company``user`
- [ ] Resolver prioridade e herança entre namespaces
### 2.3 Frontend — Editor de Skills
- [ ] Criar componente `SkillEditor.tsx``client/src/components/skills/`
- Modo Visual
- Modo Code (TypeScript)
- Modo Markdown
- [ ] Criar `ReferenceParser.ts` no frontend com autocomplete de `/`
- [ ] Criar `SkillMarketplace.tsx` — descoberta de skills disponíveis
### 2.4 Versionamento de Skills
- [ ] Implementar versionamento Git-like para skills (histórico de versões)
---
## FASE 3 — MIROFLOW EMBUTIDO (Semanas 5-6)
**Entregável:** Análises científicas disponíveis
### 3.1 Configuração MiroFlow para Ollama Local
- [ ] Criar `config/arcadia_agents.yaml` em `server/modules/miroflow/config/`
- `provider: ollama`
- `base_url: http://localhost:11434`
- Modelos: `deepseek-r1:32b` (default), `deepseek-r1:70b` (reasoning), `llama3.1:8b` (fast)
- [ ] Instalar dependências Python: `pip install -r requirements.txt`
### 3.2 Agentes Especializados
- [ ] Configurar agente `Arcádia Statistician` (deepseek-r1:32b)
- Tools: arcadia_sql_query, pandas_analysis, scipy_stats, visualization_generator
- [ ] Configurar agente `Arcádia Fiscal Auditor` (deepseek-r1:70b)
- Tools: nfe_validator, sped_generator, tax_calculator, risk_scorer
- [ ] Configurar agente `Arcádia Researcher` (llama3.1:70b)
- Tools: knowledge_graph_query, document_search, trend_analyzer
### 3.3 Bridge Superset → MiroFlow
> **(CONFLITO POTENCIAL)** — Superset já está em produção com RLS configurado.
> A bridge adiciona funcionalidade nova (Modo Científico), não deve quebrar configs existentes.
> Confirmar antes de modificar qualquer arquivo do Superset atual.
- [?] Verificar se a instância atual do Superset suporta custom viz (`BaseViz`)
- [ ] Criar `MiroFlowBridge.ts``server/modules/superset/MiroFlowBridge.ts`
- [ ] Criar `miroflow_scientific.py` — viz customizada no Superset
- Endpoint: `POST /api/miroflow/analyze`
- [ ] Criar `MiroFlowControl.tsx` — toggle "Modo Científico" no frontend
- `client/src/modules/superset-bridge/MiroFlowControl.tsx`
### 3.4 Integração com Arcádia Audit
- [ ] Garantir que execuções MiroFlow sejam registradas com `immutable_logging: true`
- [ ] Registrar proveniência no Knowledge Graph (dataset versions, analysis lineage, model registry)
### 3.5 Modelos Ollama
- [ ] Baixar modelo: `deepseek-r1:32b`
- [ ] Baixar modelo: `deepseek-r1:70b`
- [ ] Baixar modelo: `llama3.1:8b`
- [ ] Baixar modelo: `llama3.1:70b`
### 3.6 Dev Center — Dashboard de Benchmarks
- [ ] Criar dashboard de benchmarks MiroFlow no Dev Center (GAIA, HLE, FutureX)
---
## FASE 4 — OPENCLAW EMBUTIDO (Semanas 7-8)
**Entregável:** Skills emergentes funcionando
### 4.1 Backend — Pattern Detection
- [ ] Implementar `PatternDetector.ts``server/modules/openclaw/PatternDetector.ts`
- `min_occurrences: 3` | `time_window_days: 30` | `confidence_threshold: 0.8`
- [ ] Implementar `SkillEmergence.ts` — criação de skills emergentes como DRAFT
- [ ] Implementar `OpenClawEngine.ts` — motor de sugestões
### 4.2 Frontend — Widget
- [ ] Criar `OpenClawWidget.tsx` — widget flutuante
- `client/src/modules/openclaw/OpenClawWidget.tsx`
- [ ] Criar `PatternDetector.ts` frontend — hook de detecção
- [ ] Criar `SkillSuggestion.tsx` — modal de sugestão ao usuário
- [ ] Criar `useAgentEmergence.ts` — lógica de emergência
### 4.3 Configuração OpenClaw Arcádia
- [ ] Criar `config/arcadia.yaml` em `server/modules/openclaw/config/`
- `tenant_aware: true`
- `auto_suggest: false` (sempre pedir confirmação)
- `create_as_draft: true` (requer aprovação)
- `auto_publish: false`
- `notify_admins: true`
- [ ] Instalar dependências Node: `cd server/modules/openclaw && npm install`
### 4.4 Fluxo Completo de Emergência
- [ ] Implementar fluxo: Padrão detectado → Skill DRAFT criado → Dev Center para aprovação
- [ ] Integrar com Blackboard para codegen automático da skill emergente
- [ ] Painel de configuração de sugestões por tenant
---
## FASE 5 — AUTOMATION FABRIC (Semanas 9-10)
**Entregável:** Automações unificadas
> **(CONFLITO POTENCIAL)** — Esta fase unifica XOS/Automations e `/automations` Central.
> São sistemas ATIVOS no Arcádia. Migração de dados necessária.
> Confirmar estratégia de migração com João antes de iniciar.
### 5.1 Backend — Runtimes Unificados
- [ ] Implementar `AutomationEngine.ts``server/modules/automations/AutomationEngine.ts`
- [ ] Implementar runtime `WorkflowEngine.ts` (BPMN, processos longos, aprovações)
- [ ] Implementar runtime `RuleEngine.ts` (IFTTT simples, rápido)
- [ ] Implementar runtime `AgentExecutor.ts` (Manus/OpenClaw, IA adaptativa)
- [ ] Implementar runtime `ScheduleEngine.ts` (Cron, recorrente)
- [ ] (Opcional) Implementar runtime `EventEngine.ts` (Webhook, real-time)
### 5.2 Modelo Unificado de Automação
- [ ] Definir schema `Automation` com campo `runtime` polimórfico
- tipos: `workflow | rule | agent | scheduled | event`
- [ ] Implementar `auditHash` para imutabilidade
### 5.3 Migração de Dados
- [ ] Mapear automações existentes em XOS para novo modelo
- [ ] Mapear automações existentes em `/automations` Central para novo modelo
- [ ] Executar migração sem downtime
### 5.4 Frontend — UI Única
- [ ] Criar `AutomationCenter.tsx``client/src/modules/automations/AutomationCenter.tsx`
- [ ] Criar `RuleDesigner.tsx` — designer visual de regras
- [ ] Criar `WorkflowDesigner.tsx` — designer BPMN
- [ ] Criar `AgentDesigner.tsx` — designer de agentes IA
- [ ] Criar `AutomationList.tsx` — listagem unificada
### 5.5 Testes
- [ ] Testes de carga nas automações unificadas
- [ ] Testes de segurança (permissões por tenant/company/user)
---
## FASE 6 — DEV CENTER COMPLETO (Semanas 11-12)
**Entregável:** Sistema completo operacional
### 6.1 Design Studio
- [ ] Criar `DesignStudio.tsx``client/src/modules/devcenter/DesignStudio.tsx`
- Modo UML Diagram (classes, herança, relações)
- Modo Visual Flow (drag-drop nodes)
- Modo Markdown Spec (linguagem natural)
- Modo Code Editor (TypeScript + IntelliSense)
### 6.2 Assemble Line
- [ ] Criar `AssembleLine.tsx``client/src/modules/devcenter/AssembleLine.tsx`
- Composition Panel (available skills → selected skills)
- Flow Diagram automático
- Botões: Generate Code | Preview | Test | Deploy
- [ ] Integrar com Blackboard (GeneratorAgent, ValidatorAgent, ExecutorAgent)
### 6.3 Orchestrate Center
- [ ] Criar `OrchestrateCenter.tsx``client/src/modules/devcenter/OrchestrateCenter.tsx`
- Deploy de agentes
- Versionamento
- Monitoramento de execuções
### 6.4 Galeria de Agentes
- [ ] Criar marketplace interno de agentes (descoberta, instalação por tenant)
### 6.5 Finalização
- [ ] Documentação técnica dos componentes criados
- [ ] Treinamento / onboarding interno
- [ ] `npm run test:miroflow`
- [ ] `npm run test:openclaw`
- [ ] `npm run test:integration`
---
## REGISTRO DE AÇÕES EXECUTADAS
> Formato: `[AAAA-MM-DD | HH:MM | AUTOR | O que foi feito e em qual arquivo/componente]`
| # | Data | Hora | Autor | Ação | Fase |
|---|------|------|-------|------|------|
| 1 | 2026-03-24 | — | João | Adicionado serviço `neo4j` (profile: `kg`) ao `docker-compose.yml` + volumes `neo4j_data` e `neo4j_logs` | Fase 1 |
| 2 | 2026-03-24 | — | João | Adicionado serviço `neo4j` (profile: `kg`) ao `docker-compose.prod.yml` + volumes `neo4j_data` e `neo4j_logs` | Fase 1 |
| 3 | 2026-03-24 | — | João | Adicionadas tabelas `arcadia_skills` (modelo POO) e `skill_executions` ao `shared/schema.ts``xosSkillRegistry` preservado | Fase 1 + 2 |
| 4 | 2026-03-24 | — | João | Criada migration `0003_arcadia_skills.sql` — cria tabelas `arcadia_skills` e `skill_executions` com índices | Fase 1 |
---
## DECISÕES ARQUITETURAIS REGISTRADAS
| Componente | Decisão | Status |
|------------|---------|--------|
| MiroFlow | EMBUTIR como submodule em `server/modules/miroflow/` | Pendente |
| OpenClaw | EMBUTIR como submodule em `server/modules/openclaw/` | Pendente |
| Apache Superset | INTEGRAR externamente com bridge MiroFlow | Pendente |
| Skills | Modelo POO (herança, composição, polimorfismo) | Pendente |
| Automações | Unificar XOS + Central em runtime único | Pendente |
| Dev Center | Fábrica: Design → Assemble → Deploy | Pendente |
| Multi-tenant | Hierarquia: System → Tenant → Company → User | Pendente |
---
*Documento gerado em: 2026-03-24 | Baseado no Planejamento Estratégico Arcádia v1.0*

View File

@ -1,617 +0,0 @@
Plano de Implementação: MetaSet no ArcadiaSuite (Atualizado - 07/04/2026)
📊 Visão Geral da Arquitetura Atual
┌─────────────────────────────────────────────────────────────────────────┐
│ ARCADIA KERNEL (5001) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Dashboard │ │ Process │ │ Health │ │ LogAggregator │ │
│ │ (Web UI) │ │ Manager │ │ Monitor │ │ │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └─────────────────┘ │
│ │ │ │ │
│ └────────────────┴────────────────┘ │
│ │ │
│ ┌───────────┴───────────┐ │
│ │ Service Registry │ │
│ │ (services.json) │ │
│ └───────────┬───────────┘ │
│ │ │
│ ┌─────────────────────┼─────────────────────┐ │
│ │ │ │ │
│ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐ │
│ │9001 │ │9002 │ │9003 │ + NOVO: 8004 │
│ │Comm │ │Core │ │Worker│ + NOVO: 8100 │
│ │Whats│ │ API │ │Queue │ (MetaSet) │
│ └─────┘ └─────┘ └─────┘ │
│ │
│ 🆕 SERVIÇOS BI: │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 8004 │ │ 8100 │ │ 8200 │ │
│ │ BI-API │──│ MetaSet │ │ FDB-Bridge │ │
│ │ (Node.js) │ │ (Python) │ │ (Python) │ │
│ │ Gateway │ │ Superset │ │ Firebird │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
┌──────────┴──────────┐
│ PostgreSQL 16 │
│ ┌───────────────┐ │
│ │ arcadia_db │ │
│ │ metaset_db │ │ ✅ Schema criado e migrado
│ └───────────────┘ │
└─────────────────────┘
🔧 Configuração dos Serviços (services.json)
{
"services": [
{
"id": "communication",
"name": "Communication Service",
"type": "node",
"command": "node",
"args": ["server/communication/index.js"],
"cwd": ".",
"port": 9001,
"autoStart": true,
"healthCheck": {
"path": "/health",
"interval": 30000,
"timeout": 5000,
"retries": 3
},
"maxRestarts": 5
},
{
"id": "core-api",
"name": "Core API",
"type": "node",
"command": "node",
"args": ["server/core/index.js"],
"cwd": ".",
"port": 9002,
"autoStart": true,
"healthCheck": {
"path": "/health",
"interval": 30000,
"timeout": 5000,
"retries": 3
},
"maxRestarts": 5
},
{
"id": "bi-api",
"name": "BI API Gateway",
"type": "node",
"command": "node",
"args": ["server/bi/index.js"],
"cwd": ".",
"port": 8004,
"autoStart": true,
"env": {
"METASET_PORT": "8100",
"METASET_HOST": "127.0.0.1",
"FDB_BRIDGE_PORT": "8200"
},
"healthCheck": {
"path": "/bi/health",
"interval": 30000,
"timeout": 5000,
"retries": 3
},
"maxRestarts": 5,
"description": "Gateway de BI com proxy para MetaSet e FDB-Bridge"
},
{
"id": "metaset",
"name": "MetaSet BI",
"type": "python",
"command": "python",
"args": ["server/bi/metaset/run.py", "--port=8100", "--host=127.0.0.1"],
"cwd": ".",
"port": 8100,
"autoStart": false,
"env": {
"PYTHONPATH": "server/bi/metaset",
"FLASK_ENV": "production",
"SUPERSET_CONFIG_PATH": "server/bi/metaset/superset_config.py",
"SUPERSET_SECRET_KEY": "${SUPERSET_SECRET_KEY}"
},
"healthCheck": {
"path": "/health",
"interval": 30000,
"timeout": 10000,
"retries": 3
},
"maxRestarts": 3,
"description": "Apache Superset rebrandado como MetaSet (Docker-managed)"
},
{
"id": "fdb-bridge",
"name": "FDB Bridge",
"type": "python",
"command": "python",
"args": ["server/bi/fdb-bridge/main.py", "--port=8200"],
"cwd": ".",
"port": 8200,
"autoStart": false,
"env": {
"PYTHONPATH": "server/bi/fdb-bridge",
"FDB_HOST": "${FDB_HOST}",
"FDB_PORT": "${FDB_PORT:-3050}"
},
"healthCheck": {
"path": "/health",
"interval": 60000,
"timeout": 5000,
"retries": 3
},
"maxRestarts": 3,
"description": "Conector Firebird para sincronização de dados (Docker-managed)"
}
]
}
🏗️ Estrutura de Diretórios Final
arcadiasuite/
├── server/
│ ├── kernel/ # ✅ Kernel finalizado
│ │ ├── core/
│ │ │ ├── ProcessManager.ts # ✅ Suporta 'python' e 'node'
│ │ │ ├── HealthMonitor.ts
│ │ │ └── LogAggregator.ts
│ │ ├── dashboard/ # Dashboard Web UI (porta 5001)
│ │ ├── config/
│ │ │ └── services.json # ✅ Configuração dos serviços
│ │ └── index.ts
│ │
│ ├── bi/ # 🆕 MÓDULO BI COMPLETO
│ │ ├── index.ts # BI-API Gateway (porta 8004)
│ │ ├── routes/
│ │ │ ├── query.ts # Queries nativas Drizzle
│ │ │ ├── export.ts # Exportação PDF/Word
│ │ │ └── sync.ts # Sincronização FDB
│ │ │
│ │ ├── metaset/ # 🆕 METASET (Superset)
│ │ │ ├── run.py # Entry point Python
│ │ │ ├── superset_config.py # Configuração Arcadia
│ │ │ ├── security_manager.py # ArcadiaSecurityManager
│ │ │ ├── requirements.txt # Dependências Python
│ │ │ ├── Dockerfile # Multi-stage build
│ │ │ ├── init.sh # Container init script
│ │ │ ├── branding/ # 🎨 Assets MetaSet
│ │ │ │ ├── metaset-logo-horiz.png
│ │ │ │ ├── metaset-icon.png
│ │ │ │ ├── favicon-16x16.png
│ │ │ │ └── favicon-32x32.png
│ │ │ │
│ │ │ └── superset-src/ # Fork Apache Superset 4.1.0
│ │ │ ├── superset-frontend/ # ✅ Rebuild com branding MetaSet
│ │ │ │ └── src/
│ │ │ │ ├── features/home/RightMenu.tsx # ✅ Textos atualizados
│ │ │ │ ├── components/ErrorMessage/OAuth2RedirectMessage.tsx
│ │ │ │ └── pages/... # ✅ Traduções atualizadas
│ │ │ │
│ │ │ ├── superset/
│ │ │ │ ├── config.py # ✅ APP_NAME = "MetaSet"
│ │ │ │ ├── constants.py # ✅ USER_AGENT = "MetaSet"
│ │ │ │ ├── cli/
│ │ │ │ ├── templates/ # ✅ HTML templates atualizados
│ │ │ │ └── translations/ # ✅ Traduções en/pt_BR
│ │ │ └── ...
│ │ │
│ │ └── fdb-bridge/ # 🆕 FIREBIRD CONNECTOR (skeleton)
│ │ ├── main.py # Serviço Python porta 8200
│ │ ├── sync.py # Lógica de sincronização
│ │ ├── models.py # Modelos SQLAlchemy
│ │ └── requirements.txt
│ │
│ └── ... (outros serviços)
├── client/
│ └── src/
│ └── pages/
│ └── bi/ # 🆕 FRONTEND BI
│ ├── index.tsx # Dashboard BI Arcadia
│ ├── metaset/ # Embed MetaSet
│ │ ├── Dashboard.tsx
│ │ ├── SQLLab.tsx
│ │ └── Explore.tsx
│ └── components/
│ ├── QueryBuilder.tsx
│ ├── ExportButton.tsx
│ └── FDBSyncStatus.tsx
└── branding/ # 🆕 ASSETS COMPARTILHADOS
├── logos/
│ ├── arcadia-suite-logo.svg
│ ├── arcadia-suite-logo-horiz.svg
│ ├── metaset-logo.svg
│ └── metaset-icon.svg
├── colors/
│ └── arcadia-palette.json
└── fonts/
└── Inter-Variable.ttf
🔐 Arquitetura de Segurança & Multi-tenancy
Fluxo de Autenticação
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Cliente │────▶│ Arcadia │────▶│ BI-API │────▶│ MetaSet │
│ (React) │ │ Login │ │ (8004) │ │ (8100) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │
│ Headers: │ SET LOCAL
│ X-Tenant-ID: 123 │ app.current_tenant
│ X-User-Role: admin │ = 123;
│ Authorization: JWT │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ RLS Check │ │ PostgreSQL │
│ Middleware │ │ (RLS) │
└─────────────┘ └─────────────┘
ArcadiaSecurityManager (server/bi/metaset/security_manager.py) ✅ CORRIGIDO
"""
Security Manager integrado ao Arcadia Kernel
- Extrai tenant e usuário do token JWT
- Aplica RLS automaticamente
- Sincroniza roles com ArcadiaSuite
- Preserva comportamento padrão do Superset (chama super().before_request())
"""
from superset.security import SupersetSecurityManager
from flask import g, request, current_app
from sqlalchemy import text
import jwt
import logging
logger = logging.getLogger(__name__)
class ArcadiaSecurityManager(SupersetSecurityManager):
ROLE_MAPPING = {
'superadmin': 'Admin',
'admin': 'Alpha',
'analyst': 'Gamma',
'viewer': 'Public',
}
def before_request(self):
"""Executado antes de cada requisição - preserva comportamento padrão"""
super().before_request() # ← CRÍTICO: preserva autenticação padrão
auth_header = request.headers.get('Authorization', '')
if not auth_header.startswith('Bearer '):
return
token = auth_header.replace('Bearer ', '')
try:
payload = jwt.decode(
token,
current_app.config.get('SECRET_KEY', 'default-key'),
algorithms=['HS256']
)
g.tenant_id = payload.get('tenant_id')
g.user_id = payload.get('sub')
g.user_role = payload.get('role', 'viewer')
g.user_email = payload.get('email')
if g.tenant_id:
try:
from superset import db
db.session.execute(
text(f"SET LOCAL app.current_tenant = {g.tenant_id}")
)
except Exception as e:
logger.warning(f"Erro ao aplicar RLS: {e}")
except jwt.ExpiredSignatureError:
logger.warning("JWT token expirado")
except jwt.InvalidTokenError:
logger.warning("JWT token inválido")
except Exception as e:
logger.warning(f"Erro ao processar JWT: {e}")
🐍 Código dos Serviços Python
1. MetaSet Entry Point (server/bi/metaset/run.py)
"""
MetaSet - Serviço Apache Superset integrado ao Arcadia Kernel
Porta: 8100 (Docker container)
Gunicorn: 2 workers, gthread, 120s timeout
"""
import os
import sys
import logging
from pathlib import Path
logging.basicConfig(
level=logging.INFO,
format='[MetaSet] %(levelname)s: %(message)s',
handlers=[logging.StreamHandler(sys.stdout)]
)
logger = logging.getLogger(__name__)
def create_app():
"""Criar aplicação Flask do Superset com endpoints customizados"""
from superset import create_app as create_superset_app
from datetime import datetime
app = create_superset_app()
@app.route('/health')
def health_check():
return {'status': 'healthy', 'service': 'metaset'}, 200
@app.route('/info')
def service_info():
return {
'name': 'MetaSet',
'description': 'Business Intelligence by ArcadiaSuite',
'version': app.config.get('VERSION_STRING', '4.1.0'),
'features': ['sql_lab', 'dashboards', 'charts', 'rls', 'embedding']
}
return app
# MetaSetApplication: custom Gunicorn app
# Config: 2 workers, gthread, 120s timeout, stdout/stderr logging
2. BI-API Gateway (server/bi/index.ts)
"""
* BI-API Gateway
* Porta: 8004
* Integra MetaSet (8100) e FDB-Bridge (8200) ao ArcadiaSuite
* Proxy manual usando módulo http nativo (bypass http-proxy-middleware ECONNRESET)
"""
import express from 'express';
import http from 'http';
const METASET_URL = `http://${process.env.METASET_HOST || 'metaset'}:${process.env.METASET_PORT || '8100'}`;
// Manual proxy: http.request() com pathRewrite
// Solução para ECONNRESET com Gunicorn keepalive
// Health check agrega MetaSet + FDB-Bridge status
🎨 Configuração de Branding (superset_config.py)
"""
Configuração do MetaSet (Superset) para ArcadiaSuite
"""
import os
from pathlib import Path
BASE_DIR = Path(__file__).parent.absolute()
SUPERSET_HOME = BASE_DIR / 'superset_home'
# ==========================================
# BRANDING - ARCADIASUITE ✅ IMPLEMENTADO
# ==========================================
APP_NAME = "MetaSet by Arcádia"
APP_ICON = "/static/assets/images/branding/metaset-logo-horiz.png"
LOGO_TARGET_PATH = "/"
LOGO_TOOLTIP = "MetaSet - Business Intelligence by ArcadiaSuite"
# Favicons
FAVICONS = [
{"href": "/static/assets/images/branding/favicon-16x16.png", "sizes": "16x16"},
{"href": "/static/assets/images/branding/favicon-32x32.png", "sizes": "32x32"},
{"href": "/static/assets/images/branding/apple-touch-icon.png", "sizes": "180x180"},
]
# Cores ArcadiaSuite
ARCADIA_PRIMARY = "#0ea5e9" # Azul celeste
ARCADIA_SECONDARY = "#10b981" # Verde
ARCADIA_ACCENT = "#f59e0b" # Laranja
ARCADIA_BG = "#f8fafc" # Background claro
ARCADIA_TEXT = "#1e293b" # Texto escuro
# Desabilitar cache de arquivos estáticos
SEND_FILE_MAX_AGE_DEFAULT = 0
THEME_DEFAULT = {
"token": {
"brandAppName": "MetaSet",
"brandLogoAlt": "MetaSet by ArcadiaSuite",
"brandLogoUrl": "/static/assets/images/branding/metaset-logo-horiz.png",
"brandLogoMargin": "16px 0",
"brandLogoHeight": "28px",
"colorPrimary": ARCADIA_PRIMARY,
"colorLink": ARCADIA_PRIMARY,
"colorSuccess": ARCADIA_SECONDARY,
"colorWarning": ARCADIA_ACCENT,
"colorError": "#ef4444",
"colorInfo": "#3b82f6",
"colorBgLayout": ARCADIA_BG,
"colorBgContainer": "#ffffff",
"colorText": ARCADIA_TEXT,
"colorTextSecondary": "#64748b",
"fontFamily": "Inter, -apple-system, BlinkMacSystemFont, sans-serif",
"fontSize": 14,
"borderRadius": 8,
"borderRadiusLG": 12,
"boxShadow": "0 1px 3px 0 rgba(0, 0, 0, 0.1)",
"boxShadowSecondary": "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
},
"algorithm": "default",
}
THEME_DARK = { ... } # Tema escuro
# ==========================================
# SEGURANÇA & MULTI-TENANCY
# ==========================================
from security_manager import ArcadiaSecurityManager
CUSTOM_SECURITY_MANAGER = ArcadiaSecurityManager # ✅ Funcionando
AUTH_TYPE = 1
WTF_CSRF_ENABLED = True
WTF_CSRF_TIME_LIMIT = 3600
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = False # True em produção com HTTPS
SESSION_COOKIE_SAMESITE = "Lax"
# ==========================================
# BANCO DE DADOS
# ==========================================
SQLALCHEMY_DATABASE_URI = os.getenv(
'DATABASE_URL',
'postgresql://arcadia:arcadia123@db:5432/metaset_db'
)
SQLALCHEMY_ENGINE_OPTIONS = {
"connect_args": {"options": "-c row_security=on"},
"pool_pre_ping": True,
"pool_recycle": 300,
}
# ==========================================
# FEATURE FLAGS
# ==========================================
FEATURE_FLAGS = {
"EMBEDDED_SUPERSET": True,
"EMBEDDABLE_CHARTS": True,
"DASHBOARD_RBAC": True,
"SQLLAB_BACKEND_PERSISTENCE": True,
"DASHBOARD_VIRTUALIZATION": True,
"DRILL_BY": True,
"CSS_TEMPLATES": True,
"ENABLE_JAVASCRIPT_CONTROLS": False,
"ALERT_REPORTS": False,
}
📋 Roadmap de Implementação (8 Semanas)
Semana Foco Entregáveis
1 Setup MetaSet Fork Superset, branding básico (logo, cores), superset_config.py
2 Integração Kernel services.json atualizado, BI-API gateway, proxy funcionando
3 Segurança ArcadiaSecurityManager, JWT validation, injeção de headers
4 Multi-tenancy RLS PostgreSQL, isolamento de datasources, testes de segurança
5 FDB Bridge Serviço Python porta 8200, conexão Firebird, testes de sync
6 Sync Engine Sincronização incremental, agendamento, resolução de conflitos
7 Frontend Páginas BI no React, embed de dashboards, SQL Lab integrado
8 Go-Live Testes E2E, documentação, otimização de performance
✅ Checklist de Implementação - STATUS ATUAL
Semana 1-2: Setup e Integração ✅ CONCLUÍDO
✅ Clonar superset (4.1.0rc4) para server/bi/metaset/superset-src
✅ Criar superset_config.py com branding ArcadiaSuite (APP_NAME, cores, tema)
✅ Rebuild do frontend React com logos MetaSet (COMPLETO - 07/04/2026)
- Substituição de textos: "Superset" → "MetaSet" em todo codebase
- Traduções atualizadas: en/LC_MESSAGES/messages.po, pt_BR/LC_MESSAGES/messages.po
- Arquivos fonte atualizados: RightMenu.tsx, OAuth2RedirectMessage.tsx,
SliceHeaderControls, HeaderActionsDropdown, AnchorLink, EmbeddedModal,
useExploreAdditionalActionsMenu
- Templates HTML: basic.html, public_welcome.html, 404.html, 500.html
- Config Python: config.py (APP_NAME), constants.py (USER_AGENT), cli/*.py
- Build webpack concluído com sucesso (429 warnings, 0 errors)
- Assets compilados com branding MetaSet incorporado
✅ Criar server/bi/metaset/run.py entry point (Gunicorn)
✅ Criar server/bi/metaset/init.sh container initialization
✅ Criar server/bi/index.ts BI-API gateway (proxy manual Node.js)
✅ Atualizar server/kernel/config/services.json (autoStart: false, Docker Discovery)
✅ Docker Compose: metaset + bi-api + redes configuradas
✅ Dockerfile multi-stage build com assets locais (não mais copiando de apache/superset:latest)
✅ Deploy: https://bi.onboardbi.com.br (MetaSet rodando com branding completo)
✅ Container antigo arcadia-prod-superset-1 removido
✅ Container metaset conectado à rede coolify para Traefik
✅ Banco de dados metaset_db migrado e inicializado
✅ Usuário admin criado (login: admin / senha: admin)
✅ BI-API Gateway proxy /bi/metaset funcionando (porta 8004)
✅ Health checks integrados (metaset + bi-api)
✅ requirements.txt corrigido (compatível apache-superset 4.1.0)
✅ extra_hosts configurado para resolver db → 10.0.10.2 (IPv4)
Semana 3-4: Segurança 🔄 EM ANDAMENTO
✅ Implementar ArcadiaSecurityManager (CORRIGIDO - chama super().before_request())
✅ Configurar JWT validation
🔄 Criar schema metaset no PostgreSQL (banco criado, migrações aplicadas)
☐ Configurar RLS em tabelas do arcadia_db
☐ Testar isolamento entre tenants
Semana 5-6: FDB Bridge ⏳ PENDENTE
☐ Criar server/bi/fdb-bridge/main.py
☐ Implementar conexão Firebird (fdb library)
☐ Criar lógica de sincronização incremental
☐ Configurar agendador APScheduler
☐ Testar sync Matriz + 5 Filiais
Semana 7-8: Frontend e Go-Live ⏳ PENDENTE
☐ Criar páginas BI no frontend React
☐ Implementar embed de dashboards MetaSet
☐ Criar componente de status de sincronização
☐ Testes E2E completos
☐ Documentação do módulo BI
📝 Lições Aprendidas (07/04/2026)
1. Frontend rebuild obrigatório: Substituir arquivos estáticos PNG no Docker
não funciona porque os assets são compilados em bundles JS pelo webpack.
É necessário rebuildar o superset-frontend com npm run build.
2. Dependências faltantes: O build pode falhar por pacotes não instalados:
@react-spring/web, global-box, currencyformatter.js. Instalar com
npm install --legacy-peer-deps.
3. Security Manager: O ArcadiaSecurityManager DEVE chamar super().before_request()
no início do método para preservar o comportamento de autenticação padrão
do Flask-AppBuilder. Sem isso, o login falha com AttributeError: user.
4. IPv6 vs IPv4: O hostname "db" resolve para IPv6 (fd20:a15a:c2d4::c) mas
o psycopg2 falha na autenticação via IPv6. Solução: mapear db → IP IPv4
via extra_hosts no docker-compose.yml.
5. Container antigo: Sempre verificar se há containers antigos com labels
do Traefik apontando para o mesmo domínio. Eles podem conflitar com
o novo container.
6. Traefik discovery: O container DEVE estar na mesma rede Docker que o
Traefik (coolify) para ser descoberto automaticamente.
💰 Custos Adicionais
Componente Especificação Custo Estimado
Memória extra +2GB para Python (MetaSet + FDB-Bridge) ~$20/mês
Storage PostgreSQL +10GB para metadata MetaSet ~$5/mês
CPU Processamento de sync FDB ~$15/mês
Total ~$40/mês
🚀 Comandos de Deploy
# 1. Build da imagem Docker
cd /opt/arcadia_merged
docker compose build --no-cache metaset
# 2. Recriar container com nova imagem
docker compose up -d --force-recreate metaset
# 3. Verificar status
curl http://localhost:8100/health
curl https://bi.onboardbi.com.br/login/ | grep "<title>"
# 4. Logs
docker logs -f arcadia-dev-metaset-1
# 5. Migrar banco (se necessário)
docker exec -e FLASK_APP=superset \
-e DATABASE_URL="postgresql://arcadia:arcadia123@10.0.10.2:5432/metaset_db" \
arcadia-dev-metaset-1 superset db upgrade
# 6. Inicializar
docker exec -e FLASK_APP=superset \
-e DATABASE_URL="postgresql://arcadia:arcadia123@10.0.10.2:5432/metaset_db" \
arcadia-dev-metaset-1 superset init
📌 URLs de Acesso
Serviço URL Status
MetaSet UI https://bi.onboardbi.com.br ✅ Online com branding MetaSet
MetaSet Health http://localhost:8100/health ✅ OK
BI-API Gateway http://localhost:8004 ⚠️ Unhealthy (esperado - requer JWT)
Traefik Dashboard http://localhost:8080 ✅ Online
🔧 Próximos Passos Prioritários
1. Configurar RLS (Row Level Security) no PostgreSQL para isolamento de tenants
2. Testar fluxo completo de autenticação com token JWT do Arcadia
3. Criar datasource de exemplo conectado ao arcadia_db
4. Implementar FDB Bridge (conector Firebird)
5. Criar páginas BI no frontend React
6. Configurar cache Redis para MetaSet

File diff suppressed because it is too large Load Diff

View File

@ -1,469 +0,0 @@
# Requisitos Técnicos - Arcádia Suite
## Documento de Arquitetura e Especificações Técnicas
### Fevereiro 2026
---
## 1. VISÃO GERAL DO SISTEMA
**Nome:** Arcádia Suite - Sistema Operacional Empresarial (SOE)
**Tipo:** Plataforma ERP/SaaS Multi-Tenant com Inteligência Artificial
**Arquitetura:** Monolito Modular com Microserviços Python auxiliares
**Licença:** Proprietário - Arcádia Business SIG
---
## 2. STACK TECNOLÓGICO
### 2.1 Linguagens de Programação
| Linguagem | Versão | Uso |
|-----------|--------|-----|
| TypeScript | 5.6.3 | Linguagem principal (Frontend + Backend) |
| JavaScript (ES Modules) | ES2022 | Runtime Node.js |
| Python | 3.11.14 | Microserviços de IA, BI, Fiscal, Contábil |
| SQL | PostgreSQL 16 | Queries, migrations, procedures |
| HTML5 / CSS3 | - | Marcação e estilos via JSX/Tailwind |
### 2.2 Runtime e Ambiente
| Componente | Versão | Descrição |
|------------|--------|-----------|
| Node.js | 20.20.0 | Runtime principal do servidor |
| Python | 3.11.14 | Runtime dos microserviços auxiliares |
| npm | 10.x | Gerenciador de pacotes Node.js |
| pip | 24.x | Gerenciador de pacotes Python |
---
## 3. FRAMEWORKS E BIBLIOTECAS PRINCIPAIS
### 3.1 Backend (Servidor)
| Tecnologia | Versão | Função |
|------------|--------|--------|
| **Express.js** | 4.21.2 | Framework HTTP principal, API REST |
| **Drizzle ORM** | 0.39.3 | ORM para PostgreSQL (type-safe) |
| **Drizzle Kit** | 0.31.4 | Ferramenta de migrations do banco |
| **Drizzle Zod** | 0.7.1 | Validação de schemas com Zod |
| **Socket.IO** | 4.8.3 | Comunicação em tempo real (WebSocket) |
| **Passport.js** | 0.7.0 | Autenticação (estratégia local) |
| **Express Session** | 1.18.1 | Gerenciamento de sessões |
| **Connect PG Simple** | 10.0.0 | Sessões persistidas no PostgreSQL |
| **Zod** | 3.25.76 | Validação de dados e schemas |
| **OpenAI SDK** | 6.15.0 | Integração com GPT-4o / GPT-4o-mini |
| **Multer** | 2.0.2 | Upload de arquivos |
| **Nodemailer** | 7.0.12 | Envio de emails (SMTP) |
| **Octokit** | 22.0.1 | Integração com GitHub API |
| **Baileys** | 7.0.0-rc.9 | WhatsApp Web API (multi-sessão) |
| **node-fetch** | 3.3.2 | Requisições HTTP |
| **pg** | 8.16.3 | Driver PostgreSQL nativo |
| **ws** | 8.18.0 | WebSocket nativo |
| **cheerio** | 1.1.2 | Web scraping / parsing HTML |
| **pdf-parse** | 2.4.5 | Extração de texto de PDFs |
| **xlsx** | 0.18.5 | Leitura/escrita de planilhas Excel |
| **docx** | 9.5.1 | Geração de documentos Word |
| **jspdf** | 4.0.0 | Geração de documentos PDF |
| **papaparse** | 5.5.3 | Parsing de arquivos CSV |
| **adm-zip** | 0.5.16 | Compactação/descompactação ZIP |
| **qrcode** | 1.5.4 | Geração de QR Codes |
| **imap / mailparser** | 0.8.19 / 3.9.1 | Leitura de emails (IMAP) |
| **http-proxy-middleware** | 3.0.5 | Proxy reverso para microserviços |
| **p-limit / p-retry** | 7.2.0 / 7.1.1 | Controle de concorrência e retry |
| **esbuild** | 0.25.0 | Bundler do servidor para produção |
| **tsx** | 4.20.5 | Execução de TypeScript em desenvolvimento |
### 3.2 Frontend (Cliente)
| Tecnologia | Versão | Função |
|------------|--------|--------|
| **React** | 19.2.0 | Biblioteca de UI principal |
| **React DOM** | 19.2.0 | Renderização DOM |
| **Vite** | 7.1.9 | Build tool e dev server |
| **Tailwind CSS** | 4.1.14 | Framework de estilos utilitários |
| **shadcn/ui (Radix UI)** | Múltiplas | 55 componentes de interface |
| **TanStack React Query** | 5.60.5 | Gerenciamento de estado servidor |
| **React Hook Form** | 7.66.0 | Gerenciamento de formulários |
| **Wouter** | 3.3.5 | Roteamento SPA (leve) |
| **Recharts** | 2.15.4 | Gráficos e visualização de dados |
| **Framer Motion** | 12.23.24 | Animações e transições |
| **Lucide React** | 0.545.0 | Biblioteca de ícones |
| **Monaco Editor** | 4.7.0 | Editor de código (IDE integrada) |
| **Xterm.js** | 5.3.0 | Terminal integrado |
| **XYFlow (React Flow)** | 12.10.0 | Diagramas de fluxo e workflows |
| **TipTap** | 3.15.3 | Editor de texto rico (WYSIWYG) |
| **Embla Carousel** | 8.6.0 | Carrosséis de conteúdo |
| **React Day Picker** | 9.11.1 | Seletor de datas |
| **React Resizable Panels** | 2.1.9 | Painéis redimensionáveis |
| **Socket.IO Client** | 4.8.3 | WebSocket no cliente |
| **cmdk** | 1.1.1 | Command palette (Ctrl+K) |
| **Sonner** | 2.0.7 | Notificações toast |
| **html2canvas** | 1.4.1 | Captura de tela / screenshots |
| **file-saver** | 2.0.5 | Download de arquivos |
| **next-themes** | 0.4.6 | Tema claro/escuro |
| **Vaul** | 1.1.2 | Drawer mobile |
| **input-otp** | 1.4.2 | Input para códigos OTP |
### 3.3 Microserviços Python (FastAPI)
| Serviço | Porta | Função |
|---------|-------|--------|
| **Fiscal (Fisco)** | 8002 | NF-e/NFC-e via nfelib, comunicação SEFAZ |
| **Contábil** | 8003 | Serviços contábeis auxiliares |
| **People (RH)** | 8004 | Cálculos de folha de pagamento |
| **BI Engine** | 8004 | Motor de Business Intelligence, SQL, Pandas |
| **Automation Engine** | 8005 | Scheduler cron, event bus, workflows |
---
## 4. BANCO DE DADOS
### 4.1 Especificações
| Item | Detalhe |
|------|---------|
| **SGBD** | PostgreSQL 16 (Neon-backed na nuvem) |
| **ORM** | Drizzle ORM (type-safe, schema-first) |
| **Migrations** | Drizzle Kit (push strategy) |
| **Schema** | Arquivo único: `shared/schema.ts` |
| **Total de Tabelas** | 331 tabelas |
| **Linhas de Schema** | 7.146 linhas |
| **Sessões** | Persistidas em PostgreSQL (connect-pg-simple) |
### 4.2 Domínios de Dados (Principais)
| Domínio | Prefixo | Tabelas | Descrição |
|---------|---------|---------|-----------|
| Retail | `retail_*` | 24 | Lojas, vendedores, estoque, vendas, comissões |
| CRM | `crm_*` | 23 | Leads, oportunidades, propostas, contratos |
| Fiscal | `fiscal_*` | 11 | NCMs, CFOPs, notas fiscais, certificados |
| Financeiro | `fin_*` | 7 | Contas bancárias, a pagar, a receber |
| Contábil | `contabil_*` | 7 | Plano de contas, lançamentos, saldos |
| People (RH) | `people_*` | 13 | Funcionários, folha, férias, ponto |
| Qualidade | `quality_*` | 7 | Amostras, laudos, RNC, documentos |
| Projetos | `pc_*` | 12 | Projetos, tarefas, membros, atividades |
| XOS Governança | `xos_*` | 22 | Agents, pipelines, auditoria, políticas |
| Comunicação | `comm_*` | 8 | Contatos, threads, mensagens unificadas |
| WhatsApp | `whatsapp_*` | 5 | Sessões, mensagens, contatos, tickets |
| Comunidades | `community_*` | 4 | Grupos, canais, membros, mensagens |
| Valuation | `valuation_*` | 14 | Avaliação empresarial, cap table |
| BI | `bi_*` | 4 | Dashboards, gráficos, datasets |
| Automações | `automation_*` | 3 | Workflows, ações, logs |
| Knowledge | `graph_*` | 2 | Grafo semântico (nós e arestas) |
| Marketplace | `marketplace_*` | 3 | Módulos, assinaturas, uso |
| DocTypes (FA) | `arc_*` / `doctype_*` | 9 | Framework de documentos customizados |
| Multi-Tenant | `tenants` | 1 | Tenants com features JSONB |
---
## 5. ARQUITETURA DO SISTEMA
### 5.1 Diagrama de Camadas
```
┌─────────────────────────────────────────────────────────────────────┐
│ CAMADA DE APRESENTAÇÃO │
│ │
│ React 19 + TypeScript + Tailwind CSS 4 + shadcn/ui │
│ 55 componentes UI + 67 páginas │
│ SPA com roteamento Wouter │
│ 89.858 linhas de código │
│ │
│ Porta: 5000 (dev via Vite HMR) │
└────────────────────────────┬────────────────────────────────────────┘
│ HTTP/WebSocket
┌────────────────────────────▼────────────────────────────────────────┐
│ CAMADA DE ORQUESTRAÇÃO │
│ │
│ Express.js 4 + Socket.IO 4 + Passport.js │
│ 45 arquivos de rotas │ ~1.386 endpoints REST │
│ 153 arquivos TypeScript servidor │
│ 58.393 linhas de código │
│ │
│ Porta: 5000 (API + WebSocket + Static) │
└──────┬─────────┬──────────┬──────────┬─────────┬───────────────────┘
│ │ │ │ │
┌──────▼───┐ ┌───▼────┐ ┌──▼───┐ ┌────▼──┐ ┌───▼─────┐
│ Fisco │ │Contábil│ │People│ │ BI │ │Automação│
│ FastAPI │ │FastAPI │ │FastAPI│ │FastAPI│ │FastAPI │
│ :8002 │ │ :8003 │ │:8004 │ │:8004 │ │ :8005 │
└──────────┘ └────────┘ └──────┘ └───────┘ └─────────┘
│ │ │ │ │
┌──────▼─────────▼──────────▼──────────▼─────────▼───────────────────┐
│ CAMADA DE DADOS │
│ │
│ PostgreSQL 16 (Neon) │ 331 tabelas │ Drizzle ORM │
│ Schema: 7.146 linhas │ Multi-Tenant com JSONB features │
│ Sessões persistidas em PostgreSQL │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
### 5.2 Padrões Arquiteturais
| Padrão | Implementação |
|--------|--------------|
| **Monolito Modular** | Servidor Node.js único com módulos isolados por domínio |
| **Multi-Tenant** | Coluna `tenantId` em todas as tabelas, tenant scoping obrigatório |
| **Schema-First** | Schema Drizzle como fonte de verdade para tipos e validação |
| **API RESTful** | Endpoints organizados por domínio (`/api/{modulo}/*`) |
| **Proxy Pattern** | Microserviços Python acessados via proxy reverso Express |
| **Event-Driven** | Socket.IO para comunicação real-time (WhatsApp, chat, notificações) |
| **Adapter Pattern** | Motors (Plus, ERPNext, Fisco) selecionados por adaptador |
| **Singleton** | ManusIntelligence como cérebro central de IA único |
| **Agent Loop** | Thought → Action → Observation para agentes autônomos |
---
## 6. INTEGRAÇÕES EXTERNAS
### 6.1 APIs e Serviços
| Integração | Protocolo | Uso |
|------------|-----------|-----|
| **OpenAI API** | REST (HTTPS) | GPT-4o para agentes IA, GPT-4o-mini para auto-replies |
| **WhatsApp (Baileys)** | WebSocket | Multi-sessão WhatsApp, mensagens, tickets |
| **GitHub API (Octokit)** | REST (HTTPS) | Commits automáticos, PRs, análise de código |
| **ERPNext** | REST (HTTPS) | Clientes, produtos, ordens de venda, financeiro |
| **Arcádia Plus (Laravel)** | REST (HTTPS) | ERP completo, NF-e/NFC-e, POS, fiscal |
| **SEFAZ** | SOAP/XML | Emissão e consulta de notas fiscais eletrônicas |
| **SMTP/IMAP** | Email | Envio (Nodemailer) e recebimento (IMAP) de emails |
| **Portais Bancários** | Planejado | Conciliação bancária automatizada |
### 6.2 Protocolos de Interoperabilidade
| Protocolo | Endpoint | Descrição |
|-----------|----------|-----------|
| **MCP** (Model Context Protocol) | `/api/mcp/v1/` | Exposição de ferramentas para agentes externos |
| **A2A** (Agent to Agent) | `/api/a2a/v1/` | Comunicação bidirecional entre agentes |
| **REST API** | `/api/*` | API principal (~1.386 endpoints) |
| **WebSocket** | Socket.IO | Real-time: chat, WhatsApp, notificações |
---
## 7. SEGURANÇA
| Aspecto | Implementação |
|---------|--------------|
| **Autenticação** | Passport.js (estratégia local) com sessões PostgreSQL |
| **Autorização** | Multi-tenant: tenant scoping obrigatório (403 se ausente) |
| **Sessões** | Express Session + connect-pg-simple (persistidas no banco) |
| **Governança** | XOS Governance Layer com 5 políticas de segurança |
| **Auditoria** | Audit trail imutável (`xos_audit_trail`) |
| **Políticas** | Proteção de arquivos críticos, bloqueio de comandos destrutivos, aprovação humana para produção |
| **Secrets** | Gerenciados via variáveis de ambiente (nunca expostos em código) |
| **API Keys** | Header `X-API-Key` para protocolos MCP/A2A |
---
## 8. INFRAESTRUTURA E DEPLOY
### 8.1 Ambiente de Desenvolvimento
| Item | Detalhe |
|------|---------|
| **Plataforma** | Replit (NixOS) |
| **Dev Server** | Vite 7 com HMR (Hot Module Replacement) |
| **Backend Dev** | tsx (execução direta de TypeScript) |
| **Porta** | 5000 (unificada: frontend + API + WebSocket) |
### 8.2 Ambiente de Produção (VPS)
| Item | Detalhe |
|------|---------|
| **Containerização** | Docker + Docker Compose |
| **Base Image** | `node:20-alpine` (multi-stage build) |
| **Build** | Vite (frontend) + esbuild (backend → `dist/index.cjs`) |
| **Banco** | PostgreSQL 16 Alpine (container separado) |
| **Volumes** | `pgdata` (banco) + `uploads` (arquivos) |
| **Healthcheck** | HTTP GET `/` a cada 30s |
| **Porta** | 5000 |
### 8.3 Variáveis de Ambiente
| Variável | Obrigatória | Descrição |
|----------|-------------|-----------|
| `DATABASE_URL` | Sim | String de conexão PostgreSQL |
| `SESSION_SECRET` | Sim | Chave secreta para sessões |
| `SSO_SECRET` | Sim | Chave para SSO multi-tenant |
| `PGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE` | Sim | Credenciais PostgreSQL |
| `OPENAI_API_KEY` | Não | API key OpenAI (funcionalidades IA) |
| `ERPNEXT_URL/API_KEY/API_SECRET` | Não | Integração ERPNext |
| `GITHUB_TOKEN` | Não | Integração GitHub |
| `MANAGER_PASSWORD` | Não | Senha do gerente (Retail) |
| `PLUS_URL/PLUS_API_TOKEN` | Não | Integração Arcádia Plus |
| `FISCO_PYTHON_URL` | Não | URL do serviço fiscal Python |
| `CONTABIL_PYTHON_URL` | Não | URL do serviço contábil Python |
---
## 9. MÉTRICAS DO PROJETO
### 9.1 Tamanho do Código
| Métrica | Valor |
|---------|-------|
| **Linhas de código (Frontend)** | 89.858 |
| **Linhas de código (Backend)** | 58.393 |
| **Linhas de Schema (Banco)** | 7.146 |
| **Total estimado** | ~155.000+ linhas |
| **Arquivos TypeScript (Server)** | 153 |
| **Arquivos TSX (Client)** | 154 |
| **Arquivos Python** | 6 |
| **Total de arquivos de código** | ~313 |
### 9.2 Complexidade
| Métrica | Valor |
|---------|-------|
| **Tabelas no banco de dados** | 331 |
| **Endpoints de API** | ~1.386 |
| **Páginas/Telas** | 67 |
| **Componentes UI (shadcn)** | 55 |
| **Arquivos de rotas (server)** | 45 |
| **Módulos do servidor** | 40+ domínios |
| **Dependências (produção)** | 108 pacotes |
| **Dependências (desenvolvimento)** | 22 pacotes |
---
## 10. REQUISITOS MÍNIMOS DO SERVIDOR (VPS)
### 10.1 Hardware Recomendado
| Recurso | Mínimo | Recomendado |
|---------|--------|-------------|
| **CPU** | 2 vCPU | 4 vCPU |
| **RAM** | 4 GB | 8 GB |
| **Disco** | 40 GB SSD | 80 GB SSD |
| **Banda** | 100 Mbps | 1 Gbps |
| **SO** | Ubuntu 22.04+ / Debian 12+ | Ubuntu 24.04 LTS |
### 10.2 Software Necessário
| Software | Versão Mínima |
|----------|--------------|
| Docker | 24.0+ |
| Docker Compose | 2.20+ |
| Git | 2.40+ |
| (Opcional) Nginx | 1.24+ (proxy reverso com SSL) |
| (Opcional) Certbot | 2.0+ (certificados Let's Encrypt) |
### 10.3 Configuração Nginx (Proxy Reverso com SSL)
```nginx
server {
listen 80;
server_name seu-dominio.com.br;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name seu-dominio.com.br;
ssl_certificate /etc/letsencrypt/live/seu-dominio.com.br/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/seu-dominio.com.br/privkey.pem;
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
---
## 11. ESTRUTURA DE DIRETÓRIOS
```
arcadia-suite/
├── client/ # Frontend React
│ ├── src/
│ │ ├── pages/ # 67 páginas da aplicação
│ │ ├── components/
│ │ │ ├── ui/ # 55 componentes shadcn/ui
│ │ │ └── *.tsx # Componentes de negócio
│ │ ├── hooks/ # Custom hooks React
│ │ ├── contexts/ # Context providers
│ │ ├── lib/ # Utilitários
│ │ ├── modules/ # Módulos dinâmicos
│ │ ├── App.tsx # Roteamento principal
│ │ └── main.tsx # Entry point
│ └── index.html # HTML base
├── server/ # Backend Node.js
│ ├── admin/ # Administração
│ ├── retail/ # Módulo Retail (PDV)
│ ├── crm/ # CRM
│ ├── financeiro/ # Financeiro
│ ├── contabil/ # Contabilidade
│ ├── fisco/ # Fiscal
│ ├── people/ # RH / Pessoas
│ ├── quality/ # Qualidade
│ ├── erp/ # SOE (Sistema Operacional)
│ ├── bi/ # Business Intelligence
│ ├── whatsapp/ # WhatsApp multi-sessão
│ ├── chat/ # Chat interno
│ ├── manus/ # Agente IA central
│ ├── xos/ # Governança e Dev Center
│ ├── autonomous/ # Agentes autônomos (6 agentes)
│ ├── governance/ # Políticas e auditoria
│ ├── protocols/ # MCP e A2A
│ ├── python/ # 6 microserviços FastAPI
│ ├── modules/ # Módulos dinâmicos
│ ├── routes.ts # Registro central de rotas
│ ├── storage.ts # Interface de armazenamento
│ └── index.ts # Entry point do servidor
├── shared/ # Código compartilhado
│ ├── schema.ts # Schema Drizzle (331 tabelas)
│ └── schemas/ # Schemas de módulos dinâmicos
├── db/ # Configuração do banco
├── migrations/ # Arquivos de migração
├── script/ # Scripts de build
├── Dockerfile # Build de produção
├── docker-compose.yml # Orquestração de containers
├── deploy.sh # Script de deploy VPS
├── .env.example # Template de variáveis
├── package.json # Dependências Node.js
├── tsconfig.json # Configuração TypeScript
├── vite.config.ts # Configuração Vite
├── drizzle.config.ts # Configuração Drizzle
└── postcss.config.js # Configuração PostCSS
```
---
## 12. FLUXO DE BUILD E DEPLOY
```
Desenvolvimento (Replit)
├── npm run dev → tsx server/index.ts (TypeScript direto)
│ Vite HMR para frontend
├── npm run build → script/build.ts
│ ├── Vite build → client/dist/ (frontend estático)
│ └── esbuild bundle → dist/index.cjs (servidor minificado)
└── npm run start → node dist/index.cjs (produção)
Deploy (VPS Docker)
├── docker compose build → Multi-stage Dockerfile
│ ├── Stage 1 (builder) → npm install + npm run build
│ └── Stage 2 (runner) → Copia dist/ + node_modules
├── docker compose up -d → Inicia PostgreSQL + App
└── npx drizzle-kit push → Aplica schema no banco
```

View File

@ -1,485 +0,0 @@
# Especificação Técnica - Navbar do Arcádia Suite
> **Versão:** 1.0
> **Data:** 2026-04-08
> **Arquivo Fonte:** `client/src/components/Browser/BrowserFrame.tsx`
> **Status:** Implementado e em produção
---
## 1. Visão Geral
A navbar do Arcádia Suite é uma barra de navegação compacta, horizontal e fixa no topo da tela. Ela implementa um padrão de "navegação por bookmarks" onde cada item representa um módulo principal do sistema.
### 1.1 Objetivos
- Acesso rápido aos módulos principais
- Navegação intuitiva estilo "browser"
- Rastreamento de uso para analytics
- Suporte a múltiplos perfis de usuário
### 1.2 Princípios de Design
- **Minimalista:** Apenas ícone + texto curto
- **Escalável:** Scroll horizontal permite muitos itens
- **Responsiva:** Adapta-se a diferentes telas
- **Consistente:** Cores e ícones padronizados
---
## 2. Estrutura do Componente
```
BrowserFrame (Container)
└── CompactNavigationBar (Navbar principal)
├── Área de Bookmarks (scroll horizontal)
│ └── BookmarkItem[] (itens de navegação)
└── Área do Usuário (fixa à direita)
└── UserDropdown (menu do usuário)
```
### 2.1 Hierarquia de Componentes
```tsx
// BrowserFrame.tsx
interface BrowserFrameProps {
children: React.ReactNode;
}
function CompactNavigationBar() {
// Hook de navegação
const [location, setLocation] = useLocation(); // wouter
const { user, logoutMutation } = useAuth();
const { trackPageView } = useNavigationTracking();
// Handler de navegação
const navigateTo = (path: string, pageName: string) => {
trackPageView(pageName, path);
setLocation(path);
};
return (
<div className="h-10 bg-background border-b...">
{/* Bookmarks */}
{/* User Menu */}
</div>
);
}
export function BrowserFrame({ children }: BrowserFrameProps) {
return (
<div className="flex flex-col h-screen w-screen...">
<CompactNavigationBar />
<div className="flex-1...">{children}</div>
</div>
);
}
```
---
## 3. Especificação Visual
### 3.1 Dimensões e Layout
| Propriedade | Valor | Tailwind |
|-------------|-------|----------|
| Altura | 40px | `h-10` |
| Padding horizontal | 12px | `px-3` |
| Gap entre itens | 8px | `gap-2` |
| Tamanho do ícone | 16x16px | `w-4 h-4` |
| Tamanho do ícone interno | 10x10px | `w-2.5 h-2.5` |
| Padding do bookmark | 6px 8px | `px-2 py-1.5` |
| Border radius | 4px | `rounded` |
### 3.2 Cores e Estilos
```
Fundo: bg-background (varia com tema claro/escuro)
Borda inferior: border-border
Texto: text-muted-foreground
Hover: hover:bg-muted
Sombra: shadow-xs
```
### 3.3 Estados dos Bookmarks
| Estado | Estilo |
|--------|--------|
| **Default** | `text-muted-foreground` |
| **Hover** | `hover:bg-muted` + cursor pointer |
| **Ativo** | Não há estado visual de "ativo" (intencional) |
---
## 4. Itens de Navegação (Bookmarks)
### 4.1 Lista Completa de Itens
| Ordem | Nome | Rota | Ícone | Cor do Gradient | Visibilidade |
|-------|------|------|-------|-----------------|--------------|
| 1 | Administração | `/admin` | Settings | slate-700 → slate-900 | Apenas admin |
| 2 | Início | `/` | Arcádia Icon | - (imagem) | Todos |
| 3 | Agent | `/agent` | Bot | primary → blue-600 | Todos |
| 4 | Inbox | `/xos/inbox` | MessageCircle | #00a884#25D366 | Todos |
| 5 | Automações | `/automations` | Zap | #c89b3c#d4a94a | Todos |
| 6 | Insights | `/insights` | LayoutDashboard | #1f334d#2d4a6f | Todos |
| 7 | Compass | `/compass` | Compass | #c89b3c#1f334d | Todos |
| 8 | Produção | `/production` | Users | indigo-500 → indigo-700 | Todos |
| 9 | Suporte | `/support` | Ticket | rose-500 → rose-700 | Todos |
| 10 | ERP | `/erp` | Package | blue-600 → blue-800 | Todos |
| 11 | Retail | `/retail` | Store | cyan-500 → blue-600 | Todos |
| 12 | Plus | `/plus` | Layers | purple-500 → purple-700 | Todos |
| 13 | Fisco | `/fisco` | Receipt | emerald-600 → emerald-800 | Todos |
| 14 | Engenharia | `/engineering` | Compass | teal-500 → green-700 | Todos |
### 4.2 Estrutura de um Bookmark
```tsx
<div
className="flex items-center gap-1 hover:bg-muted px-2 py-1.5 rounded cursor-pointer transition-colors flex-shrink-0"
onClick={() => navigateTo("/rota", "Nome")}
data-testid="bookmark-nome"
>
{/* Container do ícone com gradiente */}
<div className="w-4 h-4 bg-gradient-to-br from-[COR1] to-[COR2] rounded-sm flex items-center justify-center">
<Icon className="w-2.5 h-2.5 text-white" />
</div>
{/* Texto (oculto em mobile) */}
<span className="hidden md:inline">Nome</span>
</div>
```
---
## 5. Menu do Usuário (Dropdown)
### 5.1 Estrutura
```
[Avatar + Nome] ▼
├── Informações do Usuário
│ ├── Nome completo
│ ├── @username
│ └── Badge "Administrador" (se aplicável)
├── Meu Perfil
├── Plataforma (apenas admin)
│ └── Centro de Desenvolvimento
└── Sair (texto vermelho)
```
### 5.2 Componentes
- **Trigger:** Botão com avatar circular + nome
- **Avatar:** Inicial do nome em círculo colorido (5x5)
- **Dropdown:** `DropdownMenu` do shadcn/ui
- **Largura:** 224px (`w-56`)
---
## 6. Comportamento e Interações
### 6.1 Fluxo de Navegação
```
┌─────────────────┐
│ Usuário clica │
│ em bookmark │
└────────┬────────┘
┌─────────────────┐
│ trackPageView │ ← Registra no backend via POST /api/learning/navigation
│ (pageName, path)│
└────────┬────────┘
┌─────────────────┐
│ setLocation │ ← wouter atualiza URL
│ (path) │
└────────┬────────┘
┌─────────────────┐
│ React.lazy() │ ← Code splitting carrega chunk
│ import() │
└────────┬────────┘
┌─────────────────┐
<Suspense> │ ← Mostra LoadingFallback
│ fallback │
└────────┬────────┘
┌─────────────────┐
│ Página carrega │ ← Componente renderizado
│ e renderiza │
└─────────────────┘
```
### 6.2 Rastreamento (Analytics)
```typescript
// use-navigation-tracking.ts
const trackPageView = (pageName: string, route?: string) => {
track({
module: pageName, // Ex: "Agent", "Fisco"
action: "page_view",
metadata: {
route, // Ex: "/agent", "/fisco"
timestamp: "2026-04-08T15:30:00Z"
},
});
};
```
**Debounce:** 2000ms para evitar spam de eventos
### 6.3 Responsividade
| Breakpoint | Comportamento |
|------------|---------------|
| **Mobile** (< 768px) | Texto dos bookmarks oculto (`hidden md:inline`), apenas ícones |
| **Desktop** (≥ 768px) | Texto visível ao lado do ícone |
### 6.4 Scroll Horizontal
- A área de bookmarks tem `overflow-x-auto`
- Scrollbar oculta (`scrollbar-hide`)
- Itens não quebram linha (`flex-shrink-0`)
---
## 7. Dependências
### 7.1 Bibliotecas
```json
{
"wouter": "^3.3.5", // Router
"lucide-react": "^0.545.0", // Ícones
"@radix-ui/react-dropdown-menu": "^2.1.16" // Dropdown
}
```
### 7.2 Hooks Customizados
| Hook | Arquivo | Propósito |
|------|---------|-----------|
| `useLocation` | `wouter` | Navegação de rotas |
| `useAuth` | `@/hooks/use-auth` | Dados do usuário e logout |
| `useNavigationTracking` | `@/hooks/use-navigation-tracking` | Analytics |
### 7.3 Componentes UI
- `Button` (shadcn/ui)
- `DropdownMenu`, `DropdownMenuContent`, `DropdownMenuItem`, etc. (shadcn/ui)
---
## 8. Test IDs (data-testid)
Usados para testes automatizados:
```
bookmark-admin
bookmark-home
bookmark-agent
bookmark-xos-inbox
bookmark-automations
bookmark-insights
bookmark-compass
bookmark-production
bookmark-support
bookmark-erp
bookmark-retail
bookmark-plus
bookmark-fisco
bookmark-engineering
button-user-menu
button-logout
menu-development
```
---
## 9. Como Modificar
### 9.1 Adicionar Novo Bookmark
```tsx
// Em CompactNavigationBar, adicionar antes do </div> da área de bookmarks:
<div
className="flex items-center gap-1 hover:bg-muted px-2 py-1.5 rounded cursor-pointer transition-colors flex-shrink-0"
onClick={() => navigateTo("/nova-rota", "Nome")}
data-testid="bookmark-novo"
>
<div className="w-4 h-4 bg-gradient-to-br from-[COR1] to-[COR2] rounded-sm flex items-center justify-center">
<NovoIcon className="w-2.5 h-2.5 text-white" />
</div>
<span className="hidden md:inline">Novo</span>
</div>
```
### 9.2 Alterar Ordem
Reorganizar os elementos `<div>` na ordem desejada dentro da área de bookmarks.
### 9.3 Condicional por Permissão
```tsx
{user?.role === "admin" && (
<div ...>...</div>
)}
// Ou por módulo permitido:
{(user as any)?.allowedModules?.includes("modulo") && (
<div ...>...</div>
)}
```
---
## 10. Integração com Sistema
### 10.1 Uso nas Páginas
Todas as páginas que usam a navbar devem ser envolvidas por `BrowserFrame`:
```tsx
// Exemplo: client/src/pages/Agent.tsx
import { BrowserFrame } from "@/components/Browser/BrowserFrame";
export default function Agent() {
return (
<BrowserFrame>
<div className="h-full...">
{/* Conteúdo da página */}
</div>
</BrowserFrame>
);
}
```
### 10.2 Páginas que NÃO usam BrowserFrame
- `AuthPage` (tela de login)
- `NotFound` (página 404)
---
## 11. Considerações Técnicas
### 11.1 Performance
- Code splitting via `React.lazy()` em todas as páginas
- Suspense com fallback de loading
- Debounce no rastreamento (2s)
### 11.2 Acessibilidade
- `cursor-pointer` em todos os bookmarks
- Estrutura semântica do dropdown
- Contraste adequado nos ícones (branco sobre gradiente)
### 11.3 Limitações Conhecidas
- Não há indicador visual de "página ativa" (design intencional)
- Scroll horizontal pode esconder itens em telas muito pequenas
- Não suporta drag-and-drop para reordenar
### 11.4 Tratamento de Erros (Resolvido)
**Problema:** Se uma página carregada via `React.lazy()` tivesse erro (import quebrado, sintaxe inválida), o `Suspense` ficava preso no estado de loading infinito.
**Solução Implementada:**
1. **ErrorBoundary** (`client/src/components/ErrorBoundary.tsx`):
- Captura erros em componentes filhos, incluindo falhas no lazy loading
- Exibe UI de fallback com detalhes do erro
- Botão "Tentar Novamente" para recarregar
2. **LoadingFallback Aprimorado** (`client/src/App.tsx`):
- Após 3s: mostra mensagem "Isso está demorando mais que o esperado..."
- Após 10s: mostra alerta amarelo com botão de recarregar
3. **Integração no Router**:
```tsx
function Router() {
return (
<ErrorBoundary> {/* ← Captura erros */}
<Suspense fallback={<LoadingFallback />}>
<Switch>
<ProtectedRoute path="/" component={Cockpit} />
{/* ... outras rotas ... */}
</Switch>
</Suspense>
</ErrorBoundary>
);
}
```
**Fluxo de Erro:**
```
Usuário clica → lazy() falha → Suspense não resolve → ErrorBoundary captura
LoadingFallback mostra
aviso após 3s/10s
ErrorBoundary renderiza
UI de erro com retry
```
---
## 12. Diagrama de Sequência
```
┌────────┐ ┌─────────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐
│ Usuário│ │ Navbar │ │ wouter │ │ React │ │ Backend │
└───┬────┘ └──────┬──────┘ └────┬─────┘ └────┬────┘ └────┬─────┘
│ │ │ │ │
│──clica─────────▶│ │ │ │
│ │ │ │ │
│ │──trackPageView────────────────────│──────────────▶│
│ │ │ │ │
│ │──setLocation───▶│ │ │
│ │ │ │ │
│ │ │──atualiza URL──│ │
│ │ │ │ │
│ │ │◀─URL changed───│ │
│ │ │ │ │
│ │ │──lazy import──▶│ │
│ │ │ │ │
│ │◀────────────────│──chunk loaded──│ │
│ │ │ │ │
│ │──renderiza nova página────────────▶│ │
│ │ │ │ │
│◀────────────────│ │ │ │
│ │ │ │ │
```
---
## 13. Changelog
| Versão | Data | Alterações |
|--------|------|------------|
| 1.0 | 2026-04-08 | Especificação inicial criada |
| 1.1 | 2026-04-08 | Adicionado tratamento de erros (ErrorBoundary + LoadingFallback com timeout) |
---
## 14. Referências
- **Arquivo Fonte:** `client/src/components/Browser/BrowserFrame.tsx`
- **Hook de Tracking:** `client/src/hooks/use-navigation-tracking.ts`
- **Rotas:** `client/src/App.tsx`
- **Autenticação:** `client/src/hooks/use-auth.tsx`
- **UI Components:** `client/src/components/ui/`
---
**Nota para Agentes:**
> Ao modificar a navbar, mantenha a consistência visual com os bookmarks existentes. Sempre adicione `data-testid` para testes e atualize esta especificação se houver mudanças significativas na estrutura ou comportamento.

Some files were not shown because too many files have changed in this diff Show More