limbo-component 1.9.1 → 2.0.1

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/dist/limbo.es.js CHANGED
@@ -12565,7 +12565,9 @@ function Tabs({ tabs, active, onChange }) {
12565
12565
  if (nextTab && !nextTab.hideNonActive) {
12566
12566
  onChange(nextTab.id);
12567
12567
  setTimeout(() => {
12568
- const tabButton = document.querySelector(`[data-tab-id="${nextTab.id}"]`);
12568
+ const tabButton = document.querySelector(
12569
+ `[data-tab-id="${nextTab.id}"]`
12570
+ );
12569
12571
  if (tabButton) {
12570
12572
  tabButton.focus();
12571
12573
  }
@@ -12597,7 +12599,7 @@ function Tabs({ tabs, active, onChange }) {
12597
12599
  "data-tab-id": tab.id,
12598
12600
  onClick: () => onChange(tab.id),
12599
12601
  onKeyDown: (e) => handleKeyDown(e, tab.id),
12600
- className: `limbo-tab ${isActiveTab ? "active" : ""} ${isMobile ? "limbo-tab--mobile" : ""}`,
12602
+ className: `limbo-tab ${isActiveTab ? "limbo-active" : ""} ${isMobile ? "limbo-tab--mobile" : ""}`,
12601
12603
  role: "tab",
12602
12604
  "aria-selected": isActiveTab,
12603
12605
  "aria-controls": `tabpanel-${tab.id}`,
@@ -12625,9 +12627,9 @@ function Tabs({ tabs, active, onChange }) {
12625
12627
  );
12626
12628
  }
12627
12629
  const API_URLS = {
12628
- DEV: "https://led-dev-limbo-dev.eu.els.local",
12629
- // PREPRODUCCIÓN - Updated URL
12630
- // DEV: "http://localhost", // LOCAL - Para desarrollo local
12630
+ // DEV: "https://led-dev-limbo-dev.eu.els.local", // PREPRODUCCIÓN - Updated URL
12631
+ DEV: "http://localhost",
12632
+ // LOCAL - Para desarrollo local
12631
12633
  PROD: "https://limbo.lefebvre.com"
12632
12634
  };
12633
12635
  let globalConfig = {
@@ -13246,14 +13248,10 @@ function ImageVariantsModal({
13246
13248
  };
13247
13249
  const handleDownloadVariant = async (variant) => {
13248
13250
  const { downloadImage: downloadImage2 } = await Promise.resolve().then(() => downloadImage$1);
13249
- await downloadImage2(
13250
- variant.url,
13251
- variant.name || "limbo-variant",
13252
- {
13253
- originalFormat: variant.format || variant.output_format,
13254
- accessibilityManager
13255
- }
13256
- );
13251
+ await downloadImage2(variant.url, variant.name || "limbo-variant", {
13252
+ originalFormat: variant.format || variant.output_format,
13253
+ accessibilityManager
13254
+ });
13257
13255
  };
13258
13256
  const handleViewVariant = (variant) => {
13259
13257
  accessibilityManager?.announce(
@@ -13826,7 +13824,7 @@ function ImageCard({
13826
13824
  variantsCount > 0 && /* @__PURE__ */ jsx(
13827
13825
  "span",
13828
13826
  {
13829
- className: "variants-count-badge",
13827
+ className: "limbo-variants-count-badge",
13830
13828
  style: {
13831
13829
  position: "absolute",
13832
13830
  top: "-8px",
@@ -13993,7 +13991,7 @@ function ImageCard({
13993
13991
  /* @__PURE__ */ jsx(
13994
13992
  "span",
13995
13993
  {
13996
- className: `text-xs mt-1 truncate w-full text-center limbo-image-card-name ${isMobile ? "limbo-image-card-name--mobile" : ""}`,
13994
+ className: `text-xs mt-1 limbo-truncate w-full text-center limbo-image-card-name ${isMobile ? "limbo-image-card-name--mobile" : ""}`,
13997
13995
  style: {
13998
13996
  // Better text visibility on mobile
13999
13997
  ...isMobile && {
@@ -14061,7 +14059,7 @@ function ImageCardSkeleton() {
14061
14059
  }
14062
14060
  ) }),
14063
14061
  /* @__PURE__ */ jsx("div", { className: "limbo-image-card-name opacity-100 position-relative bottom-0 p-2", children: /* @__PURE__ */ jsx("div", { className: "h-4 bg-neutral-gray-200 rounded w-3/4" }) }),
14064
- /* @__PURE__ */ jsx("span", { className: "limbo-sr-only", children: "Cargando imagen..." })
14062
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Cargando imagen..." })
14065
14063
  ]
14066
14064
  }
14067
14065
  );
@@ -14193,7 +14191,7 @@ function Gallery({
14193
14191
  "aria-label": "Filtrar imágenes",
14194
14192
  children: [
14195
14193
  showNameFilter && /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full sm:min-w-[180px] sm:flex-1", children: [
14196
- /* @__PURE__ */ jsx("label", { htmlFor: "name", className: "form-label mb-1", children: "Nombre" }),
14194
+ /* @__PURE__ */ jsx("label", { htmlFor: "name", className: "limbo-form-label mb-1", children: "Nombre" }),
14197
14195
  /* @__PURE__ */ jsx(
14198
14196
  "input",
14199
14197
  {
@@ -14203,7 +14201,7 @@ function Gallery({
14203
14201
  placeholder: "Buscar por nombre...",
14204
14202
  value: filters.name,
14205
14203
  onChange: handleChange,
14206
- className: "form-control",
14204
+ className: "limbo-form-control",
14207
14205
  autoComplete: "off"
14208
14206
  }
14209
14207
  )
@@ -14211,7 +14209,7 @@ function Gallery({
14211
14209
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row flex-wrap gap-2 justify-between items-start sm:items-end w-full sm:w-auto", children: [
14212
14210
  showDateFilter && /* @__PURE__ */ jsxs(Fragment, { children: [
14213
14211
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full sm:w-auto", children: [
14214
- /* @__PURE__ */ jsx("label", { htmlFor: "dateFrom", className: "form-label mb-1", children: "Desde" }),
14212
+ /* @__PURE__ */ jsx("label", { htmlFor: "dateFrom", className: "limbo-form-label mb-1", children: "Desde" }),
14215
14213
  /* @__PURE__ */ jsx(
14216
14214
  "input",
14217
14215
  {
@@ -14220,12 +14218,12 @@ function Gallery({
14220
14218
  id: "dateFrom",
14221
14219
  value: filters.dateFrom,
14222
14220
  onChange: handleChange,
14223
- className: "form-control"
14221
+ className: "limbo-form-control"
14224
14222
  }
14225
14223
  )
14226
14224
  ] }),
14227
14225
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full sm:w-auto", children: [
14228
- /* @__PURE__ */ jsx("label", { htmlFor: "dateTo", className: "form-label mb-1", children: "Hasta" }),
14226
+ /* @__PURE__ */ jsx("label", { htmlFor: "dateTo", className: "limbo-form-label mb-1", children: "Hasta" }),
14229
14227
  /* @__PURE__ */ jsx(
14230
14228
  "input",
14231
14229
  {
@@ -14234,13 +14232,13 @@ function Gallery({
14234
14232
  id: "dateTo",
14235
14233
  value: filters.dateTo,
14236
14234
  onChange: handleChange,
14237
- className: "form-control"
14235
+ className: "limbo-form-control"
14238
14236
  }
14239
14237
  )
14240
14238
  ] })
14241
14239
  ] }),
14242
14240
  showUploadedByFilter && /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full sm:w-auto", children: [
14243
- /* @__PURE__ */ jsx("label", { htmlFor: "uploadedBy", className: "form-label mb-1", children: "Subido por" }),
14241
+ /* @__PURE__ */ jsx("label", { htmlFor: "uploadedBy", className: "limbo-form-label mb-1", children: "Subido por" }),
14244
14242
  /* @__PURE__ */ jsx(
14245
14243
  "input",
14246
14244
  {
@@ -14250,7 +14248,7 @@ function Gallery({
14250
14248
  placeholder: "Subido por",
14251
14249
  value: filters.uploadedBy,
14252
14250
  onChange: handleChange,
14253
- className: "form-control"
14251
+ className: "limbo-form-control"
14254
14252
  }
14255
14253
  )
14256
14254
  ] })
@@ -14322,13 +14320,257 @@ function Gallery({
14322
14320
  )
14323
14321
  ] });
14324
14322
  }
14323
+ function ImagePreview({
14324
+ image,
14325
+ onDiscard,
14326
+ onRetry,
14327
+ onDownload,
14328
+ onSelect,
14329
+ showRetry = false,
14330
+ disabled = false
14331
+ }) {
14332
+ const [previewUrl, setPreviewUrl] = useState(null);
14333
+ const [imageInfo, setImageInfo] = useState({
14334
+ name: "",
14335
+ size: "",
14336
+ type: ""
14337
+ });
14338
+ const [showLightbox, setShowLightbox] = useState(false);
14339
+ const handleEditName = (e) => {
14340
+ const newName = e.target.value;
14341
+ const originalName = imageInfo.name;
14342
+ const lastDotIndex = originalName.lastIndexOf(".");
14343
+ const extension = lastDotIndex !== -1 ? originalName.substring(lastDotIndex) : ".webp";
14344
+ setImageInfo((prev) => ({
14345
+ ...prev,
14346
+ name: newName + extension
14347
+ }));
14348
+ };
14349
+ useEffect(() => {
14350
+ if (!image) {
14351
+ setPreviewUrl(null);
14352
+ return;
14353
+ }
14354
+ const url = URL.createObjectURL(image);
14355
+ setPreviewUrl(url);
14356
+ setImageInfo({
14357
+ name: image.name || "imagen.webp",
14358
+ size: formatFileSize(image.size || 0),
14359
+ type: image.type || "image/webp"
14360
+ });
14361
+ return () => {
14362
+ URL.revokeObjectURL(url);
14363
+ };
14364
+ }, [image]);
14365
+ useEffect(() => {
14366
+ if (!showLightbox) return;
14367
+ const handleEsc = (e) => {
14368
+ if (e.key === "Escape") {
14369
+ setShowLightbox(false);
14370
+ }
14371
+ };
14372
+ document.addEventListener("keydown", handleEsc);
14373
+ return () => document.removeEventListener("keydown", handleEsc);
14374
+ }, [showLightbox]);
14375
+ const formatFileSize = (bytes) => {
14376
+ if (bytes === 0) return "0 Bytes";
14377
+ const k = 1024;
14378
+ const sizes = ["Bytes", "KB", "MB", "GB"];
14379
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
14380
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
14381
+ };
14382
+ const handleDownload = () => {
14383
+ if (!previewUrl || disabled) return;
14384
+ const link = document.createElement("a");
14385
+ link.href = previewUrl;
14386
+ link.download = imageInfo.name;
14387
+ document.body.appendChild(link);
14388
+ link.click();
14389
+ document.body.removeChild(link);
14390
+ if (onDownload) onDownload();
14391
+ };
14392
+ if (!image || !previewUrl) {
14393
+ return null;
14394
+ }
14395
+ return /* @__PURE__ */ jsxs("div", { className: "w-fit md:w-full bg-white border border-gray-200 rounded-lg px-1 sm:px-3 py-2 m-0 shadow-sm", children: [
14396
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4 px-1", children: [
14397
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-gray-700", children: "Vista previa de imagen" }),
14398
+ /* @__PURE__ */ jsx(
14399
+ "button",
14400
+ {
14401
+ type: "button",
14402
+ onClick: onDiscard,
14403
+ disabled,
14404
+ className: "p-1.5 text-gray-400 cursor-pointer hover:text-gray-600 hover:bg-red-300 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
14405
+ "aria-label": "Descartar imagen",
14406
+ title: "Descartar imagen",
14407
+ children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small icon--md" })
14408
+ }
14409
+ )
14410
+ ] }),
14411
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col justify-center md:flex-row gap-2 md:gap-6", children: [
14412
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 md:flex-[0.4] flex flex-col items-center justify-center", children: [
14413
+ /* @__PURE__ */ jsxs(
14414
+ "div",
14415
+ {
14416
+ className: "relative w-full max-w-md aspect-video bg-gray-100 rounded-lg overflow-hidden cursor-zoom-in hover:ring-2 hover:ring-brand-blue-800 transition-all",
14417
+ onClick: () => setShowLightbox(true),
14418
+ role: "button",
14419
+ tabIndex: 0,
14420
+ onKeyDown: (e) => {
14421
+ if (e.key === "Enter" || e.key === " ") {
14422
+ e.preventDefault();
14423
+ setShowLightbox(true);
14424
+ }
14425
+ },
14426
+ "aria-label": "Click para ver imagen en tamaño completo",
14427
+ title: "Click para ver en tamaño completo",
14428
+ children: [
14429
+ /* @__PURE__ */ jsx(
14430
+ "img",
14431
+ {
14432
+ src: previewUrl,
14433
+ alt: "Vista previa",
14434
+ className: "absolute inset-0 w-full h-full object-contain"
14435
+ }
14436
+ ),
14437
+ /* @__PURE__ */ jsxs("div", { className: "absolute top-2 right-2 bg-black/50 text-white px-2 py-1 rounded text-xs flex items-center gap-1", children: [
14438
+ /* @__PURE__ */ jsx("span", { className: "icon icon-search-white icon--xs" }),
14439
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Ampliar" })
14440
+ ] })
14441
+ ]
14442
+ }
14443
+ ),
14444
+ /* @__PURE__ */ jsx("div", { className: "mt-3 text-center space-y-1 w-full", children: /* @__PURE__ */ jsxs("div", { className: "rounded-sm w-fit gap-0.5 p-0.5 min-w-fit mx-auto", children: [
14445
+ /* @__PURE__ */ jsxs(
14446
+ "label",
14447
+ {
14448
+ htmlFor: "uploadImageId",
14449
+ className: "flex items-center w-fit",
14450
+ children: [
14451
+ /* @__PURE__ */ jsx("div", { className: "rounded-full bg-brand-blue-1000 min-w-6 min-h-6 flex items-center align-middle content-center justify-center limbo-me-1", children: /* @__PURE__ */ jsx("span", { className: "icon icon-edit-white icon--sm" }) }),
14452
+ /* @__PURE__ */ jsx(
14453
+ "input",
14454
+ {
14455
+ id: "uploadImageId",
14456
+ onChange: (e) => handleEditName(e),
14457
+ className: "w-fit px-1 text-sm font-medium text-gray-800 border border-gray-300 rounded limbo-truncate focus:outline focus:outline-neutral-gray-100 focus:limbo-transform-none focus:shadow-none focus:opacity-100",
14458
+ defaultValue: imageInfo.name.split(".")[0],
14459
+ title: "Renombra la imagen a tu gusto"
14460
+ }
14461
+ )
14462
+ ]
14463
+ }
14464
+ ),
14465
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-3 text-xs text-gray-500", children: [
14466
+ /* @__PURE__ */ jsx("span", { children: imageInfo.size }),
14467
+ /* @__PURE__ */ jsx("span", { children: "•" }),
14468
+ /* @__PURE__ */ jsx("span", { children: imageInfo.type.split("/")[1]?.toUpperCase() || "IMAGE" })
14469
+ ] })
14470
+ ] }) })
14471
+ ] }),
14472
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-row md:flex-col gap-2 justify-items-stretch justify-center md:justify-start items-stretch md:min-w-[140px]", children: [
14473
+ showRetry && onRetry && /* @__PURE__ */ jsxs(
14474
+ "button",
14475
+ {
14476
+ type: "button",
14477
+ onClick: onRetry,
14478
+ disabled,
14479
+ className: "flex-1 md:flex-none flex items-center justify-center gap-2 px-4 py-2.5 bg-white border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 cursor-pointer hover:border-gray-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-sm font-medium",
14480
+ "aria-label": "Intentarlo de nuevo",
14481
+ title: "Generar de nuevo con el mismo prompt",
14482
+ children: [
14483
+ /* @__PURE__ */ jsx("span", { className: "icon icon-refresh icon--sm" }),
14484
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Reintentar" }),
14485
+ /* @__PURE__ */ jsx("span", { className: "sm:hidden", children: "Retry" })
14486
+ ]
14487
+ }
14488
+ ),
14489
+ /* @__PURE__ */ jsxs(
14490
+ "button",
14491
+ {
14492
+ type: "button",
14493
+ onClick: handleDownload,
14494
+ disabled,
14495
+ className: "flex-1 cursor-pointer md:flex-none flex items-center justify-center gap-2 px-4 py-2.5 bg-white border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 hover:border-gray-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-sm font-medium",
14496
+ "aria-label": "Descargar imagen",
14497
+ children: [
14498
+ /* @__PURE__ */ jsx("span", { className: "icon icon-download icon--sm" }),
14499
+ /* @__PURE__ */ jsx("span", { children: "Descargar" })
14500
+ ]
14501
+ }
14502
+ ),
14503
+ /* @__PURE__ */ jsxs(
14504
+ "button",
14505
+ {
14506
+ type: "button",
14507
+ onClick: () => onSelect && onSelect(imageInfo.name),
14508
+ disabled,
14509
+ className: "flex-1 md:flex-none flex items-center justify-center gap-2 px-4 py-2.5 bg-brand-blue-800 border border-brand-blue-800 text-white rounded-lg hover:bg-brand-blue-1000 cursor-pointer hover:border-brand-blue-1000 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-sm font-medium shadow-sm",
14510
+ "aria-label": "Seleccionar imagen para recortar",
14511
+ children: [
14512
+ /* @__PURE__ */ jsx("span", { className: "icon icon-tick-white icon--sm" }),
14513
+ /* @__PURE__ */ jsx("span", { children: "Seleccionar" })
14514
+ ]
14515
+ }
14516
+ )
14517
+ ] })
14518
+ ] }),
14519
+ showLightbox && /* @__PURE__ */ jsxs(
14520
+ "div",
14521
+ {
14522
+ className: "fixed inset-0 bg-black/90 z-[10000] flex items-center justify-center p-4",
14523
+ onClick: () => setShowLightbox(false),
14524
+ role: "dialog",
14525
+ "aria-modal": "true",
14526
+ "aria-label": "Vista de imagen en tamaño completo",
14527
+ children: [
14528
+ /* @__PURE__ */ jsx(
14529
+ "button",
14530
+ {
14531
+ onClick: () => setShowLightbox(false),
14532
+ className: "absolute top-4 right-4 p-2 bg-white/10 hover:bg-white/20 rounded-full transition-colors cursor-pointer z-10",
14533
+ "aria-label": "Cerrar vista ampliada",
14534
+ title: "Cerrar (Esc)",
14535
+ children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small-white icon--lg" })
14536
+ }
14537
+ ),
14538
+ /* @__PURE__ */ jsxs("div", { className: "absolute top-4 left-4 bg-black/50 text-white px-4 py-2 rounded-lg text-sm", children: [
14539
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: imageInfo.name }),
14540
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-300 mt-1", children: [
14541
+ imageInfo.size,
14542
+ " • ",
14543
+ imageInfo.type.split("/")[1]?.toUpperCase()
14544
+ ] })
14545
+ ] }),
14546
+ /* @__PURE__ */ jsx(
14547
+ "div",
14548
+ {
14549
+ className: "relative max-w-7xl max-h-[90vh] w-full h-full flex items-center justify-center",
14550
+ onClick: (e) => e.stopPropagation(),
14551
+ children: /* @__PURE__ */ jsx(
14552
+ "img",
14553
+ {
14554
+ src: previewUrl,
14555
+ alt: "Vista completa",
14556
+ className: "max-w-full max-h-full object-contain rounded-lg shadow-2xl"
14557
+ }
14558
+ )
14559
+ }
14560
+ ),
14561
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-4 left-1/2 limbo--translate-x-1/2 bg-black/50 text-white px-4 py-2 rounded-lg text-xs text-center", children: "Click fuera de la imagen o presiona ESC para cerrar" })
14562
+ ]
14563
+ }
14564
+ )
14565
+ ] });
14566
+ }
14325
14567
  function TabUpload({
14326
14568
  file,
14327
14569
  setFile,
14328
14570
  previewUrl,
14329
14571
  setPreviewUrl,
14330
14572
  fileInputRef,
14331
- onUpload,
14573
+ onSelect,
14332
14574
  disabled
14333
14575
  }) {
14334
14576
  const handleFileChange = (e) => {
@@ -14341,36 +14583,32 @@ function TabUpload({
14341
14583
  setPreviewUrl(null);
14342
14584
  }
14343
14585
  };
14344
- const handleClearFile = () => {
14345
- const confirmClear = window.confirm(
14346
- "¿Estás seguro de que deseas descartar la imagen seleccionada?"
14347
- );
14348
- if (!confirmClear) return;
14586
+ const handleDiscard = () => {
14349
14587
  setFile(null);
14350
14588
  setPreviewUrl(null);
14351
14589
  if (fileInputRef.current) fileInputRef.current.value = "";
14352
14590
  };
14353
- const handleSubmit = (e) => {
14354
- e.preventDefault();
14355
- if (file && !disabled) {
14356
- onUpload(file);
14357
- setFile(null);
14358
- setPreviewUrl(null);
14359
- if (fileInputRef.current) fileInputRef.current.value = "";
14591
+ const handleSelect = (editedName) => {
14592
+ if (file && !disabled && onSelect) {
14593
+ if (editedName && editedName !== file.name) {
14594
+ const newFile = new File([file], editedName, { type: file.type });
14595
+ onSelect(newFile);
14596
+ } else {
14597
+ onSelect(file);
14598
+ }
14360
14599
  }
14361
14600
  };
14362
14601
  return /* @__PURE__ */ jsxs(
14363
- "form",
14602
+ "div",
14364
14603
  {
14365
- onSubmit: handleSubmit,
14366
14604
  className: "flex flex-col items-center gap-6",
14367
14605
  "aria-label": "Subir imagen desde dispositivo",
14368
14606
  children: [
14369
- !previewUrl && /* @__PURE__ */ jsxs(
14607
+ /* @__PURE__ */ jsxs(
14370
14608
  "label",
14371
14609
  {
14372
14610
  htmlFor: "file-input",
14373
- className: "w-full flex flex-col items-center justify-center border-2 border-dashed border-brand-blue-200 rounded-xl p-8 cursor-pointer hover:border-brand-blue-1000 hover:to-bright-blue-200 hover:from-brand-blue-050 focus-within:border-brand-blue-800 from-bright-blue-050 to-brand-blue-050 bg-linear-to-br transition",
14611
+ className: "w-fit md:w-full flex flex-col items-center justify-center border-2 border-dashed border-brand-blue-200 rounded-xl p-8 cursor-pointer hover:border-brand-blue-1000 hover:to-bright-blue-200 hover:from-brand-blue-050 focus-within:border-brand-blue-800 from-bright-blue-050 to-brand-blue-050 bg-linear-to-br transition",
14374
14612
  tabIndex: 0,
14375
14613
  style: { outline: "none" },
14376
14614
  children: [
@@ -14393,71 +14631,16 @@ function TabUpload({
14393
14631
  ]
14394
14632
  }
14395
14633
  ),
14396
- previewUrl && /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col items-center gap-4", children: [
14397
- /* @__PURE__ */ jsxs("div", { className: "relative w-fit", children: [
14398
- /* @__PURE__ */ jsx(
14399
- "img",
14400
- {
14401
- src: previewUrl,
14402
- alt: file?.name || "Previsualización",
14403
- className: "rounded-lg max-h-64 object-contain bg-white w-fit mx-auto",
14404
- style: { maxWidth: "100%" }
14405
- }
14406
- ),
14407
- /* @__PURE__ */ jsx(
14408
- "button",
14409
- {
14410
- type: "button",
14411
- onClick: handleClearFile,
14412
- disabled,
14413
- className: "absolute cursor-pointer top-2 right-2 bg-red-600 hover:bg-red-700 text-white rounded-full p-2 shadow-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed",
14414
- title: "Descartar imagen",
14415
- "aria-label": "Descartar imagen seleccionada",
14416
- children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small-white icon--md m-0 p-0" })
14417
- }
14418
- )
14419
- ] }),
14420
- /* @__PURE__ */ jsx("div", { className: "text-sm text-neutral-800 bg-brand-blue-050 p-3 rounded-lg w-full", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-2 text-center", children: [
14421
- /* @__PURE__ */ jsxs("p", { children: [
14422
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Archivo:" }),
14423
- " ",
14424
- file?.name
14425
- ] }),
14426
- /* @__PURE__ */ jsxs("p", { children: [
14427
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Tamaño:" }),
14428
- " ",
14429
- file ? (file.size / 1024 / 1024).toFixed(2) : "--",
14430
- " MB"
14431
- ] }),
14432
- /* @__PURE__ */ jsxs("p", { children: [
14433
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Tipo:" }),
14434
- " ",
14435
- file?.type
14436
- ] })
14437
- ] }) }),
14438
- /* @__PURE__ */ jsx(
14439
- "button",
14440
- {
14441
- type: "submit",
14442
- disabled: !file || disabled,
14443
- title: !file ? "Proporcione una imagen antes de continuar" : "Subir imagen a la galeria",
14444
- className: `limbo-btn w-full ${!file ? "cursor-not-allowed limbo-btn-disabled" : "limbo-btn-primary"}`,
14445
- style: { minHeight: 44 },
14446
- children: disabled ? "Subiendo..." : "Subir imagen"
14447
- }
14448
- )
14449
- ] }),
14450
- previewUrl && /* @__PURE__ */ jsx(
14451
- "input",
14634
+ file && /* @__PURE__ */ jsx(
14635
+ ImagePreview,
14452
14636
  {
14453
- id: "file-input",
14454
- ref: fileInputRef,
14455
- type: "file",
14456
- accept: "image/jpeg,image/png,image/svg,image/webp,image/gif",
14457
- onChange: handleFileChange,
14458
- disabled,
14459
- className: "sr-only",
14460
- "aria-label": "Seleccionar imagen"
14637
+ image: file,
14638
+ onDiscard: handleDiscard,
14639
+ onDownload: () => {
14640
+ },
14641
+ onSelect: handleSelect,
14642
+ showRetry: false,
14643
+ disabled
14461
14644
  }
14462
14645
  )
14463
14646
  ]
@@ -14600,14 +14783,16 @@ function LoadingOverlay({
14600
14783
  }
14601
14784
  );
14602
14785
  }
14603
- function TabAI({ prod, disabled, onGenerated }) {
14786
+ function TabAI({ prod, disabled, onSelect }) {
14604
14787
  const aiServicesHook = useAiServices(prod);
14605
14788
  const imageParamsHook = useImageParams(prod);
14789
+ const [lastPrompt, setLastPrompt] = useState("");
14606
14790
  const [selectedService, setSelectedService] = useState("");
14607
14791
  const [dynamicForm, setDynamicForm] = useState({});
14608
14792
  const [aiLoading, setAiLoading] = useState(false);
14609
14793
  const [aiError, setAiError] = useState(null);
14610
14794
  const [aiImage, setAiImage] = useState(null);
14795
+ const [generatedFile, setGeneratedFile] = useState(null);
14611
14796
  const [showServiceSelection, setShowServiceSelection] = useState(true);
14612
14797
  const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
14613
14798
  React.useEffect(() => {
@@ -14633,12 +14818,17 @@ function TabAI({ prod, disabled, onGenerated }) {
14633
14818
  const { name, value } = e.target;
14634
14819
  setDynamicForm((prev) => ({ ...prev, [name]: value }));
14635
14820
  };
14636
- const handleDynamicFormSubmit = async (e) => {
14821
+ const handleDynamicFormSubmit = async (e, retry = false) => {
14637
14822
  e.preventDefault();
14638
14823
  setAiLoading(true);
14639
14824
  setAiError(null);
14640
14825
  setAiImage(null);
14641
14826
  try {
14827
+ if (retry) {
14828
+ dynamicForm.prompt = lastPrompt;
14829
+ } else {
14830
+ setLastPrompt(dynamicForm.prompt || "");
14831
+ }
14642
14832
  const response = await generateAiImage(dynamicForm, prod);
14643
14833
  let imageData = null;
14644
14834
  if (response?.data?.images && Array.isArray(response.data.images) && response.data.images.length > 0) {
@@ -14650,7 +14840,7 @@ function TabAI({ prod, disabled, onGenerated }) {
14650
14840
  } else if (typeof response === "string") {
14651
14841
  imageData = response;
14652
14842
  }
14653
- if (imageData && onGenerated) {
14843
+ if (imageData) {
14654
14844
  if (imageData.startsWith("http")) {
14655
14845
  try {
14656
14846
  const apiBaseUrl = window.limboCore?.config?.getGlobal()?.prod ? "https://limbo.lefebvre.com" : "http://localhost";
@@ -14669,10 +14859,10 @@ function TabAI({ prod, disabled, onGenerated }) {
14669
14859
  );
14670
14860
  }
14671
14861
  const blob = await imageResponse.blob();
14672
- const file = new File([blob], "ai-image.png", {
14673
- type: blob.type || "image/png"
14862
+ const file = new File([blob], "ai-image.webp", {
14863
+ type: blob.type || "image/webp"
14674
14864
  });
14675
- onGenerated(file);
14865
+ setGeneratedFile(file);
14676
14866
  } catch (downloadError) {
14677
14867
  throw new Error(
14678
14868
  `Error downloading image via proxy: ${downloadError.message}`
@@ -14689,7 +14879,7 @@ function TabAI({ prod, disabled, onGenerated }) {
14689
14879
  const file = new File([byteArray], "ai-image.webp", {
14690
14880
  type: "image/webp"
14691
14881
  });
14692
- onGenerated(file);
14882
+ setGeneratedFile(file);
14693
14883
  }
14694
14884
  } else {
14695
14885
  throw new Error("No se pudo extraer la imagen de la respuesta");
@@ -14704,7 +14894,10 @@ function TabAI({ prod, disabled, onGenerated }) {
14704
14894
  const renderDynamicForm = () => {
14705
14895
  const params = imageParamsHook.params?.[selectedService]?.parameters;
14706
14896
  if (!params) return null;
14707
- const allFields = Object.entries(params).filter(([_, config]) => !config.hidden);
14897
+ const allFields = Object.entries(params).filter(
14898
+ // eslint-disable-next-line no-unused-vars
14899
+ ([_, config]) => !config.hidden
14900
+ );
14708
14901
  const promptFields = allFields.filter(
14709
14902
  ([key]) => key.toLowerCase().includes("prompt") || key.toLowerCase().includes("query")
14710
14903
  );
@@ -14792,7 +14985,7 @@ function TabAI({ prod, disabled, onGenerated }) {
14792
14985
  )
14793
14986
  ] }, key);
14794
14987
  };
14795
- return /* @__PURE__ */ jsxs("div", { children: [
14988
+ return /* @__PURE__ */ jsxs("div", { className: "h-fit w-full", children: [
14796
14989
  aiServicesHook.services.length > 1 && /* @__PURE__ */ jsxs(
14797
14990
  "button",
14798
14991
  {
@@ -14803,6 +14996,7 @@ function TabAI({ prod, disabled, onGenerated }) {
14803
14996
  setDynamicForm({});
14804
14997
  setAiImage(null);
14805
14998
  setAiError(null);
14999
+ setGeneratedFile(null);
14806
15000
  },
14807
15001
  className: "flex cursor-pointer items-center gap-2 text-brand-blue-600 hover:text-brand-blue-700 mb-4 font-medium",
14808
15002
  children: [
@@ -14856,17 +15050,19 @@ function TabAI({ prod, disabled, onGenerated }) {
14856
15050
  ]
14857
15051
  }
14858
15052
  ),
14859
- showAdvancedOptions && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 animate-fadeIn", children: advancedFields.map(([key, config]) => renderField(key, config)) })
15053
+ showAdvancedOptions && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 animate-fadeIn", children: advancedFields.map(
15054
+ ([key, config]) => renderField(key, config)
15055
+ ) })
14860
15056
  ] }),
14861
15057
  /* @__PURE__ */ jsx(
14862
15058
  "button",
14863
15059
  {
14864
15060
  type: "submit",
14865
15061
  disabled: aiLoading || disabled,
14866
- title: aiLoading ? "Generando imagen..." : "Generar imagen",
15062
+ title: aiLoading ? "Generando imagen..." : generatedFile ? "Generar otra imagen" : "Generar imagen",
14867
15063
  className: `limbo-btn w-full mt-2 ${aiLoading ? "cursor-not-allowed limbo-btn-disabled" : "limbo-btn-primary"}`,
14868
15064
  style: { minHeight: 44 },
14869
- children: aiLoading ? "Generando..." : "Generar imagen"
15065
+ children: aiLoading ? "Generando imagen..." : generatedFile ? "Generar otra imagen" : "Generar imagen"
14870
15066
  }
14871
15067
  )
14872
15068
  ]
@@ -14891,12 +15087,12 @@ function TabAI({ prod, disabled, onGenerated }) {
14891
15087
  if (!aiServicesHook.services.length) {
14892
15088
  return /* @__PURE__ */ jsx("div", { className: "alert alert-warning", children: "No hay servicios IA disponibles." });
14893
15089
  }
14894
- return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 mx-4 pb-2", children: showServiceSelection ? /* @__PURE__ */ jsxs(Fragment, { children: [
15090
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col mx-4 pb-2", children: showServiceSelection ? /* @__PURE__ */ jsxs(Fragment, { children: [
14895
15091
  /* @__PURE__ */ jsx(
14896
15092
  "h3",
14897
15093
  {
14898
15094
  id: "aiSelectDescription",
14899
- className: "text-lg font-semibold text-brand-blue-1000 mb-3",
15095
+ className: "text-2xl font-semibold text-brand-blue-1000",
14900
15096
  children: "Selecciona el modelo IA"
14901
15097
  }
14902
15098
  ),
@@ -14911,15 +15107,11 @@ function TabAI({ prod, disabled, onGenerated }) {
14911
15107
  setDynamicForm({});
14912
15108
  setAiImage(null);
14913
15109
  setAiError(null);
15110
+ setGeneratedFile(null);
14914
15111
  imageParamsHook.fetchParams(service.slug, true);
14915
15112
  },
14916
15113
  disabled: aiServicesHook.loading || disabled,
14917
- className: `
14918
- group relative p-4 rounded-lg border-2 transition-all duration-200 overflow-hidden
14919
- flex flex-col items-center justify-center min-h-[120px] grow-1 basis-sm min-w-70
14920
- border-gray-200 hover:border-brand-blue-300 hover:shadow-md
14921
- ${aiServicesHook.loading || disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}
14922
- `,
15114
+ className: ` group relative p-4 rounded-lg border-2 transition-all duration-200 overflow-hidden flex flex-col items-center justify-center min-h-[120px] grow-1 basis-sm min-w-70 border-gray-200 hover:border-brand-blue-300 hover:shadow-md ${aiServicesHook.loading || disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"} `,
14923
15115
  style: {
14924
15116
  position: "relative"
14925
15117
  },
@@ -14936,7 +15128,7 @@ function TabAI({ prod, disabled, onGenerated }) {
14936
15128
  ),
14937
15129
  /* @__PURE__ */ jsxs("div", { className: "relative z-10 flex flex-col items-center justify-center gap-2 text-center", children: [
14938
15130
  /* @__PURE__ */ jsx("span", { className: "text-base font-semibold text-gray-800", children: service.label }),
14939
- service.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-600 line-clamp-2", children: service.description })
15131
+ service.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-neutral-gray-800 limbo-line-clamp-2 drop-shadow drop-shadow-neutral-white-000", children: service.description })
14940
15132
  ] })
14941
15133
  ]
14942
15134
  },
@@ -14945,22 +15137,40 @@ function TabAI({ prod, disabled, onGenerated }) {
14945
15137
  ] }) : (
14946
15138
  /* Vista del formulario del servicio seleccionado */
14947
15139
  /* @__PURE__ */ jsxs(Fragment, { children: [
14948
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-brand-blue-1000 mb-3", children: aiServicesHook.services.find((s) => s.slug === selectedService)?.label || "Generación IA" }),
15140
+ /* @__PURE__ */ jsx("h3", { className: "text-2xl font-semibold text-brand-blue-1000", children: aiServicesHook.services.find((s) => s.slug === selectedService)?.label || "Generación IA" }),
14949
15141
  imageParamsHook.loading && /* @__PURE__ */ jsx("div", { children: "Cargando parámetros..." }),
14950
15142
  imageParamsHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: imageParamsHook.error }),
14951
15143
  selectedService && renderDynamicForm(),
14952
- aiError && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: aiError }),
14953
- aiImage && aiImage.map((img, index) => /* @__PURE__ */ jsxs("div", { className: "mt-6 text-center", children: [
14954
- /* @__PURE__ */ jsx(
14955
- "img",
14956
- {
14957
- src: img.contains("http") ? img : `data:image/png;base64, ${img}`,
14958
- alt: "Imagen generada con IA",
14959
- className: "rounded-lg shadow-md border border-brand-blue-200 max-h-72 mx-auto"
14960
- }
14961
- ),
14962
- /* @__PURE__ */ jsx("button", { className: "limbo-btn limbo-btn-primary mt-4", children: "Aceptar y subir" })
14963
- ] }, "img-" + index))
15144
+ aiError && /* @__PURE__ */ jsx("div", { className: "alert alert-danger mt-4", children: aiError }),
15145
+ generatedFile && /* @__PURE__ */ jsx("div", { className: "mt-6 md:w-full mx-auto", children: /* @__PURE__ */ jsx(
15146
+ ImagePreview,
15147
+ {
15148
+ image: generatedFile,
15149
+ onDiscard: () => {
15150
+ setGeneratedFile(null);
15151
+ setAiImage(null);
15152
+ setAiError(null);
15153
+ },
15154
+ onRetry: (e) => handleDynamicFormSubmit(e, true),
15155
+ onDownload: () => {
15156
+ },
15157
+ onSelect: (editedName) => {
15158
+ if (onSelect) {
15159
+ const fileToSelect = generatedFile || aiImage;
15160
+ if (editedName && fileToSelect && editedName !== fileToSelect.name) {
15161
+ const newFile = new File([fileToSelect], editedName, {
15162
+ type: fileToSelect.type
15163
+ });
15164
+ onSelect(newFile);
15165
+ } else {
15166
+ onSelect(fileToSelect);
15167
+ }
15168
+ }
15169
+ },
15170
+ showRetry: true,
15171
+ disabled: aiLoading || disabled
15172
+ }
15173
+ ) })
14964
15174
  ] })
14965
15175
  ) });
14966
15176
  }
@@ -15033,7 +15243,7 @@ function useStockServices(prod = false) {
15033
15243
  };
15034
15244
  return { services, loading, error, invalidateCache };
15035
15245
  }
15036
- function TabStock({ prod, disabled, onSelected }) {
15246
+ function TabStock({ prod, disabled, onSelect }) {
15037
15247
  const lastSearchPayloadRef = React.useRef(null);
15038
15248
  const stockServicesHook = useStockServices(prod);
15039
15249
  const loadSavedState = (key, defaultValue) => {
@@ -15059,12 +15269,14 @@ function TabStock({ prod, disabled, onSelected }) {
15059
15269
  const [paginationInfo, setPaginationInfo] = useState(
15060
15270
  () => loadSavedState("paginationInfo", null)
15061
15271
  );
15272
+ const [lastQuery, setLastQuery] = useState(null);
15062
15273
  const imageParamsHook = useImageParams(prod);
15063
15274
  const [stockLoading, setStockLoading] = useState(false);
15064
15275
  const [stockError, setStockError] = useState(null);
15065
15276
  const [downloadingId, setDownloadingId] = useState(null);
15066
15277
  const [showServiceSelection, setShowServiceSelection] = useState(true);
15067
15278
  const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
15279
+ const [selectedFile, setSelectedFile] = useState(null);
15068
15280
  React.useEffect(() => {
15069
15281
  sessionStorage.setItem(
15070
15282
  "limbo_stock_selectedService",
@@ -15184,6 +15396,7 @@ function TabStock({ prod, disabled, onSelected }) {
15184
15396
  setStockError("La búsqueda debe tener al menos 5 caracteres");
15185
15397
  return;
15186
15398
  }
15399
+ setLastQuery(query);
15187
15400
  await performSearch(1);
15188
15401
  };
15189
15402
  const handlePageChange = (newPage) => {
@@ -15207,7 +15420,7 @@ function TabStock({ prod, disabled, onSelected }) {
15207
15420
  const file = new File([blob], filename, {
15208
15421
  type: blob.type || "image/jpeg"
15209
15422
  });
15210
- if (onSelected) onSelected(file);
15423
+ setSelectedFile(file);
15211
15424
  } else {
15212
15425
  throw new Error("No se pudo obtener la URL de descarga");
15213
15426
  }
@@ -15265,20 +15478,27 @@ function TabStock({ prod, disabled, onSelected }) {
15265
15478
  ]
15266
15479
  }
15267
15480
  ),
15268
- config.options && config.multiple ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 min-h-[50px] px-2 justify-between border-2 border-gray-transparent-500 rounded-lg", children: config.options.map((opt) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1", children: [
15269
- /* @__PURE__ */ jsx(
15270
- "input",
15271
- {
15272
- type: "checkbox",
15273
- name: `stock-${key}`,
15274
- value: opt,
15275
- checked: Array.isArray(dynamicForm[key]) && dynamicForm[key].includes(opt),
15276
- onChange: () => handleMultiCheckboxChange(key, opt),
15277
- disabled
15278
- }
15279
- ),
15280
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: opt })
15281
- ] }, opt)) }) : config.options ? /* @__PURE__ */ jsx(
15481
+ config.options && config.multiple ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 min-h-[50px] px-2 justify-between border-2 border-gray-transparent-500 rounded-lg", children: config.options.map((opt) => /* @__PURE__ */ jsxs(
15482
+ "label",
15483
+ {
15484
+ className: "flex items-center gap-1",
15485
+ children: [
15486
+ /* @__PURE__ */ jsx(
15487
+ "input",
15488
+ {
15489
+ type: "checkbox",
15490
+ name: `stock-${key}`,
15491
+ value: opt,
15492
+ checked: Array.isArray(dynamicForm[key]) && dynamicForm[key].includes(opt),
15493
+ onChange: () => handleMultiCheckboxChange(key, opt),
15494
+ disabled
15495
+ }
15496
+ ),
15497
+ /* @__PURE__ */ jsx("span", { className: "text-xs", children: opt })
15498
+ ]
15499
+ },
15500
+ opt
15501
+ )) }) : config.options ? /* @__PURE__ */ jsx(
15282
15502
  "select",
15283
15503
  {
15284
15504
  id: `stock-${key}`,
@@ -15434,8 +15654,8 @@ function TabStock({ prod, disabled, onSelected }) {
15434
15654
  if (!stockServicesHook.services.length) {
15435
15655
  return /* @__PURE__ */ jsx("div", { className: "alert alert-warning", children: "No hay servicios de Stock disponibles." });
15436
15656
  }
15437
- return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 mx-4 pb-2", children: showServiceSelection ? /* @__PURE__ */ jsxs(Fragment, { children: [
15438
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-brand-blue-1000 mb-3", children: "Selecciona el servicio de Stock" }),
15657
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col mx-4 pb-2", children: showServiceSelection ? /* @__PURE__ */ jsxs(Fragment, { children: [
15658
+ /* @__PURE__ */ jsx("h3", { className: "text-2xl font-semibold text-brand-blue-1000", children: "Selecciona el servicio de Stock" }),
15439
15659
  stockServicesHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: stockServicesHook.error }),
15440
15660
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-3", children: stockServicesHook.services.map((service) => /* @__PURE__ */ jsxs(
15441
15661
  "button",
@@ -15452,12 +15672,7 @@ function TabStock({ prod, disabled, onSelected }) {
15452
15672
  imageParamsHook.fetchParams(service.slug, true);
15453
15673
  },
15454
15674
  disabled: stockServicesHook.loading || disabled,
15455
- className: `
15456
- group relative p-4 rounded-lg border-2 transition-all duration-200 overflow-hidden
15457
- flex flex-col items-center justify-center min-h-[120px]
15458
- border-gray-200 hover:border-brand-blue-300 hover:shadow-md
15459
- ${stockServicesHook.loading || disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}
15460
- `,
15675
+ className: ` group relative p-4 rounded-lg border-2 transition-all duration-200 overflow-hidden flex flex-col items-center justify-center min-h-[120px] border-gray-200 hover:border-brand-blue-300 hover:shadow-md ${stockServicesHook.loading || disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"} `,
15461
15676
  style: {
15462
15677
  position: "relative"
15463
15678
  },
@@ -15474,7 +15689,7 @@ function TabStock({ prod, disabled, onSelected }) {
15474
15689
  ),
15475
15690
  /* @__PURE__ */ jsxs("div", { className: "relative z-10 flex flex-col items-center justify-center gap-2 text-center", children: [
15476
15691
  /* @__PURE__ */ jsx("span", { className: "text-base font-semibold text-gray-800", children: service.label }),
15477
- service.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-600 line-clamp-2", children: service.description })
15692
+ service.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-600 limbo-line-clamp-2", children: service.description })
15478
15693
  ] })
15479
15694
  ]
