Una aplicación móvil Android / iOS para Gladys: enfoque y primeros resultados

Hola a todos,

Tras el directo de ayer y la solicitud de aplicación móvil, lancé a Claude sobre el tema solo para ver. En menos de 15 minutos, me sacó una especificación completa y una demo en mi teléfono Android donde encuentro mis dashboards, escenas… todo Gladys en realidad. Estoy impresionado… :star_struck:

Aquí está su análisis:
El « yo » en el mensaje es Claude, yo no habría sido capaz de sacar todo esto en 15 minutos.

He comenzado a trabajar en una aplicación móvil nativa para Gladys, y me gustaría compartir el enfoque antes de seguir adelante — especialmente la elección de arquitectura, que me parece el punto más discutible y, por lo tanto, aquel en el que sus comentarios serán más útiles.

La base es funcional: la aplicación funciona en mi teléfono, conectada a mi instancia, con todos mis dashboards y mis cámaras.


El problema

Hoy en día, Gladys en móvil es el navegador o la adición a la pantalla de inicio. Funciona, pero tiene un límite:

  • no hay notificaciones confiables;
  • no hay seguimiento de presencia en segundo plano;
  • no hay presencia en las tiendas;
  • y el service worker actual (front/old-sw.js) se desinstala voluntariamente en cada activación — por lo tanto, no hay base PWA utilizable sobre la cual construir.

La elección de arquitectura: integrar el front existente

Esta es la decisión estructurante. Tres opciones estaban sobre la mesa.

Opción A — React Native, una aplicación nativa distinta

Una aplicación reescrita, consumiendo la API REST y el WebSocket de Gladys. Mejor rendimiento, sensación nativa real.

Pero hay que reescribir toda la interfaz: 23 rutas, 22 tipos de widgets de dashboard, el editor de escenas, el mapa, el chat, las páginas de integración. Y sobre todo, hay que mantener dos interfaces en paralelo: cada evolución del front web debería ser reportada manualmente a la aplicación móvil, con el riesgo de divergencia que esto implica. Para un proyecto comunitario, me parece insostenible a largo plazo.

Opción B — Mejorar la PWA

Reparar el service worker, agregar el caché sin conexión y el Web Push. Ligero, sin necesidad de gestionar tiendas.

Pero esto no resuelve ni la geolocalización en segundo plano, ni los widgets de pantalla de inicio, ni la presencia en las tiendas. Y en iOS, el Web Push sigue limitado. Seguiríamos con los mismos límites.

Opción C — Capacitor + el front existente ← seleccionado

El front de Gladys actual (Preact / Vite) se compila exactamente como hoy, y luego se integra en una WebView nativa mediante Capacitor. Las capacidades que el web no puede ofrecer se exponen a través de plugins, detrás de una capa de abstracción.

front/ (Preact, sin cambios)
   └── build/ ──> WebView Capacitor
                   ├── android/
                   └── ios/

+ plugins nativos: push, geoloc, biométrica,
                   mDNS, almacenamiento seguro, widgets

¿Por qué esta elección?:

  • Un solo código base. Toda evolución del front beneficia al web, a Android y a iOS sin necesidad de portabilidad. Este es el punto decisivo para un proyecto comunitario.
  • Reutilización casi total. Pantallas, widgets, acciones, traducciones, tema oscuro: todo se mantiene tal cual.
  • Nada perdido en lo nativo. Notificaciones push, geolocalización por zonas, biométrica, descubrimiento mDNS, widgets de pantalla de inicio siguen accesibles a través de los plugins.
  • Reversible. Si el enfoque muestra sus límites, el front sigue siendo el front — no se ha creado una deuda paralela.

El principio director: el shell nativo no incluye ninguna lógica de negocio. Expone capacidades, el front las consume. Toda funcionalidad nativa es opcional, y su ausencia (permiso denegado, plataforma no compatible) nunca rompe la aplicación.


Lo que se ha hecho: la base de conexión

El primer lote responde a una pregunta que el front web nunca se plantea: ¿a qué instancia hablar?

En el web, la respuesta es trivial — el front es servido por la instancia misma. Una aplicación móvil, en cambio, se distribuye en un solo build para todos y debe ser configurada por el usuario.

Concretamente:

  • Configuración resuelta en tiempo de ejecución. front/src/config.js leía process.env, que Vite reemplaza por literales en el build. El objeto exportado ahora es mutable, con una lista blanca: solo las claves de conexión son sobrescribibles, para que un perfil almacenado malformado no pueda activar el modo demo.
  • Pantalla de primera conexión pidiendo la dirección de la instancia, con prueba de la dirección antes del registro.
  • Perfiles multi-instancia (residencia principal, secundaria, instancia de prueba), almacenados en el Keychain / Keystore.
  • Conmutación automática red local ↔ Gladys Plus, con una sonda en /api/v1/ping.
  • Página de ajustes para gestionar estas instancias — oculta en el web, donde no tendría sentido.

Impacto en el front web: nulo. El código nativo se elimina por completo del bundle web mediante el tree-shaking, y los valores de build siguen siendo los mismos. Verificado manualmente en el navegador.


Tres cosas que la especificación no había previsto

Esta es la parte más instructiva, y la que puede servir a otros.

1. La regla mixed-content, y el compromiso que impone

Para que el cifrado de extremo a extremo de Gladys Plus funcione, se necesita crypto.subtle, que solo está disponible en un contexto seguro. Capacitor lo obtiene sirviendo la WebView en https://localhost.

Excepto que una página https:// no puede llamar ni a http:// ni a ws:// — Chromium lo bloquea. Sin embargo, una instancia Gladys local suele estar en HTTP simple. La configuración de seguridad de red de Android no cambia nada: es una política del navegador, no de la plataforma.

El plugin CapacitorHttp resuelve el caso de las solicitudes HTTP (salen por la capa nativa), pero no el de los WebSockets, que siguen bloqueados. Sin WebSocket, no hay tiempo real.

De ahí el arbitraje, que hay que conocer:

androidScheme Contexto seguro Gladys Plus HTTP local WebSocket
https sí OK a través de CapacitorHttp bloqueado
http no no disponible OK OK

Para este primer lote, he elegido http: el objetivo era la conexión a la instancia local, que exige el WebSocket. Es explícitamente temporal. La salida limpia es servir la instancia en HTTPS detrás de un reverse proxy (Caddy, nginx, Traefik): todo pasa entonces en https/wss, sin bloqueos, contexto seguro conservado. Es también la única vía que valdrá para iOS.

