import os
import requests
import json
import time
import csv
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from collections import defaultdict
from datetime import datetime, timedelta
import webbrowser
import re
import threading
import glob
from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.parse
# ⬇️ NOVO: Import do stock.py (ficheiro na mesma pasta ou Desktop)
import sys
sys.path.append(os.path.expanduser("~/Desktop"))
import stock
import comentarios #modulo de comentarios
# Import defensivo do motor de pesquisa visual
# (se falhar, o Dimo.py continua a funcionar sem o HTML do motor)
try:
    import motor_pesquisa_visual
    _MOTOR_DISPONIVEL = True
except Exception as e:
    print(f"⚠️ Aviso: motor_pesquisa_visual.py não pôde ser importado: {e}")
    print("   O Dimo.py vai continuar, mas o motor de pesquisa NÃO será regenerado.")
    motor_pesquisa_visual = None
    _MOTOR_DISPONIVEL = False

# ============================================================================
# SERVIDOR PROXY PARA RESOLVER CORS - CORRIGIDO
# ============================================================================

# ============================================================================
# SERVIDOR PROXY PARA RESOLVER CORS - COM RENOVAÇÃO AUTOMÁTICA DE TOKEN
# ============================================================================

class ProxyHandler(BaseHTTPRequestHandler):
    """Handler para o servidor proxy que encaminha requisições para a API"""
    
    # Variáveis de classe
    token = None
    api_instance = None  # Referência à instância da API para renovar token
    username = None
    password = None
    
    def do_GET(self):
        """Processa requisições GET"""
        parsed_path = urllib.parse.urlparse(self.path)
        
        # Verificar se é uma requisição para a API
        if parsed_path.path.startswith('/api/workOrderReader/Get'):
            # Extrair o ID da query string
            query_params = urllib.parse.parse_qs(parsed_path.query)
            ot_id = query_params.get('id', [''])[0]
            
            if ot_id:
                self._proxy_request_with_retry(ot_id)
            else:
                self._send_error(400, "ID da OT não fornecido")
        else:
            self._send_error(404, "Endpoint não encontrado")
    
    def _proxy_request_with_retry(self, ot_id, max_retries=2):
        """
        Tenta fazer a requisição, renovando o token se necessário
        """
        for attempt in range(max_retries + 1):
            try:
                # Obter o token
                token = ProxyHandler.token
                
                if not token:
                    print("❌ Proxy: Token não disponível. A renovar...")
                    if self._renovar_token():
                        token = ProxyHandler.token
                    else:
                        self._send_error(401, "Não foi possível obter token.")
                        return
                
                url = f"https://prod.mydimomaintmx.cloud/SERRATEC/api/workOrderReader/Get?id={ot_id}"
                
                headers = {
                    "Authorization": f"Bearer {token}",
                    "Content-Type": "application/json",
                    "tenant": "SERRATEC"
                }
                
                print(f"📤 Proxy: A buscar detalhes da OT {ot_id} (tentativa {attempt + 1})")
                
                response = requests.get(url, headers=headers, timeout=30)
                
                # Se for 401 e ainda houver tentativas, renovar token e tentar novamente
                if response.status_code == 401 and attempt < max_retries:
                    print(f"⚠️ Proxy: Token expirado (401). A renovar...")
                    if self._renovar_token():
                        print("✅ Proxy: Token renovado. A tentar novamente...")
                        continue  # Tentar novamente com o novo token
                    else:
                        print("❌ Proxy: Falha ao renovar token.")
                        self._send_error(401, "Token expirado e não foi possível renovar.")
                        return
                
                # Sucesso ou erro final
                self.send_response(response.status_code)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Access-Control-Allow-Origin', '*')
                self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')
                self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
                self.end_headers()
                
                self.wfile.write(response.content)
                print(f"✅ Proxy: Resposta enviada (status {response.status_code})")
                return
                
            except requests.exceptions.Timeout:
                if attempt < max_retries:
                    print(f"⚠️ Proxy: Timeout, a tentar novamente...")
                    continue
                self._send_error(504, "Timeout ao contactar a API")
                return
            except requests.exceptions.ConnectionError:
                if attempt < max_retries:
                    print(f"⚠️ Proxy: Erro de ligação, a tentar novamente...")
                    continue
                self._send_error(502, "Erro de ligação à API")
                return
            except Exception as e:
                if attempt < max_retries:
                    print(f"⚠️ Proxy: Erro {e}, a tentar novamente...")
                    continue
                self._send_error(500, f"Erro interno: {str(e)}")
                return
        
        # Se chegou aqui, todas as tentativas falharam
        self._send_error(500, "Todas as tentativas falharam.")
    
    def _renovar_token(self):
        """
        Renova o token usando Selenium
        """
        try:
            api = ProxyHandler.api_instance
            username = ProxyHandler.username
            password = ProxyHandler.password
            
            if not api or not username or not password:
                print("❌ Proxy: Credenciais não disponíveis para renovar token.")
                return False
            
            print("🔑 Proxy: A renovar token com Selenium...")
            
            # Forçar novo login
            if api.login_with_selenium(username, password, headless=True, force=True):
                token = api.token
                if token:
                    ProxyHandler.token = token
                    print(f"✅ Proxy: Token renovado com sucesso: {token[:30]}...")
                    return True
                else:
                    print("❌ Proxy: Token não foi obtido após login.")
                    return False
            else:
                print("❌ Proxy: Falha no login para renovar token.")
                return False
                
        except Exception as e:
            print(f"❌ Proxy: Erro ao renovar token: {e}")
            return False
    
    def do_OPTIONS(self):
        """Responde a requisições OPTIONS (preflight CORS)"""
        self.send_response(200)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
        self.end_headers()
    
    def _send_error(self, code, message):
        """Envia uma resposta de erro"""
        self.send_response(code)
        self.send_header('Content-Type', 'application/json')
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()
        
        error_response = json.dumps({"error": message, "code": code}).encode('utf-8')
        self.wfile.write(error_response)
        print(f"❌ Proxy: Erro {code} - {message}")
    
    def log_message(self, format, *args):
        """Log simplificado"""
        print(f"📡 Proxy: {format % args}")

def iniciar_servidor_proxy(porta=8888):
    """Inicia o servidor proxy numa thread separada"""
    try:
        server = HTTPServer(('localhost', porta), ProxyHandler)
        
        print(f"🚀 Servidor proxy iniciado em http://localhost:{porta}")
        print(f"📡 Endpoint: http://localhost:{porta}/api/workOrderReader/Get?id=OT_ID")
        print("=" * 60)
        
        server_thread = threading.Thread(target=server.serve_forever, daemon=True)
        server_thread.start()
        
        return server
    except OSError as e:
        if "Address already in use" in str(e):
            print(f"❌ Porta {porta} já está em uso. Tente outra porta.")
            print("   Exemplo: iniciar_servidor_proxy(porta=8889)")
        raise

def configurar_proxy(api_instance, username, password):
    """Configura o proxy com a instância da API e credenciais"""
    ProxyHandler.api_instance = api_instance
    ProxyHandler.username = username
    ProxyHandler.password = password
    if api_instance and api_instance.token:
        ProxyHandler.token = api_instance.token
        print(f"✅ Proxy configurado com token: {api_instance.token[:30]}...")
    else:
        print("⚠️ Proxy configurado mas sem token disponível.")
# ============================================================================
# CLASSE PRINCIPAL DimoMaintAPI
# ============================================================================

