Files
AR-VMS-Seaman/VMS_Sailor_v2_Parte_05_Mobile.md
T
alro65 deb04c9315 sprint-0: fundaciones VMS-Sailor
Sprint 0 completo del producto VMS-Sailor (Vessel Management System
integrado para buques 30-40m). Brief de referencia en
VMS_Sailor_v2_Parte_*.md (intacto).

Core (vmssailor.core, 95.17% coverage, 99 tests verde):
- ShipCoord: sistema naval x_pp/y_cl/z_bl frozen
- Vessel, Deck, Bulkhead
- Equipment, EquipmentModel, Sensor, EquipmentSpec
- Tag, AlarmConfig, TagBinding, Scaling
- CardInstance, Bus, Topology con validacion 21 puntos I/O AR-NMEA-IO-v1.0
- Alarm, PermissiveRule, Condition
- Project agregado raiz con validacion cross-entity
- Persistencia portable .vmsproj (SQLite) con roundtrip verificable

Biblioteca curada seed (vmssailor.library):
- systems_catalog.json completo (catalogo maestro Parte 1 sec 7)
- 2 vessels: Sunseeker 76, Ferretti 850
- 2 motores: MTU 12V 2000 M96, Volvo D13-900
- 1 genset: Northern Lights M65C13
- yacht_motor_planeo.yaml (reglas heuristicas)
- TODO marcado data_source=seed_estimate - requiere validacion datasheets

Tools:
- vms-validate-library: CLI valida biblioteca completa
- vms-generate-test-project: CLI demo + verificacion roundtrip persistencia

Design System + 8 mockups HTML estaticos:
- docs/design_system.md (paleta Deep Ocean, gradientes, typography, motion)
- docs/brand/ (logo + variantes SVG)
- docs/mockups/splash, studio_main, runtime_overview,
  runtime_mimic_fuel (P&ID animado), runtime_alarms, runtime_trim (panel
  estrella con horizonte artificial), mobile_overview, mobile_trim
- docs/mockups/index.html (galeria)

Firmware (Sprint 12+ implementacion):
- firmware/ar_nmea_io_v1/src/config/pinout.h con macros GPIO

Decisiones autonomas documentadas en docs/decisions_sprint0.md.

Stack: Python 3.11 + uv + Pydantic v2 + SQLite stdlib + hatchling +
pytest 9 + ruff + mypy. Sin PySide6, FastAPI, Flutter ni firmware
funcional (entran en sprints siguientes).

Criterio de aceptacion Sprint 0: cumplido.
- uv sync: OK
- pytest: 99/99 verde
- cov vmssailor.core: 95.17% (objetivo >=80%)
- ruff: clean
- vms-validate-library: OK
- vms-generate-test-project: INTEGRIDAD OK

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 07:26:06 -04:00

13 KiB
Raw Blame History

VMS-Sailor · Brief para Claude Code · Parte 5 de 6

VMS-Sailor Mobile (app nativa iOS y Android)

Esta parte detalla la app móvil. Asume que ya leíste Partes 1-4.


1. Propósito

App nativa que permite al Admin del buque (owner), técnicos y tripulación autorizados ver y operar el VMS desde tablets y smartphones. Diseñada para uso a bordo, no remoto desde tierra.

Casos de uso principales

  • Owner en cubierta o camarote: revisar estado general del buque sin ir al puente
  • Owner durante navegación: ajustar trim desde el flybridge mientras conduce
  • Técnico durante mantenimiento: ver lecturas en tiempo real estando físicamente en sala de máquinas
  • Tripulación: recibir notificaciones de alarmas en el teléfono cuando no están en estación fija
  • Owner en muelle: revisar estado del buque amarrado desde su casa cercana (siempre que esté en WiFi local)

2. Restricciones críticas de seguridad

Solo WiFi local del buque

La app JAMÁS se expone a internet. El servidor del Runtime escucha solo en la subred WiFi local (192.168.x.x típicamente). La app móvil descubre el servidor por mDNS/Bonjour o IP fija configurada al enrolar.

