Tendlo — VermogenOntwerpvarianten · ontwerp-gate
Ontwerpvoorstel · wacht op keuze eigenaar · 26 juli 2026

Vermogen wordt de overkoepeling. Vastgoed is er één onderdeel van.

Dit stuk gaat door de ontwerp-gate: het legt drie serieuze varianten naast elkaar voor hoe Vermogen zich verhoudt tot Vastgoed en de andere geld-modules, met per variant wat het kost én wat je ervoor opgeeft. Onderaan staat één expliciete aanbeveling. Er is nog géén code gebouwd, en die gaat er ook niet in voor jij een variant kiest.

Ticket: TEN-87 (programma) · raakt TEN-118 · TEN-111 gaat los vooruit · supersedeert straks ADR-0012 · geverifieerd tegen main op 2026-07-26 (schemaVersion 57, hoogste migratie 0081).

Je leest versie 3. Twee eerdere versies zijn al gecommit en gedeployed; als je de pagina eerder hebt gelezen, is dit wat er sindsdien bij is gekomen:

v3 (deze): nieuw — § Drie · Eén record, meerdere beelden (balans versus kasstroom als expliciete ontwerpregel, met de bevinding dat leningen de kosten-hub nu al voeden) en open vraag 7 (hypotheek op meerdere objecten, vier varianten). Open vraag 2 is daarop aangescherpt.
v2: § Acht herschreven — de afwijzing van Uplisting was onjuist; Uplisting is nu de voorkeursroute, met een tabel met herkomst en verificatiestatus per claim.

Eén

Wat je besloten hebt

"Het moet meer 'Vermogen' worden, waarbij vastgoed 1 onderdeel is."

"Maar ook de specifieke 'vastgoed' of verhuur opties moeten hier in blijven."

Eigenaar, 2026-07-26

Die twee zinnen samen zijn scherper dan ze los lijken. De eerste zegt: er komt een niveau erboven — Vermogen is niet een nieuwe naam voor Vastgoed en niet een uitbreiding ervan, maar een laag waaronder vastgoed hangt naast schulden, spaargeld en later beleggingen. De tweede zegt: die laag mag niets platslaan. Vastgoed is deze week juist verdiept (verhuurtype, wisseldagbolletjes, opzegtermijn-melding, objectkleur); een abstractie die daarvan "een bezitting met een waarde" maakt, breekt precies wat de tester zelf heeft aangevraagd en getest.

Dit herziet een eerder besluit. ADR-0012 (geaccepteerd 2026-07-22) legde vast dat het Vermogen-overzicht "een scherm/tab binnen het bestaande domein Financieel" zou worden — de allerlichtste vorm, expliciet géén eigen plek in de navigatie. Dat is precies wat de zin van 26 juli omdraait. Die ADR moet dus vervangen worden; zie § Wat er met de ADR's gebeurt.

Twee

Wat behouden blijft — de harde randvoorwaarde

Dit is geen terzijde maar een controlelijst: voor de fixer die het bouwt, en voor de tester die deze functies deze week zelf heeft aangevraagd en getest. Elke regel moet ná de herpositionering nog exact zo werken. Alles hieronder staat vandaag op main.

Randvoorwaarde

Vastgoed verliest niets. Geen enkele variant mag een vastgoed-functie verwijderen, verplaatsen naar een generiek scherm, of achter een extra tik zetten. Waar een variant dat wél zou doen, staat dat expliciet bij "wat je opgeeft".

  • Verhuurtype per object — kortverhuur (wisselgasten) versus langlopend contract, en dat verhuurtype bepaalt zowel de agenda-weergave als de melding.lib/features/vastgoed/vastgoed/domain/rental_mode.dart:15 · kolom vastgoed_tables.dart:47
  • Boekingen met gast en periode, inclusief de lege-gast-conventie voor de jaarlijkse ligplek-verhuur, plus overlap-waarschuwing bij dubbele boekingen.vastgoed_tables.dart:80 (guest :87, start :90, end :93) · domain/booking_overlap.dart · presentation/add_booking_sheet.dart
  • De bolletjes in de agenda-dagkop — één gevuld bolletje van 10 px per object op álle gedekte dagen, met de check-in/check-out-glyph ernaast op de wisseldagen, max 4 + +n, sortering aankomst → vertrek → doorlopend.TEN-120 fase A (commit 39a4611b)
  • Het dag-sheet "Verhuur · datum" achter één tap-target van 44 px, met per boeking object, gast en fase/einddatum. Zonder dat landen "door wie" en "tot wanneer" nergens meer.TEN-120 fase A
  • Langlopend contract = 3 momenten, geen 365 dagbanden — start, opzegdatum (einde − opzegtermijn) en einde, met één melding op de opzegdatum; band en melding delen één berekende datum.TEN-120 fase A · kolom noticePeriod vastgoed_tables.dart:72
  • Check-in/check-out-meldingen bij kortverhuur, met de per-module meldingstermijn, meldtijd en geluid uit de module-instellingen.presentation/vastgoed_providers.dart (resyncVastgoedReminders)
  • showInAgenda als escape-hatch per object — raakt bewust alleen de weergave; de meldingen blijven staan als de vlag uit gaat.vastgoed_tables.dart:53
  • Objectkleur en object-avatar als leading in de agenda in plaats van het generieke huisje.presentation/property_avatar.dart:26 + PropertyAgendaLeading:93 · colorHex vastgoed_tables.dart:58 · vastgoed_module.dart:49-67
  • De agenda-mirror geeft deel-stand en eigenaar door (TEN-129) en vastgoed staat in kSelfSurfacedModules zodat de mirrors niet ook nog in de Vandaag-dagstroom en de gezinsstrip staan. Niet terugdraaien.commit d441045b · TEN-120 fase A
  • De aanstaande structurele fix op branch fix/ten-120-nullable-kolommen (twee kolommen client-side nullable) moet vooruit kunnen. Geen enkele variant hier vereist dat die wordt teruggedraaid.lokale branch, nog niet gemerged
  • Objecttypen incl. "Overige" met vrije categorie (TEN-84) en bijlagen per object.domain/property_type.dart · presentation/add_property_sheet.dart
  • De rendement-kaart op het objectdetail (fase 1: bevestigde + afgeronde boekingswaarde per jaar, verwacht, bruto huurrendement, jaarrente-momentopname) blijft op het objectdetail staan — niet verhuizen naar het Vermogen-overzicht.domain/property_return_calc.dart · presentation/property_detail_screen.dart:170
  • Het deelbeleid van vastgoed — configureerbaar met privé als fallback, cascade van object naar zijn boekingen, en de deel-hint bij een uitgezette module.lib/core/modules/share_policy.dart:120 · share_bulk_remapper.dart:120 · shared_in_disabled_module_watcher.dart:45
  • De per-module instellingen op module-id vastgoed — aan/uit per lid, meldingstermijn, meldtijd, geluid, verbergen voor bepaalde leden, favoriet in de snelbalk. Dit is opgeslagen data, geen code: het staat op de rij <householdId>:vastgoed.lib/core/database/tables/foundation_tables.dart:185 (ModuleStates) en :236 (MemberModulePrefs)

Die laatste regel is niet zomaar de laatste. Alle veertien regels hangen aan het bestaan van een module met id vastgoed. Route, agenda-mirror, item-decoratie, reminder-resync, deelbeleid en instellingen zijn alle zes op dat id gesleuteld. Zodra vastgoed géén eigen module meer is, is elk van die zes een migratie in plaats van een gegeven. Dat is de reden dat de architectuurvraag hieronder eigenlijk al beslecht is — maar ik toets dat expliciet in plaats van het aan te nemen.

Drie

Wat hoort er in Vermogen?

Een vermogensoverzicht dat schulden negeert, is misleidend. Daarom twee lijsten: bezittingen en schulden. En een derde categorie die er bewust niet in hoort: kasstroom. Vaste kosten, abonnementen, verzekeringspremies en inkomsten zeggen iets over wat er per maand stroomt, niet over wat je hebt. Die horen in de kosten-hub en blijven daar.

Bezittingen

KandidaatTabelWaar de waarde staatNu al bruikbaar?
Vastgoed
module vastgoed
properties
vastgoed_tables.dart:13
current_value (:36), handmatig bijgehouden, met purchase_price (:32) als terugval Ja
Spaargeld
module spaardoelen
savings_goals + savings_contributions
savings_goals_table.dart:9
Géén opgeslagen saldo — afgeleid als start_amount + Σ bijdragen in savings_progress.dart:55 Ja
Auto's / wagenpark
module wagenpark
vehicles
wagenpark_tables.dart:11
Alléén purchase_price (:31). Geen actuele waarde — een auto van 8 jaar oud staat er voor zijn nieuwprijs in Open vraag 4
Betaal-/spaarrekeningen
module bankrekeningen
bank_accounts
bankrekeningen_tables.dart:14
Geen saldo-kolom — bewust puur administratief (alleen iban_last4, houder, bank) Open vraag 5
Beleggingen / crypto Bestaat niet. Geen tabel in Drift, PowerSync of Postgres. Wél ontworpen: ADR-0017 (handmatige waarderingsregel per peildatum, expliciet géén live koersfeed) Fase V3
Verzekeringen Alleen premie (kasstroom). Geen afkoop-/opgebouwde waarde — een levensverzekering met waarde kan niet worden vastgelegd Nee

Schulden

KandidaatTabelWaar het bedrag staatNu al bruikbaar?
Leningen & hypotheek
module leningen
loans
leningen_table.dart:10
current_balance (:27) = restschuld; interest_rate (:30); totaal via loan_calc.dart:10 (totalDebt) Ja
Leningdelen (hypotheek in delen) loan_parts
leningen_table.dart:54
current_balance (:65) per deel; gewogen rente in loan_calc.dart:29 Ja
Financierings­koppeling properties.financing_loan_id
vastgoed_tables.dart:42
Zwakke, éénzijdige, 1-op-1 referentie (geen FK). Een lening weet niet welk object hij financiert; het object wijst naar de lening. Resolutie in property_return_calc.dart:216 Zie open vraag 2
Openstaand kleed-/zakgeld allowance_ledger
gezinsafspraken_tables.dart:71
amount_cents met status open/uitbetaald — technisch een kleine huishoudschuld Buiten scope (te klein, verkeerd domein)

