Sistema NEXUS v3.4 migrado con: Estructura principal: - core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles) - core/catalog: Catalogo de funcionalidades reutilizables - shared/knowledge-base: Base de conocimiento compartida - devtools/scripts: Herramientas de desarrollo - control-plane/registries: Control de servicios y CI/CD - orchestration/: Configuracion de orquestacion de agentes Proyectos incluidos (11): - gamilit (submodule -> GitHub) - trading-platform (OrbiquanTIA) - erp-suite con 5 verticales: - erp-core, construccion, vidrio-templado - mecanicas-diesel, retail, clinicas - betting-analytics - inmobiliaria-analytics - platform_marketing_content - pos-micro, erp-basico Configuracion: - .gitignore completo para Node.js/Python/Docker - gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git) - Sistema de puertos estandarizado (3005-3199) Generated with NEXUS v3.4 Migration System EPIC-010: Configuracion Git y Repositorios
46 KiB
46 KiB
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
-- =====================================================
-- 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
-- =====================================================
-- 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
-- =====================================================
-- 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
-- =====================================================
-- 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
-- =====================================================
-- 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
-- =====================================================
-- 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
-- =====================================================
-- 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
-- =====================================================
-- 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
-- =====================================================
-- 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
-- =====================================================
-- 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
// 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)
-- 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)