Skip to content

Fase 3b: cliente Flutter

English version. Este documento en español es la fuente normativa de los criterios de aceptación; la versión inglesa es una traducción resumida que debe actualizarse en la misma revisión. Ante discrepancias, prevalece este documento.

Estado: plan aprobado en dirección; la aceptación de los hitos sigue pendiente, con partes de M3b.0–M3b.4 ya implementadas. Orden de ejecución por dependencias, sin estimaciones de calendario todavía. ADR-009 fija la elección de framework; base implementada se describe en la documentación del cliente.

Prioridad actual de ejecución — 1 de octubre de 2026

La beta 6 (0.1.0+27) está publicada. Una invitación, desde la instalación hasta la primera conversación está implementada y distribuida; el registro de implementación distingue las pruebas automáticas y nativas de la aceptación física pendiente. Publicar no cierra M3b.5: siguen abiertos dispositivos físicos, notificaciones y tres usuarios externos. La preparación de la beta registra paquetes, compatibilidad y tareas.

Objetivo y alcance

Una beta que permita a dos personas instalar Arveil, crear o vincular su identidad y conversar desde macOS y Android, incluyendo trabajo sin conexión y errores comprensibles. La misma base Flutter se ampliará a Windows, Linux e iOS.

No incluye SwiftUI, UniFFI, federación, llamadas, IPC entre GUI y CLI ni rediseño del protocolo. El cliente gráfico abre perfiles cifrados y da de alta una identidad mediante bootstrap e invitación, reanuda un alta fallida y reconoce la finalización al reabrir. Ya existen emparejamiento con comparación manual y exportación/restauración del kit cifrado de identidad. La disponibilidad de KeyPackages incluye fecha de consulta, agotamiento y reposición reanudable. Están implementadas creación de conversaciones, historial paginado, texto sin conexión y sincronización. El 24 de septiembre, apps de release independientes pasaron las pruebas Mac ↔ emulador Android de conversación, actualización y desconexión/reconexión; sigue pendiente el dispositivo físico. Véase el registro de plataformas.

Estructura prevista

clients/flutter/                 UI adaptativa y adaptadores de plataforma
core/crates/arveil-flutter/      Bindings y traducción del contrato
core/crates/arveil-app/          Operaciones, resultados y ejecutor existentes
core/crates/arveil-core/         Identidad, MLS y persistencia existentes

No introducir reglas de negocio en el puente ni duplicar el estado durable en otra base Dart. La GUI consulta el estado de Rust y mantiene únicamente una proyección de presentación.

Hitos y criterios de aceptación

