Sigla en azul →glosario(primera vez: expansión entre paréntesis).
M20 — Aplicaciones móviles
Por qué existe
El dueño del negocio de servicios — tu ICP de Agenda Ops — no administra citas desde un escritorio todo el día. Vive en WhatsApp y en el teléfono. Una app móvil cliente (owner/staff) que habla con la misma API que la web demuestra que entiendes auth en cliente móvil, manejo de red inestable y distribución instalable, sin duplicar reglas de negocio en el dispositivo.
No es reemplazar la web admin completa en M17; es el canal móvil mínimo viable: login, lista de citas, detalle, estados claros.
En resumen: el dueño vive en el teléfono: misma auth que la web, sesión segura, build instalable.
Objetivos de aprendizaje
Al terminar debes poder:
- Elegir un stack móvil (Flutter o React Native) y justificarlo en una nota corta.
- Implementar login contra la API real de Agenda Ops (mismos endpoints que la web).
- Persistir tokens o sesión con secure storage del framework, no en
SharedPreferencesen texto plano para secretos largos. - Mostrar lista y detalle de citas con estados de carga, vacío y error (incl. 401 → logout).
- Manejar red lenta o caída sin crashear (timeouts, reintento razonable, mensajes al usuario).
- Generar un build instalable (APK Android o equivalente) y probarlo en dispositivo físico.
Cómo estudiar esta materia (lecciones)
M20 construye la app cliente de Agenda Ops contra la API de M17/M19: L01–L20, evidencia en projects/m20-movil/.
- Un stack (Flutter o RN); no cambies a mitad.
- API staging HTTPS de M19; no mocks eternos.
- Cada pantalla: commit + captura o nota en
projects/m20-movil/. - Secure storage para tokens; 401 → logout; sin secretos de servidor en el binario.
- Cómo estudiar y producto-saas.
Semana tipo (20 h)
| Bloque | Horas | Qué haces |
|---|---|---|
| Auth / listas / detalle | 10–12 | 4× ~5 h lecciones |
| Estados y red | 4–6 | Vacío, offline, timeouts |
| Build instalable | 4–6 | APK/artefacto en dispositivo real |
| Retro | 1 | Política de logs y storage |
Si un día solo tienes 2 h: una lección con UI o build verificable.
Lecciones
Semana 1 — Scaffold, login y secure storage (~20 h)
| ID | Lección | ~h |
|---|---|---|
| L01 | Stack móvil y scaffold Agenda Ops | 5 |
| L02 | Pantalla login contra API staging | 5 |
| L03 | Secure storage de token o sesión | 5 |
| L04 | Errores de validación y flujo 401 | 5 |
Semana 2 — Lista de citas y roles (~20 h)
| ID | Lección | ~h |
|---|---|---|
| L05 | Lista de citas autenticada | 5 |
| L06 | Pull-to-refresh y paginación simple | 5 |
| L07 | Estados de carga en lista | 5 |
| L08 | Roles: confiar en la API, no solo en UI | 5 |
Semana 3 — Detalle, navegación y acciones (~20 h)
| ID | Lección | ~h |
|---|---|---|
| L09 | Pantalla detalle de cita | 5 |
| L10 | Navegación: tabs o drawer mínimo | 5 |
| L11 | Acciones permitidas: cancelar / atendida | 5 |
| L12 | Deep link opcional a una cita | 5 |
Semana 4 — Vacío, error, red y logging (~20 h)
| ID | Lección | ~h |
|---|---|---|
| L13 | Lista vacía con copy útil | 5 |
| L14 | Sin red: banner y reintento | 5 |
| L15 | Timeout, 5xx y mensajes humanos | 5 |
| L16 | AppSec móvil: no loguear PII ni tokens | 5 |
Semana 5 — Build release y entrega (~20 h)
| ID | Lección | ~h |
|---|---|---|
| L17 | Firma Android y keystore fuera del repo | 5 |
| L18 | Build release APK o artefacto | 5 |
| L19 | Release notes y demo cruzada con web | 5 |
| L20 | Cierre M20 — dominio y README proyecto | 5 |
Empieza por L01 hoy.
Lecturas (mapa rápido)
Canon: documentación oficial de Flutter o React Native (el stack elegido). Ver bibliografía.
| Semana | Lecciones | Docs oficiales | Enfoque |
|---|---|---|---|
| 1 | L01–L04 | Get started + HTTP + secure storage | Login staging, 401 |
| 2 | L05–L08 | Listas, refresh, async | P1 login+lista |
| 3 | L09–L12 | Navegación, detalle, deep links | Detalle cita |
| 4 | L13–L16 | Errores / conectividad / MASVS logging | P2 estados + storage |
| 5 | L17–L20 | Release build | P3 APK + demo web |
Regla: misma auth que la web; nada de secretos de API de servidor en el repositorio móvil.
Ejemplo — 401 en cliente móvil
// Flutter (idea): interceptor o wrapper HTTP
if (response.statusCode == 401) {
await secureStorage.delete(key: 'access_token');
navigator.pushReplacementNamed('/login');
return;
}
En web guardaste el JWT en cookie HttpOnly o memoria según M18; en móvil el equivalente es Keychain/Keystore vía flutter_secure_storage o el paquete oficial de RN. No copies el refresh token a logs.
Prácticas
- P1 — Login + lista: App contra API real; evidencia en
projects/m20-movil/demo-login-lista.md(capturas o video corto + commit hash). - P2 — Estados + storage:
projects/m20-movil/auth-storage.md+ capturas de vacío/error/401; código de storage seguro referenciado. - P3 — Build: APK o artefacto en
projects/m20-movil/build-evidence.md(comando usado, SHA, dispositivo probado).
Proyecto útil
App móvil Agenda Ops enlazada en projects/m20-movil/README.md:
- Repo o submódulo con el código.
- Misma sesión que la web: login en web y comprobar que la app con las mismas credenciales ve las mismas citas (o explicar diferencia si usas roles distintos).
- Objetivo demo: dueño abre el teléfono y ve el día de citas en 10 segundos.
Errores comunes
- App contra JSON mock que nunca se conecta al backend de M17/M19.
- Guardar JWT en almacenamiento sin cifrar “porque es más fácil”.
- Hardcodear
localhosten build de release. - Pantalla de carga infinita sin timeout.
- Reimplementar reglas de negocio en el móvil en vez de confiar en la API (doble fuente de verdad).
Evidencia de hecho
Marca la práctica en la UI solo si existe esto (o equivalente claro):
- P1 — Login+lista:
projects/m20-movil/demo-login-lista.md+ commits en repo app. - P2 — Estados:
projects/m20-movil/auth-storage.md+ evidencia UI vacío/error. - P3 — Build:
projects/m20-movil/build-evidence.mdcon artefacto instalado en dispositivo real. - Proyecto — App CRM :
projects/m20-movil/README.mdcon enlace al código y nota de paridad auth con web.
Criterios de dominio
- Misma sesión/auth que la web en demo reproducible.
- Build instalable en un dispositivo real (no solo emulador).
- Explicas dónde guardas el token y qué pasa en 401.
- Lista y detalle reflejan la API; sin datos inventados en el cliente.
- No hay secretos de servidor en el repositorio móvil.