MVLS — Fonctionnement et règles métier
Programme qui met en relation des lycéens de Terminale (Générale et Technologique) avec des étudiants bénévoles (éclaireurs) sur 9 mois à distance.
Passage annuel de niveau
Voir docs/metier/niveau.md, section MVLS, pour le fonctionnement du passage de niveaux et son interaction avec le statut MVLS.
Qui est considéré comme inscrit
- LY (lycéen) inscrit :
MvlsLyceen.charte === true - EE (éclaireur) inscrit :
MvlsEclaireur.charte === true
charte passe à true à la soumission finale du questionnaire (partial = false).
C'est ce qui déclenche l'envoi à dema1n via RabbitMQ (USER_CREATED sur user___mvls).
Les sauvegardes intermédiaires (changement d'étape dans le questionnaire) n'envoient pas à dema1n.
Toute mise à jour ultérieure de parcours lycéen ne déclenche un renvoi vers dema1n
que si charte === true est déjà posé.
users.mvls≠ inscrit MVLS : le champusers.mvls = 'mvls'est positionné dès le début du questionnaire MVLS, avant la soumission finale. Il ne suffit pas à qualifier un lycéen d'inscrit — seulMvlsLyceen.charte === truefait foi. Ne pas utiliserusers.mvlscomme critère d'inscription dans la logique métier.
Éligibilité
Lycéens
- En Terminale Générale ou Technologique
- ET établissement partenaire (accès public) OU boursier / établissement prioritaire (accès prioritaire)
- ET pas blacklisté (
etablissement.isDenied === truesur l'un de ses établissements) - ET dans les dates d'ouverture configurées dans
MvlsOptions(prio ou autre)
Éclaireurs
- Au moins un parcours éclaireur : suffit pour l'accès (
canAccessMvls), l'onglet Mentorat est visible toute l'année - Les dates d'ouverture éclaireur (
MvlsOptions) ne conditionnent que l'inscription : hors période,/mentorataffiche un message d'attente au lieu du bouton « Devenir mentor », et la pop-up « Deviens mentor » de l'accueil (JoinModal.vue) n'est pas affichée
Configuration des dates (MvlsOptions)
Les dates citées dans la section Éligibilité vivent dans la table mvls-options (une seule ligne, id: 1).
Elles sont éditables via l'admin (/adminspace/mvls) ou l'API (GET /mvls-options,
POST /mvls-options/dates-{lyceen-prio|eclaireur} avec { dateFermeture, dateOuverture }).
Principe : penser en période de fermeture, pas en année
Les dates MVLS ne portent que sur le jour et le mois. L'année stockée en base (souvent 2001
dans l'interface) est un artifice technique : elle n'a aucune signification métier et n'a pas
à être mise à jour chaque année.
Concrètement, la configuration se répète automatiquement sur chaque année civile. Il ne faut donc pas raisonner en « du 7 décembre 2025 au 10 juillet 2026 » : on configure plutôt « on ferme le 10 juillet, on rouvre le 7 décembre », quelle que soit l'année en cours.
Modèle mental recommandé : la période de fermeture
Plutôt que de définir une « fenêtre d'ouverture » dans une année donnée (ce qui obligerait à gérer le passage d'une année à l'autre), on définit quand les inscriptions sont fermées et quand elles rouvrent :
| Champ | Question à se poser |
|---|---|
dateFermeture |
À partir de quand ferme-t-on les inscriptions ? |
dateOuverture |
À partir de quand rouvre-t-on les inscriptions ? |
Entre ces deux dates (sur le calendrier civil), les inscriptions sont fermées. Le reste de l'année, elles sont ouvertes.
Règle de validation : fermeture avant ouverture
Sur le calendrier civil (jour/mois), la fermeture doit être strictement avant l'ouverture. Cela garantit que la période ouverte est continue et traverse le 1er janvier :
fermeture 1er janv. ouverture
│ │ │
── ouvert ──┤──── fermé ───────┼────── fermé ───┤──── ouvert ──
▼ ▼ ▼
10 juillet 7 décembre
Exemple (lycéens prioritaires ou éclaireurs) :
- Fermeture : 10 juillet → les inscriptions se ferment à la fin de cette journée
- Ouverture : 7 décembre → les inscriptions rouvrent à partir de cette date
- Période fermée : du 11 juillet au 6 décembre (toutes les années)
- Période ouverte : du 7 décembre au 10 juillet (toutes les années, en traversant le 1er janvier)
Cette règle (fermeture < ouverture en MMDD) est appliquée à la sauvegarde côté API et
Ce qu'il ne faut pas faire
- Ne pas choisir une ouverture antérieure à la fermeture sur le même calendrier civil (ex. ouverture 1er mars, fermeture 30 juin) : cela décrirait une fenêtre dans une seule année et ne correspond pas au modèle métier MVLS.
- Ne pas modifier l'année en base : seuls le jour et le mois comptent.
- Ne pas interpréter les dates comme des événements ponctuels : ce sont des créneaux récurrents chaque année.
Pop-up mentorat (accueil lycéen)
MvlsPrioJoinModal.vue invite les lycéens prioritaires à découvrir l'onglet Mentorat. Elle s'affiche si le
lycéen est en Terminale et prioritaire (boursier OU établissement isPrioritaire), pas blacklisté, pas
déjà inscrit, et n'a pas fermé la pop-up (users.hasSeenMvlsPrioPopup, posé aussi au clic sur le bouton :
plus jamais réaffichée). Endpoint : GET /mvls-options/popup-phase → { phase: 'parcoursup' | 'entree' | null }.
- Elle suit la période d'inscription lycéens prioritaires (
dateOuvertureLyceenPrio→dateFermetureLyceenPrio), hors période elle n'apparaît pas. - Le texte change à
dateBasculePopupLyceenPrio(à renseigner dans le BO, sans valeur par défaut : vide, la pop-up reste surparcoursuptoute la période) : avant = aide pour le dossier Parcoursup (parcoursup), après = démarches d'entrée et premiers mois de cours (entree). - Les 3 dates sont éditables dans l'admin (
/adminspace/mvls, carte « Lycéen prioritaire », un seul bouton : la période est enregistrée avant la bascule). La date de bascule est refusée à l'enregistrement si elle sort de la période d'inscription ; si on change ensuite les dates prioritaires, la garder à l'intérieur.
Statuts lycéen
Stocké dans MvlsLyceen.statut (string libre, pas d'enum). Synchronisé dans les deux sens avec
Jeune.status de dema1n via RabbitMQ (user___mvls) : certains statuts sont posés par Inspire,
d'autres reçus de dema1n.
| Statut | Signification | Posé par |
|---|---|---|
EN_ATTENTE_ACTIVATION |
Questionnaire commencé, attente d'activation du compte par le lycéen | Inspire, à la création de la ligne |
EN_ATTENTE_PARENT |
Compte activé, validation parentale requise (lycéen < 18 ans) | Inspire, à l'activation |
APTE |
En attente de mise en relation | Inspire (activation majeur, consentement parental, update-statut), dema1n |
MATCHE |
Binôme trouvé et actif | dema1n |
NON_DISPONIBLE |
Indisponible : fin de binôme, campagne d'été, matching instantané annulé, ou déclaré par le lycéen | dema1n, Inspire (update-statut) |
HORS_PROGRAMME |
Hors programme : passé post-bac (USER_POSTBAC, cron du 10/01), inscription bloquée, ou action manuelle BO |
dema1n |
SORTI |
A quitté le programme | Inspire (cancelAndQuitMvls), dema1n (quitAllBinomes) |
EN_ATTENTE_ACTIVATION est posé dès la première sauvegarde du questionnaire (création de la ligne,
même partielle), pour les mineurs comme pour les majeurs.
Pour un mineur, l'activation du compte précède le consentement parental : EN_ATTENTE_ACTIVATION → EN_ATTENTE_PARENT → APTE.
Via POST /mvls-lyceen/update-statut, le lycéen ne peut passer qu'en APTE ou NON_DISPONIBLE,
et jamais en APTE depuis EN_ATTENTE_ACTIVATION ou EN_ATTENTE_PARENT.
Le statut MATCHE a un impact sur le passage de niveau automatique — voir docs/metier/niveau.md.
Statuts éclaireur
Stocké dans MvlsEclaireur.statut (string libre, pas d'enum). Synchronisé dans les deux sens avec
Benevole.status de dema1n via RabbitMQ (user___mvls).
| Statut | Signification | Posé par |
|---|---|---|
null |
Questionnaire non finalisé (charte pas encore validée) | — |
APTE |
Inscrit, en attente de mise en relation | Inspire (soumission finale, réinscription « C'est reparti »), dema1n (fin de binôme si inscriptions éclaireur ouvertes) |
MATCHE |
Binôme trouvé et actif | dema1n |
NON_DISPONIBLE |
Indisponible : fin de binôme hors période d'inscription éclaireur, déclaré depuis le profil, campagne d'été, indisponibilité temporaire (nonDispoUntil), ou choix admin en fin de binôme |
dema1n |
HORS_PROGRAMME |
Hors programme — uniquement par action manuelle BO | dema1n |
SORTI |
A quitté le programme | Inspire (cancelAndQuitMvls), dema1n (quitAllBinomes) |
Le statut reste null pendant les sauvegardes intermédiaires et passe à APTE à la soumission
finale (partial = false, charte = true), sans écraser un statut déjà posé (ex. MATCHE).
Côté dema1n, le bénévole MVLS est créé directement APTE : pas de passage par
EN_ATTENTE_ACTIVATION ni EN_ATTENTE_FORMATION.
Fin de binôme (BO dema1n). Quand un admin termine ou annule un binôme MVLS, le statut bénévole
proposé par défaut dépend de la période d'inscription éclaireur (dateOuvertureEclaireur /
dateFermetureEclaireur) : APTE si elle est ouverte, NON_DISPONIBLE sinon. Cela évite que les
mentors de l'année précédente restent matchables hors période ; ils se réinscrivent via « C'est
reparti » à la réouverture (voir section « Page de suivi — statut NON_DISPONIBLE »). L'admin
peut toujours choisir un autre statut.
Statuts dema1n hors MVLS
dema1n connaît d'autres statuts (INSCRIT, EN_COURS_ONBOARDING, EN_COURS_INSCRIPTION,
EN_ATTENTE_FORMATION, ASSOCIE_PARCOURS) propres aux parcours hors MVLS : les flux MVLS ne les posent
pas. Listes complètes côté dema1n : front/static/model/StatusList.js (jeune) et
StatusBenevoleList.js (bénévole).
Page de suivi — étapes affichées
/mentorat/suivi (Suivi.vue) est une seule page : un stepper vertical dont getSuiviSteps()
(front/src/utils/mvls-suivi.utils.ts) fixe les étapes selon statut. Les « 2 étapes » d'un statut
sont donc 2 pastilles du même stepper, pas 2 pages. Seule l'étape active déplie son contenu (la
première à l'arrivée, puis celle sur laquelle on clique) ; les autres n'affichent que leur libellé.
Étapes → libellé et contenu (composants de pages/userspace/mentorat/components/Suivi/) :
| Étape | Libellé | Contenu lycéen | Contenu éclaireur |
|---|---|---|---|
awaiting-validation |
En attente de la validation | AttenteValidation : activer son compte via le mail |
idem |
awaiting-parent-validation |
En attente de la validation parentale | AttenteValidation parent : attendre l'accord des parents |
idem |
awaiting-mer |
En attente de mise en relation | AttenteMerLyceen : mises en relation dès janvier, lien discussions |
AttenteMerEclaireur : mises en relation dès janvier, boutons formation et mise à jour du profil |
mentor-found |
Mentor trouvé, Mentoré si éclaireur | MentorTrouveLyceen : carte de l'éclaireur, bouton « Valider mon binôme » si EN_ATTENTE_JEUNE |
AttenteMerEclaireur uniquement si EN_ATTENTE_JEUNE (libellé alors « En attente de mise en relation »), sinon rien |
mer |
Mise en relation | aucun (jalon final, jamais déplié) | aucun, sauf une note sous le libellé si APTE (« mises en relation possibles jusqu'à la rentrée ») |
Statut → étapes affichées (dans l'ordre, la première est ouverte par défaut) :
| Statut | Étapes affichées |
|---|---|
EN_ATTENTE_ACTIVATION |
awaiting-validation, awaiting-mer |
EN_ATTENTE_PARENT |
awaiting-parent-validation, awaiting-mer |
APTE |
awaiting-mer, mer |
MATCHE |
mentor-found, mer |
NON_DISPONIBLE |
FinDeSuivi à la place du stepper (ci-dessous) |
Binôme en cours (isBinomeEnCours, front/src/utils/mvls.utils.ts) : la page redirige vers
/mentorat/ton-mentor (lycéen) ou /mentorat/ton-lyceen (éclaireur). EN_ATTENTE_JEUNE n'est pas
« en cours » côté front : le lycéen valide le binôme depuis la page de suivi, et l'éclaireur voit
« En attente de mise en relation » (contenu AttenteMerEclaireur) à la place de « Mentoré trouvé »
tant que le lycéen n'a pas validé.
Flux d'inscription
Lycéen (questionnaire MVLS)
Le point d'entrée public destiné aux ateliers est /inscription-mvls-ateliers. Il vérifie
la période d'ouverture prioritaire (GET /mvls-options/is-open-pp) et redirige vers
/accueil hors période. L'intention MVLS (sessionStorage inspire_mvls_intent) est
posée par le guard de routage dès l'arrivée sur la page. Le CTA envoie le SSO vers
/mentorat?atelier pour que le callback puisse distinguer un retour atelier.
Après le retour du SSO, les utilisateurs des cas 2, 3 et 4 sautent la question
Classe : users.niveau et parcours-lyceens.niveau sont posés à Terminale.
Si l'URL de retour (callback ou targetUrl) contient le query param atelier,
ils sont aussi marqués avec users.origineInscriptionMvls = 'atelier_mvls'.
Le cas 1 n'est pas concerné puisqu'il est déjà connecté et ne passe pas par
le callback SSO.
Ordre des pages : Scolarisation → Besoins → Domaines → Après le bac → Accompagnement → Informations personnelles.
L'étape Ressenti n'est plus présentée (colonnes etudesSup / travailEfficace / preparationSup conservées, nullable).
Si le lycéen n'a pas de questionnaire Inspire finalisé, le funnel MVLS commence par :
- Skip de la page rôle (rôle lycéen automatique, intent
sessionStorageinspire_mvls_intent) - Mon profil Inspire
- Lycée (département, établissement,
isBoursierSecondaire) - Bac (
filiere,niveau= Terminale) POST /parcours-lyceen/formulaire(stockesurveyResultsans redemander le questionnaire orientation complet)- Puis les pages MVLS ci-dessus
Si Inspire est déjà finalisé (algoResult.results), on n'affiche que les pages MVLS.
Questionnaire (partial saves)
→ soumission finale (charte=true) → USER_CREATED envoyé à dema1n
→ EN_ATTENTE_ACTIVATION
→ email lycéen → activation token
→ (si mineur) EN_ATTENTE_PARENT → email parent → APTE
→ (si majeur) APTE
→ (dema1n) → MATCHE
Éclaireur
Questionnaire (partial saves)
→ soumission finale (charte=true) → USER_CREATED envoyé à dema1n
→ APTE → (dema1n) → MATCHE
Recette du funnel lycéen
Depuis front/ : yarn test:mvls. Le script rejoue les 4 cas d'inscription
(connecté avec Inspire finalisé / via SSO avec Inspire finalisé / sans compte /
compte Inspire incomplet) sur les modules réels — mvls-intent et le guard de
routage use-auth/router — seul sso.service étant remplacé par un stub.
Sources dans front/test/mvls-funnel/.
Intégration dema1n (RabbitMQ)
Routing key sortante : user___mvls
| Événement | Déclencheur |
|---|---|
USER_CREATED |
Soumission finale du formulaire LY ou EE |
USER_UPDATED |
Mise à jour de profil ou de parcours (seulement si charte === true) |
Les binômes et suivis arrivent de dema1n via RabbitMQ — voir RabbitMQ.