← Back to projects

Project

Numa — POS with CFDI invoicing built in

Multi-tenant POS and back-office SaaS for small businesses in Mexico. The wedge is CFDI 4.0 invoicing built into the product instead of sold as an add-on, plus self-serve billing — the free POS options in this market are free because they tie the merchant to their own card terminal.

In development — core closed, billing in progress Own product — in development Founder & Engineer (Full Stack — Product, Backend, Infrastructure) Private repository — pre-launch

Own product in active development for the Mexican market. The multi-tenant core, roles and invitations are built and tested; catalog, inventory, sales and CFDI invoicing are the phases in progress. Not publicly launched yet.

Stack

Angular 20TypeScriptSCSSPWA (offline-first)FirebaseFirestoreFirebase AuthCloud Functions v2 (Node 22)Firebase StorageFirebase Emulator SuiteStripeResendGCP

Impact

  • Narrowed the product to a single market, which retired the multi-currency and daily exchange-rate machinery and moved CFDI 4.0 invoicing from a post-MVP phase to a pre-launch one.
  • Catalog, inventory, POS, CFDI 4.0 invoicing and cash-and-finance all closed and tested; what remains before launch is SaaS billing, not the product.
  • Invoicing ships inside the product — in Mexico a POS that cannot stamp CFDI does not compete with the established accounting suites.
  • Self-serve billing on Stripe, against incumbents that still onboard and collect manually.
  • Built entirely against the Firebase Emulator Suite, so the product reached a tested multi-tenant core at zero cloud spend before launch.

What I did

  • Retired Venezuela support in a single refactor: the daily BCV exchange-rate job, the IGTF tax, USD/VES multi-currency, the onboarding country selector and the 'VE' branch all came out, and createTenant now seeds MXN and 16% IVA directly.
  • Business dates resolve in the merchant's own timezone, configurable per tenant and defaulting to America/Mexico_City — Mexico spans four zones and a cash-out closed in the wrong one splits a day's takings across two.
  • Multi-tenant isolation is enforced by Firestore rules with a dedicated test suite (24/24 passing), after an early review found a role-escalation hole where overlapping rule matches combine with OR.
  • Backend-owned fields such as subscription state and tenant configuration are immutable from the client.
  • Invitation-based onboarding with granular per-member permissions: acceptInvite requires a verified email, and any user-controlled text is escaped before it reaches email HTML.
  • Session state never leaks between accounts — the tenant context is dropped when the uid changes and cleared on every logout.
  • Firebase SDKs load through lazy getters rather than eager imports; the change cut the initial bundle from 654 KB to 225 KB and keeps it near 290 KB as features land.
  • Every phase closes with a multi-agent code review before moving on: 16 findings fixed in Phase 0, 13 in Phase 1.
  • Combos price and discount as a unit but consume as a recipe: selling one combo discounts every component from stock, so the shelf never lies because of a bundle.
  • Subscription state is derived from dates, never from a stored field — active, grace and suspended are computed, so a failed write cannot leave a cancelled account looking paid.
  • Global invoice for same-day sales without RFC, which is the case that actually breaks a Mexican POS at closing time.

Architecture

  • Monorepo: apps/web (Angular PWA, offline-first with IndexedDB and Firestore persistence) + functions (Node 22, TypeScript)
  • Firestore multi-tenant model: platform/ for global config and SaaS plans; tenants/{tenantId}/ for members, catalog, inventory, sales, cash boxes, banking, receivables and expenses
  • Callable functions for tenant creation and privileged operations; tenant config seeded with MXN and 16% IVA
  • Security by Firestore rules with explicit carve-outs for members and invites, since overlapping matches combine with OR
  • CFDI 4.0 stamping through a PAC is its own pre-launch phase; Stripe handles SaaS billing in MXN
  • Local-first development: the full Emulator Suite (auth, firestore, functions, storage, pub/sub, tasks) on dedicated ports

Tags

SaaSMulti-tenantPOSPWAOffline-firstFirebaseStripeLATAMFull Stack