-- Migración segura: Módulo Recibos de Carros
-- NO EJECUTAR sin autorización explícita.
--
-- Principios de seguridad:
--   - Las tablas son accedidas ÚNICAMENTE desde API routes server-side
--     usando service_role (lib/supabase-admin.ts).
--   - NO se otorgan permisos a anon ni authenticated.
--   - RLS está habilitado y bloquea cualquier acceso directo desde el
--     navegador con la anon key.
--   - No se toca public.pagos ni el módulo de Recibos de cajas.

BEGIN;

-- The client table is managed by another ERP migration. Fail clearly if its
-- primary-key type is not bigint, matching this module's foreign-key columns.
DO $$
DECLARE
  cliente_id_type regtype;
BEGIN
  SELECT a.atttypid::regtype
    INTO cliente_id_type
    FROM pg_catalog.pg_attribute AS a
   WHERE a.attrelid = pg_catalog.to_regclass('public.hce_clientes')
     AND a.attname = 'id'
     AND a.attnum > 0
     AND NOT a.attisdropped;

  IF cliente_id_type IS DISTINCT FROM 'bigint'::regtype THEN
    RAISE EXCEPTION
      'public.hce_clientes.id debe ser bigint; tipo encontrado: %',
      COALESCE(cliente_id_type::text, 'columna ausente');
  END IF;
END $$;

-- ==========================================
-- 1. TABLA DE RECIBOS DE CARROS
-- ==========================================
CREATE TABLE IF NOT EXISTS public.hce_recibos_carros (
  id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  numero text NOT NULL UNIQUE,
  fecha date NOT NULL DEFAULT CURRENT_DATE,

  remitente_id bigint REFERENCES public.hce_clientes(id) ON DELETE SET NULL,
  destinatario_id bigint REFERENCES public.hce_clientes(id) ON DELETE SET NULL,

  remitente_nombre text,
  remitente_telefono text,
  destinatario_nombre text,
  destinatario_telefono text,

  tipo_vehiculo text,
  marca text NOT NULL,
  modelo text NOT NULL,
  anio text,
  color text,
  vin text,
  puerto_destino text,
  observaciones text,

  total numeric(12,2) NOT NULL DEFAULT 0 CHECK (total >= 0),
  total_abonado numeric(12,2) NOT NULL DEFAULT 0 CHECK (total_abonado >= 0),
  saldo_pendiente numeric(12,2) NOT NULL DEFAULT 0 CHECK (saldo_pendiente >= 0),
  estado_pago text NOT NULL DEFAULT 'Pendiente'
    CHECK (estado_pago IN ('Pendiente', 'Abono parcial', 'Pagado')),
  CONSTRAINT hce_recibos_carros_abonos_no_exceden_total
    CHECK (total_abonado <= total),
  CONSTRAINT hce_recibos_carros_saldo_consistente
    CHECK (saldo_pendiente = total - total_abonado),
  CONSTRAINT hce_recibos_carros_estado_consistente CHECK (
    estado_pago = CASE
      WHEN total > 0 AND saldo_pendiente = 0 THEN 'Pagado'
      WHEN total_abonado > 0 THEN 'Abono parcial'
      ELSE 'Pendiente'
    END
  ),

  created_at timestamptz DEFAULT now(),
  updated_at timestamptz DEFAULT now()
);

