Data contract. Config citit de retailer din distributorLinks/{pair}.loyalty (deja two-party readable — zero schimbări de rules pentru nudge) · scrieri: updateLoyaltyProgram (program implicit + fan-out) și updateLinkLoyalty (personalizat / reset) — permisiune nouă loyalty.manage, doar distributor_owner · nivelul se determină din volumul cumulat: resolveLoyaltyTier(tiers, link.loyaltyProgressMinor + grossTotals.inclVatMinor) — aceeași funcție pe server (submitOrder) și pe client → previzualizarea din coș = comanda trimisă; contorul crește la submit și scade la cancel/reject (floor 0) · bonusul lucrătorului: snapshot la submit (doar retailer_worker, min pe comanda curentă), plătit la deliverOrder → doc workerBonuses/{id} · citiri câștiguri: worker where('workerUid'==uid) · owner/admin where('retailerIdno'==org) · distribuitor owner where('distributorIdno'==org). Program exemplu (toate frame-urile) — praguri pe TOTAL CUMULAT comandat (cu TVA): ≥3.000 lei → 2% · ≥10.000 lei → 4% · ≥25.000 lei → 5% · bonus 10 lei/comandă livrată ≥300 lei. Nav: tabsConfig NESCHIMBAT — worker păstrează tab-ul Loialitate; owner/admin văd loialitatea în hub-ul Distribuitori.
Descoperibilitate (frame-urile A1–A5, redesign) — problema reală, NU feature lipsă: ecranele de mai sus (1–11) există deja și funcționează. Ce lipsea: cardul fals „Loialitate · în curând" pe Acasă worker (deși tab-ul merge), zero intrare de loialitate pe Acasă pentru celelalte 4 roluri, și niciun roll-up cross-partener. A1 înlocuiește minciuna cu un card real; A2/A4 adaugă carduri pe Acasă (owner/admin, distribuitor); A3 loyalty-summary = agregat retailer; A5 loyalty-bonuses = acumulări pe magazin pentru distribuitor. Reutilizează datele existente — fără backend/settlement. Copy: „câștigat/generat", NICIODATĂ „de plată/datorat" (bonusurile n-au stare de decontare).
Coș · sub prag · nudge
Cardul-semnătură al feature-ului. LoyaltyNudgeCard: bară de progres pe volumul CUMULAT (total comandat + coșul curent) către următorul nivel + propoziție completă, profesională — niciodată „→ 4%". Coșul e push (fără tab bar).
9:41●●● ▮
Coș · Vitafor
3 produse · gata de trimis
Lapte Sec 12%
1 l · 20,57 lei/l · TVA 8%
6 × 20,57 lei123,42 lei
6
Iaurt natural 350g
buc · 13,33 lei · TVA 8%
10 × 13,33 lei133,30 lei
10
Smântână 20%
200g · 17,15 lei · TVA 8%
4 × 17,15 lei68,58 lei
4
Reducere de fidelitate fără reducere încă
Adaugă produse de încă 168,68 lei și treci la reducerea de 2%.
Subtotal fără TVA325,30 lei
TVA 8%26,02 lei
Total351,32 lei
Total · 351,32 leiTrimite comanda
Primitive: new LoyaltyNudgeCard (components/loyalty/) · CartTotalsCard neschimbat aici Date: useDistributorLink → link.loyalty + loyaltyProgressMinor · resolveLoyaltyTier(tiers, progress + grossTotals.inclVatMinor) Bară: segmentul către următorul nivel (progres în interval), nu toată scara Fără program pe link: cardul NU se randează deloc Copy „far": „Adaugă produse de încă {X} lei și treci la reducerea de {Y}%."
Coș · trece la 4% · disclosure
Momentul-cheie al mecanicii cumulate: volumul total (8.940 lei) + coșul curent (1.076,19 lei) trece de 10.000 → această comandă urcă nivelul la 4%. Rând „Reducere fidelitate" în totaluri, CTA cu totalul NET.
9:41●●● ▮
Coș · Vitafor
9 produse · gata de trimis
Lapte Sec 12%
1 l · 20,57 lei/l · TVA 8%
24 × 20,57 lei493,68 lei
24
Iaurt natural 350g
buc · 13,33 lei · TVA 8%
30 × 13,33 lei399,90 lei
30
Smântână 20%
200g · 17,15 lei · TVA 8%
6 × 17,15 lei102,90 lei
6
Reducere de fidelitate ✓ treci la 4%
Cu această comandă treci la reducerea de 4% — economisești 43,06 lei.
Subtotal fără TVA996,48 lei
Reducere fidelitate 4%−39,87 lei
Total fără TVA956,61 lei
TVA 8%76,52 lei
Total1.076,191.033,13 lei
Total · 1.033,13 leiTrimite comanda
Primitive: LoyaltyNudgeCard (unlocked) · CartTotalsCard cu prop loyalty {percentBp, savedMinor} Matematică: aceleași funcții shared ca serverul → per-linie, half-up: 996,48 → 956,61 · TVA din baza netă 76,52 · 1.033,13 grossTotals vs projectedNetTotals: nivelul se decide pe gross (996,48 excl / 1.076,19 incl); afișarea/CTA pe net — niciodată amestecate Copy „la nivel" (fără trecere): „Reducere de {Y}% aplicată — nivelul tău curent." NICIODATĂ „permanent" Ultimul prag atins: rândul „next" dispare
Comandă · float · nudge secundar
Tab-root Comandă (worker). Float-ul existent primește o singură linie de nudge — totalul devine incl-TVA precis (înlocuiește aproximarea fără TVA). Fără program → linia lipsește, float neschimbat.
9:41●●● ▮
M
Comandă
Vitafor SRL
NC
Lapte Sec 12%
1 l · 20,57 lei · TVA 8%
6 în coș
Iaurt natural 350g
buc · 13,33 lei · TVA 8%
10 în coș
Smântână 20%
200g · 17,15 lei · TVA 8%
4 în coș
Brânză de vaci 5%
500g · 28,40 lei · TVA 8%
+ Adaugă
Chefir 2,5% 1L
1 l · 15,90 lei · TVA 8%
+ Adaugă
Coș · 20 produse351,32 lei
Adaugă produse de încă 168,68 lei și treci la reducerea de 2%.
Acasă
🛒
Comandă
🕐
Istoric
Loialitate
Fișier: components/catalog/ProductOrderBody.tsx — float Schimbare: total float = incl-TVA precis via computeLineTotals (nu aproximarea excl) Baza: loyaltyProgressMinor + totalul float (gross) La nivel: caption devine „Reducere de {Y}% aplicată — nivelul tău curent."
Comandă · detail · disclosure
După trimitere: card „Reducere fidelitate" imediat sub produse — obligatoriu când order.loyalty.discount există (liniile arată preț de listă lângă valori nete).
9:41●●● ▮
Comanda A1B2C3
Vitafor SRL · azi, 14:20
trimisă
Produse · 9
Lapte Sec 12%
24 l × 20,57 lei/l (listă) · TVA 8%
valoare cu reducere473,93 lei
Iaurt natural 350g
30 buc × 13,33 lei (listă) · TVA 8%
valoare cu reducere383,90 lei
Reducere fidelitate
Subtotal fără reducere1.076,19 lei
Reducere fidelitate 4%−43,06 lei
Total1.033,13 lei
Valorile liniilor includ deja reducerea de 4%. Prețul unitar afișat este prețul de listă.
Fișier: app/(app)/orders/[orderId].tsx — card după Produse Date: order.loyalty {preDiscountInclVatMinor, discount.savedInclVatMinor} Legacy: comenzi fără loyalty → cardul lipsește, ecran identic cu azi
Factură · reconciliere fiscală
Blocul de totaluri al facturii reconciliază explicit: subtotal fără reducere → reducere → bază netă → TVA → total. Notă sub tabel. Identic în PDF (RENDER_VERSION bump).
9:41●●● ▮
Factura AB 000127
Vitafor SRL → Magazin Central
AB 000127
factură fiscală · 6 iul 2026
Lapte Sec 12%
24 l × 20,57 (listă)
473,93
Iaurt natural 350g
30 buc × 13,33 (listă)
383,90
Smântână 20%
6 buc × 17,15 (listă)
98,78
Subtotal fără reducere (fără TVA)996,48
Reducere fidelitate 4%−39,87
Total fără TVA956,61
TVA 8%76,52
Total cu TVA1.033,13 lei
Valorile liniilor includ reducerea de fidelitate 4%. Prețul unitar afișat este prețul de listă.
Fișiere: components/invoices/InvoiceDoc.tsx + renderInvoicePdfBuffer Date: invoice.loyalty {percentBp, preDiscountExclVatMinor, savedInclVatMinor} Fără reducere: factura se randează exact ca azi (rânduri doar când invoice.loyalty există)
Loialitate · worker · câștiguri
Tab-ul Loialitate (deja worker-only) devine real: „Câștiguri" — istoricul lunar, NU „sold de plată". Jos, condițiile fiecărui distribuitor.
9:41●●● ▮
M
Magazin Central
Lucrător
NC
Câștigurile mele
bonusuri pentru comenzile livrate pe care le-ai plasat
Iulie 2026
240 lei
24 comenzi livrate · plata o primești de la distribuitor
Istoric
Comanda A1B2C3 · Vitafor
livrată 4 iul · 1.033,13 lei
+10 lei
Comanda Z9Y8X7 · Vitafor
livrată 2 iul · 726,40 lei
+10 lei
Comanda Q4W5E6 · Lacto Plus
livrată 1 iul · 512,00 lei
+15 lei
Condiții
V
Vitafor SRL
10 lei pentru fiecare comandă livrată de cel puțin 300 lei pe care o plasezi tu.
Acasă
🛒
Comandă
🕐
Istoric
Loialitate
Fișier: app/(app)/(tabs)/loyalty.tsx înlocuiește StubScreen Date: useWorkerBonuses(workerUid) — sumă lunară pe client · terms din link.loyalty Copy: „Câștiguri" (istoric), NICIODATĂ „sold" / „de încasat" RBAC: rules — worker citește doar propriile bonusuri
Loialitate · empty · fără program
Două goluri distincte: niciun distribuitor nu oferă bonus (sus) vs program există, încă niciun câștig (jos, cu condițiile vizibile ca motivație).
9:41●●● ▮
M
Magazin Central
Lucrător
NC
Câștigurile mele
variante de gol — una singură pe ecran
Fără program de bonus încă

