Skip to content

Système de suivi des binômes

Dernière mise à jour : 13/04/2026

Vue d'ensemble

Le suivi des binômes est un système automatisé qui envoie des messages (emails / SMS) aux jeunes et bénévoles à des étapes clés de leur mentorat. Il permet de :

  • Détecter les binômes inactifs ou en difficulté
  • Suivre l'avancement de l'accompagnement
  • Collecter les retours des deux parties (statut + commentaire libre)
  • Déclencher des actions admin automatiques selon les réponses

Architecture technique

Schéma de données

step_sequence          → Séquence de suivi (ex: DEMA1N, MVLS)
  ├── steps            → Étapes individuelles (J+15, J+45, J+90…)
  │     ├── step_message (jeuneStep)   → Config message jeune
  │     │     └── message_template     → Template email/SMS
  │     ├── step_message (benevoleStep)→ Config message bénévole
  │     │     └── message_template     → Template email/SMS
  │     └── suivistatus[]              → Enregistrements de suivi créés
  ├── extendedStepInterval             → Intervalle de prolongation (jours)
  └── binomes[]                        → Binômes rattachés

suivistatus
  ├── status           → Statut global (EN_ATTENTE, ACTIF, INACTIF, AMBIGU)
  ├── statusJeune      → Statut côté jeune (EN_ATTENTE, RAS, Inactif)
  ├── statusBenevole   → Statut côté bénévole
  ├── commentJeune     → Commentaire libre jeune
  ├── commentBenevole  → Commentaire libre bénévole
  ├── dayStep          → Jour de déclenchement (ex: 15, 45, 90…)
  └── readJeune / readBenevole → Dates de lecture admin

step_message_binome    → Journal des messages envoyés
  ├── dateEnvoi        → Date d'envoi (toujours renseigné, envoi immédiat)
  ├── destination      → Email ou téléphone
  ├── error            → true = erreur à l'envoi
  ├── message          → Contenu SMS ou erreur (vide pour Brevo)
  ├── subject          → Sujet du mail / identifiant du template
  └── dayStep          → Jour de la step

Tables clés

Table Rôle
step_sequence Séquence de suivi (nom, intervalle prolongation)
steps Étape dans une séquence (jour, suivi activé, étendu)
step_message Configuration d'un message (type mail/sms, destination jeune/bénévole, template)
message_template Template du message (HTML ou Brevo template ID)
step_message_binome Journal effectif des messages envoyés par binôme
suivistatus Enregistrement de suivi par binôme et étape

Séquences

La sélection de la séquence se fait via StepSequenceService.getStepSequenceBySandbox() : - Sandbox MVLS → séquence nommée 'MVLS' - Sandbox DEMA1N (ou autre) → séquence avec id: '3' (hardcodé)

Flux complet : du cron à la réponse

1. Cron prepareMessages

Fichier : back/src/binomes/services/cron-tasks.service.ts Fréquence : toutes les 30 minutes ('2 */30 * * * *'), sur le Worker uniquement (WORKER_SERVER === '1')

Après chaque exécution, countRecentMessages(startedAt) compte les messages envoyés pendant cette exécution et les affiche dans les logs du worker.

Algorithme

1. Récupérer tous les binômes ACTIF/INACTIF/BAD/EN_ATTENTE/AMBIGU
   dont nextStepDate ≤ aujourd'hui OU nextStepDate IS NULL

2. Grouper par sandbox

3. Pour chaque sandbox → charger la séquence + steps + templates

4. Pour chaque binôme :
   a. Si nextStepDate est null → recalculer (recalculateDateAndLastStep)
   b. Calculer dayFromStart = jours depuis creationDate
   c. Calculer dayOfNextStep = jours entre creationDate et nextStepDate
   d. Si dayFromStart < dayOfNextStep → recalculer
   e. Chercher la step correspondant à dayOfNextStep :
      - Si trouvée et pas encore de suivistatus → launchStep
      - Si dayOfNextStep > dernière step → lancer step étendue (prolongation)
      - Sinon → recalculer
   f. Mettre à jour nextStepDate et lastStepDone

