Skip to content

Commit 885e4c9

Browse files
authored
feat(auth): claves de permiso granulares para Supply/Almacén (F0 Supply) (#75)
RECON (14/08): show_inventory no gateaba nada en el código real — Supply (Pedidos/Recepciones/Facturas/Almacén/CTB) solo tenía requiredRole:'manager' a nivel de módulo, ningún item con requiredPermission. Proveedores, Platos y Rentabilidad/Ingeniería de menús (Kitchen) igual. Los márgenes reales de Ventas (Economía/Márgenes, Margen final, Food cost/Plato) sí tenían permiso — show_ventas_analisis — pero nada que ver con show_inventory. Alcance corregido con Julio antes de construir. 6 columnas nuevas en manager_permissions (2 migraciones — la segunda corrige a la primera, que había metido show_costes en el backfill por error de alcance; no se edita la migración ya aplicada): show_recepcion, show_pedidos, show_proveedores, show_inventarios, show_facturas — backward-compat: quien tenga show_inventory=true las recibe en true. show_costes — la sensible (costes/escandallos/márgenes). Nace en false para todos, NO entra en el backfill, mismo criterio que show_salaries. Cableado real (antes decorativo, ahora gatea de verdad): - supply/module.tsx: Pedidos→show_pedidos, Recepciones→show_recepcion, Facturas→show_facturas, Almacén→show_inventory ("ver el almacén y el stock", su alcance reducido). Comunicar a CTB→requiredRole:'admin' (decisión de Julio: no es operativa de local). - kitchen/module.tsx: Proveedores→show_proveedores. Platos→show_costes (decisión explícita: el escandallo se ve dentro de la pantalla normal de editar el plato, sin separar datos — "el rol no entra ahí", no se rediseña la pantalla). Rentabilidad e Ingeniería de menús→show_costes. - ventas/module.tsx: Economía/Márgenes, Margen final y Food cost/Plato exigen AHORA show_ventas_analisis Y show_costes (antes bastaba con show_ventas_analisis, que las 2 plantillas ya tenían en true — dejaba el food cost visible por esa vía aunque show_costes fuera false). shell/types.ts: requiredPermission pasa de string a string | string[] (array = AND, todas las claves exigidas) — necesario para el caso de Ventas. Los 4 sitios que lo evalúan (ModuleSidebar, ShellTopBar, ShellBottomNav, MobileModuleTabs) actualizados con el mismo check. managerPermissionsService.ts: las 6 claves nuevas en DEFAULT_PERMISSIONS y en los 4 mappers (rowToManagerPermissions, managerPermissionsToInsertRow, patchToUpdateRow, permissionTemplateValuesToRow). Plantillas "Responsable de local"/"Oficina": las 5 no-sensibles en true, show_costes en false en las dos. Corrección adicional aprobada por Julio sobre lo ya mergeado en #74: show_informes_personal (gatea Nóminas) pasa a true en "Responsable de local" — edita el cuadrante y aprueba vacaciones; no ver las horas de su equipo sería incoherente. Se queda en false en "Oficina". ManagerPermissionsModal.tsx: 6 toggles nuevos, sección "Compras y Almacén" — no quedan decorativos, tienen editor individual desde el primer commit (a diferencia de 6 columnas previas al 14/08 que siguen sin toggle: showTasks/showScheduled/showTemplates/showIncidents/showAudits/ showHistory, deuda previa no tocada aquí). Verificado: has_permission()/get_effective_permissions() reconocen las 6 columnas nuevas sin cambio de función (impersonación real vía MCP contra la única fila existente en manager_permissions). tsc -b limpio (único error restante: DailyCountWizard.tsx, huérfano, ajeno a esta rama).
1 parent 9148f08 commit 885e4c9

14 files changed

Lines changed: 207 additions & 35 deletions

src/components/ManagerPermissionsModal.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
RefreshCw, Calendar, ClipboardList, FileText, Wallet, DollarSign, Settings2,
1616
Leaf, AlertTriangle,
1717
FlaskConical, BarChart3, Brain, Bike, Package, MapPin, Bell,
18+
PackageCheck, Truck, Store, Boxes, Coins,
1819
type LucideIcon,
1920
} from 'lucide-react'
2021
import { Modal, Button, Card, Alert } from './ui'
@@ -76,14 +77,25 @@ const PERMISSION_ITEMS: PermissionItem[] = [
7677
{ key: 'showVentasAnalisis', label: 'Análisis de Ventas', Icon: BarChart3, section: 'Inventario' },
7778
{ key: 'showPrediccionPersonal', label: 'Predicción Personal', Icon: Brain, section: 'Inventario' },
7879
{ key: 'showZonasPedido', label: 'Zonas de Pedido', Icon: Bike, section: 'Inventario', sensitive: true },
79-
{ key: 'showInventory', label: 'Inventario', Icon: Package, section: 'Inventario' },
80+
{ key: 'showInventory', label: 'Almacén (ver stock)', Icon: Package, section: 'Inventario' },
81+
82+
// Compras y Almacén (ENCARGO CODE 14/08 — separación de show_inventory,
83+
// que antes no gateaba nada de esto: Supply/Kitchen se veían por rol,
84+
// no por permiso). show_costes es la sensible — mismo criterio que
85+
// "Ver salarios": false por defecto en toda plantilla.
86+
{ key: 'showRecepcion', label: 'Recepción de pedidos', Icon: PackageCheck, section: 'Compras y Almacén' },
87+
{ key: 'showPedidos', label: 'Pedidos a proveedor', Icon: Truck, section: 'Compras y Almacén' },
88+
{ key: 'showProveedores', label: 'Proveedores (catálogo y precios)', Icon: Store, section: 'Compras y Almacén', sensitive: true },
89+
{ key: 'showInventarios', label: 'Recuentos de inventario', Icon: Boxes, section: 'Compras y Almacén' },
90+
{ key: 'showFacturas', label: 'Facturas de proveedor', Icon: FileText, section: 'Compras y Almacén' },
91+
{ key: 'showCostes', label: 'Costes, escandallos y márgenes', Icon: Coins, section: 'Compras y Almacén', sensitive: true },
8092

8193
// Configuración
8294
{ key: 'showLocations', label: 'Locales', Icon: MapPin, section: 'Configuración', sensitive: true },
8395
{ key: 'showTspoonSettings', label: 'Avisos', Icon: Bell, section: 'Configuración', sensitive: true },
8496
]
8597

