Skip to content

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 FlowSchema avant 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é :

ChampRôle
availableFlowsCatalogue des FlowSchemaRow actifs récupérés via GET /config/transaction-flow-schemas.
currentFlowFlow sélectionné par l'admin.
senderPhone / senderCountryCode / senderConfirmedUser impersoné (cf. §4).
intentIntent en cours (mutation via mergeIntent à chaque appel API).
stateState machine (idle, creating, created, otp-sent, pin-verified, succeeded, failed…).
preflightDecision / preflightLoading / preflightErrorDécision retournée par POST /v1/intents/:id/preflight-risk (cf. Pre-flight risk).
loading / errorIndicateurs 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 :

  1. Hero (catalogue + meta)USelect du flow actif, badges version / nb steps / post-actions / description.
  2. Sender card — bloque le déroulement tant que l'admin n'a pas confirmé l'identité impersonée.
  3. Wizard grid — sidebar verticale (ProceduresWizardSidebar) + renderer courant + panel droit (intent summary + timeline live).

Le mapping kind → renderer est centralisé dans le registry :

ts
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)            → userId

Le 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

AspectMobile (Nex / Nex Business)Simulator CMMS
Source du FlowSchemaSnapshot 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).
AuthFirebase custom token (PIN → API → token).Session admin CMMS (Bearer JWT classique).
ImpersonationN/A (le user opère pour lui-même).agentPhone injecté dans le payload create.
risk_evaluationIdentique : POST /v1/intents/:id/preflight-risk au mount.Identique.
pin_challengeSaisie réelle, PIN custom du user.Saisie réelle, PIN custom du user impersoné.
Post-actionsReç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 succeeded est 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 succeeded est comptabilisée. Pour les démos qui doivent rester sans trace, utiliser cancelIntent avant 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 de amount_input (pour pouvoir appeler le pre-flight), on ne peut plus revenir sur le destinataire / montant à partir de risk_evaluation. La seule sortie est Réinitialiser.
  • L'admin connecté doit avoir la permission transactions.create pour utiliser le simulator (même PermissionGuard que 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 blocked et 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

Nex — Plateforme fintech CEMAC