limbo-component 1.8.4 → 1.9.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",
12629
- // PREPRODUCCIÓN - Updated URL
12630
- // DEV: "http://localhost", // LOCAL - Para desarrollo local
12628
+ // DEV: "https://led-dev-limbo-dev.eu.els.local", // PREPRODUCCIÓN - Updated URL
12629
+ DEV: "http://localhost",
12630
+ // LOCAL - Para desarrollo local
12631
12631
  PROD: "https://limbo.lefebvre.com"
12632
12632
  };
12633
12633
  let globalConfig = {
@@ -12635,9 +12635,11 @@ let globalConfig = {
12635
12635
  token: null,
12636
12636
  // JWT token (opcional, generado automáticamente)
12637
12637
  authMode: null,
12638
- // "session" | "manual"
12638
+ // "session" | "manual" | "jwt"
12639
12639
  tokenEndpoint: null,
12640
12640
  // Endpoint para obtener token (configurable)
12641
+ tokenProvider: null,
12642
+ // Custom function to provide JWT token
12641
12643
  prod: false
12642
12644
  };
12643
12645
  function configureApiClient(config) {
@@ -12657,13 +12659,23 @@ function getBaseUrl({ prod = false } = {}) {
12657
12659
  return prod ? API_URLS.PROD : API_URLS.DEV;
12658
12660
  }
12659
12661
  async function getHeaders({ isFormData = false, useJWT = true, customHeaders = {} } = {}) {
12660
- const headers = {};
12662
+ const headers = { "Access-Control-Allow-Origin": "*" };
12661
12663
  if (!isFormData) {
12662
12664
  headers["Content-Type"] = "application/json";
12663
12665
  }
12664
12666
  let token = globalConfig.token;
12665
12667
  if (useJWT) {
12666
- if (globalConfig.authMode === "session" && !token) {
12668
+ if (globalConfig.authMode === "jwt" && globalConfig.tokenProvider && !token) {
12669
+ try {
12670
+ console.log("🔑 Calling tokenProvider...");
12671
+ token = await globalConfig.tokenProvider();
12672
+ console.log("✅ Token obtained from tokenProvider");
12673
+ globalConfig.token = token;
12674
+ } catch (error) {
12675
+ console.error("❌ tokenProvider failed:", error);
12676
+ throw new Error("Failed to obtain token from tokenProvider: " + error.message);
12677
+ }
12678
+ } else if (globalConfig.authMode === "session" && !token) {
12667
12679
  try {
12668
12680
  const baseUrl = getBaseUrl(globalConfig);
12669
12681
  const endpoint = globalConfig.tokenEndpoint || "/auth/token";
@@ -12691,7 +12703,8 @@ async function getHeaders({ isFormData = false, useJWT = true, customHeaders = {
12691
12703
  } else {
12692
12704
  console.warn("⚠️ No JWT token available:", {
12693
12705
  authMode: globalConfig.authMode,
12694
- hasPublicKey: !!globalConfig.publicKey
12706
+ hasPublicKey: !!globalConfig.publicKey,
12707
+ hasTokenProvider: !!globalConfig.tokenProvider
12695
12708
  });
12696
12709
  }
12697
12710
  }
@@ -12734,6 +12747,15 @@ async function callApi({
12734
12747
  credentials: "include"
12735
12748
  // Include cookies for CORS
12736
12749
  });
12750
+ if (res.status === 204) {
12751
+ return {
12752
+ success: true,
12753
+ data: null,
12754
+ message: "No content",
12755
+ httpCode: 204,
12756
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
12757
+ };
12758
+ }
12737
12759
  if (!res.ok) {
12738
12760
  let errorMessage = `HTTP ${res.status}: ${res.statusText}`;
12739
12761
  let errorData = null;
@@ -12906,17 +12928,37 @@ function adaptVariantGenerationFromV2(v2Response) {
12906
12928
  return { result: null };
12907
12929
  }
12908
12930
  const data = v2Response.data;
12931
+ const isSync = data.processing_mode === "sync";
12932
+ if (isSync && data.variants && data.variants.length > 0) {
12933
+ const firstVariant = data.variants[0];
12934
+ return {
12935
+ result: {
12936
+ id: firstVariant.id || null,
12937
+ name: firstVariant.name,
12938
+ width: parseInt(firstVariant.size?.split("x")[0]) || firstVariant.width,
12939
+ height: parseInt(firstVariant.size?.split("x")[1]) || firstVariant.height,
12940
+ format: firstVariant.format,
12941
+ file_size: firstVariant.file_size,
12942
+ url: firstVariant.url,
12943
+ status: firstVariant.status,
12944
+ asset_id: data.asset_id,
12945
+ created_at: firstVariant.created_at || (/* @__PURE__ */ new Date()).toISOString(),
12946
+ // Mantener info de todas las variantes si se necesita
12947
+ all_variants: data.variants
12948
+ }
12949
+ };
12950
+ }
12909
12951
  const legacyResponse = {
12910
12952
  job_id: data.job_id,
12911
12953
  asset_id: data.asset_id,
12912
12954
  variants_requested: data.variants_requested || [],
12913
- processing_mode: data.async ? "async" : "sync",
12955
+ processing_mode: data.processing_mode || "async",
12914
12956
  estimated_completion: data.estimated_completion,
12915
12957
  // Estado de cada variante
12916
- variant_statuses: (data.variant_statuses || []).map((status) => ({
12958
+ variant_statuses: (data.variant_statuses || data.variants || []).map((status) => ({
12917
12959
  name: status.name,
12918
12960
  status: status.status,
12919
- size: status.expected_size,
12961
+ size: status.expected_size || status.size,
12920
12962
  format: status.format
12921
12963
  }))
12922
12964
  };
@@ -14128,7 +14170,9 @@ function Gallery({
14128
14170
  } else if (!loading && !error) {
14129
14171
  accessibilityManager?.announceLoading(false, "galería");
14130
14172
  if (images.length > 0) {
14131
- accessibilityManager?.announce(`Se encontraron ${images.length} imágenes en la galería`);
14173
+ accessibilityManager?.announce(
14174
+ `Se encontraron ${images.length} imágenes en la galería`
14175
+ );
14132
14176
  }
14133
14177
  } else if (error) {
14134
14178
  accessibilityManager?.announceError(error, "galería");
@@ -14140,8 +14184,8 @@ function Gallery({
14140
14184
  onFiltersChange({ ...filters, [name]: value });
14141
14185
  }
14142
14186
  };
14143
- return /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
14144
- /* @__PURE__ */ jsx("div", { className: "limbo-card mb-6 p-4 bg-brand-blue-050 shadow-md", children: /* @__PURE__ */ jsxs(
14187
+ return /* @__PURE__ */ jsxs("div", { className: "w-full px-4 py-4", children: [
14188
+ /* @__PURE__ */ jsx("div", { className: "p-2 mb-2", children: /* @__PURE__ */ jsxs(
14145
14189
  "form",
14146
14190
  {
14147
14191
  className: "limbo-form-grid flex flex-col sm:flex-row flex-wrap gap-4 items-start sm:items-end justify-between",
@@ -14218,27 +14262,29 @@ function Gallery({
14218
14262
  "div",
14219
14263
  {
14220
14264
  ref: galleryRef,
14221
- className: "limbo-gallery mt-4",
14265
+ className: "limbo-gallery",
14222
14266
  "data-limbo-responsive": true,
14223
14267
  role: "grid",
14224
14268
  "aria-label": "Cargando imágenes de la galería",
14225
14269
  "aria-busy": "true",
14226
- children: Array.from({ length: loadingConfig.placeholderCount }).map((_, index) => /* @__PURE__ */ jsx(
14227
- "div",
14228
- {
14229
- role: "gridcell",
14230
- "aria-posinset": index + 1,
14231
- "aria-setsize": loadingConfig.placeholderCount,
14232
- children: /* @__PURE__ */ jsx(ImageCardSkeleton, {})
14233
- },
14234
- `skeleton-${index}`
14235
- ))
14270
+ children: Array.from({ length: loadingConfig.placeholderCount }).map(
14271
+ (_, index) => /* @__PURE__ */ jsx(
14272
+ "div",
14273
+ {
14274
+ role: "gridcell",
14275
+ "aria-posinset": index + 1,
14276
+ "aria-setsize": loadingConfig.placeholderCount,
14277
+ children: /* @__PURE__ */ jsx(ImageCardSkeleton, {})
14278
+ },
14279
+ `skeleton-${index}`
14280
+ )
14281
+ )
14236
14282
  }
14237
14283
  ) : loadingConfig.showSpinner ? /* @__PURE__ */ jsx(Loader, { text: "Cargando imágenes..." }) : null : error ? /* @__PURE__ */ jsx("div", { className: "alert alert-danger text-center", children: error }) : /* @__PURE__ */ jsx(
14238
14284
  "div",
14239
14285
  {
14240
14286
  ref: galleryRef,
14241
- className: "limbo-gallery mt-4",
14287
+ className: "limbo-gallery",
14242
14288
  "data-limbo-responsive": true,
14243
14289
  "data-grid-id": "gallery-grid",
14244
14290
  role: "grid",
@@ -14263,12 +14309,28 @@ function Gallery({
14263
14309
  )
14264
14310
  },
14265
14311
  img.id
14266
- )) : /* @__PURE__ */ jsx("div", { className: "col-span-full text-center text-gray-500 py-8", role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx("p", { children: "No se han encontrado imágenes" }) })
14312
+ )) : /* @__PURE__ */ jsx(
14313
+ "div",
14314
+ {
14315
+ className: "col-span-full text-center text-gray-500 py-8",
14316
+ role: "status",
14317
+ "aria-live": "polite",
14318
+ children: /* @__PURE__ */ jsx("p", { children: "No se han encontrado imágenes" })
14319
+ }
14320
+ )
14267
14321
  }
14268
14322
  )
14269
14323
  ] });
14270
14324
  }
14271
- function TabUpload({ file, setFile, previewUrl, setPreviewUrl, fileInputRef, onUpload, disabled }) {
14325
+ function TabUpload({
14326
+ file,
14327
+ setFile,
14328
+ previewUrl,
14329
+ setPreviewUrl,
14330
+ fileInputRef,
14331
+ onUpload,
14332
+ disabled
14333
+ }) {
14272
14334
  const handleFileChange = (e) => {
14273
14335
  const f = e.target.files[0];
14274
14336
  setFile(f);
@@ -14279,6 +14341,15 @@ function TabUpload({ file, setFile, previewUrl, setPreviewUrl, fileInputRef, onU
14279
14341
  setPreviewUrl(null);
14280
14342
  }
14281
14343
  };
14344
+ const handleClearFile = () => {
14345
+ const confirmClear = window.confirm(
14346
+ "¿Estás seguro de que deseas descartar la imagen seleccionada?"
14347
+ );
14348
+ if (!confirmClear) return;
14349
+ setFile(null);
14350
+ setPreviewUrl(null);
14351
+ if (fileInputRef.current) fileInputRef.current.value = "";
14352
+ };
14282
14353
  const handleSubmit = (e) => {
14283
14354
  e.preventDefault();
14284
14355
  if (file && !disabled) {
@@ -14295,7 +14366,7 @@ function TabUpload({ file, setFile, previewUrl, setPreviewUrl, fileInputRef, onU
14295
14366
  className: "flex flex-col items-center gap-6",
14296
14367
  "aria-label": "Subir imagen desde dispositivo",
14297
14368
  children: [
14298
- /* @__PURE__ */ jsxs(
14369
+ !previewUrl && /* @__PURE__ */ jsxs(
14299
14370
  "label",
14300
14371
  {
14301
14372
  htmlFor: "file-input",
@@ -14322,17 +14393,31 @@ function TabUpload({ file, setFile, previewUrl, setPreviewUrl, fileInputRef, onU
14322
14393
  ]
14323
14394
  }
14324
14395
  ),
14325
- previewUrl && /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col items-center gap-2", children: [
14326
- /* @__PURE__ */ jsx(
14327
- "img",
14328
- {
14329
- src: previewUrl,
14330
- alt: file?.name || "Previsualización",
14331
- className: "rounded-lg shadow-md border border-brand-blue-200 max-h-64 object-contain bg-white",
14332
- style: { maxWidth: "100%" }
14333
- }
14334
- ),
14335
- /* @__PURE__ */ jsxs("div", { className: "text-sm text-neutral-800 bg-brand-blue-050 p-2 rounded w-full text-center", children: [
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: [
14336
14421
  /* @__PURE__ */ jsxs("p", { children: [
14337
14422
  /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Archivo:" }),
14338
14423
  " ",
@@ -14349,17 +14434,30 @@ function TabUpload({ file, setFile, previewUrl, setPreviewUrl, fileInputRef, onU
14349
14434
  " ",
14350
14435
  file?.type
14351
14436
  ] })
14352
- ] })
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
+ )
14353
14449
  ] }),
14354
- /* @__PURE__ */ jsx(
14355
- "button",
14450
+ previewUrl && /* @__PURE__ */ jsx(
14451
+ "input",
14356
14452
  {
14357
- type: "submit",
14358
- disabled: !file || disabled,
14359
- title: !file ? "Proporcione una imagen antes de continuar" : "Subir imagen a la galeria",
14360
- className: `limbo-btn w-full mt-2 ${!file ? "cursor-not-allowed limbo-btn-disabled" : "limbo-btn-primary"}`,
14361
- style: { minHeight: 44 },
14362
- children: disabled ? "Subiendo..." : "Subir imagen"
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"
14363
14461
  }
14364
14462
  )
14365
14463
  ]
@@ -14393,7 +14491,21 @@ function useAiServices(prod = false) {
14393
14491
  try {
14394
14492
  const data = await getAiServices(prod);
14395
14493
  if (!isMounted) return;
14396
- const result = data?.data?.images || [];
14494
+ const servicesData = data?.data || data?.result || [];
14495
+ const result = Array.isArray(servicesData) ? servicesData.map((service) => ({
14496
+ label: service.name || service.label,
14497
+ slug: service.slug,
14498
+ type: service.type,
14499
+ description: service.description,
14500
+ image: service.image
14501
+ })) : Object.entries(servicesData).map(([label, slug]) => ({
14502
+ label,
14503
+ slug,
14504
+ type: "image_generation",
14505
+ // Default for backward compatibility
14506
+ description: null,
14507
+ image: null
14508
+ }));
14397
14509
  setServices(result);
14398
14510
  cache$4.set(cacheKey, { data: result, timestamp: now });
14399
14511
  } catch (err) {
@@ -14465,7 +14577,30 @@ function useImageParams(prod = false) {
14465
14577
  };
14466
14578
  return { params, loading, error, fetchParams, reset };
14467
14579
  }
14468
- function TabAI({ prod, disabled, onSelected, onGenerated }) {
14580
+ function LoadingOverlay({
14581
+ show = false,
14582
+ message = "Cargando...",
14583
+ className = ""
14584
+ }) {
14585
+ if (!show) return null;
14586
+ return /* @__PURE__ */ jsx(
14587
+ "div",
14588
+ {
14589
+ className: `absolute inset-0 bg-white/80 backdrop-blur-sm z-50 flex flex-col items-center justify-center ${className}`,
14590
+ "aria-live": "polite",
14591
+ "aria-busy": "true",
14592
+ role: "status",
14593
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
14594
+ /* @__PURE__ */ jsxs("div", { className: "relative w-16 h-16", children: [
14595
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 border-4 border-brand-blue-200 rounded-full" }),
14596
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 border-4 border-brand-blue-600 rounded-full border-t-transparent animate-spin" })
14597
+ ] }),
14598
+ /* @__PURE__ */ jsx("p", { className: "text-brand-blue-900 font-medium text-lg", children: message })
14599
+ ] })
14600
+ }
14601
+ );
14602
+ }
14603
+ function TabAI({ prod, disabled, onGenerated }) {
14469
14604
  const aiServicesHook = useAiServices(prod);
14470
14605
  const imageParamsHook = useImageParams(prod);
14471
14606
  const [selectedService, setSelectedService] = useState("");
@@ -14473,37 +14608,16 @@ function TabAI({ prod, disabled, onSelected, onGenerated }) {
14473
14608
  const [aiLoading, setAiLoading] = useState(false);
14474
14609
  const [aiError, setAiError] = useState(null);
14475
14610
  const [aiImage, setAiImage] = useState(null);
14476
- const serviceLabels = {
14477
- // IA Services
14478
- "dall-e-2": "Dalle2",
14479
- "dall-e-3": "Dalle3",
14480
- "freepik-classic": "Freepik Classic",
14481
- "freepik-mystic": "Freepik Mystic",
14482
- "freepik-google": "Freepik Google",
14483
- "freepik-flux": "Freepik Flux",
14484
- // Stock Services
14485
- "shutterstock": "Shutterstock",
14486
- "freepikstock": "Freepik"
14487
- };
14488
- const getServiceLabel = (service) => {
14489
- return serviceLabels[service] || service;
14490
- };
14611
+ const [showServiceSelection, setShowServiceSelection] = useState(true);
14612
+ const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
14491
14613
  React.useEffect(() => {
14492
14614
  if (!aiServicesHook.loading && aiServicesHook.services.length === 1) {
14493
14615
  const service = aiServicesHook.services[0];
14494
- setSelectedService(service);
14495
- imageParamsHook.fetchParams(service, true);
14616
+ setSelectedService(service.slug);
14617
+ setShowServiceSelection(false);
14618
+ imageParamsHook.fetchParams(service.slug, true);
14496
14619
  }
14497
14620
  }, [aiServicesHook.loading, aiServicesHook.services]);
14498
- const handleServiceChange = async (e) => {
14499
- const service = e.target.value;
14500
- setSelectedService(service);
14501
- setDynamicForm({});
14502
- setAiImage(null);
14503
- if (service) {
14504
- await imageParamsHook.fetchParams(service, true);
14505
- }
14506
- };
14507
14621
  React.useEffect(() => {
14508
14622
  if (!selectedService) return;
14509
14623
  const params = imageParamsHook.params?.[selectedService]?.parameters;
@@ -14539,9 +14653,20 @@ function TabAI({ prod, disabled, onSelected, onGenerated }) {
14539
14653
  if (imageData && onGenerated) {
14540
14654
  if (imageData.startsWith("http")) {
14541
14655
  try {
14542
- const imageResponse = await fetch(imageData);
14656
+ const apiBaseUrl = window.limboCore?.config?.getGlobal()?.prod ? "https://limbo.lefebvre.com" : "http://localhost";
14657
+ const proxyUrl = `${apiBaseUrl}/api/atenea/proxy?url=${encodeURIComponent(
14658
+ imageData
14659
+ )}`;
14660
+ const imageResponse = await fetch(proxyUrl, {
14661
+ credentials: "include",
14662
+ headers: {
14663
+ Authorization: `Bearer ${window.limboCore?.apiClient?.token || ""}`
14664
+ }
14665
+ });
14543
14666
  if (!imageResponse.ok) {
14544
- throw new Error(`Failed to download image: ${imageResponse.status}`);
14667
+ throw new Error(
14668
+ `Failed to download image: ${imageResponse.status}`
14669
+ );
14545
14670
  }
14546
14671
  const blob = await imageResponse.blob();
14547
14672
  const file = new File([blob], "ai-image.png", {
@@ -14549,7 +14674,9 @@ function TabAI({ prod, disabled, onSelected, onGenerated }) {
14549
14674
  });
14550
14675
  onGenerated(file);
14551
14676
  } catch (downloadError) {
14552
- throw new Error(`Error downloading image: ${downloadError.message}`);
14677
+ throw new Error(
14678
+ `Error downloading image via proxy: ${downloadError.message}`
14679
+ );
14553
14680
  }
14554
14681
  } else {
14555
14682
  const cleanBase64 = imageData.replace(/^data:image\/\w+;base64,/, "");
@@ -14577,86 +14704,183 @@ function TabAI({ prod, disabled, onSelected, onGenerated }) {
14577
14704
  const renderDynamicForm = () => {
14578
14705
  const params = imageParamsHook.params?.[selectedService]?.parameters;
14579
14706
  if (!params) return null;
14580
- const visibleFields = Object.entries(params).filter(([_, config]) => !config.disabled);
14581
- const useGridLayout = visibleFields.length > 3;
14582
- return /* @__PURE__ */ jsxs(
14583
- "form",
14584
- {
14585
- onSubmit: handleDynamicFormSubmit,
14586
- "data-type": "ai",
14587
- className: "flex flex-col gap-3 mt-4 border-t-1 pt-4 border-brand-blue-200",
14588
- "aria-label": "Formulario generación IA",
14589
- children: [
14590
- /* @__PURE__ */ jsx("div", { className: useGridLayout ? "grid grid-cols-1 md:grid-cols-2 gap-3" : "flex flex-col gap-3", children: visibleFields.map(([key, config]) => {
14591
- let placeholder = config.placeholder || "";
14592
- if (!placeholder) {
14593
- if (config.type === "integer") {
14594
- placeholder = config.minValue && config.maxValue ? `Entre ${config.minValue} y ${config.maxValue}` : config.minValue ? `Mínimo ${config.minValue}` : config.maxValue ? `Máximo ${config.maxValue}` : "Introduce un número";
14595
- } else if (key.toLowerCase().includes("prompt")) {
14596
- placeholder = "Describe la imagen que deseas generar...";
14597
- } else if (key.toLowerCase().includes("negative")) {
14598
- placeholder = "Elementos a evitar en la imagen...";
14599
- } else {
14600
- placeholder = `Introduce ${(config.label || key).toLowerCase()}`;
14601
- }
14707
+ const allFields = Object.entries(params).filter(([_, config]) => !config.hidden);
14708
+ const promptFields = allFields.filter(
14709
+ ([key]) => key.toLowerCase().includes("prompt") || key.toLowerCase().includes("query")
14710
+ );
14711
+ const advancedFields = allFields.filter(
14712
+ ([key]) => !key.toLowerCase().includes("prompt") && !key.toLowerCase().includes("query")
14713
+ );
14714
+ const renderField = (key, config) => {
14715
+ let placeholder = config.placeholder || "";
14716
+ if (!placeholder) {
14717
+ if (config.type === "integer") {
14718
+ placeholder = config.minValue && config.maxValue ? `Entre ${config.minValue} y ${config.maxValue}` : config.minValue ? `Mínimo ${config.minValue}` : config.maxValue ? `Máximo ${config.maxValue}` : "Introduce un número";
14719
+ } else if (key.toLowerCase().includes("prompt")) {
14720
+ placeholder = "Describe la imagen que deseas generar...";
14721
+ } else if (key.toLowerCase().includes("negative")) {
14722
+ placeholder = "Elementos a evitar en la imagen...";
14723
+ } else {
14724
+ placeholder = `Introduce ${(config.label || key).toLowerCase()}`;
14725
+ }
14726
+ }
14727
+ const isPromptField = key.toLowerCase().includes("prompt") || key.toLowerCase().includes("query");
14728
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
14729
+ /* @__PURE__ */ jsxs(
14730
+ "label",
14731
+ {
14732
+ htmlFor: `ai-${key}`,
14733
+ className: "text-xs font-medium text-brand-blue-900",
14734
+ children: [
14735
+ config.label || key,
14736
+ config.required && /* @__PURE__ */ jsx("span", { className: "text-red-600 ml-1", children: "*" })
14737
+ ]
14738
+ }
14739
+ ),
14740
+ config.options ? /* @__PURE__ */ jsx(
14741
+ "select",
14742
+ {
14743
+ id: `ai-${key}`,
14744
+ name: key,
14745
+ value: dynamicForm[key] ?? "",
14746
+ onChange: handleDynamicFormChange,
14747
+ className: "limbo-input",
14748
+ disabled,
14749
+ title: config.label || key,
14750
+ children: config.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
14751
+ }
14752
+ ) : isPromptField ? /* @__PURE__ */ jsx(
14753
+ "textarea",
14754
+ {
14755
+ id: `ai-${key}`,
14756
+ name: key,
14757
+ value: dynamicForm[key] ?? "",
14758
+ onChange: handleDynamicFormChange,
14759
+ className: "limbo-input resize-none overflow-y-auto",
14760
+ disabled,
14761
+ required: config.required,
14762
+ placeholder,
14763
+ title: config.label || key,
14764
+ rows: 3,
14765
+ style: {
14766
+ maxHeight: "9rem",
14767
+ // 3 rows * 3 = 9rem aprox
14768
+ minHeight: "3rem"
14769
+ },
14770
+ onInput: (e) => {
14771
+ e.target.style.height = "auto";
14772
+ const newHeight = Math.min(e.target.scrollHeight, 144);
14773
+ e.target.style.height = `${newHeight}px`;
14602
14774
  }
14603
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
14604
- /* @__PURE__ */ jsxs(
14605
- "label",
14606
- {
14607
- htmlFor: `ai-${key}`,
14608
- className: "text-xs font-medium text-brand-blue-900",
14609
- children: [
14610
- config.label || key,
14611
- config.required && /* @__PURE__ */ jsx("span", { className: "text-red-600 ml-1", children: "*" })
14612
- ]
14613
- }
14614
- ),
14615
- config.options ? /* @__PURE__ */ jsx(
14616
- "select",
14617
- {
14618
- id: `ai-${key}`,
14619
- name: key,
14620
- value: dynamicForm[key] ?? "",
14621
- onChange: handleDynamicFormChange,
14622
- className: "limbo-input",
14623
- disabled,
14624
- title: config.label || key,
14625
- children: config.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
14626
- }
14627
- ) : /* @__PURE__ */ jsx(
14628
- "input",
14775
+ }
14776
+ ) : /* @__PURE__ */ jsx(
14777
+ "input",
14778
+ {
14779
+ id: `ai-${key}`,
14780
+ type: config.type === "integer" ? "number" : "text",
14781
+ name: key,
14782
+ value: dynamicForm[key] ?? "",
14783
+ onChange: handleDynamicFormChange,
14784
+ className: "limbo-input",
14785
+ disabled,
14786
+ required: config.required,
14787
+ min: config.minValue,
14788
+ max: config.maxValue,
14789
+ placeholder,
14790
+ title: config.label || key
14791
+ }
14792
+ )
14793
+ ] }, key);
14794
+ };
14795
+ return /* @__PURE__ */ jsxs("div", { children: [
14796
+ aiServicesHook.services.length > 1 && /* @__PURE__ */ jsxs(
14797
+ "button",
14798
+ {
14799
+ type: "button",
14800
+ onClick: () => {
14801
+ setShowServiceSelection(true);
14802
+ setSelectedService("");
14803
+ setDynamicForm({});
14804
+ setAiImage(null);
14805
+ setAiError(null);
14806
+ },
14807
+ className: "flex cursor-pointer items-center gap-2 text-brand-blue-600 hover:text-brand-blue-700 mb-4 font-medium",
14808
+ children: [
14809
+ /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left" }),
14810
+ "Volver al menú de servicios"
14811
+ ]
14812
+ }
14813
+ ),
14814
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
14815
+ /* @__PURE__ */ jsxs(
14816
+ "form",
14817
+ {
14818
+ onSubmit: handleDynamicFormSubmit,
14819
+ "data-type": "ai",
14820
+ className: "flex flex-col gap-3 mt-4 border-t-1 pt-4 border-brand-blue-200",
14821
+ "aria-label": "Formulario generación IA",
14822
+ children: [
14823
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: promptFields.map(([key, config]) => renderField(key, config)) }),
14824
+ advancedFields.length > 0 && /* @__PURE__ */ jsxs("div", { className: "border-t border-gray-200 pt-3", children: [
14825
+ /* @__PURE__ */ jsxs(
14826
+ "button",
14827
+ {
14828
+ type: "button",
14829
+ onClick: () => setShowAdvancedOptions(!showAdvancedOptions),
14830
+ className: "flex items-center gap-2 text-sm font-medium text-brand-blue-700 hover:text-brand-blue-900 mb-3 transition-colors",
14831
+ children: [
14832
+ /* @__PURE__ */ jsx(
14833
+ "svg",
14834
+ {
14835
+ className: `w-4 h-4 transition-transform ${showAdvancedOptions ? "rotate-90" : ""}`,
14836
+ fill: "none",
14837
+ stroke: "currentColor",
14838
+ viewBox: "0 0 24 24",
14839
+ children: /* @__PURE__ */ jsx(
14840
+ "path",
14841
+ {
14842
+ strokeLinecap: "round",
14843
+ strokeLinejoin: "round",
14844
+ strokeWidth: 2,
14845
+ d: "M9 5l7 7-7 7"
14846
+ }
14847
+ )
14848
+ }
14849
+ ),
14850
+ "Opciones avanzadas",
14851
+ !showAdvancedOptions && /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500", children: [
14852
+ "(",
14853
+ advancedFields.length,
14854
+ " opciones)"
14855
+ ] })
14856
+ ]
14857
+ }
14858
+ ),
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)) })
14860
+ ] }),
14861
+ /* @__PURE__ */ jsx(
14862
+ "button",
14629
14863
  {
14630
- id: `ai-${key}`,
14631
- type: config.type === "integer" ? "number" : "text",
14632
- name: key,
14633
- value: dynamicForm[key] ?? "",
14634
- onChange: handleDynamicFormChange,
14635
- className: "limbo-input",
14636
- disabled,
14637
- required: config.required,
14638
- min: config.minValue,
14639
- max: config.maxValue,
14640
- placeholder,
14641
- title: config.label || key
14864
+ type: "submit",
14865
+ disabled: aiLoading || disabled,
14866
+ title: aiLoading ? "Generando imagen..." : "Generar imagen",
14867
+ className: `limbo-btn w-full mt-2 ${aiLoading ? "cursor-not-allowed limbo-btn-disabled" : "limbo-btn-primary"}`,
14868
+ style: { minHeight: 44 },
14869
+ children: aiLoading ? "Generando..." : "Generar imagen"
14642
14870
  }
14643
14871
  )
14644
- ] }, key);
14645
- }) }),
14646
- /* @__PURE__ */ jsx(
14647
- "button",
14648
- {
14649
- type: "submit",
14650
- disabled: aiLoading || disabled,
14651
- title: aiLoading ? "Generando imagen..." : "Generar imagen",
14652
- className: `limbo-btn w-full mt-2 ${aiLoading ? "cursor-not-allowed limbo-btn-disabled" : "limbo-btn-primary"}`,
14653
- style: { minHeight: 44 },
14654
- children: aiLoading ? "Generando..." : "Generar imagen"
14655
- }
14656
- )
14657
- ]
14658
- }
14659
- );
14872
+ ]
14873
+ }
14874
+ ),
14875
+ /* @__PURE__ */ jsx(
14876
+ LoadingOverlay,
14877
+ {
14878
+ show: aiLoading,
14879
+ message: "Generando imagen con IA..."
14880
+ }
14881
+ )
14882
+ ] })
14883
+ ] });
14660
14884
  };
14661
14885
  if (aiServicesHook.loading) {
14662
14886
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center py-8", children: [
@@ -14667,48 +14891,78 @@ function TabAI({ prod, disabled, onSelected, onGenerated }) {
14667
14891
  if (!aiServicesHook.services.length) {
14668
14892
  return /* @__PURE__ */ jsx("div", { className: "alert alert-warning", children: "No hay servicios IA disponibles." });
14669
14893
  }
14670
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
14894
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 mx-4 pb-2", children: showServiceSelection ? /* @__PURE__ */ jsxs(Fragment, { children: [
14671
14895
  /* @__PURE__ */ jsx(
14672
14896
  "h3",
14673
14897
  {
14674
14898
  id: "aiSelectDescription",
14675
- className: "text-lg font-semibold text-brand-blue-1000",
14676
- children: aiServicesHook.services.length === 1 ? `Generación con ${getServiceLabel(selectedService)}` : "Selecciona el modelo IA"
14899
+ className: "text-lg font-semibold text-brand-blue-1000 mb-3",
14900
+ children: "Selecciona el modelo IA"
14677
14901
  }
14678
14902
  ),
14679
- aiServicesHook.services.length > 1 && /* @__PURE__ */ jsxs(
14680
- "select",
14903
+ aiServicesHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: aiServicesHook.error }),
14904
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap justify-around items-stretch justify-items-stretch content-stretch gap-3 w-full max-w-7xl", children: aiServicesHook.services.map((service) => /* @__PURE__ */ jsxs(
14905
+ "button",
14681
14906
  {
14682
- name: "aiService",
14683
- "aria-describedby": "aiSelectDescription",
14684
- title: "Selecciona el servicio IA",
14685
- value: selectedService,
14686
- onChange: handleServiceChange,
14907
+ type: "button",
14908
+ onClick: () => {
14909
+ setSelectedService(service.slug);
14910
+ setShowServiceSelection(false);
14911
+ setDynamicForm({});
14912
+ setAiImage(null);
14913
+ setAiError(null);
14914
+ imageParamsHook.fetchParams(service.slug, true);
14915
+ },
14687
14916
  disabled: aiServicesHook.loading || disabled,
14688
- className: "limbo-input",
14917
+ className: `
14918
+ group relative p-4 rounded-lg border-2 transition-all duration-200 overflow-hidden
14919
+ flex flex-col items-center justify-center min-h-[120px] grow-1 basis-sm min-w-70
14920
+ border-gray-200 hover:border-brand-blue-300 hover:shadow-md
14921
+ ${aiServicesHook.loading || disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}
14922
+ `,
14923
+ style: {
14924
+ position: "relative"
14925
+ },
14689
14926
  children: [
14690
- /* @__PURE__ */ jsx("option", { value: "", children: "-- Selecciona --" }),
14691
- aiServicesHook.services.map((service) => /* @__PURE__ */ jsx("option", { value: service, children: getServiceLabel(service) }, service))
14927
+ service.image && /* @__PURE__ */ jsx(
14928
+ "div",
14929
+ {
14930
+ className: "absolute inset-0 bg-cover bg-center transition-opacity duration-200 group-hover:opacity-20",
14931
+ style: {
14932
+ backgroundImage: `url(${service.image})`,
14933
+ opacity: 0.1
14934
+ }
14935
+ }
14936
+ ),
14937
+ /* @__PURE__ */ jsxs("div", { className: "relative z-10 flex flex-col items-center justify-center gap-2 text-center", children: [
14938
+ /* @__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 })
14940
+ ] })
14692
14941
  ]
14693
- }
14694
- ),
14695
- aiServicesHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: aiServicesHook.error }),
14696
- imageParamsHook.loading && /* @__PURE__ */ jsx("div", { children: "Cargando parámetros..." }),
14697
- imageParamsHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: imageParamsHook.error }),
14698
- selectedService && renderDynamicForm(),
14699
- aiError && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: aiError }),
14700
- aiImage && aiImage.map((img, index) => /* @__PURE__ */ jsxs("div", { className: "mt-6 text-center", children: [
14701
- /* @__PURE__ */ jsx(
14702
- "img",
14703
- {
14704
- src: img.contains("http") ? img : `data:image/png;base64, ${img}`,
14705
- alt: "Imagen generada con IA",
14706
- className: "rounded-lg shadow-md border border-brand-blue-200 max-h-72 mx-auto"
14707
- }
14708
- ),
14709
- /* @__PURE__ */ jsx("button", { className: "limbo-btn limbo-btn-primary mt-4", children: "Aceptar y subir" })
14710
- ] }, "img-" + index))
14711
- ] });
14942
+ },
14943
+ service.slug
14944
+ )) })
14945
+ ] }) : (
14946
+ /* Vista del formulario del servicio seleccionado */
14947
+ /* @__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" }),
14949
+ imageParamsHook.loading && /* @__PURE__ */ jsx("div", { children: "Cargando parámetros..." }),
14950
+ imageParamsHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: imageParamsHook.error }),
14951
+ 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))
14964
+ ] })
14965
+ ) });
14712
14966
  }
