docs: contract RAR verificat live + plan unic consolidat

Verificat contractul RAR AUTOPASS pe endpoint-ul de test si compilat sursa
de adevar `docs/api-rar-contract.md`. Corectii majore fata de planurile vechi:
- JWT TTL = 30h (nu scurt); worker se re-logheaza, retry neplafonat
- b64Image optional; tipPrestatie generat de server (nu se trimite)
- anulare/corectie prin API inexistente pentru FINALIZATA
- needs_data determinist pe R-ODO/I-ODO; reguli validare exacte (VIN/data/nrInm)

Rulat plan-eng-review + plan-design-review, apoi consolidat ambele intr-un
singur plan executabil `docs/plans/plan.md` (design ca anexa). Outside voice
a prins lost-ack double-submit (P1) -> reconciliere inainte de re-send.
Re-push din ROAAUTO scos din v1 (durabilitate = SQLite persistent + restart).

- mutat fisierele spec oficiale RAR in docs/
- adaugat raspunsul oficial al programatorilor RAR (api-rar-documentatie-oficiala.md)
- sterse plan-eng-review.md + plan-design-review.md (consolidate in plan.md)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-15 11:32:11 +00:00
parent 2514a04931
commit 5ea2c4cedb
8 changed files with 757 additions and 535 deletions

321
docs/plans/plan.md Normal file
View File

