limbo-component 1.6.6 → 1.8.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
@@ -31,7 +31,7 @@ const DEFAULT_MESSAGES = {
31
31
  "message.no_images": "No hay imágenes disponibles",
32
32
  "message.upload_success": "Imagen subida correctamente",
33
33
  "message.upload_error": "Error al subir la imagen",
34
- "message.delete_confirm": "¿Estás seguro de que quieres eliminar esta imagen?",
34
+ "message.delete_confirm": "¿Estás seguro de que deseas eliminar esta imagen? Esta acción también eliminará todos sus recortes.",
35
35
  "message.copy_success": "URL copiada al portapapeles",
36
36
  "message.copy_error": "Error al copiar URL",
37
37
  // Errores
@@ -219,17 +219,70 @@ class ConfigManager {
219
219
  // libre por defecto
220
220
  lockAspectRatio: false,
221
221
  // Aspect ratios permitidos - formato: { label, value, ratio }
222
- allowedAspectRatios: [
223
- { label: "📐 Libre", value: "", ratio: null },
224
- { label: "⬜ 1:1 (Cuadrado)", value: "1", ratio: 1 },
225
- { label: "📺 4:3 (Clásico)", value: "4/3", ratio: 4 / 3 },
226
- { label: "🖥️ 16:9 (Widescreen)", value: "16/9", ratio: 16 / 9 },
227
- { label: "📷 3:2 (Foto)", value: "3/2", ratio: 3 / 2 },
228
- { label: "📱 2:3 (Retrato)", value: "2/3", ratio: 2 / 3 },
229
- { label: "📲 9:16 (Stories)", value: "9/16", ratio: 9 / 16 }
230
- ],
231
- showFreeAspectRatio: true
222
+ showFreeAspectRatio: true,
232
223
  // Permitir aspecto libre
224
+ // Nuevas opciones avanzadas
225
+ showDimensionInputs: false,
226
+ // Si true, muestra inputs width x height en lugar de aspect ratio
227
+ enableEditMode: true,
228
+ // Si permite editar variantes existentes
229
+ showDownloadInCropper: false,
230
+ // Si muestra botón de descarga en el cropper
231
+ showCropName: false,
232
+ // Si muestra campo de nombre de recorte editable
233
+ // Recortes obligatorios - Array de objetos con {label, width, height, required}
234
+ mandatoryCrops: [],
235
+ /* Ejemplo de mandatoryCrops:
236
+ [
237
+ { label: "Thumbnail", width: 300, height: 300, required: true },
238
+ { label: "Header", width: 1920, height: 400, required: true },
239
+ { label: "Mobile", width: 750, height: 1334, required: false }
240
+ ]
241
+ */
242
+ allowCustomCrops: true
243
+ // Permitir que el usuario añada recortes personalizados además de los obligatorios
244
+ },
245
+ // Configuración de galería
246
+ gallery: {
247
+ // Configuración de filtros
248
+ filters: {
249
+ showNameFilter: true,
250
+ // Mostrar filtro por nombre de archivo
251
+ showUploadedByFilter: false,
252
+ // Mostrar filtro por usuario que subió (oculto por defecto)
253
+ showDateFilter: true,
254
+ // Mostrar filtros de rango de fechas
255
+ enabledFilters: ["name", "date"],
256
+ // Filtros activos por defecto
257
+ // Filtros personalizados adicionales
258
+ customFilters: []
259
+ /* Ejemplo de customFilters:
260
+ [
261
+ {
262
+ key: "tag",
263
+ label: "Etiqueta",
264
+ type: "select", // select | text | date | range
265
+ options: ["producto", "banner", "avatar"]
266
+ }
267
+ ]
268
+ */
269
+ },
270
+ // Configuración de carga
271
+ loading: {
272
+ showPlaceholders: true,
273
+ // Mostrar placeholders mientras carga
274
+ placeholderCount: 10,
275
+ // Número de placeholders a mostrar
276
+ showSpinner: true
277
+ // Mostrar spinner de carga
278
+ },
279
+ // Configuración de paginación
280
+ pagination: {
281
+ itemsPerPage: 20,
282
+ // Elementos por página
283
+ showPageSize: true
284
+ // Permitir cambiar el tamaño de página
285
+ }
233
286
  },
234
287
  // Validación de archivos
235
288
  validation: {
@@ -240,6 +293,9 @@ class ConfigManager {
240
293
  maxWidth: null,
241
294
  maxHeight: null
242
295
  },
296
+ // Configuración de descarga
297
+ downloadFormat: "webp",
298
+ // webp | jpeg | jpg | png - Format for downloaded images
243
299
  // Configuración de retorno
244
300
  return: {
245
301
  format: "url",
@@ -290,9 +346,33 @@ class ConfigManager {
290
346
  showCloseButton: true
291
347
  },
292
348
  // Callbacks por defecto
293
- callbacks: {},
349
+ callbacks: {
350
+ // Callback cuando se sube una imagen
351
+ onUpload: null,
352
+ // (data) => { assetId, url, fileName, mime, width, height, instanceId }
353
+ // Callback cuando se selecciona una imagen
354
+ onSelect: null,
355
+ // (data) => { assetId, url, fileName, mime, width, height, instanceId }
356
+ // Callback cuando se elimina una imagen
357
+ onDelete: null,
358
+ // (assetId, instanceId) => void
359
+ // NUEVO: Callback cuando se guardan recortes/variantes
360
+ onCropsSaved: null,
361
+ // (data) => { crops: Array, assetId, instanceId }
362
+ // NUEVO: Callback cuando se completa el cropper (modo crop-only)
363
+ onCropperComplete: null,
364
+ // (data) => { crops: Array, instanceId }
365
+ // NUEVO: Callback cuando se cancela el cropper sin guardar
366
+ onCropperCancelled: null,
367
+ // (data) => { assetId?, instanceId }
368
+ // NUEVO: Callback cuando ocurre un error en el cropper
369
+ onCropperError: null
370
+ // (error, instanceId) => void
371
+ },
294
372
  // Configuración avanzada
295
373
  autoDestroy: false,
374
+ autoHideOnComplete: false,
375
+ // Si true, oculta el componente al completar (solo embed mode)
296
376
  debug: false,
297
377
  apiEndpoint: null,
298
378
  // ========== CONFIGURACIÓN DE AUTENTICACIÓN JWT V2 ==========
@@ -453,17 +533,34 @@ class ConfigManager {
453
533
  aspectRatio: null,
454
534
  lockAspectRatio: false,
455
535
  // Aspect ratios permitidos - formato: { label, value, ratio }
456
- allowedAspectRatios: [
457
- { label: "📐 Libre", value: "", ratio: null },
458
- { label: "⬜ 1:1 (Cuadrado)", value: "1", ratio: 1 },
459
- { label: "📺 4:3 (Clásico)", value: "4/3", ratio: 4 / 3 },
460
- { label: "🖥️ 16:9 (Widescreen)", value: "16/9", ratio: 16 / 9 },
461
- { label: "📷 3:2 (Foto)", value: "3/2", ratio: 3 / 2 },
462
- { label: "📱 2:3 (Retrato)", value: "2/3", ratio: 2 / 3 },
463
- { label: "📲 9:16 (Stories)", value: "9/16", ratio: 9 / 16 }
464
- ],
465
- showFreeAspectRatio: true
536
+ showFreeAspectRatio: true,
466
537
  // Permitir aspecto libre
538
+ // Nuevas opciones avanzadas
539
+ showDimensionInputs: false,
540
+ enableEditMode: true,
541
+ showDownloadInCropper: false,
542
+ showCropName: false,
543
+ mandatoryCrops: [],
544
+ allowCustomCrops: true
545
+ },
546
+ // Configuración de galería
547
+ gallery: {
548
+ filters: {
549
+ showNameFilter: true,
550
+ showUploadedByFilter: false,
551
+ showDateFilter: true,
552
+ enabledFilters: ["name", "date"],
553
+ customFilters: []
554
+ },
555
+ loading: {
556
+ showPlaceholders: true,
557
+ placeholderCount: 10,
558
+ showSpinner: true
559
+ },
560
+ pagination: {
561
+ itemsPerPage: 20,
562
+ showPageSize: true
563
+ }
467
564
  },
468
565
  // Validación de archivos
469
566
  validation: {
@@ -517,9 +614,33 @@ class ConfigManager {
517
614
  customProperties: {}
518
615
  },
519
616
  // Callbacks por defecto
520
- callbacks: {},
617
+ callbacks: {
618
+ // Callback cuando se sube una imagen
619
+ onUpload: null,
620
+ // (data) => { assetId, url, fileName, mime, width, height, instanceId }
621
+ // Callback cuando se selecciona una imagen
622
+ onSelect: null,
623
+ // (data) => { assetId, url, fileName, mime, width, height, instanceId }
624
+ // Callback cuando se elimina una imagen
625
+ onDelete: null,
626
+ // (assetId, instanceId) => void
627
+ // NUEVO: Callback cuando se guardan recortes/variantes
628
+ onCropsSaved: null,
629
+ // (data) => { crops: Array, assetId, instanceId }
630
+ // NUEVO: Callback cuando se completa el cropper (modo crop-only)
631
+ onCropperComplete: null,
632
+ // (data) => { crops: Array, instanceId }
633
+ // NUEVO: Callback cuando se cancela el cropper sin guardar
634
+ onCropperCancelled: null,
635
+ // (data) => { assetId?, instanceId }
636
+ // NUEVO: Callback cuando ocurre un error en el cropper
637
+ onCropperError: null
638
+ // (error, instanceId) => void
639
+ },
521
640
  // Configuración avanzada
522
641
  autoDestroy: false,
642
+ autoHideOnComplete: false,
643
+ // Si true, oculta el componente al completar (solo embed mode)
523
644
  debug: false,
524
645
  apiEndpoint: null,
525
646
  // ========== CONFIGURACIÓN DE AUTENTICACIÓN JWT V2 ==========
@@ -726,6 +847,128 @@ class ConfigManager {
726
847
  this.setJWTAuth(apiKey);
727
848
  return this;
728
849
  }
850
+ /**
851
+ * ========================================
852
+ * HELPERS PARA CONFIGURACIÓN DE CROPPER
853
+ * ========================================
854
+ */
855
+ /**
856
+ * Obtener recortes obligatorios configurados
857
+ */
858
+ getMandatoryCrops(config = null) {
859
+ const mergedConfig = config || this.merge();
860
+ return mergedConfig.cropper?.mandatoryCrops || [];
861
+ }
862
+ /**
863
+ * Verificar si hay recortes obligatorios configurados
864
+ */
865
+ hasMandatoryCrops(config = null) {
866
+ const crops = this.getMandatoryCrops(config);
867
+ return crops.length > 0;
868
+ }
869
+ /**
870
+ * Verificar si está habilitado el modo de edición de variantes
871
+ */
872
+ isEditModeEnabled(config = null) {
873
+ const mergedConfig = config || this.merge();
874
+ return mergedConfig.cropper?.enableEditMode !== false;
875
+ }
876
+ /**
877
+ * Verificar si se muestran inputs de dimensiones en lugar de aspect ratio
878
+ */
879
+ showDimensionInputs(config = null) {
880
+ const mergedConfig = config || this.merge();
881
+ return mergedConfig.cropper?.showDimensionInputs === true;
882
+ }
883
+ /**
884
+ * Verificar si se muestra botón de descarga en el cropper
885
+ */
886
+ showDownloadInCropper(config = null) {
887
+ const mergedConfig = config || this.merge();
888
+ return mergedConfig.cropper?.showDownloadInCropper === true;
889
+ }
890
+ /**
891
+ * Verificar si se muestra campo de nombre de recorte
892
+ */
893
+ showCropName(config = null) {
894
+ const mergedConfig = config || this.merge();
895
+ return mergedConfig.cropper?.showCropName === true;
896
+ }
897
+ /**
898
+ * Verificar si se permiten recortes personalizados
899
+ */
900
+ allowCustomCrops(config = null) {
901
+ const mergedConfig = config || this.merge();
902
+ return mergedConfig.cropper?.allowCustomCrops !== false;
903
+ }
904
+ /**
905
+ * ========================================
906
+ * HELPERS PARA CONFIGURACIÓN DE GALERÍA
907
+ * ========================================
908
+ */
909
+ /**
910
+ * Obtener configuración de filtros de galería
911
+ */
912
+ getGalleryFilters(config = null) {
913
+ const mergedConfig = config || this.merge();
914
+ return mergedConfig.gallery?.filters || this.defaults.gallery.filters;
915
+ }
916
+ /**
917
+ * Verificar si un filtro específico está habilitado
918
+ */
919
+ isGalleryFilterEnabled(filterName, config = null) {
920
+ const filters = this.getGalleryFilters(config);
921
+ return filters.enabledFilters?.includes(filterName) || false;
922
+ }
923
+ /**
924
+ * Obtener filtros personalizados de galería
925
+ */
926
+ getCustomGalleryFilters(config = null) {
927
+ const filters = this.getGalleryFilters(config);
928
+ return filters.customFilters || [];
929
+ }
930
+ /**
931
+ * Obtener configuración de carga de galería
932
+ */
933
+ getGalleryLoadingConfig(config = null) {
934
+ const mergedConfig = config || this.merge();
935
+ return mergedConfig.gallery?.loading || this.defaults.gallery.loading;
936
+ }
937
+ /**
938
+ * Verificar si se muestran placeholders en la galería
939
+ */
940
+ showGalleryPlaceholders(config = null) {
941
+ const loading = this.getGalleryLoadingConfig(config);
942
+ return loading.showPlaceholders !== false;
943
+ }
944
+ /**
945
+ * Obtener número de placeholders a mostrar
946
+ */
947
+ getPlaceholderCount(config = null) {
948
+ const loading = this.getGalleryLoadingConfig(config);
949
+ return loading.placeholderCount || 10;
950
+ }
951
+ /**
952
+ * Verificar si se muestra spinner de carga en galería
953
+ */
954
+ showGallerySpinner(config = null) {
955
+ const loading = this.getGalleryLoadingConfig(config);
956
+ return loading.showSpinner !== false;
957
+ }
958
+ /**
959
+ * Obtener configuración de paginación de galería
960
+ */
961
+ getGalleryPagination(config = null) {
962
+ const mergedConfig = config || this.merge();
963
+ return mergedConfig.gallery?.pagination || this.defaults.gallery.pagination;
964
+ }
965
+ /**
966
+ * Obtener items por página
967
+ */
968
+ getItemsPerPage(config = null) {
969
+ const pagination = this.getGalleryPagination(config);
970
+ return pagination.itemsPerPage || 20;
971
+ }
729
972
  }
730
973
  var client = { exports: {} };
731
974
  var reactDomClient_production = {};
@@ -30628,6 +30871,8 @@ function adaptAssetsListFromV2(v2Response) {
30628
30871
  height: asset.height,
30629
30872
  upload_date: asset.upload_date || asset.created_at,
30630
30873
  processing_status: asset.processing_status || asset.status,
30874
+ // Variantes
30875
+ variants: asset.variants_count ? adaptVariantsFromV2(asset.variants) || adaptVariantsFromV2(asset.image_variants) || [] : [],
30631
30876
  // URL principal - handle multiple URL formats from different backend responses
30632
30877
  url: makeAbsoluteUrl(
30633
30878
  asset.master_url || asset.master?.url_signed || asset.urls?.original || asset.url
@@ -30681,6 +30926,23 @@ function adaptVariantsListFromV2(v2Response) {
30681
30926
  });
30682
30927
  return { result: legacyVariants };
30683
30928
  }
30929
+ function adaptVariantsFromV2(v2Variants) {
30930
+ if (!Array.isArray(v2Variants)) {
30931
+ return [];
30932
+ }
30933
+ return v2Variants.map((variant) => ({
30934
+ id: variant.id,
30935
+ name: variant.name,
30936
+ width: variant.width,
30937
+ height: variant.height,
30938
+ format: variant.output_format,
30939
+ file_size: variant.file_size,
30940
+ crop_params: variant.crop_params,
30941
+ url: makeAbsoluteUrl(variant.url),
30942
+ expires_at: variant.expires_at,
30943
+ created_at: variant.created_at
30944
+ }));
30945
+ }
30684
30946
  function adaptUploadFromV2(v2Response) {
30685
30947
  if (!v2Response?.success || !v2Response?.data) {
30686
30948
  return { result: null };
@@ -30998,31 +31260,33 @@ function ImageVariantsModal({
30998
31260
  parent_asset_id: image.id,
30999
31261
  variant_info: variant
31000
31262
  };
31001
- accessibilityManager?.announce(`Variante seleccionada: ${variant.name}`);
31263
+ accessibilityManager?.announce(`Recorte seleccionado: ${variant.name}`);
31002
31264
  onSelect?.(variantAsImage);
31003
31265
  onClose?.();
31004
31266
  };
31005
31267
  const handleCopyVariantUrl = async (variant) => {
31006
31268
  try {
31007
31269
  await navigator.clipboard.writeText(variant.url);
31008
- accessibilityManager?.announce(`URL de variante ${variant.name} copiada`);
31270
+ accessibilityManager?.announce(`URL de recorte ${variant.name} copiada`);
31009
31271
  } catch (err) {
31010
31272
  console.error("Error copying variant URL:", err);
31011
- accessibilityManager?.announceError("Error al copiar URL de variante");
31273
+ accessibilityManager?.announceError("Error al copiar URL de recorte");
31012
31274
  }
31013
31275
  };
31014
- const handleDownloadVariant = (variant) => {
31015
- accessibilityManager?.announce(`Descargando variante ${variant.name}`);
31016
- const a = document.createElement("a");
31017
- a.href = variant.url;
31018
- a.download = `${image.filename}_${variant.name}.${variant.format}`;
31019
- document.body.appendChild(a);
31020
- a.click();
31021
- document.body.removeChild(a);
31276
+ const handleDownloadVariant = async (variant) => {
31277
+ const { downloadImage: downloadImage2 } = await Promise.resolve().then(() => downloadImage$1);
31278
+ await downloadImage2(
31279
+ variant.url,
31280
+ variant.name || "limbo-variant",
31281
+ {
31282
+ originalFormat: variant.format || variant.output_format,
31283
+ accessibilityManager
31284
+ }
31285
+ );
31022
31286
  };
31023
31287
  const handleViewVariant = (variant) => {
31024
31288
  accessibilityManager?.announce(
31025
- `Abriendo variante ${variant.name} en nueva pestaña`
31289
+ `Abriendo recorte ${variant.name} en nueva pestaña`
31026
31290
  );
31027
31291
  window.open(variant.url, "_blank");
31028
31292
  };
@@ -31041,21 +31305,21 @@ function ImageVariantsModal({
31041
31305
  accessibilityManager?.announceError("Error al copiar URL");
31042
31306
  }
31043
31307
  };
31044
- const handleDownloadOriginal = () => {
31045
- accessibilityManager?.announce(
31046
- `Descargando imagen original ${image.filename}`
31308
+ const handleDownloadOriginal = async () => {
31309
+ const { downloadImage: downloadImage2 } = await Promise.resolve().then(() => downloadImage$1);
31310
+ await downloadImage2(
31311
+ image.url || image.path,
31312
+ image.filename || "limbo-image",
31313
+ {
31314
+ originalFormat: image.format,
31315
+ accessibilityManager
31316
+ }
31047
31317
  );
31048
- const a = document.createElement("a");
31049
- a.href = image.url;
31050
- a.download = image.filename;
31051
- document.body.appendChild(a);
31052
- a.click();
31053
- document.body.removeChild(a);
31054
31318
  };
31055
31319
  const handleDeleteOriginal = async () => {
31056
31320
  if (!onDelete) return;
31057
31321
  const confirmed = window.confirm(
31058
- `¿Estás seguro de que deseas eliminar "${image.filename}"? Esta acción también eliminará todas sus variantes.`
31322
+ `¿Estás seguro de que deseas eliminar "${image.filename}"? Esta acción también eliminará todos sus recortes.`
31059
31323
  );
31060
31324
  if (confirmed) {
31061
31325
  accessibilityManager?.announce(`Eliminando imagen ${image.filename}`);
@@ -31073,27 +31337,27 @@ function ImageVariantsModal({
31073
31337
  };
31074
31338
  const handleDeleteVariant = async (variant) => {
31075
31339
  const confirmed = window.confirm(
31076
- `¿Estás seguro de que deseas eliminar la variante "${variant.name || variant.filename}"?`
31340
+ `¿Estás seguro de que deseas eliminar el recorte "${variant.name || variant.filename}"?`
31077
31341
  );
31078
31342
  if (confirmed) {
31079
31343
  accessibilityManager?.announce(
31080
- `Eliminando variante ${variant.name || variant.filename}`
31344
+ `Eliminando recorte ${variant.name || variant.filename}`
31081
31345
  );
31082
31346
  const result = await removeVariant(image.id, variant.id);
31083
31347
  if (result.success) {
31084
- setDeleteMessage("Variante eliminada correctamente");
31348
+ setDeleteMessage("Recorte eliminado correctamente");
31085
31349
  setDeleteMessageType("success");
31086
- accessibilityManager?.announce(`Variante eliminada correctamente`);
31350
+ accessibilityManager?.announce(`Recorte eliminado correctamente`);
31087
31351
  onVariantDeleted?.();
31088
31352
  setTimeout(() => {
31089
31353
  setDeleteMessage(null);
31090
31354
  setDeleteMessageType(null);
31091
31355
  }, 3e3);
31092
31356
  } else {
31093
- setDeleteMessage(`Error al eliminar variante: ${result.error}`);
31357
+ setDeleteMessage(`Error al eliminar recorte: ${result.error}`);
31094
31358
  setDeleteMessageType("error");
31095
31359
  accessibilityManager?.announceError(
31096
- `Error al eliminar variante: ${result.error}`
31360
+ `Error al eliminar recorte: ${result.error}`
31097
31361
  );
31098
31362
  setTimeout(() => {
31099
31363
  setDeleteMessage(null);
@@ -31118,7 +31382,7 @@ function ImageVariantsModal({
31118
31382
  children: [
31119
31383
  /* @__PURE__ */ jsxs("div", { className: "limbo-modal-header", children: [
31120
31384
  /* @__PURE__ */ jsxs("h2", { id: "variants-modal-title", children: [
31121
- "Variantes de ",
31385
+ "Recortes de ",
31122
31386
  image?.filename
31123
31387
  ] }),
31124
31388
  /* @__PURE__ */ jsx(
@@ -31126,7 +31390,7 @@ function ImageVariantsModal({
31126
31390
  {
31127
31391
  className: "limbo-modal-close",
31128
31392
  onClick: onClose,
31129
- "aria-label": "Cerrar modal de variantes",
31393
+ "aria-label": "Cerrar modal de recortes",
31130
31394
  children: "✕"
31131
31395
  }
31132
31396
  )
@@ -31149,10 +31413,10 @@ function ImageVariantsModal({
31149
31413
  ),
31150
31414
  /* @__PURE__ */ jsx("div", { className: "limbo-modal-body", children: loading ? /* @__PURE__ */ jsxs("div", { className: "limbo-variants-loading", children: [
31151
31415
  /* @__PURE__ */ jsx("div", { className: "limbo-loader" }),
31152
- /* @__PURE__ */ jsx("p", { children: "Cargando variantes..." })
31416
+ /* @__PURE__ */ jsx("p", { children: "Cargando recortes..." })
31153
31417
  ] }) : error ? /* @__PURE__ */ jsxs("div", { className: "limbo-variants-error", children: [
31154
31418
  /* @__PURE__ */ jsxs("p", { children: [
31155
- "Error al cargar variantes: ",
31419
+ "Error al cargar recortes: ",
31156
31420
  error
31157
31421
  ] }),
31158
31422
  /* @__PURE__ */ jsx(
@@ -31164,8 +31428,8 @@ function ImageVariantsModal({
31164
31428
  }
31165
31429
  )
31166
31430
  ] }) : variants.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "limbo-variants-empty", children: [
31167
- /* @__PURE__ */ jsx("p", { children: "Esta imagen no tiene variantes aún." }),
31168
- /* @__PURE__ */ jsx("small", { children: "Las variantes aparecerán aquí después de hacer recortes o redimensionados." })
31431
+ /* @__PURE__ */ jsx("p", { children: "Esta imagen no tiene recortes aún." }),
31432
+ /* @__PURE__ */ jsx("small", { children: "Los recortes aparecerán aquí después de hacer recortes o redimensionados." })
31169
31433
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
31170
31434
  /* @__PURE__ */ jsxs("div", { className: "limbo-variant-section", children: [
31171
31435
  /* @__PURE__ */ jsx("h3", { children: "Imagen original" }),
@@ -31259,7 +31523,7 @@ function ImageVariantsModal({
31259
31523
  ] }),
31260
31524
  /* @__PURE__ */ jsxs("div", { className: "limbo-variant-section", children: [
31261
31525
  /* @__PURE__ */ jsxs("h3", { children: [
31262
- "Variantes (",
31526
+ "Recortes (",
31263
31527
  variants.length,
31264
31528
  ")"
31265
31529
  ] }),
@@ -31277,7 +31541,7 @@ function ImageVariantsModal({
31277
31541
  {
31278
31542
  src: variant.url,
31279
31543
  alt: variant.name || variant.filename || `Variante ${variant.id}`,
31280
- loading: "lazy",
31544
+ loading: "eager",
31281
31545
  onError: () => handleVariantImageError(variant.id),
31282
31546
  style: {
31283
31547
  display: hasImageError ? "none" : "block"
@@ -31398,7 +31662,7 @@ function ImageCard({
31398
31662
  delete: true,
31399
31663
  crop: true,
31400
31664
  variants: true
31401
- // Nueva acción para ver variantes
31665
+ // Nueva acción para ver recortes
31402
31666
  }
31403
31667
  }) {
31404
31668
  const [copied, setCopied] = useState(false);
@@ -31453,22 +31717,18 @@ function ImageCard({
31453
31717
  );
31454
31718
  }
31455
31719
  };
31456
- const handleDownload = (e) => {
31720
+ const handleDownload = async (e) => {
31457
31721
  e.preventDefault();
31458
31722
  e.stopPropagation();
31459
- accessibilityManager?.announce(`Descargando ${image.filename}`);
31460
- fetch(image.url || image.path, { mode: "cors" }).then((resp) => resp.blob()).then((blob) => {
31461
- const url = window.URL.createObjectURL(blob);
31462
- const a = document.createElement("a");
31463
- a.href = url;
31464
- a.download = image.filename || "imagen.jpg";
31465
- document.body.appendChild(a);
31466
- a.click();
31467
- setTimeout(() => {
31468
- window.URL.revokeObjectURL(url);
31469
- document.body.removeChild(a);
31470
- }, 100);
31471
- });
31723
+ const { downloadImage: downloadImage2 } = await Promise.resolve().then(() => downloadImage$1);
31724
+ await downloadImage2(
31725
+ image.url || image.path,
31726
+ image.filename || "limbo-image",
31727
+ {
31728
+ originalFormat: image.format,
31729
+ accessibilityManager
31730
+ }
31731
+ );
31472
31732
  };
31473
31733
  const handleCrop = (e) => {
31474
31734
  e.stopPropagation();
@@ -31485,7 +31745,7 @@ function ImageCard({
31485
31745
  }, [image.variants_count]);
31486
31746
  const handleShowVariants = (e) => {
31487
31747
  e.stopPropagation();
31488
- accessibilityManager?.announce(`Mostrando variantes de ${image.filename}`);
31748
+ accessibilityManager?.announce(`Mostrando recortes de ${image.filename}`);
31489
31749
  setShowVariants(true);
31490
31750
  };
31491
31751
  const handleSelect = (e) => {
@@ -31552,7 +31812,7 @@ function ImageCard({
31552
31812
  className: `limbo-image-card flex flex-col items-center cursor-pointer relative transition ${isDeleting ? "opacity-50" : ""} ${isMobile ? "limbo-image-card--mobile" : ""}`,
31553
31813
  onClick: handleImageClick,
31554
31814
  onKeyDown: handleKeyDown,
31555
- title: isMobile ? "Toque para ver imagen" : "Haga click en la imagen para ver preview en nueva pestaña (Enter/Space para abrir, D descargar, C copiar, Delete eliminar, X recortar)",
31815
+ title: isMobile ? "Toque para ver imagen" : "Haga click en la imagen para ver preview en nueva pestaña",
31556
31816
  role: "button",
31557
31817
  tabIndex: 0,
31558
31818
  "aria-label": `Imagen ${image.filename}. ${image.width}x${image.height} px`,
@@ -31573,7 +31833,7 @@ function ImageCard({
31573
31833
  "button",
31574
31834
  {
31575
31835
  type: "button",
31576
- title: `Ver ${variantsCount} variante${variantsCount !== 1 ? "s" : ""}`,
31836
+ title: `Ver ${variantsCount} recorte${variantsCount !== 1 ? "s" : ""}`,
31577
31837
  className: `btn btn-variants border border-brand-blue-050/50 ${isMobile ? "btn--touch" : ""}`,
31578
31838
  onClick: handleShowVariants,
31579
31839
  tabIndex: -1,
@@ -31743,7 +32003,8 @@ function ImageCard({
31743
32003
  "img",
31744
32004
  {
31745
32005
  src: image.url || image.path,
31746
- alt: image.filename,
32006
+ loading: "eager",
32007
+ alt: image.filename.split(".")[0] ?? image.filename,
31747
32008
  className: "w-full object-cover rounded aspect-square",
31748
32009
  sizes: `height: ${thumbnailSize * 6}px,width: ${thumbnailSize * 6}px`,
31749
32010
  draggable: false,
@@ -31775,7 +32036,7 @@ function ImageCard({
31775
32036
  fontWeight: "500"
31776
32037
  }
31777
32038
  },
31778
- children: image.filename
32039
+ children: image.filename.split(".")[0] ?? image.filename
31779
32040
  }
31780
32041
  )
31781
32042
  ]
@@ -31802,6 +32063,38 @@ function ImageCard({
31802
32063
  )
31803
32064
  ] });
31804
32065
  }
32066
+ function ImageCardSkeleton() {
32067
+ return /* @__PURE__ */ jsxs(
32068
+ "div",
32069
+ {
32070
+ className: "limbo-image-card animate-pulse",
32071
+ role: "status",
32072
+ "aria-label": "Cargando imagen...",
32073
+ children: [
32074
+ /* @__PURE__ */ jsx("div", { className: "w-full aspect-square bg-neutral-gray-200 rounded-md flex items-center justify-center", children: /* @__PURE__ */ jsx(
32075
+ "svg",
32076
+ {
32077
+ className: "w-12 h-12 text-neutral-gray-300",
32078
+ fill: "currentColor",
32079
+ viewBox: "0 0 20 20",
32080
+ xmlns: "http://www.w3.org/2000/svg",
32081
+ "aria-hidden": "true",
32082
+ children: /* @__PURE__ */ jsx(
32083
+ "path",
32084
+ {
32085
+ fillRule: "evenodd",
32086
+ d: "M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z",
32087
+ clipRule: "evenodd"
32088
+ }
32089
+ )
32090
+ }
32091
+ ) }),
32092
+ /* @__PURE__ */ jsx("div", { className: "limbo-image-card-name opacity-100 position-relative bottom-0 p-2", children: /* @__PURE__ */ jsx("div", { className: "h-4 bg-neutral-gray-200 rounded w-3/4" }) }),
32093
+ /* @__PURE__ */ jsx("span", { className: "limbo-sr-only", children: "Cargando imagen..." })
32094
+ ]
32095
+ }
32096
+ );
32097
+ }
31805
32098
  function Loader({ text = "Cargando..." }) {
31806
32099
  return /* @__PURE__ */ jsxs(
31807
32100
  "div",
@@ -31847,50 +32140,7 @@ function Loader({ text = "Cargando..." }) {
31847
32140
  }
31848
32141
  );
31849
32142
  }
31850
- const cache$5 = /* @__PURE__ */ new Map();
31851
- const CACHE_TTL$5 = 5 * 60 * 1e3;
31852
- function useIsAllowedAll() {
31853
- const [allowed, setAllowed] = useState(null);
31854
- const [loading, setLoading] = useState(true);
31855
- const [error, setError] = useState(null);
31856
- useEffect(() => {
31857
- const cacheKey = "auth-status";
31858
- const cached = cache$5.get(cacheKey);
31859
- const now = Date.now();
31860
- if (cached && now - cached.timestamp < CACHE_TTL$5) {
31861
- setAllowed(cached.data);
31862
- setLoading(false);
31863
- return;
31864
- }
31865
- let isMounted = true;
31866
- const checkAuthStatus = async () => {
31867
- try {
31868
- const isAuthenticated = true;
31869
- if (!isMounted) return;
31870
- cache$5.set(cacheKey, { data: isAuthenticated, timestamp: Date.now() });
31871
- setAllowed(isAuthenticated);
31872
- } catch (err) {
31873
- if (isMounted) {
31874
- setError(err.message);
31875
- setAllowed(false);
31876
- }
31877
- } finally {
31878
- if (isMounted) setLoading(false);
31879
- }
31880
- };
31881
- checkAuthStatus();
31882
- return () => {
31883
- isMounted = false;
31884
- };
31885
- }, []);
31886
- const invalidateCache = () => {
31887
- cache$5.delete("auth-status");
31888
- };
31889
- return { allowed, loading, error, invalidateCache };
31890
- }
31891
32143
  function Gallery({
31892
- apiKey,
31893
- prod,
31894
32144
  onSelect,
31895
32145
  onCrop,
31896
32146
  // Nueva prop separada para cropping
@@ -31899,6 +32149,24 @@ function Gallery({
31899
32149
  images,
31900
32150
  loading,
31901
32151
  error,
32152
+ filters = {
32153
+ name: "",
32154
+ dateFrom: "",
32155
+ dateTo: "",
32156
+ uploadedBy: ""
32157
+ },
32158
+ onFiltersChange,
32159
+ filterConfig = {
32160
+ showNameFilter: true,
32161
+ showUploadedByFilter: false,
32162
+ // Oculto por defecto según especificaciones
32163
+ showDateFilter: true
32164
+ },
32165
+ loadingConfig = {
32166
+ showPlaceholders: true,
32167
+ placeholderCount: 10,
32168
+ showSpinner: true
32169
+ },
31902
32170
  allowedActions = {
31903
32171
  select: true,
31904
32172
  download: true,
@@ -31906,18 +32174,13 @@ function Gallery({
31906
32174
  delete: true,
31907
32175
  crop: true,
31908
32176
  variants: true
31909
- // Nueva acción para ver variantes
32177
+ // Nueva acción para ver recortes
31910
32178
  }
31911
32179
  }) {
31912
- const [filters, setFilters] = useState({
31913
- search: "",
31914
- dateFrom: "",
31915
- dateTo: "",
31916
- originPortal: "",
31917
- uploadedBy: ""
31918
- });
32180
+ const showNameFilter = filterConfig.showNameFilter !== false;
32181
+ const showUploadedByFilter = filterConfig.showUploadedByFilter === true;
32182
+ const showDateFilter = filterConfig.showDateFilter !== false;
31919
32183
  const galleryRef = useRef(null);
31920
- const showOriginPortal = useIsAllowedAll();
31921
32184
  const accessibilityManager = window.limboCore?.accessibilityManager;
31922
32185
  useEffect(() => {
31923
32186
  if (galleryRef.current && window.limboCore?.keyboardManager) {
@@ -31944,7 +32207,9 @@ function Gallery({
31944
32207
  }, [loading, error, images.length, accessibilityManager]);
31945
32208
  const handleChange = (e) => {
31946
32209
  const { name, value } = e.target;
31947
- setFilters((prev) => ({ ...prev, [name]: value }));
32210
+ if (onFiltersChange) {
32211
+ onFiltersChange({ ...filters, [name]: value });
32212
+ }
31948
32213
  };
31949
32214
  return /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
31950
32215
  /* @__PURE__ */ jsx("div", { className: "limbo-card mb-6 p-4 bg-brand-blue-050 shadow-md", children: /* @__PURE__ */ jsxs(
@@ -31954,16 +32219,16 @@ function Gallery({
31954
32219
  onSubmit: (e) => e.preventDefault(),
31955
32220
  "aria-label": "Filtrar imágenes",
31956
32221
  children: [
31957
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full sm:min-w-[180px] sm:flex-1", children: [
31958
- /* @__PURE__ */ jsx("label", { htmlFor: "search", className: "form-label mb-1", children: "Nombre" }),
32222
+ showNameFilter && /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full sm:min-w-[180px] sm:flex-1", children: [
32223
+ /* @__PURE__ */ jsx("label", { htmlFor: "name", className: "form-label mb-1", children: "Nombre" }),
31959
32224
  /* @__PURE__ */ jsx(
31960
32225
  "input",
31961
32226
  {
31962
32227
  type: "text",
31963
- name: "search",
31964
- id: "search",
32228
+ name: "name",
32229
+ id: "name",
31965
32230
  placeholder: "Buscar por nombre...",
31966
- value: filters.search,
32231
+ value: filters.name,
31967
32232
  onChange: handleChange,
31968
32233
  className: "form-control",
31969
32234
  autoComplete: "off"
@@ -31971,50 +32236,37 @@ function Gallery({
31971
32236
  )
31972
32237
  ] }),
31973
32238
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row flex-wrap gap-2 justify-between items-start sm:items-end w-full sm:w-auto", children: [
31974
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full sm:w-auto", children: [
31975
- /* @__PURE__ */ jsx("label", { htmlFor: "dateFrom", className: "form-label mb-1", children: "Desde" }),
31976
- /* @__PURE__ */ jsx(
31977
- "input",
31978
- {
31979
- type: "date",
31980
- name: "dateFrom",
31981
- id: "dateFrom",
31982
- value: filters.dateFrom,
31983
- onChange: handleChange,
31984
- className: "form-control"
31985
- }
31986
- )
31987
- ] }),
31988
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full sm:w-auto", children: [
31989
- /* @__PURE__ */ jsx("label", { htmlFor: "dateTo", className: "form-label mb-1", children: "Hasta" }),
31990
- /* @__PURE__ */ jsx(
31991
- "input",
31992
- {
31993
- type: "date",
31994
- name: "dateTo",
31995
- id: "dateTo",
31996
- value: filters.dateTo,
31997
- onChange: handleChange,
31998
- className: "form-control"
31999
- }
32000
- )
32001
- ] }),
32002
- showOriginPortal && /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full sm:w-auto", children: [
32003
- /* @__PURE__ */ jsx("label", { htmlFor: "originPortal", className: "form-label mb-1", children: "Portal de origen" }),
32004
- /* @__PURE__ */ jsx(
32005
- "input",
32006
- {
32007
- type: "text",
32008
- name: "originPortal",
32009
- id: "originPortal",
32010
- placeholder: "Portal de origen",
32011
- value: filters.originPortal,
32012
- onChange: handleChange,
32013
- className: "form-control"
32014
- }
32015
- )
32239
+ showDateFilter && /* @__PURE__ */ jsxs(Fragment, { children: [
32240
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full sm:w-auto", children: [
32241
+ /* @__PURE__ */ jsx("label", { htmlFor: "dateFrom", className: "form-label mb-1", children: "Desde" }),
32242
+ /* @__PURE__ */ jsx(
32243
+ "input",
32244
+ {
32245
+ type: "date",
32246
+ name: "dateFrom",
32247
+ id: "dateFrom",
32248
+ value: filters.dateFrom,
32249
+ onChange: handleChange,
32250
+ className: "form-control"
32251
+ }
32252
+ )
32253
+ ] }),
32254
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full sm:w-auto", children: [
32255
+ /* @__PURE__ */ jsx("label", { htmlFor: "dateTo", className: "form-label mb-1", children: "Hasta" }),
32256
+ /* @__PURE__ */ jsx(
32257
+ "input",
32258
+ {
32259
+ type: "date",
32260
+ name: "dateTo",
32261
+ id: "dateTo",
32262
+ value: filters.dateTo,
32263
+ onChange: handleChange,
32264
+ className: "form-control"
32265
+ }
32266
+ )
32267
+ ] })
32016
32268
  ] }),
32017
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full sm:w-auto", children: [
32269
+ showUploadedByFilter && /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full sm:w-auto", children: [
32018
32270
  /* @__PURE__ */ jsx("label", { htmlFor: "uploadedBy", className: "form-label mb-1", children: "Subido por" }),
32019
32271
  /* @__PURE__ */ jsx(
32020
32272
  "input",
@@ -32033,7 +32285,27 @@ function Gallery({
32033
32285
  ]
32034
32286
  }
32035
32287
  ) }),
32036
- loading ? /* @__PURE__ */ jsx(Loader, { text: "Cargando imágenes..." }) : error ? /* @__PURE__ */ jsx("div", { className: "alert alert-danger text-center", children: error }) : /* @__PURE__ */ jsx(
32288
+ loading ? loadingConfig.showPlaceholders ? /* @__PURE__ */ jsx(
32289
+ "div",
32290
+ {
32291
+ ref: galleryRef,
32292
+ className: "limbo-gallery mt-4",
32293
+ "data-limbo-responsive": true,
32294
+ role: "grid",
32295
+ "aria-label": "Cargando imágenes de la galería",
32296
+ "aria-busy": "true",
32297
+ children: Array.from({ length: loadingConfig.placeholderCount }).map((_, index) => /* @__PURE__ */ jsx(
32298
+ "div",
32299
+ {
32300
+ role: "gridcell",
32301
+ "aria-posinset": index + 1,
32302
+ "aria-setsize": loadingConfig.placeholderCount,
32303
+ children: /* @__PURE__ */ jsx(ImageCardSkeleton, {})
32304
+ },
32305
+ `skeleton-${index}`
32306
+ ))
32307
+ }
32308
+ ) : loadingConfig.showSpinner ? /* @__PURE__ */ jsx(Loader, { text: "Cargando imágenes..." }) : null : error ? /* @__PURE__ */ jsx("div", { className: "alert alert-danger text-center", children: error }) : /* @__PURE__ */ jsx(
32037
32309
  "div",
32038
32310
  {
32039
32311
  ref: galleryRef,
@@ -36565,6 +36837,21 @@ class CropperManager {
36565
36837
  return false;
36566
36838
  }
36567
36839
  },
36840
+ // Establecer posición y tamaño directamente
36841
+ set: (x, y, width, height) => {
36842
+ if (!this.selectionElement) return false;
36843
+ try {
36844
+ this.selectionElement.x = x;
36845
+ this.selectionElement.y = y;
36846
+ this.selectionElement.width = width;
36847
+ this.selectionElement.height = height;
36848
+ this.selectionElement.$render();
36849
+ return true;
36850
+ } catch (error) {
36851
+ console.warn("Error setting selection:", error);
36852
+ return false;
36853
+ }
36854
+ },
36568
36855
  // Exportar a canvas
36569
36856
  toCanvas: async (options = {}) => {
36570
36857
  if (!this.selectionElement) return null;
@@ -36966,11 +37253,136 @@ const useCropper = (image, options = {}) => {
36966
37253
  manager: managerRef.current
36967
37254
  };
36968
37255
  };
37256
+ async function downloadImage(imageSource, filename, options = {}) {
37257
+ const {
37258
+ format = null,
37259
+ originalFormat = "webp",
37260
+ onSuccess = null,
37261
+ onError = null,
37262
+ accessibilityManager = null
37263
+ } = options;
37264
+ try {
37265
+ const globalConfig2 = window.limboCore?.config?.getGlobal();
37266
+ const downloadFormat = format || globalConfig2?.downloadFormat || originalFormat || "webp";
37267
+ const cleanFilename = filename.split(".")[0];
37268
+ const finalFilename = `${cleanFilename}.${downloadFormat}`;
37269
+ if (accessibilityManager) {
37270
+ accessibilityManager.announce(`Descargando ${finalFilename}`);
37271
+ }
37272
+ if (imageSource.startsWith("data:image/")) {
37273
+ downloadFromBase64(imageSource, finalFilename);
37274
+ if (onSuccess) {
37275
+ onSuccess(finalFilename);
37276
+ }
37277
+ if (accessibilityManager) {
37278
+ accessibilityManager.announce(`${finalFilename} descargado correctamente`);
37279
+ }
37280
+ return true;
37281
+ }
37282
+ const response = await fetch(imageSource, { mode: "cors" });
37283
+ if (!response.ok) {
37284
+ throw new Error(`HTTP error! status: ${response.status}`);
37285
+ }
37286
+ const blob = await response.blob();
37287
+ let finalBlob = blob;
37288
+ const sourceMimeType = blob.type;
37289
+ const targetMimeType = `image/${downloadFormat === "jpg" ? "jpeg" : downloadFormat}`;
37290
+ if (sourceMimeType !== targetMimeType && shouldConvertFormat(downloadFormat)) {
37291
+ finalBlob = await convertBlobFormat(blob, downloadFormat);
37292
+ }
37293
+ const blobUrl = window.URL.createObjectURL(finalBlob);
37294
+ const link = document.createElement("a");
37295
+ link.href = blobUrl;
37296
+ link.download = finalFilename;
37297
+ link.style.display = "none";
37298
+ document.body.appendChild(link);
37299
+ link.click();
37300
+ setTimeout(() => {
37301
+ window.URL.revokeObjectURL(blobUrl);
37302
+ document.body.removeChild(link);
37303
+ }, 100);
37304
+ if (onSuccess) {
37305
+ onSuccess(finalFilename);
37306
+ }
37307
+ if (accessibilityManager) {
37308
+ accessibilityManager.announce(`${finalFilename} descargado correctamente`);
37309
+ }
37310
+ return true;
37311
+ } catch (error) {
37312
+ console.error("Error downloading image:", error);
37313
+ if (onError) {
37314
+ onError(error);
37315
+ }
37316
+ if (accessibilityManager) {
37317
+ accessibilityManager.announce(`Error al descargar la imagen: ${error.message}`);
37318
+ }
37319
+ return false;
37320
+ }
37321
+ }
37322
+ function downloadFromBase64(dataUri, filename) {
37323
+ const link = document.createElement("a");
37324
+ link.href = dataUri;
37325
+ link.download = filename;
37326
+ link.style.display = "none";
37327
+ document.body.appendChild(link);
37328
+ link.click();
37329
+ setTimeout(() => {
37330
+ document.body.removeChild(link);
37331
+ }, 100);
37332
+ }
37333
+ function shouldConvertFormat(format) {
37334
+ const supportedConversions = ["webp", "jpeg", "jpg", "png"];
37335
+ return supportedConversions.includes(format.toLowerCase());
37336
+ }
37337
+ async function convertBlobFormat(blob, targetFormat) {
37338
+ return new Promise((resolve, reject) => {
37339
+ const img = new Image();
37340
+ const objectUrl = URL.createObjectURL(blob);
37341
+ img.onload = () => {
37342
+ try {
37343
+ const canvas = document.createElement("canvas");
37344
+ canvas.width = img.width;
37345
+ canvas.height = img.height;
37346
+ const ctx = canvas.getContext("2d");
37347
+ ctx.drawImage(img, 0, 0);
37348
+ const mimeType = `image/${targetFormat === "jpg" ? "jpeg" : targetFormat}`;
37349
+ const quality = targetFormat === "png" ? void 0 : 0.92;
37350
+ canvas.toBlob(
37351
+ (convertedBlob) => {
37352
+ URL.revokeObjectURL(objectUrl);
37353
+ if (convertedBlob) {
37354
+ resolve(convertedBlob);
37355
+ } else {
37356
+ resolve(blob);
37357
+ }
37358
+ },
37359
+ mimeType,
37360
+ quality
37361
+ );
37362
+ } catch (error) {
37363
+ URL.revokeObjectURL(objectUrl);
37364
+ resolve(blob);
37365
+ }
37366
+ };
37367
+ img.onerror = () => {
37368
+ URL.revokeObjectURL(objectUrl);
37369
+ resolve(blob);
37370
+ };
37371
+ img.src = objectUrl;
37372
+ });
37373
+ }
37374
+ const downloadImage$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
37375
+ __proto__: null,
37376
+ default: downloadImage,
37377
+ downloadImage
37378
+ }, Symbol.toStringTag, { value: "Module" }));
36969
37379
  function CropperView({
36970
37380
  image,
36971
37381
  onSave,
36972
37382
  onCancel,
36973
37383
  onDelete,
37384
+ onError = null,
37385
+ // Callback para manejar errores
36974
37386
  deleting = false,
36975
37387
  onVariantCreated = null
36976
37388
  // Callback cuando se crea una variante
@@ -36978,7 +37390,6 @@ function CropperView({
36978
37390
  const [showPreview, setShowPreview] = useState(false);
36979
37391
  const [previewUrl, setPreviewUrl] = useState(null);
36980
37392
  const [previewLoading, setPreviewLoading] = useState(false);
36981
- const [aspectRatio, setAspectRatio] = useState("");
36982
37393
  const [showGrid, setShowGrid] = useState(true);
36983
37394
  const [shade, setShade] = useState(true);
36984
37395
  const [flipStates, setFlipStates] = useState({
@@ -36986,27 +37397,88 @@ function CropperView({
36986
37397
  vertical: false
36987
37398
  });
36988
37399
  const [showTips, setShowTips] = useState(false);
37400
+ const [showVisualOptions, setShowVisualOptions] = useState(true);
37401
+ const [showSelectorOptions, setShowSelectorOptions] = useState(true);
37402
+ const [showImageOptions, setShowImageOptions] = useState(true);
36989
37403
  const [zoomInfo, setZoomInfo] = useState({ current: 1, percentage: 100 });
36990
- const accessibilityManager = window.limboCore?.accessibilityManager;
36991
- const allowedAspectRatios = useMemo(() => {
37404
+ const [editableFilename] = useState(() => {
37405
+ const [name] = image.filename.split(".");
37406
+ return name;
37407
+ });
37408
+ const cropConfig = useMemo(() => {
36992
37409
  const config = window.limboCore?.config?.getGlobal() || {};
36993
- return config.cropper?.allowedAspectRatios || [
36994
- { label: "📐 Libre", value: "", ratio: null },
36995
- { label: "⬜ 1:1 (Cuadrado)", value: "1", ratio: 1 },
36996
- { label: "📺 4:3 (Clásico)", value: "4/3", ratio: 4 / 3 },
36997
- { label: "🖥️ 16:9 (Widescreen)", value: "16/9", ratio: 16 / 9 },
36998
- { label: "📷 3:2 (Foto)", value: "3/2", ratio: 3 / 2 },
36999
- { label: "📱 2:3 (Retrato)", value: "2/3", ratio: 2 / 3 },
37000
- { label: "📲 9:16 (Stories)", value: "9/16", ratio: 9 / 16 }
37001
- ];
37410
+ const mandatoryCrops = config.cropper?.mandatoryCrops || [];
37411
+ const allowCustomCrops = config.cropper?.allowCustomCrops !== false;
37412
+ const showDimensionInputs = config.cropper?.showDimensionInputs === true;
37413
+ return {
37414
+ mandatoryCrops,
37415
+ allowCustomCrops,
37416
+ showDimensionInputs
37417
+ };
37002
37418
  }, []);
37419
+ const [crops, setCrops] = useState(() => {
37420
+ if (cropConfig.mandatoryCrops.length > 0) {
37421
+ return cropConfig.mandatoryCrops.map((crop, index) => ({
37422
+ id: `crop-${index}`,
37423
+ label: crop.label,
37424
+ width: crop.width,
37425
+ height: crop.height,
37426
+ required: crop.required !== false,
37427
+ // Por defecto son obligatorios
37428
+ isCustom: false,
37429
+ confirmed: false,
37430
+ // Estado del crop (se guarda al cambiar de crop)
37431
+ savedState: null
37432
+ // {cropData, transforms: {zoom, rotation, flip}}
37433
+ }));
37434
+ } else {
37435
+ return [
37436
+ {
37437
+ id: "crop-default-0",
37438
+ label: editableFilename,
37439
+ width: image.width || 1920,
37440
+ height: image.height || 1080,
37441
+ required: false,
37442
+ isCustom: true,
37443
+ confirmed: false,
37444
+ savedState: null
37445
+ }
37446
+ ];
37447
+ }
37448
+ });
37449
+ const [activeCropIndex, setActiveCropIndex] = useState(0);
37450
+ const activeCrop = crops[activeCropIndex];
37451
+ const [shouldCenter, setShouldCenter] = useState(false);
37452
+ const calculatedAspectRatio = useMemo(() => {
37453
+ if (!activeCrop || !activeCrop.width || !activeCrop.height) return "";
37454
+ return activeCrop.width / activeCrop.height;
37455
+ }, [activeCrop]);
37456
+ const minCropSizes = useMemo(() => {
37457
+ const MIN_SIZE = 50;
37458
+ if (!calculatedAspectRatio || calculatedAspectRatio === "") {
37459
+ return { minWidth: MIN_SIZE, minHeight: MIN_SIZE };
37460
+ }
37461
+ if (calculatedAspectRatio >= 1) {
37462
+ const minWidth = MIN_SIZE;
37463
+ const minHeight = MIN_SIZE / calculatedAspectRatio;
37464
+ return { minWidth, minHeight };
37465
+ } else {
37466
+ const minHeight = MIN_SIZE;
37467
+ const minWidth = MIN_SIZE * calculatedAspectRatio;
37468
+ return { minWidth, minHeight };
37469
+ }
37470
+ }, [calculatedAspectRatio]);
37471
+ const [showConfirmModal, setShowConfirmModal] = useState(false);
37472
+ const [selectedCropsForAction, setSelectedCropsForAction] = useState([]);
37473
+ const [pendingAction, setPendingAction] = useState(null);
37474
+ const accessibilityManager = window.limboCore?.accessibilityManager;
37003
37475
  const {
37004
37476
  createCropVariant,
37005
37477
  loading: creatingVariant,
37006
37478
  error: variantError
37007
37479
  } = useCreateVariant();
37008
37480
  const cropper = useCropper(image, {
37009
- aspectRatio,
37481
+ aspectRatio: calculatedAspectRatio || null,
37010
37482
  showGrid,
37011
37483
  shade,
37012
37484
  initialCoverage: 0.5,
@@ -37015,31 +37487,37 @@ function CropperView({
37015
37487
  const { refs, state, transform, selection, utils } = cropper;
37016
37488
  const { canvasRef, imageRef, selectionRef } = refs;
37017
37489
  const { cropData, imageInfo, canExport, transformVersion } = state;
37018
- const effectiveImageInfo = useMemo(() => imageInfo || {
37019
- naturalWidth: image.width || 1920,
37020
- // Usar dimensiones de la imagen prop como fallback
37021
- naturalHeight: image.height || 1080,
37022
- currentWidth: image.width || 1920,
37023
- currentHeight: image.height || 1080
37024
- }, [imageInfo, image.width, image.height]);
37490
+ const effectiveImageInfo = useMemo(
37491
+ () => imageInfo || {
37492
+ naturalWidth: image.width || 1920,
37493
+ // Usar dimensiones de la imagen prop como fallback
37494
+ naturalHeight: image.height || 1080,
37495
+ currentWidth: image.width || 1920,
37496
+ currentHeight: image.height || 1080
37497
+ },
37498
+ [imageInfo, image.width, image.height]
37499
+ );
37025
37500
  const toggleGrid = useCallback(() => setShowGrid((v) => !v), []);
37026
37501
  const toggleShade = useCallback(() => setShade((v) => !v), []);
37027
37502
  const toggleTips = useCallback(() => setShowTips((v) => !v), []);
37503
+ const toggleVisualOptions = useCallback(
37504
+ () => setShowVisualOptions((v) => !v),
37505
+ []
37506
+ );
37507
+ const toggleSelectorOptions = useCallback(
37508
+ () => setShowSelectorOptions((v) => !v),
37509
+ []
37510
+ );
37511
+ const toggleImageOptions = useCallback(
37512
+ () => setShowImageOptions((v) => !v),
37513
+ []
37514
+ );
37028
37515
  const centerImage = useCallback(() => transform.center(), [transform]);
37029
37516
  const centerSelection = useCallback(() => selection.center(), [selection]);
37030
37517
  const resetSelection = useCallback(() => selection.reset(), [selection]);
37031
37518
  const move = useCallback((x, y) => transform.move(x, y), [transform]);
37032
37519
  const zoom = useCallback((factor) => transform.zoom(factor), [transform]);
37033
37520
  const rotate = useCallback((deg) => transform.rotate(deg), [transform]);
37034
- const resetAll = useCallback(() => {
37035
- utils.resetAll();
37036
- setAspectRatio("");
37037
- setShowGrid(true);
37038
- setShade(true);
37039
- setFlipStates({ horizontal: false, vertical: false });
37040
- setShowPreview(false);
37041
- setPreviewUrl(null);
37042
- }, [utils]);
37043
37521
  const setSelectionCoverage = useCallback(
37044
37522
  (coverage) => {
37045
37523
  selection.setCoverage(coverage);
@@ -37061,16 +37539,188 @@ function CropperView({
37061
37539
  return { ...prev, vertical: newVertical };
37062
37540
  });
37063
37541
  }, [transform]);
37064
- const handleAspectRatio = useCallback(
37065
- (ratioValue) => {
37066
- setAspectRatio(ratioValue);
37067
- const ratioConfig = allowedAspectRatios.find(
37068
- (r) => r.value === ratioValue
37542
+ const saveCurrentCropState = useCallback(() => {
37543
+ if (!cropper.manager || !state.isReady) return;
37544
+ try {
37545
+ const currentCropData = cropData ? { ...cropData } : null;
37546
+ const currentZoom = cropper.manager.transform.getZoom();
37547
+ const currentRotation = cropper.manager.transform.getRotation();
37548
+ const savedState = {
37549
+ cropData: currentCropData,
37550
+ transforms: {
37551
+ zoom: currentZoom,
37552
+ rotation: currentRotation,
37553
+ flipHorizontal: flipStates.horizontal,
37554
+ flipVertical: flipStates.vertical
37555
+ }
37556
+ };
37557
+ setCrops(
37558
+ (prevCrops) => prevCrops.map(
37559
+ (crop, index) => index === activeCropIndex ? { ...crop, savedState } : crop
37560
+ )
37069
37561
  );
37070
- const numericRatio = ratioConfig ? ratioConfig.ratio : ratioValue;
37071
- selection.setAspectRatio(numericRatio);
37562
+ return savedState;
37563
+ } catch (error) {
37564
+ console.warn("Error saving crop state:", error);
37565
+ return null;
37566
+ }
37567
+ }, [cropper.manager, state.isReady, cropData, flipStates, activeCropIndex]);
37568
+ const restoreCropState = useCallback(
37569
+ (cropState) => {
37570
+ if (!cropper.manager || !state.isReady || !cropState) return;
37571
+ try {
37572
+ const { cropData: savedCropData, transforms } = cropState;
37573
+ if (transforms) {
37574
+ utils.resetAll();
37575
+ if (transforms.zoom && transforms.zoom !== 1) {
37576
+ cropper.manager.transform.setZoom(transforms.zoom);
37577
+ }
37578
+ if (transforms.rotation && transforms.rotation !== 0) {
37579
+ cropper.manager.transform.setRotation(transforms.rotation);
37580
+ }
37581
+ if (transforms.flipHorizontal) {
37582
+ cropper.manager.transform.flipHorizontal();
37583
+ }
37584
+ if (transforms.flipVertical) {
37585
+ cropper.manager.transform.flipVertical();
37586
+ }
37587
+ setFlipStates({
37588
+ horizontal: transforms.flipHorizontal || false,
37589
+ vertical: transforms.flipVertical || false
37590
+ });
37591
+ }
37592
+ if (savedCropData) {
37593
+ const { x, y, width, height } = savedCropData;
37594
+ selection.set(x, y, width, height);
37595
+ }
37596
+ } catch (error) {
37597
+ console.warn("Error restoring crop state:", error);
37598
+ }
37599
+ },
37600
+ [cropper.manager, state.isReady, selection, utils]
37601
+ );
37602
+ const validateCropNames = useCallback(() => {
37603
+ for (let i = 0; i < crops.length; i++) {
37604
+ const crop = crops[i];
37605
+ if (!crop.label || crop.label.trim() === "") {
37606
+ return i;
37607
+ }
37608
+ }
37609
+ return -1;
37610
+ }, [crops]);
37611
+ const switchToCrop = useCallback(
37612
+ (newIndex) => {
37613
+ if (newIndex === activeCropIndex) return;
37614
+ saveCurrentCropState();
37615
+ setActiveCropIndex(newIndex);
37616
+ setShouldCenter(true);
37617
+ },
37618
+ [activeCropIndex, saveCurrentCropState]
37619
+ );
37620
+ const addCustomCrop = useCallback(() => {
37621
+ if (!cropConfig.allowCustomCrops) {
37622
+ alert("No se pueden añadir recortes personalizados en este modo.");
37623
+ return;
37624
+ }
37625
+ saveCurrentCropState();
37626
+ const newCropId = `crop-custom-${Date.now()}`;
37627
+ const newCrop = {
37628
+ id: newCropId,
37629
+ label: `Recorte ${crops.length + 1}`,
37630
+ width: image.width || 1920,
37631
+ height: image.height || 1080,
37632
+ required: false,
37633
+ isCustom: true,
37634
+ confirmed: false,
37635
+ savedState: null
37636
+ };
37637
+ setCrops((prevCrops) => [...prevCrops, newCrop]);
37638
+ setActiveCropIndex(crops.length);
37639
+ accessibilityManager?.announce(
37640
+ `Nuevo recorte personalizado añadido: ${newCrop.label}`
37641
+ );
37642
+ }, [cropConfig.allowCustomCrops, saveCurrentCropState, crops.length, image.width, image.height, accessibilityManager]);
37643
+ const updateCropDimensions = useCallback(
37644
+ (field, value) => {
37645
+ const numValue = parseInt(value, 10);
37646
+ if (value === "" || isNaN(numValue)) {
37647
+ return;
37648
+ }
37649
+ setCrops(
37650
+ (prevCrops) => prevCrops.map(
37651
+ (crop, index) => index === activeCropIndex ? { ...crop, [field]: numValue } : crop
37652
+ )
37653
+ );
37654
+ },
37655
+ [activeCropIndex]
37656
+ );
37657
+ const validateAndApplyCropDimensions = useCallback(
37658
+ (field) => {
37659
+ const currentValue = activeCrop[field];
37660
+ let minValue = 100;
37661
+ if (canvasRef.current && imageInfo) {
37662
+ const canvasRect = canvasRef.current.getBoundingClientRect();
37663
+ const canvasMinSize = Math.min(canvasRect.width, canvasRect.height);
37664
+ minValue = Math.max(100, Math.round(canvasMinSize * 0.05));
37665
+ }
37666
+ const clampedValue = Math.max(minValue, Math.min(5e3, currentValue));
37667
+ if (clampedValue !== currentValue) {
37668
+ setCrops(
37669
+ (prevCrops) => prevCrops.map(
37670
+ (crop, index) => index === activeCropIndex ? { ...crop, [field]: clampedValue } : crop
37671
+ )
37672
+ );
37673
+ }
37674
+ const updatedCrop = { ...activeCrop, [field]: clampedValue };
37675
+ const newRatio = updatedCrop.width / updatedCrop.height;
37676
+ const currentSelection = selection.getData?.();
37677
+ selection.setAspectRatio(newRatio);
37678
+ if (currentSelection && currentSelection.x !== void 0) {
37679
+ setTimeout(() => {
37680
+ selection.set?.(
37681
+ currentSelection.x,
37682
+ currentSelection.y,
37683
+ currentSelection.width,
37684
+ currentSelection.height
37685
+ );
37686
+ }, 50);
37687
+ }
37688
+ },
37689
+ [activeCropIndex, activeCrop, selection, canvasRef, imageInfo]
37690
+ );
37691
+ const updateCropLabel = useCallback(
37692
+ (newLabel) => {
37693
+ setCrops(
37694
+ (prevCrops) => prevCrops.map(
37695
+ (crop, index) => index === activeCropIndex ? { ...crop, label: newLabel } : crop
37696
+ )
37697
+ );
37698
+ },
37699
+ [activeCropIndex]
37700
+ );
37701
+ const removeCustomCrop = useCallback(
37702
+ (cropIndex) => {
37703
+ const cropToRemove = crops[cropIndex];
37704
+ if (cropToRemove.required) {
37705
+ alert("No se puede eliminar un recorte obligatorio.");
37706
+ return;
37707
+ }
37708
+ if (crops.length === 1) {
37709
+ alert("Debe haber al menos un recorte.");
37710
+ return;
37711
+ }
37712
+ setCrops(
37713
+ (prevCrops) => prevCrops.filter((_, index) => index !== cropIndex)
37714
+ );
37715
+ if (cropIndex === activeCropIndex) {
37716
+ const newIndex = Math.max(0, cropIndex - 1);
37717
+ setActiveCropIndex(newIndex);
37718
+ } else if (cropIndex < activeCropIndex) {
37719
+ setActiveCropIndex((prev) => prev - 1);
37720
+ }
37721
+ accessibilityManager?.announce(`Recorte ${cropToRemove.label} eliminado`);
37072
37722
  },
37073
- [selection, allowedAspectRatios]
37723
+ [crops, activeCropIndex, accessibilityManager]
37074
37724
  );
37075
37725
  const generatePreview = useCallback(async () => {
37076
37726
  if (!canExport) return null;
@@ -37081,12 +37731,15 @@ function CropperView({
37081
37731
  imageSmoothingEnabled: true,
37082
37732
  imageSmoothingQuality: "high"
37083
37733
  });
37084
- return canvas ? canvas.toDataURL("image/jpeg", 0.9) : null;
37734
+ return canvas ? canvas.toDataURL(
37735
+ `image/${globalThis.downloadFormat || image.mime_type.split("/")[1] || "webp"}`,
37736
+ 0.9
37737
+ ) : null;
37085
37738
  } catch (error) {
37086
37739
  console.warn("Error generating preview:", error);
37087
37740
  return null;
37088
37741
  }
37089
- }, [canExport, selection]);
37742
+ }, [canExport, image.mime_type, selection]);
37090
37743
  const preview = useCallback(async () => {
37091
37744
  if (showPreview) {
37092
37745
  setShowPreview(false);
@@ -37116,7 +37769,7 @@ function CropperView({
37116
37769
  setPreviewLoading(false);
37117
37770
  }
37118
37771
  }, [canExport, generatePreview, showPreview]);
37119
- const saveCrop = useCallback(async () => {
37772
+ const performSaveCrop = useCallback(async () => {
37120
37773
  if (!canExport) {
37121
37774
  const errorMsg = "No se puede exportar el recorte por restricciones de CORS en la imagen original.";
37122
37775
  accessibilityManager?.announceError(errorMsg);
@@ -37129,19 +37782,25 @@ function CropperView({
37129
37782
  alert(errorMsg);
37130
37783
  return;
37131
37784
  }
37132
- accessibilityManager?.announce("Creando variante recortada de la imagen");
37785
+ accessibilityManager?.announce("Creando recorte de la imagen");
37133
37786
  try {
37134
37787
  if (!cropData || !effectiveImageInfo) {
37135
37788
  console.error("❌ Datos faltantes:", { cropData, effectiveImageInfo });
37136
- throw new Error(`No hay datos de recorte disponibles. CropData: ${!!cropData}, ImageInfo: ${!!effectiveImageInfo}`);
37789
+ throw new Error(
37790
+ `No hay datos de recorte disponibles. CropData: ${!!cropData}, ImageInfo: ${!!effectiveImageInfo}`
37791
+ );
37137
37792
  }
37138
37793
  if (!cropData.x && cropData.x !== 0 || !cropData.y && cropData.y !== 0 || !cropData.width || !cropData.height) {
37139
37794
  console.error("❌ CropData inválido:", cropData);
37140
- throw new Error("Los datos de recorte no tienen las propiedades esperadas");
37795
+ throw new Error(
37796
+ "Los datos de recorte no tienen las propiedades esperadas"
37797
+ );
37141
37798
  }
37142
37799
  if (!effectiveImageInfo.naturalWidth || !effectiveImageInfo.naturalHeight) {
37143
37800
  console.error("❌ ImageInfo inválido:", effectiveImageInfo);
37144
- throw new Error("Los datos de imagen no tienen las dimensiones esperadas");
37801
+ throw new Error(
37802
+ "Los datos de imagen no tienen las dimensiones esperadas"
37803
+ );
37145
37804
  }
37146
37805
  const { x, y, width, height } = cropData;
37147
37806
  const { naturalWidth, naturalHeight } = effectiveImageInfo;
@@ -37151,61 +37810,181 @@ function CropperView({
37151
37810
  width: width / naturalWidth,
37152
37811
  height: height / naturalHeight
37153
37812
  };
37154
- const cropAspectRatio = width / height;
37155
- let variantWidth, variantHeight;
37156
- const maxDimension = 1200;
37157
- if (cropAspectRatio > 1) {
37158
- variantWidth = Math.min(width, maxDimension);
37159
- variantHeight = Math.round(variantWidth / cropAspectRatio);
37160
- } else {
37161
- variantHeight = Math.min(height, maxDimension);
37162
- variantWidth = Math.round(variantHeight * cropAspectRatio);
37163
- }
37813
+ const variantWidth = Math.min(activeCrop.width, 5e3);
37814
+ const variantHeight = Math.min(activeCrop.height, 5e3);
37164
37815
  const ts = Date.now();
37165
- const [name] = image.filename.split(".");
37166
- const variantName = `${name}_crop_${ts}`;
37816
+ const variantName = `${editableFilename}_${activeCrop.label || "crop"}_${ts}`;
37167
37817
  const result = await createCropVariant(image.id, cropParams, {
37168
37818
  name: variantName,
37169
37819
  width: variantWidth,
37170
37820
  height: variantHeight,
37171
- format: "webp",
37821
+ format: globalThis.downloadFormat || "webp",
37172
37822
  quality: 90
37173
37823
  });
37174
37824
  if (result) {
37175
- accessibilityManager?.announceSuccess(
37176
- `Variante recortada creada: ${variantName}`
37177
- );
37825
+ accessibilityManager?.announceSuccess(`Recorte creado: ${variantName}`);
37178
37826
  onVariantCreated?.(image.id, result);
37179
37827
  onSave(result);
37180
37828
  }
37181
37829
  } catch (error) {
37182
37830
  console.warn("Error creating crop variant:", error);
37183
- const errorMsg = "No se pudo crear la variante recortada. Inténtalo de nuevo.";
37831
+ const errorMsg = "No se pudo crear el recorte. Inténtalo de nuevo.";
37184
37832
  accessibilityManager?.announceError(errorMsg);
37185
37833
  alert(errorMsg);
37834
+ onError?.(error);
37186
37835
  }
37187
37836
  }, [
37188
37837
  canExport,
37838
+ state.isReady,
37839
+ accessibilityManager,
37189
37840
  cropData,
37190
- imageInfo,
37191
37841
  effectiveImageInfo,
37192
- image.filename,
37193
- image.id,
37842
+ editableFilename,
37843
+ activeCrop.label,
37844
+ activeCrop.width,
37845
+ activeCrop.height,
37194
37846
  createCropVariant,
37195
- onSave,
37847
+ image.id,
37196
37848
  onVariantCreated,
37849
+ onSave,
37850
+ onError
37851
+ ]);
37852
+ const saveCrop = useCallback(async () => {
37853
+ const invalidCropIndex = validateCropNames();
37854
+ if (invalidCropIndex !== -1) {
37855
+ const cropWithoutName = crops[invalidCropIndex];
37856
+ alert(`El recorte "${cropWithoutName.label || cropWithoutName || "(sin nombre)"}" debe tener un nombre válido.`);
37857
+ setActiveCropIndex(invalidCropIndex);
37858
+ return;
37859
+ }
37860
+ if (crops.length > 1) {
37861
+ setPendingAction("save");
37862
+ setSelectedCropsForAction(crops.map((_, index) => index));
37863
+ setShowConfirmModal(true);
37864
+ return;
37865
+ }
37866
+ await performSaveCrop();
37867
+ }, [validateCropNames, crops, performSaveCrop]);
37868
+ const performDownload = useCallback(async () => {
37869
+ if (!canExport) {
37870
+ const errorMsg = "No se puede descargar el recorte por restricciones de CORS en la imagen original.";
37871
+ accessibilityManager?.announceError(errorMsg);
37872
+ alert(errorMsg);
37873
+ return;
37874
+ }
37875
+ try {
37876
+ accessibilityManager?.announce("Preparando descarga del recorte");
37877
+ let downloadUrl = previewUrl;
37878
+ if (!downloadUrl) {
37879
+ downloadUrl = await generatePreview();
37880
+ }
37881
+ if (!downloadUrl) {
37882
+ throw new Error("No se pudo generar la imagen para descargar");
37883
+ }
37884
+ const cropName = activeCrop.label.replace(/\.[^/.]+$/, " ").replace(" ", "-").trim();
37885
+ const filename = `${editableFilename}_${cropName || "crop"}`;
37886
+ await downloadImage(downloadUrl, filename, {
37887
+ accessibilityManager,
37888
+ onSuccess: (finalFilename) => {
37889
+ accessibilityManager?.announce(
37890
+ `Recorte descargado como ${finalFilename}`
37891
+ );
37892
+ },
37893
+ onError: (error) => {
37894
+ accessibilityManager?.announceError(
37895
+ `Error al descargar: ${error.message}`
37896
+ );
37897
+ alert(`Error al descargar la imagen: ${error.message}`);
37898
+ }
37899
+ });
37900
+ } catch (error) {
37901
+ console.error("Error downloading crop:", error);
37902
+ accessibilityManager?.announceError(
37903
+ `Error al descargar el recorte: ${error.message}`
37904
+ );
37905
+ alert(`Error al descargar el recorte: ${error.message}`);
37906
+ }
37907
+ }, [
37908
+ canExport,
37197
37909
  accessibilityManager,
37198
- state.isReady
37910
+ previewUrl,
37911
+ activeCrop.label,
37912
+ editableFilename,
37913
+ generatePreview
37199
37914
  ]);
37915
+ const handleDownload = useCallback(async () => {
37916
+ const invalidCropIndex = validateCropNames();
37917
+ if (invalidCropIndex !== -1) {
37918
+ const cropWithoutName = crops[invalidCropIndex];
37919
+ alert(`El recorte "${cropWithoutName.label || cropWithoutName || "(sin nombre)"}" debe tener un nombre válido.`);
37920
+ setActiveCropIndex(invalidCropIndex);
37921
+ return;
37922
+ }
37923
+ if (crops.length > 1) {
37924
+ setPendingAction("download");
37925
+ setSelectedCropsForAction(crops.map((_, index) => index));
37926
+ setShowConfirmModal(true);
37927
+ return;
37928
+ }
37929
+ await performDownload();
37930
+ }, [validateCropNames, crops, performDownload]);
37200
37931
  useEffect(() => {
37201
37932
  setShowPreview(false);
37202
37933
  setPreviewUrl(null);
37203
37934
  }, [image]);
37204
37935
  useEffect(() => {
37205
37936
  if (!cropper.manager) return;
37206
- selection.setAspectRatio(aspectRatio);
37937
+ if (calculatedAspectRatio) {
37938
+ selection.setAspectRatio(calculatedAspectRatio);
37939
+ }
37207
37940
  utils.setBackground(shade);
37208
- }, [aspectRatio, shade, cropper.manager, selection, utils]);
37941
+ }, [calculatedAspectRatio, shade, cropper.manager, selection, utils]);
37942
+ useEffect(() => {
37943
+ if (!cropper.manager || !state.isReady) return;
37944
+ const cropState = activeCrop?.savedState;
37945
+ if (cropState) {
37946
+ restoreCropState(cropState);
37947
+ setShouldCenter(false);
37948
+ } else {
37949
+ if (calculatedAspectRatio) {
37950
+ selection.setAspectRatio(calculatedAspectRatio);
37951
+ if (shouldCenter) {
37952
+ setTimeout(() => {
37953
+ selection.center();
37954
+ setShouldCenter(false);
37955
+ }, 100);
37956
+ }
37957
+ }
37958
+ }
37959
+ }, [
37960
+ activeCropIndex,
37961
+ activeCrop,
37962
+ calculatedAspectRatio,
37963
+ cropper.manager,
37964
+ state.isReady,
37965
+ restoreCropState,
37966
+ selection,
37967
+ shouldCenter
37968
+ ]);
37969
+ useEffect(() => {
37970
+ if (!imageInfo || !state.isReady || cropConfig.mandatoryCrops.length > 0)
37971
+ return;
37972
+ const defaultCrop = crops[0];
37973
+ if (defaultCrop && defaultCrop.id === "crop-default-0" && (defaultCrop.width === 1920 || defaultCrop.height === 1080)) {
37974
+ const newWidth = Math.min(imageInfo.naturalWidth, 5e3);
37975
+ const newHeight = Math.min(imageInfo.naturalHeight, 5e3);
37976
+ setCrops([
37977
+ {
37978
+ ...defaultCrop,
37979
+ width: newWidth,
37980
+ height: newHeight
37981
+ }
37982
+ ]);
37983
+ console.log(
37984
+ `[CropperView] Crop por defecto actualizado a ${newWidth}×${newHeight}px`
37985
+ );
37986
+ }
37987
+ }, [imageInfo, state.isReady, cropConfig.mandatoryCrops.length, crops]);
37209
37988
  useEffect(() => {
37210
37989
  if (!cropper.manager || !cropper.manager.utils) return;
37211
37990
  try {
@@ -37249,7 +38028,7 @@ function CropperView({
37249
38028
  showPreview,
37250
38029
  canExport,
37251
38030
  generatePreview,
37252
- aspectRatio,
38031
+ calculatedAspectRatio,
37253
38032
  flipStates,
37254
38033
  previewUrl
37255
38034
  ]);
@@ -37282,19 +38061,27 @@ function CropperView({
37282
38061
  };
37283
38062
  }, [canvasRef]);
37284
38063
  if (!image) return null;
37285
- 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-screen flex flex-col", children: [
37286
- /* @__PURE__ */ jsxs("div", { className: "limbo-cropper-header flex flex-col sm:flex-row justify-between items-start sm:items-center p-4 sm:p-6 pb-4 border-b border-gray-200 bg-white z-10 flex-shrink-0 gap-4 sm:gap-2", children: [
37287
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
37288
- /* @__PURE__ */ jsx("h2", { className: "text-lg sm:text-xl font-bold text-gray-800 truncate", children: "Recortar imagen" }),
37289
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600 mt-1 truncate", children: image.filename })
38064
+ 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: [
38065
+ /* @__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: [
38066
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 space-y-2", children: [
38067
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
38068
+ /* @__PURE__ */ jsx("h2", { className: "text-lg sm:text-xl font-bold text-gray-800", children: "Generar recortes" }),
38069
+ 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 })
38070
+ ] }),
38071
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
38072
+ /* @__PURE__ */ jsx("label", { className: "text-xs text-gray-500 whitespace-nowrap", children: "Nombre:" }),
38073
+ /* @__PURE__ */ jsx("div", { className: "", children: editableFilename + "." + (globalThis.downloadFormat || image.mime_type.split("/")[1] || "webp") })
38074
+ ] })
37290
38075
  ] }),
37291
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2 w-full sm:w-auto", children: [
38076
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-row self-end gap-2 w-full sm:w-auto", children: [
37292
38077
  /* @__PURE__ */ jsx(
37293
38078
  "button",
37294
38079
  {
37295
38080
  onClick: onCancel,
37296
38081
  disabled: creatingVariant,
37297
- className: "limbo-btn limbo-btn-secondary px-4 py-2 flex-1 sm:flex-initial",
38082
+ className: "limbo-btn limbo-btn-secondary px-4 sm:py-1 h-min flex-1",
38083
+ "aria-label": "Cancelar y volver",
38084
+ title: "Cancelar y volver",
37298
38085
  children: "Cancelar"
37299
38086
  }
37300
38087
  ),
@@ -37303,8 +38090,9 @@ function CropperView({
37303
38090
  {
37304
38091
  onClick: () => onDelete?.(image),
37305
38092
  disabled: deleting | creatingVariant,
37306
- className: "limbo-btn limbo-btn-danger px-4 py-2 flex-1 sm:flex-initial",
38093
+ className: "limbo-btn limbo-btn-danger px-4 sm:py-1 h-min flex-1 sm:flex-initial",
37307
38094
  "aria-label": `Eliminar imagen ${image.filename}`,
38095
+ title: `Eliminar imagen ${image.filename}`,
37308
38096
  children: deleting ? "Eliminando..." : "Eliminar"
37309
38097
  }
37310
38098
  )
@@ -37316,15 +38104,6 @@ function CropperView({
37316
38104
  " ",
37317
38105
  variantError
37318
38106
  ] }),
37319
- cropData && cropData.width > 0 && /* @__PURE__ */ jsxs("div", { className: "alert alert-info mb-2 text-sm", role: "status", children: [
37320
- /* @__PURE__ */ jsx("strong", { children: "Área:" }),
37321
- " ",
37322
- Math.round(cropData.width),
37323
- " ×",
37324
- " ",
37325
- Math.round(cropData.height),
37326
- " px"
37327
- ] }),
37328
38107
  imageInfo && /* @__PURE__ */ jsxs("div", { className: "alert alert-secondary mb-2 text-sm", role: "status", children: [
37329
38108
  /* @__PURE__ */ jsx("strong", { children: "Original:" }),
37330
38109
  " ",
@@ -37342,10 +38121,135 @@ function CropperView({
37342
38121
  " px)"
37343
38122
  ] })
37344
38123
  ] }),
37345
- !canExport && /* @__PURE__ */ jsx("div", { className: "alert alert-warning mb-2 text-sm", role: "alert", children: "⚠️ No se puede exportar por restricciones CORS" })
38124
+ !canExport && /* @__PURE__ */ jsx("div", { className: "alert alert-warning mb-2 text-sm", role: "alert", children: "⚠️ No se puede exportar por restricciones CORS" }),
38125
+ /* @__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: [
38126
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
38127
+ /* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold text-gray-700", children: [
38128
+ "Recortes ",
38129
+ crops.length > 1 && `(${crops.length})`
38130
+ ] }),
38131
+ cropConfig.allowCustomCrops && /* @__PURE__ */ jsxs(
38132
+ "button",
38133
+ {
38134
+ onClick: addCustomCrop,
38135
+ disabled: creatingVariant,
38136
+ 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",
38137
+ title: "Añadir recorte personalizado",
38138
+ children: [
38139
+ /* @__PURE__ */ jsx("span", { className: "icon icon-add-green text-green-800 icon--xs" }),
38140
+ "Añadir recorte"
38141
+ ]
38142
+ }
38143
+ )
38144
+ ] }),
38145
+ /* @__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(
38146
+ "label",
38147
+ {
38148
+ 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"}`,
38149
+ children: [
38150
+ /* @__PURE__ */ jsx(
38151
+ "input",
38152
+ {
38153
+ type: "radio",
38154
+ name: "active-crop",
38155
+ checked: activeCropIndex === index,
38156
+ onChange: () => switchToCrop(index),
38157
+ disabled: creatingVariant,
38158
+ className: "w-4 h-4 text-blue-600 hidden"
38159
+ }
38160
+ ),
38161
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
38162
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
38163
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-800 truncate", children: activeCrop.label === crop.label && crop.required === false ? /* @__PURE__ */ jsx(
38164
+ "input",
38165
+ {
38166
+ type: "text",
38167
+ value: crop.label,
38168
+ onChange: (e) => updateCropLabel(e.target.value),
38169
+ disabled: creatingVariant || crop.required,
38170
+ 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",
38171
+ placeholder: "Nombre del recorte",
38172
+ required: true,
38173
+ maxLength: 25
38174
+ }
38175
+ ) : crop.label }),
38176
+ crop.required && /* @__PURE__ */ jsx("span", { className: "text-xs px-1.5 py-0.5 bg-red-100 text-red-700 rounded", children: "Obligatorio" }),
38177
+ crop.isCustom && /* @__PURE__ */ jsx("span", { className: "text-xs px-1.5 py-0.5 bg-purple-100 text-purple-700 rounded", children: "Personalizado" })
38178
+ ] }),
38179
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500", children: [
38180
+ crop.width,
38181
+ " × ",
38182
+ crop.height,
38183
+ " px"
38184
+ ] })
38185
+ ] }),
38186
+ !crop.required && crops.length > 1 && /* @__PURE__ */ jsx(
38187
+ "button",
38188
+ {
38189
+ onClick: (e) => {
38190
+ e.preventDefault();
38191
+ removeCustomCrop(index);
38192
+ },
38193
+ 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`,
38194
+ title: "Eliminar recorte",
38195
+ children: /* @__PURE__ */ jsx(
38196
+ "span",
38197
+ {
38198
+ className: `group-hover:bg-gray-transparent-50 icon icon-close-small icon--xs`
38199
+ }
38200
+ )
38201
+ }
38202
+ )
38203
+ ]
38204
+ },
38205
+ crop.id
38206
+ )) }),
38207
+ activeCrop && /* @__PURE__ */ jsx("div", { className: "mt-4 pt-4 border-t border-gray-200 space-y-3", children: /* @__PURE__ */ jsxs("div", { children: [
38208
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-gray-700 block mb-1", children: "Dimensiones (px)" }),
38209
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
38210
+ /* @__PURE__ */ jsx(
38211
+ "input",
38212
+ {
38213
+ type: "number",
38214
+ min: "100",
38215
+ max: "5000",
38216
+ value: activeCrop.width,
38217
+ onChange: (e) => updateCropDimensions("width", e.target.value),
38218
+ onBlur: () => validateAndApplyCropDimensions("width"),
38219
+ disabled: creatingVariant,
38220
+ 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",
38221
+ placeholder: "Ancho"
38222
+ }
38223
+ ),
38224
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400 font-bold", children: "×" }),
38225
+ /* @__PURE__ */ jsx(
38226
+ "input",
38227
+ {
38228
+ type: "number",
38229
+ min: "100",
38230
+ max: "5000",
38231
+ value: activeCrop.height,
38232
+ onChange: (e) => updateCropDimensions("height", e.target.value),
38233
+ onBlur: () => validateAndApplyCropDimensions("height"),
38234
+ disabled: creatingVariant,
38235
+ 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",
38236
+ placeholder: "Alto"
38237
+ }
38238
+ )
38239
+ ] }),
38240
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mt-1", children: [
38241
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500", children: [
38242
+ "Proporción:",
38243
+ " ",
38244
+ calculatedAspectRatio ? calculatedAspectRatio.toFixed(2) : "N/A"
38245
+ ] }),
38246
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: "Min: 100px | Max: 5000px" })
38247
+ ] })
38248
+ ] }) })
38249
+ ] })
37346
38250
  ] }),
37347
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row flex-1 overflow-hidden rounded-lg gap-4 md:gap-0 cropper-main-content-area", children: [
37348
- /* @__PURE__ */ jsx("div", { className: "flex-1 relative min-w-0 order-2 md:order-1", children: /* @__PURE__ */ jsxs("div", { className: "limbo-cropper-container absolute inset-2 sm:inset-4 bg-white rounded-lg border-2 border-gray-200 shadow-lg overflow-hidden", children: [
38251
+ /* @__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: [
38252
+ /* @__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: [
37349
38253
  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: [
37350
38254
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mb-2", children: [
37351
38255
  /* @__PURE__ */ jsx("h3", { className: "text-xs font-semibold text-gray-700", children: "Vista previa" }),
@@ -37418,9 +38322,13 @@ function CropperView({
37418
38322
  "initial-coverage": "0.5",
37419
38323
  movable: true,
37420
38324
  resizable: true,
37421
- keyboard: "false",
38325
+ keyboard: "true",
37422
38326
  action: "move",
37423
- style: { cursor: "move" },
38327
+ style: {
38328
+ cursor: "move",
38329
+ minWidth: `${minCropSizes.minWidth}px`,
38330
+ minHeight: `${minCropSizes.minHeight}px`
38331
+ },
37424
38332
  "data-prevent-delete": "true",
37425
38333
  tabindex: "-1",
37426
38334
  children: [
@@ -37463,61 +38371,401 @@ function CropperView({
37463
38371
  }
37464
38372
  )
37465
38373
  ] }) }),
37466
- /* @__PURE__ */ jsx("div", { className: "limbo-cropper-controls w-full md:w-80 bg-white border-t md:border-t-0 md:border-l border-gray-200 overflow-y-auto flex-shrink-0 order-1 md:order-2 max-h-96 md:max-h-none", children: /* @__PURE__ */ jsxs("div", { className: "p-3 sm:p-4 space-y-3 sm:space-y-4 relative", children: [
37467
- /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4 md:sticky top-0 z-50", children: [
37468
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700 mb-3", children: "Acciones" }),
37469
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
38374
+ /* @__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: [
38375
+ /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [
38376
+ /* @__PURE__ */ jsx(
38377
+ "button",
38378
+ {
38379
+ onClick: toggleVisualOptions,
38380
+ className: "w-full p-2 my-1 cursor-pointer hover:bg-neutral-gray-050/50 transition-colors rounded-md",
38381
+ "aria-expanded": showVisualOptions,
38382
+ "aria-label": "Mostrar/ocultar opciones de visualizción",
38383
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
38384
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Visualización" }),
38385
+ /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(
38386
+ "span",
38387
+ {
38388
+ className: `icon ${!showVisualOptions ? "icon-chevron-down" : "icon-chevron-up"} text-center icon--xs`
38389
+ }
38390
+ ) })
38391
+ ] })
38392
+ }
38393
+ ),
38394
+ showVisualOptions && /* @__PURE__ */ jsxs("div", { className: "pb-2 space-y-2", children: [
37470
38395
  /* @__PURE__ */ jsxs(
37471
38396
  "button",
37472
38397
  {
37473
- onClick: preview,
37474
- disabled: creatingVariant || !canExport,
37475
- className: `w-full min-h-10 transition-colors ${showPreview ? "limbo-btn limbo-btn-danger" : "limbo-btn limbo-btn-primary"}`,
37476
- "aria-label": "Generar vista previa del recorte",
38398
+ onClick: toggleGrid,
38399
+ 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"}`,
38400
+ disabled: creatingVariant,
38401
+ "aria-pressed": showGrid,
38402
+ title: "Mostrar/ocultar cuadrícula de la regla de los tercios en el selector",
38403
+ "aria-label": "Activar/desactivar grid",
37477
38404
  children: [
37478
- /* @__PURE__ */ jsx(
37479
- "span",
37480
- {
37481
- className: `icon ${showPreview ? "icon-close-small-white" : "icon-search-white"}`
37482
- }
37483
- ),
37484
- showPreview ? "Cerrar previa" : "Vista previa"
38405
+ /* @__PURE__ */ jsxs("span", { className: "text-sm flex items-center", children: [
38406
+ /* @__PURE__ */ jsx("span", { className: "icon icon-area-blue me-1" }),
38407
+ " ",
38408
+ "Cuadrícula"
38409
+ ] }),
38410
+ /* @__PURE__ */ jsx("span", { className: "text-xs", children: showGrid ? /* @__PURE__ */ jsxs(Fragment, { children: [
38411
+ /* @__PURE__ */ jsx("span", { className: "icon icon-tick icon--xs align-[middle!important] -mt-0.5" }),
38412
+ " ",
38413
+ "Activo"
38414
+ ] }) : "Inactivo" })
37485
38415
  ]
37486
38416
  }
37487
38417
  ),
37488
- /* @__PURE__ */ jsx(
37489
- "button",
37490
- {
37491
- onClick: saveCrop,
37492
- disabled: creatingVariant || !cropData || !effectiveImageInfo || !canExport || !state.isReady,
37493
- className: "w-full limbo-btn limbo-btn-success min-h-10",
37494
- "aria-label": "Guardar imagen recortada",
37495
- children: creatingVariant ? /* @__PURE__ */ jsxs(Fragment, { children: [
37496
- /* @__PURE__ */ jsx("span", { className: "icon icon-save-white" }),
37497
- " ",
37498
- "Guardando..."
37499
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
37500
- /* @__PURE__ */ jsx("span", { className: "icon icon-save-white" }),
37501
- " Guardar recorte"
37502
- ] })
37503
- }
37504
- ),
37505
38418
  /* @__PURE__ */ jsxs(
37506
38419
  "button",
37507
38420
  {
37508
- onClick: resetAll,
38421
+ onClick: toggleShade,
38422
+ 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"}`,
37509
38423
  disabled: creatingVariant,
37510
- className: "w-full limbo-btn limbo-btn-secondary min-h-10",
37511
- "aria-label": "Reiniciar todas las configuraciones",
38424
+ "aria-pressed": shade,
38425
+ "aria-label": "Activar/desactivar sombreado",
37512
38426
  children: [
37513
- /* @__PURE__ */ jsx("span", { className: "icon icon-refresh-white" }),
37514
- " Reiniciar todo"
38427
+ /* @__PURE__ */ jsxs("span", { className: "text-sm flex items-center", children: [
38428
+ /* @__PURE__ */ jsx("span", { className: "icon icon-comparison-blue me-1" }),
38429
+ " ",
38430
+ "Tablero"
38431
+ ] }),
38432
+ /* @__PURE__ */ jsx("span", { className: "text-xs", children: shade ? /* @__PURE__ */ jsxs(Fragment, { children: [
38433
+ /* @__PURE__ */ jsx("span", { className: "icon icon-tick icon--xs align-[middle!important] -mt-0.5" }),
38434
+ " ",
38435
+ "Activo"
38436
+ ] }) : "Inactivo" })
37515
38437
  ]
37516
38438
  }
37517
38439
  )
37518
38440
  ] })
37519
38441
  ] }),
37520
- /* @__PURE__ */ jsxs("div", { className: "bg-gradient-to-br from-brand-blue-50 to-green-50 rounded-lg border border-blue-200 shadow-sm", children: [
38442
+ /* @__PURE__ */ jsxs(
38443
+ "div",
38444
+ {
38445
+ className: "bg-white rounded-lg border border-gray-200 p-4" + (showSelectorOptions ? "" : " bg-neutral-gray-50/50"),
38446
+ children: [
38447
+ /* @__PURE__ */ jsx(
38448
+ "button",
38449
+ {
38450
+ onClick: toggleSelectorOptions,
38451
+ className: "w-full p-2 my-1 cursor-pointer hover:bg-neutral-gray-050/50 transition-colors rounded-md",
38452
+ "aria-expanded": showSelectorOptions,
38453
+ "aria-label": "Mostrar/ocultar opciones de visualizción",
38454
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
38455
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Ajustes del selector" }),
38456
+ /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(
38457
+ "span",
38458
+ {
38459
+ className: `icon ${!showSelectorOptions ? "icon-chevron-down" : "icon-chevron-up"} text-center icon--xs`
38460
+ }
38461
+ ) })
38462
+ ] })
38463
+ }
38464
+ ),
38465
+ showSelectorOptions && /* @__PURE__ */ jsxs("div", { children: [
38466
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
38467
+ /* @__PURE__ */ jsx(
38468
+ "button",
38469
+ {
38470
+ onClick: () => setSelectionCoverage(0.5),
38471
+ className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
38472
+ disabled: creatingVariant,
38473
+ "aria-label": "Selección 50%",
38474
+ title: "Tamaño de selector 50%",
38475
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "50%" }) })
38476
+ }
38477
+ ),
38478
+ /* @__PURE__ */ jsx(
38479
+ "button",
38480
+ {
38481
+ onClick: () => setSelectionCoverage(0.7),
38482
+ className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
38483
+ disabled: creatingVariant,
38484
+ "aria-label": "Selección 70%",
38485
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "70%" }) })
38486
+ }
38487
+ ),
38488
+ /* @__PURE__ */ jsx(
38489
+ "button",
38490
+ {
38491
+ onClick: () => setSelectionCoverage(0.9),
38492
+ className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
38493
+ disabled: creatingVariant,
38494
+ "aria-label": "Selección 90%",
38495
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "90%" }) })
38496
+ }
38497
+ ),
38498
+ /* @__PURE__ */ jsx(
38499
+ "button",
38500
+ {
38501
+ onClick: () => setSelectionCoverage(1),
38502
+ className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
38503
+ disabled: creatingVariant,
38504
+ "aria-label": "Selección completa",
38505
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "100%" }) })
38506
+ }
38507
+ )
38508
+ ] }) }),
38509
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2 pt-2", children: [
38510
+ /* @__PURE__ */ jsx(
38511
+ "button",
38512
+ {
38513
+ onClick: centerSelection,
38514
+ 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",
38515
+ title: "Centrar selección",
38516
+ disabled: creatingVariant,
38517
+ "aria-label": "Centrar área de selección",
38518
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
38519
+ /* @__PURE__ */ jsx("span", { className: "icon icon-radio-button icon--sm align-[middle!important] " }),
38520
+ " ",
38521
+ "Centrar selección"
38522
+ ] }) })
38523
+ }
38524
+ ),
38525
+ /* @__PURE__ */ jsx(
38526
+ "button",
38527
+ {
38528
+ onClick: resetSelection,
38529
+ 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",
38530
+ title: "Reiniciar selección",
38531
+ disabled: creatingVariant,
38532
+ "aria-label": "Reiniciar área de selección",
38533
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
38534
+ /* @__PURE__ */ jsx("span", { className: "icon icon-refresh icon--sm lign-[middle!important] " }),
38535
+ " ",
38536
+ "Reset selección"
38537
+ ] }) })
38538
+ }
38539
+ )
38540
+ ] })
38541
+ ] })
38542
+ ]
38543
+ }
38544
+ ),
38545
+ /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [
38546
+ /* @__PURE__ */ jsx(
38547
+ "button",
38548
+ {
38549
+ onClick: toggleImageOptions,
38550
+ className: "w-full p-2 my-1 cursor-pointer hover:bg-neutral-gray-050/50 transition-colors rounded-md",
38551
+ "aria-expanded": showImageOptions,
38552
+ "aria-label": "Mostrar/ocultar opciones de visualizción",
38553
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
38554
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Transformar imagen" }),
38555
+ /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(
38556
+ "span",
38557
+ {
38558
+ className: `icon ${!showImageOptions ? "icon-chevron-down" : "icon-chevron-up"} text-center icon--xs`
38559
+ }
38560
+ ) })
38561
+ ] })
38562
+ }
38563
+ ),
38564
+ showImageOptions && /* @__PURE__ */ jsxs(Fragment, { children: [
38565
+ /* @__PURE__ */ jsxs("div", { className: "mb-4 hidden lg:block", children: [
38566
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600 mb-2", children: "Mover imagen" }),
38567
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-1", children: [
38568
+ /* @__PURE__ */ jsx("div", {}),
38569
+ /* @__PURE__ */ jsx(
38570
+ "button",
38571
+ {
38572
+ onClick: () => move(0, -10),
38573
+ className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
38574
+ title: "Mover arriba",
38575
+ disabled: creatingVariant,
38576
+ "aria-label": "Mover imagen hacia arriba",
38577
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-blue" }) })
38578
+ }
38579
+ ),
38580
+ /* @__PURE__ */ jsx("div", {}),
38581
+ /* @__PURE__ */ jsx(
38582
+ "button",
38583
+ {
38584
+ onClick: () => move(-10, 0),
38585
+ className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
38586
+ title: "Mover izquierda",
38587
+ disabled: creatingVariant,
38588
+ "aria-label": "Mover imagen hacia la izquierda",
38589
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-blue" }) })
38590
+ }
38591
+ ),
38592
+ /* @__PURE__ */ jsx(
38593
+ "button",
38594
+ {
38595
+ onClick: centerImage,
38596
+ className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
38597
+ title: "Centrar y ajustar imagen",
38598
+ disabled: creatingVariant,
38599
+ "aria-label": "Centrar y ajustar imagen",
38600
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-radio-button-blue icon-md" }) })
38601
+ }
38602
+ ),
38603
+ /* @__PURE__ */ jsx(
38604
+ "button",
38605
+ {
38606
+ onClick: () => move(10, 0),
38607
+ className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
38608
+ title: "Mover derecha",
38609
+ disabled: creatingVariant,
38610
+ "aria-label": "Mover imagen hacia la derecha",
38611
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-blue" }) })
38612
+ }
38613
+ ),
38614
+ /* @__PURE__ */ jsx("div", {}),
38615
+ /* @__PURE__ */ jsx(
38616
+ "button",
38617
+ {
38618
+ onClick: () => move(0, 10),
38619
+ className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
38620
+ title: "Mover abajo",
38621
+ disabled: creatingVariant,
38622
+ "aria-label": "Mover imagen hacia abajo",
38623
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-down-blue" }) })
38624
+ }
38625
+ ),
38626
+ /* @__PURE__ */ jsx("div", {})
38627
+ ] })
38628
+ ] }),
38629
+ /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
38630
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-600 mb-2", children: [
38631
+ "Zoom ",
38632
+ zoomInfo ? zoomInfo.percentage + "%" : ""
38633
+ ] }),
38634
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
38635
+ /* @__PURE__ */ jsx(
38636
+ "button",
38637
+ {
38638
+ onClick: () => zoom(-0.2),
38639
+ 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",
38640
+ title: "Alejar 20%",
38641
+ disabled: creatingVariant,
38642
+ "aria-label": "Alejar imagen",
38643
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-zoom-out-blue" }) })
38644
+ }
38645
+ ),
38646
+ /* @__PURE__ */ jsx(
38647
+ "button",
38648
+ {
38649
+ onClick: resetZoomOnly,
38650
+ 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",
38651
+ title: "Restablecer zoom original",
38652
+ disabled: creatingVariant,
38653
+ "aria-label": "Restablecer el zoom para que la imagen se vea con su resolución original",
38654
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-screenshot-blue" }) })
38655
+ }
38656
+ ),
38657
+ /* @__PURE__ */ jsx(
38658
+ "button",
38659
+ {
38660
+ onClick: () => zoom(0.2),
38661
+ 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",
38662
+ title: "Acercar 20%",
38663
+ disabled: creatingVariant,
38664
+ "aria-label": "Acercar imagen",
38665
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-zoom-in-blue" }) })
38666
+ }
38667
+ )
38668
+ ] })
38669
+ ] }),
38670
+ /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
38671
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600 mb-2", children: "Rotación" }),
38672
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1 *:text-brand-blue-1000 *:text-sm", children: [
38673
+ /* @__PURE__ */ jsx(
38674
+ "button",
38675
+ {
38676
+ onClick: () => rotate(-90),
38677
+ 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",
38678
+ title: "Rotar -90°",
38679
+ disabled: creatingVariant,
38680
+ "aria-label": "Rotar imagen 90 grados a la izquierda",
38681
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-reply-blue" }) })
38682
+ }
38683
+ ),
38684
+ /* @__PURE__ */ jsx(
38685
+ "button",
38686
+ {
38687
+ onClick: () => rotate(-45),
38688
+ 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",
38689
+ title: "Rotar -45°",
38690
+ disabled: creatingVariant,
38691
+ "aria-label": "Rotar imagen 45 grados a la izquierda",
38692
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "-45°" }) })
38693
+ }
38694
+ ),
38695
+ /* @__PURE__ */ jsx(
38696
+ "button",
38697
+ {
38698
+ onClick: () => rotate(45),
38699
+ 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",
38700
+ title: "Rotar +45°",
38701
+ disabled: creatingVariant,
38702
+ "aria-label": "Rotar imagen 45 grados a la derecha",
38703
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "+45°" }) })
38704
+ }
38705
+ ),
38706
+ /* @__PURE__ */ jsx(
38707
+ "button",
38708
+ {
38709
+ onClick: () => rotate(90),
38710
+ 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",
38711
+ title: "Rotar +90°",
38712
+ disabled: creatingVariant,
38713
+ "aria-label": "Rotar imagen 90 grados a la derecha",
38714
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-send-arrow-blue" }) })
38715
+ }
38716
+ )
38717
+ ] })
38718
+ ] }),
38719
+ /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
38720
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600 mb-2", children: "Voltear" }),
38721
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
38722
+ /* @__PURE__ */ jsx(
38723
+ "button",
38724
+ {
38725
+ onClick: flipHorizontal,
38726
+ 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"}`,
38727
+ title: `Voltear horizontalmente ${flipStates.horizontal ? "(activo)" : ""}`,
38728
+ disabled: creatingVariant,
38729
+ "aria-label": "Voltear imagen horizontalmente",
38730
+ "aria-pressed": flipStates.horizontal,
38731
+ 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" }) })
38732
+ }
38733
+ ),
38734
+ /* @__PURE__ */ jsx(
38735
+ "button",
38736
+ {
38737
+ onClick: flipVertical,
38738
+ 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"}`,
38739
+ title: `Voltear verticalmente ${flipStates.vertical ? "(activo)" : ""}`,
38740
+ disabled: creatingVariant,
38741
+ "aria-label": "Voltear imagen verticalmente",
38742
+ "aria-pressed": flipStates.vertical,
38743
+ 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" }) })
38744
+ }
38745
+ )
38746
+ ] })
38747
+ ] }),
38748
+ /* @__PURE__ */ jsx(
38749
+ "button",
38750
+ {
38751
+ onClick: () => {
38752
+ transform.reset();
38753
+ setFlipStates({ horizontal: false, vertical: false });
38754
+ },
38755
+ 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",
38756
+ title: "Reiniciar transformaciones",
38757
+ disabled: creatingVariant,
38758
+ "aria-label": "Reiniciar todas las transformaciones de la imagen",
38759
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
38760
+ /* @__PURE__ */ jsx("span", { className: "icon icon-refresh-blue icon--sm align-[middle!important] " }),
38761
+ " ",
38762
+ "Reiniciar ajustes"
38763
+ ] }) })
38764
+ }
38765
+ )
38766
+ ] })
38767
+ ] }),
38768
+ /* @__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: [
37521
38769
  /* @__PURE__ */ jsxs(
37522
38770
  "button",
37523
38771
  {
@@ -37546,7 +38794,7 @@ function CropperView({
37546
38794
  /* @__PURE__ */ jsxs("div", { children: [
37547
38795
  "• ",
37548
38796
  /* @__PURE__ */ jsx("strong", { children: "Arrastra la imagen:" }),
37549
- " Haz clic fuera del área de selección y arrastra"
38797
+ " Haz clic sobre la imagen del área de selección y arrastra"
37550
38798
  ] }),
37551
38799
  /* @__PURE__ */ jsxs("div", { children: [
37552
38800
  "• ",
@@ -37578,11 +38826,6 @@ function CropperView({
37578
38826
  "• ",
37579
38827
  /* @__PURE__ */ jsx("strong", { children: "Tamaños rápidos:" }),
37580
38828
  " Usa los botones 50%, 70%, 90%, 100%"
37581
- ] }),
37582
- /* @__PURE__ */ jsxs("div", { children: [
37583
- "• ",
37584
- /* @__PURE__ */ jsx("strong", { children: "Proporciones:" }),
37585
- " Selecciona ratios predefinidos (1:1, 16:9, etc.)"
37586
38829
  ] })
37587
38830
  ] })
37588
38831
  ] }),
