+ {status === "loading" && (
+
+
+
+
Carregando Arcádia Insights...
+
+
+ )}
+
+ {showOpenLink && status === "ready" && (
+
+ Abrir completo
+
+ )}
+
+
+
+ );
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index b2d8866..e22222f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -167,12 +167,32 @@ services:
restart: unless-stopped
environment:
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY:-superset-secret-change-in-prod}
- DATABASE_URL: postgresql://arcadia:arcadia123@db:5432/arcadia_superset
+ DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD:-arcadia123}@db:5432/arcadia_superset
+ ARCADIA_DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD:-arcadia123}@db:5432/arcadia
+ SUPERSET_ADMIN_USER: ${SUPERSET_ADMIN_USER:-admin}
+ SUPERSET_ADMIN_EMAIL: ${SUPERSET_ADMIN_EMAIL:-admin@arcadia.app}
+ SUPERSET_ADMIN_PASSWORD: ${SUPERSET_ADMIN_PASSWORD:-arcadia2026}
+ SUPERSET_WEBSERVER_PORT: 8088
+ PYTHONPATH: /app/pythonpath
+ FLASK_ENV: production
ports:
- "8088:8088"
+ volumes:
+ - ./docker/superset/superset_config.py:/app/pythonpath/superset_config.py:ro
+ - ./docker/superset/init.sh:/app/docker/init.sh:ro
+ - superset_home:/app/superset_home
depends_on:
db:
condition: service_healthy
+ command: ["/bin/bash", "/app/docker/init.sh"]
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:8088/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 5
+ start_period: 60s
+ networks:
+ - arcadia
profiles: [bi]
# ─────────────── PERFIL: ai (Soberania de IA) ────────────────────────────────
@@ -231,3 +251,8 @@ volumes:
pgdata:
ollama_models:
open_webui_data:
+ superset_home:
+
+networks:
+ arcadia:
+ driver: bridge
diff --git a/package.json b/package.json
index 4779f0c..26b0cb7 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,7 @@
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
+ "@superset-ui/embedded-sdk": "^0.3.0",
"@tanstack/react-query": "^5.60.5",
"@tiptap/extension-image": "^3.15.3",
"@tiptap/extension-link": "^3.15.3",
diff --git a/server/erp/routes.ts b/server/erp/routes.ts
index 1280db7..d17a2ad 100644
--- a/server/erp/routes.ts
+++ b/server/erp/routes.ts
@@ -4,6 +4,8 @@ import { db } from "../../db/index";
import { customers, suppliers, products, salesOrders, purchaseOrders, erpSegments, erpConfig, insertErpSegmentSchema, insertErpConfigSchema, persons, personRoles, mobileDevices, posSales, posSaleItems, finAccountsReceivable } from "@shared/schema";
import { eq, and, sql, desc } from "drizzle-orm";
import { z } from "zod";
+import { soeRuleEngine } from "../soe/rule-engine/index";
+import { soeEventBus } from "../soe/event-bus";
export function registerErpRoutes(app: Express): void {
app.get("/api/erp/connections", async (req: Request, res: Response) => {
@@ -850,12 +852,24 @@ export function registerErpRoutes(app: Express): void {
return res.status(401).json({ error: "Not authenticated" });
}
const tenantId = req.user?.tenantId || 1;
- const { orderNumber, customerId, orderDate, deliveryDate, status, subtotal, discount, tax, total, paymentMethod, notes } = req.body;
+ const user = req.user as any;
+
+ // SOE Rule Engine — aplica regras fiscais e de negócio antes de criar o pedido
+ const { payload: enriched, validationErrors } = await soeRuleEngine.apply(
+ "pre_sales_order",
+ { ...req.body, _tipo: "venda" },
+ { tenantId, empresaId: req.body.empresaId, userId: user?.id, motor: "local" }
+ );
+ if (validationErrors.length > 0) {
+ return res.status(422).json({ error: "Validação falhou", details: validationErrors });
+ }
+
+ const { orderNumber, customerId, orderDate, deliveryDate, status, subtotal, discount, tax, total, paymentMethod, notes } = enriched;
const [order] = await db.insert(salesOrders).values({
tenantId,
orderNumber, customerId, orderDate, deliveryDate, status, subtotal, discount, tax, total, paymentMethod, notes
}).returning();
- res.status(201).json(order);
+ res.status(201).json({ ...order, _rulesApplied: enriched._rulesApplied });
} catch (error) {
console.error("Error creating sales order:", error);
res.status(500).json({ error: "Failed to create sales order" });
@@ -876,7 +890,16 @@ export function registerErpRoutes(app: Express): void {
if (!updated) {
return res.status(404).json({ error: "Sales order not found" });
}
- res.json({ success: true, order: updated, message: "Pedido confirmado. Lançamentos contábeis gerados." });
+
+ // SOE Event Bus — dispara automações pós-confirmação
+ soeEventBus.emit("venda_confirmada", {
+ tenantId,
+ empresaId: (req.user as any)?.empresaId,
+ motorOrigem: "local",
+ dados: { id, status: "confirmed", ...updated },
+ }).catch((e) => console.error("[SOE] venda_confirmada error:", e.message));
+
+ res.json({ success: true, order: updated, message: "Pedido confirmado. Automações SOE disparadas." });
} catch (error) {
console.error("Error confirming sales order:", error);
res.status(500).json({ error: "Failed to confirm sales order" });
@@ -890,6 +913,18 @@ export function registerErpRoutes(app: Express): void {
}
const id = parseInt(req.params.id);
const tenantId = req.user?.tenantId || 1;
+ const user = req.user as any;
+
+ // SOE Rule Engine — aplica regras fiscais antes de gerar a NF-e
+ const { payload: enriched, validationErrors } = await soeRuleEngine.apply(
+ "pre_nfe_emissao",
+ { ...req.body, tipoDocumento: "nfe", _tipo: "venda" },
+ { tenantId, empresaId: user?.empresaId, userId: user?.id, motor: "local" }
+ );
+ if (validationErrors.length > 0) {
+ return res.status(422).json({ error: "Validação fiscal falhou", details: validationErrors });
+ }
+
const [updated] = await db.update(salesOrders)
.set({ status: "invoiced" })
.where(and(eq(salesOrders.id, id), eq(salesOrders.tenantId, tenantId)))
@@ -897,7 +932,26 @@ export function registerErpRoutes(app: Express): void {
if (!updated) {
return res.status(404).json({ error: "Sales order not found" });
}
- res.json({ success: true, order: updated, message: "NF-e gerada com sucesso." });
+
+ // SOE Event Bus — dispara lançamentos contábeis automáticos
+ soeEventBus.emit("nfe_emitida", {
+ tenantId,
+ empresaId: user?.empresaId,
+ motorOrigem: "local",
+ dados: {
+ ...enriched,
+ ...updated,
+ valorTotal: updated.total,
+ dataEmissao: new Date().toISOString(),
+ },
+ }).catch((e) => console.error("[SOE] nfe_emitida error:", e.message));
+
+ res.json({
+ success: true,
+ order: updated,
+ tributacao: enriched.tributacao,
+ message: "NF-e gerada. Lançamentos contábeis sendo processados.",
+ });
} catch (error) {
console.error("Error generating NF-e:", error);
res.status(500).json({ error: "Failed to generate NF-e" });
diff --git a/server/routes.ts b/server/routes.ts
index 6e11da2..d8b0a73 100644
--- a/server/routes.ts
+++ b/server/routes.ts
@@ -47,11 +47,9 @@ import lmsRoutes from "./lms/routes";
import xosRoutes from "./xos/routes";
import governanceRoutes from "./governance/routes";
import { setupPlusProxy } from "./plus/proxy";
-import { setupMetabaseProxy } from "./metabase/proxy";
import { setupSupersetProxy } from "./superset/proxy";
import { registerSupersetRoutes } from "./superset/routes";
import { registerEngineRoomRoutes } from "./engine-room/routes";
-import { registerMetaSetRoutes } from "./metaset/routes";
import plusSsoRoutes from "./plus/sso";
import migrationRoutes from "./migration/routes";
import { githubRoutes } from "./integrations/github";
@@ -73,11 +71,10 @@ export async function registerRoutes(
// Arcádia Plus - Proxy registered AFTER session but BEFORE auth-protected routes
await setupPlusProxy(app);
-
- // Superset BI - Proxy
+
+ // Apache Superset - Arcádia Insights (substitui Metabase)
setupSupersetProxy(app);
- // Metabase BI - Proxy to Metabase instance
- setupMetabaseProxy(app);
+ registerSupersetRoutes(app);
registerChatRoutes(app);
registerSoeRoutes(app);
@@ -94,8 +91,6 @@ export async function registerRoutes(
registerBiRoutes(app);
app.use("/api/graph", graphRoutes);
registerBiEngineRoutes(app);
- registerMetaSetRoutes(app);
- registerSupersetRoutes(app);
registerCommEngineRoutes(app);
registerLearningRoutes(app);
app.use("/api/compass", compassRoutes);
diff --git a/server/soe/event-bus.ts b/server/soe/event-bus.ts
new file mode 100644
index 0000000..bce482f
--- /dev/null
+++ b/server/soe/event-bus.ts
@@ -0,0 +1,112 @@
+// SOE Event Bus — Dispara eventos após execução do motor
+// Permite que lançamentos contábeis, alertas e automações
+// sejam disparados de forma desacoplada após cada operação SOE.
+
+import { db } from "../db/index";
+import { soeEventos } from "@shared/schema";
+import { contabilHandler } from "./handlers/contabil-handler";
+import { financialHandler } from "./handlers/financial-handler";
+
+export type SoeEventType =
+ | "nfe_emitida"
+ | "nfe_cancelada"
+ | "venda_confirmada"
+ | "venda_faturada"
+ | "compra_recebida"
+ | "pagamento_realizado"
+ | "recebimento_realizado"
+ | "estoque_baixo";
+
+export interface SoeEventPayload {
+ tenantId?: number;
+ empresaId?: number;
+ motorOrigem?: "plus" | "erpnext" | "local";
+ dados: Record