primer commit
This commit is contained in:
1 parent
ab3c613165
commit
b2fb4e2d0a
18 files changed
+1291
-277
No files matched your search
@@ -0,0 +1,57 @@
|
||||
# Dashboard
|
||||
|
||||
Página de inicio del ERP (`/dashboard`). Muestra KPIs, accesos rápidos a las operaciones más comunes y un feed de actividad reciente, organizados en secciones con grillas de cards.
|
||||
|
||||
**Importante:** actualmente todo el contenido de `Dashboard.tsx` es **estático/hardcodeado** en el propio componente (arrays `kpiCards`, `actionCards`, `newsCards`, `activityItems`). No hay ningún fetch a `api.ts` ni al backend — los valores de ventas, compras, clientes e inventario son de ejemplo, no datos reales de SAP. La sección "Noticias y guías" está deshabilitada en código (`{false && (...)}`), por lo que no se renderiza aunque el array `newsCards` exista.
|
||||
|
||||
## Árbol de Componentes
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
DASH[Dashboard.tsx]
|
||||
SEC[DashboardSection.tsx]
|
||||
GRID[DashboardGrid.tsx]
|
||||
CARD[DashboardCard.tsx]
|
||||
KPI[DashboardKpiCard.tsx]
|
||||
ACT[DashboardActionCard.tsx]
|
||||
NEWS[DashboardNewsCard.tsx]
|
||||
|
||||
DASH -->|"KPI"| SEC
|
||||
DASH -->|"Accesos rápidos"| SEC
|
||||
DASH -->|"Noticias y guías (deshabilitada)"| SEC
|
||||
DASH -->|"Actividad reciente (bloque propio, sin card)"| ACTIVITY[div de actividad inline]
|
||||
|
||||
SEC --> GRID
|
||||
GRID --> CARD
|
||||
CARD -->|type: kpi| KPI
|
||||
CARD -->|type: action| ACT
|
||||
CARD -->|type: news| NEWS
|
||||
```
|
||||
|
||||
`DashboardCard` es un dispatcher: según la prop `type` (`"kpi" | "action" | "news"`) delega el render en `DashboardKpiCard`, `DashboardActionCard` o `DashboardNewsCard`. El bloque "Actividad reciente" no usa `DashboardCard`: es una lista renderizada directamente en `Dashboard.tsx`.
|
||||
|
||||
## Tipos de card
|
||||
|
||||
| Card | Props principales | Comportamiento |
|
||||
|---|---|---|
|
||||
| `DashboardKpiCard` | `title`, `icon` (componente de `lucide-react`), `value`, `subtitle`, `action?: {label, to}` | Muestra un valor numérico/texto grande. Si tiene `action`, renderiza un botón que navega con `useNavigate()` a la ruta `to`. |
|
||||
| `DashboardActionCard` | `title`, `icon`, `to` | Card clickeable completa (ícono + título) que navega a `to` con `useNavigate()`. Tiene hover que cambia el fondo a `--color-primary-active`. |
|
||||
| `DashboardNewsCard` | `image`, `title`, `description` | Card con imagen de fondo (`backgroundImage`) + texto. Actualmente usa URLs externas de `picsum.photos` como placeholder. No implementada en pantalla porque su sección está deshabilitada. |
|
||||
|
||||
Todas las cards usan variables CSS del tema (`--color-surface`, `--color-border`, `--color-primary`, `--color-text-muted`, etc.) vía `style` inline, no clases de Tailwind.
|
||||
|
||||
## Archivos Clave
|
||||
|
||||
| Archivo | Ruta | Propósito |
|
||||
|---|---|---|
|
||||
| `Dashboard.tsx` | `src/views/` | Vista principal; define los datos hardcodeados de KPIs, accesos rápidos, noticias y actividad, y arma las secciones |
|
||||
| `DashboardSection.tsx` | `src/components/dashboard/` | Título de sección (`h2`) + contenedor de su contenido |
|
||||
| `DashboardGrid.tsx` | `src/components/dashboard/` | Grid CSS responsivo (`auto-fill`, `minmax(260px, 1fr)`) para las cards |
|
||||
| `DashboardCard.tsx` | `src/components/dashboard/` | Dispatcher que elige la card concreta según `type` |
|
||||
| `DashboardKpiCard.tsx` | `src/components/dashboard/` | Card de indicador numérico con botón de acción opcional |
|
||||
| `DashboardActionCard.tsx` | `src/components/dashboard/` | Card de acceso rápido, navega al hacer clic |
|
||||
| `DashboardNewsCard.tsx` | `src/components/dashboard/` | Card de noticia con imagen (actualmente sin uso, sección deshabilitada) |
|
||||
|
||||
## Pendiente / a tener en cuenta
|
||||
|
||||
Si se decide conectar el dashboard a datos reales, haría falta: (1) reemplazar los arrays estáticos por llamadas a `api.ts` hacia endpoints del backend que hoy no existen para este propósito (no hay un endpoint de KPIs agregados en `mini-backend`), y (2) decidir si se reactiva la sección de noticias y de dónde saldría el contenido (hoy usa imágenes externas de `picsum.photos`, no aptas para producción).
|
||||
@@ -0,0 +1,160 @@
|
||||
# Flujo de Procesamiento — Datos Maestros de Artículos
|
||||
|
||||
## Arquitectura General
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Frontend React (sap-frontend)"
|
||||
RT[Router App.tsx]
|
||||
LIST[ItemsList.tsx]
|
||||
LIST_TMPL[ListViewTemplate.tsx]
|
||||
VISTA[Items.tsx]
|
||||
FORM[ItemForm.tsx]
|
||||
UI_FF[FormattedField.tsx]
|
||||
UI_SEL[SelectField.tsx]
|
||||
UI_TABLE[DenseTable.tsx]
|
||||
API[api.ts]
|
||||
ENUMS[enums.ts]
|
||||
TYPES[types/sapItems.ts]
|
||||
end
|
||||
|
||||
subgraph "Backend FastAPI (mini-backend)"
|
||||
ROUTER[routers/items.py<br/>make_crud_router]
|
||||
CORE[core/crud_router.py]
|
||||
SAPCORE[core/sap.py<br/>forward_to_sapb1]
|
||||
end
|
||||
|
||||
subgraph "SAP Business One"
|
||||
SL[Service Layer<br/>Items / ItemGroups]
|
||||
end
|
||||
|
||||
RT --> LIST
|
||||
RT --> VISTA
|
||||
LIST --> LIST_TMPL
|
||||
LIST_TMPL --> UI_TABLE
|
||||
LIST_TMPL --> API
|
||||
VISTA --> FORM
|
||||
FORM --> UI_FF
|
||||
FORM --> UI_SEL
|
||||
FORM --> API
|
||||
FORM --> ENUMS
|
||||
FORM --> TYPES
|
||||
|
||||
API -->|GET/POST/PATCH| ROUTER
|
||||
ROUTER --> CORE
|
||||
CORE --> SAPCORE
|
||||
SAPCORE -->|B1SESSION| SL
|
||||
```
|
||||
|
||||
**Diferencia clave respecto al flujo de Lotes:** `ItemForm.tsx` no hace mapeo de nombres SAP→amigable (no existe un `mapSapToFriendly`). El estado del formulario (`SapItem`, en `types/sapItems.ts`) usa directamente los nombres de campo de SAP (`ItemCode`, `ItemName`, `ItemsGroupCode`, etc.), tanto para mostrar como para enviar al backend. Tampoco hay tabla de mapeo de nombres en esta sección por ese motivo.
|
||||
|
||||
En el backend, `routers/items.py` no tiene lógica propia: es una instancia de la factory genérica `make_crud_router("Items", key_field="ItemCode", key_quoted=True)` definida en `core/crud_router.py` (ver `mini-backend/docs/architecture.md`), que arma las cuatro rutas CRUD y reenvía a SAP vía `core/sap.py`.
|
||||
|
||||
## Flujo Vista de Lista → Editar/Crear Artículo
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor User as Usuario
|
||||
participant Router as App.tsx
|
||||
participant ListView as ItemsList
|
||||
participant LVT as ListViewTemplate
|
||||
participant DTable as DenseTable
|
||||
participant DetailView as Items
|
||||
participant Form as ItemForm
|
||||
participant API as api.ts
|
||||
participant Backend as backend items.py
|
||||
participant SAP as SAP Service Layer
|
||||
|
||||
User->>Router: /inventario/articulos
|
||||
Router->>ListView: render
|
||||
ListView->>API: GET /api/ItemGroups (para mapear ItemsGroupCode → nombre en la columna "Grupo")
|
||||
ListView->>LVT: <ListViewTemplate endpoint="Items" baseFilter="AssetItem eq 'tNO'" .../>
|
||||
LVT->>API: GET /api/Items?skip=0&top=50&fields=ItemCode,ItemName,...
|
||||
API->>Backend: fetch con X-Session-Token
|
||||
Backend->>SAP: GET Items (con B1SESSION)
|
||||
SAP-->>Backend: JSON con datos
|
||||
Backend-->>API: JSON response
|
||||
API-->>LVT: json.value[]
|
||||
LVT->>DTable: render rows
|
||||
DTable-->>User: tabla con artículos (filtros por Grupo y Clase de artículo)
|
||||
|
||||
User->>DTable: clic en editar (ItemCode "A0001")
|
||||
DTable->>Router: navigate("/inventario/articulos/mantenimiento?code=A0001")
|
||||
Router->>DetailView: render con searchParams.code
|
||||
DetailView->>Form: <ItemForm itemCode="A0001" backPath="/inventario/articulos" />
|
||||
Form->>API: GET /api/ItemGroups (para poblar el SelectField de grupo)
|
||||
Form->>API: GET /api/Items/A0001
|
||||
API->>Backend: fetch
|
||||
Backend->>SAP: GET Items('A0001')
|
||||
SAP-->>Backend: {ItemCode, ItemName, ItemsGroupCode, ItemType, ...}
|
||||
Backend-->>API: JSON raw SAP
|
||||
API-->>Form: data
|
||||
Form->>Form: setItem(data) (sin mapeo de nombres)
|
||||
Form-->>User: formulario con valores cargados
|
||||
|
||||
User->>Form: edita campos y clic "Actualizar Artículo"
|
||||
Form->>API: PATCH /api/Items/A0001
|
||||
Note over Form,API: body: {ItemName, BarCode, ItemsGroupCode, ItemType, ...}
|
||||
API->>Backend: fetch PATCH
|
||||
Backend->>SAP: PATCH Items('A0001')
|
||||
SAP-->>Backend: 204 No Content
|
||||
Backend-->>API: 204
|
||||
API-->>Form: ok
|
||||
Form-->>User: "Artículo actualizado exitosamente"
|
||||
|
||||
Note over User,SAP: Si itemCode no viene en la URL (alta), el botón dice<br/>"Crear Artículo" y el submit hace POST /api/Items en vez de PATCH.
|
||||
```
|
||||
|
||||
## Árbol de Componentes
|
||||
|
||||
```
|
||||
App.tsx (BrowserRouter)
|
||||
└── / (ProtectedRoute → MainLayout)
|
||||
└── /inventario
|
||||
├── /articulos
|
||||
│ └── ItemsList.tsx
|
||||
│ └── ListViewTemplate.tsx
|
||||
│ ├── SearchInput.tsx
|
||||
│ ├── PrimaryButton.tsx
|
||||
│ ├── DenseTable.tsx
|
||||
│ │ └── formatValue() [formatters.ts]
|
||||
│ └── PaginationBar.tsx
|
||||
└── /articulos/mantenimiento
|
||||
└── Items.tsx
|
||||
└── ItemForm.tsx
|
||||
├── FormattedField.tsx (x6)
|
||||
└── SelectField.tsx (x3: ItemsGroupCode, ItemType, CostAccountingMethod)
|
||||
```
|
||||
|
||||
## Campos del Formulario (nombres SAP, sin mapeo)
|
||||
|
||||
| Campo SAP | Tipo | Componente | Notas |
|
||||
|---|---|---|---|
|
||||
| `ItemCode` | input text | FormattedField | obligatorio, deshabilitado implícitamente en edición (se usa como key de URL) |
|
||||
| `ItemName` | input text | FormattedField | obligatorio |
|
||||
| `BarCode` | input text | FormattedField | |
|
||||
| `ItemsGroupCode` | select | SelectField | opciones cargadas desde `GET /api/ItemGroups` |
|
||||
| `ItemType` | select | SelectField | opciones estáticas `ITEM_TYPE` (`utils/enums.ts`) |
|
||||
| `CostAccountingMethod` | select | SelectField | opciones estáticas `GL_METHOD` (`utils/enums.ts`) |
|
||||
| `SalesUnit` | input text | FormattedField | |
|
||||
| `PurchaseUnit` | input text | FormattedField | |
|
||||
| `InventoryUOM` | input text | FormattedField | |
|
||||
| `AvgStdPrice` | currency, solo lectura | FormattedField (`mode="display"`) | |
|
||||
| `PurchaseItem`, `InventoryItem`, `ManageSerialNumbers`, `ManageBatchNumbers`, `Valid`, `Frozen` | checkbox | input nativo | mapean a `"tYES"`/`"tNO"` vía `toCheck`/`fromCheck` |
|
||||
|
||||
## Archivos Clave
|
||||
|
||||
| Archivo | Ruta | Propósito |
|
||||
|---|---|---|
|
||||
| `Items.tsx` | `src/views/inventario/` | Vista detalle, extrae `code` de la URL y renderiza `ItemForm` (sirve tanto para alta como edición) |
|
||||
| `ItemForm.tsx` | `src/components/` | Formulario alta/edición, fetch + POST/PATCH sin mapeo de nombres |
|
||||
| `ItemsList.tsx` | `src/views/inventario/` | Lista con filtros por grupo de artículos y clase de artículo, columnas con formato de moneda |
|
||||
| `SelectField.tsx` | `src/components/ui/` | Select reutilizable |
|
||||
| `FormattedField.tsx` | `src/components/ui/` | Campo display/input con formato |
|
||||
| `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 |
|
||||
| `enums.ts` | `src/utils/` | Opciones estáticas `ITEM_TYPE`, `GL_METHOD` |
|
||||
| `sapItems.ts` | `src/types/` | Interfaz TypeScript `SapItem` |
|
||||
| `items.py` | `mini-backend/routers/` | Instancia de `make_crud_router` para `Items` (GET lista, GET por código, POST, PATCH) |
|
||||
| `crud_router.py` | `mini-backend/core/` | Factory genérica de routers CRUD reutilizada por `items.py` y otros routers |
|
||||
| `sap.py` | `mini-backend/core/` | Proxy universal a SAP Service Layer |
|
||||
@@ -0,0 +1,169 @@
|
||||
# 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" | 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.
|
||||
|
||||
## 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 (🔍)
|
||||
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)
|
||||
├── CopyFromModal.tsx (botón "Copiar de")
|
||||
└── (por vista) ModalSearchTemplate.tsx ×4 (Socios, Artículos, Almacén, Código de Impuesto)
|
||||
```
|
||||
|
||||
## 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` |
|
||||
| `sunat.ts` | `src/utils/` | `DETRACCION_CODIGOS` (porcentajes SUNAT usados en Facturas de Proveedor) |
|
||||
| `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.
|
||||
@@ -18,7 +18,7 @@ graph TB
|
||||
TYPES[types/sapBatch.ts]
|
||||
end
|
||||
|
||||
subgraph "Backend FastAPI (backend-sn)"
|
||||
subgraph "Backend FastAPI (mini-backend)"
|
||||
ROUTER[routers/batch.py]
|
||||
CORE[core/sap.py<br/>forward_to_sapb1]
|
||||
end
|
||||
@@ -156,5 +156,5 @@ App.tsx (BrowserRouter)
|
||||
| `api.ts` | `src/utils/` | Cliente HTTP con sesión, token, timeout |
|
||||
| `enums.ts` | `src/utils/` | Mapeos enum SAP (BATCH_STATUS_APROB, etc.) |
|
||||
| `sapBatch.ts` | `src/types/` | Interfaz TypeScript BatchNumberDetail |
|
||||
| `batch.py` | `backend-sn/routers/` | Rutas FastAPI GET/PATCH BatchNumberDetails |
|
||||
| `sap.py` | `backend-sn/core/` | Proxy universal a SAP Service Layer |
|
||||
| `batch.py` | `mini-backend/routers/` | Rutas FastAPI GET/PATCH BatchNumberDetails |
|
||||
| `sap.py` | `mini-backend/core/` | Proxy universal a SAP Service Layer |
|
||||
@@ -0,0 +1,172 @@
|
||||
# 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<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)
|
||||
|
||||
```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: <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 |
|
||||
@@ -0,0 +1,20 @@
|
||||
# Frontend mini-erp — Documentación del Proyecto
|
||||
|
||||
SPA en React 19 + TypeScript + Vite + Tailwind CSS + React Router. Consume la API del backend (`mini-backend`) para operar sobre SAP Business One: socios de negocio, artículos, documentos de venta/compra, lotes y reportes.
|
||||
|
||||
## Documentos
|
||||
|
||||
| Documento | Descripción |
|
||||
| ----------------------------------------------------------------- | ------------------------------------------------------------------- |
|
||||
| [Dashboard](dashboard.md) | Pantalla de inicio: KPIs, accesos rápidos y noticias (hoy estático) |
|
||||
| [Socios de Negocio](flujo-socios-negocio.md) | Clientes y proveedores, formulario compartido |
|
||||
| [Artículos](flujo-articulos.md) | Alta y edición de artículos de inventario |
|
||||
| [Lotes](flujo-lotes.md) | Aprobación/liberación de lotes (BatchNumberDetails) |
|
||||
| [Documentos de Marketing](flujo-documentos-marketing.md) | Órdenes/facturas de venta y compra sobre un formulario común |
|
||||
| [Reportes](reportes.md) | Los tres sistemas de reportes: HTML, Crystal e Informes |
|
||||
|
||||
## Convenciones
|
||||
|
||||
- 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).
|
||||
@@ -0,0 +1,187 @@
|
||||
# Módulo de Reportes
|
||||
|
||||
El frontend expone **tres sistemas de reportes distintos**, con configuración, rutas y componentes propios cada uno. No son variantes del mismo flujo: apuntan a integraciones distintas del lado del backend, y uno de ellos (`informes`) no tiene backend real detrás todavía.
|
||||
|
||||
| Sistema | Ruta base | Config | Componente runner | Backend real |
|
||||
|---|---|---|---|---|
|
||||
| **Reportes HTML** | `/reportes-html/:module` | `config/htmlReports.ts` → `htmlReportsConfig` | `HtmlReportRunner.tsx` | `mini-backend/routers/reports.py` (Jinja2 + WeasyPrint, datos de HANA) — **sí implementado** |
|
||||
| **Reportes Crystal** | `/reportes-crystal/:module` | `config/crystalReports.ts` → `crystalReportsConfig` | `CrystalReportRunner.tsx` | `mini-backend/routers/apigateway.py` (SAP B1 API Gateway / Crystal Reports Server) — **sí implementado** |
|
||||
| **Informes** | `/informes/:module` | `config/reports.ts` → `reportsConfig` | `ReportRunner.tsx` | Ninguno confirmado — ver nota abajo |
|
||||
|
||||
### Nota sobre `informes` (`ReportRunner.tsx` + `reportsConfig`)
|
||||
|
||||
`reportsConfig` (en `config/reports.ts`) define reportes como "Estado de Cuenta", "Saldos de Clientes", "Ventas por Artículo", "Kardex de Artículos", etc., cada uno con un `endpoint` del tipo `/api/reports/socios/estado-cuenta`, `/api/reports/inventario/kardex`, etc. El único endpoint que `mini-backend/routers/reports.py` expone bajo `/api/reports/` es `POST /api/reports/stickers`. Ninguno de los endpoints que `reportsConfig` referencia existe en el backend actual. Esto sugiere que `ReportRunner.tsx` y `reportsConfig` son un scaffold frontend para reportes planeados a futuro, no un flujo funcional hoy — cualquier reporte del módulo "Informes" fallará al generar hasta que se implemente su endpoint correspondiente en el backend.
|
||||
|
||||
## Arquitectura General
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Listados (elegir reporte)"
|
||||
RHL[HtmlReportsList.tsx]
|
||||
RCL[CrystalReportsList.tsx]
|
||||
MRL[ModuleReportsList.tsx]
|
||||
end
|
||||
|
||||
subgraph "Runners (parámetros + ejecución)"
|
||||
HRR[HtmlReportRunner.tsx]
|
||||
CRR[CrystalReportRunner.tsx]
|
||||
RR[ReportRunner.tsx]
|
||||
RPF[ReportParameterForm.tsx]
|
||||
RRV[ReportResultsView.tsx]
|
||||
MST[ModalSearchTemplate.tsx<br/>búsqueda F4]
|
||||
end
|
||||
|
||||
subgraph "Config"
|
||||
CFG_H[htmlReportsConfig]
|
||||
CFG_C[crystalReportsConfig]
|
||||
CFG_R[reportsConfig]
|
||||
end
|
||||
|
||||
subgraph "Backend FastAPI (mini-backend)"
|
||||
REP[routers/reports.py<br/>POST /api/reports/stickers]
|
||||
APIGW[routers/apigateway.py<br/>GET/POST /api/apigateway/reports/*]
|
||||
HANA[core/hana.py<br/>hana_connection]
|
||||
AGW_CORE[core/api_gateway.py<br/>forward_to_api_gateway]
|
||||
end
|
||||
|
||||
subgraph "Externo"
|
||||
HANADB[(HANA DB)]
|
||||
SAPGW[SAP B1 API Gateway<br/>rs/v1 Crystal Reports Server]
|
||||
end
|
||||
|
||||
RHL --> CFG_H --> HRR
|
||||
RCL --> CFG_C --> CRR
|
||||
MRL --> CFG_R --> RR
|
||||
|
||||
HRR --> RPF
|
||||
CRR --> RPF
|
||||
RR --> RPF
|
||||
RR --> RRV
|
||||
HRR -.f4-modal.-> MST
|
||||
CRR -.f4-modal.-> MST
|
||||
|
||||
HRR -->|POST /api/reports/:endpoint| REP
|
||||
REP --> HANA --> HANADB
|
||||
|
||||
CRR -->|POST /api/apigateway/reports/:docCode/export| APIGW
|
||||
RCL -.al listar.-> APIGW
|
||||
APIGW --> AGW_CORE --> SAPGW
|
||||
|
||||
RR -.->|"endpoint del reportsConfig<br/>(no existe en el backend)"| REP
|
||||
```
|
||||
|
||||
## Flujo real: Reporte HTML (ejemplo "stickers")
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor User as Usuario
|
||||
participant List as HtmlReportsList
|
||||
participant Runner as HtmlReportRunner
|
||||
participant Modal as ModalSearchTemplate
|
||||
participant API as api.ts
|
||||
participant Backend as reports.py
|
||||
participant HANA as HANA
|
||||
|
||||
User->>List: /reportes-html/inventario
|
||||
List->>List: lee htmlReportsConfig["inventario"]
|
||||
List-->>User: tarjeta "Impresión de Etiquetas"
|
||||
|
||||
User->>Runner: clic → /reportes-html/inventario/stickers
|
||||
Runner->>Runner: busca definition por docCode "stickers"
|
||||
Runner-->>User: formulario (Documento f4-modal, Cantidad, Formato, Papel)
|
||||
|
||||
User->>Runner: clic en campo "Documento"
|
||||
Runner->>Modal: abre ModalSearchTemplate (endpoint DeliveryNotes)
|
||||
Modal-->>User: tabla de guías de remisión
|
||||
User->>Modal: selecciona guía
|
||||
Modal-->>Runner: DocEntry seleccionado
|
||||
|
||||
User->>Runner: completa cantidad/formato y clic "Generar Reporte"
|
||||
Runner->>API: POST /api/reports/stickers {doc_entry, cantidad, formato, papel}
|
||||
API->>Backend: fetch con X-Session-Token
|
||||
Backend->>HANA: STICKERS_QUERY (datos de la guía por DocEntry)
|
||||
HANA-->>Backend: fila con cliente, dirección, etc.
|
||||
Backend->>Backend: renderiza templates/reports/stickers.html (Jinja2)
|
||||
alt formato = pdf
|
||||
Backend->>Backend: WeasyPrint HTML → PDF
|
||||
Backend-->>API: StreamingResponse application/pdf
|
||||
else formato = html
|
||||
Backend-->>API: HTMLResponse
|
||||
end
|
||||
API-->>Runner: blob
|
||||
Runner-->>User: <iframe> con vista previa
|
||||
```
|
||||
|
||||
## Flujo real: Reporte Crystal (ejemplo "DLN20015")
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor User as Usuario
|
||||
participant List as CrystalReportsList
|
||||
participant Runner as CrystalReportRunner
|
||||
participant API as api.ts
|
||||
participant Backend as apigateway.py
|
||||
participant Core as core/api_gateway.py
|
||||
participant SAPGW as SAP API Gateway
|
||||
|
||||
User->>List: /reportes-crystal/inventario
|
||||
List->>List: lee crystalReportsConfig["inventario"]
|
||||
List-->>User: tarjeta "Impresión de Etiquetas" (docCode DLN20015)
|
||||
|
||||
User->>Runner: clic → /reportes-crystal/inventario/DLN20015
|
||||
Runner-->>User: formulario (Documento f4-modal, Cantidad)
|
||||
|
||||
User->>Runner: completa parámetros y clic "Exportar PDF"
|
||||
Runner->>API: POST /api/apigateway/reports/DLN20015/export<br/>[{name, type, value}]
|
||||
API->>Backend: fetch con X-Session-Token
|
||||
Backend->>Core: forward_to_api_gateway(POST rs/v1/ExportPDFData?DocCode=DLN20015)
|
||||
Core->>SAPGW: request con credenciales APIGATEWAY_* del .env
|
||||
SAPGW-->>Core: PDF en base64
|
||||
Core-->>Backend: response
|
||||
Backend->>Backend: decodifica base64 → bytes
|
||||
Backend-->>API: StreamingResponse application/pdf
|
||||
API-->>Runner: blob
|
||||
Runner-->>User: <iframe> con vista previa PDF
|
||||
```
|
||||
|
||||
Nota: `crystalReportsConfig` solo tiene un reporte cargado, en `inventario` (`DLN20015`, "Impresión de Etiquetas"). Los otros módulos (`socios`, `ventas`, `compras`) están declarados con arrays vacíos — no hay reportes Crystal configurados para ellos todavía. Curiosamente, el comentario en `mini-backend/routers/reports.py` indica que la consulta SQL de `stickers` (el reporte HTML) es "la query original de Crystal Reports para DLN20015, adaptada" — es decir, el mismo reporte de negocio (etiquetas de guía de remisión) existe hoy por **dos caminos en paralelo**: el Crystal Report original vía API Gateway, y su reimplementación propia vía HANA + Jinja + WeasyPrint.
|
||||
|
||||
## Árbol de Componentes y Rutas
|
||||
|
||||
```
|
||||
App.tsx (BrowserRouter)
|
||||
└── / (ProtectedRoute → MainLayout)
|
||||
├── /informes/:module → ModuleReportsList.tsx
|
||||
│ └── /informes/:module/:reportId → ReportRunner.tsx
|
||||
│ ├── ReportParameterForm.tsx
|
||||
│ └── ReportResultsView.tsx
|
||||
├── /reportes-crystal/:module → CrystalReportsList.tsx
|
||||
│ └── /reportes-crystal/:module/:docCode → CrystalReportRunner.tsx
|
||||
│ └── ModalSearchTemplate.tsx (f4-modal)
|
||||
└── /reportes-html/:module → HtmlReportsList.tsx
|
||||
└── /reportes-html/:module/:docCode → HtmlReportRunner.tsx
|
||||
└── ModalSearchTemplate.tsx (f4-modal)
|
||||
```
|
||||
|
||||
`:module` es uno de `socios | ventas | compras | inventario` (`moduleLabels`/`moduleIcons` en `config/reports.ts`), compartido por los tres sistemas.
|
||||
|
||||
## Archivos Clave
|
||||
|
||||
| Archivo | Ruta | Propósito |
|
||||
|---|---|---|
|
||||
| `ModuleReportsList.tsx` | `src/views/informes/` | Lista reportes de `reportsConfig` por módulo |
|
||||
| `ReportRunner.tsx` | `src/components/report/` | Ejecuta reportes de `reportsConfig` (endpoints aún no implementados en backend) |
|
||||
| `ReportParameterForm.tsx` | `src/components/report/` | Formulario de parámetros para `ReportRunner` |
|
||||
| `ReportResultsView.tsx` | `src/components/report/` | Renderiza tabla/totales/PDF según `outputType` |
|
||||
| `CrystalReportsList.tsx` | `src/views/reportes/` | Lista reportes de `crystalReportsConfig` por módulo |
|
||||
| `CrystalReportRunner.tsx` | `src/components/report/` | Arma el body y llama a `/api/apigateway/reports/:docCode/export` |
|
||||
| `HtmlReportsList.tsx` | `src/views/reportes/` | Lista reportes de `htmlReportsConfig` por módulo |
|
||||
| `HtmlReportRunner.tsx` | `src/components/report/` | Arma el body y llama a `/api/reports/:endpoint` |
|
||||
| `ModalSearchTemplate.tsx` | `src/components/ui/` | Modal de búsqueda F4 reutilizado por ambos runners reales |
|
||||
| `config/reports.ts` | `src/config/` | `reportsConfig`, `moduleLabels`, `moduleIcons` |
|
||||
| `config/crystalReports.ts` | `src/config/` | `crystalReportsConfig` |
|
||||
| `config/htmlReports.ts` | `src/config/` | `htmlReportsConfig` |
|
||||
| `types/reports.ts` | `src/types/` | Tipos `ReportDefinition`, `F4Config`, etc. |
|
||||
| `reports.py` | `mini-backend/routers/` | `POST /api/reports/stickers` (Jinja2 + WeasyPrint, datos vía `core/hana.py`) |
|
||||
| `apigateway.py` | `mini-backend/routers/` | `GET /api/apigateway/reports`, `GET .../{docCode}/parameters`, `POST .../{docCode}/export` |
|
||||
| `api_gateway.py` | `mini-backend/core/` | `forward_to_api_gateway`, proxy hacia el SAP API Gateway |
|
||||
Reference in new issue
Block a user