"Portfolio Journal sin AdMob ni Google: es la única forma de que 'sin rastreo' sea verdad, y el ingreso por anuncios es despreciable" — la ficha de App Store prometía "sin analíticas, sin rastreo, sin venta de datos" mientras la app enlazaba GoogleMobileAds y FirebaseAnalytics, pedía permiso de rastreo al arrancar y mandaba el importe del saldo a GA4 en el evento snapshot_added. SDKs fuera del proyecto (project.pbxproj): paquetes firebase-ios-sdk y swift-package-manager-google-mobile-ads, productos FirebaseCore, FirebaseAnalytics, GoogleMobileAds y FirebaseCrashlytics, más la fase de build "Upload dSYMs to Crashlytics". Package.resolved se queda sin nada que resolver. Con la fase de script fuera, ENABLE_USER_SCRIPT_SANDBOXING vuelve a YES en el target app (estaba en NO solo por Crashlytics). Código borrado: - Services/AdMobService.swift entero — con él se van el flujo UMP, BannerAdView, BannerAdCoordinator y la llamada a ATTrackingManager.requestTrackingAuthorization(). - Services/FirebaseService.swift entero y sus 40 llamadas. Se borra en vez de dejarse como capa vacía: una clase llamada FirebaseService en una app que presume de no llevar Firebase es exactamente la clase de detalle que vuelve a colarse en una auditoría dentro de un año. - El banner y su safeAreaInset en ContentView (bannerInsetView): las cinco pestañas ya no reservan 50 pt al pie. - La entrada "Manage Ad Consent" de Ajustes y el @EnvironmentObject adMobService de SettingsView, ContentView y PortfolioJournalApp. - AppConstants: bloque AdMob, bannerAdHeight, adConsentObtained, Features.enableAnalytics. - SettingsViewModel.toggleAnalytics y analyticsEnabled — el flag no estaba conectado a nada ni tenía control en la interfaz. El atributo AppSettings.enableAnalytics se queda en CoreData con un comentario: no vale la pena una migración y un deploy de esquema CloudKit por borrarlo. - Premium ya no promete "sin anuncios": fuera PremiumFeature.noAds, la entrada de IAPService.premiumFeatures y paywallBenefits, y las claves feature_no_ads / paywall_benefit_noads de los 7 idiomas. No se toca ni el precio ni el producto. Info.plist: fuera NSUserTrackingUsageDescription, GADApplicationIdentifier, GADDelayAppMeasurementInit y los 65 SKAdNetworkItems. Borrados también GoogleService-Info.plist y los scripts Scripts/analyze_ga4.py y Scripts/analyze_crashlytics.py, que ya no tienen de dónde leer. Closes #50 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1u4K16xy7eQVtgsYNZ9Vn
5.0 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Portfolio Journal is a native iOS investment portfolio tracker built with Swift and SwiftUI. It helps users track investments, monitor performance with charts and predictions, set financial goals, and maintain an investment journal.
Target: iOS 17.6+ (widget supports iOS 16.0+)
Build Commands
# Open project in Xcode
open PortfolioJournal.xcodeproj
# Build for Debug
xcodebuild -scheme PortfolioJournal -configuration Debug build
# Build for Release
xcodebuild -scheme PortfolioJournal -configuration Release build
# Run on simulator
xcodebuild -scheme PortfolioJournal -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 15' build
# Clean build
xcodebuild -scheme PortfolioJournal clean
Architecture
The app uses Clean Architecture with MVVM pattern:
PortfolioJournal/
├── App/ # Entry point: PortfolioJournalApp.swift, AppDelegate, ContentView
├── Models/CoreData/ # Core Data entities and CoreDataStack singleton
├── Repositories/ # Data access layer with @MainActor CRUD operations
├── Services/ # Business logic (CalculationService, PredictionEngine, IAPService, etc.)
├── ViewModels/ # MVVM view models for each feature
├── Views/ # SwiftUI views organized by feature
│ ├── Dashboard/ # Main dashboard with evolution charts
│ ├── Charts/ # Financial visualizations (allocation, performance, drawdown)
│ ├── Sources/ # Investment source management
│ ├── Goals/ # Goal tracking and progress
│ ├── Journal/ # Journal entries
│ ├── Settings/ # App settings and import/export
│ ├── Security/ # Face ID/PIN lock (AppLockView)
│ └── Components/ # Shared UI components
├── Utilities/ # Helpers: KeychainService, FreemiumValidator, formatters, extensions
└── Resources/ # Info.plist, PrivacyInfo.xcprivacy, assets
PortfolioJournalWidget/ # iOS Home Screen Widget (WidgetKit)
PortfolioJournalWatch/ # watchOS app (read-only portfolio view)
PortfolioJournalWatchWidget/# watchOS complications (WidgetKit)
Shared/ # Types shared between iOS app and watch targets
Apple Watch
The watch app and its complications are read-only; every edit still happens on the iPhone. App Groups are not shared between iOS and watchOS, so the watch cannot read the Core Data store the iOS widget reads. Instead:
WatchSyncService(iOS) builds aWatchPortfolioSnapshotand pushes it as the WatchConnectivity application context. It is triggered fromCoreDataStack.refreshWidgetData(), the same hook that reloads the iOS widget.WatchDataStore(watchOS) receives it, caches it in the watch App Group viaWatchSnapshotCache, and reloads the complication timelines.- The complication extension only reads that cache — it never talks to WatchConnectivity itself.
Watch targets keep their own *.lproj/Localizable.strings with just their keys,
like the other extensions.
Core Data Model
Key entities: Account, InvestmentSource, Snapshot, Category, Goal, Asset, Transaction, AppSettings, PremiumStatus
Data flows through CoreDataStack singleton which manages CloudKit sync and AppGroup shared container for widget access.
Key Services
- CalculationService: Portfolio metrics, returns calculation, allocation analysis
- PredictionEngine: Investment forecasting algorithms with caching
- IAPService: StoreKit 2 in-app purchases
- DiagnosticsCollector: on-device MetricKit crash/hang reports (nothing is uploaded)
- ImportService/ExportService: CSV data import/export
- AppLockService: Biometric/PIN security via Keychain
Dependencies
No third-party packages. The app deliberately links nothing outside Apple's SDKs: AdMob, Firebase Analytics and Crashlytics were removed in 1.7.0 so that the "no tracking, no analytics" claim on the App Store listing is literally true. Do not reintroduce an advertising, analytics or crash-reporting SDK.
Native frameworks: SwiftUI, Combine, CoreData, CloudKit, WidgetKit, StoreKit 2, LocalAuthentication, MetricKit
App Initialization Flow
PortfolioJournalApp (@main)
└── AppDelegate (MetricKit diagnostics, Notifications init)
└── ContentView
├── OnboardingView (first launch)
├── AppLockView (if security enabled)
└── TabBar: Dashboard | Sources | Goals | Journal | Settings
Localization
Supported languages: English (en.lproj), Spanish (es-ES.lproj)
Development Notes
- Use
SampleDataServiceto generate demo data for testing - Premium features are gated via
FreemiumValidator - Widget shares data through AppGroup container
- Sensitive data stored in Keychain via
KeychainService