- 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 <noreply@anthropic.com>
196 lines
11 KiB
Markdown
196 lines
11 KiB
Markdown
# Flujo de Procesamiento — Documentos de Marketing (Ventas / Compras)
|
||
|
||
Cubre los documentos comerciales de SAP Business One que la app gestiona: Órdenes de Venta, Facturas de Venta, Órdenes de Compra y Facturas de Proveedor. Los cuatro comparten un único formulario genérico (`MarketingDocumentForm.tsx`) parametrizado por vista.
|
||
|
||
## Arquitectura General
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "Frontend React (sap-frontend)"
|
||
OL[OrdersList.tsx]
|
||
O[Orders.tsx]
|
||
IL[InvoicesList.tsx]
|
||
I[Invoices.tsx]
|
||
POL[PurchaseOrdersList.tsx]
|
||
PO[PurchaseOrders.tsx]
|
||
PIL[PurchaseInvoicesList.tsx]
|
||
PI[PurchaseInvoices.tsx]
|
||
FORM[MarketingDocumentForm.tsx]
|
||
COPY[CopyFromModal.tsx]
|
||
SEARCH[ModalSearchTemplate.tsx]
|
||
BADGE[StatusBadge.tsx]
|
||
LVT[ListViewTemplate.tsx]
|
||
TYPES[types/sapMarketing.ts]
|
||
API[api.ts]
|
||
end
|
||
|
||
subgraph "Backend FastAPI (mini-backend)"
|
||
SALES[routers/sales.py]
|
||
PURCH[routers/purchases.py]
|
||
CORE[core/sap.py]
|
||
end
|
||
|
||
subgraph "SAP Business One"
|
||
SL[Service Layer<br/>Orders / Invoices / PurchaseOrders / PurchaseInvoices]
|
||
end
|
||
|
||
OL --> LVT
|
||
IL --> LVT
|
||
POL --> LVT
|
||
PIL --> LVT
|
||
LVT --> BADGE
|
||
|
||
O --> FORM
|
||
I --> FORM
|
||
PO --> FORM
|
||
PI --> FORM
|
||
FORM --> COPY
|
||
FORM --> SEARCH
|
||
FORM --> TYPES
|
||
FORM --> API
|
||
|
||
API -->|GET/POST/PATCH| SALES
|
||
API -->|GET/POST/PATCH| PURCH
|
||
SALES --> CORE
|
||
PURCH --> CORE
|
||
CORE --> SL
|
||
```
|
||
|
||
Cada vista de mantenimiento (`Orders.tsx`, `Invoices.tsx`, `PurchaseOrders.tsx`, `PurchaseInvoices.tsx`) es una capa delgada: mantiene el estado del documento (`doc`, tipado como `MarketingDocument` en `types/sapMarketing.ts`) y le pasa a `MarketingDocumentForm` la configuración específica de ese tipo de documento — el componente de formulario en sí no sabe si está editando una orden o una factura, solo renderiza lo que recibe por props.
|
||
|
||
## Cómo un solo formulario sirve a los 4 documentos
|
||
|
||
`MarketingDocumentForm` recibe por props todo lo que varía entre documentos:
|
||
|
||
| Prop | Qué controla | Ejemplo |
|
||
|---|---|---|
|
||
| `endpoint` | Endpoint del backend a consultar/enviar (`GET/POST/PATCH /api/{endpoint}`) | `"Orders"`, `"Invoices"`, `"PurchaseOrders"`, `"PurchaseInvoices"` |
|
||
| `tipoDocumento` | Etiqueta tipada (`OrdenVenta`, `FacturaVenta`, `OrdenCompra`, `FacturaProveedor`) declarada en `DocumentConfigProps` | — no se usa dentro de la lógica del propio formulario en el código leído; parece ser solo metadata para quien lo invoca |
|
||
| `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" (í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
|
||
sequenceDiagram
|
||
actor User as Usuario
|
||
participant List as InvoicesList
|
||
participant View as Invoices.tsx
|
||
participant Form as MarketingDocumentForm
|
||
participant Copy as CopyFromModal
|
||
participant API as api.ts
|
||
participant Backend as backend sales.py
|
||
participant SAP as SAP Service Layer
|
||
|
||
User->>List: /ventas/facturas → "Nueva Factura"
|
||
List->>View: navigate("/ventas/facturas/mantenimiento")
|
||
View->>Form: <MarketingDocumentForm endpoint="Invoices" copyFromSources=[Orden, Entrega] />
|
||
Form->>API: GET /api/Currencies
|
||
API->>Backend: fetch
|
||
Backend->>SAP: GET Currencies
|
||
SAP-->>Form: lista de monedas
|
||
|
||
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"
|
||
Form->>Copy: <CopyFromModal source={Orden} cardCode docType />
|
||
Copy->>API: GET /api/Orders?filtro por CardCode/BaseType
|
||
API->>Backend: fetch
|
||
Backend->>SAP: GET Orders
|
||
SAP-->>Copy: documentos abiertos del cliente
|
||
User->>Copy: selecciona una orden
|
||
Copy-->>Form: onConfirm(lines, sourceLabel)
|
||
Note over Form: Si ya hay líneas, confirm() pregunta reemplazar o agregar
|
||
Form->>Form: setDoc({DocumentLines: lines}) — cada línea trae BaseType/BaseEntry/BaseLine
|
||
|
||
User->>Form: clic "Crear Documento"
|
||
Form->>API: POST /api/Invoices (doc + DocumentLines, sin BaseRef)
|
||
API->>Backend: fetch POST
|
||
Backend->>SAP: POST Invoices (con líneas BaseEntry → SAP cierra la orden origen)
|
||
SAP-->>Backend: 201 Created
|
||
Backend-->>Form: ok
|
||
Form-->>User: "Documento creado exitosamente en SAP"
|
||
```
|
||
|
||
El mismo patrón aplica a Facturas de Proveedor copiando desde Órdenes de Compra, solo cambian `endpoint` y `copyFromSources`.
|
||
|
||
## Árbol de Componentes
|
||
|
||
```
|
||
App.tsx (BrowserRouter)
|
||
└── / (ProtectedRoute → MainLayout)
|
||
├── /ventas
|
||
│ ├── /ordenes → OrdersList.tsx → ListViewTemplate → StatusBadge
|
||
│ ├── /ordenes/mantenimiento → Orders.tsx → MarketingDocumentForm
|
||
│ ├── /entrega → stub "Entregas / Guías" (sin vista real, ver Hallazgos)
|
||
│ └── /facturas
|
||
│ ├── (lista) → InvoicesList.tsx → ListViewTemplate → StatusBadge
|
||
│ └── /mantenimiento → Invoices.tsx → MarketingDocumentForm
|
||
└── /compras
|
||
├── /ordenes → PurchaseOrdersList.tsx → ListViewTemplate → StatusBadge
|
||
├── /ordenes/mantenimiento → PurchaseOrders.tsx → MarketingDocumentForm
|
||
├── /recepcion → stub "Recepciones de Compra" (sin vista real, ver Hallazgos)
|
||
└── /facturas
|
||
├── (lista) → PurchaseInvoicesList.tsx → ListViewTemplate → StatusBadge
|
||
└── /mantenimiento → PurchaseInvoices.tsx → MarketingDocumentForm (+ Retención/Detracción)
|
||
|
||
MarketingDocumentForm.tsx
|
||
├── 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 |
|
||
|---|---|---|
|
||
| `MarketingDocumentForm.tsx` | `src/components/` | Formulario genérico: cabecera, grilla de líneas, totales, copiar-de, submit GET/POST/PATCH |
|
||
| `Orders.tsx` / `Invoices.tsx` | `src/views/ventas/` | Configuran el formulario para Órdenes y Facturas de venta |
|
||
| `PurchaseOrders.tsx` / `PurchaseInvoices.tsx` | `src/views/compras/` | Configuran el formulario para Compras; Facturas agrega retención/detracción |
|
||
| `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` (`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)
|
||
|
||
- **`ventas/entrega` y `compras/recepcion` son stubs**: en `App.tsx` son componentes en línea que solo muestran un título ("Entregas / Guías" / "Recepciones de Compra"), sin listado ni formulario real todavía.
|
||
- **`DeliveryNotesList.tsx` existe pero no está enrutado**: el archivo `src/views/ventas/DeliveryNotesList.tsx` está escrito pero ninguna ruta en `App.tsx` lo importa o lo usa — no es alcanzable desde la navegación actual.
|
||
- El endpoint `PurchaseDeliveryNotes` (documentado en `mini-backend/docs/api-reference.md`) tampoco tiene vista de frontend correspondiente todavía.
|