Commit Graph

10 Commits

Author SHA1 Message Date
alexandrev-tibco a049e4f74b watch: el dia de hoy se resuelve al pintar, no al generar el envio
El iPhone marcaba isToday al construir el snapshot y el reloj se creia esa
marca para siempre. Como el snapshot sobrevive en el app group, un sabado
seguia enseñando la cena del miercoles: el dia que era cuando se genero.

Ahora cada dia viaja con su fecha real y el reloj busca hoy cuando dibuja. Si
el snapshot es de otra semana no hay "hoy" que enseñar, asi que en vez de
colar la comida de otro dia lo dice y pide abrir el iPhone. Para snapshots
guardados por versiones anteriores, que no traen fecha, solo se confia en el
indice del dia mientras el propio snapshot sea de esta semana.

Ademas el reloj pide datos al abrirse y el iPhone, en vez de responder con lo
ultimo que cacheo, reconstruye la semana desde el store — que es la otra mitad
del "no se actualiza": el cache podia ser de hace dias.

Y cuando no hay nada planificado se dice, que antes era un guion indistinguible
de una comida marcada como no planificada: "Hoy no hay nada planificado" para
el dia entero y "Sin planificar" por comida, en los 6 idiomas (el bundle del
reloj no tiene cadenas propias, viajan en el envio).

Refs #34, #35

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013su1ttRiMeMYxkZJ1Y3246
2026-09-12 13:21:21 +02:00
alexandrev-tibco d8345a5b20 Revert "hogar compartido: colaboracion entre cuentas sobre Firestore"
El hogar compartido cambia donde viven los datos, asi que se va a la 3.0 y la
2.x queda para correcciones. El trabajo sigue vivo en la rama 3.0.0 (163fd60).

Importante para esta rama: esto tambien retira el entitlement
com.apple.developer.applesignin, que sin la capability marcada en el App ID
habria roto la firma al subir la 2.1.1 a TestFlight.

This reverts commit 163fd60.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013su1ttRiMeMYxkZJ1Y3246
2026-09-12 13:08:27 +02:00
alexandrev-tibco 163fd6026a hogar compartido: colaboracion entre cuentas sobre Firestore
CloudKit sincroniza la base privada de un Apple ID: ni llega a Android ni deja
que dos cuentas editen el mismo plan (CKShare sigue sin existir en SwiftData).
El contenido de un hogar pasa por tanto a Firestore, y un dispositivo que entra
en un hogar construye el store local sin CloudKit — dos espejos escribiendo los
mismos objetos se pelean, que es justo lo que ya obligó a apagar el sync por
iCloud KV.

SwiftData sigue siendo el store local y el modo offline; HouseholdSyncService es
lo unico que habla con la red. Detecta cambios comparando una huella del
contenido de cada documento con la ultima sincronizada (el "shadow"), asi que no
hace falta instrumentar con updatedAt las treinta vistas que mutan modelos. Los
borrados van como tombstone: un borrado duro volveria desde cualquier miembro
que estuviera sin conexion.

Semanas y slots usan id derivado del contenido (2026-09-14, 5-dinner) para que
dos miembros que abren la misma semana escriban el mismo documento en vez de
crear dos, y para que los conflictos se resuelvan por slot y no por semana.

Incluye reglas de seguridad (solo miembros; los codigos de invitacion se pueden
leer por id pero no listar), pantalla de hogar en Ajustes con Sign in with Apple,
invitacion por codigo de 6 caracteres sin vocales ni 0/O/1/I, y la eleccion al
unirse entre llevarse los platos propios o adoptar los del hogar.

Fuera de esta fase: fotos de platos (necesitan Storage) y el cliente Android.

Refs #33

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013su1ttRiMeMYxkZJ1Y3246
2026-09-12 13:00:38 +02:00
alexandrev-tibco cfa9769a06 2.0.2: evento de fallo de compra + analytics off en UI tests
premium_purchase_failed con reason (cancelled/pending/unverified/error) en todas
las salidas no exitosas de StoreManager.purchase. Hasta ahora un usuario que se
echaba atras y uno al que le fallaba la compra eran indistinguibles: los dos
dejaban un premium_upgrade_tapped huerfano. El caso de Aleman (6 taps en 2 dias,
cero compras) no se puede diagnosticar sin esto.

Los UI tests dejan de mandar eventos a GA4 de produccion: launch argument
UITEST_DISABLE_ANALYTICS que hace no-op logEvent y apaga la recoleccion de
Firebase, para cortar tambien los automaticos (first_open, session_start,
screen_view). Ya habian contaminado los datos: los eventos de pasos 5/6 bajo la
version 2.0 eran ejecuciones de test, no usuarios.

