← About Gabriel C. Vasquez

CTCx Platforms · The complex system

The operational infrastructure of a company that trades across continents

Colombian Trading Company moves specialty coffee from Colombian producers to European buyers. Every step of that has to be traceable, priced, compliant with EU regulation, and visible to people who will never meet each other.

CTCx Platforms is the software that does it: nineteen public surfaces and three internal consoles, on one codebase, with the rules of the business enforced in code rather than in a handbook nobody reads. I am the co-founder, and I write it.

Version
V5.47
Public surfaces
19 + www
Internal consoles
3
TypeScript files
642
Lines of code
~102,200
Commits
678
Automated guardians
59
Languages
ES · EN · DE
  • Next.js 16 (App Router)
  • TypeScript
  • Supabase (Postgres · Auth · Storage · Edge Functions)
  • Row-level security
  • Vercel
  • Make · Notion · Google Workspace
  • Anthropic · Gemini · OpenAI

Nineteen sites, one codebase

Each surface of the business has its own subdomain: the producer portal, the buyer platform and its four programs, the specialist directory, each partner node, the internal consoles. They are not nineteen deployments. They are one Next.js application whose router reads the incoming Host header and serves the matching internal route.

The subdomain map used to live inside the router, until link previews needed the same map backwards, route to subdomain, to sign every shared card with the right absolute origin. Copying it would have created two truths: add a surface, the router serves it, and the link preview keeps pointing at the root domain. So the map moved to one file that both of them import.

Six of the nineteen, below. Different brands, different audiences, different languages. One repository, one deploy, one place to fix a bug. The three internal consoles are behind the staff login, so they are not pictured.

The CTC home page, with the live commodity ticker along the bottom
www.ctcexport.comThe house. Live coffee and currency rates along the bottom.
The Kaffetal Regal producer portal
kaffetal-regal.ctcexport.comThe producer portal, where a lot is registered and evaluated.
The Cherry Picked buying platform and its four programs
cherry-picked.ctcexport.comThe buying side, and the four programs it hands off to.
The directory of coffee specialists
directoriodelcafe.ctcexport.comThe people layer over the ecosystem. Everything else is lots.
The Coffee as a Service program page
caas.ctcexport.comCoffee as a Service. The old subdomain still routes here with a 308.
The public calculators and tools of the trade
herramientas.ctcexport.comThe tools, open to the trade, signed in with the network's one account.

Adding a surface is one line

One entry in the map, and both the routing and the Open Graph metadata already know about it. What does not derive itself is DNS, and that is written down as a manual step rather than pretended away.

Matching by segment boundary

The host comparison respects segment boundaries and excludes the static asset paths, because a partner node named cherry-picked and another named cherry-picked-green are two different companies, not a prefix.

One source of truth per rule, and a script that enforces it

The quality grade of a coffee lot decides its price, its badge on the public catalog, its eligibility for an auction and what the producer gets paid. It is defined in exactly one file: the bands, the rule that the score decides, the rule that a boundary belongs to the grade above, the two-decimal rounding, the seals.

Every consumer reads from there: the producer portal, the buyer catalog and its structured data, the auction engine, the quoting tools, the payment ceiling calculator. Nothing recomputes it, and a verification script fails the build if anything tries.

That pattern repeats for every cross-cutting rule: the network map, the identity matrix, the season calendar, the legal identity, the frozen vocabulary, the SEO gate, the integration spine. Fifty-nine scripts run as guardians. They do not check style, they check contracts.

Why it matters commercially

Two implementations of the same formula always diverge. When they do, the customer is looking at one number and the invoice says another, and no explanation makes that conversation go well.

Guardians as a build gate

A version bumped without a changelog entry, a console route without a guard, a file that is not UTF-8, a grade computed in a second place: each one has a script, and each script fails the build.

Identity: one account, many roles, eleven gates

One authentication account serves the whole network. On top of it sits a role matrix: producer and buyer are mutually exclusive, partners are their own role with their own account table and are never administrators, and the internal team lives in a separate table with per-console grants.

Every gate in the application asks the matrix, never its own version of the question. Two more guardians verify that every protected route has one and that account recovery works for each role.

Two cookies that must never merge

Public surfaces share a session cookie across subdomains. The consoles carry their own. They were merged once, and a token-refresh race between tabs on different subdomains took the producer login down in production. Now the separation is a written contract with the incident attached.

The database pattern of the house

Row-level security is enabled on every table with zero policies by default, which means nothing reads it except the server. Access is then opened deliberately, one narrow policy at a time, and only where a user genuinely has to read their own rows.

Public reads never go through a wide policy. They go through a narrow view that exposes exactly the columns the public is allowed to see. Update paths are restricted by triggers that limit which columns can change and which state transitions are legal, so an API call cannot walk a lot into a state the process does not allow.

Derived data is never persisted

Anything computable is computed on read. A stored derivative is a second truth waiting to drift from the first.

History is frozen in snapshots

What has to survive a rule change, an issued settlement, a published evaluation, is written into a snapshot at the moment it happens, and never recalculated backwards.

AI with a budget, and a ledger

Several parts of the platform use language models: content drafting, a producer advisor, a label scanner, improvement suggestions. Every one of those calls is recorded in a consumption ledger with its surface, its model, its tokens and its frozen cost.

The defaults are deliberate: a small model unless something demands otherwise, expensive steps opt-in with the price on the button, and a hard rule that missing a credential degrades deterministically and says so, rather than throwing an error page at a producer in a rural area.

The lesson that set the rule

An early prototype burned $61 in two days on unattended web searches. Today there are three layers: a monthly cap in the provider console, a hard stop in code before each paid call, and a reconciliation board that compares the ledger against the invoice.

Integrations that do not reach into other people's machines

The platform emits events named by domain, and an integration spine decides what to do with them. Nothing in a feature calls an automation platform directly, which is why replacing that automation platform is a change in one file rather than a search across the codebase.

The mirror to the company's knowledge base runs in one direction: the platform owns what it manages, the knowledge base reflects it with three properties that identify the record and link back. Only what has a named handler comes back the other way, and reconciliation is proposed by an agent and confirmed by a human.

Shipping, with a gate

The version number bumps in the same commit as the work, with a changelog entry and an entry in the architecture log, and the commit hash gets sealed into that entry on the next commit. Two guardian scripts enforce it.

The version badge is visible on every surface, so anyone can look at a screen and know whether they are seeing the latest deploy. That was the actual request behind the rule, and the rule is what makes it true.

What transfers to other applications

The domain is specific. The patterns are not. These are the pieces I would carry into a different product on the first day.

Multi-brand, multi-surface routing

Any company running several brands, regions or partner-facing portals gets the same benefit: one codebase, one deploy, one place to fix a bug, and a new surface added in a line rather than a project.

Contracts with guardians

The pattern generalizes to any domain with rules that pay: pricing tiers, eligibility, SLA classes, commission structures. Define once, consume everywhere, and let a script fail the build when someone reimplements it.

Role matrices instead of scattered checks

One identity model consulted by every gate is how a platform adds a role without auditing every route. It is also the only way to answer an auditor's question about who can see what without reading code.

AI that cannot surprise the invoice

Price before, record after, small model by default, expensive steps opt-in with the number visible. Any team putting a model into production needs this before they need a better prompt.