From f58f4292c38cd454a69301f2e85802cea55bfba9 Mon Sep 17 00:00:00 2001 From: George Date: Mon, 28 Sep 2026 14:28:29 -0500 Subject: [PATCH] =?UTF-8?q?Backport=20de=20mejoras=20gen=C3=A9ricas=20de?= =?UTF-8?q?=20frontend=20(desde=20cliente/tagumedica)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nuevo Icon.tsx (reemplaza emojis en toda la app), FieldTooltip, ConfirmModal, PageLoader, SecondaryButton, hook useSapCatalog, utils/errors (getErrorMessage). - App.tsx: code-splitting con lazy()+Suspense+PageLoader para todas las rutas. - MainLayout.tsx: menú usa Icon.tsx en vez de emojis. - FormattedField/SelectField: soporte de dbField+tooltip, multiline. - DenseTable/FilterBar/ListViewTemplate: onClear, mejoras varias. - PrimaryButton: variante de tamaño sm/md. - Renombres *View.tsx para uniformar el naming de vistas de detalle/mantenimiento (Dashboard, Items, BatchNumberDetails, Customers, Suppliers, Invoices, Orders, PurchaseOrders, PurchaseInvoices) y SapLogin -> SapLoginForm. - BatchNumberDetailsList: filtros y columna de descripción de artículo. - Configs de reportes e iconos de módulos actualizados a Icon.tsx. - Ajustes de paleta en los 4 temas CSS. - Docs actualizadas. Se excluyó explícitamente del backport (queda solo en cliente/tagumedica, no es reusable sin adaptar a los UDFs propios de cada cliente): - Vistas InmovilizarLotesView, LiberarLotesView, ConfirmarLiberacionLoteView y sus rutas/ítems de menú. - types/sapInventoryLock.ts (contrato con los UDFs U_SIS_MOVINV/U_EXX_TIPOOPER/U_SIS_TIPDESP/U_SIS_MOTALM de Tagumedica). - En BatchForm.tsx: los 3 campos U_SIS_MOTEST/U_SIS_COMEST/U_SIS_ACCTOM (Motivo/Comentario/Acción), manteniendo el resto de mejoras genéricas del archivo y reactivando el campo estándar "Detalles/Observaciones" que en tagumedica había quedado comentado sin justificación genérica. Co-Authored-By: Claude Sonnet 5 --- mini-frontend/docs/flujo-articulos.md | 2 + .../docs/flujo-documentos-marketing.md | 36 ++- mini-frontend/docs/flujo-lotes.md | 2 + mini-frontend/docs/flujo-socios-negocio.md | 2 +- mini-frontend/docs/index.md | 11 + mini-frontend/index.html | 2 +- mini-frontend/src/App.tsx | 252 ++++++++++++------ mini-frontend/src/components/BatchForm.tsx | 64 +++-- .../src/components/BusinessPartnerForm.tsx | 29 +- mini-frontend/src/components/ItemForm.tsx | 68 ++--- .../src/components/MarketingDocumentForm.tsx | 149 ++++++----- .../{SapLogin.tsx => SapLoginForm.tsx} | 2 +- .../components/report/CrystalReportRunner.tsx | 9 +- .../components/report/HtmlReportRunner.tsx | 9 +- .../components/report/ReportParameterForm.tsx | 5 +- .../src/components/report/ReportRunner.tsx | 23 +- .../src/components/ui/ConfirmModal.tsx | 61 +++++ .../src/components/ui/DenseTable.tsx | 29 +- .../src/components/ui/FieldTooltip.tsx | 24 ++ mini-frontend/src/components/ui/FilterBar.tsx | 19 +- .../src/components/ui/FormattedField.tsx | 53 +++- mini-frontend/src/components/ui/Icon.tsx | 143 ++++++++++ .../src/components/ui/ListViewTemplate.tsx | 9 + .../src/components/ui/PageLoader.tsx | 19 ++ .../src/components/ui/PaginationBar.tsx | 4 +- .../src/components/ui/PrimaryButton.tsx | 13 +- .../src/components/ui/SearchInput.tsx | 2 +- .../src/components/ui/SecondaryButton.tsx | 34 +++ .../src/components/ui/SelectField.tsx | 6 +- .../src/components/ui/StatusBadge.tsx | 6 +- mini-frontend/src/config/crystalReports.ts | 2 +- mini-frontend/src/config/htmlReports.ts | 2 +- mini-frontend/src/config/reports.ts | 30 +-- mini-frontend/src/hooks/useSapCatalog.ts | 58 ++++ mini-frontend/src/layout/MainLayout.tsx | 19 +- .../src/styles/themes/enterprise.css | 10 +- .../src/styles/themes/flat-light.css | 10 +- mini-frontend/src/styles/themes/ocean.css | 10 +- mini-frontend/src/styles/themes/slate.css | 10 +- mini-frontend/src/types/sapMarketing.ts | 17 +- mini-frontend/src/utils/errors.ts | 8 + mini-frontend/src/utils/statusColors.ts | 14 +- .../{Dashboard.tsx => DashboardView.tsx} | 2 +- ...eInvoices.tsx => PurchaseInvoicesView.tsx} | 2 +- ...chaseOrders.tsx => PurchaseOrdersView.tsx} | 2 +- .../src/views/informes/ModuleReportsList.tsx | 13 +- .../inventario/BatchNumberDetailsList.tsx | 36 ++- ...Details.tsx => BatchNumberDetailsView.tsx} | 2 +- .../inventario/{Items.tsx => ItemsView.tsx} | 2 +- .../src/views/reportes/CrystalReportsList.tsx | 13 +- .../src/views/reportes/HtmlReportsList.tsx | 13 +- .../{Customers.tsx => CustomersView.tsx} | 2 +- .../{Suppliers.tsx => SuppliersView.tsx} | 2 +- .../ventas/{Invoices.tsx => InvoicesView.tsx} | 2 +- .../ventas/{Orders.tsx => OrdersView.tsx} | 2 +- 55 files changed, 1014 insertions(+), 356 deletions(-) rename mini-frontend/src/components/{SapLogin.tsx => SapLoginForm.tsx} (99%) create mode 100644 mini-frontend/src/components/ui/ConfirmModal.tsx create mode 100644 mini-frontend/src/components/ui/FieldTooltip.tsx create mode 100644 mini-frontend/src/components/ui/Icon.tsx create mode 100644 mini-frontend/src/components/ui/PageLoader.tsx create mode 100644 mini-frontend/src/components/ui/SecondaryButton.tsx create mode 100644 mini-frontend/src/hooks/useSapCatalog.ts create mode 100644 mini-frontend/src/utils/errors.ts rename mini-frontend/src/views/{Dashboard.tsx => DashboardView.tsx} (99%) rename mini-frontend/src/views/compras/{PurchaseInvoices.tsx => PurchaseInvoicesView.tsx} (99%) rename mini-frontend/src/views/compras/{PurchaseOrders.tsx => PurchaseOrdersView.tsx} (99%) rename mini-frontend/src/views/inventario/{BatchNumberDetails.tsx => BatchNumberDetailsView.tsx} (89%) rename mini-frontend/src/views/inventario/{Items.tsx => ItemsView.tsx} (89%) rename mini-frontend/src/views/socios/{Customers.tsx => CustomersView.tsx} (93%) rename mini-frontend/src/views/socios/{Suppliers.tsx => SuppliersView.tsx} (96%) rename mini-frontend/src/views/ventas/{Invoices.tsx => InvoicesView.tsx} (99%) rename mini-frontend/src/views/ventas/{Orders.tsx => OrdersView.tsx} (99%) 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 = () => ( -
-

Recepciones de Compra

-
+// 2. Socios de Negocios +const CustomersList = lazy(() => import("./views/socios/CustomersList")); +const Customer = lazy(() => import("./views/socios/CustomersView")); +const SuppliersList = lazy(() => import("./views/socios/SuppliersList")); +const Supplier = lazy(() => import("./views/socios/SuppliersView")); + +// 3. Ventas +const OrdersList = lazy(() => import("./views/ventas/OrdersList")); +const Orders = lazy(() => import("./views/ventas/OrdersView")); +const InvoicesList = lazy(() => import("./views/ventas/InvoicesList")); +const Invoices = lazy(() => import("./views/ventas/InvoicesView")); + +// 4. Compras +const PurchaseOrdersList = lazy( + () => import("./views/compras/PurchaseOrdersList"), +); +const PurchaseOrders = lazy(() => import("./views/compras/PurchaseOrdersView")); +const PurchaseInvoicesList = lazy( + () => import("./views/compras/PurchaseInvoicesList"), +); +const PurchaseInvoices = lazy( + () => import("./views/compras/PurchaseInvoicesView"), ); +// 5. Informes / Reportes (nativos, Crystal y HTML) +const ModuleReportsList = lazy( + () => import("./views/informes/ModuleReportsList"), +); +const ReportRunner = lazy(() => import("./components/report/ReportRunner")); +const CrystalReportsList = lazy( + () => import("./views/reportes/CrystalReportsList"), +); +const CrystalReportRunner = lazy( + () => import("./components/report/CrystalReportRunner"), +); +const HtmlReportsList = lazy(() => import("./views/reportes/HtmlReportsList")); +const HtmlReportRunner = lazy( + () => import("./components/report/HtmlReportRunner"), +); + +// Módulos aún sin formulario propio: solo muestran un título de marcador +// de posición hasta que se implemente su pantalla real. +function Delivery() { + return ( +
+

+ Entregas / Guías +

+
+ ); +} + +function Receiving() { + return ( +
+

+ Recepciones de Compra +

+
+ ); +} + +// Bloquea el acceso a todo lo que cuelga de "/" si no hay sesión SAP activa. function ProtectedRoute({ children }: { children: ReactNode }) { if (!getToken()) { return ; @@ -49,55 +98,102 @@ function ProtectedRoute({ children }: { children: ReactNode }) { export default function App() { return ( - - } /> + {/* Suspense envuelve todas las rutas: mientras el chunk de la vista + lazy() se descarga, se muestra un único loader consistente en + vez de que cada pantalla defina el suyo. */} + }> + + } /> - - - - } - > } - /> - } /> - {/* 1. Inventario */} - } /> - } /> - } /> - } /> - {/* 2. Socios de Negocios */} - } /> - } /> - } /> - } /> - {/* 3. Ventas */} - } /> - } /> - } /> - } /> - } /> - {/* 4. Compras */} - } /> - } /> - } /> - } /> - } /> - {/* 5. Informes */} - } /> - } /> - } /> - } /> - } /> - } /> - + path="/" + element={ + + + + } + > + } /> - } /> - + } /> + + {/* 1. Inventario */} + } /> + } + /> + } + /> + } + /> + + {/* 2. Socios de Negocios */} + } /> + } + /> + } /> + } + /> + + {/* 3. Ventas */} + } /> + } /> + } /> + } /> + } + /> + + {/* 4. Compras */} + } /> + } + /> + } /> + } /> + } + /> + + {/* 5. Informes */} + } /> + } + /> + } + /> + } + /> + } + /> + } + /> + + + } /> + +
); } diff --git a/mini-frontend/src/components/BatchForm.tsx b/mini-frontend/src/components/BatchForm.tsx index 2bafa63..77611eb 100644 --- a/mini-frontend/src/components/BatchForm.tsx +++ b/mini-frontend/src/components/BatchForm.tsx @@ -3,12 +3,17 @@ import type { ChangeEvent, FormEvent } from "react"; import { useNavigate } from "react-router-dom"; import type { BatchNumberDetail } from "../types/sapBatch"; import { API_URL, apiFetch } from "../utils/api"; +import { getErrorMessage } from "../utils/errors"; import { BATCH_STATUS_APROBACION, BATCH_STATUS_LIBERACION, } from "../utils/enums"; import FormattedField from "./ui/FormattedField"; import SelectField from "./ui/SelectField"; +import PrimaryButton from "./ui/PrimaryButton"; +import SecondaryButton from "./ui/SecondaryButton"; +import PageLoader from "./ui/PageLoader"; +import FieldTooltip from "./ui/FieldTooltip"; interface BatchFormProps { batchId: string; @@ -53,8 +58,8 @@ export default function BatchForm({ batchId, backPath }: BatchFormProps) { if (!response.ok) throw new Error("Error al cargar el lote"); const data = await response.json(); setBatch(mapSapToFriendly(data)); - } catch (err: any) { - setError(err.message || "Error al cargar el lote"); + } catch (err: unknown) { + setError(getErrorMessage(err, "Error al cargar el lote")); } finally { setFetchLoading(false); } @@ -104,21 +109,15 @@ export default function BatchForm({ batchId, backPath }: BatchFormProps) { if (!response.ok) throw new Error("Error al actualizar el lote"); setSuccess("Lote actualizado exitosamente"); - } catch (err: any) { - setError(err.message || "Error de conexión"); + } catch (err: unknown) { + setError(getErrorMessage(err, "Error de conexión")); } finally { setLoading(false); } }; if (fetchLoading) { - return ( -
-
- Cargando lote... -
-
- ); + return ; } return ( @@ -156,21 +155,25 @@ export default function BatchForm({ batchId, backPath }: BatchFormProps) {