-- LADO IMPRENTA DIGITAL (2026-07-28)
-- Esto NO es Gustito facturando. Es el servicio de imprenta digital autorizada
-- que Gilberto va a solicitar ante la Intendencia Nacional de Tributos Internos
-- (Providencia SNAT/2024/000102, arts. 24 al 34): recibe documentos de las
-- Estaciones Fiscales de los negocios, les asigna el numero de control, los
-- resguarda y responde por ellos.
--
-- Va en ESQUEMA APARTE (fiscal), no en public, por tres razones:
--   1. El art. 25 obliga a confidencialidad sobre los documentos que se
--      procesan: no pueden vivir revueltos con la operacion de los restaurantes.
--   2. Al SENIAT hay que darle acceso de consulta (art. 29). Ese acceso tiene
--      que llegar SOLO hasta aca, nunca a las mesas, pedidos ni clientes de
--      Gustito.
--   3. PostgREST solo expone los esquemas configurados: 'fiscal' no se expone,
--      asi que a estas tablas no se llega desde el navegador de nadie. Se
--      entra por la puerta de servicio (Pages Functions con llave de servidor).
begin;

create schema if not exists fiscal;

-- EMISORES: los negocios autorizados a emitir a traves de esta imprenta.
create table if not exists fiscal.emisores (
  id uuid primary key default gen_random_uuid(),
  rif text not null unique,
  razon_social text not null,
  domicilio_fiscal text not null,
  -- Llave con la que la Estacion Fiscal del local se identifica. Se guarda
  -- HASHEADA: si alguien se lleva la tabla, no se lleva las llaves.
  llave_hash text not null,
  activo boolean not null default true,
  creado_en timestamptz not null default now(),
  constraint emisores_rif_valido check (public.fn_rif_valido(rif))
);

-- SERIES: aca vive el contador del numero de control, uno por emisor y serie.
-- Es el corazon de "consecutivo y unico": repetir un numero de control es
-- causal de revocatoria (art. 34, numeral 2).
create table if not exists fiscal.series (
  emisor_id uuid not null references fiscal.emisores(id) on delete restrict,
  serie text not null,
  ultimo_control bigint not null default 0,
  primary key (emisor_id, serie)
);

-- DOCUMENTOS: lo recibido y numerado. Solo se INSERTA; no se edita ni se borra.
create table if not exists fiscal.documentos (
  id uuid primary key default gen_random_uuid(),
  emisor_id uuid not null references fiscal.emisores(id) on delete restrict,
  serie text not null,
  tipo text not null check (tipo in ('factura', 'nota_credito', 'nota_debito')),
  numero_documento bigint not null,
  numero_control bigint not null,
  -- Token OPACO para el QR. El codigo impreso nunca lleva el id real ni la
  -- ruta del archivo: lleva esto, y el enrutador resuelve por dentro.
  token text not null unique,
  -- Sello que trae el documento desde el libro del local: amarra las dos puntas.
  hash_documento text not null,
  fecha_emision timestamptz not null,
  base_bs numeric(14,2) not null,
  iva_bs numeric(14,2) not null,
  total_bs numeric(14,2) not null,
  -- El documento completo, tal como llego.
  documento jsonb not null,
  recibido_en timestamptz not null default now(),
  -- Un mismo documento del mismo emisor no se numera dos veces.
  unique (emisor_id, tipo, numero_documento),
  unique (emisor_id, hash_documento)
);

create index if not exists documentos_emisor_fecha on fiscal.documentos (emisor_id, fecha_emision desc);
create index if not exists documentos_control on fiscal.documentos (emisor_id, numero_control);

-- CONSULTAS DEL QR: cada vez que alguien escanea, queda registrado. Trazabilidad
-- y ademas sirve para frenar a quien ande probando tokens al azar.
create table if not exists fiscal.consultas_qr (
  id bigserial primary key,
  token text not null,
  encontrado boolean not null,
  desde text,
  en timestamptz not null default now()
);

create index if not exists consultas_qr_token_en on fiscal.consultas_qr (token, en desc);

-- Nadie llega a este esquema desde el navegador: ni anon ni authenticated.
revoke all on schema fiscal from anon, authenticated;
revoke all on all tables in schema fiscal from anon, authenticated;

