luis pillaga
Retrato de Luis PillagaHola, soy Luis PillagaBackend · Nube · Full stack

Senior Software Engineer que construye sistemas backend, infraestructura en la nube y aplicaciones web para equipos de EE. UU.

Seis años trabajando en remoto en equipos de backend y nube para empresas de EE. UU. Trabajo en todo el stack: Python, Java y TypeScript en el backend, React, Angular y Vue en el frontend, y AWS, Terraform y Kubernetes por debajo. Hoy, a través de FullStack Labs, construyo servicios backend para la plataforma geoespacial de ensayos de campo de Syngenta con FastAPI, PostgreSQL/PostGIS y AWS. En proyectos propios he construido productos para empresas e instituciones del Ecuador. Trabajo desde Biblián, Ecuador (UTC−5), dentro del horario laboral de EE. UU.

El trabajo que puedo asumir, cada uno con un lugar donde ya lo hice.

python · apis

Servicios backend y APIs

Diseño y construyo APIs en Python con FastAPI y Django: el modelo de datos, los endpoints, los consumidores en segundo plano y tests que corren contra una base de datos real.

EjemploEn Syngenta trabajo en el backend de una plataforma geoespacial para ensayos de campo, y lideré la revisión que dejó su rediseño a la par del sistema en producción, con arreglos en 6 repositorios.

postgresql · rendimiento

Bases de datos rápidas y seguras de cambiar

Leo planes de ejecución, corrijo índices y estadísticas, y cambio esquemas de tablas grandes sin bloquear a los usuarios.

EjemploLlevé un endpoint de producción de 320 s a 0.4 s, y entrego cambios de esquema en una base PostgreSQL/PostGIS de más de 20M de filas.

datos · integraciones

Mover datos entre sistemas sin perder ninguno

Integraciones orientadas a eventos con SNS/SQS y EventBridge, captura de cambios con Debezium y migraciones desde sistemas legados.

EjemploEstuve a cargo del pipeline que mueve ~10M de registros por ambiente desde una plataforma legada a su rediseño.

nube · devops

Infraestructura en la nube y los pipelines que la despliegan

AWS definido en Terraform, Kubernetes y pipelines de CI/CD con tests automáticos y controles de seguridad.

EjemploConstruí desde cero la infraestructura AWS de un bufete de abogados de EE. UU. en Terraform (aprovisionamiento y despliegues 30% más rápidos), y migré 106 servicios de una universidad a Kubernetes.

ia aplicada

IA en productos reales: APIs de LLM y modelos en el dispositivo

Integro la API de OpenAI en productos (chat, generación de documentos, análisis de imágenes, búsqueda sobre los documentos de una empresa) y entreno modelos compactos de visión por computadora que corren en el teléfono, sin servidor. Tengo una maestría en IA aplicada de la UTPL, y en el día a día programo con Claude Code y verifico cada cambio con tests end-to-end.

EjemploGeneración de documentos legales y un chatbot que le ahorran a un equipo legal unas 6 horas de paralegal por semana. Para mi tesis de maestría, modelos YOLOv8 comprimidos a INT8 TFLite que leen medidores de agua en tiempo real dentro de una app Android (0.995 mAP50 en validación).

producto · de punta a punta

Productos completos, de la primera tabla a producción

He diseñado y construido SaaS multi-tenant para empresas del Ecuador, publicados bajo el nombre Tenpisoft, con backends en Python y frontends en React, Angular o Vue.

EjemploUn SaaS multi-tenant para juntas de agua potable, una API de facturación electrónica para el SRI, un bot de WhatsApp para parqueo municipal y un kiosco Android que lee cédulas para tomar el quórum en asambleas.

Seis años de backend y nube, casi siempre para empresas de Estados Unidos.

FullStack Labs

Remoto

06/2025 – Actualidad

Consultora de software nearshore que provee equipos de ingeniería a grandes empresas de Estados Unidos.

Senior Software EngineerSyngenta

  • Construyo servicios backend para la plataforma geoespacial de ensayos de campo de Syngenta en Python y FastAPI, sobre PostgreSQL/PostGIS y AWS.
  • Lideré la revisión de paridad entre la plataforma en producción y su rediseño en microservicios, y entregué los arreglos en 6 repositorios.
  • Llevé un endpoint de producción de 320 s a 0.4 s, y entrego cambios de esquema en una base de más de 20M de filas sin bloquear a los usuarios.
  • Estuve a cargo de la migración de ~10M de registros por ambiente desde la plataforma legada, y de las integraciones por eventos (SNS/SQS, EventBridge, Debezium CDC) con los sistemas que la rodean.
  • Atiendo incidentes de producción desde la primera alerta en Datadog o Loki hasta el plan de ejecución, y documento la causa raíz para el equipo.
  • Python
  • FastAPI
  • PostgreSQL
  • PostGIS
  • AWS
  • Terraform
  • Debezium
  • Iceberg

Devsu

Remoto

09/2021 – 05/2025

Empresa de outsourcing de software que conecta ingenieros latinoamericanos con clientes de Estados Unidos.

Full Stack Developer y DevOps EngineerZinda Law Group

  • Mejoró el rendimiento de la aplicación principal en un 20% optimizando consultas de PostgreSQL y refactorizando código en Django, Vue.js y React.
  • Redujo en un 30% el tiempo de aprovisionamiento y despliegue construyendo desde cero la infraestructura de AWS como código con Terraform.
  • Ahorró al equipo legal unas 6 horas de paralegal por semana construyendo un chatbot con OpenAI y la generación automatizada de documentos legales.
  • Aceleró los releases construyendo pipelines de CI/CD con pruebas automatizadas y controles de seguridad.
  • Inició nuevos proyectos en Django y React, definiendo su arquitectura, stack y flujo de desarrollo.
  • PostgreSQL
  • Django
  • Vue.js
  • React
  • AWS
  • Terraform
  • OpenAI

