<?php

require_once('public/lib/print/num_letras.php');
class PseModel extends Model
{

    public function __construct()
    {
        parent::__construct();
    }



    public function getDataComprobante($id_venta)
    {

        $comprobante = $this->getDataDocumento($id_venta);

        if ($comprobante === null) {

            return;
        }

        $empresa = $this->getDataEmpresa();
        $cliente = $this->getDataCliente($comprobante['id_cliente']);
        $dataSecret = $this->getSecretData();


        $data = [

            "token_cliente" => "FACTURALAYA_BZ1GPEX6RJ21EKIRAM56",
            "ruc_proveedor" => "10470949444",
            "secret_data" => $dataSecret,
            "emisor" => [
                "ruc" => $empresa['ruc'],
                "tipo_doc" => "6",
                "email" => "aquino.alex@gmail.com",
                "nom_comercial" => $empresa['nombre_comercial'],
                "razon_social" => $empresa['razon_social'],
                "codigo_ubigeo" => $empresa['ubigeo'],
                "direccion" => $empresa['direccion_fiscal'],
                "modalidad_envio_sunat" => "inmediato",
                "direccion_departamento" => $empresa['departamento'],
                "direccion_provincia" => $empresa['provincia'],
                "direccion_distrito" => $empresa['distrito'],
                "direccion_codigopais" => "PE",
            ],
            "cliente_tipodocumento" => $cliente['tipo_cliente'] == 2 ? 6 : 1,
            "cliente_numerodocumento" => $cliente['tipo_cliente'] == 2 ? $cliente['ruc'] : $cliente['dni'],
            "cliente_nombre" => $cliente['tipo_cliente'] == 2 ? $cliente['razon_social'] : $cliente['nombres'],
            "cliente_direccion" => $cliente['direccion'],
            "cliente_pais" => "PE",
            "cliente_ciudad" => "Huanuco",
            "cliente_codigoubigeo" => "",
            "cliente_departamento" => "",
            "cliente_provincia" => "",
            "cliente_distrito" => "",
            "detalle" => $comprobante['detalle'],
            "tipo_operacion" => "0101",
            "total_gravadas" => 0.00,
            "total_inafecta" => 0.00,
            "total_exoneradas" => (float) $comprobante['total'],
            "total_gratuitas" => 0.00,
            "total_exportacion" => 0.00,
            "total_isc" => 0.00,
            "total_icbper" => 0.00,
            "total_otr_imp" => 0.00,
            "total_descuento" => 0.00,
            "impuesto_icbper" => 0.00,
            "porcentaje_igv" => 18,
            "total_igv" => 0.00,
            "sub_total" => (float) $comprobante['total'],
            "total" => (float) $comprobante['total'],
            "total_letras" => numtoletras($comprobante['total']),
            "docs_referencia" => [],
            "nro_otr_comprobante" => "",
            "transporte_nro_placa" => "",
            "cod_moneda" => "PEN",
            "cod_sucursal_sunat" => "0000",
            "fecha_comprobante" => substr($comprobante['fecha_venta'], 0, 10),
            "fecha_vto_comprobante" => substr($comprobante['fecha_venta'], 0, 10),
            "forma_de_pago" => "contado",
            "monto_deuda_total" => 0.00,
            "detalle_cuotas" => [],
            "cod_tipo_documento" => $this->codigoDocumentoPorNumero((int) $comprobante['id_tipo_doc']),
            "serie_comprobante" => $comprobante['serie_doc'],
            //"serie_comprobante" => "F001",
            "numero_comprobante" => (int) $comprobante['nro_doc'],
            "modalidad_envio_sunat" => "inmediato",
        ];

        return $data;

    }

    public function getDataComprobanteBaja($id_venta)
    {

        if ($id_venta <= 0) {
            throw new \InvalidArgumentException("id_venta inválido.");
        }

        $empresa = $this->getDataEmpresa();
        $dataSecret = $this->getSecretData();


        $data = [
            "token_cliente" => "FACTURALAYA_BZ1GPEX6RJ21EKIRAM56",
            "ruc_proveedor" => "10470949444",
            "secret_data" => $dataSecret,
            "emisor" => [
                "ruc" => $empresa['ruc'],
                "tipo_doc" => "6",
                "email" => "aquino.alex@gmail.com",
                "nom_comercial" => $empresa['nombre_comercial'],
                "razon_social" => $empresa['razon_social'],
                "codigo_ubigeo" => $empresa['ubigeo'],
                "direccion" => $empresa['direccion_fiscal'],
                "modalidad_envio_sunat" => "inmediato",
                "direccion_departamento" => $empresa['departamento'],
                "direccion_provincia" => $empresa['provincia'],
                "direccion_distrito" => $empresa['distrito'],
                "direccion_codigopais" => "PE",
            ],
            "cabecera" => $this->getDataCabeceraBaja($id_venta),
            "detalle" => $this->getDataDocBaja($id_venta)
        ];

        return $data;

    }

    public function getDataComprobanteResumen($id_venta, $accion)
    {

        if ($id_venta <= 0) {
            throw new \InvalidArgumentException("id_venta inválido.");
        }

        $empresa = $this->getDataEmpresa();
        $documento = $this->getDataDocResumen($id_venta, $accion);
        $dataSecret = $this->getSecretData();


        $data = [

            "token_cliente" => "FACTURALAYA_BZ1GPEX6RJ21EKIRAM56",
            "ruc_proveedor" => "10470949444",
            "secret_data" => $dataSecret,
            "emisor" => [
                "ruc" => $empresa['ruc'],
                "tipo_doc" => "6",
                "email" => "aquino.alex@gmail.com",
                "nom_comercial" => $empresa['nombre_comercial'],
                "razon_social" => $empresa['razon_social'],
                "codigo_ubigeo" => $empresa['ubigeo'],
                "direccion" => $empresa['direccion_fiscal'],
                "modalidad_envio_sunat" => "inmediato",
                "direccion_departamento" => $empresa['departamento'],
                "direccion_provincia" => $empresa['provincia'],
                "direccion_distrito" => $empresa['distrito'],
                "direccion_codigopais" => "PE",
            ],

        ] + $documento;

        return $data;

    }

