# PRD 3.5 — Dashboard compact: import pe prima pagina, status cu bife, Trimiteri lizibile, Mapari complete

**Stare**: inchis

> Proces complet: `docs/ROADMAP.md` §5. Contractul RAR (sursa de adevar): `docs/api-rar-contract.md`.
> Starea trece: `draft → aprobat → in-executie → verify-pass → inchis` (actualizata de lead).
> Aceasta e o livrabila de **UI/UX** — continua 3.4 (`prd-3.4-ux-dashboard-web.md`). Atinge stratul web
> (Jinja2 + HTMX, zero build) si rute de prezentare/listare. **Nu** modifica `worker/`, `mapping.py`
> (logica de rezolvare), `idempotency.py`, masina de stari submissions sau contractul RAR.

## 1. Obiectiv

Feedback de utilizare pe interfata 3.4: e mai buna, dar (a) importul — operatia principala — e ascuns
intr-un tab, (b) bara de status are fonturi mici si etichete fara bife, (c) pagina "Coada" e neintuitiva
(coloane tehnice "HTTP RAR", stare in engleza "sent", "Motiv" gol, si **nu se vede la ce comanda se
refera** — doar `idPrezentare`), (d) pagina "Mapari" arata doar operatiile nerezolvate — maparile deja
salvate par pierdute, iar maparile de coloane nu se vad nicaieri.

Livrabila rezolva aceste patru zone, fara a schimba comportamentul backend de trimitere:

1. **Acasa = ecranul de import.** Prima pagina arata direct caseta de upload (importul e operatia
   principala), sub o bara de status compacta; ghidul "primii pasi" si link-urile de ajutor coboara pe
   un singur rand discret. Tab-ul "Import" separat dispare (era acelasi flux).
2. **Bara de status compacta, font normal, cu bife.** Doua randuri: sus doua bife (verde/rosu) pentru
   "Trimitere automata" si "Legatura RAR" + "Ultima autentificare RAR" in format `dd.mm.yyyy hh24:mi:ss`;
   jos contoarele (in asteptare / declarate / blocate).
3. **"Coada" → "Trimiteri", lizibila.** Coloane umane (Stare in romana via `labels.py`, Vehicul,
   Operatie, Data prestatie, Nr. prezentare RAR, Actualizat, Motiv uman). Click pe rand → detaliu complet
   (toate campurile, inclusiv codul HTTP tehnic si motivul integral). Detaliile comenzii se citesc din
   `payload_json` (text JSON simplu, nu criptat).
4. **"Mapari" complet — trei sectiuni.** (1) De rezolvat (`needs_mapping`, ca acum), (2) Mapari operatii
   salvate — `operations_mapping` editabil (schimba cod RAR / auto-send / sterge), (3) Formate de coloane
   salvate — `column_mappings` per semnatura (vezi coloanele, format data, editeaza/sterge). "Cont" ramane
   doar cheie API + creds RAR.

Decizii de layout confirmate cu utilizatorul (AskUserQuestion, cu preview): Acasa=Import direct; status pe
doua randuri cu bife; Trimiteri cu detalii in tabel + expand; toate maparile intr-un singur loc ("Mapari").

## 2. Non-Goals (anti scope-creep)

- **Fara schimbari de backend de trimitere**: worker, mapare op→cod (rezolvarea), idempotenta,
  reconciliere, masina de stari submissions raman neatinse. Doar prezentare + listare/editare web.
- **Fara endpoint-uri API noi `/v1/*`** si fara schimbari de schema SQL **de structura**. Tabelele
  `operations_mapping` / `column_mappings` exista deja; doar le expunem/edita prin rute web. Exceptie
  controlata (acceptata la CEO review): US-009 poate adauga **un index** pe `submissions` pentru filtrare
  (nu coloane noi, nu tabele noi).
- **Fara framework JS / build step**: ramane Jinja2 + HTMX + CSS in `base.html`. Eventualul JS e vanilla
  inline, minim. Fara React/Vue/Tailwind/bundler.
- **Fara rescriere a fluxului de import** (parsare, mapare coloane, preview, commit raman ca logica) —
  doar muta upload-ul pe Acasa si imbraca rezultatul.
- **Fara redesign login/signup/admin** dincolo de aplicarea acelorasi clase/etichete daca e trivial.
- **Fara i18n / tema light**: texte in romana hardcodate, paleta dark din `base.html`.
- **Fara modificari de worker / masina de stari / reconciliere**: US-010 (corectie inline) doar
  re-valideaza (`validation.py`) si re-pune randul in `queued` (re-enqueue), fara sa atinga worker-ul.
- **Editarea de continut e permisa DOAR pentru randuri ne-trimise blocate** (`needs_data`/`needs_mapping`),
  prin US-010. Randurile `sent`/`FINALIZATA` raman **read-only** (terminal la RAR, fara anulare/corectie).

## 3. Stories atomice

> Fiecare story: cea mai mica unitate care lasa sistemul functional. Backend + UI pentru acelasi
> comportament = 2 stories. Toate atingerile sunt in `app/web/` (templates + routes + `labels.py`).
> Verificare E2E = browser HTMX pe `http://localhost:8000/` (Playwright MCP sau `/browse`).
> **Regula de aur**: fluxul import → commit → worker → FINALIZATA la RAR test NU are voie sa se strice,
> iar deep-link-urile `?tab=` raman valide.

