-- BASE FISCAL DE GUSTITO, bloque 1 (2026-07-28)
-- Primer paso del modulo fiscal: hoy NO existe un solo dato fiscal en el sistema
-- (verificado en la BD viva: ni RIF, ni razon social, ni alicuota, ni IVA), y
-- cualquier camino que tomemos despues (puente a maquina fiscal, emisor por
-- imprenta autorizada u homologacion propia) necesita lo mismo: base imponible,
-- alicuota e impuesto por renglon, con el RIF de quien vende y de quien compra.
-- Decision de Gilberto (2026-07-28): los precios del menu se publican CON el IVA
-- adentro (asi no cambia ni un precio de los que ya estan puestos, el cliente
-- paga igual) y en el piloto todo va al 16% (no venden nada sin preparar).
begin;

-- 1. DATOS FISCALES DEL NEGOCIO -------------------------------------------
alter table public.restaurantes
  add column if not exists rif text,
  add column if not exists razon_social text,
  add column if not exists domicilio_fiscal text,
  add column if not exists iva_alicuota numeric(5,2) not null default 16,
  add column if not exists precios_con_iva boolean not null default true;

comment on column public.restaurantes.iva_alicuota is 'Alicuota general del negocio; cada producto puede traer la suya.';
comment on column public.restaurantes.precios_con_iva is 'true = el precio de la carta ya trae el IVA adentro (lo normal en Venezuela).';

-- Digito verificador del RIF (modulo 11). Comprobado contra RIF reales:
-- J-50856106-6 y V-22606827-5 dan su propio digito; uno inventado rebota.
create or replace function public.fn_rif_valido(p_rif text)
returns boolean
language plpgsql
immutable
as $fn$
declare
  v_limpio text;
  v_nums   text;
  v_pesos  int[] := array[4,3,2,7,6,5,4,3,2];
  v_suma   int;
  v_dv     int;
  i        int;
begin
  if p_rif is null or trim(p_rif) = '' then return false; end if;
  v_limpio := upper(regexp_replace(p_rif, '[^0-9A-Za-z]', '', 'g'));
  if v_limpio !~ '^[VEJPG][0-9]{9}$' then return false; end if;
  v_nums := substring(v_limpio from 2);
  v_suma := (case substring(v_limpio from 1 for 1)
               when 'V' then 1 when 'E' then 2 when 'J' then 3
               when 'P' then 4 when 'G' then 5 end) * v_pesos[1];
  for i in 1..8 loop
    v_suma := v_suma + substring(v_nums from i for 1)::int * v_pesos[i + 1];
  end loop;
  v_dv := 11 - (v_suma % 11);
  if v_dv > 9 then v_dv := 0; end if;
  return v_dv = substring(v_nums from 9 for 1)::int;
end;
$fn$;

-- Deja el RIF siempre igual: J-50856106-6 (asi entra a la factura y a la maquina fiscal).
create or replace function public.fn_rif_normalizado(p_rif text)
returns text
language sql
immutable
as $fn$
  select case
    when p_rif is null or trim(p_rif) = '' then null
    else upper(substring(regexp_replace(p_rif, '[^0-9A-Za-z]', '', 'g') from 1 for 1)) || '-' ||
         substring(regexp_replace(p_rif, '[^0-9A-Za-z]', '', 'g') from 2 for 8) || '-' ||
         substring(regexp_replace(p_rif, '[^0-9A-Za-z]', '', 'g') from 10 for 1)
  end;
$fn$;

alter table public.restaurantes drop constraint if exists restaurantes_rif_check;
alter table public.restaurantes
  add constraint restaurantes_rif_check check (rif is null or public.fn_rif_valido(rif));

-- 2. IVA POR PRODUCTO ------------------------------------------------------
alter table public.productos
  add column if not exists iva_alicuota numeric(5,2);
comment on column public.productos.iva_alicuota is 'Alicuota propia del producto; null = la general del negocio.';

