Un Goal ya podía acotarse por cuenta; ahora también por categoría de
fuente, de modo que se puede fijar "100.000 € en Cash" y que el progreso
cuente solo las fuentes de esa categoría.
- CoreData: relación Goal.category ↔ Category.goals (opcional, Nullify)
- Goal.includes(_:)/relevantSources(from:)/scopeKey centralizan el filtro
de ámbito (cuenta Y categoría) que antes estaba duplicado en
GoalsViewModel, AddSourceView y NotificationService
- GoalEditorView: sección "Ámbito" con selector de categoría
- Chip de categoría en la lista de Goals y en la tarjeta del Dashboard
- ETA del Dashboard para goals con ámbito: usa la proyección del propio
goal en vez de la evolución global de la cartera (que daría "objetivo
alcanzado" en cuanto la cartera total superara el importe)
- GoalRepository e ImportService deduplican por (nombre, cuenta, categoría)
- Export/Import de goals incluye "category"
- Nuevas claves localizadas en los 7 idiomas
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YcDn5ccuRFV83q7xWWokBT
Feature 1 — Aviso de huecos: SnapshotGapDetector detecta huecos internos por
source (meses sin datos entre dos snapshots). Banner desechable en Dashboard +
DataGapsSheet para rellenar (abre AddSnapshotView con el mes que falta). Complementa
la interpolación de gráficas: la línea se ve suave pero el usuario sabe que faltan
datos reales.
Feature 2 — Resumen mensual compartible: MonthlySummaryShareView (tarjeta con marca:
valor, variación desde el check-in, racha, mood/rating) renderizada con ImageRenderer
y compartida vía ShareService. Botón en la fila "check-in done" del Dashboard.
Feature 3 — Import con vista previa: ImportPreview + previewImportAsync (dry-run
READ-ONLY, no muta nada) cuenta qué se creará/actualizará usando los mismos checks
de existencia que applyImport. ImportDataView muestra la confirmación antes de
importar. El path real de import (applyImport/importDataAsync) NO se toca.
Localización en los 7 idiomas.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2p3gUZRNWW388rWFRjiU7
El JSON v2 perdía Goals, source.monthlyContribution, category.allocationTarget y
todo el Journal (nota/mood/rating/completionTime). Nuevo formato v3:
- source: + monthlyContribution, customFrequencyMonths
- category: + allocationTarget
- top-level: + goals[] (name/targetAmount/targetDate/isActive/account)
- top-level: + journalEntries[] (monthKey/note/mood/rating/completionTime/createdAt)
Import parsea y aplica todo, creando Goals y JournalEntry DIRECTAMENTE en el
contexto de background (GoalRepository es @MainActor → no usable desde el import).
Dedup: goals por nombre+cuenta, journal por monthKey → reimport idempotente.
Retrocompatible con JSON v2. CSV sin cambios.
Guard: si el import trae journal, se salta el marcado de completions derivado de
snapshots (evita JournalEntry duplicados por el desfase de merge viewContext).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2p3gUZRNWW388rWFRjiU7
El import corre en un contexto de background, pero MonthlyCheckInStore muta
viewContext (main). Llamar setCompletionDate desde el background era una
violación de concurrencia de Core Data → los JournalEntry.completionTime de los
meses importados no persistían, dejando el "siguiente check-in" anclado a una
entrada vieja (p.ej. May 2025). Envuelto el marcado en viewContext.performAndWait
para ejecutarlo en la cola correcta.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2p3gUZRNWW388rWFRjiU7
createSnapshot hacía save() por cada snapshot, y save() dispara refreshWidgetData
→ checkpointWAL (performAndWait en el viewContext main) + reloadAllTimelines. Con
213 snapshots eran 213 saves + 213 checkpoints en el main → Application Not
Responding. Añadido modo batch a createSnapshot (no guarda por fila; applyImport
ya guarda el contexto una vez al final) y flag suppressWidgetRefresh en
CoreDataStack para neutralizar el refresh por fila durante el import, con un único
refreshWidgetData al terminar.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2p3gUZRNWW388rWFRjiU7
- 1A: Notificación mensual de resumen de portfolio (día 5 de cada mes)
- 1B: Badge de racha mensual en Dashboard (streak counter)
- 1C: Empty states mejorados en Goals y Journal con CTAs claros
- 2A: Quick Update sheet — actualiza todas las fuentes desde una pantalla
- 2B: Notificación batch update redirige al Quick Update sheet
- 2C: Widget deep link portfoliojournal://quickupdate abre Quick Update
- 3A: App Store version check banner en Settings
- 3C: What's New sheet en primer launch de versión nueva
- Localización completa en 7 idiomas para todas las nuevas strings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>