DO $$
BEGIN
  IF NOT EXISTS (
    SELECT 1 FROM pg_catalog.pg_constraint
     WHERE conrelid = 'public.hce_recibos_carros'::regclass
       AND conname = 'hce_recibos_carros_abonos_no_exceden_total'
  ) THEN
    ALTER TABLE public.hce_recibos_carros
      ADD CONSTRAINT hce_recibos_carros_abonos_no_exceden_total
      CHECK (total_abonado <= total);
  END IF;

  IF NOT EXISTS (
    SELECT 1 FROM pg_catalog.pg_constraint
     WHERE conrelid = 'public.hce_recibos_carros'::regclass
       AND conname = 'hce_recibos_carros_saldo_consistente'
  ) THEN
    ALTER TABLE public.hce_recibos_carros
      ADD CONSTRAINT hce_recibos_carros_saldo_consistente
      CHECK (saldo_pendiente = total - total_abonado);
  END IF;

  IF NOT EXISTS (
    SELECT 1 FROM pg_catalog.pg_constraint
     WHERE conrelid = 'public.hce_recibos_carros'::regclass
       AND conname = 'hce_recibos_carros_estado_consistente'
  ) THEN
    ALTER TABLE public.hce_recibos_carros
      ADD CONSTRAINT hce_recibos_carros_estado_consistente
      CHECK (
        estado_pago = CASE
          WHEN total > 0 AND saldo_pendiente = 0 THEN 'Pagado'
          WHEN total_abonado > 0 THEN 'Abono parcial'
          ELSE 'Pendiente'
        END
      );
  END IF;
END $$;

COMMENT ON TABLE public.hce_recibos_carros IS 'Recibos de vehículos de Honduras Cargo Express';

-- ==========================================
-- 2. TABLA DE CONCEPTOS/CARGOS
-- ==========================================
CREATE TABLE IF NOT EXISTS public.hce_recibos_carros_conceptos (
  id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  recibo_id bigint NOT NULL REFERENCES public.hce_recibos_carros(id) ON DELETE CASCADE,
  concepto text NOT NULL,
  importe numeric(12,2) NOT NULL DEFAULT 0 CHECK (importe >= 0),
  orden integer NOT NULL DEFAULT 0
);

-- ==========================================
-- 3. TABLA DE PAGOS/ABONOS
-- ==========================================
CREATE TABLE IF NOT EXISTS public.hce_recibos_carros_pagos (
  id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  recibo_id bigint NOT NULL REFERENCES public.hce_recibos_carros(id) ON DELETE CASCADE,
  fecha date NOT NULL DEFAULT CURRENT_DATE,
  importe numeric(12,2) NOT NULL DEFAULT 0 CHECK (importe > 0),
  metodo_pago text,
  referencia text,
  observaciones text,
  created_at timestamptz DEFAULT now()
);

-- ==========================================
-- 4. ÍNDICES
-- ==========================================
CREATE INDEX IF NOT EXISTS idx_hce_recibos_carros_numero ON public.hce_recibos_carros(numero);
CREATE INDEX IF NOT EXISTS idx_hce_recibos_carros_remitente ON public.hce_recibos_carros(remitente_id);
CREATE INDEX IF NOT EXISTS idx_hce_recibos_carros_destinatario ON public.hce_recibos_carros(destinatario_id);
CREATE INDEX IF NOT EXISTS idx_hce_recibos_carros_estado ON public.hce_recibos_carros(estado_pago);
CREATE INDEX IF NOT EXISTS idx_hce_recibos_carros_fecha ON public.hce_recibos_carros(fecha);

CREATE INDEX IF NOT EXISTS idx_hce_recibos_carros_conceptos_recibo ON public.hce_recibos_carros_conceptos(recibo_id);
CREATE INDEX IF NOT EXISTS idx_hce_recibos_carros_pagos_recibo ON public.hce_recibos_carros_pagos(recibo_id);

-- ==========================================
-- 5. RLS: BLOQUEAR ACCESO DIRECTO DESDE NAVEGADOR
-- ==========================================
ALTER TABLE public.hce_recibos_carros ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.hce_recibos_carros_conceptos ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.hce_recibos_carros_pagos ENABLE ROW LEVEL SECURITY;

-- Remove any pre-existing policy that could grant direct browser-role access,
-- including permissive policies with different names. Keep policies for other
-- roles, including the service_role policies below.
DO $$
DECLARE
  policy_row record;
