diff --git a/.env b/.env
new file mode 100644
index 0000000..723e228
--- /dev/null
+++ b/.env
@@ -0,0 +1,20 @@
+PGPASSWORD=afefb610090de1646944b537f3054715ad5755665c90036c2bcd258c9ab52dd8
+SESSION_SECRET=092a26dcbebd6f7f5e7b9e020bdc4d4e966286c9780ef72e3426f437eb8d608b
+SSO_SECRET=fbb87d1c5a14a6623d4e3dcc2b679bd68c46e59afb48dd6abd42b95cac945856
+LITELLM_API_KEY=e396b827cbeb5e77d85cce4eb9da0521114f57a8e799f7aa8febad08d776c07e
+WEBUI_SECRET_KEY=76c7fc044f9e8d6e5eb60b67c82832b67e84739da4827ce1fa7c101ae1a055df
+DOMAIN=suite.onboardbi.com.br
+OLLAMA_BASE_URL=http://ollama-ia1upsekrad96at5hq97e4qa:11434
+
+LLMFIT_BASE_URL=http://llmfit:8000
+LLMFIT_API_KEY=key-arcadia
+
+# ── Superset BI ───────────────────────────────────────────────
+SUPERSET_SECRET_KEY=421274b5ba360778a5398d528116a45ea962d1197e3dfc99f36a372ff1025a63
+SUPERSET_ADMIN_USERNAME=admin
+SUPERSET_ADMIN_PASSWORD=arcadia1337
+SUPERSET_ADMIN_EMAIL=engenharia@ometas.com.br
+COMPOSE_PROFILES=bi
+
+SUPERSET_HOST=superset
+SUPERSET_PORT=8088
diff --git a/.gitignore b/.gitignore
index 688a4b9..0483d65 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,6 @@
node_modules/
dist/
.DS_Store
-.env
-.env.local
-.env.production
# Replit specific
.cache/
diff --git a/Dockerfile b/Dockerfile
index ddb0688..ddf659c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,7 +4,7 @@ FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
-RUN npm ci --ignore-scripts
+RUN npm ci
COPY . .
RUN npm run build
@@ -18,16 +18,19 @@ ENV NODE_ENV=production
# Deps de produção apenas
COPY package*.json ./
-RUN npm ci --omit=dev --ignore-scripts
+RUN npm ci --omit=dev
# Artefatos do build
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/shared ./shared
COPY --from=builder /app/migrations ./migrations
+# Arquivos de runtime necessários (caminhos quebrados pelo bundler)
+RUN cp /app/node_modules/connect-pg-simple/table.sql /app/dist/table.sql
+
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
- CMD wget -qO- http://localhost:5000/api/health || exit 1
+ CMD wget -qO- http://127.0.0.1:5000/ || exit 1
CMD ["node", "dist/index.cjs"]
diff --git a/client/src/pages/BiWorkspace.tsx b/client/src/pages/BiWorkspace.tsx
index 2a32d3b..77d36c6 100644
--- a/client/src/pages/BiWorkspace.tsx
+++ b/client/src/pages/BiWorkspace.tsx
@@ -2985,6 +2985,7 @@ export default function BiWorkspace() {
+
diff --git a/client/src/pages/SOE.tsx b/client/src/pages/SOE.tsx
index 83a5b4f..b6a75d5 100644
--- a/client/src/pages/SOE.tsx
+++ b/client/src/pages/SOE.tsx
@@ -1,2 +1,2 @@
-// SOE — Sistema de Operações Empresariais (alias para o módulo Plus/ERP)
-export { default } from "./Plus";
+// SOE — Sistema de Operações Empresariais
+export { default } from "./ERP";
diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml
index 7cc6df0..3f792f4 100644
--- a/docker-compose.prod.yml
+++ b/docker-compose.prod.yml
@@ -86,12 +86,145 @@ services:
condition: service_started
networks:
- arcadia-internal
- - arcadia-public
+ - coolify
labels:
- "traefik.enable=true"
+ - "traefik.docker.network=coolify"
+ - "traefik.http.routers.arcadia.entrypoints=https"
- "traefik.http.routers.arcadia.rule=Host(`${DOMAIN}`)"
- "traefik.http.routers.arcadia.tls=true"
- "traefik.http.routers.arcadia.tls.certresolver=letsencrypt"
+ - "traefik.http.services.arcadia.loadbalancer.server.port=5000"
+
+ # ── Arcádia Plus (Laravel + MySQL) ──────────────────────────────────────────
+ # Perfil `plus` — suba com: docker compose --profile plus up
+ plus-db:
+ image: mysql:8.0
+ restart: always
+ profiles: [plus]
+ environment:
+ MYSQL_ROOT_PASSWORD: ${PLUS_DB_ROOT_PASSWORD}
+ MYSQL_DATABASE: ${PLUS_DB_DATABASE:-arcadia_plus}
+ MYSQL_USER: ${PLUS_DB_USER:-plus}
+ MYSQL_PASSWORD: ${PLUS_DB_PASSWORD}
+ volumes:
+ - plus_db:/var/lib/mysql
+ command: --default-authentication-plugin=mysql_native_password
+ healthcheck:
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "--password=${PLUS_DB_ROOT_PASSWORD}"]
+ interval: 10s
+ timeout: 5s
+ retries: 10
+ networks:
+ - arcadia-internal
+
+ plus:
+ image: ${PLUS_IMAGE:-php:8.3-apache}
+ restart: always
+ profiles: [plus]
+ environment:
+ APP_ENV: production
+ APP_KEY: ${PLUS_APP_KEY}
+ APP_URL: https://${DOMAIN}/plus
+ DB_HOST: plus-db
+ DB_DATABASE: ${PLUS_DB_DATABASE:-arcadia_plus}
+ DB_USERNAME: ${PLUS_DB_USER:-plus}
+ DB_PASSWORD: ${PLUS_DB_PASSWORD}
+ SESSION_DRIVER: redis
+ REDIS_HOST: redis
+ ARCADIA_URL: https://${DOMAIN}
+ ARCADIA_SSO_SECRET: ${SSO_SECRET}
+ volumes:
+ - plus_storage:/var/www/html/storage
+ depends_on:
+ plus-db:
+ condition: service_healthy
+ networks:
+ - arcadia-internal
+
+ # ── Apache Superset (BI) ─────────────────────────────────────────────────────
+ # Perfil `bi` — suba com: docker compose --profile bi up
+ superset:
+ image: apache/superset:4.1.0
+ restart: always
+ profiles: [bi]
+ environment:
+ SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY}
+ DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/arcadia_superset
+ ARCADIA_DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
+ SUPERSET_ADMIN_USER: ${SUPERSET_ADMIN_USER:-admin}
+ SUPERSET_ADMIN_EMAIL: ${SUPERSET_ADMIN_EMAIL:-admin@arcadia.app}
+ SUPERSET_ADMIN_PASSWORD: ${SUPERSET_ADMIN_PASSWORD}
+ SUPERSET_WEBSERVER_PORT: "8088"
+ PYTHONPATH: /app/pythonpath
+ volumes:
+ - ./docker/superset/superset_config.py:/app/pythonpath/superset_config.py:ro
+ - ./docker/superset/init.sh:/app/docker/init.sh:ro
+ - superset_home:/app/superset_home
+ depends_on:
+ db:
+ condition: service_healthy
+ command: ["/bin/bash", "/app/docker/init.sh"]
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:8088/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 5
+ start_period: 60s
+ networks:
+ - arcadia-internal
+
+ # ── ERPNext (Frappe Framework) ───────────────────────────────────────────────
+ # Perfil `erpnext` — suba com: docker compose --profile erpnext up
+ erpnext-db:
+ image: mariadb:10.6
+ restart: always
+ profiles: [erpnext]
+ environment:
+ MYSQL_ROOT_PASSWORD: ${ERPNEXT_DB_ROOT_PASSWORD}
+ MYSQL_DATABASE: _frappe
+ MYSQL_USER: frappe
+ MYSQL_PASSWORD: ${ERPNEXT_DB_PASSWORD}
+ volumes:
+ - erpnext_db:/var/lib/mysql
+ command: >
+ --character-set-server=utf8mb4
+ --collation-server=utf8mb4_unicode_ci
+ --skip-character-set-client-handshake
+ --skip-innodb-read-only-compressed
+ healthcheck:
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "--password=${ERPNEXT_DB_ROOT_PASSWORD}"]
+ interval: 10s
+ timeout: 5s
+ retries: 10
+ start_period: 30s
+ networks:
+ - arcadia-internal
+
+ erpnext:
+ image: frappe/erpnext:version-15
+ restart: always
+ profiles: [erpnext]
+ environment:
+ FRAPPE_SITE_NAME_HEADER: erpnext.local
+ ERPNEXT_DB_ROOT_PASSWORD: ${ERPNEXT_DB_ROOT_PASSWORD}
+ ERPNEXT_ADMIN_PASSWORD: ${ERPNEXT_ADMIN_PASSWORD}
+ volumes:
+ - erpnext_sites:/home/frappe/frappe-bench/sites
+ - erpnext_logs:/home/frappe/frappe-bench/logs
+ - ./docker/erpnext/init.sh:/usr/local/bin/init-erpnext.sh:ro
+ depends_on:
+ erpnext-db:
+ condition: service_healthy
+ command: ["/bin/bash", "/usr/local/bin/init-erpnext.sh"]
+ healthcheck:
+ test: ["CMD-SHELL", "curl -sf http://localhost:8080/api/method/frappe.ping || exit 1"]
+ interval: 30s
+ timeout: 10s
+ retries: 10
+ start_period: 120s
+ networks:
+ - arcadia-internal
# ── Arcádia Plus (Laravel + MySQL) ──────────────────────────────────────────
# Perfil `plus` — suba com: docker compose --profile plus up
@@ -297,6 +430,9 @@ services:
SERVICE_NAME: embeddings
SERVICE_PORT: 8001
DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
+ LITELLM_BASE_URL: http://litellm:4000
+ LITELLM_API_KEY: ${LITELLM_API_KEY}
+ OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
depends_on:
db:
condition: service_healthy
@@ -314,12 +450,13 @@ services:
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
LITELLM_MASTER_KEY: ${LITELLM_API_KEY}
- DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
+ DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/litellm
# Ollama: se instalado no host use http://host-gateway:11434
# Se usar container Docker, mantém http://ollama:11434
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
# LLMFit: URL do serviço de modelos fine-tuned
LLMFIT_BASE_URL: ${LLMFIT_BASE_URL:-}
+ LLMFIT_API_KEY: ${LLMFIT_API_KEY:-}
# Providers externos opcionais (soberania: só habilitados se configurados)
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
GROQ_API_KEY: ${GROQ_API_KEY:-}
@@ -328,6 +465,7 @@ services:
condition: service_healthy
networks:
- arcadia-internal
+ - coolify
# ── Ollama (LLMs locais — soberania total) ────────────────────────────────────
# OPÇÃO A (padrão): Ollama como container Docker
@@ -359,7 +497,7 @@ services:
- litellm
networks:
- arcadia-internal
- - arcadia-public
+ - coolify
labels:
- "traefik.enable=true"
- "traefik.http.routers.webui.rule=Host(`ai.${DOMAIN}`)"
@@ -368,17 +506,55 @@ services:
- "traefik.http.services.webui.loadbalancer.server.port=8080"
profiles: [ai]
+ # ── Superset BI ───────────────────────────────────────────────────────────────
+ superset:
+ build:
+ context: ./docker/superset
+ dockerfile: Dockerfile
+ restart: always
+ environment:
+ SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY}
+ SQLALCHEMY_DATABASE_URI: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/superset
+ PYTHONPATH: /app/pythonpath
+ SUPERSET_ADMIN_USERNAME: ${SUPERSET_ADMIN_USERNAME:-admin}
+ SUPERSET_ADMIN_PASSWORD: ${SUPERSET_ADMIN_PASSWORD}
+ SUPERSET_ADMIN_EMAIL: ${SUPERSET_ADMIN_EMAIL:-admin@onboardbi.com.br}
+ ARCADIA_DATABASE_URL: postgresql+psycopg2://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
+ command: ["/bin/bash", "/app/docker/init.sh"]
+ volumes:
+ - ./docker/superset/superset_config.py:/app/pythonpath/superset_config.py:ro
+ - ./docker/superset/init.sh:/app/docker/init.sh:ro
+ - superset_home:/app/superset_home
+ depends_on:
+ db:
+ condition: service_healthy
+ networks:
+ - arcadia-internal
+ - coolify
+ labels:
+ - "traefik.enable=true"
+ - "traefik.docker.network=coolify"
+ - "traefik.http.routers.superset.entrypoints=https"
+ - "traefik.http.routers.superset.rule=Host(`${SUPERSET_DOMAIN:-bi.onboardbi.com.br}`)"
+ - "traefik.http.routers.superset.tls=true"
+ - "traefik.http.routers.superset.tls.certresolver=letsencrypt"
+ - "traefik.http.services.superset.loadbalancer.server.port=8088"
+ profiles: [bi]
+
networks:
arcadia-internal:
driver: bridge
- arcadia-public:
- driver: bridge
+ coolify:
+ external: true
volumes:
pgdata:
redis_data:
ollama_models:
open_webui_data:
+
+
+
plus_db:
plus_storage:
superset_home:
diff --git a/docker-compose.prod.yml.bak b/docker-compose.prod.yml.bak
new file mode 100644
index 0000000..af01a8c
--- /dev/null
+++ b/docker-compose.prod.yml.bak
@@ -0,0 +1,237 @@
+# ─── Arcádia Suite — Produção (Coolify) ───────────────────────────────────────
+# Este arquivo é usado pelo Coolify para deploy automático.
+# NÃO inclui volumes de código-fonte — só artefatos de build.
+# Configurar no Coolify: Environment Variables para todas as vars ${...}
+
+name: arcadia-prod
+
+services:
+
+ # ── Banco de dados com pgvector ─────────────────────────────────────────────
+ db:
+ image: pgvector/pgvector:pg16
+ restart: always
+ environment:
+ POSTGRES_DB: ${PGDATABASE:-arcadia}
+ POSTGRES_USER: ${PGUSER:-arcadia}
+ POSTGRES_PASSWORD: ${PGPASSWORD}
+ volumes:
+ - pgdata:/var/lib/postgresql/data
+ - ./docker/init-pgvector.sql:/docker-entrypoint-initdb.d/01-pgvector.sql
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U ${PGUSER:-arcadia}"]
+ interval: 10s
+ timeout: 5s
+ retries: 10
+ networks:
+ - arcadia-internal
+
+ # ── Redis ────────────────────────────────────────────────────────────────────
+ redis:
+ image: redis:7-alpine
+ restart: always
+ command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
+ volumes:
+ - redis_data:/data
+ networks:
+ - arcadia-internal
+
+ # ── App principal ─────────────────────────────────────────────────────────
+ app:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ restart: always
+ environment:
+ NODE_ENV: production
+ PORT: 5000
+ DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
+ REDIS_URL: redis://redis:6379
+ DOCKER_MODE: "true"
+ CONTABIL_PYTHON_URL: http://contabil:8003
+ BI_PYTHON_URL: http://bi:8004
+ AUTOMATION_PYTHON_URL: http://automation:8005
+ FISCO_PYTHON_URL: http://fisco:8002
+ PYTHON_SERVICE_URL: http://embeddings:8001
+ SESSION_SECRET: ${SESSION_SECRET}
+ SSO_SECRET: ${SSO_SECRET}
+ OPENAI_API_KEY: ${OPENAI_API_KEY:-}
+ LITELLM_BASE_URL: http://litellm:4000
+ LITELLM_API_KEY: ${LITELLM_API_KEY}
+ OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
+ # ── Manus Agent — aponta para LiteLLM como gateway unificado ──────────
+ # LiteLLM roteia para Ollama (local), LLMFit (fine-tuned) ou externo
+ AI_INTEGRATIONS_OPENAI_BASE_URL: http://litellm:4000/v1
+ AI_INTEGRATIONS_OPENAI_API_KEY: ${LITELLM_API_KEY}
+ ports:
+ - "5000:5000"
+ depends_on:
+ db:
+ condition: service_healthy
+ redis:
+ condition: service_started
+ networks:
+ - arcadia-internal
+ - arcadia-public
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.arcadia.rule=Host(`${DOMAIN}`)"
+ - "traefik.http.routers.arcadia.tls=true"
+ - "traefik.http.routers.arcadia.tls.certresolver=letsencrypt"
+
+ # ── Microserviços Python ─────────────────────────────────────────────────────
+ contabil:
+ build:
+ context: .
+ dockerfile: Dockerfile.python
+ restart: always
+ environment:
+ SERVICE_NAME: contabil
+ SERVICE_PORT: 8003
+ CONTABIL_PORT: 8003
+ DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
+ depends_on:
+ db:
+ condition: service_healthy
+ networks:
+ - arcadia-internal
+
+ bi:
+ build:
+ context: .
+ dockerfile: Dockerfile.python
+ restart: always
+ environment:
+ SERVICE_NAME: bi
+ SERVICE_PORT: 8004
+ BI_PORT: 8004
+ DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
+ depends_on:
+ db:
+ condition: service_healthy
+ networks:
+ - arcadia-internal
+
+ automation:
+ build:
+ context: .
+ dockerfile: Dockerfile.python
+ restart: always
+ environment:
+ SERVICE_NAME: automation
+ SERVICE_PORT: 8005
+ AUTOMATION_PORT: 8005
+ DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
+ depends_on:
+ db:
+ condition: service_healthy
+ networks:
+ - arcadia-internal
+
+ fisco:
+ build:
+ context: .
+ dockerfile: Dockerfile.python
+ restart: always
+ environment:
+ SERVICE_NAME: fisco
+ SERVICE_PORT: 8002
+ FISCO_PORT: 8002
+ DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
+ depends_on:
+ db:
+ condition: service_healthy
+ networks:
+ - arcadia-internal
+
+ embeddings:
+ build:
+ context: .
+ dockerfile: Dockerfile.python
+ restart: always
+ environment:
+ SERVICE_NAME: embeddings
+ SERVICE_PORT: 8001
+ DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
+ depends_on:
+ db:
+ condition: service_healthy
+ networks:
+ - arcadia-internal
+
+ # ── LiteLLM (gateway unificado de LLM — soberania dos dados) ─────────────────
+ # Roteia: LLMFit (fine-tuned) → Ollama (local) → externo (opt-in)
+ litellm:
+ image: ghcr.io/berriai/litellm:main-latest
+ restart: always
+ volumes:
+ - ./docker/litellm-config.yaml:/app/config.yaml
+ command: ["--config", "/app/config.yaml", "--port", "4000"]
+ environment:
+ OPENAI_API_KEY: ${OPENAI_API_KEY:-}
+ LITELLM_MASTER_KEY: ${LITELLM_API_KEY}
+ DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD}@db:5432/${PGDATABASE:-arcadia}
+ # Ollama: se instalado no host use http://host-gateway:11434
+ # Se usar container Docker, mantém http://ollama:11434
+ OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
+ # LLMFit: URL do serviço de modelos fine-tuned
+ LLMFIT_BASE_URL: ${LLMFIT_BASE_URL:-}
+ # Providers externos opcionais (soberania: só habilitados se configurados)
+ ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
+ GROQ_API_KEY: ${GROQ_API_KEY:-}
+ depends_on:
+ db:
+ condition: service_healthy
+ networks:
+ - arcadia-internal
+
+ # ── Ollama (LLMs locais — soberania total) ────────────────────────────────────
+ # OPÇÃO A (padrão): Ollama como container Docker
+ # OPÇÃO B: Ollama no host → comente este serviço e defina
+ # OLLAMA_BASE_URL=http://host-gateway:11434 nas env vars
+ ollama:
+ image: ollama/ollama:latest
+ restart: always
+ volumes:
+ - ollama_models:/root/.ollama
+ networks:
+ - arcadia-internal
+ # Remova 'profiles: [ai]' para ativar por padrão no deploy
+ profiles: [ai]
+
+ # ── Open WebUI (interface para Ollama + LLMFit) ───────────────────────────────
+ open-webui:
+ image: ghcr.io/open-webui/open-webui:main
+ restart: always
+ environment:
+ # Pode apontar para LiteLLM para ter acesso a todos os modelos via WebUI
+ OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
+ OPENAI_API_BASE_URL: http://litellm:4000/v1
+ OPENAI_API_KEY: ${LITELLM_API_KEY}
+ WEBUI_SECRET_KEY: ${WEBUI_SECRET_KEY}
+ volumes:
+ - open_webui_data:/app/backend/data
+ depends_on:
+ - litellm
+ networks:
+ - arcadia-internal
+ - arcadia-public
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.webui.rule=Host(`ai.${DOMAIN}`)"
+ - "traefik.http.routers.webui.tls=true"
+ - "traefik.http.routers.webui.tls.certresolver=letsencrypt"
+ - "traefik.http.services.webui.loadbalancer.server.port=8080"
+ profiles: [ai]
+
+networks:
+ arcadia-internal:
+ driver: bridge
+ arcadia-public:
+ driver: bridge
+
+volumes:
+ pgdata:
+ redis_data:
+ ollama_models:
+ open_webui_data:
diff --git a/docker/litellm-config.yaml b/docker/litellm-config.yaml
index 336d872..0085857 100644
--- a/docker/litellm-config.yaml
+++ b/docker/litellm-config.yaml
@@ -13,35 +13,22 @@
model_list:
# ── TIER 1: LLMFit (modelos fine-tuned locais — máxima soberania) ────────────
- # Descomente quando o LLMFit estiver rodando no servidor
- # O LLMFit expõe API compatível com OpenAI — basta apontar a URL
- #
- # - model_name: arcadia-finetuned
- # litellm_params:
- # model: openai/arcadia-v1 # nome do modelo no LLMFit
- # api_base: os.environ/LLMFIT_BASE_URL
- # api_key: llmfit-internal
- #
- # - model_name: arcadia-embed
- # litellm_params:
- # model: openai/arcadia-embed-v1 # modelo de embeddings fine-tuned
- # api_base: os.environ/LLMFIT_BASE_URL
- # api_key: llmfit-internal
+ - model_name: arcadia-finetuned
+ litellm_params:
+ model: openai/llama3.2:3b
+ api_base: os.environ/LLMFIT_BASE_URL
+ api_key: os.environ/LLMFIT_API_KEY
+
+ - model_name: arcadia-embed
+ litellm_params:
+ model: openai/nomic-embed-text:latest
+ api_base: os.environ/LLMFIT_BASE_URL
+ api_key: os.environ/LLMFIT_API_KEY
# ── TIER 2: Ollama (LLMs locais — soberania total) ───────────────────────────
- - model_name: llama3.3
+ - model_name: llama3.2
litellm_params:
- model: ollama/llama3.3
- api_base: os.environ/OLLAMA_BASE_URL
-
- - model_name: qwen2.5-coder
- litellm_params:
- model: ollama/qwen2.5-coder:7b
- api_base: os.environ/OLLAMA_BASE_URL
-
- - model_name: deepseek-r1
- litellm_params:
- model: ollama/deepseek-r1:7b
+ model: ollama/llama3.2:3b
api_base: os.environ/OLLAMA_BASE_URL
- model_name: nomic-embed-text
@@ -49,6 +36,12 @@ model_list:
model: ollama/nomic-embed-text
api_base: os.environ/OLLAMA_BASE_URL
+ # Alias OpenAI-compatible para embedding (Manus usa este nome por padrão)
+ - model_name: text-embedding-3-small
+ litellm_params:
+ model: ollama/nomic-embed-text
+ api_base: os.environ/OLLAMA_BASE_URL
+
# ── TIER 3: OpenAI (opt-in — só ativo se OPENAI_API_KEY configurado) ─────────
- model_name: gpt-4o
litellm_params:
@@ -73,29 +66,26 @@ model_list:
# api_key: os.environ/GROQ_API_KEY
# ── Modelo padrão do Arcádia (Manus usa este) ─────────────────────────────────
- # Prioridade: LLMFit → OpenAI (se configurado) → Ollama (sempre disponível)
- # Para soberania total: remova o fallback para gpt-4o-mini
+ # Prioridade: LLMFit (TIER 1) → Ollama (TIER 2 — fallback)
- model_name: arcadia-default
litellm_params:
- model: ollama/llama3.3
- api_base: os.environ/OLLAMA_BASE_URL
- model_info:
- # fallbacks: ["gpt-4o-mini"] # descomente para habilitar fallback externo
+ model: openai/llama3.2:3b
+ api_base: os.environ/LLMFIT_BASE_URL
+ api_key: os.environ/LLMFIT_API_KEY
router_settings:
routing_strategy: least-busy
fallbacks:
- - {"gpt-4o": ["llama3.3"]}
- - {"gpt-4o-mini": ["llama3.3"]}
- - {"arcadia-default": ["llama3.3"]}
+ - {"gpt-4o": ["llama3.2"]}
+ - {"gpt-4o-mini": ["llama3.2"]}
+ - {"arcadia-default": ["llama3.2"]}
+ - {"arcadia-finetuned": ["llama3.2"]}
+ - {"arcadia-embed": ["nomic-embed-text"]}
litellm_settings:
drop_params: true
request_timeout: 120
set_verbose: false
- # Loga todas as chamadas no banco — essencial para auditoria e soberania
- success_callback: ["langfuse"]
- failure_callback: ["langfuse"]
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
diff --git a/docker/superset/Dockerfile b/docker/superset/Dockerfile
new file mode 100644
index 0000000..923ddcf
--- /dev/null
+++ b/docker/superset/Dockerfile
@@ -0,0 +1,5 @@
+FROM apache/superset:latest
+USER root
+RUN pip install psycopg2-binary && \
+ sed -i 's/include-system-site-packages = false/include-system-site-packages = true/' /app/.venv/pyvenv.cfg
+USER superset
diff --git a/scripts/seed-soe-rules.ts b/scripts/seed-soe-rules.ts
new file mode 100644
index 0000000..4e0f3ab
--- /dev/null
+++ b/scripts/seed-soe-rules.ts
@@ -0,0 +1,62 @@
+/**
+ * Seed das regras padrão do SOE (soe_regras)
+ * Insere FISCAL_RULES_PADRAO + BUSINESS_RULES_PADRAO no banco.
+ * Idempotente: pula regras que já existem (por nome + trigger).
+ *
+ * Uso:
+ * DATABASE_URL=postgres://... npx tsx scripts/seed-soe-rules.ts
+ */
+
+import pg from "pg";
+import { FISCAL_RULES_PADRAO } from "../server/soe/rule-engine/fiscal-rules";
+import { BUSINESS_RULES_PADRAO } from "../server/soe/rule-engine/business-rules";
+
+if (!process.env.DATABASE_URL) {
+ throw new Error("DATABASE_URL não definida");
+}
+
+const client = new pg.Client({ connectionString: process.env.DATABASE_URL });
+await client.connect();
+
+const ALL_RULES = [...FISCAL_RULES_PADRAO, ...BUSINESS_RULES_PADRAO];
+
+console.log(`Inserindo ${ALL_RULES.length} regras padrão SOE...\n`);
+
+let inserted = 0;
+let skipped = 0;
+
+for (const rule of ALL_RULES) {
+ const existing = await client.query(
+ `SELECT id FROM soe_regras WHERE nome = $1 AND trigger = $2 AND origem_padrao = true`,
+ [rule.nome, rule.trigger]
+ );
+
+ if ((existing.rowCount ?? 0) > 0) {
+ console.log(` [SKIP] ${rule.id}`);
+ skipped++;
+ continue;
+ }
+
+ await client.query(
+ `INSERT INTO soe_regras (dominio, trigger, nome, condicao, acao, prioridade, ativo, origem_padrao, tenant_id)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
+ [
+ rule.dominio,
+ rule.trigger,
+ rule.nome,
+ JSON.stringify(rule.condicao),
+ JSON.stringify(rule.acao),
+ rule.prioridade,
+ rule.ativo,
+ true,
+ null,
+ ]
+ );
+
+ console.log(` [OK] ${rule.id} — ${rule.nome}`);
+ inserted++;
+}
+
+await client.end();
+
+console.log(`\nConcluído: ${inserted} inseridas, ${skipped} ignoradas.`);
diff --git a/server/bi/upload.ts b/server/bi/upload.ts
index 5ebe16f..f270556 100644
--- a/server/bi/upload.ts
+++ b/server/bi/upload.ts
@@ -2,15 +2,15 @@ import type { Express, Request, Response } from "express";
import multer from "multer";
import path from "path";
import fs from "fs";
-import { createRequire } from "module";
+
import AdmZip from "adm-zip";
import * as BSON from "bson";
import { db } from "../../db/index";
import { biDatasets, stagedTables } from "@shared/schema";
import { sql } from "drizzle-orm";
-const require = createRequire(import.meta.url);
-const XLSX = require("xlsx");
+
+import * as XLSX from "xlsx";
const uploadDir = path.join(process.cwd(), "uploads");
if (!fs.existsSync(uploadDir)) {
diff --git a/server/erp/routes.ts b/server/erp/routes.ts
index 15ba715..d17a2ad 100644
--- a/server/erp/routes.ts
+++ b/server/erp/routes.ts
@@ -1190,3 +1190,6 @@ export function registerErpRoutes(app: Express): void {
}
});
}
+// SOE routes — placeholder
+
+export function registerSoeRoutes(_app: Express): void {}
diff --git a/server/index.ts b/server/index.ts
index 20654ef..f980fb9 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -346,7 +346,7 @@ export function log(message: string, source = "express") {
password: hashedPassword,
name: "Administrador Master",
email: "admin@arcadia.suite",
- role: "master",
+ role: "admin",
status: "active",
});
console.log("[Seed] Usuário master 'admin' criado com sucesso");
diff --git a/server/modules/loader.ts b/server/modules/loader.ts
new file mode 100644
index 0000000..2c8b310
--- /dev/null
+++ b/server/modules/loader.ts
@@ -0,0 +1,6 @@
+import type { Express } from "express";
+
+// Carregador de rotas modulares — placeholder
+export async function loadModuleRoutes(_app: Express): Promise {
+ // Módulos serão registrados aqui conforme forem criados
+}
diff --git a/server/retail/plus-sync.ts b/server/retail/plus-sync.ts
new file mode 100644
index 0000000..b633698
--- /dev/null
+++ b/server/retail/plus-sync.ts
@@ -0,0 +1,9 @@
+// Serviço de sincronização com Plus — placeholder
+export const retailPlusSyncService = {
+ async getPlusStatus() { return { connected: false }; },
+ async syncAllPersonsToPlus(_tenantId: any, _empresaId: any) { return { synced: 0 }; },
+ async syncSaleToPlus(_saleId: any) { return { synced: false }; },
+ async emitirNFeSale(_saleId: any, _tipo?: string) { return { emitted: false }; },
+ async importClientesFromPlus(_tenantId: any, _empresaId: any) { return { imported: 0 }; },
+ async importProdutosFromPlus(_tenantId: any, _empresaId: any) { return { imported: 0 }; },
+};
diff --git a/server/superset/routes.ts b/server/superset/routes.ts
index 729b76e..4e08a0d 100644
--- a/server/superset/routes.ts
+++ b/server/superset/routes.ts
@@ -52,7 +52,7 @@ export function registerSupersetRoutes(app: Express): void {
last_name: user?.name?.split(" ").slice(1).join(" ") || "User",
},
resources: [{ type: "dashboard", id: dashboardId }],
- rls: [],
+ rls: user?.tenantId ? [{ clause: `tenant_id = ${user.tenantId}`, type: "BASE_ROLE" }] : [],
}),
});
diff --git a/shared/schema.ts b/shared/schema.ts
index 66183a0..1db158a 100644
--- a/shared/schema.ts
+++ b/shared/schema.ts
@@ -1,5 +1,5 @@
import { sql } from "drizzle-orm";
-import { pgTable, text, varchar, primaryKey, serial, integer, timestamp, numeric, jsonb, boolean, date } from "drizzle-orm/pg-core";
+import { pgTable, text, varchar, primaryKey, serial, integer, timestamp, numeric, jsonb, boolean, date, uuid } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { z } from "zod";
diff --git a/shared/schemas/index.ts b/shared/schemas/index.ts
new file mode 100644
index 0000000..41a9f22
--- /dev/null
+++ b/shared/schemas/index.ts
@@ -0,0 +1,3 @@
+// Schemas modulares — gerados pelo Dev Center
+// Arquivo placeholder: adicione exports conforme módulos forem criados
+export {};