@bacano/sdk 1.2.5 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/react.cjs CHANGED
@@ -259,6 +259,12 @@ var GET_PRODUCTS = (
259
259
  id
260
260
  name
261
261
  description
262
+ product_website_publications(
263
+ where: { website_id: { _eq: $websiteId }, is_visible: { _eq: true } }
264
+ limit: 1
265
+ ) {
266
+ show_when_out_of_stock
267
+ }
262
268
  long_description
263
269
  slug
264
270
  seo_title
@@ -494,6 +500,12 @@ var GET_PRODUCT_BY_PK = (
494
500
  limit: 1
495
501
  ) {
496
502
  id
503
+ product_website_publications(
504
+ where: { website_id: { _eq: $websiteId }, is_visible: { _eq: true } }
505
+ limit: 1
506
+ ) {
507
+ show_when_out_of_stock
508
+ }
497
509
  product_option_group_members(limit: 1) {
498
510
  group {
499
511
  id
@@ -743,6 +755,12 @@ var GET_PRODUCT_SEO_ENTRIES = (
743
755
  id
744
756
  name
745
757
  slug
758
+ product_website_publications(
759
+ where: { website_id: { _eq: $websiteId }, is_visible: { _eq: true } }
760
+ limit: 1
761
+ ) {
762
+ show_when_out_of_stock
763
+ }
746
764
  seo_title
747
765
  seo_description
748
766
  seo_image_file_id
@@ -2376,6 +2394,7 @@ var GET_PRODUCT_FILTERS = (
2376
2394
  query GetProductFilters(
2377
2395
  $companyId: uuid!
2378
2396
  $chainId: uuid!
2397
+ $websiteId: uuid!
2379
2398
  $where: products_bool_exp = {}
2380
2399
  $attributeListKey: String!
2381
2400
  ) {
@@ -2383,6 +2402,12 @@ var GET_PRODUCT_FILTERS = (
2383
2402
  where: {
2384
2403
  _and: [
2385
2404
  { company_id: { _eq: $companyId } }
2405
+ {
2406
+ product_website_publications: {
2407
+ website_id: { _eq: $websiteId }
2408
+ is_visible: { _eq: true }
2409
+ }
2410
+ }
2386
2411
  {
2387
2412
  chain_products: {
2388
2413
  chain_id: { _eq: $chainId }
@@ -2395,6 +2420,12 @@ var GET_PRODUCT_FILTERS = (
2395
2420
  }
2396
2421
  ) {
2397
2422
  id
2423
+ product_website_publications(
2424
+ where: { website_id: { _eq: $websiteId }, is_visible: { _eq: true } }
2425
+ limit: 1
2426
+ ) {
2427
+ show_when_out_of_stock
2428
+ }
2398
2429
  category
2399
2430
  subcategory
2400
2431
  color
@@ -2502,6 +2533,7 @@ var GET_SEARCH_SUGGESTIONS = (
2502
2533
  query GetSearchSuggestions(
2503
2534
  $companyId: uuid!
2504
2535
  $chainId: uuid!
2536
+ $websiteId: uuid!
2505
2537
  $term: String!
2506
2538
  $productLimit: Int = 5
2507
2539
  $brandLimit: Int = 3
@@ -2509,6 +2541,10 @@ var GET_SEARCH_SUGGESTIONS = (
2509
2541
  products(
2510
2542
  where: {
2511
2543
  company_id: { _eq: $companyId }
2544
+ product_website_publications: {
2545
+ website_id: { _eq: $websiteId }
2546
+ is_visible: { _eq: true }
2547
+ }
2512
2548
  chain_products: {
2513
2549
  chain_id: { _eq: $chainId }
2514
2550
  is_enabled: { _eq: true }
@@ -2521,6 +2557,29 @@ var GET_SEARCH_SUGGESTIONS = (
2521
2557
  ) {
2522
2558
  id
2523
2559
  name
2560
+ product_website_publications(
2561
+ where: { website_id: { _eq: $websiteId }, is_visible: { _eq: true } }
2562
+ limit: 1
2563
+ ) {
2564
+ show_when_out_of_stock
2565
+ }
2566
+ product_variants(where: { is_disabled: { _eq: false } }) {
2567
+ id
2568
+ product_variant_prices(
2569
+ where: {
2570
+ price_type: { _eq: CLIENT }
2571
+ _or: [
2572
+ { chain_id: { _eq: $chainId } }
2573
+ { chain_id: { _is_null: true } }
2574
+ ]
2575
+ }
2576
+ ) {
2577
+ id
2578
+ price
2579
+ price_type
2580
+ chain_id
2581
+ }
2582
+ }
2524
2583
  }
2525
2584
  brands(
2526
2585
  where: {
@@ -3002,6 +3061,7 @@ function mapProduct(raw, storageUrl, labels, chainId) {
3002
3061
  createdAt: raw.created_at,
3003
3062
  updatedAt: raw.updated_at ?? raw.created_at,
3004
3063
  variants,
3064
+ outOfStockVisibilityOverride: raw.product_website_publications?.[0]?.show_when_out_of_stock ?? null,
3005
3065
  detailVariants,
3006
3066
  media: raw.product_media.map((m) => mapMedia(m, storageUrl)),
3007
3067
  attributes: raw.product_attributes.map(
@@ -3363,7 +3423,7 @@ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variant
3363
3423
  }
3364
3424
  return availabilityByVariantId;
3365
3425
  }
3366
- async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
3426
+ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId, showOutOfStockByDefault = false, onlyInStock = false) {
3367
3427
  const variantIds = products.flatMap(
3368
3428
  (product) => product.variants.map((variant) => variant.id)
3369
3429
  );
@@ -3389,11 +3449,11 @@ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, c
3389
3449
  pricing: resolveProductPricing(prices)
3390
3450
  };
3391
3451
  }).filter(
3392
- (variant) => inStockVariantIds.has(variant.id) && variant.pricing.currentPrice != null
3452
+ (variant) => (inStockVariantIds.has(variant.id) || !onlyInStock && (product.outOfStockVisibilityOverride ?? showOutOfStockByDefault)) && variant.pricing.currentPrice != null
3393
3453
  )
3394
3454
  })).filter((product) => product.variants.length > 0);
3395
3455
  }
3396
- async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
3456
+ async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId, showOutOfStockByDefault = false) {
3397
3457
  const variantIds = products.flatMap(
3398
3458
  (product) => product.product_variants.map((variant) => variant.id)
3399
3459
  );
@@ -3415,7 +3475,7 @@ async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products
3415
3475
  const pricing = resolveProductPricing(
3416
3476
  selectPublicPrices(prices, chainId)
3417
3477
  );
3418
- return inStockVariantIds.has(variant.id) && pricing.currentPrice != null;
3478
+ return (inStockVariantIds.has(variant.id) || (product.product_website_publications?.[0]?.show_when_out_of_stock ?? showOutOfStockByDefault)) && pricing.currentPrice != null;
3419
3479
  })
3420
3480
  );
3421
3481
  }
@@ -3486,7 +3546,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3486
3546
  getHeaders,
3487
3547
  [mappedProduct],
3488
3548
  ctx.chainId,
3489
- ctx.websiteId
3549
+ ctx.websiteId,
3550
+ ctx.config?.show_out_of_stock_products === true
3490
3551
  );
3491
3552
  if (!filteredProducts[0]) return null;
3492
3553
  const specifications = await loadProductSpecifications([product.id]);
@@ -3719,7 +3780,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3719
3780
  getHeaders,
3720
3781
  data.products ?? [],
3721
3782
  ctx.chainId,
3722
- ctx.websiteId
3783
+ ctx.websiteId,
3784
+ ctx.config?.show_out_of_stock_products === true
3723
3785
  );
3724
3786
  const entries = availableProducts.slice(0, limit).map((product) => mapProductSeoEntry(product, storageUrl)).filter((entry) => Boolean(entry));
3725
3787
  return {
@@ -3796,7 +3858,9 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3796
3858
  (p) => mapProduct(p, storageUrl, publicAttributeLabels, chainId)
3797
3859
  ),
3798
3860
  chainId,
3799
- ctx.websiteId
3861
+ ctx.websiteId,
3862
+ ctx.config?.show_out_of_stock_products === true,
3863
+ opts?.inStock === true
3800
3864
  );
3801
3865
  const specifications = await loadProductSpecifications(
3802
3866
  products.map((product) => product.id)
@@ -3903,6 +3967,7 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3903
3967
  {
3904
3968
  companyId: ctx.companyId,
3905
3969
  chainId,
3970
+ websiteId: ctx.websiteId,
3906
3971
  attributeListKey: DEFAULT_FILTER_ATTRIBUTE_LIST_KEY,
3907
3972
  where: buildProductWhere(opts.filters ?? {}, chainId)
3908
3973
  },
@@ -3926,7 +3991,7 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3926
3991
  variant.product_variant_prices.map(mapPrice),
3927
3992
  chainId
3928
3993
  );
3929
- return prices.length > 0 && (availabilityByVariantId.get(variant.id)?.inStock ?? false);
3994
+ return prices.length > 0 && ((availabilityByVariantId.get(variant.id)?.inStock ?? false) || (product.product_website_publications?.[0]?.show_when_out_of_stock ?? ctx.config?.show_out_of_stock_products === true));
3930
3995
  });
3931
3996
  if (!hasVisibleVariant) continue;
3932
3997
  applicableCategories.add(product.category);
@@ -4014,6 +4079,7 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4014
4079
  {
4015
4080
  companyId: ctx.companyId,
4016
4081
  chainId,
4082
+ websiteId: ctx.websiteId,
4017
4083
  attributeListKey: opts?.attributeListKey ?? DEFAULT_FILTER_ATTRIBUTE_LIST_KEY,
4018
4084
  where: {
4019
4085
  ...opts?.categoryName ? { category: { _eq: opts.categoryName } } : {},
@@ -4068,7 +4134,9 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4068
4134
  chainId
4069
4135
  ),
4070
4136
  inStock: availabilityByVariantId.get(variant.id)?.inStock ?? false
4071
- })).filter((variant) => variant.inStock && variant.prices.length > 0);
4137
+ })).filter(
4138
+ (variant) => (variant.inStock || (product.product_website_publications?.[0]?.show_when_out_of_stock ?? ctx.config?.show_out_of_stock_products === true)) && variant.prices.length > 0
4139
+ );
4072
4140
  if (visibleVariants.length === 0) continue;
