luis pillaga
Portrait of Luis PillagaHi, I’m Luis PillagaBackend · Cloud · Full stack

Senior Software Engineer building backend systems, cloud infrastructure and web apps for U.S. teams.

Six years working remotely on backend and cloud teams for U.S. companies. I work across the stack: Python, Java and TypeScript on the backend, React, Angular and Vue on the front end, and AWS, Terraform and Kubernetes underneath. Today, through FullStack Labs, I build backend services for Syngenta’s geospatial field-trial platform with FastAPI, PostgreSQL/PostGIS and AWS. On my own projects I have built products for companies and institutions in Ecuador. I work from Biblián, Ecuador (UTC−5), inside the U.S. workday.

The work I can take on, each with a place I have already done it.

python · apis

Backend services and APIs

I design and build APIs in Python with FastAPI and Django: the data model, the endpoints, background consumers, and tests that run against a real database.

ExampleAt Syngenta I work on the backend of a geospatial platform for field trials, and I led the review that brought its redesign up to parity with the production system, with fixes across 6 repositories.

postgresql · performance

Fast databases that stay safe to change

I read query plans, fix indexes and statistics, and change schemas on large tables without locking users out.

ExampleTook a production endpoint from 320 s to 0.4 s, and I ship schema changes on a PostgreSQL/PostGIS database with 20M+ rows.

data · integrations

Moving data between systems without losing any

Event-driven integrations with SNS/SQS and EventBridge, change data capture with Debezium, and migrations off legacy systems.

ExampleOwned the pipeline that moves ~10M records per environment from a legacy platform to its redesign.

cloud · devops

Cloud infrastructure and the pipelines that deploy it

AWS defined in Terraform, Kubernetes, and CI/CD pipelines with automated tests and security checks.

ExampleBuilt a U.S. law firm’s AWS infrastructure from scratch in Terraform (30% faster provisioning and deploys), and moved 106 services of a university to Kubernetes.

applied ai

AI in real products: LLM APIs and on-device models

I integrate the OpenAI API into products (chat, document generation, image analysis, search over a company’s documents) and train compact computer-vision models that run on the phone, with no server. I hold a master’s in applied AI from UTPL, and day to day I code with Claude Code and check every change with end-to-end tests.

ExampleLegal document generation and a chatbot that save a legal team about 6 paralegal hours a week. For my master’s thesis, YOLOv8 models compressed to INT8 TFLite that read water meters in real time inside an Android app (0.995 mAP50 on validation).

product · end to end

Whole products, from the first table to production

I have designed and built multi-tenant SaaS for companies in Ecuador, published under the Tenpisoft name, with Python backends and React, Angular or Vue front ends.

ExampleA multi-tenant SaaS for community water boards, an e-invoicing API for Ecuador’s tax authority (SRI), a WhatsApp bot for municipal parking, and an Android kiosk that reads ID cards to take quorum at assemblies.

Six years of backend and cloud work, mostly for U.S. companies.

FullStack Labs

Remote

06/2025 – Present

Nearshore software consultancy that provides engineering teams to U.S. enterprises.

Senior Software EngineerSyngenta

  • Build backend services for Syngenta’s geospatial platform for field trials in Python and FastAPI, on PostgreSQL/PostGIS and AWS.
  • Led the parity review between the production platform and its microservices redesign, and shipped the fixes across 6 repositories.
  • Cut a production endpoint from 320 s to 0.4 s, and ship schema changes on a 20M+ row database without locking users out.
  • Owned the migration of ~10M records per environment off the legacy platform, and the event integrations (SNS/SQS, EventBridge, Debezium CDC) with the systems around it.
  • Handle production incidents from the first alert in Datadog or Loki down to the query plan, and write up the root cause for the team.
  • Python
  • FastAPI
  • PostgreSQL
  • PostGIS
  • AWS
  • Terraform
  • Debezium
  • Iceberg

Devsu

Remote

09/2021 – 05/2025

Software development outsourcing company connecting Latin American engineers with U.S. clients.

Full Stack Developer & DevOps EngineerZinda Law Group

  • Improved core application performance by 20% by optimizing PostgreSQL queries and refactoring Django, Vue.js and React code.
  • Reduced infrastructure provisioning and deployment time by 30% by building the AWS infrastructure from scratch as code with Terraform.
  • Saved the legal team about 6 paralegal hours per week by building an OpenAI-powered chatbot and automated legal document generation.
  • Accelerated releases by building CI/CD pipelines with automated tests and security checks.
  • Set up new Django and React projects, defining their architecture, stack and development workflow.
  • PostgreSQL
  • Django
  • Vue.js
  • React
  • AWS
  • Terraform
  • OpenAI

