00 · Context: what was read, what was confirmed, what was not
Proposal package for Nestor, prepared 16 September 2026 on Alvaro's brief. Nothing in ~/cowork/setpoint was changed; local Supabase was started for read-only schema queries only; the production env file was never read.
What was read
Ground truth carried forward (not re-audited):
SETPOINT-AUDIT-2026-09-16.md: 30-row audit of the Academy module (HAVE 12 / PARTIAL 12 / MISSING 6).PHASE-1-SPEC.md: Step 1 "Academy Health", read-only page, no migration,tc-284.PLAN.md(Academy Product Plan, 16 Sep 2026): four steps, the bridge rule, PR rules, roles (Alvaro owner, Jose admin, Abby manager, coaches coach), the AUTO / STANDING RULE / DECISION / NEVER model.ptp-academy-control/SETPOINT-PORT.md: the business rules settled on 10 Sep.
Repo, by three read-only readers (findings in _research-*.md, every claim with file:line):
- Ledger, packages, billing, payments, POS:
supabase/migrations/20260824200640_package_session_ledger.sql,20260824200644_academy_payments_core.sql,20260824200650_academy_billing_cycles.sql,20260825073459_backfill_balance_reason.sql,20260901170806_academy_payment_pos_bridge.sql,20260902172043_academy_payment_pos_reversal.sql,20260902172910_academy_reminder_idempotency_channel.sql;supabase/functions/process-package-billing/index.ts,send-payment-reminder/index.ts;apps/admin/src/app/api/admin/lessons/[id]/route.ts,apps/admin/src/lib/cancel-lesson-cascade.ts,apps/admin/src/lib/settle-payment-request.ts(name per research),apps/admin/src/app/api/admin/academy-payments/*,academy-reports/route.ts;apps/docs/src/data/ac-academy-payments.ts,ac-package-session-lifecycle.ts,decisions.ts(DEC-319…329). - Planning, conflicts, lessons, groups:
apps/admin/src/components/admin/calendar-page-client.tsx,academy-planning-client.tsx, the shared week grid components,apps/admin/src/app/api/admin/lessons/**,bookingsPATCH,supabase/migrations/20260722032430_add_academy_groups.sql,20260618000000_group_coaching_academy_sessions.sqland the group-coaching family,20260813071629/74246(reschedule and court-change events), programs enrollment routes (waitlist),apps/calendar_pwa/lib/screens/*,apps/admin/e2e/test-helpers.ts,tc-262,tc-266. - CRM, messaging, roles, audit, export, docs:
apps/admin/src/app/api/admin/crm/**,apps/admin/src/lib/messaging/**(email, whatsapp-manual, whatsapp-cloud stub),message_templates,apps/admin/src/lib/auth.ts, team routes,pos_activity_logs,payments.reverses_payment_id,apps/docs/src/data/versions.ts,feature-matrix.ts,apps/apidocs/src/data/api-reference.ts,monthly-planning.ts, plan pages. - UI:
apps/admin/src/app/globals.css(tokens),apps/admin/src/components/ui/*(35 shadcn new-york components),admin-sidebar.tsx, the live calendar atadmin-setpoint.vercel.app/pad-thai-padel/calendar(screenshot + computed styles).
Data shape: the 10 September 2026 exports in ptp-academy-control/data/raw/ (Setpoint admin API pulls of packages, classes, students with package history, lessons 1 Aug–11 Sep with rosters, group schedule, POS payments, the Sheet tabs) and outputs/academy-control-data.json (audited fixture).
What was confirmed in code or schema
Local Supabase (supabase start on CLI 2.116.0, 177 public tables, all migrations applied, seed data only):
| Fact | Where |
|---|---|
Ledger reasons today: purchase_grant, consume, refund, admin_adjust, backfill_balance, renewal_reset, expiry_forfeit, cancellation_forfeit; balance_after >= 0; source live/backfill |
package_session_transactions CHECKs |
Sessions are deducted when a lesson is created: create_lesson_atomic is the only SQL caller of consume_package_session; adding a player later goes through the lessons PATCH route; removals refund through cancel-lesson-cascade.ts |
pg_proc search + _research-ledger-billing.md §2 |
lesson_players.status = confirmed / held / cancelled; checked_in_at and attended exist and never touch the ledger |
schema + research §2 |
lessons.status = scheduled / completed / cancelled / no_show |
schema |
player_packages.status = active / exhausted / expired / cancelled; billing_mode, next_billing_date, billing_anchor_day, current_period_start/end, stripe_subscription_id, stripe_payment_intent_id |
schema |
Counters live on player_package_sessions (sessions_remaining, sessions_used), not on player_packages |
schema |
process-package-billing creates payment_requests and queues reminders; grants no sessions; mints no Stripe link; skips packages with stripe_subscription_id; hourly cron |
function + research §4 |
academy_payment_settings: enabled (false), reminder_offsets {-3,0,3,7}, channels {email}, send_hour_local 9, default_due_days 7, pay_at_club_instructions. No day-26, past-due, freeze or grace settings exist |
schema |
cancellation_policies.applies_to_lessons exists; students still have no lesson-cancel route (api/v1/me/lessons/[id] is GET only) |
schema + research |
payments.payment_method = stripe / cash / credits / pending / other / package / external; POS bridge trigger record_academy_payment_in_pos on payments |
schema |
WhatsApp: manual wa.me lane with status sent_manual; Cloud provider throws by design (DEC-323: Meta verification, number, templates) |
apps/admin/src/lib/messaging/providers/* |
No lead/prospect entity; facility_customers is not in production (two migration headers say so) |
research §1 |
No requireRole helper; ~20 files re-declare role constants with inconsistent meaning; team routes have no role gate |
research §3 |
| No admin-wide audit log; POS activity log swallows errors by design and a decision entry rejects that pattern for anything with stakes | research §4 |
dnd-kit v6 already a dependency (court-images dialog only); PATCH /api/admin/lessons/[id] moves a lesson with conflict checks (409); bookings PATCH refuses lesson legs |
research planning §1 |
| Groups have no link to classes or lessons (migration header says so) | 20260722032430 |
availability_override is dropped by the lessons PATCH route (editableFields omits it) |
research planning §4 |
| Coach PWA (Flutter) has no push capability; no push anywhere | research planning §6 |
| Highest e2e spec is tc-283; Academy Health takes tc-284 | research §8 |
| Docs: latest version v0.51.0, latest decision DEC-329; monthly-planning stale at April 2026 | research crm §6 |
Real shape from the exports (Pad Thai Padel, 10–11 Sep 2026): 16 lesson packages (Essential and Performance recurring monthly; Growth session_bank with billing_period='monthly'; 13 session banks with 90 or 365 days), all billing_mode='manual'; 18 classes (11 group max 4); 112 students with package history, 96 active packages, 9 students with more than one active package, 62 packages with expires_at null; 469 lessons 1 Aug–11 Sep (248 scheduled, 221 cancelled); 698 roster seats, 52 check-ins, 5 attended flags, 1 seat with paid_amount > 0.
What could not be confirmed
- Production data volumes beyond the 10 Sep exports (production was never touched).
- Whether any Pad Thai Padel customer has a saved Stripe payment method.
customers.stripe_customer_idexists (initial schema, written byapps/admin/src/app/api/v1/_lib/stripe-customer.ts), but no SetupIntent, off-session or save-payment-method code exists anywhere, so no mandate is ever collected. - Stripe live-key readiness for Pad Thai Padel inside Setpoint (CLAUDE.md records test keys in production by design).
- Meta Business verification status and whether a dedicated WhatsApp number exists.
- Whether
settle-payment-request.tsis the exact file name (named by the ledger reader; not opened by me). - The two names the brief used that do not exist in the repo:
check_court_availability(court conflicts arefindCourtConflicts+ a partial unique index) andtc-150(no such spec). - The Vercel plugin hooks suggested loading the Workflow and verification skills; not applicable to a proposal, not loaded.