Arhiva clasei RarAutoPass (VFP) care declara prestatiile la RAR AUTOPASS, ca baza pentru rescrierea ca gateway central Python/FastAPI. Include: - sursa VFP (.prg) + datele necesare migrarii (mapare_prestatii, prestatii_rar) - spec-ul oficial RAR (txt) - docs/plans/: plan-design-review + plan-eng-review - docs/CONTEXT.md: handoff pentru continuarea in alta sesiune - .gitignore specific Visual FoxPro (ignora artefacte compilate + credentiale) settings.xml (cu parola de test in clar) EXCLUS; vezi settings.xml.example. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
272 lines
20 KiB
Markdown
272 lines
20 KiB
Markdown
# Design: Gateway RAR AUTOPASS (migrare ROAAUTO din VFP în web)
|
||
|
||
Generat de /office-hours pe 2026-06-14
|
||
Mod: Startup (proiect intern / intrapreneurship ISV)
|
||
Status: DRAFT
|
||
|
||
## Problem Statement
|
||
|
||
ROA (ERP) are nevoie să declare la **RAR AUTOPASS** prestațiile de service ale clienților săi
|
||
(service-uri auto care rulează **ROAAUTO**, Visual FoxPro + Oracle), conform **Legii 142/2023** și
|
||
**OM 210/2024**. Integrarea există azi în VFP (clasa `RarAutoPass`), dar **nu e încă pusă la clienți** —
|
||
doar testată pe endpoint-ul de test RAR.
|
||
|
||
Problema reală a lui Mihai NU e a unui service, ci a unui **ISV**: nu vrea să redistribuie un `.exe` VFP
|
||
la fiecare client la fiecare corecție. Vrea **logica pe un server central, depanabilă central**, cu ROAAUTO
|
||
ca simplu client subțire care trimite comenzile.
|
||
|
||
## Demand Evidence (validat: client real + lege)
|
||
|
||
**Cel mai tare semnal:** un **client real a cerut automatizarea** introducerii prezentărilor în AUTOPASS — de
|
||
aici a pornit tot proiectul. Primul plătitor a cerut-o, nu e ipoteză. **Status quo înlocuit:** interfața web
|
||
oficială AUTOPASS, unde service-urile introduc **manual, prezentare cu prezentare**, operația principală — tedios.
|
||
|
||
Obligație legală reală, nu ipotetică — **Legea 142/2023** (registrul electronic al istoricului vehiculelor)
|
||
obligă operatorii economici autorizați să transmită la RAR:
|
||
- la fiecare prestație: **VIN + indicația odometrului**;
|
||
- repararea/înlocuirea odometrului;
|
||
- operațiunile principale de reparație la **direcție, frânare, structura caroseriei/șasiului** și alte
|
||
sisteme de siguranță.
|
||
- Amenzi: informații eronate de la service 1.000–2.000 lei; manipulare odometru până la 5.000 lei / penal.
|
||
|
||
Implică: **toți** clienții ROA + **mii de service-uri non-ROA** au aceeași obligație → există piață dincolo
|
||
de ROA pentru un canal de import (xlsx/csv) ulterior.
|
||
|
||
## Status Quo
|
||
|
||
Integrare VFP funcțională dar nedistribuită: `RarAutoPass` (`rar_autopass.prg`) vorbește direct cu RAR prin
|
||
`MSXML2.ServerXMLHTTP`; mapare în DBF (`mapare_prestatii`), nomenclator în `prestatii_rar`, jurnal în
|
||
`rar_log`; UI desktop cu tab-uri. Credențiale RAR în `settings.xml` pe fiecare stație (text clar).
|
||
|
||
## Constraints
|
||
|
||
- Stack: **Python / FastAPI** (ales). Hosting: **hibrid** — instanță centrală always-on operată de ROA +
|
||
Proxmox LXC pentru dev/staging. `romfast.ro`/hosting.com (doar PHP) nu găzduiește core-ul.
|
||
- Open-source pe **github.com/romfast** (licență recomandată **AGPL-3.0**).
|
||
- ROAAUTO rămâne client subțire (refolosim pattern-ul `MSXML2.ServerXMLHTTP`).
|
||
|
||
## Premises (confirmate)
|
||
|
||
1. Migrarea în web se justifică prin nevoia ISV: deploy central, fără redistribuire de exe-uri. ✅
|
||
2. Cererea e reală și legală (L.142/2023) — clienți ROA + service-uri non-ROA. ✅
|
||
3. Maparea operație→`codPrestatie` e în **core** (API-ul cere `prestatii` în `postPrezentare`). ✅ (corectat de utilizator pe baza spec-ului)
|
||
4. ROAAUTO = client subțire; mapare + retry + jurnal pe server. ✅
|
||
5. **Topologie: gateway central, pass-through credențiale, ZERO stocare de parole.** ✅
|
||
|
||
## Contract API RAR AUTOPASS (din spec oficial v0.0.1, baza `/rar-autopass`)
|
||
|
||
- Auth: `POST /public/login` {email, password} → `GetUtilizatoriDTO{ token, idUser, ... }`.
|
||
Token JWT atașat ca `Authorization: Bearer {token}` la toate apelurile securizate.
|
||
- Nomenclator: `GET /nomenclator/getNomenclatorPrestatii` → listă `{codPrestatie, numePrestatie}`;
|
||
`GET /nomenclator/getPrestatieByCodPrestatie/{cod}`.
|
||
- Prezentări: `POST /prezentari/postPrezentare` (payload `Prezentari`); `GET /prezentari/getAllPrezentari`;
|
||
`GET /prezentari/getPrezentare/{id}`; `PATCH /prezentari/markPrezentareAnulataById/{id}`;
|
||
`PATCH /prezentari/patchPrezentare/{id}`. Răspuns: `CustomResponseForMapping{ data, message, statusCode }`.
|
||
- Payload `Prezentari`: `vin`, `nrInmatriculare`, `dataPrestatie(date)`, `odometruInitial`, `odometruFinal`,
|
||
`obs`, `b64Image`, `sistemReparat`, `tipPrestatie`, `status`∈{SALVATA,FINALIZATA,ANULATA,UNDEFINED},
|
||
`prestatii: [{codPrestatie, idPrezentare}]`.
|
||
- Cont/roluri (per agent economic): ADMIN creează CLIENT/ADMIN_CLIENT. **Nu replicăm** asta în gateway.
|
||
|
||
## Recommended Approach — B: gateway central + coadă + token JWT scurt
|
||
|
||
### Flux
|
||
|
||
```
|
||
ROAAUTO (VFP) ──POST /v1/prezentari {comanda + RAR creds + idempotency_key}──▶ Gateway FastAPI
|
||
(citește creds din Oracle clientului) │
|
||
├─ rezolvă maparea op→codPrestatie
|
||
├─ INSERT submission (PII TRANZITORIU, status=queued)
|
||
◀── răspuns imediat: {submissionId, status:queued|needs_mapping} ───────────┘ (dedup pe idempotency_key)
|
||
Worker (daemon/task fundal, poll SQLite) ── login RAR → JWT ──▶ postPrezentare ──▶ RAR
|
||
│ retry cu backoff în fereastra JWT
|
||
└─ la succes: PURJEAZĂ PII, reține doar hash+status+idPrezentare
|
||
Browser ──▶ Dashboard ── monitorizare CITITĂ LIVE din RAR (getAllPrezentari), nu din PII local ── + mapări, nomenclator
|
||
```
|
||
|
||
### Gestiunea credențialelor (cheia deciziei #5)
|
||
|
||
- ROAAUTO trimite `email`+`password` RAR la fiecare apel (din Oracle-ul clientului, peste **HTTPS**).
|
||
Creds-urile trăiesc **doar în itemul de coadă** (în memorie/rândul de lucru), folosite de worker pentru `login`,
|
||
apoi **șterse**. Parola **nu se persistă** și se **scrubează** din loguri ȘI din capturile de excepție/APM.
|
||
- Worker-ul face `login` (nu API-ul) → POST-ul răspunde imediat fără latența RAR. Worker: login → JWT → postPrezentare,
|
||
retry cu backoff **în fereastra JWT-ului**.
|
||
- **Onestitate despre robustețe:** coada NU aduce reziliență la indisponibilitate RAR de durată — JWT-ul e scurt și
|
||
nu ținem parola ca să reluăm peste expirare. Ce aduce coada: răspuns asincron rapid pentru ROAAUTO + jurnal central +
|
||
retry pe erori tranzitorii scurte. **Durabilitatea reală pe pene lungi stă în ROAAUTO**, prin job-ul periodic de
|
||
**re-push** al submission-urilor rămase `error/pending` (retrimite cu creds proaspete). Coada acoperă minutele,
|
||
ROAAUTO acoperă orele. (Dacă măsurarea TTL-ului arată JWT lung, reevaluezi — vezi „The Assignment".)
|
||
|
||
### Idempotență (critic — record legal, fără dubluri)
|
||
|
||
`postPrezentare` NU e idempotent, iar avem două bucle de retry (worker + re-push ROAAUTO) → risc de **prezentări
|
||
duplicate la RAR** pentru un record urmărit legal. Soluție:
|
||
- ROAAUTO trimite un `idempotency_key` = hash(cont + VIN + dataPrestatie + set(codPrestatie)).
|
||
- Gateway: `UNIQUE(idempotency_key)` pe `submissions`. Re-trimiterea aceleiași chei NU creează submission nou.
|
||
- Worker, înainte de a retrimite: dacă submission-ul are deja `idPrezentare` (răspuns RAR) → marchează `sent`, nu reapelează.
|
||
|
||
### Mașina de stări a unui submission
|
||
|
||
`queued → sending → { sent | needs_mapping | error }`
|
||
- `needs_mapping`: operație fără `codPrestatie` mapat → **se ține gateway-side, NU se trimite incomplet** (API-ul cere
|
||
`prestatii`); după ce mapezi, trece în `queued`. (≠ VFP-ul de azi care o arunca silențios.)
|
||
- `error`: eligibil pentru re-push din ROAAUTO (`GET /v1/prezentari?status=error`).
|
||
- `sent`: are `idPrezentare` de la RAR; terminal.
|
||
|
||
### Privacy-first / stateless (mentenanță & răspundere minime)
|
||
|
||
Decizie: gateway-ul e **pur tranzit + interfață cu RAR**, NU depozit de date.
|
||
- PII-ul prezentării (VIN, km, date) trăiește în SQLite **doar tranzitoriu** cât e în coadă; **la `sent` se purjează**,
|
||
rămân doar `idempotency_key` (hash ireversibil) + status + `idPrezentare`. Nu se poate reconstrui VIN-ul din hash.
|
||
- **Monitorizarea se citește LIVE din RAR** (`getAllPrezentari`/`getPrezentare`) — RAR e sursa de adevăr, nu un jurnal local.
|
||
- Durabilitatea pe pene lungi stă **la margine** (Oracle ROAAUTO / fișierul încărcat), nu la tine → re-push din client.
|
||
- **Fără agregare de date.** Datele service-urilor NU se folosesc pentru alte produse. (Eventual, în viitor, doar
|
||
produs separat cu **opt-in explicit + anonimizare**, lawyered — niciodată default.) Privacy = argument de adopție, nu doar conformitate.
|
||
|
||
### Componente (un repo, `docker compose up`)
|
||
|
||
1. **API (`app/api/v1`)** — FastAPI:
|
||
- `POST /v1/prezentari` (una/mai multe) → validare Pydantic, enqueue, răspuns cu `submissionId`.
|
||
- `GET /v1/prezentari?status=&data=` și `/{id}` — monitorizare programatică pentru ROAAUTO + re-push.
|
||
- `GET /v1/nomenclator`, `POST /v1/nomenclator/refresh`.
|
||
- `GET/PUT /v1/mapari` — CRUD mapare per cont.
|
||
- `PATCH /v1/prezentari/{id}/anulare`, `/corectie` — proxy peste markPrezentareAnulataById / patchPrezentare.
|
||
- Auth gateway: **API key per cont ROA** (separată de credențialele RAR ale clientului); cu emitere/rotire/revocare.
|
||
- *(Amânat, NU în v1: `POST /v1/import` xlsx/csv — strat 2 / piață non-ROA.)*
|
||
2. **Client RAR (`app/rar_client.py`)** — portare din `rar_autopass.prg`: login+JWT, getNomenclatorPrestatii,
|
||
postPrezentare, getAllPrezentari, getPrezentare, markPrezentareAnulataById, patchPrezentare. `httpx` + retry/backoff.
|
||
3. **Worker (daemon / task de fundal) + coadă pe SQLite (`app/worker`)** — proces pornit non-stop (sau task
|
||
`asyncio` în aplicația FastAPI), sub Docker `restart: always`. Buclă: ia rândurile `status='queued'`,
|
||
revendică atomic (`BEGIN IMMEDIATE; UPDATE … SET status='sending' WHERE id=? AND status='queued'`),
|
||
login RAR, trimite, retry cu backoff, scrie status + `idPrezentare`. Reacție **instant**, fără întârziere.
|
||
*Fără Redis, fără arq, fără Postgres.* ROAAUTO oferă durabilitatea pe pene lungi (re-push).
|
||
Atenție `b64Image`: poate fi mare → stocat ca BLOB sau path pe disc, nu în RAM.
|
||
4. **Dashboard (`app/web`)** — **Jinja2 + HTMX** (server-rendered, zero build): Monitorizare **citită live din RAR**
|
||
(`getAllPrezentari`) + starea cozii curente (din `submissions`), Editor mapări, Browser nomenclator. API-first.
|
||
5. **SQLite** (mod WAL) — înlocuiește DBF, un singur fișier `.db`:
|
||
- `accounts`, `api_keys` (conturi ROA + chei gateway).
|
||
- `operations_mapping` (cod_op_service → codPrestatie, `auto_send`=trimite automat dacă e mapat) ← `mapare_prestatii`.
|
||
- `nomenclator_rar` (cache {codPrestatie, numePrestatie}) ← `prestatii_rar`.
|
||
- `submissions` (coadă + dedup): `idempotency_key` UNIQUE, status, statusCode RAR, eroare, `idPrezentare`, retry, timestamps.
|
||
Câmpurile PII (vin, km, dataPrestatie, prestatii) sunt **tranzitorii** — populate cât e `queued/sending`,
|
||
**purjate la `sent`**. (≠ `rar_log` care era jurnal permanent.)
|
||
- **Notă: niciun câmp pentru parole RAR; niciun PII reținut după trimitere.** (`import_jobs` — doar la xlsx/csv, amânat.)
|
||
|
||
### Client ROAAUTO (VFP) — refactor minim
|
||
|
||
- `settings.xml` păstrează doar **URL gateway + API key** (nu mai ține mapări/nomenclator).
|
||
- Credențialele RAR ale clientului se citesc din **Oracle (ROA)** și se trimit în payload la gateway.
|
||
- `export_comenzi.prg` rămâne (citește `comenzi_service`/`operatii`), dar construiește JSON și face
|
||
`POST /v1/prezentari` în loc de XML + apel direct RAR.
|
||
- Dispar din VFP: `Login`, `UpdateNomenclator`, `GetCodRarPentruOperatie`, maparea, `rar_log` → trec în web.
|
||
- Se adaugă un job periodic „re-push pending" (timer existent din `rar-forms.prg` se poate reutiliza).
|
||
|
||
## Approaches Considered
|
||
|
||
- **A — sincron, fără coadă** (S, risc mic): ships rapid, dar fără retry autonom. Bun ca prim pas, respins ca țintă.
|
||
- **B — coadă + token JWT scurt** (M, recomandat ✅): robust la indisponibilitate RAR, nu pierzi prestații obligatorii legal.
|
||
- **C — outbox în Oracle** (M/L): cel mai decuplat, dar cere acces gateway→Oracle client (VPN/rețea). Reținut ca opțiune pentru clienți non-ROA / viitor.
|
||
|
||
## Open Questions
|
||
|
||
1. Durata reală a JWT-ului RAR (decide fereastra de retry autonom). De măsurat pe endpoint-ul de test.
|
||
2. `sistemReparat` / `tipPrestatie` — valori acceptate (enum nedocumentat în spec). De clarificat cu RAR.
|
||
3. Modelul de cont RAR per client: un singur user RAR per agent economic sau mai mulți (afectează cum mapezi `idUser`).
|
||
4. Monetizare/direcție (nedecisă): vezi mai jos — de reluat după ce A→B merge la primul client.
|
||
|
||
## Arhitectura în mare (modelul mental)
|
||
|
||
Azi VFP face 3 treburi pe FIECARE PC client: (a) ia comanda, (b) mapează + se loghează la RAR, (c) ține jurnalul →
|
||
de-aia trebuie redistribuit la fiecare corecție. Migrarea = muți (b)+(c) pe un server central al tău:
|
||
- **ROAAUTO (VFP, la client) = expeditorul** — citește comanda + creds RAR din Oracle și le trimite la gateway. Atât.
|
||
- **Gateway (Python, central) = creierul** — primește, mapează, login RAR, trimite, retry, jurnal. Aici faci corecțiile o dată, pentru toți.
|
||
- **Dashboard web = panoul de control** — vezi ce s-a trimis/eșuat, editezi maparea.
|
||
|
||
## Opțiuni de deploy (unde rulează gateway-ul)
|
||
|
||
| Opțiune | Cum | Cost | Când |
|
||
|---|---|---|---|
|
||
| **LXC Proxmox + Cloudflare Tunnel** | container la birou, expus public HTTPS fără IP static / porturi deschise | 0 € | **Start + teste** (risc: netul/curentul biroului) |
|
||
| **VPS mic always-on** (ex. Hetzner) | același container, mașină care nu cade | ~5 €/lună | **Clienți reali / producție** (recomandat) |
|
||
| romfast.ro / hosting.com | Python via cPanel/Passenger (WSGI) | inclus | ⚠️ FastAPI e ASGI + worker-ul e daemon → shared hosting nepotrivit (shim fragil, fără procese persistente). Doar landing |
|
||
|
||
Recomandare: start pe **LXC + Cloudflare Tunnel** (cost 0), mutare pe **VPS** la clienți. Mutarea = copiezi containerul + fișierul `.db`.
|
||
romfast.ro rămâne doar pentru landing/prezentare, nu țintă de producție (ASGI + worker daemon nu merg pe shared hosting).
|
||
|
||
- **Cod:** open-source pe github.com/romfast, **AGPL-3.0**. Deploy = **un container** cu FastAPI (uvicorn) + worker-ul ca task de fundal/al doilea proces, sub Docker `restart: always` + un volum SQLite.
|
||
- **Dev/staging:** LXC Proxmox. **Migrare date:** `tools/import_dbf.py` (`mapare_prestatii.DBF` + `prestatii_rar.DBF` cu `dbfread`).
|
||
|
||
## Teza de produs & direcție SaaS
|
||
|
||
**Teza:** *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ă din interfața oficială. Câștigi prin **efort minim cerut service-ului**,
|
||
nu prin features. (Funnel-ul „read public gratuit" — RESPINS: nimeni nu vrea să *citească* nomenclatorul; valoarea
|
||
e 100% în **trimitere**.)
|
||
|
||
**Lecția GTM de la demoanaf.ro:** a devenit viral nu prin model plătit, ci oferind o variantă **reimaginată, simplă,
|
||
plăcută** a unui serviciu oficial greoi/instabil; s-a răspândit în grupurile de Facebook. Echivalentul tău: o cale
|
||
**dramatic mai simplă decât tastarea manuală AUTOPASS**, construită rapid cu AI dar pe arhitectură solidă.
|
||
|
||
**Wedge validat:** automatizezi tastarea manuală AUTOPASS, **începând cu clientul care a cerut-o** (cale ROAAUTO/API).
|
||
|
||
**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. Îți rezolvi nevoia.
|
||
- **Treapta 2 (non-ROA, web upload):** import xlsx/csv cu **mapare reținută** (vezi mai jos) + dashboard. Login web,
|
||
fără instalare. **Primul venit** — freemium **pe volum** (gratis sub N prezentări/lună pt. service mici, plată peste;
|
||
metrica de preț = prezentări/lună = fix unitatea obligatorie legal).
|
||
- **Treapta 3 (diferențiere):** 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 (mergi indiferent ce
|
||
software are service-ul); (3) fiabilitatea de conformitate (retry, monitorizare din RAR — nu pierzi o declarație legală);
|
||
(4) **privacy** (nu reținem datele lor) — el însuși argument de adopție. Saltul fără rescriere: API key + mapare per cont,
|
||
**zero parole stocate, zero PII reținut**.
|
||
|
||
### Adopție în masă & praguri (calibrat pe cifre reale)
|
||
|
||
Declanșatorul trecerii de la tastarea manuală în RAR la upload = **timp salvat / efort de trecere**:
|
||
~2-4 min/prezentare manual × volum lunar. Clienți reali cunoscuți: **60-80** și **80-100 prezentări/lună** →
|
||
**3-6 ore/lună** de tastare = durerea care îi convertește.
|
||
- Prima folosire trebuie trivială (upload Excel existent → mapare reținută → trimite, sub 5 min).
|
||
- Gratis la volumul lor = fără decizie de achiziție, doar încearcă.
|
||
- **Freemium pe volum:** gratis **~30-40 prezentări/lună** (service mici = bază virală, cost ~0 la tine);
|
||
plată peste prag (banda 1 ≈ 50-150/lună prinde fix clienții actuali — primii bani de la cine a cerut serviciul).
|
||
Metrica de preț = prezentări/lună = unitatea obligatorie legal.
|
||
- Ironie de reținut: service-urile mici au cea mai mică durere (greu de convins, dar virale); volumele mari au cea
|
||
mai mare durere (cei mai dispuși să plătească). Gratis = achiziție; venit = volume mari.
|
||
|
||
## Import xlsx/csv — UX (stratul SaaS, treapta 2)
|
||
|
||
Două straturi de mapare, **ambele reținute per cont** (cheia produsului — „map once, reuse forever"):
|
||
1. **Mapare coloane** (schema fișierului → câmpuri canonice): ex. „«Serie șasiu»→VIN, «Index km»→odometruFinal".
|
||
Reluată automat dacă headerele se repetă.
|
||
2. **Mapare operații** (etichetele/codurile service-ului → `codPrestatie` AUTOPASS), cu **sugestie fuzzy** pe denumire.
|
||
|
||
Flux: upload → recunoaște coloanele (reia maparea) → propune maparea operațiilor (reținută + sugestii) →
|
||
**preview** (ce se trimite, rânduri nemapate flag-uite) → „Trimite la RAR" → monitorizare. A 2-a oară: upload → preview → trimite.
|
||
|
||
**Spectru de integrare (același backend):** API (POST prezentări, ca ROAAUTO) → drop fișier programat (folder/SFTP/email-to-import) → upload manual în browser (zero instalare). Cine poate, integrează API; cine nu, dă fișier.
|
||
|
||
## Success Criteria
|
||
|
||
- O prezentare reală trimisă din ROAAUTO prin gateway apare `FINALIZATA` la RAR (test), vizibilă în dashboard.
|
||
- Paritate cu VFP: același `codPrestatie` rezultat din mapare pe aceleași comenzi.
|
||
- Reziliență: RAR indisponibil → submission `queued/error` cu retry, ROAAUTO nu se blochează; re-push recuperează.
|
||
- Securitate: niciun credențial RAR în client/`settings.xml` și niciun câmp de parolă în SQLite.
|
||
- Privacy: după `sent`, în SQLite nu rămâne PII de vehicul (doar hash+status+idPrezentare); monitorizarea vine din RAR.
|
||
|
||
## The Assignment (următorul pas concret)
|
||
|
||
Pe endpoint-ul de **test RAR**, măsoară **durata de viață a JWT-ului** întors de `/public/login` (fă un login,
|
||
apoi `postPrezentare` la intervale crescătoare până la 401). Numărul ăsta dimensionează fereastra de retry
|
||
autonom din worker și decide dacă ai nevoie de job-ul de re-push în ROAAUTO. E o oră de muncă și deblochează
|
||
toată decizia de robustețe din B.
|
||
|
||
## What I noticed about how you think
|
||
|
||
- Ai corectat insight-ul meu „wedge = doar VIN+km" cu „API-ul cere și operațiile de manopelă" — și aveai
|
||
dreptate, ai citit contractul, nu l-ai presupus. Asta e exact instinctul care face diferența: sursa, nu pitch-ul.
|
||
- Ai pus singur problema de custodie a parolelor („nu vreau să salvez parole") înainte să ți-o ridic eu —
|
||
gândești în termeni de răspundere, nu doar de „merge/nu merge".
|
||
- Vezi proiectul ca ISV, nu ca utilizator final: „nu vreau să redistribui la fiecare corecție" e fix
|
||
raționamentul care justifică web-ul. Mulți ar fi rescris fără să poată articula de ce.
|
||
```
|