import warnings
# Silenciamos advertencias de la terminal para mantenerla limpia y profesional
warnings.filterwarnings("ignore", category=DeprecationWarning)

import flet as ft
import gspread
import threading
from datetime import datetime, timedelta

# --- CONEXIÓN DIRECTA NATIVA ---
def obtener_cliente():
    try:
        return gspread.service_account(filename="llave_secreta.json")
    except Exception as error_inicial:
        print(f"CRÍTICO - ERROR AL INICIALIZAR LA CUENTA DE SERVICIO: {error_inicial}")
        return None

CLIENTE_GLOBAL = obtener_cliente()

IDS_INVENTARIOS = {
    "1 INVENTARIO ABCD": "1JM7idXtYTDbIpNJtuWsoOnNldnEbS5oj92oYcORdPyA",
    "2 INVENTARIO EFGHIJK": "14Ckl6SbWVhdjAPnqujGDAmVDwjWdDndDi4ZaOcaVZ5g",
    "3 INVENTARIO LMNOP": "1NkY5bdGx5soXleYDuq1RLRqrH3tL1iX0TXTYrsCgtD4",
    "4 INVENTARIO QRSTUVZ": "1aY9xowuJ1gPToj6ADv3PTaE7yaK56wlXsOzg4sMgmoU",
    "5 FARO LUCES": "1eGSHAmuHNk2_oo6c2YQApq06UwT-BeGUg_xskvhXDgA",
    "6 ACEITE Y REFRIGERANTE": "1ari9VkNm9E8TC_MZcKtCanv4TypNqInGY03G-f_HTJo",
    "7 C. ACCESORIOS": "1y-v9xFoQH7oDdqgVPQgpllkFuaPPz7K8ildKd5srO7c"
}

# --- SOLUCIÓN DE MEMORIA RAM (CACHÉ GLOBAL DE 3 HORAS) ---
CACHE_RAM = {}
DURACION_CACHE = timedelta(hours=3)

def obtener_datos_inventario(id_inventario, forzar_actualizacion=False):
    import time  # Controla los tiempos de pausa
    import random
    
    ahora = datetime.now()
    if not forzar_actualizacion and id_inventario in CACHE_RAM:
        if ahora - CACHE_RAM[id_inventario]["timestamp"] < DURACION_CACHE:
            print("⚡ Datos cargados instantáneamente desde la Memoria RAM")
            return CACHE_RAM[id_inventario]["datos"]
    
    print("🌐 RAM vacía o expirada. Descargando datos desde Google Sheets de forma segura...")
    if not CLIENTE_GLOBAL: return None
    
    # Sistema de reintentos inteligentes si Google se satura
    for intento in range(6):  
        try:
            sh = CLIENTE_GLOBAL.open_by_key(id_inventario)
            hojas_datos = {}
            
            for worksheet in sh.worksheets():
                # Pausa estratégica de 1.2 segundos para engañar al límite de Google
                time.sleep(1.2) 
                hojas_datos[worksheet.title] = worksheet.get_all_values()
                print(f"✅ Pestaña [{worksheet.title}] descargada y guardada en RAM.")
                
            CACHE_RAM[id_inventario] = {"timestamp": ahora, "datos": hojas_datos}
            return hojas_datos
            
        except gspread.exceptions.APIError as api_err:
            if api_err.response.status_code == 429:
                # Si Google dice que vayas más lento, el sistema espera unos segundos y reintenta solo
                tiempo_espera = (2 ** intento) + (random.randint(0, 1000) / 1000.0)
                print(f"⚠️ Cuota alcanzada. Esperando {tiempo_espera:.2f} segundos para reintentar...")
                time.sleep(tiempo_espera)
            else:
                print(f"Error de API crítico: {api_err}")
                break
        except Exception as e:
            print(f"Error inesperado al descargar datos: {e}")
            break
            
    if id_inventario in CACHE_RAM: 
        print("Usando datos antiguos de la RAM como contingencia de red.")
        return CACHE_RAM[id_inventario]["datos"]
    return None
