Skip to content
arbelxez

8 min read

What building software for a city hall teaches you

Tramo is in production with the municipality of Marinilla, in Antioquia. Filings, site plans, construction resolutions, public-space intervention, citizen consultation, and the archive. Building it changed what I understand a hard requirement to be.

01

The deadline is set by law, not by the product

In a consumer product a date is a goal: miss it and someone is annoyed. In an administrative procedure a date is a legal fact. Filing is not storing a record with a timestamp; it starts a clock somebody else wrote, with consequences for the citizen and for the institution. Business days, holidays, and the exact hour stop being edge cases in a date helper and become the center of the domain.

That changes how you ship. There is no launch-and-iterate when an open case file is at stake: a procedure that began under one set of rules has to be able to finish under those rules. Migrations preserve history instead of correcting it, and a correction is recorded as a correction, with its date and its author. The system can never be left in a state where reconstructing what it said yesterday is impossible.

The practical consequence is that traceability is not a feature you add later, in the audit tab. It is a property of the model from day one and it constrains everything else: what can be deleted, what can be edited, what can only be voided while leaving a trace. Designing that at the end would have meant rewriting the whole system, and that is exactly the lesson a city hall teaches you early.

02

The procedure was written long before I arrived

Tramo has 138 domain entities spread across roughly 40 modules. That number is not an architect's ambition: it is a transcription. The domain existed beforehand, written into regulation, into forms, into resolutions, and into the way the office already worked. Modeling it was far more reading and asking than inventing. When software runs ahead and simplifies a procedure it did not understand, it does not improve it. It leaves the office unable to issue the document it owes.

So the entities are named the way the regulation names them, not the way they would read better in English. It sounds like a minor detail and it is not: when the model speaks the vocabulary of the office, reviews with public servants stop being a translation exercise and become actual reviews. They point at the error where it lives, and I get to discuss the procedure instead of defending my naming.

The foundation is deliberately boring: NestJS on Fastify, TypeORM, and a Next.js frontend. Boring because the value sits in the domain, and because this software has to keep working years from now, maintained by people who were not there when it was written. Inside an institution, the interesting part of the system should never be the infrastructure.

03

The public servant is the real user; the citizen is the judge

The person who actually lives inside Tramo is a public servant opening the same screens every working day of the year. That inverts almost every usual design priority. What matters is not the first impression but the frequent path made short, recovery from a mistake halfway through a case file, and having the information they glance at always in the same place. Nobody is discovering this tool. They are operating it.

At the same time there is less room to explore than in any consumer product. A button is not an experiment when what it signs has legal effect, so the interface has to say plainly what is about to happen, who is recorded as the author, and what can be undone. User anxiety here is not answered with animation. It is answered by making the system predictable and keeping responsibility visible.

And on the other side sits the citizen consultation: the same case file, seen from outside by somebody who does not work there. That window is what makes all of the above verifiable. A badly entered field stops being an internal inconsistency and becomes a wrong answer to a neighbor asking about their own block. Knowing that view exists disciplines the design better than any requirements document.

04

Why AI has to move slowly inside a legal procedure

I build agents every day, and here I move slower on purpose. Inside the case file there is an assistant that uses RAG and a vector database to help find what is already written in that file. It is experimental, still in development, and that is how it is always described: it is not a feature delivered to citizens and I will not present it as one. In a public institution that distinction is not commercial caution. It is basic honesty.

The reasons are concrete. An administrative decision has to be attributable to a person who answers for it, and no model can occupy that seat. A generated answer that sounds right and is wrong costs far more than an answer that never arrives, because nobody double-checks well-written prose. And retrieval over regulatory documents has to be evaluated before anyone leans on it to decide, not after.

So the right framing is help find, never decide. Search inside the case file, show where each thing came from, and let the public servant verify. That ceiling is a deliberate design decision, not a technical limitation waiting to be lifted; raising it would require a serious evaluation of answer quality and a conversation with the institution, in that order.

The lesson stuck, and I carry it into the other projects: the ambition of an agent has to match the cost of being wrong. In a WhatsApp order, being wrong is a wrong dish and a person fixing it in two minutes. In an administrative procedure, being wrong can touch somebody's rights. The engineering discipline is the same in both cases. The speed at which you release it is not.