I study Computer Science and Technology at Tec de Monterrey and I’m a student at the Apple Developer Academy in Naples, Italy. When I arrived at the Academy I knew how to code, I had a background in web development and full-stack development, but I had never built a real product. Outside of my experience at a startup, I had never had to think about users, infrastructure, how to scale what I was writing, or how to work with a multidisciplinary team where not everyone speaks your language.

This blog is an honest retrospective of what I’ve learned along the way. It’s not a tutorial or a technical summary. It’s my reflection as a software engineer on the decisions, mistakes, and learnings that have shaped me during my experience at the Academy.


1. Technical Growth

My technical development during my time at the Apple Developer Academy has been incredibly broad. From the very beginning I knew I wanted to build products, not projects. That was always my intention, and that same intention is what has driven me to explore many tools, develop skills, and grow as a software engineer.

The stack I built

Beyond the Swift language and the SwiftUI framework, I got to explore Apple frameworks I never imagined touching so soon. In my latest project, Unnoticed, I led the decision to implement end-to-end encryption for user photos. The need was identified, security requirements were defined, and the task was assigned. Understanding the problem at an architectural level, knowing what’s needed and why, and delegating implementation effectively is an important skill I’ve been learning lately.

On the server side, I built a complete stack: Node.js with Express.js, PostgreSQL as the database, Redis for job queues, JWT authentication, and file storage on Cloudflare R2. I designed the REST API and the product lifecycle logic.

I also developed landing pages for the apps in Astro, a framework I discovered this year and that completely won me over. It’s truly optimized for pages that serve static content and for SEO. Including Unnoticed’s landing page in our monorepo.

A whole world of technologies not directly related to the Apple ecosystem, but that undeniably add to my stack and my experience as an engineer.

Architecture as a pillar

Software architecture has become a fundamental pillar for me, especially in this era where you can have multiple AI agents coding at the same time. Planning software requirements, defining quality standards, setting up local and cloud development environments, designing CI/CD pipelines: I consider them indispensable for building quality software.

In Unnoticed, for example, one of the first decisions I made was to structure the project as a monorepo. Why? Because we had three pieces that needed to share configuration and stay in sync: the iOS app, the backend, and the landing page. A monorepo allowed us to have a single repository where everything lived, share types between projects, and have a single source of truth for the development environment, Docker, and CI/CD configuration. It also made it easy for any team member to spin up the entire development environment with a single command.

For the team’s workflow, we implemented Scrum using Jira to manage sprints, organize the backlog, and track tasks. Having a structured process helped us divide work, prioritize features, and maintain visibility on what everyone was doing. We also set up CI/CD flows, which allowed us to automate validations before integrating code and maintain repository quality.

Understanding that software architecture has been and will always be indispensable for building software, especially in these times of AI, has been a turning point for me. Vibe coding is trending and, to be fair, it’s great for bringing a decent prototype to life in record time.

But the problem lies precisely in the fact that what those agents build are prototypes. They’re not designed to be scalable, maintainable, reliable, and secure. Or at least, not the ones I’ve seen so far.

That’s where software architecture and critical thinking become crucial. Understanding, if not from the start, as soon as possible, the code you have and the code AI generates for you. Otherwise the code feels fragile and it’s hard to add a new feature or even fix a bug without breaking production. I’m still learning how to get the most out of AI as a tool. And as I progress in my career, I realize more and more that I want to understand the low-level technology behind the software I build.

The contrast with university

The contrast between university and the Academy boils down to this: before, building software for me was a school project, something limited to the scope of a course that nobody was really going to use. Now building software means thinking about the future of what I’m building, about it being used by people with real needs.

There are so many technologies out there, so many frameworks, so many languages, so many things to learn. After walking this path, I realize that I want to learn and build with a clear intention. It doesn’t matter how many new technologies, new concepts, or new tools I have to learn, because as long as I understand the why behind what I’m building, I know it will push me to do it the best way I can. And I also feel it will make me a better engineer.


2. Engineering Challenges & Decisions

My most recent and complete case to talk about engineering challenges is Unnoticed. It’s a collaborative photography app: users create trips, take photos in rounds based on creative prompts assigned by role, and vote for the best shots. Building it confronted me with technical decisions I had never had to make in a real project.

Requirements and architecture from scratch

Before writing a single line of code, I sat down with the team to gather requirements. What experience did we want to create? What did the user need? What was technically viable in the time we had? From there came the architecture definition: a native SwiftUI app with MVVM pattern, an Express backend with Prisma and PostgreSQL, and cloud image storage.

The decision to use a monorepo was mine. I proposed it because I wanted the team to have a single place where everything lived. That eliminated the friction of maintaining multiple synchronized repositories and made it easier for everyone to understand the project’s complete architecture.

Cloudflare R2 over Amazon S3

For image storage we needed a cloud storage service that was affordable and easy to set up. I chose Cloudflare R2 over Amazon S3 for three reasons:

  1. Price: R2 doesn’t charge for data egress (bandwidth), which is where S3 gets expensive fast when you’re serving images to users.
  2. Accessibility: The setup is simpler, Cloudflare’s interface is more intuitive, and the learning curve is shorter compared to the AWS ecosystem.
  3. Compatibility: R2 is compatible with the S3 API, which means if we ever need to migrate, the change is minimal.

We designed the photo upload flow so the server never touches the image bytes: the client requests a temporary upload permission, uploads directly to storage, and then notifies the backend. This reduces server load and improves speed for the user.

Image caching

An important performance challenge was image loading. In a gallery with dozens of photos, downloading each image from the network every time the user viewed it made the experience slow and unacceptable.

