---
suivi: 1220
date: 2026-08-05
sujet: sélecteur programme — liste au focus pour transversaux
chantier: depot-factures
type: fix
statut: poussé
hash: 56b31b77
fichiers:
  - app/Http/Controllers/ProgrammeController.php
  - resources/js/Components/Programmes/ProgrammeSearchSelect.vue
  - public/build/
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - docs/suivi/SUIVI_1220_programme_select_transversal.md
---

## PROMPT ENVOYÉ

SUIVI #1220 — transversal ne voit aucun programme au focus (No results found).
HEAD base après #1219. Règle #1188 : vide codes = TOUT, jamais ACL.

## FICHES LUES

- `SUIVI_1190` — min_q=0 si filtre, min_q=2 sinon ; bandeau ; limit 40.
- `SUIVI_1188` — confort ≠ ProgrammeAccessService.
- `SUIVI_1191` / `1192` / `1193` — ne pas casser saisie dépôt.

## CAUSE EXACTE

Hypothèse confirmée.

1. `ProgrammeEnseigneComfortFiltre::enseigneCodesPourUser` → `[]` pour transversal
   (GS_ADMIN_ERP / DSI / DG sans GS_HECTARE|GEMME|ENVOL). Correct.
2. `metaPourUser` → `filtre_actif: false`. Correct (« vide = tout »).
3. `ProgrammeController::search` : `$minQ = $filtreApplique ? 0 : 2` → pour
   transversal **min_q=2**.
4. `ProgrammeSearchSelect.complete` : même règle → au focus `q=''` →
   `suggestions=[]` → libellé PrimeVue anglais « No results found ».

Le backend interprétait déjà le vide comme « pas de filtre SQL » (`all=1` /
pas de whereHas enseigne), mais **refusait de renvoyer la liste** tant que
`q.length < 2`. Le transversal voyait donc moins qu’un HECTARE filtré.

## CORRECTIF

- `min_q = 0` pour tous les profils (focus → 40 premiers).
- Meta `truncated` / `limit` / `count_returned` ; hint « 40 premiers affichés… ».
- Transversal : pas de bandeau filtre ; hint « N programmes » ou troncature.
- Filtre enseigne : bandeau #1190 inchangé.
- Messages FR : `empty-search-message` (plus le défaut anglais).

## TABLEAU 5 PROFILS

| Profil | codes retournés | liste au focus | bandeau filtre | programmes proposés |
|--------|-----------------|----------------|----------------|---------------------|
| Transversal (ADMIN/DSI/DG) | `[]` | oui | non (hint catalogue) | min(40, total) |
| GS_HECTARE seul | `['HECTARE']` | oui | oui + débloquer | min(40, n_HECTARE) |
| GS_ENVOL seul | `['ENVOL']` | oui | oui | min(40, n_ENVOL) |
| Multi HECTARE+ENVOL | union | oui | oui | min(40, union) |
| Aucun groupe enseigne | `[]` | oui | non (hint) | min(40, total) |

## MESSAGES D'ÉTAT

| État | Texte FR |
|------|----------|
| Chargement | indicateur `:loading` + « Recherche… » si vide pendant fetch |
| < 2 car. avant 1er load | « Saisissez 2 caractères pour rechercher » (résiduel) |
| Recherche sans hit | « Aucun programme ne correspond » |

## OBJET 3 — RECENSEMENT (non modifié)

| Écran / composant | Composant | Même symptôme transversal ? |
|-------------------|-----------|------------------------------|
| Dépôt saisie | `ProgrammeSearchSelect` | **oui — corrigé** |
| Autres écrans `ProgrammeSearchSelect` | — | **aucun** (usage unique dépôt) |
| AppShell switcher | API `/api/dashboard/programmes-switcher` + filtre client | non (charge tout) |
| Programmes/Index | filtres statut locaux | non |
| Annuaire / Juridique / Tâches / registres / Admin / Factures Global / Mes Tâches | selects ad hoc ou listes | hors `ProgrammeSearchSelect` ; pas le couple min_q/filtre enseigne |
| `FournisseurSearchSelect` (#1156) | min 2 car. pour **tous** | **pas le même défaut** (pas de filtre confort asymétrique) |

## DÉPLOIEMENT

```text
git pull origin main
php artisan view:clear
php artisan journal:sync
```

Pas de migrate. Pas de route:clear nécessaire (route search inchangée).

## LEÇON

« Codes vides = tout » côté SQL ne suffit pas : l’UX focus (`min_q`) doit
suivre la même sémantique, sinon le transversal est pénalisé. Troncature 40
silencieuse = tromperie — toujours l’annoncer.