Full Stack DeveloperBanco Pichincha

  • Delivered loan origination and evaluation features for SIMA, the bank’s lending platform for Ecuador’s micro-business sector, used by credit advisors to approve or deny loans.
  • Coordinated backend and frontend work to deliver features on schedule for each release.

Universidad de las Fuerzas Armadas – ESPE

Latacunga, Ecuador

11/2020 – 09/2021

DevOps Engineer

  • Migrated 106 institutional services to a central Kubernetes cluster, improving scalability and resource usage.
  • Replaced manual deployments to TEST and PROD with CI/CD pipelines, and introduced GitOps continuous deployment with Jenkins and ArgoCD.
  • Kubernetes
  • Jenkins
  • ArgoCD

Full Stack Engineer

  • Led development of a digital medical records system serving 4,459 patients and ~500 clinical interactions per month, built with a Spring Boot REST API and an Angular single-page app.
  • Spring Boot
  • Angular

PlanERP

Cuenca, Ecuador

02/2018 – 04/2018

Back End Developer (Internship)

  • Built a public-employee performance evaluation module for a governmental ERP, using the Python-based Tryton framework.
  • Python
  • Tryton

Products I have built for companies and institutions in Ecuador under the Tenpisoft name, plus my master’s work in applied AI.

e-invoicing api · SRI

Electronic invoicing for Ecuador’s tax authority (SRI), built as an API other products plug into: they send an invoice and get it back signed, authorized and rendered as a PDF.

Two FastAPI services on AWS Lambda plus a four-step Step Functions pipeline (XML signing, SRI submission, authorization polling, PDF). Tenants are isolated with PostgreSQL row-level security, and the fast suite runs about 2,970 tests.

  • Python
  • FastAPI
  • AWS Lambda
  • PostgreSQL
  • Terraform
  • React

water boards

Multi-tenant SaaS for Ecuador’s rural community water boards: customers, meter readings, billing and administration.

Django REST API with Celery and a React + TypeScript front end, infrastructure on two AWS accounts with Terragrunt, and nine CI pipelines including end-to-end and security checks. About 1,830 backend tests.

  • Python
  • Django
  • PostgreSQL
  • Celery
  • React
  • Terraform

assemblies

Member registry, attendance and quorum for the assemblies of water boards, cooperatives, parish councils and condominiums.

FastAPI modular monolith with row-level security on PostgreSQL and Terraform on AWS. An offline-first Android kiosk reads the national ID card with on-device OCR (ML Kit) and syncs when the network comes back.

  • FastAPI
  • PostgreSQL
  • React
  • Kotlin
  • Android
  • Cloudflare

macOS meeting copilot

Desktop copilot for meetings held in English: live transcription and translation, suggested questions to join the conversation, and a searchable library of past meetings with summaries.

A Swift sidecar captures system audio and transcribes it on the Neural Engine with WhisperKit, so raw audio never leaves the Mac. A Rust/Tauri core handles translation, the LLM copilot and search over SQLite with vector embeddings.

  • Rust
  • Tauri
  • Swift
  • React
  • TypeScript
  • SQLite

master’s thesis · on-device ai

Android app that reads water meters from the camera, for the people who take monthly readings at rural water boards.

YOLOv8 detectors for the meter window and its digits (0.995 and 0.989 mAP50 on validation), quantized to INT8 TFLite and run on the phone. The app works offline, with local storage and background sync.

  • YOLOv8
  • TensorFlow Lite
  • Kotlin
  • Jetpack Compose
  • Android
  • Python

messaging infrastructure

Notification API shared by the other products, starting with WhatsApp through Meta’s Cloud API.

Send, management and webhook services as FastAPI functions on AWS Lambda, with idempotency keys and forced row-level security in PostgreSQL. The channel sits behind an interface, so SMS or email can be added without touching the core.

  • Python
  • FastAPI
  • WhatsApp
  • AWS Lambda
  • PostgreSQL
  • Terraform

municipal parking

Paid on-street parking for small Ecuadorian towns: drivers pay from a WhatsApp bot with a prepaid wallet, officers check plates from a web app, and the town runs it from a dashboard.

FastAPI on AWS Lambda with async SQLAlchemy and PostgreSQL/PostGIS under row-level security. Tariffs and rules are stored as per-town data instead of code.

  • Python
  • FastAPI
  • PostgreSQL
  • PostGIS
  • WhatsApp
  • React

commercial erp

ERP for a trading business: sales with discounts and tax withholdings, payments, shipments with vehicles and drivers, products and accounting.

Django REST API with Celery Beat and Redis, an importer for legacy Google Sheets data, a React admin with end-to-end tests, and Terraform for the AWS environments.

  • Django
  • PostgreSQL
  • Redis
  • Celery
  • React
  • Terraform

master’s project · computer vision

Real-time face identification from a webcam or video, built to grow into an attendance system.