Niciun distribuitor nu oferă momentan bonusuri pentru comenzile plasate. Când unul activează programul, îl vezi aici.

SAU · program activ, zero câștiguri
Încă niciun câștig

Primești 10 lei pentru fiecare comandă livrată de cel puțin 300 lei pe care o plasezi la Vitafor SRL.

Acasă
🛒
Comandă
🕐
Istoric
Loialitate
Primitive: EmptyHero ×2 (fără CTA — nu există acțiune posibilă) Branch: links fără workerBonus → varianta 1 · altfel varianta 2
Distribuitor · detail · owner
Owner-ul NU primește tab nou — loialitatea trăiește în hub-ul Distribuitori: condițiile programului + câștigurile echipei, pe detaliul partenerului existent.
9:41●●● ▮
Vitafor SRL
legat de 67 zile · 18 comenzi
activ
Nivelul tău de fidelitate
Total comandat nivel curent · 4%
Ai comandat în total 14.360 lei — fiecare comandă primește acum 4% reducere.
Program de fidelitate
V
Niveluri după total comandat
program implicit
Total comenzi de la 3.000 lei2%
Total comenzi de la 10.000 lei4%
Total comenzi de la 25.000 lei5%
10 lei bonus pentru lucrătorul care plasează o comandă livrată ≥ 300 lei.
Câștigurile echipei · total
Ana Rusu
12 comenzi livrate
120 lei
Ion Ceban
9 comenzi livrate
90 lei
Transparență: owner-ul vede tot ce câștigă echipa — bonusul e plătit de distribuitor, în afara aplicației.
Economii din reduceri
%
Economisit în total
18 comenzi cu reducere
218,40 lei
Fișier: app/(app)/partners/[distributorIdno].tsx — 4 secțiuni noi Date: link.loyaltyProgressMinor (hero) · link.loyalty (terms) · useWorkerBonuses(retailerIdno) grupat pe worker · Σ order.loyalty.discount.savedInclVatMinor Perioadă: câștiguri + economii = TOTAL (lifetime, toate înregistrările) — nu lunar. Eticheta corectată din „iulie" → „total". Nav: tabsConfig NESCHIMBAT · vederea per-partener; roll-up cross-distribuitor e ecranul nou loyalty-summary (A3) Fără program: secțiunile lipsesc; istoricul rămâne dacă există acumulări
Editor program · distributor owner
Push din meniul de cont (precedent: fiscal-profile). Editor de praguri (max 5) + secțiune bonus lucrător. Salvarea se propagă automat la toți partenerii fără program personalizat.
9:41●●● ▮
Program de fidelitate
implicit pentru toți partenerii
Niveluri de reducere
Total comenzi de la (lei)
3.000
Reducere
2%
Total comenzi de la (lei)
10.000
Reducere
4%
Total comenzi de la (lei)
25.000
Reducere
5%
+ Adaugă nivel
Nivelurile se aplică la totalul cumulat al comenzilor cu TVA (inclusiv comanda curentă), pe toată durata legăturii. Nivelul se recalculează la fiecare comandă — anulările scad totalul. Maximum 5 niveluri, crescătoare.
Bonus pentru lucrători
Bonus / comandă livrată (lei)
10
Minim (lei)
300
Bonus fix pentru lucrătorul care plasează comanda — se acordă la livrare. Comenzile plasate de proprietar sau administrator nu primesc bonus. Plata se face în afara aplicației.
Salvează programul
Fișier nou: app/(app)/loyalty-program.tsx · intrare în meniul de cont Acțiune: updateLoyaltyProgram({tiers, workerBonus}) → fan-out pe link-urile cu program implicit RBAC: loyalty.manage — DOAR distributor_owner Validare: praguri strict crescătoare (lei ȘI %), 1–5 praguri, bonus null = secțiune goală Golire: zero praguri + fără bonus = programul se șterge de peste tot
Parteneri · override · sheet
Din cardul partenerului (tab Parteneri): sheet cu Implicit / Personalizat. Personalizat = praguri proprii doar pentru acest retailer; „Implicit" re-copiază programul curent.
9:41●●● ▮
V
Vitafor SRL
Proprietar
VD
Parteneri
14 activi
M
Magazin Central
IDNO 1003600012345 · legat de 67 zile
activ
Fidelitate
Pauză
Fidelitate
Magazin Central
Alege programul aplicat acestui partener.
Implicit
Personalizat
Total comenzi de la (lei)
2.000
Reducere
3%
Total comenzi de la (lei)
6.000
Reducere
5%
+ Adaugă nivel
Bonus lucrător (lei)
15
Minim (lei)
300
Componentă nouă: components/partners/LinkLoyaltySheet.tsx · BottomSheet Acțiune: updateLinkLoyalty({linkId, mode:'custom'|'default', tiers?, workerBonus?}) Badge pe card: partenerii cu program personalizat primesc badge „personalizat" în listă „Implicit": copiază programul implicit curent (sau șterge dacă nu există)
Erori · inline
Erori inline, niciodată toast: fan-out peste 400 parteneri (failed-precondition), validare praguri (invalid-argument), citire câștiguri refuzată (permission-denied).
9:41●●● ▮
Program de fidelitate
stări de eroare
invalid-argument
Nivelurile trebuie să crească
Fiecare nivel trebuie să aibă sumă și reducere mai mari decât nivelul anterior. Corectează nivelul 2.
failed-precondition
Prea mulți parteneri pentru actualizare directă
Programul nu a fost salvat. Contactează echipa SDI pentru actualizarea în masă.
permission-denied
Nu putem încărca câștigurile
Verifică conexiunea sau reîncearcă. Dacă persistă, cere proprietarului să verifice accesul tău.
Primitive: ErrorCard (variant ← errorCode) Ref: design/states.html
A1 · Acasă worker · card live
Kill-the-lie: cardul fals „Loialitate · în curând" (bară 0%) devine un rezumat REAL al câștigurilor lunii, care duce în tab-ul Loialitate. „Un copil sau un om în vârstă" vede imediat cât a câștigat.
9:41●●● ▮
M
Magazin Central
Lucrător
NC
Bună, Nicolae
Hai să comandăm pentru săptămâna asta
⇄ Comandă din nou
Repetă ultima comandă — Vitafor
14 produse · 1.247,80 lei · trimisă acum 6 zile
Începe
Vezi catalogul
Loialitate
Câștigat luna aceasta
40 lei
3 comenzi livrate · vezi toate câștigurile
Acasă
🛒
Comandă
🕐
Istoric
Loialitate
Fișier: components/home/RetailerWorkerHome.tsx — șterge Card-ul fals (63–75) Primitive: LoyaltyHomeCard (nou) · onPress → /(app)/(tabs)/loyalty Date: useWorkerBonuses({workerUid}).thisMonthMinor + count · useDistributorLinks('retailer') pentru starea de gol Copy: „câștigat" — NICIODATĂ „de încasat"
A1 · Acasă worker · fără program
Când niciun distribuitor nu oferă bonus (link fără workerBonus): linie onestă, fără bară falsă de 0% și fără chevron — nu există unde naviga încă.
9:41●●● ▮
M
Magazin Central
Lucrător
NC
Bună, Nicolae
Hai să comandăm pentru săptămâna asta
⇄ Comandă din nou
Repetă ultima comandă — Vitafor
14 produse · 1.247,80 lei · trimisă acum 6 zile
Începe
Vezi catalogul
Loialitate
Loialitate
Fără program încă
Apare aici când un distribuitor pornește un program de bonus.
Acasă
🛒
Comandă
🕐
Istoric
Loialitate
Branch: niciun activeLink cu hasWorkerBonus & zero câștiguri → varianta onestă Regula: NICIODATĂ o bară de progres 0% falsă
A2 · Acasă owner/admin · card
Owner/admin nu aveau NICIO intrare de loialitate pe Acasă. Card nou: economii + câștigurile echipei (luna aceasta), care duce în ecranul agregat loyalty-summary (A3). Admin — identic.
9:41●●● ▮
M
Magazin Central
Proprietar
AG
Bună, Ana
2 comenzi în curs · 1 invitație în așteptare
▦ Comenzi în curs
2 comenzi așteaptă livrarea
Vitafor · Lacto Plus · ultima acum 2 zile
Vezi comenzile
Comandă
Loialitate
Economisit luna aceasta
218 lei
echipa a câștigat 210 lei · vezi tot pe distribuitori
Acasă
🛒
Comandă
🕐
Istoric
🔗
Distribuitori
Fișiere: RetailerOwnerHome.tsx + RetailerAdminHome.tsx · primitivă comună LoyaltyHomeCard Date: useWorkerBonuses({retailerIdno}) (lunar) · useOrders('retailer', idno, [...PLACED_ORDER_STATUSES]) (economii lunare) · useAllDistributorLinks('retailer') Vizibil dacă: vreun activeLink hasEffectiveProgram SAU există istoric (D2/D2b) onPress: → /loyalty-summary
A3 · loyalty-summary · populated
Ecran nou (push, ScreenHeader). Roll-up cross-distribuitor pe care Frame 8 nu-l dă: nivelul la FIECARE distribuitor + economii (lună + total) + câștigurile echipei. Rânduri de nivel = doar linkuri active; economii/câștiguri = toate înregistrările (D2b).
9:41●●● ▮
Loialitate
toate distribuitorii
Economii din reduceri
Economisit luna aceasta
218,40 lei
total 1.240,00 lei · 18 comenzi cu reducere
Nivelul pe distribuitor
V
Vitafor SRL
nivel 4% · total comandat 14.360 lei
4%
L
Lacto Plus SRL
nivel 2% · mai comandă 1.200 lei → 4%
2%
Câștigurile echipei · luna aceasta
Ana Rusu
12 comenzi livrate
120 lei
Ion Ceban
9 comenzi livrate
90 lei
Transparență: bonusul e plătit de distribuitor, în afara aplicației.
Fișier nou: app/(app)/loyalty-summary.tsx (push, ScreenHeader) Date: per-distribuitor resolveLoyaltyTier(link.tiers, progress) · economii din useOrders (lună + total) · useWorkerBonuses({retailerIdno}) grupat pe worker RBAC: retailer_owner || retailer_admin (oglindă regulă retailerIdno)
A3 · loyalty-summary · loading / empty
Sus: loading (skeleton, din states.html) cât se abonează listenerii. Jos: empty — link-uri active dar niciun program efectiv și niciun istoric.
9:41●●● ▮
Loialitate
toate distribuitorii
Se încarcă
SAU · empty · fără program
Niciun program de fidelitate

