Docs

App Móvil

Aplicación React Native + Expo para iOS y Android que permite a anfitriones e invitados interactuar con eventos en tiempo real.

v1.2PublicadoMarzo 2026Equipo de desarrollo, arquitectos, stakeholders

Estructura del Proyecto

nvito-client es una aplicación móvil 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 día del evento.

Stack Tecnológico

TecnologíaVersiónPropósito
Expo54.xPlataforma de desarrollo React Native
React Native0.81.xFramework de UI nativa
TypeScript5.9Tipado estático
Expo Router6.xNavegación 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.xCámara y escaneo QR
expo-av16.xGrabación y reproducción de audio
expo-notifications0.32.xPush notifications (Expo Push API)
expo-secure-store15.xAlmacenamiento seguro de tokens
expo-image-picker17.xSelección y captura de fotos
expo-location19.xGeolocalización
expo-haptics15.xFeedback háptico nativo
date-fns4.xFormateo de fechas
Zod4.xValidación de esquemas
react-native-reanimated4.xAnimaciones de alto rendimiento
react-native-gesture-handler2.xGestos nativos
@expo/vector-icons15.xIconografía (Ionicons)

Configuración de Plataforma

AspectoiOSAndroid
Identificadorcom.nvito.clientcom.nvito.client
Soporte tabletSí (iPad)
Deep linksAssociated Domains (applinks:nvito.mx)Intent Filters (nvito.mx/join)
PermisosCámara, Micrófono, Fotos, UbicaciónCAMERA, RECORD_AUDIO, LOCATION
ArquitecturaNew Architecture habilitadaNew Architecture habilitada

Estructura de Directorios

Estructura de Directorios

nvito-client/
├── app/Expo Router (file-based routing)
├── _layout.tsxRoot layout (QueryClient + AuthProvider)
├── index.tsxRedirect segun auth state
├── +not-found.tsxPantalla 404
├── (auth)/Grupo de autenticacion
├── _layout.tsxStack navigation
├── login.tsxLogin anfitrion: codigo + PIN
└── join.tsxAcceso invitado: deep link / QR
└── (app)/Grupo principal (auth guard)
├── _layout.tsxGuard + push token registration
├── (host)/Screens del anfitrion
├── _layout.tsxBottom Tab Navigation (5 tabs)
├── dashboard.tsxOrquestador (~120 LOC)
├── guests.tsxOrquestador (~150 LOC)
├── scanner.tsxOrquestador (~80 LOC)
├── tables.tsxMesas (vista lista + plano)
├── more.tsxMenu: memorias, itinerario, regalos, logout
├── memorias.tsxOrquestador: Fotos | Audios | Mensajes
├── itinerary.tsxItinerario HOST (shared components)
├── gift-registry.tsxMesa de regalos read-only
├── gallery.tsxGaleria de fotos (hidden tab)
├── audio-guestbook.tsxOrquestador (~117 LOC)
└── messages.tsxMensajes de texto (hidden tab)
└── (guest)/Screens del invitado
├── _layout.tsxBottom Tab Navigation (4 tabs)
├── home.tsxQR pass + countdown + info
├── itinerary.tsxPrograma + ubicaciones
├── gallery.tsxOrquestador (~120 LOC)
└── interact.tsxAudio, mensajes, regalos
├── src/
├── api/Capa de comunicacion HTTP
├── client.tsFetch wrapper con interceptores
├── errors.tsApiError class
├── __tests__/Tests de API client
└── errors.test.ts
└── services/Servicios por dominio (7 archivos)
├── auth.service.tsLogin, refresh, push token
├── events.service.tsDatos del evento, stats
├── guests.service.tsInvitados, check-in, QR
├── media.service.tsGaleria, audio, mensajes
├── payments.service.tsCash fund (Stripe)
├── sharing.service.tsCompartir evento
└── __tests__/Tests de servicios (6 archivos)
├── hooks/Custom hooks
├── queries/React Query hooks (7 archivos)
├── keys.tsQuery Key Factory
├── use-event.tsDatos del evento
├── use-event-stats.tsEstadisticas en vivo
├── use-guests.tsLista de invitados
├── use-media.tsGaleria, audio, mensajes
├── use-payments.tsPagos cash fund
├── use-sharing.tsCompartir evento
└── __tests__/Tests de hooks (6 archivos)
├── use-notifications.tsPush notifications
└── use-responsive.tsResponsive / tablet detection
├── contexts/
├── AuthContext.tsxProvider limpio (~95 LOC)
├── ConnectivityContext.tsxMonitor de conectividad
├── auth/Descomposicion SOLID del auth
├── auth-reducer.tsAuthState, AuthAction, reducer
├── auth-storage.tsPersistencia en SecureStore
└── use-session-restore.tsRestauracion + auto-refresh
└── __tests__/Tests de contextos (4 archivos)
├── storage/
├── secure-store.tsWrapper de expo-secure-store
├── offline-queue.tsCola offline (AsyncStorage)
└── __tests__/Tests de storage (2 archivos)
├── validations/Zod schemas para API responses
├── auth.tsloginResponseSchema, refreshResponseSchema
├── event.tseventDetailSchema, eventStatsSchema
├── guest.tscheckInResultSchema, guestQrPassSchema
└── __tests__/Tests de validacion (3 archivos, 39 tests)
├── theme/Design system centralizado
├── colors.tsFuente unica de verdad de colores
└── index.tsBarrel export
├── types/TypeScript types
├── api.types.tsTipos genericos de API
├── auth.types.tsLoginResponse, MobileRole
├── event.types.tsEvent, EventStats
├── guest.types.tsGuest, CheckInResult
├── media.types.tsGalleryPhoto, AudioMessage
└── payments.types.tsCashFundIntentResponse
├── utils/
├── constants.tsConstantes (timeouts, tamanos)
├── date.tsFormateo de fechas
├── format.tsFormateo general
└── __tests__/Tests de utils (2 archivos)
├── config/
├── env.tsVariables de entorno
└── query-persister.tsPersistencia de React Query
└── components/
├── ShareCardPreview.tsxPreview de card para compartir
└── itinerary/Componentes compartidos HOST/GUEST
├── timeline-item.tsxItem de timeline con helpers
└── location-card.tsxCard de ubicacion con link a mapas
├── test/
└── setup.tsMocks globales (10 modulos nativos)
├── assets/Iconos, splash, fuentes
├── jest.config.jsConfiguracion Jest + jest-expo
├── app.jsonConfiguracion Expo
├── tailwind.config.jsNativeWind + colores de theme/colors
├── babel.config.jsBabel preset
├── metro.config.jsMetro bundler
└── tsconfig.jsonTypeScript (strict mode)

