# Archivo: RecepcionCotiz.py
# Ruta: src\App\Ventas_Module\Cotiz\RecepcionCotiz.py

from flask import current_app, render_template, request, session, jsonify, redirect, url_for
from functools import wraps
import logging, json
from datetime import datetime
from typing import Dict, List
import config

import requests

# Importar funciones SQL específicas para recepción de cotizaciones
from App.Utilities_module.CRMManagement import CRMManager
from Consultas_SQL.Ventas.Cotiz.RecepcionCotizSQL import (
    buscar_cotizacion_completa,
    actualizar_lineas_cotizacion,
    actualizar_calculo_financiero,
    actualizar_tiempo_y_condiciones,
    finalizar_cotizacion_y_enviar,
    get_Quote_JSON_BussinessCentral,
    json_serializer,
    get_TermsType_JSON
)

from Consultas_SQL.Ventas.Cotiz.RecepcionCotizSQL import RecepcionCotizSQL
# Importar utilidades
from App.Utilities_module.MailManagement import enviar_correo_universal
import config

logger = logging.getLogger('recepcion_cotizacion')

def registrar_rutas_recepcion_cotizacion(app, mail): # Nombre más claro
    _BASE_ROUTE = "/Ventas/Cotiz/Recepcion"

    """
    Función principal que registra todas las rutas del módulo de recepción de cotizaciones.
    """


    """
    /ventas/cotiz/reception/costing/<int:costing_num>
    """
    @app.route(f'{_BASE_ROUTE}/costing/<int:costing_num>', methods=['GET'])
    def get_reception_costing_controller(costing_num: int):
        """
        Controlador para obtener datos unificados de la cotización (Costeo y CRM).
        Ruta final: /api/reception/costing/<int:costing_num>
        """
        try:
            # Llama al nuevo método del servicio unificado
            data_dto = RecepcionCotizSQL.get_unified_data_by_costing_num(costing_num)
            
            if data_dto:
                return jsonify(data_dto.to_dict())
            else:
                # Retorna 404 si la cotización no se encuentra
                return jsonify({"error": f"Cotización con número {costing_num} no encontrada"}), 404
                
        except Exception as e:
            return jsonify({"error": "Ocurrió un error interno en el servidor"}), 500
    
    """
    /ventas/cotiz/reception/taxes
    """
    @app.route(f'{_BASE_ROUTE}/taxes', methods=['GET'])
    def get_taxes_controller():
        """
        Controlador para obtener la lista de impuestos.
        Ruta final: /api/reception/taxes
        """
        try:
            taxes = RecepcionCotizSQL.get_taxes()
            return jsonify(taxes)
        except Exception as e:
            return jsonify({"error": "Ocurrió un error interno en el servidor"}), 500

    """
    /ventas/cotiz/reception/currencies
    """
    @app.route(f'{_BASE_ROUTE}/currencies', methods=['GET'])
    def get_currencies_controller():
        """
        Controlador para obtener la lista de monedas.
        Ruta final: /api/reception/currencies
        """
        try:
            currencies = RecepcionCotizSQL.get_currencies()
            return jsonify(currencies)
        except Exception as e:
            print(f"Error en el controlador quote_reception/currencies: {e}")
            return jsonify({"error": "Ocurrió un error interno en el servidor"}), 500
    
    """
    /ventas/cotiz/reception/costing-details/<int:costing_num>
    """
    @app.route(f'{_BASE_ROUTE}/costing-details/<int:costing_num>', methods=['GET'])
    def get_costing_details_controller(costing_num: int):
        """
        Controlador para obtener las líneas de costeo.
        Ruta final: /api/reception/costing-details/<int:costing_num>
        """
        try:
            details = RecepcionCotizSQL.get_costing_details(costing_num)
            return jsonify(details)
        except Exception as e:
            print(f"Error en el controlador quote_reception/costing-details: {e}")
            return jsonify({"error": "Ocurrió un error interno en el servidor"}), 500
    
    """
    /ventas/cotiz/reception/create-quotation
    """
    @app.route(f'{_BASE_ROUTE}/create-quotation', methods=['POST'])
    def create_quotation_controller():
        """
        Controlador para crear una nueva cotización.
        Ruta final: /api/reception/create-quotation
        """
        try:
            data = request.get_json()
            result = RecepcionCotizSQL.create_quotation(data)
            
            if result.get('success'):
                return jsonify(result), 201
            else:
                return jsonify(result), 400
                
        except Exception as e:
            print(f"Error en el controlador create-quotation: {e}")
            return jsonify({"success": False, "error": "Ocurrió un error interno en el servidor"}), 500
        
    """"
    /ventas/cotiz/reception/preview-quotation
    """
    @app.route(f'{_BASE_ROUTE}/preview-quotation', methods=['POST', 'OPTIONS'])
    def preview_quotation_controller():
        """
        Controlador para generar vista previa de la cotización antes de crearla.
        Ruta final: /api/reception/preview-quotation
        """
        # Manejar preflight request (CORS)
        if request.method == 'OPTIONS':
            response = jsonify({'status': 'ok'})
            response.headers.add('Access-Control-Allow-Origin', '*')
            response.headers.add('Access-Control-Allow-Headers', 'Content-Type,X-Auth-Token')
            response.headers.add('Access-Control-Allow-Methods', 'POST')
            return response, 200
        
        try:
            # Obtener datos
            data = request.get_json()
            
            # ✅ NUEVO: Si no vienen datos completos o solo viene el token, cargar desde archivo temporal
            if not data or len(data) <= 1:  # Solo tiene token o está vacío
                print("⚠️ Datos incompletos recibidos, intentando cargar desde archivo temporal...")
                
                token = None
                if data:
                    token = data.get('token') or request.headers.get('X-Auth-Token')
                else:
                    token = request.headers.get('X-Auth-Token')
                
                if token:
                    # Buscar archivo temporal
                    import os
                    import json
                    from flask import current_app
                    
                    output_dir = os.path.join(current_app.root_path, "static", "pdf", "Ventas", "Cotiz")
                    temp_data_file = os.path.join(output_dir, f"temp_data_{token}.json")
                    
                    print(f"🔍 Buscando archivo: {temp_data_file}")
                    
                    if os.path.exists(temp_data_file):
                        with open(temp_data_file, 'r', encoding='utf-8') as f:
                            data = json.load(f)
                        print(f"✅ Datos cargados desde archivo temporal")
                        print(f"📦 Datos cargados: {list(data.keys())}")
                        
                        # Limpiar archivo temporal después de leerlo
                        try:
                            os.remove(temp_data_file)
                            print(f"🗑️ Archivo temporal eliminado")
                        except Exception as e:
                            print(f"⚠️ No se pudo eliminar archivo temporal: {e}")
                    else:
                        print(f"❌ Archivo temporal no encontrado: {temp_data_file}")
                        error_html = f"""
                        <!DOCTYPE html>
                        <html>
                        <body>
                            <h1>❌ Error: No se encontraron datos de cotización</h1>
                            <p>Token: {token}</p>
                            <p>Archivo buscado: {temp_data_file}</p>
                        </body>
                        </html>
                        """
                        return error_html, 400, {'Content-Type': 'text/html; charset=utf-8'}
                else:
                    error_html = """
                    <!DOCTYPE html>
                    <html>
                    <body>
                        <h1>❌ Error: No se recibió token</h1>
                    </body>
                    </html>
                    """
                    return error_html, 400, {'Content-Type': 'text/html; charset=utf-8'}
            
            # Si llegamos aquí, data tiene contenido
            if not data:
                error_html = """
                <!DOCTYPE html>
                <html>
                <body>
                    <h1>❌ Error: No se pudieron cargar los datos</h1>
                </body>
                </html>
                """
                return error_html, 400, {'Content-Type': 'text/html; charset=utf-8'}
            
            # Verificar token
            token = data.get('token') or request.headers.get('X-Auth-Token')
            
            print(f"📋 Generando vista previa para cotización")
            print(f"🎫 Token recibido: {token}")
            print(f"📦 Datos recibidos: {list(data.keys())}")
            
            # ✅ IMPORTANTE: Si solo viene el token, necesitamos obtener los datos completos
            # desde el JavaScript que hace la llamada desde el botón "Confirmar"
            
            # Llamar al servicio para generar la vista previa
            preview_html = RecepcionCotizSQL.generate_quotation_preview(data)
            
            if preview_html:
                print(f"✅ HTML generado: {len(preview_html)} caracteres")
                return preview_html, 200, {'Content-Type': 'text/html; charset=utf-8'}
            else:
                error_html = """
                <!DOCTYPE html>
                <html>
                <body>
                    <h1>❌ No se pudo generar la vista previa</h1>
                    <p>El servicio no retornó contenido HTML</p>
                </body>
                </html>
                """
                return error_html, 500, {'Content-Type': 'text/html; charset=utf-8'}
                
        except Exception as e:
            print(f"❌ Error en preview-quotation: {e}")
            import traceback
            traceback.print_exc()
            
            error_html = f"""
            <!DOCTYPE html>
            <html>
            <head>
                <title>Error</title>
                <style>
                    body {{
                        font-family: Arial, sans-serif;
                        padding: 40px;
                        background-color: #f5f5f5;
                    }}
                    .error {{
                        background-color: #fee;
                        border: 2px solid #f00;
                        padding: 20px;
                        border-radius: 8px;
                        max-width: 800px;
                        margin: 0 auto;
                    }}
                    pre {{
                        background-color: #333;
                        color: #fff;
                        padding: 15px;
                        overflow-x: auto;
                        border-radius: 4px;
                    }}
                </style>
            </head>
            <body>
                <div class="error">
                    <h1>❌ Error al generar vista previa</h1>
                    <p><strong>Mensaje:</strong> {str(e)}</p>
                    <pre>{traceback.format_exc()}</pre>
                </div>
            </body>
            </html>
            """
            return error_html, 500, {'Content-Type': 'text/html; charset=utf-8'}

    """
    /ventas/cotiz/reception/generate-pdf/<quote_id>
    """
    @app.route(f'{_BASE_ROUTE}/generate-pdf/<quote_id>', methods=['POST'])
    def generate_quotation_pdf_controller(quote_id):
        """
        Genera el PDF de la cotización, lo sube a FTP en estructura de carpetas y envía por email
        Ruta final: /api/reception/generate-pdf/<quote_id>
        """
        try:
            import os
            import subprocess
            import uuid
            import shutil
            import ftplib
            import json
            from flask import current_app, request
            from werkzeug.datastructures import FileStorage
            from App.Subir_Archivo import subir_archivo_ftp_desde_request
            from App.Utilities_module.MailManagement import enviar_correo_universal
            from datetime import datetime
            from Consultas_SQL.conexion import get_connection
            
            print("=" * 80)
            print("🚀 INICIANDO GENERACIÓN DE PDF CON EMAIL")
            print("=" * 80)
            
            # Obtener datos de la cotización
            data = request.get_json()
            
            # data que obtenemos.
            print("🧠 Datos recibidos para genera PDF")
            print(json.dumps(data, indent=2, ensure_ascii=False))
            
            # === Correo del usuario que creó la solicitud ===
            
            user_email = None
            
            try:
                user_id = session.get('user_id')          # del login
                session_email = session.get('email')      # fallback si no está en Profiles

                if user_id:
                    from Consultas_SQL.conexion import get_connection
                    with get_connection() as conn:
                        with conn.cursor() as cursor:
                            # INTENTO A: si Profiles tiene columna UserID
                            cursor.execute("SELECT Email FROM Profiles WHERE UserID = ?", user_id)
                            row = cursor.fetchone()

                            if not row or not row[0]:
                                # INTENTO B: si Profiles se liga vía ProfileID en Users
                                cursor.execute("""
                                    SELECT P.Email
                                    FROM Users U
                                    JOIN Profiles P ON P.ProfileID = U.ProfileID
                                    WHERE U.UserID = ?
                                """, user_id)
                                row = cursor.fetchone()

                    if row and row[0]:
                        user_email = row[0]

                # Si no encontramos en Profiles, usa el email de la sesión como último recurso
                if not user_email and session_email:
                    user_email = session_email

                print(f"📧 Email del creador detectado: {user_email or 'No disponible'}")

            except Exception as e:
                print(f"⚠️ No se pudo resolver el email del creador: {e}")
                    
            
            # Generar token único
            token = str(uuid.uuid4())
            
            # Construir URL de vista previa
            base_url = request.host_url.rstrip('/')
            preview_url = f"{base_url}/Ventas/Cotiz/Recepcion/preview-quotation"
            
            # Generar token único
            token = str(uuid.uuid4())

            # Construir URL de vista previa
            base_url = request.host_url.rstrip('/')
            preview_url = f"{base_url}/Ventas/Cotiz/Recepcion/preview-quotation"
            
            # Ruta temporal para el PDF
            base_dir = current_app.root_path
            output_dir = os.path.join(base_dir, "static", "pdf", "Ventas", "Cotiz")
            os.makedirs(output_dir, exist_ok=True)

            # ✅ NUEVO: Guardar datos temporalmente para que Puppeteer pueda accederlos
            import json
            temp_data_file = os.path.join(output_dir, f"temp_data_{token}.json")
            with open(temp_data_file, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
            print(f"💾 Datos temporales guardados: {temp_data_file}")
            

            
            # Obtener número de costeo BASE (sin versión)
            costing_num = data.get('CostingID', quote_id).split('-')[0]

            # ===== PASO 1: OBTENER VERSIÓN DESDE LA BASE DE DATOS =====
            print(f"\n📊 Obteniendo versión de cotización para: {costing_num}")
            try:
                with get_connection() as conn:
                    with conn.cursor() as cursor:
                        cursor.execute("""
                            SELECT MAX(Version) FROM Q_QuotationHead WHERE QuotationNum = ?
                        """, costing_num)
                        version_result = cursor.fetchone()
                        version = 1 if not version_result[0] else version_result[0] + 1
                
                print(f"📊 Versión calculada desde BD: {version}")
                
            except Exception as e:
                print(f"⚠️ Error al obtener versión, usando versión 1: {str(e)}")
                version = 1

            # ===== PASO 2: CONSTRUIR EL QUOTE_ID CORRECTO CON LA VERSIÓN REAL =====
            # ✅ ESTO GARANTIZA QUE LA CARPETA Y EL PDF TENGAN LA MISMA VERSIÓN
            quote_id_correcto = f"{costing_num}-{version}"
            print(f"✅ QuoteID construido: {quote_id_correcto}")

            # Variables para la estructura de carpetas
            costing_num_base = str(costing_num)      # Ejemplo: "1021"
            quote_id_folder = quote_id_correcto      # Ejemplo: "1021-1" (con versión correcta)

            print(f"📁 Carpeta base: {costing_num_base}")
            print(f"📁 Carpeta versión: {quote_id_folder}")

            # Nombre del archivo PDF con versión
            output_filename = f"cotizacion_{costing_num}_V{version}_{token}.pdf"
            output_path = os.path.join(output_dir, output_filename)

            print(f"📄 Nombre del PDF: cotizacion_{costing_num}_V{version}.pdf")

            # ===== EJECUTAR PUPPETEER (sin cambios) =====
            # Ruta al script de Puppeteer
            script_path = os.path.join(
                base_dir, "puppeteer_pdf", "Ventas", "Cotiz", "PreviewCotizacion.js"
            )

            print(f"📂 Base dir: {base_dir}")
            print(f"📄 Script path: {script_path}")
            print(f"📄 Script exists: {os.path.exists(script_path)}")

            if not os.path.exists(script_path):
                error_msg = f"Script de Puppeteer no encontrado en: {script_path}"
                print(f"❌ {error_msg}")
                return jsonify({"success": False, "error": error_msg}), 500

            print(f"✅ Script encontrado: {script_path}")

            # Buscar Node.js
            print("\n🔍 Buscando Node.js...")
            node_path = shutil.which("node")

            if not node_path:
                print("⚠️ Node.js no encontrado con shutil.which, buscando manualmente...")
                possible_paths = [
                    r"C:\Program Files\nodejs\node.exe",
                    r"C:\Program Files (x86)\nodejs\node.exe",
                    os.path.expanduser(r"~\AppData\Roaming\npm\node.exe"),
                ]
                
                for path in possible_paths:
                    if os.path.exists(path):
                        node_path = path
                        print(f"  ✅ Encontrado: {path}")
                        break

            if not node_path or not os.path.exists(node_path):
                error_msg = "Node.js no encontrado. Instala Node.js desde https://nodejs.org"
                print(f"❌ {error_msg}")
                return jsonify({"success": False, "error": error_msg}), 500

            print(f"\n✅ Usando Node.js desde: {node_path}")
            print(f"\n🌐 URL de vista previa: {preview_url}")
            print(f"💾 Ruta de salida: {output_path}")
            print(f"🎫 Token: {token}")

            print("\n🚀 Ejecutando Puppeteer...")

            # Ejecutar Puppeteer
            process = subprocess.Popen(
                [node_path, script_path, preview_url, output_path, token],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                encoding='utf-8',
                errors='ignore',
                cwd=os.path.dirname(script_path)
            )

            try:
                stdout, stderr = process.communicate(timeout=90)
                return_code = process.returncode
                
                if stdout:
                    print(f"\n📋 Puppeteer stdout:\n{stdout}")
                
                if stderr:
                    print(f"\n⚠️ Puppeteer stderr:\n{stderr}")
                
                if return_code != 0:
                    error_msg = stderr if stderr else "Error desconocido al generar PDF"
                    print(f"\n❌ Puppeteer falló con código {return_code}")
                    return jsonify({"success": False, "error": f"Error al generar PDF: {error_msg}"}), 500
                
                if not os.path.exists(output_path):
                    print(f"❌ PDF no generado en: {output_path}")
                    return jsonify({"success": False, "error": "El PDF no fue generado correctamente"}), 500
                
                print(f"✅ PDF generado exitosamente: {output_path}")
                print(f"📦 Tamaño del archivo: {os.path.getsize(output_path)} bytes")
                
            except subprocess.TimeoutExpired:
                process.kill()
                print("❌ Timeout: Puppeteer tardó más de 90 segundos")
                return jsonify({"success": False, "error": "Timeout: La generación del PDF tardó demasiado"}), 500

            # ===== FASE 6: VERIFICACIÓN Y CREACIÓN DE ESTRUCTURA DE CARPETAS EN FTP =====
            print("\n🗂️ Verificando/Creando estructura de carpetas en FTP...")
            print(f"📊 Estructura a crear: /Cotizaciones_Generales/{costing_num_base}/{quote_id_folder}/")

            host_ftp = os.getenv('FTP_HOST')
            usuario_ftp = os.getenv('FTP_USER')
            contraseña_ftp = os.getenv('FTP_PASS')

            # ✅ ESTRUCTURA CORREGIDA: Usa el quote_id_folder construido con la versión correcta
            # Estructura: /file/Ventas/Cotizaciones_Generales/1021/1021-1/
            carpetas_estructura = ['file', 'Ventas', 'Cotizaciones_Generales', costing_num_base, quote_id_folder]

            print(f"📁 Carpetas a crear/verificar: {' -> '.join(carpetas_estructura)}")

            try:
                ftp = ftplib.FTP(host_ftp)
                ftp.login(usuario_ftp, contraseña_ftp)
                print("✅ Conectado a FTP")
                
                # Navegar a la ruta base
                current_path = '/domains/sycelephant.com/public_html'
                ftp.cwd(current_path)
                print(f"📂 Ruta inicial: {current_path}")
                
                # Bandera para saber cuándo empezar a crear carpetas
                puede_crear = False
                
                for carpeta in carpetas_estructura:
                    try:
                        # Intentar entrar a la carpeta
                        ftp.cwd(carpeta)
                        print(f"  ✅ Carpeta existente: {carpeta}")
                        
                        # Una vez que llegamos a "Ventas", activamos la creación
                        if carpeta == 'Ventas':
                            puede_crear = True
                            
                    except ftplib.error_perm:
                        # La carpeta NO existe
                        
                        if not puede_crear:
                            # AÚN NO hemos llegado a Ventas, NO podemos crear
                            error_msg = f"Error: La carpeta requerida '{carpeta}' no existe en el servidor FTP. Ruta: {ftp.pwd()}/{carpeta}"
                            print(f"  ❌ {error_msg}")
                            ftp.quit()
                            return jsonify({
                                "success": False,
                                "error": error_msg,
                                "carpeta_faltante": carpeta
                            }), 500
                        else:
                            # YA pasamos Ventas, SÍ podemos crear
                            print(f"  📁 Creando carpeta: {carpeta}")
                            try:
                                ftp.mkd(carpeta)
                                ftp.cwd(carpeta)
                                print(f"  ✅ Carpeta creada: {carpeta}")
                            except Exception as e:
                                error_msg = f"Error al crear carpeta '{carpeta}': {str(e)}"
                                print(f"  ❌ {error_msg}")
                                ftp.quit()
                                return jsonify({"success": False, "error": error_msg}), 500
                
                ruta_final_ftp = ftp.pwd()
                print(f"✅ Estructura de carpetas FTP verificada/creada exitosamente")
                print(f"📂 Ruta final en FTP: {ruta_final_ftp}")
                
                ftp.quit()
                
            except Exception as e:
                error_msg = f"Error al trabajar con FTP: {str(e)}"
                print(f"❌ {error_msg}")
                return jsonify({"success": False, "error": error_msg}), 500

            # ===== SUBIR ARCHIVO A FTP =====
            print("\n📤 Subiendo PDF a FTP...")

            # ✅ CAMBIO: Usar quote_id en lugar de V{version}
            ruta_ftp_completa = f"/domains/sycelephant.com/public_html/file/Ventas/Cotizaciones_Generales/{costing_num_base}/{quote_id_folder}/"
            carpeta_relativa = f"/Ventas/Cotizaciones_Generales/{costing_num_base}/{quote_id_folder}/"

            with open(output_path, 'rb') as f:
                pdf_file = FileStorage(
                    stream=f,
                    filename=f"cotizacion_{costing_num}_{version}.pdf",  # Nombre del archivo mantiene V{version}
                    name="QuotationPDF"
                )
                
                item = f"cotizacion_{costing_num}_V{version}"
                
                resultado_ftp = subir_archivo_ftp_desde_request(
                    pdf_file, 
                    item, 
                    ruta_ftp_completa, 
                    carpeta_relativa
                )
                
                if not resultado_ftp["exito"]:
                    print(f"❌ Error al subir a FTP: {resultado_ftp['mensaje']}")
                    print("⚠️ Intentando método alternativo sin estructura de carpetas...")
                    
                    # Método alternativo: subir a ruta base
                    ruta_ftp_simple = "/domains/sycelephant.com/public_html/file/Ventas/"
                    carpeta_simple = "/Ventas/"
                    
                    with open(output_path, 'rb') as f2:
                        pdf_file2 = FileStorage(
                            stream=f2,
                            filename=f"cotizacion_{costing_num}_{version}.pdf",
                            name="QuotationPDF"
                        )
                        
                        resultado_ftp = subir_archivo_ftp_desde_request(
                            pdf_file2, 
                            item, 
                            ruta_ftp_simple, 
                            carpeta_simple
                        )
                        
                        if not resultado_ftp["exito"]:
                            return jsonify({"success": False, "error": resultado_ftp["mensaje"]}), 400
                
                pdf_url = resultado_ftp["url_web"]
                print(f"✅ PDF subido exitosamente: {pdf_url}")
                    
            # ===== ENVIAR EMAIL =====
            print("\n📧 Preparando envío de email...")

            try:
                # ✅ Calcular valores numéricos ANTES de formatear
                precio_lista = float(data.get('SalePrice', 0))
                precio_oferta = float(data.get('Amount', 0))
                precio_total = float(data.get('TotalAmount', 0))
                descuento_porcentaje = float(data.get('DiscountPercent', 0))

                # ✅ Calcular montos derivados
                monto_descuento = precio_lista - precio_oferta
                monto_impuesto = precio_total - precio_oferta

                # ✅ Obtener información completa del impuesto
                impuesto_info = {}
                try:
                    with get_connection() as conn:
                        with conn.cursor() as cursor:
                            cursor.execute("""
                                SELECT TaxCode, FrontES, TaxAmount 
                                FROM Q_TaxRate 
                                WHERE TaxCode = ?
                            """, data.get('TaxCode'))
                            tax_row = cursor.fetchone()
                            
                            if tax_row:
                                impuesto_info = {
                                    'codigo': tax_row[0],
                                    'nombre': tax_row[1],
                                    'porcentaje': float(tax_row[2]) * 100  # 0.16 -> 16
                                }
                            else:
                                impuesto_info = {
                                    'codigo': data.get('TaxCode', 'N/A'),
                                    'nombre': data.get('TaxCode', 'N/A'),
                                    'porcentaje': 0
                                }
                except Exception as e:
                    print(f"⚠️ Error al obtener info del impuesto: {e}")
                    impuesto_info = {
                        'codigo': data.get('TaxCode', 'N/A'),
                        'nombre': data.get('TaxCode', 'N/A'),
                        'porcentaje': 0
                    }
                    
                print("\n👤 Obteniendo información del vendedor...")
                
                
                # ✅ OBTENER NOMBRE DE LA MONEDA (agregar ANTES de template_data)
                moneda_codigo = data.get('CurrencyCode', 'USD')
                moneda_nombre = moneda_codigo  # Por defecto

                try:
                    with get_connection() as conn:
                        with conn.cursor() as cursor:
                            cursor.execute("""
                                SELECT FrontES FROM Q_Currency WHERE CurrencyCode = ?
                            """, moneda_codigo)
                            moneda_result = cursor.fetchone()
                            if moneda_result and moneda_result[0]:
                                moneda_nombre = moneda_result[0]
                    
                    print(f"💱 Moneda para email: {moneda_codigo} -> {moneda_nombre}")
                except Exception as e:
                    print(f"⚠️ No se pudo obtener nombre de moneda: {e}")

                vendedor_info =  RecepcionCotizSQL.get_seller_info(data.get('CostingID'))
                vendedor_email = vendedor_info.get('email')
                vendedor_nombre = vendedor_info.get('nombre')
                vendedor_telefono = vendedor_info.get('telefono')

                print(f"✅ Vendedor encontrado: {vendedor_nombre}")
                print(f"✅ Email vendedor: {vendedor_email}")
                print(f"✅ Teléfono vendedor: {vendedor_telefono}")

                template_data = {
                    'quotation_id': quote_id,
                    'version': version,
                    'costing_id': data.get('CostingID'),
                    'caso_costeo': data.get('CaseCost', 'N/A'),
                    'cliente_nombre': data.get('CRM_ContactName', 'Cliente'),
                    'tipo_contacto': data.get('CRM_ContactType', 'N/A'),
                    'oportunidad_crm': data.get('CRM_OpportunityNumber', 'N/A'),
                    'cliente_email': data.get('CRM_ContactEmail', 'N/A'),
                    'cliente_telefono': data.get('CRM_ContactNumber', 'N/A'),
                    'cliente_ciudad': data.get('CRM_ContactCity', 'N/A'),
                    'cliente_estado': data.get('CRM_ContactState', 'N/A'),
                    'cliente_pais': data.get('CRM_ContactCountry', 'N/A'),
                    'vendedor_asignado': vendedor_info.get('nombre'),
                    
                    # ✅ VALORES FINANCIEROS NUMÉRICOS (sin formatear)
                    'precio_lista': precio_lista,
                    'precio_oferta': precio_oferta,
                    'precio_total': precio_total,
                    'descuento_porcentaje': descuento_porcentaje,
                    'monto_descuento': monto_descuento,
                    'monto_impuesto': monto_impuesto,
                    
                    # ✅ INFORMACIÓN DEL IMPUESTO
                    'impuesto_codigo': impuesto_info['codigo'],
                    'impuesto_nombre': impuesto_info['nombre'],
                    'impuesto_porcentaje': impuesto_info['porcentaje'],
                    
                    'moneda': moneda_nombre,  # "moneda"
                    'moneda_codigo': moneda_codigo,  # "MX"
                    'factor_sobrecosto': data.get('OvercostFactor', 1),
                    'total_lineas': len(data.get('QuotationLines', [])),
                    'pdf_url': pdf_url,
                    'fecha_generacion': datetime.now().strftime('%d/%m/%Y %H:%M')
                }
                
                archivos_adjuntos = [
                    {
                        'tipo': 'local',
                        'ruta': output_path,
                        'nombre': f"Cotizacion_{costing_num}_V{version}.pdf",
                        'mimetype': 'application/pdf'
                    }
                ]
                
                destinatarios_to = ['jesuscadena27@hotmail.com']

                # Agregar email del creador
                if user_email:
                    destinatarios_to.append(user_email)
                    print(f"✅ Email del creador agregado a destinatarios: {user_email}")
                else:
                    print("⚠️ No se agregó email del creador (no disponible)")

                # ✅ Agregar email del vendedor
                if vendedor_email and vendedor_email not in destinatarios_to:
                    destinatarios_to.append(vendedor_email)
                    print(f"✅ Email del vendedor agregado a destinatarios: {vendedor_email}")
                elif vendedor_email:
                    print(f"ℹ️ Email del vendedor ya está en la lista: {vendedor_email}")
                else:
                    print("⚠️ No se agregó email del vendedor (no disponible)")

                print(f"📧 Destinatarios TO finales: {destinatarios_to}")
                
                resultado_email = enviar_correo_universal(
                    template_path='Emails/Ventas/Cotiz/RecepcionCotizacionMail.html',
                asunto=f'📋 Nueva Cotización {costing_num} - V{version} - {data.get("CRM_ContactName", "Cliente")}',
                    mail_list_id=1001,
                    template_data=template_data,
                    archivos_adjuntos=archivos_adjuntos,
                    destinatarios_adicionales={
                        'TO': destinatarios_to,
                        'CC': ['victor.barrera.@igsa.com.mx','victor.cervantes@igsa.com.mx','alexis.moreno@igsa.com.mx']
                    }
                )
                
                email_info = {
                    'email_enviado': resultado_email['success'],
                    'email_mensaje': resultado_email['mensaje'],
                    'email_destinatarios': resultado_email['destinatarios']['total_destinatarios']
                }
                
                if resultado_email['success']:
                    print(f"✅ Email enviado correctamente")
                    print(f"   Destinatarios: {resultado_email['destinatarios']['total_destinatarios']}")
                    print(f"✅ Email del creador agregado a destinatarios: {user_email}")
                else:
                    print(f"⚠️ Advertencia: No se pudo enviar email - {resultado_email['mensaje']}")
                
            except Exception as e:
                print(f"❌ Error al enviar email: {str(e)}")
                import traceback
                traceback.print_exc()
                email_info = {
                    'email_enviado': False,
                    'email_mensaje': f'Error al enviar email: {str(e)}',
                    'email_destinatarios': 0
                }
            
            # Al final del controlador, ANTES de limpiar
            import time

            # Esperar un momento para que Puppeteer libere el archivo
            time.sleep(1)

            # Limpiar archivo temporal
            try:
                if os.path.exists(output_path):
                    print(f"🔍 Intentando eliminar: {output_path}")
                    print(f"🔍 Archivo existe: {os.path.exists(output_path)}")
                    print(f"🔍 Permisos: {oct(os.stat(output_path).st_mode)[-3:]}")
                    
                    os.remove(output_path)
                    print(f"✅ Archivo temporal eliminado: {output_path}")
                else:
                    print(f"⚠️ Archivo no existe: {output_path}")
            except PermissionError as e:
                print(f"❌ Error de permisos al eliminar: {e}")
                print(f"💡 El archivo podría estar siendo usado por otro proceso")
            except Exception as e:
                print(f"❌ Error al eliminar archivo temporal: {str(e)}")
                import traceback
                traceback.print_exc()
                
                
            # ✅ NUEVO: Limpiar archivo temporal de datos JSON (por si quedó)
            try:
                temp_data_file = os.path.join(output_dir, f"temp_data_{token}.json")
                if os.path.exists(temp_data_file):
                    os.remove(temp_data_file)
                    print(f"🗑️ Archivo temporal JSON eliminado: {temp_data_file}")
            except Exception as e:
                print(f"⚠️ No se pudo eliminar archivo temporal JSON: {str(e)}")
                        
                        
            
            print("=" * 80)
            print("✅ PROCESO COMPLETADO EXITOSAMENTE")
            print("=" * 80)
            
            return jsonify({
                "success": True,
                "pdf_url": pdf_url,
                "pdf_ruta_ftp": carpeta_relativa,
                "version": version,
                "message": "PDF generado, subido y enviado por email exitosamente",
                **email_info
            }), 200
                    
        except Exception as e:
            print(f"\n❌ ERROR CRÍTICO: {e}")
            import traceback
            traceback.print_exc()
            return jsonify({"success": False, "error": str(e)}), 500


    # =========================================================================================
    # RUTA 1: BÚSQUEDA DE COTIZACIÓN
    # =========================================================================================
    @app.route("/Ventas/Cotiz/Recepcion/BuscarCotizacion", methods=['POST'])
    def buscar_cotizacion():
        """Busca y recupera todos los datos de una cotización finalizada por ingeniería."""
        try:
            data = request.get_json()
            if not data or 'cotizacion_id' not in data:
                return jsonify({"success": False, "error": {"message": "ID de cotización no proporcionado.", "code": "INVALID_REQUEST"}}), 400
            
            cotizacion_id = data['cotizacion_id'].strip()
            logger.info(f"Buscando cotización completada: {cotizacion_id}")
            
            # Llamada al DAO para buscar la cotización
            cotizacion_completa = buscar_cotizacion_completa(cotizacion_id)
            
            if cotizacion_completa and cotizacion_completa.get('StatusIngenieria', 'PENDIENTE') in ['COMPLETADO', 'FINALIZADA']:
                logger.info(f"Cotización {cotizacion_id} encontrada y lista para revisión.")
                return jsonify({
                    "success": True,
                    "data": cotizacion_completa,
                    "message": "Cotización lista para revisión final."
                }), 200
            elif cotizacion_completa:
                # La cotización existe, pero no ha sido completada por ingeniería
                status = cotizacion_completa.get('StatusIngenieria', 'PENDIENTE')
                logger.warning(f"Cotización {cotizacion_id} no está lista. Estatus: {status}")
                return jsonify({
                    "success": False,
                    "error": {
                        "message": f"La cotización aún no ha sido finalizada por Ingeniería. Estado actual: {status}",
                        "alert_type": "info",
                        "code": "COTIZACION_PENDIENTE"
                    }
                }), 409
            else:
                logger.warning(f"Cotización {cotizacion_id} no encontrada en el sistema.")
                return jsonify({
                    "success": False,
                    "error": {
                        "message": f"El ID de cotización '{cotizacion_id}' no existe o no está activo.",
                        "alert_type": "warning",
                        "code": "COTIZACION_NOT_FOUND"
                    }
                }), 404

        except Exception as e:
            logger.error(f"Error en búsqueda de cotización: {str(e)}", exc_info=True)
            return jsonify({"success": False, "error": {"message": "Error interno del servidor en la búsqueda.", "code": "INTERNAL_ERROR", "alert_type": "error"}}), 500


    # =========================================================================================
    # RUTA 2: ENVÍO FINAL DE LA COTIZACIÓN (Procesa la venta y notifica)
    # =========================================================================================
    @app.route("/Ventas/Cotiz/Recepcion/EnviarCotizacion", methods=['POST'])
    def enviar_cotizacion():
        """Procesa la cotización final, actualiza el estado y notifica al cliente/CRM."""
        user_id = session.get('user_id')
        if not user_id:
            return jsonify({"success": False, "error": {"message": "Usuario no autenticado", "code": "NOT_AUTHENTICATED"}}), 401
            
        try:
            data = request.get_json()
            if not data or 'cotizacion_id' not in data:
                return jsonify({"success": False, "error": {"message": "Datos de cotización incompletos.", "code": "INVALID_REQUEST"}}), 400
            
            cotizacion_id = data['cotizacion_id']
            # Obtener datos para la actualización
            financieros = data['financieros']
            lineas = data['lineas']
            tiempo_condiciones = data['tiempo_condiciones']
            
            logger.info(f"Iniciando proceso de finalización para Cotización: {cotizacion_id}")
            
            # 1. Actualizar las líneas de cotización (si hay cambios en cantidad/descuento manual)
            # Nota: Esto es opcional, pero se incluye por si el vendedor puede ajustar algo.
            # (Se asume una función para guardar líneas de DataTables)
            resultado_lineas = actualizar_lineas_cotizacion(cotizacion_id, lineas, user_id)
            if not resultado_lineas['success']:
                return jsonify({"success": False, "error": {"message": f"Error al actualizar líneas: {resultado_lineas['message']}", "alert_type": "warning"}}), 500

            # 2. Actualizar la sección de cálculo financiero
            resultado_financiero = actualizar_calculo_financiero(cotizacion_id, financieros, user_id)
            if not resultado_financiero['success']:
                return jsonify({"success": False, "error": {"message": f"Error al actualizar datos financieros: {resultado_financiero['message']}", "alert_type": "warning"}}), 500

            # 3. Actualizar tiempo de entrega y condiciones
            resultado_tiempo = actualizar_tiempo_y_condiciones(cotizacion_id, tiempo_condiciones, user_id)
            if not resultado_tiempo['success']:
                return jsonify({"success": False, "error": {"message": f"Error al actualizar tiempo y condiciones: {resultado_tiempo['message']}", "alert_type": "warning"}}), 500

            # 4. Finalizar la cotización: Marcar como 'ENVIADA_A_CLIENTE' y crear PDF final
            resultado_final = finalizar_cotizacion_y_enviar(cotizacion_id, user_id, data)
            if not resultado_final['success']:
                return jsonify({"success": False, "error": {"message": f"Error al finalizar cotización: {resultado_final['message']}", "alert_type": "error"}}), 500
            
            # 5. Enviar Correo de Notificación al Cliente (Usando la utilidad de correo)
            # Asumiendo que `resultado_final` contiene la URL del PDF final
            if resultado_final.get('pdf_url'):
                enviar_correo_cliente_v2(cotizacion_id, data, user_id, resultado_final['pdf_url'])
            
            logger.info(f"Cotización {cotizacion_id} finalizada y enviada exitosamente.")
            return jsonify({
                "success": True,
                "message": f"La cotización {cotizacion_id} ha sido enviada al cliente y actualizada en el CRM.",
                "data": {"cotizacion_id": cotizacion_id, "pdf_url": resultado_final.get('pdf_url')}
            }), 200

        except Exception as e:
            logger.error(f"Error inesperado en el envío final: {str(e)}", exc_info=True)
            return jsonify({"success": False, "error": {"message": "Error interno del servidor en el envío final.", "code": "INTERNAL_ERROR", "alert_type": "error"}}), 500
    

    # /Ventas/Cotiz/Recepcion/getTerms
    @app.route(f"{_BASE_ROUTE}/getTerms", methods=['GET'])
    def get_TermsType_JSON_controller():
        """
        Endpoint para obtener todos los tipos de términos y condiciones activos
        desde la tabla Q_TermsType y devolverlos en formato JSON.
        """
        logger.info("📥 Solicitud recibida para obtener los tipos de términos y condiciones.")

        try:
            # 1️⃣ Llamar a la función que obtiene los datos desde SQL
            resultado_dict = get_TermsType_JSON()

            # 2️⃣ Manejar el caso de que no existan registros
            if not resultado_dict:
                logger.warning("⚠️ No se encontraron registros activos en Q_TermsType.")
                return jsonify({"error": "No se encontraron registros activos."}), 404

            # 3️⃣ Convertir el resultado a JSON (con serialización segura)
            json_response = json.dumps(resultado_dict, default=json_serializer)

            # 4️⃣ Devolver respuesta HTTP 200 con JSON
            response = app.response_class(
                response=json_response,
                status=200,
                mimetype='application/json'
            )
            logger.info(f"✅ {len(resultado_dict)} registros devueltos correctamente.")
            return response

        except Exception as e:
            # 5️⃣ Capturar errores inesperados
            logger.error(f"❌ Error inesperado al obtener términos: {e}")
            return jsonify({"error": "Ocurrió un error inesperado en el servidor."}), 500


    @app.route(f"{_BASE_ROUTE}/<string:cotizacion_id>", methods=['GET'])
    def get_Quote_JSON_BussinessCentral_controller(cotizacion_id: str):
        """
        Endpoint para obtener los datos de una cotización en formato JSON.
        Responde con los datos en caso de éxito, o con un código de error apropiado.
        """
        logger.info(f"Recibida solicitud para obtener la cotización: {cotizacion_id}")
        
        try:
            # 1. Llama a la función de lógica de negocio pasando el ID de la cotización
            resultado_dict = get_Quote_JSON_BussinessCentral(cotizacion_id)
            
            # 2. Maneja el caso en que la cotización no se encuentra
            if not resultado_dict:
                logger.warning(f"Cotización no encontrada: {cotizacion_id}")
                # Devuelve una respuesta JSON de error con el código de estado 404 Not Found
                return jsonify({"error": "Cotización no encontrada"}), 404
                
            # 3. Convierte el diccionario a JSON y devuelve una respuesta exitosa
            # Usamos un serializador personalizado para manejar tipos como Decimal y datetime
            # Flask > 2.2 puede manejar Decimal automáticamente, pero es más seguro ser explícito
            json_response = json.dumps(resultado_dict, default=json_serializer)
            
            # Para devolver un string JSON, necesitamos crear un objeto Response
            # y establecer el mimetype correcto.
            response = app.response_class(
                response=json_response,
                status=200,
                mimetype='application/json'
            )
            logger.info(f"Respuesta exitosa para la cotización: {cotizacion_id}")
            return response
            
        except Exception as e:
            # 5. Captura cualquier otro error inesperado
            logger.error(f"Error inesperado al procesar cotización '{cotizacion_id}': {e}")
            return jsonify({"error": "Ocurrió un error inesperado en el servidor"}), 500


    # /ventas/cotiz/recepcion/<string:cotizacion_id>/send
    @app.route(f"{_BASE_ROUTE}/<string:cotizacion_id>/send", methods=['POST'])
    def send_quote_controller(cotizacion_id: str):
        """
        Endpoint para enviar una cotización desde la BD local al CRM.
        Se usa con método POST (por ejemplo, desde Postman).
        """
        logger.info(f"Recibida solicitud POST para enviar la cotización: {cotizacion_id}")
        
        try:
            # 1️⃣ Recuperar los datos de la cotización
            quote_data_to_send = get_Quote_JSON_BussinessCentral(cotizacion_id)

            
            if not quote_data_to_send:
                logger.warning(f"Cotización no encontrada: {cotizacion_id}")
                return jsonify({
                    "status": "error",
                    "message": f"La cotización {cotizacion_id.split('-')[0]} no fue encontrada en la base de datos."
                }), 404

            # 2️⃣ Inicializar el CRMManager
            crm_manager = CRMManager(current_app.config)

            configuration = config.Config()
            valor = configuration.PROTOTIPO

            crm_response = {"status": "success", "message": "MODO PROTOTIPO: Envío simulado."}

            # 3️⃣ Enviar los datos al CRM
            if configuration.PROTOTIPO == False:
                logger.info(f"Enviando cotización '{cotizacion_id}' al CRM...")
                crm_response = crm_manager.send_quote(quote_data_to_send)
            else:
                logger.info(f"MODO PROTOTIPO: Simulando envío de cotización '{cotizacion_id}' al CRM.")
                

            # 4️⃣ Devolver una respuesta exitosa
            logger.info(f"Cotización '{cotizacion_id}' enviada exitosamente al CRM.")
            return jsonify({
                "status": "success",
                "message": f"Cotización {cotizacion_id.split('-')[0]} enviada exitosamente al CRM.",
                "crm_response": crm_response
            }), 200

        except requests.exceptions.HTTPError as e:
            logger.error(f"Error HTTP al enviar '{cotizacion_id.split('-')[0]}': {e.response.text}")
            return jsonify({
                "status": "error",
                "message": "El CRM rechazó la solicitud.",
                "details": e.response.text
            }), e.response.status_code

        except requests.exceptions.RequestException as e:
            logger.error(f"Error de comunicación con el CRM: {e}")
            return jsonify({"status": "error", "message": "Error de comunicación con el CRM."}), 503

        except Exception as e:
            logger.error(f"Error inesperado al enviar la cotización '{cotizacion_id.split('-')[0]}': {e}", exc_info=True)
            return jsonify({"status": "error", "message": "Error interno al enviar la cotización."}), 500


    # ----------------------------------------------------------------------------------------
    # FUNCIONES AUXILIARES (simuladas aquí, usarías tus módulos)
    # ----------------------------------------------------------------------------------------

    def enviar_correo_cliente_v2(cotizacion_id: str, data: Dict, user_id: int, pdf_url: str):
        """Simula el envío de correo de cotización al cliente."""
        try:
            # 1. Obtener datos necesarios (cliente, correo, nombre del vendedor)
            # Esto debería venir del resultado de la búsqueda o de una función SQL
            cliente_email = data.get('oportunidad', {}).get('CRM_ContactEmail', 'correo_prueba@igsa.com.mx')
            cliente_name = data.get('oportunidad', {}).get('CRM_ContactName', 'Cliente Estimado')
            
            # 2. Construir el template de datos
            template_data = {
                "cliente_nombre": cliente_name,
                "cotizacion_id": cotizacion_id,
                "pdf_url": pdf_url,
                "fecha_envio": datetime.now().strftime('%d/%m/%Y'),
                # ... otros datos para el template
            }

            destinatario:str = cliente_email
            
            if app.config.get('EMAIL_PROTOTYPE_MODE'):
                    destinatario = app.config.get('EMAIL_PROTOTYPE_TOADRESS')
            
            # 3. Llamar al motor de correo universal (que debe manejar attachments o links)
            resultado = enviar_correo_universal(
                template_path='Emails/Ventas/Cotiz/CotizFinalClientMail.html',
                asunto=f"Cotización Finalizada - ID {cotizacion_id}",
                to_list=[destinatario], # Enviar directamente al cliente
                template_data=template_data
            )
            
            if not resultado['success']:
                logger.warning(f"Error al enviar correo al cliente {cliente_email}: {resultado['mensaje']}")
                return {'success': False, 'message': resultado['mensaje']}
            
            logger.info(f"Correo de cotización enviado exitosamente al cliente: {cliente_email}")
            return {'success': True}
            
        except Exception as e:
            logger.error(f"Error en el proceso de envío de correo al cliente: {str(e)}")
            return {'success': False, 'message': str(e)}