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 commit163fd60. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013su1ttRiMeMYxkZJ1Y3246
This commit is contained in:
@@ -1,112 +0,0 @@
|
||||
# Hogar compartido (2.2) — diseño
|
||||
|
||||
Colaboración entre cuentas distintas y acceso desde Android. Issue #33.
|
||||
|
||||
## Por qué no CloudKit
|
||||
|
||||
El store es hoy `NSPersistentCloudKitContainer` (SwiftData con
|
||||
`cloudKitDatabase: .automatic`), que sincroniza la base de datos **privada** de
|
||||
un Apple ID entre los dispositivos de esa persona. Dos cosas lo descartan aquí:
|
||||
|
||||
- No existe en Android.
|
||||
- Compartir entre cuentas exige `CKShare`, que SwiftData no expone (ya se
|
||||
intentó en la 2.0 y se aparcó a la 2.1).
|
||||
|
||||
## Arquitectura
|
||||
|
||||
```
|
||||
┌──────────────────────────┐
|
||||
iOS / iPadOS │ SwiftData (store local) │ ← toda la UI sigue leyendo de aquí (@Query)
|
||||
└────────────┬─────────────┘
|
||||
│ HouseholdSyncService (espejo bidireccional)
|
||||
┌────────────┴─────────────┐
|
||||
│ Firestore (el hogar) │ ← fuente de verdad cuando hay hogar
|
||||
└────────────┬─────────────┘
|
||||
│
|
||||
Android / web │ PWA (fase 2), lee y escribe el mismo hogar
|
||||
```
|
||||
|
||||
SwiftData **no** se sustituye: sigue siendo el store local y el modo offline.
|
||||
Firestore es la fuente de verdad del contenido del hogar, y el servicio de sync
|
||||
mantiene ambos lados alineados.
|
||||
|
||||
### CloudKit y Firestore no conviven
|
||||
|
||||
Dos sincronizaciones sobre los mismos objetos se pelean — ya pasó con el sync
|
||||
por iCloud KV, y por eso existe `CloudSyncRuntime.isCloudKitActive`. Regla:
|
||||
|
||||
- Sin hogar → como hoy: CloudKit activo, Firestore inactivo.
|
||||
- Con hogar → contenedor local (`cloudKitDatabase: .none`) y Firestore manda.
|
||||
|
||||
El contenedor se decide en el arranque, así que al entrar o salir de un hogar
|
||||
la app pide relanzarse. Es un corte visible, pero la alternativa (dos orígenes
|
||||
escribiendo los mismos objetos, con ecos entre ellos) es una fuente de pérdida
|
||||
de datos.
|
||||
|
||||
## Modelo en Firestore
|
||||
|
||||
```
|
||||
users/{uid}
|
||||
householdId, displayName, updatedAt
|
||||
|
||||
invites/{code} ← resuelve código → hogar sin leer todos los hogares
|
||||
householdId, createdBy, expiresAt
|
||||
|
||||
households/{householdId}
|
||||
name, createdAt, createdBy, memberIds: [uid], ownerId
|
||||
members/{uid} displayName, role: owner|member, joinedAt
|
||||
dishes/{uuid} name, descriptionText, tagIds[], ingredients[], isPriority,
|
||||
fixedDayOfWeek, fixedMealType, updatedAt, updatedBy, deletedAt?
|
||||
tags/{uuid} name, nameEN, color, reglas…, updatedAt, updatedBy, deletedAt?
|
||||
weekPlans/{yyyy-MM-dd} weekStartDate, userRating, includeWeekendsOverride,
|
||||
mealTypesOverrideRaw, updatedAt, updatedBy
|
||||
slots/{day}-{meal} dishId, secondaryDishId, isEatingOut, isSkipped,
|
||||
isRuleOverridden, isRuleIgnored, updatedAt, updatedBy
|
||||
shoppingItems/{uuid} weekStartDate, title, dishId, isChecked, isDismissed,
|
||||
sortOrder, updatedAt, updatedBy, deletedAt?
|
||||
```
|
||||
|
||||
### IDs deterministas donde importa
|
||||
|
||||
Semanas y slots usan id derivado del contenido (`2026-09-14`, `5-dinner`) en vez
|
||||
de UUID. Si dos miembros abren la misma semana a la vez, ambos escriben el mismo
|
||||
documento en lugar de crear dos. Es la misma clase de duplicado que costó el
|
||||
crash de arranque de la 2.0, pero resuelta en el origen.
|
||||
|
||||
Platos, etiquetas y líneas de la compra conservan su UUID: los crea una persona
|
||||
concreta y no hay id natural.
|
||||
|
||||
## Conflictos
|
||||
|
||||
Last-write-wins por **documento**, con `updatedAt` de servidor
|
||||
(`FieldValue.serverTimestamp()`) y `updatedBy` para poder depurar. La
|
||||
granularidad importa: el documento es el slot, no la semana, así que dos
|
||||
personas pueden rellenar martes y jueves a la vez sin pisarse.
|
||||
|
||||
Los borrados son tombstones (`deletedAt`), no borrados duros: sin ellos, un
|
||||
dispositivo offline que no vio el borrado recrearía el plato al volver.
|
||||
|
||||
## Alcance de la fase 1
|
||||
|
||||
Dentro:
|
||||
|
||||
- Cuentas (Sign in with Apple + Google) y hogar con invitación por código.
|
||||
- Sync de platos, etiquetas, semanas, slots y lista de la compra.
|
||||
- Desactivación de CloudKit al entrar en un hogar.
|
||||
|
||||
Fuera, a propósito:
|
||||
|
||||
- **Fotos de platos**: `photoData` va a `@Attribute(.externalStorage)` y un
|
||||
documento de Firestore tope a 1 MB. Van a Firebase Storage en fase 2.
|
||||
- **Ajustes personales** (`AppSettings`): no se comparten. Idioma, horas de
|
||||
calendario o estilo de export son de cada persona. Lo que sí es del hogar
|
||||
(días y comidas planificadas) viaja en el propio `weekPlan`.
|
||||
- **PWA de Android**: fase 2, sobre este mismo modelo.
|
||||
|
||||
## Premium
|
||||
|
||||
Crear un hogar requiere premium. Los invitados entran sin premium — es el gancho
|
||||
del plan familiar, y quien invita ya paga. El resto de features premium siguen
|
||||
bloqueadas para el invitado.
|
||||
|
||||
`isPremium` sigue viniendo solo de StoreKit y nunca se sincroniza, igual que hoy.
|
||||
Reference in New Issue
Block a user