Modular Python pipeline: InsightFace detection with 5-point alignment, 512-dimension ArcFace embeddings and FAISS nearest-neighbor matching, with swappable backends and unit tests.

  • Python
  • OpenCV
  • ONNX
  • pytest

master’s project · iot

Irrigation for cattle pastures in rural Azuay that waters only when needed, to save water in the dry season.

An ESP32 sends temperature and soil moisture over MQTT to Node-RED, which adds the rain forecast and asks a Random Forest model served with Flask whether to water. Readings land in InfluxDB and Grafana.

  • MQTT
  • Node-RED
  • scikit-learn
  • Flask
  • InfluxDB
  • Grafana
e-invoicing api · SRIIn production, internal use

Comprobantes

A multi-tenant API that issues, signs and submits Ecuador's electronic invoices to the tax authority (SRI).

tests in the fast suite, no external resources
~2,971
document types: invoice, credit note, debit note, withholding, shipping guide
5
Lambdas in the Step Functions pipeline
4

The problem

In Ecuador every invoice must be signed with XAdES-BES, sent to the SRI over SOAP and authorized before it is valid. Each business app that issues invoices would otherwise have to rebuild this. Comprobantes is the shared plumbing, so other products can issue invoices with one API call.

Screens and diagrams

How it works

  1. 01Client productPOST /invoices + API key
  2. 02Core APIValidate, tax, sequence, save, 202
  3. 03Step FunctionsSign XML (XAdES-BES)
  4. 04SRI (SOAP)Send, then poll authorization
  5. 05RIDE PDF + emailStored in S3, sent by SES

A client calls POST /api/v1/facturas with an API key. The core API validates the request, computes taxes with Decimal arithmetic, assigns an atomic sequential number, writes the invoice in one transaction and starts a Step Functions execution. It answers 202 right away. The client then polls the document state.

The state machine runs four Lambdas: sign the XML, send it to the SRI, poll for authorization (up to 60 attempts, 30 seconds apart, with longer waits when the SRI is down), and generate the RIDE PDF. Each step has retries and a catch that sends failures to a dead-letter queue.

Data lives in PostgreSQL with Row-Level Security. A separate management API and a React dashboard handle tenants, companies, users and API keys. The core API contract is generated from the code and feeds a Python SDK and a CLI.

Engineering highlights

  • Tenant isolation in the database

    Row-Level Security policies filter every query by tenant. The apps connect as a non-superuser role, because superusers bypass RLS. The tenant is set per transaction with SET LOCAL.

  • Hexagonal monorepo

    Domain packages hold entities and use cases. Adapters implement ports for SQLAlchemy, S3, SRI SOAP, XAdES and ReportLab. Dependency rules are written down and checked.

  • Resilient async pipeline

    Step Functions with retry and catch on each state, a DLQ with 14-day retention, idempotency keys on issuance and a scheduled SRI health check.

  • Contracts generated, not hand-written

    OpenAPI specs and the Python SDK are generated from the apps. A test in the fast suite fails in CI if they drift.

  • Infrastructure as code and releases

    Terraform and Terragrunt modules for dev and prod, GitHub Actions deploys through OIDC, and versioning with release-please from conventional commits.

My role

I designed and built it: the API, the signing and submission pipeline, the infrastructure and the release process.

Stack

  • Python
  • FastAPI
  • AWS Lambda
  • PostgreSQL
  • Terraform
  • React
water boards

TenpiYaku

Multi-tenant SaaS for rural drinking water boards: meter readings, billing, collections and accounting in one system.

backend tests (July 2026 audit)
1,800+
history tables covered by row-level security
23
functional modules documented
14
UI languages: Spanish and Kichwa
2

The problem

Community-run water boards in Ecuador bill their members by meter consumption, collect payments, charge fines and organize volunteer work days. Many of them do this with spreadsheets and paper. They need one system that handles each board's data separately and in Spanish or Kichwa.

Screens and diagrams

How it works

  1. 01Meter readingCollected per period along a route
  2. 02ValidationValidating the reading triggers bill generation
  3. 03BillProgressive tariff tiers, plus late fees from a daily job
  4. 04PaymentAllocated against bills and fines
  5. 05Ledger and invoiceDouble-entry ledger, optional electronic invoice

Each water board is a tenant. A meter reading is validated, and validation generates the bill using progressive tariff tiers. Payments are allocated against bills and fines, and a double-entry ledger records the money. A daily Celery job evaluates late fees, and invoicing to the tax authority can run in off, draft or auto mode per board.

The backend is Django and DRF with a services and selectors layout, thin views and an English-only codebase. The React frontend is organized by feature and the UI runs in Spanish and Kichwa. Authentication is AWS Cognito, while roles and permissions live only in the Django database. AWS resources are managed with Terragrunt and Terraform.

