feat(web): app Angular, librería compartida sap-types y sistema de temas
- apps/web: Angular 22 standalone, zoneless, OnPush, vitest; proxy /api hacia el backend; presupuesto de bundle (aviso 260 kB, error 400 kB). - libs/sap-types: tipos de auth/permisos/Service Layer y el evaluador de patrones de menú, compartidos por api y web (se compila en postinstall). El backend deja de tener su propia copia del evaluador. - Temas por variables CSS: claro por defecto con la paleta del mockup de Oferta de ventas, oscuro incluido; ThemeService + selector, con el tema guardado aplicado antes del primer pintado. Agregar un tema = 1 css + 1 import + 1 entrada. - Criterios de interfaz ligera en CLAUDE.md y README. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c0990ca83e
commit
f1156929c7
40 files changed
+6231
-5
No files matched your search
@@ -1,27 +0,0 @@
|
||||
import { isPathAllowed, matchesPattern } from './path-matcher.js';
|
||||
|
||||
describe('matchesPattern', () => {
|
||||
it('"*" permite todo', () => expect(matchesPattern('*', '/lo/que/sea')).toBe(true));
|
||||
|
||||
it('"/ventas/*" permite la ruta y sus hijas, no prefijos parecidos', () => {
|
||||
expect(matchesPattern('/ventas/*', '/ventas')).toBe(true);
|
||||
expect(matchesPattern('/ventas/*', '/ventas/ordenes')).toBe(true);
|
||||
expect(matchesPattern('/ventas/*', '/ventas/ordenes/mantenimiento')).toBe(true);
|
||||
expect(matchesPattern('/ventas/*', '/ventas-x')).toBe(false);
|
||||
expect(matchesPattern('/ventas/*', '/compras/ordenes')).toBe(false);
|
||||
});
|
||||
|
||||
it('ruta exacta solo coincide consigo misma', () => {
|
||||
expect(matchesPattern('/ventas/ordenes', '/ventas/ordenes')).toBe(true);
|
||||
expect(matchesPattern('/ventas/ordenes', '/ventas/ordenes/x')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPathAllowed', () => {
|
||||
it('null = cualquier sesión, incluso sin permisos', () => expect(isPathAllowed([], null)).toBe(true));
|
||||
it('sin patrones no hay acceso', () => expect(isPathAllowed([], '/ventas/ordenes')).toBe(false));
|
||||
it('basta con una de varias rutas requeridas', () => {
|
||||
expect(isPathAllowed(['/socios/proveedores'], ['/socios/clientes', '/socios/proveedores'])).toBe(true);
|
||||
expect(isPathAllowed(['/socios/otro'], ['/socios/clientes', '/socios/proveedores'])).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
/** Patrón de permiso: `*`, `/ventas/*` (la ruta y todo lo que cuelga) o una ruta exacta. */
|
||||
export function matchesPattern(pattern: string, path: string): boolean {
|
||||
if (pattern === '*') return true;
|
||||
if (pattern.endsWith('/*')) {
|
||||
const base = pattern.slice(0, -2);
|
||||
return path === base || path.startsWith(`${base}/`);
|
||||
}
|
||||
return pattern === path;
|
||||
}
|
||||
|
||||
/** `required`: una ruta o varias (basta con una). `null` = cualquier sesión. */
|
||||
export function isPathAllowed(patterns: string[], required: string | string[] | null): boolean {
|
||||
if (required === null) return true;
|
||||
const paths = Array.isArray(required) ? required : [required];
|
||||
return paths.some((path) => patterns.some((pattern) => matchesPattern(pattern, path)));
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
MethodNotAllowedException,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { isPathAllowed } from '@basic-erp/sap-types';
|
||||
import type { AuthUser } from '../auth/auth.types.js';
|
||||
import { isPathAllowed } from '../permissions/path-matcher.js';
|
||||
import { PermissionsService } from '../permissions/permissions.service.js';
|
||||
import { SapClientService } from '../sap/sap-client.service.js';
|
||||
import {
|
||||
|
||||
Reference in new issue
Block a user