Wat dit betekent voor het eerste getal

Met alléén bestaande data en zonder één migratie is dit al te berekenen: panden (waarde of aankoopprijs) + spaargeld (uit het bestaande ledger) − restschuld van alle leningen (met de gekoppelde hypotheek precies één keer geteld). Dat is geen half getal: het dekt bij dit huishouden vermoedelijk het overgrote deel van het vermogen. Alles daarna — auto's, beleggingen, waardehistorie — is verrijking, geen voorwaarde. Zie § Fasering.

Eén record, meerdere beelden — de belangrijkste ontwerpregel

Terechte zorg van de eigenaar: "Een hypotheek of lening kan in meerdere modules terugkomen, dus in meerdere contexten, net zoals een abonnement ook in vaste kosten kan zitten." De angst is een Vermogen-module waar alles in verdwijnt, waarna hypotheken en leningen niet meer in de vaste kosten opduiken.

Ontwerpregel

Vermogen is een balansbeeld: wat je bezit en wat je schuldig bent, op een peilmoment. Vaste kosten is een kasstroombeeld: wat er maandelijks afgaat. Een hypotheek hoort in béíde — hij heeft zowel een restschuld als een maandlast. Dat is géén dubbeling maar de juiste weergave: het zijn twee projecties van hetzelfde record, niet twee records. Er wordt niets verplaatst en niets weggehaald; er komt een beeld bíj.

Bevinding: dit werkt vandaag al — leningen voeden de kosten-hub

Nagekeken in plaats van aangenomen: costHubProvider leest de leningen-stream mee en zet elke lening om in een kostenpost via _loanItem (vaste_kosten_providers.dart:517), met amount: loan.monthlyAmount en frequency: CostFrequency.maand, onder de bron-id kFixedCostSourceLoans (cost_post.dart:11). Een hypotheek staat dus nu al in de vaste kosten, en er is niets in dit voorstel dat daar iets aan verandert. De gevreesde omissie bestaat niet.

Welk veld welk beeld voedt — dit is de tabel die zichtbaar maakt dat niets uit één context verdwijnt doordat het in een andere staat:

ComponentBalans (Vermogen)Kasstroom (Vaste kosten)Eigen module
Hypotheek / lening Ja — currentBalance
let op: via effectiveLoansProvider
Ja, al gebouwd — monthlyAmount Leningen & hypotheek
Spaardoel Ja — afgeleid saldo (savings_progress.dart:55) Ja, al gebouwd — monthlyContribution als impliciete post Spaardoelen
Pand Ja — currentValue Nog niet — komt met property_costs in V2 (TEN-118) Vastgoed
Abonnement Nee — geen balanswaarde Ja — amount + frequency Abonnementen
Verzekering Nee — geen afkoopwaarde in het model Ja — premie Verzekeringen
Bankrekening Nee — geen saldo-kolom (open vraag 5) Ja — monthlyFee als rekeningkosten-post Bankrekeningen
Auto Nee — alleen aankoopprijs (open vraag 4) Nee — wagenpark voedt de hub niet Wagenpark

Eén regel uit die tabel verdient nadruk, want hij is een concrete bouwinstructie: de twee beelden lezen verschillende velden van dezelfde lening. De kosten-hub gebruikt de ruwe lening, want het maandbedrag staat altijd op de parent — leningdelen kennen geen eigen maandbedrag (loan_calc.dart:58-67). Het balansbeeld moet juist effectiveLoansProvider (leningen_providers.dart:78) gebruiken, want bij een hypotheek met delen is de restschuld de som van de delen. Twee projecties, twee leesroutes, één record.

Het levende voorbeeld: de brug vaste kosten ↔ abonnementen

Dit patroon is geen theorie; het is gisteren gebouwd. TEN-125 (commit 3b34391c) maakt van een vaste kost met categorie Abonnementen automatisch óók een abonnement in de Abonnementen-module, met de markering "Vanuit vaste kosten — nog te verrijken". Uit de commitboodschap: "Geen source-veld op de abonnement-post en geen dedup-guard: de hub dedupliceert al op linkKey" en "op dezelfde post, er komt er nooit een tweede bij". Eén onderliggende post, twee plekken waar je hem ziet en verrijkt.

Kunnen leningen datzelfde mechanisme volgen? Ja, maar ze hebben het minder nodig — en het verschil is leerzaam. Die brug bestaat omdat een vaste kost die je zelf intikt nog géén onderliggend record heeft; er wordt er dan één gestubd. Bij leningen is de richting omgekeerd: de lening is er eerst en de kostenpost wordt eruit afgeleid. Dat werkt al zonder koppelrij, omdat de hub live uit de module-repo leest. Een koppelrij is pas nodig als je het bedrag in de hub wilt kunnen afwijken van de lening (het "geaccepteerde bedrag"-mechanisme). Voor de omgekeerde weg — een maandlast intikken in de hub en daar een lening van stubben — geldt hetzelfde bezwaar dat TEN-125 bij verzekeringen liet staan: je zou rente, looptijd en restschuld moeten verzinnen, en die voeden een einddatum-reminder. Voorstel: geen lening-stub vanuit de hub.

Waar de echte val zit

Dubbeltelling is geen risico tússen beelden — een hypotheek in zowel de balans als de kasstroom is correct. Het risico zit uitsluitend binnen één beeld: dezelfde schuld twee keer in de balans. Dat gebeurt op twee manieren, en beide worden hieronder afgedekt: dezelfde lening gekoppeld aan twee panden (open vraag 2), en een lening die deels aan objecten is toegerekend én nog eens volledig op huishoudniveau meetelt (open vraag 7).

Vier

De hoofdvraag, in drie varianten

De vraag die je nog niet hebt beantwoord: blijft vastgoed een eigen module die ónder Vermogen hangt, of wordt vastgoed een sectie binnen één Vermogen-module? Dat verschil raakt navigatie, module-registratie, per-module-instellingen, deelbeleid en tier-indeling. Hieronder eerst kort de nulmeting (wat er nu besloten staat), dan drie varianten.

Wat je moet weten over het modulesysteem om de varianten te kunnen wegen:

  • Er is precies één groeperingsniveau boven modules: DomainDescriptorModuleDescriptor.domainId, een platte string (module_descriptor.dart:74 en :113). Er is géén ModuleGroup, géén parentModuleId, géén nesting. (Wél een data-container-begrip: object → boekingen. Niet verwarren.)
  • Domeinen zijn vandaag de zichtbare groepering in Mijn leven: één sectiekop per domein met de modules eronder (mijn_leven_screen.dart:106-115 en :162-186), en collapsible groepen in Modules beheren (modules_beheer_screen.dart:212-257).
  • De route /domein/:id met een generiek DomainScreen bestáát al (app_router.dart:252, domain_screen.dart:17), maar niets in de app linkt ernaartoe. Er ligt dus een ongebruikte navigatielaag klaar. → Achterhaald, zie de herzieningsnotitie hieronder: die navigatielaag is verwijderd.
  • Een nieuw module-id kost: registry-regel, route, deelbeleid-regel, tier, en het krijgt automatisch een module-state-rij, member-prefs en een schermsnelkoppeling voor de favorietenbalk (module_quick_action_providers.dart:39-53).
  • Tier zit op de descriptor en is runtime overrijdbaar zonder release via module_tier_overrides (module_tier_overrides.dart:44). Plus-handhaving staat sinds de server-side entitlements aan (plus_entitlement.dart:31) — dat is nieuwer dan de pricing-doc suggereert.
  • Een domein met sensitive: true vereist een aparte AI-opt-in per domein-id (ai_gate.dart:36-38). Dat heeft een gevolg bij verhuizen; zie de kosten van variant A.
Herziening 6 augustus 2026 — een premisse onder variant A is vervallen

Er ligt géén ongebruikte navigatielaag klaar; /domein/:id bestaat niet meer

Dit stuk is geschreven op 26 juli 2026 en leunde op de vaststelling dat de route /domein/:id met een generiek DomainScreen al bestond maar nergens gelinkt werd — "een ongebruikte navigatielaag". Op 6 augustus 2026 is die route met F1-9 (E-MOD-06) verwijderd, merge 20f1b910. lib/features/shell/screens/domain_screen.dart bestaat niet meer (die map bevat nu alleen nog mijn_leven_screen.dart), de router-doc benoemt de uitfasering zelf (lib/core/router/app_router.dart:7-9) en test/core/router/domein_route_verwijderd_test.dart bewaakt dat de route niet terugkomt.

Wat dit met de afweging doet. De sub-keuze binnen variant A — "maak de domeinkop tikbaar naar het al bestaande /domein/vermogen" — was een goedkoop-lijkend alternatief juist omdat het scherm er al lag. Dat argument is weg: dat pad kost nu bouwen in plaats van aansluiten, en het zou een bewust genomen besluit terugdraaien. Die sub-keuze werd toen al afgeraden; ze is nu niet alleen onverstandig maar ook niet meer beschikbaar zonder de F1-9-beslissing terug te draaien. De hoofdafweging tussen de varianten A, B en C hangt hier niet van af — die gaat over module-id's, tier en deelbeleid, niet over dit scherm — maar wie variant A leest moet weten dat de goedkope route eronder is weggevallen.

Domeinen zelf blijven bestaan. Ze zijn nog steeds de vaste categorieën van de catalogus (onboarding, modulebeheer, meldingeninstellingen, AI-instellingen), en DomainDescriptor, ModuleDescriptor.domainId en modulesForDomain zijn ongewijzigd. Alleen het domeinscherm is weg. Alles wat dit stuk over domeinen als groepering zegt, geldt dus nog.

Let ook op de domeinhersnit. Sinds merge 0ea70a81 (6 augustus 2026) zijn er nog zes domeinen — eten · gezin · huis · geld · gezondheid · bewaren (lib/features/app_domains.dart:81-120) — en heet financieel voortaan geld. Waar dit stuk "Financieel" schrijft, leze men "Geld"; een nieuw domein vermogen zou bovendien botsen met de regel uit dat besluit dat een domein minstens twee modules draagt. Die weging is niet in dit stuk verwerkt en hoort bij de keuze meegenomen te worden.

