Cuando empiezas a investigar el desarrollo móvil, la cantidad de frameworks y enfoques se vuelve confusa rápido. Escuchas sobre apps nativas, React Native, Flutter, Expo, Capacitor, Cordova, Ionic, Tauri mobile, y no siempre queda claro qué los diferencia. ¿Están todos haciendo lo mismo de formas distintas, o algunos son arquitecturas diferentes en la raíz?
La respuesta es ambas. Hay tres formas principales de construir una app móvil, y la mayoría de los frameworks que escuchas caen en una de ellas. Las diferencias están en qué renderiza la UI, qué ejecuta la lógica, y cómo se hablan entre sí.
Los tres enfoques
Aquí está el panorama:
| Enfoque | La UI se renderiza con | La lógica corre en | Ejemplos |
|---|---|---|---|
| Nativo | Componentes UI de la plataforma | Lenguaje de la plataforma | Apps Swift/Kotlin |
| Web wrapper | WebView (navegador) | JavaScript | Capacitor, Cordova, Ionic |
| Multiplataforma | Varía (nativo, canvas, o WebView) | JS, Dart, o Rust | React Native, Flutter, Tauri |
Lo primero que hay que notar es que "multiplataforma" no es un enfoque. Es una categoría, y dentro de ella la estrategia de renderizado de UI es diferente para cada framework. De ahí viene la mayor confusión. La gente oye "multiplataforma" y asume que significa una cosa. No es así.
Repasemos cada uno.
Apps nativas
Esta es la base. Escribes una app de iOS en Swift o Objective-C, usando UIKit o SwiftUI. Escribes una app de Android en Kotlin o Java, usando Android Views o Jetpack Compose. El código compila al binario nativo de la plataforma, la UI son los componentes UI propios de la plataforma, y la lógica corre directamente en el runtime de la plataforma.
iOS: Swift/SwiftUI → compilado → UIKit/SwiftUI renderiza a pantalla
Android: Kotlin/Compose → compilado → Android Views/Compose renderiza a pantallaLa ventaja es acceso total a todo lo que la plataforma ofrece. Cada API, cada componente UI, cada característica específica de plataforma, cada optimización de rendimiento. No hay puente, no hay capa de traducción, no hay abstracción. El código que escribes es el código que corre.
El coste es que escribes la app dos veces. Una app de iOS y una app de Android son dos bases de código completamente separadas, en dos lenguajes distintos, con dos frameworks UI diferentes, dos sistemas de build distintos, y dos objetivos de despliegue diferentes. Si quieres una característica en ambas plataformas, la construyes dos veces.
De aquí vienen los otros dos enfoques. La categoría de web wrapper y la multiplataforma existen ambas porque escribir todo dos veces es caro.
Web wrappers
El enfoque de web wrapper toma una app web y la pone dentro de una shell nativa. Capacitor (de Ionic) y Cordova son los frameworks principales aquí.
HTML/CSS/JS → WebView → shell nativa → OSLa UI es un WebView. Eso es un motor de navegador, el mismo que el sistema usa cuando abres un enlace dentro de una app. En iOS es WKWebView, en Android es el Android WebView. Tu JavaScript corre dentro de ese WebView, igual que corre dentro de una pestaña del navegador. La shell nativa alrededor proporciona un puente para que tu JavaScript pueda llamar APIs de la plataforma, acceder a la cámara, leer el sistema de archivos, mostrar notificaciones nativas, y demás.
Si has leído la introducción a Tauri, esta estructura debería ser familiar. Tauri en escritorio usa el mismo patrón: UI web dentro de una ventana nativa, con un puente al OS. La diferencia es el backend. Capacitor y Cordova puentean al lenguaje nativo de la plataforma (Swift o Kotlin), escrito por plataforma. Tauri puentea a Rust, compilado nativamente para cada objetivo móvil.
El atractivo del web wrapper es obvio. Si ya tienes una app web, puedes enviarla como app móvil sin reescribir tu UI. El mismo HTML, CSS y JavaScript que corre en un navegador corre dentro del WebView. Escribes un frontend, y funciona en todas partes.
El tradeoff es rendimiento y sensación nativa. Un WebView es un navegador. Renderiza HTML a una pantalla, de la misma forma que lo hace un navegador. Los componentes UI nativos son más rápidos, más fluidos, y se sienten como la plataforma de formas que un WebView no siempre logra. La física del scroll, el manejo de gestos, las transiciones, y el comportamiento de entrada son todos sutilmente diferentes dentro de un WebView que en una UI nativa. Para algunas apps eso no importa. Para otras es todo el juego.
Frameworks multiplataforma
Aquí se pone interesante, porque "multiplataforma" significa cosas distintas para diferentes frameworks. Cada uno elige una estrategia de renderizado de UI y un mecanismo de puente distintos.
React Native
React Native no renderiza a un WebView. Tus componentes React describen una UI nativa, y React Native crea vistas nativas reales de iOS y Android para ti. Un View se convierte en un UIView en iOS y en un android.view.View en Android. No hay navegador, no hay HTML, no hay DOM.
Componentes React → puente JSI → componentes UI nativos → pantallaLa lógica corre en JavaScript, en un motor de JavaScript embebido dentro de la app. El puente entre JavaScript y nativo se llama JSI (JavaScript Interface). Permite a JavaScript llamar directamente a objetos C++ nativos, lo que es más rápido que el puente anterior que serializaba todo a JSON.
Si quieres el análisis profundo de React Native y Expo, escribí sobre eso aquí. La versión corta para este post: la UI es nativa, la lógica es JavaScript, y el puente es JSI.
Flutter
Flutter toma un enfoque diferente para la UI. No usa componentes UI nativos, y no usa un WebView. Flutter tiene su propio motor de renderizado, llamado Impeller (antes Skia), que dibuja directamente a un canvas. Cada píxel en la pantalla es pintado por el propio motor de Flutter.
Widgets de Flutter → runtime de Dart → Impeller renderiza al canvas → pantallaLa lógica corre en Dart, que se compila a código ARM nativo para la plataforma objetivo. El puente entre Dart y las APIs de la plataforma se llama Platform Channels. Cuando Flutter necesita llamar a una API de la plataforma, digamos la cámara o el sistema de archivos, envía un mensaje a través de un Platform Channel a código nativo que maneja la petición.
El atractivo es la consistencia. Como Flutter dibuja su propia UI, una app de Flutter se ve y se comporta idénticamente en iOS y Android. No hay el problema de "esto parece una app de Android en iOS", porque la UI no está usando los componentes de ninguna de las dos plataformas. El tradeoff es que una app de Flutter no adopta automáticamente la apariencia y sensación de la plataforma. Si quieres que se sienta nativa, tienes que construirla tú mismo.
Tauri Mobile
Tauri mobile es el más reciente de los tres, y combina el enfoque de UI del web wrapper con un backend de Rust en vez de un backend en lenguaje nativo.
HTML/CSS/JS → WebView → Tauri IPC → Rust → OSLa UI es un WebView, igual que Capacitor y Cordova. La diferencia es el backend. En vez de puentear a código Swift o Kotlin escrito por plataforma, Tauri puentea a Rust, que se compila nativamente para cada objetivo móvil. Una base de código de Rust maneja el lado nativo tanto para iOS como para Android.
Aquí es donde el patrón de una base de código se vuelve útil. La misma estrategia de interfaz e implementación que permite a una app de escritorio de Tauri compartir código con un frontend web se aplica también a móvil. Tus interfaces de servicio permanecen iguales, y añades una implementación móvil junto a las de web y escritorio.
El patrón compartido
Aquí está la parte que hizo que todo el panorama encajara para mí. Los tres frameworks multiplataforma siguen la misma arquitectura:
UI
|
Puente
|
MotorLa UI es la capa de presentación. El puente es el mecanismo de comunicación. El motor es el runtime que hace el trabajo real. La tecnología cambia, pero la forma es la misma.
| Framework | UI | Puente | Motor |
|---|---|---|---|
| React Native | Componentes nativos | JSI | Motor JavaScript |
| Flutter | Canvas de Impeller | Platform Channels | Runtime de Dart |
| Tauri mobile | WebView | IPC | Rust |
Una vez que ves ese patrón, las diferencias entre los frameworks dejan de ser confusas. Son todos variaciones de la misma forma. React Native elige UI nativa y JSI. Flutter elige su propio canvas y Platform Channels. Tauri elige un WebView e IPC a Rust. La elección trata sobre qué tradeoffs aceptas para la capa de UI y qué mecanismo de puente quieres usar para llegar a la plataforma.
Cómo elegir
La pregunta no es qué framework es el mejor. Es qué tradeoffs encajan con tu app.
Nativo (Swift/Kotlin) si necesitas acceso total a la plataforma, rendimiento máximo, o una UI que se sienta completamente nativa en cada plataforma. El coste es mantener dos bases de código.
React Native si quieres componentes UI nativos impulsados por JavaScript, y ya tienes un equipo de React o quieres compartir lógica con una app web de React. La UI es nativa, la lógica es JavaScript, y escribes una base de código que apunta a ambas plataformas.
Flutter si quieres consistencia pixel-perfect entre plataformas y estás dispuesto a construir la apariencia y sensación nativas tú mismo. La UI es el propio renderizado de Flutter, la lógica es Dart, y la app se ve idéntica en iOS y Android por defecto.
Tauri mobile si ya tienes un frontend web y un backend de Rust, o si quieres extender una app de escritorio de Tauri a móvil sin cambiar tu stack. La UI es un WebView, el backend es Rust, y la misma base de código puede apuntar a escritorio y móvil.
Web wrapper (Capacitor/Cordova) si ya tienes una app web y quieres enviarla a móvil con cambios mínimos, y puedes aceptar los tradeoffs del WebView en torno a rendimiento y sensación nativa.
La imagen más grande
La mayoría de los frameworks multiplataforma, y la mayoría de las arquitecturas de apps modernas en general, siguen el patrón de UI, Puente, Motor. La UI es lo que el usuario ve. El puente es cómo la UI habla con el motor. El motor es lo que hace el trabajo. Ya sea que estés construyendo una app móvil, una app de escritorio, o una app web, esa forma aparece.
La tecnología cambia. La arquitectura no. Una vez que puedes ver la forma, puedes evaluar cualquier framework nuevo haciéndote tres preguntas: qué renderiza la UI, qué corre la lógica, y cómo se comunican. Todo lo demás es detalle.
Leer más
Arquitectura para una facturación móvil confiable: Lo que aprendí a las malas
Una mirada al mundo real sobre cómo solucionar la facturación de suscripciones móviles cuando los webhooks, las compras en sandbox y la identidad del usuario fallan.
React Native y Expo: una explicación sencilla
Entiende cómo funcionan realmente React Native y Expo, desde las compilaciones nativas hasta las actualizaciones OTA y los flujos de desarrollo.
Conoce Tauri [2.0]
Cómo Tauri construye aplicaciones de escritorio a partir de tecnologías web, dónde sitúa el límite de confianza en comparación con Electron, y el cambio de mentalidad de una página web en una ventana a una aplicación nativa.