-- 3. DESGLOSE CONGELADO EN LA VENTA ---------------------------------------
-- Se guarda el resultado, no la formula: si mañana cambia el IVA, las ventas
-- viejas no se mueven (la factura tiene que reflejar el dia en que se vendio).
alter table public.pedidos
  add column if not exists base_imponible_usd numeric(10,2),
  add column if not exists iva_usd numeric(10,2),
  add column if not exists iva_alicuota numeric(5,2),
  add column if not exists cliente_rif text,
  add column if not exists cliente_razon_social text,
  add column if not exists cliente_direccion_fiscal text;

alter table public.pedido_items
  add column if not exists iva_alicuota numeric(5,2),
  add column if not exists base_usd numeric(10,2),
  add column if not exists iva_usd numeric(10,2);

alter table public.pedidos drop constraint if exists pedidos_cliente_rif_check;
alter table public.pedidos
  add constraint pedidos_cliente_rif_check check (cliente_rif is null or public.fn_rif_valido(cliente_rif));

drop function if exists public.crear_pedido(uuid, text, text, text, text, jsonb, text, double precision, double precision, text, text, text, uuid, boolean, integer, text, boolean, boolean);

CREATE OR REPLACE FUNCTION public.crear_pedido(p_restaurante_id uuid, p_cliente_nombre text, p_cliente_telefono text, p_tipo_entrega text, p_metodo_pago text, p_items jsonb, p_direccion text DEFAULT NULL::text, p_direccion_lat double precision DEFAULT NULL::double precision, p_direccion_lng double precision DEFAULT NULL::double precision, p_nota text DEFAULT NULL::text, p_mesa text DEFAULT NULL::text, p_cedula text DEFAULT NULL::text, p_comensal_id uuid DEFAULT NULL::uuid, p_para_llevar boolean DEFAULT false, p_personas integer DEFAULT NULL::integer, p_referencia text DEFAULT NULL::text, p_venta_caja boolean DEFAULT false, p_esperar_mesa boolean DEFAULT false)
 RETURNS TABLE(pedido_id uuid, codigo text, cuenta_id uuid)
 LANGUAGE plpgsql
 SECURITY DEFINER
 SET search_path TO 'public'
AS $function$
declare
  v_rest           public.restaurantes%rowtype;
  v_item           jsonb;
  v_prod           public.productos%rowtype;
  v_cant           int;
  v_extra          numeric(10,2) := 0;
  v_subtotal       numeric(10,2) := 0;
  v_costo_delivery numeric(10,2) := 0;
  v_pedido_id      uuid;
  v_codigo         text;
  v_num            int;
  v_cuenta_id      uuid := null;
  v_firma          text := null;
  v_dup_id         uuid;
  v_dup_codigo     text;
  v_dup_cuenta     uuid;
  v_mesero_id      uuid := null;
  v_mesero_nombre  text := null;
  v_comensal_id    uuid := null;
  v_estado         text := 'recibido';
  v_para_llevar    boolean := coalesce(p_para_llevar, false) and p_tipo_entrega = 'mesa';
  v_personas       int := null;
  v_alic_rest      numeric(5,2);
  v_con_iva        boolean;
  v_alic           numeric(5,2);
  v_linea          numeric(10,2);
  v_base_linea     numeric(10,2);
  v_iva_linea      numeric(10,2);
  v_base_total     numeric(10,2) := 0;
  v_iva_total      numeric(10,2) := 0;
  v_cat_dias       int[];
  v_cat_desde      time;
  v_cat_hasta      time;
  v_nom_prod       text;