Engineering highlights

  • Tenant isolation in the database

    Beyond filtering by board in application code, Postgres row-level security policies reject rows from another board, fail closed when no tenant is set, and also cover the 23 history tables.

  • Authentication and authorization split

    Cognito answers who the user is. Roles, permissions and board membership stay in Django, so a role change takes effect immediately and needs no sync with Cognito.

  • Provider-agnostic electronic invoicing

    Invoicing uses ports and adapters with the provider chosen per board, so changing provider does not mean rewriting the domain module.

  • Written business rules and audits

    Decision records cover rules such as the solvency check before any procedure. A full-app audit produced pytest scenarios that deliberately fail until each finding is fixed.

  • Least-privilege infrastructure

    IAM policies avoid wildcards, the backups prefix cannot be deleted by the app, and CI workflows run on stacked pull requests so they cannot reach merge without tests.

My role

I built it end to end: backend, frontend and infrastructure.

Stack

  • Python
  • Django
  • PostgreSQL
  • Celery
  • React
  • Terraform
assemblies

Kworum

Member registry and assembly attendance with exact quorum counting, plus an offline-first Android kiosk that reads the ID card.

API tests
1,000+
domain tables plus one import receipt table, all with forced RLS
18
maximum age of an offline entry the API accepts
24 h

The problem

Water boards, cooperatives and similar organizations take attendance on paper, count marks by hand and rebuild afterwards who was present. Venues often have unreliable internet, and many members have no smartphone.

Screens and diagrams

How it works

  1. 01RosterMembers, permanent representations, CSV import
  2. 02Open registrationQuorum policy is frozen for that assembly
  3. 03Check-inKiosk (ID number or card camera, offline queue) or attendance desk
  4. 04QuorumLive board; installing requires reaching the threshold
  5. 05CloseImmutable snapshots keep the evidence

Staff keep a member roster and permanent representation authorizations, then open an assembly. Opening registration freezes that assembly's quorum policy, so later configuration changes do not alter it. Attendance events are immutable, corrections are recorded with a reason, and snapshots keep the quorum evidence at installation and at close.

At the door, a paired Android tablet runs in kiosk mode. The member types the ID number or shows the card to a USB camera. The app reads the number on-device with ML Kit text recognition and checks the check digit, the card title, one face of believable size and the card outline over three consecutive frames. The roster is stored on the tablet and entries wait in a local queue until the network returns. Entries older than 24 hours are rejected for review by staff. A second mode, the attendance desk, lets the board's secretary sign in with their own account.

Engineering highlights

  • Row-level security on every table

    Every table has forced Postgres RLS driven by transaction-local settings, and the runtime role cannot own tables or bypass RLS. The readiness check rejects a privileged database role.

  • Offline-first kiosk

    The tablet keeps the roster and an outbox of entries with Room and WorkManager, and each entry is uploaded once when the network returns.

  • On-device ID card reading

    Text recognition and face counting run locally. Faces are only counted, never stored or compared, and the camera image is never shown on screen.

  • Architecture enforced by tests

    The Android app has three modules with dependencies pointing inward. Tests scan imports and fail the build if a rule is broken, and another test guards against hard-coded colors and sizes.

My role

I built it end to end: API, web app, Android kiosk and infrastructure.

Stack

  • FastAPI
  • PostgreSQL
  • React
  • Kotlin
  • Android
  • Cloudflare
macOS meeting copilot

Grasp

A macOS meeting copilot that transcribes and translates English meetings live, with all audio processed on the Mac.

p95 latency, compact 632 MB Whisper model, Apple M2 Max (gate: under 500 ms; 98 samples)
452.6 ms
p50 latency, same run
415.3 ms

The problem

Following a fast English meeting as a non-native speaker is tiring, and the details are gone once the call ends. Cloud transcription tools send the raw audio to a third party.

Screens and diagrams

How it works

  1. 01Audio captureCore Audio Process Tap for system audio, plus microphone
  2. 02Local transcriptionWhisperKit on the Neural Engine, SpeakerKit for speakers
  3. 03Rust coreLive translation and copilot over text events
  4. 04Live UISubtitle overlay, suggested questions, checkpoints
  5. 05Meeting librarySQLite FTS5 + sqlite-vec, distilled memory

A Swift sidecar owns all audio capture and speech-to-text. It taps system audio with Core Audio Process Tap, records the microphone, and runs WhisperKit on the Neural Engine, with SpeakerKit for speaker labels. The raw audio never leaves that process.

A Rust and Tauri v2 core consumes the transcript events, runs live translation and the copilot, and stores a searchable library of meetings in SQLite (FTS5 for keywords, sqlite-vec for embeddings). A React 19 and TypeScript front end shows the main window, a floating subtitle overlay and a tray icon.

