michangarrito/database/schemas/18-webhooks.sql
rckrdmrd 5a49ad0185 [INTEGRATION] feat: Integrate template-saas scopes and database objects
## Documentation
- Align MCH-029 to MCH-032 with template-saas modules (SAAS-008 to SAAS-015)
- Create MCH-034 (Analytics) and MCH-035 (Reports) from SAAS-016/017
- Update PLAN-DESARROLLO.md with Phase 7 and 8
- Update _MAP.md indexes (35 total epics)

## Database (5 new schemas, 14 tables)
- Add storage schema: buckets, files, signed_urls
- Add webhooks schema: endpoints, deliveries
- Add audit schema: logs, retention_policies
- Add features schema: flags, tenant_flags (14 seeds)
- Add analytics schema: metrics, events, reports, report_schedules
- Add auth.oauth_connections for MCH-030
- Add timestamptz_to_date() IMMUTABLE function
- Update EXPECTED_SCHEMAS in recreate-database.sh

## Analysis Reports
- ANALISIS-INTEGRACION-TEMPLATE-SAAS-2026-01-13.md
- VALIDACION-COHERENCIA-2026-01-13.md
- GAP-ANALYSIS-BD-2026-01-13.md
- REPORTE-EJECUCION-2026-01-13.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 07:10:55 -06:00

143 lines
4.6 KiB
PL/PgSQL

-- =============================================================================
-- MICHANGARRITO - 18 WEBHOOKS (MCH-029: Infraestructura SaaS)
-- =============================================================================
-- Sistema de webhooks salientes para integraciones externas
-- Permite a los tenants recibir notificaciones de eventos en sus sistemas
-- =============================================================================
-- Tipos de evento webhook
CREATE TYPE webhook_event_type AS ENUM (
-- Ventas
'sale.created',
'sale.completed',
'sale.cancelled',
'sale.refunded',
-- Productos
'product.created',
'product.updated',
'product.deleted',
'product.low_stock',
-- Clientes
'customer.created',
'customer.updated',
-- Fiados
'credit.created',
'credit.payment',
'credit.overdue',
-- Pedidos
'order.created',
'order.confirmed',
'order.delivered',
'order.cancelled',
-- Suscripciones
'subscription.created',
'subscription.renewed',
'subscription.cancelled',
'subscription.expired'
);
-- Endpoints de webhook
CREATE TABLE IF NOT EXISTS webhooks.endpoints (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
-- Configuracion
name VARCHAR(100) NOT NULL,
url VARCHAR(500) NOT NULL,
secret VARCHAR(255) NOT NULL, -- Para firma HMAC-SHA256
-- Eventos suscritos
events webhook_event_type[] NOT NULL,
-- Estado
is_active BOOLEAN DEFAULT true,
-- Rate limiting
rate_limit INTEGER DEFAULT 100, -- requests por minuto
-- Estadisticas
total_deliveries INTEGER DEFAULT 0,
failed_deliveries INTEGER DEFAULT 0,
last_delivery_at TIMESTAMPTZ,
last_failure_at TIMESTAMPTZ,
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_webhooks_endpoints_tenant ON webhooks.endpoints(tenant_id);
CREATE INDEX idx_webhooks_endpoints_active ON webhooks.endpoints(tenant_id, is_active) WHERE is_active = true;
CREATE TRIGGER update_webhooks_endpoints_updated_at
BEFORE UPDATE ON webhooks.endpoints
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
COMMENT ON TABLE webhooks.endpoints IS 'Endpoints de webhook configurados por tenant';
COMMENT ON COLUMN webhooks.endpoints.secret IS 'Secret para firma HMAC-SHA256 del payload';
COMMENT ON COLUMN webhooks.endpoints.events IS 'Array de tipos de evento suscritos';
-- Estado de entrega
CREATE TYPE webhook_delivery_status AS ENUM (
'pending',
'success',
'failed',
'retrying'
);
-- Entregas de webhook (log)
CREATE TABLE IF NOT EXISTS webhooks.deliveries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
endpoint_id UUID NOT NULL REFERENCES webhooks.endpoints(id) ON DELETE CASCADE,
-- Evento
event_type webhook_event_type NOT NULL,
event_id VARCHAR(100), -- ID del evento original (sale_id, order_id, etc.)
payload JSONB NOT NULL,
-- Estado de entrega
status webhook_delivery_status DEFAULT 'pending',
-- Respuesta
response_code INTEGER,
response_body TEXT,
response_time_ms INTEGER,
-- Reintentos
attempts INTEGER DEFAULT 0,
max_attempts INTEGER DEFAULT 5,
next_retry_at TIMESTAMPTZ,
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW(),
delivered_at TIMESTAMPTZ
);
CREATE INDEX idx_webhooks_deliveries_endpoint ON webhooks.deliveries(endpoint_id);
CREATE INDEX idx_webhooks_deliveries_status ON webhooks.deliveries(status) WHERE status IN ('pending', 'retrying');
CREATE INDEX idx_webhooks_deliveries_retry ON webhooks.deliveries(next_retry_at) WHERE status = 'retrying';
CREATE INDEX idx_webhooks_deliveries_created ON webhooks.deliveries(created_at DESC);
COMMENT ON TABLE webhooks.deliveries IS 'Log de entregas de webhooks';
COMMENT ON COLUMN webhooks.deliveries.attempts IS 'Numero de intentos realizados';
COMMENT ON COLUMN webhooks.deliveries.next_retry_at IS 'Timestamp del proximo reintento (backoff exponencial)';
-- =============================================================================
-- FUNCION: Calcular siguiente retry con backoff exponencial
-- =============================================================================
CREATE OR REPLACE FUNCTION webhooks.calculate_next_retry(attempts INTEGER)
RETURNS TIMESTAMPTZ AS $$
BEGIN
-- Backoff exponencial: 1min, 5min, 15min, 1hr, 4hr
RETURN NOW() + (POWER(2, LEAST(attempts, 5)) * INTERVAL '1 minute');
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION webhooks.calculate_next_retry IS 'Calcula el timestamp del siguiente reintento con backoff exponencial';