-- =============================================================
-- ARCHIVO: 28-cfdi-operations.sql
-- DESCRIPCION: Modulo CFDI - Operaciones: cancelaciones, logs,
--              complementos de pago
-- VERSION: 1.0.0
-- PROYECTO: ERP-Core V2
-- FECHA: 2026-02-03
-- DEPENDE DE: 27-cfdi-core.sql
-- =============================================================

-- =====================
-- TIPOS ENUMERADOS OPERACIONES
-- =====================

-- Motivo de cancelacion CFDI
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'cfdi_cancellation_reason') THEN
        CREATE TYPE fiscal.cfdi_cancellation_reason AS ENUM (
            '01',  -- Comprobante emitido con errores con relacion
            '02',  -- Comprobante emitido con errores sin relacion
            '03',  -- No se llevo a cabo la operacion
            '04'   -- Operacion nominativa relacionada en factura global
        );
    END IF;
END $$;

-- Estado de solicitud de cancelacion
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'cancellation_request_status') THEN
        CREATE TYPE fiscal.cancellation_request_status AS ENUM (
            'pending',          -- Pendiente de enviar
            'submitted',        -- Enviada al SAT
            'accepted',         -- Aceptada por el receptor
            'rejected',         -- Rechazada por el receptor
            'in_process',       -- En proceso (plazo de aceptacion)
            'expired',          -- Plazo expirado (cancelada automaticamente)
            'cancelled',        -- Cancelacion exitosa
            'error'             -- Error en el proceso
        );
    END IF;
END $$;

-- Tipo de operacion en log
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'cfdi_operation_type') THEN
        CREATE TYPE fiscal.cfdi_operation_type AS ENUM (
            'create',           -- Creacion de CFDI
            'stamp',            -- Timbrado
            'stamp_retry',      -- Reintento de timbrado
            'cancel_request',   -- Solicitud de cancelacion
            'cancel_accept',    -- Aceptacion de cancelacion
            'cancel_reject',    -- Rechazo de cancelacion
            'cancel_complete',  -- Cancelacion completada
            'validate',         -- Validacion en SAT
            'download',         -- Descarga de XML/PDF
            'email',            -- Envio por email
            'error'             -- Error en operacion
        );
    END IF;
END $$;

-- Estado del complemento de pago
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'payment_complement_status') THEN
        CREATE TYPE fiscal.payment_complement_status AS ENUM (
            'draft',            -- Borrador
            'pending',          -- Pendiente de timbrar
            'stamped',          -- Timbrado exitosamente
            'cancelled',        -- Cancelado
            'error'             -- Error
        );
    END IF;
END $$;

-- =====================
-- TABLA: cfdi_cancellation_requests
-- Solicitudes de cancelacion de CFDI
-- =====================
CREATE TABLE IF NOT EXISTS fiscal.cfdi_cancellation_requests (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

    -- CFDI a cancelar
    cfdi_invoice_id UUID NOT NULL REFERENCES fiscal.cfdi_invoices(id) ON DELETE CASCADE,
    cfdi_uuid VARCHAR(36) NOT NULL,           -- UUID del CFDI a cancelar

    -- Motivo de cancelacion
    cancellation_reason fiscal.cfdi_cancellation_reason NOT NULL,

    -- CFDI sustituto (para motivo 01)
    substitute_uuid VARCHAR(36),              -- UUID del CFDI que sustituye
    substitute_cfdi_id UUID REFERENCES fiscal.cfdi_invoices(id),

    -- Estado de la solicitud
    status fiscal.cancellation_request_status NOT NULL DEFAULT 'pending',

    -- Fechas del proceso
    requested_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    submitted_at TIMESTAMPTZ,                 -- Fecha de envio al SAT
    response_at TIMESTAMPTZ,                  -- Fecha de respuesta
    expires_at TIMESTAMPTZ,                   -- Fecha limite para aceptacion (72h)
    completed_at TIMESTAMPTZ,                 -- Fecha de cancelacion efectiva

    -- Respuesta del SAT
    sat_response_code VARCHAR(10),
    sat_response_message TEXT,
    sat_ack_xml TEXT,                         -- Acuse de cancelacion

    -- Respuesta del receptor (si aplica)
    receiver_response VARCHAR(20),            -- accepted, rejected
    receiver_response_at TIMESTAMPTZ,
    receiver_response_reason TEXT,

    -- Notas
    reason_notes TEXT,                        -- Notas adicionales del motivo
    internal_notes TEXT,

    -- Errores
    last_error TEXT,
    error_details JSONB,
    retry_count INTEGER DEFAULT 0,

    -- Auditoria
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES auth.users(id),
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_by UUID REFERENCES auth.users(id),

    UNIQUE(cfdi_invoice_id)
);

