limbo-component 1.9.0 → 2.0.0

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
@@ -12625,9 +12625,9 @@ function Tabs({ tabs, active, onChange }) {
12625
12625
  );
12626
12626
  }
12627
12627
  const API_URLS = {
12628
- // DEV: "https://led-dev-limbo-dev.eu.els.local", // PREPRODUCCIÓN - Updated URL
12629
- DEV: "http://localhost",
12630
- // LOCAL - Para desarrollo local
12628
+ DEV: "https://led-dev-limbo-dev.eu.els.local",
12629
+ // PREPRODUCCIÓN - Updated URL
12630
+ // DEV: "http://localhost", // LOCAL - Para desarrollo local
12631
12631
  PROD: "https://limbo.lefebvre.com"
12632
12632
  };
12633
12633
  let globalConfig = {
@@ -14322,13 +14322,257 @@ function Gallery({
14322
14322
  )
14323
14323
  ] });
14324
14324
  }
14325
+ function ImagePreview({
14326
+ image,
14327
+ onDiscard,
14328
+ onRetry,
14329
+ onDownload,
14330
+ onSelect,
14331
+ showRetry = false,
14332
+ disabled = false
14333
+ }) {
14334
+ const [previewUrl, setPreviewUrl] = useState(null);
14335
+ const [imageInfo, setImageInfo] = useState({
14336
+ name: "",
14337
+ size: "",
14338
+ type: ""
14339
+ });
14340
+ const [showLightbox, setShowLightbox] = useState(false);
14341
+ const handleEditName = (e) => {
14342
+ const newName = e.target.value;
14343
+ const originalName = imageInfo.name;
14344
+ const lastDotIndex = originalName.lastIndexOf(".");
14345
+ const extension = lastDotIndex !== -1 ? originalName.substring(lastDotIndex) : ".webp";
14346
+ setImageInfo((prev) => ({
14347
+ ...prev,
14348
+ name: newName + extension
14349
+ }));
14350
+ };
14351
+ useEffect(() => {
14352
+ if (!image) {
14353
+ setPreviewUrl(null);
14354
+ return;
14355
+ }
14356
+ const url = URL.createObjectURL(image);
14357
+ setPreviewUrl(url);
14358
+ setImageInfo({
14359
+ name: image.name || "imagen.webp",
14360
+ size: formatFileSize(image.size || 0),
14361
+ type: image.type || "image/webp"
14362
+ });
14363
+ return () => {
14364
+ URL.revokeObjectURL(url);
14365
+ };
14366
+ }, [image]);
14367
+ useEffect(() => {
14368
+ if (!showLightbox) return;
14369
+ const handleEsc = (e) => {
14370
+ if (e.key === "Escape") {
14371
+ setShowLightbox(false);
14372
+ }
14373
+ };
14374
+ document.addEventListener("keydown", handleEsc);
14375
+ return () => document.removeEventListener("keydown", handleEsc);
14376
+ }, [showLightbox]);
14377
+ const formatFileSize = (bytes) => {
14378
+ if (bytes === 0) return "0 Bytes";
14379
+ const k = 1024;
14380
+ const sizes = ["Bytes", "KB", "MB", "GB"];
14381
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
14382
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
14383
+ };
14384
+ const handleDownload = () => {
14385
+ if (!previewUrl || disabled) return;
14386
+ const link = document.createElement("a");
14387
+ link.href = previewUrl;
14388
+ link.download = imageInfo.name;
14389
+ document.body.appendChild(link);
14390
+ link.click();
14391
+ document.body.removeChild(link);
14392
+ if (onDownload) onDownload();
14393
+ };
14394
+ if (!image || !previewUrl) {
14395
+ return null;
14396
+ }
14397
+ 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: [
14398
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4 px-1", children: [
14399
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-gray-700", children: "Vista previa de imagen" }),
14400
+ /* @__PURE__ */ jsx(
14401
+ "button",
14402
+ {
14403
+ type: "button",
14404
+ onClick: onDiscard,
14405
+ disabled,
14406
+ 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",
14407
+ "aria-label": "Descartar imagen",
14408
+ title: "Descartar imagen",
14409
+ children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small icon--md" })
14410
+ }
14411
+ )
14412
+ ] }),
14413
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col justify-center md:flex-row gap-2 md:gap-6", children: [
14414
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 md:flex-[0.4] flex flex-col items-center justify-center", children: [
14415
+ /* @__PURE__ */ jsxs(
14416
+ "div",
14417
+ {
14418
+ 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",
14419
+ onClick: () => setShowLightbox(true),
14420
+ role: "button",
14421
+ tabIndex: 0,
14422
+ onKeyDown: (e) => {
14423
+ if (e.key === "Enter" || e.key === " ") {
14424
+ e.preventDefault();
14425
+ setShowLightbox(true);
14426
+ }
14427
+ },
14428
+ "aria-label": "Click para ver imagen en tamaño completo",
14429
+ title: "Click para ver en tamaño completo",
14430
+ children: [
14431
+ /* @__PURE__ */ jsx(
14432
+ "img",
14433
+ {
14434
+ src: previewUrl,
14435
+ alt: "Vista previa",
14436
+ className: "absolute inset-0 w-full h-full object-contain"
14437
+ }
14438
+ ),
14439
+ /* @__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: [
14440
+ /* @__PURE__ */ jsx("span", { className: "icon icon-search-white icon--xs" }),
14441
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Ampliar" })
14442
+ ] })
14443
+ ]
14444
+ }
14445
+ ),
14446
+ /* @__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: [
14447
+ /* @__PURE__ */ jsxs(
14448
+ "label",
14449
+ {
14450
+ htmlFor: "uploadImageId",
14451
+ className: "flex items-center w-fit",
14452
+ children: [
14453
+ /* @__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 me-1", children: /* @__PURE__ */ jsx("span", { className: "icon icon-edit-white icon--sm" }) }),
14454
+ /* @__PURE__ */ jsx(
14455
+ "input",
14456
+ {
14457
+ id: "uploadImageId",
14458
+ onChange: (e) => handleEditName(e),
14459
+ className: "w-fit px-1 text-sm font-medium text-gray-800 border border-gray-300 rounded truncate focus:outline focus:outline-neutral-gray-100 focus:transform-none focus:shadow-none focus:opacity-100",
14460
+ defaultValue: imageInfo.name.split(".")[0],
14461
+ title: "Renombra la imagen a tu gusto"
14462
+ }
14463
+ )
14464
+ ]
14465
+ }
14466
+ ),
14467
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-3 text-xs text-gray-500", children: [
14468
+ /* @__PURE__ */ jsx("span", { children: imageInfo.size }),
14469
+ /* @__PURE__ */ jsx("span", { children: "•" }),
14470
+ /* @__PURE__ */ jsx("span", { children: imageInfo.type.split("/")[1]?.toUpperCase() || "IMAGE" })
14471
+ ] })
14472
+ ] }) })
14473
+ ] }),
14474
+ /* @__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: [
14475
+ showRetry && onRetry && /* @__PURE__ */ jsxs(
14476
+ "button",
14477
+ {
14478
+ type: "button",
14479
+ onClick: onRetry,
14480
+ disabled,
14481
+ 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",
14482
+ "aria-label": "Intentarlo de nuevo",
14483
+ title: "Generar de nuevo con el mismo prompt",
14484
+ children: [
14485
+ /* @__PURE__ */ jsx("span", { className: "icon icon-refresh icon--sm" }),
14486
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Reintentar" }),
14487
+ /* @__PURE__ */ jsx("span", { className: "sm:hidden", children: "Retry" })
14488
+ ]
14489
+ }
14490
+ ),
14491
+ /* @__PURE__ */ jsxs(
14492
+ "button",
14493
+ {
14494
+ type: "button",
14495
+ onClick: handleDownload,
14496
+ disabled,
14497
+ 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",
14498
+ "aria-label": "Descargar imagen",
14499
+ children: [
14500
+ /* @__PURE__ */ jsx("span", { className: "icon icon-download icon--sm" }),
14501
+ /* @__PURE__ */ jsx("span", { children: "Descargar" })
14502
+ ]
14503
+ }
14504
+ ),
14505
+ /* @__PURE__ */ jsxs(
14506
+ "button",
14507
+ {
14508
+ type: "button",
14509
+ onClick: () => onSelect && onSelect(imageInfo.name),
14510
+ disabled,
14511
+ 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",
14512
+ "aria-label": "Seleccionar imagen para recortar",
14513
+ children: [
14514
+ /* @__PURE__ */ jsx("span", { className: "icon icon-tick-white icon--sm" }),
14515
+ /* @__PURE__ */ jsx("span", { children: "Seleccionar" })
14516
+ ]
14517
+ }
14518
+ )
14519
+ ] })
14520
+ ] }),
14521
+ showLightbox && /* @__PURE__ */ jsxs(
14522
+ "div",
14523
+ {
14524
+ className: "fixed inset-0 bg-black/90 z-[10000] flex items-center justify-center p-4",
14525
+ onClick: () => setShowLightbox(false),
14526
+ role: "dialog",
14527
+ "aria-modal": "true",
14528
+ "aria-label": "Vista de imagen en tamaño completo",
14529
+ children: [
14530
+ /* @__PURE__ */ jsx(
14531
+ "button",
14532
+ {
14533
+ onClick: () => setShowLightbox(false),
14534
+ className: "absolute top-4 right-4 p-2 bg-white/10 hover:bg-white/20 rounded-full transition-colors cursor-pointer z-10",
14535
+ "aria-label": "Cerrar vista ampliada",
14536
+ title: "Cerrar (Esc)",
14537
+ children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small-white icon--lg" })
14538
+ }
14539
+ ),
14540
+ /* @__PURE__ */ jsxs("div", { className: "absolute top-4 left-4 bg-black/50 text-white px-4 py-2 rounded-lg text-sm", children: [
14541
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: imageInfo.name }),
14542
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-300 mt-1", children: [
14543
+ imageInfo.size,
14544
+ " • ",
14545
+ imageInfo.type.split("/")[1]?.toUpperCase()
14546
+ ] })
14547
+ ] }),
14548
+ /* @__PURE__ */ jsx(
14549
+ "div",
14550
+ {
14551
+ className: "relative max-w-7xl max-h-[90vh] w-full h-full flex items-center justify-center",
14552
+ onClick: (e) => e.stopPropagation(),
14553
+ children: /* @__PURE__ */ jsx(
14554
+ "img",
14555
+ {
14556
+ src: previewUrl,
14557
+ alt: "Vista completa",
14558
+ className: "max-w-full max-h-full object-contain rounded-lg shadow-2xl"
14559
+ }
14560
+ )
14561
+ }
14562
+ ),
14563
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-4 left-1/2 -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" })
14564
+ ]
14565
+ }
14566
+ )
14567
+ ] });
14568
+ }
14325
14569
  function TabUpload({
14326
14570
  file,
14327
14571
  setFile,
14328
14572
  previewUrl,
14329
14573
  setPreviewUrl,
14330
14574
  fileInputRef,
14331
- onUpload,
14575
+ onSelect,
14332
14576
  disabled
14333
14577
  }) {
14334
14578
  const handleFileChange = (e) => {
@@ -14341,36 +14585,32 @@ function TabUpload({
14341
14585
  setPreviewUrl(null);
14342
14586
  }
14343
14587
  };
14344
- const handleClearFile = () => {
14345
- const confirmClear = window.confirm(
14346
- "¿Estás seguro de que deseas descartar la imagen seleccionada?"
14347
- );
14348
- if (!confirmClear) return;
14588
+ const handleDiscard = () => {
14349
14589
  setFile(null);
14350
14590
  setPreviewUrl(null);
14351
14591
  if (fileInputRef.current) fileInputRef.current.value = "";
14352
14592
  };
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 = "";
14593
+ const handleSelect = (editedName) => {
14594
+ if (file && !disabled && onSelect) {
14595
+ if (editedName && editedName !== file.name) {
14596
+ const newFile = new File([file], editedName, { type: file.type });
14597
+ onSelect(newFile);
14598
+ } else {
14599
+ onSelect(file);
14600
+ }
14360
14601
  }
14361
14602
  };
14362
14603
  return /* @__PURE__ */ jsxs(
14363
- "form",
14604
+ "div",
14364
14605
  {
14365
- onSubmit: handleSubmit,
14366
14606
  className: "flex flex-col items-center gap-6",
14367
14607
  "aria-label": "Subir imagen desde dispositivo",
14368
14608
  children: [
14369
- !previewUrl && /* @__PURE__ */ jsxs(
14609
+ /* @__PURE__ */ jsxs(
14370
14610
  "label",
14371
14611
  {
14372
14612
  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",
14613
+ 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
14614
  tabIndex: 0,
14375
14615
  style: { outline: "none" },
14376
14616
  children: [
@@ -14393,71 +14633,16 @@ function TabUpload({
14393
14633
  ]
14394
14634
  }
14395
14635
  ),
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",
14636
+ file && /* @__PURE__ */ jsx(
14637
+ ImagePreview,
14452
14638
  {
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"
14639
+ image: file,
14640
+ onDiscard: handleDiscard,
14641
+ onDownload: () => {
14642
+ },
14643
+ onSelect: handleSelect,
14644
+ showRetry: false,
14645
+ disabled
14461
14646
  }
14462
14647
  )
14463
14648
  ]
@@ -14600,16 +14785,23 @@ function LoadingOverlay({
14600
14785
  }
14601
14786
  );
14602
14787
  }
14603
- function TabAI({ prod, disabled, onGenerated }) {
14788
+ function TabAI({ prod, disabled, onSelect, onServiceChange }) {
14604
14789
  const aiServicesHook = useAiServices(prod);
14605
14790
  const imageParamsHook = useImageParams(prod);
14791
+ const [lastPrompt, setLastPrompt] = useState("");
14606
14792
  const [selectedService, setSelectedService] = useState("");
14607
14793
  const [dynamicForm, setDynamicForm] = useState({});
14608
14794
  const [aiLoading, setAiLoading] = useState(false);
14609
14795
  const [aiError, setAiError] = useState(null);
14610
14796
  const [aiImage, setAiImage] = useState(null);
14797
+ const [generatedFile, setGeneratedFile] = useState(null);
14611
14798
  const [showServiceSelection, setShowServiceSelection] = useState(true);
14612
14799
  const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
14800
+ React.useEffect(() => {
14801
+ if (onServiceChange && selectedService) {
14802
+ onServiceChange(selectedService);
14803
+ }
14804
+ }, [selectedService]);
14613
14805
  React.useEffect(() => {
14614
14806
  if (!aiServicesHook.loading && aiServicesHook.services.length === 1) {
14615
14807
  const service = aiServicesHook.services[0];
@@ -14633,12 +14825,17 @@ function TabAI({ prod, disabled, onGenerated }) {
14633
14825
  const { name, value } = e.target;
14634
14826
  setDynamicForm((prev) => ({ ...prev, [name]: value }));
14635
14827
  };
14636
- const handleDynamicFormSubmit = async (e) => {
14828
+ const handleDynamicFormSubmit = async (e, retry = false) => {
14637
14829
  e.preventDefault();
14638
14830
  setAiLoading(true);
14639
14831
  setAiError(null);
14640
14832
  setAiImage(null);
14641
14833
  try {
14834
+ if (retry) {
14835
+ dynamicForm.prompt = lastPrompt;
14836
+ } else {
14837
+ setLastPrompt(dynamicForm.prompt || "");
14838
+ }
14642
14839
  const response = await generateAiImage(dynamicForm, prod);
14643
14840
  let imageData = null;
14644
14841
  if (response?.data?.images && Array.isArray(response.data.images) && response.data.images.length > 0) {
@@ -14650,7 +14847,7 @@ function TabAI({ prod, disabled, onGenerated }) {
14650
14847
  } else if (typeof response === "string") {
14651
14848
  imageData = response;
14652
14849
  }
14653
- if (imageData && onGenerated) {
14850
+ if (imageData) {
14654
14851
  if (imageData.startsWith("http")) {
14655
14852
  try {
14656
14853
  const apiBaseUrl = window.limboCore?.config?.getGlobal()?.prod ? "https://limbo.lefebvre.com" : "http://localhost";
@@ -14669,10 +14866,10 @@ function TabAI({ prod, disabled, onGenerated }) {
14669
14866
  );
14670
14867
  }
14671
14868
  const blob = await imageResponse.blob();
14672
- const file = new File([blob], "ai-image.png", {
14673
- type: blob.type || "image/png"
14869
+ const file = new File([blob], "ai-image.webp", {
14870
+ type: blob.type || "image/webp"
14674
14871
  });
14675
- onGenerated(file);
14872
+ setGeneratedFile(file);
14676
14873
  } catch (downloadError) {
14677
14874
  throw new Error(
14678
14875
  `Error downloading image via proxy: ${downloadError.message}`
@@ -14689,7 +14886,7 @@ function TabAI({ prod, disabled, onGenerated }) {
14689
14886
  const file = new File([byteArray], "ai-image.webp", {
14690
14887
  type: "image/webp"
14691
14888
  });
14692
- onGenerated(file);
14889
+ setGeneratedFile(file);
14693
14890
  }
14694
14891
  } else {
14695
14892
  throw new Error("No se pudo extraer la imagen de la respuesta");
@@ -14704,7 +14901,9 @@ function TabAI({ prod, disabled, onGenerated }) {
14704
14901
  const renderDynamicForm = () => {
14705
14902
  const params = imageParamsHook.params?.[selectedService]?.parameters;
14706
14903
  if (!params) return null;
14707
- const allFields = Object.entries(params).filter(([_, config]) => !config.hidden);
14904
+ const allFields = Object.entries(params).filter(
14905
+ ([_, config]) => !config.hidden
14906
+ );
14708
14907
  const promptFields = allFields.filter(
14709
14908
  ([key]) => key.toLowerCase().includes("prompt") || key.toLowerCase().includes("query")
14710
14909
  );
@@ -14792,7 +14991,7 @@ function TabAI({ prod, disabled, onGenerated }) {
14792
14991
  )
14793
14992
  ] }, key);
14794
14993
  };
14795
- return /* @__PURE__ */ jsxs("div", { children: [
14994
+ return /* @__PURE__ */ jsxs("div", { className: "h-fit w-full", children: [
14796
14995
  aiServicesHook.services.length > 1 && /* @__PURE__ */ jsxs(
14797
14996
  "button",
14798
14997
  {
@@ -14803,6 +15002,7 @@ function TabAI({ prod, disabled, onGenerated }) {
14803
15002
  setDynamicForm({});
14804
15003
  setAiImage(null);
14805
15004
  setAiError(null);
15005
+ setGeneratedFile(null);
14806
15006
  },
14807
15007
  className: "flex cursor-pointer items-center gap-2 text-brand-blue-600 hover:text-brand-blue-700 mb-4 font-medium",
14808
15008
  children: [
@@ -14856,17 +15056,19 @@ function TabAI({ prod, disabled, onGenerated }) {
14856
15056
  ]
14857
15057
  }
14858
15058
  ),
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)) })
15059
+ showAdvancedOptions && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 animate-fadeIn", children: advancedFields.map(
15060
+ ([key, config]) => renderField(key, config)
15061
+ ) })
14860
15062
  ] }),
14861
15063
  /* @__PURE__ */ jsx(
14862
15064
  "button",
14863
15065
  {
14864
15066
  type: "submit",
14865
15067
  disabled: aiLoading || disabled,
14866
- title: aiLoading ? "Generando imagen..." : "Generar imagen",
15068
+ title: aiLoading ? "Generando imagen..." : generatedFile ? "Generar otra imagen" : "Generar imagen",
14867
15069
  className: `limbo-btn w-full mt-2 ${aiLoading ? "cursor-not-allowed limbo-btn-disabled" : "limbo-btn-primary"}`,
14868
15070
  style: { minHeight: 44 },
14869
- children: aiLoading ? "Generando..." : "Generar imagen"
15071
+ children: aiLoading ? "Generando imagen..." : generatedFile ? "Generar otra imagen" : "Generar imagen"
14870
15072
  }
14871
15073
  )
14872
15074
  ]
@@ -14891,12 +15093,12 @@ function TabAI({ prod, disabled, onGenerated }) {
14891
15093
  if (!aiServicesHook.services.length) {
14892
15094
  return /* @__PURE__ */ jsx("div", { className: "alert alert-warning", children: "No hay servicios IA disponibles." });
14893
15095
  }
14894
- return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 mx-4 pb-2", children: showServiceSelection ? /* @__PURE__ */ jsxs(Fragment, { children: [
15096
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col mx-4 pb-2", children: showServiceSelection ? /* @__PURE__ */ jsxs(Fragment, { children: [
14895
15097
  /* @__PURE__ */ jsx(
14896
15098
  "h3",
14897
15099
  {
14898
15100
  id: "aiSelectDescription",
14899
- className: "text-lg font-semibold text-brand-blue-1000 mb-3",
15101
+ className: "text-2xl font-semibold text-brand-blue-1000",
14900
15102
  children: "Selecciona el modelo IA"
14901
15103
  }
14902
15104
  ),
@@ -14911,6 +15113,7 @@ function TabAI({ prod, disabled, onGenerated }) {
14911
15113
  setDynamicForm({});
14912
15114
  setAiImage(null);
14913
15115
  setAiError(null);
15116
+ setGeneratedFile(null);
14914
15117
  imageParamsHook.fetchParams(service.slug, true);
14915
15118
  },
14916
15119
  disabled: aiServicesHook.loading || disabled,
@@ -14936,7 +15139,7 @@ function TabAI({ prod, disabled, onGenerated }) {
14936
15139
  ),
14937
15140
  /* @__PURE__ */ jsxs("div", { className: "relative z-10 flex flex-col items-center justify-center gap-2 text-center", children: [
14938
15141
  /* @__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 })
15142
+ service.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-neutral-gray-800 line-clamp-2 drop-shadow drop-shadow-neutral-white-000", children: service.description })
14940
15143
  ] })
14941
15144
  ]
14942
15145
  },
@@ -14945,22 +15148,38 @@ function TabAI({ prod, disabled, onGenerated }) {
14945
15148
  ] }) : (
14946
15149
  /* Vista del formulario del servicio seleccionado */
14947
15150
  /* @__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" }),
15151
+ /* @__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
15152
  imageParamsHook.loading && /* @__PURE__ */ jsx("div", { children: "Cargando parámetros..." }),
14950
15153
  imageParamsHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: imageParamsHook.error }),
14951
15154
  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))
15155
+ aiError && /* @__PURE__ */ jsx("div", { className: "alert alert-danger mt-4", children: aiError }),
15156
+ generatedFile && /* @__PURE__ */ jsx("div", { className: "mt-6 md:w-full mx-auto", children: /* @__PURE__ */ jsx(
15157
+ ImagePreview,
15158
+ {
15159
+ image: generatedFile,
15160
+ onDiscard: () => {
15161
+ setGeneratedFile(null);
15162
+ setAiImage(null);
15163
+ setAiError(null);
15164
+ },
15165
+ onRetry: (e) => handleDynamicFormSubmit(e, true),
15166
+ onDownload: () => {
15167
+ },
15168
+ onSelect: (editedName) => {
15169
+ if (onSelect) {
15170
+ const fileToSelect = generatedFile || aiImage;
15171
+ if (editedName && fileToSelect && editedName !== fileToSelect.name) {
15172
+ const newFile = new File([fileToSelect], editedName, { type: fileToSelect.type });
15173
+ onSelect(newFile);
15174
+ } else {
15175
+ onSelect(fileToSelect);
15176
+ }
15177
+ }
15178
+ },
15179
+ showRetry: true,
15180
+ disabled: aiLoading || disabled
15181
+ }
15182
+ ) })
14964
15183
  ] })
14965
15184
  ) });
14966
15185
  }
@@ -15033,7 +15252,7 @@ function useStockServices(prod = false) {
15033
15252
  };
15034
15253
  return { services, loading, error, invalidateCache };
15035
15254
  }
15036
- function TabStock({ prod, disabled, onSelected }) {
15255
+ function TabStock({ prod, disabled, onSelect, onServiceChange }) {
15037
15256
  const lastSearchPayloadRef = React.useRef(null);
15038
15257
  const stockServicesHook = useStockServices(prod);
15039
15258
  const loadSavedState = (key, defaultValue) => {
@@ -15059,12 +15278,14 @@ function TabStock({ prod, disabled, onSelected }) {
15059
15278
  const [paginationInfo, setPaginationInfo] = useState(
15060
15279
  () => loadSavedState("paginationInfo", null)
15061
15280
  );
15281
+ const [lastQuery, setLastQuery] = useState(null);
15062
15282
  const imageParamsHook = useImageParams(prod);
15063
15283
  const [stockLoading, setStockLoading] = useState(false);
15064
15284
  const [stockError, setStockError] = useState(null);
15065
15285
  const [downloadingId, setDownloadingId] = useState(null);
15066
15286
  const [showServiceSelection, setShowServiceSelection] = useState(true);
15067
15287
  const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
15288
+ const [selectedFile, setSelectedFile] = useState(null);
15068
15289
  React.useEffect(() => {
15069
15290
  sessionStorage.setItem(
15070
15291
  "limbo_stock_selectedService",
@@ -15114,6 +15335,11 @@ function TabStock({ prod, disabled, onSelected }) {
15114
15335
  setDynamicForm(initialValues);
15115
15336
  }
15116
15337
  }, [selectedService, imageParamsHook.params]);
15338
+ React.useEffect(() => {
15339
+ if (onServiceChange && selectedService) {
15340
+ onServiceChange(selectedService);
15341
+ }
15342
+ }, [selectedService]);
15117
15343
  const handleDynamicFormChange = (e) => {
15118
15344
  const { name, value } = e.target;
15119
15345
  setDynamicForm((prev) => ({ ...prev, [name]: value }));
@@ -15184,6 +15410,7 @@ function TabStock({ prod, disabled, onSelected }) {
15184
15410
  setStockError("La búsqueda debe tener al menos 5 caracteres");
15185
15411
  return;
15186
15412
  }
15413
+ setLastQuery(query);
15187
15414
  await performSearch(1);
15188
15415
  };
15189
15416
  const handlePageChange = (newPage) => {
@@ -15207,7 +15434,7 @@ function TabStock({ prod, disabled, onSelected }) {
15207
15434
  const file = new File([blob], filename, {
15208
15435
  type: blob.type || "image/jpeg"
15209
15436
  });
15210
- if (onSelected) onSelected(file);
15437
+ setSelectedFile(file);
15211
15438
  } else {
15212
15439
  throw new Error("No se pudo obtener la URL de descarga");
15213
15440
  }
@@ -15434,8 +15661,8 @@ function TabStock({ prod, disabled, onSelected }) {
15434
15661
  if (!stockServicesHook.services.length) {
15435
15662
  return /* @__PURE__ */ jsx("div", { className: "alert alert-warning", children: "No hay servicios de Stock disponibles." });
15436
15663
  }
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" }),
15664
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col mx-4 pb-2", children: showServiceSelection ? /* @__PURE__ */ jsxs(Fragment, { children: [
15665
+ /* @__PURE__ */ jsx("h3", { className: "text-2xl font-semibold text-brand-blue-1000", children: "Selecciona el servicio de Stock" }),
15439
15666
  stockServicesHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: stockServicesHook.error }),
15440
15667
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-3", children: stockServicesHook.services.map((service) => /* @__PURE__ */ jsxs(
15441
15668
  "button",
@@ -15483,12 +15710,36 @@ function TabStock({ prod, disabled, onSelected }) {
15483
15710
  ] }) : (
15484
15711
  /* Vista del formulario del servicio seleccionado */
15485
15712
  /* @__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" }),
15713
+ /* @__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
15714
  imageParamsHook.loading && /* @__PURE__ */ jsx("div", { children: "Cargando parámetros..." }),
15488
15715
  imageParamsHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: imageParamsHook.error }),
15489
15716
  selectedService && renderDynamicForm(),
15490
- stockError && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: stockError }),
15491
- stockImages.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
15717
+ stockError && /* @__PURE__ */ jsx("div", { className: "alert alert-danger mt-4", children: stockError }),
15718
+ selectedFile && /* @__PURE__ */ jsx("div", { className: "mt-6 md:w-full mx-auto", children: /* @__PURE__ */ jsx(
15719
+ ImagePreview,
15720
+ {
15721
+ image: selectedFile,
15722
+ onDiscard: () => {
15723
+ setSelectedFile(null);
15724
+ setStockError(null);
15725
+ },
15726
+ onDownload: () => {
15727
+ },
15728
+ onSelect: (editedName) => {
15729
+ if (onSelect) {
15730
+ if (editedName && selectedFile && editedName !== selectedFile.name) {
15731
+ const newFile = new File([selectedFile], editedName, { type: selectedFile.type });
15732
+ onSelect(newFile);
15733
+ } else {
15734
+ onSelect(selectedFile);
15735
+ }
15736
+ }
15737
+ },
15738
+ showRetry: false,
15739
+ disabled
15740
+ }
15741
+ ) }),
15742
+ !selectedFile && stockImages.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
15492
15743
  /* @__PURE__ */ jsxs(
15493
15744
  "div",
15494
15745
  {
@@ -15524,7 +15775,19 @@ function TabStock({ prod, disabled, onSelected }) {
15524
15775
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 border-4 border-brand-blue-600 rounded-full border-t-transparent animate-spin" })
15525
15776
  ] }),
15526
15777
  /* @__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" }) })
15778
+ ] }) : /* @__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: [
15779
+ /* @__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" }),
15780
+ /* @__PURE__ */ jsx(
15781
+ "span",
15782
+ {
15783
+ style: {
15784
+ 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"
15785
+ },
15786
+ className: "max-w-full max-h-full opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-neutral-white-000",
15787
+ children: "Seleccionar"
15788
+ }
15789
+ )
15790
+ ] })
15528
15791
  ] })
15529
15792
  },
15530
15793
  img.id || idx
@@ -15580,7 +15843,7 @@ function TabStock({ prod, disabled, onSelected }) {
15580
15843
  )
15581
15844
  ] })