Full Stack DeveloperBanco Pichincha

  • Entregó funcionalidades de originación y evaluación de créditos para SIMA, la plataforma de préstamos del banco para el sector de microempresas de Ecuador, usada por asesores de crédito para aprobar o rechazar préstamos.
  • Coordinó el trabajo de backend y frontend para entregar las funcionalidades de cada release a tiempo.

Universidad de las Fuerzas Armadas – ESPE

Latacunga, Ecuador

11/2020 – 09/2021

DevOps Engineer

  • Migró 106 servicios institucionales a un clúster central de Kubernetes, mejorando la escalabilidad y el uso de recursos.
  • Reemplazó los despliegues manuales a TEST y PROD por pipelines de CI/CD e introdujo despliegue continuo GitOps con Jenkins y ArgoCD.
  • Kubernetes
  • Jenkins
  • ArgoCD

Full Stack Engineer

  • Lideró el desarrollo de un sistema de historias clínicas digitales que atiende a 4.459 pacientes y ~500 interacciones clínicas por mes, construido con una API REST en Spring Boot y una aplicación de una sola página en Angular.
  • Spring Boot
  • Angular

PlanERP

Cuenca, Ecuador

02/2018 – 04/2018

Back End Developer (Pasantía)

  • Construyó un módulo de evaluación del desempeño de servidores públicos para un ERP gubernamental, usando el framework Tryton basado en Python.
  • Python
  • Tryton

Productos que construí para empresas e instituciones del Ecuador bajo el nombre Tenpisoft, y mis proyectos de la maestría en IA aplicada.

api de facturación · SRI

Facturación electrónica para el SRI, construida como una API a la que se conectan otros productos: envían una factura y la reciben firmada, autorizada y en PDF.

Dos servicios FastAPI en AWS Lambda y un pipeline de cuatro pasos con Step Functions (firma del XML, envío al SRI, consulta de autorización, PDF). Cada cliente está aislado con row-level security de PostgreSQL, y la suite rápida corre unos 2.970 tests.

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

juntas de agua

SaaS multi-tenant para las juntas de agua potable rurales del Ecuador: clientes, lecturas de medidores, facturación y administración.

API REST en Django con Celery y frontend en React + TypeScript, infraestructura en dos cuentas de AWS con Terragrunt y nueve pipelines de CI, incluidos tests end-to-end y controles de seguridad. Unos 1.830 tests en el backend.

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

asambleas

Registro de socios, asistencia y quórum para asambleas de juntas de agua, cooperativas, juntas parroquiales y condominios.

Monolito modular en FastAPI con row-level security en PostgreSQL y Terraform en AWS. Un kiosco Android que funciona sin conexión lee la cédula con OCR en el dispositivo (ML Kit) y sincroniza cuando vuelve la red.

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

copiloto de reuniones para macOS

Copiloto de escritorio para reuniones en inglés: transcripción y traducción en vivo, preguntas sugeridas para participar y una biblioteca con búsqueda y resúmenes de reuniones anteriores.

Un proceso en Swift captura el audio del sistema y lo transcribe en el Neural Engine con WhisperKit, así el audio nunca sale de la Mac. Un núcleo en Rust/Tauri se encarga de la traducción, el copiloto con LLM y la búsqueda sobre SQLite con embeddings.

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

tesis de maestría · ia en el dispositivo

App Android que lee medidores de agua con la cámara, para quienes toman las lecturas mensuales en las juntas de agua rurales.

Detectores YOLOv8 para la ventana del medidor y sus dígitos (0.995 y 0.989 mAP50 en validación), cuantizados a INT8 TFLite y ejecutados en el teléfono. La app funciona sin conexión, con almacenamiento local y sincronización en segundo plano.

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

infraestructura de mensajería

API de notificaciones compartida por los demás productos, empezando por WhatsApp a través de la Cloud API de Meta.

Servicios de envío, administración y webhooks como funciones FastAPI en AWS Lambda, con claves de idempotencia y row-level security forzado en PostgreSQL. El canal está detrás de una interfaz, así se pueden sumar SMS o correo sin tocar el núcleo.

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

parqueo municipal

Parqueo tarifado en la vía pública para cantones pequeños: los conductores pagan desde un bot de WhatsApp con billetera prepago, los agentes controlan placas desde una app web y el municipio lo administra desde un panel.

FastAPI en AWS Lambda con SQLAlchemy asíncrono y PostgreSQL/PostGIS con row-level security. Las tarifas y reglas se guardan como datos de cada municipio, no como código.

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

erp comercial

ERP para un negocio comercial: ventas con descuentos y retenciones, pagos, despachos con vehículos y choferes, productos y contabilidad.

API REST en Django con Celery Beat y Redis, un importador de datos heredados desde Google Sheets, un panel en React con tests end-to-end y Terraform para los ambientes en AWS.

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

proyecto de maestría · visión por computadora

Identificación de rostros en tiempo real desde una cámara o un video, pensada para crecer hacia un sistema de asistencia.

Pipeline modular en Python: detección con InsightFace y alineación de 5 puntos, embeddings ArcFace de 512 dimensiones y búsqueda de vecinos con FAISS, con backends intercambiables y tests unitarios.

  • Python
  • OpenCV
  • ONNX
  • pytest

proyecto de maestría · iot

Riego para pastizales ganaderos del Azuay rural que solo riega cuando hace falta, para ahorrar agua en la época seca.

Un ESP32 envía temperatura y humedad del suelo por MQTT a Node-RED, que suma el pronóstico de lluvia y le pregunta a un modelo Random Forest servido con Flask si hay que regar. Las lecturas quedan en InfluxDB y Grafana.

  • MQTT
  • Node-RED
  • scikit-learn
  • Flask
  • InfluxDB
  • Grafana
