diff --git a/docs/plan-admin-marketing-statistiques-coupons.md b/docs/plan-admin-marketing-statistiques-coupons.md new file mode 100644 index 00000000..4cb0fb71 --- /dev/null +++ b/docs/plan-admin-marketing-statistiques-coupons.md @@ -0,0 +1,295 @@ +# Memento — Plan administration : marketing, statistiques et offres Stripe + +Date : 6 septembre 2026. +Statut : plan en cours. M1 livré le 6 septembre 2026 (navigation et pages de travail). Aucun envoi, aucun coupon, aucun chiffre inventé. +Demande : examiner l'administration et préparer un plan détaillé avant toute implémentation. +Périmètre de cette session : lectures du code, consultation de documentation officielle, observation locale de `/admin` et `/admin/billing`, rédaction de ce document. Aucun courriel envoyé, aucun coupon créé, aucune donnée modifiée. + +## 1. Objectif et décisions proposées + +Permettre à un administrateur de préparer des courriels de relance ou de présentation de Memento, choisir leurs destinataires, joindre une offre commerciale réellement applicable dans Stripe, vérifier le message, envoyer une campagne et mesurer son résultat. + +Ajouter deux entrées à l'administration : +- **Marketing** : campagnes, audiences, modèles de courriel, offres et préférences d'envoi. +- **Statistiques** : revenus, abonnements, activité produit et résultats des campagnes. + +Conserver **Facturation & quotas** comme référence pour les tarifs, les identifiants de prix et les limites IA. Les offres ne constituent pas un deuxième catalogue tarifaire. + +Proposition de première version : campagnes manuelles, brouillons assistés par IA, test obligatoire, réductions en pourcentage, destinataires déjà inscrits et autorisés à recevoir de la prospection. Ajouter ensuite les montants fixes et les relances planifiées. Aucune séquence automatique active par défaut. + +### Références fournies + +Les deux captures de l'utilisateur représentent une autre application. Elles expriment des capacités souhaitées, pas une consigne de copier ses chiffres, sa marque ou ses activités. +- Capture marketing : génération IA, audience, réduction, sujet, contenu, aperçu, test et historique. +- Capture statistiques : période, revenus, abonnements et usage. +- Fichiers source des captures : `/home/devparsa/.codex/attachments/d3ab1333-8c84-4124-af46-d4dd77f73aab/codex-clipboard-04125874-6e48-421f-970c-ffc9b3641a13.png` et `/home/devparsa/.codex/attachments/3147e261-0b19-4103-a242-8d1ad26e2720/codex-clipboard-b9c36100-7ca2-4c48-83f2-00255b6e710c.png`. + +Ne pas reprendre « traductions », « liste d'attente » ou d'autres fonctions sans équivalent confirmé dans Memento. Préférer une interface de travail structurée à une accumulation de grandes cartes vides. + +## 2. État actuel vérifié + +| Élément | Présent | Conséquence | +| --- | --- | --- | +| Navigation admin | `memento-note/components/admin-sidebar.tsx` : accueil, utilisateurs, IA, facturation, publications, paramètres | Ajouter deux destinations sans réorganiser toute l'administration | +| Accueil admin | `app/(admin)/admin/page.tsx` : utilisateurs, notes, carnets, agents, abonnements et usage | Garder cette synthèse ; fournir les analyses détaillées dans Statistiques | +| Facturation | `app/(admin)/admin/billing/`, `app/actions/admin-billing.ts` | Réutiliser configuration métier en base et contrôles administrateur | +| Paiement abonnement | `app/api/billing/create-checkout/route.ts` | `allow_promotion_codes: true` existe dans les deux modes de paiement ; pas de parcours de campagne dans cette route | +| Stripe | `lib/stripe.ts` ; version API déclarée `2026-04-22.dahlia` | Vérifier les types de la version installée avant d'écrire les paramètres de coupons | +| Notifications Stripe | `app/api/billing/webhook/route.ts` | Gère notamment abonnement, fin d'essai et échec de paiement ; compléter le journal financier et la déduplication si absents | +| Courriels | `lib/mail.ts` : Resend et SMTP, identifiant de message, repli automatique entre fournisseurs | Ne pas utiliser le repli aveugle pour les campagnes : un délai dépassé peut masquer un envoi déjà accepté | +| Rappel d'essai | `lib/billing/trial-reminder-email.ts` | Éviter les doublons avec les futures relances | +| Données | `User`, `Subscription`, `UsageLog`, `AiConsentLog`, `AuditLog` dans `prisma/schema.prisma` | Étendre les modèles existants avec migrations additives | +| Historique d'usage | `UsageLog` agrège par utilisateur, fonction et période | Insuffisant pour reconstruire honnêtement un usage quotidien historique | +| Consentement marketing | Non identifié dans les modèles examinés | Ne pas assimiler consentement IA, compte vérifié ou abonnement à un consentement marketing | +| Dernière activité et langue marketing | Pas de champs dédiés identifiés sur `User` | Définir des sources fiables avant les segments « inactifs » et les envois multilingues | + +Les valeurs vues localement ne décrivent pas la production. Ne pas présenter les compteurs locaux comme chiffres commerciaux. +L'accueil actuel transforme certaines erreurs de lecture en zéro : ne pas reproduire ce comportement dans les nouvelles statistiques. + +## 3. Parcours et composition des écrans + +### 3.1 Marketing : liste des campagnes + +Route proposée : `/admin/marketing`. +Tableau avec nom interne, objectif, audience, langue, offre associée, état, date prévue, acceptés par le fournisseur, délivrés et conversions payées. +Actions : créer, reprendre un brouillon, dupliquer, consulter les résultats, suspendre les messages restants. Pagination et filtres d'état. +Une duplication crée un nouveau brouillon et exige un nouveau test ; elle ne déclenche aucun envoi. + +### 3.2 Préparation d'une campagne + +Route : `/admin/marketing/campaigns/[id]`. +Parcours en quatre étapes, brouillon sauvegardé entre les étapes : +1. **Objectif et destinataires** : nouveauté produit, découverte d'une fonction, fin d'essai commerciale, retour après inactivité, réabonnement, fidélisation. Définition d'audience et exclusions chiffrées. +2. **Message et offre** : sujet, texte d'aperçu de boîte mail, contenu, bouton principal, éventuelle réduction. Éditeur visuel adapté au courriel ; code HTML dans une vue avancée, pas comme outil principal. +3. **Aperçu et test** : ordinateur/téléphone, version texte, valeurs de personnalisation fictives, conditions de réduction et pied de page réel. Adresse de test explicite. Test obligatoire pour chaque variante linguistique finale. +4. **Validation et envoi** : nombre final, différences depuis la préparation, exclusions, coût d'envoi si connu, échéance de l'offre, résumé de consentement. Commande finale explicite, puis progression et annulation des envois non commencés. + +L'IA propose un brouillon, jamais un envoi. Une correction de sujet, contenu, offre, liens, modèle, expéditeur ou langue invalide le test correspondant. +L'audience est revue au moment de l'envoi ; un changement de taille important doit être visible avant confirmation. +Éviter les cartes imbriquées et les longs formulaires entièrement ouverts. Aides contextuelles, sauvegarde visible et messages d'erreur au bon champ. + +### 3.3 Audiences + +Audiences de départ : comptes autorisés à recevoir des annonces ; utilisateurs BASIC ; essais en cours/fin prochaine ; abonnés payants par formule et périodicité ; anciens abonnés ; langue connue. +La présence d'un compte Stripe ne prouve pas un abonnement payant. Distinguer utilisateurs inscrits, abonnés à la communication et abonnés payants dans les libellés. + +Les segments « inactifs depuis N jours » exigent une mesure d'activité humaine documentée. `User.updatedAt`, les notes créées par des agents et les accès automatiques ne sont pas de bons substituts à une dernière visite. +Exclusions : désabonnement marketing, absence de base d'envoi autorisée, plainte, adresse définitivement invalide, compte supprimé, adresse non vérifiée selon politique retenue, plafonnement de fréquence, destinataire incompatible avec l'offre. +Afficher les raisons et les nombres, avec détails accessibles uniquement aux administrateurs autorisés. Pas d'import arbitraire de fichiers d'adresses dans la première version. + +### 3.4 Génération assistée + +Champs : objectif, consignes, langue, ton, offre facultative. Fournisseur et modèle issus du catalogue serveur vivant, sélection avancée si utile. +Contexte autorisé : description validée de Memento, fonctions réellement livrées, tarifs du catalogue, essai de 7 jours, conditions exactes de l'offre. +Interdit : lire le contenu privé des notes pour cibler un message ; inventer des chiffres, témoignages, promesses ou conditions commerciales. +Produire une structure validée : sujet, aperçu, blocs de texte, bouton, mentions de l'offre. Générer ensuite le HTML avec un modèle maîtrisé et une version texte. +Échapper les personnalisations, limiter taille et liens, neutraliser scripts/événements HTML, isoler l'aperçu. Les consignes saisies ne peuvent pas déclencher d'outil d'envoi ou de création Stripe. +Créer une fonction IA dédiée `marketing_email` dans le mécanisme existant d'entitlements et réservation atomique ; quota administratif configurable dans Facturation. Réserver avant l'appel, sans remboursement automatique sur échec, et notifier le compteur d'usage. Ne pas facturer silencieusement les destinataires. + +## 4. Offres et coupons Stripe + +### 4.1 Modèle métier + +Un **coupon Stripe** définit la réduction. Un **code promotionnel Stripe** la distribue avec ses restrictions. Une **offre Memento** relie campagne, conditions, coupon/code et suivi. +Ne pas créer une remise uniquement dans le courriel ou le navigateur. + +Champs de l'offre : nom interne, réduction en pourcentage ou montant/devise, formules admissibles, mensualité/annualité, date limite d'activation avec fuseau affiché, durée d'application, maximum d'utilisations, bénéficiaires et état de synchronisation. +Séparer clairement **date limite pour profiter de l'offre** et **durée de la réduction après activation**. +Montants en unités monétaires entières, pas en nombres flottants. Une offre publiée est versionnée ; on ne modifie pas rétroactivement les conditions des messages déjà envoyés. + +### 4.2 Offre de départ recommandée + +« 20 % de réduction sur la prochaine facture mensuelle admissible du forfait Pro », réservée à une audience précise, avec date d'expiration et un usage par bénéficiaire. +Préférer une durée bornée à une réduction permanente proposée par défaut. Ne pas écrire « pendant 7 jours » pour confondre validité du lien et durée de la remise. +Pour un forfait annuel, afficher le total annuel réduit et l'équivalent mensuel ; préciser que la réduction porte sur une facture annuelle. Ne jamais présenter une réduction annuelle comme un seul mois offert. + +Avec l'essai gratuit de 7 jours, vérifier en mode Stripe test quelle facture consomme la remise, notamment une facture initiale à zéro. Ne promettre « première facture payante » qu'après validation du comportement de la version d'API utilisée. La restriction Stripe « premier achat » peut exclure une personne ayant déjà démarré un essai : ne pas l'activer aveuglément pour une relance de fin d'essai. + +### 4.3 Distribution recommandée + +Deux modes : code partagé pour une campagne publique ; code individuel restreint au client Stripe pour une offre ciblée. Préférer le second pour fidélisation et réactivation. +Bouton du courriel vers une route Memento, avec jeton opaque et date de validité ; aucun courriel ou identifiant personnel lisible dans l'URL. Le code reste copiable comme solution de secours si prévu. +Un simple clic GET, y compris par un robot d'analyse de courriel, ne crée pas de réduction, d'abonnement ou de réservation consommable. +Après connexion, vérifier le destinataire, l'offre, la formule et les restrictions. Afficher le prix et les conditions ; appliquer uniquement après action utilisateur explicite. +Le lien doit retrouver l'offre après authentification, sans redirection ouverte et sans divulguer les données du bénéficiaire. + +### 4.4 Nouvel abonnement versus abonnement existant + +- **Sans abonnement payant actif** : enrichir la création de session existante avec l'offre validée côté serveur. Choisir soit remise préappliquée, soit saisie libre de code ; vérifier la compatibilité API, ne pas transmettre les deux options sans contrôle. Préserver les deux modes de paiement. +- **Abonné actif** : ne pas créer un second abonnement. Prévoir une application contrôlée sur l'abonnement existant, avec aperçu de la prochaine facture, consentement explicite et règles de cumul. La version initiale peut exclure ces personnes des campagnes de conversion, mais le parcours de fidélisation reste une livraison identifiée. +- **Annulation programmée / impayé** : expliquer ce que l'offre change ; elle ne réactive pas automatiquement un abonnement et ne règle pas un impayé. Ne pas modifier le calendrier ni les proratisations à l'insu du client. + +Le serveur et Stripe font autorité sur les limites. Une restriction par périodicité doit être contrôlée côté Memento : les restrictions Stripe par produit ne suffisent pas forcément lorsque le même produit porte un prix mensuel et annuel. +Séparer strictement mode test et mode réel dans les données, codes, compteurs et interfaces. +Réconciliation des objets Stripe modifiés depuis son interface. Désactiver les nouveaux usages sans prétendre retirer les remises déjà appliquées. +Création et activation idempotentes ; journal des objets créés et état récupérable si Stripe réussit mais que la sauvegarde locale échoue. + +## 5. Consentement, délivrabilité et envoi fiable + +### 5.1 Préférences + +La première version doit privilégier un consentement marketing explicite, distinct de l'inscription, de l'IA et des courriels nécessaires au service. Les utilisateurs historiques ne sont pas automatiquement inscrits aux campagnes. +Prévoir un choix dans les préférences utilisateur et, si validé, à l'inscription, non précoché. Journaliser texte/version, date, source et retrait. Une exception de prospection client ne doit être activée que si ses conditions sont établies ; la seule création d'un compte ne suffit pas. Voir la source CNIL en fin de document. +Ne pas envoyer un courriel commercial de « demande de consentement » aux exclus comme contournement. +Le désabonnement marketing ne bloque pas vérification de compte, réinitialisation de mot de passe ou notifications indispensables de facturation. Une remise promotionnelle n'est pas ajoutée à ces messages transactionnels pour contourner les règles. + +Lien de désabonnement sans connexion, signé/opaque, indépendant de la durée de l'offre. GET affiche la page de confirmation pour éviter les désabonnements par robots ; POST traite l'action. Ajouter les en-têtes de désabonnement en un clic conformément aux fournisseurs, avec POST sans session pour cette voie spécifique. Recontrôler l'exclusion juste avant chaque envoi. +Retrait global marketing immédiat ; plaintes et rebonds définitifs ajoutent une suppression. Une campagne déjà confiée au fournisseur ne peut pas être rappelée : le signaler honnêtement. + +### 5.2 Service d'envoi + +Préférer Resend explicitement pour les campagnes, car les événements de livraison sont exploitables. Garder les messages transactionnels sur le service existant. SMTP reste une option identifiée avec limites de mesure ; ne pas promettre un statut « délivré » sur le seul succès SMTP. +Étendre le transport sans casser ses appelants : texte alternatif, en-têtes autorisés, identifiant de campagne, identifiant de message, clé d'idempotence. Fournisseur fixé par exécution. +File durable en PostgreSQL pour la vérité des destinataires et états ; Redis peut aider à la limitation et au verrouillage. Réutiliser les pratiques de tâches planifiées du dépôt avant d'ajouter une dépendance. Aucun envoi de masse dans une requête HTTP longue ni simple tâche mémoire. +Réservation atomique d'un message, bail limité, reprises avec délai et nombre d'essais borné. État `UNKNOWN` pour un résultat fournisseur ambigu ; ne pas basculer automatiquement sur SMTP. Vérifier le fournisseur avant réémission et tenir compte de sa fenêtre de déduplication. +Une clé unique campagne/exécution/destinataire/langue empêche les doublons métier. Un redémarrage ou double clic d'administrateur ne recrée pas les destinataires. +Ne pas utiliser BCC pour diffuser à toute une audience. Envoi individualisé, sans exposition d'autres adresses. +Rythme progressif, plafond global et par personne configurables, pause manuelle et automatique si plaintes ou erreurs anormales. Seuils configurables, pas de valeurs fournisseur inventées. + +### 5.3 Test et validation serveur + +Version immuable du message : sujet, aperçu, blocs, rendu HTML/texte, modèle, offre, liens, expéditeur et langue. Empreinte calculée côté serveur. +Un test accepté par le fournisseur pour cette version autorise une validation humaine ; il ne prouve pas la réception en boîte principale. Le statut doit refléter cette distinction. +À l'envoi réel, vérifier la version testée, l'identité administrateur, la validité de l'offre, les variantes de langue et l'état de campagne dans une transition atomique. +Les liens individuels sont issus de paramètres contrôlés ; tester leur structure sans consommer une vraie réduction. Toute modification structurelle après test invalide celui-ci. +Tests et aperçus ne comptent pas dans les conversions ni statistiques de campagne. + +## 6. Statistiques : indicateurs utiles et définitions + +Route `/admin/stats`. Période globale, comparaison à période précédente de même durée, fuseau explicite, date de dernière synchronisation et actualisation manuelle. Ne pas recopier une période « 30 jours » sur une carte pilotée par « Aujourd'hui ». +Sections : **Revenus**, **Abonnements**, **Usage**, **Campagnes**. Quatre à six indicateurs principaux par section, courbes utiles et tableaux détaillables ; pas de grandes zones vides artificielles. + +| Indicateur | Définition et source attendue | +| --- | --- | +| Encaissements de la période | Paiements effectivement réussis, datés au paiement ; séparés des factures créées et des sessions terminées | +| Remboursements | Montants réellement remboursés sur la période ; montrer séparément brut encaissé et net après remboursement, en précisant traitement des taxes/frais | +| Revenus récurrents mensuels estimés | Récurrent contractuel actif ramené au mois, annuel / 12 ; exclure crédits ponctuels et essais non payants ; distinguer avant et après remises récurrentes | +| Abonnements | Payants actifs, essais, annulations programmées, terminés, impayés ; ne pas mélanger les statuts | +| Conversion d'essai | Cohorte d'essais ayant eu le temps d'arriver à échéance, puis premier paiement réussi ; exclure cohorte encore immature | +| Résiliations | Abonnements réellement terminés pendant la période / base active au début ; afficher nombre et formule | +| Activité humaine | Utilisateurs ayant une action produit définie ; ne pas déduire l'activité de créations automatiques de notes | +| Usage IA | Réservations/consommations par fonction, séparées des appels réussis/échoués ; `UsageLog` seul ne donne ni détail quotidien ni taux d'erreur | +| Campagnes | Destinataires autorisés, acceptés, délivrés, rebonds, plaintes, désabonnements, clics si autorisés, offres appliquées, paiements attribués | + +Plusieurs devises : jamais additionner sans conversion explicite documentée. Version initiale : tableaux séparés par devise. Aucun MRR calculé en multipliant tous les abonnés par le prix public actuel. +Le cumul historique doit afficher sa date de couverture. Ne pas inventer une série quotidienne à partir d'agrégats mensuels. Prévoir import paginé Stripe en lecture seule, reprise et déduplication pour l'historique disponible. +Échecs de synchronisation : « indisponible » ou « données au [date] », jamais zéro silencieux. Si dénominateur nul, afficher « non calculable » plutôt que 100 % de réussite. +La comparaison ne doit pas mélanger données test et réelles, période complète et période partielle. + +### Attribution commerciale + +Transporter des identifiants opaques de campagne/offre dans les métadonnées de paiement puis les objets nécessaires au rapprochement des factures. Une session terminée avec essai n'est pas un revenu. +Version initiale : conversion directe si offre de campagne appliquée et paiement réussi, au plus une première conversion par abonnement/campagne ; présenter séparément nombre d'acheteurs et total de leurs paiements attribués. +Déduire les remboursements selon une règle explicitée. Un code partagé peut circuler hors audience : distinguer usages du code et conversions prouvées d'un destinataire. +Ouvertures de courriel non prioritaires, tracking désactivé par défaut : elles ne sont pas une preuve fiable de lecture. Les clics peuvent inclure des robots ; ne pas les appeler utilisateurs engagés sans qualification. L'attribution ne prouve pas que la campagne a causé la vente. +Budget/coût fournisseur inconnu : ne pas afficher un retour sur investissement ou une marge fictive. Le classement des utilisateurs reste un outil admin agrégé, sans exposer leurs notes privées. + +## 7. Architecture et données proposées + +Les noms suivants sont des propositions, pas des modèles déjà présents. Adapter au schéma réel sans créer de doublons. + +| Modèle | Rôle / contraintes essentielles | +| --- | --- | +| MarketingPreference + journal | Choix actuel par utilisateur/adresse, langue préférée, preuve/version et historique ; opposition conservée selon politique de rétention | +| EmailSuppression | Adresse normalisée ou empreinte compatible avec la politique de données, motif, source et date ; filtrage avant envoi | +| MarketingCampaign | Objectif, auteur, segment versionné, statut, programmation, dernière version | +| CampaignVersion | Contenu et conditions immuables par langue, empreinte, modèle IA et version de modèle de courriel | +| CampaignTest | Version testée, administrateur, adresse de test protégée, résultat fournisseur et validation | +| CampaignRun | Audience figée, exclusions, version approuvée, compteurs et progression | +| CampaignRecipient | Unicité run/utilisateur/variante ; état, essais, bail, identifiant fournisseur, version rendue, erreurs expurgées | +| MarketingOffer | Conditions versionnées, mode Stripe, identifiants coupon/code, statut, limites et dates | +| OfferClaim | Bénéficiaire, code individuel éventuel, état de réservation/application et lien abonnement/facture ; unicité anti-double application | +| ProviderEvent | Fournisseur + identifiant événement uniques, signature vérifiée, horodatage, traitement et erreurs | +| BillingTransaction / projection facture | Identifiants Stripe uniques, devise, montants, paiement/remboursement, campagne/offre et dates ; source financier locale réconciliable | +| ProductActivity / agrégat | Événements minimaux nécessaires aux statistiques futures ; aucune note ni consigne privée dans la mesure | + +Index : état/date prévue pour tâches ; run/état pour destinataires ; fournisseur/eventId ; mode Stripe/objet ; user/date pour activité. Pagination des audiences. Durées de conservation configurées et documentées, pas conservation illimitée des contenus et adresses de campagne par défaut. +Journal `AuditLog` pour création, validation, envoi, pause, changement d'offre et accès/export sensibles. Ne pas stocker secrets, corps privés ou jetons utilisables dans les journaux. + +États campagne : DRAFT → READY → SCHEDULED ou QUEUED → SENDING → COMPLETED/PARTIAL_FAILED ; PAUSED/CANCELED pour le restant. Une édition après READY crée une nouvelle version et retire l'approbation. +État destinataire : PENDING → SENDING → ACCEPTED → DELIVERED ; FAILED/UNKNOWN/SUPPRESSED selon résultat. Les événements arrivent parfois en désordre ; conserver les faits plutôt qu'écraser une plainte par une livraison tardive. + +### Routes/services envisagés + +- Pages sous `app/(admin)/admin/marketing/` et `app/(admin)/admin/stats/`. +- Services sous `lib/marketing/` : audiences, contenu, validation, offres, envoi, suppression, attribution. +- Actions/API admin : campagnes CRUD, audience-preview, generate, test, approve-send, pause, resume ; offres create/sync/deactivate ; statistiques read/export. +- `/api/marketing/unsubscribe` public à jeton ; `/api/marketing/email-webhook` public à signature fournisseur. +- `/offers/[token]` pour consulter l'offre, puis endpoint authentifié POST pour l'appliquer ; aucun effet facturation sur GET. +- Étendre le webhook Stripe existant, sans second traitement concurrent des mêmes événements. Vérifier la signature sur corps brut, dédupliquer, enregistrer durablement puis traiter/reprendre. +- Tâche d'envoi et tâche de réconciliation utilisant le mécanisme cron/worker existant ; protection par secret serveur et verrous. + +Chaque action admin vérifie le rôle côté serveur, y compris aperçu d'audience, génération, export et endpoints appelés directement. Contrôles CSRF/origine adaptés aux sessions, limitations de fréquence et validation Zod. URL publiques depuis configuration serveur approuvée, pas depuis un Host utilisateur non validé. Aucun domaine ou IP codé en dur dans le produit. + +## 8. Livraison par fonctionnalités + +Une fonctionnalité à la fois, vérification et validation utilisateur avant la suivante. Les dépendances de fondation peuvent être développées sans activer les envois réels. + +| Ordre | Livraison | Acceptation principale | +| --- | --- | --- | +| M1 | Navigation et pages de travail | **Livré** — `/admin/marketing`, `/admin/stats`, menu admin. Tableau campagnes vide honnête ; statistiques « pas encore disponible » (pas de zéro). Fichiers : `admin-sidebar.tsx`, `app/(admin)/admin/marketing/`, `app/(admin)/admin/stats/`, clés i18n `admin.marketing` / `admin.stats`. | +| M2 | Préférences et exclusions | Consentement explicite, retrait sans connexion, anciennes inscriptions non admises automatiquement | +| M3 | Audiences | Nombre cohérent, exclusions expliquées, segmentation réelle, pas d'utilisation de notes privées | +| M4 | Brouillons et éditeur | Sauvegarde, version texte/HTML, aperçu sécurisé, personnalisation contrôlée | +| M5 | Génération IA | Modèle serveur, consignes produit validées, langue et réservation de quota, aucun envoi automatique | +| M6 | Offres Stripe test | Création/version/synchronisation, 20 %, expiration, limites et séparation test/réel | +| M7 | Activation nouvel abonné | Offre validée côté serveur, parcours après login, interaction essai vérifiée, pas de double abonnement | +| M8 | Test et validation | Envoi test de version exacte, invalidation après modification, décision finale atomique | +| M9 | File et historique | Pause/reprise, absence de doublons métier, erreurs ambiguës gérées, exclusions revérifiées | +| M10 | Événements et mesures d'envoi | Signatures, doublons, désordre, rebonds/plaintes, test hors statistiques | +| M11 | Statistiques financières | Paiements/remboursements réconciliés, MRR défini, couverture et devises visibles | +| M12 | Usage et résultats marketing | Mesures disponibles distinguées des absentes, attribution directe vérifiable | +| M13 | Fidélisation abonnés actifs | Aperçu facture, consentement, application sur abonnement existant, cumul/prorata contrôlés | +| M14 | Planification et relances | Dates/fuseaux, règles explicites, plafonds, déduplication avec rappels existants, désactivé par défaut | + +Options ultérieures, non autorisées par ce plan : tests de variantes A/B, import de prospects externes, séquences complexes, score automatique de propension, export public, comparaison causale de campagnes. Ne pas les développer spontanément. + +## 9. Vérification et protection des données + +Respecter l'interdiction actuelle d'écrire de nouveaux tests sans demande explicite. Préparer une matrice de vérification manuelle et exécuter les contrôles existants pertinents ; proposer une autorisation ciblée de tests automatisés pour ces mécanismes critiques lors de leur développement, sans la présumer acquise. + +Scénarios indispensables : +- compte non administrateur refusé sur chaque endpoint ; aperçu HTML malveillant neutralisé ; liens externes non autorisés rejetés ; données privées absentes du contexte IA ; +- absence de consentement, retrait après mise en file, plainte, adresse invalide et dépassement de fréquence ; +- version modifiée après test, une langue non testée, offre expirée entre test et envoi ; +- double clic, deux workers, arrêt après acceptation fournisseur, événement reçu deux fois ou dans le désordre ; +- offre à 20 %, code d'un autre client, code partagé épuisé, mensualité contre annualité, coupon désactivé et ancien lien ; +- essai 7 jours, facture à zéro, premier paiement, abonnement actif, annulation programmée, remboursement partiel et plusieurs devises ; +- Stripe indisponible, historique incomplet, absence de données, erreur qui ne doit pas devenir zéro ; +- français, anglais, persan/RTL, écran de téléphone, clavier, lecture des conditions et liens de désabonnement ; +- comparaison des chiffres d'un jeu Stripe test avec son interface, sans utiliser la production comme banc d'essai. + +Ne pas envoyer de vrais courriels, créer de coupons réels ou toucher à un abonnement réel sans autorisation explicite de l'utilisateur portant sur l'action concrète. Cette demande autorise uniquement le plan. +Migrations additives et procédure du dépôt. Aucune commande destructive. Ne pas intégrer des migrations au workflow de déploiement en contredisant les règles existantes. + +## 10. Décisions à faire valider avant activation réelle + +1. Expéditeur et adresse de réponse ; Resend recommandé pour les campagnes, transport transactionnel conservé. +2. Consentement explicite par défaut et parcours de recueil pour les utilisateurs existants. +3. Offre initiale : pourcentage, formule, durée, expiration, population ; aucune remise permanente présélectionnée. +4. Traitement de l'essai gratuit et des abonnés déjà actifs, après scénario Stripe test concluant. +5. Budget de génération IA et fréquence maximale d'envoi ; paramètres métier dans l'administration. +6. Langue préférée : source existante à identifier, stockage explicite si nécessaire ; ne pas deviner à partir du contenu des notes. +7. Suivi de clics et durée de conservation ; pas de suivi d'ouverture par défaut. +8. Définition finale du MRR et périmètre de l'historique importable ; ne pas annoncer de précision comptable non démontrée. + +Ces décisions ne bloquent pas la lecture du code, la conception des écrans ou la préparation d'une migration additive. Elles bloquent seulement les actions qui dépendent réellement de leur réponse. + +## 11. Sources officielles consultées + +Vérifiées le 6 septembre 2026. Recontrôler les paramètres exacts contre les types Stripe/Resend installés au moment de l'implémentation. +- [Stripe : coupons et codes promotionnels](https://docs.stripe.com/billing/subscriptions/coupons) — distinction entre réduction et code, restrictions et application à un abonnement. +- [Stripe : réductions dans Checkout](https://docs.stripe.com/payments/checkout/discounts) — parcours de paiement et limites de remise. +- [Resend : clés d'idempotence](https://resend.com/docs/dashboard/emails/idempotency-keys) — déduplication fournisseur ; vérifier sa durée de validité lors de l'implémentation. +- [Resend : notifications serveur](https://resend.com/docs/webhooks/introduction) et [types d'événements](https://resend.com/docs/webhooks/event-types) — livraison, erreurs et réception potentiellement désordonnée. +- [CNIL : prospection par courrier électronique](https://www.cnil.fr/la-prospection-commerciale-par-courrier-electronique) — consentement, opposition et limites de l'exception client. Ce plan retient une politique explicite par défaut, pas une qualification juridique automatique de toute audience. + +## 12. Reprise par un autre agent + +- Lire ce document puis AGENTS.md, PRODUCT.md et le code réellement présent. +- Vérifier `git status` et la branche actuelle ; conserver tous les changements préexistants. Ne pas supposer que les anciens constats sont encore valides. +- Demander quelle fonctionnalité M1–M14 démarrer si aucune n'a été autorisée. Ne pas transformer la demande de plan en autorisation d'envoi ou d'implémentation complète. +- Utiliser des modèles légers pour recherche de fichiers et vérification de clés. Réserver l'analyse approfondie aux garanties d'envoi, aux remises et à la mesure financière. +- Chaque livraison met à jour ce document et `docs/user-stories.md` avec état, fichiers, vérifications, limites et prochaine étape. Ne pas marquer « terminé » sans preuve. +- Ne jamais recopier de secrets, sessions, adresses privées de test ou mots de passe dans les documents. +- Ne pas continuer les précédents chantiers de design de Carnets/éditeur : ils sont hors de ce périmètre. diff --git a/docs/user-stories.md b/docs/user-stories.md index 6c269926..3a1f55f3 100644 --- a/docs/user-stories.md +++ b/docs/user-stories.md @@ -1,7 +1,7 @@ # User Stories — Memento Next Phase > Basé sur l'analyse du prototype `architectural-grid/` et du code production `memento-note/`. -> Dernière mise à jour : 2026-06-28 (Epic 6 terminé — sync avec sprint-status.yaml) +> Dernière mise à jour : 2026-09-06 (M1 admin marketing/statistiques — pages de travail, sans envoi) --- @@ -30,6 +30,7 @@ | **US-CHAT-PDF** | Chat with PDF — RAG documentaire | ✅ **LIVRÉ** | `document-qa-overlay.tsx`, `document-ingestion`, `document-search` tool | | **US-PPTX-EXPORT** | Export PPTX + Watermark | ✅ **LIVRÉ** | `lib/brainstorm/export-pptx.ts`, `lib/ai/tools/pptx.tool.ts` | | **US-PUBLISH-IA** | Publication IA (templates magazine/brief/essay + rewrite) | ✅ **LIVRÉ** | `lib/publish/`, `publish-enhance.service.ts`, 4 templates CSS, quota `publish_enhance` | +| **M1-ADMIN-MARKETING** | Admin Marketing + Statistiques : pages de travail, menu, aucun envoi ni chiffre fictif | ✅ **LIVRÉ** | `admin-sidebar.tsx`, `app/(admin)/admin/marketing/`, `app/(admin)/admin/stats/`, i18n 15 langues. Plan : `docs/plan-admin-marketing-statistiques-coupons.md` | --- diff --git a/memento-note/app/(admin)/admin/marketing/marketing-admin-client.tsx b/memento-note/app/(admin)/admin/marketing/marketing-admin-client.tsx new file mode 100644 index 00000000..8e150662 --- /dev/null +++ b/memento-note/app/(admin)/admin/marketing/marketing-admin-client.tsx @@ -0,0 +1,76 @@ +'use client' + +import { Mail } from 'lucide-react' +import { useLanguage } from '@/lib/i18n' + +const COLUMN_KEYS = [ + 'name', + 'goal', + 'audience', + 'language', + 'offer', + 'status', + 'scheduledAt', + 'accepted', + 'delivered', + 'paidConversions', +] as const + +export function MarketingAdminClient() { + const { t } = useLanguage() + + return ( +
+
+
+
+ +