Nulmeting · wat er nu besloten staat

Vermogen als tab in een bestaand scherm

Het besluit van ADR-0012: geen eigen module-id, geen registry-werk — één extra kaart of tab binnen Financieel, precies zoals Besteedbaar nu in het Vaste-kosten-scherm zit.

Waarom het er staat

Dit is geen stroman: het is het goedkoopste pad naar een kloppend getal en er is een levend precedent voor. calculateBesteedbaar (inkomsten/domain/besteedbaar.dart:6) is een pure cross-module-berekening die als kaart in het Vaste-kosten-scherm wordt getoond (vaste_kosten_screen.dart:104-107) — nul registratie, nul migratie, nul navigatiewijziging.

Wat je opgeeft

De overkoepeling zelf. Vermogen is dan een kaart in een scherm dat "Vaste kosten" heet — Sandra moet weten dat ze daar moet kijken. Het woord "Vermogen" komt in de navigatie nergens voor, en vastgoed staat er niet ónder maar ernaast. Dit is precies wat het besluit van 26 juli omdraait, dus het staat hier als vertrekpunt, niet als optie.

Variant A · aanbevolen

Vermogen wordt een domein; Vastgoed blijft een module daarbinnen

Een nieuw domein vermogen met daarin de bezittings- en schuld-modules, plus één nieuwe read-only module Vermogensoverzicht die over die modules aggregeert — het cost-hub-patroon, één niveau abstracter.

Wat het is
  • Nieuw DomainDescriptor(id: 'vermogen', name: 'Vermogen', sensitive: true).
  • domainId van Vastgoed, Leningen & hypotheek en Spaardoelen gaat van vastgoed/financieel naar vermogen. Eén woord per descriptor.
  • Nieuwe module vermogen-overzicht (naam "Vermogensoverzicht"), route /module/vermogen, read-only: geen eigen tabel, geen eigen rijen, alleen aggregatie.
  • Het oude domein vastgoed verdwijnt (het had toch maar één module).
  • Bankrekeningen en Wagenpark blijven in Financieel — ze hebben geen waardekolom en zijn vooral administratie/kasstroom.
Hoe het voelt voor Sandra
Mijn leven Populair Vandaag · Taken · Agenda · Boodschappen … Vermogen Vermogensoverzicht € 412.000 netto Vastgoed 3 objecten Leningen & hypotheek 2 leningen Spaardoelen 4 doelen Financieel Vaste kosten · Inkomsten · Verzekeringen · Abonnementen · Bankrekeningen · Wagenpark · Garanties

Ze tikt Mijn leven en ziet een kop Vermogen met vastgoed er letterlijk als één tegel ónder. Tikt ze Vermogensoverzicht, dan krijgt ze het totaal met bezittingen en schulden eronder, en per regel een doorklik naar de bronmodule ("Vakantiewoning € 285.000" → het bestaande vastgoed-objectdetail, met álle verhuurfuncties intact). Tikt ze Vastgoed, dan komt ze in exact het scherm dat er nu is — zelfde route, zelfde boekingen, zelfde bolletjes.

Wat het kost
  • Eén nieuw DomainDescriptor + drie domainId-regels wijzigen. Letterlijk vier regels code, geen migratie.
  • Eén nieuw module-id: registry-regel, route, tier. Een deelbeleid-regel is niet nodig — de module heeft geen eigen rijen, en onbekende module-ids resolven in de registry fail-closed naar privé/niet-configureerbaar (share_policy.dart:186-198). Het krijgt automatisch een module-state-rij en een schermsnelkoppeling.
  • De aggregatielaag: één pure domeinfunctie + één provider + één scherm + tests. Zie § Hergebruik — dit is het middelgrote deel, niet het grote.
  • De domein-volgorde in Mijn leven is impliciet de volgorde van featureModules (feature_modules.dart:71). Vermogen boven Financieel zetten betekent de lijstvolgorde aanpassen; dat raakt de volgorde van álle domeinsecties, dus even kijken of dat prettig blijft.
Wat je opgeeft
  • Financieel wordt kleiner. Leningen en Spaardoelen verhuizen eruit. Wie gewend is dat "alles met geld" onder Financieel staat, moet één keer opnieuw kijken. Tegenargument: de splitsing is inhoudelijk juist — Financieel = geld dat stroomt, Vermogen = geld dat staat. Maar het is wel een verandering in een navigatie die de tester al kent.
  • Een module kan maar in één domein zitten. Leningen hoort eerlijk gezegd in beide: de restschuld is vermogen, de maandlast is kasstroom en voedt de kosten-hub. Verhuizen naar Vermogen betekent dat de kosten-hub een bron heeft die in een ander domein woont. Technisch onschuldig (geen code hangt aan domainId voor de hub), conceptueel een wringpunt dat blijft.
  • De AI-opt-in per domein moet opnieuw gegeven worden. sensitiveOptInDomains is op domein-id gesleuteld (ai_gate.dart:36-38, ai_providers.dart:120). Wie "financieel" had aangezet, moet "vermogen" apart aanzetten voor bijvoorbeeld de leningblad-scan. Klein, maar het is een stille regressie als je er niet op rekent — bewust benoemen in de release-notitie voor de tester.
  • Eén tegel meer op het startscherm. Vier tegels onder Vermogen in plaats van drie regels in Financieel; dat is drukker, niet stiller.
Sub-keuze binnen A — vervallen, zie herziening 6 augustus 2026

Het overzicht kan óók zónder nieuw module-id: maak de domeinkop tikbaar naar het al bestaande /domein/vermogen en zet het totaal daar bovenaan. Dat scheelt een registry-regel. Ik raad het af: het introduceert een app-breed interactiepatroon (alle domeinkoppen worden dan tikbaar, of alleen deze — inconsistent), en een domeinscherm kan geen favoriet zijn, geen eigen tier hebben en geen eigen instellingen dragen. Een module is hier het goedkopere geheel.

Deze optie bestaat niet meer. Ze rustte op een scherm dat er al lag; sinds F1-9 (merge 20f1b910, 6 augustus 2026) is er geen /domein/:id en geen DomainScreen meer. Een tikbare domeinkop zou nu een nieuw scherm vergen én een net genomen besluit terugdraaien. De bezwaren die hierboven al golden — een app-breed interactiepatroon en een scherm dat geen favoriet, tier of instellingen kan dragen — staan er nog bovenop. Blijft dus: als variant A gekozen wordt, met een eigen module-id.

Variant B

Vermogen wordt één nieuwe module binnen Financieel

Geen domein-verhuizing. Eén nieuwe read-only aggregatie-module vermogen in het bestaande domein Financieel, naast de negen die er al staan. Vastgoed blijft ongemoeid in zijn eigen domein.

Wat het is

Exact de constructie die de Vaste-kosten-hub al is: een module die zelf nauwelijks data heeft en vooral over andere modules aggregeert (financieel_module.dart:55-63, costHubProvider in vaste_kosten_providers.dart:67). Één nieuw id, één route, één scherm. Nul verhuizingen.

Hoe het voelt voor Sandra
Mijn leven Financieel Vaste kosten · Inkomsten · Spaardoelen · Verzekeringen · Garanties · Abonnementen · Wagenpark · Leningen · Bankrekeningen · Vermogen Vastgoed Vastgoed 3 objecten

Ze tikt Mijn leven → Financieel → Vermogen en krijgt hetzelfde overzicht als in A, met dezelfde doorkliks. Functioneel is het overzicht identiek.

Wat het kost
  • Het minste van de drie: één module-id (registry, route, tier — geen deelbeleid-regel nodig) + dezelfde aggregatielaag als in A. Geen domein-wijziging, geen AI-opt-in-effect, geen navigatie-omzetting.
  • Volledig omkeerbaar en later te promoveren naar A: het domein toevoegen is dan nog steeds vier regels.
Wat je opgeeft
  • De overkoepeling. Vermogen is de tiende tegel in een rij van tien, en Vastgoed staat in een héél ander blok. Vastgoed is dan geen "onderdeel van Vermogen" maar een buur ervan met een streepje ertussen. Dat is niet wat de zin van 26 juli vraagt.
  • Het blijft onvindbaar op de plek waar Sandra zoekt. Ze begon haar vraag bij vastgoed ("bij vastgoed wil ik eigenlijk aangeven wanneer we het gekocht hebben … denk ook nog even mee zodat we een overzicht krijgen van wat ons vermogen is"). In B is er vanuit vastgoed geen zichtbaar pad naar dat overzicht, tenzij je er handmatig een link naartoe legt.
  • Financieel wordt tien tegels. Dat is al veel; dit maakt het meer. In A wordt het juist rustiger.
Variant C · afgeraden

Één Vermogen-module die Vastgoed opneemt als sectie

De letterlijke lezing van "één Vermogen-module": vastgoed is geen aparte module meer maar een sectie binnen Vermogen, naast secties voor schulden, spaargeld en beleggingen.

Wat het is — en waarom het serieus te nemen is

Eén plek, één mentaal model. Geen discussie meer over of een pand nu "vastgoed" of "vermogen" is. Voor een gebruiker die met vermogen begint in plaats van met verhuur, is dit de schoonste indeling: je voegt een bezitting toe, kiest het type, en als het type "pand" is verschijnen de verhuur-velden. Het voorkomt ook dat financiële velden zich over meerdere modules verspreiden. Als vastgoed nog niet gebouwd was, zou dit een verdedigbaar startpunt zijn.

Wat het kost — en hier valt het om
  • Het module-id vastgoed moet ophouden te bestaan of leeg blijven staan. Aan dat id hangen: de route, de agenda-item-decoratie (vastgoed_module.dart:49-67), de reminder-resync (feature_modules.dart:231), de deelbeleid-regel (share_policy.dart:120), de deel-remap met object→boekingen-cascade (share_bulk_remapper.dart:120), de deel-hint (shared_in_disabled_module_watcher.dart:45) en kSelfSurfacedModules.
  • En de opgeslagen instellingen. Module aan/uit per lid, meldingstermijn, meldtijd, geluid en verbergen-voor-leden staan op de rij <householdId>:vastgoed in ModuleStates (foundation_tables.dart:185) en in MemberModulePrefs (:236), plus de favorieten in de _shell-prefs (module_quick_action_providers.dart:118). Dat is een data-remap, geen additieve migratie — en die valt niet onder het staande akkoord voor additieve kolom-migraties.
  • De check-in/check-out-meldingen zouden hun eigen meldingstermijn verliezen: één Vermogen-module heeft één meldingsinstelling, en die moet dan zowel "1 dag voor check-in" als "opzegtermijn-melding" als eventuele waarderings-herinneringen dekken.
