Registra lo decidido (login, usuario SAP compartido, concurrencia 1 medida, TLS, proxy con registro, TaxCode, listas de documentos), cómo verificar cambios de interfaz con seguridad, el estado actual y el orden de lo pendiente (paso 9: formulario de documentos). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
8.3 KiB
basic-erp
ERP base para SAP Business One (Service Layer). Migración del mini-erp (React + FastAPI, en /Users/george/Documents/Desarrollo/React/mini-erp) a NestJS + Angular, ambos en TypeScript. Sirve de base para crear una rama por cliente.
Monorepo npm workspaces:
apps/api— NestJS 12 (ESM): proxy hacia el Service Layer, login, sesión SAP compartida, permisos de menú (SQLite). Verapps/api/README.md.apps/web— Angular 22 (standalone, zoneless, señales): componentes reutilizables estilo SAP Business One. Verapps/web/README.md.libs/sap-types— tipos y utilidades compartidos por api y web (MENU, evaluador de permisos, tipos de auth/SL). Se compila enpostinstall.tools/— mockupoferta_venta.html(ejemplo: tiene algunos campos no estándar, pero se conserva la idea).
Reglas del proyecto base
- Solo campos estándar de SAP Business One. Ningún campo de usuario (
U_*) salvoU_WEB_USER/U_WEB_PASSdeEmployeesInfo, usados para el login web. - Los formularios se construyen con componentes reutilizables y configuración declarativa; no se duplica markup por documento.
- Aspecto por variables CSS (temas). Tema por defecto: claro, con la paleta del mockup. Esquinas contenidas (
--radius*, como SBO). - La fila de un formulario es etiqueta + uno o varios controles (input, select, número, fecha, botón); la cabecera tiene como máximo 2 columnas.
- Impuesto de las líneas:
TaxCodecon el maestroSalesTaxCodes(verificado en el Service Layer;VatGroupsno tieneRate). Un cliente conVatGrouplo cambia en su rama.
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 ~287 kB, 81 kB comprimido: 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 compilalibs/sap-types.npm run dev:api(:3000) ·npm run dev:web(:4200, proxy/api) ·npm run build·npm test.- 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 informasessionTimeoutMinutes); tokenAuthorization: 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_FILEopcional (certificado autofirmado de SAP o CA de firewall);VERIFY_SSL=truepor defecto y no se puede apagar en producción.fortinet.cerno 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 cabecerasB1S-*de lista blanca. Nunca registrarEmployeesInfo(tieneU_WEB_PASS).InventoryRequestsno existe: esInventoryTransferRequests. - Permisos de menú en SQLite (
SQLITE_PATH), patrones*,/ventas/*,/ventas/ordenes;ADMIN_WEB_USERSgarantiza 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 undefaultValue), filtro de fecha siempre rango [desde – hasta], multi-selección con chips, búsqueda numérica por igualdad. Sin filtro baseDocumentSubType(excluiría notas de débito, etc.). 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 en apps/web/src/app/features/documents/document-screens.ts (17 documentos: ventas, compras, inventario). 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_browsersdebe mostrar un navegador). Si no conecta, usar Chrome headless por CDP. - No escribir contraseñas en formularios: obtener el token con
curla/api/auth/login(la clave de prueba se lee deEmployeesInfosolo para eso, nunca se imprime ni se guarda) e inyectar solo el token enlocalStorage(erp_session). - Al terminar: cerrar la sesión de prueba (
POST /api/auth/logout), borrarerp_session/erp_themedel 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): hasta ahora solo se probaron contra un SAP simulado. Pedir autorización al usuario antes de crear o modificar datos reales, aunque sea la base demo.
- 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).
Pendiente, en orden:
- Paso 9 — formulario de documentos en
nuevo/:docEntry: cargar el documento, Crear / Actualizar (control de concurrencia con elodata.etagde SAP víaIf-Match), navegación primero/anterior/siguiente/último, socio de negocio, totales con las tasas deSalesTaxCodes, solo lectura si está cerrado/cancelado, "Copiar de/a". Empezar por Órdenes de venta y replicar. Quitar campos vacíos al guardar;B1S-ReplaceCollectionsOnPatchpara las líneas. Pendiente decidir/ pedir autorización para probar escrituras reales. - Pantallas de maestros (
Items,BusinessPartners: Clientes/Proveedores, Artículos), hoy "no disponible". - Pantalla web de administración de permisos (el API ya existe:
/api/permissions). - Auditoría (
audit_logen SQLite; hoy solo líneasAUDITen el log) ynestjs-clspara el usuario en los logs. - 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). - Revisar las vulnerabilidades de
npm audity definir una política paraU_WEB_PASS(texto plano, claves cortas) antes de producción. - Docker/despliegue (HTTPS tras Fortinet; sesiones web en memoria ⇒ un solo proceso).
Flujo de trabajo por cliente (branches)
maines la versión base/genérica, sin personalizaciones de cliente.- Cada cliente tiene su branch (
cliente/<nombre-cliente>); ahí viven sus personalizaciones y fixes. - Los cambios de una branch de cliente no se mezclan con
mainni con otro cliente, salvo backport genérico decidido explícitamente. - Antes de crear branches, mergear a
main, o hacer merge/backport entre clientes, confirmar con el usuario. No hacergit pushsin confirmación. Cada paso termina en un commit propio.