1372 lines
46 KiB
Markdown
1372 lines
46 KiB
Markdown
# PLAN DE IMPLEMENTACION - DATABASE
|
|
|
|
**Fecha:** 2025-12-18
|
|
**Fase:** 3 - Plan de Implementaciones
|
|
**Capa:** Base de Datos (PostgreSQL 15+)
|
|
|
|
---
|
|
|
|
## 1. RESUMEN EJECUTIVO
|
|
|
|
### 1.1 Alcance
|
|
- **Tablas heredadas de core:** 144
|
|
- **Tablas nuevas retail:** 26
|
|
- **Schemas afectados:** auth, core, financial, inventory, sales, retail (nuevo)
|
|
- **Migraciones requeridas:** 15
|
|
|
|
### 1.2 Prerrequisitos Bloqueantes
|
|
|
|
| ID | Modulo Core | Estado | Accion Requerida |
|
|
|----|-------------|--------|------------------|
|
|
| GAP-BLK-001 | MGN-001 Auth | 40% | Completar al 100% |
|
|
| GAP-BLK-002 | MGN-005 Catalogs | 0% | Implementar |
|
|
| GAP-BLK-003 | MGN-010 Financial | 70% | Migrar a TypeORM |
|
|
| GAP-BLK-004 | MGN-011 Inventory | 60% | Completar |
|
|
| GAP-BLK-005 | MGN-013 Sales | 50% | Completar |
|
|
|
|
---
|
|
|
|
## 2. ORDEN DE IMPLEMENTACION
|
|
|
|
### 2.1 Secuencia de Dependencias
|
|
|
|
```
|
|
SPRINT 1 (Prerrequisitos Core)
|
|
│
|
|
├── 1.1 auth.tenants (verificar/completar)
|
|
├── 1.2 auth.users (verificar/completar)
|
|
├── 1.3 auth.roles (verificar/completar)
|
|
└── 1.4 auth.user_roles (verificar/completar)
|
|
│
|
|
v
|
|
SPRINT 2 (Catalogos Core)
|
|
│
|
|
├── 2.1 core.countries
|
|
├── 2.2 core.currencies
|
|
├── 2.3 core.uom_categories
|
|
├── 2.4 core.uom
|
|
└── 2.5 core.sequences
|
|
│
|
|
v
|
|
SPRINT 3 (Financial Core)
|
|
│
|
|
├── 3.1 financial.tax_groups
|
|
├── 3.2 financial.taxes
|
|
├── 3.3 financial.accounts
|
|
├── 3.4 financial.journals
|
|
└── 3.5 financial.payment_methods
|
|
│
|
|
v
|
|
SPRINT 4 (Inventory Core)
|
|
│
|
|
├── 4.1 inventory.product_categories
|
|
├── 4.2 inventory.products
|
|
├── 4.3 inventory.warehouses
|
|
└── 4.4 inventory.stock_quants
|
|
│
|
|
v
|
|
SPRINT 5 (Partners/Sales Core)
|
|
│
|
|
├── 5.1 core.partners
|
|
├── 5.2 sales.pricelists
|
|
└── 5.3 sales.pricelist_items
|
|
│
|
|
v
|
|
SPRINT 6 (Retail Schema - Fundamentos)
|
|
│
|
|
├── 6.1 retail.branches
|
|
├── 6.2 retail.cash_registers
|
|
└── 6.3 retail.branch_users
|
|
│
|
|
v
|
|
SPRINT 7 (Retail - POS)
|
|
│
|
|
├── 7.1 retail.pos_sessions
|
|
├── 7.2 retail.pos_orders
|
|
├── 7.3 retail.pos_order_lines
|
|
└── 7.4 retail.pos_payments
|
|
│
|
|
v
|
|
SPRINT 8 (Retail - Caja)
|
|
│
|
|
├── 8.1 retail.cash_movements
|
|
├── 8.2 retail.cash_closings
|
|
└── 8.3 retail.cash_counts
|
|
│
|
|
v
|
|
SPRINT 9 (Retail - Inventario)
|
|
│
|
|
├── 9.1 retail.branch_stock (vista/extension)
|
|
├── 9.2 retail.stock_transfers
|
|
└── 9.3 retail.stock_adjustments
|
|
│
|
|
v
|
|
SPRINT 10 (Retail - Clientes/Lealtad)
|
|
│
|
|
├── 10.1 retail.loyalty_programs
|
|
├── 10.2 retail.membership_levels
|
|
├── 10.3 retail.loyalty_transactions
|
|
└── 10.4 retail.customer_memberships
|
|
│
|
|
v
|
|
SPRINT 11 (Retail - Precios/Promociones)
|
|
│
|
|
├── 11.1 retail.promotions
|
|
├── 11.2 retail.promotion_products
|
|
├── 11.3 retail.coupons
|
|
└── 11.4 retail.coupon_redemptions
|
|
│
|
|
v
|
|
SPRINT 12 (Retail - Compras)
|
|
│
|
|
├── 12.1 retail.purchase_suggestions
|
|
├── 12.2 retail.supplier_orders
|
|
└── 12.3 retail.goods_receipts
|
|
│
|
|
v
|
|
SPRINT 13 (Retail - Facturacion)
|
|
│
|
|
├── 13.1 retail.cfdi_config
|
|
└── 13.2 retail.cfdis
|
|
│
|
|
v
|
|
SPRINT 14 (Retail - E-commerce)
|
|
│
|
|
├── 14.1 retail.carts
|
|
├── 14.2 retail.cart_items
|
|
├── 14.3 retail.ecommerce_orders
|
|
├── 14.4 retail.ecommerce_order_lines
|
|
└── 14.5 retail.shipping_rates
|
|
│
|
|
v
|
|
SPRINT 15 (Vistas Materializadas y Optimizacion)
|
|
│
|
|
├── 15.1 retail.mv_daily_sales
|
|
├── 15.2 retail.mv_product_sales
|
|
└── 15.3 Indices adicionales
|
|
```
|
|
|
|
---
|
|
|
|
## 3. DDL DETALLADO POR SPRINT
|
|
|
|
### 3.1 SPRINT 6 - Retail Schema Fundamentos
|
|
|
|
```sql
|
|
-- =====================================================
|
|
-- RETAIL SCHEMA - FUNDAMENTOS
|
|
-- Sprint: 6
|
|
-- Dependencias: auth.*, core.*, inventory.warehouses
|
|
-- =====================================================
|
|
|
|
-- Crear schema
|
|
CREATE SCHEMA IF NOT EXISTS retail;
|
|
|
|
-- 6.1 Sucursales
|
|
CREATE TABLE retail.branches (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
code VARCHAR(10) NOT NULL,
|
|
name VARCHAR(100) NOT NULL,
|
|
address TEXT,
|
|
phone VARCHAR(20),
|
|
email VARCHAR(100),
|
|
warehouse_id UUID REFERENCES inventory.warehouses(id),
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
is_main BOOLEAN DEFAULT FALSE,
|
|
config JSONB DEFAULT '{}',
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ,
|
|
UNIQUE(tenant_id, code)
|
|
);
|
|
|
|
-- RLS Policy
|
|
ALTER TABLE retail.branches ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.branches
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 6.2 Cajas Registradoras
|
|
CREATE TABLE retail.cash_registers (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
branch_id UUID NOT NULL REFERENCES retail.branches(id),
|
|
code VARCHAR(10) NOT NULL,
|
|
name VARCHAR(50) NOT NULL,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
default_payment_method VARCHAR(20),
|
|
config JSONB DEFAULT '{}',
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(tenant_id, code)
|
|
);
|
|
|
|
ALTER TABLE retail.cash_registers ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.cash_registers
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 6.3 Usuarios por Sucursal
|
|
CREATE TABLE retail.branch_users (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
branch_id UUID NOT NULL REFERENCES retail.branches(id),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id),
|
|
role VARCHAR(20) NOT NULL, -- manager, cashier, inventory
|
|
is_primary BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(tenant_id, branch_id, user_id)
|
|
);
|
|
|
|
ALTER TABLE retail.branch_users ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.branch_users
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_branches_tenant ON retail.branches(tenant_id);
|
|
CREATE INDEX idx_cash_registers_branch ON retail.cash_registers(branch_id);
|
|
CREATE INDEX idx_branch_users_user ON retail.branch_users(user_id);
|
|
```
|
|
|
|
### 3.2 SPRINT 7 - Retail POS
|
|
|
|
```sql
|
|
-- =====================================================
|
|
-- RETAIL SCHEMA - POS
|
|
-- Sprint: 7
|
|
-- Dependencias: retail.branches, retail.cash_registers
|
|
-- =====================================================
|
|
|
|
-- 7.1 Sesiones POS
|
|
CREATE TABLE retail.pos_sessions (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
branch_id UUID NOT NULL REFERENCES retail.branches(id),
|
|
register_id UUID NOT NULL REFERENCES retail.cash_registers(id),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'opening',
|
|
-- opening, open, closing, closed
|
|
opening_balance DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
closing_balance DECIMAL(12,2),
|
|
opened_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
closed_at TIMESTAMPTZ,
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ
|
|
);
|
|
|
|
ALTER TABLE retail.pos_sessions ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.pos_sessions
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 7.2 Ordenes POS
|
|
CREATE TABLE retail.pos_orders (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
session_id UUID NOT NULL REFERENCES retail.pos_sessions(id),
|
|
order_number VARCHAR(20) NOT NULL,
|
|
customer_id UUID REFERENCES core.partners(id),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'draft',
|
|
-- draft, done, refunded, cancelled
|
|
order_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
subtotal DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
discount_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
tax_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
total DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
notes TEXT,
|
|
-- Campos offline
|
|
offline_id VARCHAR(50),
|
|
synced_at TIMESTAMPTZ,
|
|
-- Facturacion
|
|
is_invoiced BOOLEAN DEFAULT FALSE,
|
|
invoice_id UUID,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ,
|
|
UNIQUE(tenant_id, order_number)
|
|
);
|
|
|
|
ALTER TABLE retail.pos_orders ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.pos_orders
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 7.3 Lineas de Orden POS
|
|
CREATE TABLE retail.pos_order_lines (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
order_id UUID NOT NULL REFERENCES retail.pos_orders(id) ON DELETE CASCADE,
|
|
product_id UUID NOT NULL REFERENCES inventory.products(id),
|
|
product_name VARCHAR(255) NOT NULL,
|
|
quantity DECIMAL(12,4) NOT NULL,
|
|
unit_price DECIMAL(12,2) NOT NULL,
|
|
discount_percent DECIMAL(5,2) DEFAULT 0,
|
|
discount_amount DECIMAL(12,2) DEFAULT 0,
|
|
tax_amount DECIMAL(12,2) DEFAULT 0,
|
|
total DECIMAL(12,2) NOT NULL,
|
|
notes TEXT
|
|
);
|
|
|
|
ALTER TABLE retail.pos_order_lines ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.pos_order_lines
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 7.4 Pagos POS
|
|
CREATE TABLE retail.pos_payments (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
order_id UUID NOT NULL REFERENCES retail.pos_orders(id) ON DELETE CASCADE,
|
|
payment_method VARCHAR(20) NOT NULL,
|
|
-- cash, card, transfer, credit, mixed
|
|
amount DECIMAL(12,2) NOT NULL,
|
|
received_amount DECIMAL(12,2),
|
|
change_amount DECIMAL(12,2) DEFAULT 0,
|
|
reference VARCHAR(100),
|
|
card_last_four VARCHAR(4),
|
|
authorization_code VARCHAR(20),
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
ALTER TABLE retail.pos_payments ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.pos_payments
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_pos_sessions_branch ON retail.pos_sessions(branch_id);
|
|
CREATE INDEX idx_pos_sessions_user ON retail.pos_sessions(user_id);
|
|
CREATE INDEX idx_pos_sessions_status ON retail.pos_sessions(status);
|
|
CREATE INDEX idx_pos_orders_session ON retail.pos_orders(session_id);
|
|
CREATE INDEX idx_pos_orders_customer ON retail.pos_orders(customer_id);
|
|
CREATE INDEX idx_pos_orders_date ON retail.pos_orders(order_date);
|
|
CREATE INDEX idx_pos_orders_offline ON retail.pos_orders(offline_id) WHERE offline_id IS NOT NULL;
|
|
CREATE INDEX idx_pos_order_lines_product ON retail.pos_order_lines(product_id);
|
|
CREATE INDEX idx_pos_payments_order ON retail.pos_payments(order_id);
|
|
```
|
|
|
|
### 3.3 SPRINT 8 - Retail Caja
|
|
|
|
```sql
|
|
-- =====================================================
|
|
-- RETAIL SCHEMA - CAJA
|
|
-- Sprint: 8
|
|
-- Dependencias: retail.pos_sessions
|
|
-- =====================================================
|
|
|
|
-- 8.1 Movimientos de Caja
|
|
CREATE TABLE retail.cash_movements (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
session_id UUID NOT NULL REFERENCES retail.pos_sessions(id),
|
|
movement_type VARCHAR(10) NOT NULL, -- in, out
|
|
amount DECIMAL(12,2) NOT NULL,
|
|
reason VARCHAR(100) NOT NULL,
|
|
notes TEXT,
|
|
authorized_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
ALTER TABLE retail.cash_movements ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.cash_movements
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 8.2 Cortes de Caja
|
|
CREATE TABLE retail.cash_closings (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
session_id UUID NOT NULL REFERENCES retail.pos_sessions(id) UNIQUE,
|
|
closing_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
-- Montos esperados (calculados)
|
|
expected_cash DECIMAL(12,2) NOT NULL,
|
|
expected_card DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
expected_transfer DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
|
|
-- Montos declarados
|
|
declared_cash DECIMAL(12,2) NOT NULL,
|
|
declared_card DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
declared_transfer DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
|
|
-- Diferencias (columnas generadas)
|
|
cash_difference DECIMAL(12,2) GENERATED ALWAYS AS (declared_cash - expected_cash) STORED,
|
|
card_difference DECIMAL(12,2) GENERATED ALWAYS AS (declared_card - expected_card) STORED,
|
|
transfer_difference DECIMAL(12,2) GENERATED ALWAYS AS (declared_transfer - expected_transfer) STORED,
|
|
|
|
-- Detalle de denominaciones
|
|
denomination_detail JSONB,
|
|
notes TEXT,
|
|
|
|
-- Auditoria
|
|
closed_by UUID NOT NULL REFERENCES auth.users(id),
|
|
approved_by UUID REFERENCES auth.users(id),
|
|
is_approved BOOLEAN DEFAULT FALSE,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
ALTER TABLE retail.cash_closings ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.cash_closings
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 8.3 Arqueos Parciales
|
|
CREATE TABLE retail.cash_counts (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
session_id UUID NOT NULL REFERENCES retail.pos_sessions(id),
|
|
count_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
expected_amount DECIMAL(12,2) NOT NULL,
|
|
counted_amount DECIMAL(12,2) NOT NULL,
|
|
difference DECIMAL(12,2) GENERATED ALWAYS AS (counted_amount - expected_amount) STORED,
|
|
denomination_detail JSONB,
|
|
counted_by UUID NOT NULL REFERENCES auth.users(id),
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
ALTER TABLE retail.cash_counts ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.cash_counts
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_cash_movements_session ON retail.cash_movements(session_id);
|
|
CREATE INDEX idx_cash_closings_date ON retail.cash_closings(closing_date);
|
|
CREATE INDEX idx_cash_counts_session ON retail.cash_counts(session_id);
|
|
```
|
|
|
|
### 3.4 SPRINT 9 - Retail Inventario
|
|
|
|
```sql
|
|
-- =====================================================
|
|
-- RETAIL SCHEMA - INVENTARIO MULTI-SUCURSAL
|
|
-- Sprint: 9
|
|
-- Dependencias: retail.branches, inventory.*
|
|
-- =====================================================
|
|
|
|
-- 9.1 Vista de Stock por Sucursal (extiende inventory.stock_quants)
|
|
CREATE OR REPLACE VIEW retail.branch_stock AS
|
|
SELECT
|
|
sq.id,
|
|
sq.tenant_id,
|
|
sq.product_id,
|
|
sq.warehouse_id,
|
|
b.id as branch_id,
|
|
b.code as branch_code,
|
|
b.name as branch_name,
|
|
sq.quantity,
|
|
sq.reserved_quantity,
|
|
(sq.quantity - sq.reserved_quantity) as available_quantity,
|
|
p.default_code as product_code,
|
|
p.name as product_name,
|
|
p.sale_price,
|
|
sq.updated_at
|
|
FROM inventory.stock_quants sq
|
|
JOIN retail.branches b ON b.warehouse_id = sq.warehouse_id
|
|
JOIN inventory.products p ON p.id = sq.product_id
|
|
WHERE sq.tenant_id = current_setting('app.current_tenant_id', true)::UUID;
|
|
|
|
-- 9.2 Transferencias entre Sucursales
|
|
CREATE TABLE retail.stock_transfers (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
transfer_number VARCHAR(20) NOT NULL,
|
|
source_branch_id UUID NOT NULL REFERENCES retail.branches(id),
|
|
dest_branch_id UUID NOT NULL REFERENCES retail.branches(id),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'draft',
|
|
-- draft, confirmed, in_transit, received, cancelled
|
|
requested_by UUID NOT NULL REFERENCES auth.users(id),
|
|
confirmed_by UUID REFERENCES auth.users(id),
|
|
received_by UUID REFERENCES auth.users(id),
|
|
requested_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
confirmed_date TIMESTAMPTZ,
|
|
received_date TIMESTAMPTZ,
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ,
|
|
UNIQUE(tenant_id, transfer_number)
|
|
);
|
|
|
|
ALTER TABLE retail.stock_transfers ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.stock_transfers
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 9.2.1 Lineas de Transferencia
|
|
CREATE TABLE retail.stock_transfer_lines (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
transfer_id UUID NOT NULL REFERENCES retail.stock_transfers(id) ON DELETE CASCADE,
|
|
product_id UUID NOT NULL REFERENCES inventory.products(id),
|
|
requested_qty DECIMAL(12,4) NOT NULL,
|
|
sent_qty DECIMAL(12,4),
|
|
received_qty DECIMAL(12,4),
|
|
notes TEXT
|
|
);
|
|
|
|
ALTER TABLE retail.stock_transfer_lines ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.stock_transfer_lines
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 9.3 Ajustes de Inventario
|
|
CREATE TABLE retail.stock_adjustments (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
adjustment_number VARCHAR(20) NOT NULL,
|
|
branch_id UUID NOT NULL REFERENCES retail.branches(id),
|
|
adjustment_type VARCHAR(20) NOT NULL,
|
|
-- count, damage, theft, expiry, correction
|
|
status VARCHAR(20) NOT NULL DEFAULT 'draft',
|
|
-- draft, confirmed, cancelled
|
|
adjusted_by UUID NOT NULL REFERENCES auth.users(id),
|
|
approved_by UUID REFERENCES auth.users(id),
|
|
adjustment_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(tenant_id, adjustment_number)
|
|
);
|
|
|
|
ALTER TABLE retail.stock_adjustments ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.stock_adjustments
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 9.3.1 Lineas de Ajuste
|
|
CREATE TABLE retail.stock_adjustment_lines (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
adjustment_id UUID NOT NULL REFERENCES retail.stock_adjustments(id) ON DELETE CASCADE,
|
|
product_id UUID NOT NULL REFERENCES inventory.products(id),
|
|
theoretical_qty DECIMAL(12,4) NOT NULL,
|
|
actual_qty DECIMAL(12,4) NOT NULL,
|
|
difference DECIMAL(12,4) GENERATED ALWAYS AS (actual_qty - theoretical_qty) STORED,
|
|
reason VARCHAR(100),
|
|
notes TEXT
|
|
);
|
|
|
|
ALTER TABLE retail.stock_adjustment_lines ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.stock_adjustment_lines
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_stock_transfers_source ON retail.stock_transfers(source_branch_id);
|
|
CREATE INDEX idx_stock_transfers_dest ON retail.stock_transfers(dest_branch_id);
|
|
CREATE INDEX idx_stock_transfers_status ON retail.stock_transfers(status);
|
|
CREATE INDEX idx_stock_adjustments_branch ON retail.stock_adjustments(branch_id);
|
|
```
|
|
|
|
### 3.5 SPRINT 10 - Retail Clientes y Lealtad
|
|
|
|
```sql
|
|
-- =====================================================
|
|
-- RETAIL SCHEMA - CLIENTES Y LEALTAD
|
|
-- Sprint: 10
|
|
-- Dependencias: core.partners
|
|
-- =====================================================
|
|
|
|
-- 10.1 Programas de Lealtad
|
|
CREATE TABLE retail.loyalty_programs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
code VARCHAR(20) NOT NULL,
|
|
name VARCHAR(100) NOT NULL,
|
|
description TEXT,
|
|
points_per_currency DECIMAL(10,4) NOT NULL DEFAULT 1,
|
|
-- 1 punto por cada $1
|
|
currency_per_point DECIMAL(10,4) NOT NULL DEFAULT 0.01,
|
|
-- $0.01 por punto al canjear
|
|
min_points_redeem INT NOT NULL DEFAULT 100,
|
|
max_discount_percent DECIMAL(5,2) DEFAULT 100,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ,
|
|
UNIQUE(tenant_id, code)
|
|
);
|
|
|
|
ALTER TABLE retail.loyalty_programs ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.loyalty_programs
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 10.2 Niveles de Membresia
|
|
CREATE TABLE retail.membership_levels (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
program_id UUID NOT NULL REFERENCES retail.loyalty_programs(id),
|
|
code VARCHAR(20) NOT NULL,
|
|
name VARCHAR(50) NOT NULL,
|
|
min_points INT NOT NULL DEFAULT 0,
|
|
discount_percent DECIMAL(5,2) DEFAULT 0,
|
|
points_multiplier DECIMAL(5,2) DEFAULT 1,
|
|
benefits JSONB,
|
|
color VARCHAR(7), -- hex color
|
|
sort_order INT DEFAULT 0,
|
|
UNIQUE(tenant_id, program_id, code)
|
|
);
|
|
|
|
ALTER TABLE retail.membership_levels ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.membership_levels
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 10.3 Transacciones de Puntos
|
|
CREATE TABLE retail.loyalty_transactions (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
customer_id UUID NOT NULL REFERENCES core.partners(id),
|
|
program_id UUID NOT NULL REFERENCES retail.loyalty_programs(id),
|
|
order_id UUID REFERENCES retail.pos_orders(id),
|
|
transaction_type VARCHAR(20) NOT NULL,
|
|
-- earn, redeem, expire, adjust, bonus
|
|
points INT NOT NULL,
|
|
balance_after INT NOT NULL,
|
|
description TEXT,
|
|
expires_at DATE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
ALTER TABLE retail.loyalty_transactions ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.loyalty_transactions
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 10.4 Membresias de Clientes
|
|
CREATE TABLE retail.customer_memberships (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
customer_id UUID NOT NULL REFERENCES core.partners(id),
|
|
program_id UUID NOT NULL REFERENCES retail.loyalty_programs(id),
|
|
level_id UUID REFERENCES retail.membership_levels(id),
|
|
membership_number VARCHAR(20) NOT NULL,
|
|
current_points INT NOT NULL DEFAULT 0,
|
|
lifetime_points INT NOT NULL DEFAULT 0,
|
|
enrolled_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
last_activity_at TIMESTAMPTZ,
|
|
status VARCHAR(20) DEFAULT 'active',
|
|
-- active, inactive, suspended
|
|
UNIQUE(tenant_id, membership_number),
|
|
UNIQUE(tenant_id, customer_id, program_id)
|
|
);
|
|
|
|
ALTER TABLE retail.customer_memberships ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.customer_memberships
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_loyalty_programs_active ON retail.loyalty_programs(is_active);
|
|
CREATE INDEX idx_membership_levels_program ON retail.membership_levels(program_id);
|
|
CREATE INDEX idx_loyalty_trans_customer ON retail.loyalty_transactions(customer_id);
|
|
CREATE INDEX idx_loyalty_trans_date ON retail.loyalty_transactions(created_at);
|
|
CREATE INDEX idx_customer_memberships_customer ON retail.customer_memberships(customer_id);
|
|
```
|
|
|
|
### 3.6 SPRINT 11 - Retail Precios y Promociones
|
|
|
|
```sql
|
|
-- =====================================================
|
|
-- RETAIL SCHEMA - PRECIOS Y PROMOCIONES
|
|
-- Sprint: 11
|
|
-- Dependencias: retail.branches, inventory.products
|
|
-- =====================================================
|
|
|
|
-- 11.1 Promociones
|
|
CREATE TABLE retail.promotions (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
code VARCHAR(20) NOT NULL,
|
|
name VARCHAR(100) NOT NULL,
|
|
description TEXT,
|
|
promotion_type VARCHAR(30) NOT NULL,
|
|
-- percentage, fixed_amount, buy_x_get_y, bundle
|
|
discount_value DECIMAL(10,2) NOT NULL,
|
|
start_date DATE NOT NULL,
|
|
end_date DATE NOT NULL,
|
|
applies_to_all BOOLEAN DEFAULT FALSE,
|
|
min_quantity INT,
|
|
min_amount DECIMAL(12,2),
|
|
branch_ids UUID[], -- null = todas
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
max_uses INT,
|
|
current_uses INT DEFAULT 0,
|
|
-- Para NxM
|
|
buy_quantity INT,
|
|
get_quantity INT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ,
|
|
UNIQUE(tenant_id, code),
|
|
CONSTRAINT valid_dates CHECK (end_date >= start_date)
|
|
);
|
|
|
|
ALTER TABLE retail.promotions ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.promotions
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 11.2 Productos en Promocion
|
|
CREATE TABLE retail.promotion_products (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
promotion_id UUID NOT NULL REFERENCES retail.promotions(id) ON DELETE CASCADE,
|
|
product_id UUID REFERENCES inventory.products(id),
|
|
category_id UUID REFERENCES inventory.product_categories(id),
|
|
-- Al menos uno debe tener valor
|
|
CONSTRAINT product_or_category CHECK (product_id IS NOT NULL OR category_id IS NOT NULL)
|
|
);
|
|
|
|
ALTER TABLE retail.promotion_products ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.promotion_products
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 11.3 Cupones
|
|
CREATE TABLE retail.coupons (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
code VARCHAR(20) NOT NULL,
|
|
coupon_type VARCHAR(20) NOT NULL,
|
|
-- percentage, fixed_amount
|
|
discount_value DECIMAL(10,2) NOT NULL,
|
|
min_purchase DECIMAL(12,2),
|
|
max_discount DECIMAL(12,2),
|
|
valid_from DATE NOT NULL,
|
|
valid_until DATE NOT NULL,
|
|
max_uses INT DEFAULT 1,
|
|
times_used INT DEFAULT 0,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
customer_id UUID REFERENCES core.partners(id), -- null = cualquier cliente
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(tenant_id, code),
|
|
CONSTRAINT valid_dates CHECK (valid_until >= valid_from)
|
|
);
|
|
|
|
ALTER TABLE retail.coupons ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.coupons
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 11.4 Canjes de Cupones
|
|
CREATE TABLE retail.coupon_redemptions (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
coupon_id UUID NOT NULL REFERENCES retail.coupons(id),
|
|
order_id UUID NOT NULL REFERENCES retail.pos_orders(id),
|
|
discount_applied DECIMAL(12,2) NOT NULL,
|
|
redeemed_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
ALTER TABLE retail.coupon_redemptions ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.coupon_redemptions
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_promotions_active ON retail.promotions(is_active, start_date, end_date);
|
|
CREATE INDEX idx_promotions_branches ON retail.promotions USING GIN(branch_ids);
|
|
CREATE INDEX idx_promotion_products_promotion ON retail.promotion_products(promotion_id);
|
|
CREATE INDEX idx_promotion_products_product ON retail.promotion_products(product_id);
|
|
CREATE INDEX idx_coupons_code ON retail.coupons(code);
|
|
CREATE INDEX idx_coupons_valid ON retail.coupons(valid_from, valid_until, is_active);
|
|
```
|
|
|
|
### 3.7 SPRINT 12 - Retail Compras
|
|
|
|
```sql
|
|
-- =====================================================
|
|
-- RETAIL SCHEMA - COMPRAS
|
|
-- Sprint: 12
|
|
-- Dependencias: retail.branches, inventory.products, core.partners
|
|
-- =====================================================
|
|
|
|
-- 12.1 Sugerencias de Compra
|
|
CREATE TABLE retail.purchase_suggestions (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
branch_id UUID NOT NULL REFERENCES retail.branches(id),
|
|
product_id UUID NOT NULL REFERENCES inventory.products(id),
|
|
supplier_id UUID REFERENCES core.partners(id),
|
|
current_stock DECIMAL(12,4) NOT NULL,
|
|
min_stock DECIMAL(12,4) NOT NULL,
|
|
max_stock DECIMAL(12,4) NOT NULL,
|
|
suggested_qty DECIMAL(12,4) NOT NULL,
|
|
avg_daily_sales DECIMAL(12,4),
|
|
days_of_stock DECIMAL(8,2),
|
|
priority VARCHAR(10) NOT NULL DEFAULT 'medium',
|
|
-- critical, high, medium, low
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
|
-- pending, ordered, ignored
|
|
generated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE(tenant_id, branch_id, product_id, generated_at::DATE)
|
|
);
|
|
|
|
ALTER TABLE retail.purchase_suggestions ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.purchase_suggestions
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 12.2 Ordenes a Proveedores
|
|
CREATE TABLE retail.supplier_orders (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
order_number VARCHAR(20) NOT NULL,
|
|
supplier_id UUID NOT NULL REFERENCES core.partners(id),
|
|
branch_id UUID NOT NULL REFERENCES retail.branches(id),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'draft',
|
|
-- draft, sent, confirmed, partial, received, cancelled
|
|
order_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
expected_date DATE,
|
|
subtotal DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
tax_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
total DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
notes TEXT,
|
|
created_by UUID NOT NULL REFERENCES auth.users(id),
|
|
approved_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ,
|
|
UNIQUE(tenant_id, order_number)
|
|
);
|
|
|
|
ALTER TABLE retail.supplier_orders ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.supplier_orders
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 12.2.1 Lineas de Orden a Proveedor
|
|
CREATE TABLE retail.supplier_order_lines (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
order_id UUID NOT NULL REFERENCES retail.supplier_orders(id) ON DELETE CASCADE,
|
|
product_id UUID NOT NULL REFERENCES inventory.products(id),
|
|
quantity DECIMAL(12,4) NOT NULL,
|
|
unit_price DECIMAL(12,2) NOT NULL,
|
|
tax_percent DECIMAL(5,2) DEFAULT 0,
|
|
total DECIMAL(12,2) NOT NULL,
|
|
received_qty DECIMAL(12,4) DEFAULT 0
|
|
);
|
|
|
|
ALTER TABLE retail.supplier_order_lines ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.supplier_order_lines
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 12.3 Recepciones de Mercancia
|
|
CREATE TABLE retail.goods_receipts (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
receipt_number VARCHAR(20) NOT NULL,
|
|
supplier_order_id UUID REFERENCES retail.supplier_orders(id),
|
|
branch_id UUID NOT NULL REFERENCES retail.branches(id),
|
|
supplier_id UUID NOT NULL REFERENCES core.partners(id),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'draft',
|
|
-- draft, confirmed, cancelled
|
|
receipt_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
supplier_invoice VARCHAR(50),
|
|
notes TEXT,
|
|
received_by UUID NOT NULL REFERENCES auth.users(id),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(tenant_id, receipt_number)
|
|
);
|
|
|
|
ALTER TABLE retail.goods_receipts ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.goods_receipts
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 12.3.1 Lineas de Recepcion
|
|
CREATE TABLE retail.goods_receipt_lines (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
receipt_id UUID NOT NULL REFERENCES retail.goods_receipts(id) ON DELETE CASCADE,
|
|
product_id UUID NOT NULL REFERENCES inventory.products(id),
|
|
expected_qty DECIMAL(12,4),
|
|
received_qty DECIMAL(12,4) NOT NULL,
|
|
unit_cost DECIMAL(12,2),
|
|
notes TEXT
|
|
);
|
|
|
|
ALTER TABLE retail.goods_receipt_lines ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.goods_receipt_lines
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_purchase_suggestions_branch ON retail.purchase_suggestions(branch_id);
|
|
CREATE INDEX idx_purchase_suggestions_status ON retail.purchase_suggestions(status);
|
|
CREATE INDEX idx_purchase_suggestions_priority ON retail.purchase_suggestions(priority);
|
|
CREATE INDEX idx_supplier_orders_supplier ON retail.supplier_orders(supplier_id);
|
|
CREATE INDEX idx_supplier_orders_status ON retail.supplier_orders(status);
|
|
CREATE INDEX idx_goods_receipts_order ON retail.goods_receipts(supplier_order_id);
|
|
CREATE INDEX idx_goods_receipts_branch ON retail.goods_receipts(branch_id);
|
|
```
|
|
|
|
### 3.8 SPRINT 13 - Retail Facturacion CFDI
|
|
|
|
```sql
|
|
-- =====================================================
|
|
-- RETAIL SCHEMA - FACTURACION CFDI 4.0
|
|
-- Sprint: 13
|
|
-- Dependencias: retail.pos_orders, core.partners
|
|
-- =====================================================
|
|
|
|
-- 13.1 Configuracion CFDI por Tenant
|
|
CREATE TABLE retail.cfdi_config (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) UNIQUE,
|
|
emisor_rfc VARCHAR(13) NOT NULL,
|
|
emisor_nombre VARCHAR(255) NOT NULL,
|
|
emisor_regimen VARCHAR(3) NOT NULL,
|
|
emisor_cp VARCHAR(5) NOT NULL,
|
|
pac_provider VARCHAR(20) NOT NULL DEFAULT 'finkok',
|
|
pac_user VARCHAR(100),
|
|
pac_password_encrypted TEXT,
|
|
cer_path TEXT,
|
|
key_path TEXT,
|
|
key_password_encrypted TEXT,
|
|
serie_factura VARCHAR(10) DEFAULT 'A',
|
|
serie_nota_credito VARCHAR(10) DEFAULT 'NC',
|
|
folio_actual INT DEFAULT 1,
|
|
autofactura_enabled BOOLEAN DEFAULT TRUE,
|
|
autofactura_dias INT DEFAULT 30,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ
|
|
);
|
|
|
|
ALTER TABLE retail.cfdi_config ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.cfdi_config
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 13.2 CFDIs Emitidos
|
|
CREATE TABLE retail.cfdis (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
|
|
-- Relacion origen
|
|
source_type VARCHAR(20) NOT NULL, -- pos_order, ecommerce_order
|
|
source_id UUID NOT NULL,
|
|
|
|
-- Datos comprobante
|
|
serie VARCHAR(10),
|
|
folio VARCHAR(20),
|
|
uuid VARCHAR(36), -- UUID del SAT
|
|
fecha_emision TIMESTAMPTZ NOT NULL,
|
|
tipo_comprobante CHAR(1) NOT NULL, -- I, E, T, N, P
|
|
forma_pago VARCHAR(2),
|
|
metodo_pago VARCHAR(3),
|
|
|
|
-- Receptor
|
|
receptor_rfc VARCHAR(13) NOT NULL,
|
|
receptor_nombre VARCHAR(255) NOT NULL,
|
|
receptor_regimen VARCHAR(3),
|
|
receptor_cp VARCHAR(5),
|
|
uso_cfdi VARCHAR(4),
|
|
|
|
-- Totales
|
|
subtotal DECIMAL(12,2) NOT NULL,
|
|
descuento DECIMAL(12,2) DEFAULT 0,
|
|
total_impuestos DECIMAL(12,2) NOT NULL,
|
|
total DECIMAL(12,2) NOT NULL,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) NOT NULL DEFAULT 'vigente',
|
|
-- vigente, cancelado, pendiente
|
|
cancel_date TIMESTAMPTZ,
|
|
cancel_reason VARCHAR(2),
|
|
|
|
-- Archivos
|
|
xml_content TEXT,
|
|
xml_path TEXT,
|
|
pdf_path TEXT,
|
|
|
|
-- Timbre
|
|
fecha_timbrado TIMESTAMPTZ,
|
|
rfc_pac VARCHAR(13),
|
|
sello_cfd TEXT,
|
|
sello_sat TEXT,
|
|
no_certificado_sat VARCHAR(20),
|
|
|
|
-- CFDI relacionado (para notas de credito)
|
|
cfdi_relacionado_uuid VARCHAR(36),
|
|
tipo_relacion VARCHAR(2),
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ,
|
|
|
|
UNIQUE(tenant_id, uuid)
|
|
);
|
|
|
|
ALTER TABLE retail.cfdis ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.cfdis
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_cfdis_source ON retail.cfdis(source_type, source_id);
|
|
CREATE INDEX idx_cfdis_uuid ON retail.cfdis(uuid);
|
|
CREATE INDEX idx_cfdis_fecha ON retail.cfdis(fecha_emision);
|
|
CREATE INDEX idx_cfdis_receptor ON retail.cfdis(receptor_rfc);
|
|
CREATE INDEX idx_cfdis_status ON retail.cfdis(status);
|
|
```
|
|
|
|
### 3.9 SPRINT 14 - Retail E-commerce
|
|
|
|
```sql
|
|
-- =====================================================
|
|
-- RETAIL SCHEMA - E-COMMERCE
|
|
-- Sprint: 14
|
|
-- Dependencias: retail.branches, inventory.products, core.partners
|
|
-- =====================================================
|
|
|
|
-- 14.1 Carritos
|
|
CREATE TABLE retail.carts (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
customer_id UUID REFERENCES core.partners(id),
|
|
session_id VARCHAR(100), -- Para guests
|
|
subtotal DECIMAL(12,2) DEFAULT 0,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ,
|
|
expires_at TIMESTAMPTZ,
|
|
CONSTRAINT customer_or_session CHECK (customer_id IS NOT NULL OR session_id IS NOT NULL)
|
|
);
|
|
|
|
ALTER TABLE retail.carts ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.carts
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 14.2 Items del Carrito
|
|
CREATE TABLE retail.cart_items (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
cart_id UUID NOT NULL REFERENCES retail.carts(id) ON DELETE CASCADE,
|
|
product_id UUID NOT NULL REFERENCES inventory.products(id),
|
|
quantity DECIMAL(12,4) NOT NULL,
|
|
unit_price DECIMAL(12,2) NOT NULL,
|
|
total DECIMAL(12,2) NOT NULL,
|
|
added_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
ALTER TABLE retail.cart_items ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.cart_items
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 14.3 Pedidos E-commerce
|
|
CREATE TABLE retail.ecommerce_orders (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
order_number VARCHAR(20) NOT NULL,
|
|
customer_id UUID NOT NULL REFERENCES core.partners(id),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
|
-- pending, paid, preparing, shipped, ready_pickup, delivered, cancelled
|
|
order_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
-- Totales
|
|
subtotal DECIMAL(12,2) NOT NULL,
|
|
discount_amount DECIMAL(12,2) DEFAULT 0,
|
|
shipping_cost DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
tax_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
total DECIMAL(12,2) NOT NULL,
|
|
|
|
-- Pago
|
|
payment_status VARCHAR(20), -- pending, paid, failed, refunded
|
|
payment_method VARCHAR(50),
|
|
payment_reference VARCHAR(100),
|
|
|
|
-- Entrega
|
|
delivery_method VARCHAR(20) NOT NULL, -- shipping, pickup
|
|
pickup_branch_id UUID REFERENCES retail.branches(id),
|
|
shipping_address JSONB,
|
|
tracking_number VARCHAR(50),
|
|
|
|
-- Facturacion
|
|
is_invoiced BOOLEAN DEFAULT FALSE,
|
|
invoice_id UUID,
|
|
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ,
|
|
|
|
UNIQUE(tenant_id, order_number)
|
|
);
|
|
|
|
ALTER TABLE retail.ecommerce_orders ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.ecommerce_orders
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 14.4 Lineas de Pedido E-commerce
|
|
CREATE TABLE retail.ecommerce_order_lines (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
order_id UUID NOT NULL REFERENCES retail.ecommerce_orders(id) ON DELETE CASCADE,
|
|
product_id UUID NOT NULL REFERENCES inventory.products(id),
|
|
product_name VARCHAR(255) NOT NULL,
|
|
quantity DECIMAL(12,4) NOT NULL,
|
|
unit_price DECIMAL(12,2) NOT NULL,
|
|
total DECIMAL(12,2) NOT NULL
|
|
);
|
|
|
|
ALTER TABLE retail.ecommerce_order_lines ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.ecommerce_order_lines
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- 14.5 Tarifas de Envio
|
|
CREATE TABLE retail.shipping_rates (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
name VARCHAR(100) NOT NULL,
|
|
carrier VARCHAR(50) NOT NULL,
|
|
base_rate DECIMAL(12,2) NOT NULL,
|
|
free_shipping_minimum DECIMAL(12,2),
|
|
estimated_days INT,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
ALTER TABLE retail.shipping_rates ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON retail.shipping_rates
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_carts_customer ON retail.carts(customer_id);
|
|
CREATE INDEX idx_carts_session ON retail.carts(session_id);
|
|
CREATE INDEX idx_carts_expires ON retail.carts(expires_at);
|
|
CREATE INDEX idx_cart_items_cart ON retail.cart_items(cart_id);
|
|
CREATE INDEX idx_ecommerce_orders_customer ON retail.ecommerce_orders(customer_id);
|
|
CREATE INDEX idx_ecommerce_orders_status ON retail.ecommerce_orders(status);
|
|
CREATE INDEX idx_ecommerce_orders_date ON retail.ecommerce_orders(order_date);
|
|
```
|
|
|
|
### 3.10 SPRINT 15 - Vistas Materializadas
|
|
|
|
```sql
|
|
-- =====================================================
|
|
-- RETAIL SCHEMA - VISTAS MATERIALIZADAS
|
|
-- Sprint: 15
|
|
-- Para performance en reportes
|
|
-- =====================================================
|
|
|
|
-- 15.1 Ventas Diarias
|
|
CREATE MATERIALIZED VIEW retail.mv_daily_sales AS
|
|
SELECT
|
|
DATE(po.order_date) as sale_date,
|
|
po.tenant_id,
|
|
ps.branch_id,
|
|
ps.user_id as cashier_id,
|
|
COUNT(*) as transaction_count,
|
|
SUM(po.total) as total_sales,
|
|
SUM(po.discount_amount) as total_discounts,
|
|
AVG(po.total) as avg_ticket,
|
|
SUM(CASE WHEN po.status = 'refunded' THEN po.total ELSE 0 END) as refunds
|
|
FROM retail.pos_orders po
|
|
JOIN retail.pos_sessions ps ON po.session_id = ps.id
|
|
WHERE po.status IN ('done', 'refunded')
|
|
GROUP BY DATE(po.order_date), po.tenant_id, ps.branch_id, ps.user_id;
|
|
|
|
CREATE UNIQUE INDEX idx_mv_daily_sales
|
|
ON retail.mv_daily_sales(sale_date, tenant_id, branch_id, cashier_id);
|
|
|
|
-- 15.2 Ventas por Producto
|
|
CREATE MATERIALIZED VIEW retail.mv_product_sales AS
|
|
SELECT
|
|
pol.product_id,
|
|
pol.tenant_id,
|
|
ps.branch_id,
|
|
DATE_TRUNC('month', po.order_date) as sale_month,
|
|
SUM(pol.quantity) as qty_sold,
|
|
SUM(pol.total) as revenue,
|
|
COUNT(DISTINCT po.id) as order_count
|
|
FROM retail.pos_order_lines pol
|
|
JOIN retail.pos_orders po ON pol.order_id = po.id
|
|
JOIN retail.pos_sessions ps ON po.session_id = ps.id
|
|
WHERE po.status = 'done'
|
|
GROUP BY pol.product_id, pol.tenant_id, ps.branch_id, DATE_TRUNC('month', po.order_date);
|
|
|
|
CREATE UNIQUE INDEX idx_mv_product_sales
|
|
ON retail.mv_product_sales(product_id, tenant_id, branch_id, sale_month);
|
|
|
|
-- 15.3 Stock Actual por Sucursal
|
|
CREATE MATERIALIZED VIEW retail.mv_branch_stock_summary AS
|
|
SELECT
|
|
b.tenant_id,
|
|
b.id as branch_id,
|
|
b.code as branch_code,
|
|
b.name as branch_name,
|
|
COUNT(DISTINCT sq.product_id) as product_count,
|
|
SUM(sq.quantity) as total_stock,
|
|
SUM(sq.quantity * p.cost_price) as stock_value,
|
|
COUNT(CASE WHEN sq.quantity <= p.min_stock THEN 1 END) as low_stock_count,
|
|
COUNT(CASE WHEN sq.quantity = 0 THEN 1 END) as out_of_stock_count
|
|
FROM retail.branches b
|
|
JOIN inventory.stock_quants sq ON sq.warehouse_id = b.warehouse_id
|
|
JOIN inventory.products p ON p.id = sq.product_id
|
|
GROUP BY b.tenant_id, b.id, b.code, b.name;
|
|
|
|
CREATE UNIQUE INDEX idx_mv_branch_stock_summary
|
|
ON retail.mv_branch_stock_summary(tenant_id, branch_id);
|
|
|
|
-- Funcion para refrescar vistas
|
|
CREATE OR REPLACE FUNCTION retail.refresh_materialized_views()
|
|
RETURNS void AS $$
|
|
BEGIN
|
|
REFRESH MATERIALIZED VIEW CONCURRENTLY retail.mv_daily_sales;
|
|
REFRESH MATERIALIZED VIEW CONCURRENTLY retail.mv_product_sales;
|
|
REFRESH MATERIALIZED VIEW CONCURRENTLY retail.mv_branch_stock_summary;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Comentario: Programar cron job para ejecutar cada hora
|
|
-- SELECT cron.schedule('refresh-retail-views', '0 * * * *', 'SELECT retail.refresh_materialized_views()');
|
|
```
|
|
|
|
---
|
|
|
|
## 4. MIGRACIONES TYPEORM
|
|
|
|
### 4.1 Estructura de Archivos
|
|
|
|
```
|
|
src/migrations/
|
|
├── 1734500000000-CreateRetailSchema.ts
|
|
├── 1734500000001-CreateBranchTables.ts
|
|
├── 1734500000002-CreatePOSTables.ts
|
|
├── 1734500000003-CreateCashTables.ts
|
|
├── 1734500000004-CreateInventoryTables.ts
|
|
├── 1734500000005-CreateLoyaltyTables.ts
|
|
├── 1734500000006-CreatePromotionTables.ts
|
|
├── 1734500000007-CreatePurchaseTables.ts
|
|
├── 1734500000008-CreateCFDITables.ts
|
|
├── 1734500000009-CreateEcommerceTables.ts
|
|
├── 1734500000010-CreateMaterializedViews.ts
|
|
├── 1734500000011-CreateIndices.ts
|
|
└── 1734500000012-SeedCatalogs.ts
|
|
```
|
|
|
|
### 4.2 Ejemplo de Migracion
|
|
|
|
```typescript
|
|
// 1734500000002-CreatePOSTables.ts
|
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
|
|
export class CreatePOSTables1734500000002 implements MigrationInterface {
|
|
name = 'CreatePOSTables1734500000002';
|
|
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
// pos_sessions
|
|
await queryRunner.query(`
|
|
CREATE TABLE retail.pos_sessions (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
branch_id UUID NOT NULL REFERENCES retail.branches(id),
|
|
register_id UUID NOT NULL REFERENCES retail.cash_registers(id),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'opening',
|
|
opening_balance DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
closing_balance DECIMAL(12,2),
|
|
opened_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
closed_at TIMESTAMPTZ,
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ
|
|
)
|
|
`);
|
|
|
|
// RLS
|
|
await queryRunner.query(`
|
|
ALTER TABLE retail.pos_sessions ENABLE ROW LEVEL SECURITY
|
|
`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE POLICY tenant_isolation ON retail.pos_sessions
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID)
|
|
`);
|
|
|
|
// Similar para pos_orders, pos_order_lines, pos_payments...
|
|
}
|
|
|
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`DROP TABLE IF EXISTS retail.pos_payments`);
|
|
await queryRunner.query(`DROP TABLE IF EXISTS retail.pos_order_lines`);
|
|
await queryRunner.query(`DROP TABLE IF EXISTS retail.pos_orders`);
|
|
await queryRunner.query(`DROP TABLE IF EXISTS retail.pos_sessions`);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. SCRIPTS DE SEED
|
|
|
|
### 5.1 Catalogos SAT (para CFDI)
|
|
|
|
```sql
|
|
-- Catalogos SAT
|
|
INSERT INTO retail.sat_forma_pago (clave, descripcion) VALUES
|
|
('01', 'Efectivo'),
|
|
('02', 'Cheque nominativo'),
|
|
('03', 'Transferencia electrónica de fondos'),
|
|
('04', 'Tarjeta de crédito'),
|
|
('28', 'Tarjeta de débito'),
|
|
('99', 'Por definir');
|
|
|
|
INSERT INTO retail.sat_metodo_pago (clave, descripcion) VALUES
|
|
('PUE', 'Pago en una sola exhibición'),
|
|
('PPD', 'Pago en parcialidades o diferido');
|
|
|
|
INSERT INTO retail.sat_uso_cfdi (clave, descripcion, persona_fisica, persona_moral) VALUES
|
|
('G01', 'Adquisición de mercancías', true, true),
|
|
('G03', 'Gastos en general', true, true),
|
|
('P01', 'Por definir', true, true);
|
|
|
|
INSERT INTO retail.sat_regimen_fiscal (clave, descripcion, persona_fisica, persona_moral) VALUES
|
|
('601', 'General de Ley Personas Morales', false, true),
|
|
('612', 'Personas Físicas con Actividades Empresariales y Profesionales', true, false),
|
|
('621', 'Incorporación Fiscal', true, false),
|
|
('626', 'Régimen Simplificado de Confianza', true, true);
|
|
```
|
|
|
|
---
|
|
|
|
## 6. CHECKLIST DE IMPLEMENTACION
|
|
|
|
### Sprint 1-5: Core Prerequisites
|
|
- [ ] Verificar schema auth completo
|
|
- [ ] Completar MGN-001 Auth al 100%
|
|
- [ ] Implementar MGN-005 Catalogs
|
|
- [ ] Migrar MGN-010 Financial a TypeORM
|
|
- [ ] Completar MGN-011 Inventory
|
|
- [ ] Completar MGN-013 Sales
|
|
|
|
### Sprint 6-15: Retail Schema
|
|
- [ ] Crear schema retail
|
|
- [ ] Sprint 6: Branches y cajas
|
|
- [ ] Sprint 7: POS
|
|
- [ ] Sprint 8: Caja
|
|
- [ ] Sprint 9: Inventario multi-sucursal
|
|
- [ ] Sprint 10: Lealtad
|
|
- [ ] Sprint 11: Promociones
|
|
- [ ] Sprint 12: Compras
|
|
- [ ] Sprint 13: CFDI
|
|
- [ ] Sprint 14: E-commerce
|
|
- [ ] Sprint 15: Vistas materializadas
|
|
|
|
### Post-implementacion
|
|
- [ ] Ejecutar todas las migraciones
|
|
- [ ] Verificar RLS en todas las tablas
|
|
- [ ] Cargar seeds de catalogos
|
|
- [ ] Validar indices de performance
|
|
- [ ] Configurar cron de refresh de vistas
|
|
|
|
---
|
|
|
|
**Estado:** PLAN COMPLETO
|
|
**Fecha estimada:** 15 sprints (7.5 semanas con sprints de 3 dias)
|