Files
rar-autopass/docs/prd/prd-5.17-tipuri-cont-planuri-trial.md
Claude Agent 8dd0e1678c docs(prd): 5.16 tipografie+bugfix editare + 5.17 tipuri cont + mockup-uri
PRD 5.16 (draft) — propagare design uniform peste aplicatie:
- fonturi standard web (system font stack), scala uniforma --fs-* (carduri aerisite)
- RAR online = dot in antet (datetime pe hover) + meniu burger; banda doar cand e blocat
- antet branded "ROMFAST AUTOPASS" + nume service + badge plan (gate is_authenticated)
- /login profesional (antet minimal pre-login), selector tema stil landing
- bug-uri editare: denumiri in picker, adaugare operatie extra, fix save no-op, fix Renunta
- dashboard compact: strip-less, contoare separate (mobil = bara numere), import colapsat,
  ordine carduri->import->tab-uri->lista, meniu cu separatoare
- wizard import (4 pasi) + editare/corectie aliniate la design

PRD 5.17 (draft) — tipuri de cont (Gratuit/Standard/Pro/Premium) + trial Pro 30 zile:
- model accounts.tier + trial_until, app/plans.py sursa unica
- enforcement DUR: limita Gratuit 60/luna (era 100) + API doar Pro+
- downgrade automat la expirare trial; aliniere landing (60, "Pro gratuit 30 zile")

Mockup-uri vizuale (docs/mockups/prd-5.16-*.html): fonturi, header+login+tema,
dashboard desktop+mobil, wizard import. Doar documentatie + mockup-uri; fara cod aplicatie.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 21:20:20 +00:00