api de facturación · SRIEn producción, uso interno

Comprobantes

Una API multi-tenant que emite, firma y envía las facturas electrónicas de Ecuador al SRI.

tests en la suite rápida, sin recursos externos
~2,971
tipos de comprobante: factura, nota de crédito, nota de débito, retención, guía de remisión
5
Lambdas en el pipeline de Step Functions
4

El problema

En Ecuador cada comprobante debe firmarse con XAdES-BES, enviarse al SRI por SOAP y ser autorizado antes de ser válido. Cada aplicación que factura tendría que reconstruir todo eso. Comprobantes es esa infraestructura compartida: otros productos emiten facturas con una sola llamada a la API.

Pantallas y diagramas

Cómo funciona

  1. 01Producto clientePOST /facturas + API key
  2. 02Core APIValida, impuestos, secuencial, guarda, 202
  3. 03Step FunctionsFirma XML (XAdES-BES)
  4. 04SRI (SOAP)Envío y consulta de autorización
  5. 05RIDE PDF + correoGuardado en S3, enviado por SES

El cliente llama a POST /api/v1/facturas con una API key. La core API valida la solicitud, calcula impuestos con aritmética Decimal, asigna un secuencial atómico, guarda la factura en una sola transacción e inicia una ejecución de Step Functions. Responde 202 de inmediato. Luego el cliente consulta el estado del documento.

La máquina de estados ejecuta cuatro Lambdas: firmar el XML, enviarlo al SRI, consultar la autorización (hasta 60 intentos cada 30 segundos, con esperas más largas si el SRI está caído) y generar el PDF del RIDE. Cada paso tiene reintentos y un catch que envía los fallos a una cola de mensajes fallidos (DLQ).

Los datos viven en PostgreSQL con Row-Level Security. Una API de administración y un dashboard en React gestionan tenants, empresas, usuarios y API keys. El contrato de la core API se genera desde el código y alimenta un SDK en Python y un CLI.

Decisiones técnicas

  • Aislamiento por tenant en la base de datos

    Las políticas de Row-Level Security filtran cada consulta por tenant. Las apps se conectan con un rol sin privilegios de superusuario, porque los superusuarios saltan RLS. El tenant se fija por transacción con SET LOCAL.

  • Monorepo hexagonal

    Los paquetes de dominio contienen entidades y casos de uso. Los adapters implementan puertos para SQLAlchemy, S3, SOAP del SRI, XAdES y ReportLab. Las reglas de dependencia están documentadas y se verifican.

  • Pipeline asíncrono resiliente

    Step Functions con retry y catch en cada estado, una DLQ con retención de 14 días, llaves de idempotencia en la emisión y una verificación programada del estado del SRI.

  • Contratos generados, no escritos a mano

    Los specs OpenAPI y el SDK en Python se generan desde las apps. Una prueba de la suite rápida falla en CI si quedan desactualizados.

  • Infraestructura como código y releases

    Módulos de Terraform y Terragrunt para dev y prod, despliegues con GitHub Actions por OIDC y versionado con release-please a partir de conventional commits.

Mi rol

Lo diseñé y construí: la API, el pipeline de firma y envío, la infraestructura y el proceso de releases.

Stack

  • Python
  • FastAPI
  • AWS Lambda
  • PostgreSQL
  • Terraform
  • React
juntas de agua

TenpiYaku

SaaS multi-tenant para juntas rurales de agua potable: lecturas de medidor, facturación, cobros y contabilidad en un solo sistema.

tests de backend (auditoría de julio de 2026)
1,800+
tablas de historial cubiertas por row-level security
23
módulos funcionales documentados
14
idiomas de interfaz: español y kichwa
2

El problema

Las juntas comunitarias de agua en Ecuador cobran a sus socios según el consumo del medidor, reciben pagos, cobran multas y organizan mingas. Muchas lo hacen con hojas de cálculo y papel. Necesitan un sistema que maneje los datos de cada junta por separado y en español o kichwa.

Pantallas y diagramas

Cómo funciona

  1. 01Lectura de medidorSe registra por período a lo largo de un recorrido
  2. 02ValidaciónValidar la lectura dispara la generación de la planilla
  3. 03PlanillaTarifa por tramos progresivos, más recargos por mora desde una tarea diaria
  4. 04CobroSe asigna a planillas y multas
  5. 05Contabilidad y facturaLibro de doble partida y factura electrónica opcional

Cada junta es un tenant. Se valida una lectura de medidor y esa validación genera la planilla con tarifas por tramos progresivos. Los pagos se asignan a planillas y multas, y un libro contable de doble partida registra el dinero. Una tarea diaria de Celery evalúa los recargos por mora, y la facturación electrónica al SRI puede estar en modo apagado, borrador o automático según la junta.

El backend es Django y DRF con capas de services y selectors, vistas delgadas y código solo en inglés. El frontend en React está organizado por funcionalidad y la interfaz funciona en español y kichwa. La autenticación usa AWS Cognito y los roles y permisos viven solo en la base de datos de Django. Los recursos de AWS se gestionan con Terragrunt y Terraform.

Decisiones técnicas

  • Aislamiento de tenants en la base de datos

    Además de filtrar por junta en el código, las políticas de row-level security de Postgres rechazan filas de otra junta, niegan el acceso si no hay tenant y cubren también las 23 tablas de historial.

  • Autenticación y autorización separadas

    Cognito responde quién es el usuario. Los roles, permisos y la pertenencia a una junta quedan en Django, así que un cambio de rol aplica al instante y no requiere sincronizar con Cognito.

  • Facturación electrónica independiente del proveedor

    La facturación usa puertos y adaptadores con el proveedor elegido por junta, por lo que cambiar de proveedor no obliga a reescribir el módulo de dominio.

  • Reglas de negocio y auditorías documentadas

    Hay registros de decisión para reglas como la verificación de solvencia antes de cualquier trámite. Una auditoría de toda la app generó escenarios en pytest que fallan a propósito hasta que se corrige cada hallazgo.

  • Infraestructura con mínimo privilegio

    Las políticas de IAM evitan comodines, la app no puede borrar el prefijo de respaldos y el CI corre en PRs encadenados para que ninguno llegue a merge sin pruebas.

