7d2f605c16
"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
122 lines
5.0 KiB
Markdown
122 lines
5.0 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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 a `WatchPortfolioSnapshot` and pushes it as the
|
|
WatchConnectivity application context. It is triggered from
|
|
`CoreDataStack.refreshWidgetData()`, the same hook that reloads the iOS widget.
|
|
- `WatchDataStore` (watchOS) receives it, caches it in the watch App Group via
|
|
`WatchSnapshotCache`, 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 `SampleDataService` to generate demo data for testing
|
|
- Premium features are gated via `FreemiumValidator`
|
|
- Widget shares data through AppGroup container
|
|
- Sensitive data stored in Keychain via `KeychainService`
|