Distribuitorii tăi nu au încă un program activ. Când unul pornește, nivelul și economiile apar aici.

Primitive: skeleton-row (loading) · EmptyHero (empty, fără CTA) Error: permission-denied → ScreenHeader + ErrorCard (vezi Frame 11) · toți hookurile primesc null
A4 · Acasă distribuitor · card
Distribuitorul configura programul într-un rând „award" din meniul de cont, apoi devenea invizibil. Card nou pe Acasă: bonusuri generate (nu „de plată" — D1) + starea programului, care duce în loyalty-bonuses (A5).
9:41●●● ▮
V
Vitafor SRL
Proprietar
VG
Bună, Victor
4 comenzi așteaptă confirmarea ta
▦ De confirmat
4 comenzi noi așteaptă
De confirmat și pregătit pentru livrare
Vezi comenzile
Loialitate
Bonusuri generate luna aceasta
210 lei
Program activ · 3 niveluri · 5 parteneri · vezi detalii
Acasă
📦
Produse
🛒
Comenzi
🔗
Parteneri
📍
Vizite
Fișier: components/home/DistributorOwnerHome.tsx Date: useWorkerBonuses({distributorIdno}).thisMonthMinor · organization.loyaltyProgram · useAllDistributorLinks('distributor') pentru programe custom-only (D2) RBAC: hasPermission('loyalty.manage') — altfel cardul lipsește Copy: „generate" — NICIODATĂ „de plată/datorat"
A5 · loyalty-bonuses · populated
Ecran nou (push). Acumulări grupate pe magazin (nu pe lucrător — numele lucrătorilor nu-s citibile cross-org, D3). Lună + total. Buton secundar → editorul de program existent.
9:41●●● ▮
Bonusuri de fidelitate
generate pentru parteneri
Generat luna aceasta
210 lei
total 1.480 lei · plătit în afara aplicației
Pe magazin · luna aceasta
M
Magazin Central
21 comenzi livrate · 2 lucrători
210 lei
A
Alimentara Nord
niciun bonus luna aceasta
0 lei
Notă: bonusurile se plătesc de tine, în afara aplicației — aici vezi cât s-a generat, nu un sold.
Fișier nou: app/(app)/loyalty-bonuses.tsx (push, ScreenHeader) Date: useWorkerBonuses({distributorIdno}) grupat pe retailerIdno · nume din useAllDistributorLinks('distributor').retailerName else maskIdno (D3) Secundar: → ./loyalty-program (editorul existent) RBAC: hasPermission('loyalty.manage')
A5 · loyalty-bonuses · empty / error
Sus: empty — program activ dar niciun bonus generat încă. Jos: error — acces refuzat pe rută deschisă direct (toți hookurile primesc null).
9:41●●● ▮
Bonusuri de fidelitate
generate pentru parteneri
Încă niciun bonus generat

Când un lucrător al unui partener plasează o comandă care se livrează, bonusul apare aici.

SAU · acces refuzat (rută directă)
permission-denied
Acces refuzat
Doar proprietarul distribuitorului poate vedea bonusurile generate. Cere accesul potrivit.
Primitive: EmptyHero (empty) · ErrorCard variant ← errorCode Regula: rută refuzată = ScreenHeader + ErrorCard; toți hookurile primesc null (fără citiri permise)