diff --git a/DEPLOY_COOLIFY.md b/DEPLOY_COOLIFY.md new file mode 100644 index 0000000..9410139 --- /dev/null +++ b/DEPLOY_COOLIFY.md @@ -0,0 +1,359 @@ +# Deploy no Coolify — Arcádia Suite + +> Guia completo para subir o Arcádia Suite no Coolify com Docker Compose. + +--- + +## Índice + +1. [Pré-requisitos](#pré-requisitos) +2. [Arquitetura de Serviços](#arquitetura-de-serviços) +3. [Configuração no Coolify](#configuração-no-coolify) +4. [Variáveis de Ambiente](#variáveis-de-ambiente) +5. [Serviços Opcionais (Profiles)](#serviços-opcionais-profiles) +6. [Sequência de Inicialização](#sequência-de-inicialização) +7. [Traefik e HTTPS](#traefik-e-https) +8. [Volumes e Persistência](#volumes-e-persistência) +9. [Stack de IA (LiteLLM + Ollama)](#stack-de-ia-litellm--ollama) +10. [Pós-deploy](#pós-deploy) +11. [Checklist Final](#checklist-final) + +--- + +## Pré-requisitos + +- Coolify instalado e acessível (v4+) +- Docker Engine 24+ e Docker Compose v2+ no servidor +- Domínio apontando para o IP do servidor (`A record`) +- Mínimo recomendado: **4 vCPU, 8 GB RAM, 50 GB SSD** +- Para IA local (Ollama): **16 GB RAM, GPU opcional** + +--- + +## Arquitetura de Serviços + +``` +Internet → Traefik (Coolify) → app:5000 → arcadia-internal + → open-webui:8080 (opcional) + +arcadia-internal: + db (PostgreSQL 16 + pgvector) :5432 + redis :6379 + app (Node.js/Express) :5000 + embeddings (Python FastAPI) :8001 + fisco (Python FastAPI) :8002 + contabil (Python FastAPI) :8003 + bi (Python FastAPI) :8004 + automation (Python FastAPI) :8005 + litellm (gateway LLM) :4000 ← profile: ai + ollama (modelos locais) :11434 ← profile: ai + superset (BI) :8088 ← profile: bi + plus (Laravel/ERP) :8080 ← profile: plus + erpnext :8090 ← profile: erpnext +``` + +--- + +## Configuração no Coolify + +### 1. Criar novo projeto + +No painel Coolify: **New Project → Docker Compose**. + +### 2. Apontar para o repositório + +- **Source:** Git (GitHub/GitLab/Gitea) +- **Branch:** `main` (ou sua branch de produção) +- **Docker Compose file:** `docker-compose.prod.yml` + +### 3. Definir o domínio + +Em **Domains**, adicione `seu-dominio.com`. O Coolify irá automaticamente: +- Criar as rotas no Traefik +- Emitir certificado Let's Encrypt + +--- + +## Variáveis de Ambiente + +Configure todas no painel **Environment Variables** do Coolify (nunca no `.env` do repositório). + +### Obrigatórias + +```env +# Aplicação +NODE_ENV=production +PORT=5000 +DOMAIN=seu-dominio.com +DOCKER_MODE=true + +# Segredos — gerar com: openssl rand -hex 32 +SESSION_SECRET= +SSO_SECRET= + +# Banco de dados +PGHOST=db +PGPORT=5432 +PGUSER=arcadia +PGPASSWORD= +PGDATABASE=arcadia +DATABASE_URL=postgresql://arcadia:@db:5432/arcadia + +# Redis +REDIS_URL=redis://redis:6379 + +# IA — LiteLLM gateway +LITELLM_API_KEY= +AI_INTEGRATIONS_OPENAI_BASE_URL=http://litellm:4000/v1 +AI_INTEGRATIONS_OPENAI_API_KEY= +OLLAMA_BASE_URL=http://ollama:11434 + +# Microserviços Python +PYTHON_SERVICE_URL=http://embeddings:8001 +FISCO_PYTHON_URL=http://fisco:8002 +CONTABIL_PYTHON_URL=http://contabil:8003 +BI_PYTHON_URL=http://bi:8004 +AUTOMATION_PYTHON_URL=http://automation:8005 +``` + +### LLMs externos (opcionais — soberania: deixar em branco) + +```env +OPENAI_API_KEY= +ANTHROPIC_API_KEY= +GROQ_API_KEY= +LLMFIT_BASE_URL= # habilitar quando LLMFit estiver disponível +``` + +### Superset — profile `bi` + +```env +SUPERSET_SECRET_KEY= +SUPERSET_ADMIN_USER=admin +SUPERSET_ADMIN_EMAIL=admin@seu-dominio.com +SUPERSET_ADMIN_PASSWORD= +SUPERSET_HOST=superset +SUPERSET_PORT=8088 +``` + +### Arcádia Plus — profile `plus` + +```env +SSO_PLUS_BASE_URL=http://plus:8080 +PLUS_APP_KEY= +PLUS_DB_ROOT_PASSWORD= +PLUS_DB_PASSWORD= +PLUS_DB_DATABASE=arcadia_plus +PLUS_DB_USER=plus +``` + +### ERPNext — profile `erpnext` + +```env +ERPNEXT_PROXY_ENABLED=true +ERPNEXT_CONTAINER_HOST=erpnext +ERPNEXT_DB_ROOT_PASSWORD= +ERPNEXT_DB_PASSWORD= +ERPNEXT_ADMIN_PASSWORD= +ERPNEXT_URL=http://erpnext:8090 +``` + +--- + +## Serviços Opcionais (Profiles) + +O `docker-compose.prod.yml` usa profiles para habilitar serviços sob demanda. +No Coolify, adicione a variável de ambiente `COMPOSE_PROFILES`: + +| O que habilitar | `COMPOSE_PROFILES` | +|---|---| +| Somente core | *(vazio)* | +| IA local (Ollama + LiteLLM + WebUI) | `ai` | +| Superset BI | `bi` | +| Arcádia Plus (Laravel ERP) | `plus` | +| ERPNext | `erpnext` | +| Tudo | `ai,bi,plus,erpnext` | + +> **Atenção:** Ollama com modelos grandes (llama3.3, qwen2.5) pode consumir 10–20 GB de disco. Certifique-se de ter espaço no volume `ollama_models`. + +--- + +## Sequência de Inicialização + +Os `depends_on` garantem a ordem correta automaticamente: + +``` +1. db → healthcheck: pg_isready (até 10 tentativas) +2. redis → healthcheck: redis-cli ping +3. embeddings, fisco, contabil, bi, automation → aguardam db healthy +4. litellm, ollama → aguardam redis (se profile ai) +5. app → aguarda db healthy + redis started +6. open-webui, superset, plus, erpnext → aguardam serviços base +``` + +Se um serviço falhar ao iniciar, cheque os logs: + +```bash +# No servidor +docker compose -f docker-compose.prod.yml logs app --tail 50 +docker compose -f docker-compose.prod.yml logs db --tail 50 +``` + +--- + +## Traefik e HTTPS + +O Coolify gerencia o Traefik. As labels já estão no `docker-compose.prod.yml`: + +```yaml +# Serviço principal +labels: + - "traefik.enable=true" + - "traefik.http.routers.arcadia.rule=Host(`${DOMAIN}`)" + - "traefik.http.routers.arcadia.tls=true" + - "traefik.http.routers.arcadia.tls.certresolver=letsencrypt" + - "traefik.http.services.arcadia.loadbalancer.server.port=5000" + +# Open WebUI (profile ai) — subdomínio ai.seu-dominio.com +labels: + - "traefik.http.routers.webui.rule=Host(`ai.${DOMAIN}`)" + - "traefik.http.routers.webui.tls=true" + - "traefik.http.routers.webui.tls.certresolver=letsencrypt" + - "traefik.http.services.webui.loadbalancer.server.port=8080" +``` + +Certifique-se de que o DNS do subdomínio `ai.seu-dominio.com` também aponta para o servidor antes de habilitar o profile `ai`. + +--- + +## Volumes e Persistência + +| Volume | Conteúdo | Criticidade | +|---|---|---| +| `pgdata` | Banco de dados PostgreSQL | **CRÍTICO — fazer backup** | +| `redis_data` | Sessões e cache | Alta | +| `ollama_models` | Modelos LLM baixados (~10–20 GB) | Média (redownload possível) | +| `open_webui_data` | Histórico do WebUI | Baixa | +| `superset_home` | Dashboards Superset | Média | +| `plus_db` | Banco MySQL do Plus | Alta | +| `plus_storage` | Arquivos do Plus | Alta | +| `erpnext_sites` | Sites ERPNext | **CRÍTICO** | +| `erpnext_logs` | Logs ERPNext | Baixa | + +### Backup do PostgreSQL + +```bash +# Backup +docker exec arcadia-db pg_dump -U arcadia arcadia | gzip > backup-$(date +%Y%m%d).sql.gz + +# Restore +gunzip < backup-20240101.sql.gz | docker exec -i arcadia-db psql -U arcadia arcadia +``` + +Configure backups automáticos no Coolify em **Project → Backups**. + +--- + +## Stack de IA (LiteLLM + Ollama) + +### Três tiers configurados em `docker/litellm-config.yaml` + +``` +TIER 1 — LLMFit (fine-tuned, soberano) → slot pronto, habilitar via LLMFIT_BASE_URL +TIER 2 — Ollama (local, padrão) → llama3.3, qwen2.5-coder, nomic-embed-text +TIER 3 — Externos (opt-in) → OpenAI, Anthropic, Groq (apenas se API key definida) +``` + +### Baixar modelos Ollama após o primeiro deploy + +```bash +docker exec arcadia-ollama ollama pull llama3.3 +docker exec arcadia-ollama ollama pull qwen2.5-coder:7b +docker exec arcadia-ollama ollama pull nomic-embed-text +``` + +### Habilitar LLMFit (quando disponível) + +1. Defina `LLMFIT_BASE_URL=http://seu-llmfit:porta` +2. Descomente o bloco TIER 1 em `docker/litellm-config.yaml` +3. Redeploy do serviço `litellm` + +--- + +## Pós-deploy + +### Rodar migrations do banco + +```bash +# Via Coolify → Run Command, ou no servidor: +docker exec arcadia-app npm run db:push +``` + +### Verificar saúde dos serviços + +```bash +docker compose -f docker-compose.prod.yml ps +``` + +Todos devem estar `healthy` ou `running`. Se algum ficar em `restarting`, verifique os logs. + +### Verificar conectividade IA + +```bash +curl -H "Authorization: Bearer $LITELLM_API_KEY" \ + http://localhost:4000/v1/models +``` + +--- + +## Checklist Final + +### Antes do deploy + +- [ ] Domínio DNS apontando para o servidor (`A record`) +- [ ] Todas as variáveis obrigatórias definidas no Coolify +- [ ] `SESSION_SECRET` e `SSO_SECRET` gerados com `openssl rand -hex 32` +- [ ] Senhas do banco definidas (nunca reutilizar senhas de dev) +- [ ] `DOCKER_MODE=true` definido + +### No Coolify + +- [ ] Projeto criado apontando para `docker-compose.prod.yml` +- [ ] Domínio configurado no Coolify +- [ ] HTTPS ativo (Let's Encrypt) +- [ ] Volumes persistentes configurados (especialmente `pgdata`) +- [ ] `COMPOSE_PROFILES` definido conforme os serviços desejados + +### Após o deploy + +- [ ] `npm run db:push` executado (migrations) +- [ ] Login funcional no `https://seu-dominio.com` +- [ ] Healthchecks verdes no painel Coolify +- [ ] Modelos Ollama baixados (se profile `ai` ativo) +- [ ] Backup automático configurado para `pgdata` +- [ ] Monitoramento/alertas configurados (Coolify suporta webhooks) + +--- + +## Comandos de Referência Rápida + +```bash +# Subir apenas o core +docker compose -f docker-compose.prod.yml up -d + +# Subir com IA +docker compose -f docker-compose.prod.yml --profile ai up -d + +# Subir tudo +docker compose -f docker-compose.prod.yml --profile ai --profile bi --profile plus up -d + +# Ver logs em tempo real +docker compose -f docker-compose.prod.yml logs -f app + +# Reiniciar um serviço específico +docker compose -f docker-compose.prod.yml restart app + +# Atualizar para nova versão (após git pull) +docker compose -f docker-compose.prod.yml pull +docker compose -f docker-compose.prod.yml up -d --no-deps app +``` diff --git a/INTEGRACAO_IA.md b/INTEGRACAO_IA.md new file mode 100644 index 0000000..226791d --- /dev/null +++ b/INTEGRACAO_IA.md @@ -0,0 +1,262 @@ +# Integração de IA — Ollama + LLMFit no Servidor + +Guia para conectar o Arcádia Suite às IAs locais em produção. + +--- + +## Visão geral do fluxo + +``` +Arcádia Suite (Manus, Agents, Embeddings) + │ + │ http://litellm:4000/v1 + ▼ + LiteLLM (porta 4000) — gateway único + │ + ├──► LLMFit (seus modelos fine-tuned) [TIER 1 — prioridade] + └──► Ollama (modelos open source locais) [TIER 2 — padrão/fallback] +``` + +Nenhum serviço do Arcádia chama Ollama ou LLMFit diretamente. +Tudo passa pelo LiteLLM — que roteia, loga e faz fallback automaticamente. + +--- + +## Cenário A: Ollama já instalado no servidor (fora do Docker) + +### 1. Verificar se o Ollama responde + +```bash +curl http://localhost:11434/api/tags +# Deve retornar a lista de modelos instalados +``` + +### 2. Configurar a variável no Coolify + +``` +OLLAMA_BASE_URL=http://host-gateway:11434 +``` + +> `host-gateway` é o endereço do host dentro da rede Docker. +> Se não funcionar, use o IP da interface de rede do servidor (ex: `http://192.168.1.X:11434`). + +### 3. Desativar o container Ollama (não precisa dos dois) + +No Coolify, não suba o profile `ai` se não for usar o container Docker do Ollama: +```bash +# Sobe tudo EXCETO ollama e open-webui +docker compose -f docker-compose.prod.yml up -d +# Sobe só o LiteLLM (necessário sempre) +docker compose -f docker-compose.prod.yml --profile ai up litellm -d +``` + +### 4. Puxar os modelos necessários + +```bash +# Modelos usados pelo Arcádia (mínimo recomendado) +ollama pull llama3.3 # agente principal (Manus) +ollama pull nomic-embed-text # embeddings semânticos +ollama pull qwen2.5-coder:7b # geração de código (DevCenter) +ollama pull deepseek-r1:7b # raciocínio complexo +``` + +--- + +## Cenário B: Ollama como container Docker + +Use este cenário se o Ollama NÃO está instalado no servidor. + +### 1. Configurar variável + +``` +OLLAMA_BASE_URL=http://ollama:11434 +``` + +### 2. Subir com o profile ai + +```bash +docker compose -f docker-compose.prod.yml --profile ai up -d +``` + +### 3. Puxar modelos após container subir + +```bash +docker exec -it $(docker ps -qf "name=ollama") ollama pull llama3.3 +docker exec -it $(docker ps -qf "name=ollama") ollama pull nomic-embed-text +docker exec -it $(docker ps -qf "name=ollama") ollama pull qwen2.5-coder:7b +docker exec -it $(docker ps -qf "name=ollama") ollama pull deepseek-r1:7b +``` + +--- + +## Configuração do LLMFit + +### 1. Pré-requisito + +O LLMFit deve expor uma API compatível com OpenAI (formato `/v1/chat/completions`). +Verifique se está respondendo: +```bash +curl http://IP_DO_LLMFIT:PORTA/v1/models +``` + +### 2. Configurar variável no Coolify + +``` +LLMFIT_BASE_URL=http://IP_DO_LLMFIT:PORTA +``` + +### 3. Ativar no LiteLLM config + +Edite `docker/litellm-config.yaml` e **descomente** o bloco TIER 1: + +```yaml +model_list: + + # TIER 1 — LLMFit (fine-tuned, prioridade máxima) + - model_name: arcadia-finetuned + litellm_params: + model: openai/NOME_DO_SEU_MODELO # substitua pelo nome real + api_base: os.environ/LLMFIT_BASE_URL + api_key: llmfit-internal + + # Modelo de embeddings fine-tuned (se disponível) + - model_name: arcadia-embed + litellm_params: + model: openai/NOME_DO_MODELO_EMBED + api_base: os.environ/LLMFIT_BASE_URL + api_key: llmfit-internal +``` + +### 4. Definir LLMFit como modelo padrão do Arcádia + +No mesmo arquivo, atualize o `arcadia-default`: + +```yaml + - model_name: arcadia-default + litellm_params: + model: openai/NOME_DO_SEU_MODELO + api_base: os.environ/LLMFIT_BASE_URL + api_key: llmfit-internal + model_info: + fallbacks: ["llama3.3"] # cai para Ollama se LLMFit falhar +``` + +### 5. Reiniciar o LiteLLM para aplicar + +```bash +docker compose -f docker-compose.prod.yml restart litellm +``` + +--- + +## Variáveis de ambiente — resumo completo + +Configure todas no Coolify antes do deploy: + +```bash +# ── Banco ───────────────────────────────────────────────────────────────────── +PGPASSWORD= # senha forte, não use a padrão +DATABASE_URL=postgresql://arcadia:SENHA@db:5432/arcadia + +# ── Segurança (gerar com: openssl rand -hex 32) ──────────────────────────────── +SESSION_SECRET= +SSO_SECRET= +LITELLM_API_KEY= +WEBUI_SECRET_KEY= +SUPERSET_SECRET_KEY= + +# ── Manus → LiteLLM (não alterar — já configurado) ─────────────────────────── +AI_INTEGRATIONS_OPENAI_BASE_URL=http://litellm:4000/v1 +AI_INTEGRATIONS_OPENAI_API_KEY=${LITELLM_API_KEY} + +# ── Ollama ──────────────────────────────────────────────────────────────────── +# Ollama no host: http://host-gateway:11434 +# Ollama em container: http://ollama:11434 +OLLAMA_BASE_URL=http://host-gateway:11434 + +# ── LLMFit (deixar vazio até estar disponível) ──────────────────────────────── +LLMFIT_BASE_URL= + +# ── Providers externos (deixar vazio para soberania total) ─────────────────── +OPENAI_API_KEY= +ANTHROPIC_API_KEY= +GROQ_API_KEY= + +# ── Domínio ─────────────────────────────────────────────────────────────────── +DOMAIN=seudominio.com.br +``` + +--- + +## Verificar se a integração está funcionando + +### 1. Testar LiteLLM direto + +```bash +curl http://localhost:4000/v1/chat/completions \ + -H "Authorization: Bearer SEU_LITELLM_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"model":"arcadia-default","messages":[{"role":"user","content":"Oi"}]}' +``` + +### 2. Testar Manus via interface + +Acesse `https://seudominio.com.br` → abra o Manus → envie uma mensagem simples. +O Manus deve responder via Ollama (ou LLMFit se configurado). + +### 3. Ver logs em tempo real + +```bash +# Logs do LiteLLM (todas as chamadas de IA) +docker compose logs -f litellm + +# Logs do app (Manus, erros, requests) +docker compose logs -f app +``` + +--- + +## Ordem de inicialização recomendada + +```bash +# 1. Banco e Redis +docker compose -f docker-compose.prod.yml up -d db redis + +# 2. Aguardar banco ficar saudável +docker compose -f docker-compose.prod.yml ps # esperar db = healthy + +# 3. Migrations (primeira vez ou após atualização de schema) +docker compose -f docker-compose.prod.yml run --rm app npm run db:push + +# 4. LiteLLM +docker compose -f docker-compose.prod.yml up -d litellm + +# 5. App + microserviços Python +docker compose -f docker-compose.prod.yml up -d app contabil bi automation fisco embeddings + +# 6. Ollama (se usando container) +docker compose -f docker-compose.prod.yml --profile ai up -d ollama +# Aguardar e puxar modelos: +docker exec $(docker ps -qf "name=ollama") ollama pull llama3.3 +docker exec $(docker ps -qf "name=ollama") ollama pull nomic-embed-text +``` + +--- + +## Dúvidas frequentes + +**O Manus não responde / trava** +→ Verifique se o LiteLLM está de pé: `docker compose logs litellm` +→ Verifique se o Ollama tem o modelo: `ollama list` + +**Erro "model not found" no LiteLLM** +→ O modelo referenciado em `litellm-config.yaml` não foi baixado no Ollama. +→ Execute `ollama pull NOME_DO_MODELO` + +**LLMFit não está sendo chamado** +→ Confirme que `LLMFIT_BASE_URL` está definido e o serviço está respondendo. +→ Reinicie o LiteLLM após alterar o config: `docker compose restart litellm` + +**Ollama no host não é alcançado de dentro do Docker** +→ Tente `OLLAMA_BASE_URL=http://172.17.0.1:11434` (IP padrão do docker0) +→ Ou use o IP real da interface de rede: `ip addr show` para descobrir diff --git a/PLANO_BI_SUPERSET.md b/PLANO_BI_SUPERSET.md new file mode 100644 index 0000000..5a74ae6 --- /dev/null +++ b/PLANO_BI_SUPERSET.md @@ -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 → + ✅ 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 │ +│ │ +│ ┌────────────────────────────────────────────────────────────┐ │ +│ │ │ │ +│ │ 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 { + 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(null); + const [error, setError] = useState(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 ( +
+ Superset indisponível: {error} +
+ ); + } + + return ( +
+ {loading && ( +
+
+
+

Carregando Arcádia Insights...

+
+
+ )} +
+
+ ); +} +``` + +**Uso em QUALQUER página:** +```tsx +// Exemplo: na página /financeiro +import { SupersetDashboard } from "@/components/SupersetDashboard"; + +// Dashboard financeiro embeddado diretamente no módulo Financeiro + + +// Exemplo: na página /contabil + + +// Exemplo: no Dashboard principal (/) + +``` + +### 4.7 BiWorkspace.tsx — Advanced tab (única mudança na UI) + +```tsx +// Substituir a tab Advanced (linhas 2918-2945) + + +
+
+
+

