contentoh-components-library 21.6.32 → 21.6.34

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 (30) hide show
  1. package/.claude/launch.json +15 -0
  2. package/dist/ai/prompts/attribute.prompt.js +28 -0
  3. package/dist/ai/prompts/description.prompt.js +33 -0
  4. package/dist/ai/schemas/attribute.schema.js +35 -0
  5. package/dist/ai/schemas/description.schema.js +27 -0
  6. package/dist/components/atoms/InheritanceTag/InheritanceTag.stories.js +5 -5
  7. package/dist/components/atoms/InheritanceTag/index.js +6 -6
  8. package/dist/components/organisms/InputGroup/index.js +12 -1
  9. package/dist/components/organisms/LinkVariantsModal/index.js +269 -356
  10. package/dist/components/organisms/LinkVariantsModal/styles.js +14 -5
  11. package/dist/components/organisms/PropagateChangesModal/index.js +12 -13
  12. package/dist/components/pages/ProviderProductEdition/context/provider-product-edition.context.js +5 -5
  13. package/dist/components/pages/ProviderProductEdition/context/reducers/inputs.js +5 -2
  14. package/dist/components/pages/ProviderProductEdition/index.js +96 -36
  15. package/dist/components/pages/RetailerProductEdition/context/provider-product-edition.context.js +5 -5
  16. package/dist/components/pages/RetailerProductEdition/context/reducers/inputs.js +5 -2
  17. package/dist/components/pages/RetailerProductEdition/index.js +100 -38
  18. package/package.json +1 -1
  19. package/src/components/atoms/InheritanceTag/InheritanceTag.stories.js +3 -3
  20. package/src/components/atoms/InheritanceTag/index.js +6 -6
  21. package/src/components/organisms/InputGroup/index.js +12 -0
  22. package/src/components/organisms/LinkVariantsModal/index.js +260 -287
  23. package/src/components/organisms/LinkVariantsModal/styles.js +65 -32
  24. package/src/components/organisms/PropagateChangesModal/index.js +19 -17
  25. package/src/components/pages/ProviderProductEdition/context/provider-product-edition.context.jsx +4 -4
  26. package/src/components/pages/ProviderProductEdition/context/reducers/inputs.js +13 -1
  27. package/src/components/pages/ProviderProductEdition/index.js +61 -20
  28. package/src/components/pages/RetailerProductEdition/context/provider-product-edition.context.jsx +4 -4
  29. package/src/components/pages/RetailerProductEdition/context/reducers/inputs.js +13 -1
  30. package/src/components/pages/RetailerProductEdition/index.js +74 -23
@@ -202,22 +202,6 @@ export const List = styled.ul`
202
202
  min-width: 0;
203
203
  }
204
204
 
205
- .quitar {
206
- flex-shrink: 0;
207
- margin-right: 10px;
208
- border: none;
209
- background: none;
210
- padding: 2px 6px;
211
- cursor: pointer;
212
- font-size: 16px;
213
- line-height: 1;
214
- color: ${GlobalColors.gray};
215
- }
216
-
217
- .quitar:hover {
218
- color: ${PINK};
219
- }
220
-
221
205
  .count {
222
206
  margin-left: auto;
223
207
  font-size: 12px;
@@ -288,6 +272,13 @@ export const Message = styled.p`
288
272
  background-color: rgba(${PINK_RGB}, 0.1);
289
273
  color: ${PINK};
290
274
  }
275
+
276
+ /* Aviso, no error: el padre ya tiene hijos y planchar les cambia el origen.
277
+ Se puede continuar, así que no usa el rosa de rechazo. */
278
+ &.aviso {
279
+ background-color: rgba(${PINK_RGB}, 0.06);
280
+ border-left: 3px solid ${PINK};
281
+ }
291
282
  `;
292
283
 
293
284
  export const Footer = styled.div`
@@ -386,29 +377,71 @@ export const ImageGrid = styled.div`
386
377
  }
387
378
  `;
388
379
 