Détail de launchStep

1. Charger les relations manquantes du binôme (jeune, bénévole, admin)
2. Créer un suivistatus (EN_ATTENTE si suiviActivated)
3. Si suiviActivated :
   - Passer le binôme en statut EN_ATTENTE
   - Logique spéciale J+15 (VIP/partenaire) et J+45 (programme EL)
     → passage en state "Appel de suivi"
4. Pour chaque message (jeune + bénévole) de la step :
   - Déterminer le destinataire et l'URL
   - Appeler createMessage

2. Création des messages (createMessage)

Fichier : back/src/binomes/services/messages.service.ts (ligne 671)

Signature :

createMessage(template, destination, binome, partUrl, step, messageType, dayStep?, suiviId?, destinationTypeParam?)

Paramètres :

Paramètre Type Provenance
template MessageTemplate Vient de step.jeuneStep.template ou step.benevoleStep.template
destination string Email ou téléphone, déterminé par getDestination()
binome Binome Le binôme courant (avec relations chargées)
partUrl string /jeune/{jeuneId} ou /benevole/{benevoleId} — sert à construire les URLs de réponse
step Steps La step en cours de traitement
messageType string 'mail' ou 'sms' (vient de StepMessage.type)
dayStep number? Jour depuis la création du binôme (ex: 15, 45, 90…)
suiviId string? ID du suivistatus créé par launchStep (pour les URLs MVLS)
destinationTypeParam 'jeune' \| 'benevole'? Type explicite du destinataire

Algorithme détaillé

1. BRANCHEMENT : template.brevoTemplateId existe ?

   ┌─ OUI → CHEMIN BREVO ─────────────────────────────────────────────┐
   │                                                                    │
   │  a. Déterminer le destinationType ('jeune' ou 'benevole')          │
   │     Priorité :                                                     │
   │     1) Paramètre explicite destinationTypeParam                    │
   │     2) Nom du template contient '-jeune' ou '-benevole'            │
   │     3) Fallback : comparaison email destination vs email jeune     │
   │                                                                    │
   │  b. Construire les variables Brevo                                 │
   │     - MVLS → prepareMvlsBrevoParams(binome, destinationType, …)   │
   │     - DEMA1N → prepareSuiviBrevoParams(binome, step, dayStep, partUrl) │
   │                                                                    │
   │  c. Nettoyer l'email (retirer le préfixe 'mvls_')                 │
   │                                                                    │
   │  d. Valider brevoTemplateId (conversion Number, check NaN)         │
   │                                                                    │
   │  e. Appeler mailService.sendForTemplate({                          │
   │       templateId, messageVersions: [{ to, params }]                │
   │     })                                                             │
   │     → Envoi IMMÉDIAT via API Brevo POST /smtp/email                │
   │     → En dev/staging : redirige vers Mailcatcher via               │
   │       sendBrevoTemplateViaMailcatcher                              │
   │                                                                    │
   │  f. Créer StepMessageBinome (journal) :                            │
   │     - Succès : dateEnvoi=now, error=false, message=''              │
   │     - Échec  : dateEnvoi=now, error=true, message=erreur           │
   │                                                                    │
   │  g. return (sortie de la fonction)                                 │
   └────────────────────────────────────────────────────────────────────┘

   ┌─ NON + messageType === 'sms' → CHEMIN SMS (IMMÉDIAT) ─────────────┐
   │                                                                    │
   │  a. Construire le contenu SMS :                                    │
   │     mailService.replaceVariables(template.html, {jeune,benevole,  │
   │     admin})                                                        │
   │                                                                    │
   │  b. Envoyer via smsService.send(PhoneData)                         │
   │     → Envoi IMMÉDIAT                                               │
   │                                                                    │
   │  c. Créer StepMessageBinome (journal) :                            │
   │     - Succès : dateEnvoi=now, error=false, message=contenu SMS     │
   │     - Échec  : dateEnvoi=now, error=true, message=erreur           │
   │                                                                    │
   │  d. return (sortie de la fonction)                                 │
   └────────────────────────────────────────────────────────────────────┘

   ┌─ SINON → ALERTE (template mail sans brevoTemplateId) ────────────┐
   │                                                                    │
   │  Tous les templates mail doivent avoir un brevoTemplateId.         │
   │  Si ce cas se présente :                                           │
   │     → console.error + Sentry.captureMessage (niveau error)         │
   │     → Le message n'est PAS envoyé                                  │
   └────────────────────────────────────────────────────────────────────┘

