FASE 4-5-6: Infraestructura, CRM, Service Orders, Notificaciones, Ahorro, Logistica, API Publica

FASE 4:
- Redis cache de stock con fallback graceful
- Multi-moneda (MXN/USD) con contabilidad en MXN
- Proveedores y ordenes de compra completo
- Meilisearch 1.5M+ partes indexadas
- Metabase KPIs con dashboard auto-generado

FASE 5:
- CRM mejorado: activities, tags, loyalty program, analytics
- Imagenes de partes: upload, resize, thumbnails WebP
- Ordenes de servicio Kanban: received->diagnosis->repair->ready->delivered
- Garantias/RMA, alertas de reorden, multi-sucursal
- Stubs BNPL (APLAZO) y ERP Sync (Aspel/Contpaqi)

FASE 6:
- Notificaciones automaticas: push/WhatsApp/email/in-app
- Reportes de ahorro vs retail_price
- Logistica + tracking: DHL, FedEx, Estafeta, 99min, Uber
- API Publica: API keys, rate limiting, catalog search

Migraciones: v1.9-v3.0
Tests: 93/93 pasando
Backup: nexus_backup_20260427_045859.tar.gz
This commit is contained in:
Nexus Dev
2026-04-27 05:23:30 +00:00
parent b70cb3042b
commit 9ff3dc4c8b
71 changed files with 10939 additions and 420 deletions

View File

@@ -0,0 +1,67 @@
-- v2.7 Automatic Notifications Engine
-- Notification templates per tenant
CREATE TABLE IF NOT EXISTS notification_templates (
id SERIAL PRIMARY KEY,
tenant_id INTEGER NOT NULL,
event_type VARCHAR(50) NOT NULL, -- 'low_stock', 'order_ready', 'maintenance_due', 'new_sale', 'po_received', 'reorder_alert', 'warranty_expiring'
channel VARCHAR(20) NOT NULL DEFAULT 'push', -- 'push', 'email', 'whatsapp', 'sms', 'in_app'
name VARCHAR(200) NOT NULL,
subject_template VARCHAR(500),
body_template TEXT NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, event_type, channel)
);
-- Notification delivery log
CREATE TABLE IF NOT EXISTS notification_logs (
id SERIAL PRIMARY KEY,
tenant_id INTEGER NOT NULL,
recipient_type VARCHAR(20) NOT NULL DEFAULT 'employee', -- 'employee', 'customer', 'owner', 'role'
recipient_id INTEGER,
event_type VARCHAR(50) NOT NULL,
channel VARCHAR(20) NOT NULL,
subject TEXT,
body TEXT,
status VARCHAR(20) DEFAULT 'pending', -- pending, sent, delivered, failed, read
error_message TEXT,
metadata JSONB, -- {sale_id, po_id, inventory_id, etc.}
sent_at TIMESTAMPTZ,
read_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_notif_logs_recipient ON notification_logs(recipient_type, recipient_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_notif_logs_status ON notification_logs(status) WHERE status IN ('pending', 'failed');
CREATE INDEX IF NOT EXISTS idx_notif_logs_event ON notification_logs(event_type, created_at DESC);
-- Notification preferences per employee
CREATE TABLE IF NOT EXISTS notification_preferences (
id SERIAL PRIMARY KEY,
employee_id INTEGER NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
event_type VARCHAR(50) NOT NULL,
channel VARCHAR(20) NOT NULL,
is_enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(employee_id, event_type, channel)
);
-- Push subscriptions table (if not already created by push_service)
CREATE TABLE IF NOT EXISTS push_subscriptions (
id SERIAL PRIMARY KEY,
employee_id INTEGER NOT NULL UNIQUE,
subscription_data TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Default templates
INSERT INTO notification_templates (tenant_id, event_type, channel, name, subject_template, body_template) VALUES
(1, 'low_stock', 'push', 'Stock Bajo', 'Stock bajo: {part_name}', 'El inventario de {part_name} ({part_number}) tiene solo {stock} unidades. Punto de reorden: {reorder_point}.'),
(1, 'order_ready', 'push', 'Orden Lista', 'Orden #{order_number} lista', 'La orden de servicio #{order_number} está lista para entrega. Cliente: {customer_name}.'),
(1, 'maintenance_due', 'push', 'Mantenimiento Vencido', 'Mantenimiento vencido', 'El vehículo {vehicle_plate} tiene mantenimiento de {maintenance_type} vencido. Kilometraje: {current_mileage}.'),
(1, 'new_sale', 'push', 'Nueva Venta', 'Venta registrada', 'Venta #{sale_id} por ${total} registrada. Método: {payment_method}.'),
(1, 'po_received', 'push', 'OC Recibida', 'Orden de compra recibida', 'La orden de compra #{po_id} fue recibida. Total: ${total}.'),
(1, 'reorder_alert', 'push', 'Alerta de Reorden', 'Reorden requerida', '{part_name} ({part_number}) está por debajo del punto de reorden. Stock: {stock}, Reorden: {reorder_point}.'),
(1, 'warranty_expiring', 'push', 'Garantía por vencer', 'Garantía por vencer', 'La garantía del item {part_name} vence el {expiry_date}. Cliente: {customer_name}.')
ON CONFLICT DO NOTHING;