COMMENT ON TABLE fiscal.cfdi_cancellation_requests IS 'Solicitudes de cancelacion de CFDI segun proceso SAT 2022+';
COMMENT ON COLUMN fiscal.cfdi_cancellation_requests.cancellation_reason IS 'Motivo: 01=Con relacion, 02=Sin relacion, 03=No se realizo operacion, 04=Nominativa global';
COMMENT ON COLUMN fiscal.cfdi_cancellation_requests.expires_at IS 'Fecha limite para que el receptor acepte/rechace (72 horas habiles)';

-- =====================
-- TABLA: cfdi_operation_logs
-- Audit trail de todas las operaciones CFDI
-- =====================
CREATE TABLE IF NOT EXISTS fiscal.cfdi_operation_logs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

    -- Referencia al CFDI
    cfdi_invoice_id UUID REFERENCES fiscal.cfdi_invoices(id) ON DELETE SET NULL,
    cfdi_uuid VARCHAR(36),                    -- Guardamos UUID por si se elimina el registro

    -- Tipo de operacion
    operation_type fiscal.cfdi_operation_type NOT NULL,

    -- Estado antes y despues
    status_before VARCHAR(50),
    status_after VARCHAR(50),

    -- Resultado
    success BOOLEAN NOT NULL DEFAULT FALSE,

    -- Detalles de la operacion
    request_payload JSONB,                    -- Datos enviados
    response_payload JSONB,                   -- Respuesta recibida

    -- Error (si aplica)
    error_code VARCHAR(50),
    error_message TEXT,
    error_details JSONB,

    -- PAC utilizado
    pac_code VARCHAR(20),
    pac_transaction_id VARCHAR(100),

    -- Tiempo de respuesta
    duration_ms INTEGER,                      -- Duracion en milisegundos

    -- IP y user agent (para auditorias)
    ip_address INET,
    user_agent VARCHAR(500),

    -- Auditoria
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES auth.users(id)
);

COMMENT ON TABLE fiscal.cfdi_operation_logs IS 'Log de todas las operaciones realizadas sobre CFDIs';
COMMENT ON COLUMN fiscal.cfdi_operation_logs.operation_type IS 'Tipo de operacion: stamp, cancel_request, validate, etc.';
COMMENT ON COLUMN fiscal.cfdi_operation_logs.duration_ms IS 'Tiempo de respuesta del PAC/SAT en milisegundos';

