Saltar al contenido principal

App de iOS

La app de iOS de Closegram es una aplicación SwiftUI lista para producción ubicada en apps/ios/. Está dirigida a iOS 15+ y requiere Xcode 15+.

Arquitectura: MVVM + Redux + Clean Architecture

Cada funcionalidad está organizada en tres capas:

Feature/
├── Data/ → Repository implementations, Services
├── Domain/ → Business models, protocols
└── Presentation/ → Redux Store, ViewModels (UI only), Views

Flujo de datos unidireccional:

  1. La vista despacha una Action al Store
  2. El Reducer (función pura) produce un nuevo State inmutable
  3. Para trabajo asíncrono, el Store llama a un Repository
  4. El repositorio coordina uno o más Services (Apollo, Firebase, Stripe…)
  5. El store actualiza el estado → SwiftUI vuelve a renderizar

Inyección de dependencias

Los servicios y repositorios se inyectan mediante un DIContainer seguro para hilos:

@Injected private var authRepository: AuthRepositoryProtocol

Todo se registra una sola vez en appApp.swift al iniciar.

SwiftUI puro, sin puente a UIKit: MainTabView es un NavigationStack(path: $router.path) que envuelve un TabView, con cada una de las 5 pestañas (Updates/Calls/Communities/Chats/Settings) teniendo su propio NavigationStack interno para navegación push por pestaña, más .navigationDestination(for: AppRoute.self) para las 4 rutas entre pestañas (.chat, .contactInfo, .archivedChats, .userProfile) manejadas por AppRouter. Al ser SwiftUI puro, el entorno (todos los @EnvironmentObject) se propaga automáticamente por el árbol — ya no hay una lista manual de reasignación por ruta que mantener sincronizada, que era la fuente recurrente de crashes en la implementación anterior basada en UIViewControllerRepresentable.

Módulos de funcionalidades

FuncionalidadEstadoAspectos destacados
Auth✅ 95%OTP por teléfono, Apple Sign In, Google Sign In, restablecimiento de contraseña — la actualización de token y eliminar cuenta son las brechas restantes, ver abajo
Chat✅ 100%Suscripciones en tiempo real, compartir ubicación, reacciones, chats grupales, indicadores de escritura, selector de GIF
Calls✅ 100%LiveKit WebRTC, CallKit, Picture-in-Picture, roles de orador/espectador
Search✅ 100%Con debounce (500ms), búsqueda de usuarios + conversaciones, filtros en línea/verificado
Profile✅ 100%Ver/editar el perfil propio y el de otros, subir foto/portada
Payment✅ 90%Integración con Stripe, agregar/quitar/predeterminar métodos de pago
CoinPackage✅ 100%Explorar y comprar paquetes de monedas

Pendientes conocidos

Crítico (Chat): carga de contenido multimedia (imágenes, audio, documentos, encuestas), edición/reenvío de mensajes.

Crítico (Auth): actualización de token (AuthService.refreshToken() es un stub TODO literal), eliminar cuenta (AuthRepository.deleteAccount() llama a una mutation real del backend, pero el ProfileRepository.deleteAccount() que en realidad dispara la UI del lado de Configuración es un stub que lanza ProfileError.featureNotSupported, y ninguno de los dos caminos tiene un botón/vista real conectado).

Medio: búsqueda de mensajes (pendiente en el backend de GraphQL), caché local de pagos.

No implementado: modo oscuro, soporte para iPad, accesibilidad VoiceOver/Dynamic Type.

Infraestructura principal

ComponenteDescripción
DIContainerContenedor de inyección de dependencias seguro para hilos
CacheCoordinatorCaché unificado con TTL + expulsión: .memory 10MB, .images 50MB, .disk 100MB
ErrorHandlerManejo centralizado de errores con reintento automático, soporte de reautenticación, notificaciones al usuario
NetworkMonitorConectividad en tiempo real mediante NWPathMonitor + publishers de Combine
MessageQueueCola de mensajes offline persistente con reintento por backoff exponencial (máximo 3)
LoggerSistema de logging unificado

Servicios externos (iOS)

ServicioPropósito
FirebaseAutenticación (teléfono, Google), Analytics, Crashlytics
LiveKitLlamadas de audio/video WebRTC
StripeIntegración de hoja de pago
Google Sign-InInicio de sesión OAuth
Apple Sign InAutenticación nativa
GiphySelector de GIF/stickers en el chat
CallKitInterfaz nativa de llamadas de iOS
VoIP pushAlertas de llamadas en segundo plano

Networking

Las queries, mutations y suscripciones de GraphQL se ejecutan mediante Apollo iOS. Los tipos de Swift generados viven en el módulo ClosegramGraphQL (paquete SwiftPM packages/apollo-swift, generado desde packages/graphql con npm run codegen:ios). La estrategia de caché es adaptativa — en línea usa .returnCacheDataAndFetch, sin conexión usa .returnCacheDataDontFetch.

Soporte sin conexión (97% completo)

Los usuarios pueden:

  • Enviar mensajes sin conexión (se encolan y reintentan automáticamente al reconectar)
  • Ver conversaciones y mensajes en caché
  • Ver métodos de pago en caché
  • Ver un OfflineBanner visual con tres estados: sin conexión → reconectando → conectado

Localización

i18n con tipado seguro y cambio de idioma en tiempo de ejecución (sin necesidad de reiniciar):

Text(LocalizedString.Chat.Message.send.localized)
LocalizedString.Chat.Group.participants(count: 5) // "5 participants"
LocalizationManager.shared.setLanguage(.spanish)

Idiomas: inglés (por defecto), español — más de 230 claves en 11 categorías.

Configuración

cd apps/ios/app
open app.xcodeproj
# File → Packages → Resolve Package Versions

Configura las variables de entorno en el scheme (Run → Arguments):

STRIPE_PUBLISHABLE_KEY = pk_test_...
GIPHY_API_KEY = ...
API_HOST = <la IP local de tu máquina> # backend local, solo dispositivo físico
API_ENVIRONMENT = development # opcional: apunta los builds Debug a dev.api.closegram.com en vez de local

A qué entorno de backend (local / development / production) apunta un build, y cómo dirigir cada uno desde Xcode y Fastlane, se cubre en Entornos de despliegue.

Regenerar el código de GraphQL

Cada vez que cambie packages/graphql/schema/schema.web.graphqls o cualquier operación .graphql bajo packages/graphql/operations/Web/**:

npm run codegen:schema # regenera el schema contra el que apollo-swift genera código
npm run codegen:ios # regenera packages/apollo-swift/Sources/ClosegramGraphQL

y luego reconstruye. CI lo hace cumplir — el job test en .github/workflows/ios-ci.yml corre swift run codegen (desde packages/apollo-swift) y falla el build si el Swift generado está desactualizado respecto a lo versionado, así que código generado obsoleto no puede fusionarse silenciosamente.

Ejecutar pruebas

# Via Fastlane
SKIP_GIT_CHECK=true bundle exec fastlane test

# Via xcodebuild
xcodebuild test -scheme app -destination 'platform=iOS Simulator,name=iPhone 15 Pro'

Corre en cada push/PR vía .github/workflows/ios-ci.yml (runner de macOS, Xcode 26.1 — requerido por el IPHONEOS_DEPLOYMENT_TARGET = 26.1 de este proyecto). Hoy la cobertura de pruebas está concentrada por completo en AuthTests (9 archivos) — pruebas de reducer, store y use-case para los flujos de login/registro/contraseña, todas pasando en CI. Ninguna otra funcionalidad tiene cobertura de pruebas todavía; ver Migración de paridad de iOS.