-- EL PANEL DE LA IMPRENTA DIGITAL (2026-07-28)
-- El negocio visto desde la silla de Gilberto: dar de alta clientes, generar y
-- revocar llaves, ver cuanto factura cada uno y sacar el reporte mensual del
-- SENIAT. Sin esto no se puede vender el servicio aunque el motor funcione,
-- porque cada alta habria que hacerla a mano en la base.
begin;

-- Quien entra al panel. Clave con bcrypt, nunca en claro.
create table if not exists fiscal.operadores (
  id uuid primary key default gen_random_uuid(),
  usuario text not null unique,
  clave_hash text not null,
  nombre text not null,
  activo boolean not null default true,
  creado_en timestamptz not null default now(),
  ultimo_acceso timestamptz
);

-- Datos de contacto y control comercial de cada cliente de la imprenta.
alter table fiscal.emisores
  add column if not exists representante text,
  add column if not exists cedula_representante text,
  add column if not exists correo text,
  add column if not exists telefono text,
  add column if not exists sistema text,
  add column if not exists serie_asignada text default 'A',
  add column if not exists rif_comprobado boolean not null default false,
  add column if not exists nota text;

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

create or replace function fiscal.crear_operador(p_usuario text, p_clave text, p_nombre text)
returns uuid
language plpgsql
security definer
set search_path to 'fiscal', 'public', 'extensions'
as $fn$
declare v_id uuid;
begin
  if length(p_clave) < 10 then raise exception 'La clave debe tener al menos 10 caracteres'; end if;
  insert into fiscal.operadores (usuario, clave_hash, nombre)
    values (lower(trim(p_usuario)), extensions.crypt(p_clave, extensions.gen_salt('bf', 10)), p_nombre)
    on conflict (usuario) do update
      set clave_hash = excluded.clave_hash, nombre = excluded.nombre, activo = true
  returning id into v_id;
  return v_id;
end;
$fn$;

create or replace function fiscal.entrar_panel(p_usuario text, p_clave text)
returns table(entro boolean, nombre text)
language plpgsql
security definer
set search_path to 'fiscal', 'public', 'extensions'
as $fn$
declare v_op record;
begin
  select * into v_op from fiscal.operadores
   where usuario = lower(trim(p_usuario)) and activo
     and clave_hash = extensions.crypt(p_clave, clave_hash);
  if not found then
    return query select false, null::text;
    return;
  end if;
  update fiscal.operadores set ultimo_acceso = now() where id = v_op.id;
  return query select true, v_op.nombre;
end;
$fn$;

-- LA LISTA DE CLIENTES con lo que importa para cobrar y para vigilar.
create or replace function fiscal.clientes()
returns table(
  rif text,
  razon_social text,
  correo text,
  telefono text,
  sistema text,
  serie_asignada text,
  activo boolean,
  rif_comprobado boolean,
  llaves_activas integer,
  documentos_mes integer,
  total_mes_bs numeric,
  ultimo_documento timestamptz,
  fallidos_hoy integer
)
language sql
stable
security definer
set search_path to 'fiscal', 'public'
as $fn$
  select e.rif, e.razon_social, e.correo, e.telefono, e.sistema, e.serie_asignada,
         e.activo, e.rif_comprobado,
         (select count(*)::int from fiscal.llaves l where l.emisor_id = e.id and l.activa),
         (select count(*)::int from fiscal.documentos d
           where d.emisor_id = e.id and d.fecha_emision >= date_trunc('month', now())),
         coalesce((select round(sum(d.total_bs), 2) from fiscal.documentos d
           where d.emisor_id = e.id and d.fecha_emision >= date_trunc('month', now())), 0),
         (select max(d.recibido_en) from fiscal.documentos d where d.emisor_id = e.id),
         (select count(*)::int from fiscal.intentos i
           where i.rif = e.rif and not i.entro and i.en >= current_date)
    from fiscal.emisores e
   order by e.razon_social;
$fn$;

-- Alta de un cliente. El comprobante de RIF es obligacion del art. 29, asi que
-- se guarda si ya se verifico o no: no se emite para quien no lo entrego.
create or replace function fiscal.guardar_cliente(
  p_rif text,
  p_razon_social text,
  p_domicilio text,
  p_representante text default null,
  p_cedula text default null,
  p_correo text default null,
  p_telefono text default null,
  p_sistema text default null,
  p_serie text default 'A',
  p_rif_comprobado boolean default false,
  p_nota text default null
)
returns text
language plpgsql
security definer
set search_path to 'fiscal', 'public'
as $fn$
declare v_rif text;
begin
  if not public.fn_rif_valido(p_rif) then
    raise exception 'Ese RIF no es valido, revisa el ultimo digito';
  end if;
  v_rif := public.fn_rif_normalizado(p_rif);
  insert into fiscal.emisores (rif, razon_social, domicilio_fiscal, llave_hash,
                               representante, cedula_representante, correo, telefono,
                               sistema, serie_asignada, rif_comprobado, nota)
    values (v_rif, trim(p_razon_social), trim(p_domicilio), 'sin-uso',
            p_representante, p_cedula, p_correo, p_telefono,
            p_sistema, upper(coalesce(p_serie, 'A')), p_rif_comprobado, p_nota)
    on conflict (rif) do update set
      razon_social = excluded.razon_social,
      domicilio_fiscal = excluded.domicilio_fiscal,
      representante = excluded.representante,
      cedula_representante = excluded.cedula_representante,
      correo = excluded.correo,
      telefono = excluded.telefono,
      sistema = excluded.sistema,
      serie_asignada = excluded.serie_asignada,
      rif_comprobado = excluded.rif_comprobado,
      nota = excluded.nota;
  return v_rif;