318 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PRD 5.17 — Tipuri de cont (planuri) + trial Pro 30 zile + enforcement
**Stare**: draft
> Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`.
> Landing comercial cu planurile: `app/web/templates/landing.html` (sectiunea PRICING).
> Lifecycle cont existent: `app/accounts.py`, `app/schema.sql` (tabela `accounts`, coloana `status`).
> Signup: `app/web/auth_routes.py` (`signup_post`, butoanele landing trimit `data-plan`).
> Starea trece: `draft -> aprobat -> in-executie -> verify-pass -> inchis`.
## 1. Introducere
Landing-ul comercial promite patru planuri — **Gratuit**, **Standard (39 lei)**, **Pro (59 lei, cu
API)**, **Premium (la cerere)** — si afirma ca **fiecare cont incepe cu acces gratuit 30 de zile** la
un plan superior. In aplicatie insa **nu exista deloc conceptul de tip de cont**: tabela `accounts`
are doar `status` (pending/active/blocked/archived/deleted) si `on_unmapped_error_default`. Nimic nu
diferentiaza un cont gratuit de unul platit, nimic nu aplica limita de volum sau gate-ul de API, si nu
exista niciun trial.
In plus, userul a decis doua corectii fata de landing-ul actual:
1. Trial-ul de 30 de zile e pe **Pro**, NU pe Premium (landing-ul scrie azi "Premium gratuit 30 de
zile" — gresit; trebuie "Pro 30 de zile").
2. Limita planului **Gratuit** scade de la **100** la **60 de prestatii/luna** — actualizata si in
landing si in aplicatie.
5.17 introduce modelul de tipuri de cont, trial-ul Pro de 30 de zile, **enforcement DUR** al
diferentelor (volum lunar + acces API), si downgrade automat la expirarea trial-ului. NU include
integrare de plata (nu exista inca sistem de facturare) — alocarea planului platit ramane manuala
(admin), iar trial-ul porneste automat la creare cont.
## 2. Obiective
### Obiectiv principal
Aplicatia sa sustina real diferentele dintre planuri pe care landing-ul le promite: cont nou →
trial Pro 30 zile → la expirare downgrade pe Gratuit (60/luna, fara API), cu enforcement efectiv.
### Obiective secundare
- Sursa unica de adevar pentru definitia planurilor (limite + capabilitati), consumata de backend si UI.
- Mesaje oneste cand un cont atinge limita sau cere o capabilitate neinclusa (3 niveluri, ca 5.4).
- Vizibilitate in dashboard: planul curent + zile ramase din trial + consum lunar.
### Metrici de succes
- Un cont Gratuit care depaseste 60 prestatii/luna primeste un raspuns clar de respingere (API + web),
iar contoarele lunare se reseteaza corect la inceput de luna (timp local RO).
- Un cont fara plan Pro+ primeste 403 onest pe `/v1/*` de import API.
- Un cont nou are trial Pro activ; dupa 30 zile (sau setand `trial_until` in trecut in test) trece
automat pe Gratuit, cu enforcement-ul aferent.
- Landing + app afiseaza coerent "60 prestatii/luna" si "Pro gratuit 30 de zile".
## 3. User Stories
> Database → backend → API → UI (ordinea dependentelor). Un singur autor pe `accounts.py`/`schema.sql`
> in valul de model.
### US-001: Schema — `accounts.tier` + `trial_until` + definitia planurilor
**Ca** sistem **vreau** sa stiu planul fiecarui cont si pana cand e in trial **pentru ca** restul
logicii depinde de asta.
- **Depinde de**: —
- **Fisiere**: `app/schema.sql` (coloane noi + migrare defensiva), `app/accounts.py` (helperi),
`app/plans.py` (NOU — definitia planurilor, sursa de adevar), `tests/test_accounts.py` /
`tests/test_plans.py` (~4 fisiere)
- **Test intai (RED)**: `test_migrare_tier_trial_defensiva`, `test_plan_definitii`,
`test_cont_nou_trial_pro_30z`
- **Acceptance criteria**:
- [ ] `accounts` capata (migrare aditiva defensiva, ca `email`/`status` in 5.5/5.12):
`tier TEXT NOT NULL DEFAULT 'free' CHECK (tier IN ('free','standard','pro','premium'))`
si `trial_until TEXT` (nullable; ISO datetime UTC sau NULL daca nu e in trial).
- [ ] `app/plans.py` = SINGURA sursa de adevar: dict `PLANS` cu, per plan,
`{label, monthly_limit, api_access, ...}`. Valori: `free``monthly_limit=60`, `api_access=False`;
`standard``monthly_limit=None` (nelimitat), `api_access=False`; `pro``monthly_limit=None`,
`api_access=True`; `premium``monthly_limit=None`, `api_access=True`. (Aliniat landing-ului,
cu limita Gratuit 60.)
- [ ] Helper `effective_tier(account)`: daca `trial_until` e in viitor → randeaza ca `pro`
(trial); altfel `tier`. (Trial-ul = acces Pro temporar peste tier-ul de baza `free`.)
- [ ] `create_account` seteaza `tier='free'` si `trial_until = now + 30 zile` (trial Pro automat la
creare). Contul implicit id=1 (dev) e exceptat / setat coerent (nu blocheaza dev-ul).
- [ ] Migrare idempotenta (re-rulabila); conturile legacy fara `tier` primesc `free` + fara trial
(sau trial calculat din `created_at` — decizie la executie; implicit: legacy → free fara trial).
- **Verificare E2E**: creez cont nou → `tier=free`, `trial_until ≈ now+30z`, `effective_tier=pro`.
### US-002: Numarator de consum lunar (prestatii/luna pe cont)
**Ca** sistem **vreau** sa stiu cate prestatii a trimis un cont in luna curenta **pentru ca** limita
Gratuit (60/luna) se aplica pe acest numar.
- **Depinde de**: US-001
- **Fisiere**: `app/accounts.py` SAU `app/plans.py` (`monthly_usage(conn, account_id)`),
`tests/test_plans.py` (~2 fisiere)
- **Test intai (RED)**: `test_consum_lunar_numara_sent_si_queued`, `test_consum_lunar_timp_local_ro`,
`test_consum_lunar_resetare_luna_noua`
- **Acceptance criteria**:
- [ ] `monthly_usage(conn, account_id)` numara prestatiile contului in luna calendaristica curenta.
**Definitia "prestatie consumata"** (de fixat la executie, propus): randuri `submissions` ale
contului cu `status` in (`queued`,`sending`,`sent`) cu `created_at` in luna curenta — adica
prestatiile ACCEPTATE in coada, nu cele respinse/blocate. (Justificare: limita e pe ce trimitem
la RAR, nu pe incercari esuate.) Alternativ doar `sent` — de decis; implicit: acceptate-in-coada.
- [ ] **Timp local RO** (ca E7 din 5.15): bucketarea lunii foloseste offset RO (`created_at,'+3 hours'`
sau echivalent), nu UTC pur, ca prestatiile de la granita de luna sa cada corect. Test la granita.
- [ ] Scoped strict pe cont (nu numara cross-account).
- [ ] Fara coloana noua daca `submissions.created_at` ajunge (respecta non-goal migrare minima).
- **Verificare E2E**: cont cu N trimiteri in luna → `monthly_usage == N`; luna urmatoare → reset la 0.
### US-003: Enforcement DUR — limita lunara Gratuit (60) pe ambele canale
**Ca** owner **vreau** ca un cont Gratuit care depaseste 60 prestatii/luna sa fie oprit **pentru ca**
asa sustinem diferenta de plan promisa.
- **Depinde de**: US-001, US-002
- **Fisiere**: `app/api/v1/router.py` (`create_prezentari`), `app/api/v1/import_router.py`
(commit import), `app/errors.py` (cod nou `PLAN_LIMITA_LUNARA`), `app/web/routes.py` (commit web),
`tests/test_api_scope.py` / `tests/test_web_*` / `tests/test_plans.py` (~6 fisiere)
- **Test intai (RED)**: `test_free_peste_60_respins_api`, `test_free_peste_60_respins_import_web`,
`test_pro_si_trial_nelimitat`, `test_eroare_3_niveluri_plan_limita`
- **Acceptance criteria**:
- [ ] La enqueue (API `POST /v1/prezentari` + commit import web + commit import API), daca
`effective_tier` are `monthly_limit` si `monthly_usage + nr_cerut > monthly_limit` → cererea
e respinsa (sau respinsa partial, la limita) cu eroare 3 niveluri (`app/errors.py`, cod
`PLAN_LIMITA_LUNARA`: problema "Ai atins limita planului Gratuit (60/luna)", cauza, fix
"Treci pe Standard/Pro sau astepti luna viitoare"). NU se face enqueue peste limita.
- [ ] `standard`/`pro`/`premium` si conturile in **trial Pro** → fara limita de volum.
- [ ] Comportament la cerere de lot care depaseste partial limita (ex. 50 folosite, vin 20):
decizie la executie — implicit RESPINGERE clara a intregului lot cu mesaj cat mai e disponibil
("mai poti trimite 10 luna asta"), NU enqueue partial tacut (evita surprize). De confirmat.
- [ ] Enforcement aliniat cu `AUTOPASS_REQUIRE_API_KEY` (dev vs prod): in dev, contul id=1 nu e
blocat artificial (trial/standard coerent), ca dogfooding-ul sa nu se loveasca de limita.
- [ ] **Idempotenta neatinsa**: respingerea pe limita se face INAINTE de `build_key`/enqueue; un
retry idempotent al unei prestatii deja acceptate nu consuma din nou cota.
- **Verificare E2E**: cont free cu 60 trimise → a 61-a respinsa cu mesaj 3 niveluri (API si import web);
cont pro → trece.
### US-004: Enforcement DUR — gate API doar pe Pro/Premium
**Ca** owner **vreau** ca importul prin API sa fie disponibil doar pe Pro+ **pentru ca** landing-ul
spune ca API-ul e o capabilitate Pro.
- **Depinde de**: US-001
- **Fisiere**: `app/auth.py` (sau dependinta de ruta), `app/api/v1/router.py`,
`app/api/v1/import_router.py`, `app/errors.py` (cod `PLAN_FARA_API`), `tests/test_api_scope.py`
(~5 fisiere)
- **Test intai (RED)**: `test_free_fara_api_403`, `test_standard_fara_api_403`, `test_pro_api_ok`,
`test_trial_pro_api_ok`, `test_dry_run_valideaza_ramane_permis`
- **Acceptance criteria**:
- [ ] Rutele de **import/ingestie prin API** (`POST /v1/prezentari`, `POST /v1/import`, etc.)
cer `effective_tier.api_access == True` (pro/premium sau trial Pro). Altfel 403 cu eroare
3 niveluri (`PLAN_FARA_API`: "Importul prin API e disponibil pe planul Pro", fix).
- [ ] **Canalul web ramane neafectat** — operatorii pe plan gratuit pot folosi import xlsx/csv prin
dashboard (asa promite landing-ul: Gratuit are import manual, NU API). Doar suprafata API e gated.
- [ ] `GET /v1/nomenclator` ramane public (coduri RAR, fara PII) — invariant CLAUDE.md.
- [ ] `POST /v1/prezentari/valideaza` (dry-run) — decizie: ramane permis pe orice plan (read-only,
ajuta integrarea inainte de upgrade) SAU gated ca restul API. Implicit: PERMIS (read-only,
fara enqueue). De confirmat.
- [ ] In dev (`AUTOPASS_REQUIRE_API_KEY=false`), contul id=1 are acces API (tier coerent), ca testele
API existente sa nu pice.
- **Verificare E2E**: cheie API pe cont free → 403 onest pe import; cheie pe cont pro/trial → 200.
### US-005: Downgrade automat la expirarea trial-ului
**Ca** owner **vreau** ca la expirarea celor 30 de zile contul sa treaca automat pe Gratuit **pentru ca**
landing-ul spune "apoi trece automat pe Gratuit, fara plata".
- **Depinde de**: US-001, US-003, US-004
- **Fisiere**: `app/plans.py` (`effective_tier` deja trateaza expirarea — lazy), optional
`app/worker/__main__.py` SAU un job de intretinere (eager), `tests/test_plans.py` (~3 fisiere)
- **Test intai (RED)**: `test_trial_expirat_efective_free`, `test_trial_expirat_aplica_limita_60`,
`test_trial_expirat_pierde_api`
- **Acceptance criteria**:
- [ ] **Lazy-first**: `effective_tier` returneaza `tier` de baza (`free`) imediat ce
`trial_until <= now` — fara job necesar pentru corectitudine (enforcement-ul US-003/004 se
bazeaza pe `effective_tier`, deci downgrade-ul e automat la prima cerere dupa expirare).
- [ ] Optional (eager, non-blocant): un pas in purjarea orara a worker-ului (T16 existent) poate
normaliza `trial_until` expirat → NULL pentru igiena (NU obligatoriu pentru corectitudine).
- [ ] Un cont cu `tier='standard'/'pro'/'premium'` setat de admin NU e downgradat de expirarea
trial-ului (trial-ul e un BONUS peste `free`; un plan platit alocat persista).
- [ ] Mesajele de limita/API dupa expirare sunt cele 3-niveluri din US-003/004.
- **Verificare E2E**: setez `trial_until` in trecut → contul aplica limita 60 + pierde API, fara restart.
### US-006: UI dashboard — plan curent + zile ramase din trial + consum lunar
**Ca** operator **vreau** sa vad pe ce plan sunt, cat mi-a mai ramas din trial si cat am consumat
luna asta **pentru ca** vreau sa stiu cand ma apropii de limita.
- **Depinde de**: US-001, US-002
- **Fisiere**: `app/web/routes.py` (context), `app/web/templates/_status.html` SAU `_cont.html`
(afisaj plan), `tests/test_web_status.py` / `tests/test_dashboard.py` (~4 fisiere)
- **Test intai (RED)**: `test_afisaj_plan_si_zile_trial`, `test_afisaj_consum_lunar`,
`test_avertizare_aproape_de_limita`
- **Acceptance criteria**:
- [ ] Dashboard-ul afiseaza discret planul curent (ex. "Plan: Pro · trial 18 zile ramase" sau
"Plan: Gratuit · 47/60 luna asta"). In trial → eticheta "trial" + zile ramase; pe Gratuit →
consum `N/60`.
- [ ] **Plasare (aliniat cu PRD 5.16)**: planul apare ca **badge in titlul din antet**
(`Gratuit`/`Standard`/`Pro`/`Premium`) SI ca linie in **meniul burger** ("Plan: <tier> [· trial
N zile]"), nu doar intr-un card pe Acasa. Vezi mockup-urile 5.16
(`docs/mockups/prd-5.16-dashboard.html` / `...-mobil.html`). 5.16 furnizeaza locul de afisare
(antet + meniu); 5.17 furnizeaza datele (tier, trial, consum).
- [ ] Avertizare vizuala cand consumul Gratuit se apropie de limita (ex. ≥80% → ton warn), fara a
ingropa stripul de sanatate (zero-silent-failures pastrat).
- [ ] Scoped pe cont; design conform 5.15/5.16 (tokeni, fonturi system, fara hex hardcodat).
- [ ] Pagina "Cont" arata planul + (daca exista) o explicatie "cum trec pe alt plan" (contact, ca
nu exista plata self-service inca).
- **Verificare E2E**: cont trial → "trial N zile"; cont free aproape de 60 → avertizare; cont pro →
fara contor de limita.
### US-007: Aliniere landing — limita 60 + trial pe Pro (nu Premium)
**Ca** vizitator **vreau** ca landing-ul sa spuna adevarul **pentru ca** azi promite "100/luna" si
"Premium gratuit 30 zile", dar realitatea va fi 60/luna si trial pe Pro.
- **Depinde de**: — (copy-only; aliniaza cu modelul din US-001)
- **Fisiere**: `app/web/templates/landing.html`, `tests/test_web_*` (~2 fisiere)
- **Test intai (RED)**: `test_landing_limita_60`, `test_landing_trial_pro_nu_premium`
- **Acceptance criteria**:
- [ ] Toate aparitiile "100 de prestatii/luna" / "100/luna" / `meta description`
(`landing.html:7,65,266` + oriunde apar) → **60**. Inclusiv cardul Gratuit din sectiunea PRICING.
- [ ] Textul "Fiecare cont incepe cu **Premium gratuit 30 de zile**" (`landing.html:256`) →
"**Pro gratuit 30 de zile**" (planul corect). Restul frazei ("Apoi trece automat pe Gratuit…")
ramane.
- [ ] Coerenta: orice alt loc care implica trial/limita reflecta 60 + Pro.
- [ ] Fara alte schimbari de pret/continut (39/59 lei raman).
- **Verificare E2E**: landing in browser — "60 prestatii/luna" peste tot, "Pro gratuit 30 de zile".
### US-008: Admin — alocare manuala de plan (fara plata self-service)
**Ca** admin **vreau** sa pot seta planul unui cont **pentru ca** nu exista inca facturare automata,
dar trebuie sa pot acorda Standard/Pro/Premium.
- **Depinde de**: US-001
- **Fisiere**: `tools/account.py` (CLI `set-tier`), optional `app/web/routes.py` (`/admin` actiune),
`tests/test_accounts.py` / `tests/test_web_admin*.py` (~3 fisiere)
- **Test intai (RED)**: `test_cli_set_tier`, `test_admin_set_tier_scoped`, `test_tier_invalid_respins`
- **Acceptance criteria**:
- [ ] CLI `python3 -m tools.account set-tier --account N --tier pro [--trial-days 30|--no-trial]`
seteaza `tier`/`trial_until`. Tier invalid → eroare clara.
- [ ] Optional (la executie): actiune in panoul `/admin` pentru a seta planul unui cont (scoped,
CSRF, ca bulk-ul de status din 5.5). Daca nu intra in 5.17, CLI e suficient (admin-only).
- [ ] Alocarea unui plan platit de catre admin NU e suprascrisa de expirarea trial-ului (US-005).
- [ ] Audit: schimbarea de plan se logheaza in `app_events` (reuse jurnalul din 5.6), fara PII nou.
- **Verificare E2E**: `set-tier --account 2 --tier pro` → contul 2 are API + volum nelimitat.
### US-009: Teste de regresie + E2E plan/trial/enforcement
**Ca** dezvoltator **vreau** acoperire completa **pentru ca** enforcement-ul atinge ambele canale de
ingestie si nu vreau sa blochez gresit conturi legitime.
- **Depinde de**: US-003, US-004, US-005, US-006, US-007
- **Fisiere**: `tests/test_plans.py`, `tests/test_api_scope.py`, `tests/test_web_*` (~3 fisiere)
- **Test intai (RED)**: matricea plan × capabilitate (volum, API) × canal (API, web) × trial activ/expirat.
- **Acceptance criteria**:
- [ ] `python3 -m pytest -q -m "not live"` verde; regresia de aur (`POST /v1/prezentari` → queued
pe un cont cu drept) ramane verde.
- [ ] Matrice testata: free(volum-blocat/API-blocat), standard(volum-ok/API-blocat),
pro(ok/ok), trial-pro(ok/ok), trial-expirat(=free).
- [ ] Contoarele lunare resetate la luna noua (test la granita timp local RO).
- [ ] Dev (id=1) nu e blocat de enforcement (dogfooding).
- **Verificare E2E**: rulare completa documentata in Raportul VERIFY.
## 4. Cerinte functionale (rezumat)
1. [REQ-001] `accounts.tier` ∈ {free,standard,pro,premium} + `trial_until`; migrare aditiva defensiva.
2. [REQ-002] `app/plans.py` = sursa unica: limite (free=60/luna) + capabilitati (API doar Pro+).
3. [REQ-003] Cont nou → trial Pro 30 zile automat; `effective_tier` randeaza Pro in trial, free dupa.
4. [REQ-004] Enforcement DUR: free peste 60/luna respins (API + import web) cu eroare 3 niveluri.
5. [REQ-005] Enforcement DUR: import API gated pe Pro+ (403 onest); canalul web ramane liber.
6. [REQ-006] Downgrade automat la expirare trial (lazy via `effective_tier`).
7. [REQ-007] Dashboard arata plan + zile trial + consum lunar; landing aliniat (60, Pro).
8. [REQ-008] Admin aloca planuri manual (CLI `set-tier`), audit in `app_events`.
## 5. Non-Goals (anti scope-creep)
- **Fara integrare de plata / facturare / abonamente** (Stripe etc.) — alocarea platita = manuala (admin).
- Fara self-service upgrade din UI (doar afisare plan + "contacteaza-ne"); plata vine intr-un PRD viitor.
- Fara modificari pe backend-ul de trimitere (worker, masina de stari, idempotenta `build_key`,
reconciliere, contract RAR). Enforcement-ul se face la ingestie/enqueue, INAINTE de coada.
- Fara schimbarea capabilitatilor de produs in sine (sugestii/mapare exista deja pe toate planurile in
cod; diferentierea 5.17 e pe VOLUM + ACCES API, exact ce promite landing-ul ca diferentiator hard).
- Fara modificari de design (tipografia/temele sunt 5.16/5.15); doar reuse-ul stilurilor existente.
## 6. Consideratii tehnice
- **Stack**: SQLite (migrare aditiva defensiva ca 5.5/5.12), FastAPI, Jinja2/HTMX.
- **Patterns de urmat**: sursa unica (`app/plans.py` ca `app/errors.py`); eroare 3 niveluri (5.4);
scope pe cont (5.15/US-011); timp local RO la bucketare (5.15/E7); audit `app_events` (5.6).
- **Riscuri**:
- **Blocare gresita a unui cont legitim** (enforcement prea agresiv) — risc de business. Mitigare:
dev id=1 exceptat; teste matrice; mesaje 3 niveluri cu cale de iesire; respingere INAINTE de enqueue
(nu pierde date).
- **Definitia "prestatie consumata"** (acceptate-in-coada vs sent) schimba cand musca limita.
Mitigare: o decidem explicit (US-002 AC) + test; documentam.
- **Granita de luna / fus orar** — off-by-a-day la reset. Mitigare: timp local RO + test la granita
(lectia E7 din 5.15).
- **Idempotenta vs cota** — un retry idempotent nu trebuie sa consume cota de doua ori. Mitigare:
enforce inainte de `build_key`; testul de retry.
- **Conturi legacy fara tier** — migrare le pune `free`; un cont real activ ar putea fi limitat brusc
la 60. Mitigare: decizie de migrare (legacy activ → ce plan?) confirmata cu user inainte de deploy.
## 7. Consideratii UI/UX
- Afisaj plan discret, conform 5.16 (fonturi system, tokeni `--fs-*`, fara hex).
- Stari: trial activ (zile ramase) / free (consum N/60, warn la ≥80%) / platit (fara contor limita).
- Mesaje de respingere oneste, actionabile (cum trec pe alt plan), nu doar "403".
## 8. Open Questions
- [ ] "Prestatie consumata" = acceptate-in-coada (queued+sending+sent) sau doar `sent`? (implicit: acceptate)
- [ ] Lot care depaseste partial limita → respingere totala sau enqueue partial? (implicit: respingere totala clara)
- [ ] `POST /v1/prezentari/valideaza` (dry-run) — gated pe Pro sau permis tuturor? (implicit: permis)
- [ ] Migrare conturi legacy active: raman `free` (risc limitare brusca) sau primesc un trial/plan? (de confirmat cu user)
- [ ] Standard (39 lei) si Premium difera de Pro doar prin API + suport in landing — pastram exact maparea
de capabilitati din landing in `plans.py`? (implicit: da)
## 9. Valuri de executie
```
Val 1: [US-001] schema tier+trial + app/plans.py (autor unic schema/accounts)
Val 2: [US-002] numarator consum lunar (dupa model) ||
[US-007] landing copy 60 + Pro (independent, copy-only)
Val 3: [US-003] [US-004] [US-005] enforcement volum + API + downgrade (consuma plans.py)
Val 4: [US-006] [US-008] UI dashboard plan/consum || admin set-tier
Val 5: [US-009] regresie + E2E matrice (dupa toate)
```
> Secventiere fata de 5.16: independent (5.16 = design/tipografie; 5.17 = model de cont). Pot rula in
> paralel; doar US-006 (afisaj plan in `_status.html`) atinge un fisier pe care 5.16/US-003 il modifica
> (dot RAR) — serializeaza acel template daca ambele PRD-uri sunt in executie simultan.
---
> Acest PRD nu a fost inca trecut prin `/plan-ceo-review` / `/plan-eng-review`. Recomandat inainte de
> executie (enforcement de business cu risc de blocare gresita + decizia de migrare a conturilor legacy).