{"id":1648,"date":"2026-06-07T13:24:09","date_gmt":"2026-06-07T11:24:09","guid":{"rendered":"https:\/\/decotex.es\/?page_id=1648"},"modified":"2026-06-07T14:05:19","modified_gmt":"2026-06-07T12:05:19","slug":"facturas","status":"publish","type":"page","link":"https:\/\/decotex.es\/en\/facturas\/","title":{"rendered":"facturas"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"1648\" class=\"elementor elementor-1648\">\n\t\t\t\t<div class=\"elementor-element elementor-element-e05ea95 e-flex e-con-boxed e-con e-parent\" data-id=\"e05ea95\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0d47603 elementor-widget elementor-widget-html\" data-id=\"0d47603\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\n<html lang=\"es\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>SmartInvoice - Aut\u00f3nomos<\/title>\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/@tailwindcss\/browser@4\"><\/script>\n    <script src=\"https:\/\/unpkg.com\/lucide@latest\"><\/script>\n    <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.11.174\/pdf.min.js\"><\/script>\n    <style>\n        @import url('https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@300;400;500;600;700&display=swap');\n        body { font-family: 'Inter', sans-serif; }\n        #log-panel { font-family: monospace; font-size: 12px; max-height: 220px; overflow-y: auto; }\n        .log-ok   { color: #16a34a; }\n        .log-err  { color: #dc2626; }\n        .log-info { color: #64748b; }\n        .log-warn { color: #d97706; }\n    <\/style>\n<\/head>\n<body class=\"bg-slate-50 text-slate-900 min-h-screen\">\n\n    <!-- \u2550\u2550 MODAL API KEY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n    <div id=\"apiKeyModal\"\n         class=\"fixed inset-0 bg-black\/50 backdrop-blur-sm z-50\n                flex items-center justify-center p-4 hidden\">\n        <div class=\"bg-white rounded-2xl shadow-xl max-w-md w-full p-8 space-y-5\">\n            <div class=\"flex items-center gap-3\">\n                <div class=\"bg-slate-900 text-white p-2 rounded-lg\">\n                    <i data-lucide=\"key\" class=\"w-5 h-5\"><\/i>\n                <\/div>\n                <h2 class=\"text-lg font-semibold\">Configurar API Key de Gemini<\/h2>\n            <\/div>\n            <p class=\"text-sm text-slate-500 leading-relaxed\">\n                Obt\u00e9n tu clave gratuita en\n                <a href=\"https:\/\/aistudio.google.com\/app\/apikey\" target=\"_blank\"\n                   class=\"text-blue-600 underline font-medium\">aistudio.google.com \u2192 Get API key<\/a>.\n                C\u00f3piala y p\u00e9gala aqu\u00ed.\n            <\/p>\n            <div class=\"space-y-2\">\n                <label class=\"block text-xs font-semibold uppercase tracking-wider text-slate-500\">\n                    Tu API Key\n                <\/label>\n                <input id=\"apiKeyInput\" type=\"text\"\n                       placeholder=\"AIza...\"\n                       autocomplete=\"off\"\n                       class=\"w-full bg-slate-50 border border-slate-200 focus:border-slate-900\n                              rounded-lg px-3 py-2.5 text-sm outline-none transition-all font-mono\" \/>\n                <label class=\"flex items-center gap-2 text-xs text-slate-500 cursor-pointer mt-1\">\n                    <input type=\"checkbox\" id=\"rememberKey\" checked \/>\n                    Recordar en este navegador\n                <\/label>\n            <\/div>\n            <div id=\"apiKeyError\"\n                 class=\"hidden text-xs text-red-600 bg-red-50 border border-red-200\n                        rounded-lg px-3 py-2 whitespace-pre-wrap\"><\/div>\n            <!-- Bot\u00f3n de test de la key -->\n            <button onclick=\"testApiKey()\"\n                    class=\"w-full border border-slate-200 hover:bg-slate-50 text-slate-700\n                           font-medium text-sm py-2.5 rounded-xl transition-all flex\n                           items-center justify-center gap-2\">\n                <i data-lucide=\"zap\" class=\"w-4 h-4\"><\/i>\n                Probar clave antes de guardar\n            <\/button>\n            <button onclick=\"guardarApiKey()\"\n                    class=\"w-full bg-slate-900 hover:bg-slate-700 text-white font-medium\n                           text-sm py-3 rounded-xl transition-all flex items-center\n                           justify-center gap-2\">\n                <i data-lucide=\"check-circle\" class=\"w-4 h-4\"><\/i>\n                Guardar y continuar\n            <\/button>\n        <\/div>\n    <\/div>\n\n    <!-- \u2550\u2550 NAVBAR \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n    <nav class=\"bg-white border-b border-slate-200 py-4 px-6 md:px-12\n                flex justify-between items-center\">\n        <div class=\"flex items-center gap-2\">\n            <div class=\"bg-slate-900 text-white p-2 rounded-lg\">\n                <i data-lucide=\"wallet\" class=\"w-6 h-6\"><\/i>\n            <\/div>\n            <span class=\"text-xl font-bold tracking-tight\">SmartInvoice<\/span>\n            <span class=\"text-xs bg-emerald-100 text-emerald-800 font-medium\n                         px-2 py-0.5 rounded-full\">Aut\u00f3nomos<\/span>\n        <\/div>\n        <div class=\"flex items-center gap-3\">\n            <button onclick=\"mostrarModalApiKey()\" id=\"apiKeyStatus\"\n                    class=\"flex items-center gap-1.5 text-xs font-medium px-3 py-1.5\n                           rounded-lg border border-slate-200 text-slate-600\n                           hover:bg-slate-50 transition-colors\">\n                <span id=\"apiKeyDot\" class=\"w-2 h-2 rounded-full bg-slate-300\"><\/span>\n                <span id=\"apiKeyLabel\">Sin API Key<\/span>\n            <\/button>\n            <button onclick=\"exportarCSV()\"\n                    class=\"flex items-center gap-2 text-sm font-medium text-slate-600\n                           hover:text-slate-900 bg-slate-100 hover:bg-slate-200\n                           px-4 py-2 rounded-lg transition-colors\">\n                <i data-lucide=\"download\" class=\"w-4 h-4\"><\/i>\n                Exportar (.CSV)\n            <\/button>\n        <\/div>\n    <\/nav>\n\n    <!-- \u2550\u2550 MAIN \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n    <main class=\"max-w-7xl mx-auto px-4 py-8 md:px-12\n                 grid grid-cols-1 lg:grid-cols-12 gap-8\">\n\n        <!-- Columna izquierda: upload -->\n        <div class=\"lg:col-span-5 space-y-4\">\n            <div class=\"bg-white p-6 rounded-2xl border border-slate-200 shadow-sm\">\n                <h2 class=\"text-lg font-semibold mb-1 text-slate-800\">Cargar Documento<\/h2>\n                <p class=\"text-sm text-slate-500 mb-4\">JPG, PNG o PDF \u2014 Gemini leer\u00e1 los datos autom\u00e1ticamente.<\/p>\n\n                <div id=\"dropzone\"\n                     onclick=\"document.getElementById('fileInput').click()\"\n                     class=\"border-2 border-dashed border-slate-300 hover:border-slate-900\n                            bg-slate-50 rounded-xl p-8 text-center cursor-pointer\n                            transition-all flex flex-col items-center justify-center min-h-[200px]\">\n                    <input type=\"file\" id=\"fileInput\" class=\"hidden\"\n                           accept=\".pdf,.jpg,.jpeg,.png\"\n                           onchange=\"procesarArchivo(this.files[0])\">\n                    <div class=\"bg-white p-3 rounded-full shadow-sm border border-slate-100 mb-3\">\n                        <i data-lucide=\"upload-cloud\" class=\"w-8 h-8 text-slate-500\"><\/i>\n                    <\/div>\n                    <p class=\"text-sm font-medium text-slate-700\">\n                        Arrastra aqu\u00ed o <span class=\"underline\">selecciona archivo<\/span>\n                    <\/p>\n                    <p class=\"text-xs text-slate-400 mt-1\">PDF \u00b7 JPG \u00b7 PNG<\/p>\n                <\/div>\n            <\/div>\n\n            <!-- Estado IA -->\n            <div id=\"loadingStatus\"\n                 class=\"hidden bg-slate-900 text-white p-4 rounded-xl shadow-sm space-y-3\">\n                <div class=\"flex items-center gap-3\">\n                    <div id=\"ai-spinner\"\n                         class=\"animate-spin rounded-full h-5 w-5 border-2\n                                border-white border-t-transparent flex-shrink-0\"><\/div>\n                    <p id=\"ai-status-text\" class=\"text-sm font-medium\">Iniciando\u2026<\/p>\n                <\/div>\n                <div class=\"w-full h-1.5 bg-white\/20 rounded-full overflow-hidden\">\n                    <div id=\"ai-progress-bar\"\n                         class=\"h-full bg-white rounded-full transition-all duration-500 w-0\"><\/div>\n                <\/div>\n                <div id=\"retry-box\" class=\"hidden text-xs text-white\/70 text-center\">\n                    L\u00edmite alcanzado \u2014 reintentando en\n                    <span id=\"retry-countdown\" class=\"font-bold text-white\">65<\/span>s\n                <\/div>\n            <\/div>\n\n            <!-- LOG VISIBLE \u2014 clave para depurar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n            <div id=\"log-container\"\n                 class=\"hidden bg-slate-900 rounded-xl border border-slate-700 overflow-hidden\">\n                <div class=\"flex items-center justify-between px-4 py-2 border-b border-slate-700\">\n                    <span class=\"text-xs font-semibold text-slate-300 flex items-center gap-1.5\">\n                        <i data-lucide=\"terminal\" class=\"w-3.5 h-3.5\"><\/i>\n                        Log de Gemini\n                    <\/span>\n                    <button onclick=\"limpiarLog()\"\n                            class=\"text-xs text-slate-500 hover:text-slate-300 transition-colors\">\n                        limpiar\n                    <\/button>\n                <\/div>\n                <div id=\"log-panel\" class=\"p-3 space-y-0.5 text-slate-300\">\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <!-- Columna derecha: formulario -->\n        <div class=\"lg:col-span-7\">\n            <div class=\"bg-white p-6 rounded-2xl border border-slate-200 shadow-sm\">\n                <div class=\"flex justify-between items-center mb-6\">\n                    <div>\n                        <h2 class=\"text-lg font-semibold text-slate-800\">Datos Extra\u00eddos<\/h2>\n                        <p class=\"text-sm text-slate-500\">Revisa y corrige antes de guardar.<\/p>\n                    <\/div>\n                    <span id=\"confidence-badge\" class=\"hidden text-xs font-medium px-2.5 py-1\n                                                        rounded-md border\"><\/span>\n                <\/div>\n\n                <form id=\"invoiceForm\" onsubmit=\"guardarFactura(event)\" class=\"space-y-4\">\n                    <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n                        <div>\n                            <label class=\"block text-xs font-semibold uppercase tracking-wider\n                                          text-slate-500 mb-1\">Nombre de la Empresa<\/label>\n                            <input type=\"text\" id=\"nombre_empresa\" required\n                                   class=\"w-full bg-slate-50 border border-slate-200\n                                          focus:border-slate-900 focus:bg-white rounded-lg\n                                          px-3 py-2 text-sm outline-none transition-all\" \/>\n                        <\/div>\n                        <div>\n                            <label class=\"block text-xs font-semibold uppercase tracking-wider\n                                          text-slate-500 mb-1\">CIF \/ NIF<\/label>\n                            <input type=\"text\" id=\"cif_factura\"\n                                   placeholder=\"Ej: B12345678\"\n                                   class=\"w-full bg-slate-50 border border-slate-200\n                                          focus:border-slate-900 focus:bg-white rounded-lg\n                                          px-3 py-2 text-sm outline-none transition-all\" \/>\n                        <\/div>\n                    <\/div>\n                    <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n                        <div>\n                            <label class=\"block text-xs font-semibold uppercase tracking-wider\n                                          text-slate-500 mb-1\">N\u00famero de Factura<\/label>\n                            <input type=\"text\" id=\"num_factura\"\n                                   class=\"w-full bg-slate-50 border border-slate-200\n                                          focus:border-slate-900 focus:bg-white rounded-lg\n                                          px-3 py-2 text-sm outline-none transition-all\" \/>\n                        <\/div>\n                        <div>\n                            <label class=\"block text-xs font-semibold uppercase tracking-wider\n                                          text-slate-500 mb-1\">Fecha de Emisi\u00f3n<\/label>\n                            <input type=\"date\" id=\"fecha_factura\"\n                                   class=\"w-full bg-slate-50 border border-slate-200\n                                          focus:border-slate-900 focus:bg-white rounded-lg\n                                          px-3 py-2 text-sm outline-none transition-all\" \/>\n                        <\/div>\n                    <\/div>\n                    <div class=\"grid grid-cols-1 md:grid-cols-3 gap-4 bg-slate-50\n                                p-4 rounded-xl border border-slate-100\">\n                        <div>\n                            <label class=\"block text-xs font-semibold uppercase tracking-wider\n                                          text-slate-500 mb-1\">Base Imponible (\u20ac)<\/label>\n                            <input type=\"number\" step=\"0.01\" id=\"base_imponible\"\n                                   oninput=\"calcularTotal()\"\n                                   class=\"w-full bg-white border border-slate-200\n                                          focus:border-slate-900 rounded-lg px-3 py-2\n                                          text-sm outline-none transition-all\" \/>\n                        <\/div>\n                        <div>\n                            <label class=\"block text-xs font-semibold uppercase tracking-wider\n                                          text-slate-500 mb-1\">IVA (%)<\/label>\n                            <select id=\"iva_porcentaje\" onchange=\"calcularTotal()\"\n                                    class=\"w-full bg-white border border-slate-200\n                                           focus:border-slate-900 rounded-lg px-3 py-2\n                                           text-sm outline-none transition-all cursor-pointer\">\n                                <option value=\"21\">21%<\/option>\n                                <option value=\"10\">10%<\/option>\n                                <option value=\"4\">4%<\/option>\n                                <option value=\"0\">0%<\/option>\n                            <\/select>\n                        <\/div>\n                        <div>\n                            <label class=\"block text-xs font-semibold uppercase tracking-wider\n                                          text-slate-400 mb-1\">Total<\/label>\n                            <div id=\"total_factura\"\n                                 class=\"w-full bg-slate-200\/60 rounded-lg px-3 py-2\n                                        text-sm font-bold text-slate-800 h-[38px]\n                                        flex items-center\">0.00 \u20ac<\/div>\n                        <\/div>\n                    <\/div>\n                    <button type=\"submit\"\n                            class=\"w-full bg-slate-900 hover:bg-slate-800 text-white\n                                   font-medium text-sm py-3 rounded-xl transition-all\n                                   shadow-sm flex items-center justify-center gap-2\">\n                        <i data-lucide=\"check-circle\" class=\"w-4 h-4\"><\/i>\n                        Confirmar y Guardar Factura\n                    <\/button>\n                <\/form>\n            <\/div>\n        <\/div>\n\n        <!-- Tabla historial -->\n        <div class=\"lg:col-span-12\">\n            <div class=\"bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden\">\n                <div class=\"p-6 border-b border-slate-100 flex flex-col sm:flex-row\n                            justify-between items-start sm:items-center gap-4\">\n                    <div>\n                        <h3 class=\"text-lg font-semibold text-slate-800\">Historial del Trimestre<\/h3>\n                        <p class=\"text-sm text-slate-500\">Listas para exportar al gestor.<\/p>\n                    <\/div>\n                    <div class=\"bg-slate-50 px-4 py-2 rounded-xl border border-slate-100 text-right\">\n                        <span class=\"text-xs font-medium text-slate-500 block\">Total acumulado<\/span>\n                        <span id=\"totalAcumulado\" class=\"text-lg font-bold text-slate-900\">0.00 \u20ac<\/span>\n                    <\/div>\n                <\/div>\n                <div class=\"overflow-x-auto\">\n                    <table class=\"w-full text-left\">\n                        <thead>\n                            <tr class=\"bg-slate-50 border-b border-slate-200 text-xs\n                                       font-semibold uppercase tracking-wider text-slate-500\">\n                                <th class=\"py-3 px-6\">Empresa \/ CIF<\/th>\n                                <th class=\"py-3 px-6\">N\u00ba Factura<\/th>\n                                <th class=\"py-3 px-6\">Fecha<\/th>\n                                <th class=\"py-3 px-6 text-right\">Base<\/th>\n                                <th class=\"py-3 px-6 text-center\">IVA<\/th>\n                                <th class=\"py-3 px-6 text-right\">Total<\/th>\n                                <th class=\"py-3 px-6\"><\/th>\n                            <\/tr>\n                        <\/thead>\n                        <tbody id=\"tablaFacturas\" class=\"divide-y divide-slate-100 text-sm\">\n                            <tr id=\"filaVacia\">\n                                <td colspan=\"7\" class=\"py-10 text-center text-slate-400\">\n                                    <div class=\"flex flex-col items-center gap-2\">\n                                        <i data-lucide=\"folder-open\" class=\"w-8 h-8 opacity-40\"><\/i>\n                                        Sin facturas guardadas a\u00fan.\n                                    <\/div>\n                                <\/td>\n                            <\/tr>\n                        <\/tbody>\n                    <\/table>\n                <\/div>\n            <\/div>\n        <\/div>\n    <\/main>\n\n    <script>\n    \/\/ \u2500\u2500 PDF.js worker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    if (typeof pdfjsLib !== 'undefined') {\n        pdfjsLib.GlobalWorkerOptions.workerSrc =\n            'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.11.174\/pdf.worker.min.js';\n    }\n\n    \/\/ \u2500\u2500 Modelos Gemini (orden de preferencia) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    const GEMINI_MODELS = [\n        'gemini-2.0-flash',\n        'gemini-1.5-flash',\n        'gemini-2.5-flash-preview-05-20',\n        'gemini-2.5-flash',\n        'gemini-2.0-flash-lite',\n    ];\n\n    \/\/ \u2500\u2500 Estado \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    let loteFacturas = [];\n    let apiKey = '';\n\n    \/\/ \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    document.addEventListener('DOMContentLoaded', () => {\n        lucide.createIcons();\n        apiKey = localStorage.getItem('gemini_api_key') || '';\n        actualizarBadgeKey();\n        if (!apiKey) mostrarModalApiKey();\n    });\n\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    \/\/ LOG VISIBLE\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    function log(msg, tipo = 'info') {\n        const container = document.getElementById('log-container');\n        const panel     = document.getElementById('log-panel');\n        container.classList.remove('hidden');\n\n        const line = document.createElement('div');\n        line.className = `log-${tipo}`;\n        const ts = new Date().toLocaleTimeString('es-ES');\n        line.textContent = `[${ts}] ${msg}`;\n        panel.appendChild(line);\n        panel.scrollTop = panel.scrollHeight;\n    }\n\n    function limpiarLog() {\n        document.getElementById('log-panel').innerHTML = '';\n    }\n\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    \/\/ API KEY\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    function mostrarModalApiKey() {\n        document.getElementById('apiKeyModal').classList.remove('hidden');\n        document.getElementById('apiKeyInput').value = apiKey || '';\n        document.getElementById('apiKeyError').classList.add('hidden');\n    }\n\n    function guardarApiKey() {\n        const val = document.getElementById('apiKeyInput').value.trim();\n        if (!val || val.length < 10) {\n            document.getElementById('apiKeyError').textContent =\n                'La clave parece demasiado corta. C\u00f3piala completa desde AI Studio.';\n            document.getElementById('apiKeyError').classList.remove('hidden');\n            return;\n        }\n        apiKey = val;\n        if (document.getElementById('rememberKey').checked) {\n            localStorage.setItem('gemini_api_key', apiKey);\n        }\n        document.getElementById('apiKeyModal').classList.add('hidden');\n        actualizarBadgeKey();\n        lucide.createIcons();\n    }\n\n    \/\/ Test r\u00e1pido de la clave con una llamada de texto (sin imagen)\n    async function testApiKey() {\n        const val = document.getElementById('apiKeyInput').value.trim();\n        if (!val) {\n            document.getElementById('apiKeyError').textContent = 'Escribe la clave primero.';\n            document.getElementById('apiKeyError').classList.remove('hidden');\n            return;\n        }\n        document.getElementById('apiKeyError').textContent = 'Probando\u2026';\n        document.getElementById('apiKeyError').classList.remove('hidden');\n\n        const url = `https:\/\/generativelanguage.googleapis.com\/v1beta\/models\/gemini-2.0-flash:generateContent?key=${val}`;\n        try {\n            const res = await fetch(url, {\n                method: 'POST',\n                headers: { 'Content-Type': 'application\/json' },\n                body: JSON.stringify({\n                    contents: [{ parts: [{ text: 'Responde solo: OK' }] }]\n                })\n            });\n            const data = await res.json();\n            if (res.ok) {\n                const reply = data?.candidates?.[0]?.content?.parts?.[0]?.text || '?';\n                document.getElementById('apiKeyError').textContent = `\u2705 Clave v\u00e1lida. Gemini respondi\u00f3: \"${reply.trim()}\"`;\n                document.getElementById('apiKeyError').className =\n                    'text-xs text-green-700 bg-green-50 border border-green-200 rounded-lg px-3 py-2';\n            } else {\n                const err = data?.error?.message || `HTTP ${res.status}`;\n                document.getElementById('apiKeyError').textContent = `\u274c Error: ${err}`;\n                document.getElementById('apiKeyError').className =\n                    'text-xs text-red-600 bg-red-50 border border-red-200 rounded-lg px-3 py-2';\n            }\n        } catch (e) {\n            document.getElementById('apiKeyError').textContent = `\u274c Error de red: ${e.message}`;\n        }\n        document.getElementById('apiKeyError').classList.remove('hidden');\n    }\n\n    function actualizarBadgeKey() {\n        document.getElementById('apiKeyDot').className =\n            apiKey ? 'w-2 h-2 rounded-full bg-emerald-500'\n                   : 'w-2 h-2 rounded-full bg-slate-300';\n        document.getElementById('apiKeyLabel').textContent =\n            apiKey ? 'API Key activa' : 'Sin API Key';\n    }\n\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    \/\/ PREPARAR IMAGEN \/ PDF\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    async function prepararParaGemini(file) {\n        log(`Preparando: ${file.name} (${formatBytes(file.size)})`);\n\n        const isPDF = file.type === 'application\/pdf' ||\n                      file.name.toLowerCase().endsWith('.pdf');\n\n        if (isPDF) {\n            if (typeof pdfjsLib === 'undefined') {\n                throw new Error('PDF.js no carg\u00f3. Prueba con una imagen JPG o PNG.');\n            }\n            log('PDF detectado \u2192 renderizando p\u00e1gina 1 con PDF.js\u2026');\n            const buf  = await file.arrayBuffer();\n            const pdf  = await pdfjsLib.getDocument({ data: buf }).promise;\n            const page = await pdf.getPage(1);\n            const vp   = page.getViewport({ scale: 2.0 });\n            const cv   = Object.assign(document.createElement('canvas'),\n                                       { width: vp.width, height: vp.height });\n            await page.render({ canvasContext: cv.getContext('2d'), viewport: vp }).promise;\n            const base64 = cv.toDataURL('image\/jpeg', 0.92).split(',')[1];\n            log(`PDF \u2192 JPEG listo (${vp.width}\u00d7${vp.height}px)`, 'ok');\n            return { base64, mimeType: 'image\/jpeg' };\n        }\n\n        \/\/ Imagen: redimensionar a m\u00e1x 1400px\n        return new Promise((resolve, reject) => {\n            const reader = new FileReader();\n            reader.onerror = () => reject(new Error('No se pudo leer el archivo'));\n            reader.onload = e => {\n                const img = new Image();\n                img.onerror = () => reject(new Error('No es una imagen v\u00e1lida'));\n                img.onload = () => {\n                    const MAX = 1400;\n                    let { width, height } = img;\n                    if (width > MAX || height > MAX) {\n                        const r = Math.min(MAX \/ width, MAX \/ height);\n                        width  = Math.round(width  * r);\n                        height = Math.round(height * r);\n                    }\n                    const cv = Object.assign(document.createElement('canvas'),\n                                             { width, height });\n                    cv.getContext('2d').drawImage(img, 0, 0, width, height);\n                    const base64 = cv.toDataURL('image\/jpeg', 0.92).split(',')[1];\n                    log(`Imagen lista: ${width}\u00d7${height}px`, 'ok');\n                    resolve({ base64, mimeType: 'image\/jpeg' });\n                };\n                img.src = e.target.result;\n            };\n            reader.readAsDataURL(file);\n        });\n    }\n\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    \/\/ LLAMADA A GEMINI\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    async function llamarGemini(base64, mimeType) {\n        \/\/ Prompt directo \u2014 sin plantilla JSON en el prompt para evitar confusi\u00f3n al modelo\n        const prompt =\n`Eres un asistente experto en facturas espa\u00f1olas.\nAnaliza la imagen y devuelve SOLO un objeto JSON v\u00e1lido con estos campos (usa null si no encuentras el dato):\n- empresa: nombre de la empresa emisora\n- cif: CIF o NIF (ej: A12345678)\n- numero_factura: n\u00famero de factura\n- fecha: fecha en formato YYYY-MM-DD\n- base_imponible: n\u00famero decimal (sin s\u00edmbolo \u20ac)\n- iva_porcentaje: n\u00famero entero (21, 10, 4 o 0)\n- total: n\u00famero decimal (sin s\u00edmbolo \u20ac)\nNo incluyas explicaciones, solo el JSON.`;\n\n        for (const model of GEMINI_MODELS) {\n            for (const ver of ['v1beta', 'v1']) {\n                const url = `https:\/\/generativelanguage.googleapis.com\/${ver}\/models\/${model}:generateContent?key=${apiKey}`;\n                log(`\u2192 Probando ${model} [${ver}]\u2026`);\n                setStatus(`${model} (${ver})\u2026`);\n\n                let res;\n                try {\n                    res = await fetch(url, {\n                        method: 'POST',\n                        headers: { 'Content-Type': 'application\/json' },\n                        body: JSON.stringify({\n                            contents: [{\n                                parts: [\n                                    { text: prompt },\n                                    { inline_data: { mime_type: mimeType, data: base64 } }\n                                ]\n                            }],\n                            generationConfig: {\n                                temperature: 0,\n                                maxOutputTokens: 2048\n                            }\n                        })\n                    });\n                } catch (netErr) {\n                    log(`  Red: ${netErr.message}`, 'err');\n                    continue;\n                }\n\n                log(`  HTTP ${res.status}`);\n\n                \/\/ Rate limit\n                if (res.status === 429) {\n                    log('  L\u00edmite de solicitudes (429) \u2014 esperando 65s\u2026', 'warn');\n                    for (let s = 65; s > 0; s--) {\n                        document.getElementById('retry-box').classList.remove('hidden');\n                        document.getElementById('retry-countdown').textContent = s;\n                        await new Promise(r => setTimeout(r, 1000));\n                    }\n                    document.getElementById('retry-box').classList.add('hidden');\n                    try {\n                        res = await fetch(url, {\n                            method: 'POST',\n                            headers: { 'Content-Type': 'application\/json' },\n                            body: JSON.stringify({\n                                contents: [{\n                                    parts: [\n                                        { text: prompt },\n                                        { inline_data: { mime_type: mimeType, data: base64 } }\n                                    ]\n                                }],\n                                generationConfig: { temperature: 0, maxOutputTokens: 2048 }\n                            })\n                        });\n                        log(`  Reintento \u2192 HTTP ${res.status}`);\n                    } catch (e) {\n                        log(`  Reintento fall\u00f3: ${e.message}`, 'err');\n                        continue;\n                    }\n                }\n\n                if (!res.ok) {\n                    \/\/ Mostrar el cuerpo del error para diagnosticar\n                    let body = '';\n                    try { body = (await res.json())?.error?.message || ''; } catch (_) {}\n                    log(`  \u2717 Error ${res.status}: ${body}`, 'err');\n                    continue;\n                }\n\n                const data = await res.json();\n                const text = data?.candidates?.[0]?.content?.parts?.[0]?.text;\n                if (!text) {\n                    const reason = data?.candidates?.[0]?.finishReason || 'sin texto';\n                    log(`  \u2717 Respuesta vac\u00eda (${reason})`, 'err');\n                    continue;\n                }\n\n                log(`  Respuesta: ${text.slice(0, 120)}\u2026`);\n\n                \/\/ Parsear JSON \u2014 Gemini a veces a\u00f1ade bloques ```json \u2026 ```\n                const clean = text\n                    .replace(\/^```(?:json)?\\s*\/im, '')\n                    .replace(\/\\s*```\\s*$\/m, '')\n                    .trim();\n                try {\n                    const parsed = JSON.parse(clean);\n                    log(`  \u2713 JSON parseado correctamente`, 'ok');\n                    return parsed;\n                } catch (_) {\n                    \/\/ \u00daltimo intento: extraer el primer objeto JSON con regex\n                    const match = clean.match(\/\\{[\\s\\S]*\\}\/);\n                    if (match) {\n                        try {\n                            const parsed = JSON.parse(match[0]);\n                            log(`  \u2713 JSON extra\u00eddo con regex`, 'ok');\n                            return parsed;\n                        } catch (e2) {\n                            log(`  \u2717 JSON inv\u00e1lido: ${e2.message}`, 'err');\n                        }\n                    } else {\n                        log(`  \u2717 No se encontr\u00f3 JSON en la respuesta`, 'err');\n                    }\n                }\n            }\n        }\n        throw new Error(\n            'Ning\u00fan modelo pudo extraer los datos. Revisa el log y comprueba tu API Key.'\n        );\n    }\n\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    \/\/ PROCESAR ARCHIVO\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    async function procesarArchivo(file) {\n        if (!file) return;\n        if (!apiKey) { mostrarModalApiKey(); return; }\n\n        limpiarLog();\n        log(`Archivo: ${file.name}`);\n\n        document.getElementById('loadingStatus').classList.remove('hidden');\n        document.getElementById('dropzone').classList.add('opacity-40', 'pointer-events-none');\n        document.getElementById('confidence-badge').classList.add('hidden');\n        animarProgreso(15, 500);\n\n        try {\n            const { base64, mimeType } = await prepararParaGemini(file);\n            animarProgreso(40, 300);\n            log('Llamando a Gemini Vision\u2026');\n            const datos = await llamarGemini(base64, mimeType);\n            animarProgreso(100, 200);\n            setStatus('\u2713 Completado');\n            rellenarFormulario(datos);\n            mostrarConfianza(datos);\n            log('Formulario rellenado.', 'ok');\n        } catch (err) {\n            log(`ERROR: ${err.message}`, 'err');\n            setStatus('Error \u2014 ver log');\n        } finally {\n            document.getElementById('dropzone').classList.remove('opacity-40', 'pointer-events-none');\n            setTimeout(() => {\n                document.getElementById('loadingStatus').classList.add('hidden');\n                document.getElementById('ai-progress-bar').style.width = '0';\n            }, 3000);\n        }\n    }\n\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    \/\/ RELLENAR FORMULARIO\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    function rellenarFormulario(d) {\n        const poner = (id, val) => {\n            if (val === null || val === undefined) return;\n            const el = document.getElementById(id);\n            el.value = val;\n            el.style.background = '#f0fdf4';\n            setTimeout(() => el.style.background = '', 1000);\n        };\n        poner('nombre_empresa', d.empresa);\n        poner('cif_factura',    d.cif);\n        poner('num_factura',    d.numero_factura);\n        poner('fecha_factura',  d.fecha);\n        poner('base_imponible', d.base_imponible != null\n                                    ? parseFloat(d.base_imponible).toFixed(2)\n                                    : null);\n\n        if (d.iva_porcentaje != null) {\n            const opciones = [0, 4, 10, 21];\n            const mejor = opciones.reduce((p, c) =>\n                Math.abs(c - d.iva_porcentaje) < Math.abs(p - d.iva_porcentaje) ? c : p);\n            document.getElementById('iva_porcentaje').value = String(mejor);\n        }\n        calcularTotal();\n    }\n\n    function mostrarConfianza(d) {\n        const campos = ['empresa','cif','numero_factura','fecha','base_imponible'];\n        const ok = campos.filter(k => d[k] !== null && d[k] !== undefined && d[k] !== '').length;\n        const pct = Math.round(ok \/ campos.length * 100);\n        const badge = document.getElementById('confidence-badge');\n        badge.classList.remove('hidden');\n        if (pct >= 80) {\n            badge.className = 'text-xs font-medium px-2.5 py-1 rounded-md border ' +\n                              'bg-emerald-50 border-emerald-200 text-emerald-700';\n            badge.textContent = `Confianza alta \u00b7 ${pct}%`;\n        } else if (pct >= 50) {\n            badge.className = 'text-xs font-medium px-2.5 py-1 rounded-md border ' +\n                              'bg-amber-50 border-amber-200 text-amber-700';\n            badge.textContent = `Confianza media \u00b7 ${pct}% \u2014 revisa`;\n        } else {\n            badge.className = 'text-xs font-medium px-2.5 py-1 rounded-md border ' +\n                              'bg-red-50 border-red-200 text-red-700';\n            badge.textContent = `Confianza baja \u00b7 ${pct}% \u2014 revisa`;\n        }\n    }\n\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    \/\/ HELPERS\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    function setStatus(msg) {\n        document.getElementById('ai-status-text').textContent = msg;\n    }\n\n    function animarProgreso(target, ms) {\n        const bar = document.getElementById('ai-progress-bar');\n        bar.style.transition = `width ${ms}ms ease`;\n        bar.style.width = target + '%';\n    }\n\n    function calcularTotal() {\n        const base = parseFloat(document.getElementById('base_imponible').value) || 0;\n        const iva  = parseFloat(document.getElementById('iva_porcentaje').value) || 0;\n        document.getElementById('total_factura').textContent =\n            (base + base * iva \/ 100).toFixed(2) + ' \u20ac';\n    }\n\n    function formatBytes(b) {\n        if (b < 1024) return b + ' B';\n        if (b < 1048576) return (b \/ 1024).toFixed(1) + ' KB';\n        return (b \/ 1048576).toFixed(1) + ' MB';\n    }\n\n    function esc(s) {\n        return String(s || '')\n            .replace(\/&\/g,'&amp;').replace(\/<\/g,'&lt;')\n            .replace(\/>\/g,'&gt;').replace(\/\"\/g,'&quot;');\n    }\n\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    \/\/ GUARDAR \/ TABLA \/ EXPORT\n    \/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n    function guardarFactura(e) {\n        e.preventDefault();\n        const base  = parseFloat(document.getElementById('base_imponible').value) || 0;\n        const iva   = parseFloat(document.getElementById('iva_porcentaje').value) || 0;\n        loteFacturas.push({\n            id:      Date.now(),\n            empresa: document.getElementById('nombre_empresa').value,\n            cif:     document.getElementById('cif_factura').value,\n            numero:  document.getElementById('num_factura').value,\n            fecha:   document.getElementById('fecha_factura').value,\n            base, iva,\n            total: base + base * iva \/ 100\n        });\n        actualizarTabla();\n        document.getElementById('invoiceForm').reset();\n        document.getElementById('total_factura').textContent = '0.00 \u20ac';\n        document.getElementById('fileInput').value = '';\n        document.getElementById('confidence-badge').classList.add('hidden');\n    }\n\n    function actualizarTabla() {\n        const tbody = document.getElementById('tablaFacturas');\n        const fv    = document.getElementById('filaVacia');\n        if (fv) fv.remove();\n        tbody.querySelectorAll('.fr').forEach(r => r.remove());\n\n        let total = 0;\n        loteFacturas.forEach(f => {\n            total += f.total;\n            const tr = document.createElement('tr');\n            tr.className = 'fr hover:bg-slate-50 transition-colors';\n            tr.innerHTML = `\n                <td class=\"py-3 px-6\">\n                    <div class=\"font-medium\">${esc(f.empresa)}<\/div>\n                    <div class=\"text-xs text-slate-400\">${esc(f.cif)}<\/div>\n                <\/td>\n                <td class=\"py-3 px-6 font-mono text-xs text-slate-600\">${esc(f.numero)}<\/td>\n                <td class=\"py-3 px-6 text-slate-600\">${f.fecha}<\/td>\n                <td class=\"py-3 px-6 text-right\">${f.base.toFixed(2)} \u20ac<\/td>\n                <td class=\"py-3 px-6 text-center text-slate-500\">${f.iva}%<\/td>\n                <td class=\"py-3 px-6 text-right font-semibold\">${f.total.toFixed(2)} \u20ac<\/td>\n                <td class=\"py-3 px-6 text-right\">\n                    <button onclick=\"borrarFila(${f.id})\"\n                            class=\"text-slate-300 hover:text-red-400 transition-colors\">\n                        <i data-lucide=\"trash-2\" class=\"w-4 h-4\"><\/i>\n                    <\/button>\n                <\/td>`;\n            tbody.appendChild(tr);\n        });\n        document.getElementById('totalAcumulado').textContent = total.toFixed(2) + ' \u20ac';\n        lucide.createIcons();\n    }\n\n    function borrarFila(id) {\n        loteFacturas = loteFacturas.filter(f => f.id !== id);\n        if (loteFacturas.length === 0) {\n            document.getElementById('tablaFacturas').innerHTML = `\n                <tr id=\"filaVacia\">\n                    <td colspan=\"7\" class=\"py-10 text-center text-slate-400\">\n                        <div class=\"flex flex-col items-center gap-2\">\n                            <i data-lucide=\"folder-open\" class=\"w-8 h-8 opacity-40\"><\/i>\n                            Sin facturas guardadas a\u00fan.\n                        <\/div>\n                    <\/td>\n                <\/tr>`;\n            lucide.createIcons();\n        } else {\n            actualizarTabla();\n        }\n    }\n\n    function exportarCSV() {\n        if (!loteFacturas.length) { alert('Guarda alguna factura primero.'); return; }\n        let csv = '\ufeffEmpresa;CIF;Numero;Fecha;Base;IVA;Total\\r\\n';\n        loteFacturas.forEach(f => {\n            csv += `\"${f.empresa}\";\"${f.cif}\";\"${f.numero}\";\"${f.fecha}\";`\n                 + `\"${f.base.toFixed(2)}\";\"${f.iva}\";\"${f.total.toFixed(2)}\"\\r\\n`;\n        });\n        const a = Object.assign(document.createElement('a'), {\n            href:     URL.createObjectURL(new Blob([csv], { type: 'text\/csv;charset=utf-8' })),\n            download: `Facturas_${new Date().toISOString().split('T')[0]}.csv`\n        });\n        a.click();\n    }\n\n    \/\/ \u2500\u2500 Drag & drop \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    const dz = document.getElementById('dropzone');\n    ['dragenter','dragover'].forEach(ev =>\n        dz.addEventListener(ev, e => { e.preventDefault();\n            dz.classList.add('border-slate-900'); }));\n    ['dragleave','drop'].forEach(ev =>\n        dz.addEventListener(ev, e => { e.preventDefault();\n            dz.classList.remove('border-slate-900'); }));\n    dz.addEventListener('drop', e => procesarArchivo(e.dataTransfer.files[0]));\n    document.addEventListener('dragover', e => e.preventDefault());\n    document.addEventListener('drop',     e => e.preventDefault());\n    <\/script>\n<\/body>\n<\/html>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f6a44e3 elementor-widget elementor-widget-invoice_generator_4f693ede\" data-id=\"f6a44e3\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"invoice_generator_4f693ede.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t        <div class=\"invoice-generator-wrapper-4f693ede\">\n            <div class=\"invoice-controls\">\n                <button class=\"btn btn-primary add-row-btn\">A\u00f1adir L\u00ednea<\/button>\n                <button class=\"btn btn-secondary export-pdf-btn\">Exportar a PDF<\/button>\n            <\/div>\n            \n            <div class=\"invoice-document\" id=\"invoice-doc-4f693ede\">\n                <h2 class=\"invoice-header\">My Company LLC - Factura<\/h2>\n                <div class=\"invoice-meta\">\n                    <p><strong>Fecha:<\/strong> <span class=\"current-date\"><\/span><\/p>\n                <\/div>\n                \n                <table class=\"invoice-table\">\n                    <thead>\n                        <tr>\n                            <th>Descripci\u00f3n<\/th>\n                            <th>Cantidad<\/th>\n                            <th>Precio Unitario (\u20ac)<\/th>\n                            <th>Subtotal (\u20ac)<\/th>\n                            <th class=\"no-print\">Acci\u00f3n<\/th>\n                        <\/tr>\n                    <\/thead>\n                    <tbody class=\"invoice-body\">\n                        <!-- Rows will be added here via JS -->\n                    <\/tbody>\n                    <tfoot>\n                        <tr>\n                            <td colspan=\"3\" class=\"text-right\"><strong>Total:<\/strong><\/td>\n                            <td class=\"total-cell\"><strong>\u20ac <span class=\"invoice-total\">0.00<\/span><\/strong><\/td>\n                            <td class=\"no-print\"><\/td>\n                        <\/tr>\n                    <\/tfoot>\n                <\/table>\n            <\/div>\n        <\/div>\n        \t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>SmartInvoice &#8211; Aut\u00f3nomos Configurar API Key de Gemini Obt\u00e9n tu clave gratuita en aistudio.google.com \u2192 Get API key. C\u00f3piala y [&hellip;]<\/p>\n","protected":false},"author":5,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_angie_page":false,"site-sidebar-layout":"no-sidebar","site-content-layout":"","ast-site-content-layout":"full-width-container","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"disabled","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"class_list":["post-1648","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>facturas -<\/title>\n<meta name=\"robots\" content=\"noindex, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<meta property=\"og:locale\" content=\"en_GB\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"facturas -\" \/>\n<meta property=\"og:description\" content=\"SmartInvoice &#8211; Aut\u00f3nomos Configurar API Key de Gemini Obt\u00e9n tu clave gratuita en aistudio.google.com \u2192 Get API key. C\u00f3piala y [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/decotex.es\/en\/facturas\/\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-07T12:05:19+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Estimated reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/decotex.es\\\/facturas\\\/\",\"url\":\"https:\\\/\\\/decotex.es\\\/facturas\\\/\",\"name\":\"facturas -\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/decotex.es\\\/#website\"},\"datePublished\":\"2026-06-07T11:24:09+00:00\",\"dateModified\":\"2026-06-07T12:05:19+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/decotex.es\\\/facturas\\\/#breadcrumb\"},\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/decotex.es\\\/facturas\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/decotex.es\\\/facturas\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Portada\",\"item\":\"https:\\\/\\\/decotex.es\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"facturas\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/decotex.es\\\/#website\",\"url\":\"https:\\\/\\\/decotex.es\\\/\",\"name\":\"\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/decotex.es\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-GB\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"facturas -","robots":{"index":"noindex","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"og_locale":"en_GB","og_type":"article","og_title":"facturas -","og_description":"SmartInvoice &#8211; Aut\u00f3nomos Configurar API Key de Gemini Obt\u00e9n tu clave gratuita en aistudio.google.com \u2192 Get API key. C\u00f3piala y [&hellip;]","og_url":"https:\/\/decotex.es\/en\/facturas\/","article_modified_time":"2026-06-07T12:05:19+00:00","twitter_card":"summary_large_image","twitter_misc":{"Estimated reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/decotex.es\/facturas\/","url":"https:\/\/decotex.es\/facturas\/","name":"facturas -","isPartOf":{"@id":"https:\/\/decotex.es\/#website"},"datePublished":"2026-06-07T11:24:09+00:00","dateModified":"2026-06-07T12:05:19+00:00","breadcrumb":{"@id":"https:\/\/decotex.es\/facturas\/#breadcrumb"},"inLanguage":"en-GB","potentialAction":[{"@type":"ReadAction","target":["https:\/\/decotex.es\/facturas\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/decotex.es\/facturas\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Portada","item":"https:\/\/decotex.es\/"},{"@type":"ListItem","position":2,"name":"facturas"}]},{"@type":"WebSite","@id":"https:\/\/decotex.es\/#website","url":"https:\/\/decotex.es\/","name":"","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/decotex.es\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-GB"}]}},"_links":{"self":[{"href":"https:\/\/decotex.es\/en\/wp-json\/wp\/v2\/pages\/1648","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/decotex.es\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/decotex.es\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/decotex.es\/en\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/decotex.es\/en\/wp-json\/wp\/v2\/comments?post=1648"}],"version-history":[{"count":9,"href":"https:\/\/decotex.es\/en\/wp-json\/wp\/v2\/pages\/1648\/revisions"}],"predecessor-version":[{"id":1660,"href":"https:\/\/decotex.es\/en\/wp-json\/wp\/v2\/pages\/1648\/revisions\/1660"}],"wp:attachment":[{"href":"https:\/\/decotex.es\/en\/wp-json\/wp\/v2\/media?parent=1648"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}