docs: add POS + Inventario complete design spec
Multi-tenant POS system for Mexican auto parts stores (refaccionarias): - DB-per-tenant isolation with template versioning - PWA with offline-first via IndexedDB + Service Worker - Inventory by operations (append-only, no conflicts) - CFDI 4.0 via Horux360 API with offline queue - Accounting with auto journal entries - Role-based permissions (owner/admin/cashier/warehouse/accountant) - Themeable frontend via CSS custom properties - 22 table schemas defined, offline edge cases addressed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
869
docs/plans/2026-03-27-pos-inventario-design.md
Normal file
869
docs/plans/2026-03-27-pos-inventario-design.md
Normal file
@@ -0,0 +1,869 @@
|
||||
# Nexus POS + Inventario — Design Spec
|
||||
|
||||
**Date:** 2026-03-27
|
||||
**Status:** Approved
|
||||
**Goal:** Sistema completo de Punto de Venta e Inventario para refaccionarias automotrices en Mexico, instalable como PWA con modo offline, multi-tenant con DB aislada por cliente.
|
||||
|
||||
> **Nota:** Todos los montos en MXN (pesos mexicanos). Multi-moneda fuera de alcance para v1.
|
||||
|
||||
---
|
||||
|
||||
## 1. Arquitectura General
|
||||
|
||||
### Multi-Tenant con DB por cliente + Template versionado
|
||||
|
||||
- **nexus_master DB**: tenants, subscriptions, tenant_schema_version, catalogo compartido (1.5M+ partes), marketplace (bodegas, precios, stock)
|
||||
- **tenant_template DB**: schema completo vacio, usado con `CREATE DATABASE ... TEMPLATE`
|
||||
- **tenant_{id} DB**: una por refaccionaria, aislamiento total de datos
|
||||
- **Horux360 API**: credenciales SAT (CSD) para timbrado CFDI
|
||||
|
||||
### Provisioning automatico
|
||||
|
||||
1. Nueva refaccionaria se registra -> INSERT en `nexus_master.tenants`
|
||||
2. `CREATE DATABASE tenant_xxx TEMPLATE tenant_template`
|
||||
3. INSERT `schema_version = 'v1.0'`
|
||||
4. Carga catalogo de cuentas SAT predeterminado
|
||||
5. Crea empleado owner con credenciales iniciales
|
||||
|
||||
### Migraciones versionadas
|
||||
|
||||
- `tenant_schema_version` rastrea version por tenant
|
||||
- Al actualizar schema (v1.0 -> v1.1): script recorre todos los tenants, aplica ALTER/CREATE, actualiza version
|
||||
- `tenant_template` se actualiza tambien
|
||||
|
||||
### Conexion offline (PWA)
|
||||
|
||||
- **Service Worker**: cache-first para app, network-first para busquedas externas
|
||||
- **IndexedDB**: inventario completo del cliente, clientes, operaciones pendientes, CFDIs por timbrar
|
||||
- **Sync por operaciones**: nunca se sincroniza estado, solo operaciones append-only
|
||||
- **Conflictos**: no existen porque las operaciones son aditivas (VENTA: -1, ENTRADA: +10), el servidor suma/resta
|
||||
|
||||
#### IndexedDB Storage
|
||||
|
||||
El inventario se almacena comprimido como JSON en IndexedDB. Para una refaccionaria tipica (10K-30K SKUs), esto ocupa ~5-15MB, bien dentro de los limites del navegador (~50-100MB tipico). Si un tenant tiene >50K SKUs, el sync se pagina en bloques de 10K registros.
|
||||
|
||||
#### Politicas offline (edge cases)
|
||||
|
||||
- **Stock negativo**: Se PERMITE vender con stock negativo mientras se esta offline. Al sincronizar, las ventas se aplican normalmente y el stock negativo se marca para revision. Se genera alerta automatica al dueno.
|
||||
- **Cambios de precio mientras offline**: La sucursal offline sigue vendiendo a los precios cacheados en IndexedDB. Al sincronizar, los registros de VENTA conservan el precio al momento de la venta (sin cambios retroactivos). Los nuevos precios aplican solo a ventas futuras.
|
||||
- **Limite de credito excedido**: Se PERMITE autorizar ventas a credito que excedan el limite mientras se esta offline. Al sincronizar, se genera alerta al dueno indicando el excedente. El dueno decide si cobrar inmediatamente o ajustar el limite.
|
||||
|
||||
### Cola de timbrado CFDI offline
|
||||
|
||||
1. Venta en POS -> genera factura con folio provisional (PRE-XXXXX)
|
||||
2. Cliente recibe ticket impreso inmediato
|
||||
3. Factura entra a `cfdi_queue` con status `pending`
|
||||
4. Sync engine (cuando hay internet) -> envia XML a Horux API -> Horux firma con CSD y timbra
|
||||
5. Retorna UUID fiscal + XML timbrado -> `cfdi_queue.status = 'stamped'`
|
||||
6. Envio CFDI por email al cliente
|
||||
7. Si falla: retry con backoff exponencial (5s, 30s, 2m, 10m, 1h)
|
||||
|
||||
---
|
||||
|
||||
## 2. Modulo POS (Punto de Venta)
|
||||
|
||||
### Flujo dual
|
||||
|
||||
- **Mostrador rapido**: cliente pide pieza, se vende y se va
|
||||
- **Cotizacion -> Venta**: se cotiza primero, cliente regresa a comprar
|
||||
|
||||
### Funcionalidades
|
||||
|
||||
**Venta:**
|
||||
- Busqueda por numero de parte, nombre, o codigo de barras (lector USB)
|
||||
- Agregar desde catalogo (carrito -> POS)
|
||||
- Cantidades editables inline
|
||||
- Descuento por item o por venta total (requiere permiso, maximo configurable por empleado)
|
||||
- Margen en tiempo real visible para vendedores con permiso (costo, margen %, alerta si margen bajo)
|
||||
- Descuento maximo sin perder margen calculado automaticamente
|
||||
|
||||
**Clientes:**
|
||||
- Venta a publico general (sin RFC)
|
||||
- Seleccionar cliente registrado (autocompletado)
|
||||
- Crear cliente nuevo inline sin salir del POS
|
||||
- Credito: ver limite, saldo disponible. Validacion antes de autorizar venta a credito
|
||||
|
||||
**Formas de cobro:**
|
||||
- Efectivo (calculo de cambio)
|
||||
- Transferencia (campo de referencia)
|
||||
- Tarjeta (referencia de terminal bancaria)
|
||||
- Pago mixto (ej: $1,000 efectivo + $1,115.84 transferencia)
|
||||
|
||||
**Acciones:**
|
||||
- **Cobrar** -> cierra venta, genera operaciones de inventario, ticket
|
||||
- **Cotizacion** -> guarda sin cobrar, imprimible
|
||||
- **Apartado** -> reserva partes, pago parcial
|
||||
- **Credito** -> venta a cuenta del cliente (valida disponible)
|
||||
|
||||
**Ticket/impresion:**
|
||||
- Ticket termico (58mm/80mm) con datos fiscales
|
||||
- PDF descargable
|
||||
- Opcion de facturar (encola en cfdi_queue)
|
||||
|
||||
### Modo Mostrador Rapido (F-keys)
|
||||
|
||||
```
|
||||
F1 -> Buscar parte F5 -> Ultima venta
|
||||
F2 -> Buscar cliente F6 -> Abrir cajon
|
||||
F3 -> Cobrar +/- -> Cantidad
|
||||
F4 -> Cotizacion * -> Descuento
|
||||
Enter -> Agregar al carrito
|
||||
```
|
||||
|
||||
100% operacion por teclado sin mouse.
|
||||
|
||||
### Caja registradora
|
||||
|
||||
- **Apertura**: empleado inicia turno, registra fondo inicial
|
||||
- **Durante el dia**: ventas automaticas, entradas/salidas manuales con motivo
|
||||
- **Corte parcial (X)**: consulta sin cerrar
|
||||
- **Corte final (Z)**: cierre con desglose (efectivo, transferencias, tarjeta, credito), diferencia esperado vs contado
|
||||
- **Multi-caja**: cada caja con sesion y corte independiente, corte consolidado del dia
|
||||
|
||||
### Historial del vehiculo como herramienta de venta
|
||||
|
||||
- Al seleccionar cliente habitual, muestra su vehiculo y ultima visita
|
||||
- "Ultima vez le vendiste filtro de aceite hace 3 meses" (dato informativo)
|
||||
|
||||
---
|
||||
|
||||
## 3. Modulo de Catalogo Local
|
||||
|
||||
### Concepto
|
||||
|
||||
El catalogo es el **inventario del cliente organizado para venta rapida**. No es un cache del catalogo Nexus.
|
||||
|
||||
> **Nota sobre naming:** El modulo POS usa su propia convencion de nombres (id, name, etc.) independiente del schema del catalogo Nexus (id_brand, name_brand). El campo `catalog_part_id` en la tabla `inventory` es un entero almacenado en la DB del tenant que se resuelve via llamada API al nexus_master. No es un foreign key a nivel de base de datos (cross-DB FK).
|
||||
|
||||
### Offline (siempre disponible)
|
||||
|
||||
- Todo su inventario navegable por:
|
||||
- Categoria (Frenos, Motor, Suspension, Electrico...)
|
||||
- Marca de vehiculo (Nissan, Toyota, VW...)
|
||||
- Fabricante (Bosch, Monroe, NGK...)
|
||||
- Busqueda por numero de parte o nombre
|
||||
- Stock en tiempo real (propio)
|
||||
- Precios de venta configurados
|
||||
- **Carrito**: agregar multiples partes, ver total, "Ir a cobrar" -> POS recibe carrito completo
|
||||
|
||||
### Online (features adicionales)
|
||||
|
||||
- "Ver disponibilidad externa" (solo lectura):
|
||||
- Stock y precios en bodegas registradas
|
||||
- Stock en otras refaccionarias de la red Nexus
|
||||
- Alternativas aftermarket del catalogo TecDoc (1.5M+ partes)
|
||||
- Cross-references OEM <-> Aftermarket
|
||||
|
||||
### Flujos
|
||||
|
||||
```
|
||||
Venta rapida: Catalogo -> Carrito -> "Ir a cobrar" -> POS con carrito pre-cargado -> Cobrar
|
||||
Parte faltante: Catalogo -> "No tengo" -> Buscar bodegas (solo lectura) -> Ofrecer alternativa
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Modulo de Inventario
|
||||
|
||||
### Modelo de datos
|
||||
|
||||
**inventory**: id, branch_id, part_number, barcode, name, description, category_id, brand, vehicle_compatibility (JSON), unit, cost, price_1 (mostrador), price_2 (taller), price_3 (mayoreo), tax_rate, min_stock, max_stock, location, image_url, is_active, catalog_part_id (entero opcional, referencia a catalogo Nexus via API)
|
||||
|
||||
**inventory_operations** (append-only, nunca se edita): id, inventory_id, branch_id, operation_type (SALE, PURCHASE, RETURN, ADJUST, TRANSFER, INITIAL), quantity (+/-), reference_id, reference_type, cost_at_time, employee_id, device_id, notes, created_at
|
||||
|
||||
**Stock actual** = `SUM(inventory_operations.quantity)` por inventory_id + branch_id
|
||||
|
||||
### Operaciones
|
||||
|
||||
- **PURCHASE**: entrada de mercancia (proveedor, cantidad, costo, # factura opcional). Actualiza costo promedio
|
||||
- **SALE**: automatico al cobrar en POS (negativo)
|
||||
- **RETURN**: devolucion de cliente -> reingresa al stock
|
||||
- **ADJUST**: ajuste manual (merma, robo, error). Motivo obligatorio + audit_log
|
||||
- **TRANSFER**: mover entre sucursales (operacion negativa en origen, positiva en destino)
|
||||
- **INITIAL**: carga inicial de inventario
|
||||
|
||||
### Toma fisica
|
||||
|
||||
1. Iniciar toma (puede ser parcial por categoria/zona)
|
||||
2. Empleado captura cantidades reales
|
||||
3. Sistema compara esperado vs contado
|
||||
4. Admin aprueba diferencias
|
||||
5. Genera operaciones ADJUST automaticas
|
||||
|
||||
### 3 listas de precios
|
||||
|
||||
- Precio 1 -- Mostrador (publico general)
|
||||
- Precio 2 -- Taller (clientes frecuentes)
|
||||
- Precio 3 -- Mayoreo (volumen)
|
||||
- Al seleccionar cliente en POS, aplica automaticamente segun su categoria. Editable con permiso.
|
||||
|
||||
### Codigos de barras
|
||||
|
||||
- Leer codigos existentes con lector USB
|
||||
- Generar codigos internos: formato `NX-{tenant_short}-{secuencial}`
|
||||
- Imprimible en etiquetas (compatible con impresoras de etiquetas)
|
||||
|
||||
### Alertas
|
||||
|
||||
- Stock en cero (agotado)
|
||||
- Stock bajo minimo (reabastecer)
|
||||
- Stock sobre maximo (sobreinventario)
|
||||
|
||||
### Reportes de inventario
|
||||
|
||||
- Inventario valorizado (stock x costo)
|
||||
- Rotacion ABC (80/20)
|
||||
- Productos sin movimiento (>60 dias)
|
||||
- Stock bajo minimo
|
||||
- Historial de movimientos por parte
|
||||
- Comparativo entre sucursales
|
||||
|
||||
---
|
||||
|
||||
## 5. Modulo de Facturacion (CFDI 4.0 via Horux)
|
||||
|
||||
### Version y campos obligatorios
|
||||
|
||||
- **Version CFDI**: 4.0 (explicito en XML)
|
||||
- **Exportacion**: "01" (no aplica, venta domestica)
|
||||
- **ObjetoImp**: "02" (si, objeto de impuesto) para conceptos gravados
|
||||
- **InformacionGlobal**: requerido para facturas a publico en general (RFC generico XAXX010101000)
|
||||
- **IEPS**: manejo diferido a v1.1 (no aplica para mayoria de autopartes)
|
||||
|
||||
### Tipos de comprobante
|
||||
|
||||
- **Ingreso**: venta normal (factura)
|
||||
- **Egreso**: nota de credito (devolucion, descuento posterior)
|
||||
- **Pago**: complemento de pago (ventas a credito pagadas despues)
|
||||
|
||||
### Complemento de pago (PPD)
|
||||
|
||||
Cuando una venta a credito (metodo de pago PPD) recibe un pago parcial o total a traves del modulo de pagos/cobranza:
|
||||
1. Sistema auto-genera complemento de pago CFDI tipo "Pago"
|
||||
2. Se encola en `cfdi_queue` con type='pago'
|
||||
3. El sync engine envia a Horux para timbrado
|
||||
4. Se relaciona con el CFDI de ingreso original via UUID
|
||||
|
||||
### Dos momentos para facturar
|
||||
|
||||
1. **Al vender**: checkbox "Facturar" en POS, genera automaticamente
|
||||
2. **Despues**: desde historial de ventas, buscar por folio, generar CFDI (limite: mismo mes fiscal)
|
||||
|
||||
### Datos del CFDI
|
||||
|
||||
- Del tenant (config): RFC emisor, razon social, regimen fiscal, CP, CSD via Horux
|
||||
- Del cliente: RFC receptor, razon social, regimen fiscal, uso CFDI, CP
|
||||
- De la venta: ClaveProdServ SAT, ClaveUnidad SAT, cantidad, precio, descuento, IVA 16%, metodo de pago (PUE/PPD), forma de pago (01/03/04/99)
|
||||
|
||||
### Generacion de XML
|
||||
|
||||
El backend POS genera la estructura XML del CFDI usando Python (`lxml` para construccion de XML). El XML sin firmar se envia a Horux360, que firma con CSD del tenant y timbra con PAC.
|
||||
|
||||
### Cancelacion de CFDI
|
||||
|
||||
- **Quien puede cancelar**: solo owner o admin
|
||||
- **Plazo**: preferentemente dentro del mismo mes (reglas SAT)
|
||||
- **Motivos de cancelacion** (catalogo SAT):
|
||||
- 01: Comprobante emitido con errores con relacion (requiere CFDI de sustitucion)
|
||||
- 02: Comprobante emitido con errores sin relacion
|
||||
- 03: No se llevo a cabo la operacion
|
||||
- 04: Operacion nominativa relacionada en factura global
|
||||
- Para motivo 01: se debe crear primero el CFDI de sustitucion, luego cancelar el original referenciando el nuevo UUID
|
||||
- Cancelacion genera reverso contable automatico (poliza de egreso o cancelacion)
|
||||
|
||||
### API Horux
|
||||
|
||||
- `POST /api/nexus/cfdi/stamp` -- envia XML sin firmar, Horux firma y timbra
|
||||
- `GET /api/nexus/cfdi/status/:uuid` -- estado del timbrado
|
||||
- `POST /api/nexus/cfdi/cancel` -- cancelacion ante SAT (incluye motivo + UUID sustitucion si aplica)
|
||||
- Auth: API key entre Nexus <-> Horux
|
||||
|
||||
### Pendiente construir en Horux360
|
||||
|
||||
- 3 endpoints API (stamp, status, cancel)
|
||||
- Auth API key
|
||||
- Logica de timbrado con PAC (base ya existe con @nodecfdi)
|
||||
|
||||
---
|
||||
|
||||
## 6. Modulo de Contabilidad
|
||||
|
||||
### Catalogo de cuentas SAT
|
||||
|
||||
Pre-cargado al crear tenant. Contador puede agregar subcuentas. Estructura estandar: Activo (100), Pasivo (200), Capital (300), Ingresos (400), Costos (500), Gastos (600).
|
||||
|
||||
### Polizas automaticas
|
||||
|
||||
Cada operacion genera polizas contables:
|
||||
- Venta efectivo: cargo Bancos, abono Ventas + IVA trasladado + cargo Costo mercancia, abono Inventarios
|
||||
- Venta credito: cargo Clientes, abono Ventas + IVA trasladado
|
||||
- Cobro credito: cargo Bancos, abono Clientes
|
||||
- Compra proveedor: cargo Inventarios + IVA acreditable, abono Proveedores
|
||||
- Corte de caja: cargo Bancos, abono Caja
|
||||
|
||||
### Cierre de periodos fiscales
|
||||
|
||||
- Cerrar un periodo fiscal **impide** crear nuevas polizas contables en ese periodo
|
||||
- Los CFDIs SI pueden seguir emitiendose (el SAT lo permite), pero sus polizas se registran en el periodo abierto actual
|
||||
- Al cerrar un periodo se genera automaticamente la balanza de comprobacion final del periodo
|
||||
- Solo el owner puede cerrar periodos
|
||||
|
||||
### Reportes contables
|
||||
|
||||
- Balanza de comprobacion (saldo inicial, cargos, abonos, saldo final)
|
||||
- Estado de resultados (mensual)
|
||||
- Balance general
|
||||
- Estado de cuenta por cliente
|
||||
- Antiguedad de saldos (cartera vencida: corriente, 1-30d, 31-60d, 61-90d, 90d+)
|
||||
|
||||
### Tablas
|
||||
|
||||
- **accounts**: id, code, name, parent_id, type, sat_code, is_system, is_active
|
||||
- **journal_entries**: id, entry_number, date, type, description, reference_type, reference_id, status, created_by, is_auto
|
||||
- **journal_entry_lines**: id, journal_entry_id, account_id, debit, credit, description
|
||||
- **fiscal_periods**: id, year, month, status (open/closed), closed_by, closed_at
|
||||
|
||||
---
|
||||
|
||||
## 7. Permisos y Empleados
|
||||
|
||||
### Roles
|
||||
|
||||
- **DUENO (owner)**: todo sin restricciones. Dashboard movil.
|
||||
- **ADMINISTRADOR (admin)**: casi todo excepto cerrar periodos contables y eliminar admins.
|
||||
- **CAJERO (cashier)**: POS (vender, cobrar, catalogo). Descuentos hasta % maximo configurable. Cancelar solo sus ventas en primeros 30 min. NO ve costos ni margenes.
|
||||
- **ALMACENISTA (warehouse)**: inventario (entradas, salidas, toma fisica, transferencias). NO ve precios de venta ni contabilidad.
|
||||
- **CONTADOR (accountant)**: contabilidad y facturacion. Todos los reportes. NO puede vender ni modificar inventario.
|
||||
|
||||
### Permisos granulares
|
||||
|
||||
Modulos: pos, inventory, catalog, customers, accounting, invoicing, reports, config.
|
||||
Acciones: view, create, edit, delete, authorize.
|
||||
Ejemplos: `pos.sell`, `pos.discount`, `pos.view_cost`, `inventory.adjust`, `customers.edit_credit`, `config.edit_prices`
|
||||
|
||||
### Tablas
|
||||
|
||||
- **employees**: id, name, email, phone, pin (4 digitos para login rapido POS), password_hash, role, branch_id, max_discount_pct, is_active
|
||||
- **employee_permissions**: employee_id, permission
|
||||
- **employee_sessions**: id, employee_id, device_id, token, expires_at
|
||||
|
||||
### Login rapido POS
|
||||
|
||||
PIN pad de 4 digitos para cambio rapido de cajero sin salir de la app.
|
||||
|
||||
#### Seguridad del PIN
|
||||
|
||||
- Rate limit: maximo 5 intentos por minuto por dispositivo
|
||||
- Lockout: despues de 10 intentos fallidos, bloqueo de 15 minutos (cooldown)
|
||||
- El PIN es por dispositivo (no global): se combina con `device_id` para binding de sesion
|
||||
- PIN hasheado en DB (nunca en texto plano)
|
||||
|
||||
---
|
||||
|
||||
## 8. Dashboard del Dueno
|
||||
|
||||
### Vista principal (movil + web)
|
||||
|
||||
- Ventas hoy vs meta diaria con progreso visual
|
||||
- Cajas activas: ventas y margen por cajero
|
||||
- Resumen: ticket promedio, margen, ventas a credito, efectivo en caja
|
||||
- Alertas: descuentos, cancelaciones, stock bajo
|
||||
- Grafica semanal de ventas
|
||||
- Multi-sucursal: vista consolidada + por sucursal
|
||||
|
||||
---
|
||||
|
||||
## 9. Auditoria
|
||||
|
||||
### audit_log (tabla INSERT-only)
|
||||
|
||||
- employee_id, action, entity_type, entity_id, old_value (JSON), new_value (JSON), device_id, ip_address, branch_id, timestamp
|
||||
- Nunca UPDATE, nunca DELETE
|
||||
- Acciones rastreadas: SALE, CANCEL, PRICE_CHANGE, STOCK_ADJUST, LOGIN, DISCOUNT, CREDIT_CHANGE, CONFIG_CHANGE
|
||||
- Cambios de precio requieren permiso `config.edit_prices`
|
||||
- Cancelaciones requieren motivo obligatorio
|
||||
|
||||
---
|
||||
|
||||
## 10. Schema de Base de Datos
|
||||
|
||||
### nexus_master (schema centralizado)
|
||||
|
||||
```sql
|
||||
CREATE TABLE tenants (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
db_name VARCHAR(100) UNIQUE NOT NULL, -- 'tenant_001'
|
||||
rfc VARCHAR(13),
|
||||
plan VARCHAR(50) DEFAULT 'basic', -- basic, pro, enterprise
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE subscriptions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
tenant_id INTEGER REFERENCES tenants(id),
|
||||
plan VARCHAR(50) NOT NULL,
|
||||
status VARCHAR(20) DEFAULT 'active', -- active, past_due, cancelled
|
||||
started_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ,
|
||||
stripe_id VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE TABLE tenant_schema_version (
|
||||
tenant_id INTEGER PRIMARY KEY REFERENCES tenants(id),
|
||||
version VARCHAR(20) NOT NULL DEFAULT 'v1.0',
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### tenant_{id} DB (schema completo por refaccionaria)
|
||||
|
||||
```sql
|
||||
-- =====================
|
||||
-- SUCURSALES
|
||||
-- =====================
|
||||
CREATE TABLE branches (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
address TEXT,
|
||||
phone VARCHAR(20),
|
||||
is_active BOOLEAN DEFAULT TRUE
|
||||
);
|
||||
|
||||
-- =====================
|
||||
-- EMPLEADOS Y PERMISOS
|
||||
-- =====================
|
||||
CREATE TABLE employees (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
email VARCHAR(200),
|
||||
phone VARCHAR(20),
|
||||
pin VARCHAR(100), -- hashed, 4 digitos
|
||||
password_hash VARCHAR(200),
|
||||
role VARCHAR(20) NOT NULL, -- owner, admin, cashier, warehouse, accountant
|
||||
branch_id INTEGER REFERENCES branches(id),
|
||||
max_discount_pct NUMERIC(5,2) DEFAULT 0,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE employee_permissions (
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
permission VARCHAR(100) NOT NULL, -- 'pos.sell', 'inventory.adjust', etc.
|
||||
PRIMARY KEY (employee_id, permission)
|
||||
);
|
||||
|
||||
CREATE TABLE employee_sessions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
device_id VARCHAR(200),
|
||||
token VARCHAR(500) NOT NULL,
|
||||
expires_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
-- =====================
|
||||
-- CLIENTES
|
||||
-- =====================
|
||||
CREATE TABLE customers (
|
||||
id SERIAL PRIMARY KEY,
|
||||
branch_id INTEGER REFERENCES branches(id),
|
||||
name VARCHAR(300) NOT NULL,
|
||||
rfc VARCHAR(13),
|
||||
razon_social VARCHAR(300),
|
||||
regimen_fiscal VARCHAR(10), -- codigo SAT regimen
|
||||
uso_cfdi VARCHAR(10) DEFAULT 'G03', -- codigo SAT uso CFDI
|
||||
cp VARCHAR(5),
|
||||
email VARCHAR(200),
|
||||
phone VARCHAR(20),
|
||||
address TEXT,
|
||||
price_tier SMALLINT DEFAULT 1 CHECK (price_tier IN (1,2,3)), -- 1=mostrador, 2=taller, 3=mayoreo
|
||||
credit_limit NUMERIC(12,2) DEFAULT 0,
|
||||
credit_balance NUMERIC(12,2) DEFAULT 0, -- saldo actual de credito
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
vehicle_info JSONB, -- [{make, model, year, vin, plates}]
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- =====================
|
||||
-- INVENTARIO
|
||||
-- =====================
|
||||
CREATE TABLE inventory (
|
||||
id SERIAL PRIMARY KEY,
|
||||
branch_id INTEGER REFERENCES branches(id),
|
||||
part_number VARCHAR(100) NOT NULL,
|
||||
barcode VARCHAR(100),
|
||||
name VARCHAR(300) NOT NULL,
|
||||
description TEXT,
|
||||
category_id INTEGER,
|
||||
brand VARCHAR(100),
|
||||
vehicle_compatibility JSONB,
|
||||
unit VARCHAR(20) DEFAULT 'PZA',
|
||||
cost NUMERIC(12,2) DEFAULT 0,
|
||||
price_1 NUMERIC(12,2) DEFAULT 0, -- mostrador
|
||||
price_2 NUMERIC(12,2) DEFAULT 0, -- taller
|
||||
price_3 NUMERIC(12,2) DEFAULT 0, -- mayoreo
|
||||
tax_rate NUMERIC(5,4) DEFAULT 0.16,
|
||||
min_stock INTEGER DEFAULT 0,
|
||||
max_stock INTEGER DEFAULT 0,
|
||||
location VARCHAR(50), -- ubicacion en almacen
|
||||
image_url VARCHAR(500),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
catalog_part_id INTEGER, -- referencia a catalogo Nexus (via API, no FK)
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE inventory_operations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
inventory_id INTEGER REFERENCES inventory(id),
|
||||
branch_id INTEGER REFERENCES branches(id),
|
||||
operation_type VARCHAR(20) NOT NULL, -- SALE, PURCHASE, RETURN, ADJUST, TRANSFER, INITIAL
|
||||
quantity INTEGER NOT NULL, -- positivo o negativo
|
||||
reference_id INTEGER,
|
||||
reference_type VARCHAR(50), -- 'sale', 'purchase', 'return', etc.
|
||||
cost_at_time NUMERIC(12,2),
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
device_id VARCHAR(200),
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
-- Stock actual = SUM(inventory_operations.quantity) WHERE inventory_id=X AND branch_id=Y
|
||||
|
||||
-- =====================
|
||||
-- VENTAS
|
||||
-- =====================
|
||||
CREATE TABLE sales (
|
||||
id SERIAL PRIMARY KEY,
|
||||
branch_id INTEGER REFERENCES branches(id),
|
||||
customer_id INTEGER REFERENCES customers(id), -- NULL = publico general
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
register_id INTEGER, -- FK cash_registers
|
||||
sale_type VARCHAR(20) NOT NULL, -- cash, credit, mixed
|
||||
payment_method VARCHAR(20), -- efectivo, transferencia, tarjeta, mixto
|
||||
subtotal NUMERIC(12,2) NOT NULL,
|
||||
discount_total NUMERIC(12,2) DEFAULT 0,
|
||||
tax_total NUMERIC(12,2) NOT NULL,
|
||||
total NUMERIC(12,2) NOT NULL,
|
||||
amount_paid NUMERIC(12,2) DEFAULT 0,
|
||||
change_given NUMERIC(12,2) DEFAULT 0,
|
||||
metodo_pago_sat VARCHAR(3), -- PUE o PPD
|
||||
forma_pago_sat VARCHAR(2), -- 01, 03, 04, 99
|
||||
status VARCHAR(20) DEFAULT 'completed', -- completed, cancelled, returned
|
||||
device_id VARCHAR(200),
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE sale_items (
|
||||
id SERIAL PRIMARY KEY,
|
||||
sale_id INTEGER REFERENCES sales(id),
|
||||
inventory_id INTEGER REFERENCES inventory(id),
|
||||
part_number VARCHAR(100),
|
||||
name VARCHAR(300),
|
||||
quantity INTEGER NOT NULL,
|
||||
unit_price NUMERIC(12,2) NOT NULL, -- precio al momento de la venta
|
||||
unit_cost NUMERIC(12,2), -- costo al momento de la venta
|
||||
discount_pct NUMERIC(5,2) DEFAULT 0,
|
||||
discount_amount NUMERIC(12,2) DEFAULT 0,
|
||||
tax_rate NUMERIC(5,4) DEFAULT 0.16,
|
||||
tax_amount NUMERIC(12,2) DEFAULT 0,
|
||||
subtotal NUMERIC(12,2) NOT NULL,
|
||||
clave_prod_serv VARCHAR(10), -- clave SAT
|
||||
clave_unidad VARCHAR(10) -- clave unidad SAT
|
||||
);
|
||||
|
||||
-- =====================
|
||||
-- COTIZACIONES
|
||||
-- =====================
|
||||
CREATE TABLE quotations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
branch_id INTEGER REFERENCES branches(id),
|
||||
customer_id INTEGER REFERENCES customers(id),
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
subtotal NUMERIC(12,2) NOT NULL,
|
||||
tax_total NUMERIC(12,2) NOT NULL,
|
||||
total NUMERIC(12,2) NOT NULL,
|
||||
status VARCHAR(20) DEFAULT 'active', -- active, converted, expired, cancelled
|
||||
valid_until DATE,
|
||||
converted_sale_id INTEGER REFERENCES sales(id),
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE quotation_items (
|
||||
id SERIAL PRIMARY KEY,
|
||||
quotation_id INTEGER REFERENCES quotations(id),
|
||||
inventory_id INTEGER REFERENCES inventory(id),
|
||||
part_number VARCHAR(100),
|
||||
name VARCHAR(300),
|
||||
quantity INTEGER NOT NULL,
|
||||
unit_price NUMERIC(12,2) NOT NULL,
|
||||
discount_pct NUMERIC(5,2) DEFAULT 0,
|
||||
tax_rate NUMERIC(5,4) DEFAULT 0.16,
|
||||
subtotal NUMERIC(12,2) NOT NULL
|
||||
);
|
||||
|
||||
-- =====================
|
||||
-- APARTADOS (LAYAWAYS)
|
||||
-- =====================
|
||||
CREATE TABLE layaways (
|
||||
id SERIAL PRIMARY KEY,
|
||||
branch_id INTEGER REFERENCES branches(id),
|
||||
customer_id INTEGER REFERENCES customers(id) NOT NULL,
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
total NUMERIC(12,2) NOT NULL,
|
||||
amount_paid NUMERIC(12,2) DEFAULT 0,
|
||||
status VARCHAR(20) DEFAULT 'active', -- active, completed, cancelled
|
||||
expires_at DATE,
|
||||
converted_sale_id INTEGER REFERENCES sales(id),
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE layaway_payments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
layaway_id INTEGER REFERENCES layaways(id),
|
||||
amount NUMERIC(12,2) NOT NULL,
|
||||
payment_method VARCHAR(20),
|
||||
reference VARCHAR(100),
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- =====================
|
||||
-- CAJA REGISTRADORA
|
||||
-- =====================
|
||||
CREATE TABLE cash_registers (
|
||||
id SERIAL PRIMARY KEY,
|
||||
branch_id INTEGER REFERENCES branches(id),
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
register_number SMALLINT NOT NULL, -- numero de caja (1, 2, 3...)
|
||||
opening_amount NUMERIC(12,2) NOT NULL, -- fondo inicial
|
||||
closing_amount NUMERIC(12,2), -- monto contado al cerrar
|
||||
expected_amount NUMERIC(12,2), -- monto esperado calculado
|
||||
difference NUMERIC(12,2), -- closing - expected
|
||||
status VARCHAR(10) DEFAULT 'open', -- open, closed
|
||||
opened_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
closed_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TABLE cash_movements (
|
||||
id SERIAL PRIMARY KEY,
|
||||
register_id INTEGER REFERENCES cash_registers(id),
|
||||
type VARCHAR(5) NOT NULL, -- 'in' o 'out'
|
||||
amount NUMERIC(12,2) NOT NULL,
|
||||
reason VARCHAR(300) NOT NULL,
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- =====================
|
||||
-- FACTURACION (CFDI QUEUE)
|
||||
-- =====================
|
||||
CREATE TABLE cfdi_queue (
|
||||
id SERIAL PRIMARY KEY,
|
||||
sale_id INTEGER REFERENCES sales(id),
|
||||
type VARCHAR(10) NOT NULL, -- ingreso, egreso, pago
|
||||
xml_unsigned TEXT, -- XML generado por POS backend
|
||||
xml_signed TEXT, -- XML firmado+timbrado por Horux
|
||||
uuid_fiscal VARCHAR(36), -- UUID del SAT
|
||||
status VARCHAR(20) DEFAULT 'pending', -- pending, sending, stamped, failed, cancelled
|
||||
retry_count SMALLINT DEFAULT 0,
|
||||
provisional_folio VARCHAR(20), -- PRE-XXXXX
|
||||
error_message TEXT,
|
||||
cancel_motive VARCHAR(2), -- 01, 02, 03, 04
|
||||
cancel_replacement_uuid VARCHAR(36), -- UUID del CFDI sustituto (motivo 01)
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
stamped_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- =====================
|
||||
-- CONTABILIDAD
|
||||
-- =====================
|
||||
CREATE TABLE accounts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
code VARCHAR(20) NOT NULL UNIQUE,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
parent_id INTEGER REFERENCES accounts(id),
|
||||
type VARCHAR(20) NOT NULL, -- activo, pasivo, capital, ingreso, costo, gasto
|
||||
sat_code VARCHAR(20),
|
||||
is_system BOOLEAN DEFAULT FALSE, -- cuentas predeterminadas no editables
|
||||
is_active BOOLEAN DEFAULT TRUE
|
||||
);
|
||||
|
||||
CREATE TABLE journal_entries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
entry_number INTEGER NOT NULL,
|
||||
date DATE NOT NULL,
|
||||
type VARCHAR(20), -- ingreso, egreso, diario, poliza
|
||||
description TEXT,
|
||||
reference_type VARCHAR(50), -- sale, purchase, cash_register, etc.
|
||||
reference_id INTEGER,
|
||||
status VARCHAR(20) DEFAULT 'posted', -- draft, posted, cancelled
|
||||
created_by INTEGER REFERENCES employees(id),
|
||||
is_auto BOOLEAN DEFAULT TRUE, -- generada automaticamente
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE journal_entry_lines (
|
||||
id SERIAL PRIMARY KEY,
|
||||
journal_entry_id INTEGER REFERENCES journal_entries(id),
|
||||
account_id INTEGER REFERENCES accounts(id),
|
||||
debit NUMERIC(14,2) DEFAULT 0,
|
||||
credit NUMERIC(14,2) DEFAULT 0,
|
||||
description TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE fiscal_periods (
|
||||
id SERIAL PRIMARY KEY,
|
||||
year SMALLINT NOT NULL,
|
||||
month SMALLINT NOT NULL,
|
||||
status VARCHAR(10) DEFAULT 'open', -- open, closed
|
||||
closed_by INTEGER REFERENCES employees(id),
|
||||
closed_at TIMESTAMPTZ,
|
||||
UNIQUE (year, month)
|
||||
);
|
||||
|
||||
-- =====================
|
||||
-- AUDITORIA
|
||||
-- =====================
|
||||
CREATE TABLE audit_log (
|
||||
id SERIAL PRIMARY KEY,
|
||||
employee_id INTEGER REFERENCES employees(id),
|
||||
action VARCHAR(50) NOT NULL,
|
||||
entity_type VARCHAR(50),
|
||||
entity_id INTEGER,
|
||||
old_value JSONB,
|
||||
new_value JSONB,
|
||||
device_id VARCHAR(200),
|
||||
ip_address VARCHAR(45),
|
||||
branch_id INTEGER REFERENCES branches(id),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
-- INSERT-only: nunca UPDATE, nunca DELETE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Estructura del Proyecto
|
||||
|
||||
### Codigo
|
||||
|
||||
```
|
||||
/home/Autopartes/pos/
|
||||
+-- app.py (Flask app con blueprints)
|
||||
+-- blueprints/ (auth, pos, catalog, inventory, customers, invoicing, accounting, reports, config, sync)
|
||||
+-- services/ (tenant_manager, inventory_engine, accounting_engine, cfdi_queue, barcode_generator, sync_engine)
|
||||
+-- static/pwa/ (manifest.json, sw.js, icons)
|
||||
+-- static/js/ (pos, catalog, inventory, dashboard, accounting, reports, sync, keyboard, barcode)
|
||||
+-- static/css/ (pos, catalog, common)
|
||||
+-- templates/ (pos, catalog, inventory, customers, accounting, reports, dashboard, config, login)
|
||||
+-- migrations/ (v1.0_initial.sql, runner.py)
|
||||
+-- tenant_template.sql
|
||||
```
|
||||
|
||||
### Tech Stack
|
||||
|
||||
- **Backend**: Python 3 + Flask + Blueprints + psycopg2
|
||||
- **Frontend**: Vanilla JS + HTML + CSS (sin frameworks)
|
||||
- **PWA**: Service Worker + IndexedDB + manifest.json
|
||||
- **DB**: PostgreSQL (una DB por tenant + nexus_master)
|
||||
- **Facturacion**: Horux360 API (timbrado CFDI)
|
||||
- **XML CFDI**: Python lxml (generacion de XML)
|
||||
- **Impresion**: Web Print API (tickets termicos 58/80mm)
|
||||
|
||||
### Comunicacion entre sistemas
|
||||
|
||||
- PWA <-> POS API (Flask): REST + sync engine
|
||||
- POS API <-> Nexus Master (catalogo, bodegas): REST interno
|
||||
- POS API <-> Horux360 (CFDI): REST con API key
|
||||
|
||||
---
|
||||
|
||||
## 12. Theming y Branding
|
||||
|
||||
### Sistema de temas
|
||||
|
||||
El frontend es completamente temable via CSS custom properties. No hay colores, tipografia ni estilos hardcodeados.
|
||||
|
||||
```css
|
||||
/* Cada tema define estas variables */
|
||||
:root[data-theme="default"] {
|
||||
--color-primary: ...;
|
||||
--color-secondary: ...;
|
||||
--color-accent: ...;
|
||||
--color-bg: ...;
|
||||
--color-surface: ...;
|
||||
--color-text: ...;
|
||||
--color-border: ...;
|
||||
--font-display: ...;
|
||||
--font-body: ...;
|
||||
--font-mono: ...;
|
||||
--radius: ...;
|
||||
--shadow: ...;
|
||||
}
|
||||
```
|
||||
|
||||
### Selector de tema
|
||||
|
||||
- Cada tenant puede seleccionar un tema desde Configuracion
|
||||
- El tema se almacena en `config` del tenant
|
||||
- Se aplica al cargar la PWA (antes del primer render)
|
||||
- El equipo de diseno crea los temas, el sistema los aplica
|
||||
|
||||
### Responsabilidades
|
||||
|
||||
- **Desarrollo**: infraestructura de temas (CSS variables, selector, persistencia, aplicacion)
|
||||
- **Equipo de diseno**: definicion de temas (paletas, tipografia, iconos, variantes light/dark)
|
||||
|
||||
---
|
||||
|
||||
## 13. Infraestructura y Deployment
|
||||
|
||||
### Deployment
|
||||
|
||||
- Hosted en el mismo servidor que Nexus (actual)
|
||||
- Nginx como reverse proxy
|
||||
- SSL via Cloudflare
|
||||
- PWA servida desde path `/pos/`
|
||||
- Tenant routing via JWT: el token contiene `tenant_id`, el backend resuelve la DB correspondiente
|
||||
|
||||
### Backups
|
||||
|
||||
- `pg_dump` por tenant DB via cron nightly
|
||||
- 30 dias de retencion
|
||||
- Almacenado en volumen separado del servidor principal
|
||||
|
||||
---
|
||||
|
||||
## 14. Fases Futuras (fuera de alcance v1)
|
||||
|
||||
Las siguientes funcionalidades estan planeadas pero **no se incluyen en v1**:
|
||||
|
||||
### Sugerencias de mantenimiento preventivo por vehiculo
|
||||
|
||||
- Basado en historial de compras del cliente y kilometraje
|
||||
- Requiere base de conocimiento de calendarios de mantenimiento por modelo/motor
|
||||
- Ej: "Ultima vez le vendiste filtro de aceite hace 3 meses. Toca cambio de bujias?"
|
||||
- **Prerequisito**: construir o integrar knowledge base de programas de mantenimiento
|
||||
|
||||
### Marketplace / Pedidos a bodegas de la red Nexus
|
||||
|
||||
- En v1 se incluye la funcion de **ver disponibilidad** en bodegas (solo lectura)
|
||||
- Diferido: pedido directo a bodega, fulfillment, tracking de envio, confirmacion de recepcion
|
||||
- Requiere: acuerdos comerciales entre bodegas, sistema de logistica, precios de distribuidor
|
||||
|
||||
### Integracion WhatsApp
|
||||
|
||||
- Venta por WhatsApp (cliente pregunta pieza, vendedor cotiza, apartado con QR)
|
||||
- Notificaciones al cliente cuando llega su pedido/apartado
|
||||
- Requiere: WhatsApp Business API, costos por mensaje, integracion con Meta
|
||||
- Planeado para fase posterior
|
||||
|
||||
### Push notifications
|
||||
|
||||
- Notificaciones al dueno: alertas criticas (cancelaciones, diferencias de caja, stock cero)
|
||||
- Notificaciones importantes (descuentos altos, credito excedido)
|
||||
- Resumen diario de ventas
|
||||
- Requiere: Web Push API, service worker notifications, backend push service
|
||||
- Planeado para fase posterior
|
||||
Reference in New Issue
Block a user