+ {t('admin.marketing.title')} +

+
+

+ {t('admin.marketing.description')} +

+
+ +
+ +
+ + + + + {COLUMN_KEYS.map((key) => ( + + ))} + + + + + + + +
{t('admin.marketing.title')}
+ {t(`admin.marketing.columns.${key}`)} +
+

{t('admin.marketing.emptyTitle')}

+

+ {t('admin.marketing.emptyBody')} +

+
+
+
+ ) +} diff --git a/memento-note/app/(admin)/admin/marketing/page.tsx b/memento-note/app/(admin)/admin/marketing/page.tsx new file mode 100644 index 00000000..6a7e222f --- /dev/null +++ b/memento-note/app/(admin)/admin/marketing/page.tsx @@ -0,0 +1,7 @@ +import { MarketingAdminClient } from './marketing-admin-client' + +export const dynamic = 'force-dynamic' + +export default function AdminMarketingPage() { + return +} diff --git a/memento-note/app/(admin)/admin/stats/page.tsx b/memento-note/app/(admin)/admin/stats/page.tsx new file mode 100644 index 00000000..39acbbe0 --- /dev/null +++ b/memento-note/app/(admin)/admin/stats/page.tsx @@ -0,0 +1,7 @@ +import { StatsAdminClient } from './stats-admin-client' + +export const dynamic = 'force-dynamic' + +export default function AdminStatsPage() { + return +} diff --git a/memento-note/app/(admin)/admin/stats/stats-admin-client.tsx b/memento-note/app/(admin)/admin/stats/stats-admin-client.tsx new file mode 100644 index 00000000..75f4327e --- /dev/null +++ b/memento-note/app/(admin)/admin/stats/stats-admin-client.tsx @@ -0,0 +1,54 @@ +'use client' + +import { BarChart3 } from 'lucide-react' +import { useLanguage } from '@/lib/i18n' + +const SECTIONS = [ + { id: 'revenue', titleKey: 'admin.stats.sectionRevenue' }, + { id: 'subscriptions', titleKey: 'admin.stats.sectionSubscriptions' }, + { id: 'usage', titleKey: 'admin.stats.sectionUsage' }, + { id: 'campaigns', titleKey: 'admin.stats.sectionCampaigns' }, +] as const + +export function StatsAdminClient() { + const { t } = useLanguage() + + return ( +
+
+
+ +

+ {t('admin.stats.title')} +

+
+

+ {t('admin.stats.description')} +

+

+ {t('admin.stats.lastSyncUnknown')} · {t('admin.stats.timezoneNote')} +

+
+ +
+ {SECTIONS.map((section) => ( +
+

+ {t(section.titleKey)} +

+

+ {t('admin.stats.unavailable')} +

+

+ {t('admin.stats.unavailableBody')} +

+
+ ))} +
+
+ ) +} diff --git a/memento-note/components/admin-sidebar.tsx b/memento-note/components/admin-sidebar.tsx index c3b2cfe7..7639b43d 100644 --- a/memento-note/components/admin-sidebar.tsx +++ b/memento-note/components/admin-sidebar.tsx @@ -14,6 +14,8 @@ import { LogOut, Brain, CreditCard, + Mail, + BarChart3, } from 'lucide-react' import { cn } from '@/lib/utils' import { useLanguage } from '@/lib/i18n' @@ -48,6 +50,16 @@ const ADMIN_NAV_ITEMS = [ href: '/admin/billing', icon: CreditCard, }, + { + titleKey: 'admin.sidebar.marketing', + href: '/admin/marketing', + icon: Mail, + }, + { + titleKey: 'admin.sidebar.stats', + href: '/admin/stats', + icon: BarChart3, + }, { titleKey: 'admin.sidebar.published', href: '/admin/published', diff --git a/memento-note/locales/ar.json b/memento-note/locales/ar.json index eb81d76c..44e05172 100644 --- a/memento-note/locales/ar.json +++ b/memento-note/locales/ar.json @@ -1527,7 +1527,42 @@ "agents": "الوكلاء", "settings": "الإعدادات", "billing": "الفوترة والحصص", - "published": "الصفحات المنشورة" + "published": "الصفحات المنشورة", + "marketing": "التسويق", + "stats": "الإحصاءات" + }, + "marketing": { + "title": "التسويق", + "description": "إعداد الحملات واختيار المستلمين وإرفاق عرض. الإرسال غير مفعّل بعد.", + "emptyTitle": "لا حملات بعد", + "emptyBody": "عندما توجد حملات ستظهر في هذا الجدول مع الجمهور والعرض والحالة. هذه الصفحة لا ترسل أي بريد.", + "createCampaign": "إنشاء حملة", + "createDisabledHint": "إرسال الحملات غير مفعّل في هذا الإصدار.", + "columns": { + "name": "الاسم الداخلي", + "goal": "الغرض", + "audience": "المستلمون", + "language": "اللغة", + "offer": "العرض", + "status": "الحالة", + "scheduledAt": "التاريخ المخطط", + "accepted": "قبلتها خدمة الإرسال", + "delivered": "تم التسليم", + "paidConversions": "تحويلات مدفوعة" + } + }, + "stats": { + "title": "الإحصاءات", + "description": "الإيرادات والاشتراكات ونشاط المنتج ونتائج الحملات. تظهر الأرقام فقط من مصدر موثوق.", + "unavailable": "غير متاح بعد", + "unavailableBody": "هذا القسم غير مربوط بقياس بعد. لا يظهر صفرًا لأن الصفر يعني أن شيئًا لم يحدث.", + "lastSyncUnknown": "آخر تحديث: غير معروف", + "sectionRevenue": "الإيرادات", + "sectionSubscriptions": "الاشتراكات", + "sectionUsage": "الاستخدام", + "sectionCampaigns": "الحملات", + "periodLabel": "الفترة", + "timezoneNote": "ستتبع التواريخ المنطقة الزمنية للمشرف عندما تتوفر القياسات." }, "metrics": { "vsLastPeriod": "vs الفترة السابقة" diff --git a/memento-note/locales/de.json b/memento-note/locales/de.json index b1e58804..161a02c5 100644 --- a/memento-note/locales/de.json +++ b/memento-note/locales/de.json @@ -1527,7 +1527,42 @@ "agents": "Agenten", "settings": "Einstellungen", "billing": "Abrechnung & Kontingente", - "published": "Veröffentlichte Seiten" + "published": "Veröffentlichte Seiten", + "marketing": "Marketing", + "stats": "Statistiken" + }, + "marketing": { + "title": "Marketing", + "description": "Kampagnen vorbereiten, Empfänger wählen und ein Angebot anhängen. Der Versand ist noch nicht aktiv.", + "emptyTitle": "Noch keine Kampagnen", + "emptyBody": "Sobald Kampagnen existieren, erscheinen sie in dieser Tabelle mit Empfängern, Angebot und Status. Diese Seite versendet keine E-Mails.", + "createCampaign": "Kampagne erstellen", + "createDisabledHint": "Der Kampagnenversand ist in dieser Version nicht aktiv.", + "columns": { + "name": "Interner Name", + "goal": "Zweck", + "audience": "Empfänger", + "language": "Sprache", + "offer": "Angebot", + "status": "Status", + "scheduledAt": "Geplantes Datum", + "accepted": "Vom Versanddienst angenommen", + "delivered": "Zugestellt", + "paidConversions": "Bezahlte Abschlüsse" + } + }, + "stats": { + "title": "Statistiken", + "description": "Umsatz, Abonnements, Produktnutzung und Kampagnenergebnisse. Zahlen erscheinen nur aus einer zuverlässigen Quelle.", + "unavailable": "Noch nicht verfügbar", + "unavailableBody": "Dieser Bereich ist noch nicht mit einer Messung verbunden. Es wird keine Null angezeigt, weil Null „nichts ist passiert“ bedeuten würde.", + "lastSyncUnknown": "Letzte Aktualisierung: unbekannt", + "sectionRevenue": "Umsatz", + "sectionSubscriptions": "Abonnements", + "sectionUsage": "Nutzung", + "sectionCampaigns": "Kampagnen", + "periodLabel": "Zeitraum", + "timezoneNote": "Daten folgen der Zeitzone der Verwaltung, sobald Messungen vorliegen." }, "metrics": { "vsLastPeriod": "vs vorheriger Zeitraum" diff --git a/memento-note/locales/en.json b/memento-note/locales/en.json index ca8cea88..71d52149 100644 --- a/memento-note/locales/en.json +++ b/memento-note/locales/en.json @@ -1598,7 +1598,42 @@ "chat": "AI Chat", "lab": "The Lab (Ideas)", "agents": "Agents", - "settings": "Settings" + "settings": "Settings", + "marketing": "Marketing", + "stats": "Statistics" + }, + "marketing": { + "title": "Marketing", + "description": "Prepare campaigns, choose who receives them, and attach an offer. Sending is not enabled yet.", + "emptyTitle": "No campaigns yet", + "emptyBody": "When campaigns exist, they will appear in this table with their audience, offer, and status. Nothing is sent from this page.", + "createCampaign": "Create a campaign", + "createDisabledHint": "Campaign sending is not enabled in this version.", + "columns": { + "name": "Internal name", + "goal": "Purpose", + "audience": "Audience", + "language": "Language", + "offer": "Offer", + "status": "Status", + "scheduledAt": "Scheduled date", + "accepted": "Accepted by the mail service", + "delivered": "Delivered", + "paidConversions": "Paid conversions" + } + }, + "stats": { + "title": "Statistics", + "description": "Revenue, subscriptions, product activity, and campaign results. Figures appear only from a reliable source.", + "unavailable": "Not available yet", + "unavailableBody": "This section is not connected to a measurement source. It does not show zero, because zero would mean “nothing happened”.", + "lastSyncUnknown": "Last update: unknown", + "sectionRevenue": "Revenue", + "sectionSubscriptions": "Subscriptions", + "sectionUsage": "Usage", + "sectionCampaigns": "Campaigns", + "periodLabel": "Period", + "timezoneNote": "Dates follow the administrator’s time zone once measurements exist." }, "metrics": { "vsLastPeriod": "vs last period" diff --git a/memento-note/locales/es.json b/memento-note/locales/es.json index 7944540a..aba9786f 100644 --- a/memento-note/locales/es.json +++ b/memento-note/locales/es.json @@ -1527,7 +1527,42 @@ "agents": "Agentes", "settings": "Configuración", "billing": "Facturación y cuotas", - "published": "Páginas publicadas" + "published": "Páginas publicadas", + "marketing": "Marketing", + "stats": "Estadísticas" + }, + "marketing": { + "title": "Marketing", + "description": "Preparar campañas, elegir destinatarios y adjuntar una oferta. El envío aún no está activado.", + "emptyTitle": "Todavía no hay campañas", + "emptyBody": "Cuando existan campañas, aparecerán en esta tabla con el público, la oferta y el estado. Esta página no envía correos.", + "createCampaign": "Crear una campaña", + "createDisabledHint": "El envío de campañas no está activado en esta versión.", + "columns": { + "name": "Nombre interno", + "goal": "Objetivo", + "audience": "Destinatarios", + "language": "Idioma", + "offer": "Oferta", + "status": "Estado", + "scheduledAt": "Fecha prevista", + "accepted": "Aceptados por el servicio de envío", + "delivered": "Entregados", + "paidConversions": "Conversiones de pago" + } + }, + "stats": { + "title": "Estadísticas", + "description": "Ingresos, suscripciones, actividad del producto y resultados de campañas. Las cifras solo aparecen si vienen de una fuente fiable.", + "unavailable": "Aún no disponible", + "unavailableBody": "Esta parte no está conectada a una medición. No muestra un cero, porque un cero significaría que no ocurrió nada.", + "lastSyncUnknown": "Última actualización: desconocida", + "sectionRevenue": "Ingresos", + "sectionSubscriptions": "Suscripciones", + "sectionUsage": "Uso", + "sectionCampaigns": "Campañas", + "periodLabel": "Periodo", + "timezoneNote": "Las fechas seguirán la zona horaria del administrador cuando haya mediciones." }, "metrics": { "vsLastPeriod": "vs período anterior" diff --git a/memento-note/locales/fa.json b/memento-note/locales/fa.json index 70b08a4f..745bbeec 100644 --- a/memento-note/locales/fa.json +++ b/memento-note/locales/fa.json @@ -1527,7 +1527,42 @@ "agents": "عامل‌ها", "settings": "تنظیمات", "billing": "صورتحساب و سهمیه‌ها", - "published": "صفحات منتشر شده" + "published": "صفحات منتشر شده", + "marketing": "بازاریابی", + "stats": "آمار" + }, + "marketing": { + "title": "بازاریابی", + "description": "آماده‌سازی کارزارها، انتخاب گیرندگان و پیوست کردن پیشنهاد. ارسال هنوز فعال نیست.", + "emptyTitle": "هنوز کارزاری نیست", + "emptyBody": "وقتی کارزارها ساخته شوند در این جدول با مخاطب، پیشنهاد و وضعیت دیده می‌شوند. این صفحه نامه‌ای نمی‌فرستد.", + "createCampaign": "ساخت کارزار", + "createDisabledHint": "ارسال کارزار در این نسخه فعال نیست.", + "columns": { + "name": "نام داخلی", + "goal": "هدف", + "audience": "گیرندگان", + "language": "زبان", + "offer": "پیشنهاد", + "status": "وضعیت", + "scheduledAt": "تاریخ برنامه‌ریزی‌شده", + "accepted": "پذیرفته‌شده توسط سرویس ارسال", + "delivered": "تحویل‌شده", + "paidConversions": "تبدیل‌های پرداخت‌شده" + } + }, + "stats": { + "title": "آمار", + "description": "درآمد، اشتراک‌ها، فعالیت محصول و نتیجهٔ کارزارها. عدد فقط از منبع قابل اعتماد نشان داده می‌شود.", + "unavailable": "هنوز در دسترس نیست", + "unavailableBody": "این بخش هنوز به اندازه‌گیری وصل نیست. صفر نشان داده نمی‌شود، چون صفر یعنی هیچ اتفاقی نیفتاده.", + "lastSyncUnknown": "آخرین به‌روزرسانی: نامعلوم", + "sectionRevenue": "درآمد", + "sectionSubscriptions": "اشتراک‌ها", + "sectionUsage": "استفاده", + "sectionCampaigns": "کارزارها", + "periodLabel": "بازه", + "timezoneNote": "وقتی اندازه‌گیری باشد، تاریخ‌ها منطقهٔ زمانی مدیر را دنبال می‌کنند." }, "metrics": { "vsLastPeriod": "در مقایسه با دوره قبل" diff --git a/memento-note/locales/fr.json b/memento-note/locales/fr.json index 49b3f436..b66314b9 100644 --- a/memento-note/locales/fr.json +++ b/memento-note/locales/fr.json @@ -1604,7 +1604,42 @@ "chat": "Chat IA", "lab": "Le Lab (Idées)", "agents": "Agents", - "settings": "Paramètres" + "settings": "Paramètres", + "marketing": "Marketing", + "stats": "Statistiques" + }, + "marketing": { + "title": "Marketing", + "description": "Préparer des campagnes, choisir les destinataires et joindre une offre. Les envois ne sont pas encore activés.", + "emptyTitle": "Aucune campagne pour le moment", + "emptyBody": "Quand des campagnes existeront, elles apparaîtront dans ce tableau avec le public, l’offre et l’état. Cette page n’envoie aucun courriel.", + "createCampaign": "Créer une campagne", + "createDisabledHint": "L’envoi des campagnes n’est pas activé dans cette version.", + "columns": { + "name": "Nom interne", + "goal": "Objectif", + "audience": "Destinataires", + "language": "Langue", + "offer": "Offre", + "status": "État", + "scheduledAt": "Date prévue", + "accepted": "Acceptés par le service d’envoi", + "delivered": "Délivrés", + "paidConversions": "Conversions payées" + } + }, + "stats": { + "title": "Statistiques", + "description": "Revenus, abonnements, activité du produit et résultats des campagnes. Les chiffres n’apparaissent que s’ils viennent d’une source fiable.", + "unavailable": "Pas encore disponible", + "unavailableBody": "Cette partie n’est pas encore reliée à une mesure. Elle n’affiche pas zéro : un zéro voudrait dire « rien n’est arrivé ».", + "lastSyncUnknown": "Dernière mise à jour : inconnue", + "sectionRevenue": "Revenus", + "sectionSubscriptions": "Abonnements", + "sectionUsage": "Utilisation", + "sectionCampaigns": "Campagnes", + "periodLabel": "Période", + "timezoneNote": "Les dates suivront le fuseau de l’administrateur dès qu’il y aura des mesures." }, "metrics": { "vsLastPeriod": "vs période précédente" diff --git a/memento-note/locales/hi.json b/memento-note/locales/hi.json index c8ee5fc0..cdb7dc08 100644 --- a/memento-note/locales/hi.json +++ b/memento-note/locales/hi.json @@ -1527,7 +1527,42 @@ "agents": "एजेंट", "settings": "सेटिंग्स", "billing": "बिलिंग और कोटा", - "published": "प्रकाशित पृष्ठ" + "published": "प्रकाशित पृष्ठ", + "marketing": "मार्केटिंग", + "stats": "आँकड़े" + }, + "marketing": { + "title": "मार्केटिंग", + "description": "अभियान तैयार करें, प्राप्तकर्ता चुनें और एक प्रस्ताव जोड़ें। भेजना अभी चालू नहीं है।", + "emptyTitle": "अभी कोई अभियान नहीं", + "emptyBody": "जब अभियान होंगे, वे इस तालिका में दर्शकों, प्रस्ताव और स्थिति के साथ दिखेंगे। यह पृष्ठ कोई पत्र नहीं भेजता।", + "createCampaign": "अभियान बनाएँ", + "createDisabledHint": "इस संस्करण में अभियान भेजना चालू नहीं है।", + "columns": { + "name": "आंतरिक नाम", + "goal": "उद्देश्य", + "audience": "प्राप्तकर्ता", + "language": "भाषा", + "offer": "प्रस्ताव", + "status": "स्थिति", + "scheduledAt": "नियोजित तिथि", + "accepted": "भेजने वाली सेवा ने स्वीकार किया", + "delivered": "पहुँचाया गया", + "paidConversions": "भुगतान वाली रूपांतरण" + } + }, + "stats": { + "title": "आँकड़े", + "description": "आय, सदस्यता, उत्पाद गतिविधि और अभियान परिणाम। संख्याएँ केवल विश्वसनीय स्रोत से दिखती हैं।", + "unavailable": "अभी उपलब्ध नहीं", + "unavailableBody": "यह भाग अभी माप से जुड़ा नहीं है। शून्य नहीं दिखाया जाता, क्योंकि शून्य का अर्थ होता “कुछ नहीं हुआ”।", + "lastSyncUnknown": "अंतिम अपडेट: अज्ञात", + "sectionRevenue": "आय", + "sectionSubscriptions": "सदस्यता", + "sectionUsage": "उपयोग", + "sectionCampaigns": "अभियान", + "periodLabel": "अवधि", + "timezoneNote": "माप आने पर तिथियाँ व्यवस्थापक के समय क्षेत्र का पालन करेंगी।" }, "metrics": { "vsLastPeriod": "vs पिछली अवधि" diff --git a/memento-note/locales/it.json b/memento-note/locales/it.json index 0f2e31ed..05d2aa28 100644 --- a/memento-note/locales/it.json +++ b/memento-note/locales/it.json @@ -1527,7 +1527,42 @@ "agents": "Agenti", "settings": "Impostazioni", "billing": "Fatturazione e quote", - "published": "Pagine pubblicate" + "published": "Pagine pubblicate", + "marketing": "Marketing", + "stats": "Statistiche" + }, + "marketing": { + "title": "Marketing", + "description": "Preparare campagne, scegliere i destinatari e allegare un’offerta. L’invio non è ancora attivo.", + "emptyTitle": "Nessuna campagna per ora", + "emptyBody": "Quando ci saranno campagne, compariranno in questa tabella con destinatari, offerta e stato. Questa pagina non invia e-mail.", + "createCampaign": "Crea una campagna", + "createDisabledHint": "L’invio delle campagne non è attivo in questa versione.", + "columns": { + "name": "Nome interno", + "goal": "Obiettivo", + "audience": "Destinatari", + "language": "Lingua", + "offer": "Offerta", + "status": "Stato", + "scheduledAt": "Data prevista", + "accepted": "Accettati dal servizio di invio", + "delivered": "Consegnati", + "paidConversions": "Conversioni a pagamento" + } + }, + "stats": { + "title": "Statistiche", + "description": "Ricavi, abbonamenti, attività del prodotto e risultati delle campagne. Le cifre appaiono solo da una fonte affidabile.", + "unavailable": "Non ancora disponibile", + "unavailableBody": "Questa sezione non è collegata a una misura. Non mostra zero: zero vorrebbe dire che non è successo nulla.", + "lastSyncUnknown": "Ultimo aggiornamento: sconosciuto", + "sectionRevenue": "Ricavi", + "sectionSubscriptions": "Abbonamenti", + "sectionUsage": "Utilizzo", + "sectionCampaigns": "Campagne", + "periodLabel": "Periodo", + "timezoneNote": "Le date seguiranno il fuso dell’amministratore quando ci saranno misure." }, "metrics": { "vsLastPeriod": "vs periodo precedente" diff --git a/memento-note/locales/ja.json b/memento-note/locales/ja.json index f9eabc7b..a71b5a93 100644 --- a/memento-note/locales/ja.json +++ b/memento-note/locales/ja.json @@ -1527,7 +1527,42 @@ "agents": "エージェント", "settings": "設定", "billing": "請求とクォータ", - "published": "公開済みページ" + "published": "公開済みページ", + "marketing": "マーケティング", + "stats": "統計" + }, + "marketing": { + "title": "マーケティング", + "description": "キャンペーンを用意し、宛先を選び、特典を付けます。送信はまだ有効ではありません。", + "emptyTitle": "キャンペーンはまだありません", + "emptyBody": "キャンペーンができると、宛先・特典・状態とともにこの表に出ます。このページからメールは送られません。", + "createCampaign": "キャンペーンを作成", + "createDisabledHint": "この版ではキャンペーン送信は有効ではありません。", + "columns": { + "name": "内部名", + "goal": "目的", + "audience": "宛先", + "language": "言語", + "offer": "特典", + "status": "状態", + "scheduledAt": "予定日", + "accepted": "送信サービスが受け付け", + "delivered": "配達済み", + "paidConversions": "有料の成果" + } + }, + "stats": { + "title": "統計", + "description": "売上、定期契約、製品の利用、キャンペーン結果。数字は信頼できる出典があるときだけ出ます。", + "unavailable": "まだ利用できません", + "unavailableBody": "この欄はまだ計測とつながっていません。ゼロは出しません。ゼロは「何も起きなかった」という意味になるためです。", + "lastSyncUnknown": "最終更新:不明", + "sectionRevenue": "売上", + "sectionSubscriptions": "定期契約", + "sectionUsage": "利用", + "sectionCampaigns": "キャンペーン", + "periodLabel": "期間", + "timezoneNote": "計測が始まると、日付は管理者の時間帯に従います。" }, "metrics": { "vsLastPeriod": "前回期間比" diff --git a/memento-note/locales/ko.json b/memento-note/locales/ko.json index bbab3fe9..c5d3f109 100644 --- a/memento-note/locales/ko.json +++ b/memento-note/locales/ko.json @@ -1527,7 +1527,42 @@ "agents": "에이전트", "settings": "설정", "billing": "결제 및 할당량", - "published": "게시된 페이지" + "published": "게시된 페이지", + "marketing": "마케팅", + "stats": "통계" + }, + "marketing": { + "title": "마케팅", + "description": "캠페인을 준비하고 받는 사람을 고르며 혜택을 붙입니다. 보내기는 아직 켜져 있지 않습니다.", + "emptyTitle": "아직 캠페인이 없습니다", + "emptyBody": "캠페인이 생기면 대상, 혜택, 상태와 함께 이 표에 나타납니다. 이 페이지에서는 메일을 보내지 않습니다.", + "createCampaign": "캠페인 만들기", + "createDisabledHint": "이 버전에서는 캠페인 보내기가 켜져 있지 않습니다.", + "columns": { + "name": "내부 이름", + "goal": "목적", + "audience": "받는 사람", + "language": "언어", + "offer": "혜택", + "status": "상태", + "scheduledAt": "예정일", + "accepted": "발송 서비스가 수락", + "delivered": "전달됨", + "paidConversions": "유료 전환" + } + }, + "stats": { + "title": "통계", + "description": "매출, 구독, 제품 이용, 캠페인 결과. 숫자는 믿을 수 있는 출처가 있을 때만 나옵니다.", + "unavailable": "아직 없음", + "unavailableBody": "이 부분은 아직 측정과 연결되지 않았습니다. 0을 보여 주지 않습니다. 0은 “아무 일도 없었다”는 뜻이기 때문입니다.", + "lastSyncUnknown": "마지막 업데이트: 알 수 없음", + "sectionRevenue": "매출", + "sectionSubscriptions": "구독", + "sectionUsage": "이용", + "sectionCampaigns": "캠페인", + "periodLabel": "기간", + "timezoneNote": "측정이 생기면 날짜는 관리자 시간대를 따릅니다." }, "metrics": { "vsLastPeriod": "이전 기간 대비" diff --git a/memento-note/locales/nl.json b/memento-note/locales/nl.json index c310ab4b..50bfc11d 100644 --- a/memento-note/locales/nl.json +++ b/memento-note/locales/nl.json @@ -1527,7 +1527,42 @@ "agents": "Agenten", "settings": "Instellingen", "billing": "Facturering & quota's", - "published": "Gepubliceerde pagina's" + "published": "Gepubliceerde pagina's", + "marketing": "Marketing", + "stats": "Statistieken" + }, + "marketing": { + "title": "Marketing", + "description": "Campagnes voorbereiden, ontvangers kiezen en een aanbieding toevoegen. Verzenden is nog niet ingeschakeld.", + "emptyTitle": "Nog geen campagnes", + "emptyBody": "Zodra er campagnes zijn, verschijnen ze in deze tabel met publiek, aanbieding en status. Deze pagina verstuurt geen e-mail.", + "createCampaign": "Campagne maken", + "createDisabledHint": "Het verzenden van campagnes is in deze versie niet actief.", + "columns": { + "name": "Interne naam", + "goal": "Doel", + "audience": "Ontvangers", + "language": "Taal", + "offer": "Aanbieding", + "status": "Status", + "scheduledAt": "Geplande datum", + "accepted": "Geaccepteerd door de verzenddienst", + "delivered": "Bezorgd", + "paidConversions": "Betaalde conversies" + } + }, + "stats": { + "title": "Statistieken", + "description": "Omzet, abonnementen, productgebruik en campagneresultaten. Cijfers verschijnen alleen uit een betrouwbare bron.", + "unavailable": "Nog niet beschikbaar", + "unavailableBody": "Dit deel is nog niet gekoppeld aan een meting. Er staat geen nul, omdat nul zou betekenen dat er niets is gebeurd.", + "lastSyncUnknown": "Laatste update: onbekend", + "sectionRevenue": "Omzet", + "sectionSubscriptions": "Abonnementen", + "sectionUsage": "Gebruik", + "sectionCampaigns": "Campagnes", + "periodLabel": "Periode", + "timezoneNote": "Datums volgen de tijdzone van de beheerder zodra er metingen zijn." }, "metrics": { "vsLastPeriod": "vs vorige periode" diff --git a/memento-note/locales/pl.json b/memento-note/locales/pl.json index 35165a2e..6204ed99 100644 --- a/memento-note/locales/pl.json +++ b/memento-note/locales/pl.json @@ -1527,7 +1527,42 @@ "agents": "Agenci", "settings": "Ustawienia", "billing": "Rozliczenia i limity", - "published": "Opublikowane strony" + "published": "Opublikowane strony", + "marketing": "Marketing", + "stats": "Statystyki" + }, + "marketing": { + "title": "Marketing", + "description": "Przygotuj kampanie, wybierz odbiorców i dołącz ofertę. Wysyłka nie jest jeszcze włączona.", + "emptyTitle": "Brak kampanii", + "emptyBody": "Gdy kampanie powstaną, pojawią się w tej tabeli z odbiorcami, ofertą i statusem. Ta strona nie wysyła wiadomości.", + "createCampaign": "Utwórz kampanię", + "createDisabledHint": "Wysyłka kampanii nie jest włączona w tej wersji.", + "columns": { + "name": "Nazwa wewnętrzna", + "goal": "Cel", + "audience": "Odbiorcy", + "language": "Język", + "offer": "Oferta", + "status": "Status", + "scheduledAt": "Planowana data", + "accepted": "Przyjęte przez usługę wysyłki", + "delivered": "Dostarczone", + "paidConversions": "Płatne konwersje" + } + }, + "stats": { + "title": "Statystyki", + "description": "Przychody, subskrypcje, aktywność produktu i wyniki kampanii. Liczby pojawiają się tylko z wiarygodnego źródła.", + "unavailable": "Jeszcze niedostępne", + "unavailableBody": "Ta część nie jest jeszcze połączona z pomiarem. Nie pokazuje zera, bo zero oznaczałoby, że nic się nie stało.", + "lastSyncUnknown": "Ostatnia aktualizacja: nieznana", + "sectionRevenue": "Przychody", + "sectionSubscriptions": "Subskrypcje", + "sectionUsage": "Użycie", + "sectionCampaigns": "Kampanie", + "periodLabel": "Okres", + "timezoneNote": "Daty będą zgodne ze strefą administratora, gdy pojawią się pomiary." }, "metrics": { "vsLastPeriod": "względem poprzedniego okresu" diff --git a/memento-note/locales/pt.json b/memento-note/locales/pt.json index a526db4d..9defcbe4 100644 --- a/memento-note/locales/pt.json +++ b/memento-note/locales/pt.json @@ -1527,7 +1527,42 @@ "agents": "Agentes", "settings": "Configurações", "billing": "Faturação e cotas", - "published": "Páginas publicadas" + "published": "Páginas publicadas", + "marketing": "Marketing", + "stats": "Estatísticas" + }, + "marketing": { + "title": "Marketing", + "description": "Preparar campanhas, escolher destinatários e anexar uma oferta. O envio ainda não está ativo.", + "emptyTitle": "Ainda não há campanhas", + "emptyBody": "Quando existirem campanhas, elas aparecerão nesta tabela com o público, a oferta e o estado. Esta página não envia e-mails.", + "createCampaign": "Criar uma campanha", + "createDisabledHint": "O envio de campanhas não está ativo nesta versão.", + "columns": { + "name": "Nome interno", + "goal": "Objetivo", + "audience": "Destinatários", + "language": "Idioma", + "offer": "Oferta", + "status": "Estado", + "scheduledAt": "Data prevista", + "accepted": "Aceites pelo serviço de envio", + "delivered": "Entregues", + "paidConversions": "Conversões pagas" + } + }, + "stats": { + "title": "Estatísticas", + "description": "Receita, assinaturas, atividade do produto e resultados das campanhas. Os números só aparecem a partir de uma fonte fiável.", + "unavailable": "Ainda não disponível", + "unavailableBody": "Esta parte ainda não está ligada a uma medição. Não mostra zero, porque zero significaria que nada aconteceu.", + "lastSyncUnknown": "Última atualização: desconhecida", + "sectionRevenue": "Receita", + "sectionSubscriptions": "Assinaturas", + "sectionUsage": "Utilização", + "sectionCampaigns": "Campanhas", + "periodLabel": "Período", + "timezoneNote": "As datas seguirão o fuso do administrador quando houver medições." }, "metrics": { "vsLastPeriod": "em relação ao período anterior" diff --git a/memento-note/locales/ru.json b/memento-note/locales/ru.json index fff7b2de..86ab21b8 100644 --- a/memento-note/locales/ru.json +++ b/memento-note/locales/ru.json @@ -1527,7 +1527,42 @@ "agents": "Агенты", "settings": "Настройки", "billing": "Биллинг и квоты", - "published": "Опубликованные страницы" + "published": "Опубликованные страницы", + "marketing": "Маркетинг", + "stats": "Статистика" + }, + "marketing": { + "title": "Маркетинг", + "description": "Готовить рассылки, выбирать получателей и прикреплять предложение. Отправка ещё не включена.", + "emptyTitle": "Пока нет кампаний", + "emptyBody": "Когда кампании появятся, они будут в этой таблице с аудиторией, предложением и статусом. Эта страница не отправляет письма.", + "createCampaign": "Создать кампанию", + "createDisabledHint": "Отправка кампаний в этой версии не включена.", + "columns": { + "name": "Внутреннее имя", + "goal": "Цель", + "audience": "Получатели", + "language": "Язык", + "offer": "Предложение", + "status": "Статус", + "scheduledAt": "Запланированная дата", + "accepted": "Принято службой отправки", + "delivered": "Доставлено", + "paidConversions": "Оплаченные конверсии" + } + }, + "stats": { + "title": "Статистика", + "description": "Выручка, подписки, активность в продукте и результаты кампаний. Цифры появляются только из надёжного источника.", + "unavailable": "Пока недоступно", + "unavailableBody": "Этот раздел ещё не связан с измерением. Ноль не показывается: ноль означал бы, что ничего не произошло.", + "lastSyncUnknown": "Последнее обновление: неизвестно", + "sectionRevenue": "Выручка", + "sectionSubscriptions": "Подписки", + "sectionUsage": "Использование", + "sectionCampaigns": "Кампании", + "periodLabel": "Период", + "timezoneNote": "Даты будут в часовом поясе администратора, когда появятся измерения." }, "metrics": { "vsLastPeriod": "к предыдущему периоду" diff --git a/memento-note/locales/zh.json b/memento-note/locales/zh.json index 6ce6ebc6..12637e0a 100644 --- a/memento-note/locales/zh.json +++ b/memento-note/locales/zh.json @@ -1527,7 +1527,42 @@ "agents": "代理", "settings": "设置", "billing": "计费与配额", - "published": "已发布页面" + "published": "已发布页面", + "marketing": "营销", + "stats": "统计" + }, + "marketing": { + "title": "营销", + "description": "准备活动、选择收件人并附上优惠。发送尚未启用。", + "emptyTitle": "还没有活动", + "emptyBody": "有活动后,会显示在此表中,包含受众、优惠和状态。本页不会发送邮件。", + "createCampaign": "创建活动", + "createDisabledHint": "此版本尚未启用活动发送。", + "columns": { + "name": "内部名称", + "goal": "目的", + "audience": "收件人", + "language": "语言", + "offer": "优惠", + "status": "状态", + "scheduledAt": "计划日期", + "accepted": "已被发送服务接受", + "delivered": "已送达", + "paidConversions": "付费转化" + } + }, + "stats": { + "title": "统计", + "description": "收入、订阅、产品使用与活动结果。只有来自可靠来源的数字才会显示。", + "unavailable": "尚不可用", + "unavailableBody": "此部分尚未接入统计来源。不会显示零,因为零表示“什么都没发生”。", + "lastSyncUnknown": "上次更新:未知", + "sectionRevenue": "收入", + "sectionSubscriptions": "订阅", + "sectionUsage": "使用", + "sectionCampaigns": "活动", + "periodLabel": "时间范围", + "timezoneNote": "有统计后,日期将使用管理员时区。" }, "metrics": { "vsLastPeriod": "与上一周期相比"