Sprint 2 - EstebanRojas01/Moviles GitHub Wiki

Sprint 2: App Implementation

UniEat is a daily-menu feed for students near Universidad de los Andes. This page records the implemented Sprint 2 work against the Sprint 1 business questions and the MS7 screen design. The project has iOS and Android subgroups. The iOS, shared-backend and Android evidence below is filled in by each subgroup from its own repository and commits. A design, local demo, passing build, and live user validation are different forms of evidence. None is presented as a substitute for another.

Component Repository / evidence Status represented here
iOS app UniEat---iOS Implemented SwiftUI client. macOS CI passed for main.
Shared service UniEat---iOS-Back Supabase Auth, PostgreSQL, and Edge Function api-v1. backend CI passed. BQ-03, BQ-04 and engagement remain scoped to iOS by default. The BQ-05 dashboard section and the ?platform= option of the performance endpoints include Android (backend PR #2, PR #4).
Android app UniEat-Android Kotlin + Jetpack Compose client. Base project, menu detail and BQ-05 location card by Camilo (PR #2, PR #4), feed, filters, recommendation, BQ-06 wait estimate and Performance tab by Juan José. Supabase sign-in, session, profile and BQ-01 instrumentation by Samuel (PR #23). Connected to the same shared backend deployment as the iOS app.

1. Business questions implemented

The identifiers, questions, types, and original owners come from Sprint 1 §6.2. Type 2 is appropriate because the answer is delivered to a student during a decision, rather than being only an internal diagnostic. The admin dashboard measures how those answers are used. It does not replace the answers in the app. BQ-05 and BQ-06 are implemented in the Android client, and BQ-01 client instrumentation is in place (see the Android table below), on iOS, BQ-05/06 concepts support some views but are not claimed as fully implemented business questions.

BQ Original owner Type and rationale Implemented answer and data Evidence
BQ-03: Relevant Menus for the Current Student. Which currently valid publications should appear first given budget, diet, area and available minutes? Kevin David Alvarez Romero Type 2: a ranked and explained feed is shown to the student while choosing food. Active publications, structured dish prices/dietary declarations, establishment area, time and wait evidence are combined with selected filters. rank-v1 rejects incompatible menus and explains ordering and uncertainty. FiltersView, TodayFeedView, backend ranking Strategy, GET /feed.
BQ-04: Publication Validity and Reported Changes. What validity and reported-change status should be shown for the publication being consulted? Juan Esteban Rojas Castañeda Type 2: status and qualified notices are shown in the menu detail before the student travels. Server time, publication/version history, expiry and unresolved reports determine active/expiring/expired/closed states and pending-versus-confirmed notices. A student report does not silently rewrite the official menu. MenuDetailView, ReportSheet, backend publication rules, GET /menus/:id.

The original BQ-03/BQ-04 wording and the BQ-04 reformulation rationale remain in Sprint 1. The current implementation uses an approximate area-based walk time and requires at least three distinct recent observations (within 30 minutes) for a queue estimate. It does not present an unknown dietary declaration as a verified match or an unsupported wait as zero.

Android business questions

Team member Sprint 1 BQ identifier and exact question Type and rationale Implemented answer Repository evidence
Camilo Andrés Molina Plata BQ-05: Location Guidance for the Selected Establishment. For the establishment a student has selected, which entrance and location references are currently available, and which need an uncertainty warning because details are missing or location discrepancies remain unresolved? Type 2: the location card is shown to the student in the menu detail, before they walk to the establishment. A pure Kotlin function turns a DailyMenu into LocationGuidance: available references (pin, storefront photo, address, entrance description), an explicit label for each missing one, a warning when location reports remain pending, and the last update time. The card renders it with an OpenStreetMap pin, GPS walking distance and an "Abrir en Google Maps" action. Opening Maps emits location_open, and every request carries X-UniEat-Platform: android, so BQ-05 usage and location reports are attributed to Android instead of unknown. LocationGuidance.kt, LocationGuidanceTest.kt, LocationCard.kt, PR #2, PR #10
Juan José Murillo BQ-06. How long would a student wait in line, and how reliable is that estimate? Type 2: lunch decisions happen inside a fixed break, so the wait matters as much as the price. The estimate is shown on the feed card and in the detail while the student is choosing. The backend aggregates community wait reports and sends waitMinutes, waitSampleCount and waitNewestReportAt. Feed cards and the detail show "~N min de fila" only when the evidence rule holds (at least three reports, the newest within 30 minutes) and "Fila sin datos" otherwise. The client never makes up a number. FeedScreen, MenuDetailScreen, wait-evidence unit tests
Samuel David Rozen Mogollon BQ-01. Over the last seven days, which combinations of connection type, device model, and OS version had the highest feed-loading failure rates and 95th-percentile request-to-render times, broken down by hour? Type 1: a technical diagnostic of feed-loading reliability across devices and networks, used by the team rather than shown to the student. Android captures load identifiers, timestamps, outcome, connection type, device model and OS version, buffered locally. A load pending for more than 60 seconds is reported as abandoned and excluded from the technical-failure denominator, since leaving the screen is not evidence of a network or render failure. Client instrumentation only: the shared ingestion and aggregation endpoint is still pending, so this BQ is not yet answered in the analytics system.

2. Implemented functionality and responsibility

The implementation responsibility below follows code and PR history. It does not reassign authorship of the earlier MS7 mockups. The iOS subgroup built one end-to-end flow across the SwiftUI client and the shared service. Kevin built the initial SwiftUI app and later role/analytics extensions. Juan Esteban built the initial shared service, connected the live iOS client to api-v1, and implemented the BQ-04 detail updates and queue-evidence refinement. These contributions depend on each other for a working app. “shared feature” does not mean that both edited every file. iOS commit history and backend commit history remain the record for individual contributions.

Member Verifiable contribution to the subgroup Direct evidence
Kevin Initial SwiftUI screens and local demo. Filters, location and recommendation UI. Subsequent role controls, saved-menu flow and combined analytics dashboard. Initial iOS client, roles, MS7 analytics PR #14.
Juan Esteban Initial Supabase/PostgreSQL API and domain rules. First live iOS-to-API integration. BQ-04 validity/version detail. Queue-v2 evidence threshold. Initial backend, iOS API integration, BQ-04 PR #11, queue-v2.
iOS functionality Implementation responsibility Behavior and evidence
Account registration, sign-in, session restore and sign-out Kevin (initial client UI), Juan Esteban (base service and live iOS authentication integration) AuthView, SupabaseAuthService, Supabase Auth and Keychain. Registration limits the display name to 15 characters and password to 6–20 with uppercase, lowercase, digit and special character.
Live iOS-to-service integration across features Juan Esteban (initial APIClient and AppStore API wiring), Kevin (subsequent integration and feature extensions) Integration commit connected the existing SwiftUI app to api-v1, touching feed/detail state, authentication, reports and publication. Follow-up integration and later features extended this path.
Effective roles and authorized navigation Kevin (client and admin extension), Juan Esteban (base service) GET /me supplies the database role. Students cannot open the global dashboard. Approved restaurant members see their own publication tools and scoped metrics. Admin role changes require an existing admin and are audited. MainTabsView, AdminRolesView.
Current feed, filters and explained ranking (BQ-03) Kevin (feed/filter UI), Juan Esteban (initial backend ranking and live feed API connection) Budget, available minutes, diet, area and payment method filter current menus. Backend rank-v1 orders the real feed. The client retains server order. TodayFeedView, FiltersView.
One-shot location suggestion and connectivity adaptation Kevin LocationContextService reads CoreLocation on request. The nearest published establishment within an approximate 800 m suggests a zone, with manual fallback. NWPathMonitor and cached feed adapt to connectivity.
Contextual recommendation, “Elige por mí” Kevin (recommendation UI and interaction), Juan Esteban (server ranking Strategy used by live feed) RecommendationView walks compatible menus in contextual ranking order and displays the reason. It is a rule-based recommendation, not a trained prediction or random draw.
Menu detail and publication validity (BQ-04) Juan Esteban (live status, version and report details), Kevin (initial view and later actions) MenuDetailView fetches the current version, displays expiry and pending changes, and restricts actions on unavailable publications. Juan Esteban's BQ-04 PR #11 and follow-up PR #13 are linked evidence.
Structured menu publication, revision and closure Kevin (initial iOS form), Juan Esteban (base backend and live publish API connection) Approved restaurants submit dishes, prices, dietary declarations, payment details and validity. Each revision preserves version history. PublishView, POST /menus, PUT /menus/:id, POST /menus/:id/close.
Establishment request and approval Kevin (iOS flow), Juan Esteban (base backend) A restaurant requests an establishment. Approval is admin-only. The request can include a public HTTPS photo URL, displayed in feed/detail when present. EstablishmentManagementView. Image-file upload is not implemented.
Community reports and queue evidence Kevin (initial report form), Juan Esteban (live iOS report connection, backend report/version rules and queue-v2) ReportSheet sends version-linked discrepancies/observations. Pending discrepancies are not confirmed corrections. Insufficient wait evidence has its own state.
Saved menus and directions Kevin A student saves/removes a menu, recovers saved menus from Profile and opens Apple Maps directions. menu_save and location_open events enter the analytics queue. SavedMenusView, MenuDetailView.
Offline/demo states Kevin A clearly labelled local demo uses local publications. Real accounts use the shared API and an account/filter-scoped cache with fetch time. Offline, no-menu and insufficient-evidence views remain navigable. AppStore, OfflineFeedView.
Admin dashboard and restaurant metrics Kevin (iOS and later backend extension), Juan Esteban (base event service) The admin's PerformanceView places BQ-03, BQ-04 and engagement counts in one 7/28-day dashboard. Restaurants see only approved establishments. The backend also includes an English HTML dashboard that polls every three seconds.
Screen-gallery route and Spanish interface Kevin ScreenGalleryView links the ten MS7 view states, subject to role checks. Quicksand is bundled with its license. Remote restaurant photos have a placeholder when absent.

Android functionality

Functionality and view Implementer Behavior, repository path and PR/commit evidence
Android base project: Gradle setup, models, ApiClient, MenuRepository, navigation and theme Camilo Shared foundation ported from the iOS models and the api-v1 contract. Contract tests decode the documented feed and 410 responses. MenuRepository.kt, 40ce6a3
Menu detail and BQ-05 location card (view 03) Camilo MenuDetailViewModel exposes loading/content/gone/session-expired/error states. The card shows the OpenStreetMap pin, address, entrance, missing-information labels and pending-report warnings. MenuDetailViewModel.kt, LocationCard.kt, PR #2, PR #4
GPS walking distance, "Tú" marker and arrival prompt Camilo FusedLocationProviderClient with Haversine walking distance and the student's position on the map. Explained permission request. Full fallback when permission is denied or GPS is off. "¿Ya llegaste?" within ~50 m. PR #2, PR #4
Report a change (view 06) Camilo Bottom sheet sending POST /reports, including the location kind that feeds the BQ-05 warning. ReportSheet.kt, PR #2
Session expiry in the menu detail (authentication integration) Camilo AUTH_REQUIRED on GET /menus/:id becomes a SessionExpired state and returns to Samuel's login with a cleared back stack, as the feed already does, instead of an error whose retry could never succeed. MenuDetailViewModel.kt, PR #27
Recommendation reason in the detail (smart-feature integration) Camilo "Elige por mí" passes the chosen criterion and the backend rank-v1 explanation to the detail route, which shows "Por qué te lo recomendamos". Opened from the feed, no card appears. RecommendationNote.kt, PR #28
Platform attribution, location_open and BQ-05 metrics card (view 10) Camilo X-UniEat-Platform: android on every request and location_open from the Maps buttons (PR #10). The Rendimiento tab uses each role's endpoint (restaurants: /restaurant/performance), requests platform=all, labels the platform scope the server reports, and adds a BQ-05 card split iOS / Android / no platform with location-reference coverage (PR #29).
Feed tab with BQ-06 wait estimate (views 01 and 08) Juan José Menus in backend rank-v1 order. Each card shows restaurant, dish, price, area, the BQ-06 wait sticker, a near-expiry warning and the backend explanation. Empty, offline, server-error and expired-session states have their own screens. FeedViewModel, FeedScreen
Filters sheet (view 02) Juan José Budget slider plus time, diet, area and payment chips, the five parameters GET /feed accepts. A local draft is applied only when the user confirms, and the filters are shared between the feed and recommendation tabs.
Recommendation tab, "Elige por mí" (view 04) Juan José Active-filter summary, interchangeable selection criteria, the recommended menu from the backend rank-v1 options with its explanation, and a button to ask for another option.
Live expiry with the server clock Juan José Menus disappear when their valid-until time passes, judged with the server clock rather than the device clock. Stale responses are discarded when the filters change while a request is in flight.
Performance tab (view 10) Juan José (tab), Camilo (BQ-05 card, see above) Metrics from GET /performance (admins) or GET /restaurant/performance (restaurants, limited to their establishments) on one screen, with a 7/28-day selector, a notice when the backend marks the data as insufficient, and a restricted state for accounts without access. The client computes nothing.
Feed analytics events Juan José feed_impression the first time a card actually becomes visible, once per menu per session, plus selection from the detail, through the shared EventTracker queue.
Login Samuel Supabase email/password form with client validation and idle/loading/error/success states. The demo entry remains available when no backend is configured. PR #23
Session management Samuel SessionManager restores the persisted session, refreshes an expiring access token through Supabase Auth (one-minute margin, Mutex against duplicate refreshes), and always clears local credentials on sign-out even if the remote logout fails. The refresh token is stored in DataStore and is not encrypted at rest, a known limitation before production use. PR #23
Profile Samuel Authenticated data from GET /me: display name, role and establishment memberships, with retry and sign-out actions.
Role-adaptive navigation Samuel The navigation shell shows the tabs allowed by the role from GET /me. A profile-load failure is visible and retryable instead of silently assuming a role.
Shake-to-refresh Samuel The accelerometer refreshes the feed while the screen is resumed. Shakes are ignored while a load is already running.
"Menor fila" recommendation criterion Samuel An additional RecommendationStrategy that picks the shortest supported wait estimate, keeping backend order as the tie-breaker.
BQ-01 feed-load instrumentation Samuel Load records with connection type, device model, OS version, timestamps and outcome, buffered locally. Abandoned loads are separated from failures. Shared ingestion and aggregation are still a backend dependency.

3. Analytics pipeline and business-question answers

flowchart LR
    F["iOS feed request + selected filters"] --> API["Shared API v1"]
    D["iOS menu-detail request"] --> API
    E["iOS interaction: impression, open, save, directions, selection, arrival"] --> Q["Persisted local event queue"]
    Q --> B["POST /events/batch: validate + deduplicate eventId"]
    R["iOS change or queue report"] --> API
    API --> P[("PostgreSQL: publications, versions, reports")]
    API --> FQ[("feed_queries: outcome + filters + valid results")]
    API --> DQ[("menu_detail_queries: status + notice counts")]
    B --> IE[("interaction_events: platform + version + session")]
    API --> CR[("community_reports: platform + resolution")]
    P --> A["SQL snapshot / ranking / aggregates"]
    FQ --> A
    DQ --> A
    IE --> A
    CR --> A
    A --> U["BQ-03 ranked feed, BQ-04 detail status"]
    A --> DB["GET /admin/dashboard?days=7|28"]
    DB --> UI["iOS admin Rendimiento + English HTML dashboard"]

Pipeline rationale. Publication validity comes from the server clock and versioned data, so a stale device clock cannot make an expired menu current. Events carry an idempotent eventId plus session, publication and version. The persisted queue retries after connectivity returns without double counting. The service records iOS platform attribution for feed/detail requests, events and reports. Older unattributed records stay unknown rather than being relabelled. SQL aggregates 7/28-day observations for a single dashboard. The server checks the current admin role before returning global metrics, and hides rates with fewer than five sessions or zero impressions. Selection is not a purchase. Arrival is self-reported. The local demo is illustrative and never contributes fabricated production metrics. See AppStore tracking, analytics handlers, MS7 SQL migration, and dashboard contract.

Answer or metric Observation and processing User-facing output
BQ-03 Current publications and dishes + chosen constraints. Compatible items ranked with rank-v1. feed_queries records request outcome, contextual filters and count of valid results. Ordered feed and explanation. Dashboard shows total, successful, failed, contextual and zero-result requests plus average valid results.
BQ-04 Publication/version timestamps and report status. menu_detail_queries records status and notice counts at a detail request. Live detail validity and pending/confirmed warnings. Dashboard shows active, expiring, expired, closed and notice-bearing detail views.
Supporting engagement Deduplicated interaction events and iOS-attributed reports. Impressions, detail opens, saves, directions, reports, selections, self-reported arrivals, sample size and guarded rates in the same period.

Android analytics pipeline and BQ mapping

flowchart LR
    D["Android menu detail: GET /menus/:id"] --> API["Shared API v1"]
    D --> G["LocationGuidance (BQ-05): available, missing, pending reports"]
    G --> CARD["Location card"]
    FE["feed_impression, selection (feed)"] --> Q["Persisted EventQueue (retry on reconnect)"]
    E["detail_open, location_open, arrival"] --> Q
    Q --> B["POST /events/batch, platform = android"]
    B --> IE[("interaction_events")]
    R["Location report (X-UniEat-Platform: android)"] --> API
    API --> CR[("community_reports: platform + pending / resolved")]
    CR --> G
    CR --> W["Server wait aggregation (BQ-06)"]
    W --> STK["Wait sticker: ~N min de fila / Fila sin datos"]
    IE --> AGG["location_guidance_snapshot: by platform"]
    CR --> AGG
    AGG --> DASH["BQ-05 section: HTML dashboard + Android Rendimiento card"]
    L["Feed-load records (BQ-01), buffered locally"] -. "ingestion endpoint pending" .-> API

Android pipeline rationale. BQ-05 is answered at the moment of decision: the menu detail returns the establishment's location fields and its community reports, and the pure LocationGuidance function decides which references are available, which are missing, and whether unresolved location reports require a warning. Students close the loop by submitting location reports, which stay pending until resolved and are never shown as confirmed corrections. Usage of the answer is measured with detail_open, location_open (opening Google Maps from the card) and self-reported arrival events, sent with a client-generated eventId and platform = "android" through a persisted queue that retries after connectivity returns, so the shared service can deduplicate them. Reports carry the X-UniEat-Platform: android header, so the BQ-05 dashboard section can split iOS, Android and unmarked records instead of counting Android as unknown. Location-reference coverage (coordinates, entrance description, photo) is computed on the server from the establishments, independent of the period. BQ-06 is aggregated on the server from community wait reports, and the client only displays the estimate when the evidence rule holds. BQ-01 records are captured and buffered on the device, but the dashed line marks that their shared ingestion and aggregation are not implemented yet.

Android event/data source Processing and platform attribution BQ/metric Dashboard or in-app output Evidence
detail_open, location_open, arrival events Client-generated eventId UUID and platform = "android". Persisted local queue retried on reconnect behind the reusable EventTracker interface. POST /events/batch deduplicates by eventId. BQ-05 usage: Maps opens from the location card and self-reported arrivals BQ-05 section of GET /admin/dashboard (HTML dashboard) and the Rendimiento card, split by platform (backend PR #2) EventTracker.kt, EventQueue.kt
location community reports POST /reports with kind location and header X-UniEat-Platform: android, stored as pending until confirmed or dismissed. BQ-05 discrepancy warning. Pending vs. resolved reports Warning on the location card. Pending / confirmed / dismissed counts by platform in the BQ-05 section ReportSheet.kt
feed_impression, selection events (Juan José) feed_impression the first time a card becomes visible, once per menu per session. selection from the detail. Same EventTracker queue and platform = "android". Feed engagement on Android Performance tab requesting platform=all FeedViewModel
Community wait reports → waitMinutes, waitSampleCount, waitNewestReportAt (Juan José) Aggregated on the server. The estimate is shown only with at least three reports, the newest within 30 minutes. BQ-06 "~N min de fila" or "Fila sin datos" on feed cards and in the detail FeedScreen, MenuDetailScreen
Feed-load records (Samuel) Load id, timestamps, outcome, connection type, device model and OS version, buffered locally. Loads pending over 60 s count as abandoned. BQ-01 Pending: the shared ingestion/aggregation endpoint does not exist yet

4. Architecture, communication and rationale

flowchart LR
    subgraph IOS["iOS client (implemented)"]
      V["SwiftUI Views"] --> S["AppStore: presentation state/actions"]
      S --> C["UniEatCore: models + decisions"]
      S --> LR["DemoMenuRepository / cache"]
      S --> AU["SupabaseAuthService / Keychain"]
      S --> AC["APIClient"]
      S --> LC["CoreLocation + NWPathMonitor"]
    end
    AND["Android client (see the Android diagram below)"] -->|"HTTPS JSON + user JWT"| EDGE
    AU --> AUTH["Supabase Auth"]
    AC -->|"HTTPS JSON + user JWT"| EDGE["Edge Function api-v1: HTTP facade"]
    EDGE --> APP["Application services + authorization"]
    APP --> DOM["Domain: ranking, validity, validation"]
    APP --> REP["Repository / DAO"]
    REP --> DB[("PostgreSQL: RLS, versions, aggregates")]

Architecture rationale. SwiftUI views observe one presentation store. Pure UniEatCore decisions can be tested without iOS UI. The client holds only a publishable key and stores session tokens in Keychain. The shared HTTPS API validates the JWT, derives permissions from server-owned profiles/memberships, then applies domain rules through a repository. PostgreSQL is the source of truth for versioned publications and aggregates. Its RLS and constraints are a second boundary. The Android client uses the same shared API and backend deployment as iOS. See iOS architecture and backend architecture and tactics.

Pattern or tactic Responsible implementation Why it was chosen and where it is implemented
MVVM (architectural, iOS) Kevin (initial views/store), Juan Esteban (live API state and BQ-04 detail integration) SwiftUI Views render and send actions. AppStore owns observable state and coordinates services, keeping navigation and loading logic outside individual screens. Juan Esteban's APIClient and AppStore changes connected that state to the real service.
Layered service architecture (architectural, backend) Juan Esteban (initial service), Kevin (later admin/analytics extension) HTTP facade → application/auth service → pure domain → repository/SQL separates request handling, permission checks, rules and persistence.
Repository (design) Kevin for MenuRepository/DemoMenuRepository. Juan Esteban for backend data/repository.ts Storage details and API/SQL access are behind interfaces or modules so views and rules do not depend directly on UserDefaults or tables.
Strategy (design) Juan Esteban for backend FeedRankingStrategy/ContextualRankingStrategy. Kevin for the iOS demo strategy Compatibility and ranking have an explicit algorithm (rank-v1) and explanation, allowing replacement without changing the caller. Real-account ranking is authoritative on the server.
Security and privacy Juan Esteban (base API), Kevin (role controls and dashboard) Server-derived roles, admin-only global metrics, approved-establishment scoping, RLS and no service key in the mobile app reduce unauthorized access.
Availability and consistency Kevin (client cache/event queue), Juan Esteban (versioning/base service) Offline cache keeps a dated read-only feed. Retries use idempotent events. Publication versions and server time prevent stale or overwritten reports.

Android client architecture, patterns, tactics and rationale

flowchart LR
    subgraph AND["Android client"]
      V["Compose screens"] --> VM["ViewModels: UI state"]
      VM --> DOM["Pure Kotlin decisions (BQ-05 LocationGuidance)"]
      VM --> REPO["MenuRepository (interface)"]
      REPO --> RR["RemoteMenuRepository"]
      REPO --> FR["FakeMenuRepository (debug seed data)"]
      RR --> AC["ApiClient (Ktor)"]
      VM --> FIL["Shared filters StateFlow (Observer)"]
      VM --> STR["RecommendationStrategy (Strategy)"]
      VM --> ET["EventTracker + persisted EventQueue"]
      VM --> AN["AnalyticsRepository: performance + BQ-05"]
      AN --> AC
      VM --> LOC["FusedLocationProvider (GPS)"]
      V --> OSM["OpenStreetMap tiles (osmdroid)"]
      AC -->|"access token"| SM["SessionManager (Proxy): persisted session + token refresh"]
      SM --> SA["SupabaseAuthRepository"]
    end
    SA --> AUTH["Supabase Auth"]
    AC -->|"HTTPS JSON + user JWT + X-UniEat-Platform: android"| EDGE["Edge Function api-v1"]
    ET -->|"POST /events/batch"| EDGE

Android architecture rationale. Compose screens only render the state exposed by their ViewModel, mirroring the iOS SwiftUI/AppStore split so both clients share one model. ViewModels depend on the MenuRepository interface rather than the network client, which allowed development against seed data before authentication existed. Decisions such as BQ-05 are pure Kotlin functions, unit-tested without a device. Heavy computation stays on the backend, which does the ranking, the wait aggregation and the report moderation. The client contributes only what the phone alone can know, such as location, permission state and the local event queue. Analytics events are persisted locally and retried, matching the shared idempotent eventId contract. Feature repositories never handle tokens directly: ApiClient asks SessionManager for a valid JWT.

Diagram / component Interaction Architectural pattern and owner Design pattern and owner Rationale and code evidence
Data layer: MenuRepository → RemoteMenuRepository / FakeMenuRepository ViewModels call the repository interface. The remote implementation calls api-v1 through ApiClient, and the debug-only fake serves seed data. AppContainer chooses which one is used. MVVM (shared) Repository: Camilo ViewModels never depend on the network client, which allowed development with seed data before authentication existed and leaves room for an offline cache without changing screens. MenuRepository.kt, RemoteMenuRepository.kt, FakeMenuRepository.kt
Menu detail: MenuDetailScreen → MenuDetailViewModel → LocationGuidance The screen observes the ViewModel's state. The ViewModel loads the menu through the repository and runs the pure BQ-05 decision. MVVM: Camilo (detail), Juan José (feed, filters, recommendation, performance), Samuel (login, profile) None The UI only renders state, so the BQ-05 logic is unit-tested without a device. MenuDetailViewModel.kt, LocationGuidanceTest.kt
Feed slice: FeedScreen / RecommendScreen → FeedViewModel → shared filters The ViewModel is the only writer of a sealed FeedUiState. Both tabs observe the same filters MutableStateFlow, so applying filters in the feed also reloads the recommendation tab. MVVM: Juan José Observer: Juan José The screen never asks for data and never holds business state, which removes a class of synchronization bugs and makes every state reachable from a test. A unit test proves the filters chain. FeedViewModel
Recommendation criteria: FeedViewModel → RecommendationStrategy The ViewModel holds the active strategy and the recommendation screen calls pick(). The user switches criteria at runtime. MVVM: Juan José Strategy: Juan José (BestRankedStrategy, CheapestStrategy), Samuel ("Menor fila") The selection criterion is the one axis of the feature that varies, so each criterion stays a few lines long and independently tested. No strategy invents a ranking: the backend filters, orders and explains, and a strategy only chooses among its results.
Authentication: ApiClient → AccessTokenProvider → SessionManager → AuthRepository / SessionStore ApiClient requests a JWT. SessionManager serves it from memory, from persisted storage or after a refresh. MVVM (login and profile ViewModels): Samuel Proxy: Samuel Repositories only ask for a valid token and never see refresh logic. A one-minute refresh margin and a Mutex avoid nearly expired tokens and duplicate refreshes. A failed refresh clears the session and exposes SignedOut. The refresh token is not encrypted at rest (known limitation). PR #23
Architectural tactics (feed slice) Applied in ApiClient and FeedViewModel None None Juan José: a single error envelope whose backend code drives the UI reaction. The server clock instead of the device clock to judge expiry. Timeouts on every request. Stale responses discarded when filters change mid-request.

5. Views and required mobile capabilities

The iOS client implements the ten MS7 view states through ScreenGalleryView: 01 Today, 02 Filters, 03 Menu Detail, 04 Choose for Me, 05 Publish Menu, 06 Report a Change, 07 Offline, 08 No Menu Published, 09 Insufficient Queue Evidence, 10 Performance. They are reachable through the role-appropriate tabs/detail flow or the gallery. The global performance view remains admin-only. Kevin built the first SwiftUI set. Juan Esteban made the feed, reporting and publication flows work with the live API and implemented the BQ-04 detail updates. The responsibility table above separates those contributions. These are implemented SwiftUI views, distinct from the Sprint 1 MS7 mockups.

MS7 view SwiftUI implementation Implemented behavior and integration
01 Today / menu feed TodayFeedView Feed cards, photos and navigation. Live results use the API connection and backend ranking.
02 Filters FiltersView Student constraints and one-shot area suggestion shape the live feed request.
03 Menu detail MenuDetailView Juan Esteban's PR #11 and PR #13 added live validity, version and report states to the initial view.
04 Choose for Me RecommendationView Contextual explanation traverses menus ordered by the live ranking Strategy.
05 Publish menu PublishView Structured restaurant form submits through the live API connection.
06 Report a change ReportSheet Version-linked report form sends to the shared service and shows feedback.
07 Offline OfflineFeedView Dated cache and recovery state when the live service is unavailable.
08 No menu published NoMenuPublishedView in TodayFeedView.swift Explicit empty state and alternatives.
09 Insufficient queue evidence InsufficientQueueEvidenceView Uncertainty state reflects the backend queue-v2 evidence rule.
10 Performance PerformanceView / RestaurantPerformanceView Combined admin and scoped restaurant metrics extend the original analytics service.
Android view Compose implementation and behavior Owner
01 Feed tab, with 07 offline and 08 empty states Menus in rank-v1 order with the BQ-06 wait sticker, near-expiry warning and backend explanation. Separate loading, empty, offline, server-error and expired-session screens. Juan José
02 Filters sheet Budget slider plus time, diet, area and payment chips. Local draft applied on confirm. Juan José
03 Menu detail with the BQ-05 location card OpenStreetMap pin, GPS distance, missing-information labels, pending-report warnings and the recommendation reason when opened from "Elige por mí". Camilo
04 Recommendation tab Filter summary, criterion chips, recommended menu with its explanation, and "another option". Juan José
06 Report a change Bottom sheet for POST /reports, including location reports. Camilo
10 Performance tab Role-scoped metrics with a 7/28-day selector and restricted state (Juan José), BQ-05 card by platform (Camilo). Juan José, Camilo
Login Email/password form with validation and idle/loading/error/success states. Samuel
Profile GET /me data with retry and sign-out. Samuel
Navigation shell and theme Bottom tabs and the iOS-matching theme (Camilo), tabs adapted to the user's role (Samuel). Camilo, Samuel
Required capability iOS implementation and reason Android implementation and evidence
Phone sensor On-demand CoreLocation suggests an area. Permission denial or inaccurate readings fall back to manual selection. GPS via FusedLocationProviderClient: walking distance and a "Tú" marker on the location card (Camilo. PR #2, PR #4). Accelerometer shake-to-refresh on the feed (Samuel).
Type 2 BQ feature BQ-03's ranked/explained feed and BQ-04's versioned live detail answer student decisions. BQ-05 location card in the menu detail (Camilo. LocationGuidance.kt). BQ-06 wait estimate with its evidence level on the feed and detail (Juan José).
Context awareness Location-derived area suggestion and network-aware offline cache adapt to device/user context. The location card stays usable without location permission or GPS and explains why. "¿Ya llegaste?" appears within ~50 m (Camilo). Tabs adapt to the authenticated role from GET /me (Samuel). Offline state screen in the feed (Juan José).
Smart feature “Elige por mí” chooses among filtered, context-ranked menus with an explanation and uncertainty labels. Recommendation tab with interchangeable criteria over the backend rank-v1 options (Juan José), "Menor fila" criterion (Samuel), the recommendation's reason shown in the menu detail (Camilo. PR #28).
Authentication Supabase Auth, token refresh and Keychain. Effective role comes from GET /me. Supabase email/password login, persisted session and token refresh through SessionManager (Samuel. PR #23), expired-session screen in the feed (Juan José), the menu detail returns to the login when the session expires (Camilo. PR #27).
External service other than authentication API v1 supplies live feed, publications, reports, events and metrics from PostgreSQL. Apple Maps can open directions. GET /menus/:id, POST /reports, POST /events/batch, GET /admin/dashboard (BQ-05) and GET /restaurant/location-guidance. OpenStreetMap tiles. Google Maps directions (Camilo). GET /feed and GET /performance / GET /restaurant/performance (Juan José). GET /me (Samuel).

6. Ethics video

Sprint 2 ethics video (6 min)