### US-001: Bara de status compacta cu bife + data formatata (backend format + UI)
**Ca** operator de service **vreau** o bara de status compacta, cu font normal, cu bife clare si data
ultimei autentificari completa **pentru ca** sa vad starea sistemului dintr-o privire, fara sa ghicesc.

- **Depinde de**: —
- **Fisiere**: `app/web/labels.py` (helper format data), `app/web/routes.py` (`fragment_status`),
  `app/web/templates/_status.html`, `tests/test_web_labels.py`, `tests/test_web_status.py` (~5 fisiere)
- **Test intai (RED)**: `test_format_data_rar` — `2026-06-18T14:30:22` (sau forma stocata in
  `worker_heartbeat.last_rar_login_ok`) → `"18.06.2026 14:30:22"`; valoare lipsa → `"—"`; format invalid
  → fallback grijuliu (nu arunca). `tests/test_web_status.py::test_status_are_bife` — fragmentul randat
  contine bifa verde cand worker viu + RAR ok, rosie cand oprit/indisponibil.
- **Continut**:
  - Helper pur in `labels.py` (ex. `format_data_rar(raw) -> str`) care produce `dd.mm.yyyy hh24:mi:ss`.
  - `_status.html` rescris pe **doua randuri**: rand 1 = `[bifa] Trimitere automata activa` +
    `[bifa] Legatura RAR OK` + `Ultima autentificare RAR: <data>`; rand 2 = `In asteptare: N |
    Declarate la RAR: N | Blocate: N`. Font normal (13-14px), fara `font-size:11px/12px`.
  - **Accesibilitate (design review)**: starea NU se distinge doar prin culoare. Glifa difera —
    `&#10003;` (✓) pentru activ/OK, `&#10007;` (✗) pentru oprit/indisponibil — plus textul difera
    (activa/oprita, functionala/indisponibila). Culoarea (verde/rosu) e redundanta, nu singurul semnal.
  - Pastreaza avertismentul "cont in asteptare de activare" (regresia reparata in 3.4) si poll-ul 15s.
