Docs

App Movil (nvito-client)

Tabla de Contenidos

  1. Estructura del Proyecto
  2. Navegacion y Rutas
  3. Autenticacion Movil
  4. Pantallas del Anfitrion (HOST)
  5. Pantallas del Invitado (GUEST)
  6. Arquitectura del API Client
  7. Gestion de Estado
  8. QR Scanner y Check-in
  9. Media: Galeria y Audio Guestbook
  10. Push Notifications
  11. Deep Linking
  12. Design System
  13. Arquitectura SOLID
  14. Validacion con Zod
  15. Testing
  16. 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

TecnologiaVersionProposito
Expo54.xPlataforma de desarrollo React Native
React Native0.81.xFramework de UI nativa
TypeScript5.9Tipado estatico
Expo Router6.xNavegacion file-based con layouts anidados
NativeWind4.xTailwind CSS para React Native
Tailwind CSS3.4Estilos utilitarios
TanStack React Query5.xCache y fetching de datos
expo-camera17.xCamara y escaneo QR
expo-av16.xGrabacion y reproduccion de audio
expo-notifications0.32.xPush notifications (Expo Push API)
expo-secure-store15.xAlmacenamiento seguro de tokens
expo-image-picker17.xSeleccion y captura de fotos
expo-location19.xGeolocalizacion
expo-haptics15.xFeedback haptico nativo
date-fns4.xFormateo de fechas
Zod4.xValidacion de esquemas
react-native-reanimated4.xAnimaciones de alto rendimiento
react-native-gesture-handler2.xGestos nativos
@expo/vector-icons15.xIconografia (Ionicons)

Configuracion de Plataforma

AspectoiOSAndroid
Identificadorcom.nvito.clientcom.nvito.client
Soporte tabletSi (iPad)Si
Deep linksAssociated Domains (applinks:nvito.app)Intent Filters (nvito.app/join)
PermisosCamara, Microfono, Fotos, UbicacionCAMERA, RECORD_AUDIO, LOCATION
ArquitecturaNew Architecture habilitadaNew 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)

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

RutaRolDescripcion
/Redirect a login o app segun estado de sesion
/(auth)/loginLogin anfitrion con codigo de evento + PIN
/(auth)/joinAcceso invitado via deep link o codigo QR
/(app)/(host)/dashboardHOSTDashboard con metricas en tiempo real
/(app)/(host)/guestsHOSTLista de invitados con busqueda y filtros
/(app)/(host)/scannerHOSTQR Scanner para check-in de invitados
/(app)/(host)/tablesHOSTMesas: vista lista + plano visual read-only
/(app)/(host)/moreHOSTMenu con acceso a memorias, itinerario, regalos y logout
/(app)/(host)/memoriasHOSTVista unificada: Fotos, Audios y Mensajes con segmented control
/(app)/(host)/itineraryHOSTItinerario del evento (componentes compartidos con GUEST)
/(app)/(host)/gift-registryHOSTMesa de regalos read-only (fondo, tiendas, cuentas bancarias)
/(app)/(host)/galleryHOSTGaleria de fotos (hidden tab, retrocompatibilidad)
/(app)/(host)/audio-guestbookHOSTMensajes de audio (hidden tab, retrocompatibilidad)
/(app)/(host)/messagesHOSTMensajes de texto (hidden tab, retrocompatibilidad)
/(app)/(guest)/homeGUESTQR pass, countdown, info rapida
/(app)/(guest)/itineraryGUESTItinerario del evento + ubicaciones
/(app)/(guest)/galleryGUESTGaleria colaborativa + subir fotos
/(app)/(guest)/interactGUESTAudio guestbook, mensajes, mesa de regalos

Tabs del Anfitrion

TabIconoPantalla
Dashboardbar-chart-outlineEstadisticas en vivo del evento
Invitadospeople-outlineLista de invitados con estados
Scannerscan-outlineQR Scanner (tab central destacado)
Mesasgrid-outlineMesas con vista lista y plano visual
Masmenu-outlineMemorias, itinerario, mesa de regalos, compartir, logout