    private function getSecretData(): array
    {
        // 1) Traer empresa/contribuyente
        $sql = "SELECT * FROM tm_empresa LIMIT 1";
        $stmt = $this->db->query($sql);
        $e = $stmt->fetch(\PDO::FETCH_ASSOC);

        if (!$e) {
            throw new \RuntimeException("Los datos de la empresa no fueron encontrados.");
        }

        $directoriobeta = 'api_fact/UBL21/archivos_xml_sunat/certificados/beta/' . $e['ruc'];
        $directorioproduccion = 'api_fact/UBL21/archivos_xml_sunat/certificados/produccion/' . $e['ruc'];

        $isProd = $e['modo'] == 1;
        $isPse = $e['certpse'] == 1;

        $secret = [
            'tipo_proceso' => $isProd ? 'produccion' : 'prueba',
            'tipo_certificado' => $isPse ? 'pse_facturalaya' : 'propio', // <-- siempre definido
            'file_certificado' => '',
            'pass_certificado' => '',
            'usuariosol' => '',
            'clavesol' => '',

            'ruta_certificado' => '',
            'ruta_dir_xml' => '',
            'ruc_pse' => '',

            'auth_api_sunat_ruc' => $e['ruc'] ?? '',
            'auth_api_sunat_usol' => '',
            'auth_api_sunat_csol' => '',
            'auth_api_sunat_id' => $e['client_id'] ?? '',
            'auth_api_sunat_secret' => $e['client_secret'] ?? '',
        ];

        $rutaXml = $isProd ? $directorioproduccion : $directoriobeta;
        if (!is_dir($rutaXml)) {
            @mkdir($rutaXml, 0777, true);
        }
        $secret['ruta_dir_xml'] = $rutaXml;

        // 5) Ramas exactas de tu lógica
        if ($isProd) {
            if (!$isPse) {
                // PRODUCCIÓN con certificado PROPIO
                $rutaPfx = $directorioproduccion . '/' . $e['ruc'] . '.pfx';
                if (!is_file($rutaPfx) || !is_readable($rutaPfx)) {
                    return [
                        'respuesta' => 'error',
                        'titulo' => 'Error RUTA Certificado',
                        'mensaje' => 'No se encuentra la ruta del certificado de producción',
                    ];
                }

                $secret['ruta_certificado'] = $rutaPfx;
                $secret['file_certificado'] = base64_encode(file_get_contents($rutaPfx));
                $secret['pass_certificado'] = (string) ($e['clavecertificado'] ?? '');
                $secret['usuariosol'] = (string) ($e['usuariosol'] ?? '');
                $secret['clavesol'] = (string) ($e['clavesol'] ?? '');

                // ya está tipo_certificado = 'propio'
            } else {
                // PRODUCCIÓN con PSE (no envías PFX ni SOL)
                $secret['ruta_certificado'] = '';
                $secret['file_certificado'] = '';
                $secret['pass_certificado'] = '';
                $secret['usuariosol'] = '';
                $secret['clavesol'] = '';
                $secret['ruc_pse'] = $e['ruc_pse'] ?? ''; // si lo manejas
                // tipo_certificado = 'pse_facturalaya'
            }
        } else {
            // PRUEBA (usa PFX de prueba y credenciales MODDATOS)
            $rutaPfx = $directoriobeta . '/firmabeta.pfx';
            if (!is_file($rutaPfx)) {
                return [
                    'respuesta' => 'error',
                    'titulo' => 'Error RUTA Certificado',
                    'mensaje' => 'No se encuentra la ruta del certificado',
                ];
            }

            $secret['ruta_certificado'] = $rutaPfx;
            $secret['file_certificado'] = base64_encode(file_get_contents($rutaPfx));
            $secret['pass_certificado'] = '123456';
            $secret['tipo_proceso'] = 'prueba';
            $secret['usuariosol'] = 'MODDATOS';
            $secret['clavesol'] = 'moddatos';
            $secret['tipo_certificado'] = 'propio'; // así lo tenías
        }

        return $secret;
    }

    private function codigoDocumentoPorNumero(int $n): ?string
    {
        static $map = [
        1 => '03',
        2 => '01',
        3 => '77'
        ];
        return $map[$n] ?? null; // null si no existe
    }

    public function getDataCliente(int $id_cliente): ?array
    {
        try {
            $sql = "SELECT * FROM tm_cliente WHERE id_cliente = :id LIMIT 1";
            $stmt = $this->db->prepare($sql);
            $stmt->bindValue(':id', $id_cliente, \PDO::PARAM_INT);
            $stmt->execute();

            $row = $stmt->fetch(\PDO::FETCH_ASSOC);
            return $row ?: null;
        } catch (\Throwable $th) {
            throw $th;
        }
    }

    public function getDataEmpresa(): ?array
    {
        try {
            // Si solo tienes 1 registro de empresa, usa LIMIT 1
            $sql = "SELECT * FROM tm_empresa LIMIT 1";
            $stmt = $this->db->query($sql);
            $row = $stmt->fetch(\PDO::FETCH_ASSOC);
            return $row ?: null;
        } catch (\Throwable $th) {
            throw $th;
        }
    }

    public function getDataDocumento(int $id_venta): ?array
    {
        try {


            $sql = "SELECT * FROM tm_venta WHERE id_venta = :id LIMIT 1";
            $stmt = $this->db->prepare($sql);
            $stmt->bindValue(':id', $id_venta, \PDO::PARAM_INT);
            $stmt->execute();

            $venta = $stmt->fetch(\PDO::FETCH_ASSOC);

            if (!$venta) {
                return null;
            }


            $igvPercent = 0;
            $igvRate = $igvPercent / 100.0;


            $sqlDet = "
                SELECT
                    d.id_prod,
                    pp.cod_prod,
                    SUM(d.cantidad)                               AS cantidad_total,
                    SUM(d.cantidad * d.precio)                    AS total,
                    'NIU'                                           AS unidad_medida,
                    COALESCE(MAX(pp.cod_prod), '')                  AS codigo_det,
                    COALESCE(MAX(pp.presentacion), '')            AS presentacion
                FROM tm_detalle_venta d
                INNER JOIN tm_producto_pres pp ON pp.id_pres = d.id_prod
                WHERE d.id_venta = :id_ref
                GROUP BY d.id_prod
                ORDER BY MIN(d.id_prod) ASC
            ";

            $stDet = $this->db->prepare($sqlDet);
            $stDet->execute(['id_ref' => (int) $venta['id_venta']]);
            $rows = $stDet->fetchAll(\PDO::FETCH_ASSOC) ?: [];

            $detalle = [];
            $item = 1;

            foreach ($rows as $r) {
                $cant = (float) $r['cantidad_total'];
                $importeCon = (float) $r['total'];                 // total con IGV
                $precioCon = $cant > 0 ? round($importeCon / $cant, 2) : 0.0;                 // unitario con IGV
                $precioSin = round($precioCon / (1 + $igvRate), 2);                              // unitario sin IGV
                $importeSin = round($importeCon / (1 + $igvRate), 2);                             // total sin IGV
                $igvDet = round($importeCon - $importeSin, 2);

                $desc = trim(($r['presentacion'] ?? ''));

                $detalle[] = [
                    "ITEM_DET" => (int) $item++,
                    "UNIDAD_MEDIDA_DET" => $r['unidad_medida'] ?: 'NIU',
                    "PRECIO_TIPO_CODIGO" => "01",
                    "COD_TIPO_OPERACION_DET" => "20",
                    "CANTIDAD_DET" => $cant,
                    "PRECIO_DET" => $precioCon,      // unitario CON IGV
                    "IGV_DET" => $igvDet,         // IGV total del ítem
                    "ICBPER_DET" => 0,
                    "ISC_DET" => 0,
                    "PRECIO_SIN_IGV_DET" => $precioSin,      // unitario SIN IGV
                    "IMPORTE_DET" => round($importeCon, 2), // total CON IGV
                    "CODIGO_DET" => $r['codigo_det'],
                    "DESCRIPCION_DET" => $desc,
                    "DESCUENTO_ITEM" => "no",
                    "PORCENTAJE_DESCUENTO" => 0,
                    "MONTO_DESCUENTO" => 0,
                    "CODIGO_DESCUENTO" => "00",
                ];
            }


            $venta['detalle'] = $detalle;

            return $venta;

        } catch (\Throwable $th) {
            throw $th;
        }
    }

