Commit Graph

114 Commits

Author SHA1 Message Date
alexandrev-tibco 41cb8b4c50 export: branding en los tres estilos (logo, wordmark degradado, tagline)
- Logo real de la app en el header de los 3 estilos (antes solo en el
  default, y pequeno) y en el footer junto a un wordmark MealMood en
  degradado coral->verde con tagline localizada en cursiva
- "Powered by MealMood" y "Scan me" estaban hardcodeados en ingles;
  ahora share_footer_tagline y share_footer_scan en 6 idiomas
- Titulo del header default con el mismo degradado; QR con borde coral

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-09-01 13:08:00 +02:00
alexandrev-tibco e5252a3fd8 export: vista previa con selector de estilo en la propia pantalla de export
- WeekExportPreviewSheet: preview renderizado del export con picker
  segmentado de los 3 estilos (cachea cada render); compartir usa el
  estilo elegido sin tocar el predeterminado (salvo primera vez)
- Sustituye al WeekExportStylePickerSheet de thumbnails y al doble
  sheet picker+share de HomeView; strings nuevas en 6 idiomas

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-09-01 13:06:21 +02:00
alexandrev-tibco 666731e280 2.0.5: build 73 subida a TestFlight
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-09-01 11:46:38 +02:00
alexandrev-tibco 27b061d4fb 2.0.5: bump de version (app + widget, build 73 en widget)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-09-01 11:43:37 +02:00
alexandrev-tibco a0f9fa63d3 platos: segundo plato por comida (ensalada + salmon)
- MealSlot.secondaryDishId (opcional, aditivo CloudKit); se limpia al
  sustituir/quitar/vaciar/marcar comer fuera y viaja en swaps, copia de
  semana anterior, undo y snapshot KV de iCloud (retrocompatible)
- Picker de hueco lleno: toggle "anadir como segundo plato" + boton para
  quitarlo; HomeViewModel.assignSecondaryDish/removeSecondaryDish
- Se muestra "Plato + Segundo" en calendario, widget y export; la lista
  de la compra incluye los ingredientes del segundo; stats lo cuentan
- Evento analytics secondary_dish_added; strings en 6 idiomas

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-09-01 11:37:23 +02:00
alexandrev-tibco 7193234024 reglas: plato fijo por dia y comida (ej. tortilla los viernes de cena)
- Dish.fixedDayOfWeek/fixedMealType (opcionales, aditivos para CloudKit)
- Toggle + pickers de dia/comida en el formulario de plato
- AutocompleteEngine: pre-pasada que asigna los platos fijos a su hueco
  antes del bucle MRV, de modo que cuentan para el resto de reglas
- Evento analytics dish_fixed_slot_set; strings en 6 idiomas

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-09-01 11:22:34 +02:00
alexandrev-tibco 47daab9e51 platos: paywall inmediato al llegar al limite y orden alfabetico
- attemptAddDish/attemptCreateDish: el limite gratuito se comprueba en el
  tap de anadir plato (DishListView y los 3 puntos de Home) y muestra el
  DishLimitModal -> paywall directamente; antes solo se comprobaba al
  guardar el formulario y el usuario podia rellenarlo entero para nada
- DishLimitModal compartido (era private de DishFormView) y
  PaywallPresentation promovido a PremiumView.swift; DishListView usa
  sheet(item:) tambien para el banner (source dish_counter vs dish_limit)
- Boton en la toolbar de Mis Platos que alterna orden alfabetico
  (persistido en dish_list_alphabetical_sort); borrado por swipe corregido
  para usar la lista mostrada

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-09-01 11:18:34 +02:00
alexandrev-tibco f51f975941 docs: CLAUDE.md — 2.0.4 en review, 2.0.3 publicada
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-08-26 14:41:19 +02:00
alexandrev-tibco 0f6abe5d7a 2.0.4: build 72 subida a TestFlight
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-08-26 14:36:13 +02:00
alexandrev-tibco 25bd0ec638 2.0.4: release notes en 6 idiomas
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-08-26 14:34:01 +02:00
alexandrev-tibco 855965565c 2.0.4: widget a build 72 antes de fastlane beta
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-08-26 14:33:18 +02:00
alexandrev-tibco 26d00f65e0 reviews: ask unico a premium existentes, funnel instrumentado y fix source unknown del paywall
- requestForExistingPremiumUser(): prompt nativo una sola vez para quien
  ya era premium antes del ask post-compra (evento review_prompt_premium_user)