Tabs del Invitado

TabIconoPantalla
Iniciohome-outlineQR pass + countdown
Programacalendar-outlineItinerario + ubicaciones
Galeriacamera-outlineVer + subir fotos
Interactuarmic-outlineAudio, 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

DatoStorageProposito
accessToken (JWT)expo-secure-storeAutenticacion de requests
refreshTokenexpo-secure-storeRenovacion del JWT
Session data (rol, eventId)expo-secure-storeRestauracion de sesion

Auto-refresh de Tokens

El AuthContext programa automaticamente la renovacion del JWT:

  1. Al hacer login, se programa un timer basado en expiresIn del JWT
  2. El refresh se ejecuta cuando quedan < 2 minutos de vida
  3. Al volver del background, se programa un refresh conservador inmediato
  4. 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.

SeccionDescripcion
HeaderNombre del evento + fecha
CountdownDias, horas, minutos hasta el evento
Stats CardsTotal invitados, confirmados, check-ins, fotos
RSVP RingGrafico circular de confirmaciones
Actividad recienteFeed 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.

FuncionalidadDescripcion
BusquedaPor nombre del invitado
FiltrosPor status: todos, confirmados, pendientes, declinados
Guest CardNombre, status badge, grupo, # acompanantes. Pressable para abrir detalle
Stats HeaderContadores por status
Guest Detail SheetBottom 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:

SeccionContenido
HeaderAvatar (iniciales), nombre completo, status badge, indicador check-in
InfoGrupo (con color), mesa, asiento, asistentes (checkedIn/groupSize)
RSVPStatus, cantidad de asistentes, mensaje, fecha de respuesta
NecesidadesRestricciones dietarias (chips), alergias, necesidades especiales, notas dieteticas
NotasNotas generales del invitado
TagsEtiquetas 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.

CaracteristicaDetalle
Scannerexpo-camera CameraView con barcode scanning
Tipos QR['qr']
Check-in parcialPara grupos, permite indicar cuantos asisten
Estadisticas en vivoHeader con checked-in / total
PollingCada 15 segundos para stats actualizados
Feedbackexpo-haptics (success, warning, error)
Check-in manualBusqueda por nombre para check-in sin QR (ver abajo)
HistorialLista 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:

FuncionalidadDescripcion
BusquedaInput de texto, se activa al escribir 2+ caracteres
ResultadosLista de invitados con avatar, nombre, grupo, status y badge check-in
SeleccionAl tocar un invitado, se muestra el selector de asistentes (si groupSize > 1)
ConfirmacionBoton para confirmar el check-in manual con la cantidad de asistentes
ResultadoReutiliza 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.

FuncionalidadDescripcion
Toggle vistaSelector segmentado Lista / Plano
Stats summaryBarra con total mesas, capacidad, ocupados, disponibles, %
Vista ListaFlatList con TableCard (forma visual, barra de ocupacion, invitados). Layout responsive: 2 columnas en tablet
Vista PlanoScrollView con pinch-to-zoom (0.5x–3x). Mismas dimensiones y auto-layout que el admin (900x800, TABLE_SLOT=180px)
Detalle modalSheet slide-up con stats de mesa + lista de invitados asignados
Colores ocupacionVerde (menos de 70%), Amarillo (70-90%), Rojo (mas de 90%)
Formas visualesROUND (circulo), SQUARE, RECTANGLE, OVAL — con borde y fondo por ocupacion
Pull-to-refreshEn 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.

FuncionalidadDescripcion
Segmented control3 segmentos: Fotos, Audios, Mensajes — con badges de pendientes
HeaderBack button + titulo "Memorias"

Segmento Fotos

FuncionalidadDescripcion
Grid3 columnas (movil) / 5 columnas (tablet)
ThumbnailFoto con badge del nombre del invitado
Viewer modalFullscreen con caption, autor, fecha relativa
PaginacionControles prev/next para navegacion

