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):
| Mode | Planner usage | Entity resolution |
|---|---|---|
catalog | Everything | Full RPAPOS_CATALOG_ENTITIES, unless entities[] is passed |
custom | Quick start and Custom | entities[] from UI |
incremental | Not used in planner | Defaults to full catalog |
historical | Not used in planner | entities[] 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 vialocalStorage.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:
- Update
rpapos_sync_run.per_entity_stats[entity]→running fetchEntity→ raw JSON arrayrpapos_extraction_archive— full payload snapshot for audit/replay- Special case: SKU — pre-create missing categories from
SKU_Grupo_Nombreon wire - Transform — entity-specific transformer → import row shape +
__rpaposmetadata - Sink —
RestSinkorHandlerSink - Id map —
IdMapWriterServicerecords(business_id, entity_type, external_id) → flowpos_entity_id - Update per-entity stats →
completedorfailed
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:
- Catalog phase — Same REST pipeline as above (
importProductImagesOnlyskips this entirely). - Token — Prefer encrypted
soap_token_encryptedfrom SATDispositivo_Registra_2; fall back to the short-lived JWT from WebForms login if no device token is stored. - SOAP metadata —
RpaposSoapAdapteron db4:Objeto_Archivo_1— image blob catalog (object ids)Catalogo_4(tried withpTabla:Producto_U_M,viw_Producto_1,Producto) — linksCodigo_PLU→Objeto_Archivo; best table is selected by rows that have bothCodigo_PLUpopulated andObjeto_Archivo > 0(requiresestacion_trabajofrom SAT registration)
- Binary fetch —
imgGrabURL per object id (HTTP GET with session cookies). - Match — Resolve FlowPOS
product.idviarpapos_id_mapwhereentity_type = pluandexternal_idmatchesCodigo_PLU. - Upload —
ProductsServicestores image in GCS and setsproduct.imageUrl. - Audit — Creates an
import_jobwith typeproduct-image(options includesyncRunId); updatesper_entity_stats.product_imageson 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.
| Entity | Import type string |
|---|---|
| Moneda | (RestSink only — currency tables) |
| UnidadMedida | (RestSink — unit_of_measure) |
| PluGrupo, SkuGrupo, ProduccionGrupo | category |
| Bodega | location |
| Proveedor | supplier |
| Area | (RestSink — dining_area) |
| Plu, Sku | product |
| Usuario | employee |
| 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 forDispositivo_Registra_2and SOAP image calls. Defaults to tenant credentials when SAT password is omitted. - SOAP device token:
soap_token_encryptedpluslocalizacion_id,conexion_db,conexion_mainframe,estacion_trabajopopulated by SAT registration (POST /rpapos/connections/:id/refresh-soap-token). - Optional
api_key_encryptedviaCryptoService+ENCRYPTION_KEY. - API responses never return plaintext passwords (
hasApiKey,hasSoapTokenbooleans only). probe,probe-sat, andpreview-samplesdecrypt server-side for live RPApos calls.
Logo preview
- Public tenant image:
GET {origin}/api/logo?subDominio={subdomain}(default originhttps://ys.2rpa.com). - Backend classification:
GET /rpapos/connections/logo-preview— returns base64 PNG +hasCustomLogo(fallback PNG is a fixed byte length).
Key source files
| Concern | File |
|---|---|
| HTTP routes | apps/backend/src/rpapos/interfaces/rpapos.controller.ts |
| Sync lifecycle | apps/backend/src/rpapos/application/rpapos-sync.service.ts |
| Per-entity pipeline | apps/backend/src/rpapos/application/rpapos-orchestrator.service.ts |
| Product images | apps/backend/src/rpapos/application/rpapos-product-image-import.service.ts |
| Image probe | apps/backend/src/rpapos/application/rpapos-product-image-probe.service.ts |
| Catalog reset | apps/backend/src/rpapos/application/rpapos-catalog-reset.service.ts |
| External REST | apps/backend/src/rpapos/infrastructure/rpapos-http.adapter.ts |
| External SOAP | apps/backend/src/rpapos/infrastructure/rpapos-soap.adapter.ts |
| SAT registration | apps/backend/src/rpapos/infrastructure/rpapos-sat.adapter.ts |
| Planner UI | apps/frontend-pwa/.../planner/MigrationPlannerPage.tsx |
| SOAP token UI | apps/frontend-pwa/.../gateway/rpapos/SoapTokenStatus.tsx |
| PWA API client | apps/frontend-pwa/src/services/rpaposService.ts |