Todos los artículos
mobiledeep linksiOSAndroid

Pruebas de deep links móviles con un túnel localhost

Los Universal Links en iOS y los App Links en Android requieren un archivo JSON servido por HTTPS en la raíz de tu dominio. iOS quiere /.well-known/apple-app-site-association. Android quiere /.well-known/assetlinks.json. localhost no puede servir ninguno sobre un hostname que un SO asocie con tu app. Así que tunelizamos.

El requisito real en cada plataforma

iOS Universal Links

El SO obtiene https://tu-dominio.com/.well-known/apple-app-site-association (o /apple-app-site-association) cuando tu app se instala. Compara el contenido contra el entitlement de dominios asociados de tu app. La obtención debe tener éxito sobre HTTPS real. iOS no confía en certificados autofirmados para esto, ni en localhost.

Android App Links

Android verifica los App Links obteniendo https://tu-dominio.com/.well-known/assetlinks.json. El archivo debe listar los fingerprints SHA-256 de tu app. La verificación ocurre al instalar y en el primer lanzamiento. La misma restricción: HTTPS real, dominio real.

Por qué localhost no puede hacerlo directamente

Incluso con mkcert dándote HTTPS confiable en el navegador de tu portátil, el SO móvil no confía en tu CA local. Y el dominio en tu apple-app-site-association tiene que coincidir con el dominio asociado en el entitlement de la app — que es un dominio real resoluble por DNS, no localhost.

Algunos equipos sortean esto con un dominio de staging dedicado. Funciona, pero el bucle de iteración es lento. Un túnel localhost te da una URL HTTPS real en un subdominio real que los dispositivos móviles alcanzan sin quejarse.

Cómo conectarlo

  1. Sirve tu apple-app-site-association y assetlinks.json desde tu servidor de desarrollo local en /.well-known/. Ambos archivos. Ambas rutas.
  2. Ejecuta npx portpreview 3000 (o el puerto que use tu servidor de desarrollo).
  3. Anota el hostname del túnel — algo como abc123.portpreview.dev.
  4. En el entitlement de dominios asociados de tu app iOS, añade applinks:abc123.portpreview.dev. En el intent filter de Android, añade el mismo host.
  5. Compila e instala la app en un dispositivo real (los simuladores tienen un comportamiento raro con Universal Links).
  6. Abre la URL desde otra app — Notas, Mail, un código QR — y mira cómo enruta a tu app instalada en vez del navegador.

El truco al tunelizar para deep links: el hostname del túnel cambia entre sesiones a menos que tengas un subdominio reservado. Cada rotación significa recompilar la app con la nueva entrada de dominio asociado. Si iteras a menudo en el comportamiento de deep links, consigue un subdominio reservado.

El content-type importa para apple-app-site-association

iOS espera el archivo sin extensión y con content-type application/json (iOS más nuevo) o application/pkcs7-mime (formato antiguo firmado). Casi todas las apps modernas usan la variante JSON plana. Asegúrate de que tu servidor de desarrollo devuelva el content-type correcto o iOS rechaza el archivo en silencio sin error útil.

Prueba primero desde un navegador de escritorio: abre https://tu-tunel.portpreview.dev/.well-known/apple-app-site-association y confirma que el JSON se renderiza y la cabecera content-type en dev tools es correcta. Si está mal, arréglalo antes de perseguir bugs de Universal Link en el simulador.

Las otras trampas de depuración de deep links

Desajuste de entitlement de la app

Si tu entitlement de dominios asociados dice applinks:abc.portpreview.dev pero tu apple-app-site-association lista def.portpreview.dev, iOS no obtiene nada. El hostname debe ser consistente.

Universal Link desde dentro de Safari

Tocar un Universal Link dentro de Safari (la misma app donde está la pestaña) a veces abre en Safari en vez de la app. Es por diseño — iOS evita el truco de "abrir app cada vez que el usuario hace clic en un enlace". Prueba desde Notas o Mail.

Android App Links y digital asset links

Android también admite esquemas de URL personalizados (tuapp://ruta), que no necesitan HTTPS ni assetlinks.json. Son más fáciles de probar pero menos seguros — cualquier app puede registrar el mismo esquema. Para deep linking con calidad de producción, los App Links son la respuesta.

El flujo de pruebas móviles que usamos

Para un proyecto que entrega iOS y Android con deep links:

  1. Arranca el backend que sirve los archivos .well-known.
  2. Tunelízalo con npx portpreview 3000.
  3. Una vez la URL del túnel sea estable para la sesión (o usa un subdominio reservado), actualiza las entradas de dominio asociado de la app y compila.
  4. QA en dispositivos físicos — tanto instalaciones nuevas como actualizaciones.
  5. Para combos de OAuth-y-deep-link (proveedores de inicio de sesión que redirigen de vuelta a la app), combínalo con pruebas de callbacks OAuth.

La configuración es molesta la primera vez. Después, la URL del túnel es solo otra variable de entorno en el scheme de Xcode y Gradle.

Para patrones más amplios de pruebas móviles, mira pruebas móviles con un túnel localhost. Únete a la lista de espera de PortPreview.

Preguntas frecuentes

¿Puedo probar Universal Links de iOS en localhost?
No directamente. Los Universal Links requieren que iOS obtenga apple-app-site-association sobre HTTPS real desde un dominio real. localhost no califica. Un túnel te da una URL HTTPS en un subdominio de túnel que iOS asociará con tu app una vez la listes en el entitlement de dominios asociados.
¿Qué content-type debe usar apple-app-site-association?
iOS moderno espera application/json. Las versiones antiguas también aceptaban application/pkcs7-mime para archivos firmados. La mayoría de las apps hoy usan la variante JSON plana. Si iOS no toma tu archivo, revisa primero la cabecera content-type desde un navegador de escritorio.
¿Los Android App Links necesitan un túnel para pruebas locales?
Sí si quieres la verificación completa de App Links (no esquemas de URL personalizados). Android obtiene assetlinks.json sobre HTTPS real desde el dominio de tu intent filter. Un túnel provee una URL HTTPS real en un dominio que el dispositivo puede verificar. Los esquemas personalizados no lo necesitan pero son menos seguros.