Commit Graph

88 Commits

Author SHA1 Message Date
alexandrev-tibco 1e6e3ef361 Re-engagement con KPIs reales (goal ETA, YoY, mes, milestone, racha) + rotación (build 64)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZFmGhbWzibhApev3C4554
2026-07-20 19:29:27 +02:00
claude-code 8f14d4cd06 Add Crashlytics BigQuery report script
Consulta el export de Crashlytics en BigQuery (dataset firebase_crashlytics,
proyecto portfoliojournal-ef2d7) con el service account de pass. Top crashes por
versión/build. Setup documentado en el docstring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-13 00:54:07 +02:00
alexandrev-tibco 41e2a22a64 Swipe fiable en card del Home y Charts: simultaneousGesture sobre la gráfica (build 63)
El swipe entre páginas del Home (build 62) y entre charts (build 60) no
funcionaba encima de la gráfica: su DragGesture de scrub (minimumDistance 0)
consumía el gesto. Cambiado a .simultaneousGesture — ambos se reconocen; la
dominancia horizontal en onEnded evita romper el scroll vertical. Ahora el swipe
funciona en toda la card, también sobre el chart.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-11 10:50:52 +02:00
alexandrev-tibco d8bd625327 Swipe entre mini-charts en la card del Home (build 62)
El feedback #4 original apuntaba a la card 'Portfolio Evolution' del HOME, no
solo a la pestaña Charts (donde ya se hizo en build 60). Ahora la card del Home
se desliza entre páginas:
- Evolution (con su toggle Total/By Category intacto)
- Allocation (semicírculo + leyenda con %), datos de viewModel.categoryMetrics
Puntos indicadores, transición direccional, título dinámico. 'By Category' no
es página aparte porque ya es un toggle dentro de Evolution.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-10 22:13:00 +02:00
alexandrev-tibco 7c6a29c821 OCR desde imagen del portapapeles + botón copiar diagnóstico iCloud (build 61)
- Quick Update: checkClipboard ahora detecta también una IMAGEN copiada
  (pb.hasImages) y hace OCR automático — te ofrece pegar el importe detectado
  sin pulsar 'Escanear'. Gated por changeCount. El 'Pegar €X' del teclado
  también usa el importe OCR'd de la imagen.
- Settings iCloud: botón 'Copiar diagnóstico de iCloud' SIEMPRE visible (el
  desplegable de detalle solo salía con un error activo, difícil de encontrar).
  Copia versión/build, contadores locales, últimas fechas import/export,
  criticidad, hint y el error completo con códigos internos. Strings ×7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-10 21:52:53 +02:00
alexandrev-tibco 6bdf2740c5 Feedback #3 y #4: pegar/OCR por campo en Quick Update + swipe entre charts (build 60)
#3 Quick Update paste/scan por campo:
- Barra de teclado con 'Pegar €X' (si hay importe en portapapeles) y 'Escanear'
  (PhotosPicker → OCR on-device). Actúan sobre el campo enfocado.
- ImageAmountScanner nuevo (app target, Vision) espejo del scanner del Share
  Extension: candidatos rankeados por prominencia, filtra % y años.
- 1 candidato → rellena directo; varios → confirmationDialog para elegir.
- Strings ×7.

#4 Swipe entre charts (iPhone):
- Gesto horizontal en el área del chart avanza al chart anterior/siguiente en
  el orden del menú de título (respeta showForecast). Indicador de página con
  puntos. Transición direccional. selectChart ya no bloquea premium (muestra
  teaser), así que deslizar a uno premium funciona.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-10 21:37:21 +02:00
alexandrev-tibco 86c95a9e73 Feedback TestFlight build 58 (build 59): labels, needs-update, period selector, Home masonry
De 7 comentarios de TestFlight:
- Labels de línea ilegibles (puntos 5+7): ChartLabels.showsLabel thin-out
  (máx 5 iPhone / 8 iPad, siempre primero y último). Charts multi-serie
  (Compare, Period, Year vs Year) etiquetan solo el ENDPOINT de cada serie.
  Prediction etiqueta solo el forecast final. Aplicado a Evolution, Rolling,
  Drawdown, Volatility, Compare, Period, YoY, Prediction.
- 'Needs update' en meses pasados (punto 2): MonthlyCheckInView.snapshotForViewedMonth
  — el estado es por el MES QUE SE VE (effective month de referenceDate), no
  contra la última completion global. Diff usa el snapshot de ese mes.
- Selector de periodo Performance (punto 6): slider 1..60 → picker segmentado
  consistente con el resto, opciones capadas a los meses de datos disponibles
  (availableHistoryMonths). Ya no deja pedir ventanas sin datos.
- Home iPad/Mac (feedback previo): masonry con nº de columnas según ancho
  (2 iPad / 3 Mac ancho >=1250), reparto por peso, propio ScrollView.