BEGIN
  FOR policy_row IN
    SELECT n.nspname, c.relname, p.polname
      FROM pg_catalog.pg_policy AS p
      JOIN pg_catalog.pg_class AS c ON c.oid = p.polrelid
      JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace
     WHERE n.nspname = 'public'
       AND c.relname = ANY (ARRAY[
         'hce_recibos_carros',
         'hce_recibos_carros_conceptos',
         'hce_recibos_carros_pagos'
       ])
       AND (
         0::oid = ANY (p.polroles)
         OR EXISTS (
           SELECT 1
             FROM pg_catalog.unnest(p.polroles) AS roles(role_oid)
            WHERE pg_catalog.pg_get_userbyid(role_oid) IN ('anon', 'authenticated')
         )
       )
  LOOP
    EXECUTE pg_catalog.format(
      'DROP POLICY %I ON %I.%I',
      policy_row.polname,
      policy_row.nspname,
      policy_row.relname
    );
  END LOOP;
END $$;

-- Políticas para service_role. Aunque service_role bypass RLS por defecto,
-- estas políticas documentan explícitamente el acceso permitido.
DROP POLICY IF EXISTS "service_role_full_access_hce_recibos_carros" ON public.hce_recibos_carros;
CREATE POLICY "service_role_full_access_hce_recibos_carros"
  ON public.hce_recibos_carros
  FOR ALL
  TO service_role
  USING (true)
  WITH CHECK (true);

DROP POLICY IF EXISTS "service_role_full_access_hce_recibos_carros_conceptos" ON public.hce_recibos_carros_conceptos;
CREATE POLICY "service_role_full_access_hce_recibos_carros_conceptos"
  ON public.hce_recibos_carros_conceptos
  FOR ALL
  TO service_role
  USING (true)
  WITH CHECK (true);

DROP POLICY IF EXISTS "service_role_full_access_hce_recibos_carros_pagos" ON public.hce_recibos_carros_pagos;
CREATE POLICY "service_role_full_access_hce_recibos_carros_pagos"
  ON public.hce_recibos_carros_pagos
  FOR ALL
  TO service_role
  USING (true)
  WITH CHECK (true);

-- ==========================================
-- 6. PERMISOS: SOLO service_role
-- ==========================================
-- Revocar explícitamente a roles públicos por seguridad.
REVOKE ALL ON public.hce_recibos_carros FROM PUBLIC, anon, authenticated;
REVOKE ALL ON public.hce_recibos_carros_conceptos FROM PUBLIC, anon, authenticated;
REVOKE ALL ON public.hce_recibos_carros_pagos FROM PUBLIC, anon, authenticated;

-- Solo service_role puede operar estas tablas.
GRANT SELECT, INSERT, UPDATE, DELETE ON public.hce_recibos_carros TO service_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON public.hce_recibos_carros_conceptos TO service_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON public.hce_recibos_carros_pagos TO service_role;

-- ==========================================
-- 7. SECUENCIAS
-- ==========================================
-- Discover the actual identity-sequence names from PostgreSQL's catalogs so
-- grants do not depend on generated naming conventions.
DO $$
DECLARE
  seq_record record;