86-
const SECTIONS = ['Principal', 'Personal', 'APPCC', 'Inventario', 'Configuración']
98+
const SECTIONS = ['Principal', 'Personal', 'APPCC', 'Inventario', 'Compras y Almacén', 'Configuración']
8799

88100
export default function ManagerPermissionsModal({ userProfileId, userName, onClose, onSaved }: Props) {
89101
const [perms, setPerms] = useState<ManagerPermissions | null>(null)

src/modules/kitchen/module.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
//
33
// ModuleDefinition del módulo Folvy Kitchen (escandallo de cocina).
44
// Sigue el patrón canónico de appcc/module.tsx.
5+
//
6+
// ENCARGO CODE (14/08) — Proveedores, Platos, Rentabilidad e Ingeniería de
7+
// menús ahora exigen show_proveedores/show_costes además de requiredRole:
8+
// 'manager' (antes solo tenían el rol, ninguna columna de manager_permissions
9+
// los gateaba). 'Platos' (kitchen_recipes) bajo show_costes por decisión
10+
// explícita de Julio: el coste/escandallo se ve dentro de la pantalla
11+
// normal de editar el plato, sin separación posible hoy — "sin rediseñar
12+
// la pantalla, simplemente el rol no entra ahí". Quien no tenga show_costes
13+
// tampoco puede editar platos/menús desde aquí.
514
import { LayoutDashboard, ChefHat, BookOpen, TrendingUp, Target, Truck, UtensilsCrossed, SlidersHorizontal, CircleSlash, Megaphone, Sliders, BarChart3, Link2 } from 'lucide-react'
615
import type { ModuleDefinition } from '@/shell/types'
716
import KitchenDashboardPage from '@/modules/kitchen/pages/KitchenDashboardPage'
@@ -58,10 +67,10 @@ export const kitchenModule: ModuleDefinition = {
5867
{ id: 'kitchen_availability', label: 'Disponibilidad', icon: CircleSlash, path: 'disponibilidad', requiredRole: 'manager' },
5968
{ id: 'kitchen_availability_reports', label: 'Informes de disponibilidad', icon: BarChart3, path: 'disponibilidad-informes', requiredRole: 'manager' },
6069
{ id: 'kitchen_items', label: 'Ingredientes', icon: ChefHat, path: '' },
61-
{ id: 'kitchen_suppliers', label: 'Proveedores', icon: Truck, path: 'proveedores', requiredRole: 'manager' },
62-
{ id: 'kitchen_recipes', label: 'Platos', icon: BookOpen, path: 'recetas', requiredRole: 'manager' },
63-
{ id: 'kitchen_profitability', label: 'Rentabilidad', icon: TrendingUp, path: 'rentabilidad', requiredRole: 'manager' },
64-
{ id: 'kitchen_menu_eng', label: 'Ingeniería de menús', icon: Target, path: 'ingenieria-menus', requiredRole: 'manager' },
70+
{ id: 'kitchen_suppliers', label: 'Proveedores', icon: Truck, path: 'proveedores', requiredRole: 'manager', requiredPermission: 'show_proveedores' },
71+
{ id: 'kitchen_recipes', label: 'Platos', icon: BookOpen, path: 'recetas', requiredRole: 'manager', requiredPermission: 'show_costes' },
72+
{ id: 'kitchen_profitability', label: 'Rentabilidad', icon: TrendingUp, path: 'rentabilidad', requiredRole: 'manager', requiredPermission: 'show_costes' },
73+
{ id: 'kitchen_menu_eng', label: 'Ingeniería de menús', icon: Target, path: 'ingenieria-menus', requiredRole: 'manager', requiredPermission: 'show_costes' },
6574
{ id: 'kitchen_offers', label: 'Ofertas del agente', icon: Megaphone, path: 'ofertas', requiredRole: 'manager' },
6675
{ id: 'kitchen_offer_rules', label: 'Reglas de ofertas', icon: Sliders, path: 'ofertas-reglas', requiredRole: 'manager' },
6776
{ id: 'kitchen_settings', label: 'Ajustes', icon: SlidersHorizontal, path: 'ajustes', requiredRole: 'manager' },

src/modules/multitenancy/services/managerPermissionsService.ts

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ export const DEFAULT_PERMISSIONS: Omit<ManagerPermissions, 'userProfileId' | 'cr
9191
canApproveVacations: true,
9292
showAppccToday: false,
9393
showAppccIncidents: false,
94+
showRecepcion: true,
95+
showPedidos: true,
96+
showProveedores: true,
97+
showInventarios: true,
98+
showFacturas: true,
99+
showCostes: false,
94100
}
95101

96102
/**
@@ -108,19 +114,24 @@ export function defaultPermissions(): typeof DEFAULT_PERMISSIONS {
108114
// TypeScript, no una tabla: si una plantilla se equivoca de clave, el
109115
// `Omit<..., 'userProfileId'|'createdAt'|'updatedAt'>` de abajo NO COMPILA
110116
// — no puede inventarse un permiso que no existe. Aplicar una plantilla
111-
// escribe las 31 columnas reales de manager_permissions vía savePermissions;
117+
// escribe las 37 columnas reales de manager_permissions vía savePermissions;
112118
// las casillas siguen siendo editables una a una después (no es una jaula).
113119
//
114-
// Valores aprobados por Julio (14/08). show_salaries y show_informes_personal
115-
// (gatea Nóminas, tan sensible como salarios) en false en las dos. Plantilla
116-
// "Solo lectura" retirada por decisión de Julio: el modelo solo gatea
117-
// visibilidad de pantalla, no acciones dentro de ella, y el nombre prometía
118-
// algo que el sistema de permisos no puede garantizar todavía.
119-
// No hay claves de Supply/Almacén/Recepción en manager_permissions: ese
120-
// módulo no está gateado por esta tabla hoy (cualquier manager/admin ya lo ve
121-
// por rol, sin mirar ninguna columna — ver ENCARGO CODE 14/08 "claves de
122-
// Supply", rama aparte). "Oficina" solo puede tocar los toggles que sí
123-
// existen (inventario, ventas, zonas de pedido, fichas técnicas).
120+
// Valores aprobados por Julio (14/08, con una ronda de correcciones).
121+
// show_salaries en false en las dos, sin excepción. show_informes_personal
122+
// (gatea Nóminas, además de los informes de fichaje/gestoría — una sola
123+
// clave para las cuatro pantallas, no hay forma de separarlas hoy) en true
124+
// para "Responsable de local" (edita el cuadrante y aprueba vacaciones; no
125+
// ver las horas de su equipo sería incoherente) y en false para "Oficina".
126+
// Plantilla "Solo lectura" retirada por decisión de Julio: el modelo solo
127+
// gatea visibilidad de pantalla, no acciones dentro de ella, y el nombre
128+
// prometía algo que el sistema de permisos no puede garantizar todavía.
129+
//
130+
// Las 6 claves de Supply/Almacén (show_recepcion, show_pedidos,
131+
// show_proveedores, show_inventarios, show_facturas, show_costes) — ver
132+
// ENCARGO CODE 14/08 "claves de Supply" — en true para las dos salvo
133+
// show_costes, que nace en false en las dos y NO entra en el backfill de
134+
// compatibilidad de la migración, mismo criterio que show_salaries.
124135
type PermissionTemplateValues = Omit<ManagerPermissions, 'userProfileId' | 'createdAt' | 'updatedAt'>
125136

126137
export interface PermissionTemplate {
@@ -139,14 +150,16 @@ export const PERMISSION_TEMPLATES: readonly PermissionTemplate[] = [
139150
showDashboard: true, showStaff: true, showAhoraMismo: true,
140151
showFichajesGlobal: true, showKioskoFichaje: true, showSolicitudesPendientes: true,
141152
showTurnosAbiertos: true, showCambiosPendientes: true, showCalendario: true,
142-
showPlantillaTurnos: true, showInformesPersonal: false, showBolsaHoras: true,
153+
showPlantillaTurnos: true, showInformesPersonal: true, showBolsaHoras: true,
143154
showTasks: true, showScheduled: true, showTemplates: false,
144155
showIncidents: true, showAudits: true, showHistory: true,
145156
showTspoon: false, showVentasAnalisis: false, showPrediccionPersonal: false,
146157
showZonasPedido: false, showInventory: true, showLocations: false,
147158
showTspoonSettings: false, showSalaries: false,
148159
canManageEmployees: false, canEditSchedule: true, canApproveVacations: true,
149160
showAppccToday: true, showAppccIncidents: true,
161+
showRecepcion: true, showPedidos: true, showProveedores: true,
162+
showInventarios: true, showFacturas: true, showCostes: false,
150163
},
151164
},
152165
{
@@ -165,6 +178,8 @@ export const PERMISSION_TEMPLATES: readonly PermissionTemplate[] = [
165178
showTspoonSettings: false, showSalaries: false,
166179
canManageEmployees: false, canEditSchedule: false, canApproveVacations: false,
167180
showAppccToday: false, showAppccIncidents: false,
181+
showRecepcion: true, showPedidos: true, showProveedores: true,
182+
showInventarios: true, showFacturas: true, showCostes: false,
168183
},
169184
},
170185
] as const
@@ -210,6 +225,12 @@ export function permissionTemplateValuesToRow(values: PermissionTemplateValues):
210225
can_approve_vacations: values.canApproveVacations,
211226
show_appcc_today: values.showAppccToday,
212227
show_appcc_incidents: values.showAppccIncidents,
228+
show_recepcion: values.showRecepcion,
229+
show_pedidos: values.showPedidos,
230+
show_proveedores: values.showProveedores,
231+
show_inventarios: values.showInventarios,
232+
show_facturas: values.showFacturas,
233+
show_costes: values.showCostes,
213234
}
214235
}
215236

@@ -255,6 +276,12 @@ export function rowToManagerPermissions(row: RowManagerPermissions): ManagerPerm
255276
canApproveVacations: row.can_approve_vacations,
256277
showAppccToday: row.show_appcc_today ?? false,
257278
showAppccIncidents: row.show_appcc_incidents ?? false,
279+
showRecepcion: row.show_recepcion,
280+
showPedidos: row.show_pedidos,
281+
showProveedores: row.show_proveedores,
282+
showInventarios: row.show_inventarios,
283+
showFacturas: row.show_facturas,
284+
showCostes: row.show_costes,
258285
createdAt: row.created_at,
259286
updatedAt: row.updated_at,
260287
}
@@ -302,6 +329,12 @@ function managerPermissionsToInsertRow(
302329
can_approve_vacations: perms.canApproveVacations,
303330
show_appcc_today: perms.showAppccToday,
304331
show_appcc_incidents: perms.showAppccIncidents,
332+
show_recepcion: perms.showRecepcion,
333+
show_pedidos: perms.showPedidos,
334+
show_proveedores: perms.showProveedores,
335+
show_inventarios: perms.showInventarios,
336+
show_facturas: perms.showFacturas,
337+
show_costes: perms.showCostes,
305338
}
306339
}
307340

@@ -342,6 +375,12 @@ function patchToUpdateRow(patch: ManagerPermissionsPatch): RowManagerPermissions
342375
if (patch.canApproveVacations !== undefined) row.can_approve_vacations = patch.canApproveVacations
343376
if (patch.showAppccToday !== undefined) row.show_appcc_today = patch.showAppccToday
344377
if (patch.showAppccIncidents !== undefined) row.show_appcc_incidents = patch.showAppccIncidents
378+
if (patch.showRecepcion !== undefined) row.show_recepcion = patch.showRecepcion
379+
if (patch.showPedidos !== undefined) row.show_pedidos = patch.showPedidos
380+
if (patch.showProveedores !== undefined) row.show_proveedores = patch.showProveedores
381+
if (patch.showInventarios !== undefined) row.show_inventarios = patch.showInventarios
382+
if (patch.showFacturas !== undefined) row.show_facturas = patch.showFacturas
383+
if (patch.showCostes !== undefined) row.show_costes = patch.showCostes
345384
return row
346385
}
347386

src/modules/supply/module.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
// - C3: Facturas (three-way match + OCR) → eslabón al coste.
1515
// - Luego: Inventario, Previsión, Planificación.
1616
//
17-
// Gating: requiredRole 'manager' (el aprovisionamiento lo gestiona admin/manager).
17+
// Gating: requiredRole 'manager' a nivel de módulo, más requiredPermission
18+
// por item (ENCARGO CODE 14/08 — antes NINGÚN item de este módulo tenía
19+
// requiredPermission; cualquier manager/admin veía las 5 pantallas,
20+
// show_inventory incluido, sin que esa columna gatease nada realmente).
21+
// Comunicar a CTB sube a requiredRole: 'admin' (decisión de Julio, no
22+
// manager) — comunica datos a la gestoría, no es operativa de local.
1823

1924
import { Truck, ClipboardList, PackageCheck, FileText, Boxes, Send } from 'lucide-react'
2025
import type { ModuleDefinition } from '@/shell/types'
@@ -45,11 +50,11 @@ export const supplyModule: ModuleDefinition = {
4550
// C2 añade Recepciones. Al construir C3 se añaden Facturas, etc.
4651
sidebar: {
4752
items: [
48-
{ id: 'supply_orders', label: 'Pedidos', icon: ClipboardList, path: '' },
49-
{ id: 'supply_receipts', label: 'Recepciones', icon: PackageCheck, path: 'recepciones' },
50-
{ id: 'supply_invoices', label: 'Facturas', icon: FileText, path: 'facturas' },
51-
{ id: 'supply_inventory', label: 'Almacén', icon: Boxes, path: 'inventario' },
52-
{ id: 'supply_ctb', label: 'Comunicar a CTB', icon: Send, path: 'comunicar-ctb' },
53+
{ id: 'supply_orders', label: 'Pedidos', icon: ClipboardList, path: '', requiredPermission: 'show_pedidos' },
54+
{ id: 'supply_receipts', label: 'Recepciones', icon: PackageCheck, path: 'recepciones', requiredPermission: 'show_recepcion' },
55+
{ id: 'supply_invoices', label: 'Facturas', icon: FileText, path: 'facturas', requiredPermission: 'show_facturas' },
56+
{ id: 'supply_inventory', label: 'Almacén', icon: Boxes, path: 'inventario', requiredPermission: 'show_inventory' },
57+
{ id: 'supply_ctb', label: 'Comunicar a CTB', icon: Send, path: 'comunicar-ctb', requiredRole: 'admin' },
5358
],
5459
},
5560
}

src/modules/ventas/module.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
// Añadida la pagina "Economia / Margenes" (EconomiaPlataformasPage), que lee la
55
// economia real de plataforma (channel_settlement + channel_settlement_order) via
66
// la RPC channel_economics_dashboard. Convive con el Resumen de ventas (que lee `sale`).
7+
//
8+
// ENCARGO CODE (14/08) "claves de Supply" — los 3 items que muestran food
9+
// cost/márgenes (economia, margen-final, margen) exigen AHORA show_costes
10+
// ADEMÁS de show_ventas_analisis (AND, ver requiredPermission como array en
11+
// shell/types.ts): antes bastaba con show_ventas_analisis, que ninguna de
12+
// las 2 plantillas actuales tiene en false — dejaba el food cost visible
13+
// por esta vía aunque show_costes fuera false.
714

815
import { BarChart3, LineChart, Bike, Wallet, Percent, UtensilsCrossed, Target, Star, TrendingUp, Lightbulb, Store } from 'lucide-react'
916
import type { ModuleDefinition } from '@/shell/types'
@@ -48,9 +55,9 @@ export const ventasModule: ModuleDefinition = {
4855
items: [
4956
{ id: 'ventas_dashboard', label: 'Resumen de ventas', icon: BarChart3, path: '', requiredPermission: 'show_ventas_analisis' },
5057
{ id: 'ventas_recos', label: 'Recomendaciones', icon: Lightbulb, path: 'recomendaciones', requiredPermission: 'show_ventas_analisis' },
51-
{ id: 'ventas_economia', label: 'Economia / Margenes', icon: Wallet, path: 'economia', requiredPermission: 'show_ventas_analisis' },
52-
{ id: 'ventas_margenfin', label: 'Margen final', icon: Target, path: 'margen-final', requiredPermission: 'show_ventas_analisis' },
53-
{ id: 'ventas_margen', label: 'Food cost / Plato', icon: UtensilsCrossed, path: 'margen', requiredPermission: 'show_ventas_analisis' },
58+
{ id: 'ventas_economia', label: 'Economia / Margenes', icon: Wallet, path: 'economia', requiredPermission: ['show_ventas_analisis', 'show_costes'] },
59+
{ id: 'ventas_margenfin', label: 'Margen final', icon: Target, path: 'margen-final', requiredPermission: ['show_ventas_analisis', 'show_costes'] },
60+
{ id: 'ventas_margen', label: 'Food cost / Plato', icon: UtensilsCrossed, path: 'margen', requiredPermission: ['show_ventas_analisis', 'show_costes'] },
5461
{ id: 'ventas_cedidas', label: 'Cedidas (CTB)', icon: Store, path: 'cedidas', requiredPermission: 'show_ventas_analisis' },
5562
{ id: 'ventas_calidad', label: 'Calidad', icon: Star, path: 'calidad', requiredPermission: 'show_ventas_analisis' },
5663
{ id: 'ventas_tendencia', label: 'Tendencia', icon: TrendingUp, path: 'tendencia', requiredPermission: 'show_ventas_analisis' },

src/shell/MobileModuleTabs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export default function MobileModuleTabs({
2525
// y helpers del Shell se extrae a un sitio común y se quita la repetición.
2626
const { hasPermission, role } = usePermissions()
2727
const visibleItems = sidebar.items.filter(item => {
28-
const passesPermission = !item.requiredPermission || hasPermission(item.requiredPermission)
28+
const passesPermission = !item.requiredPermission || (Array.isArray(item.requiredPermission) ? item.requiredPermission.every(hasPermission) : hasPermission(item.requiredPermission))
2929
const passesRole = !item.requiredRole || role === item.requiredRole || role === 'admin'
3030
return passesPermission && passesRole
3131
})

src/shell/ModuleSidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ export default function ModuleSidebar({
2828
}: ModuleSidebarProps) {
2929
// Gating por permiso granular Y por rol mínimo. Un item se muestra si
3030
// pasa AMBOS filtros:
31-
// - Permiso: si declara requiredPermission, hasPermission(clave) === true.
31+
// - Permiso: si declara requiredPermission (string o string[], AND si es array), hasPermission(clave) === true para todas.
3232
// - Rol: si declara requiredRole, roleInActiveAccount === requiredRole
3333
// (los admin de cuenta pasan siempre el filtro de rol).
3434
// El `role` viene del propio hook usePermissions (que lo lee del context
3535
// como `roleInActiveAccount`); NO usamos isAdmin del context — es la
3636
// deuda B-8 que ya está documentada en usePermissions.
3737
const { hasPermission, role } = usePermissions()
3838
const visibleItems = sidebar.items.filter(item => {
39-
const passesPermission = !item.requiredPermission || hasPermission(item.requiredPermission)
39+
const passesPermission = !item.requiredPermission || (Array.isArray(item.requiredPermission) ? item.requiredPermission.every(hasPermission) : hasPermission(item.requiredPermission))
4040
const passesRole = !item.requiredRole || role === item.requiredRole || role === 'admin'
4141
return passesPermission && passesRole
4242
})

src/shell/ShellBottomNav.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function isModuleVisible(
5757
const items = module.sidebar?.items
5858
if (!items || items.length === 0) return false
5959
return items.some(item => {
60-
const passesPermission = !item.requiredPermission || hasPermission(item.requiredPermission)
60+
const passesPermission = !item.requiredPermission || (Array.isArray(item.requiredPermission) ? item.requiredPermission.every(hasPermission) : hasPermission(item.requiredPermission))
6161
const passesRole = !item.requiredRole || role === item.requiredRole || role === 'admin'
6262
return passesPermission && passesRole
6363
})

0 commit comments

Comments
 (0)