Enlaces profundos para apps móviles nativas: rutas, tokens y “abrir en la app”
Aprende enlaces profundos para apps móviles nativas: planifica rutas, maneja “abrir en la app” y transfiere tokens de forma segura para Kotlin y SwiftUI sin código de enrutamiento desordenado.

Qué debe hacer un enlace profundo, en términos sencillos
Cuando alguien toca un enlace en su teléfono, espera un resultado: que le lleve al lugar correcto, de inmediato. No a un lugar aproximado. No a la pantalla principal con una barra de búsqueda. No a una pantalla de login que olvida por qué vino.
Una buena experiencia de enlace profundo se ve así:
- Si la app está instalada, se abre en la pantalla exacta que implica el enlace.
- Si la app no está instalada, el toque aún ayuda (por ejemplo, abre una página web de fallback o la tienda de apps y puede devolver a la persona al mismo destino después de la instalación).
- Si la persona debe iniciar sesión, lo hace una vez y aterriza en la pantalla prevista, no en el inicio por defecto de la app.
- Si el enlace lleva una acción (aceptar invitación, ver pedido, confirmar correo), la acción es clara y segura.
La mayoría de la frustración viene de enlaces que “más o menos funcionan” pero rompen el flujo. La gente ve la pantalla equivocada, pierde lo que estaba haciendo o queda atrapada en un bucle: tocar enlace, iniciar sesión, aterrizar en el panel, tocar enlace otra vez, iniciar sesión otra vez. Incluso un paso extra puede hacer que los usuarios abandonen, especialmente para acciones de una sola vez como invitaciones o restablecimiento de contraseña.
Antes de escribir cualquier código en Kotlin o SwiftUI, decide qué quieres que signifiquen los enlaces. ¿Qué pantallas pueden abrirse desde fuera? ¿Qué cambia si la app está cerrada o ya en ejecución? ¿Qué debe pasar cuando el usuario está desconectado?
La planificación es lo que previene la mayor parte del dolor más adelante: rutas claras, comportamiento predecible de “abrir en la app” y una forma segura de transferir códigos de un solo uso sin poner secretos directamente en la URL.
Tipos de enlaces profundos y dónde falla “abrir en la app”
No todos los “enlaces que abren una app” se comportan igual. Si los tratas como intercambiables, tendrás los fallos clásicos: el enlace abre el lugar equivocado, abre un navegador en lugar de la app, o funciona solo en una plataforma.
Tres categorías comunes:
- Esquemas personalizados (por ejemplo, un esquema de app como myapp:). Fáciles de configurar, pero muchas apps y navegadores los tratan con cautela.
- Universal Links (iOS) y App Links (Android). Usan enlaces web normales y pueden abrir la app cuando está instalada o degradar a una web cuando no lo está.
- Enlaces en navegadores embebidos dentro de apps. Los enlaces abiertos dentro de una app de correo o el navegador integrado de un mensajero a menudo se comportan distinto que en Safari o Chrome.
“Abrir en la app” puede significar cosas distintas según dónde se toque. Un enlace tocado en Safari puede saltar directamente a la app. Ese mismo enlace tocado dentro de un correo o mensajero puede abrir primero una vista web embebida, y la persona tiene que pulsar un botón extra de “abrir” (o puede que nunca lo vea). En Android, Chrome puede respetar App Links mientras que el navegador interno de una app social puede ignorarlos.
Cold start vs app ya en ejecución es la siguiente trampa.
- Cold start: el SO lanza tu app, tu app se inicializa y solo después recibes el enlace profundo. Si tu flujo de arranque muestra una pantalla de bienvenida, comprueba auth o carga configuración remota, el enlace puede perderse a menos que lo guardes y lo reproduzcas cuando la app esté lista.
- Ya en ejecución: recibes el enlace mientras el usuario está en una sesión. La pila de navegación existe, así que el mismo destino puede necesitar un manejo distinto (push de pantalla vs reiniciar la pila).
Un ejemplo simple: un enlace de invitación tocado desde Telegram a menudo se abre primero dentro del navegador embebido. Si tu app asume que el SO siempre realizará la transferencia directa, los usuarios verán una página web en su lugar y pensarán que el enlace está roto. Planifica estos entornos desde el principio y escribirás menos pegamento específico de plataforma después.
Planifica tus rutas antes de implementar nada
La mayoría de los bugs de enlaces profundos no son problemas de Kotlin o SwiftUI. Son problemas de planificación. El enlace no se mapea claramente a una sola pantalla, o lleva demasiadas opciones “tal vez”.
Empieza con un patrón de rutas consistente que coincida con cómo la gente piensa sobre tu app: lista, detalle, ajustes, checkout, invitación. Mantenlo legible y estable, porque lo reutilizarás en correos, códigos QR y páginas web.
Un conjunto de rutas simple podría incluir:
- Inicio
- Lista de pedidos y detalles de pedido (orderId)
- Ajustes de la cuenta
- Aceptación de invitación (inviteId)
- Búsqueda (query, tab)
Luego define tus parámetros:
- Usa IDs para objetos únicos (orderId).
- Usa parámetros opcionales para estado de UI (tab, filtro).
- Decide valores por defecto para que cada enlace tenga un destino preferido.
También decide qué pasa cuando el enlace está mal: datos faltantes, ID inválido o contenido al que el usuario no puede acceder. El valor predeterminado más seguro es abrir la pantalla estable más cercana (como la lista) y mostrar un mensaje corto. Evita dejar a la gente en una pantalla en blanco o en una pantalla de login sin contexto.
Finalmente, planifica según la fuente. Un código QR suele necesitar una ruta corta que abra rápido y tolere conectividad débil. Un enlace de correo puede ser más largo e incluir contexto extra. Una página web debería degradar con gracia: si la app no está instalada, la persona aún debe aterrizar en un lugar que explique qué hacer a continuación.
Si usas un enfoque impulsado por backend (por ejemplo, generando endpoints de API y pantallas con una plataforma como AppMaster), este plan de rutas se convierte en un contrato compartido: la app sabe a dónde ir y el backend sabe qué IDs y estados son válidos.
Entrega segura de tokens sin poner secretos en URLs
Un enlace profundo a menudo se trata como un sobre seguro. No lo es. Todo lo que está en la URL puede acabar en el historial del navegador, capturas de pantalla, vistas previas compartidas, logs de analítica o copiado en un chat.
Evita poner secretos en el enlace. Eso incluye tokens de acceso de larga duración, tokens de refresco, contraseñas, datos personales o cualquier cosa que permitiera a alguien actuar como el usuario si se reenvía el enlace.
Un patrón más seguro es un código de corta duración y de un solo uso. El enlace solo lleva ese código, y la app lo intercambia por una sesión real después de abrirse. Si alguien roba el enlace, el código debería ser inútil después de uno o dos minutos, o tras el primer intercambio exitoso.
Un flujo de transferencia simple:
- El enlace contiene un código de un solo uso, no un token de sesión.
- La app se abre y llama a tu backend para canjear el código.
- El backend valida la expiración, verifica que no se haya usado y lo marca como usado.
- El backend devuelve una sesión autenticada normal para la app.
- La app borra el código de la memoria una vez canjeado.
Incluso después de un canje exitoso, confirma la identidad dentro de la app antes de ejecutar algo sensible. Si el enlace pretende aprobar un pago, cambiar un correo o exportar datos, exige una comprobación rápida como biometría o un login fresco.
Almacena la sesión resultante de forma segura. En iOS, eso suele significar Keychain. En Android, usa almacenamiento respaldado por Keystore. Guarda solo lo necesario y bórralo en logout, eliminación de cuenta o cuando detectes un reuso sospechoso.
Un ejemplo concreto: envías un enlace de invitación para unirse a un workspace. El enlace lleva un código de un solo uso que expira en 10 minutos. La app lo canjea y luego muestra una pantalla que indica claramente qué pasará a continuación (unirse a qué workspace). Solo después de que el usuario confirme, la app completa la unión.
Si construyes con AppMaster, esto se mapea limpiamente a un endpoint que canjea códigos y devuelve una sesión, mientras tu UI móvil maneja el paso de confirmación antes de cualquier acción de alto impacto.
Autenticación y “continuar donde lo dejaste”
Los enlaces profundos a menudo apuntan a pantallas con datos privados. Empieza por decidir qué puede abrir cualquiera (público) y qué requiere sesión (protegido). Esta única decisión evita la mayoría de las sorpresas de “funcionaba en pruebas”.
Una regla simple: enlaza primero a un estado de aterrizaje seguro y luego navega a la pantalla protegida solo después de confirmar que el usuario está autenticado.
Decide qué es público vs protegido
Trata los enlaces profundos como si pudieran reenviarse a la persona equivocada.
- Público: páginas de marketing, artículos de ayuda, inicio de restablecimiento de contraseña, inicio de aceptación de invitación (sin mostrar datos aún)
- Protegido: detalles de pedidos, mensajes, ajustes de cuenta, pantallas de admin
- Mixto: una pantalla de vista previa puede estar bien, pero muestra solo marcadores no sensibles hasta el login
“Continuar después del login” que devuelve al lugar correcto
El enfoque fiable es: analiza el enlace, guarda el destino previsto y luego enruta según el estado de auth.
Ejemplo: un usuario toca un enlace “abrir en la app” a un ticket de soporte específico estando desconectado. Tu app debería abrir a una pantalla neutral, pedirle que inicie sesión y luego llevarle a ese ticket automáticamente.
Para mantenerlo fiable, guarda un pequeño “objetivo de retorno” localmente (nombre de ruta más ID del ticket) con una expiración corta. Después de que el login termine, léelo una vez, navega y bórralo. Si el login falla o el objetivo expira, vuelve a una pantalla de inicio segura.
Maneja los casos límite con respeto:
- Sesión expirada: muestra un mensaje corto, vuelve a autenticar y luego continúa.
- Acceso revocado: abre la cáscara del destino y muestra “Ya no tienes acceso” y ofrece un siguiente paso seguro.
También evita mostrar datos privados en vistas previas del lock screen, en el selector de apps o en notificaciones. Mantén las pantallas sensibles en blanco hasta que los datos se carguen y la sesión esté verificada.
Un enfoque de enrutamiento que evita el espagueti de navegación personalizado
Los enlaces profundos se vuelven un lío cuando cada pantalla parsea URLs a su manera. Eso distribuye pequeñas decisiones (qué es opcional, qué es obligatorio, qué es válido) por toda la app y se vuelve difícil de cambiar con seguridad.
Trata el enrutamiento como fontanería compartida. Mantén una sola tabla de rutas y un único parser, y que la UI reciba entradas limpias.
Usa una tabla de rutas compartida
Haz que iOS y Android acepten una única lista legible de rutas. Piénsalo como un contrato.
Cada ruta mapea a:
- una pantalla y
- un pequeño modelo de entrada.
Por ejemplo, “Detalles de pedido” mapea a una pantalla Order con una entrada como OrderRouteInput(id). Si una ruta necesita valores extra (como una fuente ref), pertenecen a ese modelo de entrada, no dispersos por el código de vistas.
Centraliza el parsing y la validación
Mantén el parsing, la decodificación y la validación en un solo lugar. La UI no debería preguntarse “¿está presente este token?” o “¿es válido este ID?”. Debería recibir o bien un input de ruta válido o un estado de error claro.
Un flujo práctico:
- Recibe la URL (toque, escaneo, hoja de compartir)
- Parséala en una ruta conocida
- Valida campos requeridos y formatos permitidos
- Produce un destino de pantalla más un modelo de entrada
- Navega a través de un único punto de entrada
Agrega una pantalla de fallback para “enlace desconocido”. Hazla útil, no un callejón sin salida: muestra qué no se pudo abrir, explica por qué en lenguaje sencillo y ofrece acciones como ir a Inicio, buscar o iniciar sesión.
Paso a paso: diseña enlaces profundos y el comportamiento de “abrir en la app”
Los buenos enlaces profundos se sienten aburridos en el mejor sentido. La gente toca y aterriza en la pantalla correcta, tanto si la app está instalada como si no.
Paso 1: elige los puntos de entrada que importan
Haz una lista de los 10 primeros tipos de enlace que la gente usa realmente: invitaciones, restablecimientos de contraseña, recibos de pedido, enlaces a tickets, promociones. Mantén la lista pequeña a propósito.
Paso 2: escribe los patrones como un contrato
Para cada punto de entrada, define un patrón canónico y los datos mínimos necesarios para abrir la pantalla correcta. Prefiere IDs estables en lugar de nombres. Decide qué es obligatorio vs opcional.
Reglas útiles:
- Un propósito por ruta (invite, reset, receipt).
- Los parámetros obligatorios siempre están presentes; los opcionales tienen valores por defecto seguros.
- Usa los mismos patrones en iOS (SwiftUI) y Android (Kotlin).
- Si esperas cambios, reserva un prefijo de versión simple (como v1).
- Define qué pasa cuando faltan parámetros (muestra una pantalla de error, no una página en blanco).
Paso 3: decide el comportamiento de login y el objetivo post-login
Anota, por tipo de enlace, si requiere login. Si lo requiere, recuerda el destino y continúa después del login.
Ejemplo: un enlace a un recibo puede mostrar una vista previa sin login, pero al pulsar “Descargar factura” puede requerir login y debe devolver al usuario a ese recibo.
Paso 4: establece reglas de transferencia de tokens (mantén secretos fuera de las URLs)
Si el enlace necesita un token de un solo uso (aceptación de invitación, restablecimiento, inicio mágico), define cuánto tiempo es válido y cómo se puede usar.
El enfoque práctico: la URL lleva un código de corta duración y de un solo uso, y la app lo intercambia con tu backend por una sesión real.
Paso 5: prueba los tres estados del mundo real
Los enlaces profundos fallan en los extremos. Prueba cada tipo de enlace en estas situaciones:
- Cold start (app cerrada)
- Warm start (app en memoria)
- App no instalada (el enlace todavía debe aterrizar en algo sensato)
Si mantienes rutas, comprobaciones de auth y reglas de intercambio de tokens en un solo lugar, evitarás dispersar lógica personalizada por las pantallas Kotlin y SwiftUI.
Errores comunes que rompen los enlaces profundos (y cómo evitarlos)
Los enlaces profundos suelen fallar por razones mundanas: una suposición pequeña, una pantalla renombrada o un token “temporal” que acaba en todas partes.
Fallos que ves en producción (y correcciones)
-
Poner tokens de acceso en la URL (y filtrarlos a logs). Las query strings se copian, comparten, almacenan en el historial y quedan registradas en analítica y logs de fallos. Solución: pon solo un código corto y de un solo uso en el enlace, canjéalo dentro de la app y expíralo rápidamente.
-
Asumir que la app está instalada (sin fallback). Si un enlace abre una página de error o no hace nada, los usuarios abandonan. Solución: proporciona una página web de fallback que explique qué pasará y ofrezca la ruta normal de instalación. Incluso un simple “Abre la app para continuar” es mejor que el silencio.
-
No manejar múltiples cuentas en un dispositivo. Abrir la pantalla correcta para el usuario equivocado es peor que un enlace roto. Solución: cuando la app reciba un enlace, comprueba qué cuenta está activa, pide al usuario confirmar o cambiar de cuenta y luego continua. Si la acción requiere un workspace específico, incluye un identificador de workspace (no un secreto) y valídalo.
-
Romper enlaces cuando cambian pantallas o rutas. Si tu ruta está ligada a nombres de UI, los enlaces antiguos mueren cuando renombras una pestaña. Solución: diseña rutas estables basadas en intención (invite, ticket, order) y mantén versiones antiguas funcionando.
-
Sin trazabilidad cuando algo sale mal. Sin forma de reproducir lo ocurrido, soporte solo puede adivinar. Solución: incluye un request ID no sensible en el enlace, regístralo en servidor y app, y muestra un mensaje de error que incluya ese ID.
Una comprobación de realidad rápida: imagina un enlace de invitación enviado en un chat grupal. Alguien lo abre en un móvil de trabajo con dos cuentas, la app no está instalada en su tablet y el enlace se reenvía a un colega. Si el enlace contiene solo un código de invitación, soporta comportamiento de fallback, solicita la cuenta correcta y registra un request ID, ese único enlace puede tener éxito en todas esas situaciones sin exponer secretos.
Ejemplo: un enlace de invitación que abre la pantalla correcta siempre
Las invitaciones son un caso clásico: alguien envía un enlace a un compañero en un mensajero y el destinatario espera un toque que le lleve a la pantalla de invitación, no a la página principal genérica.
Escenario: un manager invita a un nuevo agente de soporte a unirse al workspace “Support Team”. El agente toca la invitación en Telegram.
Si la app está instalada, el sistema debería abrir la app y pasarle los detalles de la invitación. Si la app no está instalada, el usuario debería aterrizar en una página web sencilla que explique para qué sirve la invitación y ofrezca una ruta de instalación. Después de instalar y abrir por primera vez, la app aún debería poder terminar el flujo de invitación para que el usuario no tenga que buscar el enlace de nuevo.
Dentro de la app, el flujo es el mismo en Kotlin y SwiftUI:
- Lee el código de invitación del enlace entrante.
- Comprueba si el usuario está logueado.
- Verifica la invitación con tu backend y luego enruta a la pantalla correcta.
La verificación es el punto clave. El enlace no debe contener secretos como un token de sesión de larga duración. Debe llevar un código de invitación de uso único que solo sirva después de que tu servidor lo valide.
Lo que vive el usuario debe sentirse predecible:
- Sin iniciar sesión: verá una pantalla de login y luego volverá a la aceptación de la invitación tras el login.
- Con sesión: verá una confirmación “Unirse al workspace” y luego aterrizará en el workspace correcto.
Si la invitación está caducada o ya fue usada, no mandes al usuario a una página de error vacía. Muestra un mensaje claro y un siguiente paso: solicitar una nueva invitación, cambiar de cuenta o contactar al admin. “Esta invitación ya fue aceptada” es mejor que “Token inválido”.
Lista de verificación rápida y siguientes pasos
Los enlaces profundos solo se sienten “listos” cuando se comportan igual en todas partes: cold start, warm start y cuando el usuario ya está autenticado.
Lista de verificación rápida
Antes de lanzar, prueba cada punto en dispositivos reales y versiones de SO reales:
- El enlace abre la pantalla correcta en cold start y warm start.
- No hay nada sensible en la URL. Si debes pasar un token, que sea de corta duración y preferiblemente de un solo uso.
- Los enlaces desconocidos, expirados o ya usados degradan a una pantalla clara con un mensaje útil y una acción segura.
- Funciona desde apps de correo, navegadores, escáneres de QR y vistas previas en mensajería (algunas abrirán enlaces previamente).
- El logging te dice qué pasó (enlace recibido, ruta parseada, auth requerida, razón de fallo o éxito).
Una forma simple de validar el comportamiento es elegir un puñado de enlaces críticos (invitación, restablecimiento de contraseña, detalle de pedido, ticket de soporte, promoción) y pasarlos por el mismo flujo de pruebas: tocar desde correo, tocar desde chat, escanear un QR, abrir después de reinstalar.
Siguientes pasos (para mantenerlo sostenible)
Si los enlaces profundos empiezan a dispersarse por las pantallas, trata el enrutamiento y la auth como fontanería compartida, no como código por pantalla. Centraliza el parsing de rutas en un lugar y haz que cada destino acepte parámetros limpios (no URLs crudas). Haz lo mismo para auth: una única puerta que decide “continuar ahora” vs “iniciar sesión primero y luego continuar”.
Si quieres reducir el código personalizado, puede ayudar construir backend, auth y apps móviles juntos. AppMaster (appmaster.io) es una plataforma no-code que genera backends listos para producción y apps móviles nativas, lo que puede facilitar mantener alineados los nombres de rutas y los endpoints de redención de códigos de un solo uso conforme cambian los requisitos.
Si haces una sola cosa la próxima semana, haz esto: escribe tus rutas canónicas y el comportamiento de fallback exacto para cada caso de fallo, y luego implementa esas reglas en una única capa de enrutamiento.
FAQ
Un enlace profundo debe abrir exactamente la pantalla que indica el enlace, no una pantalla genérica o el panel principal. Si la app no está instalada, el enlace aún debe ayudar aterrizando en un lugar sensato y guiando al usuario de vuelta al mismo destino después de la instalación.
Universal Links (iOS) y App Links (Android) usan URLs web normales y pueden abrir la app cuando está instalada, con una degradación elegante cuando no lo está. Los esquemas personalizados son más fáciles de configurar pero pueden ser bloqueados o manejados de forma inconsistente por navegadores y otras apps, por lo que conviene usarlos como opción secundaria.
Muchas apps de correo y mensajería abren enlaces dentro de su propio navegador integrado, que puede no pasar el enlace al sistema igual que Safari o Chrome. Planifica un paso extra haciendo que la página web de fallback sea clara y manejando los casos en que el usuario aterriza primero en una página web.
En cold start tu app puede mostrar una pantalla de carga, ejecutar comprobaciones o cargar configuración antes de estar lista para navegar. La solución fiable es guardar inmediatamente el destino entrante, terminar la inicialización y luego “reproducir” la navegación una vez la app está lista.
No pongas tokens de acceso de larga duración, tokens de refresco, contraseñas ni datos personales en la URL, porque las URLs pueden registrarse, compartirse y almacenarse. Usa un código de corta duración y de un solo uso en el enlace y cámbialo por una sesión en tu backend después de que la app abra.
Analiza el enlace, guarda el destino pretendido y luego enruta según el estado de autenticación para que el login ocurra una sola vez y el usuario aterrice en la pantalla correcta después. Mantén el “return target” pequeño, con expiración corta, y bórralo después de usarlo.
Trata las rutas como un contrato compartido y centraliza el parsing y la validación en un solo lugar, luego pasa parámetros limpios a las pantallas en lugar de URLs crudas. Así evitas que cada pantalla invente sus propias reglas sobre parámetros opcionales, IDs faltantes y manejo de errores.
Primero verifica qué cuenta está activa y si coincide con el workspace o tenant que implica el enlace, luego pide al usuario que confirme o cambie de cuenta antes de mostrar contenido privado. Mejor un paso corto de confirmación que abrir la pantalla correcta bajo la cuenta equivocada.
Por defecto, abre la pantalla estable más cercana, como una página de lista, y muestra un mensaje corto que explique qué no se pudo abrir. Evita pantallas en blanco, fallos silenciosos o dejar al usuario en una pantalla de login sin contexto.
Prueba cada tipo de enlace importante en tres estados: app cerrada, app ya en memoria y app no instalada, y hazlo desde fuentes reales como correo, chats y lectores de QR. Si construyes con AppMaster (appmaster.io), puedes mantener alineados nombres de rutas y endpoints de redención de códigos de un solo uso entre backend y apps nativas mientras cambian los requisitos.


