Skip to main content

RPApos Gateway — Architecture

Technical design of the RPApos → FlowPOS catalog sync pipeline.

Document version: 1.0 · Last updated: 2026-05


High-level flow

sequenceDiagram
participant PWA as frontend-pwa
participant API as RpaposController
participant Sync as RpaposSyncService
participant Orch as RpaposOrchestrator
participant HTTP as RpaposHttpAdapter
participant RPA as subdomain.2rpa.com
participant DB as PostgreSQL

PWA->>API: POST /rpapos/sync
API->>DB: INSERT rpapos_sync_run
API-->>PWA: syncRunId
Sync->>HTTP: login (WebForms)
HTTP->>RPA: GET+POST /Login
loop Each entity (dependency order)
Sync->>Orch: orchestrateEntity
Orch->>HTTP: fetchEntity
HTTP->>RPA: GET /api/...
Orch->>DB: rpapos_extraction_archive
Orch->>Orch: transform
alt RestSink entity
Orch->>DB: direct upsert + rpapos_id_map
else HandlerSink entity
Orch->>DB: import_job via DataImportService
end
end
opt importProductImages
Sync->>Sync: RpaposProductImageImportService
Sync->>DB: import_job (type product-image)
Sync->>DB: per_entity_stats.product_images
end
Sync->>DB: UPDATE rpapos_sync_run status

The HTTP handler returns syncRunId immediately; runSync continues asynchronously in the same Node process.


Sync modes

Defined in RpaposSyncMode (packages/global/enums/rpapos.enums.ts):

ModePlanner usageEntity resolution
catalogEverythingFull RPAPOS_CATALOG_ENTITIES, unless entities[] is passed
customQuick start and Customentities[] from UI
incrementalNot used in plannerDefaults to full catalog
historicalNot used in plannerentities[] or empty

resolveEntities() always applies expandEntityDependencies() so partial selections still run prerequisites in canonical order.

Bodega skip: When targetLocationId is set and the user did not request bodega, auto-included bodega is filtered out so dining areas/tables map to the chosen FlowPOS location instead of creating a new one from RPA.


Extraction layer

RpaposHttpAdapter implements IRpaposSource:

  • Login: ASP.NET WebForms at https://{subdomain}.2rpa.com/Login — parses __VIEWSTATE, posts credentials, detects success via localStorage.token = '<jwt>' in the response HTML.
  • Fetch: Session cookies on GET https://{subdomain}.2rpa.com/api/{path}.
  • Mesa: No flat list endpoint — fetches /api/area, then /api/Mesa/{areaId} per area and flattens.
  • Resilience: 5 req/s token bucket per subdomain; circuit breaker after 5 consecutive failures; 3 retries with exponential backoff.

See External RPApos API for path mapping.


Orchestration layer

RpaposOrchestratorService per entity:

  1. Update rpapos_sync_run.per_entity_stats[entity]running
  2. fetchEntity → raw JSON array
  3. rpapos_extraction_archive — full payload snapshot for audit/replay
  4. Special case: SKU — pre-create missing categories from SKU_Grupo_Nombre on wire
  5. Transform — entity-specific transformer → import row shape + __rpapos metadata
  6. SinkRestSink or HandlerSink
  7. Id mapIdMapWriterService records (business_id, entity_type, external_id) → flowpos_entity_id
  8. Update per-entity stats → completed or failed

Pause/cancel: RpaposSyncService checks rpapos_sync_run before each entity; pause polls every 5s until paused is false.


Product image import (optional second phase)