@@ -37620,9 +38863,14 @@ function CropperView({
37620
38863
  ] }),
37621
38864
  /* @__PURE__ */ jsxs("div", { children: [
37622
38865
  "• ",
37623
- /* @__PURE__ */ jsx("strong", { children: "Grid:" }),
38866
+ /* @__PURE__ */ jsx("strong", { children: "Cuadricula:" }),
37624
38867
  " Actívalo para aplicar la regla de los tercios"
37625
38868
  ] }),
38869
+ /* @__PURE__ */ jsxs("div", { children: [
38870
+ "• ",
38871
+ /* @__PURE__ */ jsx("strong", { children: "Tablero:" }),
38872
+ " Actívalo para cuadrar medjor las medidas o como ayuda visual para imagenes con transparencia"
38873
+ ] }),
37626
38874
  /* @__PURE__ */ jsxs("div", { children: [
37627
38875
  "• ",
37628
38876
  /* @__PURE__ */ jsx("strong", { children: "Transformaciones:" }),
@@ -37651,366 +38899,139 @@ function CropperView({
37651
38899
  ] })
37652
38900
  ] })
37653
38901
  ] })
37654
- ] }),
37655
- /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-3 sm:p-4", children: [
37656
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700 mb-3", children: "Proporción" }),
37657
- /* @__PURE__ */ jsx("div", { className: "limbo-cropper-aspect-buttons grid grid-cols-2 md:grid-cols-1 gap-2 md:hidden", children: allowedAspectRatios.map((ratio) => /* @__PURE__ */ jsx(
37658
- "button",
37659
- {
37660
- onClick: () => handleAspectRatio(ratio.value),
37661
- className: `p-2 text-xs rounded border transition-colors cursor-pointer ${aspectRatio === ratio.value ? "bg-blue-100 border-blue-300 text-blue-800" : "bg-gray-100 border-gray-300 text-gray-700"}`,
37662
- disabled: creatingVariant,
37663
- title: `Cambiar a proporción ${ratio.label}`,
37664
- children: ratio.label
37665
- },
37666
- ratio.value
37667
- )) }),
37668
- /* @__PURE__ */ jsx(
37669
- "select",
37670
- {
37671
- value: aspectRatio,
37672
- onChange: (e) => handleAspectRatio(e.target.value),
37673
- className: "w-full form-control hidden md:block",
37674
- disabled: creatingVariant,
37675
- "aria-label": "Seleccionar proporción de aspecto",
37676
- children: allowedAspectRatios.map((ratio) => /* @__PURE__ */ jsx("option", { value: ratio.value, children: ratio.label }, ratio.value))
37677
- }
37678
- )
37679
- ] }),
37680
- /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [
37681
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700 mb-3", children: "Visualización" }),
37682
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
37683
- /* @__PURE__ */ jsxs(
37684
- "button",
37685
- {
37686
- onClick: toggleGrid,
37687
- 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"}`,
37688
- disabled: creatingVariant,
37689
- "aria-pressed": showGrid,
37690
- "aria-label": "Activar/desactivar grid",
37691
- children: [
37692
- /* @__PURE__ */ jsxs("span", { className: "text-sm flex items-center", children: [
37693
- /* @__PURE__ */ jsx("span", { className: "icon icon-area-blue me-1" }),
37694
- " Grid"
37695
- ] }),
37696
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: showGrid ? /* @__PURE__ */ jsxs(Fragment, { children: [
37697
- /* @__PURE__ */ jsx("span", { className: "icon icon-tick icon--xs align-[middle!important] -mt-0.5" }),
37698
- " ",
37699
- "Activo"
37700
- ] }) : "Inactivo" })
37701
- ]
37702
- }
37703
- ),
37704
- /* @__PURE__ */ jsxs(
37705
- "button",
37706
- {
37707
- onClick: toggleShade,
37708
- 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"}`,
37709
- disabled: creatingVariant,
37710
- "aria-pressed": shade,
37711
- "aria-label": "Activar/desactivar sombreado",
37712
- children: [
37713
- /* @__PURE__ */ jsxs("span", { className: "text-sm flex items-center", children: [
37714
- /* @__PURE__ */ jsx("span", { className: "icon icon-comparison-blue me-1" }),
37715
- " ",
37716
- "Sombreado"
37717
- ] }),
37718
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: shade ? /* @__PURE__ */ jsxs(Fragment, { children: [
37719
- /* @__PURE__ */ jsx("span", { className: "icon icon-tick icon--xs align-[middle!important] -mt-0.5" }),
37720
- " ",
37721
- "Activo"
37722
- ] }) : "Inactivo" })
37723
- ]
37724
- }
37725
- )
38902
+ ] })
38903
+ ] }) })
38904
+ ] }),
38905
+ /* @__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: [
38906
+ /* @__PURE__ */ jsx(
38907
+ "button",
38908
+ {
38909
+ onClick: preview,
38910
+ disabled: creatingVariant || !canExport,
38911
+ className: `px-6 py-2.5 min-h-[44px] transition-colors order-2 ${showPreview ? "limbo-btn limbo-btn-danger" : "limbo-btn limbo-btn-info"}`,
38912
+ "aria-label": "Generar vista previa del recorte",
38913
+ title: "Mostar/Ocultar vista previa del recorte",
38914
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center", children: [
38915
+ /* @__PURE__ */ jsx("span", { className: `icon md:mr-1 icon-search-white` }),
38916
+ /* @__PURE__ */ jsx("span", { className: "hidden md:block text-nowrap", children: showPreview ? "Cerrar previa" : "Vista previa" })
37726
38917
  ] })
