GeorgeandClaude Sonnet 5 ca57403663 Initial commit: sap-intercompany-worker
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>
2026-09-16 07:45:57 -05:00

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:

  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/<code>.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 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-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.

S
Description
No description provided
Readme
759 KiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 100%