Saltar al catálogo

Portafolio · Santiago de Chile · Actualizado 30-09-2026

Cristian Amigo

Desarrollador Java backend

Vengo del soporte y la infraestructura de TI. Hoy construyo la parte que no se ve: cómo se guardan los datos, cómo se protege el acceso y cómo dos sistemas se ponen de acuerdo para hablar entre ellos.

01 Aplicaciones Java

El centro del portafolio. De un gestor por consola con pruebas unitarias hasta aplicaciones web con persistencia, control de acceso por roles y API REST.

SpringEduManager

Java 21 · Spring Boot · MVC · Data JPA · JdbcTemplate · Security · JWT · REST · RestTemplate

Aplicación web para gestionar estudiantes, cursos y evaluaciones. Separa controlador, servicio y repositorio; distingue dos roles con permisos distintos y expone una API REST para integrarse con otros sistemas.

Al cerrarlo encontré que la API redirigía al formulario de login en vez de responder 401: un programa que la consumiera no podía distinguir un rechazo de una respuesta válida. Corregido y verificado con 35 comprobaciones sobre la aplicación corriendo. Al cierre del módulo el proyecto quedó con 43 pruebas automáticas y 64 comprobaciones, en H2 y en MariaDB, más JWT en la API, reportes con JdbcTemplate e integración con RestTemplate.

Código y documentación →

20024-09-2026 Proyecto del módulo 6 Spring Boot

CERBERO

Java 21 · POO · polimorfismo · DAO · Servlets · JUnit 5

Freno de despacho para comercio electrónico: ningún pedido sale mientras tenga una gestión abierta. Reúne WhatsApp, Instagram y correo en una bandeja, y cada tipo de ticket decide por sí mismo si bloquea el envío.

Salió de un problema real que viví como cliente: avisé un error de dirección en menos de una hora y el paquete salió igual, porque el mensaje y el sistema de despacho no se cruzaban. No era una falla técnica — nadie preguntaba si había un ticket abierto. CERBERO es esa pregunta convertida en código.

Dos de las cinco pruebas son controles negativos: comprueban que el freno no actúa cuando no corresponde. Un freno que retiene de más es tan inútil como uno que no retiene.

Código y pruebas →

5/5PRUEBAS De un caso real

Biblioteca Digital UNTEC

Java EE · Servlets · JSP · JDBC · DAO · MVC · Tomcat

Aplicación web de préstamos de libros construida con Java EE puro, sin framework: servlets como controladores, JSP como vistas, capa DAO sobre JDBC y sesión propia para el control de acceso. Se empaqueta como WAR y se despliega en Tomcat.

Es el proyecto donde entendí qué hace Spring por debajo. También donde aprendí a revisar el bytecode del WAR antes de entregarlo: compilado con la versión de Java equivocada, la aplicación devolvía error 500 en un servidor distinto del mío.

Código y capturas →

20024-09-2026 Proyecto del módulo 5 Java EE sin framework

Radar de Datos · Guardián PYME

Java · POO · interfaces · consola

Agente de consola que revisa un equipo y diagnostica riesgos de datos antes de que se pierdan: bases sin respaldo, datos personales expuestos, credenciales en texto plano y disco lleno. Seis detectores distintos detrás de una misma interfaz.

Sólo lee metadatos —nombres, tamaños, tipos— y nunca copia ni envía el contenido. Esa restricción se decidió antes de escribir el primer detector, porque una herramienta que revisa datos ajenos tiene que poder explicar qué no hace.

La prueba que importa es la del round-trip: empaquetar y descomprimir una carpeta y comprobar que los datos vuelven byte a byte. En una herramienta de respaldos, demostrar que se restaura es parte del producto. El checksum se valida además contra un SHA-256 calculado por fuera, como control positivo: sin eso, una implementación consistentemente equivocada pasaría igual.

Código →

20024-09-2026 10 pruebas

SmartTask

Java · herencia · polimorfismo · JUnit 5 · Mockito

