Problema
La mayoría del acceso a internet en Cuba pasa por el servicio Nauta de Etecsa: te autenticas contra un portal cautivo para conectarte, y gestionas tu cuenta, plan y saldo en otro sitio completamente distinto. Durante años eso significó flujos de navegador de escritorio que no compartían estado — malabarear pestañas, copiar y pegar credenciales, y forzar la vista sobre una página del portal para deducir cuánto tiempo de conexión te quedaba.
Los dos sistemas detrás de esos flujos no se parecen en nada. Uno es una API JSON moderna. El otro es un portal heredado de servlets Java que responde en HTML y que solo fue diseñado para que hablara con él un navegador. La app tenía que aterrizar sobre ambos tal como estaban, sin ningún cambio de backend en el que apoyarse.
La superficie móvil
Construí la app móvil Nauta de punta a punta en Flutter, como único desarrollador móvil. Se entrega como dos apps dentro de un mismo binario, elegidas en un splash de entrada, porque son productos genuinamente distintos hablando con sistemas genuinamente distintos:
- Portal — gestión de cuenta contra la API REST de Nauta: login detrás de un captcha emitido por el servidor, perfil y servicios, facturas, noticias, y las operaciones y consultas en sí, que se renderizan como formularios dinámicos a partir de los parámetros que devuelve la API en vez de estar cableadas pantalla por pantalla.
- Navegación — un cliente de portal cautivo para el Wi-Fi de Etecsa: conectarte, ver el tiempo restante de sesión en cuenta atrás, desconectarte, y guardar cuentas en almacenamiento seguro para acceder con un toque.
El lado Portal es deliberadamente un espejo del portal web público de Etecsa en nauta.cu — misma API, mismas operaciones, mismo modelo de cuenta, reconstruido como producto móvil nativo. Yo no construí ese portal web; lo llevaba un compañero de equipo y trabajábamos el mismo contrato de backend desde extremos opuestos. Mi trabajo era la paridad: todo lo que un abonado podía hacer en un navegador tenía que funcionar en un teléfono, sin el navegador.
Decisiones que vale la pena mencionar:
- Un binario, dos clientes muy distintos. El lado Portal es una API JSON limpia. El portal cautivo es un sistema heredado de servlets Java y JSP que responde en HTML. Mismo lenguaje de diseño, máquinas de estado y vidas de sesión deliberadamente separadas, para que una sesión de un lado nunca pueda filtrarse al otro.
- Sin WebView en el portal cautivo. El atajo evidente es empotrar un navegador y dejar que el usuario pelee con la propia página del portal. En su lugar la app reimplementa la sesión como cliente HTTP puro: levanta los tokens de sesión que el portal solo publica dentro de su página de login, mantiene un cookie jar para que la sesión sobreviva entre peticiones, sigue la redirección posterior al login y recupera el identificador que el servidor entierra en la página devuelta. Eso es lo que hace que se sienta una app y no un navegador disfrazado.
- El éxito se infiere, no se devuelve. El portal cautivo no responde con códigos de estado fiables — responde con una página. El login exitoso se reconoce parseando la cadena de tiempo restante del cuerpo y confirmando que la respuesta no es otra vez el formulario de login; cada modo de fallo (credenciales inválidas, ya conectado, sin tiempo disponible) es un fallo tipado aparte en vez de un error genérico.
- La cuenta atrás sobrevive a la UI. El tiempo de sesión corre en su propio isolate detrás de una notificación persistente, más un overlay nativo opcional en Android con temporizador independiente y medidor de velocidad en vivo. Cierras la app y ambos siguen corriendo — porque lo que el usuario necesita ver de verdad es cuánta conexión le queda.
- Honesto con el captcha. El portal de cuenta exige un captcha por imagen emitido por el servidor. No traté de saltarlo — la app refresca el desafío a demanda y lo trata como parte de primer nivel del formulario.
Por debajo, cada feature se divide en domain / data / presentation con sus propios casos de uso y repositorios, Riverpod para estado, y fallos tipados vía Either en vez de excepciones lanzadas entre capas. Compila para Android, Windows y Linux desde la misma base de código.
Una nota sobre el backend
Los módulos operativos internos de Etecsa corren sobre un stack heredado de Javax. Aparte de la app, me asignaron uno de esos módulos para responder una pregunta acotada: ¿Spring Boot sería realmente mejor? Porté ese único módulo y llevé ambas implementaciones cara a cara con SoapUI bajo carga comparable — el port a Spring Boot salió unas 3× más rápido, y documenté los números como evidencia para la decisión de plataforma. Mi alcance terminó ahí. Es una medición que me pertenece, no una migración que haya entregado.
Resultado
La app colapsó dos flujos de navegador en un solo producto: conectarte, ver exactamente cuánto tiempo te queda, y gestionar la cuenta sin abrir un navegador nunca. Se entregó contra ambos backends tal como estaban — y esa es la restricción que moldeó cada decisión. Cuando no puedes cambiar el sistema de abajo, la ingeniería consiste en absorber su forma para que el usuario no tenga que hacerlo.