Si tienes una opinión sobre este punto, me interesa mucho: ¿deberíamos asumir exigir una instancia en HTTPS para el uso de Gladys Plus desde la aplicación móvil, o es mejor invertir en un plugin WebSocket nativo para permanecer en https?

2. Un cliente HTTP que congelaba su URL

HttpClient capturaba config.localApiUrl en su constructor — ejecutado al cargar el módulo, por lo tanto antes de que el perfil móvil se resolviera. En la WebView, window.location.origin vale https://localhost, es decir, la aplicación misma: todas las solicitudes habrían ido en vano.

Se ha convertido en un getter que lee la configuración en cada solicitud. En el web, el valor nunca cambia, por lo tanto, comportamiento idéntico.

3. viewport-fit=cover faltante

La barra superior de la aplicación pasaba por debajo del reloj y los iconos de notificación de Android. La causa no era el encabezado, sino el <meta viewport>: suCon viewport-fit=cover, todas las variables env(safe-area-inset-*) valen cero.

Detalle interesante: el front de Gladys ya utiliza safe-area-inset-bottom en cinco lugares (chat, dashboard, lista de dispositivos). Por lo tanto, no hacían nada en móvil. La corrección las reactiva todas.


Validación en dispositivo

Probado en un Xiaomi con Android 16 (WebView Chromium 151), contra una instancia real:

Punto Resultado
Web Crypto (RSA-OAEP 2048, ECDSA P-256, AES-GCM, PBKDF2) 9/9 en contexto https
WebSocket hacia una instancia HTTP Handshake en 44 ms
MSE / hls.js (cámaras) Disponible
Recorrido completo: configuración, inicio de sesión, dashboards, cámaras Funcional

Una medida contraintuitiva: PBKDF2 100,000 iteraciones en 15 ms en el teléfono, frente a 45 ms en mi PC. La implementación de Android está acelerada por hardware. El riesgo de un desbloqueo lento en móvil, que había anticipado, no existe.

Una lección también: mi prototipo de validación inicial daba tres luces verdes, y sin embargo no detectó ninguno de los dos bloqueos reales. Probaba un WebSocket desde una página http://localhost — por lo tanto sin mixed-content — y no emitía ninguna solicitud API. Hay que validar crypto, WebSocket y una solicitud API real en la configuración de esquema definitiva, no aisladamente.


Lo que queda por hacer

Lote 2 — ergonomía táctil: barra de pestañas baja, gesto de retroceso de Android, arrastrar y soltar del dashboard con el dedo, comportamiento del teclado.

Lote 3 — notificaciones y presencia. Es la principal aportación de la app en comparación con el navegador, y el único lote que requiere desarrollo en el lado del servidor: una ruta de registro de tokens push, y una acción de escena user.send-push-notification.

Un punto a decidir colectivamente: una instancia autoalojada no puede hablar directamente con FCM o APNs, por falta de claves de servicio — que no se pueden distribuir en una imagen pública. El relay por Gladys Plus es la vía natural (contenido cifrado de extremo a extremo, la notificación solo lleva un desencadenante), con una caída a las notificaciones locales sin suscripción. ¿Es un compromiso aceptable para la comunidad?

Lote 4 — integración del sistema: descubrimiento mDNS (el servidor ya publica el servicio), widgets de pantalla de inicio, App Intents iOS y accesos directos de Android, asistente de voz.

Lote 5 — publicación: CI de compilación y firma, fichas de la tienda, beta.


Dos puntos abiertos en los que necesito ayuda

iOS no ha podido ser validado: no tengo un Mac. Cuatro incógnitas persisten — contexto seguro en capacitor://, MSE en WKWebView, entitlement multicast para el mDNS (concedido caso por caso por Apple, con plazos impredecibles), y el comportamiento de ATS frente a una instancia en HTTP. Si alguien tiene un Mac y media hora, ejecutar el prototipo de validación resolvería estos cuatro puntos de una vez.

La cuestión del HTTPS local. Condiciona el uso de Gladys Plus desde la app. Exigir una instancia en HTTPS es correcto técnicamente, pero añade un paso de configuración a usuarios que no lo necesitan hoy. Sus opiniones me interesan.


La especificación completa está adjunta a continuación (despliegue la sección): arquitectura, conexión, capacidades nativas, seguridad, puntos duros, lotes de entrega, compilación y aceptación. El código está en una rama dedicada.

No duden en cuestionar la elección de arquitectura — es precisamente el momento en que aún es fácil cambiarla.

Gracias por su lectura.


[details=« 📄 Especificación técnica completa (despliegue) »]

Especificación técnica — v1

Portear el front de Gladys existente (Preact / Vite) a una aplicación nativa de Android e iOS a través de Capacitor, con paridad funcional completa y las capacidades que solo una app nativa puede ofrecer: notificaciones push, geolocalización en segundo plano, desbloqueo biométrico, descubrimiento de la instancia en la red local.

Base de código Gladys 5.0.2
Enfoque Capacitor + front existente
Objetivos Android 8+ · iOS 15+
Ámbito v1 Paridad completa
Fecha 30 de agosto de 2026

Índice

  1. Contexto y objetivos
  2. Estado del existente
  3. Arquitectura objetivo
  4. Conexión y autenticación
  5. Capacidades nativas
  6. Adaptaciones de interfaz
  7. Seguridad
  8. Puntos duros identificados
  9. Lotes de entrega
  10. Compilación, CI y publicación
  11. Pruebas y aceptación
  12. Fuera del ámbito de la v1

1. Contexto y objetivos

Gladys Assistant se usa hoy en día en móvil a través del navegador o la adición a la pantalla de inicio. Este enfoque tiene sus límites: no hay notificaciones fiables, no hay seguimiento de presencia en segundo plano, no hay presencia en las tiendas, y un service worker que, en el estado actual del repositorio, se desinstala voluntariamente en cada activación.

Objetivos

  • Paridad funcional con el front web: dashboard, dispositivos, escenas, cámaras, chat, calendario, mapa, ajustes e integraciones.
  • Notificaciones push nativas desencadenadas por las escenas de Gladys, con acciones rápidas desde la notificación.
  • Presencia automática mediante geolocalización en segundo plano, alimentando la detección de zonas de Gladys.
  • Conexión sin fricción: descubrimiento automático de la instancia en la red local, cambio transparente local ↔ Gladys Plus.
  • Una sola base de código para el web, Android e iOS: toda evolución del front beneficia a los tres objetivos sin portabilidad.

