docs: README raíz, api y web al día (módulos, acciones del proxy, auditoría, extensiones, bancos y finanzas)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-16 15:05:26 -05:00
1 parent 99c0a9259c
commit 89e25ad8e6
4 files changed
+53 -7

No files matched your search

+2 -2
View File
@@ -29,7 +29,7 @@ Menú `Finanzas`: **Plan de cuentas** (`/finanzas/plan-de-cuentas`) es un maestr
## Criterios de la interfaz ## 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 ## Comandos
@@ -64,7 +64,7 @@ Cada menú abre primero una **lista de los últimos documentos** (barra de filtr
## Estado y próximos pasos ## 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: 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. 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.
+16
View File
@@ -19,3 +19,19 @@ npm test # libs + api + web
``` ```
Configuración del backend: `apps/api/.env` (ver `apps/api/.env.example`). 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/<nombre>`).
Las decisiones de diseño, las reglas de SAP verificadas, los comandos y el estado del proyecto están en [`CLAUDE.md`](CLAUDE.md).
+15 -2
View File
@@ -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. | | `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`). | | `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ó). | | `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: Reglas:
- **Solo recursos registrados** en `src/sl/sl-resources.ts`; el resto es 404. Nunca registrar `EmployeesInfo` (contiene `U_WEB_PASS`). - **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. - 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. - 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`. - 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. - 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 dejan una línea `AUDIT <usuario> <método> <recurso>` en el log. - 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. 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.
+20 -3
View File
@@ -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). - Sin librerías de UI (nada de Material/PrimeNG): componentes propios, densos (13 px).
- Fuentes del sistema, sin webfonts; sin animaciones. - Fuentes del sistema, sin webfonts; sin animaciones.
- Zoneless + `OnPush`; rutas con `loadComponent` para cargar solo lo que se usa. - 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. - Listas largas con paginación del servidor (`$top/$skip`) o virtualización.
## Temas ## 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/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. - `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). - 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`) ## 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`) #### 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). - **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. - **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`. - **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`. - 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/<nombre>` 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`) ### 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`. 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`.