-- LA PUERTA DE LA ESTACION FISCAL + EL LATIDO (2026-07-29)
--
-- La Estacion Fiscal corre EN EL NEGOCIO (127.0.0.1), asi que la nube no la
-- puede llamar. Es ella la que hala: "dame las ventas que faltan por sellar".
-- Se identifica con la misma llave que le entrega la imprenta.
--
-- El LATIDO es para que Gilberto se entere: si una Estacion lleva horas sin
-- reportar, en el panel se ve. Sirve de control comercial y de evidencia.
--
-- FRONTERA: Gustito entrega el total que cobro y el detalle de lo que se
-- vendio. NO desglosa impuestos. Eso lo hace la Estacion, que es lo que se
-- homologa.
begin;

-- 1. QUIEN ERES ------------------------------------------------------------
-- Revisa la llave y devuelve el emisor. Deja rastro del intento, entre o no.
create or replace function fiscal.autenticar(p_llave text, p_rif text, p_motivo text)
returns uuid
language plpgsql
security definer
set search_path to 'fiscal', 'public', 'extensions'
as $fn$
declare
  v_emisor uuid;
  v_llave_id uuid;
  v_recientes int;
begin
  select id into v_emisor from fiscal.emisores
   where rif = public.fn_rif_normalizado(p_rif) and activo;
  if v_emisor is null then
    insert into fiscal.intentos (rif, entro, motivo, desde) values (p_rif, false, 'emisor no registrado', p_motivo);
    raise exception 'No autorizado';
  end if;

  select count(*) into v_recientes from fiscal.intentos
   where rif = public.fn_rif_normalizado(p_rif) and not entro and en > now() - interval '5 minutes';
  if v_recientes >= 20 then
    raise exception 'Demasiados intentos fallidos. Espera unos minutos.';
  end if;

  select l.id into v_llave_id
    from fiscal.llaves l
   where l.emisor_id = v_emisor and l.activa
     and l.llave_hash = extensions.crypt(p_llave, l.llave_hash)
   limit 1;
  if v_llave_id is null then
    insert into fiscal.intentos (rif, entro, motivo, desde) values (p_rif, false, 'llave invalida', p_motivo);
    raise exception 'No autorizado';
  end if;

  update fiscal.llaves set ultimo_uso = now() where id = v_llave_id;
  return v_emisor;
end;
$fn$;

-- 2. LAS VENTAS QUE FALTAN POR SELLAR --------------------------------------
create or replace function fiscal.ventas_pendientes(p_llave text, p_rif text, p_limite integer default 50)
returns jsonb
language plpgsql
security definer
set search_path to 'fiscal', 'public'
as $fn$
declare
  v_emisor uuid;
  v_res jsonb;
begin
  v_emisor := fiscal.autenticar(p_llave, p_rif, 'estacion: pendientes');

  with tomadas as (
    update fiscal.cola_gustito c
       set tomado_en = now()
     where c.id in (
       select id from fiscal.cola_gustito
        where emisor_id = v_emisor and estado = 'pendiente'
        order by creado_en
        limit greatest(1, least(coalesce(p_limite, 50), 200))
     )
    returning c.*
  )
  select coalesce(jsonb_agg(jsonb_build_object(
    'cola_id', t.id,
    'tipo', t.tipo,
    'origen_id', t.origen_id,
    'venta', case t.tipo
      when 'pedido' then (
        select jsonb_build_object(
          'codigo', p.codigo,
          'fecha', coalesce(p.entregado_en, p.recibido_en),
          'tipo_entrega', p.tipo_entrega,
          'mesa', p.mesa,
          'metodo_pago', p.metodo_pago,
          'origen', p.origen,
          'tasa_bs', p.tasa_bs,
          'total_usd', p.total_usd,
          'costo_delivery_usd', p.costo_delivery_usd,
          'comprador', jsonb_build_object(
            'nombre', p.cliente_nombre,
            'rif', p.cliente_rif,
            'razon_social', p.cliente_razon_social,
            'direccion', p.cliente_direccion_fiscal,
            'correo', p.cliente_correo,
            'telefono', p.cliente_telefono
          ),
          'renglones', coalesce((
            select jsonb_agg(jsonb_build_object(
              'nombre', it.nombre, 'cantidad', it.cantidad, 'precio_usd', it.precio_usd))
              from public.pedido_items it where it.pedido_id = p.id), '[]'::jsonb)
        ) from public.pedidos p where p.id = t.origen_id
      )
      else (
        select jsonb_build_object(
          'codigo', 'MESA-' || coalesce(cm.mesa, '?'),
          'fecha', coalesce(cm.cerrada_en, cm.abierta_en),
          'tipo_entrega', 'mesa',
          'mesa', cm.mesa,
          'metodo_pago', cm.metodo_pago,
          'tasa_bs', (select max(p.tasa_bs) from public.pedidos p where p.cuenta_id = cm.id),
          'total_usd', coalesce((select sum(p.total_usd) from public.pedidos p
                                  where p.cuenta_id = cm.id and p.estado <> 'cancelado'), 0),
          'costo_delivery_usd', 0,
          'comprador', jsonb_build_object(
            'nombre', coalesce(cm.cliente_razon_social, 'Consumidor Final'),
            'rif', cm.cliente_rif,
            'razon_social', cm.cliente_razon_social,
            'direccion', cm.cliente_direccion_fiscal,
            'correo', cm.cliente_correo,
            'telefono', null
          ),
          -- La propina va aparte: es servicio voluntario, no forma parte de la
          -- base imponible. La Estacion decide que hace con ella.
          'propina_usd', coalesce((select sum(pm.propina_usd) from public.pagos_mesa pm
                                    where pm.cuenta_id = cm.id), 0),
          'renglones', coalesce((
            select jsonb_agg(jsonb_build_object(
              'nombre', it.nombre, 'cantidad', it.cantidad, 'precio_usd', it.precio_usd))
              from public.pedido_items it
              join public.pedidos p on p.id = it.pedido_id
             where p.cuenta_id = cm.id and p.estado <> 'cancelado'), '[]'::jsonb)
        ) from public.cuentas_mesa cm where cm.id = t.origen_id
      )
    end
  ) order by t.creado_en), '[]'::jsonb) into v_res
  from tomadas t;

  return v_res;