Gestor de tareas por consola con jerarquía de clases —tareas normales y urgentes que se comportan distinto tras la misma interfaz—, persistencia mediante DAO y pruebas unitarias con informe de cobertura.

El proyecto donde el polimorfismo dejó de ser un concepto de manual: agregar un tipo nuevo de tarea no obligó a tocar el gestor, que era exactamente lo que la teoría prometía y yo no había visto pasar.

Código y pruebas →

20024-09-2026 Proyecto del módulo 4 POO y pruebas

Colmillo

Spring Boot · Spring Security · PostgreSQL · BCrypt

Plataforma de cursos en línea, desplegada y en uso. Dieciséis unidades, sesenta y ocho lecciones y doscientas noventa y una tarjetas de estudio, con registro de avance por persona detrás de autenticación.

Es el proyecto donde más aprendí sobre lo que pasa después de programar: desplegar, migrar la base y darse cuenta de que la primera visita del día tarda dieciséis segundos porque el servidor estaba dormido.

Aplicación en vivo → Presentación → Código →

20024-09-2026 En producción

Panel de gestión

Spring Boot · PostgreSQL · Security · Bean Validation

Treinta y una rutas REST sobre nueve entidades, con autorización por rol probada de punta a punta: un usuario común recibe 403 en las rutas de administración y el administrador las lee. Veintidós pruebas en verde.

Es el único que usa una persona que no soy yo, y eso cambió el trabajo: los errores que importan no son los que rompen la aplicación, sino los mensajes que no se entienden.

Para probarlo: correo demo@portafolio.cl · clave DemoPanel2026 — cuenta de demostración con una cartera ficticia. Cada usuario ve sólo sus propios datos, así que esta cuenta no alcanza ninguna cartera real. El servidor es gratuito y se suspende por inactividad: la primera carga puede tardar cerca de un minuto.

Aplicación en vivo → Código privado

20024-09-2026 Con usuario real

02 Caso de estudio

De todo lo que construí durante el curso, esto es lo que mejor muestra cómo trabajo. No es el proyecto más grande: es el problema que más me costó, y el que más me enseñó.

Caso de estudio · SpringEduManager

El 401 que sí se generaba, y algo se comía tres pasos después

Agosto de 2026 · Java 21 · Spring Boot · Spring Security

La actividad
Cierre del proyecto del módulo de Spring: revisar la aplicación entera antes de presentarla, depurarla y dejarla lista para el portafolio. Gestiona estudiantes, cursos y evaluaciones, con dos roles y una API REST para integrarse con otros sistemas.
El desafío

La aplicación parecía terminada: compilaba, las pruebas pasaban y todos los flujos del navegador funcionaban. Al ejercitarla por HTTP encontré que la API respondía 302 —un redirect al formulario de login en HTML— cuando debía responder 401.

Para un navegador da igual. Para un programa que consuma la API es grave: sigue el redirect, recibe el HTML del login con estado 200, y no puede distinguir un rechazo de una respuesta válida. La promesa de interoperabilidad del proyecto no se sostenía.

Lo difícil no fue verlo: fue que el primer arreglo no funcionó. Declaré un punto de entrada explícito para la API y siguió devolviendo 302.

La solución

