Simulateur Flow (CMMS sandbox)
Périmètre : outil interne CMMS qui rejoue n'importe quel flow transactionnel actif comme le ferait l'application mobile, en utilisant les vrais endpoints de l'orchestrator. Page : Sandbox > Flow (/sandbox/flow). Source de référence : ticket NEX-438 (Epic SIM). Date : 2026-05-12.
1. Objectif
Le simulateur permet à un admin (ou un dev) de dérouler un flow transactionnel de bout en bout sans toucher à l'app mobile. C'est le moyen le plus rapide de :
- Valider un changement de
FlowSchemaavant rollout (cf. Flows config-driven). - Reproduire un bug remonté par le support, en impersonant le user concerné.
- Tester un changement de policy / risk-engine sans avoir besoin d'un téléphone physique.
- Démontrer un parcours métier (P2P, MERCHANT_PAY, CASH_IN/OUT, MA_*) à un stakeholder.
Le simulateur n'est pas une maquette : il appelle les vrais endpoints POST /v1/intents/* de l'orchestrator. Tout intent qu'il crée est réel, comptabilisé, et apparaît dans /transactions.
2. Architecture
2.1 Briques
apps/cmms/layers/
├── 0-core/app/stores/
│ └── sandbox-flow-simulator.ts # Store Pinia — état + actions API
└── 5-admin/app/
├── pages/sandbox/flow.vue # Page wizard
└── components/sandbox-flow/
├── StepRecipientInput.vue # kind: recipient_input
├── StepAmountInput.vue # kind: amount_input
├── StepRiskEvaluation.vue # kind: risk_evaluation (pre-flight)
├── StepReviewScreen.vue # kind: review_screen
├── StepPinChallenge.vue # kind: pin_challenge
├── StepOtpChallenge.vue # kind: otp_challenge
└── StepUpdateRequired.vue # fallback "kind inconnu"2.2 Store — useSandboxFlowSimulatorStore
État exposé :
| Champ | Rôle |
|---|---|
availableFlows | Catalogue des FlowSchemaRow actifs récupérés via GET /config/transaction-flow-schemas. |
currentFlow | Flow sélectionné par l'admin. |
senderPhone / senderCountryCode / senderConfirmed | User impersoné (cf. §4). |
intent | Intent en cours (mutation via mergeIntent à chaque appel API). |
state | State machine (idle, creating, created, otp-sent, pin-verified, succeeded, failed…). |
preflightDecision / preflightLoading / preflightError | Décision retournée par POST /v1/intents/:id/preflight-risk (cf. Pre-flight risk). |
loading / error | Indicateurs UI génériques. |
Actions principales :
loadAvailableFlows()— recharge le catalogue.selectFlow(transactionType)— sélection + reset interne.confirmSender(phone, country)/unconfirmSender()— impersonation.createIntent(payload)—POST /v1/intents.sendOtp()/verifyOtp(code)/verifyPin(pin)— étapes API auth.preflightRisk()—POST /v1/intents/:id/preflight-risk(idempotent côté store).confirmIntent()—POST /v1/intents/:id/confirm.cancelIntent()/refreshIntent()/reset()/resetAll().
2.3 Page sandbox/flow.vue
Pilote 3 zones distinctes :
- Hero (catalogue + meta) —
USelectdu flow actif, badges version / nb steps / post-actions / description. - Sender card — bloque le déroulement tant que l'admin n'a pas confirmé l'identité impersonée.
- Wizard grid — sidebar verticale (
ProceduresWizardSidebar) + renderer courant + panel droit (intent summary + timeline live).
Le mapping kind → renderer est centralisé dans le registry :
const STEP_RENDERERS: Record<string, unknown> = {
recipient_input: StepRecipientInput,
amount_input: StepAmountInput,
pin_challenge: StepPinChallenge,
otp_challenge: StepOtpChallenge,
review_screen: StepReviewScreen,
risk_evaluation: StepRiskEvaluation,
}Un kind inconnu retombe sur StepUpdateRequired (pas de fallback silencieux).
3. Cycle de vie d'une simulation
4. Impersonation admin
L'admin connecté (caller) n'est pas systématiquement le sender de la transaction simulée. Avant chaque simulation, l'admin saisit le numéro du user au nom duquel le flow doit se dérouler. Ce numéro est transmis dans le payload createIntent comme agentPhone + agentCountryCode, et l'orchestrator résout l'identité via :
findPersonByPhone(phone, countryCode) → personId
getUserByPersonId(personId) → userIdLe userId résolu sert d'initiator à l'intent (et donc de sender effectif pour le ledger, les notifications, l'audit trail).
Pourquoi obligatoire ? Les flows agent (
CASH_IN,CASH_OUT,MA_*) requièrent un user avec le bon rôle. L'admin connecté est rarement un agent ; sans impersonation, l'eligibility check refuserait l'opération.
Pour les flows non-agent (P2P, MERCHANT_PAY), l'impersonation reste recommandée mais l'admin peut aussi se simuler lui-même s'il est un customer valide.
5. Différences avec l'application mobile
| Aspect | Mobile (Nex / Nex Business) | Simulator CMMS |
|---|---|---|
Source du FlowSchema | Snapshot embarqué dans intent.flow_snapshot retourné par create. | Catalogue chargé via GET /config/transaction-flow-schemas. |
| Renderers | @nex/ui-mobile (React Native). | @nex/ui-web + Nuxt UI 4 (Vue 3). |
| Auth | Firebase custom token (PIN → API → token). | Session admin CMMS (Bearer JWT classique). |
| Impersonation | N/A (le user opère pour lui-même). | agentPhone injecté dans le payload create. |
risk_evaluation | Identique : POST /v1/intents/:id/preflight-risk au mount. | Identique. |
pin_challenge | Saisie réelle, PIN custom du user. | Saisie réelle, PIN custom du user impersoné. |
| Post-actions | Reçus par le user (SMS, push, callback marchand). | Reçus par le user impersoné (et l'admin n'a pas visibilité). |
Conséquence importante : une simulation
succeededest une vraie transaction. Le ledger est débité, le destinataire est crédité, les notifications partent. Il n'existe pas de "mode dry-run" — c'est volontaire pour garantir que ce que voit le simulator est exactement ce que vivra le mobile.
6. Configurations Flows (page sœur)
Configurations > Flows (/configurations/flows) liste les 7 FlowSchema actifs et permet :
- Toggle
is_active(active/désactive un flow sans deploy). - Visualiseur graphique (
FlowSchemaVisualizer) qui rend chaque step en pipeline. - Vue JSON brute en option (toggle dans le détail).
Une modification de is_active=false retire immédiatement le flow du catalogue du simulator et bloque la création d'intent côté mobile (CreateIntentUseCase renvoie une erreur "flow inactif").
7. Limites et pièges connus
- Pas de mode dry-run — toute simulation
succeededest comptabilisée. Pour les démos qui doivent rester sans trace, utilisercancelIntentavant le PIN ou rester dans un environnement de staging. - Le retour en arrière (
canGoBack) est désactivé une fois l'intent créé. Comme l'intent est créé à la fin deamount_input(pour pouvoir appeler le pre-flight), on ne peut plus revenir sur le destinataire / montant à partir derisk_evaluation. La seule sortie estRéinitialiser. - L'admin connecté doit avoir la permission
transactions.createpour utiliser le simulator (mêmePermissionGuardque les endpoints orchestrator). - L'impersonation ne contourne pas les contrôles KYC / éligibilité — si le user impersoné n'a pas le KYC requis, le pre-flight retourne
blockedet le wizard s'arrête. C'est la même politique que sur mobile.
8. Références
- Store :
apps/cmms/layers/0-core/app/stores/sandbox-flow-simulator.ts - Page :
apps/cmms/layers/5-admin/app/pages/sandbox/flow.vue - Renderers :
apps/cmms/layers/5-admin/app/components/sandbox-flow/ - Visualiseur de schéma :
apps/cmms/layers/5-admin/app/components/flow-schema/FlowSchemaVisualizer.vue - Page sœur Configurations :
apps/cmms/layers/5-admin/app/pages/configurations/flows/ - DSL FlowSchema : ./transaction-flows-config-driven
- Pre-flight risk : /services/risk-engine/preflight