# Gestione multi-atleta

## Perimetro

FranCoach mantiene un solo coach e supporta atleti illimitati. Il periodo di accesso non rappresenta un pagamento o un piano commerciale. Non sono presenti rinnovi automatici, fatturazione, gateway di pagamento o multi-tenancy.

## Assunzioni rimosse

- selezione del primo utente con ruolo atleta nei service;
- testi applicativi riferiti a un atleta nominativo;
- seeder di un atleta fisso;
- dashboard, profilo, diario, progressi, foto, report e nutrizione senza contesto atleta;
- audit associato implicitamente allo stesso atleta;
- account atleta valido sulla base del solo `is_active`.

Il contesto coach usa `AthleteContextService`: route esplicita quando disponibile e sessione validata per ruolo e relazione coach. Se il contesto è ambiguo, la pagina richiede una selezione. `<x-athlete-selector>` mostra nome, stato e scadenza.

## Architettura

```text
app/
├── Console/Commands/{MigrateExistingAthleteSubscriptions,ProcessSubscriptionExpirations}.php
├── DTO/AthleteAccessResult.php
├── Enums/{AccountStatus,AthleteInvitationStatus,SubscriptionEventType,SubscriptionReminderStatus,SubscriptionStatus}.php
├── Http/
│   ├── Controllers/{Auth,Coach,Athlete}/
│   ├── Middleware/EnsureAthleteSubscriptionIsValid.php
│   └── Requests/{Auth,Coach}/
├── Mail/{AthleteInvitationMail,SubscriptionStatusMail}.php
├── Models/{AthleteInvitation,AthleteSubscription,SubscriptionEvent,SubscriptionReminder}.php
├── Notifications/*Subscription*.php
├── Policies/{Athlete,AthleteInvitation,AthleteSubscription}Policy.php
└── Services/{AthleteAccess,AthleteContext,AthleteCreation,AthleteInvitation,AthleteSubscription,AthleteSubscriptionExpiration,AthleteSubscriptionReminder,AthleteSubscriptionRenewal,AthleteSuspension}Service.php
resources/views/
├── auth/accept-athlete-invitation.blade.php
├── coach/athletes/
├── athlete/subscription/
├── components/athlete-selector.blade.php
└── emails/
```

## Database

Le migration `2026_07_28_000012` e `000013` estendono utenti e profili e creano quattro tabelle:

- `athlete_subscriptions`: periodo corrente e storico, grace period, sospensione e controllo login;
- `subscription_reminders`: canale, esito e chiave univoca anti-duplicato;
- `subscription_events`: storico append-only;
- `athlete_invitations`: solo hash SHA-256 del token, scadenza ed esito invio.

`athlete_profiles.user_id` era già univoco. Proroga aggiorna il periodo corrente e registra l’evento; rinnovo chiude il periodo precedente e crea un record collegato da `renewed_from_id`.

## Flussi coach

`/coach/athletes` offre ricerca, filtri, ordinamento, paginazione, piani correnti e azioni. Creazione, profilo, relazioni iniziali, periodo e audit sono atomici. L’invio e-mail avviene dopo il commit: un errore lascia l’account `pending_activation`, registra l’errore e consente il reinvio.

Proroga, rinnovo, sospensione e riattivazione usano service transazionali e conferma esplicita. Il soft delete conserva i dati secondo le relazioni e le policy esistenti.

## File esistenti modificati

- `User`, `AthleteProfile`, factory e seeder: relazioni e campi multi-atleta;
- controller/service dei moduli profilo, schede, calendario, diario, progressi, foto, nutrizione e report: contesto atleta esplicito;
- dashboard, topbar, sidebar e JavaScript: selettore e riepiloghi multi-atleta;
- route, middleware, provider e scheduler: protezione accesso, rate limit e processi giornalieri;
- `.env.example`: soglie, durata invito e scadenza iniziale di migrazione.

## Comandi

```bash
php artisan migrate
php artisan subscriptions:process-expirations
php artisan athletes:migrate-existing-subscriptions --expires=YYYY-MM-DD --dry-run
php artisan athletes:migrate-existing-subscriptions --expires=YYYY-MM-DD
php artisan test
```

## Dipendenze

Laravel 12, PHP 8.3+, MySQL 8, sessioni Laravel, mailer sincrono e cron cPanel. Non è richiesto un worker permanente.