- Funnel de semanas completadas emite review_funnel_shown/positive/negative/dismissed
- Paywall de Home via sheet(item:) con PaywallPresentation: el source viaja
  con la presentacion; antes dos @State separados hacian que a veces se
  registrara paywall_viewed con source=unknown (post_onboarding nunca llegaba)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-08-26 14:33:18 +02:00
alexandrev-tibco d41f72a84f docs: CLAUDE.md a version 2.0.4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-08-23 13:42:47 +02:00
alexandrev-tibco 9221ecb7e5 2.0.4: build 71 subida a TestFlight
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-08-23 13:42:30 +02:00
alexandrev-tibco 0af330a765 2.0.4: bump de version (app + widget, build 71 en widget)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-08-23 13:39:25 +02:00
alexandrev-tibco 57c52afc3c reviews: pedir valoracion tras la compra y deep link write-review en ajustes
- Tras una compra exitosa se pide la review nativa (una vez por usuario,
  con 2s de delay para que se vea el estado premium activo)
- El boton de ajustes abre el formulario de resena del App Store via
  ?action=write-review en vez de SKStoreReviewController, que Apple
  limita a 3 prompts/ano y el resto de veces no muestra nada
- Evento analytics review_prompt_post_purchase para medirlo

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
2026-08-23 13:36:06 +02:00
alexandrev-tibco 889a2b2d2f fastlane: regenerar README con las lanes nuevas
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-20 13:04:44 +02:00
alexandrev-tibco 8283948d5b 2.0.3: build 70 subida a TestFlight
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-20 12:42:36 +02:00
alexandrev-tibco d5eca0eeb0 2.0.3: renumerar 2.0.4 -> 2.0.3 (build 70)
La 2.0.3 nunca llego a la App Store, solo a TestFlight, asi que el numero queda
libre y evita saltarselo. El binario lleva la version dentro, y ASC solo adjunta
a una version las builds cuyo CFBundleShortVersionString coincide, asi que la
build 69 no se puede reetiquetar: hay que reconstruir.

Codigo identico al de la build 69, ya verificada en dispositivo.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-20 12:39:46 +02:00
alexandrev-tibco 573dde374d fastlane: feedback muestra la build de cada reporte y status lista las de TestFlight
Sin el numero de build no se puede saber si un reporte de crash viene de la
version que se acaba de subir o de la anterior. Justo el caso de hoy: el reporte
"Sigue fallando" era de la build 68, no de la 69.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-20 12:19:07 +02:00
alexandrev-tibco d2b30810a6 2.0.4: build 69 subida a TestFlight
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-20 09:26:56 +02:00
alexandrev-tibco 90eebcf953 2.0.4: bump de version y release notes (fix tap del microfono)
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-20 09:22:31 +02:00
alexandrev-tibco 39d3534c57 fix: el tap del microfono seguia aislado al main actor (2.0.3 build 68)
El fix anterior arreglo los permisos —el crash se movio de start a beginSession,
y de TCC a AVAudioNodeTap::CheckEmitBuffer— pero el tap seguia petando.

Mi error: quitar el acceso a self del closure no elimina la inferencia de
aislamiento. La inferencia viene de DONDE se escribe el closure, no de lo que
captura. Escrito dentro de un metodo @MainActor, seguia siendo main-actor
isolated, y el tap se dispara desde el hilo de audio en tiempo real.

Ahora el handler se construye en makeTapHandler, que es nonisolated, y se pasa a
installTap. La estructura se auto-verifica: quitarle nonisolated a makeTapHandler
no compila, porque installTap es nonisolated y no puede llamar a un metodo
aislado. El bug pasa de crash en produccion a error de compilacion.

Tests: testTapHandlerRunsOffTheMainThread invoca el handler desde una cola de
fondo, que es exactamente la condicion que trapeaba. No hace falta microfono; el
intento anterior con AVAudioEngine se saltaba siempre porque el simulador no
tiene entrada de audio utilizable.

Barrido del mismo patron en el resto de servicios @MainActor: CalendarService y
NotificationService usan las variantes async/await, que estan anotadas y no
tienen closures. SpeechDictationService era el unico sitio.

