Files
sap-intercompany-worker/docs/TABLAS.md
T
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

157 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Referencia de tablas
Todas viven en la base Postgres compartida (`docker-compose.yml` en la raíz
del workspace), definidas en [`prisma/schema.prisma`](../prisma/schema.prisma).
Los ejemplos de esta página usan datos reales de la config actual del
ambiente de desarrollo (bases `HQ01`/`SUB_01`.."SUB_04", escenario
`BP_PROVEEDORES`).
## `webhook_events`
Bandeja de entrada. `sap-webhook-api` inserta una fila acá por cada
notificación real que recibe de SAP (además de guardar el `.json` local).
No se toca a mano — el worker es el único que la actualiza.
| Columna | Tipo | Notas |
|---|---|---|
| `id` | `String` (PK) | El `id` del CloudEvent que manda SAP. |
| `business_object` | `String` | `'BusinessPartners'`, `'SalesPersons'`, `'ProfitCenters'`, ... |
| `operation` | `String` | `'Created'` \| `'Updated'`. |
| `subject` | `String` | Clave del registro en la base que lo generó (`CardCode`, `SalesEmployeeCode`, `CenterCode`, según el objeto). |
| `payload` | `Json` | El CloudEvent completo tal cual llegó — incluye `source` (`/default/sap.b1/<CompanyDB>`), que el motor usa para la protección contra loops (ver `ARQUITECTURA.md`). |
| `status` | `EventStatus` | `pending` → `processing` → `done` \| `partial` \| `failed` \| `filtered`. |
| `received_at` | `DateTime` | |
## `databases`
Registro único de toda base SAP alcanzable por Service Layer. Sin
distinción HQ/subsidiaria — ver [`ARQUITECTURA.md`](./ARQUITECTURA.md).
| Columna | Tipo | Notas |
|---|---|---|
| `id` | `Int` (PK, autoincremental) | Identificador interno, no se usa como referencia legible. |
| `code` | `String` (único) | Mnemónico corto y **estable**, asignado una sola vez al crear (ej. `HQ01`, `SUB_01`). No autoincremental — es lo que aparece en los logs. |
| `name` | `String` | Descriptivo, solo para mostrar. |
| `base_url` | `String` | URL del Service Layer, ej. `https://sap10:50000/b1s/v2`. |
| `company_db` | `String` | `CompanyDB` de esa base. |
| `username` / `password` | `String` | Credenciales de login del Service Layer. |
| `active` | `Boolean` | Una base inactiva no se usa como origen ni destino en ningún escenario. |
Ejemplo (ambiente de desarrollo actual):
| code | name | base_url | company_db | active |
|---|---|---|---|---|
| `HQ01` | HQ DemoCL | `https://sap10:50000/b1s/v2` | `SBODemoCL` | `true` |
| `SUB_01` | SUB 01 DemoCL | `https://sap10:50000/b1s/v2` | `SUBDemoCL_01` | `true` |
| `SUB_02` | SUB 02 DemoCL | `https://sap10:50000/b1s/v2` | `SUBDemoCL_02` | `true` |
| `SUB_03` | SUB 03 DemoCL | `https://sap10:50000/b1s/v2` | `SUBDemoCL_03` | `true` |
| `SUB_04` | SUB 04 DemoCL | `https://sap10:50000/b1s/v2` | `SUBDemoCL_04` | `true` |
## `scenarios`
Una fila por escenario. Ver [`ESCENARIOS.md`](./ESCENARIOS.md) para cómo
armar uno con el CLI.
| Columna | Tipo | Notas |
|---|---|---|
| `id` | `Int` (PK) | |
| `code` | `String` (único) | Identificador legible del escenario, en mayúsculas (ej. `BP_PROVEEDORES`). Determina el nombre de su archivo (`filter` + `transform`) en minúsculas: `src/scenarios/<code en minúsculas>.ts` (ej. `bp_proveedores.ts`) — ver `scenarioFileSlug` en `src/scenarioTransform.ts`. |
| `name` | `String` | Descriptivo. |
| `business_object` | `String` | Objeto que dispara el escenario: con qué se matchea `webhook_events` y con qué se lee el registro del origen. |
| `destination_business_object` | `String?` | Objeto a usar en el `GET`/`POST`/`PATCH` del **destino**, si es distinto al de origen (ej. origen `PurchaseOrders` → destino `Orders`, típico de comercio intercompañía). `null` ⇒ mismo objeto que `business_object` (el caso común). |
| `origin_database_code` | `String` (FK → `databases.code`) | De dónde se lee el registro completo. |
| `destination_mode` | `ScenarioDestinationMode` | `fixed` (lista fija en `scenario_destinations`) \| `payload` (destino resuelto por `routing_field` — ambos implementados). |
| `key_field` | `String` | Campo clave del registro, ej. `CardCode`, `SalesEmployeeCode`. |
| `key_type` | `ScenarioKeyType` | `string` (va entre comillas en la URL OData) \| `number` (sin comillas — un campo numérico con comillas da HTTP 400). |
| `key_strategy` | `ScenarioKeyStrategy` | `portable` (la clave vale lo mismo en origen y destino) \| `udfLink` (SAP asigna la clave por base; se busca por `match_field`). |
| `match_field` | `String?` | Solo si `key_strategy = udfLink`: nombre del UDF en el destino que guarda la clave de origen (ej. `U_ITC_Source_JdtNum`). |
| `routing_field` | `String?` | Solo si `destination_mode = payload`: UDF en el origen cuyo valor es el `code` de la base destino (ej. `U_ITC_Target`). |
| `active` | `Boolean` | |
No hay columnas de filtro: el filtro es la función `filter()` del archivo
del escenario (ver [`ARQUITECTURA.md`](./ARQUITECTURA.md)).
Ejemplo (los 5 escenarios activos hoy en desarrollo):
| code | business_object | destination_business_object | origin | destinations | key_field | key_strategy |
|---|---|---|---|---|---|---|
| `BP_PROVEEDORES` | `BusinessPartners` | — | `HQ01` | `SUB_01`, `SUB_02` | `CardCode` | `portable` |
| `CHARTOFACCOUNTS` | `ChartOfAccounts` | — | `HQ01` | `SUB_01`, `SUB_02` | `Code` | `portable` |
| `JOURNALENTRIES` | `JournalEntries` | — | `HQ01` | (payload: `U_ITC_Target`) | `JdtNum` | `udfLink` (`U_ITC_Source_JdtNum`) |
| `PROFITCENTERS` | `ProfitCenters` | — | `HQ01` | `SUB_01`, `SUB_02`, `SUB_03` | `CenterCode` | `portable` |
| `SALESPERSONS` | `SalesPersons` | — | `HQ01` | `SUB_01`, `SUB_03` | `SalesEmployeeCode` | `udfLink` (`U_HQ_SlpCode`) |
Ninguno usa todavía `destination_business_object` (ninguno lo necesita por
ahora) — quedaría, por ejemplo, `PurchaseOrders` → `Orders` en un futuro
escenario de compras intercompañía.
## `scenario_destinations`
Destinos fijos de un escenario (solo aplica a `destination_mode = fixed`).
Único por `(scenario_id, database_code)`.
| Columna | Tipo | Notas |
|---|---|---|
| `scenario_id` | `Int` (FK → `scenarios.id`) | |
| `database_code` | `String` (FK → `databases.code`) | |
## `scenario_value_mappings`
Datos puros que el archivo de transformación de un escenario consulta vía
`ctx.mapValue(fieldName, sourceValue)` — ver
[`ARQUITECTURA.md`](./ARQUITECTURA.md). El código decide QUÉ campo mapear y
QUÉ hacer si falta el valor (tirar error, dejar sin traducir, etc.); acá
solo vive la equivalencia. Escopada por escenario (no global por
objeto+campo) porque el mismo campo puede tener dominios de valores
distintos según el escenario (ej. `GroupCode` de clientes vs. de
proveedores).
| Columna | Tipo | Notas |
|---|---|---|
| `scenario_id` | `Int` (FK) | |
| `field_name` | `String` | Ej. `GroupCode`. |
| `database_code` | `String` (FK → `databases.code`) | El destino al que aplica esta traducción puntual. |
| `source_value` | `String` | Valor tal como viene de la base origen. |
| `target_value` | `String` | Valor a usar en ese destino. |
Único por `(scenario_id, field_name, database_code, source_value)`.
Ejemplo real (`BP_PROVEEDORES`, `GroupCode`, hoy 1:1 en ambas subsidiarias):
| scenario | field_name | database_code | source_value | target_value |
|---|---|---|---|---|
| `BP_PROVEEDORES` | `GroupCode` | `SUB_01` | `101` | `101` |
| `BP_PROVEEDORES` | `GroupCode` | `SUB_01` | `110` | `110` |
| `BP_PROVEEDORES` | `GroupCode` | `SUB_02` | `101` | `101` |
| ... | | | | (7 códigos × 2 destinos = 14 filas) |
## `replication_attempts`
Log de auditoría inmutable. Una fila por cada intento de un escenario hacia
una base destino.
| Columna | Tipo | Notas |
|---|---|---|
| `event_id` | `String` (FK → `webhook_events.id`) | |
| `scenario_id` | `Int` (FK → `scenarios.id`) | Imprescindible porque un evento puede pasar por varios escenarios a la vez. |
| `origin_database` | `String` | Código de la base origen usada en **este** intento — texto libre, no FK: sobrevive si la base se borra o renombra después. |
| `destination_database` | `String` | Ídem para el destino. `'N/A'` si el intento falló antes de resolver un destino (ej. error leyendo el origen). |
| `verb_used` | `String` | `POST` \| `PATCH` \| `SKIPPED` (falta mapeo, o transform tiró error) \| `N/A` (error antes de llamar al destino). |
| `http_status` | `Int?` | |
| `error_message` | `String?` | |
| `payload_sent` | `Json?` | El payload ya transformado enviado al destino — se guarda **siempre**, en éxito y en falla. |
| `result_key` | `String?` | Para `key_strategy=udfLink`: la clave propia del destino (encontrada o recién asignada por SAP en un `POST`). `null` en `key_strategy=portable` y en datos maestros sin esta estrategia. |
| `attempted_at` | `DateTime` | |
Índices sobre `event_id`, `scenario_id` y `attempted_at`, pensados para la
futura UI de consulta (`sap-intercompany-viewer`, ver `ARQUITECTURA.md`).
Ejemplo real (`SALESPERSONS`, vendedor nuevo `SalesEmployeeCode=7` en HQ01,
`POST` en ambas subsidiarias — SAP le asignó el mismo número por
coincidencia, pero el motor no lo asume, lo captura del response):
| event_id | scenario | destination_database | verb_used | http_status | result_key |
|---|---|---|---|---|---|
| `149be29e-...` | `SALESPERSONS` | `SUB_01` | `POST` | `201` | `7` |
| `149be29e-...` | `SALESPERSONS` | `SUB_03` | `POST` | `201` | `7` |