# MealMood - Product Requirements Document (PRD) ## πŸ“‹ Índice 1. [VisiΓ³n General](#visiΓ³n-general) 2. [Problema y SoluciΓ³n](#problema-y-soluciΓ³n) 3. [Usuario Objetivo](#usuario-objetivo) 4. [Propuesta de Valor](#propuesta-de-valor) 5. [Arquitectura de InformaciΓ³n](#arquitectura-de-informaciΓ³n) 6. [Modelo de Datos](#modelo-de-datos) 7. [Funcionalidades MVP](#funcionalidades-mvp) 8. [Funcionalidades Freemium (Futuro)](#funcionalidades-freemium-futuro) 9. [Identidad Visual y Branding](#identidad-visual-y-branding) 10. [Especificaciones de Pantallas](#especificaciones-de-pantallas) 11. [Interacciones y Micro-animaciones](#interacciones-y-micro-animaciones) 12. [IntegraciΓ³n con Calendario Nativo](#integraciΓ³n-con-calendario-nativo) 13. [Algoritmo de Autocompletado](#algoritmo-de-autocompletado) 14. [SincronizaciΓ³n y Persistencia](#sincronizaciΓ³n-y-persistencia) 15. [MonetizaciΓ³n](#monetizaciΓ³n) 16. [Stack TΓ©cnico](#stack-tΓ©cnico) 17. [Plan de ImplementaciΓ³n (Sprints)](#plan-de-implementaciΓ³n-sprints) 18. [Anexos](#anexos) --- ## VisiΓ³n General ### Nombre **MealMood** 🍽️✨ ### Tagline - **EspaΓ±ol**: "Planifica tu semana, disfruta tus comidas" - **English**: "Plan your week, enjoy your meals" ### DescripciΓ³n del Producto MealMood es una aplicaciΓ³n iOS para planificar comidas y cenas semanales de manera rΓ‘pida, visual y sin estrΓ©s. Orientada principalmente a familias (target: madres), permite organizar el menΓΊ semanal en 5 minutos usando drag & drop, respetando reglas nutricionales configurables y sincronizando opcionalmente con el calendario nativo de iOS. **NO es un tracker de calorΓ­as ni macros**. El enfoque estΓ‘ en la simplicidad, variedad y disfrute de las comidas familiares. --- ## Problema y SoluciΓ³n ### Problema - Planificar comidas semanales es una tarea recurrente que genera **estrΓ©s y fatiga decisional**. - Las familias necesitan **variedad** sin repetir platos constantemente. - Restricciones dietΓ©ticas (gluten, frecuencia de proteΓ­nas) complican la planificaciΓ³n. - Apps existentes son **complejas** (trackers nutricionales) o requieren mucha configuraciΓ³n manual. ### SoluciΓ³n MealMood ofrece: 1. **PlanificaciΓ³n en 5 minutos**: interfaz visual con drag & drop. 2. **Reglas configurables**: limita frecuencia de ingredientes (carne, pescado, etc.) sin ser obsesivo. 3. **Autocompletado inteligente**: algoritmo rellena huecos respetando reglas. 4. **SincronizaciΓ³n con calendario**: eventos automΓ‘ticos en el calendario nativo de iOS. 5. **DiseΓ±o acogedor**: tonos pastel, ilustraciones suaves, experiencia sin fricciΓ³n. --- ## Usuario Objetivo ### Perfil Primario - **Madres de familia** (25-45 aΓ±os). - Con hijos pequeΓ±os/adolescentes. - Responsables de la planificaciΓ³n semanal de comidas. - Buscan **eficiencia** sin perder variedad. - No obsesionadas con calorΓ­as/macros, pero conscientes de dieta equilibrada. ### Perfil Secundario - Padres que comparten la responsabilidad de cocinar. - Personas organizadas que planifican con antelaciΓ³n. - Familias con restricciones dietΓ©ticas leves (gluten, vegetarianos). ### Jobs to Be Done (JTBD) *"Cuando llega el domingo por la tarde y necesito planificar la semana, quiero una forma rΓ‘pida de decidir quΓ© cocinar cada dΓ­a sin repetir los mismos platos, para no estresarme y que mi familia tenga variedad."* --- ## Propuesta de Valor ### Diferenciadores clave 1. **Rapidez**: planificaciΓ³n en 5 minutos vs. 30+ en competencia. 2. **Visual**: interfaz calendario + drag & drop vs. listas aburridas. 3. **Reglas automΓ‘ticas**: evita repeticiones sin pensar manualmente. 4. **No es un tracker nutricional**: enfoque en disfrute, no en restricciΓ³n. 5. **SincronizaciΓ³n calendario**: eventos listos para compartir con la familia. ### Competencia | App | Enfoque | Diferencia con MealMood | |-----|---------|------------------------| | **Paprika** | Recetas + listas de compra | MΓ‘s complejo, orientado a recetas detalladas | | **Mealime** | Planes nutricionales | Muy centrado en macros/calorΓ­as | | **Plan to Eat** | Recetas + calendario | Requiere importar recetas, setup largo | | **AnyList** | Listas de compra | No tiene planificaciΓ³n visual con reglas | --- ## Arquitectura de InformaciΓ³n ### Flujo principal ``` Onboarding (primera vez) ↓ ConfiguraciΓ³n inicial β”œβ”€β”€ Ventanas de comida (cenas / comidas / ambas) β”œβ”€β”€ ΒΏIncluir fines de semana? └── SincronizaciΓ³n calendario (opcional) ↓ Home (pantalla principal) β”œβ”€β”€ Calendario semanal (vista principal) β”œβ”€β”€ Drawer de platos disponibles └── Botones: Completar / Resetear ↓ GestiΓ³n de platos β”œβ”€β”€ Lista de platos β”œβ”€β”€ Crear nuevo plato └── Editar/Eliminar platos ↓ GestiΓ³n de etiquetas (Tags) β”œβ”€β”€ Lista de tags β”œβ”€β”€ Editar reglas de tags └── Crear tags custom (Freemium) ↓ Settings β”œβ”€β”€ Ventanas de comida β”œβ”€β”€ Fines de semana β”œβ”€β”€ Calendario (sincronizaciΓ³n) β”œβ”€β”€ Idioma └── Premium ``` --- ## Modelo de Datos ### Entidades principales #### 1. Settings ```swift struct Settings { // General var mealWindows: MealWindows // .dinnerOnly, .lunchOnly, .both var includeWeekends: Bool var language: Language // .spanish, .english // Calendario var calendarId: String? // ID del calendario nativo, nil si no sincroniza var syncEnabled: Bool var lunchTime: Date // hora del dΓ­a (default: 14:00) var dinnerTime: Date // hora del dΓ­a (default: 21:00) var eventDuration: Int // minutos (default: 60) var eventPrefix: String // default: "🍽️" var reminderMinutesBefore: Int? // nil = sin alerta, valores: 30, 60, 120 // Premium var isPremium: Bool } enum MealWindows { case dinnerOnly case lunchOnly case both } enum Language { case spanish case english } ``` #### 2. Tag (Etiqueta) ```swift struct Tag: Identifiable, Codable { var id: UUID var name: String // localizado segΓΊn idioma var color: String // hex (ej: "#E74C3C") var rules: TagRules } struct TagRules: Codable { var maxPerWeek: Int? // nil = sin lΓ­mite var noConsecutive: Bool // no puede estar en dΓ­as consecutivos var noDuplicateInDay: Bool // no puede estar en comida y cena el mismo dΓ­a var mealTypeRestriction: MealType? // .lunch, .dinner, nil } enum MealType: String, Codable { case lunch case dinner } ``` #### 3. Dish (Plato) ```swift struct Dish: Identifiable, Codable { var id: UUID var name: String var description: String? // opcional var tags: [UUID] // referencias a Tag IDs var createdAt: Date } ``` #### 4. MealSlot (Slot de comida) ```swift struct MealSlot: Identifiable, Codable { var id: UUID var dayOfWeek: Int // 0=lunes, 6=domingo var mealType: MealType // .lunch o .dinner var dish: UUID? // nil si vacΓ­o var calendarEventId: String? // ID del evento en EventKit, nil si no sincroniza } ``` #### 5. WeekPlan (PlanificaciΓ³n semanal) ```swift struct WeekPlan: Identifiable, Codable { var id: UUID var weekStartDate: Date // lunes de esa semana var slots: [MealSlot] var createdAt: Date var updatedAt: Date } ``` --- ## Funcionalidades MVP ### F1: ConfiguraciΓ³n Inicial (Onboarding) **DescripciΓ³n**: Primera vez que el usuario abre la app. **Pasos**: 1. **Bienvenida**: logo + tagline + ilustraciΓ³n. 2. **Ventanas de comida**: selector (solo cenas / solo comidas / ambas). 3. **Fines de semana**: toggle (incluir o no). 4. **Calendario** (opcional): selector de calendario nativo + configuraciΓ³n de horas. 5. **Crear primeros platos**: formulario rΓ‘pido para aΓ±adir 2-3 platos iniciales. **Criterios de AceptaciΓ³n**: - Usuario puede completar onboarding en <2 minutos. - Settings guardados en persistencia local (SwiftData/Core Data). - NavegaciΓ³n fluida sin saltos bruscos. --- ### F2: GestiΓ³n de Platos **DescripciΓ³n**: Crear, editar y eliminar platos personales. **Funcionalidad**: - **Lista de platos**: muestra todos los platos del usuario. - **Crear plato**: - Campo: Nombre (obligatorio). - Campo: DescripciΓ³n (opcional). - Selector: Etiquetas (multi-selecciΓ³n de tags disponibles). - **Editar plato**: mismo formulario, pre-rellenado. - **Eliminar plato**: swipe-to-delete + confirmaciΓ³n. **Validaciones**: - Nombre no puede estar vacΓ­o. - No permitir eliminar plato si estΓ‘ asignado a un slot de la semana actual (mostrar alert). **Criterios de AceptaciΓ³n**: - Usuario puede crear plato en <30 segundos. - Cambios se reflejan inmediatamente en la lista de platos. - EliminaciΓ³n con confirmaciΓ³n para evitar errores. --- ### F3: GestiΓ³n de Etiquetas (Tags) **DescripciΓ³n**: Ver y editar reglas de etiquetas predefinidas. **Funcionalidad**: - **Lista de tags**: muestra tags por defecto (10 en MVP). - **Editar reglas de tag**: - Max por semana (slider 1-7 o "sin lΓ­mite"). - Toggle: No consecutivo. - Toggle: No duplicar en el mismo dΓ­a. - Selector: RestricciΓ³n de tipo de comida (ninguna / solo comida / solo cena). - **Ver color del tag**: visualizaciΓ³n en lista con pill de color. **Tags por defecto (MVP)**: | Tag (ES) | Tag (EN) | Color | Reglas por defecto | |----------|----------|-------|-------------------| | Carne | Meat | #E74C3C | Max 3/semana, no consecutivo, no mismo dΓ­a | | Pescado | Fish | #3498DB | Max 2/semana, no consecutivo, no mismo dΓ­a | | Legumbres | Legumes | #95A5A6 | Max 2/semana, no mismo dΓ­a | | Verduras | Vegetables | #2ECC71 | Sin lΓ­mite | | Huevos | Eggs | #F1C40F | Max 2/semana, no mismo dΓ­a | | Pasta/Arroz | Pasta/Rice | #D4AC6E | Max 3/semana, no consecutivo | | Solo Cena | Dinner Only | #9B59B6 | RestricciΓ³n: solo cena | | Solo Comida | Lunch Only | #E67E22 | RestricciΓ³n: solo comida | | Con Gluten | Contains Gluten | #8B4513 | Informativo (sin reglas) | | Sin Gluten | Gluten Free | #1ABC9C | Informativo (sin reglas) | **Criterios de AceptaciΓ³n**: - Usuario puede modificar reglas en <1 minuto. - Cambios se aplican inmediatamente al algoritmo de validaciΓ³n. - No permitir crear tags custom en MVP (solo Freemium). --- ### F4: PlanificaciΓ³n Semanal (Pantalla Principal) **DescripciΓ³n**: Vista calendario con drag & drop para asignar platos a slots. **Layout**: ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ MealMood Semana 3-9 Feb [βš™οΈ] β”‚ β”‚ β”‚ β”‚ ← Anterior Siguiente β†’ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ Lun Mar MiΓ© Jue Vie β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚πŸŒ™ β”‚ β”‚πŸŒ™ β”‚ β”‚πŸŒ™ β”‚ β”‚πŸŒ™ β”‚ β”‚πŸŒ™ β”‚ β”‚ β”‚ β”‚Polloβ”‚ β”‚Sal- β”‚ β”‚Tortiβ”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚asadoβ”‚ β”‚mΓ³n β”‚ β”‚lla β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚πŸ”΄ β”‚ β”‚πŸ”΅ β”‚ β”‚πŸŸ‘ β”‚ β”‚ - - β”‚ β”‚ - - β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Completar β”‚ β”‚ Resetear β”‚ β”‚ β”‚ β”‚ ✨ β”‚ β”‚ πŸ”„ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–² Arrastra platos aquΓ­ β–² β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ πŸ“‹ Mis platos [+] β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Pollo asado πŸ”΄ β”‚ β”‚ β”‚ β”‚ Jugoso y al horno β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ SalmΓ³n a la plancha πŸ”΅ β”‚ β”‚ β”‚ β”‚ Con limΓ³n y eneldo β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Funcionalidad**: - **Calendario semanal**: - Muestra dΓ­as segΓΊn configuraciΓ³n (lun-dom si incluye fines de semana, lun-vie si no). - Muestra slots segΓΊn configuraciΓ³n (solo cena, solo comida, o ambas). - **Slots vacΓ­os**: placeholder visual con icono (β˜€οΈ para comida, πŸŒ™ para cena) + texto "Arrastra un plato". - **Slots llenos**: muestra nombre del plato + descripciΓ³n corta + pills de colores (tags). - **Drawer de platos**: - Lista scrolleable de todos los platos creados. - Card por plato: nombre + descripciΓ³n + pills de tags. - BotΓ³n [+] para crear nuevo plato (abre modal/sheet). - **Drag & Drop**: - Long press (200ms) en plato β†’ se "levanta" con sombra. - Arrastrar sobre slot: - **VΓ‘lido** (respeta reglas): borde verde, haptic feedback suave. - **InvΓ‘lido** (viola reglas): borde rojo, haptic feedback "error". - Drop: - **VΓ‘lido**: plato se asigna al slot, desaparece temporalmente del drawer, animaciΓ³n "plop". - **InvΓ‘lido**: plato vuelve al drawer con bounce animation. - **NavegaciΓ³n semanal**: - Flechas ← β†’ para cambiar de semana. - Muestra rango de fechas (ej: "Semana 3-9 Feb"). - **Botones de acciΓ³n**: - **Completar ✨**: ejecuta algoritmo de autocompletado (ver F5). - **Resetear πŸ”„**: limpia todos los slots de la semana (con confirmaciΓ³n). **Criterios de AceptaciΓ³n**: - Usuario puede planificar semana completa en <5 minutos. - Drag & drop fluido (60fps), sin lag. - ValidaciΓ³n de reglas en tiempo real (antes de drop). - NavegaciΓ³n entre semanas sin perder datos. --- ### F5: Autocompletado Inteligente **DescripciΓ³n**: Algoritmo que rellena slots vacΓ­os respetando reglas de tags. **Funcionamiento**: 1. Usuario presiona botΓ³n "Completar ✨". 2. Algoritmo analiza: - Slots vacΓ­os en la semana actual. - Platos disponibles (todos los creados por el usuario). - Reglas de cada tag asociado a cada plato. 3. Para cada slot vacΓ­o: - Filtra platos vΓ‘lidos (que no violen reglas). - Prioriza platos menos usados en la semana actual (para variedad). - Asigna plato aleatoriamente entre vΓ‘lidos. 4. Si no encuentra plato vΓ‘lido para un slot β†’ lo deja vacΓ­o. 5. Muestra resultado con animaciΓ³n secuencial (slots se rellenan uno por uno). **LΓ³gica de validaciΓ³n**: ```python def violates_rules(dish, slot, current_plan, rules): for tag_id in dish.tags: tag_rule = rules[tag_id] # Check max per week if tag_rule.maxPerWeek: count = current_plan.count_tag_usage(tag_id) if count >= tag_rule.maxPerWeek: return True # Check no consecutive if tag_rule.noConsecutive: adjacent_slots = current_plan.get_adjacent_slots(slot) for adj_slot in adjacent_slots: if adj_slot.dish and tag_id in adj_slot.dish.tags: return True # Check no duplicate in same day if tag_rule.noDuplicateInDay: same_day_slots = current_plan.get_same_day_slots(slot) for sd_slot in same_day_slots: if sd_slot.dish and tag_id in sd_slot.dish.tags: return True # Check meal type restriction if tag_rule.mealTypeRestriction: if slot.mealType != tag_rule.mealTypeRestriction: return True return False ``` **Criterios de AceptaciΓ³n**: - Algoritmo completa semana en <2 segundos. - Respeta todas las reglas configuradas. - Si no puede completar todos los slots, informa al usuario ("No puedo completar con las reglas actuales. ΒΏDeseas relajar restricciones?"). - Usuario puede manualmente sobrescribir asignaciones del algoritmo. --- ### F6: SincronizaciΓ³n con Calendario Nativo **DescripciΓ³n**: Crear eventos en el calendario nativo de iOS para cada comida planificada. **ConfiguraciΓ³n** (Settings β†’ Calendario): - Toggle: Sincronizar (ON/OFF). - Selector: Calendario nativo (lista de calendarios del dispositivo). - Time picker: Hora de comidas (default: 14:00). - Time picker: Hora de cenas (default: 21:00). - Slider: DuraciΓ³n del evento (30min, 1h, 1.5h, 2h) - default: 1h. - Text field: Prefijo del tΓ­tulo (default: "🍽️"). - Selector: Recordatorio (sin recordatorio / 30min antes / 1h antes / 2h antes). **Funcionamiento**: 1. **Al asignar plato a slot**: - Si `syncEnabled == true` y `calendarId != nil`: - Crear evento en EventKit: - **TΓ­tulo**: `{eventPrefix} {mealType}: {dish.name}` (ej: "🍽️ Cena: Pollo asado"). - **Fecha**: `weekStartDate + dayOfWeek`. - **Hora inicio**: `lunchTime` (si es comida) o `dinnerTime` (si es cena). - **Hora fin**: `startDate + eventDuration`. - **Notas**: `dish.description` (si existe). - **Alerta**: segΓΊn `reminderMinutesBefore`. - Guardar `eventId` en `slot.calendarEventId`. 2. **Al eliminar plato de slot**: - Si `slot.calendarEventId != nil`: - Eliminar evento de EventKit usando `eventId`. - Limpiar `slot.calendarEventId`. - Mostrar toast: "Evento eliminado del calendario". 3. **Al cambiar hora en Settings**: - Preguntar: "ΒΏActualizar eventos existentes?". - Si SΓ­: - Para cada `slot` con `calendarEventId != nil` y fecha >= hoy: - Actualizar evento en EventKit con nueva hora. **Permisos**: - Solicitar permiso de EventKit al activar sincronizaciΓ³n. - Si usuario deniega permiso β†’ mostrar alert explicando que necesita acceso para sincronizar (con link a Settings del sistema). **Casos edge**: - Si calendario sincronizado es eliminado del dispositivo β†’ detectar y mostrar warning: "El calendario sincronizado ya no existe. Selecciona otro." - Si el usuario edita el evento directamente en la app Calendario nativa β†’ **ignorar cambios** (no sincronizaciΓ³n bidireccional en MVP). **Criterios de AceptaciΓ³n**: - Eventos creados correctamente con todos los campos (tΓ­tulo, fecha, hora, notas, alerta). - EliminaciΓ³n de eventos al quitar plato del slot. - Usuario puede desactivar sincronizaciΓ³n en cualquier momento sin perder datos en la app. --- ### F7: Settings (Ajustes) **DescripciΓ³n**: Pantalla de configuraciΓ³n general. **Secciones**: #### 🍽️ PlanificaciΓ³n - **Ventanas de comida**: selector (Solo cenas / Solo comidas / Comidas y cenas). - **Incluir fines de semana**: toggle (ON/OFF). #### πŸ“… Calendario - **Sincronizar**: toggle (ON/OFF). - **Calendario**: selector de calendarios nativos (solo visible si toggle ON). - **Hora de comidas**: time picker (default: 14:00). - **Hora de cenas**: time picker (default: 21:00). - **DuraciΓ³n del evento**: selector (30min, 1h, 1.5h, 2h). - **Prefijo del tΓ­tulo**: text field (default: "🍽️"). - **Recordatorio**: selector (Sin recordatorio / 30min antes / 1h antes / 2h antes). #### 🏷️ Etiquetas - **Gestionar etiquetas**: navegar a pantalla de gestiΓ³n de tags. #### 🌍 Idioma - **Idioma**: selector (EspaΓ±ol / English). #### ⭐ Premium - **Quitar publicidad**: navegar a pantalla de compra (IAP). - **Estado**: mostrar si el usuario es Premium o Free. **Criterios de AceptaciΓ³n**: - Cambios se guardan inmediatamente (auto-save). - Cambio de idioma actualiza toda la interfaz (incluyendo tags predefinidos). - NavegaciΓ³n fluida entre Settings y otras pantallas. --- ### F8: Persistencia Local **DescripciΓ³n**: Guardar datos del usuario en el dispositivo. **TecnologΓ­a**: SwiftData (iOS 17+) o Core Data (si necesitas compatibilidad con iOS 16). **Entidades persistentes**: - Settings - Tag (10 tags por defecto) - Dish (platos creados por el usuario) - WeekPlan (planificaciones semanales, al menos la actual y las prΓ³ximas 2-3) **Comportamiento**: - Datos se guardan automΓ‘ticamente en cada cambio (no requiere botΓ³n "Guardar"). - Al abrir la app, carga la planificaciΓ³n de la semana actual. - Si no existe planificaciΓ³n para la semana actual, crea una nueva vacΓ­a. **Criterios de AceptaciΓ³n**: - Datos persisten entre sesiones de la app. - No hay pΓ©rdida de datos al cerrar/abrir la app. - Performance: carga inicial <1 segundo. --- ### F9: Multiidioma (EspaΓ±ol/InglΓ©s) **DescripciΓ³n**: Soporte para espaΓ±ol e inglΓ©s desde dΓ­a 1. **ImplementaciΓ³n**: - Usar `Localizable.strings` para textos de UI. - Tags predefinidos tienen nombre localizado (ej: "Carne" en ES, "Meat" en EN). - Cambio de idioma en Settings β†’ actualiza toda la interfaz sin reiniciar la app. **Textos a traducir**: - Onboarding (bienvenida, pasos). - Pantalla principal (botones, placeholders). - GestiΓ³n de platos (formularios, validaciones). - GestiΓ³n de tags (nombres, reglas). - Settings (todas las opciones). - Alerts y toasts. **Criterios de AceptaciΓ³n**: - Usuario puede cambiar idioma en Settings. - Cambio se refleja inmediatamente en toda la app. - Tags predefinidos cambian de nombre segΓΊn idioma. --- ### F10: NavegaciΓ³n entre Semanas **DescripciΓ³n**: Usuario puede planificar mΓΊltiples semanas (pasadas y futuras). **Funcionalidad**: - Flechas ← β†’ en la pantalla principal. - Mostrar rango de fechas de la semana actual (ej: "Semana 3-9 Feb 2025"). - Al cambiar de semana: - Cargar planificaciΓ³n de esa semana (si existe). - Si no existe, crear planificaciΓ³n vacΓ­a. - **LimitaciΓ³n MVP**: solo se pueden editar semana actual y futuras (no pasadas). **Criterios de AceptaciΓ³n**: - NavegaciΓ³n fluida sin lag. - Cada semana tiene su propia planificaciΓ³n independiente. - Usuario puede ver hasta 4 semanas futuras. --- ## Funcionalidades Freemium (Futuro) ### Freemium 1: Tags Custom Ilimitados - Usuario puede crear tags personalizados con nombre, color y reglas. - En Free: solo 10 tags predefinidos, no se pueden crear nuevos. ### Freemium 2: Platos Ilimitados - Free: mΓ‘ximo 20 platos. - Premium: ilimitados. ### Freemium 3: Reglas Avanzadas - **SeparaciΓ³n mΓ­nima**: "Este plato no puede repetirse hasta X dΓ­as despuΓ©s". - **DΓ­a fijo**: "Este plato siempre los viernes" (tradiciones familiares). - En Free: solo reglas bΓ‘sicas (max/semana, no consecutivo, no mismo dΓ­a). ### Freemium 4: MΓΊltiples Planificaciones Guardadas - Guardar "plantillas" de semanas (ej: "Semana tΓ­pica", "Semana de invitados"). - Aplicar plantilla a cualquier semana futura. ### Freemium 5: Exportar a Lista de Compra - Generar lista de ingredientes basada en la planificaciΓ³n semanal. - Exportar a Notes, Reminders o apps de terceros (AnyList). ### Freemium 6: Analytics/HistΓ³rico - Dashboard con estadΓ­sticas: - "ΒΏQuΓ© platos repito mΓ‘s?" - "ΒΏCuΓ‘ntas veces comΓ­ carne esta semana?" - GrΓ‘ficos de variedad semanal. ### Freemium 7: ColaboraciΓ³n Familiar - MΓΊltiples usuarios pueden editar la misma planificaciΓ³n. - Notificaciones push: "PapΓ‘ cambiΓ³ la cena del jueves". --- ## Identidad Visual y Branding ### Logo **DescripciΓ³n conceptual**: - **Forma**: circular o redondeada (amigable). - **Icono**: plato con tenedor/cuchara cruzados, estilo line art minimalista. - **Colores**: gradiente suave coral β†’ menta (#FFB4A2 β†’ #B8E6D5). - **LΓ­neas**: grosor 2-3px, esquinas redondeadas. - **Opcional**: pequeΓ±o "sparkle" (✨) en la esquina superior derecha. **Placeholder en cΓ³digo**: ```swift struct AppIconPlaceholder: View { var body: some View { ZStack { Circle() .fill( LinearGradient( colors: [Color(hex: "#FFB4A2"), Color(hex: "#B8E6D5")], startPoint: .topLeading, endPoint: .bottomTrailing ) ) Image(systemName: "fork.knife.circle") .font(.system(size: 60, weight: .light)) .foregroundColor(.white) } .frame(width: 120, height: 120) .shadow(color: .black.opacity(0.1), radius: 10, y: 5) } } ``` --- ### Paleta de Colores ```swift // Colores principales extension Color { // Primary static let mealMoodCoral = Color(hex: "#FFB4A2") // Coral pastel (acento cΓ‘lido) static let mealMoodMint = Color(hex: "#B8E6D5") // Menta claro (secundario) // Background static let mealMoodBackground = Color(hex: "#FFF9F5") // Crema muy claro static let mealMoodSurface = Color.white // Blanco puro (cards) // Text static let mealMoodTextPrimary = Color(hex: "#2D2D2D") // Gris oscuro static let mealMoodTextSecondary = Color(hex: "#6B6B6B") // Gris medio // Feedback static let mealMoodSuccess = Color(hex: "#A8D5BA") // Verde sage static let mealMoodWarning = Color(hex: "#FFD6A5") // MelocotΓ³n static let mealMoodError = Color(hex: "#FF9999") // Rojo suave // Helpers init(hex: String) { let scanner = Scanner(string: hex) scanner.currentIndex = hex.hasPrefix("#") ? hex.index(after: hex.startIndex) : hex.startIndex var rgb: UInt64 = 0 scanner.scanHexInt64(&rgb) let r = Double((rgb & 0xFF0000) >> 16) / 255.0 let g = Double((rgb & 0x00FF00) >> 8) / 255.0 let b = Double(rgb & 0x0000FF) / 255.0 self.init(red: r, green: g, blue: b) } } ``` --- ### TipografΓ­a ```swift // Font system extension Font { // Headers static let mealMoodH1 = Font.system(size: 28, weight: .bold, design: .rounded) static let mealMoodH2 = Font.system(size: 20, weight: .semibold, design: .rounded) static let mealMoodH3 = Font.system(size: 18, weight: .semibold, design: .rounded) // Body static let mealMoodBody = Font.system(size: 16, weight: .regular, design: .default) static let mealMoodBodyBold = Font.system(size: 16, weight: .semibold, design: .default) // Small static let mealMoodCaption = Font.system(size: 12, weight: .medium, design: .default) static let mealMoodSmall = Font.system(size: 14, weight: .regular, design: .default) } ``` --- ### Componentes UI Reutilizables #### 1. Card Style (para slots y platos) ```swift struct MealCardStyle: ViewModifier { func body(content: Content) -> some View { content .background(Color.mealMoodSurface) .cornerRadius(16) .shadow(color: Color.black.opacity(0.05), radius: 8, y: 4) .overlay( RoundedRectangle(cornerRadius: 16) .stroke(Color(hex: "#F0F0F0"), lineWidth: 1) ) } } extension View { func mealCardStyle() -> some View { modifier(MealCardStyle()) } } ``` #### 2. Tag Pill (visual de etiquetas) ```swift struct TagPill: View { let tag: Tag var body: some View { Text(tag.name) .font(.mealMoodCaption) .foregroundColor(.white) .padding(.horizontal, 10) .padding(.vertical, 4) .background( Capsule() .fill(Color(hex: tag.color)) ) } } ``` #### 3. Empty Slot (placeholder visual) ```swift struct EmptySlot: View { let mealType: MealType var body: some View { VStack(spacing: 8) { Image(systemName: mealType == .lunch ? "sun.max.fill" : "moon.stars.fill") .font(.system(size: 24)) .foregroundColor(Color(hex: "#C4C4C4")) Text(mealType == .lunch ? "Comida" : "Cena") .font(.mealMoodSmall) .foregroundColor(.mealMoodTextSecondary) Text("Arrastra un plato") .font(.mealMoodCaption) .foregroundColor(Color(hex: "#A0A0A0")) } .frame(maxWidth: .infinity, minHeight: 100) .background( RoundedRectangle(cornerRadius: 12) .fill(Color(hex: "#F9F9F9")) .overlay( RoundedRectangle(cornerRadius: 12) .strokeBorder( style: StrokeStyle(lineWidth: 2, dash: [5]) ) .foregroundColor(Color(hex: "#E0E0E0")) ) ) } } ``` #### 4. Filled Slot (con plato asignado) ```swift struct FilledSlot: View { let dish: Dish let tags: [Tag] var body: some View { VStack(alignment: .leading, spacing: 6) { Text(dish.name) .font(.system(size: 16, weight: .semibold)) .foregroundColor(.mealMoodTextPrimary) .lineLimit(2) if let description = dish.description { Text(description) .font(.mealMoodCaption) .foregroundColor(.mealMoodTextSecondary) .lineLimit(1) } HStack(spacing: 4) { ForEach(tags.prefix(3)) { tag in TagPill(tag: tag) } if tags.count > 3 { Text("+\(tags.count - 3)") .font(.system(size: 10, weight: .medium)) .foregroundColor(.mealMoodTextSecondary) } } } .padding(12) .frame(maxWidth: .infinity, minHeight: 100, alignment: .topLeading) .mealCardStyle() } } ``` #### 5. Primary Button ```swift struct PrimaryButton: View { let title: String let icon: String? let action: () -> Void var body: some View { Button(action: action) { HStack(spacing: 8) { Text(title) .font(.mealMoodBodyBold) if let icon = icon { Text(icon) } } .foregroundColor(.white) .padding(.horizontal, 24) .padding(.vertical, 12) .background( LinearGradient( colors: [Color.mealMoodCoral, Color.mealMoodMint], startPoint: .leading, endPoint: .trailing ) ) .cornerRadius(12) .shadow(color: Color.mealMoodCoral.opacity(0.3), radius: 8, y: 4) } } } ``` #### 6. Secondary Button ```swift struct SecondaryButton: View { let title: String let icon: String? let action: () -> Void var body: some View { Button(action: action) { HStack(spacing: 8) { Text(title) .font(.mealMoodBodyBold) if let icon = icon { Text(icon) } } .foregroundColor(.mealMoodTextPrimary) .padding(.horizontal, 24) .padding(.vertical, 12) .background(Color.mealMoodSurface) .overlay( RoundedRectangle(cornerRadius: 12) .stroke(Color.mealMoodTextSecondary.opacity(0.3), lineWidth: 1.5) ) .cornerRadius(12) } } } ``` --- ## Especificaciones de Pantallas ### Pantalla 1: Onboarding #### 1.1: Bienvenida ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ [Logo MealMood placeholder] β”‚ β”‚ β”‚ β”‚ ✨ Planifica tu semana en 5 minutos β”‚ β”‚ β”‚ β”‚ [IlustraciΓ³n: familia cenando feliz] β”‚ β”‚ β”‚ β”‚ β€’ Sin estrΓ©s β”‚ β”‚ β€’ Sin contar calorΓ­as β”‚ β”‚ β€’ Sincroniza con tu calendario β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ [Comenzar] ──────────► β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Elementos**: - Logo placeholder (usar componente AppIconPlaceholder). - TΓ­tulo: H1, centrado, coral. - IlustraciΓ³n: placeholder con SF Symbols (ej: "figure.2.and.child.holdinghands"). - Lista de beneficios: bullets con iconos. - BotΓ³n primario: "Comenzar". **NavegaciΓ³n**: tap en "Comenzar" β†’ Onboarding paso 1. --- #### 1.2: Paso 1 - Ventanas de Comida ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ΒΏQuΓ© quieres planificar? β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ πŸŒ™ Solo cenas β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β˜€οΈ Solo comidas β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ πŸŒžπŸŒ™ Comidas y cenas β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ [Continuar] ────────► β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Elementos**: - TΓ­tulo: H2, alineado a la izquierda. - 3 cards seleccionables (radio button style). - BotΓ³n primario: "Continuar" (solo habilitado si hay selecciΓ³n). **NavegaciΓ³n**: tap en "Continuar" β†’ Onboarding paso 2. --- #### 1.3: Paso 2 - Fines de Semana ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ΒΏIncluir fines de semana? β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Planificar tambiΓ©n sΓ‘bados β”‚ β”‚ β”‚ β”‚ y domingos [ON] β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ (Puedes cambiar esto despuΓ©s en ajustes) β”‚ β”‚ β”‚ β”‚ [Continuar] ────────► β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Elementos**: - TΓ­tulo: H2. - Toggle prominente. - Nota aclaratoria (small text, gris). - BotΓ³n primario: "Continuar". **NavegaciΓ³n**: tap en "Continuar" β†’ Onboarding paso 3. --- #### 1.4: Paso 3 - Calendario (Opcional) ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ΒΏSincronizar con tu calendario? β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Sincronizar [OFF] β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ [Si activas sincronizaciΓ³n:] β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Calendario β”‚ β”‚ β”‚ β”‚ Personal β–Ό β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Hora de comidas 14:00 ⏰ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Hora de cenas 21:00 ⏰ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ (Opcional, puedes activarlo despuΓ©s) β”‚ β”‚ β”‚ β”‚ [Omitir] [Continuar] ──────► β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Elementos**: - Toggle: "Sincronizar". - Si toggle ON β†’ mostrar selector de calendario + time pickers. - Botones: "Omitir" (secondary) y "Continuar" (primary). **NavegaciΓ³n**: - "Omitir" o "Continuar" β†’ Onboarding paso 4. - Si toggle ON, solicitar permiso de EventKit antes de continuar. --- #### 1.5: Paso 4 - Crear Primeros Platos ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ AΓ±ade tus primeros platos β”‚ β”‚ β”‚ β”‚ AΓ±ade al menos 2-3 platos para empezar β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Nombre del plato β”‚ β”‚ β”‚ β”‚ [Pollo asado ] β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ Etiquetas β”‚ β”‚ β”‚ β”‚ πŸ”΄ Carne [Γ—] β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ [+ AΓ±adir plato] β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ Platos aΓ±adidos: β”‚ β”‚ βœ“ Pollo asado (πŸ”΄ Carne) β”‚ β”‚ β”‚ β”‚ [Finalizar] ────────► β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Elementos**: - Formulario simple: nombre + selector de tags. - BotΓ³n "AΓ±adir plato" (aΓ±ade a lista temporal). - Lista de platos aΓ±adidos (con opciΓ³n de eliminar). - BotΓ³n "Finalizar" (habilitado cuando hay >= 2 platos). **NavegaciΓ³n**: tap en "Finalizar" β†’ Home (pantalla principal). --- ### Pantalla 2: Home (Pantalla Principal) ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ MealMood Semana 3-9 Feb [βš™οΈ] β”‚ β”‚ β”‚ β”‚ ← Anterior Siguiente β†’ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ Lun Mar MiΓ© Jue Vie β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚πŸŒ™ β”‚ β”‚πŸŒ™ β”‚ β”‚πŸŒ™ β”‚ β”‚πŸŒ™ β”‚ β”‚πŸŒ™ β”‚ β”‚ β”‚ β”‚Polloβ”‚ β”‚Sal- β”‚ β”‚Tortiβ”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚asadoβ”‚ β”‚mΓ³n β”‚ β”‚lla β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚πŸ”΄ β”‚ β”‚πŸ”΅ β”‚ β”‚πŸŸ‘ β”‚ β”‚ - - β”‚ β”‚ - - β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ [Si incluye comida y cena:] β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚β˜€οΈ β”‚ β”‚β˜€οΈ β”‚ β”‚β˜€οΈ β”‚ β”‚β˜€οΈ β”‚ β”‚β˜€οΈ β”‚ β”‚ β”‚ β”‚Lenteβ”‚ β”‚ β”‚ β”‚Pastaβ”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚jas β”‚ β”‚ β”‚ β”‚πŸŸ‘ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚πŸŸ’πŸŸ€ β”‚ β”‚ - - β”‚ β”‚ β”‚ β”‚ - - β”‚ β”‚ - - β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Completar β”‚ β”‚ Resetear β”‚ β”‚ β”‚ β”‚ ✨ β”‚ β”‚ πŸ”„ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–² Arrastra platos aquΓ­ β–² β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ πŸ“‹ Mis platos [+] β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Pollo asado πŸ”΄ β”‚ β”‚ β”‚ β”‚ Jugoso y al horno β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ SalmΓ³n a la plancha πŸ”΅ β”‚ β”‚ β”‚ β”‚ Con limΓ³n y eneldo β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Lentejas 🟒🟀 β”‚ β”‚ β”‚ β”‚ Estofadas con verduras β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ ... (scroll si hay mΓ‘s platos) β”‚ β”‚ β”‚ β”‚ [Banner AdMob - 50px altura] β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Elementos**: #### Header - TΓ­tulo: "MealMood" (H2, bold). - Rango de semana: "Semana 3-9 Feb" (body, gris). - BotΓ³n Settings: icono βš™οΈ (top-right). #### NavegaciΓ³n Semanal - Flechas: ← Anterior / Siguiente β†’. - Centrado en el rango de fechas. #### Calendario Semanal - **Grid de slots**: - OrientaciΓ³n: horizontal scroll si incluye fines de semana (7 columnas). - OrientaciΓ³n: vertical scroll si incluye comida+cena (2 filas por dΓ­a). - **Slot vacΓ­o**: usar componente `EmptySlot`. - **Slot lleno**: usar componente `FilledSlot`. #### Botones de AcciΓ³n - **Completar ✨**: PrimaryButton. - **Resetear πŸ”„**: SecondaryButton. #### Drawer de Platos - Lista scrolleable vertical. - Card por plato (usar `mealCardStyle`). - BotΓ³n [+]: circular, floating, color coral. #### AdMob Banner (si Free) - Altura: 50px. - Background: mismo color que la app (#FFF9F5). - Sticky al fondo del drawer. **Interacciones**: - **Long press en plato** β†’ drag & drop. - **Tap en slot lleno** β†’ opciΓ³n de eliminar plato. - **Tap en [+]** β†’ abrir sheet "Crear Plato". - **Tap en βš™οΈ** β†’ navegar a Settings. - **Tap en "Completar"** β†’ ejecutar algoritmo de autocompletado. - **Tap en "Resetear"** β†’ mostrar alert de confirmaciΓ³n. --- ### Pantalla 3: Crear/Editar Plato ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ← Nuevo plato [βœ“] β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ Nombre del plato * β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Pollo asado β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ DescripciΓ³n (opcional) β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Jugoso y al horno con hierbas β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ Etiquetas * β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ πŸ”΄ Carne [Γ—] β”‚ β”‚ β”‚ β”‚ 🟒 Verduras [Γ—] β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ [+ AΓ±adir etiqueta] β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ [Eliminar plato] (solo en modo ediciΓ³n) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Elementos**: - **Header**: - BotΓ³n back: ← (top-left). - TΓ­tulo: "Nuevo plato" (crear) o "Editar plato" (editar). - BotΓ³n guardar: βœ“ (top-right, habilitado solo si campos vΓ‘lidos). - **Formulario**: - Campo: Nombre (obligatorio, TextField). - Campo: DescripciΓ³n (opcional, TextEditor, max 100 caracteres). - Lista de tags seleccionados (pills con botΓ³n [Γ—] para eliminar). - BotΓ³n "AΓ±adir etiqueta" β†’ abre sheet selector de tags. - **BotΓ³n Eliminar** (solo en modo ediciΓ³n): - SecondaryButton, color rojo. - Mostrar alert de confirmaciΓ³n. **Validaciones**: - Nombre no puede estar vacΓ­o. - Debe tener al menos 1 etiqueta. **NavegaciΓ³n**: - Tap en [βœ“] β†’ guardar y cerrar sheet. - Tap en ← β†’ descartar cambios (mostrar alert si hay cambios sin guardar). --- #### 3.1: Sheet Selector de Tags ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Selecciona etiquetas [Γ—] β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ πŸ”΄ Carne [βœ“] β”‚ β”‚ πŸ”΅ Pescado [ ] β”‚ β”‚ 🟒 Verduras [βœ“] β”‚ β”‚ 🟑 Huevos [ ] β”‚ β”‚ 🟀 Legumbres [ ] β”‚ β”‚ 🟠 Pasta/Arroz [ ] β”‚ β”‚ 🟣 Solo Cena [ ] β”‚ β”‚ 🟠 Solo Comida [ ] β”‚ β”‚ 🟀 Con Gluten [ ] β”‚ β”‚ 🟒 Sin Gluten [ ] β”‚ β”‚ β”‚ β”‚ [Listo] β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Elementos**: - Lista de todos los tags disponibles. - Checkbox por tag (multi-selecciΓ³n). - Color pill al lado del nombre. - BotΓ³n "Listo" (primary). **NavegaciΓ³n**: tap en "Listo" β†’ cerrar sheet, actualizar lista de tags seleccionados. --- ### Pantalla 4: GestiΓ³n de Platos ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ← Mis platos [+] β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Pollo asado πŸ”΄ [β€Ί] β”‚ β”‚ β”‚ β”‚ Jugoso y al horno β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ SalmΓ³n a la plancha πŸ”΅ [β€Ί] β”‚ β”‚ β”‚ β”‚ Con limΓ³n y eneldo β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Lentejas 🟒🟀 [β€Ί] β”‚ β”‚ β”‚ β”‚ Estofadas con verduras β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ ... (scroll si hay mΓ‘s platos) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Elementos**: - **Header**: - BotΓ³n back: ← (top-left). - TΓ­tulo: "Mis platos". - BotΓ³n [+]: crear nuevo plato (top-right). - **Lista de platos**: - Card por plato (mealCardStyle). - Tap en card β†’ abrir sheet "Editar Plato". - Swipe left β†’ botΓ³n "Eliminar" (rojo). **Interacciones**: - **Tap en plato** β†’ abrir sheet ediciΓ³n. - **Swipe left** β†’ mostrar botΓ³n "Eliminar". - **Tap en "Eliminar"** β†’ mostrar alert de confirmaciΓ³n. - **Tap en [+]** β†’ abrir sheet "Crear Plato". --- ### Pantalla 5: GestiΓ³n de Etiquetas ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ← Etiquetas β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ πŸ”΄ Carne [β€Ί] β”‚ β”‚ Max 3/semana, no consecutivo β”‚ β”‚ β”‚ β”‚ πŸ”΅ Pescado [β€Ί] β”‚ β”‚ Max 2/semana, no consecutivo β”‚ β”‚ β”‚ β”‚ 🟒 Verduras [β€Ί] β”‚ β”‚ Sin lΓ­mite β”‚ β”‚ β”‚ β”‚ 🟑 Huevos [β€Ί] β”‚ β”‚ Max 2/semana, no mismo dΓ­a β”‚ β”‚ β”‚ β”‚ ... (resto de tags) β”‚ β”‚ β”‚ β”‚ [+ Crear etiqueta] (solo Premium) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Elementos**: - Lista de tags con resumen de reglas. - Tap en tag β†’ abrir pantalla "Editar Reglas de Tag". - BotΓ³n [+ Crear etiqueta] (disabled en Free, enabled en Premium). **NavegaciΓ³n**: tap en tag β†’ Pantalla 5.1. --- #### 5.1: Editar Reglas de Tag ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ← Carne [βœ“] β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ πŸ”΄ Carne β”‚ β”‚ β”‚ β”‚ MΓ‘ximo por semana β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ [1] [2] [3] [4] [5] [6] [7] [∞] β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ Seleccionado: 3 β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ No consecutivo [ON] β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ Evita que aparezca dΓ­as seguidos β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ No repetir en el mismo dΓ­a [ON] β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ No puede estar en comida y cena el mismo β”‚ β”‚ dΓ­a β”‚ β”‚ β”‚ β”‚ RestricciΓ³n de horario β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Sin restricciΓ³n β–Ό β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ Opciones: Sin restricciΓ³n / Solo comida / β”‚ β”‚ Solo cena β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Elementos**: - Pill de color del tag (centrado). - **Max por semana**: segmented control (1-7, ∞). - **Toggles**: no consecutivo, no mismo dΓ­a. - **RestricciΓ³n horario**: picker (sin restricciΓ³n / solo comida / solo cena). - BotΓ³n [βœ“] (top-right): guardar y volver. **NavegaciΓ³n**: tap en [βœ“] β†’ guardar y volver a lista de tags. --- ### Pantalla 6: Settings ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ← Ajustes β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ 🍽️ PLANIFICACIΓ“N β”‚ β”‚ β”‚ β”‚ Ventanas de comida β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Solo cenas β–Ό β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ Incluir fines de semana β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ [ON] β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ ───────────────────────────────────── β”‚ β”‚ β”‚ β”‚ πŸ“… CALENDARIO β”‚ β”‚ β”‚ β”‚ Sincronizar β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ [OFF] β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ [Si ON, mostrar:] β”‚ β”‚ Calendario β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Personal β–Ό β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ Hora de comidas β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ 14:00 ⏰ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ Hora de cenas β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ 21:00 ⏰ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ DuraciΓ³n del evento β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ 1 hora β–Ό β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ Prefijo del tΓ­tulo β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ 🍽️ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ Recordatorio β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Sin recordatorio β–Ό β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ ───────────────────────────────────── β”‚ β”‚ β”‚ β”‚ 🏷️ ETIQUETAS β”‚ β”‚ β”‚ β”‚ Gestionar etiquetas [β€Ί] β”‚ β”‚ β”‚ β”‚ ───────────────────────────────────── β”‚ β”‚ β”‚ β”‚ 🌍 IDIOMA β”‚ β”‚ β”‚ β”‚ Idioma β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ EspaΓ±ol β–Ό β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ ───────────────────────────────────── β”‚ β”‚ β”‚ β”‚ ⭐ PREMIUM β”‚ β”‚ β”‚ β”‚ Estado: Free β”‚ β”‚ Quitar publicidad [β€Ί] β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Elementos**: - Secciones separadas por headers (emoji + tΓ­tulo). - Separadores visuales (lΓ­nea gris claro). - Controles nativos de iOS (Toggle, Picker, TextField). - NavegaciΓ³n a sub-pantallas con [β€Ί]. **NavegaciΓ³n**: - **Gestionar etiquetas** β†’ Pantalla 5. - **Quitar publicidad** β†’ Pantalla de IAP (In-App Purchase). --- ### Pantalla 7: Premium / IAP ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ← Premium [Γ—] β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ ✨ MealMood Premium ✨ β”‚ β”‚ β”‚ β”‚ Disfruta de todas las funciones β”‚ β”‚ β”‚ β”‚ βœ“ Sin publicidad β”‚ β”‚ βœ“ Platos ilimitados β”‚ β”‚ βœ“ Etiquetas personalizadas β”‚ β”‚ βœ“ Reglas avanzadas (prΓ³ximamente) β”‚ β”‚ β”‚ β”‚ ───────────────────────────────────── β”‚ β”‚ β”‚ β”‚ Planes disponibles: β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Mensual β”‚ β”‚ β”‚ β”‚ $2.99 / mes β”‚ β”‚ β”‚ β”‚ [Suscribirse] β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Anual β”‚ β”‚ β”‚ β”‚ $19.99 / aΓ±o (ahorra 44%) β”‚ β”‚ β”‚ β”‚ [Suscribirse] β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ [Restaurar compras] β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Elementos**: - Header con tΓ­tulo y botΓ³n cerrar [Γ—]. - Logo premium (placeholder con ✨). - Lista de beneficios con checkmarks. - Cards de planes (mensual / anual). - Botones "Suscribirse" (StoreKit). - Link "Restaurar compras". **NavegaciΓ³n**: - Tap en "Suscribirse" β†’ StoreKit purchase flow. - Tap en [Γ—] β†’ cerrar sheet. --- ## Interacciones y Micro-animaciones ### 1. Drag & Drop **Gestos**: 1. **Long press** (200ms) en card de plato del drawer: - Card se "levanta" con escala 1.05. - Sombra aumenta (radius: 12, y: 8). - Haptic feedback: `.light`. 2. **Drag** sobre slot: - **VΓ‘lido** (respeta reglas): - Slot muestra borde verde (#A8D5BA, 2px). - Haptic feedback: `.light` (suave, continuo cada 100ms). - **InvΓ‘lido** (viola reglas): - Slot muestra borde rojo (#FF9999, 2px). - Haptic feedback: `.rigid` (fuerte, ΓΊnico). 3. **Drop**: - **VΓ‘lido**: - AnimaciΓ³n "plop": slot hace scale 1.0 β†’ 1.1 β†’ 1.0 (duration: 0.3s). - Plato desaparece del drawer con fade out. - Haptic feedback: `.success`. - **InvΓ‘lido**: - Plato vuelve al drawer con spring animation (bouncy). - Haptic feedback: `.error`. **CΓ³digo ejemplo (SwiftUI)**: ```swift .onLongPressGesture(minimumDuration: 0.2) { // Iniciar drag withAnimation(.spring()) { draggedDish = dish isLifted = true } HapticManager.shared.impact(style: .light) } .gesture( DragGesture() .onChanged { value in dragOffset = value.translation validateDrop(at: value.location) } .onEnded { value in performDrop(at: value.location) } ) ``` --- ### 2. Completar AutomΓ‘ticamente **Flujo**: 1. Usuario tap en botΓ³n "Completar ✨". 2. BotΓ³n muestra loading indicator (ProgressView, color coral). 3. Algoritmo ejecuta en background (no bloquea UI). 4. AnimaciΓ³n secuencial: - Slots vacΓ­os se rellenan uno por uno (delay: 150ms entre cada uno). - Cada slot hace fade in + scale animation (0.8 β†’ 1.0). 5. Al terminar: - Confetti suave (partΓ­culas pequeΓ±as, colores pastel, duration: 2s). - Haptic feedback: `.success`. - Toast: "Β‘Listo! Tu semana estΓ‘ completa πŸŽ‰" (auto-dismiss en 3s). **CΓ³digo ejemplo (SwiftUI)**: ```swift func autoComplete() { isLoading = true Task { let filledSlots = await MealPlanner.shared.autoComplete(weekPlan: weekPlan) for (index, slot) in filledSlots.enumerated() { try? await Task.sleep(nanoseconds: 150_000_000) // 150ms withAnimation(.spring()) { weekPlan.slots[slot.id] = slot } HapticManager.shared.impact(style: .light) } isLoading = false showConfetti = true HapticManager.shared.notification(type: .success) showToast = true } } ``` --- ### 3. Resetear **Flujo**: 1. Usuario tap en botΓ³n "Resetear πŸ”„". 2. Alert suave (no modal agresivo): ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ΒΏLimpiar la semana completa? β”‚ β”‚ β”‚ β”‚ Esto no se puede deshacer β”‚ β”‚ β”‚ β”‚ [Cancelar] [Limpiar] β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` 3. Si usuario confirma: - Todos los slots vacΓ­os hacen fade out (simultΓ‘neo, duration: 0.3s). - Haptic feedback: `.warning`. - Platos vuelven al drawer con fade in (staggered, delay: 50ms entre cada uno). **CΓ³digo ejemplo (SwiftUI)**: ```swift func resetWeek() { withAnimation(.easeOut(duration: 0.3)) { for slot in weekPlan.slots { slot.dish = nil } } HapticManager.shared.notification(type: .warning) } ``` --- ### 4. Micro-interactions Adicionales #### 4.1: Empty State (sin platos) - IlustraciΓ³n de familia con plato vacΓ­o. - Texto: "AΓ±ade tu primer plato para empezar". - BotΓ³n "AΓ±adir plato" (primary, centrado). #### 4.2: Pull to Refresh (en lista de platos) - Arrastrar hacia abajo β†’ loading indicator. - Aunque no haya backend, simular recarga con delay 1s. #### 4.3: Swipe to Delete (en lista de platos) - Swipe left β†’ botΓ³n "Eliminar" (rojo, icono πŸ—‘οΈ). - Tap en eliminar β†’ animaciΓ³n de desapariciΓ³n (slide left + fade out). #### 4.4: Toast Notifications - PosiciΓ³n: top-center. - Background: coral suave con opacity 0.95. - Sombra suave. - Auto-dismiss en 3s. - Ejemplos: - "Plato guardado βœ“" - "Evento eliminado del calendario" - "No puedo completar con las reglas actuales" --- ## IntegraciΓ³n con Calendario Nativo ### Permisos **Solicitar permiso** al activar sincronizaciΓ³n por primera vez: ```swift import EventKit let eventStore = EKEventStore() func requestCalendarAccess() async -> Bool { do { return try await eventStore.requestFullAccessToEvents() } catch { print("Error requesting calendar access: \(error)") return false } } ``` **Si usuario deniega**: - Mostrar alert: "MealMood necesita acceso a tu calendario para sincronizar eventos. Ve a Ajustes > MealMood > Calendario." - Link directo a Settings del sistema: `UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)` --- ### Crear Evento **FunciΓ³n**: ```swift func createEvent( slot: MealSlot, dish: Dish, weekStartDate: Date, settings: Settings ) -> String? { guard let calendarId = settings.calendarId, let calendar = eventStore.calendar(withIdentifier: calendarId) else { return nil } let event = EKEvent(eventStore: eventStore) // TΓ­tulo let prefix = settings.eventPrefix.isEmpty ? "" : "\(settings.eventPrefix) " let mealName = slot.mealType == .lunch ? "Comida" : "Cena" event.title = "\(prefix)\(mealName): \(dish.name)" // Fecha y hora let slotDate = weekStartDate.addingDays(slot.dayOfWeek) let slotTime = slot.mealType == .lunch ? settings.lunchTime : settings.dinnerTime event.startDate = combine(date: slotDate, time: slotTime) event.endDate = event.startDate.addingMinutes(settings.eventDuration) // Nota event.notes = dish.description // Calendario event.calendar = calendar // Alerta if let reminderMinutes = settings.reminderMinutesBefore { let alarm = EKAlarm(relativeOffset: -TimeInterval(reminderMinutes * 60)) event.addAlarm(alarm) } // Guardar do { try eventStore.save(event, span: .thisEvent) return event.eventIdentifier } catch { print("Error saving event: \(error)") return nil } } ``` --- ### Eliminar Evento **FunciΓ³n**: ```swift func deleteEvent(eventId: String) { guard let event = eventStore.event(withIdentifier: eventId) else { return } do { try eventStore.remove(event, span: .thisEvent) } catch { print("Error deleting event: \(error)") } } ``` --- ### Actualizar Eventos (cambio de hora) **FunciΓ³n**: ```swift func updateEventsTime(weekPlan: WeekPlan, newLunchTime: Date, newDinnerTime: Date) { for slot in weekPlan.slots { guard let eventId = slot.calendarEventId, let event = eventStore.event(withIdentifier: eventId), event.startDate >= Date() else { // solo futuros continue } // Actualizar hora let newTime = slot.mealType == .lunch ? newLunchTime : newDinnerTime let newStartDate = combine(date: event.startDate, time: newTime) event.startDate = newStartDate event.endDate = newStartDate.addingMinutes(settings.eventDuration) // Guardar do { try eventStore.save(event, span: .thisEvent) } catch { print("Error updating event: \(error)") } } } ``` --- ### Helpers ```swift extension Date { func addingDays(_ days: Int) -> Date { Calendar.current.date(byAdding: .day, value: days, to: self)! } func addingMinutes(_ minutes: Int) -> Date { Calendar.current.date(byAdding: .minute, value: minutes, to: self)! } } func combine(date: Date, time: Date) -> Date { let calendar = Calendar.current let dateComponents = calendar.dateComponents([.year, .month, .day], from: date) let timeComponents = calendar.dateComponents([.hour, .minute], from: time) var combined = DateComponents() combined.year = dateComponents.year combined.month = dateComponents.month combined.day = dateComponents.day combined.hour = timeComponents.hour combined.minute = timeComponents.minute return calendar.date(from: combined)! } ``` --- ## Algoritmo de Autocompletado ### PseudocΓ³digo ```python def autocomplete(week_plan, available_dishes, tags_rules): """ Rellena slots vacΓ­os respetando reglas de tags. Args: week_plan: WeekPlan actual available_dishes: [Dish] todos los platos del usuario tags_rules: {tag_id: TagRules} reglas de cada tag Returns: WeekPlan actualizado """ empty_slots = week_plan.get_empty_slots() for slot in empty_slots: # Filtrar platos vΓ‘lidos candidates = filter_valid_dishes( dishes=available_dishes, slot=slot, current_plan=week_plan, rules=tags_rules ) if len(candidates) == 0: continue # dejar slot vacΓ­o # Priorizar platos menos usados esta semana dish = weighted_random_pick(candidates, week_plan) week_plan.assign(slot, dish) return week_plan def filter_valid_dishes(dishes, slot, current_plan, rules): """Filtra platos que no violen reglas.""" valid = [] for dish in dishes: if not violates_rules(dish, slot, current_plan, rules): valid.append(dish) return valid def violates_rules(dish, slot, current_plan, rules): """ Verifica si asignar este plato al slot viola alguna regla. Returns: True si viola alguna regla, False si es vΓ‘lido """ for tag_id in dish.tags: tag_rule = rules[tag_id] # Regla 1: Max por semana if tag_rule.maxPerWeek is not None: count = current_plan.count_tag_usage(tag_id) if count >= tag_rule.maxPerWeek: return True # Regla 2: No consecutivo if tag_rule.noConsecutive: adjacent_slots = current_plan.get_adjacent_slots(slot) for adj_slot in adjacent_slots: if adj_slot.dish and tag_id in adj_slot.dish.tags: return True # Regla 3: No duplicar en el mismo dΓ­a if tag_rule.noDuplicateInDay: same_day_slots = current_plan.get_same_day_slots(slot) for sd_slot in same_day_slots: if sd_slot.dish and tag_id in sd_slot.dish.tags: return True # Regla 4: RestricciΓ³n de tipo de comida if tag_rule.mealTypeRestriction is not None: if slot.mealType != tag_rule.mealTypeRestriction: return True return False def weighted_random_pick(candidates, current_plan): """ Elige un plato aleatoriamente, priorizando los menos usados. LΓ³gica: - Platos con 0 usos: peso 3 - Platos con 1 uso: peso 2 - Platos con 2+ usos: peso 1 """ weights = [] for dish in candidates: usage_count = current_plan.count_dish_usage(dish.id) if usage_count == 0: weight = 3 elif usage_count == 1: weight = 2 else: weight = 1 weights.append(weight) # Random weighted choice import random return random.choices(candidates, weights=weights)[0] ``` --- ### Helpers ```swift extension WeekPlan { func getEmptySlots() -> [MealSlot] { slots.filter { $0.dish == nil } } func countTagUsage(_ tagId: UUID) -> Int { var count = 0 for slot in slots { if let dishId = slot.dish, let dish = DishRepository.shared.getDish(by: dishId), dish.tags.contains(tagId) { count += 1 } } return count } func countDishUsage(_ dishId: UUID) -> Int { slots.filter { $0.dish == dishId }.count } func getAdjacentSlots(_ slot: MealSlot) -> [MealSlot] { var adjacent: [MealSlot] = [] // DΓ­a anterior, mismo tipo de comida if slot.dayOfWeek > 0 { if let prevSlot = slots.first(where: { $0.dayOfWeek == slot.dayOfWeek - 1 && $0.mealType == slot.mealType }) { adjacent.append(prevSlot) } } // DΓ­a siguiente, mismo tipo de comida if slot.dayOfWeek < 6 { if let nextSlot = slots.first(where: { $0.dayOfWeek == slot.dayOfWeek + 1 && $0.mealType == slot.mealType }) { adjacent.append(nextSlot) } } return adjacent } func getSameDaySlots(_ slot: MealSlot) -> [MealSlot] { slots.filter { $0.dayOfWeek == slot.dayOfWeek && $0.id != slot.id } } } ``` --- ## SincronizaciΓ³n y Persistencia ### SwiftData (iOS 17+) #### ConfiguraciΓ³n ```swift import SwiftData @main struct MealMoodApp: App { var body: some Scene { WindowGroup { ContentView() } .modelContainer(for: [ Settings.self, Tag.self, Dish.self, WeekPlan.self, MealSlot.self ]) } } ``` #### Modelo de Datos ```swift @Model final class Settings { var mealWindows: String // "dinnerOnly", "lunchOnly", "both" var includeWeekends: Bool var language: String // "spanish", "english" var calendarId: String? var syncEnabled: Bool var lunchTime: Date var dinnerTime: Date var eventDuration: Int var eventPrefix: String var reminderMinutesBefore: Int? var isPremium: Bool init() { self.mealWindows = "dinnerOnly" self.includeWeekends = true self.language = "spanish" self.calendarId = nil self.syncEnabled = false self.lunchTime = Date() // set to 14:00 self.dinnerTime = Date() // set to 21:00 self.eventDuration = 60 self.eventPrefix = "🍽️" self.reminderMinutesBefore = nil self.isPremium = false } } @Model final class Tag { var id: UUID var name: String var color: String var maxPerWeek: Int? var noConsecutive: Bool var noDuplicateInDay: Bool var mealTypeRestriction: String? // "lunch", "dinner", nil init(id: UUID = UUID(), name: String, color: String, maxPerWeek: Int? = nil, noConsecutive: Bool = false, noDuplicateInDay: Bool = false, mealTypeRestriction: String? = nil) { self.id = id self.name = name self.color = color self.maxPerWeek = maxPerWeek self.noConsecutive = noConsecutive self.noDuplicateInDay = noDuplicateInDay self.mealTypeRestriction = mealTypeRestriction } } @Model final class Dish { var id: UUID var name: String var descriptionText: String? var tagIds: [UUID] var createdAt: Date init(id: UUID = UUID(), name: String, descriptionText: String? = nil, tagIds: [UUID] = [], createdAt: Date = Date()) { self.id = id self.name = name self.descriptionText = descriptionText self.tagIds = tagIds self.createdAt = createdAt } } @Model final class WeekPlan { var id: UUID var weekStartDate: Date var createdAt: Date var updatedAt: Date @Relationship(deleteRule: .cascade) var slots: [MealSlot] init(id: UUID = UUID(), weekStartDate: Date, slots: [MealSlot] = [], createdAt: Date = Date(), updatedAt: Date = Date()) { self.id = id self.weekStartDate = weekStartDate self.slots = slots self.createdAt = createdAt self.updatedAt = updatedAt } } @Model final class MealSlot { var id: UUID var dayOfWeek: Int var mealType: String // "lunch", "dinner" var dishId: UUID? var calendarEventId: String? init(id: UUID = UUID(), dayOfWeek: Int, mealType: String, dishId: UUID? = nil, calendarEventId: String? = nil) { self.id = id self.dayOfWeek = dayOfWeek self.mealType = mealType self.dishId = dishId self.calendarEventId = calendarEventId } } ``` --- ### CloudKit (SincronizaciΓ³n iCloud) #### ConfiguraciΓ³n 1. Habilitar CloudKit en Xcode: - Target β†’ Signing & Capabilities β†’ + Capability β†’ iCloud. - Activar "CloudKit". - Crear container (ej: `iCloud.com.mealMood.app`). 2. Configurar SwiftData para usar CloudKit: ```swift @main struct MealMoodApp: App { var body: some Scene { WindowGroup { ContentView() } .modelContainer( for: [Settings.self, Tag.self, Dish.self, WeekPlan.self, MealSlot.self], isStoredInMemoryOnly: false, isAutosaveEnabled: true, isUndoEnabled: false, cloudKitContainerIdentifier: "iCloud.com.mealMood.app" ) } } ``` **Comportamiento**: - Datos se sincronizan automΓ‘ticamente entre dispositivos del mismo usuario. - SincronizaciΓ³n en background (no requiere acciΓ³n del usuario). - Conflictos resueltos por "last write wins" (por defecto). --- ## MonetizaciΓ³n ### Free Tier **Limitaciones**: - MΓ‘ximo 20 platos. - Solo 10 tags predefinidos (no se pueden crear custom). - Solo reglas bΓ‘sicas (max/semana, no consecutivo, no mismo dΓ­a). - Banner AdMob en parte inferior de Home. **AdMob ConfiguraciΓ³n**: ```swift import GoogleMobileAds struct AdBannerView: UIViewRepresentable { func makeUIView(context: Context) -> GADBannerView { let banner = GADBannerView(adSize: GADAdSizeBanner) banner.adUnitID = "ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX" // reemplazar con tu ID banner.rootViewController = UIApplication.shared.windows.first?.rootViewController banner.load(GADRequest()) return banner } func updateUIView(_ uiView: GADBannerView, context: Context) {} } ``` **UbicaciΓ³n**: - Home β†’ parte inferior, sticky. - Background: #FFF9F5 (mismo color que la app). - Altura: 50px. - NO en otras pantallas (Settings, gestiΓ³n de platos, etc.). --- ### Premium Tier **Precio**: - Mensual: $2.99/mes. - Anual: $19.99/aΓ±o (ahorro 44%). **Beneficios**: - Sin publicidad. - Platos ilimitados. - Tags custom ilimitados. - (Futuro) Reglas avanzadas. - (Futuro) MΓΊltiples planificaciones guardadas. - (Futuro) Exportar a lista de compra. - (Futuro) Analytics/histΓ³rico. **IAP ConfiguraciΓ³n**: ```swift import StoreKit class StoreManager: ObservableObject { @Published var products: [Product] = [] @Published var isPremium: Bool = false private let productIds = [ "com.mealmood.premium.monthly", "com.mealmood.premium.yearly" ] init() { Task { await loadProducts() await checkPremiumStatus() } } func loadProducts() async { do { products = try await Product.products(for: productIds) } catch { print("Failed to load products: \(error)") } } func purchase(_ product: Product) async { do { let result = try await product.purchase() switch result { case .success(let verification): switch verification { case .verified(let transaction): await transaction.finish() isPremium = true case .unverified: break } case .userCancelled, .pending: break @unknown default: break } } catch { print("Purchase failed: \(error)") } } func restorePurchases() async { do { try await AppStore.sync() await checkPremiumStatus() } catch { print("Restore failed: \(error)") } } private func checkPremiumStatus() async { for await result in Transaction.currentEntitlements { if case .verified(let transaction) = result, productIds.contains(transaction.productID) { isPremium = true return } } isPremium = false } } ``` --- ## Stack TΓ©cnico ### Plataforma - **iOS**: mΓ­nimo iOS 17.0 (para SwiftData). - **Dispositivos**: iPhone (optimizado para todos los tamaΓ±os). - **OrientaciΓ³n**: Portrait only (landscape no necesario para MVP). ### Lenguajes y Frameworks - **Swift**: 5.9+. - **SwiftUI**: para toda la UI. - **SwiftData**: persistencia local. - **EventKit**: integraciΓ³n con calendario nativo. - **CloudKit**: sincronizaciΓ³n iCloud. - **StoreKit 2**: IAP (In-App Purchases). ### Dependencias Externas - **Google Mobile Ads SDK**: para AdMob. - InstalaciΓ³n: Swift Package Manager (SPM) o CocoaPods. - URL: `https://github.com/googleads/swift-package-manager-google-mobile-ads` ### Arquitectura - **MVVM** (Model-View-ViewModel): - **Models**: Settings, Tag, Dish, WeekPlan, MealSlot (SwiftData models). - **ViewModels**: HomeViewModel, DishViewModel, SettingsViewModel, etc. - **Views**: SwiftUI views. ### Testing - **Unit tests**: lΓ³gica de algoritmo de autocompletado, validaciΓ³n de reglas. - **UI tests**: flujos crΓ­ticos (onboarding, drag & drop, crear plato). --- ## Plan de ImplementaciΓ³n (Sprints) ### Sprint 1: Setup e Infraestructura (1 semana) - Configurar proyecto Xcode. - Configurar SwiftData models. - Configurar CloudKit. - Crear paleta de colores y componentes UI reutilizables. - Setup Google AdMob. **Entregables**: - Proyecto compilable con estructura de carpetas. - Modelos de datos persistentes. - Componentes UI bΓ‘sicos (buttons, cards, pills). --- ### Sprint 2: Onboarding (1 semana) - Pantalla de bienvenida. - 4 pasos de configuraciΓ³n inicial. - NavegaciΓ³n fluida entre pasos. - Crear tags predefinidos en primera carga. **Entregables**: - Onboarding completo y funcional. - Usuario puede completar setup inicial. --- ### Sprint 3: GestiΓ³n de Platos (1 semana) - Pantalla de lista de platos. - Crear/editar/eliminar platos. - Selector de tags (modal). - Validaciones de formulario. **Entregables**: - Usuario puede crear y gestionar platos. - Lista de platos persistente. --- ### Sprint 4: Pantalla Principal (Calendario) (2 semanas) - Layout de calendario semanal. - Slots vacΓ­os y llenos. - Drawer de platos. - Drag & drop bΓ‘sico (sin validaciones). - NavegaciΓ³n entre semanas. **Entregables**: - Pantalla Home funcional. - Usuario puede asignar platos manualmente. --- ### Sprint 5: ValidaciΓ³n de Reglas y Drag & Drop Avanzado (1 semana) - Implementar lΓ³gica de validaciΓ³n de reglas. - Feedback visual en drag & drop (bordes verdes/rojos). - Haptic feedback. - Animaciones. **Entregables**: - Drag & drop valida reglas en tiempo real. - Feedback visual y tΓ‘ctil funcional. --- ### Sprint 6: Autocompletado (1 semana) - Implementar algoritmo de autocompletado. - AnimaciΓ³n secuencial al rellenar slots. - Confetti y toast. - BotΓ³n "Resetear" con confirmaciΓ³n. **Entregables**: - BotΓ³n "Completar" funcional. - Usuario puede autocompletar semana respetando reglas. --- ### Sprint 7: GestiΓ³n de Tags (1 semana) - Pantalla de lista de tags. - Editar reglas de tags. - LocalizaciΓ³n de tags predefinidos. **Entregables**: - Usuario puede modificar reglas de tags. - Cambios se aplican al algoritmo. --- ### Sprint 8: SincronizaciΓ³n con Calendario (1 semana) - Solicitar permiso de EventKit. - Crear/eliminar eventos al asignar/quitar platos. - ConfiguraciΓ³n en Settings (horarios, prefijo, etc.). - Actualizar eventos al cambiar hora. **Entregables**: - SincronizaciΓ³n con calendario funcional. - Eventos visibles en app Calendario nativa. --- ### Sprint 9: Settings y Multiidioma (1 semana) - Pantalla de Settings completa. - Cambio de idioma (espaΓ±ol/inglΓ©s). - LocalizaciΓ³n de toda la app. - Persistencia de configuraciΓ³n. **Entregables**: - Settings funcional. - App totalmente bilingΓΌe. --- ### Sprint 10: IAP y Premium (1 semana) - Configurar StoreKit. - Pantalla de Premium con planes. - Compra y restauraciΓ³n. - Limitar features en Free. **Entregables**: - Usuario puede comprar Premium. - Features Free/Premium funcionan correctamente. --- ### Sprint 11: Testing y QA (1 semana) - Unit tests para algoritmo. - UI tests para flujos crΓ­ticos. - Bug fixing. - Performance optimization. **Entregables**: - App estable, sin bugs crΓ­ticos. - Cobertura de tests >70%. --- ### Sprint 12: Polish y Lanzamiento (1 semana) - Ajustes finales de UI/UX. - App Store assets (screenshots, descripciΓ³n, keywords). - Beta testing con TestFlight. - EnvΓ­o a App Store. **Entregables**: - App publicada en App Store. --- ## Anexos ### A. Glosario - **Slot**: espacio en el calendario donde se asigna una comida (comida o cena de un dΓ­a especΓ­fico). - **Tag**: etiqueta asociada a un plato (ej: Carne, Pescado, Sin Gluten). - **Tag Rule**: regla asociada a un tag (ej: mΓ‘ximo 3 veces por semana). - **Autocompletado**: algoritmo que rellena slots vacΓ­os respetando reglas. - **Drag & Drop**: interacciΓ³n de arrastrar un plato y soltarlo en un slot. - **EventKit**: framework de iOS para interactuar con el calendario nativo. - **SwiftData**: framework de Apple para persistencia local (iOS 17+). - **CloudKit**: servicio de Apple para sincronizaciΓ³n en la nube. - **IAP**: In-App Purchase (compra dentro de la app). --- ### B. Referencias TΓ©cnicas #### SwiftUI Drag & Drop - DocumentaciΓ³n: https://developer.apple.com/documentation/swiftui/view/ondrag(_:) - Tutorial: https://www.hackingwithswift.com/quick-start/swiftui/how-to-support-drag-and-drop-in-swiftui #### EventKit - DocumentaciΓ³n: https://developer.apple.com/documentation/eventkit - Tutorial: https://www.hackingwithswift.com/example-code/system/how-to-add-events-to-the-users-calendar-using-eventkit #### SwiftData - DocumentaciΓ³n: https://developer.apple.com/documentation/swiftdata - Tutorial: https://www.hackingwithswift.com/quick-start/swiftdata #### StoreKit 2 - DocumentaciΓ³n: https://developer.apple.com/documentation/storekit - Tutorial: https://www.hackingwithswift.com/quick-start/storekit #### Google Mobile Ads - DocumentaciΓ³n: https://developers.google.com/admob/ios/quick-start - Setup: https://developers.google.com/admob/ios/quick-start --- ### C. Notas de ImplementaciΓ³n #### Consideraciones de Performance - **SwiftData queries**: usar `@Query` con predicates para filtrar eficientemente. - **Drag & Drop**: mantener 60fps, usar `LazyVStack` en lista de platos. - **Algoritmo de autocompletado**: ejecutar en background thread (`Task`), no bloquear UI. - **SincronizaciΓ³n CloudKit**: usar queue de operaciones para batch updates. #### Consideraciones de UX - **Feedback inmediato**: haptic feedback en cada interacciΓ³n importante. - **Animaciones suaves**: spring animations (0.3-0.5s duration). - **Loading states**: mostrar ProgressView durante operaciones lentas. - **Empty states**: ilustraciones amigables, no solo texto. - **Error handling**: mostrar alerts/toasts amigables, no errores tΓ©cnicos. #### Consideraciones de Accesibilidad - **VoiceOver**: etiquetar todos los elementos interactivos. - **Dynamic Type**: usar `@ScaledMetric` para tamaΓ±os de fuente. - **Color Contrast**: asegurar ratio mΓ­nimo 4.5:1 (WCAG AA). - **Haptic Feedback**: opcional, respetar configuraciΓ³n del sistema. --- ### D. Checklist Pre-Lanzamiento #### Funcionalidad - [ ] Onboarding completo y sin bugs. - [ ] Crear/editar/eliminar platos funcional. - [ ] Drag & drop fluido (60fps). - [ ] ValidaciΓ³n de reglas correcta. - [ ] Autocompletado respeta reglas. - [ ] SincronizaciΓ³n con calendario funcional. - [ ] Settings guarda cambios correctamente. - [ ] Multiidioma (ES/EN) sin strings faltantes. - [ ] IAP (compra y restauraciΓ³n) funcional. - [ ] AdMob muestra ads correctamente. #### Testing - [ ] Unit tests para algoritmo (>80% cobertura). - [ ] UI tests para flujos crΓ­ticos. - [ ] Testeo manual en iPhone SE, iPhone 15 Pro, iPhone 15 Pro Max. - [ ] Beta testing con 10+ usuarios externos (TestFlight). - [ ] Performance: app carga en <2s, drag & drop sin lag. - [ ] Memoria: no memory leaks (Instruments). #### App Store - [ ] Screenshots en 3 tamaΓ±os (6.7", 6.5", 5.5"). - [ ] Video preview (opcional, recomendado). - [ ] DescripciΓ³n optimizada (keywords: meal planner, family meals, weekly menu). - [ ] Privacy policy URL. - [ ] Support URL. - [ ] CategorΓ­a: Food & Drink. - [ ] Keywords: meal planner, weekly menu, family meals, meal organizer, recipe planner. #### Legal - [ ] Privacy policy publicada (mencionar EventKit, CloudKit, AdMob). - [ ] Terms of Service (opcional, recomendado). - [ ] App Store Review Guidelines (revisar compliance). --- ## Fin del PRD **VersiΓ³n**: 1.0 **Fecha**: 2025-02-16 **Autor**: Alex + Claude **PrΓ³xima revisiΓ³n**: Post-Sprint 6 (evaluar ajustes de MVP) --- ## Notas para Claude Code Este PRD estΓ‘ diseΓ±ado para ser consumido por Claude Code. Contiene: 1. **Especificaciones completas** de funcionalidades MVP y Freemium. 2. **Modelo de datos** con Swift structs/classes. 3. **Ejemplos de cΓ³digo** para componentes clave (drag & drop, EventKit, SwiftData). 4. **Wireframes textuales** detallados de cada pantalla. 5. **Paleta de colores** y componentes UI reutilizables. 6. **Plan de implementaciΓ³n** en sprints secuenciales. **Instrucciones para Claude Code**: - Implementar MVP siguiendo orden de sprints. - Usar SwiftUI + SwiftData (iOS 17+). - Priorizar UX fluida (animaciones, haptic feedback). - CΓ³digo limpio, comentado, siguiendo Swift style guide. - Tests unitarios para lΓ³gica crΓ­tica (algoritmo, validaciones). **Contacto**: Si hay dudas o ambigΓΌedades, consultar con el product owner (Alex). --- Β‘Buena suerte con la implementaciΓ³n! πŸš€βœ¨