Lanes feedback y crashlog para leer los reportes de TestFlight desde la API:
spaceship apunta a v1/betaFeedbacks, que Apple ya retiro; el endpoint vivo es
v1/apps/<id>/betaFeedbackCrashSubmissions y el log viene inline en logText.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-20 09:21:52 +02:00
alexandrev-tibco 6ba1ef1430 2.0.3: build 68 subida a TestFlight
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-20 08:54:50 +02:00
alexandrev-tibco 42df1df917 2.0.3: bump de version y release notes (fix crash dictado)
Widget adelantado a build 68, que es el que generara fastlane.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-20 08:50:21 +02:00
alexandrev-tibco 896c78d260 fix: crash al dictar por closures de callbacks aislados al main actor
Crashlytics: dispatch_assert_queue_fail en com.apple.root.default-qos, dentro de
closure #1 in SpeechDictationService.start, llamado desde __TCCAccessRequest.

El proyecto compila en Swift 6 (activo desde febrero, antes del dictado). Los
callbacks de APIs ObjC pre-concurrency escritos dentro de este tipo @MainActor se
infieren aislados al main actor, y Swift 6 inserta una comprobacion de aislamiento
al entrar. TCC invoca el handler en cola de fondo, la comprobacion falla y mata el
proceso. El Task { @MainActor } de dentro no ayuda: el crash es antes de llegar.

Cuatro sitios con el mismo patron, tres enmascarados detras del primero porque
nadie pasaba de los permisos:

- requestAuthorization y requestRecordPermission -> wrappers nonisolated con
  continuation, el handler deja de estar aislado
- recognitionTask -> closure @Sendable explicito; solo cruzan valores Sendable
  porque SFSpeechRecognitionResult no lo es
- installTap -> captura el request local en vez de ir por self, que el hilo de
  audio en tiempo real no debe tocar estado del main actor

requestAuthorization llama al handler aunque el permiso ya este concedido, asi
que afectaba tambien a usuarios recurrentes: el dictado probablemente no ha
funcionado nunca desde la 2.0.

Los tests que acompanan NO reproducen el crash: el simulador entrega el callback
sincrono en el hilo principal y el codigo previo pasa igual. Comprobado
revirtiendo el fix. Queda pendiente validarlo en dispositivo real.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-19 21:28:29 +02:00
alexandrev-tibco a6dcb5605c fastlane: submit crea la version en App Store Connect si no existe
El envio de la 2.0.2 fallo con "could not find an editable version for 'IOS'":
deliver no crea el registro de version, y con la 2.0.1 ya en READY_FOR_SALE no
habia nada editable. Con la 2.0.1 no se noto porque la version ya existia.

ensure_editable_version usa app.ensure_version! de spaceship y avisa antes de
renumerar un borrador ajeno. Login de Connect API extraido a connect_api_app,
que ya compartia status.

Ademas shipped_app_version leia un path relativo y el directorio de trabajo de
la lane no es la raiz del repo, asi que devolvia vacio y ASC rechazaba el POST
por versionString ausente. Ahora es absoluto respecto al Fastfile y falla ruidoso
si no puede leerlo.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-13 14:36:36 +02:00
alexandrev-tibco 954a6b94d5 fastlane: submit deduce la version del Info.plist en vez de hardcodearla
Estaba fijada a 2.0.1, asi que en la siguiente release habria subido metadata a
la version equivocada.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-13 14:07:12 +02:00
alexandrev-tibco 87e8735621 2.0.2: bump de version y release notes
Version en los tres sitios (Info.plist de app y widget + MARKETING_VERSION) y
widget adelantado a build 67, que es el que generara fastlane.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-13 14:03:14 +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 95a1e3a133 fastlane: lanes submit y status
submit envia a review una build ya subida: la 2.0.1 build 66 ya estaba en
TestFlight, y release habria reconstruido mientras publish nunca envia.
Distribucion automatica (AFTER_APPROVAL) y sin tocar screenshots, que se heredan.

status lista el estado de cada version en App Store Connect. Sirvio para
confirmar que la 2.0.1 quedo en AFTER_APPROVAL y no en MANUAL como la 2.0.
Ojo: asc_api_key devuelve el hash de opciones de fastlane, no un token de
spaceship; hay que construir el token aparte.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-07 11:10:22 +02:00
alexandrev-tibco 55739c8f89 2.0.1: release notes en los 6 idiomas
Nota centrada en lo que el usuario nota de verdad: el resumen de la primera
semana planificada vuelve a mostrarse al terminar la configuracion inicial.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-06 17:45:26 +02:00
alexandrev-tibco dfe97e9553 2.0.1: build 66 subida a TestFlight (fix onboarding)
La primera subida la rechazo App Store Connect con 90062: el IPA salio con
CFBundleShortVersionString 2.0. MARKETING_VERSION del pbxproj no manda; la
version real esta hardcodeada en MealMood/Resources/Info.plist y en el
Info.plist del widget.

