# INWENTARZ BASE/BRW — co już istnieje i działa (inspekcja 2026-10-04) **Cel dokumentu:** mapa „co już mamy" dla agentów wdrażających Krok 4, żeby **nie pisali od zera** tego, co jest przetestowane. Zasada: zaadaptuj istniejące, nie wymyślaj nowego. --- ## 1. Co realnie siedzi w Base.com (konto `bazarowy-swiat`, inwentarz meblowy `62360`) Źródło: realny read-back `vilmax-cockpit/bl_oskar_full.json` + `docs/brw_instrukcja_mapowania.md`. | Rekord | ID | SKU | Uwagi | |---|---|---|---| | Rodzic OSKAR | `690927524` | `VIL-OSK-TIL` | wysłany 16.09 przez `scripts/export-base-oskar.mts` + 34 warianty; `category_id: 9247629`, `manufacturer_id: 2856198`, `tax_rate: -1`, grupa cen `54601` (4399 zł), magazyny `bl_91023`/`bl_91183`, `shipping_template` pod konto Allegro. **Warianty mają `ean:""`** — EAN-y NIE trafiły do wsadu | | Kopia pilotażowa | `692241911` | `VIL-OSK-TIL_copy` | rodzic utworzony ręcznie (kopia) | | Wariant testowy BRW | `692241915` | `VIL-OSK-L-TIL01` | zaktualizowany 29.09 przez `scripts/brw-pilot-oskar.mts`: EAN `5909990000197`, **45 cech z etykietami BRW**, opis HTML 2078 zn., `bl_91183=10`, `bl_91023=0`, kategoria `9481504` | **Wniosek:** eksport DZIAŁA (połączenie, mapowanie, token, read-back) — ale obie wysyłki zrobiono **jednorazowymi skryptami** z ręcznie wpisanymi wartościami, nie ścieżką UI (Krok 4). Dla kolejnego produktu skryptowej ścieżki nie powtarzamy. ## 2. Gotowe rozwiązania do reuse (kolejność wg wartości) | # | Skarb | Plik | Do czego w vilmal | |---|---|---|---| | 1 | **Mapa Mirakl↔BL 1:1** (kody `pimcore-*` → etykiety cech + wartości pilotażowe + pola ofertowe + znane braki) | `docs/brw_instrukcja_mapowania.md` | **F1: `channelMap.brw` rejestru faktów** — przepisać, nie projektować | | 2 | **45 cech BRW na żywym rekordzie** | `scripts/brw-pilot-oskar.mts` (`FEATURES`) | wzorzec wartości: „Marka (BRW)" zamiast „Marka" (zamknięty słownik BL), „Kategoria Mirakl" = `Meble/Meble wypoczynkowe/Narożniki` | | 3 | **Kanoniczny słownik cech FR/PL→PL** (aliasy grup + valueMaps: mechanizmy, pianki, tkaniny, stelaże) | `vilmax-cockpit/config/baselinker-feature-canonical.json` | **F1: seed valueMaps rejestru**; normalizacja wartości → etykiety Base | | 4 | **Profile kanałów jako działający koncept** (`channels.{base,allegro,woocommerce,olx,social_*}` z `targetField`, titlePrompt, bodyPrompt, limity znaków) | `vilmax-cockpit/config/editor-config.json` | **C1: wzorzec struktury `channel_profiles`** (u nas: tabela DB, nie JSON) | | 5 | **Deterministyczny renderer opisów** (bloki HTML + placeholdery + warunki `hasSleepingSurface`…) | `vilmax-cockpit/config/description-blocks.json` + `src/descriptionAssembly.js` | **C2: wzorzec `deterministicRenderer`** — bloki sekcji BRW | | 6 | **Bramka live `BASELINKER_SEND_HTTP`** + `sendToBaseLinker` (dry-run, log payloadu) | `vilmax-cockpit/src/baselinker.js` | **P0: wzorzec `export.live_enabled`** | | 7 | **Odczyt słowników z API** (`getInventoryTags` + filtrowanie tagów przed wysyłką, cache 5 min) | `vilmax-cockpit/src/baselinker.js` | **B1: słowniki z API w Ustawieniach** (D-11) | | 8 | **Transport zdjęć `data:` base64** (preflight ≤2 MB, kolejność slotów) | `vilmax-cockpit/src/baselinkerImages.js` + `docs/BASE-KONTRAKT.md` §3 | adapter/outbox — już zgodne | | 9 | **Kontrakt z checklistą 17 pól + metody słownikowe** (`getInventoryCategories/Manufacturers/Warehouses/PriceGroups/Integrations/ShippingTemplates/ExtraFields/AvailableTextFieldKeys`) | `docs/BASE-KONTRAKT.md` §1, §5, §6 | **B1: checklista zamiast projektować pola** — pozycje [TEST] dziś odczytywalne przez API | | 10 | **Eksport z guardami** (snapshot+sha+0 blokerów+idempotencja po SKU+SHA zdjęć+read-back) | `scripts/export-base-oskar.mts` | wzorzec guardów dla outboxu — NIE dla ścieżki UI | | 11 | Generic adapter `callApi` (timeout, ExportBlocked/Rejected/Uncertain, token z panelu>env) | `server/src/integration/baseAdapter.ts` | rozszerzyć o brakujące `get*` — ten sam helper | | 12 | **Kategorie per-inwentarz**: CSV dywanów to INNY inwentarz | `_audit/dywanowy-bazar/docs/Kategorie_BaseLinker.csv` | ostrzeżenie: nie kopiować ID kategorii między inwentarzami | ## 2b. Pula reuse na KOLEJNE fazy (poza fazą 1 BRW — nie implementować teraz) Sprawdzone 2026-10-04, wszystko w `vilmax-cockpit/src/`: | Moduł cockpit | Do czego w vilmal (faza 2+) | |---|---| | `factExtractor.js` | ekstrakcja faktów ze źródeł (asystent faktów) | | `openaiVisionOcr.js`, `geminiOcr.js`, `labelOcrCommon.js`, `labelQuery.js` | OCR etykiet/kart fabrycznych → kandydaci faktów (Krok 1-2) | | `llmClient.js`, `openai.js` | klient LLM z budżetem/retry — wzorzec dla C6/C7 | | `titleGenerator.js`, `frPlCopySanitizer.js` | generowanie tytułów + sanityzacja FR→PL copy | | `variantMatrix.js` | macierz wariantów (Krok 3) | | `catalog/` + `catalogOffer.js` (Puppeteer PDF) | karty produktu PDF / dokumenty | | `visual/staging.js`, `restage.js`, `textureSwap.js`, `dimensionBlueprint.js`, `interiorWorkflow.js`, `photoWorkflow.js` | pipeline zdjęć ComfyUI (Krok 1-2) | | `dispatch/`, `bulkJobStore.js`, `bulkOperations.js` | wzorzec outbox/zadań wsadowych | | `pricingStore.js`, `tagsStore.js`, `templateStore.js`, `aiConfigSnapshots.js`, `buildDraftFromSource.js`, `draft.js`, `pipeline.js` | draft/snapshot configu — porównać z modelem vilmal przed budową nowych store | | `social/`, `video/` | social/ads — poza zakresem vilmal | **Maciuś (nadzorca jakości): NIE istnieje w cockpit** — koncept wyłącznie z `PROMPT_STARTOWY.md` §8, do zaprojektowania w vilmal (faza 2). Nie szukać wzorca — nie ma. **Zasada repo-first per slajs:** przed każdym slajsem sprawdź (a) INWENTARZ §2/§2b, (b) `grep` w `vilmax-cockpit/src` dla odpowiednika tematu slajsa; wynik odnotuj w planie slajsa („reuse: plik X / brak odpowiednika"). Koszt ~2 minuty; zapobiega pisaniu na nowo tego, co już działa. ## 3. Potwierdzone braki w vilmal (stan po inspekcji kodu) 1. **`send-now` bez bramki** — `server/src/exportRoutes.ts:61-63`: brak `requireApprover` (reszta tras ją ma); `exportOutbox.sendExportNow` auto-tworzy snapshot/akceptację/zgodę i lekceważy blokery domenowe. → **P0**. 2. **Dryf kluczy faktów** (potwierdzony): - `brwExport.ts:122-126` czyta `cushions.seat_fill`/`cushions.seat_suspension`; dane siedzą pod `filling.seat`/`suspension.seat` → puste „Wypełnienie siedziska" w arkuszu. - `brwExport.ts:108` czyta `dimensions.depth_cm`; import pisze `dimensions.depth_body_cm`; inne domeny czytają `body_depth_cm` → **trzy klucze jednej głębokości**. - `brwExport.ts:69` czyta `commerce.parent_sku`; `offerPackage.ts:324` czyta `base.parent_sku`. - `brwExport.ts` `factValue` wymaga `status=confirmed` **i `sourceRef`** — fakty bez sourceRef po cichu puste. 3. **`base.*` jako fakty produktu** — `offerPackage.ts:210-227` miesza konfigurację konta z faktami mebla + fallback `app_settings` (W12). Przenieść do `channel_targets`/`product_channel_settings`. 4. **Zdjęcia w arkuszu BRW** — `brwExport.ts` pisze `/api/renditions/{id}` (lokalna ścieżka); Mirakl wymaga publicznych URL-i. Pilotaż odłożył to („podpiąć rano"). Do rozwiązania: hosting obrazków lub import bez zdjęć + upload przez panel/integrację. 5. **Ustawienia jedno-kontowe** — `settingsRoutes.ts`: tylko token/inventoryId/priceGroupId/warehouseId → `app_settings`. Brak: wyboru konta, kategorii, producenta, integracji (allegro/brw), szablonu wysyłki, słowników z API. 6. **Adapter ma tylko `getInventories`** — do dodania pozostałe `get*` (trywialne, ten sam `callApi`). 7. **BRW opis przez kanał Allegro** — `brwExport.ts`/`validate` czyta `channelText.allegro` dla BRW (hack; BRW powinien mieć własny kanał/profil). 8. ~~Trzy różne ID „Narożniki"~~ **Rozstrzygnięte przez API (§5.2):** `9247629` = „Meble tapicerowane" (ogólna, default env — tam siedzi rodzic OSKAR), `9481504` = „Narożniki" (**właściwa dla OSKAR-a**, kopia pilotażowa), `7488990` nie istnieje. Dla eksportu narożnika celować w `9481504` — decyzja per channel_targets. ## 4. Weryfikacja live read-only — pierwszy krok nowej sesji Token: `BASELINKER_API_TOKEN` w `vilmal/.env` (config.ts:53; pilot z niego korzystał). Wywołania metodą `callApi`/`bl()` ze skryptów — **tylko odczyt, żadnych zapisów**: ``` getInventories → potwierdź 62360 (meble) + lista innych inwentarzy getInventoryCategories {inventory_id:62360} → prawdziwe ID "Narożniki" (rozstrzygnij 9247629 vs 9481504) getInventoryIntegrations → czy jest integracja brw_* / mirakl_*? jaki source_id? oraz allegro_* getInventoryManufacturers → czy 2856198 = Vilmax? getInventoryWarehouses → bl_91183 "Biała", bl_91023 — nazwy/role getInventoryPriceGroups → 54601 "Domyślna" PLN; co to 80521/82270? getInventoryShippingTemplates → lista szablonów per integracja getInventoryAvailableTextFieldKeys → czy klucze "description|pl|brw_" są dostępne getInventoryProductsData {products:[690927524,692241915]} → aktualny stan OSKAR-ów (EAN-y? zdjęcia?) ``` Wynik → dopisać do tego dokumentu §5 + `channel_targets` seed. Dodatkowo na `data/vilmal.db` (właściwa baza, `config.ts:44`): `SELECT count(*), count(DISTINCT product_id) FROM product_facts` (przeliczenie §1.1 wizji — wcześniejsze liczby mogą być z `app.db`), `SELECT key FROM product_facts WHERE key LIKE 'base.%'` (stan konfiguracji konta w faktach), slug produktu OSKAR (teza W14). ## 5. Stan po weryfikacji (uzupełnić w sesji) ### 5.1 Config celu Base — źródło prawdy: `vilmax-cockpit/.env` (2026-10-04, od właściciela) Kompletna mapa konfiguracji, której vilmal nie ma w Ustawieniach — do implementacji jako `channel_targets` (NIE jako fakty produktu): | Klucz env (cockpit) | Wartość | Znaczenie dla vilmal | |---|---|---| | `BASELINKER_API_URL` | `https://api.baselinker.com/connector.php` | już jest (`config.ts`) | | `BASELINKER_METHOD` | `addInventoryProduct` | jedyna metoda zapisu | | `BASELINKER_INVENTORY_ID` | `62360` | katalog meblowy → `channel_targets.inventory_id` | | `BASELINKER_CATEGORY_ID` | `9247629` | „Meble tapicerowane" (ogólny default); dla narożników właściwa jest `9481504` „Narożniki" — §5.2 | | `BASELINKER_TAX_RATE` | `-1` | VAT ZW → `commerce.vat_rate` mapowanie już jest (`mapTaxRate`) | | `BASELINKER_WAREHOUSE_ID` | `bl_91023` | magazyn główny; pilot używał `bl_91183` „Biała" — **oba istnieją, wybór = pole w channel_targets** | | `BASELINKER_PRICE_GROUP` | `54601` | „Domyślna" PLN | | `BASELINKER_SEND_HTTP` | `1` | wzorzec flagi `export.live_enabled` (P0) | | `BASELINKER_STAN_HANDLOWY_FIELD` | `extra_field_24081` | pole dodatkowe „Stan handlowy" | | `BASELINKER_PROMO_PRICE_FIELD` | `extra_field_20298` | pole dodatkowe „promocja_cena" | Token `BASELINKER_API_TOKEN` identyczny w `vilmal/.env` i `vilmax-cockpit/.env` (sha zweryfikowany, wartości nie udostępniamy). ### 5.2 Wynik weryfikacji live (2026-10-04, `scripts/verify-base-readonly.mts`) - Autoryzacja: **`token` w body form DZIAŁA**; `X-BLToken` + JSON body → `ERROR_EMPTY_METHOD`. - **Inwentarz:** `62360` „Domyślny" — jedyny na koncie. - **Kategorie (62360):** meblowe to `9247629` „Meble tapicerowane" (rodzic OSKAR, default z env) i `9481504` „Narożniki" (kopia pilotażowa — **kategoria właściwa dla narożnika**). Reszta to kategorie dywanowe (Dywany, Borkum, Madrid…). `7488990` z mapy cockpit **nie istnieje** w API. - **Producenci:** `2856198` „Bazarowy-Świat" — jedyny. - **Magazyny:** `bl_91023` „Dąbrowskiego", `bl_91183` „Biała". - **Grupy cen:** `54601` Domyślna PLN, `80521` Promocyjna -50% PLN, `82270` -40% Wyprzedaż PLN. - **Integracje na koncie (source → id):** `allegro`→`allegro_24646`, `erli_connector`→`erli_connector_24594`, `taniey`→`taniey_24595`, `olx`→`olx_21037`, **`blackredwhitepl`→`blackredwhitepl_45744`** ✓ BRW JEST na koncie, `shop`→`shop_5020241`,`shop_5032324`. - **Szablony wysyłki:** tylko dywanowe (Allegro „Dywany Gabaryt/Standard", Erli „Kurier", Taniey -80/-150cm) — **dla mebli trzeba utworzyć szablon gabarytowy albo wysyłać bez `delivery_template`**. - **Pola tekstowe per kanał:** BRW ma tylko `description|pl|blackredwhitepl_45744` (bez name/features/extra). Allegro/Erli mają pełny zestaw name+description+features+extra1-4. - **Stan OSKAR w Base:** rodzic `690927524` (cat 9247629, 8 img) ma **136 wariantów — tylko 34 z EAN** (prawdopodobnie zdublowane przy pilotach — do inwentaryzacji przed kolejnymi wysyłkami). Kopia `692241915` w `9481504` ma EAN i 8 img.