{
  "date": "20 octobre 2026",
  "timezone": "America/Toronto",
  "application": "raca_ssr",
  "url": "http://127.0.0.1:8088",
  "vite": "http://127.0.0.1:5173",
  "baseline_tests": {
    "passed": 58,
    "skipped": 2,
    "assertions": 289
  },
  "audit_probes": {
    "passed": 14,
    "assertions": 39,
    "database": "SQLite mémoire"
  },
  "counts": {
    "P0": 2,
    "P1": 15,
    "P2": 29,
    "P3": 5
  },
  "findings": [
    {
      "id": "RACA-01",
      "priority": "P0",
      "area": "Confidentialité",
      "title": "Le catalogue public expose les données complètes des entreprises et contacts",
      "proof": "Test isolé + code",
      "observation": "La réponse Inertia contient le courriel et l’adresse même lorsqu’ils sont masqués, ainsi que les objets contacts complets, dont des notes internes. Le DTO PublicEntrepriseResource existe mais le contrôleur transmet les modèles bruts.",
      "repro": "La sonde crée une entreprise avec courriel masqué et une note de contact interne, puis vérifie leur présence dans les props publiques. Aucun compte visiteur nécessaire.",
      "fix": "Utiliser une projection publique explicite partout : catalogue, aperçu des destinataires et fiches. Exclure notes, courriels secondaires, adresses privées et champs internes; appliquer chaque visibilité côté serveur.",
      "source": "app/Http/Controllers/Public/PublicPageController.php",
      "line": 62,
      "evidence": ""
    },
    {
      "id": "RACA-02",
      "priority": "P0",
      "area": "Permissions",
      "title": "La permission manageUsers permet de créer un administrateur",
      "proof": "Test isolé",
      "observation": "Un utilisateur non administrateur ayant manageUsers peut transmettre le rôle admin lors de la création. Le contrôleur synchronise tous les role_ids reçus sans restriction supplémentaire.",
      "repro": "La sonde crée un délégué avec manageUsers, poste un nouvel utilisateur avec le rôle admin et vérifie que le compte créé est administrateur. Cela a été fait uniquement en SQLite mémoire.",
      "fix": "Réserver l’attribution des rôles privilégiés à un administrateur. Restreindre les rôles assignables par acteur, puis couvrir création et modification avec des tests d’autorisation.",
      "source": "app/Http/Controllers/Admin/UserController.php",
      "line": 42,
      "evidence": ""
    },
    {
      "id": "RACA-03",
      "priority": "P1",
      "area": "Inscription",
      "title": "Un code postal du référentiel est refusé",
      "proof": "Chrome + test isolé",
      "observation": "G0R 1B0 est enregistré dans les codes postaux valides et refusé à l’inscription. La saisie est normalisée sans espaces, mais pas les valeurs du référentiel. Les sept valeurs locales contiennent un espace.",
      "repro": "Inscription entreprise : renseigner les champs, Bellechasse / Saint-Anselme, un produit et G0R 1B0. Le message « Ce code postal n’est pas desservi » apparaît.",
      "fix": "Normaliser les deux côtés avec la même règle. Décider si le référentiel contient des préfixes ou des codes complets et tester les deux formats autorisés.",
      "source": "app/Http/Requests/PublicRegistrationRequest.php",
      "line": 54,
      "evidence": "14-inscription-code-postal-refuse.png"
    },
    {
      "id": "RACA-04",
      "priority": "P1",
      "area": "Inscription",
      "title": "L’inscription acheteur échoue sur des champs entreprise masqués",
      "proof": "Chrome + test isolé",
      "observation": "Le formulaire acheteur conserve produit_ids=[]; la règle min:1 s’applique même lorsque required_if ne rend pas le champ obligatoire. Les erreurs des champs entreprise sont masquées dans ce parcours.",
      "repro": "Sélectionner Acheteur, remplir nom, prénom, courriels et téléphone, puis envoyer. La sonde reproduit le rejet sur produit_ids; avec consentements=[] elle confirme aussi le rejet des consentements.",
      "fix": "Exclure les champs entreprise du contrat acheteur ou appliquer leurs règles uniquement au type entreprise. Afficher un résumé des erreurs pour ne jamais rester sans explication.",
      "source": "app/Http/Requests/PublicRegistrationRequest.php",
      "line": 40,
      "evidence": ""
    },
    {
      "id": "RACA-05",
      "priority": "P1",
      "area": "Entreprise",
      "title": "Le propriétaire ne peut pas enregistrer sa propre fiche",
      "proof": "Chrome + test isolé",
      "observation": "Le formulaire renvoie toujours user_id. Le validateur interdit à tout non-admin d’envoyer une valeur user_id, même s’il s’agit du propriétaire actuel et qu’elle ne change pas. Cette erreur n’est pas affichée.",
      "repro": "Se connecter avec entreprise03@raca.test, ouvrir sa fiche puis Enregistrer sans rien modifier. La sonde confirme l’erreur user_id pour ce même contrat.",
      "fix": "Ne pas envoyer user_id depuis la vue entreprise ou accepter sa valeur inchangée en empêchant strictement les changements. Ajouter un message visible.",
      "source": "app/Http/Requests/Admin/EntrepriseRequest.php",
      "line": 78,
      "evidence": ""
    },
    {
      "id": "RACA-06",
      "priority": "P1",
      "area": "Produits",
      "title": "L’édition peut effacer quatre attributs produit existants",
      "proof": "Chrome + lecture base + code",
      "observation": "Pour le produit Œufs de l’entreprise 3, contenant, durée de vie, code-barres et quantité minimale existent en base mais apparaissent vides. relationMap lit du camelCase alors que Laravel sérialise ces relations en snake_case. Enregistrer envoie les listes vides et sync les retire.",
      "repro": "Ouvrir le premier produit de l’entreprise 3. Comparer les champs vides avec inspect.php : Sac, Moins de 7 jours, UPC-A et 1 unité sont bien associés. Aucun enregistrement destructeur de cette fiche n’a été effectué.",
      "fix": "Corriger le mapping des clés sérialisées et ajouter un test ouvrir/enregistrer sans changement qui préserve tous les pivots.",
      "source": "resources/js/pages/admin/Entreprises/Products.vue",
      "line": 32,
      "evidence": "03-produit-relations.png"
    },
    {
      "id": "RACA-07",
      "priority": "P1",
      "area": "Demandes",
      "title": "Un brouillon enregistré ne peut être repris ni envoyé",
      "proof": "Chrome + routes",
      "observation": "L’enregistrement du brouillon fonctionne dans Chrome. La fiche obtenue propose seulement Annuler; aucune édition, reprise ou action d’envoi n’existe, ni route de mise à jour correspondante.",
      "repro": "Créer une demande et Enregistrer en brouillon. Ouvrir ensuite sa fiche depuis Mes demandes. Le brouillon d’audit 1 a montré ce cas avant d’être annulé.",
      "fix": "Ajouter l’édition et l’envoi d’un brouillon existant, en préservant critères, destinataires et pièce jointe. Ne pas créer un nouvel objet à chaque reprise.",
      "source": "resources/js/pages/portal/QuoteRequests/Show.vue",
      "line": 7,
      "evidence": "07-brouillon-sans-reprise.png"
    },
    {
      "id": "RACA-08",
      "priority": "P1",
      "area": "Demandes",
      "title": "La pièce jointe d’une réponse n’est pas accessible dans la vue acheteur",
      "proof": "Chrome + lecture base",
      "observation": "Le PDF offre-audit.pdf est bien stocké avec la réponse. Le tableau acheteur présente seulement entreprise, statut, montant et message; aucun lien ne permet de récupérer la pièce jointe.",
      "repro": "Envoyer une réponse entreprise avec un PDF puis consulter la demande côté acheteur. La demande d’audit 2 contient une réponse à 74,95 CAD et le fichier en base.",
      "fix": "Afficher le nom et le lien de téléchargement de chaque document de réponse, via la route protégée déjà présente. Ajouter une indication claire lorsqu’aucun document n’est joint.",
      "source": "resources/js/pages/portal/QuoteRequests/Show.vue",
      "line": 7,
      "evidence": "10-offre-sans-lien-piece-jointe.png"
    },
    {
      "id": "RACA-09",
      "priority": "P1",
      "area": "Navigation",
      "title": "La déconnexion ouvre l’accueil dans une fenêtre d’erreur Inertia",
      "proof": "Chrome",
      "observation": "La session est fermée mais le POST Inertia suit une redirection vers l’accueil Blade. L’accueil apparaît dans inertia-error-dialog au-dessus de l’ancien écran, au lieu d’une navigation normale.",
      "repro": "Depuis un espace connecté, cliquer Se déconnecter. Reproduit dans un nouvel onglet dédié; le relevé AX identifie explicitement inertia-error-dialog.",
      "fix": "Retourner une redirection externe Inertia vers l’accueil ou assurer une navigation complète à la déconnexion. Vérifier le même passage Inertia/Blade après toute suppression de compte ou redirection publique.",
      "source": "resources/js/layouts/AdminLayout.vue",
      "line": 407,
      "evidence": "15-deconnexion-overlay-inertia.png"
    },
    {
      "id": "RACA-10",
      "priority": "P1",
      "area": "Listes",
      "title": "Des données deviennent inaccessibles après la première page",
      "proof": "Code; seuil non atteint dans la base locale",
      "observation": "Les contrôleurs paginent les utilisateurs, les demandes émises et reçues, mais leurs vues ne rendent ni pagination ni chargement supplémentaire. La supervision admin réutilise aussi la liste des demandes.",
      "repro": "Dépasser 20 utilisateurs ou 20 demandes. Le serveur renvoie la première page et l’interface ne permet pas d’accéder à la suivante. Les 21 référentiels, eux, ont un Paginator.",
      "fix": "Ajouter une pagination avec total, page courante et conservation des filtres sur ces listes. Vérifier avec au moins 21 éléments par type.",
      "source": "resources/js/pages/portal/QuoteRequests/Index.vue",
      "line": 23,
      "evidence": ""
    },
    {
      "id": "RACA-11",
      "priority": "P1",
      "area": "Utilisateurs",
      "title": "Un compte créé par un admin reste non vérifié sans invitation",
      "proof": "Test isolé",
      "observation": "La création back-office stocke un mot de passe et les rôles, mais ne vérifie pas le courriel et n’envoie aucune invitation. Les routes métier exigent pourtant verified.",
      "repro": "La sonde crée un acheteur via /admin/users et confirme email_verified_at=null et aucune notification. L’interface ne signale pas cet état sur la liste.",
      "fix": "Définir un parcours d’invitation cohérent avec activation et choix du mot de passe. Afficher le statut et une action de renvoi; éviter de demander à l’admin de communiquer un mot de passe.",
      "source": "app/Http/Controllers/Admin/UserController.php",
      "line": 39,
      "evidence": ""
    },
    {
      "id": "RACA-12",
      "priority": "P1",
      "area": "Publication",
      "title": "Une entreprise peut être publique avant l’activation du compte",
      "proof": "Test isolé",
      "observation": "Après inscription, la nouvelle entreprise entre directement dans le catalogue, alors que son propriétaire n’a pas confirmé son courriel. La recherche ne contrôle pas de statut de publication ni la vérification du propriétaire.",
      "repro": "La sonde utilise un préfixe postal normalisé, inscrit une entreprise puis vérifie simultanément compte non vérifié et nom présent dans le catalogue.",
      "fix": "Décider le moment de publication et le matérialiser en base. Filtrer toutes les surfaces publiques et exports selon cette règle, et prévoir une modération si nécessaire.",
      "source": "app/Services/CatalogueSearch.php",
      "line": 76,
      "evidence": ""
    },
    {
      "id": "RACA-13",
      "priority": "P1",
      "area": "Validation",
      "title": "Plusieurs formulaires échouent sans afficher leurs erreurs",
      "proof": "Chrome + code",
      "observation": "Entreprise, produit, utilisateur, référentiel, outils et réponse fournisseur stockent les erreurs useForm sans rendre de messages de champ ou de résumé. Une soumission peut donc sembler ignorée.",
      "repro": "Exemple : enregistrer la fiche entreprise en tant que propriétaire. Autres cas à vérifier après correction : produit vide, utilisateur sans mot de passe conforme, import invalide et réponse vide.",
      "fix": "Afficher un résumé focalisé et des erreurs près des champs; ouvrir l’onglet concerné. Conserver la saisie et annoncer les erreurs aux lecteurs d’écran.",
      "source": "resources/js/pages/admin/Entreprises/Form.vue",
      "line": 32,
      "evidence": ""
    },
    {
      "id": "RACA-14",
      "priority": "P1",
      "area": "Produits",
      "title": "Un identifiant de relation invalide provoque une erreur 500 et une création partielle",
      "proof": "Test isolé",
      "observation": "Les IDs des attributs multiples sont validés comme entiers, sans exists. Le produit est créé avant les sync et sans transaction. Une clé étrangère invalide produit une 500 après l’insertion du produit.",
      "repro": "La sonde poste un nouveau produit avec specificite_ids=[999999], obtient 500 puis confirme que le produit de base a quand même été créé.",
      "fix": "Valider les références avec exists et distinct, puis entourer création/mise à jour et synchronisations d’une transaction atomique.",
      "source": "app/Http/Controllers/Admin/EntrepriseProduitController.php",
      "line": 38,
      "evidence": ""
    },
    {
      "id": "RACA-15",
      "priority": "P1",
      "area": "Import",
      "title": "Un CSV mal formé provoque une erreur 500; les imports ne sont pas atomiques",
      "proof": "Test isolé + code",
      "observation": "Une ligne plus longue que l’en-tête fait échouer array_combine. L’import applique les lignes au fil de l’eau sans validation préalable ni transaction; une erreur tardive peut donc laisser un import incomplet.",
      "repro": "Sonde : fichier avec en-tête nom et ligne Audit;ColonneEnTrop → 500. Le scénario de plusieurs lignes et erreur tardive reste à tester visuellement.",
      "fix": "Prévalider en-têtes, longueurs, encodage et références MRC. Afficher les erreurs avec numéros de ligne et fournir un aperçu avant application atomique.",
      "source": "app/Http/Controllers/Admin/DataToolsController.php",
      "line": 90,
      "evidence": ""
    },
    {
      "id": "RACA-16",
      "priority": "P1",
      "area": "Échéances",
      "title": "Les dates saisies n’ont pas de fuseau et les écrans exposent les valeurs UTC brutes",
      "proof": "Chrome + configuration + code",
      "observation": "Une échéance saisie à 16:00 est persistée comme 16:00Z avec APP_TIMEZONE=UTC, puis affichée en ISO. Le 30 octobre, 16:00 Toronto correspond à 20:00Z; le contrat de saisie ne précise pas ce décalage.",
      "repro": "Demande d’audit 2 : saisie 2026-10-30T16:00, affichage 2026-10-30T16:00:00.000000Z. Les fermetures automatiques utilisent cette date serveur.",
      "fix": "Définir America/Toronto pour l’interprétation utilisateur, convertir explicitement en UTC et afficher une date française lisible avec fuseau. Tester les changements d’heure.",
      "source": "resources/js/pages/portal/QuoteRequests/Form.vue",
      "line": 24,
      "evidence": "08-demande-envoyee.png"
    },
    {
      "id": "RACA-17",
      "priority": "P1",
      "area": "Confidentialité",
      "title": "Les exports publics révèlent la ville malgré son masquage",
      "proof": "Test isolé + code",
      "observation": "Le CSV public exporte systématiquement ville et MRC. Le PDF liste affiche également la ville sans consulter le réglage ville. Cela diverge du contrat de visibilité proposé par l’interface.",
      "repro": "La sonde crée une ville masquée, télécharge le CSV et y retrouve son nom. Le template PDF a le même défaut; le PDF individuel contrôle la ville mais affiche encore la MRC.",
      "fix": "Centraliser les règles de visibilité dans la même projection publique pour HTML, JSON, CSV et tous les PDF.",
      "source": "app/Http/Controllers/Public/EntrepriseExportController.php",
      "line": 39,
      "evidence": ""
    },
    {
      "id": "RACA-18",
      "priority": "P2",
      "area": "Inscription",
      "title": "Les consentements sont tous précochés",
      "proof": "Chrome",
      "observation": "Communications courriel, Conditions d’utilisation et Publication au catalogue sont sélectionnés dès l’arrivée. Les trois choix ont pourtant des finalités distinctes.",
      "repro": "Ouvrir une nouvelle inscription entreprise; les trois cases valent 1. Le formulaire les présélectionne aussi après réinitialisation.",
      "fix": "Présenter des choix explicites, expliquer les finalités et distinguer les exigences métier des options. Faire valider le texte par le responsable du projet.",
      "source": "resources/js/pages/public/Inscription.vue",
      "line": 48,
      "evidence": ""
    },
    {
      "id": "RACA-19",
      "priority": "P2",
      "area": "Inscription",
      "title": "Le serveur n’exige aucun consentement précis",
      "proof": "Test isolé + code",
      "observation": "Le serveur demande au moins un identifiant de consentement, quel qu’il soit. Le test d’inscription réussit avec uniquement Publication au catalogue, sans Conditions d’utilisation. Ce constat décrit le comportement; il ne constitue pas un avis juridique.",
      "repro": "La sonde de publication avant activation utilise uniquement le consentement Publication au catalogue et obtient une inscription sans erreur.",
      "fix": "Définir les consentements réellement requis par le projet, les vérifier individuellement et conserver une trace datée/versionnée de leur acceptation.",
      "source": "app/Http/Requests/PublicRegistrationRequest.php",
      "line": 38,
      "evidence": ""
    },
    {
      "id": "RACA-20",
      "priority": "P2",
      "area": "Inscription",
      "title": "Le parcours impose deux courriels successifs et son texte de succès est ambigu",
      "proof": "Code",
      "observation": "Le premier courriel sert à activer l’adresse; son clic déclenche ensuite un second courriel de réinitialisation pour choisir un mot de passe. Le dialogue de succès parle directement du lien de choix du mot de passe.",
      "repro": "Lire RegistrationController, AccountActivationController et le dialogue « Inscription complétée ». Livraison réelle et complétion de ce parcours non validées pendant cet audit.",
      "fix": "Afficher les étapes exactes, le destinataire et une action de renvoi. Envisager un lien d’invitation unique qui vérifie l’adresse et conduit au choix du mot de passe.",
      "source": "app/Http/Controllers/Public/AccountActivationController.php",
      "line": 20,
      "evidence": ""
    },
    {
      "id": "RACA-21",
      "priority": "P2",
      "area": "Navigation",
      "title": "Le menu acheteur contient deux destinations interdites",
      "proof": "Chrome",
      "observation": "Tableau de bord et logo pointent vers /admin, refusé à l’acheteur. Le lien Entreprises mène aussi à un périmètre non autorisé pour un acheteur normal.",
      "repro": "Connexion avec le compte acheteur d’audit : cliquer Tableau de bord → page 403 Forbidden. Les droits serveur restent correctement appliqués.",
      "fix": "Construire la navigation selon les capacités réelles et faire pointer le logo vers l’accueil adapté au rôle.",
      "source": "resources/js/layouts/AdminLayout.vue",
      "line": 155,
      "evidence": "06-menu-acheteur.png"
    },
    {
      "id": "RACA-22",
      "priority": "P2",
      "area": "Navigation",
      "title": "Les vues entreprise et acheteur se présentent comme une administration",
      "proof": "Chrome",
      "observation": "L’en-tête affiche Espace d’administration pour les trois rôles. Le tableau de bord entreprise reprend aussi ADMINISTRATION RACA et des instructions de gestion du contenu non accessibles à ce compte.",
      "repro": "Comparer l’espace acheteur, l’espace entreprise03 et l’administrateur.",
      "fix": "Adapter les titres, les raccourcis et les indicateurs : espace acheteur, espace entreprise et administration.",
      "source": "resources/js/layouts/AdminLayout.vue",
      "line": 377,
      "evidence": ""
    },
    {
      "id": "RACA-23",
      "priority": "P2",
      "area": "Permissions",
      "title": "Le menu ignore les permissions déléguées et les rôles multiples",
      "proof": "Code",
      "observation": "canSee vérifie seulement le premier role.slug. Des contrôleurs acceptent pourtant des gestionnaires MRC ou des permissions spécifiques; leurs outils peuvent être autorisés mais invisibles dans le menu.",
      "repro": "Exemples : accès outils autorisé par isMrcManager ou exportMrc, entrée de menu réservée à role=admin. L’ordre des rôles influence aussi la visibilité.",
      "fix": "Exposer les capacités autorisées côté serveur et les réutiliser dans le menu. Tester les comptes MRC et les combinaisons de rôles.",
      "source": "resources/js/layouts/AdminLayout.vue",
      "line": 247,
      "evidence": ""
    },
    {
      "id": "RACA-24",
      "priority": "P2",
      "area": "Demandes",
      "title": "La confirmation finale ne récapitule pas les informations à envoyer",
      "proof": "Chrome",
      "observation": "La dernière étape indique seulement le nombre de destinataires. Le titre, les dates, le besoin, le fichier et les noms des fournisseurs ne sont pas récapitulés.",
      "repro": "Passer à l’étape 3 de création; l’écran « Prêt à envoyer » ne contient qu’un court texte et les boutons.",
      "fix": "Afficher un récapitulatif complet avant l’envoi avec accès clair à la modification de chaque bloc.",
      "source": "resources/js/pages/portal/QuoteRequests/Form.vue",
      "line": 504,
      "evidence": ""
    },
    {
      "id": "RACA-25",
      "priority": "P2",
      "area": "Demandes",
      "title": "Le formulaire de réponse reste disponible pour une demande fermée ou expirée",
      "proof": "Code; état fermé créé pendant l’audit",
      "observation": "Le serveur refuse correctement de répondre à une demande non ouverte avec 422, mais le formulaire fournisseur n’est conditionné ni au statut ni à l’échéance et n’affiche pas ses erreurs.",
      "repro": "Ouvrir côté fournisseur une demande fermée ou dépassant son échéance et tenter une réponse. L’interface annonce toujours Envoyer la réponse.",
      "fix": "Afficher Fermée/Expirée, conserver l’offre existante en lecture seule et expliquer pourquoi l’envoi est indisponible.",
      "source": "resources/js/pages/portal/ReceivedQuoteRequests/Show.vue",
      "line": 4,
      "evidence": ""
    },
    {
      "id": "RACA-26",
      "priority": "P2",
      "area": "Demandes",
      "title": "Une nouvelle réponse écrase l’offre précédente sans historique",
      "proof": "Code",
      "observation": "updateOrCreate remplace montant, message et date; aucune version de l’ancienne offre n’est conservée. Le formulaire reste un formulaire d’envoi après la première réponse.",
      "repro": "Soumettre deux offres successives pour le même destinataire. Le modèle impose une seule ligne de réponse.",
      "fix": "Décider si la modification d’offre est autorisée. Si oui, afficher Modifier ma réponse et conserver les versions/horodatages utiles.",
      "source": "app/Http/Controllers/Portal/ReceivedQuoteRequestController.php",
      "line": 41,
      "evidence": ""
    },
    {
      "id": "RACA-27",
      "priority": "P2",
      "area": "Demandes",
      "title": "Le fournisseur ne voit pas l’identité et les coordonnées de l’acheteur",
      "proof": "Chrome + code",
      "observation": "Le contrôleur charge le demandeur et son profil acheteur mais l’écran reçu affiche uniquement titre, échéance, description et besoins.",
      "repro": "Ouvrir la demande d’audit côté entreprise : Cuisine Audit RACA et le téléphone de test ne sont pas présentés.",
      "fix": "Afficher un bloc demandeur avec organisation et coordonnées prévues par le métier, en respectant leur périmètre de partage.",
      "source": "resources/js/pages/portal/ReceivedQuoteRequests/Show.vue",
      "line": 4,
      "evidence": "09-reponse-entreprise.png"
    },
    {
      "id": "RACA-28",
      "priority": "P2",
      "area": "Demandes",
      "title": "Fermer et Annuler s’appliquent immédiatement sans explication",
      "proof": "Chrome + routes",
      "observation": "Les deux actions changent immédiatement le statut et disparaissent après succès. Aucune confirmation, explication de l’effet ou possibilité de réouverture n’est proposée.",
      "repro": "La demande 2 a été fermée et le brouillon 1 annulé pendant l’audit; les boutons ont appliqué les actions directement.",
      "fix": "Préciser l’effet sur les réponses et demander une confirmation pour ces changements. Prévoir une réouverture si le processus métier le permet.",
      "source": "resources/js/pages/portal/QuoteRequests/Show.vue",
      "line": 7,
      "evidence": ""
    },
    {
      "id": "RACA-29",
      "priority": "P2",
      "area": "Demandes",
      "title": "Les statuts métier sont affichés en anglais",
      "proof": "Chrome",
      "observation": "draft, sent, pending, viewed, responded, closed et cancelled sont exposés tels quels. La présentation n’aide pas à distinguer état de la demande et état du destinataire.",
      "repro": "Consulter Mes demandes, Demandes reçues et la fiche d’échange d’audit.",
      "fix": "Centraliser des libellés français et des badges cohérents : Brouillon, Envoyée, Consultée, Réponse reçue, Fermée, Annulée.",
      "source": "resources/js/pages/portal/QuoteRequests/Index.vue",
      "line": 46,
      "evidence": ""
    },
    {
      "id": "RACA-30",
      "priority": "P2",
      "area": "Demandes",
      "title": "Le montant acheteur est affiché avec un point décimal",
      "proof": "Chrome + lecture base",
      "observation": "Le formulaire fournisseur affiche 74,95 $, la base conserve correctement 74.95, mais la vue acheteur rend 74.95 $. Le défaut est uniquement de présentation.",
      "repro": "Comparer les captures de la réponse entreprise et de l’offre reçue par l’acheteur.",
      "fix": "Employer Intl.NumberFormat fr-CA avec devise CAD et conserver les montants décimaux exacts côté serveur.",
      "source": "resources/js/pages/portal/QuoteRequests/Show.vue",
      "line": 7,
      "evidence": "10-offre-sans-lien-piece-jointe.png"
    },
    {
      "id": "RACA-31",
      "priority": "P2",
      "area": "Compte",
      "title": "Le profil ne permet pas de modifier les informations de l’inscription",
      "proof": "Chrome + code",
      "observation": "Le profil ne propose que nom et courriel. Prénom, téléphone, organisation et fonction acheteur n’ont pas d’écran de maintenance, bien qu’ils soient collectés à l’inscription.",
      "repro": "Ouvrir Mon profil depuis un compte acheteur et comparer avec le formulaire initial.",
      "fix": "Ajouter les champs de profil nécessaires selon le type de compte et conserver la séparation personne/entreprise.",
      "source": "resources/js/pages/settings/Profile.vue",
      "line": 30,
      "evidence": ""
    },
    {
      "id": "RACA-32",
      "priority": "P2",
      "area": "Catalogue",
      "title": "Réinitialiser les filtres ne permet pas de sortir d’une recherche sans résultat",
      "proof": "Chrome",
      "observation": "Sur zzzauditintrouvable sans filtres, le bouton laisse le mot-clé et reconduit exactement la même recherche vide. L’utilisateur voit un bouton de récupération qui ne change rien.",
      "repro": "Rechercher zzzauditintrouvable puis Réinitialiser les filtres. Reproduit sur écran mobile.",
      "fix": "Proposer Réinitialiser la recherche qui vide mot-clé et filtres, ou distinguer clairement les deux actions.",
      "source": "resources/js/pages/public/Catalogue.vue",
      "line": 131,
      "evidence": "13-reinitialisation-sans-effet.png"
    },
    {
      "id": "RACA-33",
      "priority": "P2",
      "area": "Statistiques",
      "title": "Les recherches quittées rapidement ne sont pas journalisées",
      "proof": "Code",
      "observation": "L’enregistrement est différé de dix secondes et son timer annulé au démontage. Un visiteur qui cherche puis ouvre une autre page avant ce délai n’est pas compté.",
      "repro": "Rechercher puis quitter la page avant dix secondes; comparer au rapport. Une nouvelle recherche réinitialise aussi le timer.",
      "fix": "Définir la métrique désirée et enregistrer l’événement au moment convenu, avec dédoublonnage. Vérifier séparément la politique de collecte et les choix de confidentialité.",
      "source": "resources/js/pages/public/Catalogue.vue",
      "line": 113,
      "evidence": ""
    },
    {
      "id": "RACA-34",
      "priority": "P2",
      "area": "Catalogue",
      "title": "La fiche publique indexable est beaucoup moins complète que la fenêtre du répertoire",
      "proof": "Code",
      "observation": "La page Blade dédiée à une entreprise présente nom, adresse, ville, courriel et noms de produits. Certifications, modes de livraison, détails et liens web y sont absents. Elle ne respecte pas non plus tous les réglages de visibilité.",
      "repro": "Comparer la route /repertoire/{id}/{slug} à Afficher plus dans le catalogue.",
      "fix": "Partager les données publiques projetées et un contenu métier cohérent entre page indexable, fenêtre et export.",
      "source": "resources/views/public/entreprise.blade.php",
      "line": 1,
      "evidence": ""
    },
    {
      "id": "RACA-35",
      "priority": "P2",
      "area": "Catalogue",
      "title": "Plusieurs attributs saisis ne sont jamais montrés dans la fiche détaillée",
      "proof": "Chrome + code",
      "observation": "La fenêtre entreprise affiche surtout spécificités, conservation, volume et transaction. Contenants, durée de vie, code-barres, quantité minimale, disponibilités et modes de livraison sont chargés mais non présentés.",
      "repro": "Comparer la fiche du produit Œufs de l’entreprise 3 aux informations existantes en base.",
      "fix": "Afficher les informations utiles à la décision d’approvisionnement, en regroupant les détails et en masquant les blocs réellement vides.",
      "source": "resources/js/components/public/EnterpriseDialog.vue",
      "line": 135,
      "evidence": ""
    },
    {
      "id": "RACA-36",
      "priority": "P2",
      "area": "Tableau de bord",
      "title": "Les fiches « récemment modifiées » sont triées par création",
      "proof": "Code",
      "observation": "Le contrôleur utilise latest() sans colonne; cela trie sur created_at. L’écran promet les six dernières mises à jour.",
      "repro": "Modifier une ancienne entreprise puis comparer son ordre au tableau de bord. Le contrat de requête confirme le décalage.",
      "fix": "Trier par updated_at avec un critère secondaire stable et, au besoin, afficher la date de modification.",
      "source": "app/Http/Controllers/Admin/AdminController.php",
      "line": 36,
      "evidence": ""
    },
    {
      "id": "RACA-37",
      "priority": "P2",
      "area": "Contenu",
      "title": "Publier ne prend pas en compte les modifications non enregistrées",
      "proof": "Code",
      "observation": "Le bouton Publier appelle une route séparée sans envoyer le formulaire. Le serveur copie le draft_content déjà sauvegardé. Un utilisateur peut croire publier son texte actuel alors qu’il publie la version précédente.",
      "repro": "Modifier un titre puis Publier sans Enregistrer le brouillon. Ce cas n’a pas été appliqué au contenu existant pendant l’audit.",
      "fix": "Désactiver Publier tant que le formulaire est sale, ou enregistrer puis publier atomiquement. Afficher la version et la date publiées.",
      "source": "resources/js/pages/admin/Content/Edit.vue",
      "line": 52,
      "evidence": ""
    },
    {
      "id": "RACA-38",
      "priority": "P2",
      "area": "Contenu",
      "title": "Le contenu structuré n’est validé que comme tableau",
      "proof": "Code",
      "observation": "Le serveur ne valide pas les champs internes, les tailles de textes, la structure des cartes ni les questions/réponses. Les rendus supposent pourtant plusieurs structures précises.",
      "repro": "Examiner les règles du ContentController : content required|array seulement. Les cas malformés n’ont pas été injectés dans les pages locales existantes.",
      "fix": "Valider les sous-champs et valeurs autorisées, produire des erreurs lisibles et refuser une publication incohérente.",
      "source": "app/Http/Controllers/Admin/ContentController.php",
      "line": 30,
      "evidence": ""
    },
    {
      "id": "RACA-39",
      "priority": "P2",
      "area": "Formulaires",
      "title": "Des libellés techniques apparaissent dans les classifications et produits",
      "proof": "Chrome",
      "observation": "Les écrans affichent type production, type clientele, specificite, duree vie et quantite minimale commande, issus directement des clés de programmation.",
      "repro": "Ouvrir Classifications d’une entreprise ou Modifier le produit.",
      "fix": "Définir des libellés français métier explicites, avec accents, pluriels et aide pour les termes spécialisés.",
      "source": "resources/js/pages/admin/Entreprises/Form.vue",
      "line": 179,
      "evidence": "02-classifications-libelles.png"
    },
    {
      "id": "RACA-40",
      "priority": "P2",
      "area": "Accessibilité",
      "title": "Des boutons et champs n’ont pas de nom accessible explicite",
      "proof": "Chrome + code",
      "observation": "Dans les produits et utilisateurs, plusieurs actions ne sont identifiées que par un pictogramme. Les contacts et marques utilisent principalement des placeholders qui disparaissent pendant la saisie.",
      "repro": "Lire les contrôles de produits : boutons crayon/poubelle sans texte; lire les champs contacts remplis, dont le nom accessible devient la valeur seule.",
      "fix": "Ajouter aria-label, titres et labels persistants. Identifier précisément la ligne concernée dans les actions répétées.",
      "source": "resources/js/pages/admin/Entreprises/Products.vue",
      "line": 101,
      "evidence": ""
    },
    {
      "id": "RACA-41",
      "priority": "P2",
      "area": "Référentiels",
      "title": "Une ville peut être créée sans MRC",
      "proof": "Code",
      "observation": "Le référentiel Villes propose une MRC mais la validation la rend nullable. Une ville orpheline ne peut plus être sélectionnée normalement à l’inscription et échappe aux filtres MRC.",
      "repro": "Ajouter une ville sans sélectionner de MRC; le contrat serveur l’autorise. Ne pas utiliser un référentiel réel pour ce test.",
      "fix": "Rendre mrc_id requis pour les villes, contrôler les imports et prévoir le traitement des villes orphelines déjà présentes.",
      "source": "app/Http/Requests/Admin/UpsertReferenceRequest.php",
      "line": 29,
      "evidence": ""
    },
    {
      "id": "RACA-42",
      "priority": "P2",
      "area": "Export",
      "title": "Les exports CSV n’échappent pas les préfixes de formule Excel",
      "proof": "Test isolé",
      "observation": "Un nom commençant par = est exporté tel quel. Selon le tableur et ses réglages, une donnée utilisateur peut être interprétée comme une formule à l’ouverture. La sonde confirme la chaîne brute, pas une exécution dans Excel.",
      "repro": "La sonde exporte une entreprise nommée =1+1 et vérifie sa présence sans neutralisation dans le CSV.",
      "fix": "Neutraliser les préfixes de formule pour les cellules textuelles dans tous les exports CSV et tester aussi +, -, @, tabulations et retours de ligne.",
      "source": "app/Http/Controllers/Public/EntrepriseExportController.php",
      "line": 35,
      "evidence": ""
    },
    {
      "id": "RACA-43",
      "priority": "P2",
      "area": "Présentation",
      "title": "Les données de démonstration ne sont pas cohérentes avec la région",
      "proof": "Chrome + export",
      "observation": "Portneuf/Pont-Rouge apparaissent dans le répertoire régional, des codes postaux commencent par T, P, N ou V et les sites pointent vers des domaines .test. Certaines civilités ne correspondent pas aux prénoms de démonstration.",
      "repro": "Consulter la liste, les contacts et l’export public de 12 entreprises.",
      "fix": "Préparer un jeu de démonstration crédible et approuvé, avec coordonnées fictives clairement identifiées et géographie correcte. Éviter les liens externes non fonctionnels pendant la présentation.",
      "source": "database/seeders/EntrepriseSeeder.php",
      "line": 34,
      "evidence": ""
    },
    {
      "id": "RACA-44",
      "priority": "P2",
      "area": "Navigation publique",
      "title": "Un utilisateur connecté voit encore Connexion et Inscription",
      "proof": "Chrome + code",
      "observation": "Le site public ne présente pas Mon espace, le rôle courant ou un accès rapide à Mes demandes. Il reste identique après connexion.",
      "repro": "Depuis une session acheteur, ouvrir le répertoire public; l’en-tête propose toujours Connexion.",
      "fix": "Afficher un accès au portail approprié et un état de session clair sur les surfaces Blade et Vue.",
      "source": "resources/js/layouts/PublicLayout.vue",
      "line": 51,
      "evidence": ""
    },
    {
      "id": "RACA-45",
      "priority": "P2",
      "area": "HTTP / SEO",
      "title": "Une page inexistante répond HTTP 200",
      "proof": "Test isolé",
      "observation": "La route de secours rend la page NotFound sans statut 404. Les moteurs et outils de suivi peuvent traiter une URL inexistante comme valide.",
      "repro": "La sonde GET /audit-page-inexistante obtient 200.",
      "fix": "Rendre la page d’erreur avec statut HTTP 404 et conserver une navigation de retour utile.",
      "source": "routes/web.php",
      "line": 83,
      "evidence": ""
    },
    {
      "id": "RACA-46",
      "priority": "P3",
      "area": "Statistiques",
      "title": "Les filtres du rapport sont affichés comme des compteurs opaques",
      "proof": "Chrome",
      "observation": "Le détail indique MRC (1) ou Produits (1) sans nommer Appalaches ou le produit. Deux lignes peuvent ainsi paraître identiques malgré des choix différents.",
      "repro": "Consulter le détail du Rapport des recherches et comparer les regroupements MRC.",
      "fix": "Résoudre les IDs en libellés et afficher les valeurs ou un détail dépliable par groupe.",
      "source": "resources/js/pages/admin/Reports/Searches.vue",
      "line": 245,
      "evidence": "04-rapport-recherches.png"
    },
    {
      "id": "RACA-47",
      "priority": "P3",
      "area": "PDF",
      "title": "L’export est lisible mais peu aligné sur l’identité du site",
      "proof": "PDF rendu et inspecté",
      "observation": "Le PDF public des 12 entreprises tient correctement sur une page A4 paysage sans texte coupé. Il utilise une palette verte, sans logo RACA, ni rappel des filtres ou pagination.",
      "repro": "Ouvrir export-public-page1.png, rendu par Poppler depuis le PDF téléchargé dans Chrome.",
      "fix": "Ajouter le logo, les filtres appliqués, un pied de page et une palette cohérente. Revalider la mise en page avec beaucoup plus de lignes.",
      "source": "resources/views/pdf/entreprises.blade.php",
      "line": 17,
      "evidence": "export-public-page1.png"
    },
    {
      "id": "RACA-48",
      "priority": "P3",
      "area": "Mobile",
      "title": "Le bouton de confidentialité occupe une place importante sur petit écran",
      "proof": "Chrome à 390 × 844",
      "observation": "Le bouton fixe Préférences de confidentialité recouvre le bas de la zone de lecture. Les cartes restent lisibles et la recherche s’empile correctement à cette largeur.",
      "repro": "Voir les captures mobiles du répertoire et de la recherche vide.",
      "fix": "Réduire l’encombrement du bouton, préserver une zone de clic accessible et vérifier les dialogues/claviers sur appareils réels.",
      "source": "resources/js/components/public/CookieBanner.vue",
      "line": 72,
      "evidence": "12-mobile-recherche.png"
    },
    {
      "id": "RACA-49",
      "priority": "P3",
      "area": "Contenu",
      "title": "Le texte de la politique de témoins paraît ancien et générique",
      "proof": "Chrome; observation éditoriale",
      "observation": "La page annonce août 2023 et évoque panier/paiement, boutons sociaux et Google Analytics. Ces passages doivent être rapprochés des technologies réellement utilisées. Aucun avis juridique n’est formulé.",
      "repro": "Ouvrir Politique de témoins et lire la date et les sections techniques.",
      "fix": "Faire actualiser les informations factuelles et les coordonnées par le responsable du contenu, puis vérifier l’adéquation avec le fonctionnement effectif.",
      "source": "resources/js/pages/public/CookiePolicy.vue",
      "line": 1,
      "evidence": ""
    },
    {
      "id": "RACA-50",
      "priority": "P3",
      "area": "Contenu",
      "title": "L’éditeur ne propose pas d’aperçu ni d’historique de publication",
      "proof": "Chrome + code",
      "observation": "Voir le site ouvre la version publiée. Aucun aperçu du brouillon, comparaison, historique ou retour à une publication antérieure n’est présent.",
      "repro": "Ouvrir Contenu Accueil et FAQ et examiner les actions disponibles.",
      "fix": "Ajouter un aperçu protégé et un petit historique de versions avec auteur, date et restauration contrôlée.",
      "source": "resources/js/pages/admin/Content/Edit.vue",
      "line": 61,
      "evidence": ""
    },
    {
      "id": "RACA-51",
      "priority": "P2",
      "area": "Validation",
      "title": "Des clés de traduction brutes sont affichées à l’inscription",
      "proof": "Chrome + code",
      "observation": "Le formulaire entreprise vide affiche validation.required_if pour plusieurs champs. Le fichier français ne fournit que deux messages génériques et deux attributs; les autres règles manquent de traduction.",
      "repro": "Ouvrir Inscription et envoyer le formulaire vide. Observé pour nom d’entreprise, courriel d’entreprise, produits, adresse, MRC, ville et code postal.",
      "fix": "Compléter les traductions de validation utilisées et les noms français des champs. Couvrir aussi confirmations, dates, unicité, fichiers, tailles et relations dans la recette.",
      "source": "lang/fr/validation.php",
      "line": 4,
      "evidence": ""
    }
  ],
  "coverage": [
    [
      "Accueil Blade",
      "Chrome, navigation, refus des témoins, capture",
      "Affichage correct; parcours public et cohérence éditoriale examinés."
    ],
    [
      "Répertoire",
      "Chrome bureau + 390 px, filtre MRC, recherche vide, réinitialisation",
      "12 résultats initiaux; filtre Appalaches → 2; réinitialisation de recherche inefficace."
    ],
    [
      "Fiche entreprise en fenêtre",
      "Chrome + base + code",
      "Ouverture et fermeture, liens, produits, visibilité et détails comparés."
    ],
    [
      "Fiche indexable et sitemap",
      "Code",
      "Contenu et visibilité analysés; pas de validation SEO externe."
    ],
    [
      "Exports publics CSV / PDF",
      "Téléchargements Chrome; CSV lu; PDF rendu avec Poppler",
      "12 entreprises, PDF 1 page lisible. Export filtré via URL constaté; tous les filtres combinés non rejoués."
    ],
    [
      "FAQ / politique / témoins",
      "Chrome + code",
      "Pages ouvertes; refus conservé entre les surfaces observées. Pas d’audit juridique ou complet des traceurs."
    ],
    [
      "Inscription acheteur",
      "Chrome + sonde SQLite",
      "Soumission bloquée par des champs masqués. Compte d’audit créé techniquement pour la suite."
    ],
    [
      "Inscription entreprise",
      "Chrome + sonde SQLite",
      "Dépendance MRC/ville testée; code postal valide refusé. Entreprise d’audit créée techniquement."
    ],
    [
      "Connexion admin / acheteur / entreprise",
      "Chrome",
      "Trois rôles authentifiés. Le compte entreprise03 a servi à l’échange; le nouveau compte entreprise a été préparé en base."
    ],
    [
      "Déconnexion",
      "Chrome",
      "Session fermée, mais accueil affiché dans une erreur Inertia."
    ],
    [
      "Activation / mot de passe oublié / réinitialisation",
      "Code + tests existants",
      "Deux courriels successifs identifiés. Réception externe et parcours navigateur complet non validés."
    ],
    [
      "Tableau de bord admin et entreprise",
      "Chrome + code",
      "Statistiques, droits de périmètre et tri examinés."
    ],
    [
      "Entreprises / fiche / classifications / contacts / visibilité",
      "Chrome admin et entreprise + sonde SQLite",
      "Onglets parcourus; sauvegarde propriétaire testée et rejetée. Aucun contact existant supprimé."
    ],
    [
      "Produits",
      "Chrome + lecture base + sonde SQLite",
      "Édition ouverte; attributs manquants démontrés sans écraser les données existantes; 500 partielle reproduite en mémoire."
    ],
    [
      "21 référentiels",
      "Chaque page ouverte dans Chrome; relevé texte par page",
      "MRC, modes de livraison, villes, produits, volumes, transactions, productions, clientèles, spécificités, conservations, conditionnements, contenants, durées de vie, codes-barres, minimums, disponibilités, certifications, permis, installations, consentements, codes postaux."
    ],
    [
      "Utilisateurs / création / édition",
      "Chrome + code + sondes SQLite",
      "Liste et formulaire ouverts; invitation et élévation de rôles testées en mémoire. Pas de rôle réel modifié."
    ],
    [
      "Rapport des recherches",
      "Chrome + code + tests existants",
      "Graphiques et détails rendus; noms de filtres, dates et collecte analysés. Export admin non téléchargé durant cette session."
    ],
    [
      "Contenu Accueil / FAQ",
      "Chrome + code + tests existants",
      "Éditeurs ouverts; séparation brouillon/publication analysée. Aucune publication réelle modifiée."
    ],
    [
      "Outils : export, import, courriel",
      "Chrome + code + sonde SQLite",
      "Écran ouvert, validation vide essayée; CSV invalide reproduit en mémoire. Aucun courriel de masse ni import de référentiel réel appliqué."
    ],
    [
      "Demande acheteur",
      "Chrome",
      "Brouillon enregistré; autre demande prévisualisée sur 12 fournisseurs puis envoyée à une entreprise."
    ],
    [
      "Réception et réponse entreprise",
      "Chrome + lecture base",
      "Réponse 74,95 CAD + PDF téléversé; stockage vérifié, statut responded puis fermeture côté acheteur."
    ],
    [
      "Fermeture / annulation",
      "Chrome",
      "Demande 2 fermée; brouillon 1 annulé. Résultats conservés comme traces d’audit."
    ],
    [
      "Pièces jointes",
      "Téléversement Chrome + base + code",
      "PDF de réponse stocké, lien absent côté acheteur. Téléchargement autorisé/interdit de toutes les variantes non rejoué dans Chrome."
    ],
    [
      "Profil / sécurité",
      "Chrome + code + tests existants",
      "Profil ouvert; écran de confirmation de mot de passe atteint. Aucun mot de passe, passkey, 2FA ou compte existant supprimé/modifié."
    ],
    [
      "Gestionnaire MRC",
      "Code + tests existants",
      "Périmètres et menu analysés; aucune session MRC ouverte. À ajouter à la recette finale."
    ]
  ]
}