Corregidos ambos a 2.0.1 y alineados en build 66. Documentado en CLAUDE.md que
un cambio de version toca tres sitios y que fastlane solo automatiza el build
number de la app.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-06 08:22:56 +02:00
alexandrev-tibco d10517b356 2.0.1: bump MARKETING_VERSION 2.0 -> 2.0.1 (fix onboarding)
La 2.0 salio a la App Store el 2026-07-28 con los dos ultimos pasos del
onboarding sin mostrarse. 2.0.1 lleva ese fix.

El Info.plist del widget lleva la version hardcodeada y fastlane no la toca, asi
que se alinea a mano (2.0.1 / 65) antes de construir. Documentado en CLAUDE.md
para no volver a tropezar.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-06 08:15:54 +02:00
alexandrev-tibco 79423e3511 tests: target MealMoodUITests que recorre el onboarding completo
El bug de los pasos 5 y 6 era invisible a los tests unitarios: el contrato de
datos estaba bien, lo que fallaba era que la vista se desmontaba antes de pintar
el aha moment y el paywall. Hace falta recorrer la UI de verdad.

Anade el target MealMoodUITests (solo simulador, sin firma) y un test que hace
el onboarding entero en instalacion limpia y comprueba que Week Ready y el
paywall aparecen antes de la Home.

Los CTA del onboarding llevan ahora accessibilityIdentifier para que el test no
dependa de los textos, que estan en 6 idiomas.

Validado en los dos sentidos: pasa con el fix, y revirtiendo temporalmente
markFinished: false falla en la asercion de Week Ready. Suite completa: 27 tests.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-05 23:06:04 +02:00
alexandrev-tibco 9f5db245a9 fastlane: lane refresh_dsyms para subir dSYMs a Crashlytics
La build phase de Xcode solo sube el dSYM de la compilacion local. No habia red
de seguridad, asi que un fallo del script deja los crashes sin simbolizar.

refresh_dsyms baja los dSYM de App Store Connect y los manda a Crashlytics. Se
ejecuta aparte de beta a proposito: beta usa skip_waiting_for_build_processing,
asi que justo tras el upload los dSYM aun no existen en ASC.

Dos detalles que salieron al ejecutarla de verdad:

- upload_symbols_to_crashlytics busca el binario en ./Pods, pero Firebase entra
  por SPM y vive en DerivedData. Se resuelve con un glob.
- ASC no tiene dSYMs para la build 64 (normal sin bitcode: Apple no recompila
  nada). Sin fallback la lane seria un no-op permanente, asi que tira de los
  dSYM del ultimo archive local (app + widget).

Verificado ejecutandola: sube los dos dSYM de la 64 correctamente.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-05 20:57:50 +02:00
alexandrev-tibco 3694297d4f 2.0: mostrar los dos ultimos pasos del onboarding (aha moment + paywall)
completeOnboarding escribia settings.onboardingCompleted al terminar el paso
de platos. Ese flag es el que ContentView observa para cambiar de OnboardingView
a HomeView, asi que el save destruia el flujo antes de que nextStep() pudiera
pintar el paso 5. WeekReadyStepView (el aha moment de la 1.2.0) y PaywallStepView
no se han visto nunca. GA4 lo confirma: 3 onboarding_completed en 28 dias y cero
eventos con step 5 o 6.

Se separa persistir los datos de marcar el onboarding como terminado:
completeOnboarding(markFinished:) commitea platos y plan sin tocar el flag, y el
nuevo finishOnboarding() lo escribe al salir del paywall. HomeView ya difiere su
prompt premium a la segunda sesion, asi que no hay paywall duplicado.

De paso, los nombres de paso en AnalyticsService estaban desalineados: el indice
5 decia "5_paywall" cuando el paso 5 es WeekReady, y el 6 no tenia nombre.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-05 20:53:10 +02:00
alexandrev-tibco 10a3d8bf32 2.0: bump build 63 → 64 after TestFlight upload (slider, banner, share toolbar)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-26 09:21:43 +02:00
alexandrev-tibco a4aedffd5a 2.0: banner export solo en semana completa + compartir desde toolbar
- El banner "ready to export" ya no aparece con la semana incompleta; solo
  cuando no quedan slots vacíos (y sigue siendo descartable con la X).