37727
- ] }),
37728
- /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [
37729
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700 mb-3", children: "Ajustes del selector" }),
37730
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
38918
+ }
38919
+ ),
38920
+ /* @__PURE__ */ jsx(
38921
+ "button",
38922
+ {
38923
+ onClick: handleDownload,
38924
+ disabled: creatingVariant || !canExport,
38925
+ className: "limbo-btn limbo-btn-primary px-6 py-2.5 min-h-[44px] order-3",
38926
+ "aria-label": "Descargar recorte sin guardar",
38927
+ title: "Descargar recorte sin guardar",
38928
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center", children: [
38929
+ /* @__PURE__ */ jsx("span", { className: "icon icon-download-white md:mr-1" }),
38930
+ /* @__PURE__ */ jsx("span", { className: "hidden md:block text-nowrap", children: "Descargar" })
38931
+ ] })
38932
+ }
38933
+ ),
38934
+ /* @__PURE__ */ jsx(
38935
+ "button",
38936
+ {
38937
+ onClick: saveCrop,
38938
+ disabled: creatingVariant || !cropData || !effectiveImageInfo || !canExport || !state.isReady,
38939
+ className: "limbo-btn limbo-btn-success px-6 py-2.5 min-h-[44px] order-4",
38940
+ "aria-label": "Guardar imagen recortada",
38941
+ title: "Guardar imagen recortada",
38942
+ children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: creatingVariant ? /* @__PURE__ */ jsxs(Fragment, { children: [
38943
+ /* @__PURE__ */ jsx("span", { className: "hidden md:block text-nowrap icon icon-save-white md:mr-1" }),
38944
+ "Guardando..."
38945
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
38946
+ /* @__PURE__ */ jsx("span", { className: "icon icon-save-white md:mr-1" }),
38947
+ /* @__PURE__ */ jsx("span", { className: "hidden md:block text-nowrap", children: "Guardar recorte" })
38948
+ ] }) })
38949
+ }
38950
+ )
38951
+ ] }) }),
38952
+ 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: [
38953
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 border-b border-gray-200", children: [
38954
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-800", children: pendingAction === "save" ? "Seleccionar recortes a guardar" : "Seleccionar recortes a descargar" }),
38955
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-600 mt-1", children: [
38956
+ "Marca los recortes que deseas ",
38957
+ pendingAction === "save" ? "guardar" : "descargar"
38958
+ ] })
38959
+ ] }),
38960
+ /* @__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(
38961
+ "label",
38962
+ {
38963
+ 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" : ""}`,
38964
+ children: [
37731
38965
  /* @__PURE__ */ jsx(
37732
- "button",
38966
+ "input",
37733
38967
  {
37734
- onClick: centerSelection,
37735
- 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",
37736
- title: "Centrar selección",
37737
- disabled: creatingVariant,
37738
- "aria-label": "Centrar área de selección",
37739
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
37740
- /* @__PURE__ */ jsx("span", { className: "icon icon-radio-button icon--sm align-[middle!important] " }),
37741
- " ",
37742
- "Centrar selección"
37743
- ] }) })
38968
+ type: "checkbox",
38969
+ checked: selectedCropsForAction.includes(index),
38970
+ disabled: crop.required,
38971
+ onChange: (e) => {
38972
+ if (e.target.checked) {
38973
+ setSelectedCropsForAction((prev) => [...prev, index]);
38974
+ } else {
38975
+ setSelectedCropsForAction((prev) => prev.filter((i) => i !== index));
38976
+ }
38977
+ },
38978
+ className: "w-4 h-4 text-blue-600"
37744
38979
  }