end;
$fn$;

create or replace function fiscal.activar_cliente(p_rif text, p_activo boolean)
returns boolean
language sql
security definer
set search_path to 'fiscal', 'public'
as $fn$
  update fiscal.emisores set activo = p_activo
   where rif = public.fn_rif_normalizado(p_rif)
  returning activo;
$fn$;

-- Las llaves de un cliente. NUNCA se devuelve la llave, solo sus datos.
create or replace function fiscal.llaves_de(p_rif text)
returns table(id uuid, nombre text, activa boolean, creada_en timestamptz, ultimo_uso timestamptz)
language sql
stable
security definer
set search_path to 'fiscal', 'public'
as $fn$
  select l.id, l.nombre, l.activa, l.creada_en, l.ultimo_uso
    from fiscal.llaves l
    join fiscal.emisores e on e.id = l.emisor_id
   where e.rif = public.fn_rif_normalizado(p_rif)
   order by l.creada_en desc;
$fn$;

create or replace function fiscal.revocar_llave(p_id uuid)
returns boolean
language sql
security definer
set search_path to 'fiscal', 'public'
as $fn$
  update fiscal.llaves set activa = false, revocada_en = now()
   where id = p_id
  returning true;
$fn$;

-- Como va el negocio en el mes: lo que Gilberto mira de primero.
create or replace function fiscal.tablero()
returns table(
  clientes_activos integer,
  documentos_mes integer,
  total_mes_bs numeric,
  documentos_hoy integer,
  intentos_fallidos_hoy integer,
  periodos_sin_reportar integer
)
language sql
stable
security definer
set search_path to 'fiscal', 'public'
as $fn$
  select
    (select count(*)::int from fiscal.emisores where activo),
    (select count(*)::int from fiscal.documentos where fecha_emision >= date_trunc('month', now())),
    coalesce((select round(sum(total_bs), 2) from fiscal.documentos where fecha_emision >= date_trunc('month', now())), 0),
    (select count(*)::int from fiscal.documentos where recibido_en >= current_date),
    (select count(*)::int from fiscal.intentos where not entro and en >= current_date),
    -- Meses cerrados con documentos y sin constancia de envio al SENIAT: eso es
    -- lo que hay que vigilar, porque dos periodos sin reportar es revocatoria.
    (select count(*)::int from (
       select d.emisor_id, date_trunc('month', d.fecha_emision) as periodo
         from fiscal.documentos d
        where d.fecha_emision < date_trunc('month', now())
        group by 1, 2
       except
       select em.emisor_id, em.periodo from fiscal.envios_mensuales em
     ) pendientes);
$fn$;

-- Puente unico para el panel: solo lo llama el servidor.
create or replace function public.fn_panel_imprenta(p_accion text, p_datos jsonb default '{}'::jsonb)
returns jsonb
language plpgsql
security definer
set search_path to 'public', 'fiscal'
as $fn$
declare v jsonb;
begin
  case p_accion
    when 'entrar' then
      select to_jsonb(t) into v from fiscal.entrar_panel(p_datos->>'usuario', p_datos->>'clave') t;
    when 'tablero' then
      select to_jsonb(t) into v from fiscal.tablero() t;
    when 'clientes' then
      select coalesce(jsonb_agg(to_jsonb(t)), '[]'::jsonb) into v from fiscal.clientes() t;
    when 'guardar_cliente' then
      select to_jsonb(fiscal.guardar_cliente(
        p_datos->>'rif', p_datos->>'razon_social', p_datos->>'domicilio',
        p_datos->>'representante', p_datos->>'cedula', p_datos->>'correo',
        p_datos->>'telefono', p_datos->>'sistema', coalesce(p_datos->>'serie', 'A'),
        coalesce((p_datos->>'rif_comprobado')::boolean, false), p_datos->>'nota')) into v;
    when 'activar_cliente' then
      select to_jsonb(fiscal.activar_cliente(p_datos->>'rif', (p_datos->>'activo')::boolean)) into v;
    when 'llaves' then
      select coalesce(jsonb_agg(to_jsonb(t)), '[]'::jsonb) into v from fiscal.llaves_de(p_datos->>'rif') t;
    when 'crear_llave' then
      select to_jsonb(fiscal.crear_llave(p_datos->>'rif', coalesce(p_datos->>'nombre', 'llave'))) into v;
    when 'revocar_llave' then
      select to_jsonb(fiscal.revocar_llave((p_datos->>'id')::uuid)) into v;
    when 'resumen_mensual' then
      select to_jsonb(t) into v from fiscal.resumen_mensual(
        p_datos->>'rif', (p_datos->>'anio')::int, (p_datos->>'mes')::int) t;
    when 'relacion_mensual' then
      select coalesce(jsonb_agg(to_jsonb(t)), '[]'::jsonb) into v from fiscal.relacion_mensual(
        p_datos->>'rif', (p_datos->>'anio')::int, (p_datos->>'mes')::int) t;
    when 'marcar_enviado' then
      select to_jsonb(fiscal.marcar_enviado(
        p_datos->>'rif', (p_datos->>'anio')::int, (p_datos->>'mes')::int,
        p_datos->>'quien', p_datos->>'nota')) into v;
    else
      raise exception 'Accion desconocida';
  end case;
  return coalesce(v, 'null'::jsonb);
end;
$fn$;

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

commit;