begin
  select * into v_rest from public.restaurantes where id = p_restaurante_id;
  -- Configuracion fiscal del negocio: alicuota general y si el menu se publica
  -- con el IVA adentro (asi se hace en Venezuela).
  v_alic_rest := coalesce(v_rest.iva_alicuota, 16);
  v_con_iva   := coalesce(v_rest.precios_con_iva, true);
  if not found or not v_rest.activo or not v_rest.publicado then
    raise exception 'Restaurante no disponible';
  end if;
  if not v_rest.abierto then
    raise exception 'El restaurante esta cerrado en este momento';
  end if;
  if p_tipo_entrega not in ('delivery','pickup','mesa','reserva') then
    raise exception 'Tipo de entrega invalido';
  end if;
  if p_metodo_pago not in ('pago_movil','efectivo','transferencia','zelle','punto_venta') then
    raise exception 'Metodo de pago invalido';
  end if;
  if jsonb_array_length(coalesce(p_items, '[]'::jsonb)) = 0 then
    raise exception 'El carrito esta vacio';
  end if;

  if auth.uid() is not null then
    select pf.id, coalesce(nullif(trim(pf.nombre), ''), 'Mesero')
      into v_mesero_id, v_mesero_nombre
      from public.perfiles pf
     where pf.id = auth.uid()
       and pf.restaurante_id = p_restaurante_id
       and pf.rol in ('dueno','staff','encargado');
  end if;

  -- Horario de la categoria (ej: sopas solo los domingos, arepas desde las
  -- 5pm). Bloquea SOLO la venta en linea: el mesero y la caja venden igual.
  -- Va ANTES del anti-duplicado y de crear el pedido, para no depender del
  -- orden de insercion ni dejar filas a medias.
  if v_mesero_id is null then
    for v_item in select * from jsonb_array_elements(p_items)
    loop
      select c.dias_semana, c.hora_desde, c.hora_hasta, pr.nombre
        into v_cat_dias, v_cat_desde, v_cat_hasta, v_nom_prod
        from public.productos pr
        join public.categorias c on c.id = pr.categoria_id
       where pr.id = (v_item->>'producto_id')::uuid
         and pr.restaurante_id = p_restaurante_id;
      if found and not public.fn_categoria_en_horario(v_cat_dias, v_cat_desde, v_cat_hasta) then
        raise exception '% no se esta vendiendo en este momento', v_nom_prod;
      end if;
    end loop;
  end if;

  -- VENTA DE CAJA: el personal cobra y entrega en el momento. Nace ENTREGADO
  -- (no pasa por pedidos activos) con el pago confirmado. Solo personal, solo pickup.
  if p_venta_caja then
    if v_mesero_id is null then
      raise exception 'Solo el personal del negocio registra ventas de caja';
    end if;
    if p_tipo_entrega <> 'pickup' then
      raise exception 'La venta de caja va como retiro en el local (pickup)';
    end if;
    v_estado := 'entregado';
  end if;

  if p_tipo_entrega = 'delivery' then
    v_costo_delivery := coalesce(v_rest.costo_delivery_usd, 0);
    if coalesce(length(trim(p_direccion)), 0) < 4 then
      raise exception 'Para delivery necesitas indicar la direccion';
    end if;
  end if;

  if p_tipo_entrega = 'reserva' then
    v_personas := greatest(1, coalesce(p_personas, 1));
  end if;

  if p_tipo_entrega in ('delivery','pickup','reserva') then
    v_firma := md5(coalesce(lower(trim(p_cliente_telefono)), '') || '|' || coalesce(p_items::text, ''));
    select p.id, p.codigo, p.cuenta_id into v_dup_id, v_dup_codigo, v_dup_cuenta
      from public.pedidos p
     where p.restaurante_id = p_restaurante_id
       and p.firma = v_firma
       and p.recibido_en > now() - interval '3 minutes'
     order by p.recibido_en desc
     limit 1;
    if found then
      return query select v_dup_id, v_dup_codigo, v_dup_cuenta;
      return;
    end if;
  end if;

  if p_tipo_entrega = 'mesa' then
    -- ORDEN DE MESA: por defecto el pedido va DERECHO a la cocina (sale la
    -- comanda sola, sin que nadie toque nada). Solo si el comensal dice que
    -- faltan personas por pedir (p_esperar_mesa) queda de borrador 'recibido'
    -- y la mesa completa se manda con enviar_orden_mesa (UNA sola comanda);
    -- si nadie la manda, fn_soltar_ordenes_mesa_vencidas la suelta a los 5 min.
    -- El del personal (mesero/encargado/dueno) siempre va directo a cocina.
    if v_mesero_id is not null or not coalesce(p_esperar_mesa, false) then
      v_estado := 'confirmado';
    end if;
    if coalesce(nullif(p_mesa, ''), '') = '' then
      raise exception 'Falta el numero de mesa';
    end if;
    insert into public.cuentas_mesa (restaurante_id, mesa, mesero_id, mesero_nombre)
      values (p_restaurante_id, p_mesa, v_mesero_id, v_mesero_nombre)
      on conflict (restaurante_id, mesa) where (estado <> 'cerrada') do nothing;
    select cm.id into v_cuenta_id from public.cuentas_mesa cm
      where cm.restaurante_id = p_restaurante_id and cm.mesa = p_mesa and cm.estado <> 'cerrada'
      limit 1;
    if v_mesero_id is not null and v_cuenta_id is not null then
      update public.cuentas_mesa
         set mesero_id = v_mesero_id, mesero_nombre = v_mesero_nombre
       where id = v_cuenta_id and mesero_id is null;
    end if;
    if p_comensal_id is not null then
      perform 1 from public.comensales_mesa cm where cm.id = p_comensal_id and cm.cuenta_id = v_cuenta_id;
      if not found then raise exception 'La persona no pertenece a esta mesa'; end if;
      v_comensal_id := p_comensal_id;
    end if;
  end if;

  update public.restaurantes set contador_pedidos = contador_pedidos + 1
   where id = p_restaurante_id
   returning contador_pedidos into v_num;
  v_codigo := coalesce(nullif(v_rest.prefijo, ''), 'GX') || '-' || lpad(v_num::text, 4, '0');

  insert into public.pedidos (
    restaurante_id, codigo, cliente_nombre, cliente_telefono, cliente_cedula, tipo_entrega, mesa,
    direccion, direccion_latitud, direccion_longitud, estado, confirmado_en, entregado_en, origen,
    subtotal_usd, costo_delivery_usd, total_usd, tasa_bs, metodo_pago, nota, cuenta_id, firma,
    mesero_id, mesero_nombre, comensal_id, para_llevar, personas
  ) values (
    p_restaurante_id, v_codigo, p_cliente_nombre, p_cliente_telefono, nullif(p_cedula, ''), p_tipo_entrega, nullif(p_mesa, ''),
    p_direccion, p_direccion_lat, p_direccion_lng, v_estado,
    case when v_estado in ('confirmado','entregado') then now() else null end,
    case when v_estado = 'entregado' then now() else null end,
    case when p_venta_caja then 'caja' else null end,
    0, v_costo_delivery, 0, v_rest.tasa_bs, p_metodo_pago, p_nota, v_cuenta_id, v_firma,
    v_mesero_id, v_mesero_nombre, v_comensal_id, v_para_llevar, v_personas
  ) returning id into v_pedido_id;

  for v_item in select * from jsonb_array_elements(p_items)
  loop
    v_cant := greatest(1, coalesce((v_item->>'cantidad')::int, 1));
    select * into v_prod from public.productos
      where id = (v_item->>'producto_id')::uuid
        and restaurante_id = p_restaurante_id;
    if not found then
      raise exception 'Un producto no pertenece a este restaurante';
    end if;
    if not v_prod.disponible then
      raise exception 'Producto agotado: %', v_prod.nombre;
    end if;
    select coalesce(sum((op->>'precio_extra')::numeric), 0) into v_extra
      from jsonb_array_elements(coalesce(v_prod.opciones, '[]'::jsonb)) as grupo,
           jsonb_array_elements(coalesce(grupo->'opciones', '[]'::jsonb)) as op
     where jsonb_exists(coalesce(v_item->'opciones', '[]'::jsonb), op->>'nombre');
    -- IMPUESTO POR RENGLON, congelado con la alicuota del dia de la venta.
    -- Si el precio se publica con IVA adentro, la base sale hacia atras y el
    -- cliente paga exactamente el precio de la carta.
    v_alic       := coalesce(v_prod.iva_alicuota, v_alic_rest);
    v_linea      := round((v_prod.precio_usd + v_extra) * v_cant, 2);
    if v_con_iva then
      v_base_linea := round(v_linea / (1 + v_alic / 100), 2);
      v_iva_linea  := v_linea - v_base_linea;
    else
      v_base_linea := v_linea;
      v_iva_linea  := round(v_linea * v_alic / 100, 2);
    end if;
    insert into public.pedido_items (pedido_id, producto_id, nombre, precio_usd, cantidad, nota, iva_alicuota, base_usd, iva_usd)
      values (v_pedido_id, v_prod.id, v_prod.nombre, v_prod.precio_usd + v_extra, v_cant, nullif(v_item->>'nota', ''), v_alic, v_base_linea, v_iva_linea);
    v_base_total := v_base_total + v_base_linea;
    v_iva_total  := v_iva_total + v_iva_linea;
    v_subtotal   := v_subtotal + v_base_linea + v_iva_linea;
  end loop;

  -- El servicio de delivery tambien esta gravado, a la alicuota general.
  if v_costo_delivery > 0 then
    if v_con_iva then
      v_base_total := v_base_total + round(v_costo_delivery / (1 + v_alic_rest / 100), 2);
      v_iva_total  := v_iva_total + (v_costo_delivery - round(v_costo_delivery / (1 + v_alic_rest / 100), 2));
    else
      v_base_total := v_base_total + v_costo_delivery;
      v_iva_total  := v_iva_total + round(v_costo_delivery * v_alic_rest / 100, 2);
    end if;
  end if;

  update public.pedidos
     set subtotal_usd       = v_subtotal,
         total_usd          = v_subtotal + v_costo_delivery,
         base_imponible_usd = v_base_total,
         iva_usd            = v_iva_total,
         iva_alicuota       = v_alic_rest
   where id = v_pedido_id;

  -- Reserva se paga como cualquier mesa: al final, junto con lo que se agregue
  -- despues de sentarse. Sin esto, el pre-pago individual (SeccionPago) se
  -- solapa con el cobro de la cuenta de mesa y el cliente paga dos veces.
  -- Venta de caja: el pago nace CONFIRMADO (la caja cobro y verifico ahi mismo).
  if p_tipo_entrega not in ('mesa','reserva') then
    insert into public.pagos (pedido_id, metodo, monto_usd, estado, referencia)
      values (v_pedido_id, p_metodo_pago, v_subtotal + v_costo_delivery,
              case when p_venta_caja then 'confirmado' else 'pendiente' end,
              nullif(trim(p_referencia), ''));
  end if;

  return query select v_pedido_id, v_codigo, v_cuenta_id;