-- ASIGNAR NUMERO DE CONTROL. Consecutivo, unico y sin huecos.
-- El update toma el candado de la fila, asi que dos documentos que lleguen en
-- el mismo instante se ponen en fila india: uno recibe el 501 y el otro el 502.
-- Nunca el mismo numero para los dos.
create or replace function fiscal.asignar_control(p_emisor uuid, p_serie text)
returns bigint
language plpgsql
security definer
set search_path to 'fiscal', 'public'
as $fn$
declare v_control bigint;
begin
  insert into fiscal.series (emisor_id, serie, ultimo_control)
    values (p_emisor, p_serie, 0)
    on conflict (emisor_id, serie) do nothing;

  update fiscal.series
     set ultimo_control = ultimo_control + 1
   where emisor_id = p_emisor and serie = p_serie
  returning ultimo_control into v_control;

  if v_control is null then
    raise exception 'No se pudo asignar numero de control al emisor %', p_emisor;
  end if;
  return v_control;
end;
$fn$;

-- RECIBIR UN DOCUMENTO: numera y guarda, todo en la misma transaccion.
-- Si el mismo documento vuelve a llegar (la Estacion reintento porque se cayo
-- el internet), devuelve el numero que ya se le habia dado en vez de numerarlo
-- otra vez: eso evita el peor error posible, dos numeros de control para la
-- misma factura.
create or replace function fiscal.recibir_documento(
  p_rif text,
  p_serie text,
  p_tipo text,
  p_numero_documento bigint,
  p_hash text,
  p_fecha timestamptz,
  p_base_bs numeric,
  p_iva_bs numeric,
  p_total_bs numeric,
  p_documento jsonb
)
returns table(numero_control bigint, token text, ya_estaba boolean)
language plpgsql
security definer
set search_path to 'fiscal', 'public', 'extensions'
as $fn$
declare
  v_emisor uuid;
  v_control bigint;
  v_token text;
begin
  select e.id into v_emisor
    from fiscal.emisores e
   where e.rif = public.fn_rif_normalizado(p_rif) and e.activo;
  if v_emisor is null then
    raise exception 'Ese RIF no esta autorizado a emitir por esta imprenta';
  end if;

  -- ¿Ya lo habiamos numerado? (mismo sello del libro del local)
  select d.numero_control, d.token into v_control, v_token
    from fiscal.documentos d
   where d.emisor_id = v_emisor and d.hash_documento = p_hash;
  if found then
    return query select v_control, v_token, true;
    return;
  end if;

  v_control := fiscal.asignar_control(v_emisor, p_serie);
  -- Token de 16 bytes (32 caracteres): 128 bits, imposible de adivinar, y la
  -- direccion del sello queda corta para que el QR impreso salga menos denso.
  v_token := encode(extensions.gen_random_bytes(16), 'hex');

  insert into fiscal.documentos (
    emisor_id, serie, tipo, numero_documento, numero_control, token,
    hash_documento, fecha_emision, base_bs, iva_bs, total_bs, documento
  ) values (
    v_emisor, p_serie, p_tipo, p_numero_documento, v_control, v_token,
    p_hash, p_fecha, p_base_bs, p_iva_bs, p_total_bs, p_documento
  );

  return query select v_control, v_token, false;
end;
$fn$;

-- VALIDAR UN TOKEN (lo que hay detras del enrutador del QR).
-- Devuelve SOLO lo justo para comprobar que la factura es real: quien la
-- emitio, su numero de control, la fecha y el monto. NUNCA el detalle de lo
-- que consumio el cliente (art. 25: confidencialidad).
create or replace function fiscal.validar_token(p_token text, p_desde text default null)
returns table(
  valido boolean,
  rif_emisor text,
  razon_social text,
  tipo text,
  numero_documento bigint,
  numero_control bigint,
  fecha_emision timestamptz,
  total_bs numeric
)
language plpgsql
security definer
set search_path to 'fiscal', 'public'
as $fn$
declare v_hay boolean := false;
begin
  return query
  select true, e.rif, e.razon_social, d.tipo, d.numero_documento, d.numero_control, d.fecha_emision, d.total_bs
    from fiscal.documentos d
    join fiscal.emisores e on e.id = d.emisor_id
   where d.token = p_token;
  v_hay := found;

  insert into fiscal.consultas_qr (token, encontrado, desde) values (p_token, v_hay, p_desde);

  if not v_hay then
    return query select false, null::text, null::text, null::text, null::bigint, null::bigint, null::timestamptz, null::numeric;
  end if;
end;
$fn$;

commit;