Hito Trabajo Condición de salida
M3b.0 — Compilación y puente Crear proyecto Flutter y crate adaptador; definir ProfileConfig y cierre determinista (contrato inferior); fijar versiones antes de aceptar el hito; compilar Rust, SQLCipher/OpenSSL y puente para macOS y Android. En Mac y un dispositivo Android: abrir/cerrar perfil, ejecutar una consulta por Dart y recibir un error tipado sin bloquear la UI. Clave explícita obligatoria para el perfil gráfico; ausencia no crea una base en claro. Tras cerrar, otro proceso abre inmediatamente el perfil. Registrar matriz y comandos reproducibles según el contrato inferior.
M3b.1 — Contrato y sistema Configuración explícita de perfil, claves y TLS; integración con almacén seguro; llamadas asíncronas; eventos incrementales identificables; paginación, límites de admisión y política de fallos; resultados parciales; consulta de estado al reabrir. Perfil gráfico cifrado por defecto; reinicio conserva acceso; clave ausente o incorrecta produce flujo de recuperación sin recrear el perfil. ProfileInUse se presenta correctamente. Un fallo tras commit muestra el mismo mensaje pendiente, sin duplicarlo al reintentar.
M3b.2 — Alta y emparejamiento Pantallas para crear identidad, introducir bootstrap/invitación, iniciar/aprobar sesión, comparar código, confirmar o cancelar y reanudar finalización. Dos dispositivos se vinculan sin terminal. Código incorrecto, sesión caducada, cancelación y fallo de red producen resultados coherentes. Confirmaciones duplicadas no crean buzones nuevos. Ofrecer exportación del kit durante el alta y restaurarlo en una prueba; permitir posponerlo con el riesgo visible. Probar agotamiento y reposición de KeyPackages al incorporar varios grupos. El alta por invitación también debe poder recuperarse de una respuesta perdida, no solo el pairing; cumplir el contrato de inscripción concurrente descrito abajo.
M3b.3 — Conversación vertical (Mac ↔ emulador Android verificado; falta dispositivo físico) Lista de conversaciones, selección de contacto/ruta verificada, creación de grupo, historial paginado, composición, envío, recepción y sincronización. Conversación Mac ↔ Android; modo avión conserva lectura y envío local; reconexión no duplica mensajes. Mostrar aceptación local y del relay sin confundirlas con lectura humana. Historial responde durante red lenta.
M3b.4 — Funciones para uso diario (contactos/alias, adjuntos explícitos, revocación de dispositivos e historial cifrado implementados; aceptación por plataforma pendiente) Adjuntos, contactos/nombres, verificación, dispositivos/revocación y exportación/importación de kit e historial mediante API. Recuperación explícita ante estados no soportados. Adjuntos identificados por event_id, con política explícita de descarga, almacenamiento privado y exportación mediante permisos del sistema. Transferencia interrumpida se reanuda; cancelaciones y archivos caducados son visibles. Simulacro de pérdida de dispositivo y recuperación. No presentar una resincronización MLS como resuelta si todavía requiere reincorporación.
M3b.5 — Beta macOS/Android Accesibilidad, atajos y navegación de escritorio, adaptación móvil, permisos, comportamiento de suspensión, empaquetado y diagnóstico sin secretos. Instalación reproducible fuera del entorno de desarrollo; firma y limitaciones de distribución documentadas. Tres usuarios externos realizan los flujos principales; antes del uso con una identidad que quieran conservar, cada participante verifica exportación y restauración del kit en un perfil de prueba (sin prometer recuperar todo el historial o grupos MLS); incidencias registradas y bloqueantes corregidos. La beta inicial garantiza sincronización en primer plano y al reabrir; no promete recepción inmediata suspendida/cerrada. Probar Doze, suspensión y reconexión en Android real. Cualquier ampliación requiere decisión y pruebas de push/servicio antes de anunciarla.
M3b.6 — Windows/Linux Compilación nativa, empaquetado, gestión de claves, archivos, notificaciones y pruebas de bloqueo/reinicio. Flujos M3b.2–M3b.4 verificados en sistemas identificados; matriz distingue compilado, probado y distribuido.
M3b.7 — iOS Compilación y firma, Keychain, permisos, suspensión, decisión documentada sobre APNs y eventual extensión. Reutilizar UI Flutter adaptando navegación. Prueba en dispositivo físico; declarar con precisión recepción con app activa, suspendida y cerrada. Si se introduce otro proceso/extensión, resolver su acceso al perfil bajo el bloqueo exclusivo existente.
M3b.8 — Salida de fase y producción Revisión externa de seguridad del cliente, puente y su integración con el protocolo; actualizaciones firmadas y matriz final. Informe con alcance y commit; hallazgos bloqueantes corregidos y verificados, riesgos restantes documentados, builds/actualizaciones firmados y plataformas probadas. Este hito cierra 3b; M3b.5 es una beta limitada, no acredita auditoría.

M3b.0 debe completarse antes de diseñar todas las pantallas. macOS/Android son prioritarios, no una promesa de que las otras plataformas se implementarán sin trabajo adicional. El orden Windows/Linux frente a iOS puede ajustarse según uso real sin cambiar la arquitectura.

El sistema visual, la personalización y el plan del rediseño que precede a la prueba con usuarios externos de M3b.5 están en Diseño del cliente. Ese plan cubre la accesibilidad, la navegación de escritorio, la adaptación móvil y el diagnóstico sin secretos de M3b.5, y añade los datos que la interfaz necesita del contrato Rust. No sustituye los criterios de aceptación de este documento.

Contrato de instalación y distribución

La instalación sencilla es un requisito de producto. Mantener actualizada la entrada de instalación del README mientras avanza M3b.0–M3b.3 y preparar paquetes de prueba junto a los flujos utilizables; el empaquetado no se deja íntegramente para el final. Esto no da por cerrados los hitos ni las plataformas cuya aceptación siga pendiente.

