HOCO B2B
DesignScreen specificationsDelegate app

Access

Splash and version check, sign-in, password reset and the permissions and location-consent gate of the delegate app (DA-01 to DA-04).

These four screens get a délégué (field sales delegate) from a cold start to the home screen: version policy, session, account status, and the consent that every later position depends on (cahier §6, §9, §10). Shared rules — offline recording, sync badges, settings, error fallbacks — are on the delegate app overview.

DA-01 — Splash & version check

FieldValue
French title« Démarrage »
ReleaseV1
Design dayDay 11 — DES-11
Build taskMOB-05 — Navigation, app shells & system states
RequirementsAUTH-09 · ADM-07
Reference screenNone — system screen (cahier §9, §13.4)

Purpose. Open the app in under 3 seconds on a mid-range phone (NFR-01), restore the session, enforce the minimum version (AUTH-09, ADM-07) and route the delegate to the right screen — without ever waiting for a network that may not be there.

Entry points & exits. Entry: app launch; tap on a notification while the process was killed (the route is kept and opened after the splash). Exits: DA-02 (no session), change-password sheet (temporary password), DA-04 (consent missing or outdated), DA-10 (a visit is CHECKED_IN or IN_PROGRESS), the pending notification route, DA-05 (default), DA-28 (forced update, start-up error).

Layout.

  1. Full-bleed brand background, HOCO logo centred, app name « HOCO Délégué ».
  2. Progress indicator, shown only if routing takes more than 1 s.
  3. Version line at the bottom (1.4.2, with the flavor suffix staging or dev outside production).

Data.

FieldAPI sourceOffline sourceFreshness
Session token—Secure storage (flutter_secure_storage)Until a 401
Version policyappConfig.get → minVersion, latestVersion, forceUpdate, updateAvailable, storeUrl, releaseNotesapp_configEvery cold start and after one hour in the background, timeout 2 s; cached copy otherwise
MaintenanceappConfig.get → maintenance.enabled, .message, .untilapp_configSame
Public settings and server timeappConfig.get → settings.field.*, settings.sync.*, serverTimeapp_config, clock offset in sync_stateSame
User, status, temporary password, consentme.get → status, mustChangePassword, consents.locationTrackingVersion, delegate.zonesprofileRefreshed in the background after routing
Visit to resume—visits where status is CHECKED_IN or IN_PROGRESSLive-local
Push registrationme.devices.registersync_state (last registered token and version)Every start, after the session check

Actions (automatic, no user input).

  1. Open the drift database and run its migrations; in parallel read the token. Migrations are additive and never touch outbox, upload_queue or location_pings.
  2. Call appConfig.get with app=DELEGATE_APP&platform=android&version=<installed>; store the result and the clock offset.
  3. Route with the table below.
  4. After routing, in the background: me.get, delegate.profile, me.devices.register (Idempotency-Key generated per registration attempt and reused until it succeeds), then a sync cycle (push the outbox, then sync.delta, then delegate.tours.today if no snapshot exists for today).
Condition, checked in this orderDestination
Database cannot be openedDA-28 start-up error
Installed version below minVersion (fresh or cached policy)DA-28 forced update
No tokenDA-02
Cached Me.status is SUSPENDED or DISABLEDDA-02 with the explicit message
mustChangePasswordChange-password sheet (auth.password.change)
consents.locationTrackingVersion empty or older than the notice versionDA-04
A visit is CHECKED_IN or IN_PROGRESSDA-10 of that visit, stacked on DA-06
A notification route is pendingThat route
OtherwiseDA-05

Rules.

  • The forced update applies even offline when the cached policy demands it; queued data survives the store update (MOB-09).
  • maintenance.enabled does not block the delegate app: it opens on local data with the maintenance banner, and the sync engine pauses until until (calls would answer 503 MAINTENANCE).
  • A 426 UPGRADE_REQUIRED received later on any call leads to the same forced-update state.
  • Sessions last 30 days and slide with activity; a 401 sends the delegate to DA-02 with the phone prefilled and the outbox kept.

