Saltar a contenido

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_admin puede exportar sus propios datos (ventas, caja, stock) en formato CSV/JSON desde el panel.
  • El rol empresa_admin puede 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: columna JSONB en PostgreSQL.
  • El contenido se valida en el backend contra PlantillaRubro y AtributoConfig del tenant.
  • Índices de tipo GIN sobre 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 IndexedDB del navegador.
  • Si se pierde la conexión, el frontend continúa operando: escanea, vende, emite comprobante interno.
  • Las ventas generadas offline se encolan en localStorage con estado PENDIENTE_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_id en el código de negocio.
  • Queries sin search_path activo 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.