Wat je opgeeft

Precies de randvoorwaarde. Vastgoed houdt in deze variant geen eigen schermen, eigen meldingsinstellingen en eigen deelbeleid — het krijgt een sectie in een groter scherm. Om álle veertien punten van § Wat behouden blijft te bewaren, zou je in feite een module-in-een-module moeten bouwen: een sub-registratie-mechanisme dat vandaag niet bestaat (er is geen ModuleGroup, geen parentModuleId, geen nesting — nagekeken in de hele lib/). Dat mechanisme bouwen is een raamwerk-project bovenop het Vermogen-project, met een data-remap in het midden en vier testtoestellen die er doorheen moeten.

Conclusie: C levert een net iets schoner datamodel op tegen de prijs van een nieuw raamwerk en een remap van instellingen, en het schendt de randvoorwaarde tenzij je dat raamwerk eerst bouwt. Afgeraden — niet omdat het idee slecht is, maar omdat het te laat komt.

Naast elkaar

Nulmeting (ADR-0012)A · domeinB · module in FinancieelC · opgeslokt
"Vastgoed is 1 onderdeel van Vermogen" zichtbaarNeeJaNeeJa
Vastgoed verliest nietsJaJaJaAlleen mét nieuw raamwerk
Nieuw module-idGeen111, en 1 vervalt
Data-remap nodigNeeNeeNeeJa (module-states, prefs, favorieten)
Migratie nodig voor het eerste getalNeeNeeNeeJa
Navigatie verandert voor de testerNietJa, 3 modules verhuizenNauwelijksIngrijpend
Later te promoverenNaar B of AEindvormNaar A
Grofweg werkKleinMiddelMiddel-kleinGroot
Vijf

Aanbeveling

Advies

Variant A — Vermogen als domein, met één read-only Vermogensoverzicht-module. Vastgoed blijft een volwaardige module daarbinnen.

En bouw het in de fasering van § Zes: eerst het getal (zonder enige migratie), dan de overkoepeling.

Waarom A en niet C. De vraag "module ónder Vermogen of sectie ín Vermogen" wordt niet beslecht door welke indeling het mooiste datamodel geeft, maar door wat er al gebouwd is. Aan het module-id vastgoed hangen zes registraties in code én drie soorten opgeslagen voorkeuren. Vastgoed heeft daarnaast dingen die geen andere bezitting heeft en ook nooit zal hebben: een boekingsmodel met gasten en periodes, overlap-detectie, twee verhuurmodi die de agenda-weergave bepalen, wisseldag-glyphs, een opzegtermijn-melding en een objectkleur die als avatar in de agenda meeloopt. Dat is geen "bezitting met een waarde" — dat is een mini-PMS. Een abstractielaag die daar een sectie van maakt, moet al die eigenschappen terugbouwen als uitzonderingen binnen de generieke laag. Dat is meer werk dan de laag zelf.

Waarom A en niet B. B is goedkoper en volstrekt verdedigbaar, en als het besluit "geef ons een vermogensoverzicht" was geweest, zou ik B adviseren. Maar het besluit is "Vermogen wordt de overkoepeling, vastgoed is er één onderdeel van", en in B is Vermogen de tiende tegel naast Vastgoed in plaats van erboven. Het duurdere deel van A is niet het domein (vier regels) maar de aggregatielaag — en die heb je in B óók nodig. De extra kosten van A boven B zijn dus klein: een domein-regel, drie verhuizingen, en één keer uitleggen aan de tester dat Leningen en Spaardoelen verhuisd zijn.

Waarom het cost-hub-patroon dit goedkoop maakt. Zie de volgende sectie: een vermogens-aggregatie is niet alleen hetzelfde patroon als de kosten-hub, hij is een eenvoudiger geval — de drie zwaarste onderdelen van de kosten-hub vallen weg. Dat is het verschil tussen een groot en een middelgroot project.

Wat A expliciet niet oplost. Leningen hoort inhoudelijk in twee domeinen en kan er maar in één; de AI-opt-in moet één keer opnieuw voor het nieuwe domein-id; en de tester ziet haar navigatie veranderen. Dat zijn de prijzen. Ik vind ze goedkoop tegenover het alternatief, maar ze zijn echt.

Zes

Hergebruik: kan de vermogens-aggregatie het cost-hub-patroon volgen?

Ja — en het is een eenvoudiger geval. Dat is de belangrijkste bevinding voor de omvangsschatting.

De kosten-hub bestaat uit een pure bouwfunctie buildCostHub (vaste_kosten/domain/cost_hub.dart:71) die genormaliseerde CostModuleItem-objecten uit meerdere modules samenbrengt (domain/cost_post.dart:58), dedupliceert op de sleutel '<moduleId>|<sourceRef>' (cost_post.dart:322) en via één provider aan één scherm wordt gehangen (vaste_kosten_providers.dart:67). Die vorm is één-op-één over te zetten:

Kosten-hub (bestaat)Vermogen (nieuw)Verschil
CostModuleItemWealthItem+ kind (bezitting/schuld), + asOf peildatum, + financedByRef
buildCostHub(...) pure functiebuildVermogen(...) pure functieZelfde vorm; sommeert saldi i.p.v. te normaliseren op frequentie
Dedupe op <moduleId>|<sourceRef>Idem, plus dedupe per lening-idVereist door ADR-0014: één hypotheek aan twee objecten telt één keer
costHubProvider leest 6 module-streamsvermogenProvider leest 3 (panden, leningen, spaardoelen)Minder bronnen
Koppel-rijen in fixed_costs met geaccepteerd bedragVervaltEr is niets te "accepteren" — geen tabel, dus geen migratie
Afwijking-signalering → Vandaag-suggestiesVervaltEen waarde die verandert is geen afwijking maar een feit
Wees-koppelrijen (bron verwijderd)VervaltZonder koppel-rijen bestaan er geen wezen
Onvolledigheid als eerste-klas uitkomstFinancingUnavailable bestaat al (property_return_calc.dart:203)

Wat dat betekent voor de omvang. De drie duurste onderdelen van de kosten-hub (koppel-rijen met een eigen tabel en accept-flow, afwijking-signalering met een pijplijn naar Vandaag, en wees-beheer) zijn hier niet nodig. Wat overblijft is: één pure domeinfunctie met tests, één provider, één scherm, en een doorklik per regel naar de bronmodule. Voor de eerste versie: nul migraties, nul schemaVersion-bump, nul sync-config-wijziging — en dus ook geen 4-weg lockstep en geen prod-deploy-afhankelijkheid. Dat is de reden dat dit middelgroot is en niet groot.

Één ontwerpkeuze in de aggregatie: hard gedraad of via de registry

De kosten-hub draadt zijn bronnen hard: zes ref.watch-regels en een mapper per bron in de provider (vaste_kosten_providers.dart:68-115), plus drie hardgecodeerde switches in het scherm voor icoon, label en route (vaste_kosten_screen.dart:727/736/748). Er is géén uitbreidingspunt. Het alternatief bestaat óók al in deze app: FeatureModule.todayRowSources (module_descriptor.dart:184, geplat in module_providers.dart:42) laat modules zichzelf aanmelden.

Voorstel: begin hard gedraad — met drie bronnen is een registry-hook voorbarig — maar los meteen één bekend gebrek van de kosten-hub op: daar valt de hele hub in laden of fout zodra één bron dat doet (vaste_kosten_providers.dart:75-83). Voor een vermogensoverzicht is dat onaanvaardbaar: dan is het totaal leeg omdat de spaardoelen nog laden. Degradeer per bron ("spaargeld kon niet geladen worden") en toon de rest. Zodra beleggingen erbij komen (fase V3) is dat het natuurlijke moment om naar de registry-hook te gaan.

Zeven

Open vragen — dit moet jij beslissen

Dit zijn geen implementatiedetails maar productkeuzes met een verkeerd antwoord. Ik doe per vraag een voorstel, maar ik ga het niet zelf beslissen.

Open vraag 1

Hoe wordt de actuele waarde van een pand bepaald?

Vandaag is properties.current_value een handmatig veld (vastgoed_tables.dart:36) zonder peildatum en zonder geschiedenis. Drie routes: (a) handmatig bijhouden, (b) afleiden uit aankoopprijs × een indexatie, of (c) een jaaropgave/WOZ-beschikking scannen met de bestaande AI-extractie-stack (ontworpen in ADR-0015). Er zit géén WOZ-veld en géén enkele externe waardebron in de app — geverifieerd: het woord komt in het hele schema niet voor.

Mijn voorstel: (a) nu, met twee toevoegingen die het eerlijk maken — een peildatum naast de waarde ("€ 285.000, door jou ingevuld op 1 jan 2026") en een zachte hint als die datum oud is. (c) in een latere fase. (b) nooit: een verzonnen indexatie ziet eruit als een meting en is dat niet; een verouderd cijfer dat je zelf hebt ingevuld is minder schadelijk dan een verzonnen cijfer dat actueel lijkt.

Wat ik van jou nodig heb: is een handmatige waarde met peildatum genoeg voor v1, en na hoeveel maanden wil je erop gewezen worden dat hij oud is (voorstel: 12)?
Open vraag 2

Telt een financieringslening dubbel tegen de waarde van het object dat hij financiert?

Dit is het echte dubbeltelling-risico. Een pand van € 285.000 met een hypotheek van € 180.000 is € 105.000 vermogen. Als de hypotheek óók nog los in de schuldenlijst staat, wordt het € 285.000 − € 180.000 − € 180.000. De koppeling die dit moet voorkomen is properties.financing_loan_id (vastgoed_tables.dart:42) — een zwakke, éénzijdige, 1-op-1 referentie zonder FK. De lening weet niet welk object hij financiert.

