contentoh-components-library 21.6.61 → 21.6.62

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 (88) 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/pages/CreatorsProductEdition/CreatorsProductEditionView.js +11 -5
  7. package/dist/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +58 -31
  8. package/dist/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +5 -1
  9. package/dist/components/pages/CreatorsProductEdition/mocks/mockApi.js +3 -40
  10. package/dist/components/pages/CreatorsProductEdition/sections/ImagesSection.js +18 -3
  11. package/dist/components/pages/CreatorsProductEdition/sections/StatusChip.js +4 -13
  12. package/dist/components/pages/CreatorsProductEdition/sections/TabsBar.js +10 -0
  13. package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +10 -2
  14. package/dist/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +2 -15
  15. package/dist/components/pages/RetailerProductEdition/index.js +40 -57
  16. package/dist/components/pages/RetailerProductEdition/utils.js +2 -6
  17. package/dist/index.js +0 -13
  18. package/package.json +1 -1
  19. package/src/components/pages/CreatorsProductEdition/CreatorsProductEditionView.jsx +22 -3
  20. package/src/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +36 -1
  21. package/src/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +15 -3
  22. package/src/components/pages/CreatorsProductEdition/mocks/mockApi.js +8 -18
  23. package/src/components/pages/CreatorsProductEdition/sections/ImagesSection.jsx +24 -2
  24. package/src/components/pages/CreatorsProductEdition/sections/StatusChip.jsx +4 -8
  25. package/src/components/pages/CreatorsProductEdition/sections/TabsBar.jsx +9 -0
  26. package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +6 -0
  27. package/src/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +0 -12
  28. package/src/components/pages/RetailerProductEdition/index.js +3 -28
  29. package/src/components/pages/RetailerProductEdition/utils.js +0 -5
  30. package/src/index.js +0 -1
  31. package/dist/components/organisms/ServicesNavigation/ServiceToolbar.js +0 -210
  32. package/dist/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -138
  33. package/dist/components/organisms/ServicesNavigation/ServicesRail.js +0 -705
  34. package/dist/components/organisms/ServicesNavigation/catalog.js +0 -505
  35. package/dist/components/organisms/ServicesNavigation/index.js +0 -337
  36. package/dist/components/organisms/ServicesNavigation/model.js +0 -167
  37. package/dist/components/organisms/ServicesNavigation/ui.js +0 -371
  38. package/dist/components/organisms/ServicesNavigation/views.js +0 -1237
  39. package/dist/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -459
  40. package/dist/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -119
  41. package/dist/components/organisms/ServicesWorkspace/demo.js +0 -137
  42. package/dist/components/organisms/ServicesWorkspace/index.js +0 -813
  43. package/dist/components/organisms/ServicesWorkspace/servicesModel.js +0 -351
  44. package/dist/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -37
  45. package/dist/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -387
  46. package/dist/components/pages/ProductEdition/ProductEdition.stories.js +0 -148
  47. package/dist/components/pages/ProductEdition/ProductEditionView.js +0 -1355
  48. package/dist/components/pages/ProductEdition/ServiceHeader.js +0 -142
  49. package/dist/components/pages/ProductEdition/api.js +0 -256
  50. package/dist/components/pages/ProductEdition/index.js +0 -37
  51. package/dist/components/pages/ProductEdition/permissions.js +0 -200
  52. package/dist/components/pages/ProductEdition/permissions.test.js +0 -320
  53. package/dist/components/pages/ProductEdition/sections/CatalogServicePanel.js +0 -237
  54. package/dist/components/pages/ProductEdition/sections/EditionTopBar.js +0 -174
  55. package/dist/components/pages/ProductEdition/sections/ImagePanel.js +0 -440
  56. package/dist/components/pages/ProductEdition/sections/ServiceTeam.js +0 -275
  57. package/dist/components/pages/ProductEdition/sections/ServiceTools.js +0 -186
  58. package/dist/components/pages/ProductEdition/services.js +0 -233
  59. package/dist/components/pages/ProductEdition/services.test.js +0 -195
  60. package/src/components/organisms/ServicesNavigation/ServiceToolbar.jsx +0 -143
  61. package/src/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -101
  62. package/src/components/organisms/ServicesNavigation/ServicesRail.jsx +0 -591
  63. package/src/components/organisms/ServicesNavigation/catalog.js +0 -180
  64. package/src/components/organisms/ServicesNavigation/index.jsx +0 -232
  65. package/src/components/organisms/ServicesNavigation/model.js +0 -85
  66. package/src/components/organisms/ServicesNavigation/ui.jsx +0 -229
  67. package/src/components/organisms/ServicesNavigation/views.jsx +0 -705
  68. package/src/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -348
  69. package/src/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -61
  70. package/src/components/organisms/ServicesWorkspace/demo.js +0 -58
  71. package/src/components/organisms/ServicesWorkspace/index.js +0 -556
  72. package/src/components/organisms/ServicesWorkspace/servicesModel.js +0 -203
  73. package/src/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -27
  74. package/src/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -175
  75. package/src/components/pages/ProductEdition/ProductEdition.stories.js +0 -106
  76. package/src/components/pages/ProductEdition/ProductEditionView.jsx +0 -1142
  77. package/src/components/pages/ProductEdition/ServiceHeader.jsx +0 -104
  78. package/src/components/pages/ProductEdition/api.js +0 -84
  79. package/src/components/pages/ProductEdition/index.js +0 -21
  80. package/src/components/pages/ProductEdition/permissions.js +0 -128
  81. package/src/components/pages/ProductEdition/permissions.test.js +0 -249
  82. package/src/components/pages/ProductEdition/sections/CatalogServicePanel.jsx +0 -123
  83. package/src/components/pages/ProductEdition/sections/EditionTopBar.jsx +0 -134
  84. package/src/components/pages/ProductEdition/sections/ImagePanel.jsx +0 -342
  85. package/src/components/pages/ProductEdition/sections/ServiceTeam.jsx +0 -184
  86. package/src/components/pages/ProductEdition/sections/ServiceTools.jsx +0 -134
  87. package/src/components/pages/ProductEdition/services.js +0 -187
  88. package/src/components/pages/ProductEdition/services.test.js +0 -88