BEGIN
  FOR seq_record IN
    SELECT DISTINCT sequence_namespace.nspname, sequence_class.relname
      FROM pg_catalog.pg_class AS table_class
      JOIN pg_catalog.pg_namespace AS table_namespace
        ON table_namespace.oid = table_class.relnamespace
      JOIN pg_catalog.pg_attribute AS table_attribute
        ON table_attribute.attrelid = table_class.oid
       AND table_attribute.attnum > 0
       AND NOT table_attribute.attisdropped
      JOIN pg_catalog.pg_depend AS dependency
        ON dependency.refobjid = table_class.oid
       AND dependency.refobjsubid = table_attribute.attnum
       AND dependency.classid = 'pg_catalog.pg_class'::regclass
       AND dependency.refclassid = 'pg_catalog.pg_class'::regclass
       AND dependency.deptype = 'i'
      JOIN pg_catalog.pg_class AS sequence_class
        ON sequence_class.oid = dependency.objid
       AND sequence_class.relkind = 'S'
      JOIN pg_catalog.pg_namespace AS sequence_namespace
        ON sequence_namespace.oid = sequence_class.relnamespace
     WHERE table_namespace.nspname = 'public'
       AND table_class.relname = ANY (ARRAY[
         'hce_recibos_carros',
         'hce_recibos_carros_conceptos',
         'hce_recibos_carros_pagos'
       ])
  LOOP
    EXECUTE pg_catalog.format(
      'REVOKE ALL ON SEQUENCE %I.%I FROM PUBLIC, anon, authenticated',
      seq_record.nspname,
      seq_record.relname
    );
    EXECUTE pg_catalog.format(
      'GRANT USAGE ON SEQUENCE %I.%I TO service_role',
      seq_record.nspname,
      seq_record.relname
    );
  END LOOP;
END $$;

-- These invoker-rights routines make each receipt/concept/payment update a
-- single transaction. API routes call them only after validating the signed
-- ERP session; service_role is the only role granted EXECUTE.
CREATE OR REPLACE FUNCTION public.hce_crear_recibo_carro(
  p_recibo jsonb,
  p_conceptos jsonb
)
RETURNS jsonb
LANGUAGE plpgsql
SET search_path = pg_catalog
AS $function$
DECLARE
  concepto_item jsonb;
  siguiente_numero bigint;
  numero_recibo text;
  recibo_creado public.hce_recibos_carros%ROWTYPE;
  total_recibo numeric(12, 2);
  pago_inicial jsonb;
  tipo_pago_inicial text;
  importe_pago_inicial numeric(12, 2);
  total_abonado_inicial numeric(12, 2);
