contentoh-components-library 21.6.50 → 21.6.51

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.
Files changed (86) hide show
  1. package/.env.test +0 -1
  2. package/dist/components/atoms/IconFile/index.js +2 -85
  3. package/dist/components/atoms/IconFile/styles.js +1 -1
  4. package/dist/components/atoms/ProductImage/ProductImage.stories.js +2 -70
  5. package/dist/components/atoms/ProductImage/index.js +27 -100
  6. package/dist/components/atoms/ProductImage/styles.js +1 -3
  7. package/dist/components/molecules/EditionActiveImage/index.js +2 -4
  8. package/dist/components/molecules/GalleryElement/index.js +4 -13
  9. package/dist/components/molecules/GalleryHeader/index.js +24 -96
  10. package/dist/components/molecules/GalleryHeader/styles.js +1 -4
  11. package/dist/components/molecules/ImageSelector/index.js +0 -1
  12. package/dist/components/organisms/ImagePreviewer/index.js +0 -1
  13. package/dist/components/organisms/ProductImageModal/index.js +2 -3
  14. package/dist/components/pages/ProviderProductEdition/index.js +78 -53
  15. package/dist/components/pages/RetailerProductEdition/context/provider-product-edition.context.js +15 -330
  16. package/dist/components/pages/RetailerProductEdition/context/provider-product-edition.reducer.js +2 -12
  17. package/dist/components/pages/RetailerProductEdition/index.js +445 -603
  18. package/dist/components/pages/RetailerProductEdition/stories/ImageEditor.stories.js +0 -1
  19. package/dist/components/pages/RetailerProductEdition/stories/TextEditor.stories.js +145 -45
  20. package/dist/global-files/data.js +3 -16
  21. package/dist/index.js +90 -142
  22. package/package.json +1 -1
  23. package/src/components/atoms/IconFile/index.js +1 -28
  24. package/src/components/atoms/IconFile/styles.js +0 -8
  25. package/src/components/atoms/ProductImage/ProductImage.stories.js +0 -38
  26. package/src/components/atoms/ProductImage/index.js +20 -95
  27. package/src/components/atoms/ProductImage/styles.js +7 -92
  28. package/src/components/molecules/EditionActiveImage/index.js +2 -2
  29. package/src/components/molecules/GalleryElement/index.js +0 -9
  30. package/src/components/molecules/GalleryHeader/index.js +13 -65
  31. package/src/components/molecules/GalleryHeader/styles.js +1 -2
  32. package/src/components/molecules/ImageSelector/index.js +0 -1
  33. package/src/components/organisms/ImagePreviewer/index.js +0 -1
  34. package/src/components/organisms/ProductImageModal/index.js +0 -1
  35. package/src/components/pages/ProviderProductEdition/index.js +36 -3
  36. package/src/components/pages/RetailerProductEdition/context/provider-product-edition.context.jsx +0 -201
  37. package/src/components/pages/RetailerProductEdition/context/provider-product-edition.reducer.js +0 -12
  38. package/src/components/pages/RetailerProductEdition/index.js +179 -360
  39. package/src/components/pages/RetailerProductEdition/stories/ImageEditor.stories.js +0 -1
  40. package/src/components/pages/RetailerProductEdition/stories/TextEditor.stories.js +104 -51
  41. package/src/global-files/data.js +2 -17
  42. package/src/index.js +0 -4
  43. package/.claude/launch.json +0 -14
  44. package/dist/ai/prompts/attribute.prompt.js +0 -28
  45. package/dist/ai/prompts/description.prompt.js +0 -33
  46. package/dist/ai/schemas/attribute.schema.js +0 -35
  47. package/dist/ai/schemas/description.schema.js +0 -27
  48. package/dist/components/atoms/AssetPreview/AssetPreview.stories.js +0 -118
  49. package/dist/components/atoms/AssetPreview/index.js +0 -150
  50. package/dist/components/atoms/AssetPreview/styles.js +0 -28
  51. package/dist/components/molecules/GalleryAssetElement/GalleryAssetElement.stories.js +0 -147
  52. package/dist/components/molecules/GalleryAssetElement/index.js +0 -178
  53. package/dist/components/molecules/GalleryAssetElement/styles.js +0 -26
  54. package/dist/components/molecules/GalleryTrayTabs/GalleryTrayTabs.stories.js +0 -56
  55. package/dist/components/molecules/GalleryTrayTabs/index.js +0 -60
  56. package/dist/components/molecules/GalleryTrayTabs/styles.js +0 -20
  57. package/dist/components/organisms/DigitalAssetsTray/DigitalAssetsTray.stories.js +0 -171
  58. package/dist/components/organisms/DigitalAssetsTray/index.js +0 -211
  59. package/dist/components/organisms/DigitalAssetsTray/styles.js +0 -22
  60. package/dist/global-files/digital-assets/assetTypes.js +0 -480
  61. package/dist/global-files/digital-assets/assets.actions.js +0 -64
  62. package/dist/global-files/digital-assets/assets.adapter.js +0 -304
  63. package/dist/global-files/digital-assets/assets.mock.js +0 -136
  64. package/dist/global-files/digital-assets/assets.reducer.js +0 -220
  65. package/dist/global-files/digital-assets/uploadAssets.js +0 -179
  66. package/dist/global-files/digital-assets/useDigitalAssets.js +0 -522
  67. package/docs/activos-digitales.md +0 -273
  68. package/src/components/atoms/AssetPreview/AssetPreview.stories.js +0 -78
  69. package/src/components/atoms/AssetPreview/index.js +0 -119
  70. package/src/components/atoms/AssetPreview/styles.js +0 -190
  71. package/src/components/molecules/GalleryAssetElement/GalleryAssetElement.stories.js +0 -102
  72. package/src/components/molecules/GalleryAssetElement/index.js +0 -162
  73. package/src/components/molecules/GalleryAssetElement/styles.js +0 -159
  74. package/src/components/molecules/GalleryTrayTabs/GalleryTrayTabs.stories.js +0 -32
  75. package/src/components/molecules/GalleryTrayTabs/index.js +0 -41
  76. package/src/components/molecules/GalleryTrayTabs/styles.js +0 -52
  77. package/src/components/organisms/DigitalAssetsTray/DigitalAssetsTray.stories.js +0 -101
  78. package/src/components/organisms/DigitalAssetsTray/index.js +0 -189
  79. package/src/components/organisms/DigitalAssetsTray/styles.js +0 -183
  80. package/src/global-files/digital-assets/assetTypes.js +0 -419
  81. package/src/global-files/digital-assets/assets.actions.js +0 -50
  82. package/src/global-files/digital-assets/assets.adapter.js +0 -229
  83. package/src/global-files/digital-assets/assets.mock.js +0 -122
  84. package/src/global-files/digital-assets/assets.reducer.js +0 -197
  85. package/src/global-files/digital-assets/uploadAssets.js +0 -109
  86. package/src/global-files/digital-assets/useDigitalAssets.js +0 -329
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contentoh-components-library",
3
- "version": "21.6.50",
3
+ "version": "21.6.51",
4
4
  "dependencies": {
5
5
  "@aws-amplify/auth": "^4.5.3",
6
6
  "@aws-amplify/datastore": "^3.11.0",
@@ -11,8 +11,6 @@ import {
11
11
  faFileAudio as IconFileAudio,
12
12
  faFileLines as IconFileDefault,
13
13
  faFileVideo as IconFileVideo,
14
- faSwatchbook as IconFileDesign,
15
- faFilm as IconFileMotion,
16
14
  } from "@fortawesome/free-solid-svg-icons";
17
15
  import React, { useRef } from "react";
18
16
  import { CustomIcon } from "../CustomIcon";
@@ -43,30 +41,8 @@ export const IconFile = (props) => {
43
41
  // file csv
44
42
  csv: { icon: IconFileCsv, type: "csv" },
45
43
  // file image
44
+ svg: { icon: IconFileImg, type: "image" },
46
45
  gif: { icon: IconFileImg, type: "image" },
47
- jpg: { icon: IconFileImg, type: "image" },
48
- jpeg: { icon: IconFileImg, type: "image" },
49
- png: { icon: IconFileImg, type: "image" },
50
- webp: { icon: IconFileImg, type: "image" },
51
-
52
- // archivos de diseño editables: no se pueden previsualizar en el navegador,
53
- // así que el icono es lo único que distingue uno de otro en la bandeja.
54
- psd: { icon: IconFileDesign, type: "design" },
55
- psb: { icon: IconFileDesign, type: "design" },
56
- ai: { icon: IconFileDesign, type: "design" },
57
- eps: { icon: IconFileDesign, type: "design" },
58
- indd: { icon: IconFileDesign, type: "design" },
59
- idml: { icon: IconFileDesign, type: "design" },
60
- sketch: { icon: IconFileDesign, type: "design" },
61
- fig: { icon: IconFileDesign, type: "design" },
62
- xd: { icon: IconFileDesign, type: "design" },
63
- afphoto: { icon: IconFileDesign, type: "design" },
64
- afdesign: { icon: IconFileDesign, type: "design" },
65
- svg: { icon: IconFileDesign, type: "design" },
66
-
67
- // proyectos de animación y edición de video
68
- aep: { icon: IconFileMotion, type: "motion" },
69
- prproj: { icon: IconFileMotion, type: "motion" },
70
46
  // file pdf
71
47
  pdf: { icon: IconFilePdf, type: "pdf" },
72
48
  // file power point
@@ -84,7 +60,6 @@ export const IconFile = (props) => {
84
60
  rar: { icon: IconFileZip, type: "zip" },
85
61
  zip: { icon: IconFileZip, type: "zip" },
86
62
  tar: { icon: IconFileZip, type: "zip" },
87
- "7z": { icon: IconFileZip, type: "zip" },
88
63
 
89
64
  // file code
90
65
  xml: { icon: IconFileCode, type: "code" },
@@ -105,8 +80,6 @@ export const IconFile = (props) => {
105
80
  // file video
106
81
  avi: { icon: IconFileVideo, type: "video" },
107
82
  mp4: { icon: IconFileVideo, type: "video" },
108
- mov: { icon: IconFileVideo, type: "video" },
109
- webm: { icon: IconFileVideo, type: "video" },
110
83
  mpeg: { icon: IconFileVideo, type: "video" },
111
84
  mpg: { icon: IconFileVideo, type: "video" },
112
85
  wmv: { icon: IconFileVideo, type: "video" },
@@ -52,14 +52,6 @@ export const Container = styled.div`
52
52
  color: #1c778e;
53
53
  border-color: #1c778e;
54
54
  }
55
- &.icon-design .icon {
56
- color: #2a5cb8;
57
- border-color: #2a5cb8;
58
- }
59
- &.icon-motion .icon {
60
- color: #6d3fc0;
61
- border-color: #6d3fc0;
62
- }
63
55
  &.icon-video .icon {
64
56
  color: #a15bb1;
65
57
  border-color: #a15bb1;
@@ -26,41 +26,3 @@ ProductImageDefault.args = {
26
26
  imageType: "main-image",
27
27
  altText: "default image",
28
28
  };
29
-
30
- /*
31
- Las escalas de video, lado a lado. Las cuatro primeras son miniaturas: sin
32
- controles, con su duración y un botón de play, y todo el recuadro clicable.
33
- Las dos últimas son reproductores de verdad.
34
- */
35
- const VIDEO =
36
- "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4";
37
-
38
- const Escala = ({ imageType }) => (
39
- <figure style={{ margin: 0 }}>
40
- <ProductImage
41
- img={VIDEO}
42
- ext="mp4"
43
- imageType={imageType}
44
- altText="video de producto"
45
- onClick={() => console.log("clic en", imageType)}
46
- />
47
- <figcaption style={{ fontFamily: "Lato", fontSize: 11, marginTop: 4 }}>
48
- {imageType}
49
- </figcaption>
50
- </figure>
51
- );
52
-
53
- export const VideoMiniaturas = () => (
54
- <div style={{ display: "flex", gap: 24, alignItems: "flex-start" }}>
55
- <Escala imageType="carousel-image" />
56
- <Escala imageType="catalogue-image-small-size" />
57
- <Escala imageType="catalogue-modal-image" />
58
- <Escala imageType="catalogue-image-big-size" />
59
- </div>
60
- );
61
-
62
- export const VideoReproductor = () => (
63
- <div style={{ display: "flex", gap: 24, alignItems: "flex-start" }}>
64
- <Escala imageType="main-image" />
65
- </div>
66
- );
@@ -1,104 +1,29 @@
1
- import { useState } from "react";
2
- import ReactImageFallback from "react-image-fallback";
3
-
4
1
  import { Container } from "./styles";
5
- import {
6
- ASSET_PREVIEW,
7
- formatDuration,
8
- resolvePreviewKind,
9
- } from "../../../global-files/digital-assets/assetTypes";
2
+ import ReactImageFallback from "react-image-fallback";
10
3
  import defaultProductImage from "../../../assets/images/defaultImages/defaultProductImage.png";
11
4
 
12
- /*
13
- Tamaños en los que un video se muestra como miniatura y no como reproductor.
14
-
15
- Los controles nativos del navegador necesitan alrededor de 150px de ancho
16
- para ser usables; en un recuadro de 60 o de 85 se pintan encimados y no se
17
- puede ni dar play. Peor aún, un <video> con controles se traga el clic, así
18
- que un video en el carrusel no se podía ni seleccionar: `onClick` nunca
19
- llegaba a correr.
20
-
21
- En estos tamaños se muestra el primer fotograma con su duración y un botón de
22
- play, y el recuadro completo lleva al visor grande, que es donde el video sí
23
- se ve y se controla.
24
- */
25
- const THUMBNAIL_TYPES = [
26
- "carousel-image",
27
- "catalogue-image-small-size",
28
- "catalogue-image-big-size",
29
- "catalogue-modal-image",
30
- ];
31
-
32
- const PlayBadge = () => (
33
- <span className="play-badge" aria-hidden="true">
34
- <svg viewBox="0 0 24 24">
35
- <circle cx="12" cy="12" r="11" />
36
- <polygon points="9.5,7.5 17,12 9.5,16.5" />
37
- </svg>
38
- </span>
39
- );
40
-
41
- /**
42
- * Imagen —o video— de producto.
43
- *
44
- * El tipo de archivo se decide por `ext`/`mime` cuando quien llama los conoce.
45
- * Antes se comprobaba con `/\.mp4$/` sobre la URL, y nunca acertaba: la URL
46
- * guardada apunta al redimensionador de CloudFront y no conserva la extensión.
47
- * El regex queda solo como respaldo para los llamadores que aún mandan `img`
48
- * a secas.
49
- */
50
- export const ProductImage = ({ img, imageType, altText, onClick, ext, mime, poster }) => {
51
- // La duración no está en la base: se toma de los metadatos, que el navegador
52
- // ya descarga por el `preload="metadata"`. No cuesta una petición extra.
53
- const [duration, setDuration] = useState(null);
54
-
55
- const kind =
56
- ext || mime
57
- ? resolvePreviewKind({ ext, mime })
58
- : /\.mp4($|[?#])/i.test(img || "")
59
- ? ASSET_PREVIEW.VIDEO
60
- : ASSET_PREVIEW.IMAGE;
61
-
62
- if (kind === ASSET_PREVIEW.VIDEO) {
63
- const isThumbnail = THUMBNAIL_TYPES.includes(imageType);
64
-
5
+ export const ProductImage = ({ img, imageType, altText, onClick}) => {
6
+ // Expresión regular para validar que los últimos tres caracteres sean ".mp4"
7
+ const regex = /\.mp4$/;
8
+ if (regex.test(img)){
65
9
  return (
66
- <Container
67
- className={`${imageType} ${isThumbnail ? "video-thumbnail" : "video-player"}`}
68
- onClick={isThumbnail ? onClick : undefined}
69
- >
70
- <video
71
- // `#t=0.5` hace que el navegador muestre el fotograma del medio
72
- // segundo en lugar de un cuadro negro, sin descargar el archivo
73
- // completo.
74
- src={poster ? img : `${img}#t=0.5`}
75
- poster={poster || undefined}
76
- controls={!isThumbnail}
77
- muted={isThumbnail}
78
- playsInline
79
- preload="metadata"
80
- onLoadedMetadata={(e) => setDuration(e.target.duration)}
10
+ <Container className={imageType}>
11
+ <video controls width="100%" height="auto">
12
+ <source src= {img} type="video/mp4" />
13
+ </video>
14
+ </Container>
15
+ );
16
+ }
17
+ else{
18
+ return (
19
+ <Container className={imageType}>
20
+ <ReactImageFallback
21
+ src={img}
22
+ fallbackImage={defaultProductImage}
23
+ alt={altText}
24
+ onClick={onClick}
81
25
  />
82
- {isThumbnail && (
83
- <>
84
- <PlayBadge />
85
- {duration ? (
86
- <span className="video-duration">{formatDuration(duration)}</span>
87
- ) : null}
88
- </>
89
- )}
90
26
  </Container>
91
27
  );
92
28
  }
93
-
94
- return (
95
- <Container className={imageType}>
96
- <ReactImageFallback
97
- src={img}
98
- fallbackImage={defaultProductImage}
99
- alt={altText}
100
- onClick={onClick}
101
- />
102
- </Container>
103
- );
104
29
  };
@@ -1,92 +1,14 @@
1
1
  import styled from "styled-components";
2
- import { FontFamily } from "../../../global-files/variables";
3
2
 
4
3
  export const Container = styled.div`
5
4
  width: fit-content;
6
5
 
7
- img,
8
- video {
6
+ img {
9
7
  object-fit: contain;
10
8
  }
11
9
 
12
- /*
13
- Miniatura de video: fotograma quieto, con su duración y un botón de play.
14
- El clic lo recibe el contenedor, no el <video>, para que lleve al visor
15
- grande en vez de quedarse peleando con los controles.
16
- */
17
- &.video-thumbnail {
18
- position: relative;
19
- cursor: pointer;
20
-
21
- video {
22
- pointer-events: none;
23
- background: #000;
24
- }
25
-
26
- .play-badge {
27
- position: absolute;
28
- top: 50%;
29
- left: 50%;
30
- transform: translate(-50%, -50%);
31
- width: 32px;
32
- height: 32px;
33
- pointer-events: none;
34
-
35
- svg {
36
- width: 100%;
37
- height: 100%;
38
- display: block;
39
- }
40
-
41
- circle {
42
- fill: rgba(0, 0, 0, 0.55);
43
- }
44
-
45
- polygon {
46
- fill: #ffffff;
47
- }
48
- }
49
-
50
- &:hover .play-badge circle {
51
- fill: rgba(0, 0, 0, 0.75);
52
- }
53
-
54
- .video-duration {
55
- position: absolute;
56
- right: 4px;
57
- bottom: 4px;
58
- padding: 0 4px;
59
- border-radius: 3px;
60
- background: rgba(0, 0, 0, 0.75);
61
- color: #ffffff;
62
- font-family: ${FontFamily.Lato};
63
- font-size: 10px;
64
- line-height: 15px;
65
- pointer-events: none;
66
- }
67
- }
68
-
69
- /* En 60px un badge de 32 tapa el fotograma completo. */
70
- &.catalogue-image-small-size.video-thumbnail .play-badge {
71
- width: 20px;
72
- height: 20px;
73
- }
74
-
75
- &.carousel-image.video-thumbnail {
76
- .play-badge {
77
- width: 22px;
78
- height: 22px;
79
- }
80
-
81
- .video-duration {
82
- font-size: 9px;
83
- line-height: 13px;
84
- }
85
- }
86
-
87
10
  &.carousel-image {
88
- img,
89
- video {
11
+ img {
90
12
  width: 85px;
91
13
  height: 55px;
92
14
  cursor: pointer;
@@ -98,8 +20,7 @@ export const Container = styled.div`
98
20
  }