La app usa Expo Router v6 con navegación file-based. La estructura de navegación se adapta según el rol del usuario autenticado (HOST o GUEST).

Diagrama de Navegación

Descripción de Rutas

RutaRolDescripción
/Redirect a login o app según estado de sesión
/(auth)/loginLogin anfitrión con código de evento + PIN
/(auth)/joinAcceso invitado via deep link o código QR
/(app)/(host)/dashboardHOSTDashboard con métricas en tiempo real
/(app)/(host)/guestsHOSTLista de invitados con búsqueda y filtros
/(app)/(host)/scannerHOSTQR Scanner para check-in de invitados
/(app)/(host)/tablesHOSTMesas: vista lista + plano visual read-only
/(app)/(host)/moreHOSTMenú 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)/galleryHOSTGalería 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 rápida
/(app)/(guest)/itineraryGUESTItinerario del evento + ubicaciones
/(app)/(guest)/galleryGUESTGalería colaborativa + subir fotos
/(app)/(guest)/interactGUESTAudio guestbook, mensajes, mesa de regalos

Tabs del Anfitrión

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

Tabs del Invitado

TabIconoPantalla
Iniciohome-outlineQR pass + countdown
Programacalendar-outlineItinerario + ubicaciones
Galeríacamera-outlineVer + subir fotos
Interactuarmic-outlineAudio, mensajes, regalos

Autenticación Móvil

La app usa un sistema de autenticación 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 Autenticación del Anfitrión

Flujo de Autenticación del Invitado

Almacenamiento de Sesión

DatoStoragePropósito
accessToken (JWT)expo-secure-storeAutenticación de requests
refreshTokenexpo-secure-storeRenovación del JWT
Session data (rol, eventId)expo-secure-storeRestauración de sesión

Auto-refresh de Tokens

El AuthContext programa automáticamente la renovación 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 rotación 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 Anfitrión (HOST)

Dashboard

Pantalla principal del anfitrión con estadísticas del evento en tiempo real.