14713
14967
  const BASE_PATH$1 = "/api";
14714
14968
  function getStockServices(prod = false) {
@@ -14740,7 +14994,27 @@ function useStockServices(prod = false) {
14740
14994
  try {
14741
14995
  const data = await getStockServices(prod);
14742
14996
  if (!isMounted) return;
14743
- const result = data?.data?.images || [];
14997
+ if (data?.httpCode === 204 || data?.data === null) {
14998
+ setServices([]);
14999
+ setError("No hay servicios de stock disponibles en este momento");
15000
+ cache$2.set(cacheKey, { data: [], timestamp: now });
15001
+ return;
15002
+ }
15003
+ const servicesData = data?.data || data?.result || [];
15004
+ const result = Array.isArray(servicesData) ? servicesData.map((service) => ({
15005
+ label: service.name || service.label,
15006
+ slug: service.slug,
15007
+ type: service.type,
15008
+ description: service.description,
15009
+ image: service.image
15010
+ })) : Object.entries(servicesData).map(([label, slug]) => ({
15011
+ label,
15012
+ slug,
15013
+ type: "stock_image",
15014
+ // Default for backward compatibility
15015
+ description: null,
15016
+ image: null
15017
+ }));
14744
15018
  setServices(result);
14745
15019
  cache$2.set(cacheKey, { data: result, timestamp: now });
14746
15020
  } catch (err) {
@@ -14789,6 +15063,8 @@ function TabStock({ prod, disabled, onSelected }) {
14789
15063
  const [stockLoading, setStockLoading] = useState(false);
14790
15064
  const [stockError, setStockError] = useState(null);
14791
15065
  const [downloadingId, setDownloadingId] = useState(null);
15066
+ const [showServiceSelection, setShowServiceSelection] = useState(true);
15067
+ const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
14792
15068
  React.useEffect(() => {
14793
15069
  sessionStorage.setItem(
14794
15070
  "limbo_stock_selectedService",
@@ -14819,30 +15095,14 @@ function TabStock({ prod, disabled, onSelected }) {
14819
15095
  JSON.stringify(paginationInfo)
14820
15096
  );
14821
15097
  }, [paginationInfo]);
14822
- const serviceLabels = {
14823
- shutterstock: "Shutterstock",
14824
- freepikstock: "Freepik"
14825
- };
14826
- const getServiceLabel = (service) => {
14827
- return serviceLabels[service] || service;
14828
- };
14829
15098
  React.useEffect(() => {
14830
15099
  if (!stockServicesHook.loading && stockServicesHook.services.length === 1) {
14831
15100
  const service = stockServicesHook.services[0];
14832
- setSelectedService(service);
14833
- imageParamsHook.fetchParams(service, true);
15101
+ setSelectedService(service.slug);
15102
+ setShowServiceSelection(false);
15103
+ imageParamsHook.fetchParams(service.slug, true);
14834
15104
  }
14835
15105
  }, [stockServicesHook.loading, stockServicesHook.services]);
14836
- const handleServiceChange = async (e) => {
14837
- const service = e.target.value;
14838
- setSelectedService(service);
14839
- setDynamicForm({});
14840
- setStockImages([]);
14841
- setCurrentPage(1);
14842
- if (service) {
14843
- await imageParamsHook.fetchParams(service, true);
14844
- }
14845
- };
14846
15106
  React.useEffect(() => {
14847
15107
  if (!selectedService) return;
14848
15108
  const params = imageParamsHook.params?.[selectedService]?.parameters;
@@ -14912,7 +15172,7 @@ function TabStock({ prod, disabled, onSelected }) {
14912
15172
  id: img.id || idx,
14913
15173
  title: img.title || img.filename || `Imagen ${idx + 1}`,
14914
15174
  source: img.service || selectedService,
14915
- sourceTitle: serviceLabels?.[selectedService] || selectedService
15175
+ sourceTitle: stockServicesHook.services.find((s) => s.slug === selectedService)?.label || selectedService
14916
15176
  });
14917
15177
  });
14918
15178
  return allImages;
@@ -14962,10 +15222,15 @@ function TabStock({ prod, disabled, onSelected }) {
14962
15222
  const renderDynamicForm = () => {
14963
15223
  const params = imageParamsHook.params?.[selectedService]?.parameters;
14964
15224
  if (!params) return null;
14965
- const visibleFields = Object.entries(params).filter(
14966
- ([, config]) => !config.disabled
15225
+ const allFields = Object.entries(params).filter(
15226
+ ([, config]) => !config.hidden
15227
+ );
15228
+ const searchFields = allFields.filter(
15229
+ ([key]) => key.toLowerCase().includes("query") || key.toLowerCase().includes("search") || key.toLowerCase().includes("term")
15230
+ );
15231
+ const advancedFields = allFields.filter(
15232
+ ([key]) => !key.toLowerCase().includes("query") && !key.toLowerCase().includes("search") && !key.toLowerCase().includes("term")
14967
15233
  );
14968
- const useGridLayout = visibleFields.length > 3;
14969
15234
  const handleMultiCheckboxChange = (key, value) => {
14970
15235
  setDynamicForm((prev) => {
14971
15236
  const arr = Array.isArray(prev[key]) ? prev[key] : [];
@@ -14976,103 +15241,192 @@ function TabStock({ prod, disabled, onSelected }) {
14976
15241
  }
14977
15242
  });
14978
15243
  };
14979
- return /* @__PURE__ */ jsxs(
14980
- "form",
14981
- {
14982
- onSubmit: handleDynamicFormSubmit,
14983
- "data-type": "stock",
14984
- className: "flex flex-col gap-3 mt-4 border-t-1 pt-4 border-brand-blue-200",
14985
- "aria-label": "Formulario búsqueda de imágenes de Stock",
14986
- children: [
15244
+ const renderField = (key, config) => {
15245
+ let placeholder = config.placeholder || "";
15246
+ if (!placeholder) {
15247
+ if (config.type === "integer") {
15248
+ placeholder = config.minValue && config.maxValue ? `Entre ${config.minValue} y ${config.maxValue}` : config.minValue ? `Mínimo ${config.minValue}` : config.maxValue ? `Máximo ${config.maxValue}` : "Introduce un número";
15249
+ } else if (key.toLowerCase().includes("query") || key.toLowerCase().includes("search") || key.toLowerCase().includes("term")) {
15250
+ placeholder = "Buscar imágenes...";
15251
+ } else {
15252
+ placeholder = `Introduce ${(config.label || key).toLowerCase()}`;
15253
+ }
15254
+ }
15255
+ const isSearchField = key.toLowerCase().includes("query") || key.toLowerCase().includes("search") || key.toLowerCase().includes("term");
15256
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
15257
+ /* @__PURE__ */ jsxs(
15258
+ "label",
15259
+ {
15260
+ htmlFor: `stock-${key}`,
15261
+ className: "text-xs font-medium text-brand-blue-900",
15262
+ children: [
15263
+ config.label || key,
15264
+ config.required && /* @__PURE__ */ jsx("span", { className: "text-red-600 ml-1", children: "*" })
15265
+ ]
15266
+ }
15267
+ ),
15268
+ config.options && config.multiple ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 min-h-[50px] px-2 justify-between border-2 border-gray-transparent-500 rounded-lg", children: config.options.map((opt) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1", children: [
14987
15269
  /* @__PURE__ */ jsx(
14988
- "div",
15270
+ "input",
14989
15271
  {
14990
- className: useGridLayout ? "grid grid-cols-1 md:grid-cols-2 gap-3" : "flex flex-col gap-3",
14991
- children: visibleFields.map(([key, config]) => {
14992
- let placeholder = config.placeholder || "";
14993
- if (!placeholder) {
14994
- if (config.type === "integer") {
14995
- placeholder = config.minValue && config.maxValue ? `Entre ${config.minValue} y ${config.maxValue}` : config.minValue ? `Mínimo ${config.minValue}` : config.maxValue ? `Máximo ${config.maxValue}` : "Introduce un número";
14996
- } else if (key.toLowerCase().includes("query") || key.toLowerCase().includes("search") || key.toLowerCase().includes("term")) {
14997
- placeholder = "Buscar imágenes...";
14998
- } else {
14999
- placeholder = `Introduce ${(config.label || key).toLowerCase()}`;
15000
- }
15001
- }
15002
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
15003
- /* @__PURE__ */ jsxs(
15004
- "label",
15005
- {
15006
- htmlFor: `stock-${key}`,
15007
- className: "text-xs font-medium text-brand-blue-900",
15008
- children: [
15009
- config.label || key,
15010
- config.required && /* @__PURE__ */ jsx("span", { className: "text-red-600 ml-1", children: "*" })
15011
- ]
15012
- }
15013
- ),
15014
- config.options && config.multiple ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 min-h-[50px] px-2 justify-between border-2 border-gray-transparent-500 rounded-lg", children: config.options.map((opt) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1", children: [
15015
- /* @__PURE__ */ jsx(
15016
- "input",
15017
- {
15018
- type: "checkbox",
15019
- name: `stock-${key}`,
15020
- value: opt,
15021
- checked: Array.isArray(dynamicForm[key]) && dynamicForm[key].includes(opt),
15022
- onChange: () => handleMultiCheckboxChange(key, opt),
15023
- disabled
15024
- }
15025
- ),
15026
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: opt })
15027
- ] }, opt)) }) : config.options ? /* @__PURE__ */ jsx(
15028
- "select",
15029
- {
15030
- id: `stock-${key}`,
15031
- name: key,
15032
- value: dynamicForm[key] ?? "",
15033
- onChange: handleDynamicFormChange,
15034
- className: "limbo-input",
15035
- disabled,
15036
- title: config.label || key,
15037
- children: config.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
15038
- }
15039
- ) : /* @__PURE__ */ jsx(
15040
- "input",
15041
- {
15042
- id: `stock-${key}`,
15043
- type: config.type === "integer" ? "number" : "text",
15044
- name: key,
15045
- value: dynamicForm[key] ?? "",
15046
- onChange: handleDynamicFormChange,
15047
- className: "limbo-input",
15048
- disabled,
15049
- required: config.required,
15050
- min: config.minValue,
15051
- max: config.maxValue,
15052
- placeholder,
15053
- title: config.label || key
15054
- }
15055
- )
15056
- ] }, key);
15057
- })
15272
+ type: "checkbox",
15273
+ name: `stock-${key}`,
15274
+ value: opt,
15275
+ checked: Array.isArray(dynamicForm[key]) && dynamicForm[key].includes(opt),
15276
+ onChange: () => handleMultiCheckboxChange(key, opt),
15277
+ disabled
15058
15278
  }
15059
15279
  ),
15060
- /* @__PURE__ */ jsx(
15061
- "button",
15062
- {
15063
- type: "submit",
15064
- disabled: stockLoading || disabled,
15065
- className: `limbo-btn w-full mt-2 ${stockLoading ? "cursor-not-allowed limbo-btn-disabled" : "limbo-btn-primary"}`,
15066
- style: { minHeight: 44 },
15067
- children: stockLoading ? "Buscando..." : "Buscar imágenes"
15280
+ /* @__PURE__ */ jsx("span", { className: "text-xs", children: opt })
15281
+ ] }, opt)) }) : config.options ? /* @__PURE__ */ jsx(
15282
+ "select",
15283
+ {
15284
+ id: `stock-${key}`,
15285
+ name: key,
15286
+ value: dynamicForm[key] ?? "",
15287
+ onChange: handleDynamicFormChange,
15288
+ className: "limbo-input",
15289
+ disabled,
15290
+ title: config.label || key,
15291
+ children: config.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
15292
+ }
15293
+ ) : isSearchField ? /* @__PURE__ */ jsx(
15294
+ "textarea",
15295
+ {
15296
+ id: `stock-${key}`,
15297
+ name: key,
15298
+ value: dynamicForm[key] ?? "",
15299
+ onChange: handleDynamicFormChange,
15300
+ className: "limbo-input resize-none overflow-y-auto",
15301
+ disabled,
15302
+ required: config.required,
15303
+ placeholder,
15304
+ title: config.label || key,
15305
+ rows: 3,
15306
+ style: {
15307
+ maxHeight: "9rem",
15308
+ minHeight: "3rem"
15309
+ },
15310
+ onInput: (e) => {
15311
+ e.target.style.height = "auto";
15312
+ const newHeight = Math.min(e.target.scrollHeight, 144);
15313
+ e.target.style.height = `${newHeight}px`;
15068
15314
  }
15069
- )
15070
- ]
15071
- }
15072
- );
15073
- };
15074
- if (stockServicesHook.loading) {
15075
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center py-8", children: [
15315
+ }
15316
+ ) : /* @__PURE__ */ jsx(
15317
+ "input",
15318
+ {
15319
+ id: `stock-${key}`,
15320
+ type: config.type === "integer" ? "number" : "text",
15321
+ name: key,
15322
+ value: dynamicForm[key] ?? "",
15323
+ onChange: handleDynamicFormChange,
15324
+ className: "limbo-input",
15325
+ disabled,
15326
+ required: config.required,
15327
+ min: config.minValue,
15328
+ max: config.maxValue,
15329
+ placeholder,
15330
+ title: config.label || key
15331
+ }
15332
+ )
15333
+ ] }, key);
15334
+ };
15335
+ return /* @__PURE__ */ jsxs("div", { children: [
15336
+ stockServicesHook.services.length > 1 && /* @__PURE__ */ jsxs(
15337
+ "button",
15338
+ {
15339
+ type: "button",
15340
+ onClick: () => {
15341
+ setShowServiceSelection(true);
15342
+ setSelectedService("");
15343
+ setDynamicForm({});
15344
+ setStockImages([]);
15345
+ setCurrentPage(1);
15346
+ setPaginationInfo(null);
15347
+ setStockError(null);
15348
+ },
15349
+ className: "flex cursor-pointer items-center gap-2 text-brand-blue-600 hover:text-brand-blue-700 mb-4 font-medium",
15350
+ children: [
15351
+ /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left" }),
15352
+ "Volver al menú de servicios"
15353
+ ]
15354
+ }
15355
+ ),
15356
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
15357
+ /* @__PURE__ */ jsxs(
15358
+ "form",
15359
+ {
15360
+ onSubmit: handleDynamicFormSubmit,
15361
+ "data-type": "stock",
15362
+ className: "flex flex-col gap-3 mt-4 border-t-1 pt-4 border-brand-blue-200",
15363
+ "aria-label": "Formulario búsqueda de imágenes de Stock",
15364
+ children: [
15365
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: searchFields.map(([key, config]) => renderField(key, config)) }),
15366
+ advancedFields.length > 0 && /* @__PURE__ */ jsxs("div", { className: "border-t border-gray-200 pt-3", children: [
15367
+ /* @__PURE__ */ jsxs(
15368
+ "button",
15369
+ {
15370
+ type: "button",
15371
+ onClick: () => setShowAdvancedOptions(!showAdvancedOptions),
15372
+ className: "flex items-center gap-2 text-sm font-medium text-brand-blue-700 hover:text-brand-blue-900 mb-3 transition-colors",
15373
+ children: [
15374
+ /* @__PURE__ */ jsx(
15375
+ "svg",
15376
+ {
15377
+ className: `w-4 h-4 transition-transform ${showAdvancedOptions ? "rotate-90" : ""}`,
15378
+ fill: "none",
15379
+ stroke: "currentColor",
15380
+ viewBox: "0 0 24 24",
15381
+ children: /* @__PURE__ */ jsx(
15382
+ "path",
15383
+ {
15384
+ strokeLinecap: "round",
15385
+ strokeLinejoin: "round",
15386
+ strokeWidth: 2,
15387
+ d: "M9 5l7 7-7 7"
15388
+ }
15389
+ )
15390
+ }
15391
+ ),
15392
+ "Opciones avanzadas",
15393
+ !showAdvancedOptions && /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500", children: [
15394
+ "(",
15395
+ advancedFields.length,
15396
+ " opciones)"
15397
+ ] })
15398
+ ]
15399
+ }
15400
+ ),
15401
+ showAdvancedOptions && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3 animate-fadeIn", children: advancedFields.map(
15402
+ ([key, config]) => renderField(key, config)
15403
+ ) })
15404
+ ] }),
15405
+ /* @__PURE__ */ jsx(
15406
+ "button",
15407
+ {
15408
+ type: "submit",
15409
+ disabled: stockLoading || disabled,
15410
+ className: `limbo-btn w-full mt-2 ${stockLoading ? "cursor-not-allowed limbo-btn-disabled" : "limbo-btn-primary"}`,
15411
+ style: { minHeight: 44 },
15412
+ children: stockLoading ? "Buscando..." : "Buscar imágenes"
15413
+ }
15414
+ )
15415
+ ]
15416
+ }
15417
+ ),
15418
+ /* @__PURE__ */ jsx(
15419
+ LoadingOverlay,
15420
+ {
15421
+ show: stockLoading && !stockImages.length,
15422
+ message: "Buscando imágenes..."
15423
+ }
15424
+ )
15425
+ ] })
15426
+ ] });
15427
+ };
15428
+ if (stockServicesHook.loading) {
15429
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center py-8", children: [
15076
15430
  /* @__PURE__ */ jsx("span", { className: "limbo-loader mr-2" }),
15077
15431
  " Cargando servicios de Stock..."
15078
15432
  ] });
