Saltar al contenido principal

Migración de iOS a paridad

apps/ios se construyó contra una versión anterior del backend y quedó desfasada. El paquete de operaciones compartidas declara hoy 746 operaciones GraphQL; apps/frontend-nextjs usa 649, apps/ios resuelve 75. Además, siete tipos de operación que el código de iOS todavía referencia ya no existen en el paquete, así que hoy la app no se puede regenerar limpiamente.

Esta página es el plan de trabajo para llevar iOS a paridad con web. Es una lista viva: marca las casillas conforme avance el trabajo — nada regenera esta página.

Decisión de alcance registrada: paridad total. Toda operación y feature que exista en apps/frontend-nextjs se planea para iOS, salvo lo listado en Fuera de alcance, que es web por construcción (superficie SEO, páginas de marketing, retornos de pago por redirección del navegador). Los flujos de compra sí entran, pero deben rediseñarse para StoreKit, no portarse.

Relacionado: App de iOS · GraphQL · Testing · Roadmap

Dónde está iOS hoy

Conteo de operaciones por dominio en packages/graphql/operations/Web. "web" = documentos importados por apps/frontend-nextjs; "iOS" = tipos generados que el código Swift referencia y que todavía resuelven.

DominioOperacioneswebiOSCobertura iOS
AppVersion1000%
Auth5145611%
Calls2752385%
Coins414049%
Contacts6600%
Conversations44152454%
DeviceTokens202100%
Family7700%
Follow393925%
Live343400%
Messages896566%
Misc262500%
Notes4400%
Notifications282400%
Payments66466%
Posts12612400%
Profile2824310%
Reports3300%
Search424112%
Settings484800%
Shop161600%
SocialLinks6600%
Stories474700%
Subscriptions252500%
Total7466497510%

Dos cosas que la tabla esconde y que conviene saber antes de planear:

  • iOS va adelante de web en llamadas en tiempo real. Calls (23/27) y Conversations (24/44) son los únicos dominios donde iOS lidera. 45 de las 75 operaciones que usa iOS no las usa web: CallKit/VoIP, solicitudes de speaker, conversaciones archivadas y bloqueadas, device tokens. Nada de eso se reescribe; se conserva y se cubre con tests.
  • Algunas operaciones de iOS son variantes legacy que todavía compilan. MeQuery, GetUserQuery, UpdateProfileMutation, LoginWithAppleMutation, LoginWithPhoneMutation, RequestPhoneOtpMutation y SearchConversationsQuery siguen existiendo en el paquete, pero web ya migró a documentos *Inline más nuevos (GetMe, GetPublicProfile, UpdateProfileInline, LoginWithAppleInline, LoginWithPhoneInline) con selection sets más ricos. iOS muestra menos datos que web desde el mismo backend.

Fase 0 — Desbloquear el build y el codegen

Nada más se puede verificar hasta que iOS compile contra código recién generado. Esta fase es prerrequisito de todas las demás.

Restaurar el enlace al paquete Swift

El módulo generado se renombró ClosegramAPIClosegramGraphQL (commit 2762f0fa), y packages/apollo-swift/Package.swift ahora expone el producto ClosegramGraphQL. 104 archivos Swift hacen import ClosegramGraphQL, pero apps/ios/app/app.xcodeproj/project.pbxproj sigue enlazando un producto llamado ClosegramAPI, que ya no existe — la cadena ClosegramGraphQL aparece cero veces en el archivo de proyecto.

  • Reemplazar la dependencia de producto ClosegramAPI por ClosegramGraphQL en el target app y commitear project.pbxproj — hecho; project.pbxproj enlaza ClosegramGraphQL (0 referencias restantes al producto anterior)
  • Confirmar que xcodebuild -scheme app build compila desde derived data limpio — reconfirmado tras la reescritura de NavigationStack de la Fase 1 (ver abajo)
  • Actualizar las referencias obsoletas a ClosegramAPI en la prosa: apps/ios/README.md (líneas 136, 681), apps/ios/app/app/Core/Services/Auth/README.md (línea 194), packages/apollo-swift/README.md — siguen obsoletas, aún no tocadas

