Skip to content
← Projects Professional Project

Sentric Inventory

Internal asset-tracking system for Sentric's camera fleet (~150 units). Replaces a physical logbook with an append-only audit trail, intrinsic asset identity, and a DataMatrix-scanner PWA for field technicians. NestJS + Next.js monorepo with a pure domain layer and property-based tests. Under active development.

01

Context

Sentric operates a fleet of ~150 CCTV cameras deployed across multiple clients. Control was kept in a physical notebook — no traceability, no audit trail, no way to know in real time where each camera was. The outcome: lost cameras, idle assets, and reactive logistics that cost time and money. The system addresses this with per-camera intrinsic identity (unique DataMatrix code printed on a physical label), an append-only audit trail, and a PWA so field technicians can check cameras in and out from their phone — no native app required.

02

Role & Team

Role Full-stack Developer (solo)
Timeline under development
Team Solo
03

Options considered

Destructive database updates Rejected

Simple to implement, but destroys history — impossible to audit past movements

Append-only trail with immutable events Chosen

Every movement is a permanent event; current state is projected from the history — full audit trail as a structural consequence, not a separate feature

Asset identity tied to the client Rejected

Common modelling, but breaks when a camera moves to a new client — the asset would lose its history

Intrinsic asset identity (client-decoupled) Chosen

The camera has a permanent unique identity; client bindings are separate relationships — history is preserved across any movement

Native app (React Native) Rejected

Smoother UX, but requires app-store distribution and a separate build — unnecessary friction for ~3 internal technicians

Installable PWA Chosen

Zero app-store install, works offline with a service worker, camera-based scanner in the browser — instant rollout

04

Solution

Architecture

pnpm monorepo — apps/api (NestJS 10) + apps/web (Next.js 15 + PWA). packages/domain is PURE (zero I/O): camera state machine, invariants, and commands. PostgreSQL 16 + Drizzle ORM for append-only persistence.

Technical highlights

  • DataMatrix generation via bwip-js and scanning via @undecaf/zbar-wasm in the field PWA
  • Append-only audit trail — every event is immutable; current state projected by folding over history
  • packages/domain with zero I/O: testable with property-based tests (fast-check) using no mocks
  • OpenTelemetry exporting to obs.timelapseobras.com.br — observability built-in from day one
  • Luhn check digit on asset codes — field typo detection before reaching the database

API / Backend: NestJS 10 (REST) — Node 22 · PostgreSQL 16 · Drizzle ORM

05

Results

Metric Value
Target fleet coverage ~150 cameras / 3 operators
Audit trail Append-only — zero data destruction
Field technician rollout PWA — zero app-store install
Domain layer testability 100% I/O-free — property-based via fast-check
Status Under development (F0 — monorepo)
06

Learnings

The problem a physical logbook cannot solve

At Sentric, when a camera left for installation, the technician made a note in a logbook. When it came back, another note. When it went in for repair, another note — if they remembered. The predictable result: cameras with no known whereabouts, technicians calling each other to find out “where is camera X”, assets sitting idle because nobody knew whether the equipment was available or in the field.

The fundamental problem was not lack of discipline — it was the absence of a system that made recording movements inevitable and immediate.

The most important design decision: append-only

Most inventory systems work with a central record that is destructively updated: “camera X is now at client Y.” That resolves the current query, but destroys the history. If a camera goes missing, you have no way to know who last had it.

The Sentric Inventory works differently: every movement is an immutable event. When a camera leaves for installation, a SAIDA_CAMPO event is created. When it returns, a RETORNO_BASE event. The current state of the camera is calculated from the event sequence — not stored directly. This means a complete audit trail with zero additional effort: it is a structural consequence of the design, not a separate feature.

DataMatrix in the field

The physical label is what anchors digital identity to the physical object. We chose DataMatrix over QR Code for three reasons:

  1. Density: stores more data in the same physical space
  2. Robustness: reliable reading even with up to 30% of the label damaged
  3. Oblique reading: works at angles that would cause a QR Code to fail

The code printed on the label includes a Luhn check digit — the same algorithm used in credit cards — to detect typing errors before they reach the database.

packages/domain — the stateless heart

The decision to completely isolate the domain in a package with no I/O dependency has an immediate practical effect: the camera state machine can be tested with fast-check generating thousands of random event sequences, verifying invariants such as “a camera can never be in two places simultaneously” or “you cannot check out a camera that is not at the base.”

These tests need no database, no mocks, no server. They are pure functions — and fast-check finds edge cases that no hand-written test would ever uncover.