Campo Valor Versión 1.0 Stack Next.js 16.x, React 19, TypeScript 5, Tailwind v4 Puerto local 3002 Dominio app.nvito.mxEstado En desarrollo
La PWA resuelve una brecha critica en la distribución: los invitados y anfitriones no siempre quieren o pueden instalar la app nativa. La PWA ofrece la misma funcionalidad accesible desde cualquier navegador moderno via URL.
Coexistencia con la app nativa : La PWA no reemplaza a nvito-client. Ambas apps comparten los mismos 33 endpoints moviles (/v1/mobile/*) del backend.
La PWA implementa un patrón BFF donde los JWT tokens nunca llegan al JavaScript del browser:
Browser → /api/proxy/* (BFF) → Authorization: Bearer <jwt> → nvito-api
Usuario ingresa credenciales en el formulario de login
Browser envía POST a /api/auth/login (BFF route handler)
BFF valida con Zod, aplica rate limiting, forward a nvito-api /v1/mobile/auth/login
BFF recibe JWT tokens de la respuesta
BFF encripta tokens con AES-256-GCM y los almacena en cookies HttpOnly
BFF retorna al browser solo metadata de sesion (role, eventId) — SIN tokens
Browser → GET /api/proxy/events/:id (con cookie HttpOnly automática)
BFF valida CSRF (si es mutacion), desencripta JWT de la cookie
Si JWT expira en < 2 minutos → refresh proactivo transparente
Forward a nvito-api /v1/mobile/events/:id con Authorization: Bearer <jwt>
Si 401 → refresh → retry una vez → si falla → clear cookies → 401 al browser
Cookie HttpOnly Secure SameSite Path Max-Age __Host-nvito-atSi Si Strict / 900 (15min) __Host-nvito-rtSi Si Strict /api/auth 2,592,000 (30d) __Host-nvito-csrfNo Si Strict / 2,592,000 (30d) __Host-nvito-csrf-sigSi Si Strict / 2,592,000 (30d)
Prefijo __Host- : Previene ataques de subdomain cookie injection
AES-256-GCM : Encriptacion de tokens con IV aleatorio de 12 bytes
CSRF doble submit : Token en cookie no-HttpOnly + firma en cookie HttpOnly, verificacion timing-safe
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
connect-src 'self'; ← Bloquea llamadas directas a nvito-api
frame-src 'none';
frame-ancestors 'none';
Login: 5 requests / IP / 15 minutos
Implementación in-memory con limpieza periodica
Rutas bajo (app)/* requieren cookie __Host-nvito-at
Rutas públicas: /login, /join/*, /api/auth/*, assets estáticos
Redireccion automática a /login si no hay sesion
Estructura de Directorios ├── layout.tsx Root: Providers wrapper ├── providers.tsx QueryClient + SessionProvider ├── layout.tsx Auth layout con branding ├── login/page.tsx Login HOST (eventCode + PIN) └── join/[token]/page.tsx Login GUEST (accessToken URL) ├── layout.tsx App shell con auth check ├── auth/ 5 route handlers └── ... Login, guest-access, refresh, logout, session ├── proxy/[...path]/route.ts Catch-all proxy a nvito-api └── push/subscribe/route.ts Web Push registration ├── security/ AES-256-GCM, CSRF, rate limiter ├── bff/ Cookie manager, proxy handler ├── api/ Client, errors, services, types ├── hooks/queries/ React Query hooks + keys ├── validations/ Schemas Zod ├── contexts/ Session, connectivity ├── offline/ IndexedDB, offline queue └── utils/ Constants, date, format ├── hooks/ use-online-status, use-install-prompt, etc. ├── components/shared/ Bottom nav, offline banner, etc. ├── middleware.ts Auth guard └── theme/colors.ts Design tokens Nvito
Pantalla Tab Ruta Dashboard Tab 0 /(host)/dashboardInvitados Tab 1 /(host)/guestsScanner QR Tab 2 /(host)/scannerMesas Tab 3 /(host)/tablesMas Tab 4 /(host)/moreMemorias Hidden /(host)/memoriasItinerario Hidden /(host)/itineraryMesa de Regalos Hidden /(host)/gift-registryGalería Hidden /(host)/galleryAudio Guestbook Hidden /(host)/audio-guestbookMensajes Hidden /(host)/messagesCompartir Hidden /(host)/sharing
Pantalla Tab Ruta Inicio Tab 0 /(guest)/homePrograma Tab 1 /(guest)/itinerary-guestInteractuar Tab 2 /(guest)/interactMas Tab 3 /(guest)/more-guestGalería Hidden /(guest)/gallery-guestCompartir Hidden /(guest)/sharing-guestMesa de Regalos Hidden /(guest)/gift-registry-guestHospedaje Hidden /(guest)/accommodation
Feature API Web Fallback QR Scanner html5-qrcode + getUserMediaInput manual de código Audio Recording MediaRecorder API— Photo Upload <input type="file" accept="image/*">— Sharing navigator.share()Copiar al portapapeles Haptic Feedback navigator.vibrate()Silencioso en iOS Offline Service Worker + IndexedDB — Install beforeinstallprompt eventBanner manual Push Notifications Web Push API + VAPID Polling (iOS sin PWA)
IndexedDB : 3 stores (offline-queue, event-cache, qr-passes)
Cola FIFO : Solo operación CHECK_IN se encola offline
Sincronización : Al reconectar, procesa cola y actualiza React Query cache
Banner visual : Indicador amarillo cuando no hay conexión
Servicio Metodos Endpoint base events 9 /api/proxy/events/*guests 7 /api/proxy/guests/*media 12 /api/proxy/media/*tables 2 /api/proxy/tables/*payments 2 /api/proxy/payments/*sharing 3 /api/proxy/sharing/*
Centralizado en lib/hooks/queries/keys.ts. Patron identico a nvito-client.
Stats del evento: cada 30 segundos (refetchInterval: 30_000)
QR passes: staleTime: 10min, gcTime: 7 dias (cache largo para offline)
Capa Framework Suites Tests Security (crypto, CSRF, rate limiter, hardening) Vitest 4 61 BFF (proxy handler) Vitest 1 8 API (services, client, errors) Vitest 3 43 Validations (auth schemas) Vitest 1 15 Query Keys Vitest 1 8 Utils (date, format) Vitest 2 26 Offline Queue Vitest 1 11 Config (env) Vitest 1 9 Middleware Vitest 1 12 Hooks (online status, media recorder) Vitest 2 7 Theme (colors) Vitest 1 4 Total 18 204
Coverage thresholds : 80% statements, 70% branches, 75% functions, 80% lines
Aspecto nvito-client nvito-pwa Plataforma React Native + Expo Next.js 16 (browser) Distribucion App Store / Play Store URL directa Almacenamiento tokens SecureStore (encriptado nativo) Cookies HttpOnly AES-256-GCM Tokens en JS Si (SecureStore accesible) No (patrón BFF) API calls Directas a nvito-api Via BFF proxy (/api/proxy/*) Offline storage AsyncStorage IndexedDB QR Scanner expo-camera html5-qrcode + getUserMedia Audio expo-av MediaRecorder API Push Expo Push Notifications Web Push API + VAPID Haptics expo-haptics navigator.vibrate()
# Server-side only (NUNCA exponer al browser)
NVITO_API_URL = http://localhost:3000
COOKIE_ENCRYPTION_KEY = < 64 hex char s > # AES-256-GCM key
CSRF_SECRET = < 64 hex char s > # HMAC secret
# Web Push
VAPID_PUBLIC_KEY = < public key >
VAPID_PRIVATE_KEY = < private key >
# Public
NEXT_PUBLIC_APP_URL = http://localhost:3002
# Clonar y configurar
cd nvito-pwa
npm install
cp .env.local.example .env.local # Editar variables
# Desarrollo
npm run dev # http://localhost:3002
# Testing
npm run test:run # Vitest
npm run build # Build de producción
Prerequisito : nvito-api corriendo en :3000 (o la URL configurada en NVITO_API_URL).