diff --git a/schemas/notifications.sql b/schemas/notifications.sql new file mode 100644 index 0000000..4509bd3 --- /dev/null +++ b/schemas/notifications.sql @@ -0,0 +1,156 @@ +-- ============================================ +-- Schema: notifications +-- Épica: MCH-017 - Notificaciones +-- Version: 1.0.0 +-- ============================================ + +-- Create schema +CREATE SCHEMA IF NOT EXISTS notifications; + +-- ============================================ +-- Table: notifications +-- Stores all notification records +-- ============================================ +CREATE TABLE notifications.notifications ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + user_id UUID, + customer_id UUID, + type VARCHAR(50) NOT NULL, + channel VARCHAR(20) NOT NULL, + title VARCHAR(255) NOT NULL, + body TEXT NOT NULL, + data JSONB, + status VARCHAR(20) NOT NULL DEFAULT 'pending', + error_message TEXT, + retry_count INTEGER DEFAULT 0, + scheduled_at TIMESTAMPTZ, + sent_at TIMESTAMPTZ, + delivered_at TIMESTAMPTZ, + read_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Indexes +CREATE INDEX idx_notifications_tenant_user_status + ON notifications.notifications(tenant_id, user_id, status); +CREATE INDEX idx_notifications_tenant_created + ON notifications.notifications(tenant_id, created_at); +CREATE INDEX idx_notifications_scheduled + ON notifications.notifications(scheduled_at) + WHERE scheduled_at IS NOT NULL AND status = 'pending'; + +-- ============================================ +-- Table: notification_templates +-- Notification templates per type and channel +-- ============================================ +CREATE TABLE notifications.notification_templates ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID, -- NULL = default template + type VARCHAR(50) NOT NULL, + channel VARCHAR(20) NOT NULL, + title VARCHAR(255) NOT NULL, + body TEXT NOT NULL, + variables JSONB, -- List of required variables + active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(tenant_id, type, channel) +); + +-- ============================================ +-- Table: notification_preferences +-- User preferences per channel +-- ============================================ +CREATE TABLE notifications.notification_preferences ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL, + channel VARCHAR(20) NOT NULL, + enabled BOOLEAN DEFAULT true, + quiet_hours_enabled BOOLEAN DEFAULT false, + quiet_hours_start TIME, + quiet_hours_end TIME, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(user_id, channel) +); + +-- ============================================ +-- Table: device_tokens +-- FCM/APNs tokens for push notifications +-- ============================================ +CREATE TABLE notifications.device_tokens ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL, + platform VARCHAR(20) NOT NULL, + token TEXT NOT NULL UNIQUE, + device_name VARCHAR(100), + active BOOLEAN DEFAULT true, + last_used_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Indexes +CREATE INDEX idx_device_tokens_user ON notifications.device_tokens(user_id); + +-- ============================================ +-- Default Templates (seed data) +-- ============================================ +INSERT INTO notifications.notification_templates (tenant_id, type, channel, title, body, variables) VALUES +-- Order notifications +(NULL, 'new_order', 'push', '🛒 Nuevo Pedido #{{order_id}}', 'Pedido de {{customer_name}} por ${{total}}', '["order_id", "customer_name", "total"]'), +(NULL, 'new_order', 'whatsapp', '🛒 *Nuevo Pedido*', 'Pedido #{{order_id}}\nCliente: {{customer_name}}\nTotal: ${{total}}\n\nRevisa tu app para confirmar.', '["order_id", "customer_name", "total"]'), + +(NULL, 'order_confirmed', 'whatsapp', '✅ *Pedido Confirmado*', '¡Tu pedido #{{order_id}} ha sido confirmado!\n\nEstamos preparándolo.\nTe avisamos cuando esté listo.', '["order_id"]'), + +(NULL, 'order_ready', 'push', '📦 Pedido Listo', 'Tu pedido #{{order_id}} está listo para recoger', '["order_id"]'), +(NULL, 'order_ready', 'whatsapp', '📦 *Pedido Listo*', '¡Tu pedido #{{order_id}} está listo!\n\nPuedes pasar a recogerlo a:\n{{business_address}}', '["order_id", "business_address"]'), + +(NULL, 'order_delivered', 'whatsapp', '🎉 *Pedido Entregado*', '¡Tu pedido #{{order_id}} ha sido entregado!\n\nTotal: ${{total}}\n\n¡Gracias por tu compra!', '["order_id", "total"]'), + +(NULL, 'order_cancelled', 'whatsapp', '❌ *Pedido Cancelado*', 'Tu pedido #{{order_id}} ha sido cancelado.\n\nSi tienes dudas, contáctanos.', '["order_id"]'), + +-- Payment notifications +(NULL, 'payment_received', 'push', '💰 Pago Recibido', 'Se recibió pago de ${{amount}} de {{customer_name}}', '["amount", "customer_name"]'), + +-- Alert notifications +(NULL, 'low_stock', 'push', '⚠️ Stock Bajo', '{{product_name}}: quedan {{quantity}} unidades', '["product_name", "quantity"]'), + +(NULL, 'fiado_reminder', 'whatsapp', '💳 *Recordatorio de Pago*', 'Hola {{customer_name}},\n\nTe recordamos que tienes un saldo pendiente de ${{balance}} en {{business_name}}.\n\n¿Cuándo podrías pasar a abonar?', '["customer_name", "balance", "business_name"]'), + +-- Report notifications +(NULL, 'daily_summary', 'push', '📊 Resumen del Día', 'Ventas: ${{total_sales}} | Pedidos: {{orders_count}}', '["total_sales", "orders_count"]'), + +(NULL, 'weekly_report', 'whatsapp', '📊 *Reporte Semanal*', '📈 Resumen de la semana:\n\n💰 Ventas: ${{total_sales}}\n📦 Pedidos: {{orders_count}}\n🎫 Ticket promedio: ${{avg_ticket}}\n\n¿Necesitas más detalles?', '["total_sales", "orders_count", "avg_ticket"]'), + +-- System notifications +(NULL, 'welcome', 'whatsapp', '👋 *¡Bienvenido!*', '¡Hola {{name}}! Bienvenido a {{business_name}}.\n\nPuedes escribirme para:\n• Ver productos\n• Hacer pedidos\n• Consultar tu cuenta\n\n¿En qué te puedo ayudar?', '["name", "business_name"]'); + +-- ============================================ +-- Updated_at trigger +-- ============================================ +CREATE OR REPLACE FUNCTION notifications.update_updated_at() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_notifications_updated_at + BEFORE UPDATE ON notifications.notifications + FOR EACH ROW EXECUTE FUNCTION notifications.update_updated_at(); + +CREATE TRIGGER trg_templates_updated_at + BEFORE UPDATE ON notifications.notification_templates + FOR EACH ROW EXECUTE FUNCTION notifications.update_updated_at(); + +CREATE TRIGGER trg_preferences_updated_at + BEFORE UPDATE ON notifications.notification_preferences + FOR EACH ROW EXECUTE FUNCTION notifications.update_updated_at(); + +CREATE TRIGGER trg_device_tokens_updated_at + BEFORE UPDATE ON notifications.device_tokens + FOR EACH ROW EXECUTE FUNCTION notifications.update_updated_at();