class DimoMaintAPI:
    def __init__(self, base_url="https://prod.mydimomaintmx.cloud"):
        self.base_url = base_url
        self.session = requests.Session()
        self.token = None
        self.token_file = os.path.join(os.path.expanduser("~/Desktop"), "dimo_token.json")
        self.driver = None
        self.estados_cache = {}
        self.estados_workrequest_cache = {}
        self.token_expiry = None
        
    def _load_token(self):
        """Carrega o token do ficheiro"""
        try:
            if os.path.exists(self.token_file):
                with open(self.token_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    self.token = data.get('token')
                    self.token_expiry = data.get('expiry')
                    if self.token and self.token_expiry:
                        expiry = datetime.fromisoformat(self.token_expiry)
                        if datetime.now() < expiry - timedelta(minutes=5):
                            print(f"✅ Token carregado do ficheiro (válido até {self.token_expiry})")
                            self.session.cookies.set('MX-Access-Token', self.token)
                            return True
                        else:
                            print("⚠️ Token expirado. A renovar...")
                            return False
            return False
        except Exception as e:
            print(f"⚠️ Erro ao carregar token: {e}")
            return False
    
    def _save_token(self):
        """Guarda o token no ficheiro"""
        try:
            expiry = datetime.now() + timedelta(hours=1)
            data = {
                'token': self.token,
                'expiry': expiry.isoformat(),
                'timestamp': datetime.now().isoformat()
            }
            with open(self.token_file, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
            print(f"✅ Token guardado (válido até {expiry.isoformat()})")
            return True
        except Exception as e:
            print(f"⚠️ Erro ao guardar token: {e}")
            return False
    
    def login_with_selenium(self, username, password, headless=False, force=False):
        """Faz login usando Selenium, reutilizando token se possível"""
        
        if not force and self._load_token():
            return True
        
        print("🌐 A iniciar navegador para login...")
        
        options = Options()
        
        if headless:
            print("🔇 Modo headless (sem janela visível)")
            options.add_argument("--headless=new")
        else:
            print("🪟 Modo visível (janela aberta)")
        
        options.add_argument("--disable-blink-features=AutomationControlled")
        options.add_experimental_option("excludeSwitches", ["enable-automation"])
        options.add_experimental_option('useAutomationExtension', False)
        options.add_argument("--window-size=1280,720")
        options.add_argument("--no-sandbox")
        options.add_argument("--disable-dev-shm-usage")
        options.add_argument("--disable-gpu")
        options.add_argument("--disable-logging")
        options.add_argument("--log-level=3")
        options.add_argument("--ignore-certificate-errors")
        options.add_argument("--ignore-ssl-errors")
        
        try:
            service = Service(ChromeDriverManager().install())
            self.driver = webdriver.Chrome(service=service, options=options)
            self.driver.implicitly_wait(10)
            
            print("📄 A carregar página de login...")
            self.driver.get(f"{self.base_url}/SERRATEC/account")
            time.sleep(3)
            
            if self._check_already_logged_in():
                print("✅ Já estás logado!")
                return self._extract_token_from_browser()
            
            print("🔍 A preencher credenciais...")
            time.sleep(2)
            
            username_field = self._find_element([
                "input[name='username']",
                "input[name='email']",
                "input[type='text']",
                "#username",
                "#email"
            ])
            
            if username_field:
                username_field.clear()
                username_field.send_keys(username)
                print("✅ Username preenchido")
            else:
                print("❌ Campo de username não encontrado")
                self.driver.quit()
                return False
            
            password_field = self._find_element([
                "input[name='password']",
                "input[type='password']",
                "#password"
            ])
            
            if password_field:
                password_field.clear()
                password_field.send_keys(password)
                print("✅ Password preenchida")
            else:
                print("❌ Campo de password não encontrado")
                self.driver.quit()
                return False
            
            print("🔍 A submeter login...")
            login_button = self._find_element([
                "button[type='submit']",
                "input[type='submit']",
                "button.btn-primary",
                "#login-button"
            ])
            
            if login_button:
                login_button.click()
                print("✅ Login submetido (botão)")
            else:
                password_field.send_keys("\n")
                print("✅ Login submetido (Enter)")
            
            print("⏳ A aguardar resposta do login...")
            
            for i in range(30):
                time.sleep(1)
                
                if self._check_already_logged_in():
                    print("✅ Login bem-sucedido!")
                    success = self._extract_token_from_browser()
                    if success:
                        self._save_token()
                    return success
                
                if self._handle_session_modal():
                    print("✅ Modal de sessão ativa resolvido!")
                    time.sleep(2)
                    if self._check_already_logged_in():
                        print("✅ Login bem-sucedido após resolver modal!")
                        success = self._extract_token_from_browser()
                        if success:
                            self._save_token()
                        return success
                
                if i % 5 == 0 and i > 0:
                    print(f"⏳ A aguardar... ({i}s)")
            
            print(f"❌ Login falhou. URL: {self.driver.current_url}")
            self.driver.quit()
            return False
                
        except Exception as e:
            print(f"❌ Erro: {e}")
            if self.driver:
                self.driver.quit()
            return False
    
    def _handle_session_modal(self):
        """Lida especificamente com o modal 'A conta... deseja desligar?'"""
        try:
            modal_indicators = [
                "A conta a que está a tentar ligar-se está a ser utilizada",
                "conta a que está a tentar ligar-se",
                "Gostaria de a desligar",
                "desligar",
                "Dimo Maint"
            ]
            
            page_source = self.driver.page_source
            
            modal_present = False
            for indicator in modal_indicators:
                if indicator.lower() in page_source.lower():
                    modal_present = True
                    break
            
            if not modal_present:
                return False
            
            print("⚠️ Modal de sessão ativa detetado!")
            
            try:
                sim_button = self.driver.find_element(By.XPATH, "//button[contains(text(), 'Sim')]")
                if sim_button.is_displayed() and sim_button.is_enabled():
                    print("✅ A clicar em 'Sim' para desligar a outra sessão...")
                    sim_button.click()
                    time.sleep(2)
                    return True
            except:
                pass
            
            try:
                result = self.driver.execute_script("""
                    const buttons = document.querySelectorAll('button');
                    for (const btn of buttons) {
                        if (btn.textContent && btn.textContent.trim() === 'Sim') {
                            btn.click();
                            return true;
                        }
                    }
                    return false;
                """)
                if result:
                    print("✅ Clicou em 'Sim' via JavaScript")
                    time.sleep(2)
                    return True
            except:
                pass
            
            return False
            
        except Exception as e:
            print(f"⚠️ Erro ao lidar com modal: {e}")
            return False
    
    def _check_already_logged_in(self):
        """Verifica se já está logado"""
        try:
            current_url = self.driver.current_url
            if '/SERRATEC' in current_url and 'account' not in current_url:
                cookies = self.driver.get_cookies()
                for cookie in cookies:
                    if cookie['name'] == 'MX-Access-Token':
                        return True
            return False
        except:
            return False
    
    def _find_element(self, selectors):
        """Tenta encontrar um elemento por múltiplos selectors"""
        for selector in selectors:
            try:
                element = self.driver.find_element(By.CSS_SELECTOR, selector)
                if element.is_displayed():
                    return element
            except:
                pass
        return None
    
    def _extract_token_from_browser(self):
        """Extrai o token do navegador"""
        try:
            cookies = self.driver.get_cookies()
            for cookie in cookies:
                if cookie['name'] == 'MX-Access-Token':
                    self.token = cookie['value']
                    print(f"🔑 Token obtido: {self.token[:30]}...")
                    self.session.cookies.set(cookie['name'], cookie['value'])
                    self.driver.quit()
                    return True
            
            token = self.driver.execute_script("""
                const cookies = document.cookie.split('; ');
                for (const c of cookies) {
                    const parts = c.trim().split('=');
                    if (parts[0] === 'MX-Access-Token') {
                        return parts[1];
                    }
                }
                return null;
            """)
            
            if token:
                self.token = token
                self.session.cookies.set('MX-Access-Token', token)
                print(f"🔑 Token obtido via JS: {token[:30]}...")
                self.driver.quit()
                return True
            
            print("⚠️ Token não encontrado")
            self.driver.quit()
            return False
            
        except Exception as e:
            print(f"❌ Erro: {e}")
            if self.driver:
                self.driver.quit()
            return False
    
    def _carregar_estados(self):
        """Carrega a lista de estados do sistema através da API"""
        if not self.token:
            return {}
        
        if self.estados_cache:
            return self.estados_cache
        
        try:
            print("📊 A carregar lista de estados do sistema...")
            
            query = """
            query {
              states {
                items {
                  id
                  name
                }
                totalCount
              }
            }
            """
            
            headers = {
                "Authorization": f"Bearer {self.token}",
                "Content-Type": "application/json",
                "controller": "workorder",
                "action": "list",
                "tenant": "SERRATEC"
            }
            
            response = self.session.post(
                f"{self.base_url}/graphql",
                headers=headers,
                json={"query": query},
                timeout=30
            )
            
            if response.status_code == 200:
                data = response.json()
                if "data" in data and "states" in data["data"]:
                    items = data["data"]["states"]["items"]
                    for item in items:
                        state_id = item.get('id', '')
                        state_name = item.get('name', '')
                        if state_id and state_name:
                            self.estados_cache[state_id] = state_name
                    print(f"✅ Carregados {len(self.estados_cache)} estados do sistema")
                    return self.estados_cache
                    
        except Exception as e:
            print(f"⚠️ Erro ao carregar estados: {e}")
        
        self.estados_cache = {
            '8b959fa9-92da-48af-83a8-8960b86e5d3e' : 'Por fazer',
            '08059026-de6e-8b90-bde1-4ae92f676643': 'Suspenso',
            '32fbd621-0458-4e3e-968d-77ffc56c892a': 'Em curso',
            'd17964fa-1174-ed36-eb9c-f50d58980859': 'A aguardar intervenção externa',
            '658ee047-7054-cae7-8e55-b9ab543b67c7': 'A aguardar material',
            'df9e4a1c-4670-8242-3843-d0a3e8d175da': 'A aguardar reparação da peça',
            '7b419f98-ebc2-dcd4-8db2-312cdfbe8072' : 'A aguardar validação qualidade',
            '8240c123-1dd5-860b-6c08-041805b85dbf' : 'A aguardar validação segurança',
            'ecdfc42d-e39a-d2e4-1455-78fc9d35dc05' : 'Em analise com o fabricante',
            '85c3cbee-2503-4bf5-8271-b13b41b02a08' : 'Concluída',
            '529a1f42-ebb9-0688-3523-54e7b4dd5783' : 'A aguardar documentação',
            '1ee4fca1-325d-948d-af5d-bc5fefa53635' : 'Em teste',
            '0ef45d0d-1901-959e-8727-869a178e0f6b' : 'Reparação corretiva temporaria',
            'd67894ef-2d53-4e63-a840-71089d2cc851' : 'Anulada',
            'ada41e02-5e6a-4dc7-9f5c-e6b14340f1a6' : 'Encerrada',

        }
        return self.estados_cache
    
    def _carregar_estados_workrequest(self):
        """Carrega a lista de estados de workrequest do sistema"""
        if not self.token:
            return {}
        
        if self.estados_workrequest_cache:
            return self.estados_workrequest_cache
        
        try:
            print("📊 A carregar estados de workrequest...")
            
            self.estados_workrequest_cache = {
                'f890de1b-26b3-45a9-b8b8-9887afbf3360': 'Em curso',
                'a7386d0d-5244-4811-94d9-1e77b58693f4': 'A processar',
                '50f51fa2-045d-48c2-92e7-a81637ea33f4': 'Encerrado',
                '70f32bd4-875b-48e9-a51d-133ebe30f0f8': 'Efetuado',
                'a579a008-d90b-48fe-850c-43ff3f6c4de7' : 'Anulado',

            }
            return self.estados_workrequest_cache
            
        except Exception as e:
            print(f"⚠️ Erro ao carregar estados de workrequest: {e}")
            return {}
    
    def _obter_nome_estado(self, state_id):
        """Obtém o nome do estado a partir do ID"""
        if state_id is None:
            return 'Desconhecido'
        
        state_id_str = str(state_id)
        
        if not self.estados_cache:
            self._carregar_estados()
        
        if state_id_str in self.estados_cache:
            return self.estados_cache[state_id_str]
        
        return state_id_str[:8] + '...'
    
    def _obter_nome_estado_workrequest(self, state_id):
        """Obtém o nome do estado de workrequest a partir do ID"""
        if state_id is None:
            return 'Desconhecido'
        
        state_id_str = str(state_id)
        
        if not self.estados_workrequest_cache:
            self._carregar_estados_workrequest()
        
        if state_id_str in self.estados_workrequest_cache:
            return self.estados_workrequest_cache[state_id_str]
        
        mapping = {
            'f890de1b-26b3-45a9-b8b8-9887afbf3360': 'Em curso',
            'a7386d0d-5244-4811-94d9-1e77b58693f4': 'Por fazer',
            '50f51fa2-045d-48c2-92e7-a81637ea33f4': 'Encerrado',
            '70f32bd4-875b-48e9-a51d-133ebe30f0f8': 'Concluído',
        }
        
        if state_id_str in mapping:
            return mapping[state_id_str]
        
        return state_id_str[:8] + '...'
    
    def get_workorders(self, skip=0, take=100):
        """Obtém ordens de trabalho"""
        if not self.token:
            print("❌ Erro: Token não definido!")
            return None
            
        query = """
        query workorders($skip: Int, $take: Int) {
          workorder(
            skip: $skip
            take: $take
            order: [{beginDateTime: DESC}]
            where: {and: [{archived: {eq: false}}]}
            filter: {}
          ) {
            items {
              id
              code
              name
              assetCode
              assetName
              brand
              brandModel
              serialNumber
              criticality
              beginDateTime
              closed
              stateId
              technician
              issuerFullName
              urgency
              remedy
              alert
              archived
            }
            totalCount
          }
        }
        """
        
        headers = {
            "Authorization": f"Bearer {self.token}",
            "Content-Type": "application/json",
            "controller": "workorder",
            "action": "list",
            "tenant": "SERRATEC"
        }
        
        try:
            response = self.session.post(
                f"{self.base_url}/graphql",
                headers=headers,
                json={
                    "query": query,
                    "variables": {
                        "skip": skip,
                        "take": take
                    }
                },
                timeout=30
            )
            
            if response.status_code == 200:
                return response.json()
            elif response.status_code == 401:
                print("⚠️ Token expirado!")
                self.token = None
                if os.path.exists(self.token_file):
                    os.remove(self.token_file)
                return None
            else:
                print(f"❌ Erro HTTP: {response.status_code}")
                return None
        except Exception as e:
            print(f"❌ Erro: {e}")
            return None
    
    def get_all_workorders(self, limit=5000):
        """
        Obtém as últimas N ordens de trabalho (mais recentes)
        - limit: número máximo de OTs a buscar (default 2000)
        """
        if not self.token:
            print("❌ Erro: Não estás autenticado!")
            return []
        
        self._carregar_estados()
        
        all_items = []
        skip = 0
        take = min(100, limit)
        
        print(f"📥 A buscar as últimas {limit} OTs (mais recentes)...")
        
        while len(all_items) < limit:
            print(f"📥 A buscar registos de {skip} a {skip + take}...")
            result = self.get_workorders(skip, take)
            
            if result is None:
                break
            
            if "errors" in result:
                print(f"❌ Erro GraphQL: {result['errors']}")
                break
            
            data = result["data"]["workorder"]
            items = data["items"]
            total_count = data["totalCount"]
            
            all_items.extend(items)
            print(f"✅ Obtidos {len(items)} registos (total: {len(all_items)}/{min(limit, total_count)})")
            
            if len(all_items) >= limit:
                break
            
            if len(all_items) >= total_count:
                break
            
            skip += take
            time.sleep(0.5)
        
        print(f"📊 Total de OTs extraídas: {len(all_items)} (limite: {limit})")
        return all_items
    
    # ========================================================================
    # MÉTODOS PARA WORK REQUESTS (PEDIDOS DE INTERVENÇÃO)
    # ========================================================================
    
    def get_workrequests(self, skip=0, take=100):
        """Obtém pedidos de intervenção (work requests)"""
        if not self.token:
            print("❌ Erro: Token não definido!")
            return None
            
        query = """
        query workrequests($skip: Int, $take: Int) {
          workrequest(
            skip: $skip
            take: $take
            order: [{creationDateTime: DESC}]
            filter: {}
          ) {
            items {
              id
              code
              name
              asset
              brand
              brandModel
              serialNumber
              criticality
              creationDateTime
              stateId
              stateCode
              requesterFullName
              urgency
              alert
              archived
              color
            }
            totalCount
          }
        }
        """
        
        headers = {
            "Authorization": f"Bearer {self.token}",
            "Content-Type": "application/json",
            "controller": "workrequest",
            "action": "list",
            "tenant": "SERRATEC"
        }
        
        try:
            response = self.session.post(
                f"{self.base_url}/graphql",
                headers=headers,
                json={
                    "query": query,
                    "variables": {
                        "skip": skip,
                        "take": take
                    }
                },
                timeout=30
            )
            
            if response.status_code == 200:
                return response.json()
            elif response.status_code == 401:
                print("⚠️ Token expirado!")
                self.token = None
                if os.path.exists(self.token_file):
                    os.remove(self.token_file)
                return None
            else:
                print(f"❌ Erro HTTP: {response.status_code}")
                return None
        except Exception as e:
            print(f"❌ Erro: {e}")
            return None
    
    def get_all_workrequests(self, limit=5000):
        """
        Obtém os últimos N pedidos de intervenção (mais recentes)
        - limit: número máximo de PIs a buscar (default 2000)
        """
        if not self.token:
            print("❌ Erro: Não estás autenticado!")
            return []
        
        self._carregar_estados_workrequest()
        
        all_items = []
        skip = 0
        take = min(100, limit)
        
        print(f"📥 A buscar os últimos {limit} PIs (mais recentes)...")
        
        while len(all_items) < limit:
            print(f"📥 A buscar pedidos de {skip} a {skip + take}...")
            result = self.get_workrequests(skip, take)
            
            if result is None:
                break
            
            if "errors" in result:
                print(f"❌ Erro GraphQL: {result['errors']}")
                break
            
            data = result["data"]["workrequest"]
            items = data["items"]
            total_count = data["totalCount"]
            
            all_items.extend(items)
            print(f"✅ Obtidos {len(items)} pedidos (total: {len(all_items)}/{min(limit, total_count)})")
            
            if len(all_items) >= limit:
                break
            
            if len(all_items) >= total_count:
                break
            
            skip += take
            time.sleep(0.5)
        
        print(f"📊 Total de PIs extraídos: {len(all_items)} (limite: {limit})")
        return all_items
    
    def export_workrequests_to_csv(self, workrequests, filename):
        """Exporta pedidos de intervenção para CSV"""
        if not workrequests:
            print("❌ Sem dados para exportar")
            return False
        
        fieldnames = [
            'code', 'name', 'asset', 'brand', 'brandModel',
            'serialNumber', 'criticality', 'creationDateTime',
            'stateId', 'stateCode', 'requesterFullName', 'urgency',
            'alert', 'archived', 'color', 'id'
        ]
        
        try:
            os.makedirs(os.path.dirname(filename), exist_ok=True)
            
            with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
                writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';')
                writer.writeheader()
                
                for wr in workrequests:
                    row = {k: wr.get(k, '') for k in fieldnames}
                    writer.writerow(row)
            
            print(f"✅ Exportado {len(workrequests)} pedidos para:")
            print(f"   📄 {filename}")
            return True
        except Exception as e:
            print(f"❌ Erro ao guardar CSV: {e}")
            return False
    
    def gerar_relatorio_ultimos_pis(self, workrequests):
        """Gera relatório com os pedidos de intervenção"""
        if not workrequests:
            print("❌ Sem dados para gerar relatório")
            return []
        
        relatorio = []
        
        for wr in workrequests:
            state_id = wr.get('stateId', '')
            estado_nome = self._obter_nome_estado_workrequest(state_id)
            
            cor = wr.get('color', '#FFFFFF')
            mapa_cores = {
                '#FFEB3B': '🟡 Amarelo',
                '#ff3b3b': '🔴 Vermelho',
                '#FECD55': '🟠 Laranja',
                '#FFFFFF': '⚪ Branco'
            }
            cor_nome = mapa_cores.get(cor, cor)
            
            relatorio.append({
                'Código PI': wr.get('code', ''),
                'Nome': wr.get('name', ''),
                'Equipamento': wr.get('asset', ''),
                'Marca': wr.get('brand', ''),
                'Modelo': wr.get('brandModel', ''),
                'Nº Série': wr.get('serialNumber', ''),
                'Criticidade': wr.get('criticality', ''),
                'Data Criação': wr.get('creationDateTime', '')[:16] if wr.get('creationDateTime') else '',
                'Estado': estado_nome,
                'Estado Código': wr.get('stateCode', ''),
                'Solicitante': wr.get('requesterFullName', ''),
                'Urgência': wr.get('urgency', ''),
                'Alerta': 'Sim' if wr.get('alert') else 'Não',
                'Arquivado': 'Sim' if wr.get('archived') else 'Não',
                'Cor': cor_nome,
                'Alterações': wr.get('_alteracoes')
            })
        
        print(f"📊 Relatório gerado com {len(relatorio)} pedidos")
        return relatorio
    
    def gerar_html_pis(self, relatorio, filename, refresh_interval=300):
        """Gera um HTML visual e interativo com os pedidos de intervenção"""
        print(f"📝 A gerar HTML visual dos PIs: {filename}")
        
        if not relatorio:
            print("❌ Sem dados para gerar o relatório HTML")
            return False
        
        total_pis = len(relatorio)
        
        estados = defaultdict(int)
        urgencias = defaultdict(int)
        criticidades = defaultdict(int)
        for r in relatorio:
            estado = r.get('Estado', 'Desconhecido')
            urgencia = r.get('Urgência', 'Desconhecida')
            criticidade = r.get('Criticidade', 'Desconhecida')
            estados[estado] += 1
            urgencias[urgencia] += 1
            criticidades[criticidade] += 1
        
        ativos = sum(1 for r in relatorio if r.get('Estado') in ['Em curso', 'Por fazer'])
        concluidos = sum(1 for r in relatorio if r.get('Estado') in ['Concluído', 'Encerrado'])
        
        now = datetime.now()
        novos_pis = 0
        for r in relatorio:
            data_criacao = r.get('Data Criação', '')
            if data_criacao:
                try:
                    data_str = data_criacao.strip()
                    if 'T' in data_str:
                        data_str = data_str.replace('T', ' ')
                    if len(data_str) > 16:
                        data_str = data_str[:16]
                    data_obj = datetime.strptime(data_str, "%Y-%m-%d %H:%M")
                    diff = (now - data_obj).total_seconds()
                    if diff < 86400:
                        novos_pis += 1
                except:
                    pass
        
        refresh_minutes = refresh_interval // 60 if refresh_interval > 0 else 0
        
        html_content = f'''
<!DOCTYPE html>
<html lang="pt">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>📋 Pedidos de Intervenção (PIs) - Últimos {total_pis}</title>
'''
        
        if refresh_interval > 0:
            html_content += f'    <meta http-equiv="refresh" content="{refresh_interval}">\n'
        
        html_content += f'''
    <style>
        * {{ margin: 0; padding: 0; box-sizing: border-box; }}
        
        body {{ 
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            min-height: 100vh;
            padding: 20px;
        }}
        
        .container {{
            max-width: 1400px;
            margin: 0 auto;
        }}
        
        .header {{
            background: linear-gradient(135deg, #1a1a2e 0%, #0f3460 100%);
            color: white;
            padding: 30px;
            border-radius: 20px;
            margin-bottom: 30px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
            position: relative;
            overflow: hidden;
        }}
        
        .header::before {{
            content: '';
            position: absolute;
            top: -50%;
            right: -20%;
            width: 500px;
            height: 500px;
            background: rgba(255,255,255,0.05);
            border-radius: 50%;
        }}
        
        .header h1 {{
            font-size: 32px;
            margin-bottom: 10px;
            position: relative;
            z-index: 1;
        }}
        
        .header .subtitle {{
            opacity: 0.9;
            font-size: 16px;
            position: relative;
            z-index: 1;
        }}
        
        .header .timestamp {{
            opacity: 0.7;
            font-size: 13px;
            margin-top: 5px;
            position: relative;
            z-index: 1;
        }}
        
        .refresh-info {{
            background: rgba(255,255,255,0.15);
            padding: 6px 15px;
            border-radius: 20px;
            font-size: 13px;
            display: inline-block;
            margin-top: 8px;
            position: relative;
            z-index: 1;
        }}
        .refresh-info .countdown {{
            font-weight: bold;
            color: #ffeb3b;
        }}
        
        .novos-badge {{
            display: inline-block;
            background: #ed0e22;
            color: white;
            padding: 2px 12px;
            border-radius: 20px;
            font-size: 12px;
            font-weight: 700;
            margin-left: 10px;
            animation: pulse 1.5s ease-in-out infinite;
            position: relative;
            z-index: 1;
        }}
        
        @keyframes pulse {{
            0% {{ transform: scale(1); }}
            50% {{ transform: scale(1.1); }}
            100% {{ transform: scale(1); }}
        }}
        
        .stats {{
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
            gap: 15px;
            margin-top: 20px;
            position: relative;
            z-index: 1;
        }}
        
        .stat-item {{
            background: rgba(255,255,255,0.15);
            backdrop-filter: blur(10px);
            padding: 15px;
            border-radius: 12px;
            text-align: center;
            transition: transform 0.3s;
        }}
        
        .stat-item:hover {{
            transform: scale(1.05);
        }}
        
        .stat-item .number {{
            font-size: 28px;
            font-weight: bold;
        }}
        
        .stat-item .label {{
            font-size: 12px;
            opacity: 0.8;
            margin-top: 5px;
        }}
        
        .stat-item .novos-destaque {{
            background: #ff4757;
            color: white;
            border-radius: 50%;
            padding: 0 8px;
            font-size: 14px;
            margin-left: 5px;
        }}
        
        .filters {{
            background: white;
            padding: 15px 20px;
            border-radius: 12px;
            margin-bottom: 25px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.08);
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            align-items: center;
        }}
        
        .filters label {{
            font-weight: 600;
            color: #555;
            font-size: 14px;
        }}
        
        .filters select, .filters input {{
            padding: 8px 12px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 14px;
            background: white;
            transition: all 0.3s;
        }}
        
        .filters select:focus, .filters input:focus {{
            outline: none;
            border-color: #f5576c;
            box-shadow: 0 0 0 3px rgba(245, 87, 108, 0.1);
        }}
        
        .filters .clear-btn {{
            padding: 8px 16px;
            background: #f8f9fa;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            cursor: pointer;
            font-size: 13px;
            transition: all 0.3s;
        }}
        
        .filters .clear-btn:hover {{
            background: #e9ecef;
            border-color: #f5576c;
        }}
        
        .btn-refresh {{
            padding: 8px 16px;
            background: #f5576c;
            color: white;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-size: 13px;
            transition: background 0.3s;
        }}
        .btn-refresh:hover {{ background: #d6304a; }}
        
        .filter-stats {{
            margin-left: auto;
            font-size: 13px;
            color: #666;
        }}
        
        .pi-grid {{
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
            gap: 20px;
        }}
        
        .pi-card {{
            background: white;
            border-radius: 16px;
            overflow: hidden;
            box-shadow: 0 4px 15px rgba(0,0,0,0.08);
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            cursor: default;
            border-left: 6px solid #667eea;
            position: relative;
        }}
        
        .pi-card.novo {{
            border-left-color: #ff4757;
            animation: highlightNew 2s ease;
        }}
        
        .pi-card.novo::after {{
            content: '🆕 NOVO';
            position: absolute;
            top: 10px;
            right: 10px;
            background: #ff4757;
            color: white;
            padding: 3px 12px;
            border-radius: 20px;
            font-size: 11px;
            font-weight: 700;
            animation: pulse 1.5s ease-in-out infinite;
            z-index: 5;
        }}
        
        .pi-card.muito-novo {{
            border-left-color: #ff6b81;
            background: #fff5f6;
        }}
        
        @keyframes highlightNew {{
            0% {{ transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 71, 87, 0.4); }}
            50% {{ transform: scale(1.02); box-shadow: 0 0 30px 10px rgba(255, 71, 87, 0.2); }}
            100% {{ transform: scale(1); box-shadow: 0 4px 15px rgba(0,0,0,0.08); }}
        }}
        
        .pi-card:hover {{
            transform: translateY(-5px);
            box-shadow: 0 8px 30px rgba(0,0,0,0.15);
        }}
        
        .pi-card-header {{
            padding: 15px 20px 10px;
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            background: #fafafa;
        }}
        
        .pi-card-header .pi-code {{
            font-weight: 700;
            font-size: 16px;
            color: #333;
            font-family: monospace;
        }}
        
        .pi-card-header .pi-status {{
            font-size: 12px;
            padding: 3px 10px;
            border-radius: 20px;
            font-weight: 600;
        }}
        
        .pi-card-body {{
            padding: 15px 20px 20px;
        }}
        
        .pi-card-body .pi-title {{
            font-size: 15px;
            font-weight: 600;
            color: #333;
            margin-bottom: 10px;
        }}
        
        .pi-info {{
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 8px;
        }}
        
        .pi-info-item {{
            padding: 6px 10px;
            background: #f8f9fa;
            border-radius: 6px;
        }}
        
        .pi-info-item .label {{
            font-size: 10px;
            color: #888;
            text-transform: uppercase;
            letter-spacing: 0.3px;
            font-weight: 600;
        }}
        
        .pi-info-item .value {{
            font-size: 13px;
            color: #333;
            margin-top: 1px;
            font-weight: 500;
        }}
        
        .pi-info-item .value.code {{
            font-family: monospace;
            font-size: 12px;
        }}
        
        .pi-info-item.full-width {{
            grid-column: 1 / -1;
        }}
        
        .status-Em_curso {{ background: #fff3cd; color: #856404; }}
        .status-Por_fazer {{ background: #e2e3e5; color: #383d41; }}
        .status-Concluído {{ background: #d4edda; color: #155724; }}
        .status-Encerrado {{ background: #d4edda; color: #155724; }}
        .status-default {{ background: #e9ecef; color: #495057; }}
        
        .urgency-Muito_Urgente {{ background: #f8d7da; color: #721c24; }}
        .urgency-Urgente {{ background: #f5c6cb; color: #721c24; }}
        .urgency-Moderado {{ background: #fff3cd; color: #856404; }}
        .urgency-Não_urgente {{ background: #d4edda; color: #155724; }}
        .urgency-default {{ background: #e9ecef; color: #495057; }}
        
        .criticality-Muito_Crítico {{ background: #f8d7da; color: #721c24; }}
        .criticality-Crítico {{ background: #f5c6cb; color: #721c24; }}
        .criticality-Não_Crítico {{ background: #d4edda; color: #155724; }}
        .criticality-default {{ background: #e9ecef; color: #495057; }}
        
        .badge {{
            display: inline-block;
            padding: 2px 10px;
            border-radius: 12px;
            font-size: 11px;
            font-weight: 600;
        }}
        
        .no-data {{
            text-align: center;
            padding: 60px 20px;
            color: #666;
        }}
        
        .no-data .icon {{
            font-size: 64px;
            margin-bottom: 20px;
        }}
        
        .footer {{
            text-align: center;
            margin-top: 30px;
            padding: 20px;
            color: #666;
            font-size: 13px;
        }}
        
        @media (max-width: 768px) {{
            .pi-grid {{
                grid-template-columns: 1fr;
            }}
            
            .pi-info {{
                grid-template-columns: 1fr;
            }}
            
            .header h1 {{
                font-size: 24px;
            }}
            
            .stats {{
                grid-template-columns: repeat(2, 1fr);
            }}
            
            .filters {{
                flex-direction: column;
                align-items: stretch;
            }}
            
            .filter-stats {{
                margin-left: 0;
                text-align: center;
            }}
        }}
        
        .pi-card {{
            opacity: 0;
            animation: fadeInUp 0.6s ease forwards;
        }}
        
        @keyframes fadeInUp {{
            from {{
                opacity: 0;
                transform: translateY(20px);
            }}
            to {{
                opacity: 1;
                transform: translateY(0);
            }}
        }}
        
        .pi-card:nth-child(1) {{ animation-delay: 0.03s; }}
        .pi-card:nth-child(2) {{ animation-delay: 0.06s; }}
        .pi-card:nth-child(3) {{ animation-delay: 0.09s; }}
        .pi-card:nth-child(4) {{ animation-delay: 0.12s; }}
        .pi-card:nth-child(5) {{ animation-delay: 0.15s; }}
        .pi-card:nth-child(6) {{ animation-delay: 0.18s; }}
        .pi-card:nth-child(7) {{ animation-delay: 0.21s; }}
        .pi-card:nth-child(8) {{ animation-delay: 0.24s; }}
        .pi-card:nth-child(9) {{ animation-delay: 0.27s; }}
        .pi-card:nth-child(10) {{ animation-delay: 0.3s; }}
        
        .pi-card.novo {{
            animation: highlightNew 0.8s ease forwards, fadeInUp 0.6s ease forwards;
        }}
        
        ::-webkit-scrollbar {{
            width: 8px;
        }}
        
        ::-webkit-scrollbar-track {{
            background: #f1f1f1;
            border-radius: 10px;
        }}
        
        ::-webkit-scrollbar-thumb {{
            background: #888;
            border-radius: 10px;
        }}
        
        ::-webkit-scrollbar-thumb:hover {{
            background: #555;
        }}
        
        .export-btn {{
            padding: 8px 20px;
            background: white;
            color: #f5576c;
            border: 2px solid white;
            border-radius: 8px;
            cursor: pointer;
            font-weight: 600;
            transition: all 0.3s;
        }}
        
        .export-btn:hover {{
            background: rgba(255,255,255,0.2);
            color: white;
        }}
        
        .novo-tag {{
            display: inline-block;
            background: #ff4757;
            color: white;
            padding: 1px 10px;
            border-radius: 12px;
            font-size: 10px;
            font-weight: 700;
            margin-left: 6px;
            animation: pulse 1.5s ease-in-out infinite;
        }}
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <div style="display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap;">
                <div>
                    <h1>📋 Pedidos de Intervenção (PIs)</h1>
                    <div class="subtitle">Últimos {total_pis} pedidos (mais recentes)</div>
                    <div class="timestamp">📅 Gerado em {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}</div>
'''
        
        if refresh_interval > 0:
            html_content += f'''
                    <div class="refresh-info">
                        🔄 Atualização automática a cada <span class="countdown">{refresh_minutes}</span> minutos
                        <span id="countdownTimer" style="margin-left:10px;color:#ffeb3b;">({refresh_interval}s)</span>
                    </div>
'''
        
        html_content += f'''
                    <div style="margin-top: 5px; position: relative; z-index: 1;">
                        <span style="background: rgba(255,255,255,0.15); padding: 4px 12px; border-radius: 20px; font-size: 13px;">
                            🆕 <strong>{novos_pis}</strong> novos nas últimas 24h
                        </span>
                    </div>
                </div>
                <div style="margin-top: 10px;">
                    <button class="btn-refresh" onclick="location.reload()">🔄 Atualizar Agora</button>
                    <button class="export-btn" onclick="window.print()">🖨️ Imprimir / PDF</button>
                </div>
            </div>
            
            <div class="stats">
                <div class="stat-item">
                    <div class="number">{total_pis}</div>
                    <div class="label">📋 Total de PIs</div>
                </div>
                <div class="stat-item">
                    <div class="number">{ativos}</div>
                    <div class="label">🔄 Ativos</div>
                </div>
                <div class="stat-item">
                    <div class="number">{concluidos}</div>
                    <div class="label">✅ Concluídos</div>
                </div>
'''
        
        for urgencia, count in sorted(urgencias.items(), key=lambda x: -x[1]):
            emoji = '🔴' if 'Muito' in urgencia else '🟠' if 'Urgente' in urgencia else '🟡' if 'Moderado' in urgencia else '🟢'
            html_content += f'''
                <div class="stat-item">
                    <div class="number">{count}</div>
                    <div class="label">{emoji} {urgencia}</div>
                </div>
            '''
        
        html_content += f'''
            </div>
        </div>
        
        <div class="filters">
            <label>📊 Estado:</label>
            <select id="statusFilter" onchange="filterCards()">
                <option value="all">Todos</option>
'''
        
        estados_ordenados = sorted([e for e in estados.keys() if e and e != 'Desconhecido'])
        for estado in estados_ordenados:
            html_content += f'                <option value="{estado}">{estado}</option>\n'
        
        html_content += '''
            </select>
            
            <label>🔴 Urgência:</label>
            <select id="urgencyFilter" onchange="filterCards()">
                <option value="all">Todas</option>
                <option value="Muito Urgente">🔴 Muito Urgente</option>
                <option value="Urgente">🟠 Urgente</option>
                <option value="Moderado">🟡 Moderado</option>
                <option value="Não urgente">🟢 Não urgente</option>
            </select>
            
            <label>⚠️ Criticidade:</label>
            <select id="criticalityFilter" onchange="filterCards()">
                <option value="all">Todas</option>
                <option value="Muito Crítico">🔴 Muito Crítico</option>
                <option value="Crítico">🟠 Crítico</option>
                <option value="Não Crítico">🟢 Não Crítico</option>
            </select>
            
            <label>🆕 Novos:</label>
            <select id="novosFilter" onchange="filterCards()">
                <option value="all">Todos</option>
                <option value="novos">🆕 Apenas Novos (24h)</option>
                <option value="muito_novos">🆕🆕 Muito Novos (2h)</option>
            </select>
            
            <label>🔍 Pesquisar:</label>
            <input type="text" id="searchInput" onkeyup="filterCards()" placeholder="Código, equipamento ou descrição...">
            
            <button class="clear-btn" onclick="clearFilters()">✖️ Limpar</button>
            
            <span class="filter-stats" id="filterStats">Mostrando <span id="visibleCount">0</span> de <span id="totalCount">0</span></span>
        </div>
        
        <div class="pi-grid" id="piGrid">
'''
        
        if not relatorio:
            html_content += '''
            <div class="no-data">
                <div class="icon">📭</div>
                <h2>Nenhum pedido de intervenção encontrado</h2>
                <p>Verifique se há dados disponíveis na API.</p>
            </div>
        '''
        else:
            for idx, r in enumerate(relatorio):
                estado = r.get('Estado', 'Desconhecido')
                estado_class = estado.replace(' ', '_')
                if estado_class not in ['Em_curso', 'Por_fazer', 'Concluído', 'Encerrado']:
                    estado_class = 'default'
                
                urgencia = r.get('Urgência', 'Não definida')
                urgencia_class = urgencia.replace(' ', '_')
                if urgencia_class not in ['Muito_Urgente', 'Urgente', 'Moderado', 'Não_urgente']:
                    urgencia_class = 'default'
                
                criticidade = r.get('Criticidade', 'Não definida')
                criticidade_class = criticidade.replace(' ', '_')
                if criticidade_class not in ['Muito_Crítico', 'Crítico', 'Não_Crítico']:
                    criticidade_class = 'default'
                
                border_color = '#f5576c' if 'Muito Urgente' in urgencia else '#f5a623' if 'Urgente' in urgencia else '#4a90d9'
                
                data_criacao = r.get('Data Criação', '')
                solicitante = r.get('Solicitante', '-')
                equipamento = r.get('Equipamento', '-')
                alteracoes = r.get('Alterações')
                change_badge = ''
                change_description = ''
                if alteracoes:
                    change_badge = '<div class="change-timer" data-change-until="{}">🔔 Alterado</div>'.format(
                        alteracoes['at'] + stock.ChangeTracker.DURACAO_SEGUNDOS
                    )
                    change_description = '<div style="font-size:12px;color:#a55b00;margin-bottom:8px;">🔔 {}</div>'.format(
                        ' • '.join(alteracoes['fields'])
                    )
                
                eh_novo = False
                eh_muito_novo = False
                if data_criacao:
                    try:
                        data_str = data_criacao.strip()
                        if 'T' in data_str:
                            data_str = data_str.replace('T', ' ')
                        if len(data_str) > 16:
                            data_str = data_str[:16]
                        data_obj = datetime.strptime(data_str, "%Y-%m-%d %H:%M")
                        diff = (now - data_obj).total_seconds()
                        if diff < 7200:
                            eh_muito_novo = True
                            eh_novo = True
                        elif diff < 86400:
                            eh_novo = True
                    except:
                        pass
                
                card_classes = "pi-card"
                if alteracoes:
                    card_classes += " alterado"
                if eh_muito_novo:
                    card_classes += " muito-novo novo"
                elif eh_novo:
                    card_classes += " novo"
                
                html_content += f'''
            <div class="{card_classes}" style="border-left-color: {border_color}; {'box-shadow: 0 0 0 3px #ff9800, 0 8px 20px rgba(255,152,0,.25);' if alteracoes else ''}" data-status="{estado}" data-urgency="{urgencia}" data-criticality="{criticidade}" data-novo="{str(eh_novo).lower()}" data-muito-novo="{str(eh_muito_novo).lower()}" data-search="{r.get('Código PI', '')} {equipamento} {r.get('Nome', '')}">
                <div class="pi-card-header">
                    <div>
                        <div class="pi-code">{r.get('Código PI', 'N/A')}</div>
                        <div style="font-size: 11px; color: #888; margin-top: 2px;">{equipamento}</div>
                    </div>
                    <div>
                        <span class="pi-status status-{estado_class}">{estado}</span>
                        {change_badge}
'''
                
                if eh_novo:
                    html_content += f'''
                        <span class="novo-tag">🆕 NOVO</span>
'''
                
                html_content += f'''
                    </div>
                </div>
                <div class="pi-card-body">
                    {change_description}
                    <div class="pi-title">{r.get('Nome', 'Sem título')}</div>
                    <div class="pi-info">
                        <div class="pi-info-item">
                            <div class="label">🕐 Data Criação</div>
                            <div class="value">{data_criacao}</div>
                        </div>
                        <div class="pi-info-item">
                            <div class="label">👤 Solicitante</div>
                            <div class="value">{solicitante}</div>
                        </div>
                        <div class="pi-info-item">
                            <div class="label">🔴 Urgência</div>
                            <div class="value"><span class="badge urgency-{urgencia_class}">{urgencia}</span></div>
                        </div>
                        <div class="pi-info-item">
                            <div class="label">⚠️ Criticidade</div>
                            <div class="value"><span class="badge criticality-{criticidade_class}">{criticidade}</span></div>
                        </div>
                        <div class="pi-info-item">
                            <div class="label">🏷️ Marca</div>
                            <div class="value">{r.get('Marca', '-')}</div>
                        </div>
                        <div class="pi-info-item">
                            <div class="label">📦 Modelo</div>
                            <div class="value">{r.get('Modelo', '-')}</div>
                        </div>
                        <div class="pi-info-item full-width">
                            <div class="label">🔢 Nº Série</div>
                            <div class="value code">{r.get('Nº Série', '-')}</div>
                        </div>
                    </div>
                </div>
            </div>
            '''
        
        html_content += f'''
        </div>
        
        <div class="footer">
            <p>📋 Relatório de Pedidos de Intervenção (PIs) • Últimos {total_pis} pedidos • {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}</p>
            <p style="font-size: 11px; opacity: 0.6;">Total de PIs: {total_pis} | 🆕 Novos (24h): {novos_pis}</p>
        </div>
    </div>
    
    <script>
        function filterCards() {{
            const statusFilter = document.getElementById('statusFilter').value;
            const urgencyFilter = document.getElementById('urgencyFilter').value;
            const criticalityFilter = document.getElementById('criticalityFilter').value;
            const novosFilter = document.getElementById('novosFilter').value;
            const searchInput = document.getElementById('searchInput').value.toLowerCase().trim();
            const cards = document.querySelectorAll('.pi-card');
            let visible = 0;
            
            cards.forEach(card => {{
                const status = card.dataset.status || '';
                const urgency = card.dataset.urgency || '';
                const criticality = card.dataset.criticality || '';
                const isNovo = card.dataset.novo === 'true';
                const isMuitoNovo = card.dataset.muitoNovo === 'true';
                const search = card.dataset.search || '';
                
                let show = true;
                
                if (statusFilter !== 'all' && status !== statusFilter) {{
                    show = false;
                }}
                
                if (show && urgencyFilter !== 'all' && urgency !== urgencyFilter) {{
                    show = false;
                }}
                
                if (show && criticalityFilter !== 'all' && criticality !== criticalityFilter) {{
                    show = false;
                }}
                
                if (show && novosFilter === 'novos' && !isNovo) {{
                    show = false;
                }}
                
                if (show && novosFilter === 'muito_novos' && !isMuitoNovo) {{
                    show = false;
                }}
                
                if (show && searchInput && !search.toLowerCase().includes(searchInput)) {{
                    show = false;
                }}
                
                if (show) {{
                    card.style.display = 'block';
                    visible++;
                }} else {{
                    card.style.display = 'none';
                }}
            }});
            
            document.getElementById('visibleCount').textContent = visible;
            document.getElementById('totalCount').textContent = cards.length;
        }}
        
        function clearFilters() {{
            document.getElementById('statusFilter').value = 'all';
            document.getElementById('urgencyFilter').value = 'all';
            document.getElementById('criticalityFilter').value = 'all';
            document.getElementById('novosFilter').value = 'all';
            document.getElementById('searchInput').value = '';
            filterCards();
        }}

        function updateChangeTimers() {{
            document.querySelectorAll('.change-timer').forEach(function(timer) {{
                const seconds = Math.max(0, Math.ceil(Number(timer.dataset.changeUntil) - Date.now() / 1000));
                const hours = Math.floor(seconds / 3600);
                const minutes = Math.floor((seconds % 3600) / 60);
                const secs = seconds % 60;
                timer.textContent = seconds ? '🔔 Alterado • ' + hours + 'h ' + minutes + 'm ' + secs + 's' : 'Atualize a página';
            }});
        }}
        
        document.addEventListener('DOMContentLoaded', function() {{
            filterCards();
            updateChangeTimers();
            setInterval(updateChangeTimers, 1000);
        }});
    </script>
</body>
</html>
        '''
        
        try:
            os.makedirs(os.path.dirname(filename), exist_ok=True)
            
            with open(filename, 'w', encoding='utf-8') as f:
                f.write(html_content)
            
            print(f"✅ Relatório HTML dos PIs gerado com sucesso!")
            print(f"   📄 {filename}")
            print(f"   📊 Tamanho: {os.path.getsize(filename)} bytes")
            return True
        except Exception as e:
            print(f"❌ Erro ao gerar HTML: {e}")
            import traceback
            traceback.print_exc()
            return False
    
    def export_to_csv(self, workorders, filename):
        """Exporta para CSV"""
        if not workorders:
            print("❌ Sem dados para exportar")
            return False
        
        fieldnames = [
            'id', 'name', 'assetCode', 'assetName', 'brand', 'brandModel',
            'serialNumber', 'criticality', 'beginDateTime', 'closed',
            'stateId', 'technician', 'issuerFullName', 'urgency',
            'remedy', 'alert', 'archived'
        ]
        
        try:
            os.makedirs(os.path.dirname(filename), exist_ok=True)
            
            with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
                writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';')
                writer.writeheader()
                
                for wo in workorders:
                    row = {k: wo.get(k, '') for k in fieldnames}
                    writer.writerow(row)
            
            print(f"✅ Exportado {len(workorders)} registos para:")
            print(f"   📄 {filename}")
            return True
        except Exception as e:
            print(f"❌ Erro ao guardar CSV: {e}")
            return False

    # ========================================================================
    # FUNÇÕES PARA RELATÓRIO VISUAL DAS ÚLTIMAS OTs
    # ========================================================================
    
    def _formatar_valor(self, valor):
        """Formata um valor para string, tratando diferentes tipos"""
        if valor is None:
            return ''
        if isinstance(valor, bool):
            return 'Sim' if valor else 'Não'
        if isinstance(valor, (int, float)):
            return str(valor)
        if isinstance(valor, str):
            return valor
        return str(valor)
    
    def _formatar_data(self, valor):
        """Formata uma data para exibição (apenas os primeiros 10 caracteres)"""
        valor_str = self._formatar_valor(valor)
        if valor_str and len(valor_str) >= 10:
            return valor_str[:10]
        return valor_str
    
    def _normalizar_codigo_maquina(self, codigo):
        """Normaliza o código da máquina removendo sufixos (A, B, C, etc.)"""
        if not codigo:
            return codigo
        
        codigo_normalizado = re.sub(r'[A-Da-d]$', '', codigo)
        
        if codigo_normalizado == codigo:
            return codigo
        
        return codigo_normalizado
    
    def _is_ot_planeada(self, wo):
        """Verifica se uma OT é planeada"""
        palavras_planeadas = [
            'anual', 'semestral', 'trimestral', 'mensal', 'planeada', 'planejada',
            'preventiva', 'revisão', 'revisao', 'calibração', 'calibracao',
            'inspeção', 'inspecao', 'manutenção programada', 'manutencao programada'
        ]
        
        nome = wo.get('name', '').lower()
        
        for palavra in palavras_planeadas:
            if palavra in nome:
                return True
        
        return False
    
    def gerar_relatorio_ultimas_ots(self, workorders, num_ultimas=1):
        """Gera um relatório com as últimas N ordens de trabalho por máquina"""
        if not workorders:
            print("❌ Sem dados para gerar relatório")
            return []
        
        workorders_filtradas = []
        ots_planeadas = 0
        
        for wo in workorders:
            if self._is_ot_planeada(wo):
                ots_planeadas += 1
            else:
                workorders_filtradas.append(wo)
        
        print(f"📊 Filtradas {ots_planeadas} OTs planeadas")
        print(f"📊 Restam {len(workorders_filtradas)} OTs não-planeadas")
        
        maquinas = defaultdict(list)
        
        for wo in workorders_filtradas:
            machine_id = wo.get('assetCode') or wo.get('assetName') or 'SEM_MÁQUINA'
            if machine_id:
                machine_id_normalizado = self._normalizar_codigo_maquina(machine_id)
                maquinas[machine_id_normalizado].append(wo)
        
        print(f"📊 Encontradas {len(maquinas)} máquinas (após normalização)")
        
        for machine_id in maquinas:
            maquinas[machine_id].sort(
                key=lambda x: x.get('beginDateTime', ''),
                reverse=True
            )
        
        relatorio = []
        
        for machine_id, ots in sorted(maquinas.items()):
            ultimas = ots[:num_ultimas]
            
            for wo in ultimas:
                nome_maquina = wo.get('assetName', '')
                state_id = wo.get('stateId', '')
                estado_nome = self._obter_nome_estado(state_id)
                
                relatorio.append({
                    'Máquina': machine_id,
                    'Nome Máquina': self._formatar_valor(nome_maquina),
                    'ID OT': self._formatar_valor(wo.get('id', '')),
                    'Código OT': self._formatar_valor(wo.get('code', '')),
                    'Título': self._formatar_valor(wo.get('name', '')),
                    'Estado': estado_nome,
                    'Estado ID': self._formatar_valor(state_id),
                    'Urgência': self._formatar_valor(wo.get('urgency', '')),
                    'Data Início': self._formatar_data(wo.get('beginDateTime', '')),
                    'Data Fecho': self._formatar_data(wo.get('closed', '')),
                    'Técnico': self._formatar_valor(wo.get('technician', '')),
                    'Emissor': self._formatar_valor(wo.get('issuerFullName', '')),
                    'Solução': self._formatar_valor(wo.get('remedy', '')),
                    'Marca': self._formatar_valor(wo.get('brand', '')),
                    'Modelo': self._formatar_valor(wo.get('brandModel', '')),
                    'Série': self._formatar_valor(wo.get('serialNumber', '')),
                    'Criticidade': self._formatar_valor(wo.get('criticality', '')),
                    'Arquivada': 'Sim' if wo.get('archived') else 'Não',
                    'Alerta': 'Sim' if wo.get('alert') else 'Não',
                    'Total OTs': len(ots),
                    'Alterações': wo.get('_alteracoes')
                })
        
        print(f"📊 Relatório gerado com {len(relatorio)} registos")
        return relatorio
    
    def gerar_html_visual(self, relatorio, filename, refresh_interval=300):
        """
        Gera um HTML visual e interativo com as últimas OTs por máquina
        Ao clicar no ID da OT, faz uma requisição para o proxy local (resolução CORS)
        """
        print(f"📝 A gerar HTML visual: {filename}")
        
        if not relatorio:
            print("❌ Sem dados para gerar o relatório HTML")
            return False
        
        # Agrupar OTs por máquina
        maquinas = defaultdict(list)
        for r in relatorio:
            machine_id = r.get('Máquina', 'SEM_MÁQUINA')
            maquinas[machine_id].append(r)
        
        total_maquinas = len(maquinas)
        total_ots = len(relatorio)
        
        estados = defaultdict(int)
        urgencias = defaultdict(int)
        for r in relatorio:
            estado = r.get('Estado', 'Desconhecido')
            urgencia = r.get('Urgência', 'Desconhecida')
            estados[estado] += 1
            urgencias[urgencia] += 1
        
        ativas = sum(1 for r in relatorio if r.get('Estado') in ['Em curso', 'Por fazer', 'Suspenso'])
        encerradas = sum(1 for r in relatorio if r.get('Estado') == 'Encerrada')
        
        refresh_minutes = refresh_interval // 60 if refresh_interval > 0 else 0
        
        # Proxy local para resolver CORS
        proxy_url = "http://localhost:8888"
        
        html_content = f'''
    <!DOCTYPE html>
    <html lang="pt">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>📊 Últimas Ordens de Trabalho por Máquina</title>
    '''
        
        if refresh_interval > 0:
            html_content += f'    <meta http-equiv="refresh" content="{refresh_interval}">\n'
        
        html_content += f'''
        <style>
            * {{ margin: 0; padding: 0; box-sizing: border-box; }}
            
            body {{ 
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
                background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
                min-height: 100vh;
                padding: 20px;
            }}
            
            .container {{
                max-width: 1400px;
                margin: 0 auto;
            }}
            
            .header {{
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                padding: 30px;
                border-radius: 20px;
                margin-bottom: 30px;
                box-shadow: 0 10px 30px rgba(0,0,0,0.2);
                position: relative;
                overflow: hidden;
            }}
            
            .header::before {{
                content: '';
                position: absolute;
                top: -50%;
                right: -20%;
                width: 500px;
                height: 500px;
                background: rgba(255,255,255,0.05);
                border-radius: 50%;
            }}
            
            .header h1 {{
                font-size: 32px;
                margin-bottom: 10px;
                position: relative;
                z-index: 1;
            }}
            
            .header .subtitle {{
                opacity: 0.9;
                font-size: 16px;
                position: relative;
                z-index: 1;
            }}
            
            .header .timestamp {{
                opacity: 0.7;
                font-size: 13px;
                margin-top: 5px;
                position: relative;
                z-index: 1;
            }}
            
            .refresh-info {{
                background: rgba(255,255,255,0.15);
                padding: 6px 15px;
                border-radius: 20px;
                font-size: 13px;
                display: inline-block;
                margin-top: 8px;
                position: relative;
                z-index: 1;
            }}
            .refresh-info .countdown {{
                font-weight: bold;
                color: #ffeb3b;
            }}
            
            .stats {{
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
                gap: 15px;
                margin-top: 20px;
                position: relative;
                z-index: 1;
            }}
            
            .stat-item {{
                background: rgba(255,255,255,0.15);
                backdrop-filter: blur(10px);
                padding: 15px;
                border-radius: 12px;
                text-align: center;
                transition: transform 0.3s;
            }}
            
            .stat-item:hover {{
                transform: scale(1.05);
            }}
            
            .stat-item .number {{
                font-size: 28px;
                font-weight: bold;
            }}
            
            .stat-item .label {{
                font-size: 12px;
                opacity: 0.8;
                margin-top: 5px;
            }}
            
            .filters {{
                background: white;
                padding: 15px 20px;
                border-radius: 12px;
                margin-bottom: 25px;
                box-shadow: 0 2px 10px rgba(0,0,0,0.08);
                display: flex;
                flex-wrap: wrap;
                gap: 10px;
                align-items: center;
            }}
            
            .filters label {{
                font-weight: 600;
                color: #555;
                font-size: 14px;
            }}
            
            .filters select, .filters input {{
                padding: 8px 12px;
                border: 2px solid #e0e0e0;
                border-radius: 8px;
                font-size: 14px;
                background: white;
                transition: all 0.3s;
            }}
            
            .filters select:focus, .filters input:focus {{
                outline: none;
                border-color: #667eea;
                box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
            }}
            
            .filters .clear-btn {{
                padding: 8px 16px;
                background: #f8f9fa;
                border: 2px solid #e0e0e0;
                border-radius: 8px;
                cursor: pointer;
                font-size: 13px;
                transition: all 0.3s;
            }}
            
            .filters .clear-btn:hover {{
                background: #e9ecef;
                border-color: #667eea;
            }}
            
            .btn-refresh {{
                padding: 8px 16px;
                background: #667eea;
                color: white;
                border: none;
                border-radius: 8px;
                cursor: pointer;
                font-size: 13px;
                transition: background 0.3s;
            }}
            .btn-refresh:hover {{ background: #5a67d8; }}
            
            .filter-stats {{
                margin-left: auto;
                font-size: 13px;
                color: #666;
            }}
            
            .machine-grid {{
                display: grid;
                grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
                gap: 20px;
            }}
            
            .machine-card {{
                background: white;
                border-radius: 16px;
                overflow: hidden;
                box-shadow: 0 4px 15px rgba(0,0,0,0.08);
                transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                cursor: default;
            }}
            
            .machine-card:hover {{
                transform: translateY(-5px);
                box-shadow: 0 8px 30px rgba(0,0,0,0.15);
            }}
            
            .machine-card-header {{
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                padding: 15px 20px;
                display: flex;
                justify-content: space-between;
                align-items: center;
                cursor: pointer;
            }}
            
            .machine-card-header .machine-name {{
                font-size: 18px;
                font-weight: 600;
            }}
            
            .machine-card-header .machine-id {{
                font-size: 12px;
                opacity: 0.8;
                font-family: monospace;
            }}
            
            .machine-card-header .badge {{
                background: rgba(255,255,255,0.2);
                padding: 4px 12px;
                border-radius: 20px;
                font-size: 12px;
                font-weight: 600;
            }}
            
            .machine-card-header .expand-icon {{
                font-size: 20px;
                transition: transform 0.3s;
                margin-left: 10px;
            }}
            .machine-card-header .expand-icon.rotated {{
                transform: rotate(180deg);
            }}
            
            .machine-card-body {{
                padding: 20px;
            }}
            
            .ot-list {{
                display: none;
                margin-top: 10px;
                border-top: 1px solid #e9ecef;
                padding-top: 10px;
            }}
            .ot-list.open {{
                display: block;
            }}
            
            .ot-item {{
                padding: 10px 12px;
                background: #f8f9fa;
                border-radius: 8px;
                margin-bottom: 6px;
                border-left: 4px solid #667eea;
                cursor: pointer;
                transition: all 0.2s;
                display: flex;
                justify-content: space-between;
                align-items: center;
                flex-wrap: wrap;
                gap: 8px;
            }}
            .ot-item:hover {{
                background: #e9ecef;
            }}
            .ot-item .ot-code {{
                font-family: monospace;
                font-size: 13px;
                color: #667eea;
                font-weight: 600;
                cursor: pointer;
                text-decoration: underline;
            }}
            .ot-item .ot-code:hover {{
                color: #764ba2;
            }}
            .ot-item .ot-title {{
                font-size: 13px;
                color: #333;
                flex: 1;
            }}
            .ot-item .ot-state {{
                font-size: 11px;
                padding: 2px 10px;
                border-radius: 12px;
                font-weight: 600;
                white-space: nowrap;
            }}
            
            .ot-info {{
                display: grid;
                grid-template-columns: 1fr 1fr;
                gap: 10px;
            }}
            
            .ot-info-item {{
                padding: 8px 12px;
                background: #f8f9fa;
                border-radius: 8px;
            }}
            
            .ot-info-item .label {{
                font-size: 11px;
                color: #888;
                text-transform: uppercase;
                letter-spacing: 0.5px;
                font-weight: 600;
            }}
            
            .ot-info-item .value {{
                font-size: 14px;
                color: #333;
                margin-top: 2px;
                font-weight: 500;
            }}
            
            .ot-info-item .value.code {{
                font-family: monospace;
                font-size: 13px;
            }}
            
            .ot-info-item .value.clickable {{
                color: #667eea;
                text-decoration: underline;
                cursor: pointer;
            }}
            
            .ot-info-item .value.clickable:hover {{
                color: #764ba2;
            }}
            
            .ot-info-item.full-width {{
                grid-column: 1 / -1;
            }}
            
            .status-badge {{
                display: inline-block;
                padding: 4px 12px;
                border-radius: 20px;
                font-size: 12px;
                font-weight: 600;
            }}
            
            .status-Encerrada {{ background: #d4edda; color: #155724; }}
            .status-Em_curso {{ background: #fff3cd; color: #856404; }}
            .status-A_aguardar_documentação {{ background: #cce5ff; color: #004085; }}
            .status-Anulada {{ background: #f8d7da; color: #721c24; }}
            .status-Por_fazer {{ background: #e2e3e5; color: #383d41; }}
            .status-Suspenso {{ background: #f5c6cb; color: #721c24; }}
            .status-default {{ background: #e9ecef; color: #495057; }}
            
            .status-dot {{
                display: inline-block;
                width: 10px;
                height: 10px;
                border-radius: 50%;
                margin-right: 6px;
            }}
            .status-dot-Encerrada {{ background: #28a745; }}
            .status-dot-Em_curso {{ background: #ffc107; }}
            .status-dot-A_aguardar_documentação {{ background: #17a2b8; }}
            .status-dot-Anulada {{ background: #dc3545; }}
            .status-dot-Por_fazer {{ background: #6c757d; }}
            .status-dot-Suspenso {{ background: #fd7e14; }}
            .status-dot-default {{ background: #6c757d; }}
            
            .urgency-badge {{
                display: inline-block;
                padding: 4px 12px;
                border-radius: 20px;
                font-size: 12px;
                font-weight: 600;
            }}
            
            .urgency-Muito_Urgente {{ background: #f8d7da; color: #721c24; }}
            .urgency-Urgente {{ background: #f5c6cb; color: #721c24; }}
            .urgency-Moderado {{ background: #fff3cd; color: #856404; }}
            .urgency-Não_urgente {{ background: #d4edda; color: #155724; }}
            .urgency-default {{ background: #e9ecef; color: #495057; }}
            
            .criticality-badge {{
                display: inline-block;
                padding: 2px 10px;
                border-radius: 12px;
                font-size: 11px;
                font-weight: 600;
            }}
            
            .criticality-Muito_Crítico {{ background: #f8d7da; color: #721c24; }}
            .criticality-Crítico {{ background: #f5c6cb; color: #721c24; }}
            .criticality-Não_Crítico {{ background: #d4edda; color: #155724; }}
            .criticality-default {{ background: #e9ecef; color: #495057; }}
            
            .remedy-text {{
                font-size: 13px;
                color: #555;
                padding: 10px 12px;
                background: #f8f9fa;
                border-radius: 8px;
                margin-top: 10px;
                grid-column: 1 / -1;
                border-left: 4px solid #667eea;
            }}
            
            .no-data {{
                text-align: center;
                padding: 60px 20px;
                color: #666;
            }}
            
            .no-data .icon {{
                font-size: 64px;
                margin-bottom: 20px;
            }}
            
            .footer {{
                text-align: center;
                margin-top: 30px;
                padding: 20px;
                color: #666;
                font-size: 13px;
            }}
            
            /* ===== MODAL DETALHES DA OT ===== */
            .modal-overlay {{
                display: none;
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0,0,0,0.6);
                backdrop-filter: blur(4px);
                z-index: 1000;
                justify-content: center;
                align-items: center;
                animation: fadeIn 0.3s;
            }}
            .modal-overlay.active {{ display: flex; }}
            
            @keyframes fadeIn {{
                from {{ opacity: 0; }}
                to {{ opacity: 1; }}
            }}
            
            .modal {{
                background: white;
                border-radius: 16px;
                max-width: 700px;
                width: 95%;
                max-height: 90vh;
                overflow-y: auto;
                padding: 30px;
                box-shadow: 0 20px 60px rgba(0,0,0,0.3);
                animation: slideUp 0.3s ease;
            }}
            
            @keyframes slideUp {{
                from {{ transform: translateY(30px); opacity: 0; }}
                to {{ transform: translateY(0); opacity: 1; }}
            }}
            
            .modal-header {{
                display: flex;
                justify-content: space-between;
                align-items: flex-start;
                margin-bottom: 20px;
                padding-bottom: 15px;
                border-bottom: 2px solid #e9ecef;
            }}
            .modal-header h2 {{
                font-size: 18px;
                color: #1a1a2e;
                flex: 1;
                word-break: break-word;
            }}
            .modal-close {{
                background: none;
                border: none;
                font-size: 28px;
                color: #888;
                cursor: pointer;
                padding: 0 8px;
                transition: color 0.2s;
            }}
            .modal-close:hover {{ color: #333; }}
            
            .modal-loading {{
                text-align: center;
                padding: 40px;
                color: #888;
            }}
            .modal-loading .spinner {{
                display: inline-block;
                width: 40px;
                height: 40px;
                border: 4px solid #f3f3f3;
                border-top: 4px solid #667eea;
                border-radius: 50%;
                animation: spin 1s linear infinite;
                margin-bottom: 15px;
            }}
            @keyframes spin {{
                0% {{ transform: rotate(0deg); }}
                100% {{ transform: rotate(360deg); }}
            }}
            
            .modal-error {{
                text-align: center;
                padding: 40px;
                color: #dc3545;
            }}
            
            .modal-grid {{
                display: grid;
                grid-template-columns: 1fr 1fr;
                gap: 12px;
            }}
            .modal-grid .full {{
                grid-column: 1 / -1;
            }}
            .modal-item {{
                padding: 8px 12px;
                background: #f8f9fa;
                border-radius: 8px;
            }}
            .modal-item .label {{
                font-size: 11px;
                color: #888;
                text-transform: uppercase;
                letter-spacing: 0.3px;
                font-weight: 600;
            }}
            .modal-item .value {{
                font-size: 14px;
                color: #333;
                margin-top: 2px;
                font-weight: 500;
                word-break: break-word;
            }}
            .modal-item .value.code {{
                font-family: monospace;
                font-size: 13px;
            }}
            
            .modal-comments {{
                margin-top: 15px;
                padding: 15px;
                background: #f8f9fa;
                border-radius: 8px;
                border-left: 4px solid #667eea;
            }}
            .modal-comments h4 {{
                font-size: 14px;
                color: #333;
                margin-bottom: 10px;
            }}
            .modal-comment {{
                padding: 10px 12px;
                background: white;
                border-radius: 6px;
                margin-bottom: 8px;
                border-left: 3px solid #e9ecef;
            }}
            .modal-comment .comment-author {{
                font-size: 12px;
                font-weight: 600;
                color: #667eea;
            }}
            .modal-comment .comment-date {{
                font-size: 11px;
                color: #888;
                margin-left: 10px;
            }}
            .modal-comment .comment-text {{
                font-size: 13px;
                color: #333;
                margin-top: 4px;
                line-height: 1.5;
            }}
            .modal-comment .comment-text p {{
                margin-bottom: 4px;
            }}
            
            .modal-status {{
                display: inline-block;
                padding: 4px 12px;
                border-radius: 20px;
                font-size: 13px;
                font-weight: 600;
            }}
            .modal-status-Concluída {{ background: #d4edda; color: #155724; }}
            .modal-status-Encerrada {{ background: #e9ecef; color: #495057; }}
            .modal-status-Em_curso {{ background: #fff3cd; color: #856404; }}
            .modal-status-Por_fazer {{ background: #f8d7da; color: #721c24; }}
            .modal-status-Suspenso {{ background: #ffe5d0; color: #7b4a1e; }}
            
            @media (max-width: 768px) {{
                .machine-grid {{
                    grid-template-columns: 1fr;
                }}
                .ot-info {{
                    grid-template-columns: 1fr;
                }}
                .modal-grid {{
                    grid-template-columns: 1fr;
                }}
                .modal {{ padding: 20px; }}
                .header h1 {{ font-size: 24px; }}
                .stats {{ grid-template-columns: repeat(2, 1fr); }}
                .filters {{ flex-direction: column; align-items: stretch; }}
                .filter-stats {{ margin-left: 0; text-align: center; }}
            }}
            
            .machine-card {{
                opacity: 0;
                animation: fadeInUp 0.6s ease forwards;
            }}
            
            @keyframes fadeInUp {{
                from {{ opacity: 0; transform: translateY(20px); }}
                to {{ opacity: 1; transform: translateY(0); }}
            }}
            
            .machine-card:nth-child(1) {{ animation-delay: 0.05s; }}
            .machine-card:nth-child(2) {{ animation-delay: 0.1s; }}
            .machine-card:nth-child(3) {{ animation-delay: 0.15s; }}
            .machine-card:nth-child(4) {{ animation-delay: 0.2s; }}
            .machine-card:nth-child(5) {{ animation-delay: 0.25s; }}
            .machine-card:nth-child(6) {{ animation-delay: 0.3s; }}
            .machine-card:nth-child(7) {{ animation-delay: 0.35s; }}
            .machine-card:nth-child(8) {{ animation-delay: 0.4s; }}
            .machine-card:nth-child(9) {{ animation-delay: 0.45s; }}
            .machine-card:nth-child(10) {{ animation-delay: 0.5s; }}
            
            ::-webkit-scrollbar {{ width: 8px; }}
            ::-webkit-scrollbar-track {{ background: #f1f1f1; border-radius: 10px; }}
            ::-webkit-scrollbar-thumb {{ background: #888; border-radius: 10px; }}
            ::-webkit-scrollbar-thumb:hover {{ background: #555; }}
            
            .export-btn {{
                padding: 8px 20px;
                background: white;
                color: #667eea;
                border: 2px solid white;
                border-radius: 8px;
                cursor: pointer;
                font-weight: 600;
                transition: all 0.3s;
            }}
            .export-btn:hover {{
                background: rgba(255,255,255,0.2);
                color: white;
            }}
            
            .comments-section {{
                margin-top: 15px;
                border-top: 1px solid #e9ecef;
                padding-top: 15px;
            }}
            .comments-section h4 {{
                font-size: 14px;
                color: #333;
                margin-bottom: 10px;
            }}
            .no-comments {{
                color: #888;
                font-style: italic;
                font-size: 13px;
            }}
            
            .ot-item .ot-state {{
                font-size: 11px;
                padding: 2px 10px;
                border-radius: 12px;
                font-weight: 600;
                white-space: nowrap;
            }}
            .ot-state-Encerrada {{ background: #d4edda; color: #155724; }}
            .ot-state-Em_curso {{ background: #fff3cd; color: #856404; }}
            .ot-state-A_aguardar_documentação {{ background: #cce5ff; color: #004085; }}
            .ot-state-Anulada {{ background: #f8d7da; color: #721c24; }}
            .ot-state-Por_fazer {{ background: #e2e3e5; color: #383d41; }}
            .ot-state-Suspenso {{ background: #f5c6cb; color: #721c24; }}
            .ot-state-default {{ background: #e9ecef; color: #495057; }}
        </style>
    </head>
    <body>
        <div class="container">
            <div class="header">
                <div style="display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap;">
                    <div>
                        <h1>🔧 Últimas Ordens de Trabalho</h1>
                        <div class="subtitle">Resumo das últimas ordens de trabalho por máquina (exclui OTs planeadas)</div>
                        <div class="timestamp">📅 Gerado em {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}</div>
    '''
        
        if refresh_interval > 0:
            html_content += f'''
                        <div class="refresh-info">
                            🔄 Atualização automática a cada <span class="countdown">{refresh_minutes}</span> minutos
                            <span id="countdownTimer" style="margin-left:10px;color:#ffeb3b;">({refresh_interval}s)</span>
                        </div>
    '''
        
        html_content += f'''
                    </div>
                    <div style="margin-top: 10px;">
                        <button class="btn-refresh" onclick="location.reload()">🔄 Atualizar Agora</button>
                        <button class="export-btn" onclick="window.print()">🖨️ Imprimir / PDF</button>
                    </div>
                </div>
                
                <div class="stats">
                    <div class="stat-item">
                        <div class="number">{total_maquinas}</div>
                        <div class="label">🖥️ Máquinas</div>
                    </div>
                    <div class="stat-item">
                        <div class="number">{total_ots}</div>
                        <div class="label">📝 Total de OTs</div>
                    </div>
                    <div class="stat-item">
                        <div class="number">{encerradas}</div>
                        <div class="label">✅ Encerradas</div>
                    </div>
                    <div class="stat-item">
                        <div class="number">{ativas}</div>
                        <div class="label">🔄 Ativas</div>
                    </div>
                </div>
            </div>
            
            <div class="filters">
                <label>📊 Estado:</label>
                <select id="statusFilter" onchange="filterCards()">
                    <option value="all">Todos</option>
    '''
        
        estados_ordenados = sorted([e for e in estados.keys() if e and e != 'Desconhecido'])
        for estado in estados_ordenados:
            html_content += f'                <option value="{estado}">{estado}</option>\n'
        
        html_content += '''
                </select>
                
                <label>🔴 Urgência:</label>
                <select id="urgencyFilter" onchange="filterCards()">
                    <option value="all">Todas</option>
                    <option value="Muito Urgente">🔴 Muito Urgente</option>
                    <option value="Urgente">🟠 Urgente</option>
                    <option value="Moderado">🟡 Moderado</option>
                    <option value="Não urgente">🟢 Não urgente</option>
                </select>
                
                <label>🔍 Pesquisar:</label>
                <input type="text" id="searchInput" onkeyup="filterCards()" placeholder="Máquina ou ID da OT...">
                
                <button class="clear-btn" onclick="clearFilters()">✖️ Limpar</button>
                
                <span class="filter-stats" id="filterStats">Mostrando <span id="visibleCount">0</span> de <span id="totalCount">0</span></span>
            </div>
            
            <div class="machine-grid" id="machineGrid">
    '''
        
        if not relatorio:
            html_content += '''
                <div class="no-data">
                    <div class="icon">📭</div>
                    <h2>Nenhuma ordem de trabalho encontrada</h2>
                    <p>Verifique se há dados disponíveis na API.</p>
                </div>
            '''
        else:
            for machine_id, ots in sorted(maquinas.items()):
                # Primeira OT da máquina (a mais recente) para mostrar no resumo
                primeira_ot = ots[0] if ots else {}
                alteracoes_ots = [ot.get('Alterações') for ot in ots if ot.get('Alterações')]
                alteracoes = alteracoes_ots[0] if alteracoes_ots else None
                change_badge = ''
                change_description = ''
                if alteracoes:
                    change_badge = '<div class="change-timer" data-change-until="{}">🔔 Alterado</div>'.format(
                        alteracoes['at'] + stock.ChangeTracker.DURACAO_SEGUNDOS
                    )
                    change_description = '<div style="font-size:12px;color:#a55b00;margin:8px 0;">🔔 OT alterada: {}</div>'.format(
                        ' • '.join(alteracoes['fields'])
                    )
                estado = primeira_ot.get('Estado', 'Desconhecido')
                estado_class = estado.replace(' ', '_')
                if estado_class not in ['Encerrada', 'Em_curso', 'A_aguardar_documentação', 'Anulada', 'Por_fazer', 'Suspenso']:
                    estado_class = 'default'
                
                urgencia = primeira_ot.get('Urgência', 'Não definida')
                urgencia_class = urgencia.replace(' ', '_')
                if urgencia_class not in ['Muito_Urgente', 'Urgente', 'Moderado', 'Não_urgente']:
                    urgencia_class = 'default'
                
                criticidade = primeira_ot.get('Criticidade', 'Não definida')
                criticidade_class = criticidade.replace(' ', '_')
                if criticidade_class not in ['Muito_Crítico', 'Crítico', 'Não_Crítico']:
                    criticidade_class = 'default'
                
                data_ini = primeira_ot.get('Data Início', '')
                data_fecho = primeira_ot.get('Data Fecho', '')
                id_ot = primeira_ot.get('ID OT', '')
                nome_maquina = primeira_ot.get('Nome Máquina', '')
                
                html_content += f'''
                <div class="machine-card" style="{'box-shadow: 0 0 0 3px #ff9800, 0 8px 20px rgba(255,152,0,.25);' if alteracoes else ''}" data-status="{estado}" data-urgency="{urgencia}" data-search="{machine_id} {nome_maquina}">
                    <div class="machine-card-header" onclick="toggleOTList('{machine_id.replace("'", "\\'")}')">
                        <div>
                            <div class="machine-name">🖥️ {machine_id}</div>
                            <div class="machine-id">{nome_maquina}</div>
                        </div>
                        <div style="display:flex;align-items:center;gap:10px;">
                            <span class="badge">📝 {len(ots)} OTs</span>
                            {change_badge}
                            <span class="expand-icon" id="expand_{machine_id.replace('-', '_')}">▼</span>
                        </div>
                    </div>
                    <div class="machine-card-body">
                        {change_description}
                        <div class="ot-info">
                            <div class="ot-info-item" onclick="abrirDetalhesOT('{id_ot}')">
                                <div class="label">ID da OT (última)</div>
                                <div class="value code clickable">🔍 {id_ot}</div>
                            </div>
                            <div class="ot-info-item">
                                <div class="label">Título</div>
                                <div class="value" style="font-weight: 600;">{primeira_ot.get('Título', '-')}</div>
                            </div>
                            <div class="ot-info-item">
                                <div class="label">Estado</div>
                                <div class="value"><span class="status-badge status-{estado_class}">{estado}</span></div>
                            </div>
                            <div class="ot-info-item">
                                <div class="label">Urgência</div>
                                <div class="value"><span class="urgency-badge urgency-{urgencia_class}">{urgencia}</span></div>
                            </div>
                            <div class="ot-info-item">
                                <div class="label">📅 Data Início</div>
                                <div class="value">{data_ini}</div>
                            </div>
                            <div class="ot-info-item">
                                <div class="label">📅 Data Fecho</div>
                                <div class="value">{data_fecho}</div>
                            </div>
                            <div class="ot-info-item">
                                <div class="label">👤 Técnico</div>
                                <div class="value">{primeira_ot.get('Técnico', '-')}</div>
                            </div>
                            <div class="ot-info-item">
                                <div class="label">👤 Emissor</div>
                                <div class="value">{primeira_ot.get('Emissor', '-')}</div>
                            </div>
                            <div class="ot-info-item">
                                <div class="label">🏷️ Marca/Modelo</div>
                                <div class="value">{primeira_ot.get('Marca', '-')} {primeira_ot.get('Modelo', '')}</div>
                            </div>
                            <div class="ot-info-item">
                                <div class="label">🔢 Nº Série</div>
                                <div class="value code">{primeira_ot.get('Série', '-')}</div>
                            </div>
                            <div class="ot-info-item">
                                <div class="label">⚠️ Criticidade</div>
                                <div class="value"><span class="criticality-badge criticality-{criticidade_class}">{criticidade}</span></div>
                            </div>
                            <div class="ot-info-item">
                                <div class="label">📦 Arquivada</div>
                                <div class="value">{'✅ Sim' if primeira_ot.get('Arquivada') == 'Sim' else '❌ Não'}</div>
                            </div>
                            <div class="remedy-text">
                                <strong>💡 Solução:</strong> {primeira_ot.get('Solução', 'N/A')}
                            </div>
                        </div>
                        
                        <!-- Lista de todas as OTs da máquina -->
                        <div class="ot-list" id="otlist_{machine_id.replace('-', '_')}">
                            <div style="margin-top:10px;font-weight:600;color:#333;font-size:14px;border-bottom:1px solid #e9ecef;padding-bottom:8px;">
                                📋 Todas as OTs desta máquina ({len(ots)})
                            </div>
    '''
                
                # Listar todas as OTs da máquina
                for ot in ots:
                    ot_estado = ot.get('Estado', 'Desconhecido')
                    ot_estado_class = ot_estado.replace(' ', '_')
                    ot_id = ot.get('ID OT', '')
                    ot_code = ot.get('Código OT', '')
                    
                    ot_titulo = ot.get('Título', 'Sem título')
                    ot_data = ot.get('Data Início', '')
                    
                    html_content += f'''
                            <div class="ot-item" onclick="abrirDetalhesOT('{ot_id}')">
                                <span class="ot-title">{ot_data}</span>
                                <span class="ot-code">🔍 {ot_code}</span>
                                <span class="ot-title">{ot_titulo}</span>
                                
                                <span class="ot-state ot-state-{ot_estado_class}">{ot_estado}</span>
                            </div>
    '''
                
                html_content += '''
                        </div>
                    </div>
                </div>
                '''
        
        html_content += f'''
            </div>
            
            <div class="footer">
                <p>📊 Relatório gerado automaticamente • Última OT por máquina (exclui OTs planeadas) • {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}</p>
                <p style="font-size: 11px; opacity: 0.6;">Total de máquinas: {total_maquinas} | Total de OTs: {total_ots}</p>
            </div>
        </div>
        
        <!-- ===== MODAL DETALHES DA OT ===== -->
        <div class="modal-overlay" id="modalOverlay" onclick="if(event.target===this) fecharModal()">
            <div class="modal" id="modalContent">
                <div class="modal-header">
                    <h2 id="modalTitle">📋 Detalhes da OT</h2>
                    <button class="modal-close" onclick="fecharModal()">&times;</button>
                </div>
                <div id="modalBody">
                    <div class="modal-loading">
                        <div class="spinner"></div>
                        <p>A carregar detalhes da OT...</p>
                    </div>
                </div>
            </div>
        </div>
    '''
        
        # JavaScript no final do body
        js_code = f'''
    <script>
        // Configuração da API - usa o proxy local para resolver CORS
        const API_BASE_URL = '{proxy_url}';
        
        // Função para alternar a lista de OTs
        function toggleOTList(machineId) {{
            var listId = 'otlist_' + machineId.replace(/-/g, '_');
            var list = document.getElementById(listId);
            var expandIcon = document.getElementById('expand_' + machineId.replace(/-/g, '_'));
            
            if (list) {{
                if (list.style.display === 'block') {{
                    list.style.display = 'none';
                    if (expandIcon) expandIcon.textContent = '▼';
                }} else {{
                    list.style.display = 'block';
                    if (expandIcon) expandIcon.textContent = '▲';
                }}
            }}
        }}
        
        function abrirDetalhesOT(otId) {{
            if (!otId) {{
                alert('ID da OT não disponível.');
                return;
            }}
            
            console.log('🔍 A abrir detalhes da OT:', otId);
            
            var modal = document.getElementById('modalOverlay');
            var modalBody = document.getElementById('modalBody');
            var modalTitle = document.getElementById('modalTitle');
            
            if (!modal || !modalBody || !modalTitle) {{
                console.error('❌ Elementos do modal não encontrados!');
                alert('Erro: elementos do modal não encontrados.');
                return;
            }}
            
            modalTitle.textContent = '📋 Carregando...';
            modalBody.innerHTML = '<div class="modal-loading"><div class="spinner"></div><p>A carregar detalhes da OT <strong>' + otId + '</strong>...</p></div>';
            modal.classList.add('active');
            
            // USAR O PROXY LOCAL EM VEZ DA API DIRETA
            var url = API_BASE_URL + '/api/workOrderReader/Get?id=' + otId;
            console.log('📤 Requisição para proxy:', url);
            
            fetch(url, {{
                method: 'GET',
                headers: {{
                    'Content-Type': 'application/json'
                }}
            }})
            .then(function(response) {{
                console.log('📥 Resposta recebida: status', response.status);
                if (!response.ok) {{
                    throw new Error('HTTP error! status: ' + response.status);
                }}
                return response.json();
            }})
            .then(function(data) {{
                console.log('✅ Dados recebidos com sucesso');
                preencherModalOT(data);
            }})
            .catch(function(error) {{
                console.error('❌ Erro:', error);
                modalBody.innerHTML = '<div class="modal-error"><p>❌ Erro ao carregar os detalhes da OT.</p><p style="font-size:13px;color:#888;margin-top:10px;">' + error.message + '</p><p style="font-size:12px;color:#888;margin-top:5px;">URL: ' + url + '</p><button onclick="fecharModal()" style="margin-top:15px;padding:8px 20px;background:#667eea;color:white;border:none;border-radius:8px;cursor:pointer;">Fechar</button></div>';
            }});
        }}
        
        function preencherModalOT(data) {{
            var modalBody = document.getElementById('modalBody');
            var modalTitle = document.getElementById('modalTitle');
            
            // Extrair dados com verificações corretas
            var code = data.code || 'N/A';
            var name = data.name || 'Sem título';
            
            // ESTADO - extrair corretamente do objeto state
            var state = 'Desconhecido';
            if (data.state) {{
                if (typeof data.state === 'object') {{
                    state = data.state.name || data.state.stateLabel || 'Desconhecido';
                }} else if (typeof data.state === 'string') {{
                    state = data.state;
                }}
            }}
            
            var issuer = data.issuerFullName || (data.issuer && data.issuer.fullName) || '-';
            
            var beginDateTime = data.beginDateTime ? new Date(data.beginDateTime).toLocaleString('pt-PT') : '-';
            var endDateTime = data.endDateTime ? new Date(data.endDateTime).toLocaleString('pt-PT') : '-';
            var creationDateTime = data.creationDateTime ? new Date(data.creationDateTime).toLocaleString('pt-PT') : '-';
            var lastModification = data.lastModificationDateTime ? new Date(data.lastModificationDateTime).toLocaleString('pt-PT') : '-';
            var urgency = (data.urgency && data.urgency.name) ? data.urgency.name : '-';
            
            // SOLUÇÃO - remedy é um objeto, extrair o nome
            var remedy = 'N/A';
            if (data.remedy) {{
                if (typeof data.remedy === 'object') {{
                    remedy = data.remedy.name || data.remedy.codename || 'N/A';
                }} else if (typeof data.remedy === 'string') {{
                    remedy = data.remedy;
                }}
            }}
            
            var assetName = (data.asset && data.asset.name) || data.assetCode || '-';
            var assetCode = (data.asset && data.asset.code) || '-';
            
            // ===== ASSINATURAS =====
            var signaturesHtml = '';
            var signatures = data.signatures || [];
            
            if (signatures.length > 0) {{
                signaturesHtml = '<div class="comments-section"><h4>✍️ Assinaturas (' + signatures.length + ')</h4>';
                
                for (var i = 0; i < signatures.length; i++) {{
                    var sig = signatures[i];
                    var signer = (sig.signatory && sig.signatory.fullName) || 'Desconhecido';
                    var sigDate = sig.signatureDateTime ? new Date(sig.signatureDateTime).toLocaleString('pt-PT') : '-';
                    
                    signaturesHtml += '<div class="modal-comment" style="border-left-color: #28a745;"><span class="comment-author" style="color:#28a745;">✍️ ' + signer + '</span><span class="comment-date">📅 ' + sigDate + '</span></div>';
                }}
                
                signaturesHtml += '</div>';
            }}
            
            // ===== COMENTÁRIOS =====
            var commentsHtml = '';
            var comments = data.commentList || [];
            
            if (comments.length > 0) {{
                commentsHtml = '<div class="comments-section"><h4>💬 Comentários (' + comments.length + ')</h4>';
                
                comments.sort(function(a, b) {{
                    var dateA = new Date(a.creationDateTime || 0);
                    var dateB = new Date(b.creationDateTime || 0);
                    return dateB - dateA;
                }});
                
                for (var i = 0; i < comments.length; i++) {{
                    var comment = comments[i];
                    var author = (comment.user && comment.user.fullName) || 'Desconhecido';
                    var date = comment.creationDateTime ? new Date(comment.creationDateTime).toLocaleString('pt-PT') : '-';
                    var html = comment.commentHtml || comment.comment || 'Sem conteúdo';
                    
                    commentsHtml += '<div class="modal-comment"><div><span class="comment-author">👤 ' + author + '</span><span class="comment-date">📅 ' + date + '</span></div><div class="comment-text">' + html + '</div></div>';
                }}
                
                commentsHtml += '</div>';
            }} else {{
                commentsHtml = '<div class="comments-section"><h4>💬 Comentários</h4><p class="no-comments">📭 Nenhum comentário registado.</p></div>';
            }}
            
            var stateClass = state.replace(/ /g, '_');
            
            modalTitle.textContent = '📋 ' + code + ' - ' + name.substring(0, 40);
            
            modalBody.innerHTML = 
                '<div class="modal-grid">' +
                    '<div class="modal-item full"><div class="label">📌 Título</div><div class="value">' + name + '</div></div>' +
                    '<div class="modal-item"><div class="label">🔢 Código</div><div class="value code">' + code + '</div></div>' +
                    '<div class="modal-item"><div class="label">📊 Estado</div><div class="value"><span class="modal-status modal-status-' + stateClass + '">' + state + '</span></div></div>' +
                    '<div class="modal-item"><div class="label">👤 Emissor</div><div class="value">' + issuer + '</div></div>' +
                    '<div class="modal-item"><div class="label">🕐 Data Criação</div><div class="value">' + creationDateTime + '</div></div>' +
                    '<div class="modal-item"><div class="label">🕐 Última Modificação</div><div class="value">' + lastModification + '</div></div>' +
                    '<div class="modal-item"><div class="label">⏰ Início</div><div class="value">' + beginDateTime + '</div></div>' +
                    '<div class="modal-item"><div class="label">⏰ Fim</div><div class="value">' + endDateTime + '</div></div>' +
                    '<div class="modal-item"><div class="label">🔴 Urgência</div><div class="value">' + urgency + '</div></div>' +
                    '<div class="modal-item"><div class="label">🏷️ Equipamento</div><div class="value">' + assetCode + ' - ' + assetName + '</div></div>' +
                    '<div class="modal-item full"><div class="label">💡 Solução</div><div class="value">' + remedy + '</div></div>' +
                '</div>' +
                signaturesHtml +
                commentsHtml +
                '<div style="margin-top:15px;text-align:right;font-size:11px;color:#888;">🆔 ID: <span class="code">' + (data.id || '-') + '</span></div>';
        }}
        
        function fecharModal() {{
            var modal = document.getElementById('modalOverlay');
            if (modal) {{
                modal.classList.remove('active');
            }}
        }}
        
        // Fechar modal com tecla ESC
        document.addEventListener('keydown', function(e) {{
            if (e.key === 'Escape') {{
                fecharModal();
            }}
        }});
        
        function filterCards() {{
            var statusFilter = document.getElementById('statusFilter').value;
            var urgencyFilter = document.getElementById('urgencyFilter').value;
            var searchInput = document.getElementById('searchInput').value.toLowerCase().trim();
            var cards = document.querySelectorAll('.machine-card');
            var visible = 0;
            
            for (var i = 0; i < cards.length; i++) {{
                var card = cards[i];
                var status = card.dataset.status || '';
                var urgency = card.dataset.urgency || '';
                var search = card.dataset.search || '';
                
                var show = true;
                
                if (statusFilter !== 'all' && status !== statusFilter) {{
                    show = false;
                }}
                
                if (show && urgencyFilter !== 'all' && urgency !== urgencyFilter) {{
                    show = false;
                }}
                
                if (show && searchInput && !search.toLowerCase().includes(searchInput)) {{
                    show = false;
                }}
                
                if (show) {{
                    card.style.display = 'block';
                    visible++;
                }} else {{
                    card.style.display = 'none';
                }}
            }}
            
            document.getElementById('visibleCount').textContent = visible;
            document.getElementById('totalCount').textContent = cards.length;
        }}
        
        function clearFilters() {{
            document.getElementById('statusFilter').value = 'all';
            document.getElementById('urgencyFilter').value = 'all';
            document.getElementById('searchInput').value = '';
            filterCards();
        }}
        
        document.addEventListener('DOMContentLoaded', function() {{
            // Fechar todas as listas de OTs por padrão
            var lists = document.querySelectorAll('.ot-list');
            for (var i = 0; i < lists.length; i++) {{
                lists[i].style.display = 'none';
            }}
            filterCards();
        }});
    </script>
    '''
        
        html_content += js_code
        
        html_content += '''
    </body>
    </html>
        '''
        
        try:
            os.makedirs(os.path.dirname(filename), exist_ok=True)
            
            with open(filename, 'w', encoding='utf-8') as f:
                f.write(html_content)
            
            print(f"✅ Relatório HTML visual gerado com sucesso!")
            print(f"   📄 {filename}")
            print(f"   📊 Tamanho: {os.path.getsize(filename)} bytes")
            return True
        except Exception as e:
            print(f"❌ Erro ao gerar HTML: {e}")
            import traceback
            traceback.print_exc()
            return False
    # ========================================================================
    # MÉTODOS PARA PLANEAMENTO DE TÉCNICOS (PLANNING)
    # ========================================================================
    
    def get_planning_technician(self, start_date=None, end_date=None, days=5):
        """
        Obtém o planeamento de técnicos para um período
        
        Args:
            start_date (str): Data de início no formato 'YYYY-MM-DDTHH:MM:SS'
            end_date (str): Data de fim no formato 'YYYY-MM-DDTHH:MM:SS'
            days (int): Número de dias a buscar (se start_date e end_date não forem fornecidos)
        """
        if not self.token:
            print("❌ Erro: Token não definido!")
            return None
        
        # Se não forem fornecidas datas, usa os últimos N dias
        if start_date is None or end_date is None:
            hoje = datetime.now()
            start = hoje - timedelta(days=days)
            end = hoje + timedelta(days=7)
            start_date = start.strftime("%Y-%m-%dT%H:%M:%S")
            end_date = end.strftime("%Y-%m-%dT%H:%M:%S")
            print(f"📅 A buscar planeamento dos últimos {days} dias...")
        
        url = f"{self.base_url}/SERRATEC/api/planningTechnician/getPlanning"
        params = {
            "startDate": start_date,
            "endDate": end_date
        }
        
        headers = {
            "Authorization": f"Bearer {self.token}",
            "Content-Type": "application/json",
            "tenant": "SERRATEC"
        }
        
        try:
            print(f"📅 A buscar planeamento de {start_date} a {end_date}...")
            response = self.session.get(url, headers=headers, params=params, timeout=30)
            
            if response.status_code == 200:
                print("✅ Planning obtido com sucesso!")
                data = response.json()
                if isinstance(data, dict):
                    data['period'] = f"{start_date[:10]} a {end_date[:10]}"
                elif isinstance(data, list):
                    data = {
                        'items': data,
                        'period': f"{start_date[:10]} a {end_date[:10]}"
                    }
                return data
            elif response.status_code == 401:
                print("⚠️ Token expirado! A renovar...")
                self.token = None
                if os.path.exists(self.token_file):
                    os.remove(self.token_file)
                return None
            else:
                print(f"❌ Erro HTTP: {response.status_code}")
                print(f"   Resposta: {response.text[:200]}...")
                return None
        except Exception as e:
            print(f"❌ Erro ao buscar planning: {e}")
            return None

    def gerar_html_planeamento(self, planning_data, filename):
        """
        Gera um HTML visual em formato de escala/calendário com as OTs organizadas por dia e técnico
        Usa os dados da API de planning (lista de OTs)
        
        Args:
            planning_data: Lista de OTs obtidas pela API getPlanning
            filename: Nome do ficheiro HTML a gerar
        """
        print(f"📝 A gerar HTML do planeamento em escala: {filename}")
        
        if not planning_data:
            print("❌ Sem dados para gerar o planeamento")
            return False
        
        # Debug: mostrar estrutura
        print(f"🔍 Total de OTs recebidas: {len(planning_data)}")
        if len(planning_data) > 0:
            print(f"   - Primeiro item: {list(planning_data[0].keys()) if isinstance(planning_data[0], dict) else 'N/A'}")
        
        # Extrair técnicos e suas OTs
        tecnicos_ots = {}
        ots_sem_tecnico = []
        
        # Processar cada item (que é uma OT)
        for item in planning_data:
            if not isinstance(item, dict):
                continue
            
            # Extrair nome do técnico
            tech_nome = None
            technician = item.get('technician')
            
            if isinstance(technician, dict):
                tech_nome = technician.get('fullName') or technician.get('name')
            elif isinstance(technician, str) and technician.strip():
                tech_nome = technician.strip()
            
            # Se não tem technician, verificar outros campos
            if not tech_nome:
                if item.get('technicianName'):
                    tech_nome = item.get('technicianName')
                elif item.get('assignedTo'):
                    tech_nome = item.get('assignedTo')
            
            # Se ainda não tem, verificar no objeto technician por outros nomes
            if not tech_nome and isinstance(technician, dict):
                # Tentar firstName + lastName
                first = technician.get('firstName', '')
                last = technician.get('lastName', '')
                if first or last:
                    tech_nome = f"{first} {last}".strip()
            
            # Se encontrou técnico, adicionar
            if tech_nome:
                if tech_nome not in tecnicos_ots:
                    tecnicos_ots[tech_nome] = []
                tecnicos_ots[tech_nome].append(item)
            else:
                # Sem técnico - guardar separadamente
                ots_sem_tecnico.append(item)
        
        # Adicionar OTs sem técnico como "Não atribuído"
        if ots_sem_tecnico:
            tecnicos_ots['Não atribuído'] = ots_sem_tecnico
        
        if not tecnicos_ots:
            print("⚠️ Nenhum técnico com OTs encontrado no planning.")
            print("   Usando 'Não atribuído' para todas as OTs...")
            tecnicos_ots['Não atribuído'] = [item for item in planning_data if isinstance(item, dict)]
        
        # Ordenar técnicos
        tecnicos = sorted(tecnicos_ots.keys())
        total_ots = sum(len(ots) for ots in tecnicos_ots.values())
        
        print(f"👤 Técnicos encontrados: {len(tecnicos)}")
        for tech, ots in tecnicos_ots.items():
            print(f"   - {tech}: {len(ots)} OTs")
        print(f"📋 Total de OTs: {total_ots}")
        
        # Dias da semana
        dias_semana = ['Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado', 'Domingo']
        
        # Agrupar OTs por dia e técnico - usar ID único
        agenda = {}
        ots_sem_data = 0
        
        # Mapeamento de ID da OT para dados
        mapa_ots = {}
        
        for tech, ots in tecnicos_ots.items():
            for ot in ots:
                # Extrair data
                begin = ot.get('beginDateTime')
                if not begin:
                    begin = ot.get('begin') or ot.get('startDateTime') or ot.get('start') or ot.get('date')
                
                if not begin:
                    ots_sem_data += 1
                    continue
                
                data_str = str(begin)[:10]
                if data_str not in agenda:
                    agenda[data_str] = {}
                
                if tech not in agenda[data_str]:
                    agenda[data_str][tech] = []
                
                # Extrair hora
                hora = ''
                if len(str(begin)) >= 16:
                    hora = str(begin)[11:16]
                
                # Extrair hora de fim
                end = ot.get('endDateTime') or ot.get('end') or ot.get('finishDateTime') or ''
                hora_fim = ''
                if end and len(str(end)) >= 16:
                    hora_fim = str(end)[11:16]
                
                # Extrair estado
                estado_obj = ot.get('state', {})
                if isinstance(estado_obj, dict):
                    estado_nome = estado_obj.get('name', '')
                    estado_code = estado_obj.get('code', '')
                else:
                    estado_nome = str(estado_obj) if estado_obj else ''
                    estado_code = ''
                
                # Extrair asset
                asset_display = ''
                asset_code = ''
                asset_name = ''
                asset_obj = ot.get('asset', {})
                if isinstance(asset_obj, dict):
                    asset_code = asset_obj.get('code', '')
                    asset_name = asset_obj.get('name', '')
                    if asset_code:
                        asset_display = f"{asset_code} - {asset_name}"[:40]
                    else:
                        asset_display = asset_name[:40]
                else:
                    asset_code = ot.get('assetCode', '') or ''
                    asset_name = ot.get('assetName', '') or ''
                    asset_display = asset_code or asset_name
                
                # Extrair código da OT
                codigo = ot.get('code', '')
                if not codigo:
                    codigo = ot.get('id', '')[:12]
                
                # ID único da OT
                ot_id = ot.get('id', '')
                
                # Criar dados do evento
                evento = {
                    'code': codigo,
                    'name': ot.get('name', ''),
                    'asset': asset_display,
                    'asset_code': asset_code,
                    'asset_name': asset_name,
                    'asset_id': asset_obj.get('id', '') if isinstance(asset_obj, dict) else '',
                    'hora': hora,
                    'hora_fim': hora_fim,
                    'state': estado_nome,
                    'state_code': estado_code,
                    'id': ot_id,
                    'beginDateTime': begin,
                    'endDateTime': end,
                    'preventiveMaintenanceCode': ot.get('preventiveMaintenanceCode', ''),
                    'workRequestCode': ot.get('workRequestCode', ''),
                    'labourId': ot.get('labourId', ''),
                    'anyLabourAchievedLine': ot.get('anyLabourAchievedLine', False),
                    'isLabourAchieved': ot.get('isLabourAchieved', False),
                    'isWorkOrderClosed': ot.get('isWorkOrderClosed', False),
                    'technician': tech,
                    'bodyId': ot.get('bodyId', ''),
                    'workOrderGroupId': ot.get('workOrderGroupId', ''),
                    'timeZoneId': ot.get('timeZoneId', ''),
                    'documentType': ot.get('documentType', ''),
                }
                
                agenda[data_str][tech].append(evento)
                # Guardar no mapa por ID
                if ot_id:
                    mapa_ots[ot_id] = evento
                # Guardar também por código (fallback)
                if codigo:
                    mapa_ots[codigo] = evento
        
        if ots_sem_data > 0:
            print(f"⚠️ {ots_sem_data} OTs ignoradas por não terem data válida")
        
        # Verificar se há dados na agenda
        if not agenda:
            html_content = '''
    <!DOCTYPE html>
    <html lang="pt">
    <head>
        <meta charset="UTF-8">
        <title>📅 Planeamento de Técnicos</title>
        <style>
            body { font-family: Arial, sans-serif; padding: 40px; background: #f5f7fa; }
            .container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 16px; text-align: center; }
            h1 { color: #333; }
            .empty { font-size: 18px; color: #888; padding: 40px 0; }
        </style>
    </head>
    <body>
        <div class="container">
            <h1>📅 Planeamento de Técnicos</h1>
            <div class="empty">📭 Nenhuma OT com data válida encontrada</div>
        </div>
    </body>
    </html>
            '''
        else:
            datas_ordenadas = sorted(agenda.keys())
            
            if datas_ordenadas:
                period = f"{datas_ordenadas[0]} a {datas_ordenadas[-1]}"
            else:
                period = "Semana atual"
            
            import json
            # Passar tanto a agenda como o mapa de OTs por ID
            dados_json = json.dumps({
                'agenda': agenda,
                'mapa_ots': mapa_ots
            }, ensure_ascii=False)
            
            html_content = f'''
    <!DOCTYPE html>
    <html lang="pt">
    <head>
        <meta http-equiv="refresh" content="30">
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>📅 Planeamento de Técnicos</title>
        <style>
            * {{ margin: 0; padding: 0; box-sizing: border-box; }}
            body {{ 
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 
                background: #f0f2f5; 
                padding: 20px;
            }}
            .container {{ max-width: 100%; margin: 0 auto; }}
            
            .header {{
                background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
                color: white;
                padding: 25px 35px;
                border-radius: 16px;
                margin-bottom: 25px;
                box-shadow: 0 10px 30px rgba(0,0,0,0.2);
            }}
            .header h1 {{ font-size: 26px; margin-bottom: 5px; }}
            .header .subtitle {{ opacity: 0.8; font-size: 14px; }}
            .header .period {{ 
                display: inline-block; 
                background: rgba(255,255,255,0.12); 
                padding: 4px 14px; 
                border-radius: 20px;
                margin-top: 8px;
                font-size: 13px;
            }}
            
            .header .stats {{
                display: flex;
                gap: 25px;
                margin-top: 15px;
                flex-wrap: wrap;
            }}
            .header .stats .stat {{
                font-size: 13px;
                opacity: 0.9;
            }}
            .header .stats .stat strong {{
                font-size: 18px;
            }}
            
            .filters {{
                background: white;
                padding: 12px 18px;
                border-radius: 12px;
                margin-bottom: 20px;
                box-shadow: 0 2px 10px rgba(0,0,0,0.06);
                display: flex;
                flex-wrap: wrap;
                gap: 10px;
                align-items: center;
            }}
            .filters label {{ font-weight: 600; color: #555; font-size: 13px; }}
            .filters input, .filters select {{
                padding: 6px 12px;
                border: 2px solid #e0e0e0;
                border-radius: 8px;
                font-size: 13px;
                background: white;
            }}
            .filters input:focus, .filters select:focus {{ outline: none; border-color: #0f3460; }}
            .filters input {{ flex: 1; min-width: 180px; }}
            .filter-stats {{ font-size: 13px; color: #666; margin-left: auto; }}
            
            .legenda {{
                display: flex;
                flex-wrap: wrap;
                gap: 15px;
                padding: 8px 15px;
                background: white;
                border-radius: 10px;
                margin-bottom: 15px;
                box-shadow: 0 2px 10px rgba(0,0,0,0.06);
            }}
            .legenda-item {{ display: flex; align-items: center; gap: 5px; font-size: 12px; }}
            .legenda-cor {{ width: 14px; height: 14px; border-radius: 4px; }}
            
            .scroll-wrapper {{
                overflow-x: auto;
                padding-bottom: 10px;
                border-radius: 12px;
                background: white;
                box-shadow: 0 2px 10px rgba(0,0,0,0.06);
            }}
            
            .calendario {{
                display: table;
                width: 100%;
                border-collapse: collapse;
                min-width: 600px;
            }}
            
            .calendario .linha {{
                display: table-row;
            }}
            
            .calendario .linha-cabecalho {{
                display: table-row;
                background: #f8f9fa;
                border-bottom: 2px solid #dee2e6;
            }}
            
            .calendario .linha-cabecalho .celula {{
                font-weight: 600;
                font-size: 13px;
                color: #333;
                padding: 10px 8px;
                text-align: center;
                border-bottom: 2px solid #dee2e6;
                position: sticky;
                top: 0;
                background: #f8f9fa;
                z-index: 10;
            }}
            
            .calendario .celula {{
                display: table-cell;
                padding: 6px 4px;
                border-bottom: 1px solid #e9ecef;
                vertical-align: top;
                min-width: 150px;
                max-width: 250px;
            }}
            
            .calendario .celula-data {{
                min-width: 80px;
                max-width: 80px;
                text-align: center;
                font-weight: 600;
                font-size: 13px;
                background: #f8f9fa;
                vertical-align: middle;
                padding: 6px 4px;
                border-right: 1px solid #e9ecef;
            }}
            .calendario .celula-data .dia-semana {{ 
                font-weight: 400; 
                font-size: 11px; 
                color: #888; 
                display: block;
            }}
            .calendario .celula-data .dia-num {{ font-size: 16px; }}
            
            .calendario .celula-vazia {{
                text-align: center;
                color: #ccc;
                font-size: 12px;
                padding: 15px 4px;
                vertical-align: middle;
            }}
            
            .calendario .celula-tec {{
                background: #fafafa;
                vertical-align: top;
                padding: 6px 4px;
            }}
            
            .evento {{
                padding: 4px 8px;
                border-radius: 4px;
                margin-bottom: 3px;
                font-size: 12px;
                cursor: pointer;
                transition: all 0.2s;
                border-left: 4px solid #1976d2;
                background: #e3f2fd;
            }}
            .evento:hover {{ 
                transform: scale(1.02);
                box-shadow: 0 2px 8px rgba(0,0,0,0.15);
            }}
            
            .evento .hora {{ font-weight: 600; color: #1976d2; font-size: 11px; display: inline-block; min-width: 40px; }}
            .evento .codigo {{ font-size: 10px; color: #888; font-family: monospace; display: inline-block; margin-right: 4px; }}
            .evento .titulo {{ font-weight: 500; color: #333; font-size: 11px; }}
            .evento .asset {{ font-size: 10px; color: #666; display: block; margin-top: 1px; }}
            
            .evento-estado-Concluída {{ border-left-color: #28a745; background: #d4edda; }}
            .evento-estado-Encerrada {{ border-left-color: #6c757d; background: #e9ecef; }}
            .evento-estado-Em_curso {{ border-left-color: #ffc107; background: #fff3cd; }}
            .evento-estado-Por_fazer {{ border-left-color: #dc3545; background: #f8d7da; }}
            .evento-estado-Suspenso {{ border-left-color: #fd7e14; background: #ffe5d0; }}
            .evento-estado-A_aguardar_material {{ border-left-color: #17a2b8; background: #d1ecf1; }}
            .evento-estado-A_aguardar_intervencao_externa {{ border-left-color: #6f42c1; background: #e8d4f8; }}
            .evento-estado-Em_analise_com_o_fabricante {{ border-left-color: #e83e8c; background: #f8d4e8; }}
            
            .footer {{
                text-align: center;
                margin-top: 25px;
                padding: 15px;
                color: #888;
                font-size: 12px;
            }}
            
            .print-btn {{
                padding: 8px 20px;
                background: white;
                color: #0f3460;
                border: none;
                border-radius: 8px;
                cursor: pointer;
                font-weight: 600;
                font-size: 13px;
                transition: background 0.3s;
            }}
            .print-btn:hover {{ background: rgba(255,255,255,0.2); color: white; }}
            
            .modal-overlay {{
                display: none;
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0,0,0,0.6);
                backdrop-filter: blur(4px);
                z-index: 1000;
                justify-content: center;
                align-items: center;
                animation: fadeIn 0.3s;
            }}
            .modal-overlay.active {{ display: flex; }}
            
            @keyframes fadeIn {{
                from {{ opacity: 0; }}
                to {{ opacity: 1; }}
            }}
            
            .modal {{
                background: white;
                border-radius: 16px;
                max-width: 600px;
                width: 95%;
                max-height: 90vh;
                overflow-y: auto;
                padding: 30px;
                box-shadow: 0 20px 60px rgba(0,0,0,0.3);
                animation: slideUp 0.3s ease;
            }}
            
            @keyframes slideUp {{
                from {{ transform: translateY(30px); opacity: 0; }}
                to {{ transform: translateY(0); opacity: 1; }}
            }}
            
            .modal-header {{
                display: flex;
                justify-content: space-between;
                align-items: flex-start;
                margin-bottom: 20px;
                padding-bottom: 15px;
                border-bottom: 2px solid #e9ecef;
            }}
            .modal-header h2 {{
                font-size: 18px;
                color: #1a1a2e;
                flex: 1;
            }}
            .modal-close {{
                background: none;
                border: none;
                font-size: 28px;
                color: #888;
                cursor: pointer;
                padding: 0 8px;
                transition: color 0.2s;
            }}
            .modal-close:hover {{ color: #333; }}
            
            .modal-grid {{
                display: grid;
                grid-template-columns: 1fr 1fr;
                gap: 12px;
            }}
            .modal-grid .full {{
                grid-column: 1 / -1;
            }}
            .modal-item {{
                padding: 8px 12px;
                background: #f8f9fa;
                border-radius: 8px;
            }}
            .modal-item .label {{
                font-size: 11px;
                color: #888;
                text-transform: uppercase;
                letter-spacing: 0.3px;
                font-weight: 600;
            }}
            .modal-item .value {{
                font-size: 14px;
                color: #333;
                margin-top: 2px;
                font-weight: 500;
                word-break: break-word;
            }}
            .modal-item .value.code {{
                font-family: monospace;
                font-size: 13px;
            }}
            
            .modal-status {{
                display: inline-block;
                padding: 4px 12px;
                border-radius: 20px;
                font-size: 13px;
                font-weight: 600;
            }}
            .modal-status-Concluída {{ background: #d4edda; color: #155724; }}
            .modal-status-Encerrada {{ background: #e9ecef; color: #495057; }}
            .modal-status-Em_curso {{ background: #fff3cd; color: #856404; }}
            .modal-status-Por_fazer {{ background: #f8d7da; color: #721c24; }}
            .modal-status-Suspenso {{ background: #ffe5d0; color: #7b4a1e; }}
            .modal-status-A_aguardar_material {{ background: #d1ecf1; color: #0c5460; }}
            .modal-status-A_aguardar_intervencao_externa {{ background: #e8d4f8; color: #5a2d7a; }}
            .modal-status-Em_analise_com_o_fabricante {{ background: #f8d4e8; color: #7a2d5a; }}
            
            @media (max-width: 768px) {{
                .modal-grid {{
                    grid-template-columns: 1fr;
                }}
                .modal {{ padding: 20px; }}
                .modal-header h2 {{ font-size: 16px; }}
            }}
            
            @media print {{
                .filters, .print-btn, .no-print {{ display: none; }}
                .scroll-wrapper {{ box-shadow: none; border: 1px solid #ddd; }}
                .calendario .linha-cabecalho .celula {{ background: #f8f9fa !important; }}
                .modal-overlay {{ display: none !important; }}
            }}
            
            @media (max-width: 768px) {{
                .calendario .celula-data {{ min-width: 55px; max-width: 55px; font-size: 11px; }}
                .calendario .celula {{ min-width: 100px; max-width: 150px; padding: 4px 3px; }}
                .evento {{ font-size: 10px; padding: 3px 6px; }}
                .evento .hora {{ font-size: 10px; min-width: 30px; }}
                .evento .titulo {{ font-size: 10px; }}
                .header {{ padding: 18px 20px; }}
                .header h1 {{ font-size: 20px; }}
            }}
        </style>
    </head>
    <body>
        <div class="container">
            <div class="header">
                <div style="display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap;">
                    <div>
                        <h1>📅 Planeamento de Técnicos</h1>
                        <div class="subtitle">Escala de ordens de trabalho por dia e técnico</div>
                        <div class="period">📆 {period}</div>
                    </div>
                    <div style="margin-top: 8px;">
                        <button class="print-btn" onclick="window.print()">🖨️ Imprimir / PDF</button>
                    </div>
                </div>
                <div class="stats">
                    <span class="stat">👤 <strong>{len(tecnicos)}</strong> Técnicos</span>
                    <span class="stat">📋 <strong>{total_ots}</strong> OTs</span>
                    <span class="stat">📅 <strong>{len(datas_ordenadas)}</strong> Dias</span>
                </div>
            </div>
            
            <div class="filters">
                <label>🔍 Pesquisar:</label>
                <input type="text" id="searchInput" onkeyup="filterAgenda()" placeholder="Código, descrição ou equipamento...">
                <label>👤 Técnico:</label>
                <select id="techFilter" onchange="filterAgenda()">
                    <option value="all">Todos</option>
    '''
            
            for tech in tecnicos:
                html_content += f'                <option value="{tech}">{tech}</option>\n'
            
            html_content += '''
                </select>
                <span class="filter-stats" id="filterStats">Mostrando <span id="visibleCount">0</span> OTs</span>
            </div>
            
            <div class="legenda">
                <span style="font-weight: 600; margin-right: 8px;">Legenda:</span>
                <span class="legenda-item"><span class="legenda-cor" style="background:#d4edda;border:1px solid #28a745;"></span> Concluída</span>
                <span class="legenda-item"><span class="legenda-cor" style="background:#e9ecef;border:1px solid #6c757d;"></span> Encerrada</span>
                <span class="legenda-item"><span class="legenda-cor" style="background:#fff3cd;border:1px solid #ffc107;"></span> Em curso</span>
                <span class="legenda-item"><span class="legenda-cor" style="background:#f8d7da;border:1px solid #dc3545;"></span> Por fazer</span>
                <span class="legenda-item"><span class="legenda-cor" style="background:#ffe5d0;border:1px solid #fd7e14;"></span> Suspenso</span>
                <span class="legenda-item"><span class="legenda-cor" style="background:#d1ecf1;border:1px solid #17a2b8;"></span> Aguardar material</span>
                <span class="legenda-item"><span class="legenda-cor" style="background:#e8d4f8;border:1px solid #6f42c1;"></span> Aguardar externa</span>
                <span class="legenda-item"><span class="legenda-cor" style="background:#f8d4e8;border:1px solid #e83e8c;"></span> Análise fabricante</span>
            </div>
            
            <div class="scroll-wrapper">
                <div class="calendario" id="calendario">
                    <!-- Cabeçalho com nomes dos técnicos -->
                    <div class="linha linha-cabecalho">
                        <div class="celula" style="min-width:80px;max-width:80px;text-align:center;">📅 Data</div>
    '''
            
            for tech in tecnicos:
                html_content += f'                    <div class="celula" style="min-width:150px;max-width:250px;text-align:center;">{tech}</div>\n'
            
            html_content += '''
                    </div>
    '''
            
            # Corpo do calendário
            for data_str in datas_ordenadas:
                try:
                    data_obj = datetime.strptime(data_str, "%Y-%m-%d")
                    dia_semana = dias_semana[data_obj.weekday()]
                    dia_num = data_obj.day
                    mes_num = data_obj.month
                    data_formatada = f"{dia_num:02d}/{mes_num:02d}"
                except:
                    dia_semana = ''
                    data_formatada = data_str[5:10]
                
                html_content += f'''
                    <div class="linha" data-date="{data_str}">
                        <div class="celula celula-data">
                            <span class="dia-num">{data_formatada}</span>
                            <span class="dia-semana">{dia_semana}</span>
                        </div>
    '''
                
                for tech in tecnicos:
                    eventos = agenda.get(data_str, {}).get(tech, [])
                    eventos_ordenados = sorted(eventos, key=lambda x: x.get('hora', '99:99'))
                    
                    if eventos_ordenados:
                        html_content += f'                    <div class="celula celula-tec" data-tech="{tech}" data-date="{data_str}">\n'
                        
                        for idx, evento in enumerate(eventos_ordenados):
                            estado = evento.get('state', '')
                            ot_id = evento.get('id', '')
                            
                            # Mapear estado para classe CSS
                            estado_class = ''
                            if 'Concluída' in estado or 'DONE' in estado:
                                estado_class = 'evento-estado-Concluída'
                            elif 'Encerrada' in estado or 'CLOSED' in estado:
                                estado_class = 'evento-estado-Encerrada'
                            elif 'Em curso' in estado or 'INPROGRESS' in estado:
                                if 'aguardar material' in estado.lower():
                                    estado_class = 'evento-estado-A_aguardar_material'
                                elif 'intervenção externa' in estado.lower() or 'externa' in estado.lower():
                                    estado_class = 'evento-estado-A_aguardar_intervencao_externa'
                                elif 'análise com o fabricante' in estado.lower() or 'analise com o fabricante' in estado.lower():
                                    estado_class = 'evento-estado-Em_analise_com_o_fabricante'
                                else:
                                    estado_class = 'evento-estado-Em_curso'
                            elif 'Por fazer' in estado or 'TODO' in estado:
                                estado_class = 'evento-estado-Por_fazer'
                            elif 'Suspenso' in estado:
                                estado_class = 'evento-estado-Suspenso'
                            elif 'aguardar material' in estado.lower():
                                estado_class = 'evento-estado-A_aguardar_material'
                            elif 'analise com o fabricante' in estado.lower():
                                estado_class = 'evento-estado-Em_analise_com_o_fabricante'
                            
                            titulo = evento.get('name', '')[:50]
                            titulo = titulo.replace('<', '&lt;').replace('>', '&gt;')
                            codigo = evento.get('code', '')
                            hora = evento.get('hora', '--:--')
                            asset = evento.get('asset', '')
                            
                            # USAR O ID DA OT COMO IDENTIFICADOR ÚNICO
                            evento_id = ot_id if ot_id else f"evt_{data_str}_{tech}_{idx}_{codigo}"
                            
                            html_content += f'''
                            <div class="evento {estado_class}" 
                                data-id="{evento_id}"
                                data-tech="{tech}" 
                                data-date="{data_str}"
                                data-code="{codigo}"
                                onclick="abrirModal('{evento_id}')">
                                <span class="hora">{hora}</span>
                                <span class="codigo">{codigo}</span>
                                <span class="titulo">{titulo}</span>
                                <span class="asset">🏷️ {asset}</span>
                            </div>
    '''
                        
                        html_content += '                    </div>\n'
                    else:
                        html_content += f'                    <div class="celula celula-vazia" data-tech="{tech}" data-date="{data_str}">—</div>\n'
                
                html_content += '                </div>\n'
            
            html_content += '''
                </div>
            </div>
            
            <div class="footer">
                <p>📅 Planeamento gerado automaticamente • ''' + datetime.now().strftime('%d/%m/%Y %H:%M:%S') + '''</p>
                <p style="font-size: 11px; opacity: 0.6;">Total: ''' + str(len(tecnicos)) + ''' técnicos | ''' + str(total_ots) + ''' OTs</p>
            </div>
        </div>
        
        <!-- ===== MODAL ===== -->
        <div class="modal-overlay" id="modalOverlay" onclick="if(event.target===this) fecharModal()">
            <div class="modal" id="modalContent">
                <div class="modal-header">
                    <h2 id="modalTitle">📋 Detalhes da OT</h2>
                    <button class="modal-close" onclick="fecharModal()">&times;</button>
                </div>
                <div id="modalBody">
                    <!-- Preenchido por JavaScript -->
                </div>
            </div>
        </div>
        
        <script>
            // Dados da agenda - contém a agenda e o mapa de OTs por ID
            const dados = ''' + dados_json + ''';
            const agendaData = dados.agenda;
            const mapaOts = dados.mapa_ots;

            // Função para abrir o modal
            function abrirModal(eventoId) {
                // Encontrar o elemento clicado
                const elemento = document.querySelector(`[data-id="${eventoId}"]`);
                if (!elemento) {
                    console.log('Elemento não encontrado:', eventoId);
                    return;
                }
                
                // Tentar encontrar os dados do evento pelo ID no mapa
                let eventoData = mapaOts[eventoId];
                
                // Se não encontrou pelo ID, tentar pelo código
                if (!eventoData) {
                    const codigo = elemento.dataset.code;
                    eventoData = mapaOts[codigo];
                }
                
                // Se ainda não encontrou, percorrer a agenda
                if (!eventoData) {
                    const tech = elemento.dataset.tech;
                    const date = elemento.dataset.date;
                    const codigo = elemento.dataset.code;
                    
                    if (agendaData[date] && agendaData[date][tech]) {
                        for (const evt of agendaData[date][tech]) {
                            if (evt.id === eventoId || evt.code === codigo) {
                                eventoData = evt;
                                break;
                            }
                        }
                    }
                }
                
                if (!eventoData) {
                    alert('Não foi possível encontrar os detalhes desta OT.');
                    return;
                }
                
                // Preencher o modal
                const modalTitle = document.getElementById('modalTitle');
                const modalBody = document.getElementById('modalBody');
                
                modalTitle.textContent = '📋 ' + (eventoData.code || 'OT');
                
                // Formatar datas
                const dataInicio = eventoData.beginDateTime ? new Date(eventoData.beginDateTime) : null;
                const dataFim = eventoData.endDateTime ? new Date(eventoData.endDateTime) : null;
                
                const formatarData = (data) => {
                    if (!data) return '-';
                    return data.toLocaleDateString('pt-PT', { day: '2-digit', month: '2-digit', year: 'numeric' });
                };
                
                const estado = eventoData.state || 'Sem estado';
                const estadoClass = estado.replace(/ /g, '_').replace(/á/g, 'a').replace(/é/g, 'e').replace(/í/g, 'i').replace(/ó/g, 'o').replace(/ú/g, 'u');
                
                modalBody.innerHTML = `
                    <div class="modal-grid">
                        <div class="modal-item full">
                            <div class="label">📌 Título</div>
                            <div class="value">${eventoData.name || 'Sem título'}</div>
                        </div>
                        <div class="modal-item">
                            <div class="label">🔢 Código</div>
                            <div class="value code">${eventoData.code || '-'}</div>
                        </div>
                        <div class="modal-item">
                            <div class="label">📊 Estado</div>
                            <div class="value"><span class="modal-status modal-status-${estadoClass}">${estado}</span></div>
                        </div>
                        <div class="modal-item">
                            <div class="label">👤 Técnico</div>
                            <div class="value">${eventoData.technician || '-'}</div>
                        </div>
                        <div class="modal-item">
                            <div class="label">⏰ Início</div>
                            <div class="value">${eventoData.hora ? eventoData.hora : '-'}</div>
                        </div>
                        <div class="modal-item">
                            <div class="label">⏰ Fim</div>
                            <div class="value">${eventoData.hora_fim || '-'}</div>
                        </div>
                        <div class="modal-item">
                            <div class="label">📅 Data Início</div>
                            <div class="value">${formatarData(dataInicio)}</div>
                        </div>
                        <div class="modal-item">
                            <div class="label">📅 Data Fim</div>
                            <div class="value">${formatarData(dataFim)}</div>
                        </div>
                        <div class="modal-item">
                            <div class="label">🏷️ Equipamento</div>
                            <div class="value">${eventoData.asset || '-'}</div>
                        </div>
                        <div class="modal-item">
                            <div class="label">🔧 Código Equip.</div>
                            <div class="value code">${eventoData.asset_code || '-'}</div>
                        </div>
                        <div class="modal-item full">
                            <div class="label">📋 Código Manutenção Preventiva</div>
                            <div class="value code">${eventoData.preventiveMaintenanceCode || '-'}</div>
                        </div>
                        <div class="modal-item full">
                            <div class="label">📋 Código Pedido</div>
                            <div class="value code">${eventoData.workRequestCode || '-'}</div>
                        </div>
                        <div class="modal-item">
                            <div class="label">✅ Mão-de-obra alcançada</div>
                            <div class="value">${eventoData.anyLabourAchievedLine ? '✅ Sim' : '❌ Não'}</div>
                        </div>
                        <div class="modal-item">
                            <div class="label">🔒 OT Encerrada</div>
                            <div class="value">${eventoData.isWorkOrderClosed ? '✅ Sim' : '❌ Não'}</div>
                        </div>
                        <div class="modal-item full">
                            <div class="label">🆔 ID</div>
                            <div class="value code" style="font-size:11px;">${eventoData.id || '-'}</div>
                        </div>
                    </div>
                `;
                
                document.getElementById('modalOverlay').classList.add('active');
            }
            
            function fecharModal() {
                document.getElementById('modalOverlay').classList.remove('active');
            }
            
            // Fechar modal com tecla ESC
            document.addEventListener('keydown', function(e) {
                if (e.key === 'Escape') {
                    fecharModal();
                }
            });
            
            function filterAgenda() {
                const searchInput = document.getElementById('searchInput').value.toLowerCase().trim();
                const techFilter = document.getElementById('techFilter').value;
                const linhas = document.querySelectorAll('.calendario .linha:not(.linha-cabecalho)');
                let totalVisiveis = 0;
                
                linhas.forEach(linha => {
                    const celulas = linha.querySelectorAll('.celula:not(.celula-data)');
                    let linhaVisivel = false;
                    
                    celulas.forEach(celula => {
                        const eventos = celula.querySelectorAll('.evento');
                        const tech = celula.dataset.tech || '';
                        
                        if (eventos.length === 0) {
                            if (techFilter === 'all' || tech === techFilter) {
                                celula.style.display = 'table-cell';
                                linhaVisivel = true;
                            } else {
                                celula.style.display = 'none';
                            }
                        } else {
                            let celulaVisivel = false;
                            
                            eventos.forEach(evento => {
                                const search = evento.dataset.search || '';
                                let show = true;
                                
                                if (techFilter !== 'all' && tech !== techFilter) {
                                    show = false;
                                }
                                
                                if (show && searchInput && !search.toLowerCase().includes(searchInput)) {
                                    show = false;
                                }
                                
                                if (show) {
                                    evento.style.display = 'block';
                                    celulaVisivel = true;
                                } else {
                                    evento.style.display = 'none';
                                }
                            });
                            
                            const eventosVisiveis = celula.querySelectorAll('.evento[style*="display: block"]');
                            if (eventosVisiveis.length === 0 && eventos.length > 0) {
                                celulaVisivel = false;
                            }
                            
                            if (!celulaVisivel) {
                                celula.innerHTML = '<div style="text-align:center;color:#ccc;font-size:12px;padding:10px;">—</div>';
                                celula.className = 'celula celula-vazia';
                                celula.dataset.tech = tech;
                                celulaVisivel = true;
                            }
                            
                            if (celulaVisivel) {
                                linhaVisivel = true;
                            }
                        }
                    });
                    
                    if (linhaVisivel) {
                        linha.style.display = 'table-row';
                        totalVisiveis++;
                    } else {
                        linha.style.display = 'none';
                    }
                });
                
                document.getElementById('visibleCount').textContent = totalVisiveis;
            }
            
            document.addEventListener('DOMContentLoaded', function() {
                filterAgenda();
            });
        </script>
    </body>
    </html>
            '''
        
        try:
            os.makedirs(os.path.dirname(filename), exist_ok=True)
            
            with open(filename, 'w', encoding='utf-8') as f:
                f.write(html_content)
            
            print(f"✅ HTML do planeamento gerado com sucesso!")
            print(f"   📄 {filename}")
            print(f"   📊 Tamanho: {os.path.getsize(filename)} bytes")
            return True
        except Exception as e:
            print(f"❌ Erro ao gerar HTML do planeamento: {e}")
            import traceback
            traceback.print_exc()
            return False