Mijn voorstel: één schuldenlijst met deduplicatie op lening-id. Elke lening telt precies één keer in het totaal. Bij een pand met gekoppelde lening tonen we het netto per object ("waarde € 285.000 − restschuld € 180.000 = € 105.000") en die lening verschijnt niet nóg eens los. Leningen zonder gekoppeld object (persoonlijke lening, autolening) staan wél los. Bij een niet-resolvebare koppeling: "onvolledig", nooit stil nul — die uitkomst bestaat al (FinancingUnavailable).

Preciezer geformuleerd na open vraag 7: de dedupe zit niet in de object-weergave maar in het totaal. De balans telt elke lening precies één keer op huishoudniveau (dedupe op lening-id, ná effectiveLoans); wat een object over "zijn" financiering toont, is presentatie en wordt nooit gesommeerd. Daarmee kan een lening niet zowel los als via een object meetellen.

Wat ik van jou nodig heb: één grensgeval blijft over. Een lening die hangt aan een object dat de kijker niet mag zien: meetellen of niet? Voorstel: niet meetellen en het expliciet zeggen, want de per-kijker-doorsnede is al beleid (ADR-0016). Het geval "één hypotheek op twee objecten" is verhuisd naar open vraag 7, waar het nu volledig is uitgewerkt.
Open vraag 3

Wat is "vermogen" — bruto bezittingen of netto na schulden?

Beide zijn verdedigbaar en ze verschillen bij dit huishouden vermoedelijk met een factor. Bruto voelt groter; netto is wat je werkelijk hebt.

Mijn voorstel: netto is het hoofdgetal — "Ons vermogen: € 412.000" — met direct eronder twee subregels: "Bezittingen € 592.000" en "Schulden € 180.000". Nooit alleen bruto: een vermogensoverzicht dat de hypotheek weglaat is niet optimistisch maar onjuist. En nooit alleen netto zonder de opbouw, want dan is het getal niet na te rekenen.

Wat ik van jou nodig heb: is netto bovenaan het getal dat je wilt zien, of wil je bruto groot en netto klein?
Open vraag 4

Doen auto's mee?

vehicles heeft alléén purchase_price (wagenpark_tables.dart:31) en geen actuele waarde. Een auto van acht jaar oud staat dus voor zijn nieuwprijs in de boeken — dat maakt het totaal aantoonbaar te hoog.

Mijn voorstel: niet in de eerste versies. Aankoopprijs is geen vermogen. Als je auto's erin wilt, is de eerlijke weg een current_value-kolom op vehicles naar hetzelfde model als bij panden (additief, nullable) — een klein vervolg, niet iets voor v1.

Wat ik van jou nodig heb: wil je auto's erin, en zo ja, ben je bereid hun actuele waarde net als bij een pand zelf bij te houden?
Open vraag 5

Doet bankrekening-saldo mee?

bank_accounts heeft bewust geen saldo-kolom (bankrekeningen_tables.dart:14 — het bestand legt in zijn kop uit dat er alleen de laatste 4 IBAN-cijfers worden opgeslagen). Zonder saldo is liquide geld alleen indirect zichtbaar via het spaardoelen-ledger.

Mijn voorstel: niet toevoegen. Een handmatig bijgehouden betaalrekeningsaldo is de dag erna verouderd, en het is de meest privacy-gevoelige waarde in de app zonder dat er een bankkoppeling tegenover staat. Spaargeld via het spaardoelen-ledger is de betere benadering, want dat wordt toch al per bijdrage bijgehouden.

Wat ik van jou nodig heb: akkoord dat "liquide middelen" in v1 gelijkstaat aan het spaardoelen-totaal, of wil je een handmatig veld "overig spaargeld"?
Open vraag 6

Eén gezinsvermogen of per persoon?

Vastgoed en financieel zijn default privé per lid. ADR-0016 legde vast dat cijfers per kijker verschillen: ieder rekent over de eigen zichtbare objecten en leningen. Dat blijft inhoudelijk juist, maar bij een scherm dat "Ons vermogen" heet is het verwarrend als jij en Sandra een ander getal zien.

Mijn voorstel: per kijker rekenen (ongewijzigd), met de bestaande voetnoot "op basis van wat jij kunt zien", plus een stille deel-hint bij financiële velden: "deel dit object om samen één vermogensbeeld te zien". Géén gezinsbrede wijziging van de privé-default — dat is een aparte privacy-beslissing.

Wat ik van jou nodig heb: bevestiging dat dat zo blijft, of de wens dat vermogens-data standaard met de partner gedeeld wordt.
Open vraag 7 · nieuw onderzocht

Een hypotheek die op meerdere objecten zit

Gangbaar scenario: één hypothecaire inschrijving op twee panden, of een hypotheek op de eigen woning die deels een tweede object financiert. Wat het model nu aankan: properties.financing_loan_id is een enkele verwijzing van pand naar lening (vastgoed_tables.dart:42), dus twee panden kunnen naar dezelfde lening wijzen — maar er is geen verdeling. Elk pand ziet dan de volledige lening, en de rentemomentopname toont bij beide objecten hetzelfde volledige bedrag (bewust zo, en in fase 1 nergens opgeteld).

Is loan_parts de natuurlijke plek? Onderzocht: nee. Die tabel (leningen_table.dart:54, TEN-91 deel b) modelleert een hypotheek die uit rente-/looptijddelen bestaat — annuïtair naast aflossingsvrij — met per deel een eigen rente, looptijd en einddatum. Er staat geen propertyId op (nagekeken: geen enkele treffer in de hele leningen-map) en géén maandbedrag. De assen zijn orthogonaal: één deel kan over twee objecten lopen en twee delen over één object. Toerekening erin proppen zou de derivatie in loan_calc.dart besmetten, waar de lening-totalen uit de delen worden berekend. Wél relevant: als toerekening én delen samen bestaan, moet de toerekening tegen de effectieve restschuld gevalideerd worden, niet tegen het parent-veld.

Let op — een onvervulde belofte: ADR-0013 zei dat een koppeltabel pand↔lening "bij TEN-91-deel-b" zou komen. Wat er als TEN-91 deel b is gebouwd, is loan_parts — iets anders. Die koppeltabel bestaat dus nog niet, en dat is precies wat hier op tafel ligt.

Vier varianten:

VariantWat het isWat het kostWat je opgeeft
a · Geen toerekening
huishoudniveau
De lening staat als schuldpost in de balans, niet aan een object gehangen. Netto vermogen blijft correct. Nul — dit kan vandaag al door financing_loan_id leeg te laten Overwaarde of rendement per object wordt onmogelijk. En het objectdetail zegt "geen financiering" terwijl er wél een hypotheek is — misleidend tenzij je het uitlegt.
b · Vaste bedragen Koppeltabel property_loan_allocations(property_id, loan_id, amount). "€ 120.000 van de € 300.000 zit op de vakantiewoning." 1 tabel + migratie + 4-weg lockstep + invoer-UI + somvalidatie Loopt scheef bij elke aflossing: de restschuld daalt, het toegerekende bedrag niet. Vraagt onderhoud door de gebruiker — precies het soort veld dat verouderd raakt zonder dat iemand het merkt.
c · Percentages
aanbevolen
Zelfde koppeltabel, maar met share_pct. Het bedrag per object = percentage × actuele restschuld. Identiek aan b Bij invoer iets minder intuïtief ("hoeveel procent?" i.p.v. een bedrag), en afrondingsresten in de weergave. Maar blijft automatisch kloppen bij aflossing.
d · Via loan_parts Een deel per object. Lijkt gratis Verkeerde as (zie hierboven), besmet de totaal-derivatie, en delen kennen geen maandbedrag. Afgeraden.
Mijn voorstel: (a) als default, (c) als optionele verfijning — en toerekening telt nóóit mee in het totaal.

Dat laatste is de kern, en het maakt dubbeltelling structureel onmogelijk in plaats van afhankelijk van een controle: de balans telt elke lening precies één keer op huishoudniveau (dedupe op lening-id, ná effectiveLoans). Toerekening is uitsluitend een presentatielaag voor "overwaarde van dít pand" en wordt nergens gesommeerd. Zo kan een gedeeltelijk toegerekende lening niet één keer volledig én één keer per object meetellen — die som wordt simpelweg nooit gemaakt.

In V0 is dat (a), gratis en correct. (c) hoort bij V2/V3, wanneer overwaarde per object werkelijk gevraagd wordt.

Wat ik van jou nodig heb — drie grensgevallen:
(i) Telt de verdeling niet op tot de restschuld: afdwingen of tolereren? Voorstel: tolereren met waarschuwing, want afdwingen blokkeert je midden in het invullen en de restschuld verandert buiten de app om. Met percentages: som boven 100% is een echte fout (blokkeren), som onder 100% is legitiem — de rest is niet toegerekend en dat toont het scherm ("niet toegerekend: € 45.000").
(ii) Object verkocht, lening blijft? Voorstel: de toerekening vervalt mét het object (dezelfde cascade als object→boekingen), de lening blijft volledig in de balans en het niet-toegerekende deel groeit. Nooit stil de schuld verlagen omdat een pand verdwijnt.
(iii) Wil je dit überhaupt per object kunnen zien, of is één correct netto-vermogen op huishoudniveau genoeg? Als (a) volstaat, valt een tabel, een migratie en een invoerscherm weg.
Acht

Kunnen boekingen automatisch binnenkomen?

Onderzoeksvraag van de eigenaar: kunnen we de vastgoed-boekingen eenvoudig aan Uplisting, Airbnb of Booking hangen, zodat boekingen gesynchroniseerd binnenkomen in plaats van dat ze continu handmatig ingevuld moeten worden? Terechte wens: bij kortverhuur verandert de boekingslijst wekelijks, en gast, periode en status worden nu alle drie met de hand ingevoerd.

Correctie · 2026-07-26