BEGIN
  IF p_recibo IS NULL OR pg_catalog.jsonb_typeof(p_recibo) <> 'object' THEN
    RAISE EXCEPTION 'Los datos del recibo no son válidos'
      USING ERRCODE = '22023';
  END IF;

  IF p_conceptos IS NULL
     OR pg_catalog.jsonb_typeof(p_conceptos) <> 'array'
     OR pg_catalog.jsonb_array_length(p_conceptos) = 0 THEN
    RAISE EXCEPTION 'Agrega al menos un concepto de cobro'
      USING ERRCODE = '22023';
  END IF;

  FOR concepto_item IN
    SELECT item.value FROM pg_catalog.jsonb_array_elements(p_conceptos) AS item(value)
  LOOP
    IF pg_catalog.jsonb_typeof(concepto_item) <> 'object'
       OR NULLIF(pg_catalog.btrim(concepto_item ->> 'concepto'), '') IS NULL
       OR pg_catalog.jsonb_typeof(concepto_item -> 'importe') <> 'number' THEN
      RAISE EXCEPTION 'Los conceptos de cobro no son válidos'
        USING ERRCODE = '22023';
    END IF;
    IF (concepto_item ->> 'importe')::numeric < 0 THEN
      RAISE EXCEPTION 'El importe de un concepto no puede ser negativo'
        USING ERRCODE = '22023';
    END IF;
  END LOOP;

  SELECT COALESCE(pg_catalog.sum(pg_catalog.round((item.value ->> 'importe')::numeric, 2)), 0)
    INTO total_recibo
    FROM pg_catalog.jsonb_array_elements(p_conceptos) AS item(value);

  pago_inicial := COALESCE(p_recibo -> 'pago_inicial', '{"tipo":"sin_pago"}'::jsonb);
  IF pg_catalog.jsonb_typeof(pago_inicial) <> 'object'
     OR pg_catalog.jsonb_typeof(pago_inicial -> 'tipo') <> 'string' THEN
    RAISE EXCEPTION 'La forma de pago inicial no es válida'
      USING ERRCODE = '22023';
  END IF;

  tipo_pago_inicial := pago_inicial ->> 'tipo';
  IF tipo_pago_inicial NOT IN ('sin_pago', 'abono_inicial', 'pago_completo') THEN
    RAISE EXCEPTION 'La forma de pago inicial no es válida'
      USING ERRCODE = '22023';
  END IF;

  total_abonado_inicial := 0;
  IF tipo_pago_inicial = 'abono_inicial' THEN
    IF pg_catalog.jsonb_typeof(pago_inicial -> 'importe') <> 'number' THEN
      RAISE EXCEPTION 'El importe del abono inicial no es válido'
        USING ERRCODE = '22023';
    END IF;
    importe_pago_inicial := pg_catalog.round((pago_inicial ->> 'importe')::numeric, 2);
    IF importe_pago_inicial <= 0 OR importe_pago_inicial > total_recibo THEN
      RAISE EXCEPTION 'El abono inicial debe ser mayor que cero y no superar el total'
        USING ERRCODE = '22023';
    END IF;
    total_abonado_inicial := importe_pago_inicial;
  ELSIF tipo_pago_inicial = 'pago_completo' AND total_recibo > 0 THEN
    total_abonado_inicial := total_recibo;
  END IF;

  PERFORM pg_catalog.pg_advisory_xact_lock(72631, 1);

  SELECT COALESCE(
           pg_catalog.max(pg_catalog.substring(numero, 5)::bigint),
           0
         ) + 1
    INTO siguiente_numero
    FROM public.hce_recibos_carros
   WHERE numero ~ '^RCC-[0-9]+$';
  numero_recibo := 'RCC-' || pg_catalog.lpad(siguiente_numero::text, 6, '0');

  INSERT INTO public.hce_recibos_carros (
    numero,
    fecha,
    remitente_id,
    destinatario_id,
    remitente_nombre,
    remitente_telefono,
    destinatario_nombre,
    destinatario_telefono,
    tipo_vehiculo,
    marca,
    modelo,
    anio,
    color,
    vin,
    puerto_destino,
    observaciones,
    total,
    total_abonado,
    saldo_pendiente,
    estado_pago
  )
  VALUES (
    numero_recibo,
    COALESCE(NULLIF(p_recibo ->> 'fecha', '')::date, CURRENT_DATE),
    NULLIF(p_recibo ->> 'remitente_id', '')::bigint,
    NULLIF(p_recibo ->> 'destinatario_id', '')::bigint,
    NULLIF(pg_catalog.btrim(p_recibo ->> 'remitente_nombre'), ''),
    NULLIF(p_recibo ->> 'remitente_telefono', ''),
    NULLIF(pg_catalog.btrim(p_recibo ->> 'destinatario_nombre'), ''),
    NULLIF(p_recibo ->> 'destinatario_telefono', ''),
    NULLIF(pg_catalog.btrim(p_recibo ->> 'tipo_vehiculo'), ''),
    NULLIF(pg_catalog.btrim(p_recibo ->> 'marca'), ''),
    NULLIF(pg_catalog.btrim(p_recibo ->> 'modelo'), ''),
    NULLIF(pg_catalog.btrim(p_recibo ->> 'anio'), ''),
    NULLIF(pg_catalog.btrim(p_recibo ->> 'color'), ''),
    NULLIF(pg_catalog.btrim(p_recibo ->> 'vin'), ''),
    NULLIF(pg_catalog.btrim(p_recibo ->> 'puerto_destino'), ''),
    NULLIF(pg_catalog.btrim(p_recibo ->> 'observaciones'), ''),
    total_recibo,
    0,
    total_recibo,
    'Pendiente'
  )
  RETURNING * INTO recibo_creado;

  INSERT INTO public.hce_recibos_carros_conceptos (
    recibo_id,
    concepto,
    importe,
    orden
  )
  SELECT
    recibo_creado.id,
    pg_catalog.btrim(item.value ->> 'concepto'),
    pg_catalog.round((item.value ->> 'importe')::numeric, 2),
    (item.ordinality - 1)::integer
    FROM pg_catalog.jsonb_array_elements(p_conceptos)
         WITH ORDINALITY AS item(value, ordinality);

  IF total_abonado_inicial > 0 THEN
    INSERT INTO public.hce_recibos_carros_pagos (
      recibo_id,
      fecha,
      importe
    )
    VALUES (
      recibo_creado.id,
      recibo_creado.fecha,
      total_abonado_inicial
    );

    UPDATE public.hce_recibos_carros
       SET total_abonado = total_abonado_inicial,
           saldo_pendiente = total_recibo - total_abonado_inicial,
           estado_pago = CASE
             WHEN total_recibo > 0 AND total_recibo - total_abonado_inicial = 0
               THEN 'Pagado'
             ELSE 'Abono parcial'
           END
     WHERE id = recibo_creado.id
     RETURNING * INTO recibo_creado;
  END IF;

  RETURN pg_catalog.to_jsonb(recibo_creado);
