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