M3b.5 debe entregar imágenes versionadas del servidor para Linux x86-64/ARM64, app macOS empaquetada y APK Android, instrucciones en español/inglés, checksums y una ruta verificada de primera instalación y actualización que conserve el acceso al perfil. El usuario de la app no debe necesitar toolchains ni pasos de terminal no documentados para instalarla o darse de alta. La guía del servidor incluye requisitos, acceso de red, invitaciones, persistencia, salud, reinicio, copia y recuperación. Demostrar la instalación desde una máquina/teléfono limpios siguiendo únicamente las instrucciones publicadas.

No se presupone una membresía Apple Developer de pago para desarrollo local ni para la ruta inicial de evaluación en macOS. Verificar el almacén de claves elegido, primera apertura, reapertura y recompilación; documentar las limitaciones de firma y notarización. Las releases Android requieren clave de firma privada persistente y prueba de actualización; un APK de depuración no es un artefacto de release. La distribución para iPhone tiene un hito separado y no hereda las afirmaciones de instalación de Android. Los criterios completos están en la guía de instalación.

Contrato mínimo de M3b.0

  • ProfileConfig proporciona ruta, clave y configuración de transporte/políticas; la biblioteca no depende del entorno global. La CLI puede traducir sus variables a esta configuración. No registrar secretos ni incluirlos en eventos. La GUI no abre ni crea perfiles sin clave; cualquier modo de desarrollo sin cifrar debe ser explícito. Keychain/Keystore se integra en M3b.1; el spike usa una clave de prueba inyectada.
  • Cada sesión posee una configuración inmutable. open valida la apertura real de la base y su clave antes de devolver un handle; reservar el perfil mientras está abriendo evita dos inicializaciones. Una segunda apertura independiente de la misma ruta canónica devuelve AlreadyOpen (aunque aporte otra clave o política); compartir requiere clonar explícitamente el handle. No reutilizar silenciosamente configuración ni comparar/exponer secretos. Mientras cierra, devolver Closing; tras cerrar, una nueva apertura vuelve a validar la clave. Probar aperturas simultáneas, clave incorrecta en perfil libre y ocupado, políticas distintas, alias simbólicos y apertura durante cierre.
  • Distinguir reserva (ProfileGuard, lock del SO) de sesión (Application, configuración y ejecutor). AlreadyOpen se refiere a una segunda sesión, no a una reserva sin sesión. En M3b.0 adaptar la CLI: los comandos de aplicación adquieren una sola sesión que posee la reserva; los legacy conservan su guard exclusivo. No mantener la adquisición genérica de guard seguida de open como mecanismo implícito de compartición. Probar chat, alta/pairing y comandos legacy, así como exclusión con otro proceso, para evitar regresiones al introducir el nuevo contrato.
  • Cierre explícito e idempotente de la sesión de perfil: dejar de admitir trabajo, esperar las operaciones pendientes con límites o devolver Busy sin liberar prematuramente el lock. Definir qué sucede con clones, streams y llamadas posteriores; no depender del recolector Dart. El éxito implica ejecutor detenido y recursos liberados. La vida del lock debe cubrir todo el trabajo del ejecutor, incluso si desaparecen los handles de UI; retenerlo en el trabajador o mediante un propietario con apagado verificable. Esperar la terminación del hilo (JoinHandle o mecanismo equivalente). Probar cierre explícito y abandono del último handle: con una escritura retenida, ninguna reapertura local ni externa puede adquirir el perfil; tras terminar, comprobar última escritura durable, ausencia de trabajo en vuelo y apertura inmediata desde otro proceso. Probar también doble cierre.
  • El spike puede explorar versiones, pero su aceptación exige Flutter SDK/canal, FRB/generador, Rust toolchain/targets, lockfiles, NDK, ABIs, compiladores y mínimos Android/macOS fijados. Registrar versiones/features de SQLCipher/OpenSSL y demostrar cifrado, reapertura y rechazo de clave incorrecta en ambos sistemas. Distinguir compilado, probado y distribuido. iOS tendrá su prueba específica en M3b.7.
  • Mantener unsafe_code = "forbid" en core y aplicación. El futuro crate adaptador no puede heredar ese forbid si su código generado requiere unsafe: declarar lints propios, deny(unsafe_code) para código manual y excepción localizada al módulo generado, con revisión del diff. Verificar el código generado con la edición 2024 y la toolchain fijada. Si una integración manual exige unsafe, justificar y revisar una excepción concreta antes de aceptarla; no ampliar permisos al workspace. Esta política no certifica ausencia de unsafe en dependencias.
  • Si falla OpenSSL vendorizado, evaluar bibliotecas nativas SQLCipher/OpenSSL compiladas y enlazadas explícitamente para el target. Documentar reproducción, licencias y mantenimiento antes de cambiar la estrategia; no sustituir silenciosamente por SQLite sin cifrar. Un target sin solución comprobada no supera el hito.

