feat(web): pantalla de permisos de menú (/admin/permisos)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-13 22:17:50 -05:00
1 parent d900a96740
commit 4d4df17d07
11 files changed
+496 -5

No files matched your search

@@ -0,0 +1,56 @@
import { type MenuModule, isPathAllowed } from '@basic-erp/sap-types';
/** Lo que el administrador marca en pantalla para un usuario. */
export interface Selection {
/** Acceso total (`*`). */
all: boolean;
/** Rutas de menú marcadas (incluye las cubiertas por un patrón `/modulo/*`). */
paths: ReadonlySet<string>;
/** Patrones que no corresponden a ninguna pantalla actual (ej. de una versión futura): se conservan tal cual. */
extras: readonly string[];
}
/** Módulos que se otorgan con casillas; "Administración" solo llega con acceso total. */
export const grantableModules = (menu: readonly MenuModule[]): readonly MenuModule[] => menu.filter((m) => m.id !== 'admin');
export function toSelection(patterns: readonly string[], menu: readonly MenuModule[]): Selection {
const all = patterns.includes('*');
const known = menu.flatMap((m) => m.items.map((i) => i.path));
const paths = new Set(known.filter((p) => !all && isPathAllowed(patterns, p)));
const extras = patterns.filter((p) => p !== '*' && !known.some((k) => isPathAllowed([p], k)));
return { all, paths, extras };
}
/**
* Patrones a guardar: `*` si es acceso total; si no, `/modulo/*` cuando están todas las pantallas
* del módulo y rutas sueltas en otro caso, más los patrones desconocidos conservados.
*/
export function toPatterns(sel: Selection, menu: readonly MenuModule[]): string[] {
if (sel.all) return ['*'];
const out: string[] = [];
for (const m of grantableModules(menu)) {
const checked = m.items.filter((i) => sel.paths.has(i.path));
if (checked.length === m.items.length && m.items.length > 0) out.push(`/${m.id}/*`);
else out.push(...checked.map((i) => i.path));
}
return [...out, ...sel.extras];
}
export function togglePath(sel: Selection, path: string, on: boolean): Selection {
const paths = new Set(sel.paths);
if (on) paths.add(path);
else paths.delete(path);
return { ...sel, paths };
}
export function toggleModule(sel: Selection, module: MenuModule, on: boolean): Selection {
const paths = new Set(sel.paths);
for (const i of module.items) {
if (on) paths.add(i.path);
else paths.delete(i.path);
}
return { ...sel, paths };
}
export const samePatterns = (a: readonly string[], b: readonly string[]): boolean =>
a.length === b.length && [...a].sort().every((p, i) => p === [...b].sort()[i]);