end;
$fn$;

-- 3. LISTO, YA LA SELLE ----------------------------------------------------
create or replace function fiscal.confirmar_sellado(
  p_llave text, p_rif text, p_cola_id uuid,
  p_numero_documento bigint, p_numero_control bigint, p_token text
)
returns jsonb
language plpgsql
security definer
set search_path to 'fiscal', 'public'
as $fn$
declare v_emisor uuid;
begin
  v_emisor := fiscal.autenticar(p_llave, p_rif, 'estacion: confirmar');

  update fiscal.cola_gustito
     set estado = 'sellado', sellado_en = now(), ultimo_error = null,
         numero_documento = p_numero_documento,
         numero_control = p_numero_control,
         token = p_token
   where id = p_cola_id and emisor_id = v_emisor;

  if not found then raise exception 'Esa venta no es de este emisor'; end if;
  return jsonb_build_object('ok', true);
end;
$fn$;

-- Si algo salio mal, se devuelve a la cola con su motivo. A los 5 intentos deja
-- de reintentarse sola y queda marcada para que Gilberto la vea en el panel.
create or replace function fiscal.reportar_error(p_llave text, p_rif text, p_cola_id uuid, p_error text)
returns jsonb
language plpgsql
security definer
set search_path to 'fiscal', 'public'
as $fn$
declare
  v_emisor uuid;
  v_intentos int;
begin
  v_emisor := fiscal.autenticar(p_llave, p_rif, 'estacion: error');
  update fiscal.cola_gustito
     set intentos = intentos + 1,
         ultimo_error = left(coalesce(p_error, 'sin detalle'), 500),
         tomado_en = null,
         estado = case when intentos + 1 >= 5 then 'error' else 'pendiente' end
   where id = p_cola_id and emisor_id = v_emisor
  returning intentos into v_intentos;
  if not found then raise exception 'Esa venta no es de este emisor'; end if;
  return jsonb_build_object('ok', true, 'intentos', v_intentos);
end;
$fn$;

-- 4. EL LATIDO -------------------------------------------------------------
create table if not exists fiscal.estaciones (
  id uuid primary key default gen_random_uuid(),
  emisor_id uuid not null references fiscal.emisores(id) on delete cascade,
  nombre text not null default 'Estacion Fiscal',
  version text,
  equipo text,
  ultimo_latido timestamptz not null default now(),
  pendientes integer not null default 0,
  ultimo_documento bigint,
  libro_integro boolean,
  nota text,
  creada_en timestamptz not null default now(),
  unique (emisor_id, equipo)
);

revoke all on table fiscal.estaciones from anon, authenticated;

create or replace function fiscal.latido(p_llave text, p_rif text, p_datos jsonb default '{}'::jsonb)
returns jsonb
language plpgsql
security definer
set search_path to 'fiscal', 'public'
as $fn$
declare
  v_emisor uuid;
  v_pend int;