SecciónDescripción
HeaderNombre del evento + fecha
CountdownDías, horas, minutos hasta el evento
Stats CardsTotal invitados, confirmados, check-ins, fotos
RSVP RingGráfico circular de confirmaciones
Actividad recienteFeed de últimos 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 búsqueda y filtros.

FuncionalidadDescripción
BúsquedaPor nombre del invitado
FiltrosPor status: todos, confirmados, pendientes, declinados
Guest CardNombre, status badge, grupo, # acompañantes. 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:

SecciónContenido
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 dietéticas
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.

CaracterísticaDetalle
Scannerexpo-camera CameraView con barcode scanning
Tipos QR['qr']
Check-in parcialPara grupos, permite indicar cuántos asisten
Estadísticas en vivoHeader con checked-in / total
PollingCada 15 segundos para stats actualizados
Feedbackexpo-haptics (success, warning, error)
Check-in manualBúsqueda por nombre para check-in sin QR (ver abajo)
HistorialLista de últimos check-ins debajo del scanner

Check-in Manual por Nombre

Botón de búsqueda en el header del Scanner que abre un modal slide-up para registrar invitados sin necesidad de escanear QR:

FuncionalidadDescripción
BúsquedaInput de texto, se activa al escribir 2+ caracteres
ResultadosLista de invitados con avatar, nombre, grupo, status y badge check-in
SelecciónAl tocar un invitado, se muestra el selector de asistentes (si groupSize > 1)
ConfirmaciónBotón para confirmar el check-in manual con la cantidad de asistentes
ResultadoReutiliza el CheckInResultModal existente con feedback háptico

Componentes: scanner/manual-check-in-sheet.tsx, scanner/hooks/use-manual-search.ts.

Mesas (HOST)

Pantalla read-only para visualizar la distribución y asignación de mesas del evento. La gestión (crear, editar, asignar) se realiza exclusivamente desde el admin web.

FuncionalidadDescripción
Toggle vistaSelector segmentado Lista / Plano
Stats summaryBarra con total mesas, capacidad, ocupados, disponibles, %
Vista ListaFlatList con TableCard (forma visual, barra de ocupación, 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 ocupaciónVerde (menos de 70%), Amarillo (70-90%), Rojo (más de 90%)
Formas visualesROUND (círculo), SQUARE, RECTANGLE, OVAL — con borde y fondo por ocupación
Pull-to-refreshEn vista lista
Empty state"No hay mesas configuradas. Configura las mesas desde el panel de administración"

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 Galería, Audio Guestbook y Mensajes en una sola pantalla con segmented control e infinite scroll. Accesible desde el menú "Más", reemplaza los 3 items separados.

FuncionalidadDescripción
Segmented control3 segmentos: Fotos, Audios, Mensajes — con badges de pendientes
HeaderBack button + título "Memorias"
Infinite scrollCada segmento usa useInfiniteQuery con carga progresiva (20 items por página)

Segmento Fotos

FuncionalidadDescripción
Grid3 columnas (móvil) / 5 columnas (tablet)
ThumbnailFoto con badge del nombre del invitado
Viewer modalFullscreen con caption, autor, fecha relativa
Infinite scrolluseInfiniteGallery() — carga más fotos al llegar al final de la lista

Segmento Audios

FuncionalidadDescripción
FiltrosTodos / Pendientes / Aprobados
ReproductorPlay/pause inline con expo-av
ModeraciónAprobar o rechazar mensajes pendientes (flujo completo)
BadgeContador de audios pendientes en segmented control
Infinite scrolluseInfiniteAudioMessages() — carga progresiva de audios

Segmento Mensajes

FuncionalidadDescripción
FiltrosTodos / No leídos
IndicadorPunto violeta + fondo destacado para no leídos
Marcar como leídoEl host puede marcar mensajes individuales como leídos
AvatarInicial del nombre del invitado
BadgeContador de no leídos en segmented control
Infinite scrolluseInfiniteMessages() — carga progresiva de mensajes

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 idéntico al del invitado, accesible desde el menú "Más". Reutiliza los mismos componentes compartidos (src/components/itinerary/).

FuncionalidadDescripción
HeaderBack button + título "Itinerario"
TimelineLínea 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 aún no está 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 ubicación).

Mesa de Regalos (HOST)

Vista read-only del estado de la mesa de regalos del evento. Accesible desde el menú "Más". No incluye widget de Stripe ni botón de contribución (el HOST no dona a su propio fondo).