States.

StateTriggerUI
LoadingCold startSplash; never more than 3 s before routing
OfflineappConfig.get times outCached policy used; Home shows the offline banner
Maintenancemaintenance.enabledHome banner « Maintenance en cours jusqu'à 14:00. Vous pouvez continuer hors ligne. »
Update availableupdateAvailableDismissible sheet once per version: « Une nouvelle version est disponible » with releaseNotes, « Mettre à jour » / « Plus tard »
Forced updateBelow minVersionDA-28
Start-up errorDatabase corrupt or migration failed« Impossible d'ouvrir les données de l'application. » with « Réessayer » and « Envoyer un rapport » (Sentry); local data is never wiped automatically

Copy. « HOCO Délégué » · « Une nouvelle version est disponible » · « Mettre à jour » · « Plus tard » · « Impossible d'ouvrir les données de l'application. » · « Réessayer » · « Envoyer un rapport ».

Acceptance criteria.

  1. With a cached session, the interactive Home appears within 3 s of a cold start on the reference mid-range phone, online and in airplane mode.
  2. In airplane mode the app opens on DA-05 with cached data and the offline banner; no error dialog appears.
  3. With minVersion above the installed version the forced-update screen is shown, and the items queued before the update are still in DA-27 after installing the new version.
  4. Killing the app during a visit and reopening it lands on DA-10 of that visit with the correct elapsed duration.
  5. A notification tapped while the app was killed opens its target screen after the splash.

DA-02 — Sign in

FieldValue
French title« Connexion »
ReleaseV1
Design dayDay 11 — DES-11
Build taskMOB-06 — Authentication flows (both apps)
RequirementsAUTH-02 · AUTH-08
Reference screendelegate-01-login-profile — shown below
Client reference — the upper half (sign-in form) is DA-02; the profile preview below it belongs to DA-24
Client reference — the upper half (sign-in form) is DA-02; the profile preview below it belongs to DA-24 — client reference (cahier des charges)

Purpose. Let a delegate open a session with their phone number and password; refuse suspended or disabled accounts with an explicit message (cahier §6 acceptance criterion; AUTH-02, AUTH-08).

Entry points & exits. Entry: DA-01 without a session, sign-out (DA-24), 401 on any call, password reset done (DA-03). Exits: DA-03 (« Mot de passe oublié ? »), change-password sheet, data-preparation sheet, then DA-04 or DA-05.

Layout.

  1. Header: delegate icon, « Délégué Commercial », sub-title « Connexion à votre compte ».
  2. Text field « Téléphone » with a phone icon, numeric keyboard, formatted as typed (0555 12 34 56), prefilled with the last phone used on this device.
  3. Password field « Mot de passe » with a lock icon and a show/hide eye.
  4. Link « Mot de passe oublié ? » aligned to the end.
  5. Primary button « Se connecter » (full width, 56 dp).
  6. Footer: « Un problème pour vous connecter ? Contactez votre responsable. » and the version line.

The reference's « Se souvenir de moi » checkbox is removed: mobile sessions last 30 days and the phone is prefilled. Its « ou » separator and profile preview are DA-24.

Data.

FieldAPI sourceOffline sourceFreshness
Phone (input)Sent as identifier to auth.loginLast phone in secure storage (never the password)Written after each successful sign-in
Sessionauth.login → token, expiresAt, sessionId, user (Me)Secure storage; profile—
Delegate contextme.get → delegate.code, delegate.zones; delegate.profileprofileAt sign-in
Initial datadelegate.tours.today, sync.delta (full pull), reference.lists, reference.wilayas, reference.communes for the zone's wilayastours, visits, resellers, tasks, reference_items, visit_types, wilayas, communesAt sign-in, then per freshness class