4073
4141
  categoryCounts.set(
4074
4142
  product.category,
@@ -4153,6 +4221,7 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4153
4221
  "NO_CHAIN"
4154
4222
  );
4155
4223
  }
4224
+ const chainId = ctx.chainId;
4156
4225
  const term = `%${opts.query}%`;
4157
4226
  const limit = opts.limit ?? 8;
4158
4227
  const data = await executeGraphQL(
@@ -4160,7 +4229,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4160
4229
  GET_SEARCH_SUGGESTIONS,
4161
4230
  {
4162
4231
  companyId: ctx.companyId,
4163
- chainId: ctx.chainId,
4232
+ chainId,
4233
+ websiteId: ctx.websiteId,
4164
4234
  term,
4165
4235
  productLimit: Math.min(limit, 5),
4166
4236
  brandLimit: Math.max(limit - 5, 3)
@@ -4168,8 +4238,29 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4168
4238
  getHeaders
4169
4239
  );
4170
4240
  const suggestions = [];
4241
+ const availability = await getWebsiteAvailabilityByVariantId(
4242
+ graphqlUrl,
4243
+ getHeaders,
4244
+ (data.products ?? []).flatMap(
4245
+ (product) => product.product_variants.map((variant) => variant.id)
4246
+ ),
4247
+ chainId,
4248
+ ctx.websiteId
4249
+ );
4171
4250
  for (const p of data.products ?? []) {
4172
- suggestions.push({ type: "product", text: p.name, id: p.id });
4251
+ const showWithoutStock = p.product_website_publications?.[0]?.show_when_out_of_stock ?? ctx.config?.show_out_of_stock_products === true;
4252
+ const isVisible = p.product_variants.some((variant) => {
4253
+ const hasPrice = resolveProductPricing(
4254
+ selectPublicPrices(
4255
+ variant.product_variant_prices.map(mapPrice),
4256
+ chainId
4257
+ )
4258
+ ).currentPrice != null;
4259
+ return hasPrice && (showWithoutStock || availability.get(variant.id)?.inStock === true);
4260
+ });
4261
+ if (isVisible) {
4262
+ suggestions.push({ type: "product", text: p.name, id: p.id });
4263
+ }
4173
4264
  }
4174
4265
  for (const b of data.brands ?? []) {
4175
4266
  suggestions.push({ type: "brand", text: b.name, id: b.id });
package/react.js CHANGED
@@ -202,6 +202,12 @@ var GET_PRODUCTS = (
202
202
  id
203
203
  name
204
204
  description
205
+ product_website_publications(
206
+ where: { website_id: { _eq: $websiteId }, is_visible: { _eq: true } }
207
+ limit: 1
208
+ ) {
209
+ show_when_out_of_stock
210
+ }
205
211
  long_description
206
212
  slug
207
213
  seo_title
@@ -437,6 +443,12 @@ var GET_PRODUCT_BY_PK = (
437
443
  limit: 1
438
444
  ) {
439
445
  id
446
+ product_website_publications(
447
+ where: { website_id: { _eq: $websiteId }, is_visible: { _eq: true } }
448
+ limit: 1
449
+ ) {
450
+ show_when_out_of_stock
451
+ }
440
452
  product_option_group_members(limit: 1) {
441
453
  group {
442
454
  id
@@ -686,6 +698,12 @@ var GET_PRODUCT_SEO_ENTRIES = (
686
698
  id
687
699
  name
688
700
  slug
701
+ product_website_publications(
702
+ where: { website_id: { _eq: $websiteId }, is_visible: { _eq: true } }
703
+ limit: 1
704
+ ) {
705
+ show_when_out_of_stock
706
+ }
689
707
  seo_title
690
708
  seo_description
691
709
  seo_image_file_id
@@ -2319,6 +2337,7 @@ var GET_PRODUCT_FILTERS = (
2319
2337
  query GetProductFilters(
2320
2338
  $companyId: uuid!
2321
2339
  $chainId: uuid!
2340
+ $websiteId: uuid!
2322
2341
  $where: products_bool_exp = {}
2323
2342
  $attributeListKey: String!
2324
2343
  ) {
@@ -2326,6 +2345,12 @@ var GET_PRODUCT_FILTERS = (
2326
2345
  where: {
2327
2346
  _and: [
2328
2347
  { company_id: { _eq: $companyId } }
2348
+ {
2349
+ product_website_publications: {
2350
+ website_id: { _eq: $websiteId }
2351
+ is_visible: { _eq: true }
2352
+ }
2353
+ }
2329
2354
  {
2330
2355
  chain_products: {
2331
2356
  chain_id: { _eq: $chainId }
@@ -2338,6 +2363,12 @@ var GET_PRODUCT_FILTERS = (
2338
2363
  }
2339
2364
  ) {
2340
2365
  id
2366
+ product_website_publications(
2367
+ where: { website_id: { _eq: $websiteId }, is_visible: { _eq: true } }
2368
+ limit: 1
2369
+ ) {
2370
+ show_when_out_of_stock
2371
+ }
2341
2372
  category
2342
2373
  subcategory
2343
2374
  color
@@ -2445,6 +2476,7 @@ var GET_SEARCH_SUGGESTIONS = (
2445
2476
  query GetSearchSuggestions(
2446
2477
  $companyId: uuid!
2447
2478
  $chainId: uuid!
2479
+ $websiteId: uuid!
2448
2480
  $term: String!
2449
2481
  $productLimit: Int = 5
2450
2482
  $brandLimit: Int = 3
@@ -2452,6 +2484,10 @@ var GET_SEARCH_SUGGESTIONS = (
2452
2484
  products(
2453
2485
  where: {
2454
2486
  company_id: { _eq: $companyId }
2487
+ product_website_publications: {
2488
+ website_id: { _eq: $websiteId }
2489
+ is_visible: { _eq: true }
2490
+ }
2455
2491
  chain_products: {
2456
2492
  chain_id: { _eq: $chainId }
2457
2493
  is_enabled: { _eq: true }
@@ -2464,6 +2500,29 @@ var GET_SEARCH_SUGGESTIONS = (
2464
2500
  ) {
2465
2501
  id
2466
2502
  name
2503
+ product_website_publications(
2504
+ where: { website_id: { _eq: $websiteId }, is_visible: { _eq: true } }
2505
+ limit: 1
2506
+ ) {
2507
+ show_when_out_of_stock
2508
+ }
2509
+ product_variants(where: { is_disabled: { _eq: false } }) {
2510
+ id
2511
+ product_variant_prices(
2512
+ where: {
2513
+ price_type: { _eq: CLIENT }
2514
+ _or: [
2515
+ { chain_id: { _eq: $chainId } }
2516
+ { chain_id: { _is_null: true } }
2517
+ ]
2518
+ }
2519
+ ) {
2520
+ id
2521
+ price
2522
+ price_type
2523
+ chain_id
2524
+ }
2525
+ }
2467
2526
  }
2468
2527
  brands(
2469
2528
  where: {
@@ -2945,6 +3004,7 @@ function mapProduct(raw, storageUrl, labels, chainId) {
2945
3004
  createdAt: raw.created_at,
2946
3005
  updatedAt: raw.updated_at ?? raw.created_at,
2947
3006
  variants,
3007
+ outOfStockVisibilityOverride: raw.product_website_publications?.[0]?.show_when_out_of_stock ?? null,
2948
3008
  detailVariants,
2949
3009
  media: raw.product_media.map((m) => mapMedia(m, storageUrl)),
2950
3010
  attributes: raw.product_attributes.map(
@@ -3306,7 +3366,7 @@ async function getWebsiteAvailabilityByVariantId(graphqlUrl, getHeaders, variant
3306
3366
  }
3307
3367
  return availabilityByVariantId;
3308
3368
  }
3309
- async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
3369
+ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId, showOutOfStockByDefault = false, onlyInStock = false) {
3310
3370
  const variantIds = products.flatMap(
3311
3371
  (product) => product.variants.map((variant) => variant.id)
3312
3372
  );
@@ -3332,11 +3392,11 @@ async function filterProductsForWebsiteStock(graphqlUrl, getHeaders, products, c
3332
3392
  pricing: resolveProductPricing(prices)
3333
3393
  };
3334
3394
  }).filter(
3335
- (variant) => inStockVariantIds.has(variant.id) && variant.pricing.currentPrice != null
3395
+ (variant) => (inStockVariantIds.has(variant.id) || !onlyInStock && (product.outOfStockVisibilityOverride ?? showOutOfStockByDefault)) && variant.pricing.currentPrice != null
3336
3396
  )
3337
3397
  })).filter((product) => product.variants.length > 0);
3338
3398
  }
3339
- async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId) {
3399
+ async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products, chainId, websiteId, showOutOfStockByDefault = false) {
3340
3400
  const variantIds = products.flatMap(
3341
3401
  (product) => product.product_variants.map((variant) => variant.id)
3342
3402
  );
@@ -3358,7 +3418,7 @@ async function filterSeoProductsForWebsiteStock(graphqlUrl, getHeaders, products
3358
3418
  const pricing = resolveProductPricing(
3359
3419
  selectPublicPrices(prices, chainId)
3360
3420
  );
3361
- return inStockVariantIds.has(variant.id) && pricing.currentPrice != null;
3421
+ return (inStockVariantIds.has(variant.id) || (product.product_website_publications?.[0]?.show_when_out_of_stock ?? showOutOfStockByDefault)) && pricing.currentPrice != null;
3362
3422
  })
3363
3423
  );
3364
3424
  }
@@ -3429,7 +3489,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3429
3489
  getHeaders,
3430
3490
  [mappedProduct],
3431
3491
  ctx.chainId,
3432
- ctx.websiteId
3492
+ ctx.websiteId,
3493
+ ctx.config?.show_out_of_stock_products === true
3433
3494
  );
3434
3495
  if (!filteredProducts[0]) return null;
3435
3496
  const specifications = await loadProductSpecifications([product.id]);
@@ -3662,7 +3723,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3662
3723
  getHeaders,
3663
3724
  data.products ?? [],
3664
3725
  ctx.chainId,
3665
- ctx.websiteId
3726
+ ctx.websiteId,
3727
+ ctx.config?.show_out_of_stock_products === true
3666
3728
  );
3667
3729
  const entries = availableProducts.slice(0, limit).map((product) => mapProductSeoEntry(product, storageUrl)).filter((entry) => Boolean(entry));
3668
3730
  return {
@@ -3739,7 +3801,9 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3739
3801
  (p) => mapProduct(p, storageUrl, publicAttributeLabels, chainId)
3740
3802
  ),
3741
3803
  chainId,
3742
- ctx.websiteId
3804
+ ctx.websiteId,
3805
+ ctx.config?.show_out_of_stock_products === true,
3806
+ opts?.inStock === true
3743
3807
  );
3744
3808
  const specifications = await loadProductSpecifications(
3745
3809
  products.map((product) => product.id)
@@ -3846,6 +3910,7 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3846
3910
  {
3847
3911
  companyId: ctx.companyId,
3848
3912
  chainId,
3913
+ websiteId: ctx.websiteId,
3849
3914
  attributeListKey: DEFAULT_FILTER_ATTRIBUTE_LIST_KEY,
3850
3915
  where: buildProductWhere(opts.filters ?? {}, chainId)
3851
3916
  },
@@ -3869,7 +3934,7 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3869
3934
  variant.product_variant_prices.map(mapPrice),
3870
3935
  chainId
3871
3936
  );
3872
- return prices.length > 0 && (availabilityByVariantId.get(variant.id)?.inStock ?? false);
3937
+ return prices.length > 0 && ((availabilityByVariantId.get(variant.id)?.inStock ?? false) || (product.product_website_publications?.[0]?.show_when_out_of_stock ?? ctx.config?.show_out_of_stock_products === true));
3873
3938
  });
3874
3939
  if (!hasVisibleVariant) continue;
3875
3940
  applicableCategories.add(product.category);
@@ -3957,6 +4022,7 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
3957
4022
  {
3958
4023
  companyId: ctx.companyId,
3959
4024
  chainId,
4025
+ websiteId: ctx.websiteId,
3960
4026
  attributeListKey: opts?.attributeListKey ?? DEFAULT_FILTER_ATTRIBUTE_LIST_KEY,
3961
4027
  where: {
3962
4028
  ...opts?.categoryName ? { category: { _eq: opts.categoryName } } : {},
@@ -4011,7 +4077,9 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4011
4077
  chainId
4012
4078
  ),
4013
4079
  inStock: availabilityByVariantId.get(variant.id)?.inStock ?? false
4014
- })).filter((variant) => variant.inStock && variant.prices.length > 0);
4080
+ })).filter(
4081
+ (variant) => (variant.inStock || (product.product_website_publications?.[0]?.show_when_out_of_stock ?? ctx.config?.show_out_of_stock_products === true)) && variant.prices.length > 0
4082
+ );
4015
4083
  if (visibleVariants.length === 0) continue;
4016
4084
  categoryCounts.set(
4017
4085
  product.category,
@@ -4096,6 +4164,7 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4096
4164
  "NO_CHAIN"
4097
4165
  );
4098
4166
  }
4167
+ const chainId = ctx.chainId;
4099
4168
  const term = `%${opts.query}%`;
4100
4169
  const limit = opts.limit ?? 8;
4101
4170
  const data = await executeGraphQL(
@@ -4103,7 +4172,8 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4103
4172
  GET_SEARCH_SUGGESTIONS,
4104
4173
  {
4105
4174
  companyId: ctx.companyId,
4106
- chainId: ctx.chainId,
4175
+ chainId,
4176
+ websiteId: ctx.websiteId,
4107
4177
  term,
4108
4178
  productLimit: Math.min(limit, 5),
4109
4179
  brandLimit: Math.max(limit - 5, 3)
@@ -4111,8 +4181,29 @@ function createCatalogModule(graphqlUrl, getHeaders, getContext, storageUrl) {
4111
4181
  getHeaders
4112
4182
  );
4113
4183
  const suggestions = [];
4184
+ const availability = await getWebsiteAvailabilityByVariantId(
4185
+ graphqlUrl,
4186
+ getHeaders,
4187
+ (data.products ?? []).flatMap(
4188
+ (product) => product.product_variants.map((variant) => variant.id)
4189
+ ),
4190
+ chainId,
4191
+ ctx.websiteId
4192
+ );
4114
4193
  for (const p of data.products ?? []) {
4115
- suggestions.push({ type: "product", text: p.name, id: p.id });
4194
+ const showWithoutStock = p.product_website_publications?.[0]?.show_when_out_of_stock ?? ctx.config?.show_out_of_stock_products === true;
4195
+ const isVisible = p.product_variants.some((variant) => {
4196
+ const hasPrice = resolveProductPricing(
4197
+ selectPublicPrices(
4198
+ variant.product_variant_prices.map(mapPrice),
4199
+ chainId
4200
+ )
4201
+ ).currentPrice != null;
4202
+ return hasPrice && (showWithoutStock || availability.get(variant.id)?.inStock === true);
4203
+ });
4204
+ if (isVisible) {
4205
+ suggestions.push({ type: "product", text: p.name, id: p.id });
4206
+ }
4116
4207
  }
4117
4208
  for (const b of data.brands ?? []) {
4118
4209
  suggestions.push({ type: "brand", text: b.name, id: b.id });
@@ -40,11 +40,21 @@ función apagada en un bloqueo artificial.
40
40
  ## SEO y compartición
41
41
 
42
42
  - Título, descripción, canonical, robots, Open Graph y Twitter por ruta.
43
+ - Conservar los campos SEO editados de verdad en Bacano, pero no publicar
44
+ `lorem ipsum` u otros borradores visibles como metadata. Usar fallbacks
45
+ específicos para cada página y evitar duplicar la marca en el título.
43
46
  - `robots.txt`, sitemap con URLs canónicas y ausencia de rutas privadas.
47
+ - En el dominio publicado, comprobar con `GET` el código, tipo de contenido y
48
+ política de caché de sitemap y robots. Las rutas estables de descubrimiento
49
+ no deben heredar `immutable` de los assets versionados; contrastar un error
50
+ de Search Console con su prueba en tiempo real antes de culpar al XML. Ver
51
+ `STOREFRONT_PERFORMANCE.md`.
44
52
  - Jerarquía de encabezados, idioma, texto alternativo, enlaces internos y
45
53
  JSON-LD coherente con el contenido visible.
46
54
  - Confirmar 404 real, canonical único, ausencia de slugs/URLs duplicadas y
47
55
  redirección consistente entre `www`, dominio raíz y HTTP/HTTPS.
56
+ - El host del sitemap, canonical, Open Graph y redirección final debe coincidir;
57
+ no basta con que cada URL funcione por separado.
48
58
  - Usar `hreflang` solo cuando existan versiones regionales o de idioma reales.
49
59
  - Favicon, iconos, manifest y vista previa al compartir.
50
60
 
@@ -56,6 +66,9 @@ función apagada en un bloqueo artificial.
56
66
  - Revisar desktop y mobile, DPR 1/2, CLS, LCP, fuentes y JavaScript inicial.
57
67
  Tratar una medición sintética aislada como diagnóstico, no como verdad de
58
68
  campo; repetir y distinguir regresión sostenida de ruido.
69
+ - En carruseles, contar imágenes descargadas, no solo elementos visibles;
70
+ comprobar navegación a fotos lejanas y compatibilidad con los navegadores
71
+ móviles soportados. Ver `STOREFRONT_PERFORMANCE.md`.
59
72
  - Confirmar caché y CORS/CORP de media pública sin proxies locales.
60
73
 
61
74
  ## Accesibilidad y experiencia
@@ -18,6 +18,17 @@ Aplicar además de los controles comunes.
18
18
  alterar canonical.
19
19
  - Precio, SKU, stock, variante seleccionada, JSON-LD, carrito, WhatsApp y
20
20
  eventos deben referirse a la misma variante.
21
+ - Verificar la regla de **productos agotados** del website y las excepciones
22
+ por producto y por web. La regla predeterminada oculta agotados; una excepción
23
+ `true` los muestra, `false` los oculta y `null` hereda la regla. La publicación
24
+ del producto, cadena activa y precio público siguen siendo requisitos. El
25
+ SDK aplica la visibilidad en catálogo, rutas, SEO, filtros, recomendaciones y
26
+ búsqueda: la tienda no debe duplicar ese filtrado ni consultar Hasura.
27
+ - Un producto agotado visible no es comprable: comprobar `inStock: false` y
28
+ `maxPurchasableQuantity: 0` en estado vivo, controles de cantidad y checkout.
29
+ `getProducts({ inStock: true })` debe seguir excluyéndolo. Si el sitio es
30
+ estático, cambiar esta regla exige rebuild para que rutas, sitemap y HTML
31
+ reflejen los productos ahora visibles u ocultos.
21
32
  - El HTML inicial debe contener producto, precio y enlaces reales sin depender
22
33
  de JavaScript. Después de hidratar, refrescar únicamente estado volátil por
23
34
  lotes con `getProductLiveState()` y respetar `maxPurchasableQuantity`.
@@ -45,6 +56,8 @@ Aplicar además de los controles comunes.
45
56
  ## Carrito y checkout
46
57
 
47
58
  - Carrito invitado persiste y sincroniza sin duplicar líneas.
59
+ - Si una operación bloquea temporalmente otras mutaciones del carrito, mostrar
60
+ el indicador de carga en el producto accionado, no en todas las tarjetas.
48
61
  - Probar alta, baja, cambios de cantidad, límites de stock, cupón, envío gratis,
49
62
  impuestos, moneda y redondeo.
50
63
  - Probar solo los modos activos entre COUNTER, PICKUP y SHIPPING; propia y
@@ -56,6 +69,9 @@ Aplicar además de los controles comunes.
56
69
 
57
70
  - Leer y aplicar `STOREFRONT_PAYMENTS.md`; no reconstruir en la tienda URLs,
58
71
  credenciales ni payloads privados de Wompi, Mercado Pago o Addi.
72
+ - Si se muestran marcas de pasarela o redes, comprobar que corresponden al
73
+ método efectivamente devuelto por Bacano y no sugieren un método adicional
74
+ ni un medio que el checkout alojado no tenga confirmado.
59
75
  - Ejecutar al menos un happy path real o sandbox por cada método crítico
60
76
  publicado. Probar pago inmediato, transferencia, Wompi y contraentrega solo
61
77
  si están activos; los estados alternos de cada proveedor quedan en su matriz