Mi rol

Lo construí de punta a punta: backend, frontend e infraestructura.

Stack

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

Kworum

Padrón de socios y asistencia a asambleas con conteo exacto de quórum, más un quiosco Android sin conexión que lee la cédula.

tests de la API
1,000+
tablas de dominio más una de recibos de importación, todas con RLS forzado
18
antigüedad máxima de una entrada sin conexión que acepta la API
24 h

El problema

Las juntas de agua, cooperativas y organizaciones similares toman asistencia en papel, cuentan marcas a mano y reconstruyen después quién estuvo presente. En los locales el internet suele fallar y muchos socios no tienen smartphone.

Pantallas y diagramas

Cómo funciona

  1. 01PadrónSocios, representaciones permanentes, importación CSV
  2. 02Abrir registroLa política de quórum queda congelada para esa asamblea
  3. 03Registro de entradaQuiosco (cédula escrita o cámara, cola sin conexión) o mesa de asistencia
  4. 04QuórumTablero en vivo; instalar exige alcanzar el umbral
  5. 05CierreLas instantáneas inmutables conservan la evidencia

El equipo mantiene el padrón y las autorizaciones permanentes de representación, y luego abre una asamblea. Al abrir el registro se congela la política de quórum de esa asamblea, de modo que cambios posteriores de configuración no la alteran. Los eventos de asistencia son inmutables, las correcciones quedan con su motivo y las instantáneas conservan la evidencia del quórum al instalar y al cerrar.

En la puerta, una tableta Android emparejada funciona en modo quiosco. El socio escribe el número de cédula o la muestra a una cámara USB. La app lee el número en el dispositivo con reconocimiento de texto de ML Kit y verifica el dígito verificador, el título de la tarjeta, un rostro de tamaño razonable y el contorno de la cédula durante tres cuadros seguidos. El padrón se guarda en la tableta y las entradas esperan en una cola local hasta que vuelve la red. Las entradas de más de 24 horas se rechazan y quedan para revisión de la secretaría. Un segundo modo, la mesa de asistencia, permite que la secretaría entre con su propia cuenta.

Decisiones técnicas

  • Row-level security en todas las tablas

    Todas las tablas tienen RLS forzado en Postgres, controlado con variables locales a la transacción, y el rol de ejecución no puede ser dueño de tablas ni saltarse RLS. La verificación de disponibilidad rechaza un rol de base de datos con privilegios.

  • Quiosco que funciona sin conexión

    La tableta guarda el padrón y una cola de entradas con Room y WorkManager, y cada entrada se sube una sola vez cuando vuelve la red.

  • Lectura de cédula en el dispositivo

    El reconocimiento de texto y el conteo de rostros corren de forma local. Los rostros solo se cuentan, no se guardan ni se comparan, y la imagen de la cámara nunca se muestra en pantalla.

  • Arquitectura verificada por pruebas

    La app Android tiene tres módulos con dependencias hacia adentro. Hay tests que revisan los imports y rompen el build si se viola una regla, y otro que impide colores y tamaños escritos a mano.

Mi rol

Lo construí de punta a punta: API, app web, kiosco Android e infraestructura.

Stack

  • FastAPI
  • PostgreSQL
  • React
  • Kotlin
  • Android
  • Cloudflare
copiloto de reuniones para macOS

Grasp

Un copiloto de reuniones para macOS que transcribe y traduce reuniones en inglés en vivo, con todo el audio procesado en la Mac.

Latencia p95, modelo Whisper compacto de 632 MB, Apple M2 Max (gate: menos de 500 ms; 98 muestras)
452.6 ms
Latencia p50, misma corrida
415.3 ms

El problema

Seguir una reunión rápida en inglés siendo hablante no nativo cansa, y los detalles se pierden al terminar la llamada. Las herramientas de transcripción en la nube envían el audio crudo a un tercero.

Pantallas y diagramas

Cómo funciona

  1. 01Captura de audioCore Audio Process Tap para el audio del sistema, más el micrófono
  2. 02Transcripción localWhisperKit en el Neural Engine, SpeakerKit para hablantes
  3. 03Núcleo en RustTraducción en vivo y copiloto sobre eventos de texto
  4. 04Interfaz en vivoOverlay de subtítulos, preguntas sugeridas, checkpoints
  5. 05Biblioteca de reunionesSQLite FTS5 + sqlite-vec, memoria destilada

Un sidecar en Swift se encarga de toda la captura de audio y la transcripción. Toma el audio del sistema con Core Audio Process Tap, graba el micrófono y ejecuta WhisperKit en el Neural Engine, con SpeakerKit para identificar a quien habla. El audio crudo nunca sale de ese proceso.

Un núcleo en Rust y Tauri v2 consume los eventos de transcripción, ejecuta la traducción en vivo y el copiloto, y guarda una biblioteca de reuniones con búsqueda en SQLite (FTS5 para palabras clave, sqlite-vec para embeddings). Una interfaz en React 19 y TypeScript muestra la ventana principal, un overlay flotante de subtítulos y un ícono en la bandeja.

Durante la llamada la app sugiere preguntas para hacer en inglés y puede crear un checkpoint bajo demanda: un resumen corto y un diagrama Mermaid de lo que se acaba de hablar. Al terminar, guarda una memoria destilada (resumen, decisiones, tareas) que se puede consultar en reuniones futuras.

