limbo-component 1.8.5 → 1.9.0

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