37745
38980
  ),
37746
- /* @__PURE__ */ jsx(
37747
- "button",
37748
- {
37749
- onClick: resetSelection,
37750
- 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",
37751
- title: "Reiniciar selección",
37752
- disabled: creatingVariant,
37753
- "aria-label": "Reiniciar área de selección",
37754
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
37755
- /* @__PURE__ */ jsx("span", { className: "icon icon-refresh icon--sm lign-[middle!important] " }),
37756
- " ",
37757
- "Reset selección"
37758
- ] }) })
37759
- }
37760
- )
37761
- ] }),
37762
- /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
37763
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600 mb-2", children: "Tamaños rápidos" }),
37764
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-1", children: [
37765
- /* @__PURE__ */ jsx(
37766
- "button",
37767
- {
37768
- onClick: () => setSelectionCoverage(0.5),
37769
- className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
37770
- disabled: creatingVariant,
37771
- "aria-label": "Selección 50%",
37772
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "50%" }) })
37773
- }
37774
- ),
37775
- /* @__PURE__ */ jsx(
37776
- "button",
37777
- {
37778
- onClick: () => setSelectionCoverage(0.7),
37779
- className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
37780
- disabled: creatingVariant,
37781
- "aria-label": "Selección 70%",
37782
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "70%" }) })
37783
- }
37784
- ),
37785
- /* @__PURE__ */ jsx(
37786
- "button",
37787
- {
37788
- onClick: () => setSelectionCoverage(0.9),
37789
- className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
37790
- disabled: creatingVariant,
37791
- "aria-label": "Selección 90%",
37792
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "90%" }) })
37793
- }
37794
- ),
37795
- /* @__PURE__ */ jsx(
37796
- "button",
37797
- {
37798
- onClick: () => setSelectionCoverage(1),
37799
- className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
37800
- disabled: creatingVariant,
37801
- "aria-label": "Selección completa",
37802
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "100%" }) })
37803
- }
37804
- )
37805
- ] })
37806
- ] })
37807
- ] }),
37808
- /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [
37809
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-700 mb-3", children: "Transformar imagen" }),
37810
- /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
37811
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600 mb-2", children: "Mover imagen" }),
37812
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-1", children: [
37813
- /* @__PURE__ */ jsx("div", {}),
37814
- /* @__PURE__ */ jsx(
37815
- "button",
37816
- {
37817
- onClick: () => move(0, -10),
37818
- className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
37819
- title: "Mover arriba",
37820
- disabled: creatingVariant,
37821
- "aria-label": "Mover imagen hacia arriba",
37822
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-blue" }) })
37823
- }
37824
- ),
37825
- /* @__PURE__ */ jsx("div", {}),
37826
- /* @__PURE__ */ jsx(
37827
- "button",
37828
- {
37829
- onClick: () => move(-10, 0),
37830
- className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
37831
- title: "Mover izquierda",
37832
- disabled: creatingVariant,
37833
- "aria-label": "Mover imagen hacia la izquierda",
37834
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-blue" }) })
37835
- }
37836
- ),
37837
- /* @__PURE__ */ jsx(
37838
- "button",
37839
- {
37840
- onClick: centerImage,
37841
- className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
37842
- title: "Centrar y ajustar imagen",
37843
- disabled: creatingVariant,
37844
- "aria-label": "Centrar y ajustar imagen",
37845
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-radio-button-blue icon-md" }) })
37846
- }
37847
- ),
37848
- /* @__PURE__ */ jsx(
37849
- "button",
37850
- {
37851
- onClick: () => move(10, 0),
37852
- className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
37853
- title: "Mover derecha",
37854
- disabled: creatingVariant,
37855
- "aria-label": "Mover imagen hacia la derecha",
37856
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-blue" }) })
37857
- }
37858
- ),
37859
- /* @__PURE__ */ jsx("div", {}),
37860
- /* @__PURE__ */ jsx(
37861
- "button",
37862
- {
37863
- onClick: () => move(0, 10),
37864
- className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
37865
- title: "Mover abajo",
37866
- disabled: creatingVariant,
37867
- "aria-label": "Mover imagen hacia abajo",
37868
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-down-blue" }) })
37869
- }
37870
- ),
37871
- /* @__PURE__ */ jsx("div", {})
37872
- ] })
37873
- ] }),
37874
- /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
37875
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-600 mb-2", children: [
37876
- "Zoom ",
37877
- zoomInfo ? zoomInfo.percentage + "%" : ""
37878
- ] }),
37879
- /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
37880
- /* @__PURE__ */ jsx(
37881
- "button",
37882
- {
37883
- onClick: () => zoom(-0.2),
37884
- 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",
37885
- title: "Alejar 20%",
37886
- disabled: creatingVariant,
37887
- "aria-label": "Alejar imagen",
37888
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-zoom-out-blue" }) })
37889
- }
37890
- ),
37891
- /* @__PURE__ */ jsx(
37892
- "button",
37893
- {
37894
- onClick: resetZoomOnly,
37895
- 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",
37896
- title: "Restablecer zoom original",
37897
- disabled: creatingVariant,
37898
- "aria-label": "Restablecer el zoom para que la imagen se vea con su resolución original",
37899
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-screenshot-blue" }) })
37900
- }
37901
- ),
37902
- /* @__PURE__ */ jsx(
37903
- "button",
37904
- {
37905
- onClick: () => zoom(0.2),
37906
- 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",
37907
- title: "Acercar 20%",
37908
- disabled: creatingVariant,
37909
- "aria-label": "Acercar imagen",
37910
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-zoom-in-blue" }) })
37911
- }
37912
- )
37913
- ] })
37914
- ] }),
37915
- /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
37916
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600 mb-2", children: "Rotación" }),
37917
- /* @__PURE__ */ jsxs("div", { className: "flex gap-1 *:text-brand-blue-1000 *:text-sm", children: [
37918
- /* @__PURE__ */ jsx(
37919
- "button",
37920
- {
37921
- onClick: () => rotate(-90),
37922
- 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",
37923
- title: "Rotar -90°",
37924
- disabled: creatingVariant,
37925
- "aria-label": "Rotar imagen 90 grados a la izquierda",
37926
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-reply-blue" }) })
37927
- }
37928
- ),
37929
- /* @__PURE__ */ jsx(
37930
- "button",
37931
- {
37932
- onClick: () => rotate(-45),
37933
- 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",
37934
- title: "Rotar -45°",
37935
- disabled: creatingVariant,
37936
- "aria-label": "Rotar imagen 45 grados a la izquierda",
37937
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "-45°" }) })
37938
- }
37939
- ),
37940
- /* @__PURE__ */ jsx(
37941
- "button",
37942
- {
37943
- onClick: () => rotate(45),
37944
- 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",
37945
- title: "Rotar +45°",
37946
- disabled: creatingVariant,
37947
- "aria-label": "Rotar imagen 45 grados a la derecha",
37948
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "+45°" }) })
37949
- }
37950
- ),
37951
- /* @__PURE__ */ jsx(
37952
- "button",
37953
- {
37954
- onClick: () => rotate(90),
37955
- 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",
37956
- title: "Rotar +90°",
37957
- disabled: creatingVariant,
37958
- "aria-label": "Rotar imagen 90 grados a la derecha",
37959
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-send-arrow-blue" }) })
37960
- }
37961
- )
37962
- ] })
37963
- ] }),
37964
- /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
37965
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600 mb-2", children: "Voltear" }),
37966
- /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
37967
- /* @__PURE__ */ jsx(
37968
- "button",
37969
- {
37970
- onClick: flipHorizontal,
37971
- 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"}`,
37972
- title: `Voltear horizontalmente ${flipStates.horizontal ? "(activo)" : ""}`,
37973
- disabled: creatingVariant,
37974
- "aria-label": "Voltear imagen horizontalmente",
37975
- "aria-pressed": flipStates.horizontal,
37976
- 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" }) })
37977
- }
37978
- ),
37979
- /* @__PURE__ */ jsx(
37980
- "button",
37981
- {
37982
- onClick: flipVertical,
37983
- 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"}`,
37984
- title: `Voltear verticalmente ${flipStates.vertical ? "(activo)" : ""}`,
37985
- disabled: creatingVariant,
37986
- "aria-label": "Voltear imagen verticalmente",
37987
- "aria-pressed": flipStates.vertical,
37988
- 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" }) })
37989
- }
37990
- )
38981
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
38982
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
38983
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-800 truncate", children: crop.label }),
38984
+ crop.required && /* @__PURE__ */ jsx("span", { className: "text-xs px-1.5 py-0.5 bg-red-100 text-red-700 rounded", children: "Obligatorio" })
38985
+ ] }),
38986
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500", children: [
38987
+ crop.width,
38988
+ " × ",
38989
+ crop.height,
38990
+ " px"
38991
+ ] })
37991
38992
  ] })