FuncionalidadDescripción
HeaderBack button + título "Mesa de Regalos"
Fondo en efectivoBarra de progreso, monto recaudado/meta (centavos→pesos MXN), porcentaje, mensaje
Tiendas/registrosCards con nombre de tienda, descripción, link externo
Cuentas bancariasNombre del banco + descripción
NotaTexto adicional del anfitrión
Empty state"No hay mesa de regalos configurada"
ConversiónValores 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.

SecciónDescripción
BienvenidaSaludo personalizado con nombre del invitado
QR CodeCódigo QR grande para check-in
CountdownDías, horas, minutos hasta el evento
Info rápidaHora, mesa asignada, grupo
Nombre del eventoTítulo prominente + fecha

Itinerario

Programa del evento con timeline visual. Las LocationCards incluyen información enriquecida.

FuncionalidadDescripción
TimelineLínea vertical con items ordenados por hora
Item activoDestacado basado en la hora actual
UbicacionesLinks a mapas para cada item
Código de vestimentaDestacado visual llamativo por ubicación
EstacionamientoIndicador de disponibilidad e instrucciones
AccesibilidadInformación de accesibilidad por ubicación
InfoHora, título, descripción por item

Interactuar

Pantalla con secciones condicionales (service gating) para que el invitado interactúe con el evento. Cada tipo de contenido tiene un límite por invitado.

SecciónServicio requeridoLímite por invitadoDescripción
Mis fotosGALLERY / GUEST_UPLOADS3 fotosSolo muestra fotos subidas por el invitado con contador de restantes
Audio GuestbookAUDIO_GUESTBOOK3 audiosGrabar mensaje de voz + ver mis audios con badge de estado (pendiente/aprobado/rechazado)
MensajeGUEST_MESSAGES3 mensajesInput de texto para dejar mensaje a anfitriones con contador de restantes
Empty stateCuando ninguna sección está habilitada

Los límites se consultan mediante endpoints dedicados (/my-count) que retornan { count, limit }. Cuando el invitado alcanza el límite, el botón de acción se deshabilita con un mensaje informativo.

Hospedaje

Pantalla accesible desde "Mas" con toda la información de hospedaje del evento.

SecciónDescripción
Info de viajeAeropuerto más cercano, estacionamiento, tips
HotelesCards con foto, dirección, teléfono, distancia, botones "Ver sitio" y "Reservar"
TransporteOpciones de transporte con iconos por tipo

Más

Menú con opciones condicionales según servicios habilitados.

OpciónServicio requeridoDescripción
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 según los servicios contratados para el evento. El sistema usa dos mecanismos complementarios:

  • useEventServices(): Hook que obtiene la lista de servicios habilitados del evento. Si los servicios aún no se han cargado, se muestran todas las secciones como fallback.
  • useRequireService(serviceNames): Guard hook que verifica si al menos uno de los servicios requeridos está habilitado. Retorna { allowed, isLoading } para proteger pantallas accesibles via deep link o navegación directa.

Las vistas completas (Galería, Audio Guestbook, Mensajes, Mesa de Regalos, Hospedaje) solo se renderizan si el servicio correspondiente está contratado en el evento.

Arquitectura del API Client

El sistema de comunicación con el backend sigue una arquitectura en capas, similar a nvito-admin pero adaptada para autenticación móvil.

API Client (client.ts)

Fetch wrapper que encapsula todas las operaciones HTTP:

  • Métodos: 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 automáticamente
  • Auto-refresh: Si recibe 401, intenta renovar el JWT con el refresh token
  • Error handling: Clase ApiError con statusCode y message