Actions.

  • Se connecter → auth.login with { identifier, password, app: "DELEGATE", deviceName } and the mobile headers (X-App-Platform, X-App-Version, X-Device-Id). No Idempotency-Key (ignored by this operation); the button is disabled while the call runs so a double tap sends one request. On success: token to secure storage, Me to profile, then:
    • user.mustChangePassword → change-password sheet (current temporary password, new password twice) → auth.password.change.
    • Local data belonging to another delegate on this phone: if their outbox is empty it is wiped; otherwise the sheet « Des données d'un autre délégué ne sont pas synchronisées sur ce téléphone » offers « Annuler » or « Supprimer ces données » (destructive, second confirmation).
    • Data-preparation sheet « Préparation de vos données… » with four lines ticked as they finish (Profil, Tournée du jour, Points de vente (412), Listes et communes); after 10 s the button « Continuer » lets the rest finish in the background.
    • Then DA-04 when consent is missing, else DA-05. Haptic mediumImpact on success, heavyImpact on an error.

Rules.

  • Client validation before the call: phone normalisable to an Algerian mobile E.164 number (05, 06, 07 followed by 8 digits, or +213 form) — « Numéro invalide »; password not empty.
  • Only UserType = DELEGATE may sign in to this app (403 WRONG_APP).
  • Throttling (server): 5 attempts per minute per phone and IP, 15-minute lock after 5 consecutive failures (429 TOO_MANY_ATTEMPTS with details.lockedUntil).
  • ACCOUNT_SUSPENDED is revealed only after a correct password (server rule), so the explicit message never helps an attacker.
  • A new sign-in from the same device replaces the previous session of that device.
  • The password is never stored on the phone; no biometric unlock in V1.

States.

StateTriggerUI
Idle—Phone prefilled, focus on the password
SubmittingTapSpinner in the button, fields disabled
Wrong credentials401 INVALID_CREDENTIALSInline error under the password: « Numéro ou mot de passe incorrect. »
Locked429 TOO_MANY_ATTEMPTSBanner « Trop de tentatives. Réessayez à 10:42. », button disabled until lockedUntil
Suspended / disabled403 ACCOUNT_SUSPENDEDFull-width error card « Votre compte est suspendu. Contactez votre responsable. » (or « désactivé »), no session created
Wrong app403 WRONG_APP« Ce compte n'est pas un compte délégué. Utilisez l'application HOCO Revendeur. »
OfflineNo network« Pas de connexion Internet. La première connexion nécessite le réseau. »
Outdated app426 UPGRADE_REQUIREDDA-28 forced update

Copy. « Délégué Commercial » · « Connexion à votre compte » · « Téléphone » · « Mot de passe » · « Mot de passe oublié ? » · « Se connecter » · « Numéro invalide » · « Préparation de vos données… » · « Continuer » · « Choisissez un nouveau mot de passe pour continuer. »

Acceptance criteria.

  1. An active delegate signs in and reaches DA-04 (first time) or DA-05; the session survives an app restart.
  2. A suspended delegate with the right password sees the explicit suspension message and no screen behind it (cahier §6).
  3. After 5 wrong passwords the button stays disabled until the lock time shown.
  4. A reseller's credentials are refused with the wrong-app message.
  5. After sign-in and data preparation, switching to airplane mode still shows today's tour and the sheets of its points of sale.
  6. A temporary password forces the change-password sheet before any other screen.

DA-03 — Forgot password

FieldValue
French title« Mot de passe oublié »
ReleaseV1
Design dayDay 11 — DES-11
Build taskMOB-06 — Authentication flows (both apps)
RequirementsAUTH-07
Reference screenNone — standard reset flow (cahier §10)

Purpose. Let a delegate set a new password with a one-time SMS code, without contacting the back-office (AUTH-07).

Entry points & exits. Entry: « Mot de passe oublié ? » on DA-02. Exits: back to DA-02 (phone prefilled, snackbar « Mot de passe modifié. Connectez-vous. »), or back without change.

Layout. One screen, three steps with a progress indicator:

  1. « Votre numéro » — phone field, primary « Recevoir un code ».
  2. « Code reçu par SMS » — 6 boxes with SMS autofill (Android SMS User Consent API), countdown « Renvoyer le code dans 0:42 », link « Renvoyer le code » when allowed, link « Modifier le numéro ».
  3. « Nouveau mot de passe » — new password and confirmation with show/hide, live checklist of the policy (8 characters minimum, a letter, a digit), primary « Enregistrer ».

