Files
sap-intercompany-worker/docs/TABLAS.md
T

156 lines
9.1 KiB
Markdown
Raw Normal View History

2026-09-16 07:45:57 -05:00
# 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` |