15480
15695
  },
@@ -15483,12 +15698,38 @@ function TabStock({ prod, disabled, onSelected }) {
15483
15698
  ] }) : (
15484
15699
  /* Vista del formulario del servicio seleccionado */
15485
15700
  /* @__PURE__ */ jsxs(Fragment, { children: [
15486
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-brand-blue-1000 mb-3", children: stockServicesHook.services.find((s) => s.slug === selectedService)?.label || "Búsqueda Stock" }),
15701
+ /* @__PURE__ */ jsx("h3", { className: "text-2xl font-semibold text-brand-blue-1000", children: stockServicesHook.services.find((s) => s.slug === selectedService)?.label || "Búsqueda Stock" }),
15487
15702
  imageParamsHook.loading && /* @__PURE__ */ jsx("div", { children: "Cargando parámetros..." }),
15488
15703
  imageParamsHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: imageParamsHook.error }),
15489
15704
  selectedService && renderDynamicForm(),
15490
- stockError && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: stockError }),
15491
- stockImages.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
15705
+ stockError && /* @__PURE__ */ jsx("div", { className: "alert alert-danger mt-4", children: stockError }),
15706
+ selectedFile && /* @__PURE__ */ jsx("div", { className: "mt-6 md:w-full mx-auto", children: /* @__PURE__ */ jsx(
15707
+ ImagePreview,
15708
+ {
15709
+ image: selectedFile,
15710
+ onDiscard: () => {
15711
+ setSelectedFile(null);
15712
+ setStockError(null);
15713
+ },
15714
+ onDownload: () => {
15715
+ },
15716
+ onSelect: (editedName) => {
15717
+ if (onSelect) {
15718
+ if (editedName && selectedFile && editedName !== selectedFile.name) {
15719
+ const newFile = new File([selectedFile], editedName, {
15720
+ type: selectedFile.type
15721
+ });
15722
+ onSelect(newFile);
15723
+ } else {
15724
+ onSelect(selectedFile);
15725
+ }
15726
+ }
15727
+ },
15728
+ showRetry: false,
15729
+ disabled
15730
+ }
15731
+ ) }),
15732
+ !selectedFile && stockImages.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
15492
15733
  /* @__PURE__ */ jsxs(
15493
15734
  "div",
15494
15735
  {
@@ -15524,7 +15765,19 @@ function TabStock({ prod, disabled, onSelected }) {
15524
15765
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 border-4 border-brand-blue-600 rounded-full border-t-transparent animate-spin" })
15525
15766
  ] }),
