IvánDíazLab

Your warehouse keeps going. Even offline.

Scan, count and trace every movement. Even offline.

Concept illustration: The warehouse that tells the truth.
The warehouse that tells the truthConcept illustration

Vite · React 18 · TypeScript · vite-plugin-pwa · pdfjs-dist · BarcodeDetector · zxing-wasm · Web NFC

Built. And verified.

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
How it was built.Decisions, evidence and details

Project facts

Business
Hilos y Encanto, my haberdashery in Chiapas. Warehouse and marketplace fulfilment.
Timeline
July – August 2026
Status
v2.19 in production, used daily by the warehouse team
Role
Product, architecture, build and operational support
Scope
Shipment verification, stock by location, and quotes

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.

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.

Key decisions

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.

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.

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.

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.

«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.

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.

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.

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».

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.

  1. Someone scans a box

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

  2. The event is stored, never overwritten

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

  3. The balance is computed by replaying

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

  4. A fix can be demonstrated

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

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.

Results

  • Replaying 3,536 real events validated the phantom-shipment fix: 512 units shipped now deduct 512, with no box going negative.
  • v2.19 in production, used daily by the warehouse team and independent of connectivity.
  • Close to 30 in-house test suites, including the one preventing a stock count from behaving like a shipment.
  • The scanner chain recovers on its own when the native reader goes silent, with no need for the operator to restart anything.

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.

Your operation can take the next step.

Let’s talkMore projects