# PROGRESS — stan projektu (kwiecień 2026) > **Cel**: szybko importować dywany z `tara-carpet.com` (sklep DE / publiczny JSON storefrontu) do BaseLinker (inventory 62360), > żeby właściciel mógł je sprzedawać na Allegro/OLX/sklepie. Posiada ok. 400 sztuk końcówek serii. > > **Zasada nadrzędna**: minimum klikania, zero wymyślanych parametrów, parametry trafiają w > dokładnie te pola w BL które BL realnie ma. > > **Kopia PL (marketing vs fakty)**: JSON/HTML ze sklepu źródłowego dostarcza **faktów** (wymiary, materiał, specs, EAN itd.). Polska nazwa, opis HTML i teksty kanałów są **własną redakcją Dywanowy Bazar** wg `config/editor-config.json` i promptów w `src/openai.js` — bez przenoszenia opisu marketingowego karty dostawcy **1:1** (model: zwroty konsumenckie / outlet). Parametry w `specsPl` to nadal techniczne mapowanie wierszy DE→PL w **tej samej kolejności** co `specsDe`. --- ## 1. STAN POTWIERDZONY (przetestowane LIVE) - **Pierwszy produkt zapisany live do BL**: `Dywan krótki Madrid Uni - Antracyt`, EAN `4062381018421`, kategoria 5230632 (Madrid), magazyn `bl_91023`, inventory 62360. - **Pełen pipeline DZIAŁA**: Tara JSON + Tara HTML → adapter → wiersze specs → jedno wywołanie OpenAI (`translateForBase`): **`namePl`**, **`descriptionPlHtml`** (oryginalna polska narracja z faktów, struktura `salesFramework`; bez wklejania HTML źródła 1:1), **`specsPl`** (etykiety/wartości PL zsynchronizowane z `specsDe`) → normalizacja/zapis draftu → BaseLinker payload → live save z `productId`. ## 2. KONFIGURACJA `.env` (zweryfikowana z żywym BL) | Klucz | Wartość | Uwagi | | --- | --- | --- | | `BASELINKER_INVENTORY_ID` | `62360` | "Domyślny" — jedyny inventory na koncie | | `BASELINKER_CATEGORY_ID` | `5153921` | "Dywany" (root). Fallback. | | `BASELINKER_TAX_RATE` | `-1` | "Użyj domyślnej stawki magazynu BL" | | `BASELINKER_WAREHOUSE_ID` | `bl_91023` | "Dąbrowskiego" — JEDYNY warehouse w 62360 | | `BASELINKER_PRICE_GROUP` | `54601` | Default price group dla 62360 | | `BASELINKER_SEND_HTTP` | `1` | LIVE save włączone | | `EUR_TO_PLN` | `3.5` | Kurs do auto-przeliczenia ceny (`priceEur × 3.5 × multiplier`) | | `PRICE_MULTIPLIER` | `1` | Mnożnik marży (1.0 = bez zmiany; 1.2 = +20%) | | `PRICE_ROUND_TO` | `0.01` | Jednostka zaokrąglenia ceny PLN | **Dodatkowe klucze** (opcjonalne — pełna lista i domyślne wartości w `src/config.js`): - **AI (polska karta produktu)**: `OPENAI_API_KEY` (wymagane do generacji `namePl` / opisu / `specsPl`), `OPENAI_MODEL`, `OPENAI_BASE_URL`. - **OCR etykiety**: `GEMINI_API_KEY`, `GEMINI_MODEL`; albo OpenAI Vision przez ten sam `OPENAI_API_KEY` + `OCR_OPENAI_MODEL`. Wybór silnika: `OCR_PROVIDER` = `gemini` \| `openai`. - **Dostęp**: `APP_PASSWORD` — włącza HTTP Basic na UI i API (wyjątki: `/health`, `/manifest.webmanifest`). Puste = brak blokady hasłem (np. localhost). - **Cena katalogowa w BL (opcjonalnie)**: `BASELINKER_PROMO_PRICE_FIELD` — domyślnie `extra_field_20298`; pusty string = nie wysyłaj. - **Inne**: `BASELINKER_STAN_HANDLOWY_FIELD`, `DESCRIPTION_APPEND_HTML`, `LOG_AI_USAGE`, `HOST`, `PORT`. > **NIE** używaj `bl_91183` (Biała) — istnieje na koncie, ale nie jest podpięty do inventory 62360. > Jeśli chcesz mieć stock w obu, musisz w panelu BL: Magazyn → Konfiguracja → Magazyny → przypisać do 62360. ## 3. TWOJE ZASOBY W BL (zweryfikowane przez `getInventories` / `getInventoryWarehouses` itd.) ``` Inventory 62360 "Domyślny" languages: ["pl"] default_language: "pl" warehouses: ["bl_91023" — Dąbrowskiego] price_groups: [54601 (default), 80521, 82270] reservations: true ``` ``` Magazyny ilościowe (warehouses) na koncie bl_91023 Dąbrowskiego ← podpięty do inventory 62360 bl_91183 Biała ← osobny, NIE w inventory 62360 ``` ``` Pola dodatkowe (extra fields) extra_field_20298 "promocja_cena" typ: number ``` ``` Kategorie BL (zgodne 1:1 z config/series-to-category.json — 23/23 OK) Root "Dywany" (5153921): Borkum, Fiona, Fresh, Helsinski, HomeStyle, Homestyle Flora, Joy, KIds, Kitten, Madrid, Malmoe, Oslo, Rainbow, Regina, Santa monica, Shell, Sunrise, Sylt, Tiber, Valetta, Vanessa Root "Wyprzedaż" (7488993): Dywany Tarasowe (7488989), Do domu (7488992) ``` ``` Kanały sprzedaży (z getInventoryAvailableTextFieldKeys) Allegro: konto 24646 (i default 0) Erli connector: konto 24594 (i default 0) Taniey: tylko default 0 ``` ``` Dozwolone klucze w text_fields name, description, description_extra1..4, features + 70 wariantów per-channel (np. description|pl|allegro_24646) ``` ## 4. ARCHITEKTURA KODU **Zależność npm**: `sharp` (obróbka obrazów / kolaż). Reszta — czysty Node.js (`package.json`: engines `>=20`). Testy: `npm test`. ### Ścieżki UI (`server.js`) | GET | Plik / zachowanie | | --- | --- | | `/` | Przekierowanie 302 → `/editor` | | `/editor` | `public/dashboard.html` — panel startowy (wyszukiwarka Tary, OCR, lista draftów) | | `/editor/wizard` | `public/wizard.html` — kreator wielokrokowy | | `/editor/legacy` | `public/editor.html` — pojedynczy edytor draftu (legacy) | | `/static/*` | Statyczne assety z `public/` | | `/manifest.webmanifest`, `/service-worker.js` | PWA (bez hasła gdy `APP_PASSWORD` ustawione — manifest na liście wyjątków) | ### API HTTP (wybrane) ``` server.js ├── GET /health ├── GET/POST /api/config → editor-config.json ├── GET /api/source/search?q=... → gdy `q` wygląda na EAN (8–14 cyfr) lub Artikel `d-d-d` i istnieje │ `data/tara-index.json` — wyszukiwarka z indeksu; w odpowiedzi opcjonalnie │ `searchSource`: `tara-index` | `tara-index-empty` | `storefront-suggest` │ (indeks: `npm run build:tara-index`) ├── GET /api/source/variants?url=... → warianty produktu ├── GET /api/source/image-proxy → proxy obrazów (CORS) ├── GET /api/categories → kategorie z docs/Kategorie_BaseLinker.csv ├── POST /api/draft-from-source → adapter Tara → AI → draft (alias: POST /api/import) ├── POST /api/regenerate-channel ├── POST /api/build-payload → podgląd payloadu BL (dry run) ├── POST /api/save-to-base ├── GET /api/drafts | POST /api/drafts ├── GET/PATCH/DELETE /api/drafts/:id ├── POST /api/drafts/:id/duplicate | generate-ai | suggest-label-copy ├── POST /api/drafts/:id/compose-collage ├── POST /api/drafts/:id/generate-badge-plan ├── POST /api/drafts/:id/render-gallery-badges-from-plan ├── GET /api/tags | POST /api/tags → GET zwraca też baselinkerInventoryTags (nazwy tagów z magazynu BL) ├── POST /api/bulk-import/start → wiele URL-i Tary → kolejno drafty (+ AI); max 80 URL ├── GET /api/bulk-import/status → postęp masowego importu ├── GET /api/ocr-providers → które silniki OCR są skonfigurowane └── POST /api/ocr-label-scan → body: imageBase64, opcjonalnie provider (gemini|openai) src/pipeline.js └── extractSourceProduct(url, variantId) → resolveAdapter + extract src/buildDraftFromSource.js └── buildDraftFromSource(body, runtimeConfig) → extract → buildProductDraft → opcjonalnie AI (używane przez POST /api/draft-from-source, duplikat z nowym URL, bulk import) src/catalogOffer.js └── SOURCE_HTTP_RESPONSE_FORMAT, getSourceTagList (sourceTags / legacy shopifyTags) src/adapters/taraCarpet.js ├── search(query, limit=10) → /search/suggest.json — szuka po NAZWIE │ (nie po EAN ani po Artikelnummer — suggest ich nie indeksuje; EAN/Artikel → indeks lokalny) │ zwraca: [{title, handle, url, priceMinEur, priceMaxEur, available, image, vendor}] ├── getVariants(url) → /products/{handle}.js → lista wariantów │ zwraca: {title, handle, image, options, variants:[{id, color, size, sku, ean, │ priceEur, compareAtPriceEur, available, url}]} ├── fetchStorefrontProductJson (wewn.) → /products/{handle}.js ├── fetchProductHtml → /products/{handle} (HTML do parsowania spec) ├── parseSpecsFromHtml → regex SPEC_ROW_REGEX, 13 par {nameDe, valueDe} └── extract → łączy JSON + HTML, zwraca specsDe, pileHeightMm, variantWeightKg src/draft.js ├── loadSeriesRules + resolveCategoryFromTitle │ (config/series-to-category.json: keyword w tytule → category_id) ├── parseSize (np. "120x170 cm" → widthCm, lengthCm; "120 cm rund" → round) ├── getPileHeightCm (z pileHeightMm / 10) ├── buildProductDraft (główna funkcja) └── validateDraft (z ZERO_IS_VALID_FOR — price=0 jest OK) src/openai.js ├── translateForBase (DEFAULT, nazwa historyczna) — JEDEN call OpenAI: polska karta magazynowa z faktów źródłowych — nie dosłowne DE→PL opisu; zwraca {namePl, descriptionPlHtml, specsPl[]} ├── applyBaseTranslationToDraft └── generateChannelDrafts (OPT-IN, gdy generateMarketplaceChannels=true) src/baselinker.js ├── buildImagesObject → {"0":"url:https://...","1":"url:..."} ├── buildFeaturesMap → text_fields.features = {Materiał: "Polipropylen", ...} ├── buildTextFields → name (+ "OUTLET |" gdy outletMode) ├── resolveSku → fallback: DYW-{EAN}, gdy brak EAN to DYW-{timestamp} ├── fetchInventoryTagNames → dla GET /api/tags (podpowiedź vs katalog BL) ├── buildBaseLinkerEnvelope └── sendToBaseLinker (HTTP do BL gdy sendHttp=true) src/draftStore.js / src/tagsStore.js Persystencja draftów (JSON) i predefiniowanych tagów. src/collageComposer.js Składanie kolaża JPEG z URL-i zdjęć; layout `grid` | `row` (parametry w drafcie). src/labelOcrCommon.js, src/geminiOcr.js, src/openaiVisionOcr.js Wspólny format wyniku OCR (EAN, kandydaci, Artikelnummer, wymiary); implementacje Gemini / OpenAI Vision. src/badgePlanner.js, src/badgeRenderer.js Plan etykiet galerii + render z planu (endpointy generate-badge-plan, render-gallery-badges-from-plan). src/payloadImageOrder.js (+ public/payloadImageOrder.js) Ta sama kolejka zdjęć do payloadu BL (max 16 slotów) — serwer i klient. public/dashboard.js, public/wizard.js, public/editor.js UI: wyszukiwarka, OCR (wybór providera, localStorage), kreator (m.in. podsumowanie kroków, „Sprawdź payload”, podgląd HTML Base, regeneracja AI: pełna / tylko Base / tylko kanały), legacy edytor. scripts/bl-inspect.js DIAGNOSTYKA — czysty odczyt z BL. Uruchom: node scripts/bl-inspect.js Pokazuje: inventories, extra_fields, allowed text keys, warehouses, categories, cross-check series-to-category.json vs BL. config/editor-config.json Profile sprzedaży, kanały, definicje pól, prompty AI. DEFAULT: tylko BaseStore_Default, marketplace OFF, outletMode OFF. config/series-to-category.json 23 reguły keyword→categoryId, plus fallback (5153921) i outletCategoryId (7488993). SPRAWDZONE z BL: 23/23 ID istnieją. ``` ### Tytuł Allegro (Faza A — marketplace copy) - Reguły w jednym module: [`src/channelTitlePolicy.js`](../src/channelTitlePolicy.js) — ten sam kod serwuje endpoint `/deps/channelTitlePolicy.js` dla legacy edytora (`/editor/legacy`) i **importuje się** w kreatorze (`wizard.js`, krok 5 — skrót kanału Allegro). - Walidacja to **ostrzeżenia** (`warning`): nie blokują zapisu do BL; komunikaty są w `meta.validationIssues` (`field`: `channelTitle:allegro`), pod polem tytułu Allegro w legacy oraz przy karcie Allegro w kreatorze. - **AI może:** dopasować tytuł do limitów po generacji (clamp + banlista w `openai.js`). **Operator musi** sprawdzić ostrzeżenia policy (np. za krótki tytuł, URL w tytule) przed publikacją na Allegro. - Limity i lista fraz — raz na kwartał zweryfikuj względem aktualnej [pomocy Allegro](https://help.allegro.com/en/sell/a/rules-for-placing-links-and-website-urls-in-the-offer-description-E7kzr4jkZS7) i zaktualizuj stałe w module. **Faza B (copy):** w promptach wielokanałowych jest jawny kontrakt „wspólne fakty + `salesFramework` dla Base, reszta kanałów przez `bodyPrompt`” (`buildSharedChannelBodyContractBlock` w `openai.js`). Checklista zdjęć pod Allegro: [`public/allegroPhotoChecklist.js`](../public/allegroPhotoChecklist.js) — kreator krok 3 i legacy edytor. ## 5. PRZEPŁYW JEDNEGO PRODUKTU (dla nowego czatu) 0. **Wyszukiwarka Tary w UI** (`/editor` — dashboard) — user nie musi wchodzić na tara-carpet.com. - W panelu „Szukaj produktu na Tarze" wpisuje słowo (np. `Madrid Anthrazit`, `capri beige`, `sylt grau 200x290`). - Klik **Szukaj** → GET `/api/source/search?q=...` → karty wyników (miniaturka + tytuł + cena od €). - Klik karty → URL automatycznie wpisany do pola, draft pobiera się sam. - **OCR etykiety** (opcjonalnie): zdjęcie → POST `/api/ocr-label-scan` → wypełnienie pola wyszukiwania (priorytet EAN). **Bez lokalnego indeksu** trafienie w Tarze po samym EAN **nie jest gwarantowane** — patrz sekcja 8. - **OGRANICZENIE**: publiczna wyszukiwarka suggest u Tary indeksuje tylko nazwę. Wyszukanie po artykule (`10619-01-11`) ani po EAN (`4062381018421`) przez `/api/source/search` **standardowo nie działa**. Rozwiązanie: mechanizm z sekcji 8. 0b. **Kreator** (`/editor/wizard`): wielokrokowy flow na jednym drafcie — podsumowanie z linkami do kroków, preview payloadu (`/api/build-payload`), podgląd HTML Base, selektywna regeneracja AI. 1. User wkleja URL Tary (dashboard / legacy `editor.html`) lub kontynuuje z kreatora — **Pobierz draft**. 2. `POST /api/draft-from-source` → `buildDraftFromSource` → `extractSourceProduct` → `taraCarpetAdapter.extract`: - fetch JSON `/products/{handle}.js` (warianty, ceny, zdjęcia) - fetch HTML `/products/{handle}` (parametry z tabeli) - regex: 13 par {nameDe, valueDe} 3. `buildProductDraft`: - parsuje rozmiar → wymiary, kształt - waga z wariantu (gramy → kg) - wysokość runa z `Gesamthöhe` mm → cm - kategoria: keyword w tytule → ID z series-to-category.json 4. `translateForBase` (1 call OpenAI, response_format: json_object): - generuje `namePl`, `descriptionPlHtml` (oryginalna redakcja PL wg `salesFramework`; bez kopiowania HTML źródła 1:1) oraz `specsPl` (wiersze techniczne DE→PL w tej samej kolejności co `specsDe`) 5. `applyBaseTranslationToDraft` → wstawia PL do draftu. 6. UI pokazuje: zdjęcia (6 zaznaczone), pola, walidacja. 6a. **Kolejka zdjęć w BL (max 16 slotów)** — to samo liczy `src/payloadImageOrder.js` i `public/payloadImageOrder.js`. `validateDraft` / `meta.validationIssues` (ostrzeżenie `payloadImages` gdy przekroczenie). Kreator i edytor po zapisie (PATCH) i **preview payloadu** (`/api/build-payload`) pokazują te same uwagi. 7. User klika **Zapisz do Base.com**: - `normalizeDraftForSave` → `validateDraft` (price=0 OK) - `buildBaseLinkerEnvelope` → `addInventoryProduct` z parametrami: - `text_fields.name` (PL) - `text_fields.description` (PL HTML) - `text_fields.features` (mapa 13 polskich parametrów) - `images` `{"0":"url:..."}` (max 16) - `stock` `{"bl_91023": 1}` - `prices` `{"54601": 0}` (cena 0, ustawiana ręcznie w BL) - jeśli `BASELINKER_SEND_HTTP=1` → POST do BL → `productId`. ## 6. ROZWIĄZANE BUGI (chronologicznie) 1. **Atrapy parametrów** — usunięty `getSourceAttributes` z hardcodem; teraz scrapowanie HTML. 2. **`images` w złym formacie** — naprawione na `{"0":"url:..."}` z prefiksem `url:`. 3. **OUTLET prefiks i badge zawsze** — teraz tylko gdy `outletMode=true`. 4. **4 wywołania AI per produkt** — zredukowane do 1 (`translateForBase`: jedna odpowiedź JSON na polską kartę Base z faktów, bez wklejania opisu DE). 5. **Strategiczna kategoria nie mapowała na ID** — dodane `series-to-category.json` z keyword→ID. 6. **`BASELINKER_INVENTORY_ID=244037`** — placeholder, prawdziwy to **62360**. 7. **`BASELINKER_WAREHOUSE_ID=bl_1`** — placeholder, prawdziwy to **bl_91023**. 8. **`BASELINKER_PRICE_GROUP=1`** — placeholder, prawdziwy to **54601**. 9. **Walidacja `price=0` blokowała live save** — naprawione w obu walidacjach (server + client) za pomocą `ZERO_IS_VALID_FOR`. 10. **SKU = `DYW-{timestamp}`** — zmienione na `DYW-{EAN}` (czytelne, unikalne, traceable). ## 7. PENDING / DECYZJE OTWARTE **Uwaga (terminologia vs kod):** w drafcie i API nadal występuje `aiMode: "translate-only"` oraz eksport `translateForBase` — to **nazwy historyczne**. Semantyka: jedno wywołanie OpenAI na **polską kartę magazynową z faktów** (`namePl`, `descriptionPlHtml`, `specsPl`), bez fallbacku na wklejenie `descriptionHtmlDe` ani `nameDe`, gdy AI zwróci puste pole (patrz `finalizeTranslationFromParsed` w `src/openai.js`). UI kreatora i prompty mówią jawnie o redakcji PL, nie o „tłumaczeniu 1:1”. - [x] **Auto etykiety premium (deterministyczna różnorodność) — etap wdrożony częściowo** - Model: `mainImageOverlay.mode/policyVersion/coveragePct` + `galleryBadgePlanByImageId`. - Policy: `config/badge-policy.json`, `config/badge-layout-policy.json`. - API: `generate-badge-plan`, `render-gallery-badges-from-plan`. - UI: kontrolki auto-plan w kreatorze i edytorze (mode/coverage + generate/re-render + szybka edycja per-image). - Render zdjęć: sharp + SVG (etykieta na JPEG źródłowym); brak „AI na pikselach” produktu. - [x] **Etap 8 uproszczony pod operacje 1-osobowe** - Usunięto bramkę rollout (allowlista / procenty) jako zbędną w bieżącym workflow. - Auto etykiety wybierane per produkt przez `manual/auto` i parametry planu. - Zachowany brak regresji flow BL (sloty, walidacje, payload). - [x] **Wyszukiwarka produktów po słowie** — gotowe (`dashboard.html` na `/editor`, legacy: `editor.html` na `/editor/legacy`; endpoint `/api/source/search`, adapter `searchTara`). - [x] **OCR etykiety (Gemini / OpenAI Vision)** — `GET /api/ocr-providers`, `POST /api/ocr-label-scan`, wybór providera w panelu; normalizacja w `src/labelOcrCommon.js`. - [x] **Tagi vs katalog BL** — `GET /api/tags` zwraca `baselinkerInventoryTags` (gdy BL odpowiada); podpowiedź w kreatorze przy wyborze tagów. - [x] **Kreator wielokrokowy** — `/editor/wizard` (`wizard.js`): podsumowanie z nawigacją, „Sprawdź payload”, podgląd HTML Base, regeneracja AI (pełna / Base / kanały). - [x] **Kolaż** — `collage.layout` `grid` \| `row`, endpoint `POST /api/drafts/:id/compose-collage`, moduł `src/collageComposer.js` (wymaga `sharp`). - [x] **PWA** — manifest + service worker pod `/`; instalacja „Dodaj do ekranu głównego” (nie sklep mobilny). - [x] **Lokalny indeks EAN / Artikelnummer (Tara)** — `npm run build:tara-index` → `data/tara-index.json`; `src/taraIndex.js` + `GET /api/source/search`; podpowiedź w dashboardzie przy pustym trafieniu z indeksu. - [x] **Wybór wariantu po kliku** — gotowe (endpoint `/api/source/variants`, adapter `getVariants`, panel w `editor.js`). User klika kartę → widzi listę kolor+rozmiar+EAN+cena PLN → wybiera dokładnie ten swój → draft. - [x] **Cena PLN automatyczna** — `priceEur × EUR_TO_PLN × PRICE_MULTIPLIER` (obecnie 3.5 × 1) w `src/draft.js::computePricePln`. Pole edytowalne w UI. - [x] **Lepszy prompt AI** — `buildBaseTranslationMessages` / `editor-config.json`: po polsku, SEO, bez marki Tara w treści, naturalne parametry; jawny zakaz kopiowania opisu marketingowego ze źródła 1:1 i fallback w kodzie bez wklejania `descriptionHtmlDe` ani `nameDe`, gdy AI nie zwróci pola. - [ ] **Adapter Merinos** — etykieta ELIO (firma Medipa Handels GmbH, EAN prefiks `4065656`) to inny dostawca, nie Tara. Wymaga osobnego adaptera lub generic EAN lookup. - [x] **`Stan: "używany"` w features gdy outletMode** — ustawiane w `src/baselinker.js` (`buildFeaturesMap`). - [x] **`extra_field_20298` (promocja / cena katalogowa PLN)** — gdy `compareAtPriceEur` > 0: wartość liczbowa PLN w polu z `BASELINKER_PROMO_PRICE_FIELD` (domyślnie `extra_field_20298`; pusty klucz = wyłączone). - [x] **Bulk import (Tara)** — panel na dashboardzie: wiele URL-i → `POST /api/bulk-import/start`, status `GET /api/bulk-import/status`, zapis kolejnych draftów (max 80 URL na przebieg). - [x] **Auto-publikacja na Allegro/OLX z BL** — instrukcja operatorska [`docs/AUTO-PUBLISH-BL.md`](AUTO-PUBLISH-BL.md); apka domyślnie wysyła copy kanałowe (`generateMarketplaceChannels: true`), mapowanie OLX → `description_extra2`; ostrzeżenia `PUBLISH_*` przed wystawieniem; Studio otwiera panel publikacji po zapisie. Reguły automatyczne — konfiguracja w panelu BL (poza repo). - [x] **Podpowiedź cenowa** — `meta.pricingHint` w drafcie: aktualna PLN z `priceEur`, katalogowa PLN z `compareAtPriceEur` (kreator krok 5 pokazuje baner). ## 8. SZUKANIE PO EAN / Artikelnummer (lokalny indeks) Publiczne `/search/suggest.json` u Tary indeksuje tylko **nazwę** — frazy typu EAN/Artikel z suggest **nie** działają. Dodany jest **lokalny plik** `data/tara-index.json` (nie w repozytorium — generowany u Ciebie), serwowany przez `src/taraIndex.js`. **Budowa / odświeżanie indeksu** (ręcznie lub cron, np. raz dziennie): ```powershell npm run build:tara-index ``` Co robi skrypt `scripts/tara-build-index.mjs`: 1. Pobiera katalog: `https://tara-carpet.com/products.json?limit=250&page=N`. 2. Dla każdego produktu: HTML `/products/{handle}` → `parseSpecsFromHtml` (Artikelnummer + wiersze EAN/GTIN; dodatkowo kody z `variant.barcode` w JSON). 3. Zapisuje `data/tara-index.json` z polami `byEan`, `byArtikel` (karty zgodne z wynikami wyszukiwania: `title`, `handle`, `url`, ceny, obraz). **API** `GET /api/source/search`: - `q` = wyłącznie cyfry (8–14) lub wzorzec `\d+-\d+-\d+` **oraz** istniejący plik indeksu → wynik z indeksu; pusty wynik → `searchSource: "tara-index-empty"` (dashboard pokazuje podpowiedź). - Inaczej → suggest sklepu (publiczne API wyszukiwania) + `searchSource: "storefront-suggest"`. **Uwaga**: przy duplikatach EAN/Artikel na sklepie ostatni produkt w przebiegu budowy nadpisuje klucz (log ostrzeżeń w konsoli). ## 9. JAK URUCHOMIĆ (czysty start) ```powershell cd c:\xampp\htdocs\dywanowy-bazar-ext npm install # sharp — potrzebny m.in. do kolażu node server.js ``` Domyślnie: `http://127.0.0.1:8787` (port `PORT` w `.env`). | Adres | Opis | | --- | --- | | `/editor` | Dashboard (wyszukiwarka, OCR, lista draftów) | | `/editor/wizard` | Kreator wielokrokowy | | `/editor/legacy` | Pojedynczy edytor (`editor.html`) | Przykładowy flow: 1. URL produktu → `https://tara-carpet.com/products/?variant=` (z wyszukiwarki lub wklejony). 2. **Pobierz draft** / przejście kroków w kreatorze. 3. **Odśwież preview payloadu** / „Sprawdź payload” (JSON zgodny z BL). 4. **Zapisz do Base.com** (live save, jeśli `BASELINKER_SEND_HTTP=1`). Testy automatyczne: `npm test`. **Indeks wyszukiwania po kodzie (EAN/Artikel)** — po świeżym deployu lub pierwszym klonie repozytorum uruchom na hoście z Node: ```powershell npm run build:tara-index ``` (plik `data/tara-index.json` jest w `.gitignore`). Diagnostyka BL: ```powershell node scripts/bl-inspect.js ``` ## 10. PROMPT DLA NOWEGO CZATU CURSOR > Pracuję nad importerem dywanów z tara-carpet.com (sklep DE, publiczny storefront JSON) do BaseLinker > (inventory 62360, magazyn bl_91023, account user). Pełen aktualny stan projektu i > wszystkie zweryfikowane wartości BL są w `docs/PROGRESS.md`. **Przed zmianą czegokolwiek > przeczytaj ten plik i nie wymyślaj wartości** (kategorie, magazyn, price group itd.). > Pipeline jest sprawdzony live (Madrid 4062381018421 zapisał się jako productId w BL). > Zależność produkcyjna: `sharp` (npm). Silnik: Node.js ≥20 (patrz `package.json`).