Data.

FieldAPI sourceOffline sourceFreshness
Code sendingauth.otp.send { phone, purpose: "PASSWORD_RESET" } → expiresAt, resendAvailableAt—Online only
Code checkauth.otp.verify → verificationToken (single use, 10 min)Memory only—
New passwordauth.password.reset { verificationToken, newPassword }——
OTP length and resend delayappConfig.get → settings.auth.otpLength, settings.auth.otpResendSecondsapp_configReference

Actions. Recevoir un code → auth.otp.send; the next step always opens with the same neutral text, whether or not the phone has an account. Valider (automatic when the 6th digit is typed) → auth.otp.verify. Enregistrer → auth.password.reset; on success other sessions are revoked by the server, the app returns to DA-02. No Idempotency-Key needed (the token is single-use).

Rules. Code: 6 digits, valid 5 minutes, 5 attempts, one active code per phone and purpose; sending limited to 1 per 60 s and 5 per hour per phone. Password policy from the authentication page (PASSWORD_TOO_WEAK). The flow never reveals whether a phone has an account.

States.

StateTriggerUI
Code sentAny answer to send« Si ce numéro correspond à un compte, un code vous a été envoyé par SMS. »
Too soon429 OTP_RESEND_TOO_SOONCountdown from details.retryAfterSeconds
Wrong code422 OTP_INVALID« Code incorrect. Il reste 3 essais. »
Expired code422 OTP_EXPIRED« Ce code a expiré. Demandez-en un nouveau. »
Too many attempts429 TOO_MANY_ATTEMPTS« Trop d'essais. Demandez un nouveau code. »
Weak password400 VALIDATION_FAILED / PASSWORD_TOO_WEAKInline under the field
Token expired422 VERIFICATION_TOKEN_INVALIDBack to step 1 with « La vérification a expiré. Recommencez. »
OfflineNo network« Connexion requise pour réinitialiser votre mot de passe. »

Copy. « Mot de passe oublié » · « Recevoir un code » · « Code reçu par SMS » · « Renvoyer le code » · « Modifier le numéro » · « Nouveau mot de passe » · « Confirmer le mot de passe » · « Enregistrer » · « Mot de passe modifié. Connectez-vous. »

Acceptance criteria.

  1. An unknown phone and a known phone show exactly the same confirmation text.
  2. « Renvoyer le code » stays disabled for 60 s after each sending.
  3. The fifth wrong code forces a new code request.
  4. After a reset the delegate signs in with the new password, and the session on their other device is revoked.

DA-04 — Permissions & location consent

FieldValue
French title« Autorisations et consentement »
ReleaseV1
Design dayDay 11 — DES-11
Build taskMOB-10 — Location, maps & consent foundation
RequirementsSEC-07 · FLD-15
Reference screenNone — required by cahier §8 and §10 (consent and transparency of geolocation)

Purpose. Before any position is read, explain what the app records about the delegate's location, collect the consent with its version, then obtain the Android permissions the field day needs (SEC-07, FLD-15).

Entry points & exits. Entry: after the first sign-in; when the notice version changes; when a location action (tour start, check-in, GPS capture) finds consent or permission missing; « Revoir le consentement » in DA-26. Exits: DA-05 (or back to the action that triggered it).

