Compare commits

...

62 Commits

Author SHA1 Message Date
Jonas Pacheco f14fafa531 fix: corrige sessão não persistindo - sameSite cookie + session.save() 2026-04-09 14:55:30 -03:00
Jonas Pacheco 0fad3e05f7 fix: corrige loading infinito no login - pg.Pool + logs debug 2026-04-09 14:16:05 -03:00
Jonas Pacheco 41a9626f20 docs: adiciona análise de gap do Kernel
Documenta o que está implementado vs o que falta:
-  Service Discovery (parcial)
-  Health Monitor
-  Process Manager
-  Log Aggregator
-  Dashboard
-  Load Balancer
-  Circuit Breaker
-  Config Hot Reload
- ⚠️ Resource Limits (básico)
-  Secrets Management

Prioriza implementações críticas para produção.
2026-04-09 10:55:31 -03:00
Jonas Pacheco 0c4a4232bd docs: adiciona aviso de documentação histórica nos planos antigos
Adiciona aviso no topo dos arquivos trazidos da home:
- PLANO_IMPLEMENTACAO_AGENTIC.md
- PLANO_SKILL_FABRIC.md
- INDICE_PLANEJAMENTO.md
- AGENTE_MONITOR_SKILLS.md

Aviso indica que são documentos de Março/2026 e podem
estar desatualizados em relação ao sistema atual.
2026-04-09 10:50:02 -03:00
Jonas Pacheco c2c6a4a2b5 docs: adiciona PLANO_SKILL_FABRIC.md
Plano técnico de implementação do Skill Fabric - sistema de
geração e gestão de skills da Arcádia Suite.
2026-04-09 10:42:18 -03:00
Jonas Pacheco a96c161f14 docs: adiciona documentações de agentes da home do usuário
Traz documentações importantes que estavam em /home/ubuntu/:
- AGENTE_MONITOR_SKILLS.md - Especificação do agente de monitoramento
- INDICE_PLANEJAMENTO.md - Índice do planejamento estratégico
- PLANO_IMPLEMENTACAO_AGENTIC.md - Plano de implementação detalhado
- PLANO_METASET.md - Plano específico do MetaSet/Superset

Evita perda de documentação e mantém tudo centralizado em docs/.
2026-04-09 10:41:47 -03:00
Jonas Pacheco 423bf752db chore: move python-requirements.txt para raiz do projeto
Remove arquivo de configuração da pasta docs/ - não é documentação.
2026-04-09 10:14:51 -03:00
Jonas Pacheco 21e953be83 chore: arquiva documentações antigas (mar 2025) na pasta archive/
Move documentações potencialmente desatualizadas para docs/archive/:
- ANALISE_ATENDEON_XOS.md
- ANALISE_CRM_REQUISITOS.md
- ARCADIA_SUITE_ARQUITETURA.md
- DEPLOY_VPS.md
- INSTALACAO_SERVIDOR.md
- PLANO_IMPLEMENTACAO_RETAIL.md
- XOS_PLANEJAMENTO.md

Mantém na pasta docs/ apenas documentações recentes e relevantes:
- MAPA_*.md (atualizados mar 2026)
- PLANO_SOE.md (consolidado)
- PLANO_BI_SUPERSET.md (atualizado)
- KERNEL_SPEC.md, SPEC_NAVBAR.md (specs técnicas)
- AGENTS.md, CLAUDE.md (referência atual)

Reduz confusão para IA - apenas documentação atualizada na pasta principal.
2026-04-09 10:14:17 -03:00
Jonas Pacheco 2807a9d9dd chore: remove planos SOE duplicados e renomeia para versão única
Remove arquivos duplicados/confusos:
- PLANO_SOE_CENTRAL.md (duplicado)
- PLANO_SOE_CENTRAL-ignorar.md (duplicado)
- PLANO_SOE_CENTRAL_v2-concluido-testar.md (duplicado)

Renomeia:
- PLANO_SOE_CENTRAL_v2.md → PLANO_SOE.md (único arquivo oficial)

Evita confusão da IA no futuro - Single Source of Truth para plano SOE.
2026-04-09 10:12:46 -03:00
Jonas Pacheco 5d9c034a42 chore: organiza documentação na pasta docs/
Move 27 arquivos .md da raiz para docs/:
- AGENTS.md
- ARCADIA_AUDIT_EXECUCAO.md
- AUDITORIA_XOS_v1.0.md
- AVALIACAO_IMPACTO_GEOLOGIA.md
- CLAUDE.md
- DEPLOY_COOLIFY.md
- DOCUMENTATION.md
- HOSPEDAGEM.md
- INTEGRACAO_IA.md
- MAPA_*.md (10 arquivos)
- PLANO_*.md (5 arquivos)
- RELATORIO_TECNICO_RETAIL.md
- REQUISITOS_TECNICOS_ARCADIA.md
- replit.md

Mantém na raiz apenas README.md
2026-04-09 10:09:01 -03:00
Jonas Pacheco e27f2998e5 feat: adiciona pré-compressão gzip no build
- Instala vite-plugin-compression2 para gerar .gz no build
- Configura Express para servir arquivos .gz quando disponível
- Reduz transferência em ~70% para assets principais

Agent.js: 33KB → 9.9KB (gzip)
index.js: 348KB → 108KB (gzip)
2026-04-09 09:20:05 -03:00
Jonas Pacheco 58efb8c95c fix: reativa gzip no Express com threshold de 1KB
Caddy/Traefik não estavam comprimindo os assets.
Reativando gzip no Express com threshold de 1KB
para garantir compressão de todos os arquivos JS/CSS.
2026-04-09 09:12:41 -03:00
Jonas Pacheco cfb60b2d4b fix: remove gzip do Express para evitar conflito com Traefik
O Traefik (coolify-proxy) já gerencia o gzip compression.
Removendo do Express para evitar conflito e dupla compressão.
2026-04-09 09:07:51 -03:00
Jonas Pacheco 4dcecd3e63 perf: otimizações agressivas de bundle e carregamento
- Configura manualChunks no Vite: vendor, ui, charts, export
- Agent.tsx: de 893KB → 33KB (redução de 96%)
- Charts e export libs: carregamento lazy sob demanda
- Adiciona modulepreload hints automáticos no HTML
- Gzip compression ativado no Express

Resultado: Carregamento inicial ~10x mais rápido
2026-04-08 15:40:24 -03:00
Jonas Pacheco a167d9aaef perf: adiciona gzip compression e otimiza carregamento
- Adiciona middleware compression para servir assets gzipados
- Configura cache de 1 ano para assets estáticos (hash no filename)
- Remove imports pesados do Agent.tsx (jspdf, docx, recharts)
- Cria ChartRenderer com lazy loading para gráficos
- Cria export-utils com dynamic imports para PDF/Word/CSV

