End-to-end checklist to ship Caluru (com.agarcia.caluru) on the App Store, including RevenueCat subscriptions.
Status snapshot (2026-08-08)
| Area | Status | Notes |
|---|---|---|
| Phase 0 pre-flight (camera string, export compliance) | Done | Fixed in project.pbxproj + Info.plist |
| RevenueCat + hard paywall in app | Done | SPM RevenueCat / RevenueCatUI; SubscriptionService; CaluruPaywallView |
| Release build compiles / automated tests | Done | See QA_RESULTS.md |
| Distribution archive + TestFlight smoke | Done | CAL-63 |
| Paid Apps Agreement + IAP products | Done | CAL-67 |
| Legal pages / company / URL alignment | Done | Milestone 2. Legal & company (CAL-68–74) |
| Manual QA matrix (11 flows) | In progress | CAL-61 |
| App Store Connect listing | In progress | CAL-66 |
| Remaining P0/P1 product + launch work | Open in Linear | See below — Linear is source of truth |
Remaining work (Linear → Caluru Release Readiness):
| Status | Issues |
|---|---|
| In Progress | CAL-61 manual QA · CAL-66 ASC listing · CAL-83 PR unit-test CI |
| Review | P0: CAL-58 usernames · CAL-59 account-delete family · CAL-60 paywall prices · CAL-64 app icon PNG · plus P1 accuracy/legal + P2 UI polish (CAL-71, 75–77, 87–95) |
| Todo | CAL-82 crash reporting · CAL-84 recipe image backfill |
Do not treat unchecked boxes in older phase write-ups as current blockers if the matching Linear issue is Done. Re-check Linear before planning.
Related docs: APP_STORE_CONNECT.md · QA_MATRIX.md · QA_RESULTS.md · BACKEND.md
flowchart TD
P0[Phase 0: Pre-flight fixes] --> P1[Phase 1: RevenueCat]
P1 --> P2[Phase 2: Production backend]
P2 --> P3[Phase 3: Website & legal]
P3 --> P4[Phase 4: Apple Developer]
P4 --> P5[Phase 5: QA & TestFlight]
P5 --> P6[Phase 6: App Store Connect]
P6 --> P7[Phase 7: Submit for review]
P7 --> P8[Phase 8: Launch]
| Phase | Goal | Est. effort |
|---|---|---|
| 0 | Fix known config bugs | 0.5 day |
| 1 | Subscriptions + paywall in app | 3–5 days |
| 2 | Supabase production ready | 0.5–1 day |
| 3 | Public legal/support pages | 1 day |
| 4 | Certificates, IAP products, capabilities | 1 day |
| 5 | Manual QA on real devices | 2–3 days |
| 6 | Screenshots, metadata, privacy labels | 1–2 days |
| 7 | Archive, upload, review submission | 0.5 day |
| 8 | Monitor, iterate | Ongoing |
Status: Done (camera string + ITSAppUsesNonExemptEncryption = NO in Release Debug/Release build settings and Info.plist).
INFOPLIST_KEY_NSCameraUsageDescription = "Caluru necesita acceso a la cámara para escanear códigos de barras y fotografiar alimentos.";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO
Only valid while the app uses standard HTTPS/TLS (no custom encryption).
Config.xcconfig exists locally with production SUPABASE_URL, SUPABASE_ANON_KEY, and REVENUECAT_API_KEY (copy from Config.xcconfig.example) — local secret; verify on each machine before archiveConfig.xcconfig (wired in project.pbxproj)CURRENT_PROJECT_VERSION for each App Store / TestFlight uploadexport DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
xcodebuild test \
-project Caluru.xcodeproj \
-scheme Caluru \
-destination 'platform=iOS Simulator,name=iPhone 17' \
-only-testing:CaluruTests
xcodebuild archive \
-project Caluru.xcodeproj \
-scheme Caluru \
-destination 'generic/platform=iOS' \
-archivePath ./build/Caluru.xcarchive
Status: Done in code — SPM packages linked; hard paywall ships. Remaining pricing edge cases tracked in Linear (CAL-60 / related CAL-32).
Caluru uses a hard paywall: after onboarding, MainTabView.canShowMainShell requires Caluru Pro (subscription.status == .pro). Non-subscribers never see Home, Plan, Recipes, Scan, or other main-shell features. The paywall is non-dismissible; Restore Purchases is on the paywall and in Profile → Preferences.
| Feature | Free | Caluru Pro |
|---|---|---|
| Onboarding (profile, targets, first-log nudge) | Yes | Yes |
| Main tab shell (Home, Plan, Recipes, Log FAB) | No | Yes |
| Daily calorie / macro tracking + food logging | No | Yes |
| Barcode scan (Open Food Facts) | No | Yes |
| Photo food recognition (GPT-4o Vision) | No | Yes |
| Recipe library + cooking mode | No | Yes |
| Weekly meal plan + shopping list | No | Yes |
| AI nutrition assistant | No | Yes |
| Family groups + leaderboard | No | Yes |
| Gamification (XP, streaks) | No | Yes |
| iCloud sync across devices | No | Yes |
| Restore Purchases | Paywall only (pre-subscribe) | Paywall + Profile → Preferences |
Product rule: Free = onboarding only. Everything past onboarding requires an active Caluru Pro entitlement (or Restore Purchases that restores one).
Prerequisites: Paid Applications Agreement signed in App Store Connect (Agreements, Tax, and Banking).
caluru_pro).| Product ID | Type | Suggested price (ES) | Notes |
|---|---|---|---|
caluru_pro_monthly |
Auto-renewable, 1 month | €4.99 | Primary offer |
caluru_pro_yearly |
Auto-renewable, 1 year | €39.99 | ~33% savings vs monthly |
caluru_pro_yearly_trial |
Same as yearly + 7-day free trial | €39.99 | Optional intro offer |
CaluruPaywallScreenshotView in Xcode Previews if needed.caluru_pro_monthly, caluru_pro_yearly) → upload the review screenshot → add localized name/description until status is Ready to Submit.com.agarcia.caluru..p8 file)pro (or caluru_pro).default with packages:
$rc_monthly → caluru_pro_monthly$rc_annual → caluru_pro_yearlyIn Xcode: File → Add Package Dependencies
https://github.com/RevenueCat/purchases-ios
Add products:
RevenueCat — core SDKRevenueCatUI — prebuilt paywalls (recommended)Pin to latest stable major version (e.g. 5.x).
Add to Config.xcconfig.example (and your local Config.xcconfig):
REVENUECAT_API_KEY = appl_XXXXXXXXXXXXXXXXXXXXXXXX
Add to Info.plist:
<key>REVENUECAT_API_KEY</key>
<string>$(REVENUECAT_API_KEY)</string>
Read in a small config enum (mirror SupabaseConfig pattern in Config.swift).
SubscriptionService.swiftSingleton @Observable service wrapping RevenueCat:
// Caluru/Services/SubscriptionService.swift (new file)
import RevenueCat
@Observable
final class SubscriptionService {
static let shared = SubscriptionService()
var isPro: Bool = false
var currentOffering: Offering?
var customerInfo: CustomerInfo?
func configure() {
Purchases.logLevel = .debug // .warn in Release
Purchases.configure(withAPIKey: RevenueCatConfig.apiKey)
}
/// Call after Sign in with Apple so purchases restore across devices.
func identify(userId: UUID) async {
_ = try? await Purchases.shared.logIn(userId.uuidString)
await refreshCustomerInfo()
}
func logOut() async {
_ = try? await Purchases.shared.logOut()
await MainActor.run { isPro = false }
}
func refreshCustomerInfo() async {
guard let info = try? await Purchases.shared.customerInfo() else { return }
await MainActor.run {
customerInfo = info
isPro = info.entitlements["pro"]?.isActive == true
}
}
func restorePurchases() async throws {
let info = try await Purchases.shared.restorePurchases()
await MainActor.run {
customerInfo = info
isPro = info.entitlements["pro"]?.isActive == true
}
}
}
Wire in CaluruApp.swift .onAppear / launch:
SubscriptionService.shared.configure()
After successful Apple Sign In (AuthService.handleAppleSignIn), call:
await SubscriptionService.shared.identify(userId: session.user.id)
On sign-out / account deletion:
await SubscriptionService.shared.logOut()
CaluruPaywallView (custom hard paywall)Ship a non-dismissible custom paywall (interactiveDismissDisabled). Do not offer a close/skip control.
.pro → MainTabView mounts the main shellRevenueCatUI.PaywallView(displayCloseButton: true) is not used for the post-onboarding gate.
Primary gate (ships today): MainTabView — canShowMainShell is true only when onboarding is complete and subscription.status == .pro. Otherwise present non-dismissible CaluruPaywallView.
Per-feature checks (assistant, photo scan, family, server openai-proxy 402) remain defense-in-depth; they are not a free tier.
| Moment | Trigger |
|---|---|
| End of onboarding (after first-log nudge) | Hard paywall — non-dismissible; subscribe or Restore Purchases |
| Cold launch / entitlement lost | Same hard paywall if status == .notPro |
| Profile → Preferences → Caluru Pro | Status, renewal date, Restore Purchases (after unlock) |
Always show Restore Purchases on the paywall (App Review requirement).
Add to AppPreferencesView or ProfileView:
https://apps.apple.com/account/subscriptionscustomerInfo.entitlements["pro"]?.expirationDateClient-side gates are not enough — users can bypass them. For AI cost control, add a server check in openai-proxy:
revenuecat-webhook updates user_subscriptions table.openai-proxy checks is_pro before forwarding to OpenAI.402 Payment Required if not Pro (app shows paywall).supabase/functions/revenuecat-webhook/index.ts (new)
supabase/migrations/YYYYMMDD_user_subscriptions.sql (new)
Webhook events to handle: INITIAL_PURCHASE, RENEWAL, CANCELLATION, EXPIRATION.
Update Localizable.xcstrings / LegalView:
App Store Connect also requires a Terms of Use (EULA) URL for apps with subscriptions.
pro entitlementpro entitlementlogOut() + no orphaned entitlementsApple rejects free-download apps that require a subscription if the App Store description does not say so clearly (Accurate Metadata). Lead the description with paid-access language — see APP_STORE_CONNECT.md.
Include in App Review Information (first line):
IMPORTANT — PAID ACCESS ONLY: Caluru is a free download but requires a paid Caluru Pro subscription (or Restore Purchases) to use the app. No free tier after onboarding. Hard paywall is non-dismissible. Restore Purchases is on the paywall and in Profile → Preferences.
Supabase project: opxqcnxiqbitezragpch
brew install supabase/tap/supabase
supabase login
supabase link --project-ref opxqcnxiqbitezragpch
supabase functions deploy openai-proxy --project-ref opxqcnxiqbitezragpch
supabase functions deploy delete-account --project-ref opxqcnxiqbitezragpch
# After Phase 1.6.6:
supabase functions deploy revenuecat-webhook --project-ref opxqcnxiqbitezragpch
supabase secrets set OPENAI_API_KEY=sk-... --project-ref opxqcnxiqbitezragpch
supabase secrets set OPENAI_HOURLY_LIMIT=50 --project-ref opxqcnxiqbitezragpch
# After webhook (Authorization header value RevenueCat sends; Bearer prefix optional):
supabase secrets set REVENUECAT_WEBHOOK_SECRET=... --project-ref opxqcnxiqbitezragpch
supabase db push --project-ref opxqcnxiqbitezragpch
Existing migrations:
20250219000000_openai_rate_limit.sql20260612170000_family_groups.sql20260612170001_gamification.sql20260612180000_user_gamification_insert_policy.sql20260613120000_usernames_and_userid_invites.sql20260709000000_user_subscriptions.sql20260720120000_family_gamification_rls.sql20260720120100_subscription_event_ordering.sqlIn Supabase Dashboard → Authentication → Providers → Apple:
.p8) configuredIn Apple Developer → Identifiers:
com.agarcia.caluru has Sign in with Apple enabled# openai-proxy up, auth required
curl -s -o /dev/null -w "%{http_code}" -X POST \
"https://opxqcnxiqbitezragpch.supabase.co/functions/v1/openai-proxy" \
-H "Content-Type: application/json" -d '{}'
# Expect: 401
Manual smoke test (signed-in user on device):
App Store Connect requires public HTTPS URLs. In-app legal text alone is not sufficient.
https://caluru.app| URL | Content source |
|---|---|
https://caluru.app/privacy |
legal_privacy_content in Localizable.xcstrings |
https://caluru.app/terms |
legal_terms_content in Localizable.xcstrings |
https://caluru.app/support |
Contact: support@caluru.app, FAQ, subscription help |
https://caluru.app |
Landing page (optional but recommended) |
caluru.appdid not respond at time of writing — set up DNS + hosting before submission.
LegalView support email: support@caluru.appLegalView matches published pagesPrivacyInfo.xcprivacy + legal textTeam ID: S95DC78UWJ (already in project)
In Apple Developer → Identifiers → com.agarcia.caluru:
iCloud.com.agarcia.caluru)iCloud.com.agarcia.caluru exists in CloudKit DashboardXcode automatic signing (already configured) should handle this when you archive with a distribution certificate.
com.agarcia.caluruComplete the manual matrix in QA_MATRIX.md on a physical iPhone with a Release build.
| # | Flow | Device |
|---|---|---|
| 1 | Fresh install → full onboarding → hard paywall (no skip) | Simulator or device |
| 2 | Sign in with Apple → kill app → session restored | Physical |
| 10 | Account deletion → clean state → re-onboard | Physical + test Apple ID |
| — | Sandbox subscription purchase → main shell unlocks | Physical |
| — | Restore Purchases on paywall → Pro unlocks | Physical |
| 4 | Photo food scan (Pro) | Physical + auth |
| 5 | AI Assistant (Pro) | Auth required |
Update QA_RESULTS.md with Pass/Fail per row. File Linear/GitHub issues for P0/P1.
Use copy from APP_STORE_CONNECT.md. Description must lead with the paid-subscription requirement (Accurate Metadata).
| Asset | Spec |
|---|---|
| App icon | Caluru/Assets.xcassets/AppIcons/appstore.png (1024×1024) |
| iPhone 6.7” screenshots | 1290×2796 (required) |
| iPhone 6.5” screenshots | 1284×2778 (required) |
| iPad screenshots | If supporting iPad (TARGETED_DEVICE_FAMILY = 1,2) |
| Subscription screenshot | Paywall UI for IAP review |
Branding: use Caluru only — not NutriGuru, GuruDeSalud, or Guru de Salud.
| Field | Value |
|---|---|
| App Name | Caluru |
| Subtitle (es) | Nutrición, recetas y asistente IA |
| Bundle ID | com.agarcia.caluru |
| Primary language | Spanish (es) |
| Category | Health & Fitness |
| Privacy Policy URL | https://caluru.app/privacy |
| Terms of Use URL | https://caluru.app/terms (required for subscriptions) |
| Support URL | https://caluru.app/support |
Declare data collected per PrivacyInfo.xcprivacy and APP_STORE_CONNECT.md:
daily_progress on Supabase shared with other members per sharing level; allergies/targets may go to OpenAI for the AssistantPartners to name where ASC asks: Apple, Supabase, OpenAI, Open Food Facts, RevenueCat. Tracking = No. Hosted policy must match (§2.6 / §4 / §5 on https://caluru.app/privacy.html).
Tracking: No (NSPrivacyTracking = false)
Answer questionnaire honestly. Nutrition + AI assistant typically rates 4+ or 12+ depending on health/medical framing. Emphasise “educational, not medical advice.”
caluru_pro_monthly, caluru_pro_yearly) before tapping Add for ReviewITSAppUsesNonExemptEncryption = NO)| Field | Suggested value |
|---|---|
| Contact | Your name, phone, email |
| Notes | See template below |
| Sign-in required? | Yes for AI proxy / family / account features (Sign in with Apple). Main shell still requires Caluru Pro after onboarding. |
Review notes template:
IMPORTANT — PAID ACCESS ONLY: Caluru is a free download but requires a paid Caluru Pro subscription (or Restore Purchases) to use the app. There is no free feature tier after onboarding. The App Store description states this up front.
After onboarding, a non-dismissible hard paywall appears. Home, meal logging, plans, recipes, barcode/photo scan, AI assistant, and family unlock only with an active Caluru Pro entitlement.
Subscriptions: test with a Sandbox Apple ID. Plans are monthly and yearly (yearly may include a free trial when eligible).
Restore Purchases: on the hard paywall (required for returning subscribers) and in Profile → Preferences → Caluru Pro after unlocking.
Sign in with Apple is used for cloud AI, family, and account sync once Pro unlocks the shell.
The app provides educational nutrition information only, not medical advice.
HEALTH SOURCES (Guideline 1.4.1): Calorie and macro targets cite published methods with tappable links. Reviewers can open citations without a subscription:
1. Complete onboarding until the calorie-target screen (“Here’s what your body needs”).
2. Directly under the calorie ring, tap “View sources”, or scroll to the Health sources list and tap any row (opens the paper or database).
3. After unlocking Pro: Profile → Health sources (first settings row), Profile → Legal, Assistant, Scan, and Weight tracking.
Public page: https://caluru.app/sources.html
IN-APP PURCHASES submitted with this binary (Guideline 2.1(b)):
- caluru_pro_monthly (auto-renewable, 1 month)
- caluru_pro_yearly (auto-renewable, 1 year; intro trial when eligible)
Please review these subscriptions together with the app. After onboarding the first screen is the Caluru Pro paywall.
Test flow:
1. Fresh install → complete onboarding
2. Hard paywall appears (cannot dismiss / skip)
3. Tap Restore Purchases (Sandbox Apple ID with prior purchase) OR purchase Caluru Pro
4. Main shell unlocks — exercise logging, scan, assistant as needed
5. Account deletion: Profile → Eliminar Cuenta
App Store Connect → version page → In-App Purchases and Subscriptions → add caluru_pro_monthly and caluru_pro_yearly → Add for Review → select build → submit.
Typical review: 24–48 hours. Respond quickly to rejection notes.
| Reason | Prevention |
|---|---|
| Missing account deletion | Already implemented — verify it works |
| Subscription terms unclear | Legal page + paywall fine print |
| Missing EULA link on product page (3.1.2) | Add https://www.apple.com/legal/internet-services/itunes/dev/stdeula/ to the App Description for every localization (see APP_STORE_CONNECT.md); in-app links and Privacy Policy URL are not enough |
| IAP products not submitted with the binary (2.1(b)) | Attach caluru_pro_monthly + caluru_pro_yearly on the version page; upload a paywall screenshot on each product |
| Health info without citations (1.4.1) | Tappable sources on onboarding targets and Profile → Health sources; hosted https://caluru.app/sources.html |
| AI without disclosure | Mention AI in description and review notes |
| Broken Sign in with Apple | Test on physical device before submit |
| Privacy label mismatch | Cross-check with PrivacyInfo.xcprivacy |
user_subscriptions correctlySubscriptionService entitlement logic-UITesting launch argsSource of truth for open work: Linear project Caluru Release Readiness. Checkboxes below mirror code/process state as of 2026-08-08; prefer Linear status when they disagree.
ITSAppUsesNonExemptEncryption = NO addedConfig.xcconfig verified on the archiving machine (local secret)SubscriptionService implementedcanShowMainShell)identify / logOut wired to auth lifecycleopenai-proxy (is_user_pro + webhook)openai-proxy Pro gate + rate limit RPCsrevenuecat-webhook + user_subscriptionsTrack merge/close in Linear — do not treat as closed until status is Done:
# Tests
xcodebuild test -project Caluru.xcodeproj -scheme Caluru \
-destination 'platform=iOS Simulator,name=iPhone 17'
# Archive
xcodebuild archive -project Caluru.xcodeproj -scheme Caluru \
-destination 'generic/platform=iOS' \
-archivePath ./build/Caluru.xcarchive
# Supabase deploy
supabase functions deploy openai-proxy --project-ref opxqcnxiqbitezragpch
supabase functions deploy delete-account --project-ref opxqcnxiqbitezragpch
supabase functions deploy revenuecat-webhook --project-ref opxqcnxiqbitezragpch
supabase db push --project-ref opxqcnxiqbitezragpch
Last updated: 2026-08-08 — align with Linear Caluru Release Readiness before each submit.