diff --git a/mini-backend/menu_permissions.json b/mini-backend/menu_permissions.json
index b4d6968..4a0d036 100644
--- a/mini-backend/menu_permissions.json
+++ b/mini-backend/menu_permissions.json
@@ -18,5 +18,8 @@
"/inventario/lotes",
"/ventas/*",
"/compras/*"
+ ],
+ "vania.barrios@tagumedica.com":[
+ "/inventario/lotes"
]
}
\ No newline at end of file
diff --git a/mini-backend/test/ApiGateway/ExportPDFData.yml b/mini-backend/tools/ApiGateway/ExportPDFData.yml
similarity index 100%
rename from mini-backend/test/ApiGateway/ExportPDFData.yml
rename to mini-backend/tools/ApiGateway/ExportPDFData.yml
diff --git a/mini-backend/test/ApiGateway/LoadAuthorizedCRList.yml b/mini-backend/tools/ApiGateway/LoadAuthorizedCRList.yml
similarity index 100%
rename from mini-backend/test/ApiGateway/LoadAuthorizedCRList.yml
rename to mini-backend/tools/ApiGateway/LoadAuthorizedCRList.yml
diff --git a/mini-backend/test/ApiGateway/LoadCR.yml b/mini-backend/tools/ApiGateway/LoadCR.yml
similarity index 100%
rename from mini-backend/test/ApiGateway/LoadCR.yml
rename to mini-backend/tools/ApiGateway/LoadCR.yml
diff --git a/mini-backend/test/ApiGateway/Login.yml b/mini-backend/tools/ApiGateway/Login.yml
similarity index 100%
rename from mini-backend/test/ApiGateway/Login.yml
rename to mini-backend/tools/ApiGateway/Login.yml
diff --git a/mini-backend/test/ApiGateway/opencollection.yml b/mini-backend/tools/ApiGateway/opencollection.yml
similarity index 100%
rename from mini-backend/test/ApiGateway/opencollection.yml
rename to mini-backend/tools/ApiGateway/opencollection.yml
diff --git a/mini-backend/test/apiSN/Login.yml b/mini-backend/tools/apiSN/Login.yml
similarity index 100%
rename from mini-backend/test/apiSN/Login.yml
rename to mini-backend/tools/apiSN/Login.yml
diff --git a/mini-backend/test/apiSN/opencollection.yml b/mini-backend/tools/apiSN/opencollection.yml
similarity index 100%
rename from mini-backend/test/apiSN/opencollection.yml
rename to mini-backend/tools/apiSN/opencollection.yml
diff --git a/mini-backend/test/backend/Login.yml b/mini-backend/tools/backend/Login.yml
similarity index 100%
rename from mini-backend/test/backend/Login.yml
rename to mini-backend/tools/backend/Login.yml
diff --git a/mini-backend/test/backend/Logout.yml b/mini-backend/tools/backend/Logout.yml
similarity index 100%
rename from mini-backend/test/backend/Logout.yml
rename to mini-backend/tools/backend/Logout.yml
diff --git a/mini-backend/test/backend/opencollection.yml b/mini-backend/tools/backend/opencollection.yml
similarity index 100%
rename from mini-backend/test/backend/opencollection.yml
rename to mini-backend/tools/backend/opencollection.yml
diff --git a/mini-backend/test/sap-aberio/BusinessPartners.yml b/mini-backend/tools/sap-aberio/BusinessPartners.yml
similarity index 100%
rename from mini-backend/test/sap-aberio/BusinessPartners.yml
rename to mini-backend/tools/sap-aberio/BusinessPartners.yml
diff --git a/mini-backend/test/sap-aberio/DeliveryNotes.yml b/mini-backend/tools/sap-aberio/DeliveryNotes.yml
similarity index 100%
rename from mini-backend/test/sap-aberio/DeliveryNotes.yml
rename to mini-backend/tools/sap-aberio/DeliveryNotes.yml
diff --git a/mini-backend/test/sap-aberio/Invoices.yml b/mini-backend/tools/sap-aberio/Invoices.yml
similarity index 100%
rename from mini-backend/test/sap-aberio/Invoices.yml
rename to mini-backend/tools/sap-aberio/Invoices.yml
diff --git a/mini-backend/test/sap-aberio/Items.yml b/mini-backend/tools/sap-aberio/Items.yml
similarity index 100%
rename from mini-backend/test/sap-aberio/Items.yml
rename to mini-backend/tools/sap-aberio/Items.yml
diff --git a/mini-backend/test/sap-aberio/Login.yml b/mini-backend/tools/sap-aberio/Login.yml
similarity index 100%
rename from mini-backend/test/sap-aberio/Login.yml
rename to mini-backend/tools/sap-aberio/Login.yml
diff --git a/mini-backend/test/sap-aberio/PurchaseInvoices.yml b/mini-backend/tools/sap-aberio/PurchaseInvoices.yml
similarity index 100%
rename from mini-backend/test/sap-aberio/PurchaseInvoices.yml
rename to mini-backend/tools/sap-aberio/PurchaseInvoices.yml
diff --git a/mini-backend/test/sap-aberio/opencollection.yml b/mini-backend/tools/sap-aberio/opencollection.yml
similarity index 100%
rename from mini-backend/test/sap-aberio/opencollection.yml
rename to mini-backend/tools/sap-aberio/opencollection.yml
diff --git a/mini-backend/test/sap-tagumedica/sl.http b/mini-backend/tools/sap-tagumedica/sl.http
similarity index 100%
rename from mini-backend/test/sap-tagumedica/sl.http
rename to mini-backend/tools/sap-tagumedica/sl.http
diff --git a/mini-frontend/docs/flujo-articulos.md b/mini-frontend/docs/flujo-articulos.md
index a2da49c..770d170 100644
--- a/mini-frontend/docs/flujo-articulos.md
+++ b/mini-frontend/docs/flujo-articulos.md
@@ -128,6 +128,8 @@ App.tsx (BrowserRouter)
## Campos del Formulario (nombres SAP, sin mapeo)
+El label de cada campo muestra el nombre técnico SAP en un tooltip (ícono ⓘ, prop `dbField`) en vez de mostrarlo dentro del propio texto del label — ver `FieldTooltip.tsx` y la misma convención en `flujo-lotes.md`.
+
| Campo SAP | Tipo | Componente | Notas |
|---|---|---|---|
| `ItemCode` | input text | FormattedField | obligatorio, deshabilitado implícitamente en edición (se usa como key de URL) |
diff --git a/mini-frontend/docs/flujo-documentos-marketing.md b/mini-frontend/docs/flujo-documentos-marketing.md
index 8400358..840dac9 100644
--- a/mini-frontend/docs/flujo-documentos-marketing.md
+++ b/mini-frontend/docs/flujo-documentos-marketing.md
@@ -69,12 +69,30 @@ Cada vista de mantenimiento (`Orders.tsx`, `Invoices.tsx`, `PurchaseOrders.tsx`,
| `titulo` | Encabezado visible del formulario | `"Factura de Ventas (OINV)"` |
| `headerExtraFields` | Campos de cabecera exclusivos de ese documento | Facturas: `FolioPrefixString`, `FolioNumber`, `GroupNum` |
| `lineColumns` | Columnas de la grilla de líneas, cada una con un `show(doc)` opcional para ocultar/mostrar según `DocType` | Compras oculta `ItemCode`/`Almacén` si `DocType` es de servicio |
-| `copyFromSources` | Documentos origen habilitados para el botón "📋 Copiar de" | Facturas de Venta: Orden de Venta y Entrega; Facturas de Proveedor: Orden de Compra |
+| `copyFromSources` | Documentos origen habilitados para el botón "Copiar de" (ícono de portapapeles) | Facturas de Venta: Orden de Venta y Entrega; Facturas de Proveedor: Orden de Compra |
| `retencionConfig` / `detraccionConfig` | Solo presentes en `PurchaseInvoices.tsx` — habilitan los bloques de Retención (3%/6%) y Detracción (SUNAT) en el resumen de totales | No se pasan en Órdenes ni en Facturas de Venta |
| `onBuscarSocio`, `onBuscarArticulos`, `onCellSearch` | Callbacks que abren los `ModalSearchTemplate` de Socios, Artículos, Almacén y Código de Impuesto | Cada vista instancia sus propios modales y decide qué endpoint/filtro usar |
El cálculo de totales (subtotal, impuesto 18% fijo, retención, detracción, total general) vive dentro del propio `MarketingDocumentForm`, no en las vistas.
+Los íconos de acción (búsqueda, copiar de, catálogo, eliminar línea) son SVG inline definidos en `src/components/ui/Icon.tsx` — no hay emojis ni dependencias externas de iconografía en el proyecto.
+
+### Campo técnico en tooltip, no en el label
+
+Cada `CampoConfig`/`HeaderExtraFieldConfig` de `genericHeaderFields` acepta un `dbField` (= su propio `name`, ya que ambos son literalmente el nombre del campo en `MarketingDocument`) que `FormattedField`/`SelectField` muestran como un ícono ⓘ junto al label; al pasar el mouse aparece el nombre técnico SAP (ej. `DocEntry`, `CardCode`). Antes el label concatenaba ambos textos ("Documento (DocEntry)"), lo que a veces partía el campo en dos líneas y deformaba la grilla — ver `FieldTooltip.tsx`.
+
+### Campos de solo lectura (`readOnly` / `readOnlyOnUpdate`)
+
+`CampoConfig` soporta dos flags que `MarketingDocumentForm` traduce a `mode="display"` (o `disabled` en los `SelectField`):
+
+| Campo | Flag | Comportamiento |
+|---|---|---|
+| `DocEntry` | `readOnly: true` | Nunca editable (ni al crear ni al actualizar) — lo asigna SAP |
+| `DocNum` | `readOnlyOnUpdate: true` | Editable al crear, bloqueado al actualizar |
+| `CardCode` | `readOnlyOnUpdate: true` | Editable al crear, bloqueado al actualizar; además oculta el botón de búsqueda de socio cuando está bloqueado |
+
+`readOnlyOnUpdate` se evalúa contra `isEditMode` (`!!docEntry`), definido dentro del propio `MarketingDocumentForm`.
+
## Flujo: Crear una Factura de Venta copiando una Orden
```mermaid
@@ -96,11 +114,11 @@ sequenceDiagram
Backend->>SAP: GET Currencies
SAP-->>Form: lista de monedas
- User->>Form: busca y selecciona Cliente (🔍)
+ User->>Form: busca y selecciona Cliente (ícono de lupa)
Form->>View: onBuscarSocio() abre ModalSearchTemplate(BusinessPartners)
View-->>Form: setDoc({CardCode, CardName})
- User->>Form: clic "📋 Copiar de" → "Orden de Venta"
+ User->>Form: clic "Copiar de" → "Orden de Venta"
Form->>Copy:
Copy->>API: GET /api/Orders?filtro por CardCode/BaseType
API->>Backend: fetch
@@ -143,11 +161,16 @@ App.tsx (BrowserRouter)
└── /mantenimiento → PurchaseInvoices.tsx → MarketingDocumentForm (+ Retención/Detracción)
MarketingDocumentForm.tsx
-├── FormattedField.tsx / SelectField.tsx (cabecera)
+├── FormattedField.tsx / SelectField.tsx (cabecera, con FieldTooltip por campo)
+├── PrimaryButton.tsx / SecondaryButton.tsx (submit / cancelar)
+├── Icon.tsx (íconos SVG: buscar, copiar, catálogo, eliminar)
+├── PageLoader.tsx ("Cargando documento..." mientras hace fetch)
├── CopyFromModal.tsx (botón "Copiar de")
└── (por vista) ModalSearchTemplate.tsx ×4 (Socios, Artículos, Almacén, Código de Impuesto)
```
+Cada vista de mantenimiento (`Orders`, `Invoices`, `PurchaseOrders`, `PurchaseInvoices`) se carga con `React.lazy()` desde `App.tsx`: el JS de un módulo no se descarga hasta que el usuario navega a él (ver `docs/index.md`).
+
## Archivos Clave
| Archivo | Ruta | Propósito |
@@ -158,8 +181,11 @@ MarketingDocumentForm.tsx
| `OrdersList.tsx`, `InvoicesList.tsx`, `PurchaseOrdersList.tsx`, `PurchaseInvoicesList.tsx` | `src/views/ventas/`, `src/views/compras/` | Listados vía `ListViewTemplate`, con `StatusBadge` por estado del documento |
| `CopyFromModal.tsx` | `src/components/ui/` | Modal para copiar líneas desde un documento base (Orden→Entrega→Factura) |
| `ModalSearchTemplate.tsx` | `src/components/ui/` | Modal de búsqueda genérico reutilizado para Socios, Artículos, Almacenes, Códigos de Impuesto |
-| `sapMarketing.ts` | `src/types/` | `MarketingDocument`, `DocumentLine`, `DocumentConfigProps`, `CopyFromSource`, `LineColumnConfig` |
+| `sapMarketing.ts` | `src/types/` | `MarketingDocument`, `DocumentLine`, `DocumentConfigProps`, `CopyFromSource`, `LineColumnConfig` (`key`/`name` tipados como `keyof DocumentLine`/`keyof MarketingDocument`, no `string` suelto) |
| `sunat.ts` | `src/utils/` | `DETRACCION_CODIGOS` (porcentajes SUNAT usados en Facturas de Proveedor) |
+| `Icon.tsx` | `src/components/ui/` | Registro de íconos SVG inline (reemplaza los emojis que se usaban antes en botones y menú) |
+| `FieldTooltip.tsx` | `src/components/ui/` | Ícono ⓘ con el nombre técnico SAP del campo, usado por `FormattedField`/`SelectField` |
+| `errors.ts` | `src/utils/` | `getErrorMessage(err, fallback)` — evita `catch (err: any)` en cada formulario |
| `sales.py` / `purchases.py` | `mini-backend/routers/` | Endpoints REST que proxean a SAP Service Layer |
## Hallazgos (código tal cual está hoy, sin corregir)
diff --git a/mini-frontend/docs/flujo-lotes.md b/mini-frontend/docs/flujo-lotes.md
index b172532..17cb126 100644
--- a/mini-frontend/docs/flujo-lotes.md
+++ b/mini-frontend/docs/flujo-lotes.md
@@ -124,6 +124,8 @@ App.tsx (BrowserRouter)
## Mapeo de Nombres SAP → Friendly
+El label visible ya no concatena el nombre técnico (antes: "Estado de Aprobación (U_SIS_ESTLOT)", partiéndose en 2 líneas y deformando la grilla). Ahora el label muestra solo el nombre amigable y el campo SAP aparece en un tooltip (ícono ⓘ) vía el prop `dbField` de `FormattedField`/`SelectField` — ver `FieldTooltip.tsx`.
+
| Campo SAP | Nombre Amigable | Tipo | Componente |
|---|---|---|---|
| `Batch` | `Batch` | display | FormattedField |
diff --git a/mini-frontend/docs/flujo-socios-negocio.md b/mini-frontend/docs/flujo-socios-negocio.md
index 088fb6b..18f5dd0 100644
--- a/mini-frontend/docs/flujo-socios-negocio.md
+++ b/mini-frontend/docs/flujo-socios-negocio.md
@@ -143,7 +143,7 @@ App.tsx (BrowserRouter)
## Campos del Formulario
-A diferencia del módulo de Lotes, acá **no hay mapeo de nombres SAP → amigable**: el formulario usa los nombres de campo de SAP directamente como `name` de cada input (`CardCode`, `CardName`, `FederalTaxID`, `Phone1`, `EmailAddress`).
+A diferencia del módulo de Lotes, acá **no hay mapeo de nombres SAP → amigable**: el formulario usa los nombres de campo de SAP directamente como `name` de cada input (`CardCode`, `CardName`, `FederalTaxID`, `Phone1`, `EmailAddress`). El label visible sí es un texto amigable en español; el nombre técnico SAP se muestra en un tooltip (ícono ⓘ, componente `FieldTooltip.tsx`) en vez de ir concatenado al label.
| Campo SAP | Pestaña | Tipo | Notas |
| ------------------------- | ------------ | ------------------------------ | ------------------------------------------------------------- |
diff --git a/mini-frontend/docs/index.md b/mini-frontend/docs/index.md
index 77ab8c8..5b525db 100644
--- a/mini-frontend/docs/index.md
+++ b/mini-frontend/docs/index.md
@@ -18,3 +18,14 @@ SPA en React 19 + TypeScript + Vite + Tailwind CSS + React Router. Consume la AP
- Todos los documentos siguen el mismo formato: diagrama de arquitectura (mermaid), diagrama de secuencia del flujo principal, árbol de componentes y tabla de "Archivos Clave".
- Ver también `mini-erp/CLAUDE.md` (raíz del repo) para la arquitectura general del monorepo (`mini-backend` + `mini-frontend`) y el flujo de trabajo por cliente.
- Ver `mini-backend/docs/` para la documentación del backend (API, autenticación, proxy SAP, logging).
+
+## Rendimiento: carga por ruta (code splitting)
+
+`App.tsx` no importa las vistas de forma directa: cada una se carga con `React.lazy()`, agrupadas por módulo (Inventario, Socios, Ventas, Compras, Informes), y `}>` envuelve todo el árbol de rutas. Así el usuario solo descarga el JS de la pantalla que realmente visita, en vez de un único bundle con las 20+ vistas del ERP. `SapLogin` y `MainLayout` (el "shell" de la app) se mantienen fuera de esto porque se necesitan en casi toda visita.
+
+## Componentes UI compartidos relevantes
+
+- `Icon.tsx` (`src/components/ui/`) — registro de íconos SVG inline (buscar, copiar, editar, eliminar, etc.). No hay emojis ni librerías de iconografía externas en el proyecto.
+- `FieldTooltip.tsx` — ícono ⓘ junto al label de un campo, muestra el nombre técnico SAP (prop `dbField` de `FormattedField`/`SelectField`) sin concatenarlo al texto del label.
+- `PageLoader.tsx` — estado de carga a pantalla completa, reutilizado como fallback de `Suspense` y en los formularios de detalle mientras traen sus datos.
+- `PrimaryButton.tsx` / `SecondaryButton.tsx` — botones de acción con dos tamaños (`size="sm"` para barras de herramientas, `size="md"` para pies de formulario).
diff --git a/mini-frontend/index.html b/mini-frontend/index.html
index 53c1b4a..d02f1ba 100644
--- a/mini-frontend/index.html
+++ b/mini-frontend/index.html
@@ -4,7 +4,7 @@
-
sap-frontend
+ Mini ERP · SAP Business One
diff --git a/mini-frontend/src/App.tsx b/mini-frontend/src/App.tsx
index 1d4ab74..30bc069 100644
--- a/mini-frontend/src/App.tsx
+++ b/mini-frontend/src/App.tsx
@@ -1,44 +1,93 @@
+import { lazy, Suspense } from "react";
import type { ReactNode } from "react";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
-import SapLogin from "./components/SapLogin";
+import SapLogin from "./components/SapLoginForm";
import MainLayout from "./layout/MainLayout";
+import PageLoader from "./components/ui/PageLoader";
import { getToken } from "./utils/api";
-import CustomersList from "./views/socios/CustomersList";
-import SuppliersList from "./views/socios/SuppliersList";
-import Supplier from "./views/socios/Suppliers";
-import Customer from "./views/socios/Customers";
-import InvoicesList from "./views/ventas/InvoicesList";
-import Invoices from "./views/ventas/Invoices";
-import OrdersList from "./views/ventas/OrdersList";
-import Orders from "./views/ventas/Orders";
-import PurchaseOrdersList from "./views/compras/PurchaseOrdersList";
-import PurchaseOrders from "./views/compras/PurchaseOrders";
-import PurchaseInvoicesList from "./views/compras/PurchaseInvoicesList";
-import PurchaseInvoices from "./views/compras/PurchaseInvoices";
-import ItemsList from "./views/inventario/ItemsList";
-import Items from "./views/inventario/Items";
-import BatchNumberDetailsList from "./views/inventario/BatchNumberDetailsList";
-import BatchNumberDetails from "./views/inventario/BatchNumberDetails";
-import Dashboard from "./views/Dashboard";
-import ModuleReportsList from "./views/informes/ModuleReportsList";
-import ReportRunner from "./components/report/ReportRunner";
-import CrystalReportsList from "./views/reportes/CrystalReportsList";
-import CrystalReportRunner from "./components/report/CrystalReportRunner";
-import HtmlReportsList from "./views/reportes/HtmlReportsList";
-import HtmlReportRunner from "./components/report/HtmlReportRunner";
-const Delivery = () => (
-
-
Entregas / Guías
-
+// Cada vista se carga de forma diferida (code splitting por ruta): así el
+// usuario solo descarga el código de la pantalla que realmente visita, en
+// vez de recibir en un único bundle el JS de los 20+ módulos del ERP.
+// SapLogin y MainLayout quedan fuera de esto porque son el "shell" que se
+// necesita en (casi) toda visita, sin importar el módulo.
+
+// 0. Dashboard
+const Dashboard = lazy(() => import("./views/DashboardView"));
+
+// 1. Inventario
+const ItemsList = lazy(() => import("./views/inventario/ItemsList"));
+const Items = lazy(() => import("./views/inventario/ItemsView"));
+const BatchNumberDetailsList = lazy(
+ () => import("./views/inventario/BatchNumberDetailsList"),
+);
+const BatchNumberDetails = lazy(
+ () => import("./views/inventario/BatchNumberDetailsView"),
);
-const Receiving = () => (
-