analytics
Portafolio de Iván
Projects chevron_right The warehouse that tells the truth

Production system · v2.19

wifi_off Offline-first history Event sourcing qr_code_scanner Scanner + NFC science ~30 test suites

The warehouse that tells the truth: an offline-first PWA with event sourcing

In a warehouse there is no stable wifi and no time to wait for a screen to load. This PWA guides shipment scanning, counts stock by location, and stores every action as an event that can be replayed.

Key numbers

3,536

real events replayed to validate a fix

512 = 512

units shipped and deducted after the fix

v2.19

version in production

~30

in-house test suites

Tech stack

ViteReact 18TypeScriptvite-plugin-pwapdfjs-distBarcodeDetectorzxing-wasmWeb NFC
schema

Why it records events instead of balances

The app does not store «how many pieces there are». It stores everything that happened, in order, and computes the balance by replaying it. Changing the rule and replaying is what proved the fix worked.

touch_app 1 · Action

Someone scans a box

Every operational action becomes an event with its time, location and quantity.

save 2 · Record

The event is stored, never overwritten

Versioned local storage that survives losing the network and closing the browser.

replay 3 · Replay

The balance is computed by replaying

Current state is the deterministic result of replaying the whole history.

fact_check 4 · Proof

A fix can be demonstrated

Change the rule, replay the real history, and compare the two outcomes.

store

Context

The same business behind the operations hub runs a physical warehouse with boxes, locations and a constant flow of shipments into the marketplace's fulfilment programme. Each shipment arrives with a PDF of instructions, requires labels, and must be verified piece by piece before the box leaves.

The app — the team calls it «Central» — does three things: it prepares and verifies those shipments, tracks physical stock by location, and builds quotes. It installs on a phone and works offline, because in a warehouse signal is a courtesy, not a guarantee.

The detail that matters: whoever uses it has their hands full. Any friction — a badly placed button, a step that requires reading — turns into miscounted boxes.

report

The problem

512 units left the warehouse. Inventory deducted 408.

One hundred and four pieces had physically gone and the system still counted them as available. That is not a rounding error: it is exactly the kind of discrepancy that leads to selling what no longer exists.

Finding the cause was one thing; proving the fix worked was another. With a system that stores balances, the only available proof is «it adds up now», which says nothing about the old data. And recounting the entire warehouse to check was not a realistic option.

There was a second, quieter problem: the barcode reader chain. On some Android phones the browser's native reader stops working without warning — it does not fail, it simply returns zero codes forever — and the operator is left pointing the camera at a label that will never be read.

rule

Key decisions

01 Store events, not balances

The app records every action as an event and derives state by replaying them in order. Local storage is versioned so history can be migrated without losing it.

psychology Because it allows exactly what was needed: fix the rule, replay 3,536 real events, and confirm that 512 units shipped now deduct 512, with no box going negative. A balance-based record could not have proved anything.

02 A count is an observation, not a movement

The link between stock counting and warehouse movements is wired at screen level, held in place by a dedicated test, instead of living in the scanner's shared layer.

psychology If it lived in the shared layer, every stock count would behave like goods leaving and would silently empty boxes. Counting what is there and shipping what leaves are two different facts, and the system has to tell them apart even though the physical gesture looks the same.

03 «Unlabelled» is a quantity, not a yes or no

Unlabelled pieces are modelled as a number of loose pieces per location, not as a checkbox on the product.

psychology Because the same product can have labelled pieces in one box and unlabelled ones in another at the same time. A boolean forces you to lie about one of the two locations, and that lie comes back weeks later as an unexplained discrepancy.

04 Detect the scanner's silence

The reading chain goes from the browser's native reader to WebAssembly and then to a third implementation. The system measures silence — more than 40 attempts and 8 seconds without a single read — and switches to the next reader on its own.

psychology The failure raises no error: the native reader simply stops finding codes. The only available signal is the absence of results, so it has to be treated as a symptom rather than waiting for someone to report that «the camera isn't working».

construction

The solution

It is an installable PWA built with Vite, React and TypeScript. The shipment's PDF of instructions is parsed on the phone itself, the label file the process requires is generated, and the screen guides scanning piece by piece, showing what is missing and what is in excess.

Stock is tracked by location, with barcode reading and, on Android, NFC tags to identify boxes with a tap. Everything works with no network: events are stored locally and the interface never waits on a server.

Around it there are close to thirty in-house test suites, including the one that pins down that a count is not a movement. That test exists because the opposite mistake nearly happened, and it is the kind of safety net that stops a future refactor from reintroducing it.

emoji_events

Results

lightbulb

What I learned

That event sourcing is not an elegant architectural preference: it is what turns «I think it's fixed now» into a reproducible proof over real data. The day it was needed, it was the difference between demonstrating and promising.

That the most expensive bugs are not the ones that fail loudly, but the ones that do exactly what they were asked to do in the wrong layer. A count treated as a shipment throws no exception: it just leaves boxes empty.

And that in operational software the data model has to survive physical reality. If in the warehouse a piece can be labelled here and loose over there, the model has to allow it, even if a checkbox would be easier to code.