99
21
 
100
22
  &.main-image {
101
- img,
102
- video {
23
+ img {
103
24
  width: 340px;
104
25
  height: 295px;
105
26
  }
@@ -109,16 +30,14 @@ export const Container = styled.div`
109
30
  display: flex;
110
31
  justify-content: center;
111
32
  align-items: center;
112
- img,
113
- video {
33
+ img {
114
34
  width: 179px;
115
35
  height: 179px;
116
36
  }
117
37
  }
118
38
 
119
39
  &.catalogue-image-small-size {
120
- img,
121
- video {
40
+ img {
122
41
  width: 60px;
123
42
  height: 60px;
124
43
  }
@@ -128,9 +47,7 @@ export const Container = styled.div`
128
47
  flex: 1 0 50%;
129
48
  cursor: pointer;
130
49
 
131
- img,
132
-
133
- video {
50
+ img {
134
51
  width: 168px;
135
52
  height: 108px;
136
53
  }
@@ -143,9 +60,7 @@ export const Container = styled.div`
143
60
  justify-content: center;
144
61
  align-items: center;
145
62
 
146
- img,
147
-
148
- video {
63
+ img {
149
64
  width: 710px;
150
65
  height: 710px;
151
66
  }
@@ -2,10 +2,10 @@ import { Container } from "./styles";
2
2
  import { ProductImage } from "../../atoms/ProductImage/index";
3
3
  import { Button } from "../../atoms/GeneralButton/index";
4
4
 
5
- export const EditionActiveImage = ({ img, altText, setShowModal, ext }) => {
5
+ export const EditionActiveImage = ({ img, altText, setShowModal }) => {
6
6
  return (
7
7
  <Container>
8
- <ProductImage img={img} imageType="main-image" altText={altText} ext={ext} />
8
+ <ProductImage img={img} imageType="main-image" altText={altText} />
9
9
  <Button
10
10
  buttonType="open-modal-button"
11
11
  onClick={() => setShowModal(true)}
@@ -25,7 +25,6 @@ export const GalleryElement = ({
25
25
  auditImages,
26
26
  compare,
27
27
  updateApprovedInputsImages,
28
- onSelect,
29
28
  }) => {
30
29
  const [showValidationPanel, setShowValidationPanel] = useState(false);
31
30
  const [showDivStates, setShowDivStates] = useState({});
@@ -94,15 +93,7 @@ export const GalleryElement = ({
94
93
  <ProductImage
95
94
  img={compare ? auditImages?.values[number]?.src : image?.src}
96
95
  altText={image?.altText}
97
- // Sin `ext`, un video se pinta como <img> rota: la URL guardada no
98
- // conserva la extensión del archivo.
99
- ext={compare ? auditImages?.values[number]?.ext : image?.ext}
100
96
  imageType={`catalogue-image-${gridLayout ? "big" : "small"}-size`}
101
- // La miniatura de un video ya no lleva controles, así que el clic es
102
- // la única forma de llegar al visor grande. Se aplica también a las
103
- // imágenes: dos recuadros idénticos donde solo uno responde al clic
104
- // sería peor que no responder ninguno.
105
- onClick={onSelect}
106
97
  />
107
98
  <div className="information-container">
108
99
  <p>{image?.name}</p>
@@ -1,80 +1,28 @@
1
1
  import { Container } from "./styles";
2
2
  import { CheckBox } from "../../atoms/CheckBox";
3
3
 
4
- /*
5
- Las columnas de la bandeja dependen de qué se está listando: un PDF no tiene
6
- "tipo de toma" ni alto por ancho, y una foto no tiene "tipo de activo".
7
- Estaban escritas a mano en el marcado, así que la única forma de cambiarlas
8
- era duplicar el componente.
9
-
10
- El preset `image` reproduce exactamente las columnas y clases de antes, para
11
- que la bandeja de imágenes se vea idéntica sin que nadie tenga que pasar nada.
12
- */
13
- export const GALLERY_COLUMNS = {
14
- image: {
15
- template: "0.1fr 1fr 1fr 1fr 1fr 1fr",
16
- columns: [
17
- { key: "preview", label: "Imagen", className: "image-header" },
18
- { key: "name", label: "Nombre", className: "image-name" },
19
- { key: "size", label: "Tamaño", className: "image-size" },
20
- { key: "format", label: "Formato", className: "image-format" },
21
- {
22
- key: "legend",
23
- label: "¿Tiene leyenda?",
24
- className: "image-description",
25
- },
26
- { key: "shotType", label: "Tipo de toma", className: "shoot-type" },
27
- ],
28
- },
29
- asset: {
30
- template: "0.1fr 1fr 1fr 1fr 1fr",
31
- columns: [
32
- { key: "preview", label: "Activo", className: "asset-header" },
33
- { key: "name", label: "Nombre", className: "asset-name" },
34
- { key: "weight", label: "Peso", className: "asset-weight" },
35
- { key: "format", label: "Formato", className: "asset-format" },
36
- { key: "assetType", label: "Tipo de activo", className: "asset-type" },
37
- ],
38
- },
39
- };
40
-
41
- /**
42
- * Encabezado de la bandeja, con su casilla de "seleccionar todo".
43
- *
44
- * `checkAllId` e `itemSelector` se pueden cambiar porque imágenes y activos
45
- * conviven en la misma pestaña: si las dos listas compartieran el id de la
46
- * casilla global, marcar todas las imágenes marcaría también los activos.
47
- */
48
- export const GalleryHeader = ({
49
- setCheckAll,
50
- setSelectedImages,
51
- shotThd,
52
- variant = "image",
53
- columns,
54
- checkAllId = "check-all-images",
55
- itemSelector = "[id^='gallery-element-']",
56
- }) => {
57
- const preset = GALLERY_COLUMNS[variant] ?? GALLERY_COLUMNS.image;
58
- const visibleColumns = columns ?? preset.columns;
59
-
4
+ export const GalleryHeader = ({ setCheckAll, setSelectedImages, shotThd }) => {
60
5
  const checkAll = (e) => {
61
- const checkboxList = document.querySelectorAll(itemSelector);
6
+ const checkboxList = document.querySelectorAll("[id^='gallery-element-']");
62
7
  checkboxList.forEach((chk) => (chk.checked = e.target.checked));
63
8
  setCheckAll(e.target.checked);
64
9
  if (!e.target.checked) setSelectedImages([]);
65
10
  };
66
-
67
11
  return (
68
- <Container template={preset.template}>
12
+ <Container>
69
13
  <div className="checkbox">
70
- <CheckBox id={checkAllId} onChange={(e) => checkAll(e)} />
14
+ <CheckBox id="check-all-images" onChange={(e) => checkAll(e)} />
71
15
  </div>
72
16
  <div className="information">
73
- {visibleColumns.map((column) => (
74
- <p key={column.key} className={column.className}>
75
- {column.label}
76
- </p>
77
- ))}
17
+ <p className="image-header">Imagen</p>
18
+ <p className="image-name">Nombre</p>
19
+ <p className="image-size">Tamaño</p>
20
+ <p className="image-format">Formato</p>
21
+ <p className="image-description">¿Tiene leyenda?</p>
22
+ <p className="shoot-type">Tipo de toma</p>
23
+ {/* <p className="">Tipo de imagen</p> */}
24
+ {/* <p className="">Tipo de empaque</p> */}
25
+ {/* <p className="">Estatus</p> */}
78
26
  </div>
79
27
  </Container>
80
28
  );
@@ -15,8 +15,7 @@ export const Container = styled.div`
15
15
  .information{
16
16
  width: 90%;
17
17
  display: grid;
18
- /* Lo define el preset de columnas; el valor por defecto es el de imágenes. */
19
- grid-template-columns: ${({ template }) => template || "0.1fr 1fr 1fr 1fr 1fr 1fr"};
18
+ grid-template-columns: 0.1fr 1fr 1fr 1fr 1fr 1fr;
20
19
  }
21
20
  .image-header{
22
21
 
@@ -13,7 +13,6 @@ export const ImageSelector = ({
13
13
  key={index}
14
14
  img={image.src}
15
15
  altText={image.altText}
16
- ext={image.ext}
17
16
  imageType={modalSelector ? "catalogue-modal-image" : "carousel-image"}
18
17
  onClick={() => setActiveImage(index)}
19
18
  />
@@ -13,7 +13,6 @@ export const ImagePreviewer = ({
13
13
  <EditionActiveImage
14
14
  img={activeImage?.src}
15
15
  altText={activeImage?.name}
16
- ext={activeImage?.ext}
17
16
  setShowModal={setShowModal}
18
17
  />
19
18
  <ImageSelector images={imagesArray} setActiveImage={setActiveImage} />
@@ -48,7 +48,6 @@ export const ProductImageModal = ({
48
48
  images?.values[activeImage]?.srcDB
49
49
  }
50
50
  altText={images?.values[activeImage]?.name}
51
- ext={images?.values[activeImage]?.ext}
52
51
  imageType={"expanded-modal-image"}
53
52
  />
54
53
  </div>
@@ -96,6 +96,17 @@ const ProviderProductEditionView = ({
96
96
  const [variantGroup, setVariantGroup] = useState(null);
97
97
  const [showGroup, setShowGroup] = useState(false);
98
98
  const [showGroupModal, setShowGroupModal] = useState(false);
99
+
100
+ /*
101
+ Un hijo no se agrupa como variante (decisión de producto, 2026-09-25).
102
+
103
+ Agrupar declara «estos son el mismo producto en otra presentación», y un
104
+ hijo ya declaró de dónde deriva; tener las dos cosas lo dejaba con dos
105
+ respuestas distintas a la misma pregunta. El servidor lo rechaza con
106
+ VARIANT_GROUP_NOT_FOR_CHILD, así que ofrecer el botón aquí sería ofrecer
107
+ algo que siempre falla.
108
+ */
109
+ const puedeAgruparse = !state.product?.parentArticleId;
99
110
  const [pendingPropagation, setPendingPropagation] = useState(null);
100
111
  const [preparandoPropagacion, setPreparandoPropagacion] = useState(false);
101
112
  const [preparandoEdicionMultiple, setPreparandoEdicionMultiple] =
@@ -751,8 +762,24 @@ const ProviderProductEditionView = ({
751
762
  const companeras = (variantGroup?.members ?? []).filter(
752
763
  (m) => !m.isCurrent
753
764
  );
765
+ /*
766
+ Un HIJO no propaga a nadie, y hay que decirlo explícitamente.
767
+
768
+ Sin este corte el diálogo se abría desde la ficha de un hijo y le
769
+ ofrecía a sus HERMANOS como si fueran sus hijos: `family.variants`
770
+ mira la familia desde el padre —eso es lo correcto para navegar entre
771
+ hermanos— pero aquí se estaba leyendo como «los destinos de este
772
+ producto». El servidor resolvía los destinos por su cuenta y no
773
+ escribía nada, así que el usuario elegía hermanos, guardaba, y no
774
+ pasaba nada: un no-operativo silencioso.
775
+
776
+ Un hijo tampoco puede estar agrupado desde 2026-09-25, así que para
777
+ él no queda ningún destino posible.
778
+ */
779
+ const esHijo = !!state.product?.parentArticleId;
754
780
  const hayDestinos =
755
- state.product?.variantsCount > 0 || companeras.length > 0;
781
+ !esHijo &&
782
+ (state.product?.variantsCount > 0 || companeras.length > 0);
756
783
  const cambios = cambiosDeFichaTecnica();
757
784
 
758
785
  if (hayDestinos && cambios.length > 0) {
@@ -1659,7 +1686,11 @@ const ProviderProductEditionView = ({
1659
1686
  {pendingPropagation && (
1660
1687
  <PropagateChangesModal
1661
1688
  changedAttributes={pendingPropagation.cambios}
1662
- variants={(family?.variants ?? []).filter((v) => !v.isCurrent)}
1689
+ variants={
1690
+ state.product?.parentArticleId
1691
+ ? []
1692
+ : (family?.variants ?? []).filter((v) => !v.isCurrent)
1693
+ }
1663
1694
  groupMembers={(variantGroup?.members ?? []).filter(
1664
1695
  (m) => !m.isCurrent
1665
1696
  )}
@@ -2067,6 +2098,7 @@ const ProviderProductEditionView = ({
2067
2098
  código compartido que dice "estos son el mismo producto en
2068
2099
  otra presentación". Mezclarlos en una sola caja haría creer
2069
2100
  que agrupar hereda algo, que es justo lo que no pasa. */}
2101
+ {puedeAgruparse && (
2070
2102
  <div className="variants-actions">
2071
2103
  <button
2072
2104
  type="button"
@@ -2088,8 +2120,9 @@ const ProviderProductEditionView = ({
2088
2120
  </button>
2089
2121
  )}
2090
2122
  </div>
2123
+ )}
2091
2124
 
2092
- {showGroup && variantGroup?.group && (
2125
+ {puedeAgruparse && showGroup && variantGroup?.group && (
2093
2126
  <div className="variants-family">
2094
2127
  {/* Los hermanos de grupo NO se listan como padre/hijo: aquí
2095
2128
  nadie hereda de nadie, así que no hay rol que mostrar,