Desactiva auto-dedup (pérdida de datos cross-device) + forward-fill en gráficas de evolution

CRÍTICO: el auto-dedup de build 68 borraba entidades padre con regla Cascade
(Account/Category). Al propagarse el delete por CloudKit, otros dispositivos
aplicaban la cascada al importar ANTES de la reasignación de hijos → wipe de
sources/snapshots. Vació iPhone y iPad. Quitadas las llamadas automáticas a
cleanupLogicalDuplicates() en processRemoteChanges y en la carga; el UUID-dedup
seguro se mantiene. Métodos quedan definidos para un rediseño CloudKit-safe.

Gráficas: forward-fill por source en evolution/allocation/period para que el
total de cada mes refleje el último valor de CADA source (no solo las
actualizadas ese mes) y el último punto coincida con el total del portfolio.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2p3gUZRNWW388rWFRjiU7
This commit is contained in:
alexandrev-tibco
2026-07-22 19:58:13 +02:00
parent 20b8a32ea3
commit 99dc855467
3 changed files with 83 additions and 50 deletions
@@ -283,21 +283,33 @@ class DashboardViewModel: ObservableObject {
return EvolutionSummary(evolutionData: [], categorySeries: [], categoryTotals: [:])
}
let sortedSnapshots = snapshots.sorted { $0.date < $1.date }
let groupedByMonth = Dictionary(grouping: sortedSnapshots) { snapshot -> DateComponents in
let components = Calendar.current.dateComponents([.year, .month], from: snapshot.date)
let calendar = Calendar.current
let groupedByMonth = Dictionary(grouping: snapshots) { snapshot -> DateComponents in
let components = calendar.dateComponents([.year, .month], from: snapshot.date)
return DateComponents(year: components.year, month: components.month)
}
let sortedMonthKeys = groupedByMonth.keys.sorted {
(calendar.date(from: $0) ?? .distantPast) < (calendar.date(from: $1) ?? .distantPast)
}
var evolution: [(date: Date, value: Decimal)] = []
evolution.reserveCapacity(groupedByMonth.count)
evolution.reserveCapacity(sortedMonthKeys.count)
var series: [(date: Date, valuesByCategory: [UUID: Decimal])] = []
series.reserveCapacity(groupedByMonth.count)
series.reserveCapacity(sortedMonthKeys.count)
var categoryTotals: [UUID: Decimal] = [:]
for (key, monthSnapshots) in groupedByMonth {
// Forward-fill per source: carry each source's last known value into later
// months where it has no new snapshot, so a month's total reflects EVERY
// source's latest value (not only the ones updated that month). This keeps
// the most-recent point equal to the dashboard total, which sums each
// source's latest snapshot regardless of month.
var currentValueBySource: [UUID: Decimal] = [:]
var categoryBySource: [UUID: UUID] = [:]
for key in sortedMonthKeys {
var latestBySource: [UUID: Snapshot] = [:]
for snapshot in monthSnapshots {
for snapshot in groupedByMonth[key] ?? [] {
guard let sourceId = snapshot.source?.id else { continue }
if let existing = latestBySource[sourceId] {
if snapshot.date > existing.date {
@@ -307,27 +319,29 @@ class DashboardViewModel: ObservableObject {
latestBySource[sourceId] = snapshot
}
}
// Apply this month's updates on top of the carried-forward state.
for (sourceId, snapshot) in latestBySource {
currentValueBySource[sourceId] = snapshot.decimalValue
if let categoryId = snapshot.source?.category?.id {
categoryBySource[sourceId] = categoryId
}
}
var total: Decimal = 0
var valuesByCategory: [UUID: Decimal] = [:]
for snapshot in latestBySource.values {
let value = snapshot.decimalValue
for (sourceId, value) in currentValueBySource {
total += value
if let categoryId = snapshot.source?.category?.id {
if let categoryId = categoryBySource[sourceId] {
valuesByCategory[categoryId, default: 0] += value
categoryTotals[categoryId, default: 0] += value
}
}
let date = Calendar.current.date(from: key) ?? Date()
let date = calendar.date(from: key) ?? Date()
evolution.append((date: date, value: total))
series.append((date: date, valuesByCategory: valuesByCategory))
}
evolution.sort { $0.date < $1.date }
series.sort { $0.date < $1.date }
return EvolutionSummary(
evolutionData: evolution,
categorySeries: series,