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
This commit is contained in:
alexandrev-tibco
2026-09-12 13:08:27 +02:00
parent 163fd6026a
commit d8345a5b20
22 changed files with 8 additions and 2261 deletions
-112
View File
@@ -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.