15526
15767
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-brand-blue-900", children: "Descargando..." })
15527
- ] }) : /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-brand-blue-800/0 group-hover:bg-brand-blue-800/50 transition-colors duration-200 flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-round-check-filled icon--2xl max-w-full max-h-full opacity-0 group-hover:opacity-100 transition-opacity duration-200" }) })
15768
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 bg-brand-blue-800/0 group-hover:bg-brand-blue-800/50 transition-colors duration-200 flex items-center justify-center flex-col", children: [
15769
+ /* @__PURE__ */ jsx("span", { className: "icon icon-round-check-filled icon--2xl max-w-full max-h-full opacity-0 group-hover:opacity-100 transition-opacity duration-200" }),
15770
+ /* @__PURE__ */ jsx(
15771
+ "span",
15772
+ {
15773
+ style: {
15774
+ textShadow: "1px 0 #0d542b, -1px 0 #0d542b, 0 1px #0d542b, 0 -0.5px #0d542b, 0.5px 0.5px #0d542b, -1px -1px #0d542b, 1px -1px #0d542b, -1px 1px #0d542b"
15775
+ },
15776
+ className: "max-w-full max-h-full opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-neutral-white-000",
15777
+ children: "Seleccionar"
15778
+ }
15779
+ )
15780
+ ] })
15528
15781
  ] })
