Volver a la documentación
Guía 02

Arquitectura

Simmetric Chat es un monorepo TypeScript modular con 5 packages más una app de escritorio Tauri. El principio rector es la separación de fronteras: cada package tiene responsabilidades distintas y se comunica con los demás solo mediante interfaces bien d…

5 min de lectura

Simmetric Chat es un monorepo TypeScript modular con 5 packages más una app de escritorio Tauri. El principio rector es la separación de fronteras: cada package tiene responsabilidades distintas y se comunica con los demás solo mediante interfaces bien definidas (HTTP o el package shared).

Vista general

text
┌──────────────┐   ┌──────────────┐   ┌──────────────┐
│   frontend   │   │    widget    │   │  src-tauri   │
│  React 19    │   │  Preact IIFE │   │  Desktop app │
│  Vite :5173  │   │  Express:3211│   │              │
└──────┬───────┘   └──────┬───────┘   └──────┬───────┘
    │  /api (JWT)     │  /api/internal/widget (X-Api-Key) │
    │                 │                                  │
    ▼                 ▼                                  │
┌────────────────────────────────────┐                      │
│              server                │◄──── Tauri usa il ────┘
│  Express :3000  (RBAC, agent, RAG) │     server via HTTP
└──────┬──────────────────┬──────────┘
    │ HTTP (COLLECTOR_SECRET)     │ HTTP (status callback)
    ▼                             ▼
┌──────────────┐             ┌──────────────┐
│   collector  │             │  PostgreSQL  │
│  Express:3210│             │  (Prisma)    │
│  parse/embed │             └──────────────┘
└──────────────┘
    │
    ▼
┌──────────────────────────┐
│  Vector DB (LanceDB/     │
│  Qdrant) + LLM (Ollama/  │
│  OpenAI/Anthropic)       │
└──────────────────────────┘

Los 5 packages

shared - el leaf node

Tipos, schema Zod y constantes. Cero business logic, única dependencia runtime zod. Lo importan server, collector y frontend. Nunca importar desde otros packages: la dirección es siempre * → shared. Esto garantiza el orden de build de Turborepo y evita ciclos.

  • src/types/ - interfaces de entidades (User, Workspace, Chat, Document, Provider, Widget, BackupDestination, Archive, OcrJob, SynthesisRun, ...).
  • src/schemas/ - Zod (auth, chat, document, widget, mcpConnection, license, backup, archive, wiki, ocr, synthesis, provider). Un schema por dominio, inferencia de tipo z.infer.
  • src/constants/ - permissions.ts (31 permisos RBAC), license.ts (11 feature flags enterprise; los 8 flags commodity están siempre activos en Community).

server - el corazón (Express :3000)

API REST + RBAC + orquestador de agente. Ver packages/server/CLAUDE.md.

  • src/routes/ - un archivo por dominio (auth, users, roles, projects, workspaces, documents, agent, chat, providers, apiKeys, eventLogs, analytics, backups, backupJobs, backupDestinations, webhooks, push, license, sso, templates, settings, mcpConnections, marketplace, widgets, internalWidget, health, ocr, archives, wiki, synthesis).
  • src/middleware/ - auth.ts (JWT + API key), rbac.ts (permisos + prevención de IDOR), rateLimit.ts (200/min general, 10/min auth, 10/min probe - ningún limiter de chat dedicado: el agente impone concurrencia/tiempo límite/presupuesto internamente), license.ts (feature flag gating), widgetCors.ts (CORS dinámico por origen).
  • src/services/ - authService, providerService (resolución de modelo + capability), hybridSearchService (RRF), ftsService (tsvector), encryptionService (AES-256-GCM), backupSchedulerService (Bree), eventLogService (+ dispatch de webhooks), dlpFilter, etc.
  • src/agent/ - orchestrator.ts (bucle ReAct), builtinSkills.ts (rag_search, workspace_memory, document_temp_process), llmStreaming.ts (Ollama/OpenAI/Anthropic), mcpClient.ts (conexiones MCP outbound), mcpServer.ts (RAG como tool MCP para IDE).
  • src/config/env.ts - schema Zod para todas las env var; process.exit(1) si son inválidas.

collector - microservicio de ingesta (Express :3210)

Habla con el servidor solo vía HTTP (secreto COLLECTOR_SECRET). No importa Prisma ni servicios del servidor: frontera crítica para el despliegue air-gap en nodos separados.

  • src/routes/ingest.ts - upload (multer), query, youtube, delete.
  • src/services/ - parser (PDF/DOCX/PPTX/XLSX/TXT/CSV/YouTube), chunker (RecursiveCharacterTextSplitter), embeddings (Xenova local / OpenAI), vectorStore (LanceDB / Qdrant).
  • Flujo: server → collector POST /api/ingest/upload → parse → chunk → embed → store → callback del collector PUT /api/documents/:id/status → el servidor actualiza la DB.

