- Al reactivar una suscripción (resume o detección X -> A en el dispatcher) se reinicia U_Retry de sus entregas pendientes, para que no vuelva a 'X' en el siguiente tick. - Nuevo comando subscription:delete: borra filtros, entregas fallidas y eventos de @OEVN que quedan huérfanos. - Evita TypeError en sendPendingDeliveries cuando la suscripción pasa a 'X' después de armar su grupo de envío. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
sap-legacy-webhook-emulator
Reconstruye el mecanismo del Webhook Messenger nativo de SAP Business
One (disponible desde FP2605) para clientes que todavía no están en esa
versión, sin tocar nada de sap-webhook-api: termina
mandando el mismo payload CloudEvents contra el mismo endpoint
(POST /webhooks/sap) que mandaría el Messenger real.
Ver docs/DISENO.md para el diseño completo: por qué
SBO_SP_PostTransactionNotice y no SBO_SP_TransactionNotification, por
qué las 4 tablas son UserTablesMD en vez de SQL crudo, el diccionario de
datos completo (esquema real de SAP vs. el que implementamos), los mapeos
ObjectType↔BizObject / TransType↔Operation, y el diagrama de flujo
del dispatcher.
Cómo funciona
- Captura: la lógica vive en
SP_Webhook_CaptureEvent(sql/transaction-notification/sp-webhook-capture-event-mssql.sql) — hace un únicoINSERTen la UserTable@OEVNsi el evento no es un eco de una escritura nuestra (verdocs/DISENO.md, hallazgo 5) y hay al menos una suscripción activa interesada en eseObjectType+TransType(EXISTScontra@EVC1/@OEVC— lee la config real, no duplica ninguna lista). El hook compartido de SAP,SBO_SP_PostTransactionNotice(sql/transaction-notification/alter_sbo_sp_posttransactionnotice.sql), queda reducido a un soloEXEChacia ese procedimiento — así nuestra lógica es identificable y no se mezcla con la de otros add-ons que puedan tener código en ese mismo hook. ⚠️ El filtro por suscripción activa está escrito pero no reinstalado ni reverificado contra un ambiente real todavía — verdocs/DISENO.md, "Pendientes" ítem 0. - Provisioning:
npm run provisioncrea (si no existen) las 4 UserTables —@OEVC/@EVC1/@OEVN/@EVN1— vía Service Layer (UserTablesMD/UserFieldsMD), replicando el modelo de datos del Webhook Messenger real. - Dispatcher (
src/dispatcher/, corre connpm start): cadaPOLL_INTERVAL_SECONDS, un tick hace dos pasadas:- Registra eventos nuevos (
@OEVNconStatus='N'): resuelveBizObject/Operation(src/mapping/), los matchea contra las suscripciones activas en@OEVC+@EVC1, crea las filas de@EVN1que falten, y descarta (borra) la fila de@OEVNsi elObjectTypeno mapea a nada conocido o si no hay ninguna suscripción interesada. - Despacha lo pendiente (
@EVN1conStatus='F'): agrupa por suscripción, arma el batch de CloudEvents y hace un soloPOSTporWebhookURL(firmando con HMAC si la suscripción esAuthType='H'), actualiza@EVN1/@OEVN, e incrementaRetryen cada fallo. Al superarRETRY_LIMIT, la suscripción pasa aState='X'(Exceptional) y deja de reintentarse hasta que se reactive a mano.
- Registra eventos nuevos (
Quick start
npm install
cp .env.example .env # completar SL_* / POLL_INTERVAL_SECONDS / RETRY_LIMIT
npm run provision # crea las 4 UserTables en la company DB del cliente
Si preferís revisar o postear el body exacto a mano (Postman, curl,
tools/sl.http) en vez de correr npm run provision, están en
json/user-tables/*.json — uno por tabla, con table (body de
POST /UserTablesMD) y fields (bodies de POST /UserFieldsMD, en orden).
Se regeneran con npx tsx scripts/export-table-json.ts si cambia
table-definitions.ts.
Después, instalar en esa company DB, en este orden (SQL Server):
sql/transaction-notification/sp-webhook-capture-event-mssql.sql(creaSP_Webhook_CaptureEvent).sql/transaction-notification/alter_sbo_sp_posttransactionnotice.sql(reemplaza el cuerpo deSBO_SP_PostTransactionNotice, que SAP crea vacío por cada compañía nueva, con elEXEChacia el procedimiento anterior).
En HANA es el mismo orden, con sp-webhook-capture-event-hana.sql y
recreate_sbo_sp_posttransactionnotice_hana.sql (que hace DROP+CREATE
en vez de ALTER, porque HANA no soporta modificar el cuerpo de un
procedimiento existente). ✅ Ambos ya se probaron e instalaron contra un
SAP Business One sobre HANA real (captura y entrega confirmadas de punta
a punta) — ⚠️ pero la versión actual del archivo (con el filtro por
suscripción activa, ver más arriba) todavía no se reinstaló ni reverificó
después de ese cambio.
Antes de arrancar el dispatcher, dar de alta al menos una suscripción (el
destino y la autenticación salen de acá, no de .env). Para probar sin
necesitar una instancia real de sap-webhook-api, usar
sap-test-legacy-webhook (receptor mínimo
de prueba, HTTP o HTTPS):
npx tsx scripts/manage-subscriptions.ts subscription:add SapWebhookApiTest https://192.168.1.166:3000/webhooks/sap
npx tsx scripts/manage-subscriptions.ts filter:add SapWebhookApiTest Orders A 17
Otros comandos disponibles: subscription:list, subscription:update-url <webhookId> <url>, subscription:pause/subscription:resume,
filter:list, filter:remove <webhookId> <bizObject> <transType>.
npm run run-once # una sola pasada, útil para probar
npm run start # corre en loop
Logs
Cada tick escribe a logs/dispatcher-YYYY-MM-DD.log (uno por día) además
de la consola — línea simple <timestamp ISO> [NIVEL] mensaje, legible
por cualquier visualizador genérico (tail -f logs/dispatcher-*.log,
lnav, etc.). Siempre deja al menos una línea de resumen por tick
(Tick: N evento(s) nuevo(s), M pendiente(s) de entrega), incluso sin
actividad — para poder confirmar que el proceso sigue vivo. No versionado
(.gitignore).
Estructura
docs/DISENO.md # diseño completo, diccionario de datos, decisiones, diagrama de flujo
sql/transaction-notification/
sp-webhook-capture-event-mssql.sql # SP_Webhook_CaptureEvent — la lógica real (SQL Server)
alter_sbo_sp_posttransactionnotice.sql # SBO_SP_PostTransactionNotice — un EXEC al de arriba
sp-webhook-capture-event-hana.sql # ídem, HANA/SQLScript — no verificado, ver aviso en el archivo
recreate_sbo_sp_posttransactionnotice_hana.sql # ídem, HANA (DROP+CREATE, no ALTER)
scripts/
manage-subscriptions.ts # CLI: alta/listado de suscripciones y filtros (@OEVC/@EVC1)
export-table-json.ts # regenera json/user-tables/*.json a partir de table-definitions.ts
json/user-tables/ # body real de UserTablesMD/UserFieldsMD por tabla, para revisar o postear a mano
OEVC.json / EVC1.json / OEVN.json / EVN1.json
src/
config.ts # env: Service Layer, poll interval, retry limit
service-layer-client.ts # cliente mínimo del Service Layer (Login/GET/POST/PATCH/DELETE)
lib/
codes.ts # Code sintético (UUID) para filas creadas desde Node
timestamps.ts # formato HHMMSS para U_LastSentTM
cloud-event.ts # arma el payload CloudEvents a partir de una fila de @OEVN
logger.ts # log a consola + archivo (logs/dispatcher-YYYY-MM-DD.log)
mapping/
object-types.ts # ObjectType -> BizObject
trans-types.ts # TransType -> Operation
provisioning/
table-definitions.ts # esquema declarativo de las 4 UserTables (única fuente de verdad)
payloads.ts # traduce table-definitions.ts a los bodies reales de Service Layer
user-tables.ts # crea UserTablesMD/UserFieldsMD si no existen (idempotente)
run-provision.ts # CLI: npm run provision
subscriptions/
subscriptions-repository.ts # acceso a @OEVC/@EVC1 (suscripciones activas y filtros)
dispatcher/
oevn-repository.ts # acceso a @OEVN (leer/borrar/actualizar estado)
evn1-repository.ts # acceso a @EVN1 (crear, listar pendientes, marcar entrega/fallo)
send-webhook.ts # POST del batch de CloudEvents, firma HMAC opcional
own-table-guard.ts # detecta ecos de escrituras propias (ver docs/DISENO.md, hallazgo 5)
process-tick.ts # orquesta un tick completo
index.ts # cron loop / --once
Stack
Mismo criterio que sap-intercompany-worker:
script plano en TypeScript (ESM) con tsx, sin framework, sin base de
datos propia — todo el estado vive en las 4 UserTables dentro de SAP.
Probar sin una instancia real de sap-webhook-api
sap-test-legacy-webhook es un receptor
mínimo (sin framework, sin autenticación por ahora, HTTP o HTTPS con
certificado autofirmado) pensado específicamente para probar este
proyecto de punta a punta sin depender de sap-webhook-api. Guarda cada
CloudEvent recibido en data/events/<id>.json. Usado para confirmar el
flujo completo (captura → dispatcher → entrega) y, por separado, que
HTTPS + certificado autofirmado funciona bien del lado del dispatcher.
Simplificaciones deliberadas de esta v1
- Solo
AuthTypeN(sin firma) yH(HMAC) —BasicyOAuthno están implementados. - Solo se maneja una clave simple en el evento (
FieldsInKe=1, típicamenteDocEntry) — claves compuestas (ej.CardCode+ItemCode) no se arman todavía endatadel CloudEvent. - No hay CLI para administrar
RETRY_LIMIT/Statede forma más fina que pausar/reactivar toda la suscripción.