Compare commits
1 Commits
main
...
claude/ana
| Author | SHA1 | Date |
|---|---|---|
|
|
dde0fa661a |
|
|
@ -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...
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
13
.env.example
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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.*
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "server/modules/openclaw"]
|
||||
path = server/modules/openclaw
|
||||
url = https://github.com/miaoxworld/OpenClawInstaller.git
|
||||
|
|
@ -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]
|
||||
|
Before Width: | Height: | Size: 26 KiB |
|
|
@ -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]
|
||||
|
Before Width: | Height: | Size: 23 KiB |
|
|
@ -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]
|
||||
|
|
@ -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...
|
||||
|
|
@ -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]
|
||||
|
Before Width: | Height: | Size: 23 KiB |
|
|
@ -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)
|
||||
```
|
||||
|
|
@ -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
|
||||
|
||||
28
Dockerfile
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
@ -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
|
||||
|
|
@ -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 |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -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*
|
||||
|
|
@ -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 (2–3 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 (2–3 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*
|
||||
|
|
@ -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 1–4 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 (2–3h) → 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 (3–4h) → página mais estratégica
|
||||
5. Adaptador Omie — primeira integração ERP real (4–6h)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Sessão registrada em 2026-03-18 | Branch: `claude/analyze-project-0mXjP`*
|
||||
|
Before Width: | Height: | Size: 69 B |
|
Before Width: | Height: | Size: 74 KiB |
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
/**
|
||||
* OpenClawWidget.tsx
|
||||
*
|
||||
* Widget flutuante do OpenClaw — aparece quando há 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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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],
|
||||
};
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
18
db/index.ts
|
|
@ -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 });
|
||||
|
|
|
|||
72
deploy.sh
|
|
@ -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 ""
|
||||
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.9 MiB |
|
Before Width: | Height: | Size: 4.1 MiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
|
@ -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!"
|
||||
|
|
|
|||
|
|
@ -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 ────────────────────
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
203
docs/AGENTS.md
|
|
@ -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>
|
||||
```
|
||||
|
|
@ -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.*
|
||||
|
|
@ -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.
|
||||
|
|
@ -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`.
|
||||
|
|
@ -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*
|
||||
|
|
@ -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?**
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
|
@ -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 │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
|
@ -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_*) │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
|
@ -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)**
|
||||
|
|
@ -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` (L3367–3517) | `customers`, `suppliers`, `products`, `sales_orders`, `sales_order_items`, `purchase_orders`, `purchase_order_items` |
|
||||
| `shared/schema.ts` (L5082–5617) | 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, L3303–3517, L5082–5617, 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 │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
|
@ -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 │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
|
@ -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/)
|
||||
|
|
@ -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*
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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.
|
||||