- Se añade un icono de compartir en la toolbar (cuando hay algo planificado)
  para no perder el acceso a exportar en semanas parciales; misma lógica de
  premium/paywall que el banner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-26 08:34:05 +02:00
alexandrev-tibco 543254337f 2.0: banner export descartable + slider calendario/platos en iPad
- El banner "Your week is ready to export" ahora tiene botón de cerrar (X)
  que lo oculta para esa semana (persistido en AppStorage).
- iPad/Mac: handle arrastrable entre calendario y platos para rebalancear
  el alto (fracción persistida, clamp 0.28–0.72); el calendario y la lista
  se recalculan al arrastrar. Sustituye la altura fija anterior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-24 16:29:22 +02:00
alexandrev-tibco b70140191a 2.0: firma manual (app+widget) para Push + bump build 62 → 63
El archive con firma automática no podía incluir la capability Push de
forma headless. Se pasan los targets MealMood y MealMoodWidget (config
Release) a firma manual con "Apple Distribution" y sus perfiles AppStore
(regenerados con Push). Build 63 sube a TestFlight con aps-environment
production para el sync CloudKit en tiempo real.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-24 15:59:42 +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 19bf3178d8 2.0: actualizar review notes (sin login, dictado, IA ingredientes, premium)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-24 15:03:14 +02:00
alexandrev-tibco 5862180426 2.0: bump build 61 → 62 after TestFlight upload (dedup platos)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-24 15:01:37 +02:00
alexandrev-tibco 9a898be7cf 2.0: release notes "What's New" en los 6 idiomas
Lista de compra, ingredientes IA on-device, dictado, fotos de platos,
desayuno/merienda, sync CloudKit, editar plato desde la lista.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-24 14:59:59 +02:00
alexandrev-tibco 4f3664cdd4 2.0: deduplicar Dishes por id en DeduplicationService (determinista)
DeduplicationService colapsaba settings, tags y week plans pero NO los
platos, dejando duplicados de CloudKit (mismo UUID) que causaban el crash
de arranque y platos repetidos en pantalla. Se añade dedupeDishes:

- Solo agrupa y colapsa copias que comparten el mismo id (nunca fusiona
  platos distintos ni toca platos únicos).
- Keeper elegido de forma DETERMINISTA (copia más rica: foto/desc/
  ingredientes/tags/prioridad; desempate por persistentModelID estable),
  así todos los dispositivos de la cuenta borran los mismos perdedores y
  el grafo converge a una copia, nunca a cero.
- CloudKit private DB por Apple ID (sin CKShare) → jamás cruza cuentas de
  Family Sharing.

Junto con el uniquingKeysWith previo, la app deja de crashear y limpia los
platos duplicados en cada arranque, sin borrar la BD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-22 20:39:07 +02:00
alexandrev-tibco 0ac81e5fe3 2.0: bump build 60 → 61 after TestFlight upload (fix crash arranque IDs duplicados)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-22 18:28:05 +02:00
alexandrev-tibco 5272f6533f 2.0: fix crash de arranque por IDs duplicados (CloudKit)
Fatal error "Duplicate values for key" al construir diccionarios con
Dictionary(uniqueKeysWithValues:) sobre dishes/tags/slots cuyos UUID se
duplican por la sincronización CloudKit de 2.0. Se sustituyen todas las
ocurrencias por Dictionary(_, uniquingKeysWith:) para tolerar duplicados
(se conserva el primero) en lugar de abortar. La app ya no crashea al
abrir con datos duplicados; la deduplicación real queda pendiente aparte.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-22 18:23:50 +02:00
alexandrev-tibco 817ae549dc 2.0: bump build 59 → 60 after TestFlight upload (dictado + calendario iPad)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-22 18:17:07 +02:00
alexandrev-tibco 8e4d0c2d89 2.0: dictado de platos/ingredientes + calendario más alto en iPad/Mac
- Dictado por voz (Speech/SFSpeechRecognizer, on-device) en el nombre de
  plato y en ingredientes, con transcripción en vivo.
- Un solo texto dictado se separa en ingredientes individuales vía Apple
  Foundation Models on-device (IngredientParser), con fallback heurístico.
- Permisos de micrófono y reconocimiento de voz en Info.plist.
- Cadenas de dictado en los 6 idiomas.
- iPad/Mac: el calendario reclama ~50% de la altura disponible en lugar
  de quedar fijo a ~240px; iPhone (compact) sin cambios.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3HaWmmtTQ1vdTERtSYU6p
2026-07-22 18:16:51 +02:00