ARCHITECTURE.md
Arquitectura del Sistema
1. Principio rector
El backend es el source of truth. El frontend presenta estado y opera offline con datos cacheados, pero no decide reglas de negocio, permisos, precios, feature flags ni validaciones críticas.
2. Estrategia multi-tenant
Venuo usa Shared Database, Separate Schemas mediante django-tenants.
Esquema public (centralizado — compartido)
Contiene las tablas globales. Accesible por el Súper Administrador únicamente.
| Tabla | Descripción |
|---|---|
Usuario |
Autenticación global. Vinculado a empresas y sucursales |
Empresa |
Tenant padre. Define el esquema propio (tenant_empresa_id) |
UsuarioEmpresaSucursal |
Mapping de accesos: qué usuario tiene qué rol en qué sucursal |
Suscripcion |
Plan activo por tenant (Basic / Essential / Advanced) |
FeatureFlag |
Funcionalidades habilitadas por plan. Validadas en backend |
Esquemas de tenant (tenant_empresa_id)
Cada Empresa posee su propio esquema aislado. Ningún tenant puede ver datos de otro.
| Tabla | Descripción |
|---|---|
Producto |
Catálogo del tenant con especificaciones JSONB + proveedor_principal |
StockPorSucursal |
M2M Producto–Sucursal con stock_actual y stock_minimo |
EntidadComercial |
Tabla unificada de Clientes y Proveedores. Campo tipo: CLIENTE / PROVEEDOR |
PlantillaRubro |
Configuración de atributos válidos para un rubro comercial |
AtributoConfig |
Define un atributo: nombre, tipo (texto/número/lista), opciones |
Caja |
Registro de apertura, movimientos y cierre por sucursal |
Venta |
Transacción de venta. Incluye estado PENDIENTE_SYNC para modo offline |
2.1 Límite de aislamiento — Empresa, no Sucursal
El schema de PostgreSQL se crea por Empresa, no por Sucursal. Decisión formal: docs/adr/ADR-001-aislamiento-schema-por-empresa.md.
Las sucursales son registros dentro del schema de su empresa, filtrados por sucursal_id en cada query. Nunca tienen schema propio.
Data portability por sucursal
La autonomía de datos de cada sucursal se resuelve como feature de la aplicación:
- El rol
sucursal_adminpuede exportar sus propios datos (ventas, caja, stock) en formato CSV/JSON desde el panel. - El rol
empresa_adminpuede exportar datos de cualquiera de sus sucursales. - Esta exportación es independiente de los backups operacionales, que son responsabilidad del DevOps.
3. Capas del sistema
Frontend (React / Vite / TS / PWA)
↓ (HTTPS — JWT en header)
API / ViewSets (Django REST Framework)
↓
Serializers / DTOs (validación de estructura)
↓
Service Layer (lógica de negocio, feature flags, reglas de caja)
↓
Models / ORM (django-tenants aware — search_path activo)
↓
PostgreSQL
├── schema: public (Usuario, Empresa, Suscripcion, FeatureFlag)
└── schema: tenant_X (Producto, Stock, Caja, Venta, EntidadComercial)
4. Aplicaciones Django
| App | Responsabilidad | Schema |
|---|---|---|
core_api |
Middleware de routing por tenant, aislamiento de schema, validación JWT, feature flags | Ambos |
app_empresas |
CRUD de Empresa, Sucursal, UsuarioEmpresaSucursal, roles y permisos | Public + Tenant |
app_suscripciones |
Gestión de planes y Feature Flags. Bloqueo 403 si plan insuficiente |
Public |
app_inventario |
Producto, StockPorSucursal, PlantillaRubro, AtributoConfig, importador masivo | Tenant |
app_caja |
Apertura/cierre de caja, movimientos, cierre ciego, vinculación a proveedor | Tenant |
5. Regla de Service Layer
Toda lógica de negocio compleja vive en services.py de cada app.
Permitido en services
- Validación de feature flags antes de operaciones avanzadas.
- Cálculo de descuadre en cierre de caja.
- Actualización masiva de precios con transacción atómica.
- Procesamiento asíncrono de importación (dispara tarea Celery).
- Creación de schema de tenant al registrar una nueva Empresa.
- Encolado de ventas offline y lógica de sincronización.
- Auditoría de acciones críticas.
Prohibido en vistas, serializers o frontend
- Lógica de cierre de caja o cálculo de descuadre.
- Validación de feature flags.
- Switching de schema de tenant.
- Cálculo de precios o descuentos.
- Decisiones de workflow de Venta o Caja.
6. Patrón de atributos dinámicos por rubro
Para evitar cambios de esquema al incorporar nuevos rubros:
Producto.especificaciones: columnaJSONBen PostgreSQL.- El contenido se valida en el backend contra
PlantillaRubroyAtributoConfigdel tenant. - Índices de tipo
GINsobre las claves del JSONB para búsquedas eficientes en el POS. - Ningún atributo libre no validado puede guardarse en
especificaciones.
7. Resiliencia offline del POS
- El catálogo esencial (productos, precios, códigos) se cachea en
IndexedDBdel navegador. - Si se pierde la conexión, el frontend continúa operando: escanea, vende, emite comprobante interno.
- Las ventas generadas offline se encolan en
localStoragecon estadoPENDIENTE_SYNC. - Al recuperar la conexión, Celery procesa la cola y sincroniza con el servidor.
- El backend valida cada venta sincronizada (stock, precios, permisos) antes de confirmarla.
8. Auditoría
Toda acción relevante genera evento auditable:
- Login y logout.
- Creación, edición y eliminación lógica de entidades.
- Apertura y cierre de caja.
- Sincronización de ventas offline.
- Cambios de plan/suscripción.
- Cambios de permisos de usuarios.
- Exportaciones y descargas de reportes.
9. Integraciones externas
| Integración | Propósito | Responsable | Estado |
|---|---|---|---|
| ARCA / AFIP | Facturación electrónica | Backend Agent | Fase 2 — módulo visible pero deshabilitado en Fase 1 |
| Celery + Redis | Importación masiva y sync offline | DevOps + Backend | Sprint 0 |
10. Decisiones arquitectónicas
Toda decisión relevante se registra como ADR en docs/adr/.
Decisiones registradas:
- Política de backups y aislamiento de schemas →
docs/adr/ADR-001-aislamiento-schema-por-empresa.md - Estrategia de CI/CD →
docs/adr/ADR-002-cicd-github-actions.md - Estrategia de logs y observabilidad →
docs/adr/ADR-003-logs-observabilidad.md - Resolución de conflictos en sincronización offline →
docs/adr/ADR-004-resolucion-conflictos-sync-offline.md
11. Anti-patrones prohibidos
- Hardcodear
tenant_iden el código de negocio. - Queries sin
search_pathactivo del tenant. - Lógica de feature flags o cierre de caja en el frontend.
- Monto esperado de caja accesible desde el endpoint del cajero.
- Endpoints del POS sin
select_related/prefetch_related. - Mutaciones complejas fuera del service layer.
- Modificar el schema de un tenant directamente sin usar
django-tenants.