-- HALLAZGO 1, ARREGLADO DE RAIZ.
--
-- El primer intento (migration 20260730100000) le puso frenos al INSERT
-- publico: tope de largo, freno por direccion, techo de filas. Gilberto lo
-- paro en seco y tenia razon: eso era ponerle candados a una puerta que NO
-- DEBERIA ESTAR ABIERTA.
--
-- LA RAIZ: el navegador escribia DIRECTO en una tabla. Ese es el error de
-- diseño. Ninguna telemetria se hace asi: mientras la tabla acepte escritura
-- anonima, siempre habra que estar cuidandole las espaldas.
--
-- Como queda: la tabla NO acepta escritura de nadie de afuera. La unica
-- entrada es una funcion que valida lo que entra, lo recorta, lo frena y
-- recien ahi escribe. El navegador ya no toca la tabla: llama a la puerta.

begin;

-- ===== 1. SE CIERRA LA TABLA =====
drop policy if exists eventos_web_insert_publico on public.eventos_web;
revoke insert, update, delete on public.eventos_web from anon, authenticated;

-- Los frenos del intento anterior se retiran: la validacion vive en la puerta,
-- no repartida entre un trigger y una politica.
drop trigger if exists trg_freno_medicion on public.eventos_web;
drop function if exists public.tg_freno_medicion();

-- El tope de largo SI se queda: es integridad de la tabla, no un parche. Que
-- la puerta valide no quita que la tabla se defienda sola.

-- ===== 2. LA UNICA PUERTA =====
/**
 * Registra un evento de la medicion propia (landing y pantallas publicas).
 *
 * Es la UNICA forma de escribir en eventos_web. Aca se decide todo:
 *   - El evento tiene que ser uno de los que existen. Nombre inventado, fuera.
 *   - Los textos se RECORTAN (no se rechazan): la medicion nunca debe hacer
 *     ruido, y un referrer largo no es culpa de nadie.
 *   - Freno por direccion: 120 por hora. Una visita normal manda unos pocos.
 *   - Nunca lanza por culpa de la medicion misma: devuelve false y ya. Medir
 *     JAMAS puede romper una pagina ni ensuciar la consola del visitante.
 */
create or replace function public.fn_medir(
  p_evento text,
  p_pantalla text default 'landing',
  p_sesion uuid default null,
  p_origen text default null,
  p_utm_source text default null,
  p_utm_medium text default null,
  p_utm_campaign text default null
)
returns boolean
language plpgsql
security definer
set search_path to 'public'
as $$
declare v_ip text;
begin
  -- Solo los eventos que existen. Esta lista es la misma de medicion.ts.
  if p_evento is null or p_evento not in (
    'visita', 'click_registro', 'click_entrar', 'click_vitrina',
    'click_whatsapp', 'click_demo', 'cuenta_creada'
  ) then
    return false;
  end if;

  -- Freno por direccion. No es muralla (una direccion puede rotar), pero es
  -- lo que evita que una sola maquina inunde. Si el freno salta, se responde
  -- false en vez de reventar: medir no rompe nada.
  begin
    v_ip := split_part(coalesce(
      current_setting('request.headers', true)::jsonb ->> 'cf-connecting-ip',
      current_setting('request.headers', true)::jsonb ->> 'x-forwarded-for',
      'sin-direccion'), ',', 1);
    perform public.fn_rate_limit('medicion:' || v_ip, 120, 3600);
  exception when others then
    return false;
  end;

  insert into public.eventos_web (evento, pantalla, sesion, origen, utm_source, utm_medium, utm_campaign)
    values (
      p_evento,
      left(coalesce(nullif(trim(p_pantalla), ''), 'landing'), 60),
      p_sesion,
      left(nullif(trim(p_origen), ''), 200),
      left(nullif(trim(p_utm_source), ''), 100),
      left(nullif(trim(p_utm_medium), ''), 100),
      left(nullif(trim(p_utm_campaign), ''), 100)
    );
  return true;
end $$;

revoke all on function public.fn_medir(text, text, uuid, text, text, text, text) from public;
grant execute on function public.fn_medir(text, text, uuid, text, text, text, text) to anon, authenticated;

-- ===== 3. QUE SE LIMPIE SOLA =====
-- Ya no hace falta techo de filas: con la puerta cerrada y el freno adentro,
-- lo que queda es que la tabla no crezca para siempre.
create or replace function public.fn_limpiar_medicion_vieja()
returns integer
language plpgsql
security definer
set search_path to 'public'
as $$
declare v_n integer;
begin
  delete from public.eventos_web where creado_en < now() - interval '90 days';
  get diagnostics v_n = row_count;
  delete from public.rate_limits where ventana_fin < now() - interval '1 day';
  return v_n;
end $$;

commit;