2. CATCH : Si erreur à n'importe quelle étape
   → Sentry.captureException + log console
   → Le message n'est pas créé / partiellement créé

Trois chemins d'envoi — Résumé

Critère Chemin Brevo (email) Chemin SMS Fallback (alerte)
Condition template.brevoTemplateId renseigné messageType === 'sms' Aucun des deux
Variables prepareSuiviBrevoParams ou prepareMvlsBrevoParams replaceVariables sur template.html
Envoi Immédiat via API Brevo Immédiat via smsService.send Pas d'envoi (alerte Sentry)
Journal StepMessageBinome avec dateEnvoi: now StepMessageBinome avec dateEnvoi: now Aucun
Contenu stocké message: '' (template dans Brevo) message: contenu SMS

Détermination du destinataire (getDestination)

Appelé dans launchStep avant createMessage. Lit StepMessage.type et StepMessage.destination :

Si type === 'mail' :
  - destination 'jeune'    → email = binome.jeune.user.email
                              partUrl = /jeune/{jeuneId}
  - destination 'benevole' → email = binome.benevole.user.email
                              partUrl = /benevole/{benevoleId}

Si type === 'sms' :
  - destination 'jeune'    → phone = binome.jeune.phone
  - destination 'benevole' → phone = binome.benevole.phone

Construction du hash de sécurité (suivi)

Le hash sert à authentifier les réponses sans connexion utilisateur :

Hash = MD5( binomeId + partUrl + '/step/' + stepId + '/' + dayStep )

Exemple pour un jeune, step 26, J+15 :
  MD5( "42" + "/jeune/7" + "/step/" + "26" + "/15" )
  = MD5( "42/jeune/7/step/26/15" )

Ce hash est inclus dans l'URL de chaque bouton du mail. Le ReponseController recalcule le même hash pour valider la réponse.

Variables Brevo (DEMA1N) — prepareSuiviBrevoParams

Variable Description
PRENOM_JEUNE, NOM_JEUNE Identité jeune
PRENOM_BENEVOLE, NOM_BENEVOLE Identité bénévole
PRENOM_ADMIN, NOM_ADMIN, CODE_ADMIN Admin associé (fallback "L'équipe DEMA1N.org")
schoolCursus, filiereJeune Infos scolaires
besoinsJeune Besoins du jeune (noms séparés par des virgules)
sousBesoinsJeune Sous-besoins du jeune (noms séparés par des retours à la ligne)
moodJeune Humeur du jeune (ex: "Au top", "Non renseigné")
secteurJeune Secteurs d'intérêt du jeune
boutonProfilBenevole URL du profil bénévole (/compte/benevole/profil)
boutonDashboardJeune URL du dashboard jeune (/compte/jeune/dashboard)
boutonRasJeune / boutonRasBenevole Lien "Nous échangeons" / "On avance"
boutonInactifJeune / boutonInactifBenevole Lien "Il y a un problème"
boutonObjectifJeune / boutonObjectifBenevole Lien "Objectifs atteints"

Ces boutons ne sont renseignés que si step.suiviActivated && partUrl (sinon absents du mail).