During a call the app suggests questions to ask in English and can create a checkpoint on demand: a short summary plus a Mermaid diagram of what was just said. After the call, a distilled memory (summary, decisions, tasks) is saved and can be queried in later meetings.

Engineering highlights

  • Two processes, one privacy boundary

    Audio capture and transcription live in a Swift sidecar, separate from the Rust core. Only text events cross the boundary, so the privacy claim is enforced by the architecture.

  • A latency gate before the build

    The project set a blocking Sprint 0 gate: p95 under 500 ms from dispatch to first decode callback. The full Whisper large-v3 model failed it in several runs, so the app ships the compact 632 MB variant, which passed.

  • Hybrid retrieval for past meetings

    Search combines sqlite-vec nearest-neighbor results with FTS5 keyword results, fused with Reciprocal Rank Fusion. Keyword search still works when no embeddings are configured.

  • Crash recovery and graceful degradation

    Meeting state is snapshotted during the call and orphaned meetings are recovered at startup. If the AI provider is unavailable, transcription and subtitles keep running.

  • Diagrams as the stored artifact

    Checkpoint diagrams are saved as Mermaid source only. Label language switches, AI edits with undo, and conversion to an editable Excalidraw drawing are all derived from that one source.

My role

I designed and built it end to end: the Swift audio sidecar, the Rust core, the React interface and the latency benchmark harness.

Stack

  • Rust
  • Tauri
  • Swift
  • React
  • TypeScript
  • SQLite
master’s thesis · on-device aiMaster’s thesis, prototype

Meter Reader

An offline-first Android app that reads water meters with the phone camera, using two YOLOv8 models running on the device.

mAP50, meter detector (validation, last epoch)
0.995
mAP50, digit detector (validation, last epoch)
0.989
precision / recall, meter detector (validation)
0.997 / 0.997

The problem

A rural water board in Azuay reads meters by hand and writes the numbers down. Transcription errors happen, and the field has little or no mobile signal.

Screens and diagrams

How it works

  1. 01Camera frameLive preview in the Compose camera screen
  2. 02Meter detectorYOLOv8n TFLite finds the counter window
  3. 03CropCounter window cropped and letterboxed to 640x640
  4. 04Digit detector10 classes, confidence 0.5, NMS IoU 0.5
  5. 05Confirm and saveEditable form, Room database, WorkManager sync

A Python pipeline downloads an annotated dataset, trains a YOLOv8n model to find the meter's counter window, and a second YOLOv8 model to find the digits 0 to 9. Both are exported to TensorFlow Lite. The detector was trained on 7,342 images and the digit detector on 7,350 images, with about 700 validation images each.

The Android app is written in Kotlin with Jetpack Compose, Hilt and Room. In the camera screen the first model locates the counter window, the app crops it, and the second model reads the digits. The reading fills the form and the user confirms or corrects it.

Everything is saved to a local database first. WorkManager uploads readings and photos when the phone gets a connection. Login uses access and refresh tokens kept in encrypted storage, with optional biometric unlock.

Engineering highlights

  • Two-stage detection instead of one OCR model

    Finding the counter window first, then detecting digits inside the crop, keeps each model small and easy to evaluate. The digit stage is an object detector with ten classes, not a text recognizer.

  • Models run on the phone

    Both models run as TFLite files inside the app, with no server call during a reading. The pipeline also exported float16 and int8 variants (about 6 MB and 3 MB for the digit model against 12 MB in float32).

  • Human confirmation stays in the loop

    The camera screen only auto-captures when the meter detection confidence is high and stable. The detected digits go into a form that the reader can edit before saving, and the app stores the model confidence with each reading.

  • Offline-first by design

    Room is the source of truth in the field. Background sync, token refresh and a visible sync status bar mean a reader can finish a whole route without a connection.

  • Models checked outside the app

    The repo includes desktop camera tools that run the TFLite models with a manual non-maximum suppression step, the same approach the Android code uses, so the models can be checked outside the app.

My role

I trained the two detection models and built the Android app, including the camera flow, local storage and sync.

Stack

  • YOLOv8
  • TensorFlow Lite
  • Kotlin
  • Jetpack Compose
  • Android
  • Python
messaging infrastructure

Notificaciones

A multi-tenant API for sending WhatsApp messages through Meta's official Cloud API, built so other products can call it.

test functions in the repo (fast suite excludes integration tests)
359
deployable apps: send API, management API, webhook receiver, webhook processor, pipeline, dashboard
6
architecture decision records
6

The problem

Several products in the same company needed to send WhatsApp notifications. The first solution was a script tied to one use case, with its own database queries and Excel parsing. A shared service lets each new product send messages by being a tenant with templates, with no changes to the service.

Screens and diagrams

How it works

  1. 01Caller productPOST /messages + API key
  2. 02Send APIValidate, idempotency, log
  3. 03Step FunctionsChunk and fan out (batches)
  4. 04Meta Cloud APIWhatsApp delivery
  5. 05Webhook receiverVerify, queue, update status