Contratos que cerrar en M3b.1

  • El puente no expone conexiones SQLite, motores MLS ni claves privadas como objetos genéricos de UI. La integración de claves tiene una responsabilidad explícita y mínima.
  • Application::execute es bloqueante hoy: usar ejecución de fondo en el adaptador, nunca el hilo de interfaz. Preservar la exclusión de un único ejecutor por perfil, adaptando la propiedad del lock y el apagado según M3b.0.
  • Ampliar el ciclo de vida de M3b.0 con cancelación por operación. Cancelar una espera de interfaz no implica deshacer un commit ni revocar una credencial.
  • Resultados parciales y eventos deben incluir identificadores suficientes de conversación, mensaje y operación; añadir los que todavía faltan en archivos/membresía.
  • Entregar eventos incrementales durante Sync, SendFile y pairing, correlacionados por operación y entidad. El adaptador puede usar StreamSink; arveil-app no depende de FRB. Definir orden, capacidad acotada, desuscripción y resincronización por snapshot si se pierde progreso. Eventos de presentación no sustituyen persistencia ni resultados parciales.
  • Definir una proyección de eventos orientada a UI; no exportar automáticamente todas las variantes internas de StateChange. Separar diagnóstico de cambios de estado, documentar evolución del esquema y comportamiento ante eventos desconocidos sin perder errores/resultados durables. Usar un canal acotado de DTOs transferibles entre hebras, o un callback con las garantías de concurrencia que exija el límite; StreamSink permanece en el adaptador. Los futuros !Send del ejecutor no salen de su hebra.
  • Definir historial por conversación, cursor estable y límite máximo, con orden inequívoco y prueba de inserciones concurrentes entre páginas. Implementar este contrato antes de la pantalla M3b.3.
  • Acotar admisión y operaciones activas. Mantener una sincronización activa por perfil, limitar su cola y devolver un error tipado de saturación; probar que consultas locales siguen respondiendo bajo carga.
  • Un pánico invalida la sesión afectada y produce un fallo reconocible cuando el modo de compilación permite contenerlo. No basta catch_unwind seguido de reinicio: garantizar rollback o descarte de conexiones, recargar MLS desde estado durable y retirar ejecutores muertos del registro antes de permitir reapertura. Probar fallo durante transacción y con comandos pendientes; no anunciar recuperación si el runtime aborta el proceso.
  • El alta por invitación y las operaciones legacy que la GUI necesite requieren revisar reintentos y mover su lógica a arveil-app; no ejecutar la CLI como backend.
  • Fijar una política de migraciones/backup del perfil y de actualización del cliente antes de distribuirlo a usuarios externos.

Claves, backups y reloj en M3b.1

  • Política inicial: excluir el perfil privado (base, WAL/SHM, credenciales, adjuntos y temporales) de copias automáticas en nube y migraciones de datos gestionadas por la app; las exportaciones de kit/historial son acciones explícitas del usuario. Documentar qué excluye cada plataforma y no prometer controlar copias manuales o del administrador del sistema. Excluir backups no sustituye cifrado local.
  • Android: configurar allowBackup, reglas de backup para versiones anteriores y dataExtractionRules para cloud/device transfer según los targets. No asumir que allowBackup=false bloquea todas las transferencias de todos los fabricantes. Verificar reglas del paquete final, restore y transferencia en la matriz de dispositivos, incluida ausencia de claves exportables o archivos que las envuelvan en el backup.
  • Apple: clave local no sincronizable (kSecAttrSynchronizable=false) y clase ThisDeviceOnly apropiada a la disponibilidad requerida, verificando el tipo de Keychain utilizado en macOS. Sincronización de Keychain y migración mediante backup son políticas distintas. En iOS excluir archivos privados con isExcludedFromBackup, reaplicarlo tras operaciones que sustituyan archivos y verificarlo. No inferir sincronización por la mera ausencia de ThisDeviceOnly.
  • Definir estas políticas en M3b.1; comprobar Android/macOS empaquetados en M3b.5 e iOS en M3b.7. Probar reinstalación, restauración sin clave y pérdida de dispositivo sin recrear silenciosamente el perfil. Explicar la necesidad del kit o de otro dispositivo autorizado para los flujos de recuperación que realmente estén implementados; ninguno garantiza recuperar todo el historial.
  • Distinguir errores temporales tipados (todavía no válido/caducado) de firma o autorización inválidas cuando el protocolo aporte ese detalle. Presentar desfase de reloj como causa posible, no diagnóstico probado a partir de credential rejected. No relajar validez, TTL ni caducidad para acomodarlo; probar reloj adelantado y atrasado. Cualquier señal de hora remota debe tener confianza y límites definidos.

