Estudio Ingeniería en Tecnologías Computacionales en el Tec de Monterrey y soy estudiante de la Apple Developer Academy en Nápoles, Italia. Cuando llegué a la Academy sabía programar, tenía bases de desarrollo web y desarrollo full-stack, pero nunca había construido un producto real. Fuera de mi experiencia en una startup no había tenido que pensar en usuarios, en infraestructura, en cómo escalar lo que estaba escribiendo o en cómo trabajar con un equipo multidisciplinario donde no todos hablan tu idioma.
Este blog es una retrospectiva honesta de lo que he aprendido en este camino. No es un tutorial ni un resumen técnico. Es mi reflexión como ingeniero de software sobre las decisiones, los errores y los aprendizajes que me han marcado durante mi experiencia en la Academy.
1. Technical Growth
El camino de mi desarrollo técnico en este tiempo en la Apple Developer Academy ha sido muy amplio. Desde el principio supe que quería desarrollar productos, no proyectos. Esa fue siempre mi intención, y esa misma intención ha sido lo que me ha llevado a explorar muchas herramientas, desarrollar habilidades y crecer como ingeniero de software.
El stack que construí
Además del lenguaje Swift y el framework SwiftUI, he podido explorar frameworks de Apple que nunca imaginé tocar tan pronto. En mi último proyecto, Unnoticed, lideré la decisión de implementar encriptación de extremo a extremo para las fotografías de los usuarios. Se identificó la necesidad, se definieron los requerimientos de seguridad y se asignó la tarea. Entender el problema a nivel arquitectónico, saber qué se necesita y por qué, y delegar la implementación de manera efectiva es una habilidad importante que he aprendido últimamente.
Del lado del servidor, construí un stack completo: Node.js con Express.js, PostgreSQL como base de datos, Redis para colas de trabajo, autenticación con JWT, y almacenamiento de archivos en Cloudflare R2. Diseñé la API REST y la lógica del ciclo de vida del producto.
También desarrollé landing pages para las aplicaciones en Astro, un framework que descubrí este año y que me atrapó por completo. Está realmente optimizado para páginas que sirven contenido estático y para SEO. Incluida la landing de Unnoticed en nuestro monorepo.
Un mundo de tecnologías que no están directamente relacionadas con el entorno de Apple, pero que sin lugar a dudas suman a mi stack y a mi experiencia como ingeniero.
La arquitectura como pilar
La arquitectura de software se ha vuelto un pilar fundamental para mí, sobre todo en esta era donde puedes tener varios agentes de IA programando al mismo tiempo. Planear los requerimientos de software, definir estándares de calidad, configurar entornos de desarrollo locales y en la nube, diseñar pipelines de CI/CD: los considero indispensables para construir software de calidad.
En Unnoticed, por ejemplo, una de las primeras decisiones que tomé fue estructurar el proyecto como un monorepo. ¿Por qué? Porque teníamos tres piezas que necesitaban compartir configuración y mantenerse sincronizadas: la app de iOS, el backend y la landing page. Un monorepo nos permitió tener un solo repositorio donde viviera todo, compartir tipos entre proyectos, y tener un solo punto de verdad para la configuración del entorno de desarrollo, Docker y CI/CD. Además, facilitó que cualquier miembro del equipo pudiera levantar todo el entorno de desarrollo con un solo comando.
Para el flujo de trabajo del equipo, implementamos Scrum usando Jira para gestionar sprints, organizar el backlog y dar seguimiento a las tareas. Tener un proceso estructurado nos ayudó a dividir el trabajo, priorizar features y mantener visibilidad sobre lo que cada quien estaba haciendo. También configuramos flujos de CI/CD, lo que nos permitió automatizar validaciones antes de integrar código y mantener la calidad del repositorio.
Entender que la arquitectura de software ha sido y será siempre indispensable para construir software, especialmente en estos tiempos de IA, ha sido un parteaguas para mí. El vibe coding está en tendencia y, a decir verdad, es muy bueno para llevar un prototipo decente a la realidad en tiempo récord.
Pero el problema recae precisamente en que lo que construyen esos agentes son prototipos. No están pensados para ser escalables, mantenibles, fiables y seguros. O por lo menos, no los que he visto hasta ahora.
Ahí es donde la arquitectura de software y el pensamiento crítico se vuelven cruciales. Entender, sino desde el principio, lo más pronto posible, el código que tienes y el código que te genera la IA. De otra manera el código se siente frágil y es complicado añadir una nueva feature o incluso arreglar un bug sin romper producción. Sigo aprendiendo cómo sacar el máximo provecho a la IA como herramienta. Y conforme avanzo en mi carrera me doy cuenta de que cada vez más quiero entender el bajo nivel de la tecnología que hay detrás para construir software.
El contraste con la universidad
El contraste de la universidad con la Academy se resume en esto: antes, construir software para mí era un proyecto escolar, algo limitado al alcance de la materia que nadie iba realmente a usar. Ahora construir software significa pensar en el futuro de lo que estoy construyendo, en que será usado por personas con necesidades reales.
Hay tantas tecnologías allá afuera, tantos frameworks, tantos lenguajes, tantas cosas que aprender. Después de recorrer este camino me doy cuenta de que quiero aprender y construir con una intención clara. No importa cuántas tecnologías nuevas, conceptos nuevos o herramientas nuevas tenga que aprender, porque mientras entienda el porqué de lo que estoy construyendo, sé que me empujará a hacerlo de la mejor manera. Y siento también que me hará mejor ingeniero.
2. Engineering Challenges & Decisions
Mi caso más reciente y más completo para hablar de retos de ingeniería es Unnoticed. Es una app colaborativa de fotografía: los usuarios crean viajes, toman fotos en rondas basadas en prompts creativos asignados por rol, y votan por las mejores tomas. Construirla me enfrentó a decisiones técnicas que nunca había tenido que tomar en un proyecto real.
Requerimientos y arquitectura desde cero
Antes de escribir una sola línea de código, me senté con el equipo a levantar requerimientos. ¿Qué experiencia queríamos crear? ¿Qué necesitaba el usuario? ¿Qué era técnicamente viable en el tiempo que teníamos? De ahí salió la definición de la arquitectura: una app nativa en SwiftUI con patrón MVVM, un backend en Express con Prisma y PostgreSQL, y almacenamiento de imágenes en la nube.
La decisión de usar un monorepo fue mía. La propuse porque quería que el equipo tuviera un solo lugar donde viviera todo. Eso eliminó la fricción de mantener múltiples repositorios sincronizados y facilitó que todos entendieran la arquitectura completa del proyecto.
Cloudflare R2 sobre Amazon S3
Para el almacenamiento de imágenes necesitábamos un servicio de almacenamiento en la nube que fuera accesible en precio y fácil de configurar. Elegí Cloudflare R2 sobre Amazon S3 por tres razones:
- Precio: R2 no cobra por egreso de datos (bandwidth), que es donde S3 se vuelve caro rápido cuando sirves imágenes a usuarios.
- Accesibilidad: La configuración es más sencilla, la interfaz de Cloudflare es más intuitiva, y la curva de aprendizaje es menor comparada con el ecosistema de AWS.
- Compatibilidad: R2 es compatible con la API de S3, lo que significa que si algún día necesitamos migrar, el cambio es mínimo.
El flujo de subida de fotos lo diseñamos para que el servidor nunca tocara los bytes de la imagen: el cliente solicita un permiso temporal de subida, sube directamente al almacenamiento, y luego le notifica al backend. Esto reduce la carga del servidor y mejora la velocidad para el usuario.
El caché de imágenes
Un reto importante de rendimiento fue la carga de imágenes. En una galería con decenas de fotos, descargar cada imagen desde la red cada vez que el usuario la veía hacía que la experiencia fuera lenta e inaceptable.
La solución fue implementar un sistema de caché inteligente: la primera vez que se descarga una foto, se guarda en caché (tanto en memoria como en disco). Las siguientes veces que el usuario ve esa foto, se carga instantáneamente desde el caché sin necesidad de volver a descargar.
También implementamos deduplicación de solicitudes: si varias partes de la app piden la misma imagen al mismo tiempo, solo se hace una descarga y todas comparten el resultado. Esto mejoró significativamente la experiencia de usuario al navegar por la galería de fotos.
Encriptación de extremo a extremo
Uno de los retos más ambiciosos del proyecto fue la encriptación de fotos. La premisa que definimos como equipo era simple: el servidor no debe poder ver las fotos de los usuarios. Solo los participantes del viaje deberían poder verlas.
Mi rol aquí fue identificar esta necesidad de privacidad, definir los requerimientos a alto nivel y asignar la tarea. La implementación la llevó un compañero del equipo, pero la decisión arquitectónica de que el servidor fuera zero-knowledge (que solo facilitara el intercambio de llaves sin nunca aprender su contenido) fue una decisión de equipo que impulsé.
El resultado: las fotos se cifran antes de salir del teléfono y lo que llega al almacenamiento en la nube es indescifrable sin la llave del viaje. Solo los dispositivos de los participantes pueden ver las imágenes. Es una de las features de las que más orgulloso estoy como líder técnico, porque demuestra que saber delegar y definir requerimientos claros es tan importante como implementar.
Con la encriptación en su lugar, el sistema de caché que ya teníamos se adaptó para manejar fotos encriptadas: descifra la imagen una sola vez y almacena el resultado descifrado, evitando repetir el proceso costoso de descifrado en cada vista.
La decisión de no tener login
Una decisión de producto que también fue de ingeniería: decidimos no implementar login ni registro en el prototipo. Queríamos que la experiencia fuera lo más fluida posible, que el usuario pudiera crear un viaje o unirse a uno en segundos. La limitación es obvia: menos control sobre la información del usuario y la persistencia de su identidad. Pero para un prototipo cuyo objetivo era validar la experiencia de juego, priorizar la velocidad de onboarding fue la decisión correcta.
Reglas de desarrollo y calidad de código
Los errores más comunes y frecuentes no fueron bugs técnicos, fueron de colaboración y calidad de código. Después de demasiados merge conflicts que nos costaban horas, propuse al equipo establecer reglas de desarrollo que documentamos en el repositorio:
- Siempre sincronizar tu rama con el repositorio remoto antes de subir cambios.
- Resolver conflictos prefiriendo la combinación de ambas secciones sobre reemplazar una completamente.
- Seguir estándares de calidad en el código para no afectar el trabajo de otros.
Seguimos trabajando en eficientizar este proceso y mejorarlo, pero creo que todos nos hemos llevado una buena experiencia aprendiendo a desarrollar en equipo de verdad.
3. Collaboration & Product Development
Trabajar en equipos dentro de la Academy ha sido de las experiencias más enriquecedoras que he tenido hasta ahora. A decir verdad, también un reto muy grande.
La barrera del idioma
Empezando por la barrera del idioma. Si bien la comunicación es en inglés, al no ser mi idioma natal ni el de mi día a día, no siempre me fue fácil comunicar mi punto de vista u opiniones de manera asertiva o clara. Pero fue un buen reto y sin duda una buena práctica para los futuros equipos internacionales que deseo tener.
Liderar desde la técnica y más allá
En Unnoticed tomé el liderazgo del equipo, no solo en el aspecto técnico sino en la organización general del proyecto. Fui quien propuso la arquitectura del monorepo, quien configuró el entorno de desarrollo con Docker y el script de inicialización, quien definió las convenciones de Git y las reglas de calidad de código, y quien estableció Scrum como metodología de trabajo con Jira.
Pero liderar no fue solo tomar decisiones técnicas. Fue también facilitar las reuniones de sprint, ayudar a dividir el trabajo de manera que cada quien pudiera contribuir desde sus fortalezas, mediar cuando había desacuerdos sobre la dirección del producto, y asegurarme de que el equipo se mantuviera motivado y alineado. Aprendí que ser el líder técnico no significa hacer todo: significa crear las condiciones para que el equipo pueda trabajar bien.
Un equipo internacional
Compartir mi cultura con mis compañeros y aprender de las suyas fue realmente increíble. Conocer personas de Italia, Irán, Brasil, Rusia, Vietnam, India, Egipto, España, y más. Cada uno traía una perspectiva distinta no solo sobre la tecnología, sino sobre cómo se resuelven problemas, cómo se comunican ideas y qué significa “calidad”.
Perspectivas multidisciplinarias
Compartir perspectiva con personas que no son de mi carrera también me permitió ampliar mi punto de vista. Fue fundamental tener diseñadores y perfiles de negocio para tomar decisiones sobre la comunicación de la app, el flujo de usuario, modelos de negocio y organización del proyecto. Un ingeniero solo no construye un buen producto. Un equipo sí.
La diferencia con la universidad
La diferencia más grande con esta experiencia en relación a la universidad es que aquí no había nadie que te estuviera siguiendo ni que te diera las fechas específicas de entrega y las pautas para esta. Si bien teníamos una presentación final, lo más retador fue hacer un plan con el equipo y apegarse a él y a las fechas que nosotros mismos definimos. Esa responsabilidad compartida es lo que más se parece al mundo real.
4. Professional Growth
De proyectos escolares a productos reales
Mi forma de trabajar ha cambiado fundamentalmente. Antes pensaba en entregar una tarea. Ahora pienso en construir algo que alguien va a usar. Esa diferencia parece sutil pero cambia todo: cómo diseñas la arquitectura, cómo manejas errores, cómo piensas en rendimiento, cómo documentas tu código.
Aprender a tomar decisiones con información incompleta
En la universidad, los problemas vienen con especificaciones claras. En la Academy, tuve que aprender a tomar decisiones con información incompleta y a vivir con el trade-off. ¿R2 o S3? ¿Login o sin login? ¿Monorepo o multi-repo? ¿Cifrado de extremo a extremo o confiar en el servidor? Ninguna de estas decisiones tiene una respuesta “correcta”. Todas tienen trade-offs, y aprender a evaluarlos rápidamente y seguir adelante ha sido uno de los aprendizajes más valiosos.
Comunicar ideas técnicas
Aprendí que saber programar no es suficiente. Necesitas saber comunicar lo que estás construyendo y por qué. Documentar la arquitectura del proyecto, escribir especificaciones técnicas para que un compañero pudiera implementar exactamente lo que el frontend esperaba, o explicar a un diseñador por qué una decisión técnica afectaba la experiencia de usuario. Todo eso es parte del trabajo de un ingeniero.
La IA como herramienta, no como reemplazo
He usado herramientas de IA como Claude Code a lo largo de mis proyectos en la Academy. En iDeary, lo usé para generar la estructura del onboarding a partir de mi código existente. En Unnoticed, para iterar sobre implementaciones del backend y el protocolo de encriptación. Pero en cada caso, la lógica del producto, las decisiones de arquitectura y el diseño de la experiencia fueron míos y del equipo.
La lección que me llevo es que la IA es extraordinariamente útil para ejecutar más rápido, pero no reemplaza el criterio de ingeniería. Saber qué construir, por qué y cómo estructurarlo para que sea mantenible sigue siendo trabajo humano. Y creo que eso será cada vez más valioso, no menos.
Lo que sigue
Estoy en la recta final de la Apple Developer Academy y salgo con un stack técnico mucho más amplio del que llegué, pero sobre todo con una mentalidad distinta. Quiero seguir construyendo productos que resuelvan problemas reales, quiero seguir liderando equipos técnicos, y quiero seguir profundizando en el bajo nivel de la tecnología que uso todos los días. Porque mientras entienda el porqué de lo que estoy construyendo, sé que me empujará a hacerlo de la mejor manera.