01
the challenge
IAgency, a Colombian school teaching AI skills, ran its cohorts on three borrowed tools: the course lived on Hotmart, conversations with students on WhatsApp, and the one-to-one session calendar in a spreadsheet. Each worked on its own; together, they never spoke. Nobody could say, at any given moment, where a student stood: what she had watched, what she had asked, when her next session was.
The original brief fit in a sentence: a login screen with a link to the Hotmart-hosted course. It grew as each borrowed tool hit its limit: content that can't be released cohort by cohort, work conversations tangled up with personal messages, an appointment charged by hand.
Two constraints framed everything else, and they never moved: students come in from their phones, and the hosting is Hostinger shared, with no container, no build on the server, no node_modules in the package.
02
the solution
A private web application, designed and built from scratch, where content, messaging, the paid booking calendar and notifications all live in one place. A student sees her course, her live classes, the recordings of her one-to-one sessions, her goals and her certificate; the team runs all of it from the same portal, without ever touching the database.
The structural decision is a contract: a single OpenAPI file describes the API's 116 operations, and Orval generates two packages from it that nobody edits by hand: the Zod schemas the server uses to validate input and shape output, and the TanStack Query hooks the frontend consumes. API and screen can no longer drift apart in silence: the typecheck fails before the user does.
The second rule, written into the code, is that the portal belongs to the client: no integration may take it down. Google, the assistant, email, push and payments each switch off their own feature, say so on screen, and everything else carries on. That holds for all seven.
03
the result
In production, with real students. Hotmart, WhatsApp and the spreadsheet are gone: the course now releases cohort by cohort on a fixed date or "X days after your cohort starts", messaging carries attachments behind the session, and a coaching session is booked and paid for without leaving the page.
As of today: ~34,000 lines of TypeScript, 37 tables, 116 API operations, 123 Express handlers across 18 routers, and 107 commits in four months.
What I take from it sits mostly in the failures. A host suspending the mailbox for "suspicious activity", with no intruder: it was the portal itself, one announcement going out as thirty simultaneous sends. A Google brand verification that failed twice because the crawler was downloading an empty SPA behind a login. A `drizzle-kit push --force` that offered to empty the students table. Each one left a rule written down somewhere: in the code, in package.json, or in the control document that gets read before touching anything.