Segmento Audios

FuncionalidadDescripcion
FiltrosTodos / Pendientes / Aprobados
ReproductorPlay/pause inline con expo-av
AprobacionBoton para aprobar mensajes pendientes
BadgeContador de audios pendientes en segmented control

Segmento Mensajes

FuncionalidadDescripcion
FiltrosTodos / No leidos
IndicadorPunto violeta + fondo destacado para no leidos
AvatarInicial del nombre del invitado
BadgeContador 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/).

FuncionalidadDescripcion
HeaderBack button + titulo "Itinerario"
TimelineLinea vertical con items ordenados por hora
Item activoDestacado con punto primary cuando es la hora actual
Items pasadosEstilo atenuado (opacity reducida)
UbicacionesCards 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).

FuncionalidadDescripcion
HeaderBack button + titulo "Mesa de Regalos"
Fondo en efectivoBarra de progreso, monto recaudado/meta (centavos→pesos MXN), porcentaje, mensaje
Tiendas/registrosCards con nombre de tienda, descripcion, link externo
Cuentas bancariasNombre del banco + descripcion
NotaTexto adicional del anfitrion
Empty state"No hay mesa de regalos configurada"
ConversionValores en centavos convertidos a pesos con Intl.NumberFormat('es-MX')
Colores progresoVerde (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.

SeccionDescripcion
BienvenidaSaludo personalizado con nombre del invitado
QR CodeCodigo QR grande para check-in
CountdownDias, horas, minutos hasta el evento
Info rapidaHora, mesa asignada, grupo
Nombre del eventoTitulo prominente + fecha

Itinerario

Programa del evento con timeline visual. Las LocationCards incluyen informacion enriquecida.

FuncionalidadDescripcion
TimelineLinea vertical con items ordenados por hora
Item activoDestacado basado en la hora actual
UbicacionesLinks a mapas para cada item
Codigo de vestimentaDestacado visual llamativo por ubicacion
EstacionamientoIndicador de disponibilidad e instrucciones
AccesibilidadInformacion de accesibilidad por ubicacion
InfoHora, titulo, descripcion por item

Interactuar

Pantalla con secciones condicionales (service gating) para que el invitado interactue con el evento.

SeccionServicio requeridoDescripcion
Mis fotosGALLERY / GUEST_UPLOADSSolo muestra fotos subidas por el invitado
Audio GuestbookAUDIO_GUESTBOOKGrabar mensaje de voz + ver mis audios con badge de estado
MensajeGUEST_MESSAGESInput de texto para dejar mensaje a anfitriones
Empty stateCuando ninguna seccion esta habilitada

Hospedaje

Pantalla accesible desde "Mas" con toda la informacion de hospedaje del evento.

SeccionDescripcion
Info de viajeAeropuerto mas cercano, estacionamiento, tips
HotelesCards con foto, direccion, telefono, distancia, botones "Ver sitio" y "Reservar"
TransporteOpciones de transporte con iconos por tipo

Mas

Menu con opciones condicionales segun servicios habilitados.

OpcionServicio requeridoDescripcion
Mesa de regalosGIFT_REGISTRYLinks a tiendas + fondo en efectivo
HospedajeACCOMMODATIONHoteles, transporte, tips de viaje
Compartir eventoSiempre 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 token desde SecureStore automaticamente
  • Auto-refresh: Si recibe 401, intenta renovar el JWT con el refresh token
  • Error handling: Clase ApiError con statusCode y message

Services (6 archivos)

ServicioArchivoEndpoints
Authauth.service.tsLogin, guest access, refresh, logout, push token
Eventsevents.service.tsDatos del evento, stats, servicios, itinerario, ubicaciones
Guestsguests.service.tsLista de invitados, check-in, QR pass, RSVP stats
Mediamedia.service.tsGaleria, upload URLs, audio guestbook, mensajes, gift registry
Paymentspayments.service.tsCash fund intent (Stripe), confirmacion de pago
Sharingsharing.service.tsCompartir 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

HookTipoDescripcion
useEventQueryDatos del evento actual
useEventStatsQueryEstadisticas con polling
useGuestsQueryLista de invitados con filtros
useGalleryQueryFotos paginadas
useAudioMessagesQueryMensajes de audio
useMessagesQueryMensajes de texto
useGuestDetailQueryDetalle completo de un invitado
useGuestStatsQueryEstadisticas de invitados
useCheckInMutationRegistrar check-in
useManualCheckInMutationCheck-in manual por nombre (sin QR)
useUploadPhotoMutationSubir foto con presigned URL
useUploadAudioMutationSubir audio con presigned URL
useApproveAudioMutationAprobar mensaje de audio
useSendMessageMutationEnviar mensaje de texto

AuthContext (Descomposicion SOLID)

El contexto de autenticacion fue descompuesto siguiendo SRP en 4 archivos con responsabilidad unica:

ArchivoLOCResponsabilidad
AuthContext.tsx~95Provider orquestador (login, logout, render)
auth/auth-reducer.ts~54Estado, acciones y reducer puro
auth/auth-storage.ts~86Persistencia tipada en SecureStore
auth/use-session-restore.ts~68Restauracion de sesion + callbacks API + auto-refresh

El contexto gestiona el estado de sesion con un useReducer:

PropiedadTipoDescripcion
isAuthenticatedbooleanUsuario autenticado
isLoadingbooleanCargando sesion
role'HOST' | 'GUEST' | nullRol del usuario
eventIdstring | nullID del evento activo
guestIdstring | nullID del invitado (GUEST)
userIdstring | nullID del usuario (HOST)
accessTokenstring | nullJWT actual
refreshTokenstring | nullToken de renovacion

Acciones del reducer:

  • RESTORE_SESSION — Restaurar sesion desde SecureStore al montar
  • LOGIN — Login exitoso (host o guest)
  • LOGOUT — Cerrar sesion y limpiar tokens
  • TOKENS_REFRESHED — Actualizar tokens tras refresh
  • SET_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:

  1. La app extrae el guestId del contenido del QR
  2. Envia POST /v1/mobile/events/:id/check-in con el guestId
  3. El API verifica que el invitado pertenece al evento y no esta ya registrado
  4. 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

TipoLimite
Imagen max width2048 px
Calidad de imagen0.8 (80%)
Duracion max audio120 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

EventoDestinatarioMensaje
Nuevo RSVPHOST"{nombre} confirmo asistencia"
Check-inHOST"{nombre} llego al evento"
Foto subidaHOST"Nueva foto de {nombre}"
Audio subidoHOST"Nuevo mensaje de voz de {nombre}"
Cambio en eventoGUEST"El evento fue actualizado"
RecordatorioGUEST"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

URIProposito
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

TokenColorHexUso
primaryVioleta#7C3AEDAcciones principales, tabs activos, badges
secondaryRosa#EC4899Acentos, elementos decorativos
successVerde#10B981Check-in exitoso, confirmados
warningAmbar#F59E0BPendientes, alertas suaves
errorRojo#EF4444Errores, declinados, logout
infoAzul#3B82F6Informacion, links
backgroundBlanco#FFFFFFFondo principal
surfaceGris claro#F9FAFBFondo de cards y secciones
text-primaryGris oscuro#111827Texto principal
text-secondaryGris medio#6B7280Texto secundario
text-mutedGris claro#9CA3AFTexto 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

TokenFuenteUso
font-sansInterTexto general
font-headingInter-BoldTitulos y encabezados

Responsive / Tablet

La app detecta si se ejecuta en tablet mediante el hook useResponsive() y adapta los layouts:

AspectoMovilTablet
Grid de galeria3 columnas5 columnas
Cards de statsStack verticalGrid 2x2
Lista de invitados1 columna2 columnas
Padding general16px24px

Iconografia

Se usa Ionicons via @expo/vector-icons con estilo outline consistente en toda la app.

Variables de Entorno

VariableDescripcionDefault
EXPO_PUBLIC_API_URLURL base de nvito-apihttp://localhost:3000/v1
EXPO_PUBLIC_APP_NAMENombre de la aplicacionNvito
EXPO_PUBLIC_ENVEntorno actualdevelopment

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

PantallaAntes (LOC)Despues (LOC)Sub-componentesHooks extraidos
scanner.tsx737~805 (modals, stats, list, permissions)use-scanner.ts
dashboard.tsx326~1204 (stat-card, countdown, rsvp, activity)
guests.tsx331~1503 (guest-card, stats-bar, filters)
gallery.tsx (guest)303~1202 (thumbnail, viewer)use-photo-upload.ts
audio-guestbook.tsx266~1172 (audio-item, filter-tabs)use-audio-player.ts
Total1,963~587163

Reduccion total: 70% menos lineas en archivos orquestadores, distribuidas en componentes enfocados y reutilizables.

Descomposicion del AuthContext

ArchivoLOCResponsabilidad
AuthContext.tsx~95Provider: login/logout callbacks, render children
auth/auth-reducer.ts~54Reducer puro: estado, acciones, transiciones
auth/auth-storage.ts~86Persistencia: save/restore/clear en SecureStore tipado
auth/use-session-restore.ts~68Efecto: 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

ArchivoSchemasProposito
validations/auth.tsloginResponseSchema, refreshResponseSchemaValidar respuestas de login y refresh
validations/event.tseventDetailSchema, eventStatsSchemaValidar datos del evento y estadisticas
validations/guest.tscheckInResultSchema, guestQrPassSchemaValidar 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

HerramientaVersionProposito
Jestvia jest-expoTest runner
jest-expoPresetConfiguracion para Expo/React Native
@testing-library/react-native13.xRenders de hooks y componentes
@testing-library/jest-nativeMatchersMatchers nativos (toBeOnTheScreen, etc.)

Mocks Globales

El archivo test/setup.ts configura mocks de 10 modulos nativos que no estan disponibles en Node.js:

ModuloMock
expo-secure-storeIn-memory store con get/set/delete
expo-cryptoUUID fijo + hash mock
expo-cameraCameraView + useCameraPermissions
@react-native-async-storage/async-storageIn-memory store completo
@react-native-community/netinfoConectividad siempre true
expo-constantsConfig con API_URL local
expo-notificationsPermisos + push token mock
expo-hapticsFeedback haptico no-op
expo-routeruseRouter, useLocalSearchParams, etc.

Cobertura por Area

AreaSuitesTestsQue prueban
Utils217date.ts (formatEventDate, formatRelative, getCountdown), format.ts
API errors112ApiError constructor, isUnauthorized, isForbidden, isNotFound, isServerError
Servicios API638auth, events, guests, media, payments, sharing
Hooks queries626use-event, use-event-stats, use-guests, use-media, use-payments, use-sharing
Storage221secure-store (7 funciones), offline-queue (6 operaciones)
Contexts433AuthContext (login/logout/restore), ConnectivityContext, auth-reducer, auth-storage
Validaciones Zod339auth, event, guest schemas (datos validos + invalidos)
Theme1Colors structure
Total28216

Calidad del Codigo

IndicadorValor
TypeScript strictHabilitado
as any0
JSDoc100% en metodos publicos (~80 JSDoc comments)
Zod schemas6 schemas con tests
Test suites28
Tests individuales216
Tasa de exito100%
Bugs corregidos3 (import bug, as any, hooks duplicados)
Colores hardcodeados0 (130+ migrados a theme)
Componentes >300 LOC0 (5 descompuestos via SOLID)
Design tokens centralizadosSi (theme/colors.tstailwind.config.js)
Violaciones SRP0
tsc --noEmit0 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)
Esta pagina fue util?