@@ -15080,43 +15434,81 @@ function TabStock({ prod, disabled, onSelected }) {
15080
15434
  if (!stockServicesHook.services.length) {
15081
15435
  return /* @__PURE__ */ jsx("div", { className: "alert alert-warning", children: "No hay servicios de Stock disponibles." });
15082
15436
  }
15083
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
15084
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-brand-blue-1000", children: stockServicesHook.services.length === 1 ? `Búsqueda en ${getServiceLabel(selectedService)}` : "Selecciona el servicio de Stock" }),
15085
- stockServicesHook.services.length > 1 && /* @__PURE__ */ jsxs(
15086
- "select",
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" }),
15439
+ stockServicesHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: stockServicesHook.error }),
15440
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-3", children: stockServicesHook.services.map((service) => /* @__PURE__ */ jsxs(
15441
+ "button",
15087
15442
  {
15088
- value: selectedService,
15089
- onChange: handleServiceChange,
15443
+ type: "button",
15444
+ onClick: () => {
15445
+ setSelectedService(service.slug);
15446
+ setShowServiceSelection(false);
15447
+ setDynamicForm({});
15448
+ setStockImages([]);
15449
+ setCurrentPage(1);
15450
+ setPaginationInfo(null);
15451
+ setStockError(null);
15452
+ imageParamsHook.fetchParams(service.slug, true);
15453
+ },
15090
15454
  disabled: stockServicesHook.loading || disabled,
15091
- className: "limbo-input",
15455
+ className: `
15456
+ group relative p-4 rounded-lg border-2 transition-all duration-200 overflow-hidden
15457
+ flex flex-col items-center justify-center min-h-[120px]
15458
+ border-gray-200 hover:border-brand-blue-300 hover:shadow-md
15459
+ ${stockServicesHook.loading || disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}
15460
+ `,
15461
+ style: {
15462
+ position: "relative"
15463
+ },
15092
15464
  children: [
15093
- /* @__PURE__ */ jsx("option", { value: "", children: "-- Selecciona --" }),
15094
- stockServicesHook.services.map((service) => /* @__PURE__ */ jsx("option", { value: service, children: getServiceLabel(service) }, service))
15465
+ service.image && /* @__PURE__ */ jsx(
15466
+ "div",
15467
+ {
15468
+ className: "absolute inset-0 bg-cover bg-center transition-opacity duration-200 group-hover:opacity-20",
15469
+ style: {
15470
+ backgroundImage: `url(${service.image})`,
15471
+ opacity: 0.1
15472
+ }
15473
+ }
15474
+ ),
15475
+ /* @__PURE__ */ jsxs("div", { className: "relative z-10 flex flex-col items-center justify-center gap-2 text-center", children: [
15476
+ /* @__PURE__ */ jsx("span", { className: "text-base font-semibold text-gray-800", children: service.label }),
15477
+ service.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-600 line-clamp-2", children: service.description })
15478
+ ] })
15095
15479
  ]
15096
- }
15097
- ),
15098
- stockServicesHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: stockServicesHook.error }),
15099
- imageParamsHook.loading && /* @__PURE__ */ jsx("div", { children: "Cargando parámetros..." }),
15100
- imageParamsHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: imageParamsHook.error }),
15101
- selectedService && renderDynamicForm(),
15102
- stockError && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: stockError }),
15103
- stockImages.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
15104
- /* @__PURE__ */ jsxs(
15105
- "div",
15106
- {
15107
- className: "mt-6 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 relative",
15108
- "aria-live": "polite",
15109
- children: [
15110
- stockLoading && /* @__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: [
15111
- /* @__PURE__ */ jsx("span", { className: "limbo-loader" }),
15112
- /* @__PURE__ */ jsx("span", { className: "text-sm text-brand-blue-800", children: "Buscando imágenes..." })
15113
- ] }) }),
15114
- stockImages.map((img, idx) => /* @__PURE__ */ jsxs(
15115
- "div",
15116
- {
15117
- className: "border border-brand-blue-200 rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-md transition-shadow",
15118
- children: [
15119
- /* @__PURE__ */ jsxs("div", { className: "relative aspect-video bg-neutral-100", children: [
15480
+ },
15481
+ service.slug
15482
+ )) })
15483
+ ] }) : (
15484
+ /* Vista del formulario del servicio seleccionado */
15485
+ /* @__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" }),
15487
+ imageParamsHook.loading && /* @__PURE__ */ jsx("div", { children: "Cargando parámetros..." }),
15488
+ imageParamsHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: imageParamsHook.error }),
15489
+ selectedService && renderDynamicForm(),
15490
+ stockError && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: stockError }),
15491
+ stockImages.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
15492
+ /* @__PURE__ */ jsxs(
15493
+ "div",
15494
+ {
15495
+ className: "mt-6 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-1 relative",
15496
+ "aria-live": "polite",
15497
+ children: [
15498
+ /* @__PURE__ */ jsx(
15499
+ LoadingOverlay,
15500
+ {
15501
+ show: stockLoading,
15502
+ message: "Cargando imágenes..."
15503
+ }
15504
+ ),
15505
+ stockImages.map((img, idx) => /* @__PURE__ */ jsx(
15506
+ "div",
15507
+ {
15508
+ className: "group border border-brand-blue-200 rounded-sm overflow-hidden bg-white shadow-sm hover:shadow-md transition-all duration-200 cursor-pointer",
15509
+ title: "Seleccionar imagen",
15510
+ onClick: () => !downloadingId && !stockLoading && !disabled && handleImageSelect(img),
15511
+ children: /* @__PURE__ */ jsxs("div", { className: "relative aspect-video bg-neutral-100", children: [
15120
15512
  /* @__PURE__ */ jsx(
15121
15513
  "img",
15122
15514
  {
@@ -15126,84 +15518,79 @@ function TabStock({ prod, disabled, onSelected }) {
15126
15518
  loading: "lazy"
15127
15519
  }
15128
15520
  ),
15129
- img.id && /* @__PURE__ */ jsxs("span", { className: "absolute bottom-1 right-1 bg-black/60 text-white text-xs px-2 py-1 rounded", children: [
15130
- "ID: ",
15131
- img.id
15132
- ] })
15133
- ] }),
15134
- /* @__PURE__ */ jsx("div", { className: "p-2", children: /* @__PURE__ */ jsx(
15135
- "button",
15136
- {
15137
- className: `limbo-btn w-full text-sm ${downloadingId === img.id ? "limbo-btn-disabled cursor-not-allowed" : "limbo-btn-primary"}`,
15138
- onClick: () => handleImageSelect(img),
15139
- disabled: stockLoading || disabled || downloadingId === img.id,
15140
- children: downloadingId === img.id ? /* @__PURE__ */ jsxs(Fragment, { children: [
15141
- /* @__PURE__ */ jsx("span", { className: "limbo-loader limbo-loader--sm mr-1" }),
15142
- "Descargando..."
15143
- ] }) : "Seleccionar"
15144
- }
15145
- ) })
15146
- ]
15147
- },
15148
- img.id || idx
15149
- ))
15150
- ]
15151
- }
15152
- ),
15153
- paginationInfo && /* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-center gap-2", children: [
15154
- /* @__PURE__ */ jsxs(
15155
- "button",
15156
- {
15157
- onClick: () => handlePageChange(paginationInfo.current_page - 1),
15158
- disabled: paginationInfo.current_page <= 1 || stockLoading,
15159
- className: "limbo-btn limbo-btn-secondary px-4 py-2 disabled:opacity-25" + (paginationInfo.current_page <= 1 || stockLoading ? " pointer-events-none cursor-default" : " limbo-btn-primary"),
15160
- children: [
15161
- /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-white icon--sm" }),
15162
- " ",
15163
- "Anterior"
15521
+ downloadingId === img.id ? /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 bg-white/90 flex flex-col items-center justify-center gap-2", children: [
15522
+ /* @__PURE__ */ jsxs("div", { className: "relative w-10 h-10", children: [
15523
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 border-4 border-brand-blue-200 rounded-full" }),
15524
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 border-4 border-brand-blue-600 rounded-full border-t-transparent animate-spin" })
15525
+ ] }),
15526
+ /* @__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" }) })
15528
+ ] })
15529
+ },
15530
+ img.id || idx
15531
+ ))
15164
15532
  ]
15165
15533
  }
15166
15534
  ),
15167
- /* @__PURE__ */ jsxs("span", { className: "px-4 py-2 text-sm text-neutral-700", children: [
15168
- "Página ",
15169
- paginationInfo.current_page,
15170
- " de",
15171
- " ",
15172
- Math.max(
15173
- 1,
15174
- Math.ceil(paginationInfo.total / paginationInfo.per_page)
15535
+ paginationInfo && /* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-center gap-2", children: [
15536
+ /* @__PURE__ */ jsxs(
15537
+ "button",
15538
+ {
15539
+ onClick: () => handlePageChange(paginationInfo.current_page - 1),
15540
+ disabled: paginationInfo.current_page <= 1 || stockLoading,
15541
+ className: "limbo-btn limbo-btn-secondary px-4 py-2 disabled:opacity-25" + (paginationInfo.current_page <= 1 || stockLoading ? " pointer-events-none cursor-default" : " limbo-btn-primary"),
15542
+ children: [
15543
+ /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-white icon--sm" }),
15544
+ " ",
15545
+ "Anterior"
15546
+ ]
15547
+ }
15175
15548
  ),
15176
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-neutral-500 block", children: [
15177
- "(",
15178
- paginationInfo.total,
15179
- " imágenes totales)"
15180
- ] })
15549
+ /* @__PURE__ */ jsxs("span", { className: "px-4 py-2 text-sm text-neutral-700", children: [
15550
+ "Página ",
15551
+ paginationInfo.current_page,
15552
+ " de",
15553
+ " ",
15554
+ Math.max(
15555
+ 1,
15556
+ Math.ceil(paginationInfo.total / paginationInfo.per_page)
15557
+ ),
15558
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-neutral-500 block", children: [
15559
+ "(",
15560
+ paginationInfo.total,
15561
+ " imágenes totales)"
15562
+ ] })
15563
+ ] }),
15564
+ /* @__PURE__ */ jsxs(
15565
+ "button",
15566
+ {
15567
+ onClick: () => handlePageChange(paginationInfo.current_page + 1),
15568
+ disabled: stockLoading || paginationInfo.current_page >= Math.ceil(
15569
+ paginationInfo.total / paginationInfo.per_page
15570
+ ),
15571
+ className: "limbo-btn limbo-btn-secondary px-4 py-2 disabled:opacity-25" + (stockLoading || paginationInfo.current_page >= Math.ceil(
15572
+ paginationInfo.total / paginationInfo.per_page
15573
+ ) ? " pointer-events-none cursor-default" : " limbo-btn-primary"),
15574
+ children: [
15575
+ "Siguiente",
15576
+ " ",
15577
+ /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-white icon--sm" })
15578
+ ]
15579
+ }
15580
+ )
15581
+ ] })
15582
+ ] }),
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: [
15584
+ /* @__PURE__ */ jsx("span", { className: "icon icon-search icon--lg mb-2" }),
15585
+ /* @__PURE__ */ jsxs("p", { children: [
15586
+ 'No se encontraron imágenes para "',
15587
+ dynamicForm.query,
15588
+ '"'
15181
15589
  ] }),
15182
- /* @__PURE__ */ jsxs(
15183
- "button",
15184
- {
15185
- onClick: () => handlePageChange(paginationInfo.current_page + 1),
15186
- disabled: stockLoading || paginationInfo.current_page >= Math.ceil(paginationInfo.total / paginationInfo.per_page),
15187
- className: "limbo-btn limbo-btn-secondary px-4 py-2 disabled:opacity-25" + (stockLoading || paginationInfo.current_page >= Math.ceil(paginationInfo.total / paginationInfo.per_page) ? " pointer-events-none cursor-default" : " limbo-btn-primary"),
15188
- children: [
15189
- "Siguiente",
15190
- " ",
15191
- /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-white icon--sm" })
15192
- ]
15193
- }
15194
- )
15590
+ /* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: "Intenta con otros términos de búsqueda" })
15195
15591
  ] })
15196
- ] }),
15197
- !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: [
15198
- /* @__PURE__ */ jsx("span", { className: "icon icon-search icon--lg mb-2" }),
15199
- /* @__PURE__ */ jsxs("p", { children: [
15200
- 'No se encontraron imágenes para "',
15201
- dynamicForm.query,
15202
- '"'
15203
- ] }),
15204
- /* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: "Intenta con otros términos de búsqueda" })
15205
15592
  ] })
15206
- ] });
15593
+ ) });
15207
15594
  }
15208
15595
  const BASE_PATH = "/api";
15209
15596
  function getExternalImageSources(prod = false) {
@@ -15519,10 +15906,10 @@ function TabPortals({ prod, disabled, onSelected }) {
15519
15906
  onSubmit: handleSearch,
15520
15907
  className: "flex flex-col gap-4 border-t-1 pt-4 border-brand-blue-200",
15521
15908
  children: [
15522
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
15909
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
15523
15910
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
15524
- /* @__PURE__ */ jsxs("label", { className: "text-sm font-medium text-brand-blue-1000", children: [
15525
- "Portales (",
15911
+ /* @__PURE__ */ jsxs("h3", { className: "text-lg font-semibold text-brand-blue-1000", children: [
15912
+ "Selecciona portales (",
15526
15913
  selectedPortals.length,
15527
15914
  " seleccionados)"
15528
15915
  ] }),
@@ -15531,31 +15918,52 @@ function TabPortals({ prod, disabled, onSelected }) {
15531
15918
  {
15532
15919
  type: "button",
15533
15920
  onClick: handleSelectAll,
15534
- className: "text-xs text-brand-blue-800 hover:underline",
15921
+ className: "text-sm text-brand-blue-700 hover:text-brand-blue-900 font-medium transition-colors",
15535
15922
  children: selectedPortals.length === portalSourcesHook.sources.length ? "Deseleccionar todos" : "Seleccionar todos"
15536
15923
  }
15537
15924
  )
15538
15925
  ] }),
15539
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-2", children: portalSourcesHook.sources.map((portal) => /* @__PURE__ */ jsxs(
15540
- "label",
15541
- {
15542
- className: "flex items-center gap-2 p-2 border border-brand-blue-200 rounded cursor-pointer hover:bg-brand-blue-050 transition",
15543
- children: [
15544
- /* @__PURE__ */ jsx(
15545
- "input",
15546
- {
15547
- type: "checkbox",
15548
- checked: selectedPortals.includes(portal.id),
15549
- onChange: () => handlePortalToggle(portal.id),
15550
- disabled,
15551
- className: "w-4 h-4"
15552
- }
15553
- ),
15554
- /* @__PURE__ */ jsx("span", { className: "text-sm", children: portal.title })
15555
- ]
15556
- },
15557
- portal.id
15558
- )) })
15926
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-3", children: portalSourcesHook.sources.map((portal) => {
15927
+ const isSelected = selectedPortals.includes(portal.id);
15928
+ return /* @__PURE__ */ jsxs(
15929
+ "button",
15930
+ {
15931
+ type: "button",
15932
+ onClick: () => handlePortalToggle(portal.id),
15933
+ disabled,
15934
+ className: `
15935
+ relative p-4 rounded-lg border-2 transition-all duration-200
15936
+ flex flex-col items-center justify-center min-h-[100px] gap-2
15937
+ ${isSelected ? "border-brand-blue-600 bg-brand-blue-50" : "border-gray-200 bg-white hover:border-brand-blue-300"}
15938
+ ${disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}
15939
+ hover:shadow-md
15940
+ `,
15941
+ 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" }) }),
15943
+ /* @__PURE__ */ jsx(
15944
+ "svg",
15945
+ {
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
+ )
15959
+ }
15960
+ ),
15961
+ /* @__PURE__ */ jsx("span", { className: `text-sm font-medium text-center ${isSelected ? "text-brand-blue-900" : "text-gray-700"}`, children: portal.title })
15962
+ ]
15963
+ },
15964
+ portal.id
15965
+ );
15966
+ }) })
15559
15967
  ] }),
15560
15968
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
15561
15969
  /* @__PURE__ */ jsx(
@@ -15646,72 +16054,82 @@ function TabPortals({ prod, disabled, onSelected }) {
15646
16054
  /* @__PURE__ */ jsx("span", { className: "limbo-loader" }),
15647
16055
  /* @__PURE__ */ jsx("span", { className: "text-sm text-brand-blue-800", children: "Buscando imágenes..." })
15648
16056
  ] }) }),
15649
- /* @__PURE__ */ jsx(
15650
- "div",
15651
- {
15652
- className: `grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 ${loading ? "opacity-50" : ""}`,
15653
- children: images.map((img, idx) => {
15654
- const imageKey = `${img.source}-${img.id || idx}`;
15655
- const imageUrl = img.preview || img.thumbnail || img.url || img.full;
15656
- if (failedImages.has(imageKey)) {
15657
- return null;
15658
- }
15659
- return /* @__PURE__ */ jsxs(
15660
- "div",
15661
- {
15662
- className: "border border-brand-blue-200 rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-md transition-shadow",
15663
- children: [
15664
- /* @__PURE__ */ jsxs("div", { className: "relative aspect-video bg-neutral-100", children: [
15665
- /* @__PURE__ */ jsx(
15666
- ValidatedImage,
15667
- {
15668
- src: imageUrl,
15669
- alt: img.title || img.filename || `Imagen ${idx + 1}`,
15670
- className: "object-cover w-full h-full",
15671
- onError: () => {
15672
- setFailedImages((prev) => {
15673
- const newSet = new Set(prev);
15674
- newSet.add(imageKey);
15675
- return newSet;
15676
- });
15677
- }
15678
- }
15679
- ),
15680
- /* @__PURE__ */ jsx("span", { className: "absolute top-1 left-1 bg-black/60 text-white text-xs px-2 py-1 rounded", children: img.sourceTitle }),
15681
- img.id && /* @__PURE__ */ jsxs("span", { className: "absolute bottom-1 right-1 bg-black/60 text-white text-xs px-2 py-1 rounded", children: [
15682
- "ID: ",
15683
- img.id
15684
- ] })
15685
- ] }),
15686
- /* @__PURE__ */ jsxs("div", { className: "p-2", children: [
15687
- img.title && /* @__PURE__ */ jsx(
15688
- "p",
15689
- {
15690
- className: "text-xs text-neutral-700 mb-1 truncate",
15691
- title: img.title,
15692
- children: img.title
15693
- }
15694
- ),
15695
- /* @__PURE__ */ jsx(
15696
- "button",
15697
- {
15698
- className: `limbo-btn w-full text-sm ${downloadingUrl === (img.url || img.full) ? "limbo-btn-disabled cursor-not-allowed" : "limbo-btn-primary"}`,
15699
- onClick: () => handleImageSelect(img),
15700
- disabled: loading || disabled || downloadingUrl === (img.url || img.full),
15701
- children: downloadingUrl === (img.url || img.full) ? /* @__PURE__ */ jsxs(Fragment, { children: [
15702
- /* @__PURE__ */ jsx("span", { className: "limbo-loader limbo-loader--sm mr-1" }),
15703
- "Descargando..."
15704
- ] }) : "Seleccionar"
16057
+ /* @__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
+ ),
16065
+ images.map((img, idx) => {
16066
+ const imageKey = `${img.source}-${img.id || idx}`;
16067
+ const imageUrl = img.preview || img.thumbnail || img.url || img.full;
16068
+ if (failedImages.has(imageKey)) {
16069
+ return null;
16070
+ }
16071
+ return /* @__PURE__ */ jsxs(
16072
+ "div",
16073
+ {
16074
+ className: "group border border-brand-blue-200 rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-md transition-all duration-200 cursor-pointer",
16075
+ onClick: () => !downloadingUrl && !loading && !disabled && handleImageSelect(img),
16076
+ children: [
16077
+ /* @__PURE__ */ jsxs("div", { className: "relative aspect-video bg-neutral-100", children: [
16078
+ /* @__PURE__ */ jsx(
16079
+ ValidatedImage,
16080
+ {
16081
+ src: imageUrl,
16082
+ alt: img.title || img.filename || `Imagen ${idx + 1}`,
16083
+ className: "object-cover w-full h-full",
16084
+ onError: () => {
16085
+ setFailedImages((prev) => {
16086
+ const newSet = new Set(prev);
16087
+ newSet.add(imageKey);
16088
+ return newSet;
16089
+ });
15705
16090
  }
15706
- )
15707
- ] })
15708
- ]
15709
- },
15710
- imageKey
15711
- );
15712
- })
15713
- }
15714
- )
16091
+ }
16092
+ ),
16093
+ /* @__PURE__ */ jsx("span", { className: "absolute top-1 left-1 bg-black/60 text-white text-xs px-2 py-1 rounded", children: img.sourceTitle }),
16094
+ downloadingUrl === (img.url || img.full) ? /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 bg-white/90 flex flex-col items-center justify-center gap-2", children: [
16095
+ /* @__PURE__ */ jsxs("div", { className: "relative w-10 h-10", children: [
16096
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 border-4 border-brand-blue-200 rounded-full" }),
16097
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 border-4 border-brand-blue-600 rounded-full border-t-transparent animate-spin" })
16098
+ ] }),
16099
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-brand-blue-900", children: "Descargando..." })
16100
+ ] }) : /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-brand-blue-600/0 group-hover:bg-brand-blue-600/20 transition-colors duration-200 flex items-center justify-center", children: /* @__PURE__ */ jsx(
16101
+ "svg",
16102
+ {
16103
+ className: "w-12 h-12 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-200",
16104
+ fill: "none",
16105
+ stroke: "currentColor",
16106
+ viewBox: "0 0 24 24",
16107
+ children: /* @__PURE__ */ jsx(
16108
+ "path",
16109
+ {
16110
+ strokeLinecap: "round",
16111
+ strokeLinejoin: "round",
16112
+ strokeWidth: 2,
16113
+ d: "M5 13l4 4L19 7"
16114
+ }
16115
+ )
16116
+ }
16117
+ ) })
16118
+ ] }),
16119
+ img.title && /* @__PURE__ */ jsx("div", { className: "px-2 pb-2 pt-1", children: /* @__PURE__ */ jsx(
16120
+ "p",
16121
+ {
16122
+ className: "text-xs text-gray-600 truncate",
16123
+ title: img.title,
16124
+ children: img.title
16125
+ }
16126
+ ) })
16127
+ ]
16128
+ },
16129
+ imageKey
16130
+ );
16131
+ })
16132
+ ] })
15715
16133
  ] }),
