Compare commits
62 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
f14fafa531 | |
|
|
0fad3e05f7 | |
|
|
41a9626f20 | |
|
|
0c4a4232bd | |
|
|
c2c6a4a2b5 | |
|
|
a96c161f14 | |
|
|
423bf752db | |
|
|
21e953be83 | |
|
|
2807a9d9dd | |
|
|
5d9c034a42 | |
|
|
e27f2998e5 | |
|
|
58efb8c95c | |
|
|
cfb60b2d4b | |
|
|
4dcecd3e63 | |
|
|
a167d9aaef | |
|
|
6bd0ea6d4f | |
|
|
b037512250 | |
|
|
317926f600 | |
|
|
2222e78999 | |
|
|
f623ee20d8 | |
|
|
1989928329 | |
|
|
a6ac6e9dd9 | |
|
|
a7ecf0defb | |
|
|
95ee51bf68 | |
|
|
9ec61dd932 | |
|
|
bb7cff6c80 | |
|
|
da371af0e0 | |
|
|
524a049d03 | |
|
|
71e2b654bd | |
|
|
ea58a45ea1 | |
|
|
b0a8e73f67 | |
|
|
fcfce87df8 | |
|
|
a5adf7e096 | |
|
|
6182a9c490 | |
|
|
14e440cc1f | |
|
|
202101f0e0 | |
|
|
16bb5b9bfb | |
|
|
b3166173df | |
|
|
aa83dbdbce | |
|
|
015273ffdd | |
|
|
13abdbba3b | |
|
|
5c884d9066 | |
|
|
465775ec99 | |
|
|
1f23256032 | |
|
|
1cf73f55ba | |
|
|
c3c91ed966 | |
|
|
5a99236aff | |
|
|
08d6820e76 | |
|
|
7651167d72 | |
|
|
2916fb04ae | |
|
|
d971d07327 | |
|
|
06fe8af1fb | |
|
|
80f9352a7e | |
|
|
162bc4f943 | |
|
|
1fdeb72492 | |
|
|
5ba2bd963d | |
|
|
75953a8542 | |
|
|
2e76496ff4 | |
|
|
7ce8735c04 | |
|
|
849934dae8 | |
|
|
201580a47a | |
|
|
a2aff30b8c |
|
|
@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ✅*
|
||||
|
|
@ -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`
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"workflow": {
|
||||
"research": false,
|
||||
"plan_check": false,
|
||||
"verifier": false,
|
||||
"nyquist_validation": false
|
||||
},
|
||||
"model_profile": "budget"
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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)
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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`*
|
||||
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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]
|
||||
|
|
@ -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...
|
||||
|
|
@ -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 |
|
|
@ -1,812 +0,0 @@
|
|||
# Arcádia Tenants — Mapa Completo da Gestão Multi-Tenant
|
||||
|
||||
> Mapa da arquitetura multi-tenant da Arcádia Suite: hierarquia
|
||||
> Master/Parceiro/Cliente, sistema de planos, ativação de módulos,
|
||||
> empresas (Matriz/Filiais), comissões de parceiros, controle de acesso,
|
||||
> e middleware de isolamento.
|
||||
> Atualizado em: Março 2026
|
||||
|
||||
---
|
||||
|
||||
## 1. Visão Geral
|
||||
|
||||
A Arcádia Suite opera em modelo **multi-tenant hierárquico** com três níveis. Cada tenant possui um plano que define quais módulos da plataforma estão habilitados, e pode conter múltiplas empresas (CNPJ) para operações fiscais.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ HIERARQUIA DE TENANTS │
|
||||
│ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ MASTER │ ← Operador da plataforma │
|
||||
│ │ │ ← Vê tudo, controla tudo │
|
||||
│ │ tenantType: │ ← Pode criar parceiros │
|
||||
│ │ "master" │ ← Pode excluir tenants │
|
||||
│ └──────┬───────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────┼────────────┐ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ PARCEIRO A │ │ PARCEIRO B │ │ PARCEIRO C │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ tenantType: │ │ Consultorias │ │ Integradores │ │
|
||||
│ │ "partner" │ │ Revendas │ │ Franquias │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ partnerCode: │ │ comissão: % │ │ plano: │ │
|
||||
│ │ "PARC-001" │ │ sobre clientes│ │partner_pro │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌────┴────┐ ┌───┴───┐ ┌───┴───┐ │
|
||||
│ ▼ ▼ ▼ ▼ ▼ ▼ │
|
||||
│ ┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐ │
|
||||
│ │Cliente1││Cliente2││Cliente3││Cliente4││Cliente5││Cliente6│ │
|
||||
│ │ ││ ││ ││ ││ ││ │ │
|
||||
│ │tenant ││tenant ││tenant ││tenant ││tenant ││tenant │ │
|
||||
│ │Type: ││Type: ││Type: ││Type: ││Type: ││Type: │ │
|
||||
│ │"client"││"client"││"client"││"client"││"client"││"client"│ │
|
||||
│ │ ││ ││ ││ ││ ││ │ │
|
||||
│ │parent: ││parent: ││parent: ││parent: ││parent: ││parent: │ │
|
||||
│ │Parc.A ││Parc.A ││Parc.B ││Parc.B ││Parc.C ││Parc.C │ │
|
||||
│ │ ││ ││ ││ ││ ││ │ │
|
||||
│ │Plano: ││Plano: ││Plano: ││Plano: ││Plano: ││Plano: │ │
|
||||
│ │starter ││pro ││free ││enterpr.││starter ││pro │ │
|
||||
│ └────────┘└────────┘└────────┘└────────┘└────────┘└────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Visibilidade por tipo:
|
||||
|
||||
| Tipo | Vê | Cria | Exclui |
|
||||
|------|-----|------|--------|
|
||||
| **Master** | Todos os tenants | Parceiros e Clientes | Qualquer (exceto master) |
|
||||
| **Partner** | Seu tenant + seus clientes | Apenas clientes vinculados | Nenhum |
|
||||
| **Client** | Apenas seu próprio tenant | Nenhum | Nenhum |
|
||||
|
||||
---
|
||||
|
||||
## 2. Banco de Dados — Tabelas de Tenant
|
||||
|
||||
### 2.1 — tenants (Tabela Principal)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ tenants │
|
||||
│ Tabela: "tenants" — shared/schema.ts:849 │
|
||||
│ │
|
||||
│ ┌─────────── Identificação ──────────────────┐ │
|
||||
│ │ id serial PK │ │
|
||||
│ │ name text NOT NULL │ │
|
||||
│ │ slug text UNIQUE │ │
|
||||
│ │ email text │ │
|
||||
│ │ phone text │ │
|
||||
│ │ logoUrl text │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────── Hierarquia ─────────────────────┐ │
|
||||
│ │ tenantType "master" | "partner" | │ │
|
||||
│ │ "client" │ │
|
||||
│ │ parentTenantId integer (self-ref FK) │ │
|
||||
│ │ partnerCode text (código único parceiro) │ │
|
||||
│ │ commissionRate numeric(5,2) (% comissão) │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────── Plano e Limites ────────────────┐ │
|
||||
│ │ plan "free" | "starter" | "pro" │ │
|
||||
│ │ | "enterprise" | │ │
|
||||
│ │ "partner_starter" | │ │
|
||||
│ │ "partner_pro" │ │
|
||||
│ │ status "active" | "trial" | │ │
|
||||
│ │ "suspended" | "cancelled" │ │
|
||||
│ │ maxUsers integer (default 5) │ │
|
||||
│ │ maxStorageMb integer (default 1000) │ │
|
||||
│ │ features JSONB → TenantFeatures │ ← Módulos │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────── Billing ────────────────────────┐ │
|
||||
│ │ billingEmail text │ │
|
||||
│ │ trialEndsAt timestamp │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────── Dados Empresariais ─────────────┐ │
|
||||
│ │ cnpj text │ │
|
||||
│ │ tradeName text │ │
|
||||
│ │ address text │ │
|
||||
│ │ city, state text │ │
|
||||
│ │ segment text │ │
|
||||
│ │ notes, source text │ │
|
||||
│ │ commercialContact / commercialPhone │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────── Metadata ───────────────────────┐ │
|
||||
│ │ settings text (JSON config) │ │
|
||||
│ │ createdAt timestamp │ │
|
||||
│ │ updatedAt timestamp │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 — TenantFeatures (JSONB — Sistema de Módulos)
|
||||
|
||||
O campo `features` na tabela `tenants` é um JSONB que controla **quais módulos da Arcádia estão habilitados** para cada tenant. O tipo é definido em `shared/schema.ts:821` e espelhado no frontend em `client/src/hooks/use-tenant-features.ts:3`.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ TenantFeatures (JSONB) │
|
||||
│ │
|
||||
│ ┌─────────── Módulos Booleanos ──────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ ide → IDE integrada │ │
|
||||
│ │ whatsapp → WhatsApp multi-sessão │ │
|
||||
│ │ crm → CRM completo │ │
|
||||
│ │ erp → SOE/ERP │ │
|
||||
│ │ bi → Business Intelligence │ │
|
||||
│ │ manus → IA Manus (GPT-4o) │ │
|
||||
│ │ centralApis → APIs centrais │ │
|
||||
│ │ centralApisManage → Gerenciar APIs │ │
|
||||
│ │ comunidades → Chat interno │ │
|
||||
│ │ biblioteca → Biblioteca de conhecimento │ │
|
||||
│ │ bibliotecaPublish → Publicar na biblioteca │ │
|
||||
│ │ suporteN3 → Suporte nível 3 │ │
|
||||
│ │ retail → Módulo Varejo │ │
|
||||
│ │ plus → ERP Plus (Laravel) │ │
|
||||
│ │ fisco → Motor Fiscal │ │
|
||||
│ │ cockpit → Cockpit executivo │ │
|
||||
│ │ compass → Process Compass │ │
|
||||
│ │ production → Módulo Produção │ │
|
||||
│ │ support → Módulo Suporte │ │
|
||||
│ │ xosCrm → XOS CRM (Kanban) │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────── Configurações Numéricas ────────┐ │
|
||||
│ │ │ │
|
||||
│ │ whatsappSessions → Nº sessões WA (0-N) │ │
|
||||
│ │ maxChannels → Nº canais permitidos │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────── Configurações de Texto ─────────┐ │
|
||||
│ │ │ │
|
||||
│ │ ideMode → "none" | "no-code" | │ │
|
||||
│ │ "low-code" | "pro-code" │ │
|
||||
│ │ manusTools[] → Lista de tools habilitadas │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.3 — tenant_empresas (Matriz/Filiais)
|
||||
|
||||
Cada tenant pode ter **múltiplas empresas** (CNPJs) cadastradas — essencial para operações fiscais (NF-e/NFC-e) por filial.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ tenant_empresas │
|
||||
│ Tabela: "tenant_empresas" — shared/schema.ts:889 │
|
||||
│ │
|
||||
│ ┌─────────── Identificação ──────────────────┐ │
|
||||
│ │ id serial PK │ │
|
||||
│ │ tenantId FK → tenants.id (CASCADE) │ │
|
||||
│ │ razaoSocial text NOT NULL │ │
|
||||
│ │ nomeFantasia text │ │
|
||||
│ │ cnpj text NOT NULL │ │
|
||||
│ │ ie text (inscrição estadual) │ │
|
||||
│ │ im text (inscrição municipal) │ │
|
||||
│ │ email, phone text │ │
|
||||
│ │ tipo "matriz" | "filial" │ │
|
||||
│ │ status "active" | "inactive" │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────── Endereço ───────────────────────┐ │
|
||||
│ │ cep, logradouro, numero, complemento, │ │
|
||||
│ │ bairro, cidade, uf, codigoIbge │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────── Fiscal ─────────────────────────┐ │
|
||||
│ │ regimeTributario "simples" | "presumido" │ │
|
||||
│ │ | "real" │ │
|
||||
│ │ certificadoDigitalId FK certificado │ │
|
||||
│ │ ambienteFiscal "producao" | "homologacao" │ │
|
||||
│ │ serieNfe integer (default 1) │ │
|
||||
│ │ serieNfce integer (default 1) │ │
|
||||
│ │ plusEmpresaId integer (link ERP Plus) │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────── Metadata ───────────────────────┐ │
|
||||
│ │ createdAt, updatedAt timestamp │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Uso típico:**
|
||||
|
||||
```
|
||||
Tenant "Empresa ABC" (id: 5)
|
||||
├── Empresa Matriz: "ABC Comércio LTDA" (CNPJ: 12.345.678/0001-00)
|
||||
│ regime: simples, ambiente: producao, série NF-e: 1
|
||||
├── Filial SP: "ABC Comércio SP" (CNPJ: 12.345.678/0002-81)
|
||||
│ regime: simples, ambiente: producao, série NF-e: 2
|
||||
└── Filial RJ: "ABC Comércio RJ" (CNPJ: 12.345.678/0003-62)
|
||||
regime: simples, ambiente: homologacao, série NF-e: 1
|
||||
```
|
||||
|
||||
### 2.4 — tenant_users (Membership)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ tenant_users │
|
||||
│ Tabela: "tenant_users" — shared/schema.ts:923 │
|
||||
│ │
|
||||
│ id serial PK │
|
||||
│ tenantId FK → tenants.id (CASCADE) │
|
||||
│ userId FK → users.id (CASCADE) │
|
||||
│ role "owner" | "admin" | "member" │
|
||||
│ isOwner "true" | "false" │
|
||||
│ createdAt timestamp │
|
||||
│ │
|
||||
│ → Link N:N entre users e tenants │
|
||||
│ → Um usuário pode pertencer a múltiplos tenants │
|
||||
│ → Cada tenant tem um owner + admins + members │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.5 — tenant_plans (Planos Disponíveis)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ tenant_plans │
|
||||
│ Tabela: "tenant_plans" — shared/schema.ts:933 │
|
||||
│ │
|
||||
│ id serial PK │
|
||||
│ code text UNIQUE ("free", "starter", "pro", │
|
||||
│ "enterprise", "partner_starter", "partner_pro") │
|
||||
│ name text NOT NULL │
|
||||
│ description text │
|
||||
│ tenantType "master" | "partner" | "client" │
|
||||
│ maxUsers integer (default 5) │
|
||||
│ maxStorageMb integer (default 1000) │
|
||||
│ features JSONB → TenantFeatures │
|
||||
│ monthlyPrice integer (centavos) │
|
||||
│ yearlyPrice integer (centavos) │
|
||||
│ trialDays integer (default 14) │
|
||||
│ isActive "true" | "false" │
|
||||
│ sortOrder integer │
|
||||
│ createdAt timestamp │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.6 — partner_clients (Relacionamento Parceiro↔Cliente)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ partner_clients │
|
||||
│ Tabela: "partner_clients" — shared/schema.ts:951 │
|
||||
│ │
|
||||
│ id serial PK │
|
||||
│ partnerId FK → tenants.id (CASCADE) │
|
||||
│ clientId FK → tenants.id (CASCADE) │
|
||||
│ commissionRate numeric(5,2) — Override do rate do parceiro │
|
||||
│ status "active" | "suspended" | "ended" │
|
||||
│ notes text │
|
||||
│ startedAt timestamp │
|
||||
│ endedAt timestamp │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.7 — partner_commissions (Comissões dos Parceiros)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ partner_commissions │
|
||||
│ Tabela: "partner_commissions" — shared/schema.ts:963 │
|
||||
│ │
|
||||
│ id serial PK │
|
||||
│ partnerId FK → tenants.id (CASCADE) │
|
||||
│ clientId FK → tenants.id (CASCADE) │
|
||||
│ referenceMonth text ("2026-01", "2026-02", ...) │
|
||||
│ clientPlanCode text (código do plano do cliente) │
|
||||
│ clientPlanValue integer (valor do plano em centavos) │
|
||||
│ commissionRate numeric(5,2) NOT NULL │
|
||||
│ commissionValue integer NOT NULL (valor em centavos) │
|
||||
│ status "pending" | "approved" | "paid" | "cancelled" │
|
||||
│ approvedAt timestamp │
|
||||
│ paidAt timestamp │
|
||||
│ paymentReference text │
|
||||
│ createdAt timestamp │
|
||||
│ │
|
||||
│ Fluxo: pending → approved → paid │
|
||||
│ │
|
||||
│ Exemplo: │
|
||||
│ Parceiro A tem Cliente X no plano "pro" (R$ 299/mês) │
|
||||
│ commissionRate: 20% │
|
||||
│ commissionValue: 5980 centavos (R$ 59,80) │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Planos Pré-definidos (Seed)
|
||||
|
||||
A rota `POST /api/admin/plans/seed` cria os 6 planos padrão:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ PLANOS PARA CLIENTES │
|
||||
├────────────┬──────────┬──────────┬──────────────────────────────────┤
|
||||
│ Plano │ Preço/mês│ Usuários │ Módulos │
|
||||
├────────────┼──────────┼──────────┼──────────────────────────────────┤
|
||||
│ Gratuito │ R$ 0 │ 2 │ erp, crm │
|
||||
│ (free) │ │ │ │
|
||||
├────────────┼──────────┼──────────┼──────────────────────────────────┤
|
||||
│ Starter │ R$ 99 │ 5 │ erp, crm, bi, fisco, │
|
||||
│ │ │ │ whatsapp, compass │
|
||||
├────────────┼──────────┼──────────┼──────────────────────────────────┤
|
||||
│ Pro │ R$ 299 │ 15 │ erp, crm, bi, fisco, retail, │
|
||||
│ │ │ │ plus, whatsapp, manus, cockpit, │
|
||||
│ │ │ │ compass, support, biblioteca │
|
||||
├────────────┼──────────┼──────────┼──────────────────────────────────┤
|
||||
│ Enterprise │ R$ 999 │ 100 │ TODOS os módulos │
|
||||
│ │ │ 50GB │ (erp, crm, bi, fisco, retail, │
|
||||
│ │ │ │ plus, whatsapp, manus, ide, │
|
||||
│ │ │ │ cockpit, compass, production, │
|
||||
│ │ │ │ support, xosCrm, centralApis, │
|
||||
│ │ │ │ comunidades, biblioteca) │
|
||||
└────────────┴──────────┴──────────┴──────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ PLANOS PARA PARCEIROS │
|
||||
├──────────────────┬──────────┬──────────┬────────────────────────────┤
|
||||
│ Plano │ Preço/mês│ Usuários │ Módulos │
|
||||
├──────────────────┼──────────┼──────────┼────────────────────────────┤
|
||||
│ Parceiro Starter │ R$ 199 │ 10 │ erp, crm, bi, fisco, │
|
||||
│ │ │ │ retail, whatsapp, compass, │
|
||||
│ │ │ │ support │
|
||||
├──────────────────┼──────────┼──────────┼────────────────────────────┤
|
||||
│ Parceiro Pro │ R$ 499 │ 50 │ TODOS os módulos │
|
||||
│ │ │ 25GB │ (igual Enterprise) │
|
||||
└──────────────────┴──────────┴──────────┴────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Sistema de Papéis (Roles)
|
||||
|
||||
A Arcádia opera com **dois níveis de papéis** que se complementam:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ SISTEMA DUPLO DE ROLES │
|
||||
│ │
|
||||
│ ┌─────────── Papel do Sistema (users.role) ──────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ "master" → Superadmin, acesso total, bypass de tenant │ │
|
||||
│ │ "admin" → Administrador, acessa /api/admin/* │ │
|
||||
│ │ "user" → Usuário padrão, sem acesso admin │ │
|
||||
│ │ │ │
|
||||
│ │ Verificado no middleware global de admin: │ │
|
||||
│ │ if (role !== "admin" && role !== "master") → 403 │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────── Papel no Tenant (tenant_users.role) ────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ "owner" → Dono do tenant, criador, full control │ │
|
||||
│ │ "admin" → Administrador do tenant │ │
|
||||
│ │ "member" → Membro comum, acesso operacional │ │
|
||||
│ │ │ │
|
||||
│ │ Usado para scoping de dados e permissões intra-tenant │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Combinação no login (getEnrichedUser): │
|
||||
│ user.role + user.tenantId + user.tenantType + user.tenantRole │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Controle de Acesso — Helpers
|
||||
|
||||
O backend implementa dois helpers essenciais em `server/admin/routes.ts`:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ getAllowedTenantIds(user) │
|
||||
│ │
|
||||
│ Retorna os IDs de tenants que o usuário pode acessar: │
|
||||
│ │
|
||||
│ • master → null (acesso total, sem filtro) │
|
||||
│ • partner → [seu tenantId, ...clientIds vinculados] │
|
||||
│ • client → [apenas seu tenantId] │
|
||||
│ │
|
||||
│ Usado em: GET /tenants, GET /partner-clients, GET /commissions, │
|
||||
│ GET /empresas, GET /tenants/hierarchy │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ canAccessTenant(user, tenantId) │
|
||||
│ │
|
||||
│ Verifica se o usuário pode operar sobre um tenant específico: │
|
||||
│ │
|
||||
│ • master → true (sempre) │
|
||||
│ • partner → true se tenantId ∈ getAllowedTenantIds() │
|
||||
│ • client → true apenas se tenantId === user.tenantId │
|
||||
│ │
|
||||
│ Usado em: PATCH /tenants/:id, DELETE /empresas/:id, │
|
||||
│ POST /empresas, etc. │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Middleware de Módulos (requireModule)
|
||||
|
||||
O middleware `requireModule` em `server/retail/routes.ts:25` bloqueia endpoints quando um módulo não está ativo para o tenant:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ requireModule(moduleKey: keyof TenantFeatures) │
|
||||
│ │
|
||||
│ Fluxo: │
|
||||
│ 1. Identifica tenantId do usuário logado │
|
||||
│ 2. Busca tenant no banco │
|
||||
│ 3. Verifica features[moduleKey] === true │
|
||||
│ 4. Se false → 403 com mensagem: │
|
||||
│ { │
|
||||
│ error: "Módulo 'X' não está ativo para este tenant", │
|
||||
│ moduleKey: "plus", │
|
||||
│ action: "Ative o módulo em Admin → Módulos" │
|
||||
│ } │
|
||||
│ │
|
||||
│ Exemplo de uso: │
|
||||
│ router.post("/plus/sync/customers", │
|
||||
│ requireModule("plus"), ← Bloqueia se Plus não está ativo │
|
||||
│ async (req, res) => { ... } │
|
||||
│ ); │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Hook Frontend (useTenantFeatures)
|
||||
|
||||
O hook `useTenantFeatures()` em `client/src/hooks/use-tenant-features.ts` fornece acesso às features do tenant no frontend:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ useTenantFeatures() │
|
||||
│ │
|
||||
│ Fonte: GET /api/soe/tenant/modules │
|
||||
│ Cache: 60 segundos (staleTime: 60000) │
|
||||
│ │
|
||||
│ Retorna: │
|
||||
│ { │
|
||||
│ features: TenantFeatures, ← Objeto completo de features │
|
||||
│ isLoading: boolean, │
|
||||
│ plan: string, ← Código do plano atual │
|
||||
│ isEnabled: (key) => bool, ← Verificação rápida │
|
||||
│ } │
|
||||
│ │
|
||||
│ Defaults (quando não logado / sem tenant): │
|
||||
│ ide: true, crm: true, erp: true, manus: true, compass: true │
|
||||
│ Todos os demais: false │
|
||||
│ │
|
||||
│ Uso no componente: │
|
||||
│ const { isEnabled } = useTenantFeatures(); │
|
||||
│ if (isEnabled("bi")) { /* mostra menu BI */ } │
|
||||
│ if (isEnabled("retail")) { /* mostra módulo varejo */ } │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Interface de Administração
|
||||
|
||||
### 8.1 — Admin.tsx (Página Principal)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Admin.tsx — client/src/pages/Admin.tsx (2.635 linhas) │
|
||||
│ Rota: /admin │
|
||||
│ │
|
||||
│ 6 Abas: │
|
||||
│ ┌─────────────┬──────────┬────────────┬───────────┬──────────────┐ │
|
||||
│ │ Conexões │ Tarefas │ Knowledge │ Bibliotecas│ Módulos │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ WhatsApp │ Dev │ Knowledge │ Conteúdo │ Ativação │ │
|
||||
│ │ ERPNext │ Center │ Graph │ Docs │ por tenant │ │
|
||||
│ │ APIs │ Pipeline │ Q&A │ Templates │ Toggle on/ │ │
|
||||
│ │ │ │ │ │ off módulos │ │
|
||||
│ └─────────────┴──────────┴────────────┴───────────┴──────────────┘ │
|
||||
│ ┌─────────────┐ │
|
||||
│ │Casa de │ │
|
||||
│ │Máquinas │ │
|
||||
│ │ │ │
|
||||
│ │ Status dos │ │
|
||||
│ │ motores │ │
|
||||
│ │ (:8002-8006)│ │
|
||||
│ └─────────────┘ │
|
||||
│ │
|
||||
│ Aba "Módulos" renderiza: <MultiTenantSection /> │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 8.2 — MultiTenantSection (Gestão Completa)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ MultiTenantSection.tsx (1.486 linhas) │
|
||||
│ client/src/components/MultiTenantSection.tsx │
|
||||
│ │
|
||||
│ 4 Abas internas: │
|
||||
│ │
|
||||
│ ┌─ Tenants ──────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ Lista de tenants com: │ │
|
||||
│ │ • Nome, tipo (Master/Parceiro/Cliente) │ │
|
||||
│ │ • Plano atual e status │ │
|
||||
│ │ • Nº de filhos (childCount) │ │
|
||||
│ │ • Tenant pai (parentTenant) │ │
|
||||
│ │ • Ações: Editar, Ativar/Desativar módulos │ │
|
||||
│ │ │ │
|
||||
│ │ Formulário de criação: │ │
|
||||
│ │ • Nome, email, phone │ │
|
||||
│ │ • Tipo (master/partner/client) │ │
|
||||
│ │ • Plano (seleciona do tenant_plans) │ │
|
||||
│ │ • Tenant pai (para hierarquia) │ │
|
||||
│ │ • Features auto-preenchidas pelo plano selecionado │ │
|
||||
│ │ • Toggle individual de cada módulo │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─ Planos ───────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ CRUD de planos com: │ │
|
||||
│ │ • Código, nome, descrição │ │
|
||||
│ │ • Tipo de tenant (client/partner) │ │
|
||||
│ │ • Preço mensal e anual (centavos) │ │
|
||||
│ │ • Max usuários e storage │ │
|
||||
│ │ • Features (toggle de cada módulo) │ │
|
||||
│ │ • Botão "Seed" para criar planos padrão │ │
|
||||
│ │ • Botão "Propagar" para aplicar features do plano │ │
|
||||
│ │ a todos os tenants que usam esse plano │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─ Comissões ────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ Lista de comissões de parceiros: │ │
|
||||
│ │ • Parceiro, Cliente, Mês referência │ │
|
||||
│ │ • Valor do plano, taxa de comissão, valor da comissão │ │
|
||||
│ │ • Status (pending → approved → paid) │ │
|
||||
│ │ • Ações: Aprovar, Marcar como paga │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─ Empresas ─────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ EmpresasSubSection: │ │
|
||||
│ │ • Lista de empresas (Matriz/Filiais) por tenant │ │
|
||||
│ │ • CNPJ, Razão Social, Nome Fantasia │ │
|
||||
│ │ • IE, IM, Regime Tributário │ │
|
||||
│ │ • Endereço completo (CEP, logradouro, cidade, UF) │ │
|
||||
│ │ • Config Fiscal (ambiente, série NF-e, série NFC-e) │ │
|
||||
│ │ • Link com ERP Plus (plusEmpresaId) │ │
|
||||
│ │ • Ações: Criar, Editar, Excluir │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. API REST Completa (/api/admin/*)
|
||||
|
||||
### Tenants
|
||||
|
||||
| Método | Endpoint | Função | Acesso |
|
||||
|--------|----------|--------|--------|
|
||||
| GET | `/tenants` | Lista tenants (filtrado por acesso) | master/partner/client |
|
||||
| POST | `/tenants` | Cria tenant (resolve features do plano) | master/admin |
|
||||
| PATCH | `/tenants/:id` | Atualiza tenant (canAccessTenant) | master/partner |
|
||||
| DELETE | `/tenants/:id` | Exclui tenant (proteção master) | somente master |
|
||||
| GET | `/tenants/hierarchy` | Árvore hierárquica de tenants | master/partner |
|
||||
| GET | `/tenants/stats` | Estatísticas (total, por tipo, por status) | master/admin |
|
||||
|
||||
### Planos
|
||||
|
||||
| Método | Endpoint | Função |
|
||||
|--------|----------|--------|
|
||||
| GET | `/plans` | Lista todos os planos |
|
||||
| POST | `/plans` | Cria novo plano |
|
||||
| PATCH | `/plans/:id` | Atualiza plano |
|
||||
| DELETE | `/plans/:id` | Exclui plano |
|
||||
| POST | `/plans/:id/propagate` | Propaga features do plano para todos os tenants que o usam |
|
||||
| POST | `/plans/seed` | Cria 6 planos padrão |
|
||||
|
||||
### Parceiro-Cliente
|
||||
|
||||
| Método | Endpoint | Função |
|
||||
|--------|----------|--------|
|
||||
| GET | `/partner-clients` | Lista relacionamentos parceiro↔cliente |
|
||||
| POST | `/partner-clients` | Cria vínculo parceiro↔cliente |
|
||||
|
||||
### Comissões de Parceiros
|
||||
|
||||
| Método | Endpoint | Função |
|
||||
|--------|----------|--------|
|
||||
| GET | `/commissions` | Lista comissões (filtro por partnerId, status) |
|
||||
| PATCH | `/commissions/:id/approve` | Aprova comissão |
|
||||
| PATCH | `/commissions/:id/pay` | Marca como paga |
|
||||
|
||||
### Empresas (Matriz/Filiais)
|
||||
|
||||
| Método | Endpoint | Função |
|
||||
|--------|----------|--------|
|
||||
| GET | `/empresas` | Lista empresas (filtro por tenantId + acesso) |
|
||||
| GET | `/empresas/:id` | Detalhe da empresa |
|
||||
| POST | `/empresas` | Cria empresa (verifica canAccessTenant) |
|
||||
| PATCH | `/empresas/:id` | Atualiza empresa |
|
||||
| DELETE | `/empresas/:id` | Exclui empresa |
|
||||
|
||||
### Outros Admin
|
||||
|
||||
| Método | Endpoint | Função |
|
||||
|--------|----------|--------|
|
||||
| GET | `/profiles` | Lista perfis de usuários |
|
||||
| POST | `/profiles` | Cria perfil |
|
||||
| PATCH | `/profiles/:id` | Atualiza perfil |
|
||||
| DELETE | `/profiles/:id` | Exclui perfil |
|
||||
| GET | `/users` | Lista usuários (com tenant info) |
|
||||
| PATCH | `/users/:id` | Atualiza usuário |
|
||||
| PATCH | `/users/:id/status` | Altera status do usuário |
|
||||
| PATCH | `/partners/:id/status` | Altera status de parceiro |
|
||||
| PATCH | `/clients/:id/status` | Altera status de cliente |
|
||||
| GET | `/stats` | Estatísticas gerais (tenants, users, leads) |
|
||||
| GET | `/libraries` | Lista bibliotecas de conteúdo |
|
||||
|
||||
**Total: 33 endpoints de administração**
|
||||
|
||||
---
|
||||
|
||||
## 10. Fluxo Completo — Onboarding de Tenant
|
||||
|
||||
```
|
||||
┌──────────┐ ┌────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ 1. CRIAR │───▶│ 2. PLANO │───▶│ 3. FEATURES │───▶│ 4. EMPRESAS │
|
||||
│ TENANT │ │ │ │ │ │ │
|
||||
│ │ │ Seleciona │ │ Auto-resolve │ │ Cadastra │
|
||||
│ POST │ │ plano │ │ do plano ou │ │ Matriz │
|
||||
│ /admin/ │ │ (free, │ │ toggle │ │ + Filiais │
|
||||
│ tenants │ │ starter, │ │ individual │ │ │
|
||||
│ │ │ pro, etc) │ │ de módulos │ │ CNPJ, IE, │
|
||||
│ name, │ │ │ │ │ │ endereço, │
|
||||
│ email, │ │ maxUsers │ │ ide: true │ │ regime │
|
||||
│ type │ │ maxStorage │ │ crm: true │ │ tributário, │
|
||||
│ │ │ preço │ │ bi: false │ │ certificado │
|
||||
│ │ │ │ │ ... │ │ digital │
|
||||
└──────────┘ └────────────┘ └──────────────┘ └──────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐
|
||||
│ 5. VINCULAR │ │ 6. COMISSÕES │ │ 7. USUÁRIOS │
|
||||
│ PARCEIRO │ │ │ │ │
|
||||
│ (se partner) │ │ Automáticas │ │ Convida users │
|
||||
│ │ │ mensais │ │ tenant_users │
|
||||
│ POST /admin/ │ │ baseadas no │ │ roles: owner/admin/ │
|
||||
│ partner- │ │ plano do │ │ member │
|
||||
│ clients │ │ cliente │ │ │
|
||||
│ │ │ │ │ Scoping automático │
|
||||
│ partnerId │ │ pending → │ │ de todos os dados │
|
||||
│ clientId │ │ approved → │ │ por tenantId │
|
||||
│ commission% │ │ paid │ │ │
|
||||
└──────────────┘ └──────────────┘ └──────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Propagação de Plano
|
||||
|
||||
Quando um plano é atualizado, a feature de **propagação** atualiza todos os tenants que usam esse plano:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ POST /api/admin/plans/:id/propagate │
|
||||
│ │
|
||||
│ 1. Busca o plano por ID │
|
||||
│ 2. Pega features, maxUsers, maxStorageMb do plano │
|
||||
│ 3. Atualiza TODOS os tenants onde plan === plan.code: │
|
||||
│ UPDATE tenants SET │
|
||||
│ features = plan.features, │
|
||||
│ maxUsers = plan.maxUsers, │
|
||||
│ maxStorageMb = plan.maxStorageMb │
|
||||
│ WHERE plan = 'pro' │
|
||||
│ 4. Retorna: { propagated: 15, planCode: "pro" } │
|
||||
│ │
|
||||
│ Exemplo: │
|
||||
│ Plano "pro" ganha módulo "production" → │
|
||||
│ Propagar → 15 tenants "pro" recebem production: true │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Isolamento de Dados
|
||||
|
||||
Toda query de dados respeita o escopo do tenant:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ ISOLAMENTO POR tenantId │
|
||||
│ │
|
||||
│ Padrão em todas as queries: │
|
||||
│ │
|
||||
│ const tenantId = (req.user as any).tenantId; │
|
||||
│ const data = await db.select() │
|
||||
│ .from(tabela) │
|
||||
│ .where(eq(tabela.tenantId, tenantId)); │
|
||||
│ │
|
||||
│ Tabelas com tenantId: │
|
||||
│ • tenants (hierarquia) • crm_channels │
|
||||
│ • tenant_empresas • crm_threads │
|
||||
│ • crm_leads • crm_events │
|
||||
│ • crm_clients • crm_campaigns │
|
||||
│ • crm_opportunities • crm_products │
|
||||
│ • crm_partners • crm_contracts │
|
||||
│ • crm_pipeline_stages • crm_commission_rules │
|
||||
│ • crm_proposals • crm_quick_messages │
|
||||
│ • retail_activity_feed • valuation_* │
|
||||
│ │
|
||||
│ Resultado: Dados do Tenant A NUNCA são vistos pelo Tenant B │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. Arquivos-Chave
|
||||
|
||||
| Arquivo | Função | Linhas |
|
||||
|---------|--------|--------|
|
||||
| `shared/schema.ts` (L821-977) | TenantFeatures type + 6 tabelas tenant | ~160 |
|
||||
| `server/admin/routes.ts` | API admin (33 endpoints) | 876 |
|
||||
| `client/src/pages/Admin.tsx` | Página admin (6 abas) | 2.635 |
|
||||
| `client/src/components/MultiTenantSection.tsx` | Gestão multi-tenant (4 sub-abas) | 1.486 |
|
||||
| `client/src/hooks/use-tenant-features.ts` | Hook de features no frontend | 59 |
|
||||
| `server/retail/routes.ts` (L25) | Middleware requireModule | ~25 |
|
||||
| `server/storage.ts` (getEnrichedUser) | Enriquece user com tenant info | — |
|
||||
|
||||
---
|
||||
|
||||
## 14. Resumo Visual
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ ARCÁDIA MULTI-TENANT STACK │
|
||||
│ │
|
||||
│ ┌───── Autenticação ──────────────────────────────────────────┐ │
|
||||
│ │ Login → getEnrichedUser → user + tenantId + tenantType │ │
|
||||
│ │ + tenantRole + features │ │
|
||||
│ └─────────────────────────────────────────┬───────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────────────────────────▼───────────────────┐ │
|
||||
│ │ Middleware │ │
|
||||
│ │ ┌──────────┐ ┌─────────────┐ ┌────────────────────┐ │ │
|
||||
│ │ │requireAuth│ │requireModule │ │getAllowedTenantIds │ │ │
|
||||
│ │ │role check │ │feature check │ │canAccessTenant │ │ │
|
||||
│ │ └──────────┘ └─────────────┘ └────────────────────┘ │ │
|
||||
│ └─────────────────────────────────────────┬───────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────────────────────────▼───────────────────┐ │
|
||||
│ │ API /api/admin/* │ │
|
||||
│ │ 33 endpoints: Tenants, Plans, Empresas, Comissões │ │
|
||||
│ └─────────────────────────────────────────┬───────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────────────────────────▼───────────────────┐ │
|
||||
│ │ PostgreSQL │ │
|
||||
│ │ 6 tabelas: tenants, tenant_users, tenant_plans, │ │
|
||||
│ │ tenant_empresas, partner_clients, partner_commissions │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───── Frontend ──────────────────────────────────────────────┐ │
|
||||
│ │ Admin.tsx (6 abas) → MultiTenantSection (4 sub-abas) │ │
|
||||
│ │ useTenantFeatures() hook → GET /api/soe/tenant/modules │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
|
@ -1,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*
|
||||
|
|
@ -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 (2–3 semanas)
|
||||
```
|
||||
[ ] 1. Criar server/soe/ com estrutura básica
|
||||
[ ] 2. Criar tabelas: soe_empresa_config, soe_regras, soe_eventos, soe_lancamentos
|
||||
[ ] 3. Implementar Plus Adapter (encapsula chamadas ao Laravel)
|
||||
[ ] 4. Implementar Frappe Adapter (encapsula chamadas ao ERPNext)
|
||||
[ ] 5. Endpoint GET /api/soe/status (health check de todos os motores)
|
||||
[ ] 6. Migrar /api/fisco/* para passar pelo SOE
|
||||
```
|
||||
|
||||
### Fase 2 — Central Fiscal (2–3 semanas)
|
||||
```
|
||||
[ ] 7. Implementar Rule Engine para fiscal
|
||||
[ ] 8. Criar regras fiscais base (NCM, CFOP, tributação por regime)
|
||||
[ ] 9. POST /api/soe/fiscal/emitir-nfe → Plus (invisível)
|
||||
[ ] 10. POST /api/soe/fiscal/cancelar-nfe → Plus (invisível)
|
||||
[ ] 11. GET /api/soe/fiscal/situacao → agrega Plus + Python 8002
|
||||
[ ] 12. Atualizar frontend /fisco para usar /api/soe/fiscal/*
|
||||
```
|
||||
|
||||
### Fase 3 — Central Contábil (2 semanas)
|
||||
```
|
||||
[ ] 13. Implementar auto-lancamentos para eventos fiscais
|
||||
[ ] 14. Integrar Motor Python 8003 via SOE
|
||||
[ ] 15. GET /api/soe/contabil/dre → resultado automático
|
||||
[ ] 16. GET /api/soe/contabil/balancete → atualizado em tempo real
|
||||
[ ] 17. Atualizar frontend /contabil para usar /api/soe/contabil/*
|
||||
```
|
||||
|
||||
### Fase 4 — Central Financeiro (2 semanas)
|
||||
```
|
||||
[ ] 18. Regras de boleto automático (via Asaas)
|
||||
[ ] 19. Alertas de vencimento (via Manus/WhatsApp)
|
||||
[ ] 20. Conciliação bancária semi-automática
|
||||
[ ] 21. Previsão financeira com IA (Motor Python 8003 + Manus)
|
||||
[ ] 22. Atualizar frontend /financeiro para usar /api/soe/financeiro/*
|
||||
```
|
||||
|
||||
### Fase 5 — Manus + SOE (1 semana)
|
||||
```
|
||||
[ ] 23. Registrar novas tools Manus apontando para SOE
|
||||
[ ] 24. Manus usa SOE para responder perguntas de negócio
|
||||
[ ] 25. Dashboard SOE: visão geral de todos os eventos
|
||||
```
|
||||
|
||||
### Fase 6 — Central ERP (3 semanas)
|
||||
```
|
||||
[ ] 26. Frappe Adapter completo (produtos, pedidos, estoque, compras)
|
||||
[ ] 27. Regras de negócio ERP no SOE
|
||||
[ ] 28. Sincronização bidirecional SOE ↔ ERPNext (via eventos)
|
||||
[ ] 29. Atualizar /erp para usar /api/soe/erp/*
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. RESULTADO FINAL — O QUE O USUÁRIO EXPERIMENTA
|
||||
|
||||
```
|
||||
Antes (fragmentado):
|
||||
Usuário → /plus → vê Laravel diretamente
|
||||
Usuário → /erp → vê ERPNext diretamente
|
||||
Regras fiscais só no Plus
|
||||
Contabilidade manual
|
||||
Financeiro sem automação
|
||||
|
||||
Depois (SOE Central):
|
||||
Usuário → /fisco → emite NF-e, Plus executa invisível
|
||||
Usuário → /contabil → vê DRE gerado automaticamente
|
||||
Usuário → /financeiro → vê fluxo, previsões, alertas automáticos
|
||||
Usuário → Manus → pergunta "qual meu resultado?" → resposta completa
|
||||
|
||||
Plus e ERPNext: INVISÍVEIS, executando em background
|
||||
Regras: CENTRALIZADAS no SOE, editáveis
|
||||
Lançamentos: AUTOMÁTICOS baseados em eventos (NF-e, pagamentos, etc.)
|
||||
Auditoria: TOTAL de todas as operações
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. PRINCÍPIO ORIENTADOR
|
||||
|
||||
> **"O usuário não sabe o que está por trás. Ele só sabe que funciona."**
|
||||
>
|
||||
> O Arcádia Suite é o **Sistema Operacional** da empresa.
|
||||
> O Plus e o ERPNext são **drivers de hardware** — poderosos, mas invisíveis.
|
||||
> O SOE Central é o **kernel** que os orquestra com regras e inteligência.
|
||||
|
||||
---
|
||||
|
||||
*PLANO_SOE_CENTRAL.md — Arcádia Suite v3.0*
|
||||
*Gerado em: 2026-03-16*
|
||||
|
|
@ -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 (2–3 semanas)
|
||||
```
|
||||
[ ] 1. Criar server/soe/ com estrutura básica
|
||||
[ ] 2. Criar tabelas: soe_empresa_config, soe_regras, soe_eventos, soe_lancamentos
|
||||
[ ] 3. Implementar Plus Adapter (encapsula chamadas ao Laravel)
|
||||
[ ] 4. Implementar Frappe Adapter (encapsula chamadas ao ERPNext)
|
||||
[ ] 5. Endpoint GET /api/soe/status (health check de todos os motores)
|
||||
[ ] 6. Migrar /api/fisco/* para passar pelo SOE
|
||||
```
|
||||
|
||||
### Fase 2 — Central Fiscal (2–3 semanas)
|
||||
```
|
||||
[ ] 7. Implementar Rule Engine para fiscal
|
||||
[ ] 8. Criar regras fiscais base (NCM, CFOP, tributação por regime)
|
||||
[ ] 9. POST /api/soe/fiscal/emitir-nfe → Plus (invisível)
|
||||
[ ] 10. POST /api/soe/fiscal/cancelar-nfe → Plus (invisível)
|
||||
[ ] 11. GET /api/soe/fiscal/situacao → agrega Plus + Python 8002
|
||||
[ ] 12. Atualizar frontend /fisco para usar /api/soe/fiscal/*
|
||||
```
|
||||
|
||||
### Fase 3 — Central Contábil (2 semanas)
|
||||
```
|
||||
[ ] 13. Implementar auto-lancamentos para eventos fiscais
|
||||
[ ] 14. Integrar Motor Python 8003 via SOE
|
||||
[ ] 15. GET /api/soe/contabil/dre → resultado automático
|
||||
[ ] 16. GET /api/soe/contabil/balancete → atualizado em tempo real
|
||||
[ ] 17. Atualizar frontend /contabil para usar /api/soe/contabil/*
|
||||
```
|
||||
|
||||
### Fase 4 — Central Financeiro (2 semanas)
|
||||
```
|
||||
[ ] 18. Regras de boleto automático (via Asaas)
|
||||
[ ] 19. Alertas de vencimento (via Manus/WhatsApp)
|
||||
[ ] 20. Conciliação bancária semi-automática
|
||||
[ ] 21. Previsão financeira com IA (Motor Python 8003 + Manus)
|
||||
[ ] 22. Atualizar frontend /financeiro para usar /api/soe/financeiro/*
|
||||
```
|
||||
|
||||
### Fase 5 — Manus + SOE (1 semana)
|
||||
```
|
||||
[ ] 23. Registrar novas tools Manus apontando para SOE
|
||||
[ ] 24. Manus usa SOE para responder perguntas de negócio
|
||||
[ ] 25. Dashboard SOE: visão geral de todos os eventos
|
||||
```
|
||||
|
||||
### Fase 6 — Central ERP (3 semanas)
|
||||
```
|
||||
[ ] 26. Frappe Adapter completo (produtos, pedidos, estoque, compras)
|
||||
[ ] 27. Regras de negócio ERP no SOE
|
||||
[ ] 28. Sincronização bidirecional SOE ↔ ERPNext (via eventos)
|
||||
[ ] 29. Atualizar /erp para usar /api/soe/erp/*
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. RESULTADO FINAL — O QUE O USUÁRIO EXPERIMENTA
|
||||
|
||||
```
|
||||
Antes (fragmentado):
|
||||
Usuário → /plus → vê Laravel diretamente
|
||||
Usuário → /erp → vê ERPNext diretamente
|
||||
Regras fiscais só no Plus
|
||||
Contabilidade manual
|
||||
Financeiro sem automação
|
||||
|
||||
Depois (SOE Central):
|
||||
Usuário → /fisco → emite NF-e, Plus executa invisível
|
||||
Usuário → /contabil → vê DRE gerado automaticamente
|
||||
Usuário → /financeiro → vê fluxo, previsões, alertas automáticos
|
||||
Usuário → Manus → pergunta "qual meu resultado?" → resposta completa
|
||||
|
||||
Plus e ERPNext: INVISÍVEIS, executando em background
|
||||
Regras: CENTRALIZADAS no SOE, editáveis
|
||||
Lançamentos: AUTOMÁTICOS baseados em eventos (NF-e, pagamentos, etc.)
|
||||
Auditoria: TOTAL de todas as operações
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. PRINCÍPIO ORIENTADOR
|
||||
|
||||
> **"O usuário não sabe o que está por trás. Ele só sabe que funciona."**
|
||||
>
|
||||
> O Arcádia Suite é o **Sistema Operacional** da empresa.
|
||||
> O Plus e o ERPNext são **drivers de hardware** — poderosos, mas invisíveis.
|
||||
> O SOE Central é o **kernel** que os orquestra com regras e inteligência.
|
||||
|
||||
---
|
||||
|
||||
*PLANO_SOE_CENTRAL.md — Arcádia Suite v3.0*
|
||||
*Gerado em: 2026-03-16*
|
||||
|
|
@ -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)*
|
||||
|
|
@ -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 1–4 completas: automações, SLA, horário comercial, campanhas, Socket.IO tempo real |
|
||||
| **Novas páginas React** | `XosSupervisor`, `XosReports`, `XosProtocols` |
|
||||
| **SOE Rule Engine** | Motor de regras contábil/fiscal + importador ERPNext |
|
||||
| **Superset BI** | Integração completa embed + proxy + guest token |
|
||||
| **Infra Docker** | `docker-compose.prod.yml` com profiles: `ai`, `bi`, `plus`, `erpnext` |
|
||||
| **IA Soberania** | LiteLLM 3 tiers + Ollama + slot LLMFit pronto |
|
||||
| **Qualidade** | Rate limiting, logging estruturado (`server/logger.ts`), paginação |
|
||||
| **App Store** | `GET /api/marketplace/my-apps` + AppCenter por subscription |
|
||||
| **Docs** | `DEPLOY_COOLIFY.md`, `INTEGRACAO_IA.md`, `PLANO_BI_SUPERSET.md`, `PLANO_SOE_CENTRAL_v2.md` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Demandas prioritárias pendentes
|
||||
|
||||
### Críticas (bloqueia produção estável)
|
||||
|
||||
1. **LMS e Quality** — auth ainda incompleta (SEC-02, SEC-03)
|
||||
- `server/lms/routes.ts` — 8 rotas ainda públicas
|
||||
- `server/quality/routes.ts` — ~50 rotas revisão insuficiente
|
||||
|
||||
2. **WhatsApp auto-reply** — configuração ainda em `Map` na memória
|
||||
- Reiniciar servidor = perda total da configuração sem aviso
|
||||
- Persistir em tabela `whatsapp_auto_reply_config` (migration já existe)
|
||||
|
||||
3. **`pg_dump` do Replit** — exportar banco antes de encerrar o plano
|
||||
- Risco de perda de dados de produção atual
|
||||
|
||||
### Altas (produto incompleto)
|
||||
|
||||
4. **Ciclo de aprendizado Manus** — embeddings não são populados automaticamente
|
||||
- Busca semântica retorna vazio, sistema não aprende das interações
|
||||
|
||||
5. **Páginas placeholder** — `Agent.tsx`, `CentralApis.tsx`, `ApiHub.tsx`
|
||||
- Três das páginas mais estratégicas ainda sem implementação
|
||||
|
||||
6. **Sprint 4 inteiro — Integrações ERP reais**
|
||||
- Adaptadores Omie e TOTVS: zero implementado
|
||||
- Todos os conectores da Central de API ainda mockados
|
||||
- Estrutura fragmentada em 4 lugares (`server/erpnext/`, `server/crm/frappe-service.ts`, `server/api-central/`, `server/migration/`)
|
||||
|
||||
### Médias (qualidade de engenharia)
|
||||
|
||||
7. **Error boundaries globais no React** — sem captura de erros em cascata
|
||||
8. **Testes automatizados** — zero no projeto inteiro
|
||||
9. **Connection pooling Python** — nova conexão DB por request nos 6 microserviços
|
||||
10. **Consolidar sistemas de comunicação** — legacy (`whatsapp_*`, `chat_*`) e moderno (`comm_*`, `xosConversations`) duplicados
|
||||
11. **36 tabelas sem insert schema** — risco de runtime errors no Drizzle
|
||||
12. **40+ tabelas sem `createdAt`/`updatedAt`** — auditoria e ordenação impossíveis
|
||||
|
||||
### Decisões arquiteturais abertas
|
||||
|
||||
| # | Decisão |
|
||||
|---|---|
|
||||
| D1 | BI padrão: Metabase (existente) vs Superset (integrado)? |
|
||||
| D2 | LMS: tabelas dinâmicas vs schema.ts fixo? |
|
||||
| D3 | Sistema de comunicação canônico: legacy vs moderno vs XOS? |
|
||||
| D4 | Marketplace público ou autenticado (hoje público — intencional)? |
|
||||
|
||||
---
|
||||
|
||||
## 5. Próxima sessão recomendada
|
||||
|
||||
**Sequência sugerida por impacto/risco:**
|
||||
|
||||
```
|
||||
1. Corrigir auth LMS + Quality (2–3h) → fecha todas as vulnerabilidades SEC
|
||||
2. Persistir WhatsApp auto-reply no banco (1h) → estabilidade em produção
|
||||
3. Fechar ciclo embeddings Manus (2h) → IA começa a aprender de verdade
|
||||
4. Implementar Agent.tsx com UI real (3–4h) → página mais estratégica
|
||||
5. Adaptador Omie — primeira integração ERP real (4–6h)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Sessão registrada em 2026-03-18 | Branch: `claude/analyze-project-0mXjP`*
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
18
db/index.ts
18
db/index.ts
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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
Loading…
Reference in New Issue