From 89e25ad8e644e6baf983d53cbd69764cab2989ad Mon Sep 17 00:00:00 2001 From: George Date: Fri, 16 Oct 2026 15:05:26 -0500 Subject: [PATCH] =?UTF-8?q?docs:=20README=20ra=C3=ADz,=20api=20y=20web=20a?= =?UTF-8?q?l=20d=C3=ADa=20(m=C3=B3dulos,=20acciones=20del=20proxy,=20audit?= =?UTF-8?q?or=C3=ADa,=20extensiones,=20bancos=20y=20finanzas)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 4 ++-- README.md | 16 ++++++++++++++++ apps/api/README.md | 17 +++++++++++++++-- apps/web/README.md | 23 ++++++++++++++++++++--- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3e29d30..3a1eaac 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,7 +29,7 @@ Menú `Finanzas`: **Plan de cuentas** (`/finanzas/plan-de-cuentas`) es un maestr ## Criterios de la interfaz -Es un ERP, no una página de publicidad: **ligera y rápida**. Sin librerías de componentes (Material/PrimeNG), sin webfonts ni animaciones, componentes propios y densos (13 px), zoneless + `OnPush`, rutas con carga diferida, listas largas paginadas en el servidor (50 por página). Presupuesto del bundle inicial en `angular.json` (aviso 300 kB; hoy ~286 kB, 81 kB comprimido; la configuración de las pantallas de documento se carga en diferido con un `resolve`, y `document-paths.ts` es la única lista que el enrutador necesita al arrancar: `httpResource` aporta ~18 kB al chunk compartido; si hace falta recuperarlos, reemplazarlo por RxJS). +Es un ERP, no una página de publicidad: **ligera y rápida**. Sin librerías de componentes (Material/PrimeNG), sin webfonts ni animaciones, componentes propios y densos (13 px), zoneless + `OnPush`, rutas con carga diferida, listas largas paginadas en el servidor (50 por página). Presupuesto del bundle inicial en `angular.json` (aviso 300 kB; hoy ~289 kB, 82 kB comprimido; la configuración de las pantallas de documento se carga en diferido con un `resolve`, y `document-paths.ts` es la única lista que el enrutador necesita al arrancar: `httpResource` aporta ~18 kB al chunk compartido; si hace falta recuperarlos, reemplazarlo por RxJS). ## Comandos @@ -64,7 +64,7 @@ Cada menú abre primero una **lista de los últimos documentos** (barra de filtr ## Estado y próximos pasos -Hecho (commits en `main`): monorepo · API (config, capa SAP con sesión compartida y cola, login, permisos SQLite, proxy `/api/sl`) · web (temas, login, layout con menú por permisos, `sbo-value-help`, campos y filas compuestas, listas con filtros, ventana/toolbar/pestañas/menús, grilla de líneas, `/dev/componentes`). +Hecho (commits en `main`): monorepo · API (config, capa SAP con sesión compartida y cola, login, permisos SQLite, proxy `/api/sl`) · web (temas, login, layout con menú por permisos, `sbo-value-help`, campos y filas compuestas, listas con filtros, ventana/toolbar/pestañas/menús, grilla de líneas, `/dev/componentes`) · formularios de todos los documentos y maestros · auditoría · Bancos (pagos recibidos y efectuados) · Finanzas (plan de cuentas y asientos). Pendiente, en orden: 1. **Paso 9 — formulario de documentos:** hecho para ventas y compras (`document-form.page`, `document-model.ts`: cargar, crear, actualizar con `If-Match`/412, navegación, totales con `SalesTaxCodes` (también en compras), solo lectura), probado con SAP simulado y contra `SBODemoCL` (leer, crear, actualizar, líneas, 412). Un código escrito a mano (socio, artículo, almacén, impuesto) se resuelve al salir del campo con `SlLookupApi` (mismo recurso y filtro base que la lista "…"); si no existe se avisa. "Copiar de/a" hecho (`document-copy.ts`: flujos estándar y `BaseType/BaseEntry/BaseLine`; probado: orden de compra 2883 → recepción 299, orden de venta 315 → entrega 580, ambas órdenes quedaron cerradas). Formularios sin socio ni totales **hechos** (`document-layouts.ts`, configuración declarativa: cabecera de 2 columnas, columnas de línea, colección `DocumentLines` o `StockTransferLines`): entradas y salidas de inventario, transferencias de stock, solicitudes de traslado y solicitudes de compra. Entradas, salidas y transferencias contabilizadas son de solo lectura (SAP no las modifica); solicitudes de traslado y de compra se crean y modifican. Probado contra `SBODemoCL`: solicitud de compra 2 (la 1 es una prueba por API), solicitud de traslado 472 (cantidad modificada) y entrada de inventario 155 (1 unidad de `3MG30080714 M 10.5` en el almacén 8803). Limitaciones conocidas: las líneas de compra/inventario llevan `UoMEntry` solo si el artículo tiene grupo de unidades (`uomEntryOf`; sin él SAP responde "specify a UoM code"); los formularios de ventas y compras también lo envían (la unidad de venta o de compra del artículo, o la de inventario; `itemColumnWithUom`), y no se envía la unidad manual (-1). Probado: orden de compra 2884 con `SU01`, que no tiene unidad de compra por defecto. **Cómo se define un formulario:** el formulario base (`document-form.page`) no sabe de qué documento se trata; recibe de la pantalla su `layout` (`DocumentLayout` en `document-layouts.ts`): `header` y `footer` (cada uno con `left`/`right`, máximo 2 columnas) son arreglos de campos tomados del catálogo `FIELDS` (texto, fecha, número, almacén, socio, folio, calculado), `columns` son las columnas de línea y `lines` la colección que se guarda. Cada pantalla (en la carpeta de su módulo) declara los suyos (la orden de venta no lleva folio; las facturas, notas de crédito, entregas y recepciones sí, con `TAX_DOCUMENT`); agregar o quitar un campo es editar ese arreglo, sin tocar el HTML. En ventas y compras la pantalla puede agregar campos (`headerLeft`/`headerRight`), reemplazar la cabecera (`header`) o elegir sus columnas de línea del catálogo `LINE_COLUMNS` (`lineColumns: [LINE_COLUMNS.item, LINE_COLUMNS.quantity, …]`; lo que se envía por línea sale de ellas); sin elegir, el documento lleva la cabecera y todas las columnas base. diff --git a/README.md b/README.md index c0647a3..e12517f 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,19 @@ npm test # libs + api + web ``` Configuración del backend: `apps/api/.env` (ver `apps/api/.env.example`). + +## Módulos del menú + +| Módulo | Pantallas | +|---|---| +| Ventas | Ofertas, órdenes, entregas, facturas, boletas, facturas y boletas de reserva, devoluciones, notas de crédito | +| Compras | Solicitudes, ofertas, órdenes, recepción, facturas, devoluciones, notas de crédito | +| Inventario | Artículos, entradas, salidas, transferencias de stock, solicitudes de traslado | +| Bancos | Pagos recibidos (con cheques y tarjetas de crédito) y pagos efectuados | +| Finanzas | Plan de cuentas (árbol) y asientos contables | +| Socios de negocio | Clientes y proveedores | +| Administración | Permisos de menú y auditoría | + +Solo campos estándar de SAP; lo propio de cada cliente va en sus archivos de extensiones (rama `cliente/`). + +Las decisiones de diseño, las reglas de SAP verificadas, los comandos y el estado del proyecto están en [`CLAUDE.md`](CLAUDE.md). diff --git a/apps/api/README.md b/apps/api/README.md index 58c0ef6..375771c 100644 --- a/apps/api/README.md +++ b/apps/api/README.md @@ -65,13 +65,26 @@ El frontend arma los parámetros OData y el cuerpo; el backend solo valida y eje | `GET /api/sl/:recurso/:clave?$select` | Un registro. La clave se valida y escapa según su tipo. | | `POST /api/sl/:recurso` | Crea; devuelve lo que responde SAP (ej. `DocEntry`, `DocNum`). | | `PATCH /api/sl/:recurso/:clave` | Modifica (204). Acepta `If-Match` con el `odata.etag` leído (concurrencia optimista: SAP responde 412 si el documento cambió). | +| `POST /api/sl/:recurso/:clave/:accion` | Acción de un registro (hoy `Cancel` de pagos y asientos). Solo las que el recurso declara en `actions`; requiere el permiso de escritura. | +| `POST /api/sl/:accion` | Acción de solo consulta sin registro (`SeriesService_GetDocumentSeries`, series de numeración): cualquier sesión. | Reglas: - **Solo recursos registrados** en `src/sl/sl-resources.ts`; el resto es 404. Nunca registrar `EmployeesInfo` (contiene `U_WEB_PASS`). - Cada recurso declara clave, métodos y la ruta de menú que gobierna lectura/escritura. Los catálogos de consulta (`Items`, `Warehouses`...) se leen con cualquier sesión. - Parámetros OData fuera de la lista (`$apply`...) → 400. `$expand` solo si el recurso declara qué colecciones admite. - Cabeceras B1S-* permitidas: `B1S-ReplaceCollectionsOnPatch` (PATCH), `B1S-CaseInsensitive` y `B1S-PageSize` (GET). Se fijan solas: `B1S-PageSize` = `$top` y `B1S-CaseInsensitive` si hay `$filter`. -- No hay DELETE ni acciones (`Cancel`, `Close`); se agregan por recurso cuando haga falta. -- Las escrituras dejan una línea `AUDIT ` en el log. +- No hay DELETE. Las acciones (`Cancel`) existen solo donde el recurso las declara en `actions` (pagos recibidos y efectuados, asientos); `Close` y las demás se agregan por recurso cuando haga falta. +- Las escrituras y las acciones se registran en la auditoría (ver abajo) y dejan una línea `AUDIT` en el log. Agregar un recurso nuevo = una línea en `SL_RESOURCES`. El test en vivo (`SAP_LIVE=1 npm run test:e2e`) comprueba que todos existan en el Service Layer del cliente. + +## Auditoría + +Tabla `audit_log` en la misma SQLite. Registra inicios de sesión (correctos, fallidos con su motivo y cierres), cada creación, modificación y acción (`sl_action`) que pasa por el proxy (recurso, clave, resultado y **solo los nombres de los campos**, nunca valores ni contraseñas), las operaciones denegadas por permisos y los cambios de permisos de menú. Las lecturas no se auditan. `AuditService.record` nunca lanza: un fallo del registro no cambia la operación del usuario. + +| Endpoint | Quién | Descripción | +|---|---|---| +| `GET /api/audit?usuario&accion&recurso&resultado&desde&hasta&page` | Administrador | Consulta; 50 por página (máx. 200) | + +Pendiente de afinar: retención programada, IP real detrás del proxy de desarrollo, exportar a CSV. + diff --git a/apps/web/README.md b/apps/web/README.md index 1aac868..7bff906 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -15,7 +15,7 @@ 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: aviso a 260 kB, error a 400 kB (`angular.json`). Hoy: ~200 kB (56 kB comprimido). +- 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 @@ -39,7 +39,7 @@ Tokens de color: `--app --shell --shell-ink --shell-quiet --win --panel --line - - `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ú; las que aún no existen muestran "no disponible" (`ComingSoonPage`). Para construir una pantalla, se reemplaza su `loadComponent`. +- `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`) @@ -126,7 +126,7 @@ Las tres rutas las gobierna el permiso de menú de la ruta padre. Se generan sol #### Formulario de documentos de ventas y compras (`document-form.page`) -Lo usan las pantallas con `partner` en su `DocumentScreen` (ventas y compras); el resto de documentos muestra aún un aviso. +Lo usan todas las pantallas de documento: ventas y compras (con socio, líneas con precios e impuestos) y las de inventario y solicitudes (sin socio ni totales). Cada pantalla declara su `DocumentLayout` (`document-layouts.ts`): campos de cabecera y pie (máximo 2 columnas), columnas de línea y colección que se guarda; el formulario no sabe de qué documento se trata. - **Cabecera**: socio de negocio (lista de valores filtrada por `CardType`; no se cambia en un documento ya creado), Nº de referencia, fechas y descuento %; **líneas** con `sbo-lines-grid`; **totales** informativos (SAP recalcula al guardar) con las tasas de `SalesTaxCodes` (también en compras: el Service Layer no tiene un maestro propio; `partner.taxResource` permite cambiarlo en una rama de cliente). - **Códigos escritos a mano**: al salir del campo (socio con `resolveOnLeave` en `sbo-lookup-field`; artículo, almacén e impuesto en la grilla) se buscan con `SlLookupApi` (`core/sl/sl-lookup.ts`) en la misma lista que abre "…": se normaliza el código y se completa nombre/descripción; si no existe se avisa (en el campo o en la barra de estado) y la celda vuelve a su valor. @@ -137,6 +137,23 @@ Lo usan las pantallas con `partner` en su `DocumentScreen` (ventas y compras); e - **Navegación** primero / anterior / siguiente / último por `DocEntry`. - La lógica sin Angular está en `document-model.ts` (totales, payload, validación, navegación) y el acceso HTTP en `core/sl/sl-document.ts`. +### Campos por cliente (extensiones) + +Cada módulo tiene su archivo de extensiones, vacío en `main` (`ventas.extensions.ts`, `compras.extensions.ts`, `inventario.extensions.ts`, `socios.extensions.ts`, `articulos.extensions.ts`, `finanzas.extensions.ts`). La rama `cliente/` agrega ahí, por ruta de pantalla, campos de cabecera o pie, columnas de línea y de lista (estándar o `U_*`), y puede **ocultar** (`hide…`) o dejar de **solo lectura** (`readOnly…`) lo que no use. Un nombre inexistente, ocultar un campo obligatorio o una ruta ajena lanzan un error al arrancar. + +### Maestros (`features/masters`) + +Clientes, proveedores, artículos y plan de cuentas comparten una lista y un formulario (`MasterScreen` / `MasterLayout`): código y nombre arriba y los demás campos en pestañas, con tablas editables (`sbo-rows-table`: direcciones, contactos, precios, stock por almacén). El código y los campos `immutable` no se cambian una vez creado el registro; al modificar se envía `If-Match` y un 412 avisa del conflicto. El plan de cuentas abre como **árbol** (`chart-tree.page`) y su formulario muestra las casillas de grupo. + +### Bancos y finanzas + +- **Pagos recibidos y efectuados** (`features/banking`): un mismo formulario configurado por `PaymentScreen`. Se aplican a las facturas y notas de crédito abiertas del socio, a cuenta (lo que sobra de los medios queda como anticipo) o a cuentas contables; medios: efectivo, transferencia, cheques y, en los recibidos, tarjetas de crédito. Un pago contabilizado no se edita: se consulta y se anula (`Cancel`). Lógica sin Angular en `payment-model.ts`. +- **Asientos contables** (`features/finance`): líneas con cuenta, debe o haber, dimensiones, proyecto, referencias y vencimiento; solo se crea si cuadra. Los manuales se cancelan (SAP genera el reverso). Lógica en `journal-model.ts`. + +### Administración + +`/admin/permisos` (permisos de menú) y `/admin/auditoria` (consulta de la auditoría: filtros por usuario, acción, recurso, resultado y fechas), solo con acceso total. + ### Permisos de menú (`/admin/permisos`) Pantalla del módulo "Administración", solo para quien tiene acceso total (`*`; el API de permisos exige ser administrador). Lista los usuarios web con permisos y, al elegir uno, muestra los módulos y pantallas con casillas (el módulo completo se guarda como `/modulo/*`, las pantallas sueltas como rutas). "Acceso total" guarda `*`; "Administración" no tiene casilla: solo llega con acceso total. Los patrones que no corresponden a ninguna pantalla actual se conservan. Un usuario nuevo se agrega por su `U_WEB_USER` y no existe hasta pulsar Guardar. El servidor impide quitar al último administrador. La conversión casillas ↔ patrones está en `features/admin/permissions-model.ts`.