
Clubinho — Corações Preciosos
The mobile product and loyalty platform for a Brazilian children's book subscription club with over 12,000 active subscribers.
Corações Preciosos ships monthly boxes of children's books to more than 12,000 paying families. I own the subscriber-facing half: the React Native app they use every month, and the retention platform behind it — a points currency, a redemption store, a referral program, push infrastructure and a community support network. The billing and fulfillment engine was already running and built by other engineers; my work had to integrate with it in production, never pause it.
ROLE
Mobile Engineer
Full Stack
Product
TOOLS
React Native
Expo SDK 54
TypeScript
NestJS
PostgreSQL
TypeORM
EAS
DURATION
2026 - Present
INTEGRATIONS
Expo Push
AppMax (existing)
Discord
Panel ACL
✶ What I built, and what I built it on
A shared NestJS API and one Postgres database, where the modules I own sit next to a billing and fulfillment engine written by others. The map marks the boundary — tap any node to open it.
✶ Points as a ledger of lots, not a number on a row
Clubzz points move through a per-lot lifecycle — pendente → aprovada → reservada → debitada, plus expirada and anulada — so expiring a reward or reversing a charge touches only the lot it came from, instead of reconciling a single mutable balance after the fact.
✶ Double-click protection pushed into the schema
Redemption is guarded by a partial unique index on (client_id, product_id) WHERE status = 'pending'. A second tap becomes a database constraint violation rather than a race the API layer has to win — and it holds no matter which route or job reaches the table.
✶ Privacy as the constraint that shaped the module
Rede de Apoio carries prayer requests, some describing real crises. Request text never enters a push payload or a log line; automatic risk triage blocks publication and routes to CVV 188, SAMU 192 and 180; consent is versioned, retention is 90 days, and no AI-generated text is ever shown to a subscriber.
✶ Signed media the native player can actually use
The offline audio player streams from URLs signed with HMAC-SHA256 over trackId:exp and compared with timingSafeEqual, on a 6-hour TTL — self-contained because a native player cannot attach a custom auth header. Expiry is validated with Number.isInteger rather than isNaN, which would coerce a raw string and reopen the bypass.
✶ Push that survives its own scale
Expo Push delivery batched 100 tokens at a time, pruning devices Expo reports as DeviceNotRegistered so dead tokens never accumulate, with digest and urgency sweeps on separate cadences to keep notification pressure off the same people.