    private function getDataDocBaja(int $id_venta): array
    {
        $sql = "SELECT * FROM tm_venta WHERE id_venta = :id LIMIT 1";
        $stmt = $this->db->prepare($sql);
        $stmt->bindValue(':id', $id_venta, \PDO::PARAM_INT);
        $stmt->execute();

        $venta = $stmt->fetch(\PDO::FETCH_ASSOC);
        if (!$venta) {
            throw new \InvalidArgumentException("Venta no encontrada: {$id_venta}");
        }

        $tipo = $this->codigoDocumentoPorNumero((int) ($venta['id_tipo_doc'] ?? 0));
        if (!in_array($tipo, ['01', '03', '07', '08'], true)) {
            throw new \InvalidArgumentException("Tipo de comprobante inválido para baja: {$tipo}");
        }

        $serie = strtoupper(trim((string) ($venta['serie_doc'] ?? '')));
        $numero = (int) ($venta['nro_doc'] ?? 0);

        if ($serie === '' || $numero <= 0) {
            throw new \InvalidArgumentException("Serie y número del comprobante son obligatorios.");
        }

        // Opcional: validar formato común de serie (ej. F001, B001, etc.)
        // if (!preg_match('/^[A-Z0-9]{1}[A-Z0-9]{3,}$/', $serie)) { ... }

        $detalleItem = [
            "ITEM_DET" => 1,
            "TIPO_COMPROBANTE" => $tipo,
            "SERIE" => $serie,
            "NUMERO" => $numero,
            "MOTIVO" => "Error en el RUC", // ajusta tu motivo si corresponde
        ];


        return [$detalleItem];
    }

    private function getDataDocResumen(int $id_venta, int $accion): array
    {

        $sql = $sql = "
            SELECT
            v.*,
            c.dni        AS dni_cliente,
            c.estado     AS estado_cliente
            FROM tm_venta v
            INNER JOIN tm_cliente c
            ON c.id_cliente = v.id_cliente
            AND c.estado = 'a'
            WHERE v.id_venta = :id
        LIMIT 1";

        $stmt = $this->db->prepare($sql);
        $stmt->bindValue(':id', $id_venta, \PDO::PARAM_INT);
        $stmt->execute();

        $venta = $stmt->fetch(\PDO::FETCH_ASSOC);
        if (!$venta) {
            throw new \InvalidArgumentException("Venta no encontrada: {$id_venta}");
        }

        $tipo = $this->codigoDocumentoPorNumero((int) ($venta['id_tipo_doc'] ?? 0)); // tu helper existente
        if (!in_array($tipo, ['03', '07', '08'], true)) {
            throw new \InvalidArgumentException("Solo Boletas (03) y Notas de Boleta (07/08) van en RC. Tipo: {$tipo}");
        }

        $serie = strtoupper(trim((string) ($venta['serie_doc'] ?? ''))); // ej. B001
        $numero = (int) ($venta['nro_doc'] ?? 0);                        // ej. 4
        if ($serie === '' || $numero <= 0) {
            throw new \InvalidArgumentException("Serie y número del comprobante son obligatorios.");
        }
        $nroComprobante = "{$serie}-{$numero}";

        $tipoDocCli = "1"; // por defecto DNI

        $fechaEmision = $venta['fecha_venta'] ?? $venta['fecha_venta'] ?? date('Y-m-d'); // ajusta nombre de columna
        $fechaReferencia = (new \DateTime($fechaEmision))->format('Y-m-d');
        $fechaDocumento = date('Y-m-d'); // hoy
        $serieRC = (new \DateTime($fechaDocumento))->format('Ymd'); // p.ej. 20240702
        $secuencia = $this->obtenerSiguienteSecuenciaRC();

        $moneda = strtoupper((string) ($venta['moneda'] ?? 'PEN'));
        $gravada = (float) ($venta['op_gravada'] ?? $venta['total_gravadas'] ?? 0);
        $exonerado = (float) ($venta['op_exonerada'] ?? $venta['total_exoneradas'] ?? 0);
        $inafecto = (float) ($venta['op_inafecta'] ?? $venta['total_inafecta'] ?? 0);
        $exporta = (float) ($venta['op_exportacion'] ?? $venta['total_exportacion'] ?? 0);
        $gratuitas = (float) ($venta['op_gratuitas'] ?? $venta['total_gratuitas'] ?? 0);
        $igv = (float) ($venta['igv'] ?? $venta['total_igv'] ?? 0);
        $isc = 0;
        $otros = 0;
        $icbper = (float) ($venta['icbper'] ?? 0);
        $numDocCli = trim((string) ($venta['dni_cliente'] ?? '00000000'));

        $total = (float) ($venta['total'] ?? $venta['importe_total'] ?? 0);
        if ($total <= 0) {
            $total = $gravada + $exonerado + $inafecto + $exporta + $igv + $isc + $otros; // ICBPER normalmente ya va sumado; ajusta según tu modelo
        }

        $status = $accion;

        // 8) Armar estructura RC
        $detalleItem = [
            "ITEM_DET" => 1,
            "TIPO_COMPROBANTE" => $tipo,
            "NRO_COMPROBANTE" => $nroComprobante,
            "NRO_DOCUMENTO" => $numDocCli,
            "TIPO_DOCUMENTO" => $tipoDocCli, // "1"=DNI, "6"=RUC, "0"=SIN DOC
            "NRO_COMPROBANTE_REF" => "",          // para notas: pon aquí el doc base si aplica
            "TIPO_COMPROBANTE_REF" => "",          // "03" si refieres boleta
            "STATUS" => $status,
            "COD_MONEDA" => $moneda,
            "TOTAL" => round($total, 2),
            "GRAVADA" => round($gravada, 2),
            "EXONERADO" => round($exonerado, 2),
            "INAFECTO" => round($inafecto, 2),
            "EXPORTACION" => round($exporta, 2),
            "ICBPER" => round($icbper, 2),
            "GRATUITAS" => round($gratuitas, 2),
            "IGV" => round($igv, 2),
            "MONTO_CARGO_X_ASIG" => "0",
            "CARGO_X_ASIGNACION" => "0",
            "ISC" => round($isc, 2),
            "OTROS" => round($otros, 2),
        ];

        return [
            "codigo" => "RC",
            "serie" => $serieRC,        // p.ej. "20240702"
            "secuencia" => $secuencia,
            "fecha_referencia" => $fechaReferencia,
            "fecha_documento" => $fechaDocumento,
            "detalle" => [$detalleItem],
        ];
    }

    /**
     * Mapea tu valor de tipo de doc de cliente a los códigos SUNAT (1=DNI, 6=RUC, 0=Sin Doc).
     * Si el CPE es boleta y no hay doc, devuelve "0".
     */
    private function mapTipoDocCliente($rawTipo, string $tipoCpe): string
    {
        $t = strtoupper(trim((string) $rawTipo));
        if ($t === '' && $tipoCpe === '03') {
            return '0';
        }
        // Acepta variantes comunes
        $map = [
            'DNI' => '1',
            '1' => '1',
            'RUC' => '6',
            '6' => '6',
            'CE' => '4',
            '4' => '4',
            'PAS' => '7',
            '7' => '7',
            '0' => '0',
        ];
        return $map[$t] ?? '0';
    }

