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 {};