contentoh-components-library 21.6.61 → 21.6.63

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 (102) hide show
  1. package/.claude/settings.local.json +7 -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/MetricCard/index.js +3 -1
  7. package/dist/components/organisms/DashboardMetric/DashboardMetric.stories.js +12 -2
  8. package/dist/components/organisms/DashboardMetric/index.js +24 -1
  9. package/dist/components/organisms/DashboardMetric/styles.js +1 -1
  10. package/dist/components/pages/CreatorsProductEdition/CreatorsProductEditionView.js +11 -5
  11. package/dist/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +58 -31
  12. package/dist/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +5 -1
  13. package/dist/components/pages/CreatorsProductEdition/mocks/mockApi.js +3 -40
  14. package/dist/components/pages/CreatorsProductEdition/sections/ImagesSection.js +18 -3
  15. package/dist/components/pages/CreatorsProductEdition/sections/StatusChip.js +4 -13
  16. package/dist/components/pages/CreatorsProductEdition/sections/TabsBar.js +10 -0
  17. package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +10 -2
  18. package/dist/components/pages/Dashboard/dashboardUtils.js +3 -3
  19. package/dist/components/pages/Dashboard/index.js +102 -88
  20. package/dist/components/pages/Dashboard/styles.js +1 -1
  21. package/dist/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +2 -15
  22. package/dist/components/pages/RetailerProductEdition/index.js +40 -57
  23. package/dist/components/pages/RetailerProductEdition/utils.js +2 -6
  24. package/dist/index.js +0 -13
  25. package/package.json +1 -1
  26. package/src/components/atoms/MetricCard/index.js +2 -2
  27. package/src/components/organisms/DashboardMetric/DashboardMetric.stories.js +11 -0
  28. package/src/components/organisms/DashboardMetric/index.js +13 -1
  29. package/src/components/organisms/DashboardMetric/styles.js +13 -0
  30. package/src/components/pages/CreatorsProductEdition/CreatorsProductEditionView.jsx +22 -3
  31. package/src/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +36 -1
  32. package/src/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +15 -3
  33. package/src/components/pages/CreatorsProductEdition/mocks/mockApi.js +8 -18
  34. package/src/components/pages/CreatorsProductEdition/sections/ImagesSection.jsx +24 -2
  35. package/src/components/pages/CreatorsProductEdition/sections/StatusChip.jsx +4 -8
  36. package/src/components/pages/CreatorsProductEdition/sections/TabsBar.jsx +9 -0
  37. package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +6 -0
  38. package/src/components/pages/Dashboard/dashboardUtils.js +3 -3
  39. package/src/components/pages/Dashboard/index.js +62 -106
  40. package/src/components/pages/Dashboard/styles.js +17 -12
  41. package/src/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +0 -12
  42. package/src/components/pages/RetailerProductEdition/index.js +3 -28
  43. package/src/components/pages/RetailerProductEdition/utils.js +0 -5
  44. package/src/index.js +0 -1
  45. package/dist/components/organisms/ServicesNavigation/ServiceToolbar.js +0 -210
  46. package/dist/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -138
  47. package/dist/components/organisms/ServicesNavigation/ServicesRail.js +0 -705
  48. package/dist/components/organisms/ServicesNavigation/catalog.js +0 -505
  49. package/dist/components/organisms/ServicesNavigation/index.js +0 -337
  50. package/dist/components/organisms/ServicesNavigation/model.js +0 -167
  51. package/dist/components/organisms/ServicesNavigation/ui.js +0 -371
  52. package/dist/components/organisms/ServicesNavigation/views.js +0 -1237
  53. package/dist/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -459
  54. package/dist/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -119
  55. package/dist/components/organisms/ServicesWorkspace/demo.js +0 -137
  56. package/dist/components/organisms/ServicesWorkspace/index.js +0 -813
  57. package/dist/components/organisms/ServicesWorkspace/servicesModel.js +0 -351
  58. package/dist/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -37
  59. package/dist/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -387
  60. package/dist/components/pages/ProductEdition/ProductEdition.stories.js +0 -148
  61. package/dist/components/pages/ProductEdition/ProductEditionView.js +0 -1355
  62. package/dist/components/pages/ProductEdition/ServiceHeader.js +0 -142
  63. package/dist/components/pages/ProductEdition/api.js +0 -256
  64. package/dist/components/pages/ProductEdition/index.js +0 -37
  65. package/dist/components/pages/ProductEdition/permissions.js +0 -200
  66. package/dist/components/pages/ProductEdition/permissions.test.js +0 -320
  67. package/dist/components/pages/ProductEdition/sections/CatalogServicePanel.js +0 -237
  68. package/dist/components/pages/ProductEdition/sections/EditionTopBar.js +0 -174
  69. package/dist/components/pages/ProductEdition/sections/ImagePanel.js +0 -440
  70. package/dist/components/pages/ProductEdition/sections/ServiceTeam.js +0 -275
  71. package/dist/components/pages/ProductEdition/sections/ServiceTools.js +0 -186
  72. package/dist/components/pages/ProductEdition/services.js +0 -233
  73. package/dist/components/pages/ProductEdition/services.test.js +0 -195
  74. package/src/components/organisms/ServicesNavigation/ServiceToolbar.jsx +0 -143
  75. package/src/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -101
  76. package/src/components/organisms/ServicesNavigation/ServicesRail.jsx +0 -591
  77. package/src/components/organisms/ServicesNavigation/catalog.js +0 -180
  78. package/src/components/organisms/ServicesNavigation/index.jsx +0 -232
  79. package/src/components/organisms/ServicesNavigation/model.js +0 -85
  80. package/src/components/organisms/ServicesNavigation/ui.jsx +0 -229
  81. package/src/components/organisms/ServicesNavigation/views.jsx +0 -705
  82. package/src/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -348
  83. package/src/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -61
  84. package/src/components/organisms/ServicesWorkspace/demo.js +0 -58
  85. package/src/components/organisms/ServicesWorkspace/index.js +0 -556
  86. package/src/components/organisms/ServicesWorkspace/servicesModel.js +0 -203
  87. package/src/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -27
  88. package/src/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -175
  89. package/src/components/pages/ProductEdition/ProductEdition.stories.js +0 -106
  90. package/src/components/pages/ProductEdition/ProductEditionView.jsx +0 -1142
  91. package/src/components/pages/ProductEdition/ServiceHeader.jsx +0 -104
  92. package/src/components/pages/ProductEdition/api.js +0 -84
  93. package/src/components/pages/ProductEdition/index.js +0 -21
  94. package/src/components/pages/ProductEdition/permissions.js +0 -128
  95. package/src/components/pages/ProductEdition/permissions.test.js +0 -249
  96. package/src/components/pages/ProductEdition/sections/CatalogServicePanel.jsx +0 -123
  97. package/src/components/pages/ProductEdition/sections/EditionTopBar.jsx +0 -134
  98. package/src/components/pages/ProductEdition/sections/ImagePanel.jsx +0 -342
  99. package/src/components/pages/ProductEdition/sections/ServiceTeam.jsx +0 -184
  100. package/src/components/pages/ProductEdition/sections/ServiceTools.jsx +0 -134
  101. package/src/components/pages/ProductEdition/services.js +0 -187
  102. package/src/components/pages/ProductEdition/services.test.js +0 -88