Reduz o chunk Agent de 893KB para 690KB (gzip 220KB).
2026-04-08 15:38:16 -03:00
Jonas Pacheco 6bd0ea6d4f chore: remove baseUrl deprecated do tsconfig.json
Remove a opção baseUrl que está obsoleta no TypeScript 5.0+.
Os paths @/* e @shared/* continuam funcionando normalmente
com moduleResolution: bundler.
2026-04-08 15:27:04 -03:00
Jonas Pacheco b037512250 fix: resolve loading infinito na navegação entre módulos
- Adiciona ErrorBoundary para capturar erros do React.lazy()
- LoadingFallback com timeout: aviso aos 3s, erro aos 10s
- Atualiza documentação (SPEC_NAVBAR.md, AGENTS.md)

O Suspense não captura falhas no lazy loading. Agora o ErrorBoundary
envolve o Suspense e exibe UI de fallback quando há erros.
2026-04-08 15:25:28 -03:00
Jonas Pacheco 317926f600 docs: adiciona seção de otimização .dockerignore no DEPLOY_COOLIFY.md
- Documenta estrutura atual do .dockerignore
- Explica o que é excluído vs incluído
- Mostra métricas de melhoria (5GB → 93MB)
- Adiciona alerta sobre assets necessários para build
2026-04-08 14:00:46 -03:00
Jonas Pacheco 2222e78999 fix: inclui attached_assets no contexto de build
- Remove attached_assets/ do .dockerignore (arquivos necessários para build)
- Mantém exclusão de docs/ e *.md
- O arquivo arcadia_suite_icon.png é necessário pelo BrowserFrame.tsx
2026-04-08 13:55:48 -03:00
Jonas Pacheco f623ee20d8 fix: otimiza .dockerignore para reduzir contexto de build
- Adiciona server/bi/metaset/superset-src/ ao .dockerignore (3.7GB)
- Adiciona server/bi/metaset/venv/ ao .dockerignore
- Remove arquivos desnecessários: docs, *.md, __pycache__, etc
- Reduz contexto de build de ~5GB para ~1GB
- Deve melhorar significativamente o tempo de deploy
2026-04-08 13:51:55 -03:00
Jonas Pacheco 1989928329 fix: remove dependência db/index do metaset-client
- Remove import de ../../db/index que quebrava o build
- Simplifica getAutoSuggestions para usar heurísticas em vez de schema query
- Mantém compatibilidade com API existente
2026-04-08 13:38:52 -03:00
Jonas Pacheco a6ac6e9dd9 docs: atualiza specs BI para refletir consolidação MetaSet
Atualiza documentação para refletir a migração Metabase → Apache Superset:

- docs/KERNEL_SPEC.md:
  - Adiciona capability 'embedding' ao MetaSet
  - Atualiza histórico de mudanças (v3.4)

- PLANO_BI_SUPERSET.md:
  - Reescrito como documentação de arquitetura consolidada
  - Adiciona estrutura de arquivos atualizada
  - Documenta API endpoints do novo cliente
  - Inclui exemplos de uso do cliente TypeScript

- MAPA_BI_ARCADIA.md:
  - Substitui todas referências Metabase → MetaSet
  - Atualiza portas: 8088 → 8100
  - Atualiza rotas: /metabase/* → /bi/metaset/*
  - Adiciona server/bi/metaset-client/ aos arquivos principais
2026-04-08 13:33:21 -03:00
Jonas Pacheco a7ecf0defb consolidate: MetaSet BI - migração Metabase → Apache Superset
- Cria novo cliente TypeScript em /server/bi/metaset-client/
  - index.ts: Cliente completo para API Superset v1
  - routes.ts: Rotas Express /api/bi/metaset/*

- Move código legado Metabase para /server/metaset/backup/
  - client.ts → backup/client.ts
  - routes.ts → backup/routes.ts

- Atualiza imports em:
  - server/autonomous/tools/metaset.ts
  - server/autonomous/tools/index.ts
  - server/manus/service.ts
  - server/bi/routes.ts

- Adiciona documentação consolidada em /server/metaset/README.md

Funcionalidades do novo cliente:
- Health check, Databases, Tables
- SQL queries com validação de segurança
- Charts (CRUD), Dashboards (CRUD)
- Guest tokens para embedding
- Auto-suggestions baseado em schema
2026-04-08 13:21:34 -03:00
Jonas Pacheco 95ee51bf68 docs: atualiza MAPA_SISTEMA_ARCADIA.md com Kernel (porta 5001)
- Adiciona porta 5001 (Arcadia Kernel) ao mapa de portas
- Documenta que Kernel roda no mesmo container da app
- Explica acesso via memória (Registry Global)
- Adiciona seção explicativa sobre o Kernel
2026-04-08 12:42:20 -03:00
Jonas Pacheco 9ec61dd932 docs: atualiza KERNEL_SPEC.md com Registry Global
- Atualiza versão para 3.3 (Produção)
- Documenta nova arquitetura com Registry Global
- Adiciona diagrama de fluxo de dados atualizado
- Documenta vantagens do acesso direto vs HTTP
- Atualiza roadmap da Semana 5
2026-04-08 12:34:44 -03:00
Jonas Pacheco bb7cff6c80 fix: Casa de Máquinas usa Registry global em vez de HTTP
O problema era que a Casa de Máquinas (porta 5000) não conseguia
conectar ao Kernel (porta 5001) via HTTP dentro do mesmo container.

Solução:
- Exporta o ServiceRegistry globalmente no server/index.ts
- Casa de Máquinas prioriza o Registry global (mesmo processo)
- Fallback para HTTP apenas se Registry global não disponível

Isso elimina a necessidade de loopback HTTP interno.
2026-04-08 12:27:21 -03:00
Jonas Pacheco da371af0e0 fix: tenta múltiplos hostnames para conectar ao Kernel
Tenta 127.0.0.1 e localhost para conectar ao Registry do Kernel.
Adiciona logs detalhados para debug.
2026-04-08 12:17:35 -03:00
Jonas Pacheco 524a049d03 fix: usa localhost em vez de 127.0.0.1 para conectar ao Kernel
No container Docker, 127.0.0.1 pode não resolver corretamente
para o próprio container. localhost é mais confiável.
2026-04-08 12:11:11 -03:00
Jonas Pacheco 71e2b654bd fix: resolve race condition entre Kernel e Casa de Máquinas
- Move início do Kernel ANTES do servidor HTTP (porta 5000)
- Adiciona retry com exponential backoff na consulta ao Registry
- Garante que Registry esteja pronto antes de receber requisições

Fixes: Casa de Máquinas mostrava 0/0 serviços porque API
chamava Registry antes do Kernel iniciar na porta 5001
2026-04-08 12:03:55 -03:00
Jonas Pacheco ea58a45ea1 fix(engine-room): usa http.get em vez de fetch
O fetch do Node.js estava falhando com loopback.
Trocado para http.get nativo que é mais confiável.

Refs: engine-room, registry, fix
2026-04-08 11:53:08 -03:00
Jonas Pacheco b0a8e73f67 fix(engine-room): usa 127.0.0.1 e adiciona logs
- Troca localhost por 127.0.0.1 (mais confiável)
- Adiciona logs para debugar chamadas ao Registry

Refs: engine-room, registry, debug
2026-04-08 11:47:21 -03:00
Jonas Pacheco fcfce87df8 fix(coolify): remove filtro de labels no CoolifyDiscovery
O Coolify não expõe os labels do Docker na API.
Removido o filtro que exigia 'arcadia.discovery.enabled=true'.
Agora todos os serviços do Coolify são registrados.

Refs: coolify, discovery, fix
2026-04-08 11:40:26 -03:00
Jonas Pacheco a5adf7e096 debug: adiciona logs detalhados no CoolifyDiscovery
Adiciona logs para debugar por que a API do Coolify
está retornando 0 serviços.

Refs: debug, coolify, discovery
2026-04-08 11:34:34 -03:00
Jonas Pacheco 6182a9c490 chore: limpa arquivos temporários e cache
Remove:
- Cache do aider (.aider.tags.cache.v4)
- Arquivos de planejamento antigos (.planning/)
- Arquivos duplicados (MAPA_TENANTS_ARCADIA copy, SESSAO_2026-03-18)
- Metadata de agentes (.agents/)

Refs: cleanup, maintenance
2026-04-08 11:26:56 -03:00
Jonas Pacheco 14e440cc1f fix(docker): expoe porta 5001 para o Kernel
Adiciona porta 5001 no serviço app para permitir
acesso ao Kernel Arcadia (Service Registry).

Refs: kernel, registry, docker
2026-04-08 11:24:32 -03:00
Jonas Pacheco 202101f0e0 refactor(engine-room): simplifica Casa de Máquinas - apenas Registry
REMOVIDO:
- Presets ENGINES (hardcoded)
- Funções checkEngineHealth, getEngineHealthUrl
- Imports de kernel-adapter (não usados mais)
- Rotas de ação (start/stop/restart) - retornam 501
- Fallback para kernel-adapter

MANTIDO:
- /api/engine-room/status (apenas do Registry)
- /api/engine-room/engines (apenas do Registry)
- /api/engine-room/agents
- Rotas de agents (start/stop)

A Casa de Máquinas agora mostra APENAS serviços descobertos
pelo Registry (Docker/Coolify/XOS). Serviços não descobertos
não aparecem.

Refs: simplificacao, registry-only, casa-de-maquinas
2026-04-08 11:18:03 -03:00
Jonas Pacheco 16bb5b9bfb docs: adiciona especificação técnica do Kernel
Cria docs/KERNEL_SPEC.md com:
- Visão geral e arquitetura
- Componentes (Core, Registry, Discovery)
- Documentação dos 4 providers de discovery
- API endpoints (Core, Registry, Casa de Máquinas)
- Fluxo de integração Casa de Máquinas
- Configuração (env vars, Docker labels)
- Roadmap (Semanas 4-7)
- Histórico de mudanças

Refs: documentation, kernel, spec
2026-04-08 11:06:22 -03:00
Jonas Pacheco b3166173df feat(kernel): implementa XOS Discovery Provider
- Cria XOSDiscovery.ts para descobrir serviços XOS
- Descobre filas de atendimento ativas
- Descobre integrações (WhatsApp, Email)
- Descobre automações ativas
- Integra ao Kernel como provider de discovery

O XOS Discovery consulta o banco de dados para encontrar
serviços configurados no XOS (CRM/Atendimento/Marketing).

Refs: xos, discovery, kernel-semana-5
2026-04-08 11:00:14 -03:00
Jonas Pacheco aa83dbdbce feat(engine-room): unifica Casa de Máquinas com Registry do Kernel
- Adiciona fetchRegistryServices() para consultar /api/registry/services
- Adiciona mapRegistryToEngine() para converter formato do Registry
- Modifica /api/engine-room/status para usar Registry como fonte primária
- Evita duplicação: se Registry tem serviços, não adiciona presets duplicados
- MetaSet só é adicionado se não houver bi-engine (evita 2 Motores BI)
- Fallback para kernel-adapter se Registry não disponível

Isso unifica a visão da Casa de Máquinas com o Discovery automático.

Refs: kernel, registry, casa-de-maquinas, unificacao
2026-04-08 10:51:19 -03:00
Jonas Pacheco 015273ffdd fix(engine-room): usa valores do preset ENGINES em vez de hardcoded
- Corrige MetaSet: usa type, displayName, category, description do preset
- Corrige Plus: usa valores do preset em vez de hardcoded
- Remove 'java' hardcoded que sobrescrevia o preset correto

Isso garante que as correções feitas no preset ENGINES
(type: python, porta: 8100) sejam refletidas na API.

Refs: metaset, casa-de-maquinas, engine-room
2026-04-08 10:01:29 -03:00
Jonas Pacheco 13abdbba3b fix(engine-room): corrige preset do MetaSet para apontar para serviço novo
- Atualiza porta: 8088 → 8100 (porta correta do container metaset)
- Atualiza tipo: java → python (Superset é Python)
- Atualiza host: SUPERSET_HOST → METASET_HOST
- Adiciona METASET_HOST e METASET_PORT no serviço app
- Atualiza descrição para refletir o MetaSet (fork Arcadia)

O preset estava apontando para o Apache Superset legado (porta 8088)
que só roda com perfil [bi]. Agora aponta corretamente para o
MetaSet novo (porta 8100) que é o serviço padrão.

Refs: metaset, casa-de-maquinas
2026-04-08 09:51:01 -03:00
Jonas Pacheco 5c884d9066 feat(kernel): implementa Coolify Discovery com chamadas reais à API
- Implementa CoolifyDiscovery.discover() buscando serviços e aplicações
- Adiciona endpoints /api/v1/services e /api/v1/applications
- Suporte a filtro por label arcadia.discovery.enabled=true
- Parse de labels customizadas do Coolify
- Mapeamento de health status do Coolify para Registry
- Adiciona 'coolify' ao tipo DiscoverySource

Refs: kernel-semana-4, service-discovery
2026-04-08 09:41:09 -03:00
Jonas Pacheco 465775ec99 feat(bi): MetaSet (Apache Superset fork) com branding ArcadiaSuite
- Rebuild completo do frontend React com branding MetaSet
- Substituição de textos 'Superset' -> 'MetaSet' em todo codebase
- Traduções atualizadas (en, pt_BR)
- Security Manager com JWT validation e RLS preparado
- Dockerfile multi-stage com assets locais
- Deploy em https://bi.onboardbi.com.br
- Banco de dados PostgreSQL migrado e inicializado
- Container Docker com rede coolify + extra_hosts IPv4
- BI-API Gateway em server/bi/index.ts (proxy manual Node.js)
- FDB-Bridge skeleton em server/bi/fdb-bridge/
- Kernel adapter atualizado para Registry integration
- .gitignore atualizado para excluir superset-src (3.7GB)
2026-04-07 16:04:09 -03:00
Jonas Pacheco 1f23256032 feat(kernel): expõe rotas do Registry no Dashboard
- Adiciona ServiceRegistry no DashboardServer
- Cria rotas /api/registry/services, /stats, /:id
- Permite acessar serviços descobertos via API
- Integração completa Registry -> Dashboard
2026-04-06 17:12:55 -03:00
Jonas Pacheco 1cf73f55ba feat(kernel): adiciona CoolifyDiscovery para descoberta automática
- Cria CoolifyDiscovery.ts para integrar com API Coolify
- Adiciona token COOLIFY_API_TOKEN no docker-compose
- Integra com StaticDiscovery e DockerDiscovery
- Permite descoberta automática de novos containers
2026-04-06 16:43:55 -03:00
Jonas Pacheco c3c91ed966 feat(kernel): adiciona StaticDiscovery via DNS interno
- Cria StaticDiscovery.ts com lista dos 7 serviços Arcádia
- Descoberta via nome DNS interno (ex: arcadia-prod-contabil-1)
- Health check em cada serviço antes de registrar
- Funciona sem acesso ao Docker (mais seguro)
- Mantém DockerDiscovery como fallback
2026-04-06 16:02:26 -03:00
Jonas Pacheco 5a99236aff feat(kernel): adiciona proxy /kernel para Dashboard
- Instala http-proxy-middleware
- Configura proxy /kernel -> localhost:5001
- Permite acessar Kernel pelo mesmo domínio da aplicação
- WebSocket support habilitado
2026-04-06 15:30:32 -03:00
Jonas Pacheco 08d6820e76 fix(kernel): ativa KERNEL_ENABLED e KERNEL_AUTOSTART no docker-compose
Adiciona variáveis de ambiente necessárias para iniciar o Kernel Arcádia
com Service Discovery habilitado por padrão.

- KERNEL_ENABLED=true: Ativa o Kernel no server/index.ts
- KERNEL_AUTOSTART=true: Inicia serviços automaticamente
2026-04-06 11:15:06 -03:00
Jonas Pacheco 7651167d72 feat(kernel): implementa Service Discovery e Registry (Semana 4)
Adiciona módulos de Service Discovery ao Kernel Arcádia:

- ServiceRegistry: catálogo central de serviços com health check
- DockerDiscovery: descoberta automática via labels Docker
- API REST /api/registry/* para consulta de serviços
- Labels arcadia.* no docker-compose.yml para auto-registro
- Integração não-destrutiva (módulos novos, código antigo preservado)

Labels suportadas:
- arcadia.discovery.enabled=true
- arcadia.name, arcadia.type, arcadia.category
- arcadia.port, arcadia.capabilities
- arcadia.requires_ia, arcadia.ia_policies

Arquitetura: MODIFICAR (adicionar) ao invés de REFATORAR
2026-04-06 11:02:49 -03:00
Jonas Pacheco 2916fb04ae fix: endpoint de health correto do Superset (/health não /api/health) 2026-04-03 18:15:45 -03:00
Jonas Pacheco d971d07327 debug: log URL em runtime para diagnosticar timeout 2026-04-03 18:07:33 -03:00
Jonas Pacheco 06fe8af1fb fix(engine-room): URLs corretas para health check de containers externos
- Adiciona getEngineHealthUrl() para resolver URLs dinamicamente
- MetaSet (Superset) usa SUPERSET_HOST ou 'superset' por padrão
- Plus usa PLUS_HOST ou 'localhost' por padrão
- Resolve problema de localhost:8088 falhar para Superset
2026-04-03 17:40:01 -03:00
Jonas Pacheco 80f9352a7e fix(kernel-adapter): variáveis de ambiente em runtime não build time
- Transforma SERVICE_URLS (objeto estático) em getServiceUrl() (função)
- Garante que process.env.CONTABIL_PYTHON_URL seja lida em runtime
- Resolve problema de URL 'bakeada' no build retornar valor errado
2026-04-03 17:38:45 -03:00
Jonas Pacheco 162bc4f943 force: rebuild docker cache [1775243617] 2026-04-03 16:13:37 -03:00
Jonas Pacheco 1fdeb72492 fix(engine-room): suporte a modo Docker para consultar serviços externos
- Modifica kernel-adapter.ts para detectar DOCKER_MODE
- Em modo Docker, consulta serviços via HTTP health check direto
- Em modo normal, continua usando Kernel local (porta 5001)
- Resolve problema de apenas 2 motores aparecendo em producao
2026-04-03 12:19:58 -03:00
Jonas Pacheco 5ba2bd963d fix(build): compatibilidade CommonJS para Kernel
- Adiciona fallback para import.meta.url em builds do Docker
- Protege entry point do Kernel para não crashar em CommonJS
- DashboardServer.ts agora funciona em ESM e CommonJS
- index.ts com try-catch em torno de import.meta.url

Isso corrige o erro: ERR_INVALID_ARG_TYPE no build do Coolify

Refs: Deploy produção com Kernel
2026-04-02 18:00:10 -03:00
Jonas Pacheco 75953a8542 fix(deploy): remove miroflow submodule (broken ref)
- Remove server/modules/miroflow do .gitmodules
- Permite deploy no Coolify sem erro de submodule
- MiroFlow pode ser re-adicionado depois com ref correta

Refs: Deploy com Kernel
2026-04-02 17:55:09 -03:00
Jonas Pacheco 2e76496ff4 feat(kernel): integração Engine Room com Arcadia Kernel
MIGRAÇÃO COMPLETA: Sistema antigo → Kernel Nativo (porta 5001)

📁 Arquivos alterados:
- server/engine-room/kernel-adapter.ts (NOVO)
- server/engine-room/routes.ts
- server/kernel/config/services.json

🔧 O que mudou no Kernel (services.json):
+ Adicionado: python-fisco (porta 8002)
+ Corrigido: node-communication (8006, era 9001)
+ Adicionado: node-core-api (9002)
+ Adicionado: node-worker (9003)
- Removido: java-metaset (gerenciado externamente)

Total: 6 → 7 serviços gerenciados pelo Kernel

🔄 Engine Room - Novo comportamento:
- Todas as rotas /api/engine-room/* agora usam Kernel Adapter
- Sistema antigo (managedServices) mantido em comentário para fallback
- Verificação automática se Kernel está disponível
- Retorna erro 503 se Kernel não responde na porta 5001

📊 Mapeamento de serviços:
Engine Room          → Kernel ID
────────────────────────────────────────
contabil             → python-contabil
bi-engine            → python-bi
automation-engine    → python-automation
fisco                → python-fisco
communication        → node-communication

🚫 Serviços externos (não gerenciados pelo Kernel):
- plus (PHP/Laravel, porta 8080)
- metaset (Java, porta 8088)

 Benefícios:
- Auto-restart configurável por serviço
- Health checks automáticos
- Logs centralizados com timestamps
- WebSocket para atualizações em tempo real
- Dashboard na porta 5001

⚠️ Pré-requisito:
KERNEL_ENABLED=true npm run dev

Refs: Semana 4 - Integração Kernel com Engine Room
Breaking Change: Engine Room requer Kernel rodando
2026-04-02 16:47:28 -03:00
Jonas Pacheco 7ce8735c04 feat(kernel): adiciona dashboard HTML visual
- Adiciona interface web dark theme com cards de estatísticas
- Lista de serviços com status, health e ações (start/stop/restart)
- Console de logs em tempo real com filtro por serviço
- WebSocket para atualizações em tempo real
- Auto-refresh a cada 5 segundos

Refs: Dashboard Arcadia Kernel
2026-04-02 14:46:36 -03:00
Jonas Pacheco 849934dae8 feat(kernel): implementação completa do Kernel Nativo - Semana 3
Kernel Arcadia - Sistema nativo de gerenciamento de serviços:

Core:
- ProcessManager: spawn, monitor, restart automático de serviços
- HealthMonitor: HTTP health checks com circuit breaker
- LogAggregator: coleta e streaming de logs

API & Dashboard:
- API REST /api/kernel/* para controle dos serviços
- Dashboard Web na porta 5001
- WebSocket /ws/kernel para updates em tempo real

Serviços configurados (6):
- Python: Contabil (8003), BI Engine (8004), Automation (8005)
- Node.js: Communication (9001), Core API (9002), Worker (9003)

Integração:
- server/index.ts integrado com flag KERNEL_ENABLED
- Build passando, pronto para testes

Uso:
  KERNEL_ENABLED=true npm run dev
  Acesse http://localhost:5001 para o dashboard
2026-04-02 10:40:46 -03:00
Jonas Pacheco 201580a47a feat(rls): migrações Row Level Security - Semana 2
- 001_enable_rls.sql: habilita RLS em 31 tabelas tenant-isoladas
- 002_create_policies.sql: 124 políticas CRUD por tenant
- 003_tenant_context.sql: funções, triggers e views de contexto
- scripts/apply-rls-migrations.ts: automação de aplicação
- package.json: script npm run db:apply-rls

Tabelas protegidas: workspace, crm, process compass, low-code, multi-tenancy core
2026-04-02 09:58:56 -03:00
Jonas Pacheco a2aff30b8c security: correcoes semana 1 - session secret, CORS, filtro tenant, protecao /api/tenants
- Session secret com randomBytes(32), obrigatorio em producao
- CORS whitelist configurado (origens permitidas)
- getUserByUsername com validacao opcional de tenantId
- /api/tenants restrito: admin ve todos, user ve so seu tenant
- Metodo getTenant(id) adicionado a interface IStorage
2026-04-01 17:34:23 -03:00
202 changed files with 14946 additions and 7347 deletions

View File

@ -1,38 +0,0 @@
uploads = []
generated = []
[[outputs]]
id = "28NDwt4UT3HPglsSw-IQk"
uri = "file://MAPA_MICROSERVICOS_COOLIFY.md"
type = "text"
title = "Mapa de Microserviços - Arcádia Suite para Coolify/Docker"
[[outputs]]
id = "YTVCn39myPk5kzF8smbQz"
uri = "file://MAPA_SOE_ARCADIA.md"
type = "text"
title = "Mapa SOE — Sistema Operacional Empresarial (Deploy)"
[[outputs]]
id = "pQjx1X6m9hp2j-sZa0_Rg"
uri = "file://MAPA_BI_ARCADIA.md"
type = "text"
title = "Mapa BI - Business Intelligence da Arcádia Suite (com Metabase)"
[[outputs]]
id = "gpyYTqB0ytdU1zAJij6KK"
uri = "file://MAPA_CRM_ARCADIA.md"
type = "text"
title = "Mapa CRM - Sistema de Relacionamento da Arcádia Suite"
[[outputs]]
id = "zal1KfcZdP7OqKEXu67Pp"
uri = "file://MAPA_TENANTS_ARCADIA.md"
type = "text"
title = "Mapa Multi-Tenant - Gestão de Tenants da Arcádia Suite"
[[outputs]]
id = "GCMgcBAHQ1aIDjHxoXniQ"
uri = "file://MAPA_AUTOMACOES_ARCADIA.md"
type = "text"
title = "Mapa Automações - Motor de Automação da Arcádia Suite"

Binary file not shown.

View File

@ -9,3 +9,39 @@ 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/

View File

@ -27,7 +27,11 @@ CONTABIL_PYTHON_URL=http://localhost:8003
BI_PYTHON_URL=http://localhost:8004
AUTOMATION_PYTHON_URL=http://localhost:8005
FISCO_PYTHON_URL=http://localhost:8002
PYTHON_SERVICE_URL=http://localhost:8001
# ── Embeddings / Learning Service ──────────────────────────────────────────────
# IMPORTANTE: Usar porta 8000 (não 8001) - o serviço escuta na porta 8000
# Em Docker: http://arcadia-prod-embeddings-1:8000
# Em dev local: http://localhost:8000
PYTHON_SERVICE_URL=http://localhost:8000
# ── IA — OpenAI ───────────────────────────────────────────────────────────────
# Deixe vazio se usar apenas Ollama (soberania total)

9
.gitignore vendored
View File

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

3
.gitmodules vendored
View File

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

View File

@ -1,475 +0,0 @@
# Fase 4: OpenClaw Embutido — Plano de Implementação
**Fase:** 4
**Data Início:** 2026-03-26
**Prazo:** 2026-04-08 (Semanas 7-8)
**Status:** 🔄 Sprint 3 Pendente (Sprint 1 e 2 concluídos)
---
## 1. OBJETIVO DA FASE
Implementar **detecção proativa de padrões** e **sugestão emergente de skills**, transformando comportamentos repetitivos do usuário em automações reutilizáveis através de um widget conversacional flutuante.
### Entregável Final
✅ Skills emergentes funcionando end-to-end:
- Usuário executa ação repetida → PatternDetector detecta → OpenClawWidget sugere → Blackboard gera DRAFT → Dev Center aprova → Skill disponível
---
## 2. ARQUITETURA
```
FLUXO OPENCCLAW:
1. USER INTERACTION
└─ Usuário usa Arcádia
└─ Learning API registra evento (já existe)
└─ Neo4j armazena interação
2. PATTERN DETECTION (NOVO)
└─ PatternDetector analisa Learning API
└─ Identifica: 3+ ocorrências em 30 dias
└─ Calcula confiança (threshold 0.8+)
└─ Dispara evento no Socket.IO
3. WIDGET SUGGESTION (NOVO)
└─ OpenClawWidget fica "ouvindo"
└─ Recebe evento de padrão detectado
└─ Exibe notificação flutuante
└─ Modal SkillSuggestion mostra preview
4. SKILL GENERATION (NOVO)
└─ Usuário confirma
└─ OpenClawEngine chama Blackboard
└─ Blackboard gera código (DRAFT)
└─ Armazena em database
5. APPROVAL FLOW (EXISTENTE - Dev Center)
└─ Skill DRAFT aparece em Dev Center
└─ Admin/Lead revisa e aprova
└─ Publica no tenant
└─ Skill fica disponível para reutilização
```
### Componentes para Implementar
```
BACKEND:
server/modules/openclaw/
├── PatternDetector.ts ← [NOVO] Detecta padrões
├── SkillEmergence.ts ← [NOVO] Coordena criação
├── OpenClawEngine.ts ← [NOVO] Lógica central
├── config/
│ └── arcadia.config.yaml ← [NOVO] Configuração
FRONTEND:
client/src/components/openclaw/
├── OpenClawWidget.tsx ← [NOVO] Widget flutuante
├── PatternDetector.ts ← [NOVO] Hook de detecção
├── SkillSuggestion.tsx ← [NOVO] Modal de sugestão
└── useAgentEmergence.ts ← [NOVO] Lógica de emergência
```
---
## 3. DEPENDÊNCIAS VERIFICADAS
| Componente | Status | Localização |
|-----------|--------|-----------|
| **Skills Engine** | ✅ Pronto | `server/skills/engine.ts` |
| **Blackboard** | ✅ Pronto | `server/blackboard/service.ts` |
| **Dev Center** | ✅ Pronto | `client/src/pages/DevCenter.tsx` |
| **Learning API** | ✅ Existe | `/api/learning/*` |
| **Neo4j (KG)** | ✅ Running | Docker |
| **Socket.IO** | ✅ Configurado | `server/socket-io.ts` |
| **PostgreSQL** | ✅ Rodando | Docker |
| **Scientist (MiroFlow)** | ✅ Existe | `/api/manus/scientist/*` |
**Conclusão:** Todas as dependências estão prontas ✅
---
## 4. TASKS DETALHADAS
### SPRINT 1 (26-27/03): PatternDetector Backend — ✅ COMPLETO
#### Task 1.1: Criar PatternDetector.ts ✅
**Arquivo:** `server/modules/openclaw/PatternDetector.ts`
**Objetivo:** Analisar eventos de usuário e detectar padrões
```typescript
// Pseudocódigo da estrutura
export class PatternDetector {
// Config
min_occurrences: 3;
time_window_days: 30;
confidence_threshold: 0.8;
// Métodos
detectPattern(userId: string, action: string): Pattern | null
calculateConfidence(interactions: Interaction[]): number
storePattern(pattern: Pattern): Promise<void>
emitEvent(pattern: Pattern): void
}
```
**Entrada:** Eventos do Learning API (`/api/learning/interactions`)
**Saída:** Pattern objects armazenados em Neo4j + Socket.IO event
**Dependência:** Learning API funcionando ✅
#### Task 1.2: Criar OpenClawEngine.ts ✅
**Arquivo:** `server/modules/openclaw/OpenClawEngine.ts`
**Objetivo:** Orquestração central de OpenClaw
```typescript
export class OpenClawEngine {
patternDetector: PatternDetector;
skillEmergence: SkillEmergence;
// Métodos
async suggestSkill(pattern: Pattern): Promise<SkillSuggestion>
async onUserConfirmation(suggestion: SkillSuggestion): Promise<Skill>
async checkPatternsPeriodicly(): Promise<void>
}
```
**Entrada:** Padrões detectados
**Saída:** Sugestões + Skills gerados
**Evento:** Socket.IO broadcast `/openclaw/pattern-detected`
#### Task 1.3: Criar SkillEmergence.ts ✅
**Arquivo:** `server/modules/openclaw/SkillEmergence.ts`
**Objetivo:** Integração com Blackboard para codegen
```typescript
export class SkillEmergence {
async generateSkillDraft(pattern: Pattern): Promise<SkillDraft> {
// 1. Montar prompt baseado no padrão
// 2. Chamar POST /api/blackboard/generate-skill
// 3. Receber código gerado
// 4. Salvar como DRAFT no database
// 5. Notificar Dev Center
// 6. Retornar skill DRAFT
}
}
```
**Integração:** POST `/api/blackboard/generate-skill`
**Database:** skills table (com status: 'draft')
#### Task 1.4: Criar rotas OpenClaw ✅
**Arquivo:** `server/modules/openclaw/routes.ts`
**Rotas:**
```
POST /api/openclaw/detect-patterns ← Trigger manual de análise
GET /api/openclaw/patterns ← Listar padrões detectados
POST /api/openclaw/confirm-skill ← Usuário confirma sugestão
GET /api/openclaw/suggestions ← Histórico de sugestões
```
#### Task 1.5: Integrar em server/index.ts ✅
**Arquivo:** `server/index.ts`
**Ação:** Importar rotas OpenClaw e carregá-las
```typescript
import openclawRoutes from "./modules/openclaw/routes";
app.use("/api/openclaw", openclawRoutes);
```
### SPRINT 2 (28-29/03): Frontend Widgets — ✅ COMPLETO (commit 5231166)
#### Task 2.1: Criar OpenClawWidget.tsx ✅
**Arquivo:** `client/src/components/openclaw/OpenClawWidget.tsx`
**Objetivo:** Widget flutuante que recebe notificações de padrões
```typescript
export function OpenClawWidget() {
// Escutar Socket.IO event: /openclaw/pattern-detected
// Exibir notificação flutuante (canto inferior direito)
// Mostrar padrão detectado
// Botão "Ver Sugestão" abre modal
// Botão "Ignorar" descarta
return (
<div className="fixed bottom-4 right-4">
{/* Widget flutuante */}
</div>
);
}
```
**Socket.IO:** Escuta evento `/openclaw/pattern-detected`
**Trigger:** Modal SkillSuggestion
#### Task 2.2: Criar SkillSuggestion.tsx ✅
**Arquivo:** `client/src/components/openclaw/SkillSuggestion.tsx`
**Objetivo:** Modal com preview da skill sugerida
```typescript
export function SkillSuggestion({ pattern, suggestion }) {
// Modal mostra:
// 1. "Detectamos um padrão na sua utilização:"
// 2. Preview do padrão (ações repetidas)
// 3. Preview da automação gerada
// 4. Campo de nome (auto-preenchido)
// 5. Descrição (auto-gerada)
// 6. Botão "Criar Skill" ou "Cancelar"
const handleCreate = async () => {
// POST /api/openclaw/confirm-skill
// Esperar resposta (skill DRAFT)
// Toast: "Skill criada! Vá para Dev Center para publicar"
};
}
```
**Integração:** Chama `POST /api/openclaw/confirm-skill`
**Feedback:** Toast notification
#### Task 2.3: Hook useAgentEmergence.ts ✅
**Arquivo:** `client/src/hooks/useAgentEmergence.ts`
**Objetivo:** Lógica de emergência de agentes
```typescript
export function useAgentEmergence(userId: string) {
const [suggestions, setSuggestions] = useState([]);
useEffect(() => {
// Socket.IO listener
socket.on("openclaw:pattern-detected", (pattern) => {
// Filtrar por tenant, user prefs
// Atualizar suggestions state
setSuggestions([...suggestions, pattern]);
});
}, []);
return { suggestions, dismiss, confirm };
}
```
#### Task 2.4: Integrar Widget em layout principal ✅
**Arquivo:** `client/src/App.tsx` ou `client/src/layouts/MainLayout.tsx`
**Ação:** Adicionar OpenClawWidget ao layout persistente
```typescript
import { OpenClawWidget } from "@/components/openclaw/OpenClawWidget";
export default function App() {
return (
<>
{/* Conteúdo existente */}
<OpenClawWidget /> {/* ← Adicionar aqui */}
</>
);
}
```
### SPRINT 3 (30-31/03): Configuração e Integração — ⏳ PRÓXIMO
#### Task 3.1: Criar arcadia.config.yaml
**Arquivo:** `server/modules/openclaw/config/arcadia.config.yaml`
```yaml
openclaw:
enabled: true
# Endpoints
arcadia_api_url: http://localhost:3000/api
arcadia_kg_url: http://localhost:7474
blackboard_url: http://localhost:3000/api/blackboard
# Tenant awareness
tenant_aware: true
# Detecção de padrões
pattern_detection:
enabled: true
min_occurrences: 3
time_window_days: 30
confidence_threshold: 0.8
scheduled_check_interval_minutes: 60
# Sugestões
suggestions:
enabled: true
auto_suggest: false # Sempre pedir confirmação do usuário
channels: ['widget', 'chat']
priority_threshold: 0.85
# Skills emergentes
emergence:
create_as_draft: true # Sempre requer aprovação
auto_publish: false
notify_admins: true
skill_prefix: "emergent_" # Prefixo para skills auto-geradas
# Logging
logging:
enabled: true
store_patterns: true
store_in_kg: true
audit_trail: true
```
#### Task 3.2: Integrar Socket.IO events
**Arquivo:** `server/socket-io.ts`
**Ação:** Adicionar listeners e emitters para OpenClaw
```typescript
socket.on("openclaw:confirm-skill", async (data) => {
// Receber confirmação do usuário
// Chamar SkillEmergence.generateSkillDraft()
// Emitir "openclaw:skill-created"
});
// Emitter no PatternDetector
io.emit("openclaw:pattern-detected", pattern);
```
#### Task 3.3: Database schema para OpenClaw
**Arquivo:** `shared/schema.ts` (Drizzle ORM)
**Adicionar tabelas:**
```typescript
// Padrões detectados
export const detectedPatterns = pgTable("detected_patterns", {
id: serial().primaryKey(),
tenant_id: uuid().notNull(),
user_id: uuid().notNull(),
action_type: varchar(255).notNull(),
occurrences: integer().notNull(),
confidence: numeric(3, 2).notNull(),
time_window_days: integer().notNull(),
created_at: timestamp().defaultNow(),
metadata: jsonb(),
});
// Sugestões geradas
export const skillSuggestions = pgTable("skill_suggestions", {
id: serial().primaryKey(),
pattern_id: integer().references(() => detectedPatterns.id),
skill_draft_id: uuid().references(() => skills.id),
status: varchar(50).notNull(), // 'suggested', 'accepted', 'rejected'
created_at: timestamp().defaultNow(),
});
```
#### Task 3.4: Migration SQL
**Arquivo:** `migrations/0004_openclaw_tables.sql`
```sql
CREATE TABLE detected_patterns (
id SERIAL PRIMARY KEY,
tenant_id UUID NOT NULL,
user_id UUID NOT NULL,
action_type VARCHAR(255) NOT NULL,
occurrences INTEGER NOT NULL,
confidence NUMERIC(3,2) NOT NULL,
time_window_days INTEGER NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
metadata JSONB
);
CREATE TABLE skill_suggestions (
id SERIAL PRIMARY KEY,
pattern_id INTEGER REFERENCES detected_patterns(id),
skill_draft_id UUID REFERENCES skills(id),
status VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_detected_patterns_user_tenant
ON detected_patterns(user_id, tenant_id);
```
#### Task 3.5: Testes unitários
**Arquivo:** `tests/openclaw.test.ts`
- PatternDetector detecta padrão corretamente
- OpenClawEngine orquestra fluxo correto
- SkillEmergence integra com Blackboard
- Socket.IO emite eventos corretamente
#### Task 3.6: Documentação
**Arquivo:** `DOCUMENTATION.md` (adicionar seção)
- Como OpenClaw funciona
- Configuração por tenant
- Troubleshooting
---
## 5. TIMELINE
| Data | Sprint | Tasks | Status |
|------|--------|-------|--------|
| 26-27/03 | 1 | PatternDetector backend | ✅ Completo |
| 26/03 | 2 | OpenClawWidget frontend | ✅ Completo (commit 5231166) |
| 26/03 | 3 | Migration DB + testes | ⏳ Próximo |
| 01-08/04 | Buffer | Refinamento, testes E2E | ⏳ Próximo |
**Nota:** Trabalho em `/opt/arcadia_merged/` (diretório canônico do projeto).
---
## 6. CRITÉRIOS DE SUCESSO
✅ **PatternDetector funciona:**
- Detecta padrão após 3 ocorrências em 30 dias
- Confiança calculada corretamente
- Eventos emitidos para Socket.IO
✅ **OpenClawWidget funciona:**
- Widget aparece quando padrão é detectado
- Modal mostra sugestão corretamente
- Usuário pode confirmar ou rejeitar
✅ **SkillEmergence funciona:**
- Integra com Blackboard
- Gera skill como DRAFT
- Aparece em Dev Center para aprovação
✅ **E2E funciona:**
- User faz ação 3x
- Widget sugere
- Approva
- Skill publicada
- Skill disponível para reutilização
---
## 7. RISCOS E MITIGAÇÕES
| Risco | Probabilidade | Mitigação |
|-------|--------------|-----------|
| Blackboard não retorna código correto | Média | Testar integração antes, ter fallback |
| Padrões detectados incorretamente | Média | Validar threshold, testes no KG |
| Socket.IO não emite em tempo real | Baixa | Já funciona em outras partes |
| Performance com muitos eventos | Média | Usar índices em PG, cache em Redis |
---
## 8. BRANCH E COMMITS
**Branch:** `Servidor` (deploy branch, conforme feedback anterior)
**Commit pattern:**
```
feat(openclaw): PatternDetector backend
feat(openclaw): OpenClawWidget frontend
feat(openclaw): Integração Blackboard + esquema DB
docs(openclaw): Documentação e testes
```
---
## 9. PRÓXIMAS AÇÕES
1. ✅ **26/03:** Sprint 1 concluído (PatternDetector, OpenClawEngine, SkillEmergence, routes, config)
2. ✅ **26/03:** Sprint 2 concluído (OpenClawWidget, SkillSuggestion, useAgentEmergence, App.tsx — commit 5231166)
3. ⏳ **Agora:** Sprint 3 — migration SQL + vitest + testes unitários em `/opt/arcadia_merged/`
4. ⏳ **01-08/04:** Refinamento e testes E2E
---
*Fase 4: OpenClaw Embutido — Plano Executável*
*Data: 2026-03-26*
*Pronto para implementação ✅*

View File

@ -1,27 +0,0 @@
# Arcádia Agentic Suite
## Vision
Evoluir o Arcádia Suite de ERP tradicional para um **Sistema Agêntico Orientado a Objetos**, onde Skills são objetos reutilizáveis, Agentes instanciam Skills para objetivos específicos, Automações são composições orquestradas, e o Dev Center é a fábrica de agentes.
## Stack
- **Backend:** Node.js + TypeScript, PostgreSQL, Neo4j (Knowledge Graph)
- **Frontend:** React + TypeScript, Monaco Editor
- **IA:** Ollama local (modelos até 14B — ex: deepseek-r1:14b padrão, llama3.1:8b rápido)
- **BI:** Apache Superset (externo, integrado via bridge)
- **Módulos embutidos:** MiroFlow (`server/modules/miroflow/`), OpenClaw (`server/modules/openclaw/`)
## Princípios
- Skills são o primitivo de execução universal
- Soberania tecnológica — eliminar dependências externas para lógica de negócio
- Multi-tenant: System → Tenant → Company → User
- Auditoria imutável em todas as execuções
- Backend-first em cada fase — API definida antes do frontend
## Non-negotiables
- Confirmar com João antes de executar qualquer migração de dados ou alteração de sistemas ativos
- Nunca sobrescrever configurações existentes do Superset (RLS já configurado em produção)
- Branch de deploy: `Servidor`

View File

@ -1,116 +0,0 @@
# Roadmap: Arcádia Agentic Suite
## Overview
Evolução do Arcádia Suite de ERP tradicional para Sistema Agêntico Orientado a Objetos. Skills são objetos reutilizáveis (POO), Agentes instanciam Skills, Automações são composições orquestradas, Dev Center é a fábrica de agentes.
## Phases
- [x] **Phase 1: Fundação** - Infraestrutura base: submodules MiroFlow/OpenClaw, tabelas skills, Neo4j, ReferenceParser
- [x] **Phase 2: Skills Engine** - Skills criáveis e executáveis com editor Monaco e Marketplace
- [x] **Phase 3: MiroFlow Embutido** - Análises científicas via agentes especializados + bridge Superset
- [ ] **Phase 4: OpenClaw Embutido** - Skills emergentes com detecção de padrões
- [ ] **Phase 5: Automation Fabric** - Automações unificadas (XOS + Central)
- [ ] **Phase 6: Dev Center Completo** - Fábrica de agentes: Design → Assemble → Deploy
## Phase Details
### Phase 1: Fundação
**Goal**: Infraestrutura base funcionando com submodules, banco, KG e parser de referências
**Depends on**: Nothing
**Success Criteria** (what must be TRUE):
1. Submodules MiroFlow e OpenClaw clonados e inicializados
2. Tabelas arcadia_skills e skill_executions existem no banco
3. Neo4j rodando via docker-compose
4. ReferenceParser parseia referências /skill/, /kg/, /file/ etc.
Plans:
- [x] 01-01: Submodules MiroFlow + OpenClaw
- [x] 01-02: Schema tabelas skills + migration
- [x] 01-03: Neo4j docker-compose + ReferenceParser
### Phase 2: Skills Engine
**Goal**: Skills criáveis, editáveis e executáveis com marketplace
**Depends on**: Phase 1
**Success Criteria** (what must be TRUE):
1. SkillEngine.ts suporta herança, composição e polimorfismo
2. API REST /skills CRUD funcionando
3. Editor Monaco com autocomplete de referências (/)
4. Skill Marketplace lista e filtra skills disponíveis
5. Versionamento Git-like de skills implementado
Plans:
- [x] 02-01: SkillEngine + API REST
- [x] 02-02: Editor Monaco + autocomplete + rota /skills
- [x] 02-03: Skill Marketplace (Biblioteca)
- [x] 02-04: Versionamento Git-like de skills
### Phase 3: MiroFlow Embutido
**Goal**: Análises científicas disponíveis via agentes especializados integrados ao Superset
**Depends on**: Phase 2
**Success Criteria** (what must be TRUE):
1. MiroFlow configurado para Ollama local com modelos até 14B
2. Agente Statistician analisa dados SQL com deepseek-r1:14b
3. Agente Fiscal Auditor valida NFe/SPED com deepseek-r1:14b
4. Agente Researcher consulta KG com llama3.1:8b
5. Endpoint POST /api/miroflow/analyze retorna análise estruturada
6. MiroFlowControl.tsx toggle "Modo Científico" aparece no Superset
7. Execuções registradas com imutabilidade no KG
**Plans**: 3 planos
Plans:
- [x] 03-01-PLAN.md — Setup (ollama pull llama3.1:8b) + miroflow_service.py FastAPI porta 8006 com 3 agentes
- [x] 03-02-PLAN.md — Node bridge (engine-proxy.ts + routes.ts) + KG logging SHA-256
- [x] 03-03-PLAN.md — Frontend MiroFlowControl.tsx + tab "Científico" em BiWorkspace.tsx
### Phase 4: OpenClaw Embutido
**Goal**: Skills emergentes criadas automaticamente a partir de padrões detectados
**Depends on**: Phase 3
**Success Criteria** (what must be TRUE):
1. PatternDetector detecta padrões (min 3 ocorrências, 30 dias, 80% confiança)
2. Skills emergentes criadas como DRAFT aguardando aprovação
3. Widget flutuante notifica usuário de sugestões
4. Fluxo completo: Padrão → DRAFT → Dev Center aprovação
**Plans**: TBD
### Phase 5: Automation Fabric
**Goal**: Automações unificadas sob runtime único substituindo XOS + Central
**Depends on**: Phase 4
**Success Criteria** (what must be TRUE):
1. 5 runtimes funcionando: WorkflowEngine, RuleEngine, AgentExecutor, ScheduleEngine, EventEngine
2. Automações existentes do XOS migradas sem perda de dados
3. Automações existentes do /automations Central migradas
4. AutomationCenter.tsx lista todas as automações unificadas
**Plans**: TBD
### Phase 6: Dev Center Completo
**Goal**: Fábrica completa de agentes: Design → Assemble → Deploy
**Depends on**: Phase 5
**Success Criteria** (what must be TRUE):
1. DesignStudio com modos UML, Visual Flow, Markdown Spec, Code Editor
2. AssembleLine gera código via Blackboard (GeneratorAgent)
3. OrchestrateCenter faz deploy, versionamento e monitoramento
4. Galeria de agentes por tenant funcional
**Plans**: TBD
## Progress
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. Fundação | 3/3 | Complete | 2026-03-25 |
| 2. Skills Engine | 4/4 | Complete | 2026-03-26 |
| 3. MiroFlow Embutido | 3/3 | Complete | 2026-03-26 |
| 4. OpenClaw Embutido | 0/TBD | Not started | - |
| 5. Automation Fabric | 0/TBD | Not started | - |
| 6. Dev Center Completo | 0/TBD | Not started | - |
### Phase 7: Skill Fabric Expandido: Compiladores, Sandbox Executor, Visual/Code/Markdown Editors com Validation Pipeline
**Goal:** [To be planned]
**Requirements**: TBD
**Depends on:** Phase 6
**Plans:** 0 plans
Plans:
- [ ] TBD (run /gsd:plan-phase 7 to break down)

View File

@ -1,22 +0,0 @@
# State
## Current Phase: 6 — Dev Center Completo
## Completed
- Phase 1: submodules, tabelas, Neo4j, ReferenceParser
- Phase 2: SkillEngine, API REST, Monaco Editor, /skills, autocomplete, Marketplace, versionamento Git-like
- Phase 3: miroflow_service.py, bridge TS + KG logging, MiroFlowControl.tsx + tab Científico
- Phase 4: PatternDetector (cron 1h), OpenClaw routes (suggestions/patterns/accept/reject), tab Sugestões em /skills + badge contador
- Phase 5: 5 runtimes (WorkflowEngine/RuleEngine/AgentExecutor/ScheduleEngine/EventEngine), AutomationFabricService, /api/automation-fabric, AutomationCenter.tsx em /automations-center
- Phase 6: arcadia_agent_defs (schema+migration+API CRUD+assemble/deploy/run/fork), 4 tabs no DevCenter (Design/Montar/Deploy/Galeria)
## In Progress
- (nenhum — Phase 6 concluída, iniciar Phase 7)
## Roadmap Evolution
- Phase 7 added: Skill Fabric Expandido (compiladores, sandbox, 3 editor modes, validation pipeline)
## Notes
- Superset em produção com RLS configurado — não alterar sem confirmação
- Branch de deploy: `Servidor`
- Modelos Ollama precisam ser baixados: deepseek-r1:14b, llama3.1:8b (máximo 14B)

View File

@ -1,9 +0,0 @@
{
"workflow": {
"research": false,
"plan_check": false,
"verifier": false,
"nyquist_validation": false
},
"model_profile": "budget"
}

View File

@ -1,329 +0,0 @@
---
id: "03-01"
phase: 03-miroflow-embutido
plan: 01
type: execute
wave: 0
depends_on: []
files_modified:
- server/python/miroflow_service.py
- server/python/test_miroflow_service.py
autonomous: true
requirements:
- REQ-3.1
- REQ-3.2
- REQ-3.3
- REQ-3.4
must_haves:
truths:
- "ollama pull llama3.1:8b completa sem erro"
- "miroflow_service.py sobe na porta 8006 sem ModuleNotFoundError"
- "GET http://localhost:8006/health retorna {status: ok}"
- "Agente statistician instanciado com deepseek-r1:14b via UnifiedOpenAIClient"
- "Agente fiscal_auditor instanciado com deepseek-r1:14b via UnifiedOpenAIClient"
- "Agente researcher instanciado com llama3.1:8b via UnifiedOpenAIClient"
artifacts:
- path: "server/python/miroflow_service.py"
provides: "FastAPI microserviço porta 8006 com 3 agentes MiroFlow"
exports: ["app", "AnalyzeRequest", "AnalyzeResponse", "make_agent_cfg", "run_agent"]
- path: "server/python/test_miroflow_service.py"
provides: "Testes pytest para REQ-3.1 a REQ-3.4"
contains: "test_health, test_statistician_model, test_fiscal_auditor_model, test_researcher_model"
key_links:
- from: "miroflow_service.py"
to: "http://localhost:11434/v1/chat/completions"
via: "UnifiedOpenAIClient base_url=OLLAMA_BASE_URL/v1"
pattern: "OLLAMA_BASE_URL.*v1"
- from: "miroflow_service.py"
to: "server/modules/miroflow"
via: "sys.path.insert(0, /app/server/modules/miroflow)"
pattern: "sys.path.insert"
---
<objective>
Preparar o ambiente e criar o microserviço Python MiroFlow (FastAPI, porta 8006) com os 3 agentes especializados configurados para Ollama local.
Purpose: Fundação do Phase 3 — sem esse microserviço rodando, os planos 02 e 03 não têm backend para chamar.
Output: miroflow_service.py funcional + testes pytest cobrindo REQ-3.1 a REQ-3.4 + llama3.1:8b instalado.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/03-miroflow-embutido/03-RESEARCH.md
<interfaces>
<!-- Padrão de microserviço FastAPI existente — copiar estrutura de bi_analysis_service.py -->
<!-- server/python/bi_analysis_service.py -->
app = FastAPI(title="...", version="1.0.0")
app.add_middleware(CORSMiddleware, allow_origins=["*"], ...)
@app.get("/health")
async def health_check(): return {"status": "ok", "service": "..."}
@app.post("/analyze", response_model=AnalysisResult)
async def analyze_data(request: AnalysisRequest): ...
if __name__ == "__main__":
port = int(os.environ.get("SERVICE_PORT", 8003))
uvicorn.run(app, host="0.0.0.0", port=port)
<!-- MiroFlow agent API — extraído de server/modules/miroflow/miroflow/agents/ -->
from miroflow.agents.factory import build_agent # build_agent(config_dict) -> BaseAgent
from miroflow.agents.context import AgentContext # AgentContext(task_description="...")
# agent.run(ctx) -> dict com chave "summary" ou texto direto
<!-- Config obrigatória do LLM (campos do base.yaml confirmados) -->
{
"type": "IterativeAgentWithToolAndRollback",
"name": "arcadia_deepseek_r1_14b",
"max_turns": 10,
"llm": {
"provider_class": "UnifiedOpenAIClient",
"model_name": "deepseek-r1:14b", # ou llama3.1:8b
"api_key": "ollama",
"base_url": "{OLLAMA_BASE_URL}/v1",
"max_tokens": 8192,
"temperature": 0.7,
"top_p": 1.0, "min_p": 0.0, "top_k": -1,
"reasoning_effort": None, "repetition_penalty": 1.0,
"max_context_length": -1, "async_client": True,
"disable_cache_control": True, "keep_tool_result": -1,
"use_tool_calls": False, "oai_tool_thinking": False,
}
}
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Setup — baixar llama3.1:8b e instalar dependências MiroFlow</name>
<files>server/modules/miroflow/ (sem modificar), .env (adicionar MIROFLOW_PORT)</files>
<read_first>
- /opt/arcadia_merged/.env (verificar se OLLAMA_BASE_URL e MIROFLOW_PORT já existem)
- /opt/arcadia_merged/server/modules/miroflow/pyproject.toml (confirmar deps necessárias)
</read_first>
<action>
1. Baixar o modelo llama3.1:8b no Ollama (necessário para o agente Researcher):
```bash
ollama pull llama3.1:8b
```
Se o download demorar muito, continuar e validar após. O fallback é llama3.2:3b já instalado.
2. Instalar dependências Python necessárias para o microserviço (o sistema Python 3.12 já tem fastapi/uvicorn, faltam as deps do MiroFlow):
```bash
cd /opt/arcadia_merged/server/modules/miroflow && uv sync --frozen 2>/dev/null || pip3 install omegaconf hydra-core openai python-dotenv pydantic
```
3. Verificar se MIROFLOW_PORT=8006 existe no .env. Se não existir, adicionar a linha `MIROFLOW_PORT=8006` ao arquivo .env. NÃO alterar OLLAMA_BASE_URL existente.
4. Verificar se OLLAMA_BASE_URL está presente no .env. Se não, adicionar `OLLAMA_BASE_URL=http://localhost:11434`.
</action>
<verify>
<automated>ollama list | grep "llama3.1:8b" && python3 -c "import omegaconf; print('omegaconf ok')" && python3 -c "from miroflow.agents.factory import build_agent; print('miroflow importado')" 2>/dev/null || echo "PENDENTE: llama3.1:8b pode estar baixando"</automated>
</verify>
<acceptance_criteria>
- `ollama list` contém "llama3.1:8b" OU download em andamento (verificar com `ollama ps`)
- `python3 -c "import omegaconf"` não gera ImportError
- `python3 -c "from miroflow.agents.factory import build_agent"` não gera ImportError (executado do diretório com o venv ativo ou com sys.path configurado)
- .env contém linha MIROFLOW_PORT=8006
</acceptance_criteria>
<done>Modelo llama3.1:8b disponível no Ollama, deps MiroFlow instaladas, MIROFLOW_PORT configurado no .env</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Criar test_miroflow_service.py + miroflow_service.py (FastAPI porta 8006)</name>
<files>server/python/test_miroflow_service.py, server/python/miroflow_service.py</files>
<read_first>
- /opt/arcadia_merged/server/python/bi_analysis_service.py (padrão de estrutura FastAPI a seguir)
- /opt/arcadia_merged/server/modules/miroflow/miroflow/agents/base.py (API do BaseAgent)
- /opt/arcadia_merged/server/modules/miroflow/miroflow/agents/factory.py (build_agent signature)
- /opt/arcadia_merged/server/modules/miroflow/miroflow/agents/context.py (AgentContext fields)
- /opt/arcadia_merged/.env (OLLAMA_BASE_URL atual)
</read_first>
<behavior>
- test_health: GET /health retorna {"status": "ok", "service": "miroflow"}
- test_statistician_config: make_agent_cfg("statistician") retorna dict com model_name="deepseek-r1:14b" e base_url terminando em "/v1"
- test_fiscal_auditor_config: make_agent_cfg("fiscal_auditor") retorna dict com model_name="deepseek-r1:14b"
- test_researcher_config: make_agent_cfg("researcher") retorna dict com model_name="llama3.1:8b" (fallback "llama3.2:3b" se llama3.1:8b ausente)
- test_analyze_request_validation: POST /analyze com agent="invalid" retorna 422 Unprocessable Entity
- test_analyze_request_schema: AnalyzeRequest aceita {agent, task, context, tenant_id} e AnalyzeResponse tem {agent, model, result, execution_id, duration_ms}
</behavior>
<action>
PASSO 1 (RED): Criar server/python/test_miroflow_service.py com os 6 testes listados em <behavior>. Rodar pytest — DEVE falhar (miroflow_service.py não existe).
PASSO 2 (GREEN): Criar server/python/miroflow_service.py seguindo o padrão de bi_analysis_service.py:
```python
"""
Arcádia MiroFlow Service — Agentes científicos via MiroFlow + Ollama local
FastAPI microserviço porta 8006
"""
import os, sys, time, uuid
from typing import Optional
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
# Adicionar o submodule MiroFlow ao path
MIROFLOW_MODULE_PATH = os.path.join(
os.path.dirname(__file__), "..", "modules", "miroflow"
)
sys.path.insert(0, os.path.abspath(MIROFLOW_MODULE_PATH))
from miroflow.agents.factory import build_agent
from miroflow.agents.context import AgentContext
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
# Verificar se llama3.1:8b está disponível; senão usar fallback
RESEARCHER_MODEL = os.getenv("MIROFLOW_RESEARCHER_MODEL", "llama3.1:8b")
AGENT_MODELS = {
"statistician": "deepseek-r1:14b",
"fiscal_auditor": "deepseek-r1:14b",
"researcher": RESEARCHER_MODEL,
}
def make_agent_cfg(agent_type: str) -> dict:
model = AGENT_MODELS.get(agent_type, "deepseek-r1:14b")
return {
"type": "IterativeAgentWithToolAndRollback",
"name": f"arcadia_{agent_type}",
"max_turns": 10,
"llm": {
"provider_class": "UnifiedOpenAIClient",
"model_name": model,
"api_key": "ollama",
"base_url": f"{OLLAMA_BASE_URL}/v1",
"max_tokens": 8192,
"temperature": 0.7,
"top_p": 1.0, "min_p": 0.0, "top_k": -1,
"reasoning_effort": None, "repetition_penalty": 1.0,
"max_context_length": -1, "async_client": True,
"disable_cache_control": True, "keep_tool_result": -1,
"use_tool_calls": False, "oai_tool_thinking": False,
},
}
async def run_agent(agent_type: str, task: str) -> tuple[str, str]:
"""Retorna (result_text, model_name)"""
if agent_type not in AGENT_MODELS:
raise ValueError(f"Agente desconhecido: {agent_type}. Use: {list(AGENT_MODELS.keys())}")
cfg = make_agent_cfg(agent_type)
agent = build_agent(cfg)
ctx = AgentContext(task_description=task)
result = await agent.run(ctx)
text = result.get("summary", str(result)) if isinstance(result, dict) else str(result)
return text, cfg["llm"]["model_name"]
app = FastAPI(title="Arcádia MiroFlow Service", version="1.0.0")
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True,
allow_methods=["*"], allow_headers=["*"])
class AnalyzeRequest(BaseModel):
agent: str # "statistician" | "fiscal_auditor" | "researcher"
task: str
context: dict = {}
tenant_id: Optional[int] = None
class AnalyzeResponse(BaseModel):
agent: str
model: str
result: str
execution_id: str
duration_ms: int
@app.get("/health")
async def health_check():
return {"status": "ok", "service": "miroflow", "agents": list(AGENT_MODELS.keys())}
@app.post("/analyze", response_model=AnalyzeResponse)
async def analyze(req: AnalyzeRequest):
if req.agent not in AGENT_MODELS:
raise HTTPException(status_code=422, detail=f"Agente inválido: {req.agent}")
start = time.time()
try:
result_text, model_name = await run_agent(req.agent, req.task)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
return AnalyzeResponse(
agent=req.agent,
model=model_name,
result=result_text,
execution_id=str(uuid.uuid4()),
duration_ms=int((time.time() - start) * 1000),
)
if __name__ == "__main__":
import uvicorn
port = int(os.environ.get("MIROFLOW_PORT", 8006))
uvicorn.run(app, host="0.0.0.0", port=port)
```
PASSO 3 (REFACTOR): Rodar pytest novamente — TODOS devem passar. Não alterar lógica, apenas organizar se necessário.
IMPORTANTE: Não usar modelos acima de 14B. Não criar arquivos YAML externos para config do MiroFlow — usar dict inline conforme padrão.
</action>
<verify>
<automated>cd /opt/arcadia_merged && python3 -m pytest server/python/test_miroflow_service.py -x -v 2>&1 | tail -20</automated>
</verify>
<acceptance_criteria>
- `pytest server/python/test_miroflow_service.py -x` passa com 0 falhas
- test_health verifica {"status": "ok", "service": "miroflow"}
- test_statistician_config e test_fiscal_auditor_config verificam model_name="deepseek-r1:14b"
- test_researcher_config verifica model_name em ["llama3.1:8b", "llama3.2:3b"] (aceita ambos)
- test_analyze_request_validation verifica que agent="invalid" retorna 422
- miroflow_service.py tem menos de 150 linhas
- Nenhuma referência a modelos acima de 14B no código
</acceptance_criteria>
<done>miroflow_service.py criado e testado. Microserviço pode ser iniciado com `python3 server/python/miroflow_service.py` na porta 8006. Todos os testes passam.</done>
</task>
</tasks>
<verification>
```bash
# Verificar que o serviço sobe:
cd /opt/arcadia_merged && MIROFLOW_PORT=8006 python3 server/python/miroflow_service.py &
sleep 3
curl -s http://localhost:8006/health | python3 -m json.tool
# Esperado: {"status": "ok", "service": "miroflow", "agents": ["statistician", "fiscal_auditor", "researcher"]}
kill %1
```
</verification>
<success_criteria>
- `pytest server/python/test_miroflow_service.py -x` passa (0 falhas)
- GET http://localhost:8006/health retorna {"status": "ok"} quando o serviço está rodando
- .env contém MIROFLOW_PORT=8006
- llama3.1:8b disponível no Ollama (ou download iniciado)
- Nenhum modelo acima de 14B referenciado
</success_criteria>
<output>
Após conclusão, criar `.planning/phases/03-miroflow-embutido/03-01-SUMMARY.md` com:
- Arquivos criados/modificados
- Resultado dos testes
- Status do download do llama3.1:8b
- Modelo de fallback usado para Researcher (se aplicável)
</output>

View File

@ -1,48 +0,0 @@
---
plan: "03-01"
phase: "03-miroflow-embutido"
status: complete
completed: 2026-03-25
---
# Summary: 03-01 — Python MiroFlow Microservice
## What was built
Microserviço FastAPI porta 8006 com 3 agentes científicos MiroFlow configurados para Ollama local, mais testes pytest cobrindo REQ-3.1 a REQ-3.4.
## Files created/modified
### Created
- `server/python/miroflow_service.py` — FastAPI app com make_agent_cfg(), run_agent(), GET /health, POST /analyze
- `server/python/test_miroflow_service.py` — 6 testes: health, statistician/fiscal_auditor/researcher config, request schema, 422 validation
## Test results
Todos os testes passaram (validados inline via python3):
- test_health OK (status: ok, service: miroflow)
- test_statistician_config OK (deepseek-r1:14b, base_url ends /v1)
- test_fiscal_auditor_config OK (deepseek-r1:14b)
- test_researcher_config OK (llama3.1:8b ou llama3.2:3b)
- test_analyze_request_validation OK (422 para agent invalido)
- test_analyze_request_schema OK
## Modelo Researcher
llama3.1:8b nao disponivel no Ollama local — fallback automatico para llama3.2:3b (ja instalado). Configuravel via MIROFLOW_RESEARCHER_MODEL no .env.
## Deps instaladas
omegaconf 2.3.0, hydra-core 1.3.2, mcp 1.26.0 instalados via pip3 --break-system-packages.
## Self-Check: PASSED
## Commits (03-01 execution)
- 60f1c5c: feat(03-01): criar miroflow_service.py FastAPI porta 8006 com 3 agentes + testes pytest
- 76e1d34: fix(03-01): adicionar conftest.py para resolver imports em pytest do root
## Deviations
- [Rule 2] conftest.py adicionado: pytest da raiz falhava sem sys.path fix
- [Documentado] llama3.2:3b usado como fallback para researcher (llama3.1:8b nao instalado)

View File

@ -1,331 +0,0 @@
---
id: "03-02"
phase: 03-miroflow-embutido
plan: 02
type: execute
wave: 1
depends_on: ["03-01"]
files_modified:
- server/miroflow/routes.ts
- server/miroflow/engine-proxy.ts
- server/routes.ts
autonomous: true
requirements:
- REQ-3.5
- REQ-3.7
must_haves:
truths:
- "POST /api/miroflow/analyze retorna JSON com {agent, model, result, execution_id, duration_ms}"
- "GET /api/miroflow/health retorna {online: true} quando microserviço está rodando"
- "Requisição não autenticada retorna 401"
- "Cada execução cria um nó graph_nodes com type='miroflow_execution' e auditHash SHA-256"
- "Timeout de 5 minutos no proxy (adequado para deepseek-r1:14b)"
artifacts:
- path: "server/miroflow/engine-proxy.ts"
provides: "Função proxyToMiroFlow() com timeout 300s"
exports: ["proxyToMiroFlow"]
- path: "server/miroflow/routes.ts"
provides: "Express router: POST /api/miroflow/analyze, GET /api/miroflow/health"
exports: ["registerMiroFlowRoutes"]
key_links:
- from: "server/miroflow/routes.ts"
to: "server/python/miroflow_service.py"
via: "fetch() para http://localhost:8006/analyze"
pattern: "MIROFLOW_PORT.*8006"
- from: "server/miroflow/routes.ts"
to: "server/graph/service.ts"
via: "createNode({type: 'miroflow_execution', ...})"
pattern: "createNode.*miroflow_execution"
- from: "server/routes.ts"
to: "server/miroflow/routes.ts"
via: "registerMiroFlowRoutes(app)"
pattern: "registerMiroFlowRoutes"
---
<objective>
Criar o bridge Node.js → Python para o MiroFlow: proxy TypeScript (engine-proxy.ts), rotas Express (routes.ts) e registro imutável de execuções no KG via graph_nodes.
Purpose: Expõe POST /api/miroflow/analyze como endpoint autenticado do Arcádia Suite, conectando o frontend ao microserviço Python do Plano 01.
Output: Rotas Express registradas, cada chamada proxiada para :8006 e auditada no KG PostgreSQL.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/03-miroflow-embutido/03-01-SUMMARY.md
@.planning/phases/03-miroflow-embutido/03-RESEARCH.md
<interfaces>
<!-- Padrão de proxy existente — server/bi/engine-proxy.ts -->
// TIMEOUT padrão atual: 30000ms — MiroFlow PRECISA de 300000ms (5min)
async function proxyToEngine(path: string, options: RequestInit = {}): Promise<any> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), BI_ENGINE_TIMEOUT);
const response = await fetch(`${BI_ENGINE_URL}${path}`, { ...options, signal: controller.signal });
if (!response.ok) {
const error = await response.json().catch(() => ({ detail: response.statusText }));
throw new Error(error.detail || `BI Engine error: ${response.status}`);
}
return await response.json();
}
export function registerBiEngineRoutes(app: Express): void {
app.post("/api/bi-engine/analyze", async (req, res) => {
if (!req.isAuthenticated()) return res.status(401).json({ error: "Not authenticated" });
...
});
}
<!-- KG service — server/graph/service.ts -->
export async function createNode(data: InsertGraphNode): Promise<GraphNode>
// InsertGraphNode: { type: string, tenantId?: number, data: object }
// graph_nodes tabela PostgreSQL — NÃO usar Neo4j direto
<!-- Registro de routes em server/routes.ts (linha ~105) -->
import { registerBiEngineRoutes } from "./bi/engine-proxy"; // padrão a seguir
registerBiEngineRoutes(app); // linha ~105
<!-- AnalyzeResponse do microserviço Python (03-01) -->
{
agent: string, // "statistician" | "fiscal_auditor" | "researcher"
model: string, // "deepseek-r1:14b" | "llama3.1:8b"
result: string,
execution_id: string, // UUID v4
duration_ms: number
}
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Criar server/miroflow/engine-proxy.ts e server/miroflow/routes.ts</name>
<files>server/miroflow/engine-proxy.ts, server/miroflow/routes.ts</files>
<read_first>
- /opt/arcadia_merged/server/bi/engine-proxy.ts (padrão completo de proxy a seguir)
- /opt/arcadia_merged/server/graph/service.ts (createNode signature, linhas 41-49)
- /opt/arcadia_merged/server/graph/routes.ts (confirmar que /api/graph/nodes aceita POST)
- /opt/arcadia_merged/server/auth.ts (confirmar req.isAuthenticated() e req.user shape)
</read_first>
<behavior>
- proxyToMiroFlow("/health", "GET") faz fetch para MIROFLOW_URL/health com timeout 5s
- proxyToMiroFlow("/analyze", "POST", body) faz fetch para MIROFLOW_URL/analyze com timeout 300000ms
- POST /api/miroflow/analyze sem autenticação retorna 401 {"error": "Não autenticado"}
- POST /api/miroflow/analyze autenticado encaminha body + tenant_id para :8006/analyze
- Após resposta bem-sucedida, registerExecutionInKG cria nó com type="miroflow_execution"
- auditHash é SHA-256 do JSON.stringify({execution_id, agent, model, input, output})
- GET /api/miroflow/health não requer autenticação, retorna {online: bool, url: string}
</behavior>
<action>
Criar diretório server/miroflow/ se não existir.
Criar server/miroflow/engine-proxy.ts:
```typescript
import type { Express, Request, Response } from "express";
import crypto from "crypto";
import { createNode } from "../graph/service";
const MIROFLOW_HOST = process.env.MIROFLOW_HOST || "localhost";
const MIROFLOW_PORT = parseInt(process.env.MIROFLOW_PORT || "8006", 10);
const MIROFLOW_URL = `http://${MIROFLOW_HOST}:${MIROFLOW_PORT}`;
const MIROFLOW_TIMEOUT = 300_000; // 5 minutos — deepseek-r1:14b pode levar 60-120s
async function proxyToMiroFlow(path: string, method: string = "GET", body?: object): Promise<any> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), MIROFLOW_TIMEOUT);
try {
const response = await fetch(`${MIROFLOW_URL}${path}`, {
method,
headers: { "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
signal: controller.signal,
});
clearTimeout(timeout);
if (!response.ok) {
const err = await response.json().catch(() => ({ detail: response.statusText }));
throw new Error(err.detail || `MiroFlow error: ${response.status}`);
}
return await response.json();
} catch (err: any) {
clearTimeout(timeout);
if (err.name === "AbortError") throw new Error("MiroFlow timeout (300s)");
throw err;
}
}
async function registerExecutionInKG(req: Request, execData: any, inputBody: any): Promise<void> {
try {
const auditHash = crypto
.createHash("sha256")
.update(JSON.stringify({
execution_id: execData.execution_id,
agent: execData.agent,
model: execData.model,
input: inputBody,
output: execData.result,
}))
.digest("hex");
await createNode({
type: "miroflow_execution",
tenantId: (req.user as any)?.tenantId ?? null,
data: {
...execData,
input: inputBody,
auditHash,
immutable: true,
registeredAt: new Date().toISOString(),
},
});
} catch (kgErr: any) {
// KG failure não deve bloquear a resposta ao cliente
console.error("[MiroFlow] Falha ao registrar no KG:", kgErr.message);
}
}
export function registerMiroFlowRoutes(app: Express): void {
app.get("/api/miroflow/health", async (_req: Request, res: Response) => {
try {
const data = await proxyToMiroFlow("/health", "GET");
res.json({ online: true, url: MIROFLOW_URL, ...data });
} catch {
res.json({ online: false, url: MIROFLOW_URL });
}
});
app.post("/api/miroflow/analyze", async (req: Request, res: Response) => {
if (!req.isAuthenticated()) {
return res.status(401).json({ error: "Não autenticado" });
}
try {
const inputBody = {
...req.body,
tenant_id: (req.user as any)?.tenantId ?? null,
};
const data = await proxyToMiroFlow("/analyze", "POST", inputBody);
// Registrar no KG de forma assíncrona (não bloqueia a resposta)
registerExecutionInKG(req, data, req.body).catch(() => {});
res.json(data);
} catch (err: any) {
res.status(502).json({ error: err.message });
}
});
console.log(`[MiroFlow Proxy] Rotas registradas -> ${MIROFLOW_URL}`);
}
```
Criar server/miroflow/routes.ts como re-export limpo (manter compatibilidade com o loader de módulos):
```typescript
export { registerMiroFlowRoutes } from "./engine-proxy";
```
IMPORTANTE:
- Timeout de 300_000ms (5 minutos) — NÃO usar o padrão de 30s do bi/engine-proxy.ts
- registerExecutionInKG usa createNode do graph/service.ts, NÃO faz fetch para /api/graph/nodes
- Falha no KG não retorna erro ao cliente (try/catch com log)
- auditHash cobre: execution_id + agent + model + input + output
</action>
<verify>
<automated>cd /opt/arcadia_merged && npx tsc --noEmit --project tsconfig.json 2>&1 | grep -i "miroflow" | head -10 || echo "TypeScript OK (nenhum erro em miroflow)"</automated>
</verify>
<acceptance_criteria>
- server/miroflow/engine-proxy.ts existe e compila sem erros TypeScript
- server/miroflow/routes.ts existe (re-export ou implementação direta)
- MIROFLOW_TIMEOUT = 300_000 está definido (não 30000)
- Função registerExecutionInKG usa createNode importado de "../graph/service"
- auditHash calculado com crypto.createHash("sha256")
- POST /api/miroflow/analyze faz verificação req.isAuthenticated() antes de proxiar
- grep -n "300_000\|300000" server/miroflow/engine-proxy.ts retorna resultado
- grep -n "miroflow_execution" server/miroflow/engine-proxy.ts retorna resultado
</acceptance_criteria>
<done>Arquivos TypeScript criados sem erros de compilação. Funções exportadas: registerMiroFlowRoutes. Timeout de 5min configurado. KG logging com SHA-256 implementado.</done>
</task>
<task type="auto">
<name>Task 2: Registrar MiroFlow em server/routes.ts</name>
<files>server/routes.ts</files>
<read_first>
- /opt/arcadia_merged/server/routes.ts (ARQUIVO INTEIRO — verificar imports e onde registerBiEngineRoutes é chamado para inserir MiroFlow no mesmo padrão)
</read_first>
<action>
Adicionar import e registro das rotas MiroFlow em server/routes.ts, seguindo o padrão exato de registerBiEngineRoutes:
1. Adicionar import após a linha que importa registerBiEngineRoutes (linha ~19):
```typescript
import { registerMiroFlowRoutes } from "./miroflow/engine-proxy";
```
2. Adicionar chamada após registerBiEngineRoutes(app) (linha ~105):
```typescript
registerMiroFlowRoutes(app);
```
NÃO alterar nenhuma outra linha do arquivo. NÃO mover, reordenar ou reformatar código existente.
NÃO modificar configurações do Superset ou rotas existentes.
</action>
<verify>
<automated>cd /opt/arcadia_merged && grep -n "registerMiroFlowRoutes" server/routes.ts && npx tsc --noEmit --project tsconfig.json 2>&1 | grep -c "error" || echo "0 erros"</automated>
</verify>
<acceptance_criteria>
- `grep "registerMiroFlowRoutes" server/routes.ts` retorna 2 linhas (import + chamada)
- `npx tsc --noEmit` não retorna erros relacionados a miroflow
- A linha de import está próxima de registerBiEngineRoutes (mesma região do arquivo)
- A chamada registerMiroFlowRoutes(app) está próxima de registerBiEngineRoutes(app)
- Nenhuma linha existente do arquivo foi modificada (verificar com git diff)
</acceptance_criteria>
<done>server/routes.ts registra as rotas MiroFlow. POST /api/miroflow/analyze e GET /api/miroflow/health estarão disponíveis quando o servidor Node iniciar.</done>
</task>
</tasks>
<verification>
```bash
# Compilação TypeScript limpa:
cd /opt/arcadia_merged && npx tsc --noEmit 2>&1 | grep -i "error" | head -5
# Confirmar registro no KG:
grep -n "miroflow_execution\|auditHash\|createNode" /opt/arcadia_merged/server/miroflow/engine-proxy.ts
# Confirmar timeout correto:
grep -n "300" /opt/arcadia_merged/server/miroflow/engine-proxy.ts
# Confirmar rotas registradas:
grep -n "registerMiroFlowRoutes" /opt/arcadia_merged/server/routes.ts
```
</verification>
<success_criteria>
- server/miroflow/engine-proxy.ts e server/miroflow/routes.ts existem
- TypeScript compila sem erros nos novos arquivos
- server/routes.ts inclui import e chamada registerMiroFlowRoutes
- Timeout de 300s configurado no proxy
- auditHash SHA-256 calculado e salvo via createNode
- GET /api/miroflow/health acessível sem autenticação
- POST /api/miroflow/analyze requer autenticação (401 sem sessão)
</success_criteria>
<output>
Após conclusão, criar `.planning/phases/03-miroflow-embutido/03-02-SUMMARY.md` com:
- Arquivos criados/modificados
- Resultado da compilação TypeScript
- Confirmação do padrão de timeout (300s)
- Confirmação do KG logging com auditHash
</output>

View File

@ -1,40 +0,0 @@
---
plan: "03-02"
phase: "03-miroflow-embutido"
status: complete
completed: 2026-03-25
---
# Summary: 03-02 — Node.js MiroFlow Bridge
## What was built
Bridge Node.js → Python para o MiroFlow: proxy TypeScript com timeout 300s, rotas Express autenticadas e registro imutável de execuções no KG via auditHash SHA-256.
## Files created/modified
### Created
- `server/miroflow/engine-proxy.ts` — proxy para :8006, MIROFLOW_TIMEOUT=300_000ms, registerExecutionInKG com SHA-256
- `server/miroflow/routes.ts` — re-export de registerMiroFlowRoutes
### Modified
- `server/routes.ts` — import + chamada registerMiroFlowRoutes(app) após registerBiEngineRoutes
## Key decisions
- Timeout de 300_000ms (5 min) para POST /analyze — adequado para deepseek-r1:14b
- Health check usa timeout curto (5_000ms)
- KG failure não bloqueia resposta ao cliente (try/catch com console.error)
- auditHash cobre: execution_id + agent + model + input + output
## TypeScript compilation
Erros nos novos arquivos: **0**
(Erros pré-existentes em App.tsx e server/modules/miroflow/ não relacionados)
## Commits
- `d53be76` — feat(03-02): criar MiroFlow proxy TypeScript com timeout 300s e KG audit logging
- `1a70e87` — feat(03-02): registrar MiroFlow routes em server/routes.ts
## Self-Check: PASSED

View File

@ -1,315 +0,0 @@
---
id: "03-03"
phase: 03-miroflow-embutido
plan: 03
type: execute
wave: 2
depends_on: ["03-02"]
files_modified:
- client/src/components/MiroFlowControl.tsx
- client/src/pages/BiWorkspace.tsx
autonomous: false
requirements:
- REQ-3.6
must_haves:
truths:
- "Tab 'Científico' aparece na TabsList de BiWorkspace.tsx"
- "MiroFlowControl.tsx renderiza toggle 'Modo Científico' com ícone Brain"
- "Usuário pode selecionar agente (Statistician / Fiscal Auditor / Researcher)"
- "Usuário pode digitar tarefa e clicar em Analisar"
- "Resultado da análise é exibido em área de texto com scroll"
- "Estado de loading é exibido durante a chamada (ícone Loader2 girando)"
- "Erro da API é exibido ao usuário de forma legível"
- "Tabs existentes (overview, datasources, etc.) continuam funcionando normalmente"
artifacts:
- path: "client/src/components/MiroFlowControl.tsx"
provides: "Componente React com toggle, seletor de agente, input de tarefa e exibição de resultado"
exports: ["MiroFlowControl"]
- path: "client/src/pages/BiWorkspace.tsx"
provides: "Tab 'cientifico' adicionada à TabsList e TabsContent existentes"
contains: "TabsTrigger value=\"cientifico\""
key_links:
- from: "client/src/components/MiroFlowControl.tsx"
to: "/api/miroflow/analyze"
via: "apiRequest('POST', '/api/miroflow/analyze', {agent, task})"
pattern: "miroflow/analyze"
- from: "client/src/pages/BiWorkspace.tsx"
to: "client/src/components/MiroFlowControl.tsx"
via: "import { MiroFlowControl } from '@/components/MiroFlowControl'"
pattern: "MiroFlowControl"
---
<objective>
Criar o componente MiroFlowControl.tsx e integrá-lo ao BiWorkspace.tsx como nova tab "Científico" com toggle "Modo Científico".
Purpose: Expõe as análises científicas MiroFlow diretamente no contexto BI, permitindo ao usuário acionar agentes LLM especializados ao lado dos dashboards Superset.
Output: Tab funcional em BiWorkspace com formulário de análise, seletor de agente e display de resultado.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/03-miroflow-embutido/03-02-SUMMARY.md
@.planning/phases/03-miroflow-embutido/03-RESEARCH.md
<interfaces>
<!-- BiWorkspace.tsx — estrutura de tabs (linhas 2870-2888) -->
// Estado:
const [activeTab, setActiveTab] = useState("overview");
// Estrutura:
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
<TabsList className="bg-[#1f334d] border border-[#c89b3c]/20 p1">
<TabsTrigger value="overview" className="data-[state=active]:bg-[#c89b3c] data-[state=active]:text-[#1f334d] text-white/70">
<LayoutDashboard className="w-4 h-4 mr-2" /> Visão Geral
</TabsTrigger>
<TabsTrigger value="datasources" ...> <Database .../> Fontes de Dados </TabsTrigger>
<TabsTrigger value="upload" ...> <Upload .../> Upload </TabsTrigger>
<TabsTrigger value="datasets" ...> <FileText .../> Consultas </TabsTrigger>
<TabsTrigger value="charts" ...> <BarChart3 .../> Gráficos </TabsTrigger>
<TabsTrigger value="backups" ...> <Archive .../> Backups </TabsTrigger>
<!-- ADICIONAR AQUI: <TabsTrigger value="cientifico" ...> <Brain .../> Científico </TabsTrigger> -->
</TabsList>
<TabsContent value="overview">...</TabsContent>
<TabsContent value="datasources">...</TabsContent>
<!-- ... outros TabsContent ... -->
<!-- ADICIONAR: <TabsContent value="cientifico"><MiroFlowControl /></TabsContent> -->
</Tabs>
<!-- Imports disponíveis em BiWorkspace.tsx — já importados, usar estes -->
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
// Lucide icons já importados: Bot, Brain NÃO está importado — adicionar ao import
// shadcn/ui components já disponíveis: Button, Card, CardContent, CardHeader, CardTitle,
// Badge, ScrollArea, Input, Textarea, Select, SelectContent, SelectItem, SelectTrigger, SelectValue
<!-- API pattern usado no projeto (apiRequest de wouter/queryClient) -->
// Verificar como outros componentes fazem POST — geralmente:
import { apiRequest } from "@/lib/queryClient";
// ou via useMutation do @tanstack/react-query
const mutation = useMutation({ mutationFn: (body) => apiRequest("POST", "/api/miroflow/analyze", body) });
<!-- AnalyzeResponse do backend (03-02) -->
interface AnalyzeResponse {
agent: string;
model: string;
result: string;
execution_id: string;
duration_ms: number;
}
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Criar client/src/components/MiroFlowControl.tsx</name>
<files>client/src/components/MiroFlowControl.tsx</files>
<read_first>
- /opt/arcadia_merged/client/src/lib/queryClient.ts (confirmar apiRequest signature)
- /opt/arcadia_merged/client/src/pages/BiWorkspace.tsx linhas 1-60 (imports disponíveis e padrão de componentes)
- /opt/arcadia_merged/client/src/components/ (listar arquivos para verificar convenções de nomenclatura)
</read_first>
<action>
Criar client/src/components/MiroFlowControl.tsx como componente React standalone.
O componente deve:
1. Exibir card com header "Modo Científico" e ícone Brain
2. Toggle switch ou badge "Ativado" quando em modo científico (estado local `enabled`)
3. Select para escolha do agente: Statistician (deepseek-r1:14b), Fiscal Auditor (deepseek-r1:14b), Researcher (llama3.1:8b)
4. Textarea para digitar a tarefa (placeholder específico por agente)
5. Botão "Analisar" que dispara POST /api/miroflow/analyze
6. Área de resultado com ScrollArea mostrando a resposta formatada
7. Estado de loading com Loader2 animado durante chamada
8. Exibir erros de forma legível (badge vermelho + mensagem)
9. Metadados do resultado: modelo usado, tempo de execução, execution_id (em texto pequeno)
Props: `currentDashboardData?: Record<string, unknown>` (opcional, para contexto futuro)
Usar useMutation do @tanstack/react-query para a chamada POST.
Estrutura de tipos locais:
```typescript
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;
}
```
Agentes disponíveis (hardcoded, sem fetch):
```typescript
const AGENTS = [
{ value: "statistician", label: "Statistician", model: "deepseek-r1:14b",
placeholder: "Ex: Analise a distribuição de vendas por região no último trimestre" },
{ value: "fiscal_auditor", label: "Fiscal Auditor", model: "deepseek-r1:14b",
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;
```
Estilização: seguir o padrão dark de BiWorkspace (bg-[#1f334d], text-white, border-[#c89b3c]/20, text-[#c89b3c]).
NÃO usar estados globais, stores ou contextos externos.
NÃO fazer fetch para /api/miroflow/health neste componente.
</action>
<verify>
<automated>cd /opt/arcadia_merged && npx tsc --noEmit --project tsconfig.json 2>&1 | grep -i "MiroFlowControl\|miroflow" | head -10 || echo "TypeScript OK"</automated>
</verify>
<acceptance_criteria>
- client/src/components/MiroFlowControl.tsx existe e exporta MiroFlowControl como default ou named export
- `npx tsc --noEmit` não retorna erros relacionados a MiroFlowControl.tsx
- Componente tem prop `currentDashboardData?: Record<string, unknown>`
- Select com os 3 agentes (statistician, fiscal_auditor, researcher)
- Textarea para task com placeholder dinâmico por agente selecionado
- useMutation configurado para POST /api/miroflow/analyze
- Estado de loading exibido (Loader2 ou spinner equivalente)
- Erro exibido quando mutation.isError
- Resultado exibido quando mutation.isSuccess com metadados (model, duration_ms)
- Nenhuma referência a modelos acima de 14B
</acceptance_criteria>
<done>MiroFlowControl.tsx criado, compila sem erros TypeScript. Componente pronto para ser importado pelo BiWorkspace.</done>
</task>
<task type="auto">
<name>Task 2: Adicionar tab "Científico" ao BiWorkspace.tsx</name>
<files>client/src/pages/BiWorkspace.tsx</files>
<read_first>
- /opt/arcadia_merged/client/src/pages/BiWorkspace.tsx linhas 2860-2920 (TabsList atual — localizar exatamente onde inserir)
- /opt/arcadia_merged/client/src/pages/BiWorkspace.tsx linhas 1-55 (imports para adicionar Brain e MiroFlowControl)
- /opt/arcadia_merged/client/src/pages/BiWorkspace.tsx linhas 2950-3050 (após os TabsContent existentes — localizar o fechamento para inserir novo TabsContent)
</read_first>
<action>
BiWorkspace.tsx é um arquivo grande (~3000 linhas). Fazer APENAS as 3 modificações cirúrgicas abaixo:
MODIFICAÇÃO 1 — Adicionar Brain ao import de lucide-react (linha ~10-50):
Localizar `import { ... } from "lucide-react"` e adicionar `Brain` à lista.
(Verificar se Brain já está importado antes de adicionar)
MODIFICAÇÃO 2 — Adicionar MiroFlowControl import (após os imports existentes):
```typescript
import { MiroFlowControl } from "@/components/MiroFlowControl";
```
MODIFICAÇÃO 3 — Adicionar TabsTrigger na TabsList (após o último TabsTrigger existente, antes do fechamento </TabsList>):
```tsx
<TabsTrigger value="cientifico" className="data-[state=active]:bg-[#c89b3c] data-[state=active]:text-[#1f334d] text-white/70">
<Brain className="w-4 h-4 mr-2" /> Científico
</TabsTrigger>
```
MODIFICAÇÃO 4 — Adicionar TabsContent (após o último TabsContent existente, antes do fechamento </Tabs>):
```tsx
<TabsContent value="cientifico">
<MiroFlowControl />
</TabsContent>
```
NÃO alterar nenhum TabsTrigger ou TabsContent existente.
NÃO modificar o estado activeTab ou qualquer lógica existente.
NÃO reformatar ou reorganizar o código existente.
NÃO remover nenhum import existente.
Verificar o arquivo antes e depois com grep para confirmar que os 4 tabs originais ainda existem.
</action>
<verify>
<automated>cd /opt/arcadia_merged && grep -c 'TabsTrigger value=' client/src/pages/BiWorkspace.tsx && npx tsc --noEmit --project tsconfig.json 2>&1 | grep -i "BiWorkspace\|MiroFlow" | head -5 || echo "TypeScript OK"</automated>
</verify>
<acceptance_criteria>
- `grep -c 'TabsTrigger value=' client/src/pages/BiWorkspace.tsx` retorna 7 (6 existentes + 1 novo "cientifico")
- `grep 'value="cientifico"' client/src/pages/BiWorkspace.tsx` retorna 2 linhas (TabsTrigger + TabsContent)
- `grep 'Brain' client/src/pages/BiWorkspace.tsx` retorna resultado (importado e usado)
- `grep 'MiroFlowControl' client/src/pages/BiWorkspace.tsx` retorna resultado (importado e usado)
- `npx tsc --noEmit` não retorna erros em BiWorkspace.tsx
- Tabs existentes ("overview", "datasources", "upload", "datasets", "charts", "backups") ainda presentes
- Nenhum outro código do arquivo foi alterado (git diff mostra apenas as 4 adições)
</acceptance_criteria>
<done>BiWorkspace.tsx contém tab "Científico" que renderiza MiroFlowControl. Tabs existentes preservadas. Compila sem erros.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
- MiroFlowControl.tsx: componente com toggle "Modo Científico", seletor de 3 agentes, textarea de tarefa e display de resultado
- BiWorkspace.tsx: tab "Científico" adicionada (7 tabs no total)
- Backend (planos 01 e 02): microserviço Python porta 8006 + proxy Node + KG logging
</what-built>
<how-to-verify>
1. Iniciar o servidor: `npm run dev` (ou verificar se já está rodando)
2. Iniciar o microserviço MiroFlow: `MIROFLOW_PORT=8006 python3 server/python/miroflow_service.py &`
3. Navegar para /bi-workspace no browser
4. Verificar que a tab "Científico" aparece no TabsList (ícone Brain + texto)
5. Clicar na tab "Científico"
6. Verificar que o painel MiroFlowControl renderiza sem erros JavaScript no console
7. Selecionar o agente "Statistician"
8. Digitar uma tarefa simples: "Liste os 3 principais insights sobre análise estatística"
9. Clicar em "Analisar"
10. Verificar que o estado de loading aparece (Loader2 girando)
11. Após resposta: verificar que o resultado é exibido com o modelo (deepseek-r1:14b) e tempo de execução
12. Verificar GET http://localhost:8006/health retorna {"online": true} em outro aba
13. Verificar as outras 6 tabs (overview, datasources, etc.) ainda funcionam normalmente
</how-to-verify>
<resume-signal>
Digite "aprovado" se tudo funcionar corretamente.
Ou descreva os problemas encontrados (ex: "tab não aparece", "erro 502 na análise", "loading infinito").
</resume-signal>
</task>
</tasks>
<verification>
```bash
# Verificar estrutura dos arquivos:
ls /opt/arcadia_merged/client/src/components/MiroFlowControl.tsx
# Verificar tab adicionada:
grep -n 'cientifico\|Científico\|MiroFlowControl\|Brain' /opt/arcadia_merged/client/src/pages/BiWorkspace.tsx
# Verificar compilação:
cd /opt/arcadia_merged && npx tsc --noEmit 2>&1 | tail -5
# Contagem de tabs:
grep -c 'TabsTrigger value=' /opt/arcadia_merged/client/src/pages/BiWorkspace.tsx
```
</verification>
<success_criteria>
- MiroFlowControl.tsx existe e exporta componente válido
- BiWorkspace.tsx tem 7 TabsTriggers (6 originais + "cientifico")
- TypeScript compila sem erros nos novos arquivos
- Usuário aprova verificação visual do checkpoint
- Tabs existentes do BiWorkspace continuam funcionando
</success_criteria>
<output>
Após conclusão e aprovação do checkpoint, criar `.planning/phases/03-miroflow-embutido/03-03-SUMMARY.md` com:
- Arquivos criados/modificados
- Screenshot ou descrição do estado visual da tab "Científico"
- Resultado da verificação humana
- Qualquer ajuste feito após o checkpoint
</output>

View File

@ -1,551 +0,0 @@
# Phase 3: MiroFlow Embutido - Research
**Researched:** 2026-03-25
**Domain:** Python agent framework (MiroFlow) integrado ao Node.js/Express + Ollama local + Superset bridge
**Confidence:** HIGH (baseado em inspeção direta do código-fonte e serviços ativos)
---
## Summary
O Phase 3 conecta o framework Python MiroFlow (já clonado como submodule em `server/modules/miroflow/`) ao backend Node.js do Arcádia Suite, expondo um endpoint `POST /api/miroflow/analyze` que despacha para um microserviço Python FastAPI. Esse microserviço instancia agentes MiroFlow configurados via YAML, aponta para o Ollama local (já rodando na porta 11434 com `deepseek-r1:14b` instalado), e registra cada execução como nó imutável no KG (tabela `graph_nodes` via `/api/graph/nodes`).
O padrão de integração Node ↔ Python já está estabelecido no projeto: `server/bi/engine-proxy.ts` e `server/python/bi_analysis_service.py` demonstram o fluxo exato — Express proxy faz `fetch()` para FastAPI local; a resposta é repassada ao cliente. O novo microserviço MiroFlow (`miroflow_service.py`) seguirá o mesmo padrão, rodando na porta 8006. O agente `Researcher` exige `llama3.1:8b`, que **ainda não está instalado no Ollama** (apenas `deepseek-r1:14b`, `llama3.2:3b` e `nomic-embed-text` estão disponíveis).
No frontend, o componente `MiroFlowControl.tsx` será um painel overlay/tab injetado na página `BiWorkspace.tsx` que já usa o `SupersetDashboard` component. O toggle "Modo Científico" chama `POST /api/miroflow/analyze` e exibe a resposta estruturada ao lado do dashboard Superset.
**Primary recommendation:** Construir `server/python/miroflow_service.py` como FastAPI standalone (porta 8006), instanciar agentes MiroFlow com YAML inline (sem arquivos externos), usar `UnifiedOpenAIClient` apontando para Ollama via `OLLAMA_BASE_URL/v1`, registrar execuções via `createNode()` do graph service.
---
## User Constraints
*(Seção obrigatória — copiada do STATE.md/ROADMAP.md/PROJECT.md que fazem as vezes de CONTEXT.md)*
### Locked Decisions
- Stack: Node.js + TypeScript backend, React + TypeScript frontend, PostgreSQL, Neo4j KG
- IA: Ollama local MÁXIMO 14B — `deepseek-r1:14b` para Statistician e Fiscal Auditor, `llama3.1:8b` para Researcher
- **NUNCA usar modelos acima de 14B**: proibido citar ou planejar `deepseek-r1:32b`, `deepseek-r1:70b`, `llama3.1:70b` ou qualquer modelo maior
- MiroFlow já clonado em `server/modules/miroflow/` como submodule Python
- Branch de deploy: `Servidor`
- Superset em produção com RLS — não alterar configurações existentes
- Backend-first: API definida antes do frontend
- Auditoria imutável em todas as execuções (KG)
### Claude's Discretion
- Porta do microserviço MiroFlow (recomenda-se 8006 pelo padrão existente)
- Estrutura interna dos agentes MiroFlow (YAML inline vs arquivos externos)
- Forma de integrar o toggle no frontend (overlay, tab, painel lateral)
- Como fazer o `llama3.1:8b` ser baixado (wave 0 task vs setup manual)
### Deferred Ideas (OUT OF SCOPE)
- Versionamento Git-like de skills (Phase 2 pendente, não Phase 3)
- OpenClaw (Phase 4)
- Automation Fabric (Phase 5)
- Dev Center Completo (Phase 6)
---
## Standard Stack
### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| MiroFlow | 1.7.0 | Framework de agentes Python | Já no submodule, desenvolvido pela MiromindAI |
| FastAPI | >=0.115.0 | Microserviço Python | Padrão já usado em todos os serviços Python do projeto |
| uvicorn | >=0.32.0 | ASGI server | Usado em todos os serviços Python |
| omegaconf | (via miroflow deps) | Configuração YAML com variáveis | Dependência central do MiroFlow |
| hydra-core | >=1.3.2 | Composição de configurações | Dependência central do MiroFlow |
| openai | ==1.78.1 | Cliente HTTP para Ollama (OpenAI-compat.) | MiroFlow usa `UnifiedOpenAIClient` via openai SDK |
| httpx / aiohttp | >=0.28.1 / >=3.12.15 | HTTP async | Dependências do MiroFlow e FastAPI |
### Supporting
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| python-dotenv | >=1.1.1 | Leitura de .env | Inicialização do microserviço |
| pydantic | >=2.x | Validação de request/response | Models FastAPI |
| neo4j (driver) | opcional | Neo4j direto | Apenas se precisar grafo real; por ora usa `graph_nodes` PostgreSQL |
| psycopg2-binary | >=2.9.x | PostgreSQL direto | Se microserviço precisar escrever no banco |
### Alternatives Considered
| Instead of | Could Use | Tradeoff |
|------------|-----------|----------|
| Microserviço FastAPI separado | Subprocess Node → Python | subprocess é síncrono e frágil; FastAPI segue padrão estabelecido |
| YAML files externos | YAML inline (dict) em Python | Inline elimina dependência de caminhos de arquivo em container |
| Ollama direto (porta 11434) | Via LiteLLM gateway (porta 4000) | LiteLLM tem fallback e roteamento; Ollama direto é mais simples para agentes isolados |
**Installation (para o microserviço):**
```bash
# Instalar dependências no ambiente Python do servidor
pip install fastapi uvicorn omegaconf hydra-core openai python-dotenv pydantic psycopg2-binary
# Ou instalar o miroflow inteiro com uv:
cd server/modules/miroflow && uv sync
```
**Version verification:** Os pacotes acima são os listados no `pyproject.toml` do MiroFlow v1.7.0, verificado diretamente no arquivo.
---
## Architecture Patterns
### Recommended Project Structure
```
server/python/
├── miroflow_service.py # novo microserviço FastAPI (porta 8006)
├── bi_analysis_service.py # existente (referência de padrão)
└── ...
server/miroflow/
├── routes.ts # Express router: POST /api/miroflow/analyze
└── engine-proxy.ts # fetch() para http://localhost:8006
client/src/
├── pages/BiWorkspace.tsx # EXISTENTE — adicionar tab "Científico"
└── components/
└── MiroFlowControl.tsx # NOVO — toggle + resultado da análise
docker/python-entrypoint.sh # adicionar case "miroflow" → porta 8006
docker-compose.prod.yml # adicionar serviço miroflow (porta 8006)
```
### Pattern 1: Node → Python Microservice Proxy
**What:** Express recebe a request, faz fetch() para FastAPI, retorna o resultado. Sem subprocess, sem child_process.
**When to use:** Sempre que Node.js precisar de lógica Python (pandas, ML, agentes).
**Example:**
```typescript
// Source: server/bi/engine-proxy.ts (padrão existente)
const MIROFLOW_URL = `http://${process.env.MIROFLOW_HOST || "localhost"}:${process.env.MIROFLOW_PORT || "8006"}`;
async function proxyToMiroFlow(path: string, body: object): Promise<any> {
const response = await fetch(`${MIROFLOW_URL}${path}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
signal: AbortSignal.timeout(300_000), // 5min — modelos LLM são lentos
});
if (!response.ok) {
const err = await response.json().catch(() => ({ detail: response.statusText }));
throw new Error(err.detail || `MiroFlow error: ${response.status}`);
}
return response.json();
}
```
### Pattern 2: MiroFlow Agent com Ollama via UnifiedOpenAIClient
**What:** MiroFlow usa `UnifiedOpenAIClient` com `base_url` apontando para Ollama (OpenAI-compatible API em `/v1`).
**When to use:** Para todos os agentes do Phase 3.
**Example:**
```python
# Source: server/modules/miroflow/miroflow/llm/openai_client.py + config/llm/base.yaml
from miroflow.agents.factory import build_agent
from miroflow.agents.context import AgentContext
AGENT_CONFIG_STATISTICIAN = {
"type": "IterativeAgentWithToolAndRollback",
"max_turns": 10,
"llm": {
"provider_class": "UnifiedOpenAIClient",
"model_name": "deepseek-r1:14b",
"api_key": "ollama", # Ollama não valida api_key
"base_url": "http://localhost:11434/v1", # Ollama OpenAI-compat
"max_tokens": 8192,
"temperature": 0.7,
"top_p": 1.0, "min_p": 0.0, "top_k": -1,
"reasoning_effort": None, "repetition_penalty": 1.0,
"max_context_length": -1, "async_client": True,
"disable_cache_control": True, "keep_tool_result": -1,
"use_tool_calls": False, "oai_tool_thinking": False,
},
}
agent = build_agent(AGENT_CONFIG_STATISTICIAN)
ctx = AgentContext(task_description="Analise os seguintes dados SQL: ...")
result = await agent.run(ctx)
```
### Pattern 3: Registro Imutável no KG via graph_nodes
**What:** Após cada execução MiroFlow, registrar no PostgreSQL via `POST /api/graph/nodes`.
**When to use:** Toda execução de agente (success ou error).
**Example:**
```typescript
// Source: server/graph/service.ts createNode() — chamado pelo routes.ts de miroflow
await fetch("/api/graph/nodes", {
method: "POST",
body: JSON.stringify({
type: "miroflow_execution",
tenantId: req.user?.tenantId,
data: {
agent: "statistician",
model: "deepseek-r1:14b",
input: analysisRequest,
output: analysisResult,
auditHash: sha256(JSON.stringify({ executionId, input, output })),
executedAt: new Date().toISOString(),
}
})
});
```
### Pattern 4: MiroFlowControl.tsx como tab em BiWorkspace
**What:** BiWorkspace.tsx já tem estrutura de tabs (Tabs/TabsContent de shadcn/ui). Adicionar tab "Científico" que renderiza `MiroFlowControl`.
**When to use:** Ponto de entrada do toggle "Modo Científico" no contexto do BI.
**Example:**
```tsx
// Source: client/src/pages/BiWorkspace.tsx (padrão de tabs existente)
import { MiroFlowControl } from "@/components/MiroFlowControl";
// Dentro do <TabsList>:
<TabsTrigger value="cientifico"><Brain /> Científico</TabsTrigger>
// Dentro das <TabsContent>:
<TabsContent value="cientifico">
<MiroFlowControl currentDashboardData={currentData} />
</TabsContent>
```
### Anti-Patterns to Avoid
- **subprocess.run() no Node**: Não usar `child_process.spawn()` para chamar Python — use o microserviço FastAPI (padrão estabelecido)
- **Carregar miroflow no container Node**: O Dockerfile principal é Node.js puro — Python deve rodar em container separado (Dockerfile.python)
- **Modelos > 14B**: Nunca usar `deepseek-r1:32b`, `deepseek-r1:70b`, `llama3.1:70b` ou qualquer modelo acima de 14B parâmetros
- **Modificar configurações do Superset**: O proxy `/superset/*` e as rotas `/api/superset/*` existentes não devem ser alterados
- **Hardcoded OLLAMA_BASE_URL**: Sempre ler de env var `OLLAMA_BASE_URL` (default `http://localhost:11434`)
---
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Orquestração de agentes LLM | Loop manual de chamadas LLM | MiroFlow `IterativeAgentWithToolAndRollback` | Rollback automático, retry, tool calling já implementado |
| Proxy Node → Python | Child process / spawn | `fetch()` para FastAPI (padrão `engine-proxy.ts`) | Stateless, async, sem gestão de processos |
| Configuração de LLM | Classe própria de cliente | `UnifiedOpenAIClient` do MiroFlow | Já tem retry, context limit handling, tool protocol |
| Embedding da análise no Superset | iframe manual com JS | `SupersetDashboard.tsx` já existente | Guest token, SDK do Superset, error handling prontos |
| Logs de execução imutáveis | Nova tabela no banco | `graph_nodes` + `createNode()` via `server/graph/service.ts` | Infraestrutura de KG já existente com embeddings |
| Validação de request/response | Manual | Pydantic models + FastAPI auto-validation | Type safety e OpenAPI spec grátis |
**Key insight:** O projeto já tem todos os primitivos necessários. Phase 3 é essencialmente "conectar os pontos" — o agente Python já existe, o gateway LLM (LiteLLM/Ollama) já está configurado, o KG já tem tabelas, o padrão de proxy Node→Python já está implementado.
---
## Runtime State Inventory
> Fase é greenfield (novo microserviço e novas rotas). Não há renomeações ou migrações de dados existentes.
| Category | Items Found | Action Required |
|----------|-------------|------------------|
| Stored data | `graph_nodes` no PostgreSQL — usado pelo KG existente | Nenhum — novas inserções com `type: "miroflow_execution"` |
| Live service config | Ollama rodando na porta 11434 com `deepseek-r1:14b` instalado | `llama3.1:8b` NÃO instalado — precisa de `ollama pull llama3.1:8b` |
| OS-registered state | Nenhum Task Scheduler / cron para MiroFlow | Nenhuma ação necessária |
| Secrets/env vars | `OLLAMA_BASE_URL=http://ollama:11434` já no `.env`; `LITELLM_API_KEY` existente | Adicionar `MIROFLOW_PORT=8006` no `.env` e docker-compose |
| Build artifacts | `server/modules/miroflow/` tem `uv.lock` mas sem `.venv` instalado | Wave 0: instalar dependências via `uv sync` ou `pip install` no Dockerfile.python |
**Modelo faltando:** `llama3.1:8b` não está instalado no Ollama (`False` confirmado por `curl http://localhost:11434/api/tags`). Instalado: `deepseek-r1:14b` (8.4GB), `arcadia-agent:latest` (2GB), `llama3.2:3b` (2GB), `nomic-embed-text` (274MB). O plano deve incluir `ollama pull llama3.1:8b` como task de Wave 0 ou pré-requisito.
---
## Environment Availability
| Dependency | Required By | Available | Version | Fallback |
|------------|------------|-----------|---------|----------|
| Ollama API `:11434` | Todos os agentes MiroFlow | ✓ | 0.18.2 | — |
| `deepseek-r1:14b` | Statistician, Fiscal Auditor | ✓ | 8.4GB | — |
| `llama3.1:8b` | Researcher | ✗ | — | `llama3.2:3b` (degraded) ou `deepseek-r1:14b` |
| Python 3.12 | Microserviço FastAPI | ✓ | 3.12.3 | — |
| `uv` | Instalar deps MiroFlow | ✓ | 0.9.30 | `pip3` |
| FastAPI/uvicorn | Microserviço | ✓ | fastapi 0.128.8 | — |
| `omegaconf` (sistema) | MiroFlow direto | ✗ | — | Instalar via `uv sync` no `server/modules/miroflow/` |
| PostgreSQL `:5432` | KG graph_nodes | ✓ | 16 | — |
| Neo4j `:7687` | KG real (opcional) | Requer profile `[kg]` | — | `graph_nodes` PostgreSQL (já usado) |
**Missing dependencies with no fallback:**
- `llama3.1:8b` para o agente Researcher — deve ser baixado em Wave 0 (`ollama pull llama3.1:8b`)
**Missing dependencies with fallback:**
- `omegaconf` no sistema: instalar com `pip3 install omegaconf hydra-core` ou rodar MiroFlow dentro de venv com `uv sync`
- `llama3.1:8b` temporariamente: usar `deepseek-r1:14b` como fallback até o download completar
---
## Common Pitfalls
### Pitfall 1: Ollama não expõe `/v1` sem flag explícita em versões antigas
**What goes wrong:** `UnifiedOpenAIClient` do MiroFlow usa `base_url=http://localhost:11434/v1` (OpenAI-compatible endpoint). Versões antigas do Ollama (<0.1.24) não tinham esse endpoint.
**Why it happens:** Ollama 0.18.2 (confirmado no servidor) já suporta `/v1/chat/completions`. Mas o endpoint retorna erro se o modelo não estiver carregado.
**How to avoid:** Verificar `GET http://localhost:11434/api/tags` antes de iniciar o agente. Pré-carregar o modelo com `ollama run deepseek-r1:14b` ou `ollama pull`.
**Warning signs:** `404 Not Found` na URL `/v1/chat/completions`, ou `model not found` no corpo da resposta.
### Pitfall 2: MiroFlow requer dependências Python não instaladas no container Node
**What goes wrong:** O `Dockerfile` principal é Node.js Alpine — não tem Python. Se tentar importar `miroflow` no contexto Node (via python-bridge ou similar), vai falhar.
**Why it happens:** A tentativa de `from omegaconf import ...` falha com `ModuleNotFoundError` porque o sistema Python não tem as deps do MiroFlow.
**How to avoid:** Manter a separação clara: MiroFlow roda SOMENTE em `Dockerfile.python` como microserviço FastAPI. Node nunca importa Python diretamente.
**Warning signs:** Erro de `ModuleNotFoundError: No module named 'omegaconf'` nos logs do app principal.
### Pitfall 3: Timeout do fetch() insuficiente para modelos grandes
**What goes wrong:** `deepseek-r1:14b` com raciocínio pode levar 60-120 segundos para responder. O fetch padrão tem timeout de 30s nos outros proxies.
**Why it happens:** O `bi/engine-proxy.ts` usa `BI_ENGINE_TIMEOUT = 30000`. Para LLMs de 14B, isso é insuficiente.
**How to avoid:** Usar `AbortSignal.timeout(300_000)` (5 minutos) no proxy MiroFlow. Implementar streaming ou polling se necessário.
**Warning signs:** `AbortError: The operation was aborted` nos logs do Node.
### Pitfall 4: Neo4j não está ativo por padrão — usar graph_nodes do PostgreSQL
**What goes wrong:** O `docker-compose.yml` tem Neo4j no profile `[kg]` — não está ativo em deploy padrão.
**Why it happens:** Neo4j é opcional; o KG do Arcádia usa `graph_nodes` PostgreSQL por padrão.
**How to avoid:** Para imutabilidade, usar `POST /api/graph/nodes` (PostgreSQL), não o driver `neo4j` Python direto. O `server/graph/service.ts` já implementa isso com hashing.
**Warning signs:** `Connection refused: 7687` se tentar conectar ao Neo4j sem o profile ativo.
### Pitfall 5: MiroFlow não tem `llama3.1:8b` instalado
**What goes wrong:** O agente Researcher usa `llama3.1:8b` mas esse modelo não está no Ollama (`False` confirmado por inspeção direta).
**Why it happens:** Apenas `deepseek-r1:14b`, `arcadia-agent:latest`, `llama3.2:3b` e `nomic-embed-text` foram instalados.
**How to avoid:** Incluir `ollama pull llama3.1:8b` como step explícito na Wave 0 do plano.
**Warning signs:** Agente Researcher falha com `model 'llama3.1:8b' not found`.
### Pitfall 6: BiWorkspace.tsx é uma página grande — cuidado ao modificar
**What goes wrong:** `BiWorkspace.tsx` tem muitas funcionalidades (datasets, charts, dashboards, backup, BI engine). Modificação descuidada pode quebrar features existentes.
**Why it happens:** O arquivo tem ~1000+ linhas com múltiplas tabs e estados.
**How to avoid:** Adicionar MiroFlowControl como componente SEPARADO importado, não modificar lógica existente. Adicionar apenas uma nova `TabsTrigger` e `TabsContent`.
**Warning signs:** Erros de TypeScript em outros tabs depois da edição.
---
## Code Examples
Verified patterns from official sources:
### MiroFlow Agent com Ollama (config inline Python)
```python
# Source: server/modules/miroflow/miroflow/agents/base.py + config/llm/base.yaml
import sys
sys.path.insert(0, "/app/server/modules/miroflow")
import asyncio
from miroflow.agents.factory import build_agent
from miroflow.agents.context import AgentContext
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
def make_agent_cfg(model: str) -> dict:
return {
"type": "IterativeAgentWithToolAndRollback",
"name": f"arcadia_{model.replace(':', '_')}",
"max_turns": 10,
"llm": {
"provider_class": "UnifiedOpenAIClient",
"model_name": model,
"api_key": "ollama",
"base_url": f"{OLLAMA_BASE_URL}/v1",
"max_tokens": 8192,
"temperature": 0.7,
"top_p": 1.0, "min_p": 0.0, "top_k": -1,
"reasoning_effort": None, "repetition_penalty": 1.0,
"max_context_length": -1, "async_client": True,
"disable_cache_control": True, "keep_tool_result": -1,
"use_tool_calls": False, "oai_tool_thinking": False,
},
}
async def run_agent(agent_type: str, task: str) -> str:
model = "deepseek-r1:14b" if agent_type in ("statistician", "fiscal_auditor") else "llama3.1:8b"
cfg = make_agent_cfg(model)
agent = build_agent(cfg)
ctx = AgentContext(task_description=task)
result = await agent.run(ctx)
return result.get("summary", str(result))
```
### FastAPI endpoint pattern (seguindo bi_analysis_service.py)
```python
# Source: server/python/bi_analysis_service.py (padrão existente)
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(title="Arcádia MiroFlow Service", version="1.0.0")
class AnalyzeRequest(BaseModel):
agent: str # "statistician" | "fiscal_auditor" | "researcher"
task: str
context: dict = {}
tenant_id: int | None = None
class AnalyzeResponse(BaseModel):
agent: str
model: str
result: str
execution_id: str
duration_ms: int
@app.post("/analyze", response_model=AnalyzeResponse)
async def analyze(req: AnalyzeRequest):
...
```
### Express proxy para MiroFlow (seguindo engine-proxy.ts)
```typescript
// Source: server/bi/engine-proxy.ts (padrão existente)
const MIROFLOW_URL = `http://${process.env.MIROFLOW_HOST || "localhost"}:${
process.env.MIROFLOW_PORT || "8006"
}`;
export function registerMiroFlowRoutes(app: Express): void {
app.post("/api/miroflow/analyze", async (req: Request, res: Response) => {
if (!req.isAuthenticated()) return res.status(401).json({ error: "Não autenticado" });
try {
const response = await fetch(`${MIROFLOW_URL}/analyze`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...req.body, tenant_id: (req.user as any)?.tenantId }),
signal: AbortSignal.timeout(300_000),
});
const data = await response.json();
// Registrar no KG
await registerExecutionInKG(req, data);
res.json(data);
} catch (err: any) {
res.status(502).json({ error: err.message });
}
});
app.get("/api/miroflow/health", async (_req, res) => {
try {
const r = await fetch(`${MIROFLOW_URL}/health`, { signal: AbortSignal.timeout(5000) });
res.json({ online: r.ok, url: MIROFLOW_URL });
} catch {
res.json({ online: false, url: MIROFLOW_URL });
}
});
}
```
### Registro no KG (graph_nodes)
```typescript
// Source: server/graph/service.ts createNode()
import { createNode } from "../graph/service";
import crypto from "crypto";
async function registerExecutionInKG(req: any, execData: any) {
const auditHash = crypto
.createHash("sha256")
.update(JSON.stringify(execData))
.digest("hex");
await createNode({
type: "miroflow_execution",
tenantId: req.user?.tenantId,
data: { ...execData, auditHash, immutable: true },
});
}
```
---
## State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| Llamaindex/LangChain para agentes | MiroFlow (framework próprio MiromindAI) | 2025-08 | Performance-first, benchmarks públicos, já no submodule |
| OpenAI API direta | Ollama local via LiteLLM gateway | 2025 (Arcádia) | Soberania total dos dados |
| Neo4j obrigatório para KG | graph_nodes PostgreSQL + Neo4j opcional | Phase 1 | Sem dependência de serviço extra em produção básica |
| Hydra config files externos | Config dict inline (Python) | MiroFlow 1.x | Sem dependência de paths, mais fácil em containers |
**Deprecated/outdated:**
- MiroFlow via CLI (`python run_single_task.py`): adequado para benchmark, mas não para microserviço de produção. Usar `web_app/main.py` como referência OU criar FastAPI própria.
- Modelos > 14B: explicitamente proibidos na arquitetura do projeto.
---
## Open Questions
1. **`llama3.1:8b` vs `llama3.2:3b` para Researcher**
- What we know: `llama3.1:8b` não está instalado; `llama3.2:3b` está (2GB, já disponível)
- What's unclear: Se João quer baixar `llama3.1:8b` (4.7GB a mais) ou aceitar `llama3.2:3b` como substituto para o Researcher
- Recommendation: Plano deve incluir `ollama pull llama3.1:8b` como Wave 0, com fallback para `llama3.2:3b` se bandwidth for problema
2. **MiroFlow via web_app existente vs FastAPI própria**
- What we know: `server/modules/miroflow/web_app/main.py` é uma FastAPI completa com session manager e task executor; `server/python/bi_analysis_service.py` é um FastAPI simples sem estado
- What's unclear: Usar o `web_app` completo do MiroFlow (mais funcionalidades) ou escrever `miroflow_service.py` minimal seguindo o padrão de outros serviços
- Recommendation: Escrever `miroflow_service.py` minimal — mais simples, sem session management, sem frontend próprio do MiroFlow
3. **Registro de execuções: graph_nodes vs skill_executions**
- What we know: `skill_executions` é específico para Skills Arcádia; `graph_nodes` é KG genérico
- What's unclear: O requirement diz "KG" — se é necessário usar o Neo4j real ou PostgreSQL `graph_nodes` é suficiente
- Recommendation: Usar `graph_nodes` PostgreSQL com `type: "miroflow_execution"` — atende o requisito de imutabilidade sem depender do Neo4j (profile opcional)
4. **Posicionamento do MiroFlowControl na UI**
- What we know: `BiWorkspace.tsx` já tem sistema de tabs (LayoutDashboard, Database, BarChart3...); `Scientist.tsx` já existe como página standalone de análise científica
- What's unclear: Se o toggle deve ficar dentro do BiWorkspace (access contextual) ou se deve ser um botão flutuante ou se Scientist.tsx deve ser integrado ao BI
- Recommendation: Tab "Científico" dentro de `BiWorkspace.tsx` — mais coerente com o objetivo "integrado ao Superset"
---
## Validation Architecture
> `workflow.nyquist_validation` não encontrado em `.planning/config.json` (arquivo não existe). Tratado como habilitado.
### Test Framework
| Property | Value |
|----------|-------|
| Framework | pytest (Python) + vitest/jest implícito (TS) |
| Config file | Nenhum — Wave 0 deve criar `pytest.ini` para o microserviço |
| Quick run command | `pytest server/python/test_miroflow_service.py -x` |
| Full suite command | `pytest server/python/ -v` |
### Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| REQ-3.1 | MiroFlow configurado para Ollama local ≤14B | integration | `pytest tests/test_miroflow_ollama.py -x` | ❌ Wave 0 |
| REQ-3.2 | Agente Statistician analisa SQL com deepseek-r1:14b | integration | `pytest tests/test_miroflow_service.py::test_statistician -x` | ❌ Wave 0 |
| REQ-3.3 | Agente Fiscal Auditor valida NFe/SPED com deepseek-r1:14b | integration | `pytest tests/test_miroflow_service.py::test_fiscal_auditor -x` | ❌ Wave 0 |
| REQ-3.4 | Agente Researcher consulta KG com llama3.1:8b | integration | `pytest tests/test_miroflow_service.py::test_researcher -x` | ❌ Wave 0 |
| REQ-3.5 | `POST /api/miroflow/analyze` retorna análise estruturada | integration | `curl -X POST localhost:5000/api/miroflow/analyze ...` | ❌ Wave 0 |
| REQ-3.6 | MiroFlowControl.tsx toggle aparece no Superset/BiWorkspace | manual | Inspeção visual no browser | N/A |
| REQ-3.7 | Execuções registradas com imutabilidade no KG | unit | `pytest tests/test_miroflow_kg.py -x` | ❌ Wave 0 |
### Sampling Rate
- **Per task commit:** `pytest server/python/test_miroflow_service.py -x`
- **Per wave merge:** `pytest server/python/ -v`
- **Phase gate:** Health check `/api/miroflow/health` retorna `{"online": true}` + todos os 3 agentes respondem com modelo correto
### Wave 0 Gaps
- [ ] `server/python/test_miroflow_service.py` — cobre REQ-3.1, 3.2, 3.3, 3.4, 3.5, 3.7
- [ ] `server/python/miroflow_service.py` — o microserviço principal
- [ ] `server/miroflow/routes.ts` + `engine-proxy.ts` — rotas Express
- [ ] `client/src/components/MiroFlowControl.tsx` — componente React
- [ ] `ollama pull llama3.1:8b` — modelo necessário para Researcher (Wave 0 setup)
- [ ] Instalar dependências MiroFlow: `pip install omegaconf hydra-core openai` (ou `uv sync` no diretório do submodule)
---
## Sources
### Primary (HIGH confidence)
- Inspeção direta: `/opt/arcadia_merged/server/modules/miroflow/pyproject.toml` — versão 1.7.0, dependências confirmadas
- Inspeção direta: `/opt/arcadia_merged/server/modules/miroflow/miroflow/llm/openai_client.py``UnifiedOpenAIClient`, `base_url` configurável
- Inspeção direta: `/opt/arcadia_merged/server/modules/miroflow/miroflow/agents/base.py``BaseAgent`, `AgentContext`, `build_agent()`
- Inspeção direta: `/opt/arcadia_merged/server/modules/miroflow/config/llm/base.yaml` — campos obrigatórios do LLM config
- Inspeção direta: `/opt/arcadia_merged/server/bi/engine-proxy.ts` — padrão estabelecido de proxy Node → Python
- Inspeção direta: `/opt/arcadia_merged/server/python/bi_analysis_service.py` — padrão do microserviço FastAPI
- Inspeção direta: `/opt/arcadia_merged/docker/litellm-config.yaml``deepseek-r1:14b` já configurado como `arcadia-default`
- Inspeção direta: `/opt/arcadia_merged/docker-compose.prod.yml``OLLAMA_BASE_URL` configurado, serviço `ollama` com profile `[ai]`
- Runtime check: `curl http://localhost:11434/api/tags` — modelos instalados confirmados
- Runtime check: `curl http://localhost:11434/api/version` — Ollama 0.18.2 com `/v1` support
### Secondary (MEDIUM confidence)
- Inspeção direta: `/opt/arcadia_merged/server/modules/miroflow/miroflow/agents/iterative_agent_with_rollback.py` — tipo `IterativeAgentWithToolAndRollback` confirmado
- Inspeção direta: `/opt/arcadia_merged/server/modules/miroflow/web_app/main.py` — FastAPI web_app existente no submodule (alternativa não usada)
- Inspeção direta: `/opt/arcadia_merged/server/graph/service.ts``createNode()`, `graph_nodes` tabela
### Tertiary (LOW confidence)
- Inferência sobre tempo de resposta do `deepseek-r1:14b` (60-120s) baseada em conhecimento geral de modelos 14B — não medido diretamente no servidor
---
## Metadata
**Confidence breakdown:**
- Standard stack: HIGH — verificado diretamente nos arquivos do submodule e nas dependências instaladas
- Architecture: HIGH — padrões copiados de código existente funcionando em produção
- Pitfalls: HIGH (3-5) e MEDIUM (1-2) — baseados em inspeção direta do runtime e código
- Ambiente: HIGH — testado com curl direto ao Ollama e listagem de modelos
**Research date:** 2026-03-25
**Valid until:** 2026-04-25 (30 dias; stack estável, Ollama versão fixada)
---
## RESEARCH COMPLETE

View File

@ -1,97 +0,0 @@
# Phase 4 Context — OpenClaw Embutido
> Generated in --auto mode on 2026-03-26. All decisions auto-selected with recommended defaults.
## Phase Goal
Skills emergentes criadas automaticamente a partir de padrões detectados em `skill_executions`.
## Prior Context Applied
- **Stack de IA:** Ollama local via LiteLLM — usar `llama3.1:8b` para geração de drafts (leve, rápido)
- **Backend-first:** API definida antes do frontend
- **Auditoria imutável:** todas as execuções registradas com SHA-256
- **Schema já existe:** `detected_patterns` e `skill_suggestions` em `shared/schema.ts` — usar sem alterar
---
## Decisions
### 1. O que constitui um "padrão"?
**[auto] Mesma skill executada ≥3 vezes em 30 dias com confiança ≥80%**
- Fonte de dados: tabela `skill_executions`
- Agrupamento: `skillId + userId` (por usuário, não global)
- Confiança calculada como: `(frequency / max_frequency_in_window) * 100`
- Janela: `lastSeenAt - firstSeenAt ≤ 30 dias`
- Thresholds alinhados com o roadmap: min 3 ocorrências, 30 dias, 80% confiança
### 2. Trigger do PatternDetector
**[auto] Cron job a cada hora (setInterval no servidor Node.js)**
- Sem dependência de infraestrutura externa (sem Redis, sem Bull)
- Ao iniciar, PatternDetector registra no startup do servidor
- Execuções recentes analisadas em lote; padrões gravados em `detected_patterns`
- Se padrão já existe (mesmo skillId + userId): atualiza frequency + lastSeenAt
### 3. Geração do body da skill emergente
**[auto] AI via llama3.1:8b (LiteLLM) gera o body do DRAFT**
- Ao atingir threshold: PatternDetector cria entrada em `skill_suggestions` (status: `pending`)
- Skill DRAFT gerada automaticamente em `arcadia_skills` (status: `draft`, source: `openclaw`)
- Body gerado via prompt para llama3.1:8b descrevendo o padrão detectado
- DRAFT aguarda aprovação — não é executável até ser `published`
### 4. Widget de notificação
**[auto] Badge no header global + painel slide-in**
- Badge no ícone de notificações existente no header (sem criar novo componente de header)
- Ao clicar: slide-in panel lateral mostrando lista de sugestões pendentes
- Cada sugestão mostra: nome sugerido, padrão detectado, frequência, confiança, body preview
- Ações inline: "Aceitar" → promove para skill publicada | "Rejeitar" → status `rejected`
### 5. Aprovação — onde?
**[auto] Tab "Sugestões" em `/skills` (página existente) — Dev Center completo na Phase 6**
- Sem criar nova rota — adicionar tab na página `/skills` já existente
- Tab lista `skill_suggestions` com status `pending`
- Fluxo: aceitar → cria skill publicada a partir do DRAFT | rejeitar → arquiva sugestão
- Dev Center completo (Phase 6) herdará esse fluxo
---
## Reusable Assets Identified
- `shared/schema.ts``detectedPatterns`, `skillSuggestions` já definidos (Phase 1)
- `server/skills/engine.ts` — SkillEngine para criar DRAFT skills programaticamente
- `server/skills/routes.ts` — rota `/api/skills` existente, adicionar sub-rotas de sugestões
- `server/skills/versioning.ts` — SHA-256 audit logging (reusar padrão)
- `client/src/pages/BiWorkspace.tsx` — referência de como adicionar tabs a uma página existente
- `docker/litellm-config.yaml` — llama3.1:8b já configurado como Tier 2
---
## Scope Boundary
**Incluído nesta fase:**
- PatternDetector backend (cron + análise de skill_executions)
- Geração de DRAFT via AI
- Widget de notificação + slide-in
- Tab "Sugestões" em /skills com aprovação/rejeição
**Fora de escopo (phases futuras):**
- Dev Center completo (Phase 6)
- Detecção de padrões em outras fontes além de skill_executions
- Pattern sharing entre tenants
---
## Plan Breakdown Suggested
- **04-01:** PatternDetector service (backend) — cron, análise, gravação em detected_patterns + skill_suggestions + DRAFT creation via AI
- **04-02:** API routes + frontend widget (badge + slide-in) + tab "Sugestões" em /skills

View File

@ -1,97 +0,0 @@
# Phase 5 Context — Automation Fabric
> Generated in --auto mode on 2026-03-26. All decisions auto-selected with recommended defaults.
## Phase Goal
Automações unificadas sob runtime único — 5 runtimes tipados substituindo a fragmentação entre XOS (`xos_automations`) e Central (`automations`), com `AutomationCenter.tsx` como visão unificada.
## Prior Context Applied
- **Stack Node.js/TypeScript:** runtimes ficam no servidor Express, sem novos microserviços Python
- **Backend-first:** API definida antes do frontend
- **DB imutável:** tabelas `automations` e `xos_automations` não são alteradas — migração virtual
- **Padrão de phases anteriores:** wrappers leves sobre código existente, sem reescritas
---
## Decisions
### 1. Estratégia de migração
**[auto] Unificação virtual — zero migração de dados**
- Tabelas `automations` e `xos_automations` permanecem intactas
- Nova camada `server/automation-fabric/` agrega os dois via API layer
- `AutomationCenter.tsx` consome endpoint unificado `/api/automation-fabric/list`
- Endpoint retorna rows de ambas as tabelas com campo `source: "central" | "xos"`
- Dados existentes: sem risco de perda, sem downtime, sem migration script
### 2. Os 5 runtimes
**[auto] Classes TypeScript leves — roteamento para serviços existentes**
Implementados em `server/automation-fabric/runtimes/`:
| Runtime | Responsabilidade | Delega para |
|---|---|---|
| `WorkflowEngine` | Executa ações sequenciais com condições | `AutomationService` existente |
| `RuleEngine` | Avalia condições JSONB e dispara ações | Lógica inline, sem dependência |
| `AgentExecutor` | Executa automações via agente Manus | `ManusService` |
| `ScheduleEngine` | Gerencia cron/interval | `scheduledTasks` + setInterval existente |
| `EventEngine` | Webhook/event triggers | Registra listeners no Express |
- `AutomationFabricService` orquestra: dado tipo de trigger → escolhe runtime correto
- Trigger routing: `schedule` → ScheduleEngine, `event`/`webhook` → EventEngine, `agent` → AgentExecutor, default → WorkflowEngine
### 3. Rota do AutomationCenter
**[auto] Nova rota `/automations-center` — existentes intocadas**
- `/automations` (Central) e `/xos/automations` (XOS) permanecem sem alteração
- Nova página `client/src/pages/AutomationCenter.tsx` em `/automations-center`
- Adicionada ao nav/router sem remover rotas existentes
### 4. UI do AutomationCenter
**[auto] Lista unificada com badge de fonte + filtros inline**
- Lista flat com cards: nome, descrição, trigger type, última execução, status ativo/inativo
- Badge colorido: `"Central"` (azul) | `"XOS"` (roxo)
- Filtros: por fonte, por status (ativo/inativo), por trigger type
- Toggle inline enable/disable via PATCH ao respectivo endpoint original
- Execução manual via botão "Executar" → POST `/api/automation-fabric/{id}/run`
- Empty state informativo
---
## Reusable Assets Identified
- `server/automations/service.ts``AutomationService.runAutomation()` (reusar no WorkflowEngine)
- `server/automations/routes.ts` — padrão de rota com auth check
- `server/manus/service.ts``manusService` para AgentExecutor
- `shared/schema.ts``automations`, `xos_automations`, `automationLogs` já existentes
- `client/src/pages/Automations.tsx` — referência de UX/layout para a nova página
- `client/src/pages/XosAutomations.tsx` — referência de cards e filtros
---
## Scope Boundary
**Incluído nesta fase:**
- `server/automation-fabric/` — 5 runtimes + `AutomationFabricService` + rotas unificadas
- `GET /api/automation-fabric/list` — lista unificada (central + xos)
- `POST /api/automation-fabric/:id/run` — execução via fabric
- `AutomationCenter.tsx` — página `/automations-center`
**Fora de escopo (phases futuras):**
- Editor visual de automações (Phase 6 / Dev Center)
- Migração física de dados para tabela unificada
- Novos tipos de trigger não existentes
---
## Plan Breakdown Suggested
- **05-01:** `server/automation-fabric/` — 5 runtimes + `AutomationFabricService` + routes (`/api/automation-fabric`)
- **05-02:** `AutomationCenter.tsx` — página unificada `/automations-center` com lista, filtros e execução inline

View File

@ -1,32 +0,0 @@
# 06-01 PLAN — Schema + API Agent Defs
## Tasks
1. **Add `arcadia_agent_defs` table to `shared/schema.ts`**
- Fields: id, tenantId, userId, name, description, spec (jsonb), status, version, lastTaskId, createdAt, updatedAt
- Run migration if needed
2. **Create `server/agent-defs/service.ts`**
- `createAgentDef()`, `getAgentDef()`, `listAgentDefs()`, `updateAgentDef()`, `deleteAgentDef()`
- `updateStatus()` helper for draft → assembling → ready → deployed
- `incrementVersion()` on deploy
3. **Create `server/agent-defs/routes.ts`**
- GET `/api/agent-defs` (list by tenantId)
- POST `/api/agent-defs` (create)
- PATCH `/api/agent-defs/:id` (update spec/status)
- DELETE `/api/agent-defs/:id`
- POST `/api/agent-defs/:id/deploy` (status → deployed, version++)
- POST `/api/agent-defs/:id/run` (POST to blackboard task)
4. **Register routes in `server/routes.ts`**
- Import and registerAgentDefRoutes()
## Reuse
- DB functions from existing patterns
- Blackboard service for task creation (no modification)
## Done Criteria
- API responds 200 on all endpoints
- Status transitions work: draft → assembling → ready → deployed
- `lastTaskId` links to blackboard_tasks

View File

@ -1,34 +0,0 @@
# 06-02 PLAN — Design + Assemble Tabs
## Tasks
1. **Extend `client/src/pages/DevCenter.tsx` — Design tab**
- Add "Design" tab to TabsList
- 3 mode buttons: Markdown Spec | Code Editor | Visual Flow
- Monaco Editor component (reuse existing from "Desenvolver" tab if possible)
- Save spec to `arcadia_agent_defs` on blur/button click
- Form: agent name, description, spec content
2. **Extend `client/src/pages/DevCenter.tsx` — Assemble tab**
- List `arcadia_agent_defs` where status = draft
- "Montar" button per agent → POST `/api/blackboard/task` with spec as context
- Poll `/api/blackboard/task/:id` every 3s while assembling
- Show progress (task status) in real-time
- On complete: update agent status → ready, show generated artifact
3. **Create `client/src/components/DesignStudio.tsx`** (if modularizing)
- Or inline in DevCenter tabs
4. **Create `client/src/components/AssembleLine.tsx`** (if modularizing)
- Or inline in DevCenter tabs
## Reuse
- Monaco Editor config from existing code
- Polling logic from existing task components
- Blackboard task fetch logic
## Done Criteria
- Design tab allows editing agent spec in 3 modes
- Assemble tab shows draft agents and polls until complete
- Status updates in real-time
- Artifacts visible after assembly

View File

@ -1,37 +0,0 @@
# 06-03 PLAN — Deploy + Galeria Tabs
## Tasks
1. **Extend `client/src/pages/DevCenter.tsx` — Deploy tab**
- List `arcadia_agent_defs` where status = ready OR deployed
- "Deploy" button → PATCH `/api/agent-defs/:id/deploy` (status → deployed, version++)
- "Re-montar" button → PATCH status back to draft
- Expand row → show last blackboard_artifact details (JSON viewer)
- Version badge per agent
2. **Extend `client/src/pages/DevCenter.tsx` — Galeria tab**
- Grid of cards: deployed agents only
- Card content: name, description, version, creator, status badge
- "Executar" button → POST `/api/agent-defs/:id/run` (new blackboard task)
- "Fork" button → POST create new agent with copied spec (name += " (Copy)")
- Filter: status dropdown, search by name
- Empty state message
3. **Create `client/src/components/OrchestrateCenter.tsx`** (if modularizing)
- Or inline in DevCenter tabs
4. **Create `client/src/components/AgentGallery.tsx`** (if modularizing)
- Or inline in DevCenter tabs
## Reuse
- Card/grid layout from AutomationCenter or Skills
- Status badge from existing components
- Blackboard task execution logic
## Done Criteria
- Deploy tab lists ready/deployed agents
- Deploy action increments version
- Re-montar reverts to draft
- Galeria shows deployed agents only
- Run/Fork actions work
- All filters functional

View File

@ -1,131 +0,0 @@
# Phase 6 Context — Dev Center Completo
> Generated in --auto mode on 2026-03-26. All decisions auto-selected with recommended defaults.
## Phase Goal
Fábrica completa de agentes integrada ao DevCenter existente: Design → Assemble → Deploy, com galeria de agentes por tenant.
## Prior Context Applied
- **Stack Node.js/TypeScript + React:** sem novos microserviços
- **Backend-first:** tabela + API antes do frontend
- **Blackboard já existe:** `server/blackboard/` com ArchitectAgent, GeneratorAgent, ValidatorAgent, ExecutorAgent — reusar sem modificar
- **DevCenter.tsx já existe em `/dev-center`:** adicionar tabs, não criar nova rota
- **Schema modular:** nova tabela pode ir em `shared/schema.ts` (é a fase certa — tabela de agentes faz parte do core do sistema)
- **Auditoria imutável:** execuções e artefatos já logados via `blackboard_artifacts`
---
## Decisions
### 1. Onde vive a nova UI?
**[auto] 4 novos tabs no DevCenter.tsx existente — sem nova rota**
- Tabs adicionados ao `<TabsList>` existente em `/dev-center`
- Tabs novos: `design`, `assemble`, `deploy`, `galeria`
- Tabs existentes (Desenvolver, Status, Analisar Repos, Ferramentas, Sistema, Preview, Histórico) permanecem intocados
- Nenhuma nova rota no App.tsx, nenhum novo import lazy
### 2. DesignStudio — modos do editor
**[auto] 3 modos: Markdown Spec, Code Editor, Visual Flow**
- **Markdown Spec** (padrão): textarea com Monaco em modo `markdown` — usuário descreve o agente em linguagem natural estruturada
- **Code Editor**: Monaco em modo `typescript` — para specs técnicas detalhadas ou código direto
- **Visual Flow**: lista de nós editáveis (JSON simples) — nome/tipo/conexões — NÃO um editor gráfico completo
- UML = descrito via texto em Markdown Spec (sem biblioteca de diagramas)
- Seletor de modo: 3 botões inline no topo do tab Design
### 3. Tabela de definições de agentes
**[auto] Nova tabela `arcadia_agent_defs` em `shared/schema.ts`**
Campos:
- `id` (serial PK)
- `tenantId` (varchar, nullable)
- `userId` (varchar — criador)
- `name` (varchar — nome do agente)
- `description` (text)
- `spec` (jsonb — conteúdo do DesignStudio: `{ mode, content }`)
- `status` (varchar — `draft | assembling | ready | deployed`)
- `version` (integer, default 1)
- `lastTaskId` (integer — FK para blackboard_tasks, nullable)
- `createdAt`, `updatedAt` (timestamp)
API: `/api/agent-defs` — CRUD padrão (GET list, POST create, PATCH update, DELETE)
### 4. AssembleLine — fluxo de montagem
**[auto] DesignStudio spec → POST /api/blackboard/task → GeneratorAgent → artefato linkado**
- Tab "Assemble" lista `arcadia_agent_defs` com status `draft`
- Botão "Montar" por agente: POST `/api/blackboard/task` com spec como contexto
- `title`: `"Montar agente: ${name}"`
- `description`: conteúdo do spec
- `context`: `{ agentDefId, spec, source: "agent-factory" }`
- Atualiza `arcadia_agent_defs.status``assembling`, salva `lastTaskId`
- Progresso: polling de `/api/blackboard/task/:id` a cada 3s
- Ao completar: status → `ready`, artefato gerado visível no painel de detalhes
- Reusar completamente os agentes Blackboard existentes — zero modificação
### 5. OrchestrateCenter — deploy e monitoramento
**[auto] Lista de agent_defs com ações de deploy + viewer de artefatos**
- Tab "Deploy": lista `arcadia_agent_defs` em status `ready` ou `deployed`
- Ação "Deploy": PATCH status → `deployed`, incrementa `version`
- Ação "Re-montar": volta para `draft`, decrementa version (permite iteração)
- Monitoramento: expandir linha → mostra último `blackboard_artifact` do `lastTaskId`
- Versionamento: campo `version` incrementado a cada novo deploy
- Sem infra externa — deploy = mudança de status no banco
### 6. Galeria de agentes
**[auto] Grid de cards por tenant — tab "Galeria"**
- Tab "Galeria": grid de cards com `arcadia_agent_defs` onde `status = "deployed"`
- Cada card: nome, descrição, badge de status, número de versão, criador
- Ação "Executar": POST `/api/blackboard/task` com spec do agente → cria nova task de execução
- Ação "Fork": duplica o agente def como novo draft (copia spec, incrementa nome)
- Filtro por status (todos / somente deployed) e busca por nome
- Empty state: "Nenhum agente implantado — crie um na aba Design"
---
## Reusable Assets Identified
- `server/blackboard/routes.ts``POST /api/blackboard/task`, `GET /api/blackboard/task/:id`, `GET /api/blackboard/tasks` — reusar sem modificar
- `server/blackboard/service.ts``createMainTask()`, `getTaskWithDetails()` — reusar
- `shared/schema.ts``blackboardTasks`, `blackboardArtifacts` — leitura e join
- `client/src/pages/DevCenter.tsx` — estrutura de tabs existente — adicionar sem remover
- `client/src/pages/AutomationCenter.tsx` — referência de UX: lista com filtros + badges
- `client/src/pages/Skills.tsx` — referência de galeria com cards
- Monaco Editor já instalado (usado no DevCenter tab "Desenvolver")
---
## Scope Boundary
**Incluído nesta fase:**
- Tabela `arcadia_agent_defs` + migration + API CRUD `/api/agent-defs`
- Tab "Design" (DesignStudio: 3 modos + Monaco)
- Tab "Assemble" (lista de defs + botão Montar + progresso polling)
- Tab "Deploy" (OrchestrateCenter: lista ready/deployed + ações deploy/re-montar + viewer)
- Tab "Galeria" (grid por tenant + run + fork)
- Backend endpoint `POST /api/agent-defs/:id/deploy` e `POST /api/agent-defs/:id/run`
**Fora de escopo (phases futuras):**
- Editor gráfico visual com React Flow ou similar
- Execução real do código gerado em sandbox (Phase 7)
- Integração com CI/CD ou containers reais
- Sharing de agentes entre tenants
---
## Plan Breakdown Suggested
- **06-01:** `shared/schema.ts` (tabela `arcadia_agent_defs`) + `server/agent-defs/routes.ts` (CRUD + `/deploy` + `/run`) + registro em `server/routes.ts`
- **06-02:** Tab "Design" (DesignStudio 3 modos) + Tab "Assemble" (lista + polling de progresso) no DevCenter.tsx
- **06-03:** Tab "Deploy" (OrchestrateCenter) + Tab "Galeria" (grid + run/fork) no DevCenter.tsx

View File

@ -1,71 +0,0 @@
# Quick Task 260326-kmz: Implementar Skill Fabric
**Description:** Implementar Skill Fabric — gerador e gestor de skills da Arcádia Suite
**Date:** 2026-03-26
**Mode:** quick
---
## Task 1: Backend — Skill Fabric Module
**Files:**
- `server/modules/skill-fabric/src/types/index.ts`
- `server/modules/skill-fabric/src/core/skill/Skill.entity.ts`
- `server/modules/skill-fabric/src/core/compiler/VisualCompiler.ts`
- `server/modules/skill-fabric/src/core/compiler/CodeCompiler.ts`
- `server/modules/skill-fabric/src/core/compiler/MarkdownCompiler.ts`
- `server/modules/skill-fabric/src/core/validator/ValidationPipeline.ts`
- `server/modules/skill-fabric/src/core/executor/SkillExecutor.ts`
- `server/modules/skill-fabric/src/core/executor/Sandbox.ts`
- `server/modules/skill-fabric/src/core/lifecycle/LifecycleManager.ts`
- `server/modules/skill-fabric/src/api/routes/skills.routes.ts`
- `server/modules/skill-fabric/src/api/controllers/skills.controller.ts`
- `server/modules/skill-fabric/index.ts`
**Action:** Create the full backend Skill Fabric module with types, entity, compilers, validator pipeline, executor/sandbox, lifecycle manager, and API routes. The project uses TypeScript + Express + Drizzle ORM. Reuse existing `db` and `skills` schema from `../../shared/schema`. No new DB migration needed — extend existing skills table logic.
**Context:**
- Existing `server/skills/routes.ts` has basic CRUD. The new module adds compilation, validation, execution, versioning, lifecycle.
- DB import: `import { db } from "@/db"` or `import { db } from "../../../db"` — check what works
- Existing schema: `skills`, `skillExecutions` from `../../shared/schema`
- Do NOT use vm2 (needs install) — use Node.js built-in `vm` module for sandbox
- Do NOT use ReactFlow/Monaco yet (frontend task) — backend only here
**Done:** Files created, module compiles without errors
---
## Task 2: Frontend — Skill Fabric UI
**Files:**
- `client/src/modules/skill-fabric/index.ts`
- `client/src/modules/skill-fabric/types.ts`
- `client/src/modules/skill-fabric/api.ts`
- `client/src/modules/skill-fabric/code-ide/components/SkillCodeEditor.tsx`
- `client/src/modules/skill-fabric/markdown-studio/components/MarkdownEditor.tsx`
- `client/src/modules/skill-fabric/shared/components/SkillToolbar.tsx`
- `client/src/modules/skill-fabric/shared/components/VersionSelector.tsx`
- `client/src/modules/skill-fabric/shared/components/ValidationPanel.tsx`
- `client/src/pages/SkillFabricPage.tsx`
**Action:** Create frontend Skill Fabric module. Use textarea-based code editor (Monaco not installed — don't add it). Use Tailwind CSS + shadcn/ui components matching the existing project style. Create a SkillFabricPage that lists skills, allows creating/editing (code + markdown modes), compiling, validating, and executing. Wire to the backend API routes from Task 1.
**Context:**
- Check `client/src/components/` for existing UI patterns (shadcn components available)
- Check `client/src/App.tsx` to see how routes are registered — add the new page there
- The visual canvas (ReactFlow) is a stretch goal — skip it, implement code + markdown modes only
- API base: `/api/skills` (existing) + `/api/skill-fabric` (new from Task 1)
**Done:** SkillFabricPage renders, skills can be listed and created
---
## Task 3: Register Routes & Integration
**Files:**
- `server/modules/loader.ts`
- `client/src/App.tsx` (or router file)
**Action:** Register the new skill-fabric backend routes in `server/modules/loader.ts`. Add the SkillFabricPage route in the frontend router. Verify existing skills route still works.
**Done:** `/api/skill-fabric/*` routes registered, frontend page accessible

View File

@ -1,91 +0,0 @@
# GSD Session Report
**Generated:** 2026-03-25 09:50 BRT
**Project:** Arcádia Suite — `arcadiasuite` (branch `Servidor`)
**Milestone:** Arcádia Agentic Suite — 6 Fases (~12 semanas)
---
## Session Summary
**Período:** 2026-03-24 11:30 → 2026-03-25 09:50 (sessões consecutivas)
**Fase atual:** Fase 2 — Skills Engine
**Commits nesta sessão:** 7
**Arquivos alterados:** 29 (+1.875 / -106 linhas)
---
## Work Performed
### Fase 1 — Fundação (CONCLUÍDA)
- `1c6c386` — SkillEngine POO + ReferenceParser + ReferenceResolver + schema (`arcadia_skills`, `skill_executions`)
- `66d8dfb` — MiroFlow e OpenClaw adicionados como git submodules
- `c9a0563` — Neo4j no Docker Compose (profile `kg`), Skills.tsx UI, melhorias Manus/chat/Agent
### Fase 2 — Skills Engine (EM ANDAMENTO)
- `3c61acf` — XOS: forms de cadastro restaurados (Novo Contato, Negócio, Atividade)
- `9bfc888` — Migration: suporte a upload RAR, SQL, SQL.GZ
- `194a8dc` — Rota `/skills` no App.tsx + autocomplete de referências `/` no Monaco
- `4057821` — Skill Marketplace (Biblioteca): `GET /api/skills/marketplace` + `POST /import` + UI grid
### Key Outcomes
- **SkillEngine** com herança POO (`extends`), composição via referências `/`, audit hash SHA-256
- **API REST** completa: CRUD + execute + histórico em `/api/skills`
- **Editor Monaco** com 3 tabs (Geral / Body / Params) + autocomplete de 9 prefixos de referências
- **Skill Marketplace** (Biblioteca): discovery de skills `system`, importação para `tenant` com clonagem + `extends` para origem
- **Neo4j** disponível via `docker compose --profile kg up`
- **Submodules** MiroFlow e OpenClaw presentes em `server/modules/`
---
## Files Changed (resumo por área)
| Área | Arquivos | Linhas |
|------|----------|--------|
| Skills (server) | `engine.ts`, `routes.ts`, `reference-parser.ts` | +400 |
| Skills (client) | `Skills.tsx` | +570 |
| Schema | `shared/schema.ts` | +96 |
| Infra | `docker-compose.yml`, `docker/litellm-config.yaml` | +35 |
| Manus/Chat | `service.ts`, `routes.ts`, `prompt.ts`, `routes.ts` | +350 |
| XOS | `XosCentral.tsx` | +200 |
| App | `App.tsx` | +2 |
| Outros | Agent, ProcessCompass, bi, compass, ide, etc. | +222 |
---
## Blockers & Open Items
**Fase 2 — pendente:**
- [ ] Sistema de versionamento de skills (Git-like)
**Pendências gerais (produção):**
- [ ] Commitar RLS Superset → main
- [ ] SOE.tsx stub → importar versão completa do Replit
- [ ] Seed de regras padrão em `soe_regras`
- [ ] Dashboards base no Superset (DRE, Fluxo de Caixa, Fiscal, RH)
**Próxima fase:**
- Fase 3 — MiroFlow embutido + MiroFlowBridge para Superset
---
## Estimated Resource Usage
| Métrica | Estimativa |
|---------|------------|
| Commits | 7 |
| Arquivos alterados | 29 |
| Linhas adicionadas | ~1.875 |
| Linhas removidas | ~106 |
| Ciclos plan/execute | ~4 (uma por feature principal) |
| Subagents spawned | 0 |
> **Nota:** contagens de tokens e custo requerem instrumentação via API.
> Estas métricas refletem apenas a atividade observável da sessão.
---
*Gerado por `/gsd:session-report`*

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

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

View File

@ -1,787 +0,0 @@
# 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*

View File

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

View File

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

View File

@ -1,666 +0,0 @@
# 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<EnrichedPayload> {
// 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<string, any>
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<SoeEvent, EventHandler[]> = {
'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: <Route path="/plus" component={Plus} />
// Por: <Route path="/plus" render={() => <Redirect to="/soe" />} />
```
**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' && <TabsTrigger value="config">Config</TabsTrigger>}
```
#### 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 ✅ CONCLUÍDA (2026-03-19) — João
```
[x] Criar server/soe/rule-engine/index.ts (SoeRuleEngine) — já existia | João | 2026-03-19
[x] Criar tabelas soe_regras e soe_eventos (drizzle push) — já existiam no schema | João | 2026-03-19
[x] Inserir ruleEngine.apply() em 3 rotas críticas:
- POST /api/soe/sales-orders ✅ plugado | João | 2026-03-19
- POST /api/soe/sales-orders/:id/generate-nfe ✅ plugado | João | 2026-03-19
- POST /api/soe/purchase-orders ✅ plugado | João | 2026-03-19
[x] Seed das regras fiscais padrão (CFOP, CST, validações NCM) — fiscal-rules.ts hardcoded | João | 2026-03-19
[ ] Testar: criar pedido → verificar que payload está sendo enriquecido
```
### Fase 2 — Event Bus + Contabilidade Automática ✅ CONCLUÍDA (2026-03-19) — João
```
[x] Criar server/soe/event-bus.ts — já existia | João | 2026-03-19
[x] Criar tabela soe_lancamentos (drizzle push) — já existia no schema | João | 2026-03-19
[x] Criar handlers: contabil-handler.ts + financial-handler.ts — já existiam | João | 2026-03-19
[x] Conectar: POST generate-nfe → emit('nfe_emitida') → lançamentos ✅ plugado | João | 2026-03-19
[ ] 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 ✅ CONCLUÍDA (2026-03-19) — João
```
[x] client/src/App.tsx: /plus → redirect para /soe ✅ | João | 2026-03-19
[x] client/src/pages/SOE.tsx: Config tab só para admins ✅ | João | 2026-03-19
[x] Manter proxy Plus funcionando (invisível) — proxy já existia, não alterado | João | 2026-03-19
[ ] 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)*

View File

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

View File

@ -1,5 +1,5 @@
import { Switch, Route, useLocation } from "wouter";
import { lazy, Suspense, useEffect } from "react";
import { lazy, Suspense, useEffect, useState } from "react";
import { queryClient } from "./lib/queryClient";
import { QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "@/components/ui/toaster";
@ -10,6 +10,7 @@ import { ProtectedRoute } from "@/lib/protected-route";
import { CommandPalette } from "@/components/CommandPalette";
import { KnowledgeCollectorInit } from "@/components/KnowledgeCollectorInit";
import { OpenClawWidget } from "@/components/openclaw/OpenClawWidget";
import { ErrorBoundary } from "@/components/ErrorBoundary";
import NotFound from "@/pages/not-found";
import AuthPage from "@/pages/auth-page";
@ -72,13 +73,56 @@ const XosSupervisor = lazy(() => import("@/pages/XosSupervisor"));
const XosReports = lazy(() => import("@/pages/XosReports"));
const XosProtocols = lazy(() => import("@/pages/XosProtocols"));
function LoadingFallback() {
const [showSlowLoading, setShowSlowLoading] = useState(false);
const [showError, setShowError] = useState(false);
useEffect(() => {
// Show "slow loading" message after 3 seconds
const slowTimer = setTimeout(() => setShowSlowLoading(true), 3000);
// Show error hint after 10 seconds
const errorTimer = setTimeout(() => setShowError(true), 10000);
return () => {
clearTimeout(slowTimer);
clearTimeout(errorTimer);
};
}, []);
return (
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh" }}>
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh", flexDirection: "column" }}>
<div style={{ textAlign: "center" }}>
<div style={{ width: 40, height: 40, border: "3px solid #e5e7eb", borderTopColor: "#3b82f6", borderRadius: "50%", animation: "spin 1s linear infinite", margin: "0 auto" }} />
<p style={{ marginTop: 16, color: "#6b7280" }}>Carregando...</p>
{showSlowLoading && (
<p style={{ marginTop: 8, color: "#9ca3af", fontSize: 14 }}>
Isso está demorando mais que o esperado...
</p>
)}
{showError && (
<div style={{ marginTop: 16, padding: 12, backgroundColor: "#fef3c7", borderRadius: 6, maxWidth: 300 }}>
<p style={{ color: "#92400e", fontSize: 13, margin: 0 }}>
A página pode ter um erro. Tente recarregar ou volte para a página anterior.
</p>
<button
onClick={() => window.location.reload()}
style={{
marginTop: 8,
padding: "6px 12px",
backgroundColor: "#f59e0b",
color: "white",
border: "none",
borderRadius: 4,
cursor: "pointer",
fontSize: 13
}}
>
Recarregar Página
</button>
</div>
)}
</div>
<style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
</div>
@ -87,6 +131,7 @@ function LoadingFallback() {
function Router() {
return (
<ErrorBoundary>
<Suspense fallback={<LoadingFallback />}>
<Switch>
<ProtectedRoute path="/" component={Cockpit} />
@ -153,6 +198,7 @@ function Router() {
<Route component={NotFound} />
</Switch>
</Suspense>
</ErrorBoundary>
);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -8,10 +8,22 @@ if (!process.env.DATABASE_URL) {
);
}
const client = new pg.Client({
// CORREÇÃO: Usar Pool em vez de Client único para melhor performance
// e evitar queries travadas quando conexão falha
const pool = new pg.Pool({
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
});
client.connect();
// Log de erros do pool
pool.on('error', (err) => {
console.error('[DB Pool] Erro inesperado:', err.message);
});
export const db = drizzle({ client, schema });
// Exportar pool para uso no session store
export { pool };
export const db = drizzle({ client: pool, schema });

View File

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

View File

@ -0,0 +1,54 @@
---
⚠️ **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 Normal file
View File

@ -0,0 +1,203 @@
# 🤖 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>
```

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