Motor de escenarios de replicacion entre bases SAP Business One (Service Layer). Poll periodico sobre webhook_events, escenarios configurables via Prisma (filter + transform por escenario). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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 al
recibir un webhook de SAP — buscando eventos pendientes. Por cada uno:
- Busca los escenarios activos que aplican a ese
businessObject(puede haber más de uno, ej. "clientes" y "proveedores" sobreBusinessPartners, 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 endocs/ARQUITECTURA.md). - Lee el registro completo desde la base origen y lo pasa por el
filterdel escenario (src/scenarios/<code>.ts) — si no aplica, sigue con el próximo. Si aplica, por cada destino lo pasa portransform(con acceso a mapeos de valor cargados en Postgres víactx.mapValue) y resuelve si ya existe en ese destino — por clave directa o por un UDF de correspondencia, según elkeyStrategydel escenario — para decidirPATCHoPOST. - Registra cada intento (escenario, verbo usado, HTTP status, error, y el
payload ya transformado enviado) en
replication_attempts. - 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 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 para la guía paso a paso de
cómo configurar uno con ejemplos reales, y
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
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" <baseUrl> <companyDb> <user> <pass>
npx tsx scripts/manage-config.ts scenario:add <code> <name> <businessObject> HQ01 <keyField>
npx tsx scripts/manage-config.ts scenario:add-destination <code> <databaseCode>
# editar src/scenarios/<code>.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 <code>.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-pgcontra Postgres. node-cronpara 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.