La causa real estaba un nivel más abajo, y sólo apareció al seguir la petición completa:

  1. Spring responde el 401 con sendError().
  2. Eso hace que Tomcat re-despache la petición internamente hacia /error.
  3. Ese re-despacho vuelve a pasar por el filtro de seguridad, pero ahora la ruta ya no calza con /api/**.
  4. La atiende la cadena web, que exigía autenticación, y sobrescribe el 401 con un 302.

El 401 sí se generaba; algo se lo comía después. La cabecera WWW-Authenticate sobrevivía porque ya estaba escrita, y esa contradicción fue la pista. El arreglo fue declarar /error como ruta pública: una línea. Encontrarlo fue el trabajo.

Herramientas

Java 21 · Spring Boot · Spring Security · Spring Data JPA · Maven · JUnit para el producto.

Para el diagnóstico, cURL sobre la aplicación corriendo, leyendo cabeceras y códigos de estado. Elegí esa herramienta a propósito: el problema era invisible desde el navegador y desde las pruebas unitarias, porque los dos ocultan el redirect. Sólo un cliente que no lo siga automáticamente lo muestra.

Impacto medido
Medido sobre la aplicación corriendo, antes y después del cambio
ComprobaciónAntesDespués
API sin credenciales302 → HTML401
¿Redirige a una página?síno
Error de validación400 sin detalle400 con campo y motivo
Comprobaciones automatizadas035
Resultado de la suite—35 / 35
Regresiones en los flujos web—0

Las 35 comprobaciones quedaron en un guion reutilizable: la revisión dejó de ser un recorrido manual que hay que recordar y pasa a correrse en un comando. Las cifras de la tabla son las del día del arreglo; el guion siguió creciendo con el proyecto y al cierre del módulo corre 64 comprobaciones, con 43 pruebas automáticas al lado.

Aprendizajes
  1. Un código de estado correcto puede estar generado por el motivo equivocado. Cuatro de mis primeras comprobaciones fallaron y resultaron ser errores del guion de prueba, no del producto. La más peligrosa fue una que pasaba: devolvía 400 por un JSON mal formado, no por la validación. Estaba en verde sin medir nada.
  2. Probar desde el navegador no es probar una API. Son dos clientes distintos y sólo uno de los dos perdona.
  3. Cuando el primer arreglo no funciona, la hipótesis está mal. Insistir con variantes del mismo parche habría gastado horas; seguir la petición paso a paso lo resolvió.
Habilidades aplicadas
Depuración de una cadena de filtros de seguridad · configuración de Spring Security · diseño de respuestas de una API REST · manejo de excepciones con @RestControllerAdvice · pruebas de integración por HTTP · control de versiones con Git · documentación técnica del cambio.
Por qué elegí este caso

Porque muestra lo que quiero que se vea de mí, y no lo que quedó más lucido. Podría haber elegido el proyecto con más pantallas; elegí el problema donde todo indicaba que estaba terminado y no lo estaba.

Vengo de más de trece años en soporte de TI, arreglando lo que otros construían. De ahí me quedó la costumbre de no confiar en que algo funcione porque compila. Este caso es esa costumbre aplicada a mi propio código, y encontrar un error propio, admitir que el primer intento de arreglo falló y dejarlo documentado me parece mejor carta de presentación que un proyecto donde supuestamente nada falló.

03 Servicios y APIs

IRIS

Python · IMAP · API de Telegram · Google Calendar · RAG · tareas programadas

Asistente omnicanal que corre solo: clasifica y archiva correo, arma un resumen diario, responde por Telegram citando la documentación del servicio, registra oportunidades comerciales y agenda reuniones en el calendario. Cuatro tareas programadas lo mantienen vivo sin que nadie abra una sesión.

Es el sistema con más pruebas que escribí: 213, sobre integraciones que fallan de verdad —una casilla que no responde, una API que corta, un archivo a medio escribir—. Varias existen para fijar lo que el sistema no debe hacer: hay una que impide que el clasificador reciba el cuerpo del correo.

Al revisarlo para este portafolio encontré que cuatro pruebas habían dejado de pasar sin que nadie tocara el código: fijaban fechas de julio contra una ventana móvil de catorce días, y se pudrieron solas al llegar agosto. No era un fallo del producto, era el reloj. Corregidas.

Código privado — lee correo personal

213PRUEBAS En producción

radar-mundo

Python · RSS · concurrencia · sin dependencias

Colector de acontecer mundial sin claves de API: reúne titulares de medios internacionales, los agrupa por historia y los ordena por cuántos medios distintos cubren lo mismo.

Lo que más me interesó resolver: un barrido a medias no puede parecerse a uno completo. Si tres de nueve fuentes no responden, el informe lo dice en la primera línea. Cero noticias porque no pasó nada y cero porque se cayó la red se ven igual en casi cualquier colector.

El criterio de corroboración lo corregí después de correrlo contra fuentes reales: la historia mejor rankeada decía «2 medios: BBC Business, BBC Tech» — un solo diario en dos secciones. Estaba inflando justo la señal que dice medir.

Código y pruebas →

18/18PRUEBAS Sin dependencias

AEGIS

Python · FastAPI · PostgreSQL · Docker

Servicio que intercala una aprobación humana antes de que un proceso automático ejecute una acción sensible. Doce rutas documentadas y un control de duplicados para que reintentar una petición no la ejecute dos veces.

La idea salió de un problema concreto: si el cliente reintenta porque se cortó la red, el servidor no puede saber si es una acción nueva o la misma de antes. La respuesta fue que el cliente nombre cada operación.

Presentación → Documentación viva de la API → Cliente →

20024-09-2026 API publicada

04 Front-end y datos

Alke Wallet

HTML · CSS · Bootstrap · JavaScript · jQuery · SQL

Interfaz de una billetera digital, adaptada a móvil, tablet y escritorio, con su modelo de datos relacional documentado en un repositorio aparte: diagrama entidad-relación, script de creación y consultas de ejemplo.

Fue lo primero que publiqué. Lo dejo tal como quedó: sirve para ver de dónde salí, y porque un portafolio que sólo muestra lo último no muestra el recorrido.

Ver la interfaz → Código → Modelo de datos →

20024-09-2026 Proyecto de los módulos 2 y 3 Primer proyecto

05 Herramientas publicadas

Cinco paquetes están en el índice público de Python: cualquiera los instala con pip install, lo que obliga a algo que un repositorio no obliga —que el empaquetado, las dependencias y la versión estén bien—.

06 Contribución a código abierto

Publicar código propio y que a uno le acepten un cambio en el código de otros son cosas distintas. Lo segundo obliga a trabajar con las reglas ajenas y a que alguien más revise el razonamiento.

Kestra

Java · orquestador de flujos · 28.000 estrellas

Un usuario reportó que el disparador de sistema de archivos no ejecutaba nada. La causa resultó estar en la documentación: el ejemplo de configuración indicaba el nombre del paquete, mientras el motor compara contra el nombre completo de la clase. Seguir el ejemplo al pie de la letra equivale a no configurar nada.

Reproduje el fallo en una instancia local, descarté cinco causas candidatas midiendo —no opinando—, usé un disparador de control para probar que el planificador seguía vivo, y ubiqué la causa desensamblando el bytecode del motor de plugins. El cambio final es de una línea. El trabajo fue llegar a ella.

El cambio, ya integrado → El análisis completo →

✓22-09-2026 Integrado en main

Kestra · guía de migración

Documentación · expresiones Pebble

La guía oficial para migrar a la versión 2 enseñaba a comparar una fecha contra un texto. Dentro de esa condición la fecha no es texto, así que la comparación no se puede resolver: quien seguía la guía no obtenía un flujo que se saltea las ejecuciones, sino uno que falla en cada ejecución programada. Kestra acepta el flujo al guardarlo, de modo que nada avisa hasta que corre.

Lo medí sobre un Kestra 2.0.2 local durante 24 ejecuciones, con un flujo de control en paralelo para demostrar que el planificador estaba sano: el de la guía falló las 24 veces y el de control pasó las 24. La corrección convierte ambos lados a instante antes de compararlos, en vez de ordenarlos como cadenas de texto —que para fechas con distinto huso horario da el resultado opuesto.

El cambio, ya integrado → El fallo reportado →

✓22-09-2026 Integrado en main

Kestra · variables del disparador

Documentación · guía de migración

Volviendo sobre la misma página para responderle a un mantenedor, encontré que la tabla de variables disponibles anunciaba una que no existe en ese contexto. Quien la usaba quedaba en un callejón sin salida, a dos párrafos de la expresión que acababa de corregirse.

Es un cambio de una palabra y no tiene mérito técnico: lo dejo acá porque muestra algo que sí lo tiene, y es volver a mirar una página que uno ya dio por corregida. El error original y este vivían en el mismo archivo.

El cambio, ya integrado →

✓23-09-2026 Integrado en main

Kestra · herramienta de migración

Go · pruebas unitarias · herramienta oficial v1 → v2

La herramienta oficial que convierte los flujos de la versión 1 a la 2 generaba, para las condiciones de rango de fechas, la misma comparación que había corregido en la guía: una fecha contra un texto. Todo flujo migrado con ella fallaba en cada ejecución programada, y Kestra lo aceptaba al guardarlo sin advertir nada.

Fue mi primera contribución de código y no de documentación: cambia lo que la herramienta genera para que ambos lados se comparen como instantes, trata aparte los límites que traen sólo una hora del día y agrega las pruebas que lo demuestran. Medido sobre un Kestra 2.0.2 local: la salida anterior fallaba en cada ejecución; la nueva se ejecuta cuando la fecha cae dentro del rango y no se dispara cuando cae fuera. Un segundo cambio corrige dos expresiones del mismo tipo en su documentación. A partir del reporte, el mantenedor agregó a Kestra la función isDateBetween (versión 2.0.4), la sintaxis más simple que habíamos discutido.

El cambio de código, ya integrado → La corrección de su documentación → La conversación con el mantenedor →

✓29-09-2026 Integrado en main

Kestra · descifrado PGP

Java · seguridad · pruebas unitarias · plugin oficial

El plugin que descifra archivos PGP nunca comprobaba el sello de integridad del mensaje. Si alguien alteraba el archivo cifrado en el camino, Kestra lo descifraba igual y daba la tarea por exitosa. Lo demostré con una factura: cambiando un solo bit del archivo cifrado, sin la clave, "Pay 100 USD" pasaba a "Pay 900 USD".

Es mi primera contribución de código en Java a un proyecto ajeno. El cambio verifica el sello en los dos formatos del estándar (MDC y AEAD), rechaza los mensajes antiguos que no lo tienen y distingue una alteración de un problema de clave. Las pruebas nuevas fallan sin el arreglo y pasan con él. Lo probé en un Kestra 1.3.39 antes y después, con mensajes reales de GnuPG: ninguna de 961 alteraciones produjo una salida corrupta. El mantenedor revisó el código, pidió cinco ajustes, repitió la prueba por su cuenta (11 de 11 escenarios) y lo integró.

El cambio, ya integrado → El reporte →

✓30-09-2026 Integrado en main

Kestra · descifrado PGP para varios destinatarios

Java · seguridad · plugin oficial

El mismo plugin de descifrado PGP solo probaba la primera clave de destinatario del mensaje. Cuando un archivo se cifra para varios destinatarios y el tuyo no es el primero de la lista, Kestra fallaba con "No private key found" aunque tuvieras la clave correcta.

Segunda contribución de código al mismo plugin. El cambio recorre todos los destinatarios del mensaje hasta encontrar una clave privada que coincida, en vez de mirar solo el primero. Las pruebas nuevas —un mensaje cifrado para dos destinatarios, descifrado con la clave del segundo— fallan sin el arreglo y pasan con él. Verificado antes y después en un Kestra 1.3.39 local.

El cambio, ya integrado → El reporte →

✓01-10-2026 Integrado en main

Kestra · integración con Sentry

Java · plugin oficial · pruebas unitarias

La tarea que envía eventos a Sentry tomaba el DSN —la cadena de conexión— y lo reenviaba tal cual, fallando con "deprecated userinfo component" en las versiones nuevas de Sentry. Tampoco servía para instancias self-hosted ni para DSN que llevan un prefijo de ruta.

El cambio parsea el DSN en sus partes —clave secreta, host, prefijo de ruta— y arma la petición correctamente, en lugar de mandar la cadena cruda. Las pruebas nuevas fallan sin el arreglo y pasan con él. El mantenedor lo revisó en tres rondas y lo re-probó por su cuenta contra una instancia self-hosted de Sentry antes de integrarlo.

El cambio, ya integrado → El reporte →

✓05-10-2026 Integrado en main

Kestra · CLI de Databricks

Java · plugin oficial

La tarea DatabricksSQLCLI ignoraba dos de sus propias opciones: no cargaba los namespaceFiles que el comando necesita y no usaba el containerImage configurado, corriendo siempre con la imagen por defecto.

El cambio hace que la tarea use de verdad las dos: monta los archivos del namespace y respeta la imagen declarada. Con las pruebas que lo verifican y el control antes y después.

El cambio, ya integrado → El reporte →

✓05-10-2026 Integrado en main

Kestra · filas múltiples en GraalVM

Java · plugin oficial · pruebas unitarias

Las tareas FileTransform documentan rows como la forma de emitir varias filas de salida por cada fila de entrada, pero no funcionaba en ninguno de los tres lenguajes: en Python la tarea fallaba con "No serializer found", y en JavaScript y Ruby rows se ignoraba en silencio y se escribía la fila original.

Había dos causas en el mismo método: rows se buscaba en el valor devuelto por el script en lugar de en los bindings, donde se lee row, y sus elementos se emitían sin convertir. El cambio lo lee de los bindings, convierte cada elemento, lo reinicia antes de cada fila para que no se repita en las siguientes, descarta la fila si la lista viene vacía y falla con un mensaje claro si no es una lista. Las pruebas nuevas de los tres lenguajes fallan en main y pasan con el cambio. Lo verifiqué de punta a punta en un Kestra 2.0.2, antes y después.

El cambio, ya integrado → El reporte →

✓12-10-2026 Integrado en main

Kestra · blueprint SFTP con PGP

Flujo YAML · blueprint oficial · seguridad

Bancos, procesadores de pago y proveedores de nómina suelen dejar archivos cifrados y firmados con PGP en un servidor SFTP. Este blueprint vigila la bandeja de entrada y acepta un archivo solo si se descifra con tu clave, pasa su verificación de integridad, lleva la firma de la clave y el usuario del socio configurados, y su contenido no se había aceptado antes. Todo lo demás va a cuarentena con su motivo.

Es un flujo nuevo, escrito para Kestra Hacktober, que usa un disparador por evento de SFTP y complementa un blueprint existente de verificación PGP. Lo probé en un Kestra 2.0.5 con un servidor SFTP real y mensajes de GnuPG: archivos válidos, sin firma, firmados por otra clave, con un bit alterado y reenvíos del mismo contenido con otro nombre. Cada caso terminó en el resultado esperado. Documenta también lo que la firma no prueba. Lo revisó y aprobó un miembro del equipo de Kestra.

El cambio, ya integrado →

✓12-10-2026 Integrado en main

Kestra · expresiones en el plugin LDAP

Java · plugin oficial · pruebas unitarias

Las tareas Add, Delete, IonToLdif y LdifToIon solo aceptaban inputs escrito como lista literal: pasarles la salida de una tarea anterior con una expresión, como {{ outputs.convertir.urisList }}, hacía que el flujo se rechazara al guardarlo.

Las cuatro tareas pasaron a recibir una propiedad dinámica que se resuelve una sola vez antes de procesar los archivos, con el mismo patrón que ya usaba Modify. Las pruebas nuevas fallan en main y pasan con el cambio, y la suite completa del plugin pasa. A pedido del revisor, la documentación de las cinco tareas muestra además cómo pasar un único archivo. El mantenedor lo verificó en Kestra 1.3 y 2.0 antes de integrarlo.

El cambio, ya integrado → El reporte →

✓14-10-2026 Integrado en main

Kestra · alertas con tildes en Sentry

Java · plugin oficial · pruebas unitarias

La tarea SentryAlert fallaba con un error 400 de Sentry cuando el mensaje tenía tildes, eñes o emojis, en cualquier sistema, y con cualquier texto cuando Kestra corría en Windows. La alerta no llegaba.

Al armar el mensaje a mano, el largo se contaba en caracteres en lugar de bytes y los saltos de línea cambiaban según el sistema operativo, cuando el formato de Sentry pide bytes y saltos de línea fijos. El cambio corrige las dos cosas y suma pruebas que fallan en main y pasan con el arreglo. Lo verifiqué contra sentry.io en Linux y Windows, y el mantenedor lo validó en Kestra 1.3 y 2.0 antes de integrarlo.

El cambio, ya integrado → El reporte →

✓16-10-2026 Integrado en main

Kestra · nombres con tildes al mover archivos

Java · plugin oficial · pruebas unitarias

Al mover archivos por SFTP hacia una carpeta, los nombres con tildes o eñes quedaban corruptos en el servidor (por ejemplo, facturación.txt llegaba como facturación.txt) y la tarea informaba un nombre que no existía.

El nombre se codificaba dos veces al armar la ruta de destino. El cambio arma esa ruta sin la segunda codificación, en los tres lugares donde se repetía el mismo patrón, y suma pruebas que fallan en main con el nombre corrupto y pasan con el arreglo. Lo verifiqué en un Kestra 2.0.5 real, antes y después.

El cambio, ya integrado → El reporte →

✓19-10-2026 Integrado en main

Kestra · archivos con % en el nombre

Java · plugin oficial · pruebas unitarias

Las descargas y el disparador por SFTP fallaban con cualquier archivo que tuviera % en el nombre (por ejemplo, report 100%.txt): la ruta se codificaba dos veces y el archivo "no existía". Además, el filtro regExp nunca encontraba esos archivos y la tarea terminaba en éxito sin hacer nada.

Fueron dos cambios. El primero toma la ruta decodificada al armar la lista de archivos. El segundo compara el filtro contra esa misma ruta, en List, Downloads, Delete y el disparador, y lo deja documentado. Los dos traen pruebas que fallan en main y pasan con el arreglo, con la suite completa en verde. Los mantenedores los validaron en Kestra 1.3 y 2.0, con SFTP, FTP y FTPS, antes de integrarlos.

El primer cambio, ya integrado → El segundo, ya integrado →

✓23-10-2026 Integrado en main

Kestra · nombres con espacios al guardar en disco

Java · plugin oficial · pruebas unitarias

La tarea que guarda varios archivos en una carpeta local escribía my%20report.csv en vez de my report.csv: tomaba el nombre de la dirección interna sin decodificarla.

El arreglo usa la misma función que ya usaba la tarea de un solo archivo y trae una prueba que falla en main y pasa con el cambio. Lo reporté, me lo asignaron, y el mantenedor lo aprobó y lo integró el mismo día en que abrí el cambio.

El cambio, ya integrado →

✓26-10-2026 Integrado en main

Kestra · respuesta a medida en el webhook (documentación)

Documentación oficial · versión 2.1

Documenté la propiedad responseBody del disparador Webhook, que permite responder a quien llama con un texto exacto en lugar del JSON de salidas. Es lo que necesita, por ejemplo, la validación de suscripciones de Microsoft Graph, que espera de vuelta el token tal cual.

La página incluye el ejemplo de Microsoft Graph, el aviso de que esa respuesta la recibe cualquiera que llame al webhook (por lo que no debe llevar secretos) y una nota sobre los tiempos de espera. La propiedad en sí está en revisión en el repositorio principal de Kestra; la documentación quedó integrada en la rama de la versión 2.1.

El cambio, ya integrado → El reporte →

✓13-10-2026 Integrado en la rama 2.1

07 Sistemas propios

Fuera del bootcamp mantengo software que otras personas usan todos los días. Vive en repositorios privados y por eso no lo enlazo, pero es donde aprendí lo que se ve arriba: que un sistema no está listo cuando compila, sino cuando sigue funcionando sin que nadie lo mire. Puedo mostrarlo en funcionamiento a quien lo pida.

Informe diario con usuario real
Servicio que cada mañana reúne datos de varias fuentes, aplica las reglas que definió el propio usuario y le envía un resumen por Telegram. Lleva meses corriendo solo, con una persona que no soy yo dependiendo de que llegue. Ahí aprendí que un error silencioso es peor que una caída: si no llega el mensaje, nadie se entera de que algo falló.
Ensayo general del envío diario: nueve fases, todas correctas, con el emisor bloqueado Verificación de entrega: dos intentos de envío bloqueados, ninguno salió
Veinte minutos antes del envío real, el sistema corre el proceso entero con el emisor bloqueado y comprueba que el mensaje de las 9:00 va a salir. Si algo se rompió durante la noche, se sabe a las 8:40.
Gestión para una empresa que factura
Sistema en Java para la operación de una sociedad real: emisión y control de documentos tributarios, y vigilancia del plazo legal de ocho días para objetar una factura. Si ese plazo se pasa, la empresa pierde el derecho a reclamar; el sistema avisa antes. No es un caso de estudio: si falla, el costo es dinero.
Reloj de ocho días: una factura en plazo, una urgente y una con el plazo ya vencido Calendario tributario generado automáticamente con sus vencimientos La misma factura cargada dos veces: el sistema la reconoce y no la duplica
Spring Boot sobre JPA. El plazo son ocho días corridos y corren desde que el organismo recibe el documento, no desde que llega el correo. Datos de demostración inventados; la lógica y los cálculos son del sistema.
Orquestación y tolerancia a fallos
Plataforma que coordina más de ciento cincuenta tareas programadas, con registro durable de eventos y panel de estado. Lo que más me enseñó: comprobar el efecto y no el código de salida. Una tarea puede terminar con éxito y no haber hecho su trabajo, y eso no se ve en los registros hasta que lo buscás a propósito.
Chequeo de salud: 154 tareas vigiladas y un veredicto que señala dos problemas Comparación entre medir el código de salida y medir el efecto producido
El veredicto no es «todo bien»: es «revisar», y nombra lo que hay que mirar. Nació de encontrar treinta y siete tareas terminando con éxito mientras sus archivos de salida llevaban días sin cambiar.
Servicios de datos
Servicios FastAPI que ingieren datos de fuentes distintas, los normalizan y los publican por una API interna, para que el resto del sistema no dependa del formato de origen.
Asistentes y automatización
Fábrica de servidores MCP, agente de búsqueda de empleo, generador de contenido y un sistema de gestión de conocimiento con recuperación por citas.

Dos de estos trabajos sí son públicos y se pueden abrir:

Extractor de tiendas publicado en Apify
Actor publicado en el mercado de Apify, disponible para cualquiera que lo quiera ejecutar. apify.com/cstamigo/shopify-store-scraper
Servicio de servidores MCP a medida
Desarrollo de servidores MCP publicado como servicio en Fiverr, con los tres paquetes de la sección anterior como muestra de trabajo.

08 Con qué trabajo

Lenguaje y framework
Java 21 · Spring Boot · Spring MVC · Spring Data JPA · Spring Security · Thymeleaf · Maven
Java EE
Servlets · JSP · JSTL · JDBC · patrón DAO · Tomcat · empaquetado WAR
Datos
PostgreSQL · MySQL · MariaDB · H2 · SQLite · modelado relacional y SQL
Pruebas
JUnit 5 · Mockito · cobertura · pruebas de flujo sobre la aplicación corriendo
Front-end
HTML · CSS · JavaScript · Bootstrap · jQuery
También trabajo con
Python · FastAPI · Docker · Git y GitHub

09 Contacto

Busco mi primer puesto como desarrollador Java backend, en Santiago o remoto.