-- =====================
-- TABLA: cfdi_payment_complements
-- Complementos de pago (CFDI tipo P - REP)
-- =====================
CREATE TABLE IF NOT EXISTS fiscal.cfdi_payment_complements (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

    -- Pago asociado
    payment_id UUID REFERENCES billing.payments(id) ON DELETE SET NULL,

    -- Identificadores CFDI
    uuid VARCHAR(36),                         -- UUID del complemento de pago
    serie VARCHAR(25),
    folio VARCHAR(40),

    -- Estado
    status fiscal.payment_complement_status NOT NULL DEFAULT 'draft',

    -- Certificado usado
    certificate_id UUID REFERENCES fiscal.cfdi_certificates(id),
    certificate_number VARCHAR(20),

    -- Datos del emisor
    issuer_rfc VARCHAR(13) NOT NULL,
    issuer_name VARCHAR(300) NOT NULL,
    issuer_fiscal_regime VARCHAR(10) NOT NULL,

    -- Datos del receptor
    receiver_rfc VARCHAR(13) NOT NULL,
    receiver_name VARCHAR(300) NOT NULL,
    receiver_fiscal_regime VARCHAR(10),
    receiver_zip_code VARCHAR(5),

    -- Lugar de expedicion
    expedition_place VARCHAR(5) NOT NULL,

    -- Datos del pago
    payment_date DATE NOT NULL,
    payment_form VARCHAR(10) NOT NULL,        -- Forma de pago SAT
    currency VARCHAR(3) NOT NULL DEFAULT 'MXN',
    exchange_rate DECIMAL(10, 6) DEFAULT 1,
    total_amount DECIMAL(18, 6) NOT NULL,

    -- Datos bancarios (si aplica)
    payer_bank_rfc VARCHAR(13),
    payer_bank_name VARCHAR(300),
    payer_bank_account VARCHAR(50),
    payee_bank_rfc VARCHAR(13),
    payee_bank_account VARCHAR(50),

    -- Cadena de pago (para transferencias)
    payment_chain TEXT,
    payment_certificate TEXT,
    payment_stamp TEXT,

    -- Operacion (efectivo, cheque, etc.)
    operation_number VARCHAR(100),

    -- XML y timbrado
    xml_original TEXT,
    xml_stamped TEXT,
    stamp_date TIMESTAMPTZ,
    stamp_sat_seal TEXT,
    stamp_cfdi_seal TEXT,
    stamp_original_chain TEXT,
    sat_certificate_number VARCHAR(20),

    -- PDF
    pdf_url VARCHAR(500),
    pdf_generated_at TIMESTAMPTZ,

    -- Cancelacion
    cancellation_request_id UUID REFERENCES fiscal.cfdi_cancellation_requests(id),
    cancellation_date TIMESTAMPTZ,

    -- Errores
    last_error TEXT,
    error_details JSONB,

    -- Auditoria
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES auth.users(id),
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_by UUID REFERENCES auth.users(id),
    deleted_at TIMESTAMPTZ,

    UNIQUE(tenant_id, uuid)
);

COMMENT ON TABLE fiscal.cfdi_payment_complements IS 'Complementos de pago (CFDI tipo P - Recibo Electronico de Pago)';
COMMENT ON COLUMN fiscal.cfdi_payment_complements.uuid IS 'UUID del timbre fiscal del complemento de pago';
COMMENT ON COLUMN fiscal.cfdi_payment_complements.payment_form IS 'Forma de pago segun catalogo SAT c_FormaPago';

