# Flujo de Procesamiento — Socios de Negocio (Clientes / Proveedores) ## Arquitectura General ```mermaid 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
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
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) ```mermaid 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: 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: 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`). 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 | | ------------------------- | ------------ | ------------------------------ | ------------------------------------------------------------- | | `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 |