end;
$function$
;

grant execute on function public.crear_pedido(uuid, text, text, text, text, jsonb, text, double precision, double precision, text, text, text, uuid, boolean, integer, text, boolean, boolean) to anon, authenticated, service_role;

-- 4. DATOS DE FACTURACION DEL CLIENTE -------------------------------------
-- Aparte de crear_pedido a proposito (ya lleva 18 parametros): el cliente que
-- quiere factura los manda despues, con el id de su pedido como llave, igual
-- que registrar_comprobante.
create or replace function public.datos_factura_pedido(
  p_pedido_id uuid,
  p_rif text,
  p_razon_social text,
  p_direccion text default null
)
returns void
language plpgsql
security definer
set search_path to 'public'
as $fn$
begin
  if not public.fn_rif_valido(p_rif) then
    raise exception 'Ese RIF no es valido, revisalo';
  end if;
  if coalesce(nullif(trim(p_razon_social), ''), '') = '' then
    raise exception 'Falta el nombre o razon social para la factura';
  end if;
  update public.pedidos
     set cliente_rif              = public.fn_rif_normalizado(p_rif),
         cliente_razon_social     = trim(p_razon_social),
         cliente_direccion_fiscal = nullif(trim(coalesce(p_direccion, '')), '')
   where id = p_pedido_id
     and estado <> 'cancelado'
     and recibido_en > now() - interval '24 hours';
  if not found then
    raise exception 'Ese pedido ya no admite datos de facturacion';
  end if;
end;
$fn$;

grant execute on function public.datos_factura_pedido(uuid, text, text, text) to anon, authenticated, service_role;

commit;
