CognxSafeTrack commited on
Commit ·
f1a06cd
1
Parent(s): c286b2d
docs: add audit_production.md + plan_implementation_prod.md; fix admin phone field
Browse files- apps/admin/src/App.tsx +2 -2
- docs/Xamle_System_Spec_Deployment_Sheet.md +237 -0
- docs/architecture_pedagogique_whatsapp.md +208 -0
- docs/audit_production.md +123 -0
- docs/flow utilisateurs et formation .md +641 -0
- docs/plan technique solution .md +592 -0
- docs/plan_implementation_prod.md +97 -0
apps/admin/src/App.tsx
CHANGED
|
@@ -161,7 +161,7 @@ function Dashboard() {
|
|
| 161 |
const headers = ['ID', 'User Phone', 'Track Title', 'Status', 'Current Day', 'Started At'];
|
| 162 |
const rows = data.enrollments.map(env => [
|
| 163 |
env.id,
|
| 164 |
-
env.user?.
|
| 165 |
env.track?.title || 'Unknown',
|
| 166 |
env.status,
|
| 167 |
env.currentDay,
|
|
@@ -234,7 +234,7 @@ function Dashboard() {
|
|
| 234 |
<tbody>
|
| 235 |
{data.enrollments.map((env: any) => (
|
| 236 |
<tr key={env.id} className="border-b border-slate-50 hover:bg-slate-50/50">
|
| 237 |
-
<td className="px-6 py-4 font-medium text-slate-900">{env.user?.
|
| 238 |
<td className="px-6 py-4">{env.track?.title || 'Unknown Track'}</td>
|
| 239 |
<td className="px-6 py-4">
|
| 240 |
<span className={`px-2.5 py-1 py-0.5 rounded-full text-xs font-medium
|
|
|
|
| 161 |
const headers = ['ID', 'User Phone', 'Track Title', 'Status', 'Current Day', 'Started At'];
|
| 162 |
const rows = data.enrollments.map(env => [
|
| 163 |
env.id,
|
| 164 |
+
env.user?.phone || 'Unknown',
|
| 165 |
env.track?.title || 'Unknown',
|
| 166 |
env.status,
|
| 167 |
env.currentDay,
|
|
|
|
| 234 |
<tbody>
|
| 235 |
{data.enrollments.map((env: any) => (
|
| 236 |
<tr key={env.id} className="border-b border-slate-50 hover:bg-slate-50/50">
|
| 237 |
+
<td className="px-6 py-4 font-medium text-slate-900">{env.user?.phone || 'Unknown'}</td>
|
| 238 |
<td className="px-6 py-4">{env.track?.title || 'Unknown Track'}</td>
|
| 239 |
<td className="px-6 py-4">
|
| 240 |
<span className={`px-2.5 py-1 py-0.5 rounded-full text-xs font-medium
|
docs/Xamle_System_Spec_Deployment_Sheet.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# XAMLÉ – System Spec & Deployment Sheet (HF + Railway Worker)
|
| 2 |
+
|
| 3 |
+
Ce document centralise toutes les informations techniques, de configuration et d'architecture de l'infrastructure XAMLÉ (SafeTrack EdTech). Il sert de référence absolue (Single Source of Truth) pour déployer et maintenir le backend scindé entre Hugging Face (API) et Railway (Worker).
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1. Résumé Architecture (Actuelle / Cible)
|
| 8 |
+
|
| 9 |
+
Le système est construit sur un monorepo Turborepo (pnpm) composé de multiples applications qui se parlent via une base de données et un cache central.
|
| 10 |
+
|
| 11 |
+
* **Hugging Face Space (Public Inbound Endpoint) :**
|
| 12 |
+
* Héberge l'application `apps/api`.
|
| 13 |
+
* Rôle : Point d'entrée public pour le Webhook Meta (WhatsApp Inbound), les requêtes Stripe, et le back-office Admin.
|
| 14 |
+
* C'est ici que l'IA (OpenAI) génère le contenu et que l'utilisateur est enregistré en base. Aucune requête sortante vers Meta n'est émise d'ici à cause des restrictions DNS (ENOTFOUND).
|
| 15 |
+
* **Railway (Outbound Background Worker) :**
|
| 16 |
+
* Héberge **uniquement** l'application `apps/whatsapp-worker`.
|
| 17 |
+
* Rôle : Consommer les jobs BullMQ enregistrés dans Redis par Hugging Face, et envoyer physiquement les messages sortants vers l'API WhatsApp Cloud (graph.facebook.com). Contient le Cron (Scheduler) quotidien.
|
| 18 |
+
* **Neon Postgres :**
|
| 19 |
+
* Rôle : Base de données relationnelle centrale (Prisma) partagée entre HF et Railway (Tables `User`, `Track`, `Enrollment`, `TrackDay`).
|
| 20 |
+
* **Upstash Redis :**
|
| 21 |
+
* Rôle : Gestion de la file d'attente asynchrone BullMQ (queue `whatsappQueue`). Il agit comme le pont de communication en temps réel entre Hugging Face et Railway.
|
| 22 |
+
* **Cloudflare R2 :**
|
| 23 |
+
* Rôle : Stockage S3 public pour héberger les fichiers générés par l'IA (PDF, Audio, PPTX) afin que Meta puisse les télécharger via des URLs publiques et les envoyer aux utilisateurs WhatsApp.
|
| 24 |
+
* **OpenAI :**
|
| 25 |
+
* Rôle : LLM utilisé dans `apps/api` (sur Hugging Face) pour personnaliser les leçons et générer l'audio (Whisper STT / TTS).
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## 2. URLs & Endpoints
|
| 30 |
+
|
| 31 |
+
**URL de base Hugging Face :** `https://safetrack-edtech.hf.space`
|
| 32 |
+
|
| 33 |
+
### Webhook WhatsApp (Configuration Meta App Dashboard)
|
| 34 |
+
* **GET Verify (Public) :** `GET https://safetrack-edtech.hf.space/v1/whatsapp/webhook`
|
| 35 |
+
* Utilisé par Meta une seule fois lors de la configuration avec `<WHATSAPP_VERIFY_TOKEN>`.
|
| 36 |
+
* **POST Webhook (Public mais Sécurisé HMAC) :** `POST https://safetrack-edtech.hf.space/v1/whatsapp/webhook`
|
| 37 |
+
* L'endpoint où Meta poste les messages des utilisateurs.
|
| 38 |
+
* Sécurisé via validation de signature `X-Hub-Signature-256` en utilisant `<WHATSAPP_APP_SECRET>`.
|
| 39 |
+
|
| 40 |
+
### Utilitaires & Healthchecks (Publics)
|
| 41 |
+
* `GET /` : Healthcheck basique (`{"ok": true}`).
|
| 42 |
+
* `GET /health` : Monitoring avec timestamp.
|
| 43 |
+
* `GET /privacy` : Politique de confidentialité servie en HTML (requise par Meta App Review).
|
| 44 |
+
* `GET /debug/net` : Teste la connectivité Google (diagnostique Egress).
|
| 45 |
+
* `GET /debug/graph` : Teste explicitement si la résolution DNS `graph.facebook.com` fonctionne (Diagnostic HF DNS Block).
|
| 46 |
+
|
| 47 |
+
### Routes Privées (Protégées par `X-API-Key`)
|
| 48 |
+
Les routes suivantes exigent le header `Authorization: Bearer <ADMIN_API_KEY>` :
|
| 49 |
+
* `/v1/admin/*` : Endpoints pour le frontend Admin (Stats, Enrollments).
|
| 50 |
+
* `/v1/ai/*` : Génération à la demande de PDF/Audio.
|
| 51 |
+
* `/v1/payments/*` : Création de sessions Stripe.
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
## 3. Variables & Secrets (Environment)
|
| 56 |
+
|
| 57 |
+
| Name | Env | Type | Example / Placeholder | Used by | Notes |
|
| 58 |
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
| 59 |
+
| `WHATSAPP_VERIFY_TOKEN` | HF | Secret | `my_secure_verify_token_123` | `apps/api` | Vérifie le webhook initial de Meta. |
|
| 60 |
+
| `WHATSAPP_APP_SECRET` | HF | Secret | `c2a5...` (32 chars) | `apps/api` | Clé secrète de l'App Meta pour signer (`crypto.createHmac`) les payloads. |
|
| 61 |
+
| `WHATSAPP_ACCESS_TOKEN` | **Railway** & HF | Secret | `EAA...` | `whatsapp-worker` & `api` | Le jeton d'accès permanent System User. Worker (envoi message) / API (téléchargement audio STT). |
|
| 62 |
+
| `WHATSAPP_PHONE_NUMBER_ID` | **Railway** | Public | `5249583...` | `whatsapp-worker` | L'ID Meta du numéro expéditeur. |
|
| 63 |
+
| `DATABASE_URL` | Both | Secret | `postgresql://user:pass@ep-neon.tech/db?sslmode=require` | `api` & `worker` | DSN Neon (Pooler ou direct). |
|
| 64 |
+
| `REDIS_URL` | Both | Secret | `rediss://default:pass@upstash.io:6379` | `api` & `worker` | **Important:** Si Upstash TLS, utiliser `rediss://` (avec 2 's') et non `redis://`. |
|
| 65 |
+
| `ADMIN_API_KEY` | HF | Secret | `sk_admin_12345` | `apps/api` | Sécurise le dashboard et les routes privées. |
|
| 66 |
+
| `OPENAI_API_KEY` | HF | Secret | `sk-proj...` | `apps/api` | Provider IA. |
|
| 67 |
+
| `R2_ACCOUNT_ID` | HF | Public | `a1b2c3d4...` | `apps/api` | Cloudflare Storage. |
|
| 68 |
+
| `R2_BUCKET` | HF | Public | `xamle-edtech-assets` | `apps/api` | Nom du seau Cloudflare. |
|
| 69 |
+
| `R2_PUBLIC_URL` | Both | Public | `https://assets.xamle.com` | `api` & `worker` | URL de diffusion pour que l'App Meta puisse télécharger l'asset. |
|
| 70 |
+
| `R2_ACCESS_KEY_ID` | HF | Secret | `...` | `apps/api` | AWS S3 Compat layer credential. |
|
| 71 |
+
| `R2_SECRET_ACCESS_KEY` | HF | Secret | `...` | `apps/api` | AWS S3 Compat layer credential. |
|
| 72 |
+
| `STRIPE_SECRET_KEY` | HF | Secret | `sk_test_...` | `apps/api` | Paiements et abonnements Premium. |
|
| 73 |
+
| `STRIPE_WEBHOOK_SECRET` | HF | Secret | `whsec_...` | `apps/api` | Signature Webhook Stripe. |
|
| 74 |
+
|
| 75 |
+
---
|
| 76 |
+
|
| 77 |
+
## 4. BullMQ / Worker Logic
|
| 78 |
+
|
| 79 |
+
La file d'attente garantit la résilience.
|
| 80 |
+
* **Nom de la Queue :** `whatsappQueue`
|
| 81 |
+
* **Producer (Hugging Face) :**
|
| 82 |
+
Quand un webhook Meta arrive, l'API appelle `scheduleMessage(userId, text)` dans `apps/api/src/services/queue.ts`.
|
| 83 |
+
* **Consumer (Railway) :**
|
| 84 |
+
Le Worker écoute la queue dans `apps/whatsapp-worker/src/index.ts`. Le handler de la file est instancié avec `new Worker('whatsappQueue', async (job) => { ... })`.
|
| 85 |
+
* **Job Types (Payload JSON) :**
|
| 86 |
+
* Type `send-message` : `{ "userId": "uuid-1234", "text": "Bienvenue !" }`
|
| 87 |
+
* Type `send-content` : `{ "userId": "uuid-123", "trackDayId": "day-123" }` (déclenche génération IA et envois médias).
|
| 88 |
+
* **Logs Attendus sur Railway :**
|
| 89 |
+
`Worker: Processing job: send-message 123`
|
| 90 |
+
`[WhatsApp] ✅ Text message sent to 22177...`
|
| 91 |
+
|
| 92 |
+
---
|
| 93 |
+
|
| 94 |
+
## 5. WhatsApp Cloud API
|
| 95 |
+
|
| 96 |
+
Toutes les requêtes sortantes vers Meta sont centralisées dans `apps/whatsapp-worker/src/whatsapp-cloud.ts`.
|
| 97 |
+
|
| 98 |
+
* **URL Exacte :** `https://graph.facebook.com/v18.0/<WHATSAPP_PHONE_NUMBER_ID>/messages` (Forcée via Axios avec un agent HTTP configuré explicitement sur l'IPv4 `family: 4` pour anticiper tout problème de configuration DNS Docker).
|
| 99 |
+
* **Headers :**
|
| 100 |
+
```json
|
| 101 |
+
{
|
| 102 |
+
"Content-Type": "application/json",
|
| 103 |
+
"Authorization": "Bearer <WHATSAPP_ACCESS_TOKEN>"
|
| 104 |
+
}
|
| 105 |
+
```
|
| 106 |
+
* **Body Type "Text" :**
|
| 107 |
+
```json
|
| 108 |
+
{
|
| 109 |
+
"messaging_product": "whatsapp",
|
| 110 |
+
"recipient_type": "individual",
|
| 111 |
+
"to": "221781476249",
|
| 112 |
+
"type": "text",
|
| 113 |
+
"text": { "preview_url": false, "body": "Hello World" }
|
| 114 |
+
}
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
## 6. Sécurité
|
| 120 |
+
|
| 121 |
+
1. **Validations Webhook HMAC**
|
| 122 |
+
La vérification cryptographique est active dans `apps/api/src/routes/whatsapp.ts`.
|
| 123 |
+
```typescript
|
| 124 |
+
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
|
| 125 |
+
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
|
| 126 |
+
```
|
| 127 |
+
2. **Rate Limiting Global (Hugging Face / Fastify)**
|
| 128 |
+
Configuré dans `index.ts` avec `@fastify/rate-limit`. Le seuil actuel est de `300 requêtes / minute` par adresse IP (qui protégera l'API si le Webhook fuite mais n'impactera pas l'IP de Meta qui est distribuée).
|
| 129 |
+
3. **API Key (Admin routes)**
|
| 130 |
+
Vérification stricte de `Bearer <ADMIN_API_KEY>` sur chaque route admin/backoffice.
|
| 131 |
+
4. **CORS**
|
| 132 |
+
CORS est ouvert globalement via `@fastify/cors` pour permettre au dashboard frontal d'intéragir depuis n'importe où.
|
| 133 |
+
|
| 134 |
+
---
|
| 135 |
+
|
| 136 |
+
## 7. Repo & Build
|
| 137 |
+
|
| 138 |
+
Le projet est un monorepo géré avec **pnpm** et ses workspaces.
|
| 139 |
+
|
| 140 |
+
* `apps/api/` : Application Fastify (Node).
|
| 141 |
+
* `apps/whatsapp-worker/` : Processus BullMQ natif (Node).
|
| 142 |
+
* `packages/database/` : Couche Prisma partagée avec le schéma `schema.prisma`.
|
| 143 |
+
|
| 144 |
+
**Commandes critiques (Railway Build & Start) :**
|
| 145 |
+
* Root command (Installation) : `pnpm install`
|
| 146 |
+
* Génération client : `pnpm --filter @repo/database build` (ou `prisma generate`).
|
| 147 |
+
* Build du Worker : `pnpm --filter whatsapp-worker build`
|
| 148 |
+
* Start du Worker (Prod) : `pnpm --filter whatsapp-worker start`
|
| 149 |
+
|
| 150 |
+
_Note : Le script start de whatsapp-worker utilise actuellement `tsx src/index.ts` en développement. Pour la prod (Railway), il est conseillé de compiler avec `tsc` et lancer `node dist/index.js`._
|
| 151 |
+
|
| 152 |
+
---
|
| 153 |
+
|
| 154 |
+
## 8. Plan de Déploiement "Railway Worker Only"
|
| 155 |
+
|
| 156 |
+
Pour déployer le Worker sur Railway, nous voulons ignorer totalement l'API et le Frontend. La configuration Railway cible le dossier du worker :
|
| 157 |
+
|
| 158 |
+
1. **Connect GitHub** : Ajouter le repository Edtech à Railway.
|
| 159 |
+
2. **Configuration du Service (Settings)** :
|
| 160 |
+
* **Root Directory :** Laisser à `/` (car c'est un monorepo pnpm, Railway comprendra les workspaces).
|
| 161 |
+
* **Build Command :** `pnpm install && pnpm --filter @repo/database build && pnpm --filter whatsapp-worker build`
|
| 162 |
+
* **Start Command :** `pnpm --filter whatsapp-worker start`
|
| 163 |
+
3. **Injector les Variables (Variables tab)** :
|
| 164 |
+
* Coller en Block Import les variables requises signalées en "Railway" ou "Both" dans la section [3].
|
| 165 |
+
* Spécialement `DATABASE_URL`, `REDIS_URL`, `WHATSAPP_PHONE_NUMBER_ID`, et `WHATSAPP_ACCESS_TOKEN`.
|
| 166 |
+
4. **Checklist de validation finale :**
|
| 167 |
+
* Envoyer `INSCRIPTION` au numéro WhatsApp.
|
| 168 |
+
* Vérifier les logs sur le container de Hugging Face => on y voit la réception `[WEBHOOK] Received webhook event...`.
|
| 169 |
+
* Ouvrir les logs de Railway => on y voit le worker prendre la relève `Processing job: send-message`.
|
| 170 |
+
* Le téléphone reçoit le message instantanément.
|
| 171 |
+
|
| 172 |
+
---
|
| 173 |
+
|
| 174 |
+
## 9. Extraits de code de référence
|
| 175 |
+
|
| 176 |
+
### A. Producer (Hugging Face) -> `services/queue.ts`
|
| 177 |
+
```typescript
|
| 178 |
+
import { Queue } from 'bullmq';
|
| 179 |
+
import Redis from 'ioredis';
|
| 180 |
+
|
| 181 |
+
const redisConnection = new Redis(process.env.REDIS_URL!);
|
| 182 |
+
export const whatsappQueue = new Queue('whatsappQueue', { connection: redisConnection });
|
| 183 |
+
|
| 184 |
+
export async function scheduleMessage(userId: string, text: string, delayMs: number = 0) {
|
| 185 |
+
await whatsappQueue.add('send-message', { userId, text }, { delay: delayMs });
|
| 186 |
+
}
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
### B. Consumer (Railway Worker) -> `whatsapp-worker/src/index.ts`
|
| 190 |
+
```typescript
|
| 191 |
+
import { Worker, Job } from 'bullmq';
|
| 192 |
+
import { sendTextMessage } from './whatsapp-cloud';
|
| 193 |
+
|
| 194 |
+
const worker = new Worker('whatsappQueue', async (job: Job) => {
|
| 195 |
+
if (job.name === 'send-message') {
|
| 196 |
+
const { userId, text } = job.data;
|
| 197 |
+
const user = await prisma.user.findUnique({ where: { id: userId } });
|
| 198 |
+
if (user?.phone) {
|
| 199 |
+
await sendTextMessage(user.phone, text); // Appelle Graph API
|
| 200 |
+
}
|
| 201 |
+
}
|
| 202 |
+
}, { connection: redisConnection });
|
| 203 |
+
```
|
| 204 |
+
|
| 205 |
+
### C. Bypass du blocage DNS (whatsapp-cloud.ts)
|
| 206 |
+
```typescript
|
| 207 |
+
import axios from 'axios';
|
| 208 |
+
import https from 'https';
|
| 209 |
+
import dns from 'node:dns';
|
| 210 |
+
|
| 211 |
+
// Fix global ipv4first
|
| 212 |
+
dns.setDefaultResultOrder('ipv4first');
|
| 213 |
+
|
| 214 |
+
// Agent de secours
|
| 215 |
+
const httpsAgent = new https.Agent({ family: 4 });
|
| 216 |
+
|
| 217 |
+
export async function sendTextMessage(to: string, text: string): Promise<void> {
|
| 218 |
+
const url = `https://graph.facebook.com/v18.0/${process.env.WHATSAPP_PHONE_NUMBER_ID}/messages`;
|
| 219 |
+
await axios.post(url, body, {
|
| 220 |
+
headers: { Authorization: `Bearer ${process.env.WHATSAPP_ACCESS_TOKEN}` },
|
| 221 |
+
httpsAgent // Force le DNS à utiliser une route supportée
|
| 222 |
+
});
|
| 223 |
+
}
|
| 224 |
+
```
|
| 225 |
+
|
| 226 |
+
### D. Endpoints de Diagnostique DNS & Egress Hugging Face (`api/src/index.ts`)
|
| 227 |
+
```typescript
|
| 228 |
+
server.get('/debug/net', async (_req, reply) => {
|
| 229 |
+
const res = await fetch('https://www.google.com', { method: 'GET' });
|
| 230 |
+
return reply.send({ ok: true, status: res.status }); // ✅ SUCCESS HF
|
| 231 |
+
});
|
| 232 |
+
|
| 233 |
+
server.get('/debug/graph', async (_req, reply) => {
|
| 234 |
+
const res = await fetch('https://graph.facebook.com/v18.0', { method: 'GET' });
|
| 235 |
+
return reply.send({ ok: true, status: res.status }); // ❌ ENOTFOUND HF
|
| 236 |
+
});
|
| 237 |
+
```
|
docs/architecture_pedagogique_whatsapp.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Audit & Fonctionnement — Architecture Pédagogique Interactive (WhatsApp & Voix)
|
| 2 |
+
|
| 3 |
+
Ce document est destiné à l'équipe technique et aux experts pédagogiques. Il détaille la transition de Xamlé d'un bot textuel "statique" vers **un moteur pédagogique interactif** centré sur la voix (`audioUrl` / TTS), les interactions rapides (`buttonsJson`), et le retour intelligent par l'IA (`UserProgress` / OpenAI).
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1. Modèle de Données (Prisma)
|
| 8 |
+
|
| 9 |
+
Pour soutenir des leçons interactives qui ne se valident que sous l'action de l'étudiant, deux changements majeurs ont été effectués dans `packages/database/prisma/schema.prisma`.
|
| 10 |
+
|
| 11 |
+
### A. Le Contenu de la Leçon (`TrackDay`)
|
| 12 |
+
L'ancien format texte brut a été enrichi pour supporter les audios et les interactions (boutons, vocaux).
|
| 13 |
+
|
| 14 |
+
```prisma
|
| 15 |
+
model TrackDay {
|
| 16 |
+
id String @id @default(uuid())
|
| 17 |
+
trackId String
|
| 18 |
+
dayNumber Int
|
| 19 |
+
title String?
|
| 20 |
+
audioUrl String? // URL de l'audio pré-enregistré (sur R2) ou généré par TTS
|
| 21 |
+
lessonText String? // Texte de remplacement de l'audio
|
| 22 |
+
exerciseType ExerciseType @default(TEXT) // Type (TEXT, AUDIO, BUTTON)
|
| 23 |
+
exercisePrompt String? // Question de validation à envoyer en texte ou en bouton
|
| 24 |
+
validationKeyword String? // (Option) Si la validation nécessite un mot précis
|
| 25 |
+
buttonsJson Json? // Structure du bouton if ExerciseType == BUTTON
|
| 26 |
+
unlockCondition String? // Logique pour débloquer le jour suivant
|
| 27 |
+
createdAt DateTime @default(now())
|
| 28 |
+
updatedAt DateTime @updatedAt
|
| 29 |
+
|
| 30 |
+
track Track @relation(fields: [trackId], references: [id])
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
enum ExerciseType {
|
| 34 |
+
TEXT
|
| 35 |
+
AUDIO
|
| 36 |
+
BUTTON
|
| 37 |
+
}
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
### B. Le Suivi de Mémorisation et Statut (`UserProgress`)
|
| 41 |
+
Au lieu de passer l'enrollement au "jour suivant" immédiatement, nous utilisons désormais un modèle précis pour l'exercice en cours.
|
| 42 |
+
|
| 43 |
+
```prisma
|
| 44 |
+
model UserProgress {
|
| 45 |
+
id String @id @default(uuid())
|
| 46 |
+
userId String
|
| 47 |
+
trackId String
|
| 48 |
+
currentDay Int @default(1)
|
| 49 |
+
score Int @default(0) // Permet la gamification
|
| 50 |
+
lastInteraction DateTime @default(now())
|
| 51 |
+
exerciseStatus ExerciseStatus @default(PENDING) // PENDING -> l'étudiant doit répondre
|
| 52 |
+
createdAt DateTime @default(now())
|
| 53 |
+
updatedAt DateTime @updatedAt
|
| 54 |
+
|
| 55 |
+
user User @relation(fields: [userId], references: [id])
|
| 56 |
+
track Track @relation(fields: [trackId], references: [id])
|
| 57 |
+
|
| 58 |
+
@@unique([userId, trackId])
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
enum ExerciseStatus {
|
| 62 |
+
PENDING
|
| 63 |
+
COMPLETED
|
| 64 |
+
}
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
## 2. Le Moteur d'Envoi côté Worker (`pedagogy.ts`)
|
| 70 |
+
|
| 71 |
+
La logique d'envoi du contenu (déclenchée par l'inscription ou la validation d'un exercice) a été isolée dans `apps/whatsapp-worker/src/pedagogy.ts`.
|
| 72 |
+
|
| 73 |
+
La fonction principale, `sendLessonDay(...)`, structure l'expérience en trois temps :
|
| 74 |
+
1. Envoyer la leçon audio ou texte.
|
| 75 |
+
2. Envoyer la question interactive.
|
| 76 |
+
3. Bloquer l'utilisateur au statut `PENDING`.
|
| 77 |
+
|
| 78 |
+
```typescript
|
| 79 |
+
// Extrait de pedagogy.ts dans le worker Railway
|
| 80 |
+
export async function sendLessonDay(userId: string, trackId: string, dayNumber: number) {
|
| 81 |
+
// 1. Envoi Audio (Via R2 ou TTS par défaut)
|
| 82 |
+
if (finalAudioUrl) {
|
| 83 |
+
await sendAudioMessage(user.phone, finalAudioUrl);
|
| 84 |
+
} else if (lessonText) {
|
| 85 |
+
await sendTextMessage(user.phone, lessonText);
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
// 2. Envoi de l'exercice interactif
|
| 89 |
+
if (trackDay.exercisePrompt) {
|
| 90 |
+
if (trackDay.exerciseType === 'BUTTON' && trackDay.buttonsJson) {
|
| 91 |
+
// Appelle l'API Meta avec le format Interactive Button
|
| 92 |
+
const buttons = trackDay.buttonsJson as Array<{ id: string; title: string }>;
|
| 93 |
+
await sendInteractiveButtonMessage(user.phone, trackDay.exercisePrompt, buttons);
|
| 94 |
+
} else {
|
| 95 |
+
await sendTextMessage(user.phone, trackDay.exercisePrompt);
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
// 3. Mise en attente de la réponse de l'utilisateur
|
| 100 |
+
await prisma.userProgress.upsert({
|
| 101 |
+
where: { userId_trackId: { userId, trackId } },
|
| 102 |
+
update: { currentDay: dayNumber, exerciseStatus: 'PENDING', lastInteraction: new Date() },
|
| 103 |
+
create: { userId, trackId, currentDay: dayNumber, exerciseStatus: 'PENDING' }
|
| 104 |
+
});
|
| 105 |
+
}
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
---
|
| 109 |
+
|
| 110 |
+
## 3. Le Routage Webhook (`WhatsAppMessageSchema` et Interception)
|
| 111 |
+
|
| 112 |
+
L'API (Hugging Face) écoute les réponses. Si une réponse est reçue avec le statut `PENDING`, elle l'envoie au coach IA.
|
| 113 |
+
De plus, le typage Zod de l'événement entrant a été corrigé pour intercepter les boutons WhatsApp :
|
| 114 |
+
|
| 115 |
+
```typescript
|
| 116 |
+
// Extrait de apps/api/src/routes/whatsapp.ts
|
| 117 |
+
const WhatsAppMessageSchema = z.object({
|
| 118 |
+
from: z.string(),
|
| 119 |
+
type: z.enum(['text', 'audio', 'image', 'video', 'document', 'sticker', 'reaction', 'interactive']),
|
| 120 |
+
text: z.object({ body: z.string() }).optional(),
|
| 121 |
+
audio: z.object({ id: z.string(), mime_type: z.string().optional() }).optional(),
|
| 122 |
+
interactive: z.object({
|
| 123 |
+
type: z.enum(['button_reply', 'list_reply']),
|
| 124 |
+
button_reply: z.object({ id: z.string(), title: z.string() }).optional()
|
| 125 |
+
}).optional()
|
| 126 |
+
});
|
| 127 |
+
|
| 128 |
+
// Dans le parsing du payload, on convertit un clic bouton en "Text", ou un Audio via STT Whisper :
|
| 129 |
+
if (message.type === 'interactive' && message.interactive) {
|
| 130 |
+
if (message.interactive.type === 'button_reply' && message.interactive.button_reply) {
|
| 131 |
+
text = message.interactive.button_reply.id; // L'ID du bouton devient la réponse envoyée à l'IA
|
| 132 |
+
}
|
| 133 |
+
} else if (message.type === 'audio' && message.audio) {
|
| 134 |
+
// On télécharge l'audio de Meta et on utilise Whisper pour le transcrire
|
| 135 |
+
text = await aiService.transcribeAudio(buffer, 'message.ogg');
|
| 136 |
+
}
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
---
|
| 140 |
+
|
| 141 |
+
## 4. La Boucle de Correction IA (`whatsapp.ts` et `ai.ts`)
|
| 142 |
+
|
| 143 |
+
Lorsqu'une réponse (texte ou bouton) est interceptée par l'API alors qu'un exercice est "PENDING", elle sollicite `generateFeedback`.
|
| 144 |
+
|
| 145 |
+
```typescript
|
| 146 |
+
// Logique d'interception (app/api/src/services/whatsapp.ts)
|
| 147 |
+
const pendingProgress = await prisma.userProgress.findFirst({
|
| 148 |
+
where: { userId: user.id, exerciseStatus: 'PENDING' },
|
| 149 |
+
include: { track: true }
|
| 150 |
+
});
|
| 151 |
+
|
| 152 |
+
if (pendingProgress) {
|
| 153 |
+
await scheduleMessage(user.id, "⏳ Analyse de votre réponse...");
|
| 154 |
+
|
| 155 |
+
// Génération du feedback IA
|
| 156 |
+
const feedback = await aiService.generateFeedback(
|
| 157 |
+
text, // Ce que l'utilisateur a répondu ou cliqué
|
| 158 |
+
trackDay.exercisePrompt || '', // L'exercice attendu
|
| 159 |
+
trackDay.lessonText || '' // Le contexte de la leçon
|
| 160 |
+
);
|
| 161 |
+
|
| 162 |
+
await scheduleMessage(user.id, feedback);
|
| 163 |
+
|
| 164 |
+
// ✅ On débloque l'utilisateur
|
| 165 |
+
await prisma.userProgress.update({
|
| 166 |
+
where: { id: pendingProgress.id },
|
| 167 |
+
data: { exerciseStatus: 'COMPLETED', score: { increment: 1 } }
|
| 168 |
+
});
|
| 169 |
+
}
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
### Le Prompt Pédagogique (OpenAI GPT-4o-mini)
|
| 173 |
+
L'intelligence est formatée pour un coach bienveillant sénégalais du secteur informel (WhatsApp Native).
|
| 174 |
+
|
| 175 |
+
```typescript
|
| 176 |
+
// apps/api/src/services/ai/index.ts
|
| 177 |
+
const prompt = `
|
| 178 |
+
Tu es un coach business bienveillant pour des entrepreneurs du secteur informel au Sénégal.
|
| 179 |
+
L'étudiant vient d'écouter cette leçon : "${lessonContent}"
|
| 180 |
+
L'exercice attendu était : "${expectedExercise}"
|
| 181 |
+
Voici la réponse ou action de l'étudiant : "${userInput}"
|
| 182 |
+
|
| 183 |
+
Ta mission :
|
| 184 |
+
Analyse sa réponse logiquement. Valide-la avec beaucoup d'enthousiasme, et ajoute UN conseil très pratique, précis, ou une mini correction bienveillante.
|
| 185 |
+
Contrainte stricte : MAXIMUM 5 LIGNES.
|
| 186 |
+
Format WhatsApp : Utilise un langage très simple, direct, des mots courants, une structure aérée avec du *gras* pour les mots clés importants, et des emojis adaptés.
|
| 187 |
+
`;
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
---
|
| 191 |
+
|
| 192 |
+
## 5. Fonctionnalités Utiles Ajoutées pour le Déboguage
|
| 193 |
+
|
| 194 |
+
- **Le mot-clé `INSCRIPTION` (WhatsApp)** : Pour ne pas bloquer les utilisateurs en cours de test, le mot-clé supprime les données "ville" et "activité", et "enrollments" pour **REMETTRE L'ÉTUDIANT À ZÉRO** à la volée.
|
| 195 |
+
- **Le mot-clé `SUITE` (WhatsApp)** : Par défaut, la prochaine leçon (lorsque "COMPLETED" est atteint) n'arrive que le lendemain matin via le Scheduler de BullMQ. Si l'utilisateur envoie `SUITE`, nous forçons le passage immédiat au jour suivant sans attendre 24 heures.
|
| 196 |
+
|
| 197 |
+
---
|
| 198 |
+
|
| 199 |
+
## 6. Feedbacks Requis par l'Expert et Axes d'Amélioration (V2)
|
| 200 |
+
|
| 201 |
+
L'expert observant la codebase doit envisager ces améliorations :
|
| 202 |
+
|
| 203 |
+
1. **Fallback si Feedback "Négatif"** : Actuellement, le système génère un feedback bienveillant et passe `UserProgress` à `COMPLETED` même si l'étudiant dit une erreur ou envoie un contenu inapproprié. Il faudrait un "score de validation IA" (`true/false`) dans le schéma Zod de l'IA. Si `false`, on laisse le statut à `PENDING`.
|
| 204 |
+
2. **Audio Natifs R2** : Lors de la création d'un "TrackDay", le tableau de bord Admin (pas encore construit sur ce pan) doit pouvoir uploader de vrais MP3 professionnels vers R2, et enregistrer l'URL dans `TrackDay.audioUrl`. Sans quoi le système "fallback" sur la génération automatique (TTS) assez robotique.
|
| 205 |
+
3. **Limites des Boutons WhatsApp** :
|
| 206 |
+
- Meta impose un Maximum de 3 boutons interactifs par message. S'il y a 4 questions, il faudra switcher vers l'événement `List_reply` (Menu interactif WhatsApp).
|
| 207 |
+
- Les titres des boutons sont limités à 20 caractères.
|
| 208 |
+
4. **Vidéos Verticales** : Le schéma BDD ne gère pas encore de champ `videoUrl`. Quand le format Vlog entrera en lice, il suffira de rajouter ce champ dans `TrackDay` et la fonction `sendVideoMessage` au Wrapper API Cloud Meta.
|
docs/audit_production.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Audit Complet — État de la Codebase
|
| 2 |
+
*Date : 2026-02-22 — Commit base : `c286b2d`*
|
| 3 |
+
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
## 1. Architecture globale
|
| 7 |
+
|
| 8 |
+
```
|
| 9 |
+
apps/
|
| 10 |
+
api/ → Fastify REST API (Railway — port 3001)
|
| 11 |
+
whatsapp-worker/ → BullMQ Worker (Railway — même container)
|
| 12 |
+
admin/ → Dashboard React (Vite + Tailwind)
|
| 13 |
+
web/ → Landing page + portail étudiant (Vite + Tailwind)
|
| 14 |
+
|
| 15 |
+
packages/
|
| 16 |
+
database/ → Prisma schema (PostgreSQL) + migrations
|
| 17 |
+
shared-types/ → Types TypeScript partagés
|
| 18 |
+
tsconfig/ → Config TS commune
|
| 19 |
+
ui/ → (vide)
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## 2. Ce qui fonctionne ✅
|
| 25 |
+
|
| 26 |
+
| Composant | État |
|
| 27 |
+
|---|---|
|
| 28 |
+
| Webhook WhatsApp (GET verify + POST events) | ✅ Implémenté |
|
| 29 |
+
| Onboarding : INSCRIPTION → langue → secteur | ✅ |
|
| 30 |
+
| Menus interactifs LIST (boutons natifs) | ✅ Titre ≤ 24 chars corrigé |
|
| 31 |
+
| Download audio Meta Graph API (Railway) | ✅ |
|
| 32 |
+
| Transcription Whisper via `/v1/ai/transcribe` | ✅ |
|
| 33 |
+
| Feedback AI `generateFeedback()` + 429 fallback | ✅ |
|
| 34 |
+
| Timeout IA 10s (Promise.race) | ✅ |
|
| 35 |
+
| TTS leçon → audio R2 | ✅ |
|
| 36 |
+
| Personnalisation leçon par secteur | ✅ |
|
| 37 |
+
| SUITE / DAY_CONTINUE → jour suivant | ✅ Regex fixé |
|
| 38 |
+
| REPLAY → réécoute leçon | ✅ |
|
| 39 |
+
| EXERCISE → invite réponse | ✅ |
|
| 40 |
+
| Dock génération OnePager PDF + PitchDeck PPTX | ✅ |
|
| 41 |
+
| Paiements Stripe (checkout + webhook) | ✅ Implémenté |
|
| 42 |
+
| Admin dashboard (stats + enrollments + CSV export) | ✅ |
|
| 43 |
+
| Dockerfile combiné API + Worker (Railway) | ✅ |
|
| 44 |
+
| Scheduler cron leçons quotidiennes | ✅ (basique) |
|
| 45 |
+
| Stockage audio R2 via `/v1/ai/store-audio` | ✅ Ajouté |
|
| 46 |
+
|
| 47 |
+
---
|
| 48 |
+
|
| 49 |
+
## 3. Problèmes identifiés ❌
|
| 50 |
+
|
| 51 |
+
### 3.1 Critiques (bloquants en prod)
|
| 52 |
+
|
| 53 |
+
| # | Fichier | Problème | Solution |
|
| 54 |
+
|---|---|---|---|
|
| 55 |
+
| C1 | `Railway vars` | `WHATSAPP_APP_SECRET` non défini → HMAC skip (warning) | Ajouter la variable |
|
| 56 |
+
| C2 | `Railway vars` | `DISABLE_WHATSAPP_SEND=true` encore présent → aucun envoi | Supprimer la variable |
|
| 57 |
+
| C3 | Base de données | **Aucun contenu (Track/TrackDay) en production** → enrollement impossible | Seed ou admin content manager |
|
| 58 |
+
| C4 | `api/src/services/stripe.ts` | STRIPE_SECRET_KEY et STRIPE_WEBHOOK_SECRET non vérifiés au démarrage | Ajouter à Railway vars |
|
| 59 |
+
| C5 | Meta webhook | URL pas encore validée si container 502 | Corriger après Railway restart |
|
| 60 |
+
|
| 61 |
+
### 3.2 Importants (fonctionnalités incomplètes)
|
| 62 |
+
|
| 63 |
+
| # | Composant | Problème |
|
| 64 |
+
|---|---|---|
|
| 65 |
+
| I1 | `apps/admin` | **Pas de gestion de contenu (Tracks/TrackDays)** — impossible de créer des leçons via l'interface |
|
| 66 |
+
| I2 | `apps/admin` | `env.user?.whatsappId` utilisé mais le modèle User a `phone` (pas `whatsappId`) → colonne vide dans le tableau |
|
| 67 |
+
| I3 | `apps/web` | **Portail étudiant `/login` est factice** — `handleLogin` fait un fake `alert` avec `setTimeout`, n'appelle aucune API |
|
| 68 |
+
| I4 | `apps/web` | Pas de page de succès paiement Stripe (`/payment/success`) |
|
| 69 |
+
| I5 | `whatsapp-worker/src/scheduler.ts` | Cron envoie les leçons mais **sans logique de vérification du jour courant** — risque d'envoyer à des utilisateurs déjà complétés |
|
| 70 |
+
| I6 | `apps/api/routes/admin.ts` | Pas de route pour **créer/modifier des Tracks ou TrackDays** API-side |
|
| 71 |
+
| I7 | `packages/ui` | Package vide — prévu pour composants partagés mais non utilisé |
|
| 72 |
+
|
| 73 |
+
### 3.3 Mineurs (dette technique)
|
| 74 |
+
|
| 75 |
+
| # | Problème |
|
| 76 |
+
|---|---|
|
| 77 |
+
| M1 | `Enrollment` et `UserProgress` sont deux tables qui trackent le même `currentDay` et `exerciseStatus` — duplication de données |
|
| 78 |
+
| M2 | `admin/src/App.tsx` ligne 108 : import `lucide-react` collé avec une déclaration d'interface (mauvais formatage) |
|
| 79 |
+
| M3 | `Message` modèle Prisma non utilisé nulle part dans le code (payload jamais stocké) |
|
| 80 |
+
| M4 | `web/src` n'a pas de `VITE_API_URL` configuré par défaut → fallback `localhost:3001` en prod |
|
| 81 |
+
| M5 | Pas de gestion du cas où un utilisateur envoie `INSCRIPTION` alors qu'il est déjà inscrit à une formation active |
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
## 4. Variables d'environnement — état complet
|
| 86 |
+
|
| 87 |
+
### Railway (whatsapp-worker service)
|
| 88 |
+
|
| 89 |
+
| Variable | Statut | Action |
|
| 90 |
+
|---|---|---|
|
| 91 |
+
| `WHATSAPP_VERIFY_TOKEN` | ✅ | OK |
|
| 92 |
+
| `WHATSAPP_ACCESS_TOKEN` | ✅ | OK |
|
| 93 |
+
| `WHATSAPP_PHONE_NUMBER_ID` | ✅ | OK |
|
| 94 |
+
| `WHATSAPP_APP_SECRET` | ⚠️ Non défini | **Ajouter** (Meta App → Basic Settings → App Secret) |
|
| 95 |
+
| `ADMIN_API_KEY` | ✅ (confirmé) | OK |
|
| 96 |
+
| `DATABASE_URL` | ✅ | OK |
|
| 97 |
+
| `REDIS_URL` | ✅ | OK |
|
| 98 |
+
| `OPENAI_API_KEY` | ✅ | OK |
|
| 99 |
+
| `API_URL` | ✅ | OK (doit pointer vers URL Railway publique) |
|
| 100 |
+
| `DISABLE_WHATSAPP_SEND` | ❌ Présent | **Supprimer** |
|
| 101 |
+
| `STRIPE_SECRET_KEY` | ❓ Non confirmé | Ajouter si Stripe actif |
|
| 102 |
+
| `STRIPE_WEBHOOK_SECRET` | ❓ Non confirmé | Ajouter si Stripe actif |
|
| 103 |
+
| `NODE_ENV` | ✅ | OK |
|
| 104 |
+
| `R2_*` variables | ✅ | OK |
|
| 105 |
+
|
| 106 |
+
### HuggingFace (api service — inbound webhook désactivé)
|
| 107 |
+
|
| 108 |
+
| Variable | Statut |
|
| 109 |
+
|---|---|
|
| 110 |
+
| `DISABLE_WHATSAPP_SEND` | ✅ `true` (correct) |
|
| 111 |
+
| `OPENAI_API_KEY` | ✅ |
|
| 112 |
+
| `DATABASE_URL` | ✅ |
|
| 113 |
+
| Autres | Même que Railway |
|
| 114 |
+
|
| 115 |
+
---
|
| 116 |
+
|
| 117 |
+
## 5. Déploiements
|
| 118 |
+
|
| 119 |
+
| Service | Plateforme | URL | État |
|
| 120 |
+
|---|---|---|---|
|
| 121 |
+
| API + Worker (webhook + BullMQ) | Railway | `whatsapp-worker-production-0bc0.up.railway.app` | 🟡 En restart |
|
| 122 |
+
| Landing + Portail | HF / Netlify ? | À confirmer | ❓ |
|
| 123 |
+
| Admin Dashboard | HF / Netlify ? | À confirmer | ❓ |
|
docs/flow utilisateurs et formation .md
ADDED
|
@@ -0,0 +1,641 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# XAMLE – NOUVELLE ARCHITECTURE PÉDAGOGIQUE VOIX & WHATSAPP
|
| 2 |
+
|
| 3 |
+
> Objectif : transformer Xamle en plateforme EdTech mobile-first ultra intuitive pour le secteur informel, priorisant AUDIO + BOUTONS WHATSAPP + EXERCICES TERRAIN + IA.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
# 1️⃣ POSITIONNEMENT PÉDAGOGIQUE STRATÉGIQUE
|
| 8 |
+
|
| 9 |
+
Xamle ne doit pas être un simple bot qui envoie des messages.
|
| 10 |
+
|
| 11 |
+
Il doit devenir :
|
| 12 |
+
|
| 13 |
+
→ Un coach business vocal interactif
|
| 14 |
+
→ Un système d’apprentissage terrain progressif
|
| 15 |
+
→ Un moteur IA qui transforme apprentissage → documents concrets
|
| 16 |
+
|
| 17 |
+
Cible : entrepreneurs secteur informel
|
| 18 |
+
Contraintes :
|
| 19 |
+
- Faible écriture
|
| 20 |
+
- Connexion instable
|
| 21 |
+
- Temps limité
|
| 22 |
+
- Préférence audio
|
| 23 |
+
|
| 24 |
+
Donc priorité :
|
| 25 |
+
|
| 26 |
+
✅ Audio court
|
| 27 |
+
✅ Boutons interactifs
|
| 28 |
+
✅ Exercices terrain concrets
|
| 29 |
+
✅ Réponses vocales possibles
|
| 30 |
+
✅ Vidéo courte mobile verticale
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
# 2️⃣ NOUVEAU FLOW UTILISATEUR APRÈS INSCRIPTION
|
| 35 |
+
|
| 36 |
+
## Étape 1 – Choix langue
|
| 37 |
+
|
| 38 |
+
1. Français 🇫🇷
|
| 39 |
+
2. Wolof 🇸🇳
|
| 40 |
+
|
| 41 |
+
## Étape 2 – Choix OBJECTIF PRINCIPAL (UX simplifiée)
|
| 42 |
+
|
| 43 |
+
🎯 Trouver des clients
|
| 44 |
+
💰 Mieux gagner de l’argent
|
| 45 |
+
🧠 Comprendre son business
|
| 46 |
+
🚀 Lever des fonds / Pitcher
|
| 47 |
+
|
| 48 |
+
➡ IMPORTANT : Ces choix restent visibles côté utilisateur (langage simple et concret).
|
| 49 |
+
|
| 50 |
+
⚙️ MAIS en backend, chaque objectif correspond à de vrais modules structurés :
|
| 51 |
+
|
| 52 |
+
- "Trouver des clients" → Module Marketing + Validation terrain (Design Thinking appliqué)
|
| 53 |
+
- "Mieux gagner de l’argent" → Module Pricing + Business Model
|
| 54 |
+
- "Comprendre son business" → Module Business Model Simplifié
|
| 55 |
+
- "Lever des fonds / Pitcher" → Module Pitch + Pitch Deck
|
| 56 |
+
|
| 57 |
+
👉 Conclusion :
|
| 58 |
+
On garde les choix orientés PROBLÈME (UX simple)
|
| 59 |
+
Mais on structure les contenus avec de vrais modules entrepreneuriaux (Design Thinking, Business Model, Pitch).
|
| 60 |
+
|
| 61 |
+
---
|
| 62 |
+
|
| 63 |
+
# 3️⃣ STRUCTURE DES MODULES D’APPRENTISSAGE (VERSION MVP SANS VIDÉO)
|
| 64 |
+
|
| 65 |
+
⚠️ Décision :
|
| 66 |
+
Pour la V1, on n’intègre PAS encore les vidéos.
|
| 67 |
+
On se concentre sur : AUDIO + BOUTONS + EXERCICE + FEEDBACK IA.
|
| 68 |
+
Les vidéos seront ajoutées en V2.
|
| 69 |
+
|
| 70 |
+
Chaque module contient :
|
| 71 |
+
|
| 72 |
+
1️⃣ Leçon audio (2–3 min max)
|
| 73 |
+
- langage simple
|
| 74 |
+
- voix naturelle locale
|
| 75 |
+
- 1 seul objectif par leçon
|
| 76 |
+
- stockée sur R2
|
| 77 |
+
|
| 78 |
+
2️⃣ Exercice terrain obligatoire
|
| 79 |
+
Exemples :
|
| 80 |
+
- Parler à 3 clients
|
| 81 |
+
- Calculer son prix réel
|
| 82 |
+
- Observer un problème
|
| 83 |
+
- Faire un mini pitch audio
|
| 84 |
+
|
| 85 |
+
3️⃣ Interaction WhatsApp
|
| 86 |
+
- Boutons interactifs
|
| 87 |
+
- Validation par chiffre possible
|
| 88 |
+
- Message vocal accepté
|
| 89 |
+
|
| 90 |
+
4️⃣ Feedback IA
|
| 91 |
+
- Encouragement
|
| 92 |
+
- Correction simple
|
| 93 |
+
- Suggestion pratique
|
| 94 |
+
- Génération automatique si nécessaire
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
Architecture logique d’une journée :
|
| 99 |
+
|
| 100 |
+
Étape 1 → sendLessonDay()
|
| 101 |
+
Étape 2 → envoi audio
|
| 102 |
+
Étape 3 → envoi boutons
|
| 103 |
+
Étape 4 → attente réponse utilisateur
|
| 104 |
+
Étape 5 → sauvegarde DB
|
| 105 |
+
Étape 6 → feedback IA
|
| 106 |
+
Étape 7 → déblocage jour suivant
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
# 4️⃣ STRUCTURE PROGRESSIVE DES MODULES (STRUCTURE HYBRIDE RECOMMANDÉE)
|
| 112 |
+
|
| 113 |
+
⚠️ Décision stratégique :
|
| 114 |
+
|
| 115 |
+
On ne doit PAS opposer :
|
| 116 |
+
- Objectifs simples (Trouver clients, Gagner argent…)
|
| 117 |
+
- Modules structurés (Design Thinking, Business Model, Pitch)
|
| 118 |
+
|
| 119 |
+
On combine les deux.
|
| 120 |
+
|
| 121 |
+
---
|
| 122 |
+
|
| 123 |
+
## NIVEAU 1 – MODULES VISIBLES (Langage simple UX)
|
| 124 |
+
|
| 125 |
+
Ce que l’utilisateur voit :
|
| 126 |
+
|
| 127 |
+
1️⃣ Comprendre son business
|
| 128 |
+
2️⃣ Trouver des clients
|
| 129 |
+
3️⃣ Mieux gagner de l’argent
|
| 130 |
+
4️⃣ Structurer son activité
|
| 131 |
+
5️⃣ Pitcher son projet
|
| 132 |
+
|
| 133 |
+
---
|
| 134 |
+
|
| 135 |
+
## NIVEAU 2 – STRUCTURE ACADÉMIQUE INTERNE (Backend)
|
| 136 |
+
|
| 137 |
+
Ces modules sont en réalité construits sur :
|
| 138 |
+
|
| 139 |
+
### MODULE A – Design Thinking Terrain
|
| 140 |
+
- Observer problème
|
| 141 |
+
- Tester solution
|
| 142 |
+
- Ajuster
|
| 143 |
+
|
| 144 |
+
### MODULE B – Business Model Simplifié
|
| 145 |
+
- Client
|
| 146 |
+
- Proposition de valeur
|
| 147 |
+
- Revenus
|
| 148 |
+
- Coûts
|
| 149 |
+
- Partenaires
|
| 150 |
+
|
| 151 |
+
### MODULE C – Pricing & Rentabilité
|
| 152 |
+
- Coût réel
|
| 153 |
+
- Marge
|
| 154 |
+
- Prix stratégique
|
| 155 |
+
|
| 156 |
+
### MODULE D – Marketing Terrain
|
| 157 |
+
- Où trouver clients
|
| 158 |
+
- Message simple
|
| 159 |
+
- Test terrain
|
| 160 |
+
|
| 161 |
+
### MODULE E – Pitch Simple
|
| 162 |
+
- Problème
|
| 163 |
+
- Solution
|
| 164 |
+
- Client
|
| 165 |
+
- Différence
|
| 166 |
+
|
| 167 |
+
### MODULE F – Pitch Deck (avancé)
|
| 168 |
+
- Histoire
|
| 169 |
+
- Marché
|
| 170 |
+
- Traction
|
| 171 |
+
- Besoin financier
|
| 172 |
+
|
| 173 |
+
---
|
| 174 |
+
|
| 175 |
+
## Progression pédagogique recommandée
|
| 176 |
+
|
| 177 |
+
Phase 1 – Survie & Cash
|
| 178 |
+
→ Comprendre business
|
| 179 |
+
→ Trouver clients
|
| 180 |
+
→ Fixer prix
|
| 181 |
+
|
| 182 |
+
Phase 2 – Structuration
|
| 183 |
+
→ Business Model
|
| 184 |
+
→ Marketing terrain
|
| 185 |
+
|
| 186 |
+
Phase 3 – Croissance
|
| 187 |
+
→ Pitch
|
| 188 |
+
→ Pitch deck
|
| 189 |
+
|
| 190 |
+
---
|
| 191 |
+
|
| 192 |
+
🎯 Conclusion stratégique :
|
| 193 |
+
|
| 194 |
+
UX = langage problème (simple)
|
| 195 |
+
Architecture = vrais frameworks entrepreneuriaux
|
| 196 |
+
|
| 197 |
+
C’est la meilleure combinaison entre accessibilité et crédibilité.
|
| 198 |
+
|
| 199 |
+
---
|
| 200 |
+
|
| 201 |
+
# 5️⃣ UX WHATSAPP RECOMMANDÉE
|
| 202 |
+
|
| 203 |
+
Après chaque leçon :
|
| 204 |
+
|
| 205 |
+
1️⃣ Réécouter 🎧
|
| 206 |
+
2️⃣ Voir vidéo ▶️
|
| 207 |
+
3️⃣ Faire exercice 📝
|
| 208 |
+
4️⃣ Continuer ➡️
|
| 209 |
+
|
| 210 |
+
Réponses possibles :
|
| 211 |
+
- Boutons interactifs
|
| 212 |
+
- Message vocal
|
| 213 |
+
- Chiffre (1/2/3/4)
|
| 214 |
+
|
| 215 |
+
---
|
| 216 |
+
|
| 217 |
+
# 6️⃣ ADAPTATION BASE DE DONNÉES RECOMMANDÉE
|
| 218 |
+
|
| 219 |
+
Nouvelle structure recommandée pour track_days :
|
| 220 |
+
|
| 221 |
+
- id
|
| 222 |
+
- track_id
|
| 223 |
+
- day_number
|
| 224 |
+
- title
|
| 225 |
+
- audio_url
|
| 226 |
+
- video_url
|
| 227 |
+
- lesson_text
|
| 228 |
+
- exercise_type (audio / texte / photo / bouton)
|
| 229 |
+
- exercise_prompt
|
| 230 |
+
- validation_keyword
|
| 231 |
+
- buttons_json
|
| 232 |
+
|
| 233 |
+
Ajouter :
|
| 234 |
+
|
| 235 |
+
- user_progress (score, last_activity)
|
| 236 |
+
- media_storage_url
|
| 237 |
+
- exercise_status
|
| 238 |
+
|
| 239 |
+
---
|
| 240 |
+
|
| 241 |
+
# 7️⃣ FAISABILITÉ TECHNIQUE AVEC INFRA ACTUELLE
|
| 242 |
+
|
| 243 |
+
Infra actuelle :
|
| 244 |
+
|
| 245 |
+
✅ Neon Postgres
|
| 246 |
+
✅ Railway Worker
|
| 247 |
+
✅ Redis Upstash
|
| 248 |
+
✅ R2 Cloudflare
|
| 249 |
+
✅ WhatsApp Cloud API
|
| 250 |
+
|
| 251 |
+
Conclusion :
|
| 252 |
+
|
| 253 |
+
✔ Audio sortant = possible
|
| 254 |
+
✔ Boutons interactifs = possible
|
| 255 |
+
✔ Messages vocaux entrants = possible
|
| 256 |
+
✔ Vidéos = possible
|
| 257 |
+
✔ Stockage média = possible via R2
|
| 258 |
+
|
| 259 |
+
Aucun changement d’infrastructure nécessaire.
|
| 260 |
+
|
| 261 |
+
Le blocage n’est pas technique.
|
| 262 |
+
Il est pédagogique et UX.
|
| 263 |
+
|
| 264 |
+
---
|
| 265 |
+
|
| 266 |
+
# 8️⃣ CE QUE L’HUMAIN DOIT DÉFINIR
|
| 267 |
+
|
| 268 |
+
1️⃣ Scripts audio simples (langue locale validée)
|
| 269 |
+
2️⃣ Exercices terrain réels
|
| 270 |
+
3️⃣ Progression pédagogique cohérente
|
| 271 |
+
4️⃣ Vocabulaire accessible secteur informel
|
| 272 |
+
|
| 273 |
+
---
|
| 274 |
+
|
| 275 |
+
# 9️⃣ PROMPT À DONNER À ANTIGRAVITY POUR INTÉGRATION RAILWAY + BACKEND
|
| 276 |
+
|
| 277 |
+
Utilise le prompt ci-dessous :
|
| 278 |
+
|
| 279 |
+
---
|
| 280 |
+
|
| 281 |
+
Tu es architecte senior Node.js + Prisma + WhatsApp Cloud API.
|
| 282 |
+
|
| 283 |
+
Contexte :
|
| 284 |
+
Nous transformons le WhatsApp Worker actuel en moteur pédagogique interactif vocal (sans vidéo pour la V1).
|
| 285 |
+
|
| 286 |
+
Stack existante :
|
| 287 |
+
- Node.js
|
| 288 |
+
- Prisma + Neon Postgres
|
| 289 |
+
- Redis Upstash
|
| 290 |
+
- Railway
|
| 291 |
+
- R2 Cloudflare
|
| 292 |
+
- WhatsApp Cloud API
|
| 293 |
+
|
| 294 |
+
⚠️ Ne pas modifier l’architecture globale.
|
| 295 |
+
⚠️ Ne pas supprimer le système actuel d’onboarding.
|
| 296 |
+
⚠️ Ajouter uniquement la couche pédagogique.
|
| 297 |
+
|
| 298 |
+
---
|
| 299 |
+
|
| 300 |
+
OBJECTIFS TECHNIQUES
|
| 301 |
+
|
| 302 |
+
1️⃣ Adapter le modèle Prisma :
|
| 303 |
+
|
| 304 |
+
Model TrackDay {
|
| 305 |
+
id
|
| 306 |
+
trackId
|
| 307 |
+
dayNumber
|
| 308 |
+
title
|
| 309 |
+
audioUrl
|
| 310 |
+
lessonText
|
| 311 |
+
exerciseType
|
| 312 |
+
exercisePrompt
|
| 313 |
+
validationKeyword
|
| 314 |
+
buttonsJson
|
| 315 |
+
unlockCondition
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
Créer table UserProgress :
|
| 319 |
+
|
| 320 |
+
Model UserProgress {
|
| 321 |
+
id
|
| 322 |
+
userId
|
| 323 |
+
trackId
|
| 324 |
+
currentDay
|
| 325 |
+
score
|
| 326 |
+
lastInteraction
|
| 327 |
+
exerciseStatus
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
---
|
| 331 |
+
|
| 332 |
+
2️⃣ Créer fonction principale :
|
| 333 |
+
|
| 334 |
+
async function sendLessonDay(userId: string, dayNumber: number)
|
| 335 |
+
|
| 336 |
+
Elle doit :
|
| 337 |
+
- Charger TrackDay
|
| 338 |
+
- Envoyer audio WhatsApp
|
| 339 |
+
- Envoyer message interactif avec boutons
|
| 340 |
+
- Sauvegarder état progression
|
| 341 |
+
|
| 342 |
+
---
|
| 343 |
+
|
| 344 |
+
3️⃣ Modifier Webhook WhatsApp
|
| 345 |
+
|
| 346 |
+
Le webhook doit :
|
| 347 |
+
- Détecter message texte
|
| 348 |
+
- Détecter message vocal
|
| 349 |
+
- Détecter bouton interactif
|
| 350 |
+
- Mapper réponse vers exercice
|
| 351 |
+
- Sauvegarder réponse
|
| 352 |
+
- Déclencher feedback IA
|
| 353 |
+
|
| 354 |
+
---
|
| 355 |
+
|
| 356 |
+
4️⃣ Gestion des exercices
|
| 357 |
+
|
| 358 |
+
Si exerciseType = "audio"
|
| 359 |
+
→ Télécharger media WhatsApp
|
| 360 |
+
→ Stocker sur R2
|
| 361 |
+
→ Sauvegarder URL
|
| 362 |
+
|
| 363 |
+
Si exerciseType = "text"
|
| 364 |
+
→ Sauvegarder texte
|
| 365 |
+
|
| 366 |
+
Si exerciseType = "button"
|
| 367 |
+
→ Mapper reply.id
|
| 368 |
+
|
| 369 |
+
---
|
| 370 |
+
|
| 371 |
+
5️⃣ Feedback IA
|
| 372 |
+
|
| 373 |
+
Créer fonction :
|
| 374 |
+
|
| 375 |
+
async function generateFeedback(userInput, context)
|
| 376 |
+
|
| 377 |
+
Utiliser OPENAI_API_KEY existante
|
| 378 |
+
Retourner feedback court (max 5 lignes)
|
| 379 |
+
|
| 380 |
+
---
|
| 381 |
+
|
| 382 |
+
6️⃣ Scoring
|
| 383 |
+
|
| 384 |
+
- +1 point par exercice validé
|
| 385 |
+
- Mise à jour UserProgress
|
| 386 |
+
- Si dernier jour → trigger génération document (pitch / onepager)
|
| 387 |
+
|
| 388 |
+
---
|
| 389 |
+
|
| 390 |
+
Livrables attendus :
|
| 391 |
+
|
| 392 |
+
- Schéma Prisma complet
|
| 393 |
+
- Migration SQL
|
| 394 |
+
- Exemple JSON bouton WhatsApp
|
| 395 |
+
- Code complet sendLessonDay()
|
| 396 |
+
- Code webhook mis à jour
|
| 397 |
+
- Plan de migration base existante
|
| 398 |
+
|
| 399 |
+
---
|
| 400 |
+
|
| 401 |
+
IMPORTANT :
|
| 402 |
+
|
| 403 |
+
Ne pas ajouter vidéo pour la V1.
|
| 404 |
+
Concentrer la logique sur audio + interaction + progression.
|
| 405 |
+
|
| 406 |
+
---
|
| 407 |
+
|
| 408 |
+
Retourner le code prêt à intégrer sur Railway.
|
| 409 |
+
|
| 410 |
+
---
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
---
|
| 414 |
+
|
| 415 |
+
# 🔟 MON AVIS STRATÉGIQUE
|
| 416 |
+
|
| 417 |
+
Ce que vous avez fait jusqu’ici est faisable.
|
| 418 |
+
|
| 419 |
+
L’infrastructure est solide.
|
| 420 |
+
|
| 421 |
+
Le vrai chantier maintenant est :
|
| 422 |
+
|
| 423 |
+
➡ UX pédagogique
|
| 424 |
+
➡ Structuration des modules
|
| 425 |
+
➡ Interaction intuitive
|
| 426 |
+
|
| 427 |
+
La proposition "Voix + Boutons + Terrain" est excellente.
|
| 428 |
+
|
| 429 |
+
C’est différenciant.
|
| 430 |
+
C’est adapté au secteur informel.
|
| 431 |
+
C’est scalable.
|
| 432 |
+
|
| 433 |
+
Et surtout : c’est réaliste techniquement avec votre stack actuelle.
|
| 434 |
+
|
| 435 |
+
---
|
| 436 |
+
|
| 437 |
+
# 🚀 PROCHAINE ÉTAPE RECOMMANDÉE
|
| 438 |
+
|
| 439 |
+
Décision : programme de 1 mois (4 semaines) avec terrain.
|
| 440 |
+
|
| 441 |
+
Pour la V1 WhatsApp, on recommande :
|
| 442 |
+
- 3 leçons / semaine (lun-mer-ven) → 12 leçons / mois
|
| 443 |
+
- Chaque leçon : audio 2–3 min + 1 exercice terrain + 1 réponse simple
|
| 444 |
+
- Les jours "OFF" : l’apprenant applique sur le terrain (sans surcharge)
|
| 445 |
+
|
| 446 |
+
✅ Objectif : apprentissage réel + action terrain + progression mesurable.
|
| 447 |
+
|
| 448 |
+
---
|
| 449 |
+
|
| 450 |
+
# 11️⃣ MODULE 1 (1 MOIS) – COMPRENDRE SON BUSINESS (SECTEUR INFORMEL)
|
| 451 |
+
|
| 452 |
+
## 11.1 Résultat attendu à la fin du mois
|
| 453 |
+
|
| 454 |
+
À la fin du Module 1, l’apprenant doit pouvoir :
|
| 455 |
+
- Décrire son activité en 1 phrase (simple)
|
| 456 |
+
- Identifier 1 client principal + où le trouver
|
| 457 |
+
- Expliquer le problème client qu’il résout
|
| 458 |
+
- Énoncer son offre (produit/service) clairement
|
| 459 |
+
- Donner un prix de base cohérent (premier test)
|
| 460 |
+
- Construire un mini pitch vocal de 30–45 secondes
|
| 461 |
+
|
| 462 |
+
⚠️ Ce module n’est pas théorique : chaque semaine impose des tests terrain.
|
| 463 |
+
|
| 464 |
+
---
|
| 465 |
+
|
| 466 |
+
## 11.2 Règles UX WhatsApp (V1)
|
| 467 |
+
|
| 468 |
+
- Réponses autorisées :
|
| 469 |
+
- 1/2/3/4 (chiffres)
|
| 470 |
+
- audio (recommandé)
|
| 471 |
+
- texte très court
|
| 472 |
+
- Longs formulaires interdits.
|
| 473 |
+
- Toujours proposer des choix via boutons quand possible.
|
| 474 |
+
|
| 475 |
+
### Boutons standard (après chaque leçon)
|
| 476 |
+
1️⃣ Réécouter 🎧
|
| 477 |
+
2️⃣ Faire l’exercice 📝
|
| 478 |
+
3️⃣ Envoyer ma réponse 🎙️
|
| 479 |
+
4️⃣ Continuer ➡️
|
| 480 |
+
|
| 481 |
+
---
|
| 482 |
+
|
| 483 |
+
## 11.3 Structure d’une leçon (TrackDay)
|
| 484 |
+
|
| 485 |
+
Chaque TrackDay doit contenir :
|
| 486 |
+
- title
|
| 487 |
+
- audioUrl (R2)
|
| 488 |
+
- lessonText (résumé très court)
|
| 489 |
+
- exercisePrompt (terrain)
|
| 490 |
+
- exerciseType (audio|text|button|photo)
|
| 491 |
+
- buttonsJson (interactive)
|
| 492 |
+
- validationKeyword (ex: FAIT, OK, DONE) + acceptation audio
|
| 493 |
+
- unlockCondition (complétion jour)
|
| 494 |
+
|
| 495 |
+
---
|
| 496 |
+
|
| 497 |
+
## 11.4 Calendrier – 4 semaines / 12 leçons
|
| 498 |
+
|
| 499 |
+
> Note : l’apprenant reçoit la leçon le matin (ex: 08:00) mais peut répondre quand il veut.
|
| 500 |
+
|
| 501 |
+
### Semaine 1 – Clarifier l’activité (ce que je fais vraiment)
|
| 502 |
+
|
| 503 |
+
**Jour 1 (Lun) – "Mon activité en 1 phrase"**
|
| 504 |
+
- Objectif : clarifier ce que tu vends (sans jargon)
|
| 505 |
+
- Audio (script FR – simple) :
|
| 506 |
+
- "Dis-moi simplement : tu aides QUI à faire QUOI, et comment tu gagnes de l’argent. Exemple : Je vends du jus bissap aux étudiants devant l’université."
|
| 507 |
+
- Exercice terrain : écrire ou dire une phrase :
|
| 508 |
+
- "Je vends [offre] à [client] à [lieu]" (ou audio)
|
| 509 |
+
- Réponse attendue : audio 10–20s (ou texte 1 ligne)
|
| 510 |
+
- Feedback IA : reformule en phrase plus claire
|
| 511 |
+
|
| 512 |
+
**Jour 2 (Mer) – "Ce que le client achète vraiment"**
|
| 513 |
+
- Objectif : différencier produit vs bénéfice
|
| 514 |
+
- Audio :
|
| 515 |
+
- "Le client n’achète pas un produit, il achète un résultat. Exemple : il n’achète pas du savon, il achète la propreté."
|
| 516 |
+
- Exercice terrain : demander à 2 clients :
|
| 517 |
+
- "Pourquoi tu achètes ça ?" → noter 2 réponses
|
| 518 |
+
- Réponse : 1 audio (20s) avec les 2 raisons
|
| 519 |
+
- Feedback IA : extrait 1 bénéfice principal
|
| 520 |
+
|
| 521 |
+
**Jour 3 (Ven) – "Mon client principal"**
|
| 522 |
+
- Objectif : choisir un client prioritaire (pas "tout le monde")
|
| 523 |
+
- Audio :
|
| 524 |
+
- "Si tu essaies de vendre à tout le monde, tu vends à personne. On choisit 1 client principal."
|
| 525 |
+
- Exercice : choisir 1 client principal parmi 3 options (boutons)
|
| 526 |
+
- A) Jeunes
|
| 527 |
+
- B) Femmes
|
| 528 |
+
- C) Commerçants
|
| 529 |
+
- D) Autre (texte court)
|
| 530 |
+
- Réponse : bouton + (si Autre) texte
|
| 531 |
+
- Feedback IA : confirme le persona simple
|
| 532 |
+
|
| 533 |
+
### Semaine 2 – Problème client (ce que je résous)
|
| 534 |
+
|
| 535 |
+
**Jour 4 (Lun) – "Le problème n°1"**
|
| 536 |
+
- Objectif : identifier 1 douleur forte
|
| 537 |
+
- Audio : "Quel problème ton client veut éviter ?"
|
| 538 |
+
- Exercice terrain : parler à 3 personnes du client choisi et demander :
|
| 539 |
+
- "C’est quoi ton plus gros problème sur ça ?"
|
| 540 |
+
- Réponse : audio 30s résumant les 3 réponses
|
| 541 |
+
- Feedback IA : synthèse en 1 problème
|
| 542 |
+
|
| 543 |
+
**Jour 5 (Mer) – "Quand le problème arrive"**
|
| 544 |
+
- Objectif : comprendre contexte et moment
|
| 545 |
+
- Audio : "À quel moment ton client a le problème ?"
|
| 546 |
+
- Exercice : choisir un moment (boutons)
|
| 547 |
+
- Matin / Midi / Soir / Tout le temps
|
| 548 |
+
- Réponse : bouton + 1 phrase (option)
|
| 549 |
+
- Feedback IA : propose une phrase de positionnement
|
| 550 |
+
|
| 551 |
+
**Jour 6 (Ven) – "Comment ils le résolvent aujourd’hui"**
|
| 552 |
+
- Objectif : concurrence = solutions actuelles
|
| 553 |
+
- Audio : "Avant toi, le client fait comment ?"
|
| 554 |
+
- Exercice : écrire 2 solutions actuelles (texte court) ou audio
|
| 555 |
+
- Feedback IA : clarifie ton avantage
|
| 556 |
+
|
| 557 |
+
### Semaine 3 – Offre (solution) & preuve terrain
|
| 558 |
+
|
| 559 |
+
**Jour 7 (Lun) – "Mon offre simple"**
|
| 560 |
+
- Objectif : décrire l’offre en mots simples
|
| 561 |
+
- Audio : "Dis ta solution en une phrase."
|
| 562 |
+
- Exercice : phrase offre + 1 exemple réel
|
| 563 |
+
- Réponse : audio 20s
|
| 564 |
+
- Feedback IA : reformule en offre claire
|
| 565 |
+
|
| 566 |
+
**Jour 8 (Mer) – "Promesse et limite"**
|
| 567 |
+
- Objectif : éviter promesses irréalistes
|
| 568 |
+
- Audio : "Promets petit, livre grand."
|
| 569 |
+
- Exercice : choisir 1 promesse parmi 3 (boutons)
|
| 570 |
+
- Rapide / Moins cher / Plus fiable
|
| 571 |
+
- Feedback IA : propose promesse adaptée au contexte
|
| 572 |
+
|
| 573 |
+
**Jour 9 (Ven) – "Test terrain 1"**
|
| 574 |
+
- Objectif : valider l’intérêt
|
| 575 |
+
- Audio : "Parle à 5 personnes et propose ton offre en 10 secondes."
|
| 576 |
+
- Exercice :
|
| 577 |
+
- Dire à 5 personnes : "Je fais X pour Y"
|
| 578 |
+
- Noter combien disent OUI/NON
|
| 579 |
+
- Réponse : chiffre (ex: OUI=2, NON=3)
|
| 580 |
+
- Feedback IA : conseil pratique (améliorer phrase)
|
| 581 |
+
|
| 582 |
+
### Semaine 4 – Argent (premier prix) & mini pitch
|
| 583 |
+
|
| 584 |
+
**Jour 10 (Lun) – "Prix de base"**
|
| 585 |
+
- Objectif : fixer un premier prix cohérent
|
| 586 |
+
- Audio : "Ton prix doit couvrir tes coûts + marge."
|
| 587 |
+
- Exercice :
|
| 588 |
+
- écrire 2 coûts principaux + prix actuel
|
| 589 |
+
- Réponse : texte court (ou audio)
|
| 590 |
+
- Feedback IA : suggère marge minimale
|
| 591 |
+
|
| 592 |
+
**Jour 11 (Mer) – "Ton avantage"**
|
| 593 |
+
- Objectif : différenciation simple
|
| 594 |
+
- Audio : "Pourquoi toi plutôt qu’un autre ?"
|
| 595 |
+
- Exercice : choisir 1 avantage (boutons)
|
| 596 |
+
- Qualité / Rapidité / Confiance / Proximité
|
| 597 |
+
- Feedback IA : phrase d’avantage
|
| 598 |
+
|
| 599 |
+
**Jour 12 (Ven) – "Mini pitch audio"**
|
| 600 |
+
- Objectif : sortir un pitch 30–45s
|
| 601 |
+
- Audio : guide pitch :
|
| 602 |
+
1) Je suis…
|
| 603 |
+
2) J’aide…
|
| 604 |
+
3) Parce que…
|
| 605 |
+
4) Je vends… à …
|
| 606 |
+
- Exercice : envoyer pitch vocal 30–45s
|
| 607 |
+
- Réponse : audio obligatoire
|
| 608 |
+
- Feedback IA : 3 conseils max + version texte du pitch
|
| 609 |
+
|
| 610 |
+
---
|
| 611 |
+
|
| 612 |
+
## 11.5 Sorties IA après Module 1 (automatiques)
|
| 613 |
+
|
| 614 |
+
Après Jour 12 complété :
|
| 615 |
+
- Générer :
|
| 616 |
+
- Pitch texte 30s
|
| 617 |
+
- One-liner activité
|
| 618 |
+
- Mini fiche (1 page) "Mon activité" (option)
|
| 619 |
+
|
| 620 |
+
⚠️ Pitch deck complet = V2 (ou Module Pitch dédié).
|
| 621 |
+
|
| 622 |
+
---
|
| 623 |
+
|
| 624 |
+
## 11.6 Notes d’implémentation (pour Antigravity)
|
| 625 |
+
|
| 626 |
+
- Créer Track "MODULE1_COMPRENDRE_BUSINESS" (FR + WO)
|
| 627 |
+
- Seeder : insérer 12 TrackDays
|
| 628 |
+
- sendLessonDay() :
|
| 629 |
+
- envoyer audio (media)
|
| 630 |
+
- envoyer interactive buttons
|
| 631 |
+
- Webhook :
|
| 632 |
+
- accepter chiffres + boutons + audio
|
| 633 |
+
- Exercice terrain :
|
| 634 |
+
- validationKeyword = {"FAIT","OK","DONE"} ou audio reçu
|
| 635 |
+
- Progression :
|
| 636 |
+
- currentDay++ après validation
|
| 637 |
+
|
| 638 |
+
---
|
| 639 |
+
|
| 640 |
+
Fin du document.
|
| 641 |
+
|
docs/plan technique solution .md
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 12. PLAN COMPLET DE LA SOLUTION (TECHNIQUE)
|
| 2 |
+
|
| 3 |
+
> Objectif : développer une plateforme EdTech **mobile-first** centrée sur WhatsApp + contenus sociaux, avec une brique IA pour générer des documents (pitch, pitch deck, one-pager, mini business plan, logo basique).
|
| 4 |
+
|
| 5 |
+
## 12.1 Produits livrables (MVP → V1)
|
| 6 |
+
|
| 7 |
+
### MVP (6–8 semaines)
|
| 8 |
+
|
| 9 |
+
- WhatsApp Learning Track (7–14 jours) : inscription, séquençage, exercices, suivi de complétion
|
| 10 |
+
- Admin dashboard : cohortes, progression, export CSV, tags, statistiques de base
|
| 11 |
+
- Générateur IA v0 : **Pitch (30s + 2min)** + **One-pager PDF** (1 page)
|
| 12 |
+
- Landing page : inscription, FAQ, contact
|
| 13 |
+
|
| 14 |
+
### V1 (8–16 semaines)
|
| 15 |
+
|
| 16 |
+
- Générateur IA v1 : Pitch deck (8–10 slides PPTX) + mini business plan (2–4 pages)
|
| 17 |
+
- Bibliothèque de templates (deck / one-pager) + styles
|
| 18 |
+
- Scoring pédagogique (auto-évaluation) : maturité business (non-financier complexe)
|
| 19 |
+
- Paiement (Wave / Orange Money) pour cohortes présentiel
|
| 20 |
+
|
| 21 |
+
### V2 (16–24 semaines)
|
| 22 |
+
|
| 23 |
+
- Personnalisation par filière (livreur, artisanat, commerce, transformation)
|
| 24 |
+
- Multi-langue (Wolof + Français)
|
| 25 |
+
- Data products (anonymisés) : tableaux de bord d’insights pour partenaires
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
# 13. ARCHITECTURE (SIMPLE, SCALABLE)
|
| 30 |
+
|
| 31 |
+
## 13.1 Vue d’ensemble
|
| 32 |
+
|
| 33 |
+
**Canaux**
|
| 34 |
+
|
| 35 |
+
- TikTok / Instagram : acquisition (contenus + CTA)
|
| 36 |
+
- WhatsApp : parcours structuré (learning track)
|
| 37 |
+
- Web : inscription + paiement + téléchargement documents
|
| 38 |
+
|
| 39 |
+
**Back-end**
|
| 40 |
+
|
| 41 |
+
- API principale (auth, learning, admin)
|
| 42 |
+
- Service WhatsApp (webhooks + envoi messages)
|
| 43 |
+
- Service IA (génération texte + génération PDF/PPTX)
|
| 44 |
+
|
| 45 |
+
**Données**
|
| 46 |
+
|
| 47 |
+
- Base Postgres (users, tracks, réponses, documents)
|
| 48 |
+
- Stockage fichiers (PDF/PPTX) : S3 compatible (ex: Cloudflare R2 / AWS S3 / OVH Object Storage)
|
| 49 |
+
|
| 50 |
+
## 13.2 Recommandation repo Git
|
| 51 |
+
|
| 52 |
+
✅ Démarrage conseillé : **Monorepo** (plus simple au début)
|
| 53 |
+
|
| 54 |
+
Structure type :
|
| 55 |
+
|
| 56 |
+
- `/apps/api` : API (Node/TS ou Python)
|
| 57 |
+
- `/apps/whatsapp-worker` : worker WhatsApp (queues + scheduling)
|
| 58 |
+
- `/apps/admin` : dashboard admin (Next.js)
|
| 59 |
+
- `/apps/web` : landing + paiement + portail apprenant
|
| 60 |
+
- `/packages/shared` : types, templates, prompts, contenu pédagogique
|
| 61 |
+
|
| 62 |
+
➡️ Séparer en plusieurs repos uniquement si l’IA devient un produit autonome.
|
| 63 |
+
|
| 64 |
+
---
|
| 65 |
+
|
| 66 |
+
# 14. STACK TECH RECOMMANDÉ
|
| 67 |
+
|
| 68 |
+
## 14.1 Front-end
|
| 69 |
+
|
| 70 |
+
- **Next.js** (React) : admin + web portal
|
| 71 |
+
- UI : TailwindCSS + component library (shadcn/ui)
|
| 72 |
+
|
| 73 |
+
## 14.2 Back-end
|
| 74 |
+
|
| 75 |
+
Option A (très simple, robuste) : **Node.js + TypeScript**
|
| 76 |
+
|
| 77 |
+
- Framework : Fastify ou NestJS
|
| 78 |
+
- Validation : Zod
|
| 79 |
+
|
| 80 |
+
Option B (si équipe plus Python) : **Python + FastAPI**
|
| 81 |
+
|
| 82 |
+
## 14.3 Base de données
|
| 83 |
+
|
| 84 |
+
- **PostgreSQL** (Neon / Supabase / OVH / RDS)
|
| 85 |
+
- ORM : Prisma (Node) ou SQLModel (Python)
|
| 86 |
+
|
| 87 |
+
## 14.4 Messaging / Jobs
|
| 88 |
+
|
| 89 |
+
- Queue : **BullMQ (Redis)** ou **Cloud Tasks**
|
| 90 |
+
- Objectif : envoi WhatsApp programmé, génération documents asynchrone
|
| 91 |
+
|
| 92 |
+
## 14.5 WhatsApp
|
| 93 |
+
|
| 94 |
+
- Recommandé : **WhatsApp Cloud API (Meta)**
|
| 95 |
+
- Alternative : Twilio (plus simple mais coûts + dépendances)
|
| 96 |
+
|
| 97 |
+
## 14.6 IA / Document generation
|
| 98 |
+
|
| 99 |
+
- Génération texte : via LLM (prompts + garde-fous)
|
| 100 |
+
- Génération PDF : HTML → PDF (Playwright) ou ReportLab
|
| 101 |
+
- Génération PPTX : PptxGenJS (templates) ou python-pptx
|
| 102 |
+
- Logos : au MVP, générer **logo basique** (icône + typographie) ou proposer 3 styles (pas de promesse “brand pro”)
|
| 103 |
+
|
| 104 |
+
---
|
| 105 |
+
|
| 106 |
+
# 15. CHARTE GRAPHIQUE – DESIGN SYSTEM PROPOSÉ
|
| 107 |
+
|
| 108 |
+
Objectif : créer une identité visuelle moderne, crédible, inclusive et technologique, adaptée au secteur informel africain mais suffisamment institutionnelle pour parler à des bailleurs et partenaires.
|
| 109 |
+
|
| 110 |
+
## 15.1 Positionnement visuel
|
| 111 |
+
|
| 112 |
+
Le design doit transmettre :
|
| 113 |
+
|
| 114 |
+
- Inclusion
|
| 115 |
+
- Modernité
|
| 116 |
+
- Technologie accessible
|
| 117 |
+
- Confiance financière
|
| 118 |
+
- Éducation structurée
|
| 119 |
+
|
| 120 |
+
Le style est :
|
| 121 |
+
|
| 122 |
+
- Mobile-first
|
| 123 |
+
- Clair et lisible
|
| 124 |
+
- Peu chargé
|
| 125 |
+
- Orienté impact
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
## 15.2 Palette Couleurs (proposition stratégique)
|
| 130 |
+
|
| 131 |
+
### 🎯 Couleur primaire – Vert Structuration
|
| 132 |
+
|
| 133 |
+
`#1C7C54`
|
| 134 |
+
|
| 135 |
+
Signification : croissance, stabilité, transformation progressive. Utilisation : boutons principaux, titres, éléments forts, KPI.
|
| 136 |
+
|
| 137 |
+
---
|
| 138 |
+
|
| 139 |
+
### 🎯 Couleur secondaire – Bleu Profondeur
|
| 140 |
+
|
| 141 |
+
`#1B3A57`
|
| 142 |
+
|
| 143 |
+
Signification : crédibilité, sérieux institutionnel, tech. Utilisation : header, footer, dashboard admin, slide de couverture.
|
| 144 |
+
|
| 145 |
+
---
|
| 146 |
+
|
| 147 |
+
### 🎯 Accent – Orange Activation
|
| 148 |
+
|
| 149 |
+
`#F4A261`
|
| 150 |
+
|
| 151 |
+
Signification : énergie, entrepreneuriat, action. Utilisation : CTA, highlights, icônes actives, progression.
|
| 152 |
+
|
| 153 |
+
---
|
| 154 |
+
|
| 155 |
+
### 🎯 Gris neutres
|
| 156 |
+
|
| 157 |
+
- Gris clair fond : `#F5F7F9`
|
| 158 |
+
- Gris texte secondaire : `#6B7280`
|
| 159 |
+
- Noir texte principal : `#111827`
|
| 160 |
+
|
| 161 |
+
---
|
| 162 |
+
|
| 163 |
+
## 15.3 Typographie
|
| 164 |
+
|
| 165 |
+
### Titres
|
| 166 |
+
|
| 167 |
+
**Montserrat Bold / SemiBold**
|
| 168 |
+
|
| 169 |
+
- Moderne
|
| 170 |
+
- Lisible
|
| 171 |
+
- Impact visuel fort
|
| 172 |
+
|
| 173 |
+
### Texte courant
|
| 174 |
+
|
| 175 |
+
**Inter / Poppins Regular**
|
| 176 |
+
|
| 177 |
+
- Optimisé mobile
|
| 178 |
+
- Lecture fluide
|
| 179 |
+
- Compatible PDF & PPT
|
| 180 |
+
|
| 181 |
+
Fallback system :
|
| 182 |
+
|
| 183 |
+
- Arial / Helvetica
|
| 184 |
+
|
| 185 |
+
---
|
| 186 |
+
|
| 187 |
+
## 15.4 Tokens Design (à utiliser dans le code)
|
| 188 |
+
|
| 189 |
+
```ts
|
| 190 |
+
export const theme = {
|
| 191 |
+
colors: {
|
| 192 |
+
primary: '#1C7C54',
|
| 193 |
+
secondary: '#1B3A57',
|
| 194 |
+
accent: '#F4A261',
|
| 195 |
+
background: '#F5F7F9',
|
| 196 |
+
textPrimary: '#111827',
|
| 197 |
+
textSecondary: '#6B7280'
|
| 198 |
+
},
|
| 199 |
+
radius: {
|
| 200 |
+
card: '12px',
|
| 201 |
+
button: '8px'
|
| 202 |
+
},
|
| 203 |
+
shadow: {
|
| 204 |
+
soft: '0 4px 12px rgba(0,0,0,0.08)'
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
---
|
| 210 |
+
|
| 211 |
+
## 15.5 UI Components Standards
|
| 212 |
+
|
| 213 |
+
### Boutons
|
| 214 |
+
|
| 215 |
+
- Primary : fond vert `#1C7C54`, texte blanc
|
| 216 |
+
- Secondary : fond bleu `#1B3A57`, texte blanc
|
| 217 |
+
- Accent : fond orange `#F4A261`, texte noir
|
| 218 |
+
|
| 219 |
+
Hover : légère augmentation luminosité + shadow soft.
|
| 220 |
+
|
| 221 |
+
---
|
| 222 |
+
|
| 223 |
+
### Cards
|
| 224 |
+
|
| 225 |
+
- Fond blanc
|
| 226 |
+
- Border radius 12px
|
| 227 |
+
- Ombre douce
|
| 228 |
+
- Padding généreux (16–24px)
|
| 229 |
+
|
| 230 |
+
---
|
| 231 |
+
|
| 232 |
+
### Dashboard Admin
|
| 233 |
+
|
| 234 |
+
- Sidebar bleu foncé
|
| 235 |
+
- KPI cards vertes
|
| 236 |
+
- Alerts orange
|
| 237 |
+
- Graphiques simples (bar / line)
|
| 238 |
+
|
| 239 |
+
---
|
| 240 |
+
|
| 241 |
+
## 15.6 Style Documents Générés (PDF / PPT)
|
| 242 |
+
|
| 243 |
+
One-pager :
|
| 244 |
+
|
| 245 |
+
- Header bleu profond
|
| 246 |
+
- Titres verts
|
| 247 |
+
- Accent orange pour chiffres clés
|
| 248 |
+
- Icônes simples outline
|
| 249 |
+
|
| 250 |
+
Pitch deck :
|
| 251 |
+
|
| 252 |
+
- Slide couverture : fond bleu + titre blanc
|
| 253 |
+
- Slides contenu : fond blanc + titres verts
|
| 254 |
+
- Graphiques en vert + orange
|
| 255 |
+
|
| 256 |
+
---
|
| 257 |
+
|
| 258 |
+
## 15.7 Ton visuel global
|
| 259 |
+
|
| 260 |
+
Pas trop “startup Silicon Valley”. Pas trop “ONG traditionnelle”.
|
| 261 |
+
|
| 262 |
+
→ Tech africaine inclusive. → Sérieuse mais accessible.
|
| 263 |
+
|
| 264 |
+
---
|
| 265 |
+
|
| 266 |
+
# 16. WHATSAPP LEARNING TRACK – COMMENT LE CODER
|
| 267 |
+
|
| 268 |
+
WHATSAPP LEARNING TRACK – COMMENT LE CODER
|
| 269 |
+
|
| 270 |
+
## 16.1 Logique pédagogique
|
| 271 |
+
|
| 272 |
+
- Track = 7 ou 14 jours
|
| 273 |
+
- Chaque jour :
|
| 274 |
+
- 1 audio (1–3 min)
|
| 275 |
+
- 1 visuel (option)
|
| 276 |
+
- 1 exercice (réponse texte/voice)
|
| 277 |
+
- 1 validation (mot-clé “OK”, “SUIVANT”, bouton)
|
| 278 |
+
|
| 279 |
+
## 16.2 Parcours utilisateur
|
| 280 |
+
|
| 281 |
+
1. L’utilisateur envoie “INSCRIPTION”
|
| 282 |
+
2. Le bot demande : prénom, activité, ville, langue
|
| 283 |
+
3. Inscription au Track
|
| 284 |
+
4. Envoi Jour 1
|
| 285 |
+
5. Sauvegarde réponses + progression
|
| 286 |
+
6. Relance automatique si inactif (J+1)
|
| 287 |
+
7. Checkpoints (J7/J14) : mini pitch + données business
|
| 288 |
+
|
| 289 |
+
## 16.3 Modèle de données (minimum viable)
|
| 290 |
+
|
| 291 |
+
Tables :
|
| 292 |
+
|
| 293 |
+
- `users(id, phone, name, language, city, created_at)`
|
| 294 |
+
- `tracks(id, name, duration_days, language)`
|
| 295 |
+
- `track_days(id, track_id, day_number, content_type, content_url, prompt_text)`
|
| 296 |
+
- `enrollments(id, user_id, track_id, status, current_day, started_at, completed_at)`
|
| 297 |
+
- `responses(id, enrollment_id, day_number, text, media_url, created_at)`
|
| 298 |
+
- `messages(id, user_id, channel, direction, payload_json, created_at)`
|
| 299 |
+
|
| 300 |
+
## 16.4 Endpoints API (exemple)
|
| 301 |
+
|
| 302 |
+
- `POST /webhooks/whatsapp` : réception messages
|
| 303 |
+
- `POST /enroll` : créer inscription
|
| 304 |
+
- `POST /send/day` : envoyer contenu du jour N
|
| 305 |
+
- `GET /admin/metrics` : stats
|
| 306 |
+
|
| 307 |
+
## 16.5 Exemple (Node/TS) – Webhook WhatsApp (simplifié)
|
| 308 |
+
|
| 309 |
+
```ts
|
| 310 |
+
import Fastify from 'fastify'
|
| 311 |
+
|
| 312 |
+
const app = Fastify()
|
| 313 |
+
|
| 314 |
+
app.post('/webhooks/whatsapp', async (req, reply) => {
|
| 315 |
+
const payload: any = req.body
|
| 316 |
+
// 1) extraire phone + message
|
| 317 |
+
const phone = payload?.entry?.[0]?.changes?.[0]?.value?.messages?.[0]?.from
|
| 318 |
+
const text = payload?.entry?.[0]?.changes?.[0]?.value?.messages?.[0]?.text?.body?.trim()?.toUpperCase()
|
| 319 |
+
|
| 320 |
+
if (!phone) return reply.code(200).send({ ok: true })
|
| 321 |
+
|
| 322 |
+
// 2) router simple
|
| 323 |
+
if (text === 'INSCRIPTION') {
|
| 324 |
+
// TODO: créer user + enrollment
|
| 325 |
+
// TODO: répondre avec questions profil
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
// TODO: progression track
|
| 329 |
+
return reply.code(200).send({ ok: true })
|
| 330 |
+
})
|
| 331 |
+
|
| 332 |
+
app.listen({ port: 3000, host: '0.0.0.0' })
|
| 333 |
+
```
|
| 334 |
+
|
| 335 |
+
---
|
| 336 |
+
|
| 337 |
+
# 17. IA – GÉNÉRATION PITCH / ONE-PAGER / DECK
|
| 338 |
+
|
| 339 |
+
## 17.1 Par quoi commencer (recommandation)
|
| 340 |
+
|
| 341 |
+
1. **Pitch (30s + 2min)**
|
| 342 |
+
2. **One-pager PDF (1 page)** ➡️ Ensuite seulement : Pitch deck PPTX
|
| 343 |
+
|
| 344 |
+
## 17.2 Inputs (collectés via WhatsApp + formulaire web)
|
| 345 |
+
|
| 346 |
+
- Activité
|
| 347 |
+
- Client cible
|
| 348 |
+
- Problème
|
| 349 |
+
- Solution
|
| 350 |
+
- Prix / revenus
|
| 351 |
+
- Coûts principaux
|
| 352 |
+
- Preuves (photos, témoignages, traction)
|
| 353 |
+
- Besoin (montant, équipement, stock)
|
| 354 |
+
|
| 355 |
+
## 17.3 Sorties (MVP)
|
| 356 |
+
|
| 357 |
+
- Pitch 30 secondes (wolof/fr)
|
| 358 |
+
- Pitch 2 minutes (wolof/fr)
|
| 359 |
+
- One-pager PDF : 6 blocs (problème, solution, marché, modèle, traction, besoin)
|
| 360 |
+
|
| 361 |
+
## 17.4 Templates & garde-fous
|
| 362 |
+
|
| 363 |
+
- Templates de contenu (structure fixe)
|
| 364 |
+
- Pas de promesses irréalistes
|
| 365 |
+
- Toujours demander 1 preuve (même simple)
|
| 366 |
+
- Mentionner types de financement informel (microcrédit, crédit équipement, etc.)
|
| 367 |
+
|
| 368 |
+
## 17.5 Exemple (pseudo-code) – endpoint docgen
|
| 369 |
+
|
| 370 |
+
```ts
|
| 371 |
+
app.post('/ai/onepager', async (req, reply) => {
|
| 372 |
+
const input = req.body // validated
|
| 373 |
+
// 1) appeler modèle texte → sections
|
| 374 |
+
const sections = await generateSectionsWithLLM(input)
|
| 375 |
+
// 2) rendre HTML template
|
| 376 |
+
const html = renderOnePagerHTML(sections)
|
| 377 |
+
// 3) convertir en PDF
|
| 378 |
+
const pdfUrl = await htmlToPdfAndUpload(html)
|
| 379 |
+
return reply.send({ pdfUrl })
|
| 380 |
+
})
|
| 381 |
+
```
|
| 382 |
+
|
| 383 |
+
---
|
| 384 |
+
|
| 385 |
+
---
|
| 386 |
+
|
| 387 |
+
## 17.6 AI SLIDES ENGINE – VERSION “GENSPARK-LIKE”
|
| 388 |
+
|
| 389 |
+
Objectif : ne pas seulement générer du texte, mais produire un **pitch deck complet, designé, prêt à envoyer (PPTX/PDF)** avec :
|
| 390 |
+
|
| 391 |
+
- Couleurs appliquées automatiquement (charte interne)
|
| 392 |
+
- Icônes cohérentes
|
| 393 |
+
- Hiérarchie visuelle
|
| 394 |
+
- Mise en page intelligente
|
| 395 |
+
|
| 396 |
+
### Architecture recommandée
|
| 397 |
+
|
| 398 |
+
Le système ne repose pas uniquement sur un prompt texte. Il repose sur 3 couches :
|
| 399 |
+
|
| 400 |
+
### 1️⃣ LLM → Structure JSON avancée
|
| 401 |
+
|
| 402 |
+
Le modèle doit produire une structure riche, par exemple :
|
| 403 |
+
|
| 404 |
+
```json
|
| 405 |
+
[
|
| 406 |
+
{
|
| 407 |
+
"type": "cover",
|
| 408 |
+
"title": "Nom du projet",
|
| 409 |
+
"subtitle": "Pitch court",
|
| 410 |
+
"themeVariant": "primary"
|
| 411 |
+
},
|
| 412 |
+
{
|
| 413 |
+
"type": "problem",
|
| 414 |
+
"title": "Problème",
|
| 415 |
+
"bullets": ["...", "..."],
|
| 416 |
+
"icon": "alert-circle",
|
| 417 |
+
"highlightStat": "80% des clients..."
|
| 418 |
+
}
|
| 419 |
+
]
|
| 420 |
+
```
|
| 421 |
+
|
| 422 |
+
Le LLM ne génère pas seulement du texte. Il génère une **intention de layout**.
|
| 423 |
+
|
| 424 |
+
---
|
| 425 |
+
|
| 426 |
+
### 2️⃣ Slide Renderer Engine
|
| 427 |
+
|
| 428 |
+
Un moteur interne transforme cette structure en slides réelles :
|
| 429 |
+
|
| 430 |
+
- Mapping automatique des types (`cover`, `problem`, `solution`, `market`, etc.)
|
| 431 |
+
- Application des couleurs selon le `themeVariant`
|
| 432 |
+
- Injection des icônes (Lucide / Heroicons / custom SVG)
|
| 433 |
+
- Placement automatique des blocs (grille 2 colonnes, full width, etc.)
|
| 434 |
+
|
| 435 |
+
Avec **PptxGenJS** :
|
| 436 |
+
|
| 437 |
+
- Création slide
|
| 438 |
+
- Ajout titre
|
| 439 |
+
- Ajout shape colorée
|
| 440 |
+
- Ajout icône SVG
|
| 441 |
+
- Ajout bullets stylisées
|
| 442 |
+
|
| 443 |
+
---
|
| 444 |
+
|
| 445 |
+
### 3️⃣ Design System Binding
|
| 446 |
+
|
| 447 |
+
Le moteur doit utiliser les tokens définis section 15 :
|
| 448 |
+
|
| 449 |
+
- Primary → #1C7C54
|
| 450 |
+
- Secondary → #1B3A57
|
| 451 |
+
- Accent → #F4A261
|
| 452 |
+
|
| 453 |
+
Chaque type de slide a une règle :
|
| 454 |
+
|
| 455 |
+
- Cover → fond bleu profond
|
| 456 |
+
- Problem → bande accent orange
|
| 457 |
+
- Solution → titre vert
|
| 458 |
+
- Financial → chiffres en accent
|
| 459 |
+
|
| 460 |
+
Ainsi, le rendu est cohérent automatiquement.
|
| 461 |
+
|
| 462 |
+
---
|
| 463 |
+
|
| 464 |
+
### 4️⃣ Auto-Layout Rules
|
| 465 |
+
|
| 466 |
+
Le moteur doit :
|
| 467 |
+
|
| 468 |
+
- Limiter à 5 bullets max
|
| 469 |
+
- Couper les phrases trop longues
|
| 470 |
+
- Ajuster taille police selon longueur
|
| 471 |
+
- Éviter slide surchargée
|
| 472 |
+
|
| 473 |
+
Ce n’est pas seulement du texte. C’est une logique produit.
|
| 474 |
+
|
| 475 |
+
---
|
| 476 |
+
|
| 477 |
+
### 5️⃣ Pipeline complet
|
| 478 |
+
|
| 479 |
+
1. Collect inputs utilisateur
|
| 480 |
+
2. Générer JSON structuré via LLM
|
| 481 |
+
3. Valider JSON (Zod schema)
|
| 482 |
+
4. Mapper vers template engine
|
| 483 |
+
5. Générer PPTX
|
| 484 |
+
6. Stocker fichier (S3)
|
| 485 |
+
7. Retourner lien WhatsApp
|
| 486 |
+
|
| 487 |
+
---
|
| 488 |
+
|
| 489 |
+
### Important
|
| 490 |
+
|
| 491 |
+
Pour obtenir un résultat niveau Genspark :
|
| 492 |
+
|
| 493 |
+
- Toujours utiliser output JSON structuré (pas texte libre)
|
| 494 |
+
- Toujours passer par un renderer interne
|
| 495 |
+
- Toujours appliquer un design system cohérent
|
| 496 |
+
|
| 497 |
+
Le rendu final doit être :
|
| 498 |
+
|
| 499 |
+
"Document prêt à envoyer à un financeur"
|
| 500 |
+
|
| 501 |
+
et non
|
| 502 |
+
|
| 503 |
+
"Texte copié-collé dans PowerPoint"
|
| 504 |
+
|
| 505 |
+
---
|
| 506 |
+
|
| 507 |
+
# 18. ADMIN DASHBOARD (IGNITE.E / ÉQUIPE)
|
| 508 |
+
|
| 509 |
+
Fonctionnalités admin MVP :
|
| 510 |
+
|
| 511 |
+
- Liste des inscrits (WhatsApp)
|
| 512 |
+
- Progression par track
|
| 513 |
+
- Taux de complétion J1/J7/J14
|
| 514 |
+
- Export CSV
|
| 515 |
+
- Gestion des contenus (track\_days)
|
| 516 |
+
- Gestion des cohortes présentiel
|
| 517 |
+
- Paiements (V1)
|
| 518 |
+
|
| 519 |
+
---
|
| 520 |
+
|
| 521 |
+
# 19. SÉCURITÉ, DONNÉES & CONFORMITÉ
|
| 522 |
+
|
| 523 |
+
- Consentement explicite à l’inscription (WhatsApp) : utilisation pédagogique + données anonymisées
|
| 524 |
+
- Pseudonymisation : séparer identité (phone) des analytics
|
| 525 |
+
- Stockage chiffré des médias (si possible)
|
| 526 |
+
- Règles d’accès : admin role-based
|
| 527 |
+
- Journalisation (logs) + monitoring
|
| 528 |
+
|
| 529 |
+
---
|
| 530 |
+
|
| 531 |
+
# 20. PLAN D’EXÉCUTION (PHASÉ)
|
| 532 |
+
|
| 533 |
+
## Phase 0 – Préparation (1 semaine)
|
| 534 |
+
|
| 535 |
+
- Finaliser 2 tracks (Business Model 7j + Pitch 7j)
|
| 536 |
+
- Écrire scripts audios wolof + exercices
|
| 537 |
+
- Définir templates one-pager
|
| 538 |
+
|
| 539 |
+
## Phase 1 – MVP WhatsApp Track (2–3 semaines)
|
| 540 |
+
|
| 541 |
+
- Webhook WhatsApp + parsing
|
| 542 |
+
- Enrollment + progression
|
| 543 |
+
- Scheduling envoi jour N
|
| 544 |
+
- Dashboard metrics minimal
|
| 545 |
+
- Pilote 50–100 utilisateurs
|
| 546 |
+
|
| 547 |
+
## Phase 2 – IA v0 (2–3 semaines)
|
| 548 |
+
|
| 549 |
+
- Collect inputs
|
| 550 |
+
- Générer pitch + one-pager PDF
|
| 551 |
+
- Retour WhatsApp (lien)
|
| 552 |
+
|
| 553 |
+
## Phase 3 – V1 (4–8 semaines)
|
| 554 |
+
|
| 555 |
+
- Paiement cohorte présentiel
|
| 556 |
+
- Pitch deck PPTX
|
| 557 |
+
- Auto-évaluation (scoring pédagogique)
|
| 558 |
+
|
| 559 |
+
---
|
| 560 |
+
|
| 561 |
+
# 21. LISTE DES FONCTIONNALITÉS (CHECKLIST)
|
| 562 |
+
|
| 563 |
+
## Apprenants
|
| 564 |
+
|
| 565 |
+
- Inscription WhatsApp
|
| 566 |
+
- Parcours 7/14 jours
|
| 567 |
+
- Exercices + feedback
|
| 568 |
+
- Documents générés (liens)
|
| 569 |
+
|
| 570 |
+
## Admin
|
| 571 |
+
|
| 572 |
+
- Gestion contenus
|
| 573 |
+
- Stats complétion
|
| 574 |
+
- Exports
|
| 575 |
+
- Cohortes + paiements (V1)
|
| 576 |
+
|
| 577 |
+
## IA
|
| 578 |
+
|
| 579 |
+
- Pitch 30s / 2min
|
| 580 |
+
- One-pager PDF
|
| 581 |
+
- Pitch deck PPTX (V1)
|
| 582 |
+
- Logo basique (V1/V2)
|
| 583 |
+
|
| 584 |
+
---
|
| 585 |
+
|
| 586 |
+
# 22. NOTES IMPORTANTES
|
| 587 |
+
|
| 588 |
+
- Commencer simple : WhatsApp Track d’abord, puis IA.
|
| 589 |
+
- Le “logo IA” est optionnel au MVP.
|
| 590 |
+
- Mettre l’accent sur la qualité pédagogique + tracking.
|
| 591 |
+
- Les documents générés doivent rester réalistes et adaptés au financement informel.
|
| 592 |
+
|
docs/plan_implementation_prod.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Plan d'Implémentation Production
|
| 2 |
+
*Priorité : du plus bloquant au moins urgent*
|
| 3 |
+
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
## Phase 1 — Déploiement Railway opérationnel (immediat)
|
| 7 |
+
|
| 8 |
+
> Objectif : Railway répond, webhook Meta validé, premiers messages reçoivent une réponse.
|
| 9 |
+
|
| 10 |
+
### Étape 1.1 — Variables Railway (manuel, 5 min)
|
| 11 |
+
1. **Supprimer** `DISABLE_WHATSAPP_SEND`
|
| 12 |
+
2. **Ajouter** `WHATSAPP_APP_SECRET` (Meta App → Basic Settings → App Secret)
|
| 13 |
+
3. Vérifier que `API_URL` = `https://whatsapp-worker-production-0bc0.up.railway.app`
|
| 14 |
+
|
| 15 |
+
### Étape 1.2 — Valider le webhook Meta (manuel, 2 min)
|
| 16 |
+
1. Attendre fin du redéploiement Railway (logs : `Server listening on http://0.0.0.0:3001`)
|
| 17 |
+
2. Tester : `curl https://whatsapp-worker-production-0bc0.up.railway.app/` → `{"ok":true}`
|
| 18 |
+
3. Meta Developers → WhatsApp → Configuration → Webhook → Retenter la validation
|
| 19 |
+
|
| 20 |
+
### Étape 1.3 — Créer le contenu pédagogique (base de données)
|
| 21 |
+
Le flow complet nécessite au moins **1 Track avec 3+ TrackDays**. À insérer via l'endpoint seed existant (`SEED_DATA`) ou directement en base :
|
| 22 |
+
- Envoyer `SEED_DATA` depuis WhatsApp (déclencheur existant dans `whatsapp.ts`)
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## Phase 2 — Corriger les bugs d'interface (1-2 jours)
|
| 27 |
+
|
| 28 |
+
### Étape 2.1 — Fix Admin App : champ `whatsappId` → `phone`
|
| 29 |
+
**Fichier** : `apps/admin/src/App.tsx` lignes 164, 237
|
| 30 |
+
|
| 31 |
+
```diff
|
| 32 |
+
- env.user?.whatsappId || 'Unknown'
|
| 33 |
+
+ env.user?.phone || 'Unknown'
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
### Étape 2.2 — Fix Web App : portail étudiant factice
|
| 37 |
+
**Fichier** : `apps/web/src/App.tsx` — fonction `handleLogin`
|
| 38 |
+
|
| 39 |
+
Implémenter l'authentification réelle :
|
| 40 |
+
- Appel API avec le numéro de téléphone
|
| 41 |
+
- Retour des documents générés (OnePager, PitchDeck) via liens R2
|
| 42 |
+
|
| 43 |
+
### Étape 2.3 — Page de succès Stripe
|
| 44 |
+
Créer une page `/payment/success` dans `apps/web` qui :
|
| 45 |
+
- Confirme le paiement à l'utilisateur
|
| 46 |
+
- Affiche un lien vers WhatsApp pour commencer
|
| 47 |
+
|
| 48 |
+
---
|
| 49 |
+
|
| 50 |
+
## Phase 3 — Gestion de contenu admin (3-5 jours)
|
| 51 |
+
|
| 52 |
+
### Étape 3.1 — API backend : routes Track/TrackDay
|
| 53 |
+
**Fichier** : `apps/api/src/routes/admin.ts`
|
| 54 |
+
|
| 55 |
+
Ajouter :
|
| 56 |
+
```typescript
|
| 57 |
+
POST /v1/admin/tracks // Créer un track
|
| 58 |
+
PUT /v1/admin/tracks/:id // Modifier
|
| 59 |
+
POST /v1/admin/tracks/:id/days // Ajouter un TrackDay
|
| 60 |
+
PUT /v1/admin/tracks/:id/days/:dayId
|
| 61 |
+
DELETE /v1/admin/tracks/:id
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
### Étape 3.2 — Interface admin : Content Manager
|
| 65 |
+
**Fichier** : `apps/admin/src/App.tsx`
|
| 66 |
+
|
| 67 |
+
Ajouter une vue "Content" dans le sidebar :
|
| 68 |
+
- Liste des Tracks avec bouton Créer/Modifier
|
| 69 |
+
- Pour chaque Track : liste des jours avec éditeur de texte + upload audio
|
| 70 |
+
|
| 71 |
+
---
|
| 72 |
+
|
| 73 |
+
## Phase 4 — Améliorations qualité (optionnel)
|
| 74 |
+
|
| 75 |
+
### Étape 4.1 — Unifier Enrollment / UserProgress
|
| 76 |
+
Les deux tables doublonnent `currentDay`. Migrer vers `UserProgress` uniquement et retirer `currentDay` de `Enrollment`.
|
| 77 |
+
|
| 78 |
+
### Étape 4.2 — Logger les messages entrants (modèle Message)
|
| 79 |
+
Le modèle `Message` en base n'est jamais rempli. Ajouter un `prisma.message.create()` dans `handleIncomingMessage`.
|
| 80 |
+
|
| 81 |
+
### Étape 4.3 — Scheduler : vérification état avant envoi
|
| 82 |
+
Dans `scheduler.ts`, avant d'envoyer la leçon du jour, vérifier que l'enrollment est `ACTIVE` et que le `currentDay` correspond.
|
| 83 |
+
|
| 84 |
+
### Étape 4.4 — Variables VITE_API_URL dans les apps frontend
|
| 85 |
+
S'assurer que `VITE_API_URL` est configuré dans les `.env` de production de `apps/admin` et `apps/web`.
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
|
| 89 |
+
## Résumé des priorités
|
| 90 |
+
|
| 91 |
+
```
|
| 92 |
+
✅ FAIT → API + Worker Railway, todos les bugs WhatsApp fixés
|
| 93 |
+
🔴 CRITIQUE → Supprimer DISABLE_WHATSAPP_SEND, ajouter WHATSAPP_APP_SECRET
|
| 94 |
+
🟠 IMPORTANT → Valider webhook Meta, créer contenu (SEED_DATA)
|
| 95 |
+
🟡 SOON → Fix admin phone field, portail étudiant réel
|
| 96 |
+
🟢 OPTIONNEL → Stripe live, content manager admin, Message logging
|
| 97 |
+
```
|