Decisiones técnicas

  • Dos procesos, un límite de privacidad

    La captura y la transcripción viven en un sidecar en Swift, separado del núcleo en Rust. Solo cruzan eventos de texto, así que la promesa de privacidad la impone la arquitectura.

  • Una compuerta de latencia antes de construir

    El proyecto fijó un gate bloqueante en el Sprint 0: p95 menor a 500 ms desde el envío hasta el primer callback de decodificación. El modelo Whisper large-v3 completo no lo pasó en varias corridas, por eso la app usa la variante compacta de 632 MB, que sí lo pasó.

  • Recuperación híbrida de reuniones pasadas

    La búsqueda combina resultados vectoriales de sqlite-vec con resultados por palabra clave de FTS5, fusionados con Reciprocal Rank Fusion. La búsqueda por palabras funciona aunque no haya embeddings configurados.

  • Recuperación ante fallos y degradación controlada

    El estado de la reunión se guarda durante la llamada y las reuniones huérfanas se recuperan al iniciar. Si el proveedor de IA no está disponible, la transcripción y los subtítulos siguen funcionando.

  • El diagrama como artefacto guardado

    Los diagramas de los checkpoints se guardan solo como código Mermaid. El cambio de idioma de las etiquetas, la edición con IA con deshacer y la conversión a un dibujo editable en Excalidraw salen de esa única fuente.

Mi rol

Lo diseñé y lo construí de punta a punta: el sidecar de audio en Swift, el núcleo en Rust, la interfaz en React y el arnés de benchmark de latencia.

Stack

  • Rust
  • Tauri
  • Swift
  • React
  • TypeScript
  • SQLite
tesis de maestría · ia en el dispositivoTesis de maestría, prototipo

Lector de medidores

Una app Android offline-first que lee medidores de agua con la cámara del teléfono, con dos modelos YOLOv8 que corren en el dispositivo.

mAP50, detector de medidor (validación, última época)
0.995
mAP50, detector de dígitos (validación, última época)
0.989
precisión / recall, detector de medidor (validación)
0.997 / 0.997

El problema

Una junta de agua rural en Azuay toma las lecturas de los medidores a mano y las anota. Se cometen errores de transcripción y en el campo casi no hay señal móvil.

Pantallas y diagramas

Cómo funciona

  1. 01Imagen de cámaraVista en vivo en la pantalla de cámara de Compose
  2. 02Detector de medidorYOLOv8n en TFLite ubica la ventana del contador
  3. 03RecorteVentana recortada y ajustada a 640x640
  4. 04Detector de dígitos10 clases, confianza 0.5, NMS con IoU 0.5
  5. 05Confirmar y guardarFormulario editable, base Room, sincronización con WorkManager

Un pipeline en Python descarga un dataset anotado, entrena un modelo YOLOv8n que ubica la ventana del contador del medidor y un segundo modelo YOLOv8 que detecta los dígitos del 0 al 9. Ambos se exportan a TensorFlow Lite. El detector de medidor se entrenó con 7.342 imágenes y el de dígitos con 7.350, con unas 700 imágenes de validación cada uno.

La app Android está escrita en Kotlin con Jetpack Compose, Hilt y Room. En la pantalla de cámara el primer modelo ubica la ventana del contador, la app la recorta y el segundo modelo lee los dígitos. La lectura llena el formulario y el usuario la confirma o la corrige.

Todo se guarda primero en una base de datos local. WorkManager sube las lecturas y las fotos cuando el teléfono tiene conexión. El inicio de sesión usa tokens de acceso y de refresco guardados en almacenamiento cifrado, con desbloqueo biométrico opcional.

Decisiones técnicas

  • Detección en dos etapas en lugar de un solo modelo de OCR

    Ubicar primero la ventana del contador y luego detectar los dígitos dentro del recorte mantiene cada modelo pequeño y fácil de evaluar. La segunda etapa es un detector de objetos con diez clases, no un reconocedor de texto.

  • Los modelos corren en el teléfono

    Ambos modelos corren como archivos TFLite dentro de la app, sin llamadas a un servidor durante la lectura. El pipeline también exportó variantes float16 e int8 (cerca de 6 MB y 3 MB para el modelo de dígitos, frente a 12 MB en float32).

  • La confirmación humana se mantiene

    La pantalla de cámara solo captura automáticamente cuando la confianza de detección del medidor es alta y estable. Los dígitos detectados van a un formulario que el lector puede editar antes de guardar, y la app guarda la confianza del modelo con cada lectura.

  • Offline-first por diseño

    Room es la fuente de verdad en el campo. La sincronización en segundo plano, el refresco de tokens y una barra visible de estado de sincronización permiten completar una ruta entera sin conexión.

  • Modelos revisados fuera de la app

    El repositorio incluye herramientas de cámara de escritorio que ejecutan los modelos TFLite con un paso manual de supresión de no máximos, el mismo enfoque del código Android, para revisar los modelos fuera de la app.

Mi rol

Entrené los dos modelos de detección y construí la app Android, incluyendo el flujo de cámara, el almacenamiento local y la sincronización.

Stack

  • YOLOv8
  • TensorFlow Lite
  • Kotlin
  • Jetpack Compose
  • Android
  • Python
infraestructura de mensajería

Notificaciones

Una API multi-tenant para enviar mensajes de WhatsApp con la Cloud API oficial de Meta, pensada para que otros productos la consuman.

funciones de test en el repo (la suite rápida excluye las de integración)
359
apps desplegables: API de envío, API de administración, receptor de webhooks, procesador de webhooks, pipeline, dashboard
6
registros de decisiones de arquitectura (ADR)
6

El problema

Varios productos de la misma empresa necesitaban enviar notificaciones por WhatsApp. La primera solución fue un script atado a un solo caso de uso, con sus propias consultas a base de datos y lectura de Excel. Un servicio compartido permite que cada producto nuevo envíe mensajes siendo un tenant con plantillas, sin cambiar el servicio.

