IvánDíazLab

One inventory. Every sales channel.

One central inventory. Every channel reads the same stock.

Concept illustration: Multichannel operations hub.
Multichannel operations hubConcept illustration

Node 22 · TypeScript · Express · PostgreSQL · zod · pino · sharp · PDFKit · Mercado Libre API

Built. And verified.

796
listings audited in the initial clean-up
1,742
movements in the append-only ledger
3.5 ms
90-day query, with no new indexes
143
automated tests (up from 115)
How it was built.Decisions, evidence and details

Project facts

Business
Hilos y Encanto, my haberdashery in Chiapas. Sells mainly on Mercado Libre.
Timeline
July – August 2026
Status
In production, rolled out listing by listing
Role
Architecture, build, deployment and day-to-day operation
Scope
6 phases: SKU clean-up, central inventory, ledger, channel write-back, storefront and analytics

Context

The business is Hilos y Encanto, my haberdashery in Chiapas, whose main sales channel is Mercado Libre, moving in the order of 500 units a month on that channel alone. Day-to-day operations are run by a small team that moves physical boxes, packs shipments and answers customers; nobody is sitting around waiting for a system to ask for attention.

When I came in, stock lived in three places at once: a mirror of the Mercado Libre catalogue, the physical warehouse count, and the point of sale. None of them talked to the others. Channel sales were not recorded in any system of their own, and the web shop did not even decrement stock when someone paid.

The brief was not «build a dashboard». It was to build the place where stock finally means one single thing — without switching off the business while it was being built.

The problem

Three isolated inventories, and none of the three was the right one.

With three sources out of sync, overselling is not an accident: it is a matter of time. Selling a piece that already left through another channel means cancelling, apologising and paying for it in account reputation, which is the most expensive asset a marketplace seller owns.

What made it worse is that history could not be reconstructed. With no record of movements, the question «why does this box say eight when there are five?» had no possible answer: all you could do was count again and trust the new number until the next discrepancy.

And then there was the risk of the cure itself. A system that starts writing stock levels into a channel with 796 live listings can do enormous damage in a single miscalculated run. Any solution had to be useful even while switched off.

Key decisions

These five are why the system could be switched on without anyone losing a night of sleep.

A 2xx proves nothing

Every write to Mercado Libre is read back from the channel and compared against what we meant to write. If they do not match, the movement is marked as failed, not as done.

A third-party API can accept a request, answer «fine» and apply something else — or nothing. If the system believes the response instead of the state, inventory drifts away from reality exactly where nobody is looking.

An activation ladder, listing by listing

Nothing is switched on at once. The mode goes from off to simulation for three days, then to reduce-only, and only at the end to full writes. The first listing to climb the ladder was hand-picked, and it was a cheap one.

If the system gets it wrong, I want it to get it wrong on the cheapest item in the catalogue with the team watching, not on 796 listings at once. Simulation shows exactly what it would have done before letting it do it.

Not writing is always safe

The process refuses to write when its data is stale: a catalogue older than 24 hours, or a warehouse cursor more than 15 minutes behind. It also aborts a run that would touch more than 20 listings or more than 150 units at a time.

When in doubt, the worst outcome of doing nothing is that the channel stays as it was. The worst outcome of writing with stale data is overselling or emptying healthy listings. The asymmetry is too big to argue about.

A red button with no redeploy

There is a global kill switch that stops every write instantly without touching code or shipping a release, plus intermediate modes that allow reducing stock but not increasing it.

When something goes wrong in production, the person who notices needs to be able to stop it in seconds. If switching off requires a deployment, in practice nothing gets switched off: people wait it out, and the damage grows.

Coverage with no sales is null, never zero

If a product has no sales velocity, days of coverage is reported as missing data. The CSV files the team consumes are written with a UTF-8 BOM so Excel does not mangle accents.

A zero reads as «critical, it runs out today» and sends someone to restock what nobody buys. Missing data reads as what it is: there is not enough information here to have an opinion.

How a sale travels

The central inventory is the single source of truth. Channels read from it, and the ledger records — never edits — every movement.

  1. Sale on Mercado Libre

    The order arrives through the channel API, with its listing and variation.

  2. Append-only movement

    A new movement is recorded. Nothing is edited or deleted: corrections are added.

  3. Stock recalculated

    The central inventory recomputes real availability for that SKU.

  4. Write to the channel

    If needed, the channel is adjusted — and the write is read back to verify it.

  5. The shop serves from the hub

    The web shop no longer keeps its own stock; it publishes the hub's availability.

The solution

The system was built in six phases, each useful on its own. First, cleaning up product keys: with no trustworthy SKU there is nothing to reconcile. Then a read-only central inventory that spent weeks just watching and comparing, with no authority to change anything.

The third phase brought the ledger: an append-only record of sales movements. A wrong movement is not edited, it is corrected by adding another, and stock is always the sum of history. Backfilling 90 days of history takes about two minutes, and querying that same window runs in 3.5 ms with no new indexes.

With trustworthy history in place the relationship could be inverted: the fourth phase writes back to the channel through a reconciler that verifies each write, and the fifth makes the web shop drop its own stock and serve the hub's availability instead. The last two open the way to a second channel and to the analytics layer.

It all runs on Node 22 with TypeScript over PostgreSQL, with schema validation at the boundaries, structured event logging, and CSV and PDF reports for people who work from a desk, not from a terminal.

Results

  • 796 listings audited in the initial clean-up; of 23 down listings, 22 could be reactivated.
  • A ledger with 1,742 recorded movements and 772 physical units reconciled against the real count.
  • Backfilling 90 days of history takes about two minutes; querying that window answers in 3.5 ms with no new indexes.
  • The test suite grew from 115 to 143 cases, covering the safety brakes and the reconciler.
  • 1,915 units sold over 90 days are now traced movement by movement, including 74 returns (≈ 4%).

Sales figures are published in units only. The business's monetary amounts are not published, by the project's own rule.

What I learned

That the hard part of channel integration is not the API: it is deciding who is right when two systems disagree. That agreement has to be made before the first line of code, because afterwards the code makes it on its own, and almost always badly.

That a system that can be switched off quickly gets switched on sooner. The activation ladder looked like bureaucracy and turned out to be what made starting possible: nobody had to bet the account on the software being right first time.

And that a figure's honesty is worth more than its availability. Reporting absence instead of a comfortable zero is awkward the first time someone sees it on screen, and it is what stops decisions being made on invented numbers.

Selling on more than one channel and your stock never adds up? This is exactly the problem I solve.

Let's talk

Your operation can take the next step.

Let’s talkMore projects