Skip to content
Case study · 06 / 09← All work

Maderera Juan B. Justo

One platform for a lumber company: public site, store, counter, sawmill, invoicing and customer portals.

Role
Design and full-stack development
Stack
Next.js 16 (App Router), React 19, TypeScript, Postgres + Drizzle ORM, Better Auth, Tailwind CSS 4
Public site
Public siteEvery integration has a demo provider
(01) The problem

Maderera Juan B. Justo has sold lumber in Mar del Plata since 1981, from two branches. Sales, stock, cuts and invoices had to live in one place. The counter, the sawmill and the online store all touch the same stock. Retail customers and trade professionals each need their own view of prices and accounts.

My roleI designed and built the whole platform, from the database schema to every screen.
(02) ResultsCounted in the code
108pages in the app
100Postgres tables
721tests on money and cutting logic
(03) Architecture

Three decisions that shaped it.

  1. 01

    Every integration has a demo provider

    Why. ARCA, Mercado Pago, WhatsApp and email depend on credentials the client still had to obtain. Without them, each module runs a demo provider behind the same interface. The full flow works and the screen says so.

    Trade-off. Two implementations per integration to keep in sync. The demo can drift from the real API without anyone noticing.

  2. 02

    No RLS: one server-only data layer

    Why. The database credential never reaches the browser. Every read goes through the DAL and filters by the session user. Every write is a Server Action that checks the session, then validates with Zod.

    Trade-off. The DAL is the only line of defense. One loose query in a component would bypass it, and only code review catches that.

  3. 03

    The counter sells without internet

    Why. A dropped connection can't stop a sale at the counter. The counter keeps a local copy of the catalog and queues sales in IndexedDB. Each sale gets a provisional number per register and syncs when the connection returns.

    Trade-off. Local prices can be stale, and provisional numbers need reconciling. The shift can't close while sales are still pending.

(04) GalleryKeep scrolling →
Public site
Public site · The home page of the public site and store.
Cut to size
Cut to size · Customers lay out their pieces in millimeters and see how they fit on the board.
Counter
Counter · The point of sale, built for the keyboard and able to work offline.
Invoicing
Invoicing · Electronic invoices for ARCA, issued from the management panel.
(05) What I'd do next
01Add Playwright tests for checkout, offline sync at the counter and invoice issuing. Today there are no UI tests.
02Set up CI that runs lint, type checks, tests and the build on every push. The repo has no pipeline yet.
03Send server errors to an error tracker. Degraded reads only go to console.error, so nobody sees them in production.
Next project · 07 / 09
El Rey Jesús →Website and content admin for a church community
Case study · 06 / 09

Maderera Juan B. Justo

One platform for a lumber company: public site, store, counter, sawmill, invoicing and customer portals.

Role
Design and full-stack development
Stack
Next.js 16 (App Router), React 19, TypeScript, Postgres + Drizzle ORM, Better Auth, Tailwind CSS 4
Maderera Juan B. Justo
(01) The problem

Maderera Juan B. Justo has sold lumber in Mar del Plata since 1981, from two branches. Sales, stock, cuts and invoices had to live in one place. The counter, the sawmill and the online store all touch the same stock. Retail customers and trade professionals each need their own view of prices and accounts.

My roleI designed and built the whole platform, from the database schema to every screen.
(02) Results
108pages in the app
100Postgres tables
721tests on money and cutting logic
(03) Architecture

Three decisions that shaped it.

01

Every integration has a demo provider

Why. ARCA, Mercado Pago, WhatsApp and email depend on credentials the client still had to obtain. Without them, each module runs a demo provider behind the same interface. The full flow works and the screen says so.

Trade-off. Two implementations per integration to keep in sync. The demo can drift from the real API without anyone noticing.

02

No RLS: one server-only data layer

Why. The database credential never reaches the browser. Every read goes through the DAL and filters by the session user. Every write is a Server Action that checks the session, then validates with Zod.

Trade-off. The DAL is the only line of defense. One loose query in a component would bypass it, and only code review catches that.

03

The counter sells without internet

Why. A dropped connection can't stop a sale at the counter. The counter keeps a local copy of the catalog and queues sales in IndexedDB. Each sale gets a provisional number per register and syncs when the connection returns.

Trade-off. Local prices can be stale, and provisional numbers need reconciling. The shift can't close while sales are still pending.

(04) Gallery
Public site
Public site · The home page of the public site and store.
Cut to size
Cut to size · Customers lay out their pieces in millimeters and see how they fit on the board.
Counter
Counter · The point of sale, built for the keyboard and able to work offline.
Invoicing
Invoicing · Electronic invoices for ARCA, issued from the management panel.
(05) What I'd do next
01Add Playwright tests for checkout, offline sync at the counter and invoice issuing. Today there are no UI tests.
02Set up CI that runs lint, type checks, tests and the build on every push. The repo has no pipeline yet.
03Send server errors to an error tracker. Degraded reads only go to console.error, so nobody sees them in production.
Next projectEl Rey Jesús →
Nahuel SantillánEN