@@ -0,0 +1,321 @@
# Plan unic — Gateway RAR AUTOPASS (migrare ROAAUTO din VFP în Web API)
> **Planul executabil, sursă unică.** Consolidează design-ul de produs + planul de implementare,
> aliniat la contractul verificat live. Citește-l împreună cu:
> - `docs/api-rar-contract.md` — **contractul RAR (sursa de adevăr)**, verificat live 2026-06-15.
> - `docs/CONTEXT.md` — continuitate între sesiuni.
>
> Înlocuiește fostele `plan-eng-review.md` + `plan-design-review.md` (consolidate aici).
> Ultima actualizare: 2026-06-15.
## Cum reiei (sesiune nouă)
1. `git clone git@gitea.romfast.ro:romfast/rar-autopass.git`
2. `cp settings.xml.example settings.xml` și completează credențialele de test RAR (blocul `<test>`). NU se comite.
3. Citește `docs/api-rar-contract.md` (contractul) și acest plan.
4. Pornește de la **Roadmap de execuție** (mai jos) — pasul blocant următor e **T1**.
---
## 1. Problema și rezultatul țintă
ROAAUTO (Visual FoxPro + Oracle, la fiecare service client) declară azi prestațiile la **RAR AUTOPASS**
direct din clasa `RarAutoPass` (`rar_autopass.prg`), prin `MSXML2.ServerXMLHTTP`. Obligație legală
(L.142/2023, OM 210/2024). Integrarea e testată doar pe endpoint-ul de test RAR, nepusă la clienți.
Problema reală e de **ISV**: nu vrei să redistribui un `.exe` VFP la fiecare corecție. Muți logica
(mapare + login RAR + jurnal + retry) pe un **gateway central depanabil o dată pentru toți**; ROAAUTO
rămâne client subțire. Un client real a cerut automatizarea — primul plătitor, nu ipoteză.
**Rezultat țintă (treapta 1):** o prezentare reală trimisă din ROAAUTO prin gateway apare `FINALIZATA`
la RAR, vizibilă în dashboard, cu retry pe erori tranzitorii și fără a stoca parole.
## 2. Contract RAR — reguli care guvernează implementarea
Detalii complete: `docs/api-rar-contract.md`. Esențialul pentru cod:
- **Endpoint-uri** (verificate live): login `POST /public/login`; nomenclator `GET /nomenclator/getNomenclatorPrestatii`;
trimitere `POST /prezentari/postPrezentare`. Bază test `https://apps.rarom.ro/test-rar-autopass`, prod `…/rar-autopass`.
- **JWT TTL = 30h** (108000s). Worker-ul se re-loghează la expirare; retry-ul NU e plafonat la 30h.
- **Payload** `postPrezentare`: `vin`, `nrInmatriculare`, `dataPrestatie`, `odometruFinal`, `odometruInitial`,
`prestatii:[{codPrestatie,idPrezentare:null}]`, `sistemReparat`, `status:"FINALIZATA"`, `obs?`, `b64Image?`.
**`tipPrestatie` NU se trimite** (generat de server, `GENERIC`).
- **Reguli de validare (RAR le aplică; le replicăm în gateway înainte de enqueue):**
- `vin`: 17 caractere majuscule, fără spații/special, **fără O, I, Q** → regex `^[A-HJ-NPR-Z0-9]{17}$`.
- `nrInmatriculare`: max 10, litere+cifre, majuscule → `^[A-Z0-9]{1,10}$`.
- `dataPrestatie`: ∈ [2024-12-01, azi], TZ `Europe/Bucharest`.
- `b64Image`: **opțional**; dacă prezent, base64 valid.
- `odometruInitial`: `null` normal; **obligatoriu dacă `prestatii` ∋ `R-ODO` sau `I-ODO`**; `odometruInitial <= odometruFinal`.
- `odometruFinal`: trimis ca string (per exemplul oficial).
- `sistemReparat`: trimis mereu; default `"null"` în v1.
- Normalizează `vin`/`nrInmatriculare` cu `.strip().upper()` înainte de regex (DBF-urile pot avea spații).
- **Anulare/corecție prin API: NU există.** API e strict INSERT `FINALIZATA`; `FINALIZATA` nu se anulează
(doar `SALVATA`, pe care API nu le produce). Corecția = email `suport.autopass@rarom.ro`.
- **Monitorizare:** pe TEST lista finalizate **nu întoarce `prestatii`** (pe prod da, cu filtrare + export Excel).
- **Idempotency:** RAR n-are câmp nr. comandă și acceptă duplicate → dedup-ul e responsabilitatea noastră.
## 3. Arhitectură
```
ROAAUTO (VFP, la client) GATEWAY FastAPI (central, 1 container)
citește comanda + creds RAR din Oracle POST /v1/prezentari {comanda + RAR creds + idempotency implicit}
──HTTPS──────────────────────────────────────▶ API
├─ valid Pydantic (vin 17/fără O,I,Q; nrInm; dată ∈ [2024-12-01,azi] TZ Bucharest)
├─ rezolvă op→codPrestatie (operations_mapping)
├─ odometruInitial cerut DOAR dacă coduri ∋ R-ODO/I-ODO
├─ NU trimite tipPrestatie; sistemReparat="null"
├─ calc idempotency_key = hash(conținut canonic)
├─ INSERT submission (PII criptat tranzitoriu, queued)
◀── {submissionId, status: queued|needs_mapping|needs_data} ──┘ (UNIQUE → dedup, întoarce id existent)
WORKER (proces separat, restart:always, poll SQLite WAL)
claim atomic: BEGIN IMMEDIATE; UPDATE…SET sending WHERE id=? AND status='queued'
login RAR → JWT (30h, re-login la expirare) → reconciliere → postPrezentare → retry/backoff
succes: scrie idPrezentare(data.id); PURJEAZĂ creds; PII criptat rămâne max 90 zile
Browser ─▶ Dashboard (Jinja2+HTMX): monitorizare live RAR + coadă + editor mapări + nomenclator + audit CSV
+ BANNER alertă submission-uri blocate (singura plasă pe pene > 30h)
(Re-push din ROAAUTO: SCOS din v1. Durabilitatea = SQLite pe volum persistent + backup + restart.)
```
### Mașina de stări submission
```
queued → sending → { sent | needs_mapping | needs_data | error }
needs_mapping : operație fără codPrestatie mapat → ținut gateway-side, NU trimis incomplet
needs_data : prestatii ∋ R-ODO/I-ODO fără odometruInitial, SAU validare RAR eșuată (VIN/dată/nrInm) → ținut
error : login creds invalide, RAR 4xx nerecuperabil, sau pană > 30h → ALERTĂ banner (fără re-push automat)
sent : are idPrezentare (data.id) RAR; creds purjate; PII criptat max 90 zile; terminal
(anulare/corecție: NU există tranziție — FINALIZATA e terminal la RAR; corecția = suport RAR)
```
## 4. Componente (un repo, `docker compose up`)
1. **API `app/api/v1`** (FastAPI):
- `POST /v1/prezentari` (una/mai multe) → Pydantic (validările din §2), mapare, enqueue, `submissionId`.
- `GET /v1/prezentari?status=&data=` și `/{id}` — monitorizare programatică (stare coadă + idPrezentare).
- `GET /v1/nomenclator`, `POST /v1/nomenclator/refresh`.
- `GET/PUT /v1/mapari` — CRUD mapare per cont, cu **sugestie fuzzy** pe denumire (cherry-pick).
- `GET /v1/audit/export?from=&to=`**CSV** cu ce s-a trimis (cherry-pick, leagă reținerea 90 zile).
- Auth gateway: **API key per cont ROA** (separată de creds RAR), cu emitere/rotire/revocare.
- **Redactare credențiale (CORE):** middleware care garantează că body-ul pe `/v1/prezentari` NU se loghează
niciodată și `password` se scrubează din excepții/APM.
- *(SCOS din v1: anulare/corecție prin API. Amânat: `POST /v1/import` xlsx/csv — treapta 2.)*
2. **Client RAR `app/rar_client.py`** — portare din `rar_autopass.prg` + `rar-forms.prg`: login+JWT,
getNomenclatorPrestatii, postPrezentare, getAllPrezentari/getAllPrezentariFinalizate. `httpx` + retry/backoff.
**URL-uri din VFP testat** (verificate live). *(NU portăm anulare/corecție.)*
3. **Worker `app/worker`** — proces propriu supravegheat (NU task `asyncio` în uvicorn — altfel worker mort lasă
containerul „sănătos"), sub Docker `restart: always`. Buclă claim atomic → login → reconciliere → send → retry.
- JWT 30h, **re-login la expirare** (retry nu e plafonat la 30h).
- **Reconciliere înainte de re-send (anti-duplicat pe răspuns pierdut — P1):** dacă răspunsul RAR se pierde după
ce RAR a inserat, rândul rămâne `sending`; un re-send orb ar crea duplicat (RAR acceptă duplicate). Înainte de
re-send, worker interoghează lista RAR pe `VIN + dataPrestatie + odometruFinal`; dacă găsește → marchează `sent`
cu `idPrezentare` găsit, NU re-trimite. **UNIQUE pe `submissions` NU acoperă acest caz.**
- **Lease/timeout pe `sending` orfane** (worker mort mid-POST) → recuperat de reconciliere.
- `b64Image` (opțional) mare → BLOB/path pe disc, nu RAM.
4. **Dashboard `app/web`** — Jinja2 + HTMX (server-rendered, zero build): monitorizare live RAR + coadă + editor
mapări (cu fuzzy) + browser nomenclator + **banner alertă submission-uri blocate**. Stări explicite:
empty (coadă goală cu CTA), error (needs_mapping/needs_data cu motiv), **RAR indisponibil → ultima stare a cozii**.
5. **SQLite (WAL)** — un fișier `.db` pe **volum persistent + backup**:
- `accounts`, `api_keys`.
- `operations_mapping` (cod_op_service → codPrestatie, `auto_send`) ← `mapare_prestatii.DBF`.
- `nomenclator_rar` (cache {codPrestatie, numePrestatie}) ← `prestatii_rar.DBF`.
- `submissions`: `idempotency_key` UNIQUE, status, statusCode RAR, eroare, `idPrezentare`, retry, timestamps.
PII (`vin`, `odometru`, `dataPrestatie`, `prestatii`, `b64Image`) **criptat** + `purge_after` (sent+90z).
- **Niciun câmp pentru parole RAR.**
## 5. Securitate & raza de explozie
Gateway-ul vede parolele RAR ale tuturor clienților în memorie/tranzit. Zero-storage reduce riscul la rest,
dar o greșeală de logging scurge parole live, iar AGPL = codul e public. Controale **hard**:
- Middleware de redactare — body-uri cu parole niciodată în loguri/APM/excepții.
- HTTPS obligatoriu; recomandare TLS pinning din ROAAUTO către gateway.
- Parola folosită doar pentru `login` în worker. Cu JWT 30h, după primul login reușit tokenul acoperă retry-urile;
creds se șterg. Dacă login-ul eșuează (RAR jos), creds rămân în itemul (criptat) până la primul login reușit.
### Failure modes registry
| Codepath | Failure | Rescued | User vede | Logged |
|---|---|---|---|---|
| postPrezentare cu R-ODO/I-ODO fără odometru init | record fraud-sensibil incomplet | Y (needs_data determinist) | flag dashboard | DA |
| dublu-send (răspuns RAR pierdut, rând `sending`) | duplicat la RAR (UNIQUE NU acoperă) | Y (reconciliere VIN+dată+odometru) | nimic (transparent) | DA |
| dublu-enqueue (același conținut, 2 cereri API) | submission dublu | Y (idempotency UNIQUE) | „deja înregistrat" | DA |
| VIN cu O/I/Q sau ≠17 / dată în afara intervalului | RAR respinge 4xx | Y (validare Pydantic înainte de enqueue) | flag „date invalide" | DA |
| login 401 creds greșite | nu se poate trimite | Y (NU retry; error+motiv) | „credențiale RAR invalide" | DA |
| pană RAR > 30h (fără re-push) | declarație legală întârziată | Parțial (alertă, NU auto-recovery) | banner + webhook | DA |
| worker mort (proces, nu container) | coada se oprește tăcut | Y (proces propriu + /healthz → restart) | banner | DA |
**Regulă:** fără `except Exception` generic. Fiecare rescue: retry / degradare cu mesaj / re-raise cu context.
> **Risc acceptat (2026-06-15):** scoaterea re-push-ului lasă penele RAR > 30h fără recuperare automată. Acoperit de
> alerta de submission blocat. Re-introducem re-push (timer VFP `OnAutoProcessTimer`) dacă apare în practică o pană > 30h.
## 6. Client ROAAUTO (VFP) — refactor minim
- `settings.xml` păstrează doar **URL gateway + API key** (rotește parola de test expusă acum în SVN!).
- Creds RAR ale clientului se citesc din **Oracle** și se trimit în payload la gateway peste HTTPS.
- `export_comenzi.prg` rămâne, dar construiește JSON și face `POST /v1/prezentari` (nu XML + apel RAR direct).
- Dispar din VFP: `Login`, `UpdateNomenclator`, `GetCodRarPentruOperatie`, maparea, `rar_log` → în web.
- **ROAAUTO = pur expeditor în v1.** Re-push scos; timer-ul `OnAutoProcessTimer` rămâne disponibil dacă revine.
- **Fără atașare poză** (`b64Image` opțional) — un câmp mai puțin de construit în VFP.
## 7. Migrare date
`tools/import_dbf.py` (cu `dbfread`) — **dry-run + raport întâi**: rânduri valide, mapări orfane, coduri
necunoscute în nomenclator. Confirmi, apoi scrie în SQLite. Surse: `mapare_prestatii.DBF`, `prestatii_rar.DBF`.
(`rar_log.DBF` NU se migrează — jurnalul nou e `submissions` + live din RAR.)
## 8. Observabilitate (cherry-picks)
- `/healthz` — worker viu + ultimul login RAR reușit + adâncime coadă; pică → semnal de restart.
- `/metrics` — submissions pe status, latență send, retry count, backlog needs_mapping/needs_data.
- Alertă submission-uri blocate — banner dashboard + webhook/email peste prag (plasă de siguranță legală, acum critică).
## 9. Deploy
- Start: **LXC Proxmox + Cloudflare Tunnel** (0 €, teste). Producție: **VPS mic always-on** (~5 €/lună).
- Un container: uvicorn (API) + worker (proces 2), `restart: always`, **volum SQLite persistent + backup**.
Mutare = copiezi container + `.db`.
- Open-source pe github.com/romfast, **AGPL-3.0**. ⚠️ Decide **CLA / copyright assignment din ziua 1** dacă vrei
opțiunea de dual-license (open core + hosted comercial). Relicențierea fără CLA cere acordul tuturor contributorilor.
- romfast.ro/hosting.com = doar landing (ASGI + worker daemon nu merg pe shared hosting).
---
## 10. Roadmap de execuție (pașii rămași)
Nimic din cod nu e scris încă (`app/`, `tools/` nu există). Ordine recomandată:
- [ ] **T1 (P1, BLOCANT) — verificare contract live.** Un singur `POST /prezentari/postPrezentare` real pe **test**
cu o prezentare minimă validă (status FINALIZATA, sistemReparat `"null"`, fără b64Image/odometruInitial). Capturează:
mesajele de eroare exacte pe constrângeri (VIN cu O/I/Q, dată < 2024-12-01), forma exactă a răspunsului, `data.id`,
acceptarea `sistemReparat:"null"`. **Fă-l ÎNAINTE de a construi suprafața** poate invalida design-ul validării/erorilor.
Verify: 200 + record vizibil în `getAllPrezentariFinalizate`.
- [ ] **T5 (P1) — `tools/import_dbf.py`** dry-run + raport pe `mapare_prestatii.DBF` / `prestatii_rar.DBF`, apoi import în SQLite.
Verify: raport rânduri valide/orfane/coduri necunoscute; import idempotent.
- [ ] **Schelet repo** `app/api/v1`, `app/rar_client.py`, `app/worker`, `app/web`, SQLite (WAL), `docker compose up`, `/healthz` verde.
- [ ] **T3 (P1) — validare Pydantic completă** (VIN `^[A-HJ-NPR-Z0-9]{17}$`, nrInm, dată [2024-12-01,azi] TZ Bucharest,
R-ODO/I-ODOodometruInitial, `odometruInitial<=odometruFinal`, normalize strip/upper). Verify: unit tests per regulă.
- [ ] **T4 (P1) — payload builder** cu `status:"FINALIZATA"`, `sistemReparat:"null"`, fără `tipPrestatie`, `odometruFinal` string.
Verify: snapshot payload == exemplul oficial din contract.
- [ ] **T2 (P1) — `app/worker` reconciliere** VIN+dată+odometru înainte de re-send pe `sending` + lease/timeout orfane.
Verify: test integration răspuns pierdut simulat fără duplicat la RAR.
- [ ] **T6 (P2) — worker proces/container propriu supravegheat;** `/healthz` pică restart. Verify: worker omorât restart automat.
- [ ] **T7 (P2) — deploy:** SQLite pe volum persistent numit + backup (singura copie durabilă, re-push scos).
Verify: recreare container coada supraviețuiește.
- [ ] **Dashboard** (Jinja2+HTMX) cu stările empty/error/RAR-indisponibil + banner alertă. Apoi `/design-review` pe UI-ul live.
### De decis ulterior (urmărit, nu blocant)
- **[P2]** Defer criptare-at-rest + purjare 90z până după primul postPrezentare real reușit? (gold-plating vs. privacy-argument-de-adopție).
- **[P2]** Gate pe `auto_send` pentru operații nou-mapate / valori neobișnuite un record `FINALIZATA` eronat e permanent.
## 11. Verificare (end-to-end)
1. Spike The Assignment" (JWT 30h, b64Image opțional, tipPrestatie server-gen, R-ODO/I-ODO) `docs/api-rar-contract.md`.
2. T1: `postPrezentare` real pe test 200 + `data.id`.
3. `import_dbf.py --dry-run` raport corect; apoi import confirmat.
4. `docker compose up`; `/healthz` verde.
5. `POST /v1/prezentari` cu o comandă reală (test) `submissionId`, worker trimite, apare `FINALIZATA` la RAR + dashboard.
6. Re-trimite aceeași comandă identică același `submissionId` (idempotency), NU dublă la RAR.
7. Operație nemapată `needs_mapping`; `prestatii` R-ODO/I-ODO fără `odometruInitial` `needs_data`;
VIN cu O/I/Q sau dată < 2024-12-01 respins la validare, NU ajunge la RAR.
8. Oprește RAR / forțează 5xx submission `error`, worker reia în fereastra de re-login; pană > 30h → alertă banner.
9. Simulează răspuns pierdut → reconcilierea previne duplicatul.
10. SQLite n-are câmp parolă; după `sent`, PII criptat + `purge_after`; logurile n-au parole.
11. Teste: unit (mapare, idempotency hash, validare VIN/dată/nrInm, R-ODO→odometruInitial), integration (claim atomic,
retry/backoff, reconciliere), E2E pe endpoint test RAR.
## 12. NOT in scope (amânat, cu motiv)
- **Re-push din ROAAUTO** — scos din v1; worker + alertă acoperă. Re-introducem dacă apare pană RAR > 30h.
- **Anulare/corecție prin API** — `FINALIZATA` nu se anulează/corectează prin API; corecția = suport RAR.
- **Atașare poză odometru în ROAAUTO** — `b64Image` opțional, nu în treapta 1.
- `POST /v1/import` xlsx/csv + UX mapare coloane — treapta 2 (piață non-ROA). Motor identic, fără rescriere.
- Modelul de conturi RAR (addClient/roluri) — rămâne la RAR.
- Outbox în Oracle (Approach C) — pentru clienți non-ROA / viitor, cere acces gateway→Oracle.
- Agregare/produse din datele service-urilor — niciodată default; doar opt-in + anonimizare + lawyered.
- Redis/arq/Postgres — SQLite WAL + un worker acoperă volumul (60-100 prezentări/lună/client).
## 13. Open questions rămase
1. ~~Sursa pozei odometrului~~ — închis (b64Image opțional).
2. ~~`tipPrestatie` / JWT TTL~~ — închis (server-generated; 30h). Rămâne: ce valori reale acceptă `sistemReparat` în afară de `"null"`.
3. Un singur user RAR per agent economic sau mai mulți (`idUser`/`idAgent` — afectează filtrarea monitorizării).
4. Monetizare/direcție SaaS — de reluat după ce prima prezentare reală merge la primul client.
## 14. Decizii blocate (nu le re-deschide fără motiv)
- Idempotency = **hash de conținut pe server**, UNIQUE (RAR n-are câmp nr. comandă, acceptă duplicate) + **reconciliere
înainte de re-send** pentru răspuns pierdut.
- **Reținere temporară 90 zile** a payload-ului **criptat**, apoi purjare (defensibilitate vs privacy).
- Odometru: **strict + `needs_data`**, declanșat determinist de `R-ODO`/`I-ODO`.
- **Re-push scos din v1**; **anulare/corecție API scoase** (FINALIZATA).
- URL-urile RAR: **sursa de adevăr = VFP testat / contract verificat**, NU spec-ul vechi (typo-uri).
- Cherry-picks v1: alertă submission-uri blocate (critică), `/healthz`+`/metrics`, sugestie fuzzy mapare, export audit CSV.
---
# ANEXĂ — Rațiunea de produs & direcția SaaS
> Context de produs (din `/office-hours`), păstrat pentru deciziile de prioritizare. Nu blochează execuția treptei 1.
## Demand evidence (validat: client real + lege)
Un **client real a cerut automatizarea** introducerii prezentărilor în AUTOPASS — de aici a pornit proiectul.
Status quo înlocuit: interfața web oficială AUTOPASS, unde service-urile introduc **manual, prezentare cu prezentare**.
Obligație legală reală (L.142/2023): la fiecare prestație VIN + odometru; reparare/înlocuire odometru; operațiuni
principale la direcție, frânare, structura caroseriei/șasiului și alte sisteme de siguranță. Amenzi: info eronate
1.0002.000 lei; manipulare odometru până la 5.000 lei / penal. Implică **toți** clienții ROA + **mii de service-uri
non-ROA** cu aceeași obligație → piață dincolo de ROA pentru un canal de import (xlsx/csv) ulterior.
## Teza de produs
*Cel mai ușor mod de a băga operațiile de service în AUTOPASS* — din ROAAUTO (API), din alte aplicații, sau din
fișiere — în loc de tastarea manuală. Câștigi prin **efort minim cerut service-ului**, nu prin features.
Lecția GTM (demoanaf.ro): viral prin variantă **reimaginată, simplă** a unui serviciu oficial greoi.
## Trepte (același motor — mapare + coadă + trimitere + monitorizare — fără rescriere)
- **Treapta 1 (acum):** core-ul pentru clientul care a cerut + clienții ROA, prin ROAAUTO.
- **Treapta 2 (non-ROA, web upload):** import xlsx/csv cu **mapare reținută** (map once, reuse forever) + dashboard.
Login web, fără instalare. Freemium **pe volum** (gratis ~30-40 prezentări/lună; plată peste prag). Metrica de preț =
prezentări/lună = unitatea obligatorie legal.
- **Treapta 3:** integrări mai adânci + sugestii AI de mapare (eventual conector MCP).
**Moat:** (1) mapările reținute per service cresc costul de plecare; (2) lățimea integrării; (3) fiabilitatea de
conformitate (retry, monitorizare din RAR); (4) **privacy** (nu reținem datele lor) — argument de adopție.
## Adopție & praguri (cifre reale)
Clienți reali cunoscuți: **60-80** și **80-100 prezentări/lună** → ~2-4 min/prezentare manual × volum = **3-6 ore/lună**
de tastare = durerea care convertește. Prima folosire trivială (upload → mapare reținută → trimite, sub 5 min).
Gratis la volumul lor = fără decizie de achiziție. Service-urile mici = bază virală (durere mică); volumele mari =
cei mai dispuși să plătească.
## Import xlsx/csv — UX (treapta 2)
Două straturi de mapare, **ambele reținute per cont**: (1) mapare coloane (schema fișier → câmpuri canonice),
(2) mapare operații (etichete service → `codPrestatie`, cu sugestie fuzzy). Flux: upload → recunoaște coloanele →
propune maparea → **preview** (rânduri nemapate flag-uite) → „Trimite la RAR" → monitorizare.
Spectru de integrare (același backend): API → drop fișier (folder/SFTP/email-to-import) → upload manual în browser.
## Opțiuni de deploy
| Opțiune | Cost | Când |
|---|---|---|
| LXC Proxmox + Cloudflare Tunnel | 0 € | Start + teste (risc: net/curent birou) |
| VPS mic always-on (ex. Hetzner) | ~5 €/lună | Clienți reali / producție (recomandat) |
| romfast.ro / shared hosting | inclus | ⚠️ Doar landing (ASGI + worker daemon nepotrivite pe shared) |
---
## What already exists (reuse din VFP)
| Sub-problemă | Reuse |
|---|---|
| Contract RAR (login/JWT, nomenclator, postPrezentare) | `rar_autopass.prg`, `rar-forms.prg:655,720``rar_client.py` |
| Mapare op→codPrestatie + `auto_send` | `GetCodRarPentruOperatie``operations_mapping` |
| Export (oglindă treapta 2) | `btnExportExcel.Click` (`rar_advanced.prg`) |
| Migrare DBF | `import_dbf.py` citește direct cele `.DBF` |
| Timer (dacă revine re-push) | `OnAutoProcessTimer`/`nTimerHandle` (`rar-forms.prg`) — nefolosit în v1 |
</content>