END;
$function$;

CREATE OR REPLACE FUNCTION public.hce_actualizar_recibo_carro(
  p_recibo_id bigint,
  p_recibo jsonb,
  p_conceptos jsonb
)
RETURNS jsonb
LANGUAGE plpgsql
SET search_path = pg_catalog
AS $function$
DECLARE
  concepto_item jsonb;
  recibo_actual public.hce_recibos_carros%ROWTYPE;
  recibo_actualizado public.hce_recibos_carros%ROWTYPE;
  total_recibo numeric(12, 2);
  total_abonado_actual numeric(12, 2);
  saldo_pendiente_actual numeric(12, 2);
  estado_pago_actual text;
BEGIN
  IF p_recibo IS NULL OR pg_catalog.jsonb_typeof(p_recibo) <> 'object' THEN
    RAISE EXCEPTION 'Los datos del recibo no son válidos'
      USING ERRCODE = '22023';
  END IF;

  IF p_conceptos IS NULL
     OR pg_catalog.jsonb_typeof(p_conceptos) <> 'array'
     OR pg_catalog.jsonb_array_length(p_conceptos) = 0 THEN
    RAISE EXCEPTION 'Agrega al menos un concepto de cobro'
      USING ERRCODE = '22023';
  END IF;

  FOR concepto_item IN
    SELECT item.value FROM pg_catalog.jsonb_array_elements(p_conceptos) AS item(value)
  LOOP
    IF pg_catalog.jsonb_typeof(concepto_item) <> 'object'
       OR NULLIF(pg_catalog.btrim(concepto_item ->> 'concepto'), '') IS NULL
       OR pg_catalog.jsonb_typeof(concepto_item -> 'importe') <> 'number' THEN
      RAISE EXCEPTION 'Los conceptos de cobro no son válidos'
        USING ERRCODE = '22023';
    END IF;
    IF (concepto_item ->> 'importe')::numeric < 0 THEN
      RAISE EXCEPTION 'El importe de un concepto no puede ser negativo'
        USING ERRCODE = '22023';
    END IF;
  END LOOP;

  SELECT *
    INTO recibo_actual
    FROM public.hce_recibos_carros
   WHERE id = p_recibo_id
   FOR UPDATE;
  IF NOT FOUND THEN
    RAISE EXCEPTION 'Recibo no encontrado'
      USING ERRCODE = 'P0002';
  END IF;

  SELECT COALESCE(pg_catalog.sum(pg_catalog.round((item.value ->> 'importe')::numeric, 2)), 0)
    INTO total_recibo
    FROM pg_catalog.jsonb_array_elements(p_conceptos) AS item(value);
  SELECT COALESCE(pg_catalog.sum(importe), 0)
    INTO total_abonado_actual
    FROM public.hce_recibos_carros_pagos
   WHERE recibo_id = p_recibo_id;

  IF total_recibo < total_abonado_actual THEN
    RAISE EXCEPTION 'El total no puede ser menor que los abonos registrados'
      USING ERRCODE = '23514';
  END IF;

  saldo_pendiente_actual := total_recibo - total_abonado_actual;
  estado_pago_actual := CASE
    WHEN total_recibo > 0 AND saldo_pendiente_actual = 0 THEN 'Pagado'
    WHEN total_abonado_actual > 0 THEN 'Abono parcial'
    ELSE 'Pendiente'
  END;

  DELETE FROM public.hce_recibos_carros_conceptos
   WHERE recibo_id = p_recibo_id;

  INSERT INTO public.hce_recibos_carros_conceptos (
    recibo_id,
    concepto,
    importe,
    orden
  )
  SELECT
    p_recibo_id,
    pg_catalog.btrim(item.value ->> 'concepto'),
    pg_catalog.round((item.value ->> 'importe')::numeric, 2),
    (item.ordinality - 1)::integer
    FROM pg_catalog.jsonb_array_elements(p_conceptos)
         WITH ORDINALITY AS item(value, ordinality);

  UPDATE public.hce_recibos_carros
     SET fecha = COALESCE(NULLIF(p_recibo ->> 'fecha', '')::date, recibo_actual.fecha),
         remitente_id = NULLIF(p_recibo ->> 'remitente_id', '')::bigint,
         destinatario_id = NULLIF(p_recibo ->> 'destinatario_id', '')::bigint,
         remitente_nombre = NULLIF(pg_catalog.btrim(p_recibo ->> 'remitente_nombre'), ''),
         remitente_telefono = NULLIF(p_recibo ->> 'remitente_telefono', ''),
         destinatario_nombre = NULLIF(pg_catalog.btrim(p_recibo ->> 'destinatario_nombre'), ''),
         destinatario_telefono = NULLIF(p_recibo ->> 'destinatario_telefono', ''),
         tipo_vehiculo = NULLIF(pg_catalog.btrim(p_recibo ->> 'tipo_vehiculo'), ''),
         marca = NULLIF(pg_catalog.btrim(p_recibo ->> 'marca'), ''),
         modelo = NULLIF(pg_catalog.btrim(p_recibo ->> 'modelo'), ''),
         anio = NULLIF(pg_catalog.btrim(p_recibo ->> 'anio'), ''),
         color = NULLIF(pg_catalog.btrim(p_recibo ->> 'color'), ''),
         vin = NULLIF(pg_catalog.btrim(p_recibo ->> 'vin'), ''),
         puerto_destino = NULLIF(pg_catalog.btrim(p_recibo ->> 'puerto_destino'), ''),
         observaciones = NULLIF(pg_catalog.btrim(p_recibo ->> 'observaciones'), ''),
         total = total_recibo,
         total_abonado = total_abonado_actual,
         saldo_pendiente = saldo_pendiente_actual,
         estado_pago = estado_pago_actual,
         updated_at = CURRENT_TIMESTAMP
   WHERE id = p_recibo_id
  RETURNING * INTO recibo_actualizado;

  RETURN pg_catalog.to_jsonb(recibo_actualizado);