Variables Brevo (MVLS) — prepareMvlsBrevoParams

Variable Description
PRENOM, PRENOM_admin Prénom du destinataire / admin
prenom_mentor, tel_mentor, mail_mentor Infos mentor (email sans préfixe mvls_)
PRENOM_LY, tel_LY, mail_LY Infos lycéen
CP1 Date J+5 formatée (uniquement à dayStep === 0)
contactok URL Inspire : réponse "contact ok" (token MD5 spécifique)
nocontact URL Inspire : réponse "pas de contact" (token MD5 spécifique)

Variables SMS

Les SMS utilisent mailService.replaceVariables(template.html, {jeune, benevole, admin}) pour injecter les données dans le template. Les variables disponibles sont les champs directs des entités (dot notation, ex: {{jeune.user.firstName}}).

3. Réponse au suivi — Flux email

Quand un jeune ou bénévole clique sur un bouton dans l'email de suivi, il est redirigé vers une URL de la forme :

/reponse/{type}/{id}/{actif}/{hash}?daystep={dayStep}
  • type : jeune ou benevole
  • id : ID du jeune ou bénévole
  • actif : 1 (RAS/actif), 0 (Inactif), 2 (Objectif atteint)
  • hash : MD5 de {binomeId}/{type}/{id}/step/{stepId}/{dayStep}
  • daystep : jour de la step (query param)

Flux front

  1. Page front/pages/reponse/_type/_id/_actif/_hash.vue
  2. Au chargement : POST /reponse → enregistre le statut
  3. Affichage conditionnel selon actif :
  4. 1 : message positif + champ commentaire libre
  5. 0 : sélecteur de raison + champ commentaire
  6. 2 : message objectif atteint + commentaire
  7. Soumission du commentaire : POST /reponse/comment

Flux backend (ReponseController)

POST /reponse (sans auth — accès via hash) : 1. Retrouve le jeune/bénévole avec ses binômes et suivistatus 2. Vérifie le hash MD5 pour identifier le binôme et la step 3. Vérifie que c'est le suivi le plus récent (anti-réponse sur ancien mail) 4. Appelle changeSuivistatus(statusId, type, status, state) 5. Retourne le CP actuel (CP1/CP2/CP3/CP4) pour le wording front

POST /reponse/comment (sans auth) : 1. Même vérification de hash 2. Enregistre le commentaire via suivistatusService.commentStatus() 3. Si bénévole + "Je souhaite que l'on m'appelle" → state "Appel de suivi" 4. Si bénévole + J≥45 + "on est en contact" → marque prise de contact 5. Si commentaire d'inactivité → émet un événement verbatim

5. Réponse au suivi — Flux dashboard

Les utilisateurs connectés peuvent aussi répondre depuis leur dashboard.

POST /reponse/dashboard (auth JWT) : - Identifie jeune/bénévole via l'utilisateur connecté - Appelle changeSuivistatus avec le suivistatusId fourni

POST /reponse/dashboard/comment (auth JWT) : - Même principe pour le commentaire

6. Calcul du statut global (getSuivistatusStatus)

La combinaison des réponses jeune + bénévole donne le statut global :

statusJeune statusBenevole → status global
RAS RAS ACTIF
RAS EN_ATTENTE ACTIF
EN_ATTENTE RAS ACTIF
EN_ATTENTE EN_ATTENTE EN_ATTENTE
Inactif EN_ATTENTE INACTIF
EN_ATTENTE Inactif INACTIF
Inactif Inactif INACTIF
RAS Inactif AMBIGU
Inactif RAS AMBIGU

Conséquences sur le state du binôme : - INACTIF ou AMBIGU + state "Autonome" → state "A traiter" - ACTIF → state "Autonome" - actif = 2 (objectif atteint) → state "A cloturer"

7. Prolongation (steps étendues)