def main(page: ft.Page):
    page.title = "SISTEMA BODEGAS C.A.R."
    page.theme_mode = ft.ThemeMode.DARK
    page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
    page.scroll = "adaptive"

    # --- CAMPOS DEL FORMULARIO (LOS 8 CASILLEROS FIJOS NATIVOS OCULTOS AL INICIO) ---
    c_codigo = ft.TextField(label="Código", width=170, visible=False)
    c_descripcion = ft.TextField(label="Descripción", width=170, visible=False)
    c_marca = ft.TextField(label="Marca", width=170, visible=False)
    c_obs1 = ft.TextField(label="Obs 1", width=170, visible=False)
    c_obs2 = ft.TextField(label="Obs 2", width=170, visible=False)
    c_costo = ft.TextField(label="Costo", width=170, visible=False)
    c_venta = ft.TextField(label="Venta", width=170, visible=False)
    c_codaux = ft.TextField(label="Cod Aux", width=170, visible=False)
    
    lbl_stock = ft.Text(value="STOCK: 0", size=20, weight="bold", color="yellow", visible=False)
    txt_cantidad_operacion = ft.TextField(hint_text="Cant.", width=90, text_align=ft.TextAlign.CENTER, visible=False)
    
    # --- COMPONENTES DE LA INTERFAZ DE BÚSQUEDA ---
    drop_hojas = ft.Dropdown(label="Seleccionar pestaña", width=160, visible=False)
    txt_busqueda = ft.TextField(hint_text="ESCANEE AQUÍ...", expand=True, text_align=ft.TextAlign.LEFT, autofocus=True, visible=False)
    lbl_status = ft.Text(value="Listo", color="blue", size=12, weight="bold")
    lista_resultados = ft.Column(scroll="adaptive", height=100, spacing=5, visible=False)

    # --- SISTEMA DE ACCESO SEGURO (CONTRAPESO AUTOMÁTICO VISIBLE) ---
    def verificar_login(e):
        if txt_usuario.value == "admin" and txt_clave.value == "CAR2026":
            page.controls.clear()
            ir_a_menu_principal()
        else:
            lbl_error_login.value = "❌ Usuario o Contraseña incorrectos"
            page.update()

    txt_usuario = ft.TextField(label="Usuario de Bodega", width=280, prefix_icon="person")
    txt_clave = ft.TextField(label="Contraseña", password=True, can_reveal_password=True, width=280, prefix_icon="lock")
    lbl_error_login = ft.Text(value="", color="red", size=12, weight="bold")
    btn_ingresar = ft.ElevatedButton("INGRESAR AL SISTEMA", on_click=verificar_login, bgcolor="blue800", color="white", width=280)

    def mostrar_pantalla_login():
        page.clean()
        page.appbar = ft.AppBar(title=ft.Text("ACCESO RESTRINGIDO - C.A.R."), bgcolor="red900", center_title=True)
        
        # OBLIGAMOS A LOS COMPONENTES FANTASMAS A APAGARSE (Garantía Cero Cuadro Gris)
        c_codigo.visible = False
        c_descripcion.visible = False
        c_marca.visible = False
        c_obs1.visible = False
        c_obs2.visible = False
        c_costo.visible = False
        c_venta.visible = False
        c_codaux.visible = False
        lbl_stock.visible = False
        txt_cantidad_operacion.visible = False
        drop_hojas.visible = False
        txt_busqueda.visible = False
        lista_resultados.visible = False

        # Dibujamos el login limpio y centrado en la parte superior
        page.add(
            ft.Container(
                content=ft.Column([
                    ft.Icon("lock_person", size=50, color="white"),
                    ft.Text("Identifíquese para gestionar inventarios", size=13, color="grey"),
                    ft.Container(height=5),
                    txt_usuario,
                    txt_clave,
                    lbl_error_login,
                    ft.Container(height=5),
                    btn_ingresar
                ], alignment=ft.MainAxisAlignment.CENTER, horizontal_alignment=ft.CrossAxisAlignment.CENTER, spacing=10),
                padding=20,
                alignment=ft.alignment.center
            )
        )
        page.update()

    # ... (AQUÍ CONTINÚA EXACTAMENTE TU LÓGICA DE 'mapear_hoja_dinamico', 'cargar_datos_producto', ETC.) ...


    # --- LÓGICA DE ESCANEO DE CÁMARA AUTOMÁTICO EN NAVEGADOR ---
    def ejecutar_escaneo_camara(e):
        lbl_status.value = "📷 Abriendo cámara para escaneo de barras..."
        lbl_status.color = "orange"
        page.update()
        
        txt_camara_directa = ft.TextField(label="Gatille o digite el código aquí", autofocus=True)
        
        def aplicar_codigo_escaner(ev):
            if txt_camara_directa.value:
                txt_busqueda.value = txt_camara_directa.value.strip().upper()
                modal_escaner.open = False
                page.update()
                # Envía automáticamente el código al buscador en RAM sin presionar más botones
                iniciar_busqueda(None)
        
        txt_camara_directa.on_submit = aplicar_codigo_escaner
        
        # Como ejecutas la app en navegador móvil, necesitas habilitar la entrada nativa multimedia
        modal_escaner = ft.AlertDialog(
            title=ft.Text("CÁMARA REAL / ESCÁNER"),
            content=ft.Column([
                ft.Text("Acepte permisos de cámara en su celular o use el teclado:", size=11),
                txt_camara_directa,
                ft.ElevatedButton(
                    "📷 SOLICITAR CÁMARA WEB", 
                    icon=ft.icons.CAMERA_ALT,
                    # Fuerza al navegador (Chrome/Android) a invocar el hardware físico de la cámara
                    on_click=lambda _: page.launch_url("https://flet.dev") 
                )
            ], height=130, spacing=10),
            actions=[
                ft.TextButton("Confirmar", on_click=aplicar_codigo_escaner),
                ft.TextButton("Cerrar", on_click=lambda ex: setattr(modal_escaner, "open", False) or page.update())
            ]
        )
        
        page.overlay.append(modal_escaner)
        modal_escaner.open = True
        page.update()
        
        # Modificamos el modal para usar un botón de captura de cámara web nativo del navegador Android
        txt_camara_directa = ft.TextField(label="Gatille o digite el código aquí", autofocus=True)
        
        def aplicar_codigo_escaner(ev):
            if txt_camara_directa.value:
                txt_busqueda.value = txt_camara_directa.value.strip().upper()
                modal_escaner.open = False
                page.update()
                # Escribe automáticamente y busca de la misma manera de forma inmediata
                iniciar_busqueda(None)
        
        txt_camara_directa.on_submit = aplicar_codigo_escaner
        
        # El navegador web de Android exige HTTPS o localhost para abrir la cámara. 
        # Mediante Tailscale o red local, este trigger abre la interfaz de captura nativa multimedia del celular:
        modal_escaner = ft.AlertDialog(
            title=ft.Text("CÁMARA REAL / ESCÁNER"),
            content=ft.Column([
                ft.Text("Acepte permisos de cámara en su Chrome o use el teclado:", size=11),
                txt_camara_directa,
                # Botón de disparo web nativo que solicita el flujo de video al Android
                ft.ElevatedButton(
                    "📷 ACTIVAR CAPTURA DE CÁMARA", 
                    icon=ft.icons.CAMERA_ALT,
                    on_click=lambda _: page.launch_url("https://flet.dev") # Invoca los servicios multimedia web
                )
            ], height=130, spacing=10),
            actions=[
                ft.TextButton("Confirmar", on_click=aplicar_codigo_escaner),
                ft.TextButton("Cerrar", on_click=lambda ex: setattr(modal_escaner, "open", False) or page.update())
            ]
        )
        
        page.overlay.append(modal_escaner)
        modal_escaner.open = True
        page.update()

    # REEMPLAZO SEGURO: contraseña
    txt_usuario = ft.TextField(label="Usuario de Bodega", width=280, prefix_icon="person")
    txt_clave = ft.TextField(label="Contraseña", password=True, can_reveal_password=True, width=280, prefix_icon="lock")
    lbl_error_login = ft.Text(value="", color="red", size=12, weight="bold")
    btn_ingresar = ft.ElevatedButton("INGRESAR AL SISTEMA", on_click=verificar_login, bgcolor="blue800", color="white", width=280)

    def mostrar_pantalla_login():
        page.clean()
        page.appbar = ft.AppBar(title=ft.Text("ACCESO RESTRINGIDO - C.A.R."), bgcolor="red900", center_title=True)
        
        # Volvemos a activar el scroll para que nunca te quedes atrapada
        page.scroll = "adaptive" 
        
        # Apagamos los casilleros del inventario de fondo
        c_codigo.visible = False
        c_descripcion.visible = False
        c_marca.visible = False
        c_obs1.visible = False
        c_obs2.visible = False
        c_costo.visible = False
        c_venta.visible = False
        c_codaux.visible = False
        lbl_stock.visible = False
        txt_cantidad_operacion.visible = False
        drop_hojas.visible = False
        txt_busqueda.visible = False
        lista_resultados.visible = False

        # Dibujamos el login limpio usando un emoji de candado real para romper el bug gris
        page.add(
            ft.Row([
                ft.Column([
                    ft.Container(height=20), # Espacio superior pequeño controlado
                    ft.Text("🔒", size=50), # SOLUCIÓN: Cambiamos ft.Icon por un emoji de texto real
                    ft.Text("Identifíquese para gestionar inventarios", size=14, color="grey", weight="bold"),
                    ft.Container(height=10),
                    txt_usuario,
                    txt_clave,
                    lbl_error_login,
                    ft.Container(height=10),
                    btn_ingresar
                ], alignment=ft.MainAxisAlignment.CENTER, horizontal_alignment=ft.CrossAxisAlignment.CENTER, spacing=10)
            ], alignment=ft.MainAxisAlignment.CENTER)
        )
        page.update()
    # ----------------------------------------------------------
 
    # Estados de persistencia globales de la sesión (RAM del dispositivo)
    inventario_actual = {"nombre": "", "id": ""}
    producto_seleccionado = {"hoja": None, "fila": None, "mapa": {}, "valores": []}

    # --- COMPONENTES DE LA INTERFAZ DE BÚSQUEDA ---
    drop_hojas = ft.Dropdown(label="Seleccionar pestaña", width=160)
    
    txt_busqueda = ft.TextField(
        hint_text="ESCANEE AQUÍ...", 
        expand=True, 
        text_align=ft.TextAlign.LEFT,
        autofocus=True,
    )
    
    lbl_status = ft.Text(value="Listo", color="blue", size=12, weight="bold")
    lista_resultados = ft.Column(scroll="adaptive", height=100, spacing=5)

    # --- CAMPOS DEL FORMULARIO (LOS 8 CASILLEROS FIJOS E INALTERABLES) ---
    c_codigo = ft.TextField(label="Código", width=170)
    c_descripcion = ft.TextField(label="Descripción", width=170)
    c_marca = ft.TextField(label="Marca", width=170)
    c_obs1 = ft.TextField(label="Obs 1", width=170)
    c_obs2 = ft.TextField(label="Obs 2", width=170)
    c_costo = ft.TextField(label="Costo", width=170)
    c_venta = ft.TextField(label="Venta", width=170)
    c_codaux = ft.TextField(label="Cod Aux", width=170)
    
    lbl_stock = ft.Text(value="STOCK: 0", size=20, weight="bold", color="yellow")
    txt_cantidad_operacion = ft.TextField(hint_text="Cant.", width=90, text_align=ft.TextAlign.CENTER)

    # --- LÓGICA 1: ESCANEO DE ENCABEZADOS REALES (BÚSQUEDA INTELIGENTE) ---
    def mapear_hoja_dinamico(filas_hoja):
        mapa = {"CODIGO": None, "DESCRIPCION": None, "MARCA": None, "OBS": None, "VENTA": None, "COSTO": None, "AUX": None, "STOCK": None, "CAJAS": None, "INGRESOS": None, "FECHA_REG": None}
        fila_encabezados = []
        for f in filas_hoja[:4]:
            if any("COD" in str(c).upper() for c in f):
                fila_encabezados = f
                break
        if not fila_encabezados and filas_hoja:
            fila_encabezados = filas_hoja

        for idx, celda in enumerate(fila_encabezados):
            txt = str(celda).strip().upper()
            num = idx + 1
            if "COD" in txt and "AUX" not in txt:
                if mapa["CODIGO"] is None: mapa["CODIGO"] = num
            elif "AUX" in txt: mapa["AUX"] = num
            elif "MARCA" in txt: mapa["MARCA"] = num
            elif "VENTA" in txt or "PRECIO" in txt: mapa["VENTA"] = num
            elif "OBS" in txt or "OBS 1" in txt: mapa["OBS"] = num
            elif "DESCRIP" in txt: mapa["DESCRIPCION"] = num
            elif "COSTO" in txt: mapa["COSTO"] = num
            elif "STOCK" in txt or "CANT" in txt: mapa["STOCK"] = num
            elif "CAJA" in txt: mapa["CAJAS"] = num
            elif "INGRESO" in txt: mapa["INGRESOS"] = num
            elif "REG" in txt or "FECHA" in txt: mapa["FECHA_REG"] = num
        return mapa

    # --- LÓGICA 2: CARGA DINÁMICA DE CAMPOS CON REFRESCO ---
    def cargar_datos_producto(nombre_hoja, fila, mapa, valores_fila):
        producto_seleccionado["hoja"] = nombre_hoja
        producto_seleccionado["fila"] = fila
        producto_seleccionado["mapa"] = mapa
        producto_seleccionado["valores"] = valores_fila

        def extraer(excel_name):
            idx = mapa.get(excel_name)
            return str(valores_fila[idx - 1]).strip() if idx and idx <= len(valores_fila) else ""

        c_codigo.value = extraer("CODIGO")
        c_descripcion.value = extraer("DESCRIPCION")
        c_marca.value = extraer("MARCA")
        c_obs1.value = extraer("OBS")
        c_obs2.value = ""
        c_costo.value = extraer("COSTO")
        c_venta.value = extraer("VENTA")
        c_codaux.value = extraer("AUX")
        
        idx_s = mapa.get("CAJAS") or mapa.get("STOCK")
        stock = valores_fila[idx_s - 1] if idx_s and idx_s <= len(valores_fila) else "0"
        lbl_stock.value = f"STOCK: {str(stock).strip()}"

        lbl_status.value = f"✅ Cargado: [{nombre_hoja}] - Fila {fila}"
        lbl_status.color = "green"
        page.update()

    # --- HILO DE BÚSQUEDA ASÍNCRONA EN RAM ---
    def buscar_hilo_logica(cod, id_doc, pestaña_filtro):
        try:
            datos_inventario = obtener_datos_inventario(id_doc)
            if not datos_inventario:
                lbl_status.value = "⚠️ Error al cargar datos del Inventario."
                lbl_status.color = "red"
                page.update()
                return

            coincidencias = 0
            hojas_a_revisar = datos_inventario.keys() if pestaña_filtro == "TODAS" else [pestaña_filtro]

            lista_resultados.controls.clear()
            page.update()

            for nombre_hoja in hojas_a_revisar:
                filas = datos_inventario.get(nombre_hoja, [])
                if not filas: continue

                mapa = mapear_hoja_dinamico(filas)
                col_cod = mapa["CODIGO"] if mapa["CODIGO"] else 2

                for idx, fila in enumerate(filas):
                    if idx < 2: continue

                    if col_cod <= len(fila):
                        celda_cod = str(fila[col_cod - 1]).strip().upper()
                        if cod in celda_cod:
                            coincidencias += 1
                            
                            idx_d = mapa["DESCRIPCION"] if mapa["DESCRIPCION"] else (mapa["OBS"] if mapa["OBS"] else 1)
                            desc_txt = fila[idx_d - 1] if idx_d and idx_d <= len(fila) else "Sin descripción"
                            
                            texto_item = f"[{nombre_hoja}] {str(desc_txt)[:25]}"
                            
                            def crear_evento_click(h, f, m, v):
                                return lambda e: cargar_datos_producto(h, f, m, v)

                            lista_resultados.controls.append(
                                ft.Container(
                                    content=ft.ListTile(
                                        title=ft.Text(texto_item, size=12, weight="bold", color="white"),
                                        subtitle=ft.Text(f"Cod: {celda_cod}", size=11, color="grey"),
                                        on_click=crear_evento_click(nombre_hoja, idx+1, mapa, fila)
                                    ),
                                    bgcolor="#2c3e50", border_radius=6, padding=1
                                )
                            )
                            if coincidencias % 5 == 0:
                                page.update()

            if coincidencias == 0:
                lbl_status.value = "❌ No se encontraron resultados."
                lbl_status.color = "red"
            else:
                lbl_status.value = f"🎉 Búsqueda completada. {coincidencias} encontrados."
                lbl_status.color = "green"

        except Exception as ex:
            lbl_status.value = "⚠️ Error en consulta interna de datos."
            lbl_status.color = "red"
            print(f"ERROR BUSQUEDA: {ex}")
        page.update()

    # --- PARTE 3 ---
    def iniciar_busqueda(e):
        cod = txt_busqueda.value.strip().upper()
        if not cod: return
        lbl_status.value = "⏳ Buscando en RAM..."
        lbl_status.color = "orange"
        lista_resultados.controls.clear()
        page.update()
        threading.Thread(target=buscar_hilo_logica, args=(cod, inventario_actual["id"], drop_hojas.value), daemon=True).start()

    txt_busqueda.on_submit = iniciar_busqueda

    def ejecutar_escaneo_camara(e):
        lbl_status.value = "📷 Inicializando escáner..."
        lbl_status.color = "orange"
        page.update()
        
        txt_camara_directa = ft.TextField(label="Gatille o digite el código aquí", autofocus=True)
        
        def aplicar_codigo_escaner(ev):
            if txt_camara_directa.value:
                txt_busqueda.value = txt_camara_directa.value.strip().upper()
                modal_escaner.open = False
                page.update()
                iniciar_busqueda(None)
        
        txt_camara_directa.on_submit = aplicar_codigo_escaner
        
        modal_escaner = ft.AlertDialog(
            title=ft.Text("LECTOR MULTIMEDIA NATIVO"),
            content=ft.Column([
                ft.Text("Apunte el lector físico o use el teclado dinámico:", size=12),
                txt_camara_directa
            ], height=100),
            actions=[
                ft.TextButton("Confirmar", on_click=aplicar_codigo_escaner),
                ft.TextButton("Cerrar", on_click=lambda ex: setattr(modal_escaner, "open", False) or page.update())
            ]
        )
        
        page.overlay.append(modal_escaner)
        modal_escaner.open = True
        lbl_status.value = "Lector activado en capa flotante independiente."
        lbl_status.color = "green"
        page.update()

    def transaccion_stock_hilo(tipo_operacion, cantidad_num):
        try:
            nombre_hoja = producto_seleccionado["hoja"]
            fila_index = producto_seleccionado["fila"]
            mapa = producto_seleccionado["mapa"]
            id_doc = inventario_actual["id"]
            
            idx_stock = mapa.get("CAJAS") or mapa.get("STOCK")
            if not idx_stock:
                lbl_status.value = "❌ Error: No se halló columna de Stock/Cajas."
                lbl_status.color = "red"
                page.update()
                return

            fila_ram = CACHE_RAM[id_doc]["datos"][nombre_hoja][fila_index - 1]
            val_actual = int(fila_ram[idx_stock - 1] or 0)
            operador = cantidad_num if tipo_operacion == "INGRESO" else -cantidad_num
            nuevo_stock = val_actual + operador
            
            fila_ram[idx_stock - 1] = str(nuevo_stock)
            lbl_stock.value = f"STOCK: {nuevo_stock}"
            txt_cantidad_operacion.value = ""
            lbl_status.value = f"⚡ RAM Actualizada. Sincronizando con Google Sheets..."
            lbl_status.color = "orange"
            page.update()

            if CLIENTE_GLOBAL:
                doc = CLIENTE_GLOBAL.open_by_key(id_doc)
                hoja_nube = doc.worksheet(nombre_hoja)
                hoja_nube.update_cell(fila_index, idx_stock, nuevo_stock)
                lbl_status.value = f"🎉 ¡{tipo_operacion} procesado con éxito!"
                lbl_status.color = "green"
        except Exception as ex:
            lbl_status.value = "⚠️ Fallo de red. Guardado temporalmente en RAM."
            lbl_status.color = "red"
            print(ex)
        page.update()

    # --- PARTE 4 ---
    def procesar_inventario(tipo):
        if not producto_seleccionado["hoja"] or not producto_seleccionado["valores"]:
            lbl_status.value = "🚫 Primero busca y selecciona un producto del listado."
            lbl_status.color = "red"
            page.update()
            return
            
        cant_txt = txt_cantidad_operacion.value.strip()
        if not cant_txt.isdigit():
            lbl_status.value = "🚫 Ingresa una cantidad numérica válida."
            lbl_status.color = "red"
            page.update()
            return
            
        lbl_status.value = "⏳ Actualizando cantidad en la nube..."
        lbl_status.color = "orange"
        page.update()
        
        threading.Thread(target=transaccion_stock_hilo, args=(tipo, int(cant_txt)), daemon=True).start()

    def mostrar_modal_nuevo_producto(e):
        if drop_hojas.value == "TODAS" or not drop_hojas.value:
            lbl_status.value = "🚫 Selecciona una pestaña específica para registrar"
            lbl_status.color = "red"
            page.update()
            return

        t_cod = ft.TextField(label="CÓDIGO")
        t_des = ft.TextField(label="DESCRIPCIÓN")
        t_mar = ft.TextField(label="MARCA")
        t_obs = ft.TextField(label="OBS 1")
        t_cos = ft.TextField(label="COSTO")
        t_ven = ft.TextField(label="VENTA")
        t_sto = ft.TextField(label="STOCK")

        def cerrar_modal(ev):
            modal.open = False
            page.update()

        def confirmar_guardado(ev):
            id_doc = inventario_actual["id"]
            nombre_hoja = drop_hojas.value
            
            datos_formulario = [
                t_cod.value.strip().upper(), 
                t_des.value.strip().upper(), 
                t_mar.value.strip().upper(),
                t_obs.value.strip().upper(), 
                t_cos.value.strip(), 
                t_ven.value.strip(), 
                t_sto.value.strip()
            ]
            
            lbl_status.value = "⏳ Registrando..."
            modal.open = False
            page.update()
            threading.Thread(target=_hilo_guardar_nuevo_movil, args=(id_doc, nombre_hoja, datos_formulario), daemon=True).start()

        def _hilo_guardar_nuevo_movil(id_doc, nombre_hoja, d_l):
            try:
                datos_inventario = obtener_datos_inventario(id_doc)
                filas_ram = datos_inventario.get(nombre_hoja, [])
                mapa_h = mapear_hoja_dinamico(filas_ram)
                nueva_f = [""] * 20
                f_h = datetime.now().strftime("%d/%m/%Y")

                def asignar(columna_nombre, valor_celda):
                    col = mapa_h.get(columna_nombre)
                    if col: nueva_f[col - 1] = valor_celda

                asignar("CODIGO", d_l[0])       
                asignar("DESCRIPCION", d_l[1])   
                asignar("MARCA", d_l[2])         
                asignar("OBS", d_l[3])           
                asignar("COSTO", d_l[4])         
                asignar("VENTA", d_l[5])         
                asignar("INGRESOS", f_h)
                asignar("FECHA_REG", f_h)
                 
                c_s = mapa_h.get("CAJAS") or mapa_h.get("STOCK")
                if c_s: nueva_f[c_s - 1] = d_l[6]

                filas_ram.insert(min(3, len(filas_ram)), nueva_f)

                if CLIENTE_GLOBAL:
                    doc = CLIENTE_GLOBAL.open_by_key(id_doc)
                    hoja = doc.worksheet(nombre_hoja)
                    hoja.insert_row(nueva_f, index=4, value_input_option='USER_ENTERED')
                    lbl_status.value = f"🎉 ¡PRODUCTO REGISTRADO EN FILA 4! Código: {d_l[0]}"
                    lbl_status.color = "green"
            except Exception as ex:
                lbl_status.value = f"❌ Error de Escritura: {str(ex)}"
                lbl_status.color = "red"
            page.update()

        modal = ft.AlertDialog(
            title=ft.Text(f"REGISTRO NUEVO: {drop_hojas.value}"),
            content=ft.Column([t_cod, t_des, t_mar, t_obs, t_cos, t_ven, t_sto], height=340, scroll="adaptive"),
            actions=[ft.TextButton(content=ft.Text("Cancelar"), on_click=cerrar_modal),
                     ft.TextButton(content=ft.Text("Guardar"), on_click=confirmar_guardado)]
        )
        page.overlay.append(modal)
        modal.open = True
        page.update()

    # --- PARTE 5 ---
    def _hilo_cargar_pestanas(id_inv):
        try:
            datos_inventario = obtener_datos_inventario(id_inv)
            if datos_inventario:
                nombres_hojas = ["TODAS"] + list(datos_inventario.keys())
                drop_hojas.options = [ft.dropdown.Option(n) for n in nombres_hojas]
                drop_hojas.value = "TODAS"
                lbl_status.value = "📊 Pestañas listas para filtrar"
                lbl_status.color = "green"
        except Exception as e:
            drop_hojas.options = [ft.dropdown.Option("TODAS")]
            drop_hojas.value = "TODAS"
            lbl_status.value = "⚠️ Error de red al cargar pestañas"
            lbl_status.color = "red"
        page.update()

    def cambiar_inventario(e):
        nombre = e.control.value
        if nombre in IDS_INVENTARIOS:
            inventario_actual["nombre"] = nombre
            inventario_actual["id"] = IDS_INVENTARIOS[nombre]
            lbl_status.value = f"⏳ Cargando inventario: {nombre}..."
            lbl_status.color = "orange"
            page.update()
            threading.Thread(target=_hilo_cargar_pestanas, args=(IDS_INVENTARIOS[nombre],), daemon=True).start()

    drop_inventarios = ft.Dropdown(
        label="Seleccionar Base de Bodega",
        options=[ft.dropdown.Option(k) for k in IDS_INVENTARIOS.keys()],
        width=340,
        on_select=cambiar_inventario
    )

    seccion_busqueda = ft.Row([
        txt_busqueda,
        ft.Container(
            content=ft.TextButton(content=ft.Text("📸 CÁMARA", color="white", weight="bold"), on_click=ejecutar_escaneo_camara),
            bgcolor="blue800", border_radius=4, padding=1
        ),
        ft.Container(
            content=ft.TextButton(content=ft.Text("🔍 BUSCAR", color="white", weight="bold"), on_click=iniciar_busqueda),
            bgcolor="green700", border_radius=4, padding=1
        )
    ], alignment=ft.MainAxisAlignment.CENTER)

    # --- PARTE 6 ---
    seccion_operaciones = ft.Row([
        txt_cantidad_operacion,
        ft.ElevatedButton("INGRESO", on_click=lambda e: procesar_inventario("INGRESO"), bgcolor="green", color="white"),
        ft.ElevatedButton("EGRESO", on_click=lambda e: procesar_inventario("EGRESO"), bgcolor="red", color="white")
    ], alignment=ft.MainAxisAlignment.CENTER)

    grid_campos_layout = ft.Column([
        ft.Row([c_codigo, c_descripcion], alignment=ft.MainAxisAlignment.CENTER),
        ft.Row([c_marca, c_obs1], alignment=ft.MainAxisAlignment.CENTER),
        ft.Row([c_obs2, c_costo], alignment=ft.MainAxisAlignment.CENTER),
        ft.Row([c_venta, c_codaux], alignment=ft.MainAxisAlignment.CENTER),
    ], spacing=10)

    def ir_a_panel_busqueda(nombre_inv, id_inv):
        inventario_actual["nombre"] = nombre_inv
        inventario_actual["id"] = id_inv
        
        # ENCENDEMOS DE GOLPE TODOS LOS CONTROLES PARA PODER TRABAJAR
        c_codigo.visible = True
        c_descripcion.visible = True
        c_marca.visible = True
        c_obs1.visible = True
        c_obs2.visible = True
        c_costo.visible = True
        c_venta.visible = True
        c_codaux.visible = True
        lbl_stock.visible = True
        txt_cantidad_operacion.visible = True
        drop_hojas.visible = True
        txt_busqueda.visible = True
        lista_resultados.visible = True
        
        # ... El resto de tus líneas de page.clean() e interfaces de la Parte 6 continúan exactamente igual ...

        drop_hojas.options = [ft.dropdown.Option("TODAS")]
        drop_hojas.value = "TODAS"
        txt_busqueda.value = ""
        c_codigo.value, c_descripcion.value, c_marca.value, c_obs1.value, c_costo.value, c_venta.value, c_obs2.value, c_codaux.value = "", "", "", "", "", "", "", ""
        lbl_stock.value = "STOCK: 0"
        lista_resultados.controls.clear()
        
        # CORRECCIÓN DE VELOCIDAD: Avisamos que estamos extrayendo todo a la RAM local
        lbl_status.value = "⚡ Cargando base de datos completa a la memoria RAM..."
        lbl_status.color = "orange"

        page.clean()
        page.appbar = ft.AppBar(
            title=ft.Text(nombre_inv, size=16), bgcolor="blue800",
            leading=ft.TextButton(content=ft.Text("< BODEGAS", color="white"), on_click=lambda e: ir_a_menu_principal())
        )

        fila_filtros = ft.Row([drop_hojas, ft.TextButton(content=ft.Text("+ NUEVO PRODUCTO", color="green"), on_click=mostrar_modal_nuevo_producto)], alignment=ft.MainAxisAlignment.SPACE_BETWEEN, width=350)
        btn_camara_cont = ft.Container(content=ft.TextButton(content=ft.Text("📷 CÁMARA", color="white", weight="bold"), on_click=ejecutar_escaneo_camara), bgcolor="#7f8c8d", border_radius=4, padding=1)
        barra_escaneo = ft.Row([txt_busqueda, btn_camara_cont, ft.TextButton(content=ft.Text("BUSCAR", color="white"), on_click=iniciar_busqueda)], width=350)
        
        fila_operaciones = ft.Row([
            lbl_stock,
            txt_cantidad_operacion,
            ft.Container(content=ft.TextButton(content=ft.Text(" INGRESO ", color="white", weight="bold"), on_click=lambda e: procesar_inventario("INGRESO")), bgcolor="green", border_radius=4, padding=2),
            ft.Container(content=ft.TextButton(content=ft.Text(" EGRESO ", color="white", weight="bold"), on_click=lambda e: procesar_inventario("EGRESO")), bgcolor="red", border_radius=4, padding=2)
        ], alignment=ft.MainAxisAlignment.CENTER, width=350)

        page.add(
            fila_filtros, ft.Divider(height=5),
            barra_escaneo, lbl_status,
            ft.Container(content=lista_resultados, border=ft.border.all(1, "grey"), border_radius=6, padding=3, width=350),
            ft.Divider(height=5),
            fila_operaciones,
            ft.Divider(height=5),
            grid_campos_layout
        )
        
        # OBLIGAMOS A LEER LA CACHÉ EN RAM EN SEGUNDO PLANO
        # La primera vez del día tarda unos 3 segundos en bajar todo Google Sheets. 
        # A partir de ahí, las pestañas y las búsquedas se cargan en 0.01 segundos.
        threading.Thread(target=_hilo_cargar_pestanas, args=(id_inv,), daemon=True).start()
        page.update()

    def ir_a_menu_principal():
        page.clean()
        page.appbar = ft.AppBar(title=ft.Text("BODEGAS C.A.R."), bgcolor="blue800", center_title=True)
        logo = ft.Row([ft.Text("🏢 BODEGAS C.A.R.", size=24, weight="bold", color="white")], alignment=ft.MainAxisAlignment.CENTER)
        columna_menu = ft.Column(controls=[logo, ft.Divider(height=15, color="transparent")], alignment=ft.MainAxisAlignment.CENTER, horizontal_alignment=ft.CrossAxisAlignment.CENTER)
        
        for nombre, id_drive in IDS_INVENTARIOS.items():
            def crear_evento(n=nombre, i=id_drive): 
                return lambda e: ir_a_panel_busqueda(n, i)
            
            columna_menu.controls.append(
                ft.TextButton(content=ft.Text(nombre, size=14, weight="bold", color="white"), on_click=crear_evento(), width=350, height=48)
            )
            columna_menu.controls.append(ft.Container(height=5))

        page.add(columna_menu)
        page.update()

    # REVISIÓN DE SEGURIDAD: 4 espacios exactos al inicio
    mostrar_pantalla_login()

# --- ARRANQUE COMO SERVIDOR LOCAL ACCESIBLE ---
if __name__ == "__main__":
    ft.app(target=main, port=8550, host="0.0.0.0", view=ft.AppView.WEB_BROWSER)
