Anterior
PuntoOS POS Arquitectura Engineering

PuntoOS por dentro: cómo construimos un POS para la era de la facturación electrónica

Dawlin Peña
Dawlin Peña
15 de mayo de 2026 10 min de lectura

Lo que un POS tenía que dejar de ser

Cuando arrancamos PuntoOS, los puntos de venta dominantes en República Dominicana eran:

  • Aplicaciones Windows con base de datos local.
  • Sincronización por correo electrónico o archivos .csv enviados a fin de día.
  • Cero capacidad de operar entre sucursales.
  • Inventario que el dueño confiaba que era cierto.

Y desde 2023, una presión nueva: facturación electrónica DGII obligatoria. El POS dejó de ser una calculadora con impresora; pasó a ser una pieza de infraestructura fiscal en tiempo real.

Construimos PuntoOS asumiendo esto desde el día uno.

Los pilares de arquitectura

1. Offline-first, cloud-authoritative

El cajero debe poder vender si se cae internet. Esa es la regla número uno. Pero también: la nube debe ser la fuente de verdad. Esas dos restricciones parecen contradictorias hasta que aceptas el costo: replicación eventual con resolución determinista de conflictos.

┌──────────────────┐                      ┌──────────────────┐
│ PuntoOS Caja     │ ◀──── WebSocket ───▶ │ PuntoOS Cloud    │
│                  │                      │                  │
│ SQLite local     │                      │ SQL Server +     │
│ Vector clock     │                      │ Append-only log  │
│ Outbox pattern   │                      │ Read replicas    │
└──────────────────┘                      └──────────────────┘
       │                                          │
       │     Si la red está abajo:                │
       │     ▶ La venta se cierra local            │
       │     ▶ El e-CF se cola                     │
       │     ▶ Se sincroniza al volver             │
       │                                          │
       │     Si la red está arriba:                │
       │     ▶ Eventos en tiempo real (WS)        │
       │     ▶ e-CF enviado en <2s                 │

Cada caja mantiene un outbox local: cualquier venta, ajuste de inventario o e-CF que no haya sido confirmado por la nube vive ahí. Cuando vuelve la conectividad, se drena en orden con Idempotency-Key. Las réplicas remotas no pueden adelantarse porque la caja firma cada evento con un vector clock local.

2. Inventario en tiempo real, multi-sucursal

El segundo pilar es el inventario unificado. En una cadena con 50 tiendas, la pregunta de negocio nunca es “¿cuánto tengo en esta tienda?” — es “¿cuánto tengo en total y dónde está?”

Nuestro modelo: cada movimiento de inventario es un evento inmutable. La existencia en cualquier momento es una proyección sobre ese log.

-- Tabla de hechos (inmutable, 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,  -- positivo = entrada, negativo = salida
  reason        TEXT NOT NULL,     -- 'sale', 'transfer_in', 'adjustment', ...
  reference     TEXT,              -- id de la venta, transferencia, etc.
  vector_clock  JSONB NOT NULL
);

-- Vista materializada (proyección, eventualmente consistente)
CREATE MATERIALIZED VIEW inventory_balance AS
SELECT store_id, sku, SUM(quantity) AS on_hand
FROM inventory_event
GROUP BY store_id, sku;

¿Por qué importa? Porque hace que los desajustes sean investigables, no misteriosos. Cualquier discrepancia se reproduce reproduciendo el log hasta el punto donde se rompió.

3. e-CF integrado, no atornillado

PuntoOS no tiene un “módulo de facturación electrónica” — es facturación electrónica. Cada venta genera el e-CF correspondiente como parte del flujo natural.

┌─────────────┐  Cobrar  ┌────────────┐  Firmar   ┌──────────┐
│ Cajero      │ ───────▶ │ Generar    │ ────────▶ │ DGII     │
│ (UI)        │          │ XML e-CF   │           │          │
└─────────────┘          └────────────┘           └────┬─────┘
                                                       │ ACECF
                          ┌────────────┐               │
                          │ Imprimir   │ ◀─────────────┘
                          │ con NCF +  │
                          │ QR fiscal  │
                          └────────────┘

Detalles que importan en producción:

  • Numeración local de NCF: cada caja toma rangos pre-asignados desde la nube; nunca se queda sin secuencia si la red se cae.
  • Cierre diario automatizado: el RFCE se construye y se envía sin intervención humana a las 23:55.
  • Notas de crédito sobre ventas offline: la nota tiene que referenciar el NCF original, que puede estar todavía en el outbox. PuntoOS las mantiene encoladas hasta que el original tenga ACECF aceptado.

4. Multi-país sin reescribir el código

PuntoOS opera en cinco países. Cada uno con su propio régimen fiscal:

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

La cápsula de cumplimiento fiscal es un plug-in, no un branch del código. Cada país tiene su adaptador que implementa la misma interfaz:

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
}

Añadir un nuevo país es un sprint, no un proyecto.

Decisiones que tomamos a contramano

React Native, no nativo puro

Para la caja en tablet, evaluamos Swift/Kotlin nativo vs React Native. Optamos por React Native porque:

  • 90% del código compartido entre iOS, Android y la app de inventario del dueño.
  • Performance suficiente para una caja (no es un juego AAA).
  • El equipo puede iterar UI sin esperar dos despliegues separados.

Lo que pagamos: integraciones con periféricos (impresora térmica, cajón monedero, lector de código de barras) requirieron módulos nativos. Eso lo asumimos consciente.

SQL Server, no PostgreSQL

Sí, sabemos. La decisión vino de los clientes — la mayoría ya operaba sobre SQL Server con licencias compradas y DBAs entrenados. Pelear contra eso habría sido vanidad de ingenieros, no servicio al cliente. Para los clientes que prefirieron PostgreSQL, lo soportamos también.

.NET 9 en el backend

Combinación de:

  • Hot path de transacciones que necesita rendimiento predecible (GC tuneable).
  • Equipo con experiencia profunda en C#.
  • Ecosistema robusto de librerías para firma XML XAdES, manejo de certificados, integración con SAT/DGII/etc.

No es la tecnología más cool. Es la que se acuesta bien en producción a las 3 AM.

Lo que pasa cuando algo se rompe

Tres incidentes reales del último año:

Una sucursal sin internet por 14 horas

PuntoOS vendió normalmente durante las 14 horas. Cuando volvió la red, drenamos 847 e-CFs en orden. La DGII recibió cada uno con su Idempotency-Key; cero duplicados. El dueño se enteró por nuestro dashboard de alertas, no porque algo dejara de funcionar.

Cambio inesperado de esquema en la DGII

A media tarde, la DGII actualizó un campo en el ACECF. Nuestro parser estricto empezó a rechazar respuestas válidas. Hicimos rollback del parser en 12 minutos, cliente nunca se enteró. La lección: validar lo nuestro estrictamente, recibir lo externo permisivamente (Postel revisitado).

Cajera escribió 99,999 en lugar de 9.99

Imposible de prevenir con código. Pero detectable: a las 3 minutos sonó la alarma de “ticket atípico” (z-score sobre la mediana de las últimas 200 ventas). Reverso en caliente, nota de crédito antes de que el cliente saliera del local.

Lo que viene

  • Inteligencia de inventario: pronóstico de demanda por SKU usando histórico real.
  • Conciliación bancaria automática: cruzar ventas con depósitos.
  • Marketplace de extensiones: que terceros construyan módulos sobre la plataforma.

Si quieres conocer PuntoOS, contáctanos o visita puntoos.net.

Dawlin