Files
mini-erp/mini-frontend/docs/flujo-documentos-marketing.md
T
GeorgeandClaude Sonnet 5 f58f4292c3 Backport de mejoras genéricas de frontend (desde cliente/tagumedica)
- 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>
2026-09-28 14:28:29 -05:00

11 KiB
Raw Blame History

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

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

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.