# sap-intercompany-worker Worker que replica datos maestros entre bases de SAP Business One (Service Layer), a partir de **escenarios** configurables — no hay una casa matriz fija ni una lista fija de subsidiarias: cualquier base registrada puede ser origen o destino, según lo que declare cada escenario. ## Qué hace Cada `POLL_INTERVAL_SECONDS` (default 30s), revisa la tabla `webhook_events` en Postgres — la misma que llena [`sap-webhook-api`](../sap-webhook-api) al recibir un webhook de SAP — buscando eventos pendientes. Por cada uno: 1. Busca los **escenarios activos** que aplican a ese `businessObject` (puede haber más de uno, ej. "clientes" y "proveedores" sobre `BusinessPartners`, cada uno con su propio filtro), y descarta los que no vinieron realmente de la base origen que ese escenario declara (ver protección contra loops en `docs/ARQUITECTURA.md`). 2. Lee el registro completo desde la base origen y lo pasa por el `filter` del escenario (`src/scenarios/.ts`) — si no aplica, sigue con el próximo. Si aplica, por cada destino lo pasa por `transform` (con acceso a mapeos de valor cargados en Postgres vía `ctx.mapValue`) y resuelve si ya existe en ese destino — por clave directa o por un UDF de correspondencia, según el `keyStrategy` del escenario — para decidir `PATCH` o `POST`. 3. Registra cada intento (escenario, verbo usado, HTTP status, error, y el **payload ya transformado enviado**) en `replication_attempts`. 4. Calcula el estado final del evento sobre la suma de todos los intentos de todos los escenarios que aplicaron: `done` / `partial` / `failed` / `filtered` (ningún escenario aceptó el mensaje). Es un proceso separado de `sap-webhook-api` a propósito: SAP tiene un timeout de 10s para la respuesta del webhook, y replicar hacia varios destinos (con sus propios logins y llamadas) puede tardar más que eso — por eso el trabajo pesado corre acá, a su propio ritmo, sin poner en riesgo la suscripción del webhook. Ver [`docs/ARQUITECTURA.md`](./docs/ARQUITECTURA.md) para el detalle completo del diseño (por qué escenarios y no reglas fijas, `keyStrategy`, diagrama de flujo, reglas de agregación de estado), [`docs/ESCENARIOS.md`](./docs/ESCENARIOS.md) para la guía paso a paso de cómo configurar uno con ejemplos reales, y [`docs/FLUJO.md`](./docs/FLUJO.md) para el diagrama de secuencia archivo-por-archivo, función por función, de todo el camino de un mensaje (incluye `sap-webhook-api`). **Implementado**: datos maestros con destino fijo (`destinationMode = 'fixed'` — `BP_PROVEEDORES`, `CHARTOFACCOUNTS`, `PROFITCENTERS`, `SALESPERSONS`) y transacciones con destino resuelto desde el propio mensaje (`destinationMode = 'payload'` — `JOURNALENTRIES`, primer caso real). Un escenario también puede declarar un `destinationBusinessObject` distinto al de origen (ej. `PurchaseOrders` → `Orders`), para cuando el objeto cambia de nombre entre bases. **Trabajo futuro**: `sap-intercompany-viewer` (frontend) + `sap-intercompany-api` (backend nuevo, dueño de `databases`/`scenarios`/ `scenario_destinations`/`scenario_value_mappings`/`webhook_events`/ `replication_attempts` vía HTTP, ya que este worker no expone HTTP a propósito) — visor único de eventos/log/escenarios con reproceso, en reemplazo de `sap-webhook-viewer`. ## Quick start ```bash npm install cp .env.example .env # completar DATABASE_URL / POLL_INTERVAL_SECONDS # Postgres: instancia nativa (Homebrew) en localhost:5432 — ya no se usa # Docker para esto (docker-compose.yml queda sin uso; si tenés un Postgres # corriendo por Homebrew/apt en el mismo puerto, ojo con el conflicto: # localhost prioriza la instancia nativa sobre un contenedor Docker). npx prisma migrate deploy # Registrar bases y al menos un escenario antes de correr el worker # (ver docs/ESCENARIOS.md para la guía completa con ejemplos reales) npx tsx scripts/manage-config.ts database:add HQ01 "Casa Matriz" npx tsx scripts/manage-config.ts scenario:add HQ01 npx tsx scripts/manage-config.ts scenario:add-destination # editar src/scenarios/.ts (se generó solo con la primera línea) npm run run-once # una sola pasada, útil para probar npm run start # corre en loop ``` Sin destinos configurados para un escenario, el worker igual corre: lee el registro de la base origen y deja el evento en `failed` (0 intentos), sin romper — útil para probar el flujo antes de tener todas las bases reales. ## Estructura ``` prisma/schema.prisma # modelos (ver docs/TABLAS.md) docs/ ARQUITECTURA.md # conceptos, keyStrategy, diagrama de flujo, reglas de estado TABLAS.md # referencia de columnas por tabla, con ejemplos reales ESCENARIOS.md # guía paso a paso con 5 ejemplos reales configurados FLUJO.md # diagrama de secuencia + funciones principales por archivo scripts/manage-config.ts # CLI de administración (databases, scenarios, destinos, mapeos) src/ config.ts # solo POLL_INTERVAL_SECONDS; todo lo demás vive en Postgres serviceLayer.ts # cliente mínimo del Service Layer (Login/GET/POST/PATCH) replicationConfig.ts # carga de config activa desde Postgres en cada tick scenarioTransform.ts # carga/ejecuta filter+transform de un escenario + ctx.mapValue replicateEvents.ts # motor: resuelve N escenarios por evento (datos maestros y transacciones) scenarios/ # un archivo .ts por escenario (filter + transform) index.ts # cron / --once ``` ## Stack - Script plano en TypeScript (ESM), ejecutado con `tsx` — sin framework, porque no expone HTTP. - Prisma 7 + `@prisma/adapter-pg` contra Postgres. - `node-cron` para el loop periódico. Contexto completo de por qué se armó así (decisiones de arquitectura, alternativas descartadas) en `CLAUDE.md`, en la raíz del repo.