Implicaciones:

  • No se requiere certificado SSL público (TLS interno con CA propia)
  • Sin cuentas en la nube, sin datos saliendo del buque
  • Si la WiFi del buque está apagada, la app no funciona
  • Si el dueño quiere acceso remoto desde tierra → debe pasar por VPN del armador (instalación separada, no parte de este proyecto)

Autenticación TOTP + biométrico

Doble factor obligatorio:

  1. Algo que sabes: usuario + password
  2. Algo que tienes: el dispositivo enrolado (TOTP de 30s)
  3. Algo que eres: biométrico del dispositivo (FaceID, TouchID, huella Android) — antes de revelar el TOTP

Enrollment controlado

Solo el Admin del buque (owner) puede autorizar nuevos dispositivos. Proceso:

  1. Owner en su estación principal del Runtime → "Gestión de dispositivos móviles"
  2. Crea nuevo dispositivo: nombre (ej: "iPhone Carlos"), usuario asociado
  3. Sistema genera QR con secreto TOTP único
  4. Usuario escanea con la app VMS-Sailor Mobile en su dispositivo
  5. App pide configurar biométrico local
  6. Dispositivo queda enrolado
  7. Owner puede revocar en cualquier momento desde la estación

Permissives respetados igual que estación fija

Los permissives se evalúan en el SERVIDOR, no en el cliente. Móvil y desktop son solo UIs. Si un permissive bloquea una acción, bloquea desde cualquier UI. No hay forma de saltarse permissives desde móvil.


3. Arquitectura técnica

Stack tecnológico

Componente Tecnología
Framework Flutter 3.x
Lenguaje Dart 3.x
State management Riverpod 2.x
Comunicación WebSocket (web_socket_channel) + HTTP/REST (dio)
TOTP otp package
Biométrico local_auth
Almacenamiento seguro flutter_secure_storage (Keychain iOS, Keystore Android)
Gráficas fl_chart
SVG (mímicos) flutter_svg
Notificaciones locales flutter_local_notifications
Cliente HTTPS con CA propia configurar en dio con IOHttpClientAdapter

Por qué Flutter (no React Native ni nativo puro)

  • Performance gráfico superior: motor Skia/Impeller propio. Para mímicos con muchas lecturas refrescándose en tiempo real, esto es decisivo. RN sufre con UIs densas y actualizaciones rápidas
  • Un solo código para iOS + Android + posible escritorio: a futuro se puede tener "VMS-Sailor Mobile Lite" como app de escritorio liviana con el mismo código
  • Coherencia visual: misma UI en iPhone, iPad y Android. Flutter renderiza por sí mismo, no usa widgets nativos
  • Dart bien diseñado: tipado fuerte, async/await nativo, null safety. Reduce bugs runtime en sistema crítico
  • Soporte enterprise: Google + BMW + Toyota lo usan en industria

Estructura de carpetas Flutter