15716
16134
  /* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-center gap-2", children: [
15717
16135
  /* @__PURE__ */ jsxs(
@@ -15818,7 +16236,7 @@ function UploadForm({
15818
16236
  }, 0);
15819
16237
  }
15820
16238
  };
15821
- return /* @__PURE__ */ jsxs("div", { className: "limbo-upload-form p-4 border-2 border-gray-200/50 rounded-lg min-w-fit max-w-5xl mx-auto", children: [
16239
+ return /* @__PURE__ */ jsxs("div", { className: "limbo-upload-form py-4 min-w-fit max-w-7xl mx-auto", children: [
15822
16240
  /* @__PURE__ */ jsx("div", { className: "limbo-tabs-container", children: /* @__PURE__ */ jsx(
15823
16241
  "div",
15824
16242
  {
@@ -19125,7 +19543,15 @@ const useCropper = (image, options = {}) => {
19125
19543
  }
19126
19544
  };
19127
19545
  setupImage();
19128
- }, [image, config.aspectRatio, config.shade, config.paspartuFactor]);
19546
+ }, [image]);
19547
+ useEffect(() => {
19548
+ if (!managerRef.current || !isReady) return;
19549
+ managerRef.current.selection.setAspectRatio(config.aspectRatio);
19550
+ }, [config.aspectRatio, isReady]);
19551
+ useEffect(() => {
19552
+ if (!managerRef.current || !isReady) return;
19553
+ managerRef.current.canvas.setBackground(config.shade);
19554
+ }, [config.shade, isReady]);
19129
19555
  const transform = useCallback(() => ({
19130
19556
  move: (x, y) => managerRef.current?.transform.move(x, y) || false,
19131
19557
  zoom: (factor) => managerRef.current?.transform.zoom(factor) || false,
@@ -19321,15 +19747,16 @@ function CropperView({
19321
19747
  const [previewLoading, setPreviewLoading] = useState(false);
19322
19748
  const [showGrid, setShowGrid] = useState(true);
19323
19749
  const [shade, setShade] = useState(true);
19750
+ const [initialLoading, setInitialLoading] = useState(true);
19751
+ const [cropTransitioning, setcropTransitioning] = useState(false);
19324
19752
  const [flipStates, setFlipStates] = useState({
19325
19753
  horizontal: false,
19326
19754
  vertical: false
19327
19755
  });
19328
- const [showTips, setShowTips] = useState(false);
19329
- const [showVisualOptions, setShowVisualOptions] = useState(true);
19330
- const [showSelectorOptions, setShowSelectorOptions] = useState(true);
19331
- const [showImageOptions, setShowImageOptions] = useState(true);
19332
- const [zoomInfo, setZoomInfo] = useState({ current: 1, percentage: 100 });
19756
+ const [showTipsModal, setShowTipsModal] = useState(false);
19757
+ const [showVisualOptions, setShowVisualOptions] = useState(false);
19758
+ const [showSelectorOptions, setShowSelectorOptions] = useState(false);
19759
+ const [showImageOptions, setShowImageOptions] = useState(false);
19333
19760
  const [editableFilename] = useState(() => {
19334
19761
  if (!image || !image.filename) {
19335
19762
  return "image";
@@ -19380,7 +19807,6 @@ function CropperView({
19380
19807
  });
19381
19808
  const [activeCropIndex, setActiveCropIndex] = useState(0);
19382
19809
  const activeCrop = crops[activeCropIndex];
19383
- const [shouldCenter, setShouldCenter] = useState(false);
19384
19810
  const calculatedAspectRatio = useMemo(() => {
19385
19811
  if (!activeCrop || !activeCrop.width || !activeCrop.height) return "";
19386
19812
  return activeCrop.width / activeCrop.height;
@@ -19431,7 +19857,6 @@ function CropperView({
19431
19857
  );
19432
19858
  const toggleGrid = useCallback(() => setShowGrid((v) => !v), []);
19433
19859
  const toggleShade = useCallback(() => setShade((v) => !v), []);
19434
- const toggleTips = useCallback(() => setShowTips((v) => !v), []);
19435
19860
  const toggleVisualOptions = useCallback(
19436
19861
  () => setShowVisualOptions((v) => !v),
19437
19862
  []
@@ -19456,7 +19881,6 @@ function CropperView({
19456
19881
  },
19457
19882
  [selection]
19458
19883
  );
19459
- const resetZoomOnly = useCallback(() => utils.resetZoomOnly(), [utils]);
19460
19884
  const flipHorizontal = useCallback(() => {
19461
19885
  setFlipStates((prev) => {
19462
19886
  const newHorizontal = !prev.horizontal;
@@ -19472,20 +19896,44 @@ function CropperView({
19472
19896
  });
19473
19897
  }, [transform]);
19474
19898
  const saveCurrentCropState = useCallback(() => {
19475
- if (!cropper.manager || !state.isReady) return;
19899
+ if (!cropper.manager || !state.isReady) return null;
19476
19900
  try {
19477
19901
  const currentCropData = cropData ? { ...cropData } : null;
19478
- const currentZoom = cropper.manager.transform.getZoom();
19479
- const currentRotation = cropper.manager.transform.getRotation();
19902
+ let selectorState = null;
19903
+ if (selectionRef.current) {
19904
+ const sel = selectionRef.current;
19905
+ selectorState = {
19906
+ x: sel.x ?? 0,
19907
+ y: sel.y ?? 0,
19908
+ width: sel.width ?? 0,
19909
+ height: sel.height ?? 0
19910
+ };
19911
+ }
19912
+ let imageTransform = null;
19913
+ if (imageRef.current) {
19914
+ const transform2 = imageRef.current.$getTransform?.();
19915
+ if (transform2 && Array.isArray(transform2)) {
19916
+ imageTransform = [...transform2];
19917
+ }
19918
+ }
19919
+ const flipState = {
19920
+ horizontal: flipStates.horizontal,
19921
+ vertical: flipStates.vertical
19922
+ };
19480
19923
  const savedState = {
19481
19924
  cropData: currentCropData,
19482
- transforms: {
19483
- zoom: currentZoom,
19484
- rotation: currentRotation,
19485
- flipHorizontal: flipStates.horizontal,
19486
- flipVertical: flipStates.vertical
19487
- }
19925
+ // Para cálculos de guardado
19926
+ selectorState,
19927
+ // Posición y tamaño del selector
19928
+ imageTransform,
19929
+ // Matriz de transformación completa
19930
+ flipState
19931
+ // Estado de flips
19488
19932
  };
19933
+ console.log(
19934
+ `[CropperView] 💾 Guardando estado del crop ${activeCropIndex}:`,
19935
+ savedState
19936
+ );
19489
19937
  setCrops(
19490
19938
  (prevCrops) => prevCrops.map(
19491
19939
  (crop, index) => index === activeCropIndex ? { ...crop, savedState } : crop
@@ -19496,40 +19944,94 @@ function CropperView({
19496
19944
  console.warn("Error saving crop state:", error);
19497
19945
  return null;
19498
19946
  }
19499
- }, [cropper.manager, state.isReady, cropData, flipStates, activeCropIndex]);
19947
+ }, [
19948
+ cropper.manager,
19949
+ state.isReady,
19950
+ cropData,
19951
+ flipStates,
19952
+ activeCropIndex,
19953
+ imageRef,
19954
+ selectionRef
19955
+ ]);
19500
19956
  const restoreCropState = useCallback(
19501
- (cropState) => {
19502
- if (!cropper.manager || !state.isReady || !cropState) return;
19503
- try {
19504
- const { cropData: savedCropData, transforms } = cropState;
19505
- if (transforms) {
19506
- utils.resetAll();
19507
- if (transforms.zoom && transforms.zoom !== 1) {
19508
- cropper.manager.transform.setZoom(transforms.zoom);
19509
- }
19510
- if (transforms.rotation && transforms.rotation !== 0) {
19511
- cropper.manager.transform.setRotation(transforms.rotation);
19512
- }
19513
- if (transforms.flipHorizontal) {
19514
- cropper.manager.transform.flipHorizontal();
19515
- }
19516
- if (transforms.flipVertical) {
19517
- cropper.manager.transform.flipVertical();
19518
- }
19957
+ (cropIndex) => {
19958
+ const crop = crops[cropIndex];
19959
+ if (!crop || !crop.savedState) {
19960
+ console.log(
19961
+ `[CropperView] ℹ️ No hay estado guardado para crop ${cropIndex}, centrando...`
19962
+ );
19963
+ setTimeout(() => {
19964
+ centerImage();
19965
+ setTimeout(() => centerSelection(), 100);
19966
+ }, 100);
19967
+ return;
19968
+ }
19969
+ if (!cropper.manager || !state.isReady) {
19970
+ console.warn("Cropper not ready for state restoration");
19971
+ return;
19972
+ }
19973
+ try {
19974
+ const { savedState } = crop;
19975
+ console.log(
19976
+ `[CropperView] ♻️ Restaurando estado del crop ${cropIndex}:`,
19977
+ savedState
19978
+ );
19979
+ if (savedState.imageTransform && imageRef.current && Array.isArray(savedState.imageTransform)) {
19980
+ setTimeout(() => {
19981
+ if (imageRef.current && imageRef.current.$setTransform) {
19982
+ const [a, b, c, d, e, f] = savedState.imageTransform;
19983
+ imageRef.current.$setTransform(a, b, c, d, e, f);
19984
+ console.log(
19985
+ `[CropperView] ✅ Imagen restaurada: transform(${a.toFixed(
19986
+ 2
19987
+ )}, ${b.toFixed(2)}, ${c.toFixed(2)}, ${d.toFixed(
19988
+ 2
19989
+ )}, ${e.toFixed(0)}, ${f.toFixed(0)})`
19990
+ );
19991
+ }
19992
+ }, 100);
19993
+ }
19994
+ if (savedState.selectorState && selectionRef.current) {
19995
+ const { x, y, width, height } = savedState.selectorState;
19996
+ setTimeout(() => {
19997
+ if (selectionRef.current) {
19998
+ selectionRef.current.x = x;
19999
+ selectionRef.current.y = y;
20000
+ selectionRef.current.width = width;
20001
+ selectionRef.current.height = height;
20002
+ if (selectionRef.current.$render) {
20003
+ selectionRef.current.$render();
20004
+ }
20005
+ console.log(
20006
+ `[CropperView] ✅ Selector restaurado: ${width}×${height} en (${x}, ${y})`
20007
+ );
20008
+ }
20009
+ }, 150);
20010
+ }
20011
+ if (savedState.flipState) {
19519
20012
  setFlipStates({
19520
- horizontal: transforms.flipHorizontal || false,
19521
- vertical: transforms.flipVertical || false
20013
+ horizontal: savedState.flipState.horizontal ?? false,
20014
+ vertical: savedState.flipState.vertical ?? false
19522
20015
  });
19523
20016
  }
19524
- if (savedCropData) {
19525
- const { x, y, width, height } = savedCropData;
19526
- selection.set(x, y, width, height);
19527
- }
20017
+ console.log(`[CropperView] ✨ Estado restaurado completamente`);
19528
20018
  } catch (error) {
19529
- console.warn("Error restoring crop state:", error);
20019
+ console.error("Error restoring crop state:", error);
20020
+ setTimeout(() => {
20021
+ centerImage();
20022
+ setTimeout(() => centerSelection(), 100);
20023
+ }, 100);
19530
20024
  }
19531
20025
  },
19532
- [cropper.manager, state.isReady, selection, utils]
20026
+ [
20027
+ crops,
20028
+ cropper.manager,
20029
+ state.isReady,
20030
+ centerImage,
20031
+ centerSelection,
20032
+ selectionRef,
20033
+ imageRef
20034
+ ]
19533
20035
  );
19534
20036
  const validateCropNames = useCallback(() => {
19535
20037
  for (let i = 0; i < crops.length; i++) {
@@ -19541,20 +20043,24 @@ function CropperView({
19541
20043
  return -1;
19542
20044
  }, [crops]);
19543
20045
  const switchToCrop = useCallback(
19544
- (newIndex) => {
20046
+ async (newIndex) => {
19545
20047
  if (newIndex === activeCropIndex) return;
20048
+ setcropTransitioning(true);
19546
20049
  saveCurrentCropState();
20050
+ await new Promise((resolve) => setTimeout(resolve, 100));
19547
20051
  setActiveCropIndex(newIndex);
19548
- setShouldCenter(true);
20052
+ await new Promise((resolve) => setTimeout(resolve, 250));
20053
+ restoreCropState(newIndex);
20054
+ await new Promise((resolve) => setTimeout(resolve, 200));
20055
+ setcropTransitioning(false);
19549
20056
  },
19550
- [activeCropIndex, saveCurrentCropState]
20057
+ [activeCropIndex, saveCurrentCropState, restoreCropState]
19551
20058
  );
19552
20059
  const addCustomCrop = useCallback(() => {
19553
20060
  if (!cropConfig.allowCustomCrops) {
19554
20061
  alert("No se pueden añadir recortes personalizados en este modo.");
19555
20062
  return;
19556
20063
  }
19557
- saveCurrentCropState();
19558
20064
  const newCropId = `crop-custom-${Date.now()}`;
19559
20065
  const newCrop = {
19560
20066
  id: newCropId,
@@ -19567,11 +20073,16 @@ function CropperView({
19567
20073
  savedState: null
19568
20074
  };
19569
20075
  setCrops((prevCrops) => [...prevCrops, newCrop]);
19570
- setActiveCropIndex(crops.length);
19571
20076
  accessibilityManager?.announce(
19572
- `Nuevo recorte personalizado añadido: ${newCrop.label}`
20077
+ `Nuevo recorte personalizado añadido: ${newCrop.label}. Selecciónalo para editarlo.`
19573
20078
  );
19574
- }, [cropConfig.allowCustomCrops, saveCurrentCropState, crops.length, image.width, image.height, accessibilityManager]);
20079
+ }, [
20080
+ cropConfig.allowCustomCrops,
20081
+ crops.length,
20082
+ image.width,
20083
+ image.height,
20084
+ accessibilityManager
20085
+ ]);
19575
20086
  const updateCropDimensions = useCallback(
19576
20087
  (field, value) => {
19577
20088
  const numValue = parseInt(value, 10);
@@ -19631,7 +20142,12 @@ function CropperView({
19631
20142
  [activeCropIndex]
19632
20143
  );
19633
20144
  const removeCustomCrop = useCallback(
19634
- (cropIndex) => {
20145
+ (cropIndex, cropLabel = null) => {
20146
+ cropLabel = cropLabel || crops[cropIndex].label;
20147
+ const confirmed = window.confirm(
20148
+ `¿Estás seguro de que deseas eliminar "${cropLabel || "este recorte"}"? Esta acción también eliminará todos sus recortes.`
20149
+ );
20150
+ if (!confirmed) return;
19635
20151
  const cropToRemove = crops[cropIndex];
19636
20152
  if (cropToRemove.required) {
19637
20153
  alert("No se puede eliminar un recorte obligatorio.");
@@ -19701,40 +20217,37 @@ function CropperView({
19701
20217
  setPreviewLoading(false);
19702
20218
  }
19703
20219
  }, [canExport, generatePreview, showPreview]);
19704
- const performSaveCrop = useCallback(async () => {
19705
- if (!canExport) {
19706
- const errorMsg = "No se puede exportar el recorte por restricciones de CORS en la imagen original.";
19707
- accessibilityManager?.announceError(errorMsg);
19708
- alert(errorMsg);
19709
- return;
19710
- }
19711
- if (!state.isReady) {
19712
- const errorMsg = "El cropper aún no está inicializado. Espera un momento e inténtalo de nuevo.";
19713
- accessibilityManager?.announceError(errorMsg);
19714
- alert(errorMsg);
19715
- return;
19716
- }
19717
- accessibilityManager?.announce("Creando recorte de la imagen");
19718
- try {
19719
- if (!cropData || !effectiveImageInfo) {
19720
- console.error("❌ Datos faltantes:", { cropData, effectiveImageInfo });
19721
- throw new Error(
19722
- `No hay datos de recorte disponibles. CropData: ${!!cropData}, ImageInfo: ${!!effectiveImageInfo}`
20220
+ const processSingleCrop = useCallback(
20221
+ async (cropIndex) => {
20222
+ const crop = crops[cropIndex];
20223
+ if (!crop) {
20224
+ throw new Error(`Crop ${cropIndex} no encontrado`);
20225
+ }
20226
+ let usedCropData;
20227
+ if (crop.savedState && crop.savedState.cropData) {
20228
+ usedCropData = crop.savedState.cropData;
20229
+ console.log(
20230
+ `[CropperView] Usando savedState para crop ${cropIndex}:`,
20231
+ usedCropData
19723
20232
  );
19724
- }
19725
- if (!cropData.x && cropData.x !== 0 || !cropData.y && cropData.y !== 0 || !cropData.width || !cropData.height) {
19726
- console.error("❌ CropData inválido:", cropData);
19727
- throw new Error(
19728
- "Los datos de recorte no tienen las propiedades esperadas"
20233
+ } else if (cropIndex === activeCropIndex && cropData) {
20234
+ usedCropData = cropData;
20235
+ console.log(
20236
+ `[CropperView] Usando cropData actual para crop activo ${cropIndex}:`,
20237
+ usedCropData
19729
20238
  );
19730
- }
19731
- if (!effectiveImageInfo.naturalWidth || !effectiveImageInfo.naturalHeight) {
19732
- console.error("❌ ImageInfo inválido:", effectiveImageInfo);
20239
+ } else {
19733
20240
  throw new Error(
19734
- "Los datos de imagen no tienen las dimensiones esperadas"
20241
+ `No hay datos de recorte para el crop "${crop.label}". Ajusta el recorte antes de guardar.`
19735
20242
  );
19736
20243
  }
19737
- const { x, y, width, height } = cropData;
20244
+ if (!usedCropData.x && usedCropData.x !== 0 || !usedCropData.y && usedCropData.y !== 0 || !usedCropData.width || !usedCropData.height) {
20245
+ throw new Error(`Datos de recorte inválidos para "${crop.label}"`);
20246
+ }
20247
+ if (!effectiveImageInfo?.naturalWidth || !effectiveImageInfo?.naturalHeight) {
20248
+ throw new Error("Información de imagen no disponible");
20249
+ }
20250
+ const { x, y, width, height } = usedCropData;
19738
20251
  const { naturalWidth, naturalHeight } = effectiveImageInfo;
19739
20252
  const cropParams = {
19740
20253
  x: x / naturalWidth,
@@ -19742,10 +20255,10 @@ function CropperView({
19742
20255
  width: width / naturalWidth,
19743
20256
  height: height / naturalHeight
19744
20257
  };
19745
- const variantWidth = Math.min(activeCrop.width, 5e3);
19746
- const variantHeight = Math.min(activeCrop.height, 5e3);
20258
+ const variantWidth = Math.min(crop.width, 5e3);
20259
+ const variantHeight = Math.min(crop.height, 5e3);
19747
20260
  const ts = Date.now();
19748
- const variantName = `${editableFilename}_${activeCrop.label || "crop"}_${ts}`;
20261
+ const variantName = `${editableFilename}_${crop.label || "crop"}_${ts}`;
19749
20262
  const result = await createCropVariant(image.id, cropParams, {
19750
20263
  name: variantName,
19751
20264
  width: variantWidth,
@@ -19756,11 +20269,45 @@ function CropperView({
19756
20269
  if (result) {
19757
20270
  accessibilityManager?.announceSuccess(`Recorte creado: ${variantName}`);
19758
20271
  onVariantCreated?.(image.id, result);
20272
+ return result;
20273
+ }
20274
+ throw new Error("No se pudo crear la variante");
20275
+ },
20276
+ [
20277
+ crops,
20278
+ activeCropIndex,
20279
+ cropData,
20280
+ effectiveImageInfo,
20281
+ editableFilename,
20282
+ createCropVariant,
20283
+ image.id,
20284
+ onVariantCreated,
20285
+ accessibilityManager
20286
+ ]
20287
+ );
20288
+ const performSaveCrop = useCallback(async () => {
20289
+ if (!canExport) {
20290
+ const errorMsg = "No se puede exportar el recorte por restricciones de CORS en la imagen original.";
20291
+ accessibilityManager?.announceError(errorMsg);
20292
+ alert(errorMsg);
20293
+ return;
20294
+ }
20295
+ if (!state.isReady) {
20296
+ const errorMsg = "El cropper aún no está inicializado. Espera un momento e inténtalo de nuevo.";
20297
+ accessibilityManager?.announceError(errorMsg);
20298
+ alert(errorMsg);
20299
+ return;
20300
+ }
20301
+ saveCurrentCropState();
20302
+ accessibilityManager?.announce("Creando recorte de la imagen");
20303
+ try {
20304
+ const result = await processSingleCrop(activeCropIndex);
20305
+ if (result) {
19759
20306
  onSave(result);
19760
20307
  }
19761
20308
  } catch (error) {
19762
20309
  console.warn("Error creating crop variant:", error);
19763
- const errorMsg = "No se pudo crear el recorte. Inténtalo de nuevo.";
20310
+ const errorMsg = error.message || "No se pudo crear el recorte. Inténtalo de nuevo.";
19764
20311
  accessibilityManager?.announceError(errorMsg);
19765
20312
  alert(errorMsg);
19766
20313
  onError?.(error);
@@ -19769,34 +20316,208 @@ function CropperView({
19769
20316
  canExport,
19770
20317
  state.isReady,
19771
20318
  accessibilityManager,
19772
- cropData,
19773
- effectiveImageInfo,
19774
- editableFilename,
19775
- activeCrop.label,
19776
- activeCrop.width,
19777
- activeCrop.height,
19778
- createCropVariant,
19779
- image.id,
19780
- onVariantCreated,
20319
+ saveCurrentCropState,
20320
+ processSingleCrop,
20321
+ activeCropIndex,
19781
20322
  onSave,
19782
20323
  onError
19783
20324
  ]);
20325
+ const performSaveMultipleCrops = useCallback(
20326
+ async (cropIndexes) => {
20327
+ saveCurrentCropState();
20328
+ accessibilityManager?.announce(
20329
+ `Guardando ${cropIndexes.length} recortes...`
20330
+ );
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);
20338
+ }
20339
+ } catch (error) {
20340
+ errors.push({
20341
+ crop: crops[index]?.label || `Crop ${index}`,
20342
+ error: error.message
20343
+ });
20344
+ }
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]);
20352
+ }
20353
+ }
20354
+ if (errors.length > 0) {
20355
+ const errorMsg = `Errores al guardar algunos recortes:
20356
+ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20357
+ accessibilityManager?.announceError(errorMsg);
20358
+ alert(errorMsg);
20359
+ }
20360
+ },
20361
+ [
20362
+ saveCurrentCropState,
20363
+ processSingleCrop,
20364
+ crops,
20365
+ accessibilityManager,
20366
+ onSave
20367
+ ]
20368
+ );
19784
20369
  const saveCrop = useCallback(async () => {
19785
20370
  const invalidCropIndex = validateCropNames();
19786
20371
  if (invalidCropIndex !== -1) {
19787
20372
  const cropWithoutName = crops[invalidCropIndex];
19788
- alert(`El recorte "${cropWithoutName.label || cropWithoutName || "(sin nombre)"}" debe tener un nombre válido.`);
20373
+ alert(
20374
+ `El recorte "${cropWithoutName.label || cropWithoutName || "(sin nombre)"}" debe tener un nombre válido.`
20375
+ );
19789
20376
  setActiveCropIndex(invalidCropIndex);
19790
20377
  return;
19791
20378
  }
19792
20379
  if (crops.length > 1) {
19793
20380
  setPendingAction("save");
19794
- setSelectedCropsForAction(crops.map((_, index) => index));
20381
+ const selectableCrops = crops.map((crop, index) => ({ crop, index })).filter(
20382
+ ({ crop, index }) => crop.required || crop.savedState || index === activeCropIndex
20383
+ ).map(({ index }) => index);
20384
+ setSelectedCropsForAction(selectableCrops);
19795
20385
  setShowConfirmModal(true);
19796
20386
  return;
19797
20387
  }
19798
20388
  await performSaveCrop();
19799
- }, [validateCropNames, crops, performSaveCrop]);
20389
+ }, [validateCropNames, crops, performSaveCrop, activeCropIndex]);
20390
+ const downloadSingleCrop = useCallback(
20391
+ async (cropIndex) => {
20392
+ const crop = crops[cropIndex];
20393
+ if (!crop) {
20394
+ throw new Error(`Crop ${cropIndex} no encontrado`);
20395
+ }
20396
+ if (!crop.savedState && cropIndex !== activeCropIndex) {
20397
+ throw new Error(
20398
+ `El recorte "${crop.label}" aún no ha sido configurado`
20399
+ );
20400
+ }
20401
+ const currentCropState = {
20402
+ index: activeCropIndex,
20403
+ imageTransform: imageRef.current?.$getTransform?.(),
20404
+ selectorState: selectionRef.current ? {
20405
+ x: selectionRef.current.x,
20406
+ y: selectionRef.current.y,
20407
+ width: selectionRef.current.width,
20408
+ height: selectionRef.current.height
20409
+ } : null
20410
+ };
20411
+ try {
20412
+ if (cropIndex !== activeCropIndex && crop.savedState) {
20413
+ console.log(
20414
+ `[CropperView] 🔄 Restaurando temporalmente crop ${cropIndex} para descarga...`
20415
+ );
20416
+ if (crop.savedState.imageTransform && imageRef.current?.$setTransform) {
20417
+ const [a, b, c, d, e, f] = crop.savedState.imageTransform;
20418
+ imageRef.current.$setTransform(a, b, c, d, e, f);
20419
+ }
20420
+ if (crop.savedState.selectorState && selectionRef.current) {
20421
+ const { x, y, width, height } = crop.savedState.selectorState;
20422
+ selectionRef.current.x = x;
20423
+ selectionRef.current.y = y;
20424
+ selectionRef.current.width = width;
20425
+ selectionRef.current.height = height;
20426
+ selectionRef.current.$render?.();
20427
+ }
20428
+ await new Promise((resolve) => setTimeout(resolve, 100));
20429
+ }
20430
+ const canvas = await selection.toCanvas({
20431
+ width: crop.width,
20432
+ height: crop.height,
20433
+ imageSmoothingEnabled: true,
20434
+ imageSmoothingQuality: "high"
20435
+ });
20436
+ if (!canvas) {
20437
+ throw new Error(`No se pudo generar el canvas para "${crop.label}"`);
20438
+ }
20439
+ const downloadUrl = canvas.toDataURL(
20440
+ `image/${globalThis.downloadFormat || image.mime_type.split("/")[1] || "webp"}`,
20441
+ 0.9
20442
+ );
20443
+ const cropName = crop.label.replace(/\.[^/.]+$/, "").replace(/\s+/g, "-").trim();
20444
+ const filename = `${editableFilename}_${cropName || "crop"}`;
20445
+ await downloadImage(downloadUrl, filename, {
20446
+ accessibilityManager,
20447
+ onSuccess: (finalFilename) => {
20448
+ console.log(`✅ Descargado: ${finalFilename}`);
20449
+ },
20450
+ onError: (error) => {
20451
+ throw new Error(
20452
+ `Error al descargar "${crop.label}": ${error.message}`
20453
+ );
20454
+ }
20455
+ });
20456
+ return { crop: crop.label, filename };
20457
+ } finally {
20458
+ if (cropIndex !== activeCropIndex && currentCropState.imageTransform && imageRef.current?.$setTransform) {
20459
+ console.log(
20460
+ `[CropperView] ↩️ Restaurando estado original del crop ${currentCropState.index}...`
20461
+ );
20462
+ const [a, b, c, d, e, f] = currentCropState.imageTransform;
20463
+ imageRef.current.$setTransform(a, b, c, d, e, f);
20464
+ if (currentCropState.selectorState && selectionRef.current) {
20465
+ const { x, y, width, height } = currentCropState.selectorState;
20466
+ selectionRef.current.x = x;
20467
+ selectionRef.current.y = y;
20468
+ selectionRef.current.width = width;
20469
+ selectionRef.current.height = height;
20470
+ selectionRef.current.$render?.();
20471
+ }
20472
+ }
20473
+ }
20474
+ },
20475
+ [
20476
+ crops,
20477
+ activeCropIndex,
20478
+ selection,
20479
+ image.mime_type,
20480
+ editableFilename,
20481
+ accessibilityManager,
20482
+ imageRef,
20483
+ selectionRef
20484
+ ]
20485
+ );
20486
+ const performDownloadMultipleCrops = useCallback(
20487
+ async (cropIndexes) => {
20488
+ saveCurrentCropState();
20489
+ accessibilityManager?.announce(
20490
+ `Descargando ${cropIndexes.length} recortes...`
20491
+ );
20492
+ const results = [];
20493
+ const errors = [];
20494
+ for (const index of cropIndexes) {
20495
+ try {
20496
+ const result = await downloadSingleCrop(index);
20497
+ if (result) {
20498
+ results.push(result);
20499
+ }
20500
+ } catch (error) {
20501
+ errors.push({
20502
+ crop: crops[index]?.label || `Crop ${index}`,
20503
+ error: error.message
20504
+ });
20505
+ }
20506
+ }
20507
+ if (results.length > 0) {
20508
+ accessibilityManager?.announceSuccess(
20509
+ `${results.length} recorte(s) descargado(s) correctamente`
20510
+ );
20511
+ }
20512
+ if (errors.length > 0) {
20513
+ const errorMsg = `Errores al descargar algunos recortes:
20514
+ ${errors.map((e) => `- ${e.crop}: ${e.error}`).join("\n")}`;
20515
+ accessibilityManager?.announceError(errorMsg);
20516
+ alert(errorMsg);
20517
+ }
20518
+ },
20519
+ [saveCurrentCropState, downloadSingleCrop, crops, accessibilityManager]
20520
+ );
19800
20521
  const performDownload = useCallback(async () => {
19801
20522
  if (!canExport) {
19802
20523
  const errorMsg = "No se puede descargar el recorte por restricciones de CORS en la imagen original.";
@@ -19804,99 +20525,82 @@ function CropperView({
19804
20525
  alert(errorMsg);
19805
20526
  return;
19806
20527
  }
20528
+ saveCurrentCropState();
20529
+ accessibilityManager?.announce("Preparando descarga del recorte");
19807
20530
  try {
19808
- accessibilityManager?.announce("Preparando descarga del recorte");
19809
- let downloadUrl = previewUrl;
19810
- if (!downloadUrl) {
19811
- downloadUrl = await generatePreview();
19812
- }
19813
- if (!downloadUrl) {
19814
- throw new Error("No se pudo generar la imagen para descargar");
19815
- }
19816
- const cropName = activeCrop.label.replace(/\.[^/.]+$/, " ").replace(" ", "-").trim();
19817
- const filename = `${editableFilename}_${cropName || "crop"}`;
19818
- await downloadImage(downloadUrl, filename, {
19819
- accessibilityManager,
19820
- onSuccess: (finalFilename) => {
19821
- accessibilityManager?.announce(
19822
- `Recorte descargado como ${finalFilename}`
19823
- );
19824
- },
19825
- onError: (error) => {
19826
- accessibilityManager?.announceError(
19827
- `Error al descargar: ${error.message}`
19828
- );
19829
- alert(`Error al descargar la imagen: ${error.message}`);
19830
- }
19831
- });
20531
+ await downloadSingleCrop(activeCropIndex);
20532
+ accessibilityManager?.announceSuccess("Recorte descargado correctamente");
19832
20533
  } catch (error) {
19833
20534
  console.error("Error downloading crop:", error);
19834
- accessibilityManager?.announceError(
19835
- `Error al descargar el recorte: ${error.message}`
19836
- );
19837
- alert(`Error al descargar el recorte: ${error.message}`);
20535
+ const errorMsg = error.message || "Error al descargar el recorte";
20536
+ accessibilityManager?.announceError(errorMsg);
20537
+ alert(errorMsg);
19838
20538
  }
19839
20539
  }, [
19840
20540
  canExport,
19841
20541
  accessibilityManager,
19842
- previewUrl,
19843
- activeCrop.label,
19844
- editableFilename,
19845
- generatePreview
20542
+ saveCurrentCropState,
20543
+ downloadSingleCrop,
20544
+ activeCropIndex
19846
20545
  ]);
19847
20546
  const handleDownload = useCallback(async () => {
19848
20547
  const invalidCropIndex = validateCropNames();
19849
20548
  if (invalidCropIndex !== -1) {
19850
20549
  const cropWithoutName = crops[invalidCropIndex];
19851
- alert(`El recorte "${cropWithoutName.label || cropWithoutName || "(sin nombre)"}" debe tener un nombre válido.`);
20550
+ alert(
20551
+ `El recorte "${cropWithoutName.label || cropWithoutName || "(sin nombre)"}" debe tener un nombre válido.`
20552
+ );
19852
20553
  setActiveCropIndex(invalidCropIndex);
19853
20554
  return;
19854
20555
  }
19855
20556
  if (crops.length > 1) {
19856
20557
  setPendingAction("download");
19857
- setSelectedCropsForAction(crops.map((_, index) => index));
20558
+ const configuredCrops = crops.map((crop, index) => ({ crop, index })).filter(
20559
+ ({ crop, index }) => crop.savedState || index === activeCropIndex
20560
+ ).map(({ index }) => index);
20561
+ setSelectedCropsForAction(configuredCrops);
19858
20562
  setShowConfirmModal(true);
19859
20563
  return;
19860
20564
  }
19861
20565
  await performDownload();
19862
- }, [validateCropNames, crops, performDownload]);
20566
+ }, [validateCropNames, crops, performDownload, activeCropIndex]);
19863
20567
  useEffect(() => {
19864
20568
  setShowPreview(false);
19865
20569
  setPreviewUrl(null);
19866
20570
  }, [image]);
19867
20571
  useEffect(() => {
19868
- if (!cropper.manager) return;
20572
+ if (!cropper.manager || !state.isReady || !initialLoading) return;
20573
+ console.log(
20574
+ "[CropperView] Carga inicial completada, centrando imagen y selector..."
20575
+ );
20576
+ setTimeout(() => {
20577
+ centerImage();
20578
+ setTimeout(() => {
20579
+ centerSelection();
20580
+ setInitialLoading(false);
20581
+ console.log("[CropperView] Carga inicial finalizada");
20582
+ }, 100);
20583
+ }, 50);
20584
+ }, [
20585
+ cropper.manager,
20586
+ state.isReady,
20587
+ initialLoading,
20588
+ centerImage,
20589
+ centerSelection
20590
+ ]);
20591
+ useEffect(() => {
20592
+ if (!cropper.manager || !state.isReady) return;
19869
20593
  if (calculatedAspectRatio) {
19870
20594
  selection.setAspectRatio(calculatedAspectRatio);
19871
20595
  }
19872
20596
  utils.setBackground(shade);
19873
- }, [calculatedAspectRatio, shade, cropper.manager, selection, utils]);
19874
- useEffect(() => {
19875
- if (!cropper.manager || !state.isReady) return;
19876
- const cropState = activeCrop?.savedState;
19877
- if (cropState) {
19878
- restoreCropState(cropState);
19879
- setShouldCenter(false);
19880
- } else {
19881
- if (calculatedAspectRatio) {
19882
- selection.setAspectRatio(calculatedAspectRatio);
19883
- if (shouldCenter) {
19884
- setTimeout(() => {
19885
- selection.center();
19886
- setShouldCenter(false);
19887
- }, 100);
19888
- }
19889
- }
19890
- }
19891
20597
  }, [
19892
- activeCropIndex,
19893
- activeCrop,
19894
20598
  calculatedAspectRatio,
20599
+ shade,
19895
20600
  cropper.manager,
19896
20601
  state.isReady,
19897
- restoreCropState,
19898
20602
  selection,
19899
- shouldCenter
20603
+ utils
19900
20604
  ]);
19901
20605
  useEffect(() => {
19902
20606
  if (!imageInfo || !state.isReady || cropConfig.mandatoryCrops.length > 0)
@@ -19917,21 +20621,6 @@ function CropperView({
19917
20621
  );
19918
20622
  }
19919
20623
  }, [imageInfo, state.isReady, cropConfig.mandatoryCrops.length, crops]);
19920
- useEffect(() => {
19921
- if (!cropper.manager || !cropper.manager.utils) return;
19922
- try {
19923
- const currentZoom = cropper.manager.transform.getZoom();
19924
- const info = {
19925
- current: currentZoom,
19926
- isZoomedIn: currentZoom > 1,
19927
- isZoomedOut: currentZoom < 1,
19928
- percentage: Math.round(currentZoom * 100)
19929
- };
19930
- setZoomInfo(info);
19931
- } catch (error) {
19932
- console.warn("Error updating zoom info:", error);
19933
- }
19934
- }, [transformVersion, cropper.manager]);
19935
20624
  useEffect(() => {
19936
20625
  if (!showPreview || !canExport) return;
19937
20626
  const timeoutId = setTimeout(async () => {
@@ -19993,9 +20682,9 @@ function CropperView({
19993
20682
  };
19994
20683
  }, [canvasRef]);
19995
20684
  if (!image) return null;
19996
- return /* @__PURE__ */ jsxs("div", { className: "limbo-cropper-view px-2 border-2 border-gray-200/50 rounded-lg min-w-fit max-w-7xl mx-auto h-full min-h-full flex flex-col", children: [
19997
- /* @__PURE__ */ jsxs("div", { className: "limbo-cropper-header flex flex-col sm:flex-row justify-between items-start sm:items-center p-4 lg:p-6 pb-4 border-b border-gray-200 bg-white z-10 flex-shrink-0 gap-4 lg:gap-2", children: [
19998
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 space-y-2", children: [
20685
+ return /* @__PURE__ */ jsxs("div", { className: "limbo-cropper-view px-2 border-2 border-gray-200/50 rounded-lg max-w-7xl mx-auto w-full min-w-full h-full min-h-full flex flex-col", children: [
20686
+ /* @__PURE__ */ jsxs("div", { className: "limbo-cropper-header flex flex-row justify-between items-start flex-wrap mx-2 py-2 border-b border-gray-200 bg-white z-10 flex-shrink-0 lg:gap-2", children: [
20687
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-fit space-y-2 max-w-fit", children: [
19999
20688
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
20000
20689
  /* @__PURE__ */ jsx("h2", { className: "text-lg sm:text-xl font-bold text-gray-800", children: "Generar recortes" }),
20001
20690
  crops.length > 1 && /* @__PURE__ */ jsx("span", { className: "px-2 py-0.5 bg-blue-100 text-blue-800 text-xs font-semibold rounded", children: activeCrop.label })
@@ -20005,32 +20694,39 @@ function CropperView({
20005
20694
  /* @__PURE__ */ jsx("div", { className: "", children: editableFilename + "." + (globalThis.downloadFormat || image.mime_type.split("/")[1] || "webp") })
20006
20695
  ] })
20007
20696
  ] }),
20008
- /* @__PURE__ */ jsxs("div", { className: "flex flex-row self-end gap-2 w-full sm:w-auto", children: [
20009
- /* @__PURE__ */ jsx(
20697
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-row self-end gap-2 w-full max-w-fit sm:w-auto", children: [
20698
+ /* @__PURE__ */ jsxs(
20010
20699
  "button",
20011
20700
  {
20012
20701
  onClick: onCancel,
20013
20702
  disabled: creatingVariant,
20014
- className: "limbo-btn limbo-btn-secondary px-4 sm:py-1 h-min flex-1",
20703
+ className: `flex flex-row items-center gap-1 self-end w-full max-w-fit sm:w-auto px-3 cursor-pointer py-1.5 text-sm rounded bg-neutral-gray-500 hover:bg-neutral-gray-700 transition-colors text-white disabled:opacity-50 disabled:cursor-not-allowed`,
20015
20704
  "aria-label": "Cancelar y volver",
20016
20705
  title: "Cancelar y volver",
20017
- children: "Cancelar"
20706
+ children: [
20707
+ /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-white icon--sm m-0 p-0" }),
20708
+ " ",
20709
+ "Cancelar"
20710
+ ]
20018
20711
  }
20019
20712
  ),
20020
- /* @__PURE__ */ jsx(
20713
+ onDelete && /* @__PURE__ */ jsxs(
20021
20714
  "button",
20022
20715
  {
20023
- onClick: () => onDelete?.(image),
20716
+ onClick: () => onDelete(image),
20024
20717
  disabled: deleting | creatingVariant,
20025
- className: "limbo-btn limbo-btn-danger px-4 sm:py-1 h-min flex-1 sm:flex-initial",
20718
+ className: "flex flex-row items-center gap-1 self-end w-full max-w-fit sm:w-auto px-3 cursor-pointer py-1.5 text-sm rounded bg-red-700 hover:bg-red-900 transition-colors text-white disabled:opacity-50 disabled:cursor-not-allowed",
20026
20719
  "aria-label": `Eliminar imagen ${image.filename}`,
20027
20720
  title: `Eliminar imagen ${image.filename}`,
20028
- children: deleting ? "Eliminando..." : "Eliminar"
20721
+ children: [
20722
+ /* @__PURE__ */ jsx("span", { className: "icon icon-close-small-white icon--sm m-0 p-0" }),
20723
+ deleting ? "Eliminando..." : "Eliminar"
20724
+ ]
20029
20725
  }
20030
20726
  )
20031
20727
  ] })
20032
20728
  ] }),
20033
- /* @__PURE__ */ jsxs("div", { className: "limbo-cropper-status bg-white border-gray-100 px-4 sm:px-6 py-2 pb-0 flex-shrink-0", children: [
20729
+ /* @__PURE__ */ jsxs("div", { className: "limbo-cropper-status bg-white border-gray-100 py-0 pb-0 flex-shrink-0", children: [
20034
20730
  variantError && /* @__PURE__ */ jsxs("div", { className: "alert alert-danger mb-2 text-sm", role: "alert", children: [
20035
20731
  /* @__PURE__ */ jsx("strong", { children: "Error:" }),
20036
20732
  " ",
@@ -20054,8 +20750,8 @@ function CropperView({
20054
20750
  ] })
20055
20751
  ] }),
20056
20752
  !canExport && /* @__PURE__ */ jsx("div", { className: "alert alert-warning mb-2 text-sm", role: "alert", children: "⚠️ No se puede exportar por restricciones CORS" }),
20057
- /* @__PURE__ */ jsxs("div", { className: "bg-white border-b border-gray-200 p-3 sm:p-4 flex-shrink-0 sticky top-0 z-10 mb-3 shadow-sm", children: [
20058
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
20753
+ /* @__PURE__ */ jsxs("div", { className: "bg-white border-b border-gray-200 py-2 mx-2 flex-shrink-0 sticky top-0 z-10 mb-1", children: [
20754
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
20059
20755
  /* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold text-gray-700", children: [
20060
20756
  "Recortes ",
20061
20757
  crops.length > 1 && `(${crops.length})`
@@ -20068,121 +20764,111 @@ function CropperView({
20068
20764
  className: "text-xs cursor-pointer px-2 py-1 bg-green-100 hover:bg-green-200 text-green-800 rounded border border-green-300 transition-colors flex items-center gap-1",
20069
20765
  title: "Añadir recorte personalizado",
20070
20766
  children: [
20071
- /* @__PURE__ */ jsx("span", { className: "icon icon-add-green text-green-800 icon--xs" }),
20767
+ /* @__PURE__ */ jsx("span", { className: "icon icon-add-green text-green-800 icon--sm" }),
20072
20768
  "Añadir recorte"
20073
20769
  ]
20074
20770
  }
20075
20771
  )
20076
20772
  ] }),
20077
- /* @__PURE__ */ jsx("div", { className: "space-y-2 max-h-48 overflow-y-auto grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2 justify-items-stretch items-start justify-stretch w-full", children: crops.map((crop, index) => /* @__PURE__ */ jsxs(
20078
- "label",
20079
- {
20080
- className: `flex items-center gap-3 p-2 rounded border cursor-pointer transition-colors ${activeCropIndex === index ? "bg-blue-50 border-blue-300" : "bg-gray-50 border-gray-200 hover:bg-gray-100"}`,
20081
- children: [
20082
- /* @__PURE__ */ jsx(
20083
- "input",
20084
- {
20085
- type: "radio",
20086
- name: "active-crop",
20087
- checked: activeCropIndex === index,
20088
- onChange: () => switchToCrop(index),
20089
- disabled: creatingVariant,
20090
- className: "w-4 h-4 text-blue-600 hidden"
20091
- }
20092
- ),
20093
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
20094
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
20095
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-800 truncate", children: activeCrop.label === crop.label && crop.required === false ? /* @__PURE__ */ jsx(
20096
- "input",
20773
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-1.5 max-h-48 overflow-y-auto", children: crops.map((crop, index) => {
20774
+ const isConfigured = crop.savedState || index === activeCropIndex;
20775
+ return /* @__PURE__ */ jsxs(
20776
+ "label",
20777
+ {
20778
+ className: `flex flex-col p-1.5 rounded border cursor-pointer transition-colors min-h-[70px] ${activeCropIndex === index ? "bg-blue-50 border-blue-300" : isConfigured ? "bg-gray-50 border-gray-200 hover:bg-gray-100" : "bg-orange-50 border-orange-300 hover:bg-orange-100"}`,
20779
+ children: [
20780
+ /* @__PURE__ */ jsx(
20781
+ "input",
20782
+ {
20783
+ type: "radio",
20784
+ name: "active-crop",
20785
+ checked: activeCropIndex === index,
20786
+ onChange: () => switchToCrop(index),
20787
+ disabled: creatingVariant,
20788
+ className: "hidden"
20789
+ }
20790
+ ),
20791
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 mb-1", children: [
20792
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1", children: [
20793
+ activeCropIndex === index && !crop.required ? /* @__PURE__ */ jsx(
20794
+ "input",
20795
+ {
20796
+ type: "text",
20797
+ value: crop.label,
20798
+ onChange: (e) => updateCropLabel(e.target.value),
20799
+ disabled: creatingVariant,
20800
+ className: "w-full text-xs px-1 py-0.5 border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500",
20801
+ placeholder: "Nombre",
20802
+ maxLength: 25,
20803
+ onClick: (e) => e.stopPropagation()
20804
+ }
20805
+ ) : /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-800 truncate", children: crop.label }),
20806
+ crop.required && /* @__PURE__ */ jsx("span", { className: "text-[10px] px-1 py-0.5 bg-red-100 text-red-700 rounded whitespace-nowrap", children: "Oblig." }),
20807
+ crop.isCustom && activeCropIndex !== index && /* @__PURE__ */ jsx("span", { className: "text-[10px] px-1 py-0.5 bg-purple-100 text-purple-700 rounded whitespace-nowrap", children: "Custom" })
20808
+ ] }) }),
20809
+ !crop.required && crops.length > 1 && /* @__PURE__ */ jsx(
20810
+ "button",
20097
20811
  {
20098
- type: "text",
20099
- value: crop.label,
20100
- onChange: (e) => updateCropLabel(e.target.value),
20101
- disabled: creatingVariant || crop.required,
20102
- className: "w-full text-sm px-2 py-1.5 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed",
20103
- placeholder: "Nombre del recorte",
20104
- required: true,
20105
- maxLength: 25
20812
+ onClick: (e) => {
20813
+ e.preventDefault();
20814
+ e.stopPropagation();
20815
+ removeCustomCrop(index, crop.label);
20816
+ },
20817
+ className: "flex-shrink-0 hover:bg-red-500/50 text-red-500 p-1 rounded max-h-fit aspect-square cursor-pointer flex",
20818
+ title: "Eliminar",
20819
+ children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small icon--xs" })
20106
20820
  }
20107
- ) : crop.label }),
20108
- crop.required && /* @__PURE__ */ jsx("span", { className: "text-xs px-1.5 py-0.5 bg-red-100 text-red-700 rounded", children: "Obligatorio" }),
20109
- crop.isCustom && /* @__PURE__ */ jsx("span", { className: "text-xs px-1.5 py-0.5 bg-purple-100 text-purple-700 rounded", children: "Personalizado" })
20821
+ )
20110
20822
  ] }),
20111
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500", children: [
20823
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 mt-auto", children: activeCropIndex === index ? /* @__PURE__ */ jsxs(Fragment, { children: [
20824
+ /* @__PURE__ */ jsx(
20825
+ "input",
20826
+ {
20827
+ type: "number",
20828
+ min: "100",
20829
+ max: "5000",
20830
+ value: crop.width,
20831
+ onChange: (e) => updateCropDimensions("width", e.target.value),
20832
+ onBlur: () => validateAndApplyCropDimensions("width"),
20833
+ disabled: creatingVariant,
20834
+ className: "flex-1 text-xs px-1 py-0.5 border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500",
20835
+ placeholder: "W",
20836
+ onClick: (e) => e.stopPropagation()
20837
+ }
20838
+ ),
20839
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400 text-xs", children: "×" }),
20840
+ /* @__PURE__ */ jsx(
20841
+ "input",
20842
+ {
20843
+ type: "number",
20844
+ min: "100",
20845
+ max: "5000",
20846
+ value: crop.height,
20847
+ onChange: (e) => updateCropDimensions("height", e.target.value),
20848
+ onBlur: () => validateAndApplyCropDimensions("height"),
20849
+ disabled: creatingVariant,
20850
+ className: "flex-1 text-xs px-1 py-0.5 border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500",
20851
+ placeholder: "H",
20852
+ onClick: (e) => e.stopPropagation()
20853
+ }
20854
+ )
20855
+ ] }) : /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-gray-500", children: [
20112
20856
  crop.width,
20113
20857
  " × ",
20114
20858
  crop.height,
20115
20859
  " px"
20116
- ] })
20117
- ] }),
20118
- !crop.required && crops.length > 1 && /* @__PURE__ */ jsx(
20119
- "button",
20120
- {
20121
- onClick: (e) => {
20122
- e.preventDefault();
20123
- removeCustomCrop(index);
20124
- },
20125
- className: `flex items-stretch group max-h-fit hover:bg-red-500/50 text-red-500 hover:text-red-700 p-1 rounded-full aspect-square cursor-pointer`,
20126
- title: "Eliminar recorte",
20127
- children: /* @__PURE__ */ jsx(
20128
- "span",
20129
- {
20130
- className: `group-hover:bg-gray-transparent-50 icon icon-close-small icon--xs`
20131
- }
20132
- )
20133
- }
20134
- )
20135
- ]
20136
- },
20137
- crop.id
20138
- )) }),
20139
- activeCrop && /* @__PURE__ */ jsx("div", { className: "mt-4 pt-4 border-t border-gray-200 space-y-3", children: /* @__PURE__ */ jsxs("div", { children: [
20140
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-gray-700 block mb-1", children: "Dimensiones (px)" }),
20141
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
20142
- /* @__PURE__ */ jsx(
20143
- "input",
20144
- {
20145
- type: "number",
20146
- min: "100",
20147
- max: "5000",
20148
- value: activeCrop.width,
20149
- onChange: (e) => updateCropDimensions("width", e.target.value),
20150
- onBlur: () => validateAndApplyCropDimensions("width"),
20151
- disabled: creatingVariant,
20152
- className: "flex-1 text-sm px-2 py-1.5 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500",
20153
- placeholder: "Ancho"
20154
- }
20155
- ),
20156
- /* @__PURE__ */ jsx("span", { className: "text-gray-400 font-bold", children: "×" }),
20157
- /* @__PURE__ */ jsx(
20158
- "input",
20159
- {
20160
- type: "number",
20161
- min: "100",
20162
- max: "5000",
20163
- value: activeCrop.height,
20164
- onChange: (e) => updateCropDimensions("height", e.target.value),
20165
- onBlur: () => validateAndApplyCropDimensions("height"),
20166
- disabled: creatingVariant,
20167
- className: "flex-1 text-sm px-2 py-1.5 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500",
20168
- placeholder: "Alto"
20169
- }
20170
- )
20171
- ] }),
20172
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mt-1", children: [
20173
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500", children: [
20174
- "Proporción:",
20175
- " ",
20176
- calculatedAspectRatio ? calculatedAspectRatio.toFixed(2) : "N/A"
20177
- ] }),
20178
- /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: "Min: 100px | Max: 5000px" })
20179
- ] })
20180
- ] }) })
20860
+ ] }) }),
20861
+ !isConfigured && /* @__PURE__ */ jsx("div", { className: "text-[9px] px-1 py-0.5 bg-orange-200 text-orange-800 rounded text-center mt-1", children: "Sin configurar" })
20862
+ ]
20863
+ },
20864
+ crop.id
20865
+ );
20866
+ }) })
20181
20867
  ] })
20182
20868
  ] }),
20183
- /* @__PURE__ */ jsxs("div", { className: "cropper-main-content-area flex flex-col lg:flex-row flex-1 overflow-hidden rounded-lg gap-4 md:gap-0 aspect-auto lg:aspect-square xl:aspect-auto w-full max-w-[95%] lg:max-w-full self-center lg:max-h-[50rem]", children: [
20184
- /* @__PURE__ */ jsx("div", { className: "flex-1 relative min-w-0 order-1", children: /* @__PURE__ */ jsxs("div", { className: "limbo-cropper-container lg:absolute inset-2 sm:inset-4 bg-white rounded-lg border-2 border-gray-200 shadow-lg overflow-hidden", children: [
20185
- showPreview && /* @__PURE__ */ jsxs("div", { className: "absolute 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: [
20869
+ /* @__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
+ /* @__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: [
20186
20872
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mb-2", children: [
20187
20873
  /* @__PURE__ */ jsx("h3", { className: "text-xs font-semibold text-gray-700", children: "Vista previa" }),
20188
20874
  /* @__PURE__ */ jsx(
@@ -20213,6 +20899,20 @@ function CropperView({
20213
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" }) })
20214
20900
  ] }) })
20215
20901
  ] }),
20902
+ /* @__PURE__ */ jsx(
20903
+ LoadingOverlay,
20904
+ {
20905
+ show: initialLoading,
20906
+ message: "Cargando recortador..."
20907
+ }
20908
+ ),
20909
+ /* @__PURE__ */ jsx(
20910
+ LoadingOverlay,
20911
+ {
20912
+ show: cropTransitioning,
20913
+ message: `Cambiando a: ${crops[activeCropIndex]?.label || "recorte"}...`
20914
+ }
20915
+ ),
20216
20916
  /* @__PURE__ */ jsxs(
20217
20917
  "cropper-canvas",
20218
20918
  {
@@ -20303,629 +21003,524 @@ function CropperView({
20303
21003
  }
20304
21004
  )
20305
21005
  ] }) }),
20306
- /* @__PURE__ */ jsx("div", { className: "flex limbo-cropper-controls w-full lg:w-80 h-full bg-white border-t md:border-t-0 md:border-l border-gray-200 lg:overflow-y-auto flex-shrink-0 order-1 max-h-none", children: /* @__PURE__ */ jsxs("div", { className: "p-3 min-h-full sm:p-4 space-y-3 sm:space-y-4 relative w-full grid grid-cols-1 lg:flex lg:flex-col", children: [
20307
- /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [
21006
+ /* @__PURE__ */ jsx("div", { className: "w-full lg:max-w-80 mt-2 lg:my-0 overflow-y-auto bg-white border-t lg:border-t-0 lg:border-l border-gray-200 flex-shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "h-full mt-2 lg:my-0 me-2 lg:mx-2 space-y-2 grid grid-cols-2 md:grid-cols-3 flex-wrap md:flex-nowrap items-start flex-row gap-2 lg:gap-0 lg:block", children: [
21007
+ /* @__PURE__ */ jsxs("div", { className: "bg-white rounded border w-full border-gray-200 mb-2", children: [
20308
21008
  /* @__PURE__ */ jsx(
20309
21009
  "button",
20310
21010
  {
20311
- onClick: toggleVisualOptions,
20312
- className: "w-full p-2 my-1 cursor-pointer hover:bg-neutral-gray-050/50 transition-colors rounded-md",
20313
- "aria-expanded": showVisualOptions,
20314
- "aria-label": "Mostrar/ocultar opciones de visualizción",
21011
+ onClick: toggleImageOptions,
21012
+ className: "hidden sm:block w-full p-2 mb-05 cursor-pointer hover:bg-neutral-gray-050/50 transition-colors rounded-t-sm",
21013
+ "aria-expanded": showImageOptions,
21014
+ "aria-label": "Mostrar/ocultar opciones de visualización",
20315
21015
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
20316
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Visualización" }),
21016
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Transformar imagen" }),
20317
21017
  /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(
20318
21018
  "span",
20319
21019
  {
20320
- className: `icon ${!showVisualOptions ? "icon-chevron-down" : "icon-chevron-up"} text-center icon--xs`
21020
+ className: `icon ${!showImageOptions ? "icon-chevron-down" : "icon-chevron-up"} text-center icon--xs`
20321
21021
  }
20322
21022
  ) })
20323
21023
  ] })
20324
21024
  }
20325
21025
  ),
20326
- showVisualOptions && /* @__PURE__ */ jsxs("div", { className: "pb-2 space-y-2", children: [
20327
- /* @__PURE__ */ jsxs(
20328
- "button",
20329
- {
20330
- onClick: toggleGrid,
20331
- className: `w-full flex cursor-pointer items-center justify-between p-2 rounded transition-colors ${showGrid ? "bg-blue-100 border border-blue-300 text-blue-800" : "bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200"}`,
20332
- disabled: creatingVariant,
20333
- "aria-pressed": showGrid,
20334
- title: "Mostrar/ocultar cuadrícula de la regla de los tercios en el selector",
20335
- "aria-label": "Activar/desactivar grid",
20336
- children: [
20337
- /* @__PURE__ */ jsxs("span", { className: "text-sm flex items-center", children: [
20338
- /* @__PURE__ */ jsx("span", { className: "icon icon-area-blue me-1" }),
20339
- " ",
20340
- "Cuadrícula"
20341
- ] }),
20342
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: showGrid ? /* @__PURE__ */ jsxs(Fragment, { children: [
20343
- /* @__PURE__ */ jsx("span", { className: "icon icon-tick icon--xs align-[middle!important] -mt-0.5" }),
20344
- " ",
20345
- "Activo"
20346
- ] }) : "Inactivo" })
20347
- ]
20348
- }
20349
- ),
20350
- /* @__PURE__ */ jsxs(
20351
- "button",
20352
- {
20353
- onClick: toggleShade,
20354
- className: `w-full flex cursor-pointer items-center justify-between p-2 rounded transition-colors ${shade ? "bg-blue-100 border border-blue-300 text-blue-800" : "bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200"}`,
20355
- disabled: creatingVariant,
20356
- "aria-pressed": shade,
20357
- "aria-label": "Activar/desactivar sombreado",
20358
- children: [
20359
- /* @__PURE__ */ jsxs("span", { className: "text-sm flex items-center", children: [
20360
- /* @__PURE__ */ jsx("span", { className: "icon icon-comparison-blue me-1" }),
20361
- " ",
20362
- "Tablero"
20363
- ] }),
20364
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: shade ? /* @__PURE__ */ jsxs(Fragment, { children: [
20365
- /* @__PURE__ */ jsx("span", { className: "icon icon-tick icon--xs align-[middle!important] -mt-0.5" }),
20366
- " ",
20367
- "Activo"
20368
- ] }) : "Inactivo" })
20369
- ]
20370
- }
20371
- )
20372
- ] })
20373
- ] }),
20374
- /* @__PURE__ */ jsxs(
20375
- "div",
20376
- {
20377
- className: "bg-white rounded-lg border border-gray-200 p-4" + (showSelectorOptions ? "" : " bg-neutral-gray-50/50"),
20378
- children: [
20379
- /* @__PURE__ */ jsx(
20380
- "button",
20381
- {
20382
- onClick: toggleSelectorOptions,
20383
- className: "w-full p-2 my-1 cursor-pointer hover:bg-neutral-gray-050/50 transition-colors rounded-md",
20384
- "aria-expanded": showSelectorOptions,
20385
- "aria-label": "Mostrar/ocultar opciones de visualizción",
20386
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
20387
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Ajustes del selector" }),
20388
- /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(
20389
- "span",
20390
- {
20391
- className: `icon ${!showSelectorOptions ? "icon-chevron-down" : "icon-chevron-up"} text-center icon--xs`
20392
- }
20393
- ) })
20394
- ] })
20395
- }
20396
- ),
20397
- showSelectorOptions && /* @__PURE__ */ jsxs("div", { children: [
20398
- /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
21026
+ /* @__PURE__ */ jsxs(
21027
+ "div",
21028
+ {
21029
+ className: "p-2 space-y-2 justify-items-stretch static " + (showImageOptions ? "block" : "sm:hidden"),
21030
+ children: [
21031
+ /* @__PURE__ */ jsx("div", { className: "mb-3 block", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-5 gap-1 content-center h-fit mx-auto", children: [
20399
21032
  /* @__PURE__ */ jsx(
20400
21033
  "button",
20401
21034
  {
20402
- onClick: () => setSelectionCoverage(0.5),
20403
- className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
21035
+ onClick: () => zoom(-0.2),
21036
+ className: "px-0 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200 aspect-square",
21037
+ title: "Alejar",
20404
21038
  disabled: creatingVariant,
20405
- "aria-label": "Selección 50%",
20406
- title: "Tamaño de selector 50%",
20407
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "50%" }) })
21039
+ "aria-label": "Alejar imagen",
21040
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-zoom-out-blue" }) })
20408
21041
  }
20409
21042
  ),
20410
21043
  /* @__PURE__ */ jsx(
20411
21044
  "button",
20412
21045
  {
20413
- onClick: () => setSelectionCoverage(0.7),
20414
- className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
21046
+ onClick: () => move(-10, -10),
21047
+ className: "px-0 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200 aspect-square",
21048
+ title: "Mover arriba-izquierda",
20415
21049
  disabled: creatingVariant,
20416
- "aria-label": "Selección 70%",
20417
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "70%" }) })
21050
+ "aria-label": "Mover imagen hacia arriba-izquierda",
21051
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-blue icon--md -rotate-45" }) })
20418
21052
  }
20419
21053
  ),
20420
21054
  /* @__PURE__ */ jsx(
20421
21055
  "button",
20422
21056
  {
20423
- onClick: () => setSelectionCoverage(0.9),
20424
- className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
21057
+ onClick: () => move(0, -10),
21058
+ className: "px-0 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200 aspect-square",
21059
+ title: "Mover arriba",
20425
21060
  disabled: creatingVariant,
20426
- "aria-label": "Selección 90%",
20427
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "90%" }) })
21061
+ "aria-label": "Mover imagen hacia arriba",
21062
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-blue icon--md" }) })
20428
21063
  }
20429
21064
  ),
20430
21065
  /* @__PURE__ */ jsx(
20431
21066
  "button",
20432
21067
  {
20433
- onClick: () => setSelectionCoverage(1),
20434
- className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
21068
+ onClick: () => move(10, -10),
21069
+ className: "px-0 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200 aspect-square",
21070
+ title: "Mover arriba-derecha",
20435
21071
  disabled: creatingVariant,
20436
- "aria-label": "Selección completa",
20437
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "100%" }) })
21072
+ "aria-label": "Mover imagen hacia arriba-derecha",
21073
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-blue icon--md rotate-45" }) })
20438
21074
  }
20439
- )
20440
- ] }) }),
20441
- /* @__PURE__ */ jsxs("div", { className: "space-y-2 pt-2", children: [
21075
+ ),
20442
21076
  /* @__PURE__ */ jsx(
20443
21077
  "button",
20444
21078
  {
20445
- onClick: centerSelection,
20446
- className: "w-full p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20447
- title: "Centrar selección",
21079
+ onClick: () => zoom(0.2),
21080
+ className: "px-0 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200 aspect-square",
21081
+ title: "Acercar 20%",
20448
21082
  disabled: creatingVariant,
20449
- "aria-label": "Centrar área de selección",
20450
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
20451
- /* @__PURE__ */ jsx("span", { className: "icon icon-radio-button icon--sm align-[middle!important] " }),
20452
- " ",
20453
- "Centrar selección"
20454
- ] }) })
21083
+ "aria-label": "Acercar imagen",
21084
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-zoom-in-blue" }) })
20455
21085
  }
20456
21086
  ),
20457
21087
  /* @__PURE__ */ jsx(
20458
21088
  "button",
20459
21089
  {
20460
- onClick: resetSelection,
20461
- className: "w-full p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20462
- title: "Reiniciar selección",
21090
+ onClick: () => rotate(-45),
21091
+ className: "px-0 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200 aspect-square",
21092
+ title: "Rotar -45°",
20463
21093
  disabled: creatingVariant,
20464
- "aria-label": "Reiniciar área de selección",
20465
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
20466
- /* @__PURE__ */ jsx("span", { className: "icon icon-refresh icon--sm lign-[middle!important] " }),
20467
- " ",
20468
- "Reset selección"
20469
- ] }) })
21094
+ "aria-label": "Rotar imagen 45 grados a la izquierda",
21095
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "text-brand-blue-1000 font-medium", children: "-45°" }) })
21096
+ }
21097
+ ),
21098
+ /* @__PURE__ */ jsx(
21099
+ "button",
21100
+ {
21101
+ onClick: () => move(-10, 0),
21102
+ className: "px-0 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200 aspect-square",
21103
+ title: "Mover izquierda",
21104
+ disabled: creatingVariant,
21105
+ "aria-label": "Mover imagen hacia la izquierda",
21106
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-blue" }) })
21107
+ }
21108
+ ),
21109
+ /* @__PURE__ */ jsx(
21110
+ "button",
21111
+ {
21112
+ onClick: centerImage,
21113
+ className: "px-0 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200 aspect-square",
21114
+ title: "Centrar y ajustar imagen",
21115
+ disabled: creatingVariant,
21116
+ "aria-label": "Centrar y ajustar imagen",
21117
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-radio-button-blue icon-md" }) })
21118
+ }
21119
+ ),
21120
+ /* @__PURE__ */ jsx(
21121
+ "button",
21122
+ {
21123
+ onClick: () => move(10, 0),
21124
+ className: "px-0 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200 aspect-square",
21125
+ title: "Mover derecha",
21126
+ disabled: creatingVariant,
21127
+ "aria-label": "Mover imagen hacia la derecha",
21128
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-blue" }) })
21129
+ }
21130
+ ),
21131
+ /* @__PURE__ */ jsx(
21132
+ "button",
21133
+ {
21134
+ onClick: () => rotate(45),
21135
+ className: "px-0 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200 aspect-square",
21136
+ title: "Rotar +45°",
21137
+ disabled: creatingVariant,
21138
+ "aria-label": "Rotar imagen 45 grados a la derecha",
21139
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "text-brand-blue-1000 font-medium", children: "+45°" }) })
21140
+ }
21141
+ ),
21142
+ /* @__PURE__ */ jsx(
21143
+ "button",
21144
+ {
21145
+ onClick: flipHorizontal,
21146
+ className: `px-0 rounded cursor-pointer transition-colors text-sm aspect-square ${flipStates.horizontal ? "bg-blue-100 border border-blue-300 text-blue-800" : "bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200"}`,
21147
+ title: `Voltear horizontalmente ${flipStates.horizontal ? "(activo)" : ""}`,
21148
+ disabled: creatingVariant,
21149
+ "aria-label": "Voltear imagen horizontalmente",
21150
+ "aria-pressed": flipStates.horizontal,
21151
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center gap-2", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-right-blue border border-brand-blue-1000 rounded" }) })
21152
+ }
21153
+ ),
21154
+ /* @__PURE__ */ jsx(
21155
+ "button",
21156
+ {
21157
+ onClick: () => move(-10, 10),
21158
+ className: "px-0 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200 aspect-square",
21159
+ title: "Mover abajo-izquierda",
21160
+ disabled: creatingVariant,
21161
+ "aria-label": "Mover imagen hacia abajo-izquierda",
21162
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-blue icon--md -rotate-145" }) })
21163
+ }
21164
+ ),
21165
+ /* @__PURE__ */ jsx(
21166
+ "button",
21167
+ {
21168
+ onClick: () => move(0, 10),
21169
+ className: "px-0 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200 aspect-square",
21170
+ title: "Mover abajo",
21171
+ disabled: creatingVariant,
21172
+ "aria-label": "Mover imagen hacia abajo",
21173
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-down-blue" }) })
21174
+ }
21175
+ ),
21176
+ /* @__PURE__ */ jsx(
21177
+ "button",
21178
+ {
21179
+ onClick: () => move(10, 10),
21180
+ className: "px-0 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200 aspect-square",
21181
+ title: "Mover abajo-derecha",
21182
+ disabled: creatingVariant,
21183
+ "aria-label": "Mover imagen hacia abajo-derecha",
21184
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-blue icon--md rotate-145" }) })
21185
+ }
21186
+ ),
21187
+ /* @__PURE__ */ jsx(
21188
+ "button",
21189
+ {
21190
+ onClick: flipVertical,
21191
+ className: `px-0 rounded cursor-pointer transition-colors aspect-square text-sm ${flipStates.vertical ? "bg-blue-100 border border-blue-300 text-blue-800" : "bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200"}`,
21192
+ title: `Voltear verticalmente ${flipStates.vertical ? "(activo)" : ""}`,
21193
+ disabled: creatingVariant,
21194
+ "aria-label": "Voltear imagen verticalmente",
21195
+ "aria-pressed": flipStates.vertical,
21196
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-down-blue border border-brand-blue-1000 rounded" }) })
20470
21197
  }
20471
21198
  )
20472
- ] })
20473
- ] })
20474
- ]
20475
- }
20476
- ),
20477
- /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [
20478
- /* @__PURE__ */ jsx(
20479
- "button",
20480
- {
20481
- onClick: toggleImageOptions,
20482
- className: "w-full p-2 my-1 cursor-pointer hover:bg-neutral-gray-050/50 transition-colors rounded-md",
20483
- "aria-expanded": showImageOptions,
20484
- "aria-label": "Mostrar/ocultar opciones de visualizción",
20485
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
20486
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Transformar imagen" }),
20487
- /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(
20488
- "span",
20489
- {
20490
- className: `icon ${!showImageOptions ? "icon-chevron-down" : "icon-chevron-up"} text-center icon--xs`
20491
- }
20492
- ) })
20493
- ] })
20494
- }
20495
- ),
20496
- showImageOptions && /* @__PURE__ */ jsxs(Fragment, { children: [
20497
- /* @__PURE__ */ jsxs("div", { className: "mb-4 hidden lg:block", children: [
20498
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600 mb-2", children: "Mover imagen" }),
20499
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-1", children: [
20500
- /* @__PURE__ */ jsx("div", {}),
20501
- /* @__PURE__ */ jsx(
20502
- "button",
20503
- {
20504
- onClick: () => move(0, -10),
20505
- className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20506
- title: "Mover arriba",
20507
- disabled: creatingVariant,
20508
- "aria-label": "Mover imagen hacia arriba",
20509
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-blue" }) })
20510
- }
20511
- ),
20512
- /* @__PURE__ */ jsx("div", {}),
20513
- /* @__PURE__ */ jsx(
20514
- "button",
20515
- {
20516
- onClick: () => move(-10, 0),
20517
- className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20518
- title: "Mover izquierda",
20519
- disabled: creatingVariant,
20520
- "aria-label": "Mover imagen hacia la izquierda",
20521
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-blue" }) })
20522
- }
20523
- ),
20524
- /* @__PURE__ */ jsx(
20525
- "button",
20526
- {
20527
- onClick: centerImage,
20528
- className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20529
- title: "Centrar y ajustar imagen",
20530
- disabled: creatingVariant,
20531
- "aria-label": "Centrar y ajustar imagen",
20532
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-radio-button-blue icon-md" }) })
20533
- }
20534
- ),
20535
- /* @__PURE__ */ jsx(
20536
- "button",
20537
- {
20538
- onClick: () => move(10, 0),
20539
- className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20540
- title: "Mover derecha",
20541
- disabled: creatingVariant,
20542
- "aria-label": "Mover imagen hacia la derecha",
20543
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-blue" }) })
20544
- }
20545
- ),
20546
- /* @__PURE__ */ jsx("div", {}),
20547
- /* @__PURE__ */ jsx(
20548
- "button",
20549
- {
20550
- onClick: () => move(0, 10),
20551
- className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20552
- title: "Mover abajo",
20553
- disabled: creatingVariant,
20554
- "aria-label": "Mover imagen hacia abajo",
20555
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-down-blue" }) })
20556
- }
20557
- ),
20558
- /* @__PURE__ */ jsx("div", {})
20559
- ] })
20560
- ] }),
20561
- /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
20562
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-600 mb-2", children: [
20563
- "Zoom ",
20564
- zoomInfo ? zoomInfo.percentage + "%" : ""
20565
- ] }),
20566
- /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
20567
- /* @__PURE__ */ jsx(
20568
- "button",
20569
- {
20570
- onClick: () => zoom(-0.2),
20571
- className: "flex-1 p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20572
- title: "Alejar 20%",
20573
- disabled: creatingVariant,
20574
- "aria-label": "Alejar imagen",
20575
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-zoom-out-blue" }) })
20576
- }
20577
- ),
20578
- /* @__PURE__ */ jsx(
20579
- "button",
20580
- {
20581
- onClick: resetZoomOnly,
20582
- className: "flex-1 p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20583
- title: "Restablecer zoom original",
20584
- disabled: creatingVariant,
20585
- "aria-label": "Restablecer el zoom para que la imagen se vea con su resolución original",
20586
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-screenshot-blue" }) })
20587
- }
20588
- ),
20589
- /* @__PURE__ */ jsx(
20590
- "button",
20591
- {
20592
- onClick: () => zoom(0.2),
20593
- className: "flex-1 p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20594
- title: "Acercar 20%",
20595
- disabled: creatingVariant,
20596
- "aria-label": "Acercar imagen",
20597
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-zoom-in-blue" }) })
20598
- }
20599
- )
20600
- ] })
20601
- ] }),
20602
- /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
20603
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600 mb-2", children: "Rotación" }),
20604
- /* @__PURE__ */ jsxs("div", { className: "flex gap-1 *:text-brand-blue-1000 *:text-sm", children: [
20605
- /* @__PURE__ */ jsx(
20606
- "button",
20607
- {
20608
- onClick: () => rotate(-90),
20609
- className: "flex-1 p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20610
- title: "Rotar -90°",
20611
- disabled: creatingVariant,
20612
- "aria-label": "Rotar imagen 90 grados a la izquierda",
20613
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-reply-blue" }) })
20614
- }
20615
- ),
20616
- /* @__PURE__ */ jsx(
20617
- "button",
20618
- {
20619
- onClick: () => rotate(-45),
20620
- className: "flex-1 p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20621
- title: "Rotar -45°",
20622
- disabled: creatingVariant,
20623
- "aria-label": "Rotar imagen 45 grados a la izquierda",
20624
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "-45°" }) })
20625
- }
20626
- ),
20627
- /* @__PURE__ */ jsx(
21199
+ ] }) }),
21200
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: /* @__PURE__ */ jsx(
20628
21201
  "button",
20629
21202
  {
20630
- onClick: () => rotate(45),
20631
- className: "flex-1 p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20632
- title: "Rotar +45°",
21203
+ onClick: () => {
21204
+ transform.reset();
21205
+ setFlipStates({ horizontal: false, vertical: false });
21206
+ },
21207
+ className: "w-full p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
21208
+ title: "Reiniciar transformaciones",
20633
21209
  disabled: creatingVariant,
20634
- "aria-label": "Rotar imagen 45 grados a la derecha",
20635
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "+45°" }) })
21210
+ "aria-label": "Reiniciar todas las transformaciones de la imagen",
21211
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
21212
+ /* @__PURE__ */ jsx("span", { className: "icon icon-refresh-blue icon--sm align-[middle!important] " }),
21213
+ " ",
21214
+ "Reiniciar ajustes"
21215
+ ] }) })
20636
21216
  }
20637
- ),
20638
- /* @__PURE__ */ jsx(
20639
- "button",
21217
+ ) })
21218
+ ]
21219
+ }
21220
+ )
21221
+ ] }),
21222
+ /* @__PURE__ */ jsxs(
21223
+ "div",
21224
+ {
21225
+ className: "bg-white rounded border border-gray-200 w-full" + (showSelectorOptions ? "" : " bg-neutral-gray-50/50"),
21226
+ children: [
21227
+ /* @__PURE__ */ jsx(
21228
+ "button",
21229
+ {
21230
+ onClick: toggleSelectorOptions,
21231
+ className: "hidden sm:block w-full cursor-pointer hover:bg-neutral-gray-050/50 p-2 mb-05 transition-colors rounded-t-sm",
21232
+ "aria-expanded": showSelectorOptions,
21233
+ "aria-label": "Mostrar/ocultar opciones de visualizción",
21234
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
21235
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Ajustes del selector" }),
21236
+ /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(
21237
+ "span",
21238
+ {
21239
+ className: `icon ${!showSelectorOptions ? "icon-chevron-down" : "icon-chevron-up"} text-center icon--xs`
21240
+ }
21241
+ ) })
21242
+ ] })
21243
+ }
21244
+ ),
21245
+ /* @__PURE__ */ jsxs(
21246
+ "div",
21247
+ {
21248
+ className: "p-2 space-y-2 justify-items-stretch static " + (showSelectorOptions ? "block" : "sm:hidden"),
21249
+ children: [
21250
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
21251
+ /* @__PURE__ */ jsx(
21252
+ "button",
21253
+ {
21254
+ onClick: () => setSelectionCoverage(0.5),
21255
+ className: "p-1 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
21256
+ disabled: creatingVariant,
21257
+ "aria-label": "Selección 50%",
21258
+ title: "Tamaño de selector 50%",
21259
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "50%" }) })
21260
+ }
21261
+ ),
21262
+ /* @__PURE__ */ jsx(
21263
+ "button",
21264
+ {
21265
+ onClick: () => setSelectionCoverage(0.7),
21266
+ className: "p-1 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
21267
+ disabled: creatingVariant,
21268
+ "aria-label": "Selección 70%",
21269
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "70%" }) })
21270
+ }
21271
+ ),
21272
+ /* @__PURE__ */ jsx(
21273
+ "button",
21274
+ {
21275
+ onClick: () => setSelectionCoverage(0.9),
21276
+ className: "p-1 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
21277
+ disabled: creatingVariant,
21278
+ "aria-label": "Selección 90%",
21279
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "90%" }) })
21280
+ }
21281
+ ),
21282
+ /* @__PURE__ */ jsx(
21283
+ "button",
21284
+ {
21285
+ onClick: () => setSelectionCoverage(1),
21286
+ className: "p-1 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
21287
+ disabled: creatingVariant,
21288
+ "aria-label": "Selección completa",
21289
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "100%" }) })
21290
+ }
21291
+ )
21292
+ ] }) }),
21293
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2 pt-2", children: [
21294
+ /* @__PURE__ */ jsx(
21295
+ "button",
21296
+ {
21297
+ onClick: centerSelection,
21298
+ className: "w-full p-1 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
21299
+ title: "Centrar selección",
21300
+ disabled: creatingVariant,
21301
+ "aria-label": "Centrar área de selección",
21302
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
21303
+ /* @__PURE__ */ jsx("span", { className: "icon icon-radio-button icon--sm align-[middle!important] " }),
21304
+ " ",
21305
+ "Centrar selección"
21306
+ ] }) })
21307
+ }
21308
+ ),
21309
+ /* @__PURE__ */ jsx(
21310
+ "button",
21311
+ {
21312
+ onClick: resetSelection,
21313
+ className: "w-full p-1 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
21314
+ title: "Reiniciar selección",
21315
+ disabled: creatingVariant,
21316
+ "aria-label": "Reiniciar área de selección",
21317
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
21318
+ /* @__PURE__ */ jsx("span", { className: "icon icon-refresh icon--sm lign-[middle!important] " }),
21319
+ " ",
21320
+ "Reset selección"
21321
+ ] }) })
21322
+ }
21323
+ )
21324
+ ] })
21325
+ ]
21326
+ }
21327
+ )
21328
+ ]
21329
+ }
21330
+ ),
21331
+ /* @__PURE__ */ jsxs("div", { className: "bg-white rounded border border-gray-200 w-full", children: [
21332
+ /* @__PURE__ */ jsx(
21333
+ "button",
21334
+ {
21335
+ onClick: toggleVisualOptions,
21336
+ className: "hidden sm:block w-full cursor-pointer hover:bg-neutral-gray-050/50 transition-colors rounded-t-sm mb-05 p-2",
21337
+ "aria-expanded": showVisualOptions,
21338
+ "aria-label": "Mostrar/ocultar opciones de visualización",
21339
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
21340
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Visualización" }),
21341
+ /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(
21342
+ "span",
20640
21343
  {
20641
- onClick: () => rotate(90),
20642
- className: "flex-1 p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20643
- title: "Rotar +90°",
20644
- disabled: creatingVariant,
20645
- "aria-label": "Rotar imagen 90 grados a la derecha",
20646
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-send-arrow-blue" }) })
21344
+ className: `icon ${!showVisualOptions ? "icon-chevron-down" : "icon-chevron-up"} text-center icon--xs`
20647
21345
  }
20648
- )
21346
+ ) })
20649
21347
  ] })
20650
- ] }),
20651
- /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
20652
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600 mb-2", children: "Voltear" }),
20653
- /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
20654
- /* @__PURE__ */ jsx(
21348
+ }
21349
+ ),
21350
+ /* @__PURE__ */ jsxs(
21351
+ "div",
21352
+ {
21353
+ className: "py-2 space-y-2 justify-items-stretch static " + (showVisualOptions ? "block" : "sm:hidden"),
21354
+ children: [
21355
+ /* @__PURE__ */ jsxs(
20655
21356
  "button",
20656
21357
  {
20657
- onClick: flipHorizontal,
20658
- className: `flex-1 p-2 rounded cursor-pointer transition-colors text-sm ${flipStates.horizontal ? "bg-blue-100 border border-blue-300 text-blue-800" : "bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200"}`,
20659
- title: `Voltear horizontalmente ${flipStates.horizontal ? "(activo)" : ""}`,
21358
+ onClick: toggleGrid,
21359
+ className: `w-auto flex cursor-pointer items-center justify-between mx-2 p-1 rounded transition-colors ${showGrid ? "bg-blue-100 border border-blue-300 text-blue-800" : "bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200"}`,
20660
21360
  disabled: creatingVariant,
20661
- "aria-label": "Voltear imagen horizontalmente",
20662
- "aria-pressed": flipStates.horizontal,
20663
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-right-blue border border-brand-blue-1000 rounded" }) })
21361
+ "aria-pressed": showGrid,
21362
+ title: "Mostrar/ocultar cuadrícula de la regla de los tercios en el selector",
21363
+ "aria-label": "Activar/desactivar grid",
21364
+ children: [
21365
+ /* @__PURE__ */ jsxs("span", { className: "text-sm flex items-center", children: [
21366
+ /* @__PURE__ */ jsx("span", { className: "icon icon-area-blue me-1" }),
21367
+ " ",
21368
+ "Cuadrícula"
21369
+ ] }),
21370
+ /* @__PURE__ */ jsx("span", { className: "text-xs", children: showGrid ? /* @__PURE__ */ jsxs(Fragment, { children: [
21371
+ /* @__PURE__ */ jsx("span", { className: "icon icon-tick icon--xs align-[middle!important] -mt-0.5" }),
21372
+ " ",
21373
+ "Activo"
21374
+ ] }) : "Inactivo" })
21375
+ ]
20664
21376
  }
20665
21377
  ),
20666
- /* @__PURE__ */ jsx(
21378
+ /* @__PURE__ */ jsxs(
20667
21379
  "button",
20668
21380
  {
20669
- onClick: flipVertical,
20670
- className: `flex-1 p-2 cursor-pointer rounded transition-colors text-sm ${flipStates.vertical ? "bg-blue-100 border border-blue-300 text-blue-800" : "bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200"}`,
20671
- title: `Voltear verticalmente ${flipStates.vertical ? "(activo)" : ""}`,
21381
+ onClick: toggleShade,
21382
+ className: `w-auto flex cursor-pointer items-center justify-between mx-2 p-1 rounded transition-colors ${shade ? "bg-blue-100 border border-blue-300 text-blue-800" : "bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200"}`,
20672
21383
  disabled: creatingVariant,
20673
- "aria-label": "Voltear imagen verticalmente",
20674
- "aria-pressed": flipStates.vertical,
20675
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-down-blue border border-brand-blue-1000 rounded" }) })
20676
- }
20677
- )
20678
- ] })
20679
- ] }),
20680
- /* @__PURE__ */ jsx(
20681
- "button",
20682
- {
20683
- onClick: () => {
20684
- transform.reset();
20685
- setFlipStates({ horizontal: false, vertical: false });
20686
- },
20687
- className: "w-full p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
20688
- title: "Reiniciar transformaciones",
20689
- disabled: creatingVariant,
20690
- "aria-label": "Reiniciar todas las transformaciones de la imagen",
20691
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
20692
- /* @__PURE__ */ jsx("span", { className: "icon icon-refresh-blue icon--sm align-[middle!important] " }),
20693
- " ",
20694
- "Reiniciar ajustes"
20695
- ] }) })
20696
- }
20697
- )
20698
- ] })
20699
- ] }),
20700
- /* @__PURE__ */ jsxs("div", { className: "bg-gradient-to-br from-brand-blue-50 to-green-50 rounded-lg border border-blue-200 shadow-sm hidden lg:block", children: [
20701
- /* @__PURE__ */ jsxs(
20702
- "button",
20703
- {
20704
- onClick: toggleTips,
20705
- className: "w-full p-4 flex items-center justify-between cursor-pointer hover:bg-blue-100/50 transition-colors rounded-t-lg",
20706
- "aria-expanded": showTips,
20707
- "aria-label": "Mostrar/ocultar guía de uso",
20708
- children: [
20709
- /* @__PURE__ */ jsxs("h3", { className: "text-lg font-semibold text-blue-800 flex items-center", children: [
20710
- /* @__PURE__ */ jsx("span", { className: "icon icon-lightbulb text-yellow-500 mr-2" }),
20711
- "Guía de uso"
20712
- ] }),
20713
- /* @__PURE__ */ jsx(
20714
- "span",
20715
- {
20716
- className: `icon icon-arrow-down-blue transition-transform duration-200 ${showTips ? "rotate-180" : ""}`
21384
+ "aria-pressed": shade,
21385
+ "aria-label": "Activar/desactivar sombreado",
21386
+ children: [
21387
+ /* @__PURE__ */ jsxs("span", { className: "text-sm flex items-center", children: [
21388
+ /* @__PURE__ */ jsx("span", { className: "icon icon-comparison-blue me-1" }),
21389
+ " ",
21390
+ "Tablero"
21391
+ ] }),
21392
+ /* @__PURE__ */ jsx("span", { className: "text-xs", children: shade ? /* @__PURE__ */ jsxs(Fragment, { children: [
21393
+ /* @__PURE__ */ jsx("span", { className: "icon icon-tick icon--xs align-[middle!important] -mt-0.5" }),
21394
+ " ",
21395
+ "Activo"
21396
+ ] }) : "Inactivo" })
21397
+ ]
20717
21398
  }
20718
21399
  )
20719
21400
  ]
20720
21401
  }
20721
- ),
20722
- showTips && /* @__PURE__ */ jsxs("div", { className: "px-4 pb-4 space-y-3", children: [
20723
- /* @__PURE__ */ jsxs("div", { className: "bg-white/60 rounded-md p-3 border-l-4 border-blue-400", children: [
20724
- /* @__PURE__ */ jsx("h4", { className: "font-semibold text-blue-700 mb-2 text-sm", children: "🖱️ Navegación básica" }),
20725
- /* @__PURE__ */ jsxs("div", { className: "text-sm text-blue-600 space-y-1", children: [
20726
- /* @__PURE__ */ jsxs("div", { children: [
20727
- "• ",
20728
- /* @__PURE__ */ jsx("strong", { children: "Arrastra la imagen:" }),
20729
- " Haz clic sobre la imagen del área de selección y arrastra"
20730
- ] }),
20731
- /* @__PURE__ */ jsxs("div", { children: [
20732
- "• ",
20733
- /* @__PURE__ */ jsx("strong", { children: "Zoom:" }),
20734
- " Usa la rueda del ratón (fuera del área de selección)"
20735
- ] }),
20736
- /* @__PURE__ */ jsxs("div", { children: [
20737
- "• ",
20738
- /* @__PURE__ */ jsx("strong", { children: "Vista completa:" }),
20739
- ' Botón "Reset zoom" para ajustar al tamaño óptimo'
20740
- ] })
20741
- ] })
20742
- ] }),
20743
- /* @__PURE__ */ jsxs("div", { className: "bg-white/60 rounded-md p-3 border-l-4 border-green-400", children: [
20744
- /* @__PURE__ */ jsx("h4", { className: "font-semibold text-green-700 mb-2 text-sm", children: "✂️ Área de selección" }),
20745
- /* @__PURE__ */ jsxs("div", { className: "text-sm text-green-600 space-y-1", children: [
20746
- /* @__PURE__ */ jsxs("div", { children: [
20747
- "• ",
20748
- /* @__PURE__ */ jsx("strong", { children: "Mover selección:" }),
20749
- " Arrastra desde el centro",
20750
- /* @__PURE__ */ jsx("span", { className: "icon bg-white icon-move align-[middle!important] mx-1 border border-green-600 rounded-full" })
20751
- ] }),
20752
- /* @__PURE__ */ jsxs("div", { children: [
20753
- "• ",
20754
- /* @__PURE__ */ jsx("strong", { children: "Redimensionar:" }),
20755
- " Arrastra desde las esquinas o bordes"
20756
- ] }),
20757
- /* @__PURE__ */ jsxs("div", { children: [
20758
- "• ",
20759
- /* @__PURE__ */ jsx("strong", { children: "Tamaños rápidos:" }),
20760
- " Usa los botones 50%, 70%, 90%, 100%"
20761
- ] })
20762
- ] })
20763
- ] }),
20764
- /* @__PURE__ */ jsxs("div", { className: "bg-white/60 rounded-md p-3 border-l-4 border-purple-400", children: [
20765
- /* @__PURE__ */ jsx("h4", { className: "font-semibold text-purple-700 mb-2 text-sm", children: "⌨️ Atajos de teclado" }),
20766
- /* @__PURE__ */ jsxs("div", { className: "text-sm text-purple-600 space-y-1", children: [
20767
- /* @__PURE__ */ jsxs("div", { children: [
20768
- "• ",
20769
- /* @__PURE__ */ jsx("strong", { children: "Flechas:" }),
20770
- /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-box rotate-180 mx-1" }),
20771
- /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-box mx-1" }),
20772
- /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-box -rotate-90 mx-1" }),
20773
- /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-box rotate-90 mx-1" }),
20774
- "Mueven la selección (10px)"
20775
- ] }),
20776
- /* @__PURE__ */ jsxs("div", { children: [
20777
- "• ",
20778
- /* @__PURE__ */ jsx("strong", { children: "Shift + Arrastrar:" }),
20779
- " Mantiene proporciones al redimensionar"
20780
- ] }),
20781
- /* @__PURE__ */ jsxs("div", { children: [
20782
- "• ",
20783
- /* @__PURE__ */ jsx("strong", { children: "Ctrl + Rueda:" }),
20784
- " Zoom más preciso"
20785
- ] })
20786
- ] })
20787
- ] }),
20788
- /* @__PURE__ */ jsxs("div", { className: "bg-white/60 rounded-md p-3 border-l-4 border-amber-400", children: [
20789
- /* @__PURE__ */ jsx("h4", { className: "font-semibold text-amber-700 mb-2 text-sm", children: "💡 Consejos profesionales" }),
20790
- /* @__PURE__ */ jsxs("div", { className: "text-sm text-amber-600 space-y-1", children: [
20791
- /* @__PURE__ */ jsxs("div", { children: [
20792
- "• ",
20793
- /* @__PURE__ */ jsx("strong", { children: "Vista previa:" }),
20794
- ' Usa el botón "Vista previa" antes de guardar'
20795
- ] }),
20796
- /* @__PURE__ */ jsxs("div", { children: [
20797
- "• ",
20798
- /* @__PURE__ */ jsx("strong", { children: "Cuadricula:" }),
20799
- " Actívalo para aplicar la regla de los tercios"
20800
- ] }),
20801
- /* @__PURE__ */ jsxs("div", { children: [
20802
- "• ",
20803
- /* @__PURE__ */ jsx("strong", { children: "Tablero:" }),
20804
- " Actívalo para cuadrar medjor las medidas o como ayuda visual para imagenes con transparencia"
20805
- ] }),
20806
- /* @__PURE__ */ jsxs("div", { children: [
20807
- "• ",
20808
- /* @__PURE__ */ jsx("strong", { children: "Transformaciones:" }),
20809
- " Rota y voltea antes de hacer el recorte final"
20810
- ] }),
20811
- /* @__PURE__ */ jsxs("div", { children: [
20812
- "• ",
20813
- /* @__PURE__ */ jsx("strong", { children: "Calidad:" }),
20814
- " Las imágenes se exportan en alta calidad (JPEG 90%)"
20815
- ] })
20816
- ] })
20817
- ] }),
20818
- /* @__PURE__ */ jsxs("div", { className: "bg-white/60 rounded-md p-3 border-l-4 border-orange-600", children: [
20819
- /* @__PURE__ */ jsx("h4", { className: "font-semibold text-orange-700 mb-2 text-sm", children: "⚠️ Advertencias" }),
20820
- /* @__PURE__ */ jsxs("div", { className: "text-sm text-orange-600 space-y-1", children: [
20821
- /* @__PURE__ */ jsxs("div", { children: [
20822
- "• ",
20823
- /* @__PURE__ */ jsx("strong", { children: "Recortes grandes:" }),
20824
- " Pueden tardar más en procesarse"
20825
- ] }),
20826
- /* @__PURE__ */ jsxs("div", { children: [
20827
- "• ",
20828
- /* @__PURE__ */ jsx("strong", { children: "Calidad de imagen:" }),
20829
- " La calidad puede verse afectada al escalar"
20830
- ] })
20831
- ] })
20832
- ] })
20833
- ] })
21402
+ )
20834
21403
  ] })
20835
21404
  ] }) })
20836
21405
  ] }),
20837
- /* @__PURE__ */ jsx("div", { className: "limbo-cropper-footer flex-shrink-0 border-t border-gray-200 bg-white p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-2 sm:gap-3 items-stretch sm:items-center justify-end max-w-7xl mx-auto", children: [
20838
- /* @__PURE__ */ jsx(
20839
- "button",
20840
- {
20841
- onClick: preview,
20842
- disabled: creatingVariant || !canExport,
20843
- className: `px-6 py-2.5 min-h-[44px] transition-colors order-2 ${showPreview ? "limbo-btn limbo-btn-danger" : "limbo-btn limbo-btn-info"}`,
20844
- "aria-label": "Generar vista previa del recorte",
20845
- title: "Mostar/Ocultar vista previa del recorte",
20846
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center", children: [
20847
- /* @__PURE__ */ jsx("span", { className: `icon md:mr-1 icon-search-white` }),
20848
- /* @__PURE__ */ jsx("span", { className: "hidden md:block text-nowrap", children: showPreview ? "Cerrar previa" : "Vista previa" })
20849
- ] })
20850
- }
20851
- ),
20852
- /* @__PURE__ */ jsx(
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: [
21407
+ /* @__PURE__ */ jsxs(
20853
21408
  "button",
20854
21409
  {
20855
- onClick: handleDownload,
20856
- disabled: creatingVariant || !canExport,
20857
- className: "limbo-btn limbo-btn-primary px-6 py-2.5 min-h-[44px] order-3",
20858
- "aria-label": "Descargar recorte sin guardar",
20859
- title: "Descargar recorte sin guardar",
20860
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center", children: [
20861
- /* @__PURE__ */ jsx("span", { className: "icon icon-download-white md:mr-1" }),
20862
- /* @__PURE__ */ jsx("span", { className: "hidden md:block text-nowrap", children: "Descargar" })
20863
- ] })
21410
+ onClick: () => setShowTipsModal(true),
21411
+ className: "text-nowrap block cursor-pointer lg:flex items-center gap-2 px-2 py-1 bg-gradient-to-br from-brand-blue-50 to-green-50 hover:from-brand-blue-100 hover:to-green-100 border border-blue-200 rounded-lg shadow-sm transition-colors",
21412
+ "aria-label": "Abrir guía de uso",
21413
+ children: [
21414
+ /* @__PURE__ */ jsx("span", { className: "icon icon-lightbulb text-yellow-500" }),
21415
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-blue-800", children: "Guía de uso" }),
21416
+ /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-down scale-75 -rotate-145" })
21417
+ ]
20864
21418
  }
20865
21419
  ),
20866
- /* @__PURE__ */ jsx(
20867
- "button",
20868
- {
20869
- onClick: saveCrop,
20870
- disabled: creatingVariant || !cropData || !effectiveImageInfo || !canExport || !state.isReady,
20871
- className: "limbo-btn limbo-btn-success px-6 py-2.5 min-h-[44px] order-4",
20872
- "aria-label": "Guardar imagen recortada",
20873
- title: "Guardar imagen recortada",
20874
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: creatingVariant ? /* @__PURE__ */ jsxs(Fragment, { children: [
20875
- /* @__PURE__ */ jsx("span", { className: "hidden md:block text-nowrap icon icon-save-white md:mr-1" }),
20876
- "Guardando..."
20877
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
20878
- /* @__PURE__ */ jsx("span", { className: "icon icon-save-white md:mr-1" }),
20879
- /* @__PURE__ */ jsx("span", { className: "hidden md:block text-nowrap", children: "Guardar recorte" })
20880
- ] }) })
20881
- }
20882
- )
20883
- ] }) }),
21420
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-2 items-center justify-end", children: [
21421
+ /* @__PURE__ */ jsx(
21422
+ "button",
21423
+ {
21424
+ onClick: preview,
21425
+ 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",
21429
+ 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" })
21432
+ ] })
21433
+ }
21434
+ ),
21435
+ /* @__PURE__ */ jsx(
21436
+ "button",
21437
+ {
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",
21443
+ 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" })
21446
+ ] })
21447
+ }
21448
+ ),
21449
+ /* @__PURE__ */ jsx(
21450
+ "button",
21451
+ {
21452
+ onClick: saveCrop,
21453
+ disabled: creatingVariant || !cropData || !effectiveImageInfo || !canExport || !state.isReady,
21454
+ 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
+ title: "Guardar imagen recortada",
21457
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: creatingVariant ? /* @__PURE__ */ jsxs(Fragment, { children: [
21458
+ /* @__PURE__ */ jsx("span", { className: "icon icon-save-white icon--sm" }),
21459
+ /* @__PURE__ */ jsx("span", { children: "Guardando..." })
21460
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
21461
+ /* @__PURE__ */ jsx("span", { className: "icon icon-save-white icon--sm" }),
21462
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline whitespace-nowrap", children: "Guardar recorte" })
21463
+ ] }) })
21464
+ }
21465
+ )
21466
+ ] })
21467
+ ] }),
20884
21468
  showConfirmModal && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[9999] p-4", children: /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg shadow-xl max-w-lg w-full max-h-[80vh] overflow-hidden flex flex-col", children: [
20885
21469
  /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 border-b border-gray-200", children: [
20886
21470
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-800", children: pendingAction === "save" ? "Seleccionar recortes a guardar" : "Seleccionar recortes a descargar" }),
20887
21471
  /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-600 mt-1", children: [
20888
- "Marca los recortes que deseas ",
21472
+ "Marca los recortes que deseas",
21473
+ " ",
20889
21474
  pendingAction === "save" ? "guardar" : "descargar"
20890
21475
  ] })
20891
21476
  ] }),
20892
- /* @__PURE__ */ jsx("div", { className: "px-6 py-4 overflow-y-auto flex-1", children: /* @__PURE__ */ jsx("div", { className: "space-y-2", children: crops.map((crop, index) => /* @__PURE__ */ jsxs(
20893
- "label",
20894
- {
20895
- className: `flex items-center gap-3 p-3 rounded border cursor-pointer transition-colors ${selectedCropsForAction.includes(index) ? "bg-blue-50 border-blue-300" : "bg-gray-50 border-gray-200 hover:bg-gray-100"} ${crop.required ? "opacity-75" : ""}`,
20896
- children: [
20897
- /* @__PURE__ */ jsx(
20898
- "input",
20899
- {
20900
- type: "checkbox",
20901
- checked: selectedCropsForAction.includes(index),
20902
- disabled: crop.required,
20903
- onChange: (e) => {
20904
- if (e.target.checked) {
20905
- setSelectedCropsForAction((prev) => [...prev, index]);
20906
- } else {
20907
- setSelectedCropsForAction((prev) => prev.filter((i) => i !== index));
20908
- }
20909
- },
20910
- className: "w-4 h-4 text-blue-600"
20911
- }
20912
- ),
20913
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
20914
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
20915
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-800 truncate", children: crop.label }),
20916
- crop.required && /* @__PURE__ */ jsx("span", { className: "text-xs px-1.5 py-0.5 bg-red-100 text-red-700 rounded", children: "Obligatorio" })
20917
- ] }),
20918
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500", children: [
20919
- crop.width,
20920
- " × ",
20921
- crop.height,
20922
- " px"
21477
+ /* @__PURE__ */ jsx("div", { className: "px-6 py-4 overflow-y-auto flex-1", children: /* @__PURE__ */ jsx("div", { className: "space-y-2", children: crops.map((crop, index) => {
21478
+ const isConfigured = crop.savedState || index === activeCropIndex;
21479
+ const isDisabled = crop.required ? false : !isConfigured;
21480
+ return /* @__PURE__ */ jsxs(
21481
+ "label",
21482
+ {
21483
+ className: `flex items-center gap-3 p-3 rounded border transition-colors ${selectedCropsForAction.includes(index) ? "bg-blue-50 border-blue-300" : isDisabled ? "bg-gray-100 border-gray-300 opacity-60" : "bg-gray-50 border-gray-200 hover:bg-gray-100"} ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"}`,
21484
+ children: [
21485
+ /* @__PURE__ */ jsx(
21486
+ "input",
21487
+ {
21488
+ type: "checkbox",
21489
+ checked: selectedCropsForAction.includes(index),
21490
+ disabled: isDisabled,
21491
+ onChange: (e) => {
21492
+ if (e.target.checked) {
21493
+ setSelectedCropsForAction((prev) => [
21494
+ ...prev,
21495
+ index
21496
+ ]);
21497
+ } else {
21498
+ setSelectedCropsForAction(
21499
+ (prev) => prev.filter((i) => i !== index)
21500
+ );
21501
+ }
21502
+ },
21503
+ className: "w-4 h-4 text-blue-600"
21504
+ }
21505
+ ),
21506
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
21507
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
21508
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-800 truncate", children: crop.label }),
21509
+ crop.required && /* @__PURE__ */ jsx("span", { className: "text-xs px-1.5 py-0.5 bg-red-100 text-red-700 rounded whitespace-nowrap", children: "Obligatorio" }),
21510
+ !isConfigured && !crop.required && /* @__PURE__ */ jsx("span", { className: "text-xs px-1.5 py-0.5 bg-orange-100 text-orange-700 rounded whitespace-nowrap", children: "Sin configurar" })
21511
+ ] }),
21512
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500", children: [
21513
+ crop.width,
21514
+ " × ",
21515
+ crop.height,
21516
+ " px"
21517
+ ] })
20923
21518
  ] })
20924
- ] })
20925
- ]
20926
- },
20927
- crop.id
20928
- )) }) }),
21519
+ ]
21520
+ },
21521
+ crop.id
21522
+ );
21523
+ }) }) }),
20929
21524
  /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 border-t border-gray-200 flex gap-3 justify-end", children: [
20930
21525
  /* @__PURE__ */ jsx(
20931
21526
  "button",
@@ -20945,9 +21540,27 @@ function CropperView({
20945
21540
  onClick: async () => {
20946
21541
  setShowConfirmModal(false);
20947
21542
  if (pendingAction === "save") {
20948
- await performSaveCrop();
21543
+ if (selectedCropsForAction.length === 1) {
21544
+ const oldIndex = activeCropIndex;
21545
+ setActiveCropIndex(selectedCropsForAction[0]);
21546
+ await new Promise((resolve) => setTimeout(resolve, 100));
21547
+ await performSaveCrop();
21548
+ setActiveCropIndex(oldIndex);
21549
+ } else {
21550
+ await performSaveMultipleCrops(selectedCropsForAction);
21551
+ }
20949
21552
  } else {
20950
- await performDownload();
21553
+ if (selectedCropsForAction.length === 1) {
21554
+ const oldIndex = activeCropIndex;
21555
+ setActiveCropIndex(selectedCropsForAction[0]);
21556
+ await new Promise((resolve) => setTimeout(resolve, 100));
21557
+ await performDownload();
21558
+ setActiveCropIndex(oldIndex);
21559
+ } else {
21560
+ await performDownloadMultipleCrops(
21561
+ selectedCropsForAction
21562
+ );
21563
+ }
20951
21564
  }
20952
21565
  setPendingAction(null);
20953
21566
  setSelectedCropsForAction([]);
@@ -20963,6 +21576,204 @@ function CropperView({
20963
21576
  }
20964
21577
  )
20965
21578
  ] })
21579
+ ] }) }),
21580
+ showTipsModal && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[9999] p-4", children: /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg shadow-xl max-w-3xl w-full max-h-[90vh] overflow-hidden flex flex-col", children: [
21581
+ /* @__PURE__ */ jsx("div", { className: "px-6 py-4 border-b border-gray-200 bg-gradient-to-r from-brand-blue-50 to-green-50", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
21582
+ /* @__PURE__ */ jsxs("h3", { className: "text-xl font-semibold text-blue-800 flex items-center gap-2", children: [
21583
+ /* @__PURE__ */ jsx("span", { className: "icon icon-lightbulb text-yellow-500" }),
21584
+ "Guía de uso del Cropper"
21585
+ ] }),
21586
+ /* @__PURE__ */ jsx(
21587
+ "button",
21588
+ {
21589
+ onClick: () => setShowTipsModal(false),
21590
+ className: "text-gray-400 hover:text-gray-600 p-1 rounded-full hover:bg-gray-100 transition-colors",
21591
+ "aria-label": "Cerrar modal",
21592
+ children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small icon--lg" })
21593
+ }
21594
+ )
21595
+ ] }) }),
21596
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 overflow-y-auto flex-1 space-y-4", children: [
21597
+ /* @__PURE__ */ jsxs("div", { className: "bg-blue-50 rounded-lg p-4 border-l-4 border-blue-400", children: [
21598
+ /* @__PURE__ */ jsx("h4", { className: "font-semibold text-blue-800 mb-3 text-base flex items-center gap-2", children: "🖱️ Navegación básica" }),
21599
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-blue-700 space-y-2", children: [
21600
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21601
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21602
+ /* @__PURE__ */ jsxs("div", { children: [
21603
+ /* @__PURE__ */ jsx("strong", { children: "Arrastra la imagen:" }),
21604
+ " Haz clic sobre la imagen dentro del área de selección y arrástrala para reposicionarla"
21605
+ ] })
21606
+ ] }),
21607
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21608
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21609
+ /* @__PURE__ */ jsxs("div", { children: [
21610
+ /* @__PURE__ */ jsx("strong", { children: "Zoom:" }),
21611
+ " Usa la rueda del ratón sobre el canvas (fuera del área de selección) para hacer zoom"
21612
+ ] })
21613
+ ] }),
21614
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21615
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21616
+ /* @__PURE__ */ jsxs("div", { children: [
21617
+ /* @__PURE__ */ jsx("strong", { children: "Vista completa:" }),
21618
+ ' Botón "Reset zoom" para ajustar automáticamente al tamaño óptimo'
21619
+ ] })
21620
+ ] })
21621
+ ] })
21622
+ ] }),
21623
+ /* @__PURE__ */ jsxs("div", { className: "bg-green-50 rounded-lg p-4 border-l-4 border-green-400", children: [
21624
+ /* @__PURE__ */ jsx("h4", { className: "font-semibold text-green-800 mb-3 text-base flex items-center gap-2", children: "✂️ Área de selección" }),
21625
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-green-700 space-y-2", children: [
21626
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21627
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21628
+ /* @__PURE__ */ jsxs("div", { children: [
21629
+ /* @__PURE__ */ jsx("strong", { children: "Mover selección:" }),
21630
+ " Arrastra desde el centro",
21631
+ /* @__PURE__ */ jsx("span", { className: "icon bg-white icon-move inline-block mx-1 align-middle border border-green-600 rounded-full" }),
21632
+ "para reposicionar el área de recorte"
21633
+ ] })
21634
+ ] }),
21635
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21636
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21637
+ /* @__PURE__ */ jsxs("div", { children: [
21638
+ /* @__PURE__ */ jsx("strong", { children: "Redimensionar:" }),
21639
+ " Arrastra desde las esquinas o bordes para cambiar el tamaño del área"
21640
+ ] })
21641
+ ] }),
21642
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21643
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21644
+ /* @__PURE__ */ jsxs("div", { children: [
21645
+ /* @__PURE__ */ jsx("strong", { children: "Tamaños rápidos:" }),
21646
+ " Usa los botones de porcentaje (50%, 70%, 90%, 100%) para ajustar rápidamente el tamaño"
21647
+ ] })
21648
+ ] })
21649
+ ] })
21650
+ ] }),
21651
+ /* @__PURE__ */ jsxs("div", { className: "bg-purple-50 rounded-lg p-4 border-l-4 border-purple-400", children: [
21652
+ /* @__PURE__ */ jsx("h4", { className: "font-semibold text-purple-800 mb-3 text-base flex items-center gap-2", children: "⌨️ Atajos de teclado" }),
21653
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-purple-700 space-y-2", children: [
21654
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21655
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21656
+ /* @__PURE__ */ jsxs("div", { children: [
21657
+ /* @__PURE__ */ jsx("strong", { children: "Flechas:" }),
21658
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex gap-1 mx-1", children: [
21659
+ /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-box rotate-180 icon--sm" }),
21660
+ /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-box icon--sm" }),
21661
+ /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-box -rotate-90 icon--sm" }),
21662
+ /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-box rotate-90 icon--sm" })
21663
+ ] }),
21664
+ "Mueven la selección en incrementos de 10px"
21665
+ ] })
21666
+ ] }),
21667
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21668
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21669
+ /* @__PURE__ */ jsxs("div", { children: [
21670
+ /* @__PURE__ */ jsx("strong", { children: "Shift + Arrastrar:" }),
21671
+ " Mantiene las proporciones al redimensionar el área de selección"
21672
+ ] })
21673
+ ] }),
21674
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21675
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21676
+ /* @__PURE__ */ jsxs("div", { children: [
21677
+ /* @__PURE__ */ jsx("strong", { children: "Ctrl + Rueda:" }),
21678
+ " Control de zoom más preciso y fino"
21679
+ ] })
21680
+ ] })
21681
+ ] })
21682
+ ] }),
21683
+ /* @__PURE__ */ jsxs("div", { className: "bg-cyan-50 rounded-lg p-4 border-l-4 border-cyan-400", children: [
21684
+ /* @__PURE__ */ jsx("h4", { className: "font-semibold text-cyan-800 mb-3 text-base flex items-center gap-2", children: "👁️ Opciones de visualización" }),
21685
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-cyan-700 space-y-2", children: [
21686
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21687
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21688
+ /* @__PURE__ */ jsxs("div", { children: [
21689
+ /* @__PURE__ */ jsx("strong", { children: "Cuadrícula:" }),
21690
+ " Muestra la regla de los tercios para mejorar la composición de tus recortes"
21691
+ ] })
21692
+ ] }),
21693
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21694
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21695
+ /* @__PURE__ */ jsxs("div", { children: [
21696
+ /* @__PURE__ */ jsx("strong", { children: "Tablero:" }),
21697
+ " Patrón de cuadros para alinear mejor las medidas o ver imágenes con transparencia"
21698
+ ] })
21699
+ ] }),
21700
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21701
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21702
+ /* @__PURE__ */ jsxs("div", { children: [
21703
+ /* @__PURE__ */ jsx("strong", { children: "Vista previa:" }),
21704
+ " Muestra cómo quedará el recorte final antes de guardarlo"
21705
+ ] })
21706
+ ] })
21707
+ ] })
21708
+ ] }),
21709
+ /* @__PURE__ */ jsxs("div", { className: "bg-amber-50 rounded-lg p-4 border-l-4 border-amber-400", children: [
21710
+ /* @__PURE__ */ jsx("h4", { className: "font-semibold text-amber-800 mb-3 text-base flex items-center gap-2", children: "💡 Consejos profesionales" }),
21711
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-amber-700 space-y-2", children: [
21712
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21713
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21714
+ /* @__PURE__ */ jsxs("div", { children: [
21715
+ /* @__PURE__ */ jsx("strong", { children: "Orden de trabajo:" }),
21716
+ " Primero aplica transformaciones (rotar, voltear), luego haz el recorte final"
21717
+ ] })
21718
+ ] }),
21719
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21720
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21721
+ /* @__PURE__ */ jsxs("div", { children: [
21722
+ /* @__PURE__ */ jsx("strong", { children: "Múltiples recortes:" }),
21723
+ " Si tienes varios recortes configurados, revisa cada uno antes de guardar"
21724
+ ] })
21725
+ ] }),
21726
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21727
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21728
+ /* @__PURE__ */ jsxs("div", { children: [
21729
+ /* @__PURE__ */ jsx("strong", { children: "Calidad:" }),
21730
+ " Las imágenes se exportan en alta calidad (JPEG 90%) manteniendo la mayor nitidez posible"
21731
+ ] })
21732
+ ] }),
21733
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21734
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21735
+ /* @__PURE__ */ jsxs("div", { children: [
21736
+ /* @__PURE__ */ jsx("strong", { children: "Proporciones:" }),
21737
+ " Usa Shift al redimensionar para mantener el aspecto ratio original"
21738
+ ] })
21739
+ ] })
21740
+ ] })
21741
+ ] }),
21742
+ /* @__PURE__ */ jsxs("div", { className: "bg-orange-50 rounded-lg p-4 border-l-4 border-orange-500", children: [
21743
+ /* @__PURE__ */ jsx("h4", { className: "font-semibold text-orange-800 mb-3 text-base flex items-center gap-2", children: "⚠️ Notas importantes" }),
21744
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-orange-700 space-y-2", children: [
21745
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21746
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21747
+ /* @__PURE__ */ jsxs("div", { children: [
21748
+ /* @__PURE__ */ jsx("strong", { children: "Recortes grandes:" }),
21749
+ " Imágenes de alta resolución pueden tardar más en procesarse"
21750
+ ] })
21751
+ ] }),
21752
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21753
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21754
+ /* @__PURE__ */ jsxs("div", { children: [
21755
+ /* @__PURE__ */ jsx("strong", { children: "Calidad de escala:" }),
21756
+ " Evita ampliar demasiado áreas pequeñas, ya que puede afectar la nitidez"
21757
+ ] })
21758
+ ] }),
21759
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
21760
+ /* @__PURE__ */ jsx("span", { className: "font-bold min-w-fit", children: "•" }),
21761
+ /* @__PURE__ */ jsxs("div", { children: [
21762
+ /* @__PURE__ */ jsx("strong", { children: "Cambios pendientes:" }),
21763
+ " Recuerda guardar tus cambios antes de cerrar o cambiar de imagen"
21764
+ ] })
21765
+ ] })
21766
+ ] })
21767
+ ] })
21768
+ ] }),
21769
+ /* @__PURE__ */ jsx("div", { className: "px-6 py-4 border-t border-gray-200 bg-gray-50", children: /* @__PURE__ */ jsx(
21770
+ "button",
21771
+ {
21772
+ onClick: () => setShowTipsModal(false),
21773
+ className: "limbo-btn limbo-btn-primary w-full",
21774
+ children: "Entendido"
21775
+ }
21776
+ ) })
20966
21777
  ] }) })
