Custom SaaS · multi-tenant platform
Milsaca

Problem
A whole sector ran improvised between spreadsheets and WhatsApp, with no single system.
Solution
Multi-tenant SaaS (web + mobile) with role-based panels, automations and secure data (RLS). Proof I ship complex products end to end.
SaaS under active development.
How it was built
A Turborepo + pnpm monorepo with three fronts on one backend: Next.js 16 (App Router) for web, which alone covers three panels (producer, broker, and platform admin), and Expo Router for the producer's mobile app. Web and mobile talk to the same Supabase through a shared package (@milsaca/db) with three client layers: one with a session cookie for the server, one cookie-less (public key only) for cached routes, and one with secure-store for mobile.
Multi-tenant for real, not just a "WHERE filter": every table in the public schema has Row Level Security enabled, no exceptions, with isolation by corretora_id enforced in the database, not in the application layer. Roles (producer/broker/admin) live in an array on the profile table, letting the same email hold more than one role; the platform admin sits in its own table, outside the role enum, so a sloppy RLS policy can't accidentally leak it.
The auth decision is a good example of learning from the product itself: it started with magic links and was switched to a 6-digit email OTP, because Gmail Safe Links auto-prefetches the link (to scan it for safety) and ends up "burning" the one-time token before the user even clicks. It's a subtle bug that only shows up in production, with real email providers.
The domain differentiator is the COB classification (Brazilian MAPA regulation IN 8/2003): the logic for coffee type, defects, screen size and cup quality was extracted into a pure package (@milsaca/cob), with no runtime dependencies, independently testable. The generated report ships as a PDF with a QR code and a public verification page, giving the producer something that today is done on paper or from memory.
Multi-tenant via RLS
Isolation by corretora_id enforced in the database, not the app. RLS on every table.
Digital COB report
IN 8/2003 (MAPA) classification engine as a pure package, with PDF + public QR verification.
Live market quotes
CEPEA, ICE NY Coffee C and PTAX (Brazil's central bank) synced via a scheduled edge function.
Web + mobile, one backend
Next.js 16 and Expo Router sharing the same Supabase through a layered client package.
More screens
Stack
- Next.js
- Expo
- Supabase
- PostgreSQL
- TypeScript