# ============================================================================
# FUNÇÃO DE ATUALIZAÇÃO AUTOMÁTICA
# ============================================================================

def auto_update(api, username, password, desktop, limite=5000, intervalo=300):
    """
    Executa a atualização automática a cada X segundos
    Sobrescreve sempre os mesmos ficheiros (nomes fixos)
    
    Args:
        api: Instância da API
        username: Nome de utilizador
        password: Password
        desktop: Caminho da Área de Trabalho
        limite: Número máximo de registos
        intervalo: Intervalo em segundos (default 300 = 5 minutos)
    """
    print(f"🔄 Iniciando atualização automática a cada {intervalo} segundos...")
    print("📁 Os ficheiros serão sobrescritos (nomes fixos)")
    print("=" * 70)
    
    # Nomes fixos para os ficheiros
    CSV_OTS = os.path.join(desktop, "ordens_trabalho.csv")
    JSON_OTS = os.path.join(desktop, "workorders.json")
    HTML_OTS = os.path.join(desktop, "ultimas_ots_visual.html")
    
    CSV_PIS = os.path.join(desktop, "pedidos_intervencao.csv")
    JSON_PIS = os.path.join(desktop, "workrequests.json")
    HTML_PIS = os.path.join(desktop, "pedidos_intervencao_visual.html")
    
    JSON_PLANNING = os.path.join(desktop, "planning_tecnico.json")
    HTML_PLANNING = os.path.join(desktop, "planning_tecnico.html")
    
    # Ficheiros de stock
    CSV_STOCK = os.path.join(desktop, "stock.csv")
    JSON_STOCK = os.path.join(desktop, "stock.json")
    HTML_STOCK = os.path.join(desktop, "stock_visual.html")
    HTML_MOTOR = os.path.join(desktop, "motor_pesquisa_visual.html")
    
    # Ficheiro de log com timestamp da última atualização
    LOG_FILE = os.path.join(desktop, "ultima_atualizacao.txt")
    change_tracker = stock.ChangeTracker(desktop)
    # Compara já a primeira atualização com os JSON existentes, se houver.
    change_tracker.inicializar_de_ficheiro("workorders", JSON_OTS)
    change_tracker.inicializar_de_ficheiro("workrequests", JSON_PIS)
    change_tracker.inicializar_de_ficheiro("stock", JSON_STOCK)
    
    while True:
        try:
            inicio = datetime.now()
            print(f"\n📅 {inicio.strftime('%Y-%m-%d %H:%M:%S')} - Iniciando atualização...")
            
            # Verificar token
            if not api.token:
                print("🔑 Token não encontrado. A fazer login...")
                if not api.login_with_selenium(username, password, headless=False):
                    print("❌ Falha no login. A tentar novamente em 60 segundos...")
                    time.sleep(60)
                    continue
            
            # ============================================================
            # 1. BUSCAR ORDENS DE TRABALHO
            # ============================================================
            print(f"\n📊 [1/4] A extrair as últimas {limite} ordens de trabalho...")
            workorders = api.get_all_workorders(limit=limite)
            
            if workorders:
                workorders = change_tracker.analisar("workorders", workorders)
                api.export_to_csv(workorders, CSV_OTS)
                
                with open(JSON_OTS, 'w', encoding='utf-8') as f:
                    json.dump(workorders, f, ensure_ascii=False, indent=2)
                
                relatorio = api.gerar_relatorio_ultimas_ots(workorders, num_ultimas=None)
                if relatorio:
                    api.gerar_html_visual(relatorio, HTML_OTS)
                print(f"✅ OTs atualizadas: {len(workorders)} registos")
            else:
                print("⚠️ Nenhuma OT obtida.")

            # ============================================================
            # 1.5. SINCRONIZAR COMENTÁRIOS (só das OTs abertas)
            # ============================================================
            print("\n💬 [1.5/4] A sincronizar comentários das OTs...")
            try:
                comentarios.sync_comentarios(
                    api,
                    limite=20000,         # só sincroniza 2000 por ciclo (as mais relevantes)
                    concorrencia=15,     # 15 pedidos em paralelo
                    apenas_abertas=True, # ignora encerradas/anuladas
                )
            except Exception as e:
                print(f"⚠️ Erro ao sincronizar comentários: {e}")

            # ============================================================
            # 1.6. REGENERAR MOTOR DE PESQUISA VISUAL
            # ============================================================
            print("\n🔍 [1.6/4] A regenerar motor de pesquisa visual...")
            try:
                motor_pesquisa_visual.gerar_motor_visual(
                    filename=HTML_MOTOR,
                    refresh_interval=0,
                    abrir=False,
                )
                
                print("✅ Motor de pesquisa visual regenerado.")
            except Exception as e:
                print(f"⚠️ Erro ao regenerar motor visual: {e}")
            
            # ============================================================
            # 2. BUSCAR PEDIDOS DE INTERVENÇÃO
            # ============================================================
            print(f"\n📋 [2/4] A extrair os últimos {limite} pedidos de intervenção...")
            workrequests = api.get_all_workrequests(limit=limite)
            
            if workrequests:
                workrequests = change_tracker.analisar("workrequests", workrequests)
                api.export_workrequests_to_csv(workrequests, CSV_PIS)
                
                with open(JSON_PIS, 'w', encoding='utf-8') as f:
                    json.dump(workrequests, f, ensure_ascii=False, indent=2)
                
                relatorio_pis = api.gerar_relatorio_ultimos_pis(workrequests)
                if relatorio_pis:
                    api.gerar_html_pis(relatorio_pis, HTML_PIS)
                print(f"✅ PIs atualizados: {len(workrequests)} registos")
            else:
                print("⚠️ Nenhum PI obtido.")
            
            # ============================================================
            # 3. BUSCAR PLANEAMENTO DE TÉCNICOS
            # ============================================================
            print("\n📅 [3/4] A extrair planeamento de técnicos...")
            planning_data = api.get_planning_technician(days=5)
            
            if planning_data:
                if isinstance(planning_data, dict) and 'items' in planning_data:
                    planning_items = planning_data['items']
                elif isinstance(planning_data, list):
                    planning_items = planning_data
                else:
                    planning_items = []
                
                with open(JSON_PLANNING, 'w', encoding='utf-8') as f:
                    json.dump(planning_items, f, ensure_ascii=False, indent=2)
                
                api.gerar_html_planeamento(planning_items, HTML_PLANNING)
                print(f"✅ Planning atualizado: {len(planning_items)} OTs")
            else:
                print("⚠️ Nenhum planeamento obtido.")

            ########### APAGAR CACHE ApÓS X TEMPO PARA FORÇAR ATUALIZAÇÂO DE PEÇAS DO STOCK  ####
            ###########
            ############

            cache_file = os.path.join(desktop, "stock_cache.json")
            if os.path.exists(cache_file):
                idade = time.time() - os.path.getmtime(cache_file)
                if idade > 3600:  # mais de 1h
                    os.remove(cache_file)
                    print(f"🗑️ Cache stock com {idade/3600:.0f} min — apagado para refresh.")
            # ============================================================
            # 4. BUSCAR STOCK (NOVO)
            # ============================================================
            print("\n📦 [4/4] A extrair stock de peças...")
            try:
                stock_api = stock.StockAPI()
                
                if not stock_api.load_token():
                    print("⚠️ Token não disponível para o stock.")
                else:
                    # Verificar se o token é válido
                    parts = stock_api.get_all_parts(limit=limite)
                    
                    if parts and not stock_api.token_expired:
                        # Processar peças com stock
                        parts_com_stock = stock_api.process_parts_with_stock(
                            parts,
                            limit=None,
                            save_interval=50
                        )
                        
                        # Verificar se token expirou durante o processamento
                        if stock_api.token_expired:
                            print("🔄 Token expirou durante o processamento do stock!")
                            print("🔑 A renovar token com Selenium...")
                            
                            # Renovar token via Selenium (usando o método do Dimo.py)
                            if api.login_with_selenium(username, password, headless=True, force=True):
                                print("✅ Token renovado! A recarregar stock...")
                                
                                # Recarregar token no stock_api
                                if stock_api.recarregar_token():
                                    print("✅ Token recarregado no stock_api.")
                                    
                                    # Re-tentar processar (as peças em cache vão acelerar)
                                    print("🔄 A retomar processamento do stock...")
                                    parts_com_stock = stock_api.process_parts_with_stock(
                                        parts,
                                        limit=None,
                                        save_interval=50
                                    )
                                    
                                    if stock_api.token_expired:
                                        print("⚠️ Token expirou novamente. A guardar dados parciais...")
                                    else:
                                        print("✅ Stock processado com sucesso após renovação!")
                                else:
                                    print("⚠️ Falha ao recarregar token no stock_api.")
                            else:
                                print("❌ Falha ao renovar token. Stock incompleto.")
                        
                        # Guardar ficheiros de stock (mesmo que parciais)
                        if parts_com_stock:
                            parts_com_stock = change_tracker.analisar("stock", parts_com_stock)
                            stock_api.export_to_csv(parts_com_stock, CSV_STOCK)
                            
                            with open(JSON_STOCK, 'w', encoding='utf-8') as f:
                                json.dump(parts_com_stock, f, ensure_ascii=False, indent=2)
                            
                            stock_api.gerar_html_stock(
                                parts_com_stock,
                                HTML_STOCK,
                                refresh_interval=300
                            )
                            
                            print(f"✅ Stock atualizado: {len(parts_com_stock)} peças")
                        else:
                            print("⚠️ Nenhuma peça processada.")
                    else:
                        if stock_api.token_expired:
                            print("🔄 Token expirou na busca inicial de peças. A renovar...")
                            if api.login_with_selenium(username, password, headless=True, force=True):
                                print("✅ Token renovado! A retomar stock...")
                                stock_api.recarregar_token()
                                parts = stock_api.get_all_parts(limit=limite)
                                
                                if parts:
                                    parts_com_stock = stock_api.process_parts_with_stock(parts, limit=None)
                                    
                                    if parts_com_stock:
                                        parts_com_stock = change_tracker.analisar("stock", parts_com_stock)
                                        stock_api.export_to_csv(parts_com_stock, CSV_STOCK)
                                        with open(JSON_STOCK, 'w', encoding='utf-8') as f:
                                            json.dump(parts_com_stock, f, ensure_ascii=False, indent=2)
                                        stock_api.gerar_html_stock(parts_com_stock, HTML_STOCK)
                                        print(f"✅ Stock atualizado: {len(parts_com_stock)} peças")
                        else:
                            print("⚠️ Nenhuma peça obtida.")
                            
            except Exception as e:
                print(f"❌ Erro ao obter stock: {e}")
                import traceback
                traceback.print_exc()
            
            # ============================================================
            # GUARDAR TIMESTAMP
            # ============================================================
            with open(LOG_FILE, 'w', encoding='utf-8') as f:
                f.write(f"Última atualização: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                f.write(f"Total de OTs: {len(workorders) if workorders else 0}\n")
                f.write(f"Total de PIs: {len(workrequests) if workrequests else 0}\n")
                f.write(f"Total de OTs no Planning: {len(planning_items) if planning_data else 0}\n")
                f.write(f"Total de Peças (stock): {len(parts_com_stock) if 'parts_com_stock' in locals() else 0}\n")
            
            fim = datetime.now()
            duracao = (fim - inicio).total_seconds()
            
            print("\n" + "=" * 70)
            print(f"✅ Atualização concluída em {duracao:.1f}s às {fim.strftime('%H:%M:%S')}")
            print(f"📁 Ficheiros atualizados (sobrescritos):")
            print(f"   📄 {CSV_OTS}")
            print(f"   📄 {HTML_OTS}")
            print(f"   📄 {HTML_PIS}")
            print(f"   📄 {HTML_PLANNING}")
            print(f"   📄 {HTML_STOCK}")
            print(f"⏳ Próxima atualização em {intervalo} segundos...")
            print("=" * 70)
            
        except Exception as e:
            print(f"❌ Erro na atualização automática: {e}")
            import traceback
            traceback.print_exc()
        
        # Aguardar o intervalo definido
        time.sleep(intervalo)

# ============================================================================
# MAIN
# ============================================================================

def main():
    USERNAME = "francisco.amorim"
    PASSWORD = "FAmorim2026"
    
    DESKTOP = os.path.expanduser("~/Desktop")
    
    # Limite de registos
    LIMITE = 10000
    # Intervalo de atualização (5 minutos = 300 segundos)
    INTERVALO = 30
    
    api = DimoMaintAPI()
    
    print("=" * 70)
    print("🔑 DIMO MAINT - EXPORTADOR AUTOMÁTICO")
    print("=" * 70)
    print(f"📊 LIMITE DE REGISTOS: {LIMITE}")
    print(f"🔄 ATUALIZAÇÃO A CADA: {INTERVALO} segundos ({INTERVALO/60:.1f} minutos)")
    print("=" * 70)
    print()
    
    # Tentar login inicial
    if api.login_with_selenium(USERNAME, PASSWORD, headless=False, force=False):
        print("✅ Login efetuado com sucesso!")
        
        # Iniciar servidor proxy
        print("\n🚀 A iniciar servidor proxy (resolução CORS)...")
        proxy_server = iniciar_servidor_proxy(porta=8888)
        
        # Configurar proxy
        configurar_proxy(api, USERNAME, PASSWORD)
        print("✅ Proxy configurado com renovação automática de token!")
        
        # Menu
        print("\n🔍 Opções:")
        print("1. Executar uma vez e sair")
        print("2. Executar em modo automático")
        print("3. Sair")
        
        opcao = input("\nEscolha uma opção (1/2/3): ").strip()
        
        if opcao == "1":
            print("\n📊 A executar extração única...")
            auto_update(api, USERNAME, PASSWORD, DESKTOP, LIMITE, 1)
            
        elif opcao == "2":
            print("\n🔄 A iniciar modo automático...")
            print(f"⚠️ Para parar, pressione Ctrl+C no terminal")
            auto_update(api, USERNAME, PASSWORD, DESKTOP, LIMITE, INTERVALO)
            
        else:
            print("👋 A sair...")
            proxy_server.shutdown()
    else:
        print("❌ Login falhou. Verifique as credenciais.")
    
    print("\n🔍 Pressiona Enter para fechar...")
    try:
        input()
    except KeyboardInterrupt:
        print("\n👋 Até logo!")

if __name__ == "__main__":
    # Variável global para o proxy
    api_instance = None
    main()