END;
$function$;

CREATE OR REPLACE FUNCTION public.hce_registrar_pago_carro(
  p_recibo_id bigint,
  p_fecha date,
  p_importe numeric,
  p_metodo_pago text,
  p_referencia text,
  p_observaciones text
)
RETURNS jsonb
LANGUAGE plpgsql
SET search_path = pg_catalog
AS $function$
DECLARE
  recibo_actual public.hce_recibos_carros%ROWTYPE;
  pago_creado public.hce_recibos_carros_pagos%ROWTYPE;
  total_recibo numeric(12, 2);
  total_abonado_actual numeric(12, 2);
  saldo_pendiente_actual numeric(12, 2);
  estado_pago_actual text;
  importe_abono numeric(12, 2);
BEGIN
  IF p_importe IS NULL OR pg_catalog.round(p_importe, 2) <= 0 THEN
    RAISE EXCEPTION 'El importe del abono debe ser mayor que cero'
      USING ERRCODE = '23514';
  END IF;
  importe_abono := pg_catalog.round(p_importe, 2);

  SELECT *
    INTO recibo_actual
    FROM public.hce_recibos_carros
   WHERE id = p_recibo_id
   FOR UPDATE;
  IF NOT FOUND THEN
    RAISE EXCEPTION 'Recibo no encontrado'
      USING ERRCODE = 'P0002';
  END IF;

  SELECT COALESCE(pg_catalog.sum(importe), 0)
    INTO total_recibo
    FROM public.hce_recibos_carros_conceptos
   WHERE recibo_id = p_recibo_id;
  SELECT COALESCE(pg_catalog.sum(importe), 0)
    INTO total_abonado_actual
    FROM public.hce_recibos_carros_pagos
   WHERE recibo_id = p_recibo_id;

  IF importe_abono > total_recibo - total_abonado_actual THEN
    RAISE EXCEPTION 'El abono no puede superar el saldo pendiente'
      USING ERRCODE = '23514';
  END IF;

  INSERT INTO public.hce_recibos_carros_pagos (
    recibo_id,
    fecha,
    importe,
    metodo_pago,
    referencia,
    observaciones
  )
  VALUES (
    p_recibo_id,
    COALESCE(p_fecha, CURRENT_DATE),
    importe_abono,
    NULLIF(pg_catalog.btrim(p_metodo_pago), ''),
    NULLIF(pg_catalog.btrim(p_referencia), ''),
    NULLIF(pg_catalog.btrim(p_observaciones), '')
  )
  RETURNING * INTO pago_creado;

  total_abonado_actual := total_abonado_actual + importe_abono;
  saldo_pendiente_actual := total_recibo - total_abonado_actual;
  estado_pago_actual := CASE
    WHEN total_recibo > 0 AND saldo_pendiente_actual = 0 THEN 'Pagado'
    WHEN total_abonado_actual > 0 THEN 'Abono parcial'
    ELSE 'Pendiente'
  END;

  UPDATE public.hce_recibos_carros
     SET total = total_recibo,
         total_abonado = total_abonado_actual,
         saldo_pendiente = saldo_pendiente_actual,
         estado_pago = estado_pago_actual,
         updated_at = CURRENT_TIMESTAMP
   WHERE id = p_recibo_id;

  RETURN pg_catalog.to_jsonb(pago_creado);