37992
- ] }),
37993
- /* @__PURE__ */ jsx(
37994
- "button",
37995
- {
37996
- onClick: () => {
37997
- transform.reset();
37998
- setFlipStates({ horizontal: false, vertical: false });
37999
- },
38000
- 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",
38001
- title: "Reiniciar transformaciones",
38002
- disabled: creatingVariant,
38003
- "aria-label": "Reiniciar todas las transformaciones de la imagen",
38004
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
38005
- /* @__PURE__ */ jsx("span", { className: "icon icon-refresh-blue icon--sm align-[middle!important] " }),
38006
- " ",
38007
- "Reiniciar ajustes"
38008
- ] }) })
38009
- }
38010
- )
38011
- ] })
38012
- ] }) })
38013
- ] })
38993
+ ]
38994
+ },
38995
+ crop.id
38996
+ )) }) }),
38997
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 border-t border-gray-200 flex gap-3 justify-end", children: [
38998
+ /* @__PURE__ */ jsx(
38999
+ "button",
39000
+ {
39001
+ onClick: () => {
39002
+ setShowConfirmModal(false);
39003
+ setPendingAction(null);
39004
+ setSelectedCropsForAction([]);
39005
+ },
39006
+ className: "limbo-btn limbo-btn-secondary px-4 py-2",
39007
+ children: "Cancelar"
39008
+ }
39009
+ ),
39010
+ /* @__PURE__ */ jsxs(
39011
+ "button",
39012
+ {
39013
+ onClick: async () => {
39014
+ setShowConfirmModal(false);
39015
+ if (pendingAction === "save") {
39016
+ await performSaveCrop();
39017
+ } else {
39018
+ await performDownload();
39019
+ }
39020
+ setPendingAction(null);
39021
+ setSelectedCropsForAction([]);
39022
+ },
39023
+ disabled: selectedCropsForAction.length === 0,
39024
+ className: "limbo-btn limbo-btn-primary px-4 py-2 disabled:opacity-50 disabled:cursor-not-allowed",
39025
+ children: [
39026
+ pendingAction === "save" ? "Guardar" : "Descargar",
39027
+ " (",
39028
+ selectedCropsForAction.length,
39029
+ ")"
39030
+ ]
39031
+ }
39032
+ )
39033
+ ] })
39034
+ ] }) })
38014
39035
  ] });
