feat: configuración de destinatarios de notificaciones desde la app
Gestiona grupos de email y asignación por escenario en BD (natur_reservas.notification_groups / notification_scenario_recipients) en lugar de variables de entorno hardcodeadas. Incluye pantalla de ajustes, endpoint admin protegido, envío de prueba y niveles reducida/completa. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
87
supabase/migrations/012_notification_config.sql
Normal file
87
supabase/migrations/012_notification_config.sql
Normal file
@@ -0,0 +1,87 @@
|
||||
-- Migración 012: Configuración de destinatarios de notificaciones
|
||||
--
|
||||
-- Mueve "quién recibe cada correo" desde variables de entorno (Dokploy) a la BD,
|
||||
-- gestionable desde la app. Dos tablas:
|
||||
-- notification_groups — grupos de destinatarios con un nivel de info fijo
|
||||
-- notification_scenario_recipients — qué grupos recibe cada escenario (event_type)
|
||||
--
|
||||
-- IMPORTANTE: schema natur_reservas (la instancia Supabase es compartida; public es
|
||||
-- otro proyecto). Ver CLAUDE.md §1.
|
||||
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
-- Grupos de destinatarios
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
CREATE TABLE natur_reservas.notification_groups (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
level TEXT NOT NULL DEFAULT 'completa'
|
||||
CHECK (level IN ('reducida', 'completa')),
|
||||
emails TEXT[] NOT NULL DEFAULT '{}',
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE natur_reservas.notification_groups IS
|
||||
'Grupos de destinatarios de notificaciones. level=reducida solo puede recibir la versión reducida de los correos (regla de privacidad).';
|
||||
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
-- Asignación escenario → grupos (N:M)
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
CREATE TABLE natur_reservas.notification_scenario_recipients (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
scenario TEXT NOT NULL, -- igual que notification_events.event_type
|
||||
group_id UUID NOT NULL
|
||||
REFERENCES natur_reservas.notification_groups(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT uq_scenario_group UNIQUE (scenario, group_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_scenario_recipients_scenario
|
||||
ON natur_reservas.notification_scenario_recipients(scenario);
|
||||
|
||||
COMMENT ON TABLE natur_reservas.notification_scenario_recipients IS
|
||||
'Qué grupos reciben cada escenario (event_type). El motor de envío en apps/api lo consulta con service_role.';
|
||||
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
-- RLS: el acceso del frontend va por el API (service_role bypasea RLS).
|
||||
-- Bloqueamos acceso directo de anon/authenticated; el API valida admin.
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
ALTER TABLE natur_reservas.notification_groups ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE natur_reservas.notification_scenario_recipients ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- El API accede con service_role: bypasea RLS, pero necesita el GRANT de tabla
|
||||
-- (en schemas custom no se concede automáticamente, a diferencia de public).
|
||||
GRANT USAGE ON SCHEMA natur_reservas TO service_role;
|
||||
GRANT ALL ON natur_reservas.notification_groups TO service_role;
|
||||
GRANT ALL ON natur_reservas.notification_scenario_recipients TO service_role;
|
||||
|
||||
REVOKE ALL ON natur_reservas.notification_groups FROM anon;
|
||||
REVOKE ALL ON natur_reservas.notification_scenario_recipients FROM anon;
|
||||
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
-- Semilla inicial (editable después desde la pantalla)
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
WITH nuevos AS (
|
||||
INSERT INTO natur_reservas.notification_groups (name, level, emails) VALUES
|
||||
('Teneriffa', 'reducida',
|
||||
ARRAY['uwe.dotschkis@teneriffa2000.de', 'j.reichstein@teneriffa2000.de']),
|
||||
('Naturcalabacera (Admin)', 'completa',
|
||||
ARRAY['administracion@naturcalabacera.com']),
|
||||
('Limpieza', 'completa',
|
||||
ARRAY['limpieza.naturcalabacera@gmail.com']) -- placeholder editable
|
||||
RETURNING id, name
|
||||
)
|
||||
INSERT INTO natur_reservas.notification_scenario_recipients (scenario, group_id)
|
||||
SELECT s.scenario, n.id
|
||||
FROM nuevos n
|
||||
JOIN (VALUES
|
||||
('Teneriffa', 'reservation.created'),
|
||||
('Teneriffa', 'reservation.updated'),
|
||||
('Teneriffa', 'reservation.cancelled'),
|
||||
('Naturcalabacera (Admin)', 'reservation.created'),
|
||||
('Naturcalabacera (Admin)', 'reservation.updated'),
|
||||
('Naturcalabacera (Admin)', 'reservation.cancelled'),
|
||||
('Naturcalabacera (Admin)', 'reservation.invoice_second_notice'),
|
||||
('Limpieza', 'reservation.pool_heating_notice')
|
||||
) AS s(group_name, scenario) ON s.group_name = n.name;
|
||||
Reference in New Issue
Block a user