Referencias: backup Android, sincronización Keychain, clase ligada al dispositivo, exclusión de backup iOS.

Inscripción concurrente en M3b.2

Una sola inscripción activa por perfil. Persistir fase e identidad de la operación (realm/invitación/credential), sin registrar tokens en diagnósticos. Las llamadas equivalentes esperan dentro de límites y releen el resultado durable; las incompatibles reciben un conflicto tipado sin cambiar la inscripción activa. Tras reinicio o respuesta perdida, reanudar la misma operación. Serializar por sí solo no basta: evitar repetir pasos ya completados y verificar la idempotencia de InviteRedeem en el relay para la misma credencial, incluido consumo confirmado con respuesta perdida. ADR-009 permite la corrección acotada de idempotencia en el relay requerida por este flujo. Registrar el resultado y su vínculo a token/identidad/credencial de forma durable y atómica con el consumo; no basta detectar una membresía existente. Definir reintentos con invitación agotada o caducada y credencial revocada, sin reactivar autorizaciones ni consumir otro uso. Verificar compatibilidad con clientes anteriores. Si falta soporte de protocolo, resolverlo antes de aceptar el hito. Las operaciones de identidad/pairing incompatibles deben respetar esa fase.

Prueba con relay retrasado: dos Enroll equivalentes conservan identidad, buzón y ruta únicos; una invitación diferente no altera el estado. Añadir respuesta perdida, reinicio entre fases y reintento después de completar, con consultas locales todavía disponibles.

La garantía cubre también MailboxCreate: persistir la identidad de la petición antes de enviarla, vincularla al dispositivo autorizado y crear buzón/registro de idempotencia atómicamente. La repetición equivalente conserva el mismo buzón y los mismos bytes de capabilities de lectura/escritura, sin rotarlas ni ampliar su caducidad como efecto de un reintento; reutilizar la clave con parámetros distintos produce conflicto. Resolver explícitamente la recuperación segura de las capabilities y sus reglas de caducidad/revocación: hoy el relay solo almacena hashes y no puede reconstruir la respuesta original. No introducir almacenamiento de capabilities en claro como atajo implícito. Probar respuesta perdida tras commit del relay y fallo antes de persistir la respuesta en el cliente, incluidos reinicios de ambos. El reintento conserva buzón y ruta sin duplicados; revisar igualmente los pasos posteriores para no repetir efectos ya completados.

Antes de implementar la corrección de M3b.2, cerrar el diseño de capabilities. La opción preferida a validar es generar tokens independientes de 32 bytes con el RNG criptográfico del cliente y persistirlos cifrados, junto con la petición, antes de enviarlos. La generación se realiza en Rust mediante getrandom::fill, no en Dart, y un fallo del RNG aborta la preparación sin fallback débil. El relay valida formato, autorización y conflictos de reutilización, y conserva hashes; no puede certificar la entropía de un token aportado por el cliente. Si recibe solo hashes, tampoco puede comprobar la longitud del token original: el contrato de transporte debe precisar qué recibe y valida. Mantener cap_hash único globalmente en el realm: el mismo hash/buzón/alcance solo admite repetición bajo la misma operación autorizada y los parámetros originales; otro buzón o alcance produce conflicto. No tratar una coincidencia de hash como autorización, ni resolver reintentos relajando la clave primaria o usando un INSERT OR IGNORE que oculte conflictos. Probar ambos casos y el rechazo de tokens idénticos para lectura y escritura. No es la única solución posible, pero evita introducir una clave maestra de derivación en el relay. No cambiar el protocolo hasta documentar compatibilidad, tratamiento de secretos y pruebas de reintento. La ruta incluye la capability de escritura: una rotación real requiere un flujo explícito de actualización de contactos; no puede ocurrir silenciosamente al repetir la creación.