Quand toutes les steps définies d'une séquence sont épuisées, le système continue de créer des suivis à intervalle fixe (extendedStepInterval jours) en utilisant les steps marquées extended: true.

Cela ne s'applique pas au sandbox MVLS (pas de prolongation).

Affichage front

Dashboard jeune/bénévole

  • Timeline (parcours-timeline.vue) : affiche les 3 derniers suivis + le prochain
  • Réponse in-app (cp-question.vue) : boutons RAS/Inactif sur l'étape courante
  • Carte binôme (matchCardJeune/Benevole.vue) : nombre de jours avant le prochain suivi
  • Frise détaillée (binomeSteps.vue) : tous les J+ avec icônes messages/commentaires

Back-office

  • Liste binômes (bo/binomes/index.vue) : colonne "étape de suivi" avec pastille notification
  • Fiche binôme (bo/binomes/_id/index.vue) : frise binomeSteps complète
  • Logs (bo/binomes/_id/logs.vue) : tableau des StepMessageBinome (date, destinataire, sujet, contenu)

Vuex

  • store/user.js : calcule les dates d'étapes lors du fetchFullUser, dispatch binome/generateRealDates
  • store/binome.js : stocke stepsDates (getter generatedStepsDates) pour alimenter la timeline

Fichiers clés

Backend

Fichier Rôle
services/cron-tasks.service.ts Cron prepareMessages + log du nombre de messages envoyés
services/messages.service.ts Logique de préparation, création, envoi immédiat et comptage des messages
services/suivistatus.service.ts CRUD suivistatus, calcul statut global
services/stepSequence.service.ts Récupération des séquences
services/mail.service.ts API Brevo, construction HTML, remplacement de variables
controllers/reponse.controller.ts Endpoints de réponse (email et dashboard)
entities/Steps.entity.ts Entité step
entities/StepSequence.entity.ts Entité séquence
entities/StepMessage.entity.ts Config message par step
entities/MessageTemplate.entity.ts Template (HTML ou Brevo ID)
entities/StepMessageBinome.entity.ts Journal des messages envoyés
entities/Suivistatus.entity.ts Enregistrement de suivi

Frontend

Fichier Rôle
pages/reponse/_type/_id/_actif/_hash.vue Page de réponse email
components/compte/dashboard/parcours-timeline.vue Timeline dashboard
components/compte/dashboard/tooltip/cp-question.vue Réponse in-app
components/rating/binomeSteps.vue Frise J+ détaillée
pages/bo/binomes/_id/logs.vue Logs admin
services/reponse.js Service API réponses
store/user.js / store/binome.js State management dates
static/model/ReponsesSuivis.js Wording des CPs

Diagramme de flux simplifié

  Cron prepareMessages (toutes les 30 min)
         │
         ▼
  Pour chaque binôme avec nextStepDate ≤ aujourd'hui
         │
         ├── Step trouvée ?
         │     │
         │     ├─ OUI → launchStep()
         │     │         ├── Créer suivistatus (EN_ATTENTE)
         │     │         ├── Passer binôme EN_ATTENTE
         │     │         └── Pour chaque message (jeune + bénévole) :
         │     │               ├── Template Brevo ? → Envoi immédiat API Brevo
         │     │               ├── SMS ?            → Envoi immédiat SMS
         │     │               └── Sinon            → ALERTE (pas d'envoi)
         │     │
         │     └─ NON + après dernière step → lancer step étendue
         │
         └── Mettre à jour nextStepDate + lastStepDone
         │
         ▼
  countRecentMessages(startedAt) → log "N message(s) envoyé(s)"

  Utilisateur clique lien email
         │
         ▼
  GET /reponse/{type}/{id}/{actif}/{hash}
         │
         ├── POST /reponse → changeSuivistatus
         └── POST /reponse/comment → commentStatus
                    │
                    ▼
              getSuivistatusStatus → statut global
                    │
                    ▼
              changeStatusBinome + changeStateBinome