Principios rectores

  • El respeto a la vida privada sigue siendo la regla. Sin telemetría, sin SDK analítico de terceros. El cifrado de extremo a extremo de Gladys Plus se preserva sin excepción.
  • El front sigue siendo la fuente de verdad. El shell nativo no incorpora ninguna lógica de negocio: expone capacidades, el front las consume.
  • Degradación limpia. Toda funcionalidad nativa es opcional; su ausencia (permiso denegado, plataforma no compatible) nunca rompe la app.

2. Estado del existente

El front es una aplicación Preact 10 construida por Vite 6, con enrutamiento preact-router, estado global unistore, e internacionalización preact-i18n en tres idiomas (fr, en, de). Se sirve ya sea por el servidor Gladys local (server/static), ya sea por Gladys Plus.

Lo que es directamente reutilizable

Elemento Ubicación Estado
Pantallas y enrutamiento front/src/routes/ Tal cual
Widgets de dashboard (22 tipos) front/src/components/boxs/ Tal cual
Acciones y store unistore front/src/actions/ Tal cual
Traducciones fr / en / de front/src/config/i18n/ Tal cual
Tema y modo oscuro front/src/style/ Tal cual

Lo que debe ser adaptado

Elemento Ubicación Naturaleza del trabajo
Configuración de URL front/src/config.js Las URL están fijas en el momento del vite build a través de process.env. Hay que hacerlas dinámicas en tiempo de ejecución. Punto duro
Almacenamiento de sesión front/src/utils/Session.js, front/src/utils/keyValueStore.js localStorage en claro. Reemplazar por un almacenamiento cifrado apoyado en el Keychain / Keystore. Punto duro
Cliente HTTP front/src/utils/HttpClient.js :white_check_mark: Hecho (lote 1). localApiUrl estaba capturado en el constructor, ejecutado al cargar el módulo a través de getDefaultState() — por lo tanto antes de la resolución del perfil móvil. Se ha convertido en un getter que lee la configuración en cada solicitud. En la web el valor nunca cambia: comportamiento idéntico.
Zonas seguras front/index.html, front/src/template.html :white_check_mark: Hecho (lote 1). El <meta viewport>No tenía viewport-fit=cover, lo que ponía todas las variables env(safe-area-inset-*) a cero —incluyendo los cinco usos existentes de safe-area-inset-bottom.
WebSocket front/src/utils/Session.js Reconexión a intervalos fijos de 1 s, sin conciencia del ciclo de vida de la aplicación. Debe implementarse un backoff y una reacción a los eventos de suspensión.
Service worker front/old-sw.js Se desinstala a sí mismo al activarse. No se utiliza en el shell nativo; debe conservarse tal cual para la web a fin de purgar las antiguas caches.
Cámaras (HLS) front/src/components/boxs/camera/ Cargador hls.js personalizado para inyectar el token. Debe validarse en WebView, especialmente el HLS nativo en iOS.
Arrastre y suelta front/src/utils/dragAndDropBackend.js Ya cambia entre backends HTML5 y táctiles; debe revalidarse en la WebView.

Del lado del servidor

No se requieren modificaciones para los lotes 1 y 2: la API REST (server/api/controllers/, 25 controladores) y el WebSocket ya cubren las necesidades. Dos adiciones al servidor son necesarias más adelante: el registro de los tokens push (lote 3) y la acción de escena correspondiente.


3. Arquitectura objetivo

El front se compila exactamente como hoy, luego se empaqueta en una WebView nativa por Capacitor. Las capacidades que la web no puede ofrecer se exponen al front en forma de plugins Capacitor, detrás de una capa de abstracción que devuelve valores neutros cuando la app se ejecuta en un navegador.

┌─────────────────────────────────────┐
│ APLICACIÓN MÓVIL                  │
│                                     │        ┌──────────────────────────────┐
│  ┌───────────────────────────────┐  │        │ Red local                 │
│  │ WebView — front Preact        │  │───────>│ REST /api/v1 + WebSocket     │
│  │ routes/ · components/         │  │        │ Token portador, latencia min.  │
│  │ ─────────────────────────────  │  │        │ Descubrimiento mDNS              │
│  │ utils/native/ — abstracción   │  │        └──────────────┬───────────────┘
│  │ no-op en la web              │  │                       │
│  └───────────────┬───────────────┘  │        ┌──────────────▼───────────────┐
│                  ▼                  │        │ Gladys Plus — a distancia     │
│  ┌───────────────────────────────┐  │───────>│ gladys-gateway-js            │
│  │ Shell nativo Capacitor         │  │        │ RSA + ECDSA, extremo a extremo    │
│  │ push · geoloc · biométrica     │  │        │ El servidor no descifra nada │
│  │ mDNS · almacenamiento · widgets │  │        └──────────────┬───────────────┘
│  └───────┬───────────────┬───────┘  │                       │
└──────────┼───────────────┼──────────┘                       │
           ▼               ▼                   ┌──────────────▼───────────────┐
    ┌────────────┐  ┌────────────┐             │ Instancia Gladys              │
    │ Android    │  │ iOS        │             │ Node 24 · SQLite             │
    │ Kotlin·FCM │  │ Swift·APNs │             │ 39 servicios                  │
    └────────────┘  └────────────┘             └──────────────────────────────┘

La selección de la ruta de red es automática y se reevaluada cada vez que cambia la red; el usuario puede forzarla desde la configuración. El front compilado es idéntico al servido en la web: solo se añade la capa utils/native/, y esta devuelve implementaciones neutras fuera de móvil.

Estructura de directorios

Un nuevo directorio mobile/ en la raíz, junto a front/ y server/:

mobile/
├── capacitor.config.ts     configuración, apunta a front/build
├── package.json            dependencias de Capacitor únicamente
├── android/                proyecto Android generado, versionado
├── ios/                    proyecto Xcode generado, versionado
├── plugins/                plugins caseros (mDNS, descubrimiento)
└── resources/              iconos y pantallas de inicio fuentes

front/src/utils/native/     capa de abstracción, en el front
├── index.js                detección de plataforma
├── push.js
├── geolocation.js
├── biometrics.js
├── secureStorage.js
└── discovery.js

Plugins seleccionados