Callers push everything: recipients and the already-resolved parameters. The service validates, renders, sends, logs and reconciles. It never reads a caller's business data. This was an explicit design decision (ADR-0001).

A send API accepts single messages and batches with an API key and an idempotency key. Large batches go through a Step Functions pipeline that splits recipients into chunks, runs up to 10 chunks in parallel, retries failures and sends dead batches to a DLQ.

A webhook receiver verifies Meta's signature, de-duplicates events and queues them to SQS for a separate processor. This keeps Meta's request path short. A management API and a React dashboard handle tenants, WhatsApp accounts, templates and API keys. The domain is channel-agnostic, so email or SMS would be new adapters.

Engineering highlights

  • Caller-push design

    The service owns no business data. That keeps it generic and lets a new product join as a tenant plus templates.

  • Idempotency at two levels

    A partial unique index on the idempotency key for sends, and deterministic Step Functions execution names for batches.

  • Webhook handling hardened after an audit

    Constant-time signature check, inbound de-duplication, a guard against out-of-order status updates, and a split into receiver, SQS and processor so slow downstream calls do not block Meta.

  • Hexagonal ports and strict typing

    A NotificationChannel port with a real Meta adapter and a fake one for local work. The repo enforces ruff and mypy --strict.

My role

I designed and built it, from the domain model to the AWS infrastructure.

Stack

  • Python
  • FastAPI
  • WhatsApp
  • AWS Lambda
  • PostgreSQL
  • Terraform
municipal parking

Tenpi Parking

A multi-tenant system for municipal paid street parking in small Ecuadorian towns: drivers pay by WhatsApp, officers check plates on a phone.

numbered architecture decisions (AD-1 to AD-16)
16
apps: bot webhook, parking API, enforcement PWA, back-office dashboard
4
pure domains: parking, wallet, conversation
3

The problem

Small Ecuadorian municipalities run street parking with paper cards sold at shops. Drivers cannot top up or extend remotely, officers check by hand, and the municipality has little data on occupancy or revenue. The project digitizes this as a complement to the existing local ordinance, not a replacement.

Screens and diagrams

How it works

  1. 01DriverSends plate and minutes on WhatsApp
  2. 02Bot webhookVerify signature, de-dupe, find tenant
  3. 03Conversation + domainsQuote tariff, check schedule
  4. 04Wallet + PostgresAtomic debit, create session
  5. 05Officer PWAPlate check, map, digital fine

Drivers use WhatsApp, with no app to install. A chat state machine handles starting and extending a session, balance, and where to top up. Each session debits a prepaid wallet. Money is stored as integer cents everywhere, and the wallet cannot go below zero.

Officers use a mobile web app (PWA) with a live map of numbered parking spaces drawn as polygons, plate lookup, and digital fines with photos. It has an offline queue. Municipal staff and shops use a back-office dashboard for zones, tariffs, schedules, wallet top-ups, store settlements and fines.

Tariffs, schedules and rules are stored per tenant as data, so a second municipality is configuration, not a deploy. Online top-ups sit behind a provider-neutral payment port, with a payment-link flow designed but not yet enabled.

Engineering highlights

  • Money as integer cents

    A project rule: no floats for money in memory, the database or API payloads. The wallet ledger is atomic and has a reconciliation function.

  • Dependency rule enforced

    Domains import only a shared kernel and never each other. Cross-domain coordination happens in application use cases. mypy --strict is a blocking gate.

  • Tenant isolation with Row-Level Security

    PostgreSQL RLS with a non-superuser app role, plus integration tests that check isolation against a real database.

  • Bot safety details

    Webhook signature checks fail closed, messages are de-duplicated by WhatsApp message id, and replies are sent in two phases after the database commit.

  • Recorded decisions

    16 numbered architecture decisions, an open-questions log and C4 and sequence diagrams are kept in the repo.

My role

I own the design and wrote the code: domains, bot, API and the officer and back-office apps.

Stack

  • Python
  • FastAPI
  • PostgreSQL
  • PostGIS
  • WhatsApp
  • React
commercial erp

Yimmar

Commercial ERP and CRM covering sales, freight, collections, checks, purchases and driver settlements.

backend test functions (counted with rg 'def test_')
940+
database models across 10 apps
39
written standard operating procedures
8

The problem

A trading business tracked sales, shipments, payments, checks and purchases in spreadsheets. It needed one system that records who owes what, applies payments to the oldest sales first, and keeps an audit trail.

Screens and diagrams

How it works

  1. 01Sale draftLines, discounts and withholdings
  2. 02FinalizeBecomes paid or unpaid
  3. 03FreightShipments, vehicles, drivers, advances
  4. 04CollectionOldest sale first; checks tracked by state
  5. 05SettlementSurplus kept as customer credit

