9.5 KiB
Flujo de Procesamiento — Socios de Negocio (Clientes / Proveedores)
Arquitectura General
graph TB
subgraph "Frontend React (sap-frontend)"
RT[Router App.tsx]
CLIST[CustomersList.tsx]
SLIST[SuppliersList.tsx]
CVIEW[Customers.tsx]
SVIEW[Suppliers.tsx]
BPFORM[BusinessPartnerForm.tsx]
LIST_TMPL[ListViewTemplate.tsx]
UI_TABLE[DenseTable.tsx]
API[api.ts]
end
subgraph "Backend FastAPI (mini-backend)"
ROUTER[routers/business_partners.py<br/>make_crud_router]
CORE[core/crud_router.py]
SAP_CORE[core/sap.py]
SUNAT[Sunat/BusinessPartners/DocumentType]
end
subgraph "SAP Business One"
SL[Service Layer<br/>BusinessPartners]
end
RT --> CLIST
RT --> SLIST
RT --> CVIEW
RT --> SVIEW
CLIST --> LIST_TMPL
SLIST --> LIST_TMPL
CVIEW --> BPFORM
SVIEW --> BPFORM
LIST_TMPL --> UI_TABLE
LIST_TMPL --> API
BPFORM --> API
API -->|GET/POST/PATCH BusinessPartners| ROUTER
API -->|GET DocumentType| SUNAT
ROUTER --> CORE
CORE --> SAP_CORE
SAP_CORE -->|B1SESSION| SL
Un solo formulario para Clientes y Proveedores
A diferencia de otros módulos, acá no hay un componente por tipo de socio: BusinessPartnerForm.tsx es un único formulario genérico parametrizado por props, y Customers.tsx / Suppliers.tsx son wrappers finos que solo le pasan configuración distinta:
| Prop | Customers.tsx |
Suppliers.tsx |
|---|---|---|
cardType |
"cCustomer" |
"cSupplier" |
entityLabel |
"Cliente" |
"Proveedor" |
listPath |
/socios/clientes |
/socios/proveedores |
codePlaceholder |
Ej. C00001 |
Ej. P00001 |
extraFields |
[] (sin campos extra hoy) |
U_EXX_TIPODOCU (Tipo de Documento SUNAT, select) |
La diferenciación entre "es cliente" o "es proveedor" para el propio dato de SAP la hace el campo CardType (cCustomer / cSupplier), que viaja fijo en el body del POST/PATCH según qué wrapper se use — el formulario en sí no lo deja editar.
El mecanismo extraFields (tipo ExtraFieldConfig[], definido en BusinessPartnerForm.tsx) permite agregar campos exclusivos de un tipo de socio sin tocar el formulario base: cada entrada define name, label, type (text | email | select), y opcionalmente loadOptions (una función async que trae las opciones del backend). Hoy el único caso real es el campo U_EXX_TIPODOCU en Proveedores, que carga sus opciones desde GET /api/Sunat/BusinessPartners/DocumentType (catálogo SUNAT expuesto vía SAP).
Flujo Vista de Lista → Editar Socio (caso Clientes)
sequenceDiagram
actor User as Usuario
participant Router as App.tsx
participant ListView as CustomersList
participant LVT as ListViewTemplate
participant DTable as DenseTable
participant DetailView as Customers.tsx
participant Form as BusinessPartnerForm
participant API as api.ts
participant Backend as backend business_partners.py
participant SAP as SAP Service Layer
User->>Router: /socios/clientes
Router->>ListView: render
ListView->>LVT: <ListViewTemplate endpoint="BusinessPartners" baseFilter="CardType eq 'cCustomer'" .../>
LVT->>API: GET /api/BusinessPartners?...&$filter=CardType eq 'cCustomer'
API->>Backend: fetch con X-Session-Token
Backend->>SAP: GET BusinessPartners (con B1SESSION)
SAP-->>Backend: JSON con datos
Backend-->>API: JSON response
API-->>LVT: json.value[]
LVT->>DTable: render rows
DTable-->>User: tabla de clientes
User->>DTable: clic en editar (CardCode "C00001")
DTable->>Router: navigate("/socios/clientes/mantenimiento?code=C00001")
Router->>DetailView: render con searchParams.code
DetailView->>Form: <BusinessPartnerForm cardType="cCustomer" .../>
Form->>API: GET /api/BusinessPartners/C00001
API->>Backend: fetch
Backend->>SAP: GET BusinessPartners('C00001')
SAP-->>Backend: {CardCode, CardName, FederalTaxID, BPAddresses[], ...}
Backend-->>API: JSON raw SAP
API-->>Form: data
Form->>Form: setFormData(data) — sin mapeo de nombres, usa los campos SAP tal cual
Form-->>User: formulario con pestañas "General" / "Direcciones"
User->>Form: edita campos y clic "Actualizar Cliente"
Form->>API: PATCH /api/BusinessPartners/C00001
Note over Form,API: body: {CardName, FederalTaxID, Phone1, EmailAddress, BPAddresses[], ...extraFields}
API->>Backend: fetch PATCH
Backend->>SAP: PATCH BusinessPartners('C00001')
SAP-->>Backend: 204 No Content
Backend-->>API: 204
API-->>Form: ok
Form-->>User: "Cliente actualizado correctamente"
El flujo de Proveedores es idéntico, cambiando el baseFilter a CardType eq 'cSupplier' en SuppliersList.tsx y usando Suppliers.tsx como vista de detalle.
Para un socio nuevo (isEditMode = false), el submit hace POST /api/BusinessPartners en vez de PATCH, y tras la respuesta exitosa navega de vuelta al listado (listPath) después de 1.5s.
Árbol de Componentes
App.tsx (BrowserRouter)
└── / (ProtectedRoute → MainLayout)
└── /socios
├── /clientes
│ └── CustomersList.tsx
│ └── ListViewTemplate.tsx
│ ├── SearchInput.tsx
│ ├── PrimaryButton.tsx
│ ├── DenseTable.tsx
│ └── PaginationBar.tsx
├── /clientes/mantenimiento
│ └── Customers.tsx
│ └── BusinessPartnerForm.tsx (cardType="cCustomer")
├── /proveedores
│ └── SuppliersList.tsx
│ └── ListViewTemplate.tsx (mismo árbol que CustomersList)
└── /proveedores/mantenimiento
└── Suppliers.tsx
└── BusinessPartnerForm.tsx (cardType="cSupplier", extraFields=[U_EXX_TIPODOCU])
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).
| Campo SAP | Pestaña | Tipo | Notas |
|---|---|---|---|
CardCode |
General | input text (solo lectura en edición) | Llave primaria en SAP; no editable una vez creado el socio |
CardName |
General | input text | Razón social / nombre |
CardType |
General | fijo, no editable en UI | cCustomer o cSupplier según el wrapper usado |
FederalTaxID |
General | input text | RUC / documento de identidad |
Phone1 |
General | input text | Teléfono principal |
EmailAddress |
General | input email | Correo |
U_EXX_TIPODOCU |
General | select (dinámico, solo Proveedores) | Catálogo SUNAT vía /api/Sunat/BusinessPartners/DocumentType |
BPAddresses[].Street, City, County, GlobalLocationNumber |
Direcciones | input text | Dos direcciones por defecto: bo_BillTo (Facturación) y bo_ShipTo (Despacho) |
Archivos Clave
| Archivo | Ruta | Propósito |
|---|---|---|
BusinessPartnerForm.tsx |
src/components/ |
Formulario genérico único para Clientes y Proveedores, parametrizable |
Customers.tsx |
src/views/socios/ |
Wrapper que configura BusinessPartnerForm para cCustomer |
Suppliers.tsx |
src/views/socios/ |
Wrapper que configura BusinessPartnerForm para cSupplier + campo SUNAT |
CustomersList.tsx |
src/views/socios/ |
Lista de clientes, filtra CardType eq 'cCustomer' |
SuppliersList.tsx |
src/views/socios/ |
Lista de proveedores, filtra CardType eq 'cSupplier' |
ListViewTemplate.tsx |
src/components/ui/ |
Template genérico de listado con paginación/búsqueda/filtros |
api.ts |
src/utils/ |
Cliente HTTP con sesión, token, timeout |
business_partners.py |
mini-backend/routers/ |
Router CRUD genérico (make_crud_router("BusinessPartners", key_field="CardCode")) |
crud_router.py |
mini-backend/core/ |
Factory que genera GET/POST/PATCH proxificados hacia SAP |
sap.py |
mini-backend/core/ |
Proxy universal a SAP Service Layer |