15529
15782
  },
15530
15783
  img.id || idx
@@ -15580,7 +15833,7 @@ function TabStock({ prod, disabled, onSelected }) {
15580
15833
  )
15581
15834
  ] })
15582
15835
  ] }),
15583
- !stockLoading && stockImages.length === 0 && selectedService && dynamicForm.query && /* @__PURE__ */ jsxs("div", { className: "mt-6 text-center text-neutral-600 py-8 bg-neutral-50 rounded-lg", children: [
15836
+ !stockLoading && stockImages.length === 0 && selectedService && lastQuery && /* @__PURE__ */ jsxs("div", { className: "mt-6 text-center text-neutral-600 py-8 bg-neutral-50 rounded-lg", children: [
15584
15837
  /* @__PURE__ */ jsx("span", { className: "icon icon-search icon--lg mb-2" }),
15585
15838
  /* @__PURE__ */ jsxs("p", { children: [
15586
15839
  'No se encontraron imágenes para "',
@@ -15676,7 +15929,7 @@ const ValidatedImage = ({ src, alt, className, onError }) => {
15676
15929
  }
15677
15930
  );
15678
15931
  };
15679
- function TabPortals({ prod, disabled, onSelected }) {
15932
+ function TabPortals({ prod, disabled, onSelect }) {
15680
15933
  const lastSearchPayloadRef = React.useRef(null);
15681
15934
  const portalSourcesHook = usePortalSources(prod);
15682
15935
  const loadSavedState = (key, defaultValue) => {
@@ -15707,6 +15960,7 @@ function TabPortals({ prod, disabled, onSelected }) {
15707
15960
  () => loadSavedState("paginationInfo", null)
15708
15961
  );
15709
15962
  const [downloadingUrl, setDownloadingUrl] = useState(null);
15963
+ const [selectedFile, setSelectedFile] = useState(null);
15710
15964
  const searchCacheRef = useRef({});
15711
15965
  const [failedImages, setFailedImages] = useState(/* @__PURE__ */ new Set());
15712
15966
  React.useEffect(() => {
@@ -15882,7 +16136,7 @@ function TabPortals({ prod, disabled, onSelected }) {
15882
16136
  const file = new File([blob], filename, {
15883
16137
  type: blob.type || "image/jpeg"
15884
16138
  });
15885
- if (onSelected) onSelected(file);
16139
+ setSelectedFile(file);
15886
16140
  } catch (err) {
15887
16141
  setError(err.message || "No se pudo recuperar la imagen del portal");
15888
16142
  } finally {
@@ -15898,7 +16152,7 @@ function TabPortals({ prod, disabled, onSelected }) {
15898
16152
  if (!portalSourcesHook.sources.length) {
15899
16153
  return /* @__PURE__ */ jsx("div", { className: "alert alert-warning", children: "No hay portales externos disponibles." });
15900
16154
  }
15901
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
16155
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
15902
16156
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-brand-blue-1000", children: "Buscar en Portales Externos" }),
15903
16157
  /* @__PURE__ */ jsxs(
15904
16158
  "form",
@@ -15931,34 +16185,17 @@ function TabPortals({ prod, disabled, onSelected }) {
15931
16185
  type: "button",
15932
16186
  onClick: () => handlePortalToggle(portal.id),
15933
16187
  disabled,
15934
- className: `
15935
- relative p-4 rounded-lg border-2 transition-all duration-200
15936
- flex flex-col items-center justify-center min-h-[100px] gap-2
15937
- ${isSelected ? "border-brand-blue-600 bg-brand-blue-50" : "border-gray-200 bg-white hover:border-brand-blue-300"}
15938
- ${disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}
15939
- hover:shadow-md
15940
- `,
16188
+ className: ` relative p-4 rounded-lg border-2 transition-all duration-200 flex flex-col items-center justify-center min-h-[100px] gap-2 max-h-[110px] box-border ${isSelected ? "border-brand-blue-600 bg-brand-blue-50" : "border-gray-200 bg-white hover:border-brand-blue-300"} ${disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"} hover:shadow-md `,
15941
16189
  children: [
15942
- isSelected && /* @__PURE__ */ jsx("div", { className: "absolute top-2 right-2 w-6 h-6 bg-brand-blue-600 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-round-check-filled" }) }),
16190
+ isSelected && /* @__PURE__ */ jsx("div", { className: "absolute top-2 right-2 w-6 h-6 bg-brand-blue-600 rounded-full flex items-center justify-center bg-black", children: /* @__PURE__ */ jsx("span", { className: "icon icon-tick-white icon--sm" }) }),
15943
16191
  /* @__PURE__ */ jsx(
15944
- "svg",
16192
+ "img",
15945
16193
  {
15946
- className: `w-8 h-8 ${isSelected ? "text-brand-blue-600" : "text-gray-400"}`,
15947
- fill: "none",
15948
- stroke: "currentColor",
15949
- viewBox: "0 0 24 24",
15950
- children: /* @__PURE__ */ jsx(
15951
- "path",
15952
- {
15953
- strokeLinecap: "round",
15954
- strokeLinejoin: "round",
15955
- strokeWidth: 2,
15956
- d: "M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
15957
- }
15958
- )
16194
+ className: "object-auto bg-no-repeat bg-center max-w-9/12 max-h-9/12",
16195
+ src: portal.image,
16196
+ alt: portal.title
15959
16197
  }
15960
- ),
15961
- /* @__PURE__ */ jsx("span", { className: `text-sm font-medium text-center ${isSelected ? "text-brand-blue-900" : "text-gray-700"}`, children: portal.title })
16198
+ )
15962
16199
  ]
15963
16200
  },
15964
16201
  portal.id
@@ -15987,7 +16224,7 @@ function TabPortals({ prod, disabled, onSelected }) {
15987
16224
  }
15988
16225
  )
15989
16226
  ] }),
15990
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
16227
+ /* @__PURE__ */ jsxs("div", { className: "flex-col gap-1 hidden", children: [
15991
16228
  /* @__PURE__ */ jsx(
15992
16229
  "label",
15993
16230
  {
@@ -16000,7 +16237,7 @@ function TabPortals({ prod, disabled, onSelected }) {
16000
16237
  "select",
16001
16238
  {
16002
16239
  id: "portal-limit",
16003
- value: limit,
16240
+ value: 10,
16004
16241
  onChange: (e) => setLimit(Number(e.target.value)),
16005
16242
  className: "limbo-input",
16006
16243
  disabled,
@@ -16035,7 +16272,14 @@ function TabPortals({ prod, disabled, onSelected }) {
16035
16272
  {
16036
16273
  className: `flex items-center justify-between p-2 rounded ${result.status === 200 ? "bg-green-50" : "bg-red-50"}`,
16037
16274
  children: [
16038
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: result.title }),
16275
+ result.image ? /* @__PURE__ */ jsx(
16276
+ "img",
16277
+ {
16278
+ src: result.image,
16279
+ alt: result.title,
16280
+ className: "w-10 h-10 object-cover rounded"
16281
+ }
16282
+ ) : /* @__PURE__ */ jsx("span", { className: "font-medium", children: result.title }),
16039
16283
  /* @__PURE__ */ jsx(
16040
16284
  "span",
16041
16285
  {
@@ -16048,20 +16292,40 @@ function TabPortals({ prod, disabled, onSelected }) {
16048
16292
  portalId
16049
16293
  )) })
16050
16294
  ] }),
16051
- (images.length > 0 || loading) && /* @__PURE__ */ jsxs(Fragment, { children: [
16295
+ selectedFile && /* @__PURE__ */ jsx("div", { className: "mt-6 md:w-full mx-auto", children: /* @__PURE__ */ jsx(
16296
+ ImagePreview,
16297
+ {
16298
+ image: selectedFile,
16299
+ onDiscard: () => {
16300
+ setSelectedFile(null);
16301
+ setError(null);
16302
+ },
16303
+ onDownload: () => {
16304
+ },
16305
+ onSelect: (editedName) => {
16306
+ if (onSelect) {
16307
+ if (editedName && selectedFile && editedName !== selectedFile.name) {
16308
+ const newFile = new File([selectedFile], editedName, {
16309
+ type: selectedFile.type
16310
+ });
16311
+ onSelect(newFile);
16312
+ } else {
16313
+ onSelect(selectedFile);
16314
+ }
16315
+ }
16316
+ },
16317
+ showRetry: false,
16318
+ disabled
16319
+ }
16320
+ ) }),
16321
+ !selectedFile && (images.length > 0 || loading) && /* @__PURE__ */ jsxs(Fragment, { children: [
16052
16322
  /* @__PURE__ */ jsxs("div", { className: "mt-6 relative", "aria-live": "polite", children: [
16053
16323
  loading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-white/80 z-10 flex items-center justify-center rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
16054
16324
  /* @__PURE__ */ jsx("span", { className: "limbo-loader" }),
16055
16325
  /* @__PURE__ */ jsx("span", { className: "text-sm text-brand-blue-800", children: "Buscando imágenes..." })
16056
16326
  ] }) }),
16057
16327
  /* @__PURE__ */ jsxs("div", { className: "relative grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2", children: [
16058
- /* @__PURE__ */ jsx(
16059
- LoadingOverlay,
16060
- {
16061
- show: loading,
16062
- message: "Cargando imágenes..."
16063
- }
16064
- ),
16328
+ /* @__PURE__ */ jsx(LoadingOverlay, { show: loading, message: "Cargando imágenes..." }),
16065
16329
  images.map((img, idx) => {
16066
16330
  const imageKey = `${img.source}-${img.id || idx}`;
16067
16331
  const imageUrl = img.preview || img.thumbnail || img.url || img.full;
@@ -16119,7 +16383,7 @@ function TabPortals({ prod, disabled, onSelected }) {
16119
16383
  img.title && /* @__PURE__ */ jsx("div", { className: "px-2 pb-2 pt-1", children: /* @__PURE__ */ jsx(
16120
16384
  "p",
16121
16385
  {
16122
- className: "text-xs text-gray-600 truncate",
16386
+ className: "text-xs text-gray-600 limbo-truncate",
16123
16387
  title: img.title,
16124
16388
  children: img.title
16125
16389
  }
@@ -16188,7 +16452,7 @@ const TABS = [
16188
16452
  { id: "portals", label: "Otros portales" }
16189
16453
  ];
16190
16454
  function UploadForm({
16191
- onUpload,
16455
+ onSelect,
16192
16456
  disabled = false,
16193
16457
  apiKey,
16194
16458
  prod = false
@@ -16229,7 +16493,9 @@ function UploadForm({
16229
16493
  if (nextTab) {
16230
16494
  setActiveTab(nextTab.id);
16231
16495
  setTimeout(() => {
16232
- const tabButton = document.querySelector(`[data-upload-tab-id="${nextTab.id}"]`);
16496
+ const tabButton = document.querySelector(
16497
+ `[data-upload-tab-id="${nextTab.id}"]`
16498
+ );
16233
16499
  if (tabButton) {
16234
16500
  tabButton.focus();
16235
16501
  }
@@ -16240,7 +16506,7 @@ function UploadForm({
16240
16506
  /* @__PURE__ */ jsx("div", { className: "limbo-tabs-container", children: /* @__PURE__ */ jsx(
16241
16507
  "div",
16242
16508
  {
16243
- className: "limbo-tabs mb-6 min-w-fit",
16509
+ className: "limbo-tabs mb-1 min-w-fit",
16244
16510
  role: "tablist",
16245
16511
  "aria-label": "Opciones de subida de imagen",
16246
16512
  children: TABS.map((tab) => /* @__PURE__ */ jsx(
@@ -16248,7 +16514,7 @@ function UploadForm({
16248
16514
  {
16249
16515
  type: "button",
16250
16516
  "data-upload-tab-id": tab.id,
16251
- className: `text-nowrap limbo-tab${activeTab === tab.id ? " active" : ""}`,
16517
+ className: `text-nowrap limbo-tab${activeTab === tab.id ? " limbo-active" : ""}`,
16252
16518
  "aria-current": activeTab === tab.id ? "page" : void 0,
16253
16519
  "aria-selected": activeTab === tab.id,
16254
16520
  role: "tab",
@@ -16263,59 +16529,56 @@ function UploadForm({
16263
16529
  ))
16264
16530
  }
16265
16531
  ) }),
16266
- /* @__PURE__ */ jsxs("div", { className: "limbo-tab-content", role: "tabpanel", id: `upload-tabpanel-${activeTab}`, "aria-labelledby": `upload-tab-${activeTab}`, children: [
16267
- activeTab === "upload" && /* @__PURE__ */ jsx(
16268
- TabUpload,
16269
- {
16270
- file,
16271
- setFile,
16272
- previewUrl,
16273
- setPreviewUrl,
16274
- fileInputRef,
16275
- onUpload,
16276
- disabled
16277
- }
16278
- ),
16279
- activeTab === "ai" && /* @__PURE__ */ jsx(
16280
- TabAI,
16281
- {
16282
- apiKey,
16283
- prod,
16284
- disabled,
16285
- onGenerated: (file2) => {
16286
- setActiveTab("upload");
16287
- setFile(file2);
16288
- setPreviewUrl(URL.createObjectURL(file2));
16289
- }
16290
- }
16291
- ),
16292
- activeTab === "stock" && /* @__PURE__ */ jsx(
16293
- TabStock,
16294
- {
16295
- apiKey,
16296
- prod,
16297
- disabled,
16298
- onSelected: (file2) => {
16299
- setActiveTab("upload");
16300
- setFile(file2);
16301
- setPreviewUrl(URL.createObjectURL(file2));
16302
- }
16303
- }
16304
- ),
16305
- activeTab === "portals" && /* @__PURE__ */ jsx(
16306
- TabPortals,
16307
- {
16308
- apiKey,
16309
- prod,
16310
- disabled,
16311
- onSelected: (file2) => {
16312
- setActiveTab("upload");
16313
- setFile(file2);
16314
- setPreviewUrl(URL.createObjectURL(file2));
16315
- }
16316
- }
16317
- )
16318
- ] })
16532
+ /* @__PURE__ */ jsxs(
16533
+ "div",
16534
+ {
16535
+ className: "limbo-tab-content px-2 py-2",
16536
+ role: "tabpanel",
16537
+ id: `upload-tabpanel-${activeTab}`,
16538
+ "aria-labelledby": `upload-tab-${activeTab}`,
16539
+ children: [
16540
+ activeTab === "upload" && /* @__PURE__ */ jsx(
16541
+ TabUpload,
16542
+ {
16543
+ file,
16544
+ setFile,
16545
+ previewUrl,
16546
+ setPreviewUrl,
16547
+ fileInputRef,
16548
+ onSelect,
16549
+ disabled
16550
+ }
16551
+ ),
16552
+ activeTab === "ai" && /* @__PURE__ */ jsx(
16553
+ TabAI,
16554
+ {
16555
+ apiKey,
16556
+ prod,
16557
+ disabled,
16558
+ onSelect
16559
+ }
16560
+ ),
16561
+ activeTab === "stock" && /* @__PURE__ */ jsx(
16562
+ TabStock,
16563
+ {
16564
+ apiKey,
16565
+ prod,
16566
+ disabled,
16567
+ onSelect
16568
+ }
16569
+ ),
16570
+ activeTab === "portals" && /* @__PURE__ */ jsx(
16571
+ TabPortals,
16572
+ {
16573
+ apiKey,
16574
+ prod,
16575
+ disabled,
16576
+ onSelect
16577
+ }
16578
+ )
16579
+ ]
16580
+ }
16581
+ )
16319
16582
  ] });
16320
16583
  }
16321
16584
  const IS_BROWSER = typeof window !== "undefined" && typeof window.document !== "undefined";
@@ -19739,14 +20002,22 @@ function CropperView({
19739
20002
  onError = null,
19740
20003
  // Callback para manejar errores
19741
20004
  deleting = false,
19742
- onVariantCreated = null
20005
+ onVariantCreated = null,
19743
20006
  // Callback cuando se crea una variante
20007
+ onUpload = null,
20008
+ // Callback para subir imagen nueva (cuando image.file existe)
20009
+ uploading = false
20010
+ // Estado de carga del upload
19744
20011
  }) {
19745
20012
  const [showPreview, setShowPreview] = useState(false);
19746
20013
  const [previewUrl, setPreviewUrl] = useState(null);
19747
20014
  const [previewLoading, setPreviewLoading] = useState(false);
19748
20015
  const [showGrid, setShowGrid] = useState(true);
19749
20016
  const [shade, setShade] = useState(true);
20017
+ const [previewPosition, setPreviewPosition] = useState({ x: 20, y: 100 });
20018
+ const [isDragging, setIsDragging] = useState(false);
20019
+ const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
20020
+ const [showPreviewLightbox, setShowPreviewLightbox] = useState(false);
19750
20021
  const [initialLoading, setInitialLoading] = useState(true);
19751
20022
  const [cropTransitioning, setcropTransitioning] = useState(false);
19752
20023
  const [flipStates, setFlipStates] = useState({
@@ -20217,8 +20488,92 @@ function CropperView({
20217
20488
  setPreviewLoading(false);
20218
20489
  }
20219
20490
  }, [canExport, generatePreview, showPreview]);
20491
+ const handlePreviewMouseDown = useCallback(
20492
+ (e) => {
20493
+ if (!e.target.closest(".preview-modal-header")) return;
20494
+ setIsDragging(true);
20495
+ setDragStart({
20496
+ x: e.clientX - previewPosition.x,
20497
+ y: e.clientY - previewPosition.y
20498
+ });
20499
+ },
20500
+ [previewPosition]
20501
+ );
20502
+ const handlePreviewMouseMove = useCallback(
20503
+ (e) => {
20504
+ if (!isDragging) return;
20505
+ e.preventDefault();
20506
+ setPreviewPosition({
20507
+ x: e.clientX - dragStart.x,
20508
+ y: e.clientY - dragStart.y
20509
+ });
20510
+ },
20511
+ [isDragging, dragStart]
20512
+ );
20513
+ const handlePreviewMouseUp = useCallback(() => {
20514
+ setIsDragging(false);
20515
+ }, []);
20516
+ const handlePreviewTouchStart = useCallback(
20517
+ (e) => {
20518
+ if (!e.target.closest(".preview-modal-header")) return;
20519
+ const touch = e.touches[0];
20520
+ setIsDragging(true);
20521
+ setDragStart({
20522
+ x: touch.clientX - previewPosition.x,
20523
+ y: touch.clientY - previewPosition.y
20524
+ });
20525
+ },
20526
+ [previewPosition]
20527
+ );
20528
+ const handlePreviewTouchMove = useCallback(
20529
+ (e) => {
20530
+ if (!isDragging) return;
20531
+ e.preventDefault();
20532
+ const touch = e.touches[0];
20533
+ setPreviewPosition({
20534
+ x: touch.clientX - dragStart.x,
20535
+ y: touch.clientY - dragStart.y
20536
+ });
20537
+ },
20538
+ [isDragging, dragStart]
20539
+ );
20540
+ const handlePreviewTouchEnd = useCallback(() => {
20541
+ setIsDragging(false);
20542
+ }, []);
20543
+ useEffect(() => {
20544
+ if (isDragging) {
20545
+ document.addEventListener("mousemove", handlePreviewMouseMove);
20546
+ document.addEventListener("mouseup", handlePreviewMouseUp);
20547
+ document.addEventListener("touchmove", handlePreviewTouchMove, {
20548
+ passive: false
20549
+ });
20550
+ document.addEventListener("touchend", handlePreviewTouchEnd);
20551
+ return () => {
20552
+ document.removeEventListener("mousemove", handlePreviewMouseMove);
20553
+ document.removeEventListener("mouseup", handlePreviewMouseUp);
20554
+ document.removeEventListener("touchmove", handlePreviewTouchMove);
20555
+ document.removeEventListener("touchend", handlePreviewTouchEnd);
20556
+ };
20557
+ }
20558
+ }, [
20559
+ isDragging,
20560
+ handlePreviewMouseMove,
20561
+ handlePreviewMouseUp,
20562
+ handlePreviewTouchMove,
20563
+ handlePreviewTouchEnd
20564
+ ]);
20565
+ useEffect(() => {
20566
+ if (!showPreviewLightbox) return;
20567
+ const handleEsc = (e) => {
20568
+ if (e.key === "Escape") {
20569
+ setShowPreviewLightbox(false);
20570
+ }
20571
+ };
20572
+ document.addEventListener("keydown", handleEsc);
20573
+ return () => document.removeEventListener("keydown", handleEsc);
20574
+ }, [showPreviewLightbox]);
20220
20575
  const processSingleCrop = useCallback(
20221
- async (cropIndex) => {
20576
+ async (cropIndex, overrideImageId = null) => {
20222
20577
  const crop = crops[cropIndex];
20223
20578
  if (!crop) {
20224
20579
  throw new Error(`Crop ${cropIndex} no encontrado`);
@@ -20257,9 +20612,10 @@ function CropperView({
20257
20612
  };
20258
20613
  const variantWidth = Math.min(crop.width, 5e3);
20259
20614
  const variantHeight = Math.min(crop.height, 5e3);
20260
- const ts = Date.now();
20261
- const variantName = `${editableFilename}_${crop.label || "crop"}_${ts}`;
20262
- const result = await createCropVariant(image.id, cropParams, {
20615
+ const cropLabel = crop.label || "crop";
20616
+ const variantName = `${cropLabel}_${variantWidth}_${variantHeight}`;
20617
+ const imageId = overrideImageId || image.id;
20618
+ const result = await createCropVariant(imageId, cropParams, {
20263
20619
  name: variantName,
20264
20620
  width: variantWidth,
20265
20621
  height: variantHeight,
@@ -20268,7 +20624,7 @@ function CropperView({
20268
20624
  });
20269
20625
  if (result) {
20270
20626
  accessibilityManager?.announceSuccess(`Recorte creado: ${variantName}`);
20271
- onVariantCreated?.(image.id, result);
20627
+ onVariantCreated?.(imageId, result);
20272
20628
  return result;
20273
20629
  }
20274
20630
  throw new Error("No se pudo crear la variante");
@@ -20278,7 +20634,6 @@ function CropperView({
20278
20634
  activeCropIndex,
20279
20635
  cropData,
20280
20636
  effectiveImageInfo,
20281
- editableFilename,
20282
20637
  createCropVariant,
20283
20638
  image.id,
20284
20639
  onVariantCreated,
@@ -20301,9 +20656,25 @@ function CropperView({
20301
20656
  saveCurrentCropState();
20302
20657
  accessibilityManager?.announce("Creando recorte de la imagen");
20303
20658
  try {
20304
- const result = await processSingleCrop(activeCropIndex);
20659
+ let imageId = image.id;
20660
+ let uploadedAsset = null;
20661
+ if (image.file && onUpload) {
20662
+ accessibilityManager?.announce("Subiendo imagen...");
20663
+ const uploadResult = await onUpload(image.file);
20664
+ if (!uploadResult || !uploadResult.id) {
20665
+ throw new Error("No se pudo subir la imagen al servidor");
20666
+ }
20667
+ imageId = uploadResult.id;
20668
+ uploadedAsset = uploadResult;
20669
+ accessibilityManager?.announceSuccess("Imagen subida correctamente");
20670
+ }
20671
+ const result = await processSingleCrop(activeCropIndex, imageId);
20305
20672
  if (result) {
20306
- onSave(result);
20673
+ if (uploadedAsset) {
20674
+ onSave({ crops: [result], asset: uploadedAsset });
20675
+ } else {
20676
+ onSave(result);
20677
+ }
20307
20678
  }
20308
20679
  } catch (error) {
20309
20680
  console.warn("Error creating crop variant:", error);
@@ -20320,7 +20691,10 @@ function CropperView({
20320
20691
  processSingleCrop,
20321
20692
  activeCropIndex,
20322
20693
  onSave,
20323
- onError
20694
+ onError,
20695
+ image.file,
20696
+ image.id,
20697
+ onUpload
20324
20698
  ]);
20325
20699
  const performSaveMultipleCrops = useCallback(
20326
20700
  async (cropIndexes) => {
@@ -20328,34 +20702,55 @@ function CropperView({
20328
20702
  accessibilityManager?.announce(
20329
20703
  `Guardando ${cropIndexes.length} recortes...`
20330
20704
  );
20331
- const results = [];
20332
- const errors = [];
20333
- for (const index of cropIndexes) {
20334
- try {
20335
- const result = await processSingleCrop(index);
20336
- if (result) {
20337
- results.push(result);
20705
+ try {
20706
+ let imageId = image.id;
20707
+ let uploadedAsset = null;
20708
+ if (image.file && onUpload) {
20709
+ accessibilityManager?.announce("Subiendo imagen...");
20710
+ const uploadResult = await onUpload(image.file);
20711
+ if (!uploadResult || !uploadResult.id) {
20712
+ throw new Error("No se pudo subir la imagen al servidor");
20338
20713
  }
20339
- } catch (error) {
20340
- errors.push({
20341
- crop: crops[index]?.label || `Crop ${index}`,
20342
- error: error.message
20343
- });
20714
+ imageId = uploadResult.id;
20715
+ uploadedAsset = uploadResult;
20716
+ accessibilityManager?.announceSuccess("Imagen subida correctamente");
20344
20717
  }
20345
- }
20346
- if (results.length > 0) {
20347
- accessibilityManager?.announceSuccess(
20348
- `${results.length} recorte(s) guardado(s) correctamente`
20349
- );
20350
- if (results[0]) {
20351
- onSave(results[0]);
20718
+ const results = [];
20719
+ const errors = [];
20720
+ for (const index of cropIndexes) {
20721
+ try {
20722
+ const result = await processSingleCrop(index, imageId);
20723
+ if (result) {
20724
+ results.push(result);
20725
+ }
20726
+ } catch (error) {
20727
+ errors.push({
20728
+ crop: crops[index]?.label || `Crop ${index}`,
20729
+ error: error.message
20730
+ });
20731
+ }
20352
20732
  }
20353
- }
20354
- if (errors.length > 0) {
20355
- const errorMsg = `Errores al guardar algunos recortes:
20733
+ if (results.length > 0) {
20734
+ accessibilityManager?.announceSuccess(
20735
+ `${results.length} recorte(s) guardado(s) correctamente`
20736
+ );
20737
+ if (uploadedAsset) {
20738
+ onSave({ crops: results, asset: uploadedAsset });
20739
+ } else {
20740
+ onSave(results);
20741
+ }
20742
+ }
20743
+ if (errors.length > 0) {
20744
+ const errorMsg = `Errores al guardar algunos recortes:
20356
20745
  ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20746
+ accessibilityManager?.announceError(errorMsg);
20747
+ alert(errorMsg);
20748
+ }
20749
+ } catch (error) {
20750
+ const errorMsg = error.message || "Error al procesar los recortes";
20357
20751
  accessibilityManager?.announceError(errorMsg);
20358
20752
  alert(errorMsg);
20753
+ onError?.(error);
20359
20754
  }
20360
20755
  },
20361
20756
  [
@@ -20363,7 +20758,11 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20363
20758
  processSingleCrop,
20364
20759
  crops,
20365
20760
  accessibilityManager,
20366
- onSave
20761
+ onSave,
20762
+ image.file,
20763
+ image.id,
20764
+ onUpload,
20765
+ onError
20367
20766
  ]
20368
20767
  );
20369
20768
  const saveCrop = useCallback(async () => {
@@ -20440,8 +20839,8 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20440
20839
  `image/${globalThis.downloadFormat || image.mime_type.split("/")[1] || "webp"}`,
20441
20840
  0.9
20442
20841
  );
20443
- const cropName = crop.label.replace(/\.[^/.]+$/, "").replace(/\s+/g, "-").trim();
20444
- const filename = `${editableFilename}_${cropName || "crop"}`;
20842
+ const cropName = (crop.label || "crop").replace(/\.[^/.]+$/, "").replace(/\s+/g, "-").trim();
20843
+ const filename = `${cropName}_${crop.width}_${crop.height}`;
20445
20844
  await downloadImage(downloadUrl, filename, {
20446
20845
  accessibilityManager,
20447
20846
  onSuccess: (finalFilename) => {
@@ -20477,7 +20876,6 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20477
20876
  activeCropIndex,
20478
20877
  selection,
20479
20878
  image.mime_type,
20480
- editableFilename,
20481
20879
  accessibilityManager,
20482
20880
  imageRef,
20483
20881
  selectionRef
@@ -20564,6 +20962,27 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20564
20962
  }
20565
20963
  await performDownload();
20566
20964
  }, [validateCropNames, crops, performDownload, activeCropIndex]);
20965
+ const handleKeepOriginal = useCallback(async () => {
20966
+ if (!image.file || !onUpload) {
20967
+ onCancel();
20968
+ return;
20969
+ }
20970
+ try {
20971
+ accessibilityManager?.announce("Subiendo imagen original...");
20972
+ const uploadResult = await onUpload(image.file);
20973
+ if (!uploadResult || !uploadResult.id) {
20974
+ throw new Error("No se pudo subir la imagen al servidor");
20975
+ }
20976
+ accessibilityManager?.announceSuccess("Imagen guardada correctamente");
20977
+ onSave({ asset: uploadResult, crops: [] });
20978
+ } catch (error) {
20979
+ console.error("Error guardando imagen original:", error);
20980
+ const errorMsg = error.message || "No se pudo guardar la imagen. Inténtalo de nuevo.";
20981
+ accessibilityManager?.announceError(errorMsg);
20982
+ alert(errorMsg);
20983
+ onError?.(error);
20984
+ }
20985
+ }, [image.file, onUpload, onCancel, onSave, onError, accessibilityManager]);
20567
20986
  useEffect(() => {
20568
20987
  setShowPreview(false);
20569
20988
  setPreviewUrl(null);
@@ -20682,7 +21101,7 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20682
21101
  };
20683
21102
  }, [canvasRef]);
20684
21103
  if (!image) return null;
20685
- return /* @__PURE__ */ jsxs("div", { className: "limbo-cropper-view px-2 border-2 border-gray-200/50 rounded-lg max-w-7xl mx-auto w-full min-w-full h-full min-h-full flex flex-col", children: [
21104
+ return /* @__PURE__ */ jsxs("div", { className: "limbo-cropper-view px-2 border-2 border-gray-200/50 rounded-lg max-w-7xl mx-auto w-full min-w-full h-full min-h-full flex flex-col", children: [
20686
21105
  /* @__PURE__ */ jsxs("div", { className: "limbo-cropper-header flex flex-row justify-between items-start flex-wrap mx-2 py-2 border-b border-gray-200 bg-white z-10 flex-shrink-0 lg:gap-2", children: [
20687
21106
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-fit space-y-2 max-w-fit", children: [
20688
21107
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
@@ -20694,7 +21113,7 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20694
21113
  /* @__PURE__ */ jsx("div", { className: "", children: editableFilename + "." + (globalThis.downloadFormat || image.mime_type.split("/")[1] || "webp") })
20695
21114
  ] })
20696
21115
  ] }),
20697
- /* @__PURE__ */ jsxs("div", { className: "flex flex-row self-end gap-2 w-full max-w-fit sm:w-auto", children: [
21116
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-row self-end gap-2 w-full max-w-fit sm:w-auto flex-wrap", children: [
20698
21117
  /* @__PURE__ */ jsxs(
20699
21118
  "button",
20700
21119
  {
@@ -20727,29 +21146,50 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20727
21146
  ] })
20728
21147
  ] }),
20729
21148
  /* @__PURE__ */ jsxs("div", { className: "limbo-cropper-status bg-white border-gray-100 py-0 pb-0 flex-shrink-0", children: [
20730
- variantError && /* @__PURE__ */ jsxs("div", { className: "alert alert-danger mb-2 text-sm", role: "alert", children: [
20731
- /* @__PURE__ */ jsx("strong", { children: "Error:" }),
20732
- " ",
20733
- variantError
20734
- ] }),
20735
- imageInfo && /* @__PURE__ */ jsxs("div", { className: "alert alert-secondary mb-2 text-sm", role: "status", children: [
20736
- /* @__PURE__ */ jsx("strong", { children: "Original:" }),
20737
- " ",
20738
- imageInfo.naturalWidth,
20739
- " ×",
20740
- " ",
20741
- imageInfo.naturalHeight,
20742
- " px",
20743
- imageInfo.currentWidth !== imageInfo.naturalWidth && /* @__PURE__ */ jsxs("span", { className: "ml-2 sm:ml-3 text-xs block sm:inline", children: [
20744
- "(Mostrada: ",
20745
- Math.round(imageInfo.currentWidth),
20746
- " ×",
20747
- " ",
20748
- Math.round(imageInfo.currentHeight),
20749
- " px)"
20750
- ] })
20751
- ] }),
20752
- !canExport && /* @__PURE__ */ jsx("div", { className: "alert alert-warning mb-2 text-sm", role: "alert", children: "⚠️ No se puede exportar por restricciones CORS" }),
21149
+ variantError && /* @__PURE__ */ jsxs(
21150
+ "div",
21151
+ {
21152
+ className: "alert alert-danger mb-2 text-sm",
21153
+ role: "alert",
21154
+ children: [
21155
+ /* @__PURE__ */ jsx("strong", { children: "Error:" }),
21156
+ " ",
21157
+ variantError
21158
+ ]
21159
+ }
21160
+ ),
21161
+ imageInfo && /* @__PURE__ */ jsxs(
21162
+ "div",
21163
+ {
21164
+ className: "alert alert-secondary mb-2 text-sm",
21165
+ role: "status",
21166
+ children: [
21167
+ /* @__PURE__ */ jsx("strong", { children: "Original:" }),
21168
+ " ",
21169
+ imageInfo.naturalWidth,
21170
+ " ×",
21171
+ " ",
21172
+ imageInfo.naturalHeight,
21173
+ " px",
21174
+ imageInfo.currentWidth !== imageInfo.naturalWidth && /* @__PURE__ */ jsxs("span", { className: "ml-2 sm:ml-3 text-xs block sm:inline", children: [
21175
+ "(Mostrada: ",
21176
+ Math.round(imageInfo.currentWidth),
21177
+ " ×",
21178
+ " ",
21179
+ Math.round(imageInfo.currentHeight),
21180
+ " px)"
21181
+ ] })
21182
+ ]
21183
+ }
21184
+ ),
21185
+ !canExport && /* @__PURE__ */ jsx(
21186
+ "div",
21187
+ {
21188
+ className: "alert alert-warning mb-2 text-sm",
21189
+ role: "alert",
21190
+ children: "⚠️ No se puede exportar por restricciones CORS"
21191
+ }
21192
+ ),
20753
21193
  /* @__PURE__ */ jsxs("div", { className: "bg-white border-b border-gray-200 py-2 mx-2 flex-shrink-0 sticky top-0 z-10 mb-1", children: [
20754
21194
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
20755
21195
  /* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold text-gray-700", children: [
@@ -20802,7 +21242,7 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20802
21242
  maxLength: 25,
20803
21243
  onClick: (e) => e.stopPropagation()
20804
21244
  }
20805
- ) : /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-800 truncate", children: crop.label }),
21245
+ ) : /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-800 limbo-truncate", children: crop.label }),
20806
21246
  crop.required && /* @__PURE__ */ jsx("span", { className: "text-[10px] px-1 py-0.5 bg-red-100 text-red-700 rounded whitespace-nowrap", children: "Oblig." }),
20807
21247
  crop.isCustom && activeCropIndex !== index && /* @__PURE__ */ jsx("span", { className: "text-[10px] px-1 py-0.5 bg-purple-100 text-purple-700 rounded whitespace-nowrap", children: "Custom" })
20808
21248
  ] }) }),
@@ -20868,37 +21308,6 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20868
21308
  ] }),
20869
21309
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col lg:flex-row flex-1 w-full overflow-hidden lg:max-h-130 mx-2 py-1", children: [
20870
21310
  /* @__PURE__ */ jsx("div", { className: "relative flex-1 lg:flex-[2] h-full flex-shrink-0 flex-grow-1 order-0 min-h-100 lg:min-h-130", children: /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 bg-white rounded-lg border-2 border-gray-200 overflow-hidden", children: [
20871
- showPreview && /* @__PURE__ */ jsxs("div", { className: "absolute lg:top-2 sm:top-4 right-2 sm:right-4 bg-white rounded-lg border border-gray-300 p-2 sm:p-3 z-50 shadow-xl w-48 sm:w-64 max-w-[calc(50%-1rem)] sm:max-w-[calc(50%-2rem)] md:min-w-1/4", children: [
20872
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mb-2", children: [
20873
- /* @__PURE__ */ jsx("h3", { className: "text-xs font-semibold text-gray-700", children: "Vista previa" }),
20874
- /* @__PURE__ */ jsx(
20875
- "button",
20876
- {
20877
- onClick: () => {
20878
- setShowPreview(false);
20879
- setPreviewUrl(null);
20880
- },
20881
- className: "text-gray-400 hover:text-gray-600 p-1 -m-1",
20882
- "aria-label": "Cerrar vista previa",
20883
- children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small text-xs" })
20884
- }
20885
- )
20886
- ] }),
20887
- /* @__PURE__ */ jsx("div", { className: "bg-gray-50 rounded p-2 flex justify-center items-center min-h-[80px] relative", children: !previewUrl ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center", children: [
20888
- /* @__PURE__ */ jsx("div", { className: "animate-spin w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full mb-2" }),
20889
- /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500", children: "Generando..." })
20890
- ] }) : /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full flex justify-center items-center", children: [
20891
- /* @__PURE__ */ jsx(
20892
- "img",
20893
- {
20894
- src: previewUrl,
20895
- alt: "Vista previa del recorte",
20896
- className: "max-w-full max-h-80 object-contain rounded shadow-sm"
20897
- }
20898
- ),
20899
- previewLoading && /* @__PURE__ */ jsx("div", { className: "absolute top-1 right-1 bg-blue-500 rounded-full p-1", children: /* @__PURE__ */ jsx("div", { className: "animate-spin w-3 h-3 border border-white border-t-transparent rounded-full" }) })
20900
- ] }) })
20901
- ] }),
20902
21311
  /* @__PURE__ */ jsx(
20903
21312
  LoadingOverlay,
20904
21313
  {
@@ -21003,7 +21412,7 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21003
21412
  }
21004
21413
  )
21005
21414
  ] }) }),
21006
- /* @__PURE__ */ jsx("div", { className: "w-full lg:max-w-80 mt-2 lg:my-0 overflow-y-auto bg-white border-t lg:border-t-0 lg:border-l border-gray-200 flex-shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "h-full mt-2 lg:my-0 me-2 lg:mx-2 space-y-2 grid grid-cols-2 md:grid-cols-3 flex-wrap md:flex-nowrap items-start flex-row gap-2 lg:gap-0 lg:block", children: [
21415
+ /* @__PURE__ */ jsx("div", { className: "w-full lg:max-w-80 mt-2 lg:my-0 overflow-y-auto bg-white border-t lg:border-t-0 lg:border-l border-gray-200 flex-shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "h-full mt-2 lg:my-0 limbo-me-2 lg:mx-2 space-y-2 grid grid-cols-2 md:grid-cols-3 flex-wrap md:flex-nowrap items-start flex-row gap-2 lg:gap-0 lg:block", children: [
21007
21416
  /* @__PURE__ */ jsxs("div", { className: "bg-white rounded border w-full border-gray-200 mb-2", children: [
21008
21417
  /* @__PURE__ */ jsx(
21009
21418
  "button",
@@ -21048,7 +21457,7 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21048
21457
  title: "Mover arriba-izquierda",
21049
21458
  disabled: creatingVariant,
21050
21459
  "aria-label": "Mover imagen hacia arriba-izquierda",
21051
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-blue icon--md -rotate-45" }) })
21460
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-blue icon--md limbo--rotate-45" }) })
21052
21461
  }
21053
21462
  ),
21054
21463
  /* @__PURE__ */ jsx(
@@ -21159,7 +21568,7 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21159
21568
  title: "Mover abajo-izquierda",
21160
21569
  disabled: creatingVariant,
21161
21570
  "aria-label": "Mover imagen hacia abajo-izquierda",
21162
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-blue icon--md -rotate-145" }) })
21571
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-blue icon--md limbo--rotate-145" }) })
21163
21572
  }
21164
21573
  ),
21165
21574
  /* @__PURE__ */ jsx(
@@ -21315,7 +21724,7 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21315
21724
  disabled: creatingVariant,
21316
21725
  "aria-label": "Reiniciar área de selección",
21317
21726
  children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
21318
- /* @__PURE__ */ jsx("span", { className: "icon icon-refresh icon--sm lign-[middle!important] " }),
21727
+ /* @__PURE__ */ jsx("span", { className: "icon icon-refresh icon--sm limbo-lign-[middle!important] " }),
21319
21728
  " ",
21320
21729
  "Reset selección"
21321
21730
  ] }) })
@@ -21363,12 +21772,12 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21363
21772
  "aria-label": "Activar/desactivar grid",
21364
21773
  children: [
21365
21774
  /* @__PURE__ */ jsxs("span", { className: "text-sm flex items-center", children: [
21366
- /* @__PURE__ */ jsx("span", { className: "icon icon-area-blue me-1" }),
21775
+ /* @__PURE__ */ jsx("span", { className: "icon icon-area-blue limbo-me-1" }),
21367
21776
  " ",
21368
21777
  "Cuadrícula"
21369
21778
  ] }),
21370
21779
  /* @__PURE__ */ jsx("span", { className: "text-xs", children: showGrid ? /* @__PURE__ */ jsxs(Fragment, { children: [
21371
- /* @__PURE__ */ jsx("span", { className: "icon icon-tick icon--xs align-[middle!important] -mt-0.5" }),
21780
+ /* @__PURE__ */ jsx("span", { className: "icon icon-tick icon--xs align-[middle!important] limbo--mt-0.5" }),
21372
21781
  " ",
21373
21782
  "Activo"
21374
21783
  ] }) : "Inactivo" })
@@ -21385,12 +21794,12 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21385
21794
  "aria-label": "Activar/desactivar sombreado",
21386
21795
  children: [
21387
21796
  /* @__PURE__ */ jsxs("span", { className: "text-sm flex items-center", children: [
21388
- /* @__PURE__ */ jsx("span", { className: "icon icon-comparison-blue me-1" }),
21797
+ /* @__PURE__ */ jsx("span", { className: "icon icon-comparison-blue limbo-me-1" }),
21389
21798
  " ",
21390
21799
  "Tablero"
21391
21800
  ] }),
21392
21801
  /* @__PURE__ */ jsx("span", { className: "text-xs", children: shade ? /* @__PURE__ */ jsxs(Fragment, { children: [
21393
- /* @__PURE__ */ jsx("span", { className: "icon icon-tick icon--xs align-[middle!important] -mt-0.5" }),
21802
+ /* @__PURE__ */ jsx("span", { className: "icon icon-tick icon--xs align-[middle!important] limbo--mt-0.5" }),
21394
21803
  " ",
21395
21804
  "Activo"
21396
21805
  ] }) : "Inactivo" })
@@ -21403,7 +21812,7 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21403
21812
  ] })
21404
21813
  ] }) })
21405
21814
  ] }),
21406
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between flex-shrink-0 border-t border-gray-200 bg-white mx-2 mt-1 py-2", children: [
21815
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between flex-shrink-0 border-t border-gray-200 bg-white mx-2 mt-1 py-2 lg:gap-0", children: [
21407
21816
  /* @__PURE__ */ jsxs(
21408
21817
  "button",
21409
21818
  {
@@ -21413,36 +21822,50 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21413
21822
  children: [
21414
21823
  /* @__PURE__ */ jsx("span", { className: "icon icon-lightbulb text-yellow-500" }),
21415
21824
  /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-blue-800", children: "Guía de uso" }),
21416
- /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-down scale-75 -rotate-145" })
21825
+ /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-down scale-75 limbo--rotate-145" })
21417
21826
  ]
21418
21827
  }
21419
21828
  ),
21420
- /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-2 items-center justify-end", children: [
21829
+ /* @__PURE__ */ jsx(
21830
+ "button",
21831
+ {
21832
+ onClick: preview,
21833
+ disabled: creatingVariant || !canExport,
21834
+ className: `px-3 cursor-pointer py-1.5 text-sm rounded transition-colors ${showPreview ? "bg-red-600 hover:bg-red-700 text-white" : "bg-blue-600 hover:bg-blue-700 text-white"} disabled:opacity-50 disabled:cursor-not-allowed`,
21835
+ "aria-label": "Vista previa",
21836
+ title: "Mostrar/Ocultar vista previa",
21837
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
21838
+ /* @__PURE__ */ jsx("span", { className: "icon icon-search-white icon--sm" }),
21839
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline whitespace-nowrap", children: showPreview ? "Cerrar previa" : "Vista previa" })
21840
+ ] })
21841
+ }
21842
+ ),
21843
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-1 lg:gap-2 items-center justify-end", children: [
21421
21844
  /* @__PURE__ */ jsx(
21422
21845
  "button",
21423
21846
  {
21424
- onClick: preview,
21847
+ onClick: handleDownload,
21425
21848
  disabled: creatingVariant || !canExport,
21426
- className: `px-3 cursor-pointer py-1.5 text-sm rounded transition-colors ${showPreview ? "bg-red-600 hover:bg-red-700 text-white" : "bg-blue-600 hover:bg-blue-700 text-white"} disabled:opacity-50 disabled:cursor-not-allowed`,
21427
- "aria-label": "Vista previa",
21428
- title: "Mostrar/Ocultar vista previa",
21849
+ className: "cursor-pointer px-3 py-1.5 text-sm rounded bg-brand-blue-800 hover:bg-brand-blue-1000 text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
21850
+ "aria-label": "Descargar",
21851
+ title: "Descargar recorte sin guardar",
21429
21852
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
21430
- /* @__PURE__ */ jsx("span", { className: "icon icon-search-white icon--sm" }),
21431
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline whitespace-nowrap", children: showPreview ? "Cerrar previa" : "Vista previa" })
21853
+ /* @__PURE__ */ jsx("span", { className: "icon icon-download-white icon--sm" }),
21854
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline whitespace-nowrap", children: "Descargar" })
21432
21855
  ] })
21433
21856
  }
21434
21857
  ),
21435
- /* @__PURE__ */ jsx(
21858
+ image.file && /* @__PURE__ */ jsx(
21436
21859
  "button",
21437
21860
  {
21438
- onClick: handleDownload,
21439
- disabled: creatingVariant || !canExport,
21440
- className: "cursor-pointer px-3 py-1.5 text-sm rounded bg-brand-blue-800 hover:bg-brand-blue-1000 text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
21441
- "aria-label": "Descargar",
21442
- title: "Descargar recorte sin guardar",
21861
+ onClick: handleKeepOriginal,
21862
+ disabled: creatingVariant || uploading,
21863
+ className: `px-3 cursor-pointer py-1.5 text-sm rounded bg-teal-600 hover:bg-teal-700 text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed`,
21864
+ "aria-label": "Conservar solo imagen original sin crear recortes",
21865
+ title: "Subir y guardar la imagen original sin crear recortes",
21443
21866
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
21444
- /* @__PURE__ */ jsx("span", { className: "icon icon-download-white icon--sm" }),
21445
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline whitespace-nowrap", children: "Descargar" })
21867
+ /* @__PURE__ */ jsx("span", { className: "icon icon-tick-white icon--sm" }),
21868
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline whitespace-nowrap", children: "Conservar original" })
21446
21869
  ] })
21447
21870
  }
21448
21871
  ),
@@ -21452,7 +21875,6 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21452
21875
  onClick: saveCrop,
21453
21876
  disabled: creatingVariant || !cropData || !effectiveImageInfo || !canExport || !state.isReady,
21454
21877
  className: "px-3 cursor-pointer py-1.5 text-sm rounded bg-green-600 hover:bg-green-700 text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
21455
- "aria-label": "Guardar",
21456
21878
  title: "Guardar imagen recortada",
21457
21879
  children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: creatingVariant ? /* @__PURE__ */ jsxs(Fragment, { children: [
21458
21880
  /* @__PURE__ */ jsx("span", { className: "icon icon-save-white icon--sm" }),
@@ -21505,16 +21927,36 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21505
21927
  ),
21506
21928
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
21507
21929
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
21508
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-800 truncate", children: crop.label }),
21930
+ /* @__PURE__ */ jsxs(
21931
+ "span",
21932
+ {
21933
+ title: "Nombre del archivo a descargar",
21934
+ className: "text-sm font-medium text-gray-800 truncate",
21935
+ children: [
21936
+ crop.label,
21937
+ "_",
21938
+ crop.width,
21939
+ "_",
21940
+ crop.height
21941
+ ]
21942
+ }
21943
+ ),
21509
21944
  crop.required && /* @__PURE__ */ jsx("span", { className: "text-xs px-1.5 py-0.5 bg-red-100 text-red-700 rounded whitespace-nowrap", children: "Obligatorio" }),
21510
21945
  !isConfigured && !crop.required && /* @__PURE__ */ jsx("span", { className: "text-xs px-1.5 py-0.5 bg-orange-100 text-orange-700 rounded whitespace-nowrap", children: "Sin configurar" })
21511
21946
  ] }),
21512
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500", children: [
21513
- crop.width,
21514
- " × ",
21515
- crop.height,
21516
- " px"
21517
- ] })
21947
+ /* @__PURE__ */ jsxs(
21948
+ "span",
21949
+ {
21950
+ title: "Dimensiones del recorte",
21951
+ className: "text-xs text-gray-500",
21952
+ children: [
21953
+ crop.width,
21954
+ " × ",
21955
+ crop.height,
21956
+ " px"
21957
+ ]
21958
+ }
21959
+ )
21518
21960
  ] })
21519
21961
  ]
21520
21962
  },
@@ -21658,7 +22100,7 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21658
22100
  /* @__PURE__ */ jsxs("span", { className: "inline-flex gap-1 mx-1", children: [
21659
22101
  /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-box rotate-180 icon--sm" }),
21660
22102
  /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-box icon--sm" }),
21661
- /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-box -rotate-90 icon--sm" }),
22103
+ /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-box limbo--rotate-90 icon--sm" }),
21662
22104
  /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-box rotate-90 icon--sm" })
21663
22105
  ] }),
21664
22106
  "Mueven la selección en incrementos de 10px"
@@ -21774,7 +22216,165 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21774
22216
  children: "Entendido"
21775
22217
  }
21776
22218
  ) })
21777
- ] }) })
22219
+ ] }) }),
22220
+ showPreview && /* @__PURE__ */ jsx(
22221
+ "div",
22222
+ {
22223
+ className: "min-w-[90%] fixed md:absolute inset-0 md:inset-auto bg-black/50 md:bg-transparent flex items-center justify-center md:block z-[9999] md:z-50 pointer-events-none",
22224
+ onClick: (e) => {
22225
+ if (e.target === e.currentTarget && window.innerWidth < 768) {
22226
+ setShowPreview(false);
22227
+ setPreviewUrl(null);
22228
+ }
22229
+ },
22230
+ children: /* @__PURE__ */ jsxs(
22231
+ "div",
22232
+ {
22233
+ className: "pointer-events-auto bg-white rounded-lg shadow-2xl border-2 border-brand-blue-800 overflow-hidden flex flex-col max-w-md w-full mx-4 md:mx-0 max-h-[80vh] md:max-h-[600px]",
22234
+ style: {
22235
+ // En desktop: posicionamiento absoluto con drag y width fijo
22236
+ // En móvil: centrado con flexbox (sin drag)
22237
+ ...window.innerWidth >= 768 ? {
22238
+ position: "absolute",
22239
+ left: `${previewPosition.x}px`,
22240
+ top: `${previewPosition.y}px`,
22241
+ width: "420px",
22242
+ cursor: isDragging ? "grabbing" : "default"
22243
+ } : {}
22244
+ },
22245
+ onMouseDown: handlePreviewMouseDown,
22246
+ onTouchStart: handlePreviewTouchStart,
22247
+ children: [
22248
+ /* @__PURE__ */ jsxs(
22249
+ "div",
22250
+ {
22251
+ className: "limbo-preview-modal-header px-4 py-3 bg-gradient-to-r from-brand-blue-800 to-brand-blue-1000 text-white flex items-center justify-between cursor-grab active:cursor-grabbing select-none",
22252
+ style: { cursor: isDragging ? "grabbing" : "grab" },
22253
+ children: [
22254
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
22255
+ /* @__PURE__ */ jsx("span", { className: "icon icon-search-white icon--sm" }),
22256
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Vista previa" })
22257
+ ] }),
22258
+ /* @__PURE__ */ jsx(
22259
+ "button",
22260
+ {
22261
+ onClick: () => {
22262
+ setShowPreview(false);
22263
+ setPreviewUrl(null);
22264
+ },
22265
+ className: "flex p-1 hover:bg-white/20 rounded transition-colors cursor-pointer aspect-square min-w-fit min-h-fit",
22266
+ "aria-label": "Cerrar vista previa",
22267
+ title: "Cerrar vista previa",
22268
+ children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small-white icon--sm p-0 m-0 py-auto my-auto mx-auto" })
22269
+ }
22270
+ )
22271
+ ]
22272
+ }
22273
+ ),
22274
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto bg-gray-50 p-4", children: previewLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full min-h-[200px]", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
22275
+ /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-3" }),
22276
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "Generando vista previa..." })
22277
+ ] }) }) : previewUrl ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3", children: [
22278
+ /* @__PURE__ */ jsxs(
22279
+ "div",
22280
+ {
22281
+ className: "relative w-full bg-white rounded-lg border border-gray-200 overflow-hidden shadow-sm cursor-zoom-in hover:ring-2 hover:ring-brand-blue-800 transition-all",
22282
+ onClick: () => setShowPreviewLightbox(true),
22283
+ role: "button",
22284
+ tabIndex: 0,
22285
+ onKeyDown: (e) => {
22286
+ if (e.key === "Enter" || e.key === " ") {
22287
+ e.preventDefault();
22288
+ setShowPreviewLightbox(true);
22289
+ }
22290
+ },
22291
+ "aria-label": "Click para ver recorte en tamaño completo",
22292
+ title: "Click para ampliar",
22293
+ children: [
22294
+ /* @__PURE__ */ jsx(
22295
+ "img",
22296
+ {
22297
+ src: previewUrl,
22298
+ alt: "Vista previa del recorte",
22299
+ className: "w-full h-auto object-contain max-h-[400px]"
22300
+ }
22301
+ ),
22302
+ /* @__PURE__ */ jsxs("div", { className: "absolute top-2 right-2 bg-black/60 text-white px-2 py-1 rounded text-xs flex items-center gap-1", children: [
22303
+ /* @__PURE__ */ jsx("span", { className: "icon icon-search-white icon--xs" }),
22304
+ /* @__PURE__ */ jsx("span", { children: "Ampliar" })
22305
+ ] })
22306
+ ]
22307
+ }
22308
+ ),
22309
+ /* @__PURE__ */ jsxs("div", { className: "text-center text-xs text-gray-500", children: [
22310
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-gray-700 mb-1", children: activeCrop?.label || "Recorte" }),
22311
+ /* @__PURE__ */ jsxs("p", { children: [
22312
+ activeCrop?.width,
22313
+ " × ",
22314
+ activeCrop?.height,
22315
+ " px"
22316
+ ] })
22317
+ ] })
22318
+ ] }) : /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full min-h-[200px]", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: "No se pudo generar la vista previa" }) }) }),
22319
+ /* @__PURE__ */ jsx("div", { className: "px-4 py-2 bg-gray-100 border-t border-gray-200", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-600 text-center", children: [
22320
+ /* @__PURE__ */ jsxs("span", { className: "hidden md:inline", children: [
22321
+ "Arrastra desde el título para mover •",
22322
+ " "
22323
+ ] }),
22324
+ "Actualización automática al editar"
22325
+ ] }) })
22326
+ ]
22327
+ }
22328
+ )
22329
+ }
22330
+ ),
22331
+ showPreviewLightbox && previewUrl && /* @__PURE__ */ jsxs(
22332
+ "div",
22333
+ {
22334
+ className: "fixed inset-0 bg-black/95 z-[10001] flex items-center justify-center p-4",
22335
+ onClick: () => setShowPreviewLightbox(false),
22336
+ role: "dialog",
22337
+ "aria-modal": "true",
22338
+ "aria-label": "Vista de recorte en tamaño completo",
22339
+ children: [
22340
+ /* @__PURE__ */ jsx(
22341
+ "button",
22342
+ {
22343
+ onClick: () => setShowPreviewLightbox(false),
22344
+ className: "absolute top-4 right-4 p-3 bg-white/10 hover:bg-white/20 rounded-full transition-colors cursor-pointer z-10",
22345
+ "aria-label": "Cerrar vista ampliada",
22346
+ title: "Cerrar (Esc)",
22347
+ children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small-white icon--lg" })
22348
+ }
22349
+ ),
22350
+ /* @__PURE__ */ jsxs("div", { className: "absolute top-4 left-4 bg-black/60 text-white px-4 py-3 rounded-lg text-sm backdrop-blur-sm", children: [
22351
+ /* @__PURE__ */ jsx("p", { className: "font-semibold text-base mb-1", children: activeCrop?.label || "Recorte" }),
22352
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-300", children: [
22353
+ activeCrop?.width,
22354
+ " × ",
22355
+ activeCrop?.height,
22356
+ " px"
22357
+ ] })
22358
+ ] }),
22359
+ /* @__PURE__ */ jsx(
22360
+ "div",
22361
+ {
22362
+ className: "relative max-w-[95vw] max-h-[95vh] w-full h-full flex items-center justify-center",
22363
+ onClick: (e) => e.stopPropagation(),
22364
+ children: /* @__PURE__ */ jsx(
22365
+ "img",
22366
+ {
22367
+ src: previewUrl,
22368
+ alt: "Vista completa del recorte",
22369
+ className: "max-w-full max-h-full object-contain rounded-lg shadow-2xl"
22370
+ }
22371
+ )
22372
+ }
22373
+ ),
22374
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-4 left-1/2 limbo--translate-x-1/2 bg-black/60 text-white px-4 py-2 rounded-lg text-xs text-center backdrop-blur-sm", children: "Click fuera de la imagen o presiona ESC para cerrar" })
22375
+ ]
22376
+ }
22377
+ )
21778
22378
  ] });