El prototipo aplica a las capabilities un TTL de 365 días desde su creación, no desde que un contacto importa la ruta; la revocación puede invalidarlas antes. No hay renovación implementada. En M3b.2 documentar el contrato de caducidad y decidir si la beta incorpora renovación autorizada o mantiene este límite explícito. M3b.4 debe mostrar el fallo de acceso y guiar la recuperación disponible, sin atribuir toda respuesta Forbidden a caducidad ni prometer redistribución automática de rutas. M3b.5 prueba caducidad con reloj controlado en el entorno de pruebas, sin esperar un año, y publica la limitación si no hay renovación. Los reintentos nunca reactivan capabilities revocadas o caducadas.

La compatibilidad incluye los datos del relay, no solo sus clientes. Preferir cambios aditivos; si se requieren transformaciones, implementar migraciones ordenadas y transaccionales antes de usarlas. Comprobar versión de esquema antes de modificar datos y rechazar versiones futuras no soportadas. Probar actualización de una base preexistente con datos representativos, reapertura y restauración de un backup anterior con el binario nuevo. Documentar el rollback: no prometer que un binario antiguo, que hoy no valida versiones, rechazará el esquema nuevo; usar restauración del backup previo cuando la compatibilidad no esté demostrada.

Inventario de API para la GUI

En M3b.1 inventariar cada comando legacy y asignar su destino. Kit export/restore pasa a aplicación en M3b.2; contactos/nombres/verificación y archive export/import en M3b.4. El estado necesario para pantallas se expone como consulta tipada. probe, notify y primitivas de depuración mailbox create/send/fetch permanecen en CLI salvo necesidad concreta de producto. No trasladar comandos solo para replicar toda la terminal.

M3b.2 define umbral, reposición y tratamiento de agotamiento de KeyPackages para la GUI, incluyendo fallo de publicación y reintento. El lote inicial del alta ya se persiste con su estado privado MLS y se reutiliza tras perder una respuesta; un alta confirmada no genera otro lote. El relay aplica el cupo después de deduplicar y nunca reactiva paquetes consumidos. Las regresiones cubren respuestas perdidas, reapertura del perfil, reintentos de altas completadas y límites de cupo. La GUI muestra el último recuento fechado del relay, distingue dato desconocido de cero y ofrece reponer cuando quedan tres claves o menos, con objetivo de diez. La sincronización CLI y la reposición GUI persisten el lote junto al estado privado MLS y lo reutilizan tras una respuesta perdida. La aceptación en dispositivos físicos sigue pendiente; véase el registro de implementación.

Verificación y entregables

La documentación ya se verifica con mkdocs build --strict al publicar; CI también debe comprobarla en pull requests desde esta revisión.

CI desde M3b.0: compilación macOS y Android del puente con dependencias nativas fijadas. Desde M3b.1: regeneración FRB sin diferencias, flutter analyze, pruebas Dart/Rust. Añadir jobs por plataforma al incorporarla; las pruebas en dispositivo físico se registran separadas de los builds CI.

Por hito: pruebas Rust del comportamiento nuevo, pruebas de widgets para estados de UI relevantes y una prueba de integración de extremo a extremo del flujo. Mantener cargo test --workspace --locked, Clippy y las fases existentes al cambiar el contrato. Registrar resultados contra commit, SO y dispositivo; no usar el número histórico de pruebas como garantía de la GUI.

La primera demo debe mostrar instalación, alta o pairing, envío, desconexión, mensaje pendiente y recepción al reconectar. La documentación incluirá capturas, límites conocidos y matriz de plataformas. Push, revisión externa de seguridad y rendimiento real son entregables distintos; una demo visual no los acredita.

Antes de anunciar una versión de producción, dar seguimiento separado a los riesgos del relay, a la recuperación de grupos MLS y a las afirmaciones de seguridad de la documentación histórica. Este plan no considera esos riesgos resueltos por añadir Flutter.

Referencias: ciclo de vida FRB, concurrencia FRB, plataformas Flutter, Doze y App Standby.