@@ -1,348 +0,0 @@
1
- import { useMemo, useRef, useState } from "react";
2
- import {
3
- Avatar,
4
- Box,
5
- ButtonBase,
6
- CircularProgress,
7
- InputAdornment,
8
- List,
9
- ListItemButton,
10
- Popover,
11
- TextField,
12
- Tooltip,
13
- Typography,
14
- } from "@mui/material";
15
- import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
16
- import PersonAddAlt1OutlinedIcon from "@mui/icons-material/PersonAddAlt1Outlined";
17
- import SearchIcon from "@mui/icons-material/Search";
18
- import CheckIcon from "@mui/icons-material/Check";
19
- import { getProfilePicture } from "../../../global-files/data";
20
- import { whiteTooltipProps } from "./tooltipStyles";
21
-
22
- /** Roles que ven la asignación pero no pueden cambiarla (igual que AsignationControl). */
23
- export const READ_ONLY_ROLES = [2, 3, 7, 8];
24
-
25
- export const canCurrentUserAssign = () => {
26
- try {
27
- const user = JSON.parse(sessionStorage.getItem("user"));
28
- return !READ_ONLY_ROLES.includes(user?.id_role);
29
- } catch (error) {
30
- return true;
31
- }
32
- };
33
-
34
- /**
35
- * ¿El clic fue dentro de una capa flotante de MUI (selector, tooltip, diálogo)?
36
- * Esas capas se pintan fuera del panel lateral, así que los "clic afuera para
37
- * cerrar" deben ignorarlas.
38
- */
39
- export const isFloatingLayerClick = (target) =>
40
- Boolean(target?.closest?.(".MuiPopover-root, .MuiModal-root, .MuiTooltip-popper"));
41
-
42
- export const fullName = (user) => [user?.name, user?.last_name].filter(Boolean).join(" ");
43
-
44
- const initials = (user) =>
45
- [user?.name, user?.last_name]
46
- .filter(Boolean)
47
- .map((part) => part.trim()[0])
48
- .join("")
49
- .toUpperCase()
50
- .slice(0, 2);
51
-
52
- /** Foto del usuario; si no carga, sus iniciales (MUI muestra `children` cuando falla la imagen). */
53
- function UserAvatar({ user, size = 28 }) {
54
- return (
55
- <Avatar
56
- src={user?.id_user ? getProfilePicture(user.id_user, 40, 40) : undefined}
57
- alt={fullName(user)}
58
- sx={{
59
- width: size,
60
- height: size,
61
- fontSize: size * 0.4,
62
- fontFamily: "Lato, sans-serif",
63
- fontWeight: 700,
64
- bgcolor: "#F0EEF2",
65
- color: "#503D66",
66
- }}
67
- >
68
- {initials(user)}
69
- </Avatar>
70
- );
71
- }
72
-
73
- const pillSx = {
74
- display: "inline-flex",
75
- alignItems: "center",
76
- gap: 0.75,
77
- height: 34,
78
- pl: 1.25,
79
- pr: 0.5,
80
- borderRadius: "17px",
81
- backgroundColor: "#F0F0F0",
82
- border: "1px solid transparent",
83
- fontFamily: "Lato, sans-serif",
84
- transition: "background-color 120ms, border-color 120ms",
85
- };
86
-
87
- const statusSx = {
88
- fontFamily: "Lato, sans-serif",
89
- fontSize: 12,
90
- fontWeight: 600,
91
- color: "#707070",
92
- whiteSpace: "nowrap",
93
- };
94
-
95
- /**
96
- * Pastilla "estatus + responsable" y selector de personas.
97
- *
98
- * Copia de la de Creators (ServicesCell/AssigneePicker) para que la edición se
99
- * vea y se comporte igual que la tabla. Colores fijos porque la librería no
100
- * define las variables CSS de las aplicaciones.
101
- *
102
- * - Clic en la pastilla: abre un buscador con la lista de personas asignables.
103
- * - `lockedReason`: si viene, la pastilla muestra un candado, no abre nada
104
- * y explica el motivo en el tooltip.
105
- * - `onAssign(user)`: se llama al elegir a alguien (el padre llama assignUser).
106
- */
107
- export default function AssigneePicker({
108
- statusLabel,
109
- statusTitle,
110
- users = [],
111
- currentId = null,
112
- onAssign,
113
- lockedReason = null,
114
- roleLabel = "Responsable",
115
- loading = false,
116
- }) {
117
- const pillRef = useRef(null);
118
- const [open, setOpen] = useState(false);
119
- const [query, setQuery] = useState("");
120
- // Asignación optimista: se ve de inmediato aunque la tabla tarde en refrescarse.
121
- const [pickedId, setPickedId] = useState(null);
122
-
123
- const selectedId = pickedId ?? currentId;
124
- const current = useMemo(
125
- () => users.find((user) => user.id_user === selectedId) || null,
126
- [users, selectedId]
127
- );
128
-
129
- // `statusLabel` puede ser texto fijo o una función del asignado actual (para
130
- // mostrar el nombre de la persona apenas se elige, sin esperar al padre).
131
- const label = typeof statusLabel === "function" ? statusLabel(current) : statusLabel;
132
-
133
- const filtered = useMemo(() => {
134
- const text = query.trim().toLowerCase();
135
- if (!text) return users;
136
- return users.filter((user) => fullName(user).toLowerCase().includes(text));
137
- }, [users, query]);
138
-
139
- // Al elegir con Enter, el foco vuelve a la pastilla y el mismo Enter la
140
- // volvía a abrir. Se ignoran los clics que llegan justo después de cerrar.
141
- const closedAtRef = useRef(0);
142
- const close = () => {
143
- closedAtRef.current = Date.now();
144
- setOpen(false);
145
- setQuery("");
146
- };
147
-
148
- const pick = (user) => {
149
- if (user.id_user !== selectedId) {
150
- setPickedId(user.id_user);
151
- onAssign?.(user);
152
- }
153
- close();
154
- };
155
-
156
- // Mientras llegan las asignaciones (se piden al desplegar la etapa).
157
- if (loading) {
158
- return (
159
- <Box
160
- component="span"
161
- role="status"
162
- aria-busy="true"
163
- aria-label={`Cargando ${roleLabel.toLowerCase()}`}
164
- sx={{ ...pillSx, cursor: "progress", pr: 0.75 }}
165
- >
166
- <Typography component="span" title={statusTitle} sx={statusSx}>
167
- {label}
168
- </Typography>
169
- <Box
170
- component="span"
171
- sx={{ width: 26, height: 26, display: "inline-flex", alignItems: "center", justifyContent: "center" }}
172
- >
173
- <CircularProgress size={16} thickness={5} sx={{ color: "#8A6CAA" }} />
174
- </Box>
175
- </Box>
176
- );
177
- }
178
-
179
- if (lockedReason) {
180
- return (
181
- <Tooltip {...whiteTooltipProps} title={lockedReason} arrow placement="left">
182
- <Box
183
- component="span"
184
- role="img"
185
- aria-label={`No se puede asignar: ${lockedReason}`}
186
- sx={{ ...pillSx, cursor: "not-allowed", pr: 1, opacity: 0.85 }}
187
- >
188
- <Typography component="span" title={statusTitle} sx={statusSx}>
189
- {label}
190
- </Typography>
191
- <LockOutlinedIcon sx={{ fontSize: 18, color: "#707070" }} />
192
- </Box>
193
- </Tooltip>
194
- );
195
- }
196
-
197
- const tooltip = current ? `${roleLabel}: ${fullName(current)}` : `${roleLabel}: por asignar`;
198
-
199
- return (
200
- <>
201
- <Tooltip {...whiteTooltipProps} title={open ? "" : tooltip} arrow placement="left">
202
- <ButtonBase
203
- ref={pillRef}
204
- onClick={(event) => {
205
- event.stopPropagation();
206
- if (Date.now() - closedAtRef.current < 400) return;
207
- setOpen(true);
208
- }}
209
- aria-haspopup="dialog"
210
- aria-expanded={open}
211
- aria-label={`${tooltip}. Cambiar`}
212
- sx={{
213
- ...pillSx,
214
- cursor: "pointer",
215
- ...(open && { borderColor: "#E33AA9", backgroundColor: "#F0EEF2" }),
216
- "&:hover": { backgroundColor: "#F0EEF2" },
217
- "&.Mui-focusVisible": { borderColor: "#E33AA9" },
218
- }}
219
- >
220
- <Typography component="span" title={statusTitle} sx={statusSx}>
221
- {label}
222
- </Typography>
223
- {current ? (
224
- <UserAvatar user={current} size={26} />
225
- ) : (
226
- <Box
227
- component="span"
228
- sx={{
229
- width: 26,
230
- height: 26,
231
- borderRadius: "50%",
232
- display: "inline-flex",
233
- alignItems: "center",
234
- justifyContent: "center",
235
- border: "1px dashed #8A6CAA",
236
- color: "#8A6CAA",
237
- backgroundColor: "#FFFFFF",
238
- }}
239
- >
240
- <PersonAddAlt1OutlinedIcon sx={{ fontSize: 15 }} />
241
- </Box>
242
- )}
243
- </ButtonBase>
244
- </Tooltip>
245
-
246
- <Popover
247
- open={open}
248
- anchorEl={pillRef.current}
249
- onClose={close}
250
- onClick={(event) => event.stopPropagation()}
251
- anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
252
- transformOrigin={{ vertical: "top", horizontal: "right" }}
253
- PaperProps={{
254
- sx: {
255
- mt: 0.75,
256
- width: 300,
257
- maxWidth: "calc(100vw - 32px)",
258
- borderRadius: "12px",
259
- border: "1px solid #F0EEF2",
260
- boxShadow: 6,
261
- overflow: "hidden",
262
- },
263
- }}
264
- >
265
- <Box sx={{ p: 1.5, pb: 1, display: "flex", flexDirection: "column", gap: 1 }}>
266
- <Typography
267
- sx={{ fontFamily: "Raleway, sans-serif", fontSize: 13, fontWeight: 600, color: "#503D66" }}
268
- >
269
- Asignar {roleLabel.toLowerCase()}
270
- </Typography>
271
- <TextField
272
- autoFocus
273
- size="small"
274
- fullWidth
275
- placeholder="Buscar persona…"
276
- value={query}
277
- onChange={(event) => setQuery(event.target.value)}
278
- onKeyDown={(event) => {
279
- if (event.key === "Enter" && filtered[0]) {
280
- event.preventDefault();
281
- pick(filtered[0]);
282
- }
283
- }}
284
- InputProps={{
285
- startAdornment: (
286
- <InputAdornment position="start">
287
- <SearchIcon sx={{ fontSize: 18, color: "#707070" }} />
288
- </InputAdornment>
289
- ),
290
- sx: {
291
- borderRadius: "8px",
292
- fontFamily: "Lato, sans-serif",
293
- fontSize: 13,
294
- "&.Mui-focused .MuiOutlinedInput-notchedOutline": { borderColor: "#E33AA9" },
295
- },
296
- }}
297
- />
298
- </Box>
299
-
300
- <List dense disablePadding sx={{ maxHeight: 280, overflowY: "auto", px: 0.75, pb: 0.75 }}>
301
- {filtered.map((user) => {
302
- const isSelected = user.id_user === selectedId;
303
- return (
304
- <ListItemButton
305
- key={user.id_user}
306
- selected={isSelected}
307
- onClick={() => pick(user)}
308
- sx={{
309
- gap: 1.25,
310
- borderRadius: "8px",
311
- py: 0.75,
312
- "&:hover": { backgroundColor: "#F0EEF2" },
313
- "&.Mui-selected, &.Mui-selected:hover": { backgroundColor: "#F0EEF2" },
314
- }}
315
- >
316
- <UserAvatar user={user} size={30} />
317
- <Typography
318
- sx={{
319
- flex: 1,
320
- minWidth: 0,
321
- fontFamily: "Lato, sans-serif",
322
- fontSize: 14,
323
- fontWeight: isSelected ? 700 : 400,
324
- color: isSelected ? "#503D66" : "#262626",
325
- overflow: "hidden",
326
- textOverflow: "ellipsis",
327
- whiteSpace: "nowrap",
328
- }}
329
- >
330
- {fullName(user)}
331
- </Typography>
332
- {isSelected && <CheckIcon sx={{ fontSize: 18, color: "#E33AA9" }} />}
333
- </ListItemButton>
334
- );
335
- })}
336
-
337
- {filtered.length === 0 && (
338
- <Typography
339
- sx={{ py: 2, textAlign: "center", fontFamily: "Lato, sans-serif", fontSize: 13, color: "#707070" }}
340
- >
341
- {users.length === 0 ? "No hay personas asignables" : "Sin coincidencias"}
342
- </Typography>
343
- )}
344
- </List>
345
- </Popover>
346
- </>
347
- );
348
- }
@@ -1,61 +0,0 @@
1
- import ServicesWorkspace from "./index";
2
- import { withDemoServices, DEMO_USERS } from "./demo";
3
-
4
- export default {
5
- title: "Components/organisms/ServicesWorkspace",
6
- component: ServicesWorkspace,
7
- argTypes: { onAssign: { action: "onAssign" }, onOpenEditor: { action: "onOpenEditor" } },
8
- };
9
-
10
- const retailers = [
11
- { id_retailer: 4, retailer: "Walmart" },
12
- { id_retailer: 9, retailer: "Soriana" },
13
- { id_retailer: 12, retailer: "Alsuper" },
14
- ];
15
-
16
- // Producto con la forma que deja `normalizeProduct` en la edición.
17
- const baseProduct = {
18
- id_article: 11080102,
19
- id_order: 10478,
20
- name: "Prueba variantes 2 - hijo de 1",
21
- datasheet_status: "AS",
22
- description_status: "RA/AS",
23
- images_status: "PA",
24
- id_datasheet_especialist: 9101,
25
- id_datasheet_facilitator: 9103,
26
- id_description_especialist: 9102,
27
- id_images_especialist: 0,
28
- id_auditor: 9104,
29
- statusByRetailer: [
30
- { service: "datasheet", retailer_id: 4, status: "AS" },
31
- { service: "datasheet", retailer_id: 9, status: "CA" },
32
- { service: "description", retailer_id: 4, status: "RA/AS" },
33
- { service: "description", retailer_id: 12, status: "AC" },
34
- { service: "images", retailer_id: 4, status: "PA" },
35
- ],
36
- };
37
-
38
- const users = { especialist: DEMO_USERS, facilitator: DEMO_USERS, auditor: DEMO_USERS };
39
-
40
- const Template = (args) => (
41
- <div style={{ maxWidth: 1100, padding: 24, background: "#FAFAFA" }}>
42
- <ServicesWorkspace {...args} />
43
- </div>
44
- );
45
-
46
- /** Producto de hoy: solo Ficha técnica, Descripción e Imágenes. */
47
- export const TresServicios = Template.bind({});
48
- TresServicios.args = { product: baseProduct, retailers, users, canAssign: true };
49
-
50
- /** Con 17 servicios (3 de siempre + 14 de ejemplo). */
51
- export const DiecisieteServicios = Template.bind({});
52
- DiecisieteServicios.args = {
53
- product: withDemoServices(baseProduct, 14),
54
- retailers,
55
- users,
56
- canAssign: true,
57
- };
58
-
59
- /** Rol que solo consulta (sin asignar). */
60
- export const SoloConsulta = Template.bind({});
61
- SoloConsulta.args = { ...DiecisieteServicios.args, canAssign: false };
@@ -1,58 +0,0 @@
1
- /*
2
- Servicios de ejemplo para revisar el diseño mientras la base solo tiene los
3
- tres servicios de siempre. Mismo catálogo que la demo de la tabla de Creators
4
- (ServicesCell/demoServices.js), con el tipo de entregable de cada uno.
5
-
6
- Llegan en `product.demo_services` (los pone Creators con ?demoServicios=1) o
7
- se generan aquí con `withDemoServices` (historias de Storybook).
8
- */
9
- export const DEMO_CATALOG = [
10
- { service_name: "quality", display_name: "Calidad", deliverable_type: "Reporte" },
11
- { service_name: "datasheetHeader", display_name: "Header Ficha", deliverable_type: "Texto" },
12
- { service_name: "brandstore", display_name: "Brandstore", deliverable_type: "Diseño" },
13
- { service_name: "premiumImage", display_name: "Imagen Premium", deliverable_type: "Imágenes" },
14
- { service_name: "resize", display_name: "Resize", deliverable_type: "Imágenes" },
15
- { service_name: "uploadOnly", display_name: "Solo Carga", deliverable_type: "Carga en cadena" },
16
- { service_name: "audit", display_name: "Auditoría", deliverable_type: "Reporte" },
17
- { service_name: "photo", display_name: "Foto", deliverable_type: "Imágenes" },
18
- { service_name: "edition", display_name: "Edición", deliverable_type: "Imágenes" },
19
- { service_name: "aplusPlatinum", display_name: "A+ / Platinum SKU", deliverable_type: "Diseño" },
20
- { service_name: "benefitInfographics", display_name: "Infografías Beneficios", deliverable_type: "Diseño" },
21
- { service_name: "image360", display_name: "360°", deliverable_type: "Archivos 360" },
22
- { service_name: "secodatReview", display_name: "Revisión secodat", deliverable_type: "Reporte" },
23
- { service_name: "videos", display_name: "Videos", deliverable_type: "Video" },
24
- { service_name: "translation", display_name: "Traducción", deliverable_type: "Texto" },
25
- { service_name: "followUp", display_name: "Seguimiento", deliverable_type: "Reporte" },
26
- ];
27
-
28
- const DEMO_STATUSES = ["PA", "AS", "CA", "AC", "RA/AS", "AA", "AP", "Ex"];
29
-
30
- export const isServicesDemoEnabled = () => {
31
- try {
32
- return new URLSearchParams(window.location.search).has("demoServicios");
33
- } catch (error) {
34
- return false;
35
- }
36
- };
37
-
38
- /** Copia del producto con `count` servicios de ejemplo (estables por producto). */
39
- export const withDemoServices = (product, count = 14) => {
40
- const seed = String(product?.id_article ?? product?.article?.id_article ?? 7)
41
- .split("")
42
- .reduce((acc, char) => (acc * 31 + char.charCodeAt(0)) % 9973, 7);
43
- const demo = Array.from({ length: Math.min(count, DEMO_CATALOG.length) }, (_, index) => {
44
- const service = DEMO_CATALOG[(seed + index) % DEMO_CATALOG.length];
45
- return { ...service, status: DEMO_STATUSES[(seed + index * 3) % DEMO_STATUSES.length], has_workflow: true };
46
- });
47
- return { ...product, demo_services: demo };
48
- };
49
-
50
- /** Usuarios de ejemplo para los selectores. */
51
- export const DEMO_USERS = [
52
- ["Ana", "López"],
53
- ["Carlos", "Méndez"],
54
- ["Diana", "Ruiz"],
55
- ["Eduardo", "Pérez"],
56
- ["Fernanda", "Soto"],
57
- ["Gabriel", "Torres"],
58
- ].map(([name, lastName], index) => ({ id_user: 9100 + index, name, last_name: lastName }));