The solution was to implement a smart caching system: the first time a photo is downloaded, it’s saved in cache (both in memory and on disk). The next time the user sees that photo, it loads instantly from cache without needing to download again.

We also implemented request deduplication: if multiple parts of the app request the same image at the same time, only one download happens and they all share the result. This significantly improved the user experience when browsing the photo gallery.

End-to-end encryption

One of the project’s most ambitious challenges was photo encryption. The premise we defined as a team was simple: the server must not be able to see user photos. Only trip participants should be able to see them.

My role here was to identify this privacy need, define the high-level requirements, and assign the task. The implementation was carried out by a teammate, but the architectural decision that the server should be zero-knowledge (only facilitating key exchange without ever learning their content) was a team decision that I drove.

The result: photos are encrypted before leaving the phone and what arrives at cloud storage is indecipherable without the trip key. Only participants’ devices can see the images. It’s one of the features I’m most proud of as tech lead, because it demonstrates that knowing how to delegate and define clear requirements is as important as implementing.

With encryption in place, the caching system we already had was adapted to handle encrypted photos: it decrypts the image only once and stores the decrypted result, avoiding the costly decryption process on every view.

The decision to skip login

A product decision that was also an engineering one: we decided not to implement login or registration in the prototype. We wanted the experience to be as fluid as possible, so the user could create a trip or join one in seconds. The limitation is obvious: less control over user information and identity persistence. But for a prototype whose goal was to validate the game experience, prioritizing onboarding speed was the right call.

Development rules and code quality

The most common and frequent issues weren’t technical bugs — they were about collaboration and code quality. After too many merge conflicts that cost us hours, I proposed that the team establish development rules, which we documented in the repository:

  • Always sync your branch with the remote repository before pushing changes.
  • Resolve conflicts preferring the combination of both sections over completely replacing one.
  • Follow code quality standards to avoid affecting others’ work.

We’re still working on streamlining this process and improving it, but I think we’ve all come away with a valuable experience learning to truly develop as a team.


3. Collaboration & Product Development

Working in teams at the Academy has been one of the most enriching experiences I’ve had so far. To be honest, also a very big challenge.

The language barrier

Starting with the language barrier. While communication is in English, since it’s not my native language or my day-to-day language, it wasn’t always easy to communicate my point of view or opinions in an assertive or clear way. But it was a good challenge and certainly great practice for the future international teams I hope to have.

Leading from tech and beyond

In Unnoticed I took on the team’s leadership, not only in the technical aspect but in the overall project organization. I was the one who proposed the monorepo architecture, who set up the development environment with Docker and the initialization script, who defined the Git conventions and code quality rules, and who established Scrum as our working methodology with Jira.

But leading wasn’t just about making technical decisions. It was also about facilitating sprint meetings, helping divide work so everyone could contribute from their strengths, mediating when there were disagreements about product direction, and making sure the team stayed motivated and aligned. I learned that being the tech lead doesn’t mean doing everything: it means creating the conditions for the team to work well.

An international team

Sharing my culture with my teammates and learning about theirs was truly incredible. Meeting people from Italy, Iran, Brazil, Russia, Vietnam, India, Egypt, Spain, and more. Each one brought a different perspective not only on technology, but on how problems are solved, how ideas are communicated, and what “quality” means.

Multidisciplinary perspectives

Sharing perspectives with people outside my field also allowed me to broaden my point of view. It was essential to have designers and business profiles for making decisions about the app’s communication, user flow, business models, and project organization. An engineer alone doesn’t build a good product. A team does.

The difference from university

The biggest difference between this experience and university is that here there was nobody following up on you or giving you specific due dates and guidelines. While we had a final presentation, the most challenging part was making a plan with the team and sticking to it and to the deadlines we ourselves defined. That shared responsibility is what most closely resembles the real world.


4. Professional Growth

From school projects to real products

My way of working has changed fundamentally. Before, I thought about turning in an assignment. Now I think about building something someone is going to use. That difference seems subtle but it changes everything: how you design architecture, how you handle errors, how you think about performance, how you document your code.

Learning to make decisions with incomplete information

At university, problems come with clear specifications. At the Academy, I had to learn to make decisions with incomplete information and live with the trade-off. R2 or S3? Login or no login? Monorepo or multi-repo? End-to-end encryption or trust the server? None of these decisions has a “correct” answer. They all have trade-offs, and learning to evaluate them quickly and move forward has been one of the most valuable learnings.

Communicating technical ideas

I learned that knowing how to code isn’t enough. You need to know how to communicate what you’re building and why. Documenting the project’s architecture, writing technical specifications so a teammate could implement exactly what the frontend expected, or explaining to a designer why a technical decision affected the user experience. All of that is part of an engineer’s job.

AI as a tool, not a replacement

I’ve used AI tools like Claude Code throughout my projects at the Academy. In iDeary, I used it to generate the onboarding structure from my existing code. In Unnoticed, to iterate on backend implementations and the encryption protocol. But in every case, the product logic, architecture decisions, and experience design were mine and the team’s.

The lesson I take away is that AI is extraordinarily useful for executing faster, but it doesn’t replace engineering judgment. Knowing what to build, why, and how to structure it so it’s maintainable is still human work. And I believe that will become increasingly valuable, not less.

What’s next

I’m in the final stretch of the Apple Developer Academy and I’m leaving with a much broader technical stack than when I arrived, but above all with a different mindset. I want to keep building products that solve real problems, I want to keep leading technical teams, and I want to keep diving deeper into the low-level technology I use every day. Because as long as I understand the why behind what I’m building, I know it will push me to do it the best way I can.