Pendiente de este lote: Goals no sincronizan (verificado modelo Goal CloudKit-OK
→ es el subset del partial-failure, no filtro ni esquema; necesita códigos
internos del error), paste/OCR por campo (punto 3) y swipe entre charts (punto 4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-10 20:36:15 +02:00
alexandrev-tibco 1abffdb7bb Feedback iPad: allocation grande + %, Home 2 columnas, YoY KPIs, achievements fix, iCloud calmado (build 58)
Allocation:
- Semicírculo responsive que llena su mitad de la fila (grande en iPad, no
  minúsculo en una esquina) + porcentaje dentro de cada segmento >=7%.
  SemicircleArc acepta center explícito; labels en el ángulo medio del arco.

Home iPad:
- Ya no columna única estrecha a 720. Hero + check-in a ancho completo, resto
  de cards en DOS columnas masonry independientes (sin acoplar alturas → sin
  gaps del grid). Cap 1100pt.

Year vs Year KPIs:
- Retorno del año actual + diferencia (pp) vs año anterior + mejor/peor año
  histórico. Usa forecastEndValue para el año en curso.

Fixes:
- 'View all achievements' en Journal no hacía nada: NavigationLink inline
  (frágil según contexto de navegación) → sheet fiable en cualquier sitio.
- iCloud: CKErrorPartialFailure tras una sync que funciona ya NO se pinta en
  rojo alarmante — nota calmada 'Sincronizando con iCloud' + detalle técnico
  en disclosure. Rojo solo si nunca ha sincronizado y es fallo total.
  syncErrorIsCritical + strings ×7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-10 14:48:06 +02:00
alexandrev-tibco 18f2609d87 Fixes UI iPad + diagnóstico iCloud (build 57)
Feedback del usuario en iPad:
- Allocation: gráfica de tarta → semicírculo estilo hemiciclo (parlamento).
  SemicircleAllocation con arcos dibujados a mano (SemicircleArc Shape),
  segmentos por cuota en 180°, total/selección en el centro, tap por segmento.
- Charts de Analyze (Compare, Period vs Period, Year vs Year) ahora SÍ muestran
  KPIs en la cabecera de iPad como el resto: Compare (líder/rezagado por modo),
  Period (retorno final por periodo), YoY (retorno final por año seleccionado).
- Momentum & Streaks en el sidebar del Journal (iPad, 320pt): variante compact
  — tiles en fila sin subtítulos que rompían a 3 líneas, sin bloque de logros.

iCloud:
- CKErrorPartialFailure (CKErrorDomain code 2) mostraba un mensaje opaco.
  CoreDataStack.hint(for:) recorre CKPartialErrorsByItemIDKey y clasifica el
  código dominante → hint accionable (esquema/quota/red). Settings muestra el
  hint legible sobre el detalle técnico. lastSyncErrorHint publicado. Strings ×7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-10 14:16:24 +02:00
alexandrev-tibco b76b8f81f0 Hotfix: accessors nil-tolerantes en Core Data — crashes de bridging con iCloud sync (build 56)
Dos crashes de producción con la misma causa raíz: atributos declarados
no-opcionales en Swift (@NSManaged id: UUID, date: Date) que llegan nil en
runtime — NSPersistentCloudKitContainer materializa registros de otros
dispositivos por fases y los objetos borrados pueden seguir referenciados por
vistas vivas. El force-bridge de nil revienta:

1. UUID._unconditionallyBridgeFromObjectiveC en ForEach (keypath Identifiable.id)
2. Date._unconditionallyBridgeFromObjectiveC en ChartsViewModel.monthlyTotals

Fix sistémico (no por call-site): NSManagedObject.safeValue(forKey:fallback:healing:)
lee el primitive y cae a un valor seguro; id se auto-cura (UUID nuevo escrito al
primitive sin ensuciar el objeto). Aplicado a:
- Snapshot: id (heal), date, createdAt (fallback distantPast)
- InvestmentSource / Goal / Account: id (heal), name (fallback "")
- Category: id (heal), name, colorHex, icon (fallbacks neutros)

Los accessors son @objc → NSSortDescriptor(keyPath:) y KVC siguen funcionando.
awakeFromInsert/awakeFromFetch se mantienen. Smoke test UITest en verde.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-10 13:28:59 +02:00
alexandrev-tibco 4c0d2af564 Calm 2.0 Fase 4: TipKit para descubrimiento + aha post-onboarding (build 55)
TipKit (Tips.configure con frecuencia diaria):
- QuickUpdateTip: popover en el '+' del Home — descubre Quick Update
- ScreenshotOCRTip: inline en Quick Update — descubre el OCR por captura
- SiriTip: en el cierre del check-in guiado — descubre el intent de voz
- ChartShareTip: popover en el compartir de Charts
- Se invalidan al ejecutar la acción; máx 1-2 impresiones cada uno
- Strings ×7 idiomas

Onboarding:
- Al completar el onboarding sin datos, se abre Add Source directamente
  (notificación .openAddFirstSource) — el usuario nuevo no aterriza en un
  dashboard vacío

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-10 09:31:03 +02:00
alexandrev-tibco 08094483ed What's New versionado y acumulativo (build 54)
El modal estaba congelado en las features de 1.4.0 (hardcodeadas en la vista) y
el título decía 'What's New in 1.4' fijo. Rediseñado como catálogo por versión:

- WhatsNewCatalog: una entrada por release (1.4.0/1.4.1/1.4.2/1.5.0) con sus
  features; AÑADIR ENTRADA CON CADA RELEASE (comentario en el código).
- Diff ACUMULATIVO desde lastSeenWhatsNewVersion: saltar 1.3.x → 1.4.2 muestra
  1.4.0 + 1.4.1 + 1.4.2 agrupadas con badge de versión (headers solo si hay
  más de una release pendiente). Comparación numérica de versiones (1.10 > 1.9).
- lastSeen vacío + onboarding ya completado (usuarios pre-1.4.0) = mostrar todo
  el catálogo; instalación nueva = lastSeen sella a la versión actual al
  completar onboarding (un usuario recién onboardeado no ve 'novedades').
- Sin entrada en el catálogo para el salto → no se muestra sheet.
- Título dinámico (whats_new_title_format) y contenido scrollable.
- Strings de 4 releases + título ×7 idiomas.

Verificado en simulador con -lastSeenWhatsNewVersion 1.4.0: pending
[1.5.0, 1.4.2, 1.4.1] y sheet agrupada correcta.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-10 08:06:49 +02:00
alexandrev-tibco ed737bc290 Calm 2.0 Fase 3: Home iPad columna única, Goals con anillos, Journal timeline, Sources polish (build 53)
Home iPad/Mac (feedback del usuario: 'me gusta más el iPhone'):
- Fuera el grid de 2 columnas (acoplaba alturas de fila y dejaba huecos entre
  cards de tamaños distintos) → columna única centrada a 720pt, patrón
  Things/Day One. Eliminado el drag-drop del grid, columnSpan y el overlay
  full-screen (~120 líneas); el orden/visibilidad sigue en Customize.

Goals:
- ProgressRing nuevo (anillo estilo Fitness con porcentaje centrado y color
  semántico: verde=logrado, ámbar=retrasado, acento=en camino) en GoalRowView
  y en la card de Goals del Home. GoalProgressBar queda solo en la share card.

Journal:
- Momentum & Streaks vive ahora en la cabecera del Journal (la historia del
  hábito es narrativa, no dashboard).
- Filas timeline: círculo de mood como ancla visual (tint semántico por mood),
  mes + estrellas + preview de nota.

Sources:
- Badge 'Needs update' como chip ámbar visible; valor en tipografía rounded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-09 11:11:04 +02:00
alexandrev-tibco 7044cfd441 Calm 2.0 Fase 2: check-in guiado full-screen (build 52)
El ritual mensual como flujo paso a paso en vez de formulario:
- Una fuente por pantalla: valor anterior a la vista, entrada grande (40pt
  rounded), delta en vivo (chip verde/rojo con importe y %), sugerencia de
  portapapeles, 'Igual que el mes pasado' y 'Omitir'
- Barra de progreso del ritual; cancelable solo al inicio (interactiveDismiss
  bloqueado a mitad para no perder valores)
- Reflexión: mood (5 estados existentes) + nota de una línea
- Cierre: total nuevo, delta del mes, streak, compartir y haptic de éxito
- Persistencia por los MISMOS caminos que Quick Update (Snapshots) y el journal
  (MonthlyCheckInStore → JournalEntry): coherente con el resto de entradas
- Entrada: CTA del check-in card en Home (fullScreenCover); la fila de
  completado sigue navegando al journal
- Fix: referenceDate congelada en @State al presentar — el card la recalcula al
  guardar y SwiftUI re-evaluaba el contenido del cover cambiando el mes a mitad
  de flujo
- --pending-checkin en ScreenshotMode (revierte el seed a 'mes pendiente') +
  UITest testGuidedCheckInFlow end-to-end (valor → skips → mood → cierre) ✓
- Strings ×7 idiomas

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-09 10:51:11 +02:00
alexandrev-tibco be7119f4b9 Calm 2.0 Fase 1: sistema de diseño limpio + Home en 3 zonas (1.5.0 build 51)
Sistema:
- AppBackground: fuera gradiente + formas decorativas → systemGroupedBackground
  plano en toda la app. La profundidad la da la jerarquía, no la decoración.

Home (3 zonas: estado / acción / contexto):
- TotalValueCard: hero tipográfico — número 44pt rounded sobre card limpia,
  chip de delta semántico (verde/rojo), sparkline de evolución 56pt estilo
  Stocks, fila secundaria YoY / inception / forecast. Fuera el gradiente azul.
- MonthlyCheckInCard state-driven: pendiente → protagonista con progreso real
  de fuentes (N de M) y un CTA; completado → fila discreta con checkmark,
  próximo check-in y streak integrado. En iPad va a ancho completo bajo el
  hero (zona de acción), fuera del grid.
- InsightsRow: de carrusel de chips a un insight único rotatorio (tap para
  ciclar, dots de posición).
- Eliminados del top: streak badge (vive en el check-in) y banner de pending
  updates (el progreso del check-in lo comunica).
- Defaults nuevos: momentumStreaks/pendingUpdates/periodReturns/monthlySummary
  ocultos por defecto (siguen disponibles en Customize; configs guardadas de
  usuarios existentes se respetan).
- Strings nuevas en 7 idiomas (checkin_done_*, checkin_sources_progress).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-08 23:58:06 +02:00
alexandrev-tibco ff67ea1e71 Fix causa raíz de taps muertos en Charts iPad: AppBackground interceptaba toques (build 50)
El círculo decorativo de AppBackground (70% del ancho del panel, offset -35%)
se desborda de su panel por diseño y en layouts side-by-side queda flotando
SOBRE el sidebar de Charts. Las shapes de SwiftUI participan en hit-testing por
defecto y el panel derecho va después en el HStack → cada tap en la zona del
sidebar cubierta por el círculo moría en silencio. El patrón lo delató: fallaban
Overview + Analyze (arriba, bajo el círculo) y funcionaban Risk + Forecast
(abajo). Dependía de la geometría (orientación/tamaño), por eso no reproducía
en el simulador en portrait.

- AppBackground: .allowsHitTesting(false) — un fondo decorativo jamás debe
  interceptar toques (fix global: aplica también a Sources/Journal en iPad)
- Panel de detalle de Charts: .clipped() para que la decoración tampoco PINTE
  sobre el sidebar
- Selección premium nunca se bloquea: los charts premium se seleccionan y
  muestran teaser de desbloqueo en el área del chart (chart_locked_* ×7 idiomas);
  el paywall se presenta desde el botón (contexto fiable)
- UITests: testChartSidebarSelection ahora corre en landscape (geometría que
  reproducía el bug) + testChartSelectionWithoutPremium nuevo; --no-premium
  en ScreenshotMode para testear la experiencia free

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-08 23:30:30 +02:00
alexandrev-tibco 7b8dec196d Charts 2.0 (build 49): sidebar nativa, filtros en toolbar, title menu en iPhone
El bug de Allocation/Contributions que no respondían en iPad persistía tras dos
fixes de lógica — la causa estaba en la capa de interacción (Buttons custom en
LazyVGrid dentro de ScrollView dentro de NavigationSplitView). Rediseño completo
alineado con el HIG:

- iPad: List(selection:) nativa con estilo .sidebar y secciones agrupadas
  (Overview/Analyze/Risk/Forecast) — hit-testing del sistema, navegación por
  teclado, pointer effects y VoiceOver gratis. El gating premium sigue en
  selectChart vía el binding de selección.
- Filtros (Group/Category/Sources) → Menu nativo en la toolbar con checkmarks
  e icono con badge cuando hay filtros activos. Desaparecen las pills apiladas.
- iPhone: toolbarTitleMenu — el título es el selector de chart (patrón
  Files/Freeform); fuera el carrusel de 14 chips. Periodo como segmented picker
  estilo Stocks encima de la gráfica.
- Slider de Performance integrado en el toolbar del chart (iPad) y como card
  compacta (iPhone).
- Eliminadas ~300 líneas de selectores custom (tiles, pills, chips).

Verificado con UITest nuevo (testChartSidebarSelection) en simulador iPad Pro:
tap en Allocation/Contributions/Rolling 12M carga cada chart, y el filtro de
periodo de Rolling reacciona. ScreenshotMode ahora activa debugPremiumOverride
para poder testear charts premium.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-08 22:45:31 +02:00
alexandrev-tibco 0e0aec6bb9 Charts: fix selección iPad/charts bloqueadas, filtro Rolling 12M, KPIs específicos y botón compartir
Bugs:
- Charts bloqueadas (intermitente) y tiles que no responden: el observer Combine
  registraba el estado como actualizado ANTES de llamar a updateChartData, cuyo
  guard de reentrada descartaba la llamada en silencio — reintentarlo era no-op
  permanente. Ahora el bookkeeping vive dentro de updateChartData, las llamadas
  reentrantes se colean en vez de descartarse, y selectChart computa en síncrono
  (pulsar un tile es determinista y re-pulsar actúa de retry).
- Paywall sheet: movida del Group que cambia con el size class a cada layout —
  en NavigationSplitView no llegaba a presentarse (tiles premium 'no hacían nada').
- Rolling 12M: el filtro de periodo no filtraba — el cálculo necesita histórico
  completo para el lookback, pero ahora la salida respeta selectedTimeRange.

KPIs:
- El header de iPad muestra KPIs específicos del chart activo (los mismos que su
  stats row) en vez de las 5 métricas de cartera fijas; las stats rows dentro de
  las cards se ocultan en regular width para no duplicar (YoY opta por quedarse
  al no tener equivalente en el header).

Compartir:
- Botón de compartir por chart (toolbar iPad + navbar iPhone): renderiza la
  gráfica en una card con branding (BrandMark, KPIs, tagline, QR al App Store
  con ct=chart_share) vía ImageRenderer y abre el share sheet con imagen + link.
- drawingGroup() se desactiva durante el export (ImageRenderer no rasteriza
  capas Metal — salían en blanco). Strings nuevas en 7 idiomas.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-08 16:32:42 +02:00
alexandrev-tibco a830a5f40c Intent paramétrico Log Value + frases de Siri localizadas en 7 idiomas
LogSnapshotIntent: 'Registra el saldo de Indexa' por voz o desde Atajos, sin
abrir la app (openAppWhenRun=false). SourceEntity expone las fuentes a
Siri/Atajos desde el mirror del App Group; el valor entra por la misma cola
que el Share Extension y se materializa como Snapshot al instante (el intent
corre en el proceso de la app). Diálogo de confirmación con formato de divisa
de la fuente.

AppShortcuts.xcstrings: las 7 frases de los 3 shortcuts localizadas a
es-ES/de/fr/it/ja/pt-BR — Siri en español ya funciona. Strings de UI de
Atajos (títulos, parámetros, diálogos) añadidas a los 7 Localizable.strings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-08 16:11:18 +02:00
alexandrev-tibco 19b1408c55 1.4.2 (build 48): OCR de screenshots en Share Extension
El extension acepta ahora imágenes además de texto: al compartir un screenshot
del banco/broker, Vision reconoce el texto EN EL DISPOSITIVO (la imagen nunca
sale del teléfono), extrae los importes candidatos y rellena el más prominente
visualmente (en las UIs bancarias el balance es el número más grande). El resto
de candidatos se ofrecen como chips de un toque. Percentajes y años se filtran.

- ExtImageAmountScanner.swift: VNRecognizeTextRequest + ranking por altura de
  bounding box + dedup por valor
- ShareViewController: carga UIImage/URL/Data del provider, estado de escaneo,
  sección de candidatos
- Info.plist: NSExtensionActivationSupportsImageWithMaxCount=1
- Strings nuevas en los 7 idiomas (ext_scanning_image, ext_detected_amounts,
  ext_ocr_no_amounts)
- PredictionEngine: troceado ternario que agotaba el type-checker en Debug
- Bump a 1.4.2 build 48

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-08 15:59:54 +02:00
alexandrev-tibco a953af2da0 Firma manual de distribución en Release para app, widget y extension QuickUpdate
El archive fallaba en ValidateEmbeddedBinary con firma mixta (extension Manual
+ app/widget Automatic). Los tres targets usan ahora Apple Distribution con sus
perfiles App Store en Release; Debug sigue en Automatic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
2026-07-07 14:39:17 +02:00
alexandrev-tibco cef5924f16 1.4.1 (build 47): labels en charts, Share Extension Quick Update y clipboard auto-advance
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-07-04 07:40:30 +02:00
alexandrev-tibco 386f4ff265 Actualización sin cambiar de app: Share Extension "Quick Update" + clipboard auto-advance
El dolor: para actualizar cada source hay que saltar a la app del banco, copiar/
memorizar el valor y volver. iOS no permite ventanas flotantes editables (PiP es
solo vídeo), así que se resuelve con las dos vías sancionadas:

Share Extension (target nuevo PortfolioJournalQuickUpdate):
- Al compartir texto desde CUALQUIER app aparece "Portfolio Journal": mini-form
  sobre la app anfitriona con el importe pre-parseado del texto compartido y la
  siguiente source pendiente del mes preseleccionada. Guardar → sigues en el banco.
- Sin Core Data en la extensión: la app publica un espejo de sources activas en el
  App Group (SharedQuickUpdateSync.refreshMirror) y la extensión encola
  PendingQuickUpdate; la app los convierte en Snapshots reales al activarse
  (ingestPending). ExtSharedBridge duplica el contrato (keep-in-sync comentado).
- Parser de importes tolerante a locales (12.345,67 € / $1,234.56 / 1234,5).
- UI localizada en los 7 idiomas (lproj propios de la extensión).
- pbxproj: target app-extension completo (sync group, embed, dependency, configs)
  replicando el patrón del widget. Entitlements solo con App Group.

Clipboard auto-advance en Quick Update:
- Al volver a la app con un importe copiado: banner de un tap "Pegar X en <source>"
  que rellena la siguiente source vacía y avanza el foco a la siguiente.
- Badge NEXT en la fila activa; no repite sugerencias del mismo clipboard.

Script de upload: -allowProvisioningUpdates + API key también en el archive para
que el bundle id nuevo de la extensión se registre automáticamente.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-07-03 23:21:55 +02:00
alexandrev-tibco f7d4fdbe2d Charts: puntos + labels de valor en todas las gráficas de líneas
- Nuevo ChartValueLabels.swift: ChartValueBubble (cápsula) y ChartSelectionCard
  (tarjeta multi-serie), con umbral compartido (<=12 puntos → labels SIEMPRE
  visibles; las gráficas cortas aportan mucho más con los valores a la vista)
- PointMark añadido donde faltaba (Compare, Year vs Year, Drawdown, Volatility);
  agrandado donde ya existía (Evolution, Prediction, Period vs Period, Rolling)
- Selección por tap/arrastre (chartXSelection) en las 8 gráficas: RuleMark + burbuja
  prominente (una serie) o tarjeta con el valor de cada serie (multi-serie)
- Evolution reutiliza su scrubbing existente y ahora muestra burbuja en el punto
- Period vs Period: labels A arriba / B abajo para evitar solapes
- Drawdown/Volatility migrados de Chart(data:) a Chart{ForEach} para poder añadir
  marcas de selección

Verificado en iPad Pro 13" (labels visibles en Evolution y Period vs Period).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-07-03 23:09:35 +02:00
alexandrev-tibco 7c92ea36c0 1.4.1 (build 46): fix cambio de Chart Type roto por el cache de snapshots
El fetch del cache dependía del timeRange activo (monthsLimit = timeRange.months),
así que al reutilizarlo entre chart types/rangos (optimización del build 45) los
charts recibían historia truncada — Year vs Year o "All" con solo 12 meses, charts
vacíos o incompletos al cambiar de tipo.

Fix: el cache siempre guarda la historia completa (maxHistoryMonths) y el recorte
por rango se hace en memoria (el filtro por cutoffDate ya existía justo después).
Bonus: hiddenHistoryMonths ahora se calcula contra la ventana completa (teaser más
preciso).

Verificado en iPad Pro 13" simulador: Evolution 12M → All → Compare →
Period vs Period → Evolution, todos renderizan con datos.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-07-03 16:35:42 +02:00
alexandrev-tibco a0710a6f76 1.4.1 (build 45): rediseño Charts iPad, zoom, sin Focus Mode, perf fixes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-07-03 10:28:56 +02:00
alexandrev-tibco db844d5e80 Rediseño Charts iPad, zoom en gráficas, eliminación de Focus Mode y fixes de rendimiento
Charts iPad (rediseño):
- Fila de KPIs sobre el chart: Total Value, Period Return, CAGR, Volatility y Max
  Drawdown del rango activo (nuevo ChartsViewModel.portfolioMetrics, calculado sobre
  los TOTALES MENSUALES AGREGADOS — pasar snapshots multi-source crudos a
  calculateMetrics producía KPIs absurdos)
- Sidebar (248pt) con tiles de chart type en grid 2 col agrupados por sección:
  Overview / Analyze / Risk / Forecast, con candado premium y accesibilidad
- Selector de periodo como Picker segmentado nativo en la cabecera del chart
  (fuera del sidebar); título + descripción del chart visibles
- Eliminado sheet de paywall duplicado del layout iPad

Zoom (todas las series temporales):
- Nuevo ChartZoom.swift: pinch + botones +/- y reset (HIG: el gesto nunca es el
  único camino), chartScrollableAxes + chartXVisibleDomain al hacer zoom
- Integrado en Evolution, Contributions, Rolling 12M, Cashflow, Drawdown y Volatility

Focus Mode eliminado (todo siempre disponible):
- Fuera el toggle de Dashboard/Charts/Settings/Onboarding y los 6 @AppStorage
- Todos los chart types siempre visibles; variantes completas en SourceDetail/SourceList
- Home: Total Portfolio Value muestra SIEMPRE el cambio desde el último check-in
- PeriodReturnsCard siempre visible

Rendimiento y bugs (de la auditoría):
- El cache de snapshots ya no se invalida al cambiar solo de chart type/rango/breakdown
- calculateAnnualizedGrowth y el forecast a 12 meses ahora componen (la aproximación
  lineal sobreestimaba con historiales cortos)
- Drawdown sin force unwrap; simulador: rama muerta reemplazada por preservación
  real de las asignaciones simuladas del usuario
- UITest de captura de Charts con manejo del alert de notificaciones

12 strings nuevas localizadas en los 7 idiomas.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-07-03 10:17:10 +02:00
alexandrev-tibco d0a51d0162 1.4.1 (build 44): growth y monetización
Incluye respecto a build 43:
- Fix crítico: QR viral y enlace de Settings apuntaban a ids inexistentes (404)
- Paywall multi-plan (sub anual con trial + lifetime), 6 benefits, teaser de historia
- Rating prompt acelerado, sample data por defecto, streak protection, App Intents
- Instrumentación de onboarding/shares/backups

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-07-02 22:55:49 +02:00
alexandrev-tibco 197bddcd7e Growth y monetización: fixes críticos, paywall multi-plan, teasers y App Intents
CRÍTICO — enlaces del App Store rotos (adquisición viral = 0):
- GoalShareService.appStoreURL apuntaba a id6744983373 (404): el QR de TODAS las
  imágenes compartidas llevaba a una página muerta. Corregido a id6757678318.
- SettingsView enlazaba id6741412965 (una novela romántica). Ahora usa la constante.

Monetización:
- IAPService multi-producto: sub anual (com.portfoliojournal.premium.annual, con
  intro offer/trial) + lifetime como ancla. isPremium acepta cualquiera de los dos.
  Paywall con selector de plan (anual por defecto si existe; degrada a solo lifetime
  mientras el producto no esté creado en App Store Connect).
- paywallBenefits: añadidos Multiple Accounts y Family Sharing (diferenciadores).
- Teaser de historia bloqueada en Charts: "N meses más con Premium" en vez de
  truncar en silencio (FreemiumValidator.hiddenHistoryMonths + banner).
- Trigger de paywall de backups ahora instrumentado (logPaywallShown "backups").

Retención / adquisición:
- Rating velocity: prompt tras 2 check-ins y 30 días (antes 3 + 90 — en una app
  mensual eso era >3 meses sin poder pedir la primera review con ~0 ratings).
- Sample data ON por defecto en onboarding (skip ya no aterriza en dashboard vacío).
- Notificación de protección de streak el día 25 si el check-in del mes está pendiente.
- App Intents + AppShortcutsProvider: "Update my portfolio" / "Check my portfolio"
  vía Siri/Shortcuts/Spotlight.
- Instrumentación: onboarding_step/onboarding_skipped y content_shared (viral loop).
- ScreenshotMode (--screenshots) para capturas de marketing automatizadas.

ASO:
- 6 locales nuevos en metadata (en-GB, en-CA, en-AU, es-MX, fr-CA, pt-PT) reusando
  traducciones — 6 campos de keywords extra; pt-PT adaptado (reforma).
- Categoría secundaria: PRODUCTIVITY.
- 17 strings nuevas localizadas en los 7 idiomas.

Pendiente manual: crear la sub anual en App Store Connect (grupo de suscripción +
intro offer 7 días) con el id exacto com.portfoliojournal.premium.annual.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-07-02 22:12:32 +02:00
alexandrev-tibco 19e80c912b ASO tooling: compositores de marketing + captura UITest
- testCaptureScreenshots en PortfolioJournalUITests: captura tabs con datos demo,
  idioma vía SCREENSHOT_LANG (usa el modo --screenshots del commit siguiente)
- Scripts/aso: compositores de screenshots de App Store (frame_layout con
  perspectiva 3D + panorama multi-panel, variantes premium/tilt), layouts JSON
  iPhone/iPad, y asc.py (helper API App Store Connect para estado/screenshots)
- Fix test roto pre-existente: OnboardingQuickStartView.appSettingsURL restaurado

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-07-02 22:12:31 +02:00
alexandrev-tibco 9d56d420aa ASO: optimizar name/subtitle/keywords/promo ×7 y ampliar description EN
- Name: "…: Tracker" → keyword localizada de alto volumen (Net Worth/Patrimonio/
  Vermögen/Patrimoine/資産管理…) — el nombre es la señal de ranking #1
- Keywords ×7: fuera términos flojos/off-brand (log, monitor, watchlist, finance);
  dentro alto valor on-brand (retirement, assets, passive, compound, boglehead…),
  sin repetir palabras del name/subtitle (Apple ya las combina)
- Subtitle ×7: reordenado para cubrir asset classes + tracker sin duplicar el name
- Promotional text ×7: hook de privacidad + net worth + FIRE + charts (≤170)
- Description EN: añadidos los charts interactivos (Compare/What-If/Period vs Period)
  y Year vs Year con forecast

Todo validado a los límites de ASC (30/30/100/170). Pendiente subir con fastlane deliver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-07-01 14:46:18 +02:00
alexandrev-tibco 6651e59e22 1.4.1 (build 43): sync iCloud de monthly contributions y allocation targets
Incluye respecto a build 42:
- Migración de MonthlyContributionStore y AllocationTargetStore a CoreData (sync iCloud)
- Versionado por variables en Info.plist, fix bug .p8 del script de upload, .gitignore

NOTA: los campos CloudKit CD_monthlyContribution / CD_allocationTarget requieren deploy
del schema a producción para sincronizar en TestFlight.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-07-01 09:53:33 +02:00
alexandrev-tibco 9927d21b86 Sync monthly contributions y allocation targets vía iCloud (CoreData)
Ambos vivían en UserDefaults (device-local, no sincroniza). Migrados a atributos
CoreData en entidades ya syncable=YES, así que ahora sincronizan por CloudKit:

- InvestmentSource.monthlyContribution (Decimal, opcional)
- Category.allocationTarget (Double, opcional)

Los stores mantienen su API por UUID (0 cambios en call sites) pero ahora leen/escriben
del atributo CoreData vía fetch por id. migrateIfNeeded(context:) mueve una sola vez los
valores del diccionario UserDefaults legacy al modelo y borra la clave; enganchado en
CoreDataStack.loadPersistentStores junto a MonthlyCheckInStore.

DashboardLayoutStore se mantiene en UserDefaults a propósito (preferencia de UI por
dispositivo: el columnSpan solo aplica en iPad).

NOTA: requiere desplegar el schema CloudKit a producción (CD_monthlyContribution,
CD_allocationTarget) antes de que sincronice en TestFlight/App Store.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-07-01 09:28:37 +02:00
alexandrev-tibco 51223f7079 chore: versionado por variables, fix bug .p8 en script de upload y .gitignore
- Info.plist (app + widget): CFBundleShortVersionString -> $(MARKETING_VERSION),
  CFBundleVersion -> $(CURRENT_PROJECT_VERSION). Antes estaban hardcodeados, lo que
  obligaba a editar 3 sitios por release y causó el rebote de build (subía 41).
- archive_and_upload_appstore.sh: find_p8_file ahora asigna la variable global P8_PATH
  en vez de devolverla por $(...); el trap EXIT que limpia el .p8 temporal ya no corre
  en un subshell, así que el fichero sobrevive hasta el upload (antes xcodebuild fallaba
  en -exportArchive con "-authenticationKeyPath ... existing file").
- .gitignore nuevo: build artifacts, credenciales (.p8/.mobileprovision), basura macOS
  y documentos personales. Destrackeados .DS_Store y UserInterfaceState.xcuserstate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-06-30 17:10:59 +02:00
alexandrev-tibco 7a18dd8360 1.4.1 (build 42): contribución por snapshot, fixes Period vs Period y navegación iPad
- 151: contribución editable por snapshot en cualquier modo (quitado gate detailed)
  con diálogo de propagación al editar: solo este / adelante / atrás / todos
  (SnapshotRepository.propagateContribution, SnapshotFormViewModel.contributionChanged)
- 148: Period vs Period mostraba mal el último mes del period B — la agrupación usaba
  chartMonth(for:) que aplicaba el grace-period del check-in a snapshots históricos;
  ahora agrupa por mes calendario crudo
- 152: iPad Sources no saltaba entre fuentes — añadido .id() al SourceDetailView para
  recrear el StateObject al cambiar de selección
- 146/147: Year vs Year con forecast del año en curso (asterisco de estimado) y KPIs
  arriba / detalle debajo
- 145: card de contribución mensual en SourceDetailView
- Nuevas claves snapshot_contribution_propagate_* en los 7 idiomas

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
2026-06-30 17:01:50 +02:00
alexandrev-tibco 54dfd8a8e3 Show Compare and Period vs Period in calm mode (build 35)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 19:28:38 +02:00
alexandrev-tibco 12ab2d6009 Add 3 interactive chart types: Compare, What If, Period vs Period (build 34)
- Compare (free): multi-source overlay chart, Base 100 / Return % / Value modes,
  chip selector, reactive to time range filter
- What If (premium): allocation simulator with sliders per source, dual-line chart
  actual vs simulated indexed to 100, orange warning when weights ≠ 100%
- Period vs Period (free): date-picker-driven comparison of any two custom periods,
  return % normalized from month 1 = 0%, color-coded legend with period labels

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 15:32:57 +02:00
alexandrev-tibco 270edeb536 Migrate journal entries to CoreData for iCloud sync (build 33)
- Add JournalEntry CoreData entity (syncable=YES): id, monthKey, note, moodRaw, rating, completionTime, createdAt
- Rewrite MonthlyCheckInStore: CoreData as primary storage, same public API
- One-time migration from UserDefaults triggered after store loads
- MonthlyCheckInCard: @FetchRequest on JournalEntry — reactive to iCloud sync
- MonthlyCheckInView: onReceive NSManagedObjectContextObjectsDidChange to refresh @State vars on sync
- Stats cards: observe NSManagedObjectContextObjectsDidChange instead of UserDefaults
- ChartsViewModel.completedMonthKeys: remove MonthlyCheckInStore dependency (data-driven)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 15:05:53 +02:00
alexandrev-tibco 0b01d6f563 Fix iCloud sync: chart and next check-in date stale on secondary device (build 32)
- completedMonthKeys: remove UserDefaults-based MonthlyCheckInStore check (not synced via iCloud)
- MonthlyCheckInCard.effectiveLastCheckInDate: use max() instead of ?? to prefer newer synced snapshot date
- MonthlyCheckInView.lastCompletionDate: use max() of local + latest CoreData snapshot via @FetchRequest
- FirebaseService.logPaywallShown: fix parameter key to paywall_trigger (matches GA4 custom dimension)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 08:42:22 +02:00
alexandrev-tibco b48a47ce10 Release 1.4.0 (build 31): retención, quick update, streak, what's new
- 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>
2026-05-21 23:09:36 +02:00
alexandrev-tibco 773da6800b Release 1.3.1 (build 20): iCloud sync fix + sources search/filter
iCloud sync:
- Fix: deploy CloudKit schema to production so exports work
- Fix: PredictionCache marked syncable=NO (binary attr caused partial failures)
- Fix: forceExportToiCloud uses createdAt+1ms for guaranteed persistent history
- Fix: auto-deduplication on CloudKit import (processRemoteChanges)
- Add: NSPersistentHistoryTrackingKey always enabled regardless of CloudKit state
- Add: cloudKitForceReload notification so repositories re-fetch on remote changes

Sources UI:
- Add: horizontal category filter chips in SourceListView
- Add: search toggle button with animated TextField

Other:
- Add: ITSAppUsesNonExemptEncryption=NO in Info.plist (skip manual compliance)
- Add: fastlane submit lane with run_precheck_before_submit=false
- Update: release notes all locales for 1.3.1
- Update: fastlane API key via pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 22:19:33 +02:00
alexandrev-tibco 8acac3d529 Release 1.3.0: fastlane manual signing + release notes all locales
- Fastfile: manual signing with PortfolioJournal AppStore profiles
- Fastfile: add metadata, beta and release lanes
- Deliverfile: set app_version 1.3.0
- Release notes updated in 7 locales (en-US, es-ES, de-DE, fr-FR, it, ja, pt-BR)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 11:20:22 +02:00
alexandrev-tibco a9be27fa3a Disable script sandboxing for PortfolioJournal target (needed for Crashlytics dSYM upload) 2026-05-01 09:45:22 +02:00
alexandrev-tibco b67bcc71b8 Fix Crashlytics dSYM script: correct INFOPLIST_PATH input file 2026-05-01 09:36:52 +02:00
alexandrev-tibco 94ed4d17eb Release 1.3.0: onboarding UX, charts paywall banner, re-engagement notifications, Crashlytics
- Crashlytics: add FirebaseCrashlytics framework + dSYM upload build phase
- Onboarding: useSampleData off by default, Add First Investment as primary CTA
- AddSourceView: contextual placeholder and footer explaining what a source is
- Charts: CompactPaywallBanner visible to free users on every visit
- Notifications: re-engagement (7d) and monthly check-in local notifications
- Paywall: decorative chart preview replacing static crown icon
- Localization: all new strings in en + es-ES
2026-05-01 09:26:37 +02:00
alexandrev-tibco 10f6d0ca20 Release 1.2.1: iCloud sync improvements + ASO multilingual metadata
iCloud sync:
- Force viewContext.refreshAllObjects() on remote change notifications so
  data from other devices is picked up immediately without app restart
- Call refreshFromCloudKit() on foreground to merge any changes made while
  the device was inactive
- Wait up to 10s for initial CloudKit sync on launch before showing onboarding
  (shows "Checking iCloud..." during the wait)
- New OnboardingICloudCheckView: shown on fresh installs with iCloud available,
  lets user restore from iCloud before starting onboarding from scratch

Localization:
- Added de, fr, it, ja, pt-BR lproj folders
- New iCloud onboarding strings in en + es-ES (+ button literals)

ASO metadata (fastlane):
- Updated en-US: new subtitle, keywords, description (fixed "no cloud sync"
  claim), promotional text, release notes
- Added full metadata for es-ES, de-DE, fr-FR, it, ja, pt-BR (63 files total)
- All keyword fields validated ≤100 Unicode chars

Infrastructure:
- Gemfile + Gemfile.lock for fastlane
- Scripts/archive_and_upload_appstore.sh for CI/CD

Version: 1.2.1 (build 7)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:54:35 +01:00
alexandrev-tibco 7cb5f92cf4 Remove Add Transaction feature and clean all related code
Deleted files:
- AddTransactionView.swift
- TransactionRepository.swift
- Transaction+CoreDataClass.swift

Cleaned files:
- SourceDetailViewModel: removed transactions published property,
  showingAddTransaction flag, transactionRepository dependency,
  addTransaction() and deleteTransaction() methods
- SourceDetailView: removed transactionsSection, TransactionRow struct,
  Add Transaction button from quickActions, sheet presentation
- InvestmentSource+CoreDataClass: removed transactions NSManaged property,
  transactionsArray, totalInvested, totalDividends, totalFees computed
  properties, and all transaction Core Data accessors
- SettingsViewModel: removed "Transaction" from resetAllData entity list
- SampleDataService: removed transactionRepository and sample transaction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 23:04:03 +01:00
alexandrev-tibco b17d8866a4 Fix charts cutting off at Dec 2025: remove overly strict post-completion filter
The filter excluded snapshots with snapshot.date > completionDate for that month.
The bug: when a check-in for January is marked complete in February, the stored
completion date is backdated to Jan 31 (min(endOfMonth, now)), but batch update
snapshots were dated today (Feb 20). effectiveMonth maps Feb 20 → January, but
Feb 20 > Jan 31 triggered the exclusion, hiding all 2026 data from charts.

The check was redundant — monthDate <= lastCompleted already ensures only
completed months are included. Removing it also correctly handles the existing
snapshots already saved with a late date.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 22:56:30 +01:00
alexandrev-tibco c94974546f Fix widget gap: forward-fill months and use reference month date for batch update
BatchUpdateView now accepts a saveDate parameter. When saving from a past-month
check-in, the call site passes referenceDate.endOfMonth so snapshots are dated
within the correct month instead of today. For the current month, Date() is used.

Widget forward-fills missing month buckets (trend and category series) using the
most recent earlier month's value, preventing gaps when a check-in is done in the
following month.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 22:51:40 +01:00
alexandrev-tibco e3ec0ddb25 Fix progress bar period to start at first day of interval month
Period now starts at startOfMonth of the first month in the interval,
not 30 days before the deadline. For monthly: Feb 1 → Feb 28.
For quarterly: Jan 1 → Mar 31. Formula: startOfMonth(nextDate - (interval-1) months).

Feb 20 with next check-in Feb 28: 19/27 days ≈ 70% instead of 0%.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 22:45:59 +01:00