A sale starts as a draft and becomes paid or unpaid when finalized. Shipments carry freight, advances and settlements. A payment is allocated to the customer's oldest pending sales first, and any surplus is kept as a customer credit. Post-dated checks stay pending until a daily job makes them collectable, and a bounced check reverses its payment. Purchases from suppliers can be offset against what the same party owes.

The data came from Google Sheets. A nightly migration reloads it in an authorized environment, records every row-level problem and compares totals against the source summaries. 30 business models store where each record came from and who created it. The backend is Django and DRF with services and selectors. The frontend is a React admin dashboard, and Terraform manages the cloud storage and IAM.

Engineering highlights

  • Traceable data migration

    Each migration batch is tagged, issues are stored per row and cell, and results are compared against the source summaries. Destructive migration is blocked in production.

  • Payment allocation rules

    Payments go to the oldest sale first, never mix customers in one settlement, and bounced checks reverse their payments with a trace back to the original check.

  • Operating procedures written alongside the code

    Eight standard procedures cover sales, freight, collections, credits, purchases, nightly migration and driver settlement, each with controls and code references.

  • Login and cookie hardening

    Login has a rate limit that fails closed when Redis stalls, cookie authentication enforces CSRF checks, and the frontend has browser tests against a real backend.

  • Observability with a privacy boundary

    Logs are structured JSON that excludes request bodies, IPs, tokens, SQL parameters and financial amounts, and database metrics are exported separately.

My role

I wrote most of the backend and the Terraform for its environments.

Stack

  • Django
  • PostgreSQL
  • Redis
  • Celery
  • React
  • Terraform
master’s project · computer visionMaster’s project

Face Recognition

A modular real-time face identification pipeline in Python, with swappable detector, embedder and matcher.

InsightFace backend on Apple M2 Max, as reported (dlib: 18-22 FPS)
12-15 FPS
FAISS search time per query, as reported
<1 ms

The problem

Course-style face recognition demos tend to be one script that mixes detection, embedding and matching, so no part can be replaced or tested alone. The goal was a clean pipeline that identifies a person from a webcam or video file and can later grow into attendance tracking.

Screens and diagrams

How it works

  1. 01InputWebcam or video file via OpenCV
  2. 02DetectSCRFD boxes and 5 landmarks
  3. 03AlignFive-point alignment to 112x112
  4. 04EmbedArcFace, 512-dimension vector
  5. 05MatchFAISS cosine search, threshold decides name or unknown

Each frame goes through SCRFD (from InsightFace) for face boxes and five landmarks, a five-point alignment to a 112x112 crop, an ArcFace model that produces a 512-dimension embedding, and a FAISS index that returns the closest enrolled identity by cosine similarity. A configurable threshold (0.35 by default) decides between a name and 'unknown'.

The code is organized around interfaces for the detector, aligner, embedder and matcher, with a backend factory. A second backend based on dlib and face_recognition (128-dimension embeddings) plugs into the same services, which made a side-by-side comparison possible without touching the recognition logic.

Enrollment and recognition are separate CLI scripts and services, with webcam and video-file sources, on-screen overlays, structured logging, pytest tests and dependency management with uv.

Engineering highlights

  • Interfaces first, models second

    Detector, aligner, embedder and matcher are abstract interfaces. Swapping InsightFace for dlib meant adding a backend, not rewriting the services.

  • Two backends, measured side by side

    The report compares InsightFace and dlib on FPS and per-stage time on the same laptop. In the report's table, dlib is faster (18 to 22 FPS against 12 to 15) and InsightFace uses larger embeddings (512 against 128 dimensions).

  • Threshold treated as a tuning problem

    The report sweeps the similarity threshold: accuracy is 100% at 0.35 and 0.50, 88.5% at 0.60 and 15.4% at 0.70. The default of 0.35 comes from that sweep.

  • Built for the next feature

    The structure leaves room for tracking, cooldowns and event logging for attendance, but those were deliberately left out of scope for the first version.

  • Honest reading of the result

    The validation set has 26 images and only one belongs to the second enrolled person, so the 100% figure shows the pipeline works end to end. It does not show how it would perform on a larger group.

My role

I specified the architecture, built the pipeline and both backends, ran the evaluation and wrote the course report.

Stack

  • Python
  • OpenCV
  • ONNX
  • pytest
master’s project · iotMaster’s project, simulated

Smart Irrigation

An IoT system that decides when to irrigate cattle pasture, combining soil sensors, a rain forecast and a Random Forest model.

hourly rows of historical weather used for training (Jan 2024 to Jan 2026)
17,424
feature importance of soil moisture, the top variable
73.7%

The problem