Arcádia Insights — Apache Superset

+

+ SQL Lab avançado, 50+ tipos de gráfico, dashboards interativos +

+
+ + Abrir em Nova Aba + +
+ + {/* Dashboard selecionável */} + +
+
+``` + +--- + +## 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) │ │ +│ │ → 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* diff --git a/PLANO_SOE_CENTRAL.md b/PLANO_SOE_CENTRAL.md new file mode 100644 index 0000000..921b731 --- /dev/null +++ b/PLANO_SOE_CENTRAL.md @@ -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 + buscarProduto(codigo: string): Promise + atualizarEstoque(movimentacao: MovimentacaoEstoque): Promise + + // Pedidos + criarPedidoVenda(pedido: PedidoSOE): Promise + aprovarPedido(id: string): Promise + faturarPedido(id: string): Promise + + // Financeiro + criarLancamento(lancamento: LancamentoSOE): Promise + buscarPlanoContas(): Promise + + // Compras + criarPedidoCompra(data: PedidoCompraSOE): Promise + receberMercadoria(data: RecebimentoSOE): Promise +} + +// As regras ficam no SOE, o adapter só traduz +class FrappeAdapter implements ERPAdapter { + private baseUrl: string + private apiKey: string + + async criarPedidoVenda(pedido: PedidoSOE): Promise { + // 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* diff --git a/PLANO_SOE_CENTRAL_v2.md b/PLANO_SOE_CENTRAL_v2.md new file mode 100644 index 0000000..f5f9d40 --- /dev/null +++ b/PLANO_SOE_CENTRAL_v2.md @@ -0,0 +1,666 @@ +# PLANO SOE CENTRAL v2 — Análise Real + Evolução +## Baseado no MAPA_SOE_ARCADIA.md (Replit) + Auditoria do Código +### Versão 2.0 — Março 2026 + +--- + +## 1. DIAGNÓSTICO — O QUE JÁ EXISTE (e o plano v1 não sabia) + +O MAPA_SOE_ARCADIA.md do Replit revelou que **o SOE já é uma realidade arquitetural robusta**, não apenas um conceito. Antes de planejar qualquer coisa, é crítico entender o que já funciona. + +### 1.1 O que já está construído e funcionando + +| Componente | Status | Localização | +|---|---|---| +| `SOE.tsx` | ✅ 2.414 linhas — UI completa com 9 tabs | `client/src/pages/SOE.tsx` | +| `SoeMotorContext.tsx` | ✅ Seletor de motor (plus/erpnext) | `client/src/contexts/` | +| `ErpApiClient` (interface) | ✅ Interface unificada definida | `server/erp/index.ts` | +| `ArcadiaPlusClient` | ✅ Adaptador HTTP → Plus:8080 | `server/erp/index.ts` | +| `ArcadiaNextClient` | ✅ Adaptador HTTP → ERPNext API | `server/erp/index.ts` | +| `registerSoeRoutes()` | ✅ 60+ endpoints em /api/soe/* | `server/erp/routes.ts` | +| `server/plus/proxy.ts` | ✅ Proxy reverso para Plus | `server/plus/` | +| `server/plus/sso.ts` | ✅ SSO automático Plus ↔ Suite | `server/plus/` | +| `server/plus/launcher.ts` | ✅ Lança PHP artisan serve | `server/plus/` | +| Módulo de Pessoas unificado | ✅ `persons` + `person_roles` | Schema PostgreSQL | +| Módulo de Produtos | ✅ Com NCM, CST, CFOP, IMEI | Schema PostgreSQL | +| Módulo Financeiro | ✅ Contas a P/R + transações | Schema PostgreSQL | +| Motor Fisco (:8002) | ✅ NF-e/NFC-e via nfelib | `server/python/` | +| Motor Contábil (:8003) | ✅ DRE, Balanço, SPED | `server/python/` | +| Sistema de módulos por tenant | ✅ `tenants.features` (JSONB) | Schema PostgreSQL | +| Middleware de gating | ✅ `requireModule()` | `server/` | + +### 1.2 Bancos de Dados por Motor (detalhe crítico) + +``` +Arcádia Suite → PostgreSQL 16 (dados do SOE, pessoas, produtos, vendas...) +Arcádia Plus → MySQL 8.0 (dados do Laravel — vendas, estoque, NF-e...) +ERPNext → MariaDB (dados do Frappe — CRM, HR, projetos...) +Motor Fisco → sem banco (só processa, retorna resultado) +Motor Contábil → sem banco (só processa, retorna resultado) +``` + +**Implicação:** Não há sincronização automática entre esses bancos. Um pedido criado no SOE (PostgreSQL) não aparece no Plus (MySQL) automaticamente — e vice-versa. + +### 1.3 O padrão motor/adaptador atual + +``` +Request do usuário + │ + ▼ +/api/soe/sales-orders (POST) + │ + ▼ +Verifica motor ativo (localStorage: arcadia_soe_motor) + │ + ├── motor = "local" → INSERT direto no PostgreSQL + ├── motor = "plus" → POST → Plus:8080 → MySQL + └── motor = "erpnext" → POST → ERPNext API → MariaDB +``` + +--- + +## 2. O PROBLEMA REAL — Onde o SOE atual para + +O SOE atual é um **roteador inteligente**: recebe o request, decide para qual motor enviar, e despacha. Ele **não tem inteligência própria**. + +### 2.1 O que falta (lacuna real) + +``` +Situação atual: + +SOE.tsx → /api/soe/* ────────────────────────────► Plus/ERPNext + (sem regras, sem processamento) + Regras ficam DENTRO do Plus + Regras ficam DENTRO do ERPNext + +Situação desejada: + +SOE.tsx → /api/soe/* → [RULE ENGINE SOE] ──────► Plus/ERPNext + │ (só executa) + ├── Regras Fiscais + ├── Regras Contábeis + ├── Regras Financeiras + └── Regras de Negócio +``` + +### 2.2 Problemas concretos hoje + +| Problema | Impacto | +|---|---| +| Fiscal rules dentro do Plus | Se trocar de motor (Plus→ERPNext), perde todas as regras | +| Nenhum lançamento contábil automático | DRE fica vazio, contabilidade manual | +| Motor Contábil (:8003) existe mas nada alimenta ele | 0 lançamentos automáticos | +| Plus tem UI própria (/plus) | Usuário pode bypassar o SOE e ir direto ao Plus | +| Sem event bus entre motores | Uma NF-e emitida no Plus não dispara nada no SOE | +| 25 conectores ERP definidos, zero chamadas reais | Integração existe no papel, não funciona | + +### 2.3 O que o usuário vê hoje vs o que deveria ver + +``` +Hoje: + /soe → UI do SOE (tabs: dashboard, pessoas, produtos, vendas...) + /plus → UI do Plus (Laravel) — direto, sem passar pelo SOE + /erp → UI do ERP legado + (usuário VÊ o Plus se quiser) + +Desejado: + /soe → UI do SOE (mesma, mas com inteligência) + /plus → BLOQUEADO ou redirecionado para /soe + /erp → BLOQUEADO ou redirecionado para /soe + (Plus e ERPNext = invisíveis, executando em background) +``` + +--- + +## 3. ARQUITETURA EVOLUÍDA — SOE com Rule Engine + +### 3.1 O que muda (e o que fica igual) + +**NÃO MUDA:** +- `SOE.tsx` — a UI continua a mesma (só ganha funcionalidades) +- `SoeMotorContext.tsx` — seleção de motor continua existindo +- `ArcadiaPlusClient` / `ArcadiaNextClient` — adaptadores continuam +- Todas as rotas `/api/soe/*` — continuam funcionando +- Todos os bancos — PostgreSQL, MySQL, MariaDB — continuam separados + +**MUDA / ADICIONA:** +- Uma **camada de Rule Engine** entre as rotas e os adaptadores +- **Event Bus SOE** que dispara lançamentos contábeis automaticamente +- **Ocultação do Plus** — `/plus` passa a ser controlado pelo SOE +- **Regras fiscais** saem do Plus e entram no SOE + +### 3.2 Diagrama completo com Rule Engine + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ ARCÁDIA SUITE — CAMADA DE APRESENTAÇÃO │ +│ │ +│ SOE.tsx (2.414+ linhas) │ +│ Tabs: Dashboard | Pessoas | Produtos | Vendas | Compras │ +│ Financeiro | CRM | Sincronização | Config │ +│ │ +│ SoeMotorContext.tsx → motor = "plus" | "erpnext" | "local" │ +└───────────────────────────────┬─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ /api/soe/* — CAMADA DE API (já existe) │ +│ registerSoeRoutes() em server/erp/routes.ts │ +│ requireAuth + requireModule middleware │ +└───────────────────────────────┬─────────────────────────────────────┘ + │ + ▼ ◄── INSERIR AQUI (novo) +┌─────────────────────────────────────────────────────────────────────┐ +│ SOE RULE ENGINE (NOVO) │ +│ server/soe/rule-engine/ │ +│ │ +│ Antes de despachar para o motor, o Rule Engine: │ +│ │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ 1. FISCAL RULES (server/soe/rule-engine/fiscal.ts) │ │ +│ │ • Resolve CFOP correto (intra/interestadual) │ │ +│ │ • Aplica tributação por regime (Simples/Presumido/Real) │ │ +│ │ • Valida NCM obrigatório │ │ +│ │ • Define CST/CSOSN automático │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ 2. BUSINESS RULES (server/soe/rule-engine/business.ts) │ │ +│ │ • Aprovação automática de pedidos < R$ X │ │ +│ │ • Reserva de estoque ao confirmar venda │ │ +│ │ • Trigger de NF-e ao faturar pedido │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ 3. ACCOUNTING RULES (server/soe/rule-engine/accounting.ts) │ │ +│ │ • Emitiu NF-e → lançamento automático D: Clientes │ │ +│ │ C: Receita │ │ +│ │ • Pagou conta → lançamento automático D: Fornecedor │ │ +│ │ C: Banco │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +└───────────────────────────────┬─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ DESPACHADOR DE MOTOR (já existe, aprimorado) │ +│ createErpClient(connection) em server/erp/index.ts │ +│ │ +│ motor = "local" → INSERT PostgreSQL │ +│ motor = "plus" → ArcadiaPlusClient → Plus:8080 → MySQL │ +│ motor = "erpnext" → ArcadiaNextClient → ERPNext API → MariaDB │ +└───────────────────────────────┬─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ SOE EVENT BUS (NOVO) │ +│ server/soe/event-bus.ts │ +│ │ +│ Depois que o motor executa, o Event Bus dispara: │ +│ │ +│ evento: "nfe_emitida" → Motor Contábil (lançamento auto) │ +│ evento: "venda_confirmada" → Reserva estoque + alerta WhatsApp │ +│ evento: "pagamento_feito" → Baixa C/P + lançamento contábil │ +│ evento: "recebimento" → Baixa C/R + lançamento contábil │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 4. O QUE CONSTRUIR — Detalhamento por Módulo + +### 4.1 SOE Rule Engine (PRIORIDADE MÁXIMA) + +**Localização:** `server/soe/rule-engine/` + +Este é o coração da evolução. É um middleware que intercepta as chamadas às rotas SOE **antes** de despachar para o motor. + +#### Como funciona na prática + +```typescript +// server/erp/routes.ts — modificação cirúrgica + +// ANTES (atual): +app.post("/api/soe/sales-orders", requireAuth, async (req, res) => { + const client = getActiveClient(req) // pega Plus ou ERPNext + const result = await client.createSalesOrder(req.body) // despacha direto + res.json(result) +}) + +// DEPOIS (com Rule Engine): +app.post("/api/soe/sales-orders", requireAuth, async (req, res) => { + // 1. Aplica regras ANTES de despachar + const enriched = await ruleEngine.apply('pre_sales_order', req.body, req.user) + + // 2. Despacha para o motor com dados enriquecidos + const client = getActiveClient(req) + const result = await client.createSalesOrder(enriched.payload) + + // 3. Dispara eventos DEPOIS da execução + await eventBus.emit('sales_order_created', { result, rules: enriched.appliedRules }) + + res.json(result) +}) +``` + +#### Estrutura do Rule Engine + +```typescript +// server/soe/rule-engine/index.ts + +interface SoeRule { + id: string + dominio: 'fiscal' | 'business' | 'accounting' | 'financial' + trigger: string // ex: 'pre_sales_order', 'post_nfe_emitida' + condicao: RuleCondition // quando esta regra se aplica + acao: RuleAction // o que ela faz ao payload + prioridade: number + ativo: boolean + empresaId?: number // null = regra global de todos os tenants +} + +class SoeRuleEngine { + async apply(trigger: string, payload: any, context: RuleContext): Promise { + // 1. Busca regras ativas para este trigger (do banco + defaults hardcoded) + const rules = await this.getActiveRules(trigger, context.tenantId) + + // 2. Filtra regras cuja condição se aplica ao payload + const applicable = rules.filter(r => this.avaliarCondicao(r.condicao, payload, context)) + + // 3. Aplica cada regra em ordem de prioridade + let enriched = { ...payload } + const applied: string[] = [] + for (const rule of applicable.sort((a, b) => a.prioridade - b.prioridade)) { + enriched = await this.aplicarAcao(rule.acao, enriched, context) + applied.push(rule.id) + } + + return { payload: enriched, appliedRules: applied } + } +} +``` + +### 4.2 Regras Fiscais no SOE + +Estas regras **hoje vivem dentro do Plus** (Laravel controllers). A ideia é trazê-las para o SOE. + +**Nota importante:** Não é mover o código PHP para TypeScript. É criar regras **declarativas** no SOE que enriquecem o payload **antes** de enviar ao Plus. O Plus ainda processa e emite — mas recebe o payload já correto. + +```typescript +// server/soe/rule-engine/fiscal-rules.ts + +export const FISCAL_RULES_PADRAO: SoeRule[] = [ + + // CFOP — resolve automaticamente por operação + { + id: 'CFOP_VENDA_DENTRO_ESTADO', + dominio: 'fiscal', + trigger: 'pre_sales_order', + condicao: { uf_emitente: '==', uf_destinatario: true, tipo_op: 'venda_produto' }, + acao: { set: { 'items[*].cfop': '5.102' } }, + prioridade: 10, ativo: true + }, + { + id: 'CFOP_VENDA_OUTRO_ESTADO', + dominio: 'fiscal', + trigger: 'pre_sales_order', + condicao: { uf_emitente: '!=', uf_destinatario: true, tipo_op: 'venda_produto' }, + acao: { set: { 'items[*].cfop': '6.102' } }, + prioridade: 10, ativo: true + }, + + // Tributação por Regime + { + id: 'CST_SIMPLES_NACIONAL', + dominio: 'fiscal', + trigger: 'pre_nfe_emissao', + condicao: { regime_tributario: 'simples_nacional' }, + acao: { + set: { 'items[*].cst_icms': '400', 'items[*].pis': null, 'items[*].cofins': null }, + // No Simples Nacional: CSOSN 400 = tributado, sem destaque PIS/COFINS + }, + prioridade: 5, ativo: true + }, + { + id: 'CST_LUCRO_PRESUMIDO', + dominio: 'fiscal', + trigger: 'pre_nfe_emissao', + condicao: { regime_tributario: 'lucro_presumido' }, + acao: { set: { 'items[*].cst_pis': '01', 'items[*].cst_cofins': '01' } }, + prioridade: 5, ativo: true + }, + + // Validações obrigatórias + { + id: 'VALIDAR_NCM_NFE', + dominio: 'fiscal', + trigger: 'pre_nfe_emissao', + condicao: { tipo_documento: ['nfe', 'nfce'] }, + acao: { validar: { campo: 'items[*].ncm', obrigatorio: true, formato: /^\d{8}$/ } }, + prioridade: 1, ativo: true + }, +] +``` + +### 4.3 SOE Event Bus — Lançamentos Contábeis Automáticos + +Este é o **maior salto de valor**. Hoje o Motor Contábil (:8003) existe mas nada o alimenta automaticamente. + +```typescript +// server/soe/event-bus.ts + +type SoeEvent = + | 'nfe_emitida' + | 'nfe_cancelada' + | 'venda_confirmada' + | 'compra_recebida' + | 'pagamento_realizado' + | 'recebimento_realizado' + | 'boleto_gerado' + +interface SoeEventPayload { + empresaId: number + tenantId: number + evento: SoeEvent + dados: Record + motorOrigem: 'plus' | 'erpnext' | 'local' +} + +class SoeEventBus { + async emit(evento: SoeEvent, payload: SoeEventPayload) { + // Registra no banco (audit trail) + await db.insert(soeEventos).values({ ...payload, createdAt: new Date() }) + + // Dispara handlers + await Promise.allSettled( + this.getHandlers(evento).map(h => h(payload)) + ) + } + + // Handlers por evento + private handlers: Record = { + 'nfe_emitida': [ + contabilHandler.lancamentoVenda, // D: Clientes / C: Receita Bruta + contabilHandler.lancamentoImpostos, // D: Impostos / C: ICMS/PIS/COFINS a Recolher + financialHandler.gerarRecebivel, // Cria C/R automaticamente + ], + 'pagamento_realizado': [ + contabilHandler.lancamentoPagamento, // D: Fornecedores / C: Banco + financialHandler.baixarContaPagar, // Baixa C/P + ], + 'recebimento_realizado': [ + contabilHandler.lancamentoRecebimento, // D: Banco / C: Clientes + financialHandler.baixarContaReceber, // Baixa C/R + ], + 'compra_recebida': [ + contabilHandler.lancamentoCompra, // D: Estoque / C: Fornecedores + ], + } +} +``` + +#### Lançamentos automáticos para cada evento + +``` +NF-e emitida (venda): + D: 1.1.3.01 — Clientes (valor total da nota) + C: 3.1.1.01 — Receita Bruta de Vendas + + D: 3.1.2.01 — Deduções (ICMS) + C: 2.1.2.01 — ICMS a Recolher + + D: 3.1.2.02 — Deduções (PIS) + C: 2.1.2.02 — PIS a Recolher + + D: 3.1.2.03 — Deduções (COFINS) + C: 2.1.2.03 — COFINS a Recolher + +Pagamento de fornecedor: + D: 2.1.1.{categoria} — Fornecedores + C: 1.1.1.01 — Banco (conta configurada) + +Recebimento de cliente: + D: 1.1.1.01 — Banco + C: 1.1.3.01 — Clientes + +Compra recebida (NF-e entrada): + D: 1.1.4.01 — Estoque / Mercadorias + C: 2.1.1.01 — Fornecedores +``` + +### 4.4 Ocultação do Plus e ERPNext + +O Plus tem página própria em `/plus` (client/src/pages/Plus.tsx). O usuário pode acessá-la diretamente. Precisamos de 3 mudanças: + +#### a) Redirecionar `/plus` para `/soe` + +```typescript +// client/src/App.tsx — modificação simples +// Trocar: +// Por: } /> +``` + +**Mas manter o proxy reverso Plus ativo** — o Plus continua funcionando em background, só não fica visível como página separada. + +#### b) Ocultar o seletor de motor do usuário final + +```typescript +// client/src/contexts/SoeMotorContext.tsx +// O seletor fica SOMENTE na tab Config do SOE, e somente para admins + +// A tab Config na SOE.tsx — adicionar role check: +{user?.role === 'admin' && Config} +``` + +#### c) Na tab Config (admin), o label muda + +``` +Antes: "Selecione o motor: Plus | ERPNext" +Depois: "Motor de execução: Plus | ERPNext | Local" +(invisível para usuário final, só admin de tenant vê) +``` + +--- + +## 5. TABELAS A CRIAR (complemento ao schema existente) + +As tabelas canônicas do SOE já existem. Precisamos adicionar apenas as tabelas do Rule Engine. + +```typescript +// shared/schema.ts — adicionar ao final + +// Regras do SOE — configuráveis por tenant +export const soeRegras = pgTable('soe_regras', { + id: uuid('id').primaryKey().defaultRandom(), + tenantId: integer('tenant_id'), // null = regra global (todas as empresas) + empresaId: integer('empresa_id'), // null = todas as empresas do tenant + dominio: varchar('dominio', { length: 50 }).notNull(), + trigger: varchar('trigger', { length: 100 }).notNull(), + nome: varchar('nome', { length: 200 }).notNull(), + condicao: jsonb('condicao').notNull(), + acao: jsonb('acao').notNull(), + prioridade: integer('prioridade').default(10), + ativo: boolean('ativo').default(true), + origemPadrao: boolean('origem_padrao').default(false), // true = regra default do sistema + createdAt: timestamp('created_at').defaultNow(), + updatedAt: timestamp('updated_at').defaultNow(), +}) + +// Log de todos os eventos SOE (audit trail completo) +export const soeEventos = pgTable('soe_eventos', { + id: uuid('id').primaryKey().defaultRandom(), + tenantId: integer('tenant_id').notNull(), + empresaId: integer('empresa_id'), + evento: varchar('evento', { length: 100 }).notNull(), + motorOrigem: varchar('motor_origem', { length: 50 }), // 'plus', 'erpnext', 'local' + regraIds: jsonb('regra_ids'), // regras aplicadas + payloadEntrada: jsonb('payload_entrada'), + payloadSaida: jsonb('payload_saida'), + status: varchar('status', { length: 50 }).notNull(), + duracaoMs: integer('duracao_ms'), + erro: text('erro'), + createdAt: timestamp('created_at').defaultNow(), +}) + +// Lançamentos contábeis automáticos do SOE +export const soeLancamentos = pgTable('soe_lancamentos', { + id: uuid('id').primaryKey().defaultRandom(), + tenantId: integer('tenant_id').notNull(), + 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(), + origemEvento: varchar('origem_evento', { length: 100 }), // 'nfe_emitida', etc. + origemEventoId: uuid('origem_evento_id'), // FK para soe_eventos + periodo: varchar('periodo', { length: 7 }), // '2026-03' + enviado8003: boolean('enviado_8003').default(false), // sincronizado com Motor Contábil + createdAt: timestamp('created_at').defaultNow(), +}) +``` + +--- + +## 6. ESTRUTURA DE PASTAS (Novo código a criar) + +``` +server/ +└── soe/ ← NOVA PASTA + ├── rule-engine/ + │ ├── index.ts ← SoeRuleEngine class + │ ├── fiscal-rules.ts ← Regras fiscais padrão + │ ├── business-rules.ts ← Regras de negócio + │ └── accounting-rules.ts ← Regras de lançamento contábil + │ + ├── event-bus.ts ← SoeEventBus class + │ + ├── handlers/ + │ ├── contabil-handler.ts ← Lançamentos automáticos + │ └── financial-handler.ts ← Automação financeira + │ + └── fiscal/ + └── cfop-resolver.ts ← Resolução de CFOP (lógica complexa) +``` + +**Modificações em arquivos existentes:** +``` +server/erp/routes.ts ← Inserir ruleEngine.apply() antes do dispatch +client/src/App.tsx ← Redirecionar /plus para /soe +client/src/pages/SOE.tsx ← Ocultar Config tab para não-admins +``` + +--- + +## 7. ROADMAP — Fases de Implementação + +### Fase 1 — Rule Engine Básico (1 semana) +``` +[ ] Criar server/soe/rule-engine/index.ts (SoeRuleEngine) +[ ] Criar tabelas soe_regras e soe_eventos (drizzle push) +[ ] Inserir ruleEngine.apply() em 3 rotas críticas: + - POST /api/soe/sales-orders + - POST /api/soe/sales-orders/:id/generate-nfe + - POST /api/soe/purchase-orders +[ ] Seed das regras fiscais padrão (CFOP, CST, validações NCM) +[ ] Testar: criar pedido → verificar que payload está sendo enriquecido +``` + +### Fase 2 — Event Bus + Contabilidade Automática (1 semana) +``` +[ ] Criar server/soe/event-bus.ts +[ ] Criar tabela soe_lancamentos (drizzle push) +[ ] Criar handlers: contabil-handler.ts + financial-handler.ts +[ ] Conectar: POST generate-nfe → emit('nfe_emitida') → lançamentos +[ ] Conectar: pagamento/recebimento → emit() → lançamentos +[ ] Criar endpoint: GET /api/soe/contabil/lancamentos?periodo=YYYY-MM +[ ] DRE: agora tem dados reais (via soe_lancamentos → Motor :8003) +``` + +### Fase 3 — Ocultar Plus/ERPNext (3 dias) +``` +[ ] client/src/App.tsx: /plus → redirect para /soe +[ ] client/src/pages/SOE.tsx: Config tab só para admins +[ ] Manter proxy Plus funcionando (invisível) +[ ] Testar: fluxo completo sem nenhum acesso direto ao Plus UI +``` + +### Fase 4 — Regras Configuráveis pelo Usuário (1 semana) +``` +[ ] UI na tab Config do SOE: "Regras de Negócio" +[ ] CRUD de regras via /api/soe/rules +[ ] Admin pode criar regras custom sem código +[ ] Exemplo: "Pedidos acima de R$10.000 requerem aprovação manual" +``` + +### Fase 5 — Financeiro Avançado (2 semanas) +``` +[ ] Auto-boleto ao confirmar venda (Asaas API) +[ ] Alertas de vencimento (Manus → WhatsApp) +[ ] Conciliação bancária OFX semi-automática +[ ] Previsão financeira (Motor BI :8004 + histórico soe_lancamentos) +``` + +### Fase 6 — Contábil Avançado (2 semanas) +``` +[ ] DRE automático mensal (100% alimentado pelo Event Bus) +[ ] Balanço Patrimonial automático +[ ] Fechamento contábil mensal +[ ] SPED ECD/ECF alimentado por soe_lancamentos +[ ] Exportação para escritório contábil (OFX, Excel, PDF) +``` + +--- + +## 8. COMPARAÇÃO: PLANO v1 vs PLANO v2 + +| Aspecto | Plano v1 (antes do Replit .md) | Plano v2 (com conhecimento real) | +|---|---|---| +| Adaptadores Plus/ERPNext | "Precisam ser criados" | Já existem — não tocar | +| SOE Routes | "Precisam ser criadas" | 60+ endpoints já existem | +| SOE.tsx | "Precisa ser construída" | 2.414 linhas — já funciona | +| Onde adicionar código | server/soe/ do zero | Middleware nas rotas existentes | +| Esforço estimado | Alto (refatoração total) | Médio (adição cirúrgica) | +| Risco de quebrar | Alto | Baixo (adição, não substituição) | +| Banco Plus | Ignorado | MySQL — separado, nunca tocar direto | + +--- + +## 9. PRINCÍPIO ORIENTADOR (REVISADO) + +> O SOE já é o roteador certo. Agora vamos torná-lo o **árbitro de regras**. +> +> O padrão motor/adaptador está perfeito. O que falta é a **inteligência entre a rota e o adaptador**. +> +> Plus e ERPNext continuam existindo e funcionando — só que o usuário final não sabe. +> Ele só vê a Central SOE. O restante é infraestrutura. + +--- + +## 10. RESUMO EXECUTIVO — O que fazer agora + +**1 ação que entrega valor imediato:** +``` +Criar o SoeRuleEngine e plugar nas rotas de venda/NF-e. +Resultado: Ao emitir uma NF-e, o CFOP correto é calculado +automaticamente pelo SOE — sem o usuário saber que o Plus está +sendo chamado por baixo. +``` + +**1 ação que entrega valor a médio prazo:** +``` +Criar o Event Bus com o handler de contabilidade. +Resultado: Cada NF-e emitida gera lançamentos contábeis +automaticamente. O DRE do mês é gerado sem entrada manual de dados. +``` + +**1 ação que fecha o conceito:** +``` +Redirecionar /plus → /soe. +Resultado: O usuário não tem mais acesso ao Plus diretamente. +Ele só vê o Arcádia. O Plus é infraestrutura invisível. +``` + +--- + +*PLANO_SOE_CENTRAL_v2.md — Arcádia Suite v3.0* +*Gerado em: 2026-03-16 — Baseado na análise real do MAPA_SOE_ARCADIA.md (Replit)* diff --git a/client/src/pages/BiWorkspace.tsx b/client/src/pages/BiWorkspace.tsx index 9d3ae18..77d36c6 100644 --- a/client/src/pages/BiWorkspace.tsx +++ b/client/src/pages/BiWorkspace.tsx @@ -1,5 +1,5 @@ import { BrowserFrame } from "@/components/Browser/BrowserFrame"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; @@ -49,6 +49,7 @@ import { MessageSquare, } from "lucide-react"; import { Textarea } from "@/components/ui/textarea"; +import { SupersetDashboard } from "@/components/SupersetDashboard"; import { Dialog, DialogContent, @@ -81,6 +82,73 @@ import { Cell, } from "recharts"; +// ── Componente da aba Insights (Apache Superset) ────────────────────────────── +function SupersetAdvancedTab() { + const [selectedDashboard, setSelectedDashboard] = useState(""); + const [dashboards, setDashboards] = useState>([]); + + useEffect(() => { + fetch("/api/superset/dashboards", { credentials: "include" }) + .then(r => r.json()) + .then(data => { + if (Array.isArray(data)) { + setDashboards(data.map((d: any) => ({ id: String(d.id), title: d.dashboard_title || d.title }))); + if (data.length > 0) setSelectedDashboard(String(data[0].id)); + } + }) + .catch(() => setDashboards([])); + }, []); + + return ( +
+
+
+

Arcádia Insights — Apache Superset

+

SQL Lab avançado, 50+ tipos de gráfico, dashboards interativos

+
+
+ {dashboards.length > 0 && ( + + )} + + Abrir completo + +
+
+ + {selectedDashboard ? ( + + ) : ( +
+