    /**
     * Devuelve la siguiente secuencia del RC para una fecha dada.
     * Implementa aquí tu lógica real (tabla de resumenes, etc.). Por ahora: 1.
     */
    private function obtenerSiguienteSecuenciaRC(): int
    {
        $tz = new \DateTimeZone('America/Lima');
        $ahora = new \DateTimeImmutable('now', $tz);
        // Secuencia: mismo cálculo que mostraste (desde medianoche)
        $inicioDia = new \DateTimeImmutable($ahora->format('Y-m-d') . ' 00:00:00', $tz);
        $intervalo = $inicioDia->diff($ahora);
        // concatena horas y minutos (como string) y suma los segundos
        $secuencia = (int) ($intervalo->h . $intervalo->i) + (int) $intervalo->s;

        // Asegura que sea al menos 1
        if ($secuencia < 1) {
            $secuencia = 1;
        }
        return $secuencia;
    }

    private function getDataCabeceraBaja(int $id_venta): array
    {
        $sql = "SELECT * FROM tm_venta WHERE id_venta = :id LIMIT 1";
        $stmt = $this->db->prepare($sql);
        $stmt->bindValue(':id', $id_venta, \PDO::PARAM_INT);
        $stmt->execute();

        $venta = $stmt->fetch(\PDO::FETCH_ASSOC);
        if (!$venta) {
            throw new \InvalidArgumentException("Venta no encontrada: {$id_venta}");
        }

        // Tipo de comprobante (factura, boleta, NC, ND)
        $tipo = $this->codigoDocumentoPorNumero((int) ($venta['id_tipo_doc'] ?? 0));
        if (!in_array($tipo, ['01', '03', '07', '08'], true)) {
            throw new \InvalidArgumentException("Tipo de comprobante inválido para baja: {$tipo}");
        }

        $serie = strtoupper(trim((string) ($venta['serie_doc'] ?? '')));
        $numero = (int) ($venta['nro_doc'] ?? 0);

        if ($serie === '' || $numero <= 0) {
            throw new \InvalidArgumentException("Serie y número del comprobante son obligatorios.");
        }

        // === Armamos la cabecera de la comunicación de baja (RA) ===
        $tz = new \DateTimeZone('America/Lima');
        $ahora = new \DateTimeImmutable('now', $tz);
        $fechaHoy = $ahora->format('Y-m-d');   // ej. 2025-08-25
        $serieRA = $ahora->format('Ymd');     // ej. 20250825

        // Secuencia: mismo cálculo que mostraste (desde medianoche)
        $inicioDia = new \DateTimeImmutable($ahora->format('Y-m-d') . ' 00:00:00', $tz);
        $intervalo = $inicioDia->diff($ahora);
        // concatena horas y minutos (como string) y suma los segundos
        $secuencia = (int) ($intervalo->h . $intervalo->i) + (int) $intervalo->s;

        // Asegura que sea al menos 1
        if ($secuencia < 1) {
            $secuencia = 1;
        }

        return [
            "codigo" => $tipo == "03" ? "RC" : "RA",
            "serie" => $serieRA,
            "secuencia" => $secuencia,
            "fecha_referencia" => (new \DateTimeImmutable($venta['fecha_venta'] ?? $fechaHoy))->format('Y-m-d'),
            "fecha_baja" => $fechaHoy,
        ];
    }

    public function setDataDocumento(
        int $id_venta,
        int $enviado_sunat,
        ?string $code_respuesta_sunat,
        ?string $descripcion_sunat_cdr,
        ?string $name_file_sunat,
        ?string $hash_cdr,
        ?string $hash_cpe,
        ?string $estado
    ): bool {
        try {
            $sql = "UPDATE tm_venta
                   SET enviado_sunat         = :enviado_sunat,
                       code_respuesta_sunat  = :code_respuesta_sunat,
                       descripcion_sunat_cdr = :descripcion_sunat_cdr,
                       name_file_sunat       = :name_file_sunat,
                       hash_cdr              = :hash_cdr,
                       hash_cpe              = :hash_cpe,
                       estado                = :estado
                 WHERE id_venta = :id_venta";

            $stmt = $this->db->prepare($sql);
            $ok = $stmt->execute([
                ':enviado_sunat' => $enviado_sunat,
                ':code_respuesta_sunat' => $code_respuesta_sunat,
                ':descripcion_sunat_cdr' => $descripcion_sunat_cdr,
                ':name_file_sunat' => $name_file_sunat,
                ':hash_cdr' => $hash_cdr,
                ':hash_cpe' => $hash_cpe,
                ':id_venta' => $id_venta,
                ':estado' => $estado
            ]);

            // rowCount puede ser 0 si los valores son iguales; igual consideramos éxito si ejecutó.
            return $ok && $stmt->rowCount() >= 0;
        } catch (\Throwable $th) {
            throw $th;
        }
    }

    public function validarBaja(int $id_venta): bool
    {
        try {
            $sql = "SELECT fecha_venta FROM tm_venta WHERE id_venta = :id_venta LIMIT 1";
            $stmt = $this->db->prepare($sql);
            $stmt->bindValue(':id_venta', $id_venta, \PDO::PARAM_INT);
            $stmt->execute();

            $fechaVenta = $stmt->fetchColumn();
            if (!$fechaVenta) {
                // No existe la venta
                return false;
            }

            // Normalizamos fechas en zona horaria Lima
            $tz = new \DateTimeZone('America/Lima');
            $fechaVenta = new \DateTimeImmutable($fechaVenta, $tz);
            $hoy = new \DateTimeImmutable('today', $tz);

            $dias = $fechaVenta->diff($hoy)->days;

            // Si pasaron más de 7 días -> no se puede dar de baja
            if ($dias > 6) {
                return false;
            }

            return true;

        } catch (\Throwable $th) {
            throw $th;
        }
    }


    // public function reponerStock(int $id_venta): array
    // {
    //     try {

    //         $this->db->beginTransaction();

    //         $sqlDetalles = "
    //         SELECT dv.id_venta, dv.id_prod, dv.cantidad,pp.crt_stock, pp.receta
    //         FROM tm_detalle_venta dv
    //         JOIN tm_producto_pres pp ON dv.id_prod = pp.id_pres
    //         WHERE dv.id_venta = :id_venta
    //     ";

    //         $stDet = $this->db->prepare($sqlDetalles);
    //         $stDet->execute([':id_venta' => $id_venta]);
    //         $detalles = $stDet->fetchAll(\PDO::FETCH_ASSOC);

    //         if (!$detalles) {
    //             // Nada que reponer
    //             $this->db->commit();
    //             return [
    //                 'ok' => false,
    //                 'message' => 'No hay detalles para esta venta ${id_venta}'
    //             ];
    //         }


    //         $updPres = $this->db->prepare("
    //         UPDATE tm_producto_pres
    //         SET stock = stock + :cantidad
    //         WHERE id_pres = :id
    //     ");


    //         foreach ($detalles as $d) {