38015
39036
  }
38016
39037
  function Pagination({
@@ -38256,7 +39277,7 @@ function useImages(apiKey, prod = false, params = {}) {
38256
39277
  try {
38257
39278
  const data = await listAssets(params);
38258
39279
  if (!isMounted) return;
38259
- const result = data.result || [];
39280
+ const result = data.result || data.data || [];
38260
39281
  const paginationData = data.pagination || null;
38261
39282
  setImages(result);
38262
39283
  setPagination(paginationData);
@@ -38320,6 +39341,18 @@ function useTokenExpiration() {
38320
39341
  handleTokenExpiredClose
38321
39342
  };
38322
39343
  }
39344
+ function useDebounce(value, delay = 500) {
39345
+ const [debouncedValue, setDebouncedValue] = useState(value);
39346
+ useEffect(() => {
39347
+ const handler = setTimeout(() => {
39348
+ setDebouncedValue(value);
39349
+ }, delay);
39350
+ return () => {
39351
+ clearTimeout(handler);
39352
+ };
39353
+ }, [value, delay]);
39354
+ return debouncedValue;
39355
+ }
38323
39356
  function App({
38324
39357
  apiKey,
38325
39358
  prod = false,
@@ -38352,6 +39385,14 @@ function App({
38352
39385
  sessionStorage.removeItem("limbo_portals_images");
38353
39386
  sessionStorage.removeItem("limbo_portals_portalResults");
38354
39387
  sessionStorage.removeItem("limbo_portals_paginationInfo");
39388
+ const [galleryFilters, setGalleryFilters] = useState({
39389
+ name: "",
39390
+ uploadedBy: "",
39391
+ dateFrom: "",
39392
+ dateTo: ""
39393
+ });
39394
+ const debouncedName = useDebounce(galleryFilters.name, 500);
39395
+ const debouncedUploadedBy = useDebounce(galleryFilters.uploadedBy, 500);
38355
39396
  const getFilteredFeatures = () => {
38356
39397
  switch (modeUI) {
38357
39398
  case "gallery-only":
@@ -38395,6 +39436,14 @@ function App({
38395
39436
  error: deleteError,
38396
39437
  reset: resetDelete
38397
39438
  } = useDeleteImage();
39439
+ const apiParams = {
39440
+ limit: itemsPerPage,
39441
+ page: currentPage,
39442
+ ...debouncedName && { name: debouncedName },
39443
+ ...debouncedUploadedBy && { uploadedBy: debouncedUploadedBy },
39444
+ ...galleryFilters.dateFrom && { dateFrom: galleryFilters.dateFrom },
39445
+ ...galleryFilters.dateTo && { dateTo: galleryFilters.dateTo }
39446
+ };
38398
39447
  const {
38399
39448
  images,
38400
39449
  loading: loadingImages,
@@ -38402,7 +39451,7 @@ function App({
38402
39451
  pagination,
38403
39452
  invalidateCache,
38404
39453
  setImages
38405
- } = useImages(apiKey, prod, { limit: itemsPerPage, page: currentPage });
39454
+ } = useImages(apiKey, prod, apiParams);
38406
39455
  const { refreshVariants } = useImageVariants();
38407
39456
  const handleVariantCreated = (assetId, variantData) => {
38408
39457
  refreshVariants(assetId);
@@ -38446,7 +39495,7 @@ function App({
38446
39495
  }
38447
39496
  };
38448
39497
  const handleDelete = async (imageId) => {
38449
- if (!confirm("¿Estás seguro de que quieres eliminar esta imagen?")) {
39498
+ if (!confirm("¿Estás seguro de que deseas eliminar esta imagen? Esta acción también eliminará todos sus recortes.")) {
38450
39499
  return;
38451
39500
  }
38452
39501
  const success = await deleteImg(imageId);
@@ -38484,8 +39533,148 @@ function App({
38484
39533
  if (tabId !== "upload") resetUpload();
38485
39534
  if (tabId !== "gallery") resetDelete();
38486
39535
  };
38487
- return /* @__PURE__ */ jsxs("div", { className: "limbo-card-parent max-w-7xl mx-auto my-8 p-6 shadow-xl w-full", children: [
38488
- ui.showTabs && /* @__PURE__ */ jsx(Tabs, { tabs, active: activeTab, onChange: handleTabChange }),
39536
+ const determineScenario = () => {
39537
+ if (modeUI === "crop-only" || activeFeatures.length === 1 && activeFeatures[0] === "cropper") {
39538
+ return "crop-only";
39539
+ }
39540
+ if (activeFeatures.includes("gallery")) {
39541
+ return "with-gallery";
39542
+ }
39543
+ if (activeFeatures.includes("upload") && !activeFeatures.includes("gallery")) {
39544
+ return "upload-only";
39545
+ }
39546
+ return "with-gallery";
39547
+ };
39548
+ const handleCropSave = (result) => {
39549
+ const scenario = determineScenario();
39550
+ const globalConfig2 = window.limboCore?.config?.getGlobal() || {};
39551
+ const mode = globalConfig2.mode || "embed";
39552
+ const autoHide = globalConfig2.autoHideOnComplete || false;
39553
+ const crops = Array.isArray(result) ? result : [result];
39554
+ if (scenario === "with-gallery") {
39555
+ invalidateCache();
39556
+ }
39557
+ if (callbacks.onCropsSaved) {
39558
+ callbacks.onCropsSaved({
39559
+ crops,
39560
+ assetId: selectedImage?.id,
39561
+ instanceId
39562
+ });
39563
+ }
39564
+ if (window.limboCore?.events) {
39565
+ window.limboCore.events.emit("cropsSaved", {
39566
+ crops,
39567
+ assetId: selectedImage?.id,
39568
+ instanceId
39569
+ });
39570
+ }
39571
+ switch (scenario) {
39572
+ case "with-gallery":
39573
+ setSelectedImage(null);
39574
+ setActiveTab("gallery");
39575
+ break;
39576
+ case "upload-only":
39577
+ if (mode === "modal") {
39578
+ window.limboCore?.modals?.closeAllModals();
39579
+ } else {
39580
+ setSelectedImage(null);
39581
+ setActiveTab("upload");
39582
+ }
39583
+ break;
39584
+ case "crop-only":
39585
+ if (mode === "modal") {
39586
+ if (callbacks.onCropperComplete) {
39587
+ callbacks.onCropperComplete({
39588
+ crops,
39589
+ instanceId
39590
+ });
39591
+ }
39592
+ if (window.limboCore?.events) {
39593
+ window.limboCore.events.emit("cropperComplete", {
39594
+ crops,
39595
+ instanceId
39596
+ });
39597
+ }
39598
+ window.limboCore?.modals?.closeAllModals();
39599
+ } else {
39600
+ if (callbacks.onCropperComplete) {
39601
+ callbacks.onCropperComplete({
39602
+ crops,
39603
+ instanceId
39604
+ });
39605
+ }
39606
+ if (window.limboCore?.events) {
39607
+ window.limboCore.events.emit("cropperComplete", {
39608
+ crops,
39609
+ instanceId
39610
+ });
39611
+ }
39612
+ if (autoHide) {
39613
+ const container = document.querySelector(`#limbo-instance-${instanceId}`);
39614
+ if (container) container.style.display = "none";
39615
+ }
39616
+ }
39617
+ break;
39618
+ }
39619
+ };
39620
+ const handleCropCancel = () => {
39621
+ const scenario = determineScenario();
39622
+ const globalConfig2 = window.limboCore?.config?.getGlobal() || {};
39623
+ const mode = globalConfig2.mode || "embed";
39624
+ if (callbacks.onCropperCancelled) {
39625
+ callbacks.onCropperCancelled({
39626
+ assetId: selectedImage?.id,
39627
+ instanceId
39628
+ });
39629
+ }
39630
+ if (window.limboCore?.events) {
39631
+ window.limboCore.events.emit("cropperCancelled", {
39632
+ assetId: selectedImage?.id,
39633
+ instanceId
39634
+ });
39635
+ }
39636
+ switch (scenario) {
39637
+ case "with-gallery":
39638
+ setSelectedImage(null);
39639
+ setActiveTab("gallery");
39640
+ break;
39641
+ case "upload-only":
39642
+ if (mode === "modal") {
39643
+ window.limboCore?.modals?.closeAllModals();
39644
+ } else {
39645
+ setSelectedImage(null);
39646
+ setActiveTab("upload");
39647
+ }
39648
+ break;
39649
+ case "crop-only":
39650
+ if (mode === "modal") {
39651
+ window.limboCore?.modals?.closeAllModals();
39652
+ } else {
39653
+ if (window.limboCore?.events) {
39654
+ window.limboCore.events.emit("cropperClosed", {
39655
+ instanceId,
39656
+ reason: "cancelled"
39657
+ });
39658
+ }
39659
+ }
39660
+ break;
39661
+ }
39662
+ };
39663
+ const handleCropError = (error) => {
39664
+ if (callbacks.onCropperError) {
39665
+ callbacks.onCropperError(error, instanceId);
39666
+ }
39667
+ if (window.limboCore?.events) {
39668
+ window.limboCore.events.emit("cropperError", {
39669
+ error,
39670
+ assetId: selectedImage?.id,
39671
+ instanceId
39672
+ });
39673
+ }
39674
+ console.error("Cropper error:", error);
39675
+ };
39676
+ return /* @__PURE__ */ jsxs("div", { className: "limbo-card-parent max-w-7xl mx-auto my-8 p-6 shadow-xs w-full", children: [
39677
+ ui.showTabs && activeTab != "cropper" && /* @__PURE__ */ jsx(Tabs, { tabs, active: activeTab, onChange: handleTabChange }),
38489
39678
  imagesError && /* @__PURE__ */ jsxs("div", { className: "alert alert-danger", children: [
38490
39679
  "Error al cargar imágenes: ",
38491
39680
  imagesError
@@ -38499,8 +39688,6 @@ function App({
38499
39688
  /* @__PURE__ */ jsx(
38500
39689
  Gallery,
38501
39690
  {
38502
- apiKey,
38503
- prod,
38504
39691
  onSelect: handleImageSelect,
38505
39692
  onCrop: handleImageCrop,
38506
39693
  onDelete: isActionAllowed("delete") ? handleDelete : null,
@@ -38508,6 +39695,13 @@ function App({
38508
39695
  images,
38509
39696
  loading: loadingImages,
38510
39697
  error: imagesError,
39698
+ filters: galleryFilters,
39699
+ onFiltersChange: (newFilters) => {
39700
+ setGalleryFilters(newFilters);
39701
+ setCurrentPage(1);
39702
+ },
39703
+ filterConfig: ui.galleryFilters || {},
39704
+ loadingConfig: ui.galleryLoading || {},
38511
39705
  allowedActions: {
38512
39706
  select: isActionAllowed("select"),
38513
39707
  download: isActionAllowed("download"),
@@ -38551,16 +39745,10 @@ function App({
38551
39745
  CropperView,
38552
39746
  {
38553
39747
  image: selectedImage,
38554
- onSave: () => {
38555
- invalidateCache();
38556
- setSelectedImage(null);
38557
- setActiveTab("gallery");
38558
- },
38559
- onCancel: () => {
38560
- setSelectedImage(null);
38561
- setActiveTab("gallery");
38562
- },
39748
+ onSave: handleCropSave,
39749
+ onCancel: handleCropCancel,
38563
39750
  onDelete: () => handleDelete(selectedImage?.id),
39751
+ onError: handleCropError,
38564
39752
  deleting,
38565
39753
  onVariantCreated: handleVariantCreated
38566
39754
  }
@@ -38865,7 +40053,12 @@ class LimboInstance {
38865
40053
  id: `limbo-component-container-${this.id}`,
38866
40054
  className: "limbo-instance limbo-component-container-wrapper py-3",
38867
40055
  "data-limbo-id": this.id,
38868
- "data-limbo-mode": this.config.mode
40056
+ "data-limbo-mode": this.config.mode,
40057
+ "data-limbo-version": "2.0",
40058
+ "data-limbo-isolated": "true",
40059
+ // Agregar aria-label para accesibilidad
40060
+ "aria-label": "Limbo Image Manager",
40061
+ "role": "region"
38869
40062
  },
38870
40063
  React.createElement(App, {
38871
40064
  apiKey: this.config.apiKey || null,
@@ -41885,12 +43078,294 @@ class LimboCore {
41885
43078
  });
41886
43079
  modalInstance.open();
41887
43080
  }
43081
+ // ========================================
43082
+ // MÉTODOS PREFAB - CASOS DE USO COMUNES
43083
+ // ========================================
43084
+ /**
43085
+ * 🎨 Abrir galería de imágenes en modal
43086
+ * Caso de uso: Selector rápido de imágenes existentes
43087
+ *
43088
+ * @param {Object} options - Opciones de configuración
43089
+ * @param {Function} options.onSelect - Callback cuando se selecciona imagen
43090
+ * @param {Object} options.filters - Filtros pre-aplicados
43091
+ * @returns {Object} Instancia del modal
43092
+ *
43093
+ * @example
43094
+ * Limbo.openGallery({
43095
+ * onSelect: (image) => console.log('Imagen seleccionada:', image)
43096
+ * });
43097
+ */
43098
+ openGallery(options = {}) {
43099
+ const instance = this.create({
43100
+ mode: "modal",
43101
+ modeUI: "gallery-only",
43102
+ features: ["gallery"],
43103
+ autoDestroy: true,
43104
+ modal: {
43105
+ title: options.title || "Seleccionar Imagen",
43106
+ size: options.size || "large"
43107
+ },
43108
+ callbacks: {
43109
+ onSelect: (imageData) => {
43110
+ options.onSelect?.(imageData);
43111
+ if (options.autoClose !== false) {
43112
+ instance.close();
43113
+ }
43114
+ }
43115
+ },
43116
+ ...options
43117
+ });
43118
+ instance.open();
43119
+ return instance;
43120
+ }
43121
+ /**
43122
+ * 📤 Abrir uploader en modal
43123
+ * Caso de uso: Subida rápida de nueva imagen
43124
+ *
43125
+ * @param {Object} options - Opciones de configuración
43126
+ * @param {Function} options.onUpload - Callback cuando se sube imagen
43127
+ * @param {Boolean} options.redirectToGallery - Si true, muestra galería tras subir
43128
+ * @returns {Object} Instancia del modal
43129
+ *
43130
+ * @example
43131
+ * Limbo.openUploader({
43132
+ * onUpload: (image) => console.log('Imagen subida:', image),
43133
+ * redirectToGallery: false
43134
+ * });
43135
+ */
43136
+ openUploader(options = {}) {
43137
+ const features = options.redirectToGallery !== false ? ["upload", "gallery"] : ["upload"];
43138
+ const instance = this.create({
43139
+ mode: "modal",
43140
+ modeUI: options.redirectToGallery !== false ? "full" : "upload-only",
43141
+ features,
43142
+ autoDestroy: true,
43143
+ modal: {
43144
+ title: options.title || "Subir Imagen",
43145
+ size: options.size || "medium"
43146
+ },
43147
+ callbacks: {
43148
+ onUpload: (imageData) => {
43149
+ options.onUpload?.(imageData);
43150
+ if (options.autoClose !== false && !options.redirectToGallery) {
43151
+ instance.close();
43152
+ }
43153
+ }
43154
+ },
43155
+ ...options
43156
+ });
43157
+ instance.open();
43158
+ return instance;
43159
+ }
43160
+ /**
43161
+ * ✂️ Abrir cropper standalone con imagen externa
43162
+ * Caso de uso: Recortar una imagen específica sin galería
43163
+ *
43164
+ * @param {String} imageUrl - URL de la imagen a recortar
43165
+ * @param {Object} options - Opciones de configuración
43166
+ * @param {Array} options.mandatoryCrops - Recortes obligatorios
43167
+ * @param {Function} options.onComplete - Callback cuando se completan los recortes
43168
+ * @param {Function} options.onCancelled - Callback cuando se cancela
43169
+ * @returns {Object} Instancia del modal
43170
+ *
43171
+ * @example
43172
+ * Limbo.openCropper('https://example.com/image.jpg', {
43173
+ * mandatoryCrops: [
43174
+ * { label: 'Thumbnail', width: 300, height: 300, required: true },
43175
+ * { label: 'Header', width: 1920, height: 400, required: true }
43176
+ * ],
43177
+ * onComplete: (crops) => console.log('Recortes:', crops),
43178
+ * onCancelled: () => console.log('Cancelado')
43179
+ * });
43180
+ */
43181
+ openCropper(imageUrl, options = {}) {
43182
+ if (!imageUrl) {
43183
+ throw new Error("Limbo.openCropper: imageUrl es requerido");
43184
+ }
43185
+ const imageObject = {
43186
+ url: imageUrl,
43187
+ filename: options.filename || "image.jpg",
43188
+ width: options.width || 1920,
43189
+ height: options.height || 1080,
43190
+ mime_type: options.mimeType || "image/jpeg",
43191
+ id: options.assetId || `external-${Date.now()}`
43192
+ };
43193
+ const instance = this.create({
43194
+ mode: "modal",
43195
+ modeUI: "crop-only",
43196
+ features: ["cropper"],
43197
+ autoDestroy: true,
43198
+ modal: {
43199
+ title: options.title || "Recortar Imagen",
43200
+ size: "xlarge"
43201
+ },
43202
+ cropper: {
43203
+ mandatoryCrops: options.mandatoryCrops || [],
43204
+ allowCustomCrops: options.allowCustomCrops !== false,
43205
+ enforceRequiredCrops: options.enforceRequiredCrops !== false
43206
+ },
43207
+ callbacks: {
43208
+ onCropperComplete: (data) => {
43209
+ options.onComplete?.(data);
43210
+ if (options.autoClose !== false) {
43211
+ instance.close();
43212
+ }
43213
+ },
43214
+ onCropperCancelled: (data) => {
43215
+ options.onCancelled?.(data);
43216
+ if (options.autoClose !== false) {
43217
+ instance.close();
43218
+ }
43219
+ },
43220
+ onCropperError: (error) => {
43221
+ options.onError?.(error);
43222
+ }
43223
+ },
43224
+ // Pasar imagen al componente
43225
+ _externalImage: imageObject,
43226
+ ...options
43227
+ });
43228
+ instance.open();
43229
+ return instance;
43230
+ }
43231
+ /**
43232
+ * 🖼️ Crear galería embebida en un contenedor
43233
+ * Caso de uso: Galería permanente en una página
43234
+ *
43235
+ * @param {String|HTMLElement} container - Selector o elemento del contenedor
43236
+ * @param {Object} options - Opciones de configuración
43237
+ * @returns {Object} Instancia del componente
43238
+ *
43239
+ * @example
43240
+ * Limbo.createInlineGallery('#gallery-container', {
43241
+ * onSelect: (image) => console.log('Seleccionada:', image)
43242
+ * });
43243
+ */
43244
+ createInlineGallery(container, options = {}) {
43245
+ return this.create({
43246
+ container,
43247
+ mode: "embed",
43248
+ modeUI: "gallery-only",
43249
+ features: ["gallery"],
43250
+ callbacks: {
43251
+ onSelect: options.onSelect,
43252
+ onDelete: options.onDelete
43253
+ },
43254
+ ...options
43255
+ });
43256
+ }
43257
+ /**
43258
+ * 📤 Crear uploader embebido en un contenedor
43259
+ * Caso de uso: Formulario de subida permanente
43260
+ *
43261
+ * @param {String|HTMLElement} container - Selector o elemento del contenedor
43262
+ * @param {Object} options - Opciones de configuración
43263
+ * @returns {Object} Instancia del componente
43264
+ *
43265
+ * @example
43266
+ * Limbo.createInlineUploader('#upload-container', {
43267
+ * onUpload: (image) => console.log('Subida:', image)
43268
+ * });
43269
+ */
43270
+ createInlineUploader(container, options = {}) {
43271
+ return this.create({
43272
+ container,
43273
+ mode: "embed",
43274
+ modeUI: "upload-only",
43275
+ features: ["upload"],
43276
+ callbacks: {
43277
+ onUpload: options.onUpload
43278
+ },
43279
+ ...options
43280
+ });
43281
+ }
43282
+ /**
43283
+ * ✂️ Crear cropper embebido con imagen externa
43284
+ * Caso de uso: Editor de recortes embebido en página
43285
+ *
43286
+ * @param {String|HTMLElement} container - Selector o elemento del contenedor
43287
+ * @param {String} imageUrl - URL de la imagen a recortar
43288
+ * @param {Object} options - Opciones de configuración
43289
+ * @returns {Object} Instancia del componente
43290
+ *
43291
+ * @example
43292
+ * Limbo.createStandaloneCropper('#cropper-container', 'https://example.com/image.jpg', {
43293
+ * mandatoryCrops: [{ label: 'Avatar', width: 200, height: 200, required: true }],
43294
+ * onComplete: (crops) => console.log('Recortes completados:', crops),
43295
+ * autoHideOnComplete: true
43296
+ * });
43297
+ */
43298
+ createStandaloneCropper(container, imageUrl, options = {}) {
43299
+ if (!container) {
43300
+ throw new Error("Limbo.createStandaloneCropper: container es requerido");
43301
+ }
43302
+ if (!imageUrl) {
43303
+ throw new Error("Limbo.createStandaloneCropper: imageUrl es requerido");
43304
+ }
43305
+ const imageObject = {
43306
+ url: imageUrl,
43307
+ filename: options.filename || "image.jpg",
43308
+ width: options.width || 1920,
43309
+ height: options.height || 1080,
43310
+ mime_type: options.mimeType || "image/jpeg",
43311
+ id: options.assetId || `external-${Date.now()}`
43312
+ };
43313
+ return this.create({
43314
+ container,
43315
+ mode: "embed",
43316
+ modeUI: "crop-only",
43317
+ features: ["cropper"],
43318
+ autoHideOnComplete: options.autoHideOnComplete !== false,
43319
+ cropper: {
43320
+ mandatoryCrops: options.mandatoryCrops || [],
43321
+ allowCustomCrops: options.allowCustomCrops !== false,
43322
+ enforceRequiredCrops: options.enforceRequiredCrops !== false
43323
+ },
43324
+ callbacks: {
43325
+ onCropperComplete: options.onComplete,
43326
+ onCropperCancelled: options.onCancelled,
43327
+ onCropperError: options.onError
43328
+ },
43329
+ // Pasar imagen al componente
43330
+ _externalImage: imageObject,
43331
+ ...options
43332
+ });
43333
+ }
43334
+ /**
43335
+ * 🎯 Crear selector completo (galería + uploader + cropper)
43336
+ * Caso de uso: Selector completo con todas las funcionalidades
43337
+ *
43338
+ * @param {String|HTMLElement} container - Selector o elemento del contenedor
43339
+ * @param {Object} options - Opciones de configuración
43340
+ * @returns {Object} Instancia del componente
43341
+ *
43342
+ * @example
43343
+ * Limbo.createFullSelector('#selector', {
43344
+ * onSelect: (image) => document.getElementById('preview').src = image.url,
43345
+ * onUpload: (image) => console.log('Nueva imagen:', image)
43346
+ * });
43347
+ */
43348
+ createFullSelector(container, options = {}) {
43349
+ return this.create({
43350
+ container,
43351
+ mode: options.mode || "embed",
43352
+ modeUI: "full",
43353
+ features: ["gallery", "upload", "cropper"],
43354
+ callbacks: {
43355
+ onSelect: options.onSelect,
43356
+ onUpload: options.onUpload,
43357
+ onDelete: options.onDelete,
43358
+ onCropsSaved: options.onCropsSaved
43359
+ },
43360
+ ...options
43361
+ });
43362
+ }
41888
43363
  }
41889
43364
  const Limbo = new LimboCore();
41890
43365
  if (typeof window !== "undefined") {
41891
43366
  window.Limbo = Limbo;
41892
43367
  }
41893
- const PUBLIC_KEY = "pk_9fcfdd91a14755cc68d0e11a14269554";
43368
+ const PUBLIC_KEY = "pk_e464fd744106b7a8d63d453c4bd02582";
41894
43369
  if (typeof window !== "undefined" && document.querySelector("#root")) {
41895
43370
  Limbo.configure({
41896
43371
  prod: false,