In the dry season, pasture in the Ecuadorian highlands is irrigated by hand with no technical criterion, which wastes water. A fixed rule such as 'irrigate below 30% moisture' ignores whether rain is about to arrive.

Screens and diagrams

How it works

  1. 01ESP32 sensorsSoil moisture, temperature, humidity (Wokwi simulation)
  2. 02MQTT brokerCloud broker over TLS
  3. 03Node-REDAdds Open-Meteo forecast, stores to InfluxDB
  4. 04Random Forest APIFlask /predict: irrigate or not
  5. 05Valve and dashboardDecision back to ESP32; data in InfluxDB for Grafana

A simulated ESP32 (Wokwi) publishes soil moisture, temperature and air humidity over MQTT with TLS to a cloud broker. Node-RED subscribes, polls the Open-Meteo forecast every 30 minutes, and stores sensor and weather readings in InfluxDB.

Every minute Node-RED sends the current readings to a Flask API that serves a Random Forest classifier. The model uses seven features: soil moisture, temperature, air humidity, precipitation, rain probability, hour and month. It answers irrigate or do not irrigate, and Node-RED publishes that decision back to the ESP32 to switch a valve LED.

The model was trained on two years of hourly Open-Meteo data for the site (17,424 rows). Open-Meteo had no soil moisture for this location, so the project simulates it with a water-balance model, and labels come from irrigation rules based on FAO guidance.

Engineering highlights

  • Forecast-aware decisions

    Rain probability is a model input. In the documented test, soil at 32% moisture with an 85% chance of rain gets 'do not irrigate' (94.8% confidence).

  • Training data had to be constructed

    With no soil-moisture series available, the project built one with a water-balance simulation (rain gain, evapotranspiration loss, plant use, seasonal factor) and labeled it with FAO-derived rules.

  • Model served behind a small API

    Training is offline, and inference is an HTTP call from the Node-RED flow to a Flask service. The same flow handles ingestion, enrichment, storage and actuation.

  • Layered architecture with managed services

    Messaging, time-series storage and dashboards use free tiers of cloud services (broker, InfluxDB, Grafana), so the whole system runs from a laptop. The architecture diagram also sketches an industrial layer with a PLC and OPC-UA.

  • Honest limits of the metrics

    The 99.8% accuracy is measured against labels produced by the same rules family used to generate the data, so it shows the model learned those rules. It is not evidence about field performance.

My role

I designed the architecture, wrote the ESP32 firmware, the Node-RED flow and the training and API code, and documented each component.

Stack

  • MQTT
  • Node-RED
  • scikit-learn
  • Flask
  • InfluxDB
  • Grafana

Earlier projects

  • JAAPYSJ01/2023 – 05/2023

    Water utility system for customers, meter readings, billing and electronic invoicing. Django, Angular, PostgreSQL, AWS, Docker.

  • Carton Plast05/2022 – 03/2023

    Box manufacturing process management for a company in Ecuador. Java 11, Spring Boot, Kubernetes, Docker, MariaDB.

What I reach for, grouped by the job it does.

Languages
  • Python
  • Java
  • TypeScript
  • JavaScript
  • SQL
Backend
  • FastAPI
  • Pydantic
  • SQLAlchemy
  • Alembic
  • Django
  • Spring Boot
  • Celery
  • RabbitMQ
Data
  • PostgreSQL
  • PostGIS
  • Query tuning
  • Schema migrations
  • CDCDebezium
  • Iceberg
  • MariaDB
  • Oracle
Cloud & DevOps
  • AWSECS Fargate, EKS, SNS/SQS, EventBridge, S3, CodePipeline, CodeBuild
  • Terraform
  • Docker
  • Kubernetes
  • GitLab CI
  • ArgoCD
  • Jenkins
Observability & quality
  • Datadog
  • Loki/Grafana
  • SonarQube
  • pytest
  • End-to-end testing
Frontend
  • Angular
  • React
  • Vue.js
  • Next.js
AI & tools
  • Claude Code
  • OpenAI API
  • Jira
  • Confluence
  • Scrum

A master’s in applied AI, an engineering degree, and a handful of cloud certifications.

Education

  • Master in Applied Artificial Intelligence

    Universidad Técnica Particular de Loja (UTPL)

    04/2025 – 04/2026

  • Software Engineer

    Universidad de las Fuerzas Armadas – ESPE

    01/2015 – 01/2020

Certificationsobtained

  • PCEP – Certified Entry-Level Python Programmer

    OpenEDG Python Institute

    12/2023

  • HashiCorp Certified: Terraform Associate (002)

    HashiCorp

    12/2022

  • Microsoft Certified: Azure Fundamentals

    Microsoft

    12/2022

  • AWS Certified Solutions Architect – Associate

    AWS

    11/2022

  • AWS Certified Cloud Practitioner

    AWS

    09/2022

  • Scrum Master

    CertMind

    11/2020