Agrego carpeta del source sql de la Base de Datos
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
-- ============================================================================
|
||||
-- internal.limpiar_eventos_antiguos
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Higiene operativa: elimina filas de public.eventos cuya fecha
|
||||
-- esté fuera de la ventana de retención correspondiente a su tipo.
|
||||
-- No archiva: los eventos no son información que valga conservar
|
||||
-- más allá del operativo (ver Documentation/PoliticaRetencion.md
|
||||
-- §4.2 — el archivo registra cobros, no su historia de correcciones).
|
||||
--
|
||||
-- DOMINIO
|
||||
-- Implementa la higiene de eventos descrita en
|
||||
-- Documentation/PoliticaRetencion.md §7. Dos ventanas distintas
|
||||
-- porque los eventos de pagos viven más tiempo en la base que el
|
||||
-- resto: una corrección o anulación de pago suele auditarse más
|
||||
-- tarde que el cambio que la motivó. Tres meses es el horizonte
|
||||
-- suficiente para ese seguimiento; el resto (acciones admin sobre
|
||||
-- actividades, horarios, etc.) no requiere esa cola.
|
||||
--
|
||||
-- REGLA DE DISCRIMINACIÓN
|
||||
-- Evento de pagos sii `tipo LIKE 'pago_%'`. Lo determina el
|
||||
-- prefijo, no una tabla aparte ni un campo nuevo. Quien inserta
|
||||
-- en public.eventos elige el `tipo` y respeta el prefijo cuando
|
||||
-- corresponda; hoy lo cumplen `pago_editado` y `pago_anulado`
|
||||
-- (insertados por fc_editar_pago y fc_anular_pago vía
|
||||
-- internal.log_evento). Cualquier evento futuro relacionado a
|
||||
-- pagos debe seguir esta convención para heredar la ventana más
|
||||
-- larga.
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- Ninguno. Las dos ventanas en meses se leen de internal.app_config:
|
||||
-- - `retencion.eventos_pagos_meses` (default 3).
|
||||
-- - `retencion.eventos_otros_meses` (default 2).
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- No valida permiso propio. El schema `internal` no es alcanzable
|
||||
-- por roles cliente (ver hardening en
|
||||
-- `database/schema/00_schemas.sql`). Callers legítimos: pg_cron y
|
||||
-- fachadas `public.fc_*` SECURITY DEFINER que orquesten el pipeline.
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- Ninguno propio.
|
||||
--
|
||||
-- RETORNA
|
||||
-- JSONB con corte y eliminados desglosados por grupo (pagos / otros)
|
||||
-- más un total agregado, útil para inspección de operación.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- - DELETE de eventos `pago_*` con `fecha_evento < now() - ventana_pagos`.
|
||||
-- - DELETE de eventos no-`pago_*` con `fecha_evento < now() - ventana_otros`.
|
||||
-- - **No** registra evento propio (vía internal.log_evento o de otro
|
||||
-- modo). Hacerlo crearía un loop trivial: cada corrida generaría
|
||||
-- un evento que la próxima corrida vería viejo y volvería a
|
||||
-- producir otro. La purga es deliberadamente silenciosa; la
|
||||
-- visibilidad se garantiza por el contador devuelto en el JSONB
|
||||
-- y, eventualmente, por la vista admin del pipeline (US-R21).
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION internal.limpiar_eventos_antiguos()
|
||||
RETURNS JSONB
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
SET timezone = 'America/Argentina/Buenos_Aires'
|
||||
VOLATILE
|
||||
AS $$
|
||||
DECLARE
|
||||
v_meses_pagos INT;
|
||||
v_meses_otros INT;
|
||||
v_corte_pagos TIMESTAMPTZ;
|
||||
v_corte_otros TIMESTAMPTZ;
|
||||
v_eliminados_pagos INT;
|
||||
v_eliminados_otros INT;
|
||||
BEGIN
|
||||
v_meses_pagos := internal.get_config_int('retencion.eventos_pagos_meses', 3);
|
||||
v_meses_otros := internal.get_config_int('retencion.eventos_otros_meses', 2);
|
||||
|
||||
v_corte_pagos := now() - (v_meses_pagos * INTERVAL '1 month');
|
||||
v_corte_otros := now() - (v_meses_otros * INTERVAL '1 month');
|
||||
|
||||
DELETE FROM eventos
|
||||
WHERE tipo LIKE 'pago_%'
|
||||
AND fecha_evento < v_corte_pagos;
|
||||
GET DIAGNOSTICS v_eliminados_pagos = ROW_COUNT;
|
||||
|
||||
DELETE FROM eventos
|
||||
WHERE tipo NOT LIKE 'pago_%'
|
||||
AND fecha_evento < v_corte_otros;
|
||||
GET DIAGNOSTICS v_eliminados_otros = ROW_COUNT;
|
||||
|
||||
RETURN jsonb_build_object(
|
||||
'status', 'success',
|
||||
'corte_pagos', v_corte_pagos,
|
||||
'corte_otros', v_corte_otros,
|
||||
'meses_pagos', v_meses_pagos,
|
||||
'meses_otros', v_meses_otros,
|
||||
'eliminados_pagos', v_eliminados_pagos,
|
||||
'eliminados_otros', v_eliminados_otros,
|
||||
'eliminados_total', v_eliminados_pagos + v_eliminados_otros,
|
||||
'mensaje', format(
|
||||
'Eliminados %s eventos pago_* anteriores a %s y %s eventos no-pago anteriores a %s.',
|
||||
v_eliminados_pagos, v_corte_pagos,
|
||||
v_eliminados_otros, v_corte_otros
|
||||
)
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Defensa en profundidad: aunque el hardening del schema (00_schemas.sql)
|
||||
-- ya bloquee USAGE y EXECUTE por default, revocamos explícitamente acá
|
||||
-- por si en algún futuro alguien afloja las defensas a nivel schema.
|
||||
REVOKE ALL ON FUNCTION internal.limpiar_eventos_antiguos()
|
||||
FROM PUBLIC, anon, authenticated;
|
||||
@@ -0,0 +1,90 @@
|
||||
-- ============================================================================
|
||||
-- internal.log_evento
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Inserta una fila en public.eventos. Single entry point para que
|
||||
-- cualquier función pública registre un evento auditable (ediciones,
|
||||
-- anulaciones, acciones administrativas) sin tocar la tabla
|
||||
-- directamente.
|
||||
--
|
||||
-- Hoy lo usan fc_editar_pago y fc_anular_pago para registrar
|
||||
-- pago_editado y pago_anulado.
|
||||
--
|
||||
-- CAP DE SEGURIDAD (US-R12)
|
||||
-- Antes de insertar, chequea el conteo vigente de public.eventos
|
||||
-- contra `retencion.eventos_cap` (default 50000). Si registrar el
|
||||
-- evento haría que la tabla alcance o supere el cap, RECHAZA con
|
||||
-- excepción explícita en vez de insertar. No es FIFO: el rechazo
|
||||
-- hace fallar la función que dispara el evento y corta la cascada,
|
||||
-- de modo que un bug que dispare eventos en loop se vuelve ruidoso
|
||||
-- en vez de comerse la cuota de la base en silencio. El cap es un
|
||||
-- freno de emergencia, no la vía normal de control de tamaño (eso
|
||||
-- lo hace la purga mensual, internal.limpiar_eventos_antiguos).
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- p_tipo TEXT discriminador del evento (ej. 'pago_editado').
|
||||
-- p_tabla TEXT nombre de la tabla referenciada (ej. 'pagos').
|
||||
-- p_referencia_id UUID ID de la fila referenciada en esa tabla.
|
||||
-- p_cliente_id UUID cliente afectado (para eventos sobre clientes).
|
||||
-- p_actor_id UUID usuario que ejecutó la acción.
|
||||
-- p_valor_anterior JSONB snapshot del estado anterior (opcional).
|
||||
-- p_valor_actual JSONB snapshot del estado nuevo (opcional).
|
||||
-- p_descripcion TEXT (opcional, default NULL) descripción libre /
|
||||
-- motivo.
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- Función interna sin validación propia.
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- - 'No se puede registrar el evento ...: public.eventos alcanzó su
|
||||
-- cap de seguridad ...' (US-R12).
|
||||
--
|
||||
-- RETORNA
|
||||
-- void.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- INSERT en public.eventos.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION internal.log_evento(
|
||||
p_tipo text,
|
||||
p_tabla text,
|
||||
p_referencia_id uuid,
|
||||
p_cliente_id uuid,
|
||||
p_actor_id uuid,
|
||||
p_valor_anterior jsonb,
|
||||
p_valor_actual jsonb,
|
||||
p_descripcion text DEFAULT NULL::text
|
||||
)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path TO 'public'
|
||||
AS $$
|
||||
DECLARE
|
||||
v_cap INT;
|
||||
v_count INT;
|
||||
BEGIN
|
||||
-- Cap de seguridad (US-R12): freno anti-loop. Rechazamos el insert
|
||||
-- cuyo conteo resultante igualaría o superaría el cap, de modo que el
|
||||
-- cap es el valor que el conteo no debe alcanzar. Bug ruidoso > bug
|
||||
-- silencioso: que falle la cascada y nos enteremos.
|
||||
v_cap := internal.get_config_int('retencion.eventos_cap', 50000);
|
||||
SELECT count(*) INTO v_count FROM public.eventos;
|
||||
IF v_count + 1 >= v_cap THEN
|
||||
RAISE EXCEPTION
|
||||
'No se puede registrar el evento "%": public.eventos alcanzó su cap de seguridad (% filas). Suele indicar un bug que dispara eventos en loop; revisar la causa antes de subir retencion.eventos_cap en internal.app_config.',
|
||||
p_tipo, v_cap;
|
||||
END IF;
|
||||
|
||||
INSERT INTO public.eventos (
|
||||
tipo, tabla_referencia, referencia_id,
|
||||
cliente_id, actor_id,
|
||||
valor_anterior, valor_actual, descripcion
|
||||
) VALUES (
|
||||
p_tipo, p_tabla, p_referencia_id,
|
||||
p_cliente_id, p_actor_id,
|
||||
p_valor_anterior, p_valor_actual, p_descripcion
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
Reference in New Issue
Block a user