Plug Club
A private club in Mar del Plata: barbershop bookings, monthly memberships with automatic billing and a DJ booth rented by the hour.

The club started as a barbershop that took bookings over WhatsApp. The owner wanted members who pay every month, get a set number of haircuts and booth hours, and can use them for guests. Non-members still had to be able to book and pay for a single haircut or an hour in the booth. Payments go through MercadoPago, and its notices don't always arrive.
Three decisions that shaped it.
- 01
One wallet with a ledger
Why. Each member has haircuts and booth hours from the monthly plan, plus loose credits that don't expire. Every booking takes from the plan first and then from credits, inside one transaction, and writes a row to the ledger: renewal, credit, use, guest use or manual adjustment. A booth booking can mix both: hours from the wallet and the rest paid, with the hours taken only when MercadoPago approves the payment.
Trade-off. Memberships expire when they are read, not with a scheduled job. That keeps the deploy simple, but every query that counts active members has to repeat the end-date filter.
- 02
MercadoPago is the source of truth, not its notices
Why. Paid members were left without a membership because the webhook rejected every notice: the signing secret on the server was not the integration's. Now the webhook reads each payment or subscription again from the MercadoPago API, and a reconciliation step finds subscriptions by our own reference (user and plan). It runs when the member comes back from checkout and from a "Sync MP" button in the panel.
Trade-off. A notice with a bad signature is logged and still processed. A fake one can only make us reprocess our own data, which is idempotent, but it is a weaker check than rejecting it.
- 03
Static pages, permissions in every handler
Why. The admin panel stopped rendering on the server. Its pages are prerendered and the middleware asks for the admin role before they load. Public pages are cached for a month and the panel invalidates them when the owner saves a change. Since the pages no longer check the session, each API handler checks its own permissions.
Trade-off. Permissions are checked twice, in the middleware and in the handler, which is more code to keep in step. A change made outside the panel, straight in the database, doesn't show on the public site until the cache expires.




Plug Club
A private club in Mar del Plata: barbershop bookings, monthly memberships with automatic billing and a DJ booth rented by the hour.
- Role
- Full-stack development for a client
- Stack
- Next.js (App Router), TypeScript, Prisma + Postgres, Auth.js, MercadoPago (Checkout + subscriptions), Tailwind CSS, Resend + Twilio, Vercel

The club started as a barbershop that took bookings over WhatsApp. The owner wanted members who pay every month, get a set number of haircuts and booth hours, and can use them for guests. Non-members still had to be able to book and pay for a single haircut or an hour in the booth. Payments go through MercadoPago, and its notices don't always arrive.
Three decisions that shaped it.
One wallet with a ledger
Why. Each member has haircuts and booth hours from the monthly plan, plus loose credits that don't expire. Every booking takes from the plan first and then from credits, inside one transaction, and writes a row to the ledger: renewal, credit, use, guest use or manual adjustment. A booth booking can mix both: hours from the wallet and the rest paid, with the hours taken only when MercadoPago approves the payment.
Trade-off. Memberships expire when they are read, not with a scheduled job. That keeps the deploy simple, but every query that counts active members has to repeat the end-date filter.
MercadoPago is the source of truth, not its notices
Why. Paid members were left without a membership because the webhook rejected every notice: the signing secret on the server was not the integration's. Now the webhook reads each payment or subscription again from the MercadoPago API, and a reconciliation step finds subscriptions by our own reference (user and plan). It runs when the member comes back from checkout and from a "Sync MP" button in the panel.
Trade-off. A notice with a bad signature is logged and still processed. A fake one can only make us reprocess our own data, which is idempotent, but it is a weaker check than rejecting it.
Static pages, permissions in every handler
Why. The admin panel stopped rendering on the server. Its pages are prerendered and the middleware asks for the admin role before they load. Public pages are cached for a month and the panel invalidates them when the owner saves a change. Since the pages no longer check the session, each API handler checks its own permissions.
Trade-off. Permissions are checked twice, in the middleware and in the handler, which is more code to keep in step. A change made outside the panel, straight in the database, doesn't show on the public site until the cache expires.



