Proyectos
Casos de estudio de ingeniería.
Sistemas backend, arquitecturas en AWS, plataformas de voz y frameworks de integración que he diseñado, lanzado y operado. Cada caso de estudio cubre contexto, enfoque y resultado.
Puente para Carrito de Compras
Convierte una lista de compras del planificador de comidas en una propuesta de carrito revisable — y se detiene ahí, intencionalmente
## Problema Mi planificador de comidas autohospedado sabe lo que necesito comprar. El sitio de mi supermercado sabe cuánto cuestan las cosas, qué promociones aplican y qué está en liquidación. Conciliar eso a mano cada semana es exactamente el tipo de tarea pequeña y recurrente que la automatización debería absorber. ## Mi rol Lo diseñé y construí, incluyendo los límites — que son la mayor parte del diseño. ## Restricciones - Datos de supermercado en español, donde el mismo producto tiene varios nombres y el emparejamiento debe ser lo suficientemente determinista como para revisar. - La lógica de promociones es realmente compleja: ofertas de compra múltiple, descuentos porcentuales y fijos, precios de liquidación, recompensas de lealtad. - Toca dinero. Esa restricción determinó todo lo demás. ## Arquitectura Las necesidades de la lista de compras se normalizan, se emparejan de forma determinista contra el catálogo y se evalúan con las promociones activas para producir una **propuesta de carrito que revisa una persona**. El registro de preferencias locales significa que una corrección que hago una vez se recuerda. Expone sus capacidades a través de Model Context Protocol para que un asistente pueda llamarlo. El dry-run es el valor predeterminado. La mutación del carrito existe pero está deshabilitada detrás de un flag de aplicación explícito. La lista de fuera de alcance está escrita en el proyecto y no es un TODO: sin checkout, sin pago, sin programación de entregas, sin envío de pedidos, sin gasto de saldo de lealtad, sin compras autónomas y sin intentar evadir protecciones anti-bot. ## Resultado Funciona de extremo a extremo en la versión v0.1.0, en uso semanal, con una suite de pruebas basada en fixtures para que la lógica de promociones se pueda verificar sin acceder a un sitio en vivo. ## Qué haría diferente Nada estructural — este es el proyecto donde acerté con los límites desde el primer intento, y la razón es que escribí la lista de fuera de alcance antes que la de funcionalidades. **La automatización que toca dinero debe proponer, no actuar.** El paso de revisión me cuesta treinta segundos a la semana y elimina toda una categoría de fallas contra las que de otro modo tendría que diseñar.
Pipeline de video de formato corto
Automatización local de scripts a render con GPU: CUDA en casa, solo CPU para demo pública
## Problema Producir video de formato corto de manera constante es un problema de agenda disfrazado de uno creativo. Yo quería que la parte mecánica — guion, metraje emparejado, subtítulos, pista musical, render — estuviera automatizada y corriera en hardware que ya tengo, en vez de pagar por minuto en generación alojada. ## Mi rol Este es un **fork del proyecto open-source MoneyPrinterTurbo de harry0703**, que provee el generador principal. Los autores originales tienen la mayoría del historial de commits y merecen el crédito por la base. Mi aporte es la integración en homelab y un conjunto de extensiones: un pool de música licenciada para que el audio esté autorizado para uso, "content packs" que separan la identidad de una cuenta del motor de generación, medición de insights que va más allá del simple conteo de hashtags, y un pool de clips en movimiento limitado por tiempo de reloj. ## Restricciones - Una sola GPU, compartida con un stack local de LLM y un segundo cerebro que ejecuta un briefing matutino. La generación de video no debe dejar sin recursos a los procesos que corren en horario programado. - El demo público no tiene GPU, así que la misma base de código debe poder degradarse a solo CPU. ## Arquitectura De tema a guion, a metraje emparejado, a subtítulos, a música, a short renderizado. Codificación acelerada por hardware y speech-to-text local en la workstation; el mismo stack desplegado solo con CPU detrás del reverse proxy del homelab como demo público. ## Resultado Dos despliegues en vivo desde una sola base de código. Aproximadamente 57 commits propios sobre el upstream, enfocados en la parte de scheduling, licenciamiento y manejo de recursos, más que en el generador en sí. ## Qué haría diferente El bug que recuerdo es uno de manejo de recursos, y me enseñó algo general: un job encolado por un scheduler hereda un presupuesto de duración nuevo, no la ventana de tiempo de reloj restante. Algo que debía correr durante la noche se extendió cuatro horas dentro del horario laboral, acaparando la GPU que el briefing matutino necesitaba. **Un límite de duración nunca puede acotar el reloj** — si un job debe terminar antes de cierta hora, esa hora debe ser la restricción que realmente codifiques.
Homelab Segundo Cerebro
Una capa de memoria compilada que responde preguntas de infraestructura en 2-5K tokens, con citas
## Problema Quería un modelo local, en mi propia GPU, que respondiera preguntas sobre mi propia infraestructura: qué depende de qué, por qué algo está configurado de cierta manera, qué cambió recientemente. La recuperación simple sobre mi documentación costaba entre 30,000 y 50,000 tokens por pregunta y no tenía noción de tiempo: una decisión escrita en junio y una observación de esta mañana regresaban con la misma autoridad. ## Mi rol Diseñé y construí todo solo, en aproximadamente ocho semanas, como 220 pull requests fusionados. ## Restricciones - Debe ejecutarse completamente local. Una GPU de consumo de 24GB que frecuentemente está ocupada con otros trabajos. - Nunca debe convertirse en otra fuente de verdad. Git y las observaciones en tiempo real siguen siendo las fuentes autoritativas; cualquier cosa derivada debe poder eliminarse y reconstruirse. - Debe ser honesto. Un asistente de operaciones que inventa información es peor que no tener asistente, porque no puedes distinguir una respuesta superficial de una incorrecta. ## Arquitectura Una capa **compilada** sobre fuentes autoritativas: un almacén temporal de hechos que cierra hechos en vez de sobrescribirlos, un grafo de relaciones (940 nodos, 1,449 aristas), cápsulas de resumen con sello de generación y un índice vectorial, todo ensamblado en un paquete de contexto limitado por tokens. La recuperación es escalonada y se detiene temprano: exacta, luego léxica BM25, luego cápsula, luego grafo, luego semántica. Los conflictos se resuelven por precedencia fija: tiempo de ejecución sobre hecho sobre cápsula, y un artefacto derivado cuya generación va retrasada respecto a su fuente se marca como obsoleto para que la ruta de consulta regrese a la evidencia original. Encima están las capas de análisis: decaimiento (qué decisiones documentadas ya no son ciertas), validación de corrección contra fuentes en vivo con una línea base comprometida y una puerta de regresión en CI, reconciliación de desviaciones entre estado declarado y observado, detección de puntos ciegos, razonamiento causal y multi-hop, y un ciclo detecta-propone-aprueba-actúa con control, cuyo ejecutor se detiene en un pull request de borrador y nunca fusiona. Durante todo el proceso, una regla: **fundamentado o bloqueado.** La selección y el ranking son deterministas y reproducibles offline. Un modelo de lenguaje puede redactar un resultado ya seleccionado y nunca puede agregar, eliminar o reclasificar uno. ## Resultado Aproximadamente un 99% de reducción en tokens por consulta, validado en modo sombra contra el enfoque anterior antes de la promoción. 31 herramientas de línea de comando, 93 herramientas de solo lectura expuestas sobre Model Context Protocol, y 293 pruebas solo sobre el subsistema de memoria dentro de una capa de operaciones en Python de 116,000 líneas que lleva 1,865 pruebas. ## Qué haría diferente Construí el almacén de hechos antes de construir lo que audita si los hechos siguen siendo ciertos. Esas dos cosas deben ir en el mismo release: una memoria sin auto-auditoría es solo un caché confiado. Y habría separado “qué observé” de “qué concluyo” en el contrato del detector desde el inicio; mezclarlos produjo una tanda de alertas bien citadas que eran el mismo bug.
Operaciones de Homelab
14 hosts, ~110 contenedores y la disciplina documental para mantenerlos legibles
## Problema Un homelab deja de ser un hobby en el momento en que tu casa depende de él. El mío cruzó esa línea: DNS, automatización del hogar, medios, almacenamiento de documentos y SSO relacionado con contraseñas, todo corre ahí. En ese punto, el modo de falla ya no es "un servicio está caído". Es "ya no recuerdo por qué esto está configurado así, y la persona que lo sabía era yo hace seis semanas". ## Mi rol Arquitecto y operador único. Cada host, cada archivo compose, cada registro de decisiones. ## Restricciones - Operador único, solo en las noches y fines de semana. Cualquier cosa que requiera mi atención en un horario fijo eventualmente no la recibirá. - Dependientes reales. Las fallas de DNS y automatización del hogar son visibles en la casa en cuestión de minutos. - Sin escape a la nube para lo que realmente importa: el punto es que todo corra aquí. ## Arquitectura Un nodo Proxmox hospeda VMs Docker separadas por propósito: infraestructura compartida (reverse proxy, SSO, métricas, logs, uptime, notificaciones push), un backend operacional (Git autohospedado, runners de CI, un ledger de eventos append-only), aplicaciones de conocimiento y documentos para humanos, y un sandbox para experimentos y demos públicas. Un NAS almacena la persistencia y los respaldos, y también funciona como el segundo nodo DNS. Una estación de trabajo con GPU separada corre el stack local de modelos. El ingreso es a través de un solo reverse proxy con forward-auth al frente de todo lo interno. DNS corre en alta disponibilidad en dos nodos con sincronización de políticas unidireccional desde una fuente de verdad designada, así que el réplica nunca puede desviarse y convertirse en una segunda opinión. Todo está respaldado y priorizado en Git: inventario, archivos compose, runbooks y registros de decisiones viven en un solo repo, y la flota en ejecución se compara contra ese repo por colectores de solo lectura que no cambian nada y registran sus propios errores en vez de caerse. ## Resultado 14 hosts y alrededor de 110 contenedores, documentados en 368 páginas markdown: 21 registros de decisiones de arquitectura, 131 runbooks — incluyendo un procedimiento de restauración para cada servicio con estado — y postmortems lo suficientemente honestos como para incluir los fixes que no funcionaron. ## Qué haría diferente Escribir los registros de decisiones desde el primer día en vez de reconstruirlos después. La parte costosa nunca fue la configuración; fue recuperar el razonamiento detrás de una configuración que ya había olvidado. Y comprar el UPS antes de que una pérdida de energía sucia te enseñe por las malas.
Este Sitio — Portafolio como Producto
Aplicación Next.js 14 con asistente RAG, pipeline i18n de LLM y currículum PDF multilingüe
## Problema Un currículum afirma la senioridad; no puede demostrarla. Quería un portafolio que fuera en sí mismo un sistema de producción: cada función una muestra de trabajo verificable que un reclutador pueda clicar, leer e interrogar. ## Mi rol Todo: producto, diseño, backend, frontend y operaciones — diseñado, construido y operado en solitario. ## Restricciones - El despliegue serverless no debe agotar las conexiones de Postgres, y las páginas públicas deben permanecer cacheables ISR detrás de estrictos encabezados de seguridad. - El contenido se envía en inglés, español y alemán sin necesidad de autorizar cada publicación tres veces. - El asistente de IA responde únicamente a partir de mi contenido real, dentro de un presupuesto diario de costos, y debe resistir la inyección de comandos. ## Arquitectura Next.js 14 App Router con TypeScript y Prisma sobre Postgres. El asistente es de recuperación aumentada: el contenido del sitio se fragmenta y se incrusta en pgvector, se recupera por pregunta y se responde con modelos de OpenAI — con defensas contra inyección de comandos, límites de tasa por IP, un presupuesto diario de tokens y reindexación impulsada por cron. Un pipeline de traducción LLM genera las locales en español y alemán con detección de obsolescencia basada en hash. La superficie de administración se encuentra detrás de credenciales de NextAuth más claves de WebAuthn bajo un CSP basado en nonce. El PDF del currículum se renderiza del lado del servidor con PDFKit en los tres idiomas. La búsqueda del blog utiliza la búsqueda de texto completo de Postgres; cada ruta emite metadatos canónicos/hreflang, JSON-LD, imágenes de Open Graph por ruta, un sitemap, RSS y llms.txt. ## Resultado El sitio que estás leyendo: un blog multilingüe y estudios de caso, un asistente de IA basado en mi propia escritura, y un currículum PDF listo para reclutadores — todo generado a partir de una única base de código y operado en producción. ## Lo que haría diferente Diseñaría el enrutamiento i18n desde el principio — adaptarlo significó que cada página pública ahora existe en dos árboles de rutas. Y pondría las métricas del proyecto en el esquema en lugar de en prosa para que puedan renderizarse como chips destacados.
Framework de Integración Omnicanal — Caso de Estudio Laboral
Un único método Factory para Slack, Teams, Google Chat y Webex en Resolve Systems
## Problema Cada canal de chat empresarial — Slack, Microsoft Teams, Google Chat, Webex — comenzó como una integración personalizada. Cuando el código de fulfillment hace bifurcaciones según el canal, estás manteniendo N productos que se van separando bajo presión de tiempo. ## Mi rol Diseñé e implementé el framework de integración basado en el Método de Fábrica y las utilerías compartidas de pruebas sobre las que se construye cada adaptador de canal. Esta es la columna vertebral contra la que se escribe cada canal en la plataforma, incluyendo voz. El objetivo de diseño fue una interfaz que no diga nada sobre *cuándo* llega una respuesta: los canales de chat son asincrónicos, una llamada telefónica es sincrónica y bloqueante, y ambos son adaptadores detrás de la misma fábrica. Una abstracción que solo funciona para canales que comparten un modelo de tiempo no es un framework de integración, es un framework de chat. ## Restricciones - Los canales difieren en modelos de autenticación, formatos de payload y límites de tasa — las diferencias tenían que permanecer dentro de los adaptadores. - Las integraciones existentes en producción tenían que seguir funcionando mientras el framework las reemplazaba por debajo. - Los patrones seguros por defecto (autenticación, MFA, manejo de secretos) eran requisitos indispensables para clientes empresariales. ## Arquitectura Los adaptadores de canal normalizan cada mensaje entrante en un sobre común; un Método de Fábrica instancia el adaptador correcto por canal; el código de fulfillment opera sobre intenciones y entidades y nunca hace bifurcaciones según el canal. Las utilerías compartidas de pruebas le dan a cada nuevo adaptador la misma suite de conformidad desde el primer día. ## Resultado Slack, Microsoft Teams, Google Chat y Webex corren todos sobre una sola columna vertebral, y cada nuevo canal cuesta menos que el anterior. Cero vulnerabilidades críticas mantenidas en estas integraciones empresariales durante dos años. ## Lo que haría diferente Definir el esquema de intenciones compartido desde el día uno y versionarlo como una API — fusionar definiciones divergentes de intenciones después es el camino costoso. Construir el adaptador al final: primero lograr el sobre compartido y la historia de pruebas bien definidos.
Agente de Voz IVR — Estudio de Caso Laboral
Integrando conversaciones asíncronas de chatbots en flujos IVR de Amazon Connect en Resolve Systems
## Problema El agente virtual de soporte de Resolve Systems fue diseñado para chat asincrónico, donde una respuesta en dos segundos se siente rápida. Una llamada telefónica es un canal bloqueante y en tiempo real: los usuarios escuchan cada salto en la cadena como silencio. La plataforma necesitaba un canal de voz sin reescribir el backend del chatbot existente. ## Mi rol Diseñé la arquitectura de principio a fin y la implementé: enrutamiento de intenciones, orquestación de speech-to-text y text-to-speech a través de Amazon Connect, Amazon Lex, AWS Lambda y Polly, y la transferencia al fulfillment existente. La parte que lo hizo posible no fue específica de voz. Diseñé y construí la capa de integración con Factory Method sobre la que corre la plataforma del chatbot: cada canal se normaliza en un solo sobre, una fábrica instancia el adaptador correcto, y el fulfillment opera sobre intenciones y entidades sin ramificarse por canal. La propiedad que importó es que **la abstracción es neutral respecto al tiempo**. Slack, Teams, Google Chat y Webex son asincrónicos; una llamada telefónica es sincrónica y bloqueante. Ambos son adaptadores. La interfaz no hace ninguna afirmación sobre si una respuesta llega en dos segundos o doscientos milisegundos, por lo que la voz se agregó sobre una interfaz que ya existía en lugar de bifurcar la infraestructura de producción para adaptarla. Definir bien ese límite primero es la razón por la que el canal más difícil pudo agregarse. ## Restricciones - Los presupuestos de latencia en voz son implacables: los usuarios notan pausas de menos de un segundo que los usuarios de chat nunca ven. - El backend asincrónico de fulfillment era infraestructura de producción compartida con todos los canales de chat; no podía bifurcarse para voz. - Los requisitos de seguridad empresarial — autenticación, manejo de secretos — aplicaban a cada nuevo salto. ## Arquitectura Amazon Connect captura audio y lo transmite a Lex para resolución de intenciones; un puente Lambda traduce entre el flujo IVR sincrónico y el backend asincrónico del chatbot, luego Polly sintetiza respuestas de vuelta a la llamada. Las consultas posteriores comienzan especulativamente al inicio de la expresión en lugar de después de la resolución de intención, y el recorrido del usuario se correlaciona a través de Connect, Lex, Lambda y APIs posteriores para que los trazos sobrevivan el límite del canal. ## Resultado Se lanzó como un canal de producción de la plataforma del agente virtual de soporte que ayudó a reducir el volumen de llamadas al help-desk entre 40 y 60%. ## Qué pasó después El canal se movió fuera del stack de AWS a Vapi, que hace más orquestación en tiempo real como plataforma que el enfoque de primitivas ensambladas. Esto vale la pena decirlo claramente: después del esfuerzo de construir el puente, una plataforma de voz hecha a la medida resultó ser el mejor agente. La evaluación de otras plataformas de voz sigue en curso y corre en paralelo con ese trabajo, porque las restricciones decisivas en este tipo de sistemas rara vez son solo técnicas. Lo que sobrevivió a la migración es la parte que seguiría construyendo igual: la capa de integración subyacente. Como el fulfillment nunca se ramificó por canal, reemplazar el front end de voz no lo tocó. ## Qué haría diferente Diseñar la correlación entre servicios desde el día uno en lugar de agregarla durante el endurecimiento — los trazos que mueren en un límite de canal no son trazos. Y presupuestar desde temprano la semántica de cancelación por interrupción: cada síntesis en curso debe ser cancelable, lo que cambia los requisitos de idempotencia aguas abajo.