# web Frontend Angular (standalone, zoneless, señales) del ERP base. Interfaz densa y ligera estilo SAP Business One. ```bash npm run dev:web # desde la raíz; http://localhost:4200 (proxy /api -> localhost:3000) npm run build -w apps/web npm test -w apps/web ``` ## Criterios de rendimiento Es un ERP, no un sitio publicitario: rápido y ligero. - Sin librerías de UI (nada de Material/PrimeNG): componentes propios, densos (13 px). - Fuentes del sistema, sin webfonts; sin animaciones. - Zoneless + `OnPush`; rutas con `loadComponent` para cargar solo lo que se usa. - Presupuesto del bundle inicial en `angular.json` (aviso a 300 kB). Hoy: ~289 kB (82 kB comprimido). La configuración de las pantallas (documentos, maestros, pagos) se carga en diferido con un `resolve`; el enrutador solo necesita sus listas de rutas (`*-paths.ts`). - Listas largas con paginación del servidor (`$top/$skip`) o virtualización. ## Temas El color sale solo de variables CSS (`src/styles/themes/*.css`). Tema por defecto: **claro**, con la paleta del mockup de Oferta de ventas. Agregar un tema: 1. `src/styles/themes/.css` con un bloque `:root[data-theme='']` (copiar los tokens de `light.css`). 2. `@import` en `src/styles.css`. 3. Una entrada en `src/app/core/theme/themes.ts`. Forma: `--radius` (campos, 2 px), `--radius-lg` (botones, 3 px), `--radius-pill` (insignias) y `--radius-panel` (tablas, ventanas y diálogos; 0 = esquinas rectas como SBO). Un tema puede redondear sin tocar componentes. Tokens de color: `--app --shell --shell-ink --shell-quiet --win --panel --line --line-soft --ink --quiet --field --field-ro --field-b --focus --gold --grid-head --row-sel --row-alt --ok --ok-bg --warn --warn-bg --err --err-bg --btn --btn-b --btn-h --overlay --shadow-pop`. ## Tipos compartidos `@basic-erp/sap-types` (`libs/sap-types`): usuario, respuestas de la API y el evaluador de permisos de menú, los mismos que usa el backend. ## Autenticación y rutas - `core/auth`: `SessionStore` (sesión en localStorage, compartida entre pestañas, con timeout deslizante según el servidor), `AuthService` (login/logout/expire), interceptor (Bearer en `/api`, renueva actividad y atiende los 401) y guards (`authGuard`, `guestGuard`, `permissionGuard`). - `core/menu`: `MenuService` carga `GET /api/user/menu` una vez por sesión; el menú visible sale de `MENU` (`@basic-erp/sap-types`) filtrado por permisos. - `app.routes.ts` genera una ruta por pantalla del menú (documentos, maestros, pagos, asientos, administración); una ruta del menú sin pantalla muestra "no disponible" (`ComingSoonPage`). Un test comprueba que cada ruta del menú tiene su pantalla y su permiso. - Un 401 en cualquier llamada cierra la sesión local y lleva al login recordando la ruta (`?returnUrl=`, solo rutas internas). ## Componentes compartidos (`src/app/shared`) ### `sbo-value-help` — lista de valores ("elegir de la lista") Búsqueda por columna sobre un recurso del proxy `/api/sl`, selección única o múltiple y paginación del servidor. ```html ``` ```ts columns: VhColumn[] = [ { key: 'CardCode', title: 'Código', width: '130px', mono: true }, { key: 'CardName', title: 'Nombre' }, { key: 'CurrentAccountBalance', title: 'Saldo', type: 'currency' }, ]; ``` - Agregar una columna = una entrada en `columns`; el `$select` se deriva de ahí. - Columna activa de búsqueda: clic en el encabezado (solo columnas de texto, o `searchable: true`). Se aplica al pulsar **Buscar**/Enter con `contains()`, combinada con `baseFilter` entre paréntesis y con las comillas escapadas. - `selection="single"`: un clic en la fila confirma y cierra. `selection="multiple"`: casillas, "seleccionar página" y **Confirmar (n)**; lo marcado se conserva al cambiar de página (`[initialSelection]` precarga filas). - `type`: `text` (defecto), `number`, `currency` (2 decimales), `date` (dd/MM/yyyy); `format: (row) => string` para algo a medida. - Teclado: Enter en la búsqueda, Enter sobre una fila, Esc para cerrar. - Cada apertura reinicia búsqueda, página y selección. Las peticiones obsoletas se cancelan solas (`httpResource`). ### Campos de formulario (`shared/form`) Una **fila** (`sbo-field`) es: etiqueta + uno o varios controles en la misma línea. La cabecera de un formulario tiene **como máximo 2 columnas** (`.sbo-hdr` con dos `.sbo-col`). Las combinaciones del mockup (Cliente = input + "…", Canal / Zona = dos mitades, Serie + Número = 72 px + resto, select + número + botón) salen de combinar controles: ```html
``` **Controles combinables** (sin etiqueta, dentro de `sbo-field`; ocupan el espacio sobrante o un `width` fijo): `sbo-input`, `sbo-textarea`, `sbo-number`, `sbo-date`, `sbo-select` y el botón nativo `
… …
``` - `sbo-window`: título, insignia de estado (`tone`: `info` Nueva, `ok` Abierta, `muted` Cerrada, `err` Cancelada, `warn`), cuerpo, zona de acciones (`sboActions` a la izquierda, `sboActionsEnd` a la derecha) y **barra de estado** (`{ kind: 'info' | 'ok' | 'warn' | 'err', text }`, con `role="status"`). - `sbo-toolbar` + botones `.tb` + `sbo-icon` (iconos SVG en línea: `new find first prev next last print`). - `sbo-tabs` / `sbo-tab`: los paneles ocultos siguen en el DOM (conservan el estado); `[(selected)]`; flechas ←/→. - `sbo-menu-button`: lista de acciones que se abre hacia arriba (o `direction="down"`); se cierra con Esc o clic fuera; opciones `disabled`. - `sbo-badge`: la misma insignia que usan las listas. ### Grilla de líneas (`sbo-lines-grid`) Tabla editable de las líneas del documento, con columnas declarativas (`GridColumn`): ```html ``` - **Columnas estándar** (`features/documents/line-columns.ts`): Nº de artículo, Descripción, Cantidad, Almacén, Precio, % Dto., Impuesto y Total. El impuesto es `TaxCode` (maestro `SalesTaxCodes`, como en el mini-erp); un cliente que use `VatGroup` cambia esa columna en su rama. - Celdas con **lista de valores** (`lookup`): botón "…" o **F4**; al elegir, `apply` copia a la línea los campos de la fila elegida (artículo → código + descripción). - Las celdas se confirman al salir de ellas o con **Enter**, que baja a la misma columna de la fila siguiente y crea una línea al llegar a la última. **↑/↓** cambian de fila. Números con formato fuera de foco y valor crudo al editar; un texto que no es número conserva el anterior. - `recalc` recalcula la línea tras un cambio (`recalcLine`: cantidad × precio × (1 − % dto.)); SAP recalcula impuestos y totales al guardar. - `multiAdd`: "Elegir artículos…" agrega varias líneas de una vez (el "catálogo completo" del mini-erp). - `readonly` (documento cerrado o cancelado): sin edición, sin botones de añadir/quitar ni listas. ### Página de componentes (solo desarrollo) Con `npm run dev:web`, abrir **`/dev/componentes`**: muestra juntos la toolbar, la ventana, los campos, las pestañas, la grilla y las listas de valores sobre datos reales de SAP, sin guardar nada. La ruta solo coincide en modo desarrollo (`canMatch: isDevMode`), no aparece en el menú y se actualiza sola con cada cambio de componentes.