389
- export const AddRetailer = styled.div`
380
+ /*
381
+ Padre → hijo, dibujado.
382
+
383
+ El paso de herencia escribe en una dirección sola y hasta ahora no la decía:
384
+ el modo se elige en la pantalla anterior y desaparece de la vista. Con dos
385
+ tarjetas y una flecha se lee de un golpe, sin tener que reconstruirlo del
386
+ texto de cada bloque.
387
+ */
388
+ export const Direction = styled.div`
390
389
  display: flex;
391
- flex-wrap: wrap;
392
- align-items: flex-end;
393
- gap: 8px;
394
- margin-bottom: 16px;
390
+ align-items: stretch;
391
+ gap: 10px;
392
+ margin-bottom: 12px;
395
393
 
396
- button {
397
- height: 38px;
398
- padding: 0 14px;
399
- border: 1px solid ${PINK};
394
+ .lado {
395
+ flex: 1 1 0;
396
+ min-width: 0;
397
+ padding: 10px 12px;
398
+ border: 1px solid ${GlobalColors.s2};
400
399
  border-radius: 6px;
401
- background: none;
400
+ background-color: ${GlobalColors.white};
401
+ }
402
+
403
+ /* El padre es el que manda en esta pantalla, y se marca como tal. */
404
+ .lado:first-of-type {
405
+ border-color: rgba(${PINK_RGB}, 0.45);
406
+ background-color: rgba(${PINK_RGB}, 0.05);
407
+ }
408
+
409
+ .rol {
410
+ display: block;
402
411
  font-family: ${FontFamily.Raleway_600};
403
- font-size: 13px;
412
+ font-size: 11px;
413
+ text-transform: uppercase;
414
+ letter-spacing: 0.4px;
404
415
  color: ${PINK};
405
- cursor: pointer;
406
- flex-shrink: 0;
407
416
  }
408
417
 
409
- button:disabled {
410
- opacity: 0.4;
411
- cursor: default;
418
+ strong {
419
+ display: block;
420
+ margin-top: 2px;
421
+ font-family: ${FontFamily.Raleway_600};
422
+ font-size: 13px;
423
+ color: ${GlobalColors.deep_gray};
424
+ overflow: hidden;
425
+ text-overflow: ellipsis;
426
+ white-space: nowrap;
427
+ }
428
+
429
+ /* Cuál de los dos es la ficha abierta: sin esto, al relacionar un padre no se
430
+ sabe de qué lado está uno. */
431
+ .actual {
432
+ display: block;
433
+ margin-top: 2px;
434
+ font-size: 11px;
435
+ font-style: italic;
436
+ color: ${GlobalColors.gray};
437
+ }
438
+
439
+ .flecha {
440
+ align-self: center;
441
+ flex-shrink: 0;
442
+ font-size: 20px;
443
+ line-height: 1;
444
+ color: ${PINK};
412
445
  }
413
446
  `;
414
447
 
@@ -15,14 +15,13 @@ import {
15
15
  Diálogo de propagación al guardar un producto padre.
16
16
 
17
17
  Aparece cuando se guardan cambios de ficha técnica en un producto que tiene
18
- variantes. El backend NO propaga nada por su cuenta: solo baja a los hijos lo
19
- que venga en `propagation`, y esa lista la arma esta pantalla. Guardar sin
20
- abrirla, o cerrarla sin propagar, deja al padre modificado y a las variantes
21
- como estaban.
18
+ hijos. El backend NO propaga nada por su cuenta: solo baja a los hijos lo que
19
+ venga en `propagation`, y esa lista la arma esta pantalla. Guardar sin abrirla,
20
+ o cerrarla sin propagar, deja al padre modificado y a los hijos como estaban.
22
21
 
23
22
  props (* requerido):
24
23
  - *changedAttributes: [{ id, name, value }] lo que se acaba de editar
25
- - *variants: [{ articleId, name, upc }] las variantes activas del padre
24
+ - *variants: [{ articleId, name, upc }] los hijos activos del padre
26
25
  - *onCancel: cerrar sin guardar nada
27
26
  - *onConfirm: (propagation | null) => void
28
27
  null → guardar solo el padre
@@ -77,16 +76,19 @@ export const PropagateChangesModal = ({
77
76
  <Overlay>
78
77
  <Panel onClick={(event) => event.stopPropagation()}>
79
78
  <Header>
80
- <h2>Este producto tiene {variants.length} variante{variants.length === 1 ? "" : "s"}</h2>
79
+ <h2>
80
+ Este producto tiene {variants.length} hijo
81
+ {variants.length === 1 ? "" : "s"}
82
+ </h2>
81
83
  <p>
82
84
  Cambiaste {changedAttributes.length} atributo
83
- {changedAttributes.length === 1 ? "" : "s"}. Elige cuáles bajan a las
84
- variantes; el resto se guarda solo en este producto.
85
+ {changedAttributes.length === 1 ? "" : "s"}. Elige cuáles bajan a los
86
+ hijos; el resto se guarda solo en este producto.
85
87
  </p>
86
88
  </Header>
87
89
 
88
90
  <Body>
89
- <h3>¿Qué se baja a las variantes?</h3>
91
+ <h3>¿Qué se baja a los hijos?</h3>
90
92
  <List>
91
93
  {changedAttributes.map((attribute) => (
92
94
  <li key={attribute.id}>
@@ -115,9 +117,9 @@ export const PropagateChangesModal = ({
115
117
  onChange={() => setScope("all")}
116
118
  />
117
119
  <span>
118
- A todas
120
+ A todos
119
121
  <span className="hint">
120
- {variants.length} variante{variants.length === 1 ? "" : "s"} activa
122
+ {variants.length} hijo{variants.length === 1 ? "" : "s"} activo
121
123
  {variants.length === 1 ? "" : "s"}.
122
124
  </span>
123
125
  </span>
@@ -154,16 +156,16 @@ export const PropagateChangesModal = ({
154
156
  )}
155
157
 
156
158
  {/* La diferencia con "ligar", y la que sorprende si no se dice: aquí
157
- lo que la variante editó a mano NO se pisa. */}
159
+ lo que el hijo editó a mano NO se pisa. */}
158
160
  <Note>
159
- Una variante que haya editado alguno de estos atributos por su cuenta
161
+ Un hijo que haya editado alguno de estos atributos por su cuenta
160
162
  conserva su valor. Para devolverlo al del padre está la acción
161
- «Volver a heredar» en la ficha de esa variante.
163
+ «Volver a heredar» en la ficha de ese hijo.
162
164
  </Note>
163
165
 
164
166
  {enSegundoPlano && (
165
167
  <Note className="accent">
166
- Son más de {UMBRAL_SEGUNDO_PLANO} variantes, así que la propagación
168
+ Son más de {UMBRAL_SEGUNDO_PLANO} hijos, así que la propagación
167
169
  corre en segundo plano: el guardado termina antes de que todas estén
168
170
  actualizadas.
169
171
  </Note>
@@ -173,8 +175,8 @@ export const PropagateChangesModal = ({
173
175
  <Footer>
174
176
  <span className="resumen">
175
177
  {puedePropagar
176
- ? `${chosenAttributes.length} atributo(s) a ${destinatarios} variante(s).`
177
- : "No se bajará nada a las variantes."}
178
+ ? `${chosenAttributes.length} atributo(s) a ${destinatarios} hijo(s).`
179
+ : "No se bajará nada a los hijos."}
178
180
  </span>
179
181
  <button
180
182
  type="button"
@@ -123,7 +123,7 @@ export const ProviderProductEditionProvider = ({ children }) => {
123
123
  lo que el usuario eligió en el diálogo: { attributes: [...], targetChildren }.
124
124
 
125
125
  Sin ese bloque el backend no propaga nada. Es deliberado: bajar un cambio a
126
- las variantes tiene que ser una decisión, no un efecto secundario de guardar.
126
+ los hijos tiene que ser una decisión, no un efecto secundario de guardar.
127
127
  */
128
128
  const saveDatasheets = async (token, propagation) => {
129
129
  if (state.updated_datasheets_inputs.length === 0) return;
@@ -167,17 +167,17 @@ export const ProviderProductEditionProvider = ({ children }) => {
167
167
  type: "SET_UPDATED_DATASHEETS_INPUTS",
168
168
  payload: [],
169
169
  });
170
- // En una variante, todo atributo que el hijo toca deja de ser heredado:
170
+ // En un hijo, todo atributo que se toca deja de ser heredado:
171
171
  // update-articles-data le pone is_overridden = 1 en la base. Si aquí solo
172
172
  // se refresca el valor, el chip sigue diciendo "Heredado" hasta la
173
173
  // siguiente carga de la ficha, y el usuario ve una marca que ya es falsa.
174
- const esVariante =
174
+ const esHijo =
175
175
  state.services?.datasheets?.variant?.isVariant ??
176
176
  !!state.product?.parentArticleId;
177
177
 
178
178
  dispatch({
179
179
  type: "UPDATE_DATASHEETS_INPUTS",
180
- payload: esVariante
180
+ payload: esHijo
181
181
  ? state.updated_datasheets_inputs.map((item) => ({
182
182
  ...item,
183
183
  isOverridden: 1,
@@ -52,7 +52,19 @@ export const inputsReducer = (state, action) => {
52
52
  const updatedInput = action.payload.find(
53
53
  (updated) => updated.attributeId === input.id
54
54
  );
55
- return updatedInput ? { ...input, value: updatedInput.value } : input;
55
+ if (!updatedInput) return input;
56
+
57
+ return {
58
+ ...input,
59
+ value: updatedInput.value,
60
+ // Opcional a propósito, igual que en la ficha técnica: quien solo
61
+ // cambia el valor no toca la marca de herencia. La usa "Volver a
62
+ // heredar", que sí necesita devolverla a heredada para que el chip
63
+ // deje de decir "Propio".
64
+ ...(updatedInput.isOverridden === undefined
65
+ ? {}
66
+ : { isOverridden: updatedInput.isOverridden }),
67
+ };
56
68
  }
57
69
  );
58
70
 
@@ -207,6 +207,16 @@ const ProviderProductEditionView = ({
207
207
  setProductData();
208
208
  }, []);
209
209
 
210
+ /*
211
+ Contador que fuerza a releer los servicios tras ligar.
212
+
213
+ El efecto de abajo depende de estatus, versión y cadena activa, y ligar no
214
+ cambia ninguno: cambia la HERENCIA de cada campo en el servidor. Al planchar,
215
+ lo que era propio pasa a heredado, y sin releer los chips siguen diciendo lo
216
+ de antes hasta que alguien cierra y reabre el producto.
217
+ */
218
+ const [linkNonce, setLinkNonce] = useState(0);
219
+
210
220
  // Setter de los servicios y porcentages del producto
211
221
  useEffect(() => {
212
222
  if (!state.product || !state.active_retailer) return;
@@ -363,6 +373,7 @@ const ProviderProductEditionView = ({
363
373
  state.product?.version,
364
374
  state.active_retailer,
365
375
  token,
376
+ linkNonce,
366
377
  ]);
367
378
 
368
379
  // Actualizador de campos del ui para que coincidan con el retailer activo
@@ -709,15 +720,15 @@ const ProviderProductEditionView = ({
709
720
  saveDescriptions(token);
710
721
  break;
711
722
  case "Ficha técnica": {
712
- // Guardar un padre con variantes abre la decisión de propagar. Sin este
713
- // paso el cambio se queda solo en el padre y las variantes se van
723
+ // Guardar un padre con hijos abre la decisión de propagar. Sin este
724
+ // paso el cambio se queda solo en el padre y los hijos se van
714
725
  // separando de él sin que nadie lo note.
715
726
  const esPadre = state.product?.variantsCount > 0;
716
727
  const cambios = cambiosDeFichaTecnica();
717
728
 
718
729
  if (esPadre && cambios.length > 0) {
719
730
  // Se espera a tener la familia antes de abrir: si no, el diálogo
720
- // aparece diciendo "0 variantes" hasta que llega la respuesta, que es
731
+ // aparece diciendo "0 hijos" hasta que llega la respuesta, que es
721
732
  // justo lo contrario de lo que va a preguntar.
722
733
  setPreparandoPropagacion(true);
723
734
  loadFamily()
@@ -763,7 +774,7 @@ const ProviderProductEditionView = ({
763
774
  };
764
775
 
765
776
  /*
766
- Trae la familia del producto: sus variantes, o sus hermanas si el abierto es
777
+ Trae la familia del producto: sus hijos, o sus hermanos si el abierto es
767
778
  una de ellas.
768
779
 
769
780
  Se pide al abrir la sección y no al montar la pantalla: la mayoría de los
@@ -786,7 +797,7 @@ const ProviderProductEditionView = ({
786
797
  };
787
798
 
788
799
  /*
789
- Abre otra variante de la familia.
800
+ Abre otro producto de la familia.
790
801
 
791
802
  Esta pantalla no lee el producto de la URL: lo lee de sessionStorage, donde
792
803
  lo dejó el listado al hacer clic. Así que navegar a /products/:id sin más
@@ -801,7 +812,7 @@ const ProviderProductEditionView = ({
801
812
  raíz —que la ficha lea de la URL— toca todas las plataformas y no cabe aquí.
802
813
  */
803
814
  /*
804
- Abre la edición múltiple con las variantes de la familia ya cargadas.
815
+ Abre la edición múltiple con los hijos de la familia ya cargados.
805
816
 
806
817
  Esa pantalla lee `multipleEditionList` de sessionStorage —así la deja el
807
818
  listado al pulsar "editar varios"— y necesita el PRODUCTO COMPLETO de cada
@@ -855,7 +866,7 @@ const ProviderProductEditionView = ({
855
866
  payload: {
856
867
  show: true,
857
868
  title: "",
858
- message: "No se pudieron cargar las variantes para editarlas juntas.",
869
+ message: "No se pudieron cargar los hijos para editarlos juntos.",
859
870
  image: errorIcon,
860
871
  },
861
872
  });
@@ -918,7 +929,7 @@ const ProviderProductEditionView = ({
918
929
  el usuario tenga sin guardar.
919
930
 
920
931
  Los dos modos tienen efectos opuestos sobre este producto: ligando hijos GANA
921
- variantes; ligándose a un padre pasa a SER una, y entonces el botón de ligar
932
+ hijos; ligándose a un padre pasa a SER uno, y entonces el botón de ligar
922
933
  desaparece porque la jerarquía es de un solo nivel.
923
934
  */
924
935
  const handleOnLinkedVariants = (result) => {
@@ -941,6 +952,8 @@ const ProviderProductEditionView = ({
941
952
  dispatch({ type: "SET_PRODUCT", payload: updatedProduct });
942
953
  // La lista cacheada quedó vieja: se descarta para que vuelva a pedirse.
943
954
  setFamily(null);
955
+ // Y los servicios, para que los chips reflejen la herencia recién creada.
956
+ setLinkNonce((n) => n + 1);
944
957
 
945
958
  // Y en sessionStorage, que es de donde sale el producto al recargar. Sin
946
959
  // esto el arreglo dura hasta el siguiente F5 y vuelve el contador viejo,
@@ -965,7 +978,7 @@ const ProviderProductEditionView = ({
965
978
  /*
966
979
  Acción "Volver a heredar del producto" de los chips de herencia.
967
980
 
968
- La inicia la variante, no el padre, y descarta un valor capturado a mano: por
981
+ La inicia el hijo, no el padre, y descarta un valor capturado a mano: por
969
982
  eso el endpoint exige la lista explícita de atributos y no acepta un "todos"
970
983
  implícito.
971
984
 
@@ -973,15 +986,25 @@ const ProviderProductEditionView = ({
973
986
  alternativa —recargar la ficha— se llevaría por delante lo que el usuario
974
987
  tenga sin guardar en las otras pestañas.
975
988
  */
976
- const handleOnRestoreInheritance = async (attributeId) => {
989
+ /*
990
+ `kind` distingue de qué pestaña salió el chip. Las descripciones viven en
991
+ `rel_article_description` y la ficha técnica en `article_data`: el endpoint
992
+ recibe una lista o la otra, y lo que devuelve se repinta en su propio
993
+ reducer.
994
+ */
995
+ const handleOnRestoreInheritance = async (attributeId, kind = "attribute") => {
977
996
  if (!attributeId || !state.product?.id_article) return;
978
997
 
998
+ const esDescripcion = kind === "description";
999
+
979
1000
  try {
980
1001
  const res = await axios.post(
981
1002
  `${process.env.REACT_APP_VARIANTS_ENDPOINT}/restore`,
982
1003
  {
983
1004
  articleId: state.product.id_article,
984
- attributes: [Number(attributeId)],
1005
+ ...(esDescripcion
1006
+ ? { descriptions: [Number(attributeId)] }
1007
+ : { attributes: [Number(attributeId)] }),
985
1008
  },
986
1009
  { headers: { Authorization: token } }
987
1010
  );
@@ -990,6 +1013,18 @@ const ProviderProductEditionView = ({
990
1013
  // real viaja en el cuerpo y readPayload lo convierte en excepción.
991
1014
  const data = readPayload(res);
992
1015
 
1016
+ if (esDescripcion) {
1017
+ dispatch({
1018
+ type: "UPDATE_DESCRIPTIONS_INPUTS",
1019
+ payload: (data.descriptionValues || []).map((item) => ({
1020
+ attributeId: item.id_description,
1021
+ value: item.value,
1022
+ isOverridden: 0,
1023
+ })),
1024
+ });
1025
+ return;
1026
+ }
1027
+
993
1028
  dispatch({
994
1029
  type: "UPDATE_DATASHEETS_INPUTS",
995
1030
  payload: (data.values || []).map((item) => ({
@@ -1664,7 +1699,7 @@ const ProviderProductEditionView = ({
1664
1699
  </div>
1665
1700
  <div className="product-information">
1666
1701
  <div className="product-information">
1667
- {/* Solo cuando el producto NO es ya variante de alguien: ligarlo
1702
+ {/* Solo cuando el producto NO es ya hijo de alguien: ligarlo
1668
1703
  otra vez devolvería VARIANT_ALREADY_LINKED sin excepción, y un
1669
1704
  botón que siempre falla es peor que no tenerlo. Para salir de
1670
1705
  una familia está la desvinculación, no este botón. */}
@@ -1687,7 +1722,7 @@ const ProviderProductEditionView = ({
1687
1722
  loadFamily();
1688
1723
  }}
1689
1724
  >
1690
- {state.product.variantsCount} variante
1725
+ {state.product.variantsCount} hijo
1691
1726
  {state.product.variantsCount === 1 ? "" : "s"}
1692
1727
  <span aria-hidden="true">{showFamily ? " ▴" : " ▾"}</span>
1693
1728
  </button>
@@ -1695,8 +1730,8 @@ const ProviderProductEditionView = ({
1695
1730
  </div>
1696
1731
  )}
1697
1732
 
1698
- {/* La ficha de una variante también necesita ver la familia: desde
1699
- aquí se salta al padre o a una hermana sin pasar por el listado. */}
1733
+ {/* La ficha de un hijo también necesita ver la familia: desde
1734
+ aquí se salta al padre o a un hermano sin pasar por el listado. */}
1700
1735
  {state.product?.parentArticleId && (
1701
1736
  <div className="variants-actions">
1702
1737
  <button
@@ -1708,7 +1743,7 @@ const ProviderProductEditionView = ({
1708
1743
  loadFamily();
1709
1744
  }}
1710
1745
  >
1711
- Variante de otro producto
1746
+ Hijo de otro producto
1712
1747
  <span aria-hidden="true">{showFamily ? " ▴" : " ▾"}</span>
1713
1748
  </button>
1714
1749
  </div>
@@ -1756,7 +1791,7 @@ const ProviderProductEditionView = ({
1756
1791
  ? "Abriendo…"
1757
1792
  : variant.isCurrent
1758
1793
  ? "Estás aquí"
1759
- : "Variante"}
1794
+ : "Hijo"}
1760
1795
  </span>
1761
1796
  <span className="name">{variant.name}</span>
1762
1797
  <span className="upc">{variant.upc}</span>
@@ -1764,10 +1799,10 @@ const ProviderProductEditionView = ({
1764
1799
  ))}
1765
1800
 
1766
1801
  {family && !family.error && !family.variants?.length && (
1767
- <p className="loading">Este producto no tiene variantes.</p>
1802
+ <p className="loading">Este producto no tiene hijos.</p>
1768
1803
  )}
1769
1804
 
1770
- {/* Editarlas juntas evita abrir seis fichas para llenar lo
1805
+ {/* Editarlos juntos evita abrir seis fichas para llenar lo
1771
1806
  mismo seis veces, que es el caso real de una familia. */}
1772
1807
  {family?.variants?.length > 1 && (
1773
1808
  <button
@@ -1778,7 +1813,7 @@ const ProviderProductEditionView = ({
1778
1813
  >
1779
1814
  {preparandoEdicionMultiple
1780
1815
  ? "Abriendo…"
1781
- : `Editar las ${family.variants.length} variantes juntas`}
1816
+ : `Editar los ${family.variants.length} hijos juntos`}
1782
1817
  </button>
1783
1818
  )}
1784
1819
  </div>
@@ -1849,6 +1884,12 @@ const ProviderProductEditionView = ({
1849
1884
  updatedDescriptions={
1850
1885
  state.updated_descriptions_inputs
1851
1886
  }
1887
+ /* El parentesco es del producto, no del servicio:
1888
+ los dos endpoints lo devuelven igual. Se toma de
1889
+ la ficha técnica para no depender de que la
1890
+ llamada de descripciones traiga el bloque. */
1891
+ variant={state.services?.datasheets?.variant}
1892
+ onRestoreInheritance={handleOnRestoreInheritance}
1852
1893
  articleId={state.product?.id_article}
1853
1894
  version={state.product.version}
1854
1895
  isShowbox={false}
@@ -140,7 +140,7 @@ export const ProviderProductEditionProvider = ({ children }) => {
140
140
  /*
141
141
  `propagation` es opcional y solo lo manda la ficha de un producto padre, con
142
142
  lo que el usuario eligió en el diálogo. Sin ese bloque el backend no propaga
143
- nada: bajar un cambio a las variantes es una decisión, no un efecto
143
+ nada: bajar un cambio a los hijos es una decisión, no un efecto
144
144
  secundario de guardar.
145
145
  */
146
146
  const saveDatasheets = async (token, propagation) => {
@@ -181,17 +181,17 @@ export const ProviderProductEditionProvider = ({ children }) => {
181
181
  type: "SET_UPDATED_DATASHEETS_INPUTS",
182
182
  payload: [],
183
183
  });
184
- // En una variante, todo atributo que el hijo toca deja de ser heredado:
184
+ // En un hijo, todo atributo que se toca deja de ser heredado:
185
185
  // update-articles-data le pone is_overridden = 1 en la base. Sin
186
186
  // reflejarlo aquí, el chip sigue diciendo "Heredado" hasta la siguiente
187
187
  // carga de la ficha, y el usuario ve una marca que ya es falsa.
188
- const esVariante =
188
+ const esHijo =
189
189
  state.services?.datasheets?.variant?.isVariant ??
190
190
  !!state.product?.parentArticleId;
191
191
 
192
192
  dispatch({
193
193
  type: "UPDATE_DATASHEETS_INPUTS",
194
- payload: esVariante
194
+ payload: esHijo
195
195
  ? state.updated_datasheets_inputs.map((item) => ({
196
196
  ...item,
197
197
  isOverridden: 1,
@@ -61,7 +61,19 @@ export const inputsReducer = (state, action) => {
61
61
  const updatedInput = action.payload.find(
62
62
  (updated) => updated.attributeId === input.id
63
63
  );
64
- return updatedInput ? { ...input, value: updatedInput.value } : input;
64
+ if (!updatedInput) return input;
65
+
66
+ return {
67
+ ...input,
68
+ value: updatedInput.value,
69
+ // Opcional a propósito, igual que en la ficha técnica: quien solo
70
+ // cambia el valor no toca la marca de herencia. La usa "Volver a
71
+ // heredar", que sí necesita devolverla a heredada para que el chip
72
+ // deje de decir "Propio".
73
+ ...(updatedInput.isOverridden === undefined
74
+ ? {}
75
+ : { isOverridden: updatedInput.isOverridden }),
76
+ };
65
77
  }
66
78
  );
67
79