    //             $idPresentacion = (int) $d['id_prod'];
    //             $cantidad = (float) $d['cantidad'];
    //             $controlStock = (int) $d['crt_stock']; // 1 = controla stock
    //             $receta = (int) $d['receta'];    // 1 = usa insumos

    //             if ($controlStock === 1 && $receta === 0) {

    //                 $updPres->bindValue(':cantidad', (string) $cantidad);
    //                 $updPres->bindValue(':id', (int) $idPresentacion, \PDO::PARAM_INT);
    //                 $updPres->execute();

    //             }
    //         }

    //         $this->db->commit();

    //         return [
    //             'ok' => true,
    //             'message' => 'Stock repuesto'
    //         ];

    //     } catch (\Throwable $th) {
    //         $this->db->rollBack();
    //         throw $th;
    //     }
    // }


    public function reponerStockVenta(int $id_venta): array
    {
        // buscar el pedido de esa venta
        $stmt = $this->db->prepare("SELECT id_pedido FROM tm_venta WHERE id_venta = :id_venta LIMIT 1");
        $stmt->execute([':id_venta' => $id_venta]);
        $id_pedido = $stmt->fetchColumn();

        if ($id_pedido === false) {
            return [
                'ok' => false,
                'message' => 'No se encontró pedido para la venta',
                'id_venta' => $id_venta,
            ];
        }

        // delega toda la lógica + transacción en reponerStock
        $resp = $this->reponerStock((int) $id_pedido, true);

        // añade contexto útil
        $resp['id_venta'] = $id_venta;
        $resp['id_pedido'] = (int) $id_pedido;
        return $resp;
    }


    //     public function reponerStock(int $id_pedido): array
// {
//     try {
//         $this->db->beginTransaction();

    //         // 1) Traer detalles del pedido + flags de control de stock
//         $sqlDetalles = "
//             SELECT
//                 dp.id_pedido,
//                 dp.id_pres,
//                 dp.cant,
//                 dp.precio,
//                 pp.crt_stock,   -- 1 = controla stock
//                 pp.receta,      -- 1 = usa insumos/receta (no repone aquí)
//                 pp.stock AS stock_actual
//             FROM tm_detalle_pedido dp
//             JOIN tm_producto_pres pp ON pp.id_pres = dp.id_pres
//             WHERE dp.id_pedido = :id_pedido AND
//             dp.estado = 'a'
//         ";

    //         $stDet = $this->db->prepare($sqlDetalles);
//         $stDet->execute([':id_pedido' => $id_pedido]);
//         $detalles = $stDet->fetchAll(\PDO::FETCH_ASSOC);

    //         if (!$detalles) {
//             $this->db->commit();
//             return [
//                 'ok'   => false,
//                 'msg'  => 'Venta sin detalles',
//                 'items'=> []
//             ];
//         }

    //         // 2) Statements preparados (UPDATE y SELECT)
//         $updPres = $this->db->prepare("
//             UPDATE tm_producto_pres
//                SET stock = stock + CAST(:cantidad AS DECIMAL(12,3))
//              WHERE id_pres = :id
//              LIMIT 1
//         ");

    //         $selStock = $this->db->prepare("
//             SELECT stock
//               FROM tm_producto_pres
//              WHERE id_pres = :id
//              LIMIT 1
//         ");

    //         $totalActualizadas = 0;
//         $noActualizadas   = [];
//         $items            = [];

    //         // 3) Iterar y reponer
//         foreach ($detalles as $d) {
//             $idPres       = (int)$d['id_pres'];
//             $cantidadRaw  = (float)$d['cant'];
//             $controlStock = (int)$d['crt_stock'];
//             $receta       = (int)$d['receta'];
//             $precio       = (float)$d['precio'];

    //             // Solo productos finales que controlan stock
//             if ($controlStock !== 1 || $receta !== 0) {
//                 continue;
//             }

    //             if ($cantidadRaw <= 0) {
//                 $noActualizadas[] = ['id_pres' => $idPres, 'motivo' => 'cantidad<=0'];
//                 continue;
//             }

    //             // Normalizar a 3 decimales (string)
//             $cantidad = sprintf('%.3f', $cantidadRaw);

    //             // UPDATE
//             $ok = $updPres->execute([
//                 ':cantidad' => $cantidad,          // string "X.XXX"
//                 ':id'       => $idPres
//             ]);

    //             if (!$ok) {
//                 $noActualizadas[] = [
//                     'id_pres' => $idPres,
//                     'motivo'  => 'execute-failed',
//                     'error'   => $updPres->errorInfo()
//                 ];
//                 continue;
//             }

    //             $afectadas = $updPres->rowCount();
//             if ($afectadas === 0) {
//                 $noActualizadas[] = ['id_pres' => $idPres, 'motivo' => 'sin filas afectadas'];
//                 continue;
//             }

    //             // SELECT del stock actualizado
//             $selStock->execute([':id' => $idPres]);
//             $nuevoStock = $selStock->fetchColumn();
//             $selStock->closeCursor();

    //             $items[] = [
//                 'id_pres'     => $idPres,
//                 'cantidad'    => (float)$cantidadRaw,
//                 'nuevo_stock' => $nuevoStock !== false ? (float)$nuevoStock : null
//             ];

    //             // 4) Registrar en Kárdex (ajusta costo si tienes costo real)
//             $respKardex = $this->registrarKardex([
//                 'tipo_kardex'        => 5, // Reposición por anulación de venta
//                 'presentacion_id'    => $idPres,
//                 'tipo_presentacion'  => 1,
//                 'cantidad'           => (float)$cantidadRaw,
//                 'costo_unitario'     => $precio, // idealmente, costo contable
//                 'costo_total'        => $precio * $cantidadRaw,
//                 'serie_documento'    => null,
//                 'numero_documento'   => null,
//                 'tipo_documento'     => null,
//                 'stock'              => $nuevoStock !== false ? (float)$nuevoStock : null,
//             ]);

    //             if (!$respKardex || (isset($respKardex['ok']) && !$respKardex['ok'])) {
//                 $this->db->rollBack();
//                 return [
//                     'ok'      => false,
//                     'message' => 'Error al registrar en el kardex',
//                     'details' => $respKardex
//                 ];
//             }

    //             $totalActualizadas += $afectadas;
//         }

    //         $this->db->commit();

    //         return [
//             'ok'            => true,
//             'message'       => 'Stock repuesto',
//             'rows_updated'  => $totalActualizadas,
//             'no_updates'    => $noActualizadas,
//             'items'         => $items, // ← aquí vienen los nuevos stocks por id_pres
//         ];

    //     } catch (\Throwable $th) {
//         if ($this->db->inTransaction()) {
//             $this->db->rollBack();
//         }
//         throw $th;
//     }
// }


    //     public function reponerStock(int $id_pedido, bool $esVenta = false): array
// {
//     try {
//         $this->db->beginTransaction();