Pantallas y diagramas

Cómo funciona

  1. 01Producto llamadorPOST /messages + API key
  2. 02API de envíoValida, idempotencia, registra
  3. 03Step FunctionsDivide y reparte (lotes)
  4. 04Cloud API de MetaEntrega por WhatsApp
  5. 05Receptor de webhooksVerifica, encola, actualiza estado

Quien llama envía todo: los destinatarios y los parámetros ya resueltos. El servicio valida, renderiza, envía, registra y reconcilia. Nunca lee datos de negocio del llamador. Fue una decisión de diseño explícita (ADR-0001).

Una API de envío acepta mensajes individuales y lotes con API key y llave de idempotencia. Los lotes grandes pasan por un pipeline de Step Functions que divide los destinatarios en bloques, ejecuta hasta 10 en paralelo, reintenta los fallos y manda los lotes fallidos a una DLQ.

Un receptor de webhooks verifica la firma de Meta, deduplica eventos y los encola en SQS para un procesador aparte. Así la respuesta a Meta es corta. Una API de administración y un dashboard en React gestionan tenants, cuentas de WhatsApp, plantillas y API keys. El dominio es independiente del canal, así que email o SMS serían nuevos adapters.

Decisiones técnicas

  • Diseño caller-push

    El servicio no posee datos de negocio. Eso lo mantiene genérico y permite que un producto nuevo entre como tenant más plantillas.

  • Idempotencia en dos niveles

    Un índice único parcial sobre la llave de idempotencia en los envíos, y nombres de ejecución deterministas en Step Functions para los lotes.

  • Webhooks reforzados tras una auditoría

    Verificación de firma en tiempo constante, deduplicación de eventos entrantes, protección contra estados fuera de orden y separación en receptor, SQS y procesador para que llamadas lentas no bloqueen a Meta.

  • Puertos hexagonales y tipado estricto

    Un puerto NotificationChannel con un adapter real de Meta y uno falso para trabajo local. El repo exige ruff y mypy --strict.

Mi rol

Lo diseñé y construí, desde el modelo de dominio hasta la infraestructura en AWS.

Stack

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

Tenpi Parking

Un sistema multi-tenant para el estacionamiento tarifado en la calle de cantones pequeños de Ecuador: el conductor paga por WhatsApp y el agente verifica placas desde el celular.

decisiones de arquitectura numeradas (AD-1 a AD-16)
16
apps: webhook del bot, API de parqueo, PWA de control, dashboard administrativo
4
dominios puros: estacionamiento, billetera, conversación
3

El problema

Los municipios pequeños del Ecuador manejan el estacionamiento en la calle con tarjetas de papel vendidas en tiendas. El conductor no puede recargar ni extender el tiempo a distancia, el agente verifica a mano y el municipio casi no tiene datos de ocupación ni de recaudación. El proyecto digitaliza esto como complemento de la ordenanza local vigente, no como reemplazo.

Pantallas y diagramas

Cómo funciona

  1. 01ConductorEnvía placa y minutos por WhatsApp
  2. 02Webhook del botVerifica firma, deduplica, ubica el tenant
  3. 03Conversación + dominiosCotiza tarifa, revisa horario
  4. 04Billetera + PostgresDébito atómico, crea la sesión
  5. 05PWA del agenteConsulta de placa, mapa, multa digital

El conductor usa WhatsApp, sin instalar nada. Una máquina de estados del chat maneja iniciar y extender una sesión, consultar saldo y dónde recargar. Cada sesión debita una billetera prepago. El dinero se guarda como centavos enteros en todo el sistema y la billetera no puede quedar bajo cero.

El agente de control usa una aplicación web móvil (PWA) con un mapa en vivo de espacios numerados dibujados como polígonos, consulta de placas y multas digitales con fotos. Tiene cola sin conexión. El personal municipal y las tiendas usan un dashboard administrativo para zonas, tarifas, horarios, recargas, liquidaciones de tiendas y multas.

Las tarifas, horarios y reglas se guardan por tenant como datos, así que un segundo municipio es configuración y no un despliegue. Las recargas en línea están detrás de un puerto de pagos independiente del proveedor, con un flujo de enlaces de pago diseñado pero todavía no activado.

Decisiones técnicas

  • Dinero en centavos enteros

    Regla del proyecto: nunca decimales flotantes para dinero, ni en memoria, ni en la base de datos, ni en la API. El libro de la billetera es atómico y tiene una función de conciliación.

  • Regla de dependencias aplicada

    Los dominios solo importan un núcleo compartido y nunca entre sí. La coordinación entre dominios ocurre en casos de uso de la aplicación. mypy --strict es una compuerta bloqueante.

  • Aislamiento por tenant con Row-Level Security

    RLS en PostgreSQL con un rol de aplicación sin privilegios de superusuario, más pruebas de integración que verifican el aislamiento contra una base real.

  • Detalles de seguridad del bot

    La verificación de firma del webhook falla cerrada, los mensajes se deduplican por id de mensaje de WhatsApp y las respuestas se envían en dos fases después del commit en la base.

  • Decisiones registradas

    El repo guarda 16 decisiones de arquitectura numeradas, un registro de preguntas abiertas y diagramas C4 y de secuencia.

Mi rol

Soy responsable del diseño y escribí el código: dominios, bot, API y las apps de agentes y administración.

Stack

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

Yimmar

ERP y CRM comercial que cubre ventas, fletes, cobros, cheques, compras y liquidaciones de conductores.

funciones de test del backend (contadas con rg 'def test_')
940+
modelos de base de datos en 10 apps
39
procedimientos operativos estándar escritos
8

El problema