Necesidad Plugin Origen
Notificaciones push @capacitor/push-notifications Oficial
Notificaciones locales @capacitor/local-notifications Oficial
Geolocalización puntual @capacitor/geolocation Oficial
Geolocalización en segundo plano @capacitor-community/background-geolocation Comunidad
Almacenamiento cifrado capacitor-secure-storage-plugin Comunidad
Biometría @aparajita/capacitor-biometric-auth Comunidad
Estado de la red @capacitor/network Oficial
Ciclo de vida de la app @capacitor/app Oficial
Barra de estado y muescas @capacitor/status-bar Oficial
Descubrimiento mDNS gladys-discovery Por escribir

Elección de diseño — Cada plugin de la comunidad es un riesgo de mantenimiento. La capa utils/native/ existe precisamente para que el reemplazo de un plugin abandonado solo afecte a un archivo, nunca a las pantallas.


4. Conexión y autenticación

Esta es la parte más estructurante de la especificación. El front actual solo conoce un modo a la vez, decidido en el momento de la compilación por la variable GATEWAY_MODE. La app móvil debe gestionar los dos simultáneamente y cambiar de uno a otro sin intervención.

4.1 Configuración en tiempo de ejecución

front/src/config.js lee process.env, que Vite reemplaza por literales en el momento de la compilación. Es necesario introducir una resolución en tiempo de ejecución:

  • En la web, el comportamiento actual se conserva exactamente igual: ninguna regresión.
  • En móvil, las URL provienen del perfil de conexión activo, almacenado en el almacenamiento cifrado.

En concreto, config.js expone un objeto mutable alimentado al inicio por utils/native/, antes del primer renderizado. Los módulos que importan la configuración hoy en día permanecen inalterados.

4.2 Perfiles de conexión

La app gestiona varios perfiles — un hogar principal, una residencia secundaria, una instancia de prueba. Cada perfil registra:

Campo Contenido
id UUID local
name Nombre mostrado, introducido por el usuario
mode local, gateway o auto
localUrl URL de la instancia en la red local
localFingerprint Huella del certificado, si HTTPS auto-firmado
ssids Redes Wi-Fi donde se aplica el modo local
credentials Referencia al almacenamiento cifrado, nunca el valor

4.2 bis Las tres URL de Gladys Plus

No confundir — tienen roles distintos:

URL Rol Utilizada por
https://api.gladysgateway.com La API de la pasarela config.gladysGatewayApiUrl, llamada por gladys-gateway-js
https://plus.gladysassistant.com El front web de Gladys Plus (el mismo código, en modo gateway) Enlaces salientes: suscripción, facturación
https://gladysassistant.com/plus/ Página de marketing utils/gladysPlusUrl.js (enlaces de inscripción)

La app móvil habla con la API, nunca con el front alojado: ella es el front.
El valor por defecto de config.js es por lo tanto ya correcto y no tiene que ser
modificado.

Por otro lado, los flujos que no pueden desarrollarse en la app — gestión de
la suscripción, facturación Stripe — deben abrir plus.gladysassistant.com
en el navegador del sistema, y no en la WebView: un túnel de pago en una WebView
aplicativa es rechazado por las dos tiendas. El plugin
@capacitor/browser (pestaña del sistema) es el vehículo adecuado.

4.3 Descubrimiento de la instancia local

El servidor Gladys ya publica un servicio mDNS (server/lib/mdns/). El plugin gladys-discovery a escribir consulta este servicio y presenta las instancias encontradas:

  • Android: NsdManager, con adquisición de un MulticastLock durante la búsqueda.
  • iOS: NWBrowser (Network framework). Requiere el entitlement com.apple.developer.networking.multicast, que hay que solicitar explícitamente a Apple, y la declaración NSBonjourServices en Info.plist.

En caso de fallo en el descubrimiento, la introducción manual de una dirección sigue siendo siempre posible: nunca es un camino de repuesto oculto, sino una opción visible desde la primera pantalla.

4.4 Conmutación local ↔ remoto

En modo auto, la app elige la ruta de red en cada inicio, en cada retorno al primer plano y en cada cambio de conectividad señalado por @capacitor/network:

  1. Si un perfil local está configurado, se intenta una solicitud GET /api/v1/ping con un tiempo de espera de 1,5 segundos.
  2. En caso de éxito, se mantiene el modo local: latencia mínima, ninguna dependencia externa.
  3. En caso de fallo y si una cuenta Gladys Plus está vinculada, la app cambia a la pasarela.
  4. Si ninguna ruta responde, se muestra una pantalla fuera de línea con los últimos datos conocidos y un botón de reanudación.

La conmutación reconstruye el cliente HTTP y la conexión WebSocket. Se indica discretamente en la interfaz (un indicador en el encabezado), nunca mediante una ventana emergente bloqueante.

Atención — Los dos modos no utilizan el mismo almacén de tokens ni el mismo cliente: Session + HttpClient para el local, GatewaySession + GatewayHttpClient para Gladys Plus. El cambio debe vaciar las cachés de solicitudes en vuelo de HttpClient (la Map pendingRequests) para evitar que una respuesta del antiguo camino se asigne al nuevo.

4.5 Cifrado de extremo a extremo

GatewaySession se basa en @gladysassistant/gladys-gateway-js, que recibe window.crypto. En una WebView, la Web Crypto API solo está disponible en un contexto seguro: los esquemas capacitor:// (iOS) y https:// (Android) lo son, a diferencia de http://. Este punto debe validarse desde el lote 1, ya que condiciona todo el acceso remoto.

Las claves serializadas (gateway_serialized_keys) están hoy en localStorage. En móvil, van al almacenamiento cifrado adosado al Keychain (iOS) o al Keystore con cifrado de hardware (Android).

4.6 Autenticación de dos factores

El flujo 2FA existente (actions/login/loginGateway.js) se conserva sin modificación, incluyendo la generación de los códigos de recuperación y el pegado desde el portapapeles. Se añade un relleno automático del código desde las sugerencias del teclado a través del atributo autocomplete="one-time-code".

4.7 Bloqueo biométrico

Opcional, activable en la configuración. Cuando está activo, se solicita un desbloqueo por huella o reconocimiento facial al inicio y después de un tiempo de inactividad configurable (por defecto 5 minutos en segundo plano). La caída es el código del dispositivo; nunca hay un código propio de Gladys que memorizar además.


5. Capacidades nativas

5.1 Notificaciones push

