App Movil (nvito-client)
Tabla de Contenidos
- Estructura del Proyecto
- Navegacion y Rutas
- Autenticacion Movil
- Pantallas del Anfitrion (HOST)
- Pantallas del Invitado (GUEST)
- Arquitectura del API Client
- Gestion de Estado
- QR Scanner y Check-in
- Media: Galeria y Audio Guestbook
- Push Notifications
- Deep Linking
- Design System
- Arquitectura SOLID
- Validacion con Zod
- Testing
- Calidad del Codigo
Estructura del Proyecto
nvito-client es una aplicacion movil nativa para iOS y Android construida con React Native y Expo. Es el 4to proyecto del ecosistema Nvito y complementa las aplicaciones web existentes ofreciendo una experiencia nativa optimizada para el dia del evento.
Stack Tecnologico
| Tecnologia | Version | Proposito |
|---|---|---|
| Expo | 54.x | Plataforma de desarrollo React Native |
| React Native | 0.81.x | Framework de UI nativa |
| TypeScript | 5.9 | Tipado estatico |
| Expo Router | 6.x | Navegacion file-based con layouts anidados |
| NativeWind | 4.x | Tailwind CSS para React Native |
| Tailwind CSS | 3.4 | Estilos utilitarios |
| TanStack React Query | 5.x | Cache y fetching de datos |
| expo-camera | 17.x | Camara y escaneo QR |
| expo-av | 16.x | Grabacion y reproduccion de audio |
| expo-notifications | 0.32.x | Push notifications (Expo Push API) |
| expo-secure-store | 15.x | Almacenamiento seguro de tokens |
| expo-image-picker | 17.x | Seleccion y captura de fotos |
| expo-location | 19.x | Geolocalizacion |
| expo-haptics | 15.x | Feedback haptico nativo |
| date-fns | 4.x | Formateo de fechas |
| Zod | 4.x | Validacion de esquemas |
| react-native-reanimated | 4.x | Animaciones de alto rendimiento |
| react-native-gesture-handler | 2.x | Gestos nativos |
| @expo/vector-icons | 15.x | Iconografia (Ionicons) |
Configuracion de Plataforma
| Aspecto | iOS | Android |
|---|---|---|
| Identificador | com.nvito.client | com.nvito.client |
| Soporte tablet | Si (iPad) | Si |
| Deep links | Associated Domains (applinks:nvito.app) | Intent Filters (nvito.app/join) |
| Permisos | Camara, Microfono, Fotos, Ubicacion | CAMERA, RECORD_AUDIO, LOCATION |
| Arquitectura | New Architecture habilitada | New Architecture habilitada |
Estructura de Directorios
nvito-client/
├── app/ # Expo Router (file-based routing)
│ ├── _layout.tsx # Root layout (QueryClient + AuthProvider)
│ ├── index.tsx # Redirect segun auth state
│ ├── +not-found.tsx # Pantalla 404
│ ├── (auth)/ # Grupo de autenticacion
│ │ ├── _layout.tsx # Stack navigation
│ │ ├── login.tsx # Login anfitrion: codigo + PIN
│ │ └── join.tsx # Acceso invitado: deep link / QR
│ └── (app)/ # Grupo principal (auth guard)
│ ├── _layout.tsx # Guard + push token registration
│ ├── (host)/ # Screens del anfitrion
│ │ ├── _layout.tsx # Bottom Tab Navigation (5 tabs)
│ │ ├── dashboard.tsx # Orquestador (~120 LOC)
│ │ │ ├── stat-card.tsx # Card de estadistica individual
│ │ │ ├── countdown-card.tsx # Countdown al evento
│ │ │ ├── rsvp-breakdown-card.tsx # Desglose RSVP
│ │ │ └── recent-activity-card.tsx # Feed de actividad
│ │ ├── guests.tsx # Orquestador (~150 LOC)
│ │ │ ├── guest-card.tsx # Card de invitado (pressable)
│ │ │ ├── guest-detail-sheet.tsx # Bottom sheet detalle completo
│ │ │ ├── stats-bar.tsx # Barra de estadisticas
│ │ │ └── guest-filters.tsx # Filtros de status
│ │ ├── scanner.tsx # Orquestador (~80 LOC)
│ │ │ ├── check-in-result-modal.tsx
│ │ │ ├── partial-check-in-modal.tsx
│ │ │ ├── manual-check-in-sheet.tsx # Check-in manual por nombre
│ │ │ ├── stats-header.tsx
│ │ │ ├── recent-check-ins-list.tsx
│ │ │ ├── permission-screen.tsx
│ │ │ └── hooks/
│ │ │ ├── use-scanner.ts
│ │ │ └── use-manual-search.ts # Busqueda + check-in manual
│ │ ├── tables.tsx # Mesas (vista lista + plano)
│ │ ├── more.tsx # Menu: memorias, itinerario, regalos, logout
│ │ ├── memorias.tsx # Orquestador: Fotos | Audios | Mensajes
│ │ │ ├── segmented-control.tsx # Selector 3 segmentos con badges
│ │ │ ├── memorias-gallery.tsx # Grid de fotos + viewer
│ │ │ ├── memorias-audios.tsx # Lista + filtros + player
│ │ │ ├── memorias-messages.tsx # Lista + filtros
│ │ │ ├── photo-thumbnail.tsx # Thumbnail reutilizable
│ │ │ ├── photo-viewer.tsx # Modal full-screen
│ │ │ └── message-card.tsx # Card de mensaje
│ │ ├── itinerary.tsx # Itinerario HOST (shared components)
│ │ ├── gift-registry.tsx # Mesa de regalos read-only
│ │ │ ├── cash-fund-progress.tsx # Barra de progreso (centavos→pesos)
│ │ │ └── registry-item-card.tsx # Card de tienda con link externo
│ │ ├── gallery.tsx # Galeria de fotos (hidden tab)
│ │ ├── audio-guestbook.tsx # Orquestador (~117 LOC)
│ │ │ ├── audio-item.tsx # Item de mensaje de audio
│ │ │ ├── filter-tabs.tsx # Tabs de filtro
│ │ │ └── hooks/use-audio-player.ts
│ │ └── messages.tsx # Mensajes de texto (hidden tab)
│ └── (guest)/ # Screens del invitado
│ ├── _layout.tsx # Bottom Tab Navigation (4 tabs)
│ ├── home.tsx # QR pass + countdown + info
│ ├── itinerary.tsx # Programa + ubicaciones
│ ├── gallery.tsx # Orquestador (~120 LOC)
│ │ ├── photo-thumbnail.tsx
│ │ ├── photo-viewer.tsx
│ │ └── hooks/use-photo-upload.ts
│ └── interact.tsx # Audio, mensajes, regalos
├── src/
│ ├── api/ # Capa de comunicacion HTTP
│ │ ├── client.ts # Fetch wrapper con interceptores
│ │ ├── errors.ts # ApiError class
│ │ ├── __tests__/ # Tests de API client
│ │ │ └── errors.test.ts
│ │ └── services/ # Servicios por dominio (6 archivos)
│ │ ├── auth.service.ts # Login, refresh, push token
│ │ ├── events.service.ts # Datos del evento, stats
│ │ ├── guests.service.ts # Invitados, check-in, QR
│ │ ├── media.service.ts # Galeria, audio, mensajes
│ │ ├── payments.service.ts # Cash fund (Stripe)
│ │ ├── sharing.service.ts # Compartir evento
│ │ └── __tests__/ # Tests de servicios (6 archivos)
│ ├── hooks/ # Custom hooks
│ │ ├── queries/ # React Query hooks (7 archivos)
│ │ │ ├── keys.ts # Query Key Factory
│ │ │ ├── use-event.ts # Datos del evento
│ │ │ ├── use-event-stats.ts # Estadisticas en vivo
│ │ │ ├── use-guests.ts # Lista de invitados
│ │ │ ├── use-media.ts # Galeria, audio, mensajes
│ │ │ ├── use-payments.ts # Pagos cash fund
│ │ │ ├── use-sharing.ts # Compartir evento
│ │ │ └── __tests__/ # Tests de hooks (6 archivos)
│ │ ├── use-notifications.ts # Push notifications
│ │ └── use-responsive.ts # Responsive / tablet detection
│ ├── contexts/
│ │ ├── AuthContext.tsx # Provider limpio (~95 LOC)
│ │ ├── ConnectivityContext.tsx # Monitor de conectividad
│ │ ├── auth/ # Descomposicion SOLID del auth
│ │ │ ├── auth-reducer.ts # AuthState, AuthAction, reducer
│ │ │ ├── auth-storage.ts # Persistencia en SecureStore
│ │ │ └── use-session-restore.ts # Restauracion + auto-refresh
│ │ └── __tests__/ # Tests de contextos (4 archivos)
│ ├── storage/
│ │ ├── secure-store.ts # Wrapper de expo-secure-store
│ │ ├── offline-queue.ts # Cola offline (AsyncStorage)
│ │ └── __tests__/ # Tests de storage (2 archivos)
│ ├── validations/ # Zod schemas para API responses
│ │ ├── auth.ts # loginResponseSchema, refreshResponseSchema
│ │ ├── event.ts # eventDetailSchema, eventStatsSchema
│ │ ├── guest.ts # checkInResultSchema, guestQrPassSchema
│ │ └── __tests__/ # Tests de validacion (3 archivos, 39 tests)
│ ├── theme/ # Design system centralizado
│ │ ├── colors.ts # Fuente unica de verdad de colores
│ │ └── index.ts # Barrel export
│ ├── types/ # TypeScript types
│ │ ├── api.types.ts # Tipos genericos de API
│ │ ├── auth.types.ts # LoginResponse, MobileRole
│ │ ├── event.types.ts # Event, EventStats
│ │ ├── guest.types.ts # Guest, CheckInResult
│ │ ├── media.types.ts # GalleryPhoto, AudioMessage
│ │ └── payments.types.ts # CashFundIntentResponse
│ ├── utils/
│ │ ├── constants.ts # Constantes (timeouts, tamanos)
│ │ ├── date.ts # Formateo de fechas
│ │ ├── format.ts # Formateo general
│ │ └── __tests__/ # Tests de utils (2 archivos)
│ ├── config/
│ │ ├── env.ts # Variables de entorno
│ │ └── query-persister.ts # Persistencia de React Query
│ └── components/
│ ├── ShareCardPreview.tsx # Preview de card para compartir
│ └── itinerary/ # Componentes compartidos HOST/GUEST
│ ├── timeline-item.tsx # Item de timeline con helpers
│ └── location-card.tsx # Card de ubicacion con link a mapas
├── test/
│ └── setup.ts # Mocks globales (10 modulos nativos)
├── assets/ # Iconos, splash, fuentes
├── jest.config.js # Configuracion Jest + jest-expo
├── app.json # Configuracion Expo
├── tailwind.config.js # NativeWind + colores de theme/colors
├── babel.config.js # Babel preset
├── metro.config.js # Metro bundler
└── tsconfig.json # TypeScript (strict mode)
Navegacion y Rutas
La app usa Expo Router v6 con navegacion file-based. La estructura de navegacion se adapta segun el rol del usuario autenticado (HOST o GUEST).
Diagrama de Navegacion
Descripcion de Rutas
| Ruta | Rol | Descripcion |
|---|---|---|
/ | — | Redirect a login o app segun estado de sesion |
/(auth)/login | — | Login anfitrion con codigo de evento + PIN |
/(auth)/join | — | Acceso invitado via deep link o codigo QR |
/(app)/(host)/dashboard | HOST | Dashboard con metricas en tiempo real |
/(app)/(host)/guests | HOST | Lista de invitados con busqueda y filtros |
/(app)/(host)/scanner | HOST | QR Scanner para check-in de invitados |
/(app)/(host)/tables | HOST | Mesas: vista lista + plano visual read-only |
/(app)/(host)/more | HOST | Menu con acceso a memorias, itinerario, regalos y logout |
/(app)/(host)/memorias | HOST | Vista unificada: Fotos, Audios y Mensajes con segmented control |
/(app)/(host)/itinerary | HOST | Itinerario del evento (componentes compartidos con GUEST) |
/(app)/(host)/gift-registry | HOST | Mesa de regalos read-only (fondo, tiendas, cuentas bancarias) |
/(app)/(host)/gallery | HOST | Galeria de fotos (hidden tab, retrocompatibilidad) |
/(app)/(host)/audio-guestbook | HOST | Mensajes de audio (hidden tab, retrocompatibilidad) |
/(app)/(host)/messages | HOST | Mensajes de texto (hidden tab, retrocompatibilidad) |
/(app)/(guest)/home | GUEST | QR pass, countdown, info rapida |
/(app)/(guest)/itinerary | GUEST | Itinerario del evento + ubicaciones |
/(app)/(guest)/gallery | GUEST | Galeria colaborativa + subir fotos |
/(app)/(guest)/interact | GUEST | Audio guestbook, mensajes, mesa de regalos |
Tabs del Anfitrion
| Tab | Icono | Pantalla |
|---|---|---|
| Dashboard | bar-chart-outline | Estadisticas en vivo del evento |
| Invitados | people-outline | Lista de invitados con estados |
| Scanner | scan-outline | QR Scanner (tab central destacado) |
| Mesas | grid-outline | Mesas con vista lista y plano visual |
| Mas | menu-outline | Memorias, itinerario, mesa de regalos, compartir, logout |
Tabs del Invitado
| Tab | Icono | Pantalla |
|---|---|---|
| Inicio | home-outline | QR pass + countdown |
| Programa | calendar-outline | Itinerario + ubicaciones |
| Galeria | camera-outline | Ver + subir fotos |
| Interactuar | mic-outline | Audio, mensajes, regalos |
Autenticacion Movil
La app usa un sistema de autenticacion independiente de Clerk, basado en JWT con un secreto dedicado (MOBILE_JWT_SECRET). Esto permite que los invitados accedan sin necesidad de crear una cuenta.
Flujo de Autenticacion del Anfitrion
Flujo de Autenticacion del Invitado
Almacenamiento de Sesion
| Dato | Storage | Proposito |
|---|---|---|
accessToken (JWT) | expo-secure-store | Autenticacion de requests |
refreshToken | expo-secure-store | Renovacion del JWT |
| Session data (rol, eventId) | expo-secure-store | Restauracion de sesion |
Auto-refresh de Tokens
El AuthContext programa automaticamente la renovacion del JWT:
- Al hacer login, se programa un timer basado en
expiresIndel JWT - El refresh se ejecuta cuando quedan < 2 minutos de vida
- Al volver del background, se programa un refresh conservador inmediato
- La rotacion de refresh tokens garantiza que cada token solo se use una vez
JWT Payload
// HOST
{ sub: eventId, role: "HOST", userId: string, sessionId: string }
// GUEST
{ sub: eventId, role: "GUEST", guestId: string, sessionId: string }
Pantallas del Anfitrion (HOST)
Dashboard
Pantalla principal del anfitrion con estadisticas del evento en tiempo real.
| Seccion | Descripcion |
|---|---|
| Header | Nombre del evento + fecha |
| Countdown | Dias, horas, minutos hasta el evento |
| Stats Cards | Total invitados, confirmados, check-ins, fotos |
| RSVP Ring | Grafico circular de confirmaciones |
| Actividad reciente | Feed de ultimos RSVP, check-ins, fotos |
- Polling cada 30 segundos para datos en vivo
- Pull-to-refresh manual
- Layout adaptativo: cards en grid para tablet
Invitados
Lista completa de invitados con busqueda y filtros.
| Funcionalidad | Descripcion |
|---|---|
| Busqueda | Por nombre del invitado |
| Filtros | Por status: todos, confirmados, pendientes, declinados |
| Guest Card | Nombre, status badge, grupo, # acompanantes. Pressable para abrir detalle |
| Stats Header | Contadores por status |
| Guest Detail Sheet | Bottom sheet modal al tocar un invitado (ver abajo) |
- FlatList virtualizada para rendimiento con listas grandes
- Layout responsive: 2 columnas en tablet
Detalle de Invitado (Bottom Sheet)
Al tocar un GuestCard, se abre un modal slide-up con el detalle completo del invitado (useGuestDetail hook). Secciones:
| Seccion | Contenido |
|---|---|
| Header | Avatar (iniciales), nombre completo, status badge, indicador check-in |
| Info | Grupo (con color), mesa, asiento, asistentes (checkedIn/groupSize) |
| RSVP | Status, cantidad de asistentes, mensaje, fecha de respuesta |
| Necesidades | Restricciones dietarias (chips), alergias, necesidades especiales, notas dieteticas |
| Notas | Notas generales del invitado |
| Tags | Etiquetas como chips de colores |
Componentes: guests/guest-detail-sheet.tsx, guests/guest-card.tsx (pressable).
Scanner (QR Check-in)
Scanner QR fullscreen para registrar la asistencia de invitados.
| Caracteristica | Detalle |
|---|---|
| Scanner | expo-camera CameraView con barcode scanning |
| Tipos QR | ['qr'] |
| Check-in parcial | Para grupos, permite indicar cuantos asisten |
| Estadisticas en vivo | Header con checked-in / total |
| Polling | Cada 15 segundos para stats actualizados |
| Feedback | expo-haptics (success, warning, error) |
| Check-in manual | Busqueda por nombre para check-in sin QR (ver abajo) |
| Historial | Lista de ultimos check-ins debajo del scanner |
Check-in Manual por Nombre
Boton de busqueda en el header del Scanner que abre un modal slide-up para registrar invitados sin necesidad de escanear QR:
| Funcionalidad | Descripcion |
|---|---|
| Busqueda | Input de texto, se activa al escribir 2+ caracteres |
| Resultados | Lista de invitados con avatar, nombre, grupo, status y badge check-in |
| Seleccion | Al tocar un invitado, se muestra el selector de asistentes (si groupSize > 1) |
| Confirmacion | Boton para confirmar el check-in manual con la cantidad de asistentes |
| Resultado | Reutiliza el CheckInResultModal existente con feedback haptico |
Componentes: scanner/manual-check-in-sheet.tsx, scanner/hooks/use-manual-search.ts.
Mesas (HOST)
Pantalla read-only para visualizar la distribucion y asignacion de mesas del evento. La gestion (crear, editar, asignar) se realiza exclusivamente desde el admin web.
| Funcionalidad | Descripcion |
|---|---|
| Toggle vista | Selector segmentado Lista / Plano |
| Stats summary | Barra con total mesas, capacidad, ocupados, disponibles, % |
| Vista Lista | FlatList con TableCard (forma visual, barra de ocupacion, invitados). Layout responsive: 2 columnas en tablet |
| Vista Plano | ScrollView con pinch-to-zoom (0.5x–3x). Mismas dimensiones y auto-layout que el admin (900x800, TABLE_SLOT=180px) |
| Detalle modal | Sheet slide-up con stats de mesa + lista de invitados asignados |
| Colores ocupacion | Verde (menos de 70%), Amarillo (70-90%), Rojo (mas de 90%) |
| Formas visuales | ROUND (circulo), SQUARE, RECTANGLE, OVAL — con borde y fondo por ocupacion |
| Pull-to-refresh | En vista lista |
| Empty state | "No hay mesas configuradas. Configura las mesas desde el panel de administracion" |
Componentes: tables.tsx (orquestador), tables/stats-summary.tsx, tables/table-card.tsx, tables/table-list.tsx, tables/table-visual.tsx, tables/table-plan.tsx, tables/table-detail-sheet.tsx.
Memorias
Vista unificada que fusiona Galeria, Audio Guestbook y Mensajes en una sola pantalla con segmented control. Accesible desde el menu "Mas", reemplaza los 3 items separados.
| Funcionalidad | Descripcion |
|---|---|
| Segmented control | 3 segmentos: Fotos, Audios, Mensajes — con badges de pendientes |
| Header | Back button + titulo "Memorias" |
Segmento Fotos
| Funcionalidad | Descripcion |
|---|---|
| Grid | 3 columnas (movil) / 5 columnas (tablet) |
| Thumbnail | Foto con badge del nombre del invitado |
| Viewer modal | Fullscreen con caption, autor, fecha relativa |
| Paginacion | Controles prev/next para navegacion |
Segmento Audios
| Funcionalidad | Descripcion |
|---|---|
| Filtros | Todos / Pendientes / Aprobados |
| Reproductor | Play/pause inline con expo-av |
| Aprobacion | Boton para aprobar mensajes pendientes |
| Badge | Contador de audios pendientes en segmented control |
Segmento Mensajes
| Funcionalidad | Descripcion |
|---|---|
| Filtros | Todos / No leidos |
| Indicador | Punto violeta + fondo destacado para no leidos |
| Avatar | Inicial del nombre del invitado |
| Badge | Contador de no leidos en segmented control |
Componentes: memorias.tsx (orquestador), memorias/segmented-control.tsx, memorias/memorias-gallery.tsx, memorias/memorias-audios.tsx, memorias/memorias-messages.tsx, memorias/photo-thumbnail.tsx, memorias/photo-viewer.tsx, memorias/message-card.tsx.
Itinerario (HOST)
Programa del evento con timeline visual identico al del invitado, accesible desde el menu "Mas". Reutiliza los mismos componentes compartidos (src/components/itinerary/).
| Funcionalidad | Descripcion |
|---|---|
| Header | Back button + titulo "Itinerario" |
| Timeline | Linea vertical con items ordenados por hora |
| Item activo | Destacado con punto primary cuando es la hora actual |
| Items pasados | Estilo atenuado (opacity reducida) |
| Ubicaciones | Cards con icono por tipo + link a Google Maps |
| Empty state | "El itinerario aun no esta disponible" |
Componentes compartidos HOST/GUEST: src/components/itinerary/timeline-item.tsx (exporta isCurrentItem(), isPastItem(), TimelineItem), src/components/itinerary/location-card.tsx (LocationCard con icono por tipo de ubicacion).
Mesa de Regalos (HOST)
Vista read-only del estado de la mesa de regalos del evento. Accesible desde el menu "Mas". No incluye widget de Stripe ni boton de contribucion (el HOST no dona a su propio fondo).
| Funcionalidad | Descripcion |
|---|---|
| Header | Back button + titulo "Mesa de Regalos" |
| Fondo en efectivo | Barra de progreso, monto recaudado/meta (centavos→pesos MXN), porcentaje, mensaje |
| Tiendas/registros | Cards con nombre de tienda, descripcion, link externo |
| Cuentas bancarias | Nombre del banco + descripcion |
| Nota | Texto adicional del anfitrion |
| Empty state | "No hay mesa de regalos configurada" |
| Conversion | Valores en centavos convertidos a pesos con Intl.NumberFormat('es-MX') |
| Colores progreso | Verde (al menos 100%), Ambar (al menos 75%), Violeta (default) |
Componentes: gift-registry.tsx (orquestador), gift-registry/cash-fund-progress.tsx, gift-registry/registry-item-card.tsx.
Pantallas del Invitado (GUEST)
Home
Pantalla principal del invitado con bienvenida personalizada, pase QR y countdown al evento.
| Seccion | Descripcion |
|---|---|
| Bienvenida | Saludo personalizado con nombre del invitado |
| QR Code | Codigo QR grande para check-in |
| Countdown | Dias, horas, minutos hasta el evento |
| Info rapida | Hora, mesa asignada, grupo |
| Nombre del evento | Titulo prominente + fecha |
Itinerario
Programa del evento con timeline visual. Las LocationCards incluyen informacion enriquecida.
| Funcionalidad | Descripcion |
|---|---|
| Timeline | Linea vertical con items ordenados por hora |
| Item activo | Destacado basado en la hora actual |
| Ubicaciones | Links a mapas para cada item |
| Codigo de vestimenta | Destacado visual llamativo por ubicacion |
| Estacionamiento | Indicador de disponibilidad e instrucciones |
| Accesibilidad | Informacion de accesibilidad por ubicacion |
| Info | Hora, titulo, descripcion por item |
Interactuar
Pantalla con secciones condicionales (service gating) para que el invitado interactue con el evento.
| Seccion | Servicio requerido | Descripcion |
|---|---|---|
| Mis fotos | GALLERY / GUEST_UPLOADS | Solo muestra fotos subidas por el invitado |
| Audio Guestbook | AUDIO_GUESTBOOK | Grabar mensaje de voz + ver mis audios con badge de estado |
| Mensaje | GUEST_MESSAGES | Input de texto para dejar mensaje a anfitriones |
| Empty state | — | Cuando ninguna seccion esta habilitada |
Hospedaje
Pantalla accesible desde "Mas" con toda la informacion de hospedaje del evento.
| Seccion | Descripcion |
|---|---|
| Info de viaje | Aeropuerto mas cercano, estacionamiento, tips |
| Hoteles | Cards con foto, direccion, telefono, distancia, botones "Ver sitio" y "Reservar" |
| Transporte | Opciones de transporte con iconos por tipo |
Mas
Menu con opciones condicionales segun servicios habilitados.
| Opcion | Servicio requerido | Descripcion |
|---|---|---|
| Mesa de regalos | GIFT_REGISTRY | Links a tiendas + fondo en efectivo |
| Hospedaje | ACCOMMODATION | Hoteles, transporte, tips de viaje |
| Compartir evento | — | Siempre disponible |
Service Gating
Las secciones y pantallas del invitado se muestran condicionalmente segun los servicios habilitados para el evento (useEventServices()). Si los servicios aun no se han cargado, se muestran todas las secciones como fallback.
Arquitectura del API Client
El sistema de comunicacion con el backend sigue una arquitectura en capas, similar a nvito-admin pero adaptada para autenticacion movil.
API Client (client.ts)
Fetch wrapper que encapsula todas las operaciones HTTP:
- Metodos:
get<T>,post<T>,patch<T>,delete<T> - Base URL: Configurable via
EXPO_PUBLIC_API_URL(default:http://localhost:3000/v1) - Auth interceptor: Inyecta
Bearer tokendesdeSecureStoreautomaticamente - Auto-refresh: Si recibe 401, intenta renovar el JWT con el refresh token
- Error handling: Clase
ApiErrorconstatusCodeymessage
Services (6 archivos)
| Servicio | Archivo | Endpoints |
|---|---|---|
| Auth | auth.service.ts | Login, guest access, refresh, logout, push token |
| Events | events.service.ts | Datos del evento, stats, servicios, itinerario, ubicaciones |
| Guests | guests.service.ts | Lista de invitados, check-in, QR pass, RSVP stats |
| Media | media.service.ts | Galeria, upload URLs, audio guestbook, mensajes, gift registry |
| Payments | payments.service.ts | Cash fund intent (Stripe), confirmacion de pago |
| Sharing | sharing.service.ts | Compartir evento, generar card de preview |
Prefijo de Endpoints Moviles
Todos los endpoints consumidos por la app movil usan el prefijo /v1/mobile/ y estan protegidos por el MobileAuthGuard (independiente de Clerk). Los endpoints son wrappers ligeros que reutilizan los servicios existentes del backend.
Gestion de Estado
React Query 5
La app usa TanStack React Query como unico sistema de gestion de estado del servidor:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 min stale
gcTime: 24 * 60 * 60 * 1000, // 24h en cache
retry: 2,
},
},
});
Query Key Factory
El archivo keys.ts centraliza todas las claves de cache:
queryKeys.events.detail(eventId) // ['events', eventId]
queryKeys.events.stats(eventId) // ['events', eventId, 'stats']
queryKeys.guests.list(eventId, params) // ['guests', eventId, 'list', params]
queryKeys.media.gallery(eventId, page) // ['media', eventId, 'gallery', page]
queryKeys.media.audioMessages(eventId) // ['media', eventId, 'audio']
queryKeys.media.messages(eventId) // ['media', eventId, 'messages']
Hooks de React Query
| Hook | Tipo | Descripcion |
|---|---|---|
useEvent | Query | Datos del evento actual |
useEventStats | Query | Estadisticas con polling |
useGuests | Query | Lista de invitados con filtros |
useGallery | Query | Fotos paginadas |
useAudioMessages | Query | Mensajes de audio |
useMessages | Query | Mensajes de texto |
useGuestDetail | Query | Detalle completo de un invitado |
useGuestStats | Query | Estadisticas de invitados |
useCheckIn | Mutation | Registrar check-in |
useManualCheckIn | Mutation | Check-in manual por nombre (sin QR) |
useUploadPhoto | Mutation | Subir foto con presigned URL |
useUploadAudio | Mutation | Subir audio con presigned URL |
useApproveAudio | Mutation | Aprobar mensaje de audio |
useSendMessage | Mutation | Enviar mensaje de texto |
AuthContext (Descomposicion SOLID)
El contexto de autenticacion fue descompuesto siguiendo SRP en 4 archivos con responsabilidad unica:
| Archivo | LOC | Responsabilidad |
|---|---|---|
AuthContext.tsx | ~95 | Provider orquestador (login, logout, render) |
auth/auth-reducer.ts | ~54 | Estado, acciones y reducer puro |
auth/auth-storage.ts | ~86 | Persistencia tipada en SecureStore |
auth/use-session-restore.ts | ~68 | Restauracion de sesion + callbacks API + auto-refresh |
El contexto gestiona el estado de sesion con un useReducer:
| Propiedad | Tipo | Descripcion |
|---|---|---|
isAuthenticated | boolean | Usuario autenticado |
isLoading | boolean | Cargando sesion |
role | 'HOST' | 'GUEST' | null | Rol del usuario |
eventId | string | null | ID del evento activo |
guestId | string | null | ID del invitado (GUEST) |
userId | string | null | ID del usuario (HOST) |
accessToken | string | null | JWT actual |
refreshToken | string | null | Token de renovacion |
Acciones del reducer:
RESTORE_SESSION— Restaurar sesion desde SecureStore al montarLOGIN— Login exitoso (host o guest)LOGOUT— Cerrar sesion y limpiar tokensTOKENS_REFRESHED— Actualizar tokens tras refreshSET_LOADING— Cambiar estado de carga
QR Scanner y Check-in
Flujo Completo de Check-in
Datos del QR
Cada invitado tiene un QR unico que contiene su guestId. Al escanear:
- La app extrae el
guestIddel contenido del QR - Envia
POST /v1/mobile/events/:id/check-incon elguestId - El API verifica que el invitado pertenece al evento y no esta ya registrado
- Si el invitado tiene
groupSize > 1, se muestra un modal para seleccionar cuantos asisten
Check-in Parcial (Grupos)
Cuando un invitado tiene acompanantes, el scanner muestra:
- Nombre del invitado y total del grupo
- Stepper numerico para seleccionar asistentes
- Rango: 1 hasta
groupSize - Soporte para re-escaneo (agregar mas personas despues)
Media: Galeria y Audio Guestbook
Upload de Fotos
Grabacion de Audio
Limites de Media
| Tipo | Limite |
|---|---|
| Imagen max width | 2048 px |
| Calidad de imagen | 0.8 (80%) |
| Duracion max audio | 120 segundos (2 min) |
Push Notifications
Registro de Push Token
Configuracion del Handler
La app configura las notificaciones en foreground para mostrar alertas, sonido, badge y banners:
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
shouldShowBanner: true,
shouldShowList: true,
}),
});
Eventos que Disparan Push
| Evento | Destinatario | Mensaje |
|---|---|---|
| Nuevo RSVP | HOST | "{nombre} confirmo asistencia" |
| Check-in | HOST | "{nombre} llego al evento" |
| Foto subida | HOST | "Nueva foto de {nombre}" |
| Audio subido | HOST | "Nuevo mensaje de voz de {nombre}" |
| Cambio en evento | GUEST | "El evento fue actualizado" |
| Recordatorio | GUEST | "Recuerda: {evento} es manana" |
Canal de Android
Para Android, se configura un canal de notificaciones dedicado:
Notifications.setNotificationChannelAsync('default', {
name: 'Nvito',
importance: Notifications.AndroidImportance.HIGH,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#7C3AED',
});
Deep Linking
La app soporta deep links para acceso directo de invitados.
URIs Soportados
| URI | Proposito |
|---|---|
nvito://join/{accessToken} | Acceso directo invitado (custom scheme) |
nvito://event/{eventCode} | Acceso anfitrion (solicita PIN) |
https://nvito.app/join/{token} | Universal link (iOS Associated Domains) |
Configuracion
iOS — Associated Domains:
"associatedDomains": ["applinks:nvito.app"]
Android — Intent Filters:
"intentFilters": [{
"action": "VIEW",
"autoVerify": true,
"data": [{
"scheme": "https",
"host": "nvito.app",
"pathPrefix": "/join"
}],
"category": ["BROWSABLE", "DEFAULT"]
}]
Custom Scheme:
"scheme": "nvito"
Design System
Fuente Unica de Verdad: src/theme/colors.ts
Todos los colores de la aplicacion estan centralizados en un unico archivo src/theme/colors.ts exportado como as const para type safety. El archivo tailwind.config.js importa estos colores directamente, eliminando la duplicacion.
Antes: ~130 instancias de colores hex hardcodeados dispersos en 12+ archivos.
Despues: 0 colores hardcodeados; todos referencian colors.* desde el theme.
Paleta de Colores
| Token | Color | Hex | Uso |
|---|---|---|---|
primary | Violeta | #7C3AED | Acciones principales, tabs activos, badges |
secondary | Rosa | #EC4899 | Acentos, elementos decorativos |
success | Verde | #10B981 | Check-in exitoso, confirmados |
warning | Ambar | #F59E0B | Pendientes, alertas suaves |
error | Rojo | #EF4444 | Errores, declinados, logout |
info | Azul | #3B82F6 | Informacion, links |
background | Blanco | #FFFFFF | Fondo principal |
surface | Gris claro | #F9FAFB | Fondo de cards y secciones |
text-primary | Gris oscuro | #111827 | Texto principal |
text-secondary | Gris medio | #6B7280 | Texto secundario |
text-muted | Gris claro | #9CA3AF | Texto deshabilitado |
Integracion con Tailwind
// tailwind.config.js
const { colors } = require('./src/theme/colors');
module.exports = {
theme: { extend: { colors } },
};
NativeWind (Tailwind CSS para React Native)
La app usa NativeWind v4 que permite usar clases de Tailwind CSS directamente en componentes React Native:
<View className="flex-1 bg-background p-4">
<Text className="text-xl font-heading text-text-primary">Titulo</Text>
<Text className="text-sm text-text-secondary mt-1">Descripcion</Text>
</View>
Tipografia
| Token | Fuente | Uso |
|---|---|---|
font-sans | Inter | Texto general |
font-heading | Inter-Bold | Titulos y encabezados |
Responsive / Tablet
La app detecta si se ejecuta en tablet mediante el hook useResponsive() y adapta los layouts:
| Aspecto | Movil | Tablet |
|---|---|---|
| Grid de galeria | 3 columnas | 5 columnas |
| Cards de stats | Stack vertical | Grid 2x2 |
| Lista de invitados | 1 columna | 2 columnas |
| Padding general | 16px | 24px |
Iconografia
Se usa Ionicons via @expo/vector-icons con estilo outline consistente en toda la app.
Variables de Entorno
| Variable | Descripcion | Default |
|---|---|---|
EXPO_PUBLIC_API_URL | URL base de nvito-api | http://localhost:3000/v1 |
EXPO_PUBLIC_APP_NAME | Nombre de la aplicacion | Nvito |
EXPO_PUBLIC_ENV | Entorno actual | development |
Arquitectura SOLID
El proyecto aplica Single Responsibility Principle (SRP) sistematicamente. Las pantallas grandes fueron descompuestas en orquestadores livianos + sub-componentes + hooks custom.
Descomposicion de Pantallas
| Pantalla | Antes (LOC) | Despues (LOC) | Sub-componentes | Hooks extraidos |
|---|---|---|---|---|
scanner.tsx | 737 | ~80 | 5 (modals, stats, list, permissions) | use-scanner.ts |
dashboard.tsx | 326 | ~120 | 4 (stat-card, countdown, rsvp, activity) | — |
guests.tsx | 331 | ~150 | 3 (guest-card, stats-bar, filters) | — |
gallery.tsx (guest) | 303 | ~120 | 2 (thumbnail, viewer) | use-photo-upload.ts |
audio-guestbook.tsx | 266 | ~117 | 2 (audio-item, filter-tabs) | use-audio-player.ts |
| Total | 1,963 | ~587 | 16 | 3 |
Reduccion total: 70% menos lineas en archivos orquestadores, distribuidas en componentes enfocados y reutilizables.
Descomposicion del AuthContext
| Archivo | LOC | Responsabilidad |
|---|---|---|
AuthContext.tsx | ~95 | Provider: login/logout callbacks, render children |
auth/auth-reducer.ts | ~54 | Reducer puro: estado, acciones, transiciones |
auth/auth-storage.ts | ~86 | Persistencia: save/restore/clear en SecureStore tipado |
auth/use-session-restore.ts | ~68 | Efecto: restaurar sesion al montar, configurar callbacks API, programar refresh |
Validacion con Zod
El proyecto incluye schemas Zod v4 para validar respuestas de API en runtime, complementando la seguridad de tipos de TypeScript en compilacion.
Schemas Disponibles
| Archivo | Schemas | Proposito |
|---|---|---|
validations/auth.ts | loginResponseSchema, refreshResponseSchema | Validar respuestas de login y refresh |
validations/event.ts | eventDetailSchema, eventStatsSchema | Validar datos del evento y estadisticas |
validations/guest.ts | checkInResultSchema, guestQrPassSchema | Validar resultado de check-in y QR pass |
Cada schema tiene tests que verifican datos validos, campos faltantes y tipos incorrectos.
Testing
El proyecto cuenta con 28 suites de prueba y 216 tests individuales, todos con 100% de tasa de exito.
Framework
| Herramienta | Version | Proposito |
|---|---|---|
| Jest | via jest-expo | Test runner |
| jest-expo | Preset | Configuracion para Expo/React Native |
| @testing-library/react-native | 13.x | Renders de hooks y componentes |
| @testing-library/jest-native | Matchers | Matchers nativos (toBeOnTheScreen, etc.) |
Mocks Globales
El archivo test/setup.ts configura mocks de 10 modulos nativos que no estan disponibles en Node.js:
| Modulo | Mock |
|---|---|
expo-secure-store | In-memory store con get/set/delete |
expo-crypto | UUID fijo + hash mock |
expo-camera | CameraView + useCameraPermissions |
@react-native-async-storage/async-storage | In-memory store completo |
@react-native-community/netinfo | Conectividad siempre true |
expo-constants | Config con API_URL local |
expo-notifications | Permisos + push token mock |
expo-haptics | Feedback haptico no-op |
expo-router | useRouter, useLocalSearchParams, etc. |
Cobertura por Area
| Area | Suites | Tests | Que prueban |
|---|---|---|---|
| Utils | 2 | 17 | date.ts (formatEventDate, formatRelative, getCountdown), format.ts |
| API errors | 1 | 12 | ApiError constructor, isUnauthorized, isForbidden, isNotFound, isServerError |
| Servicios API | 6 | 38 | auth, events, guests, media, payments, sharing |
| Hooks queries | 6 | 26 | use-event, use-event-stats, use-guests, use-media, use-payments, use-sharing |
| Storage | 2 | 21 | secure-store (7 funciones), offline-queue (6 operaciones) |
| Contexts | 4 | 33 | AuthContext (login/logout/restore), ConnectivityContext, auth-reducer, auth-storage |
| Validaciones Zod | 3 | 39 | auth, event, guest schemas (datos validos + invalidos) |
| Theme | 1 | — | Colors structure |
| Total | 28 | 216 |
Calidad del Codigo
| Indicador | Valor |
|---|---|
| TypeScript strict | Habilitado |
as any | 0 |
| JSDoc | 100% en metodos publicos (~80 JSDoc comments) |
| Zod schemas | 6 schemas con tests |
| Test suites | 28 |
| Tests individuales | 216 |
| Tasa de exito | 100% |
| Bugs corregidos | 3 (import bug, as any, hooks duplicados) |
| Colores hardcodeados | 0 (130+ migrados a theme) |
| Componentes >300 LOC | 0 (5 descompuestos via SOLID) |
| Design tokens centralizados | Si (theme/colors.ts → tailwind.config.js) |
| Violaciones SRP | 0 |
tsc --noEmit | 0 errores |
Scripts de Desarrollo
# Iniciar servidor de desarrollo
npm start
# Iniciar con target especifico
npm run ios # Simulador iOS
npm run android # Emulador Android
# Tests
npm run test # Jest watch mode
npm run test:run # Jest single run
npm run test:cov # Jest con reporte de coverage
# Lint y formateo
npm run lint # ESLint
npm run format # Prettier
Referencias
- Codigo fuente:
nvito-client/ - API Client:
src/api/client.ts - Services:
src/api/services/(6 archivos) - Query Hooks:
src/hooks/queries/(7 archivos) - Query Keys:
src/hooks/queries/keys.ts - AuthContext:
src/contexts/AuthContext.tsx+src/contexts/auth/ - ConnectivityContext:
src/contexts/ConnectivityContext.tsx - Validations:
src/validations/(3 schemas) - Theme:
src/theme/colors.ts - Storage:
src/storage/(secure-store, offline-queue) - Notifications:
src/hooks/use-notifications.ts - Shared Components:
src/components/itinerary/(timeline-item, location-card) - Types:
src/types/(7 archivos) - Tests:
test/setup.ts+**/__tests__/(28 suites)