Un negocio comercial llevaba ventas, envíos, pagos, cheques y compras en hojas de cálculo. Necesitaba un sistema que registre quién debe qué, aplique los pagos a las ventas más antiguas primero y deje un rastro de auditoría.

Pantallas y diagramas

Cómo funciona

  1. 01Venta en borradorLíneas, descuentos y retenciones
  2. 02FinalizarPasa a pagada o pendiente
  3. 03FleteEnvíos, vehículos, conductores, anticipos
  4. 04CobroPrimero la venta más antigua; cheques seguidos por estado
  5. 05LiquidaciónEl excedente queda como crédito a favor

Una venta nace como borrador y pasa a pagada o pendiente al finalizarse. Los envíos llevan flete, anticipos y liquidaciones. Un pago se distribuye primero a las ventas pendientes más antiguas del cliente y el excedente queda como crédito a favor. Los cheques posfechados siguen pendientes hasta que una tarea diaria los vuelve cobrables, y un cheque protestado revierte su pago. Las compras a proveedores pueden compensarse contra lo que la misma persona adeuda.

Los datos venían de Google Sheets. Una migración nocturna los recarga en un entorno autorizado, registra cada problema por fila y compara los totales contra los resúmenes de origen. 30 modelos de negocio guardan el origen de cada registro y quién lo creó. El backend es Django y DRF con services y selectors. El frontend es un panel de administración en React y Terraform gestiona el almacenamiento y el IAM en la nube.

Decisiones técnicas

  • Migración de datos con trazabilidad

    Cada lote de migración lleva una etiqueta, los problemas se guardan por fila y celda, y los resultados se comparan con los resúmenes de origen. La migración destructiva está bloqueada en producción.

  • Reglas de distribución de pagos

    Los pagos van primero a la venta más antigua, nunca mezclan clientes en una liquidación, y los cheques protestados revierten sus pagos con rastro hasta el cheque original.

  • Procedimientos operativos junto al código

    Ocho procedimientos estándar cubren ventas, fletes, cobros, créditos, compras, migración nocturna y liquidación de conductores, cada uno con controles y referencias al código.

  • Refuerzo de login y cookies

    El login tiene límite de intentos que se bloquea por seguridad si Redis se demora, la autenticación por cookie exige verificación CSRF y el frontend tiene pruebas de navegador contra un backend real.

  • Observabilidad con límite de privacidad

    Los logs son JSON estructurado que excluye cuerpos de petición, IPs, tokens, parámetros SQL y montos, y las métricas de la base de datos se exportan por separado.

Mi rol

Escribí la mayor parte del backend y el Terraform de sus ambientes.

Stack

  • Django
  • PostgreSQL
  • Redis
  • Celery
  • React
  • Terraform
proyecto de maestría · visión por computadoraProyecto de maestría

Reconocimiento facial

Un pipeline modular de identificación facial en tiempo real en Python, con detector, embedder y matcher intercambiables.

backend InsightFace en Apple M2 Max, según el informe (dlib: 18-22 FPS)
12-15 FPS
tiempo de búsqueda en FAISS por consulta, según el informe
<1 ms

El problema

Las demos de reconocimiento facial de curso suelen ser un solo script que mezcla detección, embeddings y comparación, y ninguna parte se puede reemplazar ni probar por separado. El objetivo fue un pipeline limpio que identifique a una persona desde la webcam o un archivo de video y que pueda crecer luego hacia control de asistencia.

Pantallas y diagramas

Cómo funciona

  1. 01EntradaWebcam o archivo de video con OpenCV
  2. 02DetecciónSCRFD: cajas y 5 puntos faciales
  3. 03AlineaciónAlineación de cinco puntos a 112x112
  4. 04EmbeddingArcFace, vector de 512 dimensiones
  5. 05ComparaciónBúsqueda coseno en FAISS; el umbral decide nombre o unknown

Cada cuadro pasa por SCRFD (de InsightFace) para obtener cajas y cinco puntos faciales, una alineación de cinco puntos a un recorte de 112x112, un modelo ArcFace que genera un embedding de 512 dimensiones y un índice FAISS que devuelve la identidad registrada más cercana por similitud coseno. Un umbral configurable (0.35 por defecto) decide entre un nombre y 'unknown'.

El código se organiza con interfaces para detector, alineador, embedder y matcher, y una fábrica de backends. Un segundo backend basado en dlib y face_recognition (embeddings de 128 dimensiones) se conecta a los mismos servicios, lo que permitió comparar ambos sin tocar la lógica de reconocimiento.

El registro de personas y el reconocimiento son scripts y servicios separados, con fuentes de webcam y de archivo de video, overlays en pantalla, logging estructurado, pruebas con pytest y gestión de dependencias con uv.

Decisiones técnicas

  • Primero las interfaces, después los modelos

    Detector, alineador, embedder y matcher son interfaces abstractas. Cambiar InsightFace por dlib significó agregar un backend, no reescribir los servicios.

  • Dos backends, medidos lado a lado

    El informe compara InsightFace y dlib en FPS y tiempo por etapa en el mismo portátil. En la tabla del informe, dlib es más rápido (18 a 22 FPS frente a 12 a 15) e InsightFace usa embeddings más grandes (512 frente a 128 dimensiones).

  • El umbral como problema de ajuste

    El informe barre el umbral de similitud: la precisión es 100% con 0.35 y 0.50, 88.5% con 0.60 y 15.4% con 0.70. El valor por defecto de 0.35 sale de ese barrido.

  • Pensado para la siguiente función

    La estructura deja espacio para seguimiento, tiempos de espera y registro de eventos para asistencia, pero eso quedó fuera del alcance de la primera versión a propósito.

  • Lectura honesta del resultado

    El conjunto de validación tiene 26 imágenes y solo una pertenece a la segunda persona registrada, así que el 100% muestra que el pipeline funciona de punta a punta. No muestra cómo rendiría con un grupo más grande.

