Skip to content
Juan Ignacio Portilla Kitroser
Case study - SISOC

An engineering system for a platform that never stops changing

SISOC is the back office of a national social-policy secretariat in Argentina. I lead the team that builds it, and I built the system that lets five people ship to it every week, while its requirements change every week.

Role
Technical Lead since Nov 2024 (developer since Jul 2024)
Team
4 developers + me, also as a developer and DevOps
Stack
Python, Django, DRF, MySQL, React, TypeScript, Docker, GitHub Actions
Users
~750 active government staff
Pull requests
1,750+ merged since Nov 2024, ~670 of them mine
Status
In production - migration in progress
Problem

Large changes, fast and cheap, every week

SISOC covers community kitchens, field surveys, admissions, childcare centers, food-assistance and job-training programs, and a 360° view of each citizen record. It is used by staff across provinces and areas: technicians, coordinators, legal, accounting, auditors.

The public agency behind it asks for large changes, delivered fast and cheap. Cheap here means time: we are asked to shorten delivery by spending less of it studying, validating and being careful with decisions. Commitments are often made before engineering has scoped them.

And the ground keeps moving. Requirements change from one week to the next: what was agreed this week can be different the next, often after the work has already started.

Context

What I started from

SISOC grew out of an existing citizen-management system. It was not built to scale: it barely did its job, and it was not stable. It had no tests, no linters, no lower environments, no pull-request culture, no controlled deploys and no documentation. Tasks lived in a WhatsApp group.

The infrastructure is limited and partly owned by another government team. SISOC also consumes external systems it does not control, like the national identity registry. And other systems consume SISOC: a ticketing system, a communications system and three field apps.

My responsibility

What I do, and what the team does

I coordinate priorities with the people who talk to stakeholders, and split the work across four developers and myself. I built the standards and the automations that keep them in place. I review and approve every pull request and every production deploy, and I maintain the infrastructure. I lead architecture decisions and write them down as ADRs.

The team builds features across all modules. They built the three field apps.

Solution

SISOC as a transit map

SISOC as a transit mapRÍO DE LA PLATADjango corekernel - identity - proxyNginxBrowserRENAPERnational identity registryField-management systemTicketing systemWeb pushSMTPSentryCiudadanosComedoresVPSLPASVATCeliaquíaCDICDFDispositivosEspacios ComunitariosDataCalleGestionarand more programsUIDF
U - User pathD - SISOC: core and programsI - External integrationsF - Team-built field apps
Technical decisions

The decisions that hold it together

  1. Make quality automatic, not negotiable

    Why - Under constant pressure, anything optional gets skipped. So the standard lives in CI: every pull request runs linters, tests with a 75% coverage gate, architecture checks and secret scanning before I review it.

    Trade-off

    Pull requests take longer to merge. We accept that in exchange for not discussing quality on every change.

  2. A weekly release train

    Why - Stakeholders need a fast pace. Every Thursday a frozen, tagged release goes from QA to staging to production, with my manual approval at the end.

    Trade-off

    Urgent changes wait for the train, and everyone respects the process we designed, unless a change justifies a hotfix.

  3. Modular monolith before services

    Why - We could not stop delivery for a rewrite. First we made the monolith modular: an import-linter ratchet with a baseline, and many small ADRs that each cut one dependency.

    Trade-off

    Slower than a big-bang split, but the system kept shipping every week.

  4. Per-domain backends in one monorepo

    Why - A shared kernel plus one backend per domain, each built as its own image from the commit SHA. The Django core proxies to them, and deploys and rollbacks happen per domain.

    Trade-off

    Isolation is per process, not per database yet. The proxy is a single point of failure. Both are written down as accepted debt.

  5. React frontends with types from the API

    In progress

    Why - New screens are React and TypeScript, with types generated from the OpenAPI schema, so frontend and backend can't drift silently.

    Trade-off

    Two UI stacks coexist: the frontend is being migrated while the Django templates stay in use.

  6. Docs as the source of truth, for people and agents

    Why - Specs and ADRs come first, and PR docs and the changelog are generated. AI coding agents work inside the same guardrails as people, and the ADRs record which decisions were mine and which were an agent's recommendation.

    Trade-off

    Writing first takes time on every change. It is what makes async work possible and agent output reviewable, so saving coding time never costs us control.

Result

The system the team works in today

No before-and-after numbers exist. What can be shown is the system itself: what changed, and what every change has to pass.

When I joinedTasks in a WhatsApp groupNowKanban board, Discord, weekly sync
When I joinedNo testsNowUnit and e2e tests, 75% coverage gate in CI
When I joinedNo linters or standardsNowLinters, written standards, module boundaries checked in CI
When I joinedDeploys straight to productionNowQA → staging → production, release every Thursday
When I joinedNo documentationNowSpecs and ADRs as the source of truth

Every change to SISOC goes through this

release: every Thursday
PRCI
    lintblack, pylint, djlint, encoding
    testssmoke + pytest, 75% coverage gate
    migrationsmigration check
    mysql compattests against real MySQL
    architecturemodule boundaries (import-linter)
    frontendReact fronts: types, tests
    secretssecret scanning (gitleaks)
    release sanitydeploy checks, schema, static files
    pr docsPR docs and changelog generated
reviewI review and approve every PR
Delivery
    freeze + tagrelease candidate tagged
    QAtest environment
    stagingpre-production
    productionmanual approval, by me
Story - Field report - production incident

Wednesday, 2 a.m. A migration fails halfway.

What happenedA Django migration that passed in lower environments failed on real production data. The schema was left half migrated and no longer compatible with the previous code.
What I didI had no backup access, and nobody from the infrastructure team was on call. I rolled the code back and rebuilt the affected data from a local export I had taken days before for testing. A few records stayed missing until the infrastructure team came online and I could restore them.
What changedI got full access to backups. We added a migration check to CI. The re-deploy shipped prepared for that exact failure.

What I took from it

Juan Ignacio Portilla Kitroser
Contact

Let's talk

Software engineer focused on backend: Python, Django and DRF, with React and Next.js on the frontend. I build and maintain production systems with complex business rules and many integrations, and I use AI tools every day without lowering the bar.

  • Available now - contractor or full-time
  • Fluent English
  • Full overlap with US East Coast hours (GMT-3)