Construyendo una app de radios LATAM (1): por qué acabé eligiendo Expo y React Native
Publicado el 24/05/2026
El problema
Quería montar una app móvil para escuchar radios online de Latinoamérica. Suena trivial: un listado, un botón de play, favoritos. Lo trivial dura hasta que abres una hoja en blanco y te toca decidir el stack.
Las restricciones que tenía claras desde el día uno:
- Yo solo. Sin equipo, sin presupuesto, sin manager pidiendo timelines. Eso es una bendición y una trampa: puedo permitirme aprender, pero si elijo mal pago yo el coste del cambio.
- Android primero. Mi mercado objetivo —Latam— es mayoritariamente Android. iOS puede esperar.
- Web "gratis" si se puede. Tengo un blog y un dominio; si la app puede vivir también como PWA, mejor.
- Audio en background. Una radio que se pausa cuando bloqueas el teléfono no es una radio.
- Velocidad de iteración. Quiero rediseñar la UI un domingo a las once de la noche sin pelearme con Gradle.
Las opciones que me planteé
Hice el ejercicio honesto de listarlas, no para presumir de rigor, sino porque cada vez que salto a la primera idea acabo pagándolo dos meses después.
Kotlin + Jetpack Compose
La opción "noble". Lo nativo nativo, performance impecable, MediaSessionService de cajón, todo el ecosistema de Google a tu favor.
Lo que me echó para atrás:
- No tengo trabajo a tiempo completo en Android. Cada cambio me cuesta el doble por desconocimiento del SDK.
- Cero historia para web. Para tener PWA necesitaría escribir un cliente entero aparte.
- El ciclo "edita → recompila → instala" en Android nativo es lento comparado con lo que ya conocía de hot reload web.
No es que Compose sea malo. Es que el coste de oportunidad para mí, en este proyecto concreto, era altísimo.
Flutter
Lo consideré en serio. Tiene buen soporte de audio (just_audio + audio_service son una gozada), un ecosistema cada vez más sólido y un hot reload muy bueno.
Lo que me frenó:
- Dart. No es malo, pero cada hora aprendiéndolo es una hora que no estoy aprendiendo cosas que ya uso a diario.
- Web aún sufre. Funciona, pero los bundles son grandes y la experiencia para una app de consumo (no dashboard) no me convencía.
- Romper con todo el ecosistema NPM. Una decisión grande para mí solo.
React Native sin Expo
Lo descarté rápido. No por motivos técnicos sino emocionales: la última vez que me peleé con react-native init, pod install, Gradle versions y autolinking semibroken me prometí que no volvía sin una capa por encima que se ocupara de eso.
React Native + Expo
Lo que elegí. Pero no por descarte, sino porque casa muy bien con las restricciones:
- JS/TS: lo uso todos los días, no añade coste cognitivo nuevo.
- expo-router: routing basado en sistema de ficheros, igual que Next. Empezar es triple-click.
- Web target real:
react-native-webestá maduro y la app puede vivir como PWA del mismo bundle. - EAS Build: cuando llegue el momento de firmar el APK, no quiero pelearme con keystore + Gradle a las dos de la mañana.
- Prebuild: si en algún momento necesito tocar el manifiesto Android (spoiler: lo necesité), puedo "expulsar" parcialmente sin perder el resto de la magia.
La duda que casi me hace cambiar de idea
A mitad del primer prototipo me empezó a pesar una duda recurrente: ¿voy a poder hacer audio en background bien hecho con expo-av sin meter código nativo?
La respuesta corta —que descubrí después— es "sí, con peros". expo-av te da staysActiveInBackground: true y eso, combinado con el permiso FOREGROUND_SERVICE_MEDIA_PLAYBACK en el manifiesto, funciona. Pero la integración con la MediaSession del sistema (los controles en la pantalla de bloqueo, el "now playing") no es trivial y requiere un módulo extra o código nativo.
Para v1 acepté la limitación: el audio sigue sonando cuando bloqueas, y eso es lo crítico. Los controles del lockscreen los abordaré en v2, probablemente con react-native-track-player o saltando a Expo's nuevo módulo de audio.
Esa es la mentalidad que recomiendo: no elijas un stack porque cubra el 100% de un futuro hipotético, sino el que te permita aterrizar el 80% del valor real con el coste más bajo, y donde el 20% restante sea negociable.
La decisión, en cuatro líneas
- Framework: React Native 0.79 sobre Expo SDK 53.
- Routing: expo-router 5 (file-based, soporta tabs, modals, deep linking).
- Lenguaje: TypeScript estricto.
- Targets: Android nativo + Web (PWA). iOS queda pendiente.
Lo que validé después
- El ciclo de desarrollo es excelente:
npm run androidlevanta el emulador, fast refresh funciona, los errores se muestran limpios. - El web target compila del mismo código fuente con casi cero adaptaciones (las que hubo fueron explícitas y manejables, ver capítulo 6).
expo-routercon typed routes ("experiments": { "typedRoutes": true }enapp.json) me protege de typos en navegación, que es donde más bugs tontos sufría antes.
Lo que sigo dudando
- Si más adelante necesito un nivel de control de audio que
expo-avno me da, voy a tener que migrar aexpo-audio(el sucesor) o areact-native-track-player. Eso es deuda técnica conocida y aceptada. - Si el bundle web crece mucho, tendré que pensar en code-splitting más fino. Por ahora, con
output: "single"me sirve.
La conclusión que me llevo
Elegir el stack no fue un acto de fe en una tecnología, fue un acto de honestidad sobre mi tiempo y mis hábitos. La mejor herramienta es la que vas a usar el sábado por la tarde sin resistencia mental.
En el próximo capítulo cuento cómo me peleé con la API de Radio Browser, por qué acabé montando un proxy propio y qué problemas resolví normalizando las respuestas en cliente.