Sitemap

Construyendo la Tarjeta de Crédito MACHBANK (Parte 2): Arquitectura Pura y el Mito del Lobo Solitario

5 min readDec 19, 2025

--

Press enter or click to view image in full size

La Decisión: El Mito del Arquitecto Solitario

Cuando nos enfrentamos al lienzo en blanco de la nueva tarjeta, las preguntas técnicas nos golpearon de inmediato:

  • ¿Qué arquitectura abordamos para que esto escale sin implosionar?
  • ¿Cómo manejamos las entradas y salidas de cada feature sin crear un monstruo acoplado?

En la teoría, uno podría pensar que estas decisiones las toma un “Arquitecto de Software” encerrado en una oficina, dibujando diagramas UML perfectos tras un análisis frío de pros y contras. La realidad es muy distinta.

Aquí es donde muere el mito del “Lobo Solitario”. La solución no salió de una sola cabeza brillante. Fue el resultado de un equipo de choque: desarrolladores Android, Líderes Técnicos y el equipo de Plataforma. Todos poniendo sobre la mesa aquello en lo que más se destacaban.

No buscábamos la arquitectura “académicamente perfecta” para un paper, buscábamos una solución funcional, mantenible y rápida.

La Evolución: De Muchos Contratos a Una Sola Puerta

Es importante decir que no llegamos a la solución final por arte de magia. Nos dimos varios golpes contra la pared.

Al principio, intentamos que cada módulo interno tuviera su propia Activity y se comunicaran usando ActivityContract. En el papel sonaba ordenado, pero en la práctica nos dimos cuenta de que estábamos devolviendo estados que nadie utilizaba. Teníamos módulos internos que simplemente ejecutaban funciones y no necesitaban reportar nada de vuelta. Era burocracia de código innecesaria.

Decidimos simplificar drásticamente: Eliminamos todos esos contratos internos y pasamos a una comunicación pura mediante NavHost y Grafos.

Sin embargo, mantuvimos un único Contrato vital: El que levanta la feature completa de Tarjeta de Crédito. Para la App principal, toda la tarjeta es una “Caja Negra”. La App usa este único ActivityContract para iniciar el flujo.

  • ¿Tienes un Deep Link? Se lo pasas a este contrato.
  • ¿Simplemente quieres entrar? Llamas a este contrato.

Una vez cruzada esa puerta, el Gateway toma el control y decide el destino según el estado del usuario (Home si está activo, Onboarding si no lo está) o según el parámetro del Deep Link recibido.

Llegar a esta madurez no fue gratis. Pasamos por navegaciones vía Deep Links que no eran directas y lidiamos con dependencias cruzadas que nos dolían. Pero la clave fue siempre mapear la deuda técnica. Sabíamos que lo que hacíamos no era perfecto, pero lo teníamos identificado para atacarlo después. El ideal no se logra rápido; es un proceso lento, lleno de errores y de decisiones que te dejan una enseñanza.

Módulos Contextualizados: Preparando el Terreno para Escalar

Con esa lección aprendida, consolidamos la estrategia: Módulos Contextualizados en el Dominio.

Identificamos que piezas como Movimientos, Configuración de Tarjeta, Notificaciones, Onboarding y el Home tenían vidas propias. Cada una ameritaba ser un módulo independiente no solo por limpieza de código, sino por la naturaleza de sus endpoints y las funciones específicas que debían cumplir.

Pero había una razón estratégica detrás de esto: La Escalabilidad Humana.

Sabíamos que el equipo iba a crecer. Al aislar estas features, habilitamos el trabajo en paralelo. Un desarrollador podía estar refactorizando los Movimientos mientras otro implementaba una nueva Configuración, sin generar conflictos de merge infinitos ni bloquearse mutuamente.

La Fortaleza Interna: Clean Architecture y el Poder del internal

Dividir por features era solo la mitad de la batalla. El verdadero desafío era disciplinar el código dentro de cada módulo.

No queríamos que el módulo de Home estuviera llamando a un Repositorio de Onboarding, ni que Movimientos inyectara un UseCase de Configuración. Eso hubiese creado un espagueti de dependencias imposible de mantener.

