# Časté problémy s balíčkem a jejich řešení

> Šestnáct nejčastějších potíží při nasazení doplňku Sestav si vlastní balíček ve formátu problém → proč se děje → jak to spravit.

- **Zdroj:** https://www.webotvurci.cz/navody/sestav-si-vlastni-balicek/caste-problemy-s-balickem-a-jejich-reseni/
- **Doplněk / kategorie:** Sestav si vlastní balíček (https://www.webotvurci.cz/navody/sestav-si-vlastni-balicek/)
- **Datum:** 2026-08-12
- **Autor:** Webotvůrci (https://www.webotvurci.cz/)

---

Většina hlášení typu „doplněk Shoptet nefunguje" má jednu z několika opakujících se příčin.
Sesbírali jsme je z reálných nasazení a seřadili podle toho, jak často je vidíme.

Než začnete hledat v tomhle seznamu, projděte tři kroky. Odfiltrují většinu případů za minutu:

1. **DevTools → Network, filtr `multipack`.** Načetly se `multipack.js` a `multipack.css` se
   statusem 200? Proběhlo načtení konfigurace a jaký vrátilo status?
2. **DevTools → Console.** Je tam `Uncaught`? Chyba v renderu vypadá zvenku úplně stejně jako
   „skript se nenačetl".
3. **Podívejte se na skutečnou URL requestu.** Neobsahuje `/null` nebo `/undefined`? Je v ní
   shortcode **bez** hranatých závorek?

Příklady vycházejí ze smyšleného e-shopu `www.example.cz` (`projectId` **123456**) a konfigurace
**„Kávový balíček"** se shortcodem `w-multipack-ABC12` — 3 sloty, sleva 15 %.

## Widget se nevykreslí

### 1. Na stránce není vůbec nic

Skript se buď nenačetl, nebo se načetl a spadl při renderu. V Network musí `multipack.js` vrátit
200 — status 404 znamená překlep v cestě `/user/documents/…` nebo soubor nahraný o adresář jinam.
Žádný request znamená, že HTML vložka není uložená, nebo je uložená jen pro jinou jazykovou mutaci
či doménu e-shopu.

Když request projde a přesto se nic nevykreslí, otevřete konzoli. Typické pády jsou na `null`
v poli `url` nebo `default_category` a na skladovosti zpracované jako číslo — `stock` chodí jako
string `"12.000"`, takže potřebuje `parseFloat`.

### 2. Na stránce je vidět holý text shortcodu

Skript nenašel shortcode v podobě, kterou hledá — buď se vůbec nenačetl (viz bod výš), nebo se
text shortcodu ve stránce liší od očekávaného tvaru. Rychlá kontrola: `Ctrl+U` a vyhledat
`w-multipack` ve zdrojovém kódu stránky.

### 3. WYSIWYG editor shortcode rozbil

Nejčastější příčina předchozího bodu. Editor obsahu si text rád „opraví" — a rozdělený shortcode
už neodpovídá vzoru, který skript hledá.

| Co v HTML uvidíte | Co se stalo |
|---|---|
| `&#91;w-multipack-ABC12&#93;` | editor převedl hranaté závorky na entity |
| `[w-multipack-<span>ABC12</span>]` | text se rozpadl na víc uzlů, typicky po vložení z Wordu |
| `[w-multipack-ABC12]` s `&nbsp;` uvnitř | nezlomitelná mezera z formátování |
| `[w-multipack-abc12]` | malá písmena — shortcode je case-sensitive, tvar je `[A-Z0-9]{5}` |

Spolehlivé řešení je vždycky stejné: v editoru přepnout do režimu zdrojového kódu, shortcode
smazat a napsat ručně jako prostý text na samostatný řádek. Pak uložit a zkontrolovat
přes `Ctrl+U`.

### 4. Změna JS nebo CSS se na e-shopu neprojevila

Soubory z `/user/documents/` jdou přes CDN a browser cache s dlouhým TTL, takže samotné nahrání
nové verze nestačí. Deploy je dvoukrokový: nahrát soubor **a** zvýšit parametr `?v=` v HTML
vložce.

```html
<link rel="stylesheet" href="https://www.webotvurci.cz/user/documents/multipack/multipack.css?v=1.0.3">
<script src="https://www.webotvurci.cz/user/documents/multipack/multipack.js?v=1.0.3" defer></script>
```

Verze zvyšujte u obou souborů společně. Rozjeté verze (CSS `1.02`, JS `1.07`) jsou klasický zdroj
hlášky „u mě to vypadá jinak než u tebe".

## Chyby z API

### 5. Načtení konfigurace vrací 404

Pro dvojici `projectId` + shortcode neexistuje konfigurace. Zkontrolujte tři věci: že je
v URL shortcode **bez** hranatých závorek, že testujete na e-shopu, kde je konfigurace opravdu
založená, a že ji obchodník mezitím nesmazal — smazáním konfigurace shortcode přestává platit.

Drobnost, na kterou se hodí myslet dopředu: tělo téhle odpovědi je prostý JSON řetězec, ne objekt.
Ve svém error handleru s tím počítejte, ať vám hláška pro zákazníka nevyjde jako `undefined`.

### 6. Vytvoření sady vrací 422

Odeslaný výběr neprošel validací. Prakticky vždy jde o jednu ze tří věcí: počet GUIDů neodpovídá
`product_count` (musí jich být **přesně** tolik), shortcode nemá 17 znaků, nebo některý GUID nemá
36 znaků.

Řešení je preventivní — odesílejte až po kompletním výběru a do té doby držte tlačítko `disabled`.
Tělo 422 je objekt s klíči `guids` a `shortcode`; hlášky jsou anglicky a nejsou určené koncovému
zákazníkovi, takže podle klíče zobrazte vlastní českou hlášku.

### 7. V URL requestu se objevuje `/null`

Cenová hladina je volitelný segment cesty. Naivní skládání URL ho vyrobí i tehdy, když žádná
hladina není:

```js
// špatně – v URL vznikne "/null"
const url = `${base}/configuration-products/${shortcode}/${ctx.customer?.priceListId ?? null}`;

// správně – segment se prostě vynechá
const id = Number.isInteger(ctx.customer?.priceListId) ? ctx.customer.priceListId : null;
const url = `${base}/configuration-products/${shortcode}` + (id !== null ? `/${id}` : '');
```

Pozor při vývoji: na některých e-shopech vrací dataLayer `priceListId` i nepřihlášeným
návštěvníkům, takže se chyba na testovacím účtu vůbec nemusí projevit.

## Ceny a měny

### 8. Cena v košíku se liší od ceny ve widgetu

Widget zobrazuje vlastní orientační výpočet z listingových cen, ale závazná je cena spočítaná
backendem. Rozejít se můžou hlavně u produktů s variantami a u akčních cen — listing pracuje
s nejnižší cenou napříč variantami, sada se počítá z ceny první varianty v základní měně e-shopu.

Řešení: po úspěšném vytvoření sady přepište zobrazenou cenu hodnotou `price` z odpovědi a během
výběru cenu označte jako orientační. Produkty, které bývají v akci, do balíčku raději nezařazujte —
nebo si to předem vyjasněte s obchodníkem.

### 9. Přihlášený B2B zákazník vidí jiné ceny

Cenová hladina ovlivňuje ceny **ve výpisu produktů**, ze kterého zákazník vybírá. Sada se pak
zakládá z běžných cen se slevou nastavenou v konfiguraci.

Na e-shopech s aktivními cenovými hladinami to řešte domluvou s obchodníkem — balíček se pro B2B
segment buď nenabízí, nebo se sleva nastaví tak, aby dávala smysl i pro něj. Ve widgetu pak vždy
zobrazte závaznou cenu z odpovědi.

### 10. E-shop s víc měnami ukazuje částku v korunách

Závazná cena sady je vždy v základní měně e-shopu, zatímco listing je přepočtený kurzem podle
měny, kterou má zákazník přepnutou. Zákazníkovi v eurech tedy nesmíte tuhle hodnotu zobrazit jako
částku v jeho měně.

Máte dvě varianty: buď ji přepočítat kurzem z dataLayeru a označit jako orientační, nebo cenu ve
widgetu nepřepisovat a spolehnout se na přepočet v košíku Shoptetu. Starter na to má přepínač
`useApiPriceInSummary`. A nehardcodujte cenový klíč:

```js
const priceKey = 'price_' + (ctx.currency || 'CZK');   // ne: product.price_CZK
const price = product[priceKey];
```

### 11. Produkt se zobrazuje za 0 Kč

Produkt není zařazený v cenové hladině přihlášeného zákazníka, takže k němu nepřichází cena.
Nulu nikdy nezobrazujte jako platnou cenu a nepouštějte ji do součtu — produkt v nabídce buď
skryjte, nebo místo ceny napište „cena na dotaz".

## Košík a odesílání

### 12. Dvojklik založí dvě sady

Chybí ochrana proti dvojímu odeslání. Každé odeslání zakládá v katalogu reálný produkt, takže
dvojklik znamená dva produkty — a když se navíc mezi kliknutími změní pořadí GUIDů, vyhodnotí se
to jako jiná kombinace.

Potřebujete tři věci najednou: single-flight guard (druhý klik dostane tentýž `Promise`),
`disabled` na tlačítku a viditelný spinner. Samotné `disabled` nestačí, protože mezi kliknutím
a nastavením atributu je několik milisekund. A GUIDy vždycky řaďte:

```js
const guids = selection.map((p) => p.guid).slice().sort();
```

`.slice()` tam nechte — `sort()` mutuje pole in-place a rozhodil by vám pořadí slotů v UI.

### 13. `shoptet is not defined`

Váš skript běží dřív než šablonové skripty Shoptetu. Typicky proto, že je vložený do hlavičky
nebo bez atributu `defer`.

Skript patří do patičkové HTML vložky s `defer`, init navažte na `DOMContentLoaded` a dostupnost
si před voláním ověřte:

```js
if (typeof shoptet !== 'undefined' && shoptet.cartShared?.addToCart) {
    shoptet.cartShared.addToCart({ productCode: data.code, amount: quantity });
}
```

Když funkce chybí, zobrazte srozumitelnou chybu místo tichého selhání. Vlastním `fetch`em na
košíkové akce to neobcházejte — blokuje ho CSRF ochrana Shoptetu.

### 14. Sada zmizela z katalogu a kód přestal platit

**Tohle není chyba, ale záměrné chování.** Vygenerované sady jsou produkty vytvořené na míru
jednomu nákupu. Každou noc proto běží úklid, který smaže každou sadu, ke které neexistuje
objednávka — jinak by e-shopu za pár měsíců zaplevelily katalog stovky jednorázových produktů.

Prakticky to znamená, že kód sady platí nejdéle do nejbližší půlnoci. Neukládejte ho do
`localStorage`, do sdílených odkazů ani do e-mailů. Ukládejte si **GUIDy** vybraných produktů
a při návratu zákazníka si o sadu požádejte znovu — stejná kombinace vrátí existující sadu, jinak
se založí nová. Objednané sady zůstávají.

## Vzhled a UX

### 15. Obrázky produktů se nenačítají

Pole `image` není URL, ale název souboru včetně podepsaného query stringu. Nejčastější chyba je,
že se query string odstraní nebo znovu zaenkóduje, případně se použije hostname stránky místo
hostname z `product.url`.

```js
const host = product.url ? new URL(product.url).hostname : window.location.hostname;
const src  = `https://cdn.myshoptet.com/usr/${host}/user/shop/orig/${product.image}`;
```

Query string nechte beze změny a počítejte s tím, že `image` může chybět — mějte připravený
placeholder.

### 16. Widget rozbil layout šablony

Globální CSS. Pravidla na holých selektorech (`h2`, `select`, `.rating-star`, `:root`) se propíšou
do celého e-shopu, protože widget běží uvnitř cizí šablony.

Všechna pravidla zanořte pod kořenovou třídu widgetu (`.wt-multipack …`), včetně věcí jako
`scroll-behavior` nebo `max-width` na kontejnerech. Obrázkům nastavte `width`/`height` nebo
`aspect-ratio`, ať vám při načítání neskáče layout.

### 17. Vyprodaný produkt se dostane do sady

Hlídat, co je v nabídce, je úkol frontendu — a je to jedna z věcí, které stojí za pět minut práce
navíc. Vyprodané produkty ve výběru zablokujte nebo skryjte a nezapomeňte, že `stock` je string:

```js
const available = parseFloat(product.stock) > 0;
```

Finální kontrola dostupnosti pak proběhne v košíku Shoptetu podle nastavení e-shopu, ale zákazník
by se do ní neměl dostat překvapením.

### 18. Počet slotů nesedí nebo se sloty přehazují

Počet slotů je odvozený z `column_number` místo z `product_count`, nebo se kód spoléhá na pořadí,
ve kterém kategorie dorazí.

Autoritativní je vždy `product_count` (2–5). Kategorie si seřaďte sami — podle `column_number`,
pak podle `name` s `localeCompare(…, 'cs')`. Totéž platí pro pořadí produktů uvnitř kategorie.

## Kdy nám napsat

Když nic z výše uvedeného nepomůže, napište na
[jsme@webotvurci.cz](mailto:jsme@webotvurci.cz). Ať to nemusíme řešit na dvakrát, přiložte:

- **URL stránky** s widgetem a `projectId` i shortcode konfigurace,
- **screenshot konzole a záložky Network** — ideálně s viditelnou URL requestu, status kódem
  a tělem odpovědi,
- **čas i s časovou zónou**, kdy se to stalo,
- **co jste už zkoušeli** a jestli se to děje pořád, nebo jen občas.

Bez `projectId` a shortcodu se problém na naší straně nedohledá.

## Kam dál

- [FAQ a řešení problémů na GitHubu](https://github.com/Webotvurci-s-r-o/shoptet-premium-multipack/blob/main/docs/06-faq-a-reseni-problemu.md) — stejný index s odkazy do detailní dokumentace.
- [Startovací implementace `examples/starter`](https://github.com/Webotvurci-s-r-o/shoptet-premium-multipack/tree/main/examples/starter) — funkční widget, který má většinu těchhle pastí ošetřenou.
- [Sestav si vlastní balíček](https://www.webotvurci.cz/sluzby/shoptet/shoptet-premium-e-shop-bez-limitu/dynamicke-sady-balicky-na-miru/) — co doplněk umí, ceník a živá ukázka.

Potřebujete s nasazením pomoct nebo si nechat zkontrolovat hotovou implementaci? Ozvěte se na
[jsme@webotvurci.cz](mailto:jsme@webotvurci.cz).

---

*Návod pochází z webu Webotvůrci — Zlatý a Premium partner Shoptetu.*
*Všechny návody: https://www.webotvurci.cz/navody/ · Kontakt: jsme@webotvurci.cz*