21779
22379
  }
21780
22380
  function Pagination({
@@ -21832,10 +22432,7 @@ function Pagination({
21832
22432
  {
21833
22433
  onClick: handlePrevious,
21834
22434
  disabled: disabled || currentPage <= 1,
21835
- className: `
21836
- px-3 py-2 text-sm font-medium rounded-md transition-colors disabled:cursor-default cursor-pointer
21837
- ${disabled || currentPage <= 1 ? "text-gray-400 bg-gray-100 cursor-not-allowed" : "text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:ring-2 focus:ring-blue-500"}
21838
- `,
22435
+ className: ` px-3 py-2 text-sm font-medium rounded-md transition-colors disabled:cursor-default cursor-pointer ${disabled || currentPage <= 1 ? "text-gray-400 bg-gray-100 cursor-not-allowed" : "text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:ring-2 focus:ring-blue-500"} `,
21839
22436
  children: [
21840
22437
  /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-white icon--sm" }),
21841
22438
  " Anterior"
@@ -21847,10 +22444,7 @@ function Pagination({
21847
22444
  {
21848
22445
  onClick: () => handlePageClick(page),
21849
22446
  disabled,
21850
- className: `
21851
- px-3 py-2 text-sm font-medium rounded-md transition-colors disabled:cursor-default cursor-pointer
21852
- ${page === currentPage ? "bg-blue-600 text-white border border-blue-600" : disabled ? "text-gray-400 bg-gray-100 cursor-not-allowed" : "text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:ring-2 focus:ring-blue-500"}
21853
- `,
22447
+ className: ` px-3 py-2 text-sm font-medium rounded-md transition-colors disabled:cursor-default cursor-pointer ${page === currentPage ? "bg-blue-600 text-white border border-blue-600" : disabled ? "text-gray-400 bg-gray-100 cursor-not-allowed" : "text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:ring-2 focus:ring-blue-500"} `,
21854
22448
  children: page
21855
22449
  }
21856
22450
  ) }, `page-${page}-${index}`)) }),
@@ -21859,10 +22453,7 @@ function Pagination({
21859
22453
  {
21860
22454
  onClick: handleNext,
21861
22455
  disabled: disabled || currentPage >= totalPages,
21862
- className: `
21863
- px-3 py-2 text-sm font-medium rounded-md transition-colors disabled:cursor-default cursor-pointer
21864
- ${disabled || currentPage >= totalPages ? "text-gray-400 bg-gray-100 cursor-not-allowed" : "text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:ring-2 focus:ring-blue-500"}
21865
- `,
22456
+ className: ` px-3 py-2 text-sm font-medium rounded-md transition-colors disabled:cursor-default cursor-pointer ${disabled || currentPage >= totalPages ? "text-gray-400 bg-gray-100 cursor-not-allowed" : "text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:ring-2 focus:ring-blue-500"} `,
21866
22457
  children: [
21867
22458
  "Siguiente ",
21868
22459
  /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-white icon--sm" })
@@ -22231,28 +22822,61 @@ function App({
22231
22822
  const tabs = availableTabs.filter(
22232
22823
  (tab) => activeFeatures.includes(tab.feature)
22233
22824
  );
22234
- const handleUpload = async (file) => {
22235
- const result = await upload(file);
22236
- if (result) {
22237
- invalidateCache();
22238
- setCurrentPage(1);
22239
- setImages((prev) => [result, ...prev]);
22240
- setActiveTab("gallery");
22241
- if (callbacks.onUpload) {
22242
- callbacks.onUpload({
22243
- assetId: result.id,
22244
- url: result.url,
22245
- fileName: result.filename,
22246
- mime: result.mime_type,
22247
- width: result.width,
22248
- height: result.height,
22249
- instanceId
22250
- });
22825
+ const handleUploadAndCrop = async (file) => {
22826
+ const url2 = URL.createObjectURL(file);
22827
+ const dimensions = await new Promise((resolve) => {
22828
+ const img = new Image();
22829
+ img.onload = () => {
22830
+ resolve({ width: img.naturalWidth, height: img.naturalHeight });
22831
+ URL.revokeObjectURL(url2);
22832
+ };
22833
+ img.onerror = () => {
22834
+ resolve({ width: 1920, height: 1080 });
22835
+ URL.revokeObjectURL(url2);
22836
+ };
22837
+ img.src = url2;
22838
+ });
22839
+ const tempImage = {
22840
+ file,
22841
+ // File object (indica que es imagen nueva sin confirmar)
22842
+ filename: file.name,
22843
+ mime_type: file.type,
22844
+ url: URL.createObjectURL(file),
22845
+ // Nueva URL temporal para preview
22846
+ width: dimensions.width,
22847
+ // Dimensiones reales
22848
+ height: dimensions.height
22849
+ // Dimensiones reales
22850
+ // NO tiene id - el cropper lo generará al subir
22851
+ };
22852
+ setSelectedImage(tempImage);
22853
+ if (activeFeatures.includes("cropper")) {
22854
+ setActiveTab("cropper");
22855
+ } else {
22856
+ const result = await upload(file);
22857
+ if (result) {
22858
+ invalidateCache();
22859
+ setCurrentPage(1);
22860
+ setImages((prev) => [result, ...prev]);
22861
+ setActiveTab("gallery");
22862
+ if (callbacks.onUpload) {
22863
+ callbacks.onUpload({
22864
+ assetId: result.id,
22865
+ url: result.url,
22866
+ fileName: result.filename,
22867
+ mime: result.mime_type,
22868
+ width: result.width,
22869
+ height: result.height,
22870
+ instanceId
22871
+ });
22872
+ }
22251
22873
  }
22252
22874
  }
22253
22875
  };
22254
22876
  const handleDelete = async (imageId) => {
22255
- if (!confirm("¿Estás seguro de que deseas eliminar esta imagen? Esta acción también eliminará todos sus recortes.")) {
22877
+ if (!confirm(
22878
+ "¿Estás seguro de que deseas eliminar esta imagen? Esta acción también eliminará todos sus recortes."
22879
+ )) {
22256
22880
  return;
22257
22881
  }
22258
22882
  const success = await deleteImg(imageId);
@@ -22307,21 +22931,45 @@ function App({
22307
22931
  const globalConfig2 = window.limboCore?.config?.getGlobal() || {};
22308
22932
  const mode = globalConfig2.mode || "embed";
22309
22933
  const autoHide = globalConfig2.autoHideOnComplete || false;
22310
- const crops = Array.isArray(result) ? result : [result];
22934
+ let crops = [];
22935
+ let uploadedAsset = null;
22936
+ if (result.crops && result.asset) {
22937
+ crops = result.crops;
22938
+ uploadedAsset = result.asset;
22939
+ } else if (Array.isArray(result)) {
22940
+ crops = result;
22941
+ } else {
22942
+ crops = [result];
22943
+ }
22944
+ if (uploadedAsset && scenario === "with-gallery") {
22945
+ setImages((prev) => [uploadedAsset, ...prev]);
22946
+ setCurrentPage(1);
22947
+ if (callbacks.onUpload) {
22948
+ callbacks.onUpload({
22949
+ assetId: uploadedAsset.id,
22950
+ url: uploadedAsset.url,
22951
+ fileName: uploadedAsset.filename,
22952
+ mime: uploadedAsset.mime_type,
22953
+ width: uploadedAsset.width,
22954
+ height: uploadedAsset.height,
22955
+ instanceId
22956
+ });
22957
+ }
22958
+ }
22311
22959
  if (scenario === "with-gallery") {
22312
22960
  invalidateCache();
22313
22961
  }
22314
22962
  if (callbacks.onCropsSaved) {
22315
22963
  callbacks.onCropsSaved({
22316
22964
  crops,
22317
- assetId: selectedImage?.id,
22965
+ assetId: uploadedAsset?.id || selectedImage?.id,
22318
22966
  instanceId
22319
22967
  });
22320
22968
  }
22321
22969
  if (window.limboCore?.events) {
22322
22970
  window.limboCore.events.emit("cropsSaved", {
22323
22971
  crops,
22324
- assetId: selectedImage?.id,
22972
+ assetId: uploadedAsset?.id || selectedImage?.id,
22325
22973
  instanceId
22326
22974
  });
22327
22975
  }
@@ -22367,7 +23015,9 @@ function App({
22367
23015
  });
22368
23016
  }
22369
23017
  if (autoHide) {
22370
- const container = document.querySelector(`#limbo-instance-${instanceId}`);
23018
+ const container = document.querySelector(
23019
+ `#limbo-instance-${instanceId}`
23020
+ );
22371
23021
  if (container) container.style.display = "none";
22372
23022
  }
22373
23023
  }
@@ -22484,14 +23134,14 @@ function App({
22484
23134
  "Error al subir imagen: ",
22485
23135
  uploadError
22486
23136
  ] }),
22487
- uploadedImage && /* @__PURE__ */ jsxs("div", { className: "alert alert-success", children: [
23137
+ uploadedImage && /* @__PURE__ */ jsxs("div", { className: "alert alert-success absolute top-0 right-0 m-4", children: [
22488
23138
  "✅ Imagen subida correctamente: ",
22489
23139
  uploadedImage.filename
22490
23140
  ] }),
22491
23141
  /* @__PURE__ */ jsx(
22492
23142
  UploadForm,
22493
23143
  {
22494
- onUpload: handleUpload,
23144
+ onSelect: handleUploadAndCrop,
22495
23145
  disabled: uploading,
22496
23146
  apiKey,
22497
23147
  prod
@@ -22504,10 +23154,12 @@ function App({
22504
23154
  image: selectedImage,
22505
23155
  onSave: handleCropSave,
22506
23156
  onCancel: handleCropCancel,
22507
- onDelete: () => handleDelete(selectedImage?.id),
23157
+ onDelete: selectedImage?.id ? () => handleDelete(selectedImage.id) : null,
22508
23158
  onError: handleCropError,
22509
23159
  deleting,
22510
- onVariantCreated: handleVariantCreated
23160
+ onVariantCreated: handleVariantCreated,
23161
+ onUpload: upload,
23162
+ uploading
22511
23163
  }
22512
23164
  ),
22513
23165
  activeTab === "cropper" && !selectedImage && /* @__PURE__ */ jsx("div", { className: "limbo-empty-state", children: /* @__PURE__ */ jsx("p", { children: "Selecciona una imagen de la galería para comenzar a recortar." }) }),
@@ -26127,7 +26779,7 @@ const Limbo = new LimboCore();
26127
26779
  if (typeof window !== "undefined") {
26128
26780
  window.Limbo = Limbo;
26129
26781
  }
26130
- const PUBLIC_KEY = "pk_e464fd744106b7a8d63d453c4bd02582";
26782
+ const PUBLIC_KEY = "pk_d2edad56de145fee22c8b80f6ce3448f";
26131
26783
  if (typeof window !== "undefined" && document.querySelector("#root")) {
26132
26784
  Limbo.configure({
26133
26785
  prod: false,