Een eerdere versie van deze sectie raadde Uplisting af wegens "partnerovereenkomst met revenue share". Dat was onjuist en is hieronder herschreven. De fout: ik haalde twee verschillende dingen door elkaar — integratiepartner van Uplisting worden (om een koppeling in hun marktplaats te distribueren) versus Uplisting-klant die zijn eigen API-key gebruikt. Voor het tweede is géén goedkeuring, géén partnerovereenkomst en géén revenue share nodig. De eigenaar is klant en heeft de key al. De vraag is dus niet óf het kan, maar hoe we het inrichten.

Het onderscheid dat de fout veroorzaakte — en dat het ontwerp bepaalt

RouteWat je nodig hebtGoedkeuring?Voor ons
Klant met eigen API-key
V1-lees-endpoints + webhooks
De key die de klant zelf aanmaakt in Uplisting onder Connect → API. HTTP Basic met de base64-gecodeerde key. Geen Dit is onze route
Integratiepartner / marktplaats
V2-schrijf-endpoints, V3 OAuth2
Een X-Uplisting-Client-Id respectievelijk OAuth-client_id, aan te vragen via partner@uplisting.io; daarbij hoort de partnerovereenkomst (met revenue-share-regeling). Ja Niet nodig — wij hoeven niets te schrijven en niets te distribueren via hun marktplaats

Wij hoeven alleen te lezen: boekingen ophalen en wijzigingen ontvangen. Dat zit volledig in de eerste rij. Het schrijven van boekingen terug naar Uplisting — het enige waarvoor de partnerroute nodig is — is voor Tendlo geen wens: de bron van waarheid voor een verhuurboeking is de channel manager, niet onze app.

Uplisting is de voorkeursroute

Op elk punt beter dan de iCal-route die hieronder als terugval blijft staan:

  • Eén koppeling dekt Airbnb én Booking.com. Uplisting is de channel manager die die kanalen al bundelt; de boeking draagt een channel-veld met waarden als airbnb_official en booking_dot_com. We hoeven dus niet per platform iets te bouwen — en we hoeven al helemaal niet langs de gesloten deuren van Airbnb en Booking.com (zie hieronder).
  • Het is de eigen data van de gebruiker, geautoriseerd met zijn eigen key, in plaats van een publieke geheime kalender-URL. Dat is zowel functioneel als privacy-technisch een betere basis.
  • Webhooks in plaats van pollen. booking_created, booking_updated en booking_removed — dus ook een expliciet signaal bij annulering. Precies wat iCal structureel niet kan (daar is een annulering een stille verdwijning).
  • Annuleringen zijn opvraagbaar, niet alleen als gebeurtenis: de boekingenlijst per pand geeft ook geannuleerde boekingen terug, dus een herstel-na-downtime kan de werkelijkheid opnieuw vaststellen zonder te gokken.
Wat we nog niet weten

Of gastnaam en bedrag daadwerkelijk meekomen — en hoe we dat vaststellen

De publieke Postman-collectie van Uplisting documenteert een boekings-payload met onder meer guest_name, guest_email, guest_phone, check_in, check_out, channel, external_reservation_id, status, total_payout en cleaning_fee. Dat is precies waar iCal tekortschiet, en het zou de belofte "bezette dagen, vul zelf aan" veranderen in échte boekingssync.

Maar dit is niet geverifieerd tegen een live account, en de support-pagina over de API somt de velden niet op. Dus: gedocumenteerd ja, bevestigd nee. Ik heb geen boeking van de eigenaar opgehaald en ga dat ook niet op eigen initiatief doen.

Concrete vervolgstap voor jou (klein): mail partner@uplisting.io met het verzoek om de volledige API-documentatie — dat is wat hun support-pagina zelf als route aangeeft. Je hebt de key al, dus daarnaast kan één enkele leesaanroep op je eigen account (GET /bookings/<listing_id>) in vijf minuten uitwijzen wélke velden er echt in staan. Zeg maar of je wilt dat ik dat samen met jou doe; ik doe het niet ongevraagd, want het gaat om je eigen sleutel en echte gastgegevens.

Airbnb en Booking.com direct: dicht

Niet nastreven

Direct koppelen kan niet — niet "moeilijk", maar structureel niet beschikbaar. Airbnb neemt geen aanvragen aan: het partnerportaal heeft alleen een inlog, en Airbnb selecteert zelf wie het benadert, op basis van hoeveel woningen je meebrengt. Booking.com heeft nieuwe connectiviteits-partners gepauzeerd ("we are pausing integrations with new connectivity providers until further notice") én zegt expliciet dat ze geen directe koppelingen van individuele woningen accepteren. Dat is geen probleem meer zodra we via Uplisting gaan: die koppeling levert de Airbnb- en Booking-boekingen juist wél.

iCal als terugval — voor wie geen Uplisting heeft

Uplisting is de beste route voor deze eigenaar, maar het vereist wel dat de gebruiker Uplisting-klant is (abonnement vanaf ongeveer £72 per maand). Voor de eigenaar is dat al zo; voor een willekeurige Tendlo-gebruiker met één vakantiewoning niet. Daarom blijft iCal in het ontwerp staan als tweede implementatie — en als vangnet mocht de Uplisting-API onvoldoende blijken. Wat erin zit verschilt sterk per platform, en dat bepaalt de belofte die je erbij zet:

FeedGastnaamBedragReserverings­nummerTijden
AirbnbNeeNeeNee (alleen een link)Nee — hele dagen
Booking.comNeeNeeNeeNee
VrboMeestal welNeeNeeNee
Uplisting / Beds24JaVia API, niet in de feedVia APIJa, met tijdzone

Airbnb heeft zijn feed op 1 december 2019 bewust uitgekleed om privacyredenen: de titel is sindsdien letterlijk SUMMARY:Reserved en de omschrijving bevat alleen een link naar de reservering plus de laatste vier cijfers van het telefoonnummer. Booking.com geeft alleen datums met een statuslabel. Concreet betekent dat:

De eerlijke belofte — alleen bij de iCal-route

Bij een iCal-koppeling met Airbnb of Booking.com is de belofte niet "je boekingen komen automatisch binnen", maar: "je bezette dagen komen automatisch binnen — tik een boeking aan om de gast en het bedrag toe te voegen." Dat verandert het werk van alles typen naar bevestigen en aanvullen. Bij de Uplisting-route valt die beperking vermoedelijk weg en is het wél echte boekingssync — mits de velden uit de open vraag hierboven bevestigd zijn. Twee routes, twee verschillende beloftes: het scherm moet zeggen welke van de twee actief is.

Wat de app al heeft — geldt voor de iCal-route

Dit is de belangrijkste bevinding van de hele sectie: Tendlo importeert al iCal-feeds, in productie, op twee plekken.

  • Een volwaardige, tolerante iCal-parserlib/features/gezin/sportkalender/data/ical_parser.dart (148 regels), gebouwd op het pakket icalendar_parser (pubspec.yaml:63). Die heeft de klassieke valkuilen al opgelost: UID-stabiliteit met een synthetische terugval als de feed geen UID geeft (:26-29), VALUE=DATE versus DATE-TIME (:40), UTC en TZID omgerekend naar toesteltijd met een terugval voor onbekende zones zoals Outlook's "W. Europe Standard Time" (:33-38), en een FormatException als de URL een HTML-foutpagina teruggeeft in plaats van een kalender (:4-5).
  • Een werkend import-patroon met herkomstsport_events.externalUid is nullable met de dartdoc "UID of the source VEVENT when this event was imported; null for hand-entered events" (sportkalender_tables.dart:81-84). De import doet zoek-op-uid-dan-bijwerken-anders-invoegen (drift_sportkalender_repository.dart:304-334), aangestuurd door importIcalForTeam (sportkalender_providers.dart:241) met een overrijdbare HTTP-client zodat het testbaar is (:48).
  • Een tweede, onafhankelijke .ics-import voor de afvalkalender, mét bestand- én URL-invoer (afvalkalender/data/ics_afval_importer.dart).
  • En de boekingssemantiek klopt al toevallig precies. Boekingen worden in deze app behandeld als half-open [start, end) met einde = check-out-dag, expliciet zodat rug-aan-rug-boekingen niet als overlap gelden (domain/booking_overlap.dart:8-10). Dat is exact de conventie die verhuurplatform-feeds gebruiken voor hun DTEND. De beruchte off-by-one op de vertrekdag is hier dus geen conversie maar een gelijkheid.

Wat dat betekent: een iCal-import voor boekingen is in deze app géén nieuw traject. Het is een derde toepassing van een patroon dat al twee keer draait en getest is.

Let op bij hergebruik

Eén eigenschap van de bestaande parser is hier juist gevaarlijk

De parser verzint een synthetische UID uit summary + start + eind + locatie als de feed er geen geeft (ical_parser.dart:26-29). Voor sportwedstrijden is dat precies goed. Voor verhuurfeeds is het een dubbel-risico, want de Airbnb-feed reikt maar 365 dagen vooruit: een boeking die verder in de toekomst eindigt, wordt gerapporteerd met een einddatum van exact vandaag + 365 — die schuift dus elke dag een dag op tot de echte einddatum in beeld komt. Een identiteit waar de einddatum in zit, levert dan elke dag een "nieuwe" boeking op.

Regel: match op UID, met als terugval (object, startdatum) — en nooit de einddatum in de identiteit. Airbnb geeft wel UID's, dus de terugval is zeldzaam, maar hij moet goed staan. Diezelfde 365-dagendrift betekent ook dat een ver vooruit geboekte periode tijdelijk een verkeerde einddatum toont; dat is een reden om bij geïmporteerde boekingen de einddatum als "volgens de kalender" te labelen in plaats van als vaststaand feit. Deze regel geldt voor béíde routes: ook bij Uplisting is de identiteit het reserveringsnummer of de bron-id, nooit iets waar een datum in zit die kan schuiven.

Waar de sync draait — en waarom webhooks dat beslissen

Bij de Uplisting-route is dit geen afweging meer maar een gevolg: een webhook heeft een publiek HTTPS-eindpunt nodig. Dat kan een telefoon niet zijn. De sync hoort dus server-side, en het patroon staat er al: supabase/functions/revenuecat-webhook is precies dit — een edge function die een webhook van een externe partij ontvangt. Er staan zes edge functions in deze repo.