When importProductImages is true on POST /rpapos/sync:

  1. Catalog phase — Same REST pipeline as above (importProductImagesOnly skips this entirely).
  2. Token — Prefer encrypted soap_token_encrypted from SAT Dispositivo_Registra_2; fall back to the short-lived JWT from WebForms login if no device token is stored.
  3. SOAP metadataRpaposSoapAdapter on db4:
    • Objeto_Archivo_1 — image blob catalog (object ids)
    • Catalogo_4 (tried with pTabla: Producto_U_M, viw_Producto_1, Producto) — links Codigo_PLUObjeto_Archivo; best table is selected by rows that have both Codigo_PLU populated and Objeto_Archivo > 0 (requires estacion_trabajo from SAT registration)
  4. Binary fetchimgGrab URL per object id (HTTP GET with session cookies).
  5. Match — Resolve FlowPOS product.id via rpapos_id_map where entity_type = plu and external_id matches Codigo_PLU.
  6. UploadProductsService stores image in GCS and sets product.imageUrl.
  7. Audit — Creates an import_job with type product-image (options include syncRunId); updates per_entity_stats.product_images on the sync run.

importProductImagesOnly runs steps 2–7 only (empty entity list). The planner sends this when Custom scope has zero entities checked but Import product images is enabled.

Image step errors are logged and do not mark the sync run as failed (catalog completion is preserved). Operators should inspect product_images stats and Import History.

Availability probing (exploratory): RpaposProductImageProbeService via POST /rpapos/connections/preview-product-images. Classification codes: soap_endpoint_unavailable, no_image_sources, sync_ack_without_rows, rest_only_unsupported.

See External RPApos API — SOAP.


Sink strategy

HandlerSink

Delegates to DataImportService.createJob() with the same import types as CSV import. Rows appear under Data Imports → jobs in the UI.

RestSink

Direct Kysely upserts for entities without a suitable handler (or that need custom SQL). Still creates synthetic import_job / import_row records for history.

EntityImport type string
Moneda(RestSink only — currency tables)
UnidadMedida(RestSink — unit_of_measure)
PluGrupo, SkuGrupo, ProduccionGrupocategory
Bodegalocation
Proveedorsupplier
Area(RestSink — dining_area)
Plu, Skuproduct
Usuarioemployee
Mesa(RestSink — dining_table)
Persona(RestSink — customer)
BatchMaestro(RestSink — product_recipe)

Full table mapping: Entity mapping.


Connection credentials

  • Tenant web: usuario + password_encrypted — REST catalog import and WebForms login.
  • SAT (optional): sat_usuario + sat_password_encrypted — preferred for Dispositivo_Registra_2 and SOAP image calls. Defaults to tenant credentials when SAT password is omitted.
  • SOAP device token: soap_token_encrypted plus localizacion_id, conexion_db, conexion_mainframe, estacion_trabajo populated by SAT registration (POST /rpapos/connections/:id/refresh-soap-token).
  • Optional api_key_encrypted via CryptoService + ENCRYPTION_KEY.
  • API responses never return plaintext passwords (hasApiKey, hasSoapToken booleans only).
  • probe, probe-sat, and preview-samples decrypt server-side for live RPApos calls.

Logo preview

  • Public tenant image: GET {origin}/api/logo?subDominio={subdomain} (default origin https://ys.2rpa.com).
  • Backend classification: GET /rpapos/connections/logo-preview — returns base64 PNG + hasCustomLogo (fallback PNG is a fixed byte length).

Key source files

ConcernFile
HTTP routesapps/backend/src/rpapos/interfaces/rpapos.controller.ts
Sync lifecycleapps/backend/src/rpapos/application/rpapos-sync.service.ts
Per-entity pipelineapps/backend/src/rpapos/application/rpapos-orchestrator.service.ts
Product imagesapps/backend/src/rpapos/application/rpapos-product-image-import.service.ts
Image probeapps/backend/src/rpapos/application/rpapos-product-image-probe.service.ts
Catalog resetapps/backend/src/rpapos/application/rpapos-catalog-reset.service.ts
External RESTapps/backend/src/rpapos/infrastructure/rpapos-http.adapter.ts
External SOAPapps/backend/src/rpapos/infrastructure/rpapos-soap.adapter.ts
SAT registrationapps/backend/src/rpapos/infrastructure/rpapos-sat.adapter.ts
Planner UIapps/frontend-pwa/.../planner/MigrationPlannerPage.tsx
SOAP token UIapps/frontend-pwa/.../gateway/rpapos/SoapTokenStatus.tsx
PWA API clientapps/frontend-pwa/src/services/rpaposService.ts