-- =====================
-- TABLA: cfdi_payment_complement_documents
-- Documentos relacionados en el complemento de pago
-- =====================
CREATE TABLE IF NOT EXISTS fiscal.cfdi_payment_complement_documents (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    payment_complement_id UUID NOT NULL REFERENCES fiscal.cfdi_payment_complements(id) ON DELETE CASCADE,

    -- CFDI del documento relacionado
    related_uuid VARCHAR(36) NOT NULL,        -- IdDocumento - UUID de la factura pagada
    related_cfdi_id UUID REFERENCES fiscal.cfdi_invoices(id),

    -- Serie y folio del documento
    serie VARCHAR(25),
    folio VARCHAR(40),

    -- Moneda del documento
    document_currency VARCHAR(3) NOT NULL DEFAULT 'MXN',
    equivalence DECIMAL(18, 6) DEFAULT 1,     -- Equivalencia DR (tipo cambio)

    -- Numero de parcialidad
    installment_number INTEGER NOT NULL DEFAULT 1,

    -- Saldos
    previous_balance DECIMAL(18, 6) NOT NULL, -- ImpSaldoAnt
    amount_paid DECIMAL(18, 6) NOT NULL,      -- ImpPagado
    remaining_balance DECIMAL(18, 6) NOT NULL, -- ImpSaldoInsoluto

    -- Objeto de impuesto
    tax_object VARCHAR(2) DEFAULT '02',       -- ObjetoImpDR

    -- Auditoria
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

COMMENT ON TABLE fiscal.cfdi_payment_complement_documents IS 'Documentos (facturas) incluidos en el complemento de pago';
COMMENT ON COLUMN fiscal.cfdi_payment_complement_documents.related_uuid IS 'UUID de la factura que se esta pagando';
COMMENT ON COLUMN fiscal.cfdi_payment_complement_documents.installment_number IS 'Numero de parcialidad (1, 2, 3...)';
COMMENT ON COLUMN fiscal.cfdi_payment_complement_documents.previous_balance IS 'Saldo anterior antes de este pago';
COMMENT ON COLUMN fiscal.cfdi_payment_complement_documents.remaining_balance IS 'Saldo pendiente despues de este pago';

-- =====================
-- TABLA: cfdi_payment_complement_document_taxes
-- Impuestos por documento en complemento de pago
-- =====================
CREATE TABLE IF NOT EXISTS fiscal.cfdi_payment_complement_document_taxes (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    payment_complement_document_id UUID NOT NULL REFERENCES fiscal.cfdi_payment_complement_documents(id) ON DELETE CASCADE,

    -- Tipo de impuesto
    tax_type VARCHAR(20) NOT NULL,            -- transferred o withheld

    -- Impuesto
    tax_code VARCHAR(10) NOT NULL,            -- 001=ISR, 002=IVA, 003=IEPS

    -- Factor
    factor_type VARCHAR(10) NOT NULL,         -- Tasa, Cuota, Exento
    rate DECIMAL(10, 6),

    -- Montos
    base_amount DECIMAL(18, 6) NOT NULL,
    tax_amount DECIMAL(18, 6) NOT NULL,

    -- Auditoria
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

COMMENT ON TABLE fiscal.cfdi_payment_complement_document_taxes IS 'Impuestos por documento relacionado en el complemento de pago';

-- =====================
-- TABLA: cfdi_stamp_queue
-- Cola de timbrado para procesamiento asincrono
-- =====================
CREATE TABLE IF NOT EXISTS fiscal.cfdi_stamp_queue (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

    -- Tipo de documento a timbrar
    document_type VARCHAR(20) NOT NULL,       -- invoice, payment_complement
    document_id UUID NOT NULL,                -- ID del documento

    -- Prioridad
    priority INTEGER DEFAULT 5,               -- 1=urgente, 5=normal, 10=baja

    -- Estado de la cola
    queue_status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending, processing, completed, failed

    -- Intentos
    attempts INTEGER DEFAULT 0,
    max_attempts INTEGER DEFAULT 3,
    next_retry_at TIMESTAMPTZ,

    -- Resultado
    completed_at TIMESTAMPTZ,
    result_cfdi_uuid VARCHAR(36),
    result_error TEXT,

    -- Auditoria
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES auth.users(id),
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

COMMENT ON TABLE fiscal.cfdi_stamp_queue IS 'Cola de documentos pendientes de timbrar para procesamiento asincrono';

-- =====================
-- INDICES OPERACIONES
-- =====================

-- cfdi_cancellation_requests
CREATE INDEX IF NOT EXISTS idx_cfdi_cancel_tenant ON fiscal.cfdi_cancellation_requests(tenant_id);
CREATE INDEX IF NOT EXISTS idx_cfdi_cancel_cfdi ON fiscal.cfdi_cancellation_requests(cfdi_invoice_id);
CREATE INDEX IF NOT EXISTS idx_cfdi_cancel_uuid ON fiscal.cfdi_cancellation_requests(cfdi_uuid);
CREATE INDEX IF NOT EXISTS idx_cfdi_cancel_status ON fiscal.cfdi_cancellation_requests(status);
CREATE INDEX IF NOT EXISTS idx_cfdi_cancel_expires ON fiscal.cfdi_cancellation_requests(expires_at)
    WHERE status IN ('submitted', 'in_process');
CREATE INDEX IF NOT EXISTS idx_cfdi_cancel_pending ON fiscal.cfdi_cancellation_requests(tenant_id, status)
    WHERE status IN ('pending', 'submitted', 'in_process');

-- cfdi_operation_logs
CREATE INDEX IF NOT EXISTS idx_cfdi_logs_tenant ON fiscal.cfdi_operation_logs(tenant_id);
CREATE INDEX IF NOT EXISTS idx_cfdi_logs_cfdi ON fiscal.cfdi_operation_logs(cfdi_invoice_id);
CREATE INDEX IF NOT EXISTS idx_cfdi_logs_uuid ON fiscal.cfdi_operation_logs(cfdi_uuid);
CREATE INDEX IF NOT EXISTS idx_cfdi_logs_type ON fiscal.cfdi_operation_logs(operation_type);
CREATE INDEX IF NOT EXISTS idx_cfdi_logs_date ON fiscal.cfdi_operation_logs(created_at);
CREATE INDEX IF NOT EXISTS idx_cfdi_logs_success ON fiscal.cfdi_operation_logs(success);
CREATE INDEX IF NOT EXISTS idx_cfdi_logs_errors ON fiscal.cfdi_operation_logs(tenant_id, operation_type, created_at)
    WHERE success = FALSE;

-- cfdi_payment_complements
CREATE INDEX IF NOT EXISTS idx_cfdi_pcomp_tenant ON fiscal.cfdi_payment_complements(tenant_id);
CREATE INDEX IF NOT EXISTS idx_cfdi_pcomp_payment ON fiscal.cfdi_payment_complements(payment_id);
CREATE INDEX IF NOT EXISTS idx_cfdi_pcomp_uuid ON fiscal.cfdi_payment_complements(uuid);
CREATE INDEX IF NOT EXISTS idx_cfdi_pcomp_status ON fiscal.cfdi_payment_complements(status);
CREATE INDEX IF NOT EXISTS idx_cfdi_pcomp_issuer ON fiscal.cfdi_payment_complements(issuer_rfc);
CREATE INDEX IF NOT EXISTS idx_cfdi_pcomp_receiver ON fiscal.cfdi_payment_complements(receiver_rfc);
CREATE INDEX IF NOT EXISTS idx_cfdi_pcomp_date ON fiscal.cfdi_payment_complements(payment_date);

-- cfdi_payment_complement_documents
CREATE INDEX IF NOT EXISTS idx_cfdi_pcomp_docs_comp ON fiscal.cfdi_payment_complement_documents(payment_complement_id);
CREATE INDEX IF NOT EXISTS idx_cfdi_pcomp_docs_uuid ON fiscal.cfdi_payment_complement_documents(related_uuid);
CREATE INDEX IF NOT EXISTS idx_cfdi_pcomp_docs_cfdi ON fiscal.cfdi_payment_complement_documents(related_cfdi_id);

-- cfdi_payment_complement_document_taxes
CREATE INDEX IF NOT EXISTS idx_cfdi_pcomp_dtax_doc ON fiscal.cfdi_payment_complement_document_taxes(payment_complement_document_id);

-- cfdi_stamp_queue
CREATE INDEX IF NOT EXISTS idx_cfdi_queue_tenant ON fiscal.cfdi_stamp_queue(tenant_id);
CREATE INDEX IF NOT EXISTS idx_cfdi_queue_status ON fiscal.cfdi_stamp_queue(queue_status);
CREATE INDEX IF NOT EXISTS idx_cfdi_queue_priority ON fiscal.cfdi_stamp_queue(priority, created_at)
    WHERE queue_status = 'pending';
CREATE INDEX IF NOT EXISTS idx_cfdi_queue_retry ON fiscal.cfdi_stamp_queue(next_retry_at)
    WHERE queue_status = 'failed' AND attempts < max_attempts;
CREATE INDEX IF NOT EXISTS idx_cfdi_queue_doc ON fiscal.cfdi_stamp_queue(document_type, document_id);

-- =====================
-- FIN DEL ARCHIVO
-- =====================
