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
|
BI_PYTHON_URL=http://localhost:8004
|
||||||
AUTOMATION_PYTHON_URL=http://localhost:8005
|
AUTOMATION_PYTHON_URL=http://localhost:8005
|
||||||
FISCO_PYTHON_URL=http://localhost:8002
|
FISCO_PYTHON_URL=http://localhost:8002
|
||||||
# ── Embeddings / Learning Service ──────────────────────────────────────────────
|
PYTHON_SERVICE_URL=http://localhost:8001
|
||||||
# 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
|
|
||||||
|
|
||||||
# ── IA — OpenAI ───────────────────────────────────────────────────────────────
|
# ── IA — OpenAI ───────────────────────────────────────────────────────────────
|
||||||
# Deixe vazio se usar apenas Ollama (soberania total)
|
# 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
|
# Se Ollama está em outro servidor: OLLAMA_BASE_URL=http://IP_DO_SERVIDOR:11434
|
||||||
OLLAMA_BASE_URL=http://localhost: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)
|
# ── 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=
|
OPENAI_API_KEY=
|
||||||
ANTHROPIC_API_KEY=
|
ANTHROPIC_API_KEY=
|
||||||
GROQ_API_KEY=
|
GROQ_API_KEY=
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.production
|
||||||
|
|
||||||
# Replit specific
|
# Replit specific
|
||||||
.cache/
|
.cache/
|
||||||
|
|
@ -61,12 +64,3 @@ attached_assets/
|
||||||
# Package lock (regenerated on install)
|
# Package lock (regenerated on install)
|
||||||
package-lock.json
|
package-lock.json
|
||||||
.claude/
|
.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
|
client/ # 66 páginas React
|
||||||
shared/schema.ts # Schema do banco (7317 linhas, Drizzle ORM)
|
shared/schema.ts # Schema do banco (7317 linhas, Drizzle ORM)
|
||||||
docker/
|
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
|
## Arquitetura de IA
|
||||||
|
|
@ -29,8 +29,9 @@ Manus / Agents / Embeddings
|
||||||
│ AI_INTEGRATIONS_OPENAI_BASE_URL
|
│ AI_INTEGRATIONS_OPENAI_BASE_URL
|
||||||
▼
|
▼
|
||||||
LiteLLM :4000 (gateway unificado, loga tudo no banco)
|
LiteLLM :4000 (gateway unificado, loga tudo no banco)
|
||||||
├──► Ollama :11434 (TIER 1 — local, padrão)
|
├──► LLMFit (TIER 1 — fine-tuned, soberano) [slot pronto, comentado]
|
||||||
└──► OpenAI/Anthropic/Groq (TIER 2 — opt-in, só se API key configurada)
|
├──► Ollama :11434 (TIER 2 — local, padrão)
|
||||||
|
└──► OpenAI/Anthropic/Groq (TIER 3 — opt-in, só se API key configurada)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Variáveis chave do Manus:**
|
**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
|
- `PLANO_EVOLUCAO_ARCADIA.md` — roadmap e evolução
|
||||||
- `MAPA_SISTEMA_ARCADIA.md` — mapa do sistema
|
- `MAPA_SISTEMA_ARCADIA.md` — mapa do sistema
|
||||||
|
|
||||||
## Git e Deploy
|
## Branch de desenvolvimento
|
||||||
|
Sempre commitar em: `claude/analyze-project-0mXjP`
|
||||||
### Remote canônico
|
Push: `git push -u origin claude/analyze-project-0mXjP`
|
||||||
- **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
|
|
||||||
```
|
|
||||||
|
|
||||||
## O que está implementado
|
## O que está implementado
|
||||||
- ✅ Manus (agente autônomo, 30+ ferramentas)
|
- ✅ 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
|
- ✅ Docker dev + prod, LiteLLM gateway
|
||||||
|
|
||||||
## O que ainda falta
|
## O que ainda falta
|
||||||
|
- ❌ LLMFit: slot pronto em `litellm-config.yaml`, só habilitar quando disponível
|
||||||
- ❌ Testes automatizados / CI-CD
|
- ❌ Testes automatizados / CI-CD
|
||||||
- ❌ Monitoramento (APM, Sentry, métricas)
|
- ❌ Monitoramento (APM, Sentry, métricas)
|
||||||
- ❌ Multi-tenancy completo
|
- ❌ Multi-tenancy completo
|
||||||
- ❌ Rate limiting em todos os endpoints (parcial)
|
- ❌ Rate limiting em todos os endpoints (parcial)
|
||||||
- ❌ Fine-tuning de modelos locais (dados do Arcádia)
|
|
||||||
|
|
||||||
## Comandos úteis
|
## Comandos úteis
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -121,6 +85,7 @@ npm run build
|
||||||
```
|
```
|
||||||
SESSION_SECRET, SSO_SECRET # gerar strings seguras em prod
|
SESSION_SECRET, SSO_SECRET # gerar strings seguras em prod
|
||||||
AI_INTEGRATIONS_OPENAI_BASE_URL # aponta para LiteLLM
|
AI_INTEGRATIONS_OPENAI_BASE_URL # aponta para LiteLLM
|
||||||
|
LLMFIT_BASE_URL # LLMFit quando disponível
|
||||||
OLLAMA_BASE_URL # Ollama host ou container
|
OLLAMA_BASE_URL # Ollama host ou container
|
||||||
OPENAI_API_KEY # opcional (soberania: deixar vazio)
|
OPENAI_API_KEY # opcional (soberania: deixar vazio)
|
||||||
```
|
```
|
||||||
|
|
@ -122,6 +122,7 @@ AUTOMATION_PYTHON_URL=http://automation:8005
|
||||||
OPENAI_API_KEY=
|
OPENAI_API_KEY=
|
||||||
ANTHROPIC_API_KEY=
|
ANTHROPIC_API_KEY=
|
||||||
GROQ_API_KEY=
|
GROQ_API_KEY=
|
||||||
|
LLMFIT_BASE_URL= # habilitar quando LLMFit estiver disponível
|
||||||
```
|
```
|
||||||
|
|
||||||
### Superset — profile `bi`
|
### Superset — profile `bi`
|
||||||
|
|
@ -255,11 +256,12 @@ Configure backups automáticos no Coolify em **Project → Backups**.
|
||||||
|
|
||||||
## Stack de IA (LiteLLM + Ollama)
|
## 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 1 — LLMFit (fine-tuned, soberano) → slot pronto, habilitar via LLMFIT_BASE_URL
|
||||||
TIER 2 — Externos (opt-in) → OpenAI, Anthropic, Groq (apenas se API key definida)
|
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
|
### 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
|
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
|
## 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
|
## Checklist Final
|
||||||
|
|
||||||
### Antes do deploy
|
### 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`
|
- [ ] `SESSION_SECRET` e `SSO_SECRET` gerados com `openssl rand -hex 32`
|
||||||
- [ ] Senhas do banco definidas (nunca reutilizar senhas de dev)
|
- [ ] Senhas do banco definidas (nunca reutilizar senhas de dev)
|
||||||
- [ ] `DOCKER_MODE=true` definido
|
- [ ] `DOCKER_MODE=true` definido
|
||||||
- [ ] `.dockerignore` revisado (especialmente após adicionar novos diretórios grandes)
|
|
||||||
|
|
||||||
### No Coolify
|
### No Coolify
|
||||||
|
|
||||||
28
Dockerfile
|
|
@ -1,33 +1,33 @@
|
||||||
|
# ─── Stage 1: Build ───────────────────────────────────────────────────────────
|
||||||
FROM node:20-alpine AS builder
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json package-lock.json* ./
|
COPY package*.json ./
|
||||||
RUN NODE_ENV=development npm install --legacy-peer-deps
|
RUN npm ci --ignore-scripts
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
# ─── Stage 2: Production ──────────────────────────────────────────────────────
|
||||||
FROM node:20-alpine AS runner
|
FROM node:20-alpine AS runner
|
||||||
|
|
||||||
RUN apk add --no-cache p7zip
|
|
||||||
|
|
||||||
WORKDIR /app
|
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 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
|
EXPOSE 5000
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
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"]
|
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
|
LiteLLM (porta 4000) — gateway único
|
||||||
│
|
│
|
||||||
├──► Ollama (modelos open source locais) [TIER 1 — padrão]
|
├──► LLMFit (seus modelos fine-tuned) [TIER 1 — prioridade]
|
||||||
└──► OpenAI/Anthropic/Groq (opt-in) [TIER 2 — externo]
|
└──► 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.
|
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
|
## Variáveis de ambiente — resumo completo
|
||||||
|
|
||||||
Configure todas no Coolify antes do deploy:
|
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 em container: http://ollama:11434
|
||||||
OLLAMA_BASE_URL=http://host-gateway: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) ───────────────────
|
# ── Providers externos (deixar vazio para soberania total) ───────────────────
|
||||||
OPENAI_API_KEY=
|
OPENAI_API_KEY=
|
||||||
ANTHROPIC_API_KEY=
|
ANTHROPIC_API_KEY=
|
||||||
|
|
@ -139,7 +202,7 @@ curl http://localhost:4000/v1/chat/completions \
|
||||||
### 2. Testar Manus via interface
|
### 2. Testar Manus via interface
|
||||||
|
|
||||||
Acesse `https://seudominio.com.br` → abra o Manus → envie uma mensagem simples.
|
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
|
### 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.
|
→ O modelo referenciado em `litellm-config.yaml` não foi baixado no Ollama.
|
||||||
→ Execute `ollama pull NOME_DO_MODELO`
|
→ 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**
|
**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)
|
→ 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
|
→ Ou use o IP real da interface de rede: `ip addr show` para descobrir
|
||||||
|
|
@ -46,27 +46,15 @@
|
||||||
|
|
||||||
## Mapa de Portas
|
## Mapa de Portas
|
||||||
|
|
||||||
| Porta | Serviço | Tecnologia | Observação |
|
| Porta | Serviço | Tecnologia |
|
||||||
|-------|---------|-----------|------------|
|
|-------|---------|-----------|
|
||||||
| 5000 | API Principal + Frontend | Express.js + React | Exposta publicamente |
|
| 5000 | API Principal + Frontend | Express.js + React |
|
||||||
| 5001 | **Arcadia Kernel** | Node.js/Express | Dashboard interno (mesmo container) |
|
| 8002 | Motor Fiscal (Fisco) | FastAPI (Python) |
|
||||||
| 8002 | Motor Fiscal (Fisco) | FastAPI (Python) | Via Docker network |
|
| 8003 | Motor Contábil | FastAPI (Python) |
|
||||||
| 8003 | Motor Contábil | FastAPI (Python) | Via Docker network |
|
| 8004 | Motor BI (Insights) | FastAPI (Python) |
|
||||||
| 8004 | Motor BI (Insights) | FastAPI (Python) | Via Docker network |
|
| 8005 | Motor Automação | FastAPI (Python) |
|
||||||
| 8005 | Motor Automação | FastAPI (Python) | Via Docker network |
|
| 8006 | Motor Comunicação | Node.js/Express |
|
||||||
| 8006 | Motor Comunicação | Node.js/Express | Via Docker network |
|
| 8080 | Arcádia Plus (Laravel) | PHP/Laravel |
|
||||||
| 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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -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 { Switch, Route, useLocation } from "wouter";
|
||||||
import { lazy, Suspense, useEffect, useState } from "react";
|
import { lazy, Suspense, useEffect } from "react";
|
||||||
import { queryClient } from "./lib/queryClient";
|
import { queryClient } from "./lib/queryClient";
|
||||||
import { QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
|
|
@ -9,8 +9,6 @@ import { SoeMotorProvider } from "@/contexts/SoeMotorContext";
|
||||||
import { ProtectedRoute } from "@/lib/protected-route";
|
import { ProtectedRoute } from "@/lib/protected-route";
|
||||||
import { CommandPalette } from "@/components/CommandPalette";
|
import { CommandPalette } from "@/components/CommandPalette";
|
||||||
import { KnowledgeCollectorInit } from "@/components/KnowledgeCollectorInit";
|
import { KnowledgeCollectorInit } from "@/components/KnowledgeCollectorInit";
|
||||||
import { OpenClawWidget } from "@/components/openclaw/OpenClawWidget";
|
|
||||||
import { ErrorBoundary } from "@/components/ErrorBoundary";
|
|
||||||
import NotFound from "@/pages/not-found";
|
import NotFound from "@/pages/not-found";
|
||||||
import AuthPage from "@/pages/auth-page";
|
import AuthPage from "@/pages/auth-page";
|
||||||
|
|
||||||
|
|
@ -73,56 +71,13 @@ const XosSupervisor = lazy(() => import("@/pages/XosSupervisor"));
|
||||||
const XosReports = lazy(() => import("@/pages/XosReports"));
|
const XosReports = lazy(() => import("@/pages/XosReports"));
|
||||||
const XosProtocols = lazy(() => import("@/pages/XosProtocols"));
|
const XosProtocols = lazy(() => import("@/pages/XosProtocols"));
|
||||||
|
|
||||||
|
|
||||||
function LoadingFallback() {
|
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 (
|
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={{ textAlign: "center" }}>
|
||||||
<div style={{ width: 40, height: 40, border: "3px solid #e5e7eb", borderTopColor: "#3b82f6", borderRadius: "50%", animation: "spin 1s linear infinite", margin: "0 auto" }} />
|
<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>
|
<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>
|
</div>
|
||||||
<style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
|
<style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -131,7 +86,6 @@ function LoadingFallback() {
|
||||||
|
|
||||||
function Router() {
|
function Router() {
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
|
||||||
<Suspense fallback={<LoadingFallback />}>
|
<Suspense fallback={<LoadingFallback />}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<ProtectedRoute path="/" component={Cockpit} />
|
<ProtectedRoute path="/" component={Cockpit} />
|
||||||
|
|
@ -198,7 +152,6 @@ function Router() {
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,7 +164,6 @@ function App() {
|
||||||
<KnowledgeCollectorInit />
|
<KnowledgeCollectorInit />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<CommandPalette />
|
<CommandPalette />
|
||||||
<OpenClawWidget />
|
|
||||||
<Router />
|
<Router />
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</SoeMotorProvider>
|
</SoeMotorProvider>
|
||||||
|
|
|
||||||
|
|
@ -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";
|
} from "lucide-react";
|
||||||
import { CardDescription } from "@/components/ui/card";
|
import { CardDescription } from "@/components/ui/card";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
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 { ResultViewer } from "@/components/ResultViewer";
|
||||||
import DevHistory from "@/components/DevHistory";
|
import DevHistory from "@/components/DevHistory";
|
||||||
import { ChartRenderer } from "@/components/ChartRenderer";
|
|
||||||
import { exportToPDF, exportToWord, exportToText, exportToCSV } from "@/lib/export-utils";
|
|
||||||
|
|
||||||
interface ChartData {
|
interface ChartData {
|
||||||
type: 'bar' | 'line' | 'pie' | 'area';
|
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";
|
} from "lucide-react";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { SupersetDashboard } from "@/components/SupersetDashboard";
|
import { SupersetDashboard } from "@/components/SupersetDashboard";
|
||||||
import { MiroFlowControl } from "@/components/MiroFlowControl";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
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">
|
<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
|
<Layers className="w-4 h-4 mr-2" /> Staging
|
||||||
</TabsTrigger>
|
</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">
|
<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
|
<Settings className="w-4 h-4 mr-2" /> Insights
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
|
@ -2987,12 +2983,8 @@ export default function BiWorkspace() {
|
||||||
<TabsContent value="staging" className="mt-0">
|
<TabsContent value="staging" className="mt-0">
|
||||||
<StagingTab />
|
<StagingTab />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="cientifico" className="mt-0">
|
|
||||||
<MiroFlowControl />
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="advanced" className="mt-0">
|
<TabsContent value="advanced" className="mt-0">
|
||||||
<SupersetAdvancedTab />
|
<SupersetAdvancedTab />
|
||||||
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</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
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
Plus, Settings, Play, Zap, Layers, Puzzle, Palette,
|
Plus, Settings, Play, Zap, Layers, Puzzle, Palette,
|
||||||
Terminal, Box, Workflow, PanelLeft, Table2, FormInput,
|
Terminal, Box, Workflow, PanelLeft, Table2, FormInput,
|
||||||
ListChecks, PieChart, LineChart, Gauge, Bot, Code, Trash2, Save,
|
ListChecks, PieChart, LineChart, Gauge, Bot, Code, Trash2, Save,
|
||||||
Sparkles, Rocket, PenTool, Brain
|
Sparkles, Rocket
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
|
|
||||||
|
|
@ -25,12 +25,8 @@ import PageBuilder from "./PageBuilder";
|
||||||
import WorkflowBuilder from "./WorkflowBuilder";
|
import WorkflowBuilder from "./WorkflowBuilder";
|
||||||
import IDE from "./IDE";
|
import IDE from "./IDE";
|
||||||
import DevAgent from "@/components/lowcode/DevAgent";
|
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 {
|
interface Dashboard {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -263,42 +259,6 @@ export default function DevelopmentModule() {
|
||||||
count: 0,
|
count: 0,
|
||||||
category: "dev"
|
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();
|
const [, navigate] = useLocation();
|
||||||
|
|
@ -711,8 +671,8 @@ export default function DevelopmentModule() {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen flex bg-gray-50">
|
<div className="h-full 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="w-16 bg-gray-900 flex flex-col items-center py-4 gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTool("home")}
|
onClick={() => setActiveTool("home")}
|
||||||
className={`p-3 rounded-lg transition-colors ${activeTool === "home" ? "bg-blue-600" : "hover:bg-gray-700"}`}
|
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>
|
||||||
|
|
||||||
<div className="flex-1 h-full overflow-hidden">
|
<div className="flex-1 overflow-hidden">
|
||||||
{activeTool === "home" && <div className="h-full overflow-y-auto">{renderHome()}</div>}
|
{activeTool === "home" && renderHome()}
|
||||||
{activeTool === "doctypes" && <DocTypeBuilder />}
|
{activeTool === "doctypes" && <DocTypeBuilder />}
|
||||||
{activeTool === "pages" && <PageBuilder />}
|
{activeTool === "pages" && <PageBuilder />}
|
||||||
{activeTool === "workflows" && <WorkflowBuilder />}
|
{activeTool === "workflows" && <WorkflowBuilder />}
|
||||||
|
|
@ -745,10 +705,6 @@ export default function DevelopmentModule() {
|
||||||
{activeTool === "scripts" && renderScriptEditor()}
|
{activeTool === "scripts" && renderScriptEditor()}
|
||||||
{activeTool === "ide" && <IDE />}
|
{activeTool === "ide" && <IDE />}
|
||||||
{activeTool === "agent" && <DevAgent />}
|
{activeTool === "agent" && <DevAgent />}
|
||||||
{activeTool === "openclaw" && <OpenClawPanel />}
|
|
||||||
{activeTool === "automationcenter" && <AutomationCenter />}
|
|
||||||
{activeTool === "designstudio" && <DevCenter />}
|
|
||||||
{activeTool === "miroflow" && <MiroFlowControl />}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog open={showNewDashboardDialog} onOpenChange={setShowNewDashboardDialog}>
|
<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 { BrowserFrame } from "@/components/Browser/BrowserFrame";
|
||||||
import { MiroFlowControl } from "@/components/MiroFlowControl";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
@ -201,10 +200,6 @@ export default function Scientist() {
|
||||||
<Lightbulb className="w-4 h-4 mr-2" />
|
<Lightbulb className="w-4 h-4 mr-2" />
|
||||||
Sugestões
|
Sugestões
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="scientific" className="data-[state=active]:bg-cyan-500">
|
|
||||||
<Brain className="w-4 h-4 mr-2" />
|
|
||||||
Científico
|
|
||||||
</TabsTrigger>
|
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="knowledge" className="space-y-4">
|
<TabsContent value="knowledge" className="space-y-4">
|
||||||
|
|
@ -832,10 +827,6 @@ export default function Scientist() {
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="scientific" className="space-y-4">
|
|
||||||
<MiroFlowControl />
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Link } from "wouter";
|
import { Link } from "wouter";
|
||||||
import { BrowserFrame } from "@/components/Browser/BrowserFrame";
|
import { BrowserFrame } from "@/components/Browser/BrowserFrame";
|
||||||
import {
|
import {
|
||||||
|
|
@ -14,9 +14,6 @@ import { Input } from "@/components/ui/input";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
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 {
|
interface XosStats {
|
||||||
total_contacts: number;
|
total_contacts: number;
|
||||||
|
|
@ -70,48 +67,6 @@ interface Activity {
|
||||||
|
|
||||||
export default function XosCentral() {
|
export default function XosCentral() {
|
||||||
const [activeTab, setActiveTab] = useState("dashboard");
|
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>({
|
const { data: stats } = useQuery<XosStats>({
|
||||||
queryKey: ["/api/xos/stats"],
|
queryKey: ["/api/xos/stats"],
|
||||||
|
|
@ -226,9 +181,9 @@ export default function XosCentral() {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button data-testid="button-new-contact" onClick={() => setIsNewContactOpen(true)}>
|
<Button data-testid="button-new-contact">
|
||||||
<PlusCircle className="h-4 w-4 mr-2" />
|
<PlusCircle className="h-4 w-4 mr-2" />
|
||||||
Novo Contato
|
Novo
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -455,7 +410,7 @@ export default function XosCentral() {
|
||||||
<Filter className="h-4 w-4 mr-2" />
|
<Filter className="h-4 w-4 mr-2" />
|
||||||
Filtros
|
Filtros
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" onClick={() => setIsNewContactOpen(true)}>
|
<Button size="sm">
|
||||||
<PlusCircle className="h-4 w-4 mr-2" />
|
<PlusCircle className="h-4 w-4 mr-2" />
|
||||||
Novo Contato
|
Novo Contato
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -527,7 +482,7 @@ export default function XosCentral() {
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle>Atividades</CardTitle>
|
<CardTitle>Atividades</CardTitle>
|
||||||
<Button size="sm" onClick={() => setIsNewActivityOpen(true)}>
|
<Button size="sm">
|
||||||
<PlusCircle className="h-4 w-4 mr-2" />
|
<PlusCircle className="h-4 w-4 mr-2" />
|
||||||
Nova Atividade
|
Nova Atividade
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -569,129 +524,6 @@ export default function XosCentral() {
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</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>
|
</BrowserFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
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";
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
||||||
export default function AuthPage() {
|
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
|
const client = new pg.Client({
|
||||||
// e evitar queries travadas quando conexão falha
|
|
||||||
const pool = new pg.Pool({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
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
|
client.connect();
|
||||||
pool.on('error', (err) => {
|
|
||||||
console.error('[DB Pool] Erro inesperado:', err.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Exportar pool para uso no session store
|
export const db = drizzle({ client, schema });
|
||||||
export { pool };
|
|
||||||
|
|
||||||
export const db = drizzle({ client: pool, 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
|
retries: 10
|
||||||
networks:
|
networks:
|
||||||
- arcadia-internal
|
- arcadia-internal
|
||||||
- coolify
|
|
||||||
|
|
||||||
# ── Redis ────────────────────────────────────────────────────────────────────
|
# ── Redis ────────────────────────────────────────────────────────────────────
|
||||||
redis:
|
redis:
|
||||||
|
|
@ -36,7 +35,6 @@ services:
|
||||||
- redis_data:/data
|
- redis_data:/data
|
||||||
networks:
|
networks:
|
||||||
- arcadia-internal
|
- arcadia-internal
|
||||||
- coolify
|
|
||||||
|
|
||||||
# ── App principal ─────────────────────────────────────────────────────────
|
# ── App principal ─────────────────────────────────────────────────────────
|
||||||
app:
|
app:
|
||||||
|
|
@ -69,9 +67,6 @@ services:
|
||||||
PLUS_PORT: "8080"
|
PLUS_PORT: "8080"
|
||||||
PLUS_AUTO_START: "false" # container separado em prod
|
PLUS_AUTO_START: "false" # container separado em prod
|
||||||
SSO_PLUS_BASE_URL: http://plus:8080
|
SSO_PLUS_BASE_URL: http://plus:8080
|
||||||
# ── MiroFlow ──────────────────────────────────────────────────────────
|
|
||||||
MIROFLOW_HOST: miroflow
|
|
||||||
MIROFLOW_PORT: "8006"
|
|
||||||
# ── Apache Superset ───────────────────────────────────────────────────
|
# ── Apache Superset ───────────────────────────────────────────────────
|
||||||
SUPERSET_HOST: superset
|
SUPERSET_HOST: superset
|
||||||
SUPERSET_ADMIN_USER: ${SUPERSET_ADMIN_USER:-admin}
|
SUPERSET_ADMIN_USER: ${SUPERSET_ADMIN_USER:-admin}
|
||||||
|
|
@ -91,15 +86,12 @@ services:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
networks:
|
networks:
|
||||||
- arcadia-internal
|
- arcadia-internal
|
||||||
- coolify
|
- arcadia-public
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.docker.network=coolify"
|
|
||||||
- "traefik.http.routers.arcadia.entrypoints=https"
|
|
||||||
- "traefik.http.routers.arcadia.rule=Host(`${DOMAIN}`)"
|
- "traefik.http.routers.arcadia.rule=Host(`${DOMAIN}`)"
|
||||||
- "traefik.http.routers.arcadia.tls=true"
|
- "traefik.http.routers.arcadia.tls=true"
|
||||||
- "traefik.http.routers.arcadia.tls.certresolver=letsencrypt"
|
- "traefik.http.routers.arcadia.tls.certresolver=letsencrypt"
|
||||||
- "traefik.http.services.arcadia.loadbalancer.server.port=5000"
|
|
||||||
|
|
||||||
# ── Arcádia Plus (Laravel + MySQL) ──────────────────────────────────────────
|
# ── Arcádia Plus (Laravel + MySQL) ──────────────────────────────────────────
|
||||||
# Perfil `plus` — suba com: docker compose --profile plus up
|
# Perfil `plus` — suba com: docker compose --profile plus up
|
||||||
|
|
@ -147,6 +139,38 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- arcadia-internal
|
- 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) ───────────────────────────────────────────────
|
# ── ERPNext (Frappe Framework) ───────────────────────────────────────────────
|
||||||
# Perfil `erpnext` — suba com: docker compose --profile erpnext up
|
# Perfil `erpnext` — suba com: docker compose --profile erpnext up
|
||||||
erpnext-db:
|
erpnext-db:
|
||||||
|
|
@ -273,9 +297,6 @@ services:
|
||||||
SERVICE_NAME: embeddings
|
SERVICE_NAME: embeddings
|
||||||
SERVICE_PORT: 8001
|
SERVICE_PORT: 8001
|
||||||
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
|
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:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
@ -283,7 +304,7 @@ services:
|
||||||
- arcadia-internal
|
- arcadia-internal
|
||||||
|
|
||||||
# ── LiteLLM (gateway unificado de LLM — soberania dos dados) ─────────────────
|
# ── LiteLLM (gateway unificado de LLM — soberania dos dados) ─────────────────
|
||||||
# Roteia: Ollama (local) → externo (opt-in)
|
# Roteia: LLMFit (fine-tuned) → Ollama (local) → externo (opt-in)
|
||||||
litellm:
|
litellm:
|
||||||
image: ghcr.io/berriai/litellm:main-latest
|
image: ghcr.io/berriai/litellm:main-latest
|
||||||
restart: always
|
restart: always
|
||||||
|
|
@ -293,10 +314,12 @@ services:
|
||||||
environment:
|
environment:
|
||||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||||
LITELLM_MASTER_KEY: ${LITELLM_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
|
# Ollama: se instalado no host use http://host-gateway:11434
|
||||||
# Se usar container Docker, mantém http://ollama:11434
|
# Se usar container Docker, mantém http://ollama:11434
|
||||||
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-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)
|
# Providers externos opcionais (soberania: só habilitados se configurados)
|
||||||
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
||||||
GROQ_API_KEY: ${GROQ_API_KEY:-}
|
GROQ_API_KEY: ${GROQ_API_KEY:-}
|
||||||
|
|
@ -305,7 +328,6 @@ services:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
networks:
|
networks:
|
||||||
- arcadia-internal
|
- arcadia-internal
|
||||||
- coolify
|
|
||||||
|
|
||||||
# ── Ollama (LLMs locais — soberania total) ────────────────────────────────────
|
# ── Ollama (LLMs locais — soberania total) ────────────────────────────────────
|
||||||
# OPÇÃO A (padrão): Ollama como container Docker
|
# OPÇÃO A (padrão): Ollama como container Docker
|
||||||
|
|
@ -318,8 +340,10 @@ services:
|
||||||
- ollama_models:/root/.ollama
|
- ollama_models:/root/.ollama
|
||||||
networks:
|
networks:
|
||||||
- arcadia-internal
|
- 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:
|
open-webui:
|
||||||
image: ghcr.io/open-webui/open-webui:main
|
image: ghcr.io/open-webui/open-webui:main
|
||||||
restart: always
|
restart: always
|
||||||
|
|
@ -335,7 +359,7 @@ services:
|
||||||
- litellm
|
- litellm
|
||||||
networks:
|
networks:
|
||||||
- arcadia-internal
|
- arcadia-internal
|
||||||
- coolify
|
- arcadia-public
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.webui.rule=Host(`ai.${DOMAIN}`)"
|
- "traefik.http.routers.webui.rule=Host(`ai.${DOMAIN}`)"
|
||||||
|
|
@ -344,85 +368,17 @@ services:
|
||||||
- "traefik.http.services.webui.loadbalancer.server.port=8080"
|
- "traefik.http.services.webui.loadbalancer.server.port=8080"
|
||||||
profiles: [ai]
|
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:
|
networks:
|
||||||
arcadia-internal:
|
arcadia-internal:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
coolify:
|
arcadia-public:
|
||||||
external: true
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
redis_data:
|
redis_data:
|
||||||
ollama_models:
|
ollama_models:
|
||||||
open_webui_data:
|
open_webui_data:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
plus_db:
|
plus_db:
|
||||||
plus_storage:
|
plus_storage:
|
||||||
superset_home:
|
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:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
|
|
@ -59,23 +61,12 @@ services:
|
||||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||||
LITELLM_BASE_URL: http://litellm:4000
|
LITELLM_BASE_URL: http://litellm:4000
|
||||||
LITELLM_API_KEY: ${LITELLM_API_KEY:-arcadia-internal}
|
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:
|
ports:
|
||||||
- "5000:5000"
|
- "5000:5000"
|
||||||
- "5001:5001" # Kernel Arcadia (Service Discovery)
|
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
- /app/node_modules
|
- /app/node_modules
|
||||||
- /app/dist
|
- /app/dist
|
||||||
networks:
|
|
||||||
- arcadia
|
|
||||||
- coolify
|
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
@ -92,16 +83,6 @@ services:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.python
|
dockerfile: Dockerfile.python
|
||||||
restart: unless-stopped
|
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:
|
environment:
|
||||||
SERVICE_NAME: contabil
|
SERVICE_NAME: contabil
|
||||||
SERVICE_PORT: 8003
|
SERVICE_PORT: 8003
|
||||||
|
|
@ -119,17 +100,6 @@ services:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.python
|
dockerfile: Dockerfile.python
|
||||||
restart: unless-stopped
|
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:
|
environment:
|
||||||
SERVICE_NAME: bi
|
SERVICE_NAME: bi
|
||||||
SERVICE_PORT: 8004
|
SERVICE_PORT: 8004
|
||||||
|
|
@ -147,17 +117,6 @@ services:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.python
|
dockerfile: Dockerfile.python
|
||||||
restart: unless-stopped
|
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:
|
environment:
|
||||||
SERVICE_NAME: automation
|
SERVICE_NAME: automation
|
||||||
SERVICE_PORT: 8005
|
SERVICE_PORT: 8005
|
||||||
|
|
@ -175,17 +134,6 @@ services:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.python
|
dockerfile: Dockerfile.python
|
||||||
restart: unless-stopped
|
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:
|
environment:
|
||||||
SERVICE_NAME: fisco
|
SERVICE_NAME: fisco
|
||||||
SERVICE_PORT: 8002
|
SERVICE_PORT: 8002
|
||||||
|
|
@ -203,17 +151,6 @@ services:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.python
|
dockerfile: Dockerfile.python
|
||||||
restart: unless-stopped
|
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:
|
environment:
|
||||||
SERVICE_NAME: embeddings
|
SERVICE_NAME: embeddings
|
||||||
SERVICE_PORT: 8001
|
SERVICE_PORT: 8001
|
||||||
|
|
@ -224,106 +161,10 @@ services:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
# ── MetaSet BI (Apache Superset Fork) ────────────────────────────────────────
|
# ── Apache Superset (BI avançado) ────────────────────────────────────────────
|
||||||
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) ─────────────────────
|
|
||||||
superset:
|
superset:
|
||||||
image: apache/superset:4.1.0
|
image: apache/superset:4.1.0
|
||||||
restart: unless-stopped
|
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:
|
environment:
|
||||||
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY:-superset-secret-change-in-prod}
|
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY:-superset-secret-change-in-prod}
|
||||||
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD:-arcadia123}@db:5432/arcadia_superset
|
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/superset_config.py:/app/pythonpath/superset_config.py:ro
|
||||||
- ./docker/superset/init.sh:/app/docker/init.sh:ro
|
- ./docker/superset/init.sh:/app/docker/init.sh:ro
|
||||||
- superset_home:/app/superset_home
|
- superset_home:/app/superset_home
|
||||||
extra_hosts:
|
|
||||||
- "db:10.0.10.2"
|
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
@ -354,15 +193,34 @@ services:
|
||||||
start_period: 60s
|
start_period: 60s
|
||||||
networks:
|
networks:
|
||||||
- arcadia
|
- arcadia
|
||||||
|
profiles: [bi]
|
||||||
|
|
||||||
# ─────────────── PERFIL: ai (Soberania de IA) ────────────────────────────────
|
# ─────────────── 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 (interface para devs/consultores) ─────────────────────────────
|
||||||
open-webui:
|
open-webui:
|
||||||
image: ghcr.io/open-webui/open-webui:main
|
image: ghcr.io/open-webui/open-webui:main
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
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}
|
WEBUI_SECRET_KEY: ${WEBUI_SECRET_KEY:-webui-secret-change-in-prod}
|
||||||
DEFAULT_MODELS: llama3.3,qwen2.5-coder
|
DEFAULT_MODELS: llama3.3,qwen2.5-coder
|
||||||
ENABLE_RAG_WEB_SEARCH: "true"
|
ENABLE_RAG_WEB_SEARCH: "true"
|
||||||
|
|
@ -370,6 +228,8 @@ services:
|
||||||
- "3001:8080"
|
- "3001:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- open_webui_data:/app/backend/data
|
- open_webui_data:/app/backend/data
|
||||||
|
depends_on:
|
||||||
|
- ollama
|
||||||
profiles: [ai]
|
profiles: [ai]
|
||||||
|
|
||||||
# ── LiteLLM (proxy unificado de LLMs) ───────────────────────────────────────
|
# ── LiteLLM (proxy unificado de LLMs) ───────────────────────────────────────
|
||||||
|
|
@ -381,7 +241,7 @@ services:
|
||||||
command: ["--config", "/app/config.yaml", "--port", "4000", "--detailed_debug"]
|
command: ["--config", "/app/config.yaml", "--port", "4000", "--detailed_debug"]
|
||||||
environment:
|
environment:
|
||||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
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}
|
LITELLM_MASTER_KEY: ${LITELLM_API_KEY:-arcadia-internal}
|
||||||
ports:
|
ports:
|
||||||
- "4000:4000"
|
- "4000:4000"
|
||||||
|
|
@ -413,30 +273,6 @@ services:
|
||||||
retries: 10
|
retries: 10
|
||||||
start_period: 30s
|
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 (Frappe Framework + ERPNext app) ─────────────────────────────────
|
||||||
erpnext:
|
erpnext:
|
||||||
image: frappe/erpnext:version-15
|
image: frappe/erpnext:version-15
|
||||||
|
|
@ -466,18 +302,13 @@ services:
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
ollama_models:
|
||||||
open_webui_data:
|
open_webui_data:
|
||||||
superset_home:
|
superset_home:
|
||||||
metaset_home:
|
|
||||||
erpnext_db:
|
erpnext_db:
|
||||||
erpnext_sites:
|
erpnext_sites:
|
||||||
erpnext_logs:
|
erpnext_logs:
|
||||||
neo4j_data:
|
|
||||||
neo4j_logs:
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
arcadia:
|
arcadia:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
coolify:
|
|
||||||
external: true
|
|
||||||
name: coolify
|
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,45 @@
|
||||||
#
|
#
|
||||||
# ESTRATÉGIA DE SOBERANIA DOS DADOS:
|
# ESTRATÉGIA DE SOBERANIA DOS DADOS:
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ TIER 1 (soberania total): Ollama — modelos open source no servidor │
|
# │ TIER 1 (soberania total): LLMFit — modelos fine-tuned locais │
|
||||||
# │ TIER 2 (opt-in): Providers externos — só com configuração explícita │
|
# │ 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.
|
# O Manus, Autonomous Agents e todos os serviços chamam APENAS este proxy.
|
||||||
# Nunca chamam APIs externas diretamente.
|
# Nunca chamam APIs externas diretamente.
|
||||||
|
|
||||||
model_list:
|
model_list:
|
||||||
|
|
||||||
# ── TIER 1: Ollama (LLMs locais — soberania total) ───────────────────────────
|
# ── TIER 1: LLMFit (modelos fine-tuned locais — máxima soberania) ────────────
|
||||||
- model_name: llama3.2
|
# 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:
|
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
|
api_base: os.environ/OLLAMA_BASE_URL
|
||||||
|
|
||||||
- model_name: nomic-embed-text
|
- model_name: nomic-embed-text
|
||||||
|
|
@ -22,13 +49,7 @@ model_list:
|
||||||
model: ollama/nomic-embed-text
|
model: ollama/nomic-embed-text
|
||||||
api_base: os.environ/OLLAMA_BASE_URL
|
api_base: os.environ/OLLAMA_BASE_URL
|
||||||
|
|
||||||
# Alias OpenAI-compatible para embedding (Manus usa este nome por padrão)
|
# ── TIER 3: OpenAI (opt-in — só ativo se OPENAI_API_KEY configurado) ─────────
|
||||||
- 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) ─────────
|
|
||||||
- model_name: gpt-4o
|
- model_name: gpt-4o
|
||||||
litellm_params:
|
litellm_params:
|
||||||
model: openai/gpt-4o
|
model: openai/gpt-4o
|
||||||
|
|
@ -39,35 +60,42 @@ model_list:
|
||||||
model: openai/gpt-4o-mini
|
model: openai/gpt-4o-mini
|
||||||
api_key: os.environ/OPENAI_API_KEY
|
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
|
# - model_name: claude-sonnet
|
||||||
# litellm_params:
|
# litellm_params:
|
||||||
# model: anthropic/claude-sonnet-4-6
|
# model: anthropic/claude-sonnet-4-6
|
||||||
# api_key: os.environ/ANTHROPIC_API_KEY
|
# 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
|
# - model_name: groq-llama
|
||||||
# litellm_params:
|
# litellm_params:
|
||||||
# model: groq/llama-3.3-70b-versatile
|
# model: groq/llama-3.3-70b-versatile
|
||||||
# api_key: os.environ/GROQ_API_KEY
|
# api_key: os.environ/GROQ_API_KEY
|
||||||
|
|
||||||
# ── Modelo padrão do Arcádia (Manus usa este) ─────────────────────────────────
|
# ── 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
|
- model_name: arcadia-default
|
||||||
litellm_params:
|
litellm_params:
|
||||||
model: ollama/llama3.2:3b
|
model: ollama/llama3.3
|
||||||
api_base: os.environ/OLLAMA_BASE_URL
|
api_base: os.environ/OLLAMA_BASE_URL
|
||||||
|
model_info:
|
||||||
|
# fallbacks: ["gpt-4o-mini"] # descomente para habilitar fallback externo
|
||||||
|
|
||||||
router_settings:
|
router_settings:
|
||||||
routing_strategy: least-busy
|
routing_strategy: least-busy
|
||||||
fallbacks:
|
fallbacks:
|
||||||
- {"gpt-4o": ["llama3.2"]}
|
- {"gpt-4o": ["llama3.3"]}
|
||||||
- {"gpt-4o-mini": ["llama3.2"]}
|
- {"gpt-4o-mini": ["llama3.3"]}
|
||||||
- {"arcadia-default": ["llama3.2"]}
|
- {"arcadia-default": ["llama3.3"]}
|
||||||
|
|
||||||
litellm_settings:
|
litellm_settings:
|
||||||
drop_params: true
|
drop_params: true
|
||||||
request_timeout: 120
|
request_timeout: 120
|
||||||
set_verbose: false
|
set_verbose: false
|
||||||
|
# Loga todas as chamadas no banco — essencial para auditoria e soberania
|
||||||
|
success_callback: ["langfuse"]
|
||||||
|
failure_callback: ["langfuse"]
|
||||||
|
|
||||||
general_settings:
|
general_settings:
|
||||||
master_key: os.environ/LITELLM_MASTER_KEY
|
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
|
# Executado ao iniciar o container
|
||||||
set -e
|
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_EMAIL="${SUPERSET_ADMIN_EMAIL:-admin@arcadia.app}"
|
||||||
SUPERSET_ADMIN_PASSWORD="${SUPERSET_ADMIN_PASSWORD:-arcadia2026}"
|
SUPERSET_ADMIN_PASSWORD="${SUPERSET_ADMIN_PASSWORD:-arcadia2026}"
|
||||||
ARCADIA_DB_URL="${ARCADIA_DATABASE_URL:-postgresql://arcadia:arcadia123@db:5432/arcadia}"
|
ARCADIA_DB_URL="${ARCADIA_DATABASE_URL:-postgresql://arcadia:arcadia123@db:5432/arcadia}"
|
||||||
|
|
||||||
echo "[Superset Init] Aguardando PostgreSQL..."
|
echo "[Superset Init] Aguardando PostgreSQL..."
|
||||||
cat > /tmp/pgcheck.py << 'EOF'
|
until python -c "
|
||||||
import psycopg2, os, sys
|
import psycopg2, os, sys
|
||||||
url = os.environ.get('SQLALCHEMY_DATABASE_URI', 'postgresql://arcadia:arcadia123@db:5432/superset')
|
|
||||||
try:
|
try:
|
||||||
|
url = os.environ.get('DATABASE_URL', 'postgresql://arcadia:arcadia123@db:5432/arcadia_superset')
|
||||||
psycopg2.connect(url)
|
psycopg2.connect(url)
|
||||||
except Exception:
|
sys.exit(0)
|
||||||
sys.exit(1)
|
except: sys.exit(1)
|
||||||
EOF
|
" 2>/dev/null; do
|
||||||
until python /tmp/pgcheck.py 2>/dev/null; do
|
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
echo "[Superset Init] PostgreSQL disponível!"
|
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 ────────────────────────────────────────────
|
# ── Banco de metadados do Superset ────────────────────────────────────────────
|
||||||
SQLALCHEMY_DATABASE_URI = os.environ.get(
|
SQLALCHEMY_DATABASE_URI = os.environ.get(
|
||||||
"SQLALCHEMY_DATABASE_URI",
|
"DATABASE_URL",
|
||||||
os.environ.get("DATABASE_URL", "postgresql://arcadia:arcadia123@db:5432/superset")
|
"postgresql://arcadia:arcadia123@db:5432/arcadia_superset"
|
||||||
)
|
)
|
||||||
|
|
||||||
# ── CORS — permite o gateway Arcádia (:5000) chamar a API ────────────────────
|
# ── 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.
|
|
||||||
|
|
@ -1,394 +0,0 @@
|
||||||
# Análise Atendeon → XOS
|
|
||||||
|
|
||||||
> **Projeto:** https://github.com/JonasRodriguesPachceo/atendeon
|
|
||||||
> **Stack:** Node.js (TypeScript) + React + PostgreSQL + Baileys
|
|
||||||
> **Data:** 29/01/2026
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Resumo do Projeto Atendeon
|
|
||||||
|
|
||||||
O Atendeon é uma **central de atendimento WhatsApp multi-empresa** (SaaS) completa com:
|
|
||||||
|
|
||||||
- **477 arquivos TypeScript** no backend
|
|
||||||
- **41 Models** (Sequelize/PostgreSQL)
|
|
||||||
- **35+ Services** organizados por domínio
|
|
||||||
- Integração completa com **Baileys** (WhatsApp não-oficial)
|
|
||||||
- Suporte a **Typebot**, **N8N** e **OpenAI**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Funcionalidades do Atendeon vs XOS Atual
|
|
||||||
|
|
||||||
### ✅ O que o Atendeon tem que NOS FALTA
|
|
||||||
|
|
||||||
| Funcionalidade | Atendeon | XOS Atual | Prioridade |
|
|
||||||
|----------------|----------|-----------|------------|
|
|
||||||
| **Filas de Atendimento (Queues)** | ✅ Completo | ⚪ Não existe | 🔴 Alta |
|
|
||||||
| **Distribuição Round-Robin** | ✅ Por fila/equipe | ⚪ Não existe | 🔴 Alta |
|
|
||||||
| **Notas Internas (TicketNote)** | ✅ Model dedicado | ⚪ Não existe | 🔴 Alta |
|
|
||||||
| **Mensagens Rápidas (QuickMessage)** | ✅ Com atalhos | ⚪ Não existe | 🟠 Média |
|
|
||||||
| **Agendamento de Mensagens** | ✅ Schedule model | ⚪ Não existe | 🟠 Média |
|
|
||||||
| **Tracking de Ticket** | ✅ TicketTraking | ⚪ Não existe | 🟠 Média |
|
|
||||||
| **Horário de Funcionamento** | ✅ outOfHoursMessage | ⚪ Não existe | 🟡 Baixa |
|
|
||||||
| **Rating/Avaliação** | ✅ UserRating | ⚪ Não existe | 🟡 Baixa |
|
|
||||||
| **Campos Customizados** | ✅ ContactCustomField | ✅ customFields JSONB | ✅ OK |
|
|
||||||
| **Multi-WhatsApp** | ✅ Whatsapp model | ⚪ Não integrado | 🔴 Alta |
|
|
||||||
| **Chatbot/Typebot** | ✅ Integração nativa | ⚪ Não existe | 🟠 Média |
|
|
||||||
| **Integração N8N** | ✅ QueueIntegrations | ⚪ Não existe | 🟠 Média |
|
|
||||||
| **Prompts OpenAI** | ✅ Prompt model | ✅ Manus Agent | ✅ OK |
|
|
||||||
| **Campanhas com 5 msgs** | ✅ message1-5 | 🟡 content único | 🟠 Média |
|
|
||||||
| **Disparo em massa** | ✅ CampaignShipping | 🟡 Estrutura só | 🔴 Alta |
|
|
||||||
| **Listas de Contatos** | ✅ ContactList/Item | ⚪ Não existe | 🟠 Média |
|
|
||||||
| **Tags em Tickets** | ✅ TicketTag M:N | 🟡 Array no ticket | ✅ OK |
|
|
||||||
| **Importação de Contatos** | ✅ ImportService | ⚪ Não existe | 🟠 Média |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Models do Atendeon que Devemos Criar
|
|
||||||
|
|
||||||
### 1. **Filas de Atendimento** (Queue)
|
|
||||||
```typescript
|
|
||||||
// Atendeon: Queue.ts
|
|
||||||
{
|
|
||||||
name: string,
|
|
||||||
color: string,
|
|
||||||
greetingMessage: string, // Mensagem de boas-vindas
|
|
||||||
outOfHoursMessage: string, // Fora do expediente
|
|
||||||
schedules: JSONB, // Horários de funcionamento
|
|
||||||
orderQueue: number, // Ordem na distribuição
|
|
||||||
}
|
|
||||||
// Relacionamentos:
|
|
||||||
// - BelongsToMany → Users (UserQueue)
|
|
||||||
// - BelongsToMany → Whatsapps (WhatsappQueue)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Sugestão para XOS:**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE xos_queues (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
tenant_id INTEGER REFERENCES tenants(id),
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
color VARCHAR(20),
|
|
||||||
greeting_message TEXT,
|
|
||||||
out_of_hours_message TEXT,
|
|
||||||
schedules JSONB, -- [{dayOfWeek: 1, startTime: "09:00", endTime: "18:00"}]
|
|
||||||
order_priority INTEGER DEFAULT 0,
|
|
||||||
is_active BOOLEAN DEFAULT true
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE xos_queue_users (
|
|
||||||
queue_id INTEGER REFERENCES xos_queues(id),
|
|
||||||
user_id VARCHAR REFERENCES users(id),
|
|
||||||
is_active BOOLEAN DEFAULT true,
|
|
||||||
PRIMARY KEY (queue_id, user_id)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. **Notas Internas** (TicketNote)
|
|
||||||
```typescript
|
|
||||||
// Atendeon: TicketNote.ts
|
|
||||||
{
|
|
||||||
note: string,
|
|
||||||
userId: number, // Quem escreveu
|
|
||||||
contactId: number, // Sobre qual contato
|
|
||||||
ticketId: number, // Em qual ticket
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Sugestão para XOS:**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE xos_internal_notes (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
conversation_id INTEGER REFERENCES xos_conversations(id),
|
|
||||||
contact_id INTEGER REFERENCES xos_contacts(id),
|
|
||||||
user_id VARCHAR REFERENCES users(id),
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
is_pinned BOOLEAN DEFAULT false,
|
|
||||||
created_at TIMESTAMP DEFAULT NOW()
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. **Mensagens Rápidas** (QuickMessage)
|
|
||||||
```typescript
|
|
||||||
// Atendeon: QuickMessage.ts
|
|
||||||
{
|
|
||||||
shortcode: string, // Ex: "/preco", "/horario"
|
|
||||||
message: string,
|
|
||||||
mediaPath: string, // Anexo opcional
|
|
||||||
mediaName: string,
|
|
||||||
userId: number, // Dono da msg rápida
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Sugestão para XOS:**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE xos_quick_messages (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
tenant_id INTEGER REFERENCES tenants(id),
|
|
||||||
shortcode VARCHAR(50) NOT NULL, -- "/preco"
|
|
||||||
title VARCHAR(200),
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
media_url TEXT,
|
|
||||||
media_type VARCHAR(30), -- image, file, audio
|
|
||||||
scope VARCHAR(20) DEFAULT 'personal', -- personal, team, company
|
|
||||||
user_id VARCHAR REFERENCES users(id),
|
|
||||||
usage_count INTEGER DEFAULT 0,
|
|
||||||
created_at TIMESTAMP DEFAULT NOW()
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. **Agendamento de Mensagens** (Schedule)
|
|
||||||
```typescript
|
|
||||||
// Atendeon: Schedule.ts
|
|
||||||
{
|
|
||||||
body: string, // Conteúdo da mensagem
|
|
||||||
sendAt: Date, // Quando enviar
|
|
||||||
sentAt: Date, // Quando foi enviada
|
|
||||||
status: string, // pending, sent, failed
|
|
||||||
contactId: number,
|
|
||||||
ticketId: number,
|
|
||||||
mediaPath: string,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Sugestão para XOS:**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE xos_scheduled_messages (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
tenant_id INTEGER REFERENCES tenants(id),
|
|
||||||
contact_id INTEGER REFERENCES xos_contacts(id),
|
|
||||||
conversation_id INTEGER REFERENCES xos_conversations(id),
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
media_url TEXT,
|
|
||||||
media_type VARCHAR(30),
|
|
||||||
scheduled_at TIMESTAMP NOT NULL,
|
|
||||||
sent_at TIMESTAMP,
|
|
||||||
status VARCHAR(20) DEFAULT 'pending', -- pending, sent, failed, cancelled
|
|
||||||
error_message TEXT,
|
|
||||||
created_by VARCHAR REFERENCES users(id),
|
|
||||||
created_at TIMESTAMP DEFAULT NOW()
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. **Tracking de Atendimento** (TicketTraking)
|
|
||||||
```typescript
|
|
||||||
// Atendeon: TicketTraking.ts
|
|
||||||
{
|
|
||||||
ticketId: number,
|
|
||||||
startedAt: Date, // Início do atendimento
|
|
||||||
queuedAt: Date, // Quando entrou na fila
|
|
||||||
finishedAt: Date, // Quando finalizou
|
|
||||||
ratingAt: Date, // Quando foi avaliado
|
|
||||||
chatbotAt: Date, // Quando passou pelo bot
|
|
||||||
rated: boolean,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Sugestão para XOS:**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE xos_conversation_tracking (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
conversation_id INTEGER REFERENCES xos_conversations(id) UNIQUE,
|
|
||||||
queued_at TIMESTAMP,
|
|
||||||
first_response_at TIMESTAMP,
|
|
||||||
assigned_at TIMESTAMP,
|
|
||||||
chatbot_ended_at TIMESTAMP,
|
|
||||||
human_started_at TIMESTAMP,
|
|
||||||
resolved_at TIMESTAMP,
|
|
||||||
rated_at TIMESTAMP,
|
|
||||||
rating_score INTEGER, -- 1-5
|
|
||||||
rating_comment TEXT,
|
|
||||||
total_duration_seconds INTEGER, -- calculado
|
|
||||||
response_time_seconds INTEGER -- calculado
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. **Conexões WhatsApp** (Whatsapp)
|
|
||||||
```typescript
|
|
||||||
// Atendeon: Whatsapp.ts
|
|
||||||
{
|
|
||||||
name: string,
|
|
||||||
session: string, // Dados da sessão Baileys
|
|
||||||
qrcode: string, // QR code atual
|
|
||||||
status: string, // CONNECTED, DISCONNECTED, QR
|
|
||||||
greetingMessage: string,
|
|
||||||
farewellMessage: string,
|
|
||||||
complationMessage: string, // Ao finalizar ticket
|
|
||||||
ratingMessage: string,
|
|
||||||
expiresTicket: number, // Minutos para expirar
|
|
||||||
expiresInactiveMessage: string,
|
|
||||||
maxUseBotQueues: number,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Sugestão para XOS:**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE xos_whatsapp_connections (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
tenant_id INTEGER REFERENCES tenants(id),
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
phone_number VARCHAR(20),
|
|
||||||
status VARCHAR(30) DEFAULT 'disconnected', -- connected, disconnected, qr_pending
|
|
||||||
qr_code TEXT,
|
|
||||||
session_data JSONB, -- Dados Baileys
|
|
||||||
greeting_message TEXT,
|
|
||||||
farewell_message TEXT,
|
|
||||||
completion_message TEXT,
|
|
||||||
rating_message TEXT,
|
|
||||||
out_of_hours_message TEXT,
|
|
||||||
ticket_expires_minutes INTEGER DEFAULT 1440, -- 24h
|
|
||||||
inactive_message TEXT,
|
|
||||||
is_default BOOLEAN DEFAULT false,
|
|
||||||
last_seen_at TIMESTAMP,
|
|
||||||
created_at TIMESTAMP DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE xos_whatsapp_queue_links (
|
|
||||||
whatsapp_id INTEGER REFERENCES xos_whatsapp_connections(id),
|
|
||||||
queue_id INTEGER REFERENCES xos_queues(id),
|
|
||||||
PRIMARY KEY (whatsapp_id, queue_id)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Services do Atendeon para Referência
|
|
||||||
|
|
||||||
### Distribuição de Tickets
|
|
||||||
O Atendeon usa o conceito de **Filas** para distribuir leads:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// FindOrCreateTicketService.ts (simplificado)
|
|
||||||
const ticket = await Ticket.create({
|
|
||||||
contactId,
|
|
||||||
whatsappId,
|
|
||||||
status: "pending", // Aguardando na fila
|
|
||||||
queueId: defaultQueue.id,
|
|
||||||
// NÃO atribui userId ainda - fica na fila
|
|
||||||
});
|
|
||||||
|
|
||||||
// UpdateTicketService.ts - Quando atendente pega o ticket
|
|
||||||
ticket.userId = attendantId;
|
|
||||||
ticket.status = "open";
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lógica de Round-Robin
|
|
||||||
```typescript
|
|
||||||
// Distribui entre usuários da fila
|
|
||||||
const usersInQueue = await UserQueue.findAll({
|
|
||||||
where: { queueId },
|
|
||||||
include: [{ model: User, where: { online: true }}]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Pega o próximo da lista (round-robin)
|
|
||||||
const nextUser = usersInQueue[currentIndex % usersInQueue.length];
|
|
||||||
```
|
|
||||||
|
|
||||||
### Chatbot com Opções
|
|
||||||
```typescript
|
|
||||||
// QueueOption.ts - Menu do chatbot
|
|
||||||
{
|
|
||||||
title: string, // "Vendas"
|
|
||||||
option: string, // "1"
|
|
||||||
message: string, // Resposta quando selecionar
|
|
||||||
parentId: number, // Para sub-menus
|
|
||||||
queueId: number,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Plano de Implementação
|
|
||||||
|
|
||||||
### Fase 1: Estrutura Base (1-2 semanas)
|
|
||||||
1. ✅ Criar tabelas: `xos_queues`, `xos_queue_users`
|
|
||||||
2. ✅ Criar tabelas: `xos_internal_notes`, `xos_quick_messages`
|
|
||||||
3. ✅ Criar tabelas: `xos_scheduled_messages`
|
|
||||||
4. ✅ Criar tabelas: `xos_conversation_tracking`
|
|
||||||
5. ✅ Atualizar APIs XOS com novos endpoints
|
|
||||||
|
|
||||||
### Fase 2: Distribuição de Leads (1 semana)
|
|
||||||
1. Implementar lógica de filas
|
|
||||||
2. Round-robin entre atendentes
|
|
||||||
3. Status: pending → open → resolved
|
|
||||||
|
|
||||||
### Fase 3: Integração WhatsApp (2-3 semanas)
|
|
||||||
1. Criar `xos_whatsapp_connections`
|
|
||||||
2. Integrar Baileys existente do projeto
|
|
||||||
3. Sincronizar mensagens com `xos_messages`
|
|
||||||
4. QR Code e gestão de sessões
|
|
||||||
|
|
||||||
### Fase 4: Features Avançadas (2 semanas)
|
|
||||||
1. Chatbot com menu de opções
|
|
||||||
2. Horário de funcionamento
|
|
||||||
3. Mensagens automáticas
|
|
||||||
4. Sistema de avaliação
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Código que Podemos Reutilizar
|
|
||||||
|
|
||||||
### 1. `wbotMessageListener.ts` (62KB)
|
|
||||||
- Lógica completa de recebimento de mensagens
|
|
||||||
- Tratamento de mídia (áudio, imagem, vídeo)
|
|
||||||
- Integração com OpenAI
|
|
||||||
- Fluxo de chatbot
|
|
||||||
|
|
||||||
### 2. `providers.ts` (79KB)
|
|
||||||
- Abstração do Baileys
|
|
||||||
- Gestão de sessões
|
|
||||||
- Envio de mensagens
|
|
||||||
|
|
||||||
### 3. `UpdateTicketService.ts`
|
|
||||||
- Lógica de transferência entre filas
|
|
||||||
- Tracking de tempos
|
|
||||||
- Notificações
|
|
||||||
|
|
||||||
### 4. `CampaignService/`
|
|
||||||
- Disparo em massa
|
|
||||||
- Agendamento
|
|
||||||
- Estatísticas
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Comparativo Final
|
|
||||||
|
|
||||||
| Aspecto | Atendeon | XOS |
|
|
||||||
|---------|----------|-----|
|
|
||||||
| **Foco** | WhatsApp CRM puro | Plataforma multi-propósito |
|
|
||||||
| **Multi-tenant** | Por company | Por tenant |
|
|
||||||
| **ORM** | Sequelize | Drizzle |
|
|
||||||
| **Real-time** | Socket.IO | Socket.IO |
|
|
||||||
| **WhatsApp** | Baileys integrado | Baileys separado |
|
|
||||||
| **AI** | OpenAI básico | Manus Agent completo |
|
|
||||||
| **Código** | ~500 arquivos | Integrado na Suite |
|
|
||||||
|
|
||||||
### Vantagens do Atendeon
|
|
||||||
- Sistema maduro e testado
|
|
||||||
- Lógica de filas pronta
|
|
||||||
- Chatbot funcional
|
|
||||||
|
|
||||||
### Vantagens do XOS
|
|
||||||
- Arquitetura mais moderna
|
|
||||||
- IA mais avançada (Manus)
|
|
||||||
- Integração com ERP
|
|
||||||
- Multi-canal nativo
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Recomendação
|
|
||||||
|
|
||||||
**Estratégia:** Não copiar código diretamente, mas **usar como referência** para:
|
|
||||||
|
|
||||||
1. **Schema de dados** → Adaptar models do Atendeon para Drizzle/PostgreSQL
|
|
||||||
2. **Lógica de negócio** → Implementar distribuição de leads similar
|
|
||||||
3. **Fluxos de chatbot** → Replicar QueueOption para menus
|
|
||||||
4. **Tracking** → Copiar conceito de TicketTraking
|
|
||||||
|
|
||||||
**Estimativa de Desenvolvimento:**
|
|
||||||
- Estrutura básica: 2-3 semanas
|
|
||||||
- Integração WhatsApp: 2-3 semanas
|
|
||||||
- Features completas: 4-6 semanas
|
|
||||||
|
|
||||||
**Total:** ~2 meses para paridade com Atendeon
|
|
||||||