Layout. A gate of three to four steps with progress dots, each with an illustration, a short text and one primary button:

  1. Notifications — « Recevez vos tournées, relances et messages de votre responsable. » · « Autoriser » (Android 13+ notification permission) · « Plus tard ».
  2. Localisation et transparence — the notice, values filled from the settings:
    • « Ce que HOCO enregistre : votre position (latitude, longitude, précision) au démarrage de la tournée, à chaque arrivée chez un point de vente, à la création d'une fiche, et toutes les 5 minutes ou tous les 200 m pendant une tournée en cours. »
    • « Jamais en dehors d'une tournée en cours. Le partage s'arrête quand vous suspendez ou clôturez la tournée. »
    • « Qui la voit : votre responsable et l'administration HOCO. Conservation : 90 jours. »
    • Link « Politique de confidentialité »; checkbox « J'ai lu et j'accepte »; primary « Accepter et continuer »; secondary « Plus tard ».
  3. Autoriser la localisation — instructions « Choisissez « Pendant l'utilisation de l'appli » et « Position exacte ». » then the system prompt.
  4. Fiabilité pendant la tournée (only on manufacturers known to stop background services) — how to exclude the app from battery optimisation, « Ouvrir les réglages » / « Passer ».

The camera permission is asked later, at the first photo.

Data.

FieldAPI sourceOffline sourceFreshness
Frequency, distance, retention shown in the noticeappConfig.get → settings.field.locationIntervalSeconds, settings.field.locationDistanceMeters, settings.field.locationRetentionDaysapp_configReference
Privacy policy linkappConfig.get → settings.legal.privacyUrl (assumption — today sent to the reseller apps only; to be sent to the delegate app too)app_configReference
Consent already givenme.get → consents.locationTrackingVersion, consents.locationTrackingAcceptedAtprofileAt sign-in
Consent recordingme.update { consents: { locationTracking: { accepted: true, version } } }outboxQueued
OS permissionsAndroid (notifications, fine location, battery optimisation state)—Read on every resume

Actions.

  • Accepter et continuer → local profile updated and an outbox item for me.update (Idempotency-Key = item id, occurredAt = time of the tap; works offline). Then step 3.
  • Autoriser (localisation) → system prompt. Granted precise → ✓. Approximate only (Android 12+) → « La position exacte est nécessaire pour valider vos arrivées. » with « Réessayer » / « Ouvrir les réglages ». Denied → explanation and « Continuer sans localisation ». Permanently denied → « Ouvrir les réglages ».
  • Location services off → the Google Play services dialog « Activer la localisation ».
  • Plus tard on the notice → limited mode (rules below).

Rules.

  • No position is read before the notice is accepted (cahier §10).
  • Consent is per user and versioned; OS permissions are per device: on a new phone only steps 1, 3 and 4 are shown.
  • Without consent the delegate can sign in and consult everything, but cannot start a tour (consent is the precondition of sampling, see me.update rule 4). Home shows the banner « Localisation non acceptée : vous ne pouvez pas démarrer de tournée » with « Revoir ».
  • Consent given but OS permission missing: the tour can start without a position (recorded startPositionMissing, cahier §6.1 « selon les permissions GPS »), but check-ins and the GPS capture of a prospect need the permission; the banner says « Localisation désactivée : vos arrivées ne pourront pas être validées » with « Activer ».
  • Withdrawing consent in DA-26 sends accepted: false and stops sampling immediately.

States.

StateUI
Permission granted preciseStep marked ✓, next step
Approximate onlyExplanation card, « Ouvrir les réglages »
Denied / permanently deniedExplanation, « Continuer sans localisation » or « Ouvrir les réglages »
Location service off« Activez la localisation du téléphone » with the system dialog
OfflineConsent queued; the step completes normally

Copy. « Localisation et transparence » · « J'ai lu et j'accepte » · « Accepter et continuer » · « Plus tard » · « Autoriser la localisation » · « Position exacte » · « Ouvrir les réglages » · « Continuer sans localisation » · « Fiabilité pendant la tournée ».

Acceptance criteria.

  1. On a first sign-in the gate appears before Home and no location request reaches Android before « Accepter et continuer ».
  2. The consent reaches the server with its version and time, also when given offline (visible on the user's record in the back-office once synced).
  3. Refusing consent opens Home in limited mode with the banner, and « Commencer la tournée » leads back to the notice.
  4. Granting only approximate location shows the precise-location explanation.
  5. The values in the notice change when the back-office changes the sampling settings, without an app update.

On this page