Services (7 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.tsGalería (paginada), upload URLs, audio guestbook (paginado, moderación), mensajes (paginados, leídos), conteos por invitado, gift registry
Paymentspayments.service.tsCash fund intent (Stripe), confirmación de pago
Sharingsharing.service.tsCompartir evento, generar card de preview

Prefijo de Endpoints Móviles

Todos los endpoints consumidos por la app móvil usan el prefijo /v1/mobile/ y están protegidos por el MobileAuthGuard (independiente de Clerk). Los endpoints son wrappers ligeros que reutilizan los servicios existentes del backend.

Endpoints de Media Móvil

MétodoPathRolDescripción
GET/mobile/events/:id/galleryHOST/GUESTGalería paginada (server-side)
GET/mobile/events/:id/gallery/my-countGUESTConteo de fotos + límite del invitado
POST/mobile/events/:id/gallery/upload-urlHOST/GUESTURL pre-firmada para subir foto
POST/mobile/events/:id/galleryHOST/GUESTRegistrar foto subida
GET/mobile/events/:id/audio-guestbookHOST/GUESTAudios paginados (server-side)
GET/mobile/events/:id/audio-guestbook/my-countGUESTConteo de audios + límite del invitado
POST/mobile/events/:id/audio-guestbook/upload-urlHOST/GUESTURL pre-firmada para subir audio
POST/mobile/events/:id/audio-guestbookHOST/GUESTRegistrar audio subido
PATCH/mobile/events/:id/audio-guestbook/:audioId/approveHOSTAprobar audio
DELETE/mobile/events/:id/audio-guestbook/:audioIdHOSTRechazar y eliminar audio
GET/mobile/events/:id/messagesHOSTMensajes paginados (server-side)
GET/mobile/events/:id/messages/mineGUESTMensajes del invitado actual
GET/mobile/events/:id/messages/my-countGUESTConteo de mensajes + límite del invitado
POST/mobile/events/:id/messagesGUESTEnviar mensaje de felicitación
PATCH/mobile/events/:id/messages/:messageId/readHOSTMarcar mensaje como leído

Gestión de Estado

React Query 5

La app usa TanStack React Query como único sistema de gestión 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.myPhotoCount(eventId)     // ['media', eventId, 'my-photo-count']
queryKeys.media.audioMessages(eventId)    // ['media', eventId, 'audio-messages']
queryKeys.media.myAudioCount(eventId)     // ['media', eventId, 'my-audio-count']
queryKeys.media.messages(eventId)         // ['media', eventId, 'messages']
queryKeys.media.myMessages(eventId)       // ['media', eventId, 'my-messages']
queryKeys.media.myMessageCount(eventId)   // ['media', eventId, 'my-message-count']
queryKeys.media.giftRegistry(eventId)     // ['media', eventId, 'gift-registry']

Hooks de React Query

HookTipoDescripción
useEventQueryDatos del evento actual
useEventStatsQueryEstadísticas con polling
useGuestsQueryLista de invitados con filtros
useGalleryQueryFotos paginadas (server-side)
useInfiniteGalleryInfiniteQueryFotos con infinite scroll (Host Memorias)
useMyPhotoCountQueryConteo y límite de fotos del invitado
useAudioMessagesQueryMensajes de audio (primera página)
useInfiniteAudioMessagesInfiniteQueryAudios con infinite scroll (Host Memorias)
useMyAudioCountQueryConteo y límite de audios del invitado
useMessagesQueryMensajes de texto (primera página)
useInfiniteMessagesInfiniteQueryMensajes con infinite scroll (Host Memorias)
useMyMessagesQueryMensajes enviados por el invitado actual
useMyMessageCountQueryConteo y límite de mensajes del invitado
useGuestDetailQueryDetalle completo de un invitado
useGuestStatsQueryEstadísticas de invitados
useGiftRegistryQueryMesa de regalos del evento
useCheckInMutationRegistrar check-in
useManualCheckInMutationCheck-in manual por nombre (sin QR)
useUploadPhotoMutationSubir foto con presigned URL
useUploadAudioMutationSubir audio con presigned URL
useApproveAudioMutationAprobar mensaje de audio
useRejectAudioMutationRechazar y eliminar mensaje de audio
useMarkMessageAsReadMutationMarcar mensaje de texto como leído
useSendMessageMutationEnviar mensaje de texto

AuthContext (Descomposición SOLID)

El contexto de autenticación fue descompuesto siguiendo SRP en 4 archivos con responsabilidad única:

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~68Restauración de sesión + callbacks API + auto-refresh

El contexto gestiona el estado de sesión con un useReducer:

PropiedadTipoDescripción
isAuthenticatedbooleanUsuario autenticado
isLoadingbooleanCargando sesión
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 renovación

Acciones del reducer:

  • RESTORE_SESSION — Restaurar sesión desde SecureStore al montar
  • LOGIN — Login exitoso (host o guest)
  • LOGOUT — Cerrar sesión 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 único 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 está ya registrado
  4. Si el invitado tiene groupSize > 1, se muestra un modal para seleccionar cuántos asisten

Check-in Parcial (Grupos)

Cuando un invitado tiene acompañantes, el scanner muestra:

  • Nombre del invitado y total del grupo
  • Stepper numérico para seleccionar asistentes
  • Rango: 1 hasta groupSize
  • Soporte para re-escaneo (agregar más personas después)

Media: Galería y Audio Guestbook

Upload de Fotos

Grabación de Audio

Límites de Media

TipoLímite
Imagen max width2048 px
Calidad de imagen0.8 (80%)
Duración max audio120 segundos (2 min)
Fotos por invitado3
Audios por invitado3
Mensajes por invitado3

Los límites por invitado se verifican tanto en el frontend (contadores visuales, botón deshabilitado) como en el backend (validación server-side que retorna 403 al exceder el límite).

Push Notifications

Registro de Push Token

Configuración 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} confirmó asistencia"
Check-inHOST"{nombre} llegó 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 mañana"

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