De rate limits maken pollen bovendien onaantrekkelijk: 5 verzoeken per seconde en 100 per minuut per IP, en maximaal 15 per minuut per pand. Eén server die pollt is te doen; vier gezinstoestellen die elk pollen, tikt met meerdere panden snel tegen die grens aan — en de API-key zou dan op elk toestel moeten staan. Bij de iCal-terugval geldt hetzelfde argument: Airbnb beperkt handmatig verversen expliciet ("we're only able to request updates from the other website so many times") en ververst zelf maar elke ~3 uur.

Voorstel

Server-side, webhook-eerst, met pollen als herstelmechanisme. Eén edge function ontvangt de Uplisting-webhooks (naar het model van revenuecat-webhook) en schrijft de boekingen weg; daarnaast één periodieke volledige ophaling per pand als reconciliatie, want webhooks worden minstens-één-keer bezorgd en een endpoint dat herhaaldelijk niet antwoordt wordt door Uplisting uitgeschakeld — dan mag je niet stil uit de lucht zijn. Webhook-payloads dragen een tijdstempel, dus buiten-orde-bezorging is af te vangen door oudere gebeurtenissen te negeren.

Twee dingen die dit server-side maken en aandacht vragen: de API-key hoort niet op de toestellen maar in de serveromgeving (bij iCal geldt dat net zo voor de geheime feed-URL), en de boekingsgegevens — inclusief gastnamen — passeren daarmee de backend, wat de privacyparagraaf hieronder zwaarder maakt dan bij een puur client-side import.

Kanttekening bij de planner: edge functions bestaan hier, maar ik heb in dit project géén pg_cron aangetroffen in de migraties. Het patroon draait wél in de feedback-pijplijn (ander Supabase-project), dus het is bekend terrein — maar in pa_app is de periodieke trigger nieuw werk, geen bestaande voorziening.

Herkomst en conflicten — de regel die dit veilig maakt

Zonder herkomst-veld overschrijft een import het handwerk van de gebruiker, of ontstaan er dubbele boekingen. bookings heeft nu géén herkomst-veld (vastgoed_tables.dart:80-106: object, gast, start, eind, status, bedrag). Dat is een additieve kolom, naar het model dat al bestaat.

  • external_uid (nullable) — leeg = met de hand ingevoerd; gevuld = uit een externe bron. Exact zoals sport_events.externalUid. Omdat er nu twee bronnen in beeld zijn, hoort er een external_source naast (uplisting / ical): dezelfde boeking via twee routes moet niet twee rijen worden, en het scherm moet kunnen zeggen waar een boeking vandaan komt.
  • Wie wint: een import mag alléén rijen aanraken met een external_uid dat hij zelf herkent. Handmatige boekingen worden nooit door een import gewijzigd of verwijderd. Velden die de gebruiker zelf heeft aangevuld op een geïmporteerde rij blijven staan; de import werkt alleen bij wat hij zelf geleverd heeft. Bij Uplisting kantelt dat deels: als gastnaam en bedrag uit de bron komen, is de bron daarvoor gezaghebbend en is de vraag juist omgekeerd — mag de gebruiker ze lokaal overschrijven, en verliest hij die aanpassing dan bij de volgende webhook? Voorstel: een handmatige wijziging op een geïmporteerd veld plakt (wordt niet overschreven) en het scherm laat zien dat het van de bron afwijkt.
  • Verdwijnen is het lastige geval — bij iCal. Bij Uplisting is dit juist opgelost: er is een expliciete booking_removed-gebeurtenis en geannuleerde boekingen zijn opvraagbaar. Dat is een van de sterkste argumenten voor die route. Bij iCal geldt: een annulering levert géén tombstone: er is geen STATUS:CANCELLED en geen METHOD:CANCEL, de gebeurtenis houdt simpelweg op te bestaan. Het gevolg: een afgekapte of half-opgehaalde feed ziet er identiek uit aan "alles is geannuleerd". Voorstel: een verdwenen geïmporteerde boeking wordt gemarkeerd als "niet meer in de kalender" en de gebruiker beslist — precies zoals de kosten-hub een wees-koppelrij zichtbaar houdt in plaats van hem stil te verwijderen. En als er later automatisch geannuleerd wordt, alleen achter een sanity-guard: de feed moet schoon geparseerd zijn én netjes eindigen op END:VCALENDAR, en een feed die van veel naar bijna niets gaat, is een fout tot de gebruiker hem bevestigt.
  • Voorlopige boekingen. Airbnb blokkeert de kalender óók voor aanvragen die nog op acceptatie wachten, en er komen "spook"-blokkades voorbij van afgebroken afrekeningen. Uplisting kent een filter hiervoor, de platforms zelf niet. Reken erop dat een klein deel van de geïmporteerde blokkades geen echte boeking is — nog een reden om automatisch verwijderen niet als eerste te bouwen.
  • Overlap: de bestaande overlap-detectie blijft gelden. Een geïmporteerde boeking die overlapt met een handmatige is een signaal aan de gebruiker, geen fout die de import zelf oplost.
  • Eén bron per object. Bij iCal: één feed-URL per object (twee platforms op hetzelfde object geeft anders twee rijen voor dezelfde week). Bij Uplisting: één koppeling per huishouden die álle panden dekt, waarbij elk Uplisting-pand aan een Tendlo-object gekoppeld moet worden — dat is een eenmalige "welk pand is welk object?"-stap die in het ontwerp hoort en makkelijk vergeten wordt.

Privacy — zwaarder bij de Uplisting-route, niet lichter

Drie dingen die geregeld moeten zijn vóór dit live gaat, en die nu niet in de privacyverklaring staan (docs/release/privacybeleid-concept.md):

  • Gastnamen zijn persoonsgegevens van derden. Die gast heeft geen relatie met Tendlo en heeft nergens iets ingevuld. Hoeveel er meekomt hangt van de route af: uit Airbnb- en Booking-iCal komt geen naam (uit Airbnb wel de laatste vier cijfers van een telefoonnummer); uit Uplisting komt vermoedelijk juist het meeste — naam, e-mail én telefoon. Precies de reden dat de betere route ook de zwaardere privacyroute is. Dit hoort in §3.2 van de verklaring ("de gegevens die je zelf in de app zet" wordt "…en gegevens die via een verhuurkoppeling binnenkomen") met een rechtsgrond in §8, en het verdient een bewuste keuze of we e-mail en telefoon van gasten überhaupt willen opslaan. Voorstel: nee — sla naam en periode op, laat contactgegevens buiten Tendlo. Wat je niet opslaat, hoef je niet te beschermen.
  • Er komt een verwerker bij, en de backend gaat meekijken. Uplisting wordt daarmee een ontvanger/verwerker die in §5.2 hoort ("de diensten die we inschakelen"), en omdat de webhook server-side landt, passeren gastgegevens onze eigen backend — anders dan bij een puur client-side import. Dat vraagt een regel in §5.2 en, bij echte uitrol, een verwerkersovereenkomst.
  • De sleutel en de feed-URL zijn geheimen. Een iCal-feed-URL is een onbeveiligde geheime link: wie hem heeft, ziet de kalender, voor altijd. De Uplisting-API-key is erger: die geeft toegang tot het hele account. Geen van beide mag in logs, in foutrapportage (Sentry), in een screenshot of in een back-up terechtkomen, en geen van beide hoort op de toestellen — bij een server-side sync staan ze in de serveromgeving, niet in een gesyncte tabel. Dat is een verschil met mijn eerdere schets, waarin de feed-URL nog een kolom op het object was.

Tier

Een externe koppeling is typisch betaald, en dat komt hier gratis mee: Vastgoed is al Plus, dus de import zit automatisch achter de Plus-grens. Er is geen nieuwe tier-regel nodig. Wél een overweging: dit is precies het type feature dat een "Premium"-vlak boven Plus zou kunnen rechtvaardigen (open punt 8 in de pricing-doc). Nu niet beslissen.

Conclusie

Advies

Ja, dit kan — en Uplisting is de route. Jij bent klant en hebt de key al; voor lezen is geen goedkeuring en geen partnerovereenkomst nodig. Eén koppeling dekt Airbnb én Booking.com, het is je eigen data in plaats van een publieke kalenderfeed, en annuleringen komen als expliciete gebeurtenis binnen in plaats van als stille verdwijning.

Vorm: één edge function die de webhooks ontvangt (patroon: revenuecat-webhook), plus een periodieke volledige ophaling per pand als reconciliatie. Sleutel in de serveromgeving, niet op de toestellen. Herkomst op de boeking (external_uid + external_source), identiteit nooit met een datum erin, handmatige rijen blijven onaantastbaar.

iCal blijft in het ontwerp als tweede implementatie achter dezelfde interface — voor gebruikers zonder channel manager en als vangnet. Daar is de belofte wél beperkt tot "bezette dagen komen binnen, vul gast en bedrag zelf aan". Beds24 is de logische derde als er ooit vraag naar is (open, self-service, webhooks).

Niet nastreven: een directe Airbnb- of Booking.com-koppeling — de eerste neemt geen aanvragen aan, de tweede is gepauzeerd en weigert individuele woningen. Via Uplisting is dat ook niet nodig.

Eén eigen ontwerp-gate waard voordat er code in gaat: het raakt een externe partij, een server-side component met een sleutel, twee kolommen, de privacyverklaring (drie paragrafen) en een gebruikersbelofte. Het hangt niet aan de Vermogen-varianten en kan dus los.

Herkomst van de claims in deze sectie