Arreglar las siete referencias muertas

Estos tipos generados los referencia el código Swift pero el codegen no puede producirlos, porque ningún documento .graphql los declara. La resolución cambia por caso: tres necesitan documento nuevo, tres adoptar el renombrado, uno rediseño.

iOS referenciaEstado en backendResolución
ChangePasswordMutationEl campo changePassword existeAdoptar ChangePasswordInline (Auth/ChangePasswordInline.graphql) → ChangePasswordInlineMutation
ResetPasswordMutationEl campo resetPassword existeAdoptar ResetPasswordInline (Auth/ResetPasswordInline.graphql) → ResetPasswordInlineMutation
LoginWithFirebaseMutationCampo eliminado del schemaMigrar a loginWithIdToken (Auth/LoginWithIdToken.graphql) — es la ruta de Google/Firebase que usa web
DeleteAccountMutationEl campo deleteAccount(password: String!) existe, sin documento compartidoAgregar un documento en Auth/ o adoptar el flujo nuevo que usa web: RequestAccountDeletionInline + CancelAccountDeletionInline + DeleteAccountImmediatelyInline (Settings)
MyPurchaseHistoryQueryEl campo myPurchaseHistory existeResuelto — en vez de cambiar a MyCoinPurchases/MyRecentTransactions, se agregó un documento MyPurchaseHistory($limit, $offset) a Coins/MyCoinPurchases.graphql y se regeneró el codegen; iOS lo resuelve directamente
SearchMyMessagesQueryEl campo searchMyMessages existe, sin documento compartidoAdoptar SearchMessagesInline (Messages/SearchMessagesInline.graphql), que es lo que usa web
RefreshTokenMutationNo existe tal mutationrefreshToken es solo un campo String en la respuesta de authRediseñar el refresco de sesión: persistir el refreshToken que devuelve el login y reautenticar, o volver a emitir un ID token de Firebase y llamar a loginWithIdToken
  • Decidir, por fila, si se agrega documento compartido o se adopta el existente
  • Agregar los documentos faltantes a packages/graphql/operations/Web/** (según el CLAUDE.md raíz, las operaciones solo-móvil también viven bajo Web/, compartidas con apollo-web y apollo-swift)
  • Reescribir los siete call sites en Swift
  • Ejecutar npm run codegen:ios y commitear packages/apollo-swift/Sources/ClosegramGraphQL/Sources regenerado

Hacer imposible reintroducir el desfase

  • Agregar un job de CI de iOS (runner macOS) que ejecute el codegen y falle si las fuentes generadas quedan sucias — implementado como .github/workflows/ios-ci.yml dedicado (no fusionado en ci.yml, que solo cubre backend/frontend), movido ahí después de encontrarse en la ruta con typo .githubtest/workflows/ios-ci.yml y nunca haberse ejecutado realmente en GitHub Actions. También se corrigieron XCODE_VERSION: '16.1' / runners macos-15 obsoletos, ninguno compatible con el IPHONEOS_DEPLOYMENT_TARGET = 26.1 de este proyecto
  • Que ese mismo job compile el scheme app y ejecute appTests (ver Requisitos de testing)
  • Documentar el ritual en App de iOS: pull → npm run codegen:schemanpm run codegen:ios → build

Adoptar las variantes vigentes de cada documento

Documentos legacy que aún compilan pero ya no coinciden con lo que renderiza web. Bajo riesgo; conviene hacerlo con el codegen recién corrido.

  • MeQueryGetMe / GetMeProfileInline
  • GetUserQueryGetPublicProfile
  • UpdateProfileMutationUpdateProfileInline
  • LoginWithAppleMutationLoginWithAppleInline
  • LoginWithPhoneMutationLoginWithPhoneInline
  • RequestPhoneOtpMutation — confirmar la variante vigente y alinear
  • Comparar cada selection set nuevo contra el mapeo de modelos de iOS y ampliar los modelos de dominio donde se agregaron campos

Fase 1 — Ajustes de arquitectura

Arreglos estructurales que abaratan las fases de features. Hacerlos antes del trabajo de volumen, no después: cada uno se encarece por cada feature agregada.

Violaciones de capas

  • Sacar las llamadas directas a Apollo de las vistas SwiftUI: ChatsListView.swift (líneas 870, 959), ParticipantActionsSheet.swift (líneas 207, 226), ContactInfoView.swift — enrutarlas por el repositorio de Chat
  • Agregar Features/Calls/Data/Repositories/CallRepository y que CallStore (y sus cinco extensiones) deje de llamar a GraphQLService/ChatAPIService directamente — hoy son 22 llamadas directas a servicios
  • Fijar la regla en revisión de código: View → ViewModel → Store → Repository → Service, sin atajos

Inyección de dependencias

  • Registrar los cuatro repositorios sin registrar en DIContainer+Registration.swift: SearchRepository, PaymentRepository, CoinPackageRepository (hoy se instancian por argumento por defecto del init) — más los que agreguen las fases siguientes
  • Agregarlos todos a validateRequiredDependencies(), que hoy valida 3 de 7 y por lo tanto da falsa confianza
  • Se arregló un problema de DI relacionado pero distinto: SearchView, CreateDirectChatView y CreateGroupChatView (además, antes de borrarse arriba, el showCreateChat() de NestedNavigationView) hacían let searchStore = SearchStore() detrás de un comentario "Temporary: Create local stores until we can use @EnvironmentObject in init", evitando el singleton registrado en DI y creando instancias nuevas desechables. Los cuatro ahora resuelven DIContainer.shared.resolve(SearchStore.self). Es un arreglo a nivel de Store, no el hueco a nivel de Repository que siguen rastreando las dos líneas de arriba

Costuras de testing

  • Sacar MockAuthRepository y MockChatRepository del target de la app (o envolverlos en #if DEBUG) — hoy se compilan dentro del binario de producción
  • Eliminar el duplicado: MockAuthRepository existe dos veces, en Features/Auth/Data/Repositories/ (2.5 KB) y en appTests/Tests/AuthTests/Data/ (7.4 KB), con comportamiento divergente — sigue pendiente, ambos siguen existiendo
  • Agregar <Testables> de appTests al app.xcscheme compartido — hecho. TestAction tenía shouldAutocreateTestPlan = "YES" pero sin bloque <Testables>, así que xcodebuild test/fastlane test fallaban directamente con "Scheme app is not currently configured for the test action" en vez de ejecutar cero tests silenciosamente. Arreglarlo sacó a la luz 9 fallos antes invisibles en AuthTests (huecos reales en la validación de LoginUseCase/RegisterUseCase, más 3 tests que afirmaban el nivel incorrecto de fortaleza de contraseña), todos arreglados de verdad

Estructura

  • Reemplazar el puente a UIKit NestedNavigationView/CustomTabBarController (un UIViewControllerRepresentable que envolvía UINavigationController/UITabBarController, reconstruyendo manualmente un UIHostingController con una lista de .environmentObject() elegida a mano por cada ruta empujada) por un MainTabView puro en SwiftUI: un NavigationStack(path: $router.path) externo alrededor de un TabView, cada una de las 5 pestañas con su propio NavigationStack interno, .navigationDestination(for: AppRoute.self) para las 4 rutas entre pestañas. Ambos archivos anteriores fueron borrados. Esto era la causa raíz de la fragilidad de asignación de environment-objects que esta sección advertía — un árbol SwiftUI puro propaga el entorno automáticamente, así que ya no existe una lista manual de reasignación que mantener sincronizada
  • Consolidar las (ahora cinco, Helpers/CustomSheetModifier.swift se borró en esta pasada por estar completamente sin uso — cero referencias, y su propio diseño no adjuntaba ningún environment object) implementaciones de sheets en un solo lugar (Helpers/SheetPreferenceKey.swift, Utilities/SheetPresentationState.swift, Stores/SheetCoordinator.swift, Shared/Components/BottomSheet.swift, Chat/.../CustomBottomSheet.swift)
  • Eliminar las carpetas raíz Helpers/, Utilities/ y Stores/ — ninguna forma parte de la arquitectura declarada
  • Decidir el destino de Domain/UseCases: solo existe en Auth. O se adopta en todos los features o se quita de la descripción de arquitectura
  • Trocear los god objects antes de agregarles código: ChatStore.swift (1411 líneas), ChatsListView.swift (1010), LiveKitManager.swift (965), CallStore.swift (873 + 5 extensiones). El patrón a copiar es CallStore+*
  • Borrar o reescribir apps/ios/app/Package.swift — declara un target ClosegrammApp cuyo directorio Sources/ no existe y lista solo dos de las seis dependencias reales
  • Rotar la clave de Giphy commiteada como fallback de DEBUG en Config/Secrets.swift y mover ambos fallbacks a xcconfig/.env
  • Corregir apps/ios/README.md, que documenta un CacheCoordinator, un ApolloClientManager, un GoogleAuthManager, Core/Models/User.swift, Views/Messages/ y un TESTING_GUIDE.md que no existen

Scaffolding de features nuevos

Hacen falta nueve módulos nuevos. Crear cada uno como Features/<Nombre>/{Data/Repositories,Domain/Models,Presentation/{Store,ViewModels,Views}} con su repositorio registrado, según App de iOS.

  • Posts · [ ] Stories · [ ] Live · [ ] Notifications · [ ] Settings (promover desde solo-vistas) · [ ] Shop · [ ] Subscriptions · [ ] Social (Follow + Contacts + Family) · [ ] Monetization (Coins + Payments + Payouts)

Fase 2 — Auth, cuenta y seguridad

Faltan 45 operaciones de Auth. Esta fase desbloquea cambio de cuenta, 2FA y passkeys — todo ya soportado por el backend y ya en producción en web.

  • Flujos de contraseña sobre los documentos vigentes: ChangePasswordInline, ResetPasswordInline, ChangeForcedPasswordInline, RequestPasswordReset, VerifyResetTokenInline
  • Inicio de sesión con Google vía LoginWithIdToken (reemplaza al eliminado loginWithFirebase)
  • Apple sign-in vía LoginWithAppleInline; OTP por teléfono vía LoginWithPhoneInline + RequestPhoneOtp
  • Passkeys / WebAuthn con ASAuthorizationPlatformPublicKeyCredentialProvider: GeneratePasskeyRegOptionsInline, VerifyPasskeyRegInline, GeneratePasskeyAuthOptionsInline, VerifyPasskeyAuthInline, MyPasskeysInline, DeletePasskeyInline — con Face ID/Touch ID la experiencia queda mejor en iOS que en web
  • Doble factor: SetupTwoFactorInline, VerifyTwoFactorSetupInline, EnableTwoFactorInline, DisableTwoFactorInline, TwoFactorStatusInline, RegenerateBackupCodesInline, VerifyLoginTwoFactorInline
  • Multi-cuenta: AddAccountLogin, AddAccountGoogle, AddAccountApple, AddAccountPhone, AddAccountVerify2FA, AddAccountVerifyPasskey — requiere almacenamiento multi-token en Keychain, que hoy asume una sola sesión
  • Onboarding: CompleteOnboarding, OnboardingInterestSuggestions, OnboardingSearchUsers, OnboardingFollow, OnboardingUnfollow, OnboardingFollowRequirement
  • Sesiones y seguridad: ActiveSessionsInline, RevokeSessionInline, RevokeAllOtherSessionsInline, LoginHistoryInline, SecurityEventLogInline, SecurityScoreTwoFactorInline, SecurityScoreEmailInline, GetSecurityAlertsInline, MarkSecurityAlertReadInline, DismissSecurityAlertInline
  • Rediseño del refresco de sesión (viene de la Fase 0) implementado y cubierto por tests
  • Logout conectado a limpiar Keychain, caché de Apollo y device tokens
  • Strings i18n en en.lproj y es.lproj; tests según Requisitos de testing

Fase 3 — Perfil, grafo social y descubrimiento

Faltan 25 de Profile, 37 de Follow, 41 de Search, 6 de SocialLinks, 6 de Contacts.

  • Perfil público sobre GetPublicProfile + GetRelationshipStatusForProfile + RecordProfileVisitFromProfile
  • Perfil propio y edición: GetMeProfileInline, UpdateProfileInline, ValidateUsernameInline, GetAboutAccount, GetMyInterests, UpdateInterests
  • Presencia: UpdateLastSeen, SetUserOffline
  • Verificación y correo: RequestVerificationInline, VerificationStatusInline, SubmitIdentityVerificationInline, MyIdentityVerificationStatusInline, GetEmailStatusInline, ResendEmailVerificationInline, VerifyEmailInline
  • Grafo de follow: modales de seguidores/seguidos, RemoveFollowerModal, close friends, favoritos, bloqueados, silenciados, restringidos (AddToCloseFriendsInline, GetFavoriteUsersInline, GetBlockedUsersInline, GetMutedUsersInline, RestrictedAccountsInline y las variantes *FromProfile)
  • Acciones masivas: BulkUnfollowUsersAction, BulkBlockUsersAction, BulkRemoveFollowersAction, BulkGetUserFollowers, BulkGetUserFollowing
  • Búsqueda y descubrimiento: SearchUsersExplore, SearchHashtagsInline, DiscoverPosts, DiscoverTrending, DiscoverRecommended, DiscoverSimilar, DiscoverNearby, DiscoverMayKnow, GetSuggestedUsers, MentionSuggestionsInline, historial de búsquedas recientes
  • Enlaces sociales: MySocialLinks, AddSocialLink, UpdateSocialLink, DeleteSocialLink, ReorderSocialLinks, UserSocialLinks
  • Importación de contactos con el framework nativo Contacts y su permiso (las 6 operaciones de Contacts)
  • Share sheet nativo y QR de perfil
  • i18n + tests

Fase 4 — Ponerse al día en mensajería

Faltan 83 operaciones de Messages y 20 de Conversations. iOS ya tiene el transporte y la capa de tiempo real; esto es superficie de features sobre plomería que ya funciona.

  • Reacciones, destacados y anclados: AddReaction, GetMessageReactions, GetPinnedMessages
  • Hilos y respuestas: GetMessageThread, GetMessageReplies, GetMessage
  • Semántica de borrado: DeleteMessageForMe vs DeleteMessageForEveryone (iOS solo tiene el legacy DeleteMessage)
  • Búsqueda dentro de la conversación: SearchMessagesInline (reemplaza al muerto SearchMyMessagesQuery)
  • Navegador de multimedia: GetMessagesByMediaType
  • Encuestas: CreatePoll, GetPollResults
  • Mensajes programados y con expiración: CancelScheduledMessage, EditScheduledMessage
  • Traducción de mensajes, por mensaje y global (MyTranslationSetting)
  • Gestión de grupos: GetConversationParticipants, UpdateConversation, UpdateConversationSettings, TransferAdmin, LeaveConversation, DeleteConversation, ConversationInviteLinkInfo, unirse por enlace
  • Solicitudes de mensaje y privacidad: ConversationPrivacyInfo, aceptar/rechazar
  • Contadores de no leídos: GetConversationUnreadCount, GetTotalUnreadCount, NavTotalUnread — necesarios para un badge correcto
  • Reportes: ReportConversation
  • Notas: MyNote, CreateNote, DeleteNote, NotesFromFollowing (las 4 operaciones de Notes)
  • Mensajes y grupos de pago — la parte de compra se difiere a la Fase 10; primero el lado de lectura y permisos (HasConversationAccess, GetMyMessagePurchases)
  • i18n + tests

Fase 5 — Posts y feed

126 operaciones, el hueco más grande y el núcleo del producto. Web usa 124; iOS ninguna.

  • Feed principal: GetFeed, GetInteractionCounts, registro de vistas
  • Detalle y tarjeta de post: GetPost, HasUserLikedPostInline, IsPostSavedInline, HasUserRepostedInline
  • Crear post: subida de media, caption, menciones/hashtags, visibilidad, colaboradores, programación (CreatePost, GetScheduledPostForEdit)
  • Comentarios: CreateComment, GetPostComments, likes y ajustes de comentarios
  • Reposts, guardados y colecciones
  • Modales de likes y visitantes: GetPostInteractionsForLikesModal, GetMyProfileViewers
  • Opciones de post: editar (GetPostForEdit), archivar (ArchiveOwnPost), borrar (DeleteOwnPost), fijar, reportar (CreatePostReport)
  • Insights y promociones: CreatePostPromotionFromComposer, estadísticas de promoción
  • Páginas de hashtag: PostsByHashtag, HashtagStatsHashtagPage
  • Archivo y eliminados recientemente, con restauración
  • Desbloqueo de posts de pago — lectura ahora, compra en la Fase 10
  • Marca de agua dinámica sobre media de pago, igual que la superposición anti-captura de web
  • i18n + tests

Fase 6 — Stories, clips y destacados

47 operaciones, ninguna en iOS.

  • Barra de stories, visor, reacciones, respuestas, conteo de vistas
  • Crear story: media y texto con selector de fondo
  • Destacados: GetUserHighlights, GetMyStoriesForHighlight, crear/editar/borrar/reordenar
  • Compartir story a DM
  • Feed de clips y creación de clips (like, guardar, repost, estadísticas)
  • Historial de búsqueda que vive en este dominio (SearchHistoryExplore, ClearSearchHistoryExplore, DeleteSearchHistoryItemExplore)
  • i18n + tests

Fase 7 — Live

34 operaciones, ninguna en iOS — pero iOS ya tiene LiveKit conectado para llamadas, que es la mayor parte de lo difícil.

  • Descubrimiento de lives: en vivo ahora, trending, programados, archivo
  • Sala de live: video, comentarios, reacciones, comentarios fijados, regalos
  • Salir en vivo: crear/iniciar/actualizar/terminar, tokens de invitado, guardar grabación
  • Control de speakers — reutilizar la maquinaria de solicitudes que ya existe en CallStore
  • Subscriptions en tiempo real: LiveCommentAddedSub, LiveReactionsSub, LiveStreamUpdatedSub, LiveStreamsChangedSub
  • i18n + tests

Fase 8 — Notificaciones

28 operaciones, ninguna en iOS. El registro de device tokens ya funciona (DeviceTokens está al 100%), así que esto es la superficie in-app, no el transporte.

  • Lista de notificaciones y lista enriquecida: MyNotifications, MyNotificationsRich
  • Leídas y borrado: MarkNotificationAsRead, MarkAllNotificationsAsRead, DeleteNotification, DeleteAllNotifications
  • Badge y conteo: UnreadNotificationsCount + NavTotalUnread
  • Tiempo real: subscription NotificationAdded
  • Solicitudes de follow: PendingFollowRequests, AcceptFollowRequest, RejectFollowRequest, SendFollowRequestFromProfile, CancelFollowRequestFromProfile, GetSentFollowRequestsForProfile
  • Ajustes: GetNotificationSettingsInline, UpdateNotificationSettingsInline, ajustes extendidos, vistas previas de mensajes, GetSleepModeSettingsInline / UpdateSleepModeSettingsInline
  • Mapear cada tipo de payload APNs a un deep link in-app a través de AppRouter
  • i18n + tests

Fase 9 — Ajustes, privacidad y seguridad

48 operaciones de Settings más 26 de Misc, ninguna en iOS. Web tiene unas 85 pantallas de ajustes; el feature Settings de iOS son hoy tres vistas sin store.

  • Promover Settings a módulo completo (Data/Domain/Presentation)
  • Privacidad: GetPrivacySettingsInline, UpdatePrivacySettingsInline, privacidad extendida, ResetPrivacySettingsInline, revisión de privacidad
  • Palabras ocultas, protección de desnudos, confirmaciones de lectura, etiquetas y menciones, ajustes de compartir
  • Ciclo de vida de la cuenta: DeactivateAccountInline, ReactivateAccountInline, RequestAccountDeletionInline, CancelAccountDeletionInline, DeleteAccountImmediatelyInline, AccountStatusInline, GetAccountStandingStatusInline
  • Exportación de datos: solicitar, estado, descargar, borrar, más la subscription DataExportStatusChangedInline
  • Gestión del tiempo y uso: GetTimeManagementDataInline, RecordUsageHeartbeatInline, TakeBreakInline, EndBreakInline, GetActivityHeatmapInline
  • Límites de interacción: MyInteractionLimitInline, SetInteractionLimitInline
  • Preferencias de contenido: RemoveContentPreferenceInline, GetNotInterestedContentInline, UpdateMediaPreferencesInline
  • Compartir ubicación: GetMyLocationSharing, UpdateMyLocationSharing, DeleteMyLocationSharing
  • Ajustes de traducción: MyTranslationSetting, GetReelsTranslationSettingsInline
  • Family Center (las 7 operaciones de Family): vínculo padre-hijo, límites de tiempo, estadísticas de uso
  • Moderadores (dominio Reports): MyModerators, AddModerator, RemoveModerator, UpdateModeratorPermissions, ModeratorActivity
  • Control de versión al inicio: CheckAppVersion — el contrato de backend se construyó justo para esto, ver Control de versiones de la app
  • i18n + tests

Fase 10 — Monetización

Faltan 37 de Coins, 25 de Subscriptions, 16 de Shop, 2 de Payments, más las operaciones de propinas, top fans y referidos en Misc. Esta fase no es un port. Web vende monedas con Stripe y PayPal; en iOS los bienes digitales tienen que pasar por StoreKit o la app se rechaza.

  • Decidir y dejar por escrito la frontera de StoreKit antes de construir: qué compras son IAP (monedas, regalos, propinas, posts de pago, mensajes de pago, suscripciones de creador) y cuáles siguen con tarjeta/PayPal (producto físico de la tienda, payouts)
  • StoreKit 2 y flujo de compra con validación de recibo en servidor — esto necesita contrato nuevo de backend; las operaciones existentes CreateCoinPurchaseIntent/CapturePaypalCoinOrder no aplican
  • Saldo y billetera: MyCoinBalance (ya en iOS), MyRecentTransactions, MyCoinPurchases
  • Regalos: catálogo (GiftCatalogInline, LiveGiftCatalogInline), enviar a usuario/post/live
  • Propinas y top fans: SendTipFromChat, MyReceivedTipsInline, MyTipStatsInline, TopFansInline, TopTippersInline, RewardTopFanReward
  • Suscripciones de creador: tiers, ofertas (OfferByToken, OffersMyTiers, CreateSubscriptionOffer), suscribir/cancelar/renovar, pruebas (RedeemSubscriptionTrial)
  • Conversaciones de pago: SubscribeToConversation, GrantFreeConversationAccess, GetConversationSubscriptionStats, GetMySubscriptionEarnings
  • Tienda: gestión de productos, GetSellerProductsForProfile, PurchaseProductFromProfile, reseñas, pedidos y disputas
  • Payouts y ganancias: perfil de payout, StartPayoutOnboardingInline, solicitudes de cashout, MySalesInline, ganancias por anuncios, estados de cuenta
  • Referidos: MyReferralInfoInline
  • Métodos de pago: conservar las cuatro operaciones existentes; agregar CreatePaypalVaultSetupToken / ConfirmPaypalVaultSetupToken solo si PayPal sobrevive a la decisión de frontera de StoreKit
  • i18n + tests

Fase 11 — Pulido de llamadas

Solo faltan 4 operaciones; aquí iOS va adelante de web. Fase pequeña, se puede hacer cuando convenga.

  • ActiveCall, GetCall, MySpeakerRequests, CallStatusChanged
  • Escribir tests para las 23 operaciones de Calls que hoy no tienen ninguno

Requisitos de testing

La regla del CLAUDE.md raíz — un feature no está terminado sin tests, en ambos idiomas — también aplica a iOS. Hoy apps/ios solo tiene tests de Auth (8 archivos), y puede que ni se ejecuten.

Por fase, antes de marcar la última casilla:

  • Tests de reducer para cada *Reducer nuevo (funciones puras, la cobertura más barata del proyecto)
  • Tests de store con repositorio mock para las rutas async y de efectos secundarios
  • Tests de ViewModel para validación y ramas de error
  • Tests de repositorio contra un GraphQLService mockeado, verificando el mapeo de la respuesta
  • Strings presentes en en.lproj y es.lproj — una pantalla que solo se lee bien en inglés no está terminada
  • npm run codegen:ios limpio y xcodebuild test -scheme app en verde

Infraestructura, una sola vez:

  • <Testables> agregado a app.xcscheme (Fase 1)
  • Job de CI en macOS que compile, ejecute tests y verifique desfase de codegen (Fase 0)
  • Que el target de tests refleje la estructura de features: appTests/Tests/<Feature>Tests/{Data,Domain,Presentation} — hoy solo existe AuthTests

Definición de terminado, por feature

Un feature pasa de - [ ] a - [x] solo cuando se cumple todo esto:

  1. Las operaciones vienen de packages/graphql/operations/Web/** y el codegen está commiteado
  2. La ruta de datos es View → ViewModel → Store → Repository → Service, con el repositorio registrado en DIContainer y validado
  3. Los strings existen en en.lproj y es.lproj
  4. Los tests de reducer, store y repositorio pasan en CI
  5. El comportamiento offline es deliberado — cacheado, encolado o explícitamente no disponible
  6. La página correspondiente en docs/features/ tiene marcada su casilla de iOS

Fuera de alcance

Web por construcción; no se planea equivalente en iOS.

Feature de webMotivo
Páginas estáticas de marketing y legales (/about, /blog, /press, /terms, …)Se sirven en webview o no se sirven
sitemap.ts, robots.ts, metadata SSR, la capa components/public/* y cada variante Public*PageSuperficie SEO; iOS siempre usa las variantes autenticadas
Link-in-bio (/l/[username])Es una landing web por definición
EarnLandingPage (/coins público)Página de marketing
FloatingChatPatrón de superposición de escritorio
Web push (lib/webPush, RegisterWebPushTokenInline)Lo reemplaza APNs vía el dominio DeviceTokens, ya al 100% en iOS
/payments/returnManejador de redirección del navegador; StoreKit no tiene equivalente
settings/app-website-permissionsModelo de permisos del navegador
Etiqueta de Google AnalyticsFirebase Analytics ya está conectado en iOS

Necesitan rediseño en vez de port: todos los flujos de compra con dinero fiat (StoreKit, Fase 10), el OAuth de crossposting (ASWebAuthenticationSession) y los permisos de dispositivo (deep links a ajustes nativos).

Seguimiento de avance

Una casilla por dominio. Marcar cuando las operaciones del dominio estén conectadas, localizadas y con tests.

  • AppVersion (1 operación)
  • Auth (faltan 45)
  • Calls (faltan 4)
  • Coins (faltan 37)
  • Contacts (6)
  • Conversations (faltan 20)
  • DeviceTokens (completo)
  • Family (7)
  • Follow (faltan 37)
  • Live (34)
  • Messages (faltan 83)
  • Misc (26)
  • Notes (4)
  • Notifications (28)
  • Payments (faltan 2)
  • Posts (126)
  • Profile (faltan 25)
  • Reports (3)
  • Search (faltan 41)
  • Settings (48)
  • Shop (16)
  • SocialLinks (6)
  • Stories (47)
  • Subscriptions (25)