docs: restaura CLAUDE.md (un reemplazo lo había truncado), con las secciones de Finanzas, solo lectura y líneas del asiento
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f9ee6b5d3b
commit
9ec0bb0242
1 file changed
+66
-1
@@ -25,4 +25,69 @@ Reglas de SAP verificadas: nota de crédito se aplica con `SumApplied` **negativ
|
||||
|
||||
## Finanzas (plan de cuentas y asientos)
|
||||
|
||||
Menú `Finanzas`: **Plan de cuentas** (`/finanzas/plan-de-cuentas`) es un maestro más (`masters/finanzas/`, recurso `ChartOfAccounts`, clave `Code`; extensiones por cliente en `finanzas.extensions.ts`) y **Asientos contables** (`/finanzas/asientos`, `features/finance/`, recurso `JournalEntries`, clave `JdtNum`) tiene lista y formulario propios (`journal-model.ts`: cuadre debe = haber, líneas con cuenta, dimensiones y proyecto). Verificado en SAP: en una cuenta ya creada solo se puede cambiar el nombre y similares; `ActiveAccount` (de movimiento / de título) y la cuenta padre no se cambian (campos `immutable`), y congelarla exige un rango de fechas (no se ofrece). Un asiento no se modifica; solo los **manuales** (`OriginalJournal = ttJournalEntry`) se cancelan con `Cancel`, que genera el asiento de reverso (el original no lleva marca de cancelado). Los asientos de documentos se cancelan con su documento. El plan de cuentas se abre como **árbol** (`chart-tree.page` → `chart-tree.panel`: se cargan todas las cuentas de a 200 y el árbol se arma en el navegador; pestañas verticales por grupo —las cuentas de primer nivel—, selector «Nivel», azul = título, verde = asociada) y al elegir una cuenta su formulario queda a la izquierda con el árbol a la derecha (`MasterLayout.chartTree`), como SAP. Campos del formulario según SAP (verificado con el usuario): «Cuenta asociada» = `LockManualTransaction`, «Cuenta monetaria» = `CashAccount`, «Bloquear contabilización manual» = `BlockManualPosting`, «Confidencial» = `Protected`; pestañas Propiedades, Contabilidad de costos e Info detallada (sin `DefaultVatGroup` ni `VATRegNum`). No se ofrece congelar/validar por fechas ni se puede cambiar `ActiveAccount` ni la cuenta padre. anualTransaction`, «Cuenta monetaria» = `CashAccount`, «Bloquear contabilización manual» = `BlockManualPosting`, «Confidencial» = `Protected`; pestañas Propiedades, Contabilidad de costos e Info detallada (sin `DefaultVatGroup` ni `VATRegNum`). No se ofrece congelar/validar por fechas ni se puede cambiar `ActiveAccount` ni la cuenta padre.
|
||||
Menú `Finanzas`: **Plan de cuentas** (`/finanzas/plan-de-cuentas`) es un maestro más (`masters/finanzas/`, recurso `ChartOfAccounts`, clave `Code`; extensiones por cliente en `finanzas.extensions.ts`) y **Asientos contables** (`/finanzas/asientos`, `features/finance/`, recurso `JournalEntries`, clave `JdtNum`) tiene lista y formulario propios (`journal-model.ts`: cuadre debe = haber, líneas con cuenta, dimensiones y proyecto). Verificado en SAP: en una cuenta ya creada solo se puede cambiar el nombre y similares; `ActiveAccount` (de movimiento / de título) y la cuenta padre no se cambian (campos `immutable`), y congelarla exige un rango de fechas (no se ofrece). Un asiento no se modifica; solo los **manuales** (`OriginalJournal = ttJournalEntry`) se cancelan con `Cancel`, que genera el asiento de reverso (el original no lleva marca de cancelado). Los asientos de documentos se cancelan con su documento. El plan de cuentas se abre como **árbol** (`chart-tree.page` → `chart-tree.panel`: se cargan todas las cuentas de a 200 y el árbol se arma en el navegador; pestañas verticales por grupo —las cuentas de primer nivel—, selector «Nivel», azul = título, verde = asociada) y al elegir una cuenta su formulario queda a la izquierda con el árbol a la derecha (`MasterLayout.chartTree`), como SAP. Campos del formulario según SAP (verificado con el usuario): «Cuenta asociada» = `LockManualTransaction`, «Cuenta monetaria» = `CashAccount`, «Bloquear contabilización manual» = `BlockManualPosting`, «Confidencial» = `Protected`; pestañas Propiedades, Contabilidad de costos e Info detallada (sin `DefaultVatGroup` ni `VATRegNum`). No se ofrece congelar/validar por fechas ni se puede cambiar `ActiveAccount` ni la cuenta padre. anualTransaction`, «Cuenta monetaria» = `CashAccount`, «Bloquear contabilización manual» = `BlockManualPosting`, «Confidencial» = `Protected`; pestañas Propiedades, Contabilidad de costos e Info detallada (sin `DefaultVatGroup` ni `VATRegNum`). No se ofrece congelar/validar por fechas ni se puede cambiar `ActiveAccount` ni la cuenta padre.
|
||||
|
||||
## 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 ~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
|
||||
|
||||
- `npm install` — **npm 11** (npm 10 falla al resolver las dependencias de Nest 12: `npx npm@11 install`); instala todo y compila `libs/sap-types`.
|
||||
- `npm run dev:api` (`:3000`) · `npm run dev:web` (`:4200`, proxy `/api`) · `npm run build` · `npm test`.
|
||||
- **Pruebas con otra persona en la red:** `npm run dev:web:lan` abre la web a la red (`http://<IP del equipo>:4200`; el proxy `/api` llega al backend local). El backend (`:3000`) escucha en todas las interfaces y la web normal (`dev:web`) solo en `localhost`; ambos son de desarrollo, sin HTTPS. La otra persona necesita su propio usuario web (`U_WEB_USER`/`U_WEB_PASS` en `EmployeesInfo`) y permisos de menú (`/admin/permisos`): hay **una sesión por usuario**, así que no debe entrar con el mismo que tú.
|
||||
- Backend contra SAP real (opt-in): `SAP_LIVE=1 npm run test:e2e -w apps/api`.
|
||||
- Página de componentes **solo desarrollo**: `http://localhost:4200/dev/componentes` (toolbar, ventana, campos, pestañas, grilla y listas de valores con datos reales; no guarda nada).
|
||||
|
||||
## Decisiones tomadas (con su porqué)
|
||||
|
||||
- **Login web** con `U_WEB_USER`/`U_WEB_PASS` (se mantiene). Mismo mensaje para usuario inexistente y clave errónea; comparación en tiempo constante; 429 tras 5 fallos por usuario / 20 por IP; una sesión por usuario; timeout deslizante 30 min (el servidor informa `sessionTimeoutMinutes`); token `Authorization: Bearer`.
|
||||
- **Usuario SAP compartido** (hoy Manager de pruebas; luego un usuario de licencia indirecta). Todo sale auditado con ese usuario.
|
||||
- **Concurrencia hacia el Service Layer = 1** (`SAPB1_SL_MAX_CONCURRENCY`): medido en SAP10 (MSSQL, 32 GB), 12 peticiones tardan 0.6 s en serie y 7.5 s con 2 en paralelo. Un pool de sesiones no ayuda; la cola sí. Repetir la medición en cada cliente.
|
||||
- **TLS:** `SAPB1_SL_CA_FILE` opcional (certificado autofirmado de SAP o CA de firewall); `VERIFY_SSL=true` por defecto y no se puede apagar en producción. `fortinet.cer` no sirve para SAP10: presenta su propio certificado autofirmado (`apps/api/certs/sap10.pem`, ignorado por git).
|
||||
- **Proxy `/api/sl/:recurso`**: el frontend arma `$select/$filter/...`; el backend valida y ejecuta solo recursos registrados (`apps/api/src/sl/sl-resources.ts`), con permiso de menú y cabeceras `B1S-*` de lista blanca. Nunca registrar `EmployeesInfo` (tiene `U_WEB_PASS`). `InventoryRequests` no existe: es `InventoryTransferRequests`.
|
||||
- **Permisos de menú** en SQLite (`SQLITE_PATH`), patrones `*`, `/ventas/*`, `/ventas/ordenes`; `ADMIN_WEB_USERS` garantiza el acceso total; no se puede quitar el último admin.
|
||||
- **Listas de documentos:** más recientes primero (`DocEntry desc`), 50 por página, **Estado = Abierto por defecto** (como el mini-erp; cambiarlo es un `defaultValue`), filtro de fecha **siempre rango [desde – hasta]**, multi-selección con **chips**, búsqueda numérica por igualdad. **Sin filtro base en las demás listas; las de `Invoices` filtran por subtipo exacto:** cuatro pantallas comparten el recurso y se separan por `DocumentSubType` (enum `BoDocumentSubType`: `bod_None` 0, `bod_InvoiceExempt` 1, `bod_DebitMemo` 2, `bod_Bill` 3, `bod_ExemptBill` 4, `bod_PurchaseDebitMemo` 5, `bod_ExportInvoice` 6…) y por `ReserveInvoice` (`tYES` = de reserva): Facturas (`tNO` y `eq bod_None`), Boletas (`tNO` y `eq bod_Bill`), Facturas de reserva (`tYES` y `eq bod_None`) y Boletas de reserva (`tYES` y `eq bod_Bill`); `baseFilter` para la lista y `fixed` (subtipo, y `ReserveInvoice` en las reservas) al crear. Nunca `ne`: los demás subtipos (factura exenta, nota de débito, boleta exenta, exportación) no se mezclan y tendrán su propia pantalla si se necesitan. `baseFilter` de la pantalla se aplica también a la navegación primero/anterior/siguiente/último. El PDF y los reportes se migran después vía API Gateway (otro servicio, con su propia cola y sesión).
|
||||
- Reportes/PDF **no** se generan en la base; el botón PDF queda pendiente hasta migrar el API Gateway.
|
||||
|
||||
## Flujo de las pantallas de documento
|
||||
|
||||
Cada menú abre primero una **lista de los últimos documentos** (barra de filtros + búsqueda por columna); desde ahí se crea uno nuevo (`/…/nuevo`, formulario vacío) o se abre uno existente (`/…/:docEntry`, ver/editar; los cerrados/cancelados en solo lectura). Las pantallas se definen por módulo en `apps/web/src/app/features/documents/` (17 documentos): `ventas/ventas.screens.ts`, `compras/compras.screens.ts` (+ `compras.layouts.ts`) e `inventario/inventario.screens.ts` (+ `inventario.layouts.ts`); los maestros (socios de negocio, artículos) irán en su propia carpeta. `screen-kit.ts` tiene el tipo `DocumentScreen` y las columnas y filtros comunes; `document-screens.ts` las reúne en `DOCUMENT_SCREENS`. Las columnas de línea estándar están en `features/documents/line-columns.ts`.
|
||||
|
||||
## Cómo verificar cambios de interfaz
|
||||
|
||||
- Probar contra el SAP real con la extensión de Chrome (`list_connected_browsers` debe mostrar un navegador). Si no conecta, usar Chrome headless por CDP.
|
||||
- **No escribir contraseñas en formularios**: obtener el token con `curl` a `/api/auth/login` (la clave de prueba se lee de `EmployeesInfo` solo para eso, nunca se imprime ni se guarda) e inyectar solo el token en `localStorage` (`erp_session`).
|
||||
- Al terminar: cerrar la sesión de prueba (`POST /api/auth/logout`), borrar `erp_session`/`erp_theme` del navegador, cerrar la pestaña, apagar los servidores y borrar archivos temporales (token, `apps/api/data/`). No dejar páginas de demostración fuera de `/dev/componentes`.
|
||||
- **Escrituras a SAP (POST/PATCH):** el usuario autorizó (2026-10-14) escribir en `SBODemoCL`, su ambiente propio de pruebas; ya se probaron crear/actualizar órdenes de venta y de compra, reemplazo de líneas y el 412 por conflicto. Documentos de prueba creados: orden de venta 315 (cerrada, copiada a la entrega 580) y orden de compra 2883 (cerrada, copiada a la recepción 299). Aun así, avisar antes de escribir en cualquier otra base. **`SBO_TAGUMEDICA_21` (HANA, plan de cuentas segmentado) es solo lectura:** no se escribe nada ahí. `SAPB1_SL_READ_ONLY=true` en el `.env` hace que el proxy rechace toda creación, modificación y acción (403 «La base está en modo solo lectura», auditado); las consultas y las acciones de solo consulta (series) siguen. Al volver a `SBODemoCL` hay que quitar esa línea o ponerla en `false`.
|
||||
- Los tests no detectan todo: el foco tras crear una línea (se pedía antes de que existiera la fila) solo falló en el navegador real.
|
||||
|
||||
## 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`) · 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.
|
||||
|
||||
**Campos por cliente (estándar adicionales o `U_*`):** no se editan las pantallas base. Cada módulo tiene su archivo de extensiones, vacío en `main`: `ventas/ventas.extensions.ts`, `compras/compras.extensions.ts` e `inventario/inventario.extensions.ts` (reunidos en `client-extensions.ts`). La rama `cliente/<nombre>` declara ahí, por ruta de pantalla, qué suma: `headerLeft`/`headerRight`/`footerLeft`/`footerRight` (campos; tipos `text`, `date`, `number`, `textarea`, `select` con `options`, `warehouse`…), `lineColumns` (se agregan al final y se envían a SAP) y `listColumns`. Además de agregar, la extensión puede **ocultar** (`hideHeaderFields`, `hideLineColumns`, `hideListColumns`; en maestros `hideFields`, `hideListColumns`) y dejar de **solo lectura** (`readOnlyHeaderFields`, `readOnlyLineColumns`; en maestros `readOnlyFields`): lo oculto ni se muestra ni se envía; lo de solo lectura se ve pero no se edita y su valor sigue enviándose (columna `locked`, a diferencia de `readonly`, que no se envía). Los nombres son los de SAP (`CostingCode2`, `Indicator`…); un nombre inexistente, ocultar un campo/columna obligatorio, o una ruta inexistente o ajena al módulo lanzan un error al arrancar. El test «el proyecto base no usa campos de usuario» revisa `BASE_SCREENS`, no las extensiones. Verificado: el proxy `/api/sl` deja pasar nombres `U_*` en `$select`/`$orderby` y en el cuerpo (el error de un campo inexistente lo da SAP). Límite: una columna de línea de usuario con lista de valores válidos aún no tiene editor desplegable (solo texto/número). La alineación de una columna se declara con `align: 'left' | 'center' | 'right'` (listas y detalle; por defecto, derecha en las numéricas).
|
||||
|
||||
**Columnas de las ventanas de selección (cliente, artículo, almacén, impuesto):** son configurables por documento. En una pantalla del base (ventas/compras): `lookups: { item: [...], partner: [...], warehouse: [...], tax: [...] }` en el último parámetro de `marketing(...)` *sustituye* las columnas de esa lista (la primera columna es el código que se escribe en el campo). En la rama del cliente: `lookupColumns` en su archivo de extensiones *suma* columnas al final (por ejemplo `U_Marca` en artículos). El recurso, el filtro y el orden de cada lista no cambian; para los documentos de inventario o solicitudes se usa `withLookupColumns(layout, cols, 'replace' | 'add')`. Las columnas de almacén aplican también al almacén de la cabecera y al de origen de una transferencia. Los formularios con socio muestran `NumAtCard` y el RUT (`FederalTaxID`, solo lectura: lo completa SAP desde el socio); facturas, notas de crédito, entregas y recepciones (`FOLIO_PATHS` en `document-screens.ts`) añaden el folio tributario, que en el Service Layer es `FolioPrefixString` + `FolioNumber` (se edita como texto y se envía como número); las listas de facturas también los muestran. **Campos de contabilidad analítica y numeración (estándar):** cabecera de todos los documentos: `Series` (la fila «Número» lleva la lista de series del documento, pedida con la acción `SeriesService_GetDocumentSeries` del Service Layer —registrada en el proxy como acción de solo consulta—, filtrada por el subtipo de la pantalla; no se puede cambiar una vez creado; sin elegir, SAP usa la por defecto), `Indicator` (ventana de selección sobre `FactoringIndicators` —tabla OIDC; en tu SAP: 33 Factura, 39 Boleta; el recurso `Indicators` no existe en este Service Layer) y `Project` (lista de proyectos). Detalle de ventas y compras: `PriceAfterVAT` (el precio con IVA; solo se envía a SAP el que el usuario escribió, `Price` o `PriceAfterVAT`, nunca los dos), `CostingCode` a `CostingCode5` y `ProjectCode` (cada uno con su **ventana modal** de selección: reglas de distribución por dimensión y proyectos), `AccountCode` (plan de cuentas) y `BaseType`/`BaseEntry`/`BaseLine` (solo lectura: el documento de origen de una línea copiada). Inventario y solicitudes de compra llevan en el detalle solo las cinco dimensiones y el proyecto. `OBJECT_TYPES` (`document-objects.ts`) da el tipo de objeto de SAP de cada pantalla. El subtipo de las series de boleta (`IB`) y el resto de `SERIES_SUBTYPE` no está verificado contra SBODemoCL (solo hay series `--`).
|
||||
|
||||
**Movimientos de inventario:** cabecera con fecha de contabilización, fecha del documento (`TaxDate`), vencimiento (`DueDate`, solo traslados), `Reference2` y comentarios del asiento (`JournalMemo`); `Reference1` lo asigna SAP. Copiar de/a: la transferencia de stock se crea desde una solicitud de traslado (`COPY_FLOWS`); en las líneas de transferencia `BaseType` es un **enumerado de texto** de SAP (`InventoryTransferRequest`, `WarehouseTransfers`…; `STOCK_BASE_TYPES`, `hasBaseRef`), no el número de objeto de ventas y compras.
|
||||
|
||||
**Lotes y series (entradas, salidas y transferencias de stock):** si el artículo se gestiona por lote (`ManageBatchNumbers`) o por serie (`ManageSerialNumbers`; el maestro de artículos los trae con `extraFields` al elegirlo) la línea muestra un botón «Indicar lotes / series» (columna «Lotes / Series») que abre una ventana con una tabla editable: lotes = `BatchNumber`, `Quantity`, `ExpiryDate`, `ManufacturingDate`; series = `InternalSerialNumber`, `ManufacturerSerialNumber`, fechas (cada serie con `Quantity: 1`). Viajan dentro de la línea como `BatchNumbers` / `SerialNumbers`; solo se envían los campos propios (no los que SAP agrega al leer). Validación: los lotes deben sumar la cantidad de la línea y debe haber una serie por unidad. Al cambiar de artículo se descartan. Un documento contabilizado los muestra en solo lectura. Las solicitudes de traslado, las de compra y los documentos de ventas/compras no los llevan todavía (`layout.details`, `LINE_DETAILS` en `line-details.ts`). Probado: entrada de inventario 156 (1 unidad de `SU01` en el almacén 01, lote `LT-PRUEBA-01`). Pendiente: elegir de una lista los lotes existentes en una salida o transferencia (hoy se escribe el número; SAP valida que exista y haya saldo).
|
||||
|
||||
Flete hecho: fila «Flete» en el pie de ventas y compras (gasto adicional + importe; `freightPayload` lo guarda como `DocumentAdditionalExpenses`, reemplazando la colección; el catálogo `AdditionalExpenses` está registrado en el proxy; con varios gastos adicionales cargados en SAP solo se muestra). `freight: false` en `marketing(...)` lo quita de un documento. El toolbar del formulario ocupa todo el ancho del contenido (`<sbo-toolbar bleed>`). Falta: botón PDF. En ventas el flete falla en SBODemoCL ("Define the freight revenue account…"): el gasto adicional «Flete» solo tiene cuenta de gasto, no de ingresos (configuración de SAP, no del código); en compras funciona (orden de compra 2885).
|
||||
2. ~~Pantallas de maestros~~ — hechas (clientes, proveedores y artículos): `features/masters/`, con la misma idea que los documentos pero con clave de texto (`CardCode`, `ItemCode`). Cada pantalla declara su `MasterScreen` (`socios-negocio/socios.screens.ts`, `articulos/articulos.screens.ts`): columnas y filtros de la lista, `baseFilter` (clientes = `CardType eq 'cCustomer'`, proveedores = `cSupplier`; el tipo también se envía al crear con `fixed`), y un `MasterLayout` con código/nombre/estado arriba y los demás campos en **pestañas** (dos columnas, tipos de `HeaderField`: texto, número, fecha, área de texto, casilla `tYES/tNO`, lista de valores numérica o de texto, almacén). El código no se cambia una vez creado (`immutable`); «Inactivo» es `Frozen`. Rutas con `master-paths.ts` + `resolve` diferido; navegación primero/anterior/siguiente/último por clave de texto (`SlMasterApi`). Los campos por cliente van en `socios-negocio/socios.extensions.ts` y `articulos/articulos.extensions.ts` (vacíos en `main`; `header`, `tabs` por `id`, `newTabs`, `listColumns`, y también `hideFields`, `readOnlyFields`, `hideListColumns`; pestaña, campo o ruta inexistente = error). **Tablas en las pestañas de los maestros** (`MasterTab.collection`, `MasterCollection`; componente `sbo-rows-table`): clientes y proveedores tienen **Direcciones** (`BPAddresses`) y **Personas de contacto** (`ContactEmployees`); el artículo, **Precios** (`ItemPrices`) y **Stock por almacén** (`ItemWarehouseInfoCollection`, solo lectura). Cómo se envían (verificado contra SAP con un cliente y un artículo de prueba): `mode: 'replace'` (direcciones, contactos) manda la colección completa —las filas leídas con los cambios aplicados, para conservar sus campos de SAP (`RowNum`, `InternalCode`…), más las nuevas— con `B1S-ReplaceCollectionsOnPatch: true` (así también se puede quitar una fila, si SAP lo permite); `mode: 'merge'` (precios) manda **solo las filas cambiadas**, por `rowKey` (`PriceList`) y sin reemplazo, porque reenviar las demás listas con valores viejos impide que SAP recalcule las derivadas por factor. Las filas vacías (sin `identity`) no se guardan. Probado: cliente `TESTBP01` (dirección de envío añadida) y artículo `TESTITEM01` (precio de la lista 3), ambos de prueba en SBODemoCL. Pendiente en maestros: las extensiones de cliente aún no pueden agregar u ocultar columnas de estas tablas, ni cambiar el tipo de socio. Los catálogos `BusinessPartnerGroups` e `ItemGroups` están registrados en el proxy.
|
||||
3. ~~Pantalla web de permisos~~ — hecha (`/admin/permisos`).
|
||||
4. **Auditoría — en marcha** (`apps/api/src/audit/`, tabla `audit_log` en la misma SQLite): registra inicios de sesión (correctos, fallidos con su motivo —credenciales, usuario inactivo, demasiados intentos— y cierres), cada **creación y modificación** que pasa por el proxy `/api/sl` (recurso, clave, resultado `ok`/`error` con el mensaje de SAP, y **solo los nombres de los campos** y cuántas filas lleva cada colección: nunca valores ni contraseñas), las operaciones **denegadas** por permisos y los cambios de permisos de menú. Las lecturas y las acciones de solo consulta (series) no se auditan. `AuditService.record` nunca lanza (un fallo del registro no cambia la operación del usuario). Consulta solo para administradores: `GET /api/audit` (filtros usuario, acción, recurso, resultado, desde/hasta; 50 por página, máx. 200) y la pantalla `/admin/auditoria` (menú Administración, solo acceso total). Pendiente de afinar: retención (`purgeOlderThan` existe, falta programarlo y su variable de entorno), la IP detrás del proxy de desarrollo (todo llega como loopback; hace falta `xfwd` en el proxy y `trust proxy` solo cuando el backend no esté expuesto), exportar a CSV, y `nestjs-cls` para poner el usuario en cada línea del log.
|
||||
5. Migrar reportes/PDF vía API Gateway (módulo y cola aparte; no registrar credenciales de los `tools/` del mini-erp, traen contraseñas en claro).
|
||||
6. Revisar las vulnerabilidades de `npm audit` y definir una política para `U_WEB_PASS` (texto plano, claves cortas) antes de producción.
|
||||
7. Docker/despliegue (HTTPS tras Fortinet; sesiones web en memoria ⇒ un solo proceso).
|
||||
|
||||
## Flujo de trabajo por cliente (branches)
|
||||
|
||||
1. `main` es la versión base/genérica, sin personalizaciones de cliente.
|
||||
2. Cada cliente tiene su branch (`cliente/<nombre-cliente>`); ahí viven sus personalizaciones y fixes.
|
||||
3. Los cambios de una branch de cliente no se mezclan con `main` ni con otro cliente, salvo backport genérico decidido explícitamente.
|
||||
4. Antes de crear branches, mergear a `main`, o hacer merge/backport entre clientes, confirmar con el usuario. No hacer `git push` sin confirmación. Cada paso termina en un commit propio.
|
||||
Reference in new issue
Block a user