    //         // 1) Detalles del pedido (solo activos)
//         $sqlDetalles = "
//             SELECT
//                 dp.id_pedido,
//                 dp.id_pres,
//                 dp.cant,
//                 dp.precio,
//                 pp.crt_stock,      -- 1 = controla stock
//                 pp.receta,         -- 1 = usa insumos (receta)
//                 pp.stock AS stock_actual
//             FROM tm_detalle_pedido dp
//             JOIN tm_producto_pres pp ON pp.id_pres = dp.id_pres
//             WHERE dp.id_pedido = :id_pedido
//               AND dp.estado = 'a'
//         ";
//         $stDet = $this->db->prepare($sqlDetalles);
//         $stDet->execute([':id_pedido' => $id_pedido]);
//         $detalles = $stDet->fetchAll(\PDO::FETCH_ASSOC);

    //         if (!$detalles) {
//             $this->db->commit();
//             return [
//                 'ok'               => false,
//                 'msg'              => 'Venta sin detalles',
//                 'modo'             => $esVenta ? 'venta' : 'anulacion',
//                 'items_pres'       => [],
//                 'items_insumos'    => [],
//                 'no_updates'       => [],
//             ];
//         }

    //         // 2) Statements preparados
//         $updPres = $this->db->prepare("
//             UPDATE tm_producto_pres
//                SET stock = stock + CAST(:cantidad AS DECIMAL(12,3))
//              WHERE id_pres = :id
//                AND estado = 'a'
//              LIMIT 1
//         ");
//         $selPres = $this->db->prepare("
//             SELECT stock FROM tm_producto_pres
//              WHERE id_pres = :id
//              LIMIT 1
//         ");

    //         $selIngr = $this->db->prepare("
//             SELECT pi.id_ins, pi.cant, i.cos_uni AS costo_unitario
//               FROM tm_producto_ingr pi
//               JOIN tm_insumo i ON i.id_ins = pi.id_ins
//              WHERE pi.id_pres = :id_pres
//         ");
//         $updIns = $this->db->prepare("
//             UPDATE tm_insumo
//                SET stock = stock + CAST(:delta AS DECIMAL(12,3))
//              WHERE id_ins = :id_ins
//              LIMIT 1
//         ");
//         $selIns = $this->db->prepare("
//             SELECT stock FROM tm_insumo
//              WHERE id_ins = :id_ins
//              LIMIT 1
//         ");

    //         $totalPresAct   = 0;
//         $totalInsAct    = 0;
//         $noActualizadas = [];
//         $itemsPres      = [];
//         $itemsInsumos   = [];

    //         // 3) Iterar detalles
//         foreach ($detalles as $d) {
//             $idPres       = (int)$d['id_pres'];
//             $cantidadRaw  = (float)$d['cant'];
//             $controlStock = (int)$d['crt_stock'];
//             $receta       = (int)$d['receta'];
//             $precioVenta  = (float)$d['precio'];

    //             if ($controlStock !== 1) continue;
//             if ($cantidadRaw <= 0) {
//                 $noActualizadas[] = ['id_pres' => $idPres, 'motivo' => 'cantidad<=0'];
//                 continue;
//             }

    //             $cantidad = sprintf('%.3f', $cantidadRaw);

    //             // MODO VENTA: SIEMPRE repone PRESENTACIÓN (producto), IGNORA insumos
//             if ($esVenta) {
//                 $ok = $updPres->execute([
//                     ':cantidad' => $cantidad,
//                     ':id'       => $idPres,
//                 ]);
//                 if (!$ok || $updPres->rowCount() === 0) {
//                     $noActualizadas[] = [
//                         'id_pres' => $idPres,
//                         'motivo'  => $ok ? 'sin filas afectadas' : 'execute-failed',
//                         'error'   => $ok ? null : $updPres->errorInfo(),
//                     ];
//                     continue;
//                 }

    //                 $selPres->execute([':id' => $idPres]);
//                 $nuevoStock = $selPres->fetchColumn();
//                 $selPres->closeCursor();

    //                 $itemsPres[] = [
//                     'id_pres'              => $idPres,
//                     'cantidad_devuelta'    => (float)$cantidadRaw,
//                     'nuevo_stock'          => $nuevoStock !== false ? (float)$nuevoStock : null,
//                 ];

    //                 // Kárdex para presentación (tipo_presentacion = 1)
//                 $respKardex = $this->registrarKardex([
//                     'tipo_kardex'        => 5, // Reposición por anulación / devolución
//                     'presentacion_id'    => $idPres,
//                     'tipo_presentacion'  => 1, // 1 = presentación
//                     'cantidad'           => (float)$cantidadRaw,
//                     // ⚠ Idealmente usa costo contable (último/promedio) en vez de precio de venta
//                     'costo_unitario'     => $precioVenta,
//                     'costo_total'        => $precioVenta * $cantidadRaw,
//                     'serie_documento'    => null,
//                     'numero_documento'   => null,
//                     'tipo_documento'     => null,
//                     'stock'              => $nuevoStock !== false ? (float)$nuevoStock : null,
//                 ]);
//                 if (!$respKardex || (isset($respKardex['ok']) && !$respKardex['ok'])) {
//                     $this->db->rollBack();
//                     return [
//                         'ok'      => false,
//                         'message' => 'Error al registrar Kárdex de presentaciones (venta)',
//                         'details' => $respKardex,
//                     ];
//                 }

    //                 $totalPresAct += 1;
//                 continue;
//             }

    //             // MODO ANULACIÓN/DEVOLUCIÓN ESTÁNDAR:
//             // receta=0 → repone PRESENTACIÓN; receta=1 → repone INSUMOS
//             if ($receta === 0) {
//                 $ok = $updPres->execute([
//                     ':cantidad' => $cantidad,
//                     ':id'       => $idPres,
//                 ]);
//                 if (!$ok || $updPres->rowCount() === 0) {
//                     $noActualizadas[] = [
//                         'id_pres' => $idPres,
//                         'motivo'  => $ok ? 'sin filas afectadas' : 'execute-failed',
//                         'error'   => $ok ? null : $updPres->errorInfo(),
//                     ];
//                     continue;
//                 }

    //                 $selPres->execute([':id' => $idPres]);
//                 $nuevoStock = $selPres->fetchColumn();
//                 $selPres->closeCursor();

    //                 $itemsPres[] = [
//                     'id_pres'              => $idPres,
//                     'cantidad_devuelta'    => (float)$cantidadRaw,
//                     'nuevo_stock'          => $nuevoStock !== false ? (float)$nuevoStock : null,
//                 ];

    //                 $respKardex = $this->registrarKardex([
//                     'tipo_kardex'        => 5,
//                     'presentacion_id'    => $idPres,
//                     'tipo_presentacion'  => 1,
//                     'cantidad'           => (float)$cantidadRaw,
//                     'costo_unitario'     => $precioVenta, // ⚠ ver nota de costo
//                     'costo_total'        => $precioVenta * $cantidadRaw,
//                     'serie_documento'    => null,
//                     'numero_documento'   => null,
//                     'tipo_documento'     => null,
//                     'stock'              => $nuevoStock !== false ? (float)$nuevoStock : null,
//                 ]);
//                 if (!$respKardex || (isset($respKardex['ok']) && !$respKardex['ok'])) {
//                     $this->db->rollBack();
//                     return [
//                         'ok'      => false,
//                         'message' => 'Error al registrar Kárdex de presentaciones',
//                         'details' => $respKardex,
//                     ];
//                 }

    //                 $totalPresAct += 1;
//                 continue;
//             }