15582
15845
  ] }),
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: [
15846
+ !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
15847
  /* @__PURE__ */ jsx("span", { className: "icon icon-search icon--lg mb-2" }),
15585
15848
  /* @__PURE__ */ jsxs("p", { children: [
15586
15849
  'No se encontraron imágenes para "',
@@ -15676,7 +15939,7 @@ const ValidatedImage = ({ src, alt, className, onError }) => {
15676
15939
  }
15677
15940
  );
15678
15941
  };
15679
- function TabPortals({ prod, disabled, onSelected }) {
15942
+ function TabPortals({ prod, disabled, onSelect }) {
15680
15943
  const lastSearchPayloadRef = React.useRef(null);
15681
15944
  const portalSourcesHook = usePortalSources(prod);
15682
15945
  const loadSavedState = (key, defaultValue) => {
@@ -15707,6 +15970,7 @@ function TabPortals({ prod, disabled, onSelected }) {
15707
15970
  () => loadSavedState("paginationInfo", null)
15708
15971
  );
15709
15972
  const [downloadingUrl, setDownloadingUrl] = useState(null);
15973
+ const [selectedFile, setSelectedFile] = useState(null);
15710
15974
  const searchCacheRef = useRef({});
15711
15975
  const [failedImages, setFailedImages] = useState(/* @__PURE__ */ new Set());
15712
15976
  React.useEffect(() => {
@@ -15882,7 +16146,7 @@ function TabPortals({ prod, disabled, onSelected }) {
15882
16146
  const file = new File([blob], filename, {
15883
16147
  type: blob.type || "image/jpeg"
15884
16148
  });
15885
- if (onSelected) onSelected(file);
16149
+ setSelectedFile(file);
15886
16150
  } catch (err) {
15887
16151
  setError(err.message || "No se pudo recuperar la imagen del portal");
15888
16152
  } finally {
@@ -15898,7 +16162,7 @@ function TabPortals({ prod, disabled, onSelected }) {
15898
16162
  if (!portalSourcesHook.sources.length) {
15899
16163
  return /* @__PURE__ */ jsx("div", { className: "alert alert-warning", children: "No hay portales externos disponibles." });
15900
16164
  }
15901
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
16165
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
15902
16166
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-brand-blue-1000", children: "Buscar en Portales Externos" }),
15903
16167
  /* @__PURE__ */ jsxs(
15904
16168
  "form",
@@ -15932,33 +16196,20 @@ function TabPortals({ prod, disabled, onSelected }) {
15932
16196
  onClick: () => handlePortalToggle(portal.id),
15933
16197
  disabled,
15934
16198
  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"}
16199
+ 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"}
15938
16200
  ${disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}
15939
16201
  hover:shadow-md
15940
16202
  `,
15941
16203
  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" }) }),
16204
+ 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
16205
  /* @__PURE__ */ jsx(
15944
- "svg",
16206
+ "img",
15945
16207
  {
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
- )
16208
+ className: "object-auto bg-no-repeat bg-center max-w-9/12 max-h-9/12",
16209
+ src: portal.image,
16210
+ alt: portal.title
15959
16211
  }
15960
- ),
15961
- /* @__PURE__ */ jsx("span", { className: `text-sm font-medium text-center ${isSelected ? "text-brand-blue-900" : "text-gray-700"}`, children: portal.title })
16212
+ )
15962
16213
  ]
15963
16214
  },
15964
16215
  portal.id
@@ -15987,7 +16238,7 @@ function TabPortals({ prod, disabled, onSelected }) {
15987
16238
  }
15988
16239
  )
15989
16240
  ] }),
15990
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
16241
+ /* @__PURE__ */ jsxs("div", { className: "flex-col gap-1 hidden", children: [
15991
16242
  /* @__PURE__ */ jsx(
15992
16243
  "label",
15993
16244
  {
@@ -16000,7 +16251,7 @@ function TabPortals({ prod, disabled, onSelected }) {
16000
16251
  "select",
16001
16252
  {
16002
16253
  id: "portal-limit",
16003
- value: limit,
16254
+ value: 10,
16004
16255
  onChange: (e) => setLimit(Number(e.target.value)),
16005
16256
  className: "limbo-input",
16006
16257
  disabled,
@@ -16035,7 +16286,14 @@ function TabPortals({ prod, disabled, onSelected }) {
16035
16286
  {
16036
16287
  className: `flex items-center justify-between p-2 rounded ${result.status === 200 ? "bg-green-50" : "bg-red-50"}`,
16037
16288
  children: [
16038
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: result.title }),
16289
+ result.image ? /* @__PURE__ */ jsx(
16290
+ "img",
16291
+ {
16292
+ src: result.image,
16293
+ alt: result.title,
16294
+ className: "w-10 h-10 object-cover rounded"
16295
+ }
16296
+ ) : /* @__PURE__ */ jsx("span", { className: "font-medium", children: result.title }),
16039
16297
  /* @__PURE__ */ jsx(
16040
16298
  "span",
16041
16299
  {
@@ -16048,20 +16306,38 @@ function TabPortals({ prod, disabled, onSelected }) {
16048
16306
  portalId
16049
16307
  )) })
16050
16308
  ] }),
16051
- (images.length > 0 || loading) && /* @__PURE__ */ jsxs(Fragment, { children: [
16309
+ selectedFile && /* @__PURE__ */ jsx("div", { className: "mt-6 md:w-full mx-auto", children: /* @__PURE__ */ jsx(
16310
+ ImagePreview,
16311
+ {
16312
+ image: selectedFile,
16313
+ onDiscard: () => {
16314
+ setSelectedFile(null);
16315
+ setError(null);
16316
+ },
16317
+ onDownload: () => {
16318
+ },
16319
+ onSelect: (editedName) => {
16320
+ if (onSelect) {
16321
+ if (editedName && selectedFile && editedName !== selectedFile.name) {
16322
+ const newFile = new File([selectedFile], editedName, { type: selectedFile.type });
16323
+ onSelect(newFile);
16324
+ } else {
16325
+ onSelect(selectedFile);
16326
+ }
16327
+ }
16328
+ },
16329
+ showRetry: false,
16330
+ disabled
16331
+ }
16332
+ ) }),
16333
+ !selectedFile && (images.length > 0 || loading) && /* @__PURE__ */ jsxs(Fragment, { children: [
16052
16334
  /* @__PURE__ */ jsxs("div", { className: "mt-6 relative", "aria-live": "polite", children: [
16053
16335
  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
16336
  /* @__PURE__ */ jsx("span", { className: "limbo-loader" }),
16055
16337
  /* @__PURE__ */ jsx("span", { className: "text-sm text-brand-blue-800", children: "Buscando imágenes..." })
16056
16338
  ] }) }),
16057
16339
  /* @__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
- ),
16340
+ /* @__PURE__ */ jsx(LoadingOverlay, { show: loading, message: "Cargando imágenes..." }),
16065
16341
  images.map((img, idx) => {
16066
16342
  const imageKey = `${img.source}-${img.id || idx}`;
16067
16343
  const imageUrl = img.preview || img.thumbnail || img.url || img.full;
@@ -16188,7 +16464,11 @@ const TABS = [
16188
16464
  { id: "portals", label: "Otros portales" }
16189
16465
  ];
16190
16466
  function UploadForm({
16191
- onUpload,
16467
+ onSelect,
16468
+ onSubTabChange = null,
16469
+ // Callback para notificar cambio de sub-tab a App.jsx
16470
+ onServiceChange = null,
16471
+ // Callback para notificar cambio de servicio (modelo IA/Stock)
16192
16472
  disabled = false,
16193
16473
  apiKey,
16194
16474
  prod = false
@@ -16197,6 +16477,16 @@ function UploadForm({
16197
16477
  const [file, setFile] = useState(null);
16198
16478
  const [previewUrl, setPreviewUrl] = useState(null);
16199
16479
  const fileInputRef = useRef();
16480
+ React.useEffect(() => {
16481
+ if (onSubTabChange) {
16482
+ onSubTabChange(activeTab);
16483
+ }
16484
+ }, [activeTab, onSubTabChange]);
16485
+ const handleServiceChange = (serviceSlug) => {
16486
+ if (onServiceChange && serviceSlug) {
16487
+ onServiceChange(activeTab, serviceSlug);
16488
+ }
16489
+ };
16200
16490
  const handleTabKeyDown = (event, tabId) => {
16201
16491
  const currentIndex = TABS.findIndex((tab) => tab.id === tabId);
16202
16492
  let nextIndex = currentIndex;
@@ -16229,7 +16519,9 @@ function UploadForm({
16229
16519
  if (nextTab) {
16230
16520
  setActiveTab(nextTab.id);
16231
16521
  setTimeout(() => {
16232
- const tabButton = document.querySelector(`[data-upload-tab-id="${nextTab.id}"]`);
16522
+ const tabButton = document.querySelector(
16523
+ `[data-upload-tab-id="${nextTab.id}"]`
16524
+ );
16233
16525
  if (tabButton) {
16234
16526
  tabButton.focus();
16235
16527
  }
@@ -16240,7 +16532,7 @@ function UploadForm({
16240
16532
  /* @__PURE__ */ jsx("div", { className: "limbo-tabs-container", children: /* @__PURE__ */ jsx(
16241
16533
  "div",
16242
16534
  {
16243
- className: "limbo-tabs mb-6 min-w-fit",
16535
+ className: "limbo-tabs mb-1 min-w-fit",
16244
16536
  role: "tablist",
16245
16537
  "aria-label": "Opciones de subida de imagen",
16246
16538
  children: TABS.map((tab) => /* @__PURE__ */ jsx(
@@ -16263,59 +16555,58 @@ function UploadForm({
16263
16555
  ))
16264
16556
  }
16265
16557
  ) }),
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
- ] })
16558
+ /* @__PURE__ */ jsxs(
16559
+ "div",
16560
+ {
16561
+ className: "limbo-tab-content px-2 py-2",
16562
+ role: "tabpanel",
16563
+ id: `upload-tabpanel-${activeTab}`,
16564
+ "aria-labelledby": `upload-tab-${activeTab}`,
16565
+ children: [
16566
+ activeTab === "upload" && /* @__PURE__ */ jsx(
16567
+ TabUpload,
16568
+ {
16569
+ file,
16570
+ setFile,
16571
+ previewUrl,
16572
+ setPreviewUrl,
16573
+ fileInputRef,
16574
+ onSelect,
16575
+ disabled
16576
+ }
16577
+ ),
16578
+ activeTab === "ai" && /* @__PURE__ */ jsx(
16579
+ TabAI,
16580
+ {
16581
+ apiKey,
16582
+ prod,
16583
+ disabled,
16584
+ onSelect,
16585
+ onServiceChange: handleServiceChange
16586
+ }
16587
+ ),
16588
+ activeTab === "stock" && /* @__PURE__ */ jsx(
16589
+ TabStock,
16590
+ {
16591
+ apiKey,
16592
+ prod,
16593
+ disabled,
16594
+ onSelect,
16595
+ onServiceChange: handleServiceChange
16596
+ }
16597
+ ),
16598
+ activeTab === "portals" && /* @__PURE__ */ jsx(
16599
+ TabPortals,
16600
+ {
16601
+ apiKey,
16602
+ prod,
16603
+ disabled,
16604
+ onSelect
16605
+ }
16606
+ )
16607
+ ]
16608
+ }
16609
+ )
16319
16610
  ] });
16320
16611
  }
16321
16612
  const IS_BROWSER = typeof window !== "undefined" && typeof window.document !== "undefined";
@@ -19739,14 +20030,22 @@ function CropperView({
19739
20030
  onError = null,
19740
20031
  // Callback para manejar errores
19741
20032
  deleting = false,
19742
- onVariantCreated = null
20033
+ onVariantCreated = null,
19743
20034
  // Callback cuando se crea una variante
20035
+ onUpload = null,
20036
+ // Callback para subir imagen nueva (cuando image.file existe)
20037
+ uploading = false
20038
+ // Estado de carga del upload
19744
20039
  }) {
19745
20040
  const [showPreview, setShowPreview] = useState(false);
19746
20041
  const [previewUrl, setPreviewUrl] = useState(null);
19747
20042
  const [previewLoading, setPreviewLoading] = useState(false);
19748
20043
  const [showGrid, setShowGrid] = useState(true);
19749
20044
  const [shade, setShade] = useState(true);
20045
+ const [previewPosition, setPreviewPosition] = useState({ x: 20, y: 100 });
20046
+ const [isDragging, setIsDragging] = useState(false);
20047
+ const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
20048
+ const [showPreviewLightbox, setShowPreviewLightbox] = useState(false);
19750
20049
  const [initialLoading, setInitialLoading] = useState(true);
19751
20050
  const [cropTransitioning, setcropTransitioning] = useState(false);
19752
20051
  const [flipStates, setFlipStates] = useState({
@@ -20217,8 +20516,92 @@ function CropperView({
20217
20516
  setPreviewLoading(false);
20218
20517
  }
20219
20518
  }, [canExport, generatePreview, showPreview]);
20519
+ const handlePreviewMouseDown = useCallback(
20520
+ (e) => {
20521
+ if (!e.target.closest(".preview-modal-header")) return;
20522
+ setIsDragging(true);
20523
+ setDragStart({
20524
+ x: e.clientX - previewPosition.x,
20525
+ y: e.clientY - previewPosition.y
20526
+ });
20527
+ },
20528
+ [previewPosition]
20529
+ );
20530
+ const handlePreviewMouseMove = useCallback(
20531
+ (e) => {
20532
+ if (!isDragging) return;
20533
+ e.preventDefault();
20534
+ setPreviewPosition({
20535
+ x: e.clientX - dragStart.x,
20536
+ y: e.clientY - dragStart.y
20537
+ });
20538
+ },
20539
+ [isDragging, dragStart]
20540
+ );
20541
+ const handlePreviewMouseUp = useCallback(() => {
20542
+ setIsDragging(false);
20543
+ }, []);
20544
+ const handlePreviewTouchStart = useCallback(
20545
+ (e) => {
20546
+ if (!e.target.closest(".preview-modal-header")) return;
20547
+ const touch = e.touches[0];
20548
+ setIsDragging(true);
20549
+ setDragStart({
20550
+ x: touch.clientX - previewPosition.x,
20551
+ y: touch.clientY - previewPosition.y
20552
+ });
20553
+ },
20554
+ [previewPosition]
20555
+ );
20556
+ const handlePreviewTouchMove = useCallback(
20557
+ (e) => {
20558
+ if (!isDragging) return;
20559
+ e.preventDefault();
20560
+ const touch = e.touches[0];
20561
+ setPreviewPosition({
20562
+ x: touch.clientX - dragStart.x,
20563
+ y: touch.clientY - dragStart.y
20564
+ });
20565
+ },
20566
+ [isDragging, dragStart]
20567
+ );
20568
+ const handlePreviewTouchEnd = useCallback(() => {
20569
+ setIsDragging(false);
20570
+ }, []);
20571
+ useEffect(() => {
20572
+ if (isDragging) {
20573
+ document.addEventListener("mousemove", handlePreviewMouseMove);
20574
+ document.addEventListener("mouseup", handlePreviewMouseUp);
20575
+ document.addEventListener("touchmove", handlePreviewTouchMove, {
20576
+ passive: false
20577
+ });
20578
+ document.addEventListener("touchend", handlePreviewTouchEnd);
20579
+ return () => {
20580
+ document.removeEventListener("mousemove", handlePreviewMouseMove);
20581
+ document.removeEventListener("mouseup", handlePreviewMouseUp);
20582
+ document.removeEventListener("touchmove", handlePreviewTouchMove);
20583
+ document.removeEventListener("touchend", handlePreviewTouchEnd);
20584
+ };
20585
+ }
20586
+ }, [
20587
+ isDragging,
20588
+ handlePreviewMouseMove,
20589
+ handlePreviewMouseUp,
20590
+ handlePreviewTouchMove,
20591
+ handlePreviewTouchEnd
20592
+ ]);
20593
+ useEffect(() => {
20594
+ if (!showPreviewLightbox) return;
20595
+ const handleEsc = (e) => {
20596
+ if (e.key === "Escape") {
20597
+ setShowPreviewLightbox(false);
20598
+ }
20599
+ };
20600
+ document.addEventListener("keydown", handleEsc);
20601
+ return () => document.removeEventListener("keydown", handleEsc);
20602
+ }, [showPreviewLightbox]);
20220
20603
  const processSingleCrop = useCallback(
20221
- async (cropIndex) => {
20604
+ async (cropIndex, overrideImageId = null) => {
20222
20605
  const crop = crops[cropIndex];
20223
20606
  if (!crop) {
20224
20607
  throw new Error(`Crop ${cropIndex} no encontrado`);
@@ -20257,9 +20640,10 @@ function CropperView({
20257
20640
  };
20258
20641
  const variantWidth = Math.min(crop.width, 5e3);
20259
20642
  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, {
20643
+ const cropLabel = crop.label || "crop";
20644
+ const variantName = `${cropLabel}_${variantWidth}_${variantHeight}`;
20645
+ const imageId = overrideImageId || image.id;
20646
+ const result = await createCropVariant(imageId, cropParams, {
20263
20647
  name: variantName,
20264
20648
  width: variantWidth,
20265
20649
  height: variantHeight,
@@ -20268,7 +20652,7 @@ function CropperView({
20268
20652
  });
20269
20653
  if (result) {
20270
20654
  accessibilityManager?.announceSuccess(`Recorte creado: ${variantName}`);
20271
- onVariantCreated?.(image.id, result);
20655
+ onVariantCreated?.(imageId, result);
20272
20656
  return result;
20273
20657
  }
20274
20658
  throw new Error("No se pudo crear la variante");
@@ -20301,9 +20685,25 @@ function CropperView({
20301
20685
  saveCurrentCropState();
20302
20686
  accessibilityManager?.announce("Creando recorte de la imagen");
20303
20687
  try {
20304
- const result = await processSingleCrop(activeCropIndex);
20688
+ let imageId = image.id;
20689
+ let uploadedAsset = null;
20690
+ if (image.file && onUpload) {
20691
+ accessibilityManager?.announce("Subiendo imagen...");
20692
+ const uploadResult = await onUpload(image.file);
20693
+ if (!uploadResult || !uploadResult.id) {
20694
+ throw new Error("No se pudo subir la imagen al servidor");
20695
+ }
20696
+ imageId = uploadResult.id;
20697
+ uploadedAsset = uploadResult;
20698
+ accessibilityManager?.announceSuccess("Imagen subida correctamente");
20699
+ }
20700
+ const result = await processSingleCrop(activeCropIndex, imageId);
20305
20701
  if (result) {
20306
- onSave(result);
20702
+ if (uploadedAsset) {
20703
+ onSave({ crops: [result], asset: uploadedAsset });
20704
+ } else {
20705
+ onSave(result);
20706
+ }
20307
20707
  }
20308
20708
  } catch (error) {
20309
20709
  console.warn("Error creating crop variant:", error);
@@ -20320,7 +20720,10 @@ function CropperView({
20320
20720
  processSingleCrop,
20321
20721
  activeCropIndex,
20322
20722
  onSave,
20323
- onError
20723
+ onError,
20724
+ image.file,
20725
+ image.id,
20726
+ onUpload
20324
20727
  ]);
20325
20728
  const performSaveMultipleCrops = useCallback(
20326
20729
  async (cropIndexes) => {
@@ -20328,34 +20731,55 @@ function CropperView({
20328
20731
  accessibilityManager?.announce(
20329
20732
  `Guardando ${cropIndexes.length} recortes...`
20330
20733
  );
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);
20734
+ try {
20735
+ let imageId = image.id;
20736
+ let uploadedAsset = null;
20737
+ if (image.file && onUpload) {
20738
+ accessibilityManager?.announce("Subiendo imagen...");
20739
+ const uploadResult = await onUpload(image.file);
20740
+ if (!uploadResult || !uploadResult.id) {
20741
+ throw new Error("No se pudo subir la imagen al servidor");
20338
20742
  }
20339
- } catch (error) {
20340
- errors.push({
20341
- crop: crops[index]?.label || `Crop ${index}`,
20342
- error: error.message
20343
- });
20743
+ imageId = uploadResult.id;
20744
+ uploadedAsset = uploadResult;
20745
+ accessibilityManager?.announceSuccess("Imagen subida correctamente");
20344
20746
  }
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]);
20747
+ const results = [];
20748
+ const errors = [];
20749
+ for (const index of cropIndexes) {
20750
+ try {
20751
+ const result = await processSingleCrop(index, imageId);
20752
+ if (result) {
20753
+ results.push(result);
20754
+ }
20755
+ } catch (error) {
20756
+ errors.push({
20757
+ crop: crops[index]?.label || `Crop ${index}`,
20758
+ error: error.message
20759
+ });
20760
+ }
20352
20761
  }
20353
- }
20354
- if (errors.length > 0) {
20355
- const errorMsg = `Errores al guardar algunos recortes:
20762
+ if (results.length > 0) {
20763
+ accessibilityManager?.announceSuccess(
20764
+ `${results.length} recorte(s) guardado(s) correctamente`
20765
+ );
20766
+ if (uploadedAsset) {
20767
+ onSave({ crops: results, asset: uploadedAsset });
20768
+ } else {
20769
+ onSave(results);
20770
+ }
20771
+ }
20772
+ if (errors.length > 0) {
20773
+ const errorMsg = `Errores al guardar algunos recortes:
20356
20774
  ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20775
+ accessibilityManager?.announceError(errorMsg);
20776
+ alert(errorMsg);
20777
+ }
20778
+ } catch (error) {
20779
+ const errorMsg = error.message || "Error al procesar los recortes";
20357
20780
  accessibilityManager?.announceError(errorMsg);
20358
20781
  alert(errorMsg);
20782
+ onError?.(error);
20359
20783
  }
20360
20784
  },
20361
20785
  [
@@ -20363,7 +20787,11 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20363
20787
  processSingleCrop,
20364
20788
  crops,
20365
20789
  accessibilityManager,
20366
- onSave
20790
+ onSave,
20791
+ image.file,
20792
+ image.id,
20793
+ onUpload,
20794
+ onError
20367
20795
  ]
20368
20796
  );
20369
20797
  const saveCrop = useCallback(async () => {
@@ -20440,8 +20868,8 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20440
20868
  `image/${globalThis.downloadFormat || image.mime_type.split("/")[1] || "webp"}`,
20441
20869
  0.9
20442
20870
  );
20443
- const cropName = crop.label.replace(/\.[^/.]+$/, "").replace(/\s+/g, "-").trim();
20444
- const filename = `${editableFilename}_${cropName || "crop"}`;
20871
+ const cropName = (crop.label || "crop").replace(/\.[^/.]+$/, "").replace(/\s+/g, "-").trim();
20872
+ const filename = `${cropName}_${crop.width}_${crop.height}`;
20445
20873
  await downloadImage(downloadUrl, filename, {
20446
20874
  accessibilityManager,
20447
20875
  onSuccess: (finalFilename) => {
@@ -20564,6 +20992,27 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20564
20992
  }
20565
20993
  await performDownload();
20566
20994
  }, [validateCropNames, crops, performDownload, activeCropIndex]);
20995
+ const handleKeepOriginal = useCallback(async () => {
20996
+ if (!image.file || !onUpload) {
20997
+ onCancel();
20998
+ return;
20999
+ }
21000
+ try {
21001
+ accessibilityManager?.announce("Subiendo imagen original...");
21002
+ const uploadResult = await onUpload(image.file);
21003
+ if (!uploadResult || !uploadResult.id) {
21004
+ throw new Error("No se pudo subir la imagen al servidor");
21005
+ }
21006
+ accessibilityManager?.announceSuccess("Imagen guardada correctamente");
21007
+ onSave({ asset: uploadResult, crops: [] });
21008
+ } catch (error) {
21009
+ console.error("Error guardando imagen original:", error);
21010
+ const errorMsg = error.message || "No se pudo guardar la imagen. Inténtalo de nuevo.";
21011
+ accessibilityManager?.announceError(errorMsg);
21012
+ alert(errorMsg);
21013
+ onError?.(error);
21014
+ }
21015
+ }, [image.file, onUpload, onCancel, onSave, onError, accessibilityManager]);
20567
21016
  useEffect(() => {
20568
21017
  setShowPreview(false);
20569
21018
  setPreviewUrl(null);
@@ -20694,7 +21143,7 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20694
21143
  /* @__PURE__ */ jsx("div", { className: "", children: editableFilename + "." + (globalThis.downloadFormat || image.mime_type.split("/")[1] || "webp") })
20695
21144
  ] })
20696
21145
  ] }),
20697
- /* @__PURE__ */ jsxs("div", { className: "flex flex-row self-end gap-2 w-full max-w-fit sm:w-auto", children: [
21146
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-row self-end gap-2 w-full max-w-fit sm:w-auto flex-wrap", children: [
20698
21147
  /* @__PURE__ */ jsxs(
20699
21148
  "button",
20700
21149
  {
@@ -20868,37 +21317,6 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20868
21317
  ] }),
20869
21318
  /* @__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
21319
  /* @__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
21320
  /* @__PURE__ */ jsx(
20903
21321
  LoadingOverlay,
20904
21322
  {
@@ -21403,7 +21821,7 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21403
21821
  ] })
21404
21822
  ] }) })
21405
21823
  ] }),
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: [
21824
+ /* @__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
21825
  /* @__PURE__ */ jsxs(
21408
21826
  "button",
21409
21827
  {
@@ -21417,32 +21835,46 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21417
21835
  ]
21418
21836
  }
21419
21837
  ),
21420
- /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-2 items-center justify-end", children: [
21838
+ /* @__PURE__ */ jsx(
21839
+ "button",
21840
+ {
21841
+ onClick: preview,
21842
+ disabled: creatingVariant || !canExport,
21843
+ 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`,
21844
+ "aria-label": "Vista previa",
21845
+ title: "Mostrar/Ocultar vista previa",
21846
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
21847
+ /* @__PURE__ */ jsx("span", { className: "icon icon-search-white icon--sm" }),
21848
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline whitespace-nowrap", children: showPreview ? "Cerrar previa" : "Vista previa" })
21849
+ ] })
21850
+ }
21851
+ ),
21852
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-1 lg:gap-2 items-center justify-end", children: [
21421
21853
  /* @__PURE__ */ jsx(
21422
21854
  "button",
21423
21855
  {
21424
- onClick: preview,
21856
+ onClick: handleDownload,
21425
21857
  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",
21858
+ 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",
21859
+ "aria-label": "Descargar",
21860
+ title: "Descargar recorte sin guardar",
21429
21861
  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" })
21862
+ /* @__PURE__ */ jsx("span", { className: "icon icon-download-white icon--sm" }),
21863
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline whitespace-nowrap", children: "Descargar" })
21432
21864
  ] })
21433
21865
  }
21434
21866
  ),
21435
- /* @__PURE__ */ jsx(
21867
+ image.file && /* @__PURE__ */ jsx(
21436
21868
  "button",
21437
21869
  {
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",
21870
+ onClick: handleKeepOriginal,
21871
+ disabled: creatingVariant || uploading,
21872
+ 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`,
21873
+ "aria-label": "Conservar solo imagen original sin crear recortes",
21874
+ title: "Subir y guardar la imagen original sin crear recortes",
21443
21875
  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" })
21876
+ /* @__PURE__ */ jsx("span", { className: "icon icon-tick-white icon--sm" }),
21877
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline whitespace-nowrap", children: "Conservar original" })
21446
21878
  ] })
21447
21879
  }
21448
21880
  ),
@@ -21452,7 +21884,6 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21452
21884
  onClick: saveCrop,
21453
21885
  disabled: creatingVariant || !cropData || !effectiveImageInfo || !canExport || !state.isReady,
21454
21886
  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
21887
  title: "Guardar imagen recortada",
21457
21888
  children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: creatingVariant ? /* @__PURE__ */ jsxs(Fragment, { children: [
21458
21889
  /* @__PURE__ */ jsx("span", { className: "icon icon-save-white icon--sm" }),
@@ -21774,7 +22205,165 @@ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
21774
22205
  children: "Entendido"
21775
22206
  }
21776
22207
  ) })
21777
- ] }) })
22208
+ ] }) }),
22209
+ showPreview && /* @__PURE__ */ jsx(
22210
+ "div",
22211
+ {
22212
+ 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",
22213
+ onClick: (e) => {
22214
+ if (e.target === e.currentTarget && window.innerWidth < 768) {
22215
+ setShowPreview(false);
22216
+ setPreviewUrl(null);
22217
+ }
22218
+ },
22219
+ children: /* @__PURE__ */ jsxs(
22220
+ "div",
22221
+ {
22222
+ 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]",
22223
+ style: {
22224
+ // En desktop: posicionamiento absoluto con drag y width fijo
22225
+ // En móvil: centrado con flexbox (sin drag)
22226
+ ...window.innerWidth >= 768 ? {
22227
+ position: "absolute",
22228
+ left: `${previewPosition.x}px`,
22229
+ top: `${previewPosition.y}px`,
22230
+ width: "420px",
22231
+ cursor: isDragging ? "grabbing" : "default"
22232
+ } : {}
22233
+ },
22234
+ onMouseDown: handlePreviewMouseDown,
22235
+ onTouchStart: handlePreviewTouchStart,
22236
+ children: [
22237
+ /* @__PURE__ */ jsxs(
22238
+ "div",
22239
+ {
22240
+ className: "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",
22241
+ style: { cursor: isDragging ? "grabbing" : "grab" },
22242
+ children: [
22243
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
22244
+ /* @__PURE__ */ jsx("span", { className: "icon icon-search-white icon--sm" }),
22245
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Vista previa" })
22246
+ ] }),
22247
+ /* @__PURE__ */ jsx(
22248
+ "button",
22249
+ {
22250
+ onClick: () => {
22251
+ setShowPreview(false);
22252
+ setPreviewUrl(null);
22253
+ },
22254
+ className: "flex p-1 hover:bg-white/20 rounded transition-colors cursor-pointer aspect-square min-w-fit min-h-fit",
22255
+ "aria-label": "Cerrar vista previa",
22256
+ title: "Cerrar vista previa",
22257
+ children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small-white icon--sm p-0 m-0 py-auto my-auto mx-auto" })
22258
+ }
22259
+ )
22260
+ ]
22261
+ }
22262
+ ),
22263
+ /* @__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: [
22264
+ /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-3" }),
22265
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "Generando vista previa..." })
22266
+ ] }) }) : previewUrl ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3", children: [
22267
+ /* @__PURE__ */ jsxs(
22268
+ "div",
22269
+ {
22270
+ 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",
22271
+ onClick: () => setShowPreviewLightbox(true),
22272
+ role: "button",
22273
+ tabIndex: 0,
22274
+ onKeyDown: (e) => {
22275
+ if (e.key === "Enter" || e.key === " ") {
22276
+ e.preventDefault();
22277
+ setShowPreviewLightbox(true);
22278
+ }
22279
+ },
22280
+ "aria-label": "Click para ver recorte en tamaño completo",
22281
+ title: "Click para ampliar",
22282
+ children: [
22283
+ /* @__PURE__ */ jsx(
22284
+ "img",
22285
+ {
22286
+ src: previewUrl,
22287
+ alt: "Vista previa del recorte",
22288
+ className: "w-full h-auto object-contain max-h-[400px]"
22289
+ }
22290
+ ),
22291
+ /* @__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: [
22292
+ /* @__PURE__ */ jsx("span", { className: "icon icon-search-white icon--xs" }),
22293
+ /* @__PURE__ */ jsx("span", { children: "Ampliar" })
22294
+ ] })
22295
+ ]
22296
+ }
22297
+ ),
22298
+ /* @__PURE__ */ jsxs("div", { className: "text-center text-xs text-gray-500", children: [
22299
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-gray-700 mb-1", children: activeCrop?.label || "Recorte" }),
22300
+ /* @__PURE__ */ jsxs("p", { children: [
22301
+ activeCrop?.width,
22302
+ " × ",
22303
+ activeCrop?.height,
22304
+ " px"
22305
+ ] })
22306
+ ] })
22307
+ ] }) : /* @__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" }) }) }),
22308
+ /* @__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: [
22309
+ /* @__PURE__ */ jsxs("span", { className: "hidden md:inline", children: [
22310
+ "Arrastra desde el título para mover •",
22311
+ " "
22312
+ ] }),
22313
+ "Actualización automática al editar"
22314
+ ] }) })
22315
+ ]
22316
+ }
22317
+ )
22318
+ }
22319
+ ),
22320
+ showPreviewLightbox && previewUrl && /* @__PURE__ */ jsxs(
22321
+ "div",
22322
+ {
22323
+ className: "fixed inset-0 bg-black/95 z-[10001] flex items-center justify-center p-4",
22324
+ onClick: () => setShowPreviewLightbox(false),
22325
+ role: "dialog",
22326
+ "aria-modal": "true",
22327
+ "aria-label": "Vista de recorte en tamaño completo",
22328
+ children: [
22329
+ /* @__PURE__ */ jsx(
22330
+ "button",
22331
+ {
22332
+ onClick: () => setShowPreviewLightbox(false),
22333
+ className: "absolute top-4 right-4 p-3 bg-white/10 hover:bg-white/20 rounded-full transition-colors cursor-pointer z-10",
22334
+ "aria-label": "Cerrar vista ampliada",
22335
+ title: "Cerrar (Esc)",
22336
+ children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small-white icon--lg" })
22337
+ }
22338
+ ),
22339
+ /* @__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: [
22340
+ /* @__PURE__ */ jsx("p", { className: "font-semibold text-base mb-1", children: activeCrop?.label || "Recorte" }),
22341
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-300", children: [
22342
+ activeCrop?.width,
22343
+ " × ",
22344
+ activeCrop?.height,
22345
+ " px"
22346
+ ] })
22347
+ ] }),
22348
+ /* @__PURE__ */ jsx(
22349
+ "div",
22350
+ {
22351
+ className: "relative max-w-[95vw] max-h-[95vh] w-full h-full flex items-center justify-center",
22352
+ onClick: (e) => e.stopPropagation(),
22353
+ children: /* @__PURE__ */ jsx(
22354
+ "img",
22355
+ {
22356
+ src: previewUrl,
22357
+ alt: "Vista completa del recorte",
22358
+ className: "max-w-full max-h-full object-contain rounded-lg shadow-2xl"
22359
+ }
22360
+ )
22361
+ }
22362
+ ),
22363
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-4 left-1/2 -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" })
22364
+ ]
22365
+ }
22366
+ )
21778
22367
  ] });
21779
22368
  }
21780
22369
  function Pagination({
@@ -21922,6 +22511,27 @@ function TokenExpiredModal({ isOpen, onClose }) {
21922
22511
  }
21923
22512
  );
21924
22513
  }
22514
+ function Breadcrumb({ items = [], currentLabel }) {
22515
+ if (!items.length && !currentLabel) {
22516
+ return null;
22517
+ }
22518
+ return /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className: "mb-4", children: /* @__PURE__ */ jsxs("ol", { className: "flex items-center gap-2 text-sm flex-wrap px-4 pt-2 pb-0", children: [
22519
+ items.map((item, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
22520
+ /* @__PURE__ */ jsx(
22521
+ "button",
22522
+ {
22523
+ type: "button",
22524
+ onClick: item.onClick,
22525
+ className: "text-brand-blue-800 hover:text-brand-blue-1000 hover:underline cursor-pointer transition-colors font-medium",
22526
+ "aria-label": `Ir a ${item.label}`,
22527
+ children: item.label
22528
+ }
22529
+ ),
22530
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400", "aria-hidden": "true", children: "/" })
22531
+ ] }, index)),
22532
+ currentLabel && /* @__PURE__ */ jsx("li", { className: "text-gray-700 font-medium", "aria-current": "page", children: currentLabel })
22533
+ ] }) });
22534
+ }
21925
22535
  function useUploadImage() {
21926
22536
  const [loading, setLoading] = useState(false);
21927
22537
  const [error, setError] = useState(null);
@@ -22158,6 +22768,29 @@ function App({
22158
22768
  }
22159
22769
  };
22160
22770
  const activeFeatures = getFilteredFeatures();
22771
+ const getTabLabel = (tabName) => {
22772
+ const labels = {
22773
+ gallery: "Galería",
22774
+ upload: "Subir",
22775
+ cropper: "Recortar",
22776
+ // Sub-tabs de Upload
22777
+ "upload:upload": "Subir archivo",
22778
+ "upload:ai": "Generar con IA",
22779
+ "upload:stock": "Buscar en Stock",
22780
+ "upload:portals": "Otros portales"
22781
+ };
22782
+ if (tabName && tabName.includes(":") && tabName.split(":").length === 3) {
22783
+ const serviceName = tabName.split(":")[2];
22784
+ return serviceName.charAt(0).toUpperCase() + serviceName.slice(1);
22785
+ }
22786
+ return labels[tabName] || tabName;
22787
+ };
22788
+ const isUploadSubTab = (tabName) => {
22789
+ return tabName && tabName.startsWith("upload:") && tabName.split(":").length === 2;
22790
+ };
22791
+ const isUploadServiceTab = (tabName) => {
22792
+ return tabName && tabName.startsWith("upload:") && tabName.split(":").length === 3;
22793
+ };
22161
22794
  const getInitialTab = () => {
22162
22795
  if (activeFeatures.includes("gallery")) return "gallery";
22163
22796
  if (activeFeatures.includes("upload")) return "upload";
@@ -22167,6 +22800,79 @@ function App({
22167
22800
  };
22168
22801
  const [activeTab, setActiveTab] = useState(getInitialTab());
22169
22802
  const [selectedImage, setSelectedImage] = useState(null);
22803
+ const [navigationHistory, setNavigationHistory] = useState([
22804
+ { tab: getInitialTab(), label: getTabLabel(getInitialTab()) }
22805
+ ]);
22806
+ const addToNavigationHistory = (newTab) => {
22807
+ setNavigationHistory((prev) => {
22808
+ const existingIndex = prev.findIndex((item) => item.tab === newTab);
22809
+ if (existingIndex !== -1) {
22810
+ if (existingIndex === prev.length - 1) {
22811
+ return prev;
22812
+ }
22813
+ return prev.slice(0, existingIndex + 1);
22814
+ }
22815
+ if (isUploadServiceTab(newTab)) {
22816
+ const parts = newTab.split(":");
22817
+ const subTab = `${parts[0]}:${parts[1]}`;
22818
+ const subTabIndex = prev.findIndex((item) => item.tab === subTab);
22819
+ let newHistory2;
22820
+ if (subTabIndex === -1) {
22821
+ const uploadIndex = prev.findIndex((item) => item.tab === "upload");
22822
+ if (uploadIndex === -1) {
22823
+ newHistory2 = [
22824
+ ...prev,
22825
+ { tab: "upload", label: getTabLabel("upload") },
22826
+ { tab: subTab, label: getTabLabel(subTab) }
22827
+ ];
22828
+ } else {
22829
+ newHistory2 = [
22830
+ ...prev.slice(0, uploadIndex + 1),
22831
+ { tab: subTab, label: getTabLabel(subTab) }
22832
+ ];
22833
+ }
22834
+ } else {
22835
+ newHistory2 = prev.slice(0, subTabIndex + 1);
22836
+ }
22837
+ newHistory2.push({ tab: newTab, label: getTabLabel(newTab) });
22838
+ const MAX_HISTORY2 = 5;
22839
+ if (newHistory2.length > MAX_HISTORY2) {
22840
+ return newHistory2.slice(newHistory2.length - MAX_HISTORY2);
22841
+ }
22842
+ return newHistory2;
22843
+ }
22844
+ if (isUploadSubTab(newTab)) {
22845
+ const uploadIndex = prev.findIndex((item) => item.tab === "upload");
22846
+ let newHistory2;
22847
+ if (uploadIndex === -1) {
22848
+ newHistory2 = [
22849
+ ...prev,
22850
+ { tab: "upload", label: getTabLabel("upload") }
22851
+ ];
22852
+ } else {
22853
+ newHistory2 = prev.slice(0, uploadIndex + 1);
22854
+ }
22855
+ newHistory2.push({ tab: newTab, label: getTabLabel(newTab) });
22856
+ const MAX_HISTORY2 = 5;
22857
+ if (newHistory2.length > MAX_HISTORY2) {
22858
+ return newHistory2.slice(newHistory2.length - MAX_HISTORY2);
22859
+ }
22860
+ return newHistory2;
22861
+ }
22862
+ if (newTab === "upload") {
22863
+ const uploadIndex = prev.findIndex((item) => item.tab === "upload");
22864
+ if (uploadIndex !== -1) {
22865
+ return prev.slice(0, uploadIndex + 1);
22866
+ }
22867
+ }
22868
+ const newHistory = [...prev, { tab: newTab, label: getTabLabel(newTab) }];
22869
+ const MAX_HISTORY = 5;
22870
+ if (newHistory.length > MAX_HISTORY) {
22871
+ return newHistory.slice(newHistory.length - MAX_HISTORY);
22872
+ }
22873
+ return newHistory;
22874
+ });
22875
+ };
22170
22876
  useEffect(() => {
22171
22877
  if (_externalImage) {
22172
22878
  console.log("📸 Imagen externa detectada:", _externalImage);
@@ -22231,28 +22937,62 @@ function App({
22231
22937
  const tabs = availableTabs.filter(
22232
22938
  (tab) => activeFeatures.includes(tab.feature)
22233
22939
  );
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
- });
22940
+ const handleUploadAndCrop = async (file) => {
22941
+ const url2 = URL.createObjectURL(file);
22942
+ const dimensions = await new Promise((resolve) => {
22943
+ const img = new Image();
22944
+ img.onload = () => {
22945
+ resolve({ width: img.naturalWidth, height: img.naturalHeight });
22946
+ URL.revokeObjectURL(url2);
22947
+ };
22948
+ img.onerror = () => {
22949
+ resolve({ width: 1920, height: 1080 });
22950
+ URL.revokeObjectURL(url2);
22951
+ };
22952
+ img.src = url2;
22953
+ });
22954
+ const tempImage = {
22955
+ file,
22956
+ // File object (indica que es imagen nueva sin confirmar)
22957
+ filename: file.name,
22958
+ mime_type: file.type,
22959
+ url: URL.createObjectURL(file),
22960
+ // Nueva URL temporal para preview
22961
+ width: dimensions.width,
22962
+ // Dimensiones reales
22963
+ height: dimensions.height
22964
+ // Dimensiones reales
22965
+ // NO tiene id - el cropper lo generará al subir
22966
+ };
22967
+ setSelectedImage(tempImage);
22968
+ if (activeFeatures.includes("cropper")) {
22969
+ setActiveTab("cropper");
22970
+ addToNavigationHistory("cropper");
22971
+ } else {
22972
+ const result = await upload(file);
22973
+ if (result) {
22974
+ invalidateCache();
22975
+ setCurrentPage(1);
22976
+ setImages((prev) => [result, ...prev]);
22977
+ setActiveTab("gallery");
22978
+ if (callbacks.onUpload) {
22979
+ callbacks.onUpload({
22980
+ assetId: result.id,
22981
+ url: result.url,
22982
+ fileName: result.filename,
22983
+ mime: result.mime_type,
22984
+ width: result.width,
22985
+ height: result.height,
22986
+ instanceId
22987
+ });
22988
+ }
22251
22989
  }
22252
22990
  }
22253
22991
  };
22254
22992
  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.")) {
22993
+ if (!confirm(
22994
+ "¿Estás seguro de que deseas eliminar esta imagen? Esta acción también eliminará todos sus recortes."
22995
+ )) {
22256
22996
  return;
22257
22997
  }
22258
22998
  const success = await deleteImg(imageId);
@@ -22283,12 +23023,14 @@ function App({
22283
23023
  setSelectedImage(img);
22284
23024
  if (activeFeatures.includes("cropper")) {
22285
23025
  setActiveTab("cropper");
23026
+ addToNavigationHistory("cropper");
22286
23027
  }
22287
23028
  };
22288
23029
  const handleTabChange = (tabId) => {
22289
23030
  setActiveTab(tabId);
22290
23031
  if (tabId !== "upload") resetUpload();
22291
23032
  if (tabId !== "gallery") resetDelete();
23033
+ addToNavigationHistory(tabId);
22292
23034
  };
22293
23035
  const determineScenario = () => {
22294
23036
  if (modeUI === "crop-only" || activeFeatures.length === 1 && activeFeatures[0] === "cropper") {
@@ -22307,21 +23049,45 @@ function App({
22307
23049
  const globalConfig2 = window.limboCore?.config?.getGlobal() || {};
22308
23050
  const mode = globalConfig2.mode || "embed";
22309
23051
  const autoHide = globalConfig2.autoHideOnComplete || false;
22310
- const crops = Array.isArray(result) ? result : [result];
23052
+ let crops = [];
23053
+ let uploadedAsset = null;
23054
+ if (result.crops && result.asset) {
23055
+ crops = result.crops;
23056
+ uploadedAsset = result.asset;
23057
+ } else if (Array.isArray(result)) {
23058
+ crops = result;
23059
+ } else {
23060
+ crops = [result];
23061
+ }
23062
+ if (uploadedAsset && scenario === "with-gallery") {
23063
+ setImages((prev) => [uploadedAsset, ...prev]);
23064
+ setCurrentPage(1);
23065
+ if (callbacks.onUpload) {
23066
+ callbacks.onUpload({
23067
+ assetId: uploadedAsset.id,
23068
+ url: uploadedAsset.url,
23069
+ fileName: uploadedAsset.filename,
23070
+ mime: uploadedAsset.mime_type,
23071
+ width: uploadedAsset.width,
23072
+ height: uploadedAsset.height,
23073
+ instanceId
23074
+ });
23075
+ }
23076
+ }
22311
23077
  if (scenario === "with-gallery") {
22312
23078
  invalidateCache();
22313
23079
  }
22314
23080
  if (callbacks.onCropsSaved) {
22315
23081
  callbacks.onCropsSaved({
22316
23082
  crops,
22317
- assetId: selectedImage?.id,
23083
+ assetId: uploadedAsset?.id || selectedImage?.id,
22318
23084
  instanceId
22319
23085
  });
22320
23086
  }
22321
23087
  if (window.limboCore?.events) {
22322
23088
  window.limboCore.events.emit("cropsSaved", {
22323
23089
  crops,
22324
- assetId: selectedImage?.id,
23090
+ assetId: uploadedAsset?.id || selectedImage?.id,
22325
23091
  instanceId
22326
23092
  });
22327
23093
  }
@@ -22367,7 +23133,9 @@ function App({
22367
23133
  });
22368
23134
  }
22369
23135
  if (autoHide) {
22370
- const container = document.querySelector(`#limbo-instance-${instanceId}`);
23136
+ const container = document.querySelector(
23137
+ `#limbo-instance-${instanceId}`
23138
+ );
22371
23139
  if (container) container.style.display = "none";
22372
23140
  }
22373
23141
  }
@@ -22430,7 +23198,37 @@ function App({
22430
23198
  }
22431
23199
  console.error("Cropper error:", error);
22432
23200
  };
23201
+ const navigateToHistoryPoint = (index) => {
23202
+ const historyPoint = navigationHistory[index];
23203
+ if (!historyPoint) return;
23204
+ if (historyPoint.tab !== "cropper") {
23205
+ setSelectedImage(null);
23206
+ }
23207
+ setActiveTab(historyPoint.tab);
23208
+ setNavigationHistory((prev) => prev.slice(0, index + 1));
23209
+ };
23210
+ const getBreadcrumbs = () => {
23211
+ if (navigationHistory.length <= 1) {
23212
+ return null;
23213
+ }
23214
+ const items = navigationHistory.slice(0, -1).map((item, index) => {
23215
+ return {
23216
+ label: item.label,
23217
+ onClick: item.tab !== "subir" || item.tab !== "upload" ? () => navigateToHistoryPoint(index) : void 0
23218
+ };
23219
+ });
23220
+ const current = navigationHistory[navigationHistory.length - 1].label;
23221
+ return { items, current };
23222
+ };
23223
+ const breadcrumbData = getBreadcrumbs();
22433
23224
  return /* @__PURE__ */ jsxs(Fragment, { children: [
23225
+ breadcrumbData && /* @__PURE__ */ jsx(
23226
+ Breadcrumb,
23227
+ {
23228
+ items: breadcrumbData.items,
23229
+ currentLabel: breadcrumbData.current
23230
+ }
23231
+ ),
22434
23232
  ui.showTabs && activeTab != "cropper" && /* @__PURE__ */ jsx(Tabs, { tabs, active: activeTab, onChange: handleTabChange }),
22435
23233
  imagesError && /* @__PURE__ */ jsxs("div", { className: "alert alert-danger", children: [
22436
23234
  "Error al cargar imágenes: ",
@@ -22484,14 +23282,20 @@ function App({
22484
23282
  "Error al subir imagen: ",
22485
23283
  uploadError
22486
23284
  ] }),
22487
- uploadedImage && /* @__PURE__ */ jsxs("div", { className: "alert alert-success", children: [
23285
+ uploadedImage && /* @__PURE__ */ jsxs("div", { className: "alert alert-success absolute top-0 right-0 m-4", children: [
22488
23286
  "✅ Imagen subida correctamente: ",
22489
23287
  uploadedImage.filename
22490
23288
  ] }),
22491
23289
  /* @__PURE__ */ jsx(
22492
23290
  UploadForm,
22493
23291
  {
22494
- onUpload: handleUpload,
23292
+ onSelect: handleUploadAndCrop,
23293
+ onSubTabChange: (subTabId) => {
23294
+ addToNavigationHistory(`upload:${subTabId}`);
23295
+ },
23296
+ onServiceChange: (subTabId, serviceSlug) => {
23297
+ addToNavigationHistory(`upload:${subTabId}:${serviceSlug}`);
23298
+ },
22495
23299
  disabled: uploading,
22496
23300
  apiKey,
22497
23301
  prod
@@ -22504,10 +23308,12 @@ function App({
22504
23308
  image: selectedImage,
22505
23309
  onSave: handleCropSave,
22506
23310
  onCancel: handleCropCancel,
22507
- onDelete: () => handleDelete(selectedImage?.id),
23311
+ onDelete: selectedImage?.id ? () => handleDelete(selectedImage.id) : null,
22508
23312
  onError: handleCropError,
22509
23313
  deleting,
22510
- onVariantCreated: handleVariantCreated
23314
+ onVariantCreated: handleVariantCreated,
23315
+ onUpload: upload,
23316
+ uploading
22511
23317
  }
22512
23318
  ),
22513
23319
  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 +26933,7 @@ const Limbo = new LimboCore();
26127
26933
  if (typeof window !== "undefined") {
26128
26934
  window.Limbo = Limbo;
26129
26935
  }
26130
- const PUBLIC_KEY = "pk_d2edad56de145fee22c8b80f6ce3448f";
26936
+ const PUBLIC_KEY = "pk_e464fd744106b7a8d63d453c4bd02582";
26131
26937
  if (typeof window !== "undefined" && document.querySelector("#root")) {
26132
26938
  Limbo.configure({
26133
26939
  prod: false,