END;
$function$;

ALTER FUNCTION public.hce_crear_recibo_carro(jsonb, jsonb) OWNER TO postgres;
ALTER FUNCTION public.hce_actualizar_recibo_carro(bigint, jsonb, jsonb) OWNER TO postgres;
ALTER FUNCTION public.hce_registrar_pago_carro(bigint, date, numeric, text, text, text) OWNER TO postgres;

REVOKE ALL ON FUNCTION public.hce_crear_recibo_carro(jsonb, jsonb)
  FROM PUBLIC, anon, authenticated;
REVOKE ALL ON FUNCTION public.hce_actualizar_recibo_carro(bigint, jsonb, jsonb)
  FROM PUBLIC, anon, authenticated;
REVOKE ALL ON FUNCTION public.hce_registrar_pago_carro(bigint, date, numeric, text, text, text)
  FROM PUBLIC, anon, authenticated;

GRANT EXECUTE ON FUNCTION public.hce_crear_recibo_carro(jsonb, jsonb)
  TO service_role;
GRANT EXECUTE ON FUNCTION public.hce_actualizar_recibo_carro(bigint, jsonb, jsonb)
  TO service_role;
GRANT EXECUTE ON FUNCTION public.hce_registrar_pago_carro(bigint, date, numeric, text, text, text)
  TO service_role;

COMMIT;
