Previous
PuntoOS POS Architecture Engineering

PuntoOS from the inside: how we built a POS for the e-invoicing era

Dawlin Peña
Dawlin Peña
May 15, 2026 10 min read

What a POS had to stop being

When we started PuntoOS, the dominant point-of-sale systems in the Dominican Republic were:

  • Windows apps backed by a local database.
  • Sync via email or end-of-day .csv files.
  • No ability to operate across branches.
  • Inventory the owner trusted was accurate.

And since 2023, a new pressure: mandatory DGII electronic invoicing. The POS stopped being a calculator with a printer; it became a piece of real-time fiscal infrastructure.

We built PuntoOS assuming this from day one.

The architectural pillars

1. Offline-first, cloud-authoritative

The cashier must be able to sell if the internet goes down. That’s rule number one. But also: the cloud must be the source of truth. Those two constraints look contradictory until you accept the cost: eventual replication with deterministic conflict resolution.

┌──────────────────┐                      ┌──────────────────┐
│ PuntoOS Cashier  │ ◀──── WebSocket ───▶ │ PuntoOS Cloud    │
│                  │                      │                  │
│ Local SQLite     │                      │ SQL Server +     │
│ Vector clock     │                      │ Append-only log  │
│ Outbox pattern   │                      │ Read replicas    │
└──────────────────┘                      └──────────────────┘
       │                                          │
       │     If the network is down:               │
       │     ▶ Sale closes locally                 │
       │     ▶ e-CF queues up                      │
       │     ▶ Syncs when it returns               │
       │                                          │
       │     If the network is up:                 │
       │     ▶ Real-time events (WS)              │
       │     ▶ e-CF submitted in <2s               │

Every cashier holds a local outbox: any sale, inventory adjustment, or e-CF not yet acknowledged by the cloud lives there. When connectivity returns, it drains in order with Idempotency-Key. Remote replicas can’t get ahead because the cashier signs each event with a local vector clock.

2. Real-time, multi-branch inventory

The second pillar is unified inventory. In a 50-store chain, the business question is never “how much do I have at this store?” — it’s “how much do I have total, and where is it?”

Our model: every inventory movement is an immutable event. The on-hand at any moment is a projection over that log.

-- Fact table (immutable, append-only)
CREATE TABLE inventory_event (
  id            UUID PRIMARY KEY,
  occurred_at   TIMESTAMPTZ NOT NULL,
  store_id      INT NOT NULL,
  sku           TEXT NOT NULL,
  quantity      NUMERIC NOT NULL,  -- positive = in, negative = out
  reason        TEXT NOT NULL,     -- 'sale', 'transfer_in', 'adjustment', ...
  reference     TEXT,              -- id of the sale, transfer, etc.
  vector_clock  JSONB NOT NULL
);

-- Materialized view (projection, eventually consistent)
CREATE MATERIALIZED VIEW inventory_balance AS
SELECT store_id, sku, SUM(quantity) AS on_hand
FROM inventory_event
GROUP BY store_id, sku;

Why it matters: it makes discrepancies investigable, not mysterious. Any mismatch is reproducible by replaying the log up to the point where it broke.

3. e-CF integrated, not bolted on

PuntoOS doesn’t have an “electronic invoicing module” — it is electronic invoicing. Every sale generates the corresponding e-CF as part of the natural flow.

┌─────────────┐  Charge  ┌────────────┐  Sign     ┌──────────┐
│ Cashier     │ ───────▶ │ Build      │ ────────▶ │ DGII     │
│ (UI)        │          │ e-CF XML   │           │          │
└─────────────┘          └────────────┘           └────┬─────┘
                                                       │ ACECF
                          ┌────────────┐               │
                          │ Print with │ ◀─────────────┘
                          │ NCF +      │
                          │ fiscal QR  │
                          └────────────┘

Details that matter in production:

  • Local NCF numbering: each cashier takes pre-assigned ranges from the cloud; it never runs out of sequence if the network drops.
  • Automated daily close: the RFCE is built and submitted without human intervention at 23:55.
  • Credit notes against offline sales: the note has to reference the original NCF, which may still be in the outbox. PuntoOS keeps them queued until the original gets its accepted ACECF.

4. Multi-country without rewriting the codebase

PuntoOS runs in five countries. Each with its own tax regime:

  • 🇩🇴 DR: e-CF / DGII
  • 🇨🇷 CR: Hacienda
  • 🇸🇻 SV: DGI El Salvador
  • 🇬🇹 GT: SAT FEL
  • 🇭🇳 HN: SAR

The tax-compliance capsule is a plug-in, not a branch of the codebase. Each country has its adapter that implements the same interface:

interface TaxAdapter {
  validate(sale: Sale): ValidationResult
  buildDocument(sale: Sale): TaxDocument
  sign(doc: TaxDocument, key: SigningKey): SignedDocument
  submit(doc: SignedDocument): Promise<Acknowledgement>
  receiveAcknowledgement(rawResponse: unknown): Acknowledgement
}

Adding a new country is a sprint, not a project.

Decisions we made against the grain

React Native, not pure native

For the cashier on tablet, we evaluated native Swift/Kotlin vs React Native. We chose React Native because:

  • 90% of the code shared across iOS, Android, and the owner’s inventory app.
  • Performance sufficient for a POS (it’s not a AAA game).
  • The team can iterate the UI without waiting for two separate deploys.

What we paid: peripheral integrations (thermal printer, cash drawer, barcode scanner) required native modules. We accepted that consciously.

SQL Server, not PostgreSQL

Yes, we know. The decision came from the clients — most were already running on SQL Server with paid licenses and trained DBAs. Fighting that would have been engineering vanity, not customer service. For clients who preferred PostgreSQL, we support it too.

.NET 9 on the backend

A combination of:

  • A transaction hot path that needs predictable performance (tunable GC).
  • A team with deep C# experience.
  • A robust library ecosystem for XAdES XML signing, certificate management, and tax authority integration.

It’s not the coolest tech. It’s the one that sleeps well in production at 3 AM.

What happens when something breaks

Three real incidents from the past year:

A branch with no internet for 14 hours

PuntoOS sold normally for all 14 hours. When the network came back, we drained 847 e-CFs in order. DGII received each with its Idempotency-Key; zero duplicates. The owner found out from our alert dashboard, not because anything stopped working.

An unexpected schema change from DGII

Mid-afternoon, DGII updated a field in the ACECF. Our strict parser started rejecting valid responses. We rolled back the parser in 12 minutes; the customer never noticed. The lesson: validate ours strictly, accept theirs permissively (Postel revisited).

A cashier typed 99,999 instead of 9.99

Impossible to prevent with code. But detectable: 3 minutes in, the “atypical ticket” alarm fired (z-score over the median of the last 200 sales). Hot reverse, credit note before the customer left the store.

What’s coming

  • Inventory intelligence: SKU-level demand forecasting using real historical data.
  • Automatic bank reconciliation: cross-checking sales against deposits.
  • Extension marketplace: third parties building modules on top of the platform.

If you want to see PuntoOS, reach out or visit puntoos.net.

Dawlin