11 KiB
Arquitectura General
Diagrama de Componentes
graph TB
subgraph "FastAPI Application"
M[main.py\nEntry Point]
C[context.py\nRequest ID Context]
L[logger_config.py\nStructured Logging]
subgraph "Core"
SAP[core/sap.py\nSAP Session & Proxy]
SES[core/session.py\nEmployee Sessions]
HANA[core/hana.py\nConexión directa HANA]
AG[core/api_gateway.py\nSesión y Proxy APIGateway]
CRUD[core/crud_router.py\nFactory de routers CRUD OData]
end
subgraph "Routers"
A[auth.py\nLogin / Logout]
BP[business_partners.py\nSocios de Negocio]
IT[items.py\nArtículos]
SL[sales.py\nFacturas / Pedidos / Guías]
PR[purchases.py\nÓrdenes / Facturas / Guías Compra]
CT[catalogs.py\nCatálogos]
BT[batch.py\nLotes]
PM[permissions.py\nPermisos de Menú]
APG[apigateway.py\nCrystal Reports]
RP[reports.py\nReportes Jinja/HTML → PDF]
end
MP[menu_permissions.json]
TPL[templates/reports/*.html]
end
subgraph "External"
SAP_SL[SAP B1 Service Layer]
SAP_AG[SAP B1 API Gateway\nrs/v1/*]
SAP_HANA[(SAP HANA)]
end
M --> C
M --> L
C --> L
M --> A
M --> BP
M --> IT
M --> SL
M --> PR
M --> CT
M --> BT
M --> PM
M --> APG
M --> RP
A --> SAP
A --> SES
BP --> CRUD
IT --> CRUD
SL --> CRUD
PR --> CRUD
CT --> CRUD
BT --> CRUD
CRUD --> SAP
PM --> MP
APG --> AG
RP --> HANA
RP --> TPL
SAP --> SAP_SL
SES --> SAP
AG --> SAP_AG
HANA --> SAP_HANA
business_partners.py, items.py, sales.py, purchases.py, batch.py y
la mayor parte de catalogs.py ya no implementan cada endpoint a mano: usan
core/crud_router.py, una factory (make_crud_router) que genera el
list/get/create/update típico de un recurso OData (mismo bloque de
$skip/$top/$filter y llamada a forward_to_sapb1) a partir de un
nombre de recurso y unos flags (si soporta $orderby, si expone POST,
headers extra para el PATCH, etc). Esto evita que un copy-paste entre
recursos deje, por ejemplo, un PATCH apuntando al endpoint de otro
recurso. Un router de documentos de mercadeo (ventas o compras) puede
generar varios recursos a la vez iterando esa factory, por eso sales.py y
purchases.py ya no son un único router sino un APIRouter que incluye uno
por cada tipo de documento.
Pipeline de Middleware
Las peticiones atraviesan los siguientes middleware en orden:
flowchart LR
REQ[Request] --> CORS[CORSMiddleware\nallow_origins=*]
CORS --> CID[correlation_id_middleware\nX-Request-ID]
CID --> SES_MW[session_middleware\nValida X-Session-Token]
SES_MW --> ROUTER{Router}
ROUTER -->|/api/Login| A[auth.py]
ROUTER -->|Otros /api/*| DEST[routers → core/sap.py, core/api_gateway.py o core/hana.py]
Detalle de Middleware
| Middleware | Función |
|---|---|
CORSMiddleware |
Permite peticiones desde cualquier origen |
correlation_id_middleware |
Genera o extrae X-Request-ID para trazabilidad en logs |
session_middleware |
Valida token de sesión, adjunta el usuario (webUser) a request.state y al contexto de logging |
Arquitectura de Sesiones
Existen tres capas de sesión/credenciales independientes:
flowchart LR
subgraph Employee["Employee Session"]
T["Token aleatorio → webUser"]
E["employee_sessions dict"]
TK["active_tokens dict"]
T --> TK
TK --> E
end
subgraph SAP["SAP B1 Service Layer Session"]
S["sap_session_store"]
MC["Manager Credentials (.env: SAPB1_SL_*)"]
S -.->|Renueva en 401| MC
end
subgraph AG["SAP API Gateway Session"]
AS["api_gateway_session (requests.Session)"]
AC["Manager Credentials (.env: APIGATEWAY_*)"]
AS -.->|Renueva en 401| AC
end
| Capa | Almacenamiento | Ámbito | Timeout | Renovación |
|---|---|---|---|---|
| SAP Service Layer | sap_session_store (core/sap.py) |
Global | Gestionado SAP | Automática al recibir 401 |
| SAP API Gateway | api_gateway_session (core/api_gateway.py) |
Global | Gestionado SAP | Automática al recibir 401 |
| Empleado | active_tokens / employee_sessions (core/session.py) |
Por empleado | 30 min inactividad | Login manual |
core/hana.py no mantiene sesión: abre una conexión nueva a HANA por
petición (hana_connection(), context manager) y la cierra al terminar.
Estructura de Rutas
| Método | Ruta | Router | Descripción |
|---|---|---|---|
POST |
/api/Login |
auth.py |
Autenticar empleado |
POST |
/api/Logout |
auth.py |
Cerrar sesión |
GET |
/api/BusinessPartners |
business_partners |
Listar socios |
GET |
/api/BusinessPartners/{code} |
business_partners |
Obtener socio |
POST |
/api/BusinessPartners |
business_partners |
Crear socio |
PATCH |
/api/BusinessPartners/{code} |
business_partners |
Actualizar socio |
GET |
/api/Items |
items.py |
Listar artículos |
GET |
/api/Items/{code} |
items.py |
Obtener artículo |
POST |
/api/Items |
items.py |
Crear artículo |
PATCH |
/api/Items/{code} |
items.py |
Actualizar artículo |
GET/POST/PATCH |
/api/Invoices[/{key}] |
sales.py |
Facturas de venta |
GET/POST/PATCH |
/api/Orders[/{key}] |
sales.py |
Pedidos de venta |
GET/POST/PATCH |
/api/DeliveryNotes[/{key}] |
sales.py |
Guías de remisión de venta |
GET/PATCH |
/api/PurchaseOrders[/{key}] |
purchases.py |
Órdenes de compra |
GET/PATCH |
/api/PurchaseDeliveryNotes[/{key}] |
purchases.py |
Guías de recepción de compra |
GET/PATCH |
/api/PurchaseInvoices[/{key}] |
purchases.py |
Facturas de compra |
GET |
/api/Currencies |
catalogs.py |
Listar monedas |
GET |
/api/BusinessPartnerGroups |
catalogs.py |
Listar grupos de socios |
GET |
/api/ItemGroups |
catalogs.py |
Listar grupos artículos |
GET |
/api/Warehouses |
catalogs.py |
Listar almacenes |
GET |
/api/SalesTaxCodes |
catalogs.py |
Listar códigos de IVA |
GET |
/api/FactoringIndicators |
catalogs.py |
Listar indicadores de factoring |
GET |
/api/Sunat/BusinessPartners/DocumentType |
catalogs.py |
Tipos de documento SUNAT (campo de usuario EXX_TIPODOCU) |
GET |
/api/BatchNumberDetails |
batch.py |
Listar lotes |
GET |
/api/BatchNumberDetails/{id} |
batch.py |
Obtener lote |
PATCH |
/api/BatchNumberDetails/{id} |
batch.py |
Actualizar lote |
GET |
/api/user/menu |
permissions.py |
Obtener menú permitido |
GET |
/api/apigateway/reports |
apigateway.py |
Listar reportes Crystal Reports autorizados |
GET |
/api/apigateway/reports/{docCode}/parameters |
apigateway.py |
Parámetros de un reporte Crystal Reports |
POST |
/api/apigateway/reports/{docCode}/export |
apigateway.py |
Exportar reporte Crystal Reports a PDF |
POST |
/api/reports/stickers |
reports.py |
Generar PDF/HTML de stickers de guía de remisión (consulta directa a HANA + plantilla Jinja + WeasyPrint) |
Los recursos servidos por core/crud_router.py (todos los de la tabla salvo
auth, permissions, apigateway y reports) soportan además skip,
top, fields y filter como query params, y algunos (Invoices,
Orders, DeliveryNotes, PurchaseOrders, PurchaseDeliveryNotes,
PurchaseInvoices, BatchNumberDetails) también orderby. Ver
API Reference para el detalle completo por endpoint.
Dependencias del Proyecto
graph TD
main --> context
main --> logger_config
logger_config --> context
main --> auth
main --> business_partners
main --> items
main --> sales
main --> purchases
main --> catalogs
main --> batch
main --> permissions
main --> apigateway_router[apigateway]
main --> reports_router[reports]
auth --> sap_core
auth --> session_core
business_partners --> crud_router
items --> crud_router
sales --> crud_router
purchases --> crud_router
catalogs --> crud_router
catalogs --> sap_core
batch --> crud_router
crud_router --> sap_core
permissions --> menu_json[menu_permissions.json]
apigateway_router --> api_gateway_core[api_gateway]
reports_router --> hana_core[hana]
reports_router --> templates_reports[templates/reports/*.html]
Configuración por Entorno
La aplicación carga siempre mini-backend/.env (un solo archivo por
despliegue, sin sufijos de entorno) mediante load_dotenv. Ese archivo
no se versiona (está en .gitignore); la plantilla de referencia con
todas las variables necesarias y valores de ejemplo es
mini-backend/.env.example.
Grupos de variables:
| Grupo | Variables | Usado por |
|---|---|---|
| SAP Service Layer | SAPB1_SL_URL, SAPB1_SL_USER, SAPB1_SL_PASSWORD, SAPB1_SL_COMPANYDB, SAPB1_SL_TIMEOUT, SAPB1_SL_VERIFY_SSL |
core/sap.py, routers/auth.py |
| SAP API Gateway | APIGATEWAY_URL, APIGATEWAY_USER, APIGATEWAY_PASSWORD, APIGATEWAY_COMPANYDB, APIGATEWAY_DBINSTANCE, APIGATEWAY_TIMEOUT |
core/api_gateway.py |
| HANA (acceso directo) | HANA_HOST, HANA_PORT, HANA_USER, HANA_PASSWORD, HANA_SCHEMA |
core/hana.py, routers/reports.py |
| Logging | LOG_ENVIRONMENT (production = JSON estructurado, cualquier otro valor = texto legible en consola) |
main.py, logger_config.py |
Al desplegar para un cliente nuevo (ver flujo de branches por cliente en
CLAUDE.md de la raíz), se copia .env.example a .env dentro de
mini-backend/ y se completa con las credenciales reales de SAP Business
One / HANA de ese cliente. Las credenciales reales nunca deben
documentarse ni commitearse.