- Prefijo v2: MCH - TRACEABILITY-MASTER.yml creado - Listo para integracion como submodulo Workspace: v2.0.0 | SIMCO: v4.0.0
2280 lines
62 KiB
Markdown
2280 lines
62 KiB
Markdown
---
|
|
id: SPEC-ARQUITECTURA-DATABASE
|
|
type: Specification
|
|
title: "MiChangarrito - Arquitectura de Base de Datos"
|
|
status: Published
|
|
created_at: 2026-01-04
|
|
updated_at: 2026-01-10
|
|
simco_version: "3.8.0"
|
|
author: "Equipo MiChangarrito"
|
|
tags:
|
|
- database
|
|
- postgresql
|
|
- arquitectura
|
|
- multi-tenant
|
|
- rls
|
|
---
|
|
|
|
# MiChangarrito - Arquitectura de Base de Datos
|
|
|
|
## Resumen
|
|
|
|
- **Motor:** PostgreSQL 15
|
|
- **Puerto desarrollo:** 5432 (instancia compartida del workspace)
|
|
- **Base de datos:** michangarrito_dev
|
|
- **Usuario:** michangarrito_dev
|
|
- **Arquitectura:** Multi-tenant con Row Level Security (RLS)
|
|
|
|
---
|
|
|
|
## Schemas
|
|
|
|
| Schema | Proposito | Tablas Principales |
|
|
|--------|-----------|-------------------|
|
|
| `public` | Tenants y configuracion global | tenants, tenant_configs, tenant_integration_credentials |
|
|
| `auth` | Autenticacion y usuarios | users, sessions, otp_codes |
|
|
| `catalog` | Productos y categorias | products, categories, product_templates |
|
|
| `sales` | Ventas, pagos y CoDi/SPEI | sales, sale_items, payments, daily_closures, codi_transactions |
|
|
| `inventory` | Stock y movimientos | inventory_movements, stock_alerts |
|
|
| `customers` | Clientes y fiados | customers, fiados, fiado_payments |
|
|
| `orders` | Pedidos de clientes | orders, order_items |
|
|
| `subscriptions` | Planes, tokens y referidos | plans, subscriptions, token_usage, referral_codes |
|
|
| `messaging` | WhatsApp y notificaciones | conversations, messages, notifications |
|
|
| `billing` | Facturacion electronica SAT | tax_configs, invoices, invoice_items |
|
|
| `marketplace` | Marketplace B2B proveedores | suppliers, supplier_products, supplier_orders |
|
|
| `integrations` | Configuracion de integraciones | integration_configs, integration_logs |
|
|
|
|
**Total: 12 schemas, ~49 tablas**
|
|
|
|
---
|
|
|
|
## Schema: public
|
|
|
|
### tenants
|
|
Tabla raiz multi-tenant.
|
|
|
|
```sql
|
|
CREATE TABLE public.tenants (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Identificacion
|
|
name VARCHAR(100) NOT NULL,
|
|
slug VARCHAR(50) UNIQUE NOT NULL,
|
|
business_type VARCHAR(50) NOT NULL, -- abarrotes, comida, fonda, etc.
|
|
|
|
-- Contacto
|
|
phone VARCHAR(20) NOT NULL,
|
|
email VARCHAR(100),
|
|
address TEXT,
|
|
city VARCHAR(50),
|
|
state VARCHAR(50),
|
|
zip_code VARCHAR(10),
|
|
|
|
-- Configuracion
|
|
timezone VARCHAR(50) DEFAULT 'America/Mexico_City',
|
|
currency VARCHAR(3) DEFAULT 'MXN',
|
|
tax_rate DECIMAL(5,2) DEFAULT 16.00,
|
|
tax_included BOOLEAN DEFAULT true,
|
|
|
|
-- WhatsApp
|
|
whatsapp_number VARCHAR(20),
|
|
whatsapp_verified BOOLEAN DEFAULT false,
|
|
uses_platform_number BOOLEAN DEFAULT true,
|
|
|
|
-- Suscripcion (referencia)
|
|
current_plan_id UUID,
|
|
subscription_status VARCHAR(20) DEFAULT 'trial', -- trial, active, past_due, cancelled
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'active',
|
|
onboarding_completed BOOLEAN DEFAULT false,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_tenants_slug ON public.tenants(slug);
|
|
CREATE INDEX idx_tenants_phone ON public.tenants(phone);
|
|
CREATE INDEX idx_tenants_status ON public.tenants(status);
|
|
```
|
|
|
|
### tenant_configs
|
|
Configuraciones adicionales por tenant.
|
|
|
|
```sql
|
|
CREATE TABLE public.tenant_configs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Horarios
|
|
opening_hour TIME DEFAULT '08:00',
|
|
closing_hour TIME DEFAULT '22:00',
|
|
working_days INTEGER[] DEFAULT ARRAY[1,2,3,4,5,6], -- 0=domingo
|
|
|
|
-- Tickets
|
|
ticket_header TEXT,
|
|
ticket_footer TEXT DEFAULT 'Gracias por su compra',
|
|
print_logo BOOLEAN DEFAULT false,
|
|
|
|
-- Notificaciones
|
|
daily_summary_enabled BOOLEAN DEFAULT true,
|
|
daily_summary_time TIME DEFAULT '21:00',
|
|
low_stock_alerts BOOLEAN DEFAULT true,
|
|
|
|
-- Fiados
|
|
fiados_enabled BOOLEAN DEFAULT true,
|
|
default_fiado_limit DECIMAL(10,2) DEFAULT 500.00,
|
|
fiado_reminder_days INTEGER DEFAULT 7,
|
|
|
|
-- Pedidos
|
|
delivery_enabled BOOLEAN DEFAULT false,
|
|
delivery_fee DECIMAL(10,2) DEFAULT 0.00,
|
|
delivery_radius_km DECIMAL(5,2),
|
|
|
|
-- Metodos de pago habilitados
|
|
payment_cash BOOLEAN DEFAULT true,
|
|
payment_card_mercadopago BOOLEAN DEFAULT false,
|
|
payment_card_clip BOOLEAN DEFAULT false,
|
|
payment_codi BOOLEAN DEFAULT false,
|
|
payment_transfer BOOLEAN DEFAULT false,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id)
|
|
);
|
|
```
|
|
|
|
### tenant_integration_credentials
|
|
Credenciales de integraciones externas por tenant.
|
|
|
|
```sql
|
|
CREATE TABLE public.tenant_integration_credentials (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Tipo de integracion
|
|
integration_type VARCHAR(20) NOT NULL, -- whatsapp, llm, stripe, mercadopago, clip
|
|
provider VARCHAR(50) NOT NULL, -- meta, openai, openrouter, anthropic, ollama, azure_openai
|
|
|
|
-- Credenciales (encriptadas)
|
|
credentials JSONB NOT NULL, -- api_key, access_token, etc.
|
|
|
|
-- Estado
|
|
is_active BOOLEAN DEFAULT true,
|
|
last_validated_at TIMESTAMPTZ,
|
|
validation_error TEXT,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id, integration_type, provider)
|
|
);
|
|
|
|
CREATE INDEX idx_integration_credentials_tenant ON public.tenant_integration_credentials(tenant_id);
|
|
CREATE INDEX idx_integration_credentials_type ON public.tenant_integration_credentials(integration_type);
|
|
```
|
|
|
|
### tenant_whatsapp_numbers
|
|
Numeros de WhatsApp configurados por tenant.
|
|
|
|
```sql
|
|
CREATE TABLE public.tenant_whatsapp_numbers (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Numero
|
|
phone_number VARCHAR(20) NOT NULL,
|
|
phone_number_id VARCHAR(50), -- ID de Meta
|
|
display_name VARCHAR(100),
|
|
|
|
-- Estado
|
|
quality_rating VARCHAR(20), -- green, yellow, red
|
|
is_verified BOOLEAN DEFAULT false,
|
|
is_primary BOOLEAN DEFAULT false,
|
|
|
|
-- Configuracion
|
|
webhook_url TEXT,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id, phone_number)
|
|
);
|
|
|
|
CREATE INDEX idx_whatsapp_numbers_tenant ON public.tenant_whatsapp_numbers(tenant_id);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: auth
|
|
|
|
### users
|
|
Usuarios del sistema (duenos y empleados).
|
|
|
|
```sql
|
|
CREATE TABLE auth.users (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Identificacion
|
|
phone VARCHAR(20) NOT NULL,
|
|
email VARCHAR(100),
|
|
name VARCHAR(100) NOT NULL,
|
|
|
|
-- Autenticacion
|
|
pin_hash VARCHAR(255), -- PIN de 4 digitos hasheado
|
|
biometric_enabled BOOLEAN DEFAULT false,
|
|
biometric_key TEXT,
|
|
|
|
-- Rol
|
|
role VARCHAR(20) NOT NULL DEFAULT 'owner', -- owner, employee, viewer
|
|
permissions JSONB DEFAULT '{}',
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'active',
|
|
last_login_at TIMESTAMPTZ,
|
|
failed_attempts INTEGER DEFAULT 0,
|
|
locked_until TIMESTAMPTZ,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id, phone)
|
|
);
|
|
|
|
CREATE INDEX idx_users_tenant ON auth.users(tenant_id);
|
|
CREATE INDEX idx_users_phone ON auth.users(phone);
|
|
```
|
|
|
|
### sessions
|
|
Sesiones activas.
|
|
|
|
```sql
|
|
CREATE TABLE auth.sessions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
|
|
-- Token
|
|
token_hash VARCHAR(255) NOT NULL,
|
|
refresh_token_hash VARCHAR(255),
|
|
|
|
-- Metadata
|
|
device_type VARCHAR(20), -- mobile, web
|
|
device_info JSONB,
|
|
ip_address VARCHAR(45),
|
|
|
|
-- Expiracion
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
refresh_expires_at TIMESTAMPTZ,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
last_activity_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_sessions_user ON auth.sessions(user_id);
|
|
CREATE INDEX idx_sessions_token ON auth.sessions(token_hash);
|
|
```
|
|
|
|
### otp_codes
|
|
Codigos OTP para verificacion.
|
|
|
|
```sql
|
|
CREATE TABLE auth.otp_codes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
phone VARCHAR(20) NOT NULL,
|
|
|
|
code VARCHAR(6) NOT NULL,
|
|
purpose VARCHAR(20) NOT NULL, -- login, verify_phone, reset_pin
|
|
|
|
attempts INTEGER DEFAULT 0,
|
|
max_attempts INTEGER DEFAULT 3,
|
|
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
used_at TIMESTAMPTZ,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_otp_phone ON auth.otp_codes(phone, purpose);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: catalog
|
|
|
|
### categories
|
|
Categorias de productos.
|
|
|
|
```sql
|
|
CREATE TABLE catalog.categories (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
name VARCHAR(50) NOT NULL,
|
|
description TEXT,
|
|
icon VARCHAR(50),
|
|
color VARCHAR(7), -- hex color
|
|
sort_order INTEGER DEFAULT 0,
|
|
|
|
status VARCHAR(20) DEFAULT 'active',
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id, name)
|
|
);
|
|
|
|
-- RLS
|
|
ALTER TABLE catalog.categories ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON catalog.categories
|
|
USING (tenant_id = current_setting('app.current_tenant')::UUID);
|
|
```
|
|
|
|
### products
|
|
Catalogo de productos.
|
|
|
|
```sql
|
|
CREATE TABLE catalog.products (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
category_id UUID REFERENCES catalog.categories(id) ON DELETE SET NULL,
|
|
|
|
-- Identificacion
|
|
name VARCHAR(100) NOT NULL,
|
|
description TEXT,
|
|
sku VARCHAR(50),
|
|
barcode VARCHAR(50),
|
|
|
|
-- Precios
|
|
price DECIMAL(10,2) NOT NULL,
|
|
cost_price DECIMAL(10,2), -- precio de compra
|
|
compare_price DECIMAL(10,2), -- precio anterior/tachado
|
|
|
|
-- Inventario
|
|
track_inventory BOOLEAN DEFAULT true,
|
|
stock_quantity INTEGER DEFAULT 0,
|
|
low_stock_threshold INTEGER DEFAULT 5,
|
|
|
|
-- Presentacion
|
|
unit VARCHAR(20) DEFAULT 'pieza', -- pieza, kg, litro, etc.
|
|
|
|
-- Multimedia
|
|
image_url TEXT,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'active',
|
|
is_featured BOOLEAN DEFAULT false,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_products_tenant ON catalog.products(tenant_id);
|
|
CREATE INDEX idx_products_category ON catalog.products(category_id);
|
|
CREATE INDEX idx_products_barcode ON catalog.products(tenant_id, barcode);
|
|
CREATE INDEX idx_products_name ON catalog.products USING gin(to_tsvector('spanish', name));
|
|
|
|
-- RLS
|
|
ALTER TABLE catalog.products ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON catalog.products
|
|
USING (tenant_id = current_setting('app.current_tenant')::UUID);
|
|
```
|
|
|
|
### product_templates
|
|
Templates de productos por proveedor (Bimbo, Coca-Cola, etc.).
|
|
|
|
```sql
|
|
CREATE TABLE catalog.product_templates (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Proveedor
|
|
provider_name VARCHAR(50) NOT NULL, -- bimbo, cocacola, sabritas, etc.
|
|
provider_logo_url TEXT,
|
|
|
|
-- Producto
|
|
name VARCHAR(100) NOT NULL,
|
|
description TEXT,
|
|
barcode VARCHAR(50),
|
|
suggested_price DECIMAL(10,2),
|
|
category_suggestion VARCHAR(50),
|
|
|
|
-- Presentaciones
|
|
unit VARCHAR(20) DEFAULT 'pieza',
|
|
|
|
-- Multimedia
|
|
image_url TEXT,
|
|
|
|
-- Metadata
|
|
business_types TEXT[], -- ['abarrotes', 'tienda']
|
|
popularity INTEGER DEFAULT 0,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_templates_provider ON catalog.product_templates(provider_name);
|
|
CREATE INDEX idx_templates_barcode ON catalog.product_templates(barcode);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: sales
|
|
|
|
### sales
|
|
Registro de ventas.
|
|
|
|
```sql
|
|
CREATE TABLE sales.sales (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Numeracion
|
|
ticket_number VARCHAR(20) NOT NULL,
|
|
|
|
-- Montos
|
|
subtotal DECIMAL(10,2) NOT NULL,
|
|
discount_amount DECIMAL(10,2) DEFAULT 0,
|
|
discount_percent DECIMAL(5,2) DEFAULT 0,
|
|
tax_amount DECIMAL(10,2) DEFAULT 0,
|
|
total DECIMAL(10,2) NOT NULL,
|
|
|
|
-- Pago
|
|
payment_method VARCHAR(20) NOT NULL, -- cash, card_mercadopago, card_clip, codi, transfer, fiado
|
|
payment_status VARCHAR(20) DEFAULT 'completed', -- pending, completed, refunded
|
|
payment_reference TEXT, -- referencia externa del pago
|
|
|
|
-- Efectivo
|
|
cash_received DECIMAL(10,2),
|
|
change_amount DECIMAL(10,2),
|
|
|
|
-- Cliente (opcional)
|
|
customer_id UUID REFERENCES customers.customers(id),
|
|
|
|
-- Fiado (si aplica)
|
|
is_fiado BOOLEAN DEFAULT false,
|
|
fiado_id UUID,
|
|
|
|
-- Usuario que registro
|
|
created_by UUID REFERENCES auth.users(id),
|
|
|
|
-- Notas
|
|
notes TEXT,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'completed', -- completed, cancelled, refunded
|
|
cancelled_at TIMESTAMPTZ,
|
|
cancelled_reason TEXT,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_sales_tenant ON sales.sales(tenant_id);
|
|
CREATE INDEX idx_sales_ticket ON sales.sales(tenant_id, ticket_number);
|
|
CREATE INDEX idx_sales_date ON sales.sales(tenant_id, created_at);
|
|
CREATE INDEX idx_sales_customer ON sales.sales(customer_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE sales.sales ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON sales.sales
|
|
USING (tenant_id = current_setting('app.current_tenant')::UUID);
|
|
```
|
|
|
|
### sale_items
|
|
Detalle de productos vendidos.
|
|
|
|
```sql
|
|
CREATE TABLE sales.sale_items (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
sale_id UUID NOT NULL REFERENCES sales.sales(id) ON DELETE CASCADE,
|
|
product_id UUID REFERENCES catalog.products(id),
|
|
|
|
-- Producto (snapshot)
|
|
product_name VARCHAR(100) NOT NULL,
|
|
product_sku VARCHAR(50),
|
|
|
|
-- Cantidades
|
|
quantity DECIMAL(10,3) NOT NULL,
|
|
unit_price DECIMAL(10,2) NOT NULL,
|
|
|
|
-- Descuento por item
|
|
discount_amount DECIMAL(10,2) DEFAULT 0,
|
|
|
|
-- Total
|
|
subtotal DECIMAL(10,2) NOT NULL,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_sale_items_sale ON sales.sale_items(sale_id);
|
|
CREATE INDEX idx_sale_items_product ON sales.sale_items(product_id);
|
|
```
|
|
|
|
### payments
|
|
Registro de pagos (para pagos parciales o multiples metodos).
|
|
|
|
```sql
|
|
CREATE TABLE sales.payments (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
sale_id UUID REFERENCES sales.sales(id),
|
|
fiado_id UUID, -- Si es pago de fiado
|
|
subscription_id UUID, -- Si es pago de suscripcion
|
|
|
|
-- Metodo
|
|
method VARCHAR(20) NOT NULL,
|
|
provider VARCHAR(20), -- mercadopago, clip, stripe, oxxo
|
|
|
|
-- Montos
|
|
amount DECIMAL(10,2) NOT NULL,
|
|
fee_amount DECIMAL(10,2) DEFAULT 0, -- comision del proveedor
|
|
net_amount DECIMAL(10,2), -- monto neto
|
|
|
|
-- Referencias
|
|
external_id TEXT, -- ID del proveedor
|
|
external_status VARCHAR(20),
|
|
receipt_url TEXT,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'pending', -- pending, completed, failed, refunded
|
|
|
|
-- Metadata
|
|
metadata JSONB,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_payments_tenant ON sales.payments(tenant_id);
|
|
CREATE INDEX idx_payments_sale ON sales.payments(sale_id);
|
|
CREATE INDEX idx_payments_external ON sales.payments(external_id);
|
|
```
|
|
|
|
### daily_closures
|
|
Cortes de caja.
|
|
|
|
```sql
|
|
CREATE TABLE sales.daily_closures (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Periodo
|
|
closure_date DATE NOT NULL,
|
|
opened_at TIMESTAMPTZ,
|
|
closed_at TIMESTAMPTZ,
|
|
|
|
-- Montos esperados (calculados)
|
|
expected_cash DECIMAL(10,2) DEFAULT 0,
|
|
expected_card DECIMAL(10,2) DEFAULT 0,
|
|
expected_other DECIMAL(10,2) DEFAULT 0,
|
|
expected_total DECIMAL(10,2) DEFAULT 0,
|
|
|
|
-- Montos reales (ingresados)
|
|
actual_cash DECIMAL(10,2),
|
|
actual_card DECIMAL(10,2),
|
|
actual_other DECIMAL(10,2),
|
|
actual_total DECIMAL(10,2),
|
|
|
|
-- Diferencia
|
|
cash_difference DECIMAL(10,2),
|
|
|
|
-- Resumen
|
|
total_sales INTEGER DEFAULT 0,
|
|
total_cancelled INTEGER DEFAULT 0,
|
|
total_fiados DECIMAL(10,2) DEFAULT 0,
|
|
|
|
-- Usuario
|
|
closed_by UUID REFERENCES auth.users(id),
|
|
notes TEXT,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'open', -- open, closed
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id, closure_date)
|
|
);
|
|
```
|
|
|
|
### virtual_accounts
|
|
Cuentas CLABE virtuales para recibir SPEI.
|
|
|
|
```sql
|
|
CREATE TABLE sales.virtual_accounts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- CLABE
|
|
clabe VARCHAR(18) NOT NULL UNIQUE,
|
|
bank_name VARCHAR(50),
|
|
holder_name VARCHAR(100),
|
|
|
|
-- Proveedor
|
|
provider VARCHAR(50) NOT NULL, -- stp, arcus, openpay
|
|
|
|
-- Estado
|
|
is_active BOOLEAN DEFAULT true,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_virtual_accounts_tenant ON sales.virtual_accounts(tenant_id);
|
|
CREATE INDEX idx_virtual_accounts_clabe ON sales.virtual_accounts(clabe);
|
|
```
|
|
|
|
### codi_transactions
|
|
Transacciones de pago CoDi.
|
|
|
|
```sql
|
|
CREATE TABLE sales.codi_transactions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
sale_id UUID REFERENCES sales.sales(id),
|
|
|
|
-- Referencia
|
|
reference VARCHAR(50) NOT NULL UNIQUE,
|
|
amount DECIMAL(10,2) NOT NULL,
|
|
description VARCHAR(100),
|
|
|
|
-- QR
|
|
qr_data TEXT NOT NULL,
|
|
qr_url TEXT,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'pending', -- pending, completed, expired, failed
|
|
|
|
-- Fechas
|
|
completed_at TIMESTAMPTZ,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_codi_transactions_tenant ON sales.codi_transactions(tenant_id);
|
|
CREATE INDEX idx_codi_transactions_reference ON sales.codi_transactions(reference);
|
|
CREATE INDEX idx_codi_transactions_status ON sales.codi_transactions(status);
|
|
```
|
|
|
|
### spei_transactions
|
|
Transacciones SPEI recibidas.
|
|
|
|
```sql
|
|
CREATE TABLE sales.spei_transactions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
virtual_account_id UUID REFERENCES sales.virtual_accounts(id),
|
|
sale_id UUID REFERENCES sales.sales(id),
|
|
|
|
-- Datos SPEI
|
|
clave_rastreo VARCHAR(30) NOT NULL UNIQUE,
|
|
sender_clabe VARCHAR(18),
|
|
sender_name VARCHAR(100),
|
|
sender_rfc VARCHAR(13),
|
|
amount DECIMAL(10,2) NOT NULL,
|
|
concept VARCHAR(100),
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'received', -- received, processed, returned
|
|
|
|
-- Fechas
|
|
received_at TIMESTAMPTZ NOT NULL,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_spei_transactions_tenant ON sales.spei_transactions(tenant_id);
|
|
CREATE INDEX idx_spei_transactions_clave ON sales.spei_transactions(clave_rastreo);
|
|
```
|
|
|
|
### payment_config
|
|
Configuracion de pagos CoDi/SPEI por tenant.
|
|
|
|
```sql
|
|
CREATE TABLE sales.payment_config (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Habilitacion
|
|
codi_enabled BOOLEAN DEFAULT false,
|
|
spei_enabled BOOLEAN DEFAULT false,
|
|
|
|
-- Proveedor
|
|
provider VARCHAR(50), -- stp, arcus, openpay
|
|
credentials JSONB, -- Credenciales encriptadas
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id)
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: inventory
|
|
|
|
### inventory_movements
|
|
Movimientos de inventario.
|
|
|
|
```sql
|
|
CREATE TABLE inventory.inventory_movements (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
product_id UUID NOT NULL REFERENCES catalog.products(id) ON DELETE CASCADE,
|
|
|
|
-- Tipo
|
|
movement_type VARCHAR(20) NOT NULL, -- purchase, sale, adjustment, loss, return
|
|
|
|
-- Cantidades
|
|
quantity DECIMAL(10,3) NOT NULL, -- positivo o negativo
|
|
previous_stock DECIMAL(10,3) NOT NULL,
|
|
new_stock DECIMAL(10,3) NOT NULL,
|
|
|
|
-- Costo (para compras)
|
|
unit_cost DECIMAL(10,2),
|
|
total_cost DECIMAL(10,2),
|
|
|
|
-- Referencia
|
|
reference_type VARCHAR(20), -- sale, purchase_order, manual
|
|
reference_id UUID,
|
|
|
|
-- Notas
|
|
notes TEXT,
|
|
|
|
-- Usuario
|
|
created_by UUID REFERENCES auth.users(id),
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_inventory_tenant ON inventory.inventory_movements(tenant_id);
|
|
CREATE INDEX idx_inventory_product ON inventory.inventory_movements(product_id);
|
|
CREATE INDEX idx_inventory_date ON inventory.inventory_movements(created_at);
|
|
```
|
|
|
|
### stock_alerts
|
|
Alertas de stock bajo.
|
|
|
|
```sql
|
|
CREATE TABLE inventory.stock_alerts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
product_id UUID NOT NULL REFERENCES catalog.products(id) ON DELETE CASCADE,
|
|
|
|
-- Niveles
|
|
current_stock INTEGER NOT NULL,
|
|
threshold INTEGER NOT NULL,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'active', -- active, resolved, ignored
|
|
|
|
-- Notificacion
|
|
notified_at TIMESTAMPTZ,
|
|
resolved_at TIMESTAMPTZ,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: customers
|
|
|
|
### customers
|
|
Clientes del negocio.
|
|
|
|
```sql
|
|
CREATE TABLE customers.customers (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Identificacion
|
|
name VARCHAR(100) NOT NULL,
|
|
phone VARCHAR(20),
|
|
email VARCHAR(100),
|
|
|
|
-- Direccion (para entregas)
|
|
address TEXT,
|
|
address_reference TEXT,
|
|
latitude DECIMAL(10,8),
|
|
longitude DECIMAL(11,8),
|
|
|
|
-- Fiados
|
|
fiado_enabled BOOLEAN DEFAULT true,
|
|
fiado_limit DECIMAL(10,2),
|
|
current_fiado_balance DECIMAL(10,2) DEFAULT 0,
|
|
|
|
-- Estadisticas
|
|
total_purchases DECIMAL(12,2) DEFAULT 0,
|
|
purchase_count INTEGER DEFAULT 0,
|
|
last_purchase_at TIMESTAMPTZ,
|
|
|
|
-- WhatsApp
|
|
whatsapp_opt_in BOOLEAN DEFAULT false,
|
|
|
|
-- Notas
|
|
notes TEXT,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'active',
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_customers_tenant ON customers.customers(tenant_id);
|
|
CREATE INDEX idx_customers_phone ON customers.customers(tenant_id, phone);
|
|
CREATE INDEX idx_customers_name ON customers.customers USING gin(to_tsvector('spanish', name));
|
|
|
|
-- RLS
|
|
ALTER TABLE customers.customers ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON customers.customers
|
|
USING (tenant_id = current_setting('app.current_tenant')::UUID);
|
|
```
|
|
|
|
### fiados
|
|
Registro de fiados (creditos a clientes).
|
|
|
|
```sql
|
|
CREATE TABLE customers.fiados (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
customer_id UUID NOT NULL REFERENCES customers.customers(id) ON DELETE CASCADE,
|
|
sale_id UUID REFERENCES sales.sales(id),
|
|
|
|
-- Monto
|
|
original_amount DECIMAL(10,2) NOT NULL,
|
|
paid_amount DECIMAL(10,2) DEFAULT 0,
|
|
remaining_amount DECIMAL(10,2) NOT NULL,
|
|
|
|
-- Fechas
|
|
due_date DATE,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'pending', -- pending, partial, paid, overdue, cancelled
|
|
|
|
-- Notas
|
|
description TEXT,
|
|
|
|
-- Recordatorios
|
|
last_reminder_at TIMESTAMPTZ,
|
|
reminder_count INTEGER DEFAULT 0,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_fiados_tenant ON customers.fiados(tenant_id);
|
|
CREATE INDEX idx_fiados_customer ON customers.fiados(customer_id);
|
|
CREATE INDEX idx_fiados_status ON customers.fiados(status);
|
|
```
|
|
|
|
### fiado_payments
|
|
Pagos de fiados.
|
|
|
|
```sql
|
|
CREATE TABLE customers.fiado_payments (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
fiado_id UUID NOT NULL REFERENCES customers.fiados(id) ON DELETE CASCADE,
|
|
|
|
amount DECIMAL(10,2) NOT NULL,
|
|
payment_method VARCHAR(20) NOT NULL,
|
|
|
|
notes TEXT,
|
|
|
|
created_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: orders
|
|
|
|
### orders
|
|
Pedidos de clientes (via WhatsApp u otros).
|
|
|
|
```sql
|
|
CREATE TABLE orders.orders (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
customer_id UUID REFERENCES customers.customers(id),
|
|
|
|
-- Numeracion
|
|
order_number VARCHAR(20) NOT NULL,
|
|
|
|
-- Canal
|
|
channel VARCHAR(20) NOT NULL, -- whatsapp, app, web
|
|
|
|
-- Montos
|
|
subtotal DECIMAL(10,2) NOT NULL,
|
|
delivery_fee DECIMAL(10,2) DEFAULT 0,
|
|
discount_amount DECIMAL(10,2) DEFAULT 0,
|
|
total DECIMAL(10,2) NOT NULL,
|
|
|
|
-- Tipo
|
|
order_type VARCHAR(20) NOT NULL, -- pickup, delivery
|
|
|
|
-- Entrega
|
|
delivery_address TEXT,
|
|
delivery_notes TEXT,
|
|
estimated_delivery_at TIMESTAMPTZ,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'pending',
|
|
-- pending, confirmed, preparing, ready, delivering, completed, cancelled
|
|
|
|
-- Pago
|
|
payment_status VARCHAR(20) DEFAULT 'pending', -- pending, paid, refunded
|
|
payment_method VARCHAR(20),
|
|
|
|
-- Timestamps
|
|
confirmed_at TIMESTAMPTZ,
|
|
preparing_at TIMESTAMPTZ,
|
|
ready_at TIMESTAMPTZ,
|
|
completed_at TIMESTAMPTZ,
|
|
cancelled_at TIMESTAMPTZ,
|
|
cancelled_reason TEXT,
|
|
|
|
-- Notas
|
|
customer_notes TEXT,
|
|
internal_notes TEXT,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_orders_tenant ON orders.orders(tenant_id);
|
|
CREATE INDEX idx_orders_customer ON orders.orders(customer_id);
|
|
CREATE INDEX idx_orders_status ON orders.orders(status);
|
|
CREATE INDEX idx_orders_date ON orders.orders(created_at);
|
|
```
|
|
|
|
### order_items
|
|
Items del pedido.
|
|
|
|
```sql
|
|
CREATE TABLE orders.order_items (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
order_id UUID NOT NULL REFERENCES orders.orders(id) ON DELETE CASCADE,
|
|
product_id UUID REFERENCES catalog.products(id),
|
|
|
|
-- Producto (snapshot)
|
|
product_name VARCHAR(100) NOT NULL,
|
|
|
|
-- Cantidades
|
|
quantity DECIMAL(10,3) NOT NULL,
|
|
unit_price DECIMAL(10,2) NOT NULL,
|
|
subtotal DECIMAL(10,2) NOT NULL,
|
|
|
|
-- Notas especiales
|
|
notes TEXT,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: subscriptions
|
|
|
|
### plans
|
|
Planes de suscripcion.
|
|
|
|
```sql
|
|
CREATE TABLE subscriptions.plans (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Identificacion
|
|
name VARCHAR(50) NOT NULL,
|
|
code VARCHAR(20) UNIQUE NOT NULL, -- changarrito, tiendita
|
|
description TEXT,
|
|
|
|
-- Precio
|
|
price_monthly DECIMAL(10,2) NOT NULL,
|
|
price_yearly DECIMAL(10,2),
|
|
currency VARCHAR(3) DEFAULT 'MXN',
|
|
|
|
-- Incluido
|
|
included_tokens INTEGER NOT NULL, -- tokens IA incluidos
|
|
features JSONB, -- lista de features
|
|
|
|
-- Limites
|
|
max_products INTEGER,
|
|
max_users INTEGER DEFAULT 1,
|
|
whatsapp_own_number BOOLEAN DEFAULT false,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'active',
|
|
|
|
-- Stripe
|
|
stripe_price_id_monthly VARCHAR(100),
|
|
stripe_price_id_yearly VARCHAR(100),
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Insertar planes iniciales
|
|
INSERT INTO subscriptions.plans (name, code, price_monthly, included_tokens, max_products, features) VALUES
|
|
('Changarrito', 'changarrito', 99.00, 500, 100, '{"pos": true, "inventory": true, "reports_basic": true}'),
|
|
('Tiendita', 'tiendita', 199.00, 2000, null, '{"pos": true, "inventory": true, "reports_advanced": true, "whatsapp_own": true, "customers": true, "fiados": true}');
|
|
```
|
|
|
|
### subscriptions
|
|
Suscripciones activas.
|
|
|
|
```sql
|
|
CREATE TABLE subscriptions.subscriptions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
plan_id UUID NOT NULL REFERENCES subscriptions.plans(id),
|
|
|
|
-- Periodo
|
|
billing_cycle VARCHAR(10) DEFAULT 'monthly', -- monthly, yearly
|
|
current_period_start TIMESTAMPTZ NOT NULL,
|
|
current_period_end TIMESTAMPTZ NOT NULL,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'active', -- trialing, active, past_due, cancelled, paused
|
|
cancel_at_period_end BOOLEAN DEFAULT false,
|
|
cancelled_at TIMESTAMPTZ,
|
|
|
|
-- Pagos
|
|
payment_method VARCHAR(20), -- card, oxxo, iap_ios, iap_android
|
|
|
|
-- Stripe
|
|
stripe_subscription_id VARCHAR(100),
|
|
stripe_customer_id VARCHAR(100),
|
|
|
|
-- Trial
|
|
trial_ends_at TIMESTAMPTZ,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_subscriptions_tenant ON subscriptions.subscriptions(tenant_id);
|
|
CREATE INDEX idx_subscriptions_stripe ON subscriptions.subscriptions(stripe_subscription_id);
|
|
```
|
|
|
|
### token_packages
|
|
Paquetes de tokens para compra.
|
|
|
|
```sql
|
|
CREATE TABLE subscriptions.token_packages (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
name VARCHAR(50) NOT NULL,
|
|
tokens INTEGER NOT NULL,
|
|
price DECIMAL(10,2) NOT NULL,
|
|
currency VARCHAR(3) DEFAULT 'MXN',
|
|
|
|
-- Bonus
|
|
bonus_tokens INTEGER DEFAULT 0,
|
|
|
|
-- Stripe
|
|
stripe_price_id VARCHAR(100),
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'active',
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Insertar paquetes
|
|
INSERT INTO subscriptions.token_packages (name, tokens, price) VALUES
|
|
('Recarga Basica', 1000, 29.00),
|
|
('Recarga Plus', 3000, 69.00),
|
|
('Recarga Pro', 8000, 149.00),
|
|
('Recarga Mega', 20000, 299.00);
|
|
```
|
|
|
|
### token_usage
|
|
Consumo de tokens.
|
|
|
|
```sql
|
|
CREATE TABLE subscriptions.token_usage (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Tokens
|
|
tokens_used INTEGER NOT NULL,
|
|
|
|
-- Contexto
|
|
action VARCHAR(50) NOT NULL, -- chat, report, ocr, transcription
|
|
description TEXT,
|
|
|
|
-- LLM info
|
|
model VARCHAR(50),
|
|
input_tokens INTEGER,
|
|
output_tokens INTEGER,
|
|
|
|
-- Referencia
|
|
reference_type VARCHAR(20),
|
|
reference_id UUID,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_token_usage_tenant ON subscriptions.token_usage(tenant_id);
|
|
CREATE INDEX idx_token_usage_date ON subscriptions.token_usage(created_at);
|
|
```
|
|
|
|
### tenant_token_balance
|
|
Balance de tokens por tenant.
|
|
|
|
```sql
|
|
CREATE TABLE subscriptions.tenant_token_balance (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Balance
|
|
available_tokens INTEGER DEFAULT 0,
|
|
used_tokens INTEGER DEFAULT 0,
|
|
|
|
-- Ultimo reset (mensual)
|
|
last_reset_at TIMESTAMPTZ,
|
|
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id)
|
|
);
|
|
```
|
|
|
|
### referral_codes
|
|
Codigos de referido por tenant.
|
|
|
|
```sql
|
|
CREATE TABLE subscriptions.referral_codes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Codigo
|
|
code VARCHAR(10) NOT NULL UNIQUE,
|
|
|
|
-- Beneficios
|
|
discount_percent INTEGER DEFAULT 10, -- % descuento para referido
|
|
reward_months INTEGER DEFAULT 1, -- Meses gratis para referidor
|
|
|
|
-- Limites
|
|
max_uses INTEGER, -- null = sin limite
|
|
current_uses INTEGER DEFAULT 0,
|
|
|
|
-- Estado
|
|
is_active BOOLEAN DEFAULT true,
|
|
expires_at TIMESTAMPTZ,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_referral_codes_tenant ON subscriptions.referral_codes(tenant_id);
|
|
CREATE INDEX idx_referral_codes_code ON subscriptions.referral_codes(code);
|
|
```
|
|
|
|
### referrals
|
|
Registro de referidos.
|
|
|
|
```sql
|
|
CREATE TABLE subscriptions.referrals (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Relacion
|
|
referrer_tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
referred_tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
code_id UUID NOT NULL REFERENCES subscriptions.referral_codes(id),
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'pending', -- pending, active, expired, rewarded
|
|
|
|
-- Fechas
|
|
referred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
activated_at TIMESTAMPTZ,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(referred_tenant_id) -- Un tenant solo puede ser referido una vez
|
|
);
|
|
|
|
CREATE INDEX idx_referrals_referrer ON subscriptions.referrals(referrer_tenant_id);
|
|
CREATE INDEX idx_referrals_status ON subscriptions.referrals(status);
|
|
```
|
|
|
|
### referral_rewards
|
|
Recompensas otorgadas por referidos.
|
|
|
|
```sql
|
|
CREATE TABLE subscriptions.referral_rewards (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
referral_id UUID NOT NULL REFERENCES subscriptions.referrals(id) ON DELETE CASCADE,
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Tipo de recompensa
|
|
reward_type VARCHAR(20) NOT NULL, -- free_month, discount
|
|
|
|
-- Valor
|
|
value DECIMAL(10,2) NOT NULL, -- Meses o porcentaje
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'pending', -- pending, applied, expired
|
|
|
|
-- Fechas
|
|
applied_at TIMESTAMPTZ,
|
|
expires_at TIMESTAMPTZ,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_referral_rewards_tenant ON subscriptions.referral_rewards(tenant_id);
|
|
CREATE INDEX idx_referral_rewards_status ON subscriptions.referral_rewards(status);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: messaging
|
|
|
|
### conversations
|
|
Conversaciones de WhatsApp.
|
|
|
|
```sql
|
|
CREATE TABLE messaging.conversations (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID REFERENCES public.tenants(id), -- null si es plataforma
|
|
|
|
-- Participante
|
|
phone_number VARCHAR(20) NOT NULL,
|
|
contact_name VARCHAR(100),
|
|
|
|
-- Tipo
|
|
conversation_type VARCHAR(20) NOT NULL, -- owner, customer, support, onboarding
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'active', -- active, archived, blocked
|
|
|
|
-- Ultimo mensaje
|
|
last_message_at TIMESTAMPTZ,
|
|
last_message_preview TEXT,
|
|
unread_count INTEGER DEFAULT 0,
|
|
|
|
-- WhatsApp
|
|
wa_conversation_id VARCHAR(100),
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_conversations_tenant ON messaging.conversations(tenant_id);
|
|
CREATE INDEX idx_conversations_phone ON messaging.conversations(phone_number);
|
|
```
|
|
|
|
### messages
|
|
Mensajes individuales.
|
|
|
|
```sql
|
|
CREATE TABLE messaging.messages (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
conversation_id UUID NOT NULL REFERENCES messaging.conversations(id) ON DELETE CASCADE,
|
|
|
|
-- Direccion
|
|
direction VARCHAR(10) NOT NULL, -- inbound, outbound
|
|
|
|
-- Contenido
|
|
message_type VARCHAR(20) NOT NULL, -- text, image, audio, video, document, location
|
|
content TEXT,
|
|
media_url TEXT,
|
|
media_mime_type VARCHAR(50),
|
|
|
|
-- LLM (si fue procesado)
|
|
processed_by_llm BOOLEAN DEFAULT false,
|
|
llm_response_id UUID,
|
|
tokens_used INTEGER,
|
|
|
|
-- WhatsApp
|
|
wa_message_id VARCHAR(100),
|
|
wa_status VARCHAR(20), -- sent, delivered, read, failed
|
|
wa_timestamp TIMESTAMPTZ,
|
|
|
|
-- Error
|
|
error_code VARCHAR(20),
|
|
error_message TEXT,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_messages_conversation ON messaging.messages(conversation_id);
|
|
CREATE INDEX idx_messages_wa ON messaging.messages(wa_message_id);
|
|
```
|
|
|
|
### notifications
|
|
Notificaciones push y WhatsApp.
|
|
|
|
```sql
|
|
CREATE TABLE messaging.notifications (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
user_id UUID REFERENCES auth.users(id),
|
|
|
|
-- Tipo
|
|
notification_type VARCHAR(50) NOT NULL, -- low_stock, new_order, fiado_reminder, daily_summary
|
|
|
|
-- Canales
|
|
channels TEXT[] NOT NULL, -- ['push', 'whatsapp']
|
|
|
|
-- Contenido
|
|
title VARCHAR(100) NOT NULL,
|
|
body TEXT NOT NULL,
|
|
data JSONB,
|
|
|
|
-- Estado por canal
|
|
push_sent BOOLEAN DEFAULT false,
|
|
push_sent_at TIMESTAMPTZ,
|
|
whatsapp_sent BOOLEAN DEFAULT false,
|
|
whatsapp_sent_at TIMESTAMPTZ,
|
|
|
|
-- Lectura
|
|
read_at TIMESTAMPTZ,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_notifications_tenant ON messaging.notifications(tenant_id);
|
|
CREATE INDEX idx_notifications_user ON messaging.notifications(user_id);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: billing
|
|
|
|
### tax_configs
|
|
Configuracion fiscal por tenant (RFC, CSD, PAC).
|
|
|
|
```sql
|
|
CREATE TABLE billing.tax_configs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Datos fiscales
|
|
rfc VARCHAR(13) NOT NULL,
|
|
razon_social VARCHAR(200) NOT NULL,
|
|
regimen_fiscal VARCHAR(10) NOT NULL, -- Codigo SAT
|
|
codigo_postal VARCHAR(5) NOT NULL,
|
|
|
|
-- Certificado de Sello Digital (CSD)
|
|
csd_cer TEXT, -- Certificado en base64
|
|
csd_key TEXT, -- Llave privada encriptada
|
|
csd_password TEXT, -- Password encriptado
|
|
|
|
-- PAC
|
|
pac_provider VARCHAR(50), -- facturapi, sw_sapien, etc
|
|
pac_credentials JSONB, -- Credenciales del PAC
|
|
|
|
-- Estado
|
|
is_active BOOLEAN DEFAULT true,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id)
|
|
);
|
|
```
|
|
|
|
### invoices
|
|
Facturas CFDI emitidas.
|
|
|
|
```sql
|
|
CREATE TABLE billing.invoices (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
sale_id UUID REFERENCES sales.sales(id),
|
|
|
|
-- Folio
|
|
folio VARCHAR(20) NOT NULL,
|
|
serie VARCHAR(10),
|
|
uuid_fiscal VARCHAR(36), -- UUID del SAT
|
|
|
|
-- Tipo
|
|
tipo_comprobante VARCHAR(1) NOT NULL DEFAULT 'I', -- I=Ingreso, E=Egreso, T=Traslado
|
|
|
|
-- Receptor
|
|
receptor_rfc VARCHAR(13) NOT NULL,
|
|
receptor_nombre VARCHAR(200) NOT NULL,
|
|
receptor_regimen VARCHAR(10),
|
|
receptor_uso_cfdi VARCHAR(10) NOT NULL, -- G03, etc
|
|
|
|
-- Montos
|
|
subtotal DECIMAL(12,2) NOT NULL,
|
|
total_impuestos DECIMAL(12,2) DEFAULT 0,
|
|
total DECIMAL(12,2) NOT NULL,
|
|
|
|
-- Pago
|
|
moneda VARCHAR(3) DEFAULT 'MXN',
|
|
tipo_cambio DECIMAL(10,4) DEFAULT 1,
|
|
forma_pago VARCHAR(2) NOT NULL, -- 01=Efectivo, 04=Tarjeta, 03=Transferencia
|
|
metodo_pago VARCHAR(3) NOT NULL, -- PUE o PPD
|
|
|
|
-- XML
|
|
xml_content TEXT,
|
|
pdf_url TEXT,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'draft', -- draft, stamped, cancelled, sent
|
|
|
|
-- Cancelacion
|
|
cancelled_at TIMESTAMPTZ,
|
|
cancellation_reason VARCHAR(2),
|
|
cancellation_uuid VARCHAR(36),
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id, serie, folio)
|
|
);
|
|
|
|
CREATE INDEX idx_invoices_tenant ON billing.invoices(tenant_id);
|
|
CREATE INDEX idx_invoices_uuid ON billing.invoices(uuid_fiscal);
|
|
CREATE INDEX idx_invoices_status ON billing.invoices(status);
|
|
```
|
|
|
|
### invoice_items
|
|
Items de factura.
|
|
|
|
```sql
|
|
CREATE TABLE billing.invoice_items (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
invoice_id UUID NOT NULL REFERENCES billing.invoices(id) ON DELETE CASCADE,
|
|
|
|
-- Producto SAT
|
|
clave_prod_serv VARCHAR(8) NOT NULL, -- Clave SAT
|
|
clave_unidad VARCHAR(3) NOT NULL, -- H87=Pieza, KGM=Kg, etc
|
|
descripcion VARCHAR(500) NOT NULL,
|
|
|
|
-- Cantidades
|
|
cantidad DECIMAL(10,3) NOT NULL,
|
|
valor_unitario DECIMAL(12,4) NOT NULL,
|
|
importe DECIMAL(12,2) NOT NULL,
|
|
|
|
-- Descuento
|
|
descuento DECIMAL(12,2) DEFAULT 0,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_invoice_items_invoice ON billing.invoice_items(invoice_id);
|
|
```
|
|
|
|
### invoice_item_taxes
|
|
Impuestos por item de factura.
|
|
|
|
```sql
|
|
CREATE TABLE billing.invoice_item_taxes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
invoice_item_id UUID NOT NULL REFERENCES billing.invoice_items(id) ON DELETE CASCADE,
|
|
|
|
-- Tipo
|
|
tipo VARCHAR(10) NOT NULL, -- traslado o retencion
|
|
impuesto VARCHAR(3) NOT NULL, -- 002=IVA, 003=IEPS
|
|
tipo_factor VARCHAR(10) NOT NULL, -- Tasa, Cuota, Exento
|
|
|
|
-- Valores
|
|
tasa_o_cuota DECIMAL(6,4) NOT NULL, -- 0.16 para IVA 16%
|
|
importe DECIMAL(12,2) NOT NULL,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
### invoice_history
|
|
Historial de cambios en facturas (auditoria).
|
|
|
|
```sql
|
|
CREATE TABLE billing.invoice_history (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
invoice_id UUID NOT NULL REFERENCES billing.invoices(id) ON DELETE CASCADE,
|
|
|
|
-- Accion
|
|
action VARCHAR(20) NOT NULL, -- created, stamped, cancelled, sent
|
|
|
|
-- Detalles
|
|
details JSONB,
|
|
|
|
-- Usuario
|
|
performed_by UUID REFERENCES auth.users(id),
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_invoice_history_invoice ON billing.invoice_history(invoice_id);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: marketplace
|
|
|
|
### suppliers
|
|
Proveedores/distribuidores del marketplace B2B.
|
|
|
|
```sql
|
|
CREATE TABLE marketplace.suppliers (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Identificacion
|
|
name VARCHAR(200) NOT NULL,
|
|
slug VARCHAR(100) UNIQUE NOT NULL,
|
|
description TEXT,
|
|
logo_url TEXT,
|
|
|
|
-- Contacto
|
|
contact_phone VARCHAR(20),
|
|
contact_email VARCHAR(100),
|
|
contact_name VARCHAR(100),
|
|
|
|
-- Direccion
|
|
address TEXT,
|
|
city VARCHAR(50),
|
|
state VARCHAR(50),
|
|
zip_codes_served TEXT[], -- CPs que atiende
|
|
|
|
-- Condiciones
|
|
min_order_amount DECIMAL(10,2) DEFAULT 0,
|
|
delivery_days INTEGER DEFAULT 3,
|
|
delivery_fee DECIMAL(10,2) DEFAULT 0,
|
|
|
|
-- Metricas
|
|
rating DECIMAL(3,2) DEFAULT 0,
|
|
total_orders INTEGER DEFAULT 0,
|
|
total_reviews INTEGER DEFAULT 0,
|
|
|
|
-- Estado
|
|
is_verified BOOLEAN DEFAULT false,
|
|
is_active BOOLEAN DEFAULT true,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_suppliers_slug ON marketplace.suppliers(slug);
|
|
CREATE INDEX idx_suppliers_active ON marketplace.suppliers(is_active);
|
|
CREATE INDEX idx_suppliers_zip ON marketplace.suppliers USING gin(zip_codes_served);
|
|
```
|
|
|
|
### supplier_products
|
|
Catalogo de productos de proveedores.
|
|
|
|
```sql
|
|
CREATE TABLE marketplace.supplier_products (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
supplier_id UUID NOT NULL REFERENCES marketplace.suppliers(id) ON DELETE CASCADE,
|
|
|
|
-- Producto
|
|
name VARCHAR(200) NOT NULL,
|
|
description TEXT,
|
|
sku VARCHAR(50),
|
|
barcode VARCHAR(50),
|
|
|
|
-- Precios
|
|
price DECIMAL(10,2) NOT NULL, -- Precio mayoreo
|
|
suggested_retail_price DECIMAL(10,2), -- Precio sugerido
|
|
|
|
-- Presentacion
|
|
unit VARCHAR(20) DEFAULT 'pieza',
|
|
min_quantity INTEGER DEFAULT 1,
|
|
|
|
-- Categoria
|
|
category VARCHAR(50),
|
|
|
|
-- Imagen
|
|
image_url TEXT,
|
|
|
|
-- Estado
|
|
is_available BOOLEAN DEFAULT true,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_supplier_products_supplier ON marketplace.supplier_products(supplier_id);
|
|
CREATE INDEX idx_supplier_products_barcode ON marketplace.supplier_products(barcode);
|
|
CREATE INDEX idx_supplier_products_available ON marketplace.supplier_products(is_available);
|
|
```
|
|
|
|
### supplier_orders
|
|
Pedidos a proveedores.
|
|
|
|
```sql
|
|
CREATE TABLE marketplace.supplier_orders (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
supplier_id UUID NOT NULL REFERENCES marketplace.suppliers(id),
|
|
|
|
-- Numero
|
|
order_number VARCHAR(20) NOT NULL,
|
|
|
|
-- Montos
|
|
subtotal DECIMAL(12,2) NOT NULL,
|
|
delivery_fee DECIMAL(10,2) DEFAULT 0,
|
|
total DECIMAL(12,2) NOT NULL,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) DEFAULT 'pending', -- pending, confirmed, shipped, delivered, cancelled
|
|
|
|
-- Entrega
|
|
shipping_address TEXT NOT NULL,
|
|
shipping_notes TEXT,
|
|
estimated_delivery DATE,
|
|
delivered_at TIMESTAMPTZ,
|
|
|
|
-- Notas
|
|
notes TEXT,
|
|
|
|
-- Cancelacion
|
|
cancelled_at TIMESTAMPTZ,
|
|
cancellation_reason TEXT,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_supplier_orders_tenant ON marketplace.supplier_orders(tenant_id);
|
|
CREATE INDEX idx_supplier_orders_supplier ON marketplace.supplier_orders(supplier_id);
|
|
CREATE INDEX idx_supplier_orders_status ON marketplace.supplier_orders(status);
|
|
```
|
|
|
|
### supplier_order_items
|
|
Items de pedidos a proveedores.
|
|
|
|
```sql
|
|
CREATE TABLE marketplace.supplier_order_items (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
order_id UUID NOT NULL REFERENCES marketplace.supplier_orders(id) ON DELETE CASCADE,
|
|
product_id UUID REFERENCES marketplace.supplier_products(id),
|
|
|
|
-- Producto (snapshot)
|
|
product_name VARCHAR(200) NOT NULL,
|
|
product_sku VARCHAR(50),
|
|
|
|
-- Cantidades
|
|
quantity INTEGER NOT NULL,
|
|
unit_price DECIMAL(10,2) NOT NULL,
|
|
subtotal DECIMAL(12,2) NOT NULL,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_supplier_order_items_order ON marketplace.supplier_order_items(order_id);
|
|
```
|
|
|
|
### supplier_reviews
|
|
Resenas de proveedores.
|
|
|
|
```sql
|
|
CREATE TABLE marketplace.supplier_reviews (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
supplier_id UUID NOT NULL REFERENCES marketplace.suppliers(id) ON DELETE CASCADE,
|
|
order_id UUID REFERENCES marketplace.supplier_orders(id),
|
|
|
|
-- Calificacion
|
|
rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
|
comment TEXT,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id, order_id) -- Una resena por orden
|
|
);
|
|
|
|
CREATE INDEX idx_supplier_reviews_supplier ON marketplace.supplier_reviews(supplier_id);
|
|
```
|
|
|
|
### supplier_favorites
|
|
Proveedores favoritos por tenant.
|
|
|
|
```sql
|
|
CREATE TABLE marketplace.supplier_favorites (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
supplier_id UUID NOT NULL REFERENCES marketplace.suppliers(id) ON DELETE CASCADE,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id, supplier_id)
|
|
);
|
|
|
|
CREATE INDEX idx_supplier_favorites_tenant ON marketplace.supplier_favorites(tenant_id);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: integrations
|
|
|
|
### integration_configs
|
|
Configuraciones de integraciones por tenant.
|
|
|
|
```sql
|
|
CREATE TABLE integrations.integration_configs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Tipo de integracion
|
|
integration_type VARCHAR(50) NOT NULL, -- llm, payments, whatsapp, sat, etc.
|
|
provider VARCHAR(50) NOT NULL, -- openrouter, stripe, meta, etc.
|
|
|
|
-- Configuracion
|
|
config JSONB NOT NULL DEFAULT '{}',
|
|
is_active BOOLEAN DEFAULT true,
|
|
priority INTEGER DEFAULT 0, -- para fallback ordering
|
|
|
|
-- Limites y uso
|
|
rate_limit_per_minute INTEGER,
|
|
daily_limit INTEGER,
|
|
monthly_limit INTEGER,
|
|
current_daily_usage INTEGER DEFAULT 0,
|
|
current_monthly_usage INTEGER DEFAULT 0,
|
|
|
|
-- Timestamps
|
|
last_used_at TIMESTAMPTZ,
|
|
last_reset_at TIMESTAMPTZ DEFAULT NOW(),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id, integration_type, provider)
|
|
);
|
|
|
|
CREATE INDEX idx_integration_configs_tenant ON integrations.integration_configs(tenant_id);
|
|
CREATE INDEX idx_integration_configs_type ON integrations.integration_configs(integration_type);
|
|
```
|
|
|
|
### integration_logs
|
|
Logs de uso de integraciones para auditoria.
|
|
|
|
```sql
|
|
CREATE TABLE integrations.integration_logs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Referencia a config
|
|
integration_config_id UUID REFERENCES integrations.integration_configs(id),
|
|
integration_type VARCHAR(50) NOT NULL,
|
|
provider VARCHAR(50) NOT NULL,
|
|
|
|
-- Detalles de la llamada
|
|
request_type VARCHAR(100), -- chat_completion, send_message, create_invoice, etc.
|
|
request_data JSONB,
|
|
response_data JSONB,
|
|
|
|
-- Resultado
|
|
status VARCHAR(20) NOT NULL, -- success, error, timeout, rate_limited
|
|
error_message TEXT,
|
|
|
|
-- Metricas
|
|
latency_ms INTEGER,
|
|
tokens_used INTEGER, -- para LLM
|
|
cost_cents INTEGER, -- costo estimado
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_integration_logs_tenant ON integrations.integration_logs(tenant_id);
|
|
CREATE INDEX idx_integration_logs_type ON integrations.integration_logs(integration_type);
|
|
CREATE INDEX idx_integration_logs_created ON integrations.integration_logs(created_at);
|
|
```
|
|
|
|
---
|
|
|
|
## Funciones Utiles
|
|
|
|
### Generador de numeros de ticket
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION sales.generate_ticket_number(p_tenant_id UUID)
|
|
RETURNS VARCHAR(20) AS $$
|
|
DECLARE
|
|
v_date TEXT;
|
|
v_sequence INTEGER;
|
|
v_ticket VARCHAR(20);
|
|
BEGIN
|
|
v_date := TO_CHAR(CURRENT_DATE, 'YYMMDD');
|
|
|
|
SELECT COALESCE(MAX(
|
|
CAST(SUBSTRING(ticket_number FROM 8) AS INTEGER)
|
|
), 0) + 1
|
|
INTO v_sequence
|
|
FROM sales.sales
|
|
WHERE tenant_id = p_tenant_id
|
|
AND ticket_number LIKE v_date || '-%';
|
|
|
|
v_ticket := v_date || '-' || LPAD(v_sequence::TEXT, 4, '0');
|
|
|
|
RETURN v_ticket;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
### Trigger de actualizacion
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION update_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Aplicar a todas las tablas relevantes
|
|
CREATE TRIGGER update_tenants_updated_at
|
|
BEFORE UPDATE ON public.tenants
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
-- (repetir para otras tablas)
|
|
```
|
|
|
|
### Funcion de balance de fiados
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION customers.update_customer_fiado_balance()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
UPDATE customers.customers
|
|
SET current_fiado_balance = (
|
|
SELECT COALESCE(SUM(remaining_amount), 0)
|
|
FROM customers.fiados
|
|
WHERE customer_id = COALESCE(NEW.customer_id, OLD.customer_id)
|
|
AND status IN ('pending', 'partial', 'overdue')
|
|
)
|
|
WHERE id = COALESCE(NEW.customer_id, OLD.customer_id);
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER update_fiado_balance
|
|
AFTER INSERT OR UPDATE OR DELETE ON customers.fiados
|
|
FOR EACH ROW EXECUTE FUNCTION customers.update_customer_fiado_balance();
|
|
```
|
|
|
|
### Funciones de Sistema de Referidos
|
|
|
|
```sql
|
|
-- Generador de codigo de referido unico
|
|
CREATE OR REPLACE FUNCTION subscriptions.generate_referral_code(p_tenant_id UUID)
|
|
RETURNS VARCHAR(10) AS $$
|
|
DECLARE
|
|
v_code VARCHAR(10);
|
|
v_exists BOOLEAN;
|
|
BEGIN
|
|
LOOP
|
|
-- Genera codigo alfanumerico de 8 caracteres
|
|
v_code := UPPER(SUBSTRING(MD5(RANDOM()::TEXT || CLOCK_TIMESTAMP()::TEXT) FROM 1 FOR 8));
|
|
|
|
-- Verifica que no exista
|
|
SELECT EXISTS(
|
|
SELECT 1 FROM subscriptions.referral_codes
|
|
WHERE code = v_code
|
|
) INTO v_exists;
|
|
|
|
EXIT WHEN NOT v_exists;
|
|
END LOOP;
|
|
|
|
RETURN v_code;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Estadisticas de referidos por tenant
|
|
CREATE OR REPLACE FUNCTION subscriptions.get_referral_stats(p_tenant_id UUID)
|
|
RETURNS TABLE(
|
|
total_codes INTEGER,
|
|
active_codes INTEGER,
|
|
total_referrals INTEGER,
|
|
successful_referrals INTEGER,
|
|
total_rewards_pending DECIMAL,
|
|
total_rewards_paid DECIMAL
|
|
) AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
COUNT(DISTINCT rc.id)::INTEGER as total_codes,
|
|
COUNT(DISTINCT rc.id) FILTER (WHERE rc.is_active)::INTEGER as active_codes,
|
|
COUNT(DISTINCT r.id)::INTEGER as total_referrals,
|
|
COUNT(DISTINCT r.id) FILTER (WHERE r.status = 'converted')::INTEGER as successful_referrals,
|
|
COALESCE(SUM(rr.reward_amount) FILTER (WHERE rr.status = 'pending'), 0) as total_rewards_pending,
|
|
COALESCE(SUM(rr.reward_amount) FILTER (WHERE rr.status = 'paid'), 0) as total_rewards_paid
|
|
FROM subscriptions.referral_codes rc
|
|
LEFT JOIN subscriptions.referrals r ON r.referral_code_id = rc.id
|
|
LEFT JOIN subscriptions.referral_rewards rr ON rr.referral_id = r.id
|
|
WHERE rc.tenant_id = p_tenant_id;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
### Funciones de CoDi/SPEI
|
|
|
|
```sql
|
|
-- Generador de referencia CoDi
|
|
CREATE OR REPLACE FUNCTION sales.generate_codi_reference(p_tenant_id UUID)
|
|
RETURNS VARCHAR(20) AS $$
|
|
DECLARE
|
|
v_date TEXT;
|
|
v_sequence INTEGER;
|
|
v_reference VARCHAR(20);
|
|
BEGIN
|
|
v_date := TO_CHAR(NOW(), 'YYYYMMDD');
|
|
|
|
SELECT COALESCE(MAX(
|
|
CAST(SUBSTRING(codi_reference FROM 10) AS INTEGER)
|
|
), 0) + 1
|
|
INTO v_sequence
|
|
FROM sales.codi_transactions
|
|
WHERE tenant_id = p_tenant_id
|
|
AND codi_reference LIKE 'CODI' || v_date || '%';
|
|
|
|
v_reference := 'CODI' || v_date || LPAD(v_sequence::TEXT, 6, '0');
|
|
|
|
RETURN v_reference;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Resumen de transacciones CoDi/SPEI
|
|
CREATE OR REPLACE FUNCTION sales.get_codi_spei_summary(
|
|
p_tenant_id UUID,
|
|
p_start_date TIMESTAMPTZ DEFAULT NOW() - INTERVAL '30 days',
|
|
p_end_date TIMESTAMPTZ DEFAULT NOW()
|
|
)
|
|
RETURNS TABLE(
|
|
total_codi_transactions INTEGER,
|
|
total_codi_amount DECIMAL,
|
|
total_spei_transactions INTEGER,
|
|
total_spei_amount DECIMAL,
|
|
pending_transactions INTEGER
|
|
) AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
COUNT(ct.id)::INTEGER as total_codi_transactions,
|
|
COALESCE(SUM(ct.amount) FILTER (WHERE ct.status = 'completed'), 0) as total_codi_amount,
|
|
COUNT(st.id)::INTEGER as total_spei_transactions,
|
|
COALESCE(SUM(st.amount) FILTER (WHERE st.status = 'completed'), 0) as total_spei_amount,
|
|
(COUNT(ct.id) FILTER (WHERE ct.status = 'pending') +
|
|
COUNT(st.id) FILTER (WHERE st.status = 'pending'))::INTEGER as pending_transactions
|
|
FROM sales.codi_transactions ct
|
|
FULL OUTER JOIN sales.spei_transactions st ON st.tenant_id = ct.tenant_id
|
|
WHERE (ct.tenant_id = p_tenant_id OR st.tenant_id = p_tenant_id)
|
|
AND (ct.created_at BETWEEN p_start_date AND p_end_date
|
|
OR st.created_at BETWEEN p_start_date AND p_end_date);
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
### Funciones de Facturacion (CFDI 4.0)
|
|
|
|
```sql
|
|
-- Obtener siguiente folio de factura
|
|
CREATE OR REPLACE FUNCTION billing.get_next_invoice_folio(
|
|
p_tenant_id UUID,
|
|
p_serie VARCHAR DEFAULT 'A'
|
|
)
|
|
RETURNS VARCHAR(20) AS $$
|
|
DECLARE
|
|
v_sequence INTEGER;
|
|
v_folio VARCHAR(20);
|
|
BEGIN
|
|
SELECT COALESCE(MAX(
|
|
CAST(SUBSTRING(folio FROM 2) AS INTEGER)
|
|
), 0) + 1
|
|
INTO v_sequence
|
|
FROM billing.invoices
|
|
WHERE tenant_id = p_tenant_id
|
|
AND serie = p_serie;
|
|
|
|
v_folio := p_serie || LPAD(v_sequence::TEXT, 8, '0');
|
|
|
|
RETURN v_folio;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Resumen de facturacion por periodo
|
|
CREATE OR REPLACE FUNCTION billing.get_invoice_summary(
|
|
p_tenant_id UUID,
|
|
p_start_date TIMESTAMPTZ DEFAULT DATE_TRUNC('month', NOW()),
|
|
p_end_date TIMESTAMPTZ DEFAULT NOW()
|
|
)
|
|
RETURNS TABLE(
|
|
total_invoices INTEGER,
|
|
total_subtotal DECIMAL,
|
|
total_iva DECIMAL,
|
|
total_amount DECIMAL,
|
|
cancelled_invoices INTEGER,
|
|
pending_timbrado INTEGER
|
|
) AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
COUNT(*)::INTEGER as total_invoices,
|
|
COALESCE(SUM(subtotal), 0) as total_subtotal,
|
|
COALESCE(SUM(total_tax), 0) as total_iva,
|
|
COALESCE(SUM(total_amount), 0) as total_amount,
|
|
COUNT(*) FILTER (WHERE status = 'cancelled')::INTEGER as cancelled_invoices,
|
|
COUNT(*) FILTER (WHERE status = 'pending')::INTEGER as pending_timbrado
|
|
FROM billing.invoices
|
|
WHERE tenant_id = p_tenant_id
|
|
AND created_at BETWEEN p_start_date AND p_end_date;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
### Funciones de Marketplace
|
|
|
|
```sql
|
|
-- Actualizar rating de proveedor
|
|
CREATE OR REPLACE FUNCTION marketplace.update_supplier_rating()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
UPDATE marketplace.suppliers
|
|
SET
|
|
rating = (
|
|
SELECT COALESCE(AVG(sr.rating), 0)
|
|
FROM marketplace.supplier_reviews sr
|
|
WHERE sr.supplier_id = COALESCE(NEW.supplier_id, OLD.supplier_id)
|
|
),
|
|
reviews_count = (
|
|
SELECT COUNT(*)
|
|
FROM marketplace.supplier_reviews sr
|
|
WHERE sr.supplier_id = COALESCE(NEW.supplier_id, OLD.supplier_id)
|
|
)
|
|
WHERE id = COALESCE(NEW.supplier_id, OLD.supplier_id);
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trigger_update_supplier_rating
|
|
AFTER INSERT OR UPDATE OR DELETE ON marketplace.supplier_reviews
|
|
FOR EACH ROW EXECUTE FUNCTION marketplace.update_supplier_rating();
|
|
|
|
-- Actualizar contador de ordenes de proveedor
|
|
CREATE OR REPLACE FUNCTION marketplace.update_supplier_orders_count()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
UPDATE marketplace.suppliers
|
|
SET orders_count = (
|
|
SELECT COUNT(*)
|
|
FROM marketplace.supplier_orders so
|
|
WHERE so.supplier_id = COALESCE(NEW.supplier_id, OLD.supplier_id)
|
|
AND so.status = 'completed'
|
|
)
|
|
WHERE id = COALESCE(NEW.supplier_id, OLD.supplier_id);
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trigger_update_supplier_orders_count
|
|
AFTER INSERT OR UPDATE ON marketplace.supplier_orders
|
|
FOR EACH ROW EXECUTE FUNCTION marketplace.update_supplier_orders_count();
|
|
|
|
-- Buscar proveedores por zona geografica
|
|
CREATE OR REPLACE FUNCTION marketplace.find_suppliers_by_zone(
|
|
p_state VARCHAR,
|
|
p_city VARCHAR DEFAULT NULL,
|
|
p_category VARCHAR DEFAULT NULL
|
|
)
|
|
RETURNS TABLE(
|
|
supplier_id UUID,
|
|
business_name VARCHAR,
|
|
category VARCHAR,
|
|
rating DECIMAL,
|
|
delivery_days INTEGER,
|
|
minimum_order DECIMAL
|
|
) AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
s.id as supplier_id,
|
|
s.business_name,
|
|
s.category,
|
|
s.rating,
|
|
s.delivery_days,
|
|
s.minimum_order
|
|
FROM marketplace.suppliers s
|
|
WHERE s.is_active = TRUE
|
|
AND s.is_verified = TRUE
|
|
AND (p_state = ANY(s.delivery_zones) OR 'Nacional' = ANY(s.delivery_zones))
|
|
AND (p_category IS NULL OR s.category = p_category)
|
|
ORDER BY s.rating DESC, s.orders_count DESC;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Estadisticas generales del marketplace
|
|
CREATE OR REPLACE FUNCTION marketplace.get_marketplace_stats()
|
|
RETURNS TABLE(
|
|
total_suppliers INTEGER,
|
|
verified_suppliers INTEGER,
|
|
total_products INTEGER,
|
|
total_orders INTEGER,
|
|
total_volume DECIMAL,
|
|
avg_rating DECIMAL
|
|
) AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
(SELECT COUNT(*)::INTEGER FROM marketplace.suppliers WHERE is_active),
|
|
(SELECT COUNT(*)::INTEGER FROM marketplace.suppliers WHERE is_verified),
|
|
(SELECT COUNT(*)::INTEGER FROM marketplace.supplier_products WHERE is_active),
|
|
(SELECT COUNT(*)::INTEGER FROM marketplace.supplier_orders),
|
|
(SELECT COALESCE(SUM(total_amount), 0) FROM marketplace.supplier_orders WHERE status = 'completed'),
|
|
(SELECT COALESCE(AVG(rating), 0) FROM marketplace.suppliers WHERE is_verified);
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
---
|
|
|
|
## Indices Adicionales para Performance
|
|
|
|
```sql
|
|
-- Ventas por fecha (reportes)
|
|
CREATE INDEX idx_sales_tenant_date ON sales.sales(tenant_id, DATE(created_at));
|
|
|
|
-- Productos mas vendidos
|
|
CREATE INDEX idx_sale_items_product_count ON sales.sale_items(product_id);
|
|
|
|
-- Fiados vencidos
|
|
CREATE INDEX idx_fiados_overdue ON customers.fiados(tenant_id, due_date)
|
|
WHERE status IN ('pending', 'partial');
|
|
|
|
-- Busqueda de productos por nombre
|
|
CREATE INDEX idx_products_search ON catalog.products
|
|
USING gin(to_tsvector('spanish', name || ' ' || COALESCE(description, '')));
|
|
```
|
|
|
|
---
|
|
|
|
## Row Level Security (RLS)
|
|
|
|
Todas las tablas que manejan datos de tenant tienen RLS habilitado.
|
|
|
|
```sql
|
|
-- Configurar tenant en cada request
|
|
SET app.current_tenant = 'uuid-del-tenant';
|
|
|
|
-- Ejemplo de policy
|
|
CREATE POLICY tenant_isolation ON catalog.products
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.current_tenant')::UUID)
|
|
WITH CHECK (tenant_id = current_setting('app.current_tenant')::UUID);
|
|
```
|
|
|
|
---
|
|
|
|
## Extensiones PostgreSQL
|
|
|
|
| Extension | Proposito |
|
|
|-----------|-----------|
|
|
| `uuid-ossp` | Generacion de UUIDs |
|
|
| `pgcrypto` | Encriptacion de datos sensibles |
|
|
| `pg_trgm` | Busqueda por similitud (fuzzy search) |
|
|
| `btree_gin` | Indices GIN para busquedas combinadas |
|
|
|
|
---
|
|
|
|
## Convenciones
|
|
|
|
### Columnas Comunes
|
|
Todas las tablas siguen estas convenciones:
|
|
|
|
| Columna | Tipo | Descripcion |
|
|
|---------|------|-------------|
|
|
| `id` | UUID PRIMARY KEY | Identificador unico |
|
|
| `tenant_id` | UUID NOT NULL | Referencia al tenant (multi-tenant) |
|
|
| `created_at` | TIMESTAMPTZ DEFAULT NOW() | Fecha de creacion |
|
|
| `updated_at` | TIMESTAMPTZ DEFAULT NOW() | Fecha de ultima modificacion |
|
|
| `is_active` | BOOLEAN DEFAULT TRUE | Estado activo/inactivo |
|
|
|
|
### Indices Estandar
|
|
- PK en `id`
|
|
- Indice en `tenant_id` para todas las tablas multi-tenant
|
|
- Indices en Foreign Keys
|
|
- Indices en columnas de busqueda frecuente
|
|
- Indices GIN para busqueda full-text
|
|
|
|
### Triggers Estandar
|
|
- `set_updated_at`: Actualiza `updated_at` automaticamente en cada UPDATE
|
|
- `update_customer_fiado_balance`: Recalcula balance de fiados del cliente
|
|
|
|
---
|
|
|
|
## Conexion
|
|
|
|
### Desarrollo
|
|
```
|
|
Host: localhost
|
|
Puerto: 5432
|
|
Database: michangarrito_dev
|
|
Usuario: michangarrito_dev
|
|
Password: (ver .env)
|
|
```
|
|
|
|
### Produccion
|
|
```
|
|
Host: (configurar en .env)
|
|
Puerto: 5432
|
|
Database: michangarrito
|
|
Usuario: michangarrito
|
|
SSL: Requerido
|
|
```
|
|
|
|
---
|
|
|
|
## Scripts de Base de Datos
|
|
|
|
| Script | Ubicacion | Descripcion |
|
|
|--------|-----------|-------------|
|
|
| `create-database.sh` | database/scripts/ | Crear BD desde cero |
|
|
| `recreate-database.sh` | database/scripts/ | Recrear BD (destructivo) |
|
|
| `drop-and-recreate-database.sh` | database/scripts/ | Eliminar y recrear BD |
|
|
|
|
### Orden de Ejecucion de Schemas
|
|
```
|
|
00-extensions.sql -> Extensiones PostgreSQL
|
|
01-schemas.sql -> Creacion de schemas (11 schemas)
|
|
02-functions.sql -> Funciones utilitarias
|
|
03-public.sql -> Schema public (tenants, tenant_settings, tenant_integration_credentials, tenant_whatsapp_numbers)
|
|
04-auth.sql -> Schema auth
|
|
05-catalog.sql -> Schema catalog
|
|
06-sales.sql -> Schema sales (incluye virtual_accounts, codi_transactions, spei_transactions, payment_config)
|
|
07-inventory.sql -> Schema inventory
|
|
08-customers.sql -> Schema customers
|
|
09-orders.sql -> Schema orders
|
|
10-subscriptions.sql -> Schema subscriptions (incluye referral_codes, referrals, referral_rewards)
|
|
11-messaging.sql -> Schema messaging
|
|
12-integrations.sql -> Schema integrations
|
|
13-billing.sql -> Schema billing (tax_configs, invoices, invoice_items, invoice_item_taxes, invoice_history)
|
|
14-marketplace.sql -> Schema marketplace (suppliers, supplier_products, supplier_orders, supplier_order_items, supplier_reviews, supplier_favorites)
|
|
15-rls-policies.sql -> Politicas RLS para todos los schemas
|
|
16-seed-data.sql -> Datos iniciales (planes, configuraciones)
|
|
```
|
|
|
|
---
|
|
|
|
## Referencias
|
|
|
|
- [Environment Inventory](../../orchestration/environment/ENVIRONMENT-INVENTORY.yml)
|
|
- [Contexto del Proyecto](../../orchestration/00-guidelines/CONTEXTO-PROYECTO.md)
|
|
- [Database Inventory](../../orchestration/inventarios/DATABASE_INVENTORY.yml)
|
|
- [Especificacion de Componentes](./ESPECIFICACION-COMPONENTES.md)
|
|
|
|
---
|
|
|
|
**Version:** 2.2.0
|
|
**Fecha:** 2026-01-10
|
|
**Actualizado:** Schema integrations agregado - 12 schemas, ~49 tablas completas
|