widget - chat embebible (Express :3211)

Express CommonJS + bundle Preact IIFE. Se autentica ante el servidor con X-Api-Key.

  • src/index.ts - factory createApp(); static /widget/app.js + loader routes + API.
  • src/routes/ - chat (proxy SSE), session, config, loader, lead.
  • src/widget/ - componentes Preact (12 componentes) en un iframe sandboxed.
  • Proxy SSE transparente: relay de bytes crudos server→iframe, cero parseo.

frontend - SPA (Vite + React 19 :5173)

Nada de Next.js. Proxy /api → server.

  • State management 3-tier: TanStack Query (REST/CRUD), React Context (estado UI), fetchEventSource + useState/useRef (SSE, NO TanStack Query).
  • src/queries/ (22 hooks), src/contexts/, src/hooks/useChat.ts (streaming).
  • i18n: 8 idiomas (en, it, ru, de, fr, es, zh, pt).

src-tauri - app de escritorio

Wrapper Tauri que empaqueta el frontend como app de escritorio; se comunica con el servidor vía HTTP (la misma API). Pensado para despliegue air-gap en máquinas sin navegador.

Data flows principales

Chat streaming (usuario interno)

text
Frontend useChat → POST /api/workspaces/:id/chat/stream (SSE)
→ requireWorkspaceAccess (RBAC/IDOR)
→ agent orchestrator (ReAct loop)
  ├─ resolveSkillsForChat (builtin + MCP pinned)
  ├─ rag_search (vector + tsvector → RRF merge) se serve
  ├─ llmStreaming (provider risolto: per-chat → workspace → global → ENV)
  └─ tool execution (sandboxed)
→ SSE: token | status | citations | done(modelUsed,providerUsed,mcpSources) | error

Resolución de modelo: override per-chat → workspace default → global default → ENV. Fallback graceful en 3 niveles si el modelo no está disponible.

Ingesta de documentos

text
Frontend dropzone → POST /api/documents (server, multer)
→ server salva Document(status=pending) + forwarda file a collector
→ collector: parse → chunk → embed → store(vector) → ritorna chunks con text
→ collector callback PUT /api/documents/:id/status { status, chunkCount }
→ server scrive document_chunks + popola searchVector (tsvector) lato DB
→ frontend polla stato fino a completed

Widget chat (visitante anónimo)

text
Pagina host → <script src="/widget/:id.js"> (loader)
→ iframe sandboxed → Preact app
→ POST /api/sessions (widget) → server crea WidgetSession (256-bit token, 24h)
→ GET /api/config/:widgetId (branding, trigger, lead)
→ user messaggio → POST /api/chat/:widgetId/stream (widget, x-session-token)
  → sessionMiddleware valida → pre-search RAG (whitelist workspaces, IDOR-safe)
  → proxy a server /api/workspaces/:id/chat/stream (X-Api-Key)
  → relay SSE byte verbatim → Preact parse token/citations/done/error

Fronteras y reglas arquitectónicas

  1. `shared` es leaf - ningún import hacia otros packages, ni siquiera import type.
  2. Server ↔ collector = HTTP only - ningún import runtime cross-package; el collector no tiene acceso directo a la DB. Permite escalar el collector en nodos separados.
  3. Widget = servicio separado - no comparte código con el frontend React; usa Preact para un bundle pequeño en iframe sandboxed.
  4. Prisma singleton - importa siempre desde src/utils/prisma, nunca new PrismaClient().
  5. Soft delete universal - las entidades borrables usan deletedAt; todas las queries filtran where: { deletedAt: null }.
  6. Config resolution - ALWAYS_READONLY (JWT_SECRET, DATABASE_URL, puertos, URL): ENV > Default. Todas las demás claves: DB > ENV > Default (modificables desde la UI).

Extender la arquitectura

  • Nueva entidad/endpoint → añade el schema Zod en shared, el modelo Prisma, la ruta en server/src/routes/, el hook TanStack en frontend/src/queries/.
  • Nueva skill de agente → regístrala en server/src/agent/builtinSkills.ts (sandboxed, sin ejecución arbitraria de código).
  • Nuevo idioma → añade frontend/src/i18n/<lang>/translation.json + actualiza i18n-check.cjs (ver 10 - Development).
  • Nuevo provider LLM → extiende providerTypeSchema en shared + handler en server/src/agent/llmStreaming.ts + providerService.