mobile/
├── pubspec.yaml
├── README.md
├── android/
├── ios/
│
└── lib/
    ├── main.dart
    ├── app.dart                       # Material/Cupertino app
    │
    ├── core/
    │   ├── models/
    │   │   ├── tag.dart
    │   │   ├── alarm.dart
    │   │   ├── system.dart
    │   │   ├── permissive.dart
    │   │   └── user.dart
    │   ├── api/
    │   │   ├── ws_client.dart         # Cliente WebSocket
    │   │   ├── rest_client.dart       # Cliente REST
    │   │   └── auth_client.dart       # Auth + TOTP
    │   ├── auth/
    │   │   ├── totp_manager.dart
    │   │   ├── biometric_gate.dart    # local_auth wrapper
    │   │   └── secure_storage.dart    # secretos en Keychain/Keystore
    │   ├── discovery/
    │   │   └── mdns_finder.dart       # Encuentra servidor en WiFi
    │   └── theme/
    │       ├── light_theme.dart
    │       └── dark_theme.dart
    │
    ├── features/
    │   ├── enrollment/
    │   │   ├── qr_scanner.dart        # Escanea QR del Runtime
    │   │   └── enrollment_flow.dart
    │   ├── login/
    │   │   ├── login_screen.dart
    │   │   └── biometric_unlock.dart
    │   ├── overview/
    │   │   └── overview_screen.dart   # Dashboard inicial
    │   ├── systems/
    │   │   ├── system_list.dart       # Lista de sistemas activos
    │   │   ├── mimic_view.dart        # Mímico de un sistema
    │   │   └── tag_detail.dart
    │   ├── alarms/
    │   │   ├── alarm_list.dart
    │   │   ├── alarm_detail.dart
    │   │   └── alarm_notifications.dart
    │   ├── trends/
    │   │   └── trends_screen.dart     # Gráficas con fl_chart
    │   ├── trim/
    │   │   ├── trim_control_screen.dart  # Panel destacado de trim
    │   │   ├── trim_slider_widget.dart
    │   │   ├── stability_indicator.dart  # Roll/pitch en vivo
    │   │   └── emergency_reset_button.dart
    │   ├── logbook/
    │   │   ├── logbook_view.dart
    │   │   └── manual_entry.dart
    │   └── settings/
    │       └── settings_screen.dart
    │
    └── widgets/                       # Widgets reutilizables
        ├── tag_card.dart
        ├── alarm_badge.dart
        ├── gauge_widget.dart
        ├── lamp_indicator.dart
        ├── bar_graph.dart
        └── permissive_status.dart

4. Pantallas principales

Login

  • Detecta si hay servidor VMS-Sailor en la WiFi local (mDNS)
  • Si no, pide IP manual
  • Login con usuario + password
  • Pide biométrico para revelar TOTP
  • Verifica TOTP contra servidor
  • Si OK, guarda token de sesión

Overview (dashboard inicial)

Lo primero que ve el usuario después de loguear:

  • Nombre del buque + estado general (verde/amarillo/rojo)
  • Resumen: motores estado, gensets estado, tanques niveles, alarmas activas
  • Roll/pitch actual (gauge visual)
  • Acceso rápido a sistemas más usados

Lista de sistemas (menú lateral o pestaña)

Generado dinámicamente desde el .vmspack activo. Solo muestra los sistemas que el integrador marcó en el wizard.

Vista de mímico por sistema

Carga el SVG del mímico empaquetado en el .vmspack. Se renderiza con flutter_svg. Los elementos del mímico se actualizan con WebSocket en tiempo real:

  • Cambios de color por estado
  • Valores numéricos sobre los símbolos
  • Tap en un elemento → muestra detalle del tag

Panel de Trim y Maniobra (destacado)

Esta es la pantalla estrella para el owner. UI optimizada para uso con una mano mientras conduce:

  • Sliders verticales grandes para cada actuador de trim (touch-friendly)
  • Indicador horizontal de roll en vivo (animado)
  • Indicador de pitch
  • Botón grande rojo de "RESET EMERGENCIA"
  • Toggle de "Modo Manual del Owner" (requiere biométrico para activar)
  • Indicación clara del envelope activo (línea visual donde el sistema empieza a bloquear)

Panel de alarmas

  • Lista cronológica con color por prioridad
  • Notificaciones push locales cuando entra alarma crítica (incluso con la app en background)
  • Botón ACK por alarma (respeta permisos del rol)
  • Filtros y búsqueda
  • Gráficas con fl_chart
  • Múltiples tags simultáneos
  • Zoom y pan
  • Selector de rango temporal (1h, 24h, 7d)

Log Book

  • Lista de entradas cronológica
  • Filtros por categoría (eventos motor, alarmas, manuales)
  • Botón "Agregar entrada manual" (Técnico/Admin)

Settings

  • Cambiar password
  • Cerrar sesión / desautorizar dispositivo
  • Configuración de notificaciones
  • Idioma
  • Modo oscuro

5. Permisos por rol en la app

Mismos roles que en estación desktop, pero con restricciones adicionales por ser móvil:

Acción Operador Técnico Admin (owner)
Ver mímicos
Ver alarmas
ACK alarmas Info/Low
ACK alarmas High/Emergency
Ver trends
Ver logbook
Agregar entrada manual logbook
Control de luces, A/A (no críticos)
Control trim
Activar modo manual del owner
Reset emergencia
Arrancar motores / thrusters ✗ (solo estación fija)
Override de permissives WARNING
Gestión de usuarios y dispositivos

Regla de oro: acciones de propulsión y maquinaria principal (arranque/parada motores, thrusters, parada de emergencia general) solo desde estación fija puente o máquinas, nunca desde móvil. Por seguridad.


6. Notificaciones push locales

Sin servicios externos (no Firebase, no APNs externos). La app mantiene WebSocket persistente con el Runtime. Cuando recibe evento crítico:

  • Si la app está en foreground → notificación dentro de la app
  • Si la app está en background → notificación local del sistema operativo (flutter_local_notifications)
  • Sonido distintivo por prioridad
  • Tocar la notificación → abre la app en el evento específico

Limitación: si el usuario fuerza-cierra la app, no llegan notificaciones. Documentar al owner.


7. Modo offline degradado

Si la conexión WebSocket cae:

  • Banner persistente "Sin conexión al VMS"
  • Sigue mostrando los últimos valores conocidos (con badge "desactualizado X minutos")
  • Permite ver histórico cached
  • No permite enviar comandos
  • Intenta reconectar automáticamente cada 10s

8. Empaquetado y distribución

iOS

  • Build con Xcode
  • Distribución vía TestFlight para clientes específicos (no App Store público inicialmente)
  • Requiere cuenta Apple Developer ($99/año) — gasto del integrador (Álvaro)
  • Cada armador recibe invitación TestFlight con el binario

Android

  • Build APK firmado
  • Distribución directa por enlace (no Google Play inicialmente)
  • O via Play Store con cuenta de desarrollador ($25 una vez) si se decide más adelante

Por qué no stores públicas inicialmente:

  • Las stores requieren accesibilidad pública (cualquiera puede instalar)
  • Eso introduce riesgo de seguridad (cualquiera podría intentar usar la app contra cualquier IP)
  • Distribución privada (TestFlight + APK firmado) garantiza que solo clientes autorizados tienen acceso

9. Sprints relacionados con Mobile

  • Sprint 11: VMS-Sailor Mobile completo

    • Setup Flutter project
    • Enrollment flow + QR scan
    • Login + TOTP + biométrico
    • WebSocket client + REST client
    • Pantallas: Overview, Lista sistemas, Mímico simple
    • Panel alarmas con notificaciones locales
    • Panel Trim (caso destacado)
    • Permisos por rol
    • Build iOS + Android
  • Sprint posterior: refinamientos, trends avanzados, modo offline robusto, logbook completo


10. Consideraciones de UI/UX móvil

Touch targets

Mínimo 44×44 pt en iOS, 48×48 dp en Android. Los sliders de trim deben ser grandes para uso con guantes o manos mojadas.

Lecturabilidad bajo sol

  • Modo de pantalla "outdoor" con contraste aumentado
  • Texto grande por defecto (configurable)
  • Evitar colores pastel para datos críticos

Orientación

  • Tablets: soportar landscape y portrait
  • Smartphones: portrait principalmente; landscape para mímicos complejos y trends

Accesibilidad

  • Soporte de screen readers (VoiceOver iOS, TalkBack Android)
  • Tamaño de fuente respetando configuración del sistema
  • Contraste WCAG AA mínimo

11. Privacidad y datos

La app NO recolecta:

  • Datos de telemetría de uso
  • Analítica
  • Crash reports a servidores externos

La app guarda LOCALMENTE en almacenamiento seguro del dispositivo:

  • Secreto TOTP (cifrado en Keychain/Keystore)
  • Token de sesión
  • Últimos valores cached (para modo offline)
  • Configuración del usuario

Si el dispositivo se restablece o se borra la app → todo se pierde. El owner debe enrolar nuevamente.


Fin de Parte 5 de 6. Próxima: Parte 6 — Plan de Sprints completo.