ClaimBronStatus
Klant maakt zelf een API-key aan (Connect → API); HTTP Basic met base64-key; geen goedkeuring nodig voor lezenUplisting support: API & webhooks / partner-integratie-artikelGeverifieerd (eigenaar heeft de pagina zelf gelezen; eigenaar heeft de key)
Rate limits 5/s en 100/min per IP, 15/min per pandUplisting support: API & webhooksGeverifieerd
Webhooks bestaan; endpoint dat herhaaldelijk niet antwoordt wordt uitgeschakeldUplisting support: API & webhooksGeverifieerd
Partnerovereenkomst met revenue share geldt voor de marktplaats-/integratiepartner-route (V2/V3), niet voor een klant met eigen keyUplisting partner-integratie-artikelGedocumenteerd; het onderscheid is de correctie van 26-07 en niet nogmaals onafhankelijk nagetrokken
Boekings-payload met guest_name, total_payout, channel, external_reservation_id enz.Publieke Postman-collectie van UplistingNiet bevestigd tegen een live account — zie open vraag
Airbnb neemt geen API-aanvragen aan; Booking.com heeft nieuwe connectiviteits-partners gepauzeerdairbnb.com/partner; connect.booking.comGeverifieerd (citaat van de pagina zelf)
Airbnb-iCal uitgekleed per 1 dec 2019; 365-dagenhorizon; verversing ~3 uurAirbnb Help 99; OwnerRez; OpertoMeerdere onafhankelijke bronnen, niet zelf tegen een feed getest
Wat de app al heeft (parser, external_uid, half-open interval, edge functions)Deze repoGeverifieerd in code, met bestand:regel
Géén pg_cron in dit projectsupabase/migrations/Geverifieerd (grep, geen treffer)

Waarschuwing voor wie dit later bouwt: er circuleert op GitHub een "Airbnb iCal sample" met volledige gastnamen, e-mailadressen en telefoonnummers. Die is verzonnen — hij spreekt Airbnb's privacywijziging van december 2019 tegen. Bouw je parserverwachtingen daar niet op.

Negen

Fasering — vroeg iets bruikbaars

Een leeg vermogensoverzicht dat pas na drie maanden klopt, is waardeloos. Daarom is V0 zo gekozen dat het op zichzelf waarde heeft en geen enkele migratie nodig heeft. Als je na V0 stopt, heb je nog steeds iets dat klopt.

V0

Het getal — de kleinste versie die op zichzelf waarde heeft

Één Vermogen-kaart met: netto vermogen = panden (actuele waarde, of aankoopprijs als die leeg is) + spaargeld (uit het bestaande ledger) − restschuld van alle leningen, met de gekoppelde hypotheek precies één keer geteld. Bezittingen en schulden als twee subregels. Per regel een doorklik naar de bronmodule. Peildatum en de voetnoot "op basis van wat jij kunt zien".

Alle benodigde data bestaat al sinds fase 1 (migratie 0063, gemerged en op de toestellen). Geen migratie, geen schemaVersion-bump, geen sync-config-wijziging, geen 4-weg lockstep. Eén pure domeinfunctie met tests + één provider + één scherm. Waar dat scherm hangt is variant A/B/C — in A is dit meteen de Vermogensoverzicht-module.

V1

De overkoepeling

Het domein Vermogen met Vastgoed, Leningen en Spaardoelen eronder, en het overzicht als eerste tegel. Deel-hint bij financiële velden. Vanuit een pand een zichtbare weg naar het overzicht en terug.

Vier regels domein-werk + één module-registratie + de navigatie-uitleg voor de tester (inclusief de AI-opt-in-notitie). Geen migratie.

V2

Kosten en netto rendement — hier landt TEN-118

De property_costs-entiteit uit ADR-0018 (object-gebonden én optioneel boeking-gebonden, zodat schoonmaakkosten per boeking en onderhoud per object in één tabel passen), netto rendement naast bruto, en property_valuations voor waardegeschiedenis zodat "groei per jaar" een echte reeks wordt in plaats van een overschrijfbare momentopname.

Wél een migratie (twee tabellen + kolommen), dus 4-weg lockstep met prod-deploy vóór de app-merge. Volgende vrije migratienummer is 0082; schemaVersion gaat van 57 omhoog.

V3

Beleggingen en jaaropgave-scan

Generieke waarderingsregels per peildatum (naam, categorie effecten/crypto/overig, waarde, evt. inleg) volgens ADR-0017 — zonder live koersfeed. Plus de AI-jaaropgave-scan uit ADR-0015 die een waarderingsrij schrijft. Dit is ook het moment om de aggregatie van hard gedraad naar de registry-hook te brengen.

Eén tabel + hergebruik van de bestaande extractie-stack.

V4

Boekingen automatisch binnenhalen via Uplisting

Zie § Acht: één edge function die Uplisting-webhooks ontvangt, plus periodieke reconciliatie; herkomst op de boeking; sleutel server-side. iCal als tweede implementatie achter dezelfde interface, voor gebruikers zonder channel manager. Géén directe Airbnb-/Booking-API — die zijn niet beschikbaar en via Uplisting ook niet nodig. Los te doen en niet afhankelijk van V1–V3, maar wél met een eigen ontwerp-gate.

Tien

Tier-indeling

Geldende split (owner-besluit 2026-06-17, vastgelegd in business/verdienmodel-en-pricing.md): 9 gratis — de vier kern-surfaces plus Maaltijden, Verjaardagen, Schoolvakanties, Afvalkalender en Bewaarlijsten — en al het overige Plus, inclusief álle AI en cloud-sync. Registry-default is Plus (module_descriptor.dart:108).

Advies

Vermogen is Plus. Zonder discussie, en het hoeft geen nieuwe pricing-beslissing te zijn: het volgt automatisch uit de default.

Redenen: elke bron die Vermogen aggregeert is zelf al Plus (Vastgoed, Leningen, Spaardoelen). Een gratis gebruiker zou dus per definitie een leeg scherm zien — dat is een slechtere ervaring dan een eerlijke Plus-badge. En als aggregatie over meerdere modules is het precies het type "overzicht dat je nergens anders krijgt" waar een abonnement voor is.

Twee dingen om te weten. Ten eerste: Plus-handhaving staat sinds de server-side entitlements aan (plus_entitlement.dart:31), met een harde router-gate (app_router.dart:273-295). Dat is nieuwer dan de pricing-doc zegt, die nog van "enforcement staat uit" uitgaat — die doc loopt achter en moet bijgewerkt worden. Ten tweede: de tier is runtime instelbaar zonder release via de tabel module_tier_overrides (module_tier_overrides.dart:44). Als je Vermogen tijdelijk gratis wilt openzetten als lokkertje, kan dat zonder store-release.

Als er ooit een pakketvlak boven Plus komt (in de pricing-doc staat "Premium" als open punt), is een vermogensoverzicht een plausibele kandidaat daarvoor. Dat is nu geen beslissing die genomen hoeft te worden — noteren is genoeg.

Elf

Tickets en ADR's

Hoe de tickets zich tot deze fasering verhouden

TicketWat het isWaar het landt
TEN-87 De oorspronkelijke vraag ("denk ook nog even mee zodat we een overzicht krijgen van wat ons vermogen is") Programma-ticket. Overkoepelt V0 t/m V3. Wacht op jouw variantkeuze; geen fixer erop tot die keuze er is. Fase 1 (aankoop/financiering/bedrag op boekingen/bruto rendement, migratie 0063) is hier al uit voortgekomen en staat op de toestellen.
TEN-118 "Bij vastgoed wil ik ook kosten kunnen opvoeren zoals onderhoud of vervangen van spullen" V2. Blijft gebundeld, zoals de eerdere verrijking adviseerde: los oppakken zou een kostentabel opleveren zonder de boeking-koppeling die TEN-111's schoonmaakkosten nodig hebben, en dat is later opnieuw migreren. Niet starten voor de variantkeuze.
TEN-111 Boeking-flow: "al geweest" moet uit de datum volgen, annuleren hoort niet in de aanmaak-flow Gaat los vooruit — die kleine veilige subset raakt dit ontwerp niet en is hier niet aangeraakt. Het kosten-deel van dat ticket hoort bij V2.

Moet er een ADR volgen?

Ja, en er moet er ook één vervangen worden. De conventie in docs/architecture/adr/ is NNNN-slug.md met een kop # ADR-NNNN — titel, een **Status:**-regel met datum en ticket, en de secties Context · Besluit · Alternatieven · Consequenties. Hoogste nummer nu is 0019, dus het volgende is 0020.

  • ADR-0020 — Vermogen-positionering v2, te schrijven na jouw keuze. docs/architecture/adr/0012-vermogen-positionering.md moet dan op superseded met een verwijzing: besluit 2 van die ADR ("scherm/tab binnen Financieel, geen nieuw module-id") is precies wat 26 juli omdraait. ADR-0012 verwijderen mag niet — het archiefbeleid schrijft een banner met datum en verwijzing voor.
  • ADR-0014 (rekenlaag) blijft grotendeels staan, maar zijn definitie van vermogen moet worden aangevuld met de uitkomst van de open vragen 1–5 (peildatum, dedupe-regel, bruto/netto, auto's, saldo).
  • ADR-0018 staat nog op VOORSTEL en is deels ingehaald door de werkelijkheid: zijn sectie C (verhuurmodus + agenda) is inmiddels gebouwd en getest via TEN-120 (migratie 0076). Die ADR moet worden bijgewerkt naar wat er echt staat, en zijn secties A en B doorschuiven naar V2.
  • Dit document is géén ADR: het is een levend ontwerpdocument met varianten en open vragen. De ADR's leggen de gekozen uitkomst vast; dit stuk legt de afweging vast.
Twaalf

Wat ik van je nodig heb om verder te kunnen

  1. Een variant: A (aanbevolen), B, of C.
  2. De zeven open vragen uit § Zeven — of minimaal 1, 2 en 3, want zonder die drie kan de rekenlaag niet gebouwd worden zonder aannames. Vraag 7 (hypotheek op meerdere objecten) hoeft níet vóór V0: mijn voorstel is dat V0 het huishoudniveau gebruikt, wat al correct is — maar als je wél overwaarde per object wilt, wil ik dat vóór V2 weten, want dan komt er een tabel bij.
  3. Groen licht voor de Uplisting-route uit § Acht, plus twee kleine dingen die alleen jij kunt doen: de mail naar partner@uplisting.io voor de volledige API-documentatie, en of je wilt dat we samen één leesaanroep op je eigen account doen om te zien welke velden er echt in een boeking zitten. Daarna maak ik daar een eigen ontwerpronde voor. Dit staat los van de variantkeuze en mag later.

Zodra je een variant kiest, is de volgende stap: ADR-0020 schrijven, ADR-0012 op superseded zetten, en een implementatieplan voor V0 (dat is klein en migratievrij, dus dat kan meteen door de gewone fix-poort).