Esta es la principal aportación de la app en comparación con la web. Implica un desarrollo en el lado del servidor Gladys, y no solo en el lado móvil.

Registro. En el primer lanzamiento después de aceptar el permiso, la app obtiene un token FCM (Android) o APNs (iOS) y lo envía a la instancia. Nueva ruta a crear: POST /api/v1/user/push_token, con el token, la plataforma y el identificador de sesión. El token está vinculado a la sesión existente: revocar una sesión revoca el push asociado.

Envío. Una instancia Gladys autoalojada no puede hablar directamente con FCM o APNs sin claves de servicio — que no pueden distribuirse en una imagen pública. Dos caminos:

  • A través de Gladys Plus (recomendado) — La instancia transmite la notificación a la pasarela, que posee las claves y reenvía a FCM / APNs. El contenido útil está cifrado de extremo a extremo; la notificación transportada solo contiene un desencadenante, la app recupera el contenido real de la instancia al recibirla.
  • Sin Gladys Plus — Recurso a las notificaciones locales: mientras el WebSocket esté vivo, la app programa ella misma una notificación. Funciona en segundo plano reciente, no después de una suspensión prolongada. Este límite debe anunciarse claramente en la configuración, no descubierto en el uso.

Disparador desde una escena. Se agrega una nueva acción de escena user.send-push-notification, con destinatarios, título, mensaje y, opcionalmente, una imagen de cámara. Debe declararse obligatoriamente en el esquema Joi de server/models/scene.js, de lo contrario, el registro de la escena fallará con un código 422 sin mensaje explícito.

Acciones rápidas. Las notificaciones pueden incluir hasta tres botones de acción, definidos en la escena: ejecutar otra escena, encender o apagar un dispositivo, abrir una cámara. Estas acciones se procesan sin abrir la aplicación cuando la red lo permite.

5.2 Geolocalización y presencia

Gladys ya tiene server/lib/location/ y la gestión de zonas. La aplicación alimenta POST /api/v1/location:

  • Seguimiento por zonas en lugar de seguimiento continuo: la aplicación se suscribe a las entradas y salidas de las zonas definidas en Gladys. El costo en batería es mucho menor que un registro periódico.
  • Precisión reducida por defecto: la posición solo se transmite al cruzar una zona, no de manera continua.
  • Cola de espera sin conexión: los eventos capturados sin red se guardan localmente y se envían al reconectarse, con la fecha y hora reales.
  • Interruptor global visible en la configuración, y detención inmediata del seguimiento cuando se desactiva.

Restricción de la tienda — La geolocalización en segundo plano es el motivo de rechazo más frecuente en la App Store. Se requiere: una explicación clara antes de solicitar el permiso, una justificación precisa en NSLocationAlwaysAndWhenInUseUsageDescription, y una aplicación completamente funcional si se deniega el permiso. En Android 13+, el permiso ACCESS_BACKGROUND_LOCATION se solicita en un segundo paso, después del permiso de primer plano.

5.3 Widgets de pantalla de inicio

Escritos nativamente: WidgetKit en SwiftUI en iOS, Glance en Android. Leen una instantánea de datos escrita por la aplicación en un espacio compartido (App Group en iOS, SharedPreferences en Android), actualizada cada vez que la aplicación pasa a primer plano y cada vez que se recibe una notificación.

  • Escenas: hasta cuatro escenas favoritas, ejecutables con un toque.
  • Temperatura: temperatura de una habitación elegida.
  • Dispositivos: estado y conmutación de dos a cuatro dispositivos.

Los widgets no inician una sesión propia: delegan en la aplicación, que ejecuta la acción. Si la sesión ha expirado, el widget muestra un estado de reconexión necesaria en lugar de un error.

5.4 Accesos directos del sistema

  • iOS: exposición de escenas en App Intents, lo que las hace disponibles en Accesos directos, Siri y el botón de acción.
  • Android: accesos directos dinámicos en el icono de la aplicación para las escenas favoritas.

5.5 Asistente de voz

El front ya tiene una cadena de voz completa: speechCommandRecorder.js, recordUntilSilence.js, speechTtsPlayback.js, respaldada por gateway.stt.js y gateway.processVoiceMessage.js en el lado del servidor.

En WebView, el acceso al micrófono requiere el permiso nativo (NSMicrophoneUsageDescription, RECORD_AUDIO) y la autorización a nivel de WebView. En Android, esta última pasa por onPermissionRequest, que debe manejarse en el shell nativo. La reproducción TTS debe configurar la categoría de sesión de audio en iOS para no ser cortada por el modo silencioso.

5.6 Cámaras