    //             // receta === 1 → repone INSUMOS (solo si NO es venta)
//             $selIngr->execute([':id_pres' => $idPres]);
//             $ingreds = $selIngr->fetchAll(\PDO::FETCH_ASSOC);
//             $selIngr->closeCursor();

    //             if (!$ingreds) {
//                 $noActualizadas[] = ['id_pres' => $idPres, 'motivo' => 'receta-sin-insumos'];
//                 continue;
//             }

    //             foreach ($ingreds as $ing) {
//                 $idIns           = (int)$ing['id_ins'];
//                 $ratioIns        = (float)$ing['cant'];
//                 $costoUnitInsumo = (float)$ing['costo_unitario'];

    //                 $deltaInsStr   = sprintf('%.3f', $ratioIns * $cantidadRaw);
//                 $deltaInsFloat = (float)$deltaInsStr;
//                 if ($deltaInsFloat == 0.0) continue;

    //                 $ok = $updIns->execute([
//                     ':delta'  => $deltaInsStr,
//                     ':id_ins' => $idIns,
//                 ]);
//                 if (!$ok || $updIns->rowCount() === 0) {
//                     $noActualizadas[] = [
//                         'id_pres' => $idPres,
//                         'id_ins'  => $idIns,
//                         'motivo'  => $ok ? 'sin filas afectadas (insumo)' : 'upd-insumo-failed',
//                         'error'   => $ok ? null : $updIns->errorInfo(),
//                     ];
//                     continue;
//                 }

    //                 $selIns->execute([':id_ins' => $idIns]);
//                 $nuevoStockIns = $selIns->fetchColumn();
//                 $selIns->closeCursor();

    //                 $itemsInsumos[] = [
//                     'id_pres'                  => $idPres,
//                     'id_ins'                   => $idIns,
//                     'ratio_ins_por_pres'       => $ratioIns,
//                     'cantidad_devuelta_pres'   => (float)$cantidadRaw,
//                     'incremento_insumo'        => $deltaInsFloat,
//                     'nuevo_stock_insumo'       => $nuevoStockIns !== false ? (float)$nuevoStockIns : null,
//                 ];

    //                 $respKardexIns = $this->registrarKardex([
//                     'tipo_kardex'        => 5,
//                     'presentacion_id'    => $idIns,
//                     'tipo_presentacion'  => 2, // 2 = insumo
//                     'cantidad'           => $deltaInsFloat,
//                     'costo_unitario'     => $costoUnitInsumo,
//                     'costo_total'        => $costoUnitInsumo * $deltaInsFloat,
//                     'serie_documento'    => null,
//                     'numero_documento'   => null,
//                     'tipo_documento'     => null,
//                     'stock'              => $nuevoStockIns !== false ? (float)$nuevoStockIns : null,
//                 ]);
//                 if (!$respKardexIns || (isset($respKardexIns['ok']) && !$respKardexIns['ok'])) {
//                     $this->db->rollBack();
//                     return [
//                         'ok'      => false,
//                         'message' => 'Error al registrar Kárdex de insumos',
//                         'details' => $respKardexIns,
//                     ];
//                 }

    //                 $totalInsAct += 1;
//             }
//         }

    //         $this->db->commit();

    //         return [
//             'ok'                    => true,
//             'modo'                  => $esVenta ? 'venta' : 'anulacion',
//             'message'               => 'Stock repuesto',
//             'rows_updated_pres'     => $totalPresAct,
//             'rows_updated_insumos'  => $totalInsAct,
//             'no_updates'            => $noActualizadas,
//             'items_pres'            => $itemsPres,
//             'items_insumos'         => $itemsInsumos, // vacío en modo venta
//         ];

    //     } catch (\Throwable $th) {
//         if ($this->db->inTransaction()) {
//             $this->db->rollBack();
//         }
//         throw $th;
//     }
// }