- **Acceptance criteria**:
  - [x] Data ultimei autentificari apare ca `dd.mm.yyyy hh24:mi:ss` (test pe helper, pur).
  - [x] Doua stari binare au bifa verde/rosie dupa starea reala (worker viu/mort, RAR ok/indisponibil).
  - [x] Niciun text din bara nu mai foloseste `font-size` sub 13px.
  - [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: browser pe `/` — bara compacta, bife corecte, data formatata romaneste.

### US-002: Acasa devine ecranul de import (upload inline + help compact, scoatere tab Import)
**Ca** operator **vreau** sa import direct de pe prima pagina **pentru ca** importul e ce fac cel mai des.

- **Depinde de**: US-001 (bara status finala deasupra)
- **Fisiere**: `app/web/templates/dashboard.html` (lista tab-uri), `app/web/routes.py` (`_TABS_VALIDE`,
  `_render_panel`, `fragment_import`/`fragment_acasa`), `app/web/templates/_acasa.html`,
  `app/web/templates/_upload.html` (reutilizat), `tests/test_web_dashboard.py` (~5 fisiere)
- **Test intai (RED)**: `test_acasa_contine_upload` — fragmentul `/_fragments/acasa` contine formularul de
  upload (`hx-post` catre ruta de import existenta); `test_tab_import_redirect` — `?tab=import` nu mai e
  tab separat (redirect la `acasa` sau eticheta absenta din tab-bar), iar deep-link-ul nu da 404.
- **Continut**:
  - `_acasa.html`: caseta de upload (din `_upload.html`) ca prim element; sub ea, "primii pasi" pe un
    **singur rand** compact (`o Cont RAR  o Cheie API  * Import`) + un rand de ajutor cu link-uri mici
    (Ghid / Coada / Mapari). Upload-ul porneste stepper-ul existent (target `#import-section`).
  - **Ierarhie (design review)**: upload-ul e vizual DOMINANT (titlu clar + caseta mare); checklist-ul
    "primii pasi" si ajutorul sunt subordonate (font mai mic, sub upload), nu concureaza cu el. Prima
    pagina are un singur centru de greutate: importa un fisier.
  - Scoate `("import", ...)` din lista de tab-uri din `dashboard.html`; pastreaza `?tab=import` valid
    (redirect la `acasa`) pentru orice URL salvat. Stepper-ul, mapare-coloane, preview, commit raman.
  - "Incarca alt fisier" din stepper trimite inapoi la Acasa (nu la un tab inexistent).
- **Acceptance criteria**:
  - [x] Pe `/` (tab implicit Acasa) caseta de upload e vizibila fara click suplimentar.
  - [x] Tab-bar-ul nu mai are "Import"; `?tab=import` nu da 404 (redirect/echivalent la Acasa).
  - [x] Fluxul upload → mapare coloane → preview → commit functioneaza neschimbat (target/csrf intacte).
  - [x] Link-urile de ajutor + checklist incap pe randuri compacte, font normal.
  - [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: browser — incarc un fisier de pe Acasa, parcurg stepper-ul pana la commit.

### US-003: Prezentare detalii comanda din payload (backend pur, testat)
**Ca** operator **vreau** ca fiecare trimitere sa-mi spuna despre ce vehicul/operatie e vorba
**pentru ca** acum vad doar `idPrezentare` si nu stiu ce comanda am trimis si ce nu.

- **Depinde de**: —
- **Fisiere**: `app/payload_view.py` (nou, helper PARTAJAT web+API), `app/api/v1/router.py` (refactor
  mic: `GET /v1/prezentari` foloseste acelasi helper, nu o copie), `tests/test_payload_view.py`
  (~3 fisiere). Decizie eng review (DRY): un singur modul de extragere payload→campuri afisabile, ca sa
  nu diverge intre canalul web si cel API (router.py:247-264 face azi extragerea sa proprie).
- **Test intai (RED)**: `test_detalii_din_payload` — dat un `payload_json` (text JSON: `vin`, `numar`/
  `numarInmatriculare`, `odometru_final`, `data_prestatie`, `cod_prestatie`/`cod_op_service`/`denumire`),
  helperul intoarce un dict de prezentare `{vehicul_nr, vin_scurt, operatie, data_prestatie, odometru,
  cod}`; `test_payload_partial` — campuri lipsa → `"—"`/gol fara exceptie; `test_payload_invalid` →
  fallback grijuliu (nu arunca); `test_payload_coercion_excel` — odometru `"123.0"`/numeric si VIN
  non-string (coercion Excel) afisate curat (`str()` defensiv), chei API vs import (`numar` vs
  `numarInmatriculare`) ambele rezolvate.
- **Continut**: functie pura care primeste randul submission (sau `payload_json`) si produce campurile
  de afisat. `vin_scurt` = forma trunchiata pentru tabel (VIN integral ramane in detaliu). Citeste cheile
  defensiv (canalele API si import pot diferi usor; `payload_json` e plaintext — vezi `router.py`).
- **Acceptance criteria**:
  - [x] Helper pur (fara DB, fara request), 100% acoperit de teste pe cazurile plin/partial/invalid.
  - [x] Nu arunca niciodata pe payload malformat (degradeaza la `—`).
  - [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: — (acoperit de US-004).

### US-004: "Coada" → "Trimiteri" — tabel lizibil + detaliu la click
**Ca** operator **vreau** un tabel de trimiteri pe care il inteleg, cu detaliile comenzii
**pentru ca** "HTTP RAR"/"sent"/"Motiv gol" nu-mi spun nimic si nu stiu la ce comanda se refera randul.

- **Depinde de**: US-003 (prezentare payload), US-001 (`labels.py` pt stare umana)
- **Fisiere**: `app/web/templates/_coada.html` (titlu + tab label), `app/web/templates/_submissions.html`,
  `app/web/templates/dashboard.html` (eticheta tab "Coada"→"Trimiteri", `tab_id` ramane `coada`),
  `app/web/routes.py` (`fragment_submissions` imbogatit + ruta detaliu `/_fragments/trimitere/{id}`),
  `app/web/templates/_trimitere_detaliu.html` (nou), `tests/test_web_submissions.py` (~6 fisiere)
- **Test intai (RED)**: `test_submissions_coloane_umane` — tabelul contine antete RO (Stare, Vehicul,
  Operatie, Data prestatie, Nr. prezentare RAR, Motiv) si **nu** mai contine "HTTP RAR" ca antet principal,
  nici status brut englezesc afisat ca atare (folose `labels.eticheta_stare`); `test_detaliu_trimitere` —
  `/_fragments/trimitere/{id}` intoarce detaliul complet scoped pe cont (404 cross-account).
- **Continut**:
  - Eticheta tab "Coada" → **"Trimiteri"** (pastreaza `tab_id="coada"` ca deep-link `?tab=coada` sa ramana
    valid). Titlu sectiune "Trimiteri catre RAR".
  - `_submissions.html`: coloane = `#`, **Stare** (`eticheta_stare` text RO + pill culoare), **Vehicul**
    (nr + VIN scurt, din US-003), **Operatie**, **Data prestatie**, **Nr. prezentare RAR** (`id_prezentare`
    sau `—`), **Actualizat**, **Motiv** (text uman; pt `needs_data` → ex. "lipsa odometru"). Codul HTTP
    tehnic NU mai e coloana principala — coboara in detaliu.
    `query`-ul include `payload_json` (pt US-003) pe langa campurile actuale.
  - Click pe rand → `hx-get="/_fragments/trimitere/{id}"` → `_trimitere_detaliu.html` cu toate campurile
    (vehicul integral, operatie+cod, odometru, data, stare, `rar_status_code`, `rar_error` integral,
    retry, timestamps). Scoped pe contul sesiunii.
  - **Detaliul se randeaza intr-un PANOU DEDICAT** (ex. `#trimitere-detaliu` sub/langa tabel), NU inline
    in randul din tabel. Motiv (CEO review, Finding #1): `_submissions.html` are `hx-trigger="every 10s"`;
    un expand inline ar fi sters de poll-ul de refresh. Panoul dedicat nu e prins de poll.
  - **Vizibilitate (design review)**: la deschidere, panoul trebuie sa fie evident — scroll-to panou
    si/sau evidentiere a randului selectat in tabel. Altfel pare ca "nu s-a intamplat nimic" (panoul
    apare sub fold).
- **Acceptance criteria**:
  - [x] Antetele coloanelor sunt in romana; starea afisata e text uman (nu "sent").
  - [x] Fiecare rand arata vehicul + operatie + data (din payload), nu doar `idPrezentare`.
  - [x] Click pe rand deschide detaliul complet, scoped pe cont (404 la id-ul altui cont).
  - [x] Motivul pentru `needs_data`/`error` apare in coloana Motiv (nu gol cand exista `rar_error`).
  - [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: browser — tab Trimiteri, identific un rand dupa vehicul, deschid detaliul.

### US-005: Listare + editare mapari operatii salvate (backend)
**Ca** operator **vreau** sa vad si sa pot schimba maparile operatie→cod pe care le-am salvat
**pentru ca** acum, dupa ce mapez si trimit, ele dispar din ecran si par pierdute.

- **Depinde de**: —
- **Fisiere**: `app/web/routes.py` (query `operations_mapping` + rute edit/delete),
  `tests/test_web_mapari_salvate.py` (~2 fisiere)
- **Test intai (RED)**: `test_lista_mapari_salvate` — intoarce randurile `operations_mapping` ale contului
  cu `nume_prestatie` jonctionat din nomenclator; `test_editeaza_mapare_salvata` — POST schimba
  `cod_prestatie`/`auto_send` doar pe contul propriu (cross-account interzis), verifica `cod_prestatie`
  exista in nomenclator (ca la `/mapari` actual); `test_sterge_mapare_salvata` — DELETE scoped pe cont.
- **Continut**: functie de listare scoped pe cont + rute web `POST /mapari/salvate` (update) si
  `POST /mapari/salvate/sterge` (delete) cu CSRF + PRG/HTMX swap. **Nu** schimba logica de rezolvare din
  `mapping.py`; doar CRUD pe tabela existenta. **Re-rezolvare obligatorie** (promovata din optional la CEO
  review): la schimbarea unui cod, submission-urile blocate (`needs_mapping`) pe acel `cod_op_service` se
  re-rezolva automat, reutilizand helperul de re-rezolvare existent (`reresolve_account`/echivalent) —
  fara cod nou de rezolvare. Inchide pain-ul "am mapat dar nu vad efectul".
- **Acceptance criteria**:
  - [x] Listarea e scoped pe contul sesiunii (fara leak cross-account — vezi C6 din 3.3a).
  - [x] Editarea respinge cod inexistent in nomenclator si cont strain.
  - [x] Stergerea afecteaza doar contul propriu.
  - [x] La editarea unui cod, submission-urile `needs_mapping` pe acel `cod_op_service` se deblocheaza
        automat (test: rand blocat → editez maparea → randul trece din `needs_mapping`).
  - [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: — (acoperit de US-006).

### US-006: Listare + editare/stergere formate de coloane salvate (backend)
**Ca** operator **vreau** sa vad formatele de import memorate si sa le pot edita/sterge
**pentru ca** nu stiu ce coloane sunt retinute si ce se intampla cand vin cu un fisier cu alte coloane.

- **Depinde de**: —
- **Fisiere**: `app/web/routes.py` (query `column_mappings` + rute edit/delete),
  `tests/test_web_formate_coloane.py` (~2 fisiere)
- **Test intai (RED)**: `test_lista_formate_coloane` — intoarce randurile `column_mappings` ale contului
  cu coloanele (din `signature_coloane`/`json_mapare`), `format_data` si un contor de utilizare (cate
  `import_batches`/submissions folosesc acea semnatura — best-effort, sau omis daca nu e ieftin);
  `test_sterge_format_coloane` — DELETE scoped pe cont; `test_editeaza_format_coloane` — POST schimba
  `json_mapare`/`format_data` pentru o semnatura, scoped pe cont.
- **Continut**: listare scoped pe cont + rute `POST /formate-coloane/...` (edit/delete) cu CSRF.
  Comportament documentat (nu cod nou de import): un fisier cu **alte coloane** = semnatura noua = format
  nou separat (`UNIQUE (account_id, signature_coloane)`), nu suprascrie; **acelasi antet** = maparea
  retinuta se reaplica automat la urmatorul import (comportament existent din 2.4).
- **Acceptance criteria**:
  - [x] Listarea arata coloanele fiecarui format + format data, scoped pe cont.
  - [x] Stergerea/editarea afecteaza doar contul propriu (fara leak cross-account).
  - [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: — (acoperit de US-006 UI in US-007).

### US-007: Pagina "Mapari" cu trei sectiuni (UI)
**Ca** operator **vreau** o singura pagina "Mapari" cu tot ce tine de mapari
**pentru ca** sa rezolv ce e blocat si sa-mi gestionez maparile salvate (operatii + coloane) intr-un loc.

- **Depinde de**: US-005, US-006
- **Fisiere**: `app/web/templates/_mapari.html`, `app/web/routes.py` (`_render_mapari`/`fragment_mapari`
  imbogatit sa treaca cele 3 seturi de date), `app/web/templates/_cont.html` (ramane doar cheie API +
  creds RAR — fara mapari), `tests/test_web_mapari_ui.py` (~4 fisiere)
- **Test intai (RED)**: `test_mapari_trei_sectiuni` — fragmentul `/_fragments/mapari` contine cele trei
  sectiuni (De rezolvat / Mapari operatii salvate / Formate de coloane salvate); `test_cont_fara_mapari`
  — `/_fragments/cont` nu mai contine sectiuni de mapari.
- **Continut**:
  - `_mapari.html` reorganizat pe 3 sectiuni: (1) **De rezolvat** = `pending_unmapped` (ca acum), (2)
    **Mapari operatii salvate** = lista din US-005, fiecare cu select cod RAR + checkbox auto-send + buton
    sterge (HTMX swap pe `#mapari-section`), (3) **Formate de coloane salvate** = lista din US-006, fiecare
    cu coloanele afisate + format data + actiuni edit/sterge. Empty states prietenoase per sectiune.
  - `_cont.html`: confirma ca nu contine mapari (cheie API + creds RAR doar).
- **Acceptance criteria**:
  - [x] "Mapari" arata cele 3 sectiuni; sectiunile goale au mesaj prietenos, nu lipsesc tacit.
  - [x] Salvarea/stergerea reincarca sectiunea via HTMX fara reload de pagina.
  - [x] "Cont" nu mai contine mapari.
  - [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: browser — vad maparile salvate dupa ce am mapat+trimis; editez un cod; vad formatul
  de coloane al fisierului importat.

### US-008: Feedback clar pentru randuri respinse la import (lipsa odometru / needs_data)
**Ca** operator **vreau** sa inteleg de ce un rand nu s-a importat **pentru ca** am incarcat un fisier
fara odometru si pur si simplu "nu s-a importat", fara explicatie.

- **Depinde de**: US-002 (importul pe Acasa)
- **Fisiere**: `app/web/templates/_preview_import.html`, `app/web/routes.py` (mesaj preview, daca e cazul),
  `tests/test_web_preview_motive.py` (~3 fisiere)
- **Test intai (RED)**: `test_preview_arata_motiv_needs_data` — un rand `needs_data` din lipsa odometru
  apare in preview cu motiv explicit ("lipsa odometru" / mesajul de validare), nu doar numarat la "blocate".
- **Continut**: preview-ul de import (cele 6 stari deja existente din 2.5) afiseaza pentru randurile
  `needs_data`/`needs_review` **motivul** (din validare/`error`), ca operatorul sa stie ce sa corecteze.
  Reutilizeaza mesajele de validare existente (`validation.py`); fara reguli noi de validare.
- **Acceptance criteria**:
  - [x] Un rand fara odometru apare explicit cu motivul, nu doar in contorul "blocate".
  - [x] Contoarele preview (ok/needs_data/...) raman corecte.
  - [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: browser — import un fisier fara odometru, vad in preview de ce e blocat randul.

### US-009: Filtrare/cautare in "Trimiteri" (stare / vehicul / data prestatie)
**Ca** operator cu sute de trimiteri **vreau** sa filtrez lista **pentru ca** sa gasesc rapid o comanda
sau toate cele blocate, fara sa derulez tot.

- **Depinde de**: US-003, US-004
- **Fisiere**: `app/web/routes.py` (`fragment_submissions` accepta parametri de filtru),
  `app/web/templates/_coada.html` (controale filtru), `app/web/templates/_submissions.html`,
  `app/schema.sql` (index pe `submissions(account_id, status)` daca lipseste — exista deja
  `idx_submissions_account_status`, deci probabil zero schimbari), `tests/test_web_filtrare.py` (~4 fisiere)
- **Test intai (RED)**: `test_filtru_stare` — `?status=needs_data` intoarce doar acele randuri (scoped pe
  cont); `test_filtru_vehicul` — cautare text pe nr/VIN (case-insensitive); `test_filtru_data` — interval
  `data_prestatie`. Toate scoped pe cont, fara leak.
- **Continut**: controale HTMX (select stare + input text vehicul + interval data) care reincarca
  `/_fragments/submissions` cu query string; filtrarea pe vehicul/data se face dupa parsarea
  `payload_json` (text JSON), filtrarea pe stare in SQL (foloseste indexul existent). Pastreaza poll-ul
  (poll-ul re-trimite filtrul curent). Empty state "nimic pe filtrul curent" **+ buton „sterge filtrele"**
  (design review) cand exista un filtru activ.
- **Acceptance criteria**:
  - [x] Filtrele combina (stare + vehicul + data) si raman aplicate la refresh-ul de 10s.
  - [x] Filtrarea e scoped pe cont (fara leak cross-account).
  - [x] Nu necesita coloane/tabele noi (cel mult confirma indexul existent).
  - [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: browser — filtrez dupa "blocate" si dupa un numar de inmatriculare.

### US-010: Corectie inline pentru randuri ne-trimise blocate (needs_data)
**Ca** operator **vreau** sa completez un camp lipsa (ex. odometru) direct pe randul blocat si sa-l
re-trimit **pentru ca** acum trebuie sa refac tot fisierul de import doar pentru un camp.

- **Depinde de**: US-003, US-004
- **Fisiere**: `app/web/routes.py` (ruta `POST /trimitere/{id}/corecteaza`),
  `app/web/templates/_trimitere_detaliu.html` (form de corectie pe randurile `needs_data`/`needs_mapping`),
  `tests/test_web_corectie.py` (~3 fisiere)
- **Test intai (RED)**: `test_corectie_needs_data` — un rand `needs_data` din lipsa odometru: completez
  odometru → re-validare (`validation.py`) → status `queued`, payload actualizat, idempotency recalculata;
  `test_corectie_sent_interzis` — un rand `sent`/`FINALIZATA` NU poate fi editat (403/refuz, read-only);
  `test_corectie_coliziune_idempotency` — daca noua cheie coincide cu alt submission existent, corectia se
  opreste cu mesaj „exista deja o trimitere identica (rand #N)", fara IntegrityError/500 si fara duplicat;
  `test_corectie_cont_strain` — interzis cross-account.
- **Continut**: pe panoul de detaliu (US-004), pentru randuri `needs_data`/`needs_mapping`, un mini-form
  cu campurile relevante; la submit re-valideaza prin `validation.py` (fara reguli noi), reconstruieste
  `payload_json` + recalculeaza `idempotency_key` (canonicalize → build_key, ca la enqueue), seteaza
  status `queued` si `next_attempt_at=now`. **Nu** atinge worker-ul / masina de stari (doar re-enqueue).
  Randurile `sent`/`FINALIZATA` raman read-only (gard explicit).
  - **Coliziune idempotency** (decizie eng review): INAINTE de UPDATE, verifica daca noua `idempotency_key`
    exista deja pe alt submission al contului; daca da, opreste corectia si afiseaza „exista deja o
    trimitere identica (rand #N)" cu link la acel rand. Fara 500, fara duplicat. (UNIQUE pe
    `idempotency_key` ar arunca IntegrityError altfel.)
  - **Loc + eroare (design review)**: formul de corectie traieste IN panoul de detaliu (US-004), nu intr-o
    sectiune separata. Eroarea de validare se afiseaza clar pe campul invalid (nu un mesaj generic sus).
- **Acceptance criteria**:
  - [x] Un rand `needs_data` corectat valid trece in `queued` cu payload + idempotency actualizate.
  - [x] Randurile `sent`/`FINALIZATA` nu pot fi editate (gard testat).
  - [x] Coliziunea de idempotency e prinsa si comunicata clar (rand-duplicat identificat), fara 500.
  - [x] Corectia respinge date inca invalide (mesaj de validare) si conturi straine.
  - [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: browser — corectez odometru pe un rand blocat, il vad trecand in asteptare;
  cu worker `--send` pe RAR test, ajunge FINALIZATA.

### US-011: Badge cu contoare pe tab-uri (atentionari)
**Ca** operator **vreau** sa vad pe eticheta tab-ului cate lucruri ma asteapta **pentru ca** sa stiu
unde sa intervin fara sa deschid fiecare tab.

- **Depinde de**: US-007 (mapari), US-004 (trimiteri)
- **Fisiere**: `app/web/templates/dashboard.html` (badge pe eticheta tab), `app/web/routes.py`
  (contoarele deja calculate in `fragment_status`/panel context, transmise la tab-bar),
  `tests/test_web_badge.py` (~3 fisiere)
- **Test intai (RED)**: `test_badge_mapari` — cand exista operatii `needs_mapping`, eticheta "Mapari"
  poarta un numar; `test_badge_trimiteri_blocate` — cand exista randuri blocate, "Trimiteri" poarta
  marcaj; `test_badge_zero_ascuns` — fara nimic de rezolvat, niciun badge.
- **Continut**: numar mic pe eticheta tab-ului, alimentat din contoarele existente (needs_mapping pt
  Mapari, blocate pt Trimiteri). Pur prezentare; reutilizeaza ce calculeaza deja status-ul. Accesibil
  (text in `aria-label`, nu doar culoare).
- **Acceptance criteria**:
  - [x] Badge apare doar cand contorul > 0; dispare la zero.
  - [x] Numarul e corect si scoped pe cont.
  - [x] `aria-label`-ul tab-ului include sensul badge-ului (nu doar pastila colorata).
  - [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: browser — cu o operatie nemapata, "Mapari" arata "(1)"; dupa rezolvare, dispare.

## 4. Riscuri

- **Scoaterea tab-ului "Import" rupe deep-link-uri/teste** (`?tab=import`, link-uri din `_acasa.html`,
  "Incarca alt fisier" din stepper). Mitigare: `?tab=import` → redirect la `acasa`; grep dupa toate
  referintele `tab=import`/`/_fragments/import` inainte de stergere; test dedicat (US-002).
- **Citirea `payload_json` pentru detalii** depinde de forma payload-ului, care difera usor intre canalul
  API si import. Mitigare: helper pur defensiv cu fallback `—` (US-003), testat pe ambele forme; nu se
  bazeaza pe o cheie obligatorie.
- **Leak cross-account** pe noile listari/editari (mapari salvate, formate coloane, detaliu trimitere).
  Mitigare: toate scoped pe contul sesiunii cu regula NULL→1 (C6/3.3a), test cross-account per ruta noua,
  re-folosind pattern-ul `account_scope_clause` (3.2).
- **Afisare PII (VIN/nr) pe ecran** in Trimiteri. Acceptabil: e proprietatea contului autentificat, scoped
  pe sesiune; nu se expune in loguri si nu apare in raspunsuri 422 (handler existent in `main.py`).
- **Aglomerare "Mapari" cu 3 sectiuni**. Mitigare: empty states + colaps vizual cand o sectiune e goala.

## 5. Intrebari deschise

> Se rezolva cu utilizatorul ÎNAINTE de executie (poarta de aprobare PRD).

- ~~Detaliul trimiterii: expand inline sau panou?~~ **REZOLVAT (CEO review):** panou dedicat
  `#trimitere-detaliu`, nu inline — altfel poll-ul de 10s sterge expand-ul (vezi US-004).
- "Editare" format de coloane: redeschidem editorul de mapare campuri (`_mapcoloane.html`) prefiltrat pe
  semnatura salvata, sau permitem doar stergere + re-import? (propunere MVP: stergere + vizualizare; edit
  de campuri = nice-to-have daca incape in story).
- Contor de utilizare pe formate de coloane: il afisam (cost de query) sau il omitem in v1? (propunere:
  omitem daca nu e ieftin — nu e critic).

## 6. Valuri de executie (graful de dependente)

```
Val 1: [US-001] [US-003] [US-005] [US-006]   ← fara dependente, fisiere distincte → paralel (max 3-4)
Val 2: [US-002] [US-004]                      ← US-002←US-001 ; US-004←US-003+US-001
Val 3: [US-007] [US-008] [US-009] [US-010]    ← US-007←US-005+US-006 ; US-008←US-002 ;
                                                 US-009←US-003+US-004 ; US-010←US-003+US-004
Val 4: [US-011]                               ← US-011←US-007+US-004 (contoare din ambele)
```

> Atentie la fisiere partajate intre valuri: `routes.py`, `dashboard.html`, `_status.html`,
> `_submissions.html` si `_trimitere_detaliu.html` (US-004/009/010) sunt atinse de mai multe stories —
> secventiaza-le sau worktree + merge de catre lead (vezi anti-pattern ROADMAP). In special US-009 si
> US-010 ating ambele acelasi panou de detaliu/tabel — ruleaza-le secvential, nu in paralel.

## 7. Review-uri de plan

### CEO review (2026-06-19) — SELECTIVE EXPANSION

- **Abordare aleasa**: A (cele 8 stories complete), cu expansiuni cherry-pick acceptate.
- **Expansiuni acceptate** (adaugate ca stories): US-009 filtrare/cautare Trimiteri, US-010 corectie
  inline pentru `needs_data`, US-011 badge contoare pe tab-uri.
- **Finding promovat**: re-rezolvarea automata la editarea unei mapari salvate, din "optional" in scope
  obligatoriu (US-005).
- **Finding de robustete inchis**: detaliul Trimiteri merge in **panou dedicat**, nu inline — altfel
  poll-ul de 10s din `_submissions.html` ar sterge expand-ul (US-004 + §5).
- **Schimbare de scope fata de draft**: editarea de continut e acum permisa pentru randuri ne-trimise
  blocate (US-010); `sent`/`FINALIZATA` raman read-only. Vezi §2 non-goals actualizat.

### Eng review (2026-06-19)

- **Step 0**: 11 stories e mult, dar e o livrabila UI sparta in stories atomice TDD (conventia
  proiectului), nu overbuild. Risc real = contentia pe fisiere partajate, nu numarul. Scope confirmat.
- **Decizie idempotency (US-010)**: corectia detecteaza coliziunea de `idempotency_key` INAINTE de UPDATE
  si o comunica clar (rand-duplicat identificat), fara 500/duplicat.
- **Decizie DRY (US-003)**: helper partajat `app/payload_view.py` folosit si de web si de
  `GET /v1/prezentari` — o singura sursa de extragere payload→campuri.
- **Aserțiune adaugata**: test ca `submissions.payload_json` e plaintext (US-003) — daca vreodata se
  cripteaza, testul cade si stim sa adaptam.
- **Plafon perf notat (US-009)**: filtrarea pe vehicul/data parseaza payload in Python per rand; OK la
  scara actuala, `json_extract()` daca devine necesar. Nu blocheaza.
- **Secventiere intarita**: US-004 (schelet tabel+panou) → apoi US-009 si US-010, strict secvential pe
  `_submissions.html`/`_trimitere_detaliu.html`/`fragment_submissions`. NU paraleliza valul 3 pe ele.

### Design review (2026-06-19) — rating 7/10 → 9/10 dupa fixuri

Layout-urile au fost alese vizual cu utilizatorul (mockup-uri ASCII in AskUserQuestion). Patru cerinte
de design adaugate ca AC:

- **Accesibilitate bife (US-001)**: glife distincte (✓/✗) + text, nu doar culoare (daltonism).
- **Ierarhie Acasa (US-002)**: upload-ul vizual dominant; checklist + ajutor subordonate.
- **Vizibilitate panou (US-004)**: scroll-to / evidentiere rand la deschiderea detaliului.
- **Stari de eroare (US-009/010)**: „sterge filtrele" + empty state; eroare de validare pe campul invalid,
  in panoul de detaliu (decizie utilizator).

## 8. Raport review-uri de plan (consolidat)

| Review | Data | Rezultat | Decizii cheie |
|---|---|---|---|
| CEO (SELECTIVE EXPANSION) | 2026-06-19 | Aprobat cu expansiuni | Abordare A; +US-009/010/011; re-rezolvare US-005 obligatorie; detaliu in panou (nu inline) |
| Eng | 2026-06-19 | Aprobat | Coliziune idempotency US-010 detectata pre-UPDATE; helper partajat `payload_view.py`; secventiere val 3 |
| Design | 2026-06-19 | Aprobat (9/10) | Bife accesibile; ierarhie Acasa; vizibilitate panou; stari de eroare |

**Scope final**: 11 stories (US-001…US-011), in 4 valuri. Backend de trimitere (worker, masina de stari,
reconciliere, idempotenta ca logica) neatins; singura mutatie de date noua = corectia US-010 (re-enqueue
randuri ne-trimise) + posibil un index (US-009). **DECIZII NEREZOLVATE**: niciuna care sa blocheze
executia — raman 2 intrebari de finete in §5 (editare format coloane: stergere+vizualizare in MVP;
contor utilizare formate: omis daca nu e ieftin), ambele cu propunere si fara impact pe arhitectura.

Urmatorul pas (ROADMAP §5): `**Stare**: aprobat` → EXECUTE (TDD pe valuri). Poarta umana: aprobarea PRD.

---

## Raport VERIFY

> Completat de subagentul verificator (context curat, ROADMAP §5.6) — 2026-06-19.

**Verdict global: PASS.** Toate cele 11 stories verificate prin cod + teste. Regresia de aur intacta.
Non-Goals respectate.

- **Suita**: `python3 -m pytest -q` → **483 passed** (de la 434 baseline 3.4; +49 teste noi). Verde.
- **PASS/FAIL per story**: toate US-001…US-011 PASS. Dovezi (verificator context curat):
  - US-001: `format_data_rar` (dd.mm.yyyy hh24:mi:ss, lipsa→"—", invalid→fallback fara exceptie);
    bife accesibile cu glife `&#10003;`/`&#10007;` + text distinct + culoare redundanta; fara font-size <13px.
  - US-002: Acasa include upload (`hx-post="/_import/upload"`), tab Import scos, `?tab=import`→Acasa (fara 404).
  - US-003: `app/payload_view.py` pur/defensiv, refolosit de `GET /v1/prezentari` (DRY), payload_json brut neexpus.
  - US-004: coloane RO, stare umana, detaliu in panou dedicat `#trimitere-detaliu` (nu inline), 404 cross-account.
  - US-005/006: CRUD `operations_mapping`/`column_mappings` scoped pe cont, re-rezolvare la edit cod.
  - US-007: `_fragments/mapari` 3 sectiuni cu empty states; `_fragments/cont` fara mapari.
  - US-008: preview arata mesajul de validare pentru randuri needs_data.
  - US-009: filtre stare(SQL)/vehicul/data scoped pe cont; empty state cu "sterge filtrele".
  - US-010: corectie needs_data→queued cu payload+idempotency recalculate; sent read-only (403);
    coliziune idempotency prinsa pre-UPDATE; cross-account 404.
  - US-011: badge Mapari(needs_mapping)/Trimiteri(blocate), ascuns la zero, scoped, aria-label cu sens.
- **Regresia de aur**: flux import→commit→coada + canal API `POST /v1/prezentari` intacte
  (`test_import_ui/e2e`, `test_api`, `test_web_tabs`); deep-link-uri `?tab=` valide.
- **Non-Goals**: `git diff --stat` confirma `app/worker/`, `app/idempotency.py`, `app/mapping.py`,
  `app/schema.sql` NEATINSE; CHECK status pastreaza cele 6 stari; niciun endpoint `/v1/*` nou.
- **E2E live RAR**: neprobat in sesiune (fara credentiale RAR live, identic cu 3.4) — recomandata
  probare manuala `./start.sh test both --send` + browser pe `http://localhost:8000/`.

### Findings `/code-review` (high) — reparate inainte de inchidere

1. **Corectie + needs_mapping (sever)**: ruta `POST /trimitere/{id}/corecteaza` re-punea in `queued`
   fara re-rezolvarea prestatiilor → un cod nemapat putea ajunge la RAR cu `codPrestatie: null`
   (FINALIZATA ireversibil). **Fix**: re-ruleaza `resolve_prestatii` + `has_no_auto_send` (ca
   `reresolve_account`); cod nemapat ramane `needs_mapping`. Test: `test_corectie_needs_mapping_nu_ajunge_in_coada`.
2. **Filtru dupa LIMIT 200**: cautarea pe vehicul/data rata randuri mai vechi de 200. **Fix**: fara LIMIT
   in SQL cand filtrul text/data e activ, plafonare dupa filtrare.
3. **Coliziune idempotency non-atomica (cursa TOCTOU → 500)**: **Fix**: `try/except sqlite3.IntegrityError`
   in jurul UPDATE-ului `queued`, mesaj prietenos in loc de 500.
4. **Comparatie data non-ISO gresita la filtru**: **Fix**: `_is_iso_date` — compar doar date ISO YYYY-MM-DD.

Findings de cleanup (scope-clause hand-coded in `_status_counts`/`_get_acasa_context`, `_render_panel_*`
duplicate) sunt preexistente din 3.4, in afara scope-ului 3.5 — neatinse intentionat.