Para evitarlo, aplicamos una regla de oro: Lo que pasa en el módulo, se queda en el módulo.

Por dentro, cada feature respetaba rigurosamente Clean Architecture, con sus capas de Data, Dominio y Presentación claramente separadas. Pero la clave estuvo en el modificador de visibilidad.

Todas las clases — desde los ViewModels hasta los Data Sources — estaban marcadas como internal. Esto forzaba al compilador a ser nuestro policía: nadie desde fuera podía acceder a la lógica interna de la feature.

Get Paul Ayala’s stories in your inbox

Join Medium for free to get updates from this writer.

¿Qué era lo único público? Exclusivamente la “puerta de entrada”: Los Grafos de Navegación y las Rutas.

El “Gateway”: El Portero Inteligente

Con los módulos aislados y herméticos, surgió un nuevo problema: ¿Quién decide qué mostrar una vez que se activa el Contrato Principal?

No queríamos que la MainActivity tuviera un if/else gigante. Para resolver esto, creamos un módulo dedicado exclusivamente a la orquestación: El Módulo Gateway.

Este módulo cumplía dos funciones vitales:

Filtro de Elegibilidad (El “Patovica”): Antes de intentar cargar cualquier UI, el Gateway consultaba el estado del usuario.

  • ¿Es NO_ELEGIBLE? Ni siquiera intentes entrar.
  • ¿Es PRE_OFFER? Te redirijo al grafo de Onboarding.
  • ¿Está ACTIVE? Pasa directo al Home.

Enrutador de Deep Links: Si el contrato principal recibía un parámetro de navegación (ej: un link externo), el Gateway interceptaba la petición. Si un usuario necesitaba ir al onboarding, el Gateway ya conocía este grafo y era capaz de levantarlo, siempre respetando las reglas de negocio.

El Home: Un Segundo Nivel de Orquestación

Aquí ocurrió algo particular. Originalmente pensamos en el Home como una pantalla más, pero rápidamente se transformó en un Segundo Gateway.

Una vez que el usuario aterrizaba en el Home, este módulo tomaba el control para gestionar el acceso a las sub-features.

  • ¿Quieres ver Movimientos? El Home disponibiliza el acceso.
  • ¿Necesitas ir a Configuración? El Home gestiona la navegación hacia ese módulo.

Esto creó una Navegación Jerárquica: El Gateway Principal te deja entrar al edificio, y el Home actúa como el lobby que te distribuye a las habitaciones.

Deep Links: Cuando la URL es solo una sugerencia

El mayor riesgo de seguridad en la navegación moderna son los Deep Links. ¿Qué pasa si un usuario malintencionado (o simplemente curioso) intenta forzar la entrada escribiendo algun deeplink sin tener la tarjeta aprobada?

Aquí aplicamos una regla estricta: En nuestra arquitectura, el Deep Link es solo una intención, no una orden.

Todo el tráfico externo cae en el Gateway, y aquí entra en juego el Motor de Elegibilidad. Al recibir una petición de entrada, el Gateway no mira ciegamente la ruta de navegación ni los parámetros extra que trae el link. Lo primero que hace es validar internamente el estado del usuario contra la lógica de negocio.

  • ¿El link dice “Ir a Configuración”?
  • Motor: “Un momento. Consultando estado… Usuario NO_ELEGIBLE."
  • Resultado: El Gateway ignora la ruta del link y redirige al usuario a una pantalla de error.

No importa qué parámetros traiga la URL todo depende de este motor. Es la única fuente de verdad.

Mirando hacia atrás, lo más valioso de este proceso no fue el código final, sino entender que el software vivo siempre está en construcción.

Reconocimos nuestras debilidades, mapeamos nuestra deuda técnica y priorizamos la funcionalidad sobre la perfección académica. Teníamos los cimientos listos: la app era segura, modular y ordenada.

Pero ahora faltaba lo más visible: Hacer que todo esto se viera bien en la pantalla.

Próximamente: Parte 3

En la Parte 3 (y final), hablaremos de UI y Experiencia de Usuario: Los desafíos de Jetpack Compose, el manejo de estados de carga complejos y cómo logramos esa fluidez visual que el diseño exigía.

--

--