diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 1041145..155ef30 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -86,11 +86,26 @@ Reguli de contract (detalii in `docs/api-rar-contract.md`): `FINALIZATA` e termi | 3.5 | Dashboard compact: import pe prima pagina, status cu bife, Trimiteri lizibile, Mapari complete | DONE | 2026-06-19 | 11 stories (4 valuri), 3 review-uri de plan facute. Acasa=ecran de import (scoate tab Import); bara status compacta font normal + bife accesibile (auto-send/RAR) + data `dd.mm.yyyy hh24:mi:ss`; "Coada"→"Trimiteri" cu coloane RO + detalii comanda din `payload_json` (helper partajat `payload_view.py`) + detaliu la click in panou dedicat; filtrare Trimiteri (US-009); corectie inline `needs_data` cu re-enqueue + detectie coliziune idempotency (US-010); badge contoare pe tab-uri (US-011); "Mapari" 3 sectiuni (de rezolvat / op salvate cu re-rezolvare auto / formate coloane), "Cont"=doar cheie+creds; feedback `needs_data` la import. Backend trimitere neatins. PRD: [prd-3.5](prd/prd-3.5-dashboard-compact-trimiteri-mapari.md) | | 3.6 | Editare celule in preview + Acasa unificata (Trimiteri inline, upload slim, Mapari tabelar, comutator coada) | DONE | 2026-06-22 | 7 stories (3 valuri), 2 echipe in paralel (TeamCreate) pe fisiere disjuncte + US-007 secvential. US-003/004 tab "Trimiteri" eliminat→sectiune "Trimiterile tale" sub upload pe Acasa; upload bara slim (hero la first-run); `?tab=coada`+`/_fragments/coada`→Acasa; poll gated visibilityState. US-001 `import_rows.override_json` (Approach B, Fernet, `_migrate` defensiv) aplicat ULTIMUL in `_resolve_row_for_preview`+`commit_import` (mutatie pura; completeaza si coloane ABSENTE din fisier); ruta `.../rand/{i}/editeaza` scoped JOIN→404, guard committed→409, empty=clear. US-002 buton Editeaza pe rand, swap pe ``+OOB contoare (nu pe sectiune), form propriu (confirm dezactivat la editare), mutual-exclusion, reuse grila `_trimitere_detaliu.html`. US-005/006 Mapari/formate tabelar (.tablewrap, H4 auto_send stocat). US-007 bifa auto-send→comutator pe COADA ("Pune automat in coada"/"Tine pentru verificare"), scoped operatie, `name=auto_send` pastrat (zero backend). 523 teste pass. VERIFY E2E browser + LIVE RAR test: import fara coloana data→editare completeaza data (override)→commit→worker login RAR test→`postPrezentare`→sent `idPrezentare=68696` (confirmat in finalizate RAR). 3 bug-uri JS prinse la E2E (invizibile la TestClient) reparate: htmx `useTemplateFragments` (raspuns ``+OOB parsat in context tabel→swapError), re-activare confirm-btn deferita pe tick, `n-hint` actualizat. `/code-review` high (2026-06-22): 1 bug real reparat — decriptarea `override_json` era in afara `try/except`-ului care protejeaza `raw_json` in ambele cai de preview (`import_router.preview_import` + `routes._web_compute_preview`); la rotatie cheie Fernet / token corupt `raw_json` degrada gratios la `{}` dar `override_json` arunca 500 pe tot batch-ul — acum protejat identic. Notat ca cleanup viitor (nereparat, disciplina backend-neatins): `_override_of` + blocul canonicalize-dupa-override duplicate in 3-4 locuri (preview/commit API vs web). Backend trimitere (worker/masina stari/idempotenta/mapare) NEATINS. PRD: [prd-3.6](prd/prd-3.6-editare-preview-acasa-unificata.md) | -### Etapa 4 — Viitor (Treapta 3) +### Etapa 5 — Ergonomie & Integrare (FOCUS CURENT) + +> Directie noua (2026-06-22, decizie utilizator): produsul e functional, dar greu de adoptat de +> service-urile care vin din Visual FoxPro / soft propriu. Prioritatea trece pe **usurinta de +> integrare + ergonomie**, peste Etapa 4 (vezi nota de mai jos). Propunere fundamentata cu lentila +> DX gstack (`/devex-review`) pe codul real: lipseste suprafata de onboarding API self-service, +> exemplele de cod, puntea VFP, dry-run-ul si tema light. | # | Livrabila | Status | Data | Detalii | |---|-----------|--------|------|---------| -| 4.1 | Mapare AI / conector MCP (sugestie peste fuzzy) | TODO | | | +| 5.1 | Hub de integrare (pagina `/integrare` autentificata): exemple cod multi-limbaj (curl/Python/PHP/C#/Node) + retetar **Visual FoxPro** (POST via `MSXML2.ServerXMLHTTP` + upload CSV) + export OpenAPI/Postman + buton "Testeaza conexiunea" | TODO | | cel mai mare wedge de adoptie; absoarbe optiunea "doar exemple markdown" | +| 5.2 | Endpoint dry-run `POST /v1/prezentari/valideaza` — valideaza payload + mapare, intoarce erorile reale FARA enqueue | TODO | | "magical moment" pt integratori; refoloseste `validation.py`+`mapping.py`, NU atinge coada | +| 5.3 | Light/Dark mode — toggle in header, persistat (cookie/localStorage); CSS deja pe variabile `:root` | TODO | | efort mic, cerut explicit | +| 5.4 | Erori pe 3 niveluri (problema + cauza + fix) pe API si UI | TODO | | DX: fight uncertainty; reduce suportul | + +### Etapa 4 — Deprioritizat (post Etapa 5, daca apare nevoia din uz real) + +| # | Livrabila | Status | Data | Detalii | +|---|-----------|--------|------|---------| +| 4.1 | Mapare AI / conector MCP (sugestie peste fuzzy) | AMANAT | | deprioritizat 2026-06-22 in favoarea ergonomiei/integrarii (Etapa 5) | | 4.2 | Editare/anulare prezentari trimise | BLOCAT | | `FINALIZATA` terminal la RAR — fara flux API; corectia = suport RAR | ### Amanat / taiat (cu motiv) diff --git a/docs/prd/prd-5.1-hub-integrare.md b/docs/prd/prd-5.1-hub-integrare.md new file mode 100644 index 0000000..3e9a47c --- /dev/null +++ b/docs/prd/prd-5.1-hub-integrare.md @@ -0,0 +1,255 @@ +# PRD 5.1 — Hub de integrare (pagina /integrare cu exemple de cod + retetar VFP + ping + export) + +**Stare**: aprobat (2026-06-22 — intrebari deschise rezolvate; 3 review-uri de plan rulate si incorporate (CEO/eng/design, toate APROB CU MODIFICARI); aprobat la poarta umana. Urmeaza EXECUTE) + +> Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`. +> Starea trece: `draft → aprobat → in-executie → verify-pass → inchis` (actualizata de lead). +> **Backend trimitere (worker, masina stari, idempotenta-logica, mapping-rezolvare) NU se atinge.** +> Aceasta livrabila adauga DOAR suprafata de onboarding/integrare (citire) peste API-ul existent + stratul web. +> Schema neatinsa (zero coloane noi). + +## 1. Obiectiv + +Produsul e functional pe ambele canale (API + import web), dar un service nou care vine din **Visual FoxPro** +sau soft propriu nu are de unde sa inceapa integrarea: cheia API se creeaza/roteste din web (`/cont`), insa nu +exista nicio pagina cu **cum chem API-ul**, ce payload, ce header, in ce limbaj. Singurele referinte sunt +`README.md` (curl) si Swagger `/docs` — invizibile din aplicatie si fara exemple in limbajul integratorului. + +Livram o pagina **`/integrare`** (tab nou, autentificat, scoped pe contul din sesiune) care: +- afiseaza exemple de cod **pre-completate cu endpoint-ul real + account_id-ul utilizatorului** pentru ambele + canale (`POST /v1/prezentari` JSON si `POST /v1/import` upload fisier), in **curl, Python, PHP, C#, Node**; +- include un **retetar Visual FoxPro** (POST via `MSXML2.ServerXMLHTTP.6.0` SI `WinHttp.WinHttpRequest.5.1`, + plus upload CSV); +- ofera **export OpenAPI** (`/openapi.json` din FastAPI) + **colectie Postman** + link Swagger `/docs`; +- are un buton **"Testeaza conexiunea"**: utilizatorul lipeste cheia salvata, iar serverul confirma ca e valida + si mapeaza pe contul lui (acelasi drum de auth ca API-ul real). + +Constrangere de securitate (din `app/auth.py`): stocam DOAR SHA-256 al cheii API, deci **nu putem reafisa cheia**. +Exemplele folosesc placeholder `rfak_...`; cheia reala se obtine o singura data la creare/rotire din `/cont`. + +## 2. Non-Goals (anti scope-creep) + +- **Nu reafisam cheia API** — exemplele au placeholder, niciodata cheia in clar (SHA-256-only). +- **Fara markdown versionat in `docs/`** (decis cu utilizatorul: doar pagina in-app dinamica). Daca apare nevoia + de referinta indexabila, e o livrabila viitoare separata. +- **Fara generare automata de SDK-uri** — OpenAPI + Postman sunt punctul de plecare; integratorul genereaza singur. +- **Fara dry-run** — validarea de payload fara enqueue e livrabila 5.2 (separata). `GET /v1/ping` verifica DOAR + autentificarea (cheie valida + cont activ), nu valideaza continut. +- **Fara editarea exemplelor de catre user** — continut read-only generat din schema. +- **Nu atinge backend-ul de trimitere** (worker/masina stari/idempotenta-logica/mapping-rezolvare) si **nici schema**. +- **Fara i18n** — pagina e in romana, ca restul UI-ului. + +## 3. Stories atomice + +> Backend + UI pentru acelasi comportament = stories separate. Toate rutele web noi sub `require_login` + +> **scoped pe contul din sesiune**. CSRF pe toate POST-urile web. Endpoint-urile `/v1/*` folosesc `resolve_account_id`. + +### US-001: Backend — endpoint-uri de integrare (`GET /v1/ping` + export Postman) +**Ca** integrator extern **vreau** un endpoint care confirma ca cheia mea functioneaza si o colectie Postman gata +de importat **pentru ca** sa-mi validez conexiunea inainte sa trimit prezentari reale. + +- **Depinde de**: — +- **Fisiere**: `app/api/v1/integrare_router.py` (NOU), montare in `app/main.py` (`app.include_router`), + `tests/test_integrare_api.py` (~3 fisiere) +- **Montare (decizie eng-review P1)**: router nou cu `prefix="/v1"`, inclus in `app/main.py` cu `app.include_router(...)` + (consistent cu `import_v1_router`). NU sub-include in `app/api/v1/router.py` (acela are deja `prefix="/v1"` → ar dubla prefixul). +- **Test intai (RED)**: `tests/test_integrare_api.py` — + `test_ping_cu_cheie_valida_200` (raspuns: `account_id`, `mediu` test/prod, `autentificat_cu_cheie=true`, `are_creds_rar`, `ts`), + `test_ping_cu_bearer_valid_200` (`Authorization: Bearer rfak_...`), + `test_ping_fara_cheie_dev_cont_implicit` (flag off → cont 1, `autentificat_cu_cheie=false`), + `test_ping_x_api_key_gol_in_dev_cont_implicit` (whitespace tratat ca lipsa), + `test_ping_are_creds_rar_reflecta_contul` (cont cu `accounts.rar_creds_enc` → `true`; fara → `false`), + `test_ping_cheie_invalida_401`, + `test_ping_prod_fara_cheie_401` (flag on), + `test_ruta_ping_inregistrata_o_singura_data` (assert pe `app.routes` — fara dubla inregistrare), + `test_postman_export_json_valid` (Content-Type JSON, parseaza, schema Postman v2.1.0), + `test_postman_contine_exact_trei_requesturi` (prezentari, import, ping; header `X-API-Key: {{api_key}}`; `{{base_url}}`), + `test_postman_nu_deriva_din_app_routes` (allowlist hardcodat — NU expune `/v1/conturi/rar-creds` etc.). +- **Acceptance criteria**: + - [ ] `GET /v1/ping` (dependinta `resolve_account_id`) = **readiness check**, nu doar auth-echo: cheie valida → 200 + `{account_id, mediu, autentificat_cu_cheie, are_creds_rar, ts}`; cheie invalida → 401; fara cheie cu + `require_api_key=true` → 401; fara cheie in dev → cont 1 cu `autentificat_cu_cheie=false`. (`are_creds_rar` = contul are + `accounts.rar_creds_enc` setate; citire-only, raspunde la "esti gata sa trimiti?", nu doar "cheia merge". CEO-review P1/P2.) + - [ ] Suporta si `Authorization: Bearer rfak_...` (al doilea header documentat in pagina). + - [ ] `GET /v1/integrare/postman.json`: colectie Postman v2.1.0 cu variabile `base_url` + `api_key`, **exact 3 requesturi** + (allowlist hardcodat, NU derivat din `app.routes`): `POST /v1/prezentari`, `POST /v1/import`, `GET /v1/ping`, fiecare cu + header `X-API-Key: {{api_key}}` si body exemplu (pt prezentari: payload din schema reala `PrezentareRequest`, inclusiv `prestatii`). + - [ ] `/openapi.json` ramane accesibil (FastAPI implicit) — niciun regres. + - [ ] NU atinge `submissions`, worker, schema. `are_creds_rar` = SELECT read-only pe `accounts`. +- **Verificare E2E**: `curl -H "X-API-Key: rfak_..." http://localhost:8010/v1/ping` pe instanta locala → 200 cu contul corect + + `are_creds_rar`; import `postman.json` in Postman/Swagger. + +### US-002: Helper pur — generator de exemple de cod multi-limbaj +**Ca** dezvoltator al gateway-ului **vreau** un modul pur care produce snippet-urile de integrare dintr-un +`(base_url, account_id)` **pentru ca** pagina sa fie testabila si snippet-urile sa ramana langa schema reala. + +- **Depinde de**: — +- **Fisiere**: `app/web/integrare_examples.py` (NOU), `tests/test_integrare_examples.py` (~2 fisiere) +- **Test intai (RED)**: `tests/test_integrare_examples.py` — + `test_snippet_curl_prezentari_contine_endpoint_si_header` (`{base_url}/v1/prezentari`, `X-API-Key: rfak_...`), + `test_snippet_python_import_upload_multipart` (foloseste `files=` / multipart pe `/v1/import`), + `test_toate_limbajele_prezente` (curl, python, php, csharp, node, vfp_msxml, vfp_winhttp), + `test_ambele_canale_per_limbaj` (fiecare limbaj are si prezentari JSON, si upload fisier), + `test_vfp_msxml_si_winhttp_distincte` (MSXML2.ServerXMLHTTP.6.0 vs WinHttp.WinHttpRequest.5.1), + `test_payload_acopera_campurile_obligatorii_din_model` (snippet-ul prezentari contine TOATE campurile obligatorii + derivate prin `field.is_required()` din `PrezentareIn` — INCLUSIV `prestatii` cu ≥1 item — si `RarCredentials{email,password}`; + drift-test corect: `{c for c,f in PrezentareIn.model_fields.items() if f.is_required()}` ⊆ campurile din snippet; campurile cu + default — `odometru_initial`, `obs`, `b64_image`, `sistem_reparat` — NU dau drift fals), + `test_prestatii_in_snippet_are_cod` (item-ul `prestatii` are `cod_prestatie` SAU `cod_op_service` — vezi `PrestatieItem._require_one`), + `test_placeholder_cheie_nu_e_valoare_reala` (mereu `rfak_...`, niciodata o cheie injectata). +- **Acceptance criteria**: + - [ ] Functie pura `exemple(base_url: str, account_id: int) -> dict` care intoarce, per limbaj, snippet pt ambele canale. + - [ ] Placeholder cheie = `rfak_...` (constant), endpoint-ul derivat din `base_url`. + - [ ] Payload-ul JSON reflecta schema reala (`PrezentareRequest`): `rar_credentials{email,password}` + `prezentari[]` cu + **toate** campurile obligatorii prin `is_required()`, **inclusiv `prestatii` cu cel putin un item valid** (eng-review P1: + `prestatii` e fara default → obligatoriu; un payload fara el ar fi nevalid, exact ce pagina trebuie sa previna). + - [ ] Drift-test foloseste `field.is_required()` (nu doar prezenta cheii), ca sa nu raporteze fals campurile cu default. + - [ ] Fara I/O, fara DB, fara stare globala — pur (usor de testat). +- **Verificare E2E**: acoperita de UI (US-003) + unit teste. + +### US-003: UI — tab "Integrare" cu exemple, retetar VFP si export +**Ca** utilizator **vreau** o pagina cu exemple gata de copiat in limbajul meu + linkuri de export **pentru ca** +sa integrez gateway-ul fara sa caut prin README/Swagger. + +- **Depinde de**: US-001, US-002, US-004 +- **Fisiere**: `app/web/routes.py` (`_render_integrare` + `/_fragments/integrare` + `integrare` in `_TABS_VALIDE` + + **branch nou in `_render_panel_for_tab`** — necesar pt deep-link server-side, altfel `/?tab=integrare` cade pe Acasa), + `app/web/templates/dashboard.html` (tuplu nav nou + generalizarea scriptului ARIA tablist), `app/web/templates/_integrare.html` (NOU), + `tests/test_web_integrare.py` (~4 fisiere) +- **Test intai (RED)**: `tests/test_web_integrare.py` — + `test_tab_integrare_in_nav`, + `test_deeplink_tab_integrare_randeaza_panou_server_side` (`/?tab=integrare` → panou randat, NU Acasa — prinde branch-ul lipsa din `_render_panel_for_tab`), + `test_fragment_integrare_necesita_login` (`require_login`), + `test_pagina_contine_account_id_si_endpoint_real` (din `request.base_url`), + `test_pagina_are_tab_limbaje_si_vfp_cu_dialecte` (limbaj = tablist primar; VFP = un tab cu sub-tablist MSXML2/WinHttp), + `test_canal_secundar_prezentari_si_import`, + `test_export_card_openapi_postman_swagger`, + `test_buton_copiaza_citeste_din_pre_code` (markup copy citeste din `
`, nu din atribut),
+  `test_empty_state_cta_cont_cand_fara_cheie_sau_creds`,
+  `test_fara_culori_hardcodate_doar_tokens` (snippet-ul template-ului nu contine `#`-hex; doar `var(--...)`).
+- **Acceptance criteria**:
+  - [ ] Tab nou "Integrare" in nav (`dashboard.html`), `integrare` in `_TABS_VALIDE`, **branch nou in `_render_panel_for_tab`**,
+        deep-link `/?tab=integrare` randat server-side, fragment lazy pe click (pattern identic cu Mapari/Cont). NU atinge `_render_panel_import`/`coada`.
+  - [ ] **IA pe doua niveluri (design-review P1):** tab primar = LIMBAJ (curl/Python/PHP/C#/Node/VFP) — VFP e UN tab, nu sectiune
+        separata; in panelul VFP, al doilea tablist = dialect (MSXML2 / WinHttp). Tab secundar (in fiecare panel de limbaj) = CANAL
+        (Prezentari JSON / Import fisier). Se vede UN singur snippet o data (fara produs cartezian 14-snippet pe ecran).
+  - [ ] **Refolosire ARIA (design-review P1):** generalizeaza scriptul de keyboard-nav din `dashboard.html` (sageti/Home/End,
+        roving `tabindex`, sync `aria-selected`+`aria-controls`) ca sa prinda ORICE `[role=tablist]`, scoped pe containerul propriu
+        (nu primul `querySelector` global) — altfel tab-urile imbricate (limbaj/canal/VFP-dialect) intra in conflict pe `ArrowRight`.
+  - [ ] **Copy-to-clipboard (design-review P2):** buton per snippet care citeste textul din `
` asociat (NU din `data-*`);
+        feedback in `aria-live="polite"` + label "Copiat" cu revenire la "Copiaza" dupa ~2s; `navigator.clipboard` cu `.catch()` +
+        fallback pt context ne-securizat (selecteaza textul + "Ctrl+C") — butonul nu ramane blocat fals pe "Copiat".
+  - [ ] **Empty-state onboarding (design-review P2 + CEO P1):** cand sesiunea NU are cheie rotita sau NU are creds RAR, afiseaza
+        deasupra snippet-urilor un empty-state cu CTA direct `href="/?tab=cont"` ("Genereaza cheia din Cont; o vezi o singura data").
+        Snippet-urile raman vizibile (placeholder-based). Flux de onboarding continuu, nu doar o nota text.
+  - [ ] Card separat "Export & referinta" (componenta `.cardlink`): Swagger `/docs`, `GET /openapi.json`, `GET /v1/integrare/postman.json`
+        (Postman = `download`; restul `target="_blank" rel="noopener"`).
+  - [ ] Integreaza formularul "Testeaza conexiunea" din US-004 (primul card al panoului; markup-ul tinteste ruta US-004).
+  - [ ] **Tokens de tema (design-review P3, pregatire 5.3):** template-ul foloseste DOAR variabilele din `:root`
+        (`--bg`/`--card`/`--ink`/`--muted`/`--line`/`--accent`), zero culori hex hardcodate; fara highlight de sintaxa colorat
+        (ar introduce paleta noua). Cod copiabil pe `--ink`, nu `--muted`.
+- **Verificare E2E**: browser pe `/integrare` — comuta limbaj (inclusiv VFP cu dialect), comuta canal, copiaza un snippet (vezi
+  "Copiat" + revenire), vede account_id-ul + endpoint-ul reale, deschide `/openapi.json` si importul Postman.
+
+### US-004: Backend web — "Testeaza conexiunea" (verifica cheia lipita, scoped pe sesiune)
+**Ca** utilizator **vreau** sa lipesc cheia mea si sa primesc confirmare ca e valida **pentru ca** sa stiu ca
+integrarea va autentifica corect inainte sa scriu cod.
+
+- **Depinde de**: —
+- **Fisiere**: `app/web/routes.py` (`POST /integrare/test-cheie`, `require_login` + CSRF),
+  `app/web/templates/_integrare_test_rezultat.html` (NOU — fragment de raspuns dedicat, NU inline in `_integrare.html` care
+  apartine US-003; eng-review P2: evita fisier partajat intre valuri), `tests/test_integrare_test_cheie.py` (~3 fisiere)
+- **Test intai (RED)**: `tests/test_integrare_test_cheie.py` —
+  `test_cheie_valida_a_contului_curent_ok` (mesaj succes + account_id),
+  `test_cheie_a_altui_cont_respinsa` (cheia mapeaza pe alt cont → mesaj "nu apartine contului tau", NU dezvaluie care),
+  `test_cheie_invalida_mesaj_clar`,
+  `test_cheie_revocata_dupa_rotire_respinsa` (eng-review P2: cel mai probabil caz de suport — lipesti cheia veche dupa rotire),
+  `test_cheie_goala_nu_da_fals_pozitiv_in_dev` (eng-review P2: foloseste `account_for_key` direct, NU `resolve_account_id` cu fallback cont 1),
+  `test_fara_login_redirect_sau_401`,
+  `test_csrf_lipsa_respinsa`,
+  `test_cheia_nu_apare_in_raspuns_sau_log` (no echo).
+- **Acceptance criteria**:
+  - [ ] `POST /integrare/test-cheie` (`require_login`, CSRF): valideaza cheia lipita prin **`account_for_key` direct** (NU
+        `resolve_account_id` — altfel in dev o cheie goala/gunoi ar cadea pe cont 1 si ar raporta fals "valida"); confirma DOAR
+        daca mapeaza pe **contul sesiunii**; altfel mesaj neutru ("nu apartine contului tau", fara sa dezvaluie care cont).
+  - [ ] Cheie revocata (dupa rotire) → tratata ca invalida (`account_for_key` filtreaza `active=1`).
+  - [ ] Niciun echo al cheii in raspuns/log (regula 422-no-echo din `main.py`).
+  - [ ] Raspuns HTML fragment dedicat (`_integrare_test_rezultat.html`), htmx swap intr-un container `aria-live="polite"`:
+        succes pe `.flash` (border `--ok`, "Cheie valida — cont X"), eroare pe `.banner` (border `--err`). Label uman, fara emoji.
+  - [ ] Input `type=password` + `autocomplete="off"`; microcopy anti-confuzie langa camp: "Verificam doar daca cheia e valida.
+        Nu o salvam si nu o memoram — cheia se gestioneaza in Cont." Buton "Testeaza conexiunea" (NU "Salveaza").
+  - [ ] NU creeaza/roteste chei (doar verifica) — fara efecte secundare.
+- **Verificare E2E**: browser — lipeste cheia reala (din rotire `/cont`) → "valida, cont X"; lipeste gunoi → eroare clara;
+  lipeste cheia veche dupa rotire → respinsa.
+
+## 4. Graful de valuri
+
+- **Val 1 (paralel, fisiere disjuncte):** US-001 (`integrare_router.py` NOU) ‖ US-002 (`integrare_examples.py` NOU)
+  ‖ US-004 (`routes.py` + test). US-001 si US-002 sunt fisiere noi; US-004 atinge `routes.py` (ruta noua, fara
+  suprapunere cu US-003 care vine in Val 2). Atentie: US-003 si US-004 ating ambele `routes.py` → NU in acelasi val.
+- **Val 2:** US-003 (UI: `routes.py` + `dashboard.html` + `_integrare.html`) — depinde de US-001/US-002/US-004.
+  Nota (eng-review P2): US-004 isi pune raspunsul in `_integrare_test_rezultat.html` (fisier propriu), NU in `_integrare.html`
+  (al US-003) — asa cele doua valuri nu impart un template.
+
+**Prioritate de descope (CEO-review P2/P3)** — daca timpul se scurteaza, taie in aceasta ordine inversa de valoare:
+1. (pastreaza ultimul) **US-002 + US-003 exemple + retetar VFP in pagina** = wedge-ul de adoptie, ne-negociabil.
+2. US-004 (test cheie) — potentator.
+3. US-001 ping — potentator.
+4. (primul taiat) **export Postman/OpenAPI** — audienta moderna (Python/Node), public secundar fata de VFP. Retetarul VFP
+   ramane prim-clas indiferent de descope.
+
+## 5. Considerații tehnice
+
+- **Auth**: header `X-API-Key: rfak_...` (sau `Authorization: Bearer rfak_...`), dependinta `resolve_account_id`
+  (`app/auth.py`). In dev (`require_api_key=false`) lipsa cheii → cont 1; `ping` semnaleaza asta cu `autentificat_cu_cheie`.
+- **Schema request** (din `app/models.py`): `PrezentareRequest{rar_credentials{email,password}, prezentari[PrezentareIn]}`;
+  `PrezentareIn{vin, nr_inmatriculare, data_prestatie(YYYY-MM-DD), odometru_final(str), odometru_initial?, sistem_reparat="null",
+  obs?, b64_image?, prestatii?[{cod_prestatie|cod_op_service, denumire}]}`.
+- **Import**: `POST /v1/import`, multipart, camp `file` (`app/api/v1/import_router.py`).
+- **base_url** derivat din `request.base_url`; documenteaza override in spatele proxy (X-Forwarded). Risc minor de
+  base_url gresit in spatele reverse-proxy — mitigat prin nota in pagina.
+- **Pattern UI**: tab nou = tuplu in `dashboard.html` + `integrare` in `_TABS_VALIDE` + `_render_integrare` +
+  `/_fragments/integrare` (identic cu Mapari/Cont/Nomenclator).
+- **OpenAPI/Postman**: `/openapi.json` e gratuit din FastAPI; colectia Postman e construita o data din endpoint-urile cunoscute.
+
+## 6. Riscuri
+
+- **Drift de schema** intre snippet-uri si `PrezentareIn` real → test care leaga snippet-ul de `model_fields` (US-002).
+- **base_url** gresit in spatele proxy → nota + derivare din request.
+- **Confuzie dev "merge fara cheie"** (cont 1) → `ping` raporteaza `autentificat_cu_cheie=false`.
+- **Scurgere cheie in test-cheie** → no-echo enforce + test dedicat (US-004).
+
+## 7. Intrebari deschise
+
+Rezolvate cu utilizatorul (2026-06-22): test conexiune = `GET /v1/ping` nou (independent de 5.2); exemple pe ambele
+canale (prezentari + import); continut doar in-app dinamic (fara markdown versionat); VFP = ambele dialecte (MSXML2 + WinHttp).
+Niciuna ramasa deschisa.
+
+## 8. Verificare (rezumat E2E per canal)
+
+- API: `curl GET /v1/ping` cu cheie reala pe instanta locala (8010) → 200 cont corect; import `postman.json`.
+- Web: browser pe `/integrare` — comuta limbaj, copiaza snippet, vede account_id+endpoint reale, testeaza cheia.
+- Regresia de aur (neatinsa, dar de confirmat ca nu s-a stricat): `POST /v1/prezentari` (sau import → commit) →
+  worker → `FINALIZATA` la RAR test.
+
+**Criteriu de succes al wedge-ului (CEO-review P3):** un service nou (stil VFP) ajunge de la zero la o prezentare
+`FINALIZATA` la RAR test folosind DOAR pagina `/integrare` (copiaza snippet → testeaza cheia → vede `are_creds_rar` →
+trimite), fara suport uman si fara sa atinga README/Swagger separat.
+
+---
+
+## Review-uri de plan (§5.3 — rulate 2026-06-22)
+
+- [x] **CEO** (valoare/scope) — APROB CU MODIFICARI. Aplicat: ping/test ca readiness-check (`are_creds_rar`), CTA continuu spre
+      `/cont` in empty-state, VFP prim-clas + prioritate de descope (§4), criteriu de succes masurabil (§8). (P2 "ping redundant"
+      rezolvat prin diferentierea payload-ului: ping poarta `are_creds_rar`+`mediu`, deci e readiness, nu auth-echo.)
+- [x] **Eng** (fezabilitate/teste) — APROB CU MODIFICARI. Aplicat: montare router `prefix="/v1"` in `main.py` fara dublare +
+      test inregistrare unica; drift-test cu `is_required()` + `prestatii` obligatoriu (US-002); template dedicat
+      `_integrare_test_rezultat.html` (US-004, anti fisier-partajat intre valuri); teste Bearer/cheie-goala/cheie-revocata;
+      `account_for_key` nu `resolve_account_id`; Postman allowlist exact 3 (nu `app.routes`); `_render_panel_for_tab` in fisierele US-003.
+- [x] **Design** (UI) — APROB CU MODIFICARI, claritate plan UI 6/10 → adresat. Aplicat: IA pe 2 niveluri (limbaj primar, canal
+      secundar, VFP cu sub-dialect); refolosire + generalizare script ARIA tablist (scoped per container); copy din `
` +
+      `aria-live` + fallback non-HTTPS; empty-state cu CTA `/?tab=cont`; card "Export & referinta" cu `.cardlink`; doar tokens de
+      tema (pregatire 5.3); microcopy anti-confuzie + `type=password` la test-cheie.
+
+Toate cele trei: **APROB CU MODIFICARI**, modificarile incorporate mai sus. Asteapta poarta umana de aprobare (§5.8) → `aprobat`.