Detalle que costo encontrar: setAnalyticsCollectionEnabled persiste entre
lanzamientos, asi que se escribe siempre (!isDisabled) y no solo al apagar. Si
no, un unico test dejaba analytics muerto para siempre en esa instalacion.
Verificado por pares de lanzamientos: con flag acaba en disabled, sin flag
vuelve a enabled.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-13 14:02:35 +02:00
alexandrev-tibco d957921fe3 2.0: sync CloudKit en tiempo real vía push silencioso
NSPersistentCloudKitContainer solo sincronizaba de forma oportunista (sin
push), por lo que un plato nuevo tardaba mucho o no aparecía en el otro
dispositivo. Se activa el push de CloudKit:

- aps-environment en MealMood.entitlements vía variable APS_ENVIRONMENT
  (development en Debug, production en Release) para no romper TestFlight.
- AppDelegate con registerForRemoteNotifications() + manejo del push
  silencioso; el mirror de SwiftData importa al ser despertado.
- (Capability Push Notifications habilitada en el App ID por API.)

Pendiente: regenerar el provisioning profile AppStore con Push antes de
la build 63, y validar el sync push en iPhone+iPad vía TestFlight.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-24 15:47:46 +02:00
alexandrev-tibco 4ffa15b06a 2.0: CloudKit private-database sync replaces KV snapshot sync
Foundation for 2.1 family sharing (CKShare needs these models + container).
SwiftData cannot share across Apple IDs today, so 2.0 ships true multi-device
sync for the same account instead:

- Models made CloudKit-compatible: dropped @Attribute(.unique) on all 5,
  inline defaults on every attribute, WeekPlan.slots stored as optional
  relationship (name preserved → lightweight migration) with non-optional
  slotList facade; ~73 call sites renamed.
- Container: cloudKitDatabase .automatic, falling back to the local-only
  store when CloudKit is unavailable; failures recorded to Crashlytics.
- Entitlements: iCloud CloudKit service (container already existed);
  remote-notification background mode for push-driven sync.
- Legacy KV snapshot sync (ICloudSyncService) stays inert when CloudKit is
  active — kept only for 1.x devices.
- DeduplicationService collapses cross-device duplicates deterministically on
  launch (settings singleton, default tags with tagId remapping, same-week
  plans).
- Note: AppSettings.isPremium now syncs across same-Apple-ID devices; StoreKit
  (PremiumSyncService + Transaction.updates) remains the source of truth and
  reconciles on every launch/foreground.

All 21 unit tests pass, including ICloudSyncPremiumIsolationTests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-12 18:13:45 +02:00
alexandrev-tibco ce88d7b265 2.0: weekly shopping list from planned dishes
- ShoppingItem model (per-week lines; dish-derived or free-text) + schema.
- ShoppingListService.reconcile mirrors planned dishes' ingredients into the
  list, preserving check-off state and user items; plain-text export helper.
- ShoppingListView: grouped by dish, check-off, swipe-delete, free-text adds,
  clear-checked, ShareLink export, and inline on-device "Generate" for planned
  dishes without ingredients (paywall after 3 free generations,
  source=ingredient_generation).
- Home toolbar: cart entry point (all users) + sheet.
- Analytics: shopping_list_opened, shopping_item_added, ingredients_generated.
- Localized in all 6 languages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
2026-07-12 17:47:41 +02:00
alexandrev-tibco 7da7995538 Fix SwiftData migration crash on schema upgrades
Add = false/true defaults to all non-optional Bool properties so
SwiftData's lightweight migration knows what value to assign existing
rows. Replace try! ModelContainer with crash-safe do-catch that resets
the store on migration failure instead of crashing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 11:24:56 +02:00
alexandrev-tibco 110807d239 1.0.5: Eating out slots, rule violations panel, Crashlytics, search bar fix
- Eating out: tap any empty slot → "Mark as eating out"; shows teal indicator,
  skipped by auto-assign, counts as complete, tap again to unmark
- Rule violations panel: access via wand long-press context menu when conflicts exist;
  shows all isRuleOverridden slots with Fix (clear) or Ignore (acknowledge) actions
- Firebase Crashlytics integrated: CrashlyticsService + dSYM upload build phase,
  isPremium property tracked per session
- PremiumSyncService: extracted premium state machine, StoreKit Transaction.updates
  listener, isPremium no longer synced via iCloud to avoid stale state
- StoreManager: analytics on purchase/restore, bundle ID fallback for product ID lookup
- Search bar contrast bug fixed: TextField now has explicit foreground color for dark mode
- WelcomeStepView: redesigned onboarding welcome screen with week preview
- Version bump: 1.0.5 build 22

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 09:48:20 +02:00
alexandrev-tibco e593453abd Version casi lista 2026-02-17 13:34:02 +01:00