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,556 +0,0 @@
1
- import { useEffect, useMemo, useState } from "react";
2
- import {
3
- Box,
4
- Button,
5
- ButtonBase,
6
- Collapse,
7
- InputAdornment,
8
- TextField,
9
- Tooltip,
10
- Typography,
11
- } from "@mui/material";
12
- import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
13
- import SearchIcon from "@mui/icons-material/Search";
14
- import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
15
- import CloudUploadOutlinedIcon from "@mui/icons-material/CloudUploadOutlined";
16
- import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
17
- import AssigneePicker from "./AssigneePicker";
18
- import { whiteTooltipProps } from "./tooltipStyles";
19
- import { DEMO_CATALOG } from "./demo";
20
- import {
21
- getAttention,
22
- getProductServices,
23
- getRetailerStatuses,
24
- getServiceStage,
25
- getStatusColor,
26
- getStatusName,
27
- groupServicesByStage,
28
- pickInitialService,
29
- } from "./servicesModel";
30
-
31
- /*
32
- Pestaña "Servicios" de la edición de producto.
33
-
34
- Con 3 servicios bastaban 3 pestañas; con 20 la barra deja de servir. Aquí
35
- todos los servicios del producto viven en un mismo lugar, con el lenguaje que
36
- ya usa la tabla de Creators:
37
- - izquierda: el resumen (barra por etapas y lo que pide atención), un
38
- buscador y la lista agrupada por etapa, desplegable.
39
- - derecha: el servicio elegido: estatus, equipo (responsable, facilitador,
40
- auditor), su avance por cadena y su entregable.
41
- Ficha técnica, Descripción e Imágenes también aparecen en la lista para ver
42
- el panorama completo, pero se siguen editando en su pestaña (botón "Ir a").
43
- */
44
-
45
- const C = {
46
- pink: "#E33AA9",
47
- pinkHard: "#B12D84",
48
- purple: "#8A6CAA",
49
- textPurple: "#503D66",
50
- purpleLight: "#F0EEF2",
51
- white: "#FFFFFF",
52
- gray: "#707070",
53
- grayLight: "#F0F0F0",
54
- black: "#262626",
55
- };
56
-
57
- const sectionTitleSx = {
58
- fontFamily: "Lato, sans-serif",
59
- fontSize: 12,
60
- fontWeight: 700,
61
- letterSpacing: 0.4,
62
- textTransform: "uppercase",
63
- color: C.gray,
64
- mb: 1,
65
- };
66
-
67
- function StatusChip({ status, long = false }) {
68
- const color = getStatusColor(status);
69
- return (
70
- <Box
71
- component="span"
72
- title={getStatusName(status)}
73
- sx={{
74
- display: "inline-flex",
75
- alignItems: "center",
76
- height: 22,
77
- px: 1,
78
- borderRadius: "6px",
79
- background: color,
80
- color: C.white,
81
- fontFamily: "Lato, sans-serif",
82
- fontSize: 12,
83
- fontWeight: 700,
84
- whiteSpace: "nowrap",
85
- }}
86
- >
87
- {long ? `${status} · ${getStatusName(status)}` : status}
88
- </Box>
89
- );
90
- }
91
-
92
- function StageDot({ color, size = 8 }) {
93
- return (
94
- <Box component="span" sx={{ width: size, height: size, borderRadius: "50%", background: color, flexShrink: 0 }} />
95
- );
96
- }
97
-
98
- function ServicesBar({ groups }) {
99
- return (
100
- <Box aria-hidden="true" sx={{ display: "flex", gap: "2px", height: 8 }}>
101
- {groups.flatMap((group) =>
102
- group.services.map((service) => (
103
- <Box
104
- key={service.service_name}
105
- component="span"
106
- sx={{ flex: "1 1 0", minWidth: 3, borderRadius: "2px", background: group.stage.color }}
107
- />
108
- ))
109
- )}
110
- </Box>
111
- );
112
- }
113
-
114
- export default function ServicesWorkspace({
115
- product,
116
- retailers = [],
117
- users = {},
118
- canAssign = true,
119
- onAssign,
120
- onOpenEditor,
121
- }) {
122
- const services = useMemo(() => getProductServices(product), [product]);
123
- const groups = useMemo(() => groupServicesByStage(services), [services]);
124
- const attention = getAttention(groups);
125
-
126
- const [query, setQuery] = useState("");
127
- const [selectedName, setSelectedName] = useState(() => pickInitialService(services));
128
- const [expanded, setExpanded] = useState([]);
129
-
130
- // Al cambiar de producto: se elige lo que pide atención y se abre su etapa.
131
- const productKey = `${product?.id_article}-${product?.id_order}`;
132
- useEffect(() => {
133
- const first = pickInitialService(services);
134
- setSelectedName(first);
135
- const firstService = services.find((service) => service.service_name === first);
136
- setExpanded(firstService ? [getServiceStage(firstService).stage_name] : []);
137
- // eslint-disable-next-line react-hooks/exhaustive-deps
138
- }, [productKey]);
139
-
140
- const text = query.trim().toLowerCase();
141
- const visibleGroups = text
142
- ? groupServicesByStage(services.filter((service) => service.display_name.toLowerCase().includes(text)))
143
- : groups;
144
-
145
- const selected = services.find((service) => service.service_name === selectedName) ?? null;
146
-
147
- const toggle = (stageName) =>
148
- setExpanded((prev) => (prev.includes(stageName) ? prev.filter((name) => name !== stageName) : [...prev, stageName]));
149
-
150
- if (services.length === 0) {
151
- return (
152
- <Box sx={{ p: 4, textAlign: "center", color: C.gray, fontFamily: "Lato, sans-serif" }}>
153
- Este producto no tiene servicios solicitados.
154
- </Box>
155
- );
156
- }
157
-
158
- return (
159
- <Box
160
- sx={{
161
- display: "grid",
162
- gridTemplateColumns: { xs: "1fr", md: "300px minmax(0, 1fr)" },
163
- minHeight: 480,
164
- border: `1px solid ${C.purpleLight}`,
165
- borderRadius: "12px",
166
- background: C.white,
167
- overflow: "hidden",
168
- textAlign: "left",
169
- // La librería no fija la tipografía del cuerpo: sin esto, los textos
170
- // sueltos (Box) heredan la del navegador fuera de las aplicaciones.
171
- fontFamily: "Lato, sans-serif",
172
- }}
173
- >
174
- {/* ---- Lista ---- */}
175
- <Box
176
- component="nav"
177
- aria-label="Servicios del producto"
178
- sx={{
179
- borderRight: { md: `1px solid ${C.purpleLight}` },
180
- p: 2,
181
- display: "flex",
182
- flexDirection: "column",
183
- gap: 1.5,
184
- minWidth: 0,
185
- }}
186
- >
187
- <Box sx={{ display: "flex", alignItems: "baseline", gap: 1 }}>
188
- <Typography sx={{ fontFamily: "Raleway, sans-serif", fontSize: 16, fontWeight: 600, color: C.textPurple }}>
189
- Servicios
190
- </Typography>
191
- <Typography sx={{ flex: 1, fontFamily: "Lato, sans-serif", fontSize: 12, color: C.gray }}>
192
- {services.length}
193
- </Typography>
194
- {attention && (
195
- <Box
196
- component="span"
197
- sx={{
198
- display: "inline-flex",
199
- alignItems: "center",
200
- gap: 0.75,
201
- px: 1,
202
- height: 22,
203
- alignSelf: "center",
204
- borderRadius: "11px",
205
- background: C.purpleLight,
206
- fontFamily: "Lato, sans-serif",
207
- fontSize: 12,
208
- fontWeight: 700,
209
- color: C.textPurple,
210
- whiteSpace: "nowrap",
211
- }}
212
- >
213
- <StageDot color={attention.stage.color} />
214
- {attention.label}
215
- </Box>
216
- )}
217
- </Box>
218
-
219
- <ServicesBar groups={groups} />
220
-
221
- <TextField
222
- size="small"
223
- placeholder="Buscar servicio…"
224
- value={query}
225
- onChange={(event) => setQuery(event.target.value)}
226
- InputProps={{
227
- startAdornment: (
228
- <InputAdornment position="start">
229
- <SearchIcon sx={{ fontSize: 18, color: C.gray }} />
230
- </InputAdornment>
231
- ),
232
- sx: {
233
- borderRadius: "8px",
234
- fontFamily: "Lato, sans-serif",
235
- fontSize: 13,
236
- "&.Mui-focused .MuiOutlinedInput-notchedOutline": { borderColor: C.pink },
237
- },
238
- }}
239
- />
240
-
241
- <Box sx={{ display: "flex", flexDirection: "column", gap: 0.5, overflowY: "auto", maxHeight: 520 }}>
242
- {visibleGroups.length === 0 && (
243
- <Typography sx={{ py: 2, textAlign: "center", fontSize: 13, color: C.gray }}>Sin coincidencias</Typography>
244
- )}
245
- {visibleGroups.map(({ stage, services: inStage }) => {
246
- const isOpen = Boolean(text) || expanded.includes(stage.stage_name);
247
- return (
248
- <Box key={stage.stage_name}>
249
- <ButtonBase
250
- onClick={() => toggle(stage.stage_name)}
251
- aria-expanded={isOpen}
252
- sx={{
253
- width: "100%",
254
- display: "flex",
255
- alignItems: "center",
256
- gap: 1,
257
- px: 1,
258
- py: 0.75,
259
- borderRadius: "8px",
260
- textAlign: "left",
261
- "&:hover": { backgroundColor: C.purpleLight },
262
- }}
263
- >
264
- <StageDot color={stage.color} size={10} />
265
- <Typography sx={{ flex: 1, fontSize: 13, fontWeight: 700, color: C.textPurple }}>
266
- {stage.display_name}
267
- </Typography>
268
- <Typography sx={{ fontFamily: "Lato, sans-serif", fontSize: 12, fontWeight: 700, color: C.gray }}>
269
- {inStage.length}
270
- </Typography>
271
- <ExpandMoreIcon
272
- fontSize="small"
273
- sx={{ color: C.purple, transition: "transform 150ms", transform: isOpen ? "rotate(180deg)" : "none" }}
274
- />
275
- </ButtonBase>
276
- <Collapse in={isOpen} unmountOnExit>
277
- <Box sx={{ display: "flex", flexDirection: "column", py: 0.25 }}>
278
- {inStage.map((service) => {
279
- const active = service.service_name === selectedName;
280
- return (
281
- <ButtonBase
282
- key={service.service_name}
283
- onClick={() => setSelectedName(service.service_name)}
284
- aria-current={active ? "true" : undefined}
285
- sx={{
286
- display: "flex",
287
- alignItems: "center",
288
- gap: 1,
289
- pl: 3,
290
- pr: 1,
291
- py: 0.75,
292
- borderRadius: "8px",
293
- textAlign: "left",
294
- borderLeft: `3px solid ${active ? C.pink : "transparent"}`,
295
- backgroundColor: active ? C.purpleLight : "transparent",
296
- "&:hover": { backgroundColor: C.purpleLight },
297
- }}
298
- >
299
- <Typography
300
- sx={{
301
- flex: 1,
302
- minWidth: 0,
303
- fontSize: 14,
304
- fontWeight: active ? 700 : 400,
305
- color: active ? C.textPurple : C.black,
306
- overflow: "hidden",
307
- textOverflow: "ellipsis",
308
- whiteSpace: "nowrap",
309
- }}
310
- >
311
- {service.display_name}
312
- </Typography>
313
- {service.editor_tab && (
314
- <Tooltip {...whiteTooltipProps} title={`Se edita en la pestaña ${service.editor_tab}`} arrow>
315
- <EditOutlinedIcon sx={{ fontSize: 18, color: C.purple }} />
316
- </Tooltip>
317
- )}
318
- <StatusChip status={service.has_workflow ? service.status : "REG"} />
319
- </ButtonBase>
320
- );
321
- })}
322
- </Box>
323
- </Collapse>
324
- </Box>
325
- );
326
- })}
327
- </Box>
328
- </Box>
329
-
330
- {/* ---- Detalle ---- */}
331
- <Box component="section" aria-live="polite" sx={{ p: 3, minWidth: 0 }}>
332
- {selected ? (
333
- <ServiceDetail
334
- product={product}
335
- service={selected}
336
- retailers={retailers}
337
- users={users}
338
- canAssign={canAssign}
339
- onAssign={onAssign}
340
- onOpenEditor={onOpenEditor}
341
- />
342
- ) : (
343
- <Typography sx={{ color: C.gray }}>Elige un servicio de la lista.</Typography>
344
- )}
345
- </Box>
346
- </Box>
347
- );
348
- }
349
-
350
- const personName = (list = [], id) => {
351
- if (!id) return "Por asignar";
352
- const person = list.find((user) => user.id_user === id);
353
- return person ? [person.name, person.last_name].filter(Boolean).join(" ") : "Asignado";
354
- };
355
-
356
- function ServiceDetail({ product, service, retailers, users, canAssign, onAssign, onOpenEditor }) {
357
- const stage = getServiceStage(service);
358
- const retailerStatuses = getRetailerStatuses(product, service, retailers);
359
- const deliverableType =
360
- service.deliverable_type ??
361
- DEMO_CATALOG.find((item) => item.service_name === service.service_name)?.deliverable_type ??
362
- "Entregable";
363
-
364
- const lockReason = (role) => {
365
- // El auditor es del producto, no del servicio: no depende de este.
366
- const perService = role !== "auditor";
367
- if (perService && !service.has_workflow) return "Servicio de registro: no lleva equipo";
368
- if (perService && service.is_demo) return "Se podrá asignar cuando el servicio exista en el catálogo";
369
- if (!canAssign) return "Tu rol puede ver la asignación, pero no cambiarla";
370
- if (!onAssign) return "Asigna desde la tabla de productos";
371
- if (!(users[role] ?? []).length) return "No hay personas asignables para este rol";
372
- return null;
373
- };
374
-
375
- const team = [
376
- { role: "especialist", label: "Responsable", currentId: service.assignees?.especialist },
377
- { role: "facilitator", label: "Facilitador", currentId: service.assignees?.facilitator },
378
- { role: "auditor", label: "Auditor", currentId: product?.id_auditor || null, note: "del producto" },
379
- ];
380
-
381
- return (
382
- <Box sx={{ display: "flex", flexDirection: "column", gap: 3 }}>
383
- <Box>
384
- <Box sx={{ display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }}>
385
- <Typography
386
- component="h3"
387
- sx={{ m: 0, fontFamily: "Raleway, sans-serif", fontSize: 22, fontWeight: 600, color: C.textPurple }}
388
- >
389
- {service.display_name}
390
- </Typography>
391
- {service.is_demo && (
392
- <Typography component="span" sx={{ fontSize: 12, fontStyle: "italic", color: C.gray }}>
393
- ejemplo
394
- </Typography>
395
- )}
396
- </Box>
397
- <Box sx={{ mt: 1, display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }}>
398
- <StatusChip status={service.has_workflow ? service.status : "REG"} long />
399
- <Box
400
- component="span"
401
- sx={{ display: "inline-flex", alignItems: "center", gap: 0.75, fontSize: 13, color: C.gray }}
402
- >
403
- <StageDot color={stage.color} />
404
- {stage.display_name}
405
- </Box>
406
- </Box>
407
- </Box>
408
-
409
- {service.editor_tab && (
410
- <Box
411
- sx={{
412
- display: "flex",
413
- alignItems: "center",
414
- gap: 2,
415
- p: 1.5,
416
- borderRadius: "10px",
417
- background: C.purpleLight,
418
- }}
419
- >
420
- <EditOutlinedIcon sx={{ color: C.purple }} />
421
- <Typography sx={{ flex: 1, fontSize: 14, color: C.textPurple }}>
422
- Este servicio se trabaja en la pestaña <b>{service.editor_tab}</b>.
423
- </Typography>
424
- <Button
425
- onClick={() => onOpenEditor?.(service.editor_tab)}
426
- endIcon={<ArrowForwardIcon fontSize="small" />}
427
- sx={{ textTransform: "none", fontWeight: 700, color: C.pinkHard, borderRadius: "8px" }}
428
- >
429
- Ir a {service.editor_tab}
430
- </Button>
431
- </Box>
432
- )}
433
-
434
- {service.has_workflow && (
435
- <Box>
436
- <Typography sx={sectionTitleSx}>Acciones</Typography>
437
- {/* Propuesta: los botones salen de cat_status para el estatus actual
438
- (aprobar -> approved_status_id, rechazar -> rejected_status_id) y
439
- "Cambiar estatus" es la corrección manual con motivo. Deshabilitados
440
- hasta que exista status-engine (ver plan técnico, §8.1). */}
441
- <Tooltip {...whiteTooltipProps} title="Se habilita con el motor de estatus (status-engine)" arrow placement="top-start">
442
- <Box sx={{ display: "inline-flex", gap: 1, flexWrap: "wrap" }}>
443
- <Button disabled variant="contained" sx={{ textTransform: "none", borderRadius: "8px", boxShadow: "none" }}>
444
- Aprobar
445
- </Button>
446
- <Button disabled variant="outlined" sx={{ textTransform: "none", borderRadius: "8px" }}>
447
- Rechazar
448
- </Button>
449
- <Button disabled sx={{ textTransform: "none", borderRadius: "8px" }}>
450
- Cambiar estatus…
451
- </Button>
452
- </Box>
453
- </Tooltip>
454
- </Box>
455
- )}
456
-
457
- <Box>
458
- <Typography sx={sectionTitleSx}>Equipo</Typography>
459
- <Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
460
- {team.map(({ role, label, currentId, note }) => (
461
- <Box
462
- key={role}
463
- sx={{ display: "grid", gridTemplateColumns: "minmax(0, 1fr) auto", alignItems: "center", gap: 1 }}
464
- >
465
- <Typography sx={{ fontSize: 14, color: C.black }}>
466
- {label}
467
- {note && (
468
- <Typography component="span" sx={{ ml: 0.75, fontSize: 12, color: C.gray }}>
469
- {note}
470
- </Typography>
471
- )}
472
- </Typography>
473
- <AssigneePicker
474
- key={`${service.service_name}-${role}-${currentId}`}
475
- // Aquí el estatus ya está arriba: la pastilla dice quién es.
476
- statusLabel={(current) =>
477
- current ? personName([current], current.id_user) : personName(users[role], currentId)
478
- }
479
- statusTitle={getStatusName(service.status)}
480
- users={users[role] ?? []}
481
- currentId={currentId || null}
482
- roleLabel={label}
483
- lockedReason={lockReason(role)}
484
- onAssign={(user) => onAssign?.(service.service_name, role, user)}
485
- />
486
- </Box>
487
- ))}
488
- </Box>
489
- </Box>
490
-
491
- <Box>
492
- <Typography sx={sectionTitleSx}>Por cadena</Typography>
493
- {retailerStatuses.length === 0 ? (
494
- <Typography sx={{ fontSize: 13, color: C.gray }}>Sin cadenas para este servicio.</Typography>
495
- ) : (
496
- <Box sx={{ display: "flex", flexDirection: "column", gap: 0.5 }}>
497
- {retailerStatuses.map((item) => (
498
- <Box
499
- key={item.id_retailer}
500
- sx={{
501
- display: "flex",
502
- alignItems: "center",
503
- gap: 1,
504
- py: 0.75,
505
- px: 1.25,
506
- borderRadius: "8px",
507
- background: C.grayLight,
508
- }}
509
- >
510
- <Typography sx={{ flex: 1, fontSize: 14, color: C.black }}>{item.retailer}</Typography>
511
- <StatusChip status={item.status} />
512
- </Box>
513
- ))}
514
- </Box>
515
- )}
516
- </Box>
517
-
518
- <Box>
519
- <Typography sx={sectionTitleSx}>Entregable</Typography>
520
- {service.editor_tab ? (
521
- <Typography sx={{ fontSize: 13, color: C.gray }}>
522
- El entregable es el contenido de la pestaña {service.editor_tab}.
523
- </Typography>
524
- ) : (
525
- <Box
526
- sx={{
527
- display: "flex",
528
- alignItems: "center",
529
- gap: 2,
530
- p: 2,
531
- borderRadius: "10px",
532
- border: `1px dashed ${C.purple}`,
533
- }}
534
- >
535
- <CloudUploadOutlinedIcon sx={{ color: C.purple, fontSize: 32 }} />
536
- <Box sx={{ flex: 1 }}>
537
- <Typography sx={{ fontSize: 14, fontWeight: 700, color: C.textPurple }}>
538
- Aún no hay entregables
539
- </Typography>
540
- <Typography sx={{ fontSize: 13, color: C.gray }}>Tipo: {deliverableType}</Typography>
541
- </Box>
542
- <Tooltip {...whiteTooltipProps} title="Se habilita con el gestor de servicios" arrow>
543
- <span>
544
- <Button disabled variant="outlined" sx={{ textTransform: "none", borderRadius: "8px" }}>
545
- Subir entregable
546
- </Button>
547
- </span>
548
- </Tooltip>
549
- </Box>
550
- )}
551
- </Box>
552
- </Box>
553
- );
554
- }
555
-
556
- export { ServicesWorkspace };