20967
21778
  ] });
20968
21779
  }
@@ -21179,15 +21990,19 @@ function useDeleteImage() {
21179
21990
  }
21180
21991
  const cache = /* @__PURE__ */ new Map();
21181
21992
  const CACHE_TTL = 10 * 60 * 1e3;
21182
- function useImages(apiKey, prod = false, params = {}) {
21993
+ function useImages(apiKey, prod = false, params = {}, enabled = true) {
21183
21994
  const [images, setImages] = useState([]);
21184
- const [loading, setLoading] = useState(true);
21995
+ const [loading, setLoading] = useState(enabled);
21185
21996
  const [error, setError] = useState(null);
21186
21997
  const [pagination, setPagination] = useState(null);
21187
21998
  const [retryCount, setRetryCount] = useState(0);
21188
21999
  const MAX_RETRIES = 3;
21189
22000
  const paramsKey = useMemo(() => JSON.stringify(params), [params]);
21190
22001
  useEffect(() => {
22002
+ if (!enabled) {
22003
+ setLoading(false);
22004
+ return;
22005
+ }
21191
22006
  const cacheKey = `${paramsKey}`;
21192
22007
  const cached = cache.get(cacheKey);
21193
22008
  const now = Date.now();
@@ -21233,7 +22048,7 @@ function useImages(apiKey, prod = false, params = {}) {
21233
22048
  return () => {
21234
22049
  isMounted = false;
21235
22050
  };
21236
- }, [apiKey, prod, paramsKey, retryCount, params]);
22051
+ }, [apiKey, prod, paramsKey, retryCount, params, enabled]);
21237
22052
  const invalidateCache = () => {
21238
22053
  cache.delete(`${paramsKey}`);
21239
22054
  };
@@ -21385,6 +22200,7 @@ function App({
21385
22200
  ...galleryFilters.dateFrom && { dateFrom: galleryFilters.dateFrom },
21386
22201
  ...galleryFilters.dateTo && { dateTo: galleryFilters.dateTo }
21387
22202
  };
22203
+ const shouldFetchImages = activeFeatures.includes("gallery");
21388
22204
  const {
21389
22205
  images,
21390
22206
  loading: loadingImages,
@@ -21392,7 +22208,7 @@ function App({
21392
22208
  pagination,
21393
22209
  invalidateCache,
21394
22210
  setImages
21395
- } = useImages(apiKey, prod, apiParams);
22211
+ } = useImages(apiKey, prod, apiParams, shouldFetchImages);
21396
22212
  const { refreshVariants } = useImageVariants();
21397
22213
  const handleVariantCreated = (assetId, variantData) => {
21398
22214
  refreshVariants(assetId);
@@ -21614,7 +22430,7 @@ function App({
21614
22430
  }
21615
22431
  console.error("Cropper error:", error);
21616
22432
  };
21617
- return /* @__PURE__ */ jsxs("div", { className: "limbo-card-parent max-w-7xl mx-auto my-8 p-6 shadow-xs w-full bg-white", children: [
22433
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
21618
22434
  ui.showTabs && activeTab != "cropper" && /* @__PURE__ */ jsx(Tabs, { tabs, active: activeTab, onChange: handleTabChange }),
21619
22435
  imagesError && /* @__PURE__ */ jsxs("div", { className: "alert alert-danger", children: [
21620
22436
  "Error al cargar imágenes: ",
@@ -21993,7 +22809,7 @@ class LimboInstance {
21993
22809
  "div",
21994
22810
  {
21995
22811
  id: `limbo-component-container-${this.id}`,
21996
- className: "limbo-instance limbo-component-container-wrapper py-3",
22812
+ className: "limbo-instance limbo-component-container-wrapper h-full w-full py-2 bg-white rounded-md",
21997
22813
  "data-limbo-id": this.id,
21998
22814
  "data-limbo-mode": this.config.mode,
21999
22815
  "data-limbo-version": "2.0",
@@ -23213,8 +24029,8 @@ class Modal {
23213
24029
  const sizeClasses = {
23214
24030
  small: isMobile ? "width: 100%; height: 100%;" : "max-width: 400px;",
23215
24031
  medium: isMobile ? "width: 100%; height: 100%;" : "max-width: 600px;",
23216
- large: isMobile ? "width: 100%; height: 100%;" : "max-width: 800px;",
23217
- xlarge: isMobile ? "width: 100%; height: 100%;" : "max-width: 1200px;"
24032
+ large: isMobile ? "width: 100%; height: 100%;" : "max-width: 1300px;",
24033
+ xlarge: isMobile ? "width: 100%; height: 100%;" : "max-width: 90dvw;"
23218
24034
  };
23219
24035
  const baseStyles = isMobile ? `
23220
24036
  position: fixed;
@@ -24850,7 +25666,9 @@ class LimboCore {
24850
25666
  token: options.token,
24851
25667
  // Solo para authMode: "manual"
24852
25668
  authMode: options.authMode || "manual",
24853
- // "session" | "manual"
25669
+ // "session" | "manual" | "jwt"
25670
+ tokenProvider: options.tokenProvider,
25671
+ // Custom function to provide JWT token
24854
25672
  prod: options.prod || false,
24855
25673
  mode: options.mode || "embed",
24856
25674
  // "embed" | "modal"
@@ -25309,7 +26127,7 @@ const Limbo = new LimboCore();
25309
26127
  if (typeof window !== "undefined") {
25310
26128
  window.Limbo = Limbo;
25311
26129
  }
25312
- const PUBLIC_KEY = "pk_e464fd744106b7a8d63d453c4bd02582";
26130
+ const PUBLIC_KEY = "pk_d2edad56de145fee22c8b80f6ce3448f";
25313
26131
  if (typeof window !== "undefined" && document.querySelector("#root")) {
25314
26132
  Limbo.configure({
25315
26133
  prod: false,