    public function reponerStock(int $id_pedido, bool $esVenta = false): array
    {
        try {
            $this->db->beginTransaction();

            // 1) Detalles del pedido (solo activos)
            $stDet = $this->db->prepare("
            SELECT
                dp.id_pedido,
                dp.id_pres,
                dp.cant,
                dp.precio,
                pp.crt_stock,      -- 1 = controla stock
                pp.receta,         -- 1 = usa insumos (receta)
                pp.stock AS stock_actual
            FROM tm_detalle_pedido dp
            JOIN tm_producto_pres pp ON pp.id_pres = dp.id_pres
            WHERE dp.id_pedido = :id_pedido
              AND dp.estado = 'a'
        ");
            $stDet->execute([':id_pedido' => $id_pedido]);
            $detalles = $stDet->fetchAll(\PDO::FETCH_ASSOC);

            if (!$detalles) {
                $this->db->commit();
                return [
                    'ok' => false,
                    'msg' => 'Venta sin detalles',
                    'modo' => $esVenta ? 'venta' : 'anulacion',
                    'items_pres' => [],
                    'items_insumos' => [],
                    'no_updates' => [],
                ];
            }

            // 2) Statements preparados
            $updPres = $this->db->prepare("
            UPDATE tm_producto_pres
               SET stock = stock + CAST(:cantidad AS DECIMAL(12,3))
             WHERE id_pres = :id
               AND estado = 'a'
             LIMIT 1
        ");
            $selPres = $this->db->prepare("
            SELECT stock FROM tm_producto_pres
             WHERE id_pres = :id
             LIMIT 1
        ");

            $selIngr = $this->db->prepare("
            SELECT pi.id_ins, pi.cant, i.cos_uni AS costo_unitario
              FROM tm_producto_ingr pi
              JOIN tm_insumo i ON i.id_ins = pi.id_ins
             WHERE pi.id_pres = :id_pres
        ");
            $updIns = $this->db->prepare("
            UPDATE tm_insumo
               SET stock = stock + CAST(:delta AS DECIMAL(12,3))
             WHERE id_ins = :id_ins
             LIMIT 1
        ");
            $selIns = $this->db->prepare("
            SELECT stock FROM tm_insumo
             WHERE id_ins = :id_ins
             LIMIT 1
        ");

            $totalPresAct = 0;
            $totalInsAct = 0;
            $noActualizadas = [];
            $itemsPres = [];
            $itemsInsumos = [];

            // 3) Iterar cada detalle
            foreach ($detalles as $d) {
                $idPres = (int) $d['id_pres'];
                $cantidadRaw = (float) $d['cant'];
                $crtStock = (int) $d['crt_stock'];
                $receta = (int) $d['receta'];
                $precioVenta = (float) $d['precio'];

                // Solo trabajamos con control de stock
                if ($crtStock !== 1) {
                    $noActualizadas[] = ['id_pres' => $idPres, 'motivo' => 'crt_stock!=1'];
                    continue;
                }
                if ($cantidadRaw <= 0) {
                    $noActualizadas[] = ['id_pres' => $idPres, 'motivo' => 'cantidad<=0'];
                    continue;
                }

                $cantidad = sprintf('%.3f', $cantidadRaw);

                /* === Regla 1: receta = 0 ⇒ siempre repone PRESENTACIÓN === */
                if ($receta === 0) {
                    $ok = $updPres->execute([
                        ':cantidad' => $cantidad,
                        ':id' => $idPres,
                    ]);
                    if (!$ok || $updPres->rowCount() === 0) {
                        $noActualizadas[] = [
                            'id_pres' => $idPres,
                            'motivo' => $ok ? 'sin filas afectadas' : 'execute-failed',
                            'error' => $ok ? null : $updPres->errorInfo(),
                        ];
                        continue;
                    }

                    // Leer stock actualizado
                    $selPres->execute([':id' => $idPres]);
                    $nuevoStock = $selPres->fetchColumn();
                    $selPres->closeCursor();

                    $itemsPres[] = [
                        'id_pres' => $idPres,
                        'cantidad_devuelta' => (float) $cantidadRaw,
                        'nuevo_stock' => $nuevoStock !== false ? (float) $nuevoStock : null,
                    ];

                    // Kárdex presentación (tipo_presentacion = 1)
                    $respKardex = $this->registrarKardex([
                        'tipo_kardex' => 5, // Reposición por anulación / devolución
                        'presentacion_id' => $idPres,
                        'tipo_presentacion' => 1, // 1 = presentación
                        'cantidad' => (float) $cantidadRaw,
                        // ⚠ Idealmente usa costo contable (promedio/último), no precio de venta
                        'costo_unitario' => $precioVenta,
                        'costo_total' => $precioVenta * $cantidadRaw,
                        'serie_documento' => null,
                        'numero_documento' => null,
                        'tipo_documento' => null,
                        'stock' => $nuevoStock !== false ? (float) $nuevoStock : null,
                    ]);
                    if (!$respKardex || (isset($respKardex['ok']) && !$respKardex['ok'])) {
                        $this->db->rollBack();
                        return [
                            'ok' => false,
                            'message' => 'Error al registrar Kárdex de presentaciones',
                            'details' => $respKardex,
                        ];
                    }

                    $totalPresAct += 1;
                    continue;
                }

                /* === Regla 2: receta = 1 ===
                   - Venta (esVenta=true): NO reponer nada
                   - Anulación/Devolución (esVenta=false): reponer SOLO INSUMOS
                */
                if ($receta === 1) {
                    if ($esVenta) {
                        $noActualizadas[] = [
                            'id_pres' => $idPres,
                            'motivo' => 'venta: receta=1 (no se repone presentacion ni insumos)'
                        ];
                        continue;
                    }

                    // esVenta = false ⇒ reponer insumos por receta
                    $selIngr->execute([':id_pres' => $idPres]);
                    $ingreds = $selIngr->fetchAll(\PDO::FETCH_ASSOC);
                    $selIngr->closeCursor();

                    if (!$ingreds) {
                        $noActualizadas[] = ['id_pres' => $idPres, 'motivo' => 'receta-sin-insumos'];
                        continue;
                    }

                    foreach ($ingreds as $ing) {
                        $idIns = (int) $ing['id_ins'];
                        $ratioIns = (float) $ing['cant'];           // insumo por 1 unidad
                        $costoUnitInsumo = (float) $ing['costo_unitario'];

                        $deltaInsStr = sprintf('%.3f', $ratioIns * $cantidadRaw);
                        $deltaInsFloat = (float) $deltaInsStr;
                        if ($deltaInsFloat == 0.0)
                            continue;

                        $ok = $updIns->execute([
                            ':delta' => $deltaInsStr,
                            ':id_ins' => $idIns,
                        ]);
                        if (!$ok || $updIns->rowCount() === 0) {
                            $noActualizadas[] = [
                                'id_pres' => $idPres,
                                'id_ins' => $idIns,
                                'motivo' => $ok ? 'sin filas afectadas (insumo)' : 'upd-insumo-failed',
                                'error' => $ok ? null : $updIns->errorInfo(),
                            ];
                            continue;
                        }

                        // Leer stock actualizado del insumo
                        $selIns->execute([':id_ins' => $idIns]);
                        $nuevoStockIns = $selIns->fetchColumn();
                        $selIns->closeCursor();

                        $itemsInsumos[] = [
                            'id_pres' => $idPres,
                            'id_ins' => $idIns,
                            'ratio_ins_por_pres' => $ratioIns,
                            'cantidad_devuelta_pres' => (float) $cantidadRaw,
                            'incremento_insumo' => $deltaInsFloat,
                            'nuevo_stock_insumo' => $nuevoStockIns !== false ? (float) $nuevoStockIns : null,
                        ];

                        // Kárdex para insumo (tipo_presentacion = 2)
                        $respKardexIns = $this->registrarKardex([
                            'tipo_kardex' => 5,
                            'presentacion_id' => $idIns,
                            'tipo_presentacion' => 2, // 2 = insumo
                            'cantidad' => $deltaInsFloat,
                            'costo_unitario' => $costoUnitInsumo,
                            'costo_total' => $costoUnitInsumo * $deltaInsFloat,
                            'serie_documento' => null,
                            'numero_documento' => null,
                            'tipo_documento' => null,
                            'stock' => $nuevoStockIns !== false ? (float) $nuevoStockIns : null,
                        ]);
                        if (!$respKardexIns || (isset($respKardexIns['ok']) && !$respKardexIns['ok'])) {
                            $this->db->rollBack();
                            return [
                                'ok' => false,
                                'message' => 'Error al registrar Kárdex de insumos',
                                'details' => $respKardexIns,
                            ];
                        }

                        $totalInsAct += 1;
                    }
                }
            }

            $this->db->commit();

            return [
                'ok' => true,
                'modo' => $esVenta ? 'venta' : 'anulacion',
                'message' => 'Stock repuesto',
                'rows_updated_pres' => $totalPresAct,
                'rows_updated_insumos' => $totalInsAct,           // 0 en modo venta
                'no_updates' => $noActualizadas,
                'items_pres' => $itemsPres,
                'items_insumos' => $itemsInsumos,          // vacío en modo venta
            ];

        } catch (\Throwable $th) {
            if ($this->db->inTransaction()) {
                $this->db->rollBack();
            }
            throw $th;
        }
    }





    public function registrarKardex(array $data): array
    {

        $concepto = 'Ingreso por anulacion de venta';

        // 2.2 Construyes el payload para kardex
        $payloadKardex = [
            'id_usuario' => Session::get('usuid'),
            'tipo_kardex' => $data['tipo_kardex'],
            'tipo_envio_sunat' => 'prueba',
            'id_presentacion' => (int) $data['presentacion_id'],
            'tipo_presentacion' => (int) $data['tipo_presentacion'],
            'cantidad_entrada' => $data['cantidad'],
            'cantidad_salida' => 0,
            'costo_unitario' => (float) $data['costo_unitario'],
            'costo_total' => $data['costo_total'],
            'concepto' => $concepto,
            'serie_documento' => $data['serie_documento'] ?? null,
            'numero_documento' => $data['numero_documento'] ?? null,
            'tipo_documento' => $data['tipo_documento'] ?? null,
            'stock' => $data['stock'] ?? '0',
            'estado' => 'activo',
        ];

        $kardex = new Kardex(); // comparte el mismo PDO
        $out = $kardex->insert($payloadKardex);
        if (!$out['ok']) {

            return $out; // envelope con error (VALIDATION_ERROR / DB_ERROR, etc.)
        }

        return ['ok' => true];
    }



}