michangarrito/orchestration/_archive/analisis/backup-integracion-2026-01-10/ARQUITECTURA-DATABASE.md
Adrian Flores Cortes 2fb9f3f6b5
Some checks are pending
CI/CD Pipeline / Backend CI (push) Waiting to run
CI/CD Pipeline / Frontend CI (push) Waiting to run
CI/CD Pipeline / WhatsApp Service CI (push) Waiting to run
CI/CD Pipeline / Mobile CI (push) Waiting to run
CI/CD Pipeline / Docker Build (./apps/backend, ./apps/backend/Dockerfile, backend) (push) Blocked by required conditions
CI/CD Pipeline / Docker Build (./apps/frontend, ./apps/frontend/Dockerfile, frontend) (push) Blocked by required conditions
CI/CD Pipeline / Docker Build (./apps/whatsapp-service, ./apps/whatsapp-service/Dockerfile, whatsapp-service) (push) Blocked by required conditions
CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions
[ESTANDAR-ORCHESTRATION] refactor: Consolidate to standard structure
- Move 7 non-standard folders to _archive/
- Archive 3 extra root files
- Update _MAP.md with standardized structure

Standard: SIMCO-ESTANDAR-ORCHESTRATION v1.0.0
Level: CONSUMER (L2)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 14:38:04 -06:00

1278 lines
33 KiB
Markdown

# 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 |
| `auth` | Autenticacion y usuarios | users, sessions, otp_codes |
| `catalog` | Productos y categorias | products, categories, product_templates |
| `sales` | Ventas y pagos | sales, sale_items, payments, daily_closures |
| `inventory` | Stock y movimientos | inventory, inventory_movements, stock_alerts |
| `customers` | Clientes y fiados | customers, fiados, fiado_payments |
| `orders` | Pedidos de clientes | orders, order_items, deliveries |
| `subscriptions` | Planes y tokens IA | plans, subscriptions, token_packages, token_usage |
| `messaging` | WhatsApp y notificaciones | conversations, messages, notifications |
---
## 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)
);
```
---
## 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)
);
```
---
## 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)
);
```
---
## 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);
```
---
## 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();
```
---
## 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
02-functions.sql -> Funciones utilitarias
03-public.sql -> Schema public (tenants)
04-auth.sql -> Schema auth
05-catalog.sql -> Schema catalog
06-sales.sql -> Schema sales
07-inventory.sql -> Schema inventory
08-customers.sql -> Schema customers
09-orders.sql -> Schema orders
10-subscriptions.sql -> Schema subscriptions
11-messaging.sql -> Schema messaging
12-integrations.sql -> Schema integrations
```
---
## 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.0.0
**Fecha:** 2026-01-10
**Actualizado:** Integracion de contenido de 90-transversal/arquitectura/