Mi rol

Especifiqué la arquitectura, construí el pipeline y ambos backends, ejecuté la evaluación y escribí el informe de la materia.

Stack

  • Python
  • OpenCV
  • ONNX
  • pytest
proyecto de maestría · iotProyecto de maestría, simulado

Riego inteligente

Un sistema IoT que decide cuándo regar pastizales ganaderos, combinando sensores de suelo, pronóstico de lluvia y un modelo Random Forest.

filas horarias de clima histórico usadas en el entrenamiento (ene 2024 a ene 2026)
17,424
importancia de la humedad del suelo, la variable principal
73.7%

El problema

En la época seca, los pastizales de la sierra ecuatoriana se riegan a mano sin criterio técnico, y se desperdicia agua. Una regla fija como 'regar si la humedad baja de 30%' ignora si la lluvia está por llegar.

Pantallas y diagramas

Cómo funciona

  1. 01Sensores ESP32Humedad del suelo, temperatura, humedad (simulación en Wokwi)
  2. 02Broker MQTTBroker en la nube sobre TLS
  3. 03Node-REDAgrega el pronóstico de Open-Meteo, guarda en InfluxDB
  4. 04API Random ForestFlask /predict: regar o no regar
  5. 05Válvula y dashboardLa decisión vuelve al ESP32; datos en InfluxDB para Grafana

Un ESP32 simulado (Wokwi) publica humedad del suelo, temperatura y humedad del aire por MQTT con TLS a un broker en la nube. Node-RED se suscribe, consulta el pronóstico de Open-Meteo cada 30 minutos y guarda las lecturas de sensores y clima en InfluxDB.

Cada minuto Node-RED envía las lecturas actuales a una API en Flask que sirve un clasificador Random Forest. El modelo usa siete variables: humedad del suelo, temperatura, humedad del aire, precipitación, probabilidad de lluvia, hora y mes. Responde regar o no regar, y Node-RED publica la decisión al ESP32 para encender el LED de la válvula.

El modelo se entrenó con dos años de datos horarios de Open-Meteo para el sitio (17.424 filas). Open-Meteo no tenía humedad del suelo para esa ubicación, así que el proyecto la simula con un modelo de balance hídrico, y las etiquetas salen de reglas de riego basadas en guías de la FAO.

Decisiones técnicas

  • Decisiones que consideran el pronóstico

    La probabilidad de lluvia es una entrada del modelo. En la prueba documentada, un suelo con 32% de humedad y 85% de probabilidad de lluvia recibe 'no regar' (94.8% de confianza).

  • Los datos de entrenamiento hubo que construirlos

    Sin una serie de humedad del suelo disponible, el proyecto la generó con una simulación de balance hídrico (ganancia por lluvia, pérdida por evapotranspiración, consumo de las plantas, factor estacional) y la etiquetó con reglas derivadas de la FAO.

  • Modelo servido tras una API pequeña

    El entrenamiento es offline y la inferencia es una llamada HTTP desde el flujo de Node-RED a un servicio Flask. El mismo flujo maneja ingesta, enriquecimiento, almacenamiento y actuación.

  • Arquitectura en capas con servicios administrados

    Mensajería, base de series de tiempo y dashboards usan planes gratuitos de servicios en la nube (broker, InfluxDB, Grafana), así que todo el sistema corre desde un portátil. El diagrama de arquitectura también esboza una capa industrial con PLC y OPC-UA.

  • Límites honestos de las métricas

    La exactitud de 99.8% se mide contra etiquetas generadas con la misma familia de reglas usada para construir los datos, así que muestra que el modelo aprendió esas reglas. No es evidencia de desempeño en campo.

Mi rol

Diseñé la arquitectura, escribí el firmware del ESP32, el flujo de Node-RED y el código de entrenamiento y de la API, y documenté cada componente.

Stack

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

Proyectos anteriores

  • JAAPYSJ01/2023 – 05/2023

    Sistema para una junta de agua potable con gestión de usuarios, lecturas de medidores, facturación y facturación electrónica. Django, Angular, PostgreSQL, AWS, Docker.

  • Carton Plast05/2022 – 03/2023

    Gestión del proceso de fabricación de cajas para una empresa de Ecuador. Java 11, Spring Boot, Kubernetes, Docker, MariaDB.

Lo que uso, agrupado por el trabajo que cumple.

Lenguajes
  • Python
  • Java
  • TypeScript
  • JavaScript
  • SQL
Backend
  • FastAPI
  • Pydantic
  • SQLAlchemy
  • Alembic
  • Django
  • Spring Boot
  • Celery
  • RabbitMQ
Datos
  • PostgreSQL
  • PostGIS
  • Optimización de consultas
  • Migraciones de esquema
  • CDCDebezium
  • Iceberg
  • MariaDB
  • Oracle
Cloud y DevOps
  • AWSECS Fargate, EKS, SNS/SQS, EventBridge, S3, CodePipeline, CodeBuild
  • Terraform
  • Docker
  • Kubernetes
  • GitLab CI
  • ArgoCD
  • Jenkins
Observabilidad y calidad
  • Datadog
  • Loki/Grafana
  • SonarQube
  • pytest
  • Pruebas end-to-end
Frontend
  • Angular
  • React
  • Vue.js
  • Next.js
IA y herramientas
  • Claude Code
  • OpenAI API
  • Jira
  • Confluence
  • Scrum

Una maestría en IA aplicada, una ingeniería de software y varias certificaciones en la nube.

Educación

  • Maestría en Inteligencia Artificial Aplicada

    Universidad Técnica Particular de Loja (UTPL)

    04/2025 – 04/2026

  • Ingeniería de Software

    Universidad de las Fuerzas Armadas – ESPE

    01/2015 – 01/2020

Certificacionesobtenida

  • 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