@@ -1,180 +0,0 @@
1
- /*
2
- Datos de ejemplo del prototipo de navegación por servicios.
3
-
4
- Nada de esto sale de la base todavía. Es el catálogo de `cat_service`
5
- (docs/sql/catalog/002) con dos datos que la edición necesita para pintar cada
6
- servicio:
7
-
8
- - group: en qué bloque de la lista lateral va. Los bloques son fijos y no
9
- dependen del estatus, para que un servicio siempre esté en el mismo lugar
10
- y el usuario lo encuentre de memoria.
11
- - view: qué vista de trabajo abre. La vista depende del tipo de entregable,
12
- no del servicio: Foto, Edición y Resize abren la misma vista de imágenes.
13
- */
14
-
15
- export const SERVICE_GROUPS = [
16
- { key: "texts", label: "Textos" },
17
- { key: "images", label: "Imágenes" },
18
- { key: "media", label: "Multimedia y diseño" },
19
- { key: "review", label: "Revisión y entrega" },
20
- { key: "maintenance", label: "Mantenimiento" },
21
- { key: "logistics", label: "Logística" },
22
- ];
23
-
24
- /*
25
- Los servicios activos de `cat_service` que se trabajan en la edición
26
- (docs/sql/catalog/002 y 006 del backend), con los mismos `service_name`,
27
- `display_name` y bloque (`category_name`). Dentro de cada bloque van en el
28
- orden del catálogo (`sort_order`). Los de logística (Levantamiento, Compra,
29
- Armado) usan un flujo simple (casilla o avance) y abren la vista «simple»:
30
- su estatus por cadena y quién lo tiene, sin editor.
31
-
32
- `view` sale del tipo de entregable: attributes → ficha, text → textos
33
- (Traducción tiene la suya), images → la vista de imágenes compartida,
34
- video → videos, file → archivos. A+, Golden, Banner y Render entregan
35
- imágenes, pero son diseños: van como archivos.
36
- */
37
- export const SERVICE_CATALOG = [
38
- // Textos
39
- { service_name: "datasheet", display_name: "Ficha técnica", group: "texts", view: "attributes", icon: "datasheet" },
40
- { service_name: "description", display_name: "Descripción", group: "texts", view: "text", icon: "description" },
41
- { service_name: "datasheetHeader", display_name: "Header Ficha", group: "texts", view: "text", icon: "header" },
42
- { service_name: "translation", display_name: "Traducción", group: "texts", view: "translation", icon: "translation" },
43
- { service_name: "seo", display_name: "SEO", group: "texts", view: "text", icon: "seo" },
44
- { service_name: "benefitBullets", display_name: "Legales + beneficios (bullets)", group: "texts", view: "text", icon: "bullets" },
45
- // Imágenes (fotografía y edición sobre las imágenes del producto)
46
- { service_name: "images", display_name: "Imágenes", group: "images", view: "images", icon: "images" },
47
- { service_name: "photo", display_name: "Foto", group: "images", view: "images", icon: "photo" },
48
- { service_name: "edition", display_name: "Edición", group: "images", view: "images", icon: "edition" },
49
- { service_name: "premiumImage", display_name: "Premium", group: "images", view: "images", icon: "premium" },
50
- { service_name: "resize", display_name: "Resize", group: "images", view: "images", icon: "resize" },
51
- { service_name: "adaptations", display_name: "Adaptaciones", group: "images", view: "images", icon: "adaptations" },
52
- { service_name: "graphicTranslation", display_name: "Traducciones gráficas", group: "images", view: "images", icon: "graphicTranslation" },
53
- { service_name: "visualUpdate", display_name: "Actualización visual", group: "images", view: "images", icon: "visualUpdate" },
54
- { service_name: "ambientImage", display_name: "Ambientada", group: "images", view: "images", icon: "ambient" },
55
- { service_name: "specialPhoto", display_name: "Foto especial", group: "images", view: "images", icon: "specialPhoto" },
56
- { service_name: "modelPhoto", display_name: "Foto con modelo", group: "images", view: "images", icon: "modelPhoto" },
57
- { service_name: "premiumAmbientImage", display_name: "Foto ambientada premium", group: "images", view: "images", icon: "premiumAmbient" },
58
- // Multimedia y diseño
59
- { service_name: "aplusPlatinum", display_name: "A+ / Platinum SKU", group: "media", view: "files", icon: "aplus", deliverable: "Diseño" },
60
- { service_name: "brandstore", display_name: "Brandstore", group: "media", view: "files", icon: "brandstore", deliverable: "Diseño" },
61
- { service_name: "image360", display_name: "360°", group: "media", view: "files", icon: "image360", deliverable: "Archivos 360" },
62
- { service_name: "videos", display_name: "Videos", group: "media", view: "video", icon: "video" },
63
- { service_name: "golden", display_name: "Golden", group: "media", view: "files", icon: "golden", deliverable: "Diseño" },
64
- { service_name: "banner", display_name: "Banner", group: "media", view: "files", icon: "banner", deliverable: "Diseño" },
65
- { service_name: "render", display_name: "Render", group: "media", view: "files", icon: "render", deliverable: "Render" },
66
- { service_name: "arModel", display_name: "Modelado 3D (realidad aumentada)", group: "media", view: "files", icon: "arModel", deliverable: "Modelo 3D" },
67
- { service_name: "manualDigitization", display_name: "Digitalización de manuales", group: "media", view: "files", icon: "manual", deliverable: "Manual digital" },
68
- // Revisión y entrega
69
- { service_name: "quality", display_name: "Calidad de textos", group: "review", view: "files", icon: "quality", deliverable: "Reporte" },
70
- { service_name: "secodatReview", display_name: "Revisión secodat", group: "review", view: "files", icon: "secodat", deliverable: "Reporte" },
71
- { service_name: "followUp", display_name: "Seguimiento", group: "review", view: "files", icon: "followUp", deliverable: "Reporte" },
72
- { service_name: "uploadOnly", display_name: "Solo Carga", group: "review", view: "files", icon: "upload", deliverable: "Carga en cadena" },
73
- { service_name: "audit", display_name: "Auditoría de textos", group: "review", view: "files", icon: "audit", deliverable: "Reporte" },
74
- { service_name: "inputReview", display_name: "Revisión de Input", group: "review", view: "files", icon: "inputReview", deliverable: "Reporte" },
75
- { service_name: "clientReview", display_name: "Revisión Cliente", group: "review", view: "files", icon: "clientReview", deliverable: "Reporte" },
76
- { service_name: "visualQuality", display_name: "Calidad visual", group: "review", view: "files", icon: "visualQuality", deliverable: "Reporte" },
77
- { service_name: "visualAudit", display_name: "Auditoría visual", group: "review", view: "files", icon: "visualAudit", deliverable: "Reporte" },
78
- { service_name: "gdsnUpload", display_name: "Carga GDSN (Syncway)", group: "review", view: "files", icon: "gdsn", deliverable: "Carga en GDSN" },
79
- // Mantenimiento
80
- { service_name: "maintenance", display_name: "Mantenimiento de textos", group: "maintenance", view: "files", icon: "maintenance", deliverable: "Archivo" },
81
- { service_name: "visualMaintenance", display_name: "Mantenimiento de imágenes", group: "maintenance", view: "images", icon: "visualMaintenance" },
82
- // Logística: flujo simple (casilla o avance), sin editor. La edición muestra
83
- // su estatus por cadena y quién lo tiene (ProductEdition/sections/CatalogServicePanel).
84
- { service_name: "productPickup", display_name: "Levantamiento y recolección", group: "logistics", view: "simple", icon: "pickup" },
85
- { service_name: "productPurchase", display_name: "Compra de producto en tienda", group: "logistics", view: "simple", icon: "purchase" },
86
- { service_name: "productAssembly", display_name: "Armado de producto", group: "logistics", view: "simple", icon: "assembly" },
87
- ];
88
-
89
- export const DEMO_USERS = {
90
- ana: { id_user: 9100, name: "Ana", last_name: "López" },
91
- carlos: { id_user: 9101, name: "Carlos", last_name: "Méndez" },
92
- diana: { id_user: 9102, name: "Diana", last_name: "Ruiz" },
93
- eduardo: { id_user: 9103, name: "Eduardo", last_name: "Pérez" },
94
- fernanda: { id_user: 9104, name: "Fernanda", last_name: "Soto" },
95
- gabriel: { id_user: 9105, name: "Gabriel", last_name: "Torres" },
96
- helena: { id_user: 9106, name: "Helena", last_name: "Vargas" },
97
- };
98
-
99
- /** Producto con la forma que espera la cabecera de la edición (ProductHeader). */
100
- export const DEMO_PRODUCT = {
101
- id_article: 11096001,
102
- id_order: 11096,
103
- name: "Taladro inalámbrico 20 V con 2 baterías",
104
- upc: "7501234567890",
105
- sku: "TAL-20V-2B",
106
- categoryName: "Herramientas|Eléctricas|Taladros",
107
- status: "CA",
108
- timestamp: "2026-10-08T15:00:00Z",
109
- article: { company_name: "Herramientas del Norte" },
110
- categoryRetailer: [
111
- {
112
- id_retailer: 58,
113
- name: "The Home Depot",
114
- retailer: "The Home Depot",
115
- category: "Herramientas|Eléctricas|Taladros",
116
- },
117
- ],
118
- };
119
-
120
- const DATES = ["13/10/2026", "12/10/2026", "10/10/2026", "09/10/2026", "07/10/2026"];
121
-
122
- /**
123
- * Servicios del producto para las historias. Cada fila es
124
- * [service_name, estatus, { especialist, facilitator, auditor }].
125
- * El coordinador (facilitator) y el auditor tienen valores por omisión.
126
- */
127
- export const buildServices = (rows) =>
128
- rows.map(([serviceName, status, team = {}], index) => {
129
- const definition = SERVICE_CATALOG.find((item) => item.service_name === serviceName);
130
- return {
131
- ...definition,
132
- status,
133
- updated_at: DATES[index % DATES.length],
134
- assignees: {
135
- especialist: team.especialist ?? null,
136
- facilitator: team.facilitator === undefined ? DEMO_USERS.helena : team.facilitator,
137
- auditor: team.auditor === undefined ? DEMO_USERS.fernanda : team.auditor,
138
- },
139
- };
140
- });
141
-
142
- const { ana, carlos, diana, eduardo, fernanda, gabriel } = DEMO_USERS;
143
-
144
- /** Veinte servicios del catálogo con estatus y equipos variados. */
145
- export const TWENTY_SERVICES = buildServices([
146
- ["datasheet", "AA", { especialist: ana }],
147
- ["description", "RA/AS", { especialist: ana }],
148
- ["datasheetHeader", "AC", { especialist: ana }],
149
- ["seo", "CA", { especialist: eduardo }],
150
- ["translation", "CA", { especialist: diana }],
151
- ["images", "AS", { especialist: carlos }],
152
- ["photo", "Ex", { especialist: gabriel }],
153
- ["edition", "RA/AS", { especialist: carlos }],
154
- ["resize", "CA", { especialist: carlos }],
155
- ["adaptations", "PA"],
156
- ["premiumImage", "AP", { especialist: gabriel }],
157
- ["videos", "IE", { especialist: gabriel }],
158
- ["image360", "PA"],
159
- ["brandstore", "AC", { especialist: eduardo }],
160
- ["aplusPlatinum", "RC", { especialist: eduardo }],
161
- ["golden", "AS", { especialist: eduardo }],
162
- ["quality", "AA", { especialist: fernanda, auditor: diana }],
163
- ["audit", "R", { especialist: fernanda, auditor: diana }],
164
- ["secodatReview", "Ex", { especialist: fernanda, auditor: diana }],
165
- ["uploadOnly", "PA"],
166
- ]);
167
-
168
- /** Estatus variados para mostrar todos los servicios del catálogo a la vez. */
169
- const SAMPLE_STATUSES = ["PA", "AS", "CA", "IE", "AC", "AA", "AP", "Ex", "RA/AS", "RC"];
170
-
171
- /**
172
- * Todos los servicios activos del catálogo con un estatus de ejemplo cada uno
173
- * (los tres de siempre se pueden excluir: en la edición real salen del
174
- * producto).
175
- */
176
- export const allCatalogServices = ({ exclude = [] } = {}) =>
177
- SERVICE_CATALOG.filter((item) => !exclude.includes(item.service_name)).map((item, index) => ({
178
- service_name: item.service_name,
179
- status: SAMPLE_STATUSES[(index * 3) % SAMPLE_STATUSES.length],
180
- }));
@@ -1,232 +0,0 @@
1
- import { useState } from "react";
2
- import { Box, Chip, Drawer, Stack, Typography, useMediaQuery } from "@mui/material";
3
- import ImageOutlinedIcon from "@mui/icons-material/ImageOutlined";
4
-
5
- import { ProductHeader } from "../../pages/CreatorsProductEdition/sections/ProductHeader";
6
- import { COLORS, FONT_STACK, TITLE_STACK, captionSx } from "./ui";
7
- import { ServicesRail } from "./ServicesRail";
8
- import { ServiceToolbar } from "./ServiceToolbar";
9
- import { ServiceView } from "./views";
10
- import {
11
- SUMMARY,
12
- defaultFilter,
13
- fullName,
14
- hasUsefulFilter,
15
- initialSelection,
16
- isMine,
17
- isPendingFor,
18
- } from "./model";
19
-
20
- /*
21
- PROTOTIPO: navegación por servicios en la edición de producto.
22
-
23
- Responde a dos preguntas:
24
- 1. Si el producto solo lleva Traducción, ¿por qué ver Ficha técnica,
25
- Descripción e Imágenes? → Solo aparecen los servicios contratados. Con
26
- uno solo no hay lista: la edición abre directo en él.
27
- 2. Con 20 servicios, ¿cómo se navega sin perderse? → Lista lateral en lugar
28
- de pestañas, filtrada por omisión a lo que la persona trabaja («Míos»),
29
- con un Resumen y un punto rosa en lo que le toca a cada quien.
30
-
31
- Usa la cabecera real de la edición de Creators (ProductHeader); la columna de
32
- imagen es un marcador de lugar y las vistas de trabajo son de ejemplo. No está
33
- conectado a la base: los servicios y sus estatus llegan por props.
34
- */
35
-
36
- const ImagePanelPlaceholder = () => (
37
- <Box sx={{ display: { xs: "none", lg: "block" }, flex: "0 0 clamp(240px, 22%, 320px)", minWidth: 0 }}>
38
- <Box
39
- sx={{
40
- aspectRatio: "1 / 1",
41
- borderRadius: "8px",
42
- border: `1px solid ${COLORS.s2}`,
43
- background: `linear-gradient(135deg, ${COLORS.blue_light}, ${COLORS.s2})`,
44
- display: "flex",
45
- flexDirection: "column",
46
- alignItems: "center",
47
- justifyContent: "center",
48
- gap: 1,
49
- }}
50
- >
51
- <ImageOutlinedIcon sx={{ fontSize: 56, color: COLORS.s3 }} />
52
- <Typography sx={captionSx}>Imagen principal</Typography>
53
- </Box>
54
- <Stack direction="row" spacing={1} sx={{ mt: 1 }}>
55
- {[0, 1, 2, 3].map((index) => (
56
- <Box
57
- key={index}
58
- sx={{
59
- flex: 1,
60
- aspectRatio: "1 / 1",
61
- borderRadius: "6px",
62
- border: `1px solid ${index === 0 ? COLORS.magenta_s2 : COLORS.s2}`,
63
- bgcolor: COLORS.s1,
64
- }}
65
- />
66
- ))}
67
- </Stack>
68
- <Typography sx={{ ...captionSx, mt: 1.5 }}>La columna de imagen de la edición se queda como está.</Typography>
69
- </Box>
70
- );
71
-
72
- const TopBar = ({ currentUser, currentUserRole }) => (
73
- <Stack
74
- direction="row"
75
- alignItems="center"
76
- spacing={1.5}
77
- sx={{ px: 2, py: 1.25, borderBottom: `1px solid ${COLORS.s2}`, flexWrap: "wrap", rowGap: 0.5 }}
78
- >
79
- <Typography sx={{ fontFamily: TITLE_STACK, fontWeight: 600, fontSize: 18, color: COLORS.s5 }}>
80
- Edición de producto
81
- </Typography>
82
- <Chip
83
- label="Prototipo · datos de ejemplo"
84
- size="small"
85
- sx={{ height: 22, bgcolor: COLORS.s2, color: COLORS.s4, fontFamily: FONT_STACK, fontWeight: 700, fontSize: 11 }}
86
- />
87
- <Box sx={{ flex: 1 }} />
88
- {currentUser && (
89
- <Typography sx={captionSx}>
90
- Viendo como <b>{fullName(currentUser)}</b>
91
- {currentUserRole ? ` · ${currentUserRole}` : ""}
92
- </Typography>
93
- )}
94
- </Stack>
95
- );
96
-
97
- export const ServicesNavigation = ({
98
- product,
99
- services = [],
100
- currentUser,
101
- currentUserRole,
102
- initialService,
103
- initialCollapsed = false,
104
- }) => {
105
- const userId = currentUser?.id_user;
106
- const isDesktop = useMediaQuery("(min-width:900px)");
107
- const single = services.length === 1;
108
- const showFilter = hasUsefulFilter(services, userId);
109
-
110
- const [selected, setSelected] = useState(() => initialService ?? initialSelection(services, userId));
111
- const [filter, setFilter] = useState(() => defaultFilter(services, userId));
112
- const [collapsed, setCollapsed] = useState(initialCollapsed);
113
- const [drawerOpen, setDrawerOpen] = useState(false);
114
-
115
- const retailers = product?.categoryRetailer ?? [];
116
- const [activeRetailer, setActiveRetailer] = useState(() => ({
117
- id: retailers[0]?.id_retailer,
118
- name: retailers[0]?.name,
119
- }));
120
-
121
- const service = services.find((item) => item.service_name === selected) ?? null;
122
- const isSummary = !single && (selected === SUMMARY || !service);
123
-
124
- const select = (name) => {
125
- const target = services.find((item) => item.service_name === name);
126
- // Si se abre algo que no es suyo (desde el Resumen), se pasa a «Todos»
127
- // para que el servicio abierto se vea marcado en la lista.
128
- if (target && filter === "mine" && !isMine(target, userId)) setFilter("all");
129
- setSelected(name);
130
- setDrawerOpen(false);
131
- };
132
-
133
- const renderRail = (variant) => (
134
- <ServicesRail
135
- variant={variant}
136
- services={services}
137
- selected={isSummary ? SUMMARY : selected}
138
- onSelect={select}
139
- userId={userId}
140
- filter={filter}
141
- onFilterChange={setFilter}
142
- showFilter={showFilter}
143
- collapsed={collapsed}
144
- onToggleCollapsed={() => setCollapsed((value) => !value)}
145
- onClose={variant === "drawer" ? () => setDrawerOpen(false) : undefined}
146
- />
147
- );
148
-
149
- return (
150
- <Box
151
- sx={{
152
- flex: 1,
153
- minWidth: 0,
154
- height: "100%",
155
- display: "flex",
156
- flexDirection: "column",
157
- bgcolor: COLORS.white,
158
- fontFamily: FONT_STACK,
159
- textAlign: "left",
160
- }}
161
- >
162
- <TopBar currentUser={currentUser} currentUserRole={currentUserRole} />
163
-
164
- <Box sx={{ flex: 1, minHeight: 0, display: "flex" }}>
165
- {!single && isDesktop && renderRail("rail")}
166
-
167
- <Box
168
- sx={{
169
- flex: 1,
170
- minWidth: 0,
171
- overflowY: "auto",
172
- p: { xs: 1.5, md: 2.5 },
173
- display: "flex",
174
- gap: 2.5,
175
- alignItems: "flex-start",
176
- }}
177
- >
178
- <ImagePanelPlaceholder />
179
-
180
- <Box sx={{ flex: 1, minWidth: 0 }}>
181
- <ProductHeader
182
- headerData={product}
183
- percent={72}
184
- activeRetailer={activeRetailer}
185
- setActiveRetailer={(retailer) =>
186
- setActiveRetailer({
187
- id: retailer?.id_retailer ?? retailer?.id,
188
- name: retailer?.name ?? retailer?.retailer,
189
- })
190
- }
191
- servicesData={null}
192
- showValidationButtons={false}
193
- />
194
-
195
- <ServiceToolbar
196
- service={service}
197
- isSummary={isSummary}
198
- summaryCounts={{
199
- total: services.length,
200
- pending: services.filter((item) => isPendingFor(item, userId)).length,
201
- }}
202
- onOpenRail={!single && !isDesktop ? () => setDrawerOpen(true) : undefined}
203
- userId={userId}
204
- />
205
-
206
- <ServiceView
207
- selected={isSummary ? SUMMARY : selected}
208
- service={service}
209
- services={services}
210
- userId={userId}
211
- onSelect={select}
212
- retailerName={activeRetailer.name}
213
- />
214
- </Box>
215
- </Box>
216
- </Box>
217
-
218
- {!single && !isDesktop && (
219
- <Drawer
220
- anchor="left"
221
- open={drawerOpen}
222
- onClose={() => setDrawerOpen(false)}
223
- PaperProps={{ sx: { width: "min(320px, 88vw)" } }}
224
- >
225
- {renderRail("drawer")}
226
- </Drawer>
227
- )}
228
- </Box>
229
- );
230
- };
231
-
232
- export default ServicesNavigation;
@@ -1,85 +0,0 @@
1
- import { SERVICE_GROUPS } from "./catalog";
2
-
3
- /** Identificador del elemento «Resumen» de la lista (no es un servicio). */
4
- export const SUMMARY = "__summary";
5
-
6
- export const ROLE_LABELS = {
7
- especialist: "Especialista",
8
- facilitator: "Coordinador",
9
- auditor: "Auditor",
10
- };
11
-
12
- /*
13
- Estatus en los que le toca actuar a cada rol. Un estatus compuesto como
14
- «RA/AS» (rechazado por el auditor y de vuelta con el especialista) cuenta si
15
- cualquiera de sus partes le toca al rol.
16
- */
17
- const ACTIONABLE = {
18
- especialist: ["AS", "CA", "RC", "RA"],
19
- facilitator: ["PA", "IE"],
20
- auditor: ["AC", "RP", "RCA", "SAC"],
21
- };
22
-
23
- const statusParts = (status) => String(status ?? "").split("/");
24
-
25
- export const fullName = (user) =>
26
- user ? [user.name, user.last_name].filter(Boolean).join(" ") : "";
27
-
28
- export const initials = (user) =>
29
- user ? `${user.name?.[0] ?? ""}${user.last_name?.[0] ?? ""}`.toUpperCase() : "";
30
-
31
- export const rolesOf = (service, userId) =>
32
- Object.entries(service.assignees ?? {})
33
- .filter(([, user]) => user && user.id_user === userId)
34
- .map(([role]) => role);
35
-
36
- /*
37
- `mine` y `pending` pueden venir ya calculados en el servicio (la edición real
38
- los saca de ProductEdition/permissions.js con las asignaciones del
39
- producto). Si no vienen, se calculan aquí con `assignees`, como en el
40
- prototipo.
41
- */
42
- export const isMine = (service, userId) =>
43
- typeof service.mine === "boolean" ? service.mine : rolesOf(service, userId).length > 0;
44
-
45
- export const isPendingFor = (service, userId) =>
46
- typeof service.pending === "boolean"
47
- ? service.pending
48
- : rolesOf(service, userId).some((role) =>
49
- statusParts(service.status).some((part) => ACTIONABLE[role]?.includes(part))
50
- );
51
-
52
- export const groupLabel = (key) =>
53
- SERVICE_GROUPS.find((group) => group.key === key)?.label ?? "";
54
-
55
- /** Servicios en bloques fijos, en el orden del catálogo. */
56
- export const groupServices = (services) =>
57
- SERVICE_GROUPS.map((group) => ({
58
- ...group,
59
- services: services.filter((service) => service.group === group.key),
60
- })).filter((group) => group.services.length > 0);
61
-
62
- /** El orden en que aparecen en la lista lateral. */
63
- export const railOrder = (services) => groupServices(services).flatMap((group) => group.services);
64
-
65
- /**
66
- * Qué se abre al entrar: con un solo servicio, ese; si a la persona le toca
67
- * algo, lo primero que le toca; si no, el Resumen.
68
- */
69
- export const initialSelection = (services, userId) => {
70
- if (services.length === 1) return services[0].service_name;
71
- const pending = railOrder(services).find((service) => isPendingFor(service, userId));
72
- return pending ? pending.service_name : SUMMARY;
73
- };
74
-
75
- /**
76
- * «Míos» solo tiene sentido si la persona trabaja algunos servicios del
77
- * producto y no todos: con todos o con ninguno, el filtro no cambia nada.
78
- */
79
- export const hasUsefulFilter = (services, userId) => {
80
- const mine = services.filter((service) => isMine(service, userId)).length;
81
- return mine > 0 && mine < services.length;
82
- };
83
-
84
- export const defaultFilter = (services, userId) =>
85
- hasUsefulFilter(services, userId) ? "mine" : "all";