El flujo HLS se lee a través de hls.js con un cargador personalizado que inyecta el token de autenticación. En WebView:

  • En iOS, el HLS nativo de <video>no permite agregar encabezados; hls.js` a través de MSE sigue siendo necesario. La compatibilidad con MSE en WKWebView debe verificarse pronto.
  • El modo de pantalla completa de video requiere allowsInlineMediaPlayback y una gestión explícita de la rotación.
  • La reproducción debe pausarse al pasar a segundo plano y reanudarse al volver, para no consumir batería y datos innecesariamente.

6. Adaptaciones de interfaz

La interfaz sigue siendo la del front web. Las adaptaciones son específicas, y ninguna debe degradar la experiencia en el navegador.

Zonas seguras y muescas. Aplicación de env(safe-area-inset-*) en el encabezado, la navegación inferior y los modales. El viewport pasa a viewport-fit=cover en front/src/template.html, sin efecto en la web.

Navegación. La barra lateral actual se convierte en una barra de pestañas inferior en pantalla estrecha: Dashboard, Dispositivos, Escenas, Chat, Ajustes. El gesto de retroceso de Android se asocia a preact-router a través de @capacitor/app, con una confirmación de salida solo en la raíz.

Objetivos táctiles y gestos.

  • Todo objetivo interactivo tiene al menos 44 × 44 puntos.
  • El arrastrar y soltar del dashboard utiliza el backend táctil de react-dnd, ya presente, con una pulsación larga para armar el movimiento y evitar conflictos con el desplazamiento.
  • Tirar para actualizar en las pantallas de lista, desactivado durante una edición en curso.

Teclado. Redimensionamiento de la vista al abrir el teclado en lugar de superposición, tipos de entrada adaptados (inputmode="numeric" para los códigos, type="email"), y desplazamiento automático hacia el campo activo.

Modo oscuro. El tema oscuro de Gladys se basa en una inversión CSS global, con la clase dark-mode-no-invert para los elementos que deben mantener sus colores reales. Este mecanismo se conserva tal cual. Dos puntos nuevos: el color de la barra de estado nativa debe seguir el tema, y la inversión no debe aplicarse a la pantalla de inicio nativa.

Modo tableta. El modo tableta existente (routes/dashboard/SetTabletMode.jsx) cobra todo su sentido en una tableta mural: se añade el mantenimiento de la pantalla encendida, el bloqueo de la orientación y un modo de pantalla completa inmersivo. El bloqueo por código existente se conserva.


7. Seguridad

Almacenamiento de secretos

Ningún secreto permanece en localStorage en móvil. Migran al almacenamiento cifrado: tokens de acceso y actualización, claves serializadas Gladys Plus, huellas de claves públicas, token de autenticación doble. Permanecen en el almacenamiento ordinario las preferencias no sensibles: idioma, modo oscuro, casa seleccionada.

HTTP en claro en la red local

Una instancia Gladys local se sirve muy a menudo en http://. Sin embargo, Android bloquea el tráfico en claro por defecto desde la versión 9, e iOS a través de ATS.

Plataforma Mecanismo Alcance
Android network_security_config.xml Autorización del tráfico en claro restringido a los rangos privados: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, más .local
iOS NSAllowsLocalNetworking Excepción ATS limitada a la red local, sin desactivación global

Nunca se trata de NSAllowsArbitraryLoads ni de cleartextTrafficPermitted global: estos dos ajustes desarman la protección en todo Internet y son un motivo de rechazo en la revisión de Apple.

Certificados auto-firmados

Para una instancia local en HTTPS auto-firmado, la app propone el anclaje de la huella del certificado en el momento de añadir el perfil, con visualización de la huella para verificación. Ninguna aceptación ciega; un cambio de huella bloquea la conexión y exige una confirmación explícita.

Protección del contenido

  • Ocultación del contenido en el selector de apps (FLAG_SECURE opcional en Android, vista de superposición en iOS), activable en los ajustes.
  • Ninguna captura de pantalla de flujo de cámara en las vistas del sistema cuando la opción está activa.
  • Borrado completo del almacenamiento cifrado al desconectarse.

Lo que la app no hace

  • Ningún SDK de analítica, publicidad o seguimiento.
  • Ningún informe de errores automático de terceros; en caso de necesidad, un envío manual, explícito y consentido.
  • Ningún dato de la casa transita por un servidor distinto a la instancia del usuario o Gladys Plus, cuyo contenido está cifrado de extremo a extremo.

8. Puntos duros identificados

Estos siete puntos son aquellos que pueden hacer desbordar el calendario. Se han colocado voluntariamente lo más pronto posible en el planificación, para que su coste real sea conocido antes de haber invertido en el resto.

Punto Riesgo Tratamiento
Web Crypto en WebView Si window.crypto.subtle está indisponible, todo el acceso remoto Gladys Plus cae. Prototipo de validación desde la primera semana, en las dos plataformas. Los esquemas capacitor:// y https:// son contextos seguros: el riesgo es bajo pero el impacto total.
Configuración congelada en el build config.js se resuelve por Vite en el build: la app no puede cambiar de instancia. Reestructuración en configuración resuelta en ejecución, con conservación estricta del comportamiento web actual.
Push sin Gladys Plus Una instancia auto-alojada no tiene claves FCM / APNs. Relé por la pasarela para las cuentas Gladys Plus; notificaciones locales en caso de fallo, con sus límites anunciados explícitamente.
Multicast iOS El descubrimiento mDNS requiere un permiso concedido caso por caso por Apple, con plazos imprevisibles. Solicitud presentada tan pronto como se abra la cuenta de desarrollador. La introducción manual de la dirección es un camino de primera clase, no un repliegue.
WebSocket en segundo plano Los dos sistemas operativos cortan las conexiones en espera; el estado mostrado puede estar desactualizado al despertar. Reconexión con backoff al volver al primer plano, recarga completa del estado, e indicador de frescura de los datos.
HLS en WKWebView La reproducción MSE a través de hls.js puede comportarse de manera diferente a Safari. Prueba en dispositivo real en el lote 1, antes de cualquier compromiso en el lote 4.
Contenido mixto (descubierto en pruebas) La WebView servida en https://localhost no puede llamar ni a http:// ni a ws:// en la instancia local: Chromium bloquea. La configuración de seguridad de red de Android no cambia nada — es una política del navegador, no de la plataforma. CapacitorHttp resuelve el caso de las solicitudes HTTP, pero no el de los WebSockets, que siguen sometidos a la regla. Ver el arbitraje a continuación: el lote 1 pasa a androidScheme: "http".

Arbitraje: esquema de la WebView

La elección del esquema opone dos capacidades que no se pueden obtener juntas
mientras la instancia esté en HTTP:

androidScheme Contexto seguro Crypto / Gladys Plus HTTP local WebSocket local
https sí OK a través de CapacitorHttp BLOQUEADO
http no indisponible OK OK

Decisión para el lote 1: http. El lote tiene como objetivo la conexión a la
instancia
local, que exige el WebSocket; Gladys Plus aún no está conectado. Esta
elección es explícitamente temporal.

Debe revisarse obligatoriamente antes del lote Gladys Plus. Dos vías:

  1. Instancia en HTTPS (proxy inverso TLS: Caddy, nginx, Traefik). Todo pasa
    a https/wss, sin bloqueos, contexto seguro conservado. Es la
    única solución que también valdrá para iOS, donde el esquema capacitor:// impone
    las mismas restricciones. Vía recomendada.
  2. Plugin WebSocket nativo, abriendo la conexión fuera de WebView como
    CapacitorHttp lo hace para las solicitudes. Permite permanecer en https, pero
    exige modificar Session.js, compartido con la web.

Consecuencia práctica: mientras el lote 1 esté en androidScheme: "http",
crypto.subtle no está disponible en la app y cualquier intento de conexión
Gladys Plus fallará. El prototipo de validación, en cambio, medía bien la criptografía
en https — la capacidad de la plataforma está adquirida, pero no su coexistencia
con un backend en claro.
| Revisión App Store | Geolocalización en segundo plano y app llamada « cliente de un servicio » son dos motivos clásicos de rechazo. | Modo demo accesible a los revisores sin instancia, justificaciones de permisos redactadas con cuidado, captura de pantalla del uso real de las zonas. |


9. Lotes de entrega

Los lotes son realmente secuenciales: cada uno se basa en el anterior y termina con un entregable instalable en un dispositivo real. El lote 1 concentra intencionalmente los riesgos técnicos.

Lote 1 — Base y autenticación :white_check_mark: ENTREGADO

Objetivo: demostrar que el enfoque funciona, y resolver los puntos difíciles 1, 2 y 6.

Commit feat(mobile): resolver la instancia a la que conectarse en tiempo de ejecución —
22 archivos, 1254 inserciones, 14 eliminaciones.

  • :white_check_mark: Configuración resuelta en tiempo de ejecución (setRuntimeConfig, lista blanca de claves)
  • :white_check_mark: Capa utils/native/ — eliminada del bundle web por tree-shaking
  • :white_check_mark: Almacenamiento seguro (Keychain / Keystore, repliegue localStorage en la web)
  • :white_check_mark: Perfiles multiinstancia: lista, añadir, edición, eliminación, conmutación
  • :white_check_mark: Pantalla de primera conexión, con prueba de la dirección antes del registro
  • :white_check_mark: Página de ajustes « Instancias », oculta en la web (nativeOnly)
  • :white_check_mark: Indicador de conexión perdida en el encabezado
  • :white_check_mark: Zonas seguras: viewport-fit=cover + barra superior y cajón de navegación
  • :white_check_mark: No regresión web verificada manualmente
  • :warning: Conexión Gladys Plus no funcional — ver el arbitraje del esquema, sección 8
  • :cross_mark: Proyecto iOS no generado: no hay máquina macOS disponible

Validado en dispositivo (Xiaomi, Android 16, WebView Chromium 151): recorrido
completo de configuración, conexión a la instancia local, inicio de sesión, dashboards,
cámaras.

Controles: los tres checks del CI pasan (Prettier, ESLint 0 errores,
paridad de traducciones fr/en/de), más 43 pruebas de lógica ejecutadas en el código
real del repositorio.

Lote 2 — Paridad funcional y ergonomía táctil

Objetivo: una app utilizable a diario, sin aún las aportaciones nativas.

  • Zonas seguras, barra de pestañas baja, gesto de retorno Android
  • Arrastre y suelta del dashboard validado con el dedo
  • Comportamiento del teclado, tirar para actualizar, objetivos táctiles
  • Barra de estado y pantalla de inicio acordes al modo oscuro
  • Revisión pantalla por pantalla de las 23 rutas existentes en teléfono y tableta

Lote 3 — Notificaciones y presencia

Objetivo: la primera razón concreta para instalar la app en lugar de abrir el navegador.

  • Servidor: ruta POST /api/v1/user/push_token, vinculada a las sesiones
  • Servidor: acción de escena user.send-push-notification, esquema Joi incluido
  • Relé de notificaciones por Gladys Plus, contenido cifrado de extremo a extremo
  • Notificaciones locales en caso de fallo sin Gladys Plus
  • Acciones rápidas en las notificaciones
  • Geolocalización por zonas, cola fuera de línea, interruptor global
  • Biometría y bloqueo después de inactividad

Lote 4 — Integración al sistema

Objetivo: hacer que Gladys sea accesible sin siquiera abrir la app.

  • Descubrimiento mDNS: plugin gladys-discovery, Android e iOS
  • Widgets de pantalla de inicio: escenas, temperatura, dispositivos
  • App Intents iOS y accesos directos dinámicos Android
  • Asistente de voz: permiso de micrófono en WebView, sesión de audio iOS
  • Cámaras: pantalla completa, rotación, parada en segundo plano
  • Modo tableta mural: pantalla mantenida encendida, orientación bloqueada, pantalla inmersiva completa

Lote 5 — Publicación

Objetivo: la app es instalable desde las tiendas y se actualiza sola.

  • Cadena CI: construcción y firma Android e iOS
  • Fichas Play Store y App Store: descripciones, capturas, política de privacidad
  • Modo demo para los revisores, sin instancia requerida
  • Prueba interna luego beta abierta: Play Console y TestFlight
  • Documentación de usuario y documentación de contribución

Ordenación — Los lotes 1 y 2 ya producen una app distribuible internamente. Si los puntos difíciles del lote 1 resultan más costosos de lo previsto, el arbitraje se hace en ese momento, antes de haber iniciado el desarrollo nativo del lote 3.


10. Construcción, CI y publicación

Cadena de construcción

Nuevos scripts npm en la raíz, en continuación de los scripts existentes:

build-mobile      vite build dirigido a móvil, luego npx cap sync
mobile:android    abre el proyecto en Android Studio
mobile:ios        abre el proyecto en Xcode
mobile:live       recarga en caliente en dispositivo conectado

La construcción móvil reutiliza front/vite.config.mjs con un modo dedicado: la configuración de URL ya no se inyecta en la construcción, y el service worker no se copia.

Versionado

La versión de la app sigue la de Gladys (package.json raíz, hoy 5.0.2), con un número de construcción incremental propio del móvil. Los versionCode Android y CFBundleVersion iOS se derivan automáticamente en CI.

Compatibilidad de instancia

Una app actualizada puede conectarse a una instancia más antigua. utils/instanceVersion.js ya gestiona esta detección en la web: se extiende para desactivar correctamente las funcionalidades que requieren una versión mínima del servidor, en particular el push, en lugar de dejar que una llamada falle sin explicación.

Integración continua

Un flujo de trabajo GitHub Actions dedicado, distinto de la CI existente:

  • En cada pull request que afecta a mobile/ o front/: construcción Android en modo debug, sin firma.
  • En cada etiqueta de versión: construcción firmada Android e iOS, depósito en los canales de prueba interna.
  • Secretos de firma en secretos de repositorio; ninguna clave en el repositorio mismo.
  • Las verificaciones existentes siguen aplicándose al front: Prettier luego ESLint, y cobertura de parche al 100% para cualquier código servidor añadido.

Cuentas y costos

Poste Naturaleza Costo
Cuenta desarrollador Apple Organización 99 $ / año
Cuenta Google Play Organización 25 $ una vez
Proyecto Firebase FCM únicamente Gratis
Máquina de construcción iOS macOS, CI o local Variable

11. Pruebas y aceptación

Lo que cubre la CI

  • Las pruebas Cypress existentes siguen ejecutándose en el front web: protegen contra regresiones introducidas por la refactorización de la configuración.
  • Pruebas unitarias Mocha en el código servidor añadido (ruta de token push, acción de escena), con TZ=UTC impuesto.
  • La construcción Android en modo debug sirve de prueba de humo en la integración Capacitor.

Prueba manual en dispositivo

Una matriz de prueba se mantiene actualizada para cada lote. Los escenarios que no pueden ser automatizados y que deben ser verificados manualmente:

Escenario Lo que se verifica
Conmutación Wi-Fi → datos móviles Paso automático del modo local a Gladys Plus, sin desconexión visible
Retorno al primer plano después de una noche Reconexión WebSocket, estado actualizado, ninguna datos caducados mostrados como actuales
Notificación, app cerrada Recepción, apertura en la pantalla correcta, acción rápida ejecutada sin lanzar la app
Entrada y salida de zona Evento de presencia reportado, incluso después de un paso fuera de red
Permiso denegado La app sigue siendo completamente utilizable, con un mensaje que explica lo que está desactivado
Cámara en directo Lectura, pantalla completa, rotación, parada limpia al pasar a segundo plano
Modo oscuro Cohesión de la barra de estado, de la pantalla de inicio y de las zonas no invertidas
Instancia fuera de línea Pantalla fuera de línea, ninguna reconexión agresiva, reanudación manual posible

Resultados medidos — 30 de agosto de 2026

En Xiaomi 2412DPC0AG, Android 16 (API 36), WebView Chromium 151, frente a una
instancia real en HTTP en la red local:

Prueba Resultado
crypto.subtle en contexto https://localhost disponible
RSA-OAEP 2048 / SHA-256 — generación 104 ms
ECDSA P-256 — generación, firma, verificación < 1 ms
exportKey('jwk') (almacenamiento de claves) OK
PBKDF2 100 000 iteraciones 15 ms
AES-GCM 256, tagLength 128 < 1 ms
WebSocket a instancia HTTP (handshake) 44 ms
MSE — Hls.isSupported() disponible
Recorrido completo: configuración → inicio de sesión → paneles → cámaras funcional

El PBKDF2 es tres veces más rápido en el teléfono que en un PC de escritorio
(15 ms frente a 45 ms): la implementación de Android está acelerada por hardware. El
riesgo de un desbloqueo lento, considerado en la sección 5.7, no existe.

Lección de método. El prototipo de validación inicial dio tres luces
verdes y, sin embargo, no detectó ninguno de los dos bloqueos reales
encontrados posteriormente (contenido mixto en las solicitudes HTTP, luego en el WebSocket).
Probó un WebSocket desde una página http://localhost — por lo tanto, sin
contenido mixto — y no emitió ninguna solicitud API a la instancia. Hay que
validar crypto, WebSocket y una solicitud API real en la configuración de esquema definitiva,
nunca por separado.

Parque de pruebas mínimo

  • Un Android reciente y un Android antiguo (API 26 a 28), para la configuración de seguridad de red.
  • Un iPhone con notch y una iPad, para las zonas seguras y el modo tableta.
  • Una instancia Gladys local en http:// y una instancia vinculada a Gladys Plus.

12. Fuera del alcance de la v1

Estos elementos se descartan explícitamente. Mencionarlos evita que aparezcan en el camino.

  • Modo fuera de línea completo. La aplicación muestra los últimos datos conocidos, pero no reproduce los comandos emitidos fuera de la red. Solo los eventos de presencia se ponen en cola.
  • Aplicación Apple Watch y Wear OS. Los widgets y accesos directos del sistema cubren la mayor parte de las necesidades a menor costo.
  • Configuración de integraciones complejas desde móvil: emparejamiento Zigbee, Matter, Z-Wave. Consultables, pero configurados desde la web.
  • Rediseño visual. La aplicación retoma la interfaz actual. Un rediseño mobile-first es un proyecto aparte.
  • Soporte para Android 7 y anteriores, y para iOS 14 y anteriores.
  • Reparación del service worker web. El comportamiento actual se mantiene; la mejora de la PWA sigue siendo un tema aparte.

Preguntas a resolver antes del inicio

  1. ¿El relay de notificaciones pasa por la infraestructura Gladys Plus existente, o se necesita un servicio dedicado? Esto condiciona el lote 3.
  2. ¿Las cuentas de desarrollador están abiertas a nombre del proyecto Gladys o a título personal? Esto tiene consecuencias duraderas en la propiedad de las fichas.
  3. ¿La aplicación está reservada para los suscriptores de Gladys Plus para las notificaciones, o el repliegue local es suficiente para la promesa hecha a los usuarios?
  4. ¿Hay una máquina macOS disponible para la CI de iOS, o hay que prever un servicio de compilación alojado?

Especificación establecida a partir del repositorio Gladys 5.0.2: front Preact / Vite (front/), servidor Node 24 (server/, 39 servicios, 25 controladores API). Las rutas de archivos citadas corresponden a la estructura real del repositorio en la fecha de redacción.

¿Lo has leído todo? :face_with_crossed_out_eyes:

¡Joder, me he desconectado en « Tres cosas que la especificación no había previsto », ya no podía más de la IA, lo siento @Will_71 :face_with_peeking_eye:

Para resumir, ¿para qué servirá una app nativa?
Me parece que el sistema de WPA es realmente genial, funciona todo el tiempo, es estable y seguro.
Con las apps móviles, son 2 plataformas más para gestionar y mantener, en cuanto a la compatibilidad de Gladys, las actualizaciones del sistema operativo, la seguridad (vamos a evitar hacer como Hacienda…), etc.
Por lo tanto, ¿sí, por qué?

PD: aún no he visto el vídeo, quizá allí encuentre respuestas…

Era solo una prueba para ver qué podía hacer Claude y en 15 minutos, tuve la especificación y una aplicación funcional.

No digo que la aplicación PWA no funcione, al contrario, la uso todos los días.
Aquí, Claude no se fue a hacer 3 sistemas diferentes, sino que comparte el mismo front-end, por lo que si se modifica eso, está disponible para los 3.

La ventaja de la aplicación es poder gestionar la geolocalización de una persona (para la presencia en una zona), de forma nativa por el smartphone, por ejemplo.
Y veo claramente, más tarde, usar la aplicación para emparejar los dispositivos Matter, con el Bluetooth y la cámara del teléfono.