URIPropósito
nvito://join/{accessToken}Acceso directo invitado (custom scheme)
nvito://event/{eventCode}Acceso anfitrión (solicita PIN)
https://nvito.app/join/{token}Universal link (iOS Associated Domains)

Configuración

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 Única de Verdad: src/theme/colors.ts

Todos los colores de la aplicación están centralizados en un único archivo src/theme/colors.ts exportado como as const para type safety. El archivo tailwind.config.js importa estos colores directamente, eliminando la duplicación.

Antes: ~130 instancias de colores hex hardcodeados dispersos en 12+ archivos. Después: 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#3B82F6Información, 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

Integración 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">Descripción</Text>
</View>

Tipografía

TokenFuenteUso
font-sansInterTexto general
font-headingInter-BoldTítulos y encabezados

Responsive / Tablet

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

AspectoMóvilTablet
Grid de galería3 columnas5 columnas
Cards de statsStack verticalGrid 2x2
Lista de invitados1 columna2 columnas
Padding general16px24px

Iconografía

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

Variables de Entorno

VariableDescripciónDefault
EXPO_PUBLIC_API_URLURL base de nvito-apihttp://localhost:3000/v1
EXPO_PUBLIC_APP_NAMENombre de la aplicaciónNvito
EXPO_PUBLIC_ENVEntorno actualdevelopment

Arquitectura SOLID

El proyecto aplica Single Responsibility Principle (SRP) sistemáticamente. Las pantallas grandes fueron descompuestas en orquestadores livianos + sub-componentes + hooks custom.

Descomposición de Pantallas

PantallaAntes (LOC)Después (LOC)Sub-componentesHooks extraídos
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

Reducción total: 70% menos líneas en archivos orquestadores, distribuidas en componentes enfocados y reutilizables.

Descomposición 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 sesión al montar, configurar callbacks API, programar refresh

Validación con Zod

El proyecto incluye schemas Zod v4 para validar respuestas de API en runtime, complementando la seguridad de tipos de TypeScript en compilación.

Schemas Disponibles

ArchivoSchemasPropósito
validations/auth.tsloginResponseSchema, refreshResponseSchemaValidar respuestas de login y refresh
validations/event.tseventDetailSchema, eventStatsSchemaValidar datos del evento y estadísticas
validations/guest.tscheckInResultSchema, guestQrPassSchemaValidar resultado de check-in y QR pass

Cada schema tiene tests que verifican datos válidos, campos faltantes y tipos incorrectos.

Testing

El proyecto cuenta con 32 suites de prueba y 257 tests individuales, todos con 100% de tasa de éxito.

Framework

HerramientaVersiónPropósito
Jestvía jest-expoTest runner
jest-expoPresetConfiguración 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 módulos nativos que no están disponibles en Node.js:

MóduloMock
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 háptico no-op
expo-routeruseRouter, useLocalSearchParams, etc.

Cobertura por Area

ÁreaSuitesTestsQué 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
Total32257

Calidad del Código

IndicadorValor
TypeScript strictHabilitado
as any0
JSDoc100% en métodos públicos (~80 JSDoc comments)
Zod schemas6 schemas con tests
Test suites32
Tests individuales257
Tasa de éxito100%
Bugs corregidos3 (import bug, as any, hooks duplicados)
Colores hardcodeados0 (130+ migrados a theme)
Componentes >300 LOC0 (5 descompuestos via SOLID)
Design tokens centralizadosSí (theme/colors.tstailwind.config.js)
Violaciones SRP0
tsc --noEmit0 errores

Scripts de Desarrollo

# Iniciar servidor de desarrollo
npm start

# Iniciar con target específico
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

  • Código fuente: nvito-client/
  • API Client: src/api/client.ts
  • Services: src/api/services/ (7 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__/ (29 suites)
Esta pagina fue util?