begin
  v_emisor := fiscal.autenticar(p_llave, p_rif, 'estacion: latido');

  insert into fiscal.estaciones (emisor_id, nombre, version, equipo, ultimo_latido,
                                 pendientes, ultimo_documento, libro_integro)
    values (v_emisor,
            coalesce(nullif(p_datos->>'nombre', ''), 'Estacion Fiscal'),
            p_datos->>'version',
            coalesce(nullif(p_datos->>'equipo', ''), 'unico'),
            now(),
            coalesce((p_datos->>'pendientes')::int, 0),
            nullif(p_datos->>'ultimo_documento', '')::bigint,
            (p_datos->>'libro_integro')::boolean)
    on conflict (emisor_id, equipo) do update set
      nombre = excluded.nombre,
      version = excluded.version,
      ultimo_latido = now(),
      pendientes = excluded.pendientes,
      ultimo_documento = excluded.ultimo_documento,
      libro_integro = excluded.libro_integro;

  select count(*) into v_pend from fiscal.cola_gustito
   where emisor_id = v_emisor and estado = 'pendiente';

  -- De paso se le responde si sigue habilitado: si Gilberto le bajo el breaker
  -- por falta de pago, la Estacion se entera en el siguiente latido.
  return jsonb_build_object('ok', true, 'pendientes_en_nube', v_pend, 'habilitado', true);
end;
$fn$;

-- 5. PUENTE UNICO PARA LA ESTACION ----------------------------------------
create or replace function public.fn_estacion(
  p_accion text, p_llave text, p_rif text, p_datos jsonb default '{}'::jsonb
)
returns jsonb
language plpgsql
security definer
set search_path to 'public', 'fiscal'
as $fn$
begin
  case p_accion
    when 'pendientes' then
      return fiscal.ventas_pendientes(p_llave, p_rif, coalesce((p_datos->>'limite')::int, 50));
    when 'confirmar' then
      return fiscal.confirmar_sellado(p_llave, p_rif, (p_datos->>'cola_id')::uuid,
        nullif(p_datos->>'numero_documento','')::bigint,
        nullif(p_datos->>'numero_control','')::bigint,
        p_datos->>'token');
    when 'error' then
      return fiscal.reportar_error(p_llave, p_rif, (p_datos->>'cola_id')::uuid, p_datos->>'error');
    when 'latido' then
      return fiscal.latido(p_llave, p_rif, p_datos);
    when 'rescatar' then
      perform fiscal.autenticar(p_llave, p_rif, 'estacion: rescate');
      return jsonb_build_object('rescatadas', fiscal.rescatar_sin_encolar(coalesce((p_datos->>'horas')::int, 72)));
    else
      raise exception 'Accion desconocida';
  end case;
end;
$fn$;

revoke execute on function public.fn_estacion(text, text, text, jsonb) from anon, authenticated, public;
grant execute on function public.fn_estacion(text, text, text, jsonb) to service_role;

-- 6. LO QUE VE GILBERTO EN EL PANEL ---------------------------------------
create or replace function fiscal.estaciones_vivas()
returns table(
  rif text, razon_social text, nombre text, version text, equipo text,
  ultimo_latido timestamptz, minutos_sin_reportar integer,
  pendientes integer, pendientes_nube integer, libro_integro boolean
)
language sql
stable
security definer
set search_path to 'fiscal', 'public'
as $fn$
  select e.rif, e.razon_social, s.nombre, s.version, s.equipo, s.ultimo_latido,
         (extract(epoch from (now() - s.ultimo_latido)) / 60)::int,
         s.pendientes,
         (select count(*)::int from fiscal.cola_gustito c
           where c.emisor_id = e.id and c.estado = 'pendiente'),
         s.libro_integro
    from fiscal.estaciones s
    join fiscal.emisores e on e.id = s.emisor_id
   order by s.ultimo_latido;
$fn$;

create or replace function fiscal.cola_por_revisar()
returns table(
  rif text, razon_social text, negocio text, tipo text, origen_id uuid,
  estado text, creado_en timestamptz, intentos integer, ultimo_error text
)
language sql
stable
security definer
set search_path to 'fiscal', 'public'
as $fn$
  select e.rif, e.razon_social, r.nombre, c.tipo, c.origen_id,
         c.estado, c.creado_en, c.intentos, c.ultimo_error
    from fiscal.cola_gustito c
    join fiscal.emisores e on e.id = c.emisor_id
    join public.restaurantes r on r.id = c.restaurante_id
   where c.estado <> 'sellado'
   order by c.creado_en
   limit 200;
$fn$;

commit;
