{"id":1165,"date":"2026-04-14T10:48:53","date_gmt":"2026-04-14T08:48:53","guid":{"rendered":"https:\/\/www.tisoft.it\/?page_id=1165"},"modified":"2026-04-17T10:14:20","modified_gmt":"2026-04-17T08:14:20","slug":"shop-service","status":"publish","type":"page","link":"https:\/\/www.tisoft.it\/en\/shop-service\/","title":{"rendered":"Shop &amp; Service"},"content":{"rendered":"    <style>\n        #ti-ai-outer {\n            --ti-font-base: 13px;\n            --ti-font-small: 12px;\n            --ti-font-xs: 11px;\n            --ti-font-large: 14px;\n            --ti-btn-h: 34px;\n            --ti-btn-radius: 8px;\n            --ti-input-h: 34px;\n            --ti-row-h: 34px;\n            --ti-vh: 1vh;\n            width: 100%;\n            max-width: 800px;\n            margin: 0 auto;\n            padding: 12px;\n            font-family: Arial, Helvetica, sans-serif !important;\n            font-size: var(--ti-font-base) !important;\n            line-height: 1.3 !important;\n            color: #e5e7eb;\n            box-sizing: border-box;\n            overflow-x: hidden;\n            overflow-y: visible;\n            touch-action: pan-y;\n            overscroll-behavior-y: auto;\n            -webkit-overflow-scrolling: touch;\n            -webkit-text-size-adjust: 100%;\n            text-size-adjust: 100%;\n        }\n        #ti-ai-outer, #ti-ai-outer * { box-sizing: border-box; }\n        #ti-ai-outer button,\n        #ti-ai-outer input,\n        #ti-ai-outer select,\n        #ti-ai-outer textarea,\n        #ti-ai-outer label,\n        #ti-ai-outer th,\n        #ti-ai-outer td,\n        #ti-ai-outer span,\n        #ti-ai-outer div,\n        #ti-ai-outer a {\n            font-family: Arial, Helvetica, sans-serif !important;\n        }\n        #ti-ai-outer button,\n        #ti-ai-outer input,\n        #ti-ai-outer select,\n        #ti-ai-outer textarea {\n            font-size: var(--ti-font-base) !important;\n            line-height: 1.2 !important;\n        }\n        #ti-ai-outer .ti-btn,\n        #ti-ai-outer .ti-send,\n        #ti-ai-outer .ti-help,\n        #ti-ai-outer #ti-sort-btn,\n        #ti-ai-outer #ti-show-sospese-btn,\n        #ti-ai-outer #ti-login-btn,\n        #ti-ai-outer #ti-logout-btn,\n        #ti-ai-outer .ti-lang-opt {\n            min-height: var(--ti-btn-h) !important;\n            font-size: var(--ti-font-base) !important;\n            line-height: 1.1 !important;\n            border-radius: var(--ti-btn-radius) !important;\n            white-space: nowrap !important;\n        }\n        #ti-ai-outer .ti-btn {\n            padding: 8px 12px !important;\n            font-weight: 600 !important;\n        }\n        #ti-ai-outer .ti-send {\n            height: 38px !important;\n            min-height: 38px !important;\n            font-weight: 900 !important;\n            font-size: var(--ti-font-base) !important;\n        }\n        #ti-ai-outer .ti-help {\n            height: 24px !important;\n            min-height: 24px !important;\n            font-size: var(--ti-font-large) !important;\n            line-height: 1 !important;\n        }\n        #ti-ai-outer #ti-filter-ditte,\n        #ti-ai-outer #ti-ditta,\n        #ti-ai-outer .ti-in,\n        #ti-ai-outer .ti-msg {\n            font-size: var(--ti-font-base) !important;\n        }\n        #ti-ai-outer #ti-filter-ditte,\n        #ti-ai-outer #ti-ditta,\n        #ti-ai-outer .ti-in {\n            min-height: var(--ti-input-h) !important;\n            border-radius: 8px !important;\n        }\n        #ti-ai-outer .ti-user-name,\n        #ti-ai-outer .ti-user-role,\n        #ti-ai-outer #ti-company-sub,\n        #ti-ai-outer #ti-company-name {\n            font-family: Arial, Helvetica, sans-serif !important;\n        }\n        #ti-ai-outer .ti-ditta-row,\n        #ti-ai-outer .ti-ditta-row-main {\n            font-size: var(--ti-font-base) !important;\n            line-height: 1.25 !important;\n        }\n        #ti-ai-outer .ti-ditta-row {\n            min-height: var(--ti-row-h) !important;\n            padding: 8px 10px !important;\n        }\n        #ti-ai-outer .ti-ditta-row-badge {\n            font-size: var(--ti-font-xs) !important;\n            line-height: 1.1 !important;\n        }\n        #ti-ai-outer .ti-prod-table {\n            font-size: var(--ti-font-base) !important;\n        }\n        #ti-ai-outer .ti-prod-table th {\n            font-size: var(--ti-font-xs) !important;\n        }\n        #ti-ai-outer .ti-prod-table td,\n        #ti-ai-outer .ti-filter-input,\n        #ti-ai-outer .ti-order-filterlabel,\n        #ti-ai-outer .ti-order-filtervalue {\n            font-size: var(--ti-font-small) !important;\n        }\n        #ti-ai-outer .ti-modal h3,\n        #ti-ai-outer .ti-modal h4,\n        #ti-ai-outer .ti-modal p,\n        #ti-ai-outer .ti-modal label,\n        #ti-ai-outer .ti-modal .ti-btn,\n        #ti-ai-outer .ti-modal .ti-in,\n        #ti-ai-outer .ti-modal select,\n        #ti-ai-outer .ti-modal textarea {\n            font-family: Arial, Helvetica, sans-serif !important;\n        }\n        #ti-ai-outer .ti-modal h3 { font-size: 18px !important; line-height: 1.2 !important; }\n        #ti-ai-outer .ti-modal h4 { font-size: 16px !important; line-height: 1.2 !important; }\n        #ti-ai-outer .ti-modal p,\n        #ti-ai-outer .ti-modal label,\n        #ti-ai-outer .ti-modal .ti-in,\n        #ti-ai-outer .ti-modal select,\n        #ti-ai-outer .ti-modal textarea,\n        #ti-ai-outer .ti-modal .ti-btn { font-size: var(--ti-font-base) !important; }\n        @media (max-width: 640px) {\n            #ti-ai-outer {\n                --ti-font-base: 13px;\n                --ti-font-small: 12px;\n                --ti-font-xs: 11px;\n                --ti-font-large: 14px;\n                --ti-btn-h: 34px;\n                --ti-input-h: 34px;\n                --ti-row-h: 34px;\n            }\n        }\n        #ti-ai-outer * { box-sizing: border-box; }\n        .ti-header { display: flex; flex-direction: column; align-items: stretch; justify-content: flex-start; padding: 15px; min-height: 85px; border-radius: 14px; background: #111827; border: 1px solid rgba(255,255,255,0.08); margin-bottom: 10px; position: relative; gap: 10px; }\n        .ti-header-top { display:flex; align-items:center; justify-content:space-between; gap:12px; width:100%; min-width:0; }\n        .ti-header-main-left { display:flex; align-items:center; gap:12px; min-width:0; flex:1 1 auto; }\n        .ti-hl { display: flex; align-items: center; justify-content: flex-start; gap: 12px; width: 100%; min-width: 0; flex:1 1 auto; }\n        .ti-left-stack { display:flex; flex-direction:row; align-items:center; gap:6px; }\n        .ti-company-sub-row { display:flex; width:100%; justify-content:center; align-items:center; min-height:0; }\n        #ti-company-sub { text-align:center; width:100%; }\n        #ti-company-name { font-weight:900; font-size:2.2em; line-height:1.05; color:#fff; letter-spacing:0.01em; }\n        #ti-company-sub { font-size:14px; line-height:1.2; }\n        .ti-header-bottom { display:flex; align-items:center; justify-content:space-between; gap:10px; width:100%; flex-wrap:wrap; }\n        .ti-header-left-controls { display:flex; align-items:center; gap:8px; flex:1 1 auto; min-width:0; flex-wrap:wrap; }\n        .ti-header-right-controls { display:flex; align-items:center; justify-content:flex-end; gap:8px; margin-left:auto; flex:0 1 auto; flex-wrap:wrap; }\n        .ti-hr-right { display: flex; align-items: center; justify-content: flex-end; gap: 8px; width: auto; flex: 0 0 auto; }\n        .ti-hr-bottom { display: flex; align-items: center; gap: 8px; }\n        .ti-lang-wrap { display: flex; gap: 10px; font-size: 15px; font-weight: 800; z-index: 10; align-items:center; justify-content:flex-end; text-align:right; }\n        .ti-lang-opt { color: #6b7280; cursor: pointer; }\n        .ti-lang-opt.active { color: #38bdf8; text-decoration: underline; }\n        .ti-logo { width: 44px; height: 44px; object-fit: contain; border-radius: 8px; background: rgba(255,255,255,0.1); padding: 2px; cursor: pointer; transition: transform 0.2s; }\n        .ti-logo:hover { transform: scale(1.06); }\n        .ti-qr { width: 44px; height: 44px; object-fit: contain; border-radius: 8px; background: #fff; padding: 2px; cursor: pointer; transition: transform 0.2s; }\n        .ti-qr:hover { transform: scale(1.1); }\n        .ti-user-area { display: none; align-items: center; gap: 6px; text-align: left; margin: 0; background: rgba(255,255,255,0.05); padding: 0 10px; border-radius: 18px; border: 1px solid rgba(255,255,255,0.12); min-height: 34px; height: 34px; max-width: 240px; width: fit-content; box-sizing: border-box; }\n        .ti-hr-bottom .ti-user-area { flex: 0 1 auto; align-self: center; margin-left: 2px; }\n        .ti-user-avatar { width: 20px; height: 20px; border-radius: 50%; object-fit: cover; cursor: pointer; border: 1px solid #38bdf8; background: #000; flex: 0 0 20px; }\n        .ti-user-info { cursor: pointer; line-height: 1.05; min-width: 0; display:flex; align-items:center; height:100%; }\n        .ti-user-name { display: block; font-weight: 700; color: #fff; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n        .ti-user-role { display: inline; font-size: 10px; color: #cbd5e1; font-weight: 500; }\n        .ti-chat-box { min-height: 250px; max-height: 60vh; overflow-y: auto; padding: 12px; border-radius: 14px; background: #111827; border: 1px solid rgba(255,255,255,0.08); margin-bottom: 10px; -webkit-overflow-scrolling: touch; }\n        .ti-bubble { max-width: 96%; padding: 10px 12px; border-radius: 14px; margin: 10px 0; line-height: 1.35; white-space: pre-wrap; word-wrap: break-word; font-size: 13px !important; }\n        .ti-bubble p { font-size: 13px !important; margin: 5px 0; }\n        .ti-bubble.user { margin-left: auto; background: rgba(56,189,248,0.2); border: 1px solid rgba(56,189,248,0.3); text-align: right; }\n        .ti-bubble.ai { margin-right: auto; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); }\n        .ti-input-wrap { padding: 10px; border-radius: 14px; background: #111827; border: 1px solid rgba(255,255,255,0.08); }\n        .ti-input-main { display: flex; gap: 10px; }\n        .ti-msg { flex: 1; padding: 12px; border-radius: 12px; background: rgba(255,255,255,0.06); color: #fff; border: 1px solid rgba(255,255,255,0.12); outline: none; min-height: 126px; resize: vertical; font-size: 14px !important; line-height: 1.45; }\n\n        #ti-ai-outer,\n        #ti-ai-outer input,\n        #ti-ai-outer textarea,\n        #ti-ai-outer select,\n        #ti-ai-outer option,\n        #ti-ai-outer .ti-in,\n        #ti-ai-outer .ti-msg,\n        #ti-ai-outer .ti-filter-input,\n        #ti-ai-outer .ti-cfg-filter,\n        #ti-ai-outer .ti-import-map-select,\n        #ti-ai-outer .ti-import-map-input,\n        #ti-ai-outer .ti-p-input,\n        #ti-ai-outer .ti-q,\n        #ti-ai-outer .ti-support-box input,\n        #ti-ai-outer .ti-support-box textarea,\n        #ti-ai-outer #help-bot-in,\n        #ti-ai-outer #ai-gen-prompt,\n        #ti-ai-outer #ti-kbd-prev,\n        #ti-ai-outer [contenteditable=\"true\"] { color:#fff !important; -webkit-text-fill-color:#fff !important; caret-color:#fff !important; }\n        #ti-ai-outer .ti-conf-window input,\n        #ti-ai-outer .ti-conf-window textarea,\n        #ti-ai-outer .ti-conf-window select,\n        #ti-ai-outer .ti-conf-window option,\n        #ti-ai-outer .ti-conf-body input,\n        #ti-ai-outer .ti-conf-body textarea,\n        #ti-ai-outer .ti-conf-body select,\n        #ti-ai-outer .ti-conf-body option,\n        #ti-ai-outer .ti-cfg-table input,\n        #ti-ai-outer .ti-cfg-table textarea,\n        #ti-ai-outer .ti-cfg-table select,\n        #ti-ai-outer .ti-cfg-table option,\n        #ti-ai-outer .ti-import-map-table input,\n        #ti-ai-outer .ti-import-map-table textarea,\n        #ti-ai-outer .ti-import-map-table select,\n        #ti-ai-outer .ti-import-map-table option,\n        #ti-ai-outer .ti-conf-window .ti-in,\n        #ti-ai-outer .ti-conf-window .ti-q,\n        #ti-ai-outer .ti-conf-window .ti-p-input,\n        #ti-ai-outer .ti-conf-window .ti-import-map-input,\n        #ti-ai-outer .ti-conf-window .ti-import-map-select,\n        #ti-ai-outer .ti-conf-window [contenteditable=\"true\"] {\n            color:#fff !important;\n            -webkit-text-fill-color:#fff !important;\n            caret-color:#fff !important;\n        }\n        #ti-ai-outer .ti-conf-window input::placeholder,\n        #ti-ai-outer .ti-conf-window textarea::placeholder,\n        #ti-ai-outer .ti-conf-window .ti-in::placeholder,\n        #ti-ai-outer .ti-conf-window .ti-q::placeholder,\n        #ti-ai-outer .ti-conf-window .ti-p-input::placeholder,\n        #ti-ai-outer .ti-conf-window .ti-import-map-input::placeholder {\n            color:#cbd5e1 !important;\n            -webkit-text-fill-color:#cbd5e1 !important;\n            opacity:1 !important;\n        }\n        #ti-ai-outer .ti-conf-window input,\n        #ti-ai-outer .ti-conf-window textarea,\n        #ti-ai-outer .ti-conf-window select,\n        #ti-ai-outer .ti-cfg-table input,\n        #ti-ai-outer .ti-cfg-table textarea,\n        #ti-ai-outer .ti-cfg-table select,\n        #ti-ai-outer .ti-import-map-table input,\n        #ti-ai-outer .ti-import-map-table textarea,\n        #ti-ai-outer .ti-import-map-table select {\n            background:#000 !important;\n            border-color:#4b5563 !important;\n        }\n        #ti-ai-outer .ti-conf-window input:focus,\n        #ti-ai-outer .ti-conf-window textarea:focus,\n        #ti-ai-outer .ti-conf-window select:focus,\n        #ti-ai-outer .ti-cfg-table input:focus,\n        #ti-ai-outer .ti-cfg-table textarea:focus,\n        #ti-ai-outer .ti-cfg-table select:focus,\n        #ti-ai-outer .ti-import-map-table input:focus,\n        #ti-ai-outer .ti-import-map-table textarea:focus,\n        #ti-ai-outer .ti-import-map-table select:focus {\n            color:#fff !important;\n            -webkit-text-fill-color:#fff !important;\n            border-color:#93c5fd !important;\n            box-shadow:0 0 0 1px rgba(147,197,253,.35) !important;\n            outline:none !important;\n        }\n        #ti-ai-outer .ti-conf-window input:active,\n        #ti-ai-outer .ti-conf-window textarea:active,\n        #ti-ai-outer .ti-conf-window select:active,\n        #ti-ai-outer .ti-cfg-table input:active,\n        #ti-ai-outer .ti-cfg-table textarea:active,\n        #ti-ai-outer .ti-cfg-table select:active,\n        #ti-ai-outer .ti-import-map-table input:active,\n        #ti-ai-outer .ti-import-map-table textarea:active,\n        #ti-ai-outer .ti-import-map-table select:active,\n        #ti-ai-outer .ti-conf-window [contenteditable=\"true\"]:focus,\n        #ti-ai-outer .ti-conf-window [contenteditable=\"true\"]:active,\n        #ti-ai-outer .ti-cfg-table [contenteditable=\"true\"]:focus,\n        #ti-ai-outer .ti-cfg-table [contenteditable=\"true\"]:active,\n        #ti-ai-outer .ti-import-map-table [contenteditable=\"true\"]:focus,\n        #ti-ai-outer .ti-import-map-table [contenteditable=\"true\"]:active {\n            color:#fff !important;\n            -webkit-text-fill-color:#fff !important;\n            caret-color:#fff !important;\n            background:#000 !important;\n            text-shadow:none !important;\n        }\n        #ti-ai-outer .ti-conf-window input:-webkit-autofill,\n        #ti-ai-outer .ti-conf-window textarea:-webkit-autofill,\n        #ti-ai-outer .ti-conf-window select:-webkit-autofill,\n        #ti-ai-outer .ti-cfg-table input:-webkit-autofill,\n        #ti-ai-outer .ti-cfg-table textarea:-webkit-autofill,\n        #ti-ai-outer .ti-cfg-table select:-webkit-autofill,\n        #ti-ai-outer .ti-import-map-table input:-webkit-autofill,\n        #ti-ai-outer .ti-import-map-table textarea:-webkit-autofill,\n        #ti-ai-outer .ti-import-map-table select:-webkit-autofill {\n            -webkit-text-fill-color:#fff !important;\n            caret-color:#fff !important;\n            box-shadow:0 0 0 1000px #000 inset !important;\n            transition:background-color 99999s ease-in-out 0s !important;\n        }\n        #ti-ai-outer .ti-conf-window input,\n        #ti-ai-outer .ti-conf-window textarea,\n        #ti-ai-outer .ti-conf-window select,\n        #ti-ai-outer .ti-conf-window option,\n        #ti-ai-outer .ti-conf-body input,\n        #ti-ai-outer .ti-conf-body textarea,\n        #ti-ai-outer .ti-conf-body select,\n        #ti-ai-outer .ti-conf-body option,\n        #ti-ai-outer .ti-cfg-table input,\n        #ti-ai-outer .ti-cfg-table textarea,\n        #ti-ai-outer .ti-cfg-table select,\n        #ti-ai-outer .ti-cfg-table option,\n        #ti-ai-outer .ti-import-map-table input,\n        #ti-ai-outer .ti-import-map-table textarea,\n        #ti-ai-outer .ti-import-map-table select,\n        #ti-ai-outer .ti-import-map-table option {\n            color:#fff !important;\n            -webkit-text-fill-color:#fff !important;\n            caret-color:#fff !important;\n            background:#000 !important;\n            background-color:#000 !important;\n            background-image:none !important;\n            text-shadow:none !important;\n            color-scheme:dark !important;\n        }\n        #ti-ai-outer .ti-conf-window input:hover,\n        #ti-ai-outer .ti-conf-window textarea:hover,\n        #ti-ai-outer .ti-conf-window select:hover,\n        #ti-ai-outer .ti-cfg-table input:hover,\n        #ti-ai-outer .ti-cfg-table textarea:hover,\n        #ti-ai-outer .ti-cfg-table select:hover,\n        #ti-ai-outer .ti-import-map-table input:hover,\n        #ti-ai-outer .ti-import-map-table textarea:hover,\n        #ti-ai-outer .ti-import-map-table select:hover,\n        #ti-ai-outer .ti-conf-window input:focus,\n        #ti-ai-outer .ti-conf-window textarea:focus,\n        #ti-ai-outer .ti-conf-window select:focus,\n        #ti-ai-outer .ti-cfg-table input:focus,\n        #ti-ai-outer .ti-cfg-table textarea:focus,\n        #ti-ai-outer .ti-cfg-table select:focus,\n        #ti-ai-outer .ti-import-map-table input:focus,\n        #ti-ai-outer .ti-import-map-table textarea:focus,\n        #ti-ai-outer .ti-import-map-table select:focus,\n        #ti-ai-outer .ti-conf-window input:active,\n        #ti-ai-outer .ti-conf-window textarea:active,\n        #ti-ai-outer .ti-conf-window select:active,\n        #ti-ai-outer .ti-cfg-table input:active,\n        #ti-ai-outer .ti-cfg-table textarea:active,\n        #ti-ai-outer .ti-cfg-table select:active,\n        #ti-ai-outer .ti-import-map-table input:active,\n        #ti-ai-outer .ti-import-map-table textarea:active,\n        #ti-ai-outer .ti-import-map-table select:active {\n            color:#fff !important;\n            -webkit-text-fill-color:#fff !important;\n            background:#000 !important;\n            background-color:#000 !important;\n            background-image:none !important;\n        }\n        #ti-ai-outer .ti-conf-window option:checked,\n        #ti-ai-outer .ti-conf-window option:hover,\n        #ti-ai-outer .ti-cfg-table option:checked,\n        #ti-ai-outer .ti-cfg-table option:hover,\n        #ti-ai-outer .ti-import-map-table option:checked,\n        #ti-ai-outer .ti-import-map-table option:hover {\n            color:#fff !important;\n            -webkit-text-fill-color:#fff !important;\n            background:#111827 !important;\n            background-color:#111827 !important;\n        }\n        #ti-ai-outer .ti-conf-window input:-webkit-autofill,\n        #ti-ai-outer .ti-conf-window input:-webkit-autofill:hover,\n        #ti-ai-outer .ti-conf-window input:-webkit-autofill:focus,\n        #ti-ai-outer .ti-conf-window textarea:-webkit-autofill,\n        #ti-ai-outer .ti-conf-window textarea:-webkit-autofill:hover,\n        #ti-ai-outer .ti-conf-window textarea:-webkit-autofill:focus,\n        #ti-ai-outer .ti-cfg-table input:-webkit-autofill,\n        #ti-ai-outer .ti-cfg-table input:-webkit-autofill:hover,\n        #ti-ai-outer .ti-cfg-table input:-webkit-autofill:focus,\n        #ti-ai-outer .ti-cfg-table textarea:-webkit-autofill,\n        #ti-ai-outer .ti-cfg-table textarea:-webkit-autofill:hover,\n        #ti-ai-outer .ti-cfg-table textarea:-webkit-autofill:focus,\n        #ti-ai-outer .ti-import-map-table input:-webkit-autofill,\n        #ti-ai-outer .ti-import-map-table input:-webkit-autofill:hover,\n        #ti-ai-outer .ti-import-map-table input:-webkit-autofill:focus,\n        #ti-ai-outer .ti-import-map-table textarea:-webkit-autofill,\n        #ti-ai-outer .ti-import-map-table textarea:-webkit-autofill:hover,\n        #ti-ai-outer .ti-import-map-table textarea:-webkit-autofill:focus {\n            -webkit-text-fill-color:#fff !important;\n            caret-color:#fff !important;\n            -webkit-box-shadow:0 0 0 1000px #000 inset !important;\n            box-shadow:0 0 0 1000px #000 inset !important;\n            background:#000 !important;\n            background-color:#000 !important;\n            background-image:none !important;\n            transition:background-color 99999s ease-in-out 0s !important;\n        }\n        #ti-ai-outer .ti-conf-window input:-moz-autofill,\n        #ti-ai-outer .ti-conf-window textarea:-moz-autofill,\n        #ti-ai-outer .ti-cfg-table input:-moz-autofill,\n        #ti-ai-outer .ti-cfg-table textarea:-moz-autofill,\n        #ti-ai-outer .ti-import-map-table input:-moz-autofill,\n        #ti-ai-outer .ti-import-map-table textarea:-moz-autofill {\n            color:#fff !important;\n            background:#000 !important;\n            background-color:#000 !important;\n        }\n        #ti-ai-outer .ti-header-right-controls { display:flex; align-items:center; gap:8px; }\n        #ti-ai-outer #ti-user-area { order:1; }\n        #ti-ai-outer #ti-login-btn, #ti-ai-outer #ti-logout-btn { order:2; }\n        .ti-shop-footer { margin-top:12px; padding:12px 10px 4px; text-align:center; color:#cbd5e1; font-size:12px; line-height:1.45; }\n        .ti-shop-footer a { color:#93c5fd; text-decoration:none; }\n        #ti-ai-outer #lbl-conf-save.ti-save-dirty { background:#10b981 !important; color:#fff !important; box-shadow:0 0 0 2px rgba(16,185,129,.35), 0 0 16px rgba(16,185,129,.25) !important; font-weight:900 !important; }\n        #ti-ai-outer #lbl-conf-save.ti-save-clean { background:#374151 !important; color:#e5e7eb !important; box-shadow:none !important; }\n        #ti-ai-outer .ti-kbd-modal { width:96vw !important; max-width:760px !important; }\n        #ti-ai-outer .ti-kbd { display:flex; flex-direction:column; gap:8px; width:100%; }\n        #ti-ai-outer .ti-kbd-row { display:flex; gap:8px; width:100%; }\n        #ti-ai-outer .ti-kbd-row button { flex:1 1 0; min-width:0; min-height:48px; padding:10px 6px; border-radius:12px; border:1px solid #475569; background:#1f2937; color:#fff; font-size:18px; font-weight:900; cursor:pointer; }\n        #ti-ai-outer .ti-kbd-row button:active { transform:scale(.98); background:#334155; }\n        @media (max-width:640px) { #ti-ai-outer .ti-kbd-row { gap:5px; } #ti-ai-outer .ti-kbd-row button { min-height:44px; font-size:16px; padding:8px 4px; } }\n        #ti-ditta option.ti-ditta-attivo { color:#ffffff !important; }\n        #ti-ditta option.ti-ditta-sospesa { color:#ef4444 !important; }\n        #ti-ditta option.ti-ditta-configurazione { color:#facc15 !important; }\n        #ti-ditta option.ti-ditta-demo { color:#38bdf8 !important; font-weight:700 !important; }\n        .ti-order-confirm-host { display:none; margin:10px 0 12px; padding:12px; background:#0b1220; border:1px solid #223047; border-radius:12px; position:relative; z-index:30; box-shadow:0 8px 28px rgba(0,0,0,0.35); }\n        .ti-order-confirm-title { color:#fff; font-weight:800; font-size:15px; margin-bottom:8px; }\n        .ti-order-confirm-note { color:#94a3b8; font-size:12px; margin-bottom:10px; }\n        .ti-order-confirm-box { background:#000; border:1px solid #374151; border-radius:10px; padding:12px; color:#e5e7eb; white-space:pre-wrap; font-family:monospace; max-height:220px; overflow:auto; }\n        .ti-order-confirm-actions { display:flex; gap:8px; margin-top:10px; flex-wrap:wrap; }\n        .ti-order-confirm-actions .ti-btn { flex:1; min-width:160px; }\n        .ti-order-confirm-meta { display:flex; gap:12px; flex-wrap:wrap; margin-top:8px; color:#cbd5e1; font-size:12px; }\n        .ti-cfg-summary { display:flex; align-items:center; justify-content:space-between; gap:8px; }\n        .ti-cfg-summary-main { flex:1 1 auto; }\n        .ti-cfg-summary-help { flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center; width:22px; height:22px; margin-left:12px; border-radius:999px; border:1px solid #475569; background:#1f2937; color:#fff; font-weight:700; cursor:pointer; }\n        .ti-send-wrap { display:flex; flex-direction:column; gap:6px; align-items:stretch; width:96px; flex:0 0 96px; }\n        .ti-send { height: 38px; width: 100%; border-radius: 10px; background: rgba(56,189,248,0.2); color: #fff; font-weight: 900; cursor: pointer; border: 1px solid rgba(56,189,248,0.4); }\n        .ti-help { height: 24px; width: 100%; border-radius: 8px; background: #374151; color: #aaa; font-weight: bold; cursor: pointer; border: 1px solid #4b5563; font-size: 14px; }\n        .ti-send-wrap .ti-btn { width:100%; min-height:30px; padding:6px 8px; font-size:11px; line-height:1.1; }\n        .ti-actions-row { margin-top: 10px; display: flex; gap: 8px; justify-content: flex-start; align-items:center; flex-wrap: nowrap; overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch; padding-bottom:2px; }\n        .ti-admin-actions-row { margin-top: 6px; display:none; gap:8px; justify-content:flex-start; align-items:center; flex-wrap:nowrap; overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch; padding-bottom:2px; border-top:1px solid rgba(148,163,184,0.16); padding-top:6px; }\n        .ti-admin-actions-row .ti-btn { flex:0 0 auto; min-height:var(--ti-btn-h); }\n        \/* 30.9.392 - comandi configurazione distribuiti su due righe leggibili *\/\n        #ti-ai-outer .ti-admin-actions-row.ti-admin-actions-visible { display:grid !important; grid-template-rows:repeat(2, minmax(30px, auto)); grid-auto-flow:column; grid-auto-columns:minmax(145px, 1fr); gap:6px !important; overflow-x:auto; overflow-y:hidden; align-items:stretch; }\n        #ti-ai-outer .ti-admin-actions-row:not(.ti-admin-actions-visible) { display:none !important; }\n        #ti-ai-outer .ti-admin-actions-row[style*=\"display: none\"] { display:none !important; }\n        #ti-ai-outer .ti-admin-actions-row .ti-btn { width:100%; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n        #ti-maintenance-ov .ti-maintenance-modal { width:min(92vw,620px); max-width:620px; text-align:left; border:2px solid #f97316; box-shadow:0 18px 60px rgba(0,0,0,.75); }\n        #ti-maintenance-ov .ti-maintenance-title { color:#fed7aa; font-size:21px; font-weight:900; margin:0 0 10px 0; }\n        #ti-maintenance-ov .ti-maintenance-message { color:#fff; font-size:15px; line-height:1.55; }\n        #ti-maintenance-login-row { display:flex !important; justify-content:center !important; align-items:center !important; text-align:center; margin-top:16px; width:100%; }\n        #ti-maintenance-login-btn { background:#2563eb !important; color:#fff !important; font-weight:800 !important; border:1px solid #1d4ed8 !important; width:auto !important; min-width:min(100%,290px) !important; max-width:100% !important; margin:0 auto !important; padding:10px 18px !important; }\n        #ti-maintenance-msg #ti-maintenance-login-btn, #ti-maintenance-msg .ti-maintenance-inline-login { display:none !important; }\n        #ti-maintenance-ov.ti-maintenance-stable-active { display:flex !important; visibility:visible !important; opacity:1 !important; }\n        #ti-login-ov.ti-maintenance-login-override { z-index:2147483647 !important; }\n        #ti-login-maintenance-note { display:none; background:#78350f; color:#fff; border:1px solid #f59e0b; border-radius:8px; padding:9px; margin:0 0 10px 0; font-size:12px; line-height:1.35; text-align:left; }\n        #ti-conf-ai-chat-actions, #ti-conf-ai-image-actions { display:grid !important; grid-auto-flow:column; grid-template-rows:repeat(2, minmax(34px, auto)); grid-auto-columns:minmax(145px, 1fr); align-items:stretch !important; overflow-x:auto; }\n        #ti-conf-ai-chat-actions .ti-btn, #ti-conf-ai-image-actions .ti-btn { width:100%; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n        .ti-actions-row #ti-help-btn { margin-left:auto !important; flex:0 0 42px !important; width:42px !important; min-width:42px !important; max-width:42px !important; height:var(--ti-btn-h) !important; min-height:var(--ti-btn-h) !important; padding:8px 0 !important; display:flex !important; align-items:center !important; justify-content:center !important; background:#374151 !important; color:#fff !important; border:1px solid #4b5563 !important; border-radius:8px !important; font-size:16px !important; font-weight:900 !important; line-height:1 !important; }\n        .ti-actions-row #ti-help-btn:hover { background:#4b5563 !important; color:#fff !important; }\n        .ti-btn { padding: 8px 12px; border-radius: 8px; background: #222; color: #ccc; border: 1px solid #444; cursor: pointer; font-weight: 600; font-size: var(--ti-font-base) !important; display: flex; align-items: center; justify-content: center; gap: 5px; line-height:1.1 !important; }\n        .ti-btn-ok { background: #065f46; border-color: #059669; color: #fff; }\n        .ti-btn-x { background: #7f1d1d; border-color: #b91c1c; color: #fff; }\n\n        .ti-booking-nav-actions { margin-top:10px; }\n        .ti-booking-nav-actions .ti-btn { display:inline-flex !important; min-height:34px !important; height:34px !important; padding:6px 12px !important; font-size:13px !important; line-height:1 !important; }\n        @media (max-width: 767px) { .ti-booking-nav-actions .ti-btn { flex:1 1 120px !important; min-width:110px !important; font-size:12px !important; } }\n        html.ti-no-scroll, body.ti-no-scroll { overflow: hidden !important; height: 100% !important; touch-action: auto !important; }\n        #ti-ai-outer .ti-touch-scroll, #ti-ai-outer .ti-modal, #ti-ai-outer .ti-conf-body, #ti-ai-outer .ti-chat-box, #ti-ai-outer .ti-cfg-table-container, #ti-ai-outer .ti-table-wrap { touch-action: pan-y !important; -webkit-overflow-scrolling: touch !important; }\n        \n        #ti-ai-outer { position: relative; z-index: 1; isolation: isolate; }\n        .ti-ov { position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; background: rgba(0,0,0,0.85) !important; display: none; align-items: center; justify-content: center; z-index: 2147482000 !important; isolation: isolate !important; }\n        #ti-alert-ov { z-index: 2147483647 !important; }\n        #ti-ai-loader-ov { z-index: 2147483646 !important; }\n        .ti-modal { width: min(92vw, 820px); max-width: min(92vw, 820px); background: #1f2937; border-radius: 16px; padding: 25px; border: 1px solid #374151; text-align: center; position: relative; display: flex; flex-direction: column; pointer-events: auto; max-height: 90vh; overflow-y: auto; -webkit-overflow-scrolling: touch; z-index: 2; }\n        .ti-modal-x-red { position:absolute !important; top:10px !important; right:10px !important; width:32px !important; height:32px !important; min-width:32px !important; min-height:32px !important; border-radius:50% !important; border:2px solid #fecaca !important; background:#dc2626 !important; color:#fff !important; display:flex !important; align-items:center !important; justify-content:center !important; font-size:18px !important; line-height:1 !important; font-weight:900 !important; cursor:pointer !important; z-index:2147483647 !important; padding:0 !important; box-shadow:0 4px 14px rgba(0,0,0,.65) !important; }\n        .ti-modal-x-red:hover { background:#b91c1c !important; transform:scale(1.04); }\n        .ti-modal-bottom-close-row { display:flex !important; justify-content:center !important; align-items:center !important; margin-top:16px !important; width:100% !important; }\n        .ti-modal-bottom-close-row .ti-btn { min-width:160px !important; max-width:260px !important; background:#4b5563 !important; color:#fff !important; }\n        .ti-conf-window > .ti-modal-x-red { top:12px !important; right:12px !important; z-index:100001 !important; }\n        #ti-login-ov .ti-modal { width: min(38vw, 320px) !important; max-width: min(38vw, 320px) !important; padding: 22px !important; }\n\n        #ti-login-ov #l-db { width:100% !important; box-sizing:border-box !important; margin-top:8px !important; background:#111827 !important; color:#fff !important; border:1px solid #374151 !important; border-radius:8px !important; min-height:42px !important; }\n        @media (pointer: coarse), (max-width: 900px) {\n            #ti-login-ov { pointer-events:auto !important; touch-action:pan-y !important; }\n            #ti-login-ov .ti-modal { transform:none !important; pointer-events:auto !important; touch-action:pan-y !important; }\n            #ti-login-ov #l-db { display:block; font-size:16px !important; min-height:44px !important; }\n            #ti-login-ov #l-do { pointer-events:auto !important; touch-action:manipulation !important; }\n        }\n        #ti-login-ov #ti-login-form { width: 100%; margin: 0 auto; }\n        #ti-login-ov #ti-login-form .ti-in { margin: 8px 0; }\n        .ti-conf-window { z-index: 2 !important; }\n        #ti-plugin-modal-root { position: fixed !important; inset: 0 !important; z-index: 2147483000 !important; pointer-events: none !important; isolation:isolate !important; }\n        #ti-plugin-modal-root > .ti-ov, #ti-plugin-modal-root > #ti-ai-loader-ov { pointer-events: auto !important; }\n        .ti-plugin-scroll-lock { overscroll-behavior: contain; }\n        #ti-ai-outer, #ti-ai-outer * { box-sizing: border-box; }\n        #ti-ai-outer .ti-scrollable-plugin { overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }\n        #ti-profile-form button[type=\"submit\"] { position: relative; z-index: 2; pointer-events: auto; }\n        .ti-profile-actions .ti-btn { flex:1; min-height:42px; height:42px; padding:10px 12px; display:flex; align-items:center; justify-content:center; font-size:15px; font-weight:bold; line-height:1.2; text-transform:none; }\n\n        \n        .ti-conf-window { position: fixed !important; top: 3vh !important; bottom: 3vh !important; left: 2vw !important; right: 2vw !important; width: auto !important; max-width: 800px !important; margin: auto !important; background: #1f2937 !important; border-radius: 12px !important; border: 2px solid #4b5563 !important; overflow: hidden !important; pointer-events: auto !important; box-shadow: 0 10px 40px rgba(0,0,0,0.9) !important; }\n        .ti-conf-header { position: absolute !important; top: 0 !important; left: 0 !important; right: 0 !important; height: 60px !important; padding: 0 15px !important; background: #111827 !important; border-bottom: 1px solid #444 !important; display: flex !important; justify-content: space-between !important; align-items: center !important; z-index: 10 !important; }\n        .ti-conf-body { position: absolute !important; top: 60px !important; bottom: 70px !important; left: 0 !important; right: 0 !important; overflow-y: auto !important; overflow-x: hidden !important; padding: 15px !important; background: #1f2937 !important; -webkit-overflow-scrolling: touch !important; overscroll-behavior: contain !important; z-index: 1 !important; }\n        .ti-conf-footer { position: absolute !important; bottom: 0 !important; left: 0 !important; right: 0 !important; height: 70px !important; padding: 15px !important; background: #111827 !important; border-top: 1px solid #444 !important; display: flex !important; gap: 10px !important; z-index: 99999 !important; pointer-events: auto !important; }\n        \/* 30.9.250 - Pulsanti compatti nel piede del servizio Shop & Service e della configurazione *\/\n        #ti-ai-outer .ti-actions-row,\n        #ti-ai-outer .ti-admin-actions-row { gap:5px !important; padding-bottom:1px !important; }\n        #ti-ai-outer .ti-actions-row .ti-btn,\n        #ti-ai-outer .ti-actions-row .ti-help,\n        #ti-ai-outer .ti-admin-actions-row .ti-btn { min-height:30px !important; height:30px !important; padding:5px 8px !important; font-size:11px !important; line-height:1 !important; border-radius:7px !important; white-space:nowrap !important; flex:0 0 auto !important; }\n        #ti-ai-outer .ti-actions-row #ti-help-btn { flex:0 0 34px !important; width:34px !important; min-width:34px !important; max-width:34px !important; height:30px !important; min-height:30px !important; padding:0 !important; font-size:14px !important; border-radius:7px !important; }\n        #ti-conf-ov .ti-conf-body { bottom:88px !important; }\n        #ti-conf-ov .ti-conf-footer { height:88px !important; padding:8px 10px !important; gap:6px !important; align-items:stretch !important; }\n        #ti-conf-ov .ti-conf-footer-two-rows { display:grid !important; grid-template-columns:repeat(2, minmax(0, 1fr)) !important; grid-template-rows:repeat(2, 34px) !important; }\n        #ti-conf-ov .ti-conf-footer-two-rows .btn-close { grid-column:1 \/ -1 !important; }\n        #ti-conf-ov .ti-conf-footer .ti-btn { min-height:34px !important; height:34px !important; padding:6px 10px !important; font-size:12px !important; line-height:1 !important; border-radius:7px !important; }\n        @media (max-width: 767px) {\n            #ti-ai-outer .ti-actions-row .ti-btn,\n            #ti-ai-outer .ti-actions-row .ti-help,\n            #ti-ai-outer .ti-admin-actions-row .ti-btn { min-height:28px !important; height:28px !important; padding:4px 6px !important; font-size:10.5px !important; }\n            #ti-ai-outer .ti-actions-row #ti-help-btn { width:30px !important; min-width:30px !important; max-width:30px !important; height:28px !important; min-height:28px !important; }\n            #ti-conf-ov .ti-conf-footer { height:90px !important; padding:7px 8px !important; }\n            #ti-conf-ov .ti-conf-body { bottom:90px !important; }\n            #ti-conf-ov .ti-conf-footer-two-rows { grid-template-rows:repeat(2, 34px) !important; }\n            #ti-conf-ov .ti-conf-footer .ti-btn { min-height:32px !important; height:32px !important; padding:5px 8px !important; font-size:11px !important; }\n        }\n        \n        .ti-in { width: 100%; padding: 10px; margin: 10px 0; background: #111827; border: 1px solid #4b5563; color: #fff; border-radius: 8px; }\n        #ti-ai-outer input[type=\"text\"], #ti-ai-outer input[type=\"search\"], #ti-ai-outer input[type=\"email\"], #ti-ai-outer input[type=\"password\"], #ti-ai-outer input[type=\"number\"], #ti-ai-outer input[type=\"date\"], #ti-ai-outer input[type=\"time\"], #ti-ai-outer input[type=\"url\"], #ti-ai-outer input[type=\"tel\"], #ti-ai-outer textarea, #ti-ai-outer select, #ti-ai-outer option,\n        #ti-ai-outer .ti-in, #ti-ai-outer .ti-msg, #ti-ai-outer .ti-filter-input, #ti-ai-outer .ti-cfg-filter, #ti-ai-outer .ti-import-map-select, #ti-ai-outer .ti-import-map-input, #ti-ai-outer .ti-p-input, #ti-ai-outer .ti-q, #ti-ai-outer [contenteditable=\"true\"] {\n            color:#fff !important; -webkit-text-fill-color:#fff !important; caret-color:#fff !important; text-shadow:none !important;\n        }\n        #ti-ai-outer input:not([type=\"checkbox\"]):not([type=\"radio\"]), #ti-ai-outer textarea, #ti-ai-outer select, #ti-ai-outer .ti-in, #ti-ai-outer .ti-msg, #ti-ai-outer .ti-filter-input, #ti-ai-outer .ti-cfg-filter, #ti-ai-outer .ti-import-map-select, #ti-ai-outer .ti-import-map-input, #ti-ai-outer .ti-p-input, #ti-ai-outer .ti-q {\n            background-color:#111827 !important;\n        }\n        #ti-ai-outer input::placeholder, #ti-ai-outer textarea::placeholder, #ti-ai-outer select::placeholder, #ti-ai-outer .ti-in::placeholder, #ti-ai-outer .ti-msg::placeholder, #ti-ai-outer .ti-filter-input::placeholder, #ti-ai-outer .ti-cfg-filter::placeholder, #ti-ai-outer .ti-import-map-input::placeholder, #ti-ai-outer .ti-p-input::placeholder, #ti-ai-outer .ti-q::placeholder {\n            color:#d1d5db !important; opacity:1 !important; -webkit-text-fill-color:#d1d5db !important;\n        }\n        #ti-ai-outer input:focus, #ti-ai-outer textarea:focus, #ti-ai-outer select:focus, #ti-ai-outer .ti-in:focus, #ti-ai-outer .ti-msg:focus {\n            color:#fff !important; -webkit-text-fill-color:#fff !important;\n        }\n        #ti-filter-ditte { width: 35px; min-width: 240px; padding: 6px; border-radius: 8px; background: #000; border: 1px solid #444; color: #fff; text-align: center; margin-right: 5px; transition: width 0.3s; }\n        #ti-filter-ditte:focus { width: 140px; text-align: left; }\n        #ti-sort-btn { width: 35px; padding: 6px; border-radius: 8px; background: #222; border: 1px solid #444; color: #aaa; cursor: pointer; margin-right: 5px; text-align: center; font-size: 12px; font-weight: bold; }\n        .ti-table-wrap { max-height: 450px; overflow-y: auto; overflow-x: auto; border: 1px solid #444; border-radius: 6px; margin-top: 10px; background: #111; -webkit-overflow-scrolling: touch !important; }\n        .ti-prod-table { min-width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; text-align: left; }\n        .ti-prod-table th { background: #1f2937; padding: 8px 4px; border-bottom: 1px solid #555; font-size: 11px; position: sticky; top: 0; z-index: 20; box-shadow: 0 1px 0 #555; text-transform: uppercase; color: #aaa; cursor: pointer; }\n        .ti-prod-table td { padding: 4px; border-bottom: 1px solid #222; vertical-align: middle; line-height: 1.2; font-size: 12px; background: #000; }\n        .ti-filter-input { width: 100%; box-sizing: border-box; margin-top: 4px; padding: 2px; background: #000; border: 1px solid #444; color: #fff; font-size: 10px; border-radius: 3px; }\n        .ti-order-filterbar { display:flex; gap:8px; flex-wrap:nowrap; align-items:center; margin:8px 0 10px 0; overflow-x:auto; overflow-y:hidden; white-space:nowrap; -webkit-overflow-scrolling:touch; padding-bottom:2px; }\n        .ti-order-filtercell { display:flex; align-items:center; gap:4px; flex:0 0 auto; white-space:nowrap; min-height:32px; }\n        .ti-order-filterlabel { font-size:12px; color:#cbd5e1; min-width:auto; line-height:1; display:inline-flex; align-items:center; white-space:nowrap; margin:0; padding:0; }\n        .ti-order-filterbar .ti-filter-input, .ti-order-filterbar .ti-in { margin:0; font-size:12px; min-height:30px; line-height:1.1; vertical-align:middle; }\n        .ti-order-filterbar .ti-filter-input { width:126px; padding:5px 7px; }\n        .ti-order-filterbar .ti-in { width:150px; padding:5px 7px; }\n        .ti-order-filterbar::-webkit-scrollbar { height:7px; }\n        .ti-cfg-top-scroll, .ti-cfg-bottom-scroll, .ti-order-top-scroll, .ti-order-bottom-scroll { height:12px; overflow-x:auto; overflow-y:hidden; background:#000; border:1px solid #444; -webkit-overflow-scrolling:touch; }\n        .ti-cfg-top-scroll, .ti-order-top-scroll { position:sticky; top:0; z-index:4; border-bottom:none; border-radius:8px 8px 0 0; }\n        .ti-cfg-bottom-scroll, .ti-order-bottom-scroll { position:sticky; bottom:0; z-index:8; border-top:none; border-radius:0 0 8px 8px; margin-top:0; display:block; box-shadow:0 -1px 0 #111827, 0 -4px 10px rgba(0,0,0,0.35); }\n        .ti-order-top-scroll, .ti-order-bottom-scroll { position:relative; }\n        .ti-order-top-scroll { margin-top:10px; }\n        .ti-cfg-top-scroll-inner, .ti-sync-hscroll-inner { height:1px; }\n        .ti-cfg-top-scroll::-webkit-scrollbar, .ti-cfg-bottom-scroll::-webkit-scrollbar, .ti-order-top-scroll::-webkit-scrollbar, .ti-order-bottom-scroll::-webkit-scrollbar { height:8px; }\n        .ti-cfg-top-scroll, .ti-cfg-bottom-scroll, .ti-import-top-scroll, .ti-import-bottom-scroll, .ti-order-top-scroll, .ti-order-bottom-scroll { background:#d1d5db; border:1px solid #9ca3af; scrollbar-color:#9ca3af #e5e7eb; scrollbar-width:auto; }\n        .ti-cfg-top-scroll::-webkit-scrollbar, .ti-cfg-bottom-scroll::-webkit-scrollbar, .ti-import-top-scroll::-webkit-scrollbar, .ti-import-bottom-scroll::-webkit-scrollbar, .ti-order-top-scroll::-webkit-scrollbar, .ti-order-bottom-scroll::-webkit-scrollbar { height:12px; background:#e5e7eb; }\n        .ti-cfg-top-scroll::-webkit-scrollbar-track, .ti-cfg-bottom-scroll::-webkit-scrollbar-track, .ti-import-top-scroll::-webkit-scrollbar-track, .ti-import-bottom-scroll::-webkit-scrollbar-track, .ti-order-top-scroll::-webkit-scrollbar-track, .ti-order-bottom-scroll::-webkit-scrollbar-track { background:#e5e7eb; border-radius:8px; }\n        .ti-cfg-top-scroll::-webkit-scrollbar-thumb, .ti-cfg-bottom-scroll::-webkit-scrollbar-thumb, .ti-import-top-scroll::-webkit-scrollbar-thumb, .ti-import-bottom-scroll::-webkit-scrollbar-thumb, .ti-order-top-scroll::-webkit-scrollbar-thumb, .ti-order-bottom-scroll::-webkit-scrollbar-thumb { background:#9ca3af; border-radius:8px; border:2px solid #e5e7eb; }\n        .ti-cfg-top-scroll::-webkit-scrollbar-thumb:hover, .ti-cfg-bottom-scroll::-webkit-scrollbar-thumb:hover, .ti-import-top-scroll::-webkit-scrollbar-thumb:hover, .ti-import-bottom-scroll::-webkit-scrollbar-thumb:hover, .ti-order-top-scroll::-webkit-scrollbar-thumb:hover, .ti-order-bottom-scroll::-webkit-scrollbar-thumb:hover { background:#6b7280; }\n        .ti-ai-image-generator textarea, .ti-ai-image-generator input[type=\"text\"], .ti-imggen-wrap textarea, .ti-imggen-wrap input[type=\"text\"], #ai-gen-prompt { color:#fff !important; -webkit-text-fill-color:#fff !important; caret-color:#fff !important; background:#111827 !important; }\n        #ti-conf-ai-prompt, #ti-shared-base-path, #ti-shared-base-url, #help-email input, #help-email textarea, .ti-help-input, .ti-help-textarea { color:#fff !important; -webkit-text-fill-color:#fff !important; caret-color:#fff !important; background:#111827 !important; }\n        #ti-conf-ai-prompt::placeholder, #ti-shared-base-path::placeholder, #ti-shared-base-url::placeholder, #help-email input::placeholder, #help-email textarea::placeholder, .ti-help-input::placeholder, .ti-help-textarea::placeholder { color:#d1d5db !important; opacity:1 !important; }\n        .ti-conf-ai-desc-label { font-size:15px !important; line-height:1.45 !important; color:#cbd5e1 !important; font-weight:600 !important; }\n        .ti-ai-image-generator textarea::placeholder, .ti-ai-image-generator input[type=\"text\"]::placeholder, .ti-imggen-wrap textarea::placeholder, .ti-imggen-wrap input[type=\"text\"]::placeholder, #ai-gen-prompt::placeholder { color:#d1d5db !important; opacity:1 !important; }\n        .ti-ai-image-generator textarea:focus, .ti-ai-image-generator input[type=\"text\"]:focus, .ti-imggen-wrap textarea:focus, .ti-imggen-wrap input[type=\"text\"]:focus, #ai-gen-prompt:focus { color:#fff !important; -webkit-text-fill-color:#fff !important; outline:none !important; border-color:#9ca3af !important; box-shadow:0 0 0 1px #9ca3af inset !important; }\n        .ti-col-img { width: 50px; text-align: center; white-space: nowrap; } \n        .ti-col-disp { width: 80px; text-align: right; } \n        .ti-col-eur { width: 80px; white-space: nowrap; text-align: right; } \n        .ti-col-qta { width: 70px; text-align: right; } \n        .ti-col-tot { width: 80px; text-align: right; font-weight: bold; color: #38bdf8; }\n        .ti-row-img { width: 32px; height: 32px; object-fit: cover; border-radius: 4px; cursor: pointer; display: inline-block; vertical-align: middle; }\n        .ti-cfg-head-label { display:flex; align-items:center; justify-content:space-between; gap:6px; font-size:11px; }\n        .ti-cfg-sort-ind { color:#93c5fd; font-size:11px; margin-left:4px; }\n        .ti-cfg-filter { width:100%; box-sizing:border-box; margin-top:5px; padding:3px 5px; background:#000; border:1px solid #444; color:#fff; font-size:10px; border-radius:4px; }\n        .ti-cfg-help-btn { display:inline-flex; align-items:center; justify-content:center; width:18px; height:18px; border-radius:999px; background:#1d4ed8; color:#fff; font-size:11px; cursor:pointer; border:none; padding:0; line-height:1; }\n        .ti-cfg-help-btn:hover { background:#2563eb; }\n        .ti-import-map-table { width:max-content; min-width:1400px; border-collapse:collapse; font-size:12px; }\n        .ti-import-map-table th, .ti-import-map-table td { border:1px solid #475569; padding:6px; vertical-align:top; }\n        .ti-import-map-table th { background:#0f172a; position:sticky; top:0; z-index:3; white-space:nowrap; }\n        .ti-import-map-select, .ti-import-map-input { width:100%; min-width:160px; box-sizing:border-box; background:#111827; color:#fff; border:1px solid #475569; border-radius:6px; padding:5px 6px; font-size:12px; }\n        .ti-import-table-container { max-width:100%; overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch; }\n        .ti-import-top-scroll, .ti-import-bottom-scroll { margin-top:6px; display:block; }\n        .ti-import-bottom-scroll, .ti-order-bottom-scroll, .ti-cfg-bottom-scroll { background:#000; }\n        .ti-import-top-scroll .ti-cfg-top-scroll-inner, .ti-import-bottom-scroll .ti-cfg-top-scroll-inner { min-width:1400px; }\n        .ti-order-modal-grid { display:grid; grid-template-columns:minmax(180px,1fr) minmax(220px,1fr); gap:14px; align-items:start; text-align:left; }\n        .ti-order-modal-img { width:100%; max-height:340px; object-fit:contain; border-radius:10px; background:#000; border:1px solid #374151; }\n        .ti-order-modal-caption { font-size:14px; color:#d1d5db; line-height:1.45; margin-bottom:10px; }\n        .ti-order-modal-line { margin:8px 0; font-size:14px; color:#fff; }\n        .ti-order-modal-actions { display:flex; gap:10px; margin-top:14px; }\n        .ti-order-modal-files { margin-top:12px; padding-top:10px; border-top:1px solid #334155; }\n        .ti-order-modal-files-title { font-size:12px; color:#93c5fd; font-weight:800; margin-bottom:8px; }\n        .ti-order-modal-files-grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(92px, 1fr)); gap:8px; }\n        .ti-order-file-preview { border:1px solid #334155; background:#020617; border-radius:10px; padding:7px; color:#e5e7eb; cursor:pointer; min-height:96px; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:5px; text-align:center; }\n        .ti-order-file-preview:hover { border-color:#38bdf8; background:#0f172a; }\n        .ti-order-file-preview img { width:100%; height:62px; object-fit:cover; border-radius:7px; background:#000; border:1px solid #1f2937; }\n        .ti-order-file-preview iframe { width:100%; height:62px; border:1px solid #1f2937; border-radius:7px; background:#000; pointer-events:none; }\n        .ti-order-file-preview-icon { width:100%; height:62px; display:flex; align-items:center; justify-content:center; border-radius:7px; background:#111827; font-size:26px; border:1px solid #1f2937; }\n        .ti-order-file-preview-name { width:100%; font-size:10px; color:#cbd5e1; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n        .ti-order-file-preview-open { font-size:10px; color:#38bdf8; font-weight:700; }\n        @media (max-width: 768px) { \n            #ti-login-ov .ti-modal { width: min(92vw, 340px) !important; max-width: min(92vw, 340px) !important; padding: 20px !important; }.ti-order-modal-grid { grid-template-columns:1fr; } }\n        @media (max-width: 760px) {\n            html:not(.ti-no-scroll), body:not(.ti-no-scroll) { overflow-y:auto !important; height:auto !important; min-height:100% !important; touch-action:pan-y !important; }\n            #ti-ai-outer { width:100% !important; max-width:100% !important; min-height:auto !important; height:auto !important; overflow-y:visible !important; touch-action:pan-y !important; overscroll-behavior-y:auto !important; }\n            #ti-ai-outer .ti-header, #ti-ai-outer .ti-chat-box, #ti-ai-outer .ti-input-wrap, #ti-ai-outer .ti-actions-row, #ti-ai-outer .ti-admin-actions-row { touch-action:pan-y !important; }\n            .ti-ov { align-items:flex-start !important; justify-content:center !important; overflow-y:auto !important; overflow-x:hidden !important; padding: max(10px, env(safe-area-inset-top)) 8px max(10px, env(safe-area-inset-bottom)) 8px !important; touch-action:pan-y !important; -webkit-overflow-scrolling:touch !important; }\n            .ti-modal { max-height: calc((var(--ti-vh, 1vh) * 92)) !important; overflow-y:auto !important; touch-action:pan-y !important; -webkit-overflow-scrolling:touch !important; }\n            #ti-login-ov .ti-modal { width:min(94vw, 380px) !important; max-width:min(94vw, 380px) !important; max-height:calc((var(--ti-vh, 1vh) * 88)) !important; }\n            #ti-login-ov .ti-in, #ti-login-ov .ti-btn, #ti-ai-outer input, #ti-ai-outer textarea, #ti-ai-outer select { font-size:16px !important; }\n            #ti-login-ov .ti-in, #ti-login-ov .ti-btn { min-height:44px !important; }\n            .ti-conf-window { top: max(8px, env(safe-area-inset-top)) !important; bottom: max(8px, env(safe-area-inset-bottom)) !important; left: 1vw !important; right: 1vw !important; max-width:98vw !important; }\n            .ti-conf-body { overflow-y:auto !important; overflow-x:auto !important; touch-action:pan-y !important; -webkit-overflow-scrolling:touch !important; }\n\n            #ti-ai-outer .ti-actions-row {\n                display:grid !important;\n                grid-template-columns:repeat(5, minmax(0, 1fr)) !important;\n                gap:4px !important;\n                overflow:visible !important;\n                padding-bottom:0 !important;\n                align-items:stretch !important;\n            }\n            #ti-ai-outer .ti-actions-row .ti-btn,\n            #ti-ai-outer .ti-actions-row .ti-help {\n                width:100% !important;\n                min-width:0 !important;\n                max-width:none !important;\n                height:30px !important;\n                min-height:30px !important;\n                padding:3px 3px !important;\n                border-radius:7px !important;\n                font-size:10px !important;\n                line-height:1.05 !important;\n                white-space:nowrap !important;\n                overflow:hidden !important;\n                text-overflow:ellipsis !important;\n                display:flex !important;\n                align-items:center !important;\n                justify-content:center !important;\n                gap:2px !important;\n            }\n            #ti-ai-outer .ti-actions-row #ti-help-btn {\n                margin-left:0 !important;\n                flex:1 1 auto !important;\n                width:100% !important;\n                min-width:0 !important;\n                max-width:none !important;\n                font-size:13px !important;\n                padding:3px 0 !important;\n            }\n            #ti-ai-outer .ti-admin-actions-row {\n                display:grid !important;\n                grid-template-columns:repeat(2, minmax(0, 1fr)) !important;\n                gap:5px !important;\n                overflow:visible !important;\n                padding-bottom:0 !important;\n            }\n            #ti-ai-outer .ti-admin-actions-row[style*=\"display: none\"] {\n                display:none !important;\n            }\n            #ti-ai-outer .ti-admin-actions-row .ti-btn {\n                width:100% !important;\n                min-width:0 !important;\n                min-height:31px !important;\n                height:31px !important;\n                padding:4px 5px !important;\n                font-size:10.5px !important;\n                border-radius:7px !important;\n                white-space:nowrap !important;\n                overflow:hidden !important;\n                text-overflow:ellipsis !important;\n            }\n        }\n        .ti-p-input { width: 100%; box-sizing: border-box; padding: 4px; background: #222; border: 1px solid #444; color: #fff; border-radius: 3px; font-size: 12px; font-family: inherit; height: 24px; line-height: 16px; }\n        .ti-p-input:focus { border-color: #38bdf8; outline: none; }\n        .ti-num-step { text-align: right; }\n        .ti-qty-stepper { display:inline-flex; align-items:center; gap:4px; width:100%; }\n        .ti-qty-stepper .ti-q, .ti-qty-stepper .ti-num-step, .ti-qty-stepper #ti-order-item-qty { flex:1 1 auto; min-width:0; margin:0; }\n        .ti-qty-btn { width:26px; min-width:26px; height:26px; border:1px solid #475569; background:#0f172a; color:#fff; border-radius:6px; cursor:pointer; display:inline-flex; align-items:center; justify-content:center; font-weight:700; line-height:1; padding:0; }\n        .ti-qty-btn:hover { background:#1e293b; }\n        .ti-order-card-line .ti-qty-stepper { max-width:170px; }\n        .ti-order-modal-line .ti-qty-stepper { max-width:220px; display:inline-flex; vertical-align:middle; margin-left:8px; }\n        .ti-cfg-summary { font-weight: bold; color: #38bdf8; cursor: pointer; padding: 10px; background: #111827; border-radius: 5px; display: block; border: 1px solid #333; }\n        .ti-cfg-table-container { max-height: 60vh; overflow: auto; position: relative; background: #000; border: 1px solid #444; border-top: none; border-radius: 0 0 8px 8px; -webkit-overflow-scrolling: touch; }\n        .ti-cfg-table { width: max-content; min-width: 100%; border-collapse: separate; border-spacing: 0; }\n        .ti-cfg-table th { background: #1f2937; padding: 8px 10px; font-size: 11px; color: #ccc; border-bottom: 1px solid #444; border-right: 1px solid #444; position: sticky; top: 0; z-index: 20; text-transform: uppercase; white-space: nowrap; }\n        .ti-cfg-table th.ti-sticky-col { left: 0; z-index: 30; background: #2563eb; color: #fff; box-shadow: 1px 0 0 #444; }\n        .ti-cfg-table th.ti-cfg-rownum, .ti-cfg-table td.ti-cfg-rownum { position: sticky; left: 0; width: 58px; min-width: 58px; max-width: 58px; text-align: center; font-weight: 700; box-shadow: 1px 0 0 #444; }\n        .ti-cfg-table th.ti-cfg-rownum { top: 0; z-index: 35; background: #0f172a; color: #bfdbfe; }\n        .ti-cfg-table td.ti-cfg-rownum { z-index: 12; background: #111827; color: #93c5fd; }\n        .ti-cfg-table td { padding: 5px; border-bottom: 1px solid #333; border-right: 1px solid #333; background: #000; vertical-align: top; }\n        .ti-cfg-table td.ti-sticky-col { position: sticky; left: 0; z-index: 10; background: #111827; box-shadow: 1px 0 0 #333; }\n        .ti-cfg-table .ti-in { background: transparent; border: none; font-size: 12px; margin: 0; padding: 4px; color: #fff; width: 100%; box-sizing: border-box; }\n        .ti-cfg-table .ti-in:focus { background: rgba(56, 189, 248, 0.1); outline: 1px solid #38bdf8; }\n        .ti-profile-btn-eq { min-height:46px !important; height:46px !important; display:flex !important; align-items:center !important; justify-content:center !important; box-sizing:border-box !important; padding:0 12px !important; line-height:1 !important; font-size:15px !important; font-weight:700 !important; white-space:nowrap !important; border-radius:10px !important; }\n        .ti-profile-actions { display:grid !important; grid-template-columns:1fr 1fr 1fr !important; gap:8px !important; align-items:stretch !important; justify-content:stretch !important; }\n        .ti-profile-actions .ti-btn { min-height:46px !important; height:46px !important; display:flex !important; align-items:center !important; justify-content:center !important; box-sizing:border-box !important; margin:0 !important; }\n        .ti-eye-btn { font-size: 16px; cursor: pointer; margin-left: 5px; filter: grayscale(1); transition: 0.2s; }\n        .ti-eye-btn:hover { filter: grayscale(0); transform: scale(1.2); }\n\n        .ti-report-toolbar { display:flex; align-items:center; gap:8px; flex-wrap:nowrap; overflow-x:auto; padding-bottom:8px; margin-bottom:10px; }\n        .ti-report-toolbar .ti-btn { white-space:nowrap; flex:0 0 auto; }\n        .ti-report-hidden-cols { display:flex; gap:6px; flex-wrap:wrap; margin:0 0 10px 0; }\n        .ti-report-col-chip { display:inline-flex; align-items:center; gap:6px; background:#111827; border:1px solid #374151; color:#e5e7eb; border-radius:999px; padding:4px 9px; font-size:11px; }\n        .ti-report-config-filter { display:inline-flex; align-items:center; gap:6px; background:#0f172a; border:1px solid #334155; color:#e5e7eb; border-radius:999px; padding:5px 10px; font-size:11px; white-space:nowrap; flex:0 0 auto; }\n        .ti-report-config-filter input { margin:0; accent-color:#38bdf8; }\n        .ti-report-table-wrap { max-height:58vh; overflow:auto; border:1px solid #444; border-radius:8px; background:#000; }\n        .ti-report-table { width:max-content; min-width:100%; border-collapse:separate; border-spacing:0; }\n        .ti-report-table th { position:sticky; top:0; background:#111827; color:#fff; z-index:3; min-width:120px; padding:8px 6px; border-bottom:1px solid #444; border-right:1px solid #333; cursor:pointer; font-size:11px; text-transform:uppercase; }\n        .ti-report-table td { padding:6px; border-bottom:1px solid #222; border-right:1px solid #222; background:#000; color:#e5e7eb; font-size:12px; vertical-align:top; }\n        .ti-report-table tr.ti-report-row-excluded td { opacity:0.45; background:#140c0c; text-decoration:line-through; }\n        .ti-report-table tfoot td { background:#0f172a; color:#38bdf8; font-weight:bold; position:sticky; bottom:0; z-index:2; border-top:1px solid #444; }\n        .ti-report-filter { width:100%; margin-top:6px; padding:4px 5px; border-radius:4px; border:1px solid #444; background:#000; color:#fff; font-size:10px; }\n        .ti-report-num { text-align:right; white-space:nowrap; }\n        .ti-report-flag-btn { display:inline-flex; align-items:center; justify-content:center; width:18px; height:18px; border-radius:50%; border:1px solid #7f1d1d; background:#b91c1c; color:#fff; font-size:11px; font-weight:bold; cursor:pointer; margin-right:6px; }\n        .ti-report-flag-btn.is-off { background:#111827; color:#f87171; border-color:#ef4444; }\n        .ti-report-row-flag-cell { position:sticky; left:0; z-index:2; background:#000 !important; min-width:44px; text-align:center; }\n        .ti-report-row-flag-cell.head { z-index:4; background:#1f2937 !important; }\n        .ti-chart-wrap { background:#000; border:1px solid #444; border-radius:8px; padding:12px; overflow:auto; }\n        \n        #ti-ai-loader-ov { position:fixed !important; inset:0 !important; background:rgba(0,0,0,0.7); display:none; align-items:center; justify-content:center; z-index:2147483646 !important; border-radius:14px; isolation:isolate !important; pointer-events:auto !important; }\n        .ti-loader-box { background:#1f2937; border:2px solid #38bdf8; padding:20px 30px; border-radius:12px; text-align:center; box-shadow:0 10px 25px rgba(0,0,0,0.5); }\n        .ti-loader-spinner { font-size:30px; margin-bottom:10px; animation: ti-spin 1.5s linear infinite; display:inline-block; }\n        @keyframes ti-spin { 100% { transform: rotate(360deg); } }\n\n        \/* GRID PROFILO UTENTE E FILE MANAGER *\/\n        .ti-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; text-align: left; }\n        .ti-grid-2 label { font-size:10px; color:#aaa; display:block; margin-bottom:2px;}\n        .ti-grid-2 .ti-in { margin:0; padding:6px; font-size:12px; }\n        .ti-cb-wrap { display: flex; align-items: center; gap: 5px; font-size:11px; color:#ccc;}\n        .ti-file-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); gap: 10px; margin-top: 15px; max-height: 300px; overflow-y: auto; }\n        .ti-file-item { position: relative; background: #222; border: 1px solid #444; border-radius: 6px; padding: 5px; text-align: center; }\n        .ti-file-item img { width: 100%; height: 60px; object-fit: cover; border-radius: 4px; }\n        .ti-file-item .file-icon { font-size: 30px; line-height: 60px; color: #888; }\n        .ti-file-name { font-size: 9px; color: #ccc; margin-top: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n        .ti-file-del { position: absolute; top: -5px; right: -5px; background: #ef4444; color: white; border: none; border-radius: 50%; width: 20px; height: 20px; font-size: 10px; cursor: pointer; }\n        .ti-btn-catalog { background:#1d4ed8; color:#fff; border-color:#1e40af; }\n        .ti-btn-catalog.ti-btn-services { background:#047857; border-color:#065f46; }\n        #ti-ai-outer #ti-activities-btn.ti-btn-activities { background:#7c3aed; border-color:#5b21b6; color:#fff; font-weight:800; }\n        #ti-ditta { height: 34px; min-height: 34px; width: 400px; max-width: 400px; min-width: 400px; box-sizing: border-box; }\n        #ti-ditta option { background:#000 !important; }\n        #ti-ditta option[data-status=\"attivo\"] { color:#ffffff !important; }\n        #ti-ditta option[data-status=\"sospesa\"] { color:#ef4444 !important; font-weight:700 !important; }\n        #ti-ditta option[data-status=\"configurazione\"] { color:#facc15 !important; font-weight:700 !important; }\n        #ti-ditta option[data-status=\"demo\"] { color:#38bdf8 !important; font-weight:700 !important; }\n        #ti-ditta option[data-status=\"nuova\"] { color:#10b981 !important; font-weight:700 !important; }\n        #ti-ditta option[data-status=\"sviluppo\"] { color:#ef4444 !important; font-weight:700 !important; }\n        .ti-ditta-picker { position:relative; flex:1 1 100%; width:100%; min-width:0; max-width:none; }\n        .ti-header-left-controls .ti-ditta-picker { flex:1 1 100%; width:100%; max-width:none; }\n        .ti-ditta-trigger { display:block; width:100%; min-height:36px; padding:6px 12px; border-radius:8px; background:#000; color:#fff; border:1px solid #444; text-align:left; cursor:pointer; font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n        .ti-ditta-popup { position:absolute; top:calc(100% + 4px); left:0; right:0; width:100%; z-index:999999; max-height:320px; overflow:auto; background:#030712; border:1px solid #334155; border-radius:10px; box-shadow:0 12px 32px rgba(0,0,0,.55); padding:6px; }\n        .ti-ditta-row { display:flex; align-items:center; justify-content:space-between; gap:10px; width:100%; padding:8px 10px; border-radius:8px; cursor:pointer; background:#020617; border:1px solid transparent; text-align:left; font-size:13px; line-height:1.3; }\n        .ti-ditta-row:hover, .ti-ditta-row.active { background:#111827; border-color:#334155; }\n        .ti-ditta-row.attivo { border-left:3px solid #ffffff; }\n        .ti-ditta-row.sospesa { border-left:3px solid #ef4444; background:rgba(127,29,29,.18); }\n        .ti-ditta-row.sviluppo { border-left:3px solid #ef4444; background:rgba(127,29,29,.18); }\n        .ti-ditta-row.configurazione { border-left:3px solid #facc15; background:rgba(161,98,7,.16); }\n        .ti-ditta-row.demo { border-left:3px solid #38bdf8; background:rgba(14,116,144,.20); }\n        .ti-ditta-row.nuova { border-left:3px solid #10b981; background:rgba(6,95,70,.16); }\n        .ti-ditta-row.empty, .ti-ditta-row.none { border-left:3px solid #94a3b8; }\n        .ti-ditta-row-main { display:block; flex:1 1 auto; min-width:0; color:#fff !important; font-weight:500 !important; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; font-size:var(--ti-font-base) !important; line-height:1.25 !important; }\n        .ti-ditta-row-badge { flex:0 0 auto; padding:2px 8px; border-radius:999px; font-size:11px; font-weight:800; letter-spacing:.01em; border:1px solid currentColor; }\n        .ti-ditta-row-badge.sospesa { color:#ef4444 !important; background:rgba(127,29,29,.20); }\n        .ti-ditta-row-badge.sviluppo { color:#ef4444 !important; background:rgba(127,29,29,.20); }\n        .ti-ditta-row-badge.configurazione { color:#facc15 !important; background:rgba(161,98,7,.18); }\n        .ti-ditta-row-badge.demo { color:#38bdf8 !important; background:rgba(14,116,144,.22); }\n        .ti-ditta-row-badge.nuova { color:#10b981 !important; background:rgba(6,95,70,.18); }\n        .ti-ditta-row-badge.attivo { color:#ffffff !important; background:rgba(255,255,255,.06); }\n        #ti-login-btn, #ti-logout-btn { min-height: 34px; height: 34px; padding: 0 12px; line-height: 34px; box-sizing: border-box; display:inline-flex; align-items:center; justify-content:center; }\n        \/* 30.9.389 - Allineamento verticale Accedi pre-login con campo ditta scelta *\/\n        @media (min-width: 769px) {\n            #ti-ai-outer .ti-header-bottom {\n                align-items: flex-end !important;\n            }\n            #ti-ai-outer .ti-header-right-controls {\n                align-self: flex-end !important;\n                display: flex !important;\n                align-items: center !important;\n                justify-content: flex-end !important;\n                min-height: 36px !important;\n                margin-top: auto !important;\n                margin-bottom: 0 !important;\n                padding-bottom: 0 !important;\n            }\n            #ti-ai-outer #ti-login-btn,\n            #ti-ai-outer #ti-logout-btn {\n                height: 36px !important;\n                min-height: 36px !important;\n                line-height: 1 !important;\n                display: inline-flex !important;\n                align-items: center !important;\n                justify-content: center !important;\n                margin-top: 0 !important;\n                margin-bottom: 0 !important;\n                align-self: center !important;\n            }\n            #ti-ai-outer .ti-ditta-trigger {\n                min-height: 36px !important;\n                height: 36px !important;\n                display: flex !important;\n                align-items: center !important;\n            }\n        }\n        #ti-user-area { align-self: center; height:34px; min-height:34px; display:none; align-items:center; }\n\n        .ti-order-wrap { margin-top:10px; }\n        .ti-order-toolbar { display:flex; gap:6px; flex-wrap:wrap; align-items:center; justify-content:space-between; padding:8px; background:#0b1220; border:1px solid #223047; border-bottom:none; border-radius:8px 8px 0 0; }\n        .ti-order-toolbar-left, .ti-order-toolbar-right { display:flex; gap:6px; flex-wrap:wrap; align-items:center; }\n        .ti-order-view-btn { padding:6px 10px; border-radius:8px; background:#1f2937; color:#cbd5e1; border:1px solid #334155; cursor:pointer; font-size:12px; font-weight:700; }\n        .ti-order-view-btn.active { background:#2563eb; color:#fff; border-color:#1d4ed8; }\n        .ti-order-hint { color:#94a3b8; font-size:11px; }\n        .ti-order-table-shell { display:block; }\n        .ti-order-grid-view { display:none; padding:12px; border:1px solid #444; border-top:none; border-radius:0 0 8px 8px; background:#111; }\n        .ti-order-wrap.ti-view-small .ti-order-table-shell,\n        .ti-order-wrap.ti-view-medium .ti-order-table-shell,\n        .ti-order-wrap.ti-view-large .ti-order-table-shell { display:none; }\n        .ti-order-wrap.ti-view-small .ti-order-grid-view,\n        .ti-order-wrap.ti-view-medium .ti-order-grid-view,\n        .ti-order-wrap.ti-view-large .ti-order-grid-view { display:grid; }\n        .ti-order-wrap.ti-view-small .ti-order-grid-view { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }\n        .ti-order-wrap.ti-view-medium .ti-order-grid-view { grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }\n        .ti-order-wrap.ti-view-large .ti-order-grid-view { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }\n        .ti-qty-stepper { display:flex; align-items:center; gap:4px; min-width:118px; }\n        .ti-qty-stepper .ti-q, .ti-qty-stepper .ti-num-step { min-width:64px; flex:1 1 70px; display:block; }\n        .ti-col-qta { min-width:132px; }\n        .ti-order-card { background:#06080f; border:1px solid #243042; border-radius:12px; overflow:hidden; display:flex; flex-direction:column; min-height:100%; }\n        .ti-order-card-media { background:#000; display:flex; align-items:center; justify-content:center; overflow:hidden; }\n        .ti-order-card-media img { width:100%; object-fit:cover; display:block; }\n        .ti-order-card-media .ti-order-file-icon { font-size:36px; color:#64748b; padding:30px 0; }\n        .ti-order-wrap.ti-view-small .ti-order-card-media { height:120px; }\n        .ti-order-wrap.ti-view-medium .ti-order-card-media { height:180px; }\n        .ti-order-wrap.ti-view-large .ti-order-card-media { height:260px; }\n        .ti-order-card-body { padding:10px; display:flex; flex-direction:column; gap:8px; }\n        .ti-order-card-title { font-size:13px; font-weight:800; color:#fff; display:flex; align-items:center; gap:6px; }\n        .ti-order-card-title .ti-order-badge { font-size:11px; font-weight:900; padding:2px 6px; border-radius:99px; }\n        .ti-order-card-title .ti-order-badge.product { background:#1d4ed8; color:#fff; }\n        .ti-order-card-title .ti-order-badge.service { background:#047857; color:#fff; }\n        .ti-order-card-desc { font-size:11px; color:#cbd5e1; line-height:1.35; min-height:42px; }\n        .ti-order-card-meta { display:grid; grid-template-columns:1fr 1fr; gap:8px; align-items:end; }\n        .ti-order-card-label { font-size:10px; color:#94a3b8; text-transform:uppercase; letter-spacing:.04em; margin-bottom:3px; }\n        .ti-order-card-total { font-weight:800; color:#38bdf8; font-size:14px; }\n        .ti-order-card .ti-q { width:100%; padding:6px; border-radius:8px; border:1px solid #334155; background:#0f172a; color:#fff; text-align:right; }\n        .ti-order-footer { display:none !important; }\n        .ti-order-footer-grid { display:none !important; }\n        .ti-order-footer-total { display:none !important; }\n        .ti-order-footer-total b { display:none !important; }\n        .ti-order-footer-btn { display:none !important; visibility:hidden !important; width:0 !important; min-width:0 !important; padding:0 !important; border:0 !important; margin:0 !important; overflow:hidden !important; }\n\n        @media (max-width: 768px) { \n            .ti-header { flex-direction: column; align-items: stretch; padding: 12px; } \n            .ti-header-top { flex-wrap: wrap; align-items:flex-start; } \n            .ti-header-main-left { width:100%; } \n            .ti-hr-right { width: 100%; align-items: flex-end; justify-content:flex-end; margin-top: 0; } \n            .ti-header-bottom { flex-direction: column; align-items: stretch; } \n            .ti-header-left-controls { width:100%; } \n            .ti-header-right-controls { width:100%; justify-content:flex-end; } \n            .ti-hr-bottom { flex-wrap: wrap; width: 100%; justify-content: flex-start; } \n            #ti-ditta { max-width: 100%; min-width: 0; width: 100%; flex: 1; } \n            .ti-actions-row button { padding: 8px 10px; font-size: 11px; } \n            .ti-grid-2 { grid-template-columns: 1fr; }\n            .ti-modal { width: min(94vw, 520px); max-width:min(94vw, 520px); max-height: 88vh; overflow-y: auto; }\n\n            .ti-order-toolbar-right { width:100%; }\n            .ti-order-hint { display:block; }\n            #ti-ai-outer .ti-actions-row {\n                display:grid !important;\n                grid-template-columns:repeat(5, minmax(0, 1fr)) !important;\n                gap:4px !important;\n                overflow:visible !important;\n                padding-bottom:0 !important;\n            }\n            #ti-ai-outer .ti-actions-row button,\n            #ti-ai-outer .ti-actions-row .ti-btn,\n            #ti-ai-outer .ti-actions-row .ti-help {\n                padding:3px 3px !important;\n                font-size:10px !important;\n                min-height:30px !important;\n                height:30px !important;\n                min-width:0 !important;\n                width:100% !important;\n                max-width:none !important;\n                border-radius:7px !important;\n                white-space:nowrap !important;\n                overflow:hidden !important;\n                text-overflow:ellipsis !important;\n            }\n            #ti-ai-outer .ti-actions-row #ti-help-btn {\n                margin-left:0 !important;\n                width:100% !important;\n                min-width:0 !important;\n                max-width:none !important;\n                font-size:13px !important;\n            }\n            #ti-ai-outer .ti-admin-actions-row {\n                grid-template-columns:repeat(2, minmax(0, 1fr)) !important;\n                gap:5px !important;\n                overflow:visible !important;\n            }\n            #ti-ai-outer .ti-admin-actions-row .ti-btn {\n                width:100% !important;\n                min-width:0 !important;\n                height:31px !important;\n                min-height:31px !important;\n                padding:4px 5px !important;\n                font-size:10.5px !important;\n                border-radius:7px !important;\n                white-space:nowrap !important;\n                overflow:hidden !important;\n                text-overflow:ellipsis !important;\n            }\n        }\n\n        @media (max-width: 640px) {\n            .ti-header { align-items:flex-start; }\n            .ti-header-top { align-items:flex-start; }\n            .ti-hl { align-items:flex-start; }\n            .ti-left-stack { gap:4px; }\n            .ti-user-area { max-width: 170px; padding: 2px 6px; }\n            .ti-user-name { font-size: 10px; }\n        }\n\n    \n        \/* Mobile hardening 30.9.153: login e scroll verticale a un dito *\/\n        @media (max-width: 760px) {\n            html, body, html.ti-no-scroll, body.ti-no-scroll {\n                overflow-y:auto !important;\n                overflow-x:hidden !important;\n                height:auto !important;\n                min-height:100% !important;\n                position:static !important;\n                touch-action:auto !important;\n                -webkit-overflow-scrolling:touch !important;\n            }\n            #ti-ai-outer,\n            #ti-ai-outer * {\n                -webkit-tap-highlight-color: rgba(59,130,246,.25);\n            }\n            #ti-ai-outer,\n            #ti-ai-outer .ti-chat-box,\n            #ti-ai-outer .ti-input-wrap,\n            #ti-ai-outer .ti-actions-row,\n            #ti-ai-outer .ti-admin-actions-row,\n            #ti-ai-outer .ti-modal,\n            #ti-ai-outer .ti-ov,\n            #ti-ai-outer .ti-conf-body,\n            #ti-ai-outer .ti-cfg-table-container,\n            #ti-ai-outer .ti-table-wrap {\n                touch-action:auto !important;\n                overscroll-behavior-y:auto !important;\n                -webkit-overflow-scrolling:touch !important;\n            }\n            .ti-ov {\n                overflow-y:auto !important;\n                overflow-x:hidden !important;\n                align-items:flex-start !important;\n                pointer-events:auto !important;\n            }\n            .ti-modal {\n                max-height:90dvh !important;\n                overflow-y:auto !important;\n                pointer-events:auto !important;\n            }\n            #ti-login-ov {\n                z-index:2147483600 !important;\n                padding: max(8px, env(safe-area-inset-top)) 6px max(8px, env(safe-area-inset-bottom)) 6px !important;\n            }\n            #ti-login-ov .ti-modal {\n                width:96vw !important;\n                max-width:390px !important;\n                min-width:0 !important;\n                margin:8px auto 20px auto !important;\n                padding:16px !important;\n                box-sizing:border-box !important;\n            }\n            #ti-login-ov input,\n            #ti-login-ov select,\n            #ti-login-ov button {\n                pointer-events:auto !important;\n                font-size:16px !important;\n                min-height:42px !important;\n            }\n            #ti-login-ov #l-do {\n                min-height:44px !important;\n                touch-action:manipulation !important;\n            }\n        }\n\n\n        \/* Mobile\/touch hardening 30.9.154: scroll a un dito e comandi login\/logout *\/\n        @media (hover:none), (pointer:coarse), (max-width:900px) {\n            html, body,\n            html.ti-no-scroll, body.ti-no-scroll {\n                overflow-y:auto !important;\n                overflow-x:hidden !important;\n                height:auto !important;\n                min-height:100% !important;\n                max-height:none !important;\n                position:static !important;\n                touch-action:pan-y !important;\n                overscroll-behavior-y:auto !important;\n                -webkit-overflow-scrolling:touch !important;\n            }\n            #ti-ai-outer,\n            #ti-ai-outer.ti-plugin-scroll-lock,\n            .ti-plugin-scroll-lock {\n                overflow-y:visible !important;\n                overflow-x:hidden !important;\n                height:auto !important;\n                min-height:auto !important;\n                max-height:none !important;\n                position:relative !important;\n                touch-action:pan-y !important;\n                overscroll-behavior-y:auto !important;\n                -webkit-overflow-scrolling:touch !important;\n            }\n            #ti-ai-outer *,\n            #ti-plugin-modal-root,\n            #ti-plugin-modal-root * {\n                touch-action:pan-y !important;\n            }\n            #ti-ai-outer button,\n            #ti-ai-outer input,\n            #ti-ai-outer textarea,\n            #ti-ai-outer select,\n            #ti-plugin-modal-root button,\n            #ti-plugin-modal-root input,\n            #ti-plugin-modal-root textarea,\n            #ti-plugin-modal-root select,\n            #ti-login-btn,\n            #ti-logout-btn,\n            #l-do {\n                touch-action:manipulation !important;\n                -webkit-user-select:auto !important;\n                user-select:auto !important;\n                pointer-events:auto !important;\n            }\n            .ti-ov,\n            #ti-ai-loader-ov {\n                overflow-y:auto !important;\n                overflow-x:hidden !important;\n                align-items:flex-start !important;\n                justify-content:center !important;\n                touch-action:pan-y !important;\n                overscroll-behavior-y:auto !important;\n                -webkit-overflow-scrolling:touch !important;\n            }\n            .ti-modal,\n            .ti-conf-body,\n            .ti-chat-box,\n            .ti-cfg-table-container,\n            .ti-table-wrap,\n            #ti-conf-import-preview-body,\n            #global-action-res,\n            #help-bot-reply,\n            #col-help-content,\n            #ateco-res,\n            #fm-grid {\n                overflow-y:auto !important;\n                touch-action:pan-y !important;\n                overscroll-behavior-y:auto !important;\n                -webkit-overflow-scrolling:touch !important;\n            }\n            #ti-login-ov .ti-modal {\n                width:min(96vw, 390px) !important;\n                max-width:min(96vw, 390px) !important;\n                max-height:calc((var(--ti-vh, 1vh) * 88)) !important;\n                margin:8px auto 20px auto !important;\n            }\n            #ti-login-ov input,\n            #ti-login-ov button {\n                font-size:16px !important;\n                min-height:44px !important;\n            }\n        }\n        \/* 30.9.159 - Login: prima interfaccia PC\/modal, accesso diretto solo come fallback se il primo login non procede *\/\n        #ti-ai-outer #ti-mobile-direct-login {\n            display:none !important;\n            width:min(100%, 720px) !important;\n            margin:10px auto 12px auto !important;\n            padding:12px !important;\n            border-radius:14px !important;\n            background:#111827 !important;\n            border:1px solid #374151 !important;\n            color:#fff !important;\n            touch-action:pan-y !important;\n            box-sizing:border-box !important;\n        }\n        #ti-ai-outer #ti-mobile-direct-login.ti-direct-login-visible {\n            display:block !important;\n        }\n        #ti-ai-outer #ti-mobile-direct-login .ti-mobile-login-title {\n            font-size:14px !important;\n            font-weight:900 !important;\n            margin-bottom:8px !important;\n            color:#bfdbfe !important;\n        }\n        #ti-ai-outer #ti-mobile-direct-login form {\n            display:grid !important;\n            grid-template-columns:1fr 1fr 1.2fr auto !important;\n            gap:8px !important;\n            width:100% !important;\n            align-items:center !important;\n        }\n        #ti-ai-outer #ti-mobile-direct-login input,\n        #ti-ai-outer #ti-mobile-direct-login select,\n        #ti-ai-outer #ti-mobile-direct-login button {\n            width:100% !important;\n            min-width:0 !important;\n            min-height:38px !important;\n            height:38px !important;\n            font-size:16px !important;\n            line-height:18px !important;\n            padding:6px 8px !important;\n            border-radius:9px !important;\n            box-sizing:border-box !important;\n            touch-action:manipulation !important;\n        }\n        #ti-ai-outer #ti-mobile-direct-login input,\n        #ti-ai-outer #ti-mobile-direct-login select {\n            background:#0f172a !important;\n            color:#fff !important;\n            border:1px solid #334155 !important;\n        }\n        #ti-ai-outer #ti-mobile-direct-login .ti-mobile-login-submit {\n            background:#2563eb !important;\n            color:#fff !important;\n            border:1px solid #1d4ed8 !important;\n            font-weight:900 !important;\n            white-space:nowrap !important;\n        }\n        @media (hover:none), (pointer:coarse), (max-width:900px) {\n            #ti-ai-outer #ti-mobile-direct-login {\n                width:100% !important;\n                margin:8px 0 10px 0 !important;\n                padding:10px !important;\n            }\n            #ti-ai-outer #ti-mobile-direct-login form {\n                grid-template-columns:1fr 1fr !important;\n                gap:7px !important;\n            }\n            #ti-ai-outer #ti-mobile-direct-login select,\n            #ti-ai-outer #ti-mobile-direct-login .ti-mobile-login-submit {\n                grid-column:1 \/ -1 !important;\n            }\n        }\n\n        \/* 30.9.181 - Fix persi reintegrati in modo chirurgico *\/\n        #ti-purchase-ov .ti-purchase-table-wrap {\n            width:100% !important;\n            max-width:100% !important;\n            overflow-x:auto !important;\n            overflow-y:visible !important;\n            -webkit-overflow-scrolling:touch !important;\n            touch-action:pan-x pan-y !important;\n        }\n        #ti-purchase-ov #ti-purchase-table {\n            border-collapse:collapse !important;\n            width:100% !important;\n            min-width:1060px !important;\n        }\n        #ti-purchase-ov #ti-purchase-table th,\n        #ti-purchase-ov #ti-purchase-table td {\n            white-space:nowrap !important;\n        }\n        #ti-purchase-ov .ti-purchase-row input,\n        #ti-purchase-ov .ti-purchase-row select,\n        #ti-purchase-ov .ti-purchase-row textarea {\n            background:#0f172a !important;\n            color:#fff !important;\n            -webkit-text-fill-color:#fff !important;\n            caret-color:#fff !important;\n        }\n        .ti-purchase-row-search-popover {\n            position:fixed !important;\n            z-index:2147483647 !important;\n            background:#020617 !important;\n            border:1px solid #38bdf8 !important;\n            border-radius:10px !important;\n            box-shadow:0 16px 44px rgba(0,0,0,.65) !important;\n            max-height:min(320px, 52vh) !important;\n            overflow:auto !important;\n            padding:6px !important;\n            min-width:220px !important;\n            max-width:min(92vw, 520px) !important;\n            color:#fff !important;\n            pointer-events:auto !important;\n            -webkit-overflow-scrolling:touch !important;\n        }\n        .ti-purchase-row-search-popover .ti-purchase-result-row {\n            display:block !important;\n            width:100% !important;\n            text-align:left !important;\n            margin:0 0 5px 0 !important;\n            padding:7px 8px !important;\n            border-radius:8px !important;\n            border:1px solid #334155 !important;\n            background:#1e293b !important;\n            color:#fff !important;\n            font-size:12px !important;\n            line-height:1.25 !important;\n            cursor:pointer !important;\n        }\n        .ti-purchase-row-search-popover .ti-purchase-result-row small {\n            display:block !important;\n            color:#cbd5e1 !important;\n            font-size:10.5px !important;\n            margin-top:2px !important;\n        }\n        .ti-report-outcome-filter-wrap {\n            display:inline-flex !important;\n            align-items:center !important;\n            gap:6px !important;\n            background:#0f172a !important;\n            border:1px solid #334155 !important;\n            color:#e5e7eb !important;\n            border-radius:999px !important;\n            padding:5px 10px !important;\n            font-size:11px !important;\n            white-space:nowrap !important;\n            flex:0 0 auto !important;\n        }\n        .ti-report-outcome-filter {\n            width:auto !important;\n            min-width:120px !important;\n            padding:4px 7px !important;\n            border-radius:7px !important;\n            border:1px solid #94a3b8 !important;\n            background:#fff !important;\n            color:#111827 !important;\n            -webkit-text-fill-color:#111827 !important;\n            font-size:11px !important;\n            margin:0 !important;\n        }\n        .ti-report-outcome-filter option {\n            background:#fff !important;\n            color:#111827 !important;\n            -webkit-text-fill-color:#111827 !important;\n        }\n        #ti-ai-outer input[style*=\"background:#020617\"],\n        #ti-ai-outer input[style*=\"background: #020617\"],\n        #ti-ai-outer input[style*=\"background:#0f172a\"],\n        #ti-ai-outer input[style*=\"background: #0f172a\"],\n        #ti-ai-outer input[style*=\"background:#111827\"],\n        #ti-ai-outer input[style*=\"background: #111827\"],\n        #ti-ai-outer textarea[style*=\"background:#020617\"],\n        #ti-ai-outer textarea[style*=\"background: #020617\"],\n        #ti-ai-outer textarea[style*=\"background:#0f172a\"],\n        #ti-ai-outer textarea[style*=\"background: #0f172a\"],\n        #ti-ai-outer textarea[style*=\"background:#111827\"],\n        #ti-ai-outer textarea[style*=\"background: #111827\"],\n        #ti-ai-outer select[style*=\"background:#020617\"],\n        #ti-ai-outer select[style*=\"background: #020617\"],\n        #ti-ai-outer select[style*=\"background:#0f172a\"],\n        #ti-ai-outer select[style*=\"background: #0f172a\"],\n        #ti-ai-outer select[style*=\"background:#111827\"],\n        #ti-ai-outer select[style*=\"background: #111827\"],\n        #ti-ai-outer .ti-in,\n        #ti-plugin-modal-root .ti-in {\n            color:#fff !important;\n            -webkit-text-fill-color:#fff !important;\n            caret-color:#fff !important;\n        }\n        @media (max-width:767px) and (orientation:portrait) {\n            #ti-purchase-ov .ti-purchase-modal { width:98vw !important; max-width:98vw !important; padding:10px !important; }\n            #ti-purchase-ov #ti-purchase-table { min-width:980px !important; }\n            #ti-purchase-ov #ti-purchase-table th,\n            #ti-purchase-ov #ti-purchase-table td { font-size:10.5px !important; padding:4px !important; }\n            #ti-purchase-ov #ti-purchase-table input,\n            #ti-purchase-ov #ti-purchase-table select,\n            #ti-purchase-ov #ti-purchase-table textarea,\n            #ti-purchase-ov #ti-purchase-table button { font-size:10.5px !important; padding:4px !important; min-height:27px !important; }\n            #ti-purchase-ov .ti-pur-name { min-width:190px !important; }\n            .ti-purchase-row-search-popover .ti-purchase-result-row { font-size:10.5px !important; padding:6px !important; }\n            .ti-purchase-row-search-popover .ti-purchase-result-row small { font-size:9.5px !important; }\n        }\n        @media (max-width:920px) and (orientation:landscape) {\n            #ti-purchase-ov .ti-purchase-modal { width:98vw !important; max-width:98vw !important; padding:10px !important; }\n            #ti-purchase-ov #ti-purchase-table { min-width:900px !important; }\n            #ti-purchase-ov #ti-purchase-table th,\n            #ti-purchase-ov #ti-purchase-table td { font-size:10px !important; padding:3px !important; }\n            #ti-purchase-ov #ti-purchase-table input,\n            #ti-purchase-ov #ti-purchase-table select,\n            #ti-purchase-ov #ti-purchase-table textarea,\n            #ti-purchase-ov #ti-purchase-table button { font-size:10px !important; padding:3px !important; min-height:25px !important; }\n        }\n\n        \/* 30.9.189 - Acquisti smartphone orizzontale: riga ultra compatta *\/\n        @media (max-width: 980px) and (orientation: landscape) {\n            #ti-purchase-ov.ti-purchase-open {\n                align-items:flex-start !important;\n                padding:2px !important;\n            }\n            #ti-purchase-ov .ti-purchase-modal {\n                width:99vw !important;\n                max-width:99vw !important;\n                max-height:96vh !important;\n                margin:2px auto !important;\n                padding:5px !important;\n                border-radius:10px !important;\n                overflow:auto !important;\n            }\n            #ti-purchase-ov .ti-purchase-modal h3,\n            #ti-purchase-ov .ti-purchase-modal h4 {\n                font-size:11px !important;\n                line-height:1.05 !important;\n                margin:0 0 4px 0 !important;\n            }\n            #ti-purchase-ov .ti-purchase-table-wrap {\n                max-height:58vh !important;\n                overflow:auto !important;\n            }\n            #ti-purchase-ov #ti-purchase-table {\n                min-width:790px !important;\n                table-layout:auto !important;\n            }\n            #ti-purchase-ov #ti-purchase-table th,\n            #ti-purchase-ov #ti-purchase-table td {\n                font-size:8.5px !important;\n                line-height:1.05 !important;\n                padding:1px 2px !important;\n                height:22px !important;\n                max-height:22px !important;\n                vertical-align:middle !important;\n            }\n            #ti-purchase-ov #ti-purchase-table input,\n            #ti-purchase-ov #ti-purchase-table select,\n            #ti-purchase-ov #ti-purchase-table textarea,\n            #ti-purchase-ov #ti-purchase-table button {\n                font-size:8.5px !important;\n                line-height:1.05 !important;\n                padding:1px 2px !important;\n                height:20px !important;\n                min-height:20px !important;\n                max-height:20px !important;\n                border-radius:4px !important;\n                box-sizing:border-box !important;\n            }\n            #ti-purchase-ov .ti-pur-name { min-width:145px !important; max-width:165px !important; }\n            #ti-purchase-ov .ti-pur-type { width:42px !important; min-width:42px !important; }\n            #ti-purchase-ov .ti-pur-qty { width:46px !important; min-width:46px !important; }\n            #ti-purchase-ov .ti-pur-cost { width:62px !important; min-width:62px !important; }\n            #ti-purchase-ov .ti-pur-margin { width:56px !important; min-width:56px !important; }\n            #ti-purchase-ov .ti-pur-sale { width:70px !important; min-width:70px !important; }\n            #ti-purchase-ov .ti-pur-iva { width:42px !important; min-width:42px !important; }\n            #ti-purchase-ov .ti-pur-note { min-width:96px !important; max-width:115px !important; }\n            #ti-purchase-ov .ti-pur-row-total-net,\n            #ti-purchase-ov .ti-pur-row-total-gross {\n                display:inline-block !important;\n                min-width:42px !important;\n                font-size:8.5px !important;\n                line-height:1 !important;\n            }\n            #ti-purchase-ov .ti-pur-dup,\n            #ti-purchase-ov .ti-pur-del {\n                width:21px !important;\n                min-width:21px !important;\n                height:20px !important;\n                min-height:20px !important;\n                padding:0 !important;\n                margin:0 1px !important;\n                font-size:9px !important;\n                line-height:1 !important;\n            }\n            #ti-purchase-ov .ti-purchase-summary-grid {\n                grid-template-columns:repeat(4,minmax(70px,1fr)) !important;\n                gap:4px !important;\n                margin-top:4px !important;\n            }\n            #ti-purchase-ov .ti-purchase-summary-card {\n                font-size:8.5px !important;\n                line-height:1.05 !important;\n                padding:4px !important;\n                border-radius:7px !important;\n            }\n            #ti-purchase-ov .ti-purchase-summary-card b {\n                font-size:10px !important;\n                line-height:1.05 !important;\n            }\n            #ti-purchase-ov .ti-purchase-toolbar-fixed {\n                padding-top:4px !important;\n                gap:4px !important;\n            }\n            #ti-purchase-ov .ti-purchase-toolbar-fixed .ti-btn,\n            #ti-purchase-ov .ti-purchase-toolbar-fixed button {\n                font-size:9px !important;\n                line-height:1.05 !important;\n                padding:4px 6px !important;\n                min-height:23px !important;\n                height:23px !important;\n            }\n            .ti-purchase-row-search-popover .ti-purchase-result-row {\n                font-size:9px !important;\n                line-height:1.08 !important;\n                padding:4px 5px !important;\n                margin-bottom:3px !important;\n            }\n            .ti-purchase-row-search-popover .ti-purchase-result-row small {\n                font-size:8px !important;\n                line-height:1.05 !important;\n            }\n        }\n\n\n\n        \/* 30.9.226 - Popup separati in primo piano per comunicazioni\/report dei ruoli privilegiati *\/\n        \/* 30.9.227 - Prenotazione multipla: applica stessa scelta alle voci successive; operatore non definito nascosto all utente comune. *\/\n        \/* 30.9.233 - Fix sintassi JS nota funzioni utente: nota costruita da JSON per evitare apostrofi non protetti; ruoli privilegiati abilitati alle funzionalita ordine complete. *\/\n        #ti-admin-comm-ov { z-index:2147483646 !important; }\n        #ti-admin-comm-ov .ti-admin-comm-modal { width:min(94vw,820px); max-height:88vh; overflow:hidden; text-align:left; padding:18px; box-shadow:0 18px 50px rgba(0,0,0,.82); border:1px solid #475569; }\n        #ti-admin-comm-title { margin:0 0 10px 0; color:#fff; border-bottom:1px solid #334155; padding-bottom:10px; font-size:16px; font-weight:800; }\n        #ti-admin-comm-msg { max-height:62vh; overflow:auto; background:#020617; color:#e5e7eb; border:1px solid #334155; border-radius:10px; padding:12px; font-size:13px; line-height:1.45; white-space:normal; }\n        #ti-admin-comm-actions { display:flex; gap:10px; justify-content:flex-end; flex-wrap:wrap; margin-top:12px; }\n        #ti-admin-comm-copy { font-size:11px !important; padding:7px 10px !important; min-width:92px; }\n        @media (max-width:767px) { #ti-admin-comm-ov .ti-admin-comm-modal { width:96vw; max-height:92vh; padding:12px; } #ti-admin-comm-msg { max-height:66vh; font-size:12px; } }\n\n<\/style>\n\n    <div id=\"ti-ai-outer\" class=\"ti-plugin-scroll-lock\">\n        <div id=\"ti-ai-loader-ov\">\n            <div class=\"ti-loader-box\">\n                <div class=\"ti-loader-spinner\">\u23f3<\/div>\n                <div id=\"ti-loader-title\" style=\"color:#fff; font-weight:bold; font-size:14px;\">Elaborazione AI in corso...<\/div>\n                <div id=\"ti-loader-sub\" style=\"color:#aaa; font-size:11px; margin-top:5px;\">Attendi, l'operazione potrebbe richiedere alcuni secondi.<\/div>\n                <div id=\"ti-loader-current-item\" style=\"display:none;margin-top:8px;padding:7px 9px;border-radius:8px;background:#0f172a;border:1px solid #334155;color:#bfdbfe;font-size:12px;font-weight:700;word-break:break-word;\"><\/div>\n                <div id=\"ti-loader-progress-wrap\" style=\"margin-top:12px; width:100%; display:none;\">\n                    <div style=\"display:flex; justify-content:space-between; align-items:center; margin-bottom:6px;\">\n                        <span style=\"font-size:11px; color:#cbd5e1;\">Avanzamento stimato<\/span>\n                        <span id=\"ti-loader-percent\" style=\"font-size:12px; color:#fff; font-weight:700;\">0%<\/span>\n                    <\/div>\n                    <div style=\"width:100%; height:10px; background:#111827; border:1px solid #374151; border-radius:999px; overflow:hidden;\">\n                        <div id=\"ti-loader-bar\" style=\"width:0%; height:100%; background:linear-gradient(90deg,#38bdf8,#10b981); transition:width .25s ease;\"><\/div>\n                    <\/div>\n                <\/div>\n                <div id=\"ti-loader-actions\" style=\"margin-top:12px; display:none;\">\n                    <button type=\"button\" id=\"ti-loader-close\" class=\"ti-btn\" style=\"width:100%; background:#b91c1c; color:#fff;\" onclick=\"window.cancelLongAIProcess()\">Esci<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-alert-ov\" class=\"ti-ov\" style=\"z-index: 2147483647 !important;\">\n            <div class=\"ti-modal\" style=\"width: 300px; padding: 20px; box-shadow: 0 5px 20px rgba(0,0,0,0.8);\">\n                <h4 id=\"ti-alert-msg\" style=\"color: #fff; margin-bottom: 20px; font-size: 15px; font-weight: normal; line-height: 1.4;\"><\/h4>\n                <div style=\"display:flex; gap:10px; justify-content:center; flex-wrap:wrap;\">\n                    <button type=\"button\" id=\"ti-alert-copy\" class=\"ti-btn\" style=\"background:#2563eb; color:#fff; flex:1; min-width:90px; font-size:11px; padding:7px 8px;\">Copia testo<\/button>\n                    <button type=\"button\" id=\"ti-alert-yes\" class=\"ti-btn\" style=\"background:#10b981; color:#fff; display:none; flex:1; min-width:90px;\">SI<\/button>\n                    <button type=\"button\" id=\"ti-alert-no\" class=\"ti-btn\" style=\"background:#4b5563; color:#fff; flex:1; min-width:90px;\">OK<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-admin-comm-ov\" class=\"ti-ov ti-admin-comm-ov\" style=\"z-index: 2147483646 !important;\">\n            <div class=\"ti-modal ti-admin-comm-modal\">\n                <h3 id=\"ti-admin-comm-title\">Comunicazione<\/h3>\n                <div id=\"ti-admin-comm-msg\"><\/div>\n                <div id=\"ti-admin-comm-actions\">\n                    <button type=\"button\" id=\"ti-admin-comm-copy\" class=\"ti-btn\" style=\"background:#2563eb;color:#fff;\">Copia testo<\/button>\n                    <button type=\"button\" id=\"ti-admin-comm-close\" class=\"ti-btn btn-close\" style=\"background:#4b5563;color:#fff;\">Chiudi<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n\n\n        <div id=\"ti-maintenance-ov\" class=\"ti-ov\" data-ti-non-closable=\"1\" style=\"z-index: 2147483645 !important;\">\n            <div class=\"ti-modal ti-maintenance-modal\">\n                <h3 class=\"ti-maintenance-title\">\ud83d\udd27 Servizio in manutenzione<\/h3>\n                <div id=\"ti-maintenance-msg\" class=\"ti-maintenance-message\">\n                    Il servizio \u00e8 temporaneamente in manutenzione. Il gestore si scusa per il disagio: sar\u00e0 nuovamente disponibile in tempi brevi.\n                <\/div>\n                <div id=\"ti-maintenance-login-row\">\n                    <button type=\"button\" id=\"ti-maintenance-login-btn\" class=\"ti-btn\" onclick=\"return window.openMaintenanceLogin ? window.openMaintenanceLogin() : (window.openMaintenanceLoginAccess ? window.openMaintenanceLoginAccess() : false);\">Accesso configuratore \/ amministratore<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div class=\"ti-header\">\n            <div class=\"ti-header-top\">\n                <div class=\"ti-header-main-left\">\n                    <div class=\"ti-hl\">\n                         <div class=\"ti-left-stack\">\n                              <img id=\"ti-qr-img\" class=\"ti-qr\" style=\"display:none;\" title=\"Apri ditta in nuova scheda\" data-no-lazy=\"1\" \/>\n                         <\/div>\n                         <img id=\"ti-logo-img\" class=\"ti-logo\" style=\"display:none;\" onerror=\"this.style.display='none'\" data-no-lazy=\"1\" \/>\n                         <div style=\"display:flex; flex-direction:column; align-items:flex-start; min-width:0;\">\n                              <div style=\"display:flex; align-items:center; flex-wrap:wrap; gap:8px;\">\n                                   <div id=\"ti-company-name\" style=\"font-weight:900; font-size:1.1em; color:#fff;\">SHOP & SERVICE<\/div>\n                              <\/div>\n                         <\/div>\n                    <\/div>\n                <\/div>\n                <div class=\"ti-hr-right\">\n                     <div class=\"ti-lang-wrap\"><span class=\"ti-lang-opt active\" data-lang=\"it\">IT<\/span><span class=\"ti-lang-opt\" data-lang=\"en\">EN<\/span><\/div>\n                <\/div>\n            <\/div>\n            <div class=\"ti-company-sub-row\"><div id=\"ti-company-sub\" style=\"display:none;\"><\/div><\/div>\n            <div class=\"ti-header-bottom\">\n                 <div class=\"ti-header-left-controls\">\n                     <div id=\"ti-sort-btn\" title=\"Ordina\" style=\"cursor:pointer; background:#2563eb; border-color:#1d4ed8; color:#fff;\">AZ<\/div>\n                     <button type=\"button\" id=\"ti-show-sospese-btn\" class=\"ti-btn\" title=\"Mostra anche le ditte sospese\" style=\"display:flex; align-items:center; justify-content:center; min-width:56px; background:#374151; color:#fff;\">Sosp<\/button>\n                     <input type=\"text\" id=\"ti-filter-ditte\" placeholder=\"\ud83d\udd0d Cerca ditta o attivit\u00e0\" autocomplete=\"off\">\n                     <div class=\"ti-ditta-picker\"><button type=\"button\" id=\"ti-ditta-trigger\" class=\"ti-ditta-trigger\">-- Scegli una ditta --<\/button><div id=\"ti-ditta-popup\" class=\"ti-ditta-popup\" style=\"display:none;\"><\/div><select id=\"ti-ditta\" style=\"display:none !important;\"><\/select><\/div>\n                 <\/div>\n                 <div class=\"ti-header-right-controls\">\n                     <button type=\"button\" id=\"ti-login-btn\" class=\"ti-btn\" onclick=\"if (window.openLoginModalSafe) window.openLoginModalSafe(); else if(window.openModal) window.openModal('ti-login-ov'); return false;\">Accedi<\/button>\n                     <div id=\"ti-user-area\" class=\"ti-user-area\" onclick=\"window.openProfileModal()\">\n                          <img decoding=\"async\" id=\"ti-user-avatar\" class=\"ti-user-avatar\" src=\"\" style=\"display:none;\" onerror=\"this.style.display='none'\">\n                          <div class=\"ti-user-info\">\n                              <span id=\"ti-user-name\" class=\"ti-user-name\">Cliente\/Utente<\/span><span id=\"ti-user-role\" class=\"ti-user-role\"><\/span>\n                          <\/div>\n                      <\/div>\n                     <button type=\"button\" id=\"ti-logout-btn\" class=\"ti-btn\" style=\"display:none;\" onclick=\"window.doLogout()\">Esci<\/button>\n                 <\/div>\n            <\/div>\n        <\/div>\n                <div id=\"ti-mobile-direct-login\" class=\"\" aria-label=\"Login diretto Shop & Service fallback\">\n            <div class=\"ti-mobile-login-title\">Accesso diretto Shop & Service <span style=\"font-weight:600;color:#93c5fd;\">(fallback)<\/span><\/div>\n            <div style=\"font-size:12px;color:#cbd5e1;margin:-4px 0 8px 0;\">Usa questo accesso solo se il login principale non procede o restituisce errore.<\/div>\n            <div id=\"ti-direct-login-fallback-alert\" style=\"display:none;background:#78350f;color:#fff;border:1px solid #f59e0b;border-radius:8px;padding:7px;margin-bottom:8px;font-size:12px;line-height:1.35;\"><\/div>\n                        <form method=\"post\" action=\"\/en\/wp-json\/wp\/v2\/pages\/1165\" autocomplete=\"on\">\n                <input type=\"hidden\" name=\"ti_action\" value=\"ti_ai_login\">\n                <input type=\"hidden\" name=\"action\" value=\"ti_ai_login\">\n                <input type=\"hidden\" name=\"ti_mobile_login_redirect\" value=\"1\">\n                <input type=\"hidden\" name=\"redirect\" value=\"1\">\n                <input id=\"ti-direct-login-user\" name=\"username\" placeholder=\"Username\" autocomplete=\"username\" data-ti-login-username=\"1\" autocapitalize=\"none\" autocorrect=\"off\" spellcheck=\"false\" inputmode=\"text\">\n                <input id=\"ti-direct-login-pass\" name=\"password\" type=\"password\" placeholder=\"Password\" autocomplete=\"current-password\" data-ti-login-password=\"1\" autocapitalize=\"none\" autocorrect=\"off\" spellcheck=\"false\">\n                <select id=\"ti-direct-login-db\" name=\"db_select\" aria-label=\"Ditta\">\n                    <option value=\"\">-- Scegli una ditta --<\/option>\n                                            <option value=\"Dev_hairstyle.json\" >Dev_hairstyle<\/option>\n                                            <option value=\"bmw-terni.json\" >bmw-terni<\/option>\n                                            <option value=\"catering-terni.json\" >catering-terni<\/option>\n                                            <option value=\"farmaciaterni.json\" >farmaciaterni<\/option>\n                                            <option value=\"ferramentaterni.json\" >ferramentaterni<\/option>\n                                            <option value=\"fioraio.json\" >fioraio<\/option>\n                                            <option value=\"hairstyle.json\" >hairstyle<\/option>\n                                            <option value=\"hard&amp;soft.json\" >hard&amp;soft<\/option>\n                                            <option value=\"ristoranteterni.json\" >ristoranteterni<\/option>\n                                    <\/select>\n                <button type=\"submit\" class=\"ti-mobile-login-submit\">Entra<\/button>\n                <button type=\"button\" class=\"ti-mobile-login-submit ti-forgot-password-wide\" style=\"grid-column:1 \/ -1;margin-top:8px;background:#4b5563;font-size:12px;min-width:260px;min-height:44px;padding:10px 14px;white-space:normal;line-height:1.25;\" onclick=\"if(window.tiForgotPasswordFromLogin) window.tiForgotPasswordFromLogin('direct'); return false;\">Password dimenticata? Cambia tramite email<\/button>\n            <\/form>\n        <\/div>\n                <div id=\"ti-chat-box\" class=\"ti-chat-box ti-scrollable-plugin\"><\/div>\n        \n        <div id=\"ti-order-confirm-host\" class=\"ti-order-confirm-host\"><\/div>\n        <div class=\"ti-input-wrap\">\n            <div class=\"ti-input-main\">\n                <textarea id=\"ti-msg\" class=\"ti-msg\" rows=\"5\" placeholder=\"Scrivi qui...\"><\/textarea>\n                <div class=\"ti-send-wrap\">\n                    <button type=\"button\" id=\"ti-send\" class=\"ti-send\">INVIA<\/button>\n                    <button type=\"button\" id=\"ti-ok\" class=\"ti-btn ti-btn-ok\">OK \/ SI<\/button>\n                    <button type=\"button\" id=\"ti-x\" class=\"ti-btn ti-btn-x\">X \/ NO<\/button>\n                <\/div>\n            <\/div>\n            <div class=\"ti-actions-row\">\n                <button type=\"button\" id=\"ti-new\" class=\"ti-btn\">Nuova<\/button>\n                <button type=\"button\" id=\"ti-copy\" class=\"ti-btn\" style=\"font-size:11px; padding-left:8px; padding-right:8px;\">Copia testo<\/button>\n                <button type=\"button\" id=\"ti-file\" class=\"ti-btn\" onclick=\"window.showUploadChoice('chat')\">File<\/button>\n                <button type=\"button\" id=\"ti-products-btn\" class=\"ti-btn ti-btn-catalog\">Prodotti<\/button>\n                <button type=\"button\" id=\"ti-services-btn\" class=\"ti-btn ti-btn-catalog ti-btn-services\">Servizi<\/button>\n                <button type=\"button\" id=\"ti-activities-btn\" class=\"ti-btn ti-btn-catalog ti-btn-activities\" style=\"display:none !important;\">Attivit\u00e0<\/button>\n                <button type=\"button\" id=\"ti-kbd-btn\" class=\"ti-btn\">Tastiera<\/button>\n                <button type=\"button\" id=\"ti-mic\" class=\"ti-btn\">Mic<\/button>\n                <button type=\"button\" id=\"ti-cam\" class=\"ti-btn\">Cam<\/button>\n                <button type=\"button\" id=\"ti-mute-btn\" class=\"ti-btn\">\ud83d\udd0a ON<\/button>\n                <button type=\"button\" id=\"ti-help-btn\" class=\"ti-help\" title=\"Aiuto\" aria-label=\"Aiuto\">?<\/button>\n            <\/div>\n            <div id=\"ti-admin-actions-row\" class=\"ti-admin-actions-row\" aria-label=\"Comandi amministrativi\" style=\"display:none;\"><\/div>\n        <\/div>\n        <div class=\"ti-shop-footer\">Target Informatica S.r.l. Via Filippo Turati, 16 05100 Terni \u2013 Italy Tel. +39 0744 288409\u201310<br>P.IVA 00664210556 CCIAA Terni 67219 \u2013 Trib.Terni n. 132\/94 \u00a9 Copyright 2019 Target Informatica S.r.l.<br><a href=\"https:\/\/www.targetinformatica.it\/\" target=\"_blank\" rel=\"noopener\">www.targetinformatica.it<\/a><div class=\"ti-shop-footer-contacts\" style=\"font-size:11px; text-align:center; margin-top:5px; line-height:1.55;\">\n            <div>Info: <a href=\"mailto:SSInfo@targetinformatica.it\">SSInfo@targetinformatica.it<\/a><\/div>\n            <div>Commerciale\/amministrazione: <a href=\"mailto:SSComm@targetinformatica.it\">SSComm@targetinformatica.it<\/a><\/div>\n            <div>Supporto tecnico: <a href=\"mailto:SSSupport@targetinformatica.it\">SSSupport@targetinformatica.it<\/a><\/div>\n        <\/div><\/div>\n\n        <div id=\"ti-conf-ov\" class=\"ti-ov ti-closable-ov\" style=\"z-index: 1000 !important;\">\n            <div class=\"ti-conf-window\">\n                <div class=\"ti-conf-header\">\n                    <h3 id=\"ti-conf-title\" style=\"color:#fff; margin:0; font-size:18px; font-weight:bold;\">Impostazioni Ditta<\/h3>\n                    <div id=\"ti-conf-global-btn\"><\/div>\n                <\/div>\n                <div class=\"ti-conf-body\">\n                    <div id=\"ti-conf-cnt\" class=\"ti-scrollable-plugin\"><\/div>\n                <\/div>\n                <div class=\"ti-conf-footer ti-conf-footer-two-rows\">\n                    <button type=\"button\" id=\"lbl-conf-save\" class=\"ti-btn\" style=\"background:#374151 !important; color:#fff !important; cursor:pointer !important; font-size:12px !important; border:none !important;\" onclick=\"window.saveConfig()\">Salva Modifiche<\/button>\n                    <button type=\"button\" id=\"ti-maint-toggle-btn\" class=\"ti-btn ti-maint-toggle ti-maint-off\" data-ti-maint-state=\"off\" style=\"background:#16a34a !important; background-color:#16a34a !important; color:#fff !important; cursor:pointer !important; font-size:12px !important; border:1px solid #15803d !important;\" onclick=\"window.confirmToggleCompanyMaintenance()\">Manutenzione OFF<\/button>\n                    <button type=\"button\" class=\"ti-btn btn-close\" style=\"background:#b91c1c !important; color:#fff !important; cursor:pointer !important; font-size:12px !important; border:none !important;\" onclick=\"window.closeConfigModal()\">Chiudi<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-conf-import-preview-ov\" class=\"ti-ov\">\n            <div class=\"ti-modal\" style=\"width:90%;max-width:760px;text-align:left;\">\n                <h3 style=\"color:#fff;margin-top:0;border-bottom:1px solid #444;padding-bottom:10px;\">Anteprima modifiche import AI<\/h3>\n                <div style=\"font-size:12px;color:#9ca3af;margin-bottom:10px;\">Controlla l'anteprima delle modifiche. L'elenco non verr\u00e0 letto vocalmente. Conferma solo se vuoi procedere al salvataggio nel database.<\/div>\n                <div id=\"ti-conf-import-preview-last-update\" style=\"display:none;margin-top:10px;padding:8px 10px;border-radius:8px;border:1px solid #22c55e;background:#052e16;color:#bbf7d0;font-size:12px;font-weight:700;\"><\/div>\n                <div id=\"ti-conf-import-preview-body\" style=\"display:block;margin-top:10px;background:#000;padding:12px;border-radius:8px;border:1px solid #374151;color:#e5e7eb;white-space:normal;max-height:55vh;overflow:auto;\"><\/div>\n                <div id=\"ti-conf-import-refine-box\" style=\"margin-top:12px;padding:10px;border-radius:8px;border:1px solid #1d4ed8;background:#0f172a;\">\n                    <label for=\"ti-conf-import-refine-prompt\" style=\"display:block;font-size:12px;color:#bfdbfe;font-weight:700;margin-bottom:6px;\">Raffina import con AI sulla base della prima anteprima<\/label>\n                    <textarea id=\"ti-conf-import-refine-prompt\" class=\"ti-in\" rows=\"3\" style=\"width:100%;min-height:70px;resize:vertical;\" placeholder=\"Es: usa il prezzo medio del range, sposta il principio attivo nella descrizione, elimina righe duplicate, normalizza categorie...\"><\/textarea>\n                    <div style=\"display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;align-items:center;\">\n                        <button type=\"button\" id=\"ti-conf-import-refine-btn\" class=\"ti-btn\" style=\"background:#2563eb;\" onclick=\"window.refineConfigImportPreview()\">Raffina anteprima con AI<\/button>\n                        <span id=\"ti-conf-import-refine-status\" style=\"font-size:12px;color:#93c5fd;\"><\/span>\n                    <\/div>\n                <\/div>\n                <div style=\"display:flex;gap:8px;flex-wrap:wrap;margin-top:12px;\">\n                    <button type=\"button\" class=\"ti-btn ti-btn-ok\" onclick=\"window.confirmConfigImportPreview()\">OK Conferma e salva<\/button>\n                    <button type=\"button\" class=\"ti-btn ti-btn-x\" onclick=\"window.cancelConfigImportPreview()\">X Annulla import<\/button>\n                    <button type=\"button\" class=\"ti-btn\" onclick=\"window.closeConfigImportPreview()\">Chiudi<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-file-manager-ov\" class=\"ti-ov ti-closable-ov\" style=\"z-index: 1001 !important;\">\n            <div class=\"ti-modal\" style=\"width:90%; max-width:600px;\">\n                <h3 style=\"color:#fff;margin-top:0;border-bottom:1px solid #444;padding-bottom:10px;\">Gestione File Associati<\/h3>\n                <div id=\"fm-element-description\" style=\"display:none;margin:0 0 10px 0;padding:10px 12px;border-radius:10px;background:#0f172a;border:1px solid #2563eb;color:#dbeafe;font-size:13px;line-height:1.45;text-align:left;\"><\/div>\n                <div id=\"fm-storage-path\" style=\"display:none;margin:0 0 10px 0;padding:8px 10px;border-radius:8px;background:#020617;border:1px solid #334155;color:#bfdbfe;font-size:11px;word-break:break-all;\"><\/div>\n                <div id=\"fm-grid\" class=\"ti-file-grid\"><\/div>\n                <div style=\"display:flex; gap:10px; margin-top:20px;\">\n                    <button type=\"button\" class=\"ti-btn\" style=\"flex:1; background:#2563eb;\" onclick=\"window.chooseLocalManager()\">\ud83d\udcc1 Aggiungi da PC<\/button>\n                    <button type=\"button\" class=\"ti-btn\" style=\"flex:1; background:#8b5cf6;\" onclick=\"window.chooseAIManager()\">\ud83e\udd16 Genera con AI<\/button>\n                <\/div>\n                <button type=\"button\" onclick=\"window.closeModal('ti-file-manager-ov')\" class=\"ti-btn btn-close\" style=\"width:100%; margin-top:10px; background:#4b5563;\">Chiudi<\/button>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-login-ov\" class=\"ti-ov ti-closable-ov\">\n            <div class=\"ti-modal\">\n                <h3 id=\"lbl-login\" style=\"color:#fff;margin-top:0;\">Accedi<\/h3>\n                <div id=\"ti-login-maintenance-note\">Servizio in manutenzione: il login resta disponibile per configuratori e amministratori. Gli utenti ordinari vedranno l'avviso di manutenzione dopo l'accesso.<\/div>\n                                <form id=\"ti-login-form\" autocomplete=\"on\" method=\"post\">\n                    <input type=\"hidden\" name=\"ti_action\" value=\"ti_ai_login\">\n                    <input type=\"hidden\" name=\"action\" value=\"ti_ai_login\">\n                    <input type=\"hidden\" name=\"ti_mobile_login_redirect\" value=\"1\">\n                    <input type=\"hidden\" id=\"l-db-hidden\" name=\"db\" value=\"\">\n                    <input id=\"l-user\" name=\"username\" class=\"ti-in\" placeholder=\"Username\" autocomplete=\"username\" data-ti-login-username=\"1\" autocapitalize=\"none\" autocorrect=\"off\" spellcheck=\"false\" inputmode=\"text\">\n                    <input id=\"l-pass\" name=\"password\" class=\"ti-in\" type=\"password\" placeholder=\"Password\" autocomplete=\"current-password\" data-ti-login-password=\"1\" autocapitalize=\"none\" autocorrect=\"off\" spellcheck=\"false\">\n                    <select id=\"l-db\" name=\"db_select\" class=\"ti-in\" aria-label=\"Ditta\" style=\"margin-top:8px; display:block;\">\n                        <option value=\"\">-- Scegli una ditta --<\/option>\n                                                    <option value=\"Dev_hairstyle.json\" >Dev_hairstyle<\/option>\n                                                    <option value=\"bmw-terni.json\" >bmw-terni<\/option>\n                                                    <option value=\"catering-terni.json\" >catering-terni<\/option>\n                                                    <option value=\"farmaciaterni.json\" >farmaciaterni<\/option>\n                                                    <option value=\"ferramentaterni.json\" >ferramentaterni<\/option>\n                                                    <option value=\"fioraio.json\" >fioraio<\/option>\n                                                    <option value=\"hairstyle.json\" >hairstyle<\/option>\n                                                    <option value=\"hard&amp;soft.json\" >hard&amp;soft<\/option>\n                                                    <option value=\"ristoranteterni.json\" >ristoranteterni<\/option>\n                                            <\/select>\n                    <button type=\"submit\" id=\"l-do\" class=\"ti-btn\" style=\"width:100%; background:#3b82f6; margin-top:10px;\">Entra<\/button>\n                <\/form>\n                <button type=\"button\" id=\"ti-forgot-password-btn\" class=\"ti-btn ti-forgot-password-wide\" style=\"width:100%; min-width:260px; min-height:44px; padding:10px 14px; margin-top:8px; background:#4b5563; color:#fff; font-size:12px; line-height:1.25; white-space:normal;\" onclick=\"if(window.tiForgotPasswordFromLogin) window.tiForgotPasswordFromLogin('modal'); return false;\">Password dimenticata? Cambia tramite email<\/button>\n                <button type=\"button\" id=\"ti-show-direct-login-fallback\" class=\"ti-btn\" style=\"width:100%; margin-top:8px; background:#374151; color:#fff; font-size:12px;\" onclick=\"if(window.revealDirectLoginFallback) window.revealDirectLoginFallback('manuale'); return false;\">Se il login non procede, usa Accesso diretto<\/button>\n                <hr style=\"border-top:1px solid #444; margin:15px 0;\">\n                <button type=\"button\" class=\"ti-btn\" style=\"width:100%; background:#10b981;\" onclick=\"window.closeModal('ti-login-ov'); window.openProfileModal(true);\">Non hai un account? Registrati<\/button>\n                <button type=\"button\" onclick=\"window.closeModal('ti-login-ov')\" class=\"ti-btn btn-close\" style=\"width:100%; margin-top:10px;\">Chiudi<\/button>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-new-db-ov\" class=\"ti-ov ti-closable-ov\">\n            <div class=\"ti-modal\" style=\"width:90%;max-width:450px;text-align:left;\">\n                <h3 style=\"color:#fff;margin-top:0;text-align:center;border-bottom:1px solid #444;padding-bottom:10px;\">Crea Nuova Ditta<\/h3>\n                <a href=\"https:\/\/www.targetinformatica.it\/wp-content\/uploads\/AI-data\/AI-docs\/Shop-service\/Shop-Service%20Manual.pdf\" target=\"_blank\" rel=\"noopener\" class=\"ti-btn\" style=\"width:100%;box-sizing:border-box;background:#059669;color:#fff;text-decoration:none;text-align:center;margin:0 0 12px 0;font-weight:bold;display:block;\">\ud83d\udcc4 Manuale operativo Shop &amp; Service<\/a>\n                <form onsubmit=\"event.preventDefault(); window.createDbSubmit();\">\n                    <label style=\"font-size:11px;color:#aaa;\">Nome della nuova Ditta*<\/label>\n                    <input id=\"nd-name\" class=\"ti-in\" style=\"margin-top:2px;\" placeholder=\"Es: Pizzeria Roma\" required>\n                    <h4 style=\"color:#38bdf8; margin:15px 0 5px 0; font-size:14px;\">Primo Utente (Configuratore)<\/h4>\n                    <label style=\"font-size:11px;color:#aaa;\">Username*<\/label>\n                    <input id=\"nd-user\" class=\"ti-in\" style=\"margin-top:2px;\" placeholder=\"Es: admin_roma\" required>\n                    <label style=\"font-size:11px;color:#aaa;\">Password*<\/label>\n                    <input id=\"nd-pass\" type=\"password\" class=\"ti-in\" style=\"margin-top:2px;\" placeholder=\"***\" required>\n                    <label style=\"font-size:11px;color:#aaa;\">Email (Opzionale)<\/label>\n                    <input id=\"nd-email\" type=\"email\" class=\"ti-in\" style=\"margin-top:2px;\" placeholder=\"info@pizzeriaroma.it\">\n                    <div style=\"font-size:12px;color:#10b981;text-align:left;line-height:1.45;background:#052e25;border:1px solid #065f46;border-radius:10px;padding:10px;margin-top:12px;\">\n                        <p style=\"margin:0 0 8px 0;\">Alla fine del processo di creazione della ditta, verr\u00e0 generato l'archivio e farai l'accesso automatico a te riservato come configuratore.<\/p>\n                        <p style=\"margin:0 0 8px 0;\">Il servizio sar\u00e0 <b>GRATUITO di prova per 30 giorni<\/b>, poi potrai eventualmente attivare la ditta alle condizioni commerciali previste per il servizio Shop &amp; Service disponibili direttamente durante la configurazione ed il periodo di prova.<\/p>\n                        <p style=\"margin:0;text-align:center;font-weight:bold;\">Buon lavoro !!<\/p>\n                    <\/div>\n                    <button type=\"submit\" class=\"ti-btn\" style=\"width:100%; background:#10b981; margin-top:10px; font-weight:bold; font-size:15px; padding:10px;\">Crea e Accedi<\/button>\n                <\/form>\n                <button type=\"button\" onclick=\"window.closeModal('ti-new-db-ov')\" class=\"ti-btn btn-close\" style=\"width:100%; margin-top:5px;\">Annulla<\/button>\n            <\/div>\n        <\/div>\n        \n        <div id=\"ti-profile-ov\" class=\"ti-ov ti-closable-ov\">\n            <div class=\"ti-modal ti-scrollable-plugin\" style=\"width:90%;max-width:500px;text-align:left;\">\n                <h3 id=\"lbl-profile-title\" style=\"color:#fff;margin-top:0;text-align:center;border-bottom:1px solid #444;padding-bottom:10px;\">Profilo Utente<\/h3>\n                <form id=\"ti-profile-form\" autocomplete=\"off\" onsubmit=\"event.preventDefault(); window.saveProfile();\">\n                    <div style=\"display:flex; gap:15px; align-items:center; margin-bottom:15px;\">\n                        <img decoding=\"async\" id=\"p-foto-preview\" src=\"\" style=\"width:60px;height:60px;border-radius:50%;background:#000;display:none;object-fit:cover;border:2px solid #38bdf8;\">\n                        <input type=\"hidden\" id=\"p-foto\" value=\"\">\n                        <button type=\"button\" class=\"ti-btn\" style=\"flex:1;\" onclick=\"window.showUploadChoice('p-foto')\">\ud83d\udcf7 Scegli Avatar \/ Logo<\/button>\n                    <\/div>\n                    \n                    <div class=\"ti-grid-2\">\n                        <div><label>Utente (Nome Cognome)*<\/label><input id=\"p-utente\" class=\"ti-in\" required autocomplete=\"off\" autocapitalize=\"words\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        <div><label>Username (Accesso)*<\/label><input id=\"p-user\" class=\"ti-in\" required autocomplete=\"off\" autocapitalize=\"none\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        \n                        <div><label id=\"lbl-p-pass\">Password*<\/label><input id=\"p-pass\" type=\"password\" class=\"ti-in\" placeholder=\"***\" autocomplete=\"new-password\" autocapitalize=\"none\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        <div><label>Email<\/label><input id=\"p-email\" type=\"email\" class=\"ti-in\" autocomplete=\"off\" autocapitalize=\"none\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        \n                        <div><label>Ragione Sociale<\/label><input id=\"p-ragione_sociale\" class=\"ti-in\" autocomplete=\"off\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        <div><label>Indirizzo<\/label><input id=\"p-ind\" class=\"ti-in\" autocomplete=\"off\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        \n                        <div><label>P.IVA \/ VAT<\/label><input id=\"p-piva_vat\" class=\"ti-in\" autocomplete=\"off\" autocapitalize=\"characters\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        <div><label>Codice Fiscale<\/label><input id=\"p-cod_fisc\" class=\"ti-in\" autocomplete=\"off\" autocapitalize=\"characters\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        \n                        <div><label>Codice ADE \/ SDI<\/label><input id=\"p-ade\" class=\"ti-in\" autocomplete=\"off\" autocapitalize=\"characters\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        <div><label>Telefono<\/label><input id=\"p-tel\" class=\"ti-in\" autocomplete=\"off\" autocapitalize=\"none\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        \n                        <div><label>Sesso<\/label><input id=\"p-sesso\" class=\"ti-in\" placeholder=\"M\/F\" autocomplete=\"off\" autocapitalize=\"characters\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        <div><label>Et\u00e0<\/label><input id=\"p-eta\" type=\"number\" class=\"ti-in\" value=\"0\" min=\"0\" autocomplete=\"off\"><\/div>\n\n                        <div><label>Dato 1<\/label><input id=\"p-dato_1\" class=\"ti-in\" autocomplete=\"off\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        <div><label>Dato 2<\/label><input id=\"p-dato_2\" class=\"ti-in\" autocomplete=\"off\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        \n                        <div style=\"grid-column: span 2;\"><label>Dato 3<\/label><input id=\"p-dato_3\" class=\"ti-in\" autocomplete=\"off\" autocorrect=\"off\" spellcheck=\"false\"><\/div>\n                        \n                        <div style=\"grid-column: span 2;\">\n                            <label style=\"border-bottom:1px solid #444; padding-bottom:5px; margin-bottom:5px;\">Consensi<\/label>\n                            <div style=\"display:grid; grid-template-columns: 1fr 1fr; gap:5px;\">\n                                <label class=\"ti-cb-wrap\"><input type=\"checkbox\" id=\"p-com_mail\" checked> Com Mail<\/label>\n                                <label class=\"ti-cb-wrap\"><input type=\"checkbox\" id=\"p-com_email\" checked> Com Email<\/label>\n                                <label class=\"ti-cb-wrap\"><input type=\"checkbox\" id=\"p-com_sms\" checked> Com SMS<\/label>\n                                <label class=\"ti-cb-wrap\"><input type=\"checkbox\" id=\"p-com_tel\" checked> Com Tel<\/label>\n                            <\/div>\n                        <\/div>\n                        \n                        <div style=\"grid-column: span 2;\">\n                            <label>Note aggiuntive<\/label>\n                            <textarea id=\"p-nota\" class=\"ti-in\" rows=\"2\" autocomplete=\"off\" autocorrect=\"off\" spellcheck=\"false\"><\/textarea>\n                        <\/div>\n                    <\/div>\n\n                    <div class=\"ti-profile-actions\" style=\"display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px; margin-top:10px; align-items:stretch; justify-content:stretch;\">\n                        <button type=\"submit\" id=\"ti-profile-save-btn\" class=\"ti-btn ti-profile-btn-eq\" style=\"width:100%; background:#10b981 !important; color:#fff !important; font-weight:bold; font-size:15px; padding:0 12px !important; min-height:46px !important; height:46px !important; line-height:1 !important; display:flex !important; align-items:center !important; justify-content:center !important; box-sizing:border-box; white-space:nowrap;\">Salva<\/button>\n                        <button type=\"button\" onclick=\"window.closeModal('ti-profile-ov')\" class=\"ti-btn btn-close ti-profile-btn-eq\" style=\"width:100%; background:#374151 !important; color:#fff !important; font-size:15px; font-weight:bold; padding:0 12px !important; min-height:46px !important; height:46px !important; line-height:1 !important; display:flex !important; align-items:center !important; justify-content:center !important; box-sizing:border-box; white-space:nowrap;\">Esci<\/button>\n                        <button type=\"button\" id=\"ti-profile-cancel-btn\" onclick=\"window.cancelProfile()\" class=\"ti-btn ti-profile-btn-eq\" style=\"width:100%; background:#b91c1c !important; color:#fff !important; font-size:15px; font-weight:bold; padding:0 12px !important; min-height:46px !important; height:46px !important; line-height:1 !important; display:flex !important; align-items:center !important; justify-content:center !important; box-sizing:border-box; white-space:nowrap;\">Cancella<\/button>\n                    <\/div>\n                <\/form>\n            <\/div>\n        <\/div>\n\n\n        <div id=\"ti-report-ov\" class=\"ti-ov ti-closable-ov\" style=\"z-index:1002 !important;\">\n            <div class=\"ti-modal\" style=\"width:95%;max-width:1200px;text-align:left;\">\n                <h3 id=\"ti-report-title\" style=\"color:#fff;margin-top:0;border-bottom:1px solid #374151;padding-bottom:10px;\">\ud83d\udcca Report Attivit\u00e0<\/h3>\n                <div id=\"ti-report-cnt\"><\/div>\n                <div style=\"display:flex;gap:8px;margin-top:12px;\">\n                    <button type=\"button\" class=\"ti-btn btn-close\" style=\"flex:1;background:#4b5563;\" onclick=\"window.closeModal('ti-report-ov')\">Chiudi<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-global-action-ov\" class=\"ti-ov ti-closable-ov\">\n            <div class=\"ti-modal\" style=\"width:90%;max-width:500px;\">\n                <h3 style=\"color:#fff;margin-top:0;\">\ud83e\ude84 Modifica Globale Database<\/h3>\n                <p style=\"font-size:12px; color:#aaa; margin-bottom:10px;\">Chiedi all'AI di modificare dati in <b>TUTTE le ditte esistenti<\/b> contemporaneamente.<\/p>\n                <div style=\"display:flex;gap:6px;align-items:stretch;margin-bottom:10px;\">\n                    <textarea id=\"global-action-prompt\" class=\"ti-in\" rows=\"3\" placeholder=\"Es: Elimina la parola Terni dai nomi delle ditte...\" style=\"flex:1;margin:0;min-width:0;\"><\/textarea>\n                    <button type=\"button\" id=\"global-action-template-btn\" class=\"ti-btn\" title=\"Scrivi comando: Adegua struttura database a template shopservicemain.json\" style=\"width:58px;min-width:58px;padding:6px 4px;background:#0f766e;color:#fff;font-size:10px;line-height:1.1;font-weight:800;white-space:normal;\" onclick=\"var el=document.getElementById('global-action-prompt'); if(el){el.value='Adegua struttura database a template shopservicemain.json'; el.focus(); try{el.dispatchEvent(new Event('input',{bubbles:true}));}catch(e){} try{el.dispatchEvent(new Event('change',{bubbles:true}));}catch(e){} } return false;\">Template<\/button>\n                <\/div>\n                <button type=\"button\" id=\"global-action-btn\" class=\"ti-btn\" style=\"width:100%;background:#8b5cf6;padding:10px;font-weight:bold;margin-bottom:10px;\" onclick=\"window.doGlobalAction()\">Esegui Modifica su Tutti i DB<\/button>\n                <div id=\"global-action-progress-wrap\" style=\"display:none; margin-bottom:10px;\">\n                    <div style=\"height:12px; background:#1f2937; border:1px solid #374151; border-radius:999px; overflow:hidden;\">\n                        <div id=\"global-action-progress-bar\" style=\"width:0%; height:100%; background:linear-gradient(90deg,#8b5cf6,#38bdf8);\"><\/div>\n                    <\/div>\n                    <div id=\"global-action-progress-text\" style=\"margin-top:6px; font-size:12px; color:#cbd5e1;\">0% - In attesa di avvio...<\/div>\n                <\/div>\n                <div id=\"global-action-res\" style=\"display:none; text-align:left; background:#222; padding:10px; border-radius:8px; border:1px solid #444; color:#10b981; font-size:12px; margin-bottom:10px; max-height:150px; overflow-y:auto; white-space:pre-wrap;\"><\/div>\n                <button type=\"button\" id=\"ti-clean-orphans-global-btn\" class=\"ti-btn\" style=\"display:none;width:100%;background:#7c2d12;color:#fff;border:1px solid #9a3412;font-size:11px;margin-bottom:10px;padding:10px;font-weight:bold;\" onclick=\"if(window.runGlobalOrphanFileCleanup) window.runGlobalOrphanFileCleanup(); return false;\">\ud83e\uddf9 <b>Pulisci file non associati - Globale<\/b><\/button>\n                <button type=\"button\" class=\"ti-btn btn-close\" style=\"width:100%;background:#4b5563;\" onclick=\"window.closeModal('ti-global-action-ov')\">Chiudi<\/button>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-up-choice-ov\" class=\"ti-ov ti-closable-ov\">\n            <div class=\"ti-modal\" style=\"width:min(46vw,410px);max-width:min(46vw,410px);min-width:280px;\">\n                <h3 style=\"color:#fff;margin-top:0;\">Carica Immagine\/File<\/h3>\n                <button type=\"button\" class=\"ti-btn\" style=\"width:100%;margin-bottom:10px;background:#2563eb;padding:12px;font-size:14px;\" onclick=\"window.chooseLocal()\">\ud83d\udcc1 Carica dal Dispositivo<\/button>\n                <button type=\"button\" class=\"ti-btn\" style=\"width:100%;margin-bottom:10px;background:#059669;padding:12px;font-size:14px;\" onclick=\"window.chooseAI()\">\ud83e\udd16 Cerca \/ Genera con AI<\/button>\n                <button type=\"button\" class=\"ti-btn btn-close\" style=\"width:100%;background:#4b5563;padding:10px;\" onclick=\"window.closeModal('ti-up-choice-ov')\">Annulla<\/button>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-ai-gen-ov\" class=\"ti-ov ti-closable-ov\" style=\"z-index: 1002 !important;\">\n            <div class=\"ti-modal\" style=\"width:90%;max-width:500px;\">\n                <h3 style=\"color:#fff;margin-top:0;\">\ud83e\udd16 AI Image Generator<\/h3>\n                <textarea id=\"ai-gen-prompt\" class=\"ti-in\" rows=\"3\" placeholder=\"Cosa ti serve? (es. Un logo per una pizzeria con forno a legna)...\"><\/textarea>\n                <div style=\"font-size:12px;color:#cbd5e1;margin:8px 0 6px 0;\">Istruzioni di dettaglio per la AI (opzionale). Esempio: <i>stile elegante, sfondo chiaro, inquadratura ravvicinata, foto realistica<\/i>.<\/div>\n                <textarea id=\"ai-gen-detail\" class=\"ti-in\" rows=\"3\" placeholder=\"Indica eventuali istruzioni di dettaglio per la generazione della foto\"><\/textarea>\n                <button type=\"button\" id=\"ai-gen-btn\" class=\"ti-btn\" style=\"width:100%;background:#8b5cf6;padding:10px;font-weight:bold;margin-bottom:10px;\" onclick=\"window.doAIGen()\">Genera Immagine<\/button>\n                <div id=\"ai-gen-res\" style=\"display:none; text-align:center;\">\n                    <img decoding=\"async\" id=\"ai-gen-img\" src=\"\" style=\"max-width:100%;border-radius:8px;margin-bottom:10px;max-height:300px;object-fit:contain;\">\n                    <button type=\"button\" class=\"ti-btn\" style=\"width:100%;background:#10b981;padding:10px;font-weight:bold;margin-bottom:5px;\" onclick=\"window.useAIGen()\">\u2705 Usa questa Immagine<\/button>\n                    <button type=\"button\" class=\"ti-btn\" style=\"width:100%;background:#2563eb;padding:8px;\" onclick=\"window.resetAIGen()\">\ud83d\udd04 Riprova<\/button>\n                <\/div>\n                <button type=\"button\" class=\"ti-btn btn-close\" style=\"width:100%;background:#4b5563;margin-top:10px;\" onclick=\"window.closeModal('ti-ai-gen-ov')\">Chiudi<\/button>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-col-help-ov\" class=\"ti-ov ti-closable-ov\">\n            <div class=\"ti-modal\" style=\"width:90%;max-width:500px;text-align:left;\">\n                <h3 id=\"col-help-title\" style=\"color:#fff;margin-top:0;border-bottom:1px solid #444;padding-bottom:8px;\">Aiuto Colonna<\/h3>\n                <div id=\"col-help-company\" style=\"display:none;text-align:center;color:#e5e7eb;font-size:12px;line-height:1.35;margin:-4px 0 10px 0;padding-bottom:10px;border-bottom:1px solid #374151;\"><\/div>\n                <div id=\"col-help-content\" style=\"background:#222; padding:15px; border-radius:8px; border:1px solid #444; color:#ccc; font-size:13px; min-height:80px; max-height:300px; overflow-y:auto; white-space:pre-wrap;\">\n                    \u23f3 Cerco nel manuale...\n                <\/div>\n                <div style=\"display:flex;gap:8px;margin-top:15px;flex-wrap:wrap;\">\n                    <button type=\"button\" id=\"ti-col-help-copy\" onclick=\"window.copyColumnHelpText357 && window.copyColumnHelpText357()\" class=\"ti-btn\" style=\"flex:1;min-width:120px;background:#2563eb;color:#fff;font-size:11px;padding:8px 10px;\">Copia testo<\/button>\n                    <button type=\"button\" onclick=\"window.closeModal('ti-col-help-ov')\" class=\"ti-btn btn-close\" style=\"flex:1;min-width:120px;background:#4b5563;\">Chiudi<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-cam-ov\" class=\"ti-ov ti-closable-ov\"><div class=\"ti-modal\" style=\"width:90%;max-width:500px\">\n            <h3 id=\"lbl-cam-title\" style=\"color:#fff\">Fotocamera<\/h3>\n            <video id=\"ti-video-feed\" style=\"width:100%;border-radius:8px;background:#000\" autoplay playsinline><\/video>\n            <canvas id=\"ti-canvas-snap\" style=\"display:none\"><\/canvas>\n            <button type=\"button\" id=\"lbl-cam-snap\" class=\"ti-btn\" style=\"width:100%;margin-top:10px;background:#10b981\" onclick=\"window.snapPhoto()\">Scatta Foto<\/button>\n            <button type=\"button\" onclick=\"window.closeCamModal()\" class=\"ti-btn btn-close\" style=\"width:100%;margin-top:5px;background:#b91c1c\">Chiudi<\/button>\n        <\/div><\/div>\n\n        <div id=\"ti-ateco-ov\" class=\"ti-ov ti-closable-ov\"><div class=\"ti-modal\" style=\"width:90%;max-width:500px;\">\n            <h3 style=\"color:#fff; margin-top:0;\">Ricerca Codice ATECO<\/h3>\n            <p style=\"font-size:12px; color:#aaa;\">Descrivi la tua attivit\u00e0 e l'IA trover\u00e0 i codici pi\u00f9 adatti.<\/p>\n            <div style=\"display:flex; gap:5px; margin-bottom:10px;\">\n                <input type=\"text\" id=\"ateco-in\" class=\"ti-in\" style=\"margin:0; padding:8px;\" placeholder=\"Es: Pizzeria da asporto\">\n                <button type=\"button\" class=\"ti-btn\" style=\"background:#2563eb;\" onclick=\"window.searchAteco()\">Cerca<\/button>\n            <\/div>\n            <input type=\"hidden\" id=\"ateco-target-id\" value=\"\">\n            <div id=\"ateco-res\" style=\"background:#222; padding:10px; border-radius:8px; border:1px solid #444; color:#ccc; font-size:12px; min-height:60px; max-height:200px; overflow-y:auto; text-align:left;\">\n                <i>Risultati della ricerca...<\/i>\n            <\/div>\n            <button type=\"button\" onclick=\"window.closeModal('ti-ateco-ov')\" class=\"ti-btn btn-close\" style=\"width:100%; margin-top:15px; background:#4b5563;\">Chiudi<\/button>\n        <\/div><\/div>\n\n        <div id=\"ti-kbd-ov\" class=\"ti-ov ti-closable-ov\"><div class=\"ti-modal ti-kbd-modal\" style=\"width:96vw; max-width:760px;\"><h3 id=\"lbl-kbd\" style=\"color:#fff;\">Tastiera<\/h3><input type=\"text\" id=\"ti-kbd-prev\" class=\"ti-in\" style=\"font-size:18px; margin-bottom:10px; background:#000;\" readonly><div id=\"ti-kbd-cnt\" class=\"ti-kbd\"><\/div><div style=\"display:flex; gap:5px; margin-top:10px;\"><button type=\"button\" id=\"ti-kbd-back\" class=\"ti-btn\" style=\"flex:1; background:#b91c1c;\">\u232b<\/button><button type=\"button\" id=\"ti-kbd-ok\" class=\"ti-btn\" style=\"flex:1; background:#059669;\">OK<\/button><button type=\"button\" onclick=\"window.closeModal('ti-kbd-ov')\" class=\"ti-btn btn-close\" style=\"flex:1;\">Chiudi<\/button><\/div><\/div><\/div>\n        \n        <div id=\"ti-help-ov\" class=\"ti-ov ti-closable-ov\"><div class=\"ti-modal\" style=\"text-align:left;\">\n            <h3 id=\"help-title\" style=\"color:#fff; border-bottom:1px solid #555; padding-bottom:10px; margin-top:0;\">Shop & Service<\/h3>\n            <div id=\"ti-help-company\" class=\"ti-help-company-main\" style=\"display:none;text-align:center;color:#e5e7eb;font-size:12px;line-height:1.38;margin:0 0 10px 0;padding:0 0 10px 0;border-bottom:1px solid #374151;\"><\/div>\n            <p style=\"color:#ccc; font-size:13px; margin:5px 0;\"><b>Versione:<\/b> <span id=\"help-ver\"><\/span><\/p>\n            <div id=\"help-email\" style=\"margin-bottom:15px;\"><\/div><div style=\"font-size:12px;color:#9ca3af;margin-bottom:10px;\">Nota manuale AI: tutti i campi Istruzioni nel database sono istruzioni operative dedicate alla AI e devono essere eseguite quando applicabili.<\/div><hr style=\"border:0; border-top:1px solid #444; margin:10px 0;\">\n            <p style=\"color:#38bdf8; font-size:12px; margin-bottom:5px;\"><b>Chiedi all'Assistente Shop & Service:<\/b><\/p>\n            <div style=\"display:flex; gap:5px; margin-bottom:10px;\">\n                <input type=\"text\" id=\"help-bot-in\" class=\"ti-in\" style=\"margin:0; padding:8px;\" placeholder=\"Es: Come prenoto?\">\n                <button type=\"button\" id=\"help-bot-send\" class=\"ti-btn\" style=\"background:#2563eb;\" onclick=\"window.sendHelpChat()\">\u279c<\/button>\n            <\/div>\n            <div id=\"help-bot-reply\" style=\"background:#222; padding:10px; border-radius:8px; border:1px solid #444; margin-bottom:12px; color:#ccc; font-size:12px; min-height:40px; max-height:150px; overflow-y:auto;\">\n                <i>Scrivi la tua domanda qui sopra per ricevere assistenza AI...<\/i>\n            <\/div>\n            <button type=\"button\" id=\"ti-help-support-toggle\" class=\"ti-btn\" style=\"width:100%; background:#0f766e; margin-bottom:10px;\">\ud83d\udee0\ufe0f Segnalazione assistenza tecnica<\/button>\n            <div id=\"ti-help-support-box\" style=\"display:none; margin-bottom:14px; background:#0b1220; border:1px solid #0f766e; border-radius:10px; padding:12px;\">\n                <div style=\"font-size:12px;color:#cbd5e1;margin-bottom:8px;\">Invia una segnalazione all\u2019assistenza tecnica. Se disponibile, verr\u00e0 allegata anche la copia dell\u2019ultima chat in corso.<\/div>\n                <label for=\"ti-support-email\" style=\"display:block;font-size:12px;color:#cbd5e1;margin-bottom:4px;\">Email mittente (opzionale)<\/label>\n                <input type=\"email\" id=\"ti-support-email\" class=\"ti-in\" style=\"margin:0 0 8px 0;\" placeholder=\"nome@dominio.it\">\n                <label for=\"ti-support-subject\" style=\"display:block;font-size:12px;color:#cbd5e1;margin-bottom:4px;\">Oggetto *<\/label>\n                <input type=\"text\" id=\"ti-support-subject\" class=\"ti-in\" style=\"margin:0 0 8px 0;\" placeholder=\"Oggetto della segnalazione\">\n                <label for=\"ti-support-message\" style=\"display:block;font-size:12px;color:#cbd5e1;margin-bottom:4px;\">Segnalazione *<\/label>\n                <textarea id=\"ti-support-message\" class=\"ti-in\" rows=\"5\" style=\"margin:0 0 8px 0;\" placeholder=\"Descrivi il problema o il suggerimento\"><\/textarea>\n                <div style=\"font-size:11px;color:#94a3b8;margin-bottom:10px;\">Destinatario: <b>Shop & Service<\/b>. In copia verr\u00e0 usata la tua e.mail se indicata o rilevata.<\/div>\n                <button type=\"button\" id=\"ti-support-send\" class=\"ti-btn\" style=\"width:100%; background:#2563eb;\">Invia segnalazione<\/button>\n            <\/div>\n            <div style=\"display:flex; gap:10px;\">\n                <a href=\"https:\/\/www.targetinformatica.it\/wp-content\/uploads\/AI-data\/AI-docs\/Shop-service\/Shop-Service%20Manual.pdf\" target=\"_blank\" class=\"ti-btn\" style=\"flex:1; text-align:center; background:#059669; color:#fff; text-decoration:none; line-height:24px; font-size:13px;\" id=\"btn-manual\">\ud83d\udcc4 MANUALE PDF<\/a>\n                <button type=\"button\" onclick=\"window.closeModal('ti-help-ov')\" class=\"ti-btn btn-close\" style=\"flex:1; background:#4b5563;\">Chiudi<\/button>\n            <\/div>\n        <\/div><\/div>\n\n        <div id=\"ti-img-ov\" class=\"ti-ov ti-closable-ov\" onclick=\"window.closeModal('ti-img-ov')\" style=\"z-index: 2147483647 !important;\">\n            <div class=\"ti-modal\" style=\"background:transparent; border:none; box-shadow:none; padding:0; width:auto; max-height:100%;\">\n                <img decoding=\"async\" id=\"ti-full-img\" style=\"max-width:95vw; max-height:90vh; border-radius:8px; box-shadow:0 0 20px rgba(0,0,0,0.8);\" src=\"\" loading=\"lazy\">\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-order-item-ov\" class=\"ti-ov ti-closable-ov\" style=\"z-index:2147483647 !important;\">\n            <div class=\"ti-modal\" style=\"width:92vw;max-width:820px;\">\n                <h3 id=\"ti-order-item-title\" style=\"color:#fff;margin-top:0;margin-bottom:14px;\">Dettaglio voce<\/h3>\n                <div class=\"ti-order-modal-grid\">\n                    <div><img decoding=\"async\" id=\"ti-order-item-img\" class=\"ti-order-modal-img\" src=\"\" loading=\"lazy\"><\/div>\n                    <div>\n                        <div id=\"ti-order-item-name\" style=\"font-size:18px;font-weight:800;color:#fff;margin-bottom:8px;\"><\/div>\n                        <div id=\"ti-order-item-caption\" class=\"ti-order-modal-caption\"><\/div>\n                        <div class=\"ti-order-modal-line\">Prezzo: <span id=\"ti-order-item-price\"><\/span><\/div>\n                        <div class=\"ti-order-modal-line\">Importo: <b id=\"ti-order-item-total\"><\/b><\/div>\n                        <div class=\"ti-order-modal-line\">Quantit\u00e0: <span class=\"ti-qty-stepper\"><button type=\"button\" class=\"ti-qty-btn\" onclick=\"window.stepQtyInput(this,-1)\">-<\/button><input type=\"text\" inputmode=\"decimal\" id=\"ti-order-item-qty\" class=\"ti-in\" style=\"max-width:140px;display:inline-block;margin-left:0;\"><button type=\"button\" class=\"ti-qty-btn\" onclick=\"window.stepQtyInput(this,1)\">+<\/button><\/span><\/div>\n                        <div id=\"ti-order-item-files\" class=\"ti-order-modal-files\" style=\"display:none;\"><\/div>\n                        <div class=\"ti-order-modal-actions\">\n                            <button type=\"button\" class=\"ti-btn ti-btn-ok\" style=\"flex:1;\" onclick=\"window.confirmOrderItemPopup()\">OK<\/button>\n                            <button type=\"button\" class=\"ti-btn btn-close\" style=\"flex:1;\" onclick=\"window.closeOrderItemPopup()\">Esci<\/button>\n                        <\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <input type=\"file\" id=\"ti-cam-in\" accept=\"image\/*\" capture=\"environment\" style=\"display:none;\" onchange=\"window.handleFileSelect(event)\">\n        <input type=\"file\" id=\"ti-file-in\" class=\"ti-in\" style=\"display:none;\" data-target=\"\" onchange=\"window.handleFileSelect(event)\">\n    <\/div>\n<script>\n\n\n    \/* 30.9.181 - Guard localStorage: evita blocchi JS se browser\/cache lo nega *\/\n    (function(){\n        try {\n            var t = window.localStorage;\n            if (t) { var k='__ti_ss_ls_test__'; t.setItem(k,'1'); t.removeItem(k); }\n        } catch(e) {\n            try {\n                var mem = {};\n                Object.defineProperty(window, 'localStorage', { configurable:true, value:{\n                    getItem:function(k){ return Object.prototype.hasOwnProperty.call(mem,k) ? mem[k] : null; },\n                    setItem:function(k,v){ mem[k] = String(v); },\n                    removeItem:function(k){ delete mem[k]; },\n                    clear:function(){ mem = {}; },\n                    key:function(i){ return Object.keys(mem)[i] || null; },\n                    get length(){ return Object.keys(mem).length; }\n                }});\n            } catch(ignore) {}\n        }\n    })();\n\n    \/\/ Protezione pagina Shop & Service da errori ripetuti di script esterni di navigazione.\n    \/\/ navigazione.js puo' usare .style su elementi non presenti nella pagina Shop & Service:\n    \/\/ il guard evita che l'errore sporchi la console o interrompa le funzioni del plugin.\n    (function installTiNavigationJsSafetyNet() {\n        if (window.__tiNavigationJsSafetyNetInstalled) return;\n        window.__tiNavigationJsSafetyNetInstalled = true;\n\n        function isNavigationStyleNullError(message, source) {\n            message = String(message || '');\n            source = String(source || '');\n            return \/(?:^|\\\/)navigazione\\.js(?:\\?|$)\/i.test(source)\n                && \/Cannot read properties of null\/i.test(message)\n                && \/\\bstyle\\b\/i.test(message);\n        }\n\n        const previousOnError = window.onerror;\n        window.onerror = function(message, source, lineno, colno, error) {\n            if (isNavigationStyleNullError(message, source)) return true;\n            if (typeof previousOnError === 'function') {\n                return previousOnError.apply(this, arguments);\n            }\n            return false;\n        };\n\n        window.addEventListener('error', function(evt) {\n            const message = evt && (evt.message || (evt.error && evt.error.message));\n            const source = evt && (evt.filename || (evt.error && evt.error.fileName));\n            if (isNavigationStyleNullError(message, source)) {\n                if (evt.preventDefault) evt.preventDefault();\n                if (evt.stopImmediatePropagation) evt.stopImmediatePropagation();\n                return true;\n            }\n        }, true);\n\n        function isSafeIgnoredScrollError(error) {\n            const message = error && error.message ? String(error.message) : String(error || '');\n            return \/Cannot read properties of null\/i.test(message) && \/\\bstyle\\b\/i.test(message);\n        }\n\n        function wrapWindowOnScroll() {\n            const handler = window.onscroll;\n            if (typeof handler !== 'function' || handler.__tiNavigationSafeWrapped) return;\n            const wrapped = function() {\n                try {\n                    return handler.apply(this, arguments);\n                } catch (error) {\n                    if (isSafeIgnoredScrollError(error)) return false;\n                    throw error;\n                }\n            };\n            wrapped.__tiNavigationSafeWrapped = true;\n            wrapped.__tiOriginalScrollHandler = handler;\n            window.onscroll = wrapped;\n        }\n\n        wrapWindowOnScroll();\n        document.addEventListener('DOMContentLoaded', wrapWindowOnScroll);\n        window.addEventListener('load', wrapWindowOnScroll);\n        let navGuardAttempts = 0;\n        const navGuardTimer = window.setInterval(function() {\n            wrapWindowOnScroll();\n            navGuardAttempts++;\n            if (navGuardAttempts >= 30) window.clearInterval(navGuardTimer);\n        }, 500);\n    })();\n\n    \/\/ Inizializzazione variabili globali sicura\n    window.gE = function(id) { return document.getElementById(id); };\n    const gE = window.gE;\n\n    window.tiIsMobileLike = function() {\n        try {\n            return !!(\n                (window.matchMedia && (window.matchMedia('(max-width: 900px)').matches || window.matchMedia('(pointer: coarse)').matches || window.matchMedia('(hover: none)').matches)) ||\n                ('ontouchstart' in window) ||\n                (navigator && navigator.maxTouchPoints && navigator.maxTouchPoints > 0)\n            );\n        } catch(e) {\n            return !!('ontouchstart' in window);\n        }\n    };\n\n    window.tiUrl = window.location.href.split('?')[0]; \n    window.tiAjaxUrl = \"https:\/\/www.tisoft.it\/wp-admin\/admin-ajax.php\";\n    window.updateMobileViewportHeight = function() {\n        try {\n            const vh = (window.visualViewport && window.visualViewport.height ? window.visualViewport.height : window.innerHeight) * 0.01;\n            const root = document.getElementById('ti-ai-outer') || document.documentElement;\n            if (root && root.style) root.style.setProperty('--ti-vh', vh + 'px');\n            if (document.documentElement && document.documentElement.style) document.documentElement.style.setProperty('--ti-vh', vh + 'px');\n            if (document.body && document.body.style) document.body.style.setProperty('--ti-vh', vh + 'px');\n        } catch(e) {}\n    };\n    try { window.updateMobileViewportHeight(); window.addEventListener('resize', window.updateMobileViewportHeight, {passive:true}); if (window.visualViewport) window.visualViewport.addEventListener('resize', window.updateMobileViewportHeight, {passive:true}); } catch(e) {}\n    window.tiForced = \"\"; \n    window.tiForcedActivityQr = \"\"; \n    window.tiUser = \"\"; \n    window.tiRole = \"\"; \n    window.tiUserFoto = \"\"; window.tiUserFotoDb = \"\"; window.tiSessionDb = \"\"; \n    window.tiUserDisplay = \"\"; \n    window.tiVersion = \"30.9.431\";\n    window.tiVersionDateTime = \"2026-05-24 23:55 CEST\";\n    window.tiProducerEmails = {\"info\":\"SSInfo@targetinformatica.it\",\"comm\":\"SSComm@targetinformatica.it\",\"support\":\"SSSupport@targetinformatica.it\"};\n    window.tiShopServiceActivationProductUrl = \"https:\\\/\\\/www.tisoft.it\\\/prodotto\\\/shop-service-attivazione\\\/\";\n    window.tiShopServiceMonthlyProductUrl = \"https:\\\/\\\/www.tisoft.it\\\/prodotto\\\/shop-service\\\/\";\n    window.tiConfiguratorAccessMessage = \"\";\n    window.tiServerPrefs = [];\n    window.currLang = localStorage.getItem('ti_lang_' + window.tiUser) || 'it';\n    \n        window.tiUploadBase = \"https:\/\/www.targetinformatica.it\/wp-content\/uploads\/AI-data\";\n    if (window.tiUploadBase.startsWith('\/')) window.tiUploadBase = window.location.origin + window.tiUploadBase; \n    window.tiUploadBase = window.tiUploadBase.replace(\/\\\/$\/, \"\");\n    window.tiSharedSetup = {\"shared_base_path\":\"\\\/home\\\/lqlo5s240ldu\\\/public_html\\\/wp-content\\\/uploads\\\/AI-data\\\/\",\"shared_base_url\":\"https:\\\/\\\/www.targetinformatica.it\\\/wp-content\\\/uploads\\\/AI-data\",\"effective_base_path\":\"\\\/home\\\/lqlo5s240ldu\\\/public_html\\\/wp-content\\\/uploads\\\/AI-data\\\/\",\"effective_base_url\":\"https:\\\/\\\/www.targetinformatica.it\\\/wp-content\\\/uploads\\\/AI-data\",\"docs_base_path\":\"\\\/home\\\/lqlo5s240ldu\\\/public_html\\\/wp-content\\\/uploads\\\/AI-data\\\/AI-docs\\\/Shop-service\\\/\",\"docs_base_url\":\"https:\\\/\\\/www.targetinformatica.it\\\/wp-content\\\/uploads\\\/AI-data\\\/AI-docs\\\/Shop-service\",\"manual_url\":\"https:\\\/\\\/www.targetinformatica.it\\\/wp-content\\\/uploads\\\/AI-data\\\/AI-docs\\\/Shop-service\\\/Shop-Service%20Manual.pdf\",\"default_base_path\":\"\\\/home\\\/lqlo5s240ldu\\\/public_html\\\/tisoft.it\\\/wp-content\\\/uploads\\\/AI-data\\\/\",\"default_base_url\":\"https:\\\/\\\/www.tisoft.it\\\/wp-content\\\/uploads\\\/AI-data\",\"target_url_example\":\"https:\\\/\\\/www.targetinformatica.it\\\/wp-content\\\/uploads\\\/AI-data\\\/AI-docs\\\/Shop-service\\\/\"};\n\n    window.currentDbData = null; window.configOriginalDbData = null; window.pendingEmail = false; window.lastUploadedFile = null; window.configBotFile = null; window.lastFileContext = null; window.currentDittaName = \"Ditta\"; window.isMuted = false; window.stream = null; window.sortMode = 'AZ'; window.ditteData = []; window.modifiedFields = new Set(); window.tiConfigDeletedRowsManifest = []; window.targetUploadContext = null; window.currentGenFilename = null; window.configWasSaved = false; window.tiQuickReplyText = ''; window.tiHiddenSendText = ''; window.tiHiddenSendLabel = '';\n    window.tiCurrentSelectedDb = window.tiForced || localStorage.getItem('ti_saved_db') || window.tiSessionDb || '';\n\n    window.getAlertPlainText = function() {\n        const el = gE('ti-alert-msg');\n        if (!el) return '';\n        return String(el.innerText || el.textContent || '').trim();\n    };\n    window.copyAlertText = function() {\n        const text = window.getAlertPlainText();\n        if (!text) return;\n        const done = function(ok) {\n            const btn = gE('ti-alert-copy');\n            if (!btn) return;\n            const old = btn.innerText || 'Copia';\n            btn.innerText = ok ? 'Copiato' : 'Errore copia';\n            setTimeout(function(){ btn.innerText = old; }, 1200);\n        };\n        if (navigator.clipboard && navigator.clipboard.writeText) {\n            navigator.clipboard.writeText(text).then(function(){ done(true); }).catch(function(){ done(false); });\n            return;\n        }\n        try {\n            const ta = document.createElement('textarea');\n            ta.value = text;\n            ta.style.position = 'fixed';\n            ta.style.left = '-9999px';\n            document.body.appendChild(ta);\n            ta.focus(); ta.select();\n            const ok = document.execCommand('copy');\n            document.body.removeChild(ta);\n            done(ok);\n        } catch(e) { done(false); }\n    };\n    window.normalizeConfigStateValue = function(value) {\n        const raw = String(value == null ? '' : value).trim();\n        if (!raw) return '';\n        const v = raw.toLowerCase().normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').replace(\/\\s+\/g, ' ');\n        if (['operativo','operativa','a regime','regime','canone attivo','servizio attivo','abbonamento attivo'].includes(v)) return 'Operativo';\n        if (['attivo','attiva','attivato','abilitato','abilitata','enabled','si','s\u00ec','ok','richiesto','richiesta','registrato','registrata','completato','completata','completo','confermato','confermata'].includes(v)) return 'Attivo';\n        if (['demo','dimostrativa','dimostrativo','dimostrazione','ambiente demo'].includes(v)) return 'Demo';\n            if (['configurazione','config','in configurazione','bozza','draft','prova','test'].includes(v)) return 'Configurazione';\n        if (['sospesa','sospeso','sospendere','inattivo','inattiva','disabilitato','disabilitata','disabled','chiuso','chiusa','non disponibile','pausa','in pausa'].includes(v)) return 'Sospesa';\n        if (['cancellato','cancellata','eliminato','eliminata','rimosso','rimossa','annullato','annullata'].includes(v)) return 'Cancellato';\n        return raw;\n    };\n    window.getCurrentCompanyStateFromSelection = function() {\n        const d = gE('ti-ditta');\n        if (d && d.selectedIndex >= 0 && d.options[d.selectedIndex]) {\n            const opt = d.options[d.selectedIndex];\n            const raw = opt.dataset.companyState || opt.dataset.stato || opt.dataset.status || '';\n            if (raw) return window.normalizeConfigStateValue(raw);\n            const statusCode = String(opt.dataset.status || '').toLowerCase();\n            if (statusCode === 'sospesa') return 'Sospesa';\n            if (statusCode === 'demo') return 'Demo';\n            if (statusCode === 'configurazione') return 'Configurazione';\n        }\n        if (window.currentDbData && window.currentDbData.Tabelle) {\n            const cfgName = Object.keys(window.currentDbData.Tabelle).find(k => String(k).toLowerCase() === 'config') || 'Config';\n            const cfgRows = window.currentDbData.Tabelle[cfgName];\n            const cfg = Array.isArray(cfgRows) ? (cfgRows[0] || {}) : {};\n            const key = Object.keys(cfg).find(k => String(k).toLowerCase() === 'stato') || 'Stato';\n            return window.normalizeConfigStateValue(cfg[key] || '');\n        }\n        return '';\n    };\n    window.getCompanyLimitedNoticeHtml = function(state) {\n        state = window.normalizeConfigStateValue(state || window.getCurrentCompanyStateFromSelection());\n        if (state === 'Demo') {\n            return '<div class=\"ti-company-state-warning\" style=\"margin:8px 0 10px 0;padding:10px 12px;border-radius:10px;background:#0f172a;border:1px solid #38bdf8;color:#fff;font-weight:700;line-height:1.35;\">\u26a0\ufe0f Stato ditta: Dimostrativa<br>La ditta risulta dimostrativa. Ordini e prenotazioni possono essere provati, ma non avranno esito effettivo dal punto di vista della fatturazione, pagamento, consegna prodotti od esecuzione di servizi. \u00c8 comunque possibile provare la navigazione e registrazione utente.<\/div>';\n        }\n        if (state === 'Sospesa') {\n            return '<div class=\"ti-company-state-warning\" style=\"margin:8px 0 10px 0;padding:10px 12px;border-radius:10px;background:#78350f;border:1px solid #f59e0b;color:#fff;font-weight:700;line-height:1.35;\">\u26a0\ufe0f Stato ditta: Sospesa<br>La ditta risulta sospesa. Ordini e prenotazioni possono essere provati, ma non avranno esito effettivo finch\u00e9 la ditta non torna Attiva.<\/div>';\n        }\n        if (state === 'Configurazione') {\n            return '<div class=\"ti-company-state-warning\" style=\"margin:8px 0 10px 0;padding:10px 12px;border-radius:10px;background:#78350f;border:1px solid #f59e0b;color:#fff;font-weight:700;line-height:1.35;\">\u26a0\ufe0f Stato ditta: Configurazione<br>La ditta risulta in configurazione. Ordini e prenotazioni possono essere provati, ma non avranno esito effettivo finch\u00e9 la configurazione non viene completata e la ditta non torna Attiva.<\/div>';\n        }\n        return '';\n    };\n    window.applyCompanyLimitedNotice = function() {\n        const companySub = gE('ti-company-sub');\n        if (!companySub) return;\n        const html = window.getCompanyLimitedNoticeHtml();\n        if (html) {\n            companySub.innerHTML = html;\n            companySub.style.display = 'block';\n        } else {\n            companySub.innerHTML = '';\n            companySub.style.display = 'none';\n        }\n    };\n\n    window.normalizeAllConfigStateValues = function() {\n        if (!window.currentDbData || !window.currentDbData.Tabelle) return;\n        for (const tName in window.currentDbData.Tabelle) {\n            const rows = Array.isArray(window.currentDbData.Tabelle[tName]) ? window.currentDbData.Tabelle[tName] : Object.values(window.currentDbData.Tabelle[tName] || {});\n            rows.forEach(function(row){\n                if (!row || typeof row !== 'object') return;\n                Object.keys(row).forEach(function(k){\n                    const n = window.normalizeConfigRelationName ? window.normalizeConfigRelationName(k) : String(k || '').toLowerCase();\n                    if (n === 'stato' || n === 'status') {\n                        const canon = window.normalizeConfigStateValue(row[k]);\n                        if (['Attivo','Operativo','Demo','Configurazione','Sospesa','Cancellato'].includes(canon) && row[k] !== canon) row[k] = canon;\n                    }\n                });\n            });\n        }\n    };\n    window.updateConfigSaveButtonState = function() {\n        const btn = gE('lbl-conf-save');\n        if (!btn) return;\n        const dirty = !!(window.modifiedFields && window.modifiedFields.size > 0);\n        btn.classList.toggle('ti-save-dirty', dirty);\n        btn.classList.toggle('ti-save-clean', !dirty);\n        if (!window.__saveConfigInFlight) btn.innerText = dirty ? 'Salva Modifiche *' : 'Salva Modifiche';\n    };\n    window.markConfigDirty = function() {\n        if (!window.modifiedFields) window.modifiedFields = new Set();\n        window.updateConfigSaveButtonState && window.updateConfigSaveButtonState();\n    };\n    if (!window.__configSaveButtonWatcher) {\n        window.__configSaveButtonWatcher = setInterval(function(){ if (gE('lbl-conf-save')) window.updateConfigSaveButtonState(); }, 700);\n    }\n\n\n    const dict = { \n        it: { confirm: \"CONFERMA ORDINE\", restartConfirm: \"Vuoi chiudere e ripartire?\", muteOn: \"\ud83d\udd0a ON\", muteOff: \"\ud83d\udd07 OFF\", ph: \"Scrivi qui...\", send: \"INVIA\", new: \"Nuova\", copy: \"Copia testo\", file: \"File\", products: \"Prodotti\", services: \"Servizi\", activities: \"Attivit\u00e0\", kbd: \"Tastiera\", mic: \"Mic\", cam: \"Cam\", login: \"Accedi\" }, \n        en: { confirm: \"CONFIRM ORDER\", restartConfirm: \"Close session and restart from scratch?\", muteOn: \"\ud83d\udd0a ON\", muteOff: \"\ud83d\udd07 OFF\", ph: \"Type here...\", send: \"SEND\", new: \"New\", copy: \"Copy text\", file: \"File\", products: \"Products\", services: \"Services\", activities: \"Activities\", kbd: \"Keyboard\", mic: \"Mic\", cam: \"Cam\", login: \"Login\" } \n    };\n\n\n    \/* 30.9.274 - Motore traduzione layout: il tasto EN traduce label, placeholder, pulsanti, comunicazioni e popup dinamici. *\/\n    window.tiLayoutTranslationsEn = {\n        \"Elaborazione AI in corso...\": \"AI processing in progress...\",\n        \"Attendi, l'operazione potrebbe richiedere alcuni secondi.\": \"Please wait, the operation may take a few seconds.\",\n        \"Avanzamento stimato\": \"Estimated progress\",\n        \"Esci\": \"Exit\",\n        \"Copia testo\": \"Copy text\",\n        \"SI\": \"YES\",\n        \"S\u00cc\": \"YES\",\n        \"OK\": \"OK\",\n        \"Comunicazione\": \"Communication\",\n        \"Chiudi\": \"Close\",\n        \"Ordina\": \"Sort\",\n        \"Mostra anche le ditte sospese\": \"Show suspended companies too\",\n        \"Mostra solo le ditte demo\": \"Show demo companies only\",\n        \"Clic successivo: mostra solo le ditte demo\": \"Next click: show only demo companies\",\n        \"Cerca ditta o attivit\u00e0\": \"Search company or activity\",\n        \"-- Scegli una ditta --\": \"-- Choose a company --\",\n        \"Accedi\": \"Login\",\n        \"Cliente\/Utente\": \"Customer\/User\",\n        \"Sosp\": \"Susp\",\n        \"Sosp ON\": \"Susp ON\",\n        \"Demo\": \"Demo\",\n        \"Accesso diretto Shop & Service\": \"Direct Shop & Service access\",\n        \"Accesso diretto Shop & Service fallback\": \"Direct Shop & Service fallback access\",\n        \"fallback\": \"fallback\",\n        \"Usa questo accesso solo se il login principale non procede o restituisce errore.\": \"Use this access only if the main login does not proceed or returns an error.\",\n        \"Username\": \"Username\",\n        \"Password\": \"Password\",\n        \"Ditta\": \"Company\",\n        \"Login\": \"Login\",\n        \"Non hai un account? Registrati\": \"No account? Register\",\n        \"Annulla\": \"Cancel\",\n        \"Nuova\": \"New\",\n        \"Nuova ditta\": \"New company\",\n        \"Crea nuova ditta\": \"Create new company\",\n        \"Crea Nuova Ditta\": \"Create New Company\",\n        \"Nome della nuova Ditta*\": \"New company name*\",\n        \"Primo Utente (Configuratore)\": \"First User (Configurator)\",\n        \"Email (Opzionale)\": \"Email (Optional)\",\n        \"Crea e Accedi\": \"Create and Login\",\n        \"Alla fine del processo di creazione della ditta, verr\u00e0 generato l'archivio e farai l'accesso automatico a te riservato come configuratore.\": \"At the end of the company creation process, the archive will be generated and you will be automatically logged in with your reserved configurator access.\",\n        \"Il servizio sar\u00e0 GRATUITO di prova per 30 giorni, poi potrai eventualmente attivare la ditta alle condizioni commerciali previste per il servizio Shop & Service disponibili direttamente durante la configurazione ed il periodo di prova.\": \"The service will be FREE for a 30-day trial. Afterwards, you can activate the company under the commercial conditions provided for the Shop & Service service, available directly during configuration and the trial period.\",\n        \"Buon lavoro !!\": \"Enjoy your work !!\",\n        \"Ragione Sociale\": \"Company name\",\n        \"Ragione sociale\": \"Company name\",\n        \"Indirizzo\": \"Address\",\n        \"Telefono\": \"Phone\",\n        \"Codice Fiscale\": \"Tax code\",\n        \"Codice ADE \/ SDI\": \"ADE \/ SDI code\",\n        \"Sesso\": \"Gender\",\n        \"Et\u00e0\": \"Age\",\n        \"Eta\": \"Age\",\n        \"Dato 1\": \"Data 1\",\n        \"Dato 2\": \"Data 2\",\n        \"Dato 3\": \"Data 3\",\n        \"Utente (Nome Cognome)*\": \"User (First name Last name)*\",\n        \"Username (Accesso)*\": \"Username (Login)*\",\n        \"Password*\": \"Password*\",\n        \"Email\": \"Email\",\n        \"Salva\": \"Save\",\n        \"Salva profilo\": \"Save profile\",\n        \"Profilo\": \"Profile\",\n        \"Report attivit\u00e0\": \"Activity report\",\n        \"Report attivita\": \"Activity report\",\n        \"Modifica Globale Database\": \"Global Database Edit\",\n        \"Istruzioni AI configurazione\": \"AI configuration instructions\",\n        \"Impostazioni\": \"Settings\",\n        \"Configura\": \"Configure\",\n        \"CONFIGURA\": \"CONFIGURE\",\n        \"Acquisti\": \"Purchases\",\n        \"ACQUISTI\": \"PURCHASES\",\n        \"Globale\": \"Global\",\n        \"GLOBALE\": \"GLOBAL\",\n        \"Stato dell\\\\'azienda: Demo\": \"Company status: Demo\",\n        \"Stato dell'azienda: Demo\": \"Company status: Demo\",\n        \"Stato ditta: Demo\": \"Company status: Demo\",\n        \"\u26a0\ufe0f Stato ditta: Demo\": \"\u26a0\ufe0f Company status: Demo\",\n        \"Salva Modifiche\": \"Save Changes\",\n        \"Salva Modifiche *\": \"Save Changes *\",\n        \"Salvataggio...\": \"Saving...\",\n        \"Salvataggio configurazione\": \"Saving configuration\",\n        \"Cancellazione definitiva configurazione\": \"Final configuration deletion\",\n        \"Verifica istruzioni\": \"Check instructions\",\n        \"Carica file AI\": \"Upload AI file\",\n        \"Carica file per AI\": \"Upload file for AI\",\n        \"Invia istruzione\": \"Send instruction\",\n        \"Pulisci\": \"Clear\",\n        \"Genera foto AI Prodotti\": \"Generate AI product photos\",\n        \"Genera foto AI Servizi\": \"Generate AI service photos\",\n        \"Carica file per AI\": \"Upload file for AI\",\n        \"Invia istruzione\": \"Send instruction\",\n        \"Pulisci\": \"Clear\",\n        \"Genera foto AI Prodotti\": \"Generate AI product photos\",\n        \"Genera foto AI Servizi\": \"Generate AI service photos\",\n        \"Genera foto AI\": \"Generate AI photos\",\n        \"Genera documenti AI\": \"Generate AI documents\",\n        \"Foto AI\": \"AI photos\",\n        \"Documenti AI\": \"AI documents\",\n        \"File\": \"File\",\n        \"Prodotti\": \"Products\",\n        \"Servizi\": \"Services\",\n        \"Tastiera\": \"Keyboard\",\n        \"Mic\": \"Mic\",\n        \"Cam\": \"Cam\",\n        \"Scrivi qui...\": \"Type here...\",\n        \"INVIA\": \"SEND\",\n        \"Invia\": \"Send\",\n        \"Invio in corso...\": \"Sending...\",\n        \"Invia segnalazione\": \"Send report\",\n        \"Segnalazione assistenza tecnica\": \"Technical support report\",\n        \"Email mittente (opzionale)\": \"Sender email (optional)\",\n        \"Oggetto *\": \"Subject *\",\n        \"Oggetto della segnalazione\": \"Report subject\",\n        \"Segnalazione *\": \"Report *\",\n        \"Descrivi il problema o il suggerimento\": \"Describe the issue or suggestion\",\n        \"Destinatario:\": \"Recipient:\",\n        \"In copia verr\u00e0 usata la tua e.mail se indicata o rilevata.\": \"Your email will be used in copy if provided or detected.\",\n        \"Versione:\": \"Version:\",\n        \"Nota manuale AI: tutti i campi Istruzioni nel database sono istruzioni operative dedicate alla AI e devono essere eseguite quando applicabili.\": \"AI manual note: all Instruction fields in the database are operational instructions dedicated to the AI and must be executed when applicable.\",\n        \"Chiedi all'Assistente Shop & Service:\": \"Ask the Shop & Service Assistant:\",\n        \"Es: Come prenoto?\": \"E.g.: How do I book?\",\n        \"Scrivi la tua domanda qui sopra per ricevere assistenza AI...\": \"Write your question above to receive AI assistance...\",\n        \"MANUALE PDF\": \"PDF MANUAL\",\n        \"Manuale\": \"Manual\",\n        \"Risultati della ricerca...\": \"Search results...\",\n        \"Cerca\": \"Search\",\n        \"Es: Pizzeria da asporto\": \"E.g.: Takeaway pizzeria\",\n        \"Dettaglio voce\": \"Item details\",\n        \"Prezzo:\": \"Price:\",\n        \"Importo:\": \"Amount:\",\n        \"Quantit\u00e0:\": \"Quantity:\",\n        \"Quantita:\": \"Quantity:\",\n        \"Q.t\u00e0:\": \"Qty:\",\n        \"Ordine confermato\": \"Order confirmed\",\n        \"Conferma ordine in corso\": \"Order confirmation in progress\",\n        \"Conferma ordine\": \"Confirm order\",\n        \"Chiudi conferma\": \"Close confirmation\",\n        \"Imponibile:\": \"Net amount:\",\n        \"IVA:\": \"VAT:\",\n        \"Totale lordo:\": \"Gross total:\",\n        \"CONFERMA ORDINE\": \"CONFIRM ORDER\",\n        \"Tabella\": \"Table\",\n        \"Prodotto o servizio\": \"Product or service\",\n        \"Tipo\": \"Type\",\n        \"Costo netto\": \"Net cost\",\n        \"Margine\": \"Margin\",\n        \"Prezzo vendita IVA incl.\": \"Sale price incl. VAT\",\n        \"IVA %\": \"VAT %\",\n        \"Tot. acquisto netto\": \"Total net purchase\",\n        \"Tot. vendita\": \"Total sale\",\n        \"Note\": \"Notes\",\n        \"Azioni\": \"Actions\",\n        \"Conferma righe acquisto\": \"Confirm purchase rows\",\n        \"Attiva Shop & Service\": \"Activate Shop & Service\",\n        \"Acquista attivazione Shop & Service\": \"Buy Shop & Service activation\",\n        \"Acquista canone servizio Shop & Service\": \"Buy Shop & Service service plan\",\n        \"Cancella ditta\": \"Delete company\",\n        \"Consigli operativi per configuratore\/amministratore\": \"Operational tips for the configurator\",\n        \"Manuale operativo Shop & Service\": \"Shop & Service operating manual\",\n        \"Non mostrare pi\u00f9 questo popup in futuro\": \"Do not show this popup again in the future\",\n        \"Popup consigli operativi configuratore\/amministratore disabilitato per i prossimi accessi.\": \"Configurator operational tips popup disabled for future access.\",\n        \"Popup consigli operativi configuratore\/amministratore riabilitato. Verr\u00e0 mostrato di nuovo ai prossimi accessi.\": \"Configurator operational tips popup re-enabled. It will be shown again on future access.\",\n        \"Mostra popup\": \"Show popup\",\n        \"Consigli operativi per configuratore\/amministratore ai prossimi accessi\": \"Operational tips for the configurator on future access\",\n        \"Popup consigli operativi attivo.\": \"Operational tips popup active.\",\n        \"Popup consigli operativi disabilitato.\": \"Operational tips popup disabled.\",\n        \"Apri consigli ora\": \"Open tips now\",\n        \"Funzioni utente attive\": \"User functions active\",\n        \"Puoi usare anche il servizio Shop & Service come utente standard registrato: chat, prodotti, servizi, ordini con tabella ordine completa, conferma ordine, prenotazioni, file\/foto e comunicazioni. I popup informativi restano attivi e separati; le tabelle operative di ordine restano nel servizio Shop & Service.\": \"You can also use the Shop & Service interface as a registered standard user: chat, products, services, orders with full order table, order confirmation, bookings, files\/photos and communications. Informational popups remain active and separate; operational order tables remain in the Shop & Service interface.\",\n        \"Cliccare tasto\": \"Click the\",\n        \"Scorrere le tabelle dell\u2019archivio ditta e verificare quali dati \u00e8 opportuno compilare.\": \"Scroll through the company archive tables and check which data should be filled in.\",\n        \"In\": \"In\",\n        \"possibilit\u00e0 di fornire alla AI istruzioni o richieste per configurare e manutenere le informazioni.\": \"you can provide the AI with instructions or requests to configure and maintain information.\",\n        \"Prodotti e servizi possono essere importati da dati copiati nel campo di messaggistica per la AI o da file esterni tramite la funzione\": \"Products and services can be imported from data copied into the AI message field or from external files through the\",\n        \"Dedicare particolare attenzione alle informazioni ed istruzioni per la AI.\": \"Pay special attention to the information and instructions for the AI.\",\n        \"La AI, nel trattare l\u2019utente, tiene conto delle informazioni che ha a disposizione ed esegue le specifiche istruzioni del configuratore\/amministratore.\": \"When handling the user, the AI takes into account the information available to it and follows the configurator's specific instructions.\",\n        \"Le istruzioni fornite possono essere in ogni momento verificate tramite il tasto\": \"The provided instructions can be checked at any time using the\",\n        \"Le foto ed i documenti associati a prodotti e servizi possono essere generati dalla AI tramite le funzioni di generazione, per poi essere comunque successivamente manutenuti dal configuratore\/amministratore.\": \"Photos and documents associated with products and services can be generated by the AI through the generation functions and then maintained by the configurator later.\",\n        \"Il\": \"The\",\n        \"fornisce un macro punto di vista sulle attivit\u00e0 effettuate nel gestire il servizio.\": \"provides a high-level view of the activities performed while managing the service.\",\n        \"Anteprima import\": \"Import preview\",\n        \"Anteprima modifiche\": \"Change preview\",\n        \"Anteprima modifiche aggiornata\": \"Change preview updated\",\n        \"Conferma import\": \"Confirm import\",\n        \"Errore conferma import\": \"Import confirmation error\",\n        \"Raffina anteprima\": \"Refine preview\",\n        \"Raffinamento in corso...\": \"Refinement in progress...\",\n        \"AI al lavoro sulla prima anteprima...\": \"AI working on the first preview...\",\n        \"Anteprima raffinata. Controlla il risultato prima di confermare.\": \"Preview refined. Check the result before confirming.\",\n        \"Salva e conferma\": \"Save and confirm\",\n        \"SALVA E CONFERMA\": \"SAVE AND CONFIRM\",\n        \"ESCI SENZA SALVARE\": \"EXIT WITHOUT SAVING\",\n        \"Errore\": \"Error\",\n        \"Errore AI. Controlla la connessione.\": \"AI error. Check the connection.\",\n        \"Errore di rete\": \"Network error\",\n        \"Errore sconosciuto\": \"Unknown error\",\n        \"Operazione completata\": \"Operation completed\",\n        \"Operazione interrotta\": \"Operation interrupted\",\n        \"Operazione AI completata\": \"AI operation completed\",\n        \"Operazione interrotta dall utente. Per completarla dovrai ripetere la richiesta.\": \"Operation interrupted by the user. To complete it, you will need to repeat the request.\",\n        \"Operazione interrotta dall utente. Per completarla dovrai ripetere la generazione.\": \"Operation interrupted by the user. To complete it, you will need to repeat the generation.\",\n        \"Processo AI interrotto. Per completarlo dovrai ripetere l operazione.\": \"AI process interrupted. To complete it, you will need to repeat the operation.\",\n        \"Non e stato possibile completare l attivita richiesta.\": \"It was not possible to complete the requested activity.\",\n        \"Completamento confermato dal sistema.\": \"Completion confirmed by the system.\",\n        \"Attivit\u00e0 AI conclusa. Sono presenti modifiche non ancora salvate.\": \"AI activity completed. There are unsaved changes.\",\n        \"Ricorda di salvare le modifiche se non lo hai ancora fatto.\": \"Remember to save the changes if you have not done so yet.\",\n        \"Seleziona una ditta.\": \"Select a company.\",\n        \"Seleziona prima una ditta.\": \"Select a company first.\",\n        \"Scrivi una istruzione di configurazione.\": \"Write a configuration instruction.\",\n        \"Area risposta configurazione non disponibile.\": \"Configuration response area not available.\",\n        \"Campo riservato: modificabile solo da sistema, globaladmin o attivazione WooCommerce.\": \"Reserved field: editable only by the system, globaladmin or WooCommerce activation.\",\n        \"Warning configurazione\": \"Configuration warning\",\n        \"Tabella da modificare non trovata\": \"Table to edit not found\",\n        \"Tabella non trovata nella configurazione corrente.\": \"Table not found in the current configuration.\",\n        \"Riga non trovata o gia eliminata.\": \"Row not found or already deleted.\",\n        \"Righe cancellate dalla configurazione corrente\": \"Rows deleted from the current configuration\",\n        \"Premi Salva Modifiche per aggiornare il database.\": \"Press Save Changes to update the database.\",\n        \"Salvataggio completato.\": \"Save completed.\",\n        \"Errore durante il salvataggio.\": \"Error while saving.\",\n        \"Setup centralizzato salvato.\": \"Centralized setup saved.\",\n        \"Errore salvataggio setup centralizzato.\": \"Centralized setup save error.\",\n        \"Solo l amministratore globale puo usare questo setup.\": \"Only the global administrator can use this setup.\",\n        \"Solo l amministratore globale puo salvare questo setup.\": \"Only the global administrator can save this setup.\",\n        \"Pulizia globale file non associati\": \"Global cleanup of unassociated files\",\n        \"Scansione ditte in corso.\": \"Company scan in progress.\",\n        \"Scansione globale ditte\": \"Global company scan\",\n        \"Nessuna riga con Istruzioni o Istruzione compilata trovata nell archivio della ditta.\": \"No row with filled Instructions or Instruction found in the company archive.\",\n        \"Verifica istruzioni completata.\": \"Instruction check completed.\",\n        \"Tabelle con istruzioni:\": \"Tables with instructions:\",\n        \"Righe selezionate:\": \"Selected rows:\",\n        \"Inserisci username\": \"Enter username\",\n        \"Inserisci username prima di richiedere il cambio password.\": \"Enter username before requesting a password change.\",\n        \"Seleziona una ditta nella finestra di login.\": \"Select a company in the login window.\",\n        \"Login non riuscito\": \"Login failed\",\n        \"Login principale non completato. Puoi usare Accesso diretto Shop & Service. Dettaglio:\": \"Main login not completed. You can use Direct Shop & Service access. Detail:\",\n        \"Username gi\u00e0 esistente\": \"Username already exists\",\n        \"Cambia username perch\u00e9 \u00e8 gi\u00e0 esistente.\": \"Change username because it already exists.\",\n        \"Riga utente duplicata. Inserisci un nuovo username: non pu\u00f2 essere uguale a uno gi\u00e0 esistente.\": \"Duplicate user row. Enter a new username: it cannot be the same as an existing one.\",\n        \"La segnalazione sar\u00e0 analizzata per eventualmente rispondere.\": \"The report will be analyzed for a possible reply.\",\n        \"Grazie per la collaborazione.\": \"Thank you for your collaboration.\",\n        \"Errore invio segnalazione.\": \"Report sending error.\",\n        \"Errore invio segnalazione:\": \"Report sending error:\",\n        \"Stato ditta: Dimostrativa\": \"Company status: Demo\",\n        \"La ditta risulta dimostrativa. Ordini e prenotazioni possono essere provati, ma non avranno esito effettivo dal punto di vista della fatturazione, pagamento, consegna prodotti od esecuzione di servizi. \u00c8 comunque possibile provare la navigazione e registrazione utente.\": \"The company is in demo mode. Orders and bookings can be tested, but they will have no actual effect for invoicing, payment, product delivery or service execution. User navigation and registration can still be tested.\",\n        \"Stato ditta: Sospesa\": \"Company status: Suspended\",\n        \"La ditta risulta sospesa. Ordini e prenotazioni possono essere provati, ma non avranno esito effettivo finch\u00e9 la ditta non torna Attiva.\": \"The company is suspended. Orders and bookings can be tested, but they will have no actual effect until the company becomes Active again.\",\n        \"Stato ditta: Configurazione\": \"Company status: Configuration\",\n        \"La ditta risulta in configurazione. Ordini e prenotazioni possono essere provati, ma non avranno esito effettivo finch\u00e9 la configurazione non viene completata e la ditta non torna Attiva.\": \"The company is in configuration. Orders and bookings can be tested, but they will have no actual effect until the configuration is completed and the company becomes Active again.\",\n        \"Gestione file associati\": \"Associated file management\",\n        \"Elemento cui sono associati i file\": \"Element associated with the files\",\n        \"Path storage file:\": \"File storage path:\",\n        \"File principale layout impostato. Ricorda di salvare la configurazione.\": \"Main layout file set. Remember to save the configuration.\",\n        \"File non selezionato.\": \"No file selected.\",\n        \"Nessun record trovato nella tabella selezionata.\": \"No record found in the selected table.\",\n        \"Generazione foto AI\": \"AI photo generation\",\n        \"Generazione immagini AI pronta.\": \"AI image generation ready.\",\n        \"Generazione immagini AI in corso...\": \"AI image generation in progress...\",\n        \"Generazione immagini AI completata.\": \"AI image generation completed.\",\n        \"Immagini generate:\": \"Images generated:\",\n        \"Priorit\u00e0:\": \"Priority:\",\n        \"prodotti\/servizi senza associazioni.\": \"products\/services without associations.\",\n        \"Successivi:\": \"Next:\",\n        \"prodotti\/servizi gi\u00e0 con associazioni.\": \"products\/services already with associations.\",\n        \"Prompt base:\": \"Base prompt:\",\n        \"attivit\u00e0 ditta + descrizione prodotto\/servizio\": \"company activity + product\/service description\",\n        \"Aggiungi riga manuale\": \"Add manual row\",\n        \"Ricerca prodotto o servizio\": \"Search product or service\",\n        \"Cerca prodotto o servizio\": \"Search product or service\",\n        \"Nessun articolo selezionato.\": \"No item selected.\",\n        \"Seleziona almeno un articolo nella tabella ordine.\": \"Select at least one item in the order table.\",\n        \"Ordine non pi\u00f9 disponibile.\": \"Order no longer available.\",\n        \"Totale\": \"Total\",\n        \"Totale vendita\": \"Sale total\",\n        \"Totale acquisto\": \"Purchase total\",\n        \"Acquisto\": \"Purchase\",\n        \"Vendita\": \"Sale\",\n        \"Apri\": \"Open\",\n        \"Dettaglio\": \"Details\",\n        \"Elimina\": \"Delete\",\n        \"Modifica\": \"Edit\",\n        \"Aggiungi\": \"Add\",\n        \"Rimuovi\": \"Remove\",\n        \"Conferma\": \"Confirm\",\n        \"Annullamento\": \"Cancellation\",\n        \"Attendi\": \"Please wait\",\n        \"Elaborazione...\": \"Processing...\",\n        \"Elaborazione\": \"Processing\",\n        \"Caricamento\": \"Loading\",\n        \"Caricamento...\": \"Loading...\",\n        \"Nessun risultato\": \"No result\",\n        \"Nessun risultato trovato\": \"No result found\",\n        \"Scegli\": \"Choose\",\n        \"Seleziona\": \"Select\",\n        \"Sito\": \"Site\",\n        \"Operatore\": \"Operator\",\n        \"Dispositivo\": \"Device\",\n        \"Prodotto\": \"Product\",\n        \"Servizio\": \"Service\",\n        \"Appuntamento\": \"Appointment\",\n        \"Appuntamenti\": \"Appointments\",\n        \"Attivit\u00e0\": \"Activity\",\n        \"Attivita\": \"Activity\",\n        \"Descrizione\": \"Description\",\n        \"Disponibilit\u00e0\": \"Availability\",\n        \"Disponibilita\": \"Availability\",\n        \"Istruzioni\": \"Instructions\",\n        \"Informazioni\": \"Information\",\n        \"Configurazione\": \"Configuration\",\n        \"Operativo\": \"Operational\",\n        \"Attivo\": \"Active\",\n        \"Sospesa\": \"Suspended\",\n        \"Cancellato\": \"Deleted\",\n        \"Configuratore\": \"Configurator\",\n        \"Amministratore\": \"Administrator\",\n        \"Responsabile\": \"Manager\",\n        \"Utente\": \"User\",\n        \"Entra\": \"Enter\",\n        \"Password dimenticata? Cambia tramite email\": \"Forgot password? Change by email\",\n        \"OK \/ SI\": \"OK \/ YES\",\n        \"X \/ NO\": \"X \/ NO\",\n        \"Aiuto\": \"Help\",\n        \"Comandi amministrativi\": \"Administrative commands\",\n        \"Info\": \"Info\",\n        \"Commerciale\/amministrazione\": \"Sales\/administration\",\n        \"Supporto tecnico\": \"Technical support\",\n        \"Impostazioni Ditta\": \"Company Settings\",\n        \"Anteprima modifiche import AI\": \"AI import change preview\",\n        \"Controlla l'anteprima delle modifiche. L'elenco non verr\u00e0 letto vocalmente. Conferma solo se vuoi procedere al salvataggio nel database.\": \"Check the change preview. The list will not be read aloud. Confirm only if you want to proceed with saving to the database.\",\n        \"Raffina import con AI sulla base della prima anteprima\": \"Refine import with AI based on the first preview\",\n        \"Es: usa il prezzo medio del range, sposta il principio attivo nella descrizione, elimina righe duplicate, normalizza categorie...\": \"E.g.: use the average price in the range, move the active ingredient into the description, remove duplicate rows, normalize categories...\",\n        \"Raffina anteprima con AI\": \"Refine preview with AI\",\n        \"OK Conferma e salva\": \"OK Confirm and save\",\n        \"X Annulla import\": \"X Cancel import\",\n        \"Gestione File Associati\": \"Associated File Management\",\n        \"Cancella\": \"Delete\",\n        \"Report Attivit\u00e0\": \"Activity Report\",\n        \"Chiedi all'AI di modificare dati in\": \"Ask the AI to edit data in\",\n        \"TUTTE le ditte esistenti\": \"ALL existing companies\",\n        \"contemporaneamente.\": \"at the same time.\",\n        \"Es: Elimina la parola Terni dai nomi delle ditte...\": \"E.g.: Delete the word Terni from company names...\",\n        \"Esegui Modifica su Tutti i DB\": \"Run Edit on All DBs\",\n        \"In attesa di avvio...\": \"Waiting to start...\",\n        \"Pulisci file non associati - Globale\": \"Clean unassociated files - Global\",\n        \"Carica Immagine\/File\": \"Upload Image\/File\",\n        \"Carica dal Dispositivo\": \"Upload from Device\",\n        \"Cerca \/ Genera con AI\": \"Search \/ Generate with AI\",\n        \"AI Image Generator\": \"AI Image Generator\",\n        \"Cosa ti serve? (es. Un logo per una pizzeria con forno a legna)...\": \"What do you need? (e.g. A logo for a pizzeria with a wood-fired oven)...\",\n        \"Istruzioni di dettaglio per la AI (opzionale). Esempio:\": \"Detailed instructions for the AI (optional). Example:\",\n        \"stile elegante, sfondo chiaro, inquadratura ravvicinata, foto realistica\": \"elegant style, light background, close-up framing, realistic photo\",\n        \"Indica eventuali istruzioni di dettaglio per la generazione della foto\": \"Enter any detailed instructions for generating the photo\",\n        \"Genera Immagine\": \"Generate Image\",\n        \"Usa questa Immagine\": \"Use this Image\",\n        \"Riprova\": \"Try again\",\n        \"Aiuto Colonna\": \"Column Help\",\n        \"Cerco nel manuale...\": \"Searching the manual...\",\n        \"Fotocamera\": \"Camera\",\n        \"Scatta Foto\": \"Take Photo\",\n        \"Ricerca Codice ATECO\": \"ATECO Code Search\",\n        \"Descrivi la tua attivit\u00e0 e l'IA trover\u00e0 i codici pi\u00f9 adatti.\": \"Describe your activity and the AI will find the most suitable codes.\",\n        \"Apri ditta in nuova scheda\": \"Open company in new tab\",\n        \"Formula Prezzo finale\": \"Final price formula\",\n        \"Tabella\": \"Table\",\n        \"Colonna\": \"Column\",\n        \"Riga\": \"Row\",\n        \"Valore precedente\": \"Previous value\",\n        \"Nuovo valore\": \"New value\",\n        \"Nessuna chiamata AI eseguita.\": \"No AI call executed.\",\n        \"Istruzione breve non chiara. Usa formato: campo destinazione = campo origine. Nessuna chiamata AI eseguita.\": \"Short instruction not clear. Use format: destination field = source field. No AI call executed.\",\n        \"Regole locali applicate alla preview e al salvataggio:\": \"Local rules applied to preview and saving:\",\n        \"Path condiviso salvato:\": \"Shared path saved:\",\n        \"URL condiviso salvato:\": \"Shared URL saved:\",\n        \"Percorso effettivo in uso:\": \"Effective path in use:\",\n        \"URL effettivo in uso:\": \"Effective URL in use:\",\n        \"Valori default disponibili:\": \"Available default values:\",\n        \"Esempio URL centralizzato Target Informatica:\": \"Target Informatica centralized URL example:\",\n        \"Annullamento associazioni generate in corso...\": \"Cancellation of generated associations in progress...\",\n        \"Generazione foto AI interrotta.\": \"AI photo generation interrupted.\",\n        \"Preparazione elenco record. Priorit\u00e0 a prodotti e servizi senza immagini specifiche.\": \"Preparing record list. Priority to products and services without file or image associations.\",\n        \"Altre fonti informative saranno usate solo successivamente se richieste.\": \"Other information sources will be used only later if requested.\",\n        \"Elaborazione\": \"Processing\",\n        \"Elemento\": \"Element\",\n        \"Selezione\": \"Selection\",\n        \"Disponibile\": \"Available\",\n        \"Non disponibile\": \"Not available\",\n        \"Avviso\": \"Notice\",\n        \"Attenzione\": \"Warning\",\n        \"Conferma definitiva\": \"Final confirmation\",\n        \"Password dimenticata\": \"Forgot password\",\n        \"Cambia tramite email\": \"Change by email\",\n        \"Sono l'assistente virtuale di\": \"I am the virtual assistant of\",\n        \"Come posso aiutarti oggi?\": \"How can I help you today?\",\n        \"Sono l\\'assistente virtuale di\": \"I am the virtual assistant of\",\n        \"Sono l\u2019assistente virtuale di\": \"I am the virtual assistant of\",\n        \"Scegli una ditta dal menu in alto, oppure dimmi cosa stai cercando e ti consiglier\u00f2 l'attivit\u00e0 giusta per te!\": \"Choose a company from the top menu, or tell me what you are looking for and I will recommend the right activity for you!\",\n        \"Scegli una ditta dal menu.\": \"Choose a company from the menu.\",\n        \"Ciao\": \"Hello\",\n        \"Non rilevo attivit\u00e0 da 3 minuti. Vuoi continuare ?\": \"I have not detected activity for 3 minutes. Do you want to continue?\",\n        \"Non rilevo attivita da 3 minuti. Vuoi continuare ?\": \"I have not detected activity for 3 minutes. Do you want to continue?\",\n        \"RIEPILOGO ORDINE\": \"ORDER SUMMARY\",\n        \"RIEPILOGO ORDINE RAGGRUPPATO\": \"GROUPED ORDER SUMMARY\",\n        \"Riepilogo ordine\": \"Order summary\",\n        \"Dettaglio ordine\": \"Order details\",\n        \"Conferma ordine\": \"Confirm order\",\n        \"Conferma righe acquisto\": \"Confirm purchase rows\",\n        \"Quantit\u00e0\": \"Quantity\",\n        \"Quantita\": \"Quantity\",\n        \"Prezzo\": \"Price\",\n        \"Prezzo vendita\": \"Sale price\",\n        \"Costo\": \"Cost\",\n        \"Costo acquisto\": \"Purchase cost\",\n        \"IVA\": \"VAT\",\n        \"Totale IVA\": \"VAT total\",\n        \"Imponibile\": \"Taxable amount\",\n        \"Importo\": \"Amount\",\n        \"Subtotale\": \"Subtotal\",\n        \"Sconto\": \"Discount\",\n        \"Margine\": \"Margin\",\n        \"Data\": \"Date\",\n        \"Ora\": \"Time\",\n        \"Note\": \"Notes\",\n        \"Cliente\": \"Customer\",\n        \"Telefono\": \"Phone\",\n        \"Email\": \"Email\",\n        \"Indirizzo\": \"Address\",\n        \"Accedi a Shop & Service\": \"Log in to Shop & Service\",\n        \"Accesso diretto Shop & Service\": \"Direct Shop & Service access\",\n        \"Ditta\": \"Company\",\n        \"Seleziona ditta\": \"Select company\",\n        \"Utente \/ username\": \"User \/ username\",\n        \"Username\": \"Username\",\n        \"Password\": \"Password\",\n        \"Ricordami\": \"Remember me\",\n        \"Accedi\": \"Log in\",\n        \"Esci\": \"Exit\",\n        \"Logout\": \"Logout\",\n        \"Chiudi\": \"Close\",\n        \"Non hai un account? Registrati\": \"Don\\'t have an account? Register\",\n        \"Registrati\": \"Register\",\n        \"Registrazione\": \"Registration\",\n        \"Nome\": \"Name\",\n        \"Cognome\": \"Surname\",\n        \"Ripeti password\": \"Repeat password\",\n        \"Conferma password\": \"Confirm password\",\n        \"Crea account\": \"Create account\",\n        \"Form login\": \"Login form\",\n        \"Crea Nuova Ditta\": \"Create New Company\",\n        \"Nome della nuova Ditta*\": \"New company name*\",\n        \"Primo Utente (Configuratore)\": \"First User (Configurator)\",\n        \"Email (Opzionale)\": \"Email (Optional)\",\n        \"Crea e Accedi\": \"Create and Login\",\n        \"Alla fine del processo di creazione della ditta, verr\u00e0 generato l'archivio e farai l'accesso automatico a te riservato come configuratore.\": \"At the end of the company creation process, the archive will be generated and you will be automatically logged in with your reserved configurator access.\",\n        \"Il servizio sar\u00e0 GRATUITO di prova per 30 giorni, poi potrai eventualmente attivare la ditta alle condizioni commerciali previste per il servizio Shop & Service disponibili direttamente durante la configurazione ed il periodo di prova.\": \"The service will be FREE for a 30-day trial. Afterwards, you can activate the company under the commercial conditions provided for the Shop & Service service, available directly during configuration and the trial period.\",\n        \"Buon lavoro !!\": \"Enjoy your work !!\",\n        \"Configura\": \"Configure\",\n        \"CONFIGURA\": \"CONFIGURE\",\n        \"Acquisti\": \"Purchases\",\n        \"ACQUISTI\": \"PURCHASES\",\n        \"Globale\": \"Global\",\n        \"GLOBALE\": \"GLOBAL\",\n        \"Stato dell\\\\'azienda: Demo\": \"Company status: Demo\",\n        \"Stato dell'azienda: Demo\": \"Company status: Demo\",\n        \"Stato ditta: Demo\": \"Company status: Demo\",\n        \"\u26a0\ufe0f Stato ditta: Demo\": \"\u26a0\ufe0f Company status: Demo\",\n        \"Salva Modifiche\": \"Save Changes\",\n        \"Salva modifiche\": \"Save changes\",\n        \"Annulla modifiche\": \"Cancel changes\",\n        \"Verifica istruzioni\": \"Check instructions\",\n        \"Carica file AI\": \"Upload AI file\",\n        \"Chatbot AI configurazione\": \"Configuration AI chatbot\",\n        \"Istruzioni AI configurazione\": \"AI configuration instructions\",\n        \"Fornisci istruzioni di configurazione o ottieni indicazioni tramite AI. Esempio: Aggiungere foto AI a tutti i prodotti o fornisci consigli di configurazione\": \"Provide configuration instructions or get guidance via AI. Example: Add AI photos to all products or provide configuration advice\",\n        \"Modifica Globale Database\": \"Global Database Edit\",\n        \"Modifica globale database\": \"Global database edit\",\n        \"Report attivit\u00e0\": \"Activity report\",\n        \"Report attivita\": \"Activity report\",\n        \"Gestione File Associati\": \"Associated File Management\",\n        \"Pulisci file non associati\": \"Clean unassociated files\",\n        \"Pulisci file non associati - Globale\": \"Clean unassociated files - Global\",\n        \"Cancella ditta\": \"Delete company\",\n        \"Attiva Shop-Service\": \"Activate Shop-Service\",\n        \"Acquista attivazione Shop-Service\": \"Purchase Shop-Service activation\",\n        \"Acquista canone servizio Shop-Service\": \"Purchase Shop-Service service fee\",\n        \"Apri consigli ora\": \"Open tips now\",\n        \"Mostra di nuovo il popup consigli operativi\": \"Show the operational tips popup again\",\n        \"Non mostrare pi\u00f9 questo popup in futuro\": \"Do not show this popup again in the future\",\n        \"Non mostrare piu questo popup in futuro\": \"Do not show this popup again in the future\",\n        \"Popup attesa lettura tabella\": \"Table reading wait popup\",\n        \"Lettura tabelle in corso\": \"Reading tables in progress\",\n        \"Sto leggendo le tabelle della ditta. Attendi la fine del caricamento.\": \"I am reading the company tables. Wait until loading is complete.\",\n        \"Tabelle lette\": \"Tables read\",\n        \"Errore lettura tabelle\": \"Table reading error\",\n        \"Attendi il completamento. Premi Esci per interrompere la richiesta.\": \"Wait for completion. Press Exit to interrupt the request.\",\n        \"Modifica database in corso\": \"Database edit in progress\",\n        \"Consigli operativi per configuratore\/amministratore\": \"Operational tips for the configurator\",\n        \"Manuale operativo Shop & Service\": \"Shop & Service operating manual\",\n        \"Cliccare tasto Configura\": \"Click the Configure button\",\n        \"Scorrere le tabelle dell'archivio ditta e verificare quali dati \u00e8 opportuno compilare\": \"Scroll through the company archive tables and check which data should be filled in\",\n        \"Dedicare particolare attenzione alle informazioni ed istruzioni per la AI\": \"Pay particular attention to the information and instructions for the AI\",\n        \"Tabelle\": \"Tables\",\n        \"Campi\": \"Fields\",\n        \"Record\": \"Records\",\n        \"Righe\": \"Rows\",\n        \"Filtro righe\": \"Row filter\",\n        \"Cerca nella tabella\": \"Search in the table\",\n        \"Nessuna riga\": \"No row\",\n        \"Aggiungi riga\": \"Add row\",\n        \"Elimina riga\": \"Delete row\",\n        \"Duplica riga\": \"Duplicate row\",\n        \"Valore\": \"Value\",\n        \"Valori\": \"Values\",\n        \"Campo\": \"Field\",\n        \"Campo obbligatorio\": \"Required field\",\n        \"Non salvato\": \"Not saved\",\n        \"Salvato\": \"Saved\",\n        \"Salvataggio in corso...\": \"Saving...\",\n        \"Salvataggio\": \"Saving\",\n        \"Email\/SMS\": \"Email\/SMS\",\n        \"Email inviata!\": \"Email sent!\",\n        \"SMS inviato!\": \"SMS sent!\",\n        \"Operazione completata.\": \"Operation completed.\",\n        \"Invio email in corso...\": \"Email sending in progress...\",\n        \"Invio SMS in corso...\": \"SMS sending in progress...\",\n        \"Invio notifica\": \"Notification sending\",\n        \"Comunicazione notifica\": \"Notification communication\",\n        \"Comunicazione \/ report\": \"Communication \/ report\",\n        \"Comunicazione\/report aperto in popup.\": \"Communication\/report opened in popup.\",\n        \"Comunicazione aperta in popup.\": \"Communication opened in popup.\",\n};\n    window.tiLayoutI18nAttrNames = ['placeholder','title','aria-label','data-title'];\n    window.tiI18nApplying = false;\n    window.tiI18nObserver = null;\n    window.tiI18nTextCache = window.tiI18nTextCache || Object.create(null);\n    window.tiI18nTextCacheCount = window.tiI18nTextCacheCount || 0;\n    window.tiI18nTextCacheMax = 6000;\n    window.tiI18nSortedKeysCache = null;\n    window.tiI18nDynamicRulesCache = null;\n    window.tiI18nChangedNodes = window.tiI18nChangedNodes || new Set();\n\n    window.tiI18nClearCache = function() {\n        window.tiI18nTextCache = Object.create(null);\n        window.tiI18nTextCacheCount = 0;\n        window.tiI18nSortedKeysCache = null;\n        window.tiI18nDynamicRulesCache = null;\n    };\n    window.tiI18nCacheSet = function(key, value) {\n        key = String(key == null ? '' : key);\n        if (!key) return value;\n        if (!Object.prototype.hasOwnProperty.call(window.tiI18nTextCache, key)) {\n            window.tiI18nTextCacheCount++;\n            if (window.tiI18nTextCacheCount > window.tiI18nTextCacheMax) {\n                window.tiI18nTextCache = Object.create(null);\n                window.tiI18nTextCacheCount = 1;\n            }\n        }\n        window.tiI18nTextCache[key] = value;\n        return value;\n    };\n    window.tiI18nGetSortedKeys = function() {\n        if (!window.tiI18nSortedKeysCache) {\n            const map = window.tiLayoutTranslationsEn || {};\n            window.tiI18nSortedKeysCache = Object.keys(map).filter(function(k){ return k && k.length >= 3; }).sort(function(a,b){ return b.length - a.length; });\n        }\n        return window.tiI18nSortedKeysCache;\n    };\n    window.tiI18nGetDynamicRules = function() {\n        if (!window.tiI18nDynamicRulesCache) {\n            window.tiI18nDynamicRulesCache = [\n                [\/^\u23f3?\\s*Non rilevo attivit[\u00e0a] da (\\d+) minuti?\\. Vuoi continuare \\?$\/i, '\u23f3 I have not detected activity for $1 minutes. Do you want to continue?'],\n                [\/^Sono l\\\\?[\u2019']assistente virtuale di(?:\\s+(.+))?$\/i, function(m){ return 'I am the virtual assistant of' + (m[1] ? ' ' + m[1] : ''); }],\n                [\/^Sono l[\u2019']assistente virtuale di\\s*$\/i, 'I am the virtual assistant of '],\n                [\/^RIEPILOGO ORDINE(?:\\s+(.+))?$\/i, function(m){ return 'ORDER SUMMARY' + (m[1] ? ' ' + m[1] : ''); }],\n                [\/^Totale\\s+(.+):\\s*(.+)$\/i, 'Total $1: $2'],\n                [\/^(.+)\\s+in corso\\.\\.\\.$\/i, '$1 in progress...'],\n                [\/^Lettura tabella\\s+(.+)$\/i, 'Reading table $1'],\n                [\/^Lettura tabelle\\s+(.+)$\/i, 'Reading tables $1'],\n                [\/^Elemento in lavorazione:\\s*(.+)$\/i, 'Item being processed: $1'],\n                [\/^Impostazioni\\s+(.+)$\/i, 'Settings $1'],\n                [\/^Anteprima modifiche aggiornata alle\\s+(.+)$\/i, 'Change preview updated at $1'],\n                [\/^Errore:\\s*(.+)$\/i, 'Error: $1'],\n                [\/^Errore durante\\s+(.+)$\/i, 'Error during $1'],\n                [\/^Righe cancellate:\\s*(.+)$\/i, 'Deleted rows: $1'],\n                [\/^Tabelle con istruzioni:\\s*(.+)$\/i, 'Tables with instructions: $1'],\n                [\/^Righe selezionate:\\s*(.+)$\/i, 'Selected rows: $1'],\n                [\/^Elaborazione\\s+(.+)$\/i, 'Processing $1'],\n                [\/^Caricamento\\s+(.+)$\/i, 'Loading $1']\n            ];\n        }\n        return window.tiI18nDynamicRulesCache;\n    };\n\n    window.tiCleanVisibleEscapedChars = function(value) {\n        let s = String(value == null ? '' : value);\n        if (!s) return s;\n        let prev = null;\n        for (let i = 0; i < 4 && prev !== s; i++) {\n            prev = s;\n            s = s.replace(\/\\\\([\"'])\/g, '$1');\n            s = s.replace(\/^((?:\\s*[\"'\\\\]+)+)(?=\\s*(?:\u26a0\ufe0f|\u23f3|\u2705|\u274c|Company status|Stato ditta|Stato dell\\\\?'azienda|Sono l\\\\?'assistente|I am))\/u, '');\n            s = s.replace(\/^(\u26a0\ufe0f|\u23f3|\u2705|\u274c)(?:\\s*[\"'\\\\]+)+\\s*\/u, '$1 ');\n            s = s.replace(\/(Demo|Dimostrativa|Sospesa|Configurazione|Attivo|Operativo)(?:[\\s\"'\\\\]+)$\/u, '$1');\n        }\n        return s;\n    };\n    window.tiI18nNormalize = function(txt) {\n        const cleaned = window.tiCleanVisibleEscapedChars ? window.tiCleanVisibleEscapedChars(txt) : String(txt == null ? '' : txt);\n        return String(cleaned == null ? '' : cleaned).replace(\/\\u00a0\/g, ' ').replace(\/\\s+\/g, ' ').trim();\n    };\n    window.tiI18nEscapeRegExp = function(txt) {\n        return String(txt).replace(\/[.*+?^${}()|[\\]\\\\]\/g, '\\\\$&');\n    };\n    window.tiI18nApplyCase = function(src, dst) {\n        src = String(src || ''); dst = String(dst || '');\n        if (!dst) return dst;\n        if (src === src.toUpperCase() && \/[A-Z\u00c0-\u00d6\u00d8-\u00de]\/i.test(src)) return dst.toUpperCase();\n        return dst;\n    };\n    window.tiTranslateLayoutStringToEn = function(value) {\n        const originalRaw = String(value == null ? '' : value);\n        const original = window.tiCleanVisibleEscapedChars ? window.tiCleanVisibleEscapedChars(originalRaw) : originalRaw;\n        if (!original.trim()) return original;\n        const cacheKey = 'en|' + original;\n        const cached = window.tiI18nTextCache && Object.prototype.hasOwnProperty.call(window.tiI18nTextCache, cacheKey) ? window.tiI18nTextCache[cacheKey] : null;\n        if (cached !== null) return cached;\n        const map = window.tiLayoutTranslationsEn || {};\n        const m = original.match(\/^(\\s*)([\\s\\S]*?)(\\s*)$\/);\n        const lead = m ? m[1] : '';\n        const body = m ? m[2] : original;\n        const tail = m ? m[3] : '';\n        const normalized = window.tiI18nNormalize(body);\n        if (!normalized) return window.tiI18nCacheSet(cacheKey, original);\n        if (Object.prototype.hasOwnProperty.call(map, normalized)) return window.tiI18nCacheSet(cacheKey, lead + map[normalized] + tail);\n        let out = body;\n        const dynamicRules = window.tiI18nGetDynamicRules ? window.tiI18nGetDynamicRules() : [];\n        for (const rule of dynamicRules) {\n            const mt = normalized.match(rule[0]);\n            if (mt) {\n                const repl = (typeof rule[1] === 'function') ? rule[1](mt) : normalized.replace(rule[0], rule[1]);\n                return window.tiI18nCacheSet(cacheKey, lead + repl + tail);\n            }\n        }\n        const keys = window.tiI18nGetSortedKeys ? window.tiI18nGetSortedKeys() : Object.keys(map);\n        for (const k of keys) {\n            if (out.indexOf(k) !== -1) out = out.split(k).join(window.tiI18nApplyCase(k, map[k]));\n        }\n        return window.tiI18nCacheSet(cacheKey, lead + out + tail);\n    };\n    window.tiTranslateLayoutString = function(value) {\n        const originalRaw = String(value == null ? '' : value);\n        const original = window.tiCleanVisibleEscapedChars ? window.tiCleanVisibleEscapedChars(originalRaw) : originalRaw;\n        if (window.currLang !== 'en' || !original.trim()) return original;\n        return window.tiTranslateLayoutStringToEn(original);\n    };\n    window.tiI18nShouldSkipNode = function(node) {\n        const el = node && node.nodeType === 1 ? node : (node && node.parentElement ? node.parentElement : null);\n        if (!el) return false;\n        const tag = String(el.tagName || '').toUpperCase();\n        if (['SCRIPT','STYLE','NOSCRIPT','SVG','CANVAS'].includes(tag)) return true;\n        if (el.closest && el.closest('[data-ti-no-i18n=\"1\"], .ti-no-i18n')) return true;\n        return false;\n    };\n    window.tiI18nTranslateTextNode = function(node) {\n        if (!node || node.nodeType !== 3 || window.tiI18nShouldSkipNode(node)) return;\n        const current = String(node.nodeValue || '');\n        if (typeof node.__tiI18nOrigin === 'undefined') {\n            node.__tiI18nOrigin = current;\n        } else {\n            const translatedOrigin = window.tiTranslateLayoutStringToEn ? window.tiTranslateLayoutStringToEn(node.__tiI18nOrigin) : window.tiTranslateLayoutString(node.__tiI18nOrigin);\n            if (current !== node.__tiI18nOrigin && current !== translatedOrigin) node.__tiI18nOrigin = current;\n        }\n        node.nodeValue = (window.currLang === 'en') ? window.tiTranslateLayoutString(node.__tiI18nOrigin) : node.__tiI18nOrigin;\n    };\n    window.tiI18nTranslateAttributes = function(el) {\n        if (!el || el.nodeType !== 1 || window.tiI18nShouldSkipNode(el)) return;\n        const attrs = window.tiLayoutI18nAttrNames || [];\n        if (!el.__tiI18nAttrOrigin) el.__tiI18nAttrOrigin = {};\n        attrs.forEach(function(attr){\n            if (!el.hasAttribute || !el.hasAttribute(attr)) return;\n            const current = el.getAttribute(attr);\n            if (typeof el.__tiI18nAttrOrigin[attr] === 'undefined') {\n                el.__tiI18nAttrOrigin[attr] = current;\n            } else {\n                const translatedOrigin = window.tiTranslateLayoutStringToEn ? window.tiTranslateLayoutStringToEn(el.__tiI18nAttrOrigin[attr]) : window.tiTranslateLayoutString(el.__tiI18nAttrOrigin[attr]);\n                if (current !== el.__tiI18nAttrOrigin[attr] && current !== translatedOrigin) el.__tiI18nAttrOrigin[attr] = current;\n            }\n            const base = el.__tiI18nAttrOrigin[attr];\n            el.setAttribute(attr, window.currLang === 'en' ? window.tiTranslateLayoutString(base) : base);\n        });\n        const tag = String(el.tagName || '').toUpperCase();\n        const type = String(el.getAttribute('type') || '').toLowerCase();\n        if (tag === 'INPUT' && ['button','submit','reset'].includes(type) && el.hasAttribute('value')) {\n            const currentVal = el.getAttribute('value');\n            if (typeof el.__tiI18nAttrOrigin.value === 'undefined') {\n                el.__tiI18nAttrOrigin.value = currentVal;\n            } else {\n                const translatedVal = window.tiTranslateLayoutStringToEn ? window.tiTranslateLayoutStringToEn(el.__tiI18nAttrOrigin.value) : window.tiTranslateLayoutString(el.__tiI18nAttrOrigin.value);\n                if (currentVal !== el.__tiI18nAttrOrigin.value && currentVal !== translatedVal) el.__tiI18nAttrOrigin.value = currentVal;\n            }\n            const baseVal = el.__tiI18nAttrOrigin.value;\n            el.setAttribute('value', window.currLang === 'en' ? window.tiTranslateLayoutString(baseVal) : baseVal);\n        }\n    };\n    window.tiI18nTranslateSubtree = function(root) {\n        if (!root) return;\n        if (root.nodeType === 3) {\n            window.tiI18nTranslateTextNode(root);\n            return;\n        }\n        if (root.nodeType !== 1 || window.tiI18nShouldSkipNode(root)) return;\n        window.tiI18nTranslateAttributes(root);\n        const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {\n            acceptNode: function(node) {\n                if (!node || !node.nodeValue || !node.nodeValue.trim()) return NodeFilter.FILTER_REJECT;\n                return window.tiI18nShouldSkipNode(node) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;\n            }\n        });\n        let node;\n        while ((node = walker.nextNode())) window.tiI18nTranslateTextNode(node);\n        if (root.querySelectorAll) root.querySelectorAll('*').forEach(function(el){ window.tiI18nTranslateAttributes(el); });\n    };\n    window.tiCleanEscapedUiInSubtree = function(root) {\n        if (!root || !window.tiCleanVisibleEscapedChars) return;\n        const cleanTextNode = function(node) {\n            if (!node || node.nodeType !== 3 || window.tiI18nShouldSkipNode(node)) return;\n            const cleaned = window.tiCleanVisibleEscapedChars(node.nodeValue || '');\n            if (cleaned !== node.nodeValue) {\n                node.nodeValue = cleaned;\n                node.__tiI18nOrigin = cleaned;\n            }\n        };\n        if (root.nodeType === 3) { cleanTextNode(root); return; }\n        if (root.nodeType !== 1 || window.tiI18nShouldSkipNode(root)) return;\n        const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {\n            acceptNode: function(node) {\n                if (!node || !node.nodeValue || !node.nodeValue.trim()) return NodeFilter.FILTER_REJECT;\n                return window.tiI18nShouldSkipNode(node) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;\n            }\n        });\n        let node;\n        while ((node = walker.nextNode())) cleanTextNode(node);\n        const attrs = window.tiLayoutI18nAttrNames || [];\n        const scanAttr = function(el) {\n            if (!el || !el.hasAttribute || window.tiI18nShouldSkipNode(el)) return;\n            attrs.forEach(function(attr){\n                if (!el.hasAttribute(attr)) return;\n                const val = el.getAttribute(attr);\n                const cleaned = window.tiCleanVisibleEscapedChars(val);\n                if (cleaned !== val) el.setAttribute(attr, cleaned);\n            });\n        };\n        scanAttr(root);\n        if (root.querySelectorAll) root.querySelectorAll('*').forEach(scanAttr);\n    };\n    window.applyLayoutLanguage = function(force) {\n        const root = document.body || document.getElementById('ti-ai-outer');\n        if (!root || window.tiI18nApplying) return;\n        window.tiI18nApplying = true;\n        try {\n            document.querySelectorAll('.ti-lang-opt').forEach(function(el){\n                el.classList.toggle('active', String(el.dataset.lang || '') === String(window.currLang || 'it'));\n            });\n            if (typeof dict !== 'undefined') {\n                const d = dict[window.currLang] || dict.it || {};\n                if (gE('ti-msg')) gE('ti-msg').placeholder = d.ph || (window.currLang === 'en' ? 'Type here...' : 'Scrivi qui...');\n                if (gE('ti-send')) gE('ti-send').innerText = d.send || (window.currLang === 'en' ? 'SEND' : 'INVIA');\n                if (gE('ti-new')) gE('ti-new').innerText = d.new || (window.currLang === 'en' ? 'New' : 'Nuova');\n                if (gE('ti-copy')) { gE('ti-copy').innerText = d.copy || (window.currLang === 'en' ? 'Copy text' : 'Copia testo'); gE('ti-copy').style.fontSize='11px'; }\n                if (gE('ti-file')) gE('ti-file').innerText = d.file || 'File';\n                if (gE('ti-products-btn')) gE('ti-products-btn').innerText = d.products || (window.currLang === 'en' ? 'Products' : 'Prodotti');\n                if (gE('ti-services-btn')) gE('ti-services-btn').innerText = d.services || (window.currLang === 'en' ? 'Services' : 'Servizi');\n                if (gE('ti-activities-btn')) gE('ti-activities-btn').innerText = d.activities || (window.currLang === 'en' ? 'Activities' : 'Attivit\u00e0');\n                if (gE('ti-kbd-btn')) gE('ti-kbd-btn').innerText = d.kbd || (window.currLang === 'en' ? 'Keyboard' : 'Tastiera');\n                if (gE('ti-mic')) gE('ti-mic').innerText = d.mic || 'Mic';\n                if (gE('ti-cam')) gE('ti-cam').innerText = d.cam || 'Cam';\n                if (gE('ti-login-btn') && gE('ti-login-btn').style.display !== 'none') gE('ti-login-btn').innerText = d.login || (window.currLang === 'en' ? 'Login' : 'Accedi');\n            }\n            window.tiI18nTranslateSubtree(root);\n            if (window.updateActivitiesButtonVisibility) window.updateActivitiesButtonVisibility();\n            if (window.applyMuteState) window.applyMuteState();\n        } catch(e) {\n            try { console.warn('Shop & Service i18n layout error', e); } catch(ignore) {}\n        } finally {\n            window.tiI18nApplying = false;\n        }\n    };\n    window.tiOpenUserActivities = function() {\n        try {\n            const user = String(window.tiUser || '').trim();\n            if (!user) { if (window.tiAlert) window.tiAlert('Accedi prima di consultare le tue attivit\u00e0.'); return false; }\n            window.lastUploadedFile = null;\n            if (window.lastFileContext) {\n                window.lastFileContext.active = false;\n                window.lastFileContext.awaitingReuseChoice = false;\n                window.lastFileContext.pendingReusePrompt = '';\n            }\n            const label = (String(window.currLang || 'it').toLowerCase() === 'en') ? 'Activities' : 'Attivit\u00e0';\n            if (window.tiSendQuickReply) { window.tiSendQuickReply('__TI_USER_ACTIVITIES__', label); }\n            else {\n                window.tiHiddenSendText = '__TI_USER_ACTIVITIES__';\n                window.tiHiddenSendLabel = label;\n                const sendBtn = gE('ti-send');\n                if (sendBtn) sendBtn.click();\n            }\n            return false;\n        } catch(e) { try { console.warn('Shop & Service activities button error', e); } catch(ignore) {} return false; }\n    };\n    window.ensureActivitiesButton = function() {\n        try {\n            let b = gE('ti-activities-btn');\n            const services = gE('ti-services-btn');\n            if (!b) {\n                b = document.createElement('button');\n                b.type = 'button';\n                b.id = 'ti-activities-btn';\n                b.className = 'ti-btn ti-btn-catalog ti-btn-activities';\n                b.textContent = (String(window.currLang || 'it').toLowerCase() === 'en') ? 'Activities' : 'Attivit\u00e0';\n                if (services && services.parentNode) services.parentNode.insertBefore(b, services.nextSibling);\n                else {\n                    const row = document.querySelector('.ti-actions-row');\n                    if (row) row.appendChild(b);\n                }\n            } else if (services && services.parentNode && b.previousElementSibling !== services) {\n                services.parentNode.insertBefore(b, services.nextSibling);\n            }\n            b.onclick = window.tiOpenUserActivities;\n            b.style.alignItems = 'center';\n            b.style.justifyContent = 'center';\n            b.style.gap = '4px';\n            b.title = 'Attivit\u00e0 e appuntamenti dell utente loggato';\n            return b;\n        } catch(e) { return gE('ti-activities-btn'); }\n    };\n    window.updateActivitiesButtonVisibility = function() {\n        try {\n            const b = window.ensureActivitiesButton ? window.ensureActivitiesButton() : gE('ti-activities-btn');\n            if (!b) return;\n            const logged = !!String(window.tiUser || '').trim();\n            if (logged) {\n                b.removeAttribute('hidden');\n                b.disabled = false;\n                b.style.setProperty('display', 'inline-flex', 'important');\n                b.style.visibility = 'visible';\n                b.style.opacity = '1';\n            } else {\n                b.disabled = true;\n                b.style.setProperty('display', 'none', 'important');\n                b.style.visibility = 'hidden';\n                b.style.opacity = '0';\n            }\n        } catch(e) {}\n    };\n\n    window.installLayoutI18nObserver = function() {\n        const root = document.body || document.getElementById('ti-ai-outer');\n        if (!root || window.tiI18nObserver) return;\n        let pending = null;\n        const changedNodes = window.tiI18nChangedNodes || new Set();\n        window.tiI18nChangedNodes = changedNodes;\n        const addChangedNode = function(node) {\n            if (!node) return;\n            if (node.nodeType === 3 || node.nodeType === 1) changedNodes.add(node);\n        };\n        window.tiI18nObserver = new MutationObserver(function(mutations){\n            if (window.tiI18nApplying) return;\n            mutations.forEach(function(m){\n                if (m.type === 'characterData') addChangedNode(m.target);\n                else if (m.type === 'attributes') addChangedNode(m.target);\n                else if (m.type === 'childList') (m.addedNodes || []).forEach(addChangedNode);\n            });\n            clearTimeout(pending);\n            pending = setTimeout(function(){\n                if (window.tiI18nApplying) return;\n                if (window.currLang !== 'en') { changedNodes.clear(); return; }\n                const fullScan = changedNodes.size > 120;\n                const nodes = fullScan ? [] : Array.from(changedNodes);\n                changedNodes.clear();\n                if (fullScan) { window.applyLayoutLanguage(false); return; }\n                window.tiI18nApplying = true;\n                try {\n                    nodes.forEach(function(node){ window.tiI18nTranslateSubtree(node); });\n                    if (window.applyMuteState) window.applyMuteState();\n                } catch(e) {\n                    try { console.warn('Shop & Service i18n incremental error', e); } catch(ignore) {}\n                } finally {\n                    window.tiI18nApplying = false;\n                }\n            }, 120);\n        });\n        window.tiI18nObserver.observe(root, {childList:true, subtree:true, characterData:true, attributes:true, attributeFilter:['placeholder','title','aria-label','data-title','value']});\n    };\n    window.tiApplyEnglishLayoutIfNeeded = function() {\n        try {\n            if (!['it','en'].includes(String(window.currLang || ''))) window.currLang = 'it';\n            window.installLayoutI18nObserver();\n            window.applyLayoutLanguage(true);\n        } catch(e) {}\n    };\n    document.addEventListener('DOMContentLoaded', window.tiApplyEnglishLayoutIfNeeded);\n    setTimeout(window.tiApplyEnglishLayoutIfNeeded, 0);\n\n    window.isConfiguratorUser = function() {\n        return \/amministratore|administrator|configuratore|configurator\/i.test(String(window.tiRole || '')) || String(window.tiUser || '') === 'SSGlobalAdmin';\n    };\n    window.isAdminConfiguratorUser = function() {\n        return \/amministratore|administrator|configuratore|configurator\/i.test(String(window.tiRole || '')) || String(window.tiUser || '') === 'SSGlobalAdmin';\n    };\n    window.isDbModificationWaitRole = function() {\n        return \/amministratore|administrator|configuratore|configurator|responsabile|responsible\/i.test(String(window.tiRole || '')) || String(window.tiUser || '') === 'SSGlobalAdmin';\n    };\n    window.getStoragePathForTable = function(dbName, tblName) {\n        const dbRaw = String(dbName || '').replace(\/\\.json$\/i, '');\n        const tbl = String(tblName || '').trim();\n        if (!dbRaw || !tbl) return '';\n        let p = '\/wp-content\/uploads\/AI-data\/' + dbRaw + '\/Tabelle\/' + tbl;\n        if (!\/(prodott|serviz)\/i.test(tbl)) p += '\/Docs';\n        return p;\n    };\n    window.showDbOperationWait = function(operationLabel, detail) {\n        if (!window.isDbModificationWaitRole || !window.isDbModificationWaitRole()) return false;\n        if (typeof window.showProgressPopup === 'function') {\n            window.showProgressPopup(operationLabel || 'Modifica database in corso', detail || 'Attendi il completamento. Premi Esci per interrompere la richiesta.', {cancellable:true, targetPercent:85, stepMs:450, startPercent:5});\n            return true;\n        }\n        return false;\n    };\n    window.hideDbOperationWait = function(success, finalText, opts) {\n        if (typeof window.hideProgressPopup === 'function') {\n            window.hideProgressPopup(success !== false, finalText || (success === false ? 'Operazione interrotta' : 'Operazione completata'), Object.assign({notifyUser:false}, opts || {}));\n        }\n    };\n\n    window.showTableReadWait = function(title, detail, currentItem) {\n        \/\/ 30.9.343 - Fix chirurgico produzione:\n        \/\/ la semplice lettura tabelle di Configurazione non deve aprire un loader modale.\n        \/\/ In produzione il loader poteva restare a 100% e bloccare la UI; per questa sola operazione\n        \/\/ si preferisce non mostrare il popup di avanzamento.\n        try {\n            window.__tiTableReadWaitSuppressed343 = true;\n            if (typeof window.tiDismissCompletedTableReadLoader343 === 'function') {\n                window.tiDismissCompletedTableReadLoader343('showTableReadWait-suppressed');\n            }\n        } catch(e) {}\n        return false;\n    };\n    window.hideTableReadWait = function(success, finalText) {\n        try {\n            if (typeof window.tiDismissCompletedTableReadLoader343 === 'function') {\n                window.tiDismissCompletedTableReadLoader343('hideTableReadWait');\n            }\n            \/\/ Non richiamare hideProgressPopup per la lettura tabelle: il popup e' stato soppresso.\n        } catch(e) {}\n    };\n    window.getShopServiceManualUrl = function() {\n        try {\n            const setup = window.tiSharedSetup || {};\n            let u = String(setup.manual_url || '').trim();\n            if (!u) {\n                const base = String(setup.docs_base_url || 'https:\/\/www.targetinformatica.it\/wp-content\/uploads\/AI-data\/AI-docs\/Shop-service').replace(\/\\\/+$\/, '');\n                u = base + '\/Shop-Service%20Manual.pdf';\n            }\n            return u;\n        } catch(e) {\n            return 'https:\/\/www.targetinformatica.it\/wp-content\/uploads\/AI-data\/AI-docs\/Shop-service\/Shop-Service%20Manual.pdf';\n        }\n    };\n\n    window.escapeAttrValue = window.escapeAttrValue || function(v) {\n        return String(v == null ? '' : v).replace(\/&\/g, '&amp;').replace(\/\"\/g, '&quot;').replace(\/<\/g, '&lt;').replace(\/>\/g, '&gt;');\n    };\n\n    window.getConfiguratorOperationalTipsUserKey = function() {\n        const d = gE('ti-ditta');\n        const dbKey = d && d.value ? String(d.value) : 'no-db';\n        const userKey = String(window.tiUser || window.tiUserDisplay || 'configuratore');\n        return encodeURIComponent(dbKey) + '_' + encodeURIComponent(userKey);\n    };\n\n    window.getConfiguratorOperationalTipsShownKey = function() {\n        return 'ti_cfg_operational_tips_shown_' + window.getConfiguratorOperationalTipsUserKey();\n    };\n\n    window.getConfiguratorOperationalTipsDisabledKey = function() {\n        return 'ti_cfg_operational_tips_disabled_' + window.getConfiguratorOperationalTipsUserKey();\n    };\n\n    window.isConfiguratorOperationalTipsDisabled = function() {\n        try {\n            return !!(window.localStorage && localStorage.getItem(window.getConfiguratorOperationalTipsDisabledKey()) === '1');\n        } catch(e) { return false; }\n    };\n\n    window.setConfiguratorOperationalTipsDisabled = function(disabled, silent) {\n        disabled = !!disabled;\n        try {\n            if (window.localStorage) {\n                if (disabled) localStorage.setItem(window.getConfiguratorOperationalTipsDisabledKey(), '1');\n                else localStorage.removeItem(window.getConfiguratorOperationalTipsDisabledKey());\n            }\n            if (!disabled && window.sessionStorage) sessionStorage.removeItem(window.getConfiguratorOperationalTipsShownKey());\n        } catch(e) {}\n        if (window.syncConfiguratorOperationalTipsFlagUI) window.syncConfiguratorOperationalTipsFlagUI();\n        if (!silent && window.tiAlert) {\n            window.tiAlert(disabled ? 'Popup consigli operativi configuratore\/amministratore disabilitato per i prossimi accessi.' : 'Popup consigli operativi configuratore\/amministratore riabilitato. Verr\u00e0 mostrato di nuovo ai prossimi accessi.');\n        }\n    };\n\n    window.syncConfiguratorOperationalTipsFlagUI = function() {\n        const enabled = !(window.isConfiguratorOperationalTipsDisabled && window.isConfiguratorOperationalTipsDisabled());\n        const cb = gE('ti-cfg-tips-enabled');\n        const status = gE('ti-cfg-tips-status');\n        if (cb) cb.checked = enabled;\n        if (status) status.textContent = enabled ? 'Popup consigli operativi attivo.' : 'Popup consigli operativi disabilitato.';\n    };\n\n    window.handleConfiguratorTipsFlagChange = function(cb) {\n        const enabled = !!(cb && cb.checked);\n        window.setConfiguratorOperationalTipsDisabled(!enabled, false);\n    };\n\n    window.getConfiguratorWelcomeHtml = function() {\n        if (!window.isConfiguratorUser()) return '';\n        let html = String(window.tiConfiguratorAccessMessage || '');\n        const roleNow = String(window.tiRole || '').toLowerCase();\n        const privileged = (!!window.tiUser && window.tiUser === 'SSGlobalAdmin') || \/amministratore|configuratore|responsabile|globale\/.test(roleNow);\n        if (privileged) {\n            const note = [\n                '<div style=\"margin-top:8px;padding:8px 10px;border-radius:10px;background:#0f172a;border:1px solid #334155;color:#bfdbfe;font-size:12px;line-height:1.35;\">',\n                '<b>Funzioni utente attive<\/b><br>',\n                \"Puoi usare anche il servizio Shop & Service come utente standard registrato: chat, prodotti, servizi, ordini con tabella ordine completa, conferma ordine, prenotazioni, file\\\/foto e comunicazioni. I popup informativi restano attivi e separati; le tabelle operative di ordine restano nel servizio Shop & Service.\",\n                '<\/div>'\n            ].join('');\n            html = html ? (html + note) : note;\n        }\n        return html;\n    };\n\n    window.getConfiguratorOperationalTipsHtml = function() {\n        const manualUrl = window.getShopServiceManualUrl ? window.getShopServiceManualUrl() : 'https:\/\/www.targetinformatica.it\/wp-content\/uploads\/AI-data\/AI-docs\/Shop-service\/Shop-Service%20Manual.pdf';\n        const disabled = window.isConfiguratorOperationalTipsDisabled && window.isConfiguratorOperationalTipsDisabled();\n        const safeManualUrl = window.escapeAttrValue ? window.escapeAttrValue(manualUrl) : manualUrl;\n        return `\n            <div style=\"text-align:left;line-height:1.45;font-size:13px;color:#e5e7eb;\">\n                <div style=\"font-size:17px;font-weight:800;color:#ffffff;margin-bottom:10px;\">Consigli operativi per configuratore\/amministratore<\/div>\n                <ol style=\"margin:0;padding-left:20px;\">\n                    <li>Cliccare tasto <b>Configura<\/b>.<\/li>\n                    <li>Scorrere le tabelle dell\u2019archivio ditta e verificare quali dati \u00e8 opportuno compilare.<\/li>\n                    <li>In <b>Istruzioni AI configurazione<\/b>, possibilit\u00e0 di fornire alla AI istruzioni o richieste per configurare e manutenere le informazioni.<\/li>\n                    <li>Prodotti e servizi possono essere importati da dati copiati nel campo di messaggistica per la AI o da file esterni tramite la funzione <b>Carica file AI<\/b>.<\/li>\n                    <li>Dedicare particolare attenzione alle informazioni ed istruzioni per la AI.<\/li>\n                    <li>La AI, nel trattare l\u2019utente, tiene conto delle informazioni che ha a disposizione ed esegue le specifiche istruzioni del configuratore\/amministratore.<\/li>\n                    <li>Le istruzioni fornite possono essere in ogni momento verificate tramite il tasto <b>Verifica istruzioni<\/b>.<\/li>\n                    <li>Le foto ed i documenti associati a prodotti e servizi possono essere generati dalla AI tramite le funzioni di generazione, per poi essere comunque successivamente manutenuti dal configuratore\/amministratore.<\/li>\n                    <li>Il <b>Report attivit\u00e0<\/b> fornisce un macro punto di vista sulle attivit\u00e0 effettuate nel gestire il servizio.<\/li>\n                <\/ol>\n                <div style=\"margin-top:14px;padding-top:12px;border-top:1px solid #374151;display:flex;gap:10px;align-items:center;justify-content:space-between;flex-wrap:wrap;\">\n                    <a href=\"${safeManualUrl}\" target=\"_blank\" rel=\"noopener\" class=\"ti-btn\" style=\"background:#059669;color:#fff;text-decoration:none;font-size:12px;padding:8px 10px;border-radius:8px;display:inline-flex;align-items:center;gap:6px;\">\ud83d\udcc4 Manuale operativo Shop & Service<\/a>\n                    <label style=\"display:flex;align-items:center;gap:7px;color:#cbd5e1;font-size:12px;cursor:pointer;\">\n                        <input type=\"checkbox\" id=\"ti-cfg-tips-disable-from-popup\" ${disabled ? 'checked' : ''} onchange=\"if(window.setConfiguratorOperationalTipsDisabled) window.setConfiguratorOperationalTipsDisabled(this.checked, true);\">\n                        Non mostrare pi\u00f9 questo popup in futuro\n                    <\/label>\n                <\/div>\n            <\/div>`;\n    };\n\n    window.showConfiguratorOperationalTipsPopup = function(force) {\n        if (!window.isConfiguratorUser || !window.isConfiguratorUser()) return false;\n        if (!force && window.isConfiguratorOperationalTipsDisabled && window.isConfiguratorOperationalTipsDisabled()) return false;\n        const key = window.getConfiguratorOperationalTipsShownKey ? window.getConfiguratorOperationalTipsShownKey() : 'ti_cfg_operational_tips_shown_config_admin';\n        try {\n            if (!force && window.sessionStorage && sessionStorage.getItem(key) === '1') return false;\n            if (window.sessionStorage) sessionStorage.setItem(key, '1');\n        } catch(e) {}\n        const html = window.getConfiguratorOperationalTipsHtml ? window.getConfiguratorOperationalTipsHtml() : '';\n        if (!html) return false;\n        let shown = false;\n        if (window.showPrivilegedCommunicationPopup) {\n            shown = window.showPrivilegedCommunicationPopup(html, 'Consigli operativi per configuratore\/amministratore', {text: window.tiPlainTextFromHtml ? window.tiPlainTextFromHtml(html) : html});\n        } else if (window.tiAlert) {\n            window.tiAlert(html);\n            shown = true;\n        }\n        if (shown && window.syncConfiguratorOperationalTipsFlagUI) setTimeout(window.syncConfiguratorOperationalTipsFlagUI, 0);\n        return shown;\n    };\n\n    function utoa(str) { return window.btoa(unescape(encodeURIComponent(str))); }\n    \n    window.getRecordLabelForUpload = function(tblName, rowObj, fallback) {\n        const tbl = String(tblName || '').toLowerCase();\n        const row = rowObj || {};\n        const picks = ['Descrizione','Prodotto','Servizio','Username','Utente','Nome','Titolo','Ragione sociale'];\n        for (const k of picks) {\n            if (row && row[k] && String(row[k]).trim()) return String(row[k]).trim();\n            const hit = Object.keys(row || {}).find(x => String(x).toLowerCase() === String(k).toLowerCase());\n            if (hit && String(row[hit] || '').trim()) return String(row[hit]).trim();\n        }\n        if (fallback) return String(fallback).trim();\n        return tbl === 'utenti' ? 'utente' : (tblName || 'record');\n    };\n\n    window.isLegacyUserImagePath = function(val) {\n        return \/^wp-content\\\/uploads\\\/ai-data\\\/.+\\\/utenti\\\/\/i.test(String(val || '').trim());\n    };\n    window.getFullImgUrl = function(val, dbName, tName, recordLabel) {\n        if (!val) return '';\n        val = String(val).trim();\n        if (val.indexOf(',') !== -1) val = String(val.split(',')[0] || '').trim();\n        let dbRaw = dbName ? String(dbName).replace('.json', '') : '';\n        const tableName = String(tName || '').trim();\n        if (!val) return '';\n        if (window.isLegacyUserImagePath(val) && \/^utenti$\/i.test(tableName)) return '';\n        if (val.indexOf('ti_action=ti_ai_get_image') !== -1) return val;\n        if (\/^https?:\\\/\\\/\/i.test(val)) return val;\n        if (\/^\\\/?wp-content\\\/uploads\\\/\/i.test(val)) return window.location.origin + '\/' + val.replace(\/^\\\/+\/, '');\n        if (\/^\\\/\/.test(val)) return window.location.origin + val;\n        if (\/^https?:\\\/\\\/\/i.test(val) && \/\\\/wp-content\\\/uploads\\\/\/i.test(val)) {\n            const pts = val.split('\/');\n            val = pts[pts.length - 1];\n            return window.tiUrl + '?ti_action=ti_ai_get_image&db=' + encodeURIComponent(dbRaw) + '&tbl=' + encodeURIComponent(tableName) + '&file=' + encodeURIComponent(val) + (recordLabel ? '&record=' + encodeURIComponent(recordLabel) : '');\n        }\n        if (\/^wp-content\\\/uploads\\\/\/i.test(val) || \/^uploads\\\/\/i.test(val) || (val.indexOf('AI-data\/') !== -1 && val.indexOf('\/') !== -1)) {\n            const pts = val.split('\/');\n            val = pts[pts.length - 1];\n            return window.tiUrl + '?ti_action=ti_ai_get_image&db=' + encodeURIComponent(dbRaw) + '&tbl=' + encodeURIComponent(tableName) + '&file=' + encodeURIComponent(val) + (recordLabel ? '&record=' + encodeURIComponent(recordLabel) : '');\n        }\n        if (val.indexOf('\/') !== -1) {\n            const pts = val.split('\/');\n            val = pts[pts.length - 1];\n        }\n        return window.tiUrl + '?ti_action=ti_ai_get_image&db=' + encodeURIComponent(dbRaw) + '&tbl=' + encodeURIComponent(tableName) + '&file=' + encodeURIComponent(val) + (recordLabel ? '&record=' + encodeURIComponent(recordLabel) : '');\n    };\n    window.getCompanyFallbackMediaUrl = function(dbName) {\n        const dbVal = dbName || (gE('ti-ditta') ? gE('ti-ditta').value : '');\n        let logo = '';\n        if (window.currentDbData && window.currentDbData.Tabelle && Array.isArray(window.currentDbData.Tabelle.Config) && window.currentDbData.Tabelle.Config[0]) {\n            const cfg = window.currentDbData.Tabelle.Config[0];\n            logo = cfg.Logo || cfg.logo || cfg.Immagine || cfg.immagine || '';\n        }\n        if (!logo && Array.isArray(window.ditteData)) {\n            const hit = window.ditteData.find(d => (d.db || '') === dbVal || (d.db || '') === String(dbVal).replace('.json','') + '.json');\n            if (hit) {\n                logo = hit.logo || '';\n                if (!logo && hit.qr) return hit.qr;\n            }\n        }\n        if (logo) return window.getFullImgUrl(logo, dbVal, 'Config');\n        if (Array.isArray(window.ditteData)) {\n            const hit = window.ditteData.find(d => (d.db || '') === dbVal || (d.db || '') === String(dbVal).replace('.json','') + '.json');\n            if (hit && hit.qr) return hit.qr;\n        }\n        return '';\n    };\n\n    window.getSafePreviewUrl = function(val, dbName, tName, keyName, recordLabel) {\n        const fb = window.getCompanyFallbackMediaUrl(dbName || (gE('ti-ditta') ? gE('ti-ditta').value : '')) || '';\n        const raw = String(val || '').trim();\n        const tbl = String(tName || '').toLowerCase();\n        const key = String(keyName || '').toLowerCase();\n        const url = window.getFullImgUrl(raw, dbName, tName, recordLabel);\n        if (!raw) return fb || url;\n        if ((tbl === 'utenti' || key === 'immagine' || key === 'foto' || key === 'logo') && window.isLegacyUserImagePath(raw)) {\n            return fb || url;\n        }\n        return url || fb;\n    };\n    window.applyFallbackImage = function(imgEl) {\n        if (!imgEl) return;\n        const fb = imgEl.getAttribute('data-fallback') || window.getCompanyFallbackMediaUrl(gE('ti-ditta') ? gE('ti-ditta').value : '');\n        if (fb && imgEl.src !== fb) {\n            imgEl.onerror = null;\n            imgEl.src = fb;\n            return;\n        }\n        imgEl.style.display = 'none';\n    };\n\n    window.fmtNum = function(num) {\n        let n = parseFloat(num) || 0;\n        let parts = n.toFixed(2).split('.');\n        parts[0] = parts[0].replace(\/\\B(?=(\\d{3})+(?!\\d))\/g, \".\");\n        return parts.join(',');\n    };\n\n    window.parseIvaRate = function(val, defVal = 22) {\n        if (val == null) return defVal;\n        let raw = String(val).trim();\n        if (!raw) return defVal;\n        raw = raw.replace(\/%\/g, '').replace(',', '.');\n        let n = parseFloat(raw);\n        return isNaN(n) ? defVal : n;\n    };\n\n    window.calcVatAmountFromGross = function(gross, ivaRate) {\n        let g = parseFloat(gross) || 0;\n        let r = window.parseIvaRate(ivaRate, 22);\n        if (r <= 0) return 0;\n        return g - (g \/ (1 + (r \/ 100)));\n    };\n\n    window.calcVatNetFromGross = function(gross, ivaRate) {\n        let g = parseFloat(gross) || 0;\n        let vat = window.calcVatAmountFromGross(g, ivaRate);\n        return g - vat;\n    };\n\n\n    window.fetchJsonSafe = function(url, options) {\n        const opts = Object.assign({}, options || {});\n        let tableReadWait = false;\n        let tableReadWaitShown = false;\n        let tableReadOk = false;\n        let tableReadTitle = '\u23f3 Lettura tabelle in corso';\n        let tableReadDetail = 'Sto leggendo le tabelle della ditta. Attendi la fine del caricamento.';\n        let tableReadItem = '';\n        try {\n            tableReadWait = !!opts.tiShowTableReadWait;\n            if (opts.tiTableReadTitle) tableReadTitle = String(opts.tiTableReadTitle);\n            if (opts.tiTableReadDetail) tableReadDetail = String(opts.tiTableReadDetail);\n            if (opts.tiTableReadItem) tableReadItem = String(opts.tiTableReadItem);\n            delete opts.tiShowTableReadWait;\n            delete opts.tiTableReadTitle;\n            delete opts.tiTableReadDetail;\n            delete opts.tiTableReadItem;\n        } catch(e) {}\n        try {\n            if (opts.body instanceof FormData && opts.body.has('ti_action') && !opts.body.has('action')) {\n                opts.body.append('action', opts.body.get('ti_action'));\n            }\n            if (opts.body instanceof FormData && window.tiAppendCurrentLanguageToFormData) window.tiAppendCurrentLanguageToFormData(opts.body);\n        } catch(e) {}\n        const tiNoLongProcessSignal = !!opts.tiNoLongProcessSignal;\n        if (Object.prototype.hasOwnProperty.call(opts, 'tiNoLongProcessSignal')) delete opts.tiNoLongProcessSignal;\n        if (!tiNoLongProcessSignal && !opts.signal && window.tiLongProcessState && window.tiLongProcessState.controller) {\n            opts.signal = window.tiLongProcessState.controller.signal;\n        }\n        if (tableReadWait && typeof window.showTableReadWait === 'function') {\n            tableReadWaitShown = window.showTableReadWait(tableReadTitle, tableReadDetail, tableReadItem);\n        }\n        const requestPromise = fetch(url, opts).then(async (response) => {\n            const raw = await response.text();\n            if (!response.ok) {\n                const httpErr = new Error(`HTTP ${response.status}: ${raw.slice(0, 180) || 'Risposta server vuota'}`);\n                httpErr.httpStatus = response.status;\n                throw httpErr;\n            }\n            try {\n                return JSON.parse(raw);\n            } catch (err) {\n                throw new Error(`Risposta server non valida: ${raw.slice(0, 180) || 'vuota'}`);\n            }\n        }).then((data) => {\n            tableReadOk = true;\n            return data;\n        }).catch((err) => {\n            if (err && err.name === 'AbortError') {\n                const abortErr = new Error('Processo interrotto dall utente.');\n                abortErr.userCancelled = true;\n                throw abortErr;\n            }\n            throw err;\n        });\n        if (!tableReadWaitShown || typeof requestPromise.finally !== 'function') return requestPromise;\n        return requestPromise.finally(function(){\n            if (typeof window.hideTableReadWait === 'function') window.hideTableReadWait(tableReadOk, tableReadOk ? 'Tabelle lette' : 'Errore lettura tabelle');\n        });\n    };\n\n\n\n\n    \/\/ 30.9.423 - protezione JSON caricata subito: evita Unexpected token '<' gia nel caricamento ditte.\n    (function(){\n        if (window.__tiJsonEarly423) return;\n        window.__tiJsonEarly423 = true;\n        function clean(raw){ return String(raw == null ? '' : raw).replace(\/^\\uFEFF\/, '').trim(); }\n        function stripHtml(raw){\n            var t = clean(raw).replace(\/<script[\\s\\S]*?<\\\/script>\/gi, ' ').replace(\/<style[\\s\\S]*?<\\\/style>\/gi, ' ');\n            t = t.replace(\/<[^>]+>\/g, ' ').replace(\/&nbsp;\/gi, ' ').replace(\/&quot;\/gi, '\"').replace(\/&#039;\/gi, \"'\").replace(\/&lt;\/gi, '<').replace(\/&gt;\/gi, '>').replace(\/&amp;\/gi, '&');\n            t = t.replace(\/\\s+\/g, ' ').trim();\n            return (t || clean(raw)).slice(0, 320);\n        }\n        function looksHtml(raw){\n            var t = clean(raw).slice(0, 500).toLowerCase();\n            return t.charAt(0) === '<' || t.indexOf('<!doctype') === 0 || t.indexOf('<html') >= 0 || t.indexOf('<br') === 0 || t.indexOf('<b>') >= 0 || t.indexOf('<script') >= 0 || t.indexOf('wordpress') >= 0;\n        }\n        function actionFromUrl(url){\n            try { return String(new URL(url, window.location.href).searchParams.get('ti_action') || new URL(url, window.location.href).searchParams.get('action') || ''); }\n            catch(e) { return ''; }\n        }\n        function actionFromBody(body){\n            try { return (body instanceof FormData) ? String(body.get('ti_action') || body.get('action') || '') : ''; } catch(e) { return ''; }\n        }\n        function cloneFd(fd){\n            var out = new FormData();\n            try { fd.forEach(function(v,k){ out.append(k,v); }); } catch(e) {}\n            return out;\n        }\n        function formFromUrl(url){\n            var fd = new FormData();\n            try {\n                var u = new URL(url, window.location.href);\n                u.searchParams.forEach(function(v,k){ fd.append(k,v); });\n            } catch(e) {}\n            var act = String(fd.get('ti_action') || fd.get('action') || '').trim();\n            if (act) { if (!fd.has('ti_action')) fd.append('ti_action', act); if (!fd.has('action')) fd.append('action', act); }\n            return fd;\n        }\n        function ensureFd(fd){\n            if (!(fd instanceof FormData)) return fd;\n            try {\n                if (fd.has('ti_action') && !fd.has('action')) fd.append('action', fd.get('ti_action'));\n                if (fd.has('action') && !fd.has('ti_action')) fd.append('ti_action', fd.get('action'));\n                var lang = (String(window.currLang || 'it').toLowerCase().indexOf('en') === 0) ? 'en' : 'it';\n                if (!fd.has('ti_lang')) fd.append('ti_lang', lang);\n                if (!fd.has('ti_ui_lang')) fd.append('ti_ui_lang', lang);\n            } catch(e) {}\n            return fd;\n        }\n        function parseJson(raw, action, url){\n            var txt = clean(raw);\n            if (!txt) throw new Error('Risposta server non valida: vuota');\n            if (txt === '0' || txt === '-1') throw new Error('Endpoint non disponibile per ' + (action || 'richiesta') + ': ' + txt);\n            try { return JSON.parse(txt); }\n            catch(e) {\n                var p = looksHtml(txt) ? stripHtml(txt) : txt.slice(0, 320);\n                throw new Error('Risposta server non valida: ' + (p || 'contenuto non JSON'));\n            }\n        }\n        function buildAttempts(url, opts){\n            var attempts = [];\n            function add(u, o){\n                u = String(u || '').trim(); if (!u) return;\n                attempts.push({url:u, opts:o});\n            }\n            var baseOpts = Object.assign({}, opts || {});\n            var body = baseOpts.body;\n            var action = actionFromBody(body) || actionFromUrl(url);\n            \/\/ Primo tentativo identico alla richiesta originale.\n            add(url || window.tiUrl || window.location.href, baseOpts);\n            if (action) {\n                var fd = body instanceof FormData ? cloneFd(body) : formFromUrl(url || '');\n                if (!fd.has('ti_action')) fd.append('ti_action', action);\n                if (!fd.has('action')) fd.append('action', action);\n                ensureFd(fd);\n                var postOpts = Object.assign({}, baseOpts, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store'});\n                add(window.tiUrl || window.location.href.split('?')[0], postOpts);\n                if (window.tiAjaxUrl || window.ajaxurl) add(window.tiAjaxUrl || window.ajaxurl, Object.assign({}, postOpts));\n            }\n            return attempts;\n        }\n        var oldFetchJsonSafe423 = window.fetchJsonSafe;\n        window.fetchJsonSafe = function(url, options){\n            var opts = Object.assign({}, options || {});\n            var tableReadWait = false, tableReadWaitShown = false, tableReadOk = false;\n            var tableReadTitle = '\u23f3 Lettura tabelle in corso', tableReadDetail = 'Sto leggendo le tabelle della ditta. Attendi la fine del caricamento.', tableReadItem = '';\n            try {\n                tableReadWait = !!opts.tiShowTableReadWait;\n                if (opts.tiTableReadTitle) tableReadTitle = String(opts.tiTableReadTitle);\n                if (opts.tiTableReadDetail) tableReadDetail = String(opts.tiTableReadDetail);\n                if (opts.tiTableReadItem) tableReadItem = String(opts.tiTableReadItem);\n                delete opts.tiShowTableReadWait; delete opts.tiTableReadTitle; delete opts.tiTableReadDetail; delete opts.tiTableReadItem;\n            } catch(e) {}\n            var noLong = !!opts.tiNoLongProcessSignal;\n            if (Object.prototype.hasOwnProperty.call(opts, 'tiNoLongProcessSignal')) delete opts.tiNoLongProcessSignal;\n            try { if (opts.body instanceof FormData) ensureFd(opts.body); } catch(e) {}\n            if (!opts.credentials) opts.credentials = 'same-origin';\n            if (!opts.cache) opts.cache = 'no-store';\n            if (!opts.method) opts.method = opts.body ? 'POST' : 'GET';\n            if (!noLong && !opts.signal && window.tiLongProcessState && window.tiLongProcessState.controller) opts.signal = window.tiLongProcessState.controller.signal;\n            if (tableReadWait && typeof window.showTableReadWait === 'function') tableReadWaitShown = window.showTableReadWait(tableReadTitle, tableReadDetail, tableReadItem);\n            var attempts = buildAttempts(url || window.tiUrl || window.location.href, opts);\n            var lastErr = null;\n            function run(i){\n                var a = attempts[i];\n                if (!a) return Promise.reject(lastErr || new Error('Risposta server non valida.'));\n                return fetch(a.url, a.opts).then(function(resp){\n                    return resp.text().then(function(raw){\n                        if (!resp.ok) throw new Error('HTTP ' + resp.status + ': ' + (stripHtml(raw) || 'risposta server vuota'));\n                        return parseJson(raw, actionFromBody(a.opts && a.opts.body) || actionFromUrl(a.url), a.url);\n                    });\n                }).catch(function(err){\n                    lastErr = err;\n                    if (err && err.name === 'AbortError') {\n                        var abortErr = new Error('Processo interrotto dall utente.');\n                        abortErr.userCancelled = true;\n                        throw abortErr;\n                    }\n                    if (i + 1 < attempts.length) return run(i + 1);\n                    throw err;\n                });\n            }\n            var p = run(0).then(function(data){ tableReadOk = true; return data; });\n            if (tableReadWaitShown && typeof p.finally === 'function') {\n                p = p.finally(function(){ if (typeof window.hideTableReadWait === 'function') window.hideTableReadWait(tableReadOk, tableReadOk ? 'Tabelle lette' : 'Errore lettura tabelle'); });\n            }\n            return p;\n        };\n        window.tiParseJsonSafe423 = parseJson;\n        window.tiCleanJsonPreview423 = stripHtml;\n        try { window.fetchJsonSafe.__tiVersion423 = true; } catch(e) {}\n    })();\n\n    window.tiAppendCurrentLanguageToFormData = function(fd) {\n        try {\n            if (!(fd instanceof FormData)) return fd;\n            const lang = (String(window.currLang || 'it').toLowerCase().indexOf('en') === 0) ? 'en' : 'it';\n            if (!fd.has('ti_lang')) fd.append('ti_lang', lang);\n            if (!fd.has('ti_ui_lang')) fd.append('ti_ui_lang', lang);\n        } catch(e) {}\n        return fd;\n    };\n    window.buildAjaxFormData = function(actionName, extra) {\n        const fd = new FormData();\n        fd.append('action', actionName);\n        fd.append('ti_action', actionName);\n        if (extra && typeof extra === 'object') {\n            Object.keys(extra).forEach((key) => {\n                if (typeof extra[key] !== 'undefined' && extra[key] !== null) fd.append(key, extra[key]);\n            });\n        }\n        return window.tiAppendCurrentLanguageToFormData(fd);\n    };\n    window.ensureAjaxActionField = function(fd) {\n        if (!(fd instanceof FormData)) return fd;\n        if (!fd.has('action') && fd.has('ti_action')) fd.append('action', fd.get('ti_action'));\n        return window.tiAppendCurrentLanguageToFormData(fd);\n    };\n\n    window.postFormDataJsonSafe = function(url, fd, options) {\n        const formData = window.ensureAjaxActionField(fd);\n        let targetUrl = url || window.tiAjaxUrl || window.tiUrl;\n        \/\/ Le azioni di configurazione usano l'autenticazione interna del plugin.\n        \/\/ Se l'utente non e loggato in WordPress, admin-ajax.php puo rispondere HTTP 400: 0.\n        \/\/ Per queste richieste usiamo quindi l'endpoint pagina\/interceptor, che e lo stesso flusso del chatbot configurazione.\n        try {\n            const act = (formData instanceof FormData) ? String(formData.get('ti_action') || formData.get('action') || '') : '';\n            if (act === 'ti_ai_config_action' && window.tiUrl) targetUrl = window.tiUrl;\n        } catch(e) {}\n        const opts = Object.assign({method:'POST', body:formData, credentials:'same-origin', tiNoLongProcessSignal:true}, options || {});\n        return window.fetchJsonSafe(targetUrl, opts).catch(function(fetchErr){\n            if (!window.jQuery || !(formData instanceof FormData)) throw fetchErr;\n            return new Promise(function(resolve, reject){\n                window.jQuery.ajax({\n                    url: targetUrl,\n                    method: 'POST',\n                    data: formData,\n                    processData: false,\n                    contentType: false,\n                    cache: false,\n                    dataType: 'json',\n                    timeout: 180000,\n                    success: function(data){ resolve(data); },\n                    error: function(xhr, textStatus, errorThrown){\n                        let msg = errorThrown || textStatus || 'errore rete';\n                        try {\n                            const txt = xhr && xhr.responseText ? String(xhr.responseText).trim() : '';\n                            if (txt) msg = txt.slice(0, 220);\n                        } catch(e) {}\n                        const err = new Error('XHR fallback fallito: ' + msg);\n                        err.originalFetchError = fetchErr;\n                        reject(err);\n                    }\n                });\n            });\n        });\n    };\n\n    if (!window.__tiFetchLanguageWrapped && window.fetch) {\n        window.__tiFetchLanguageWrapped = true;\n        window.__tiOriginalFetchForLang = window.fetch.bind(window);\n        window.fetch = function(input, init) {\n            try {\n                if (init && init.body instanceof FormData && window.tiAppendCurrentLanguageToFormData) {\n                    if (init.body.has('ti_action') && !init.body.has('action')) init.body.append('action', init.body.get('ti_action'));\n                    window.tiAppendCurrentLanguageToFormData(init.body);\n                }\n            } catch(e) {}\n            return window.__tiOriginalFetchForLang(input, init);\n        };\n    }\n\n    window.tiLongProcessState = { controller: null, cancellable: false, cancelled: false, label: '' };\n    window.beginLongAIProcess = function(label, cancellable = true) {\n        if (window.tiLongProcessState.controller && !window.tiLongProcessState.cancelled) {\n            try { window.tiLongProcessState.controller.abort(); } catch(e) {}\n        }\n        window.tiLongProcessState = {\n            controller: new AbortController(),\n            cancellable: !!cancellable,\n            cancelled: false,\n            label: label || 'Processo AI'\n        };\n        return window.tiLongProcessState.controller;\n    };\n    window.clearLongAIProcess = function() {\n        window.tiLongProcessState = { controller: null, cancellable: false, cancelled: false, label: '' };\n    };\n    window.isLongAIProcessCancelled = function() {\n        return !!(window.tiLongProcessState && window.tiLongProcessState.cancelled);\n    };\n    window.cancelLongAIProcess = function() {\n        if (typeof window.confirmCancelBatchAIGen === 'function' && window.confirmCancelBatchAIGen()) return;\n        const state = window.tiLongProcessState || {};\n        if (!state.controller || !state.cancellable) { window.closeModal('ti-ai-loader-ov'); return; }\n        state.cancelled = true;\n        try { state.controller.abort(); } catch(e) {}\n        const ov = gE('ti-ai-loader-ov');\n        window.closeModal('ti-ai-loader-ov');\n        window.tiAlert('Processo AI interrotto. Per completarlo dovrai ripetere l operazione.');\n    };\n\n    window.tiModalBaseZ = 2147482000;\n    window.tiModalMaxZ = 2147483647;\n    window.tiModalCommunicationZ = {\n        'ti-alert-ov': 2147483647,\n        'ti-ai-loader-ov': 2147483646\n    };\n    window.tiModalStack = 0;\n    window.setPluginModalZIndex = function(el, z) {\n        if (!el) return;\n        const zi = Math.max(1, Math.min(window.tiModalMaxZ || 2147483647, parseInt(z, 10) || window.tiModalBaseZ || 2147482000));\n        try { el.style.setProperty('z-index', String(zi), 'important'); }\n        catch(e) { el.style.zIndex = String(zi); }\n    };\n    window.getPluginModalBaseZ = function(el) {\n        if (el && el.id && window.tiModalCommunicationZ && window.tiModalCommunicationZ[el.id]) return window.tiModalCommunicationZ[el.id];\n        return window.tiModalBaseZ || 2147482000;\n    };\n    window.ensurePluginModalRoot = function() {\n        let root = document.getElementById('ti-plugin-modal-root');\n        if (!root) {\n            root = document.createElement('div');\n            root.id = 'ti-plugin-modal-root';\n            root.style.setProperty('z-index', '2147483000', 'important');\n            document.body.appendChild(root);\n        }\n        try { root.style.setProperty('z-index', '2147483000', 'important'); } catch(e) {}\n        return root;\n    };\n    window.getPluginModalClosePanel = function(ov) {\n        if (!ov || !ov.children) return null;\n        const direct = Array.from(ov.children || []).find(function(ch){ return ch && ch.classList && (ch.classList.contains('ti-conf-window') || ch.classList.contains('ti-modal')); });\n        if (direct) return direct;\n        return ov.querySelector ? ov.querySelector('.ti-conf-window, .ti-modal') : null;\n    };\n    window.closePluginFormModal = function(id) {\n        if (id === 'ti-conf-ov' && typeof window.closeConfigModal === 'function') return window.closeConfigModal();\n        if (id === 'ti-cam-ov' && typeof window.closeCamModal === 'function') return window.closeCamModal();\n        if (id === 'ti-order-item-ov' && typeof window.closeOrderItemPopup === 'function') return window.closeOrderItemPopup();\n        return window.closeModal(id);\n    };\n    window.ensureClosableModalControls = function(root) {\n        const scope = root && root.querySelectorAll ? root : document;\n        const overlays = scope.matches && scope.matches('.ti-ov') ? [scope] : Array.from(scope.querySelectorAll ? scope.querySelectorAll('.ti-ov') : []);\n        overlays.forEach(function(ov){\n            if (!ov || !ov.id || ov.id === 'ti-img-ov' || ov.id === 'ti-alert-ov' || ov.id === 'ti-ai-loader-ov' || ov.id === 'ti-maintenance-ov' || ov.getAttribute('data-ti-non-closable') === '1') return;\n            const panel = window.getPluginModalClosePanel(ov);\n            if (!panel) return;\n            if (!panel.style.position || panel.style.position === 'static') panel.style.position = 'relative';\n            if (!panel.querySelector('.ti-modal-x-red')) {\n                const x = document.createElement('button');\n                x.type = 'button';\n                x.className = 'ti-modal-x-red';\n                x.setAttribute('aria-label', 'Chiudi');\n                x.title = 'Chiudi';\n                x.textContent = '\u00d7';\n                x.addEventListener('click', function(ev){ ev.preventDefault(); ev.stopPropagation(); window.closePluginFormModal(ov.id); });\n                panel.appendChild(x);\n            }\n            const hasCloseBtn = !!panel.querySelector('.ti-modal-bottom-close-row, [data-ti-bottom-close=\"1\"]') || Array.from(panel.querySelectorAll ? panel.querySelectorAll('button, a') : []).some(function(b){ return \/\\bchiudi\\b\/i.test(String(b.textContent || '')); });\n            if (!hasCloseBtn) {\n                const row = document.createElement('div');\n                row.className = 'ti-modal-bottom-close-row';\n                row.setAttribute('data-ti-bottom-close', '1');\n                const btn = document.createElement('button');\n                btn.type = 'button';\n                btn.className = 'ti-btn btn-close';\n                btn.textContent = 'Chiudi';\n                btn.addEventListener('click', function(ev){ ev.preventDefault(); ev.stopPropagation(); window.closePluginFormModal(ov.id); });\n                row.appendChild(btn);\n                panel.appendChild(row);\n            }\n        });\n    };\n    window.portalizePluginModals = function() {\n        const root = window.ensurePluginModalRoot();\n        document.querySelectorAll('.ti-ov').forEach(function(el){\n            if (el.parentNode !== root) root.appendChild(el);\n            el.style.position = 'fixed';\n            el.style.inset = '0';\n            window.setPluginModalZIndex(el, window.getPluginModalBaseZ(el));\n        });\n        if (window.ensureClosableModalControls) window.ensureClosableModalControls(root);\n    };\n    window.bringPluginModalToFront = function(el) {\n        if (!el) return;\n        if (el.id && window.tiModalCommunicationZ && window.tiModalCommunicationZ[el.id]) {\n            window.setPluginModalZIndex(el, window.tiModalCommunicationZ[el.id]);\n            return;\n        }\n        window.tiModalStack = (window.tiModalStack || 0) + 1;\n        if (window.tiModalStack > 1200) window.tiModalStack = 1;\n        window.setPluginModalZIndex(el, (window.tiModalBaseZ || 2147482000) + window.tiModalStack);\n    };\n    window.keepUserCommunicationPopupsInFront = function() {\n        ['ti-ai-loader-ov', 'ti-admin-comm-ov', 'ti-alert-ov'].forEach(function(id){\n            const el = gE(id);\n            if (!el) return;\n            const disp = String(el.style.display || '').replace(\/\\s+\/g, '').toLowerCase();\n            if (disp === 'flex' || disp === 'block') {\n                window.bringPluginModalToFront(el);\n                if (id === 'ti-alert-ov') {\n                    const btn = gE('ti-alert-no') || gE('ti-alert-yes');\n                    if (btn && typeof btn.focus === 'function') {\n                        setTimeout(function(){ try { btn.focus({preventScroll:true}); } catch(e) { try { btn.focus(); } catch(e2) {} } }, 0);\n                    }\n                }\n            }\n        });\n    };\n    window.preparePluginModal = function(id) {\n        const el = typeof id === 'string' ? gE(id) : id;\n        if (!el) return null;\n        window.portalizePluginModals();\n        const root = window.ensurePluginModalRoot();\n        if (el.parentNode !== root) root.appendChild(el);\n        el.style.position = 'fixed';\n        el.style.inset = '0';\n        window.setPluginModalZIndex(el, window.getPluginModalBaseZ(el));\n        return el;\n    };\n    window.showPluginModal = function(id) {\n        const el = window.preparePluginModal(id);\n        if (!el) return null;\n        if (window.ensureClosableModalControls) window.ensureClosableModalControls(el);\n        window.bringPluginModalToFront(el);\n        el.style.display = 'flex';\n        try { if (window.applyLayoutLanguage) window.applyLayoutLanguage(true); } catch(e) {}\n        window.keepUserCommunicationPopupsInFront();\n        window.updatePluginScrollLock();\n        return el;\n    };\n    window.updatePluginScrollLock = function() {\n        const hasOpen = !!document.querySelector('.ti-ov[style*=\"display: flex\"], .ti-ov[style*=\"display:flex\"], #ti-ai-loader-ov[style*=\"display: flex\"], #ti-ai-loader-ov[style*=\"display:flex\"]');\n        const isMobile = window.tiIsMobileLike ? window.tiIsMobileLike() : !!(window.matchMedia && window.matchMedia('(max-width: 900px)').matches);\n        \/\/ Su smartphone e dispositivi touch non bloccare mai lo scroll del body:\n        \/\/ alcuni browser mobili restano non scorrevoli dopo login\/modal.\n        if (isMobile) {\n            document.documentElement.classList.remove('ti-no-scroll');\n            document.body.classList.remove('ti-no-scroll');\n            document.documentElement.style.overflowY = 'auto';\n            document.body.style.overflowY = 'auto';\n            document.documentElement.style.overflowX = 'hidden';\n            document.body.style.overflowX = 'hidden';\n            document.documentElement.style.height = 'auto';\n            document.body.style.height = 'auto';\n            document.documentElement.style.position = 'static';\n            document.body.style.position = 'static';\n            document.documentElement.style.touchAction = 'pan-y';\n            document.body.style.touchAction = 'pan-y';\n            return;\n        }\n        document.documentElement.classList.toggle('ti-no-scroll', hasOpen);\n        document.body.classList.toggle('ti-no-scroll', hasOpen);\n    };\n    window.keepPluginModalInFront = function(id) {\n        const el = typeof id === 'string' ? gE(id) : id;\n        if (!el) return null;\n        if (el.style.display === 'flex' || el.style.display === 'block') {\n            window.preparePluginModal(el);\n            window.bringPluginModalToFront(el);\n            window.keepUserCommunicationPopupsInFront();\n        }\n        return el;\n    };\n    window.openModal = function(id) {\n        try {\n            if (window.updateMobileViewportHeight) window.updateMobileViewportHeight();\n            window.showPluginModal(id);\n            if (id === 'ti-login-ov') {\n                setTimeout(function(){\n                    if (window.tiMobileUnlockScroll) window.tiMobileUnlockScroll();\n                    if (!(window.tiIsMobileLike && window.tiIsMobileLike())) {\n                        const u = gE('l-user');\n                        if (u && typeof u.focus === 'function') { try { u.focus({preventScroll:true}); } catch(e) {} }\n                    }\n                    if (window.bindMobileLoginButton) window.bindMobileLoginButton();\n                }, 120);\n            }\n        } catch(e) {}\n    };\n    window.closeModal = function(id) {\n        try {\n            const el = typeof id === 'string' ? gE(id) : id;\n            if (el) {\n                if (el.id === 'ti-maintenance-ov' && window.tiMaintenanceState && window.tiMaintenanceState.active && !(window.tiMaintenanceState.can_configure)) return;\n                el.style.display = 'none';\n                window.updatePluginScrollLock();\n                window.keepUserCommunicationPopupsInFront();\n            }\n        } catch(e) {}\n    };\n    try { window.portalizePluginModals(); if (window.ensureClosableModalControls) window.ensureClosableModalControls(); } catch(e) {}\n    if (typeof window.initPluginDateInputs !== 'function') {\n        window.initPluginDateInputs = function(root) {\n            const scope = root || document;\n            scope.querySelectorAll('input[type=\"date\"], input[type=\"time\"]').forEach(function(inp){\n                const v = String(inp.value || '').trim();\n                if (inp.type === 'date' && !\/^\\d{4}-\\d{2}-\\d{2}$\/.test(v)) inp.value = '';\n                if (inp.type === 'time' && !\/^\\d{2}:\\d{2}$\/.test(v)) inp.value = '';\n                if (inp.type === 'date') { inp.placeholder = 'gg\/mm\/aaaa'; inp.title = inp.title || 'Seleziona una data'; }\n                if (inp.type === 'time') { inp.placeholder = 'hh:mm'; inp.title = inp.title || 'Seleziona un orario'; }\n            });\n        };\n    }\n\n    window.closeConfigModal = function() {\n        if (window.hasPendingImportAssociations && window.hasPendingImportAssociations()) {\n            const importOv = gE('ti-conf-import-preview-ov');\n            const previewOpen = importOv && String(importOv.style.display || '').indexOf('flex') !== -1;\n            if (previewOpen) {\n                window.closeConfigImportPreview(false);\n                return;\n            }\n        }\n        const hadSaved = !!window.configWasSaved;\n        window.closeModal('ti-conf-ov');\n        if (!hadSaved) {\n            window.currentDbData = null;\n            window.configOriginalDbData = null;\n            if (window.modifiedFields && typeof window.modifiedFields.clear === 'function') window.modifiedFields.clear();\n            if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState();\n            return;\n        }\n        window.configWasSaved = false;\n        const fd = window.buildAjaxFormData('ti_ai_reset_flow');\n        fetch(window.tiUrl, {method:'POST', body:window.ensureAjaxActionField(fd)}).then(() => location.reload());\n    };\n\n    window.tiLoaderState = { timer: null, percent: 0, target: 90, startedAt: 0, active: false };\n    window.showProgressPopup = function(title, subtitle, opts = {}) {\n        const ov = gE('ti-ai-loader-ov'); if(!ov) return;\n        const titleEl = gE('ti-loader-title'); const subEl = gE('ti-loader-sub'); const itemEl = gE('ti-loader-current-item');\n        const wrap = gE('ti-loader-progress-wrap'); const pct = gE('ti-loader-percent'); const bar = gE('ti-loader-bar');\n        const actions = gE('ti-loader-actions');\n        const cancellable = opts.cancellable !== false;\n        window.beginLongAIProcess(title || 'Elaborazione AI in corso...', cancellable);\n        if (titleEl) titleEl.innerText = title || 'Elaborazione AI in corso...';\n        if (subEl) subEl.innerText = subtitle || \"Attendi, l'operazione potrebbe richiedere alcuni secondi.\";\n        if (itemEl) {\n            const currentItem = String(opts.currentItem || opts.itemName || '').trim();\n            itemEl.style.display = currentItem ? 'block' : 'none';\n            itemEl.innerText = currentItem ? ('Elemento in lavorazione: ' + currentItem) : '';\n        }\n        if (wrap) wrap.style.display = 'block';\n        if (actions) actions.style.display = cancellable ? 'block' : 'none';\n        window.tiLoaderState.active = true;\n        window.tiLoaderState.percent = Math.max(0, Math.min(100, parseInt(opts.startPercent || 0, 10) || 0));\n        window.tiLoaderState.target = Math.max(window.tiLoaderState.percent, Math.min(99, parseInt(opts.targetPercent || 90, 10) || 90));\n        window.tiLoaderState.startedAt = Date.now();\n        if (pct) pct.innerText = window.tiLoaderState.percent + '%';\n        if (bar) bar.style.width = window.tiLoaderState.percent + '%';\n        window.showPluginModal('ti-ai-loader-ov');\n        clearInterval(window.tiLoaderState.timer);\n        const stepMs = Math.max(150, parseInt(opts.stepMs || 500, 10) || 500);\n        window.tiLoaderState.timer = setInterval(() => {\n            if (!window.tiLoaderState.active) return;\n            let inc = 1;\n            if (window.tiLoaderState.percent < 20) inc = 4;\n            else if (window.tiLoaderState.percent < 45) inc = 3;\n            else if (window.tiLoaderState.percent < 70) inc = 2;\n            else inc = 1;\n            window.tiLoaderState.percent = Math.min(window.tiLoaderState.target, window.tiLoaderState.percent + inc);\n            if (pct) pct.innerText = window.tiLoaderState.percent + '%';\n            if (bar) bar.style.width = window.tiLoaderState.percent + '%';\n            if (window.tiLoaderState.percent >= window.tiLoaderState.target) clearInterval(window.tiLoaderState.timer);\n        }, stepMs);\n    };\n    window.updateProgressPopup = function(percent, title, subtitle, currentItem) {\n        const ov = gE('ti-ai-loader-ov'); if(!ov) return;\n        const titleEl = gE('ti-loader-title'); const subEl = gE('ti-loader-sub'); const itemEl = gE('ti-loader-current-item');\n        const wrap = gE('ti-loader-progress-wrap'); const pct = gE('ti-loader-percent'); const bar = gE('ti-loader-bar');\n        window.tiLoaderState.active = true;\n        clearInterval(window.tiLoaderState.timer);\n        if (titleEl && title) titleEl.innerText = title;\n        if (subEl && subtitle) subEl.innerText = subtitle;\n        if (itemEl) {\n            const itemName = String(currentItem || '').trim();\n            itemEl.style.display = itemName ? 'block' : 'none';\n            itemEl.innerText = itemName ? ('Elemento in lavorazione: ' + itemName) : '';\n        }\n        if (wrap) wrap.style.display = 'block';\n        window.tiLoaderState.percent = Math.max(0, Math.min(100, parseInt(percent, 10) || 0));\n        if (pct) pct.innerText = window.tiLoaderState.percent + '%';\n        if (bar) bar.style.width = window.tiLoaderState.percent + '%';\n        window.showPluginModal('ti-ai-loader-ov');\n    };\n    window.hideProgressPopup = function(success = true, finalText = '', opts = {}) {\n        const ov = gE('ti-ai-loader-ov'); if(!ov) return;\n        const titleEl = gE('ti-loader-title'); const subEl = gE('ti-loader-sub'); const itemEl = gE('ti-loader-current-item'); const pct = gE('ti-loader-percent'); const bar = gE('ti-loader-bar');\n        const actions = gE('ti-loader-actions');\n        const wasCancelled = window.isLongAIProcessCancelled();\n        window.tiLoaderState.active = false;\n        clearInterval(window.tiLoaderState.timer);\n        if (actions) actions.style.display = 'none';\n        if (itemEl) { itemEl.style.display = 'none'; itemEl.innerText = ''; }\n        const remindSave = !!opts.remindSave && window.modifiedFields && window.modifiedFields.size > 0;\n        const successMsg = (opts.successMessage || finalText || 'Operazione AI completata') + (remindSave ? '\\n\\nRicorda di salvare le modifiche se non lo hai ancora fatto.' : '');\n        const errorMsg = opts.errorMessage || finalText || 'Operazione interrotta';\n        if (success) {\n            if (titleEl) titleEl.innerText = finalText || 'Operazione completata';\n            if (subEl) subEl.innerText = remindSave ? 'Attivit\u00e0 AI conclusa. Sono presenti modifiche non ancora salvate.' : 'Completamento confermato dal sistema.';\n            if (pct) pct.innerText = '100%';\n            if (bar) bar.style.width = '100%';\n            setTimeout(() => {\n                window.closeModal('ti-ai-loader-ov');\n                window.clearLongAIProcess();\n                if (opts.notifyUser !== false) { window.tiAlert(successMsg); }\n            }, 450);\n        } else {\n            if (titleEl) titleEl.innerText = finalText || 'Operazione interrotta';\n            if (subEl) subEl.innerText = 'Non e stato possibile completare l attivita richiesta.';\n            setTimeout(() => {\n                window.closeModal('ti-ai-loader-ov');\n                if (opts.notifyUser !== false) { window.tiAlert(errorMsg); }\n            }, 250);\n        }\n    };\n\n    \/\/ 30.9.327: watchdog robusto per avanzamento stimato nelle elaborazioni lunghe.\n    \/\/ Evita che la barra resti a percentuali basse quando il browser rallenta i timer\n    \/\/ o quando il server ha gi\u00e0 concluso ma il client non ha ancora aggiornato la UI.\n    (function installTiRobustLongProgress327(){\n        if (window.__tiRobustLongProgress327Installed) return;\n        window.__tiRobustLongProgress327Installed = true;\n        function clampPct(v) {\n            v = parseInt(v, 10);\n            if (!isFinite(v) || isNaN(v)) v = 0;\n            return Math.max(0, Math.min(100, v));\n        }\n        function completionText(txt) {\n            txt = String(txt || '').toLowerCase();\n            \/\/ 30.9.330: non considerare completato un processo di configurazione se la risposta sta chiedendo specifiche all'utente\n            \/\/ o se non ha aggiornato record. La barra a 100% deve arrivare solo per un esito realmente conclusivo.\n            if (\/chiarimento richiesto|indicami|specifiche|specifica|quali record|criterio piu preciso|criterio pi\u00f9 preciso|record aggiornati:\\s*0\/.test(txt)) return false;\n            return \/completamento confermato|operazione completata|completata|completato|conclusa|concluso|tabelle lette|anteprima import pronta\/.test(txt);\n        }\n        function setLoaderPercent327(percent, title, subtitle, currentItem) {\n            const state = window.tiLoaderState || (window.tiLoaderState = {});\n            const pct = clampPct(percent);\n            const pctEl = gE('ti-loader-percent');\n            const barEl = gE('ti-loader-bar');\n            const titleEl = gE('ti-loader-title');\n            const subEl = gE('ti-loader-sub');\n            const itemEl = gE('ti-loader-current-item');\n            state.percent = pct;\n            if (pctEl) pctEl.innerText = pct + '%';\n            if (barEl) barEl.style.width = pct + '%';\n            if (titleEl && title) titleEl.innerText = String(title);\n            if (subEl && subtitle) subEl.innerText = String(subtitle);\n            if (itemEl && currentItem !== undefined) {\n                const itemName = String(currentItem || '').trim();\n                itemEl.style.display = itemName ? 'block' : 'none';\n                itemEl.innerText = itemName ? ('Elemento in lavorazione: ' + itemName) : '';\n            }\n        }\n        function stopWatch327() {\n            const state = window.tiLoaderState || {};\n            if (state.watchdog327) clearInterval(state.watchdog327);\n            state.watchdog327 = null;\n        }\n        function startWatch327(opts) {\n            opts = opts || {};\n            const state = window.tiLoaderState || (window.tiLoaderState = {});\n            stopWatch327();\n            state.watchStart327 = Date.now();\n            state.watchStartPercent327 = clampPct(state.percent || opts.startPercent || 0);\n            state.watchTarget327 = Math.max(state.watchStartPercent327, Math.min(98, clampPct(opts.targetPercent || state.target || 94)));\n            \/\/ Tempo atteso realistico: se i timer sono sospesi, al tick successivo la percentuale salta in avanti in base al tempo trascorso.\n            state.watchExpectedMs327 = Math.max(30000, Math.min(120000, parseInt(opts.expectedMs || 55000, 10) || 55000));\n            state.watchdog327 = setInterval(function(){\n                try {\n                    if (!state.active) { stopWatch327(); return; }\n                    const elapsed = Math.max(0, Date.now() - (state.watchStart327 || Date.now()));\n                    const start = clampPct(state.watchStartPercent327 || 0);\n                    const target = Math.max(start, Math.min(98, clampPct(state.watchTarget327 || state.target || 94)));\n                    let estimated = start + ((target - start) * Math.min(1, elapsed \/ (state.watchExpectedMs327 || 55000)));\n                    if (elapsed > (state.watchExpectedMs327 || 55000)) {\n                        const extra = Math.min(1, (elapsed - (state.watchExpectedMs327 || 55000)) \/ 90000);\n                        estimated = target + ((99 - target) * extra);\n                    }\n                    if (elapsed > 180000) estimated = 99;\n                    let current = clampPct(state.percent || 0);\n                    if (Math.floor(estimated) > current) {\n                        let sub = '';\n                        if (elapsed > 90000) sub = 'Elaborazione ancora in corso lato server. Attendo la risposta conclusiva e verifico il completamento.';\n                        setLoaderPercent327(Math.floor(estimated), '', sub, undefined);\n                    }\n                    const titleTxt = gE('ti-loader-title') ? gE('ti-loader-title').innerText : '';\n                    const subTxt = gE('ti-loader-sub') ? gE('ti-loader-sub').innerText : '';\n                    if (completionText(titleTxt + ' ' + subTxt)) {\n                        setLoaderPercent327(100, '', 'Completamento confermato dal sistema.', undefined);\n                        stopWatch327();\n                    }\n                } catch(e) {}\n            }, 1000);\n        }\n        if (typeof window.showProgressPopup === 'function' && !window.showProgressPopup.__tiRobustProgress327) {\n            const prevShow = window.showProgressPopup;\n            window.showProgressPopup = function(title, subtitle, opts) {\n                const ret = prevShow.apply(this, arguments);\n                startWatch327(opts || {});\n                return ret;\n            };\n            window.showProgressPopup.__tiRobustProgress327 = true;\n        }\n        if (typeof window.updateProgressPopup === 'function' && !window.updateProgressPopup.__tiRobustProgress327) {\n            const prevUpdate = window.updateProgressPopup;\n            window.updateProgressPopup = function(percent, title, subtitle, currentItem) {\n                const state = window.tiLoaderState || {};\n                let pct = clampPct(percent);\n                const cur = clampPct(state.percent || 0);\n                \/\/ La percentuale non deve regredire per aggiornamenti parziali\/fallback.\n                if (pct < 100 && cur > pct) pct = cur;\n                const ret = prevUpdate.call(this, pct, title, subtitle, currentItem);\n                if (pct >= 100 || completionText(String(title || '') + ' ' + String(subtitle || ''))) {\n                    setLoaderPercent327(100, title || '', subtitle || 'Completamento confermato dal sistema.', currentItem);\n                    stopWatch327();\n                }\n                return ret;\n            };\n            window.updateProgressPopup.__tiRobustProgress327 = true;\n        }\n        if (typeof window.hideProgressPopup === 'function' && !window.hideProgressPopup.__tiRobustProgress327) {\n            const prevHide = window.hideProgressPopup;\n            window.hideProgressPopup = function(success, finalText, opts) {\n                if (success !== false) {\n                    setLoaderPercent327(100, finalText || 'Operazione completata', 'Completamento confermato dal sistema.', undefined);\n                    stopWatch327();\n                }\n                return prevHide.apply(this, arguments);\n            };\n            window.hideProgressPopup.__tiRobustProgress327 = true;\n        }\n        window.tiForceLoaderComplete327 = function(title, subtitle) {\n            setLoaderPercent327(100, title || 'Operazione completata', subtitle || 'Completamento confermato dal sistema.', undefined);\n            stopWatch327();\n        };\n    })();\n\n\n    \/\/ 30.9.338: guardia anti-blocco per popup avanzamento rimasto a 100%.\n    \/\/ Caso reale: semplice accesso a Configurazione, \"Tabelle lette\" e \"Completamento confermato dal sistema\"\n    \/\/ rimanevano visibili anche a processo finito. Questa guardia chiude il loader concluso\n    \/\/ senza uscire dalla Configurazione e senza interferire con popup\/alert utente.\n    (function installTiProgressStallGuard338(){\n        if (window.__tiProgressStallGuard338Installed) return;\n        window.__tiProgressStallGuard338Installed = true;\n        function txt(id) { const el = gE(id); return el ? String(el.innerText || el.textContent || '') : ''; }\n        function pctVal() { const raw = txt('ti-loader-percent').replace(\/[^0-9]\/g, ''); const n = parseInt(raw || '0', 10); return isNaN(n) ? 0 : n; }\n        function loaderEl() { return gE('ti-ai-loader-ov'); }\n        function isVisible(el) { return !!el && (el.style.display === 'flex' || el.style.display === 'block'); }\n        function isCompletionText(v) {\n            v = String(v || '').toLowerCase();\n            if (\/chiarimento richiesto|indicami|specifiche|specifica|quali record|criterio piu preciso|criterio pi\u00f9 preciso|record aggiornati:\\s*0|errore|fallito|interrotto\/.test(v)) return false;\n            return \/completamento confermato|operazione completata|completata|completato|conclusa|concluso|tabelle lette|anteprima import pronta\/.test(v);\n        }\n        function shouldAutoClose() {\n            const ov = loaderEl();\n            if (!isVisible(ov)) return false;\n            const combined = txt('ti-loader-title') + ' ' + txt('ti-loader-sub');\n            return pctVal() >= 100 && isCompletionText(combined);\n        }\n        function forceClose(reason) {\n            try {\n                const state = window.tiLoaderState || {};\n                state.active = false;\n                if (state.timer) clearInterval(state.timer);\n                if (state.watchdog327) clearInterval(state.watchdog327);\n                if (state.stallGuard338) clearInterval(state.stallGuard338);\n                state.timer = null;\n                state.watchdog327 = null;\n                const ov = loaderEl();\n                if (ov) ov.style.display = 'none';\n                if (typeof window.updatePluginScrollLock === 'function') window.updatePluginScrollLock();\n                if (typeof window.clearLongAIProcess === 'function') window.clearLongAIProcess();\n                if (typeof window.keepUserCommunicationPopupsInFront === 'function') window.keepUserCommunicationPopupsInFront();\n                if (window.console && console.info) console.info('[TI Shop Service] loader auto-closed 30.9.338:', reason || 'completed');\n            } catch(e) {}\n        }\n        function scheduleClose(reason, delay) {\n            try {\n                const state = window.tiLoaderState || (window.tiLoaderState = {});\n                if (state.closeTimer338) clearTimeout(state.closeTimer338);\n                state.closeTimer338 = setTimeout(function(){ if (shouldAutoClose()) forceClose(reason); }, Math.max(250, delay || 900));\n            } catch(e) {}\n        }\n        window.tiForceCloseCompletedLoader338 = function(reason) {\n            if (shouldAutoClose()) forceClose(reason || 'manual-check');\n        };\n        if (typeof window.updateProgressPopup === 'function' && !window.updateProgressPopup.__tiProgressStallGuard338) {\n            const prevUpdate338 = window.updateProgressPopup;\n            window.updateProgressPopup = function(percent, title, subtitle, currentItem) {\n                const ret = prevUpdate338.apply(this, arguments);\n                const combined = String(title || '') + ' ' + String(subtitle || '') + ' ' + txt('ti-loader-title') + ' ' + txt('ti-loader-sub');\n                if ((parseInt(percent, 10) || 0) >= 100 || isCompletionText(combined)) scheduleClose('updateProgressPopup', 900);\n                return ret;\n            };\n            window.updateProgressPopup.__tiProgressStallGuard338 = true;\n        }\n        if (typeof window.hideProgressPopup === 'function' && !window.hideProgressPopup.__tiProgressStallGuard338) {\n            const prevHide338 = window.hideProgressPopup;\n            window.hideProgressPopup = function(success, finalText, opts) {\n                const ret = prevHide338.apply(this, arguments);\n                if (success !== false) scheduleClose('hideProgressPopup', 1400);\n                return ret;\n            };\n            window.hideProgressPopup.__tiProgressStallGuard338 = true;\n        }\n        if (typeof window.hideTableReadWait === 'function' && !window.hideTableReadWait.__tiProgressStallGuard338) {\n            const prevHideTable338 = window.hideTableReadWait;\n            window.hideTableReadWait = function(success, finalText) {\n                const ret = prevHideTable338.apply(this, arguments);\n                if (success !== false) scheduleClose('hideTableReadWait', 750);\n                return ret;\n            };\n            window.hideTableReadWait.__tiProgressStallGuard338 = true;\n        }\n        const state = window.tiLoaderState || (window.tiLoaderState = {});\n        if (state.stallGuard338) clearInterval(state.stallGuard338);\n        state.stallGuard338 = setInterval(function(){\n            try {\n                if (!shouldAutoClose()) return;\n                if (!state.completedVisibleSince338) state.completedVisibleSince338 = Date.now();\n                if ((Date.now() - state.completedVisibleSince338) > 2500) forceClose('periodic-stall-guard');\n            } catch(e) {}\n        }, 1000);\n        document.addEventListener('visibilitychange', function(){\n            if (!document.hidden) scheduleClose('visibility-return', 650);\n        });\n        window.addEventListener('focus', function(){ scheduleClose('window-focus', 650); });\n    })();\n\n\n    \/\/ 30.9.339: rimozione forzata di loader conclusi rimasti visibili in Configurazione.\n    \/\/ Alcuni browser\/sovrapposizioni possono impedire a closeModal() di togliere il box dal DOM;\n    \/\/ se il testo indica chiaramente \"Tabelle lette \/ Completamento confermato \/ 100%\", il box viene nascosto fisicamente.\n    (function installTiCompletedLoaderDomReaper339(){\n        if (window.__tiCompletedLoaderDomReaper339Installed) return;\n        window.__tiCompletedLoaderDomReaper339Installed = true;\n        function byId(id){ return document.getElementById(id); }\n        function textOf(el){ return el ? String(el.innerText || el.textContent || '') : ''; }\n        function norm(s){ return String(s || '').toLowerCase().replace(\/\\s+\/g, ' ').trim(); }\n        function isBadCompletionText(s){\n            s = norm(s);\n            if (!s) return false;\n            if (\/errore|fallito|interrotto|annulla|chiarimento|specifiche|indicami|record aggiornati\\s*0\/.test(s)) return false;\n            return \/tabelle lette\/.test(s) && \/completamento confermato\/.test(s) && \/avanzamento stimato\/.test(s) && \/100\\s*%\/.test(s);\n        }\n        function isVisible(el){\n            if (!el) return false;\n            try {\n                var cs = window.getComputedStyle(el);\n                if (!cs || cs.display === 'none' || cs.visibility === 'hidden' || parseFloat(cs.opacity || '1') === 0) return false;\n                var r = el.getBoundingClientRect();\n                return r.width > 0 && r.height > 0;\n            } catch(e) {\n                return !!(el && el.offsetParent !== null);\n            }\n        }\n        function hardHide(el, reason){\n            if (!el) return false;\n            try {\n                if (window.console && console.info) console.info('[TI Shop Service] completed loader removed 30.9.339:', reason || '', el.id || el.className || el.tagName);\n            } catch(e) {}\n            try { el.classList.add('ti-loader-force-hidden-339'); } catch(e) {}\n            try {\n                el.style.setProperty('display', 'none', 'important');\n                el.style.setProperty('visibility', 'hidden', 'important');\n                el.style.setProperty('pointer-events', 'none', 'important');\n                el.style.setProperty('opacity', '0', 'important');\n                el.setAttribute('aria-hidden', 'true');\n            } catch(e) {}\n            try {\n                var st = window.tiLoaderState || (window.tiLoaderState = {});\n                st.active = false;\n                if (st.timer) clearInterval(st.timer);\n                if (st.watchdog327) clearInterval(st.watchdog327);\n                if (st.stallGuard338) clearInterval(st.stallGuard338);\n                if (st.closeTimer338) clearTimeout(st.closeTimer338);\n                st.timer = null; st.watchdog327 = null; st.closeTimer338 = null;\n            } catch(e) {}\n            try { if (typeof window.clearLongAIProcess === 'function') window.clearLongAIProcess(); } catch(e) {}\n            try { if (typeof window.updatePluginScrollLock === 'function') window.updatePluginScrollLock(); } catch(e) {}\n            return true;\n        }\n        function findSmallestCompletedNode(){\n            var nodes = [];\n            try {\n                nodes = Array.from(document.querySelectorAll('#ti-ai-loader-ov, .ti-loader-box, #ti-plugin-modal-root > *, .ti-ov, [id*=\"loader\"], [class*=\"loader\"], [id*=\"progress\"], [class*=\"progress\"], [id*=\"wait\"], [class*=\"wait\"]'));\n            } catch(e) { nodes = []; }\n            var best = null, bestArea = Infinity;\n            nodes.forEach(function(el){\n                if (!el || el.id === 'ti-conf-ov') return;\n                if (!isVisible(el)) return;\n                var tx = textOf(el);\n                if (!isBadCompletionText(tx)) return;\n                var closeTarget = el.closest && el.closest('#ti-ai-loader-ov') ? el.closest('#ti-ai-loader-ov') : el;\n                if (closeTarget && closeTarget.id === 'ti-conf-ov') closeTarget = el;\n                var r = closeTarget.getBoundingClientRect ? closeTarget.getBoundingClientRect() : {width:9999,height:9999};\n                var area = Math.max(1, (r.width || 1) * (r.height || 1));\n                if (area < bestArea) { bestArea = area; best = closeTarget; }\n            });\n            return best;\n        }\n        function reap(reason){\n            var changed = false;\n            try {\n                var ov = byId('ti-ai-loader-ov');\n                if (ov && isBadCompletionText(textOf(ov))) changed = hardHide(ov, reason || 'loader-overlay') || changed;\n            } catch(e) {}\n            try {\n                var box = findSmallestCompletedNode();\n                if (box) changed = hardHide(box, reason || 'completed-node') || changed;\n            } catch(e) {}\n            return changed;\n        }\n        window.tiReapCompletedLoader339 = reap;\n        function schedule(reason, delay){ setTimeout(function(){ reap(reason); }, delay || 120); }\n        if (typeof window.updateProgressPopup === 'function' && !window.updateProgressPopup.__tiCompletedLoaderDomReaper339) {\n            var prevUpdate339 = window.updateProgressPopup;\n            window.updateProgressPopup = function(){\n                var ret = prevUpdate339.apply(this, arguments);\n                schedule('updateProgressPopup', 180);\n                schedule('updateProgressPopup-late', 900);\n                return ret;\n            };\n            window.updateProgressPopup.__tiCompletedLoaderDomReaper339 = true;\n        }\n        if (typeof window.hideProgressPopup === 'function' && !window.hideProgressPopup.__tiCompletedLoaderDomReaper339) {\n            var prevHide339 = window.hideProgressPopup;\n            window.hideProgressPopup = function(){\n                var ret = prevHide339.apply(this, arguments);\n                schedule('hideProgressPopup', 260);\n                schedule('hideProgressPopup-late', 1100);\n                return ret;\n            };\n            window.hideProgressPopup.__tiCompletedLoaderDomReaper339 = true;\n        }\n        if (typeof window.hideTableReadWait === 'function' && !window.hideTableReadWait.__tiCompletedLoaderDomReaper339) {\n            var prevHideTable339 = window.hideTableReadWait;\n            window.hideTableReadWait = function(){\n                var ret = prevHideTable339.apply(this, arguments);\n                schedule('hideTableReadWait', 180);\n                schedule('hideTableReadWait-late', 900);\n                return ret;\n            };\n            window.hideTableReadWait.__tiCompletedLoaderDomReaper339 = true;\n        }\n        \/\/ Controllo continuo leggero: serve quando il loader e' gia' rimasto nel DOM prima dell'installazione del fix.\n        var ticks = 0;\n        var timer = setInterval(function(){\n            ticks++;\n            reap('periodic-' + ticks);\n            if (ticks > 1800) clearInterval(timer); \/\/ 30 minuti, poi basta\n        }, 1000);\n        document.addEventListener('visibilitychange', function(){ if (!document.hidden) schedule('visibilitychange', 100); });\n        window.addEventListener('focus', function(){ schedule('focus', 100); });\n        document.addEventListener('click', function(){ schedule('click', 50); }, true);\n        schedule('install', 250);\n        schedule('install-late', 1500);\n    })();\n\n    \/\/ 30.9.343 - Fix chirurgico produzione: chiusura difensiva immediata dei soli loader\n    \/\/ di lettura tabelle gia' conclusi. Non chiude la Configurazione e non tocca errori\/chiarimenti.\n    (function installTiTableReadNoBlock343(){\n        if (window.__tiTableReadNoBlock343Installed) return;\n        window.__tiTableReadNoBlock343Installed = true;\n        function txt(el){ return el ? String(el.innerText || el.textContent || '') : ''; }\n        function norm(s){ return String(s || '').toLowerCase().replace(\/\\s+\/g, ' ').trim(); }\n        function isCompletedTableReadText(s){\n            s = norm(s);\n            if (!s) return false;\n            if (\/errore|fallito|interrotto|annulla|chiarimento|specifiche|record aggiornati\\s*0\/.test(s)) return false;\n            return (\/tabelle lette|lettura tabelle\/.test(s)) && (\/completamento confermato|100\\s*%|avanzamento stimato\/.test(s));\n        }\n        function hardHide(el){\n            if (!el) return false;\n            try {\n                el.style.setProperty('display', 'none', 'important');\n                el.style.setProperty('visibility', 'hidden', 'important');\n                el.style.setProperty('opacity', '0', 'important');\n                el.style.setProperty('pointer-events', 'none', 'important');\n                el.setAttribute('aria-hidden', 'true');\n            } catch(e) {}\n            return true;\n        }\n        window.tiDismissCompletedTableReadLoader343 = function(reason){\n            var changed = false;\n            try {\n                var ov = document.getElementById('ti-ai-loader-ov');\n                if (ov && isCompletedTableReadText(txt(ov))) changed = hardHide(ov) || changed;\n            } catch(e) {}\n            try {\n                Array.prototype.forEach.call(document.querySelectorAll('.ti-loader-box, #ti-ai-loader-ov'), function(el){\n                    if (el && isCompletedTableReadText(txt(el))) {\n                        var target = (el.closest && el.closest('#ti-ai-loader-ov')) || el;\n                        changed = hardHide(target) || changed;\n                    }\n                });\n            } catch(e) {}\n            try {\n                var st = window.tiLoaderState || (window.tiLoaderState = {});\n                st.active = false;\n                if (st.timer) clearInterval(st.timer);\n                if (st.watchdog327) clearInterval(st.watchdog327);\n                if (st.stallGuard338) clearInterval(st.stallGuard338);\n                if (st.closeTimer338) clearTimeout(st.closeTimer338);\n                st.timer = null; st.watchdog327 = null; st.closeTimer338 = null;\n            } catch(e) {}\n            try { if (typeof window.clearLongAIProcess === 'function') window.clearLongAIProcess(); } catch(e) {}\n            try { if (typeof window.updatePluginScrollLock === 'function') window.updatePluginScrollLock(); } catch(e) {}\n            try { if (changed && window.console && console.info) console.info('[TI Shop Service] table-read loader dismissed 30.9.343:', reason || ''); } catch(e) {}\n            return changed;\n        };\n        if (typeof window.showProgressPopup === 'function' && !window.showProgressPopup.__tiNoTableReadLoader343) {\n            var prevShow343 = window.showProgressPopup;\n            window.showProgressPopup = function(title, subtitle, opts){\n                var combined = norm(String(title || '') + ' ' + String(subtitle || ''));\n                if (\/lettura tabelle|sto leggendo.*tabelle\/.test(combined)) {\n                    window.__tiTableReadWaitSuppressed343 = true;\n                    window.tiDismissCompletedTableReadLoader343('showProgressPopup-suppressed');\n                    return null;\n                }\n                return prevShow343.apply(this, arguments);\n            };\n            window.showProgressPopup.__tiNoTableReadLoader343 = true;\n        }\n        if (typeof window.updateProgressPopup === 'function' && !window.updateProgressPopup.__tiNoTableReadLoader343) {\n            var prevUpdate343 = window.updateProgressPopup;\n            window.updateProgressPopup = function(percent, title, subtitle, currentItem){\n                var combined = norm(String(title || '') + ' ' + String(subtitle || '') + ' ' + txt(document.getElementById('ti-loader-title')) + ' ' + txt(document.getElementById('ti-loader-sub')));\n                if ((parseInt(percent, 10) || 0) >= 100 && \/tabelle lette|lettura tabelle\/.test(combined)) {\n                    window.tiDismissCompletedTableReadLoader343('updateProgressPopup');\n                    return null;\n                }\n                return prevUpdate343.apply(this, arguments);\n            };\n            window.updateProgressPopup.__tiNoTableReadLoader343 = true;\n        }\n        [120, 700, 1500, 3000].forEach(function(ms){ setTimeout(function(){ window.tiDismissCompletedTableReadLoader343('install-' + ms); }, ms); });\n        var ticks = 0;\n        var timer = setInterval(function(){\n            ticks++;\n            window.tiDismissCompletedTableReadLoader343('periodic-' + ticks);\n            if (ticks > 240) clearInterval(timer);\n        }, 500);\n        document.addEventListener('visibilitychange', function(){ if (!document.hidden) setTimeout(function(){ window.tiDismissCompletedTableReadLoader343('visibility'); }, 80); });\n        window.addEventListener('focus', function(){ setTimeout(function(){ window.tiDismissCompletedTableReadLoader343('focus'); }, 80); });\n        document.addEventListener('click', function(){ setTimeout(function(){ window.tiDismissCompletedTableReadLoader343('click'); }, 20); }, true);\n    })();\n\n    window.clearLastFileContext = function() {\n        window.lastUploadedFile = null;\n        window.lastFileContext = null;\n    };\n\n    window.rememberLastFileContext = function(url, promptText, isImportFlow) {\n        if (!url) return;\n        window.lastFileContext = {\n            url: url,\n            lastActionPrompt: promptText || '',\n            pendingReusePrompt: '',\n            active: !!isImportFlow,\n            awaitingReuseChoice: false,\n            createdAt: Date.now()\n        };\n    };\n\n    window.extractPromptFromRow = function(row) {\n        if (!row || typeof row !== 'object') return '';\n        let descKey = Object.keys(row).find(k => ['descrizione','dettaglio','note'].includes(k.toLowerCase()));\n        let nameKey = Object.keys(row).find(k => ['nome','prodotto','servizio','articolo','titolo'].includes(k.toLowerCase()));\n        let promptText = (nameKey && row[nameKey] ? row[nameKey] + '. ' : '') + (descKey && row[descKey] ? row[descKey] : '');\n        return promptText.trim();\n    };\n\n    window.populateAIGenPrompt = function(row) {\n        let promptText = '';\n        if (row && typeof row === 'object') {\n            promptText = window.extractPromptFromRow(row);\n        } else if (gE('ti-msg') && gE('ti-msg').value.trim()) {\n            promptText = gE('ti-msg').value.trim();\n        }\n        if (gE('ai-gen-prompt')) gE('ai-gen-prompt').value = promptText;\n        if (gE('ti-msg') && promptText) gE('ti-msg').value = promptText;\n        return promptText;\n    };\n\n    window.isImportFollowupText = function(txt) {\n        if (!txt) return false;\n        return \/^(1|2|3|ok|si|s\u00ec|yes|no|x|annulla)$\/i.test(txt.trim()) || \/(import|reimport|file caricat|ultimo file|riutilizz|ripeti l'ultima azione|ultima azione)\/i.test(txt);\n    };\n\n    window.askReuseLastFile = function(requestText) {\n        if (!window.lastFileContext || !window.lastFileContext.url) return false;\n        window.lastFileContext.awaitingReuseChoice = true;\n        window.lastFileContext.pendingReusePrompt = requestText || window.lastFileContext.lastActionPrompt || 'Importa di nuovo i dati dal file caricato.';\n        window.addMsg('ai', \"Ho ancora disponibile l'ultimo file caricato. Vuoi riutilizzarlo e ripetere l'ultima azione? (SI\/NO)\");\n        return true;\n    };\n\n    window.getOrderViewStorageKey = function() {\n        const dbVal = (gE('ti-ditta') && gE('ti-ditta').value) ? gE('ti-ditta').value : (window.tiForced || 'global');\n        const userKey = window.tiUser || 'guest';\n        return 'ti_order_view_mode::' + userKey + '::' + dbVal;\n    };\n\n    window.isNonEmptyConfigFlag = function(v) { return String(v == null ? '' : v).trim() !== ''; };\n    window.getOrderConfigRow = function() {\n        try {\n            const pickRowFromDb = function(dbObj) {\n                if (!dbObj || !dbObj.Tabelle) return null;\n                const tables = dbObj.Tabelle || {};\n                const keys = Object.keys(tables || {});\n                let cfgKey = keys.find(function(k){ return String(k || '').toLowerCase() === 'config'; });\n                if (!cfgKey) cfgKey = keys.find(function(k){ return String(k || '').toLowerCase().indexOf('config') !== -1; });\n                if (!cfgKey || !Array.isArray(tables[cfgKey])) return null;\n                const firstRow = tables[cfgKey].find(function(r){ return r && typeof r === 'object' && !Array.isArray(r); });\n                return firstRow || null;\n            };\n            let row = pickRowFromDb(window.currentDbViewData || null) || pickRowFromDb(window.currentDbData || null);\n            if (row) return row;\n            const sel = gE('ti-ditta');\n            const opt = sel && sel.options ? sel.options[sel.selectedIndex] : null;\n            if (!opt) return null;\n            return { Tab_tab: opt.getAttribute('data-tab-tab') || '', Tab_sma: opt.getAttribute('data-tab-sma') || '', Tab_med: opt.getAttribute('data-tab-med') || '', Tab_lar: opt.getAttribute('data-tab-lar') || '' };\n        } catch(e) { return null; }\n    };\n    window.getOrderConfigFieldValue = function(fieldName) {\n        const row = window.getOrderConfigRow();\n        if (!row) return '';\n        const norm = function(v){ return String(v == null ? '' : v).toLowerCase().replace(\/[^a-z0-9]+\/g, ''); };\n        const wanted = norm(fieldName);\n        const realKey = Object.keys(row).find(function(k){ return norm(k) === wanted; });\n        return realKey ? row[realKey] : '';\n    };\n    window.getOrderViewAllowedModes = function() {\n        const map = [{ field:'Tab_tab', mode:'table' },{ field:'Tab_sma', mode:'small' },{ field:'Tab_med', mode:'medium' },{ field:'Tab_lar', mode:'large' }];\n        const allowed = map.filter(function(one){ return window.isNonEmptyConfigFlag(window.getOrderConfigFieldValue(one.field)); }).map(function(one){ return one.mode; });\n        return allowed.length ? allowed : ['table'];\n    };\n    window.getOrderViewPreference = function() {\n        const allowed = window.getOrderViewAllowedModes();\n        const storageKey = window.getOrderViewStorageKey();\n        const stored = localStorage.getItem(storageKey);\n        if (stored && allowed.includes(stored)) return stored;\n        const dbVal = (gE('ti-ditta') && gE('ti-ditta').value) ? gE('ti-ditta').value : (window.tiForced || '');\n        if (dbVal && window.tiServerPrefs && window.tiServerPrefs[dbVal] && allowed.includes(window.tiServerPrefs[dbVal].order_view_mode)) return window.tiServerPrefs[dbVal].order_view_mode;\n        return allowed[0] || 'table';\n    };\n\n    window.escapeHtml = function(str) {\n        return String(str || '').replace(\/[&<>\"']\/g, function(ch) {\n            return ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'})[ch] || ch;\n        });\n    };\n\n    window.setOrderView = function(btn, mode) {\n        const wrap = btn.closest('.ti-order-wrap');\n        const allowed = window.getOrderViewAllowedModes ? window.getOrderViewAllowedModes() : ['table','small','medium','large'];\n        if (!wrap || !allowed.includes(mode)) return;\n        wrap.className = wrap.className.replace(\/\\bti-view-(table|small|medium|large)\\b\/g, '').replace(\/\\s{2,}\/g, ' ').trim();\n        wrap.classList.add('ti-view-' + mode);\n        wrap.querySelectorAll('.ti-order-view-btn').forEach(function(b){ b.classList.toggle('active', b.dataset.view === mode); });\n        const storageKey = window.getOrderViewStorageKey();\n        localStorage.setItem(storageKey, mode);\n        const dbVal = (gE('ti-ditta') && gE('ti-ditta').value) ? gE('ti-ditta').value : (window.tiForced || '');\n        if (dbVal) {\n            if (!window.tiServerPrefs || typeof window.tiServerPrefs !== 'object') window.tiServerPrefs = {};\n            if (!window.tiServerPrefs[dbVal] || typeof window.tiServerPrefs[dbVal] !== 'object') window.tiServerPrefs[dbVal] = {};\n            window.tiServerPrefs[dbVal].order_view_mode = mode;\n            const fd = new FormData();\n            fd.append('ti_action', 'ti_ai_save_ui_pref');\n            fd.append('pref_key', 'order_view_mode');\n            fd.append('pref_value', mode);\n            fd.append('db', dbVal);\n            fetch(window.tiUrl, {method:'POST', body:fd}).catch(function(){});\n        }\n        if (window.initConfigTopScrollbars) setTimeout(function(){ window.initConfigTopScrollbars(wrap); }, 20);\n        window.calcOrderWrapTotals(wrap);\n    };\n\n    window.parseQtyInput = function(val) {\n        let raw = String(val == null ? '' : val).trim();\n        if (raw === '') return 0;\n        raw = raw.replace(\/\\s+\/g, '');\n        if (raw.indexOf(',') !== -1 && raw.indexOf('.') !== -1) raw = raw.replace(\/\\.\/g, '').replace(',', '.');\n        else if (raw.indexOf(',') !== -1) raw = raw.replace(',', '.');\n        let n = parseFloat(raw);\n        return isNaN(n) ? 0 : Math.max(0, n);\n    };\n    window.formatQtyInput = function(val) {\n        const n = window.parseQtyInput(val);\n        return window.fmtNum(n);\n    };\n    window.stepQtyInput = function(btn, delta) {\n        const wrap = btn ? btn.closest('.ti-qty-stepper') : null;\n        const inp = wrap ? wrap.querySelector('input') : null;\n        if (!inp) return;\n        const nextVal = Math.max(0, window.parseQtyInput(inp.value) + (parseFloat(delta) || 0));\n        inp.value = window.formatQtyInput(nextVal);\n        if (typeof window.syncOrderQty === 'function') window.syncOrderQty(inp);\n        else if (typeof inp.oninput === 'function') inp.oninput();\n    };\n\n    window.calcOrderWrapTotals = function(wrap) {\n        if (!wrap) return;\n        let totalQty = 0, totalVal = 0;\n        wrap.querySelectorAll('.ti-prod-table tbody tr').forEach(tr => {\n            if (tr.style.display === 'none') return;\n            const qtyInput = tr.querySelector('.ti-q');\n            if (!qtyInput) return;\n            const qty = window.parseQtyInput(qtyInput.value || '0');\n            const price = parseFloat(tr.getAttribute('data-price') || '0') || 0;\n            totalQty += qty;\n            totalVal += qty * price;\n        });\n        wrap.querySelectorAll('.ti-order-total-qty').forEach(el => el.innerText = window.fmtNum(totalQty));\n        wrap.querySelectorAll('.ti-order-total-value').forEach(el => el.innerText = '\u20ac ' + window.fmtNum(totalVal));\n    };\n\n    window.syncOrderQty = function(inp) {\n        const wrap = inp.closest('.ti-order-wrap');\n        if (!wrap) return;\n        const orderId = inp.getAttribute('data-order-id');\n        const qty = window.parseQtyInput(inp.value);\n        const formatted = window.fmtNum(qty);\n        wrap.querySelectorAll('.ti-q[data-order-id=\"' + orderId + '\"]').forEach(el => {\n            if (el !== inp) el.value = formatted;\n            else if (document.activeElement !== el) el.value = formatted;\n        });\n        wrap.querySelectorAll('.p-tot[data-order-id=\"' + orderId + '\"]').forEach(el => {\n            const price = parseFloat(el.getAttribute('data-price') || '0') || 0;\n            el.innerText = window.fmtNum(price * qty);\n        });\n        window.calcOrderWrapTotals(wrap);\n        window.refreshOrderConfirmPanel();\n    };\n\n    window.sortTI = function(th, colIdx) {\n        const table = th.closest('table');\n        if (!table) return;\n        const tbody = table.querySelector('tbody');\n        const rows = Array.from(tbody.querySelectorAll('tr'));\n        const asc = th.dataset.sortDir !== 'asc';\n        table.querySelectorAll('th').forEach(h => delete h.dataset.sortDir);\n        th.dataset.sortDir = asc ? 'asc' : 'desc';\n        rows.sort((a, b) => {\n            const av = window.getComparableCellValue(a.children[colIdx]);\n            const bv = window.getComparableCellValue(b.children[colIdx]);\n            let cmp = 0;\n            if (av.type === 'num' && bv.type === 'num') cmp = av.value - bv.value;\n            else cmp = String(av.value).localeCompare(String(bv.value), 'it');\n            return asc ? cmp : -cmp;\n        });\n        rows.forEach(r => tbody.appendChild(r));\n        const wrap = table.closest('.ti-order-wrap');\n        if (wrap) window.calcOrderWrapTotals(wrap);\n    };\n\n    window.filterTI = function(inp, colIdx) {\n        const table = inp.closest('table');\n        if (!table) return;\n        const val = inp.value.trim().toLowerCase();\n        table.querySelectorAll('tbody tr').forEach(tr => {\n            const cellTxt = (tr.children[colIdx]?.innerText || '').trim().toLowerCase();\n            tr.style.display = !val || cellTxt.includes(val) ? '' : 'none';\n        });\n        const wrap = table.closest('.ti-order-wrap');\n        if (wrap) window.calcOrderWrapTotals(wrap);\n    };\n\n    window.orderToolbarState = window.orderToolbarState || {};\n    window.getOrderWrapState = function(wrap) {\n        if (!wrap) return {filters:{name:'', desc:'', price:''}, sort:'name_asc'};\n        if (!wrap.dataset.orderWrapId) wrap.dataset.orderWrapId = 'ow-' + Math.random().toString(36).slice(2, 10);\n        const id = wrap.dataset.orderWrapId;\n        if (!window.orderToolbarState[id]) window.orderToolbarState[id] = {filters:{name:'', desc:'', price:''}, sort:'name_asc'};\n        return window.orderToolbarState[id];\n    };\n    window.filterOrderWrap = function(inp, key) {\n        const wrap = inp.closest('.ti-order-wrap');\n        if (!wrap) return;\n        const st = window.getOrderWrapState(wrap);\n        st.filters[key] = inp.value || '';\n        window.applyOrderWrapFilters(wrap);\n    };\n    window.sortOrderWrap = function(sel) {\n        const wrap = sel.closest('.ti-order-wrap');\n        if (!wrap) return;\n        const st = window.getOrderWrapState(wrap);\n        st.sort = sel.value || 'name_asc';\n        window.applyOrderWrapFilters(wrap);\n    };\n    window.applyOrderWrapFilters = function(wrap) {\n        if (!wrap) return;\n        const st = window.getOrderWrapState(wrap);\n        const rows = Array.from(wrap.querySelectorAll('.ti-prod-table tbody tr'));\n        const cards = Array.from(wrap.querySelectorAll('.ti-order-card'));\n        const allIds = new Set();\n        rows.forEach(tr => allIds.add(tr.getAttribute('data-order-id') || ''));\n        cards.forEach(card => allIds.add(card.getAttribute('data-order-id') || ''));\n        const visibility = {};\n        allIds.forEach(id => {\n            const src = wrap.querySelector('.ti-prod-table tbody tr[data-order-id=\"' + id + '\"]') || wrap.querySelector('.ti-order-card[data-order-id=\"' + id + '\"]');\n            if (!src) return;\n            const name = src.getAttribute('data-name') || '';\n            const desc = src.getAttribute('data-desc') || '';\n            const price = src.getAttribute('data-price') || '';\n            const okName = window.configFilterMatches(name, st.filters.name || '');\n            const okDesc = window.configFilterMatches(desc, st.filters.desc || '');\n            const okPrice = window.configFilterMatches(price, st.filters.price || '');\n            visibility[id] = !!(okName && okDesc && okPrice);\n        });\n        rows.forEach(tr => { tr.style.display = visibility[tr.getAttribute('data-order-id') || ''] ? '' : 'none'; });\n        cards.forEach(card => { card.style.display = visibility[card.getAttribute('data-order-id') || ''] ? '' : 'none'; });\n        const [sortKey, sortDir] = String(st.sort || 'name_asc').split('_');\n        const sorter = (a, b) => {\n            const getVal = (el) => {\n                const raw = (sortKey === 'price') ? (el.getAttribute('data-price') || '0') : (el.getAttribute('data-' + sortKey) || '');\n                return window.getComparableCellValue({innerText: raw});\n            };\n            const av = getVal(a), bv = getVal(b);\n            let cmp = 0;\n            if (av.type === 'num' && bv.type === 'num') cmp = av.value - bv.value;\n            else cmp = String(av.value).localeCompare(String(bv.value), 'it');\n            return sortDir === 'desc' ? -cmp : cmp;\n        };\n        const tbody = wrap.querySelector('.ti-prod-table tbody');\n        rows.sort(sorter).forEach(tr => tbody.appendChild(tr));\n        const grid = wrap.querySelector('.ti-order-grid-view');\n        cards.sort(sorter).forEach(card => grid.appendChild(card));\n        window.calcOrderWrapTotals(wrap);\n    };\n\n    window.getTemplateRowForTable = function(tName) {\n        if (window.currentDbData && window.currentDbData.__templateTabelle && window.currentDbData.__templateTabelle[tName] && window.currentDbData.__templateTabelle[tName][0] && typeof window.currentDbData.__templateTabelle[tName][0] === 'object') {\n            return window.currentDbData.__templateTabelle[tName][0];\n        }\n        return null;\n    };\n\n    window.getTemplateSchemaKeys = function(tName) {\n        let keys = [];\n        const templateRow = window.getTemplateRowForTable(tName);\n        if (templateRow && typeof templateRow === 'object' && !Array.isArray(templateRow)) {\n            keys = Object.keys(templateRow);\n        }\n        if (!keys.length) {\n            const rows = (window.currentDbData && window.currentDbData.Tabelle && Array.isArray(window.currentDbData.Tabelle[tName])) ? window.currentDbData.Tabelle[tName] : [];\n            rows.forEach(function(row){\n                if (!row || typeof row !== 'object' || Array.isArray(row)) return;\n                Object.keys(row).forEach(function(k){ if (!keys.includes(k)) keys.push(k); });\n            });\n        }\n        const low = String(tName || '').toLowerCase();\n        if (low === 'informazioni') {\n            const desired = ['ID','Tipo','Descrizione','Istruzioni','Data','Ora','Nota','Stato','Immagine','Iva'];\n            keys = desired.filter(k => keys.includes(k)).concat(keys.filter(k => !desired.includes(k)));\n        } else if (low === 'istruzioni') {\n            const desired = ['ID','Istruzione','Da gg','A gg','Da ora','A ora','Nota','Stato','Immagine','Iva','Data','Ora'];\n            keys = desired.filter(k => keys.includes(k)).concat(keys.filter(k => !desired.includes(k)));\n        }\n        if (!keys.includes('ID')) keys.unshift('ID');\n        return keys;\n    };\n\n    window.buildTemplateDefaultValue = function(tplVal) {\n        if (tplVal === undefined || tplVal === null) return '';\n        const str = String(tplVal).trim();\n        if (str === 'DD\/MM\/YYYY' || str === 'HH:MM') return '';\n        return tplVal;\n    };\n\n    window.getTemplateEmptyRow = function(tName, nextId) {\n        const templateRow = window.getTemplateRowForTable(tName);\n        const keys = window.getTemplateSchemaKeys(tName);\n        const row = {};\n        keys.forEach(function(k){\n            if (String(k).toLowerCase() === 'id') {\n                row[k] = nextId;\n                return;\n            }\n            if (templateRow && Object.prototype.hasOwnProperty.call(templateRow, k)) {\n                row[k] = window.buildTemplateDefaultValue(templateRow[k]);\n            } else {\n                row[k] = '';\n            }\n        });\n        if (!('ID' in row)) row.ID = nextId;\n        return row;\n    };\n\n    window.normalizeTableRowsToTemplate = function(tName) {\n        if (!window.currentDbData || !window.currentDbData.Tabelle || !window.currentDbData.Tabelle[tName]) return;\n        let rows = window.currentDbData.Tabelle[tName];\n        if (!Array.isArray(rows)) rows = window.currentDbData.Tabelle[tName] = Object.values(rows || {});\n        const keys = window.getTemplateSchemaKeys(tName);\n        const templateRow = window.getTemplateRowForTable(tName);\n        window.currentDbData.Tabelle[tName] = rows.map(function(srcRow, idx){\n            const safeRow = (srcRow && typeof srcRow === 'object' && !Array.isArray(srcRow)) ? srcRow : {};\n            const clean = {};\n            keys.forEach(function(k){\n                if (Object.prototype.hasOwnProperty.call(safeRow, k)) {\n                    clean[k] = safeRow[k];\n                } else if (window.getConfigFieldAliasCandidates) {\n                    const aliasKey = window.getConfigFieldAliasCandidates(k).find(function(a){ return Object.prototype.hasOwnProperty.call(safeRow, a); });\n                    if (aliasKey) clean[k] = safeRow[aliasKey];\n                    else if (String(k).toLowerCase() === 'id') clean[k] = (safeRow.ID || (idx + 1));\n                    else if (templateRow && Object.prototype.hasOwnProperty.call(templateRow, k)) clean[k] = window.buildTemplateDefaultValue(templateRow[k]);\n                    else clean[k] = '';\n                } else if (String(k).toLowerCase() === 'id') {\n                    clean[k] = (safeRow.ID || (idx + 1));\n                } else if (templateRow && Object.prototype.hasOwnProperty.call(templateRow, k)) {\n                    clean[k] = window.buildTemplateDefaultValue(templateRow[k]);\n                } else {\n                    clean[k] = '';\n                }\n            });\n            Object.keys(safeRow).forEach(function(k){ if (!Object.prototype.hasOwnProperty.call(clean, k)) clean[k] = safeRow[k]; });\n            if (!('ID' in clean)) clean.ID = idx + 1;\n            return clean;\n        });\n    };\n\n    window.isAccountTableName = function(tName) {\n        const low = String(tName || '').toLowerCase();\n        return low.indexOf('utent') !== -1 || low.indexOf('operator') !== -1;\n    };\n\n    window.isUsernameFieldName = function(key) {\n        const low = String(key || '').trim().toLowerCase();\n        return low === 'username' || low === 'user';\n    };\n\n    window.getConfigUsernameValue = function(row) {\n        if (!row || typeof row !== 'object') return '';\n        const key = Object.keys(row).find(function(k){ return window.isUsernameFieldName(k); });\n        return key ? String(row[key] == null ? '' : row[key]).trim() : '';\n    };\n\n    window.normalizeUsernameForCheck = function(v) {\n        return String(v == null ? '' : v).trim().toLowerCase();\n    };\n\n    window.getConfigRowIdValue = function(row) {\n        if (!row || typeof row !== 'object') return '';\n        for (const k of ['ID','Id','id']) {\n            if (Object.prototype.hasOwnProperty.call(row, k)) {\n                const v = String(row[k] == null ? '' : row[k]).trim();\n                if (v) return v;\n            }\n        }\n        return '';\n    };\n\n    window.getOriginalConfigRowsForTable = function(tName) {\n        const tables = window.configOriginalDbData && window.configOriginalDbData.Tabelle ? window.configOriginalDbData.Tabelle : null;\n        if (!tables) return null;\n        if (Array.isArray(tables[tName])) return tables[tName];\n        const wanted = String(tName || '').toLowerCase();\n        for (const oldName in tables) {\n            if (String(oldName).toLowerCase() === wanted && Array.isArray(tables[oldName])) return tables[oldName];\n        }\n        return null;\n    };\n\n    window.getOriginalConfigRow = function(tName, idx, row) {\n        const rows = window.getOriginalConfigRowsForTable(tName);\n        if (!rows) return null;\n        const newId = window.getConfigRowIdValue(row);\n        const indexRow = rows[parseInt(idx, 10)];\n        if (indexRow && typeof indexRow === 'object') {\n            const oldId = window.getConfigRowIdValue(indexRow);\n            if (!newId || !oldId || newId === oldId) return indexRow;\n        }\n        if (newId) {\n            for (const oldRow of rows) {\n                if (oldRow && typeof oldRow === 'object' && window.getConfigRowIdValue(oldRow) === newId) return oldRow;\n            }\n        }\n        return null;\n    };\n\n    window.isConfigUsernameNewOrChanged = function(tName, idx, row) {\n        if (!row || typeof row !== 'object') return false;\n        const username = window.getConfigUsernameValue(row);\n        const key = window.normalizeUsernameForCheck(username);\n        if (!key) return false;\n        const origRow = window.getOriginalConfigRow(tName, idx, row);\n        if (origRow && typeof origRow === 'object') {\n            return window.normalizeUsernameForCheck(window.getConfigUsernameValue(origRow)) !== key;\n        }\n        if (window.configOriginalDbData && window.configOriginalDbData.Tabelle) return true;\n        const uKey = Object.keys(row).find(function(k){ return window.isUsernameFieldName(k); }) || '';\n        return !!(window.modifiedFields && window.modifiedFields.has(`${tName}-${idx}-${uKey}`));\n    };\n\n    window.findDuplicateUsernameInConfig = function(username, excludeTbl, excludeIdx) {\n        const wanted = window.normalizeUsernameForCheck(username);\n        if (!wanted || !window.currentDbData || !window.currentDbData.Tabelle) return null;\n        const tables = window.currentDbData.Tabelle;\n        for (const tName in tables) {\n            if (!window.isAccountTableName(tName)) continue;\n            let rows = tables[tName];\n            if (!Array.isArray(rows)) rows = tables[tName] = Object.values(rows || {});\n            for (let i = 0; i < rows.length; i++) {\n                const row = rows[i] || {};\n                const sameRow = String(tName).toLowerCase() === String(excludeTbl || '').toLowerCase() && parseInt(excludeIdx, 10) === i;\n                if (sameRow) continue;\n                const state = String(row.Stato || row.state || row.status || '').trim().toLowerCase();\n                if (state === 'cancellato' || state.indexOf('cancellat') !== -1) continue;\n                const rowUsername = window.getConfigUsernameValue(row);\n                const cand = window.normalizeUsernameForCheck(rowUsername);\n                if (cand && cand === wanted) return {table:tName, idx:i, username:(rowUsername || username)};\n            }\n        }\n        return null;\n    };\n\n    window.focusConfigUsernameCell = function(tbl, idx) {\n        try {\n            const nodes = document.querySelectorAll('.ti-cfg-table input, .ti-cfg-table textarea, .ti-cfg-table select');\n            for (const el of nodes) {\n                if (!el.dataset) continue;\n                if (String(el.dataset.tbl || '').toLowerCase() !== String(tbl || '').toLowerCase()) continue;\n                if (String(el.dataset.idx || '') !== String(idx)) continue;\n                if (!window.isUsernameFieldName(el.dataset.key || '')) continue;\n                if (el.scrollIntoView) el.scrollIntoView({behavior:'smooth', block:'center', inline:'center'});\n                el.style.setProperty('border-color', '#ef4444', 'important');\n                el.style.setProperty('box-shadow', '0 0 0 2px rgba(239,68,68,.55)', 'important');\n                window.forceWhiteEditStyle && window.forceWhiteEditStyle(el);\n                setTimeout(function(){ try { el.focus(); if (el.select) el.select(); } catch(e) {} }, 120);\n                return true;\n            }\n        } catch(e) {}\n        return false;\n    };\n\n    window.showDuplicateUsernamePopup = function(username, dup) {\n        const u = String(username || '').trim();\n        const where = dup && dup.table ? ('<br><small>Presente in tabella <b>' + window.escapeHtml(dup.table) + '<\/b>, riga ' + (parseInt(dup.idx, 10) + 1) + '.<\/small>') : '';\n        window.tiAlert('Username gi\u00e0 esistente' + (u ? ': <b>' + window.escapeHtml(u) + '<\/b>' : '') + '.<br>Cambia username perch\u00e9 \u00e8 gi\u00e0 esistente.' + where);\n    };\n\n    window.checkConfigUsernameDuplicateInput = function(tbl, idx, key, el) {\n        if (!window.isAccountTableName(tbl) || !window.isUsernameFieldName(key)) return false;\n        const username = el ? String(el.value || '').trim() : '';\n        if (!username) return false;\n        const row = (window.currentDbData && window.currentDbData.Tabelle && window.currentDbData.Tabelle[tbl]) ? window.currentDbData.Tabelle[tbl][idx] : null;\n        if (row && window.isConfigUsernameNewOrChanged && !window.isConfigUsernameNewOrChanged(tbl, idx, row)) return false;\n        const dup = window.findDuplicateUsernameInConfig(username, tbl, idx);\n        if (!dup) return false;\n        if (el) {\n            el.style.setProperty('border-color', '#ef4444', 'important');\n            el.style.setProperty('box-shadow', '0 0 0 2px rgba(239,68,68,.55)', 'important');\n            window.forceWhiteEditStyle && window.forceWhiteEditStyle(el);\n            setTimeout(function(){ try { el.focus(); if (el.select) el.select(); } catch(e) {} }, 80);\n        }\n        window.showDuplicateUsernamePopup(username, dup);\n        return true;\n    };\n\n    window.validateConfigUniqueUsernames = function() {\n        if (!window.currentDbData || !window.currentDbData.Tabelle) return true;\n        const tables = window.currentDbData.Tabelle;\n        for (const tName in tables) {\n            if (!window.isAccountTableName(tName)) continue;\n            let rows = tables[tName];\n            if (!Array.isArray(rows)) rows = tables[tName] = Object.values(rows || {});\n            for (let i = 0; i < rows.length; i++) {\n                const row = rows[i] || {};\n                const state = String(row.Stato || row.state || row.status || '').trim().toLowerCase();\n                if (state === 'cancellato' || state.indexOf('cancellat') !== -1) continue;\n                const username = window.getConfigUsernameValue(row);\n                if (!window.normalizeUsernameForCheck(username)) continue;\n                if (window.isConfigUsernameNewOrChanged && !window.isConfigUsernameNewOrChanged(tName, i, row)) continue;\n                const dup = window.findDuplicateUsernameInConfig(username, tName, i);\n                if (dup) {\n                    window.showDuplicateUsernamePopup(username, dup);\n                    window.focusConfigUsernameCell(tName, i);\n                    return false;\n                }\n            }\n        }\n        return true;\n    };\n\n    window.isConfigSingleRowTable379 = function(tName) {\n        try {\n            return String(tName || '').trim().toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').replace(\/[^a-z0-9]\/g, '') === 'config';\n        } catch(e) {\n            return String(tName || '').trim().toLowerCase() === 'config';\n        }\n    };\n\n    window.addTableRow = function(tName) {\n        if (window.isConfigSingleRowTable379 && window.isConfigSingleRowTable379(tName)) {\n            if (window.tiAlert) window.tiAlert('La tabella Config \u00e8 a riga unica: non \u00e8 possibile aggiungere righe.');\n            return false;\n        }\n        if (!window.currentDbData || !window.currentDbData.Tabelle || !window.currentDbData.Tabelle[tName]) return;\n        let rows = window.currentDbData.Tabelle[tName];\n        if (!Array.isArray(rows)) rows = window.currentDbData.Tabelle[tName] = Object.values(rows || {});\n        window.normalizeTableRowsToTemplate(tName);\n        rows = window.currentDbData.Tabelle[tName];\n        const nextId = rows.reduce(function(maxId, row){ const n = parseInt((row && row.ID != null) ? row.ID : 0, 10); return isNaN(n) ? maxId : Math.max(maxId, n); }, 0) + 1;\n        rows.unshift(window.getTemplateEmptyRow(tName, nextId));\n        if (!window.modifiedFields) window.modifiedFields = new Set(); window.modifiedFields.add(`${tName}-__row_added`); if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState();\n        window.configForceTop = true;\n        window.renderConfig();\n    };\n\n    window.duplicateTableRow = function(tName, idx) {\n        if (window.isConfigSingleRowTable379 && window.isConfigSingleRowTable379(tName)) {\n            if (window.tiAlert) window.tiAlert('La tabella Config \u00e8 a riga unica: non \u00e8 possibile duplicare righe.');\n            return false;\n        }\n        if (!window.currentDbData || !window.currentDbData.Tabelle || !window.currentDbData.Tabelle[tName] || !window.currentDbData.Tabelle[tName][idx]) return;\n        let rows = window.currentDbData.Tabelle[tName];\n        if (!Array.isArray(rows)) rows = window.currentDbData.Tabelle[tName] = Object.values(rows);\n        const clone = JSON.parse(JSON.stringify(rows[idx]));\n        clone.ID = rows.length + 1;\n        if (window.isAccountTableName(tName)) {\n            Object.keys(clone || {}).forEach(function(k){ if (window.isUsernameFieldName(k)) clone[k] = ''; });\n        }\n        rows.splice(idx + 1, 0, clone);\n        if (!window.modifiedFields) window.modifiedFields = new Set(); window.modifiedFields.add(`${tName}-__row_duplicated`); if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState();\n        window.renderConfig();\n        if (window.isAccountTableName(tName)) window.tiAlert('Riga utente duplicata. Inserisci un nuovo username: non pu\u00f2 essere uguale a uno gi\u00e0 esistente.');\n    };\n\n    window.canPhysicallyDeleteUserRows = function() {\n        const r = String(window.tiRole || '').toLowerCase();\n        return r.includes('amministratore') || r.includes('configuratore') || String(window.tiUser || '').toLowerCase() === 'ssglobaladmin';\n    };\n\n    window.reindexTableRows = function(tName) {\n        if (!window.currentDbData || !window.currentDbData.Tabelle || !window.currentDbData.Tabelle[tName]) return [];\n        let rows = window.currentDbData.Tabelle[tName];\n        if (!Array.isArray(rows)) rows = window.currentDbData.Tabelle[tName] = Object.values(rows || {});\n        rows.forEach(function(rowObj, rowIdx) {\n            if (!rowObj || typeof rowObj !== 'object') return;\n            if (Object.prototype.hasOwnProperty.call(rowObj, 'ID')) rowObj.ID = rowIdx + 1;\n            else if (Object.prototype.hasOwnProperty.call(rowObj, 'Id')) rowObj.Id = rowIdx + 1;\n            else if (Object.prototype.hasOwnProperty.call(rowObj, 'id')) rowObj.id = rowIdx + 1;\n        });\n        window.currentDbData.Tabelle[tName] = rows;\n        return rows;\n    };\n\n    window.markTableRowsModified = function(tName) {\n        if (!window.currentDbData || !window.currentDbData.Tabelle || !window.currentDbData.Tabelle[tName]) return;\n        if (!window.modifiedFields) window.modifiedFields = new Set();\n        let rows = window.currentDbData.Tabelle[tName];\n        if (!Array.isArray(rows)) rows = window.currentDbData.Tabelle[tName] = Object.values(rows || {});\n        rows.forEach(function(rowObj, rowIdx) {\n            Object.keys(rowObj || {}).forEach(function(k) {\n                window.modifiedFields.add(`${tName}-${rowIdx}-${k}`); if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState();\n            });\n        });\n    };\n\n    window.mustKeepAtLeastOneConfigRow = function(tName) {\n        const low = String(tName || '').trim().toLowerCase();\n        return low === 'config' || low === 'utenti';\n    };\n\n    window.clearSingleRemainingRow = function(tName, rowIndex) {\n        if (!window.currentDbData || !window.currentDbData.Tabelle || !Object.prototype.hasOwnProperty.call(window.currentDbData.Tabelle, tName)) return false;\n        let rows = window.currentDbData.Tabelle[tName];\n        if (!Array.isArray(rows)) rows = window.currentDbData.Tabelle[tName] = Object.values(rows || {});\n        const idx = parseInt(rowIndex, 10);\n        if (isNaN(idx) || idx < 0 || idx >= rows.length) return false;\n        const oldRow = (rows[idx] && typeof rows[idx] === 'object' && !Array.isArray(rows[idx])) ? rows[idx] : {};\n        const keys = (window.getTemplateSchemaKeys ? window.getTemplateSchemaKeys(tName) : Object.keys(oldRow || {}));\n        const clean = {};\n        keys.forEach(function(k) {\n            if (String(k).toLowerCase() === 'id') clean[k] = 1;\n            else clean[k] = '';\n        });\n        Object.keys(oldRow || {}).forEach(function(k) {\n            if (!Object.prototype.hasOwnProperty.call(clean, k)) clean[k] = (String(k).toLowerCase() === 'id') ? 1 : '';\n        });\n        rows[idx] = clean;\n        window.currentDbData.Tabelle[tName] = rows;\n        if (!window.modifiedFields) window.modifiedFields = new Set();\n        Object.keys(clean || {}).forEach(function(k) { window.modifiedFields.add(`${tName}-${idx}-${k}`); });\n        window.modifiedFields.add(`${tName}-${idx}-__row_cleared`); if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState();\n        return true;\n    };\n\n    window.resolveConfigTableName = function(tName) {\n        const wanted = String(tName || '').trim();\n        const tables = (window.currentDbData && window.currentDbData.Tabelle) ? window.currentDbData.Tabelle : null;\n        if (!tables) return wanted;\n        if (Object.prototype.hasOwnProperty.call(tables, wanted)) return wanted;\n        const norm = function(v) {\n            try { v = String(v || '').normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, ''); } catch(e) { v = String(v || ''); }\n            return v.toLowerCase().replace(\/[^a-z0-9]\/g, '');\n        };\n        const nw = norm(wanted);\n        for (const k in tables) {\n            if (String(k) === wanted || norm(k) === nw) return k;\n        }\n        return wanted;\n    };\n\n    window.getConfigRowIdCI = function(row) {\n        if (!row || typeof row !== 'object') return '';\n        const keys = Object.keys(row);\n        for (let i = 0; i < keys.length; i++) {\n            if (String(keys[i]).trim().toLowerCase() === 'id') return String(row[keys[i]] == null ? '' : row[keys[i]]).trim();\n        }\n        return '';\n    };\n\n    window.recordConfigDeletedRowsManifest = function(tName, rows, indexes) {\n        if (!window.tiConfigDeletedRowsManifest) window.tiConfigDeletedRowsManifest = [];\n        rows = Array.isArray(rows) ? rows : [];\n        (indexes || []).forEach(function(i){\n            i = parseInt(i, 10);\n            if (isNaN(i) || i < 0 || i >= rows.length) return;\n            const rowCopy = JSON.parse(JSON.stringify(rows[i] || {}));\n            window.tiConfigDeletedRowsManifest.push({\n                table: String(tName || ''),\n                idx: i,\n                id: window.getConfigRowIdCI ? window.getConfigRowIdCI(rowCopy) : '',\n                row: rowCopy,\n                row_count_before: rows.length,\n                row_count_after: Math.max(0, rows.length - 1),\n                deleted_at: new Date().toISOString()\n            });\n        });\n    };\n\n    window.rememberConfigDeletedTableSnapshot = function(tName) {\n        tName = window.resolveConfigTableName ? window.resolveConfigTableName(tName) : tName;\n        if (!window.currentDbData || !window.currentDbData.Tabelle || !Object.prototype.hasOwnProperty.call(window.currentDbData.Tabelle, tName)) return;\n        if (!window.tiConfigDeletedTableSnapshots) window.tiConfigDeletedTableSnapshots = {};\n        if (!window.tiConfigDeletedTableSnapshotBeforeCounts) window.tiConfigDeletedTableSnapshotBeforeCounts = {};\n        let rows = window.currentDbData.Tabelle[tName];\n        if (!Array.isArray(rows)) rows = window.currentDbData.Tabelle[tName] = Object.values(rows || {});\n        const beforeCount = parseInt(window.tiConfigDeletedTableSnapshotBeforeCounts[tName] || rows.length, 10) || rows.length;\n        window.tiConfigDeletedTableSnapshots[tName] = {\n            table: String(tName || ''),\n            row_count_before: Math.max(beforeCount, rows.length),\n            row_count_after: rows.length,\n            rows: JSON.parse(JSON.stringify(rows || [])),\n            saved_at: new Date().toISOString()\n        };\n    };\n\n    window.refreshConfigDeletedTableSnapshotsFromCurrentData = function() {\n        if (!window.currentDbData || !window.currentDbData.Tabelle || !window.tiConfigDeletedTableSnapshots) return;\n        if (window.syncVisibleConfigInputsToMemory) window.syncVisibleConfigInputsToMemory();\n        Object.keys(window.tiConfigDeletedTableSnapshots || {}).forEach(function(tName){\n            const realTbl = window.resolveConfigTableName ? window.resolveConfigTableName(tName) : tName;\n            if (!window.currentDbData.Tabelle || !Object.prototype.hasOwnProperty.call(window.currentDbData.Tabelle, realTbl)) return;\n            let rows = window.currentDbData.Tabelle[realTbl];\n            if (!Array.isArray(rows)) rows = window.currentDbData.Tabelle[realTbl] = Object.values(rows || {});\n            const snap = window.tiConfigDeletedTableSnapshots[tName] || {};\n            const beforeCount = Math.max(parseInt(snap.row_count_before || 0, 10) || 0, parseInt((window.tiConfigDeletedTableSnapshotBeforeCounts || {})[realTbl] || 0, 10) || 0, rows.length);\n            window.tiConfigDeletedTableSnapshots[tName] = {\n                table: String(realTbl || tName || ''),\n                row_count_before: beforeCount,\n                row_count_after: rows.length,\n                rows: JSON.parse(JSON.stringify(rows || [])),\n                saved_at: new Date().toISOString()\n            };\n        });\n    };\n\n    window.applyConfigDeletedTableSnapshotsToCurrentData = function() {\n        if (!window.currentDbData || !window.currentDbData.Tabelle || !window.tiConfigDeletedTableSnapshots) return;\n        Object.keys(window.tiConfigDeletedTableSnapshots || {}).forEach(function(tName){\n            const snap = window.tiConfigDeletedTableSnapshots[tName] || {};\n            const rows = Array.isArray(snap.rows) ? snap.rows : null;\n            if (!rows) return;\n            const realTbl = window.resolveConfigTableName ? window.resolveConfigTableName(tName) : tName;\n            if (!window.currentDbData.Tabelle[realTbl] || !Array.isArray(window.currentDbData.Tabelle[realTbl])) return;\n            \/\/ Se una vecchia ricostruzione ha rimesso righe cancellate, forza la tabella nello stato post-cancellazione.\n            if (window.currentDbData.Tabelle[realTbl].length > rows.length) {\n                window.currentDbData.Tabelle[realTbl] = JSON.parse(JSON.stringify(rows));\n            }\n        });\n    };\n\n    window.syncVisibleConfigInputsToMemory = function() {\n        if (!window.currentDbData || !window.currentDbData.Tabelle) return;\n        const root = gE('ti-conf-cnt') || document;\n        root.querySelectorAll('input[data-tbl][data-idx][data-key], textarea[data-tbl][data-idx][data-key], select[data-tbl][data-idx][data-key]').forEach(function(el){\n            const tbl = el.getAttribute('data-tbl') || '';\n            const key = el.getAttribute('data-key') || '';\n            const idx = parseInt(el.getAttribute('data-idx') || '', 10);\n            if (!tbl || !key || isNaN(idx)) return;\n            const realTbl = window.resolveConfigTableName ? window.resolveConfigTableName(tbl) : tbl;\n            if (!window.currentDbData.Tabelle[realTbl] || !window.currentDbData.Tabelle[realTbl][idx]) return;\n            let val = '';\n            if (el.tagName === 'SELECT' && el.multiple) val = Array.from(el.selectedOptions || []).map(function(o){ return String(o.value || '').trim(); }).filter(Boolean).join(', ');\n            else if (el.type === 'checkbox') val = el.checked ? (el.value || '1') : '';\n            else val = el.value;\n            if (window.normalizeInputValueForKey) val = window.normalizeInputValueForKey(key, val);\n            window.currentDbData.Tabelle[realTbl][idx][key] = val;\n        });\n    };\n\n    window.syncConfigTableAfterRowMutation = function(tName) {\n        tName = window.resolveConfigTableName ? window.resolveConfigTableName(tName) : tName;\n        try {\n            if (window.currentDbViewData && window.currentDbViewData !== window.currentDbData && window.currentDbViewData.Tabelle && window.currentDbData && window.currentDbData.Tabelle && Object.prototype.hasOwnProperty.call(window.currentDbData.Tabelle, tName)) {\n                window.currentDbViewData.Tabelle[tName] = JSON.parse(JSON.stringify(window.currentDbData.Tabelle[tName] || []));\n            }\n        } catch(e) {}\n        try { window.__configRelationSupportCache = null; } catch(e) {}\n        try { window.__tiLastConfigRowMutation = Date.now(); } catch(e) {}\n    };\n\n    window.deleteConfigRowsByIndexes = function(tName, indexes, opts) {\n        opts = opts || {};\n        tName = window.resolveConfigTableName ? window.resolveConfigTableName(tName) : tName;\n        if (!window.currentDbData || !window.currentDbData.Tabelle || !Object.prototype.hasOwnProperty.call(window.currentDbData.Tabelle, tName)) return false;\n        let rows = window.currentDbData.Tabelle[tName];\n        if (!Array.isArray(rows)) rows = window.currentDbData.Tabelle[tName] = Object.values(rows || {});\n        const ixs = Array.from(new Set((indexes || []).map(function(v){ return parseInt(v, 10); }).filter(function(n){ return !isNaN(n) && n >= 0 && n < rows.length; }))).sort(function(a,b){ return b-a; });\n        if (!ixs.length) { window.tiLastConfigDeleteCount = 0; return false; }\n        const keepOne = window.mustKeepAtLeastOneConfigRow && window.mustKeepAtLeastOneConfigRow(tName);\n        if (keepOne && ixs.length >= rows.length) {\n            window.tiLastConfigDeleteCount = 0;\n            window.tiAlert('La tabella ' + String(tName || '') + ' deve mantenere almeno una riga. Righe cancellate: 0.');\n            return false;\n        }\n        if (!window.tiConfigDeletedTableSnapshotBeforeCounts) window.tiConfigDeletedTableSnapshotBeforeCounts = {};\n        if (!window.tiConfigDeletedTableSnapshotBeforeCounts[tName]) window.tiConfigDeletedTableSnapshotBeforeCounts[tName] = rows.length;\n        if (window.recordConfigDeletedRowsManifest) window.recordConfigDeletedRowsManifest(tName, rows, ixs);\n        const beforeDeleteLen = rows.length;\n        if (!keepOne && rows.length === 1 && ixs.length === 1) {\n            rows.splice(ixs[0], 1);\n        } else {\n            ixs.forEach(function(i){ rows.splice(i, 1); });\n        }\n        window.tiLastConfigDeleteCount = Math.max(0, beforeDeleteLen - rows.length);\n        window.tiConfigDeletedRowsCountTotal = Math.max(0, parseInt(window.tiConfigDeletedRowsCountTotal || 0, 10) || 0) + window.tiLastConfigDeleteCount;\n        window.currentDbData.Tabelle[tName] = rows;\n        window.reindexTableRows(tName);\n        if (window.rememberConfigDeletedTableSnapshot) window.rememberConfigDeletedTableSnapshot(tName);\n        window.markTableRowsModified(tName);\n        if (!window.modifiedFields) window.modifiedFields = new Set();\n        window.modifiedFields.add(`${tName}-__rows_deleted`);\n        window.modifiedFields.add(`${tName}-__table_rewritten`);\n        if (window.syncConfigTableAfterRowMutation) window.syncConfigTableAfterRowMutation(tName);\n        if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState();\n        if (opts.render !== false) {\n            window.configForceTop = true;\n            window.renderConfig();\n        }\n        return true;\n    };\n\n    window.delTableRow = function(tName, idx, sourceEl, opts) {\n        opts = opts || {};\n        tName = window.resolveConfigTableName ? window.resolveConfigTableName(tName) : tName;\n        if (!window.currentDbData || !window.currentDbData.Tabelle || !Object.prototype.hasOwnProperty.call(window.currentDbData.Tabelle, tName)) {\n            window.tiAlert && window.tiAlert('Tabella non trovata nella configurazione corrente.');\n            return false;\n        }\n        let rows = window.currentDbData.Tabelle[tName];\n        if (!Array.isArray(rows)) rows = window.currentDbData.Tabelle[tName] = Object.values(rows || {});\n        const rowIndex = parseInt(idx, 10);\n        if (isNaN(rowIndex) || rowIndex < 0 || rowIndex >= rows.length) {\n            window.tiAlert && window.tiAlert('Riga non trovata o gia eliminata.');\n            return false;\n        }\n        const doDelete = function(){\n            if (window.deleteConfigRowsByIndexes) {\n                const ok = window.deleteConfigRowsByIndexes(tName, [rowIndex], {render:true});\n                if (ok) window.tiAlert('Righe cancellate dalla configurazione corrente: ' + (window.tiLastConfigDeleteCount || 1) + '. Premi Salva Modifiche per aggiornare il database.');\n                return !!ok;\n            }\n            rows.splice(rowIndex, 1);\n            window.currentDbData.Tabelle[tName] = rows;\n            window.reindexTableRows(tName);\n            window.markTableRowsModified(tName);\n            if (window.syncConfigTableAfterRowMutation) window.syncConfigTableAfterRowMutation(tName);\n            window.configForceTop = true;\n            window.renderConfig();\n            window.tiAlert && window.tiAlert('Righe cancellate dalla configurazione corrente: 1. Premi Salva Modifiche per aggiornare il database.');\n            return true;\n        };\n        if (opts && opts.noConfirm) return doDelete();\n        const msg = 'Eliminare questa riga dalla tabella ' + String(tName || '') + '?<br><br>La riga verra rimossa dalla configurazione corrente. Per aggiornare il database dovrai premere Salva Modifiche.';\n        if (typeof window.tiConfirmYesNoAction === 'function') window.tiConfirmYesNoAction(msg, doDelete, null, 'ELIMINA', 'ANNULLA');\n        else if (typeof window.tiConfirm === 'function') window.tiConfirm(msg, doDelete);\n        else doDelete();\n        return true;\n    };\n\n    window.addTableCol = function(tName) {\n        const colName = prompt('Nome nuova colonna:');\n        if (!colName || !window.currentDbData || !window.currentDbData.Tabelle || !window.currentDbData.Tabelle[tName]) return;\n        let rows = window.currentDbData.Tabelle[tName];\n        if (!Array.isArray(rows)) rows = window.currentDbData.Tabelle[tName] = Object.values(rows);\n        rows.forEach(r => { if (!(colName in r)) r[colName] = ''; });\n        window.renderConfig();\n    };\n\n    window.getPluginScrollable = function(target) {\n        if (!target) return null;\n        const pluginRoot = target.closest('#ti-ai-outer');\n        if (!pluginRoot) return null;\n        let node = target.nodeType === 1 ? target : target.parentElement;\n        while (node && node !== pluginRoot) {\n            const canScroll = node.classList && (\n                node.classList.contains('ti-scrollable-plugin') ||\n                node.classList.contains('ti-modal') ||\n                node.classList.contains('ti-chat-box') ||\n                node.classList.contains('ti-cfg-table-container') ||\n                node.classList.contains('ti-conf-body') ||\n                node.classList.contains('ti-table-wrap')\n            );\n            if (canScroll) {\n                const style = window.getComputedStyle(node);\n                const overflowY = style.overflowY || '';\n                const reallyScrollable = (overflowY === 'auto' || overflowY === 'scroll' || node.scrollHeight > node.clientHeight + 4);\n                if (reallyScrollable) return node;\n            }\n            node = node.parentElement;\n        }\n        const doc = document.scrollingElement || document.documentElement || document.body;\n        return (doc && doc.scrollHeight > doc.clientHeight + 4) ? doc : pluginRoot;\n    };\n\n    document.addEventListener('wheel', function(e) {\n        const pluginRoot = e.target.closest && e.target.closest('#ti-ai-outer');\n        if (!pluginRoot) return;\n        const scrollable = window.getPluginScrollable(e.target);\n        if (!scrollable || scrollable === pluginRoot) return;\n        const before = scrollable.scrollTop;\n        scrollable.scrollTop += e.deltaY;\n        if (scrollable.scrollTop !== before || e.deltaY !== 0) {\n            e.preventDefault();\n            e.stopPropagation();\n        }\n    }, { passive: false, capture: true });\n\n    document.addEventListener('touchmove', function(e) { \n        const isMobile = window.tiIsMobileLike ? window.tiIsMobileLike() : !!(window.matchMedia && window.matchMedia('(max-width: 900px)').matches);\n        if (isMobile) return; \/\/ su smartphone\/touch lascia sempre libero lo scorrimento verticale a un dito\n        const isModalOpen = document.querySelector('.ti-ov[style*=\"display: flex\"], .ti-ov[style*=\"display:flex\"], #ti-ai-loader-ov[style*=\"display: flex\"], #ti-ai-loader-ov[style*=\"display:flex\"]'); \n        if (isModalOpen) { \n            const t = e.target;\n            const scrollable = t && t.closest ? (t.closest('.ti-conf-body') || t.closest('.ti-table-wrap') || t.closest('.ti-chat-box') || t.closest('.ti-modal') || t.closest('.ti-cfg-table-container') || t.closest('.ti-scrollable-plugin') || t.closest('#help-bot-reply') || t.closest('#col-help-content') || t.closest('#ateco-res') || t.closest('#global-action-res') || t.closest('#fm-grid')) : null; \n            if (!scrollable) e.preventDefault(); \n        } \n    }, { passive: false });\n\n\n    window.tiIsPrivilegedCurrentUser = function() {\n        const roleNow = String(window.tiRole || '').toLowerCase();\n        const userNow = String(window.tiUser || '');\n        return (!!userNow && userNow === 'SSGlobalAdmin') || \/amministratore|configuratore|responsabile|globale\/.test(roleNow);\n    };\n\n    window.tiInformativePopupsEnabled = true;\n    window.tiHasStandardMainFormAccess = function() {\n        return !!String(window.tiUser || '').trim();\n    };\n    window.ensureStandardMainFormForCurrentUser = function() {\n        if (!window.tiHasStandardMainFormAccess || !window.tiHasStandardMainFormAccess()) return;\n        const ids = ['ti-msg','ti-send','ti-new','ti-copy','ti-file','ti-products-btn','ti-services-btn','ti-activities-btn','ti-kbd-btn','ti-mic','ti-cam'];\n        ids.forEach(function(id){\n            const el = gE(id);\n            if (!el) return;\n            el.disabled = false;\n            el.removeAttribute('disabled');\n            el.removeAttribute('aria-hidden');\n            if (el.style && el.style.display === 'none') el.style.display = '';\n            if (el.id === 'ti-msg') { el.readOnly = false; el.removeAttribute('readonly'); }\n        });\n        const wrap = document.querySelector('#ti-ai-outer .ti-input-wrap');\n        if (wrap) { wrap.style.display = ''; wrap.removeAttribute('aria-hidden'); }\n        const actions = document.querySelector('#ti-ai-outer .ti-actions-row');\n        if (actions) { actions.style.display = ''; actions.removeAttribute('aria-hidden'); }\n        document.querySelectorAll('#ti-ai-outer .ti-order-wrap,#ti-ai-outer .ti-order-table-shell,#ti-ai-outer .ti-order-grid-view,#ti-ai-outer .ti-order-confirm-host,#ti-ai-outer table.ti-prod-table').forEach(function(el){\n            el.style.display = '';\n            el.removeAttribute('aria-hidden');\n        });\n        document.querySelectorAll('#ti-ai-outer .ti-order-wrap button,#ti-ai-outer .ti-order-table-shell button,#ti-ai-outer .ti-order-grid-view button,#ti-ai-outer .ti-order-confirm-host button,#ti-ai-outer table.ti-prod-table button').forEach(function(btn){\n            btn.disabled = false;\n            btn.removeAttribute('disabled');\n            btn.removeAttribute('aria-hidden');\n        });\n    };\n\n    window.tiPlainTextFromHtml = function(html) {\n        try {\n            const tmp = document.createElement('div');\n            tmp.innerHTML = String(html == null ? '' : html)\n                .replace(\/<br\\s*\\\/?>\/gi, '\\n')\n                .replace(\/<\\\/p>\/gi, '\\n')\n                .replace(\/<\\\/div>\/gi, '\\n')\n                .replace(\/<\\\/li>\/gi, '\\n');\n            return (tmp.innerText || tmp.textContent || '').replace(\/\\n{3,}\/g, '\\n\\n').trim();\n        } catch(e) {\n            return String(html == null ? '' : html).replace(\/<[^>]+>\/g, ' ').replace(\/\\s+\/g, ' ').trim();\n        }\n    };\n\n    window.tiCopyTextToClipboard = function(text, btn) {\n        text = String(text == null ? '' : text);\n        const done = function(ok){\n            if (!btn) return;\n            const old = btn.textContent;\n            btn.textContent = ok ? 'Copiato' : 'Errore copia';\n            setTimeout(function(){ btn.textContent = old || 'Copia testo'; }, 1200);\n        };\n        if (navigator.clipboard && navigator.clipboard.writeText) {\n            navigator.clipboard.writeText(text).then(function(){ done(true); }).catch(function(){\n                try { const ta=document.createElement('textarea'); ta.value=text; ta.style.position='fixed'; ta.style.left='-9999px'; document.body.appendChild(ta); ta.focus(); ta.select(); const ok=document.execCommand('copy'); document.body.removeChild(ta); done(ok); } catch(e){ done(false); }\n            });\n            return;\n        }\n        try { const ta=document.createElement('textarea'); ta.value=text; ta.style.position='fixed'; ta.style.left='-9999px'; document.body.appendChild(ta); ta.focus(); ta.select(); const ok=document.execCommand('copy'); document.body.removeChild(ta); done(ok); } catch(e){ done(false); }\n    };\n\n    window.tiSanitizePrivilegedPopupHtml = function(content) {\n        let html = String(content == null ? '' : content);\n        try {\n            if (\/\\[TABLE\\]|ti-order-wrap|ti-prod-table|ti-order-table-shell|ti-order-grid-view\/i.test(html)) {\n                let post = '';\n                if (\/\\[TABLE\\]\/i.test(html)) {\n                    const lastClose = html.lastIndexOf('[\/P]');\n                    if (lastClose >= 0) post = html.slice(lastClose + 4).trim();\n                    html = html.split('[TABLE]')[0] + (post ? '<br><br>' + post : '');\n                }\n                const tmp = document.createElement('div');\n                tmp.innerHTML = html;\n                tmp.querySelectorAll('.ti-order-wrap,.ti-order-table-shell,.ti-order-grid-view,.ti-order-toolbar,.ti-order-filterbar,.ti-order-confirm-host,table.ti-prod-table').forEach(function(el){ el.remove(); });\n                tmp.querySelectorAll('table').forEach(function(tbl){\n                    if (tbl.closest && tbl.closest('#ti-report-cnt')) return;\n                    const txt = (tbl.innerText || tbl.textContent || '').trim();\n                    if (\/voce|quantit|prezzo|totale|descrizione\/i.test(txt)) tbl.remove();\n                });\n                html = tmp.innerHTML;\n            }\n        } catch(e) {}\n        return html;\n    };\n\n    window.tiPopupPlaceholderMessage = function(title) {\n        const t = String(title || 'Comunicazione').replace(\/<[^>]+>\/g, '').trim();\n        return '\ud83d\udccc ' + (t || 'Comunicazione\/report') + ' aperto in popup separato. Usa il pulsante <b>Copia testo<\/b> nel popup per copiare il contenuto.';\n    };\n\n    window.showPrivilegedCommunicationPopup = function(content, title, opts) {\n        if (!window.tiIsPrivilegedCurrentUser || !window.tiIsPrivilegedCurrentUser()) return false;\n        const ov = gE('ti-admin-comm-ov');\n        const box = gE('ti-admin-comm-msg');\n        const titleEl = gE('ti-admin-comm-title');\n        const copyBtn = gE('ti-admin-comm-copy');\n        const closeBtn = gE('ti-admin-comm-close');\n        if (!ov || !box || !titleEl) return false;\n        const html = (window.tiSanitizePrivilegedPopupHtml ? window.tiSanitizePrivilegedPopupHtml(content) : String(content == null ? '' : content)).trim();\n        if (!html) return false;\n        titleEl.textContent = title || 'Comunicazione';\n        box.innerHTML = html;\n        const plain = window.tiPlainTextFromHtml ? window.tiPlainTextFromHtml(html) : String(html).replace(\/<[^>]+>\/g, ' ').replace(\/\\s+\/g, ' ').trim();\n        box.setAttribute('data-copy-text', plain);\n        if (copyBtn) copyBtn.onclick = function(){ window.tiCopyTextToClipboard(plain, copyBtn); };\n        if (closeBtn) closeBtn.onclick = function(){ window.closeModal('ti-admin-comm-ov'); };\n        window.showPluginModal('ti-admin-comm-ov');\n        try { if (window.applyLayoutLanguage) window.applyLayoutLanguage(true); } catch(e) {}\n        window.keepUserCommunicationPopupsInFront();\n        setTimeout(window.keepUserCommunicationPopupsInFront, 0);\n        setTimeout(window.keepUserCommunicationPopupsInFront, 80);\n        return true;\n    };\n\n    window.tiIsFunctionalOrderContent = function(content) {\n        const s = String(content == null ? '' : content);\n        if (!s.trim()) return false;\n        return \/\\[TABLE\\]|\\[P\\]|ti-order-wrap|ti-prod-table|ti-order-table-shell|ti-order-grid-view|ti-order-confirm-host|confirmPendingOrder|Conferma ordine|CONFERMA ORDINE|tabella ordine\/i.test(s);\n    };\n\n    window.shouldShowPrivilegedCommunicationPopup = function(text, force) {\n        if (window.tiIsFunctionalOrderContent && window.tiIsFunctionalOrderContent(text)) return false;\n        if (force) return true;\n        if (!window.tiIsPrivilegedCurrentUser || !window.tiIsPrivilegedCurrentUser()) return false;\n        const plain = String(text == null ? '' : text).replace(\/<[^>]+>\/g, ' ').replace(\/\\s+\/g, ' ').trim();\n        if (!plain) return false;\n        if (plain.length >= 260) return true;\n        return \/(report|riepilogo|comunicaz|avviso|warning|errore|database|configurazione|salvataggio|righe cancellate|modifica globale|anteprima|import|istruzioni|prenotaz|appuntament|ordine concluso|notifica|email)\/i.test(plain);\n    };\n\n    window.maybeShowPrivilegedCommunicationPopup = function(content, title, force) {\n        if (window.tiIsFunctionalOrderContent && window.tiIsFunctionalOrderContent(content)) return false;\n        const plain = window.tiPlainTextFromHtml ? window.tiPlainTextFromHtml(content) : String(content || '');\n        if (!window.shouldShowPrivilegedCommunicationPopup(plain, !!force)) return false;\n        return window.showPrivilegedCommunicationPopup(content, title || 'Comunicazione', {text: plain});\n    };\n\n    window.tiAlert = function(msg) {\n        if (window.tiCleanVisibleEscapedChars) msg = window.tiCleanVisibleEscapedChars(msg);\n        gE('ti-alert-msg').innerHTML = msg;\n        try { if (window.tiCleanEscapedUiInSubtree) window.tiCleanEscapedUiInSubtree(gE('ti-alert-msg')); } catch(e) {}\n        if (gE('ti-alert-copy')) { gE('ti-alert-copy').style.display = 'block'; gE('ti-alert-copy').onclick = window.copyAlertText; }\n        gE('ti-alert-yes').style.display = 'none';\n        gE('ti-alert-no').innerText = 'OK';\n        gE('ti-alert-no').onclick = () => { window.closeModal('ti-alert-ov'); };\n        window.showPluginModal('ti-alert-ov');\n        try { if (window.applyLayoutLanguage) window.applyLayoutLanguage(true); } catch(e) {}\n        window.keepUserCommunicationPopupsInFront();\n        setTimeout(window.keepUserCommunicationPopupsInFront, 0);\n        setTimeout(window.keepUserCommunicationPopupsInFront, 80);\n    };\n\n    window.tiConfirm = function(msg, callback) {\n        if (window.tiCleanVisibleEscapedChars) msg = window.tiCleanVisibleEscapedChars(msg);\n        gE('ti-alert-msg').innerHTML = msg;\n        try { if (window.tiCleanEscapedUiInSubtree) window.tiCleanEscapedUiInSubtree(gE('ti-alert-msg')); } catch(e) {}\n        if (gE('ti-alert-copy')) { gE('ti-alert-copy').style.display = 'block'; gE('ti-alert-copy').onclick = window.copyAlertText; }\n        gE('ti-alert-yes').style.display = 'block';\n        gE('ti-alert-no').innerText = 'ANNULLA';\n        gE('ti-alert-yes').onclick = () => {\n            window.closeModal('ti-alert-ov');\n            if (typeof callback === 'function') callback();\n        };\n        gE('ti-alert-no').onclick = () => { window.closeModal('ti-alert-ov'); };\n        window.showPluginModal('ti-alert-ov');\n        try { if (window.applyLayoutLanguage) window.applyLayoutLanguage(true); } catch(e) {}\n        window.keepUserCommunicationPopupsInFront();\n        setTimeout(window.keepUserCommunicationPopupsInFront, 0);\n        setTimeout(window.keepUserCommunicationPopupsInFront, 80);\n    };\n\n\n    window.tiConfirmYesNoAction = function(msg, yesCb, noCb, yesLabel = 'OK', noLabel = 'ANNULLA') {\n        gE('ti-alert-msg').innerHTML = msg;\n        if (gE('ti-alert-copy')) { gE('ti-alert-copy').style.display = 'block'; gE('ti-alert-copy').onclick = window.copyAlertText; }\n        gE('ti-alert-yes').style.display = 'block';\n        gE('ti-alert-yes').innerText = yesLabel;\n        gE('ti-alert-no').innerText = noLabel;\n        gE('ti-alert-yes').onclick = () => {\n            window.closeModal('ti-alert-ov');\n            if (typeof yesCb === 'function') yesCb();\n        };\n        gE('ti-alert-no').onclick = () => {\n            window.closeModal('ti-alert-ov');\n            if (typeof noCb === 'function') noCb();\n        };\n        window.showPluginModal('ti-alert-ov');\n        try { if (window.applyLayoutLanguage) window.applyLayoutLanguage(true); } catch(e) {}\n        window.keepUserCommunicationPopupsInFront();\n        setTimeout(window.keepUserCommunicationPopupsInFront, 0);\n        setTimeout(window.keepUserCommunicationPopupsInFront, 80);\n    };\n\n    window.tiBatchAIGenState = { active: false, pendingConfirm: false, generated: [] };\n\n    window.refreshCurrentConfigDb = function(dbValue) {\n        const dbSel = gE('ti-ditta');\n        const db = dbValue || (dbSel ? dbSel.value : '');\n        if (!db || db === 'NEW_DB') return Promise.resolve(false);\n        const fdDb = window.buildAjaxFormData('ti_ai_get_db_data', {db: db});\n        return window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fdDb, tiNoLongProcessSignal:true, tiShowTableReadWait:true, tiTableReadTitle:'\u23f3 Lettura tabelle configurazione', tiTableReadDetail:'Sto aggiornando le tabelle della ditta selezionata.', tiTableReadItem: db}).then(function(dbRes) {\n            if (dbRes && dbRes.success && dbRes.data) {\n                window.currentDbData = dbRes.data;\n                if (window.renderConfig) window.renderConfig();\n                try { if (window.showActivityFromQrIfRequested) window.showActivityFromQrIfRequested(); } catch(e) {}\n                return true;\n            }\n            return false;\n        }).catch(function(){ return false; });\n    };\n\n    window.confirmCancelBatchAIGen = function() {\n        const st = window.tiBatchAIGenState || {};\n        if (!st.active || st.pendingConfirm) return false;\n        const generated = Array.isArray(st.generated) ? st.generated : [];\n        const generatedCount = generated.length;\n        st.pendingConfirm = true;\n        const lp = window.tiLongProcessState || {};\n        lp.cancelled = true;\n        try { if (lp.controller) lp.controller.abort(); } catch(e) {}\n        window.closeModal('ti-ai-loader-ov');\n\n        const finishKeep = function() {\n            st.active = false;\n            st.pendingConfirm = false;\n            if (window.clearLongAIProcess) window.clearLongAIProcess();\n            const msg = generatedCount > 0\n                ? 'Generazione interrotta. Le associazioni gi\u00e0 generate sono state mantenute. Associazioni mantenute: ' + generatedCount + '.'\n                : 'Generazione interrotta. Nessuna associazione era stata ancora generata.';\n            const rb = gE('ti-conf-ai-reply');\n            if (rb) { rb.style.display = 'block'; rb.innerHTML = window.escapeHtml(msg); }\n            window.refreshCurrentConfigDb(st.db).then(function(){ window.tiAlert(msg); });\n        };\n\n        const finishRevert = function() {\n            if (!generatedCount) { finishKeep(); return; }\n            const fd = window.buildAjaxFormData('ti_ai_config_action', {\n                db: st.db || (gE('ti-ditta') ? gE('ti-ditta').value : ''),\n                mode: 'batch_ai_images_revert',\n                items_json: JSON.stringify(generated)\n            });\n            const rb = gE('ti-conf-ai-reply');\n            if (rb) { rb.style.display = 'block'; rb.innerHTML = 'Annullamento associazioni generate in corso...'; }\n            window.postFormDataJsonSafe((window.tiAjaxUrl || window.tiUrl), fd, {tiNoLongProcessSignal:true}).then(function(res){\n                st.active = false;\n                st.pendingConfirm = false;\n                if (window.clearLongAIProcess) window.clearLongAIProcess();\n                const removed = res && res.data && typeof res.data.removed !== 'undefined' ? parseInt(res.data.removed, 10) || 0 : 0;\n                const msg = 'Generazione interrotta. Associazioni generate annullate: ' + removed + '.';\n                if (rb) rb.innerHTML = window.escapeHtml(msg);\n                window.refreshCurrentConfigDb(st.db).then(function(){ window.tiAlert(msg); });\n            }).catch(function(err){\n                st.active = false;\n                st.pendingConfirm = false;\n                if (window.clearLongAIProcess) window.clearLongAIProcess();\n                const msg = 'Generazione interrotta. Non sono riuscito ad annullare automaticamente le associazioni gi\u00e0 generate. Verifica la configurazione. Dettaglio: ' + ((err && err.message) ? err.message : 'errore sconosciuto');\n                if (rb) rb.innerHTML = window.escapeHtml(msg);\n                window.tiAlert(msg);\n            });\n        };\n\n        const question = generatedCount > 0\n            ? 'Sono gi\u00e0 state generate ' + generatedCount + ' associazioni immagine.<br><br>Vuoi mantenerle oppure annullarle?'\n            : 'Vuoi uscire dalla generazione foto AI?<br><br>Nessuna associazione risulta ancora generata.';\n        if (generatedCount > 0 && typeof window.tiConfirmYesNoAction === 'function') {\n            window.tiConfirmYesNoAction(question, finishKeep, finishRevert, 'MANTIENI ASSOCIAZIONI', 'ANNULLA ASSOCIAZIONI');\n        } else if (typeof window.tiConfirmYesNoAction === 'function') {\n            window.tiConfirmYesNoAction(question, finishKeep, function(){\n                st.active = false;\n                st.pendingConfirm = false;\n                if (window.clearLongAIProcess) window.clearLongAIProcess();\n                const rb = gE('ti-conf-ai-reply');\n                if (rb) { rb.style.display = 'block'; rb.innerHTML = 'Generazione foto AI interrotta.'; }\n            }, 'ESCI', 'CONTINUA');\n        } else {\n            finishKeep();\n        }\n        return true;\n    };\n\n\n    window.openFileManager = function(tbl, idx, key) {\n        let val = window.currentDbData.Tabelle[tbl][idx][key] || '';\n        window.targetUploadContext = { tbl, idx, key, val };\n        const fmRowObj = (window.currentDbData && window.currentDbData.Tabelle && window.currentDbData.Tabelle[tbl]) ? window.currentDbData.Tabelle[tbl][idx] : null;\n        const fmDescBox = gE('fm-element-description');\n        if (fmDescBox) {\n            try {\n                const recordLabel = window.getRecordLabelForUpload ? window.getRecordLabelForUpload(tbl, fmRowObj, tbl) : (tbl || 'record');\n                let fullDesc = window.extractPromptFromRow ? window.extractPromptFromRow(fmRowObj) : '';\n                fullDesc = String(fullDesc || '').trim();\n                const shownDesc = fullDesc || recordLabel || 'Elemento senza descrizione disponibile';\n                const safeTbl = window.escapeHtml(tbl || '');\n                const safeKey = window.escapeHtml(key || '');\n                const safeDesc = window.escapeHtml(shownDesc);\n                const safeLabel = window.escapeHtml(recordLabel || shownDesc || '');\n                const rowNum = (parseInt(idx, 10) || 0) + 1;\n                fmDescBox.style.display = 'block';\n                fmDescBox.innerHTML = '<div style=\"font-weight:800;color:#93c5fd;margin-bottom:4px;\">Elemento cui sono associati i file<\/div>'\n                    + '<div><b>Descrizione:<\/b> ' + safeDesc + '<\/div>'\n                    + (safeLabel && safeLabel !== safeDesc ? '<div style=\"font-size:12px;color:#bfdbfe;margin-top:2px;\"><b>Voce:<\/b> ' + safeLabel + '<\/div>' : '')\n                    + '<div style=\"font-size:11px;color:#94a3b8;margin-top:4px;\">Tabella: ' + safeTbl + ' \u00b7 Riga rif.: ' + rowNum + ' \u00b7 Campo: ' + safeKey + '<\/div>';\n            } catch(e) {\n                fmDescBox.style.display = 'none';\n                fmDescBox.innerHTML = '';\n            }\n        }\n        let files = val.split(',').map(f => f.trim()).filter(f=>f);\n        let html = '';\n        files.forEach((f, i) => {\n            let rowObj = (window.currentDbData && window.currentDbData.Tabelle && window.currentDbData.Tabelle[tbl]) ? window.currentDbData.Tabelle[tbl][idx] : null;\n            let url = window.getSafePreviewUrl(f, gE('ti-ditta').value, tbl, window.targetUploadContext && window.targetUploadContext.key ? window.targetUploadContext.key : '', window.getRecordLabelForUpload(tbl, rowObj, tbl));\n            let isImg = f.match(\/\\.(jpg|jpeg|png|webp|gif)\/i);\n            let preview = isImg ? `<img decoding=\"async\" src=\"${url}\" style=\"cursor:pointer;\" onclick=\"window.open('${url}', '_blank')\">` : `<div class=\"file-icon\" style=\"cursor:pointer;\" onclick=\"window.open('${url}', '_blank')\">\ud83d\udcc4<\/div>`;\n            const primaryBadge = (i === 0) ? `<div style=\"margin-top:4px;padding:3px 5px;border-radius:999px;background:#22c55e;color:#052e16;font-size:10px;font-weight:800;\">Principale layout<\/div>` : `<button type=\"button\" onclick=\"window.setPrimaryFileInManager(${i})\" style=\"margin-top:4px;font-size:9px;padding:2px 5px;border-radius:999px;background:#334155;color:#e5e7eb;border:1px solid #64748b;\">Rendi principale<\/button>`;\n            html += `<div class=\"ti-file-item\" style=\"position:relative;${i === 0 ? 'border-color:#22c55e;box-shadow:0 0 0 1px rgba(34,197,94,.45);' : ''}\">\n                        ${preview}\n                        ${primaryBadge}\n                        <div class=\"ti-file-name\" title=\"${f}\" onclick=\"window.open('${url}', '_blank')\" style=\"cursor:pointer;\">${f}<\/div>\n                        <button type=\"button\" class=\"ti-file-del\" onclick=\"window.removeFileFromManager(${i})\">X<\/button>\n                        <button type=\"button\" onclick=\"window.open('${url}', '_blank')\" style=\"margin-top:2px; font-size:9px; padding:2px; border-radius:3px;\">Apri<\/button>\n                     <\/div>`;\n        });\n        if(files.length === 0) html = '<p style=\"color:#aaa; font-size:12px; grid-column: 1 \/ -1;\">Nessun file presente.<\/p>';\n        const pathBox = gE('fm-storage-path');\n        if (pathBox) {\n            const showPath = window.isAdminConfiguratorUser && window.isAdminConfiguratorUser();\n            const storagePath = window.getStoragePathForTable(gE('ti-ditta') ? gE('ti-ditta').value : '', tbl);\n            pathBox.style.display = showPath && storagePath ? 'block' : 'none';\n            if (showPath && storagePath) pathBox.innerHTML = '<b>Path storage file:<\/b><br>' + window.escapeHtml(storagePath);\n        }\n        gE('fm-grid').innerHTML = html;\n        try {\n            const fmOv = gE('ti-file-manager-ov');\n            if (fmOv) {\n                fmOv.dataset.tbl = tbl || '';\n                fmOv.dataset.idx = String(idx);\n                fmOv.dataset.key = key || '';\n                fmOv.setAttribute('data-ti-config-child-modal', '1');\n            }\n        } catch(e) {}\n        window.openModal('ti-file-manager-ov');\n        try {\n            const fmOv = gE('ti-file-manager-ov');\n            if (fmOv) {\n                if (window.preparePluginModal) window.preparePluginModal(fmOv);\n                fmOv.style.setProperty('display', 'flex', 'important');\n                fmOv.style.setProperty('z-index', '2147483500', 'important');\n                fmOv.removeAttribute('aria-hidden');\n                fmOv.setAttribute('data-ti-force-front', '1');\n                if (window.updatePluginScrollLock) window.updatePluginScrollLock();\n            }\n        } catch(e) {}\n    };\n\n    window.setPrimaryFileInManager = function(fileIndex) {\n        let ctx = window.targetUploadContext;\n        if (!ctx) return;\n        let val = window.currentDbData.Tabelle[ctx.tbl][ctx.idx][ctx.key] || '';\n        let files = val.split(',').map(f => f.trim()).filter(f=>f);\n        if (fileIndex <= 0 || fileIndex >= files.length) return;\n        const picked = files.splice(fileIndex, 1)[0];\n        files.unshift(picked);\n        window.currentDbData.Tabelle[ctx.tbl][ctx.idx][ctx.key] = files.join(', ');\n        if(!window.modifiedFields) window.modifiedFields = new Set();\n        window.modifiedFields.add(`${ctx.tbl}-${ctx.idx}-${ctx.key}`); if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState();\n        window.renderConfig();\n        window.openFileManager(ctx.tbl, ctx.idx, ctx.key);\n        if (window.tiAlert) window.tiAlert('File principale layout impostato. Ricorda di salvare la configurazione.');\n    };\n\n    window.removeFileFromManager = function(fileIndex) {\n        let ctx = window.targetUploadContext;\n        if (!ctx || !window.currentDbData || !window.currentDbData.Tabelle || !window.currentDbData.Tabelle[ctx.tbl] || !window.currentDbData.Tabelle[ctx.tbl][ctx.idx]) return;\n        const ctxSnapshot = { tbl: ctx.tbl, idx: ctx.idx, key: ctx.key };\n        let val = window.currentDbData.Tabelle[ctxSnapshot.tbl][ctxSnapshot.idx][ctxSnapshot.key] || '';\n        let filesBeforeDelete = String(val || '').split(\/[,;\\r\\n]+\/).map(f => f.trim()).filter(f=>f);\n        const fileName = filesBeforeDelete[fileIndex] || '';\n        if (!fileName) { window.tiAlert('File non selezionato.'); return; }\n        let filesAfterDelete = filesBeforeDelete.slice();\n        filesAfterDelete.splice(fileIndex, 1);\n        const newVal = filesAfterDelete.join(', ');\n        const rowObj = (window.currentDbData && window.currentDbData.Tabelle && window.currentDbData.Tabelle[ctxSnapshot.tbl]) ? window.currentDbData.Tabelle[ctxSnapshot.tbl][ctxSnapshot.idx] : null;\n        const applyLocalFileRemoval = function(ctx, msg, markUnsaved) {\n            if (!window.currentDbData.Tabelle) window.currentDbData.Tabelle = {};\n            if (!window.currentDbData.Tabelle[ctx.tbl]) window.currentDbData.Tabelle[ctx.tbl] = [];\n            if (!window.currentDbData.Tabelle[ctx.tbl][ctx.idx]) window.currentDbData.Tabelle[ctx.tbl][ctx.idx] = {};\n            window.currentDbData.Tabelle[ctx.tbl][ctx.idx][ctx.key] = newVal;\n            if (markUnsaved) {\n                if(!window.modifiedFields) window.modifiedFields = new Set();\n                window.modifiedFields.add(`${ctx.tbl}-${ctx.idx}-${ctx.key}`);\n                if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState();\n            }\n            window.renderConfig();\n            window.openFileManager(ctx.tbl, ctx.idx, ctx.key);\n            if (window.tiAlert && msg) window.tiAlert(msg);\n        };\n        window.tiConfirm(\"Rimuovere questa associazione file dal record?\\n\\nIl riferimento verr\u00e0 tolto dalla configurazione corrente. Premi Salva Modifiche per confermare.\", function() {\n            let ctx = ctxSnapshot;\n            try {\n                window.pendingLinkedFileRemovals = window.pendingLinkedFileRemovals || [];\n                window.pendingLinkedFileRemovals.push({\n                    db: (gE('ti-ditta') ? (gE('ti-ditta').value || '') : ''),\n                    tbl: ctx.tbl,\n                    idx: ctx.idx,\n                    key: ctx.key,\n                    file: fileName,\n                    removed_at: new Date().toISOString()\n                });\n            } catch(e) {}\n            applyLocalFileRemoval(ctx, 'Associazione file rimossa dalla configurazione corrente. Premi Salva Modifiche per confermare la modifica. Il file fisico nello storage non viene cancellato automaticamente da questa X rossa.', true);\n        });\n    };\n\n    window.tiCloseFileManagerForAction = function(actionName) {\n        try {\n            window.__tiFileManagerExplicitClose337 = Date.now ? Date.now() : new Date().getTime();\n            window.__tiFileManagerOpening337 = 0;\n            window.__tiFileManagerAction340 = actionName || 'action';\n        } catch(e) {}\n        try { if (window.closeModal) window.closeModal('ti-file-manager-ov'); } catch(e) {}\n        try {\n            const ov = gE('ti-file-manager-ov');\n            if (ov) {\n                ov.style.setProperty('display', 'none', 'important');\n                ov.setAttribute('aria-hidden', 'true');\n                ov.classList.remove('ti-modal-active','ti-ov-active','is-active','active','show');\n            }\n        } catch(e) {}\n        try { if (window.updatePluginScrollLock) window.updatePluginScrollLock(); } catch(e) {}\n    };\n\n    window.chooseLocalManager = function() {\n        const ctx = window.targetUploadContext;\n        if (!ctx || !ctx.tbl || ctx.idx === undefined || !ctx.key) {\n            if (window.tiAlert) window.tiAlert('Gestione file associati: record\/campo non disponibile. Riapri la form dalla tabella.');\n            return false;\n        }\n        const fileIn = gE('ti-file-in');\n        if (!fileIn) {\n            if (window.tiAlert) window.tiAlert('Selettore file non disponibile.');\n            return false;\n        }\n        try {\n            fileIn.dataset.target = 'manager';\n            fileIn.value = '';\n            const fmOv = gE('ti-file-manager-ov');\n            if (fmOv) {\n                fmOv.style.setProperty('display', 'flex', 'important');\n                fmOv.style.setProperty('z-index', '2147483645', 'important');\n                fmOv.removeAttribute('aria-hidden');\n            }\n            fileIn.click();\n        } catch(e) {\n            if (window.tiAlert) window.tiAlert('Impossibile aprire il selettore file: ' + (e && e.message ? e.message : 'errore browser'));\n        }\n        return false;\n    };\n\n    window.getCompanyActivity = function() {\n        try {\n            if (!window.currentDbData || !window.currentDbData.Tabelle) return '';\n            const cfgRows = window.currentDbData.Tabelle.Config || window.currentDbData.Tabelle.config || [];\n            const row = Array.isArray(cfgRows) && cfgRows.length ? cfgRows[0] : null;\n            if (!row) return '';\n            return row['Attivita'] || row['Attivit\u00e0'] || row['Attivita\\u0300'] || '';\n        } catch(e) { return ''; }\n    };\n\n    window.getInfoLinksForAIGen = function() {\n        try {\n            if (!window.currentDbData || !window.currentDbData.Tabelle) return [];\n            const tables = window.currentDbData.Tabelle;\n            const infoRows = tables.Informazioni || tables.informazioni || [];\n            const links = [];\n            (Array.isArray(infoRows) ? infoRows : []).forEach(row => {\n                if (!row || typeof row !== 'object') return;\n                Object.values(row).forEach(val => {\n                    if (typeof val !== 'string') return;\n                    const matches = val.match(\/https?:\\\/\\\/[^\\s<>'\"]+\/gi);\n                    if (matches) matches.forEach(u => { if (!links.includes(u)) links.push(u); });\n                });\n            });\n            return links.slice(0, 5);\n        } catch(e) { return []; }\n    };\n\n    window.buildAIGenPrompt = function(descriptionText) {\n        const parts = [];\n        const activity = (window.getCompanyActivity() || '').trim();\n        const desc = (descriptionText || '').trim();\n        const infoLinks = window.getInfoLinksForAIGen();\n        if (activity) parts.push('Attivit\u00e0 ditta: ' + activity);\n        if (infoLinks.length) parts.push('Verifica anzitutto questi link informazioni del database: ' + infoLinks.join(' | '));\n        if (desc) parts.push('Descrizione da associare: ' + desc);\n        return parts.join('\\n');\n    };\n\n    window.chooseAIManager = function() {\n        const ctx = window.targetUploadContext;\n        if (!ctx || !ctx.tbl || ctx.idx === undefined || !ctx.key) {\n            if (window.tiAlert) window.tiAlert('Gestione file associati: record\/campo non disponibile. Riapri la form dalla tabella.');\n            return false;\n        }\n        let row = null;\n        try {\n            if (window.currentDbData && window.currentDbData.Tabelle && window.currentDbData.Tabelle[ctx.tbl]) {\n                row = window.currentDbData.Tabelle[ctx.tbl][ctx.idx] || null;\n            }\n        } catch(e) {}\n        if (typeof window.populateAIGenPrompt === 'function') window.populateAIGenPrompt(row);\n        else if (gE('ai-gen-prompt')) gE('ai-gen-prompt').value = window.buildAIGenPrompt ? window.buildAIGenPrompt(row ? (row.Descrizione || row.Prodotto || row.Servizio || row.Nome || '') : '') : '';\n        if (gE('ai-gen-detail')) gE('ai-gen-detail').value = '';\n        if (gE('ai-gen-res')) gE('ai-gen-res').style.display = 'none';\n        if (gE('ai-gen-img')) gE('ai-gen-img').src = '';\n        if (gE('ai-gen-btn')) {\n            gE('ai-gen-btn').disabled = false;\n            gE('ai-gen-btn').innerText = 'Genera Immagine';\n            gE('ai-gen-btn').style.display = 'block';\n        }\n        window.currentGenFilename = null;\n        try { window.openModal('ti-ai-gen-ov'); } catch(e) {}\n        try {\n            const fmOv = gE('ti-file-manager-ov');\n            if (fmOv) {\n                fmOv.style.setProperty('display', 'flex', 'important');\n                fmOv.style.setProperty('z-index', '2147483645', 'important');\n                fmOv.removeAttribute('aria-hidden');\n            }\n            const aiOv = gE('ti-ai-gen-ov');\n            if (aiOv) {\n                aiOv.setAttribute('data-ti-config-child-modal', '1');\n                aiOv.style.setProperty('display', 'flex', 'important');\n                aiOv.style.setProperty('z-index', '2147483646', 'important');\n                aiOv.removeAttribute('aria-hidden');\n                if (window.preparePluginModal) window.preparePluginModal(aiOv);\n            }\n            if (window.updatePluginScrollLock) window.updatePluginScrollLock();\n            setTimeout(function(){ try { if (gE('ai-gen-prompt')) gE('ai-gen-prompt').focus({preventScroll:true}); } catch(e) {} }, 60);\n        } catch(e) {}\n        return false;\n    };\n\n    window.syncLoginDbSelect = function() {\n        try {\n            const loginDb = gE('l-db');\n            const mainDb = gE('ti-ditta');\n            const hiddenDb = gE('l-db-hidden');\n            if (!loginDb) return;\n            if (mainDb && mainDb.options && mainDb.options.length) {\n                loginDb.innerHTML = '';\n                const first = document.createElement('option');\n                first.value = '';\n                first.textContent = '-- Scegli una ditta --';\n                loginDb.appendChild(first);\n                Array.from(mainDb.options || []).forEach(function(opt){\n                if (!opt || opt.disabled) return;\n                const v = String(opt.value || '');\n                if (v === 'NEW_DB') return;\n                const o = document.createElement('option');\n                o.value = v;\n                o.textContent = opt.textContent || v || '-- Scegli una ditta --';\n                Array.from(opt.attributes || []).forEach(function(a){\n                    if (a && a.name && a.name.indexOf('data-') === 0) o.setAttribute(a.name, a.value);\n                });\n                loginDb.appendChild(o);\n                });\n            }\n            let target = (mainDb && mainDb.value && mainDb.value !== 'NEW_DB') ? mainDb.value : '';\n            if (!target) target = window.tiForced || '';\n            if (!target) {\n                try { target = localStorage.getItem('ti_saved_db') || ''; } catch(e) {}\n            }\n            if (target && Array.from(loginDb.options).some(function(o){ return o.value === target; })) loginDb.value = target;\n            else if (loginDb.options.length === 2 && !loginDb.options[0].value) loginDb.selectedIndex = 1;\n            const hasRealOptions = Array.from(loginDb.options).some(function(o){ return o.value; });\n            const shouldShow = !window.tiForced && hasRealOptions;\n            loginDb.style.display = shouldShow ? 'block' : 'none';\n            if (hiddenDb) hiddenDb.value = (loginDb && loginDb.value) ? loginDb.value : (target || hiddenDb.value || '');\n            if (!loginDb.dataset.tiHiddenSyncBound) {\n                loginDb.dataset.tiHiddenSyncBound = '1';\n                loginDb.addEventListener('change', function(){ const h = gE('l-db-hidden'); if (h) h.value = loginDb.value || ''; }, false);\n            }\n        } catch(e) {}\n    };\n\n    window.getLoginDbValue = function() {\n        const modalDb = gE('l-db');\n        const hiddenDb = gE('l-db-hidden');\n        const mainDb = gE('ti-ditta');\n        let v = '';\n        if (modalDb && modalDb.value && modalDb.value !== 'NEW_DB') v = modalDb.value;\n        if (!v && hiddenDb && hiddenDb.value && hiddenDb.value !== 'NEW_DB') v = hiddenDb.value;\n        if (!v && mainDb && mainDb.value && mainDb.value !== 'NEW_DB') v = mainDb.value;\n        if (!v && window.tiForced) v = window.tiForced;\n        if (!v) {\n            try { v = localStorage.getItem('ti_saved_db') || ''; } catch(e) {}\n        }\n        return v;\n    };\n\n    window.tiCloseDirectLoginFallback = function() {\n        try {\n            const dl = gE('ti-mobile-direct-login');\n            if (!dl) return;\n            dl.classList.remove('ti-direct-login-visible');\n            dl.classList.remove('ti-direct-login-manual-open');\n            dl.style.display = 'none';\n            dl.setAttribute('aria-hidden', 'true');\n        } catch(e) {}\n    };\n\n    window.revealDirectLoginFallback = function(reason, detail) {\n        try {\n            const dl = gE('ti-mobile-direct-login');\n            if (!dl) return;\n            dl.classList.add('ti-direct-login-visible');\n            dl.classList.add('ti-direct-login-manual-open');\n            dl.style.display = '';\n            dl.removeAttribute('aria-hidden');\n            const modalUser = gE('l-user');\n            const modalPass = gE('l-pass');\n            const directUser = gE('ti-direct-login-user');\n            const directPass = gE('ti-direct-login-pass');\n            const directDb = gE('ti-direct-login-db');\n            const directBtn = dl.querySelector('button[type=\"submit\"], .ti-mobile-login-submit');\n            if (directUser && modalUser && modalUser.value) directUser.value = modalUser.value;\n            if (directPass && modalPass && modalPass.value) directPass.value = modalPass.value;\n            const dbVal = window.getLoginDbValue ? window.getLoginDbValue() : '';\n            if (directDb && dbVal) directDb.value = dbVal;\n            const box = gE('ti-direct-login-fallback-alert');\n            if (box) {\n                const baseMsg = 'Accesso tramite tasto Accedi non riuscito. Utilizza Accesso diretto Shop & Service per completare il login.';\n                box.textContent = detail ? (baseMsg + ' Dettaglio: ' + detail) : baseMsg;\n                box.style.display = 'block';\n            }\n            if (window.closeModal) window.closeModal('ti-login-ov');\n            try { window.tiScrollIntoViewSafe ? window.tiScrollIntoViewSafe(dl) : dl.scrollIntoView({behavior:'smooth', block:'center'}); } catch(e) { try { dl.scrollIntoView({behavior:'smooth', block:'center'}); } catch(ignore) {} }\n            setTimeout(function(){\n                try {\n                    if (directPass && !directPass.value) directPass.focus();\n                    else if (directBtn) directBtn.focus();\n                    else if (directUser) directUser.focus();\n                } catch(e) {}\n            }, 220);\n        } catch(e) {}\n    };\n\n\n    window.tiPrepareChatLoginAs = function(username) {\n        try {\n            if (window.syncLoginDbSelect) window.syncLoginDbSelect();\n            const u = gE('l-user');\n            const p = gE('l-pass');\n            if (u) u.value = username || '';\n            if (window.openLoginModalSafe) window.openLoginModalSafe(); else if (window.openModal) window.openModal('ti-login-ov');\n            setTimeout(function(){ try { if (p) { p.value = ''; p.focus(); } } catch(e) {} }, 120);\n        } catch(e) {\n            if (window.openLoginModalSafe) window.openLoginModalSafe();\n        }\n    };\n\n    window.openLoginModalSafe = function() {\n        try { if (window.syncLoginDbSelect) window.syncLoginDbSelect(); } catch(e) {}\n        if (window.openModal) window.openModal('ti-login-ov');\n        setTimeout(function(){\n            try {\n                if (window.syncLoginDbSelect) window.syncLoginDbSelect();\n                if (!(window.tiIsMobileLike && window.tiIsMobileLike())) {\n                    const u = gE('l-user'); if (u) u.focus();\n                }\n            } catch(e) {}\n        }, 80);\n    };\n\n    window.mobileLoginXhr = function(url, fd) {\n        return new Promise(function(resolve, reject){\n            try {\n                const xhr = new XMLHttpRequest();\n                xhr.open('POST', url, true);\n                xhr.withCredentials = true;\n                xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n                xhr.onreadystatechange = function(){\n                    if (xhr.readyState !== 4) return;\n                    const raw = xhr.responseText || '';\n                    if (xhr.status < 200 || xhr.status >= 300) {\n                        reject(new Error('HTTP ' + xhr.status + ': ' + raw.slice(0, 180)));\n                        return;\n                    }\n                    try { resolve(JSON.parse(raw)); }\n                    catch(e) { reject(new Error('Risposta server non valida: ' + raw.slice(0, 180))); }\n                };\n                xhr.onerror = function(){ reject(new Error('Errore rete XHR')); };\n                xhr.ontimeout = function(){ reject(new Error('Timeout login')); };\n                xhr.timeout = 60000;\n                xhr.send(fd);\n            } catch(e) { reject(e); }\n        });\n    };\n\n    window.doLogin = function() {\n        const userEl = gE('l-user');\n        const passEl = gE('l-pass');\n        const loginBtn = gE('l-do');\n        try { if (window.syncLoginDbSelect) window.syncLoginDbSelect(); } catch(e) {}\n        const u = userEl ? userEl.value.trim() : '';\n        const dbVal = (window.getLoginDbValue ? window.getLoginDbValue() : '');\n        if (!u) { window.tiAlert('Inserisci username'); if (userEl && !(window.tiIsMobileLike && window.tiIsMobileLike())) userEl.focus(); return; }\n        if (u !== 'SSGlobalAdmin' && !dbVal) { window.tiAlert('Seleziona una ditta nella finestra di login.'); return; }\n        const hiddenDb = gE('l-db-hidden');\n        if (hiddenDb && dbVal) hiddenDb.value = dbVal;\n        if (window.tiLoginInProgress) return;\n        window.tiLoginInProgress = true;\n        if (loginBtn) { loginBtn.disabled = true; loginBtn.textContent = 'Accesso...'; }\n\n        const makeLoginFd = function() {\n            const f = new FormData();\n            f.append('action','ti_ai_login');\n            f.append('ti_action','ti_ai_login');\n            f.append('ti_mobile_login_redirect','0');\n            f.append('redirect','0');\n            f.append('user',u);\n            f.append('username',u);\n            const pwd = passEl ? passEl.value : '';\n            f.append('pass', pwd);\n            f.append('password', pwd);\n            f.append('db', dbVal);\n            f.append('current_db', dbVal);\n            f.append('_ti_mobile_ts', String(Date.now()));\n            return f;\n        };\n\n        const urls = [];\n        if (window.tiAjaxUrl) urls.push(window.tiAjaxUrl);\n        if (window.tiUrl && urls.indexOf(window.tiUrl) === -1) urls.push(window.tiUrl);\n\n        const loginVia = function(url) {\n            const fd = makeLoginFd();\n            if (window.mobileLoginXhr) return window.mobileLoginXhr(url, fd);\n            if (window.postFormDataJsonSafe) return window.postFormDataJsonSafe(url, fd, {tiNoLongProcessSignal:true, cache:'no-store'});\n            return window.fetchJsonSafe(url, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store', tiNoLongProcessSignal:true});\n        };\n\n        const trySequence = function(i) {\n            return loginVia(urls[i] || window.tiUrl).catch(function(err){\n                if (i + 1 < urls.length) return trySequence(i + 1);\n                throw err;\n            });\n        };\n\n        trySequence(0).then(function(d){\n            if (d && d.success) {\n                try {\n                    if (dbVal) localStorage.setItem('ti_saved_db', dbVal);\n                    if (dbVal) localStorage.setItem('ti_last_login_db', dbVal);\n                    localStorage.setItem('ti_last_login_user', u || '');\n                    sessionStorage.removeItem('ti_login_failed_clear');\n                    localStorage.setItem('ti_login_ok_ts', String(Date.now()));\n                } catch(e) {}\n                window.closeModal && window.closeModal('ti-login-ov');\n                try { if (window.tiCloseDirectLoginFallback) window.tiCloseDirectLoginFallback(); } catch(e) {}\n                if (window.tiReloadAfterSuccessfulLogin) window.tiReloadAfterSuccessfulLogin(); else location.reload();\n                return;\n            }\n            const failMsg = (((d||{}).data||{}).message || 'Login non riuscito');\n            window.tiAlert('Accesso non riuscito. Verifica username, password e ditta selezionata. Ti propongo Accesso diretto Shop & Service. Dettaglio: ' + failMsg);\n            try {\n                if (window.tiClearLoginFieldsAfterFailure) window.tiClearLoginFieldsAfterFailure(false);\n                else if (passEl) passEl.value = '';\n                if (window.revealDirectLoginFallback) window.revealDirectLoginFallback('login-non-riuscito', failMsg);\n                else if (window.openModal) window.openModal('ti-login-ov');\n            } catch(e) {}\n        }).catch(function(err){\n            const msg = err && err.message ? err.message : 'Errore di connessione durante il login.';\n            window.tiAlert('Accesso tramite tasto Accedi non completato. Ti propongo Accesso diretto Shop & Service. Dettaglio: ' + msg);\n            try {\n                if (window.tiClearLoginFieldsAfterFailure) window.tiClearLoginFieldsAfterFailure(false);\n                if (window.revealDirectLoginFallback) window.revealDirectLoginFallback('login-non-completato', msg);\n                else if (window.openModal) window.openModal('ti-login-ov');\n            } catch(e) {}\n        }).finally(function(){\n            window.tiLoginInProgress = false;\n            if (loginBtn) { loginBtn.disabled = false; loginBtn.textContent = 'Entra'; }\n        });\n    };\n\n    window.tiForgotPasswordFromLogin = function(source) {\n        const modalUser = gE('l-user');\n        const directUser = gE('ti-direct-login-user');\n        const modalDb = gE('l-db');\n        const directDb = gE('ti-direct-login-db');\n        const hiddenDb = gE('l-db-hidden');\n        const user = ((source === 'direct' && directUser) ? directUser.value : (modalUser ? modalUser.value : (directUser ? directUser.value : ''))).trim();\n        const db = (source === 'direct' && directDb && directDb.value) ? directDb.value : ((window.getLoginDbValue ? window.getLoginDbValue() : '') || (modalDb ? modalDb.value : '') || (hiddenDb ? hiddenDb.value : '') || (directDb ? directDb.value : ''));\n        if (!user) { window.tiAlert ? window.tiAlert('Inserisci username prima di richiedere il cambio password.') : alert('Inserisci username prima di richiedere il cambio password.'); return false; }\n        if (user === 'SSGlobalAdmin') { window.tiAlert ? window.tiAlert('Il recupero via email e disponibile per gli utenti registrati della ditta, non per SSGlobalAdmin.') : alert('Il recupero via email e disponibile per gli utenti registrati della ditta, non per SSGlobalAdmin.'); return false; }\n        if (!db) { window.tiAlert ? window.tiAlert('Seleziona la ditta prima di richiedere il cambio password.') : alert('Seleziona la ditta prima di richiedere il cambio password.'); return false; }\n        const ok = window.confirm('Inviare una password temporanea all indirizzo email registrato per l utente ' + user + '?');\n        if (!ok) return false;\n        const btn = gE('ti-forgot-password-btn');\n        const oldText = btn ? btn.textContent : '';\n        if (btn) { btn.disabled = true; btn.textContent = 'Invio email...'; }\n        const fd = new FormData();\n        fd.append('action','ti_ai_forgot_password');\n        fd.append('ti_action','ti_ai_forgot_password');\n        fd.append('user', user);\n        fd.append('username', user);\n        fd.append('db', db);\n        fd.append('db_select', db);\n        fd.append('current_db', db);\n        const urls = [];\n        if (window.tiAjaxUrl) urls.push(window.tiAjaxUrl);\n        if (window.tiUrl && urls.indexOf(window.tiUrl) === -1) urls.push(window.tiUrl);\n        if (!urls.length) urls.push(window.location.href.split('#')[0]);\n        const post = function(url) {\n            if (window.postFormDataJsonSafe) return window.postFormDataJsonSafe(url, fd, {tiNoLongProcessSignal:true, cache:'no-store'});\n            return fetch(url, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store', headers:{'X-Requested-With':'XMLHttpRequest'}}).then(function(resp){ return resp.text().then(function(raw){ if (!resp.ok) throw new Error('HTTP ' + resp.status + ': ' + raw.slice(0, 180)); try { return JSON.parse(raw); } catch(e) { throw new Error('Risposta server non valida: ' + raw.slice(0, 180)); } }); });\n        };\n        let chain = Promise.reject(new Error('init'));\n        urls.forEach(function(url){ chain = chain.catch(function(){ return post(url); }); });\n        chain.then(function(res){\n            const msg = res && res.data && res.data.msg ? res.data.msg : (res && res.data && res.data.message ? res.data.message : 'Richiesta completata.');\n            if (res && res.success) {\n                window.tiAlert ? window.tiAlert(msg) : alert(msg);\n                const pass = gE('l-pass') || gE('ti-direct-login-pass');\n                if (pass) try { pass.focus(); } catch(e) {}\n            } else {\n                window.tiAlert ? window.tiAlert('Errore: ' + msg) : alert('Errore: ' + msg);\n            }\n        }).catch(function(err){\n            const msg = err && err.message ? err.message : 'Errore invio richiesta password.';\n            window.tiAlert ? window.tiAlert('Errore recupero password: ' + msg) : alert('Errore recupero password: ' + msg);\n        }).finally(function(){\n            if (btn) { btn.disabled = false; btn.textContent = oldText || 'Password dimenticata? Cambia tramite email'; }\n        });\n        return false;\n    };\n\n\n    window.tiBindStableLoginUsername = function() {\n        try {\n            const bindPair = function(userId, passId) {\n                const u = gE(userId);\n                const p = gE(passId);\n                if (!u || !p || p.dataset.tiStableUserBound) return;\n                p.dataset.tiStableUserBound = '1';\n                const remember = function() {\n                    try {\n                        u.dataset.tiStableUsername = String(u.value || '');\n                    } catch(e) {}\n                };\n                const restore = function() {\n                    try {\n                        const stable = String(u.dataset.tiStableUsername || '');\n                        if (stable !== '' && document.activeElement === p && String(u.value || '') !== stable) {\n                            u.value = stable;\n                            u.dispatchEvent(new Event('input', {bubbles:true}));\n                            u.dispatchEvent(new Event('change', {bubbles:true}));\n                        }\n                    } catch(e) {}\n                };\n                const scheduleRestore = function() {\n                    try {\n                        if (!u.dataset.tiStableUsername) remember();\n                        [0, 40, 120, 300, 700].forEach(function(ms){ setTimeout(restore, ms); });\n                    } catch(e) {}\n                };\n                ['input','change','blur'].forEach(function(ev){\n                    u.addEventListener(ev, remember, false);\n                });\n                ['focus','keydown','input','paste','keyup'].forEach(function(ev){\n                    p.addEventListener(ev, scheduleRestore, false);\n                });\n                remember();\n            };\n            bindPair('l-user', 'l-pass');\n            bindPair('ti-direct-login-user', 'ti-direct-login-pass');\n        } catch(e) {}\n    };\n\n\n    window.bindMobileLoginButton = function() {\n        try {\n            const form = gE('ti-login-form');\n            const btn = gE('l-do');\n            const openBtn = gE('ti-login-btn');\n            const outBtn = gE('ti-logout-btn');\n            if (form && !form.dataset.tiSubmitBound) {\n                form.dataset.tiSubmitBound = '1';\n                form.addEventListener('submit', function(ev){ ev.preventDefault(); ev.stopPropagation(); if (window.doLogin) window.doLogin(); return false; }, false);\n            }\n            if (btn && !btn.dataset.tiClickBound) {\n                btn.dataset.tiClickBound = '1';\n                btn.addEventListener('click', function(ev){ ev.preventDefault(); ev.stopPropagation(); if (window.doLogin) window.doLogin(); return false; }, false);\n            }\n            if (openBtn && !openBtn.dataset.tiClickBound) {\n                openBtn.dataset.tiClickBound = '1';\n                const openPrimaryLogin = function(){ if (window.openLoginModalSafe) window.openLoginModalSafe(); else if (window.openModal) window.openModal('ti-login-ov'); };\n                openBtn.addEventListener('click', function(ev){ ev.preventDefault(); ev.stopPropagation(); openPrimaryLogin(); return false; }, false);\n                openBtn.addEventListener('touchend', function(ev){ ev.preventDefault(); ev.stopPropagation(); openPrimaryLogin(); return false; }, {passive:false});\n            }\n            if (outBtn && !outBtn.dataset.tiClickBound) {\n                outBtn.dataset.tiClickBound = '1';\n                outBtn.addEventListener('click', function(ev){ ev.preventDefault(); ev.stopPropagation(); if (window.doLogout) window.doLogout(); return false; }, false);\n                outBtn.addEventListener('touchend', function(ev){ ev.preventDefault(); ev.stopPropagation(); if (window.doLogout) window.doLogout(); return false; }, {passive:false});\n            }\n            if (window.syncLoginDbSelect) window.syncLoginDbSelect();\n            if (window.tiBindStableLoginUsername) window.tiBindStableLoginUsername();\n        } catch(e) {}\n    };\n    try {\n        if (new URLSearchParams(window.location.search).has('ti_login_error')) { setTimeout(function(){ if (window.openLoginModalSafe) window.openLoginModalSafe(); else if (window.openModal) window.openModal('ti-login-ov'); }, 250); }\n        setTimeout(window.bindMobileLoginButton, 0);\n        setTimeout(window.bindMobileLoginButton, 800);\n        setTimeout(window.bindMobileLoginButton, 1800);\n        document.addEventListener('DOMContentLoaded', window.bindMobileLoginButton, {once:true});\n    } catch(e) {}\n\n    window.tiMobileUnlockScroll = function() {\n        try {\n            if (window.tiIsMobileLike && !window.tiIsMobileLike()) return;\n            document.documentElement.classList.remove('ti-no-scroll');\n            document.body.classList.remove('ti-no-scroll');\n            document.documentElement.style.overflowY = 'auto';\n            document.body.style.overflowY = 'auto';\n            document.documentElement.style.overflowX = 'hidden';\n            document.body.style.overflowX = 'hidden';\n            document.documentElement.style.height = 'auto';\n            document.body.style.height = 'auto';\n            document.documentElement.style.position = 'static';\n            document.body.style.position = 'static';\n            document.documentElement.style.touchAction = 'pan-y';\n            document.body.style.touchAction = 'pan-y';\n            const root = document.getElementById('ti-ai-outer');\n            if (root) {\n                root.style.overflowY = 'visible';\n                root.style.touchAction = 'pan-y';\n                root.style.webkitOverflowScrolling = 'touch';\n            }\n        } catch(e) {}\n    };\n    try {\n        window.addEventListener('pageshow', window.tiMobileUnlockScroll, {passive:true});\n        window.addEventListener('resize', window.tiMobileUnlockScroll, {passive:true});\n        if (window.visualViewport) window.visualViewport.addEventListener('resize', window.tiMobileUnlockScroll, {passive:true});\n        document.addEventListener('touchstart', window.tiMobileUnlockScroll, {passive:true});\n    } catch(e) {}\n\n    window.forceSessionCloseNow = function() {\n        window.idleForceClosing = true;\n        if (window.clearIdleTimers) window.clearIdleTimers();\n        try { document.cookie = 'ti_ai_ss_session=; Max-Age=0; path=\/; SameSite=Lax'; } catch(e) {}\n        try {\n            if (gE('ti-chat-box')) gE('ti-chat-box').innerHTML = '';\n            if (gE('ti-msg')) gE('ti-msg').value = '';\n            if (gE('ti-user-name')) gE('ti-user-name').textContent = 'Cliente\/Utente';\n            if (gE('ti-user-role')) gE('ti-user-role').textContent = '';\n        } catch(e) {}\n        const hardReload = function(){ try { location.reload(); } catch(e) { window.location.href = window.location.href.split('#')[0]; } };\n        const failSafe = setTimeout(hardReload, 1500);\n        try {\n            const fd = new FormData();\n            fd.append('ti_action','ti_ai_logout');\n            fd.append('action','ti_ai_logout');\n            fetch(window.tiUrl, {method:'POST', body:fd, credentials:'same-origin', keepalive:true})\n                .then(function(){ clearTimeout(failSafe); hardReload(); })\n                .catch(function(){ clearTimeout(failSafe); hardReload(); });\n        } catch(e) {\n            clearTimeout(failSafe); hardReload();\n        }\n    };\n\n    window.doLogout = function(silentReset = false) {\n        window.idleForceClosing = true;\n        clearTimeout(window.idleTimer1); clearTimeout(window.idleTimer2); clearTimeout(window.idleTimer3);\n        const hardReload = function(){\n            try {\n                if (gE('ti-chat-box')) gE('ti-chat-box').innerHTML = '';\n                if (gE('ti-msg')) gE('ti-msg').value = '';\n                if (gE('ti-user-name')) gE('ti-user-name').textContent = 'Cliente\/Utente';\n                if (gE('ti-user-role')) gE('ti-user-role').textContent = '';\n                document.cookie = 'ti_ai_ss_session=; Max-Age=0; path=\/; SameSite=Lax';\n            } catch(e) {}\n            try { location.reload(); } catch(e) { window.location.href = window.location.href.split('#')[0]; }\n        };\n        const failSafe = setTimeout(hardReload, 2500);\n        const makeLogoutFd = function(actionName) {\n            const fd = new FormData();\n            fd.append('ti_action', actionName || 'ti_ai_logout');\n            fd.append('action', actionName || 'ti_ai_logout');\n            fd.append('_ti_mobile_ts', String(Date.now()));\n            return fd;\n        };\n        const postSafe = function(actionName) {\n            const urls = [];\n            if (window.tiAjaxUrl) urls.push(window.tiAjaxUrl);\n            if (window.tiUrl && urls.indexOf(window.tiUrl) === -1) urls.push(window.tiUrl);\n            const run = function(i) {\n                const fd = makeLogoutFd(actionName);\n                if (window.postFormDataJsonSafe) {\n                    return window.postFormDataJsonSafe(urls[i] || window.tiUrl, fd, {tiNoLongProcessSignal:true, cache:'no-store'}).catch(function(err){\n                        if (i + 1 < urls.length) return run(i + 1);\n                        throw err;\n                    });\n                }\n                return fetch(urls[i] || window.tiUrl, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store'}).catch(function(err){\n                    if (i + 1 < urls.length) return run(i + 1);\n                    throw err;\n                });\n            };\n            return run(0);\n        };\n        postSafe('ti_ai_logout').then(function(){\n            if (silentReset) return postSafe('ti_ai_reset_flow');\n        }).finally(function(){ clearTimeout(failSafe); hardReload(); });\n    };\n\n    window.doRestart = function() { window.tiConfirm(dict[window.currLang].restartConfirm, function(){ window.doLogout(true); }); };\n\n\n    window.resetNewRegistrationProfileForm = function() {\n        const clearIds = ['p-utente','p-user','p-pass','p-ragione_sociale','p-email','p-tel','p-ind','p-piva_vat','p-cod_fisc','p-ade','p-sesso','p-dato_1','p-dato_2','p-dato_3','p-nota','p-foto'];\n        clearIds.forEach(function(id){\n            const el = gE(id);\n            if (!el) return;\n            try {\n                if (el.type === 'checkbox' || el.type === 'radio') el.checked = false;\n                else el.value = '';\n                if (id === 'p-pass' || \/pass\/i.test(id)) el.setAttribute('autocomplete', 'new-password');\n                else el.setAttribute('autocomplete', 'off');\n                if (el.removeAttribute) el.removeAttribute('value');\n                window.forceWhiteEditStyle && window.forceWhiteEditStyle(el);\n            } catch(e) {}\n        });\n        const eta = gE('p-eta');\n        if (eta) { eta.value = ''; eta.setAttribute('autocomplete', 'off'); }\n        ['p-com_mail','p-com_email','p-com_sms','p-com_tel'].forEach(function(id){ const el = gE(id); if (el) el.checked = true; });\n        const user = gE('p-user'); if (user) { user.readOnly = false; user.value = ''; }\n        const pass = gE('p-pass'); if (pass) { pass.required = true; pass.value = ''; }\n        const prev = gE('p-foto-preview'); if (prev) { prev.removeAttribute('src'); prev.style.display = 'none'; }\n        const form = gE('ti-profile-form'); if (form) form.setAttribute('autocomplete', 'off');\n    };\n\n    window.openProfileModal = function(isNew = false) {\n        const ditta = gE('ti-ditta'); if(!ditta || !ditta.value || ditta.value === 'NEW_DB' || ditta.value === '') { window.tiAlert(\"Seleziona una ditta prima di registrarti.\"); return; }\n        gE('lbl-profile-title').innerText = isNew ? \"Crea Profilo\" : \"Modifica Profilo\";\n        gE('lbl-p-pass').innerText = isNew ? \"Password*\" : \"Nuova Password (lascia vuoto per mantenere)\";\n        gE('p-pass').required = isNew;\n        \n        const cancelBtn = gE('ti-profile-cancel-btn');\n        if(isNew) {\n            if (cancelBtn) cancelBtn.style.display = 'none';\n            window.resetNewRegistrationProfileForm();\n            window.openModal('ti-profile-ov');\n            window.resetNewRegistrationProfileForm();\n            setTimeout(window.resetNewRegistrationProfileForm, 80);\n            setTimeout(window.resetNewRegistrationProfileForm, 350);\n            window.bindProfileUsernameDuplicatePopup && window.bindProfileUsernameDuplicatePopup();\n        } else {\n            if (cancelBtn) cancelBtn.style.display = '';\n            gE('p-user').value = window.tiUser; gE('p-user').readOnly = true; gE('p-utente').value = window.tiUserDisplay;\n            if(window.tiUserFoto) { gE('p-foto').value = window.tiUserFoto; const fotoDb = window.tiUserFotoDb || gE('ti-ditta').value; const safeUserFoto = window.getSafePreviewUrl(window.tiUserFoto, fotoDb, 'Utenti', 'Immagine', (gE('p-user').value || window.tiUser || 'utente')); if(safeUserFoto){ gE('p-foto-preview').src = safeUserFoto; gE('p-foto-preview').style.display = 'block'; } else { gE('p-foto-preview').style.display = 'none'; } }\n            \n            const fd = new FormData(); fd.append('ti_action', 'ti_ai_get_db_data'); fd.append('db', ditta.value);\n            window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d=>{\n                if(d.success && d.data && d.data.Tabelle && d.data.Tabelle.Utenti) {\n                    let myU = d.data.Tabelle.Utenti.find(u => (u.Username||u.user||'').toLowerCase() === window.tiUser.toLowerCase());\n                    if(myU) {\n                        if(gE('p-utente')) gE('p-utente').value = myU['Utente'] || myU['Nome'] || window.tiUserDisplay;\n                        if(gE('p-email')) gE('p-email').value = myU['Email'] || '';\n                        if(gE('p-ragione_sociale')) gE('p-ragione_sociale').value = myU['Ragione sociale'] || '';\n                        if(gE('p-ind')) gE('p-ind').value = myU['Indirizzo'] || '';\n                        if(gE('p-piva_vat')) gE('p-piva_vat').value = myU['PIVA_VAT'] || myU['P.IVA'] || '';\n                        if(gE('p-cod_fisc')) gE('p-cod_fisc').value = myU['COD_FISC'] || '';\n                        if(gE('p-ade')) gE('p-ade').value = myU['ADE'] || '';\n                        if(gE('p-tel')) gE('p-tel').value = myU['Telefono'] || myU['Tel'] || '';\n                        if(gE('p-sesso')) gE('p-sesso').value = myU['Sesso'] || '';\n                        if(gE('p-eta')) gE('p-eta').value = myU['Et\u00e0'] || '0';\n                        if(gE('p-dato_1')) gE('p-dato_1').value = myU['Dato 1'] || '';\n                        if(gE('p-dato_2')) gE('p-dato_2').value = myU['Dato 2'] || '';\n                        if(gE('p-dato_3')) gE('p-dato_3').value = myU['Dato 3'] || '';\n                        if(gE('p-nota')) gE('p-nota').value = myU['Nota'] || '';\n                        if(gE('p-com_mail')) gE('p-com_mail').checked = (myU['Com Mail'] !== 'NO');\n                        if(gE('p-com_email')) gE('p-com_email').checked = (myU['Com Email'] !== 'NO');\n                        if(gE('p-com_sms')) gE('p-com_sms').checked = (myU['Com SMS'] !== 'NO');\n                        if(gE('p-com_tel')) gE('p-com_tel').checked = (myU['Com Tel'] !== 'NO');\n                    }\n                }\n                window.openModal('ti-profile-ov');\n                window.bindProfileUsernameDuplicatePopup && window.bindProfileUsernameDuplicatePopup();\n            });\n        }\n    };\n\n    window.createDbSubmit = function() {\n        const dbName = gE('nd-name').value.trim(); const uName = gE('nd-user').value.trim(); const uPass = gE('nd-pass').value; const uEmail = gE('nd-email').value.trim();\n        if(!dbName || !uName || !uPass) { window.tiAlert(\"Dati obbligatori mancanti!\"); return; }\n        const fd = window.buildAjaxFormData ? window.buildAjaxFormData('ti_ai_create_db') : new FormData(); fd.append('name', dbName); fd.append('username', uName); fd.append('password', uPass); fd.append('nome', uName); fd.append('email', uEmail);\n        if (!fd.has('action')) fd.append('action', 'ti_ai_create_db');\n        if (!fd.has('ti_action')) fd.append('ti_action', 'ti_ai_create_db');\n        window.fetchJsonSafe((window.tiAjaxUrl || window.tiUrl), {method:'POST', body:fd, credentials:'same-origin'}).then(d=>{\n            if(d.success) { localStorage.setItem('ti_saved_db', d.data.db); location.href = window.tiUrl; }\n            else {\n                const msg = (d && d.data && d.data.message) ? d.data.message : 'Errore creazione ditta.';\n                if (\/username\/i.test(msg) && \/(gi\u00e0|gia|esistent|uso|duplicat)\/i.test(msg)) {\n                    window.tiAlert('Username gi\u00e0 esistente.<br>Cambia username perch\u00e9 \u00e8 gi\u00e0 esistente.');\n                    const el = gE('nd-user'); if (el) { window.forceWhiteEditStyle && window.forceWhiteEditStyle(el); setTimeout(function(){ try { el.focus(); el.select && el.select(); } catch(e) {} }, 120); }\n                } else window.tiAlert(msg);\n            }\n        });\n    };\n\n    if(gE('p-foto')) gE('p-foto').addEventListener('change', function() { \n        const prev = gE('p-foto-preview'); \n        if(this.value) { \n            prev.src = window.getSafePreviewUrl(this.value, gE('ti-ditta').value, 'Utenti', 'Immagine', (gE('p-user').value || window.tiUser || 'utente')); \n            prev.style.display = 'block'; \n        } else { prev.style.display = 'none'; } \n    });\n\n    window.saveProfile = function() {\n        const submitBtn = gE('ti-profile-save-btn') || document.querySelector('#ti-profile-form button[type=\"submit\"]');\n        if (submitBtn) { submitBtn.disabled = true; submitBtn.innerText = 'Salvataggio...'; }\n        const fd = new FormData(); fd.append('ti_action','ti_ai_save_profile'); fd.append('db', gE('ti-ditta').value); \n        fd.append('utente', gE('p-utente').value); fd.append('username', gE('p-user').value); fd.append('password', gE('p-pass').value); \n        fd.append('ragione_sociale', gE('p-ragione_sociale').value); fd.append('email', gE('p-email').value); fd.append('tel', gE('p-tel').value); \n        fd.append('indirizzo', gE('p-ind').value); fd.append('piva_vat', gE('p-piva_vat').value); fd.append('cod_fisc', gE('p-cod_fisc').value); \n        fd.append('ade', gE('p-ade').value); fd.append('sesso', gE('p-sesso').value); fd.append('eta', gE('p-eta').value); \n        fd.append('dato_1', gE('p-dato_1').value); fd.append('dato_2', gE('p-dato_2').value); fd.append('dato_3', gE('p-dato_3').value); \n        fd.append('com_mail', gE('p-com_mail').checked ? 'SI' : 'NO'); fd.append('com_email', gE('p-com_email').checked ? 'SI' : 'NO'); \n        fd.append('com_sms', gE('p-com_sms').checked ? 'SI' : 'NO'); fd.append('com_tel', gE('p-com_tel').checked ? 'SI' : 'NO'); \n        fd.append('nota', gE('p-nota').value); fd.append('foto', gE('p-foto').value); fd.append('immagine', gE('p-foto').value);\n        window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d=>{\n            if(d.success) { window.tiAlert(d.data.msg); location.reload(); }\n            else {\n                if (submitBtn) { submitBtn.disabled = false; submitBtn.innerText = 'Salva'; }\n                const msg = (d && d.data && d.data.message) ? d.data.message : 'Errore salvataggio profilo.';\n                if (\/username\/i.test(msg) && \/(gi\u00e0|gia|esistent|uso|duplicat)\/i.test(msg)) {\n                    const userEl = gE('p-user');\n                    window.tiAlert('Username gi\u00e0 esistente.<br>Cambia username perch\u00e9 \u00e8 gi\u00e0 esistente.');\n                    if (userEl) {\n                        userEl.style.setProperty('border-color', '#ef4444', 'important');\n                        userEl.style.setProperty('box-shadow', '0 0 0 2px rgba(239,68,68,.55)', 'important');\n                        window.forceWhiteEditStyle && window.forceWhiteEditStyle(userEl);\n                        setTimeout(function(){ try { userEl.focus(); userEl.select && userEl.select(); } catch(e) {} }, 120);\n                    }\n                } else {\n                    window.tiAlert(msg);\n                }\n            }\n        }).catch(e=>{ if (submitBtn) { submitBtn.disabled = false; submitBtn.innerText = 'Salva'; } window.tiAlert('Errore di rete durante il salvataggio profilo.'); });\n    };\n\n    window.bindProfileUsernameDuplicatePopup = function() {\n        const el = gE('p-user');\n        if (!el || el.dataset.dupBound === '1') return;\n        el.dataset.dupBound = '1';\n        const run = function(){\n            if (el.readOnly) return;\n            const username = String(el.value || '').trim();\n            if (!username) return;\n            if (window.currentDbData && window.currentDbData.Tabelle) {\n                const dup = window.findDuplicateUsernameInConfig ? window.findDuplicateUsernameInConfig(username, '', -1) : null;\n                if (dup) {\n                    window.showDuplicateUsernamePopup(username, dup);\n                    el.style.setProperty('border-color', '#ef4444', 'important');\n                    el.style.setProperty('box-shadow', '0 0 0 2px rgba(239,68,68,.55)', 'important');\n                    window.forceWhiteEditStyle && window.forceWhiteEditStyle(el);\n                }\n            }\n        };\n        el.addEventListener('change', run);\n        el.addEventListener('blur', run);\n        el.addEventListener('input', function(){ window.forceWhiteEditStyle && window.forceWhiteEditStyle(el); });\n    };\n\n    window.bindProfileUsernameDuplicatePopup();\n\n    window.cancelProfile = function() {\n        const username = gE('p-user') ? gE('p-user').value.trim() : '';\n        if (!username) { window.tiAlert('Nessun utente selezionato.'); return; }\n        window.tiConfirm('Confermi la cancellazione logica del profilo? Lo stato verra impostato su Cancellato.', function(){\n            const fd = new FormData();\n            fd.append('ti_action', 'ti_ai_delete_profile');\n            fd.append('db', gE('ti-ditta').value);\n            fd.append('username', username);\n            fd.append('mode', 'soft');\n            window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d=>{\n                if(d.success) {\n                    window.tiAlert(d.data.msg || 'Profilo cancellato.');\n                    if (d.data.logout) { setTimeout(()=> location.reload(), 400); }\n                    else { window.closeModal('ti-profile-ov'); location.reload(); }\n                } else {\n                    window.tiAlert(d.data.message || 'Errore durante la cancellazione del profilo.');\n                }\n            }).catch(()=> window.tiAlert('Errore di rete durante la cancellazione del profilo.'));\n        });\n    };\n\n    window.tiCompanyHelpEscape = function(v) {\n        return String(v == null ? '' : v).replace(\/[&<>\"']\/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c]; });\n    };\n    window.tiGetCompanyConfigRow = function() {\n        const data = window.currentDbData || window.currentDbViewData || null;\n        const tables = data && data.Tabelle ? data.Tabelle : {};\n        const cfgRows = tables.Config || tables.config || tables.CONFIG || [];\n        return Array.isArray(cfgRows) && cfgRows[0] && typeof cfgRows[0] === 'object' ? cfgRows[0] : {};\n    };\n    window.tiPickCompanyVal = function(row, names) {\n        row = row || {};\n        const keys = Object.keys(row);\n        const norm = function(x){ return String(x || '').toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').replace(\/[_\\-]+\/g, ' ').replace(\/\\s+\/g, ' ').trim(); };\n        for (const name of names) {\n            if (Object.prototype.hasOwnProperty.call(row, name) && String(row[name] || '').trim() !== '') return String(row[name]).trim();\n            const wanted = norm(name);\n            const found = keys.find(k => norm(k) === wanted);\n            if (found && String(row[found] || '').trim() !== '') return String(row[found]).trim();\n        }\n        return '';\n    };\n    window.tiBuildCompanyInfoLinksHtml = function() {\n        const data = window.currentDbData || window.currentDbViewData || null;\n        const tables = data && data.Tabelle ? data.Tabelle : {};\n        const infoRows = tables.Informazioni || tables.informazioni || [];\n        if (!Array.isArray(infoRows)) return '';\n        const wanted = [\n            {key:'commerciali', label:'Condizioni commerciali', re:\/commercial|vendita|pagament|offert|listin|prezz|recess|reso|spedizion\/i},\n            {key:'contrattuali', label:'Condizioni contrattuali', re:\/contratt|termini|accord|fornitura|garanzi|clausol\/i},\n            {key:'privacy', label:'Privacy', re:\/privacy|gdpr|dati personal|informativa|cookie\/i}\n        ];\n        const found = {};\n        const clean = function(v){ return String(v == null ? '' : v).trim(); };\n        const firstUrl = function(txt) { const m = String(txt || '').match(\/https?:\\\/\\\/[^\\s<>'\")]+\/i); return m ? m[0].replace(\/[\\],.;]+$\/,'') : ''; };\n        infoRows.forEach(function(row){\n            if (!row || typeof row !== 'object') return;\n            const flat = Object.keys(row).map(k => k + ' ' + clean(row[k])).join(' ');\n            const tipo = clean(row.Tipo || row.tipo || row.Type || '').toUpperCase();\n            const doc = clean(row.Doc || row.Docs || row.File || row.URL || row.Url || row.Link || row.Descrizione || '');\n            const url = firstUrl(doc) || firstUrl(flat);\n            wanted.forEach(function(w){\n                if (found[w.key]) return;\n                if ((tipo === 'DOC' || tipo === 'URL' || tipo === 'WEB' || tipo === '') && w.re.test(flat)) {\n                    found[w.key] = url ? `<a href=\"${window.tiCompanyHelpEscape(url)}\" target=\"_blank\" rel=\"noopener\" style=\"color:#93c5fd;text-decoration:underline;\">${w.label}<\/a>` : `<span style=\"color:#cbd5e1;\">${w.label}<\/span>`;\n                }\n            });\n        });\n        const links = wanted.map(w => found[w.key]).filter(Boolean);\n        return links.length ? `<div>${links.join(' &nbsp;|&nbsp; ')}<\/div>` : '';\n    };\n    window.tiUpdateColumnHelpCompanyHeader = function() {\n        const box = document.getElementById('col-help-company');\n        if (!box) return;\n        const cfg = window.tiGetCompanyConfigRow ? window.tiGetCompanyConfigRow() : {};\n        const pick = window.tiPickCompanyVal || function(){ return ''; };\n        const lines = [];\n        const rag = pick(cfg, ['Ragione sociale','Ragione Sociale','Ditta','Nome ditta']);\n        const indirizzo = pick(cfg, ['Indirizzo','Sede','Indirizzo ditta']);\n        const piva = pick(cfg, ['PIVA_VAT','PIVA VAT','Partita IVA','P. IVA','PIva','PIVA']);\n        const email = pick(cfg, ['Email ditta','Email principale','Email','E-mail','Mail']);\n        const tel = pick(cfg, ['Tel','Telefono','Telefono ditta','Cellulare']);\n        if (rag) lines.push(`<div><b>${window.tiCompanyHelpEscape(rag)}<\/b><\/div>`);\n        if (indirizzo) lines.push(`<div>${window.tiCompanyHelpEscape(indirizzo)}<\/div>`);\n        if (piva) lines.push(`<div>Partita IVA: ${window.tiCompanyHelpEscape(piva)}<\/div>`);\n        if (email) lines.push(`<div>Email: <a href=\"mailto:${window.tiCompanyHelpEscape(email)}\" style=\"color:#93c5fd;\">${window.tiCompanyHelpEscape(email)}<\/a><\/div>`);\n        if (tel) lines.push(`<div>Tel: <a href=\"tel:${window.tiCompanyHelpEscape(tel)}\" style=\"color:#93c5fd;\">${window.tiCompanyHelpEscape(tel)}<\/a><\/div>`);\n        const links = window.tiBuildCompanyInfoLinksHtml ? window.tiBuildCompanyInfoLinksHtml() : '';\n        if (links) lines.push(links);\n        box.innerHTML = lines.join('');\n        box.style.display = lines.length ? 'block' : 'none';\n        try { rewriteLocalDocAnchors(document.getElementById('ti-help-ov') || document); } catch(e) {}\n    };\n\n    window.tiSanitizeColumnHelpText357 = window.tiSanitizeColumnHelpText357 || function(text) {\n        return String(text || '')\n            .replace(\/[#*]+\/g, '')\n            .replace(\/[ \\t]+\\n\/g, '\\n')\n            .replace(\/\\n{3,}\/g, '\\n\\n')\n            .trim();\n    };\n    window.copyColumnHelpText357 = window.copyColumnHelpText357 || function() {\n        const box = gE('col-help-content');\n        const title = gE('col-help-title');\n        const btn = gE('ti-col-help-copy');\n        const text = ((title ? String(title.innerText || title.textContent || '') : '') + '\\n\\n' + (box ? String(box.innerText || box.textContent || '') : '')).trim();\n        const done = function(ok){\n            if (!btn) return;\n            const old = btn.innerText;\n            btn.innerText = ok ? 'Testo copiato' : 'Copia non riuscita';\n            setTimeout(function(){ btn.innerText = old || 'Copia testo'; }, 1200);\n        };\n        if (!text) return done(false);\n        if (navigator.clipboard && navigator.clipboard.writeText) {\n            navigator.clipboard.writeText(text).then(function(){ done(true); }).catch(function(){ done(false); });\n        } else {\n            try {\n                const ta = document.createElement('textarea');\n                ta.value = text; ta.style.position = 'fixed'; ta.style.left = '-9999px';\n                document.body.appendChild(ta); ta.focus(); ta.select();\n                const ok = document.execCommand('copy'); document.body.removeChild(ta); done(!!ok);\n            } catch(e) { done(false); }\n        }\n    };\n\n    window.askColumnHelp = function(tbl, col) {\n        const dbVal = gE('ti-ditta').value || 'NEW_DB';\n        const cleanTbl = window.tiSanitizeColumnHelpText357(String(tbl || '').trim());\n        const cleanCol = window.tiSanitizeColumnHelpText357(String(col || '').replace(\/\\\\'\/g, \"'\").replace(\/[\"\u201c\u201d]\/g, '').trim());\n        const helpIsEn = String(window.currLang || 'it').toLowerCase().indexOf('en') === 0;\n        const helpOv = gE('ti-col-help-ov');\n        const helpReqId = String(Date.now()) + '-' + Math.random().toString(36).slice(2);\n        window.tiColumnHelpActiveRequestId = helpReqId;\n        if (helpOv) {\n            helpOv.setAttribute('data-ti-help-request-id', helpReqId);\n            helpOv.setAttribute('data-ti-help-table', cleanTbl);\n            helpOv.setAttribute('data-ti-help-column', cleanCol);\n        }\n        gE('col-help-title').innerText = cleanCol\n            ? (helpIsEn ? `Field info: ${cleanCol} [${cleanTbl}]` : `Info campo: ${cleanCol} [${cleanTbl}]`)\n            : (helpIsEn ? `Table info: ${cleanTbl}` : `Info tabella: ${cleanTbl}`);\n        if (window.tiUpdateColumnHelpCompanyHeader) window.tiUpdateColumnHelpCompanyHeader();\n        const contentBox = gE('col-help-content');\n        contentBox.innerHTML = cleanCol\n            ? (helpIsEn ? `<i>\u23f3 I am checking the manual, schema and available data for field <b>${cleanCol}<\/b> in table <b>${cleanTbl}<\/b>...<\/i>` : `<i>\u23f3 Consulto manuale, schema e dati disponibili per il campo <b>${cleanCol}<\/b> della tabella <b>${cleanTbl}<\/b>...<\/i>`)\n            : (helpIsEn ? `<i>\u23f3 I am checking the manual, schema and available data for table <b>${cleanTbl}<\/b>...<\/i>` : `<i>\u23f3 Consulto manuale, schema e dati disponibili per la tabella <b>${cleanTbl}<\/b>...<\/i>`);\n        window.openModal('ti-col-help-ov');\n        if (window.tiPromoteModalFront355) window.tiPromoteModalFront355('ti-col-help-ov');\n        const fd = new FormData(); fd.append('ti_action', 'ti_ai_chat_start'); fd.append('db', dbVal);\n        const langInstruction = helpIsEn ? ' Rispondi in inglese, senza aggiungere la versione italiana.' : ' Rispondi in italiano, senza aggiungere la versione inglese.';\n        const helpRequest = cleanCol\n            ? `AI_MANUAL_HELP: Dai solo informazioni operative chiare sul campo \"${cleanCol}\" nella tabella \"${cleanTbl}\". Indica a cosa serve, come compilarlo e un esempio in base a manuale, schema standard, dati disponibili della ditta, righe Informazioni Tipo RIC\/DOC e documenti\/URL collegati se pertinenti. Non citare campi vuoti o colonne senza nome.${langInstruction}`\n            : `AI_MANUAL_HELP: Dai solo informazioni operative chiare sulla tabella \"${cleanTbl}\". Indica a cosa serve, quali campi principali compilare e un esempio in base a manuale, schema standard, dati disponibili della ditta, righe Informazioni Tipo RIC\/DOC e documenti\/URL collegati se pertinenti. Non citare campi vuoti o colonne senza nome.${langInstruction}`;\n        fd.append('text', helpRequest);\n        window.fetchJsonSafe(window.tiUrl, {method: 'POST', body: fd}).then(res => {\n            if (window.tiColumnHelpActiveRequestId !== helpReqId) return;\n            if (helpOv && helpOv.getAttribute('data-ti-help-request-id') !== helpReqId) return;\n            if(res.success) {\n                gE('col-help-title').innerText = cleanCol\n                    ? (helpIsEn ? `Field info: ${cleanCol} [${cleanTbl}]` : `Info campo: ${cleanCol} [${cleanTbl}]`)\n                    : (helpIsEn ? `Table info: ${cleanTbl}` : `Info tabella: ${cleanTbl}`);\n                contentBox.innerHTML = window.tiSanitizeColumnHelpText357(String(res.data.reply || '')).replace(\/\\n\/g, '<br>');\n                try { if (window.applyLayoutLanguage) window.applyLayoutLanguage(false); } catch(e) {}\n                if (window.tiPromoteModalFront355) window.tiPromoteModalFront355('ti-col-help-ov');\n            } else {\n                contentBox.innerHTML = helpIsEn ? \"<span style='color:#ef4444;'>Error while searching the manual.<\/span>\" : \"<span style='color:#ef4444;'>Errore durante la ricerca nel manuale.<\/span>\";\n                if (window.tiPromoteModalFront355) window.tiPromoteModalFront355('ti-col-help-ov');\n            }\n        }).catch(e => {\n            if (window.tiColumnHelpActiveRequestId !== helpReqId) return;\n            if (helpOv && helpOv.getAttribute('data-ti-help-request-id') !== helpReqId) return;\n            contentBox.innerHTML = helpIsEn ? \"<span style='color:#ef4444;'>Error while searching the manual.<\/span>\" : \"<span style='color:#ef4444;'>Errore durante la ricerca nel manuale.<\/span>\";\n            if (window.tiPromoteModalFront355) window.tiPromoteModalFront355('ti-col-help-ov');\n        });\n\n    };\n\n    window.globalActionProgressState = { timer: null, percent: 0, target: 92, active: false };\n    window.setGlobalActionProgress = function(percent, text) {\n        const wrap = gE('global-action-progress-wrap'); const bar = gE('global-action-progress-bar'); const label = gE('global-action-progress-text');\n        if (wrap) wrap.style.display = 'block';\n        const pct = Math.max(0, Math.min(100, parseInt(percent, 10) || 0));\n        if (bar) bar.style.width = pct + '%';\n        if (label) label.innerText = pct + '% - ' + (text || 'Elaborazione in corso...');\n        window.globalActionProgressState.percent = pct;\n    };\n    window.startGlobalActionProgress = function(text) {\n        clearInterval(window.globalActionProgressState.timer);\n        window.globalActionProgressState.active = true;\n        window.globalActionProgressState.percent = 3;\n        window.globalActionProgressState.target = 94;\n        window.setGlobalActionProgress(3, text || 'Avvio elaborazione globale...');\n        window.globalActionProgressState.timer = setInterval(function(){\n            if (!window.globalActionProgressState.active) return;\n            let pct = window.globalActionProgressState.percent;\n            if (pct >= window.globalActionProgressState.target) return;\n            pct += (pct < 20 ? 5 : (pct < 45 ? 4 : (pct < 70 ? 3 : 1)));\n            if (pct > window.globalActionProgressState.target) pct = window.globalActionProgressState.target;\n            window.setGlobalActionProgress(pct, 'Analisi AI e scrittura sui database in corso...');\n        }, 650);\n    };\n    window.finishGlobalActionProgress = function(success, text) {\n        clearInterval(window.globalActionProgressState.timer);\n        window.globalActionProgressState.active = false;\n        window.setGlobalActionProgress(success ? 100 : Math.max(0, window.globalActionProgressState.percent || 0), text || (success ? 'Operazione completata' : 'Operazione interrotta'));\n    };\n    window.resetGlobalActionProgress = function() {\n        clearInterval(window.globalActionProgressState.timer);\n        window.globalActionProgressState.active = false;\n        window.globalActionProgressState.percent = 0;\n        const wrap = gE('global-action-progress-wrap'); const bar = gE('global-action-progress-bar'); const label = gE('global-action-progress-text');\n        if (wrap) wrap.style.display = 'none';\n        if (bar) bar.style.width = '0%';\n        if (label) label.innerText = '0% - In attesa di avvio...';\n    };\n\n    \/\/ 30.9.327: anche la modifica globale usa avanzamento monotono a tempo trascorso.\n    (function installTiRobustGlobalProgress327(){\n        if (window.__tiRobustGlobalProgress327Installed) return;\n        window.__tiRobustGlobalProgress327Installed = true;\n        function gpClamp(v){ v = parseInt(v, 10); if (!isFinite(v) || isNaN(v)) v = 0; return Math.max(0, Math.min(100, v)); }\n        function gpSet(pct, text){\n            pct = gpClamp(pct);\n            const st = window.globalActionProgressState || (window.globalActionProgressState = {});\n            const cur = gpClamp(st.percent || 0);\n            if (pct < 100 && cur > pct) pct = cur;\n            const wrap = gE('global-action-progress-wrap'); const bar = gE('global-action-progress-bar'); const label = gE('global-action-progress-text');\n            if (wrap) wrap.style.display = 'block';\n            if (bar) bar.style.width = pct + '%';\n            if (label) label.innerText = pct + '% - ' + (text || 'Elaborazione in corso...');\n            st.percent = pct;\n        }\n        if (typeof window.startGlobalActionProgress === 'function' && !window.startGlobalActionProgress.__tiRobustProgress327) {\n            const prevStart = window.startGlobalActionProgress;\n            window.startGlobalActionProgress = function(text){\n                const ret = prevStart.apply(this, arguments);\n                const st = window.globalActionProgressState || (window.globalActionProgressState = {});\n                st.startedAt327 = Date.now();\n                clearInterval(st.watchdog327);\n                st.watchdog327 = setInterval(function(){\n                    if (!st.active) { clearInterval(st.watchdog327); return; }\n                    const elapsed = Math.max(0, Date.now() - (st.startedAt327 || Date.now()));\n                    let pct = 3 + Math.min(1, elapsed \/ 60000) * 91;\n                    if (elapsed > 60000) pct = 94 + Math.min(1, (elapsed - 60000) \/ 90000) * 5;\n                    gpSet(Math.floor(pct), elapsed > 90000 ? 'Elaborazione lunga ancora in corso. Verifico il completamento lato server...' : 'Analisi AI e scrittura sui database in corso...');\n                }, 1000);\n                return ret;\n            };\n            window.startGlobalActionProgress.__tiRobustProgress327 = true;\n        }\n        if (typeof window.setGlobalActionProgress === 'function' && !window.setGlobalActionProgress.__tiRobustProgress327) {\n            const prevSet = window.setGlobalActionProgress;\n            window.setGlobalActionProgress = function(percent, text){\n                const st = window.globalActionProgressState || {};\n                let pct = gpClamp(percent);\n                const cur = gpClamp(st.percent || 0);\n                if (pct < 100 && cur > pct) pct = cur;\n                return prevSet.call(this, pct, text);\n            };\n            window.setGlobalActionProgress.__tiRobustProgress327 = true;\n        }\n        if (typeof window.finishGlobalActionProgress === 'function' && !window.finishGlobalActionProgress.__tiRobustProgress327) {\n            const prevFinish = window.finishGlobalActionProgress;\n            window.finishGlobalActionProgress = function(success, text){\n                const st = window.globalActionProgressState || {};\n                if (st.watchdog327) clearInterval(st.watchdog327);\n                return prevFinish.call(this, success, text || (success ? 'Operazione completata' : 'Operazione interrotta'));\n            };\n            window.finishGlobalActionProgress.__tiRobustProgress327 = true;\n        }\n    })();\n\n    window.doGlobalAction = function() {\n        const prompt = gE('global-action-prompt').value.trim();\n        if(!prompt) { window.tiAlert(\"Inserisci l'operazione che vuoi eseguire.\"); return; }\n        const warning = '<b>Attenzione: modifica globale database<\/b><br><br>' +\n            'Questa operazione puo modificare piu database delle ditte in una sola volta.<br>' +\n            'Prima dell esecuzione viene escluso sempre il template standard <b>shopservicemain.json<\/b>, che non verra modificato.<br><br>' +\n            '<b>Operazione richiesta:<\/b><br><span style=\"color:#fbbf24;white-space:pre-wrap;display:block;margin-top:6px;\">' + window.escapeHtml(prompt) + '<\/span><br>' +\n            'Confermi di voler procedere con cautela?';\n        window.tiConfirmYesNoAction(warning, function(){\n            window.doGlobalActionConfirmed(prompt);\n        }, null, 'CONFERMA MODIFICA', 'ANNULLA');\n    };\n\n    window.doGlobalActionConfirmed = function(prompt, previewToken, definitiveConfirm, tableScope) {\n        const btn = gE('global-action-btn'); const resBox = gE('global-action-res');\n        btn.innerText = definitiveConfirm ? \"\u23f3 Esecuzione definitiva in corso...\" : \"\u23f3 Generazione preview...\";\n        btn.disabled = true; resBox.style.display = 'block';\n        resBox.innerHTML = definitiveConfirm ? \"Esecuzione definitiva su tutti i database dopo preview approvata. Non chiudere la pagina.\" : \"Genero una preview di modifica della struttura del database su un database campione. Nessuna struttura del database viene modificata in questa fase. Sono eventualmente modificati i soli valori dei campi del database.\";\n        window.startGlobalActionProgress(definitiveConfirm ? 'Avvio modifica globale definitiva...' : 'Generazione preview obbligatoria...');\n        const fd = new FormData(); fd.append('ti_action', 'ti_ai_global_action'); fd.append('prompt', prompt); fd.append('global_confirm', '1');\n        if (previewToken) fd.append('global_template_preview_token', previewToken);\n        if (definitiveConfirm) fd.append('global_template_confirm', '1');\n        if (tableScope) fd.append('global_table_scope', tableScope);\n        window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d=>{\n            btn.innerText = \"Esegui Modifica su Tutti i DB\"; btn.disabled = false;\n            if(d.success) {\n                if (d.data && d.data.requires_table_scope) {\n                    window.finishGlobalActionProgress(false, 'Ambito tabelle richiesto. Nessun database modificato.');\n                    resBox.innerHTML = d.data.reply || 'Indica le tabelle prima di eseguire la modifica globale.';\n                    const answer = window.prompt(d.data.scope_prompt || 'Scrivi TUTTE oppure elenca le tabelle separate da virgola:', '');\n                    if (answer && String(answer).trim()) {\n                        window.doGlobalActionConfirmed(prompt, previewToken, definitiveConfirm, String(answer).trim());\n                    } else {\n                        resBox.innerHTML += '<br><br><span style=\"color:#f59e0b;\">Operazione sospesa. Nessun database e stato modificato.<\/span>';\n                    }\n                    return;\n                }\n                if (d.data && d.data.requires_template_preview) {\n                    window.finishGlobalActionProgress(false, 'Preview generata. In attesa di approvazione.');\n                    resBox.innerHTML = d.data.reply || 'Preview generata. Nessun database modificato.';\n                    const previewHtml = d.data.preview_html || 'Preview generata. Confermi la modifica definitiva?';\n                    const token = d.data.preview_token || '';\n                    window.tiConfirmYesNoAction(previewHtml, function(){\n                        window.doGlobalActionConfirmed(prompt, token, true);\n                    }, function(){\n                        resBox.innerHTML = '<span style=\"color:#f59e0b;\">Operazione annullata. Nessun database e stato modificato.<\/span>';\n                    }, 'APPROVA MODIFICA DEFINITIVA', 'ANNULLA');\n                    return;\n                }\n                let finalMsg = 'Modifica globale completata';\n                if (d.data && d.data.stats) {\n                    const s = d.data.stats;\n                    finalMsg = 'Completato: DB ' + (s.db_modified || 0) + ', tabelle ' + (s.tables_touched || 0) + ', campi ' + (s.fields_touched || 0);\n                }\n                window.finishGlobalActionProgress(true, finalMsg);\n                resBox.innerHTML = d.data.reply;\n            } else {\n                window.finishGlobalActionProgress(false, 'Errore modifica globale');\n                resBox.innerHTML = \"<span style='color:#ef4444;'>Errore: \" + d.data.message + \"<\/span>\";\n            }\n        }).catch(e => {\n            btn.innerText = \"Esegui Modifica su Tutti i DB\"; btn.disabled = false;\n            if (e && (e.userCancelled || window.isLongAIProcessCancelled())) {\n                window.clearLongAIProcess();\n                window.finishGlobalActionProgress(false, 'Operazione interrotta dall utente');\n                resBox.innerHTML = \"<span style='color:#f59e0b;'>Operazione interrotta dall utente.<\/span>\";\n                return;\n            }\n            window.finishGlobalActionProgress(false, 'Errore di rete');\n            resBox.innerHTML = \"<span style='color:#ef4444;'>Errore di rete.<\/span>\";\n        });\n    };\n\n\n    window.activityReportStorageKey = function() {\n        const u = window.tiUser || 'guest';\n        const db = (gE('ti-ditta') && gE('ti-ditta').value) ? gE('ti-ditta').value : (window.tiForced || 'global');\n        return 'ti_activity_report_flags_' + u + '_' + db;\n    };\n    window.activityReportMetricOptions = ['Prezzo','Quantit\u00e0','Totale'];\n    window.getDefaultActivityMetricFlags = function() {\n        return { Prezzo: false, Quantit\u00e0: false, Totale: true };\n    };\n    window.userCanSeeConfigurationActivities = function() {\n        const role = String(window.tiRole || '').toLowerCase();\n        const user = String(window.tiUser || '').toLowerCase();\n        return user === 'ssglobaladmin' || role.includes('amministratore') || role.includes('configuratore');\n    };\n    window.isConfigurationActivityLike = function(row) {\n        if (!row || typeof row !== 'object') return false;\n        const vals = [];\n        Object.keys(row).forEach(function(k){\n            if (\/(descrizione|attivit|attivit\u00e0|dettaglio|note|nota|tipo|stato)\/i.test(String(k || ''))) vals.push(row[k]);\n        });\n        return vals.some(function(v){\n            if (v === null || v === undefined) return false;\n            const txt = String(v).trim().toLowerCase();\n            return \/^configurazione\\b\/.test(txt) || txt.indexOf('attivit\u00e0 di configurazione') !== -1 || txt.indexOf('attivita di configurazione') !== -1;\n        });\n    };\n    window.getDefaultActivityExcludeConfig = function() { return false; };\n    window.loadActivityReportPrefs = function() {\n        try {\n            const raw = localStorage.getItem(window.activityReportStorageKey());\n            if (!raw) return {colFlags:{}, rowFlags:{}, metricFlags: window.getDefaultActivityMetricFlags(), groupField: 'Username', excludeConfigActivities: window.getDefaultActivityExcludeConfig()};\n            const d = JSON.parse(raw);\n            const metricFlags = Object.assign(window.getDefaultActivityMetricFlags(), d && d.metricFlags ? d.metricFlags : {});\n            return { colFlags: d && d.colFlags ? d.colFlags : {}, rowFlags: d && d.rowFlags ? d.rowFlags : {}, metricFlags, groupField: (d && d.groupField ? d.groupField : 'Username'), excludeConfigActivities: !!(d && d.excludeConfigActivities) };\n        } catch(e) { return {colFlags:{}, rowFlags:{}, metricFlags: window.getDefaultActivityMetricFlags(), groupField: 'Username', excludeConfigActivities: window.getDefaultActivityExcludeConfig()}; }\n    };\n    window.saveActivityReportPrefs = function() {\n        try {\n            localStorage.setItem(window.activityReportStorageKey(), JSON.stringify({\n                colFlags: (window.activityReportState && window.activityReportState.colFlags) || {},\n                rowFlags: (window.activityReportState && window.activityReportState.rowFlags) || {},\n                metricFlags: (window.activityReportState && window.activityReportState.metricFlags) || window.getDefaultActivityMetricFlags(),\n                groupField: (window.activityReportState && window.activityReportState.groupField) || 'Username',\n                excludeConfigActivities: !!(window.activityReportState && window.activityReportState.excludeConfigActivities)\n            }));\n        } catch(e) {}\n    };\n    window.activityReportState = { sortCol: 'DataOra', sortDir: 'desc', filters: {}, view: 'table', colFlags: {}, rowFlags: {}, metricFlags: window.getDefaultActivityMetricFlags(), groupField: 'Username', excludeConfigActivities: window.getDefaultActivityExcludeConfig() };\n    window.activityReportCols = ['Username','Descrizione','ID QRcode','Prezzo','Quantit\u00e0','Esito','Operatore','Dispositivo','Sito','Prodotto','Istruzioni','Durata','Data','Ora','Nota','Stato','Totale'];\n    window.activityReportGroupOptions = window.activityReportCols.slice();\n    window.getActivityAvailableGroupFields = function() {\n        const visibleCols = window.getActivityVisibleCols();\n        const allowed = window.activityReportGroupOptions.filter(k => visibleCols.includes(k));\n        return allowed.length ? allowed : ['Username'];\n    };\n    window.getActivitySelectedGroupField = function() {\n        const available = window.getActivityAvailableGroupFields();\n        const current = (window.activityReportState && window.activityReportState.groupField) || 'Username';\n        return available.includes(current) ? current : (available[0] || 'Prodotto');\n    };\n    window.setActivityGroupField = function(field) {\n        window.activityReportState.groupField = field;\n        window.saveActivityReportPrefs();\n        window.renderActivityReport();\n    };\n    window.getActivityAvailableMetrics = function() {\n        const visibleCols = window.getActivityVisibleCols();\n        const allowed = window.activityReportMetricOptions.filter(k => visibleCols.includes(k));\n        return allowed.length ? allowed : ['Totale'];\n    };\n    window.getActivitySelectedMetrics = function() {\n        const flags = (window.activityReportState && window.activityReportState.metricFlags) || window.getDefaultActivityMetricFlags();\n        const available = window.getActivityAvailableMetrics();\n        const selected = available.filter(k => !!flags[k]);\n        return selected.length ? selected : [available[0] || 'Totale'];\n    };\n    window.setActivityMetricFlag = function(metric, checked) {\n        if (!window.activityReportState.metricFlags) window.activityReportState.metricFlags = window.getDefaultActivityMetricFlags();\n        window.activityReportState.metricFlags[metric] = !!checked;\n        const available = window.getActivityAvailableMetrics();\n        const hasSelected = available.some(k => !!window.activityReportState.metricFlags[k]);\n        if (!hasSelected && available.length) window.activityReportState.metricFlags[available[0]] = true;\n        window.saveActivityReportPrefs();\n        window.renderActivityReport();\n    };\n    window.setActivityExcludeConfigActivities = function(checked) {\n        if (!window.activityReportState) window.activityReportState = {};\n        window.activityReportState.excludeConfigActivities = !!checked;\n        window.saveActivityReportPrefs();\n        window.renderActivityReport();\n    };\n    window.parseReportNum = function(v, defVal) {\n        if (v === null || v === undefined || v === '') return defVal || 0;\n        let s = String(v).trim();\n        if (!s) return defVal || 0;\n        s = s.replace(\/[^\\d,\\.\\-]\/g, '');\n        if (s.indexOf(',') !== -1 && s.indexOf('.') !== -1) s = s.replace(\/\\.\/g, '').replace(',', '.');\n        else if (s.indexOf(',') !== -1) s = s.replace(',', '.');\n        const n = parseFloat(s);\n        return isNaN(n) ? (defVal || 0) : n;\n    };\n    window.getActivityRowsNormalized = function() {\n        if(!window.currentDbData || !window.currentDbData.Tabelle) return [];\n        const raw = window.currentDbData.Tabelle['Attivita'] || window.currentDbData.Tabelle['Attivit\u00e0'] || [];\n        const rows = Array.isArray(raw) ? raw : Object.values(raw || {});\n        const normalized = rows.map((r, idx) => {\n            r = r || {};\n            const isConfigActivity = window.isConfigurationActivityLike ? window.isConfigurationActivityLike(r) : false;\n            const prezzo = window.parseReportNum(r.Prezzo || r.Totale || 0, 0);\n            const qta = window.parseReportNum(r['Quantit\u00e0'] || r.Quantita || 1, 1);\n            const data = r.Data || '';\n            const ora = r.Ora || '';\n            const descrizione = r['Descrizione attivit\u00e0'] || r['Descrizione attivita'] || r.Descrizione || r.Dettaglio || r.Note || r.Nota || '';\n            let dataora = 0;\n            const m = String(data).match(\/^(\\d{2})\\\/(\\d{2})\\\/(\\d{4})$\/);\n            const hm = String(ora).match(\/^(\\d{2}):(\\d{2})\/);\n            if (m) dataora = new Date(parseInt(m[3],10), parseInt(m[2],10)-1, parseInt(m[1],10), hm?parseInt(hm[1],10):0, hm?parseInt(hm[2],10):0, 0).getTime();\n            const rowKey = String(r.ID || '') + '|' + String(r.Username || r.Utente || r.user || '') + '|' + String(data) + '|' + String(ora) + '|' + String(descrizione || '') + '|' + idx;\n            return {\n                Username: r.Username || r.Utente || r.user || '',\n                Descrizione: descrizione,\n                Prezzo: prezzo,\n                Quantit\u00e0: qta,\n                Esito: r.Esito || '',\n                Operatore: r.Operatore || '',\n                Dispositivo: r.Dispositivo || '',\n                Sito: r.Sito || '',\n                Prodotto: r.Prodotto || '',\n                Istruzioni: r.Istruzioni || '',\n                Durata: r.Durata || '',\n                Data: data,\n                Ora: ora,\n                Nota: r.Nota || '',\n                Stato: r.Stato || '',\n                Totale: prezzo * qta,\n                'ID QRcode': r['ID QRcode'] || r['ID QR code'] || r.ID_QRcode || r.QRcode_ID || '',\n                __dataora: dataora,\n                __rowKey: rowKey,\n                __isConfigActivity: isConfigActivity\n            };\n        });\n        if (!(window.userCanSeeConfigurationActivities && window.userCanSeeConfigurationActivities())) {\n            return normalized.filter(function(r){ return !r.__isConfigActivity; });\n        }\n        return normalized;\n    };\n    window.findActivityRowByQrCode = function(qrCode) {\n        qrCode = String(qrCode || '').trim();\n        if (!qrCode || !window.currentDbData || !window.currentDbData.Tabelle) return null;\n        const tables = window.currentDbData.Tabelle;\n        const raw = tables['Attivita'] || tables['Attivit\u00e0'] || tables['attivita'] || tables['attivit\u00e0'] || [];\n        const rows = Array.isArray(raw) ? raw : Object.values(raw || {});\n        const norm = function(v){ return String(v == null ? '' : v).trim(); };\n        const keyMatch = function(k){ return \/^(id\\s*qr\\s*code|id\\s*qrcode|id_qrcode|qrcode\\s*id)$\/i.test(String(k || '').replace(\/[_-]+\/g,' ')); };\n        for (let i = 0; i < rows.length; i++) {\n            const r = rows[i] || {};\n            for (const k of Object.keys(r)) {\n                if (!keyMatch(k)) continue;\n                if (norm(r[k]) === qrCode) return {row:r, index:i, key:k};\n            }\n        }\n        return null;\n    };\n    window.showActivityFromQrIfRequested = function() {\n        const qrCode = String(window.tiForcedActivityQr || '').trim();\n        if (!qrCode || window.__tiActivityQrShown) return;\n        const show = function() {\n            const hit = window.findActivityRowByQrCode ? window.findActivityRowByQrCode(qrCode) : null;\n            if (!hit || !hit.row) return false;\n            window.__tiActivityQrShown = true;\n            const esc = window.tiCompanyHelpEscape || function(v){ return String(v == null ? '' : v).replace(\/[&<>\"']\/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c]; }); };\n            const r = hit.row;\n            const pick = function(names){ for (const n of names) { if (r[n] !== undefined && String(r[n]).trim() !== '') return String(r[n]); const found = Object.keys(r).find(k => String(k).toLowerCase() === String(n).toLowerCase()); if (found && String(r[found]).trim() !== '') return String(r[found]); } return ''; };\n            const lines = [];\n            lines.push('<b>Attivit\u00e0 richiamata da QRcode<\/b>');\n            lines.push('ID QRcode: ' + esc(qrCode));\n            const id = pick(['ID','Id','id']); if (id) lines.push('ID attivit\u00e0: ' + esc(id));\n            const data = pick(['Data','Data attivit\u00e0','Data attivita']); const ora = pick(['Ora','Ora attivit\u00e0','Ora attivita']); if (data || ora) lines.push('Data\/Ora: ' + esc((data + ' ' + ora).trim()));\n            const desc = pick(['Descrizione attivit\u00e0','Descrizione attivita','Descrizione','Attivita','Attivit\u00e0','Dettaglio','Note','Nota']); if (desc) lines.push('Descrizione: ' + esc(desc));\n            const esito = pick(['Esito','Stato']); if (esito) lines.push('Esito\/Stato: ' + esc(esito));\n            const user = pick(['Username','Utente','user']); if (user) lines.push('Utente: ' + esc(user));\n            const prod = pick(['Prodotto','Servizio']); if (prod) lines.push('Prodotto\/Servizio: ' + esc(prod));\n            if (typeof window.tiAlert === 'function') window.tiAlert(lines.join('<br>'));\n            else alert(lines.map(function(x){ return x.replace(\/<[^>]+>\/g,''); }).join('\\n'));\n            return true;\n        };\n        if (show()) return;\n        const dbSel = document.getElementById('ti-ditta');\n        const db = dbSel ? String(dbSel.value || '').trim() : (window.tiForced || '');\n        if (!db || db === 'NEW_DB' || !window.fetchJsonSafe) return;\n        const fd = new FormData(); fd.append('ti_action','ti_ai_get_db_data'); fd.append('db', db);\n        window.fetchJsonSafe(window.tiUrl || window.location.href, {method:'POST', body:fd, tiNoLongProcessSignal:true}).then(function(res){\n            if (res && res.success && res.data) { window.currentDbData = res.data; show(); }\n        }).catch(function(){});\n    };\n    window.getActivityIncludedRows = function() {\n        return window.getFilteredActivityRows(false);\n    };\n    window.getActivityVisibleCols = function() {\n        const colFlags = (window.activityReportState && window.activityReportState.colFlags) || {};\n        const visible = window.activityReportCols.filter(c => !colFlags[c]);\n        return visible.length ? visible : ['Username','Descrizione','Prezzo','Quantit\u00e0','Totale'];\n    };\n    window.getActivityReportTitle = function(rows) {\n        rows = rows || window.getActivityIncludedRows();\n        let minTs = null, maxTs = null;\n        rows.forEach(r => {\n            if (r && typeof r.__dataora === 'number' && r.__dataora > 0) {\n                if (minTs === null || r.__dataora < minTs) minTs = r.__dataora;\n                if (maxTs === null || r.__dataora > maxTs) maxTs = r.__dataora;\n            }\n        });\n        const ditta = (window.currentDittaName && String(window.currentDittaName).trim()) ? String(window.currentDittaName).trim() : 'Ditta';\n        const fmtDate = function(ts) {\n            if (!ts) return '';\n            const d = new Date(ts);\n            return String(d.getDate()).padStart(2,'0') + '\/' + String(d.getMonth()+1).padStart(2,'0') + '\/' + d.getFullYear();\n        };\n        const fromDate = fmtDate(minTs);\n        const toDate = fmtDate(maxTs);\n        return (fromDate && toDate) ? ('Report attivit\u00e0 \u2013 ' + ditta + ' periodo ' + fromDate + ' - ' + toDate) : ('Report attivit\u00e0 \u2013 ' + ditta);\n    };\n    window.getFilteredActivityRows = function(includeExcludedRows) {\n        let rows = window.getActivityRowsNormalized();\n        const state = window.activityReportState || {filters:{}, rowFlags:{}};\n        const rowFlags = state.rowFlags || {};\n        if (state.excludeConfigActivities && window.userCanSeeConfigurationActivities && window.userCanSeeConfigurationActivities()) {\n            rows = rows.filter(function(r){ return !r.__isConfigActivity; });\n        }\n        rows = rows.filter(r => Object.entries(state.filters || {}).every(([k,v]) => {\n            if (!v) return true;\n            return String(r[k] ?? '').toLowerCase().includes(String(v).toLowerCase());\n        }));\n        if (!includeExcludedRows) rows = rows.filter(r => !rowFlags[r.__rowKey]);\n        const dir = state.sortDir === 'asc' ? 1 : -1;\n        const col = state.sortCol || 'DataOra';\n        rows.sort((a,b) => {\n            const av = col === 'DataOra' ? a.__dataora : a[col];\n            const bv = col === 'DataOra' ? b.__dataora : b[col];\n            if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * dir;\n            return String(av ?? '').localeCompare(String(bv ?? ''), 'it', {numeric:true, sensitivity:'base'}) * dir;\n        });\n        return rows;\n    };\n    window.setActivityReportFilter = function(col, val, inputEl) {\n        window.activityReportState.filters[col] = val;\n        window.activityReportState.filterFocus = {\n            col,\n            start: inputEl && typeof inputEl.selectionStart === 'number' ? inputEl.selectionStart : null,\n            end: inputEl && typeof inputEl.selectionEnd === 'number' ? inputEl.selectionEnd : null\n        };\n        window.renderActivityReport();\n    };\n    window.sortActivityReport = function(col) {\n        if (window.activityReportState.sortCol === col) window.activityReportState.sortDir = window.activityReportState.sortDir === 'asc' ? 'desc' : 'asc';\n        else { window.activityReportState.sortCol = col; window.activityReportState.sortDir = 'asc'; }\n        window.renderActivityReport();\n    };\n    window.setActivityReportView = function(view) { window.activityReportState.view = view; window.renderActivityReport(); };\n    window.toggleActivityReportColumn = function(col, ev) {\n        if (ev) { ev.preventDefault(); ev.stopPropagation(); }\n        window.activityReportState.colFlags[col] = !window.activityReportState.colFlags[col];\n        window.saveActivityReportPrefs();\n        window.renderActivityReport();\n    };\n    window.toggleActivityReportRow = function(rowKey, ev) {\n        if (ev) { ev.preventDefault(); ev.stopPropagation(); }\n        window.activityReportState.rowFlags[rowKey] = !window.activityReportState.rowFlags[rowKey];\n        window.saveActivityReportPrefs();\n        window.renderActivityReport();\n    };\n    window.exportActivityReportCsv = function() {\n        const rows = window.getFilteredActivityRows(false);\n        const cols = window.getActivityVisibleCols();\n        const csv = cols.join(';') + '\\n' + rows.map(r => cols.map(c => '\"' + String((c === 'Prezzo' || c === 'Quantit\u00e0' || c === 'Totale') ? window.fmtNum(r[c] || 0) : (r[c] ?? '')).replace(\/\"\/g,'\"\"') + '\"').join(';')).join('\\n');\n        const blob = new Blob([csv], {type:'text\/csv;charset=utf-8;'});\n        const a = document.createElement('a');\n        a.href = URL.createObjectURL(blob);\n        a.download = 'report-attivita.csv';\n        a.click();\n        setTimeout(() => URL.revokeObjectURL(a.href), 500);\n    };\n    window.printActivityReport = function() {\n        const view = (window.activityReportState && window.activityReportState.view) || 'table';\n        const w = window.open('', '_blank'); if(!w) return;\n        let bodyHtml = '';\n        if (view === 'table') {\n            const rows = window.getFilteredActivityRows(false);\n            const cols = window.getActivityVisibleCols();\n            const totals = {\n                Prezzo: rows.reduce((s,r)=>s+(r.Prezzo||0),0),\n                Quantit\u00e0: rows.reduce((s,r)=>s+(r.Quantit\u00e0||0),0),\n                Totale: rows.reduce((s,r)=>s+(r.Totale||0),0)\n            };\n            bodyHtml += '<h2>' + window.escapeHtml(window.getActivityReportTitle(rows)) + '<\/h2>';\n            bodyHtml += '<table><thead><tr>' + cols.map(c => '<th>' + window.escapeHtml(c) + '<\/th>').join('') + '<\/tr><\/thead><tbody>';\n            rows.forEach(r => {\n                bodyHtml += '<tr>' + cols.map(c => {\n                    const val = (c === 'Prezzo' || c === 'Quantit\u00e0' || c === 'Totale') ? window.fmtNum(r[c] || 0) : (r[c] || '');\n                    return '<td>' + window.escapeHtml(String(val)) + '<\/td>';\n                }).join('') + '<\/tr>';\n            });\n            bodyHtml += '<\/tbody><tfoot><tr>' + cols.map(c => {\n                if (c === 'Descrizione') return '<td>Righe: ' + rows.length + '<\/td>';\n                if (c === 'Prezzo') return '<td>' + window.fmtNum(totals.Prezzo) + '<\/td>';\n                if (c === 'Quantit\u00e0') return '<td>' + window.fmtNum(totals.Quantit\u00e0) + '<\/td>';\n                if (c === 'Totale') return '<td>' + window.fmtNum(totals.Totale) + '<\/td>';\n                return '<td><\/td>';\n            }).join('') + '<\/tr><\/tfoot><\/table>';\n        } else {\n            const rows = window.getFilteredActivityRows(false);\n            const canvasId = view === 'pie' ? 'ti-report-pie' : 'ti-report-trend';\n            const canvas = gE(canvasId);\n            const dataUrl = canvas ? canvas.toDataURL('image\/png') : '';\n            const title = window.getActivityReportTitle(rows);\n            bodyHtml += '<h2>' + window.escapeHtml(title) + '<\/h2>';\n            bodyHtml += dataUrl ? ('<div style=\"text-align:center;\"><img decoding=\"async\" src=\"' + dataUrl + '\" style=\"max-width:100%;height:auto;\" \/><\/div>') : '<p>Grafico non disponibile.<\/p>';\n        }\n        const printDocTitle = window.escapeHtml(window.getActivityReportTitle(window.getFilteredActivityRows(false)));\n        w.document.write('<html><head><title>' + printDocTitle + '<\/title><style>@page{size:A4 landscape;margin:12mm;} body{font-family:Arial,sans-serif;background:#fff;color:#000;padding:0;margin:0} h2{margin:0 0 12px 0;} table{border-collapse:collapse;width:100%;table-layout:auto} th,td{border:1px solid #bbb;padding:6px;font-size:11px;text-align:left;vertical-align:top;word-break:break-word} th{background:#eee} tfoot td{font-weight:bold;background:#f3f4f6} img{display:block;margin:0 auto;}<\/style><\/head><body>' + bodyHtml + '<\/body><\/html>');\n        w.document.close(); w.focus(); setTimeout(function(){ w.print(); }, 250);\n    };\n    window.getActivityMetricLabel = function(metrics) {\n        const arr = Array.isArray(metrics) && metrics.length ? metrics : ['Totale'];\n        if (arr.length === 1) {\n            if (arr[0] === 'Prezzo') return 'Prezzo';\n            if (arr[0] === 'Quantit\u00e0') return 'Quantit\u00e0';\n            if (arr[0] === 'Totale') return 'Totale';\n            return String(arr[0]);\n        }\n        return 'Somma valori (' + arr.join(', ') + ')';\n    };\n\n    window.drawPie3D = function(canvasId, rows) {\n        const cv = gE(canvasId); if(!cv) return; const ctx = cv.getContext('2d');\n        const selectedMetrics = window.getActivitySelectedMetrics();\n        const groupField = window.getActivitySelectedGroupField();\n        const metrics = (Array.isArray(selectedMetrics) && selectedMetrics.length) ? selectedMetrics : ['Totale'];\n        const cols = metrics.length > 1 ? 2 : 1;\n        const rowsCount = Math.ceil(metrics.length \/ cols);\n        const blockW = 400;\n        const blockH = 280;\n        cv.width = Math.max(860, cols * blockW + 40);\n        cv.height = Math.max(340, rowsCount * blockH + 40);\n        ctx.clearRect(0,0,cv.width,cv.height);\n\n        const colors=['#38bdf8','#22c55e','#f59e0b','#ef4444','#a855f7','#14b8a6','#f97316','#06b6d4','#84cc16','#fb7185','#f97316','#60a5fa'];\n\n        const buildEntriesForMetric = function(metric) {\n            const groups = {};\n            rows.forEach(r => {\n                const label = String(r[groupField] || 'N\/D').trim() || 'N\/D';\n                const value = window.parseReportNum(r[metric], 0) || 0;\n                groups[label] = (groups[label] || 0) + value;\n            });\n            return Object.entries(groups).filter(e => e[1] > 0).sort((a,b) => b[1]-a[1]).slice(0, 10);\n        };\n\n        let drewAtLeastOne = false;\n\n        metrics.forEach((metric, metricIndex) => {\n            const entries = buildEntriesForMetric(metric);\n            const col = metricIndex % cols;\n            const row = Math.floor(metricIndex \/ cols);\n            const originX = 20 + col * blockW;\n            const originY = 20 + row * blockH;\n            const cx = originX + 120;\n            const cy = originY + 120;\n            const rad = 72;\n            const depth = 14;\n\n            ctx.fillStyle = '#fff';\n            ctx.font = 'bold 13px Arial';\n            ctx.fillText(window.getActivityMetricLabel([metric]), originX + 8, originY + 18);\n\n            if (!entries.length) {\n                ctx.fillStyle = '#cbd5e1';\n                ctx.font = '12px Arial';\n                ctx.fillText('Nessun dato per ' + window.getActivityMetricLabel([metric]), originX + 8, originY + 42);\n                return;\n            }\n\n            drewAtLeastOne = true;\n            const total = entries.reduce((a,b)=>a+b[1],0);\n            let start = -Math.PI\/2;\n\n            entries.forEach(([label,val],i) => {\n                const ang = total > 0 ? (val\/total)*Math.PI*2 : 0;\n                ctx.fillStyle='rgba(0,0,0,0.35)';\n                ctx.beginPath(); ctx.moveTo(cx, cy+depth); ctx.arc(cx, cy+depth, rad, start, start+ang); ctx.closePath(); ctx.fill();\n                ctx.fillStyle=colors[i%colors.length];\n                ctx.beginPath(); ctx.moveTo(cx, cy); ctx.arc(cx, cy, rad, start, start+ang); ctx.closePath(); ctx.fill();\n\n                const mid = start + ang\/2;\n                const labelX = cx + Math.cos(mid) * (rad + 18);\n                const labelY = cy + Math.sin(mid) * (rad + 18);\n                const textX = labelX + (Math.cos(mid) >= 0 ? 8 : -150);\n\n                ctx.fillStyle='#fff';\n                ctx.font='12px Arial';\n                ctx.fillText(label, textX, labelY - 10);\n                ctx.fillStyle='#cbd5e1';\n                ctx.font='11px Arial';\n                ctx.fillText(window.getActivityMetricLabel([metric]) + ': ' + window.fmtNum(val), textX, labelY + 4);\n                start += ang;\n            });\n\n            ctx.fillStyle = '#94a3b8';\n            ctx.font = '11px Arial';\n            ctx.fillText('Raggruppa per: ' + groupField, originX + 8, originY + blockH - 12);\n        });\n\n        if (!drewAtLeastOne) {\n            ctx.fillStyle='#fff'; ctx.font='14px Arial'; ctx.fillText('Nessun dato',20,20);\n        }\n    };\n    window.drawTrend3D = function(canvasId, rows) {\n        const cv = gE(canvasId); if(!cv) return; const ctx = cv.getContext('2d');\n        const selectedMetrics = window.getActivitySelectedMetrics();\n        const groupField = window.getActivitySelectedGroupField();\n        const metrics = (Array.isArray(selectedMetrics) && selectedMetrics.length) ? selectedMetrics : ['Totale'];\n        const cols = metrics.length > 1 ? 2 : 1;\n        const rowsCount = Math.ceil(metrics.length \/ cols);\n        const blockW = 460;\n        const blockH = 320;\n        cv.width = Math.max(980, cols * blockW + 40);\n        cv.height = Math.max(380, rowsCount * blockH + 40);\n        ctx.clearRect(0,0,cv.width,cv.height);\n\n        const colors=['#38bdf8','#22c55e','#f59e0b','#ef4444','#a855f7','#14b8a6'];\n\n        const buildMetricTimeline = function(metric) {\n            const timeline = {};\n            const groupTotals = {};\n            rows.forEach(r => {\n                const timeKey = (r.Data || '') + ' ' + (r.Ora || '00:00');\n                const grp = String(r[groupField] || 'N\/D').trim() || 'N\/D';\n                const value = window.parseReportNum(r[metric], 0) || 0;\n                if (!timeline[timeKey]) timeline[timeKey] = {};\n                timeline[timeKey][grp] = (timeline[timeKey][grp] || 0) + value;\n                groupTotals[grp] = (groupTotals[grp] || 0) + value;\n            });\n            const entries = Object.entries(timeline).sort((a,b)=>a[0].localeCompare(b[0])).slice(-12);\n            const groups = Object.entries(groupTotals).sort((a,b)=>b[1]-a[1]).slice(0,4).map(e => e[0]);\n            return { entries, groups };\n        };\n\n        let drewAtLeastOne = false;\n\n        metrics.forEach((metric, metricIndex) => {\n            const data = buildMetricTimeline(metric);\n            const entries = data.entries;\n            const groups = data.groups;\n            const col = metricIndex % cols;\n            const row = Math.floor(metricIndex \/ cols);\n            const originX = 20 + col * blockW;\n            const originY = 20 + row * blockH;\n\n            ctx.fillStyle = '#fff';\n            ctx.font = 'bold 13px Arial';\n            ctx.fillText(window.getActivityMetricLabel([metric]), originX + 8, originY + 18);\n\n            if (!entries.length || !groups.length) {\n                ctx.fillStyle='#cbd5e1';\n                ctx.font='12px Arial';\n                ctx.fillText('Nessun dato per ' + window.getActivityMetricLabel([metric]), originX + 8, originY + 42);\n                return;\n            }\n\n            drewAtLeastOne = true;\n            const max = Math.max(1, ...entries.flatMap(e => groups.map(g => e[1][g] || 0)));\n            const baseY = originY + 240;\n            const left = originX + 52;\n            const step = 32;\n            const barW = Math.max(8, Math.floor(34 \/ Math.max(1, groups.length)));\n\n            ctx.strokeStyle='#94a3b8';\n            ctx.beginPath();\n            ctx.moveTo(left, originY + 32);\n            ctx.lineTo(left, baseY);\n            ctx.lineTo(left + step * (entries.length), baseY);\n            ctx.stroke();\n\n            entries.forEach(([label, metricsAtTime], i)=>{\n                const groupX = left + 10 + i * step;\n                groups.forEach((grp, gi) => {\n                    const val = metricsAtTime[grp] || 0;\n                    const h = (val\/max)*160;\n                    const x = groupX + gi*(barW+3);\n                    ctx.fillStyle='rgba(0,0,0,0.25)';\n                    ctx.fillRect(x+4, baseY-h+4, barW, h);\n                    ctx.fillStyle=colors[gi%colors.length];\n                    ctx.fillRect(x, baseY-h, barW, h);\n                    if (h > 0) {\n                        ctx.fillStyle='#fff';\n                        ctx.font='10px Arial';\n                        ctx.fillText(window.fmtNum(val), x-2, baseY-h-6);\n                    }\n                });\n                ctx.save();\n                ctx.fillStyle='#fff';\n                ctx.font='10px Arial';\n                ctx.translate(groupX, baseY+14);\n                ctx.rotate(-0.6);\n                ctx.fillText(label.slice(0,16),0,0);\n                ctx.restore();\n            });\n\n            groups.forEach((grp, gi) => {\n                const lx = originX + 8 + gi * 104;\n                const ly = originY + 34;\n                ctx.fillStyle = colors[gi%colors.length];\n                ctx.fillRect(lx, ly, 12, 12);\n                ctx.fillStyle = '#fff';\n                ctx.font = '11px Arial';\n                ctx.fillText(grp, lx + 18, ly + 10);\n            });\n\n            ctx.fillStyle = '#cbd5e1';\n            ctx.font = '11px Arial';\n            ctx.fillText('Raggruppa per: ' + groupField, originX + 8, originY + blockH - 12);\n        });\n\n        if (!drewAtLeastOne) {\n            ctx.fillStyle='#fff';\n            ctx.font='14px Arial';\n            ctx.fillText('Nessun dato',20,20);\n        }\n    };\n    window.renderActivityReport = function() {\n        const box = gE('ti-report-cnt'); if(!box) return;\n        const allRows = window.getFilteredActivityRows(true);\n        const rowFlags = (window.activityReportState && window.activityReportState.rowFlags) || {};\n        const includedRows = allRows.filter(r => !rowFlags[r.__rowKey]);\n        const titleEl = gE('ti-report-title');\n        if (titleEl) titleEl.innerText = '\ud83d\udcca ' + window.getActivityReportTitle(includedRows);\n        const allCols = window.activityReportCols;\n        const visibleCols = window.getActivityVisibleCols();\n        const hiddenCols = allCols.filter(c => !visibleCols.includes(c));\n        const view = window.activityReportState.view || 'table';\n        const availableMetrics = window.getActivityAvailableMetrics();\n        const selectedMetrics = window.getActivitySelectedMetrics();\n        const totals = {\n            Prezzo: includedRows.reduce((s,r)=>s+(r.Prezzo||0),0),\n            Quantit\u00e0: includedRows.reduce((s,r)=>s+(r.Quantit\u00e0||0),0),\n            Totale: includedRows.reduce((s,r)=>s+(r.Totale||0),0)\n        };\n        let html = '<div class=\"ti-report-toolbar\">'\n            + '<button type=\"button\" class=\"ti-btn\" onclick=\"window.setActivityReportView(\\'table\\')\">Elenco dati<\/button>'\n            + '<button type=\"button\" class=\"ti-btn\" onclick=\"window.setActivityReportView(\\'pie\\')\">Torte<\/button>'\n            + '<button type=\"button\" class=\"ti-btn\" onclick=\"window.setActivityReportView(\\'trend\\')\">Andamento<\/button>'\n            + '<button type=\"button\" class=\"ti-btn\" onclick=\"window.exportActivityReportCsv()\">Export CSV<\/button>'\n            + '<button type=\"button\" class=\"ti-btn\" onclick=\"window.printActivityReport()\">Stampa<\/button>'\n            + ((window.userCanSeeConfigurationActivities && window.userCanSeeConfigurationActivities())\n                ? '<label class=\"ti-report-config-filter\" title=\"Esclude dal report le righe attivit\u00e0 create dalla configurazione\"><input type=\"checkbox\" ' + ((window.activityReportState && window.activityReportState.excludeConfigActivities) ? 'checked' : '') + ' onchange=\"window.setActivityExcludeConfigActivities(this.checked)\"><span>Escludi configurazione<\/span><\/label>'\n                : '<span class=\"ti-report-config-filter\" title=\"Le attivit\u00e0 di configurazione sono visibili solo ad amministratori e configuratori\">Configurazione nascosta<\/span>')\n            + '<\/div>';\n        if (view !== 'table') {\n            const groupFields = window.getActivityAvailableGroupFields();\n            const selectedGroupField = window.getActivitySelectedGroupField();\n            html += '<div class=\"ti-report-hidden-cols\" style=\"display:flex;gap:10px;align-items:center;flex-wrap:wrap;\">';\n            if (view === 'pie' || view === 'trend') {\n                html += '<label class=\"ti-report-col-chip\" style=\"display:inline-flex;align-items:center;gap:6px;\">'\n                    + '<span>Raggruppa per<\/span>'\n                    + '<select class=\"ti-in\" style=\"width:auto;min-width:160px;padding:6px 8px;\" onchange=\"window.setActivityGroupField(this.value)\">'\n                    + groupFields.map(function(field){ return '<option value=\"' + window.escapeHtml(field) + '\" ' + (field === selectedGroupField ? 'selected' : '') + '>' + window.escapeHtml(field) + '<\/option>'; }).join('')\n                    + '<\/select>'\n                    + '<\/label>';\n            }\n            availableMetrics.forEach(function(metric){\n                const checked = selectedMetrics.includes(metric);\n                const metricJs = JSON.stringify(String(metric));\n                html += '<label class=\"ti-report-col-chip\" style=\"display:inline-flex;align-items:center;gap:6px;\">'\n                    + '<input type=\"checkbox\" ' + (checked ? 'checked' : '') + ' onchange=\"window.setActivityMetricFlag(' + metricJs.replace(\/\"\/g, '&quot;') + ', this.checked)\">'\n                    + '<span>' + window.escapeHtml(metric) + '<\/span>'\n                    + '<\/label>';\n            });\n            html += '<\/div>';\n        }\n        if (hiddenCols.length) {\n            html += '<div class=\"ti-report-hidden-cols\">' + hiddenCols.map(c => `<span class=\"ti-report-col-chip\"><button type=\"button\" class=\"ti-report-flag-btn is-off\" onclick='window.toggleActivityReportColumn(${JSON.stringify(c)}, event)'>X<\/button>${window.escapeHtml(c)}<\/span>`).join('') + '<\/div>';\n        }\n        if (view === 'table') {\n            html += '<div class=\"ti-report-table-wrap\"><table class=\"ti-report-table\"><thead><tr>';\n            html += '<th class=\"ti-report-row-flag-cell head\">X<\/th>';\n            visibleCols.forEach(c => {\n                const sortKey = (c === 'Data' || c === 'Ora') ? 'DataOra' : c;\n                html += `<th onclick='window.sortActivityReport(${JSON.stringify(sortKey)})'><button type=\"button\" class=\"ti-report-flag-btn\" onclick='window.toggleActivityReportColumn(${JSON.stringify(c)}, event)'>X<\/button>${window.escapeHtml(c)}<br><input class=\"ti-report-filter\" data-col=\"${window.escapeHtml(c)}\" value=\"${window.escapeHtml(window.activityReportState.filters[c] || '')}\" onclick=\"event.stopPropagation()\" oninput='window.setActivityReportFilter(${JSON.stringify(c)}, this.value, this)'><\/th>`;\n            });\n            html += '<\/tr><\/thead><tbody>';\n            allRows.forEach(r => {\n                const rowExcluded = !!rowFlags[r.__rowKey];\n                html += '<tr class=\"' + (rowExcluded ? 'ti-report-row-excluded' : '') + '\">';\n                html += `<td class=\"ti-report-row-flag-cell\"><button type=\"button\" class=\"ti-report-flag-btn ${rowExcluded ? 'is-off' : ''}\" onclick='window.toggleActivityReportRow(${JSON.stringify(r.__rowKey)}, event)'>X<\/button><\/td>`;\n                visibleCols.forEach(c => {\n                    const val = (c === 'Prezzo' || c === 'Quantit\u00e0' || c === 'Totale') ? window.fmtNum(r[c] || 0) : (r[c] || '');\n                    html += '<td class=\"' + ((c === 'Prezzo' || c === 'Quantit\u00e0' || c === 'Totale') ? 'ti-report-num' : '') + '\">' + window.escapeHtml(String(val)) + '<\/td>';\n                });\n                html += '<\/tr>';\n            });\n            html += '<\/tbody><tfoot><tr><td class=\"ti-report-row-flag-cell\">Incl.<\/td>';\n            visibleCols.forEach(c => {\n                if (c === 'Descrizione') html += '<td>Righe: ' + includedRows.length + '<\/td>';\n                else if (c === 'Prezzo') html += '<td class=\"ti-report-num\">' + window.fmtNum(totals.Prezzo) + '<\/td>';\n                else if (c === 'Quantit\u00e0') html += '<td class=\"ti-report-num\">' + window.fmtNum(totals.Quantit\u00e0) + '<\/td>';\n                else if (c === 'Totale') html += '<td class=\"ti-report-num\">' + window.fmtNum(totals.Totale) + '<\/td>';\n                else html += '<td><\/td>';\n            });\n            html += '<\/tr><\/tfoot><\/table><\/div>';\n        } else if (view === 'pie') {\n            html += '<div class=\"ti-chart-wrap\"><canvas id=\"ti-report-pie\" width=\"860\" height=\"340\"><\/canvas><\/div>';\n        } else {\n            html += '<div class=\"ti-chart-wrap\"><canvas id=\"ti-report-trend\" width=\"980\" height=\"380\"><\/canvas><\/div>';\n        }\n        box.innerHTML = html;\n        const focus = window.activityReportState.filterFocus;\n        if (focus && focus.col && view === 'table') {\n            setTimeout(() => {\n                const inp = box.querySelector('.ti-report-filter[data-col=\"' + CSS.escape(focus.col) + '\"]');\n                if (inp) {\n                    inp.focus();\n                    if (typeof focus.start === 'number' && typeof focus.end === 'number' && inp.setSelectionRange) {\n                        try { inp.setSelectionRange(focus.start, focus.end); } catch(e) {}\n                    }\n                }\n            }, 0);\n        }\n        if(view === 'pie') setTimeout(() => window.drawPie3D('ti-report-pie', includedRows), 30);\n        if(view === 'trend') setTimeout(() => window.drawTrend3D('ti-report-trend', includedRows), 30);\n    };\n    window.tiReportPick = function(row, keys, defVal) {\n        row = row || {}; keys = Array.isArray(keys) ? keys : [keys];\n        for (const k of keys) {\n            if (row[k] !== undefined && row[k] !== null && String(row[k]).trim() !== '') return String(row[k]).trim();\n            const found = Object.keys(row).find(x => String(x).toLowerCase() === String(k).toLowerCase());\n            if (found && row[found] !== undefined && row[found] !== null && String(row[found]).trim() !== '') return String(row[found]).trim();\n        }\n        return defVal || '';\n    };\n    window.tiReportBelongsToCurrentUser = function(row) {\n        if (window.tiIsPrivilegedCurrentUser && window.tiIsPrivilegedCurrentUser()) return true;\n        const user = String(window.tiUser || '').trim().toLowerCase();\n        const display = String(window.tiUserDisplay || '').trim().toLowerCase();\n        if (!user && !display) return false;\n        const names = [\n            window.tiReportPick(row, ['Username','Utente','user']),\n            window.tiReportPick(row, ['Nome','Cliente','Display'])\n        ].map(v => String(v || '').trim().toLowerCase()).filter(Boolean);\n        return names.some(v => v === user || v === display || (!!user && v.indexOf(user) !== -1) || (!!display && v.indexOf(display) !== -1));\n    };\n    window.tiReportTableRows = function(tNames) {\n        if (!window.currentDbData || !window.currentDbData.Tabelle) return [];\n        for (const t of tNames) {\n            const raw = window.currentDbData.Tabelle[t];\n            if (!raw) continue;\n            return Array.isArray(raw) ? raw : Object.values(raw || {});\n        }\n        return [];\n    };\n    window.tiBuildPublicActivityReportRows = function() {\n        const rows = [];\n        const escNum = function(v, defVal) { const n = window.parseReportNum ? window.parseReportNum(v, defVal || 0) : parseFloat(String(v || '0').replace(',', '.')); return isNaN(n) ? (defVal || 0) : n; };\n        const canFull = window.tiIsPrivilegedCurrentUser && window.tiIsPrivilegedCurrentUser();\n        const appRows = window.tiReportTableRows(['Appuntamenti','Prenotazioni']);\n        appRows.forEach(function(r){\n            r = r || {};\n            if (!canFull && !window.tiReportBelongsToCurrentUser(r)) return;\n            const st = window.tiReportPick(r, ['Stato','Status']);\n            const lowerSt = String(st || '').toLowerCase();\n            if (lowerSt.indexOf('cancell') !== -1 || lowerSt.indexOf('annull') !== -1) return;\n            rows.push({kind:'appointment', title:'Dettaglio appuntamento', fields:{\n                'Username': window.tiReportPick(r, ['Username','Utente','user']),\n                'Servizio': window.tiReportPick(r, ['Servizio','Prestazione','Trattamento','Descrizione']),\n                'Data appuntamento': window.tiReportPick(r, ['Data_appuntamento','Data appuntamento','Data prenotazione','Giorno']),\n                'Ora appuntamento': window.tiReportPick(r, ['Ora_appuntamento','Ora appuntamento','Ora prenotazione','Orario']),\n                'Quantit\u00e0': window.tiReportPick(r, ['Quantit\u00e0','Quantita','Qta','Q.t\u00e0']),\n                'Email': window.tiReportPick(r, ['Email_utente','Email utente','Email','E-mail']),\n                'Tel': window.tiReportPick(r, ['Tel_Utente','Tel Utente','Tel','Telefono','Cellulare']),\n                'Data': window.tiReportPick(r, ['Data']),\n                'Ora': window.tiReportPick(r, ['Ora']),\n                'Nota': window.tiReportPick(r, ['Nota','Note']),\n                'Stato': st\n            }});\n        });\n        const actRows = window.tiReportTableRows(['Attivita','Attivit\u00e0']);\n        actRows.forEach(function(r){\n            r = r || {};\n            if (!canFull && !window.tiReportBelongsToCurrentUser(r)) return;\n            const st = window.tiReportPick(r, ['Stato','Status']);\n            const lowerSt = String(st || '').toLowerCase();\n            if (lowerSt.indexOf('cancell') !== -1 || lowerSt.indexOf('annull') !== -1) return;\n            const prezzo = window.tiReportPick(r, ['Prezzo','Importo','Costo'], '0');\n            const qta = window.tiReportPick(r, ['Quantit\u00e0','Quantita','Qta','Q.t\u00e0'], '1');\n            let totale = window.tiReportPick(r, ['Totale','Importo totale','Totale riga'], '');\n            if (!totale) totale = window.fmtNum ? window.fmtNum(escNum(prezzo, 0) * (escNum(qta, 1) || 1)) : String((escNum(prezzo, 0) * (escNum(qta, 1) || 1)).toFixed(2));\n            rows.push({kind:'activity', title:'Dettaglio attivit\u00e0', fields:{\n                'Username': window.tiReportPick(r, ['Username','Utente','user']),\n                'Descrizione': window.tiReportPick(r, ['Descrizione attivit\u00e0','Descrizione attivita','Descrizione','Dettaglio','Note','Nota','Prodotto','Servizio']),\n                'Prezzo': prezzo,\n                'Quantit\u00e0': qta,\n                'Esito': window.tiReportPick(r, ['Esito']),\n                'Data': window.tiReportPick(r, ['Data','Data attivit\u00e0','Data attivita','Data creazione']),\n                'Ora': window.tiReportPick(r, ['Ora','Ora attivit\u00e0','Ora attivita','Ora creazione']),\n                'Totale': totale\n            }});\n        });\n        return rows;\n    };\n    window.buildActivityReportChatHtml = function() {\n        const esc = window.escapeHtml || function(v){ return String(v == null ? '' : v).replace(\/[&<>\"']\/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c]; }); };\n        const rows = window.tiBuildPublicActivityReportRows ? window.tiBuildPublicActivityReportRows() : [];\n        if (!rows.length) return '<b>Report attivit\u00e0<\/b><br>Non trovo attivit\u00e0 o appuntamenti da mostrare per l utente corrente nel database selezionato.';\n        let html = '<b>Report attivit\u00e0<\/b><br>Visualizzazione in chatbot.<br><ol style=\"margin:8px 0 0 20px;padding:0;\">';\n        rows.forEach(function(item){\n            html += '<li style=\"margin:8px 0;\"><b>' + esc(item.title) + '<\/b><br><ol style=\"margin:4px 0 0 18px;padding:0;\">';\n            Object.keys(item.fields || {}).forEach(function(k){\n                html += '<li><b>' + esc(k) + '<\/b>: ' + esc(item.fields[k] || '') + '<\/li>';\n            });\n            html += '<\/ol><\/li>';\n        });\n        html += '<\/ol>';\n        return html;\n    };\n    window.openActivityReport = function() {\n        const prefs = window.loadActivityReportPrefs ? window.loadActivityReportPrefs() : {};\n        window.activityReportState = {\n            sortCol: 'DataOra',\n            sortDir: 'desc',\n            filters: {},\n            view: 'table',\n            colFlags: prefs.colFlags || {},\n            rowFlags: prefs.rowFlags || {},\n            metricFlags: prefs.metricFlags || (window.getDefaultActivityMetricFlags ? window.getDefaultActivityMetricFlags() : {}),\n            groupField: prefs.groupField || 'Username',\n            excludeConfigActivities: !!prefs.excludeConfigActivities\n        };\n        if (window.renderActivityReport) window.renderActivityReport();\n        if (window.openModal) window.openModal('ti-report-ov');\n    };\n\n    window.hasInstructionText = function(row) {\n        if (!row || typeof row !== 'object') return false;\n        return Object.keys(row).some((k) => {\n            const key = String(k).toLowerCase().trim();\n            const isInstr = (key === 'istruzioni' || key === 'istruzione' || key.indexOf('istruzioni') !== -1 || key.indexOf('istruzione') !== -1);\n            if (!isInstr) return false;\n            const val = row[k];\n            return val !== undefined && val !== null && String(val).trim() !== '';\n        });\n    };\n\n    window.buildInstructionsViewDb = function(fullDb) {\n        if (!fullDb || !fullDb.Tabelle) return null;\n        const view = Object.assign({}, fullDb, { Tabelle: {} });\n        let tableCount = 0;\n        let rowCount = 0;\n        Object.keys(fullDb.Tabelle).forEach((tName) => {\n            const raw = fullDb.Tabelle[tName];\n            const rows = Array.isArray(raw) ? raw : Object.values(raw || {});\n            const matched = rows.filter(window.hasInstructionText);\n            if (matched.length) {\n                view.Tabelle[tName] = matched;\n                tableCount += 1;\n                rowCount += matched.length;\n            }\n        });\n        view.__instructionStats = { tables: tableCount, rows: rowCount };\n        return view;\n    };\n\n\n    window.isAdminUser = function() {\n        return \/amministratore|administrator\/i.test(String(window.tiRole || '')) || String(window.tiUser || '') === 'SSGlobalAdmin';\n    };\n\n    window.canRunLocalOrphanCleanup = function() {\n        const roleNow = String(window.tiRole || '').toLowerCase();\n        const userNow = String(window.tiUser || '').toLowerCase();\n        return userNow === 'ssglobaladmin' || \/amministratore|configuratore|responsabile|globale\/.test(roleNow);\n    };\n    window.canRunGlobalOrphanCleanup = function() {\n        return String(window.tiUser || '').toLowerCase() === 'ssglobaladmin';\n    };\n    window.refreshOrphanCleanupButton = function() {\n        const localBtn = gE('ti-clean-orphans-btn');\n        const globalBtn = gE('ti-clean-orphans-global-btn');\n        if (localBtn) localBtn.style.display = (window.canRunLocalOrphanCleanup && window.canRunLocalOrphanCleanup()) ? 'inline-block' : 'none';\n        if (globalBtn) globalBtn.style.display = (window.canRunGlobalOrphanCleanup && window.canRunGlobalOrphanCleanup()) ? 'block' : 'none';\n        if (window.refreshPrivilegedActionsRow) window.refreshPrivilegedActionsRow();\n    };\n\n    window.orphanCleanupState = window.orphanCleanupState || { active:false, stopRequested:false, total:0, processed:0, deleted:0, currentPath:'' };\n\n    window.restoreDefaultLoaderActions = function() {\n        const actions = gE('ti-loader-actions');\n        if (!actions) return;\n        actions.innerHTML = '<button type=\"button\" id=\"ti-loader-close\" class=\"ti-btn\" style=\"width:100%; background:#b91c1c; color:#fff;\" onclick=\"window.cancelLongAIProcess()\">Esci<\/button>';\n    };\n\n    window.showOrphanCleanupEmergencyButton = function() {\n        const actions = gE('ti-loader-actions');\n        if (!actions) return;\n        actions.style.display = 'block';\n        actions.innerHTML = '<button type=\"button\" id=\"ti-orphan-emergency-stop\" class=\"ti-btn\" style=\"width:100%; background:#dc2626; color:#fff; font-weight:800;\" onclick=\"window.requestStopOrphanFileCleanup()\">\ud83d\uded1 Esci d\\'emergenza - interrompi cancellazione<\/button>'\n            + '<div style=\"margin-top:6px; color:#fecaca; font-size:11px; line-height:1.35;\">Il file eventualmente in cancellazione termina. I file successivi non verranno cancellati.<\/div>';\n    };\n\n    window.requestStopOrphanFileCleanup = function() {\n        const st = window.orphanCleanupState || {};\n        if (!st.active) {\n            window.tiAlert('Nessun processo di pulizia file non associati in corso.');\n            return;\n        }\n        st.stopRequested = true;\n        window.orphanCleanupState = st;\n        const btn = gE('ti-orphan-emergency-stop');\n        if (btn) { btn.disabled = true; btn.innerText = 'Interruzione richiesta...'; }\n        window.updateProgressPopup(\n            Math.max(1, Math.min(99, parseInt(st.lastPercent || 50, 10) || 50)),\n            'Pulizia file non associati',\n            'Esci d emergenza richiesto. Attendo la fine dell eventuale cancellazione in corso.',\n            st.currentPath || 'Interruzione richiesta'\n        );\n    };\n\n    window.runOrphanFileCleanup = function() {\n        if (!(window.canRunLocalOrphanCleanup && window.canRunLocalOrphanCleanup())) {\n            window.tiAlert('Funzione disponibile solo per amministratore, configuratore, responsabile o globaladmin.');\n            return;\n        }\n        const dbVal = gE('ti-ditta') ? (gE('ti-ditta').value || '') : '';\n        if (!dbVal) { window.tiAlert('Seleziona una ditta prima di avviare la pulizia.'); return; }\n        const ask = 'Questa funzione elimina fisicamente dallo storage i file non associati a nessuna tabella della ditta selezionata.<br><br>Prima verra eseguita una scansione. Durante la cancellazione vedrai il path completo del file eliminato.<br><br>Confermi di procedere?';\n        const start = function() { window.startOrphanFileCleanup(dbVal); };\n        if (typeof window.tiConfirmYesNoAction === 'function') window.tiConfirmYesNoAction(ask, start, null, 'AVVIA PULIZIA', 'ANNULLA');\n        else window.tiConfirm(ask.replace(\/<br\\s*\\\/?>\/gi, '\\n'), start);\n    };\n\n    window.startOrphanFileCleanup = function(dbVal) {\n        window.orphanCleanupState = { active:true, stopRequested:false, total:0, processed:0, deleted:0, currentPath:'Scansione file della ditta', lastPercent:2, startedAt:Date.now() };\n        const fd = new FormData();\n        fd.append('ti_action', 'ti_ai_scan_orphan_files');\n        fd.append('db', dbVal);\n        window.showProgressPopup('Pulizia file non associati', 'Scansione file della ditta in corso.', {startPercent: 2, targetPercent: 18, stepMs: 350, cancellable:false, currentItem:'Scansione file della ditta'});\n        window.showOrphanCleanupEmergencyButton();\n        window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd, tiNoLongProcessSignal:true}).then(function(res){\n            if (!res || !res.success) throw new Error((res && res.data && res.data.message) ? res.data.message : 'Scansione non riuscita.');\n            const files = (res.data && Array.isArray(res.data.files)) ? res.data.files : [];\n            if (!files.length) {\n                if (window.orphanCleanupState) window.orphanCleanupState.active = false;\n                if (typeof window.tiFinishNoOrphanCleanup431 === 'function') {\n                    window.tiFinishNoOrphanCleanup431('Pulizia completata. Nessun file non associato trovato.', {globalMode:false});\n                    return;\n                }\n                window.restoreDefaultLoaderActions();\n                window.hideProgressPopup(true, 'Nessun file non associato trovato', {notifyUser:true, successMessage:'Pulizia completata. Nessun file non associato da eliminare.'});\n                setTimeout(function(){ window.tiAlert('Pulizia completata.<br>Nessun file non associato trovato.'); }, 300);\n                return;\n            }\n            if (window.orphanCleanupState) { window.orphanCleanupState.total = files.length; window.orphanCleanupState.currentPath = 'File trovati: ' + files.length; window.orphanCleanupState.lastPercent = 20; }\n            window.updateProgressPopup(20, 'Pulizia file non associati', 'Trovati ' + files.length + ' file non associati. Avvio cancellazione.', 'File trovati: ' + files.length);\n            window.showOrphanCleanupEmergencyButton();\n            window.deleteOrphanFilesSequential(dbVal, files, 0, 0, [], []);\n        }).catch(function(e){\n            if (window.orphanCleanupState) window.orphanCleanupState.active = false;\n            window.restoreDefaultLoaderActions();\n            window.hideProgressPopup(false, 'Errore pulizia file');\n            window.tiAlert('Errore durante la scansione file non associati: ' + ((e && e.message) ? e.message : 'errore sconosciuto'));\n        });\n    };\n\n    window.showOrphanCleanupFinalReport = function(deletedPaths, errors, total, deleted, interrupted, processed, scopeInfo) {\n        const paths = Array.isArray(deletedPaths) ? deletedPaths : [];\n        const errList = Array.isArray(errors) ? errors : [];\n        const scope = scopeInfo && typeof scopeInfo === 'object' ? scopeInfo : {};\n        const proc = Math.max(0, parseInt(processed || deleted + errList.length, 10) || 0);\n        const scopeLabel = scope.label || ((window.orphanCleanupState && window.orphanCleanupState.globalMode) ? 'Pulizia globale su tutte le ditte' : 'Pulizia ditta selezionata');\n        const ditteProcessed = parseInt(scope.ditteProcessed || 0, 10) || 0;\n        const ditteTotal = parseInt(scope.ditteTotal || 0, 10) || 0;\n        const summary = (interrupted ? 'Pulizia interrotta su richiesta amministratore. ' : '') + 'File eliminati: ' + deleted + ' su ' + total + '. File processati: ' + proc + ' su ' + total + (ditteTotal ? '. Ditte processate: ' + ditteProcessed + ' su ' + ditteTotal : '') + (errList.length ? '. Errori: ' + errList.length : '.');\n        const reportLines = [];\n        reportLines.push('Report pulizia file non associati');\n        reportLines.push('Ambito: ' + scopeLabel);\n        reportLines.push('Versione plugin: ' + (window.tiVersion || '') + ' - ' + (window.tiVersionDateTime || ''));\n        if (interrupted) reportLines.push('Esito: processo interrotto con Esci d emergenza');\n        reportLines.push(summary);\n        reportLines.push('');\n        reportLines.push('Path file cancellati:');\n        if (paths.length) paths.forEach(function(path, idx){ reportLines.push((idx + 1) + '. ' + path); });\n        else reportLines.push('Nessun file cancellato.');\n        if (errList.length) {\n            reportLines.push('');\n            reportLines.push('Errori o file non cancellati:');\n            errList.forEach(function(err, idx){ reportLines.push((idx + 1) + '. ' + err); });\n        }\n        window.lastOrphanCleanupReportText = reportLines.join('\\n');\n        let html = '<div class=\"ti-orphan-report-wide\" style=\"text-align:left; max-height:72vh; overflow:auto; font-size:11px; line-height:1.35;\">';\n        html += '<div style=\"font-weight:800; font-size:14px; margin-bottom:8px;\">Report pulizia file non associati<\/div>';\n        html += '<div style=\"margin-bottom:6px; color:#93c5fd; font-size:11px;\"><b>Ambito:<\/b> ' + window.escapeHtml(scopeLabel) + '<\/div>';\n        if (interrupted) html += '<div style=\"margin-bottom:8px; color:#fbbf24; font-weight:800;\">Processo interrotto con Esci d emergenza.<\/div>';\n        html += '<div style=\"margin-bottom:8px; color:#cbd5e1; font-size:11px;\">' + window.escapeHtml(summary) + '<\/div>';\n        html += '<div style=\"font-weight:700; margin:10px 0 5px; font-size:12px;\">Path file cancellati<\/div>';\n        if (paths.length) {\n            html += '<ol style=\"padding-left:18px; margin:0; color:#e5e7eb; font-size:10.5px; line-height:1.35; word-break:break-all;\">';\n            paths.forEach(function(path){ html += '<li>' + window.escapeHtml(path) + '<\/li>'; });\n            html += '<\/ol>';\n        } else {\n            html += '<div style=\"color:#e5e7eb; font-size:11px;\">Nessun file cancellato.<\/div>';\n        }\n        if (errList.length) {\n            html += '<div style=\"font-weight:700; margin:12px 0 5px; color:#fecaca; font-size:12px;\">Errori o file non cancellati<\/div>';\n            html += '<ol style=\"padding-left:18px; margin:0; color:#fecaca; font-size:10.5px; line-height:1.35; word-break:break-all;\">';\n            errList.forEach(function(err){ html += '<li>' + window.escapeHtml(err) + '<\/li>'; });\n            html += '<\/ol>';\n        }\n        html += '<div style=\"margin-top:10px; font-size:11px; color:#93c5fd;\">Usa il pulsante Copia testo per copiare il report e incollarlo dove serve.<\/div>';\n        html += '<\/div>';\n        window.tiAlert(html);\n        setTimeout(function(){\n            const reportModal = document.querySelector('#ti-alert-ov .ti-modal');\n            const reportMsg = gE('ti-alert-msg');\n            if (reportModal) { reportModal.style.width = 'min(94vw, 1080px)'; reportModal.style.maxWidth = '1080px'; reportModal.style.padding = '16px'; }\n            if (reportMsg) { reportMsg.style.fontSize = '11px'; reportMsg.style.lineHeight = '1.35'; reportMsg.style.marginBottom = '12px'; }\n            const okBtn = gE('ti-alert-no');\n            if (okBtn && !okBtn.dataset.orphanReportWideClose) {\n                okBtn.dataset.orphanReportWideClose = '1';\n                const oldClose = okBtn.onclick;\n                okBtn.onclick = function(){\n                    if (reportModal) { reportModal.style.width = '300px'; reportModal.style.maxWidth = ''; reportModal.style.padding = '20px'; }\n                    if (reportMsg) { reportMsg.style.fontSize = '15px'; reportMsg.style.lineHeight = '1.4'; reportMsg.style.marginBottom = '20px'; }\n                    okBtn.dataset.orphanReportWideClose = '';\n                    if (typeof oldClose === 'function') oldClose.call(this);\n                    else window.closeModal('ti-alert-ov');\n                };\n            }\n            if (gE('ti-alert-copy')) {\n                gE('ti-alert-copy').onclick = function() {\n                    const text = window.lastOrphanCleanupReportText || window.getAlertPlainText();\n                    const done = function(ok) {\n                        const btn = gE('ti-alert-copy');\n                        if (!btn) return;\n                        const old = btn.innerText || 'Copia';\n                        btn.innerText = ok ? 'Copiato' : 'Errore copia';\n                        setTimeout(function(){ btn.innerText = old; }, 1200);\n                    };\n                    if (navigator.clipboard && navigator.clipboard.writeText) {\n                        navigator.clipboard.writeText(text).then(function(){ done(true); }).catch(function(){ done(false); });\n                    } else {\n                        try {\n                            const ta = document.createElement('textarea');\n                            ta.value = text; ta.style.position = 'fixed'; ta.style.left = '-9999px';\n                            document.body.appendChild(ta); ta.focus(); ta.select();\n                            const ok = document.execCommand('copy'); document.body.removeChild(ta); done(ok);\n                        } catch(e) { done(false); }\n                    }\n                };\n            }\n        }, 0);\n    };\n\n    window.deleteOrphanFilesSequential = function(dbVal, files, index, deleted, errors, deletedPaths) {\n        const total = files.length;\n        deletedPaths = Array.isArray(deletedPaths) ? deletedPaths : [];\n        const st = window.orphanCleanupState || {};\n        if (st.stopRequested) {\n            st.active = false;\n            st.processed = index;\n            window.orphanCleanupState = st;\n            window.restoreDefaultLoaderActions();\n            const msgStop = 'Pulizia interrotta. File eliminati: ' + deleted + ' su ' + total + '. File non ancora elaborati: ' + Math.max(0, total - index) + '.';\n            window.updateProgressPopup(Math.max(20, Math.min(99, parseInt(st.lastPercent || 20, 10) || 20)), 'Pulizia file non associati', msgStop, st.currentPath || 'Interruzione richiesta');\n            window.hideProgressPopup(false, 'Pulizia interrotta', {notifyUser:false});\n            setTimeout(function(){ window.showOrphanCleanupFinalReport(deletedPaths, errors, total, deleted, true, index); }, 500);\n            return;\n        }\n        if (index >= total) {\n            if (window.orphanCleanupState) { window.orphanCleanupState.active = false; window.orphanCleanupState.processed = total; }\n            window.restoreDefaultLoaderActions();\n            const ok = errors.length === 0;\n            const title = ok ? 'Pulizia file completata' : 'Pulizia completata con avvisi';\n            const msg = 'File eliminati: ' + deleted + ' su ' + total + (errors.length ? '. Errori: ' + errors.length : '.');\n            window.updateProgressPopup(100, 'Pulizia file non associati', msg, msg);\n            window.hideProgressPopup(ok, title, {notifyUser:true, successMessage:msg});\n            setTimeout(function(){ window.showOrphanCleanupFinalReport(deletedPaths, errors, total, deleted, false, total); }, 500);\n            return;\n        }\n        const file = files[index] || {};\n        const pathTxt = file.path || file.relative_path || file.name || 'file';\n        const pct = Math.max(20, Math.min(99, Math.round(20 + ((index) \/ Math.max(1, total)) * 78)));\n        if (window.orphanCleanupState) { window.orphanCleanupState.currentPath = pathTxt; window.orphanCleanupState.lastPercent = pct; window.orphanCleanupState.processed = index; }\n        window.updateProgressPopup(pct, 'Pulizia file non associati', 'Cancellazione file ' + (index + 1) + ' \/ ' + total, pathTxt);\n        window.showOrphanCleanupEmergencyButton();\n        const fd = new FormData();\n        fd.append('ti_action', 'ti_ai_delete_orphan_file');\n        fd.append('db', dbVal);\n        fd.append('rel', file.rel || '');\n        window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd, tiNoLongProcessSignal:true}).then(function(res){\n            if (res && res.success) {\n                const deletedPath = (res.data && res.data.path) ? res.data.path : pathTxt;\n                const pct2 = Math.max(pct, Math.min(99, Math.round(20 + ((index + 1) \/ Math.max(1, total)) * 78)));\n                window.updateProgressPopup(pct2, 'Pulizia file non associati', 'Eliminato file ' + (index + 1) + ' \/ ' + total, deletedPath);\n                if (window.orphanCleanupState) { window.orphanCleanupState.currentPath = deletedPath; window.orphanCleanupState.lastPercent = pct2; window.orphanCleanupState.processed = index + 1; window.orphanCleanupState.deleted = deleted + 1; }\n                deletedPaths.push(deletedPath);\n                setTimeout(function(){ window.deleteOrphanFilesSequential(dbVal, files, index + 1, deleted + 1, errors, deletedPaths); }, 80);\n            } else {\n                errors.push(pathTxt + ' - ' + ((res && res.data && res.data.message) ? res.data.message : 'errore eliminazione'));\n                if (window.orphanCleanupState) { window.orphanCleanupState.processed = index + 1; }\n                setTimeout(function(){ window.deleteOrphanFilesSequential(dbVal, files, index + 1, deleted, errors, deletedPaths); }, 80);\n            }\n        }).catch(function(e){\n            errors.push(pathTxt + ' - ' + ((e && e.message) ? e.message : 'errore rete'));\n            if (window.orphanCleanupState) { window.orphanCleanupState.processed = index + 1; }\n            setTimeout(function(){ window.deleteOrphanFilesSequential(dbVal, files, index + 1, deleted, errors, deletedPaths); }, 80);\n        });\n    };\n\n\n    window.getGlobalOrphanCleanupDitte = function() {\n        const normalize = function(list) {\n            return (Array.isArray(list) ? list : []).filter(function(d){\n                const db = d && d.db ? String(d.db) : '';\n                return db && db !== 'Global' && db !== 'NEW_DB' && db !== 'shopservicemain.json';\n            });\n        };\n        const existing = normalize(window.ditteData || []);\n        if (existing.length) return Promise.resolve(existing);\n        const url = window.tiUrl + '?ti_action=ti_ai_list_ditte&current_db=&show_suspended=1';\n        return window.fetchJsonSafe(url, {method:'GET', tiNoLongProcessSignal:true}).then(function(res){\n            if (!res || !res.success) throw new Error((res && res.data && res.data.message) ? res.data.message : 'Elenco ditte non disponibile.');\n            const list = normalize(res.data && res.data.ditte);\n            window.ditteData = list;\n            return list;\n        });\n    };\n\n    window.scanOrphanFilesForDb = function(dbVal, label) {\n        const fd = new FormData();\n        fd.append('ti_action', 'ti_ai_scan_orphan_files');\n        fd.append('db', dbVal);\n        if (window.orphanCleanupState) window.orphanCleanupState.currentPath = 'Scansione ditta: ' + (label || dbVal);\n        return window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd, tiNoLongProcessSignal:true}).then(function(res){\n            if (!res || !res.success) throw new Error((res && res.data && res.data.message) ? res.data.message : 'Scansione non riuscita.');\n            return (res.data && Array.isArray(res.data.files)) ? res.data.files : [];\n        });\n    };\n\n    window.deleteSingleOrphanFile = function(dbVal, file) {\n        const fd = new FormData();\n        fd.append('ti_action', 'ti_ai_delete_orphan_file');\n        fd.append('db', dbVal);\n        fd.append('rel', (file && file.rel) ? file.rel : '');\n        return window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd, tiNoLongProcessSignal:true});\n    };\n\n    window.runGlobalOrphanFileCleanup = function() {\n        if (String(window.tiUser || '') !== 'SSGlobalAdmin') {\n            window.tiAlert('Funzione globale disponibile solo per SSGlobalAdmin.');\n            return;\n        }\n        const ask = 'Questa funzione elimina fisicamente dallo storage i file non associati di tutte le ditte visibili al Global Admin.<br><br>Prima verra eseguita una scansione globale. Durante la cancellazione vedrai ditta e path completo del file eliminato.<br><br>Confermi di procedere?';\n        const start = function(){ window.startGlobalOrphanFileCleanup(); };\n        if (typeof window.tiConfirmYesNoAction === 'function') window.tiConfirmYesNoAction(ask, start, null, 'AVVIA PULIZIA GLOBALE', 'ANNULLA');\n        else window.tiConfirm(ask.replace(\/<br\\s*\\\/?>\/gi, '\\n'), start);\n    };\n\n    window.startGlobalOrphanFileCleanup = function() {\n        window.orphanCleanupState = { active:true, globalMode:true, stopRequested:false, total:0, processed:0, deleted:0, currentPath:'Scansione globale ditte', lastPercent:2, startedAt:Date.now(), ditteTotal:0, ditteProcessed:0 };\n        window.showProgressPopup('Pulizia globale file non associati', 'Scansione ditte in corso.', {startPercent: 2, targetPercent: 18, stepMs: 350, cancellable:false, currentItem:'Scansione globale ditte'});\n        window.showOrphanCleanupEmergencyButton();\n        window.getGlobalOrphanCleanupDitte().then(function(ditte){\n            if (!ditte.length) throw new Error('Nessuna ditta disponibile per la pulizia globale.');\n            if (window.orphanCleanupState) { window.orphanCleanupState.ditteTotal = ditte.length; window.orphanCleanupState.currentPath = 'Ditte trovate: ' + ditte.length; }\n            window.updateProgressPopup(8, 'Pulizia globale file non associati', 'Ditte trovate: ' + ditte.length + '. Avvio scansione file.', 'Ditte trovate: ' + ditte.length);\n            const allFiles = [];\n            const scanErrors = [];\n            const scanNext = function(i){\n                const st = window.orphanCleanupState || {};\n                if (st.stopRequested) {\n                    st.active = false; window.orphanCleanupState = st; window.restoreDefaultLoaderActions(); window.hideProgressPopup(false, 'Pulizia globale interrotta', {notifyUser:false});\n                    setTimeout(function(){ window.showOrphanCleanupFinalReport([], scanErrors, 0, 0, true, 0, {label:'Pulizia globale su tutte le ditte', ditteProcessed:i, ditteTotal:ditte.length}); }, 400);\n                    return;\n                }\n                if (i >= ditte.length) {\n                    if (!allFiles.length) {\n                        if (window.orphanCleanupState) window.orphanCleanupState.active = false;\n                        if (typeof window.tiFinishNoOrphanCleanup431 === 'function') {\n                            window.tiFinishNoOrphanCleanup431('Pulizia globale completata. Nessun file non associato trovato.', {globalMode:true, afterClose:function(){\n                                try { window.showOrphanCleanupFinalReport([], scanErrors, 0, 0, false, 0, {label:'Pulizia globale su tutte le ditte', ditteProcessed:ditte.length, ditteTotal:ditte.length}); } catch(e) {}\n                            }});\n                            return;\n                        }\n                        window.restoreDefaultLoaderActions();\n                        window.hideProgressPopup(true, 'Nessun file non associato trovato', {notifyUser:true, successMessage:'Pulizia globale completata. Nessun file non associato da eliminare.'});\n                        setTimeout(function(){ window.showOrphanCleanupFinalReport([], scanErrors, 0, 0, false, 0, {label:'Pulizia globale su tutte le ditte', ditteProcessed:ditte.length, ditteTotal:ditte.length}); }, 400);\n                        return;\n                    }\n                    if (window.orphanCleanupState) { window.orphanCleanupState.total = allFiles.length; window.orphanCleanupState.lastPercent = 35; window.orphanCleanupState.currentPath = 'File non associati totali: ' + allFiles.length; }\n                    window.updateProgressPopup(35, 'Pulizia globale file non associati', 'Trovati ' + allFiles.length + ' file non associati. Avvio cancellazione.', 'File non associati totali: ' + allFiles.length);\n                    window.deleteGlobalOrphanFilesSequential(allFiles, 0, 0, scanErrors, [], ditte.length, ditte.length);\n                    return;\n                }\n                const d = ditte[i] || {};\n                const db = d.db || '';\n                const label = d.label || d.nome_pura || db;\n                const pct = Math.max(8, Math.min(34, Math.round(8 + (i \/ Math.max(1, ditte.length)) * 26)));\n                if (window.orphanCleanupState) { window.orphanCleanupState.lastPercent = pct; window.orphanCleanupState.currentPath = 'Scansione: ' + label; window.orphanCleanupState.ditteProcessed = i; }\n                window.updateProgressPopup(pct, 'Pulizia globale file non associati', 'Scansione ditta ' + (i + 1) + ' \/ ' + ditte.length, label + ' - ' + db);\n                window.showOrphanCleanupEmergencyButton();\n                window.scanOrphanFilesForDb(db, label).then(function(files){\n                    files.forEach(function(f){ allFiles.push({db:db, label:label, file:f}); });\n                    if (window.orphanCleanupState) window.orphanCleanupState.ditteProcessed = i + 1;\n                    setTimeout(function(){ scanNext(i + 1); }, 40);\n                }).catch(function(e){\n                    scanErrors.push(label + ' [' + db + '] - scansione: ' + ((e && e.message) ? e.message : 'errore'));\n                    if (window.orphanCleanupState) window.orphanCleanupState.ditteProcessed = i + 1;\n                    setTimeout(function(){ scanNext(i + 1); }, 40);\n                });\n            };\n            scanNext(0);\n        }).catch(function(e){\n            if (window.orphanCleanupState) window.orphanCleanupState.active = false;\n            window.restoreDefaultLoaderActions();\n            window.hideProgressPopup(false, 'Errore pulizia globale');\n            window.tiAlert('Errore durante la pulizia globale file non associati: ' + ((e && e.message) ? e.message : 'errore sconosciuto'));\n        });\n    };\n\n    window.deleteGlobalOrphanFilesSequential = function(items, index, deleted, errors, deletedPaths, ditteProcessed, ditteTotal) {\n        const total = items.length;\n        deletedPaths = Array.isArray(deletedPaths) ? deletedPaths : [];\n        const st = window.orphanCleanupState || {};\n        if (st.stopRequested) {\n            st.active = false; st.processed = index; window.orphanCleanupState = st;\n            window.restoreDefaultLoaderActions();\n            const msgStop = 'Pulizia globale interrotta. File eliminati: ' + deleted + ' su ' + total + '. File non ancora elaborati: ' + Math.max(0, total - index) + '.';\n            window.updateProgressPopup(Math.max(35, Math.min(99, parseInt(st.lastPercent || 35, 10) || 35)), 'Pulizia globale file non associati', msgStop, st.currentPath || 'Interruzione richiesta');\n            window.hideProgressPopup(false, 'Pulizia globale interrotta', {notifyUser:false});\n            setTimeout(function(){ window.showOrphanCleanupFinalReport(deletedPaths, errors, total, deleted, true, index, {label:'Pulizia globale su tutte le ditte', ditteProcessed:ditteProcessed || 0, ditteTotal:ditteTotal || 0}); }, 500);\n            return;\n        }\n        if (index >= total) {\n            if (window.orphanCleanupState) { window.orphanCleanupState.active = false; window.orphanCleanupState.processed = total; }\n            window.restoreDefaultLoaderActions();\n            const ok = errors.length === 0;\n            const title = ok ? 'Pulizia globale completata' : 'Pulizia globale completata con avvisi';\n            const msg = 'File eliminati: ' + deleted + ' su ' + total + (errors.length ? '. Errori: ' + errors.length : '.');\n            window.updateProgressPopup(100, 'Pulizia globale file non associati', msg, msg);\n            window.hideProgressPopup(ok, title, {notifyUser:true, successMessage:msg});\n            setTimeout(function(){ window.showOrphanCleanupFinalReport(deletedPaths, errors, total, deleted, false, total, {label:'Pulizia globale su tutte le ditte', ditteProcessed:ditteProcessed || 0, ditteTotal:ditteTotal || 0}); }, 500);\n            return;\n        }\n        const item = items[index] || {};\n        const file = item.file || {};\n        const db = item.db || '';\n        const label = item.label || db;\n        const pathTxt = file.path || file.relative_path || file.name || 'file';\n        const pct = Math.max(35, Math.min(99, Math.round(35 + (index \/ Math.max(1, total)) * 63)));\n        const current = label + ' [' + db + '] - ' + pathTxt;\n        if (window.orphanCleanupState) { window.orphanCleanupState.currentPath = current; window.orphanCleanupState.lastPercent = pct; window.orphanCleanupState.processed = index; }\n        window.updateProgressPopup(pct, 'Pulizia globale file non associati', 'Cancellazione file ' + (index + 1) + ' \/ ' + total, current);\n        window.showOrphanCleanupEmergencyButton();\n        window.deleteSingleOrphanFile(db, file).then(function(res){\n            if (res && res.success) {\n                const deletedPath = (res.data && res.data.path) ? res.data.path : pathTxt;\n                const fullLine = label + ' [' + db + '] - ' + deletedPath;\n                const pct2 = Math.max(pct, Math.min(99, Math.round(35 + ((index + 1) \/ Math.max(1, total)) * 63)));\n                window.updateProgressPopup(pct2, 'Pulizia globale file non associati', 'Eliminato file ' + (index + 1) + ' \/ ' + total, fullLine);\n                if (window.orphanCleanupState) { window.orphanCleanupState.currentPath = fullLine; window.orphanCleanupState.lastPercent = pct2; window.orphanCleanupState.processed = index + 1; window.orphanCleanupState.deleted = deleted + 1; }\n                deletedPaths.push(fullLine);\n                setTimeout(function(){ window.deleteGlobalOrphanFilesSequential(items, index + 1, deleted + 1, errors, deletedPaths, ditteProcessed, ditteTotal); }, 80);\n            } else {\n                errors.push(current + ' - ' + ((res && res.data && res.data.message) ? res.data.message : 'errore eliminazione'));\n                if (window.orphanCleanupState) window.orphanCleanupState.processed = index + 1;\n                setTimeout(function(){ window.deleteGlobalOrphanFilesSequential(items, index + 1, deleted, errors, deletedPaths, ditteProcessed, ditteTotal); }, 80);\n            }\n        }).catch(function(e){\n            errors.push(current + ' - ' + ((e && e.message) ? e.message : 'errore rete'));\n            if (window.orphanCleanupState) window.orphanCleanupState.processed = index + 1;\n            setTimeout(function(){ window.deleteGlobalOrphanFilesSequential(items, index + 1, deleted, errors, deletedPaths, ditteProcessed, ditteTotal); }, 80);\n        });\n    };\n\n    window.refreshConfigHeaderButtons = function() {\n        const box = gE('ti-conf-global-btn');\n        if (!box) return;\n        let html = '<div style=\"display:flex; gap:8px; flex-wrap:wrap; justify-content:flex-end;\">';\n        \/\/ 30.9.246: i pulsanti di pulizia file non associati non devono essere mostrati nella parte alta della Configurazione, per nessun ruolo.\n        if (window.configInstructionCheckActive) {\n            html += '<button type=\"button\" class=\"ti-btn\" style=\"background:#4b5563; color:#fff;\" onclick=\"window.showAllConfigTables()\">\u21a9 Mostra tutto<\/button>';\n        }\n        html += '<\/div>';\n        box.innerHTML = html;\n        if (window.refreshOrphanCleanupButton) window.refreshOrphanCleanupButton();\n    };\n\n    window.verifyInstructionsArchive = function() {\n        if (!window.currentDbData || !window.currentDbData.Tabelle) return;\n        const view = window.buildInstructionsViewDb(window.currentDbData);\n        window.currentDbViewData = view;\n        window.configInstructionCheckActive = true;\n        window.renderConfig();\n        const stats = (view && view.__instructionStats) ? view.__instructionStats : {tables:0, rows:0};\n        if (!stats.rows) {\n            window.tiAlert('Nessuna riga con Istruzioni o Istruzione compilata trovata nell archivio della ditta.');\n        } else {\n            window.tiAlert('Verifica istruzioni completata.<br>Tabelle con istruzioni: <b>' + stats.tables + '<\/b><br>Righe selezionate: <b>' + stats.rows + '<\/b>');\n        }\n    };\n\n    window.showAllConfigTables = function() {\n        window.currentDbViewData = null;\n        window.configInstructionCheckActive = false;\n        window.renderConfig();\n    };\n\n\n    window.__ensurePluginDateInputs = function() {\n        if (typeof window.initPluginDateInputs === 'function') return;\n        window.initPluginDateInputs = function(root) {\n            const scope = root || document;\n            scope.querySelectorAll('input[type=\"date\"], input[type=\"time\"]').forEach(function(inp){\n                const v = String(inp.value || '').trim();\n                if (inp.type === 'date' && !\/^\\d{4}-\\d{2}-\\d{2}$\/.test(v)) inp.value = '';\n                if (inp.type === 'time' && !\/^\\d{2}:\\d{2}$\/.test(v)) inp.value = '';\n                if (inp.type === 'date') { inp.placeholder = 'gg\/mm\/aaaa'; inp.title = inp.title || 'Seleziona una data'; }\n                if (inp.type === 'time') { inp.placeholder = 'hh:mm'; inp.title = inp.title || 'Seleziona un orario'; }\n            });\n        };\n    };\n\n    window.initConfigTopScrollbars = function(root) {\n        try {\n            const scope = root || document;\n            scope.querySelectorAll('.ti-sync-hscroll-wrap').forEach(function(wrap){\n                if (!wrap || !wrap.querySelector) return;\n                const table = wrap.querySelector('table');\n                if (!table) return;\n                const topBar = wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains('ti-sync-hscroll-top') ? wrap.previousElementSibling : null;\n                const bottomBar = wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains('ti-sync-hscroll-bottom') ? wrap.nextElementSibling : null;\n                const bars = [topBar, bottomBar].filter(Boolean);\n                const width = Math.max(table.scrollWidth || 0, table.offsetWidth || 0, wrap.scrollWidth || 0, wrap.clientWidth || 0, (table.tHead && table.tHead.scrollWidth) || 0, (table.tBodies && table.tBodies[0] && table.tBodies[0].scrollWidth) || 0);\n                const canScroll = width > ((wrap.clientWidth || 0) + 4);\n                bars.forEach(function(bar){\n                    const inner = bar.querySelector('.ti-cfg-top-scroll-inner, .ti-sync-hscroll-inner');\n                    if (!inner) return;\n                    inner.style.width = width + 'px';\n                    bar.style.display = canScroll ? 'block' : 'none';\n                });\n                if (wrap.dataset.syncBound !== '1') {\n                    const syncBars = function(source){\n                        const left = source && typeof source.scrollLeft === 'number' ? source.scrollLeft : wrap.scrollLeft;\n                        if (wrap.scrollLeft !== left) wrap.scrollLeft = left;\n                        bars.forEach(function(bar){ if (bar && bar.scrollLeft !== left) bar.scrollLeft = left; });\n                    };\n                    wrap.addEventListener('scroll', function(){ syncBars(wrap); }, { passive:true });\n                    bars.forEach(function(bar){ bar.addEventListener('scroll', function(){ syncBars(bar); }, { passive:true }); });\n                    wrap.dataset.syncBound = '1';\n                }\n                bars.forEach(function(bar){ if (bar) bar.scrollLeft = wrap.scrollLeft || 0; });\n            });\n        } catch(e) {}\n    };\n\n    window.bindConfigTableUX = function(root) {\n        try {\n            const scope = root || document;\n            scope.querySelectorAll('details.ti-cfg-details').forEach(function(det){\n                if (det.dataset.uxBound === '1') return;\n                det.addEventListener('toggle', function(){ setTimeout(function(){ window.initConfigTopScrollbars(scope); }, 60); });\n                det.dataset.uxBound = '1';\n            });\n            if (!window.__cfgResizeBound) {\n                window.addEventListener('resize', function(){ window.initConfigTopScrollbars(document); }, { passive:true });\n                window.__cfgResizeBound = true;\n            }\n        } catch(e) {}\n    };\n\n\n    window.tiConfigSchemaDescriptions = {\"_meta\": {\"source\": \"Shop-Service - Struttura standard database.docx\", \"database\": \"shopservicemain\", \"version\": \"30.9.76\", \"note\": \"Descrizioni operative dei campi principali usate da configurazione, AI, validazioni, alias e relazioni ufficiali. Payments ristrutturata per Metodo.\"}, \"global_fields\": {\"Data\": \"Data di ultima variazione nel formato DD\/MM\/YYYY.\", \"Ora\": \"Ora di ultima variazione nel formato HH:MM.\", \"Nota\": \"Note generali.\", \"Stato\": \"Stato di disponibilit\u00e0 del set dati.\", \"Username\": \"Username univoco che individua l'utente registrato\/loggato.\", \"PIVA_VAT\": \"Partita IVA per fatturazione B2B.\", \"IVA_VAT\": \"Percentuale IVA applicata oppure scorporata dai prezzi comprensivi IVA.\", \"ADE\": \"Codice ADE per fatturazione elettronica Italia.\"}, \"tables\": {\"Config\": {\"Ditta\": \"Nome ditta.\", \"Attivita\": \"Attivit\u00e0 economica ditta con ricerca ATECO.\", \"Ragione sociale\": \"Ragione sociale della ditta.\", \"Logo\": \"Logo della ditta.\", \"COD_FISC\": \"Codice fiscale se necessario.\", \"Sez_ordine\": \"Sezionale per la numerazione ordini.\", \"Num_ordine\": \"Ultimo numero ordine.\", \"Sez_fattura\": \"Sezionale per la numerazione fatture.\", \"Num_fattura\": \"Ultimo numero fattura.\", \"Sez_Corrispettivo\": \"Sezionale per la numerazione corrispettivi.\", \"Num_Corrispettivo\": \"Ultimo numero corrispettivi.\", \"Email ditta\": \"Email principale della ditta.\", \"Email prodotti\": \"Email di riferimento per prodotti.\", \"Email servizi\": \"Email di riferimento per servizi.\", \"Email amministrazione\": \"Email amministrazione.\", \"Tel\": \"Telefono ditta, anche per SMS.\", \"Indirizzo\": \"Indirizzo ditta.\", \"Descrizione\": \"Descrizione breve della ditta e della sua attivit\u00e0.\", \"Accoglienza\": \"Messaggio di accoglienza utente standard.\", \"Istruzioni\": \"Istruzioni per AI relative alla ditta.\", \"Tab_tab\": \"Formato visualizzazione righe tabella ordine: Tabella.\", \"Tab_sma\": \"Formato visualizzazione righe tabella ordine: Small.\", \"Tab_med\": \"Formato visualizzazione righe tabella ordine: Medium.\", \"Tab_lar\": \"Formato visualizzazione righe tabella ordine: Large.\", \"Data creazione\": \"Data creazione ditta. Periodo test 30 giorni successivi.\", \"Stato\": \"Stato Shop & Service della ditta: Attivo, Operativo, Configurazione, Sospesa, Demo.\"}, \"Payments\": {\"Metodo\": \"Metodo di pagamento: bank_transfer, PayPal o altro metodo supportato.\", \"enabled\": \"Abilita\/disabilita il metodo di pagamento.\", \"beneficiary\": \"Beneficiario bonifico.\", \"iban\": \"IBAN bonifico.\", \"bic\": \"BIC\/SWIFT bonifico.\", \"bank_name\": \"Nome banca.\", \"payment_terms\": \"Condizioni di pagamento, es. Bonifico.\", \"instructions\": \"Istruzioni di pagamento per l'utente.\", \"merchant_mode\": \"Modalit\u00e0 merchant PayPal.\", \"environment\": \"Ambiente PayPal: live o sandbox.\", \"client_id\": \"Client ID PayPal.\", \"client_secret_ref\": \"Riferimento sicuro al client secret.\", \"webhook_id\": \"ID webhook PayPal.\", \"currency\": \"Valuta, es. EUR.\", \"intent\": \"Intent PayPal, es. CAPTURE.\", \"ui_mode\": \"Modalit\u00e0 UI pagamento, es. buttons.\", \"brand_name\": \"Nome brand mostrato nel pagamento.\", \"locale\": \"Locale interfaccia, es. it_IT.\", \"invoice_prefix\": \"Prefisso fattura.\", \"debug\": \"Abilita log\/debug pagamento.\"}, \"Informazioni\": {\"Tipo\": \"Tipo di informazione: WEB, URL sito e sotto-URL.\", \"Descrizione\": \"Descrizione o URL del sito, es. https:\/\/www.tisoft.it.\", \"Doc\": \"Documento collegato TXT, PDF ecc.\", \"Istruzioni\": \"Istruzioni per AI relative all'informazione.\"}, \"Istruzioni\": {\"Istruzione\": \"Istruzione particolare per la AI, es. sconto 10% fino al 31\/12\/2026.\", \"Doc\": \"Documento collegato TXT, PDF ecc.\", \"Da gg\": \"Data inizio validit\u00e0 istruzione.\", \"Da ora\": \"Ora inizio validit\u00e0 istruzione.\", \"A gg\": \"Data fine validit\u00e0 istruzione.\", \"A ora\": \"Ora fine validit\u00e0 istruzione.\"}, \"Utenti\": {\"Utente\": \"Cognome nome o username.\", \"Password\": \"Password.\", \"Ruolo\": \"Ruolo utente: Amministratore, Configuratore, Responsabile, Utente.\", \"Ragione sociale\": \"Ragione sociale utente\/cliente.\", \"Indirizzo\": \"Indirizzo utente\/cliente.\", \"Email\": \"Email utente.\", \"Tel\": \"Telefono utente.\", \"Sesso\": \"Sesso utente se utile.\", \"Et\u00e0\": \"Et\u00e0 utente.\", \"Eta\": \"Et\u00e0 utente.\", \"Immagine\": \"Avatar utente.\", \"Istruzioni\": \"Istruzioni particolari ad AI per l'utente.\", \"Dato 1\": \"Informazione utile per gestire il rapporto.\", \"Dato 2\": \"Informazione utile per gestire il rapporto.\", \"Dato 3\": \"Informazione utile per gestire il rapporto.\", \"Esito\": \"Esito della prima interazione con l'utente.\", \"Com Mail\": \"Autorizzazione comunicazione postale.\", \"Com Email\": \"Autorizzazione comunicazione email.\", \"Com SMS\": \"Autorizzazione comunicazione SMS.\", \"Com Tel\": \"Autorizzazione comunicazione telefonica.\", \"COD_FISC\": \"Codice fiscale utente se necessario.\"}, \"Operatori\": {\"Utente\": \"Cognome nome o username operatore.\", \"Ruolo\": \"Ruolo operatore.\", \"Competenze\": \"Competenze professionali dell'operatore.\", \"Disponibilita\": \"Limiti di disponibilit\u00e0 dell'operatore.\", \"Disponibilit\u00e0\": \"Limiti di disponibilit\u00e0 dell'operatore.\", \"Costo_orario\": \"Costo orario per la ditta.\", \"Prezzo_orario\": \"Prezzo orario per l'utente finale.\", \"Istruzioni\": \"Istruzioni AI relative all'operatore.\"}, \"Siti\": {\"Descrizione\": \"Locazione di esecuzione delle attivit\u00e0 della ditta.\", \"Indirizzo\": \"Indirizzo del sito\/sede.\", \"Quantit\u00e0\": \"Quantit\u00e0 disponibile.\", \"Quantita\": \"Quantit\u00e0 disponibile.\", \"Disponibilit\u00e0\": \"Disponibilit\u00e0 del sito, es. SI\/NO.\", \"Disponibilita\": \"Disponibilit\u00e0 del sito, es. SI\/NO.\", \"Istruzioni\": \"Istruzioni AI relative al sito.\"}, \"Dispositivi\": {\"Descrizione\": \"Strumento\/macchinario per l\\'esecuzione delle attivit\u00e0 della ditta.\", \"Quantit\u00e0\": \"Quantit\u00e0 disponibile.\", \"Quantita\": \"Quantit\u00e0 disponibile.\", \"Disponibilit\u00e0\": \"Disponibilit\u00e0 del dispositivo.\", \"Disponibilita\": \"Disponibilit\u00e0 del dispositivo.\", \"Istruzioni\": \"Istruzioni AI relative al dispositivo.\"}, \"Prodotti\": {\"Prodotto\": \"Nome breve prodotto.\", \"Descrizione\": \"Descrizione estesa prodotto.\", \"Prezzo\": \"Prezzo prodotto.\", \"Costo\": \"Costo prodotto.\", \"Quantit\u00e0\": \"Giacenza\/quantit\u00e0 prodotto.\", \"Quantita\": \"Giacenza\/quantit\u00e0 prodotto.\", \"Un\": \"Unit\u00e0 di misura.\", \"Fascia\": \"Eventuale fascia di riferimento.\", \"Immagine\": \"Immagine prodotto.\", \"Indicazioni\": \"Specifiche indicazioni d'uso del prodotto.\", \"Istruzioni\": \"Istruzioni AI relative al prodotto.\", \"Disponibilita\": \"Giacenza prodotto disponibile.\", \"Disponibilit\u00e0\": \"Giacenza prodotto disponibile.\", \"Riordino\": \"Giorni di riordino o consegna del fornitore.\"}, \"Servizi\": {\"Servizio\": \"Nome breve servizio.\", \"Descrizione\": \"Descrizione estesa servizio.\", \"Prezzo\": \"Prezzo servizio.\", \"Costo\": \"Costo servizio.\", \"Giorni_disponibilita\": \"Periodo di disponibilit\u00e0, es. giorni della settimana.\", \"Ora_disponibilita\": \"Periodo di disponibilit\u00e0, es. orari giornalieri.\", \"Data_variazione\": \"Data dell'ultima variazione del servizio.\", \"Ora_variazione\": \"Ora dell'ultima variazione del servizio.\", \"Durata standard\": \"Durata standard del servizio, es. 00:00:30 mezzora.\", \"Fascia\": \"Eventuale fascia di riferimento.\", \"Operatore1\": \"Operatore standard dedicato al servizio.\", \"Operatore2\": \"Operatore standard dedicato al servizio.\", \"Operatore3\": \"Operatore standard dedicato al servizio.\", \"Dispositivo1\": \"Dispositivo standard dedicato al servizio.\", \"Dispositivo2\": \"Dispositivo standard dedicato al servizio.\", \"Dispositivo3\": \"Dispositivo standard dedicato al servizio.\", \"Prodotto1\": \"Prodotto standard utilizzato per erogare il servizio.\", \"Prodotto2\": \"Prodotto standard utilizzato per erogare il servizio.\", \"Prodotto3\": \"Prodotto standard utilizzato per erogare il servizio.\", \"Sito1\": \"Locazione standard per erogare il servizio.\", \"Sito2\": \"Locazione standard per erogare il servizio.\", \"Sito3\": \"Locazione standard per erogare il servizio.\", \"Istruzioni\": \"Istruzioni AI relative al servizio.\"}, \"Appuntamenti\": {\"Username\": \"Username utente di riferimento.\", \"Servizio\": \"Descrizione breve servizio.\", \"Data_appuntamento\": \"Data dell'appuntamento.\", \"Ora_appuntamento\": \"Ora dell'appuntamento.\", \"Operatore1\": \"Operatore standard o assegnato al servizio.\", \"Operatore2\": \"Operatore standard o assegnato al servizio.\", \"Operatore3\": \"Operatore standard o assegnato al servizio.\", \"Dispositivo1\": \"Dispositivo standard o assegnato al servizio.\", \"Dispositivo2\": \"Dispositivo standard o assegnato al servizio.\", \"Dispositivo3\": \"Dispositivo standard o assegnato al servizio.\", \"Prodotto1\": \"Prodotto standard o assegnato al servizio.\", \"Prodotto2\": \"Prodotto standard o assegnato al servizio.\", \"Prodotto3\": \"Prodotto standard o assegnato al servizio.\", \"Sito1\": \"Locazione standard o assegnata al servizio.\", \"Sito2\": \"Locazione standard o assegnata al servizio.\", \"Sito3\": \"Locazione standard o assegnata al servizio.\", \"Preparazione\": \"Eventuali note all'utente di preparazione al servizio.\", \"Istruzioni\": \"Istruzioni AI relative all'appuntamento.\"}, \"Attivita\": {\"Username\": \"Username utente.\", \"Descrizione\": \"Descrizione dettagliata dell'attivit\u00e0, es. acquisto prodotti.\", \"Prezzo\": \"Prezzo dell'attivit\u00e0.\", \"Quantit\u00e0\": \"Quantit\u00e0 dell'attivit\u00e0.\", \"Quantita\": \"Quantit\u00e0 dell'attivit\u00e0.\", \"Esito\": \"Esito dell'attivit\u00e0.\", \"Operatore\": \"Operatore utilizzato.\", \"Dispositivo\": \"Dispositivo utilizzato.\", \"Sito\": \"Locazione dell'attivit\u00e0.\", \"Prodotto\": \"Prodotto fornito.\", \"Istruzioni\": \"Istruzioni AI relative all'attivit\u00e0 svolta.\", \"Durata\": \"Durata complessiva dell'attivit\u00e0 svolta.\"}}, \"aliases\": {\"Disponibilita\": [\"Disponibilita\", \"Disponibilit\u00e0\"], \"Quantita\": [\"Quantita\", \"Quantit\u00e0\"], \"Eta\": [\"Eta\", \"Et\u00e0\"], \"Attivita\": [\"Attivita\", \"Attivit\u00e0\"], \"Email amministrazione\": [\"Email amministrazione\", \"Email amministrativa\"], \"Data creazione\": [\"Data creazione\", \"Data_creazione\"], \"Data_appuntamento\": [\"Data_appuntamento\", \"Data appuntamento\"], \"Ora_appuntamento\": [\"Ora_appuntamento\", \"Ora appuntamento\"]}, \"relations\": {\"Servizi\": {\"Operatore1\": \"Operatori\", \"Operatore2\": \"Operatori\", \"Operatore3\": \"Operatori\", \"Dispositivo1\": \"Dispositivi\", \"Dispositivo2\": \"Dispositivi\", \"Dispositivo3\": \"Dispositivi\", \"Prodotto1\": \"Prodotti\", \"Prodotto2\": \"Prodotti\", \"Prodotto3\": \"Prodotti\", \"Sito1\": \"Siti\", \"Sito2\": \"Siti\", \"Sito3\": \"Siti\"}, \"Appuntamenti\": {\"Operatore1\": \"Operatori\", \"Operatore2\": \"Operatori\", \"Operatore3\": \"Operatori\", \"Dispositivo1\": \"Dispositivi\", \"Dispositivo2\": \"Dispositivi\", \"Dispositivo3\": \"Dispositivi\", \"Prodotto1\": \"Prodotti\", \"Prodotto2\": \"Prodotti\", \"Prodotto3\": \"Prodotti\", \"Sito1\": \"Siti\", \"Sito2\": \"Siti\", \"Sito3\": \"Siti\", \"Servizio\": \"Servizi\"}, \"Attivita\": {\"Operatore\": \"Operatori\", \"Dispositivo\": \"Dispositivi\", \"Sito\": \"Siti\", \"Prodotto\": \"Prodotti\"}}};\n    window.normalizeConfigSchemaName = function(v) { return window.compactConfigRelationName ? window.compactConfigRelationName(v) : String(v == null ? '' : v).normalize('NFD').replace(\/[\\u0300-\\u036f]\/g,'').toLowerCase().replace(\/[^a-z0-9]+\/g,''); };\n    window.getConfigFieldAliasCandidates = function(field) {\n        const schema = window.tiConfigSchemaDescriptions || {};\n        const aliases = schema.aliases || {};\n        const wanted = window.normalizeConfigSchemaName(field);\n        for (const canonical in aliases) {\n            const list = [canonical].concat(Array.isArray(aliases[canonical]) ? aliases[canonical] : []);\n            if (list.some(a => window.normalizeConfigSchemaName(a) === wanted)) return Array.from(new Set(list));\n        }\n        return [String(field || '')];\n    };\n    window.getConfigFieldDescription = function(tbl, key) {\n        const schema = window.tiConfigSchemaDescriptions || {};\n        const aliases = window.getConfigFieldAliasCandidates ? window.getConfigFieldAliasCandidates(key) : [key];\n        const tables = schema.tables || {};\n        const tKey = Object.keys(tables).find(t => window.normalizeConfigSchemaName(t) === window.normalizeConfigSchemaName(tbl)) || tbl;\n        const map = tables[tKey] || {};\n        for (const alias of aliases) {\n            const k = Object.keys(map).find(x => window.normalizeConfigSchemaName(x) === window.normalizeConfigSchemaName(alias));\n            if (k && map[k]) return String(map[k]);\n        }\n        const globals = schema.global_fields || {};\n        for (const alias of aliases) {\n            const k = Object.keys(globals).find(x => window.normalizeConfigSchemaName(x) === window.normalizeConfigSchemaName(alias));\n            if (k && globals[k]) return String(globals[k]);\n        }\n        return '';\n    };\n    window.getOfficialConfigRelationTarget = function(tbl, key) {\n        const schema = window.tiConfigSchemaDescriptions || {};\n        const rels = schema.relations || {};\n        const tKey = Object.keys(rels).find(t => window.normalizeConfigSchemaName(t) === window.normalizeConfigSchemaName(tbl));\n        if (!tKey) return '';\n        const map = rels[tKey] || {};\n        const k = Object.keys(map).find(f => window.normalizeConfigSchemaName(f) === window.normalizeConfigSchemaName(key));\n        return k ? String(map[k] || '') : '';\n    };\n\n    window.normalizeConfigRelationName = function(v) {\n        try {\n            return String(v == null ? '' : v).normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase().replace(\/[_\\-]+\/g, ' ').replace(\/\\s+\/g, ' ').trim();\n        } catch(e) {\n            return String(v == null ? '' : v).toLowerCase().replace(\/[_\\-]+\/g, ' ').replace(\/\\s+\/g, ' ').trim();\n        }\n    };\n\n    window.compactConfigRelationName = function(v) {\n        return window.normalizeConfigRelationName(v).replace(\/[^a-z0-9]+\/g, '');\n    };\n\n    window.getConfigRelationCatalog = function() {\n        return {\n            sito: {type:'sito', label:'Siti', tables:['Siti','Sito','Sedi','Sede'], picks:['Sito','Sede','Nome','Titolo','Descrizione','Indirizzo']},\n            operatore: {type:'operatore', label:'Operatori', tables:['Operatori','Operatore'], picks:['Operatore','Utente','Username','Nome','Cognome','Titolo','Descrizione']},\n            dispositivo: {type:'dispositivo', label:'Dispositivi', tables:['Dispositivi','Dispositivo','Risorse','Risorsa'], picks:['Dispositivo','Risorsa','Nome','Titolo','Descrizione','Codice']},\n            prodotto: {type:'prodotto', label:'Prodotti', tables:['Prodotti','Prodotto'], picks:['Prodotto','Articolo','Nome','Titolo','Descrizione','Codice']},\n            servizio: {type:'servizio', label:'Servizi', tables:['Servizi','Servizio'], picks:['Servizio','Nome','Titolo','Descrizione','Codice']},\n            voce: {type:'voce', label:'Prodotti\/Servizi', tables:['Prodotti','Servizi'], picks:['Prodotto','Servizio','Articolo','Nome','Titolo','Descrizione','Codice']}\n        };\n    };\n\n    window.getConfigRelationTypeFromKey = function(tbl, key) {\n        const nContact = window.normalizeConfigRelationName(key);\n        const cContact = window.compactConfigRelationName(key);\n        \/\/ I campi di contatto\/comunicazione sono campi liberi di edit, anche se nel nome contengono\n        \/\/ parole come prodotti o servizi. Esempio: Config > Email prodotti \/ Email servizi.\n        if (\/^(email|mail|pec|telefono|tel|cellulare|cell|sms|whatsapp)\/.test(cContact) || \/\\b(email|mail|pec|telefono|tel|cellulare|sms|whatsapp)\\b\/.test(nContact)) return '';\n        const officialTarget = window.getOfficialConfigRelationTarget ? window.getOfficialConfigRelationTarget(tbl, key) : '';\n        if (officialTarget) {\n            const oc = window.compactConfigRelationName(officialTarget);\n            if (oc.indexOf('siti') !== -1 || oc.indexOf('sede') !== -1) return 'sito';\n            if (oc.indexOf('operator') !== -1) return 'operatore';\n            if (oc.indexOf('dispositiv') !== -1 || oc.indexOf('risors') !== -1) return 'dispositivo';\n            if (oc.indexOf('prodott') !== -1) return 'prodotto';\n            if (oc.indexOf('serviz') !== -1) return 'servizio';\n        }\n        const n = window.normalizeConfigRelationName(key);\n        const c = window.compactConfigRelationName(key);\n        if (!c || c === 'id') return '';\n        const padded = ' ' + n + ' ';\n        const matchWord = function(words) {\n            return words.some(function(w){ return padded.indexOf(' ' + w + ' ') !== -1 || c === w || c === w + '1' || c === w + '2' || c === w + '3'; });\n        };\n        if (\/^voce[123]?$\/.test(c) || \/^voci[123]?$\/.test(c) || \/^(voce|voci) [123]$\/.test(n)) return 'voce';\n        if (matchWord(['sito','siti','sede','sedi','luogo','location'])) return 'sito';\n        if (matchWord(['operatore','operatori','tecnico','tecnici','addetto','addetti','incaricato','incaricati'])) return 'operatore';\n        if (matchWord(['dispositivo','dispositivi','risorsa','risorse','macchina','macchine','attrezzatura','attrezzature','postazione','postazioni'])) return 'dispositivo';\n        if (matchWord(['prodotto','prodotti','articolo','articoli','materiale','materiali'])) return 'prodotto';\n        if (matchWord(['servizio','servizi','prestazione','prestazioni'])) return 'servizio';\n        return '';\n    };\n\n    window.findConfigTableName = function(names) {\n        if (!window.currentDbData || !window.currentDbData.Tabelle) return '';\n        const all = Object.keys(window.currentDbData.Tabelle || {});\n        const wanted = (Array.isArray(names) ? names : [names]).map(function(x){ return window.compactConfigRelationName(x); });\n        return all.find(function(t){ return wanted.includes(window.compactConfigRelationName(t)); }) || '';\n    };\n\n    window.shouldUseMultiConfigRelation = function(tbl, key, row, meta) {\n        const n = window.normalizeConfigRelationName(key);\n        const v = (row && Object.prototype.hasOwnProperty.call(row, key)) ? String(row[key] == null ? '' : row[key]) : '';\n        if (\/[;,\\n]\/.test(v)) return true;\n        if (\/\\b(lista|elenco|ammessi|abilitati|disponibili|associati|selezionabili|multipli|multiplo)\\b\/.test(n)) return true;\n        if (\/\\b(siti|operatori|dispositivi|prodotti|servizi|risorse)\\b\/.test(n)) return true;\n        if (window.compactConfigRelationName(tbl) === 'servizi' && ['sito','operatore','dispositivo','prodotto'].includes(meta.type)) return true;\n        return false;\n    };\n\n    window.getConfigRelationMeta = function(tbl, key, row) {\n        const type = window.getConfigRelationTypeFromKey(tbl, key);\n        if (!type) return null;\n        const catalog = window.getConfigRelationCatalog();\n        const base = catalog[type];\n        if (!base) return null;\n        const currentTable = window.compactConfigRelationName(tbl);\n        const sameTarget = (base.tables || []).some(function(t){ return window.compactConfigRelationName(t) === currentTable; });\n        if (sameTarget && type !== 'voce') return null;\n        const meta = Object.assign({}, base);\n        meta.tables = (base.tables || []).slice();\n        meta.picks = (base.picks || []).slice();\n        meta.table = meta.tables[0] || '';\n        meta.realTables = meta.tables.map(function(t){ return window.findConfigTableName(t); }).filter(Boolean);\n        if (!meta.realTables.length) return null;\n        meta.sourceLabel = Array.from(new Set(meta.realTables)).join('\/');\n        meta.multiple = window.shouldUseMultiConfigRelation(tbl, key, row || {}, meta);\n        return meta;\n    };\n\n    window.splitConfigRelationValues = function(raw) {\n        return String(raw == null ? '' : raw)\n            .split(\/[;,\\n]+\/)\n            .map(function(v){ return String(v || '').trim(); })\n            .filter(Boolean);\n    };\n\n    window.getConfigRowValueByNames = function(row, names) {\n        if (!row || typeof row !== 'object') return '';\n        const keys = Object.keys(row);\n        const wanted = (Array.isArray(names) ? names : [names]).map(function(n){ return window.compactConfigRelationName(n); });\n        const realKey = keys.find(function(k){ return wanted.includes(window.compactConfigRelationName(k)); });\n        return realKey ? String(row[realKey] == null ? '' : row[realKey]).trim() : '';\n    };\n\n    window.getConfigRelationRowLabel = function(row, meta) {\n        if (!row || typeof row !== 'object') return '';\n        let value = '';\n        (meta.picks || []).some(function(pick) {\n            const found = window.getConfigRowValueByNames(row, pick);\n            if (found) { value = found; return true; }\n            return false;\n        });\n        if (!value && (meta.type === 'operatore')) {\n            const nome = window.getConfigRowValueByNames(row, ['Nome']);\n            const cognome = window.getConfigRowValueByNames(row, ['Cognome']);\n            value = (nome + ' ' + cognome).trim();\n        }\n        if (!value) {\n            const skip = {id:1, stato:1, status:1, immagine:1, foto:1, file:1, password:1, pass:1};\n            const firstKey = Object.keys(row).find(function(k){ return !skip[window.compactConfigRelationName(k)] && String(row[k] == null ? '' : row[k]).trim() !== ''; });\n            if (firstKey) value = String(row[firstKey]).trim();\n        }\n        return value;\n    };\n\n    window.isConfigRelationRowAvailable = function(row) {\n        if (!row || typeof row !== 'object') return false;\n        const negative = \/(cancellat|eliminat|sospes|disabilit|inattiv|non disponibile|non attiv|scadut|chius|\\bno\\b|\\bfalse\\b|^0$)\/i;\n        let available = true;\n        Object.keys(row).forEach(function(k) {\n            const ck = window.compactConfigRelationName(k);\n            if (!\/(stato|status|attivo|attiva|abilitato|abilitata|disponibile|visibile|sospeso|cancellato)\/.test(ck)) return;\n            const v = String(row[k] == null ? '' : row[k]).trim();\n            if (v !== '' && negative.test(v)) available = false;\n        });\n        return available;\n    };\n\n    window.getConfigRelationSupportText = function() {\n        if (!window.currentDbData || !window.currentDbData.Tabelle) return '';\n        const version = String((window.currentDbData && window.currentDbData.__db_version) || '') + '|' + Object.keys(window.currentDbData.Tabelle || {}).length;\n        if (window.__configRelationSupportCache && window.__configRelationSupportCache.version === version) return window.__configRelationSupportCache.text || '';\n        const aliases = ['Informazioni','Istruzioni','Appuntamenti'];\n        let out = '';\n        aliases.forEach(function(alias){\n            const real = window.findConfigTableName(alias);\n            const rows = real && window.currentDbData.Tabelle[real] ? window.currentDbData.Tabelle[real] : [];\n            (Array.isArray(rows) ? rows : Object.values(rows || {})).slice(0, 350).forEach(function(row){\n                if (!row || typeof row !== 'object') return;\n                out += ' ' + Object.keys(row).map(function(k){ return String(row[k] == null ? '' : row[k]); }).join(' ');\n                if (out.length > 60000) out = out.slice(0, 60000);\n            });\n        });\n        out = window.normalizeConfigRelationName(out);\n        window.__configRelationSupportCache = {version: version, text: out};\n        return out;\n    };\n\n    window.countConfigRelationMentions = function(text, value) {\n        const needle = window.normalizeConfigRelationName(value);\n        if (!needle || !text) return 0;\n        let count = 0;\n        let pos = 0;\n        while ((pos = text.indexOf(needle, pos)) !== -1) { count++; pos += Math.max(needle.length, 1); if (count > 20) break; }\n        return count;\n    };\n\n    window.normalizeConfigDateToken = function(v) {\n        const s = String(v == null ? '' : v).trim();\n        let m = s.match(\/^(\\d{4})-(\\d{2})-(\\d{2})\/);\n        if (m) return m[1] + '-' + m[2] + '-' + m[3];\n        m = s.match(\/^(\\d{1,2})\\\/(\\d{1,2})\\\/(\\d{4})$\/);\n        if (m) return m[3] + '-' + String(m[2]).padStart(2, '0') + '-' + String(m[1]).padStart(2, '0');\n        return '';\n    };\n\n    window.normalizeConfigTimeToken = function(v) {\n        const s = String(v == null ? '' : v).trim();\n        const m = s.match(\/^(\\d{1,2})[:.](\\d{2})\/);\n        if (!m) return '';\n        return String(m[1]).padStart(2, '0') + ':' + m[2];\n    };\n\n    window.getConfigRelationRowDate = function(row) {\n        if (!row || typeof row !== 'object') return '';\n        const keys = Object.keys(row);\n        const k = keys.find(function(key){ const c = window.compactConfigRelationName(key); return c === 'data' || c === 'giorno' || c === 'dataappuntamento' || c === 'date'; });\n        return k ? window.normalizeConfigDateToken(row[k]) : '';\n    };\n\n    window.getConfigRelationRowTime = function(row) {\n        if (!row || typeof row !== 'object') return '';\n        const keys = Object.keys(row);\n        const k = keys.find(function(key){ const c = window.compactConfigRelationName(key); return c === 'ora' || c === 'orario' || c === 'daora' || c === 'orainizio' || c === 'time'; });\n        return k ? window.normalizeConfigTimeToken(row[k]) : '';\n    };\n\n    window.getConfigRelationBusyValues = function(meta, currentRow, currentTbl, currentIdx) {\n        const busy = new Set();\n        if (!meta || !['operatore','sito','dispositivo'].includes(meta.type)) return busy;\n        const cDate = window.getConfigRelationRowDate(currentRow);\n        const cTime = window.getConfigRelationRowTime(currentRow);\n        if (!cDate || !cTime) return busy;\n        const real = window.findConfigTableName('Appuntamenti');\n        const rows = real && window.currentDbData && window.currentDbData.Tabelle ? window.currentDbData.Tabelle[real] : [];\n        (Array.isArray(rows) ? rows : Object.values(rows || {})).forEach(function(app, appIdx){\n            if (!app || typeof app !== 'object') return;\n            if (window.compactConfigRelationName(currentTbl) === window.compactConfigRelationName(real) && parseInt(currentIdx,10) === appIdx) return;\n            if (!window.isConfigRelationRowAvailable(app)) return;\n            if (window.getConfigRelationRowDate(app) !== cDate || window.getConfigRelationRowTime(app) !== cTime) return;\n            Object.keys(app).forEach(function(k){\n                if (window.getConfigRelationTypeFromKey(real, k) !== meta.type) return;\n                window.splitConfigRelationValues(app[k]).forEach(function(v){ if (v) busy.add(String(v).toLowerCase()); });\n            });\n        });\n        return busy;\n    };\n\n    window.getConfigRelationOptions = function(tbl, key, row, idx) {\n        const meta = window.getConfigRelationMeta(tbl, key, row || {});\n        const dbData = window.currentDbData;\n        if (!meta || !dbData || !dbData.Tabelle) return [];\n        const supportText = window.getConfigRelationSupportText();\n        const busy = window.getConfigRelationBusyValues(meta, row || {}, tbl, idx);\n        const out = [];\n        const seen = new Set();\n        const addOption = function(value, sourceTable, rowObj, force) {\n            value = String(value == null ? '' : value).trim();\n            if (!value) return;\n            const lower = value.toLowerCase();\n            if (seen.has(lower)) return;\n            if (!force && busy.has(lower)) return;\n            seen.add(lower);\n            const mentions = window.countConfigRelationMentions(supportText, value);\n            out.push({\n                value: value,\n                label: value,\n                sourceTable: sourceTable || '',\n                score: mentions,\n                title: (sourceTable ? ('Tabella: ' + sourceTable + '. ') : '') + (mentions ? ('Citato in Informazioni\/Istruzioni\/Appuntamenti: ' + mentions + ' volte.') : 'Record disponibile.')\n            });\n        };\n        (meta.tables || []).forEach(function(tblAlias){\n            const real = window.findConfigTableName(tblAlias);\n            if (!real) return;\n            let rows = dbData.Tabelle[real];\n            rows = Array.isArray(rows) ? rows : Object.values(rows || {});\n            rows.forEach(function(srcRow){\n                if (!window.isConfigRelationRowAvailable(srcRow)) return;\n                addOption(window.getConfigRelationRowLabel(srcRow, meta), real, srcRow, false);\n            });\n        });\n        window.splitConfigRelationValues(row && Object.prototype.hasOwnProperty.call(row, key) ? row[key] : '').forEach(function(v){ addOption(v, 'Valore attuale', null, true); });\n        out.sort(function(a, b){\n            if ((b.score || 0) !== (a.score || 0)) return (b.score || 0) - (a.score || 0);\n            return String(a.label || a.value).localeCompare(String(b.label || b.value), 'it');\n        });\n        return out;\n    };\n\n    window.updateMemFieldFromRelationSelect = function(tbl, idx, key, sel) {\n        if (!sel) return;\n        const protNorm = v => String(v == null ? '' : v).toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').replace(\/[^a-z0-9]\/g, '');\n        if (String(window.tiUser || '').toLowerCase() !== 'ssglobaladmin' && protNorm(tbl) === 'config' && ['stato','datacreazione','dataattivazione','wcorderid','wcactivateid'].includes(protNorm(key))) {\n            if (window.tiAlert) window.tiAlert('Campo riservato: modificabile solo da sistema, globaladmin o attivazione WooCommerce.');\n            return;\n        }\n        let values = [];\n        if (sel.multiple) values = Array.from(sel.selectedOptions || []).map(function(opt){ return String(opt.value || '').trim(); }).filter(Boolean);\n        else values = [String(sel.value || '').trim()].filter(Boolean);\n        const joined = values.join(', ');\n        if (window.currentDbData && window.currentDbData.Tabelle[tbl] && window.currentDbData.Tabelle[tbl][idx]) {\n            window.currentDbData.Tabelle[tbl][idx][key] = joined;\n            window.__configRelationSupportCache = null;\n            window.forceWhiteEditStyle && window.forceWhiteEditStyle(sel);\n            sel.style.setProperty('border-color', '#fbbf24', 'important');\n            if (!window.modifiedFields) window.modifiedFields = new Set();\n            window.modifiedFields.add(`${tbl}-${idx}-${key}`);\n            if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState();\n        }\n        const hint = sel.parentElement ? sel.parentElement.querySelector('.ti-rel-summary') : null;\n        if (hint) hint.textContent = joined || 'Nessun record associato';\n    };\n\n    window.updateMemFieldFromMultiSelect = window.updateMemFieldFromRelationSelect;\n\n    window.renderConfig = function() {\n        window.__ensurePluginDateInputs();\n        if(!window.currentDbData) return; if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState(); let dbData = window.currentDbViewData || window.currentDbData; let openDetails = []; const jsEsc = v => encodeURIComponent(String(v == null ? '' : v)); \n        document.querySelectorAll('details.ti-cfg-details').forEach(det => { if(det.open) openDetails.push(det.dataset.tbl); });\n        const cfgScrollState = { mainTop: 0, tables: {} };\n        try {\n            const mainCnt = gE('ti-conf-cnt');\n            if (mainCnt) cfgScrollState.mainTop = mainCnt.scrollTop || 0;\n            document.querySelectorAll('details.ti-cfg-details').forEach(function(det){\n                const tbl = det.dataset ? (det.dataset.tbl || '') : '';\n                const wrap = det.querySelector ? det.querySelector('.ti-cfg-table-container') : null;\n                if (tbl && wrap) cfgScrollState.tables[tbl] = { left: wrap.scrollLeft || 0, top: wrap.scrollTop || 0 };\n            });\n        } catch(e) {}\n        \n        const isSuperAdmin = window.tiRole.toLowerCase().includes('amministratore') || window.tiUser === 'SSGlobalAdmin'; \n        const canEditRows = isSuperAdmin || window.tiRole.toLowerCase().includes('configuratore') || window.tiRole.toLowerCase().includes('responsabile');\n        const isGlobalAdminConfig = String(window.tiUser || '').toLowerCase() === 'ssglobaladmin';\n        const cfgFieldNorm = v => String(v == null ? '' : v).toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').replace(\/[^a-z0-9]\/g, '');\n        const isConfigActivationProtectedField = function(tbl, key) {\n            if (isGlobalAdminConfig) return false;\n            if (cfgFieldNorm(tbl) !== 'config') return false;\n            return ['stato','datacreazione','dataattivazione','wcorderid','wcactivateid'].includes(cfgFieldNorm(key));\n        };\n        \n        gE('ti-conf-title').innerText = \"Impostazioni \" + window.currentDittaName + (window.configInstructionCheckActive ? ' - Verifica istruzioni' : ''); window.refreshConfigHeaderButtons(); let html = ``;\n        const visibleTables = Object.keys((dbData && dbData.Tabelle) ? dbData.Tabelle : {}).filter(tName => tName !== 'undefined');\n        visibleTables.forEach(function(tbl){ window.normalizeTableRowsToTemplate(tbl); if (dbData !== window.currentDbData && dbData.Tabelle && window.currentDbData && window.currentDbData.Tabelle && window.currentDbData.Tabelle[tbl]) { dbData.Tabelle[tbl] = JSON.parse(JSON.stringify(window.currentDbData.Tabelle[tbl])); } });\n        if (!visibleTables.length) {\n            html = `<div style=\"padding:18px; color:#ddd; background:#111827; border:1px solid #374151; border-radius:10px;\">Nessuna tabella o riga con campo <b>Istruzioni\/Istruzione<\/b> compilato trovata nell archivio della ditta.<\/div>`;\n            const confCntEmpty = gE('ti-conf-cnt'); if (confCntEmpty) confCntEmpty.innerHTML = html;\n            return;\n        }\n        \n        for(let tName in dbData.Tabelle) {\n            if (tName === 'undefined') continue; let safeCls = tName.replace(\/[^a-zA-Z0-9]\/g, ''); let isOpen = openDetails.includes(tName) ? 'open' : ''; let isInstructionsTable = (tName.toLowerCase() === 'istruzioni'); let isConfigSingleRowTable = (window.isConfigSingleRowTable379 && window.isConfigSingleRowTable379(tName));\n                        if(dbData.Tabelle[tName] && !Array.isArray(dbData.Tabelle[tName])) dbData.Tabelle[tName] = Object.values(dbData.Tabelle[tName]);\n            html += `<details class=\"ti-cfg-details\" data-tbl=\"${window.escapeHtml(tName)}\" style=\"background:#111; margin-bottom:8px; border-radius:8px; border:1px solid #333;\" ${isOpen}><summary class=\"ti-cfg-summary\"><span class=\"ti-cfg-summary-main\">${tName}<\/span><button type=\"button\" class=\"ti-cfg-summary-help\" onclick=\"event.preventDefault();event.stopPropagation();window.askColumnHelp(decodeURIComponent('${jsEsc(tName)}'), '')\">?<\/button><\/summary>`;\n            \n            if(canEditRows) { \n                html += `<div style=\"padding:10px; display:flex; gap:10px; flex-wrap:wrap; background:#1f2937; border-bottom:1px solid #444; border-radius:5px 5px 0 0;\">`;\n                if (!isConfigSingleRowTable) html += `<button type=\"button\" class=\"ti-btn\" onclick=\"window.addTableRow(decodeURIComponent('${jsEsc(tName)}'))\" style=\"background:#2563eb; color:#fff;\">+ Riga<\/button>`;\n                if (isSuperAdmin) {\n                    html += `<button type=\"button\" class=\"ti-btn\" onclick=\"window.addTableCol(decodeURIComponent('${jsEsc(tName)}'))\" style=\"background:#059669; color:#fff;\">+ Colonna<\/button>`;\n                }\n                html += `<button type=\"button\" class=\"ti-btn ti-config-delete-selected-btn\" data-ti-config-delete-selected=\"1\" data-tbl-enc=\"${jsEsc(tName)}\" data-safe-cls=\"${safeCls}\" onclick=\"return window.handleConfigDeleteSelectedClick(event, this)\" style=\"background:#b91c1c; color:#fff;\">\ud83d\uddd1\ufe0f Elimina Sel.<\/button><\/div>`; \n            }\n\n            window.normalizeTableRowsToTemplate(tName);\n            html += `<div class=\"ti-cfg-top-scroll ti-sync-hscroll-top\"><div class=\"ti-cfg-top-scroll-inner ti-sync-hscroll-inner\"><\/div><\/div><div class=\"ti-cfg-table-container ti-sync-hscroll-wrap\"><table class=\"ti-cfg-table\">`; let rows = dbData.Tabelle[tName];\n            if (rows && rows.length > 0 && rows[0]) {\n                let keys = window.getTemplateSchemaKeys(tName).filter(k => k !== 'ID');\n                if (isInstructionsTable) {\n                    const desired = ['Istruzione','Da gg','A gg','Da ora','A ora','Nota','Stato','Immagine','Iva','Data','Ora'];\n                    keys = desired.filter(k => keys.includes(k)).concat(keys.filter(k => !desired.includes(k)));\n                } else if (tName.toLowerCase() === 'informazioni') {\n                    const desired = ['Tipo','Descrizione','Istruzioni','Data','Ora','Nota','Stato','Immagine','Iva'];\n                    keys = desired.filter(k => keys.includes(k)).concat(keys.filter(k => !desired.includes(k)));\n                }\n                if (!window.configTableState[tName]) window.configTableState[tName] = {filters:{}, filterDrafts:{}, sortKey:'', sortDir:'asc'};\n                if (!window.configTableState[tName].filterDrafts) window.configTableState[tName].filterDrafts = {};\n                const tableState = window.configTableState[tName];\n                let rowEntries = rows.map((row, idx) => ({row, idx}));\n                if (\/^attivit[a\u00e0]$\/i.test(String(tName || '')) && !(window.userCanSeeConfigurationActivities && window.userCanSeeConfigurationActivities())) {\n                    rowEntries = rowEntries.filter(function(entry){ return !(window.isConfigurationActivityLike && window.isConfigurationActivityLike(entry.row)); });\n                }\n                rowEntries = rowEntries.filter(entry => keys.every(k => {\n                    const filterVal = ((tableState.filters && tableState.filters[k]) || '').trim();\n                    if (!filterVal) return true;\n                    return window.configFilterMatches(entry.row && entry.row[k] != null ? entry.row[k] : '', filterVal);\n                }));\n                if (!tableState.sortKey) { const defKey = keys.find(k => \/^descrizione$\/i.test(k)) || keys.find(k => \/^(prodotto|servizio|nome|titolo)$\/i.test(k)) || ''; if (defKey) { tableState.sortKey = defKey; tableState.sortDir = 'asc'; } }\n                if (tableState.sortKey) {\n                    const sortKey = tableState.sortKey;\n                    const ascSort = tableState.sortDir !== 'desc';\n                    rowEntries.sort((a, b) => {\n                        let av, bv;\n                        if (sortKey === '__row_index') {\n                            av = {type:'num', value: (parseInt(a.idx, 10) || 0) + 1};\n                            bv = {type:'num', value: (parseInt(b.idx, 10) || 0) + 1};\n                        } else {\n                            av = window.getComparableCellValue({innerText: String(a.row && a.row[sortKey] != null ? a.row[sortKey] : '')});\n                            bv = window.getComparableCellValue({innerText: String(b.row && b.row[sortKey] != null ? b.row[sortKey] : '')});\n                        }\n                        let cmp = 0;\n                        if (av.type === 'num' && bv.type === 'num') cmp = av.value - bv.value;\n                        else cmp = String(av.value).localeCompare(String(bv.value), 'it');\n                        return ascSort ? cmp : -cmp;\n                    });\n                }\n                const rowSortDir = (tableState.sortKey === '__row_index' ? (tableState.sortDir === 'desc' ? '\u25bc' : '\u25b2') : '\u2195');\n                html += `<thead><tr><th class=\"ti-cfg-rownum\" title=\"Numero riga di riferimento\"><span style=\"cursor:pointer;display:inline-block;\" onclick=\"window.sortConfigTable(decodeURIComponent('${jsEsc(tName)}'),'__row_index')\">Riga<br><span style=\"font-size:10px;color:#93c5fd;\">rif.<\/span> <span class=\"ti-cfg-sort-ind\">${rowSortDir}<\/span><\/span><\/th>`;\n                keys.forEach((k, i) => {\n                    let stickyClass = '';\n                    let safeK = jsEsc(k);\n                    let dir = (tableState.sortKey === k ? (tableState.sortDir === 'desc' ? '\u25bc' : '\u25b2') : '\u2195');\n                    let fRaw = (tableState.filterDrafts && Object.prototype.hasOwnProperty.call(tableState.filterDrafts, k)) ? tableState.filterDrafts[k] : ((tableState.filters && tableState.filters[k]) ? tableState.filters[k] : '');\n                    let fVal = String(fRaw || '').replace(\/\"\/g,'&quot;');\n                    html += `<th class=\"${stickyClass}\"><div class=\"ti-cfg-head-label\"><span style=\"cursor:pointer;\" onclick=\"window.sortConfigTable(decodeURIComponent('${jsEsc(tName)}'),decodeURIComponent('${safeK}'))\">${k} <span class=\"ti-cfg-sort-ind\">${dir}<\/span><\/span><button type=\"button\" class=\"ti-cfg-help-btn\" title=\"Spiegazione colonna\" onclick=\"event.stopPropagation();window.askColumnHelp(decodeURIComponent('${jsEsc(tName)}'), decodeURIComponent('${safeK}'))\">?<\/button><\/div><input type=\"text\" class=\"ti-cfg-filter\" data-tbl=\"${window.escapeHtml(tName)}\" data-key=\"${window.escapeHtml(k)}\" value=\"${fVal}\" oninput=\"window.scheduleConfigTableFilter(decodeURIComponent('${jsEsc(tName)}'),decodeURIComponent('${safeK}'), this.value, this)\" placeholder=\"Filtra...\"><\/th>`;\n                });\n                if(canEditRows) html += `<th style=\"background:#991b1b; color:#fff; position:sticky; top:0; right:60px; z-index:20;\">SEL<br><input type=\"checkbox\" onclick=\"window.toggleAllTI(this, '${safeCls}', this)\"><\/th><th style=\"background:#991b1b; color:#fff; position:sticky; top:0; right:0; z-index:20;\">AZIONI<\/th>`;\n                html += `<\/tr><\/thead><tbody>`;\n                rowEntries.forEach((entry) => {\n                    const row = entry.row; const idx = entry.idx;\n                    if(!row) return; \n                    let isUtentiTable = (tName.toLowerCase() === 'utenti'); let rowUser = ''; let rowRole = '';\n                    if (isUtentiTable) {\n                        let kUser = Object.keys(row).find(k => k.toLowerCase() === 'username' || k.toLowerCase() === 'user'); let kRole = Object.keys(row).find(k => k.toLowerCase() === 'ruolo' || k.toLowerCase() === 'role');\n                        if(kUser && row[kUser]) rowUser = String(row[kUser]).trim(); if(kRole && row[kRole]) rowRole = String(row[kRole]).trim();\n                        if ((rowUser.toLowerCase() === 'ssglobaladmin' || rowUser.toLowerCase() === 'targetai') && window.tiUser.toLowerCase() !== rowUser.toLowerCase()) return;\n                        if (!isSuperAdmin && rowRole.toLowerCase().includes('amministratore')) return;\n                    }\n                    html += `<tr data-row-idx=\"${idx}\" data-tbl-enc=\"${jsEsc(tName)}\"><td class=\"ti-cfg-rownum\" title=\"Riga di riferimento ${idx + 1}\">${idx + 1}<\/td>`;\n                    keys.forEach((key, i) => {\n                        let val = row[key]; if (val === undefined || val === null) val = ''; let safeVal = String(val).replace(\/\"\/g, '&quot;');\n                        let stickyClass = ''; let isMod = window.modifiedFields && window.modifiedFields.has(`${tName}-${idx}-${key}`); let bStyle = isMod ? 'border-color:#fbbf24 !important;' : '';\n                        const fieldDesc = window.getConfigFieldDescription ? window.getConfigFieldDescription(tName, key) : '';\n                        const fieldTitle = fieldDesc ? window.escapeHtml(key + ': ' + fieldDesc) : window.escapeHtml(key);\n                        html += `<td class=\"${stickyClass}\" title=\"${fieldTitle}\">`;\n                        let isImg = String(val).match(\/\\.(jpg|jpeg|png|webp|gif)\/i) || String(val).startsWith('http');\n                        let isDataCreazione = (key.toLowerCase() === 'data creazione' || key.toLowerCase() === 'data_creazione');\n                        let isActivationProtectedConfigField = isConfigActivationProtectedField(tName, key);\n                        let readOnlyAttr = (isDataCreazione && !isSuperAdmin) ? 'readonly title=\"Modificabile solo da Amministratore Globale\"' : '';\n                        if (isActivationProtectedConfigField && !isGlobalAdminConfig) readOnlyAttr = 'readonly disabled data-ti-activation-protected=\"1\" title=\"Campo riservato a sistema, globaladmin o attivazione WooCommerce\"';\n                        if (isInstructionsTable && !canEditRows) readOnlyAttr = 'readonly title=\"Tabella modificabile solo da amministratore, configuratore o responsabile\"';\n                        let isAteco = (key.toLowerCase() === 'attivit\u00e0' || key.toLowerCase() === 'attivita' || key.toLowerCase() === 'codice ateco');\n                        let isPassword = (key.toLowerCase() === 'password' || key.toLowerCase() === 'pass');\n                        let isMyRow = false; let isNewRow = false;\n                        if (isUtentiTable) { let kUser = Object.keys(row).find(k => k.toLowerCase() === 'username' || k.toLowerCase() === 'user'); let rU = kUser && row[kUser] ? String(row[kUser]).trim() : ''; isMyRow = (rU.toLowerCase() === window.tiUser.toLowerCase()); isNewRow = (rU === ''); }\n                        \n                        if (key.toLowerCase() === 'logo' || key.toLowerCase() === 'immagine' || key.toLowerCase().includes('file') || key.toLowerCase() === 'foto' || key.toLowerCase() === 'doc' || key.toLowerCase() === 'docs') {\n                            \n                            let fileList = window.splitLinkedFileValues ? window.splitLinkedFileValues(val) : String(val || '').split(',').map(f => f.trim()).filter(f=>f);\n                            let firstLinkedFile = fileList.length ? fileList[0] : '';\n                            let firstLinkedName = firstLinkedFile ? window.getFileNameFromUrlOrValue(firstLinkedFile) : '';\n                            let firstLinkedIsImage = firstLinkedFile && (window.isImageFileValue ? window.isImageFileValue(firstLinkedFile) : \/\\.(jpg|jpeg|png|webp|gif)\/i.test(firstLinkedFile));\n                            let thumbHtml = '';\n                            if (firstLinkedFile && firstLinkedIsImage) {\n                                const recordLabel426 = window.getRecordLabelForUpload(tName, row, tName);\n                                let viewUrl = window.getSafePreviewUrl(firstLinkedFile, gE('ti-ditta').value, tName, key, recordLabel426);\n                                try {\n                                    if (viewUrl && \/ti_action=ti_ai_get_image\/i.test(viewUrl)) {\n                                        const u426 = new URL(viewUrl, window.location.href);\n                                        u426.searchParams.set('ti_cleanup_missing', '1');\n                                        viewUrl = u426.toString();\n                                    }\n                                } catch(_e426) {}\n                                const fbUrl = window.getCompanyFallbackMediaUrl(gE('ti-ditta').value);\n                                thumbHtml = `<img decoding=\"async\" src=\"${window.escapeHtml(viewUrl)}\" data-fallback=\"${window.escapeHtml(fbUrl)}\" data-ti-broken-image-cleanup=\"1\" data-tbl=\"${window.escapeHtml(tName)}\" data-idx=\"${idx}\" data-key=\"${window.escapeHtml(key)}\" data-file=\"${window.escapeHtml(firstLinkedFile)}\" data-record=\"${window.escapeHtml(recordLabel426)}\" onerror=\"if(window.handleConfigImageError426){window.handleConfigImageError426(this);return false;}window.applyFallbackImage(this)\" style=\"width:24px; height:24px; object-fit:cover; border-radius:4px; cursor:pointer; border:1px solid #444;\" onclick=\"window.openFileManager(decodeURIComponent('${jsEsc(tName)}'), ${idx}, decodeURIComponent('${jsEsc(key)}'))\" title=\"Apri Galleria - primo collegato: ${window.escapeHtml(firstLinkedName)}\">`;\n                            } else if (firstLinkedFile) {\n                                const firstIcon = window.getFilePreviewIcon ? window.getFilePreviewIcon(firstLinkedName) : '\ud83d\udcc4';\n                                thumbHtml = `<span style=\"font-size:18px; cursor:pointer;\" onclick=\"window.openFileManager(decodeURIComponent('${jsEsc(tName)}'), ${idx}, decodeURIComponent('${jsEsc(key)}'))\" title=\"Apri Galleria - primo collegato: ${window.escapeHtml(firstLinkedName)}\">${firstIcon}<\/span>`;\n                            } else {\n                                thumbHtml = `<span style=\"font-size:18px; color:#555; cursor:pointer;\" onclick=\"window.openFileManager(decodeURIComponent('${jsEsc(tName)}'), ${idx}, decodeURIComponent('${jsEsc(key)}'))\" title=\"Apri galleria file\">\ud83d\udcc1<\/span>`;\n                            }\n\n                            html += `<div style=\"display:flex; align-items:center; gap:5px;\">\n                                        ${thumbHtml}\n                                        <input type=\"text\" id=\"cfg-img-${tName}-${idx}-${key}\" data-tbl=\"${tName}\" data-idx=\"${idx}\" data-key=\"${key}\" onchange=\"window.updateMemField(decodeURIComponent('${jsEsc(tName)}'), ${idx}, decodeURIComponent('${jsEsc(key)}'), this)\" class=\"ti-in\" style=\"min-width:80px; width:120px; ${bStyle}\" value=\"${safeVal}\" readonly title=\"Clicca su File per gestire\">\n                                        <button type=\"button\" class=\"ti-btn\" onclick=\"window.openFileManager(decodeURIComponent('${jsEsc(tName)}'), ${idx}, decodeURIComponent('${jsEsc(key)}'))\" style=\"padding:4px 6px; font-size:10px; background:#4b5563; white-space:nowrap;\">File (${fileList.length})<\/button>\n                                     <\/div>`;\n\n                        } else { \n                            const relationMeta = window.getConfigRelationMeta(tName, key, row);\n                            if (relationMeta) {\n                                const relOptions = window.getConfigRelationOptions(tName, key, row, idx);\n                                const selectedVals = window.splitConfigRelationValues(val);\n                                const selectedMap = new Set(selectedVals.map(function(v){ return String(v).toLowerCase(); }));\n                                const sourceLabel = window.escapeHtml(relationMeta.sourceLabel || relationMeta.label || relationMeta.table || 'record');\n                                const selectSize = Math.max(1, Math.min(3, relOptions.length || 3));\n                                const optionsHtml = relOptions.map(function(opt){\n                                    const optValue = String(opt && opt.value != null ? opt.value : '').trim();\n                                    const optLower = optValue.toLowerCase();\n                                    const sel = selectedMap.has(optLower) ? ' selected' : '';\n                                    const source = String((opt && opt.sourceTable) || '');\n                                    const suffix = (relationMeta.realTables && relationMeta.realTables.length > 1 && source && source !== 'Valore attuale') ? (' [' + source + ']') : (source === 'Valore attuale' ? ' [attuale]' : '');\n                                    const title = window.escapeHtml((opt && opt.title) ? opt.title : 'Record disponibile.');\n                                    return `<option value=\"${window.escapeHtml(optValue)}\" title=\"${title}\"${sel}>${window.escapeHtml(optValue + suffix)}<\/option>`;\n                                }).join('');\n                                const relSummary = selectedVals.join(', ');\n                                const relHint = relationMeta.multiple ? ('Seleziona una o pi\u00f9 voci disponibili da ' + sourceLabel) : ('Seleziona una voce disponibile da ' + sourceLabel);\n                                if (relationMeta.multiple) {\n                                    html += `<div style=\"display:flex; flex-direction:column; gap:5px; min-width:230px;\">\n                                        <div style=\"font-size:11px; color:#93c5fd;\">${relHint}<\/div>\n                                        <select multiple size=\"${selectSize}\" id=\"cfg-rel-${tName}-${idx}-${key}\" data-tbl=\"${tName}\" data-idx=\"${idx}\" data-key=\"${key}\" onchange=\"window.updateMemFieldFromRelationSelect(decodeURIComponent('${jsEsc(tName)}'), ${idx}, decodeURIComponent('${jsEsc(key)}'), this)\" class=\"ti-in ti-rel-select\" title=\"${fieldTitle}\" style=\"min-width:230px; max-height:92px; ${bStyle}\" ${readOnlyAttr}>${optionsHtml}<\/select>\n                                        <div class=\"ti-rel-summary\" style=\"font-size:11px; color:#cbd5e1; white-space:normal;\">${window.escapeHtml(relSummary || 'Nessun record associato')}<\/div>\n                                    <\/div>`;\n                                } else {\n                                    const blankSel = selectedVals.length ? '' : ' selected';\n                                    html += `<div style=\"display:flex; flex-direction:column; gap:5px; min-width:210px;\">\n                                        <div style=\"font-size:11px; color:#93c5fd;\">${relHint}<\/div>\n                                        <select id=\"cfg-rel-${tName}-${idx}-${key}\" data-tbl=\"${tName}\" data-idx=\"${idx}\" data-key=\"${key}\" onchange=\"window.updateMemFieldFromRelationSelect(decodeURIComponent('${jsEsc(tName)}'), ${idx}, decodeURIComponent('${jsEsc(key)}'), this)\" class=\"ti-in ti-rel-select\" title=\"${fieldTitle}\" style=\"min-width:210px; ${bStyle}\" ${readOnlyAttr}><option value=\"\"${blankSel}>-- nessuno --<\/option>${optionsHtml}<\/select>\n                                        <div class=\"ti-rel-summary\" style=\"font-size:11px; color:#cbd5e1; white-space:normal;\">${window.escapeHtml(relSummary || 'Nessun record associato')}<\/div>\n                                    <\/div>`;\n                                }\n                            } else if (isPassword && isUtentiTable && !isMyRow && !isNewRow) {\n                                html += `<div style=\"display:flex; align-items:center; gap:5px;\"><input type=\"password\" id=\"cfg-txt-${tName}-${idx}-${key}\" data-tbl=\"${tName}\" data-idx=\"${idx}\" data-key=\"${key}\" onchange=\"if(this.value.trim()!=='') window.updateMemField(decodeURIComponent('${jsEsc(tName)}'), ${idx}, decodeURIComponent('${jsEsc(key)}'), this)\" class=\"ti-in\" title=\"${fieldTitle}\" style=\"min-width:100px; ${bStyle}\" placeholder=\"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\"><\/div>`;\n                            } else {\n                                let isLong = String(val).length > 40 || key.toLowerCase().includes('descrizione') || key.toLowerCase().includes('note');\n                                if(isLong && !isDataCreazione && !isPassword) { html += `<div style=\"display:flex; align-items:center; gap:5px;\"><textarea id=\"cfg-txt-${tName}-${idx}-${key}\" data-tbl=\"${tName}\" data-idx=\"${idx}\" data-key=\"${key}\" onchange=\"window.updateMemField(decodeURIComponent('${jsEsc(tName)}'), ${idx}, decodeURIComponent('${jsEsc(key)}'), this)\" class=\"ti-in\" title=\"${fieldTitle}\" style=\"min-width:200px; height:40px; resize:vertical; ${bStyle}\" ${readOnlyAttr}>${safeVal}<\/textarea><\/div>`; } else {\n                                    const isDateLikeField = window.isDateFieldName(key);\n                                    const isTimeLikeField = window.isTimeFieldName(key);\n                                    let inpType = isPassword ? 'text' : (\/\\b(email|mail|pec)\\b\/i.test(String(key)) ? 'email' : 'text'); \n                                    let inputVal = safeVal;\n                                    let inputTitle = fieldTitle;\n                                    let inputPlaceholder = '';\n                                    if (isDateLikeField) { inputPlaceholder = 'DD\/MM\/YYYY o testo libero'; inputTitle = fieldTitle + ' - Puoi inserire una data precisa o testo libero da interpretare successivamente.'; }\n                                    if (isTimeLikeField) { inputPlaceholder = 'HH:MM o testo libero'; inputTitle = fieldTitle + ' - Puoi inserire un orario preciso o testo libero da interpretare successivamente.'; }\n                                    if ((isDateLikeField || isTimeLikeField) && (\/^D{2}\\\/M{2}\\\/Y{4}$\/i.test(String(val||'')) || \/^H{2}:M{2}$\/i.test(String(val||'')))) inputVal = ''; \n                                    html += `<div style=\"display:flex; align-items:center; gap:5px;\"><input type=\"${inpType}\" id=\"cfg-txt-${tName}-${idx}-${key}\" data-tbl=\"${tName}\" data-idx=\"${idx}\" data-key=\"${key}\" onchange=\"window.updateMemField(decodeURIComponent('${jsEsc(tName)}'), ${idx}, decodeURIComponent('${jsEsc(key)}'), this)\" class=\"ti-in ti-date-aware\" title=\"${window.escapeHtml(inputTitle)}\" placeholder=\"${window.escapeHtml(inputPlaceholder)}\" style=\"min-width:130px; ${bStyle}\" value=\"${String(inputVal).replace(\/\"\/g,'&quot;')}\" ${readOnlyAttr}>`;\n                                    if (isDateLikeField && !isActivationProtectedConfigField) html += `<button type=\"button\" class=\"ti-btn\" title=\"Inserisci data di oggi\" onclick=\"const d=new Date(); const v=String(d.getDate()).padStart(2,'0')+'\/'+String(d.getMonth()+1).padStart(2,'0')+'\/'+d.getFullYear(); this.previousElementSibling.value=v; this.previousElementSibling.dispatchEvent(new Event('change'));\" style=\"padding:4px 6px; font-size:11px; background:#374151;\">\ud83d\udcc5<\/button>`;\n                                    if (isAteco) html += `<button type=\"button\" class=\"ti-btn\" onclick=\"window.openAtecoSearch('cfg-txt-${tName}-${idx}-${key}')\" style=\"padding:4px; font-size:10px; background:#2563eb;\">\ud83d\udd0d<\/button>`;\n                                    html += `<\/div>`;\n                                }\n                            }\n                        }\n                        html += `<\/td>`;\n                    });\n                    if(canEditRows) { \n                        html += `<td style=\"padding:5px; text-align:center; background:#111827; position:sticky; right:60px; border-left:1px solid #374151; z-index:5;\"><input type=\"checkbox\" class=\"ti-config-row-select ti-cb-${safeCls}\" data-tbl=\"${window.escapeHtml(tName)}\" data-row-idx=\"${idx}\" value=\"${idx}\"><\/td>\n                        <td style=\"padding:5px; text-align:center; background:#111827; position:sticky; right:0; border-left:1px solid #374151; z-index:5; display:flex; gap:4px;\">\n                            ${isConfigSingleRowTable ? '' : `<button type=\"button\" class=\"ti-btn\" onclick=\"window.duplicateTableRow(decodeURIComponent('${jsEsc(tName)}'), ${idx})\" title=\"Duplica riga\" style=\"background:#10b981;color:#fff;padding:4px 6px;\">\ud83d\udcc4<\/button>`}\n                            <button type=\"button\" class=\"ti-btn ti-config-delete-row-btn\" data-ti-config-delete-row=\"1\" data-tbl-enc=\"${jsEsc(tName)}\" data-row-idx=\"${idx}\" onclick=\"return window.handleConfigDeleteRowClick(event, this)\" title=\"Elimina riga\" style=\"background:#ef4444;color:#fff;padding:4px 6px;\">\u274c<\/button>\n                        <\/td>`; \n                    }\n                    html += `<\/tr>`;\n                }); html += `<\/tbody>`;\n            } else html += `<tr><td style=\"padding:10px; color:#aaa;\">${isConfigSingleRowTable ? 'Nessun record Config disponibile. La tabella Config \u00e8 a riga unica e non consente aggiunta righe manuali.' : 'Nessun record. Usa il pulsante + Riga per iniziare.'}<\/td><\/tr>`; \n            html += `<\/table><\/div><div class=\"ti-cfg-bottom-scroll ti-sync-hscroll-bottom\"><div class=\"ti-cfg-top-scroll-inner ti-sync-hscroll-inner\"><\/div><\/div><\/details>`;\n        }\n        html += `<div style=\"margin-top:18px;padding:14px;border:1px solid #374151;border-radius:10px;background:#111827;\">\n            <h4 style=\"margin:0 0 12px 0;color:#fff;font-size:19px;line-height:1.25;\">\ud83e\udd16 Istruzioni AI configurazione<\/h4>\n            <div class=\"ti-conf-ai-desc-label\" style=\"font-size:15px;line-height:1.45;color:#cbd5e1;margin-bottom:10px;font-weight:600;\">Fornisci istruzioni di configurazione o ottieni indicazioni tramite AI. Esempio: <i>Aggiungere foto AI a tutti i prodotti<\/i> o <i>fornisci consigli di configurazione<\/i><\/div>\n            <textarea id=\"ti-conf-ai-prompt\" class=\"ti-in\" rows=\"4\" placeholder=\"Fornisci istruzioni di configurazione o ottieni indicazioni tramite AI. Esempio: Aggiungere foto AI a tutti i prodotti o fornisci consigli di configurazione\"><\/textarea>\n            <div id=\"ti-conf-ai-chat-actions\" style=\"display:flex;gap:8px;flex-wrap:wrap;margin-top:10px;margin-bottom:10px;padding:10px;border:1px solid #334155;border-radius:10px;background:#0b1220;\">\n                <button type=\"button\" id=\"ti-conf-ai-send\" class=\"ti-btn\" style=\"background:#7c3aed;color:#fff;\" onclick=\"window.runConfigBot()\">Invia istruzione<\/button>\n                <button type=\"button\" class=\"ti-btn\" style=\"background:#2563eb;color:#fff;\" onclick=\"window.verifyInstructionsArchive()\">\ud83d\udd0e Verifica istruzioni<\/button>\n                <button type=\"button\" class=\"ti-btn\" style=\"background:#2563eb;color:#fff;\" onclick=\"window.pickConfigBotFile()\">Carica file per AI<\/button>\n                <button type=\"button\" class=\"ti-btn\" style=\"background:#1d4ed8;color:#fff;\" onclick=\"window.openActivityReport()\">Report Attivit\u00e0<\/button>\n                <button type=\"button\" class=\"ti-btn\" onclick=\"window.clearConfigBot()\">Pulisci<\/button>\n            <\/div>\n            <div id=\"ti-conf-ai-file\" style=\"display:none;margin-top:10px;margin-bottom:10px;font-size:14px;line-height:1.45;color:#bfdbfe;background:#0b1220;padding:10px 12px;border-radius:8px;border:1px solid #334155;\"><\/div>\n            <div id=\"ti-conf-ai-reply\" style=\"display:none;margin-top:10px;background:#000;padding:10px;border-radius:8px;border:1px solid #374151;color:#e5e7eb;white-space:pre-wrap;\"><\/div>\n            <div class=\"ti-conf-ai-desc-label\" style=\"font-size:13px;line-height:1.45;color:#9ca3af;margin-top:8px;margin-bottom:10px;font-weight:500;\">La AI usa il database della ditta corrente e, solo in forma aggregata\/non sensibile, esperienze coerenti di altre ditte. Non vengono riportati dati di dettaglio di altre ditte.<\/div>\n            <div class=\"ti-conf-ai-desc-label\" style=\"font-size:15px;line-height:1.45;color:#cbd5e1;margin-top:10px;margin-bottom:10px;font-weight:600;\">Per le funzioni <b>Genera foto AI<\/b> puoi indicare eventuali istruzioni di dettaglio per ottenere immagini piu coerenti con le intenzioni dell utente. Esempio: <i>sfondo chiaro, stile elegante, foto ravvicinata, ambientazione realistica<\/i>.<\/div>\n            <textarea id=\"ti-conf-ai-photo-detail\" class=\"ti-in\" rows=\"3\" placeholder=\"Istruzioni di dettaglio per la generazione foto AI (opzionale)\"><\/textarea>\n            <div id=\"ti-conf-ai-image-actions\" style=\"display:flex;gap:8px;flex-wrap:wrap;margin-top:10px;\">\n                <button type=\"button\" class=\"ti-btn\" style=\"background:#059669;color:#fff;\" onclick=\"window.runBatchAIGen('Prodotti')\">Genera foto AI Prodotti<\/button>\n                <button type=\"button\" class=\"ti-btn\" style=\"background:#0f766e;color:#fff;\" onclick=\"window.runBatchAIGen('Servizi')\">Genera foto AI Servizi<\/button>\n            <\/div>\n            <div id=\"ti-configurator-tips-control\" style=\"${(window.isConfiguratorUser && window.isConfiguratorUser()) ? 'display:flex' : 'display:none'};gap:10px;align-items:center;justify-content:space-between;flex-wrap:wrap;margin-top:12px;padding:10px;border:1px solid #334155;border-radius:10px;background:#0f172a;color:#cbd5e1;font-size:12px;\">\n                <label style=\"display:flex;align-items:center;gap:7px;cursor:pointer;\"><input type=\"checkbox\" id=\"ti-cfg-tips-enabled\" onchange=\"window.handleConfiguratorTipsFlagChange(this)\"> Mostra popup <b>Consigli operativi per configuratore\/amministratore<\/b> ai prossimi accessi<\/label>\n                <span id=\"ti-cfg-tips-status\" style=\"color:#93c5fd;\">Popup consigli operativi attivo.<\/span>\n                <button type=\"button\" class=\"ti-btn\" style=\"background:#475569;color:#fff;font-size:12px;padding:7px 10px;\" onclick=\"window.showConfiguratorOperationalTipsPopup(true)\">Apri consigli ora<\/button>\n            <\/div>\n        <\/div>`;\n        const confCnt = gE('ti-conf-cnt'); if (confCnt) { confCnt.innerHTML = html; window.__ensurePluginDateInputs(); window.initPluginDateInputs(confCnt); window.initConfigTopScrollbars(confCnt); window.bindConfigTableUX(confCnt); if (!window.__configRenderedOnce || window.configForceTop) { confCnt.scrollTop = 0; document.querySelectorAll('.ti-cfg-table-container').forEach(el => { el.scrollTop = 0; el.scrollLeft = 0; }); document.querySelectorAll('.ti-cfg-top-scroll, .ti-cfg-bottom-scroll').forEach(el => { el.scrollLeft = 0; }); window.__configRenderedOnce = true; window.configForceTop = false; } else { try { confCnt.scrollTop = cfgScrollState.mainTop || 0; document.querySelectorAll('details.ti-cfg-details').forEach(function(det){ const tbl = det.dataset ? (det.dataset.tbl || '') : ''; const st = cfgScrollState.tables && cfgScrollState.tables[tbl]; const wrap = det.querySelector ? det.querySelector('.ti-cfg-table-container') : null; if (st && wrap) { wrap.scrollLeft = st.left || 0; wrap.scrollTop = st.top || 0; const topBar = wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains('ti-sync-hscroll-top') ? wrap.previousElementSibling : null; const bottomBar = wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains('ti-sync-hscroll-bottom') ? wrap.nextElementSibling : null; if (topBar) topBar.scrollLeft = wrap.scrollLeft; if (bottomBar) bottomBar.scrollLeft = wrap.scrollLeft; } }); } catch(e) {} } if (window.restoreConfigFilterFocus) setTimeout(window.restoreConfigFilterFocus, 0); if (window.syncConfiguratorOperationalTipsFlagUI) setTimeout(window.syncConfiguratorOperationalTipsFlagUI, 0); }\n        if (window.keepUserCommunicationPopupsInFront) window.keepUserCommunicationPopupsInFront();\n        if (window.refreshMaintenanceButton) window.refreshMaintenanceButton();\n    };\n\n\n    window.isValidConfigDate = function(v) { const s = String(v == null ? '' : v).trim(); if (!s || \/^DD\\\/MM\\\/YYYY$\/i.test(s) || s === '--\/--\/----') return true; const m = s.match(\/^(\\d{1,2})\\\/(\\d{1,2})\\\/(\\d{4})$\/); if (m) { const d = new Date(Number(m[3]), Number(m[2])-1, Number(m[1])); return d && d.getFullYear()===Number(m[3]) && d.getMonth()===Number(m[2])-1 && d.getDate()===Number(m[1]); } if (\/^\\d{1,2}[\\\/.-]\\d{1,2}[\\\/.-]\\d{2,4}$\/.test(s)) return false; return true; };\n    window.isValidConfigTime = function(v) { const s = String(v == null ? '' : v).trim(); if (!s || \/^HH:MM$\/i.test(s) || s === '--:--') return true; const m = s.match(\/^(\\d{1,2})[:.](\\d{2})$\/); if (m) return Number(m[1]) >= 0 && Number(m[1]) <= 23 && Number(m[2]) >= 0 && Number(m[2]) <= 59; return true; };\n    window.isValidConfigNumber = function(v) { const s = String(v == null ? '' : v).trim().replace(\/[\u20ac\\s]\/g,'').replace(',', '.'); return !s || !isNaN(Number(s)); };\n    window.isEmptyOrStandardDayConfigTime = function(v) { const s = String(v == null ? '' : v).trim(); return !s || \/^HH:MM$\/i.test(s) || s === '--:--'; };\n    window.isEmptyConfigPeriodBoundary = function(v, kind) { const s = String(v == null ? '' : v).trim(); if (!s) return true; if (kind === 'ora' && (\/^HH:MM$\/i.test(s) || s === '--:--')) return true; if (kind === 'data' && (\/^DD\\\/MM\\\/YYYY$\/i.test(s) || s === '--\/--\/----')) return true; return false; };\n    window.getConfigPeriodPairMeta = function(key) {\n        const n = window.normalizeConfigRelationName ? window.normalizeConfigRelationName(key) : String(key || '').toLowerCase();\n        const c = String(n || '').replace(\/[^a-z0-9]+\/g, '');\n        const dateStart = ['dagg','dadata','datada','datainizio','inizioperiodo','iniziovalidita','dal'];\n        const dateEnd = ['agg','adata','dataa','datafine','fineperiodo','finevalidita','al'];\n        const timeStart = ['daora','orainizio','inizioora','orarioda','dalleore','dalle'];\n        const timeEnd = ['aora','orafine','fineora','orarioa','alleore','alle'];\n        if (dateStart.includes(c)) return {kind:'data', side:'start', counter:['agg','a gg','a data','data a','data fine','fine periodo','fine validita','al']};\n        if (dateEnd.includes(c)) return {kind:'data', side:'end', counter:['dagg','da gg','da data','data da','data inizio','inizio periodo','inizio validita','dal']};\n        if (timeStart.includes(c)) return {kind:'ora', side:'start', counter:['aora','a ora','ora fine','fine ora','orario a','alle ore','alle']};\n        if (timeEnd.includes(c)) return {kind:'ora', side:'end', counter:['daora','da ora','ora inizio','inizio ora','orario da','dalle ore','dalle']};\n        return null;\n    };\n    window.getConfigPeriodCounterpartKey = function(row, meta) {\n        if (!row || !meta) return '';\n        const counters = (meta.counter || []).map(x => String(window.normalizeConfigRelationName ? window.normalizeConfigRelationName(x) : x).replace(\/[^a-z0-9]+\/g, ''));\n        for (const k in row) { const c = String(window.normalizeConfigRelationName ? window.normalizeConfigRelationName(k) : k).replace(\/[^a-z0-9]+\/g, ''); if (counters.includes(c)) return k; }\n        return '';\n    };\n    window.getConfigEmptyTimeWarnings = function() {\n        if (!window.currentDbData || !window.currentDbData.Tabelle) return '';\n        const labels = [];\n        for (const tName in window.currentDbData.Tabelle) {\n            const rows = Array.isArray(window.currentDbData.Tabelle[tName]) ? window.currentDbData.Tabelle[tName] : Object.values(window.currentDbData.Tabelle[tName] || {});\n            for (let i = 0; i < rows.length; i++) {\n                const row = rows[i] || {};\n                for (const key in row) {\n                    const val = row[key];\n                    if (val && typeof val === 'object') continue;\n                    const n = window.normalizeConfigRelationName ? window.normalizeConfigRelationName(key) : String(key || '').toLowerCase();\n                    const isHourlyAmount = \/(^| )(costo|prezzo|tariffa|importo) orari[oa]( |$)\/.test(n)\n                        || \/(^| )(costo|prezzo|tariffa|importo) ora( |$)\/.test(n)\n                        || \/(^| )(costo|prezzo|tariffa|importo) per ora( |$)\/.test(n);\n                    const isTime = !isHourlyAmount && (\/(^| )(ora|orario)( |$)\/.test(n) || n.includes('ora appuntamento') || n.includes('ora variazione'));\n                    if (isTime && window.isEmptyOrStandardDayConfigTime(val)) labels.push('tabella ' + tName + ', riga ' + (i + 1) + ', campo ' + key);\n                }\n            }\n        }\n        if (!labels.length) return '';\n        const shown = labels.slice(0, 30).map(function(label){ return '<li>' + window.escapeHtml(label) + '<\/li>'; }).join('');\n        const more = labels.length > 30 ? '<li>... altri ' + (labels.length - 30) + ' campi orari<\/li>' : '';\n        return '<div style=\"text-align:left;line-height:1.5;max-width:720px;margin:0 auto;\">'\n            + '<div style=\"font-weight:800;margin-bottom:8px;\">Seguono informazioni su campi non necessari, ma che sarebbe utile compilare.<\/div>'\n            + '<div style=\"font-weight:800;color:#fed7aa;margin:8px 0 6px 0;\">Avviso campi orari vuoti o --:--<\/div>'\n            + '<ul style=\"margin:6px 0 12px 20px;padding:0;text-align:left;\">' + shown + more + '<\/ul>'\n            + '<div style=\"margin-top:10px;\">Orario eventualmente riportato in note config, informazioni o istruzioni.<\/div>'\n            + '<div>In mancanza viene considerato l orario standard <b>09:00 - 18:00<\/b>.<\/div>'\n            + '<div style=\"margin-top:8px;\">Verificare direttamente con la ditta la disponibilita specifica.<\/div>'\n            + '<\/div>';\n\n    };\n    window.extractConfigWarningTarget = function(issueOrMessage) {\n        const issue = (issueOrMessage && typeof issueOrMessage === 'object') ? issueOrMessage : {message:String(issueOrMessage || '')};\n        const msg = String(issue.message || issue.msg || issueOrMessage || '');\n        let table = String(issue.table || '');\n        let row = (typeof issue.row !== 'undefined') ? parseInt(issue.row, 10) : 0;\n        let field = String(issue.field || '');\n        if (!table) {\n            const mt = msg.match(\/tabella\\s+([^,\\.]+)\/i);\n            if (mt && mt[1]) table = mt[1].trim();\n        }\n        if (!row) {\n            const mr = msg.match(\/riga\\s+(\\d+)\/i);\n            if (mr && mr[1]) row = parseInt(mr[1], 10) || 0;\n        }\n        if (!field) {\n            const mf = msg.match(\/campo\\s+([^\\.]+?)(?:\\.|$)\/i);\n            if (mf && mf[1]) field = mf[1].trim();\n        }\n        return {table:table, row:row, field:field, message:msg};\n    };\n\n    window.openConfigTableForWarning = function(tableName, rowNum, fieldName) {\n        const tbl = String(tableName || '').trim();\n        if (!tbl) return false;\n        try { window.closeModal && window.closeModal('ti-alert-ov'); } catch(e) {}\n        if (window.openModal) window.openModal('ti-conf-ov');\n        if (window.renderConfig) window.renderConfig();\n        let det = null;\n        document.querySelectorAll('details.ti-cfg-details').forEach(function(d){\n            if (!det && d.dataset && String(d.dataset.tbl || '') === tbl) det = d;\n        });\n        if (!det) {\n            const nt = window.normalizeConfigRelationName ? window.normalizeConfigRelationName(tbl) : tbl.toLowerCase();\n            document.querySelectorAll('details.ti-cfg-details').forEach(function(d){\n                const dt = d && d.dataset ? String(d.dataset.tbl || '') : '';\n                const nd = window.normalizeConfigRelationName ? window.normalizeConfigRelationName(dt) : dt.toLowerCase();\n                if (!det && nd === nt) det = d;\n            });\n        }\n        if (!det) { window.tiAlert && window.tiAlert('Tabella da modificare non trovata: ' + window.escapeHtml(tbl)); return false; }\n        det.open = true;\n        det.style.boxShadow = '0 0 0 3px rgba(251,191,36,.9)';\n        setTimeout(function(){ det.style.boxShadow = ''; }, 4000);\n        let target = det;\n        const wantedIdx = rowNum ? (parseInt(rowNum, 10) - 1) : -1;\n        const wantedField = String(fieldName || '').trim();\n        if (wantedIdx >= 0) {\n            const inputs = det.querySelectorAll('[data-idx]');\n            inputs.forEach(function(el){\n                if (target !== det) return;\n                const idxOk = parseInt(el.getAttribute('data-idx') || '-1', 10) === wantedIdx;\n                const keyOk = !wantedField || String(el.getAttribute('data-key') || '').toLowerCase() === wantedField.toLowerCase();\n                if (idxOk && keyOk) target = el;\n            });\n            if (target === det) {\n                const rowEl = det.querySelector('tr[data-row-idx=\"' + wantedIdx + '\"]');\n                if (rowEl) target = rowEl;\n            }\n        }\n        setTimeout(function(){\n            try { target.scrollIntoView({behavior:'smooth', block:'center', inline:'center'}); } catch(e) { target.scrollIntoView && target.scrollIntoView(); }\n            if (target && target.focus) target.focus({preventScroll:true});\n            if (target && target.style) {\n                target.style.setProperty('outline', '3px solid #fbbf24', 'important');\n                target.style.setProperty('outline-offset', '2px', 'important');\n                setTimeout(function(){ target.style.removeProperty('outline'); target.style.removeProperty('outline-offset'); }, 5000);\n            }\n        }, 120);\n        return true;\n    };\n\n    window.showConfigValidationWarning = function(issues, partialSaveMode) {\n        const arr = Array.isArray(issues) ? issues : (issues ? [issues] : []);\n        if (!arr.length) return;\n        const first = window.extractConfigWarningTarget(arr[0]);\n        const shown = arr.slice(0, 6).map(function(it){\n            const t = window.extractConfigWarningTarget(it);\n            return '<li>' + window.escapeHtml(t.message || String(it || 'Warning configurazione')) + '<\/li>';\n        }).join('');\n        const more = arr.length > 6 ? '<li>... altri ' + (arr.length - 6) + ' warning<\/li>' : '';\n        const encTable = encodeURIComponent(first.table || '');\n        const encField = encodeURIComponent(first.field || '');\n        const btn = first.table ? '<br><button type=\"button\" class=\"ti-btn\" style=\"margin-top:10px;background:#2563eb;color:#fff;\" onclick=\"window.openConfigTableForWarning(decodeURIComponent(\\'' + encTable + '\\'),' + String(first.row || 0) + ',decodeURIComponent(\\'' + encField + '\\'))\">Apri tabella ' + window.escapeHtml(first.table) + '<\/button>' : '';\n        const intro = partialSaveMode ? 'Le tabelle valide sono state salvate o verranno salvate. La tabella indicata dal warning resta da correggere e non viene sovrascritta.' : 'Correggi il valore indicato prima di salvare quella tabella.';\n        window.tiAlert('<div style=\"text-align:left;\"><b>Warning configurazione<\/b><br>' + window.escapeHtml(intro) + '<ul style=\"margin-top:8px;padding-left:18px;\">' + shown + more + '<\/ul>' + btn + '<\/div>');\n    };\n\n    window.collectConfigValidationIssues = function() {\n        const issues = [];\n        if (!window.currentDbData || !window.currentDbData.Tabelle) return issues;\n        const allowedState = \/^(attivo|operativo|demo|configurazione|sospesa|cancellato)?$\/i;\n        const pushIssue = function(tName, rowNumber, key, message) {\n            issues.push({table:String(tName || ''), row:rowNumber || 0, field:String(key || ''), message:String(message || '')});\n        };\n        if (window.normalizeAllConfigStateValues) window.normalizeAllConfigStateValues();\n        window.configTimeFallbackWarning = window.getConfigEmptyTimeWarnings ? window.getConfigEmptyTimeWarnings() : '';\n        for (const tName in window.currentDbData.Tabelle) {\n            const rows = Array.isArray(window.currentDbData.Tabelle[tName]) ? window.currentDbData.Tabelle[tName] : Object.values(window.currentDbData.Tabelle[tName] || {});\n            for (let i = 0; i < rows.length; i++) {\n                const row = rows[i] || {};\n                for (const key in row) {\n                    const val = row[key];\n                    if (val && typeof val === 'object') continue;\n                    const n = window.normalizeConfigRelationName ? window.normalizeConfigRelationName(key) : String(key || '').toLowerCase();\n                    const label = `tabella ${tName}, riga ${i + 1}, campo ${key}`;\n                    const periodMeta = window.getConfigPeriodPairMeta ? window.getConfigPeriodPairMeta(key) : null;\n                    if (periodMeta) {\n                        const counterKey = window.getConfigPeriodCounterpartKey ? window.getConfigPeriodCounterpartKey(row, periodMeta) : '';\n                        if (!counterKey) {\n                            const needed = periodMeta.kind === 'data' ? (periodMeta.side === 'start' ? 'A data \/ A gg' : 'Da data \/ Da gg') : (periodMeta.side === 'start' ? 'A ora' : 'Da ora');\n                            pushIssue(tName, i + 1, key, 'Periodo non completo in ' + label + '. I periodi devono avere due campi: da data a data e\/o da ora ad ora. Campo richiesto: ' + needed + '.');\n                            continue;\n                        }\n                        const thisEmpty = window.isEmptyConfigPeriodBoundary ? window.isEmptyConfigPeriodBoundary(val, periodMeta.kind) : !String(val || '').trim();\n                        const otherEmpty = window.isEmptyConfigPeriodBoundary ? window.isEmptyConfigPeriodBoundary(row[counterKey], periodMeta.kind) : !String(row[counterKey] || '').trim();\n                        if (thisEmpty !== otherEmpty) {\n                            pushIssue(tName, i + 1, key, 'Periodo non completo in tabella ' + tName + ', riga ' + (i + 1) + '. Compila entrambi i campi collegati: ' + key + ' e ' + counterKey + ', oppure lascia entrambi vuoti. Puoi indicare anche testo libero, che verra interpretato successivamente.');\n                            continue;\n                        }\n                    }\n                    const isDate = \/(^| )(data|gg)( |$)\/.test(n) || n.includes('data appuntamento') || n.includes('data variazione');\n                    const isHourlyAmount = \/(^| )(costo|prezzo|tariffa|importo) orari[oa]( |$)\/.test(n)\n                        || \/(^| )(costo|prezzo|tariffa|importo) ora( |$)\/.test(n)\n                        || \/(^| )(costo|prezzo|tariffa|importo) per ora( |$)\/.test(n);\n                    const isTime = !isHourlyAmount && (\/(^| )(ora|orario)( |$)\/.test(n) || n.includes('ora appuntamento') || n.includes('ora variazione'));\n                    if (isDate && !window.isValidConfigDate(val)) { pushIssue(tName, i + 1, key, 'Formato data non valido in ' + label + '. Usa DD\/MM\/YYYY per una data precisa, oppure testo libero descrittivo da interpretare successivamente.'); continue; }\n                    if (isTime && !window.isValidConfigTime(val)) { pushIssue(tName, i + 1, key, 'Formato ora non valido in ' + label + '. Usa HH:MM per un orario preciso, oppure testo libero descrittivo da interpretare successivamente.'); continue; }\n                    if (isHourlyAmount || \/^(prezzo|costo|iva vat|iva|quantita|num ordine|num fattura|num corrispettivo)$\/.test(n) || \/(^| )(prezzo|costo|iva vat|quantita|num ordine|num fattura|num corrispettivo)( |$)\/.test(n)) {\n                        if (!window.isValidConfigNumber(val)) { pushIssue(tName, i + 1, key, 'Valore numerico non valido in ' + label + '. Usa un numero.'); continue; }\n                    }\n                    if (n === 'stato' || n === 'status') {\n                        const canonState = window.normalizeConfigStateValue ? window.normalizeConfigStateValue(val) : String(val || '').trim();\n                        if (canonState && ['Attivo','Operativo','Demo','Configurazione','Sospesa','Cancellato'].includes(canonState)) row[key] = canonState;\n                        if (!allowedState.test(String(row[key] || '').trim())) { pushIssue(tName, i + 1, key, 'Stato non valido in ' + label + '. Usa Attivo, Operativo, Demo, Configurazione, Sospesa o Cancellato.'); continue; }\n                    }\n                }\n            }\n        }\n        return issues;\n    };\n\n    window.validateConfigValues = function() {\n        const issues = window.collectConfigValidationIssues ? window.collectConfigValidationIssues() : [];\n        if (issues.length) { window.showConfigValidationWarning && window.showConfigValidationWarning(issues, false); return false; }\n        return true;\n    };\n\n    window.saveConfig = function() {\n        if (window.__saveConfigInFlight) return;\n        if(!window.currentDbData) return;\n        if (window.syncVisibleConfigInputsToMemory) window.syncVisibleConfigInputsToMemory();\n        for(let t in window.currentDbData.Tabelle) { if(!Array.isArray(window.currentDbData.Tabelle[t])) window.currentDbData.Tabelle[t] = Object.values(window.currentDbData.Tabelle[t]); }\n        if (window.normalizeAllConfigStateValues) window.normalizeAllConfigStateValues();\n        if (window.validateConfigUniqueUsernames && !window.validateConfigUniqueUsernames()) return;\n        const configValidationIssues = window.collectConfigValidationIssues ? window.collectConfigValidationIssues() : [];\n        if (configValidationIssues.length) {\n            window.tiConfigValidationIssues = configValidationIssues;\n            if (window.showConfigValidationWarning) window.showConfigValidationWarning(configValidationIssues, true);\n        }\n        const btn = gE('lbl-conf-save');\n        const oldText = btn ? btn.innerText : 'Salva Modifiche';\n        const oldBg = btn ? btn.style.background : '';\n        const restoreBtn = function(renderAfter){\n            window.__saveConfigInFlight = false;\n            if (btn) {\n                btn.disabled = false;\n                btn.style.pointerEvents = '';\n                btn.innerText = oldText;\n                btn.style.background = oldBg;\n            }\n            if (renderAfter) {\n                window.renderConfig();\n                window.keepPluginModalInFront('ti-conf-ov');\n                window.keepUserCommunicationPopupsInFront();\n            }\n        };\n        window.__saveConfigInFlight = true;\n        if (btn) {\n            btn.disabled = true;\n            btn.style.pointerEvents = 'none';\n            btn.innerText = 'Salvataggio...';\n            btn.style.background = '#d97706';\n        }\n        if (window.refreshConfigDeletedTableSnapshotsFromCurrentData) window.refreshConfigDeletedTableSnapshotsFromCurrentData();\n        const pendingDeletedManifest = JSON.parse(JSON.stringify(window.tiConfigDeletedRowsManifest || []));\n        const pendingDeletedSnapshots = JSON.parse(JSON.stringify(window.tiConfigDeletedTableSnapshots || {}));\n        const pendingDeletedClientTotal = Math.max(parseInt(window.tiConfigDeletedRowsCountTotal || 0, 10) || 0, pendingDeletedManifest.length || 0);\n        const pendingDefinitiveDelete = pendingDeletedClientTotal > 0 || pendingDeletedManifest.length > 0 || Object.keys(pendingDeletedSnapshots || {}).length > 0;\n        const postConfigForm = function(fd, customSignal) {\n            return fetch(window.tiUrl, {method:'POST', body:fd, signal: customSignal || undefined, credentials:'same-origin', cache:'no-store'}).then(function(r){\n                if (r.status === 413) throw new Error('Errore 413: Limite superato.');\n                if (!r.ok) throw new Error('Errore Server ' + r.status);\n                return r.json();\n            });\n        };\n        const dbName = gE('ti-ditta') ? gE('ti-ditta').value : '';\n        const currentVersion = function(){ return (window.currentDbData && window.currentDbData.__db_version) ? window.currentDbData.__db_version : ''; };\n        const makeSaveForm = function(dbVersion, includeDeleteManifest) {\n            const fdSave = new FormData();\n            fdSave.append('ti_action', 'ti_ai_save_full_db');\n            fdSave.append('db', dbName);\n            fdSave.append('payload', utoa(JSON.stringify(window.currentDbData || {})));\n            fdSave.append('db_version', dbVersion || currentVersion() || '');\n            if (includeDeleteManifest) {\n                fdSave.append('deleted_rows', utoa(JSON.stringify(pendingDeletedManifest)));\n                fdSave.append('deleted_table_snapshots', utoa(JSON.stringify(pendingDeletedSnapshots)));\n                fdSave.append('deleted_rows_client_total', String(pendingDeletedClientTotal || 0));\n            } else {\n                fdSave.append('deleted_rows', utoa(JSON.stringify([])));\n                fdSave.append('deleted_table_snapshots', utoa(JSON.stringify({})));\n                fdSave.append('deleted_rows_client_total', '0');\n                fdSave.append('delete_already_applied', '1');\n            }\n            return fdSave;\n        };\n        const makeDeleteForm = function() {\n            const fdDelete = new FormData();\n            fdDelete.append('ti_action', 'ti_ai_config_delete_rows_definitive');\n            fdDelete.append('db', dbName);\n            fdDelete.append('db_version', currentVersion() || '');\n            fdDelete.append('deleted_rows', utoa(JSON.stringify(pendingDeletedManifest)));\n            fdDelete.append('deleted_table_snapshots', utoa(JSON.stringify(pendingDeletedSnapshots)));\n            fdDelete.append('deleted_rows_client_total', String(pendingDeletedClientTotal || 0));\n            return fdDelete;\n        };\n\n        window.showProgressPopup(pendingDefinitiveDelete ? 'Cancellazione definitiva configurazione' : 'Salvataggio configurazione', pendingDefinitiveDelete ? 'Clessidra attiva. Avvio la cancellazione definitiva dei record selezionati dal database ditta, poi salvo le modifiche residue. Attendi la fine del processo.' : 'Sto preparando e salvando le modifiche al database.', {startPercent: 5, targetPercent: 95, stepMs: 500, cancellable:false});\n\n        const saveSignal = window.tiLongProcessState && window.tiLongProcessState.controller ? window.tiLongProcessState.controller.signal : undefined;\n        const finishSaveOk = function(saveData, definitiveDeletedTotal){\n            saveData = saveData || {};\n            const saveDeletedTotal = (typeof saveData.deleted_rows_total !== 'undefined') ? (parseInt(saveData.deleted_rows_total, 10) || 0) : 0;\n            const deletedTotal = Math.max(0, parseInt(definitiveDeletedTotal || 0, 10) || 0) + Math.max(0, saveDeletedTotal);\n            const saveMsg = 'Configurazione salvata con successo. Cancellazione definitiva completata. Righe cancellate: ' + deletedTotal + '.';\n            window.hideProgressPopup(true, 'Configurazione salvata', {notifyUser:true, remindSave:false, successMessage:saveMsg});\n            const serverWarnings = (saveData && Array.isArray(saveData.value_warnings)) ? saveData.value_warnings : [];\n            setTimeout(function(){ if (serverWarnings.length && window.showConfigValidationWarning) window.showConfigValidationWarning(serverWarnings, true); else if (window.tiAlert) window.tiAlert('\u2705 Salvataggio completato. Righe cancellate: ' + deletedTotal + '.'); }, 450);\n            const cfgTimeWarn = String(window.configTimeFallbackWarning || '');\n            if (cfgTimeWarn) setTimeout(function(){ window.tiAlert(cfgTimeWarn); }, 1000);\n            window.configWasSaved = true;\n            if (saveData && saveData.db && saveData.db.Tabelle) {\n                window.currentDbData = saveData.db;\n                window.currentDbViewData = null;\n            } else if (window.currentDbData && saveData && saveData.db_version) {\n                window.currentDbData.__db_version = saveData.db_version;\n            }\n            if (window.currentDbData && saveData && saveData.db_version) window.currentDbData.__db_version = saveData.db_version;\n            if (window.currentDbData) window.configOriginalDbData = JSON.parse(JSON.stringify(window.currentDbData || {}));\n            if(window.modifiedFields) window.modifiedFields.clear();\n            window.tiConfigDeletedRowsManifest = [];\n            window.tiConfigDeletedTableSnapshots = {};\n            window.tiConfigDeletedTableSnapshotBeforeCounts = {};\n            window.tiConfigDeletedRowsCountTotal = 0;\n            window.tiLastConfigDeleteCount = 0;\n            if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState();\n            setTimeout(function(){ restoreBtn(true); }, 900);\n        };\n        const runFinalSave = function(versionAfterDelete, definitiveDeletedTotal, includeDeleteManifest) {\n            if (pendingDefinitiveDelete && window.updateProgressPopup) window.updateProgressPopup(78, 'Salvataggio finale configurazione', 'Cancellazione definitiva conclusa. Salvo ora il database aggiornato.', 'Righe cancellate: ' + Math.max(0, definitiveDeletedTotal || 0));\n            return postConfigForm(makeSaveForm(versionAfterDelete || currentVersion(), includeDeleteManifest), saveSignal).then(function(d){\n                if (d && d.success) {\n                    finishSaveOk((d && d.data) ? d.data : {}, definitiveDeletedTotal || 0);\n                    return d;\n                }\n                const errData = (d && d.data) ? d.data : {}; const err = new Error(errData.message || 'salvataggio non riuscito'); err.code = errData.code || ''; err.data = errData; throw err;\n            });\n        };\n\n        let flowPromise;\n        if (pendingDefinitiveDelete) {\n            if (window.updateProgressPopup) window.updateProgressPopup(20, 'Cancellazione definitiva in corso', 'Il server sta rileggendo il database reale e cancellando i record selezionati.', 'Righe da cancellare: ' + pendingDeletedClientTotal);\n            flowPromise = postConfigForm(makeDeleteForm(), saveSignal).then(function(delRes){\n                if (!(delRes && delRes.success)) throw new Error((delRes && delRes.data && delRes.data.message) ? delRes.data.message : 'cancellazione definitiva non riuscita');\n                const delData = (delRes && delRes.data) ? delRes.data : {};\n                const deletedByEndpoint = (typeof delData.deleted_rows_total !== 'undefined') ? (parseInt(delData.deleted_rows_total, 10) || 0) : 0;\n                const versionAfterDelete = delData.db_version || currentVersion();\n                if (window.updateProgressPopup) window.updateProgressPopup(62, 'Cancellazione definitiva verificata', 'Il processo server di cancellazione definitiva e terminato. Proseguo con il salvataggio finale.', 'Righe cancellate: ' + deletedByEndpoint);\n                return runFinalSave(versionAfterDelete, deletedByEndpoint, false);\n            }).catch(function(firstErr){\n                \/\/ Fallback controllato: se l endpoint separato non riesce a trovare righe ma il payload corrente e gia ridotto,\n                \/\/ il salvataggio finale usa il payload ridotto come fonte autorevole e verifica la scrittura su file.\n                if (window.updateProgressPopup) window.updateProgressPopup(55, 'Cancellazione tramite salvataggio finale', 'Endpoint separato non conclusivo. Applico la tabella ridotta direttamente nel salvataggio finale verificato.', firstErr && firstErr.message ? firstErr.message : 'fallback');\n                return runFinalSave(currentVersion(), 0, true);\n            });\n        } else {\n            flowPromise = runFinalSave(currentVersion(), 0, true);\n        }\n\n        flowPromise.catch(function(e) {\n            if (e && (e.name === 'AbortError' || e.userCancelled || window.isLongAIProcessCancelled())) { restoreBtn(false); window.clearLongAIProcess(); return; }\n            window.hideProgressPopup(false, 'Errore salvataggio');\n            if (e && (e.code === 'db_protected_connected_users' || \/database protetto|utenti connessi\/i.test(String(e.message || '')))) {\n                restoreBtn(false);\n                const data = e.data || {message:e.message || '', connected_count:0};\n                if (window.showConfigProtectedPopup) {\n                    window.showConfigProtectedPopup(data, function(){ setTimeout(function(){ if (window.saveConfig) window.saveConfig(); }, 450); }, function(){ window.tiAlert(data.message || 'Database protetto da utenti connessi.'); });\n                } else {\n                    window.tiAlert(data.message || 'Database protetto da utenti connessi.');\n                }\n                return;\n            }\n            window.tiAlert((e && e.message) ? e.message : 'Errore durante il salvataggio.');\n            restoreBtn(false);\n        });\n    };\n\n    window.updateMemField = function(tbl, idx, key, el) { \n        const protNorm = v => String(v == null ? '' : v).toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').replace(\/[^a-z0-9]\/g, '');\n        if (String(window.tiUser || '').toLowerCase() !== 'ssglobaladmin' && protNorm(tbl) === 'config' && ['stato','datacreazione','dataattivazione','wcorderid','wcactivateid'].includes(protNorm(key))) {\n            if (el && typeof el.blur === 'function') el.blur();\n            if (window.tiAlert) window.tiAlert('Campo riservato: modificabile solo da sistema, globaladmin o attivazione WooCommerce.');\n            return;\n        }\n        if(window.currentDbData && window.currentDbData.Tabelle[tbl] && window.currentDbData.Tabelle[tbl][idx]) { \n            let normalizedValue = window.normalizeInputValueForKey(key, el.value);\n            const fieldNorm = window.normalizeConfigRelationName ? window.normalizeConfigRelationName(key) : String(key || '').toLowerCase();\n            if (fieldNorm === 'stato' || fieldNorm === 'status') {\n                normalizedValue = window.normalizeConfigStateValue ? window.normalizeConfigStateValue(normalizedValue) : normalizedValue;\n                if (el && ['Attivo','Operativo','Demo','Configurazione','Sospesa','Cancellato'].includes(normalizedValue)) el.value = normalizedValue;\n            }\n            window.currentDbData.Tabelle[tbl][idx][key] = normalizedValue;\n            window.__configRelationSupportCache = null;\n            window.forceWhiteEditStyle && window.forceWhiteEditStyle(el);\n            el.style.setProperty('border-color', '#fbbf24', 'important');\n            if(!window.modifiedFields) window.modifiedFields = new Set();\n            window.modifiedFields.add(`${tbl}-${idx}-${key}`);\n            if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState();\n            if (window.checkConfigUsernameDuplicateInput) window.checkConfigUsernameDuplicateInput(tbl, idx, key, el);\n        } \n    };\n\n    window.ensureSharedSetupModal = function() {\n        if (gE('ti-shared-setup-ov')) return;\n        const wrap = document.createElement('div');\n        wrap.id = 'ti-shared-setup-ov';\n        wrap.className = 'ti-ov';\n        wrap.innerHTML = `<div class=\"ti-modal\" style=\"width:96%;max-width:760px;text-align:left;\">\n            <h3 style=\"margin-top:0;color:#fff;\">Setup storage centralizzato<\/h3>\n            <div style=\"font-size:12px;color:#9ca3af;margin-bottom:12px;\">Disponibile solo per l amministratore globale. Definisce percorso filesystem condiviso e URL pubblico del repository AI-data usato dal plugin su siti diversi. PATH e URL non devono essere identici come stringa: il PATH e un percorso server, l URL e un indirizzo web pubblico.<\/div>\n            <label style=\"display:block;font-size:12px;color:#bfdbfe;margin-bottom:6px;\">TI_AI_SS_SHARED_BASE_PATH<\/label>\n            <input id=\"ti-shared-base-path\" class=\"ti-in\" placeholder=\"\/percorso\/condiviso\/wp-content\/uploads\/AI-data\/\">\n            <label style=\"display:block;font-size:12px;color:#bfdbfe;margin:12px 0 6px 0;\">TI_AI_SS_SHARED_BASE_URL<\/label>\n            <input id=\"ti-shared-base-url\" class=\"ti-in\" placeholder=\"https:\/\/www.targetinformatica.it\/wp-content\/uploads\/AI-data\">\n            <div id=\"ti-shared-setup-info\" style=\"margin-top:12px;font-size:12px;line-height:1.45;color:#cbd5e1;background:#020617;border:1px solid #334155;border-radius:8px;padding:10px;\"><\/div>\n            <div style=\"display:flex;gap:8px;justify-content:flex-end;flex-wrap:wrap;margin-top:14px;\">\n                <button type=\"button\" class=\"ti-btn\" style=\"background:#334155;color:#fff;\" onclick=\"window.fillSharedSetupDefaults()\">Usa valori default<\/button>\n                <button type=\"button\" class=\"ti-btn\" onclick=\"window.closeModal('ti-shared-setup-ov')\">Chiudi<\/button>\n                <button type=\"button\" class=\"ti-btn\" style=\"background:#a16207;color:#fff;\" onclick=\"window.saveSharedSetup()\">Salva setup centralizzato<\/button>\n            <\/div>\n        <\/div>`;\n        document.body.appendChild(wrap);\n        if (window.portalizePluginModals) window.portalizePluginModals();\n    };\n\n    window.refreshSharedSetupInfo = function() {\n        const info = gE('ti-shared-setup-info');\n        if (!info) return;\n        const payload = window.tiSharedSetup || {};\n        info.innerHTML = 'Path condiviso salvato:<br><b>' + window.escapeHtml(payload.shared_base_path || '(non impostato)') + '<\/b><br><br>URL condiviso salvato:<br><b>' + window.escapeHtml(payload.shared_base_url || '(non impostato)') + '<\/b><br><br>Percorso effettivo in uso:<br><b>' + window.escapeHtml(payload.effective_base_path || '') + '<\/b><br><br>URL effettivo in uso:<br><b>' + window.escapeHtml(payload.effective_base_url || '') + '<\/b><br><br>Valori default disponibili:<br><b>' + window.escapeHtml(payload.default_base_path || '') + '<\/b><br><b>' + window.escapeHtml(payload.default_base_url || '') + '<\/b><br><br>Esempio URL centralizzato Target Informatica:<br><b>' + window.escapeHtml(payload.target_url_example || '') + '<\/b>';\n    };\n\n    window.fillSharedSetupDefaults = function() {\n        const payload = window.tiSharedSetup || {};\n        const pathInp = gE('ti-shared-base-path');\n        const urlInp = gE('ti-shared-base-url');\n        if (pathInp) pathInp.value = payload.default_base_path || '';\n        if (urlInp) urlInp.value = payload.default_base_url || '';\n    };\n\n    window.openSharedSetupModal = function() {\n        if (window.tiUser !== 'SSGlobalAdmin') { window.tiAlert('Solo l amministratore globale puo usare questo setup.'); return; }\n        window.ensureSharedSetupModal();\n        const payload = window.tiSharedSetup || {};\n        const pathInp = gE('ti-shared-base-path');\n        const urlInp = gE('ti-shared-base-url');\n        if (pathInp) pathInp.value = payload.shared_base_path || payload.default_base_path || '';\n        if (urlInp) urlInp.value = payload.shared_base_url || payload.default_base_url || '';\n        window.refreshSharedSetupInfo();\n        window.openModal('ti-shared-setup-ov');\n    };\n\n    window.saveSharedSetup = function() {\n        if (window.tiUser !== 'SSGlobalAdmin') { window.tiAlert('Solo l amministratore globale puo salvare questo setup.'); return; }\n        const fd = new FormData();\n        fd.append('ti_action', 'ti_ai_config_action');\n        fd.append('mode', 'shared_setup_save');\n        fd.append('shared_base_path', (gE('ti-shared-base-path') && gE('ti-shared-base-path').value) ? gE('ti-shared-base-path').value : '');\n        fd.append('shared_base_url', (gE('ti-shared-base-url') && gE('ti-shared-base-url').value) ? gE('ti-shared-base-url').value : '');\n        window.fetchJsonSafe((window.tiAjaxUrl || window.tiUrl), {method:'POST', body:fd, credentials:'same-origin'}).then(function(res){\n            if (!res || !res.success) { window.tiAlert((res && res.data && res.data.message) ? res.data.message : 'Errore salvataggio setup centralizzato.'); return; }\n            window.tiSharedSetup = (res.data && res.data.shared_setup) ? res.data.shared_setup : (window.tiSharedSetup || {});\n            window.tiUploadBase = String((window.tiSharedSetup && window.tiSharedSetup.effective_base_url) || window.tiUploadBase || '').replace(\/\\\/$\/, '');\n            const manualBtn = gE('btn-manual');\n            const sharedManualUrl = String((window.tiSharedSetup && window.tiSharedSetup.manual_url) || '');\n            const sharedDocsBaseUrl = String((window.tiSharedSetup && window.tiSharedSetup.docs_base_url) || 'https:\/\/www.targetinformatica.it\/wp-content\/uploads\/AI-data\/AI-docs\/Shop-service');\n            if (manualBtn && sharedManualUrl) manualBtn.href = sharedManualUrl;\n            else if (manualBtn && sharedDocsBaseUrl) manualBtn.href = sharedDocsBaseUrl.replace(\/\\\/+$\/, '') + '\/Shop-Service%20Manual.pdf';\n            window.refreshSharedSetupInfo();\n            const replyBox = gE('ti-conf-ai-reply');\n            if (replyBox && res.data && res.data.reply) { replyBox.style.display = 'block'; replyBox.innerHTML = window.escapeHtml(res.data.reply); }\n            window.tiAlert('Setup centralizzato salvato.');\n        }).catch(function(err){\n            window.tiAlert('Errore salvataggio setup centralizzato: ' + ((err && err.message) ? err.message : 'errore sconosciuto'));\n        });\n    };\n\n\n    window.ensureConfigBotLoadingStyle = function() {\n        if (gE('ti-conf-ai-loading-style')) return;\n        const st = document.createElement('style');\n        st.id = 'ti-conf-ai-loading-style';\n        st.innerHTML = '@keyframes tiConfHourglassSpin{0%{transform:rotate(0deg);}50%{transform:rotate(180deg);}100%{transform:rotate(360deg);}}' +\n            '@keyframes tiConfDots{0%,20%{opacity:.2;}50%{opacity:1;}100%{opacity:.2;}}' +\n            '.ti-conf-wait{display:flex;align-items:center;gap:12px;padding:2px 0;}' +\n            '.ti-conf-wait-icon{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:999px;background:#1f2937;border:1px solid #475569;font-size:18px;animation:tiConfHourglassSpin 1.25s ease-in-out infinite;}' +\n            '.ti-conf-wait-text{display:flex;flex-direction:column;gap:2px;line-height:1.35;}' +\n            '.ti-conf-wait-title{font-weight:700;color:#f8fafc;}' +\n            '.ti-conf-wait-sub{font-size:12px;color:#93c5fd;}' +\n            '.ti-conf-wait-dots span{display:inline-block;animation:tiConfDots 1.2s infinite;}' +\n            '.ti-conf-wait-dots span:nth-child(2){animation-delay:.2s;}' +\n            '.ti-conf-wait-dots span:nth-child(3){animation-delay:.4s;}';\n        document.head.appendChild(st);\n    };\n\n    window.getConfigBotWaitingHtml = function(message, detail) {\n        window.ensureConfigBotLoadingStyle();\n        const msg = window.escapeHtml(message || 'Fammi pensare');\n        const sub = window.escapeHtml(detail || 'Sto verificando database, documenti AI e istruzioni disponibili.');\n        return '<div class=\"ti-conf-wait\" aria-live=\"polite\" aria-busy=\"true\">'\n            + '<div class=\"ti-conf-wait-icon\" title=\"Attendere\">\u23f3<\/div>'\n            + '<div class=\"ti-conf-wait-text\">'\n            + '<div class=\"ti-conf-wait-title\">' + msg + ' <span class=\"ti-conf-wait-dots\"><span>.<\/span><span>.<\/span><span>.<\/span><\/span><\/div>'\n            + '<div class=\"ti-conf-wait-sub\">' + sub + '<\/div>'\n            + '<\/div>'\n            + '<\/div>';\n    };\n\n\n    window.getUserWaitingHtml = function(message, detail) {\n        window.ensureConfigBotLoadingStyle();\n        const msg = window.escapeHtml(message || 'Fammi pensare...');\n        const sub = window.escapeHtml(detail || 'Sto elaborando la richiesta e verifico i dati disponibili.');\n        return '<div class=\"ti-conf-wait ti-user-wait\" aria-live=\"polite\" aria-busy=\"true\">'\n            + '<div class=\"ti-conf-wait-icon\" title=\"Attendere\">\u23f3<\/div>'\n            + '<div class=\"ti-conf-wait-text\">'\n            + '<div class=\"ti-conf-wait-title\">' + msg + '<\/div>'\n            + '<div class=\"ti-conf-wait-sub\">' + sub + '<\/div>'\n            + '<\/div>'\n            + '<\/div>';\n    };\n\n\n    \/* 30.9.198 - Fallback OpenAI obbligatorio per richieste informative\/comparative senza DOC\/catalogo *\/\n    window.tiIsInfoVerificationRequest = function(text) {\n        const raw = String(text || '').trim();\n        if (!raw) return false;\n        const t = raw.toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '');\n        if (\/\\b(conferma\\s+ordine|conferma\\s+righe|salva\\s+ordine|annulla|login|logout|accedi|importa|carica\\s+file|scatta|foto)\\b\/i.test(t)) return false;\n        if (\/\\b(che\\s+mi\\s+dici|cosa\\s+mi\\s+dici|cosa\\s+sai|dimmi|spiegami|spiega|informazioni?\\s+su|info\\s+su|a\\s+cosa\\s+serve|come\\s+funziona|condizioni|contrattual|commercial|privacy|gdpr|documento|documenti|scheda|bugiardino|foglietto|aspirina|farmaco|farmaci|prodotto|servizio)\\b\/i.test(t)) return true;\n        const wordCount = t.split(\/\\s+\/).filter(Boolean).length;\n        return wordCount >= 2 && wordCount <= 8 && !\/[0-9]+\\s*(pz|pezzi|unita|unit\u00e0|conf|confezioni|kg|g|ml|litri?)\\b\/i.test(t);\n    };\n\n    window.getInfoVerificationWaitingHtml = function(message, detail) {\n        return window.getUserWaitingHtml(\n            message || '\u23f3 Verifica informazioni in corso...',\n            detail || 'Sto cercando informazioni in database ditta, prodotti\/servizi, righe ordine e documenti collegati.'\n        );\n    };\n\n    window.clearConfigBot = function() {\n        const p = gE('ti-conf-ai-prompt'); const r = gE('ti-conf-ai-reply'); const f = gE('ti-conf-ai-file');\n        if (p) p.value = '';\n        if (r) { r.style.display = 'none'; r.innerHTML = ''; }\n        window.configBotFile = null;\n        if (f) { f.style.display = 'none'; f.innerHTML = ''; }\n    };\n\n    window.showConfigImportPreview = function(previewText, importToken) {\n        if (importToken) window.tiConfigImportToken = importToken;\n        const body = gE('ti-conf-import-preview-body');\n        if (body) {\n            body.innerHTML = (previewText || '').replace(\/\\[NO_VOICE\\]\/g, '').trim();\n            if (window.tiConfigImportToken) body.dataset.importToken = window.tiConfigImportToken;\n            if (window.initConfigTopScrollbars) setTimeout(function(){ window.initConfigTopScrollbars(body); }, 40);\n        }\n        const refineBox = gE('ti-conf-import-refine-prompt');\n        const refineStatus = gE('ti-conf-import-refine-status');\n        if (refineBox) refineBox.value = '';\n        if (refineStatus) refineStatus.innerHTML = '';\n        window.tiConfigImportRefineRules = [];\n        window.markConfigImportPreviewUpdated('Anteprima pronta. Verifica i dati e conferma solo se corretti.');\n        window.openModal('ti-conf-import-preview-ov');\n    };\n\n    window.hasPendingImportAssociations = function() {\n        const body = gE('ti-conf-import-preview-body');\n        if (!body) return false;\n        if (window.tiConfigImportToken) return true;\n        if (Array.isArray(window.tiConfigImportRefineRules) && window.tiConfigImportRefineRules.length) return true;\n        try {\n            const selects = Array.from(body.querySelectorAll('.ti-import-map-select'));\n            if (selects.some(function(sel){ return String(sel.value || '').trim() !== '' && String(sel.value || '').trim() !== '--'; })) return true;\n            const formulas = Array.from(body.querySelectorAll('.ti-import-price-formula'));\n            if (formulas.some(function(inp){ return String(inp.value || '').trim() !== ''; })) return true;\n            const targets = Array.from(body.querySelectorAll('.ti-import-group-target'));\n            if (targets.some(function(sel){ return String(sel.value || '').trim() !== '' && String(sel.value || '').trim() !== 'Prodotti'; })) return true;\n        } catch(e) {}\n        return false;\n    };\n\n    window.closeConfigImportPreview = function(forceClose) {\n        if (!forceClose && window.hasPendingImportAssociations && window.hasPendingImportAssociations()) {\n            const msg = '<b>Associazioni import non ancora salvate<\/b><br><br>' +\n                'Hai gi\u00e0 fatto associazioni o regole di import nella preview.<br>' +\n                'Vuoi salvarle adesso con la conferma definitiva oppure uscire senza salvarle?<br><br>' +\n                '<span style=\"color:#bbf7d0;font-weight:700;\">SALVA E CONFERMA<\/span> salva l import nel database.<br>' +\n                '<span style=\"color:#fecaca;font-weight:700;\">ESCI SENZA SALVARE<\/span> chiude la finestra e non rende definitive le associazioni.';\n            window.tiConfirmYesNoAction(msg, function(){\n                window.confirmConfigImportPreview(true);\n            }, function(){\n                window.closeConfigImportPreview(true);\n            }, 'SALVA E CONFERMA', 'ESCI SENZA SALVARE');\n            return;\n        }\n        window.closeModal('ti-conf-import-preview-ov');\n    };\n\n    window.markConfigImportPreviewUpdated = function(message) {\n        const note = gE('ti-conf-import-preview-last-update');\n        const body = gE('ti-conf-import-preview-body');\n        const text = message || 'Anteprima modifiche aggiornata. Controlla il risultato e conferma solo se corretto.';\n        if (note) {\n            const now = new Date();\n            let hh = String(now.getHours()).padStart(2, '0');\n            let mm = String(now.getMinutes()).padStart(2, '0');\n            note.style.display = 'block';\n            note.innerHTML = 'Anteprima modifiche aggiornata alle ' + hh + ':' + mm + '. ' + window.escapeHtml(text);\n        }\n        if (body) {\n            try {\n                const active = document.activeElement;\n                const editingImport = !!(active && body.contains(active) && active.matches && active.matches('.ti-import-map-select, .ti-import-group-target, .ti-import-price-formula'));\n                if (!editingImport) body.scrollTop = 0;\n            } catch(e) {}\n            body.setAttribute('data-preview-updated', '1');\n        }\n    };\n\n    window.redisplayConfigImportPreviewForFinalConfirm = function(message) {\n        window.markConfigImportPreviewUpdated(message || 'Rivedi questa anteprima prima della conferma definitiva.');\n        try { window.openModal('ti-conf-import-preview-ov'); } catch(e) {}\n        const body = gE('ti-conf-import-preview-body');\n        if (body && typeof body.focus === 'function') {\n            try { body.setAttribute('tabindex', '-1'); body.focus({preventScroll:true}); } catch(e) {}\n        }\n    };\n\n    window.normalizeImportRefineToken = function(v) {\n        return String(v || '').toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').replace(\/[^a-z0-9]+\/g, ' ').trim();\n    };\n\n    window.tiConfigImportRefineRules = Array.isArray(window.tiConfigImportRefineRules) ? window.tiConfigImportRefineRules : [];\n    window.rememberImportLocalRefineRule = function(dest, src) {\n        const d = String(dest || '').trim();\n        const s = String(src || '').trim();\n        if (!d || !s) return false;\n        window.tiConfigImportRefineRules = Array.isArray(window.tiConfigImportRefineRules) ? window.tiConfigImportRefineRules : [];\n        const nd = window.normalizeImportRefineToken(d);\n        const ns = window.normalizeImportRefineToken(s);\n        const exists = window.tiConfigImportRefineRules.some(function(r){ return window.normalizeImportRefineToken(r.dest) === nd && window.normalizeImportRefineToken(r.src) === ns; });\n        if (!exists) window.tiConfigImportRefineRules.push({dest:d, src:s});\n        return true;\n    };\n\n    window.getImportRefineAssignment = function(prompt) {\n        let txt = String(prompt || '').trim();\n        if (!txt) return null;\n        txt = txt.replace(\/[\\r\\n]+\/g, ' ').replace(\/\\s+\/g, ' ').trim();\n        txt = txt.replace(\/^(?:ut|utente|richiesta|istruzione|comando)\\s*[:\\-]\\s*\/i, '').trim();\n        txt = txt.replace(\/^['\"`]+|['\"`]+$\/g, '').trim();\n        const m = txt.match(\/^(.+?)\\s*(?:=|->|\\u2192|come| in | da )\\s*(.+?)$\/i);\n        if (!m) return null;\n        let leftRaw = String(m[1] || '').replace(\/^campo\\s+\/i, '').trim();\n        let rightRaw = String(m[2] || '').replace(\/^campo\\s+\/i, '').trim();\n        leftRaw = leftRaw.replace(\/^['\"`]+|['\"`]+$\/g, '').trim();\n        rightRaw = rightRaw.replace(\/^['\"`]+|['\"`]+$\/g, '').trim();\n        const left = window.normalizeImportRefineToken ? window.normalizeImportRefineToken(leftRaw) : leftRaw.toLowerCase();\n        const right = window.normalizeImportRefineToken ? window.normalizeImportRefineToken(rightRaw) : rightRaw.toLowerCase();\n        if (!left || !right || left.length > 80 || right.length > 80) return null;\n        return {dest: leftRaw, src: rightRaw, destNorm: left, srcNorm: right, raw: txt};\n    };\n\n    window.isSimpleImportRefineInstruction = function(prompt) {\n        const txt = String(prompt || '').trim();\n        if (!txt) return false;\n        if (window.getImportRefineAssignment && window.getImportRefineAssignment(txt)) return true;\n        return txt.length <= 180 && \/(?:=|->|\\u2192)\/.test(txt);\n    };\n\n    window.getImportTableHeaderTexts = function(table) {\n        const ths = Array.from((table || document).querySelectorAll('thead th'));\n        return ths.map(function(th){\n            let label = '';\n            const span = th.querySelector('span');\n            if (span) label = span.textContent || '';\n            if (!label) label = th.childNodes && th.childNodes.length ? Array.from(th.childNodes).map(function(n){ return n.nodeType === 3 ? n.textContent : ''; }).join(' ') : th.textContent;\n            if (!label) label = th.textContent || '';\n            return String(label || '').replace(\/\\?\/g, '').trim();\n        });\n    };\n\n    window.findImportPreviewRowsTable = function(root, destNorm, srcNorm) {\n        const box = root || gE('ti-conf-import-preview-body') || document;\n        const tables = Array.from(box.querySelectorAll('table'));\n        let best = null;\n        tables.forEach(function(table){\n            if (best) return;\n            if (table.querySelector('.ti-import-map-select, .ti-import-group-target')) return;\n            const headers = window.getImportTableHeaderTexts(table);\n            if (!headers.length) return;\n            const norms = headers.map(function(h){ return window.normalizeImportRefineToken ? window.normalizeImportRefineToken(h) : String(h).toLowerCase(); });\n            const hasSrc = norms.some(function(n){ return n === srcNorm || n.indexOf(srcNorm) !== -1 || srcNorm.indexOf(n) !== -1; });\n            const hasDest = norms.some(function(n){ return n === destNorm || n.indexOf(destNorm) !== -1 || destNorm.indexOf(n) !== -1; });\n            if (hasSrc || hasDest) best = table;\n        });\n        return best;\n    };\n\n    window.ensureImportPreviewLocalRulesNote = function(root) {\n        const box = root || gE('ti-conf-import-preview-body');\n        if (!box) return null;\n        let note = box.querySelector('#ti-import-local-rules-note');\n        if (!note) {\n            note = document.createElement('div');\n            note.id = 'ti-import-local-rules-note';\n            note.style.cssText = 'margin:10px 0;padding:8px 10px;border-radius:8px;border:1px solid #22c55e;background:#052e16;color:#bbf7d0;font-size:12px;font-weight:700;';\n            box.insertBefore(note, box.firstChild);\n        }\n        return note;\n    };\n\n    window.renderImportPreviewLocalRulesNote = function(root) {\n        const note = window.ensureImportPreviewLocalRulesNote(root);\n        if (!note) return;\n        const rules = Array.isArray(window.tiConfigImportRefineRules) ? window.tiConfigImportRefineRules : [];\n        if (!rules.length) { note.style.display = 'none'; return; }\n        note.style.display = 'block';\n        note.innerHTML = 'Regole locali applicate alla preview e al salvataggio: ' + rules.map(function(r){\n            return window.escapeHtml(String(r.dest || '').trim() + ' = ' + String(r.src || '').trim());\n        }).join(' ; ') + '. Controlla l anteprima aggiornata e conferma solo se corretta.';\n    };\n\n    window.findImportColumnIndexByToken = function(headers, tokenNorm) {\n        const norms = headers.map(function(h){ return window.normalizeImportRefineToken ? window.normalizeImportRefineToken(h) : String(h).toLowerCase(); });\n        let idx = norms.findIndex(function(n){ return n === tokenNorm; });\n        if (idx < 0) idx = norms.findIndex(function(n){ return n.indexOf(tokenNorm) !== -1 || tokenNorm.indexOf(n) !== -1; });\n        return idx;\n    };\n\n    window.updateImportPreviewRowsLocal = function(dest, src, root) {\n        const destNorm = window.normalizeImportRefineToken ? window.normalizeImportRefineToken(dest) : String(dest || '').toLowerCase();\n        const srcNorm = window.normalizeImportRefineToken ? window.normalizeImportRefineToken(src) : String(src || '').toLowerCase();\n        const box = root || gE('ti-conf-import-preview-body');\n        if (!box || !destNorm || !srcNorm) return false;\n        const table = window.findImportPreviewRowsTable(box, destNorm, srcNorm);\n        window.renderImportPreviewLocalRulesNote(box);\n        if (!table) return false;\n        let headers = window.getImportTableHeaderTexts(table);\n        let srcIdx = window.findImportColumnIndexByToken(headers, srcNorm);\n        let destIdx = window.findImportColumnIndexByToken(headers, destNorm);\n        if (srcIdx < 0) return false;\n        if (destIdx < 0) {\n            const headRow = table.querySelector('thead tr');\n            if (headRow) {\n                const th = document.createElement('th');\n                th.style.cssText = 'border:1px solid #475569; padding:5px 6px; background:#0f172a; text-align:left; white-space:nowrap;';\n                th.innerHTML = '<div style=\"display:flex;align-items:center;justify-content:space-between;gap:6px;\"><span>' + window.escapeHtml(dest) + '<\/span><\/div>';\n                headRow.appendChild(th);\n                headers.push(dest);\n                destIdx = headers.length - 1;\n                Array.from(table.querySelectorAll('tbody tr')).forEach(function(tr){\n                    const td = document.createElement('td');\n                    td.style.cssText = 'border:1px solid #475569; padding:5px 6px; text-align:left; vertical-align:top; background:#052e16;';\n                    tr.appendChild(td);\n                });\n            }\n        }\n        if (destIdx < 0) return false;\n        let changed = 0;\n        Array.from(table.querySelectorAll('tbody tr')).forEach(function(tr){\n            const cells = Array.from(tr.children || []);\n            if (!cells[srcIdx] || !cells[destIdx]) return;\n            const value = (cells[srcIdx].textContent || '').trim();\n            cells[destIdx].textContent = value;\n            cells[destIdx].style.background = '#052e16';\n            changed++;\n        });\n        return changed > 0;\n    };\n\n    window.tryApplyImportRefineMappingInstruction = function(prompt, root, statusEl) {\n        const parsed = window.getImportRefineAssignment ? window.getImportRefineAssignment(prompt) : null;\n        if (!parsed) {\n            if (window.isSimpleImportRefineInstruction && window.isSimpleImportRefineInstruction(prompt)) {\n                if (statusEl) statusEl.innerHTML = '<span style=\"color:#fbbf24;\">Istruzione breve non chiara. Usa formato: campo destinazione = campo origine. Nessuna chiamata AI eseguita.<\/span>';\n                if (window.redisplayConfigImportPreviewForFinalConfirm) window.redisplayConfigImportPreviewForFinalConfirm('Chiarimento richiesto per il raffinamento. La preview resta invariata fino a istruzione chiara.');\n                return true;\n            }\n            return false;\n        }\n        const box = root || document;\n        const destRaw = parsed.dest;\n        const srcRaw = parsed.src;\n        window.rememberImportLocalRefineRule(destRaw, srcRaw);\n        const updatedRows = window.updateImportPreviewRowsLocal ? window.updateImportPreviewRowsLocal(destRaw, srcRaw, box) : false;\n        const selects = Array.from(box.querySelectorAll('.ti-import-map-select'));\n        if (selects.length) {\n            function optionValueFor(sel, wanted) {\n                const opts = Array.from(sel.options || []);\n                let hit = opts.find(function(o){ return window.normalizeImportRefineToken(o.value) === wanted || window.normalizeImportRefineToken(o.text) === wanted; });\n                if (!hit) hit = opts.find(function(o){ return window.normalizeImportRefineToken(o.value).indexOf(wanted) !== -1 || window.normalizeImportRefineToken(o.text).indexOf(wanted) !== -1; });\n                return hit ? hit.value : '';\n            }\n            selects.forEach(function(sel){\n                const hdr = window.normalizeImportRefineToken(sel.getAttribute('data-source-header') || '');\n                if (hdr === parsed.srcNorm || hdr.indexOf(parsed.srcNorm) !== -1 || parsed.srcNorm.indexOf(hdr) !== -1) {\n                    const val = optionValueFor(sel, parsed.destNorm);\n                    if (val && val !== '--') sel.value = val;\n                }\n            });\n        }\n        if (statusEl) {\n            statusEl.innerHTML = updatedRows\n                ? 'Anteprima aggiornata localmente. Non chiamo il server. Controlla il risultato e conferma per salvare.'\n                : 'Regola locale memorizzata. Non chiamo il server. Verr\u00e0 applicata al salvataggio. Controlla la preview prima di confermare.';\n        }\n        if (window.redisplayConfigImportPreviewForFinalConfirm) window.redisplayConfigImportPreviewForFinalConfirm(updatedRows ? 'Anteprima modifiche aggiornata localmente. Conferma solo se corretta.' : 'Regola locale registrata. Conferma solo se corretta.');\n        return true;\n    };\n\n    window.refineConfigImportPreview = function() {\n        const dbSel = gE('ti-ditta');\n        if (!dbSel || !dbSel.value || dbSel.value === 'NEW_DB') { window.tiAlert('Seleziona una ditta.'); return; }\n        const body = gE('ti-conf-import-preview-body');\n        const promptEl = gE('ti-conf-import-refine-prompt');\n        const statusEl = gE('ti-conf-import-refine-status');\n        const btn = gE('ti-conf-import-refine-btn');\n        const refinePrompt = promptEl ? String(promptEl.value || '').trim() : '';\n        if (!refinePrompt) { window.tiAlert('Scrivi cosa vuoi far raffinare alla AI.'); if (promptEl) promptEl.focus(); return; }\n        const mapping = (window.collectImportMappingFromUI ? window.collectImportMappingFromUI(body || document) : (window.tiConfigImportLastMapping || {}));\n        const fd = new FormData();\n        fd.append('action', 'ti_ai_config_action');\n        fd.append('ti_action', 'ti_ai_config_action');\n        fd.append('db', dbSel.value);\n        fd.append('mode', 'refine_import_preview');\n        fd.append('prompt', 'raffina import preview');\n        fd.append('refine_prompt', refinePrompt);\n        if (window.tiConfigImportToken) fd.append('import_token', window.tiConfigImportToken);\n        fd.append('mapping_json', JSON.stringify(mapping));\n        const oldText = btn ? btn.innerText : '';\n        if (btn) { btn.disabled = true; btn.innerText = 'Raffinamento in corso...'; }\n        if (window.tryApplyImportRefineMappingInstruction && window.tryApplyImportRefineMappingInstruction(refinePrompt, body || document, statusEl)) {\n            if (btn) { btn.disabled = false; btn.innerText = oldText || 'Raffina anteprima con AI'; }\n            return;\n        }\n        if (statusEl) statusEl.innerHTML = 'AI al lavoro sulla prima anteprima...';\n        if (body) body.style.opacity = '0.72';\n        window.postFormDataJsonSafe(window.tiUrl, fd, {tiNoLongProcessSignal:true}).then(function(res){\n            if (btn) { btn.disabled = false; btn.innerText = oldText || 'Raffina anteprima con AI'; }\n            if (body) body.style.opacity = '';\n            if (res && res.success) {\n                if (res.data && (res.data.import_token || res.data.preview_token)) window.tiConfigImportToken = res.data.import_token || res.data.preview_token;\n                const rawReply = ((res.data || {}).reply || '').replace(\/\\[NO_VOICE\\]\/g, '').trim();\n                if (rawReply && body) {\n                    body.innerHTML = rawReply;\n                    if (window.tiConfigImportToken) body.dataset.importToken = window.tiConfigImportToken;\n                    if (window.initConfigTopScrollbars) setTimeout(function(){ window.initConfigTopScrollbars(body); }, 40);\n                }\n                if (window.redisplayConfigImportPreviewForFinalConfirm) window.redisplayConfigImportPreviewForFinalConfirm('Anteprima raffinata. Controlla il risultato prima di confermare.');\n                if (statusEl) statusEl.innerHTML = 'Anteprima raffinata. Controlla il risultato prima di confermare.';\n            } else {\n                const msg = (res && res.data && res.data.message) ? res.data.message : 'Raffinamento non riuscito.';\n                if (statusEl) statusEl.innerHTML = '<span style=\"color:#fca5a5;\">' + window.escapeHtml(msg) + '<\/span>';\n                else window.tiAlert(msg);\n            }\n        }).catch(function(err){\n            if (btn) { btn.disabled = false; btn.innerText = oldText || 'Raffina anteprima con AI'; }\n            if (body) body.style.opacity = '';\n            const msg = 'Errore raffinamento import: ' + ((err && err.message) ? err.message : 'errore sconosciuto');\n            if (statusEl) statusEl.innerHTML = '<span style=\"color:#fca5a5;\">' + window.escapeHtml(msg) + '<\/span>';\n            else window.tiAlert(msg);\n        });\n    };\n\n    window.confirmConfigImportPreview = function(skipClosePrompt) {\n        const dbSel = gE('ti-ditta');\n        if (!dbSel || !dbSel.value || dbSel.value === 'NEW_DB') { window.tiAlert('Seleziona una ditta.'); return; }\n        const body = gE('ti-conf-import-preview-body');\n        const statusEl = gE('ti-conf-import-refine-status');\n        const mapping = (window.collectImportMappingFromUI ? window.collectImportMappingFromUI(body || document) : {});\n\n        const fd = new FormData();\n        fd.append('action', 'ti_ai_config_action');\n        fd.append('ti_action', 'ti_ai_config_action');\n        fd.append('db', dbSel.value);\n        fd.append('mode', 'confirm_import_preview');\n        fd.append('prompt', 'conferma import preview');\n        if (window.tiConfigImportToken) fd.append('import_token', window.tiConfigImportToken);\n        fd.append('mapping_json', JSON.stringify(mapping));\n\n        const buttons = Array.from(document.querySelectorAll('#ti-conf-import-preview-ov button'));\n        buttons.forEach(function(btn){ btn.disabled = true; });\n        if (statusEl) statusEl.innerHTML = 'Salvataggio import in corso...';\n        if (body) body.style.opacity = '0.72';\n\n        \/\/ Usare window.tiUrl evita HTTP 400: 0 di admin-ajax.php quando l'accesso e quello interno del plugin.\n        window.postFormDataJsonSafe(window.tiUrl, fd, {tiNoLongProcessSignal:true, cache:'no-store'}).then(function(res){\n            buttons.forEach(function(btn){ btn.disabled = false; });\n            if (body) body.style.opacity = '';\n            if (res && res.success) {\n                const rawReply = ((res.data || {}).reply || '').trim();\n                const cleanReply = rawReply.replace(\/\\[NO_VOICE\\]\/g, '').trim();\n                const finalImportMsg = cleanReply || '\u2705 Import completato e salvato nel database.';\n                window.closeConfigImportPreview(true);\n                window.addMsg('ai', window.formatTable(finalImportMsg), finalImportMsg);\n                setTimeout(function(){ if (window.tiAlert) window.tiAlert('\u2705 Salvataggio import completato con successo.'); }, 250);\n                window.tiConfigImportToken = null;\n                if (res.data && res.data.needs_reload) setTimeout(function(){ location.reload(); }, 1800);\n            } else {\n                const msg = (res && res.data && res.data.message) ? res.data.message : 'Errore conferma import';\n                if (statusEl) statusEl.innerHTML = '<span style=\"color:#fca5a5;\">' + window.escapeHtml(msg) + '<\/span>';\n                window.tiAlert(msg);\n            }\n        }).catch(function(err){\n            buttons.forEach(function(btn){ btn.disabled = false; });\n            if (body) body.style.opacity = '';\n            const msg = 'Errore conferma import: ' + ((err && err.message) ? err.message : 'errore sconosciuto');\n            if (statusEl) statusEl.innerHTML = '<span style=\"color:#fca5a5;\">' + window.escapeHtml(msg) + '<\/span>';\n            window.tiAlert(msg);\n        });\n    };\n\n    window.cancelConfigImportPreview = function() {\n        const box = gE('ti-conf-ai-prompt');\n        if (box) box.value = 'ANNULLA';\n        window.closeConfigImportPreview(true);\n        window.runConfigBot();\n    };\n\n    window.pickConfigBotFile = function() {\n        window.targetUploadContext = 'config-bot-file';\n        const fileIn = gE('ti-file-in');\n        if (fileIn) { fileIn.dataset.target = 'config-bot-file'; fileIn.value = ''; setTimeout(() => fileIn.click(), 100); }\n    };\n\n    window.runBatchAIGen = function(tableName) {\n        const dbSel = gE('ti-ditta'); const replyBox = gE('ti-conf-ai-reply');\n        if (!dbSel || !dbSel.value || dbSel.value === 'NEW_DB') { window.tiAlert('Seleziona prima una ditta.'); return; }\n        if (!replyBox) { window.tiAlert('Area risposta configurazione non disponibile.'); return; }\n        const photoDetail = ((gE('ti-conf-ai-photo-detail') && gE('ti-conf-ai-photo-detail').value) ? gE('ti-conf-ai-photo-detail').value : '').trim();\n        replyBox.style.display = 'block';\n        replyBox.innerHTML = 'Preparazione elenco record. Priorit\u00e0 a prodotti e servizi senza immagini specifiche.<br>Prompt base: attivit\u00e0 ditta + descrizione prodotto\/servizio.';\n        const initFd = window.buildAjaxFormData('ti_ai_config_action', {\n            db: dbSel.value,\n            mode: 'batch_ai_images',\n            tables: tableName\n        });\n        window.showProgressPopup('Generazione foto AI', 'Sto preparando l elenco dei record. Prompt base: attivit\u00e0 ditta e descrizione prodotto o servizio.', {startPercent: 2, targetPercent: 18, stepMs: 350});\n        window.fetchJsonSafe(window.tiUrl, {method:'POST', body:initFd}).then(d => {\n            if (!d.success) {\n                if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                window.hideProgressPopup(false, 'Errore preparazione batch');\n                replyBox.innerHTML = 'Errore: ' + (d.data && d.data.message ? d.data.message : 'Errore sconosciuto');\n                return;\n            }\n            const targets = (d.data && Array.isArray(d.data.targets)) ? d.data.targets : [];\n            if (!targets.length) {\n                if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                window.hideProgressPopup(true, 'Nessun elemento da elaborare', {notifyUser:false, remindSave:false});\n                replyBox.innerHTML = 'Nessun record trovato nella tabella selezionata.';\n                return;\n            }\n            let index = 0;\n            let okCount = 0;\n            const logs = [];\n            const total = targets.length;\n            window.tiBatchAIGenState = {\n                active: true,\n                pendingConfirm: false,\n                generated: [],\n                db: dbSel.value,\n                tableName: tableName,\n                total: total,\n                startedAt: Date.now()\n            };\n            const withoutAssoc = (d.data && typeof d.data.without_associations !== 'undefined') ? parseInt(d.data.without_associations, 10) : targets.filter(function(x){ return !x.has_associations; }).length;\n            const withAssoc = (d.data && typeof d.data.with_associations !== 'undefined') ? parseInt(d.data.with_associations, 10) : Math.max(0, total - withoutAssoc);\n            const detailMode = (d.data && d.data.detail_prompt_mode) ? String(d.data.detail_prompt_mode) : '';\n            const detailInfo = (photoDetail && detailMode === 'leaflet')\n                ? '<br>Istruzione aggiuntiva riconosciuta: <b>foglietto illustrativo \/ materiale informativo<\/b>.'\n                : (photoDetail ? '<br>Istruzione aggiuntiva: <b>' + window.escapeHtml(photoDetail) + '<\/b>.' : '');\n            replyBox.innerHTML = 'Generazione immagini AI pronta.<br>Prompt base: <b>attivit\u00e0 ditta + descrizione prodotto\/servizio<\/b>.<br>Altre fonti informative saranno usate solo successivamente se richieste.' + detailInfo + '<br>Priorit\u00e0: <b>' + withoutAssoc + '<\/b> prodotti\/servizi senza associazioni.<br>Successivi: <b>' + withAssoc + '<\/b> prodotti\/servizi gi\u00e0 con associazioni.';\n            const refreshDb = () => {\n                if (window.refreshCurrentConfigDb) return window.refreshCurrentConfigDb(dbSel.value);\n                const fdDb = window.buildAjaxFormData('ti_ai_get_db_data', {db: dbSel.value});\n                return window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fdDb, tiNoLongProcessSignal:true, tiShowTableReadWait:true, tiTableReadTitle:'\u23f3 Lettura tabelle configurazione', tiTableReadDetail:'Sto aggiornando le tabelle dopo l operazione.', tiTableReadItem: dbSel.value}).then(dbRes => {\n                    if (dbRes.success && dbRes.data) { window.currentDbData = dbRes.data; window.renderConfig(); }\n                }).catch(() => {});\n            };\n            const runNext = () => {\n                if (window.isLongAIProcessCancelled()) {\n                    if (window.tiBatchAIGenState && window.tiBatchAIGenState.pendingConfirm) return;\n                    replyBox.innerHTML = 'Operazione interrotta dall utente. Per completarla dovrai ripetere la generazione.';\n                    if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                    window.clearLongAIProcess();\n                    return;\n                }\n                if (index >= total) {\n                    if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                    window.updateProgressPopup(100, 'Generazione foto AI', 'Completamento e verifica salvataggio associazioni.');\n                    Promise.resolve(refreshDb()).finally(function(){\n                        window.hideProgressPopup(true, 'Generazione foto AI completata', {\n                            remindSave: false,\n                            successMessage: 'Attivita AI completata. Immagini associate e verificate: ' + okCount + ' su ' + total + '.'\n                        });\n                        replyBox.innerHTML = '\u2705 Generazione immagini AI completata.<br>Immagini associate e verificate: <b>' + okCount + '<\/b> \/ ' + total + '<br><br>' + logs.slice(0, 40).join('<br>');\n                    });\n                    return;\n                }\n                const item = targets[index];\n                const currentStep = index + 1;\n                const pct = Math.max(5, Math.min(98, Math.round((index \/ total) * 100)));\n                const priorityText = item.has_associations ? 'Elemento gi\u00e0 con associazioni' : 'Priorit\u00e0 senza associazioni';\n                window.updateProgressPopup(pct, 'Generazione foto AI', priorityText + '. Elaborazione ' + currentStep + ' \/ ' + total + ': ' + (item.name || 'Elemento'), (item.name || 'Elemento'));\n                replyBox.innerHTML = 'Generazione immagini AI in corso...<br>' + priorityText + '<br>Elaborazione ' + currentStep + ' \/ ' + total + ': <b>' + (item.name || 'Elemento') + '<\/b>';\n                const fd = window.buildAjaxFormData('ti_ai_config_action', {\n                    db: dbSel.value,\n                    mode: 'batch_ai_image_one',\n                    table: item.table || tableName,\n                    row_index: item.row_index,\n                    detail_prompt: photoDetail\n                });\n                window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(one => {\n                    const data = one && one.data ? one.data : {};\n                    if (one.success && data.generated) {\n                        okCount++;\n                        if (window.tiBatchAIGenState && Array.isArray(window.tiBatchAIGenState.generated) && (data.added_value || data.filename || data.url)) {\n                            window.tiBatchAIGenState.generated.push({\n                                table: data.table || item.table || tableName,\n                                row_index: (typeof data.row_index !== 'undefined') ? data.row_index : item.row_index,\n                                field: data.image_field || 'Immagine',\n                                value: data.added_value || data.filename || data.url,\n                                name: data.name || item.name || 'Elemento'\n                            });\n                        }\n                        if (data.fallback_logo) logs.push('\u2705 ' + (data.name || item.name || 'Elemento') + ': foto non disponibile, usato logo ditta');\n                        else logs.push('\u2705 ' + (data.name || item.name || 'Elemento') + ': immagine generata e associazione salvata');\n                    } else if (one.success && data.skipped) {\n                        logs.push('\u23ed\ufe0f ' + (data.name || item.name || 'Elemento') + ': immagine gia presente');\n                    } else {\n                        logs.push('\u274c ' + (data.name || item.name || 'Elemento') + ': ' + (data.message || 'errore'));\n                    }\n                    index++;\n                    setTimeout(runNext, 350);\n                }).catch(err => {\n                    if (err && (err.userCancelled || window.isLongAIProcessCancelled())) {\n                        if (window.tiBatchAIGenState && window.tiBatchAIGenState.pendingConfirm) return;\n                        replyBox.innerHTML = 'Operazione interrotta dall utente. Per completarla dovrai ripetere la generazione.';\n                        if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                        window.clearLongAIProcess();\n                        return;\n                    }\n                    logs.push('\u274c ' + (item.name || 'Elemento') + ': ' + err.message);\n                    index++;\n                    setTimeout(runNext, 700);\n                });\n            };\n            runNext();\n        }).catch(err => {\n            if (err && (err.userCancelled || window.isLongAIProcessCancelled())) {\n                if (window.tiBatchAIGenState && window.tiBatchAIGenState.pendingConfirm) return;\n                replyBox.innerHTML = 'Operazione interrotta dall utente. Per completarla dovrai ripetere la generazione.';\n                if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                window.clearLongAIProcess();\n                return;\n            }\n            if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n            window.hideProgressPopup(false, 'Errore batch immagini');\n            replyBox.innerHTML = 'Errore di rete: ' + err.message;\n        });\n    };\n\n    window.formatConfigAiErrorMessage = function(payload, fallbackTitle, promptText) {\n        const data = payload && payload.data ? payload.data : {};\n        const rawMsg = String(data.message || (payload && payload.message) || '').trim();\n        const lower = rawMsg.toLowerCase();\n        let reason = rawMsg || 'Errore sconosciuto durante la configurazione AI.';\n        let hint = '';\n        if (!rawMsg) {\n            hint = 'Possibile motivo: errore di interpretazione dei dati o lista troppo complessa.';\n        } else if (lower.includes('nessuna anteprima import')) {\n            hint = 'Possibile motivo: hai confermato con OK ma non esiste piu una anteprima import valida. Rigenera l anteprima dal listino e poi conferma.';\n        } else if (lower.includes('json') || lower.includes('incomplet') || lower.includes('interpretazione') || lower.includes('nessun record') || lower.includes('parser')) {\n            hint = 'Possibile motivo: dati troppo complessi, colonne non chiare, descrizioni con molte virgole\/virgolette o fasce prezzo non omogenee.';\n        } else if (lower.includes('accesso') || lower.includes('ruolo')) {\n            hint = 'Possibile motivo: il ruolo utente non consente questa modifica.';\n        }\n        const simplify = 'Semplifica la lista: usa poche colonne chiare, ad esempio Nome\/Servizio, Prezzo, Descrizione; una sola voce per riga; evita testi troppo lunghi o colonne non necessarie.';\n        let html = '<b>' + window.escapeHtml(fallbackTitle || 'Errore configurazione AI') + '<\/b>';\n        html += '<br><br><b>Dettaglio:<\/b><br>' + window.escapeHtml(reason);\n        if (hint) html += '<br><br><b>Possibile motivo:<\/b><br>' + window.escapeHtml(hint);\n        html += '<br><br><b>Come correggere:<\/b><br>' + window.escapeHtml(simplify);\n        return html;\n    };\n\n    window.setConfigAiReplyHtml = function(replyBox, html, plainText) {\n        if (!replyBox) return;\n        const safeHtml = String(html || '');\n        const rawText = plainText != null ? String(plainText) : safeHtml.replace(\/<br\\s*\\\/?>\/gi, '\\n').replace(\/<[^>]+>\/g, '');\n        const btnId = 'ti-conf-ai-copy-' + Date.now() + '-' + Math.floor(Math.random() * 10000);\n        replyBox.style.display = 'block';\n        replyBox.innerHTML = '<div class=\"ti-conf-ai-copy-box\">'\n            + '<div class=\"ti-conf-ai-copy-content\">' + safeHtml + '<\/div>'\n            + '<div style=\"margin-top:10px;display:flex;justify-content:flex-end;\">'\n            + '<button type=\"button\" id=\"' + btnId + '\" class=\"ti-btn ti-conf-ai-copy-btn\" style=\"font-size:11px;padding:7px 10px;background:#2563eb;color:#fff;\">Copia testo<\/button>'\n            + '<\/div><\/div>';\n        const btn = document.getElementById(btnId);\n        if (btn) {\n            btn.addEventListener('click', function(){\n                const box = btn.closest('.ti-conf-ai-copy-box');\n                const content = box ? box.querySelector('.ti-conf-ai-copy-content') : null;\n                const txt = (content ? content.innerText : rawText || '').trim();\n                const done = function(){ const old = btn.innerText; btn.innerText = 'Copiato'; setTimeout(function(){ btn.innerText = old; }, 1200); };\n                if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(txt).then(done).catch(function(){ window.prompt('Copia il testo:', txt); });\n                else window.prompt('Copia il testo:', txt);\n            });\n        }\n    };\n\n    \/\/ 30.9.330: stato persistente dei processi AI di configurazione.\n    \/\/ Se la AI chiede specifiche o non aggiorna record, non e un completamento: la richiesta resta visibile anche dopo F5.\n    window.tiConfigAiStateKey330 = function(db) {\n        return 'ti_ai_ss_config_ai_state_330_' + String(db || '').replace(\/[^a-zA-Z0-9_.-]\/g, '_');\n    };\n    window.saveConfigAiState330 = function(db, prompt, reply, status) {\n        try {\n            if (!db) return;\n            const payload = {\n                db: String(db || ''),\n                prompt: String(prompt || ''),\n                reply: String(reply || ''),\n                status: String(status || ''),\n                ts: Date.now()\n            };\n            localStorage.setItem(window.tiConfigAiStateKey330(db), JSON.stringify(payload));\n            sessionStorage.setItem('ti_ai_ss_last_config_db_330', String(db || ''));\n        } catch(e) {}\n    };\n    window.clearConfigAiState330 = function(db) {\n        try { if (db) localStorage.removeItem(window.tiConfigAiStateKey330(db)); } catch(e) {}\n    };\n    window.restoreConfigAiState330 = function(force) {\n        try {\n            const dbSel = gE('ti-ditta');\n            const db = dbSel ? String(dbSel.value || '') : String(sessionStorage.getItem('ti_ai_ss_last_config_db_330') || '');\n            if (!db || db === 'NEW_DB') return false;\n            const raw = localStorage.getItem(window.tiConfigAiStateKey330(db));\n            if (!raw) return false;\n            const payload = JSON.parse(raw);\n            if (!payload || payload.db !== db) return false;\n            \/\/ Evita ripristini vecchi oltre 24 ore.\n            if (payload.ts && (Date.now() - payload.ts) > 86400000) { localStorage.removeItem(window.tiConfigAiStateKey330(db)); return false; }\n            const replyBox = gE('ti-conf-ai-reply');\n            const promptBox = gE('ti-conf-ai-prompt');\n            if (!replyBox) return false;\n            const currentText = String(replyBox.innerText || replyBox.textContent || '').trim();\n            if (!force && currentText && !\/fammi pensare|elaborando|attendere\/i.test(currentText)) return false;\n            if (promptBox && !String(promptBox.value || '').trim() && payload.prompt) promptBox.value = payload.prompt;\n            let label = '';\n            if (payload.status === 'awaiting_user_details') label = '<b>Richiesta AI in attesa di specifiche utente<\/b><br><br>';\n            else if (payload.status === 'running') label = '<b>Elaborazione precedente non verificata dopo refresh<\/b><br><br>';\n            replyBox.style.display = 'block';\n            window.setConfigAiReplyHtml(replyBox, label + window.escapeHtml(String(payload.reply || '')).replace(\/\\n\/g, '<br>'), String(payload.reply || ''));\n            return true;\n        } catch(e) { return false; }\n    };\n    window.finishConfigAiAwaitingUser330 = function(replyBox, db, prompt, replyText) {\n        if (window.hideProgressPopup) window.hideProgressPopup(false, 'Chiarimento richiesto', {notifyUser:false});\n        if (replyBox) {\n            replyBox.style.display = 'block';\n            window.setConfigAiReplyHtml(replyBox, window.escapeHtml(String(replyText || '')).replace(\/\\n\/g, '<br>'), String(replyText || ''));\n        }\n        window.saveConfigAiState330(db, prompt, replyText, 'awaiting_user_details');\n        try { window.showPluginModal('ti-conf-ov'); } catch(e) {}\n    };\n    (function installConfigAiRestore330(){\n        if (window.__tiConfigAiRestore330Installed) return;\n        window.__tiConfigAiRestore330Installed = true;\n        const prevOpen = window.openModal;\n        if (typeof prevOpen === 'function') {\n            window.openModal = function(id) {\n                const ret = prevOpen.apply(this, arguments);\n                if (id === 'ti-conf-ov') setTimeout(function(){ window.restoreConfigAiState330(false); }, 250);\n                return ret;\n            };\n        }\n        document.addEventListener('DOMContentLoaded', function(){ setTimeout(function(){ window.restoreConfigAiState330(false); }, 700); });\n        setTimeout(function(){ window.restoreConfigAiState330(false); }, 1200);\n    })();\n\n    window.runConfigBot = function() {\n        const box = gE('ti-conf-ai-prompt'); const replyBox = gE('ti-conf-ai-reply'); const btn = gE('ti-conf-ai-send');\n        const dbSel = gE('ti-ditta');\n        if (!box || !replyBox || !btn || !dbSel || !dbSel.value || dbSel.value === 'NEW_DB') { window.tiAlert('Seleziona prima una ditta.'); return; }\n        const prompt = (box.value || '').trim();\n        if (!prompt) { window.tiAlert('Scrivi una istruzione di configurazione.'); return; }\n        const roleTxt = String(window.tiRole || '').toLowerCase();\n        const isDbAdmin = roleTxt.includes('amministratore') || window.tiUser === 'SSGlobalAdmin';\n        const wantsStructure = \/struttura\\s+database|adegua\\s+struttura|allinea\\s+struttura|schema\\s+template|aggiungi\\s+campo|rimuovi\\s+campo|nuova\\s+tabella|elimina\\s+tabella\/i.test(prompt);\n        if (wantsStructure && !isDbAdmin) {\n            replyBox.style.display = 'block';\n            replyBox.innerHTML = 'Le modifiche della struttura del database non sono possibili per il tuo ruolo. Richiedi l intervento di un amministratore.';\n            return;\n        }\n        btn.disabled = true; const oldText = btn.innerText; btn.innerText = '\u23f3 Elaborazione...';\n        replyBox.style.display = 'block'; replyBox.innerHTML = window.getConfigBotWaitingHtml('Fammi pensare', 'Sto verificando database della ditta, documenti AI, istruzioni, pattern anonimi non sensibili e l eventuale file allegato.');\n        window.saveConfigAiState330(dbSel.value, prompt, 'Elaborazione avviata. Se hai aggiornato la pagina prima della risposta finale, rientra in Configurazione e ripeti o verifica i record: la richiesta resta qui come promemoria.', 'running');\n        window.showProgressPopup('Configurazione AI', 'Sto elaborando le istruzioni e l\\'eventuale file allegato. La percentuale \u00e8 stimata.', {startPercent: 2, targetPercent: 94, stepMs: 800});\n        const fd = window.buildAjaxFormData('ti_ai_config_action', {\n            db: dbSel.value,\n            mode: 'ai_ops',\n            prompt: prompt\n        });\n        if (window.configBotFile) fd.append('file_url', window.configBotFile);\n        if (window.tiConfigImportToken) fd.append('import_token', window.tiConfigImportToken);\n        window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d => {\n            btn.disabled = false; btn.innerText = oldText;\n            if (!d.success) {\n                const maybeData = d && d.data ? d.data : {};\n                if (maybeData && maybeData.clarify_request && maybeData.reply) {\n                    window.finishConfigAiAwaitingUser330(replyBox, dbSel.value, prompt, String(maybeData.reply));\n                    return;\n                }\n                window.hideProgressPopup(false, 'Errore configurazione AI');\n                replyBox.style.display = 'block';\n                window.setConfigAiReplyHtml(replyBox, window.formatConfigAiErrorMessage(d, 'Errore configurazione AI', prompt));\n                return;\n            }\n            const replyText = d.data && d.data.reply ? d.data.reply : 'Operazione completata.';\n            if (d.data && d.data.awaiting_confirm) {\n                window.hideProgressPopup(true, 'Anteprima import pronta', {notifyUser:false, remindSave:false, successMessage:'Anteprima pronta.'});\n                replyBox.style.display = 'none';\n                window.tiConfigImportToken = d.data.import_token || d.data.preview_token || window.tiConfigImportToken || null;\n                window.showConfigImportPreview(replyText, window.tiConfigImportToken);\n                return;\n            }\n            if (d.data && d.data.needs_reload) window.tiConfigImportToken = null;\n            const cleanReplyText = replyText.replace(\/\\[NO_VOICE\\]\/g, '').trim();\n            const updatedCount = (d.data && typeof d.data.updated_count !== 'undefined') ? (parseInt(d.data.updated_count, 10) || 0) : null;\n            const processStatus = d.data && d.data.process_status ? String(d.data.process_status) : '';\n            const replyLower330 = cleanReplyText.toLowerCase();\n            const needsUserDetails330 = !!(d.data && d.data.clarify_request)\n                || processStatus === 'awaiting_user_details'\n                || (updatedCount === 0 && !d.data.needs_reload && \/(indicami|specific|quali record|quale record|criterio piu preciso|criterio pi\u00f9 preciso|non ho trovato|record aggiornati:\\s*0)\/i.test(replyLower330));\n            if (needsUserDetails330) {\n                window.finishConfigAiAwaitingUser330(replyBox, dbSel.value, prompt, cleanReplyText);\n                return;\n            } else {\n                window.hideProgressPopup(true, 'Configurazione AI completata', {remindSave:true, successMessage:'Attivita AI di configurazione completata.'});\n            }\n            replyBox.style.display = 'block';\n            window.setConfigAiReplyHtml(replyBox, cleanReplyText);\n            window.saveConfigAiState330(dbSel.value, prompt, cleanReplyText, (d.data && d.data.needs_reload) ? 'completed_with_updates' : 'completed');\n            if (d.data && d.data.needs_reload) {\n                const fdDb = window.buildAjaxFormData('ti_ai_get_db_data', {db: dbSel.value});\n                fetch(window.tiUrl, {method:'POST', body:window.ensureAjaxActionField(fdDb)}).then(r2 => r2.json()).then(dbRes => {\n                    if (dbRes.success && dbRes.data) { window.currentDbData = dbRes.data; window.renderConfig(); }\n                });\n            }\n        }).catch(err => {\n            btn.disabled = false; btn.innerText = oldText;\n            if (err && (err.userCancelled || window.isLongAIProcessCancelled())) { replyBox.style.display = 'block'; window.setConfigAiReplyHtml(replyBox, 'Operazione interrotta dall utente. Per completarla dovrai ripetere la richiesta.'); window.clearLongAIProcess(); return; }\n            window.hideProgressPopup(false, 'Errore configurazione AI');\n            replyBox.style.display = 'block';\n            window.setConfigAiReplyHtml(replyBox, window.formatConfigAiErrorMessage({message: 'Errore di comunicazione o risposta server non valida: ' + (err && err.message ? err.message : 'errore sconosciuto')}, 'Errore configurazione AI', prompt));\n        });\n    };\n\n    window.openAtecoSearch = function(targetId) {\n        const target = gE('ateco-target-id');\n        const input = gE('ateco-in');\n        const res = gE('ateco-res');\n        const ov = gE('ti-ateco-ov');\n        if (!target || !input || !res || !ov) {\n            if (window.tiAlert) window.tiAlert('Ricerca ATECO non disponibile: ricarica la configurazione e riprova.');\n            return false;\n        }\n        target.value = targetId || '';\n        input.value = '';\n        res.innerHTML = '<i>Scrivi l\\'attivit\u00e0 da cercare...<\/i>';\n        try { window.openModal('ti-ateco-ov'); } catch(e) {}\n        try {\n            ov.setAttribute('data-ti-config-child-modal', '1');\n            if (window.preparePluginModal) window.preparePluginModal(ov);\n            ov.style.setProperty('display', 'flex', 'important');\n            ov.style.setProperty('z-index', '2147483500', 'important');\n            ov.removeAttribute('aria-hidden');\n            ov.setAttribute('data-ti-force-front', '1');\n            if (window.updatePluginScrollLock) window.updatePluginScrollLock();\n            setTimeout(function(){ try { input.focus({preventScroll:true}); } catch(e) {} }, 60);\n        } catch(e) {}\n        return false;\n    };\n    window.searchAteco = function() {\n        const query = gE('ateco-in').value; if (!query) return;\n        const resBox = gE('ateco-res'); resBox.innerHTML = '<i>Ricerca in corso con l\\'IA... \u23f3<\/i>';\n        const fd = new FormData(); fd.append('ti_action', 'ti_ai_chat_start'); fd.append('db', gE('ti-ditta').value || 'NEW_DB'); fd.append('text', \"CERCA ATECO: \" + query);\n        fetch(window.tiUrl, {method: 'POST', body: fd}).then(r=>r.json()).then(res=>{ if (res.success) resBox.innerHTML = res.data.reply; else resBox.innerHTML = \"<span style='color:#ef4444;'>Errore ricerca ATECO.<\/span>\"; });\n    };\n    window.selectAteco = function(codice, desc) { const targetId = gE('ateco-target-id').value; if (targetId) { const inputEl = gE(targetId); if (inputEl) { inputEl.value = desc; inputEl.dispatchEvent(new Event('change')); } } window.closeModal('ti-ateco-ov'); };\n\n    window.showUploadChoice = function(target) { window.targetUploadContext = target; window.openModal('ti-up-choice-ov'); };\n    window.chooseLocal = function() { window.closeModal('ti-up-choice-ov'); const fileIn = gE('ti-file-in'); fileIn.dataset.target = window.targetUploadContext; fileIn.value = ''; setTimeout(() => fileIn.click(), 100); };\n    \n    window.chooseAI = function() { \n        window.closeModal('ti-up-choice-ov'); \n        let row = null;\n        if (window.targetUploadContext && typeof window.targetUploadContext === 'object' && window.targetUploadContext.tbl) {\n            row = window.currentDbData.Tabelle[window.targetUploadContext.tbl][window.targetUploadContext.idx] || null;\n        }\n        window.populateAIGenPrompt(row);\n        if (gE('ai-gen-detail')) gE('ai-gen-detail').value = '';\n        gE('ai-gen-res').style.display = 'none'; \n        gE('ai-gen-btn').style.display = 'block'; \n        window.openModal('ti-ai-gen-ov'); \n    };\n    \n    window.doAIGen = function() {\n        let prompt = gE('ai-gen-prompt').value.trim();\n        const detailInstructions = (gE('ai-gen-detail') && gE('ai-gen-detail').value) ? gE('ai-gen-detail').value.trim() : '';\n        if(!prompt && !detailInstructions) { window.tiAlert(\"Inserisci una descrizione!\"); return; }\n        if (!prompt && detailInstructions) prompt = detailInstructions;\n        if (!\/^Attivit\u00e0 ditta:\/i.test(prompt) && !\/^Verifica anzitutto\/i.test(prompt)) { prompt = window.buildAIGenPrompt(prompt); gE('ai-gen-prompt').value = prompt; }\n        if (detailInstructions) {\n            const detailBlock = ' Istruzioni di dettaglio per la generazione foto: ' + detailInstructions + '.';\n            if (prompt.indexOf(detailInstructions) === -1) prompt += detailBlock;\n        }\n        const btn = gE('ai-gen-btn'); btn.innerText = \"\u23f3 Generazione in corso... (10-20 sec)\"; btn.disabled = true;\n        window.showProgressPopup(\"AI Image Generator\", \"Sto generando l'immagine richiesta. La percentuale e stimata fino alla risposta del servizio.\", {startPercent: 6, targetPercent: 93, stepMs: 650});\n        const dbVal = gE('ti-ditta').value || 'Global'; const fd = new FormData(); fd.append('ti_action', 'ti_ai_generate_media'); fd.append('db', dbVal); fd.append('prompt', prompt);\n        window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d=>{\n            btn.innerText = \"Genera Immagine\"; btn.disabled = false;\n            if(d.success) { window.hideProgressPopup(true, 'Immagine generata', {remindSave:true, successMessage:'Attivita AI completata. Immagine generata correttamente.'}); window.currentGenFilename = d.data.filename; gE('ai-gen-img').src = d.data.url; gE('ai-gen-btn').style.display = 'none'; gE('ai-gen-res').style.display = 'block'; } else { window.hideProgressPopup(false, 'Errore generazione immagine'); window.tiAlert((d && d.data && d.data.message) ? d.data.message : 'Errore generazione immagine.'); } \n        }).catch(e => { btn.innerText = \"Genera Immagine\"; btn.disabled = false; if (e && (e.userCancelled || window.isLongAIProcessCancelled())) { window.clearLongAIProcess(); return; } window.hideProgressPopup(false, 'Errore generazione immagine'); const msg = (e && e.message) ? e.message : 'Errore di rete.'; window.tiAlert(msg); });\n    };\n    \n    window.resetAIGen = function() { gE('ai-gen-res').style.display = 'none'; gE('ai-gen-btn').style.display = 'block'; gE('ai-gen-img').src = ''; if (gE('ai-gen-detail')) gE('ai-gen-detail').value = ''; window.currentGenFilename = null; };\n    \n    window.useAIGen = function() {\n        if(!window.currentGenFilename) return; const dbVal = gE('ti-ditta').value || 'Global'; const tgt = window.targetUploadContext; window.closeModal('ti-ai-gen-ov');\n        if(tgt === 'chat') {\n            if(window.addMsg) window.addMsg('ai',\"\u23f3 Elaborazione file AI...\"); \n            const fd = new FormData(); fd.append('ti_action', 'ti_ai_media_upload'); fd.append('db', dbVal); fd.append('tbl', 'Chat'); fd.append('temp_file', window.currentGenFilename);\n            window.fetchJsonSafe(window.tiUrl,{method:'POST',body:fd}).then(d=>{ if(d.success) { window.lastUploadedFile = d.data.url; const promptText = `Ho generato questa immagine. Analizzala.`; window.rememberLastFileContext(window.lastUploadedFile, promptText, false); gE('ti-msg').value = promptText; gE('ti-send').click(); } else if(window.addMsg) window.addMsg('ai',`\u26a0\ufe0f Errore salvataggio file AI.`); });\n        } else if (typeof tgt === 'object') {\n            let ctx = tgt;\n            const fd = new FormData(); fd.append('ti_action', 'ti_ai_media_upload'); fd.append('db', dbVal); fd.append('tbl', ctx.tbl); fd.append('temp_file', window.currentGenFilename); fd.append('record_label', window.getRecordLabelForUpload(ctx.tbl, window.currentDbData && window.currentDbData.Tabelle && window.currentDbData.Tabelle[ctx.tbl] ? window.currentDbData.Tabelle[ctx.tbl][ctx.idx] : null, ctx.tbl));\n            window.fetchJsonSafe(window.tiUrl,{method:'POST',body:fd}).then(d=>{ \n                if(d.success) { \n                    let oldVal = window.currentDbData.Tabelle[ctx.tbl][ctx.idx][ctx.key] || '';\n                    let arr = oldVal.split(',').map(f=>f.trim()).filter(f=>f);\n                    arr.push(d.data.name);\n                    window.currentDbData.Tabelle[ctx.tbl][ctx.idx][ctx.key] = arr.join(', ');\n                    if(!window.modifiedFields) window.modifiedFields = new Set();\n                    window.modifiedFields.add(`${ctx.tbl}-${ctx.idx}-${ctx.key}`); if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState();\n                    window.renderConfig();\n                    window.openFileManager(ctx.tbl, ctx.idx, ctx.key);\n                } else window.tiAlert(\"Errore: \" + d.data.message); \n            });\n        } else {\n            let tblName = 'Config'; if(tgt.startsWith('cfg-img-')) { const match = tgt.match(\/cfg-img-([^-]+)-\/); if(match) tblName = match[1]; } else if(tgt === 'p-foto') tblName = 'Utenti'; \n            const fd = new FormData(); fd.append('ti_action', 'ti_ai_media_upload'); fd.append('db', dbVal); fd.append('tbl', tblName); fd.append('temp_file', window.currentGenFilename); const recordLabel = (tgt === 'p-foto') ? (gE('p-user').value || gE('p-utente').value || 'utente') : tblName; fd.append('record_label', recordLabel);\n            window.fetchJsonSafe(window.tiUrl,{method:'POST',body:fd}).then(d=>{ if(d.success) { const inp = gE(tgt); if(inp) { inp.value = d.data.name; inp.dispatchEvent(new Event('change')); if(tgt.startsWith('cfg-')) window.renderConfig(); } window.tiAlert(\"Immagine salvata!\"); } else window.tiAlert(\"Errore: \" + d.data.message); });\n        }\n    };\n\n    window.handleFileSelect = function(e) {\n        if(!e.target.files.length) return;\n        const tgt = e.target.dataset.target;\n        const dbVal = gE('ti-ditta').value || 'Global';\n        const fileObj = e.target.files[0];\n\n        if (tgt === 'manager') {\n            let ctx = window.targetUploadContext;\n            const fd = new FormData(); fd.append('ti_action', 'ti_ai_media_upload'); fd.append('db', dbVal); fd.append('file', fileObj); fd.append('tbl', ctx.tbl); fd.append('record_label', window.getRecordLabelForUpload(ctx.tbl, window.currentDbData && window.currentDbData.Tabelle && window.currentDbData.Tabelle[ctx.tbl] ? window.currentDbData.Tabelle[ctx.tbl][ctx.idx] : null, ctx.tbl));\n            window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d=>{\n                if(d.success) {\n                    let oldVal = window.currentDbData.Tabelle[ctx.tbl][ctx.idx][ctx.key] || '';\n                    let arr = oldVal.split(',').map(f=>f.trim()).filter(f=>f);\n                    arr.push(d.data.name);\n                    window.currentDbData.Tabelle[ctx.tbl][ctx.idx][ctx.key] = arr.join(', ');\n                    if(!window.modifiedFields) window.modifiedFields = new Set();\n                    window.modifiedFields.add(`${ctx.tbl}-${ctx.idx}-${ctx.key}`); if (window.updateConfigSaveButtonState) window.updateConfigSaveButtonState();\n                    window.renderConfig();\n                    window.openFileManager(ctx.tbl, ctx.idx, ctx.key);\n                } else {\n                    window.tiAlert('Err: ' + (d.data && d.data.message ? d.data.message : 'Errore upload.'));\n                }\n                e.target.dataset.target = ''; e.target.value = '';\n            }).catch(err => {\n                window.tiAlert('Errore di rete: ' + err.message);\n                e.target.dataset.target = ''; e.target.value = '';\n            });\n\n        } else if (tgt === 'config-bot-file') {\n            const fd = new FormData();\n            fd.append('ti_action', 'ti_ai_media_upload');\n            fd.append('db', dbVal);\n            fd.append('file', fileObj);\n            fd.append('tbl', 'Chat');\n            window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d=>{\n                if (d.success) {\n                    window.configBotFile = d.data.url;\n                    const fileBox = gE('ti-conf-ai-file');\n                    const promptBox = gE('ti-conf-ai-prompt');\n                    const replyBox = gE('ti-conf-ai-reply');\n                    const fileName = d.data.name || fileObj.name || 'file';\n                    if (fileBox) {\n                        fileBox.style.display = 'block';\n                        fileBox.innerHTML = `\ud83d\udcce <b>File caricato per AI:<\/b> ${fileName}<br><span style=\"font-size:13px;color:#cbd5e1;\">Adesso indica cosa deve fare la AI con il contenuto del file.<\/span>`;\n                    }\n                    if (replyBox) {\n                        replyBox.style.display = 'block';\n                        replyBox.innerHTML = '\u2705 File caricato correttamente.';\n                    }\n                    if (promptBox && !promptBox.value.trim()) {\n                        promptBox.value = 'Ho caricato un file per la AI. Analizza il contenuto del file e applica la mia prossima istruzione operativa.';\n                    }\n                    if (promptBox) promptBox.focus();\n                } else {\n                    window.tiAlert('Err: ' + (d.data && d.data.message ? d.data.message : 'Errore upload file.'));\n                }\n                e.target.dataset.target = ''; e.target.value = '';\n            }).catch(err => {\n                window.tiAlert('Errore di rete: ' + err.message);\n                e.target.dataset.target = ''; e.target.value = '';\n            });\n\n        } else if (tgt === 'purchase-import-file') {\n            const fd = new FormData();\n            fd.append('ti_action', 'ti_ai_media_upload');\n            fd.append('db', dbVal);\n            fd.append('file', fileObj);\n            fd.append('tbl', 'Chat');\n            window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d=>{\n                if (d.success) {\n                    const fileName = d.data.name || fileObj.name || 'file';\n                    if (window.tiAlert) window.tiAlert('File acquisti caricato: ' + fileName + '. Preparo import acquisti.');\n                    window.startPurchaseFileImport(d.data.url, fileName);\n                } else {\n                    window.tiAlert('Err: ' + (d.data && d.data.message ? d.data.message : 'Errore upload file acquisti.'));\n                }\n                e.target.dataset.target = ''; e.target.value = '';\n            }).catch(err => {\n                window.tiAlert('Errore di rete: ' + err.message);\n                e.target.dataset.target = ''; e.target.value = '';\n            });\n\n        } else if (tgt && tgt !== 'chat') {\n            const tblMatch = tgt.match(\/cfg-img-([^-]+)-\/);\n            const tblName = tblMatch ? tblMatch[1] : (tgt === 'p-foto' ? 'Utenti' : 'Config');\n            const fd = new FormData(); fd.append('ti_action', 'ti_ai_media_upload'); fd.append('db', dbVal); fd.append('file', fileObj); fd.append('tbl', tblName); const recordLabel = (tgt === 'p-foto') ? (gE('p-user').value || gE('p-utente').value || 'utente') : tblName; fd.append('record_label', recordLabel);\n            window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d=>{\n                if(d.success) {\n                    const inp = gE(tgt);\n                    if(inp) {\n                        inp.value = d.data.name;\n                        inp.dispatchEvent(new Event('change'));\n                        if(tgt.startsWith('cfg-')) window.renderConfig();\n                    }\n                    window.tiAlert('Memoria aggiornata!');\n                } else {\n                    window.tiAlert('Err: ' + (d.data && d.data.message ? d.data.message : 'Errore upload.'));\n                }\n                e.target.dataset.target = ''; e.target.value = '';\n            }).catch(err => {\n                window.tiAlert('Errore di rete: ' + err.message);\n                e.target.dataset.target = ''; e.target.value = '';\n            });\n        } else {\n            const runUpload = function(uploadFile){\n                const realFile = (uploadFile instanceof File) ? uploadFile : fileObj;\n                const fd = new FormData(); fd.append('ti_action', 'ti_ai_media_upload'); fd.append('db', dbVal); fd.append('file', realFile, realFile.name || fileObj.name || 'upload'); fd.append('tbl', 'Chat');\n                if(window.addMsg) window.addMsg('ai', '\u23f3 Analisi in corso...');\n                window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d=>{\n                    if(d.success) {\n                        window.lastUploadedFile = d.data.url;\n                        window.startUserUploadedFileAI(window.lastUploadedFile, realFile.name || fileObj.name || '');\n                    } else {\n                        window.tiAlert('Err: ' + (d.data && d.data.message ? d.data.message : 'Errore upload.'));\n                    }\n                    e.target.dataset.target = ''; e.target.value = '';\n                }).catch(err => {\n                    window.tiAlert('Errore di rete: ' + err.message);\n                    e.target.dataset.target = ''; e.target.value = '';\n                });\n            };\n            if (typeof window.prepareUploadFileForAI === 'function') {\n                window.prepareUploadFileForAI(fileObj).then(runUpload);\n            } else {\n                runUpload(fileObj);\n            }\n        }\n    };\n\n    window.getConfigButtonTableName = function(btn) {\n        let tbl = '';\n        try {\n            const enc = btn && btn.getAttribute ? (btn.getAttribute('data-tbl-enc') || '') : '';\n            if (enc) tbl = decodeURIComponent(enc);\n        } catch(e) { tbl = ''; }\n        try {\n            if (!tbl && btn && btn.closest) {\n                const det = btn.closest('details.ti-cfg-details');\n                if (det && det.dataset) tbl = det.dataset.tbl || '';\n            }\n        } catch(e) {}\n        return window.resolveConfigTableName ? window.resolveConfigTableName(tbl) : tbl;\n    };\n\n    window.handleConfigDeleteRowClick = function(ev, btn) {\n        try { if (ev) { ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); } } catch(e) {}\n        const tbl = window.getConfigButtonTableName ? window.getConfigButtonTableName(btn) : '';\n        let idx = NaN;\n        try { idx = parseInt((btn && btn.getAttribute && (btn.getAttribute('data-row-idx') || btn.getAttribute('data-idx'))) || '', 10); } catch(e) { idx = NaN; }\n        if (isNaN(idx) && btn && btn.closest) {\n            try { const tr = btn.closest('tr[data-row-idx]'); if (tr) idx = parseInt(tr.getAttribute('data-row-idx') || '', 10); } catch(e) {}\n        }\n        if (!tbl) { window.tiAlert && window.tiAlert('Tabella non riconosciuta. Riapri la configurazione e riprova.'); return false; }\n        if (isNaN(idx)) { window.tiAlert && window.tiAlert('Riga non riconosciuta. Riapri la configurazione e riprova.'); return false; }\n        if (typeof window.delTableRow === 'function') window.delTableRow(tbl, idx, btn);\n        return false;\n    };\n\n    window.handleConfigDeleteSelectedClick = function(ev, btn) {\n        try { if (ev) { ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); } } catch(e) {}\n        const tbl = window.getConfigButtonTableName ? window.getConfigButtonTableName(btn) : '';\n        const safeCls = (btn && btn.getAttribute) ? (btn.getAttribute('data-safe-cls') || '') : '';\n        if (!tbl) { window.tiAlert && window.tiAlert('Tabella non riconosciuta. Riapri la configurazione e riprova.'); return false; }\n        if (typeof window.delBulk === 'function') window.delBulk(tbl, safeCls, btn);\n        return false;\n    };\n\n    window.getConfigTableScope = function(tName, sourceEl) {\n        let scope = null;\n        try {\n            if (sourceEl && sourceEl.closest) scope = sourceEl.closest('details.ti-cfg-details');\n            if (scope && scope.dataset && String(scope.dataset.tbl || '') === String(tName || '')) return scope;\n            document.querySelectorAll('details.ti-cfg-details').forEach(function(det){\n                if (!scope && det.dataset && String(det.dataset.tbl || '') === String(tName || '')) scope = det;\n            });\n        } catch(e) {}\n        return scope || document;\n    };\n\n    window.getSelectedConfigRowIndexes = function(tName, sC, sourceEl) {\n        const scope = window.getConfigTableScope ? window.getConfigTableScope(tName, sourceEl) : document;\n        let checks = [];\n        try { checks = Array.from(scope.querySelectorAll('input.ti-config-row-select:checked')); } catch(e) { checks = []; }\n        if (!checks.length && sC) {\n            try { checks = Array.from(scope.querySelectorAll('.ti-cb-' + sC + ':checked')); } catch(e) { checks = []; }\n        }\n        return checks.map(function(x){\n            const raw = (x && x.dataset && x.dataset.rowIdx != null) ? x.dataset.rowIdx : (x ? x.value : '');\n            return parseInt(raw, 10);\n        }).filter(function(n){ return !isNaN(n); });\n    };\n\n    window.toggleAllTI = function(ck, sC, sourceEl) {\n        const tbl = (ck && ck.closest && ck.closest('details.ti-cfg-details') && ck.closest('details.ti-cfg-details').dataset) ? ck.closest('details.ti-cfg-details').dataset.tbl : '';\n        const scope = window.getConfigTableScope ? window.getConfigTableScope(tbl, sourceEl || ck) : document;\n        let boxes = [];\n        try { boxes = Array.from(scope.querySelectorAll('input.ti-config-row-select')); } catch(e) { boxes = []; }\n        if (!boxes.length && sC) {\n            try { boxes = Array.from(scope.querySelectorAll('.ti-cb-' + sC)); } catch(e) { boxes = []; }\n        }\n        boxes.forEach(function(c){ if (c && !c.disabled) c.checked = !!(ck && ck.checked); });\n    };\n    \n    window.delBulk = function(tN, sC, sourceEl) { \n        tN = window.resolveConfigTableName ? window.resolveConfigTableName(tN) : tN;\n        const low = String(tN || '').toLowerCase();\n        if ((low === 'utenti' || low === 'operatori') && !window.canPhysicallyDeleteUserRows()) {\n            window.tiAlert('Solo amministratore o configuratore possono cancellare fisicamente utenti o operatori dal database.');\n            return;\n        }\n        const selectedIndexes = window.getSelectedConfigRowIndexes ? window.getSelectedConfigRowIndexes(tN, sC, sourceEl) : [];\n        if(selectedIndexes.length===0) { window.tiAlert('Seleziona almeno una riga da eliminare nella tabella corrente.'); return; }\n        const doDelete = function(){ \n            if (window.deleteConfigRowsByIndexes) {\n                const ok = window.deleteConfigRowsByIndexes(tN, selectedIndexes, {render:true});\n                if (ok) window.tiAlert('Righe cancellate dalla configurazione corrente: ' + (window.tiLastConfigDeleteCount || selectedIndexes.length) + '. Premi Salva Modifiche per aggiornare il database.');\n                else window.tiAlert('Nessuna riga eliminata. Righe cancellate: 0. Verifica che le righe selezionate siano ancora presenti e che la tabella possa essere svuotata.');\n                return;\n            }\n            if(!Array.isArray(window.currentDbData.Tabelle[tN])) window.currentDbData.Tabelle[tN]=Object.values(window.currentDbData.Tabelle[tN] || {}); \n            if (window.recordConfigDeletedRowsManifest) window.recordConfigDeletedRowsManifest(tN, window.currentDbData.Tabelle[tN], selectedIndexes);\n            selectedIndexes.sort((a,b)=>b-a).forEach(i=>{ window.currentDbData.Tabelle[tN].splice(i,1); }); \n            window.reindexTableRows(tN);\n            window.markTableRowsModified(tN);\n            if (window.syncConfigTableAfterRowMutation) window.syncConfigTableAfterRowMutation(tN);\n            window.renderConfig(); \n        };\n        if (typeof window.tiConfirmYesNoAction === 'function') window.tiConfirmYesNoAction('Eliminare ' + selectedIndexes.length + ' righe selezionate dalla tabella ' + String(tN || '') + '?', doDelete, null, 'ELIMINA', 'ANNULLA');\n        else window.tiConfirm('Eliminare ' + selectedIndexes.length + ' righe?', doDelete);\n    };\n\n    window.bindConfigDeleteActions = function() {\n        if (window.__tiConfigDeleteActionsBound) return;\n        window.__tiConfigDeleteActionsBound = true;\n        document.addEventListener('click', function(ev){\n            const target = ev && ev.target && ev.target.closest ? ev.target : null;\n            if (!target) return;\n            const rowBtn = target.closest('[data-ti-config-delete-row=\"1\"], .ti-config-delete-row-btn');\n            if (rowBtn && rowBtn.closest && rowBtn.closest('details.ti-cfg-details')) {\n                ev.preventDefault();\n                ev.stopPropagation();\n                if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();\n                if (typeof window.handleConfigDeleteRowClick === 'function') window.handleConfigDeleteRowClick(ev, rowBtn);\n                return false;\n            }\n            const bulkBtn = target.closest('[data-ti-config-delete-selected=\"1\"], .ti-config-delete-selected-btn');\n            if (bulkBtn && bulkBtn.closest && bulkBtn.closest('details.ti-cfg-details')) {\n                ev.preventDefault();\n                ev.stopPropagation();\n                if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();\n                if (typeof window.handleConfigDeleteSelectedClick === 'function') window.handleConfigDeleteSelectedClick(ev, bulkBtn);\n                return false;\n            }\n        }, true);\n    };\n    window.bindConfigDeleteActions();\n\n    \/* 30.9.224 - Stato sessione pesante lato server per prenotazioni multi-servizio: evita cookie troppo grandi\/ERR_HTTP2_PROTOCOL_ERROR. *\/\n    \/* 30.9.225 - Comunicazioni e report per amministratori\/configuratori\/responsabili in popup separato in primo piano con pulsante Copia testo. *\/\n    \/* 30.9.233 - Popup informativi attivi ma non intercettano le tabelle operative ordine; ruoli privilegiati usano anche ordine e tabella ordine. *\/\n    \/* 30.9.223 - Salvataggio parziale tabelle valide con warning mirato; pulsante per aprire la prima tabella da correggere. *\/\n\n\n    window.lastUserPromptText = '';\n    window.restoreLastUserPromptText = function() {\n        const m = gE('ti-msg');\n        if (m && window.lastUserPromptText) {\n            m.value = window.lastUserPromptText;\n            try { m.focus(); } catch(e) {}\n        }\n    };\n\n    \/\/ v30.9.235 - Il campo input del chatbot utente viene sempre svuotato dopo ogni invio utente e dopo ogni risposta AI.\n    window.tiClearUserChatInput = function() {\n        try {\n            const msgInp = gE('ti-msg');\n            if (!msgInp) return;\n            msgInp.value = '';\n            msgInp.removeAttribute('data-ti-pending-text');\n            try { msgInp.dispatchEvent(new Event('input', { bubbles: true })); } catch(_e) {}\n            try { msgInp.dispatchEvent(new Event('change', { bubbles: true })); } catch(_e) {}\n        } catch(_e) {}\n    };\n    window.addMsg = function(r,h,rawSource) {\n        const chat = gE('ti-chat-box');\n        if(!chat) return null;\n        const d = document.createElement('div');\n        d.className = 'ti-bubble ' + r;\n        const baseHtml = String((typeof rawSource === 'string' && rawSource !== '') ? rawSource : (h || ''));\n        d.innerHTML = String(h || '');\n        if(r === 'ai') {\n            d.__tiChatHtmlByLang = d.__tiChatHtmlByLang || {};\n            d.__tiChatHtmlByLang.it = baseHtml;\n            d.setAttribute('data-ti-it-html', baseHtml);\n            if(typeof rawSource === 'string' && rawSource !== '') {\n                d.setAttribute('data-raw-source', rawSource);\n                window.lastAiReplySource = rawSource;\n            }\n        }\n        chat.appendChild(d);\n        try {\n            if (window.tiCleanEscapedUiInSubtree) window.tiCleanEscapedUiInSubtree(d);\n            if (window.tiI18nTranslateSubtree) window.tiI18nTranslateSubtree(d);\n            if (r === 'ai' && d.__tiChatHtmlByLang) {\n                const langNow = String(window.currLang || 'it').toLowerCase() === 'en' ? 'en' : 'it';\n                d.__tiChatHtmlByLang[langNow] = d.innerHTML;\n                if (langNow === 'it') {\n                    d.__tiChatHtmlByLang.it = d.innerHTML;\n                    d.setAttribute('data-ti-it-html', d.innerHTML);\n                }\n            }\n        } catch(e) {}\n        chat.scrollTop = chat.scrollHeight;\n        if (r === 'user' || r === 'ai') { window.tiClearUserChatInput(); }\n        if (r === 'ai') {\n            setTimeout(function(){\n                window.tiClearUserChatInput();\n                const hint = d.querySelector('.ti-order-hint');\n                const toolbar = d.querySelector('.ti-order-toolbar');\n                const target = hint || toolbar;\n                if (target && target.scrollIntoView) {\n                    try { target.scrollIntoView({behavior:'smooth', block:'start'}); } catch(e) { target.scrollIntoView(true); }\n                }\n                window.refreshOrderConfirmPanel();\n            }, 80);\n        }\n        const msgInp = gE('ti-msg');\n        if(r === 'ai' && msgInp) { msgInp.value=''; msgInp.focus(); }\n        return d;\n    };\n\n    window.collectImportMappingFromUI = function(root) {\n        const box = root || document;\n        let mapping = {};\n        try {\n            if (window.tiConfigImportLastMapping && typeof window.tiConfigImportLastMapping === 'object') mapping = Object.assign({}, window.tiConfigImportLastMapping);\n            if (box && box.dataset && box.dataset.importMapping) {\n                try { mapping = Object.assign(mapping, JSON.parse(decodeURIComponent(box.dataset.importMapping))); } catch(_e) {}\n            }\n            box.querySelectorAll('.ti-import-group-target').forEach(function(sel){\n                const idx = parseInt(sel.getAttribute('data-group-index') || '0', 10);\n                if (!isNaN(idx)) mapping['__group_target_' + idx] = sel.value || 'Prodotti';\n            });\n            box.querySelectorAll('.ti-import-map-select').forEach(function(sel){\n                const hdr = sel.getAttribute('data-source-header') || '';\n                const idx = parseInt(sel.getAttribute('data-group-index') || '0', 10);\n                if (hdr) {\n                    if (!isNaN(idx)) mapping['__map_' + idx + '__' + hdr] = sel.value || '--';\n                    else mapping[hdr] = sel.value || '--';\n                }\n            });\n            box.querySelectorAll('.ti-import-price-formula').forEach(function(inp){\n                const idx = parseInt(inp.getAttribute('data-group-index') || '0', 10);\n                const val = String(inp.value || '').trim();\n                if (val !== '') {\n                    if (!isNaN(idx)) mapping['__price_formula_' + idx] = val;\n                    else mapping['__price_formula'] = val;\n                }\n            });\n            const pdfImg = box.querySelector('.ti-import-pdf-image-link');\n            if (pdfImg && pdfImg.checked) mapping['__link_pdf_images'] = '1';\n            if (Array.isArray(window.tiConfigImportRefineRules) && window.tiConfigImportRefineRules.length) {\n                mapping['__local_refine_copy_rules'] = JSON.stringify(window.tiConfigImportRefineRules);\n            }\n            try { window.tiConfigImportLastMapping = Object.assign({}, mapping); } catch(_e) {}\n        } catch(e) {}\n        return mapping;\n    };\n\n    window.reapplyImportMappingToUI = function(root, mapping) {\n        const box = root || document;\n        mapping = mapping || {};\n        try {\n            box.querySelectorAll('.ti-import-group-target').forEach(function(sel){\n                const idx = parseInt(sel.getAttribute('data-group-index') || '0', 10);\n                const key = '__group_target_' + idx;\n                if (!isNaN(idx) && Object.prototype.hasOwnProperty.call(mapping, key)) sel.value = mapping[key] || 'Prodotti';\n            });\n            box.querySelectorAll('.ti-import-map-select').forEach(function(sel){\n                const hdr = sel.getAttribute('data-source-header') || '';\n                const idx = parseInt(sel.getAttribute('data-group-index') || '0', 10);\n                const key = '__map_' + idx + '__' + hdr;\n                if (!isNaN(idx) && hdr && Object.prototype.hasOwnProperty.call(mapping, key)) sel.value = mapping[key] || '--';\n            });\n            box.querySelectorAll('.ti-import-price-formula').forEach(function(inp){\n                const idx = parseInt(inp.getAttribute('data-group-index') || '0', 10);\n                const key = '__price_formula_' + idx;\n                if (!isNaN(idx) && Object.prototype.hasOwnProperty.call(mapping, key)) inp.value = mapping[key] || '';\n            });\n            try { window.tiConfigImportLastMapping = Object.assign({}, mapping); } catch(_e) {}\n        } catch(e) {}\n    };\n\n    window.captureImportFocusState = function(sourceEl) {\n        try {\n            const el = sourceEl || document.activeElement;\n            if (!el) return null;\n            const bubble = el.closest ? el.closest('.ti-bubble.ai') : null;\n            const chatBox = gE('ti-chat-box');\n            const state = { bubbleScrollTop: bubble ? bubble.scrollTop : 0 };\n            state.windowScrollX = window.scrollX || window.pageXOffset || 0;\n            state.windowScrollY = window.scrollY || window.pageYOffset || 0;\n            state.chatScrollTop = chatBox ? chatBox.scrollTop : 0;\n            if (chatBox && el.getBoundingClientRect) {\n                try {\n                    const er = el.getBoundingClientRect();\n                    const cr = chatBox.getBoundingClientRect();\n                    state.elementTopInChat = er.top - cr.top;\n                } catch(e) {}\n            }\n            const sc = el.closest ? el.closest('.ti-cfg-table-container, .ti-import-table-container') : null;\n            state.containerScrollLeft = sc ? sc.scrollLeft : 0;\n            state.containerScrollTop = sc ? sc.scrollTop : 0;\n            if (el.classList && el.classList.contains('ti-import-map-select')) {\n                state.kind = 'map';\n                state.groupIndex = el.getAttribute('data-group-index') || '';\n                state.header = el.getAttribute('data-source-header') || '';\n                state.value = el.value || '--';\n                return state;\n            }\n            if (el.classList && el.classList.contains('ti-import-group-target')) {\n                state.kind = 'target';\n                state.groupIndex = el.getAttribute('data-group-index') || '';\n                state.value = el.value || 'Prodotti';\n                return state;\n            }\n            if (el.classList && el.classList.contains('ti-import-price-formula')) {\n                state.kind = 'formula';\n                state.groupIndex = el.getAttribute('data-group-index') || '';\n                state.value = el.value || '';\n                try { state.selStart = el.selectionStart; state.selEnd = el.selectionEnd; } catch(e) {}\n                return state;\n            }\n        } catch(e) {}\n        return null;\n    };\n\n    window.restoreImportFocusState = function(root, state) {\n        if (!state) return;\n        try {\n            const box = root || document;\n            let el = null;\n            if (state.kind === 'map') el = box.querySelector('.ti-import-map-select[data-group-index=\"' + state.groupIndex + '\"][data-source-header=\"' + CSS.escape(state.header || '') + '\"]');\n            else if (state.kind === 'target') el = box.querySelector('.ti-import-group-target[data-group-index=\"' + state.groupIndex + '\"]');\n            else if (state.kind === 'formula') el = box.querySelector('.ti-import-price-formula[data-group-index=\"' + state.groupIndex + '\"]');\n            if (el) {\n                if (typeof el.focus === 'function') el.focus({preventScroll:true});\n                if (state.kind === 'formula') {\n                    try { el.selectionStart = state.selStart || 0; el.selectionEnd = state.selEnd || state.selStart || 0; } catch(e) {}\n                }\n                const sc = el.closest ? el.closest('.ti-cfg-table-container, .ti-import-table-container') : null;\n                if (sc) {\n                    sc.scrollLeft = state.containerScrollLeft || 0;\n                    sc.scrollTop = state.containerScrollTop || 0;\n                }\n                const bubble = el.closest ? el.closest('.ti-bubble.ai') : null;\n                if (bubble) bubble.scrollTop = state.bubbleScrollTop || bubble.scrollTop || 0;\n                const chatBox = gE('ti-chat-box');\n                if (chatBox) {\n                    try {\n                        if (typeof state.elementTopInChat === 'number' && el.getBoundingClientRect) {\n                            const er = el.getBoundingClientRect();\n                            const cr = chatBox.getBoundingClientRect();\n                            chatBox.scrollTop += ((er.top - cr.top) - state.elementTopInChat);\n                        } else if (typeof state.chatScrollTop === 'number') {\n                            chatBox.scrollTop = state.chatScrollTop;\n                        }\n                    } catch(e) {}\n                }\n                try {\n                    if (typeof state.windowScrollY === 'number') window.scrollTo(state.windowScrollX || 0, state.windowScrollY || 0);\n                } catch(e) {}\n            }\n        } catch(e) {}\n    };\n\n    window.refreshImportPreviewFromUI = function(sourceEl) {\n        const previewBody = gE('ti-conf-import-preview-body');\n        const isModalPreview = !!(previewBody && sourceEl && previewBody.contains && previewBody.contains(sourceEl));\n        const bubble = (!isModalPreview && sourceEl && sourceEl.closest) ? sourceEl.closest('.ti-bubble.ai') : null;\n        const targetBox = isModalPreview ? previewBody : bubble;\n        const root = targetBox || document;\n        const dbVal = gE('ti-ditta') ? gE('ti-ditta').value : '';\n        if (!dbVal || !targetBox) return;\n        const mapping = window.collectImportMappingFromUI(root);\n        const focusState = window.captureImportFocusState(sourceEl);\n        try { targetBox.dataset.importMapping = encodeURIComponent(JSON.stringify(mapping)); } catch(e) {}\n        const fd = new FormData();\n        fd.append('action', 'ti_ai_config_action');\n        fd.append('ti_action', 'ti_ai_config_action');\n        fd.append('db', dbVal);\n        fd.append('mode', 'refresh_import_preview');\n        fd.append('prompt', 'refresh import preview');\n        if (window.tiConfigImportToken) fd.append('import_token', window.tiConfigImportToken);\n        fd.append('mapping_json', JSON.stringify(mapping));\n        targetBox.style.opacity = '0.75';\n        window.postFormDataJsonSafe(window.tiUrl, fd, {tiNoLongProcessSignal:true}).then(function(res){\n            targetBox.style.opacity = '';\n            if (res && res.success && targetBox) {\n                if (res.data && (res.data.import_token || res.data.preview_token)) window.tiConfigImportToken = res.data.import_token || res.data.preview_token;\n                const rawReply = ((res.data || {}).reply || '').replace(\/\\[NO_VOICE\\]\/g, '').trim();\n                try { window.tiConfigImportLastMapping = Object.assign({}, mapping); } catch(_e) {}\n                if (rawReply) {\n                    targetBox.innerHTML = rawReply;\n                    let applyMap = mapping;\n                    try {\n                        if (targetBox.dataset.importMapping) applyMap = JSON.parse(decodeURIComponent(targetBox.dataset.importMapping));\n                    } catch(e) {}\n                    window.reapplyImportMappingToUI(targetBox, applyMap);\n                    if (window.initConfigTopScrollbars) window.initConfigTopScrollbars(targetBox);\n                    window.restoreImportFocusState(targetBox, focusState);\n                    setTimeout(function(){ window.restoreImportFocusState(targetBox, focusState); }, 0);\n                    if (window.requestAnimationFrame) window.requestAnimationFrame(function(){ window.restoreImportFocusState(targetBox, focusState); });\n                    if (isModalPreview && window.redisplayConfigImportPreviewForFinalConfirm) {\n                        window.redisplayConfigImportPreviewForFinalConfirm('Anteprima modifiche aggiornata dopo la variazione richiesta. Conferma solo se corretta.');\n                    }\n                }\n            }\n        }).catch(function(){\n            targetBox.style.opacity = '';\n            if (isModalPreview && window.markConfigImportPreviewUpdated) {\n                window.markConfigImportPreviewUpdated('Modifica registrata. Se l anteprima non si aggiorna automaticamente, controlla le associazioni visibili prima di confermare.');\n            }\n        });\n    };\n\n    window.scheduleImportPreviewRefresh = function(sourceEl) {\n        clearTimeout(window.importPreviewRefreshTimer);\n        window.importPreviewRefreshTimer = setTimeout(function(){ window.refreshImportPreviewFromUI(sourceEl); }, 180);\n    };\n    window.showFullImg = function(url) { const fullImg = gE('ti-full-img'); if(fullImg) { fullImg.src = url; window.openModal('ti-img-ov'); } };\n    window.getFileNameFromUrlOrValue = function(value) {\n        const raw = String(value || '').trim();\n        if (!raw) return 'file';\n        try {\n            const u = new URL(raw, window.location.origin);\n            const qf = u.searchParams.get('file');\n            if (qf) return decodeURIComponent(qf);\n            const pts = u.pathname.split('\/').filter(Boolean);\n            return decodeURIComponent(pts.pop() || raw);\n        } catch(e) {\n            const clean = raw.split('?')[0].split('#')[0];\n            const pts = clean.split('\/').filter(Boolean);\n            return decodeURIComponent(pts.pop() || raw);\n        }\n    };\n    window.splitLinkedFileValues = function(value) {\n        const raw0 = String(value || '').trim();\n        if (!raw0) return [];\n        if (\/^data:\/i.test(raw0)) return [raw0];\n        let raw = raw0.replace(\/&quot;\/g, '\"').replace(\/&#039;\/g, \"'\").replace(\/&amp;\/g, '&');\n        try {\n            if (\/^\\s*\\[\/.test(raw)) {\n                const parsed = JSON.parse(raw);\n                const out = [];\n                const walk = function(v){\n                    if (Array.isArray(v)) v.forEach(walk);\n                    else if (v && typeof v === 'object') Object.keys(v).forEach(function(k){ walk(v[k]); });\n                    else if (v != null && String(v).trim()) out.push(String(v).trim());\n                };\n                walk(parsed);\n                if (out.length) return Array.from(new Set(out));\n            }\n        } catch(e) {}\n        raw = raw.replace(\/<br\\s*\\\/?\\s*>\/gi, '\\n');\n        return raw.split(\/[,;|\\r\\n]+\/).map(function(v){\n            return String(v || '').trim().replace(\/^['\"\\[]+|['\"\\]]+$\/g, '');\n        }).filter(function(v){\n            const low = v.toLowerCase();\n            return v && !['0','no','nessuno','nessuna','--','-','null','undefined'].includes(low);\n        }).filter(function(v, i, arr){ return arr.indexOf(v) === i; });\n    };\n    window.getFirstLinkedFileValue = function(value) {\n        const list = window.splitLinkedFileValues ? window.splitLinkedFileValues(value) : String(value || '').split(',').map(v => v.trim()).filter(Boolean);\n        return list.length ? list[0] : '';\n    };\n    window.isImageFileValue = function(value) {\n        const v = String(value || '').trim();\n        if (!v) return false;\n        return window.isImageFileName(window.getFileNameFromUrlOrValue(v)) || \/\\.(jpg|jpeg|png|webp|gif|bmp|svg)(?:$|[?#&])\/i.test(v);\n    };\n    window.isImageFileName = function(name) { return \/\\.(jpg|jpeg|png|webp|gif|bmp|svg)$\/i.test(String(name || '')); };\n    window.isPdfFileName = function(name) { return \/\\.pdf$\/i.test(String(name || '')); };\n    window.getFilePreviewIcon = function(name) {\n        const n = String(name || '').toLowerCase();\n        if (\/\\.pdf$\/.test(n)) return '\ud83d\udcd5';\n        if (\/\\.(doc|docx)$\/.test(n)) return '\ud83d\udcd8';\n        if (\/\\.(xls|xlsx|csv)$\/.test(n)) return '\ud83d\udcca';\n        if (\/\\.(txt|json|xml)$\/.test(n)) return '\ud83d\udcc4';\n        if (\/\\.(mp4|mov|avi|webm)$\/.test(n)) return '\ud83c\udfac';\n        return '\ud83d\udcce';\n    };\n    window.ensureFilePreviewModal = function() {\n        let ov = gE('ti-file-preview-ov');\n        if (ov) return ov;\n        ov = document.createElement('div');\n        ov.id = 'ti-file-preview-ov';\n        ov.className = 'ti-ov ti-closable-ov';\n        ov.style.zIndex = '2147483647';\n        ov.innerHTML = '<div class=\"ti-modal\" style=\"width:94vw;max-width:920px;text-align:left;\">'\n            + '<button type=\"button\" class=\"ti-modal-x-red\" onclick=\"window.closeFilePreviewModal()\" title=\"Chiudi\">X<\/button>'\n            + '<h3 id=\"ti-file-preview-title\" style=\"color:#fff;margin-top:0;margin-bottom:12px;padding-right:38px;\">Preview file<\/h3>'\n            + '<div id=\"ti-file-preview-content\" style=\"min-height:120px;color:#e5e7eb;\"><\/div>'\n            + '<div class=\"ti-modal-bottom-close-row\"><button type=\"button\" class=\"ti-btn btn-close\" onclick=\"window.closeFilePreviewModal()\">Chiudi<\/button><\/div>'\n            + '<\/div>';\n        document.body.appendChild(ov);\n        return ov;\n    };\n    window.closeFilePreviewModal = function() { window.closeModal('ti-file-preview-ov'); };\n    window.openExternalLinkedFile = function(url) { if (url) window.open(String(url), '_blank', 'noopener'); };\n    window.openFilePreviewModal = function(file, title) {\n        const obj = (typeof file === 'string') ? {url:file, name:window.getFileNameFromUrlOrValue(file)} : (file || {});\n        const url = String(obj.url || obj.raw || '').trim();\n        if (!url) return;\n        const name = String(obj.name || window.getFileNameFromUrlOrValue(url) || 'file');\n        const safeName = window.escapeHtml(name);\n        const safeUrl = window.escapeHtml(url);\n        const ov = window.ensureFilePreviewModal();\n        const titleEl = gE('ti-file-preview-title');\n        const content = gE('ti-file-preview-content');\n        if (titleEl) titleEl.textContent = title || name;\n        let html = '';\n        if (window.isImageFileName(name) || window.isImageFileName(url)) {\n            const fb = window.getCompanyFallbackMediaUrl(gE('ti-ditta') ? gE('ti-ditta').value : '');\n            html = '<div style=\"text-align:center;\"><img decoding=\"async\" src=\"' + safeUrl + '\" data-fallback=\"' + window.escapeHtml(fb) + '\" onerror=\"window.applyFallbackImage(this)\" alt=\"' + safeName + '\" style=\"max-width:100%;max-height:72vh;object-fit:contain;border-radius:10px;background:#000;border:1px solid #334155;\"><\/div>';\n        } else if (window.isPdfFileName(name) || window.isPdfFileName(url)) {\n            html = '<iframe src=\"' + safeUrl + '#toolbar=1&navpanes=0\" title=\"' + safeName + '\" style=\"width:100%;height:72vh;border:1px solid #334155;border-radius:10px;background:#000;\"><\/iframe>';\n        } else {\n            html = '<div style=\"text-align:center;padding:28px 12px;background:#020617;border:1px solid #334155;border-radius:12px;\">'\n                + '<div style=\"font-size:48px;margin-bottom:10px;\">' + window.getFilePreviewIcon(name) + '<\/div>'\n                + '<div style=\"font-weight:800;margin-bottom:12px;word-break:break-all;\">' + safeName + '<\/div>'\n                + '<button type=\"button\" class=\"ti-btn\" onclick=\"window.openExternalLinkedFile(decodeURIComponent(\\'' + encodeURIComponent(url) + '\\'))\">Apri originale<\/button>'\n                + '<\/div>';\n        }\n        if (content) content.innerHTML = html;\n        window.openModal('ti-file-preview-ov');\n        if (window.keepUserCommunicationPopupsInFront) window.keepUserCommunicationPopupsInFront();\n    };\n    window.openOriginalLinkedFile = function(url) {\n        if (!url) return;\n        window.openFilePreviewModal({url:String(url), name:window.getFileNameFromUrlOrValue(url)});\n    };\n    window.openOrderLinkedFilesChoice = function(payload) {\n        const files = Array.isArray(payload && payload.files) ? payload.files.filter(function(f){ return f && f.url; }) : [];\n        if (!files.length) { window.openOrderItemPopup(payload); return; }\n        if (files.length === 1) { window.openFilePreviewModal(files[0], payload && payload.name ? payload.name : files[0].name); return; }\n        const ov = window.ensureFilePreviewModal();\n        const titleEl = gE('ti-file-preview-title');\n        const content = gE('ti-file-preview-content');\n        if (titleEl) titleEl.textContent = 'Scegli file da vedere (' + files.length + ' file collegati)';\n        const cards = files.map(function(f, i){\n            const url = String(f.url || '');\n            const name = String(f.name || window.getFileNameFromUrlOrValue(url) || ('file ' + (i + 1)));\n            const safeName = window.escapeHtml(name);\n            const safeUrl = window.escapeHtml(url);\n            const enc = encodeURIComponent(url);\n            const encName = encodeURIComponent(name);\n            let preview = '';\n            if (window.isImageFileName(name) || window.isImageFileName(url)) {\n                const fb = window.getCompanyFallbackMediaUrl(gE('ti-ditta') ? gE('ti-ditta').value : '');\n                preview = '<img decoding=\"async\" src=\"' + safeUrl + '\" data-fallback=\"' + window.escapeHtml(fb) + '\" onerror=\"window.applyFallbackImage(this)\" alt=\"' + safeName + '\">';\n            } else if (window.isPdfFileName(name) || window.isPdfFileName(url)) {\n                preview = '<iframe src=\"' + safeUrl + '#toolbar=0&navpanes=0\" title=\"' + safeName + '\"><\/iframe>';\n            } else {\n                preview = '<div class=\"ti-order-file-preview-icon\">' + window.getFilePreviewIcon(name) + '<\/div>';\n            }\n            return '<button type=\"button\" class=\"ti-order-file-preview\" onclick=\"window.openFilePreviewModal({url:decodeURIComponent(\\'' + enc + '\\'),name:decodeURIComponent(\\'' + encName + '\\')}, decodeURIComponent(\\'' + encName + '\\'))\" title=\"Apri: ' + safeName + '\">' + preview + '<div class=\"ti-order-file-preview-name\">' + safeName + '<\/div><div class=\"ti-order-file-preview-open\">Vedi file<\/div><\/button>';\n        }).join('');\n        if (content) content.innerHTML = '<div class=\"ti-order-modal-files-grid\">' + cards + '<\/div>';\n        window.openModal('ti-file-preview-ov');\n    };\n    window.openOrderLinkedFilesFromEl = function(el) {\n        if (!el) return;\n        const raw = el.getAttribute('data-popup') || '';\n        if (!raw) return;\n        try { window.openOrderLinkedFilesChoice(JSON.parse(decodeURIComponent(raw))); }\n        catch(e) { window.openOrderItemPopupFromEl(el); }\n    };\n    window.renderOrderItemLinkedFiles = function(payload) {\n        const box = gE('ti-order-item-files');\n        if (!box) return;\n        const files = Array.isArray(payload && payload.files) ? payload.files : [];\n        const usable = files.filter(function(f){ return f && f.url; });\n        if (!usable.length) { box.style.display = 'none'; box.innerHTML = ''; return; }\n        const title = usable.length === 1 ? 'File collegato' : ('File collegati (' + usable.length + ')');\n        const cards = usable.map(function(f, i){\n            const url = String(f.url || '');\n            const name = String(f.name || window.getFileNameFromUrlOrValue(url) || ('file ' + (i + 1)));\n            const safeName = window.escapeHtml(name);\n            const safeUrl = window.escapeHtml(url);\n            const encUrl = encodeURIComponent(url);\n            let preview = '';\n            if (window.isImageFileName(name) || window.isImageFileName(url)) {\n                const fb = window.getCompanyFallbackMediaUrl(gE('ti-ditta') ? gE('ti-ditta').value : '');\n                preview = `<img decoding=\"async\" src=\"${safeUrl}\" data-fallback=\"${window.escapeHtml(fb)}\" onerror=\"window.applyFallbackImage(this)\" alt=\"${safeName}\">`;\n            } else if (window.isPdfFileName(name) || window.isPdfFileName(url)) {\n                preview = `<iframe src=\"${safeUrl}#toolbar=0&navpanes=0\" title=\"${safeName}\"><\/iframe>`;\n            } else {\n                preview = `<div class=\"ti-order-file-preview-icon\">${window.getFilePreviewIcon(name)}<\/div>`;\n            }\n            return `<button type=\"button\" class=\"ti-order-file-preview\" onclick=\"window.openOriginalLinkedFile(decodeURIComponent('${encUrl}'))\" title=\"Apri versione originale: ${safeName}\">${preview}<div class=\"ti-order-file-preview-name\">${safeName}<\/div><div class=\"ti-order-file-preview-open\">Apri originale<\/div><\/button>`;\n        }).join('');\n        box.innerHTML = `<div class=\"ti-order-modal-files-title\">Seleziona documento da vedere<\/div><div class=\"ti-order-modal-files-grid\">${cards}<\/div>`;\n        box.style.display = '';\n    };\n    window.orderItemPopupState = null;\n    window.openOrderItemPopup = function(payload) {\n        window.orderItemPopupState = payload || null;\n        if (!payload) return;\n        const qty = window.parseQtyInput(payload.qty);\n        const img = gE('ti-order-item-img'); if (img) { const fb = window.getCompanyFallbackMediaUrl(gE('ti-ditta') ? gE('ti-ditta').value : ''); img.setAttribute('data-fallback', fb || ''); img.onerror = function(){ window.applyFallbackImage(this); }; img.src = payload.img || fb || ''; img.style.display = (payload.img || fb) ? '' : 'none'; }\n        const titleEl = gE('ti-order-item-title'); if (titleEl) { const tblLow = String(payload.tblName || payload.table || '').toLowerCase(); titleEl.textContent = tblLow.indexOf('serviz') !== -1 ? 'Dettaglio servizio' : (tblLow.indexOf('prodott') !== -1 ? 'Dettaglio articolo' : 'Dettaglio voce'); }\n        const nameEl = gE('ti-order-item-name'); if (nameEl) nameEl.textContent = payload.name || '';\n        const capEl = gE('ti-order-item-caption'); if (capEl) capEl.textContent = payload.desc || payload.name || '';\n        const priceEl = gE('ti-order-item-price'); if (priceEl) priceEl.innerHTML = payload.priceHtml || ('\u20ac ' + window.fmtNum(payload.price || 0));\n        const qtyEl = gE('ti-order-item-qty'); if (qtyEl) qtyEl.value = window.formatQtyInput(qty);\n        const totalEl = gE('ti-order-item-total'); if (totalEl) totalEl.textContent = '\u20ac ' + window.fmtNum((payload.price || 0) * qty);\n        const noteEl = gE('ti-order-item-note'); if (noteEl) noteEl.textContent = payload.note || '';\n        window.renderOrderItemLinkedFiles(payload);\n        if (qtyEl) { qtyEl.oninput = function(){ const q = window.parseQtyInput(this.value); const tot = gE('ti-order-item-total'); if (tot) tot.textContent = '\u20ac ' + window.fmtNum((payload.price || 0) * q); }; }\n        window.openModal('ti-order-item-ov');\n    };\n    window.openOrderItemPopupFromEl = function(el) {\n        if (!el) return;\n        const raw = el.getAttribute('data-popup') || '';\n        if (!raw) return;\n        try {\n            const payload = JSON.parse(decodeURIComponent(raw));\n            window.openOrderItemPopup(payload);\n        } catch(e) {\n            console.error('Popup ordine non valido', e, raw);\n        }\n    };\n    window.confirmOrderItemPopup = function() {\n        const st = window.orderItemPopupState || {};\n        const qtyEl = gE('ti-order-item-qty');\n        const qty = qtyEl ? window.parseQtyInput(qtyEl.value) : 0;\n        if (st.orderId) window.setOrderQty(st.orderId, qty);\n        window.closeOrderItemPopup();\n    };\n    window.closeOrderItemPopup = function() { window.orderItemPopupState = null; window.closeModal('ti-order-item-ov'); };\n    window.setOrderQty = function(orderId, qty) {\n        const val = window.formatQtyInput(qty);\n        document.querySelectorAll('.ti-q[data-order-id=\"' + orderId + '\"]').forEach(inp => { inp.value = val; window.syncOrderQty(inp); });\n    };\n    window.canViewSystemInstructions = function() {\n        const role = String(window.tiRole || '').toLowerCase();\n        const user = String(window.tiUser || '').toLowerCase();\n        return user === 'ssglobaladmin' || role.includes('amministratore') || role.includes('configuratore');\n    };\n    window.stripSystemInstructionSegments = function(instr) {\n        let txt = String(instr || '');\n        if (!txt) return '';\n        txt = txt.replace(\/\\\\\\$sys\\s*=\/gi, '$sys=');\n        txt = txt.replace(\/&dollar;\\s*sys\\s*=\/gi, '$sys=');\n        function stripSysFromLine(line) {\n            let rawLine = String(line || '');\n            if (\/^\\s*\\$sys\\s*=\/i.test(rawLine)) return '';\n            let idx = rawLine.search(\/\\$sys\\s*=\/i);\n            if (idx < 0) return rawLine;\n            const before = rawLine.slice(0, idx).trim();\n            const after = rawLine.slice(idx).replace(\/^\\$sys\\s*=\\s*\/i, '');\n            \/\/ Se dopo l'istruzione di sistema esiste un'altra regola commerciale concatenata\n            \/\/ (es. \"... $sys=... sconto 50%\"), conserva solo quella regola commerciale.\n            const nextCommercial = after.search(\/\\b(?:sconto|prezzo\\s*(?:applicato|finale|vendita)?|rincaro|aumento|maggiorazione)\\b|[-+]\\s*\\d+[\\.,]?\\d*\\s*%\/i);\n            const tail = nextCommercial >= 0 ? after.slice(nextCommercial).trim() : '';\n            return [before, tail].filter(Boolean).join(' ');\n        }\n        const lines = txt.split(\/\\r?\\n\/).map(stripSysFromLine).filter(function(line){ return String(line || '').trim() !== ''; });\n        return lines.join('\\n').replace(\/[ \\t]{2,}\/g, ' ').trim();\n    };\n    window.extractVisibleInstructionDiscountLabels = function(instr) {\n        const txt = window.stripSystemInstructionSegments(instr);\n        const labels = [];\n        const seen = {};\n        function push(label) { if (label && !seen[label]) { seen[label] = true; labels.push(label); } }\n        let m;\n        const pctRe = \/(?:sconto\\s*(?:del|di)?\\s*|-\\s*)(\\d+[\\.,]?\\d*)\\s*%\/gi;\n        while ((m = pctRe.exec(txt)) !== null) push('Sconto ' + String(m[1]).replace('.', ',') + '%');\n        const euroRe = \/sconto\\s*(?:di)?\\s*(?:\u20ac\\s*)?(\\d+[\\.,]?\\d*)\\s*(?:euro|eur)\\b\/gi;\n        while ((m = euroRe.exec(txt)) !== null) push('Sconto \u20ac ' + String(m[1]).replace('.', ','));\n        const addPctRe = \/(?:(?:rincaro|aumento|maggiorazione)\\s*(?:del|di)?\\s*|\\+\\s*)(\\d+[\\.,]?\\d*)\\s*%\/gi;\n        while ((m = addPctRe.exec(txt)) !== null) push('Maggiorazione ' + String(m[1]).replace('.', ',') + '%');\n        return labels;\n    };\n    window.summarizeVisibleOrderInstruction = function(instr) {\n        const txt = window.stripSystemInstructionSegments(instr);\n        if (!txt) return '';\n        const labels = window.extractVisibleInstructionDiscountLabels(txt);\n        if (labels.length) return labels.join(' + ');\n        let m = txt.match(\/prezzo\\s*(?:applicato|finale|vendita)?\\s*[:=]?\\s*\u20ac?\\s*(\\d+[\\.,]?\\d*)\/i);\n        if (m) return 'Prezzo finale \u20ac ' + String(m[1]).replace('.', ',');\n        return txt;\n    };\n    window.getOrderInstructionTextsForDisplay = function(instr) {\n        const raw = String(instr || '').trim();\n        const commercial = window.stripSystemInstructionSegments(raw);\n        const summary = window.summarizeVisibleOrderInstruction(commercial);\n        const canViewSys = window.canViewSystemInstructions();\n        return {\n            raw: raw,\n            commercial: commercial,\n            display: canViewSys ? raw : summary,\n            popup: canViewSys ? raw : summary,\n            summary: summary\n        };\n    };\n    window.extractInstructionPriceAdjustments = function(instr, basePrice) {\n        const originalTxt = String(instr || '');\n        const txt = window.stripSystemInstructionSegments ? window.stripSystemInstructionSegments(originalTxt) : originalTxt;\n        const low = txt.toLowerCase();\n        let finalPrice = parseFloat(basePrice) || 0;\n        const listPrice = finalPrice;\n        let defaultQty = null;\n        const mQty = low.match(\/(?:q\\.?t\u00e0|qt\u00e0|qta|quantit\u00e0|quantita|minimo|minimum)\\s*[:=]?\\s*(\\d+[\\.,]?\\d*)\/i);\n        if (mQty) defaultQty = parseFloat(String(mQty[1]).replace(',', '.')) || null;\n\n        const mSetPrice = low.match(\/prezzo\\s*(?:applicato|finale|vendita)?\\s*[:=]?\\s*\u20ac?\\s*(\\d+[\\.,]?\\d*)\/i);\n        if (mSetPrice) {\n            finalPrice = parseFloat(String(mSetPrice[1]).replace(',', '.')) || finalPrice;\n        } else {\n            \/\/ Applica tutte le variazioni in cascata, nell'ordine in cui compaiono.\n            const re = \/(?:sconto\\s*(?:del|di)?\\s*(\\d+[\\.,]?\\d*)\\s*%|-(\\d+[\\.,]?\\d*)\\s*%|(?:rincaro|aumento|maggiorazione)\\s*(?:del|di)?\\s*(\\d+[\\.,]?\\d*)\\s*%|\\+(\\d+[\\.,]?\\d*)\\s*%|sconto\\s*(?:di)?\\s*\u20ac\\s*(\\d+[\\.,]?\\d*)\\b|sconto\\s*(?:di)?\\s*(\\d+[\\.,]?\\d*)\\s*(?:euro|eur)\\b)\/gi;\n            let m;\n            while ((m = re.exec(low)) !== null) {\n                if (m[1] || m[2]) {\n                    const pct = parseFloat(String(m[1] || m[2]).replace(',', '.')) || 0;\n                    finalPrice = finalPrice * (1 - pct \/ 100);\n                } else if (m[3] || m[4]) {\n                    const pct = parseFloat(String(m[3] || m[4]).replace(',', '.')) || 0;\n                    finalPrice = finalPrice * (1 + pct \/ 100);\n                } else if (m[5] || m[6]) {\n                    const val = parseFloat(String(m[5] || m[6]).replace(',', '.')) || 0;\n                    finalPrice = Math.max(0, finalPrice - val);\n                }\n            }\n        }\n\n        finalPrice = Math.max(0, Math.round(finalPrice * 100) \/ 100);\n        return { finalPrice: finalPrice, listPrice: listPrice, isModified: Math.abs(finalPrice - listPrice) > 0.009, note: txt.trim(), defaultQty: defaultQty };\n    };\n    window.isDateFieldName = function(key) {\n        const k = String(key || '').toLowerCase().trim();\n        return k === 'data' || k === 'data creazione' || k === 'data_appuntamento' || k === 'data appuntamento' || \/(^|\\s)(da gg|a gg)$\/.test(k) || \/(^|_)(data)(_|$)\/.test(k);\n    };\n    window.isTimeFieldName = function(key) {\n        const k = String(key || '').toLowerCase().trim();\n        return k === 'ora' || k === 'ora appuntamento' || k === 'ora_appuntamento' || \/(^|\\s)(da ora|a ora)$\/.test(k) || \/(^|_)(ora)(_|$)\/.test(k);\n    };\n    window.ddmmyyyyToInputDate = function(v) {\n        const s = String(v || '').trim();\n        const m = s.match(\/^(\\d{2})\\\/(\\d{2})\\\/(\\d{4})$\/);\n        return m ? `${m[3]}-${m[2]}-${m[1]}` : '';\n    };\n\n    window.hhmmToInputTime = function(v) {\n        const s = String(v || '').trim();\n        if (!s) return '';\n        if (\/^\\d{2}:\\d{2}$\/.test(s)) return s;\n        if (\/^\\d{1,2}:\\d{2}$\/.test(s)) { const p = s.split(':'); return p[0].padStart(2,'0') + ':' + p[1]; }\n        return '';\n    };\n\n    window.initPluginDateInputs = function(root) {\n        const scope = root || document;\n        scope.querySelectorAll('input[type=\"date\"], input[type=\"time\"]').forEach(function(inp){\n            if (inp.type === 'time' && (!inp.value || !\/^\\d{2}:\\d{2}$\/.test(inp.value))) inp.value = '';\n            if (inp.type === 'date' && (!inp.value || !\/^\\d{4}-\\d{2}-\\d{2}$\/.test(inp.value))) inp.value = '';\n            if (inp.type === 'date') { inp.placeholder = 'gg\/mm\/aaaa'; inp.title = inp.title || 'Seleziona una data'; }\n            if (inp.type === 'time') { inp.placeholder = 'hh:mm'; inp.title = inp.title || 'Seleziona un orario'; }\n        });\n    };\n    window.inputDateToDDMMYYYY = function(v) {\n        const s = String(v || '').trim();\n        const m = s.match(\/^(\\d{4})-(\\d{2})-(\\d{2})$\/);\n        return m ? `${m[3]}\/${m[2]}\/${m[1]}` : s;\n    };\n    window.normalizeInputValueForKey = function(key, value) {\n        if (window.isDateFieldName(key)) return window.inputDateToDDMMYYYY(value);\n        return value;\n    };\n\n    window.getComparableCellValue = function(cell) {\n        const txt = (cell ? (cell.innerText || cell.textContent || '') : '').trim();\n        const dataPrice = cell && cell.getAttribute ? cell.getAttribute('data-price') : '';\n        if (dataPrice !== null && dataPrice !== undefined && String(dataPrice).trim() !== '') {\n            const dp = parseFloat(String(dataPrice).replace(',', '.'));\n            if (!isNaN(dp)) return {type:'num', value: dp};\n        }\n        const numTxt = txt.replace(\/\u20ac|\\s\/g, '').replace(\/\\.\/g, '').replace(',', '.');\n        if (numTxt !== '' && !isNaN(numTxt)) return {type:'num', value: parseFloat(numTxt)};\n        const dmy = txt.match(\/^(\\d{2})\\\/(\\d{2})\\\/(\\d{4})$\/);\n        if (dmy) return {type:'date', value: dmy[3] + dmy[2] + dmy[1]};\n        return {type:'str', value: txt.toLowerCase()};\n    };\n    window.configTableState = window.configTableState || {};\n    window.__configFilterTimers = window.__configFilterTimers || {};\n    window.ensureConfigTableState = function(tbl) {\n        const t = String(tbl || '');\n        if (!window.configTableState[t]) window.configTableState[t] = {filters:{}, filterDrafts:{}, sortKey:'', sortDir:'asc'};\n        const st = window.configTableState[t];\n        if (!st.filters) st.filters = {};\n        if (!st.filterDrafts) st.filterDrafts = {};\n        if (!st.sortKey) st.sortKey = '';\n        if (!st.sortDir) st.sortDir = 'asc';\n        return st;\n    };\n    window.rememberConfigFilterFocus = function(tbl, key, value, inputEl, pending) {\n        try {\n            const active = inputEl || document.activeElement;\n            const val = String(value == null ? '' : value);\n            window.__configFilterFocus = {\n                tbl: String(tbl || ''),\n                key: String(key || ''),\n                value: val,\n                start: active && typeof active.selectionStart === 'number' ? active.selectionStart : val.length,\n                end: active && typeof active.selectionEnd === 'number' ? active.selectionEnd : val.length,\n                pending: pending !== false\n            };\n        } catch(e) {}\n    };\n    window.sortConfigTable = function(tbl, key) {\n        const st = window.ensureConfigTableState ? window.ensureConfigTableState(tbl) : (window.configTableState[tbl] || (window.configTableState[tbl] = {filters:{}, filterDrafts:{}, sortKey:'', sortDir:'asc'}));\n        st.sortDir = (st.sortKey === key && st.sortDir === 'asc') ? 'desc' : 'asc';\n        st.sortKey = key;\n        window.renderConfig();\n    };\n    window.scheduleConfigTableFilter = function(tbl, key, value, inputEl) {\n        const t = String(tbl || '');\n        const k = String(key || '');\n        const val = String(value == null ? '' : value);\n        const st = window.ensureConfigTableState ? window.ensureConfigTableState(t) : (window.configTableState[t] || (window.configTableState[t] = {filters:{}, filterDrafts:{}, sortKey:'', sortDir:'asc'}));\n        st.filterDrafts[k] = val;\n        window.rememberConfigFilterFocus(t, k, val, inputEl, true);\n        const timerKey = encodeURIComponent(t) + '|' + encodeURIComponent(k);\n        if (window.__configFilterTimers[timerKey]) clearTimeout(window.__configFilterTimers[timerKey]);\n        window.__configFilterTimers[timerKey] = setTimeout(function(){\n            try {\n                const st2 = window.ensureConfigTableState ? window.ensureConfigTableState(t) : (window.configTableState[t] || (window.configTableState[t] = {filters:{}, filterDrafts:{}, sortKey:'', sortDir:'asc'}));\n                st2.filterDrafts[k] = val;\n                st2.filters[k] = val;\n                window.rememberConfigFilterFocus(t, k, val, inputEl, true);\n                window.renderConfig();\n            } finally {\n                delete window.__configFilterTimers[timerKey];\n            }\n        }, 1000);\n    };\n    window.filterConfigTable = function(tbl, key, value, inputEl) {\n        window.scheduleConfigTableFilter(tbl, key, value, inputEl);\n    };\n    window.restoreConfigFilterFocus = function() {\n        try {\n            const f = window.__configFilterFocus;\n            if (!f || !f.pending) return;\n            const esc = (window.CSS && typeof window.CSS.escape === 'function') ? window.CSS.escape : function(v){ return String(v).replace(\/[^a-zA-Z0-9_-]\/g, function(ch){ return '\\\\' + ch; }); };\n            const q = '.ti-cfg-filter[data-tbl=' + esc(String(f.tbl)) + '][data-key=' + esc(String(f.key)) + ']';\n            const inp = document.querySelector(q);\n            f.pending = false;\n            if (!inp) return;\n            const det = inp.closest ? inp.closest('details.ti-cfg-details') : null;\n            if (det) det.open = true;\n            const confCnt = gE('ti-conf-cnt');\n            const beforeTop = confCnt ? confCnt.scrollTop : null;\n            const wrap = inp.closest ? inp.closest('.ti-cfg-table-container') : null;\n            const beforeWrapTop = wrap ? wrap.scrollTop : null;\n            const beforeWrapLeft = wrap ? wrap.scrollLeft : null;\n            inp.focus({preventScroll:true});\n            const len = String(inp.value || '').length;\n            const s = Math.max(0, Math.min(typeof f.start === 'number' ? f.start : len, len));\n            const e = Math.max(0, Math.min(typeof f.end === 'number' ? f.end : s, len));\n            if (inp.setSelectionRange) inp.setSelectionRange(s, e);\n            if (confCnt && beforeTop !== null) confCnt.scrollTop = beforeTop;\n            if (wrap) {\n                if (beforeWrapTop !== null) wrap.scrollTop = beforeWrapTop;\n                if (beforeWrapLeft !== null) wrap.scrollLeft = beforeWrapLeft;\n                const topBar = wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains('ti-sync-hscroll-top') ? wrap.previousElementSibling : null;\n                const bottomBar = wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains('ti-sync-hscroll-bottom') ? wrap.nextElementSibling : null;\n                if (topBar) topBar.scrollLeft = wrap.scrollLeft;\n                if (bottomBar) bottomBar.scrollLeft = wrap.scrollLeft;\n            }\n        } catch(e) {}\n    };\n    window.configFilterMatches = function(cellVal, filterVal) {\n        const rawCell = String(cellVal == null ? '' : cellVal).trim();\n        const f = String(filterVal == null ? '' : filterVal).trim();\n        if (!f) return true;\n        const cmpCell = window.getComparableCellValue({innerText: rawCell});\n        const numMatch = f.match(\/^\\s*(<=|>=|<|>|=)?\\s*(-?\\d+(?:[\\.,]\\d+)?)\\s*$\/);\n        if (numMatch && cmpCell.type === 'num') {\n            const op = numMatch[1] || '=';\n            const val = parseFloat(String(numMatch[2]).replace(',', '.'));\n            if (op === '>') return cmpCell.value > val;\n            if (op === '<') return cmpCell.value < val;\n            if (op === '>=') return cmpCell.value >= val;\n            if (op === '<=') return cmpCell.value <= val;\n            return Math.abs(cmpCell.value - val) < 0.0001;\n        }\n        if (\/^\\s*[<>]=?\/.test(f) && cmpCell.type === 'date') {\n            const m = f.match(\/^\\s*(<=|>=|<|>)\\s*(\\d{2}\\\/\\d{2}\\\/\\d{4})\\s*$\/);\n            if (m) {\n                const cv = cmpCell.value;\n                const dv = window.getComparableCellValue({innerText: m[2]}).value;\n                if (m[1] === '>') return cv > dv;\n                if (m[1] === '<') return cv < dv;\n                if (m[1] === '>=') return cv >= dv;\n                if (m[1] === '<=') return cv <= dv;\n            }\n        }\n        return rawCell.toLowerCase().includes(f.toLowerCase());\n    };\n\n    window.formatTable = function(t) { \n        t = String(t || '');\n        let h = t.split('[TABLE]')[0];\n        let tiPostTableInfoHtml = '';\n        if (t.includes('[TABLE]')) {\n            const lastProductClose = t.lastIndexOf('[\/P]');\n            if (lastProductClose >= 0) {\n                let postRaw = t.slice(lastProductClose + 4).trim();\n                \/\/ Mantiene visibile la risposta informativa AI scritta dopo la tabella ordine.\n                \/\/ Prima delle v30.9.194 formatTable mostrava solo il testo prima di [TABLE]\n                \/\/ e ignorava il testo successivo all'ultima riga [P].\n                if (postRaw) {\n                    postRaw = postRaw.replace(\/^(?:<br\\s*\\\/?\\s*>|\\s|&nbsp;)+\/ig, '').trim();\n                    if (postRaw) {\n                        tiPostTableInfoHtml = '<div class=\"ti-info-after-order\" style=\"margin-top:12px;padding:10px 12px;border-radius:12px;background:#111827;border:1px solid rgba(148,163,184,.35);color:#e5e7eb;line-height:1.45;\">' + postRaw.replace(\/\\n+\/g, '<br>') + '<\/div>';\n                    }\n                }\n            }\n        }\n        if (!t.includes('[TABLE]')) return h.split('\\n').join('<br>');\n        const d = dict[window.currLang] || dict['it'];\n        const rows = t.match(\/\\[P\\](.*?)\\[\\\/P\\]\/g) || [];\n        const allowedViews = window.getOrderViewAllowedModes ? window.getOrderViewAllowedModes() : ['table','small','medium','large'];\n        let preferredView = window.getOrderViewPreference ? window.getOrderViewPreference() : 'table';\n        if (!allowedViews.includes(preferredView)) preferredView = allowedViews[0] || 'table';\n        const layoutLabels = (window.currLang === 'en')\n            ? { table:'Table', small:'Small', medium:'Medium', large:'Large' }\n            : { table:'Tabella', small:'Small', medium:'Medium', large:'Large' };\n        const viewButtonsHtml = [\n            allowedViews.includes('table')  ? `<button type=\"button\" class=\"ti-order-view-btn ${preferredView === 'table' ? 'active' : ''}\" data-view=\"table\" onclick=\"window.setOrderView(this, 'table')\">${layoutLabels.table}<\/button>` : '',\n            allowedViews.includes('small')  ? `<button type=\"button\" class=\"ti-order-view-btn ${preferredView === 'small' ? 'active' : ''}\" data-view=\"small\" onclick=\"window.setOrderView(this, 'small')\">${layoutLabels.small}<\/button>` : '',\n            allowedViews.includes('medium') ? `<button type=\"button\" class=\"ti-order-view-btn ${preferredView === 'medium' ? 'active' : ''}\" data-view=\"medium\" onclick=\"window.setOrderView(this, 'medium')\">${layoutLabels.medium}<\/button>` : '',\n            allowedViews.includes('large')  ? `<button type=\"button\" class=\"ti-order-view-btn ${preferredView === 'large' ? 'active' : ''}\" data-view=\"large\" onclick=\"window.setOrderView(this, 'large')\">${layoutLabels.large}<\/button>` : ''\n        ].filter(Boolean).join('');\n        const showLayoutButtons = viewButtonsHtml !== '' && allowedViews.length > 1;\n        let items = rows.map((r, idx) => {\n            const p = r.replace('[P]','').replace('[\/P]','').split(' ## ');\n            const n = p[0] || '';\n            const priceStr = p[1] || '0';\n            let desc = p[4] || '';\n            const descEn = p[18] ? p[18].trim() : '';\n            if (String(window.currLang || 'it').toLowerCase() === 'en' && descEn) desc = descEn;\n            const ivaRate = p[5] || '22';\n            const tblName = p[6] || '';\n            const imgUrl = p[8] || '';\n            const istr = p[15] ? p[15].trim() : '';\n            const istrEn = p[17] ? p[17].trim() : '';\n            const linkedFileValues = window.splitLinkedFileValues ? window.splitLinkedFileValues(imgUrl || '') : String(imgUrl || '').split(',').map(v => v.trim()).filter(v => v);\n            let firstImg = linkedFileValues[0] || '';\n            const linkedFiles = linkedFileValues.map(function(fileVal){\n                const url = window.getFullImgUrl(fileVal, gE('ti-ditta').value, tblName, n || tblName);\n                return {name: window.getFileNameFromUrlOrValue(fileVal), raw: fileVal, url: url};\n            }).filter(function(f){ return f && f.url; });\n            let fullImgUrl = firstImg ? window.getFullImgUrl(firstImg, gE('ti-ditta').value, tblName, n || tblName) : '';\n            let pFinal = 0, pOrig = 0, isMod = false;\n            if (priceStr.includes('|||')) {\n                let pts = priceStr.split('|||');\n                pFinal = parseFloat(pts[0]) || 0;\n                pOrig = parseFloat(pts[1]) || 0;\n                isMod = pFinal !== pOrig;\n            } else {\n                pFinal = parseFloat(priceStr) || 0;\n                pOrig = pFinal;\n            }\n            const instrTexts = window.getOrderInstructionTextsForDisplay ? window.getOrderInstructionTextsForDisplay(istr) : {raw:String(istr||''), commercial:String(istr||''), display:String(istr||''), popup:String(istr||''), summary:String(istr||'')};\n            if (String(window.currLang || 'it').toLowerCase() === 'en' && istrEn) {\n                instrTexts.display = istrEn;\n                instrTexts.popup = istrEn;\n                instrTexts.summary = istrEn;\n            }\n            const cleanIstr = instrTexts.display ? String(instrTexts.display).replace(\/\"\/g, '&quot;') : '';\n            const commercialIstr = instrTexts.commercial ? String(instrTexts.commercial).replace(\/\"\/g, '&quot;') : '';\n            const popupIstr = instrTexts.popup ? String(instrTexts.popup).replace(\/\"\/g, '&quot;') : '';\n            const localAdj = window.extractInstructionPriceAdjustments(commercialIstr, pFinal);\n            const hasPriceRule = !!(commercialIstr && localAdj && localAdj.isModified && \/(sconto|rincaro|aumento|maggiorazione|prezzo\\s*(applicato|finale|vendita)?)\/i.test(commercialIstr));\n            if (hasPriceRule) {\n                const lp = parseFloat(localAdj.listPrice);\n                const fp = parseFloat(localAdj.finalPrice);\n                pOrig = isNaN(lp) ? pFinal : lp;\n                pFinal = isNaN(fp) ? pFinal : fp;\n                isMod = Math.abs(pFinal - pOrig) > 0.009;\n            }\n            const encodedIstr = encodeURIComponent(`DATI BASE:\nPrezzo Base: \u20ac${window.fmtNum(pOrig)}\n\nISTRUZIONI E VARIAZIONI:\n${popupIstr || cleanIstr}`);\n            const isImgFile = firstImg && (window.isImageFileValue ? window.isImageFileValue(firstImg) : (\/\\.(jpg|jpeg|png|webp|gif)(?:$|[?#&])\/i.test(firstImg) || window.isImageFileName(window.getFileNameFromUrlOrValue(firstImg))));\n            const orderSource = String(tblName || '').toLowerCase();\n            const vocePrefix = orderSource.includes('serviz') ? 'S' : 'P';\n            const voceBaseName = String(n || '').replace(\/^\\s*[PS]\\s*-\\s*\/i, '').trim();\n            const voceName = (vocePrefix + ' - ' + (voceBaseName || n || 'Voce')).trim();\n            const orderId = 'ord-' + idx;\n            let qtyDefault = (localAdj && localAdj.defaultQty != null) ? localAdj.defaultQty : 0;\n            const istrLower = String(commercialIstr || '').toLowerCase();\n            let mQty = istrLower.match(\/(?:q\\.t\u00e0|qt\u00e0|qta|quantit\u00e0|quantita|minimo|minimum)\\s*[:=]?\\s*(\\d+[\\.,]?\\d*)\/i);\n            if (mQty) qtyDefault = parseFloat(String(mQty[1]).replace(',', '.')) || qtyDefault;\n            const priceHtml = isMod\n                ? `<span style=\"display:block;font-size:14px;font-weight:800;color:#fff;\">\u20ac ${window.fmtNum(pFinal)}<\/span><span style=\"display:block;font-size:11px;color:#ef4444;text-decoration:line-through;\">\u20ac ${window.fmtNum(pOrig)}<\/span>`\n                : `<span style=\"display:block;font-size:13px;color:#fff;font-weight:700;\">\u20ac ${window.fmtNum(pFinal)}<\/span>`;\n            return {\n                orderId, n, voceName, vocePrefix, desc, tblName, firstImg, fullImgUrl, linkedFiles, cleanIstr, encodedIstr, isImgFile, pFinal, pOrig, isMod, hasPriceRule, priceHtml, qtyDefault, ivaRate,\n                popupJson: encodeURIComponent(JSON.stringify({orderId: orderId, name: voceName, rawName: n, desc: (desc || n), tblName: tblName, img: fullImgUrl, files: linkedFiles, note: (popupIstr || cleanIstr), price: pFinal, priceHtml: priceHtml, qty: qtyDefault || 0, ivaRate: ivaRate})),\n                nameFilter: window.escapeHtml(voceName + ' ' + n),\n                descFilter: window.escapeHtml(desc || ''),\n                priceFilter: String(pFinal)\n            };\n        }).filter(item => item && String(item.voceName || item.n || '').trim() !== '');\n\n        const tableRows = items.map(item => {\n            const fbUrl = window.getCompanyFallbackMediaUrl(gE('ti-ditta').value);\n            const popupTriggerAttrs = `data-popup=\"${window.escapeHtml(item.popupJson)}\" onclick=\"window.openOrderItemPopupFromEl(this)\" title=\"Apri dettaglio voce\"`;\n            const fileTriggerAttrs = `data-popup=\"${window.escapeHtml(item.popupJson)}\" onclick=\"window.openOrderLinkedFilesFromEl(this)\" title=\"Apri file collegati\"`;\n            const warnIcon = item.hasPriceRule ? `<span class=\"ti-order-warn ti-order-popup-trigger\" ${popupTriggerAttrs} data-istr=\"${window.escapeHtml(item.encodedIstr)}\" style='color:#22c55e;font-weight:900;cursor:pointer;font-size:16px;margin-right:5px;'>\u2757<\/span>` : '';\n            const fileIcon = `<span class=\"ti-order-file-icon ti-order-popup-trigger\" ${fileTriggerAttrs} style=\"cursor:pointer;\">\ud83d\udcc4<\/span>`;\n            const fileCountBadge = item.linkedFiles && item.linkedFiles.length > 1 ? `<span class=\"ti-order-file-count ti-order-popup-trigger\" ${fileTriggerAttrs} style=\"display:inline-flex;align-items:center;justify-content:center;margin-left:3px;min-width:18px;height:18px;border-radius:999px;background:#2563eb;color:#fff;font-size:10px;font-weight:800;vertical-align:middle;\">${item.linkedFiles.length}<\/span>` : '';\n            const imgHtml = item.fullImgUrl && item.isImgFile\n                ? `${warnIcon}<img decoding=\"async\" src=\"${item.fullImgUrl}\" data-fallback=\"${window.escapeHtml(fbUrl)}\" class=\"ti-row-img ti-order-popup-trigger\" data-popup=\"${window.escapeHtml(item.popupJson)}\" onclick=\"window.openOrderLinkedFilesFromEl(this)\" title=\"Apri file collegati\" onerror=\"window.applyFallbackImage(this)\" data-no-lazy=\"1\">${fileCountBadge}`\n                : (item.hasPriceRule ? warnIcon + fileIcon + fileCountBadge : fileIcon + fileCountBadge);\n            return `<tr data-order-id=\"${item.orderId}\" data-name=\"${item.nameFilter}\" data-desc=\"${item.descFilter}\" data-price=\"${item.priceFilter}\" data-iva=\"${window.escapeHtml(String(item.ivaRate || '22'))}\">\n                <td class=\"ti-col-img\">${imgHtml}<\/td>\n                <td class=\"ti-col-art\"><div style=\"display:flex;align-items:center;\"><span class=\"ti-item-name\" style=\"font-size:11px;font-weight:bold;\">${window.escapeHtml(item.voceName || item.n)}<\/span><\/div><\/td>\n                <td style=\"font-size:10px;color:#ccc;\">${window.escapeHtml(item.desc)}<\/td>\n                <td><\/td>\n                <td class=\"ti-col-eur ti-order-popup-trigger\" data-price=\"${item.pFinal}\" data-popup=\"${window.escapeHtml(item.popupJson)}\" onclick=\"window.openOrderItemPopupFromEl(this)\" title=\"Apri dettaglio voce\" style=\"cursor:pointer;\">${item.priceHtml}<\/td>\n                <td class=\"ti-col-qta\"><span class=\"ti-qty-stepper\"><button type=\"button\" class=\"ti-qty-btn\" onclick=\"window.stepQtyInput(this,-1)\">-<\/button><input type=\"text\" inputmode=\"decimal\" class=\"ti-inp ti-q ti-num-step\" data-order-id=\"${item.orderId}\" style=\"width:100%;min-width:64px;text-align:right;padding:2px;font-size:11px;\" value=\"${window.fmtNum(item.qtyDefault || 0)}\" placeholder=\"0,00\" aria-label=\"Quantit\u00e0\" onfocus=\"if(this.value==='0,00') this.value='';\" onblur=\"if(this.value==='') this.value='0,00'; else this.value=window.formatQtyInput(this.value); window.syncOrderQty(this);\" oninput=\"window.syncOrderQty(this)\"><button type=\"button\" class=\"ti-qty-btn\" onclick=\"window.stepQtyInput(this,1)\">+<\/button><\/span><\/td>\n                <td class=\"ti-col-tot p-tot\" data-order-id=\"${item.orderId}\" data-price=\"${item.pFinal}\">0,00<\/td>\n            <\/tr>`;\n        }).join('');\n\n        const cardsHtml = items.map(item => {\n            const fbUrl = window.getCompanyFallbackMediaUrl(gE('ti-ditta').value);\n            const cardFileBadge = item.linkedFiles && item.linkedFiles.length > 1 ? `<span class=\"ti-order-card-file-count ti-order-popup-trigger\" data-popup=\"${window.escapeHtml(item.popupJson)}\" onclick=\"window.openOrderLinkedFilesFromEl(this)\" title=\"${item.linkedFiles.length} file collegati\" style=\"position:absolute;right:7px;top:7px;min-width:22px;height:22px;border-radius:999px;background:#2563eb;color:#fff;font-size:11px;font-weight:800;display:flex;align-items:center;justify-content:center;cursor:pointer;\">${item.linkedFiles.length}<\/span>` : '';\n            const mediaHtml = item.fullImgUrl && item.isImgFile\n                ? `<div style=\"position:relative;width:100%;height:100%;\"><img decoding=\"async\" src=\"${item.fullImgUrl}\" alt=\"${window.escapeHtml(item.voceName || item.n)}\" class=\"ti-order-popup-trigger\" data-fallback=\"${window.escapeHtml(fbUrl)}\" data-popup=\"${window.escapeHtml(item.popupJson)}\" onclick=\"window.openOrderLinkedFilesFromEl(this)\" title=\"Apri file collegati\" onerror=\"window.applyFallbackImage(this)\">${cardFileBadge}<\/div>`\n                : `<div style=\"position:relative;width:100%;height:100%;\"><div class=\"ti-order-file-icon ti-order-popup-trigger\" data-popup=\"${window.escapeHtml(item.popupJson)}\" onclick=\"window.openOrderLinkedFilesFromEl(this)\" title=\"Apri file collegati\" style=\"cursor:pointer;\">\ud83d\udcc4<\/div>${cardFileBadge}<\/div>`;\n            const warnBtn = item.hasPriceRule ? `<button type=\"button\" class=\"ti-btn ti-order-warn ti-order-popup-trigger\" data-popup=\"${window.escapeHtml(item.popupJson)}\" data-istr=\"${window.escapeHtml(item.encodedIstr)}\" onclick=\"window.openOrderItemPopupFromEl(this)\" title=\"Apri dettaglio voce\" style=\"padding:4px 8px;font-size:10px;background:#14532d;cursor:pointer;\">\u2757 Prezzo<\/button>` : '';\n            return `<div class=\"ti-order-card\" data-order-id=\"${item.orderId}\" data-name=\"${item.nameFilter}\" data-desc=\"${item.descFilter}\" data-price=\"${item.priceFilter}\" data-iva=\"${window.escapeHtml(String(item.ivaRate || '22'))}\">\n                <div class=\"ti-order-card-media\">${mediaHtml}<\/div>\n                <div class=\"ti-order-card-body\">\n                    <div class=\"ti-order-card-title\">${window.escapeHtml(item.voceName || item.n || 'Voce')}<\/div>\n                    <div class=\"ti-order-card-desc\">${window.escapeHtml(item.desc || item.n || 'Nessuna descrizione disponibile')}<\/div>\n                    <div>${warnBtn}<\/div>\n                    <div class=\"ti-order-card-line\">Q.t\u00e0: <span class=\"ti-qty-stepper\"><button type=\"button\" class=\"ti-qty-btn\" onclick=\"window.stepQtyInput(this,-1)\">-<\/button><input type=\"text\" inputmode=\"decimal\" class=\"ti-q\" data-order-id=\"${item.orderId}\" value=\"${window.fmtNum(item.qtyDefault || 0)}\" placeholder=\"0,00\" aria-label=\"Quantit\u00e0\" onfocus=\"if(this.value==='0,00') this.value='';\" onblur=\"if(this.value==='') this.value='0,00'; else this.value=window.formatQtyInput(this.value); window.syncOrderQty(this);\" oninput=\"window.syncOrderQty(this)\"><button type=\"button\" class=\"ti-qty-btn\" onclick=\"window.stepQtyInput(this,1)\">+<\/button><\/span><\/div>\n                    <div class=\"ti-order-card-line\">Prezzo: <span class=\"ti-order-inline-price ti-order-popup-trigger\" data-popup=\"${window.escapeHtml(item.popupJson)}\" onclick=\"window.openOrderItemPopupFromEl(this)\" title=\"Apri dettaglio voce\" style=\"cursor:pointer;display:inline-block;\">${item.priceHtml}<\/span> <span class=\"ti-order-inline-total-wrap\">Totale: <span class=\"ti-order-card-total p-tot\" data-order-id=\"${item.orderId}\" data-price=\"${item.pFinal}\">0,00<\/span><\/span><\/div>\n                <\/div>\n            <\/div>`;\n        }).join('');\n\n        h += `<div class=\"ti-order-wrap ti-view-${preferredView}\">\n            <div class=\"ti-order-toolbar\">\n                <div class=\"ti-order-toolbar-left\">${showLayoutButtons ? viewButtonsHtml : ''}<\/div>\n                <div class=\"ti-order-toolbar-right\"><span class=\"ti-order-hint\">Descrizione \/ quantit\u00e0 \/ prezzo \/ totale<\/span><\/div>\n            <\/div>\n            <div class=\"ti-order-filterbar\">\n                <div class=\"ti-order-filtercell\"><span class=\"ti-order-filterlabel\">Voce<\/span><input type=\"text\" class=\"ti-filter-input\" oninput=\"window.filterOrderWrap(this,'name')\" placeholder=\"Filtra voce\"><\/div>\n                <div class=\"ti-order-filtercell\"><span class=\"ti-order-filterlabel\">Descrizione<\/span><input type=\"text\" class=\"ti-filter-input\" oninput=\"window.filterOrderWrap(this,'desc')\" placeholder=\"Filtra descrizione\"><\/div>\n                <div class=\"ti-order-filtercell\"><span class=\"ti-order-filterlabel\">Prezzo<\/span><input type=\"text\" class=\"ti-filter-input\" oninput=\"window.filterOrderWrap(this,'price')\" placeholder=\"> 10 \/ < 20 \/ 15\"><\/div>\n                <div class=\"ti-order-filtercell\"><span class=\"ti-order-filterlabel\">Ordina<\/span><select class=\"ti-in\" onchange=\"window.sortOrderWrap(this)\"><option value=\"name_asc\">Voce A-Z<\/option><option value=\"name_desc\">Voce Z-A<\/option><option value=\"desc_asc\">Descrizione A-Z<\/option><option value=\"desc_desc\">Descrizione Z-A<\/option><option value=\"price_asc\">Prezzo crescente<\/option><option value=\"price_desc\">Prezzo decrescente<\/option><\/select><\/div>\n            <\/div>\n            <div class=\"ti-order-table-shell\"><div class=\"ti-order-top-scroll ti-sync-hscroll-top\"><div class=\"ti-cfg-top-scroll-inner ti-sync-hscroll-inner\"><\/div><\/div><div class=\"ti-order-table-view ti-table-wrap ti-sync-hscroll-wrap\"><table class=\"ti-prod-table\"><thead><tr><th class=\"ti-col-img\">Doc<\/th><th onclick=\"window.sortTI(this,1)\">Voce<\/th><th style=\"text-align:left;\" onclick=\"window.sortTI(this,2)\">Descrizione<\/th><th class=\"ti-col-disp\">Disp<\/th><th class=\"ti-col-eur\" onclick=\"window.sortTI(this,4)\">Prezzo<\/th><th class=\"ti-col-qta\">Qt\u00e0<\/th><th class=\"ti-col-tot\">Tot<\/th><\/tr><\/thead><tbody>${tableRows}<\/tbody><\/table><\/div><div class=\"ti-order-bottom-scroll ti-sync-hscroll-bottom\"><div class=\"ti-cfg-top-scroll-inner ti-sync-hscroll-inner\"><\/div><\/div><\/div>\n            <div class=\"ti-order-grid-view\">${cardsHtml}<\/div>\n        <\/div>`;\n        if (tiPostTableInfoHtml) h += tiPostTableInfoHtml;\n        return h;\n    };\n\n    window.pendingOrderState = null;\n    window.confirmedOrderTokens = window.confirmedOrderTokens || {};\n\n    window.ensureExternalOrderConfirmHost = function() {\n        const inputWrap = document.querySelector('#ti-ai-outer .ti-input-wrap');\n        const chatBox = gE('ti-chat-box');\n        let host = gE('ti-order-confirm-host');\n        if (!inputWrap || !chatBox || !inputWrap.parentNode) return null;\n        if (!host) {\n            host = document.createElement('div');\n            host.id = 'ti-order-confirm-host';\n            host.className = 'ti-order-confirm-host';\n        }\n        if (host.parentNode !== inputWrap.parentNode || host.nextElementSibling !== inputWrap) {\n            inputWrap.parentNode.insertBefore(host, inputWrap);\n        }\n        return host;\n    };\n\n    window.getLastVisibleOrderWrap = function() {\n        const chatBox = gE('ti-chat-box');\n        if (!chatBox) return null;\n        const wraps = Array.from(chatBox.querySelectorAll('.ti-order-wrap'));\n        let last = null;\n        wraps.forEach(function(w){\n            if (window.isOrderWrapVisibleInChat(w)) last = w;\n        });\n        return last;\n    };\n\n    window.isOrderWrapVisibleInChat = function(wrap) {\n        const chatBox = gE('ti-chat-box');\n        if (!wrap || !chatBox || !document.body.contains(wrap)) return false;\n        const wrapRect = wrap.getBoundingClientRect();\n        const chatRect = chatBox.getBoundingClientRect();\n        return wrapRect.bottom > chatRect.top + 20 && wrapRect.top < chatRect.bottom - 20;\n    };\n\n    window.ensureOrderWrapToken = function(wrap) {\n        if (!wrap) return '';\n        if (!wrap.dataset.orderWrapToken) wrap.dataset.orderWrapToken = 'ow_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8);\n        return wrap.dataset.orderWrapToken;\n    };\n\n    window.buildOrderSummaryFromWrap = function(wrap) {\n        if (!wrap) return null;\n        const table = wrap.querySelector('table');\n        if (!table) return null;\n        let orderText = \"RIEPILOGO ORDINE:\\n\";\n        let totalGross = 0;\n        let totalVat = 0;\n        let hasItems = false;\n        table.querySelectorAll('tbody tr').forEach(tr => {\n            if (tr.style.display === 'none') return;\n            const qtyInput = tr.querySelector('.ti-q');\n            if (!qtyInput) return;\n            let qty = window.parseQtyInput(qtyInput.value);\n            if (qty > 0) {\n                let name = tr.querySelector('.ti-item-name') ? tr.querySelector('.ti-item-name').innerText.trim() : '';\n                let price = parseFloat(tr.getAttribute('data-price') || (tr.querySelector('.ti-col-eur') ? tr.querySelector('.ti-col-eur').getAttribute('data-price') : '0')) || 0;\n                let ivaRate = window.parseIvaRate(tr.getAttribute('data-iva') || 22, 22);\n                let rowGross = price * qty;\n                let rowVat = window.calcVatAmountFromGross(rowGross, ivaRate);\n                orderText += `- ${window.fmtNum(qty)}x ${name} (\u20ac ${window.fmtNum(rowGross)})\\n`;\n                totalGross += rowGross;\n                totalVat += rowVat;\n                hasItems = true;\n            }\n        });\n        const totalNet = totalGross - totalVat;\n        if (!hasItems) {\n            orderText += \"- Nessun articolo selezionato\\n\";\n        }\n        orderText += `\\nIMPONIBILE: \u20ac ${window.fmtNum(totalNet)}\\nIVA SCORPORATA: \u20ac ${window.fmtNum(totalVat)}\\nTOTALE LORDO: \u20ac ${window.fmtNum(totalGross)}`;\n        return { text: orderText, totalGross: totalGross, totalVat: totalVat, totalNet: totalNet, hasItems: hasItems };\n    };\n\n    window.hideOrderConfirmPanel = function() {\n        window.pendingOrderState = null;\n        const host = window.ensureExternalOrderConfirmHost();\n        if (host) { host.style.display = 'none'; host.innerHTML = ''; }\n    };\n\n    window.refreshOrderConfirmPanel = function() {\n        const host = window.ensureExternalOrderConfirmHost();\n        if (!host) return;\n        const visibleWraps = Array.from(document.querySelectorAll('#ti-chat-box .ti-order-wrap')).filter(function(w){\n            return window.isOrderWrapVisibleInChat(w);\n        });\n        const wrap = visibleWraps.length ? visibleWraps[visibleWraps.length - 1] : null;\n        if (!wrap) { host.style.display = 'none'; host.innerHTML = ''; window.pendingOrderState = null; return; }\n        const token = window.ensureOrderWrapToken(wrap);\n        window.pendingOrderState = { token: token };\n        const summary = window.buildOrderSummaryFromWrap(wrap);\n        if (!summary) { host.style.display = 'none'; host.innerHTML = ''; return; }\n        const alreadyConfirmed = !!window.confirmedOrderTokens[token];\n        host.style.display = 'block';\n        const stateNoticeHtml = window.getCompanyLimitedNoticeHtml ? window.getCompanyLimitedNoticeHtml() : '';\n        host.innerHTML = `<div class=\"ti-order-confirm-title\">${alreadyConfirmed ? 'Ordine confermato' : 'Conferma ordine in corso'}<\/div>\n            ${stateNoticeHtml}\n            <div class=\"ti-order-confirm-box\">${window.escapeHtml(summary.text)}<\/div>\n            <div class=\"ti-order-confirm-meta\">\n                <div>Imponibile: <b>\u20ac ${window.fmtNum(summary.totalNet)}<\/b><\/div>\n                <div>IVA: <b>\u20ac ${window.fmtNum(summary.totalVat)}<\/b><\/div>\n                <div>Totale lordo: <b>\u20ac ${window.fmtNum(summary.totalGross)}<\/b><\/div>\n            <\/div>\n            <div class=\"ti-order-confirm-actions\">\n                <button type=\"button\" class=\"ti-btn ti-btn-ok\" ${(summary.hasItems && !alreadyConfirmed) ? '' : 'disabled style=\"opacity:.55;cursor:not-allowed;\"'} onclick=\"window.confirmPendingOrder()\">${alreadyConfirmed ? 'Ordine confermato' : 'Conferma ordine'}<\/button>\n                <button type=\"button\" class=\"ti-btn btn-close\" onclick=\"window.hideOrderConfirmPanel()\">Chiudi conferma<\/button>\n            <\/div>`;\n    };\n\n    window.confirmPendingOrder = function() {\n        const st = window.pendingOrderState || null;\n        if (!st || !st.token) { window.hideOrderConfirmPanel(); return; }\n        if (window.confirmedOrderTokens[st.token]) { window.refreshOrderConfirmPanel(); return; }\n        const wrap = document.querySelector('.ti-order-wrap[data-order-wrap-token=\"' + st.token + '\"]');\n        if (!wrap || !document.body.contains(wrap)) { window.hideOrderConfirmPanel(); window.tiAlert('Ordine non pi\u00f9 disponibile.'); return; }\n        const summary = window.buildOrderSummaryFromWrap(wrap);\n        if (!summary || !summary.hasItems) { window.tiAlert('Seleziona almeno un articolo nella tabella ordine.'); return; }\n        window.confirmedOrderTokens[st.token] = true;\n        window.tiHiddenSendText = summary.text;\n        window.tiHiddenSendLabel = 'Conferma ordine';\n        if (gE('ti-msg')) gE('ti-msg').value = '';\n        window.refreshOrderConfirmPanel();\n        if (gE('ti-send')) gE('ti-send').click();\n    };\n\n    window.sendOrderInChat = function(btn) { \n        const wrap = btn.closest('.ti-order-wrap') || btn.closest('.ti-table-wrap');\n        if (!wrap) return;\n        const token = window.ensureOrderWrapToken(wrap);\n        const summary = window.buildOrderSummaryFromWrap(wrap);\n        if (!summary) { window.tiAlert(\"Nessun articolo selezionato.\"); return; }\n        window.pendingOrderState = { token: token };\n        window.refreshOrderConfirmPanel();\n        const host = window.ensureExternalOrderConfirmHost();\n        if (host && host.scrollIntoView) {\n            try { host.scrollIntoView({behavior:'smooth', block:'nearest'}); } catch(e) { host.scrollIntoView(true); }\n        }\n    };\n\n    window.bindOrderConfirmHostSync = function() {\n        const chatBox = gE('ti-chat-box');\n        if (!chatBox || chatBox.dataset.orderConfirmSyncBound === '1') return;\n        chatBox.dataset.orderConfirmSyncBound = '1';\n        chatBox.addEventListener('scroll', function(){\n            window.refreshOrderConfirmPanel();\n        }, { passive: true });\n    };\n    window.bindOrderConfirmHostSync();\n    document.addEventListener('DOMContentLoaded', function(){ window.bindOrderConfirmHostSync(); });\n\n    window.changePage = function(p) { const d = gE('ti-ditta'); if(d && d.value) { const fd = new FormData(); fd.append('ti_action','ti_ai_chat_start'); fd.append('text','lista'); fd.append('page_num',p); fd.append('db',d.value); fetch(window.tiUrl, {method:'POST',body:fd}).then(r=>r.json()).then(res=>{ const bx = gE('ti-chat-box'); if(bx) { bx.innerHTML += res.data.reply; bx.scrollTop=bx.scrollHeight; } }); } };\n    window.openCamModal = function() { const ov = gE('ti-cam-ov'); const video = gE('ti-video-feed'); if(ov && video && navigator.mediaDevices) { window.showPluginModal ? window.showPluginModal(ov) : (ov.style.display = 'flex'); navigator.mediaDevices.getUserMedia({ video: { facingMode: \"environment\" } }).then(s => { window.stream = s; video.srcObject = s; }).catch(e => { gE('ti-cam-in').click(); }); } else { gE('ti-cam-in').click(); } };\n    window.closeCamModal = function() { const ov = gE('ti-cam-ov'); if(ov) { window.closeModal ? window.closeModal(ov) : (ov.style.display = 'none'); } if(window.stream) { window.stream.getTracks().forEach(t=>t.stop()); window.stream = null; } };\n    window.snapPhoto = function() { const video = gE('ti-video-feed'); const canvas = gE('ti-canvas-snap'); if(video && canvas) { canvas.width = video.videoWidth; canvas.height = video.videoHeight; canvas.getContext('2d').drawImage(video, 0, 0); canvas.toBlob(blob => { const file = new File([blob], \"snap.jpg\", { type: \"image\/jpeg\" }); const dt = new DataTransfer(); dt.items.add(file); const camIn = gE('ti-cam-in'); if(camIn) { camIn.files = dt.files; camIn.dispatchEvent(new Event('change')); } window.closeCamModal(); }, 'image\/jpeg', 0.85); } };\n\n\n    window.tiSpeechPrimed = false;\n    window.primeSpeech = function() {\n        if (!('speechSynthesis' in window) || window.tiSpeechPrimed) return;\n        try {\n            const u = new SpeechSynthesisUtterance(' ');\n            u.volume = 0;\n            window.speechSynthesis.cancel();\n            window.speechSynthesis.speak(u);\n            window.tiSpeechPrimed = true;\n        } catch(e) {}\n    };\n    ['click','touchstart','keydown'].forEach(function(evt){\n        document.addEventListener(evt, function(){ window.primeSpeech(); }, { once:true, passive:true });\n    });\n\n    window.tiIsNoPopupVoiceRole = function() {\n        const role = String(window.tiRole || window.tiUserRole || window.currentUserRole || '').toLowerCase();\n        const user = String(window.tiUser || window.tiUsername || '').toLowerCase();\n        return user === 'ssglobaladmin' || \/amministratore|configuratore|responsabile\/.test(role);\n    };\n    window.tiHasVisiblePopupOrAlert = function() {\n        try {\n            return !!Array.from(document.querySelectorAll('.ti-ov, #ti-alert-ov, #ti-ai-loader-ov')).find(function(el){\n                if (!el) return false;\n                const cs = window.getComputedStyle ? window.getComputedStyle(el) : null;\n                const disp = cs ? cs.display : el.style.display;\n                const vis = cs ? cs.visibility : el.style.visibility;\n                return disp !== 'none' && vis !== 'hidden' && el.offsetParent !== null;\n            });\n        } catch(e) { return false; }\n    };\n    window.tiShouldSuppressPopupSpeech = function(text) {\n        if (!window.tiIsNoPopupVoiceRole || !window.tiIsNoPopupVoiceRole()) return false;\n        const raw = String(text || '');\n        const clean = raw.replace(\/<[^>]+>\/g, ' ').replace(\/\\s+\/g, ' ').trim().toLowerCase();\n        if (!clean) return false;\n        if (window.tiHasVisiblePopupOrAlert && window.tiHasVisiblePopupOrAlert()) return true;\n        return \/^(\u26a0|\u2757|attenzione|avviso|errore|conferma|operazione|seleziona|inserisci|nessun|solo|sessione|elaborazione|processo|salvataggio|login|accesso|richiesta)\/i.test(clean);\n    };\n\n    \/* 30.9.232 - La sintesi vocale non deve pronunciare punteggiatura o simboli; protegge anche gli apostrofi italiani, es. l'AI non diventa elle AI. *\/\n    window.tiPrepareSpeechPunctuation = function(text) {\n        let s = String(text || '');\n        if (!s.trim()) return s;\n\n        \/\/ Protegge l'articolo eliso prima della pulizia della punteggiatura.\n        \/\/ Senza questa normalizzazione \"l'AI\" diventava \"l AI\" e alcune voci leggevano \"elle AI\".\n        s = s\n            .replace(\/\\b([Ll])[\\u2019']\\s*(AI|A\\.?I\\.?|IA)\\b\/g, function(m, art, acr) { return (art === 'L' ? 'La ' : 'la ') + acr.replace(\/\\.\/g, ''); })\n            .replace(\/\\b([Ll])[\\u2019']\\s*([A-Za-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff])\/g, function(m, art, chr) { return (art === 'L' ? 'L' : 'l') + chr; });\n\n        \/\/ Non trasformare mai i segni in parole (niente \"punto\", \"virgola\", \"segno di spunta\").\n        \/\/ I segni di fine frase diventano a-capo per ottenere una pausa vocale senza pronunciare il carattere.\n        s = s\n            .replace(\/\\.\\.\\.\/g, '\\n')\n            .replace(\/[.!?\u00bf\u00a1]+\/g, '\\n')\n            .replace(\/[,;:]+\/g, ' ')\n            .replace(\/[()\\[\\]{}<>]\/g, ' ')\n            .replace(\/[\"\u201c\u201d\u2018\u2019'`\u00b4]\/g, ' ')\n            .replace(\/[\\\/\\\\|]\/g, ' ')\n            .replace(\/\\s+[\u2013\u2014-]\\s+\/g, ' ')\n            .replace(\/[\u2013\u2014]\/g, ' ');\n\n        \/\/ Rimuove caratteri grafici\/simboli che alcune voci leggono letteralmente,\n        \/\/ ad esempio \"segno di spunta\", \"croce\", \"triangolo di attenzione\".\n        s = s\n            .replace(\/[\u2713\u2714\u2611\u2705\u2717\u2718\u274c\u2b55\u26d4\u26a0\u2757\u2753\u2b50\u2605\u2606\u25c6\u25c7\u25a0\u25a1\u25b2\u25b3\u25bc\u25bd\u25cf\u25cb\u2022\u00b7]\/g, ' ')\n            .replace(\/[\\u2600-\\u27BF\\u2B00-\\u2BFF]\/g, ' ')\n            .replace(\/[\\uFE0E\\uFE0F\\u200D]\/g, ' ')\n            .replace(\/[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]\/g, ' ')\n            .replace(\/[ \\t]+\/g, ' ')\n            .replace(\/\\n\\s+\/g, '\\n')\n            .replace(\/\\n{2,}\/g, '\\n')\n            .trim();\n\n        return s;\n    };\n\n    window.speakText = function(text) {\n        if (!('speechSynthesis' in window) || window.isMuted) return; \n        let raw = String(text || '');\n        if (window.tiShouldSuppressPopupSpeech && window.tiShouldSuppressPopupSpeech(raw)) return;\n        window.primeSpeech();\n        window.speechSynthesis.cancel();\n\n        raw = raw.replace(\/<!--NOAUDIO_START-->[\\s\\S]*?<!--NOAUDIO_END-->\/g, '');\n        raw = raw.replace(\/<div[^>]*class=[\"'][^\"']*ti-company-state-warning[^\"']*[\"'][^>]*>[\\s\\S]*?<\\\/div>\/gi, '');\n        raw = raw.replace(\/<div[^>]*>\\s*\u26a0\ufe0f\\s*Stato ditta:[\\s\\S]*?<\\\/div>\/gi, '');\n        raw = raw.replace(\/\u26a0\ufe0f\\s*Stato ditta:\\s*(Configurazione|Sospesa)[\\s\\S]*?(?=(Sono l'assistente|Come posso|$))\/gi, '');\n        raw = raw.replace(\/Attenzione\\.\\s*La ditta risulta (sospesa|in configurazione)\\.[\\s\\S]*?(?=(Sono l'assistente|Come posso|$))\/gi, '');\n        raw = raw.replace(\/<div[^>]*>\\s*<b>\\s*\u2139\ufe0f\\s*Report esecuzione istruzioni AI<\\\/b>[\\s\\S]*?<\\\/div>\/gi, '');\n        let clean = raw.replace(\/\\[TABLE\\][\\s\\S]*?\\[\\\/TABLE\\]\/g, '').replace(\/\\[P\\][\\s\\S]*?\\[\\\/P\\]\/g, '').replace(\/<\\s*br\\s*\\\/?\\s*>\/gi, '\\n').replace(\/<\\\/(p|div|li|tr|h[1-6])>\/gi, '\\n').replace(\/<[^>]+>\/g, ' ').trim();\n        clean = clean.replace(\/[*_#~`]\/g, ''); \n        clean = clean.replace(\/(\\d{1,2})\\\/(\\d{1,2})\\\/(\\d{4})\/g, \"$1 del $2 $3\");\n        clean = clean.replace(\/(\\d{1,2})\\\/(\\d{1,2})\/g, \"$1 del $2\");\n        clean = clean.replace(\/(\\d{1,2}):(\\d{2})\/g, \"$1 e $2 minuti\");\n        clean = clean.replace(\/tasto\\s+X\\b\/gi, \"tasto ixs\");\n        clean = clean.replace(\/\\bX\\b(?=\\s*per\\s+annullare)\/gi, \"ixs\");\n        if (window.tiPrepareSpeechPunctuation) clean = window.tiPrepareSpeechPunctuation(clean);\n\n        if (clean) { \n            let u = new SpeechSynthesisUtterance(clean); \n            u.lang = window.currLang === 'en' ? 'en-US' : 'it-IT'; u.rate = 1.0; u.pitch = 1.2; \n            let voices = window.speechSynthesis.getVoices();\n            if(voices.length > 0) {\n                let femaleVoice = voices.find(v => v.lang.includes(u.lang) && (v.name.includes('Google') || v.name.includes('Elsa') || v.name.includes('Bianca') || v.name.includes('Female') || v.name.includes('Samantha') || v.name.includes('Victoria') || v.name.includes('Jenny') || v.name.includes('Zira') || v.name.includes('Hazel')));\n                if(femaleVoice) { u.voice = femaleVoice; } else { let langVoice = voices.find(v => v.lang.includes(u.lang)); if(langVoice) u.voice = langVoice; }\n            }\n            setTimeout(() => { window.speechSynthesis.speak(u); }, 150);\n        }\n    };\n    if ('speechSynthesis' in window) { window.speechSynthesis.onvoiceschanged = function() { window.speechSynthesis.getVoices(); }; }\n\n    window.extractCurrentUserEmailFromData = function(data) {\n        try {\n            if (!data || !data.Tabelle || !window.tiUser || window.tiUser === 'SSGlobalAdmin') return '';\n            const tables = ['Utenti', 'Operatori'];\n            for (const tbl of tables) {\n                const rows = Array.isArray(data.Tabelle[tbl]) ? data.Tabelle[tbl] : Object.values((data.Tabelle && data.Tabelle[tbl]) || {});\n                for (const row of rows) {\n                    if (!row || typeof row !== 'object') continue;\n                    const userKey = Object.keys(row).find(k => String(k).toLowerCase() === 'username' || String(k).toLowerCase() === 'user');\n                    if (!userKey) continue;\n                    if (String(row[userKey] || '').trim().toLowerCase() !== String(window.tiUser || '').trim().toLowerCase()) continue;\n                    const emailKey = Object.keys(row).find(k => String(k).toLowerCase() === 'email');\n                    const email = emailKey ? String(row[emailKey] || '').trim() : '';\n                    if (email) return email;\n                }\n            }\n        } catch(e) {}\n        return '';\n    };\n\n    window.prefillSupportFeedbackForm = function(forceRefresh) {\n        const emailInput = gE('ti-support-email');\n        const infoBox = gE('help-email');\n        if (!emailInput) return Promise.resolve('');\n        if (!forceRefresh && String(emailInput.value || '').trim() !== '') {\n            const keepVal = String(emailInput.value || '').trim();\n            if (infoBox) infoBox.innerHTML = keepVal ? '<div style=\"font-size:12px;color:#93c5fd;\">Email utente proposta: <b>' + window.escapeHtml(keepVal) + '<\/b><\/div>' : '';\n            return Promise.resolve(keepVal);\n        }\n        const localEmail = window.extractCurrentUserEmailFromData(window.currentDbData);\n        if (localEmail) {\n            emailInput.value = localEmail;\n            if (infoBox) infoBox.innerHTML = '<div style=\"font-size:12px;color:#93c5fd;\">Email utente proposta: <b>' + window.escapeHtml(localEmail) + '<\/b><\/div>';\n            return Promise.resolve(localEmail);\n        }\n        const dbSel = gE('ti-ditta');\n        if (!dbSel || !dbSel.value || dbSel.value === 'NEW_DB' || !window.tiUser || window.tiUser === 'SSGlobalAdmin') {\n            if (infoBox) infoBox.innerHTML = '';\n            return Promise.resolve('');\n        }\n        const fd = new FormData();\n        fd.append('ti_action', 'ti_ai_get_db_data');\n        fd.append('db', dbSel.value);\n        return window.fetchJsonSafe((window.tiAjaxUrl || window.tiUrl), {method:'POST', body:fd, credentials:'same-origin'}).then(function(res){\n            const email = (res && res.success && res.data) ? window.extractCurrentUserEmailFromData(res.data) : '';\n            if (email) {\n                window.currentDbData = res.data;\n                emailInput.value = email;\n                if (infoBox) infoBox.innerHTML = '<div style=\"font-size:12px;color:#93c5fd;\">Email utente proposta: <b>' + window.escapeHtml(email) + '<\/b><\/div>';\n            } else if (infoBox) {\n                infoBox.innerHTML = '';\n            }\n            return email;\n        }).catch(function(){ if (infoBox) infoBox.innerHTML = ''; return ''; });\n    };\n\n    window.toggleSupportFeedbackBox = function(forceOpen) {\n        const box = gE('ti-help-support-box');\n        const toggleBtn = gE('ti-help-support-toggle');\n        if (!box) return;\n        const shouldOpen = (typeof forceOpen === 'boolean') ? forceOpen : (box.style.display === 'none' || box.style.display === '');\n        box.style.display = shouldOpen ? 'block' : 'none';\n        if (toggleBtn) toggleBtn.innerText = shouldOpen ? '\u2716\ufe0f Chiudi segnalazione assistenza' : '\ud83d\udee0\ufe0f Segnalazione assistenza tecnica';\n        if (shouldOpen) {\n            window.prefillSupportFeedbackForm(true).then(function(){ const sbj = gE('ti-support-subject'); if (sbj && !sbj.value) sbj.focus(); });\n        }\n    };\n\n    window.getSupportFeedbackChatHistory = function() {\n        const chat = gE('ti-chat-box');\n        if (!chat) return '';\n        const lines = [];\n        const bubbles = Array.from(chat.querySelectorAll('.ti-bubble')).slice(-30);\n        bubbles.forEach(function(b){\n            const clone = b.cloneNode(true);\n            clone.querySelectorAll('table, .ti-order-grid-view, .ti-order-toolbar, .ti-order-filterbar, .ti-cfg-top-scroll, .ti-cfg-bottom-scroll, .ti-order-top-scroll, .ti-order-bottom-scroll').forEach(function(n){ n.remove(); });\n            const txt = String(clone.innerText || '').trim();\n            if (!txt || txt.indexOf('Fammi pensare') !== -1) return;\n            lines.push((b.classList.contains('user') ? 'Utente' : 'Assistente') + ': ' + txt);\n        });\n        let out = lines.join('\\n\\n').trim();\n        if (out.length > 15000) out = out.slice(out.length - 15000);\n        return out;\n    };\n\n    window.sendSupportFeedback = function() {\n        const dbSel = gE('ti-ditta');\n        const email = (gE('ti-support-email') ? gE('ti-support-email').value : '').trim();\n        const subject = (gE('ti-support-subject') ? gE('ti-support-subject').value : '').trim();\n        const message = (gE('ti-support-message') ? gE('ti-support-message').value : '').trim();\n        const sendBtn = gE('ti-support-send');\n        if (!subject) { window.tiAlert('Oggetto obbligatorio.'); return; }\n        if (!message) { window.tiAlert('Inserisci il testo della segnalazione.'); return; }\n        if (email && !\/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\/.test(email)) { window.tiAlert('Email mittente non valida.'); return; }\n        const fd = new FormData();\n        fd.append('ti_action', 'ti_ai_send_email');\n        fd.append('mode', 'support_feedback');\n        fd.append('db', dbSel && dbSel.value ? dbSel.value : 'NEW_DB');\n        fd.append('sender_email', email);\n        fd.append('feedback_subject', subject);\n        fd.append('feedback_message', message);\n        fd.append('chat_history', window.getSupportFeedbackChatHistory());\n        if (sendBtn) { sendBtn.disabled = true; sendBtn.innerText = 'Invio in corso...'; }\n        window.fetchJsonSafe((window.tiAjaxUrl || window.tiUrl), {method:'POST', body:fd, credentials:'same-origin'}).then(function(res){\n            if (sendBtn) { sendBtn.disabled = false; sendBtn.innerText = 'Invia segnalazione'; }\n            if (res && res.success) {\n                if (gE('ti-support-subject')) gE('ti-support-subject').value = '';\n                if (gE('ti-support-message')) gE('ti-support-message').value = '';\n                window.toggleSupportFeedbackBox(false);\n                window.tiAlert((res.data && res.data.reply) ? res.data.reply : 'La segnalazione sar\u00e0 analizzata per eventualmente rispondere.<br><br>Grazie per la collaborazione.');\n            } else {\n                window.tiAlert((res && res.data && res.data.message) ? res.data.message : 'Errore invio segnalazione.');\n            }\n        }).catch(function(err){ if (sendBtn) { sendBtn.disabled = false; sendBtn.innerText = 'Invia segnalazione'; } window.tiAlert('Errore invio segnalazione: ' + ((err && err.message) ? err.message : 'errore sconosciuto')); });\n    };\n\n    window.openHelpModal = function() {\n        window.prefillSupportFeedbackForm(true);\n        window.openModal('ti-help-ov');\n    };\n\n    window.sendHelpChat = () => {\n        const msg = gE('help-bot-in').value; if(!msg) return;\n        const replyBox = gE('help-bot-reply');\n        replyBox.innerHTML = \"<i>Analizzo il manuale... \u23f3<\/i>\";\n        gE('help-bot-in').value = '';\n        \n        const fd = new FormData(); fd.append('ti_action','ti_ai_chat_start'); fd.append('db', gE('ti-ditta').value || 'NEW_DB');\n        fd.append('text', \"DOMANDA SUL MANUALE CONFIGURAZIONE: Rispondi solo con informazioni operative chiare ed esempi di compilazione basati su manuale, schema standard e dati disponibili. Non riportare frasi su campi vuoti, colonne senza nome o valori \\\"\\\". Domanda: \" + msg);\n        \n        fetch(window.tiUrl,{method:'POST',body:fd}).then(r=>r.json()).then(res=>{\n            if(res.success) { replyBox.innerHTML = res.data.reply.replace(\/\\n\/g, '<br>'); } \n            else { replyBox.innerHTML = \"<span style='color:#ef4444;'>Errore AI. Controlla la connessione.<\/span>\"; }\n        });\n    };\n\n    window.applyMuteState = function() {\n        const muteBtn = gE('ti-mute-btn'); if(!muteBtn) return;\n        const d = dict[window.currLang] || dict['it'];\n        if(window.muteState === 0) {\n            window.isMuted = false;\n            muteBtn.innerText = d.muteOn;\n            muteBtn.style.background = '#222';\n            muteBtn.style.borderColor = '#444';\n        } else if (window.muteState === 1) {\n            window.isMuted = true;\n            muteBtn.innerText = d.muteOff;\n            muteBtn.style.background = '#000';\n            muteBtn.style.borderColor = '#333';\n        } else if (window.muteState === 2) {\n            window.isMuted = true;\n            muteBtn.innerText = d.muteOff;\n            muteBtn.style.background = '#b91c1c';\n            muteBtn.style.borderColor = '#991b1b';\n        }\n    };\n\n    window.tiTranslateHtmlLocallyToCurrentLanguage = function(html) {\n        html = String(html || '');\n        if (!html.trim() || String(window.currLang || 'it').toLowerCase() !== 'en') return html;\n        const box = document.createElement('div');\n        box.innerHTML = html;\n        try {\n            if (window.tiCleanEscapedUiInSubtree) window.tiCleanEscapedUiInSubtree(box);\n            if (window.tiI18nTranslateSubtree) window.tiI18nTranslateSubtree(box);\n        } catch(e) {}\n        return box.innerHTML || html;\n    };\n    window.tiRemoteTranslateHtmlForCurrentLanguage = function(html, dbValue) {\n        \/\/ v30.9.285: non usare fetch di traduzione durante inserimento\/switch lingua.\n        \/\/ Il fetch poteva ritardare o bloccare IT\/EN; la traduzione immediata usa dizionario\/cache DOM locale.\n        return Promise.resolve(window.tiTranslateHtmlLocallyToCurrentLanguage ? window.tiTranslateHtmlLocallyToCurrentLanguage(html) : String(html || ''));\n    };\n    window.tiAddAiMessageTranslatedIfNeeded = function(html, voice, dbValue) {\n        const originalHtml = String(html || '');\n        const originalVoice = String(voice || originalHtml || '');\n        const bubble = window.addMsg ? window.addMsg('ai', originalHtml, originalHtml) : null;\n        let voiceSource = originalVoice;\n        if (String(window.currLang || 'it').toLowerCase() === 'en') {\n            voiceSource = bubble ? String(bubble.innerText || bubble.textContent || '') : (window.tiTranslateHtmlLocallyToCurrentLanguage ? window.tiTranslateHtmlLocallyToCurrentLanguage(originalVoice) : originalVoice);\n            voiceSource = String(voiceSource || '').replace(\/<[^>]+>\/g, ' ').replace(\/\\s+\/g, ' ').trim();\n            if (window.tiQueueRemoteEnglishChatTranslation && bubble) window.tiQueueRemoteEnglishChatTranslation(bubble, originalHtml, dbValue);\n        }\n        if (voiceSource && window.speakText) window.speakText(voiceSource);\n        return bubble;\n    };\n\n\n    window.tiChatTranslationCache = window.tiChatTranslationCache || Object.create(null);\n    window.tiChatTranslationPending = window.tiChatTranslationPending || Object.create(null);\n    window.tiHashString = function(str) {\n        str = String(str == null ? '' : str);\n        let h = 0;\n        for (let i = 0; i < str.length; i++) { h = ((h << 5) - h + str.charCodeAt(i)) | 0; }\n        return String(h >>> 0) + '_' + String(str.length);\n    };\n    window.tiRememberVisibleChatLanguage = function(lang) {\n        lang = String(lang || window.currLang || 'it').toLowerCase();\n        document.querySelectorAll('.ti-bubble.ai').forEach(function(b){\n            if (!b.__tiChatHtmlByLang) b.__tiChatHtmlByLang = {};\n            if (!b.__tiChatHtmlByLang[lang]) b.__tiChatHtmlByLang[lang] = b.innerHTML;\n        });\n    };\n    window.tiRemoteTranslateHtmlToLanguage = function(html, targetLang, dbValue, fallback) {\n        targetLang = String(targetLang || 'it').toLowerCase() === 'en' ? 'en' : 'it';\n        html = String(html || '');\n        const localFallback = (fallback !== undefined) ? fallback : html;\n        if (!html.trim()) return Promise.resolve(html);\n        const cacheKey = targetLang + ':' + window.tiHashString(html);\n        if (window.tiChatTranslationCache && Object.prototype.hasOwnProperty.call(window.tiChatTranslationCache, cacheKey)) {\n            return Promise.resolve(window.tiChatTranslationCache[cacheKey]);\n        }\n        if (window.tiChatTranslationPending && window.tiChatTranslationPending[cacheKey]) return window.tiChatTranslationPending[cacheKey];\n        try {\n            const fd = new FormData();\n            fd.append('ti_action', 'ti_ai_chat_start');\n            fd.append('db', dbValue || (gE('ti-ditta') ? gE('ti-ditta').value : '') || '');\n            fd.append('text', 'TRADUCI_ESATTAMENTE_QUESTO_HTML_IN_' + targetLang.toUpperCase() + ': ' + html);\n            if (window.tiAppendCurrentLanguageToFormData) window.tiAppendCurrentLanguageToFormData(fd);\n            const pending = fetch(window.tiUrl, {method:'POST', body:fd})\n                .then(function(r){ return r.json(); })\n                .then(function(res){\n                    const out = (res && res.success && res.data && res.data.reply) ? String(res.data.reply || '') : localFallback;\n                    window.tiChatTranslationCache[cacheKey] = out;\n                    delete window.tiChatTranslationPending[cacheKey];\n                    return out;\n                })\n                .catch(function(){ delete window.tiChatTranslationPending[cacheKey]; return localFallback; });\n            window.tiChatTranslationPending[cacheKey] = pending;\n            return pending;\n        } catch(e) { return Promise.resolve(localFallback); }\n    };\n    window.tiQueueRemoteEnglishChatTranslation = function(bubble, sourceHtml, dbValue) {\n        if (!bubble || !sourceHtml || String(window.currLang || 'it').toLowerCase() !== 'en') return;\n        sourceHtml = String(sourceHtml || '');\n        if (!sourceHtml.trim() || sourceHtml.length > 24000) return;\n        if (!window.tiRemoteTranslateHtmlToLanguage) return;\n        const sourceKey = window.tiHashString ? window.tiHashString(sourceHtml) : String(sourceHtml.length);\n        if (bubble.__tiRemoteEnSourceKey === sourceKey) return;\n        bubble.__tiRemoteEnSourceKey = sourceKey;\n        const timeout = new Promise(function(resolve){ setTimeout(function(){ resolve(null); }, 6500); });\n        Promise.race([window.tiRemoteTranslateHtmlToLanguage(sourceHtml, 'en', dbValue || (gE('ti-ditta') ? gE('ti-ditta').value : ''), null), timeout]).then(function(outHtml){\n            if (!outHtml || String(window.currLang || 'it').toLowerCase() !== 'en') return;\n            const currentIt = bubble.getAttribute('data-ti-it-html') || (bubble.__tiChatHtmlByLang && bubble.__tiChatHtmlByLang.it) || '';\n            if (currentIt && currentIt !== sourceHtml) return;\n            bubble.innerHTML = String(outHtml || '');\n            bubble.__tiChatHtmlByLang = bubble.__tiChatHtmlByLang || {};\n            bubble.__tiChatHtmlByLang.en = bubble.innerHTML;\n            try { if (window.tiCleanEscapedUiInSubtree) window.tiCleanEscapedUiInSubtree(bubble); } catch(e) {}\n        }).catch(function(){});\n    };\n    window.tiTranslateChatBubblesForLanguage = function(targetLang, force) {\n        targetLang = String(targetLang || window.currLang || 'it').toLowerCase() === 'en' ? 'en' : 'it';\n        const bubbles = Array.from(document.querySelectorAll('.ti-bubble.ai')).slice(-80);\n        bubbles.forEach(function(bubble){\n            if (!bubble) return;\n            if (!bubble.__tiChatHtmlByLang) bubble.__tiChatHtmlByLang = {};\n            const rawSource = bubble.getAttribute('data-raw-source') || '';\n            const storedIt = bubble.getAttribute('data-ti-it-html') || bubble.__tiChatHtmlByLang.it || rawSource || bubble.innerHTML;\n            if (targetLang === 'it') {\n                \/\/ v30.9.285: ritorno a IT sempre immediato e locale, senza chiamate AI\/fetch.\n                bubble.innerHTML = storedIt;\n                bubble.__tiChatHtmlByLang.it = storedIt;\n                bubble.setAttribute('data-ti-it-html', storedIt);\n                try { if (window.tiCleanEscapedUiInSubtree) window.tiCleanEscapedUiInSubtree(bubble); } catch(e) {}\n                return;\n            }\n            if (!force && bubble.__tiChatHtmlByLang.en) {\n                bubble.innerHTML = bubble.__tiChatHtmlByLang.en;\n                try { if (window.tiCleanEscapedUiInSubtree) window.tiCleanEscapedUiInSubtree(bubble); } catch(e) {}\n                return;\n            }\n            if (rawSource && \/\\[TABLE\\]|\\[P\\]\/.test(rawSource) && window.formatTable) {\n                bubble.innerHTML = window.formatTable(rawSource);\n                try {\n                    if (window.tiCleanEscapedUiInSubtree) window.tiCleanEscapedUiInSubtree(bubble);\n                    if (window.tiI18nTranslateSubtree) window.tiI18nTranslateSubtree(bubble);\n                } catch(e) {}\n                bubble.__tiChatHtmlByLang.en = bubble.innerHTML;\n                return;\n            }\n            bubble.innerHTML = storedIt;\n            try {\n                if (window.tiCleanEscapedUiInSubtree) window.tiCleanEscapedUiInSubtree(bubble);\n                if (window.tiI18nTranslateSubtree) window.tiI18nTranslateSubtree(bubble);\n            } catch(e) {}\n            bubble.__tiChatHtmlByLang.en = bubble.innerHTML;\n            if (window.tiQueueRemoteEnglishChatTranslation) window.tiQueueRemoteEnglishChatTranslation(bubble, storedIt, (gE('ti-ditta') ? gE('ti-ditta').value : ''));\n        });\n    };\n    window.forceApplyCurrentLanguageEverywhere = function(reason) {\n        try {\n            if (!['it','en'].includes(String(window.currLang || '').toLowerCase())) window.currLang = 'it';\n            if (window.tiCleanEscapedUiInSubtree) window.tiCleanEscapedUiInSubtree(document.body || document.documentElement);\n            if (window.applyLayoutLanguage) window.applyLayoutLanguage(true);\n            if (window.tiTranslateChatBubblesForLanguage) window.tiTranslateChatBubblesForLanguage(window.currLang, true);\n            [60, 180, 420, 900].forEach(function(ms){\n                setTimeout(function(){\n                    try {\n                        if (window.applyLayoutLanguage) window.applyLayoutLanguage(true);\n                        if (window.tiTranslateChatBubblesForLanguage) window.tiTranslateChatBubblesForLanguage(window.currLang, true);\n                    } catch(e) {}\n                }, ms);\n            });\n        } catch(e) { try { console.warn('Shop & Service forced language apply error', e); } catch(ignore) {} }\n    };\n    window.setShopServiceLanguage = function(lang) {\n        const dittaSel = gE('ti-ditta');\n        const selectedDbBefore = dittaSel && dittaSel.value ? dittaSel.value : (window.tiCurrentSelectedDb || localStorage.getItem('ti_saved_db') || '');\n        const previousLang = String(window.currLang || 'it').toLowerCase() === 'en' ? 'en' : 'it';\n        if (window.tiRememberVisibleChatLanguage) window.tiRememberVisibleChatLanguage(previousLang);\n        window.currLang = String(lang || 'it').toLowerCase() === 'en' ? 'en' : 'it';\n        if (window.tiI18nClearCache) window.tiI18nClearCache();\n        localStorage.setItem('ti_lang_' + window.tiUser, window.currLang);\n        if (selectedDbBefore) window.tiCurrentSelectedDb = selectedDbBefore;\n        try { document.querySelectorAll('.ti-lang-opt').forEach(function(e){ e.classList.toggle('active', String(e.dataset.lang || '') === window.currLang); }); } catch(e) {}\n        try {\n            if (dittaSel && selectedDbBefore && Array.from(dittaSel.options || []).some(function(o){ return o.value === selectedDbBefore; })) {\n                dittaSel.value = selectedDbBefore;\n                if (selectedDbBefore !== 'NEW_DB') localStorage.setItem('ti_saved_db', selectedDbBefore);\n            }\n            if (window.applyDittaSelectVisual) window.applyDittaSelectVisual();\n            if (window.syncDittaTrigger) window.syncDittaTrigger();\n            if (window.applyCompanyLimitedNotice) window.applyCompanyLimitedNotice();\n        } catch(e) {}\n        try {\n            const d = dict[window.currLang] || dict['it'];\n            if(gE('ti-msg')) gE('ti-msg').placeholder = d.ph;\n            if(gE('ti-send')) gE('ti-send').innerText = d.send;\n            if(gE('ti-new')) gE('ti-new').innerText = d.new;\n            if(gE('ti-copy')) { gE('ti-copy').innerText = d.copy; gE('ti-copy').style.fontSize='11px'; }\n            if(gE('ti-file')) gE('ti-file').innerText = d.file;\n            if(gE('ti-products-btn')) gE('ti-products-btn').innerText = d.products || 'Prodotti';\n            if(gE('ti-services-btn')) gE('ti-services-btn').innerText = d.services || 'Servizi';\n            if(gE('ti-activities-btn')) gE('ti-activities-btn').innerText = d.activities || (window.currLang === 'en' ? 'Activities' : 'Attivit\u00e0');\n            if(gE('ti-kbd-btn')) gE('ti-kbd-btn').innerText = d.kbd;\n            if(gE('ti-mic')) gE('ti-mic').innerText = d.mic;\n            if(gE('ti-cam')) gE('ti-cam').innerText = d.cam;\n            if(gE('ti-login-btn') && gE('ti-login-btn').style.display !== 'none') gE('ti-login-btn').innerText = d.login;\n        } catch(e) {}\n        try { if (window.applyMuteState) window.applyMuteState(); } catch(e) {}\n        window.forceApplyCurrentLanguageEverywhere('language-switch');\n        if (window.currLang === 'en' && typeof window.fetchDitteList === 'function') {\n            try {\n                window.fetchDitteList({silent:true}).then(function(){\n                    try {\n                        const dsNow = gE('ti-ditta');\n                        if (dsNow && selectedDbBefore && Array.from(dsNow.options || []).some(function(o){ return o.value === selectedDbBefore; })) {\n                            dsNow.value = selectedDbBefore;\n                            window.tiCurrentSelectedDb = selectedDbBefore;\n                            if (window.applyDittaSelectVisual) window.applyDittaSelectVisual();\n                        }\n                        if (window.forceApplyCurrentLanguageEverywhere) window.forceApplyCurrentLanguageEverywhere('language-switch-ditta-refresh');\n                    } catch(e) {}\n                }).catch(function(){});\n            } catch(e) {}\n        }\n        setTimeout(function(){\n            try {\n                const ds = gE('ti-ditta');\n                if (ds && selectedDbBefore && Array.from(ds.options || []).some(function(o){ return o.value === selectedDbBefore; })) {\n                    ds.value = selectedDbBefore;\n                    window.tiCurrentSelectedDb = selectedDbBefore;\n                    if (selectedDbBefore !== 'NEW_DB') localStorage.setItem('ti_saved_db', selectedDbBefore);\n                    if (window.applyDittaSelectVisual) window.applyDittaSelectVisual();\n                }\n            } catch(e) {}\n        }, 250);\n    };\n\n    if (!window.__tiLangSwitchDelegatedBound) {\n        window.__tiLangSwitchDelegatedBound = true;\n        document.addEventListener('click', function(ev) {\n            const target = ev.target && ev.target.closest ? ev.target.closest('.ti-lang-opt') : null;\n            if (!target) return;\n            ev.preventDefault();\n            ev.stopPropagation();\n            window.setShopServiceLanguage(target.dataset.lang || 'it');\n        }, true);\n    }\n    document.querySelectorAll('.ti-lang-opt').forEach(function(el) {\n        el.onclick = function(ev) {\n            if (ev) { ev.preventDefault(); ev.stopPropagation(); }\n            window.setShopServiceLanguage(this.dataset.lang || 'it');\n        };\n    });\n\n    document.addEventListener(\"DOMContentLoaded\", function() {\n        const confAiBtn = gE('ti-conf-ai-send');\n        if (confAiBtn) confAiBtn.onclick = window.runConfigBot;\n        const dom = { chat: gE('ti-chat-box'), ditta: gE('ti-ditta'), msg: gE('ti-msg'), send: gE('ti-send'), btnNew: gE('ti-new') };\n\n        document.addEventListener('change', function(ev){\n            const t = ev.target;\n            if (t && (t.classList.contains('ti-import-map-select') || t.classList.contains('ti-import-group-target'))) window.scheduleImportPreviewRefresh(t);\n        });\n        document.addEventListener('input', function(ev){\n            const t = ev.target;\n            if (t && t.classList && t.classList.contains('ti-import-price-formula')) window.scheduleImportPreviewRefresh(t);\n        });\n        \n        if (gE('help-ver')) gE('help-ver').innerText = window.tiVersion + (window.tiVersionDateTime ? ' - ' + window.tiVersionDateTime : '');\n        window.tiAudioKey = 'ti_mute_state_' + location.host + '_' + window.tiUser; window.muteState = localStorage.getItem(window.tiAudioKey) ? parseInt(localStorage.getItem(window.tiAudioKey)) : 0;\n        window.applyMuteState();\n        \n        const muteBtn = gE('ti-mute-btn');\n        if(muteBtn) {\n            muteBtn.onclick = () => {\n                window.muteState = (window.muteState + 1) % 3;\n                if(window.muteState === 2) { localStorage.setItem(window.tiAudioKey, '2'); } \n                else { localStorage.removeItem(window.tiAudioKey); }\n                window.applyMuteState();\n                if(window.isMuted && window.speechSynthesis) window.speechSynthesis.cancel();\n            };\n        }\n        \n        window.ditteStateFilter = localStorage.getItem('ti_ditte_state_filter') || (localStorage.getItem('ti_show_suspended_ditte') === '1' ? 'suspended' : 'normal');\n        if (!['normal','suspended','demo'].includes(window.ditteStateFilter)) window.ditteStateFilter = 'normal';\n        window.showSuspendedDitte = (window.ditteStateFilter === 'suspended');\n        window.showDemoDitteOnly = (window.ditteStateFilter === 'demo');\n        window.renderSuspendedToggle = function() {\n            const b = gE('ti-show-sospese-btn'); if(!b) return;\n            if (window.ditteStateFilter === 'demo') {\n                b.textContent = 'Demo';\n                b.style.background = '#0369a1';\n                b.title = 'Mostra solo le ditte demo';\n            } else if (window.ditteStateFilter === 'suspended') {\n                b.textContent = 'Sosp ON';\n                b.style.background = '#b91c1c';\n                b.title = 'Clic successivo: mostra solo le ditte demo';\n            } else {\n                b.textContent = 'Sosp';\n                b.style.background = '#374151';\n                b.title = 'Mostra anche le ditte sospese. Clic successivo: solo Demo';\n            }\n            window.showSuspendedDitte = (window.ditteStateFilter === 'suspended');\n            window.showDemoDitteOnly = (window.ditteStateFilter === 'demo');\n        };\n        window.normalizeSearchText = function(str) {\n            return String(str || '')\n                .toLowerCase()\n                .normalize('NFD')\n                .replace(\/[\u0300-\u036f]\/g, '')\n                .replace(\/[^a-z0-9\\s]\/g, ' ')\n                .replace(\/\\s+\/g, ' ')\n                .trim();\n        };\n        window.filterDitteData = function(list, query) {\n            list = Array.isArray(list) ? list : [];\n            const q = window.normalizeSearchText(query || '');\n            if (!q) return list;\n            const tokens = q.split(\/\\s+\/).filter(Boolean);\n            const synonyms = {\n                farmacia: ['farmacia', 'parafarmacia', 'sanitario', 'farmaci', 'salute'],\n                ferramenta: ['ferramenta', 'bricolage', 'utensili', 'viti', 'fissaggi'],\n                fiori: ['fiori', 'fioraio', 'bouquet', 'piante', 'floreale'],\n                auto: ['auto', 'autofficina', 'carrozzeria', 'gomme', 'ricambi'],\n                capelli: ['capelli', 'hairstyle', 'parrucchiere', 'barbiere', 'acconciature'],\n                estetica: ['estetica', 'beauty', 'benessere', 'trattamenti', 'viso'],\n                ristorante: ['ristorante', 'pizzeria', 'cucina', 'menu', 'piatti'],\n                medico: ['medico', 'sanitario', 'visite', 'esami', 'specialistica']\n            };\n            const expandToken = function(tok) {\n                let out = [tok];\n                Object.keys(synonyms).forEach(key => {\n                    const vals = synonyms[key];\n                    if (key === tok || vals.includes(tok)) out = out.concat(vals);\n                });\n                return Array.from(new Set(out));\n            };\n            const scored = list.map(d => {\n                const nome = window.normalizeSearchText(d.nome_pura || d.label || '');\n                const settore = window.normalizeSearchText(d.settore || '');\n                const accoglienza = window.normalizeSearchText(d.accoglienza || '');\n                const db = window.normalizeSearchText(d.db || '');\n                const hay = [nome, settore, accoglienza, db].join(' | ');\n                let score = 0;\n                let matchedAll = true;\n                tokens.forEach(tok => {\n                    const expanded = expandToken(tok);\n                    let tokenMatched = false;\n                    expanded.forEach(v => {\n                        if (!v) return;\n                        if (nome === v) { score += 120; tokenMatched = true; }\n                        else if (nome.startsWith(v)) { score += 90; tokenMatched = true; }\n                        else if (nome.includes(v)) { score += 60; tokenMatched = true; }\n                        if (settore === v) { score += 110; tokenMatched = true; }\n                        else if (settore.startsWith(v)) { score += 85; tokenMatched = true; }\n                        else if (settore.includes(v)) { score += 55; tokenMatched = true; }\n                        if (db === v) { score += 80; tokenMatched = true; }\n                        else if (db.includes(v)) { score += 45; tokenMatched = true; }\n                        if (accoglienza.includes(v)) { score += 20; tokenMatched = true; }\n                        if (!tokenMatched && v.length >= 3) {\n                            const prefix = v.slice(0, 4);\n                            if (nome.includes(prefix) || settore.includes(prefix) || db.includes(prefix)) {\n                                score += 18; tokenMatched = true;\n                            }\n                        }\n                    });\n                    if (!tokenMatched) matchedAll = false;\n                });\n                if (nome.includes(q)) score += 100;\n                if (settore.includes(q)) score += 90;\n                if (db.includes(q)) score += 70;\n                return { item: d, score, matchedAll };\n            }).filter(x => x.score > 0 || x.matchedAll);\n            scored.sort((a,b) => {\n                if ((b.score||0) !== (a.score||0)) return (b.score||0) - (a.score||0);\n                return String(a.item.nome_pura || '').localeCompare(String(b.item.nome_pura || ''));\n            });\n            return scored.map(x => x.item);\n        };\n\n        window.closeDittaPopup = function() {\n            const pop = gE('ti-ditta-popup');\n            if (pop) pop.style.display = 'none';\n        };\n\n        window.syncDittaTrigger = function() {\n            const sel = gE('ti-ditta');\n            const btn = gE('ti-ditta-trigger');\n            if (!sel || !btn) return;\n            const opt = sel.options && sel.selectedIndex >= 0 ? sel.options[sel.selectedIndex] : null;\n            let label = '-- Scegli una ditta --';\n            let color = '#ffffff';\n            let borderColor = '#444';\n            if (opt) {\n                label = String(opt.text || label);\n                const status = String(opt.getAttribute('data-status') || '').toLowerCase();\n                if (status === 'sospesa' || status === 'sviluppo') { color = '#ef4444'; borderColor = '#7f1d1d'; }\n                else if (status === 'demo') { color = '#38bdf8'; borderColor = '#0e7490'; }\n                else if (status === 'configurazione') { color = '#facc15'; borderColor = '#a16207'; }\n                else if (opt.value === 'NEW_DB') { color = '#10b981'; borderColor = '#065f46'; }\n            }\n            btn.textContent = label;\n            btn.style.color = color;\n            btn.style.borderColor = borderColor;\n            btn.style.background = '#000000';\n        };\n\n        window.renderDittePopup = function() {\n            const sel = gE('ti-ditta');\n            const pop = gE('ti-ditta-popup');\n            if (!sel || !pop) return;\n            const options = Array.from(sel.options || []);\n            pop.innerHTML = '';\n            options.forEach(function(opt){\n                const status = String(opt.getAttribute('data-status') || '').toLowerCase() || 'attivo';\n                const effectiveStatus = (opt.value === 'NEW_DB') ? 'nuova' : status;\n                const fullText = String(opt.text || '').trim();\n                let badgeText = '';\n                let baseText = fullText;\n\n                if (\/ - Sosp$\/i.test(fullText)) { baseText = fullText.replace(\/ - Sosp$\/i, ''); badgeText = 'Sosp'; }\n                else if (\/ - Conf$\/i.test(fullText)) { baseText = fullText.replace(\/ - Conf$\/i, ''); badgeText = 'Conf'; }\n                else if (\/ - Demo$\/i.test(fullText)) { baseText = fullText.replace(\/ - Demo$\/i, ''); badgeText = 'Demo'; }\n                else if (\/ - Svil$\/i.test(fullText)) { baseText = fullText.replace(\/ - Svil$\/i, ''); badgeText = 'Svil'; }\n                else if (opt.value === 'NEW_DB') { badgeText = 'Nuova'; }\n\n                const row = document.createElement('div');\n                row.className = 'ti-ditta-row ' + effectiveStatus + (sel.value === opt.value ? ' active' : '');\n                row.setAttribute('role', 'button');\n                row.tabIndex = opt.disabled ? -1 : 0;\n                if (!opt.value) row.classList.add('empty');\n                if (opt.disabled) row.classList.add('none');\n\n                const main = document.createElement('span');\n                main.className = 'ti-ditta-row-main';\n                main.textContent = baseText || fullText || '--';\n                if (row.classList.contains('empty') || row.classList.contains('none')) {\n                    main.style.setProperty('color', '#cbd5e1', 'important');\n                } else {\n                    main.style.setProperty('color', '#ffffff', 'important');\n                }\n                row.appendChild(main);\n\n                if (badgeText) {\n                    const badge = document.createElement('span');\n                    badge.className = 'ti-ditta-row-badge ' + effectiveStatus;\n                    badge.textContent = badgeText;\n                    row.appendChild(badge);\n                }\n\n                const pickRow = function(ev){\n                    if (ev) ev.preventDefault();\n                    if (opt.disabled) return;\n                    sel.value = opt.value;\n                    if (window.applyDittaSelectVisual) window.applyDittaSelectVisual();\n                    window.closeDittaPopup();\n                    sel.dispatchEvent(new Event('change', {bubbles:true}));\n                };\n                row.onclick = pickRow;\n                row.onkeydown = function(ev){\n                    if (ev.key === 'Enter' || ev.key === ' ') pickRow(ev);\n                };\n                pop.appendChild(row);\n            });\n        };\n\n        window.toggleDittaPopup = function() {\n            const pop = gE('ti-ditta-popup');\n            if (!pop) return;\n            const willOpen = pop.style.display === 'none' || !pop.style.display;\n            if (willOpen) {\n                if (window.renderDittePopup) window.renderDittePopup();\n            if (window.syncLoginDbSelect) window.syncLoginDbSelect();\n                pop.style.display = 'block';\n            } else {\n                pop.style.display = 'none';\n            }\n        };\n\nwindow.forceWhiteEditStyle = function(el) {\n            if (!el) return;\n            try {\n                const inConf = !!(el.closest && (el.closest('.ti-conf-window') || el.closest('.ti-conf-body') || el.closest('.ti-cfg-table') || el.closest('.ti-import-map-table') || el.closest('#ti-profile-ov') || el.closest('#ti-newdb-ov') || el.closest('#ti-new-db-ov')));\n                if (!inConf) return;\n                el.style.setProperty('color', '#ffffff', 'important');\n                el.style.setProperty('-webkit-text-fill-color', '#ffffff', 'important');\n                el.style.setProperty('caret-color', '#ffffff', 'important');\n                el.style.setProperty('background', '#000000', 'important');\n                el.style.setProperty('background-color', '#000000', 'important');\n                el.style.setProperty('background-image', 'none', 'important');\n                el.style.setProperty('text-shadow', 'none', 'important');\n                el.style.setProperty('color-scheme', 'dark', 'important');\n                if (el.tagName === 'SELECT' && el.options) {\n                    Array.from(el.options).forEach(function(opt){\n                        opt.style.setProperty('color', '#ffffff', 'important');\n                        opt.style.setProperty('-webkit-text-fill-color', '#ffffff', 'important');\n                        opt.style.setProperty('background', '#000000', 'important');\n                        opt.style.setProperty('background-color', '#000000', 'important');\n                    });\n                }\n            } catch(e) {}\n        };\n\n        window.bindConfigWhiteEditHandlers = function() {\n            if (window.__tiConfigWhiteEditBound) return;\n            window.__tiConfigWhiteEditBound = true;\n\n            document.addEventListener('focusin', function(ev) {\n                const el = ev.target;\n                if (!el) return;\n                window.forceWhiteEditStyle(el);\n            }, true);\n\n            document.addEventListener('input', function(ev) {\n                const el = ev.target;\n                if (!el) return;\n                window.forceWhiteEditStyle(el);\n            }, true);\n\n            document.addEventListener('change', function(ev) {\n                const el = ev.target;\n                if (!el) return;\n                window.forceWhiteEditStyle(el);\n            }, true);\n\n            document.addEventListener('click', function(ev) {\n                const el = ev.target;\n                if (!el) return;\n                if (el.matches && (el.matches('.ti-conf-window input, .ti-conf-window textarea, .ti-conf-window select, .ti-cfg-table input, .ti-cfg-table textarea, .ti-cfg-table select, .ti-import-map-table input, .ti-import-map-table textarea, .ti-import-map-table select, #ti-profile-ov input, #ti-profile-ov textarea, #ti-profile-ov select, #ti-newdb-ov input, #ti-newdb-ov textarea, #ti-newdb-ov select, #ti-new-db-ov input, #ti-new-db-ov textarea, #ti-new-db-ov select, .ti-conf-window [contenteditable=\"true\"], .ti-cfg-table [contenteditable=\"true\"], .ti-import-map-table [contenteditable=\"true\"]'))) {\n                    window.forceWhiteEditStyle(el);\n                }\n            }, true);\n        };\n\n        window.bindConfigWhiteEditHandlers();\n\nwindow.applyDittaSelectVisual = function() {\n            const sel = gE('ti-ditta');\n            if (!sel) return;\n            const opt = sel.options && sel.selectedIndex >= 0 ? sel.options[sel.selectedIndex] : null;\n            let color = '#ffffff', weight = '400';\n            if (opt) {\n                const status = String(opt.getAttribute('data-status') || '').toLowerCase();\n                if (status === 'sospesa' || status === 'sviluppo') { color = '#ef4444'; weight = '700'; }\n                else if (status === 'demo') { color = '#38bdf8'; weight = '700'; }\n                else if (status === 'configurazione') { color = '#facc15'; weight = '700'; }\n                else if (opt.value === 'NEW_DB') { color = '#10b981'; weight = '700'; }\n            }\n            sel.style.color = color;\n            sel.style.fontWeight = weight;\n            sel.style.background = '#000000';\n            if (window.syncDittaTrigger) window.syncDittaTrigger();\n        };\n\n        window.renderDitteDropdown = function() {\n            const dittaInput = gE('ti-ditta'); if(!dittaInput) return;\n            const prevSelectedDb = dittaInput.value || window.tiCurrentSelectedDb || localStorage.getItem('ti_saved_db') || '';\n            if (prevSelectedDb) window.tiCurrentSelectedDb = prevSelectedDb;\n            const filterInp = gE('ti-filter-ditte');\n            const q = filterInp ? filterInp.value : '';\n            if(!window.ditteData || !Array.isArray(window.ditteData)) window.ditteData = [];\n            let sorted = window.filterDitteData([...window.ditteData], q);\n            if(window.sortMode === 'AZ') { sorted.sort((a,b) => (a.nome_pura||'').localeCompare(b.nome_pura||'')); } else { sorted.sort((a,b) => (a.settore || '').localeCompare(b.settore || '')); }\n\n            dittaInput.innerHTML = '';\n\n            const optEmpty = document.createElement('option');\n            optEmpty.value = '';\n            optEmpty.textContent = '-- Scegli una ditta --';\n            optEmpty.setAttribute('data-status', 'attivo');\n            optEmpty.style.color = '#ffffff';\n            optEmpty.style.background = '#000000';\n            dittaInput.appendChild(optEmpty);\n\n            const optNew = document.createElement('option');\n            optNew.value = 'NEW_DB';\n            optNew.textContent = '+ Nuova ditta ...';\n            optNew.setAttribute('data-status', 'nuova');\n            optNew.style.color = '#10b981';\n            optNew.style.fontWeight = '700';\n            optNew.style.background = '#000000';\n            dittaInput.appendChild(optNew);\n\n            sorted.forEach(function(d){\n                const opt = document.createElement('option');\n                const safeName = String(d.nome_pura || '');\n                const safeSettore = String(d.settore || '');\n                let statusCode = 'attivo';\n                let suffix = '';\n                let color = '#ffffff';\n                if (d.is_dev) {\n                    statusCode = 'sviluppo';\n                    suffix = ' - Svil';\n                    color = '#ef4444';\n                } else if (d.is_sosp) {\n                    statusCode = 'sospesa';\n                    suffix = ' - Sosp';\n                    color = '#ef4444';\n                } else if (d.is_demo) {\n                    statusCode = 'demo';\n                    suffix = ' - Demo';\n                    color = '#38bdf8';\n                } else if (d.is_conf) {\n                    statusCode = 'configurazione';\n                    suffix = ' - Conf';\n                    color = '#facc15';\n                }\n                opt.value = d.db;\n                opt.textContent = safeName + (safeSettore ? ' - ' + safeSettore : '') + suffix;\n                opt.setAttribute('data-status', statusCode);\n                opt.setAttribute('data-stato', String(d.stato || d.company_state || statusCode || ''));\n                opt.setAttribute('data-company-state', String(d.company_state || d.stato || statusCode || ''));\n                opt.setAttribute('data-acc', String(d.accoglienza || ''));\n                opt.setAttribute('data-acc-en', String(d.accoglienza_en || ''));\n                opt.setAttribute('data-logo', String(d.logo || ''));\n                opt.setAttribute('data-qr', String(d.qr || ''));\n                opt.setAttribute('data-name', safeName);\n                opt.setAttribute('data-tab-tab', String(d.tab_tab || ''));\n                opt.setAttribute('data-tab-sma', String(d.tab_sma || ''));\n                opt.setAttribute('data-tab-med', String(d.tab_med || ''));\n                opt.setAttribute('data-tab-lar', String(d.tab_lar || ''));\n                opt.style.color = color;\n                opt.style.background = '#000000';\n                if (statusCode !== 'attivo') opt.style.fontWeight = '700';\n                dittaInput.appendChild(opt);\n            });\n\n            if (!sorted.length && q) {\n                const optNone = document.createElement('option');\n                optNone.value = '';\n                optNone.disabled = true;\n                optNone.textContent = 'Nessuna ditta trovata';\n                optNone.style.color = '#9ca3af';\n                optNone.style.background = '#000000';\n                dittaInput.appendChild(optNone);\n            }\n\n            if (prevSelectedDb && !Array.from(dittaInput.options || []).some(function(o){ return o.value === prevSelectedDb; })) {\n                const savedRecord = (window.ditteData || []).find(function(d){ return String(d && d.db || '') === String(prevSelectedDb); });\n                if (savedRecord) {\n                    const opt = document.createElement('option');\n                    const safeName = String(savedRecord.nome_pura || '');\n                    const safeSettore = String(savedRecord.settore || '');\n                    let statusCode = 'attivo', suffix = '', color = '#ffffff';\n                    if (savedRecord.is_dev) { statusCode = 'sviluppo'; suffix = ' - Svil'; color = '#ef4444'; }\n                    else if (savedRecord.is_sosp) { statusCode = 'sospesa'; suffix = ' - Sosp'; color = '#ef4444'; }\n                    else if (savedRecord.is_demo) { statusCode = 'demo'; suffix = ' - Demo'; color = '#38bdf8'; }\n                    else if (savedRecord.is_conf) { statusCode = 'configurazione'; suffix = ' - Conf'; color = '#facc15'; }\n                    opt.value = savedRecord.db;\n                    opt.textContent = safeName + (safeSettore ? ' - ' + safeSettore : '') + suffix;\n                    opt.setAttribute('data-status', statusCode);\n                    opt.setAttribute('data-stato', String(savedRecord.stato || savedRecord.company_state || statusCode || ''));\n                    opt.setAttribute('data-company-state', String(savedRecord.company_state || savedRecord.stato || statusCode || ''));\n                    opt.setAttribute('data-acc', String(savedRecord.accoglienza || ''));\n                    opt.setAttribute('data-acc-en', String(savedRecord.accoglienza_en || ''));\n                    opt.setAttribute('data-logo', String(savedRecord.logo || ''));\n                    opt.setAttribute('data-qr', String(savedRecord.qr || ''));\n                    opt.setAttribute('data-name', safeName);\n                    opt.setAttribute('data-tab-tab', String(savedRecord.tab_tab || ''));\n                    opt.setAttribute('data-tab-sma', String(savedRecord.tab_sma || ''));\n                    opt.setAttribute('data-tab-med', String(savedRecord.tab_med || ''));\n                    opt.setAttribute('data-tab-lar', String(savedRecord.tab_lar || ''));\n                    opt.style.color = color;\n                    opt.style.background = '#000000';\n                    if (statusCode !== 'attivo') opt.style.fontWeight = '700';\n                    dittaInput.appendChild(opt);\n                }\n            }\n            if (prevSelectedDb) {\n                const hasPrev = Array.from(dittaInput.options || []).some(function(o){ return o.value === prevSelectedDb; });\n                if (hasPrev) dittaInput.value = prevSelectedDb;\n            }\n            if (dittaInput.value) {\n                window.tiCurrentSelectedDb = dittaInput.value;\n                if (dittaInput.value !== 'NEW_DB') localStorage.setItem('ti_saved_db', dittaInput.value);\n            }\n            if (window.applyDittaSelectVisual) window.applyDittaSelectVisual();\n            if (window.renderDittePopup) window.renderDittePopup();\n            if(window.tiForced) {\n                dittaInput.style.display = 'none';\n                const picker = document.querySelector('.ti-ditta-picker'); if (picker) picker.style.display = 'none';\n                const sBtn = gE('ti-sort-btn'); if(sBtn) sBtn.style.display = 'none';\n                const fInp = gE('ti-filter-ditte'); if(fInp) fInp.style.display = 'none';\n            }\n        };\n\n        window.fetchDitteList = function(opts){\n            opts = opts || {};\n            const silent = !!opts.silent;\n            const prevVal = dom.ditta ? dom.ditta.value : '';\n            const fdList423 = new FormData();\n            fdList423.append('ti_action', 'ti_ai_list_ditte');\n            fdList423.append('action', 'ti_ai_list_ditte');\n            fdList423.append('current_db', window.tiForced || '');\n            fdList423.append('show_suspended', window.showSuspendedDitte ? '1' : '0');\n            fdList423.append('show_demo_only', window.showDemoDitteOnly ? '1' : '0');\n            fdList423.append('ti_lang', (String(window.currLang || 'it').toLowerCase().indexOf('en') === 0) ? 'en' : 'it');\n            const url = window.tiUrl + '?ti_action=ti_ai_list_ditte&action=ti_ai_list_ditte&current_db=' + encodeURIComponent(window.tiForced || '') + '&show_suspended=' + (window.showSuspendedDitte ? '1' : '0') + '&show_demo_only=' + (window.showDemoDitteOnly ? '1' : '0') + '&ti_lang=' + encodeURIComponent((String(window.currLang || 'it').toLowerCase().indexOf('en') === 0) ? 'en' : 'it') + '&_ti=' + Date.now();\n            return window.fetchJsonSafe((window.tiUrl || url), {method:'POST', body:fdList423, tiNoLongProcessSignal:true}).catch(function(firstErr){\n                return window.fetchJsonSafe(url, {method:'GET', tiNoLongProcessSignal:true}).catch(function(secondErr){ throw firstErr || secondErr; });\n            }).then(function(res){\n                if(res.success && dom.ditta) {\n                    window.ditteData = Array.isArray(res.data && res.data.ditte) ? res.data.ditte : [];\n                    window.renderDitteDropdown();\n                    if(window.tiForced) {\n                        dom.ditta.value = window.tiForced;\n                        const fdReset = new FormData(); fdReset.append('ti_action', 'ti_ai_reset_flow'); fetch(window.tiUrl, {method:'POST', body:fdReset}).catch(function(){});\n                    } else {\n                        let targetDb = prevVal || localStorage.getItem('ti_saved_db');\n                        if (targetDb && Array.from(dom.ditta.options).find(o => o.value === targetDb)) { dom.ditta.value = targetDb; window.tiCurrentSelectedDb = targetDb; }\n                    }\n                    if (window.applyDittaSelectVisual) window.applyDittaSelectVisual();\n                    window.updCtx(silent);\n                }\n                return res;\n            }).catch(function(err){\n                console.error('Errore fetchDitteList:', err);\n                window.ditteData = [];\n                if (dom.ditta) dom.ditta.innerHTML = '<option value=\"\">Selezione ditte non disponibile<\/option>';\n                var msg423 = ((err && err.message) ? err.message : 'errore sconosciuto');\n                msg423 = String(msg423).replace(\/Unexpected token ['\"]?<['\"]?.*\/i, 'Risposta server HTML invece di JSON').slice(0, 360);\n                if (!silent && window.tiAlert) window.tiAlert('Errore caricamento ditte: ' + msg423 + '. Ricarica la pagina; se persiste verifica il log PHP\/server.');\n                return {success:false, data:{message:msg423, code:'ditte_load_failed_423'}};\n            });\n        };\n\n        window.fetchDitteList({silent:false});\n\n        const dittaTrigger = gE('ti-ditta-trigger');\n        if (dittaTrigger) {\n            dittaTrigger.onclick = function(ev){\n                ev.preventDefault();\n                ev.stopPropagation();\n                window.toggleDittaPopup();\n            };\n        }\n        document.addEventListener('click', function(ev){\n            const picker = document.querySelector('.ti-ditta-picker');\n            if (!picker) return;\n            if (!picker.contains(ev.target)) window.closeDittaPopup();\n        });\n\n        const sospBtn = gE('ti-show-sospese-btn');\n        if(sospBtn) {\n            window.renderSuspendedToggle();\n            sospBtn.onclick = () => {\n                if (window.ditteStateFilter === 'normal') window.ditteStateFilter = 'suspended';\n                else if (window.ditteStateFilter === 'suspended') window.ditteStateFilter = 'demo';\n                else window.ditteStateFilter = 'normal';\n                localStorage.setItem('ti_ditte_state_filter', window.ditteStateFilter);\n                localStorage.setItem('ti_show_suspended_ditte', window.ditteStateFilter === 'suspended' ? '1' : '0');\n                window.renderSuspendedToggle();\n                window.fetchDitteList({silent:true});\n            };\n        }\n\n        const sortBtn = gE('ti-sort-btn');\n        if(sortBtn) { sortBtn.onclick = () => { window.sortMode = (window.sortMode === 'AZ') ? 'ATT' : 'AZ'; sortBtn.textContent = window.sortMode; window.renderDitteDropdown(); }; }\n\n        const filterInp = gE('ti-filter-ditte');\n        if(filterInp) {\n            filterInp.oninput = () => {\n                const prevVal = dom.ditta && dom.ditta.value ? dom.ditta.value : '';\n                window.renderDitteDropdown();\n                if (dom.ditta && prevVal && Array.from(dom.ditta.options).some(o => o.value === prevVal)) dom.ditta.value = prevVal;\n                if (window.applyDittaSelectVisual) window.applyDittaSelectVisual();\n                if (window.applyDittaSelectVisual) window.applyDittaSelectVisual();\n            };\n            filterInp.onkeydown = (e) => {\n                if (e.key === 'Enter') {\n                    e.preventDefault();\n                    const realOptions = Array.from(dom.ditta.options).filter(o => o.value && o.value !== 'NEW_DB' && !o.disabled);\n                    if (realOptions.length === 1) {\n                        dom.ditta.value = realOptions[0].value;\n                        dom.ditta.dispatchEvent(new Event('change'));\n                    }\n                }\n            };\n        }\n\n        if (dom.ditta) {\n            dom.ditta.onchange = () => {\n                 let v = dom.ditta.value;\n                 if (v === 'NEW_DB') { \n                     window.openModal('ti-new-db-ov'); dom.ditta.value = window.tiForced || localStorage.getItem('ti_saved_db') || ''; return; \n                 }\n\n                 if(v !== '') { localStorage.setItem('ti_saved_db', v); window.tiCurrentSelectedDb = v; } else { localStorage.removeItem('ti_saved_db'); window.tiCurrentSelectedDb = ''; }\n                 if (window.applyDittaSelectVisual) window.applyDittaSelectVisual();\n                 const mustLogoutForCompanySwitch = !!(window.tiUser && window.tiUser !== 'SSGlobalAdmin' && window.tiSessionDb && v && v !== window.tiSessionDb);\n                 if (mustLogoutForCompanySwitch) {\n                     if(dom.chat) dom.chat.innerHTML = '';\n                     const fdReset = new FormData(); fdReset.append('ti_action', 'ti_ai_reset_flow');\n                     fetch(window.tiUrl, {method:'POST', body:fdReset}).catch(()=>{});\n                     const fdLogout = new FormData(); fdLogout.append('ti_action','ti_ai_logout');\n                     fetch(window.tiUrl,{method:'POST',body:fdLogout}).finally(()=>{ location.reload(); });\n                     return;\n                 }\n                 if(dom.chat) dom.chat.innerHTML = '';\n                 window.hideOrderConfirmPanel();\n                 const fd = new FormData(); fd.append('ti_action', 'ti_ai_reset_flow');\n                 fetch(window.tiUrl, {method:'POST', body:fd}).finally(function(){ window.updCtx(false); });\n            };\n        }\n\n        window.loadRegisteredUserProfileWelcome = function(dbValue) {\n            try {\n                if (!window.tiUser || window.tiUser === 'SSGlobalAdmin') return;\n                if (!dbValue || dbValue === 'NEW_DB') return;\n                window.tiProfileWelcomeShown = window.tiProfileWelcomeShown || {};\n                const key = String(dbValue) + '|' + String(window.tiUser || '');\n                if (window.tiProfileWelcomeShown[key]) return;\n                window.tiProfileWelcomeShown[key] = true;\n                const fdProfile = new FormData();\n                fdProfile.append('ti_action', 'ti_ai_chat_start');\n                fdProfile.append('db', dbValue);\n                fdProfile.append('text', '__TI_PROFILE_WELCOME__');\n                fetch(window.tiUrl, {method:'POST', body:fdProfile})\n                    .then(function(r){ return r.json(); })\n                    .then(function(d){\n                        const reply = d && d.success && d.data ? String(d.data.reply || '') : '';\n                        if (!reply) return;\n                        if (window.tiAddAiMessageTranslatedIfNeeded) window.tiAddAiMessageTranslatedIfNeeded(reply, String(d.data.voice || reply), dbValue);\n                        else { if (window.addMsg) window.addMsg('ai', reply, reply); if (d.data.voice && window.speakText) window.speakText(String(d.data.voice || '')); }\n                    })\n                    .catch(function(err){ console.warn('Profilazione utente non disponibile:', err); });\n            } catch(e) { console.warn('Profilazione utente non disponibile:', e); }\n        };\n\n        window.tiGetCompanyWelcomeTextForLanguage = function(optionEl, lang) {\n            if (!optionEl) return '';\n            lang = String(lang || window.currLang || 'it').toLowerCase() === 'en' ? 'en' : 'it';\n            const raw = String(optionEl.dataset && optionEl.dataset.acc ? optionEl.dataset.acc : '');\n            if (lang !== 'en') return raw;\n            const en = String(optionEl.dataset && optionEl.dataset.accEn ? optionEl.dataset.accEn : '');\n            if (en) return en;\n            return (window.tiTranslateLayoutStringToEn ? window.tiTranslateLayoutStringToEn(raw) : raw);\n        };\n        window.tiQueueCompanyWelcomeAccTranslation = function(optionEl, bubble, sourceHtml, dbValue) {\n            try {\n                if (!optionEl || !bubble || String(window.currLang || 'it').toLowerCase() !== 'en') return;\n                const rawAcc = String(optionEl.dataset && optionEl.dataset.acc ? optionEl.dataset.acc : '');\n                if (!rawAcc.trim()) return;\n                const accKey = window.tiHashString ? window.tiHashString(rawAcc) : String(rawAcc.length);\n                if (optionEl.dataset.accEn && optionEl.dataset.accEnKey === accKey) return;\n                if (!window.tiRemoteTranslateHtmlToLanguage) return;\n                window.tiRemoteTranslateHtmlToLanguage(rawAcc, 'en', dbValue || '', rawAcc).then(function(accEn){\n                    accEn = String(accEn || '').replace(\/<[^>]+>\/g, ' ').trim();\n                    if (!accEn || String(window.currLang || 'it').toLowerCase() !== 'en') return;\n                    optionEl.dataset.accEn = accEn;\n                    optionEl.dataset.accEnKey = accKey;\n                    if (bubble && sourceHtml && bubble.getAttribute('data-ti-it-html') === sourceHtml) {\n                        const translatedHtml = sourceHtml.replace(rawAcc, accEn);\n                        bubble.innerHTML = translatedHtml;\n                        if (window.tiI18nTranslateSubtree) window.tiI18nTranslateSubtree(bubble);\n                        bubble.__tiChatHtmlByLang = bubble.__tiChatHtmlByLang || {};\n                        bubble.__tiChatHtmlByLang.en = bubble.innerHTML;\n                    }\n                }).catch(function(){});\n            } catch(e) {}\n        };\n\n        window.updCtx = function(silent) {\n             silent = !!silent;\n             const d = gE('ti-ditta');\n             if(!d || !d.value || d.value === 'NEW_DB') {\n                 gE('ti-company-name').textContent = \"Shop & Service\"; const companySub = gE('ti-company-sub'); if (companySub) { companySub.innerHTML = ''; companySub.style.display = 'none'; }\n                 if(!silent) {\n                     let welcomeName = window.tiUserDisplay ? \" Ciao <b>\" + window.tiUserDisplay + \"<\/b>!\" : \"\";\n                     let cfgWelcome = window.getConfiguratorWelcomeHtml ? window.getConfiguratorWelcomeHtml() : '';\n                     let htmlMsg = cfgWelcome || `Sono l'assistente virtuale di <b>Shop & Service<\/b>.${welcomeName}<br><br>Scegli una ditta dal menu in alto, oppure dimmi cosa stai cercando e ti consiglier\u00f2 l'attivit\u00e0 giusta per te!`;\n                     const welcomeVoice = `Sono l'assistente virtuale di Shop & Service. Ciao ${window.tiUserDisplay || ''}! Scegli una ditta dal menu.`;\n                     if (window.tiAddAiMessageTranslatedIfNeeded) window.tiAddAiMessageTranslatedIfNeeded(htmlMsg, welcomeVoice, '');\n                     else { if(window.addMsg) window.addMsg('ai', htmlMsg, htmlMsg); window.speakText(welcomeVoice); }\n                     if (cfgWelcome && window.showConfiguratorOperationalTipsPopup) setTimeout(function(){ window.showConfiguratorOperationalTipsPopup(false); }, 350);\n                 }\n                 return;\n             }\n             const s = d.options[d.selectedIndex];\n             if(s) {\n                 window.currentDittaName = s.dataset.name; gE('ti-company-name').textContent = s.dataset.name; if (window.applyCompanyLimitedNotice) window.applyCompanyLimitedNotice(); \n                 const l = gE('ti-logo-img'); \n\n                 let directLink = window.location.href.split('?')[0] + \"?ti_ai_db=\" + s.value;\n                 if(l && s.dataset.logo && s.dataset.logo !== \"undefined\") { \n                     let finalLogo = window.getFullImgUrl(s.dataset.logo, s.value, 'Config');\n                     l.src = finalLogo; \n                     l.style.display = 'block'; \n                     l.title = 'Copia link diretto della ditta';\n                     l.onclick = () => { navigator.clipboard.writeText(directLink).then(() => window.tiAlert(\"Link copiato:\\n\" + directLink)); };\n                 } else if(l) { l.style.display = 'none'; l.onclick = null; }\n\n                 const q = gE('ti-qr-img'); if(q && s.value && s.value !== \"NEW_DB\") { q.src=\"https:\/\/api.qrserver.com\/v1\/create-qr-code\/?size=100x100&data=\"+encodeURIComponent(directLink); q.style.display='block'; q.onclick = () => window.open(directLink, '_blank'); } else if (q) { q.style.display='none'; }\n\n                 const uAva = gE('ti-user-avatar');\n                 if(window.tiUser && window.tiUserFoto) { const fotoDb = window.tiUserFotoDb || s.value; const avaUrl = window.getSafePreviewUrl(window.tiUserFoto, fotoDb, 'Utenti', 'Immagine', window.tiUser || 'utente'); if(uAva && avaUrl) { uAva.src = avaUrl; uAva.style.display = 'block'; } } else if(uAva) { uAva.style.display = 'none'; }\n\n                 if(!silent) {\n                     let accTextForLang = window.tiGetCompanyWelcomeTextForLanguage ? window.tiGetCompanyWelcomeTextForLanguage(s, window.currLang) : (s.dataset.acc || '');\n                     let acc = accTextForLang && accTextForLang !== 'undefined' && accTextForLang !== '' ? `<br><br><b>${accTextForLang}<\/b>` : '';\n                     let welcomeName = window.tiUserDisplay ? \" Ciao <b>\" + window.tiUserDisplay + \"<\/b>!\" : \"\";\n                     let plainAcc = accTextForLang && accTextForLang !== 'undefined' ? accTextForLang : '';\n                     let plainWelcomeName = window.tiUserDisplay ? \" Ciao \" + window.tiUserDisplay + \"!\" : \"\";\n\n                     let cfgWelcome = window.getConfiguratorWelcomeHtml ? window.getConfiguratorWelcomeHtml() : '';\n                     const stateNoticeHtml = window.getCompanyLimitedNoticeHtml ? window.getCompanyLimitedNoticeHtml() : '';\n                     let htmlMsg = cfgWelcome || `Sono l'assistente virtuale di <b>${s.dataset.name}<\/b>.${welcomeName}${acc}<br><br>${stateNoticeHtml}Come posso aiutarti oggi?`;\n                     let voiceMsg = cfgWelcome ? `Ciao ${window.tiUserDisplay || 'configuratore'}, puoi configurare la ditta oppure usare il servizio Shop & Service come utente registrato standard.` : `Sono l'assistente virtuale di ${s.dataset.name}. ${plainWelcomeName} ${plainAcc} Come posso aiutarti oggi?`;\n\n                     let welcomeBubble = null;\n                     if (window.tiAddAiMessageTranslatedIfNeeded) welcomeBubble = window.tiAddAiMessageTranslatedIfNeeded(htmlMsg, voiceMsg, s.value);\n                     else { if(window.addMsg) welcomeBubble = window.addMsg('ai', htmlMsg, htmlMsg); window.speakText(voiceMsg); }\n                     if (window.tiQueueCompanyWelcomeAccTranslation) window.tiQueueCompanyWelcomeAccTranslation(s, welcomeBubble, htmlMsg, s.value);\n                     if (cfgWelcome && window.showConfiguratorOperationalTipsPopup) setTimeout(function(){ window.showConfiguratorOperationalTipsPopup(false); }, 350);\n                     if (window.loadRegisteredUserProfileWelcome) window.loadRegisteredUserProfileWelcome(s.value);\n                     window.resetIdleTimers(); \n                 }\n                 try { if (window.requestMaintenanceStatus) window.requestMaintenanceStatus({db:s.value, silent:true}); } catch(e) {}\n             }\n        };\n\nwindow.idleTimer1 = null; window.idleTimer2 = null; window.idleTimer3 = null;\n        window.idleIgnoreUntil = 0;\n        window.idleForceClosing = false;\n        window.idleAwaitingChoice = false;\n        window.idleLastUserActivityAt = Date.now();\n        window.idleFirstWarningAt = 0;\n        window.idleFinalWarningAt = 0;\n        window.idleCloseCheckAt = 0;\n        window.IDLE_FIRST_WARNING_MS = 180000;   \/\/ 3 minuti senza attivita utente\n        window.IDLE_SECOND_WARNING_MS = 120000;  \/\/ ulteriore attesa dopo il primo avviso per utenti standard\/responsabili\n        window.IDLE_CLOSE_AFTER_MS = 60000;      \/\/ chiusura finale per utenti standard\/responsabili\n        window.IDLE_ADMIN_CONFIG_CLOSE_AFTER_WARNING_MS = 3600000; \/\/ 1 ora dall ultimo avviso per amministratori\/configuratori\n        window.isIdleSessionActive = function() {\n            return !!(dom.ditta && dom.ditta.value && dom.ditta.value !== 'NEW_DB');\n        };\n        window.isIdleAdminConfiguratorRole = function() {\n            const role = String(window.tiRole || '').toLowerCase();\n            const user = String(window.tiUser || '').trim();\n            return user === 'SSGlobalAdmin' || \/amministratore|administrator|configuratore|configurator\/.test(role);\n        };\n        window.clearIdleTimers = function() {\n            clearTimeout(window.idleTimer1);\n            clearTimeout(window.idleTimer2);\n            clearTimeout(window.idleTimer3);\n            window.idleTimer1 = null;\n            window.idleTimer2 = null;\n            window.idleTimer3 = null;\n        };\n        window.scheduleIdleFirstWarning = function() {\n            if (window.idleForceClosing) return;\n            window.clearIdleTimers();\n            if (!window.isIdleSessionActive()) return;\n            const elapsed = Date.now() - (window.idleLastUserActivityAt || Date.now());\n            const wait = Math.max(0, window.IDLE_FIRST_WARNING_MS - elapsed);\n            window.idleTimer1 = setTimeout(window.showIdleFirstWarning, wait);\n        };\n        window.showIdleFirstWarning = function() {\n            if (window.idleForceClosing || !window.isIdleSessionActive()) return;\n            const elapsed = Date.now() - (window.idleLastUserActivityAt || Date.now());\n            if (elapsed < window.IDLE_FIRST_WARNING_MS - 250) {\n                window.scheduleIdleFirstWarning();\n                return;\n            }\n            window.idleFirstWarningAt = Date.now();\n            window.idleAwaitingChoice = true;\n            window.idleIgnoreUntil = Date.now() + 8000;\n            if(window.addMsg) window.addMsg('ai', \"\u23f3 Non rilevo attivit\u00e0 da 3 minuti. Vuoi continuare ?\");\n            window.speakText(\"Non rilevo attivit\u00e0 da 3 minuti. Vuoi continuare?\");\n            clearTimeout(window.idleTimer2);\n            clearTimeout(window.idleTimer3);\n            if (window.isIdleAdminConfiguratorRole && window.isIdleAdminConfiguratorRole()) {\n                window.idleCloseCheckAt = window.idleFirstWarningAt;\n                window.idleTimer3 = setTimeout(window.closeIdleSessionIfStillInactive, window.IDLE_ADMIN_CONFIG_CLOSE_AFTER_WARNING_MS);\n            } else {\n                window.idleTimer2 = setTimeout(window.showIdleFinalWarning, window.IDLE_SECOND_WARNING_MS);\n            }\n        };\n        window.showIdleFinalWarning = function() {\n            if (window.idleForceClosing || !window.isIdleSessionActive()) return;\n            if ((window.idleLastUserActivityAt || 0) > (window.idleFirstWarningAt || 0)) {\n                window.scheduleIdleFirstWarning();\n                return;\n            }\n            window.idleFinalWarningAt = Date.now();\n            window.idleCloseCheckAt = window.idleFinalWarningAt;\n            window.idleIgnoreUntil = Date.now() + 8000;\n            if(window.addMsg) window.addMsg('ai', \"\u23f3 Se non ricevo istruzioni chiuder\u00f2 la sessione a breve.\");\n            window.speakText(\"Se non ricevo istruzioni, chiuder\u00f2 la sessione a breve per proteggere i tuoi dati.\");\n            clearTimeout(window.idleTimer3);\n            window.idleTimer3 = setTimeout(window.closeIdleSessionIfStillInactive, window.IDLE_CLOSE_AFTER_MS);\n        };\n        window.closeIdleSessionIfStillInactive = function() {\n            if (window.idleForceClosing || !window.isIdleSessionActive()) return;\n            const checkAt = window.idleCloseCheckAt || window.idleFinalWarningAt || window.idleFirstWarningAt || 0;\n            if ((window.idleLastUserActivityAt || 0) > checkAt) {\n                window.scheduleIdleFirstWarning();\n                return;\n            }\n            window.idleForceClosing = true;\n            if(window.addMsg) window.addMsg('ai', \"\ud83d\udd12 Sessione chiusa per inattivit\u00e0.\");\n            window.speakText(\"Sessione chiusa per inattivit\u00e0.\");\n            window.doLogout(true);\n        };\n        window.resetIdleTimers = function(evt) {\n            if (window.idleForceClosing) return;\n            if (evt && evt.isTrusted === false) return;\n            if (window.idleIgnoreUntil && Date.now() < window.idleIgnoreUntil) return;\n            window.idleLastUserActivityAt = Date.now();\n            window.idleFirstWarningAt = 0;\n            window.idleFinalWarningAt = 0;\n            window.idleCloseCheckAt = 0;\n            window.scheduleIdleFirstWarning();\n        };\n        ['mousemove', 'keydown', 'wheel', 'touchstart', 'click'].forEach(evt => {\n            document.addEventListener(evt, function(e){ window.resetIdleTimers(e); }, {passive: true});\n        });\n\n        window.placeMainHelpButtonRight = function() {\n            const row = document.querySelector('#ti-ai-outer .ti-actions-row');\n            const btn = gE('ti-help-btn');\n            if (!row || !btn) return;\n            if (btn.parentNode !== row || row.lastElementChild !== btn) row.appendChild(btn);\n        };\n        window.getPrivilegedActionsRow = function() {\n            let row = gE('ti-admin-actions-row');\n            const host = document.querySelector('#ti-ai-outer .ti-input-wrap');\n            if (!row && host) {\n                row = document.createElement('div');\n                row.id = 'ti-admin-actions-row';\n                row.className = 'ti-admin-actions-row';\n                row.setAttribute('aria-label', 'Comandi amministrativi');\n                host.appendChild(row);\n            }\n            return row;\n        };\n        window.refreshPrivilegedActionsRow = function() {\n            const row = window.getPrivilegedActionsRow ? window.getPrivilegedActionsRow() : gE('ti-admin-actions-row');\n            if (!row) return;\n            const ids = ['ti-conf-toggle', 'ti-activation-toggle', 'ti-purchase-toggle', 'ti-clean-orphans-btn', 'ti-global-action-toggle', 'ti-shared-setup-toggle'];\n            let visible = false;\n            ids.forEach(id => {\n                const b = gE(id);\n                if (b && b.parentNode !== row) row.appendChild(b);\n                if (b && b.style.display !== 'none') visible = true;\n            });\n            row.classList.toggle('ti-admin-actions-visible', !!visible);\n            row.style.display = visible ? 'grid' : 'none';\n        };\n\n        window.tiGetConfigRow = function(data) {\n            data = data || window.currentDbData || {};\n            const tables = data && data.Tabelle ? data.Tabelle : {};\n            const keys = Object.keys(tables || {});\n            let k = keys.find(x => String(x).toLowerCase() === 'config') || keys.find(x => \/config\/i.test(String(x))) || 'Config';\n            const rows = tables[k];\n            return Array.isArray(rows) && rows[0] && typeof rows[0] === 'object' ? rows[0] : {};\n        };\n        window.tiCiField = function(row, names, def) {\n            row = row || {}; names = Array.isArray(names) ? names : [names];\n            const keys = Object.keys(row || {});\n            const norm = function(v){ return String(v || '').normalize('NFD').replace(\/[\\u0300-\\u036f]\/g,'').toLowerCase().replace(\/[^a-z0-9]\/g,''); };\n            for (const n of names) {\n                const wn = norm(n);\n                for (const k of keys) if (norm(k) === wn) return String(row[k] == null ? '' : row[k]).trim();\n            }\n            return def || '';\n        };\n        window.tiCompanyActivationState = function(data) {\n            const cfg = window.tiGetConfigRow(data);\n            const companyKey = window.tiCiField(cfg, ['ActiveID'], '');\n            const stato = window.tiCiField(cfg, ['Stato'], '');\n            const activeId = window.tiCiField(cfg, ['active_id','wc_activate_id','wc_activation_id'], '');\n            const monthlyId = window.tiCiField(cfg, ['canone_id','active_canone_id','wc_canone_order_id','wc_monthly_order_id','wc_subscription_id','subscription_id','abbonamento_id'], '');\n            const monthlyState = window.tiCiField(cfg, ['Stato canone','Canone stato','stato_canone','Abbonamento stato'], '');\n            const activationActive = \/^(attivo|operativo)$\/i.test(stato) && activeId !== '';\n            const monthlyActive = \/^operativo$\/i.test(stato) || \/^(attivo|operativo)$\/i.test(monthlyState);\n            return {companyKey, stato, activeId, monthlyId, monthlyState, activationActive, monthlyActive};\n        };\n        window.tiNormalizeShopServiceProductUrl = function(base, kind) {\n            kind = kind === 'monthly' ? 'monthly' : 'activation';\n            const fallback = kind === 'monthly' ? 'https:\/\/www.tisoft.it\/prodotto\/shop-service\/' : 'https:\/\/www.tisoft.it\/prodotto\/shop-service-attivazione\/';\n            base = String(base || '').trim() || fallback;\n            try {\n                const u = new URL(base, window.location.origin);\n                const host = String(u.hostname || '').toLowerCase();\n                const pathNorm = '\/' + String(u.pathname || '').replace(\/^\\\/+|\\\/+$\/g, '') + '\/';\n                if (host === 'www.tisoft.it' || host === 'tisoft.it') {\n                    if (kind === 'monthly' && (pathNorm === '\/shop-service\/' || pathNorm === '\/prodotto\/shop-service-canone-mensile\/')) {\n                        u.pathname = '\/prodotto\/shop-service\/';\n                    }\n                    if (kind === 'activation' && (pathNorm === '\/shop-service-attivazione\/' || pathNorm === '\/attivazione-shop-service\/')) {\n                        u.pathname = '\/prodotto\/shop-service-attivazione\/';\n                    }\n                }\n                return u.toString();\n            } catch(e) {\n                return fallback;\n            }\n        };\n        window.tiBuildShopServiceBuyUrl = function(kind, companyKey) {\n            const rawBase = kind === 'monthly' ? (window.tiShopServiceMonthlyProductUrl || 'https:\/\/www.tisoft.it\/prodotto\/shop-service\/') : (window.tiShopServiceActivationProductUrl || 'https:\/\/www.tisoft.it\/prodotto\/shop-service-attivazione\/');\n            const base = window.tiNormalizeShopServiceProductUrl(rawBase, kind);\n            try {\n                const url = new URL(base, window.location.origin);\n                url.searchParams.set('ss_company', companyKey || '');\n                url.searchParams.set('ss_service', kind === 'monthly' ? 'monthly' : 'activation');\n                return url.toString();\n            } catch(e) {\n                const sep = String(base).indexOf('?') === -1 ? '?' : '&';\n                return String(base) + sep + 'ss_company=' + encodeURIComponent(companyKey || '') + '&ss_service=' + (kind === 'monthly' ? 'monthly' : 'activation');\n            }\n        };\n        window.ensureShopServiceActivationModal = function() {\n            let ov = gE('ti-shopservice-activation-ov');\n            if (ov) return ov;\n            ov = document.createElement('div');\n            ov.id = 'ti-shopservice-activation-ov';\n            ov.className = 'ti-modal-overlay';\n            ov.style.display = 'none';\n            ov.style.zIndex = '99992';\n            ov.style.alignItems = 'center';\n            ov.style.justifyContent = 'center';\n            ov.innerHTML = `<div class=\"ti-modal\" style=\"width:min(520px,94vw);background:#0f172a;color:#fff;border-radius:16px;padding:18px;box-shadow:0 18px 60px rgba(0,0,0,.45);\">\n                <div style=\"display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;\">\n                    <div><h2 style=\"margin:0;font-size:20px;\">Attiva Shop & Service<\/h2><div id=\"ti-shopservice-activation-sub\" style=\"font-size:13px;color:#cbd5e1;margin-top:5px;\"><\/div><\/div>\n                    <button type=\"button\" class=\"ti-btn\" style=\"background:#dc2626;color:#fff;\" onclick=\"window.closeShopServiceActivationPopup()\">X<\/button>\n                <\/div>\n                <div id=\"ti-shopservice-activation-body\" style=\"display:grid;gap:10px;margin-top:12px;\"><\/div>\n                <div style=\"font-size:12px;color:#cbd5e1;margin-top:14px;line-height:1.35;\">Eventuali variazioni del servizio\/canone saranno gestite dal profilo WooCommerce richiamando l ordine attivo.<\/div>\n            <\/div>`;\n            document.body.appendChild(ov);\n            return ov;\n        };\n        window.closeShopServiceActivationPopup = function() {\n            const ov = gE('ti-shopservice-activation-ov');\n            if (!ov) return;\n            if (window.closeModal) window.closeModal(ov); else ov.style.display = 'none';\n        };\n        window.openShopServiceActivationPopup = function() {\n            const dbVal = gE('ti-ditta') ? gE('ti-ditta').value : '';\n            if (!dbVal || dbVal === 'NEW_DB') { window.tiAlert('Seleziona prima una ditta.'); return; }\n            const openWithData = function(data) {\n                window.currentDbData = data || window.currentDbData || {};\n                const st = window.tiCompanyActivationState(window.currentDbData);\n                if (!st.companyKey) { window.tiAlert('Codice ActiveID ditta non disponibile. Riapri Configura o ricarica la ditta per generarlo.'); return; }\n                const ov = window.ensureShopServiceActivationModal();\n                const sub = gE('ti-shopservice-activation-sub');\n                const body = gE('ti-shopservice-activation-body');\n                if (sub) sub.textContent = 'Codice ditta: ' + st.companyKey;\n                const actions = [];\n                if (!st.activationActive) {\n                    actions.push('<a class=\"ti-btn\" style=\"display:block;text-align:center;background:#22c55e;color:#052e16;font-weight:800;padding:12px;border-radius:10px;text-decoration:none;\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"' + window.escapeHtml(window.tiBuildShopServiceBuyUrl('activation', st.companyKey)) + '\">Acquista attivazione Shop & Service<\/a>');\n                }\n                if (st.activationActive && !st.monthlyActive) {\n                    actions.push('<a class=\"ti-btn\" style=\"display:block;text-align:center;background:#38bdf8;color:#082f49;font-weight:800;padding:12px;border-radius:10px;text-decoration:none;\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"' + window.escapeHtml(window.tiBuildShopServiceBuyUrl('monthly', st.companyKey)) + '\">Acquista canone servizio Shop & Service<\/a>');\n                } else if (!st.activationActive) {\n                    actions.push('<a class=\"ti-btn\" style=\"display:block;text-align:center;background:#38bdf8;color:#082f49;font-weight:800;padding:12px;border-radius:10px;text-decoration:none;\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"' + window.escapeHtml(window.tiBuildShopServiceBuyUrl('monthly', st.companyKey)) + '\">Acquista canone servizio Shop & Service<\/a>');\n                }\n                if (!actions.length) {\n                    if (body) body.innerHTML = '<div style=\"padding:12px;border-radius:10px;background:#052e16;border:1px solid #16a34a;color:#bbf7d0;font-weight:700;\">Attivazione e canone risultano gi\u00e0 attivi. Non sono necessarie altre azioni.<\/div>';\n                } else if (body) body.innerHTML = actions.join('');\n                if (window.showPluginModal) window.showPluginModal(ov); else ov.style.display = 'flex';\n            };\n            if (window.currentDbData && window.currentDbData.Tabelle) { openWithData(window.currentDbData); return; }\n            const fd = new FormData(); fd.append('ti_action','ti_ai_get_db_data'); fd.append('db', dbVal);\n            window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd, tiShowTableReadWait:true, tiTableReadTitle:'Lettura stato attivazione', tiTableReadDetail:'Sto leggendo ActiveID, stato ditta e canone.', tiTableReadItem:dbVal}).then(function(res){\n                if (res && res.success) openWithData(res.data); else window.tiAlert((res && res.data && res.data.message) ? res.data.message : 'Errore lettura database.');\n            }).catch(function(err){ window.tiAlert('Errore lettura attivazione: ' + err.message); });\n        };\n        window.refreshShopServiceActivationButton = function() {\n            const btn = gE('ti-activation-toggle');\n            if (!btn) return;\n            const role = String(window.tiRole || '').toLowerCase();\n            const can = String(window.tiUser || '').toLowerCase() === 'ssglobaladmin' || role.includes('amministratore') || role.includes('configuratore') || role.includes('responsabile');\n            if (!can) { btn.style.display = 'none'; return; }\n            const st = window.tiCompanyActivationState(window.currentDbData || {});\n            btn.style.display = (st.activationActive && st.monthlyActive) ? 'none' : 'inline-block';\n        };\n\n\n        window.ensureDeleteCompanyModal = function() {\n            let ov = gE('ti-delete-company-ov');\n            if (ov) return ov;\n            ov = document.createElement('div');\n            ov.id = 'ti-delete-company-ov';\n            ov.className = 'ti-modal-overlay';\n            ov.style.display = 'none';\n            ov.style.zIndex = '99994';\n            ov.style.alignItems = 'center';\n            ov.style.justifyContent = 'center';\n            ov.innerHTML = `<div class=\"ti-modal\" style=\"width:min(560px,94vw);background:#111827;color:#fff;border-radius:16px;padding:18px;box-shadow:0 18px 60px rgba(0,0,0,.55);border:1px solid #dc2626;\">\n                <div style=\"display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;\">\n                    <div><h2 style=\"margin:0;font-size:20px;color:#fecaca;\">Cancella ditta<\/h2><div id=\"ti-delete-company-sub\" style=\"font-size:13px;color:#e5e7eb;margin-top:5px;\"><\/div><\/div>\n                    <button type=\"button\" class=\"ti-btn\" style=\"background:#374151;color:#fff;\" onclick=\"window.closeDeleteCompanyPopup()\">X<\/button>\n                <\/div>\n                <div style=\"background:#7f1d1d;border:1px solid #ef4444;border-radius:12px;padding:12px;line-height:1.4;font-size:14px;\">\n                    <b>Avviso definitivo.<\/b><br>\n                    La ditta verra cancellata da Shop & Service ed eliminati tutti gli archivi finora trattati nelle path del plugin. Prima della cancellazione viene creato un ultimo backup ZIP nella path condivisa e inviato via email il link al file.\n                <\/div>\n                <div style=\"margin-top:12px;font-size:13px;color:#e5e7eb;\">Per confermare inserisci la password dell utente loggato e premi <b>OK, cancella ditta<\/b>.<\/div>\n                <input id=\"ti-delete-company-password\" type=\"password\" class=\"ti-in\" autocomplete=\"current-password\" placeholder=\"Password\" style=\"margin-top:10px;width:100%;box-sizing:border-box;\">\n                <div id=\"ti-delete-company-status\" style=\"margin-top:10px;font-size:13px;color:#fde68a;min-height:18px;\"><\/div>\n                <div style=\"display:flex;gap:8px;justify-content:flex-end;margin-top:14px;flex-wrap:wrap;\">\n                    <button type=\"button\" class=\"ti-btn\" style=\"background:#374151;color:#fff;\" onclick=\"window.closeDeleteCompanyPopup()\">Annulla<\/button>\n                    <button type=\"button\" id=\"ti-delete-company-confirm-btn\" class=\"ti-btn\" style=\"background:#dc2626;color:#fff;border:1px solid #991b1b;font-weight:800;\" onclick=\"window.confirmDeleteCompany()\">OK, cancella ditta<\/button>\n                <\/div>\n            <\/div>`;\n            document.body.appendChild(ov);\n            return ov;\n        };\n        window.openDeleteCompanyPopup = function() {\n            const dbVal = gE('ti-ditta') ? gE('ti-ditta').value : '';\n            if (!dbVal || dbVal === 'NEW_DB') { window.tiAlert('Seleziona prima la ditta da cancellare.'); return; }\n            const ov = window.ensureDeleteCompanyModal();\n            const sub = gE('ti-delete-company-sub');\n            const pwd = gE('ti-delete-company-password');\n            const st = gE('ti-delete-company-status');\n            if (sub) sub.textContent = 'Ditta\/database selezionato: ' + dbVal;\n            if (pwd) pwd.value = '';\n            if (st) st.textContent = '';\n            if (window.showPluginModal) window.showPluginModal(ov); else ov.style.display = 'flex';\n            setTimeout(function(){ if (pwd) pwd.focus(); }, 150);\n        };\n        window.closeDeleteCompanyPopup = function() {\n            const ov = gE('ti-delete-company-ov');\n            if (!ov) return;\n            if (window.closeModal) window.closeModal(ov); else ov.style.display = 'none';\n        };\n        window.finishDeleteCompanySuccess = function(data) {\n            const m = (data && data.message) ? data.message : 'Ditta cancellata definitivamente.';\n            const st = gE('ti-delete-company-status');\n            if (st) st.textContent = m;\n            window.tiAlert(m);\n            window.closeDeleteCompanyPopup();\n            try { if (gE('ti-ditta')) gE('ti-ditta').value = ''; } catch(e) {}\n            if (typeof window.loadDitte === 'function') window.loadDitte();\n            if (data && data.logout) setTimeout(function(){ window.location.reload(); }, 900);\n        };\n        window.retryDeleteCompanyWithEmailChoice = function(opts) {\n            opts = opts || {};\n            const dbVal = opts.db || (gE('ti-ditta') ? gE('ti-ditta').value : '');\n            const pwd = opts.password || (gE('ti-delete-company-password') ? gE('ti-delete-company-password').value : '');\n            const st = gE('ti-delete-company-status');\n            const btn = gE('ti-delete-company-confirm-btn');\n            if (!dbVal || !pwd) { window.tiAlert('Ditta o password non disponibili per continuare la cancellazione.'); return; }\n            if (btn) btn.disabled = true;\n            if (st) st.textContent = opts.skipEmail ? 'Proseguo senza invio email dopo conferma finale...' : 'Riprovo invio backup all indirizzo indicato...';\n            const fd = new FormData();\n            fd.append('ti_action', 'ti_ai_delete_company');\n            fd.append('db', dbVal);\n            fd.append('password', pwd);\n            fd.append('confirm', 'OK');\n            if (opts.email) fd.append('delete_email', opts.email);\n            if (opts.skipEmail) { fd.append('skip_email', '1'); fd.append('confirm_no_email', 'OK'); }\n            window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(function(res){\n                if (btn) btn.disabled = false;\n                if (!res || !res.success) {\n                    const m = (res && res.data && res.data.message) ? res.data.message : 'Errore cancellazione ditta.';\n                    if (st) st.textContent = m;\n                    window.tiAlert(m);\n                    return;\n                }\n                const data = res.data || {};\n                if (data.needs_email_choice) { window.handleDeleteCompanyEmailChoice(data, dbVal, pwd); return; }\n                window.finishDeleteCompanySuccess(data);\n            }).catch(function(err){\n                if (btn) btn.disabled = false;\n                const m = 'Errore cancellazione ditta: ' + (err && err.message ? err.message : err);\n                if (st) st.textContent = m;\n                window.tiAlert(m);\n            });\n        };\n        window.handleDeleteCompanyEmailChoice = function(data, dbVal, pwd) {\n            const st = gE('ti-delete-company-status');\n            const msg = (data && data.message) ? data.message : 'Backup finale creato, ma email non inviata.';\n            if (st) st.textContent = msg;\n            const chooseEmail = window.confirm(msg + '\\n\\nPremi OK per indicare un altro indirizzo email.\\nPremi Annulla per proseguire senza invio email.');\n            if (chooseEmail) {\n                const email = window.prompt('Indica indirizzo email a cui inviare il backup finale ZIP:', (data && data.email) ? data.email : '');\n                if (!email) { if (st) st.textContent = 'Cancellazione sospesa. Nessuna email indicata.'; return; }\n                window.retryDeleteCompanyWithEmailChoice({db:dbVal, password:pwd, email:String(email).trim()});\n                return;\n            }\n            const finalConfirm = window.confirm('Confermi definitivamente di cancellare la ditta SENZA inviare via email il link al backup finale?\\n\\nIl backup ZIP e stato creato nello storage del plugin. Questa scelta elimina comunque database e archivi ditta, conservando la cartella Backups.');\n            if (!finalConfirm) { if (st) st.textContent = 'Cancellazione sospesa. Backup creato, ditta non cancellata.'; return; }\n            window.retryDeleteCompanyWithEmailChoice({db:dbVal, password:pwd, skipEmail:true});\n        };\n\n        window.confirmDeleteCompany = function() {\n            const dbVal = gE('ti-ditta') ? gE('ti-ditta').value : '';\n            const pwdEl = gE('ti-delete-company-password');\n            const pwd = pwdEl ? pwdEl.value : '';\n            const st = gE('ti-delete-company-status');\n            const btn = gE('ti-delete-company-confirm-btn');\n            if (!dbVal || dbVal === 'NEW_DB') { window.tiAlert('Ditta non valida.'); return; }\n            if (!pwd) { if (st) st.textContent = 'Inserisci la password.'; return; }\n            const msg = 'Confermi definitivamente la cancellazione della ditta ' + dbVal + '?\\n\\nVerr\u00e0 creato un ultimo backup ZIP, inviato via email come link nella path condivisa, poi saranno eliminati database e cartelle archivio ditta conservando Backups.';\n            if (!window.confirm(msg)) return;\n            if (btn) btn.disabled = true;\n            if (st) st.textContent = 'Attendere: creo backup finale, invio email con link al backup e cancello gli archivi ditta...';\n            const fd = new FormData();\n            fd.append('ti_action', 'ti_ai_delete_company');\n            fd.append('db', dbVal);\n            fd.append('password', pwd);\n            fd.append('confirm', 'OK');\n            window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(function(res){\n                if (btn) btn.disabled = false;\n                if (!res || !res.success) {\n                    const m = (res && res.data && res.data.message) ? res.data.message : 'Errore cancellazione ditta.';\n                    if (st) st.textContent = m;\n                    window.tiAlert(m);\n                    return;\n                }\n                const data = res.data || {};\n                if (data.needs_email_choice) { window.handleDeleteCompanyEmailChoice(data, dbVal, pwd); return; }\n                window.finishDeleteCompanySuccess(data);\n            }).catch(function(err){\n                if (btn) btn.disabled = false;\n                const m = 'Errore cancellazione ditta: ' + (err && err.message ? err.message : err);\n                if (st) st.textContent = m;\n                window.tiAlert(m);\n            });\n        };\n\n\n        window.tiMaintenanceState = window.tiMaintenanceState || {active:false, can_configure:false, connected_count:0};\n        window.tiMaintenanceLastWarnedDb = window.tiMaintenanceLastWarnedDb || '';\n\n        window.currentSelectedDbName = function() {\n            const el = gE('ti-ditta');\n            return el ? String(el.value || '') : '';\n        };\n\n        window.updateLoginMaintenanceNotice = window.updateLoginMaintenanceNotice || function(active) {\n            const note = gE('ti-login-maintenance-note');\n            if (!note) return;\n            note.style.display = active ? 'block' : 'none';\n        };\n\n        window.requestMaintenanceStatus = function(opts) {\n            opts = opts || {};\n            const db = opts.db || window.currentSelectedDbName();\n            if (!db || db === 'NEW_DB' || db === 'Global') return Promise.resolve({success:true, data:{maintenance:{active:false}, connected:{count:0, users:[]}, can_configure:false}});\n            const fd = new FormData();\n            fd.append('ti_action', 'ti_ai_maintenance_status');\n            fd.append('db', db);\n            return window.fetchJsonSafe(window.tiUrl || window.location.href, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store', tiNoLongProcessSignal:true}).then(function(res){\n                if (res && res.success && res.data) window.applyMaintenanceStatus(res.data);\n                return res;\n            }).catch(function(){ return {success:false, data:{maintenance:{active:false}, connected:{count:0, users:[]}}}; });\n        };\n\n        window.applyMaintenanceStatus = function(data) {\n            data = data || {};\n            const maint = data.maintenance || {};\n            const connected = data.connected || {};\n            window.tiMaintenanceState = {\n                active: !!maint.active,\n                can_configure: !!data.can_configure,\n                connected_count: parseInt(connected.count || data.connected_count || 0, 10) || 0,\n                maintenance: maint,\n                connected: connected,\n                database_protected: !!data.database_protected,\n                message: data.message || ''\n            };\n            if (window.refreshMaintenanceButton) window.refreshMaintenanceButton();\n            if (window.updateLoginMaintenanceNotice) window.updateLoginMaintenanceNotice(window.tiMaintenanceState.active && !window.tiMaintenanceState.can_configure);\n            const maintenanceLogged = !!String(window.tiUser || '').trim();\n            if (window.tiMaintenanceState.active && !window.tiMaintenanceState.can_configure && maintenanceLogged) window.showMandatoryMaintenancePopup(window.tiMaintenanceState);\n            else {\n                const ov = gE('ti-maintenance-ov');\n                if (ov) { ov.style.display = 'none'; window.updatePluginScrollLock && window.updatePluginScrollLock(); }\n            }\n        };\n\n        window.showMandatoryMaintenancePopup = function(state) {\n            if (!String(window.tiUser || '').trim()) {\n                const ov = gE('ti-maintenance-ov');\n                if (ov) { ov.style.display = 'none'; window.updatePluginScrollLock && window.updatePluginScrollLock(); }\n                return;\n            }\n            const msg = gE('ti-maintenance-msg');\n            const maint = (state && state.maintenance) ? state.maintenance : {};\n            const by = maint.started_by ? '<br><br><b>Manutenzione avviata da:<\/b> ' + window.escapeHtml(maint.started_by) : '';\n            const at = maint.started_at ? '<br><b>Avvio:<\/b> ' + window.escapeHtml(maint.started_at) : '';\n            if (msg) msg.innerHTML = 'Il servizio \u00e8 temporaneamente in manutenzione.<br><br>Il gestore si scusa per il disagio: sar\u00e0 nuovamente disponibile in tempi brevi.' + by + at + '<br><br><span style=\"color:#bfdbfe;font-weight:700;\">Configuratori e amministratori possono accedere dal pulsante dedicato.<\/span>';\n            const ov = gE('ti-maintenance-ov');\n            if (ov) {\n                ov.setAttribute('data-ti-non-closable','1');\n                window.showPluginModal('ti-maintenance-ov');\n                ov.querySelectorAll('.ti-modal-x-red, .ti-modal-bottom-close-row').forEach(function(n){ if (n && n.parentNode) n.parentNode.removeChild(n); });\n            }\n        };\n\n        window.refreshMaintenanceButton = function() {\n            const btn = gE('ti-maint-toggle-btn');\n            if (!btn) return;\n            const st = window.tiMaintenanceState || {};\n            const maint = st.maintenance || {};\n            const active = !!(st.active || st.maintenance_active || st.maintenance_locked || maint.active);\n            const bg = active ? '#dc2626' : '#16a34a';\n            const border = active ? '#991b1b' : '#15803d';\n            btn.textContent = active ? 'Manutenzione ON' : 'Manutenzione OFF';\n            btn.title = active ? 'Manutenzione attiva: premi per disattivarla dopo conferma.' : 'Manutenzione disattivata: premi per attivarla dopo conferma.';\n            btn.setAttribute('data-ti-maint-state', active ? 'on' : 'off');\n            btn.setAttribute('aria-pressed', active ? 'true' : 'false');\n            btn.classList.remove('ti-maint-on', 'ti-maint-off');\n            btn.classList.add(active ? 'ti-maint-on' : 'ti-maint-off');\n            btn.style.setProperty('background', bg, 'important');\n            btn.style.setProperty('background-color', bg, 'important');\n            btn.style.setProperty('border', '1px solid ' + border, 'important');\n            btn.style.setProperty('color', '#fff', 'important');\n            btn.style.setProperty('box-shadow', active ? '0 0 0 2px rgba(220,38,38,.20)' : '0 0 0 2px rgba(22,163,74,.18)', 'important');\n        };\n\n        window.toggleCompanyMaintenance = function(active) {\n            const db = window.currentSelectedDbName();\n            if (!db || db === 'NEW_DB') { window.tiAlert('Seleziona una ditta.'); return Promise.resolve(false); }\n            const fd = new FormData();\n            fd.append('ti_action', 'ti_ai_maintenance_toggle');\n            fd.append('db', db);\n            fd.append('active', active ? '1' : '0');\n            fd.append('confirm', '1');\n            return window.fetchJsonSafe(window.tiUrl || window.location.href, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store', tiNoLongProcessSignal:true}).then(function(res){\n                if (!res || !res.success) {\n                    window.tiAlert((res && res.data && res.data.message) ? res.data.message : 'Cambio manutenzione non riuscito.');\n                    return false;\n                }\n                if (res.data) window.applyMaintenanceStatus(res.data);\n                if (window.tiAlert && res.data && res.data.reply) window.tiAlert(res.data.reply);\n                return true;\n            }).catch(function(err){\n                window.tiAlert('Errore cambio manutenzione: ' + ((err && err.message) ? err.message : 'errore sconosciuto'));\n                return false;\n            });\n        };\n\n        window.confirmToggleCompanyMaintenance = function(forceActive) {\n            const current = !!((window.tiMaintenanceState || {}).active);\n            const next = (typeof forceActive === 'boolean') ? !!forceActive : !current;\n            const connected = parseInt(((window.tiMaintenanceState || {}).connected_count) || 0, 10) || 0;\n            const msg = next\n                ? 'Confermi attivazione manutenzione?<br><br>Gli altri utenti vedranno un popup non chiudibile di manutenzione. La configurazione potr\u00e0 modificare il database.' + (connected ? '<br><br>Utenti connessi rilevati: <b>' + connected + '<\/b>.' : '')\n                : 'Confermi disattivazione manutenzione?<br><br>Il servizio torner\u00e0 disponibile agli utenti.';\n            if (typeof window.tiConfirm === 'function') window.tiConfirm(msg, function(){ window.toggleCompanyMaintenance(next); });\n            else if (confirm(msg.replace(\/<br\\s*\\\/?>\/gi, '\\n').replace(\/<[^>]+>\/g,''))) window.toggleCompanyMaintenance(next);\n        };\n\n        window.showConfigProtectedPopup = function(data, onActivate, onOpenOnly) {\n            data = data || {};\n            const count = parseInt(data.connected_count || (data.connected && data.connected.count) || ((window.tiMaintenanceState || {}).connected_count) || 0, 10) || 0;\n            let users = data.connected_users || (data.connected && data.connected.users) || [];\n            if (!Array.isArray(users)) users = [];\n            const userLines = users.slice(0, 6).map(function(u){ return '\u2022 ' + window.escapeHtml(u.display || u.username || 'Utente') + (u.role ? ' (' + window.escapeHtml(u.role) + ')' : ''); }).join('<br>');\n            const msg = '<div style=\"text-align:left;line-height:1.5;\">' +\n                '<b>Database protetto: utenti connessi rilevati.<\/b><br><br>' +\n                'Utenti connessi alla ditta: <b>' + count + '<\/b>.<br>' +\n                (userLines ? '<div style=\"margin-top:8px;padding:8px;border:1px solid #334155;border-radius:8px;background:#020617;color:#dbeafe;\">' + userLines + '<\/div>' : '') +\n                '<br>I dati potrebbero essere non modificabili finch\u00e9 il servizio non viene posto in manutenzione.<br><br>' +\n                'Per configurare in sicurezza puoi attivare la manutenzione: gli altri utenti vedranno un popup non chiudibile con le scuse del gestore e l avviso di disponibilit\u00e0 a breve.' +\n                '<\/div>';\n            if (typeof window.tiConfirmYesNoAction === 'function') {\n                window.tiConfirmYesNoAction(msg, function(){\n                    window.toggleCompanyMaintenance(true).then(function(ok){ if (ok && typeof onActivate === 'function') onActivate(); });\n                }, function(){ if (typeof onOpenOnly === 'function') onOpenOnly(); }, 'ATTIVA MANUTENZIONE', 'SOLO CONSULTAZIONE');\n            } else if (window.confirm(msg.replace(\/<br\\s*\\\/?>\/gi, '\\n').replace(\/<[^>]+>\/g,''))) {\n                window.toggleCompanyMaintenance(true).then(function(ok){ if (ok && typeof onActivate === 'function') onActivate(); });\n            } else if (typeof onOpenOnly === 'function') onOpenOnly();\n        };\n\n        window.openConfigWithProtectionCheck = function() {\n            const dbEl = gE('ti-ditta');\n            const db = dbEl ? dbEl.value : '';\n            if (!db || db === 'NEW_DB') { window.tiAlert('Seleziona prima una ditta.'); return; }\n            const readAndOpen = function(){\n                const fd = new FormData();\n                fd.append('ti_action', 'ti_ai_get_db_data');\n                fd.append('db', db);\n                window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd, tiShowTableReadWait:true, tiTableReadTitle:'\u23f3 Lettura tabelle configurazione', tiTableReadDetail:'Sto leggendo tutte le tabelle della ditta prima di aprire la configurazione.', tiTableReadItem:db}).then(function(d){\n                    if(d && d.success) {\n                        window.currentDbData = d.data;\n                        window.configOriginalDbData = JSON.parse(JSON.stringify(d.data || {}));\n                        window.configWasSaved = false;\n                        window.tiConfigDeletedRowsManifest = [];\n                        if(window.modifiedFields && typeof window.modifiedFields.clear === 'function') window.modifiedFields.clear();\n                        if(window.refreshShopServiceActivationButton) window.refreshShopServiceActivationButton();\n                        window.renderConfig();\n                        window.openModal('ti-conf-ov');\n                        window.requestMaintenanceStatus({db:db});\n                    } else {\n                        window.tiAlert((d && d.data && d.data.message) ? d.data.message : 'Errore lettura database.');\n                    }\n                }).catch(function(err){ window.tiAlert('Errore apertura configurazione: ' + ((err && err.message) ? err.message : 'errore sconosciuto')); });\n            };\n            window.requestMaintenanceStatus({db:db}).then(function(res){\n                const data = (res && res.data) ? res.data : {};\n                if (data && data.database_protected) {\n                    window.showConfigProtectedPopup(data, readAndOpen, readAndOpen);\n                } else {\n                    readAndOpen();\n                }\n            }).catch(function(){ readAndOpen(); });\n        };\n\n        window.startMaintenanceHeartbeat = function() {\n            if (window.__tiMaintenanceHeartbeatStarted) return;\n            window.__tiMaintenanceHeartbeatStarted = true;\n            const run = function(){ window.requestMaintenanceStatus({silent:true}); };\n            setTimeout(run, 900);\n            setInterval(run, 45000);\n            const sel = gE('ti-ditta');\n            if (sel && !sel.__tiMaintenanceBound) {\n                sel.__tiMaintenanceBound = true;\n                sel.addEventListener('change', function(){ setTimeout(run, 250); });\n            }\n        };\n        document.addEventListener('DOMContentLoaded', function(){ if (window.startMaintenanceHeartbeat) window.startMaintenanceHeartbeat(); });\n        setTimeout(function(){ if (window.startMaintenanceHeartbeat) window.startMaintenanceHeartbeat(); }, 1200);\n\n        window.isPurchaseRoleEnabled = function(role, user) {\n            const r = String(role || '').toLowerCase();\n            const u = String(user || window.tiUser || '').toLowerCase();\n            const hasPurchaseRole = r.includes('acquisti') || r.includes('buyer') || r.includes('procurement');\n            return u === 'ssglobaladmin'\n                || r.includes('amministratore')\n                || r.includes('configuratore')\n                || r.includes('responsabile')\n                || hasPurchaseRole;\n        };\n\n        window.setAdminUI = function(user, role) {\n            const uName = gE('ti-user-name'); const uRole = gE('ti-user-role'); const uArea = gE('ti-user-area');\n            if(uName) uName.textContent = (user || ''); if(uRole) uRole.textContent = role ? (' - ' + role) : ''; if(uArea) uArea.style.display = 'flex';\n            if (window.tiCloseDirectLoginFallback) window.tiCloseDirectLoginFallback();\n            if (window.closeModal) window.closeModal('ti-login-ov');\n            gE('ti-login-btn').style.display = 'none'; gE('ti-logout-btn').style.display = 'inline-block';\n            if (typeof window.ensureStandardMainFormForCurrentUser === 'function') window.ensureStandardMainFormForCurrentUser();\n            const roleLower = String(role || '').toLowerCase();\n            const canOpenConfig = String(window.tiUser || user || '').toLowerCase() === 'ssglobaladmin' || roleLower.includes('amministratore') || roleLower.includes('configuratore') || roleLower.includes('responsabile');\n            const canOpenPurchases = window.isPurchaseRoleEnabled(role, window.tiUser || user);\n            if(canOpenConfig) {\n                let confBtn = gE('ti-conf-toggle');\n                if(!confBtn) { \n                    confBtn = document.createElement('button'); confBtn.type=\"button\"; confBtn.id = 'ti-conf-toggle'; confBtn.className = 'ti-btn ti-btn-conf-toggle'; confBtn.innerHTML = '\u2699\ufe0f <b>CONFIGURA<\/b>'; confBtn.style.background = '#10b981'; confBtn.style.color = '#ffffff'; confBtn.style.border = '1px solid #047857'; const adminRow = window.getPrivilegedActionsRow(); if (adminRow) adminRow.appendChild(confBtn); \n                }\n                confBtn.style.display = 'inline-block';\n                confBtn.onclick = () => { if (window.openConfigWithProtectionCheck) window.openConfigWithProtectionCheck(); };\n\n                let actBtn = gE('ti-activation-toggle');\n                if (!actBtn) {\n                    actBtn = document.createElement('button');\n                    actBtn.type = 'button';\n                    actBtn.id = 'ti-activation-toggle';\n                    actBtn.className = 'ti-btn';\n                    actBtn.innerHTML = '\u26a1 <b>Attiva Shop & Service<\/b>';\n                    actBtn.title = 'Acquista attivazione o canone mensile Shop & Service tramite WooCommerce';\n                    actBtn.style.background = '#f59e0b';\n                    actBtn.style.color = '#111827';\n                    actBtn.style.border = '1px solid #b45309';\n                    const adminRow = window.getPrivilegedActionsRow();\n                    if (adminRow) adminRow.appendChild(actBtn);\n                }\n                actBtn.style.display = 'inline-block';\n                actBtn.onclick = () => window.openShopServiceActivationPopup();\n                if (window.refreshShopServiceActivationButton) window.refreshShopServiceActivationButton();\n\n                let purchBtn = gE('ti-purchase-toggle');\n                if(canOpenPurchases) {\n                    if(!purchBtn) {\n                        purchBtn = document.createElement('button');\n                        purchBtn.type = 'button';\n                        purchBtn.id = 'ti-purchase-toggle';\n                        purchBtn.className = 'ti-btn';\n                        purchBtn.innerHTML = '\ud83d\uded2 <b>ACQUISTI<\/b>';\n                        purchBtn.title = 'Acquisti - disponibile per amministratore, configuratore, responsabile, ruolo acquisti o SSGlobalAdmin';\n                        purchBtn.style.background = '#0ea5e9';\n                        purchBtn.style.color = '#fff';\n                        purchBtn.style.border = '1px solid #0369a1';\n                        const adminRow = window.getPrivilegedActionsRow();\n                        if (adminRow) adminRow.appendChild(purchBtn);\n                    }\n                    purchBtn.style.display = 'inline-block';\n                    purchBtn.onclick = () => window.openPurchaseManualFlow();\n                } else if (purchBtn) {\n                    purchBtn.style.display = 'none';\n                    purchBtn.onclick = null;\n                }\n                let cleanBtn = gE('ti-clean-orphans-btn');\n                if (window.canRunLocalOrphanCleanup && window.canRunLocalOrphanCleanup()) {\n                    if(!cleanBtn) {\n                        cleanBtn = document.createElement('button');\n                        cleanBtn.type = 'button';\n                        cleanBtn.id = 'ti-clean-orphans-btn';\n                        cleanBtn.className = 'ti-btn';\n                        cleanBtn.innerHTML = '\ud83e\uddf9 <b>Pulisci file non associati<\/b>';\n                        cleanBtn.title = 'Elimina fisicamente dallo storage i file non associati alla ditta selezionata';\n                        cleanBtn.style.background = '#475569';\n                        cleanBtn.style.color = '#fff';\n                        cleanBtn.style.border = '1px solid #334155';\n                        cleanBtn.style.fontSize = '11px';\n                        const adminRow = window.getPrivilegedActionsRow();\n                        if (adminRow) adminRow.appendChild(cleanBtn);\n                    }\n                    cleanBtn.style.display = 'inline-block';\n                    cleanBtn.onclick = () => window.runOrphanFileCleanup();\n                } else if (cleanBtn) {\n                    cleanBtn.style.display = 'none';\n                    cleanBtn.onclick = null;\n                }\n                let delCompanyBtn = gE('ti-delete-company-toggle');\n                const canDeleteCompany = String(window.tiUser || user || '').toLowerCase() === 'ssglobaladmin' || roleLower.includes('amministratore') || roleLower.includes('configuratore');\n                if (canDeleteCompany) {\n                    if (!delCompanyBtn) {\n                        delCompanyBtn = document.createElement('button');\n                        delCompanyBtn.type = 'button';\n                        delCompanyBtn.id = 'ti-delete-company-toggle';\n                        delCompanyBtn.className = 'ti-btn';\n                        delCompanyBtn.innerHTML = '\ud83d\uddd1 <b>Cancella ditta<\/b>';\n                        delCompanyBtn.title = 'Cancellazione definitiva della ditta con backup finale e conferma password';\n                        delCompanyBtn.style.background = '#dc2626';\n                        delCompanyBtn.style.color = '#fff';\n                        delCompanyBtn.style.border = '1px solid #991b1b';\n                        delCompanyBtn.style.fontSize = '11px';\n                        const adminRow = window.getPrivilegedActionsRow();\n                        if (adminRow) adminRow.appendChild(delCompanyBtn);\n                    }\n                    delCompanyBtn.style.display = 'inline-block';\n                    delCompanyBtn.onclick = () => window.openDeleteCompanyPopup();\n                } else if (delCompanyBtn) {\n                    delCompanyBtn.style.display = 'none';\n                    delCompanyBtn.onclick = null;\n                }\n            } else {\n                const confBtnOff = gE('ti-conf-toggle');\n                if (confBtnOff) confBtnOff.style.display = 'none';\n                const actBtnOff = gE('ti-activation-toggle');\n                if (actBtnOff) { actBtnOff.style.display = 'none'; actBtnOff.onclick = null; }\n                const purchBtnOff = gE('ti-purchase-toggle');\n                if (purchBtnOff) { purchBtnOff.style.display = 'none'; purchBtnOff.onclick = null; }\n                const cleanBtnOff = gE('ti-clean-orphans-btn');\n                if (cleanBtnOff) { cleanBtnOff.style.display = 'none'; cleanBtnOff.onclick = null; }\n                const delCompanyBtnOff = gE('ti-delete-company-toggle');\n                if (delCompanyBtnOff) { delCompanyBtnOff.style.display = 'none'; delCompanyBtnOff.onclick = null; }\n            }\n            if (window.tiUser === 'SSGlobalAdmin') {\n                let gb = gE('ti-global-action-toggle');\n                if(!gb) {\n                    gb = document.createElement('button'); gb.type=\"button\"; gb.id='ti-global-action-toggle'; gb.className='ti-btn'; gb.innerHTML='\ud83e\ude84 <b>GLOBALE<\/b>'; gb.style.background='#8b5cf6'; gb.style.color='#fff'; gb.style.border='1px solid #5b21b6'; const adminRow = window.getPrivilegedActionsRow(); if (adminRow) adminRow.appendChild(gb);\n                }\n                gb.style.display = 'inline-block';\n                gb.onclick = () => window.openModal('ti-global-action-ov');\n\n                let sb = gE('ti-shared-setup-toggle');\n                if(!sb) {\n                    sb = document.createElement('button'); sb.type='button'; sb.id='ti-shared-setup-toggle'; sb.className='ti-btn'; sb.innerHTML='\ud83d\uddc2 <b>STORAGE<\/b>'; sb.style.background='#a16207'; sb.style.color='#fff'; sb.style.border='1px solid #78350f'; const adminRow = window.getPrivilegedActionsRow(); if (adminRow) adminRow.appendChild(sb);\n                }\n                sb.style.display = 'inline-block';\n                sb.onclick = () => window.openSharedSetupModal();\n\n                let gclean = gE('ti-clean-orphans-global-btn');\n                if (gclean) {\n                    gclean.style.display = 'block';\n                    gclean.onclick = () => window.runGlobalOrphanFileCleanup();\n                }\n            } else {\n                const gcleanOff = gE('ti-clean-orphans-global-btn');\n                if (gcleanOff) { gcleanOff.style.display = 'none'; gcleanOff.onclick = null; }\n            }\n            if (window.refreshOrphanCleanupButton) window.refreshOrphanCleanupButton();\n            window.refreshPrivilegedActionsRow();\n            window.placeMainHelpButtonRight();\n            try { if (window.updateActivitiesButtonVisibility) window.updateActivitiesButtonVisibility(); } catch(e) {}\n            try { if (window.startMaintenanceHeartbeat) window.startMaintenanceHeartbeat(); if (window.requestMaintenanceStatus) window.requestMaintenanceStatus({silent:true}); } catch(e) {}\n            try { if (window.tiCleanEscapedUiInSubtree) window.tiCleanEscapedUiInSubtree(window.getPrivilegedActionsRow ? window.getPrivilegedActionsRow() : document.body); if (window.tiI18nTranslateSubtree) window.tiI18nTranslateSubtree(window.getPrivilegedActionsRow ? window.getPrivilegedActionsRow() : document.body); } catch(e) {}\n        };\n\n        if(window.tiUser) { window.setAdminUI(window.tiUserDisplay || window.tiUser, window.tiRole); if (typeof window.ensureStandardMainFormForCurrentUser === 'function') window.ensureStandardMainFormForCurrentUser(); if (window.updateActivitiesButtonVisibility) window.updateActivitiesButtonVisibility(); } else { const uName = gE('ti-user-name'); const uRole = gE('ti-user-role'); const uArea = gE('ti-user-area'); if (uName) uName.textContent = 'Cliente\/Utente'; if (uRole) uRole.textContent = ''; if (uArea) uArea.style.display = 'flex'; ['ti-conf-toggle','ti-activation-toggle','ti-purchase-toggle','ti-clean-orphans-btn','ti-delete-company-toggle','ti-clean-orphans-global-btn','ti-global-action-toggle','ti-shared-setup-toggle'].forEach(function(id){ const b=gE(id); if(b){ b.style.display='none'; b.onclick=null; } }); const adminRow = gE('ti-admin-actions-row'); if (adminRow) adminRow.style.display = 'none'; }\n\n        window.isStructuredListinoFileName = function(fileName) {\n            const nm = String(fileName || '').toLowerCase().trim();\n            return \/\\.(txt|csv|tsv|xlsx|pdf)$\/i.test(nm);\n        };\n\n\n\n        window.ensurePurchaseModal = function() {\n            let ov = gE('ti-purchase-ov');\n            if (ov) return ov;\n            ov = document.createElement('div');\n            ov.id = 'ti-purchase-ov';\n            ov.className = 'ti-modal-overlay';\n            ov.style.display = 'none';\n            ov.style.zIndex = '99990';\n            ov.style.alignItems = 'center';\n            ov.style.justifyContent = 'center';\n            ov.innerHTML = `\n                <div class=\"ti-modal ti-purchase-modal\" style=\"width:min(1460px,98vw);max-width:98vw;max-height:92vh;overflow:auto;background:#0f172a;color:#fff;border-radius:16px;padding:18px;box-shadow:0 18px 60px rgba(0,0,0,.45);margin:0 auto;\">\n                    <div style=\"display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px;\">\n                        <div>\n                            <h2 style=\"margin:0;font-size:22px;\">Acquisti<\/h2>\n                            <div style=\"font-size:14px;color:#cbd5e1;margin-top:4px;\">Inserisci manualmente le righe nella tabella ordine acquisto. L'import file resta disponibile come funzione aggiuntiva.<\/div>\n                        <\/div>\n                        <button type=\"button\" class=\"ti-btn\" style=\"background:#dc2626;color:#fff;\" onclick=\"window.closePurchaseManualFlow()\">X<\/button>\n                    <\/div>\n                    <div style=\"display:flex;gap:8px;flex-wrap:wrap;margin:10px 0 14px 0;\">\n                        <button type=\"button\" class=\"ti-btn\" style=\"background:#22c55e;color:#052e16;font-weight:700;\" onclick=\"window.addPurchaseManualRow()\">+ Aggiungi riga manuale<\/button>\n                        <button type=\"button\" class=\"ti-btn\" style=\"background:#f59e0b;color:#111827;font-weight:700;\" onclick=\"window.pickPurchaseImportFile()\">Import file acquisti<\/button>\n                        <button type=\"button\" class=\"ti-btn\" style=\"background:#38bdf8;color:#082f49;font-weight:700;\" onclick=\"window.purchaseSendToChat()\">Invia righe acquisto alla chat<\/button>\n                    <\/div>\n                    <div style=\"border:1px solid #334155;background:#111827;border-radius:12px;padding:10px;margin-bottom:12px;\">\n                        <label style=\"display:block;font-weight:700;margin-bottom:6px;\">Ricerca prodotti e servizi gi\u00e0 presenti nel database<\/label>\n                        <input id=\"ti-purchase-search\" type=\"text\" placeholder=\"Cerca prodotto o servizio...\" style=\"width:100%;box-sizing:border-box;padding:10px;border-radius:10px;border:1px solid #475569;background:#020617;color:#fff;font-size:15px;\" oninput=\"window.searchPurchaseCatalog(this.value)\">\n                        <div id=\"ti-purchase-search-results\" style=\"margin-top:8px;display:grid;gap:6px;\"><\/div>\n                    <\/div>\n                    <div style=\"overflow:auto;border:1px solid #334155;border-radius:12px;\">\n                        <table id=\"ti-purchase-table\" style=\"width:100%;border-collapse:collapse;min-width:1120px;background:#020617;\">\n                            <thead>\n                                <tr style=\"background:#1e293b;\">\n                                    <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:left;\">Voce<\/th>\n                                    <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:left;\">Tipo<\/th>\n                                    <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Q.t\u00e0<\/th>\n                                    <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Costo acquisto<\/th>\n                                    <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Prezzo vendita<\/th>\n                                    <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">IVA<\/th>\n                                    <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:left;\">Note<\/th>\n                                    <th style=\"padding:8px;border-bottom:1px solid #334155;\">Azioni<\/th>\n                                <\/tr>\n                            <\/thead>\n                            <tbody id=\"ti-purchase-body\"><\/tbody>\n                        <\/table>\n                    <\/div>\n                    <div id=\"ti-purchase-total\" style=\"margin-top:10px;text-align:right;font-weight:700;color:#bbf7d0;\"><\/div>\n                    <div style=\"display:flex;justify-content:center;gap:8px;margin-top:14px;\">\n                        <button type=\"button\" class=\"ti-btn\" style=\"background:#38bdf8;color:#082f49;font-weight:700;\" onclick=\"window.purchaseSendToChat()\">Conferma righe in chat<\/button>\n                        <button type=\"button\" class=\"ti-btn btn-close\" onclick=\"window.closePurchaseManualFlow()\">Chiudi<\/button>\n                    <\/div>\n                <\/div>`;\n            document.body.appendChild(ov);\n            return ov;\n        };\n\n        window.purchaseGetCatalogItems = function() {\n            const data = window.currentDbData || {};\n            const tables = (data && data.Tabelle) ? data.Tabelle : {};\n            const out = [];\n            Object.keys(tables).forEach(function(tn){\n                if (!\/(prodott|serviz)\/i.test(tn)) return;\n                const rows = Array.isArray(tables[tn]) ? tables[tn] : [];\n                rows.forEach(function(r, idx){\n                    if (!r || typeof r !== 'object') return;\n                    const isServ = \/(serviz)\/i.test(tn);\n                    const name = String(r.Prodotto || r.Servizio || r.Nome || r.Titolo || r.Descrizione || '').trim();\n                    if (!name) return;\n                    out.push({\n                        table: tn,\n                        index: idx,\n                        type: isServ ? 'S' : 'P',\n                        name: name,\n                        desc: String(r.Descrizione || r.Note || '').trim(),\n                        cost: parseFloat(String(r.Costo || r.Costo_acquisto || r.Prezzo || '0').replace(',','.')) || 0,\n                        sale: parseFloat(String(r.Prezzo || r.Prezzo_vendita || '0').replace(',','.')) || 0,\n                        iva: parseFloat(String(r.IVA_VAT || r.IVA || '22').replace(',','.')) || 22\n                    });\n                });\n            });\n            return out;\n        };\n\n        window.openPurchaseManualFlow = function() {\n            if (!window.isPurchaseRoleEnabled || !window.isPurchaseRoleEnabled(window.tiRole, window.tiUser)) {\n                window.tiAlert('Funzione Acquisti disponibile solo per ruoli abilitati: Amministratore, Configuratore, Responsabile, Operatore con ruolo Acquisti o SSGlobalAdmin.');\n                return;\n            }\n            const dbSel = gE('ti-ditta');\n            const dbVal = dbSel ? dbSel.value : '';\n            if (!dbVal || dbVal === 'NEW_DB') { window.tiAlert('Seleziona prima una ditta.'); return; }\n            const openIt = function(){\n                const ov = window.ensurePurchaseModal();\n                if (window.showPluginModal) window.showPluginModal(ov); else ov.style.display = 'flex';\n                if (!gE('ti-purchase-body') || !gE('ti-purchase-body').children.length) window.addPurchaseManualRow();\n                window.refreshPurchaseTotals();\n            };\n            if (!window.currentDbData || !window.currentDbData.Tabelle) {\n                const fd = new FormData();\n                fd.append('ti_action', 'ti_ai_get_db_data');\n                fd.append('db', dbVal);\n                window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd, tiShowTableReadWait:true, tiTableReadTitle:'\u23f3 Lettura prodotti e servizi', tiTableReadDetail:'Sto leggendo il database per preparare acquisti e ricerca.', tiTableReadItem:dbVal}).then(function(res){\n                    if (res && res.success) { window.currentDbData = res.data; openIt(); }\n                    else window.tiAlert((res && res.data && res.data.message) ? res.data.message : 'Errore lettura database.');\n                }).catch(function(err){ window.tiAlert('Errore lettura database: ' + err.message); });\n            } else {\n                openIt();\n            }\n        };\n\n        window.closePurchaseManualFlow = function() {\n            const ov = gE('ti-purchase-ov');\n            if (!ov) return;\n            if (window.closeModal) window.closeModal(ov); else ov.style.display = 'none';\n        };\n\n        window.addPurchaseManualRow = function(item) {\n            const body = gE('ti-purchase-body');\n            if (!body) return;\n            const tr = document.createElement('tr');\n            tr.className = 'ti-purchase-row';\n            const safe = item || {};\n            tr.innerHTML = `\n                <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-name\" value=\"${window.escapeHtml(safe.name || '')}\" placeholder=\"Prodotto o servizio\" style=\"width:100%;box-sizing:border-box;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><select class=\"ti-pur-type\" style=\"width:70px;padding:8px;border-radius:8px;background:#0f172a;color:#fff;border:1px solid #334155;\"><option value=\"P\" ${safe.type === 'S' ? '' : 'selected'}>P<\/option><option value=\"S\" ${safe.type === 'S' ? 'selected' : ''}>S<\/option><\/select><\/td>\n                <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-qty\" value=\"${safe.qty || '1'}\" inputmode=\"decimal\" style=\"width:80px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\" oninput=\"window.refreshPurchaseTotals()\"><\/td>\n                <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-cost\" value=\"${safe.cost ? window.fmtNum(safe.cost) : ''}\" inputmode=\"decimal\" style=\"width:120px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\" oninput=\"window.refreshPurchaseTotals()\"><\/td>\n                <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-sale\" value=\"${safe.sale ? window.fmtNum(safe.sale) : ''}\" inputmode=\"decimal\" style=\"width:120px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-iva\" value=\"${safe.iva || '22'}\" inputmode=\"decimal\" style=\"width:70px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-note\" value=\"${window.escapeHtml(safe.desc || '')}\" placeholder=\"Note\" style=\"width:100%;box-sizing:border-box;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                <td style=\"padding:6px;border-bottom:1px solid #1f2937;text-align:center;\"><button type=\"button\" class=\"ti-btn\" style=\"background:#dc2626;color:#fff;\" onclick=\"this.closest('tr').remove();window.refreshPurchaseTotals();\">X<\/button><\/td>`;\n            body.appendChild(tr);\n            window.refreshPurchaseTotals();\n        };\n\n        window.searchPurchaseCatalog = function(q) {\n            const box = gE('ti-purchase-search-results');\n            if (!box) return;\n            const query = String(q || '').trim().toLowerCase();\n            if (!query) { box.innerHTML = ''; return; }\n            const items = window.purchaseGetCatalogItems().filter(function(it){\n                return (it.name + ' ' + it.desc + ' ' + it.table).toLowerCase().indexOf(query) !== -1;\n            }).slice(0, 12);\n            if (!items.length) {\n                box.innerHTML = '<div style=\"color:#fca5a5;font-size:13px;\">Nessun prodotto o servizio trovato nel database.<\/div>';\n                return;\n            }\n            box.innerHTML = items.map(function(it, idx){\n                return `<button type=\"button\" class=\"ti-btn\" style=\"text-align:left;background:#1e293b;color:#fff;border:1px solid #334155;\" onclick=\"window.addPurchaseManualRow(window.purchaseSearchCache[${idx}])\"><b>${it.type} - ${window.escapeHtml(it.name)}<\/b><br><span style=\"font-size:12px;color:#cbd5e1;\">${window.escapeHtml(it.table)} \u00b7 costo ${window.fmtNum(it.cost)} \u00b7 prezzo vendita ${window.fmtNum(it.sale)}<\/span><\/button>`;\n            }).join('');\n            window.purchaseSearchCache = items;\n        };\n\n        window.refreshPurchaseTotals = function() {\n            let total = 0;\n            document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr){\n                const qty = window.parseQtyInput ? window.parseQtyInput((tr.querySelector('.ti-pur-qty') || {}).value || '0') : (parseFloat(String((tr.querySelector('.ti-pur-qty') || {}).value || '0').replace(',','.')) || 0);\n                const cost = parseFloat(String((tr.querySelector('.ti-pur-cost') || {}).value || '0').replace(\/\\.\/g,'').replace(',','.')) || 0;\n                total += qty * cost;\n            });\n            const out = gE('ti-purchase-total');\n            if (out) out.innerHTML = 'Totale acquisto indicativo: \u20ac ' + window.fmtNum(total);\n        };\n\n        window.purchaseCollectRows = function() {\n            const rows = [];\n            document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr){\n                const name = String((tr.querySelector('.ti-pur-name') || {}).value || '').trim();\n                if (!name) return;\n                rows.push({\n                    type: String((tr.querySelector('.ti-pur-type') || {}).value || 'P'),\n                    name: name,\n                    qty: String((tr.querySelector('.ti-pur-qty') || {}).value || '1').trim(),\n                    cost: String((tr.querySelector('.ti-pur-cost') || {}).value || '').trim(),\n                    sale: String((tr.querySelector('.ti-pur-sale') || {}).value || '').trim(),\n                    iva: String((tr.querySelector('.ti-pur-iva') || {}).value || '22').trim(),\n                    note: String((tr.querySelector('.ti-pur-note') || {}).value || '').trim()\n                });\n            });\n            return rows;\n        };\n\n        window.purchaseSendToChat = function() {\n            const rows = window.purchaseCollectRows();\n            if (!rows.length) { window.tiAlert('Inserisci almeno una riga acquisto.'); return; }\n            let txt = 'ACQUISTO - righe inserite manualmente\\\\n';\n            txt += 'Usa queste righe come tabella ordine acquisto. Il campo prezzo rappresenta il costo di acquisto; se presente riporta anche il prezzo di vendita nel prodotto.\\\\n\\\\n';\n            rows.forEach(function(r, i){\n                txt += (i+1) + '. ' + r.type + ' - ' + r.name + ' | Quantita: ' + r.qty + ' | Costo acquisto: ' + r.cost + ' | Prezzo vendita: ' + r.sale + ' | IVA: ' + r.iva + (r.note ? ' | Note: ' + r.note : '') + '\\\\n';\n            });\n            window.closePurchaseManualFlow();\n            if (window.addMsg) window.addMsg('user', txt);\n            const msg = gE('ti-msg');\n            if (msg) {\n                msg.value = txt;\n                if (gE('ti-send')) gE('ti-send').click();\n            }\n        };\n\n        window.pickPurchaseImportFile = function() {\n            window.targetUploadContext = 'purchase-import-file';\n            const fileIn = gE('ti-file-in');\n            if (fileIn) { fileIn.dataset.target = 'purchase-import-file'; fileIn.value = ''; setTimeout(() => fileIn.click(), 100); }\n        };\n\n        window.startPurchaseFileImport = function(fileUrl, fileName) {\n            const dbVal = gE('ti-ditta') ? gE('ti-ditta').value : '';\n            if (!dbVal || dbVal === 'NEW_DB') { window.tiAlert('Seleziona una ditta prima di importare acquisti.'); return; }\n            const prompt = 'Import acquisti da file. Prima usa la tabella ordine acquisto per anteprima e modifica manuale delle righe. Interpreta il file come acquisto: il campo Prezzo rappresenta il costo di acquisto; se presente un prezzo di vendita riportalo nel prodotto. Rendere disponibili ricerche su prodotti o servizi gi\u00e0 presenti.';\n            window.rememberLastFileContext(fileUrl, prompt, true);\n            window.lastUploadedFile = null;\n            if (window.addMsg) window.addMsg('user', prompt);\n            const fd = new FormData();\n            fd.append('ti_action', 'ti_ai_config_action');\n            fd.append('db', dbVal);\n            fd.append('mode', 'ai_ops');\n            fd.append('prompt', prompt);\n            fd.append('file_url', fileUrl);\n            let waitBubble = null;\n            try {\n                waitBubble = document.createElement('div');\n                waitBubble.className = 'ti-bubble ai';\n                waitBubble.innerHTML = window.getUserWaitingHtml('Import acquisti in corso...', 'Sto leggendo il file e preparo la tabella ordine acquisto modificabile.');\n                if (dom.chat) { dom.chat.appendChild(waitBubble); dom.chat.scrollTop = dom.chat.scrollHeight; }\n            } catch(e) {}\n            window.fetchJsonSafe((window.tiAjaxUrl || window.tiUrl), {method:'POST', body:fd, credentials:'same-origin'}).then(function(res){\n                if (waitBubble && waitBubble.remove) waitBubble.remove();\n                if (res && res.success) {\n                    const rawReply = ((res.data || {}).reply || '').trim();\n                    const cleanReply = rawReply.replace(\/\\[NO_VOICE\\]\/g, '').trim();\n                    window.addMsg('ai', window.formatTable(cleanReply || 'Import acquisti pronto.'), cleanReply || 'Import acquisti pronto.');\n                } else {\n                    window.addMsg('ai', '\u26a0\ufe0f ' + ((res && res.data && res.data.message) ? res.data.message : 'Import acquisti non riuscito'));\n                }\n            }).catch(function(err){\n                if (waitBubble && waitBubble.remove) waitBubble.remove();\n                window.addMsg('ai', '\u26a0\ufe0f Errore import acquisti: ' + ((err && err.message) ? err.message : 'errore sconosciuto'));\n            });\n        };\n\n\n        window.startAdminGenericFileAI = function(fileUrl, fileName) {\n            const dbVal = gE('ti-ditta') ? gE('ti-ditta').value : '';\n            const prompt = 'Ho allegato un file. Analizza il contenuto del file e interpretalo al meglio per aiutarmi. Se vuoi propormi azioni sul file, chiedimi cosa devo farne prima di modificare il database.';\n            if (!dbVal || dbVal === 'NEW_DB') { window.tiAlert('Seleziona una ditta prima di analizzare il file.'); return; }\n            window.rememberLastFileContext(fileUrl, prompt, false);\n            window.lastUploadedFile = null;\n            if (window.addMsg) window.addMsg('user', prompt);\n\n            let waitBubble = null;\n            try {\n                waitBubble = document.createElement('div');\n                waitBubble.className = 'ti-bubble ai';\n                waitBubble.innerHTML = window.getUserWaitingHtml('Analisi file in corso...', 'Sto leggendo il file allegato e preparo la risposta.');\n                if (dom.chat) { dom.chat.appendChild(waitBubble); dom.chat.scrollTop = dom.chat.scrollHeight; }\n            } catch(e) {}\n\n            let chatHist = [];\n            try {\n                const bubbles = dom.chat ? dom.chat.querySelectorAll('.ti-bubble') : [];\n                const startIdx = Math.max(0, (bubbles ? bubbles.length : 0) - 8);\n                for (let i = startIdx; i < (bubbles ? bubbles.length : 0); i++) {\n                    const bubble = bubbles[i];\n                    const role = bubble.classList.contains('user') ? 'user' : 'assistant';\n                    const clone = bubble.cloneNode(true);\n                    const tw = clone.querySelector('.ti-table-wrap'); if (tw) tw.remove();\n                    const msgTxt = (clone.innerText || '').trim();\n                    if (msgTxt && !msgTxt.includes('Fammi pensare...')) chatHist.push({role: role, content: msgTxt});\n                }\n            } catch(e) {}\n\n            const fd = new FormData();\n            fd.append('ti_action', 'ti_ai_chat_start');\n            fd.append('db', dbVal);\n            fd.append('analyze_file_only', '1');\n            fd.append('text', prompt + ` [FILE_ALLEGATO: ${fileUrl}]`);\n            fd.append('history_context', JSON.stringify(chatHist));\n            fd.append('analyze_file_only', '1');\n            fd.append('analyze_file_only', '1');\n\n            window.fetchJsonSafe((window.tiAjaxUrl || window.tiUrl), {method:'POST', body:fd, credentials:'same-origin'}).then(function(res){\n                if (waitBubble && waitBubble.remove) waitBubble.remove();\n                if (res && res.success) {\n                    const rawReply = ((res.data || {}).reply || '').trim();\n                    const cleanReply = rawReply.replace(\/\\[NO_VOICE\\]\/g, '').trim();\n                    window.addMsg('ai', window.formatTable(cleanReply || 'Analisi completata.'), cleanReply || 'Analisi completata.');\n                    if (res.data && res.data.needs_reload) setTimeout(function(){ location.reload(); }, 1200);\n                } else {\n                    const msg = (res && res.data && res.data.message) ? res.data.message : 'Analisi file non riuscita';\n                    window.addMsg('ai', '\u26a0\ufe0f ' + msg);\n                }\n            }).catch(function(err){\n                if (waitBubble && waitBubble.remove) waitBubble.remove();\n                window.addMsg('ai', '\u26a0\ufe0f Errore analisi file: ' + ((err && err.message) ? err.message : 'errore sconosciuto'));\n            });\n        };\n\n        window.askAdminUploadedFileAction = function(fileUrl, fileName) {\n            const looksStructured = window.isStructuredListinoFileName(fileName || '');\n            if (!looksStructured) {\n                window.startAdminGenericFileAI(fileUrl, fileName);\n                return;\n            }\n            const msg = `Il file caricato pu\u00f2 essere trattato come <b>listino<\/b> da importare nel database oppure come <b>documento generico<\/b> da leggere con la AI.<br><br>Premi <b>IMPORTA LISTINO<\/b> per creare l'anteprima di import.<br>Premi <b>LEGGI FILE<\/b> per trattarlo come file utente e decidere dopo cosa farne.`;\n            window.tiConfirmYesNoAction(msg,\n                function(){ window.startAdminListinoImport(fileUrl, fileName); },\n                function(){ window.startAdminGenericFileAI(fileUrl, fileName); },\n                'IMPORTA LISTINO',\n                'LEGGI FILE'\n            );\n        };\n\n        window.startAdminListinoImport = function(fileUrl, fileName) {\n            const dbVal = gE('ti-ditta') ? gE('ti-ditta').value : '';\n            if (!dbVal || dbVal === 'NEW_DB') { window.tiAlert('Seleziona una ditta prima di importare il listino.'); return; }\n            const prompt = 'Tratta il file allegato come listino da importare nel database della ditta, preferibilmente per creare prodotti. Se il file e PDF, ricerca le righe utili, proponi una prima struttura di import prodotti modificabile e, se trovi immagini disponibili per le righe, chiedi se collegarle ai singoli prodotti. Mostra sempre l anteprima finale prima del salvataggio.';\n            window.rememberLastFileContext(fileUrl, prompt, true);\n            window.lastUploadedFile = null;\n            if (window.addMsg) window.addMsg('user', prompt);\n\n            let waitBubble = null;\n            try {\n                waitBubble = document.createElement('div');\n                waitBubble.className = 'ti-bubble ai';\n                waitBubble.innerHTML = window.getUserWaitingHtml('Avvio import listino in corso...', 'Sto interpretando il listino e preparo l anteprima.');\n                if (dom.chat) { dom.chat.appendChild(waitBubble); dom.chat.scrollTop = dom.chat.scrollHeight; }\n            } catch(e) {}\n\n            const fd = new FormData();\n            fd.append('ti_action', 'ti_ai_config_action');\n            fd.append('db', dbVal);\n            fd.append('mode', 'ai_ops');\n            fd.append('prompt', prompt);\n            fd.append('file_url', fileUrl);\n\n            window.fetchJsonSafe((window.tiAjaxUrl || window.tiUrl), {method:'POST', body:fd, credentials:'same-origin'}).then(function(res){\n                if (waitBubble && waitBubble.remove) waitBubble.remove();\n                if (res && res.success) {\n                    const rawReply = ((res.data || {}).reply || '').trim();\n                    const noVoice = !!((res.data || {}).no_voice) || rawReply.indexOf('[NO_VOICE]') !== -1;\n                    const cleanReply = rawReply.replace(\/\\[NO_VOICE\\]\/g, '').trim();\n                    window.addMsg('ai', window.formatTable(cleanReply || 'Import pronto.'), cleanReply || 'Import pronto.');\n                    if (!noVoice && cleanReply) window.speakText(cleanReply);\n                    if (res.data && res.data.needs_reload) setTimeout(function(){ location.reload(); }, 1200);\n                } else {\n                    const msg = (res && res.data && res.data.message) ? res.data.message : 'Import non riuscito';\n                    window.addMsg('ai', '\u26a0\ufe0f ' + msg);\n                }\n            }).catch(function(err){\n                if (waitBubble && waitBubble.remove) waitBubble.remove();\n                window.addMsg('ai', '\u26a0\ufe0f Errore import listino: ' + ((err && err.message) ? err.message : 'errore sconosciuto'));\n            });\n        };\n\n\n        window.prepareUploadFileForAI = function(file) {\n            return new Promise(function(resolve){\n                try {\n                    if (!(file instanceof File)) { resolve(file); return; }\n                    const fileName = String(file.name || '').toLowerCase();\n                    const mimeType = String(file.type || '').toLowerCase();\n                    const isImage = mimeType.indexOf('image\/') === 0 || \/\\.(jpg|jpeg|png|webp|gif|heic|heif)$\/i.test(fileName);\n                    if (!isImage) { resolve(file); return; }\n\n                    const reader = new FileReader();\n                    reader.onerror = function(){ resolve(file); };\n                    reader.onload = function(ev) {\n                        const img = new Image();\n                        img.onerror = function(){ resolve(file); };\n                        img.onload = function() {\n                            try {\n                                const maxSide = 1600;\n                                const srcW = img.naturalWidth || img.width || 0;\n                                const srcH = img.naturalHeight || img.height || 0;\n                                if (!srcW || !srcH) { resolve(file); return; }\n                                const scale = Math.min(1, maxSide \/ Math.max(srcW, srcH));\n                                const outW = Math.max(1, Math.round(srcW * scale));\n                                const outH = Math.max(1, Math.round(srcH * scale));\n                                const canvas = document.createElement('canvas');\n                                canvas.width = outW;\n                                canvas.height = outH;\n                                const ctx = canvas.getContext('2d');\n                                if (!ctx) { resolve(file); return; }\n                                ctx.drawImage(img, 0, 0, outW, outH);\n                                canvas.toBlob(function(blob){\n                                    if (!blob || blob.size >= file.size) { resolve(file); return; }\n                                    const baseName = String(file.name || 'camera').replace(\/\\.[^.]+$\/, '') || 'camera';\n                                    resolve(new File([blob], baseName + '.jpg', { type: 'image\/jpeg' }));\n                                }, 'image\/jpeg', 0.82);\n                            } catch(e) {\n                                resolve(file);\n                            }\n                        };\n                        img.src = String(ev.target && ev.target.result ? ev.target.result : '');\n                    };\n                    reader.readAsDataURL(file);\n                } catch(e) {\n                    resolve(file);\n                }\n            });\n        };\n\n        window.startUserUploadedFileAI = function(fileUrl, fileName) {\n            const dbVal = gE('ti-ditta') ? gE('ti-ditta').value : '';\n            const lowerName = String(fileName || '').toLowerCase();\n            const isImg = \/\\.(jpg|jpeg|png|webp|gif|heic|heif)$\/i.test(lowerName) || \/ti_action=ti_ai_get_image\/i.test(String(fileUrl || ''));\n            const prompt = isImg\n                ? 'Ho allegato una foto. Analizzala e leggi gli eventuali dati o testi visibili per aiutarmi.'\n                : 'Ho allegato un documento. Leggilo ed estrai i dati utili per aiutarmi.';\n\n            if (!dbVal || dbVal === 'NEW_DB') { window.tiAlert('Seleziona una ditta prima di analizzare il file.'); return; }\n\n            window.rememberLastFileContext(fileUrl, prompt, false);\n            window.lastUploadedFile = null;\n            if (window.addMsg) window.addMsg('user', prompt);\n\n            let waitBubble = null;\n            try {\n                waitBubble = document.createElement('div');\n                waitBubble.className = 'ti-bubble ai';\n                waitBubble.innerHTML = window.getUserWaitingHtml('Analisi file in corso...', 'Sto leggendo il file allegato e preparo la risposta.');\n                if (dom.chat) { dom.chat.appendChild(waitBubble); dom.chat.scrollTop = dom.chat.scrollHeight; }\n            } catch(e) {}\n\n            let chatHist = [];\n            try {\n                const bubbles = dom.chat ? dom.chat.querySelectorAll('.ti-bubble') : [];\n                const startIdx = Math.max(0, (bubbles ? bubbles.length : 0) - 8);\n                for (let i = startIdx; i < (bubbles ? bubbles.length : 0); i++) {\n                    const bubble = bubbles[i];\n                    const role = bubble.classList.contains('user') ? 'user' : 'assistant';\n                    const clone = bubble.cloneNode(true);\n                    const tw = clone.querySelector('.ti-table-wrap'); if (tw) tw.remove();\n                    const msgTxt = (clone.innerText || '').trim();\n                    if (msgTxt && !msgTxt.includes('Fammi pensare...')) chatHist.push({role: role, content: msgTxt});\n                }\n            } catch(e) {}\n\n            const fd = new FormData();\n            fd.append('ti_action', 'ti_ai_chat_start');\n            fd.append('db', dbVal);\n            fd.append('analyze_file_only', '1');\n            fd.append('reset_flow_for_file_analysis', '1');\n            fd.append('text', prompt + ` [FILE_ALLEGATO: ${fileUrl}]`);\n            fd.append('history_context', JSON.stringify(chatHist));\n\n            window.fetchJsonSafe((window.tiAjaxUrl || window.tiUrl), {method:'POST', body:fd, credentials:'same-origin'}).then(function(res){\n                if (waitBubble && waitBubble.remove) waitBubble.remove();\n                if (res && res.success) {\n                    const rawReply = ((res.data || {}).reply || '').trim();\n                    const cleanReply = rawReply.replace(\/\\[NO_VOICE\\]\/g, '').trim();\n                    window.addMsg('ai', window.formatTable(cleanReply || 'Analisi completata.'), cleanReply || 'Analisi completata.');\n                    if (res.data && res.data.needs_reload) setTimeout(function(){ location.reload(); }, 1200);\n                } else {\n                    const msg = (res && res.data && res.data.message) ? res.data.message : 'Analisi file non riuscita';\n                    window.addMsg('ai', '\u26a0\ufe0f ' + msg);\n                }\n            }).catch(function(err){\n                if (waitBubble && waitBubble.remove) waitBubble.remove();\n                window.addMsg('ai', '\u26a0\ufe0f Errore analisi file: ' + ((err && err.message) ? err.message : 'errore sconosciuto'));\n            });\n        };\n\n        window.handleFileUpload = function(file, typeName) {\n            let tbl = 'Chat'; const dbVal = gE('ti-ditta').value;\n            if(!dbVal) { window.tiAlert(\"Seleziona una ditta prima di caricare file.\"); return; }\n            window.prepareUploadFileForAI(file).then(function(uploadFile){\n                const realFile = (uploadFile instanceof File) ? uploadFile : file;\n                const fd=new FormData(); fd.append('ti_action','ti_ai_media_upload'); fd.append('db',dbVal); fd.append('file', realFile, realFile.name || file.name || 'upload'); fd.append('tbl', tbl);\n                if(window.addMsg) window.addMsg('ai',\"\u23f3 Analisi file in corso...\"); \n                window.fetchJsonSafe(window.tiUrl,{method:'POST',body:fd}).then(d=>{\n                    if(d.success) { \n                        window.lastUploadedFile = d.data && d.data.url ? d.data.url : (d.url ? d.url : ''); \n                        window.startUserUploadedFileAI(window.lastUploadedFile, realFile.name || file.name || ''); \n                    } else { if(window.addMsg) window.addMsg('ai',`\u26a0\ufe0f Errore caricamento.`); }\n                }).catch(function(err){\n                    if(window.addMsg) window.addMsg('ai', '\u26a0\ufe0f Errore caricamento file: ' + ((err && err.message) ? err.message : 'errore sconosciuto'));\n                });\n            });\n        };\n\n        if(dom.btnNew) dom.btnNew.onclick = () => { window.clearLastFileContext(); window.resetClarifyCount(); const fd = new FormData(); fd.append('ti_action', 'ti_ai_reset_flow'); fetch(window.tiUrl, {method:'POST', body:fd}).then(() => { if(dom.chat) dom.chat.innerHTML=''; window.updCtx(); }); }; \n        if(gE('ti-cam')) gE('ti-cam').onclick = (e) => { e.preventDefault(); window.openCamModal(); };\n        const camIn = gE('ti-cam-in'); if(camIn) camIn.onchange = (e) => { if(e.target.files.length) window.handleFileUpload(e.target.files[0], 'Immagine'); };\n        if(gE('ti-file')) gE('ti-file').onclick = (e) => { e.preventDefault(); window.showUploadChoice('chat'); }; \n        if(gE('ti-products-btn')) gE('ti-products-btn').onclick = () => { if(gE('ti-msg')) { window.lastUploadedFile = null; if (window.lastFileContext) { window.lastFileContext.active = false; window.lastFileContext.awaitingReuseChoice = false; window.lastFileContext.pendingReusePrompt = ''; } gE('ti-msg').value = (String(window.currLang || 'it').toLowerCase() === 'en') ? 'Show all available products in order table' : 'Mostra tutti i prodotti disponibili in tabella ordine'; gE('ti-send').click(); } };\n        if(gE('ti-services-btn')) gE('ti-services-btn').onclick = () => { if(gE('ti-msg')) { window.lastUploadedFile = null; if (window.lastFileContext) { window.lastFileContext.active = false; window.lastFileContext.awaitingReuseChoice = false; window.lastFileContext.pendingReusePrompt = ''; } gE('ti-msg').value = (String(window.currLang || 'it').toLowerCase() === 'en') ? 'Show all available services in order table' : 'Mostra tutti i servizi disponibili in tabella ordine'; gE('ti-send').click(); } };\n        if (window.ensureActivitiesButton) window.ensureActivitiesButton();\n        if(gE('ti-activities-btn')) gE('ti-activities-btn').onclick = window.tiOpenUserActivities;\n        if (window.updateActivitiesButtonVisibility) {\n            window.updateActivitiesButtonVisibility();\n            setTimeout(window.updateActivitiesButtonVisibility, 250);\n            setTimeout(window.updateActivitiesButtonVisibility, 900);\n        }\n        \n        const fileIn = gE('ti-file-in');\n        if(fileIn) fileIn.onchange = window.handleFileSelect;\n\n        window.invalidClarifyCount = 0;\n        window.clarifyWarnTimer = null;\n        window.clarifyCloseTimer = null;\n        window.pendingClarifyClosure = false;\n        window.cancelClarifyClosure = function() {\n            if (window.clarifyWarnTimer) { clearTimeout(window.clarifyWarnTimer); window.clarifyWarnTimer = null; }\n            if (window.clarifyCloseTimer) { clearTimeout(window.clarifyCloseTimer); window.clarifyCloseTimer = null; }\n            window.pendingClarifyClosure = false;\n        };\n        window.resetClarifyCount = function() {\n            window.invalidClarifyCount = 0;\n            window.cancelClarifyClosure();\n        };\n        window.startFreshChatAfterClarifyLimit = function() {\n            window.cancelClarifyClosure();\n            window.invalidClarifyCount = 0;\n            if (window.doLogout) {\n                window.doLogout(true);\n                return;\n            }\n            const fd = new FormData(); fd.append('ti_action', 'ti_ai_reset_flow');\n            fetch(window.tiUrl, {method:'POST', body:fd}).finally(() => {\n                if(dom.chat) dom.chat.innerHTML = '';\n                window.updCtx();\n            });\n        };\n        window.scheduleClarifyClosure = function() {\n            window.cancelClarifyClosure();\n            window.pendingClarifyClosure = true;\n            window.clarifyWarnTimer = setTimeout(() => {\n                if (!window.pendingClarifyClosure) return;\n                const warn = \"Se non ricevo istruzioni chiuder\u00f2 la sessione a breve.\";\n                window.addMsg('ai', warn, warn);\n                window.speakText(\"Se non ricevo istruzioni chiuder\u00f2 la sessione a breve.\");\n                window.clarifyCloseTimer = setTimeout(() => {\n                    if (!window.pendingClarifyClosure) return;\n                    window.startFreshChatAfterClarifyLimit();\n                }, 60000);\n            }, 60000);\n        };\n        window.handleClarifyReply = function(replyText, rawSource, noVoice) {\n            const safeReply = String(replyText || 'Mi dispiace, non ho capito bene la richiesta. Puoi scriverla in un altro modo o aggiungere un dettaglio in pi\u00f9?').trim();\n            window.invalidClarifyCount = (window.invalidClarifyCount || 0) + 1;\n            window.addMsg('ai', window.formatTable(safeReply), rawSource || safeReply);\n            if (!noVoice) window.speakText(safeReply);\n            if (window.invalidClarifyCount >= 3) {\n                window.scheduleClarifyClosure();\n            }\n            return false;\n        };\n\n        if(gE('ti-copy')) gE('ti-copy').onclick = () => { if(dom.chat) { navigator.clipboard.writeText(dom.chat.innerText).then(() => { window.pendingEmail = 'manual'; window.addMsg('ai', \"Chat copiata. A quale email vuoi inviarla?\"); }); } };\n\n        \/\/ v30.9.210 - invio rapido robusto per pulsanti chat generati dinamicamente (booking avanti\/indietro, OK\/X).\n        window.tiSendQuickReply = function(text, label) {\n            const msgEl = document.getElementById('ti-msg');\n            const sendBtn = document.getElementById('ti-send');\n            const quickText = String(text || '').trim();\n            if (!quickText || !sendBtn) return false;\n            window.tiQuickReplyText = quickText;\n            if (label && String(label).trim() && String(label).trim() !== quickText) {\n                window.tiHiddenSendText = quickText;\n                window.tiHiddenSendLabel = String(label).trim();\n                window.tiQuickReplyText = '';\n            }\n            if (msgEl) msgEl.value = '';\n            try { sendBtn.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, view:window})); }\n            catch(e) { try { sendBtn.click(); } catch(_e) {} }\n            return false;\n        };\n        if (!window.tiBookingNavDelegated210) {\n            window.tiBookingNavDelegated210 = true;\n            document.addEventListener('click', function(ev) {\n                const btn = ev.target && ev.target.closest ? ev.target.closest('[data-ti-booking-nav]') : null;\n                if (!btn) return;\n                ev.preventDefault();\n                ev.stopPropagation();\n                const cmd = String(btn.getAttribute('data-ti-booking-nav') || '').trim();\n                if (cmd && window.tiSendQuickReply) window.tiSendQuickReply(cmd, btn.textContent || cmd);\n            }, true);\n        }\n\n        if(dom.send) dom.send.onclick = () => {\n            if(!dom.msg) return;\n            const rawInput = (dom.msg.value || '');\n            let txt = rawInput.trim();\n            if (window.tiQuickReplyText) {\n                txt = String(window.tiQuickReplyText || '').trim();\n                window.tiQuickReplyText = '';\n            }\n            let displayTxt = txt;\n            let textToSend = txt;\n            if (window.tiHiddenSendText) {\n                textToSend = String(window.tiHiddenSendText || '').trim();\n                displayTxt = String(window.tiHiddenSendLabel || 'Conferma ordine').trim();\n                window.tiHiddenSendText = '';\n                window.tiHiddenSendLabel = '';\n            }\n            if(!textToSend) return;\n            if (window.cancelClarifyClosure) window.cancelClarifyClosure();\n            if (window.idleAwaitingChoice) {\n                const idleAns = String(textToSend || '').trim().toLowerCase();\n                if (\/^(no|n)$\/i.test(idleAns)) {\n                    window.idleAwaitingChoice = false;\n                    window.idleForceClosing = true;\n                    if (window.clearIdleTimers) window.clearIdleTimers();\n                    window.addMsg('user', displayTxt || textToSend);\n                    dom.msg.value = '';\n                    if(window.addMsg) window.addMsg('ai', '\ud83d\udd12 Sessione chiusa su richiesta. Avvio una nuova chat.');\n                    window.speakText('Sessione chiusa su richiesta. Avvio una nuova chat.');\n                    if (window.forceSessionCloseNow) window.forceSessionCloseNow(); else if (window.doLogout) window.doLogout(true);\n                    return;\n                }\n                if (\/^(si|s\u00ec|s|yes|ok|continua|continuare)$\/i.test(idleAns)) {\n                    window.idleAwaitingChoice = false;\n                    window.idleForceClosing = false;\n                    window.idleLastUserActivityAt = Date.now();\n                    window.idleFirstWarningAt = 0;\n                    window.idleFinalWarningAt = 0;\n                    window.idleCloseCheckAt = 0;\n                    if (window.clearIdleTimers) window.clearIdleTimers();\n                    if (window.scheduleIdleFirstWarning) window.scheduleIdleFirstWarning();\n                    window.addMsg('user', displayTxt || textToSend);\n                    dom.msg.value = '';\n                    if(window.addMsg) window.addMsg('ai', 'Ok, continuo la chat.');\n                    window.speakText('Ok, continuo la chat.');\n                    return;\n                }\n            }\n            const dittaInput = gE('ti-ditta'); const dbVal = dittaInput ? dittaInput.value : '';\n            const lastImportBubble = dom.chat ? Array.from(dom.chat.querySelectorAll('.ti-bubble.ai')).reverse().find(b => b.querySelector('.ti-import-map-select, .ti-import-group-target, #ti-import-price-formula, .ti-import-pdf-image-link')) : null;\n            if (lastImportBubble && \/^(ok|conferma|procedi|salva|importa|vai)$\/i.test(txt)) {\n                const mapping = window.collectImportMappingFromUI(lastImportBubble) || window.tiConfigImportLastMapping || {};\n                window.addMsg('user', txt); dom.msg.value='';\n                let b = document.createElement('div'); b.className='ti-bubble ai'; b.innerHTML = window.getUserWaitingHtml('Importazione in corso...', 'Sto salvando i dati confermati nel database.'); if(dom.chat){ dom.chat.appendChild(b); dom.chat.scrollTop=dom.chat.scrollHeight; }\n                const fdImp = new FormData();\n                fdImp.append('action','ti_ai_config_action');\n                fdImp.append('ti_action','ti_ai_config_action');\n                fdImp.append('db', dbVal);\n                fdImp.append('mode', 'confirm_import_preview');\n                fdImp.append('prompt', txt);\n                if (window.tiConfigImportToken) fdImp.append('import_token', window.tiConfigImportToken);\n                fdImp.append('mapping_json', JSON.stringify(mapping));\n                window.postFormDataJsonSafe(window.tiUrl, fdImp, {tiNoLongProcessSignal:true, cache:'no-store'}).then(res=>{\n                    if (b) b.remove();\n                    if (res.success) {\n                        const rawReply = res.data.reply || '';\n                        const noVoice = !!(res.data && res.data.no_voice) || rawReply.includes('[NO_VOICE]');\n                        const cleanReply = rawReply.replace(\/\\[NO_VOICE\\]\/g, '').trim();\n                        window.addMsg('ai', cleanReply);\n                        window.restoreLastUserPromptText();\n                        if (!noVoice) window.speakText(cleanReply);\n                        if (res.data.needs_reload) { window.tiConfigImportToken = null; setTimeout(() => { location.reload(); }, 1200); }\n                    } else {\n                        window.addMsg('ai', '\u26a0\ufe0f Errore import: ' + ((res.data && res.data.message) ? res.data.message : 'Sconosciuto'));\n                    }\n                }).catch((e)=>{ if (b) b.remove(); window.addMsg('ai', '\u26a0\ufe0f ' + e.message); });\n                return;\n            }\n\n            if(window.pendingEmail === 'manual') {\n                window.addMsg('ai', \"\u23f3 Invio email in corso...\");\n                const fd=new FormData(); fd.append('ti_action','ti_ai_send_email'); fd.append('db',dbVal); fd.append('email',txt); fd.append('history',dom.chat.innerText);\n                fetch(window.tiUrl,{method:'POST',body:fd}).then(r=>r.json()).then(res=>{ window.addMsg('ai',res.data.reply, res.data.reply); window.speakText(res.data.reply); });\n                window.pendingEmail=false; dom.msg.value=''; return;\n            }\n\n            const txtNorm = txt.toLowerCase();\n            if (window.lastFileContext && window.lastFileContext.awaitingReuseChoice) {\n                if (\/^(si|s\u00ec|yes|ok|ripeti|riutilizza)$\/i.test(txt)) {\n                    txt = window.lastFileContext.pendingReusePrompt || window.lastFileContext.lastActionPrompt || 'Importa di nuovo i dati dal file caricato.';\n                    window.lastUploadedFile = window.lastFileContext.url;\n                    window.lastFileContext.awaitingReuseChoice = false;\n                    window.lastFileContext.pendingReusePrompt = '';\n                    dom.msg.value = txt;\n                } else if (\/^(no|annulla|x)$\/i.test(txt)) {\n                    window.clearLastFileContext();\n                    window.addMsg('ai', \"Ok, non riutilizzo l'ultimo file. Puoi caricarne un altro quando vuoi.\");\n                    dom.msg.value='';\n                    return;\n                }\n            } else if (!window.lastUploadedFile && window.lastFileContext && window.lastFileContext.url) {\n                const isImportFollowup = window.isImportFollowupText(txtNorm);\n                const asksAboutLastFile = \/(import|reimport|file caricat|ultimo file|riutilizz|ripeti l'ultima azione|ultima azione)\/i.test(txtNorm);\n                if (asksAboutLastFile && !\/^(1|2|3|ok|si|s\u00ec|yes|no|x|annulla)$\/i.test(txt)) {\n                    if (window.askReuseLastFile(txt)) { dom.msg.value=''; return; }\n                } else if (window.lastFileContext.active && isImportFollowup) {\n                    window.lastUploadedFile = window.lastFileContext.url;\n                }\n            }\n\n            const asksCatalogOnly = \/\b(mostra|vedi|tabella|ordine|prodotti|servizi|catalogo|elenco|lista)\b\/i.test(txt) && !\/\b(file|allegat|pdf|document|referto|immagine|listino)\b\/i.test(txt);\n            if (asksCatalogOnly) {\n                window.lastUploadedFile = null;\n                if (window.lastFileContext) {\n                    window.lastFileContext.active = false;\n                    window.lastFileContext.awaitingReuseChoice = false;\n                    window.lastFileContext.pendingReusePrompt = '';\n                }\n            }\n            if(window.lastUploadedFile) { textToSend += ` [FILE_ALLEGATO: ${window.lastUploadedFile}]`; window.lastUploadedFile = null; }\n            window.lastUserPromptText = displayTxt;\n            window.addMsg('user',displayTxt); dom.msg.value='';\n            \n            let chatHist = []; const bubbles = dom.chat.querySelectorAll('.ti-bubble'); let startIdx = Math.max(0, bubbles.length - 8); \n            for(let i=startIdx; i<bubbles.length; i++) {\n                let b = bubbles[i]; let role = b.classList.contains('user') ? 'user' : 'assistant'; let clone = b.cloneNode(true);\n                let tw = clone.querySelector('.ti-table-wrap'); if(tw) tw.remove(); \n                let msgTxt = clone.innerText.trim(); if(msgTxt && !msgTxt.includes(\"Fammi pensare...\")) { chatHist.push({role: role, content: msgTxt}); }\n            }\n\n            const fd=new FormData(); fd.append('ti_action','ti_ai_chat_start'); fd.append('text',textToSend); fd.append('db',dbVal); fd.append('history_context', JSON.stringify(chatHist));\n\n            const tiInfoWait196 = window.tiIsInfoVerificationRequest ? window.tiIsInfoVerificationRequest(textToSend) : false;\n            let b = document.createElement('div');\n            b.className = 'ti-bubble ai' + (tiInfoWait196 ? ' ti-info-wait-bubble' : '');\n            b.innerHTML = tiInfoWait196\n                ? window.getInfoVerificationWaitingHtml('\u23f3 Verifica informazioni in corso...', 'Sto elaborando la richiesta e verifico le informazioni disponibili.')\n                : window.getUserWaitingHtml('Fammi pensare...', 'Sto elaborando la richiesta e verifico i dati disponibili.');\n            if(dom.chat){ dom.chat.appendChild(b); dom.chat.scrollTop=dom.chat.scrollHeight; }\n            \n            fetch(window.tiUrl,{method:'POST',body:fd}).then(r=> {\n                if (r.status === 413) throw new Error(\"File\/Testo troppo grande per i limiti del server (Errore 413).\"); if (!r.ok) throw new Error(\"Errore Server \" + r.status); return r.json();\n            }).then(res=>{\n                if(b) b.remove();\n                if(res.success) {\n                    if (res.data && (res.data.maintenance_locked || res.data.show_maintenance_popup || res.data.force_maintenance_popup || res.data.code === 'maintenance_active')) {\n                        const maint = res.data.maintenance || {active:true, message:(res.data.message || '')};\n                        const maintState = {\n                            active: true,\n                            maintenance: Object.assign({active:true}, maint),\n                            connected: res.data.connected || {count:0, users:[]},\n                            can_configure: !!res.data.can_configure,\n                            database_protected: false,\n                            message: res.data.message || (maint && maint.message) || ''\n                        };\n                        if (window.applyMaintenanceStatus) window.applyMaintenanceStatus(maintState);\n                        if (window.showMandatoryMaintenancePopup) window.showMandatoryMaintenancePopup(maintState);\n                        const rawMaintReply = res.data.reply || 'Servizio temporaneamente in manutenzione.';\n                        const formattedMaintReply = window.formatTable ? window.formatTable(rawMaintReply) : rawMaintReply;\n                        window.addMsg('ai', formattedMaintReply, rawMaintReply);\n                        if (window.restoreLastUserPromptText) window.restoreLastUserPromptText();\n                        return;\n                    }\n                    if(res.data.clear_chat && dom.chat) dom.chat.innerHTML = '';\n                    if(res.data.needs_reload) {\n                        window.clearLastFileContext();\n                        setTimeout(() => { location.reload(); }, 2500);\n                    }\n                    if(res.data && res.data.admin_popup) {\n                        if (window.showPrivilegedCommunicationPopup) window.showPrivilegedCommunicationPopup(res.data.admin_popup, 'Comunicazione amministrativa', {text: (window.tiPlainTextFromHtml ? window.tiPlainTextFromHtml(res.data.admin_popup) : String(res.data.admin_popup || ''))});\n                        else if (window.tiAlert) window.tiAlert(res.data.admin_popup);\n                    }\n                    \n                    if(res.data.reply && res.data.reply.includes('[UPDATE_DB]')) {\n                        let aiReplyText = res.data.reply;\n                        let start = aiReplyText.indexOf('[UPDATE_DB]') + 11;\n                        let end = aiReplyText.indexOf('[\/UPDATE_DB]');\n                        if (end !== -1 && end > start) {\n                            let jsonStr = aiReplyText.substring(start, end);\n                            let cleanJson = jsonStr.replace(\/```json|```\/ig, '').trim();\n                            try {\n                                let newTabelle = JSON.parse(cleanJson);\n                                if (typeof newTabelle === 'object') {\n                                    let currentData = window.currentDbData || { Tabelle: {} };\n                                    \n                                    let incTabs = newTabelle.Tabelle || newTabelle;\n                                    for(let t in incTabs) {\n                                        if (!currentData.Tabelle[t]) currentData.Tabelle[t] = [];\n                                        currentData.Tabelle[t] = currentData.Tabelle[t].concat(incTabs[t]);\n                                    }\n\n                                    const fdUpdate = new FormData();\n                                    fdUpdate.append('ti_action', 'ti_ai_chat_start');\n                                    fdUpdate.append('db', dbVal);\n                                    fdUpdate.append('text', \"FULL_DB_UPDATE_B64|||\" + utoa(JSON.stringify(currentData)));\n                                    fdUpdate.append('db_version', (currentData && currentData.__db_version) ? currentData.__db_version : '');\n                                    fetch(window.tiUrl, {method:'POST', body:window.ensureAjaxActionField(fdUpdate)}).then(ru=>ru.json()).then(du=>{\n                                        if(du.success) {\n                                            if (currentData && du.data && du.data.db_version) currentData.__db_version = du.data.db_version;\n                                            window.clearLastFileContext();\n                                            let userFeedback = aiReplyText.substring(0, aiReplyText.indexOf('[UPDATE_DB]')) + \"\\n\\n\u2705 **Database aggiornato con successo dall'AI!**\";\n                                            window.addMsg('ai', userFeedback); window.speakText(\"Database aggiornato con successo.\");\n                                            setTimeout(() => { location.reload(); }, 2500);\n                                        }\n                                    });\n                                    return;\n                                }\n                            } catch(e) {\n                                window.addMsg('ai', aiReplyText.substring(0, aiReplyText.indexOf('[UPDATE_DB]')) + \"\\n\\n\u26a0\ufe0f L'AI ha provato a modificare i dati ma ha generato un formato JSON non valido.\"); return;\n                            }\n                        }\n                    }\n                    \n                    if(res.data.reply && res.data.reply.includes('[RESET_SESSION]')) {\n                        let cln = res.data.reply.replace('[RESET_SESSION]', '').trim();\n                        if(cln) { window.addMsg('ai', cln, cln); window.speakText(cln); }\n                        setTimeout(() => { window.doLogout(true); }, 2500); return;\n                    }\n                    \n                    if(res.data.reply && res.data.reply.includes('[SWITCH_DB:')) {\n                        let match = res.data.reply.match(\/\\[SWITCH_DB:(.*?)\\]\/);\n                        if(match) {\n                            let targetDb = match[1]; window.addMsg('ai', \"\u23f3 Ti sto trasferendo alla ditta selezionata...\");\n                            setTimeout(() => { localStorage.setItem('ti_saved_db', targetDb); location.href = window.tiUrl; }, 1500); return;\n                        }\n                    }\n\n                    if(res.data.trigger_email) {\n                        const fd2=new FormData(); fd2.append('ti_action','ti_ai_send_email'); fd2.append('db',dbVal); fd2.append('email',res.data.trigger_email); fd2.append('subject',res.data.trigger_subj); \n                        fd2.append('history',res.data.trigger_hist); if (window.tiAppendCurrentLanguageToFormData) window.tiAppendCurrentLanguageToFormData(fd2); if(res.data.admin_copy) fd2.append('admin_copy','1');\n                        if (res.data.trigger_cc) fd2.append('cc_email', res.data.trigger_cc);\n                        if (res.data.trigger_admin_to_email) fd2.append('admin_to_email', res.data.trigger_admin_to_email);\n                        if (res.data.trigger_admin_to_tel) fd2.append('admin_to_tel', res.data.trigger_admin_to_tel);\n                        if (res.data.trigger_send_email) fd2.append('send_email_opt', res.data.trigger_send_email);\n                        if (res.data.trigger_send_sms) fd2.append('send_sms_opt', res.data.trigger_send_sms);\n                        fetch(window.tiUrl,{method:'POST',body:window.ensureAjaxActionField(fd2)}).then(rr=>{ if(!rr.ok) throw new Error('Errore Server ' + rr.status); return rr.json(); }).then(rr=>{\n                            if (rr && rr.data && rr.data.admin_popup) {\n                                if (window.showPrivilegedCommunicationPopup) window.showPrivilegedCommunicationPopup(rr.data.admin_popup, 'Fattura elettronica', {text: (window.tiPlainTextFromHtml ? window.tiPlainTextFromHtml(rr.data.admin_popup) : String(rr.data.admin_popup || ''))});\n                                else if (window.tiAlert) window.tiAlert(rr.data.admin_popup);\n                            }\n                            const replyMsg = (rr && rr.data && rr.data.reply) ? rr.data.reply : ((rr && rr.success) ? 'Operazione completata.' : 'Errore invio notifica');\n                            const shownNotifyPopup = window.maybeShowPrivilegedCommunicationPopup ? window.maybeShowPrivilegedCommunicationPopup(replyMsg, 'Comunicazione notifica') : false;\n                            if (shownNotifyPopup) window.addMsg('ai', window.tiPopupPlaceholderMessage ? window.tiPopupPlaceholderMessage('Comunicazione notifica') : 'Comunicazione aperta in popup.');\n                            else { window.addMsg('ai', replyMsg, replyMsg); window.speakText(replyMsg); }\n                            window.restoreLastUserPromptText();\n                            if(res.data.force_refresh) { const fd3=new FormData(); fd3.append('ti_action','ti_ai_chat_start'); fd3.append('text',''); fd3.append('force_refresh_flow','1'); fd3.append('db',dbVal); fetch(window.tiUrl,{method:'POST',body:window.ensureAjaxActionField(fd3)}).then(r3=>{ if(!r3.ok) throw new Error('Errore Server ' + r3.status); return r3.json(); }).then(r3=>{ const r3msg = (r3 && r3.data && r3.data.reply) ? r3.data.reply : ''; if (r3msg) { window.addMsg('ai', r3msg, r3msg); window.speakText(r3msg); } }); }\n                        }).catch((err)=>{ const em = (err && err.message) ? err.message : 'Errore invio notifica'; window.addMsg('ai', '\u26a0\ufe0f ' + em); window.restoreLastUserPromptText(); });\n                    } else {\n                        const rawReply = res.data.reply || '';\n                        const noVoice = !!(res.data && res.data.no_voice) || rawReply.includes('[NO_VOICE]');\n                        const cleanReply = rawReply.replace(\/\\[NO_VOICE\\]\/g, '').trim();\n                        if (res.data && res.data.clarify_request) {\n                            window.handleClarifyReply(cleanReply, cleanReply, noVoice);\n                            if (res.data.reset_chat) {\n                                setTimeout(() => { window.startFreshChatAfterClarifyLimit(); }, 1200);\n                            }\n                        } else {\n                            window.resetClarifyCount();\n                            const formattedReply = window.formatTable(cleanReply);\n                            const forceChatReply = !!(res.data && (res.data.force_chat || res.data.no_admin_popup || res.data.activity_chat));\n                            const shownPrivPopup = forceChatReply ? false : (window.maybeShowPrivilegedCommunicationPopup ? window.maybeShowPrivilegedCommunicationPopup(formattedReply, 'Comunicazione \/ report') : false);\n                            if (shownPrivPopup) {\n                                window.addMsg('ai', window.tiPopupPlaceholderMessage ? window.tiPopupPlaceholderMessage('Comunicazione \/ report') : 'Comunicazione\/report aperto in popup.');\n                            } else {\n                                window.addMsg('ai', formattedReply, cleanReply);\n                                if (!noVoice) window.speakText(cleanReply);\n                            }\n                        }\n                        window.restoreLastUserPromptText();\n                    }\n                } else {\n                    const roleNow = String(window.tiRole || '').toLowerCase();\n                    const isPrivilegedNow = (!!window.tiUser && window.tiUser === 'SSGlobalAdmin') || \/amministratore|configuratore|responsabile|globale\/.test(roleNow);\n                    if (isPrivilegedNow) {\n                        const errHtml = \"\u26a0\ufe0f Errore AI: \" + (res.data && res.data.message ? res.data.message : 'Sconosciuto');\n                        const shownErrPopup = window.showPrivilegedCommunicationPopup ? window.showPrivilegedCommunicationPopup(errHtml, 'Errore \/ comunicazione', {text: errHtml}) : false;\n                        window.addMsg('ai', shownErrPopup && window.tiPopupPlaceholderMessage ? window.tiPopupPlaceholderMessage('Errore \/ comunicazione') : errHtml);\n                    } else {\n                        window.handleClarifyReply(\"Mi dispiace, non ho capito bene la richiesta. Puoi scriverla in un altro modo o aggiungere un dettaglio in pi\u00f9?\", '', true);\n                    }\n                    window.restoreLastUserPromptText();\n                }\n            }).catch((e)=>{ \n                const msg = (e && e.message) ? e.message : 'Errore richiesta AI';\n                const roleNow = String(window.tiRole || '').toLowerCase();\n                const isPrivilegedNow = (!!window.tiUser && window.tiUser === 'SSGlobalAdmin') || \/amministratore|configuratore|responsabile|globale\/.test(roleNow);\n                window.hideProgressPopup(false, 'Errore richiesta AI', {notifyUser:isPrivilegedNow, errorMessage:msg}); \n                if (isPrivilegedNow) {\n                    const errHtml = \"\u26a0\ufe0f \" + msg;\n                    const shownErrPopup = window.showPrivilegedCommunicationPopup ? window.showPrivilegedCommunicationPopup(errHtml, 'Errore \/ comunicazione', {text: errHtml}) : false;\n                    if (!shownErrPopup && window.tiAlert && \/API Key|documenti AI|formato|500\/i.test(msg)) window.tiAlert(msg);\n                    window.addMsg('ai', shownErrPopup && window.tiPopupPlaceholderMessage ? window.tiPopupPlaceholderMessage('Errore \/ comunicazione') : errHtml);\n                } else {\n                    window.handleClarifyReply(\"Mi dispiace, non ho capito bene la richiesta. Puoi scriverla in un altro modo o aggiungere un dettaglio in pi\u00f9?\", '', true);\n                }\n                window.restoreLastUserPromptText(); \n            });\n        };\n\n        if(gE('ti-ok')) { gE('ti-ok').onclick=()=>{ const curr = dom.msg ? (dom.msg.value || '').trim() : ''; if(dom.msg) dom.msg.value=''; if (curr && (\/^\\+?[0-9]{8,15}$\/.test(curr) || \/^\\d{1,2}$\/.test(curr))) { if(window.tiSendQuickReply) return window.tiSendQuickReply(curr, curr); window.tiQuickReplyText = curr; if(dom.send) dom.send.click(); return; } if(window.tiSendQuickReply) return window.tiSendQuickReply('OK', 'OK'); window.tiQuickReplyText = 'OK'; if(dom.send) dom.send.click(); }; }\n        if(gE('ti-x')) { gE('ti-x').onclick=()=>{ if(dom.msg) dom.msg.value=''; if(window.tiSendQuickReply) return window.tiSendQuickReply('ANNULLA', 'ANNULLA'); window.tiQuickReplyText = 'ANNULLA'; if(dom.send) dom.send.click(); }; }\n        if(window.placeMainHelpButtonRight) window.placeMainHelpButtonRight();\n        if(gE('ti-help-btn')) { gE('ti-help-btn').onclick = () => window.openHelpModal(); }\n        if(gE('ti-help-support-toggle')) { gE('ti-help-support-toggle').onclick = () => window.toggleSupportFeedbackBox(); }\n        if(gE('ti-support-send')) { gE('ti-support-send').onclick = () => window.sendSupportFeedback(); }\n        if(gE('ti-kbd-btn')) {\n            gE('ti-kbd-btn').onclick = () => { \n                const k = gE('ti-kbd-cnt'); \n                if(k && k.innerHTML === '') { \n                    [\"1234567890\", \"QWERTYUIOP\", \"ASDFGHJKL\", \"ZXCVBNM\", \"@_-.\u20ac?!+=\"].forEach(row => { \n                        const rDiv = document.createElement('div'); rDiv.className = 'ti-kbd-row'; \n                        row.split('').forEach(c=>{ const b=document.createElement('button'); b.type=\"button\"; b.innerText=c; b.onclick=()=>{ if(gE('ti-kbd-prev')) { gE('ti-kbd-prev').value+=c; } }; rDiv.appendChild(b); }); k.appendChild(rDiv); \n                    }); \n                } \n                if(gE('ti-kbd-prev')) { gE('ti-kbd-prev').value = ''; }\n                window.openModal('ti-kbd-ov'); \n            };\n        }\n\n        if(gE('ti-mic')) {\n            gE('ti-mic').onclick = () => {\n                const SR = window.SpeechRecognition || window.webkitSpeechRecognition; \n                if(!SR) return window.tiAlert(\"Il microfono non \u00e8 supportato dal browser in uso (usa Chrome, Edge o Safari).\");\n                const r = new SR(); r.lang = 'it-IT'; gE('ti-mic').style.background = '#b91c1c'; gE('ti-mic').style.color = '#fff';\n                r.onresult = e => { if(dom.msg) dom.msg.value = e.results[0][0].transcript; if(dom.send) dom.send.click(); };\n                r.onend = () => { gE('ti-mic').style.background = '#222'; gE('ti-mic').style.color = '#ccc'; }; \n                r.start(); \n                setTimeout(() => { r.stop(); gE('ti-mic').style.background = '#222'; gE('ti-mic').style.color = '#ccc'; }, 10000);\n            };\n        }\n    }); \n    \/* 30.9.181 - Fix persi reintegrati sul backup stabile: Acquisti, Report, Esiti, input scuri *\/\n    (function(){\n        function tiInstallFixPersi181(){\n        try {\n            const byId = function(id){ return document.getElementById(id); };\n            const escapeSafe = function(v){\n                if (window.escapeHtml) return window.escapeHtml(String(v == null ? '' : v));\n                return String(v == null ? '' : v).replace(\/[&<>\"']\/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c]; });\n            };\n            const parseNumSafe = function(v){\n                if (v === null || v === undefined) return 0;\n                let s = String(v).trim();\n                if (!s) return 0;\n                s = s.replace(\/[\u20ac\\s]\/g, '');\n                if (s.indexOf(',') !== -1 && s.indexOf('.') !== -1) s = s.replace(\/\\.\/g, '').replace(',', '.');\n                else if (s.indexOf(',') !== -1) s = s.replace(',', '.');\n                const n = parseFloat(s.replace(\/[^0-9.\\-]\/g, ''));\n                return isNaN(n) ? 0 : n;\n            };\n            const fmtSafe = function(n){\n                const num = Math.round((parseFloat(n) || 0) * 100) \/ 100;\n                if (window.fmtNum) return window.fmtNum(num);\n                return num.toLocaleString('it-IT', {minimumFractionDigits:2, maximumFractionDigits:2});\n            };\n            const normKey = function(s){\n                return String(s || '').normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase().replace(\/[^a-z0-9]\/g, '');\n            };\n            const realTableName = function(data, wanted, fallback){\n                const tables = data && data.Tabelle ? data.Tabelle : {};\n                const want = normKey(wanted);\n                const keys = Object.keys(tables || {});\n                for (let i=0;i<keys.length;i++) if (normKey(keys[i]) === want) return keys[i];\n                for (let i=0;i<keys.length;i++) if (normKey(keys[i]).indexOf(want) !== -1 || want.indexOf(normKey(keys[i])) !== -1) return keys[i];\n                return fallback || wanted;\n            };\n            const getField = function(row, names, fallback){\n                row = row || {};\n                const keys = Object.keys(row);\n                for (let i=0;i<names.length;i++) {\n                    const want = normKey(names[i]);\n                    for (let j=0;j<keys.length;j++) if (normKey(keys[j]) === want) return row[keys[j]];\n                }\n                return fallback;\n            };\n            const setField = function(row, names, value){\n                row = row || {};\n                const keys = Object.keys(row);\n                for (let i=0;i<names.length;i++) {\n                    const want = normKey(names[i]);\n                    for (let j=0;j<keys.length;j++) if (normKey(keys[j]) === want) { row[keys[j]] = value; return; }\n                }\n                row[names[0]] = value;\n            };\n            const marginAdjustedNet = function(costNet, marginText){\n                const cost = parseNumSafe(costNet);\n                let m = String(marginText || '').trim();\n                if (!m) return Math.max(0, cost);\n                const isPct = m.indexOf('%') !== -1;\n                m = m.replace(\/[%\u20ac\\s]\/g, '');\n                let sign = 1;\n                if (m.charAt(0) === '-') { sign = -1; m = m.slice(1); }\n                else if (m.charAt(0) === '+') { m = m.slice(1); }\n                const val = parseNumSafe(m);\n                const net = isPct ? (cost + (cost * sign * val \/ 100)) : (cost + sign * val);\n                return Math.max(0, net);\n            };\n            const saleGross = function(costNet, marginText, ivaText){\n                let iva = parseNumSafe(ivaText);\n                if (iva <= 0) iva = 22;\n                return Math.round(marginAdjustedNet(costNet, marginText) * (1 + iva \/ 100) * 100) \/ 100;\n            };\n            const todayParts = function(){\n                const d = new Date();\n                const dd = String(d.getDate()).padStart(2,'0');\n                const mm = String(d.getMonth()+1).padStart(2,'0');\n                const yyyy = d.getFullYear();\n                const hh = String(d.getHours()).padStart(2,'0');\n                const mi = String(d.getMinutes()).padStart(2,'0');\n                return {date: dd + '\/' + mm + '\/' + yyyy, time: hh + ':' + mi};\n            };\n            const base64Utf8 = function(str){\n                try { return btoa(unescape(encodeURIComponent(str))); }\n                catch(e) { return btoa(str); }\n            };\n            window.tiPurchaseCalcSaleGross = saleGross;\n            window.tiPurchaseMarginAdjustedNet = marginAdjustedNet;\n\n            window.purchaseGetCatalogItems = function() {\n                const data = window.currentDbData || {};\n                const tables = data && data.Tabelle ? data.Tabelle : {};\n                const out = [];\n                Object.keys(tables || {}).forEach(function(tn){\n                    if (!\/(prodott|serviz)\/i.test(tn)) return;\n                    const rows = Array.isArray(tables[tn]) ? tables[tn] : [];\n                    rows.forEach(function(r, idx){\n                        if (!r || typeof r !== 'object') return;\n                        const isServ = \/(serviz)\/i.test(tn);\n                        const name = String(getField(r, isServ ? ['Servizio','Descrizione','Nome','Titolo'] : ['Prodotto','Descrizione','Nome','Titolo'], '') || '').trim();\n                        if (!name) return;\n                        out.push({\n                            table: tn,\n                            index: idx,\n                            type: isServ ? 'S' : 'P',\n                            typeLabel: isServ ? 'servizio' : 'prodotto',\n                            name: name,\n                            desc: String(getField(r, ['Descrizione','Note','Nota'], '') || '').trim(),\n                            cost: parseNumSafe(getField(r, ['Costo','Costo_acquisto','Costo acquisto'], 0)),\n                            sale: parseNumSafe(getField(r, ['Prezzo','Prezzo_vendita','Prezzo vendita'], 0)),\n                            iva: parseNumSafe(getField(r, ['IVA_VAT','IVA','Iva','VAT'], 22)) || 22\n                        });\n                    });\n                });\n                return out;\n            };\n\n            window.ensurePurchaseModal = function() {\n                let ov = byId('ti-purchase-ov');\n                if (ov && ov.dataset && ov.dataset.v181 === '1') return ov;\n                if (ov && ov.parentNode) ov.parentNode.removeChild(ov);\n                ov = document.createElement('div');\n                ov.id = 'ti-purchase-ov';\n                ov.className = 'ti-modal-overlay ti-ov ti-closable-ov';\n                ov.dataset.v181 = '1';\n                ov.style.display = 'none';\n                ov.style.zIndex = '2147482000';\n                ov.style.alignItems = 'center';\n                ov.style.justifyContent = 'center';\n                ov.innerHTML = `\n                    <div class=\"ti-modal ti-purchase-modal\" style=\"width:min(1460px,98vw);max-width:98vw;max-height:92vh;overflow:auto;background:#0f172a;color:#fff;border-radius:16px;padding:18px;box-shadow:0 18px 60px rgba(0,0,0,.45);margin:0 auto;\">\n                        <div style=\"display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px;\">\n                            <div>\n                                <h2 style=\"margin:0;font-size:22px;\">Acquisti<\/h2>\n                                <div style=\"font-size:14px;color:#cbd5e1;margin-top:4px;\">Inserisci o modifica le righe acquisto. Il costo \u00e8 netto IVA; il prezzo vendita proposto include margine e IVA.<\/div>\n                            <\/div>\n                            <button type=\"button\" class=\"ti-btn\" style=\"background:#dc2626;color:#fff;\" onclick=\"window.closePurchaseManualFlow()\">X<\/button>\n                        <\/div>\n                        <div style=\"display:flex;gap:8px;flex-wrap:wrap;margin:10px 0 14px 0;\">\n                            <button type=\"button\" class=\"ti-btn ti-ss-add-manual-row\" style=\"background:#22c55e;color:#052e16;font-weight:700;\" onclick=\"window.addPurchaseManualRow()\">+ Aggiungi riga manuale<\/button>\n                            <button type=\"button\" class=\"ti-btn\" style=\"background:#f59e0b;color:#111827;font-weight:700;\" onclick=\"window.pickPurchaseImportFile()\">Import file acquisti<\/button>\n                        <\/div>\n                        <div class=\"ti-purchase-table-wrap\" style=\"overflow:auto;border:1px solid #334155;border-radius:12px;\">\n                            <table id=\"ti-purchase-table\" style=\"width:100%;border-collapse:collapse;min-width:1060px;background:#020617;\">\n                                <thead>\n                                    <tr style=\"background:#1e293b;\">\n                                        <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:left;\">Prodotto o servizio<\/th>\n                                        <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:left;\">Tipo<\/th>\n                                        <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Q.t\u00e0<\/th>\n                                        <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Costo netto<\/th>\n                                        <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Margine<\/th>\n                                        <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Prezzo vendita ivato<\/th>\n                                        <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">IVA %<\/th>\n                                        <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:left;\">Note<\/th>\n                                        <th style=\"padding:8px;border-bottom:1px solid #334155;\">Azioni<\/th>\n                                    <\/tr>\n                                <\/thead>\n                                <tbody id=\"ti-purchase-body\"><\/tbody>\n                            <\/table>\n                        <\/div>\n                        <div id=\"ti-purchase-total\" style=\"margin-top:10px;text-align:right;font-weight:700;color:#bbf7d0;\"><\/div>\n                        <div style=\"display:flex;justify-content:center;gap:8px;margin-top:14px;flex-wrap:wrap;\">\n                            <button type=\"button\" class=\"ti-btn ti-btn-ok\" style=\"background:#22c55e;color:#052e16;font-weight:900;\" onclick=\"window.confirmPurchaseRows()\">Conferma righe acquisto<\/button>\n                            <button type=\"button\" class=\"ti-btn btn-close\" onclick=\"window.closePurchaseManualFlow()\">Chiudi<\/button>\n                        <\/div>\n                    <\/div>`;\n                document.body.appendChild(ov);\n                return ov;\n            };\n\n            window.closePurchaseSearchPopover = function(){\n                const p = byId('ti-purchase-row-search-popover');\n                if (p && p.parentNode) p.parentNode.removeChild(p);\n            };\n            window.selectPurchaseCatalogItemForRow = function(item, row){\n                try {\n                    row = row || window.__tiPurchaseActiveRow;\n                    if (!item || !row) return;\n                    const name = row.querySelector('.ti-pur-name');\n                    const type = row.querySelector('.ti-pur-type');\n                    const cost = row.querySelector('.ti-pur-cost');\n                    const sale = row.querySelector('.ti-pur-sale');\n                    const iva = row.querySelector('.ti-pur-iva');\n                    const note = row.querySelector('.ti-pur-note');\n                    if (name) name.value = item.name || '';\n                    if (type) type.value = item.type || 'P';\n                    if (cost) cost.value = item.cost ? String(item.cost).replace('.', ',') : '';\n                    if (iva) iva.value = item.iva || '22';\n                    if (note && !note.value) note.value = item.desc || '';\n                    row.dataset.catalogTable = item.table || '';\n                    row.dataset.catalogIndex = String(item.index != null ? item.index : -1);\n                    row.dataset.targetType = item.typeLabel || (item.type === 'S' ? 'servizio' : 'prodotto');\n                    window.recalcPurchaseRow(row, true);\n                    if (sale && item.sale && !sale.value) sale.value = String(item.sale).replace('.', ',');\n                    window.closePurchaseSearchPopover();\n                    window.refreshPurchaseTotals();\n                } catch(e) {}\n            };\n            window.showPurchaseRowSearch = function(input){\n                try {\n                    if (!input) return;\n                    const row = input.closest('tr');\n                    window.__tiPurchaseActiveRow = row;\n                    const q = String(input.value || '').trim().toLowerCase();\n                    window.closePurchaseSearchPopover();\n                    if (q.length < 1) return;\n                    const items = window.purchaseGetCatalogItems().filter(function(it){\n                        return (String(it.name||'') + ' ' + String(it.desc||'') + ' ' + String(it.table||'')).toLowerCase().indexOf(q) !== -1;\n                    }).slice(0, 16);\n                    const pop = document.createElement('div');\n                    pop.id = 'ti-purchase-row-search-popover';\n                    pop.className = 'ti-purchase-row-search-popover';\n                    if (!items.length) {\n                        pop.innerHTML = '<div style=\"color:#fca5a5;font-size:11px;padding:7px;\">Nessun prodotto o servizio trovato nel database.<\/div>';\n                    } else {\n                        pop.innerHTML = items.map(function(it, idx){\n                            return '<button type=\"button\" class=\"ti-purchase-result-row\" data-idx=\"' + idx + '\"><b>' + escapeSafe(it.typeLabel) + ' - ' + escapeSafe(it.name) + '<\/b><small>' + escapeSafe(it.table) + ' \u00b7 costo ' + escapeSafe(fmtSafe(it.cost)) + ' \u00b7 prezzo ' + escapeSafe(fmtSafe(it.sale)) + ' \u00b7 IVA ' + escapeSafe(it.iva) + '%<\/small><\/button>';\n                        }).join('');\n                    }\n                    document.body.appendChild(pop);\n                    const rect = input.getBoundingClientRect();\n                    const margin = 8;\n                    const topBelow = rect.bottom + margin;\n                    const estimatedH = Math.min(320, Math.max(70, items.length * 48 + 18));\n                    const top = (topBelow + estimatedH > window.innerHeight && rect.top > estimatedH) ? Math.max(margin, rect.top - estimatedH - margin) : topBelow;\n                    pop.style.left = Math.max(margin, Math.min(rect.left, window.innerWidth - Math.min(520, Math.max(240, rect.width)) - margin)) + 'px';\n                    pop.style.top = top + 'px';\n                    pop.style.width = Math.min(520, Math.max(240, rect.width)) + 'px';\n                    pop.querySelectorAll('.ti-purchase-result-row').forEach(function(btn){\n                        const handler = function(ev){\n                            ev.preventDefault(); ev.stopPropagation();\n                            const idx = parseInt(btn.getAttribute('data-idx'), 10);\n                            window.selectPurchaseCatalogItemForRow(items[idx], row);\n                        };\n                        btn.addEventListener('mousedown', handler);\n                        btn.addEventListener('touchstart', handler, {passive:false});\n                        btn.addEventListener('click', handler);\n                    });\n                } catch(e) {}\n            };\n            if (!window.__tiPurchaseSearchCloseBound) {\n                document.addEventListener('pointerdown', function(ev){\n                    const p = byId('ti-purchase-row-search-popover');\n                    if (!p) return;\n                    if (p.contains(ev.target)) return;\n                    if (ev.target && ev.target.closest && ev.target.closest('.ti-pur-name')) return;\n                    window.closePurchaseSearchPopover();\n                }, true);\n                window.addEventListener('resize', function(){ window.closePurchaseSearchPopover(); }, {passive:true});\n                window.__tiPurchaseSearchCloseBound = true;\n            }\n\n            window.recalcPurchaseRow = function(row, force){\n                try {\n                    if (!row) return;\n                    const cost = row.querySelector('.ti-pur-cost');\n                    const margin = row.querySelector('.ti-pur-margin');\n                    const sale = row.querySelector('.ti-pur-sale');\n                    const iva = row.querySelector('.ti-pur-iva');\n                    if (!sale) return;\n                    const gross = saleGross(cost ? cost.value : 0, margin ? margin.value : '', iva ? iva.value : '22');\n                    if (force || document.activeElement !== sale) sale.value = gross ? String(gross).replace('.', ',') : '';\n                } catch(e) {}\n            };\n\n            window.addPurchaseManualRow = function(item) {\n                try {\n                    const ov = byId('ti-purchase-ov') || window.ensurePurchaseModal();\n                    const body = byId('ti-purchase-body');\n                    if (!body) return;\n                    if (ov) { ov.style.display = 'flex'; }\n                    const safe = item || {};\n                    const tr = document.createElement('tr');\n                    tr.className = 'ti-purchase-row';\n                    tr.dataset.catalogTable = safe.table || '';\n                    tr.dataset.catalogIndex = String(safe.index != null ? safe.index : -1);\n                    tr.dataset.targetType = safe.type === 'S' ? 'servizio' : 'prodotto';\n                    const iva = safe.iva || '22';\n                    const cost = safe.cost ? String(safe.cost).replace('.', ',') : '';\n                    const sale = safe.sale ? String(safe.sale).replace('.', ',') : '';\n                    tr.innerHTML = `\n                        <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-name ti-purchase-row-search\" value=\"${escapeSafe(safe.name || '')}\" placeholder=\"Cerca prodotto o servizio\" autocomplete=\"off\" style=\"width:100%;min-width:230px;box-sizing:border-box;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                        <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><select class=\"ti-pur-type\" style=\"width:76px;padding:8px;border-radius:8px;background:#0f172a;color:#fff;border:1px solid #334155;\"><option value=\"P\" ${safe.type === 'S' ? '' : 'selected'}>P<\/option><option value=\"S\" ${safe.type === 'S' ? 'selected' : ''}>S<\/option><\/select><\/td>\n                        <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-qty\" value=\"${escapeSafe(safe.qty || '1')}\" inputmode=\"decimal\" style=\"width:72px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                        <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-cost\" value=\"${escapeSafe(cost)}\" inputmode=\"decimal\" style=\"width:100px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                        <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-margin\" value=\"${escapeSafe(safe.margin || '')}\" placeholder=\"+10%\" style=\"width:86px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                        <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-sale\" value=\"${escapeSafe(sale)}\" inputmode=\"decimal\" style=\"width:112px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                        <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-iva\" value=\"${escapeSafe(iva)}\" inputmode=\"decimal\" style=\"width:68px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                        <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-note\" value=\"${escapeSafe(safe.desc || '')}\" placeholder=\"Note\" style=\"width:100%;min-width:170px;box-sizing:border-box;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                        <td style=\"padding:6px;border-bottom:1px solid #1f2937;text-align:center;\"><button type=\"button\" class=\"ti-btn\" style=\"background:#dc2626;color:#fff;\" onclick=\"this.closest('tr').remove();window.refreshPurchaseTotals();\">X<\/button><\/td>`;\n                    body.appendChild(tr);\n                    const name = tr.querySelector('.ti-pur-name');\n                    if (name) {\n                        name.addEventListener('input', function(){ window.showPurchaseRowSearch(name); });\n                        name.addEventListener('focus', function(){ if (String(name.value||'').trim()) window.showPurchaseRowSearch(name); });\n                    }\n                    ['.ti-pur-qty','.ti-pur-cost','.ti-pur-margin','.ti-pur-iva'].forEach(function(sel){\n                        const el = tr.querySelector(sel);\n                        if (el) el.addEventListener('input', function(){ window.recalcPurchaseRow(tr); window.refreshPurchaseTotals(); });\n                    });\n                    window.recalcPurchaseRow(tr, !sale);\n                    window.refreshPurchaseTotals();\n                } catch(e) { if (window.tiAlert) window.tiAlert('Errore aggiunta riga acquisto: ' + (e.message || e)); }\n            };\n\n            window.refreshPurchaseTotals = function() {\n                try {\n                    let total = 0;\n                    document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr){\n                        const qty = parseNumSafe((tr.querySelector('.ti-pur-qty') || {}).value || '0');\n                        const cost = parseNumSafe((tr.querySelector('.ti-pur-cost') || {}).value || '0');\n                        total += qty * cost;\n                    });\n                    const out = byId('ti-purchase-total');\n                    if (out) out.innerHTML = 'Totale acquisto netto indicativo: \u20ac ' + fmtSafe(total);\n                } catch(e) {}\n            };\n            window.purchaseCollectRows = function() {\n                const rows = [];\n                try {\n                    document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr){\n                        const name = String((tr.querySelector('.ti-pur-name') || {}).value || '').trim();\n                        if (!name) return;\n                        rows.push({\n                            type: String((tr.querySelector('.ti-pur-type') || {}).value || 'P'),\n                            name: name,\n                            qty: String((tr.querySelector('.ti-pur-qty') || {}).value || '1').trim(),\n                            cost: String((tr.querySelector('.ti-pur-cost') || {}).value || '').trim(),\n                            margin: String((tr.querySelector('.ti-pur-margin') || {}).value || '').trim(),\n                            sale: String((tr.querySelector('.ti-pur-sale') || {}).value || '').trim(),\n                            iva: String((tr.querySelector('.ti-pur-iva') || {}).value || '22').trim(),\n                            note: String((tr.querySelector('.ti-pur-note') || {}).value || '').trim(),\n                            table: tr.dataset.catalogTable || '',\n                            index: parseInt(tr.dataset.catalogIndex || '-1', 10)\n                        });\n                    });\n                } catch(e) {}\n                return rows;\n            };\n            window.confirmPurchaseRows = function(){\n                try {\n                    const rows = window.purchaseCollectRows();\n                    if (!rows.length) { if (window.tiAlert) window.tiAlert('Inserisci almeno una riga acquisto.'); return; }\n                    const dbEl = byId('ti-ditta');\n                    const dbVal = dbEl ? dbEl.value : '';\n                    if (!dbVal || dbVal === 'NEW_DB') { if (window.tiAlert) window.tiAlert('Seleziona prima una ditta.'); return; }\n                    const source = window.currentDbData;\n                    if (!source || !source.Tabelle) { if (window.tiAlert) window.tiAlert('Database non caricato. Riapri Acquisti dopo aver selezionato la ditta.'); return; }\n                    const data = JSON.parse(JSON.stringify(source));\n                    const prodTable = realTableName(data, 'Prodotti', 'Prodotti');\n                    const servTable = realTableName(data, 'Servizi', 'Servizi');\n                    const actTable = realTableName(data, 'Attivita', 'Attivita');\n                    if (!data.Tabelle[prodTable]) data.Tabelle[prodTable] = [];\n                    if (!data.Tabelle[servTable]) data.Tabelle[servTable] = [];\n                    if (!data.Tabelle[actTable]) data.Tabelle[actTable] = [];\n                    const now = todayParts();\n                    const makeId = function(table){ return (Array.isArray(data.Tabelle[table]) ? data.Tabelle[table].length : 0) + 1; };\n                    rows.forEach(function(r){\n                        const isServ = String(r.type || 'P') === 'S';\n                        const t = isServ ? servTable : prodTable;\n                        const nameFields = isServ ? ['Servizio','Nome','Descrizione'] : ['Prodotto','Nome','Descrizione'];\n                        const tableRows = Array.isArray(data.Tabelle[t]) ? data.Tabelle[t] : (data.Tabelle[t] = []);\n                        let idx = (r.table === t && r.index >= 0 && tableRows[r.index]) ? r.index : -1;\n                        if (idx < 0) {\n                            const wanted = normKey(r.name);\n                            idx = tableRows.findIndex(function(row){ return normKey(getField(row, nameFields, '')) === wanted; });\n                        }\n                        let rec;\n                        if (idx >= 0) rec = tableRows[idx];\n                        else { rec = {}; setField(rec, ['ID','Id','id'], makeId(t)); setField(rec, nameFields, r.name); tableRows.push(rec); }\n                        setField(rec, nameFields, r.name);\n                        setField(rec, ['Costo','Costo acquisto','Costo_acquisto'], parseNumSafe(r.cost));\n                        const gross = parseNumSafe(r.sale) || saleGross(r.cost, r.margin, r.iva);\n                        setField(rec, ['Prezzo','Prezzo vendita','Prezzo_vendita'], gross);\n                        setField(rec, ['IVA_VAT','IVA','Iva','VAT'], parseNumSafe(r.iva) || 22);\n                        if (r.note) setField(rec, ['Note','Nota','Descrizione'], r.note);\n                        if (!isServ) {\n                            const oldQty = parseNumSafe(getField(rec, ['Quantit\u00e0','Quantita','Disponibilita','Disponibilit\u00e0'], 0));\n                            setField(rec, ['Quantit\u00e0','Quantita'], oldQty + parseNumSafe(r.qty));\n                        }\n                        const act = {};\n                        setField(act, ['ID','Id','id'], makeId(actTable));\n                        setField(act, ['Username','Utente','user'], window.tiUser || '');\n                        setField(act, ['Descrizione attivit\u00e0','Descrizione attivita','Descrizione','Dettaglio','Note'], 'Acquisto ' + r.name);\n                        setField(act, ['Prodotto'], r.name);\n                        setField(act, ['Prezzo'], parseNumSafe(r.cost));\n                        setField(act, ['Quantit\u00e0','Quantita'], parseNumSafe(r.qty));\n                        setField(act, ['Totale'], Math.round(parseNumSafe(r.cost) * parseNumSafe(r.qty) * 100) \/ 100);\n                        setField(act, ['Esito'], 'acquisto');\n                        setField(act, ['Data'], now.date);\n                        setField(act, ['Ora'], now.time);\n                        setField(act, ['Stato'], 'Attivo');\n                        data.Tabelle[actTable].push(act);\n                    });\n                    const fd = new FormData();\n                    fd.append('ti_action', 'ti_ai_save_full_db');\n                    fd.append('db', dbVal);\n                    fd.append('payload', base64Utf8(JSON.stringify(data)));\n                    const done = function(res){\n                        if (res && res.success) {\n                            window.currentDbData = data;\n                            window.closePurchaseManualFlow();\n                            if (window.tiAlert) window.tiAlert('\u2705 Righe acquisto confermate e salvate nel database.');\n                            if (res.data && res.data.needs_reload) setTimeout(function(){ location.reload(); }, 900);\n                        } else {\n                            const msg = (res && res.data && res.data.message) ? res.data.message : 'Salvataggio acquisto non riuscito.';\n                            if (window.tiAlert) window.tiAlert('\u26a0\ufe0f ' + msg);\n                        }\n                    };\n                    if (window.fetchJsonSafe) window.fetchJsonSafe((window.tiAjaxUrl || window.tiUrl), {method:'POST', body:fd, credentials:'same-origin'}).then(done).catch(function(e){ if (window.tiAlert) window.tiAlert('Errore salvataggio acquisto: ' + (e.message || e)); });\n                    else fetch((window.tiAjaxUrl || window.tiUrl), {method:'POST', body:fd, credentials:'same-origin'}).then(r=>r.json()).then(done).catch(function(e){ if (window.tiAlert) window.tiAlert('Errore salvataggio acquisto: ' + (e.message || e)); });\n                } catch(e) { if (window.tiAlert) window.tiAlert('Errore conferma acquisto: ' + (e.message || e)); }\n            };\n            window.purchaseSendToChat = function(){ window.confirmPurchaseRows(); };\n            if (!window.__tiPurchaseDelegated181) {\n                document.addEventListener('click', function(ev){\n                    const btn = ev.target && ev.target.closest ? ev.target.closest('.ti-ss-add-manual-row') : null;\n                    if (!btn) return;\n                    ev.preventDefault();\n                    window.addPurchaseManualRow();\n                });\n                window.__tiPurchaseDelegated181 = true;\n            }\n\n            window.normalizeActivityOutcome = function(row){\n                row = row || {};\n                const current = String(row.Esito || '').trim();\n                if (\/vendit\/i.test(current)) return 'vendita';\n                if (\/acquist\/i.test(current)) return 'acquisto';\n                if (\/configur\/i.test(current)) return current || 'configurazione';\n                if (\/appunt\/i.test(current)) return current || 'appuntamento';\n                const txt = String(row.Descrizione || row.Dettaglio || row.Note || row.Nota || '').toLowerCase();\n                if (\/acquist|fornitor\/.test(txt)) return current || 'acquisto';\n                if (\/appuntament|prenotaz\/.test(txt)) return current || 'appuntamento';\n                if (\/configur|setup|modifica database\/.test(txt)) return current || 'configurazione';\n                const price = parseNumSafe(row.Prezzo || row.Totale || 0);\n                const hasProduct = String(row.Prodotto || row.Servizio || '').trim() !== '';\n                if (!current && (hasProduct || price > 0)) return 'vendita';\n                return current;\n            };\n            if (typeof window.getActivityRowsNormalized === 'function' && !window.__tiActivityOutcomeWrap181) {\n                const oldRowsNorm = window.getActivityRowsNormalized;\n                window.getActivityRowsNormalized = function(){\n                    const arr = oldRowsNorm.apply(this, arguments) || [];\n                    return arr.map(function(r){ if (r && typeof r === 'object') r.Esito = window.normalizeActivityOutcome(r); return r; });\n                };\n                window.__tiActivityOutcomeWrap181 = true;\n            }\n            window.activityOutcomeStorageKey = function(){\n                const u = window.tiUser || 'guest';\n                const db = (byId('ti-ditta') && byId('ti-ditta').value) ? byId('ti-ditta').value : (window.tiForced || 'global');\n                return 'ti_activity_report_outcome_' + u + '_' + db;\n            };\n            window.loadActivityOutcomeFilter = function(){ try { return localStorage.getItem(window.activityOutcomeStorageKey()) || ''; } catch(e) { return ''; } };\n            window.saveActivityOutcomeFilter = function(v){ try { localStorage.setItem(window.activityOutcomeStorageKey(), String(v || '')); } catch(e) {} };\n            window.setActivityOutcomeFilter = function(v){\n                if (!window.activityReportState) window.activityReportState = {};\n                window.activityReportState.outcomeFilter = String(v || '');\n                window.saveActivityOutcomeFilter(window.activityReportState.outcomeFilter);\n                window.renderActivityReport();\n            };\n            if (typeof window.getFilteredActivityRows === 'function' && !window.__tiActivityFilterWrap181) {\n                const oldFilteredRows = window.getFilteredActivityRows;\n                window.getFilteredActivityRows = function(includeExcludedRows){\n                    let rows = oldFilteredRows.apply(this, arguments) || [];\n                    const f = window.activityReportState ? String(window.activityReportState.outcomeFilter || '') : '';\n                    if (f) rows = rows.filter(function(r){ return String(r.Esito || '') === f; });\n                    return rows;\n                };\n                window.__tiActivityFilterWrap181 = true;\n            }\n            window.injectActivityOutcomeFilter = function(){\n                try {\n                    const toolbar = document.querySelector('#ti-report-cnt .ti-report-toolbar');\n                    if (!toolbar || toolbar.querySelector('.ti-report-outcome-filter-wrap')) return;\n                    const rawRows = (window.getActivityRowsNormalized ? window.getActivityRowsNormalized() : []);\n                    const opts = [];\n                    rawRows.forEach(function(r){ const e = String(r && r.Esito ? r.Esito : '').trim(); if (e && opts.indexOf(e) === -1) opts.push(e); });\n                    opts.sort(function(a,b){ return a.localeCompare(b, 'it'); });\n                    const current = window.activityReportState ? String(window.activityReportState.outcomeFilter || '') : '';\n                    const label = document.createElement('label');\n                    label.className = 'ti-report-outcome-filter-wrap';\n                    let html = '<span>Esito<\/span><select class=\"ti-report-outcome-filter\" onchange=\"window.setActivityOutcomeFilter(this.value)\"><option value=\"\">Tutti<\/option><option value=\"__empty__\" ' + (current === '__empty__' ? 'selected' : '') + '>Senza esito<\/option>';\n                    opts.forEach(function(o){ html += '<option value=\"' + escapeSafe(o) + '\" ' + (o === current ? 'selected' : '') + '>' + escapeSafe(o) + '<\/option>'; });\n                    if (current && current !== '__empty__' && opts.indexOf(current) === -1) html += '<option value=\"' + escapeSafe(current) + '\" selected>' + escapeSafe(current) + '<\/option>';\n                    html += '<\/select>';\n                    label.innerHTML = html;\n                    toolbar.appendChild(label);\n                } catch(e) {}\n            };\n            if (typeof window.renderActivityReport === 'function' && !window.__tiActivityRenderWrap181) {\n                const oldRender = window.renderActivityReport;\n                window.renderActivityReport = function(){ oldRender.apply(this, arguments); window.injectActivityOutcomeFilter(); };\n                window.__tiActivityRenderWrap181 = true;\n            }\n            if (typeof window.openActivityReport === 'function' && !window.__tiActivityOpenWrap181) {\n                const oldOpenReport = window.openActivityReport;\n                window.openActivityReport = function(){\n                    oldOpenReport.apply(this, arguments);\n                    if (!window.activityReportState) window.activityReportState = {};\n                    window.activityReportState.outcomeFilter = window.loadActivityOutcomeFilter();\n                    window.renderActivityReport();\n                };\n                window.__tiActivityOpenWrap181 = true;\n            }\n\n            window.drawPie3D = function(canvasId, rows) {\n                const cv = byId(canvasId); if(!cv) return; const ctx = cv.getContext('2d');\n                const selectedMetrics = window.getActivitySelectedMetrics ? window.getActivitySelectedMetrics() : ['Totale'];\n                const groupField = window.getActivitySelectedGroupField ? window.getActivitySelectedGroupField() : 'Username';\n                const metrics = (Array.isArray(selectedMetrics) && selectedMetrics.length) ? selectedMetrics : ['Totale'];\n                const cols = 1;\n                const blockW = 780;\n                const blockH = 310;\n                cv.width = Math.max(900, blockW + 40);\n                cv.height = Math.max(360, metrics.length * blockH + 40);\n                ctx.clearRect(0,0,cv.width,cv.height);\n                const colors=['#38bdf8','#22c55e','#f59e0b','#ef4444','#a855f7','#14b8a6','#f97316','#06b6d4','#84cc16','#fb7185','#60a5fa','#e879f9'];\n                const buildEntriesForMetric = function(metric) {\n                    const groups = {};\n                    rows.forEach(function(r){\n                        const label = String(r[groupField] || 'N\/D').trim() || 'N\/D';\n                        const value = parseNumSafe(r[metric]) || 0;\n                        groups[label] = (groups[label] || 0) + value;\n                    });\n                    return Object.entries(groups).filter(function(e){ return e[1] > 0; }).sort(function(a,b){ return b[1]-a[1]; }).slice(0, 14);\n                };\n                let drew = false;\n                metrics.forEach(function(metric, metricIndex){\n                    const entries = buildEntriesForMetric(metric);\n                    const originX = 20;\n                    const originY = 20 + metricIndex * blockH;\n                    const cx = originX + 145;\n                    const cy = originY + 145;\n                    const rad = 82;\n                    const depth = 16;\n                    const legendX = originX + 280;\n                    ctx.fillStyle = '#fff'; ctx.font = 'bold 14px Arial'; ctx.fillText(window.getActivityMetricLabel ? window.getActivityMetricLabel([metric]) : metric, originX + 8, originY + 20);\n                    if (!entries.length) { ctx.fillStyle = '#cbd5e1'; ctx.font = '12px Arial'; ctx.fillText('Nessun dato', originX + 8, originY + 48); return; }\n                    drew = true;\n                    const total = entries.reduce(function(a,b){ return a+b[1]; },0);\n                    let start = -Math.PI\/2;\n                    entries.forEach(function(entry,i){\n                        const ang = total > 0 ? (entry[1]\/total)*Math.PI*2 : 0;\n                        ctx.fillStyle='rgba(0,0,0,0.35)'; ctx.beginPath(); ctx.moveTo(cx, cy+depth); ctx.arc(cx, cy+depth, rad, start, start+ang); ctx.closePath(); ctx.fill();\n                        ctx.fillStyle=colors[i%colors.length]; ctx.beginPath(); ctx.moveTo(cx, cy); ctx.arc(cx, cy, rad, start, start+ang); ctx.closePath(); ctx.fill();\n                        start += ang;\n                    });\n                    entries.forEach(function(entry,i){\n                        const pct = total > 0 ? (entry[1] \/ total * 100) : 0;\n                        const ly = originY + 44 + i * 18;\n                        ctx.fillStyle = colors[i%colors.length]; ctx.fillRect(legendX, ly - 10, 11, 11);\n                        ctx.fillStyle = '#fff'; ctx.font = '11px Arial';\n                        const label = String(entry[0]).length > 30 ? String(entry[0]).slice(0, 29) + '\u2026' : String(entry[0]);\n                        ctx.fillText(label + ' - ' + fmtSafe(entry[1]) + ' (' + fmtSafe(pct) + '%)', legendX + 18, ly);\n                    });\n                    ctx.fillStyle = '#94a3b8'; ctx.font = '11px Arial'; ctx.fillText('Raggruppa per: ' + groupField, originX + 8, originY + blockH - 12);\n                });\n                if (!drew) { ctx.fillStyle='#fff'; ctx.font='14px Arial'; ctx.fillText('Nessun dato',20,20); }\n            };\n            window.drawTrend3D = function(canvasId, rows) {\n                const cv = byId(canvasId); if(!cv) return; const ctx = cv.getContext('2d');\n                const selectedMetrics = window.getActivitySelectedMetrics ? window.getActivitySelectedMetrics() : ['Totale'];\n                const groupField = window.getActivitySelectedGroupField ? window.getActivitySelectedGroupField() : 'Username';\n                const metrics = (Array.isArray(selectedMetrics) && selectedMetrics.length) ? selectedMetrics : ['Totale'];\n                const blockW = 860;\n                const blockH = 330;\n                cv.width = Math.max(980, blockW + 40);\n                cv.height = Math.max(380, metrics.length * blockH + 40);\n                ctx.clearRect(0,0,cv.width,cv.height);\n                const colors=['#38bdf8','#22c55e','#f59e0b','#ef4444','#a855f7','#14b8a6','#60a5fa','#e879f9'];\n                const buildMetricTimeline = function(metric) {\n                    const timeline = {}; const groupTotals = {};\n                    rows.forEach(function(r){\n                        const timeKey = (r.Data || '') + ' ' + (r.Ora || '00:00');\n                        const grp = String(r[groupField] || 'N\/D').trim() || 'N\/D';\n                        const value = parseNumSafe(r[metric]) || 0;\n                        if (!timeline[timeKey]) timeline[timeKey] = {};\n                        timeline[timeKey][grp] = (timeline[timeKey][grp] || 0) + value;\n                        groupTotals[grp] = (groupTotals[grp] || 0) + value;\n                    });\n                    const entries = Object.entries(timeline).sort(function(a,b){ return a[0].localeCompare(b[0]); }).slice(-12);\n                    const groups = Object.entries(groupTotals).sort(function(a,b){ return b[1]-a[1]; }).slice(0,6).map(function(e){ return e[0]; });\n                    return {entries:entries, groups:groups, totals:groupTotals};\n                };\n                let drew = false;\n                metrics.forEach(function(metric, metricIndex){\n                    const data = buildMetricTimeline(metric);\n                    const entries = data.entries; const groups = data.groups;\n                    const originX = 20; const originY = 20 + metricIndex * blockH;\n                    const chartW = 610;\n                    const legendX = originX + chartW + 28;\n                    ctx.fillStyle = '#fff'; ctx.font = 'bold 14px Arial'; ctx.fillText(window.getActivityMetricLabel ? window.getActivityMetricLabel([metric]) : metric, originX + 8, originY + 20);\n                    if (!entries.length || !groups.length) { ctx.fillStyle='#cbd5e1'; ctx.font='12px Arial'; ctx.fillText('Nessun dato', originX+8, originY+48); return; }\n                    drew = true;\n                    const max = Math.max(1, ...entries.flatMap(function(e){ return groups.map(function(g){ return e[1][g] || 0; }); }));\n                    const baseY = originY + 250;\n                    const left = originX + 52;\n                    const step = Math.max(36, Math.floor((chartW - 70) \/ Math.max(1, entries.length)));\n                    const barW = Math.max(6, Math.floor(28 \/ Math.max(1, groups.length)));\n                    ctx.strokeStyle='#94a3b8'; ctx.beginPath(); ctx.moveTo(left, originY + 38); ctx.lineTo(left, baseY); ctx.lineTo(left + step * entries.length, baseY); ctx.stroke();\n                    entries.forEach(function(entry, i){\n                        const label = entry[0]; const metricsAtTime = entry[1];\n                        const groupX = left + 10 + i * step;\n                        groups.forEach(function(grp, gi){\n                            const val = metricsAtTime[grp] || 0;\n                            const h = (val\/max)*170;\n                            const x = groupX + gi*(barW+3);\n                            ctx.fillStyle='rgba(0,0,0,0.25)'; ctx.fillRect(x+4, baseY-h+4, barW, h);\n                            ctx.fillStyle=colors[gi%colors.length]; ctx.fillRect(x, baseY-h, barW, h);\n                            if (h > 0) { ctx.fillStyle='#fff'; ctx.font='9px Arial'; ctx.fillText(fmtSafe(val), x-2, baseY-h-6); }\n                        });\n                        ctx.save(); ctx.fillStyle='#fff'; ctx.font='9px Arial'; ctx.translate(groupX, baseY+14); ctx.rotate(-0.6); ctx.fillText(String(label).slice(0,16),0,0); ctx.restore();\n                    });\n                    groups.forEach(function(grp, gi){\n                        const ly = originY + 46 + gi * 20;\n                        ctx.fillStyle = colors[gi%colors.length]; ctx.fillRect(legendX, ly - 10, 12, 12);\n                        ctx.fillStyle = '#fff'; ctx.font = '11px Arial';\n                        const label = String(grp).length > 25 ? String(grp).slice(0,24) + '\u2026' : String(grp);\n                        ctx.fillText(label + ' - ' + fmtSafe(data.totals[grp] || 0), legendX + 18, ly);\n                    });\n                    ctx.fillStyle = '#cbd5e1'; ctx.font = '11px Arial'; ctx.fillText('Raggruppa per: ' + groupField, originX + 8, originY + blockH - 12);\n                });\n                if (!drew) { ctx.fillStyle='#fff'; ctx.font='14px Arial'; ctx.fillText('Nessun dato',20,20); }\n            };\n        } catch(e) {\n            try { console.error('Shop & Service fix 30.9.181 non applicato:', e); } catch(ignore) {}\n        }\n        }\n        if (document.readyState === 'loading') {\n            document.addEventListener('DOMContentLoaded', function(){ setTimeout(tiInstallFixPersi181, 0); });\n        } else {\n            setTimeout(tiInstallFixPersi181, 0);\n        }\n    })();\n\n\n    \/* 30.9.182 - Fix chirurgici: gestione tabella acquisti + Report attivita *\/\n    (function(){\n        function tiInstallPurchaseReport182(){\n            try {\n                const byId = function(id){ return document.getElementById(id); };\n                const esc = function(v){\n                    if (window.escapeHtml) return window.escapeHtml(String(v == null ? '' : v));\n                    return String(v == null ? '' : v).replace(\/[&<>\"']\/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c]; });\n                };\n                const parseNum = function(v){\n                    if (v === null || v === undefined) return 0;\n                    let s = String(v).trim();\n                    if (!s) return 0;\n                    s = s.replace(\/[\u20ac\\s]\/g, '');\n                    if (s.indexOf(',') !== -1 && s.indexOf('.') !== -1) s = s.replace(\/\\.\/g, '').replace(',', '.');\n                    else if (s.indexOf(',') !== -1) s = s.replace(',', '.');\n                    const n = parseFloat(s.replace(\/[^0-9.\\-]\/g, ''));\n                    return isNaN(n) ? 0 : n;\n                };\n                const fmt = function(n){\n                    const num = Math.round((parseFloat(n) || 0) * 100) \/ 100;\n                    if (window.fmtNum) return window.fmtNum(num);\n                    return num.toLocaleString('it-IT', {minimumFractionDigits:2, maximumFractionDigits:2});\n                };\n                const norm = function(s){\n                    return String(s || '').normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase().replace(\/[^a-z0-9]\/g, '');\n                };\n                const realTableName = function(data, wanted, fallback){\n                    const tables = data && data.Tabelle ? data.Tabelle : {};\n                    const want = norm(wanted);\n                    const keys = Object.keys(tables || {});\n                    for (let i=0;i<keys.length;i++) if (norm(keys[i]) === want) return keys[i];\n                    for (let i=0;i<keys.length;i++) if (norm(keys[i]).indexOf(want) !== -1 || want.indexOf(norm(keys[i])) !== -1) return keys[i];\n                    return fallback || wanted;\n                };\n                const getField = function(row, names, fallback){\n                    row = row || {};\n                    const keys = Object.keys(row);\n                    for (let i=0;i<names.length;i++) {\n                        const want = norm(names[i]);\n                        for (let j=0;j<keys.length;j++) if (norm(keys[j]) === want) return row[keys[j]];\n                    }\n                    return fallback;\n                };\n                const setField = function(row, names, value){\n                    row = row || {};\n                    const keys = Object.keys(row);\n                    for (let i=0;i<names.length;i++) {\n                        const want = norm(names[i]);\n                        for (let j=0;j<keys.length;j++) if (norm(keys[j]) === want) { row[keys[j]] = value; return; }\n                    }\n                    row[names[0]] = value;\n                };\n                const marginNet = function(costNet, marginText){\n                    const cost = parseNum(costNet);\n                    let m = String(marginText || '').trim();\n                    if (!m) return Math.max(0, cost);\n                    const isPct = m.indexOf('%') !== -1;\n                    m = m.replace(\/[%\u20ac\\s]\/g, '');\n                    let sign = 1;\n                    if (m.charAt(0) === '-') { sign = -1; m = m.slice(1); }\n                    else if (m.charAt(0) === '+') { m = m.slice(1); }\n                    const val = parseNum(m);\n                    return Math.max(0, isPct ? (cost + (cost * sign * val \/ 100)) : (cost + sign * val));\n                };\n                const grossSale = function(costNet, marginText, ivaText){\n                    let iva = parseNum(ivaText);\n                    if (iva <= 0) iva = 22;\n                    return Math.round(marginNet(costNet, marginText) * (1 + iva \/ 100) * 100) \/ 100;\n                };\n                const dateParts = function(){\n                    const d = new Date();\n                    return {\n                        date:String(d.getDate()).padStart(2,'0') + '\/' + String(d.getMonth()+1).padStart(2,'0') + '\/' + d.getFullYear(),\n                        time:String(d.getHours()).padStart(2,'0') + ':' + String(d.getMinutes()).padStart(2,'0')\n                    };\n                };\n                const base64Utf8 = function(str){\n                    try { return btoa(unescape(encodeURIComponent(str))); }\n                    catch(e) { return btoa(str); }\n                };\n                window.tiPurchaseCalcSaleGross = grossSale;\n                window.tiPurchaseMarginAdjustedNet = marginNet;\n\n                window.purchaseGetCatalogItems = function(){\n                    const data = window.currentDbData || {};\n                    const tables = data && data.Tabelle ? data.Tabelle : {};\n                    const out = [];\n                    Object.keys(tables || {}).forEach(function(tn){\n                        if (!\/(prodott|serviz)\/i.test(tn)) return;\n                        const rows = Array.isArray(tables[tn]) ? tables[tn] : [];\n                        rows.forEach(function(r, idx){\n                            if (!r || typeof r !== 'object') return;\n                            const isServ = \/(serviz)\/i.test(tn);\n                            const name = String(getField(r, isServ ? ['Servizio','Descrizione','Nome','Titolo'] : ['Prodotto','Descrizione','Nome','Titolo'], '') || '').trim();\n                            if (!name) return;\n                            out.push({\n                                table:tn,\n                                index:idx,\n                                type:isServ ? 'S' : 'P',\n                                typeLabel:isServ ? 'servizio' : 'prodotto',\n                                name:name,\n                                desc:String(getField(r, ['Descrizione','Note','Nota'], '') || '').trim(),\n                                cost:parseNum(getField(r, ['Costo','Costo acquisto','Costo_acquisto'], 0)),\n                                sale:parseNum(getField(r, ['Prezzo','Prezzo vendita','Prezzo_vendita'], 0)),\n                                iva:parseNum(getField(r, ['IVA_VAT','IVA','Iva','VAT'], 22)) || 22\n                            });\n                        });\n                    });\n                    return out;\n                };\n\n                window.ensurePurchaseModal = function(){\n                    let ov = byId('ti-purchase-ov');\n                    if (ov && ov.dataset && ov.dataset.v182 === '1') return ov;\n                    if (ov && ov.parentNode) ov.parentNode.removeChild(ov);\n                    ov = document.createElement('div');\n                    ov.id = 'ti-purchase-ov';\n                    ov.className = 'ti-modal-overlay ti-ov ti-closable-ov';\n                    ov.dataset.v182 = '1';\n                    ov.style.display = 'none';\n                    ov.style.zIndex = '2147482000';\n                    ov.style.alignItems = 'center';\n                    ov.style.justifyContent = 'center';\n                    ov.innerHTML = `\n                        <div class=\"ti-modal ti-purchase-modal\" style=\"width:min(1480px,98vw);max-width:98vw;max-height:92vh;overflow:auto;background:#0f172a;color:#fff;border-radius:16px;padding:18px;box-shadow:0 18px 60px rgba(0,0,0,.45);margin:0 auto;\">\n                            <div style=\"display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px;\">\n                                <div>\n                                    <h2 style=\"margin:0;font-size:22px;\">Acquisti<\/h2>\n                                    <div style=\"font-size:14px;color:#cbd5e1;margin-top:4px;\">Gestisci le righe acquisto. Il costo \u00e8 netto IVA; il prezzo vendita proposto include margine e IVA.<\/div>\n                                <\/div>\n                                <button type=\"button\" class=\"ti-btn\" style=\"background:#dc2626;color:#fff;\" onclick=\"window.closePurchaseManualFlow()\">X<\/button>\n                            <\/div>\n                            <div style=\"display:flex;gap:8px;flex-wrap:wrap;margin:10px 0 14px 0;\">\n                                <button type=\"button\" class=\"ti-btn ti-purchase-add-row\" style=\"background:#22c55e;color:#052e16;font-weight:700;\">+ Aggiungi riga manuale<\/button>\n                                <button type=\"button\" class=\"ti-btn\" style=\"background:#f59e0b;color:#111827;font-weight:700;\" onclick=\"window.pickPurchaseImportFile()\">Import file acquisti<\/button>\n                                <button type=\"button\" class=\"ti-btn\" style=\"background:#64748b;color:#fff;font-weight:700;\" onclick=\"window.clearPurchaseRows()\">Svuota righe<\/button>\n                            <\/div>\n                            <div class=\"ti-purchase-table-wrap\" style=\"overflow:auto;border:1px solid #334155;border-radius:12px;\">\n                                <table id=\"ti-purchase-table\" style=\"width:100%;border-collapse:collapse;min-width:1240px;background:#020617;\">\n                                    <thead>\n                                        <tr style=\"background:#1e293b;\">\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:left;\">Prodotto o servizio<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:left;\">Tipo<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Q.t\u00e0<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Costo netto<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Margine<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Prezzo vendita IVA incl.<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">IVA %<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Tot. acquisto netto<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Tot. vendita<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:left;\">Note<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:center;\">Azioni<\/th>\n                                        <\/tr>\n                                    <\/thead>\n                                    <tbody id=\"ti-purchase-body\"><\/tbody>\n                                <\/table>\n                            <\/div>\n                            <div id=\"ti-purchase-total\" class=\"ti-purchase-summary-grid\" aria-live=\"polite\"><\/div>\n                            <div class=\"ti-purchase-toolbar-fixed\" style=\"display:flex;justify-content:center;gap:8px;margin-top:14px;flex-wrap:wrap;\">\n                                <button type=\"button\" id=\"ti-purchase-confirm-btn\" class=\"ti-btn\" style=\"background:#38bdf8;color:#082f49;font-weight:800;\" onclick=\"window.confirmPurchaseRows()\">Conferma righe acquisto<\/button>\n                                <button type=\"button\" class=\"ti-btn btn-close\" onclick=\"window.closePurchaseManualFlow()\">Chiudi<\/button>\n                            <\/div>\n                        <\/div>`;\n                    document.body.appendChild(ov);\n                    return ov;\n                };\n\n                window.clearPurchaseRows = function(){\n                    const body = byId('ti-purchase-body');\n                    if (!body) return;\n                    if (body.children.length && window.confirm && !window.confirm('Svuotare tutte le righe acquisto?')) return;\n                    body.innerHTML = '';\n                    window.addPurchaseManualRow();\n                    window.refreshPurchaseTotals();\n                };\n\n                window.recalcPurchaseRow = function(row, force){\n                    try {\n                        if (!row) return;\n                        const qty = parseNum((row.querySelector('.ti-pur-qty') || {}).value || '0');\n                        const costEl = row.querySelector('.ti-pur-cost');\n                        const marginEl = row.querySelector('.ti-pur-margin');\n                        const saleEl = row.querySelector('.ti-pur-sale');\n                        const ivaEl = row.querySelector('.ti-pur-iva');\n                        const cost = parseNum(costEl ? costEl.value : 0);\n                        const sale = grossSale(cost, marginEl ? marginEl.value : '', ivaEl ? ivaEl.value : '22');\n                        if (saleEl && (force || document.activeElement !== saleEl || saleEl.dataset.auto === '1')) {\n                            saleEl.value = sale ? String(sale).replace('.', ',') : '';\n                            saleEl.dataset.auto = '1';\n                        }\n                        const rowSale = saleEl ? (parseNum(saleEl.value) || sale) : sale;\n                        const netTot = Math.round(qty * cost * 100) \/ 100;\n                        const grossTot = Math.round(qty * rowSale * 100) \/ 100;\n                        const netOut = row.querySelector('.ti-pur-row-total-net');\n                        const grossOut = row.querySelector('.ti-pur-row-total-gross');\n                        if (netOut) netOut.textContent = fmt(netTot);\n                        if (grossOut) grossOut.textContent = fmt(grossTot);\n                    } catch(e) {}\n                };\n\n                window.validatePurchaseRows = function(mark){\n                    const errors = [];\n                    document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr, idx){\n                        tr.classList.remove('is-invalid');\n                        const n = String((tr.querySelector('.ti-pur-name') || {}).value || '').trim();\n                        const q = parseNum((tr.querySelector('.ti-pur-qty') || {}).value || '0');\n                        const c = parseNum((tr.querySelector('.ti-pur-cost') || {}).value || '0');\n                        const ivaEl = tr.querySelector('.ti-pur-iva');\n                        if (ivaEl && !String(ivaEl.value || '').trim()) ivaEl.value = '22';\n                        const bad = !n || q <= 0 || c < 0;\n                        if (bad) {\n                            if (mark) tr.classList.add('is-invalid');\n                            if (!n) errors.push('Riga ' + (idx + 1) + ': prodotto\/servizio mancante');\n                            if (q <= 0) errors.push('Riga ' + (idx + 1) + ': quantit\u00e0 non valida');\n                            if (c < 0) errors.push('Riga ' + (idx + 1) + ': costo non valido');\n                        }\n                    });\n                    return errors;\n                };\n\n                window.addPurchaseManualRow = function(item){\n                    try {\n                        const ov = byId('ti-purchase-ov') || window.ensurePurchaseModal();\n                        const body = byId('ti-purchase-body');\n                        if (!body) return;\n                        if (ov) ov.style.display = 'flex';\n                        const safe = item || {};\n                        const tr = document.createElement('tr');\n                        tr.className = 'ti-purchase-row';\n                        tr.dataset.catalogTable = safe.table || '';\n                        tr.dataset.catalogIndex = String(safe.index != null ? safe.index : -1);\n                        tr.dataset.targetType = safe.type === 'S' ? 'servizio' : 'prodotto';\n                        const iva = safe.iva || '22';\n                        const cost = safe.cost ? String(safe.cost).replace('.', ',') : '';\n                        const sale = safe.sale ? String(safe.sale).replace('.', ',') : '';\n                        tr.innerHTML = `\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-name ti-purchase-row-search\" value=\"${esc(safe.name || '')}\" placeholder=\"Cerca prodotto o servizio\" autocomplete=\"off\" style=\"width:100%;min-width:230px;box-sizing:border-box;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><select class=\"ti-pur-type\" style=\"width:76px;padding:8px;border-radius:8px;background:#0f172a;color:#fff;border:1px solid #334155;\"><option value=\"P\" ${safe.type === 'S' ? '' : 'selected'}>P<\/option><option value=\"S\" ${safe.type === 'S' ? 'selected' : ''}>S<\/option><\/select><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-qty\" value=\"${esc(safe.qty || '1')}\" inputmode=\"decimal\" style=\"width:72px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-cost\" value=\"${esc(cost)}\" inputmode=\"decimal\" style=\"width:100px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-margin\" value=\"${esc(safe.margin || '')}\" placeholder=\"+10%\" style=\"width:86px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-sale\" data-auto=\"${sale ? '0' : '1'}\" value=\"${esc(sale)}\" inputmode=\"decimal\" style=\"width:112px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-iva\" value=\"${esc(iva)}\" inputmode=\"decimal\" style=\"width:68px;text-align:right;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;text-align:right;\"><span class=\"ti-pur-row-total-net\">0,00<\/span><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;text-align:right;\"><span class=\"ti-pur-row-total-gross\">0,00<\/span><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-note\" value=\"${esc(safe.desc || '')}\" placeholder=\"Note\" style=\"width:100%;min-width:160px;box-sizing:border-box;padding:8px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;text-align:center;white-space:nowrap;\"><button type=\"button\" class=\"ti-btn ti-pur-dup\" title=\"Duplica riga\" style=\"background:#2563eb;color:#fff;margin-right:4px;\">\u2197<\/button><button type=\"button\" class=\"ti-btn ti-pur-del\" title=\"Elimina riga\" style=\"background:#dc2626;color:#fff;\">X<\/button><\/td>`;\n                        body.appendChild(tr);\n                        const name = tr.querySelector('.ti-pur-name');\n                        if (name) {\n                            name.addEventListener('input', function(){ window.__tiPurchaseActiveRow = tr; window.showPurchaseRowSearch(name); });\n                            name.addEventListener('focus', function(){ window.__tiPurchaseActiveRow = tr; if (String(name.value || '').trim()) window.showPurchaseRowSearch(name); });\n                        }\n                        const typeEl = tr.querySelector('.ti-pur-type');\n                        if (typeEl) typeEl.addEventListener('change', function(){ tr.dataset.targetType = this.value === 'S' ? 'servizio' : 'prodotto'; });\n                        ['.ti-pur-qty','.ti-pur-cost','.ti-pur-margin','.ti-pur-iva'].forEach(function(sel){\n                            const el = tr.querySelector(sel);\n                            if (el) el.addEventListener('input', function(){\n                                const saleEl = tr.querySelector('.ti-pur-sale');\n                                if (saleEl) saleEl.dataset.auto = '1';\n                                window.recalcPurchaseRow(tr, true);\n                                window.refreshPurchaseTotals();\n                            });\n                        });\n                        const saleEl = tr.querySelector('.ti-pur-sale');\n                        if (saleEl) saleEl.addEventListener('input', function(){ saleEl.dataset.auto = '0'; window.recalcPurchaseRow(tr, false); window.refreshPurchaseTotals(); });\n                        const del = tr.querySelector('.ti-pur-del');\n                        if (del) del.addEventListener('click', function(){ tr.remove(); if (!body.children.length) window.addPurchaseManualRow(); window.refreshPurchaseTotals(); });\n                        const dup = tr.querySelector('.ti-pur-dup');\n                        if (dup) dup.addEventListener('click', function(){ window.addPurchaseManualRow(window.purchaseRowToItem(tr)); });\n                        window.recalcPurchaseRow(tr, !sale);\n                        window.refreshPurchaseTotals();\n                        if (!safe.name && name) { try { name.focus(); } catch(ignore) {} }\n                    } catch(e) { if (window.tiAlert) window.tiAlert('Errore aggiunta riga acquisto: ' + (e.message || e)); }\n                };\n\n                window.purchaseRowToItem = function(tr){\n                    tr = tr || {};\n                    return {\n                        table:tr.dataset ? (tr.dataset.catalogTable || '') : '',\n                        index:tr.dataset ? parseInt(tr.dataset.catalogIndex || '-1', 10) : -1,\n                        type:String(((tr.querySelector && tr.querySelector('.ti-pur-type')) || {}).value || 'P'),\n                        name:String(((tr.querySelector && tr.querySelector('.ti-pur-name')) || {}).value || ''),\n                        qty:String(((tr.querySelector && tr.querySelector('.ti-pur-qty')) || {}).value || '1'),\n                        cost:String(((tr.querySelector && tr.querySelector('.ti-pur-cost')) || {}).value || ''),\n                        margin:String(((tr.querySelector && tr.querySelector('.ti-pur-margin')) || {}).value || ''),\n                        sale:String(((tr.querySelector && tr.querySelector('.ti-pur-sale')) || {}).value || ''),\n                        iva:String(((tr.querySelector && tr.querySelector('.ti-pur-iva')) || {}).value || '22'),\n                        desc:String(((tr.querySelector && tr.querySelector('.ti-pur-note')) || {}).value || '')\n                    };\n                };\n\n                window.refreshPurchaseTotals = function(){\n                    try {\n                        let rows = 0, qtyTot = 0, netTot = 0, saleTot = 0;\n                        document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr){\n                            window.recalcPurchaseRow(tr, false);\n                            const qty = parseNum((tr.querySelector('.ti-pur-qty') || {}).value || '0');\n                            const cost = parseNum((tr.querySelector('.ti-pur-cost') || {}).value || '0');\n                            const sale = parseNum((tr.querySelector('.ti-pur-sale') || {}).value || '0');\n                            rows += 1;\n                            qtyTot += qty;\n                            netTot += qty * cost;\n                            saleTot += qty * sale;\n                        });\n                        const out = byId('ti-purchase-total');\n                        if (out) out.innerHTML = '<div class=\"ti-purchase-summary-card\">Righe<b>' + rows + '<\/b><\/div>'\n                            + '<div class=\"ti-purchase-summary-card\">Quantit\u00e0<b>' + fmt(qtyTot) + '<\/b><\/div>'\n                            + '<div class=\"ti-purchase-summary-card\">Totale acquisto netto<b>\u20ac ' + fmt(netTot) + '<\/b><\/div>'\n                            + '<div class=\"ti-purchase-summary-card\">Totale vendita IVA incl.<b>\u20ac ' + fmt(saleTot) + '<\/b><\/div>';\n                    } catch(e) {}\n                };\n\n                window.selectPurchaseCatalogItemForRow = function(item, row){\n                    try {\n                        item = item || {};\n                        row = row || window.__tiPurchaseActiveRow;\n                        if (!row) return;\n                        row.dataset.catalogTable = item.table || '';\n                        row.dataset.catalogIndex = String(item.index != null ? item.index : -1);\n                        row.dataset.targetType = item.type === 'S' ? 'servizio' : 'prodotto';\n                        const setVal = function(sel, val){ const el = row.querySelector(sel); if (el) el.value = val == null ? '' : String(val).replace('.', ','); };\n                        setVal('.ti-pur-name', item.name || '');\n                        setVal('.ti-pur-type', item.type === 'S' ? 'S' : 'P');\n                        setVal('.ti-pur-cost', item.cost || '');\n                        setVal('.ti-pur-iva', item.iva || '22');\n                        setVal('.ti-pur-note', item.desc || '');\n                        const sale = row.querySelector('.ti-pur-sale');\n                        if (sale) { sale.dataset.auto = '1'; sale.value = item.sale ? String(item.sale).replace('.', ',') : ''; }\n                        window.recalcPurchaseRow(row, !item.sale);\n                        window.refreshPurchaseTotals();\n                        window.closePurchaseSearchPopover && window.closePurchaseSearchPopover();\n                    } catch(e) {}\n                };\n\n                window.purchaseCollectRows = function(){\n                    const rows = [];\n                    document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr){\n                        const item = window.purchaseRowToItem(tr);\n                        item.table = tr.dataset.catalogTable || '';\n                        item.index = parseInt(tr.dataset.catalogIndex || '-1', 10);\n                        if (String(item.name || '').trim()) rows.push(item);\n                    });\n                    return rows;\n                };\n\n                window.confirmPurchaseRows = function(){\n                    const btn = byId('ti-purchase-confirm-btn');\n                    try {\n                        const errors = window.validatePurchaseRows(true);\n                        if (errors.length) { if (window.tiAlert) window.tiAlert('Correggi le righe acquisto:\\n' + errors.slice(0, 8).join('\\n')); return; }\n                        const rows = window.purchaseCollectRows();\n                        if (!rows.length) { if (window.tiAlert) window.tiAlert('Inserisci almeno una riga acquisto.'); return; }\n                        const dbEl = byId('ti-ditta');\n                        const dbVal = dbEl ? dbEl.value : '';\n                        if (!dbVal || dbVal === 'NEW_DB') { if (window.tiAlert) window.tiAlert('Seleziona prima una ditta.'); return; }\n                        const source = window.currentDbData;\n                        if (!source || !source.Tabelle) { if (window.tiAlert) window.tiAlert('Database non caricato. Riapri Acquisti dopo aver selezionato la ditta.'); return; }\n                        if (btn) { btn.disabled = true; btn.dataset.oldText = btn.innerHTML; btn.innerHTML = 'Salvataggio...'; }\n                        const data = JSON.parse(JSON.stringify(source));\n                        const prodTable = realTableName(data, 'Prodotti', 'Prodotti');\n                        const servTable = realTableName(data, 'Servizi', 'Servizi');\n                        const actTable = realTableName(data, 'Attivita', 'Attivita');\n                        if (!data.Tabelle[prodTable]) data.Tabelle[prodTable] = [];\n                        if (!data.Tabelle[servTable]) data.Tabelle[servTable] = [];\n                        if (!data.Tabelle[actTable]) data.Tabelle[actTable] = [];\n                        const now = dateParts();\n                        const nextId = function(table){ return (Array.isArray(data.Tabelle[table]) ? data.Tabelle[table].length : 0) + 1; };\n                        rows.forEach(function(r){\n                            const isServ = String(r.type || 'P') === 'S';\n                            const t = isServ ? servTable : prodTable;\n                            const nameFields = isServ ? ['Servizio','Nome','Descrizione'] : ['Prodotto','Nome','Descrizione'];\n                            const tableRows = Array.isArray(data.Tabelle[t]) ? data.Tabelle[t] : (data.Tabelle[t] = []);\n                            let idx = (r.table === t && r.index >= 0 && tableRows[r.index]) ? r.index : -1;\n                            if (idx < 0) {\n                                const wanted = norm(r.name);\n                                idx = tableRows.findIndex(function(row){ return norm(getField(row, nameFields, '')) === wanted; });\n                            }\n                            let rec;\n                            if (idx >= 0) rec = tableRows[idx];\n                            else { rec = {}; setField(rec, ['ID','Id','id'], nextId(t)); tableRows.push(rec); }\n                            setField(rec, nameFields, r.name);\n                            setField(rec, ['Costo','Costo acquisto','Costo_acquisto'], parseNum(r.cost));\n                            setField(rec, ['Margine'], String(r.margin || ''));\n                            const gross = parseNum(r.sale) || grossSale(r.cost, r.margin, r.iva);\n                            setField(rec, ['Prezzo','Prezzo vendita','Prezzo_vendita'], gross);\n                            setField(rec, ['IVA_VAT','IVA','Iva','VAT'], parseNum(r.iva) || 22);\n                            if (r.note) setField(rec, ['Note','Nota','Descrizione'], r.note);\n                            if (!isServ) {\n                                const oldQty = parseNum(getField(rec, ['Quantit\u00e0','Quantita','Disponibilita','Disponibilit\u00e0'], 0));\n                                setField(rec, ['Quantit\u00e0','Quantita'], Math.round((oldQty + parseNum(r.qty)) * 100) \/ 100);\n                            }\n                            const act = {};\n                            setField(act, ['ID','Id','id'], nextId(actTable));\n                            setField(act, ['Username','Utente','user'], window.tiUser || '');\n                            setField(act, ['Descrizione attivit\u00e0','Descrizione attivita','Descrizione','Dettaglio','Note'], 'Acquisto ' + r.name);\n                            setField(act, ['Prodotto'], r.name);\n                            setField(act, ['Prezzo'], parseNum(r.cost));\n                            setField(act, ['Quantit\u00e0','Quantita'], parseNum(r.qty));\n                            setField(act, ['Totale'], Math.round(parseNum(r.cost) * parseNum(r.qty) * 100) \/ 100);\n                            setField(act, ['Margine'], String(r.margin || ''));\n                            setField(act, ['IVA_VAT','IVA','Iva','VAT'], parseNum(r.iva) || 22);\n                            setField(act, ['Prezzo vendita','Prezzo_vendita'], gross);\n                            setField(act, ['Esito'], 'acquisto');\n                            setField(act, ['Data'], now.date);\n                            setField(act, ['Ora'], now.time);\n                            setField(act, ['Stato'], 'Attivo');\n                            data.Tabelle[actTable].push(act);\n                        });\n                        const fd = new FormData();\n                        fd.append('ti_action', 'ti_ai_save_full_db');\n                        fd.append('db', dbVal);\n                        fd.append('payload', base64Utf8(JSON.stringify(data)));\n                        const done = function(res){\n                            if (btn) { btn.disabled = false; btn.innerHTML = btn.dataset.oldText || 'Conferma righe acquisto'; }\n                            if (res && res.success) {\n                                window.currentDbData = data;\n                                window.closePurchaseManualFlow && window.closePurchaseManualFlow();\n                                if (window.tiAlert) window.tiAlert('\u2705 Righe acquisto confermate e salvate nel database.');\n                                if (res.data && res.data.needs_reload) setTimeout(function(){ location.reload(); }, 900);\n                            } else {\n                                const msg = (res && res.data && res.data.message) ? res.data.message : 'Salvataggio acquisto non riuscito.';\n                                if (window.tiAlert) window.tiAlert('\u26a0\ufe0f ' + msg);\n                            }\n                        };\n                        const fail = function(e){\n                            if (btn) { btn.disabled = false; btn.innerHTML = btn.dataset.oldText || 'Conferma righe acquisto'; }\n                            if (window.tiAlert) window.tiAlert('Errore salvataggio acquisto: ' + ((e && e.message) || e));\n                        };\n                        const url = window.tiAjaxUrl || window.tiUrl;\n                        if (window.fetchJsonSafe) window.fetchJsonSafe(url, {method:'POST', body:fd, credentials:'same-origin'}).then(done).catch(fail);\n                        else fetch(url, {method:'POST', body:fd, credentials:'same-origin'}).then(function(r){ return r.json(); }).then(done).catch(fail);\n                    } catch(e) {\n                        if (btn) { btn.disabled = false; btn.innerHTML = btn.dataset.oldText || 'Conferma righe acquisto'; }\n                        if (window.tiAlert) window.tiAlert('Errore conferma acquisto: ' + (e.message || e));\n                    }\n                };\n                window.purchaseSendToChat = function(){ window.confirmPurchaseRows(); };\n\n                if (!window.__tiPurchaseDelegated182) {\n                    document.addEventListener('click', function(ev){\n                        const btn = ev.target && ev.target.closest ? ev.target.closest('.ti-purchase-add-row,.ti-ss-add-manual-row') : null;\n                        if (!btn) return;\n                        ev.preventDefault();\n                        ev.stopPropagation();\n                        if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();\n                        window.addPurchaseManualRow();\n                    }, true);\n                    window.__tiPurchaseDelegated182 = true;\n                }\n\n                window.ti182NormalizeActivityOutcome = function(row){\n                    row = row || {};\n                    const current = String(row.Esito || '').trim();\n                    if (\/vendit\/i.test(current)) return 'vendita';\n                    if (\/acquist\/i.test(current)) return 'acquisto';\n                    if (\/configur\/i.test(current)) return current || 'configurazione';\n                    if (\/appunt|prenot\/i.test(current)) return current || 'appuntamento';\n                    const txt = String(row.Descrizione || row.Dettaglio || row.Note || row.Nota || row.Istruzioni || '').toLowerCase();\n                    if (\/acquist|fornitor\/.test(txt)) return current || 'acquisto';\n                    if (\/appuntament|prenotaz\/.test(txt)) return current || 'appuntamento';\n                    if (\/configur|setup|modifica database\/.test(txt)) return current || 'configurazione';\n                    const price = parseNum(row.Prezzo || row.Totale || 0);\n                    const hasProduct = String(row.Prodotto || row.Servizio || '').trim() !== '';\n                    if (!current && (hasProduct || price > 0)) return 'vendita';\n                    return current;\n                };\n                if (typeof window.getActivityRowsNormalized === 'function' && !window.__tiActivityOutcomeWrap182) {\n                    const prevRowsNorm = window.getActivityRowsNormalized;\n                    window.getActivityRowsNormalized = function(){\n                        const arr = prevRowsNorm.apply(this, arguments) || [];\n                        return arr.map(function(r){ if (r && typeof r === 'object') r.Esito = window.ti182NormalizeActivityOutcome(r); return r; });\n                    };\n                    window.__tiActivityOutcomeWrap182 = true;\n                }\n                window.ti182OutcomeMatches = function(row, filter){\n                    filter = String(filter || '');\n                    const val = String(row && row.Esito ? row.Esito : '').trim();\n                    if (!filter) return true;\n                    if (filter === '__empty__') return !val;\n                    return val === filter;\n                };\n                if (typeof window.getActivityRowsNormalized === 'function') {\n                    window.getFilteredActivityRows = function(includeExcludedRows){\n                        let rows = window.getActivityRowsNormalized() || [];\n                        const state = window.activityReportState || {filters:{}, rowFlags:{}};\n                        const rowFlags = state.rowFlags || {};\n                        if (state.excludeConfigActivities && window.userCanSeeConfigurationActivities && window.userCanSeeConfigurationActivities()) rows = rows.filter(function(r){ return !r.__isConfigActivity; });\n                        rows = rows.filter(function(r){\n                            return Object.entries(state.filters || {}).every(function(pair){\n                                const k = pair[0], v = pair[1];\n                                if (!v) return true;\n                                return String(r[k] == null ? '' : r[k]).toLowerCase().indexOf(String(v).toLowerCase()) !== -1;\n                            });\n                        });\n                        const outcomeFilter = String(state.outcomeFilter || '');\n                        if (outcomeFilter) rows = rows.filter(function(r){ return window.ti182OutcomeMatches(r, outcomeFilter); });\n                        if (!includeExcludedRows) rows = rows.filter(function(r){ return !rowFlags[r.__rowKey]; });\n                        const dir = state.sortDir === 'asc' ? 1 : -1;\n                        const col = state.sortCol || 'DataOra';\n                        rows.sort(function(a,b){\n                            const av = col === 'DataOra' ? a.__dataora : a[col];\n                            const bv = col === 'DataOra' ? b.__dataora : b[col];\n                            if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * dir;\n                            return String(av == null ? '' : av).localeCompare(String(bv == null ? '' : bv), 'it', {numeric:true, sensitivity:'base'}) * dir;\n                        });\n                        return rows;\n                    };\n                    window.getActivityIncludedRows = function(){ return window.getFilteredActivityRows(false); };\n                }\n                window.injectActivityOutcomeFilter = function(){\n                    try {\n                        const toolbar = document.querySelector('#ti-report-cnt .ti-report-toolbar');\n                        if (!toolbar) return;\n                        const old = toolbar.querySelector('.ti-report-outcome-filter-wrap');\n                        if (old) old.remove();\n                        const rawRows = window.getActivityRowsNormalized ? window.getActivityRowsNormalized() : [];\n                        const opts = [];\n                        rawRows.forEach(function(r){ const e = String(r && r.Esito ? r.Esito : '').trim(); if (e && opts.indexOf(e) === -1) opts.push(e); });\n                        opts.sort(function(a,b){ return a.localeCompare(b, 'it'); });\n                        const current = window.activityReportState ? String(window.activityReportState.outcomeFilter || '') : '';\n                        const label = document.createElement('label');\n                        label.className = 'ti-report-outcome-filter-wrap';\n                        let html = '<span>Esito<\/span><select class=\"ti-report-outcome-filter\" onchange=\"window.setActivityOutcomeFilter(this.value)\"><option value=\"\">Tutti<\/option><option value=\"__empty__\" ' + (current === '__empty__' ? 'selected' : '') + '>Senza esito<\/option>';\n                        opts.forEach(function(o){ html += '<option value=\"' + esc(o) + '\" ' + (o === current ? 'selected' : '') + '>' + esc(o) + '<\/option>'; });\n                        if (current && current !== '__empty__' && opts.indexOf(current) === -1) html += '<option value=\"' + esc(current) + '\" selected>' + esc(current) + '<\/option>';\n                        html += '<\/select>';\n                        label.innerHTML = html;\n                        toolbar.appendChild(label);\n                    } catch(e) {}\n                };\n                window.setActivityOutcomeFilter = function(v){\n                    if (!window.activityReportState) window.activityReportState = {};\n                    window.activityReportState.outcomeFilter = String(v || '');\n                    try { localStorage.setItem((window.activityOutcomeStorageKey ? window.activityOutcomeStorageKey() : 'ti_activity_report_outcome'), window.activityReportState.outcomeFilter); } catch(e) {}\n                    if (typeof window.renderActivityReport === 'function') window.renderActivityReport();\n                };\n                if (typeof window.renderActivityReport === 'function' && !window.__tiActivityRenderWrap182) {\n                    const oldRender182 = window.renderActivityReport;\n                    window.renderActivityReport = function(){ oldRender182.apply(this, arguments); window.injectActivityOutcomeFilter(); };\n                    window.__tiActivityRenderWrap182 = true;\n                }\n                if (typeof window.openActivityReport === 'function' && !window.__tiActivityOpenWrap182) {\n                    const oldOpen182 = window.openActivityReport;\n                    window.openActivityReport = function(){\n                        oldOpen182.apply(this, arguments);\n                        if (!window.activityReportState) window.activityReportState = {};\n                        try { window.activityReportState.outcomeFilter = localStorage.getItem(window.activityOutcomeStorageKey ? window.activityOutcomeStorageKey() : 'ti_activity_report_outcome') || ''; } catch(e) {}\n                        if (typeof window.renderActivityReport === 'function') window.renderActivityReport();\n                    };\n                    window.__tiActivityOpenWrap182 = true;\n                }\n                if (typeof window.exportActivityReportCsv === 'function' && !window.__tiActivityExportWrap182) {\n                    window.exportActivityReportCsv = function(){\n                        const rows = window.getFilteredActivityRows(false);\n                        const cols = window.getActivityVisibleCols ? window.getActivityVisibleCols() : Object.keys(rows[0] || {});\n                        const csv = cols.join(';') + '\\n' + rows.map(function(r){ return cols.map(function(c){\n                            const raw = (c === 'Prezzo' || c === 'Quantit\u00e0' || c === 'Totale') ? fmt(r[c] || 0) : (r[c] == null ? '' : r[c]);\n                            return '\"' + String(raw).replace(\/\"\/g,'\"\"') + '\"';\n                        }).join(';'); }).join('\\n');\n                        const blob = new Blob([csv], {type:'text\/csv;charset=utf-8;'});\n                        const a = document.createElement('a');\n                        a.href = URL.createObjectURL(blob);\n                        a.download = 'report-attivita.csv';\n                        a.click();\n                        setTimeout(function(){ URL.revokeObjectURL(a.href); }, 500);\n                    };\n                    window.__tiActivityExportWrap182 = true;\n                }\n            } catch(e) {\n                try { console.error('Shop & Service fix 30.9.182 non applicato:', e); } catch(ignore) {}\n            }\n        }\n        if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ setTimeout(tiInstallPurchaseReport182, 0); });\n        else setTimeout(tiInstallPurchaseReport182, 0);\n    })();\n\n    \/* 30.9.183 - Fix attivazione Acquisti base e tabella manuale *\/\n    (function(){\n        function ti183InstallPurchaseBaseFix(){\n            try {\n                if (window.__tiPurchaseBaseFix183) return;\n                window.__tiPurchaseBaseFix183 = true;\n\n                const byId = function(id){ return document.getElementById(id); };\n                const esc = function(v){\n                    if (window.escapeHtml) return window.escapeHtml(String(v == null ? '' : v));\n                    return String(v == null ? '' : v).replace(\/[&<>\"']\/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c]; });\n                };\n                const parseNum = function(v){\n                    if (v === null || v === undefined) return 0;\n                    let s = String(v).trim();\n                    if (!s) return 0;\n                    s = s.replace(\/[\u20ac\\s]\/g, '');\n                    if (s.indexOf(',') !== -1 && s.indexOf('.') !== -1) s = s.replace(\/\\.\/g, '').replace(',', '.');\n                    else if (s.indexOf(',') !== -1) s = s.replace(',', '.');\n                    const n = parseFloat(s.replace(\/[^0-9.\\-]\/g, ''));\n                    return isNaN(n) ? 0 : n;\n                };\n                const fmt = function(n){\n                    const num = Math.round((parseFloat(n) || 0) * 100) \/ 100;\n                    if (window.fmtNum) return window.fmtNum(num);\n                    return num.toLocaleString('it-IT', {minimumFractionDigits:2, maximumFractionDigits:2});\n                };\n                const norm = function(s){\n                    return String(s || '').normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase().replace(\/[^a-z0-9]\/g, '');\n                };\n                const getField = function(row, names, fallback){\n                    row = row || {};\n                    const keys = Object.keys(row);\n                    for (let i=0;i<names.length;i++) {\n                        const wanted = norm(names[i]);\n                        for (let j=0;j<keys.length;j++) if (norm(keys[j]) === wanted) return row[keys[j]];\n                    }\n                    return fallback;\n                };\n                const setField = function(row, names, value){\n                    row = row || {};\n                    const keys = Object.keys(row);\n                    for (let i=0;i<names.length;i++) {\n                        const wanted = norm(names[i]);\n                        for (let j=0;j<keys.length;j++) if (norm(keys[j]) === wanted) { row[keys[j]] = value; return; }\n                    }\n                    row[names[0]] = value;\n                };\n                const realTableName = function(data, wanted, fallback){\n                    const tables = data && data.Tabelle ? data.Tabelle : {};\n                    const wantedNorm = norm(wanted);\n                    const keys = Object.keys(tables || {});\n                    for (let i=0;i<keys.length;i++) if (norm(keys[i]) === wantedNorm) return keys[i];\n                    for (let i=0;i<keys.length;i++) if (norm(keys[i]).indexOf(wantedNorm) !== -1 || wantedNorm.indexOf(norm(keys[i])) !== -1) return keys[i];\n                    return fallback || wanted;\n                };\n                const marginNet = function(costNet, marginText){\n                    const cost = parseNum(costNet);\n                    let m = String(marginText || '').trim();\n                    if (!m) return Math.max(0, cost);\n                    const isPct = m.indexOf('%') !== -1;\n                    m = m.replace(\/[%\u20ac\\s]\/g, '');\n                    let sign = 1;\n                    if (m.charAt(0) === '-') { sign = -1; m = m.slice(1); }\n                    else if (m.charAt(0) === '+') { m = m.slice(1); }\n                    const val = parseNum(m);\n                    return Math.max(0, isPct ? cost + (cost * sign * val \/ 100) : cost + sign * val);\n                };\n                const saleGross = function(costNet, marginText, ivaText){\n                    let iva = parseNum(ivaText);\n                    if (iva <= 0) iva = 22;\n                    return Math.round(marginNet(costNet, marginText) * (1 + iva \/ 100) * 100) \/ 100;\n                };\n                const dateParts = function(){\n                    const d = new Date();\n                    return {\n                        date:String(d.getDate()).padStart(2,'0') + '\/' + String(d.getMonth()+1).padStart(2,'0') + '\/' + d.getFullYear(),\n                        time:String(d.getHours()).padStart(2,'0') + ':' + String(d.getMinutes()).padStart(2,'0')\n                    };\n                };\n                const base64Utf8 = function(str){\n                    try { return btoa(unescape(encodeURIComponent(str))); }\n                    catch(e) { return btoa(str); }\n                };\n\n                function injectPurchaseStyle(){\n                    if (byId('ti-purchase-base-fix-183-css')) return;\n                    const st = document.createElement('style');\n                    st.id = 'ti-purchase-base-fix-183-css';\n                    st.textContent = `\n#ti-purchase-ov.ti-purchase-open{display:flex!important;align-items:center!important;justify-content:center!important;pointer-events:auto!important;}\n#ti-purchase-ov .ti-purchase-modal{width:min(1480px,98vw)!important;max-width:98vw!important;max-height:92vh!important;overflow:auto!important;}\n#ti-purchase-ov .ti-purchase-table-wrap{display:block!important;width:100%!important;max-width:100%!important;overflow-x:auto!important;overflow-y:visible!important;-webkit-overflow-scrolling:touch!important;touch-action:pan-x pan-y!important;border:1px solid #334155!important;border-radius:12px!important;}\n#ti-purchase-ov #ti-purchase-table{display:table!important;width:100%!important;min-width:1180px!important;border-collapse:collapse!important;background:#020617!important;}\n#ti-purchase-ov #ti-purchase-table thead{display:table-header-group!important;}\n#ti-purchase-ov #ti-purchase-table tbody{display:table-row-group!important;}\n#ti-purchase-ov #ti-purchase-table tr{display:table-row!important;}\n#ti-purchase-ov #ti-purchase-table th,#ti-purchase-ov #ti-purchase-table td{display:table-cell!important;white-space:nowrap!important;}\n#ti-purchase-ov .ti-purchase-row input,#ti-purchase-ov .ti-purchase-row select{background:#0f172a!important;color:#fff!important;-webkit-text-fill-color:#fff!important;caret-color:#fff!important;border:1px solid #334155!important;}\n#ti-purchase-ov .ti-purchase-toolbar-fixed{position:sticky!important;bottom:0!important;z-index:5!important;background:#0f172a!important;padding-top:10px!important;border-top:1px solid #334155!important;}\n#ti-purchase-ov .ti-purchase-summary-grid{display:grid!important;grid-template-columns:repeat(4,minmax(120px,1fr))!important;gap:8px!important;margin-top:10px!important;}\n#ti-purchase-ov .ti-purchase-summary-card{background:#020617!important;border:1px solid #334155!important;border-radius:10px!important;padding:8px!important;color:#e5e7eb!important;font-size:12px!important;}\n#ti-purchase-ov .ti-purchase-summary-card b{display:block!important;color:#bbf7d0!important;font-size:15px!important;margin-top:2px!important;}\n.ti-purchase-row-search-popover{position:fixed!important;z-index:2147483647!important;background:#020617!important;border:1px solid #38bdf8!important;border-radius:10px!important;box-shadow:0 16px 44px rgba(0,0,0,.65)!important;max-height:min(320px,52vh)!important;overflow:auto!important;padding:6px!important;min-width:230px!important;max-width:min(92vw,560px)!important;color:#fff!important;pointer-events:auto!important;-webkit-overflow-scrolling:touch!important;}\n.ti-purchase-row-search-popover .ti-purchase-result-row{display:block!important;width:100%!important;text-align:left!important;margin:0 0 5px 0!important;padding:7px 8px!important;border-radius:8px!important;border:1px solid #334155!important;background:#1e293b!important;color:#fff!important;font-size:12px!important;line-height:1.25!important;cursor:pointer!important;}\n.ti-purchase-row-search-popover .ti-purchase-result-row small{display:block!important;color:#cbd5e1!important;font-size:10.5px!important;margin-top:2px!important;}\n@media (max-width:767px) and (orientation:portrait){#ti-purchase-ov .ti-purchase-modal{width:98vw!important;max-width:98vw!important;padding:10px!important;}#ti-purchase-ov #ti-purchase-table{min-width:980px!important;}#ti-purchase-ov #ti-purchase-table th,#ti-purchase-ov #ti-purchase-table td{font-size:10.5px!important;padding:4px!important;}#ti-purchase-ov #ti-purchase-table input,#ti-purchase-ov #ti-purchase-table select,#ti-purchase-ov #ti-purchase-table button{font-size:10.5px!important;padding:4px!important;min-height:27px!important;}#ti-purchase-ov .ti-pur-name{min-width:190px!important;}.ti-purchase-row-search-popover .ti-purchase-result-row{font-size:10.5px!important;padding:6px!important;}.ti-purchase-row-search-popover .ti-purchase-result-row small{font-size:9.5px!important;}}\n@media (max-width:920px) and (orientation:landscape){#ti-purchase-ov .ti-purchase-modal{width:98vw!important;max-width:98vw!important;padding:10px!important;}#ti-purchase-ov #ti-purchase-table{min-width:900px!important;}#ti-purchase-ov #ti-purchase-table th,#ti-purchase-ov #ti-purchase-table td{font-size:10px!important;padding:3px!important;}#ti-purchase-ov #ti-purchase-table input,#ti-purchase-ov #ti-purchase-table select,#ti-purchase-ov #ti-purchase-table button{font-size:10px!important;padding:3px!important;min-height:25px!important;}}\n`;\n                    document.head.appendChild(st);\n                }\n\n\n                function injectPurchaseLandscapeCompact189(){\n                    try {\n                        if (document.getElementById('ti-purchase-landscape-compact-189-css')) return;\n                        const st = document.createElement('style');\n                        st.id = 'ti-purchase-landscape-compact-189-css';\n                        st.textContent = `\n@media (max-width:980px) and (orientation:landscape){\n#ti-purchase-ov.ti-purchase-open{align-items:flex-start!important;padding:2px!important;}\n#ti-purchase-ov .ti-purchase-modal{width:99vw!important;max-width:99vw!important;max-height:96vh!important;margin:2px auto!important;padding:5px!important;border-radius:10px!important;overflow:auto!important;}\n#ti-purchase-ov .ti-purchase-modal h3,#ti-purchase-ov .ti-purchase-modal h4{font-size:11px!important;line-height:1.05!important;margin:0 0 4px 0!important;}\n#ti-purchase-ov .ti-purchase-table-wrap{max-height:58vh!important;overflow:auto!important;}\n#ti-purchase-ov #ti-purchase-table{min-width:790px!important;table-layout:auto!important;}\n#ti-purchase-ov #ti-purchase-table th,#ti-purchase-ov #ti-purchase-table td{font-size:8.5px!important;line-height:1.05!important;padding:1px 2px!important;height:22px!important;max-height:22px!important;vertical-align:middle!important;}\n#ti-purchase-ov #ti-purchase-table input,#ti-purchase-ov #ti-purchase-table select,#ti-purchase-ov #ti-purchase-table textarea,#ti-purchase-ov #ti-purchase-table button{font-size:8.5px!important;line-height:1.05!important;padding:1px 2px!important;height:20px!important;min-height:20px!important;max-height:20px!important;border-radius:4px!important;box-sizing:border-box!important;}\n#ti-purchase-ov .ti-pur-name{min-width:145px!important;max-width:165px!important;}#ti-purchase-ov .ti-pur-type{width:42px!important;min-width:42px!important;}#ti-purchase-ov .ti-pur-qty{width:46px!important;min-width:46px!important;}#ti-purchase-ov .ti-pur-cost{width:62px!important;min-width:62px!important;}#ti-purchase-ov .ti-pur-margin{width:56px!important;min-width:56px!important;}#ti-purchase-ov .ti-pur-sale{width:70px!important;min-width:70px!important;}#ti-purchase-ov .ti-pur-iva{width:42px!important;min-width:42px!important;}#ti-purchase-ov .ti-pur-note{min-width:96px!important;max-width:115px!important;}\n#ti-purchase-ov .ti-pur-row-total-net,#ti-purchase-ov .ti-pur-row-total-gross{display:inline-block!important;min-width:42px!important;font-size:8.5px!important;line-height:1!important;}\n#ti-purchase-ov .ti-pur-dup,#ti-purchase-ov .ti-pur-del{width:21px!important;min-width:21px!important;height:20px!important;min-height:20px!important;padding:0!important;margin:0 1px!important;font-size:9px!important;line-height:1!important;}\n#ti-purchase-ov .ti-purchase-summary-grid{grid-template-columns:repeat(4,minmax(70px,1fr))!important;gap:4px!important;margin-top:4px!important;}#ti-purchase-ov .ti-purchase-summary-card{font-size:8.5px!important;line-height:1.05!important;padding:4px!important;border-radius:7px!important;}#ti-purchase-ov .ti-purchase-summary-card b{font-size:10px!important;line-height:1.05!important;}#ti-purchase-ov .ti-purchase-toolbar-fixed{padding-top:4px!important;gap:4px!important;}#ti-purchase-ov .ti-purchase-toolbar-fixed .ti-btn,#ti-purchase-ov .ti-purchase-toolbar-fixed button{font-size:9px!important;line-height:1.05!important;padding:4px 6px!important;min-height:23px!important;height:23px!important;}\n.ti-purchase-row-search-popover .ti-purchase-result-row{font-size:9px!important;line-height:1.08!important;padding:4px 5px!important;margin-bottom:3px!important;}.ti-purchase-row-search-popover .ti-purchase-result-row small{font-size:8px!important;line-height:1.05!important;}\n}`;\n                        document.head.appendChild(st);\n                    } catch(e) {}\n                }\n                injectPurchaseLandscapeCompact189();\n                document.addEventListener('DOMContentLoaded', injectPurchaseLandscapeCompact189);\n\n                window.tiPurchaseCalcSaleGross = saleGross;\n                window.tiPurchaseMarginAdjustedNet = marginNet;\n\n                window.purchaseGetCatalogItems = function(){\n                    const data = window.currentDbData || {};\n                    const tables = data && data.Tabelle ? data.Tabelle : {};\n                    const out = [];\n                    Object.keys(tables || {}).forEach(function(tn){\n                        if (!\/(prodott|serviz)\/i.test(tn)) return;\n                        const rows = Array.isArray(tables[tn]) ? tables[tn] : [];\n                        rows.forEach(function(r, idx){\n                            if (!r || typeof r !== 'object') return;\n                            const isServ = \/(serviz)\/i.test(tn);\n                            const name = String(getField(r, isServ ? ['Servizio','Descrizione','Nome','Titolo'] : ['Prodotto','Descrizione','Nome','Titolo'], '') || '').trim();\n                            if (!name) return;\n                            out.push({\n                                table:tn,\n                                index:idx,\n                                type:isServ ? 'S' : 'P',\n                                typeLabel:isServ ? 'servizio' : 'prodotto',\n                                name:name,\n                                desc:String(getField(r, ['Descrizione','Note','Nota'], '') || '').trim(),\n                                cost:parseNum(getField(r, ['Costo','Costo acquisto','Costo_acquisto'], 0)),\n                                sale:parseNum(getField(r, ['Prezzo','Prezzo vendita','Prezzo_vendita'], 0)),\n                                iva:parseNum(getField(r, ['IVA_VAT','IVA','Iva','VAT'], 22)) || 22\n                            });\n                        });\n                    });\n                    return out;\n                };\n\n                window.closePurchaseSearchPopover = function(){\n                    const p = byId('ti-purchase-row-search-popover');\n                    if (p) p.remove();\n                };\n                window.selectPurchaseCatalogItemForRow = function(item, row){\n                    try {\n                        item = item || {};\n                        row = row || window.__tiPurchaseActiveRow;\n                        if (!row) return;\n                        row.dataset.catalogTable = item.table || '';\n                        row.dataset.catalogIndex = String(item.index != null ? item.index : -1);\n                        row.dataset.targetType = item.type === 'S' ? 'servizio' : 'prodotto';\n                        const setVal = function(sel, val){ const el = row.querySelector(sel); if (el) el.value = val == null ? '' : String(val).replace('.', ','); };\n                        setVal('.ti-pur-name', item.name || '');\n                        setVal('.ti-pur-type', item.type === 'S' ? 'S' : 'P');\n                        setVal('.ti-pur-cost', item.cost || '');\n                        setVal('.ti-pur-iva', item.iva || '22');\n                        setVal('.ti-pur-note', item.desc || '');\n                        const sale = row.querySelector('.ti-pur-sale');\n                        if (sale) { sale.dataset.auto = '1'; sale.value = item.sale ? String(item.sale).replace('.', ',') : ''; }\n                        window.recalcPurchaseRow(row, !item.sale);\n                        window.refreshPurchaseTotals();\n                        window.closePurchaseSearchPopover();\n                    } catch(e) {}\n                };\n                window.showPurchaseRowSearch = function(input){\n                    try {\n                        if (!input) return;\n                        const row = input.closest('tr');\n                        window.__tiPurchaseActiveRow = row;\n                        const q = String(input.value || '').trim().toLowerCase();\n                        const all = window.purchaseGetCatalogItems ? window.purchaseGetCatalogItems() : [];\n                        const filtered = q ? all.filter(function(it){ return (String(it.name || '') + ' ' + String(it.desc || '') + ' ' + String(it.typeLabel || '')).toLowerCase().indexOf(q) !== -1; }).slice(0, 40) : all.slice(0, 20);\n                        window.closePurchaseSearchPopover();\n                        if (!filtered.length) return;\n                        const pop = document.createElement('div');\n                        pop.id = 'ti-purchase-row-search-popover';\n                        pop.className = 'ti-purchase-row-search-popover';\n                        filtered.forEach(function(it){\n                            const b = document.createElement('button');\n                            b.type = 'button';\n                            b.className = 'ti-purchase-result-row';\n                            b.innerHTML = '<b>' + esc(it.name) + '<\/b><small>' + esc(it.typeLabel || it.type || '') + ' - ' + esc(it.table || '') + ' - costo ' + fmt(it.cost || 0) + ' - prezzo ' + fmt(it.sale || 0) + ' - IVA ' + fmt(it.iva || 22) + '%<\/small>';\n                            const choose = function(ev){ if (ev) { ev.preventDefault(); ev.stopPropagation(); } window.selectPurchaseCatalogItemForRow(it, row); };\n                            b.addEventListener('pointerdown', choose);\n                            b.addEventListener('click', choose);\n                            pop.appendChild(b);\n                        });\n                        document.body.appendChild(pop);\n                        const r = input.getBoundingClientRect();\n                        const width = Math.max(r.width, Math.min(560, window.innerWidth - 24));\n                        let left = Math.max(8, Math.min(r.left, window.innerWidth - width - 8));\n                        let top = r.bottom + 6;\n                        setTimeout(function(){\n                            const ph = pop.offsetHeight || 260;\n                            if (top + ph > window.innerHeight - 8) top = Math.max(8, r.top - ph - 6);\n                            pop.style.left = left + 'px';\n                            pop.style.top = top + 'px';\n                            pop.style.width = width + 'px';\n                        }, 0);\n                    } catch(e) {}\n                };\n\n                window.ensurePurchaseModal = function(){\n                    injectPurchaseStyle();\n                    let ov = byId('ti-purchase-ov');\n                    if (ov && ov.dataset && ov.dataset.v183 === '1') return ov;\n                    if (ov && ov.parentNode) ov.parentNode.removeChild(ov);\n                    ov = document.createElement('div');\n                    ov.id = 'ti-purchase-ov';\n                    ov.className = 'ti-modal-overlay ti-ov ti-closable-ov';\n                    ov.dataset.v183 = '1';\n                    ov.style.display = 'none';\n                    ov.style.zIndex = '2147482000';\n                    ov.innerHTML = `\n                        <div class=\"ti-modal ti-purchase-modal\" style=\"background:#0f172a;color:#fff;border-radius:16px;padding:18px;box-shadow:0 18px 60px rgba(0,0,0,.45);margin:0 auto;\">\n                            <div style=\"display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px;\">\n                                <div>\n                                    <h2 style=\"margin:0;font-size:22px;\">Acquisti<\/h2>\n                                    <div style=\"font-size:14px;color:#cbd5e1;margin-top:4px;\">Gestisci le righe acquisto manuale. Il costo \u00e8 netto IVA; il prezzo vendita proposto include margine e IVA.<\/div>\n                                <\/div>\n                                <button type=\"button\" class=\"ti-btn\" style=\"background:#dc2626;color:#fff;\" onclick=\"window.closePurchaseManualFlow()\">X<\/button>\n                            <\/div>\n                            <div style=\"display:flex;gap:8px;flex-wrap:wrap;margin:10px 0 14px 0;\">\n                                <button type=\"button\" class=\"ti-btn ti-purchase-add-row ti-ss-add-manual-row\" style=\"background:#22c55e;color:#052e16;font-weight:700;\" onclick=\"window.ti183ForceAddPurchaseRow(event)\">+ Aggiungi riga manuale<\/button>\n                                <button type=\"button\" class=\"ti-btn\" style=\"background:#f59e0b;color:#111827;font-weight:700;\" onclick=\"window.pickPurchaseImportFile && window.pickPurchaseImportFile()\">Import file acquisti<\/button>\n                                <button type=\"button\" class=\"ti-btn\" style=\"background:#64748b;color:#fff;font-weight:700;\" onclick=\"window.clearPurchaseRows()\">Svuota righe<\/button>\n                            <\/div>\n                            <div class=\"ti-purchase-table-wrap\">\n                                <table id=\"ti-purchase-table\">\n                                    <thead>\n                                        <tr style=\"background:#1e293b;\">\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:left;\">Prodotto o servizio<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:left;\">Tipo<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Q.t\u00e0<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Costo netto<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Margine<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Prezzo vendita IVA incl.<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">IVA %<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Tot. acquisto netto<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:right;\">Tot. vendita<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:left;\">Note<\/th>\n                                            <th style=\"padding:8px;border-bottom:1px solid #334155;text-align:center;\">Azioni<\/th>\n                                        <\/tr>\n                                    <\/thead>\n                                    <tbody id=\"ti-purchase-body\"><\/tbody>\n                                <\/table>\n                            <\/div>\n                            <div id=\"ti-purchase-total\" class=\"ti-purchase-summary-grid\" aria-live=\"polite\"><\/div>\n                            <div class=\"ti-purchase-toolbar-fixed\" style=\"display:flex;justify-content:center;gap:8px;margin-top:14px;flex-wrap:wrap;\">\n                                <button type=\"button\" id=\"ti-purchase-confirm-btn\" class=\"ti-btn\" style=\"background:#38bdf8;color:#082f49;font-weight:800;\" onclick=\"window.confirmPurchaseRows()\">Conferma righe acquisto<\/button>\n                                <button type=\"button\" class=\"ti-btn btn-close\" onclick=\"window.closePurchaseManualFlow()\">Chiudi<\/button>\n                            <\/div>\n                        <\/div>`;\n                    document.body.appendChild(ov);\n                    return ov;\n                };\n                window.closePurchaseManualFlow = function(){\n                    const ov = byId('ti-purchase-ov');\n                    window.closePurchaseSearchPopover();\n                    if (!ov) return;\n                    ov.classList.remove('ti-purchase-open', 'ti-modal-open');\n                    if (window.closeModal) window.closeModal(ov); else ov.style.display = 'none';\n                };\n                function openPurchaseOverlay(){\n                    const ov = window.ensurePurchaseModal();\n                    ov.classList.add('ti-purchase-open', 'ti-modal-open');\n                    ov.style.display = 'flex';\n                    ov.style.pointerEvents = 'auto';\n                    ov.setAttribute('aria-hidden', 'false');\n                    return ov;\n                }\n                window.ti183ForcePurchaseTableActive = function(){\n                    const ov = openPurchaseOverlay();\n                    const wrap = ov.querySelector('.ti-purchase-table-wrap');\n                    const table = byId('ti-purchase-table');\n                    const body = byId('ti-purchase-body');\n                    if (wrap) wrap.style.display = 'block';\n                    if (table) table.style.display = 'table';\n                    if (body && !body.children.length) window.addPurchaseManualRow();\n                    window.refreshPurchaseTotals();\n                    return body;\n                };\n                window.ti183ForceAddPurchaseRow = function(ev){\n                    if (ev) { ev.preventDefault(); ev.stopPropagation(); }\n                    openPurchaseOverlay();\n                    window.addPurchaseManualRow();\n                };\n                window.openPurchaseManualFlow = function(){\n                    const dbSel = byId('ti-ditta');\n                    const dbVal = dbSel ? dbSel.value : '';\n                    const openNow = function(){ window.ti183ForcePurchaseTableActive(); };\n                    if (!dbVal || dbVal === 'NEW_DB') { openNow(); return; }\n                    if (window.currentDbData && window.currentDbData.Tabelle) { openNow(); return; }\n                    const fd = new FormData();\n                    fd.append('ti_action', 'ti_ai_get_db_data');\n                    fd.append('db', dbVal);\n                    const url = window.tiAjaxUrl || window.tiUrl;\n                    if (url && window.fetchJsonSafe) {\n                        window.fetchJsonSafe(url, {method:'POST', body:fd, credentials:'same-origin'}).then(function(res){\n                            if (res && res.success) window.currentDbData = res.data;\n                            openNow();\n                        }).catch(function(){ openNow(); });\n                    } else openNow();\n                };\n\n                window.recalcPurchaseRow = function(row, force){\n                    try {\n                        if (!row) return;\n                        const qty = parseNum((row.querySelector('.ti-pur-qty') || {}).value || '0');\n                        const costEl = row.querySelector('.ti-pur-cost');\n                        const marginEl = row.querySelector('.ti-pur-margin');\n                        const saleEl = row.querySelector('.ti-pur-sale');\n                        const ivaEl = row.querySelector('.ti-pur-iva');\n                        const cost = parseNum(costEl ? costEl.value : 0);\n                        const sale = saleGross(cost, marginEl ? marginEl.value : '', ivaEl ? ivaEl.value : '22');\n                        if (saleEl && (force || document.activeElement !== saleEl || saleEl.dataset.auto === '1')) {\n                            saleEl.value = sale ? String(sale).replace('.', ',') : '';\n                            saleEl.dataset.auto = '1';\n                        }\n                        const rowSale = saleEl ? (parseNum(saleEl.value) || sale) : sale;\n                        const netTot = Math.round(qty * cost * 100) \/ 100;\n                        const grossTot = Math.round(qty * rowSale * 100) \/ 100;\n                        const netOut = row.querySelector('.ti-pur-row-total-net');\n                        const grossOut = row.querySelector('.ti-pur-row-total-gross');\n                        if (netOut) netOut.textContent = fmt(netTot);\n                        if (grossOut) grossOut.textContent = fmt(grossTot);\n                    } catch(e) {}\n                };\n                window.refreshPurchaseTotals = function(){\n                    try {\n                        let rows = 0, qtyTot = 0, netTot = 0, saleTot = 0;\n                        document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr){\n                            window.recalcPurchaseRow(tr, false);\n                            const qty = parseNum((tr.querySelector('.ti-pur-qty') || {}).value || '0');\n                            const cost = parseNum((tr.querySelector('.ti-pur-cost') || {}).value || '0');\n                            const sale = parseNum((tr.querySelector('.ti-pur-sale') || {}).value || '0');\n                            rows += 1; qtyTot += qty; netTot += qty * cost; saleTot += qty * sale;\n                        });\n                        const out = byId('ti-purchase-total');\n                        if (out) out.innerHTML = '<div class=\"ti-purchase-summary-card\">Righe<b>' + rows + '<\/b><\/div>'\n                            + '<div class=\"ti-purchase-summary-card\">Quantit\u00e0<b>' + fmt(qtyTot) + '<\/b><\/div>'\n                            + '<div class=\"ti-purchase-summary-card\">Totale acquisto netto<b>\u20ac ' + fmt(netTot) + '<\/b><\/div>'\n                            + '<div class=\"ti-purchase-summary-card\">Totale vendita IVA incl.<b>\u20ac ' + fmt(saleTot) + '<\/b><\/div>';\n                    } catch(e) {}\n                };\n                window.addPurchaseManualRow = function(item){\n                    try {\n                        const body = byId('ti-purchase-body') || window.ti183ForcePurchaseTableActive();\n                        if (!body) return;\n                        const now = Date.now();\n                        if (!item && window.__tiLastPurchaseManualRowAt && now - window.__tiLastPurchaseManualRowAt < 180) return;\n                        if (!item) window.__tiLastPurchaseManualRowAt = now;\n                        const safe = item || {};\n                        const tr = document.createElement('tr');\n                        tr.className = 'ti-purchase-row';\n                        tr.dataset.catalogTable = safe.table || '';\n                        tr.dataset.catalogIndex = String(safe.index != null ? safe.index : -1);\n                        tr.dataset.targetType = safe.type === 'S' ? 'servizio' : 'prodotto';\n                        const iva = safe.iva || '22';\n                        const cost = safe.cost ? String(safe.cost).replace('.', ',') : '';\n                        const sale = safe.sale ? String(safe.sale).replace('.', ',') : '';\n                        tr.innerHTML = `\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-name ti-purchase-row-search\" value=\"${esc(safe.name || '')}\" placeholder=\"Cerca prodotto o servizio\" autocomplete=\"off\" style=\"width:100%;min-width:230px;box-sizing:border-box;padding:8px;border-radius:8px;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><select class=\"ti-pur-type\" style=\"width:76px;padding:8px;border-radius:8px;\"><option value=\"P\" ${safe.type === 'S' ? '' : 'selected'}>P<\/option><option value=\"S\" ${safe.type === 'S' ? 'selected' : ''}>S<\/option><\/select><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-qty\" value=\"${esc(safe.qty || '1')}\" inputmode=\"decimal\" style=\"width:72px;text-align:right;padding:8px;border-radius:8px;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-cost\" value=\"${esc(cost)}\" inputmode=\"decimal\" style=\"width:100px;text-align:right;padding:8px;border-radius:8px;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-margin\" value=\"${esc(safe.margin || '')}\" placeholder=\"+10%\" style=\"width:86px;text-align:right;padding:8px;border-radius:8px;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-sale\" data-auto=\"${sale ? '0' : '1'}\" value=\"${esc(sale)}\" inputmode=\"decimal\" style=\"width:112px;text-align:right;padding:8px;border-radius:8px;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-iva\" value=\"${esc(iva)}\" inputmode=\"decimal\" style=\"width:68px;text-align:right;padding:8px;border-radius:8px;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;text-align:right;\"><span class=\"ti-pur-row-total-net\">0,00<\/span><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;text-align:right;\"><span class=\"ti-pur-row-total-gross\">0,00<\/span><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;\"><input class=\"ti-pur-note\" value=\"${esc(safe.desc || '')}\" placeholder=\"Note\" style=\"width:100%;min-width:160px;box-sizing:border-box;padding:8px;border-radius:8px;\"><\/td>\n                            <td style=\"padding:6px;border-bottom:1px solid #1f2937;text-align:center;white-space:nowrap;\"><button type=\"button\" class=\"ti-btn ti-pur-dup\" title=\"Duplica riga\" style=\"background:#2563eb;color:#fff;margin-right:4px;\">\u2197<\/button><button type=\"button\" class=\"ti-btn ti-pur-del\" title=\"Elimina riga\" style=\"background:#dc2626;color:#fff;\">X<\/button><\/td>`;\n                        body.appendChild(tr);\n                        const name = tr.querySelector('.ti-pur-name');\n                        if (name) {\n                            name.addEventListener('input', function(){ window.__tiPurchaseActiveRow = tr; window.showPurchaseRowSearch(name); });\n                            name.addEventListener('focus', function(){ window.__tiPurchaseActiveRow = tr; if (String(name.value || '').trim()) window.showPurchaseRowSearch(name); });\n                        }\n                        const typeEl = tr.querySelector('.ti-pur-type');\n                        if (typeEl) typeEl.addEventListener('change', function(){ tr.dataset.targetType = this.value === 'S' ? 'servizio' : 'prodotto'; });\n                        ['.ti-pur-qty','.ti-pur-cost','.ti-pur-margin','.ti-pur-iva'].forEach(function(sel){\n                            const el = tr.querySelector(sel);\n                            if (el) el.addEventListener('input', function(){ const saleEl = tr.querySelector('.ti-pur-sale'); if (saleEl) saleEl.dataset.auto = '1'; window.recalcPurchaseRow(tr, true); window.refreshPurchaseTotals(); });\n                        });\n                        const saleEl = tr.querySelector('.ti-pur-sale');\n                        if (saleEl) saleEl.addEventListener('input', function(){ saleEl.dataset.auto = '0'; window.recalcPurchaseRow(tr, false); window.refreshPurchaseTotals(); });\n                        const del = tr.querySelector('.ti-pur-del');\n                        if (del) del.addEventListener('click', function(){ tr.remove(); if (!body.children.length) window.addPurchaseManualRow(); window.refreshPurchaseTotals(); });\n                        const dup = tr.querySelector('.ti-pur-dup');\n                        if (dup) dup.addEventListener('click', function(){ window.addPurchaseManualRow(window.purchaseRowToItem(tr)); });\n                        window.recalcPurchaseRow(tr, !sale);\n                        window.refreshPurchaseTotals();\n                        if (!safe.name && name) { try { name.focus(); } catch(ignore) {} }\n                    } catch(e) { if (window.tiAlert) window.tiAlert('Errore aggiunta riga acquisto: ' + (e.message || e)); }\n                };\n                window.purchaseRowToItem = function(tr){\n                    tr = tr || {};\n                    return {\n                        table:tr.dataset ? (tr.dataset.catalogTable || '') : '',\n                        index:tr.dataset ? parseInt(tr.dataset.catalogIndex || '-1', 10) : -1,\n                        type:String(((tr.querySelector && tr.querySelector('.ti-pur-type')) || {}).value || 'P'),\n                        name:String(((tr.querySelector && tr.querySelector('.ti-pur-name')) || {}).value || ''),\n                        qty:String(((tr.querySelector && tr.querySelector('.ti-pur-qty')) || {}).value || '1'),\n                        cost:String(((tr.querySelector && tr.querySelector('.ti-pur-cost')) || {}).value || ''),\n                        margin:String(((tr.querySelector && tr.querySelector('.ti-pur-margin')) || {}).value || ''),\n                        sale:String(((tr.querySelector && tr.querySelector('.ti-pur-sale')) || {}).value || ''),\n                        iva:String(((tr.querySelector && tr.querySelector('.ti-pur-iva')) || {}).value || '22'),\n                        desc:String(((tr.querySelector && tr.querySelector('.ti-pur-note')) || {}).value || '')\n                    };\n                };\n                window.clearPurchaseRows = function(){\n                    const body = byId('ti-purchase-body') || window.ti183ForcePurchaseTableActive();\n                    if (!body) return;\n                    if (body.children.length && window.confirm && !window.confirm('Svuotare tutte le righe acquisto?')) return;\n                    body.innerHTML = '';\n                    window.addPurchaseManualRow();\n                    window.refreshPurchaseTotals();\n                };\n                window.validatePurchaseRows = function(mark){\n                    const errors = [];\n                    document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr, idx){\n                        tr.classList.remove('is-invalid');\n                        const n = String((tr.querySelector('.ti-pur-name') || {}).value || '').trim();\n                        const q = parseNum((tr.querySelector('.ti-pur-qty') || {}).value || '0');\n                        const ivaEl = tr.querySelector('.ti-pur-iva');\n                        if (ivaEl && !String(ivaEl.value || '').trim()) ivaEl.value = '22';\n                        if (!n || q <= 0) {\n                            if (mark) tr.classList.add('is-invalid');\n                            if (!n) errors.push('Riga ' + (idx + 1) + ': prodotto\/servizio mancante');\n                            if (q <= 0) errors.push('Riga ' + (idx + 1) + ': quantit\u00e0 non valida');\n                        }\n                    });\n                    return errors;\n                };\n                window.purchaseCollectRows = function(){\n                    const rows = [];\n                    document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr){\n                        const item = window.purchaseRowToItem(tr);\n                        if (String(item.name || '').trim()) rows.push(item);\n                    });\n                    return rows;\n                };\n                window.confirmPurchaseRows = function(){\n                    const btn = byId('ti-purchase-confirm-btn');\n                    try {\n                        const errors = window.validatePurchaseRows(true);\n                        if (errors.length) { if (window.tiAlert) window.tiAlert('Correggi le righe acquisto:\\n' + errors.slice(0, 8).join('\\n')); return; }\n                        const rows = window.purchaseCollectRows();\n                        if (!rows.length) { if (window.tiAlert) window.tiAlert('Inserisci almeno una riga acquisto.'); return; }\n                        const dbEl = byId('ti-ditta');\n                        const dbVal = dbEl ? dbEl.value : '';\n                        if (!dbVal || dbVal === 'NEW_DB') { if (window.tiAlert) window.tiAlert('Seleziona prima una ditta.'); return; }\n                        const source = window.currentDbData;\n                        if (!source || !source.Tabelle) { if (window.tiAlert) window.tiAlert('Database non caricato. Seleziona la ditta e riapri Acquisti.'); return; }\n                        if (btn) { btn.disabled = true; btn.dataset.oldText = btn.innerHTML; btn.innerHTML = 'Salvataggio...'; }\n                        const data = JSON.parse(JSON.stringify(source));\n                        const prodTable = realTableName(data, 'Prodotti', 'Prodotti');\n                        const servTable = realTableName(data, 'Servizi', 'Servizi');\n                        const actTable = realTableName(data, 'Attivita', 'Attivita');\n                        if (!data.Tabelle[prodTable]) data.Tabelle[prodTable] = [];\n                        if (!data.Tabelle[servTable]) data.Tabelle[servTable] = [];\n                        if (!data.Tabelle[actTable]) data.Tabelle[actTable] = [];\n                        const now = dateParts();\n                        const nextId = function(table){ return (Array.isArray(data.Tabelle[table]) ? data.Tabelle[table].length : 0) + 1; };\n                        rows.forEach(function(r){\n                            const isServ = String(r.type || 'P') === 'S';\n                            const t = isServ ? servTable : prodTable;\n                            const nameFields = isServ ? ['Servizio','Nome','Descrizione'] : ['Prodotto','Nome','Descrizione'];\n                            const tableRows = Array.isArray(data.Tabelle[t]) ? data.Tabelle[t] : (data.Tabelle[t] = []);\n                            let idx = (r.table === t && r.index >= 0 && tableRows[r.index]) ? r.index : -1;\n                            if (idx < 0) {\n                                const wanted = norm(r.name);\n                                idx = tableRows.findIndex(function(row){ return norm(getField(row, nameFields, '')) === wanted; });\n                            }\n                            let rec;\n                            if (idx >= 0) rec = tableRows[idx];\n                            else { rec = {}; setField(rec, ['ID','Id','id'], nextId(t)); tableRows.push(rec); }\n                            setField(rec, nameFields, r.name);\n                            setField(rec, ['Costo','Costo acquisto','Costo_acquisto'], parseNum(r.cost));\n                            setField(rec, ['Margine'], String(r.margin || ''));\n                            const gross = parseNum(r.sale) || saleGross(r.cost, r.margin, r.iva);\n                            setField(rec, ['Prezzo','Prezzo vendita','Prezzo_vendita'], gross);\n                            setField(rec, ['IVA_VAT','IVA','Iva','VAT'], parseNum(r.iva) || 22);\n                            if (!isServ) {\n                                const oldQty = parseNum(getField(rec, ['Quantit\u00e0','Quantita','Disponibilita','Disponibilit\u00e0'], 0));\n                                setField(rec, ['Quantit\u00e0','Quantita'], Math.round((oldQty + parseNum(r.qty)) * 100) \/ 100);\n                            }\n                            const act = {};\n                            setField(act, ['ID','Id','id'], nextId(actTable));\n                            setField(act, ['Username','Utente','user'], window.tiUser || '');\n                            setField(act, ['Descrizione attivit\u00e0','Descrizione attivita','Descrizione','Dettaglio','Note'], 'Acquisto ' + r.name);\n                            setField(act, ['Prodotto'], r.name);\n                            setField(act, ['Prezzo'], parseNum(r.cost));\n                            setField(act, ['Quantit\u00e0','Quantita'], parseNum(r.qty));\n                            setField(act, ['Totale'], Math.round(parseNum(r.cost) * parseNum(r.qty) * 100) \/ 100);\n                            setField(act, ['Margine'], String(r.margin || ''));\n                            setField(act, ['IVA_VAT','IVA','Iva','VAT'], parseNum(r.iva) || 22);\n                            setField(act, ['Prezzo vendita','Prezzo_vendita'], gross);\n                            setField(act, ['Esito'], 'acquisto');\n                            setField(act, ['Data'], now.date);\n                            setField(act, ['Ora'], now.time);\n                            setField(act, ['Stato'], 'Attivo');\n                            data.Tabelle[actTable].push(act);\n                        });\n                        const fd = new FormData();\n                        fd.append('ti_action', 'ti_ai_save_full_db');\n                        fd.append('db', dbVal);\n                        fd.append('payload', base64Utf8(JSON.stringify(data)));\n                        const done = function(res){\n                            if (btn) { btn.disabled = false; btn.innerHTML = btn.dataset.oldText || 'Conferma righe acquisto'; }\n                            if (res && res.success) {\n                                window.currentDbData = data;\n                                window.closePurchaseManualFlow();\n                                if (window.tiAlert) window.tiAlert('\u2705 Righe acquisto confermate e salvate nel database.');\n                                if (res.data && res.data.needs_reload) setTimeout(function(){ location.reload(); }, 900);\n                            } else if (window.tiAlert) window.tiAlert('\u26a0\ufe0f Salvataggio acquisto non riuscito.');\n                        };\n                        const fail = function(e){\n                            if (btn) { btn.disabled = false; btn.innerHTML = btn.dataset.oldText || 'Conferma righe acquisto'; }\n                            if (window.tiAlert) window.tiAlert('Errore salvataggio acquisto: ' + ((e && e.message) || e));\n                        };\n                        const url = window.tiAjaxUrl || window.tiUrl;\n                        if (window.fetchJsonSafe) window.fetchJsonSafe(url, {method:'POST', body:fd, credentials:'same-origin'}).then(done).catch(fail);\n                        else fetch(url, {method:'POST', body:fd, credentials:'same-origin'}).then(function(r){ return r.json(); }).then(done).catch(fail);\n                    } catch(e) {\n                        if (btn) { btn.disabled = false; btn.innerHTML = btn.dataset.oldText || 'Conferma righe acquisto'; }\n                        if (window.tiAlert) window.tiAlert('Errore conferma acquisto: ' + (e.message || e));\n                    }\n                };\n                window.purchaseSendToChat = function(){ window.confirmPurchaseRows(); };\n\n                document.addEventListener('click', function(ev){\n                    const target = ev.target && ev.target.closest ? ev.target.closest('#ti-purchase-toggle,.ti-purchase-add-row,.ti-ss-add-manual-row') : null;\n                    if (!target) return;\n                    if (target.id === 'ti-purchase-toggle') {\n                        ev.preventDefault();\n                        if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();\n                        window.openPurchaseManualFlow();\n                        return;\n                    }\n                    if (target.classList.contains('ti-purchase-add-row') || target.classList.contains('ti-ss-add-manual-row')) {\n                        ev.preventDefault();\n                        if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();\n                        window.ti183ForceAddPurchaseRow();\n                    }\n                }, true);\n                document.addEventListener('click', function(ev){\n                    const pop = byId('ti-purchase-row-search-popover');\n                    if (!pop) return;\n                    if (pop.contains(ev.target)) return;\n                    if (ev.target && ev.target.closest && ev.target.closest('.ti-purchase-row-search')) return;\n                    window.closePurchaseSearchPopover();\n                });\n                const pbtn = byId('ti-purchase-toggle');\n                if (pbtn) pbtn.onclick = function(ev){ if (ev) ev.preventDefault(); window.openPurchaseManualFlow(); };\n            } catch(e) {\n                try { console.error('Shop & Service purchase base fix 30.9.183 non applicato:', e); } catch(ignore) {}\n            }\n        }\n        if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ setTimeout(ti183InstallPurchaseBaseFix, 0); });\n        else setTimeout(ti183InstallPurchaseBaseFix, 0);\n    })();\n\n\n    <\/script>\n\n<script id=\"ti-purchase-manual-rescue-184\">\n(function(){\n    \"use strict\";\n    var AJAX_URL = \"https:\/\/www.tisoft.it\/wp-admin\/admin-ajax.php\";\n    if (window.__tiPurchaseManualRescue184) return;\n    window.__tiPurchaseManualRescue184 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function esc(v){ return String(v == null ? '' : v).replace(\/[&<>\"']\/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c]; }); }\n    function norm(v){ return String(v || '').normalize('NFD').replace(\/[\\u0300-\\u036f]\/g,'').toLowerCase().replace(\/[^a-z0-9]\/g,''); }\n    function num(v){\n        if (v == null) return 0;\n        var s = String(v).trim().replace(\/[\u20ac\\s]\/g,'');\n        if (!s) return 0;\n        if (s.indexOf(',') !== -1 && s.indexOf('.') !== -1) s = s.replace(\/\\.\/g,'').replace(',', '.');\n        else if (s.indexOf(',') !== -1) s = s.replace(',', '.');\n        var n = parseFloat(s.replace(\/[^0-9.\\-]\/g,''));\n        return isNaN(n) ? 0 : n;\n    }\n    function fmt(n){\n        n = Math.round((parseFloat(n) || 0) * 100) \/ 100;\n        try { return n.toLocaleString('it-IT', {minimumFractionDigits:2, maximumFractionDigits:2}); }\n        catch(e){ return String(n.toFixed(2)).replace('.', ','); }\n    }\n    function field(row, names, fallback){\n        row = row || {}; var keys = Object.keys(row);\n        for (var i=0;i<names.length;i++) { var w = norm(names[i]); for (var j=0;j<keys.length;j++) if (norm(keys[j]) === w) return row[keys[j]]; }\n        return fallback;\n    }\n    function setField(row, names, value){\n        row = row || {}; var keys = Object.keys(row);\n        for (var i=0;i<names.length;i++) { var w = norm(names[i]); for (var j=0;j<keys.length;j++) if (norm(keys[j]) === w) { row[keys[j]] = value; return; } }\n        row[names[0]] = value;\n    }\n    function realTable(data, wanted, fallback){\n        var tables = data && data.Tabelle ? data.Tabelle : {}; var keys = Object.keys(tables); var w = norm(wanted);\n        for (var i=0;i<keys.length;i++) if (norm(keys[i]) === w) return keys[i];\n        for (var j=0;j<keys.length;j++) { var k = norm(keys[j]); if (k.indexOf(w) !== -1 || w.indexOf(k) !== -1) return keys[j]; }\n        return fallback || wanted;\n    }\n    function marginNet(cost, margin){\n        cost = num(cost); var m = String(margin || '').trim();\n        if (!m) return Math.max(0, cost);\n        var pct = m.indexOf('%') !== -1; m = m.replace(\/[%\u20ac\\s]\/g,'');\n        var sign = 1; if (m.charAt(0) === '-') { sign = -1; m = m.slice(1); } else if (m.charAt(0) === '+') m = m.slice(1);\n        var v = num(m); return Math.max(0, pct ? cost + cost * sign * v \/ 100 : cost + sign * v);\n    }\n    function grossSale(cost, margin, iva){ iva = num(iva); if (iva <= 0) iva = 22; return Math.round(marginNet(cost, margin) * (1 + iva\/100) * 100) \/ 100; }\n    function base64Utf8(str){ try { return btoa(unescape(encodeURIComponent(str))); } catch(e) { return btoa(str); } }\n    function ajaxForm(tiAction, extra){\n        var fd = new FormData(); fd.append('ti_action', tiAction);\n        Object.keys(extra || {}).forEach(function(k){ fd.append(k, extra[k]); });\n        var url = window.tiAjaxUrl || window.tiUrl || AJAX_URL;\n        if (window.fetchJsonSafe) return window.fetchJsonSafe(url, {method:'POST', body:fd, credentials:'same-origin'});\n        return fetch(url, {method:'POST', body:fd, credentials:'same-origin'}).then(function(r){ return r.json(); });\n    }\n    function selectedDb(){ var el = byId('ti-ditta'); return el ? String(el.value || '') : ''; }\n    function loadDb(){\n        if (window.currentDbData && window.currentDbData.Tabelle) return Promise.resolve(window.currentDbData);\n        var db = selectedDb();\n        if (!db || db === 'NEW_DB') return Promise.resolve(window.currentDbData || {Tabelle:{}});\n        return ajaxForm('ti_ai_get_db_data', {db:db}).then(function(res){ if (res && res.success) window.currentDbData = res.data; return window.currentDbData || {Tabelle:{}}; }).catch(function(){ return window.currentDbData || {Tabelle:{}}; });\n    }\n\n    function injectCss(){\n        if (byId('ti-purchase-rescue-184-css')) return;\n        var st = document.createElement('style'); st.id = 'ti-purchase-rescue-184-css';\n        st.textContent = '#ti-purchase-ov.ti-purchase-rescue-open{display:flex!important;align-items:center!important;justify-content:center!important;pointer-events:auto!important;z-index:2147482000!important}'\n        + '#ti-purchase-ov .ti-purchase-modal{width:min(1480px,98vw)!important;max-width:98vw!important;max-height:92vh!important;overflow:auto!important;background:#0f172a!important;color:#fff!important;border-radius:16px!important;padding:18px!important;box-sizing:border-box!important}'\n        + '#ti-purchase-ov .ti-purchase-table-wrap{display:block!important;width:100%!important;max-width:100%!important;overflow-x:auto!important;overflow-y:visible!important;-webkit-overflow-scrolling:touch!important;touch-action:pan-x pan-y!important;border:1px solid #334155!important;border-radius:12px!important}'\n        + '#ti-purchase-ov #ti-purchase-table{display:table!important;width:100%!important;min-width:1160px!important;border-collapse:collapse!important;background:#020617!important}'\n        + '#ti-purchase-ov #ti-purchase-table thead{display:table-header-group!important}#ti-purchase-ov #ti-purchase-table tbody{display:table-row-group!important}#ti-purchase-ov #ti-purchase-table tr{display:table-row!important}#ti-purchase-ov #ti-purchase-table th,#ti-purchase-ov #ti-purchase-table td{display:table-cell!important;white-space:nowrap!important;vertical-align:middle!important}'\n        + '#ti-purchase-ov input,#ti-purchase-ov select,#ti-purchase-ov textarea{background:#0f172a!important;color:#fff!important;-webkit-text-fill-color:#fff!important;caret-color:#fff!important;border:1px solid #334155!important}'\n        + '#ti-purchase-row-pop-184{position:fixed!important;z-index:2147483647!important;background:#020617!important;border:1px solid #38bdf8!important;border-radius:10px!important;box-shadow:0 16px 44px rgba(0,0,0,.65)!important;max-height:min(320px,52vh)!important;overflow:auto!important;padding:6px!important;min-width:230px!important;max-width:min(92vw,560px)!important;color:#fff!important;pointer-events:auto!important;-webkit-overflow-scrolling:touch!important}'\n        + '#ti-purchase-row-pop-184 button{display:block!important;width:100%!important;text-align:left!important;margin:0 0 5px!important;padding:7px 8px!important;border-radius:8px!important;border:1px solid #334155!important;background:#1e293b!important;color:#fff!important;font-size:12px!important;line-height:1.25!important;cursor:pointer!important}'\n        + '#ti-purchase-row-pop-184 small{display:block!important;color:#cbd5e1!important;font-size:10.5px!important;margin-top:2px!important}'\n        + '#ti-purchase-total{display:grid!important;grid-template-columns:repeat(4,minmax(120px,1fr))!important;gap:8px!important;margin-top:10px!important}.ti-purchase-summary-card{background:#020617!important;border:1px solid #334155!important;border-radius:10px!important;padding:8px!important;color:#e5e7eb!important;font-size:12px!important}.ti-purchase-summary-card b{display:block!important;color:#bbf7d0!important;font-size:15px!important;margin-top:2px!important}'\n        + '@media (max-width:767px) and (orientation:portrait){#ti-purchase-ov .ti-purchase-modal{width:98vw!important;max-width:98vw!important;padding:10px!important}#ti-purchase-ov #ti-purchase-table{min-width:980px!important}#ti-purchase-ov #ti-purchase-table th,#ti-purchase-ov #ti-purchase-table td{font-size:10.5px!important;padding:4px!important}#ti-purchase-ov #ti-purchase-table input,#ti-purchase-ov #ti-purchase-table select,#ti-purchase-ov #ti-purchase-table button{font-size:10.5px!important;padding:4px!important;min-height:27px!important}#ti-purchase-ov .ti-pur-name{min-width:190px!important}#ti-purchase-row-pop-184 button{font-size:10.5px!important;padding:6px!important}#ti-purchase-row-pop-184 small{font-size:9.5px!important}}'\n        + '@media (max-width:920px) and (orientation:landscape){#ti-purchase-ov .ti-purchase-modal{width:98vw!important;max-width:98vw!important;padding:10px!important}#ti-purchase-ov #ti-purchase-table{min-width:900px!important}#ti-purchase-ov #ti-purchase-table th,#ti-purchase-ov #ti-purchase-table td{font-size:10px!important;padding:3px!important}#ti-purchase-ov #ti-purchase-table input,#ti-purchase-ov #ti-purchase-table select,#ti-purchase-ov #ti-purchase-table button{font-size:10px!important;padding:3px!important;min-height:25px!important}}';\n        document.head.appendChild(st);\n    }\n\n    function ensureModal(){\n        injectCss();\n        var ov = byId('ti-purchase-ov');\n        if (ov && ov.dataset && ov.dataset.rescue184 === '1') return ov;\n        if (ov && ov.parentNode) ov.parentNode.removeChild(ov);\n        ov = document.createElement('div');\n        ov.id = 'ti-purchase-ov'; ov.className = 'ti-modal-overlay ti-ov ti-closable-ov'; ov.dataset.rescue184 = '1';\n        ov.style.display = 'none'; ov.style.zIndex = '2147482000';\n        ov.innerHTML = '<div class=\"ti-modal ti-purchase-modal\">'\n            + '<div style=\"display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px\"><div><h2 style=\"margin:0;font-size:22px\">Acquisti<\/h2><div style=\"font-size:14px;color:#cbd5e1;margin-top:4px\">Tabella acquisto manuale. Il costo \u00e8 netto IVA; il prezzo vendita proposto include margine e IVA.<\/div><\/div><button type=\"button\" class=\"ti-btn\" style=\"background:#dc2626;color:#fff\" onclick=\"window.closePurchaseManualFlow()\">X<\/button><\/div>'\n            + '<div style=\"display:flex;gap:8px;flex-wrap:wrap;margin:10px 0 14px 0\"><button type=\"button\" class=\"ti-btn ti-purchase-add-row ti-ss-add-manual-row\" style=\"background:#22c55e;color:#052e16;font-weight:700\">+ Aggiungi riga manuale<\/button><button type=\"button\" class=\"ti-btn\" style=\"background:#f59e0b;color:#111827;font-weight:700\" onclick=\"window.pickPurchaseImportFile&&window.pickPurchaseImportFile()\">Import file acquisti<\/button><button type=\"button\" class=\"ti-btn\" style=\"background:#64748b;color:#fff;font-weight:700\" onclick=\"window.clearPurchaseRows()\">Svuota righe<\/button><\/div>'\n            + '<div class=\"ti-purchase-table-wrap\"><table id=\"ti-purchase-table\"><thead><tr style=\"background:#1e293b\"><th>Prodotto o servizio<\/th><th>Tipo<\/th><th>Q.t\u00e0<\/th><th>Costo netto<\/th><th>Margine<\/th><th>Prezzo vendita IVA incl.<\/th><th>IVA %<\/th><th>Tot. acquisto netto<\/th><th>Tot. vendita<\/th><th>Note<\/th><th>Azioni<\/th><\/tr><\/thead><tbody id=\"ti-purchase-body\"><\/tbody><\/table><\/div>'\n            + '<div id=\"ti-purchase-total\" aria-live=\"polite\"><\/div><div style=\"display:flex;justify-content:center;gap:8px;margin-top:14px;flex-wrap:wrap;position:sticky;bottom:0;background:#0f172a;padding-top:10px;border-top:1px solid #334155\"><button type=\"button\" id=\"ti-purchase-confirm-btn\" class=\"ti-btn\" style=\"background:#38bdf8;color:#082f49;font-weight:800\" onclick=\"window.confirmPurchaseRows()\">Conferma righe acquisto<\/button><button type=\"button\" class=\"ti-btn btn-close\" onclick=\"window.closePurchaseManualFlow()\">Chiudi<\/button><\/div><\/div>';\n        document.body.appendChild(ov);\n        return ov;\n    }\n    function showModal(){ var ov = ensureModal(); ov.classList.add('ti-purchase-rescue-open','ti-modal-open'); ov.style.display='flex'; ov.style.pointerEvents='auto'; ov.setAttribute('aria-hidden','false'); return ov; }\n    function closeModal(){ closePopover(); var ov=byId('ti-purchase-ov'); if(ov){ ov.classList.remove('ti-purchase-rescue-open','ti-modal-open'); ov.style.display='none'; ov.style.pointerEvents='none'; } }\n    function getBody(){ return ensureModal().querySelector('#ti-purchase-body'); }\n\n    function catalog(){\n        var data = window.currentDbData || {}; var tables = data.Tabelle || {}; var out = [];\n        function add(table, type, rows){ if (!Array.isArray(rows)) return; rows.forEach(function(r, idx){ var name = field(r, type === 'S' ? ['Servizio','Nome','Descrizione'] : ['Prodotto','Nome','Descrizione'], ''); if (!String(name||'').trim()) return; out.push({table:table,index:idx,type:type,typeLabel:type==='S'?'Servizio':'Prodotto',name:String(name),desc:String(field(r,['Descrizione','Note','Nota'], '')||''),cost:num(field(r,['Costo','Costo acquisto','Costo_acquisto'],0)),sale:num(field(r,['Prezzo','Prezzo vendita','Prezzo_vendita'],0)),iva:num(field(r,['IVA_VAT','IVA','Iva','VAT'],22))||22}); }); }\n        Object.keys(tables).forEach(function(t){ var nt = norm(t); if (nt.indexOf('serviz') !== -1) add(t,'S',tables[t]); else if (nt.indexOf('prodott') !== -1 || nt === 'listino') add(t,'P',tables[t]); });\n        return out;\n    }\n    function closePopover(){ var p = byId('ti-purchase-row-pop-184'); if (p) p.remove(); }\n    function selectItem(item, row){\n        row = row || window.__tiPurchaseActiveRow184; if (!row || !item) return;\n        row.dataset.catalogTable = item.table || ''; row.dataset.catalogIndex = String(item.index != null ? item.index : -1); row.dataset.targetType = item.type === 'S' ? 'servizio' : 'prodotto';\n        function val(sel,v){ var el=row.querySelector(sel); if(el) el.value = v == null ? '' : String(v).replace('.', ','); }\n        val('.ti-pur-name', item.name || ''); val('.ti-pur-type', item.type === 'S' ? 'S' : 'P'); val('.ti-pur-cost', item.cost || ''); val('.ti-pur-iva', item.iva || 22); val('.ti-pur-note', item.desc || '');\n        var sale = row.querySelector('.ti-pur-sale'); if (sale) { sale.value = item.sale ? String(item.sale).replace('.', ',') : ''; sale.dataset.auto = item.sale ? '0' : '1'; }\n        recalcRow(row, !item.sale); totals(); closePopover();\n    }\n    function showSearch(input){\n        if (!input) return; var row = input.closest('tr'); window.__tiPurchaseActiveRow184 = row;\n        var q = String(input.value || '').toLowerCase().trim(); var list = catalog().filter(function(it){ return !q || (it.name + ' ' + it.desc + ' ' + it.typeLabel).toLowerCase().indexOf(q) !== -1; }).slice(0, q ? 40 : 20);\n        closePopover(); if (!list.length) return;\n        var pop = document.createElement('div'); pop.id = 'ti-purchase-row-pop-184';\n        list.forEach(function(it){ var b = document.createElement('button'); b.type='button'; b.innerHTML = '<b>' + esc(it.typeLabel) + ' - ' + esc(it.name) + '<\/b><small>' + esc(it.table) + ' - costo ' + fmt(it.cost) + ' - prezzo ' + fmt(it.sale) + ' - IVA ' + fmt(it.iva) + '%<\/small>'; var choose=function(ev){ if(ev){ev.preventDefault();ev.stopPropagation();} selectItem(it,row); }; b.addEventListener('pointerdown', choose); b.addEventListener('click', choose); pop.appendChild(b); });\n        document.body.appendChild(pop); var r = input.getBoundingClientRect(); var w = Math.max(r.width, Math.min(560, window.innerWidth - 24)); var left = Math.max(8, Math.min(r.left, window.innerWidth - w - 8)); var top = r.bottom + 6; setTimeout(function(){ var h=pop.offsetHeight||260; if(top+h>window.innerHeight-8) top=Math.max(8,r.top-h-6); pop.style.left=left+'px'; pop.style.top=top+'px'; pop.style.width=w+'px'; },0);\n    }\n    function rowItem(tr){ return {table:tr.dataset.catalogTable||'', index:parseInt(tr.dataset.catalogIndex||'-1',10), type:(tr.querySelector('.ti-pur-type')||{}).value||'P', name:(tr.querySelector('.ti-pur-name')||{}).value||'', qty:(tr.querySelector('.ti-pur-qty')||{}).value||'1', cost:(tr.querySelector('.ti-pur-cost')||{}).value||'', margin:(tr.querySelector('.ti-pur-margin')||{}).value||'', sale:(tr.querySelector('.ti-pur-sale')||{}).value||'', iva:(tr.querySelector('.ti-pur-iva')||{}).value||'22', desc:(tr.querySelector('.ti-pur-note')||{}).value||''}; }\n    function recalcRow(row, force){\n        if (!row) return; var qty=num((row.querySelector('.ti-pur-qty')||{}).value||0); var cost=num((row.querySelector('.ti-pur-cost')||{}).value||0); var margin=(row.querySelector('.ti-pur-margin')||{}).value||''; var iva=(row.querySelector('.ti-pur-iva')||{}).value||'22'; var saleEl=row.querySelector('.ti-pur-sale'); var sale=grossSale(cost, margin, iva);\n        if (saleEl && (force || saleEl.dataset.auto === '1' || document.activeElement !== saleEl)) { saleEl.value = sale ? String(sale).replace('.', ',') : ''; saleEl.dataset.auto='1'; }\n        var saleVal = saleEl ? (num(saleEl.value) || sale) : sale; var net=row.querySelector('.ti-pur-row-total-net'); var gross=row.querySelector('.ti-pur-row-total-gross'); if(net) net.textContent=fmt(qty*cost); if(gross) gross.textContent=fmt(qty*saleVal);\n    }\n    function totals(){\n        var rows=0, qty=0, net=0, gross=0; document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr){ recalcRow(tr,false); rows++; qty += num((tr.querySelector('.ti-pur-qty')||{}).value||0); net += num((tr.querySelector('.ti-pur-qty')||{}).value||0) * num((tr.querySelector('.ti-pur-cost')||{}).value||0); gross += num((tr.querySelector('.ti-pur-qty')||{}).value||0) * num((tr.querySelector('.ti-pur-sale')||{}).value||0); });\n        var out=byId('ti-purchase-total'); if(out) out.innerHTML='<div class=\"ti-purchase-summary-card\">Righe<b>'+rows+'<\/b><\/div><div class=\"ti-purchase-summary-card\">Quantit\u00e0<b>'+fmt(qty)+'<\/b><\/div><div class=\"ti-purchase-summary-card\">Totale acquisto netto<b>\u20ac '+fmt(net)+'<\/b><\/div><div class=\"ti-purchase-summary-card\">Totale vendita IVA incl.<b>\u20ac '+fmt(gross)+'<\/b><\/div>';\n    }\n    function addRow(item){\n        var body = getBody(); if (!body) return; showModal(); item = item || {}; var tr=document.createElement('tr'); tr.className='ti-purchase-row'; tr.dataset.catalogTable=item.table||''; tr.dataset.catalogIndex=String(item.index!=null?item.index:-1); tr.dataset.targetType=item.type==='S'?'servizio':'prodotto';\n        var cost=item.cost?String(item.cost).replace('.', ','):''; var sale=item.sale?String(item.sale).replace('.', ','):''; var iva=item.iva||'22';\n        tr.innerHTML='<td><input class=\"ti-pur-name ti-purchase-row-search\" value=\"'+esc(item.name||'')+'\" placeholder=\"Cerca prodotto o servizio\" autocomplete=\"off\" style=\"width:100%;min-width:230px;box-sizing:border-box;padding:8px;border-radius:8px\"><\/td><td><select class=\"ti-pur-type\" style=\"width:76px;padding:8px;border-radius:8px\"><option value=\"P\" '+(item.type==='S'?'':'selected')+'>P<\/option><option value=\"S\" '+(item.type==='S'?'selected':'')+'>S<\/option><\/select><\/td><td><input class=\"ti-pur-qty\" value=\"'+esc(item.qty||'1')+'\" inputmode=\"decimal\" style=\"width:72px;text-align:right;padding:8px;border-radius:8px\"><\/td><td><input class=\"ti-pur-cost\" value=\"'+esc(cost)+'\" inputmode=\"decimal\" style=\"width:100px;text-align:right;padding:8px;border-radius:8px\"><\/td><td><input class=\"ti-pur-margin\" value=\"'+esc(item.margin||'')+'\" placeholder=\"+10%\" style=\"width:86px;text-align:right;padding:8px;border-radius:8px\"><\/td><td><input class=\"ti-pur-sale\" data-auto=\"'+(sale?'0':'1')+'\" value=\"'+esc(sale)+'\" inputmode=\"decimal\" style=\"width:112px;text-align:right;padding:8px;border-radius:8px\"><\/td><td><input class=\"ti-pur-iva\" value=\"'+esc(iva)+'\" inputmode=\"decimal\" style=\"width:68px;text-align:right;padding:8px;border-radius:8px\"><\/td><td style=\"text-align:right\"><span class=\"ti-pur-row-total-net\">0,00<\/span><\/td><td style=\"text-align:right\"><span class=\"ti-pur-row-total-gross\">0,00<\/span><\/td><td><input class=\"ti-pur-note\" value=\"'+esc(item.desc||'')+'\" style=\"width:180px;padding:8px;border-radius:8px\"><\/td><td style=\"text-align:center\"><button type=\"button\" class=\"ti-btn ti-pur-dup\" style=\"background:#64748b;color:#fff;margin-right:4px\">Duplica<\/button><button type=\"button\" class=\"ti-btn ti-pur-del\" style=\"background:#dc2626;color:#fff\">X<\/button><\/td>';\n        body.appendChild(tr); var name=tr.querySelector('.ti-pur-name'); if(name){ name.addEventListener('input', function(){ showSearch(name); }); name.addEventListener('focus', function(){ showSearch(name); }); }\n        tr.querySelectorAll('input,select').forEach(function(el){ if(!el.classList.contains('ti-purchase-row-search')) el.addEventListener('input', function(){ if(el.classList.contains('ti-pur-sale')) el.dataset.auto='0'; recalcRow(tr,false); totals(); }); });\n        var del=tr.querySelector('.ti-pur-del'); if(del) del.addEventListener('click', function(){ tr.remove(); if(!body.children.length) addRow(); totals(); }); var dup=tr.querySelector('.ti-pur-dup'); if(dup) dup.addEventListener('click', function(){ addRow(rowItem(tr)); }); recalcRow(tr,!sale); totals(); if(!item.name && name) setTimeout(function(){ try{name.focus();}catch(e){} }, 50);\n    }\n    function openFlow(){ loadDb().then(function(){ showModal(); var body=getBody(); if(body && !body.children.length) addRow(); totals(); }); }\n    function clearRows(){ var body=getBody(); if(!body) return; if(body.children.length && window.confirm && !window.confirm('Svuotare tutte le righe acquisto?')) return; body.innerHTML=''; addRow(); totals(); }\n    function collect(){ var rows=[]; document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr){ var it=rowItem(tr); if(String(it.name||'').trim()) rows.push(it); }); return rows; }\n    function validate(){ var err=[]; document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr,i){ var n=String((tr.querySelector('.ti-pur-name')||{}).value||'').trim(); var q=num((tr.querySelector('.ti-pur-qty')||{}).value||0); var iva=tr.querySelector('.ti-pur-iva'); if(iva && !String(iva.value||'').trim()) iva.value='22'; if(!n) err.push('Riga '+(i+1)+': prodotto\/servizio mancante'); if(q<=0) err.push('Riga '+(i+1)+': quantit\u00e0 non valida'); }); return err; }\n    function confirmRows(){\n        var btn=byId('ti-purchase-confirm-btn'); var err=validate(); if(err.length){ if(window.tiAlert) window.tiAlert('Correggi le righe acquisto:\\n'+err.slice(0,8).join('\\n')); else alert(err.join('\\n')); return; }\n        var rows=collect(); if(!rows.length){ if(window.tiAlert) window.tiAlert('Inserisci almeno una riga acquisto.'); else alert('Inserisci almeno una riga acquisto.'); return; }\n        var db=selectedDb(); if(!db || db==='NEW_DB'){ if(window.tiAlert) window.tiAlert('Seleziona prima una ditta.'); else alert('Seleziona prima una ditta.'); return; }\n        loadDb().then(function(source){ if(!source || !source.Tabelle){ throw new Error('Database non caricato'); } if(btn){ btn.disabled=true; btn.dataset.oldText=btn.innerHTML; btn.innerHTML='Salvataggio...'; }\n            var data=JSON.parse(JSON.stringify(source)); var prod=realTable(data,'Prodotti','Prodotti'); var serv=realTable(data,'Servizi','Servizi'); var act=realTable(data,'Attivita','Attivita'); if(!data.Tabelle[prod]) data.Tabelle[prod]=[]; if(!data.Tabelle[serv]) data.Tabelle[serv]=[]; if(!data.Tabelle[act]) data.Tabelle[act]=[];\n            var d=new Date(); var date=String(d.getDate()).padStart(2,'0')+'\/'+String(d.getMonth()+1).padStart(2,'0')+'\/'+d.getFullYear(); var time=String(d.getHours()).padStart(2,'0')+':'+String(d.getMinutes()).padStart(2,'0'); var nextId=function(t){ return (Array.isArray(data.Tabelle[t])?data.Tabelle[t].length:0)+1; };\n            rows.forEach(function(r){ var isS=String(r.type||'P')==='S'; var t=isS?serv:prod; var names=isS?['Servizio','Nome','Descrizione']:['Prodotto','Nome','Descrizione']; var arr=Array.isArray(data.Tabelle[t])?data.Tabelle[t]:(data.Tabelle[t]=[]); var idx=(r.table===t && r.index>=0 && arr[r.index])?r.index:-1; if(idx<0){ var wanted=norm(r.name); idx=arr.findIndex(function(x){ return norm(field(x,names,''))===wanted; }); } var rec=idx>=0?arr[idx]:{}; if(idx<0){ setField(rec,['ID','Id','id'],nextId(t)); arr.push(rec); } setField(rec,names,r.name); setField(rec,['Costo','Costo acquisto','Costo_acquisto'],num(r.cost)); setField(rec,['Margine'],String(r.margin||'')); var sale=num(r.sale)||grossSale(r.cost,r.margin,r.iva); setField(rec,['Prezzo','Prezzo vendita','Prezzo_vendita'],sale); setField(rec,['IVA_VAT','IVA','Iva','VAT'],num(r.iva)||22); if(!isS){ var old=num(field(rec,['Quantit\u00e0','Quantita','Disponibilita','Disponibilit\u00e0'],0)); setField(rec,['Quantit\u00e0','Quantita'],Math.round((old+num(r.qty))*100)\/100); } var a={}; setField(a,['ID','Id','id'],nextId(act)); setField(a,['Username','Utente','user'],window.tiUser||''); setField(a,['Descrizione attivit\u00e0','Descrizione attivita','Descrizione','Dettaglio','Note'],'Acquisto '+r.name); setField(a,['Prodotto'],r.name); setField(a,['Prezzo'],num(r.cost)); setField(a,['Quantit\u00e0','Quantita'],num(r.qty)); setField(a,['Totale'],Math.round(num(r.cost)*num(r.qty)*100)\/100); setField(a,['Margine'],String(r.margin||'')); setField(a,['IVA_VAT','IVA','Iva','VAT'],num(r.iva)||22); setField(a,['Prezzo vendita','Prezzo_vendita'],sale); setField(a,['Esito'],'acquisto'); setField(a,['Data'],date); setField(a,['Ora'],time); setField(a,['Stato'],'Attivo'); data.Tabelle[act].push(a); });\n            return ajaxForm('ti_ai_save_full_db', {db:db, payload:base64Utf8(JSON.stringify(data))}).then(function(res){ if(btn){btn.disabled=false;btn.innerHTML=btn.dataset.oldText||'Conferma righe acquisto';} if(res && res.success){ window.currentDbData=data; closeModal(); if(window.tiAlert) window.tiAlert('\u2705 Righe acquisto confermate e salvate nel database.'); else alert('Righe acquisto confermate.'); if(res.data && res.data.needs_reload) setTimeout(function(){ location.reload(); },900); } else { if(window.tiAlert) window.tiAlert('\u26a0\ufe0f Salvataggio acquisto non riuscito.'); else alert('Salvataggio acquisto non riuscito.'); } });\n        }).catch(function(e){ if(btn){btn.disabled=false;btn.innerHTML=btn.dataset.oldText||'Conferma righe acquisto';} if(window.tiAlert) window.tiAlert('Errore conferma acquisto: '+(e.message||e)); else alert('Errore conferma acquisto: '+(e.message||e)); });\n    }\n\n    window.ensurePurchaseModal = ensureModal;\n    window.openPurchaseManualFlow = openFlow;\n    window.addPurchaseManualRow = addRow;\n    window.ti183ForceAddPurchaseRow = function(ev){ if(ev){ ev.preventDefault(); ev.stopPropagation(); } addRow(); };\n    window.ti183ForcePurchaseTableActive = function(){ showModal(); var body=getBody(); if(body && !body.children.length) addRow(); totals(); return body; };\n    window.closePurchaseManualFlow = closeModal;\n    window.clearPurchaseRows = clearRows;\n    window.confirmPurchaseRows = confirmRows;\n    window.purchaseSendToChat = confirmRows;\n    window.refreshPurchaseTotals = totals;\n    window.recalcPurchaseRow = recalcRow;\n\n    function isAddButton(el){ return el && el.closest && el.closest('.ti-purchase-add-row,.ti-ss-add-manual-row'); }\n    function isPurchaseButton(el){\n        if (!el || !el.closest) return null;\n        var b = el.closest('#ti-purchase-toggle,button,a'); if(!b) return null;\n        var txt = String(b.textContent || '').toLowerCase();\n        if (b.id === 'ti-purchase-toggle') return b;\n        if (txt.indexOf('aggiungi riga manuale') !== -1) return null;\n        if (txt.indexOf('import') !== -1) return null;\n        if (txt.indexOf('acquisti') !== -1 || txt.indexOf('acquisto') !== -1) return b;\n        return null;\n    }\n    document.addEventListener('click', function(ev){\n        var add = isAddButton(ev.target); if(add){ ev.preventDefault(); if(ev.stopImmediatePropagation) ev.stopImmediatePropagation(); addRow(); return; }\n        var p = isPurchaseButton(ev.target); if(p){ ev.preventDefault(); if(ev.stopImmediatePropagation) ev.stopImmediatePropagation(); openFlow(); }\n    }, true);\n    document.addEventListener('click', function(ev){ var pop=byId('ti-purchase-row-pop-184'); if(pop && !pop.contains(ev.target) && !(ev.target.closest && ev.target.closest('.ti-purchase-row-search'))) closePopover(); });\n    function bindButtons(){\n        var b=byId('ti-purchase-toggle'); if(b) b.onclick=function(ev){ if(ev) ev.preventDefault(); openFlow(); return false; };\n        document.querySelectorAll('.ti-purchase-add-row,.ti-ss-add-manual-row').forEach(function(x){ x.onclick=function(ev){ if(ev) ev.preventDefault(); addRow(); return false; }; });\n    }\n    if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ setTimeout(bindButtons,0); setTimeout(bindButtons,800); }); else { setTimeout(bindButtons,0); setTimeout(bindButtons,800); }\n    try { new MutationObserver(function(){ bindButtons(); }).observe(document.documentElement, {childList:true, subtree:true}); } catch(e) {}\n})();\n<\/script>\n\n\n<style id=\"ti-dark-input-and-purchase-close-185\">\n\/* 30.9.185 - Input utente scuri con font bianco e chiusura acquisti sempre disponibile *\/\n#ti-ai-outer input:not([type=\"checkbox\"]):not([type=\"radio\"]):not([type=\"file\"]):not([type=\"submit\"]):not([type=\"button\"]),\n#ti-ai-outer textarea,\n#ti-ai-outer select,\n#ti-plugin-modal-root input:not([type=\"checkbox\"]):not([type=\"radio\"]):not([type=\"file\"]):not([type=\"submit\"]):not([type=\"button\"]),\n#ti-plugin-modal-root textarea,\n#ti-plugin-modal-root select,\n#ti-mobile-direct-login input,\n#ti-mobile-direct-login select,\n.ti-ov input:not([type=\"checkbox\"]):not([type=\"radio\"]):not([type=\"file\"]):not([type=\"submit\"]):not([type=\"button\"]),\n.ti-ov textarea,\n.ti-ov select,\n.ti-in,\n.ti-msg {\n    background:#020617 !important;\n    color:#ffffff !important;\n    -webkit-text-fill-color:#ffffff !important;\n    caret-color:#ffffff !important;\n    border-color:#334155 !important;\n}\n#ti-ai-outer input:focus,\n#ti-ai-outer textarea:focus,\n#ti-ai-outer select:focus,\n#ti-plugin-modal-root input:focus,\n#ti-plugin-modal-root textarea:focus,\n#ti-plugin-modal-root select:focus,\n#ti-mobile-direct-login input:focus,\n#ti-mobile-direct-login select:focus,\n.ti-ov input:focus,\n.ti-ov textarea:focus,\n.ti-ov select:focus,\n.ti-in:focus,\n.ti-msg:focus {\n    background:#000000 !important;\n    color:#ffffff !important;\n    -webkit-text-fill-color:#ffffff !important;\n    caret-color:#ffffff !important;\n    outline:2px solid rgba(56,189,248,.45) !important;\n    outline-offset:1px !important;\n}\n#ti-ai-outer input::placeholder,\n#ti-ai-outer textarea::placeholder,\n#ti-plugin-modal-root input::placeholder,\n#ti-plugin-modal-root textarea::placeholder,\n#ti-mobile-direct-login input::placeholder,\n.ti-ov input::placeholder,\n.ti-ov textarea::placeholder,\n.ti-in::placeholder,\n.ti-msg::placeholder {\n    color:#cbd5e1 !important;\n    -webkit-text-fill-color:#cbd5e1 !important;\n    opacity:1 !important;\n}\n#ti-ai-outer select option,\n#ti-plugin-modal-root select option,\n#ti-mobile-direct-login select option,\n.ti-ov select option,\n.ti-in option {\n    background:#020617 !important;\n    color:#ffffff !important;\n}\n#ti-ai-outer input:-webkit-autofill,\n#ti-ai-outer input:-webkit-autofill:hover,\n#ti-ai-outer input:-webkit-autofill:focus,\n#ti-plugin-modal-root input:-webkit-autofill,\n#ti-plugin-modal-root input:-webkit-autofill:hover,\n#ti-plugin-modal-root input:-webkit-autofill:focus,\n#ti-mobile-direct-login input:-webkit-autofill,\n#ti-mobile-direct-login input:-webkit-autofill:hover,\n#ti-mobile-direct-login input:-webkit-autofill:focus,\n.ti-ov input:-webkit-autofill,\n.ti-ov input:-webkit-autofill:hover,\n.ti-ov input:-webkit-autofill:focus {\n    -webkit-text-fill-color:#ffffff !important;\n    caret-color:#ffffff !important;\n    box-shadow:0 0 0 1000px #020617 inset !important;\n    transition:background-color 9999s ease-in-out 0s !important;\n}\n#ti-purchase-ov [data-ti-purchase-close=\"1\"],\n#ti-purchase-ov .ti-purchase-close-185 {\n    cursor:pointer !important;\n    pointer-events:auto !important;\n}\n<\/style>\n<script id=\"ti-purchase-close-and-input-fix-185\">\n(function(){\n    \"use strict\";\n    if (window.__tiPurchaseCloseInputFix185) return;\n    window.__tiPurchaseCloseInputFix185 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function safeText(el){ return String((el && el.textContent) || '').trim().toLowerCase(); }\n\n    function hardClosePurchase(){\n        try { if (typeof window.closePopover === 'function') window.closePopover(); } catch(ignore) {}\n        var pop = byId('ti-purchase-row-pop-184');\n        if (pop && pop.parentNode) pop.parentNode.removeChild(pop);\n        var ov = byId('ti-purchase-ov');\n        if (ov) {\n            ov.classList.remove('ti-purchase-rescue-open','ti-modal-open','ti-open','open','active','show');\n            ov.style.setProperty('display','none','important');\n            ov.style.setProperty('pointer-events','none','important');\n            ov.setAttribute('aria-hidden','true');\n        }\n        document.documentElement.classList.remove('ti-modal-open','ti-purchase-rescue-open');\n        document.body.classList.remove('ti-modal-open','ti-purchase-rescue-open');\n        document.body.style.removeProperty('overflow');\n        return false;\n    }\n\n    window.closePurchaseManualFlow = hardClosePurchase;\n    window.tiClosePurchaseManualFlow = hardClosePurchase;\n\n    function markPurchaseCloseButtons(){\n        var ov = byId('ti-purchase-ov');\n        if (!ov) return;\n        ov.querySelectorAll('button').forEach(function(btn){\n            var t = safeText(btn);\n            var hasCloseCall = \/closePurchaseManualFlow|tiClosePurchaseManualFlow|closeModal\\(['\\\"]ti-purchase-ov\/.test(String(btn.getAttribute('onclick') || ''));\n            if (hasCloseCall || t === 'x' || t === 'chiudi' || t === 'annulla') {\n                btn.setAttribute('data-ti-purchase-close','1');\n                btn.classList.add('ti-purchase-close-185');\n                btn.onclick = function(ev){\n                    if (ev) { ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); }\n                    return hardClosePurchase();\n                };\n            }\n        });\n    }\n\n    document.addEventListener('click', function(ev){\n        var target = ev.target;\n        if (!target || !target.closest) return;\n        var ov = byId('ti-purchase-ov');\n        if (!ov || !ov.contains(target)) return;\n        var btn = target.closest('button,[role=\"button\"],.btn-close,.ti-btn');\n        if (!btn) return;\n        var t = safeText(btn);\n        var attr = String(btn.getAttribute('data-ti-purchase-close') || '');\n        var inline = String(btn.getAttribute('onclick') || '');\n        if (attr === '1' || \/closePurchaseManualFlow|tiClosePurchaseManualFlow|closeModal\\(['\\\"]ti-purchase-ov\/.test(inline) || t === 'x' || t === 'chiudi' || t === 'annulla') {\n            ev.preventDefault();\n            ev.stopPropagation();\n            if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();\n            hardClosePurchase();\n        }\n    }, true);\n\n    document.addEventListener('keydown', function(ev){\n        if (ev.key === 'Escape') {\n            var ov = byId('ti-purchase-ov');\n            if (ov && (ov.classList.contains('ti-purchase-rescue-open') || ov.style.display !== 'none')) hardClosePurchase();\n        }\n    }, true);\n\n    function restyleDarkInputs(){\n        var roots = [byId('ti-ai-outer'), byId('ti-plugin-modal-root'), document.body];\n        roots.forEach(function(root){\n            if (!root) return;\n            root.querySelectorAll('input:not([type=\"checkbox\"]):not([type=\"radio\"]):not([type=\"file\"]):not([type=\"submit\"]):not([type=\"button\"]), textarea, select').forEach(function(el){\n                el.style.setProperty('background','#020617','important');\n                el.style.setProperty('color','#ffffff','important');\n                el.style.setProperty('-webkit-text-fill-color','#ffffff','important');\n                el.style.setProperty('caret-color','#ffffff','important');\n                if (!el.style.borderColor || el.style.borderColor === 'transparent') el.style.setProperty('border-color','#334155','important');\n            });\n        });\n    }\n\n    function boot(){\n        markPurchaseCloseButtons();\n        restyleDarkInputs();\n    }\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ boot(); setTimeout(boot,250); setTimeout(boot,1000); });\n    else { boot(); setTimeout(boot,250); setTimeout(boot,1000); }\n    try { new MutationObserver(function(){ boot(); }).observe(document.documentElement, {childList:true, subtree:true}); } catch(ignore) {}\n})();\n<\/script>\n\n\n<style id=\"ti-login-input-purchase-fix-186-css\">\n\/* 30.9.186 - Input scuri completi, login PC robusto, chiusura tabella acquisti *\/\n#ti-ai-outer input:not([type=\"checkbox\"]):not([type=\"radio\"]):not([type=\"file\"]):not([type=\"submit\"]):not([type=\"button\"]),\n#ti-ai-outer textarea,\n#ti-ai-outer select,\n#ti-plugin-modal-root input:not([type=\"checkbox\"]):not([type=\"radio\"]):not([type=\"file\"]):not([type=\"submit\"]):not([type=\"button\"]),\n#ti-plugin-modal-root textarea,\n#ti-plugin-modal-root select,\n#ti-login-ov input:not([type=\"checkbox\"]):not([type=\"radio\"]):not([type=\"file\"]):not([type=\"submit\"]):not([type=\"button\"]),\n#ti-login-ov textarea,\n#ti-login-ov select,\n#ti-mobile-direct-login input:not([type=\"checkbox\"]):not([type=\"radio\"]):not([type=\"file\"]):not([type=\"submit\"]):not([type=\"button\"]),\n#ti-mobile-direct-login textarea,\n#ti-mobile-direct-login select,\n#ti-purchase-ov input:not([type=\"checkbox\"]):not([type=\"radio\"]):not([type=\"file\"]):not([type=\"submit\"]):not([type=\"button\"]),\n#ti-purchase-ov textarea,\n#ti-purchase-ov select,\n.ti-ov input:not([type=\"checkbox\"]):not([type=\"radio\"]):not([type=\"file\"]):not([type=\"submit\"]):not([type=\"button\"]),\n.ti-ov textarea,\n.ti-ov select,\n.ti-in,\n.ti-msg {\n    background:#020617 !important;\n    background-color:#020617 !important;\n    color:#ffffff !important;\n    -webkit-text-fill-color:#ffffff !important;\n    caret-color:#ffffff !important;\n    border-color:#334155 !important;\n}\n#ti-ai-outer input:focus,\n#ti-ai-outer textarea:focus,\n#ti-ai-outer select:focus,\n#ti-plugin-modal-root input:focus,\n#ti-plugin-modal-root textarea:focus,\n#ti-plugin-modal-root select:focus,\n#ti-login-ov input:focus,\n#ti-login-ov textarea:focus,\n#ti-login-ov select:focus,\n#ti-mobile-direct-login input:focus,\n#ti-mobile-direct-login textarea:focus,\n#ti-mobile-direct-login select:focus,\n#ti-purchase-ov input:focus,\n#ti-purchase-ov textarea:focus,\n#ti-purchase-ov select:focus,\n.ti-ov input:focus,\n.ti-ov textarea:focus,\n.ti-ov select:focus,\n.ti-in:focus,\n.ti-msg:focus {\n    background:#000000 !important;\n    background-color:#000000 !important;\n    color:#ffffff !important;\n    -webkit-text-fill-color:#ffffff !important;\n    caret-color:#ffffff !important;\n    outline:2px solid rgba(56,189,248,.50) !important;\n    outline-offset:1px !important;\n}\n#ti-ai-outer input::placeholder,\n#ti-ai-outer textarea::placeholder,\n#ti-plugin-modal-root input::placeholder,\n#ti-plugin-modal-root textarea::placeholder,\n#ti-login-ov input::placeholder,\n#ti-login-ov textarea::placeholder,\n#ti-mobile-direct-login input::placeholder,\n#ti-mobile-direct-login textarea::placeholder,\n#ti-purchase-ov input::placeholder,\n#ti-purchase-ov textarea::placeholder,\n.ti-ov input::placeholder,\n.ti-ov textarea::placeholder,\n.ti-in::placeholder,\n.ti-msg::placeholder {\n    color:#cbd5e1 !important;\n    -webkit-text-fill-color:#cbd5e1 !important;\n    opacity:1 !important;\n}\n#ti-ai-outer select option,\n#ti-plugin-modal-root select option,\n#ti-login-ov select option,\n#ti-mobile-direct-login select option,\n#ti-purchase-ov select option,\n.ti-ov select option {\n    background:#020617 !important;\n    color:#ffffff !important;\n    -webkit-text-fill-color:#ffffff !important;\n}\n#ti-ai-outer input:-webkit-autofill,\n#ti-ai-outer input:-webkit-autofill:hover,\n#ti-ai-outer input:-webkit-autofill:focus,\n#ti-plugin-modal-root input:-webkit-autofill,\n#ti-plugin-modal-root input:-webkit-autofill:hover,\n#ti-plugin-modal-root input:-webkit-autofill:focus,\n#ti-login-ov input:-webkit-autofill,\n#ti-login-ov input:-webkit-autofill:hover,\n#ti-login-ov input:-webkit-autofill:focus,\n#ti-mobile-direct-login input:-webkit-autofill,\n#ti-mobile-direct-login input:-webkit-autofill:hover,\n#ti-mobile-direct-login input:-webkit-autofill:focus,\n#ti-purchase-ov input:-webkit-autofill,\n#ti-purchase-ov input:-webkit-autofill:hover,\n#ti-purchase-ov input:-webkit-autofill:focus,\n.ti-ov input:-webkit-autofill,\n.ti-ov input:-webkit-autofill:hover,\n.ti-ov input:-webkit-autofill:focus {\n    -webkit-text-fill-color:#ffffff !important;\n    caret-color:#ffffff !important;\n    box-shadow:0 0 0 1000px #020617 inset !important;\n    transition:background-color 9999s ease-in-out 0s !important;\n}\n#ti-login-ov,\n#ti-purchase-ov.ti-purchase-rescue-open,\n#ti-purchase-ov.ti-modal-open {\n    pointer-events:auto !important;\n}\n#ti-login-ov .ti-modal,\n#ti-purchase-ov .ti-modal,\n#ti-purchase-ov button,\n#ti-purchase-ov .btn-close,\n#ti-purchase-ov .ti-purchase-close-186 {\n    pointer-events:auto !important;\n}\n<\/style>\n<script id=\"ti-login-input-purchase-fix-186\">\n(function(){\n    \"use strict\";\n    if (window.__tiLoginInputPurchaseFix186) return;\n    window.__tiLoginInputPurchaseFix186 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function txt(el){ return String((el && el.textContent) || '').trim().toLowerCase(); }\n    function isMobile(){ return !!(window.matchMedia && window.matchMedia('(max-width: 900px)').matches); }\n    function alertUser(msg){ try { if (window.tiAlert) window.tiAlert(msg); else window.alert(msg); } catch(ignore) {} }\n\n    function unlockBody(){\n        try {\n            document.documentElement.classList.remove('ti-no-scroll','ti-modal-open','ti-purchase-rescue-open');\n            document.body.classList.remove('ti-no-scroll','ti-modal-open','ti-purchase-rescue-open');\n            document.documentElement.style.removeProperty('overflow');\n            document.body.style.removeProperty('overflow');\n            document.body.style.removeProperty('position');\n        } catch(ignore) {}\n    }\n\n    function applyDarkInputs(root){\n        root = root || document;\n        var selector = 'input:not([type=\"checkbox\"]):not([type=\"radio\"]):not([type=\"file\"]):not([type=\"submit\"]):not([type=\"button\"]), textarea, select';\n        try {\n            root.querySelectorAll(selector).forEach(function(el){\n                if (el.id === 'ti-file-in' || el.type === 'hidden') return;\n                el.style.setProperty('background', '#020617', 'important');\n                el.style.setProperty('background-color', '#020617', 'important');\n                el.style.setProperty('color', '#ffffff', 'important');\n                el.style.setProperty('-webkit-text-fill-color', '#ffffff', 'important');\n                el.style.setProperty('caret-color', '#ffffff', 'important');\n                el.style.setProperty('border-color', '#334155', 'important');\n            });\n        } catch(ignore) {}\n    }\n\n    function closePurchase(){\n        try { if (typeof window.closePopover === 'function') window.closePopover(); } catch(ignore) {}\n        ['ti-purchase-row-pop-184','ti-purchase-row-search-popover'].forEach(function(id){ var p = byId(id); if (p && p.parentNode) p.parentNode.removeChild(p); });\n        var ov = byId('ti-purchase-ov');\n        if (ov) {\n            ov.classList.remove('ti-purchase-rescue-open','ti-modal-open','ti-purchase-open','ti-open','open','active','show');\n            ov.style.setProperty('display','none','important');\n            ov.style.setProperty('pointer-events','none','important');\n            ov.setAttribute('aria-hidden','true');\n            \/* Rimozione fisica: evita overlay invisibili che bloccano i click e consente la ricreazione pulita. *\/\n            try { if (ov.parentNode) ov.parentNode.removeChild(ov); } catch(ignore) {}\n        }\n        unlockBody();\n        return false;\n    }\n    window.closePurchaseManualFlow = closePurchase;\n    window.tiClosePurchaseManualFlow = closePurchase;\n\n    function bindPurchaseClose(root){\n        root = root || document;\n        try {\n            root.querySelectorAll('#ti-purchase-ov button, #ti-purchase-ov [role=\"button\"], #ti-purchase-ov .btn-close').forEach(function(btn){\n                var label = txt(btn);\n                var inline = String(btn.getAttribute('onclick') || '');\n                var isClose = btn.classList.contains('btn-close') || btn.hasAttribute('data-ti-purchase-close') || \/closePurchaseManualFlow|tiClosePurchaseManualFlow|closeModal\\(['\\\"]ti-purchase-ov\/.test(inline) || label === 'x' || label === '\u00d7' || label === '\u2715' || label === '\u2716' || label.indexOf('chiudi') !== -1 || label.indexOf('annulla') !== -1;\n                if (!isClose) return;\n                btn.setAttribute('data-ti-purchase-close','1');\n                btn.classList.add('ti-purchase-close-186');\n                btn.style.setProperty('pointer-events','auto','important');\n                btn.onclick = function(ev){ if (ev) { ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); } return closePurchase(); };\n            });\n        } catch(ignore) {}\n    }\n\n    document.addEventListener('click', function(ev){\n        var target = ev.target;\n        if (!target || !target.closest) return;\n        var btn = target.closest('#ti-purchase-ov button, #ti-purchase-ov [role=\"button\"], #ti-purchase-ov .btn-close');\n        if (btn) {\n            var label = txt(btn);\n            var inline = String(btn.getAttribute('onclick') || '');\n            if (btn.getAttribute('data-ti-purchase-close') === '1' || btn.classList.contains('btn-close') || \/closePurchaseManualFlow|tiClosePurchaseManualFlow|closeModal\\(['\\\"]ti-purchase-ov\/.test(inline) || label === 'x' || label === '\u00d7' || label === '\u2715' || label === '\u2716' || label.indexOf('chiudi') !== -1 || label.indexOf('annulla') !== -1) {\n                ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();\n                closePurchase();\n                return;\n            }\n        }\n    }, true);\n\n    document.addEventListener('keydown', function(ev){\n        if (ev.key === 'Escape') {\n            var pov = byId('ti-purchase-ov');\n            if (pov && (pov.style.display !== 'none' || pov.classList.contains('ti-purchase-rescue-open') || pov.classList.contains('ti-modal-open'))) closePurchase();\n        }\n    }, true);\n\n    function openLogin(ev){\n        if (ev) { ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); }\n        var ov = byId('ti-login-ov');\n        if (!ov) return false;\n        try { if (window.syncLoginDbSelect) window.syncLoginDbSelect(); } catch(ignore) {}\n        ov.style.setProperty('display','flex','important');\n        ov.style.setProperty('align-items','center','important');\n        ov.style.setProperty('justify-content','center','important');\n        ov.style.setProperty('pointer-events','auto','important');\n        ov.style.setProperty('z-index','2147483000','important');\n        ov.classList.add('ti-modal-open','ti-login-open-186');\n        ov.removeAttribute('aria-hidden');\n        var modal = ov.querySelector('.ti-modal');\n        if (modal) modal.style.setProperty('pointer-events','auto','important');\n        applyDarkInputs(ov);\n        bindLoginControls();\n        if (!isMobile()) setTimeout(function(){ var u = byId('l-user'); if (u && typeof u.focus === 'function') { try { u.focus({preventScroll:true}); } catch(e) { try { u.focus(); } catch(ignore) {} } } }, 60);\n        return false;\n    }\n\n    function closeLogin(){\n        var ov = byId('ti-login-ov');\n        if (ov) {\n            ov.classList.remove('ti-modal-open','ti-login-open-186');\n            ov.style.setProperty('display','none','important');\n            ov.setAttribute('aria-hidden','true');\n        }\n        unlockBody();\n        return false;\n    }\n\n    function postLogin(url, fd){\n        return fetch(url, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store', headers:{'X-Requested-With':'XMLHttpRequest'}}).then(function(resp){\n            return resp.text().then(function(raw){\n                if (!resp.ok) throw new Error('HTTP ' + resp.status + ': ' + raw.slice(0, 180));\n                try { return JSON.parse(raw); } catch(e) { throw new Error('Risposta login non JSON: ' + raw.slice(0, 180)); }\n            });\n        });\n    }\n\n    function nativeLoginFallback(detail){\n        try {\n            if (window.revealDirectLoginFallback) {\n                window.revealDirectLoginFallback('native-fallback-disabled-186', detail || 'login principale non completato');\n                return true;\n            }\n        } catch(e) {}\n        return false;\n    }\n\n    function submitLogin(ev){\n        if (ev) { ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); }\n        var userEl = byId('l-user'), passEl = byId('l-pass'), dbEl = byId('l-db'), hiddenDb = byId('l-db-hidden'), btn = byId('l-do');\n        var u = userEl ? String(userEl.value || '').trim() : '';\n        var p = passEl ? String(passEl.value || '') : '';\n        var db = dbEl && dbEl.value ? dbEl.value : (hiddenDb ? hiddenDb.value : '');\n        if (!u) { alertUser('Inserisci username'); if (userEl && !isMobile()) try { userEl.focus(); } catch(ignore) {} return false; }\n        if (u !== 'SSGlobalAdmin' && !db) { alertUser('Seleziona una ditta nella finestra di login.'); if (dbEl && !isMobile()) try { dbEl.focus(); } catch(ignore) {} return false; }\n        if (hiddenDb && db) hiddenDb.value = db;\n        if (window.__tiLoginSubmitting186) return false;\n        window.__tiLoginSubmitting186 = true;\n        if (btn) { btn.disabled = true; btn.textContent = 'Accesso...'; }\n        var fd = new FormData();\n        fd.append('action','ti_ai_login'); fd.append('ti_action','ti_ai_login'); fd.append('ti_mobile_login_redirect','0'); fd.append('redirect','0');\n        fd.append('user',u); fd.append('username',u); fd.append('pass',p); fd.append('password',p); fd.append('db',db); fd.append('db_select',db); fd.append('current_db',db);\n        var urls = [];\n        if (window.tiAjaxUrl) urls.push(window.tiAjaxUrl);\n        if (window.tiUrl && urls.indexOf(window.tiUrl) === -1) urls.push(window.tiUrl);\n        if (!urls.length) urls.push(window.location.href.split('#')[0]);\n        var chain = Promise.reject(new Error('init'));\n        urls.forEach(function(url){ chain = chain.catch(function(){ return postLogin(url, fd); }); });\n        chain.then(function(res){\n            if (res && res.success) {\n                try { if (db) localStorage.setItem('ti_saved_db', db); if (db) localStorage.setItem('ti_last_login_db', db); localStorage.setItem('ti_last_login_user', u || ''); sessionStorage.removeItem('ti_login_failed_clear'); localStorage.setItem('ti_login_ok_ts', String(Date.now())); } catch(ignore) {}\n                closeLogin();\n                try { if (window.tiCloseDirectLoginFallback) window.tiCloseDirectLoginFallback(); } catch(ignore) {}\n                if (window.tiReloadAfterSuccessfulLogin) window.tiReloadAfterSuccessfulLogin(); else window.location.reload();\n                return;\n            }\n            var msg = (res && res.data && res.data.message) ? res.data.message : 'Login non riuscito';\n            alertUser('Accesso non riuscito. Verifica username, password e ditta selezionata. Ti propongo Accesso diretto Shop & Service. Dettaglio: ' + msg);\n            try {\n                if (window.tiClearLoginFieldsAfterFailure) window.tiClearLoginFieldsAfterFailure(false); else if (passEl) passEl.value = '';\n                if (window.revealDirectLoginFallback) window.revealDirectLoginFallback('login-non-riuscito', msg); else nativeLoginFallback(msg);\n            } catch(ignore) {}\n        }).catch(function(err){\n            var msg = err && err.message ? err.message : 'Errore di connessione durante il login.';\n            alertUser('Accesso tramite tasto Accedi non completato. Ti propongo Accesso diretto Shop & Service. Dettaglio: ' + msg);\n            try {\n                if (window.tiClearLoginFieldsAfterFailure) window.tiClearLoginFieldsAfterFailure(false); else if (passEl) passEl.focus();\n                if (window.revealDirectLoginFallback) window.revealDirectLoginFallback('login-non-completato', msg); else nativeLoginFallback(msg);\n            } catch(ignore) {}\n        }).finally(function(){\n            window.__tiLoginSubmitting186 = false;\n            if (btn) { btn.disabled = false; btn.textContent = 'Entra'; }\n        });\n        return false;\n    }\n\n    function bindLoginControls(){\n        var openBtn = byId('ti-login-btn');\n        var form = byId('ti-login-form');\n        var doBtn = byId('l-do');\n        var closeBtnList = document.querySelectorAll('#ti-login-ov .btn-close, #ti-login-ov button[onclick*=\"closeModal\"]');\n        if (openBtn && !openBtn.dataset.tiLoginFix186) {\n            openBtn.dataset.tiLoginFix186 = '1';\n            openBtn.onclick = openLogin;\n            openBtn.addEventListener('click', openLogin, true);\n            openBtn.addEventListener('touchend', openLogin, {passive:false, capture:true});\n        }\n        if (form && !form.dataset.tiLoginSubmitFix186) {\n            form.dataset.tiLoginSubmitFix186 = '1';\n            form.addEventListener('submit', submitLogin, true);\n        }\n        if (doBtn && !doBtn.dataset.tiLoginClickFix186) {\n            doBtn.dataset.tiLoginClickFix186 = '1';\n            doBtn.onclick = submitLogin;\n            doBtn.addEventListener('click', submitLogin, true);\n        }\n        closeBtnList.forEach(function(b){\n            if (b.dataset.tiLoginCloseFix186) return;\n            b.dataset.tiLoginCloseFix186 = '1';\n            b.addEventListener('click', function(ev){\n                if (String(b.getAttribute('onclick') || '').indexOf('ti-login-ov') !== -1 || txt(b).indexOf('chiudi') !== -1) {\n                    ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); closeLogin();\n                }\n            }, true);\n        });\n        var dbEl = byId('l-db'), hidden = byId('l-db-hidden');\n        if (dbEl && hidden && !dbEl.dataset.tiLoginDbFix186) {\n            dbEl.dataset.tiLoginDbFix186 = '1';\n            dbEl.addEventListener('change', function(){ hidden.value = dbEl.value || ''; }, true);\n        }\n    }\n\n    function boot(){\n        applyDarkInputs(document);\n        bindPurchaseClose(document);\n        bindLoginControls();\n        try { if (window.tiUser && window.tiCloseDirectLoginFallback) window.tiCloseDirectLoginFallback(); } catch(ignore) {}\n    }\n\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ boot(); setTimeout(boot,250); setTimeout(boot,1000); });\n    else { boot(); setTimeout(boot,250); setTimeout(boot,1000); }\n    try { new MutationObserver(function(muts){ boot(); }).observe(document.documentElement, {childList:true, subtree:true}); } catch(ignore) {}\n})();\n<\/script>\n\n\n<script id=\"ti-purchase-confirm-fix-187\">\n(function(){\n    \"use strict\";\n    if (window.__tiPurchaseConfirmFix187) return;\n    window.__tiPurchaseConfirmFix187 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function alertUser(msg){ try { if (window.tiAlert) window.tiAlert(msg); else window.alert(msg); } catch(ignore) {} }\n    function norm(v){\n        return String(v == null ? '' : v).normalize('NFD').replace(\/[\\u0300-\\u036f]\/g,'').toLowerCase().replace(\/[^a-z0-9]\/g,'');\n    }\n    function num(v){\n        if (v == null) return 0;\n        var s = String(v).trim().replace(\/[\u20ac\\s]\/g,'');\n        if (!s) return 0;\n        if (s.indexOf(',') !== -1 && s.indexOf('.') !== -1) s = s.replace(\/\\.\/g,'').replace(',', '.');\n        else if (s.indexOf(',') !== -1) s = s.replace(',', '.');\n        var n = parseFloat(s.replace(\/[^0-9.\\-]\/g,''));\n        return isNaN(n) ? 0 : n;\n    }\n    function field(row, names, fallback){\n        row = row || {};\n        var keys = Object.keys(row);\n        for (var i = 0; i < names.length; i++) {\n            var wanted = norm(names[i]);\n            for (var j = 0; j < keys.length; j++) {\n                if (norm(keys[j]) === wanted) return row[keys[j]];\n            }\n        }\n        return fallback;\n    }\n    function setField(row, names, value){\n        row = row || {};\n        var keys = Object.keys(row);\n        for (var i = 0; i < names.length; i++) {\n            var wanted = norm(names[i]);\n            for (var j = 0; j < keys.length; j++) {\n                if (norm(keys[j]) === wanted) { row[keys[j]] = value; return; }\n            }\n        }\n        row[names[0]] = value;\n    }\n    function realTable(data, wanted, fallback){\n        var tables = data && data.Tabelle ? data.Tabelle : {};\n        var keys = Object.keys(tables || {});\n        var w = norm(wanted);\n        for (var i = 0; i < keys.length; i++) if (norm(keys[i]) === w) return keys[i];\n        for (var j = 0; j < keys.length; j++) {\n            var k = norm(keys[j]);\n            if (k.indexOf(w) !== -1 || w.indexOf(k) !== -1) return keys[j];\n        }\n        return fallback || wanted;\n    }\n    function marginNet(cost, margin){\n        cost = num(cost);\n        var m = String(margin || '').trim();\n        if (!m) return Math.max(0, cost);\n        var pct = m.indexOf('%') !== -1;\n        m = m.replace(\/[%\u20ac\\s]\/g,'');\n        var sign = 1;\n        if (m.charAt(0) === '-') { sign = -1; m = m.slice(1); }\n        else if (m.charAt(0) === '+') m = m.slice(1);\n        var v = num(m);\n        return Math.max(0, pct ? cost + (cost * sign * v \/ 100) : cost + (sign * v));\n    }\n    function grossSale(cost, margin, iva){\n        iva = num(iva); if (iva <= 0) iva = 22;\n        return Math.round(marginNet(cost, margin) * (1 + iva \/ 100) * 100) \/ 100;\n    }\n    function base64Utf8(str){\n        try { return btoa(unescape(encodeURIComponent(str))); }\n        catch(e) { return btoa(str); }\n    }\n    function selectedDb(){\n        var el = byId('ti-ditta');\n        return el ? String(el.value || '') : '';\n    }\n    function buildForm(actionName, extra){\n        var fd = new FormData();\n        fd.append('action', actionName);\n        fd.append('ti_action', actionName);\n        Object.keys(extra || {}).forEach(function(k){\n            if (extra[k] !== undefined && extra[k] !== null) fd.append(k, extra[k]);\n        });\n        return fd;\n    }\n    function jsonPost(actionName, extra){\n        var fd = buildForm(actionName, extra);\n        var urls = [];\n        var pageUrl = window.tiUrl || window.location.href.split('#')[0].split('?')[0];\n        if (pageUrl) urls.push(pageUrl);\n        if (window.tiAjaxUrl && urls.indexOf(window.tiAjaxUrl) === -1) urls.push(window.tiAjaxUrl);\n        var lastErr = null;\n        function tryAt(i){\n            if (i >= urls.length) return Promise.reject(lastErr || new Error('Nessun endpoint disponibile.'));\n            return fetch(urls[i], {method:'POST', body:fd, credentials:'same-origin', cache:'no-store', headers:{'X-Requested-With':'XMLHttpRequest'}}).then(function(resp){\n                return resp.text().then(function(raw){\n                    var txt = String(raw || '').trim();\n                    if (!resp.ok) throw new Error('HTTP ' + resp.status + ': ' + txt.slice(0, 180));\n                    if (txt === '0' || txt === '-1') throw new Error('Endpoint AJAX non valido o sessione WordPress non autorizzata.');\n                    try { return JSON.parse(txt); }\n                    catch(e) { throw new Error('Risposta server non JSON: ' + txt.slice(0, 220)); }\n                });\n            }).catch(function(err){ lastErr = err; return tryAt(i + 1); });\n        }\n        return tryAt(0);\n    }\n    function loadDb(){\n        var current = window.currentDbData;\n        if (current && current.Tabelle && current.Tabelle.Config && current.Tabelle.Utenti) return Promise.resolve(current);\n        var db = selectedDb();\n        if (!db || db === 'NEW_DB') return Promise.reject(new Error('Seleziona prima una ditta.'));\n        return jsonPost('ti_ai_get_db_data', {db:db}).then(function(res){\n            if (!res || !res.success) throw new Error((res && res.data && res.data.message) || 'Lettura database non riuscita.');\n            window.currentDbData = res.data;\n            return window.currentDbData;\n        });\n    }\n    function rowItem(tr){\n        tr = tr || {};\n        var q = function(sel){ return tr.querySelector ? tr.querySelector(sel) : null; };\n        return {\n            table:tr.dataset ? String(tr.dataset.catalogTable || '') : '',\n            index:tr.dataset ? parseInt(tr.dataset.catalogIndex || '-1', 10) : -1,\n            type:String((q('.ti-pur-type') || {}).value || 'P'),\n            name:String((q('.ti-pur-name') || {}).value || '').trim(),\n            qty:String((q('.ti-pur-qty') || {}).value || '1'),\n            cost:String((q('.ti-pur-cost') || {}).value || ''),\n            margin:String((q('.ti-pur-margin') || {}).value || ''),\n            sale:String((q('.ti-pur-sale') || {}).value || ''),\n            iva:String((q('.ti-pur-iva') || {}).value || '22'),\n            note:String((q('.ti-pur-note') || {}).value || '')\n        };\n    }\n    function collectRows(){\n        var rows = [];\n        document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr){\n            var r = rowItem(tr);\n            if (r.name) rows.push(r);\n        });\n        return rows;\n    }\n    function validateRows(){\n        var errors = [];\n        document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr, i){\n            var name = String(((tr.querySelector('.ti-pur-name') || {}).value) || '').trim();\n            var qty = num(((tr.querySelector('.ti-pur-qty') || {}).value) || 0);\n            var ivaEl = tr.querySelector('.ti-pur-iva');\n            if (ivaEl && !String(ivaEl.value || '').trim()) ivaEl.value = '22';\n            if (!name) errors.push('Riga ' + (i + 1) + ': prodotto\/servizio mancante');\n            if (qty <= 0) errors.push('Riga ' + (i + 1) + ': quantit\u00e0 non valida');\n        });\n        return errors;\n    }\n    function nextIdFor(tableRows){\n        var maxId = 0;\n        (Array.isArray(tableRows) ? tableRows : []).forEach(function(r){ maxId = Math.max(maxId, parseInt(field(r, ['ID','Id','id'], 0), 10) || 0); });\n        return maxId + 1;\n    }\n    function closePurchase(){\n        try { if (typeof window.closePurchaseManualFlow === 'function') window.closePurchaseManualFlow(); } catch(ignore) {}\n        var ov = byId('ti-purchase-ov');\n        if (ov) {\n            ov.classList.remove('ti-purchase-rescue-open','ti-modal-open','open','active','show');\n            ov.style.setProperty('display','none','important');\n            ov.style.setProperty('pointer-events','none','important');\n        }\n    }\n    function confirmRows187(ev){\n        if (ev) { ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); }\n        var btn = byId('ti-purchase-confirm-btn');\n        var errors = validateRows();\n        if (errors.length) { alertUser('Correggi le righe acquisto:\\n' + errors.slice(0, 8).join('\\n')); return false; }\n        var rows = collectRows();\n        if (!rows.length) { alertUser('Inserisci almeno una riga acquisto.'); return false; }\n        var db = selectedDb();\n        if (!db || db === 'NEW_DB') { alertUser('Seleziona prima una ditta.'); return false; }\n        if (window.__tiPurchaseSaving187) return false;\n        window.__tiPurchaseSaving187 = true;\n        if (btn) { btn.disabled = true; btn.dataset.oldText187 = btn.innerHTML; btn.innerHTML = 'Salvataggio...'; }\n        loadDb().then(function(source){\n            if (!source || !source.Tabelle || !source.Tabelle.Config || !source.Tabelle.Utenti) throw new Error('Database non caricato correttamente: mancano Config o Utenti.');\n            var data = JSON.parse(JSON.stringify(source));\n            var prodTable = realTable(data, 'Prodotti', 'Prodotti');\n            var servTable = realTable(data, 'Servizi', 'Servizi');\n            var actTable = realTable(data, 'Attivita', 'Attivita');\n            if (!Array.isArray(data.Tabelle[prodTable])) data.Tabelle[prodTable] = [];\n            if (!Array.isArray(data.Tabelle[servTable])) data.Tabelle[servTable] = [];\n            if (!Array.isArray(data.Tabelle[actTable])) data.Tabelle[actTable] = [];\n            var d = new Date();\n            var date = String(d.getDate()).padStart(2,'0') + '\/' + String(d.getMonth() + 1).padStart(2,'0') + '\/' + d.getFullYear();\n            var time = String(d.getHours()).padStart(2,'0') + ':' + String(d.getMinutes()).padStart(2,'0');\n            rows.forEach(function(r){\n                var isService = String(r.type || 'P') === 'S';\n                var table = isService ? servTable : prodTable;\n                var nameFields = isService ? ['Servizio','Nome','Descrizione'] : ['Prodotto','Nome','Descrizione'];\n                var arr = data.Tabelle[table];\n                var idx = (r.table === table && r.index >= 0 && arr[r.index]) ? r.index : -1;\n                if (idx < 0) {\n                    var wanted = norm(r.name);\n                    idx = arr.findIndex(function(existing){ return norm(field(existing, nameFields, '')) === wanted; });\n                }\n                var rec = idx >= 0 ? arr[idx] : {};\n                if (idx < 0) { setField(rec, ['ID','Id','id'], nextIdFor(arr)); arr.push(rec); }\n                setField(rec, nameFields, r.name);\n                setField(rec, ['Costo','Costo acquisto','Costo_acquisto'], num(r.cost));\n                setField(rec, ['Margine'], String(r.margin || ''));\n                var sale = num(r.sale) || grossSale(r.cost, r.margin, r.iva);\n                setField(rec, ['Prezzo','Prezzo vendita','Prezzo_vendita'], sale);\n                setField(rec, ['IVA_VAT','IVA','Iva','VAT'], num(r.iva) || 22);\n                if (r.note) setField(rec, ['Note','Nota','Descrizione'], r.note);\n                if (!isService) {\n                    var oldQty = num(field(rec, ['Quantit\u00e0','Quantita','Disponibilita','Disponibilit\u00e0'], 0));\n                    setField(rec, ['Quantit\u00e0','Quantita'], Math.round((oldQty + num(r.qty)) * 100) \/ 100);\n                }\n                var act = {};\n                setField(act, ['ID','Id','id'], nextIdFor(data.Tabelle[actTable]));\n                setField(act, ['Username','Utente','user'], window.tiUser || '');\n                setField(act, ['Descrizione attivit\u00e0','Descrizione attivita','Descrizione','Dettaglio','Note'], 'Acquisto ' + r.name);\n                setField(act, ['Prodotto'], r.name);\n                setField(act, ['Prezzo'], num(r.cost));\n                setField(act, ['Quantit\u00e0','Quantita'], num(r.qty));\n                setField(act, ['Totale'], Math.round(num(r.cost) * num(r.qty) * 100) \/ 100);\n                setField(act, ['Margine'], String(r.margin || ''));\n                setField(act, ['IVA_VAT','IVA','Iva','VAT'], num(r.iva) || 22);\n                setField(act, ['Prezzo vendita','Prezzo_vendita'], sale);\n                setField(act, ['Esito'], 'acquisto');\n                setField(act, ['Data'], date);\n                setField(act, ['Ora'], time);\n                setField(act, ['Stato'], 'Attivo');\n                data.Tabelle[actTable].push(act);\n            });\n            return jsonPost('ti_ai_save_full_db', {db:db, payload:base64Utf8(JSON.stringify(data))}).then(function(res){\n                if (!res || !res.success) throw new Error((res && res.data && res.data.message) || 'Salvataggio acquisto non riuscito.');\n                window.currentDbData = data;\n                closePurchase();\n                alertUser('\u2705 Righe acquisto confermate e salvate nel database.');\n                if (res.data && res.data.needs_reload) setTimeout(function(){ window.location.reload(); }, 900);\n            });\n        }).catch(function(err){\n            alertUser('Errore conferma acquisto: ' + ((err && err.message) || err));\n        }).finally(function(){\n            window.__tiPurchaseSaving187 = false;\n            if (btn) { btn.disabled = false; btn.innerHTML = btn.dataset.oldText187 || 'Conferma righe acquisto'; }\n        });\n        return false;\n    }\n\n    window.confirmPurchaseRows = confirmRows187;\n    window.purchaseSendToChat = confirmRows187;\n\n    function bind(){\n        var btn = byId('ti-purchase-confirm-btn');\n        if (btn && btn.dataset.tiConfirmFix187 !== '1') {\n            btn.dataset.tiConfirmFix187 = '1';\n            btn.onclick = confirmRows187;\n            btn.addEventListener('click', confirmRows187, true);\n            btn.addEventListener('touchend', confirmRows187, {passive:false, capture:true});\n        }\n    }\n    document.addEventListener('click', function(ev){\n        var t = ev.target;\n        if (!t || !t.closest) return;\n        var btn = t.closest('#ti-purchase-confirm-btn,button');\n        if (!btn) return;\n        var label = String(btn.textContent || '').toLowerCase();\n        if (btn.id === 'ti-purchase-confirm-btn' || label.indexOf('conferma righe acquisto') !== -1) confirmRows187(ev);\n    }, true);\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ bind(); setTimeout(bind,250); setTimeout(bind,1000); });\n    else { bind(); setTimeout(bind,250); setTimeout(bind,1000); }\n    try { new MutationObserver(bind).observe(document.documentElement, {childList:true, subtree:true}); } catch(ignore) {}\n})();\n<\/script>\n\n\n<script id=\"ti-purchase-confirm-fix-188\">\n(function(){\n    \"use strict\";\n    if (window.__tiPurchaseConfirmFix188) return;\n    window.__tiPurchaseConfirmFix188 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function alertUser(msg){ try { if (window.tiAlert) window.tiAlert(msg); else window.alert(msg); } catch(ignore) {} }\n    function num(v){\n        if (v == null) return 0;\n        var s = String(v).trim().replace(\/[\u20ac\\s]\/g,'');\n        if (!s) return 0;\n        if (s.indexOf(',') !== -1 && s.indexOf('.') !== -1) s = s.replace(\/\\.\/g,'').replace(',', '.');\n        else if (s.indexOf(',') !== -1) s = s.replace(',', '.');\n        var n = parseFloat(s.replace(\/[^0-9.\\-]\/g,''));\n        return isNaN(n) ? 0 : n;\n    }\n    function b64(str){ try { return btoa(unescape(encodeURIComponent(str))); } catch(e) { return btoa(str); } }\n    function selectedDb(){ var el = byId('ti-ditta'); return el ? String(el.value || '') : ''; }\n    function rowItem(tr){\n        var q = function(sel){ return tr && tr.querySelector ? tr.querySelector(sel) : null; };\n        return {\n            table: tr && tr.dataset ? String(tr.dataset.catalogTable || '') : '',\n            index: tr && tr.dataset ? parseInt(tr.dataset.catalogIndex || '-1', 10) : -1,\n            type: String((q('.ti-pur-type') || {}).value || 'P'),\n            name: String((q('.ti-pur-name') || {}).value || '').trim(),\n            qty: String((q('.ti-pur-qty') || {}).value || '1'),\n            cost: String((q('.ti-pur-cost') || {}).value || ''),\n            margin: String((q('.ti-pur-margin') || {}).value || ''),\n            sale: String((q('.ti-pur-sale') || {}).value || ''),\n            iva: String((q('.ti-pur-iva') || {}).value || '22'),\n            note: String((q('.ti-pur-note') || {}).value || '')\n        };\n    }\n    function collectRows(){\n        var rows = [];\n        document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr){\n            var r = rowItem(tr);\n            if (r.name) rows.push(r);\n        });\n        return rows;\n    }\n    function validateRows(){\n        var errors = [];\n        document.querySelectorAll('#ti-purchase-body tr').forEach(function(tr, i){\n            tr.classList.remove('is-invalid');\n            var name = String(((tr.querySelector('.ti-pur-name') || {}).value) || '').trim();\n            var qty = num(((tr.querySelector('.ti-pur-qty') || {}).value) || 0);\n            var ivaEl = tr.querySelector('.ti-pur-iva');\n            if (ivaEl && !String(ivaEl.value || '').trim()) ivaEl.value = '22';\n            if (!name || qty <= 0) tr.classList.add('is-invalid');\n            if (!name) errors.push('Riga ' + (i + 1) + ': prodotto\/servizio mancante');\n            if (qty <= 0) errors.push('Riga ' + (i + 1) + ': quantit\u00e0 non valida');\n        });\n        return errors;\n    }\n    function buildForm(actionName, extra){\n        var fd = new FormData();\n        fd.append('action', actionName);\n        fd.append('ti_action', actionName);\n        Object.keys(extra || {}).forEach(function(k){ if (extra[k] !== undefined && extra[k] !== null) fd.append(k, extra[k]); });\n        return fd;\n    }\n    function postAction(actionName, extra){\n        var urls = [];\n        if (window.tiUrl) urls.push(window.tiUrl);\n        var pageUrl = window.location.href.split('#')[0].split('?')[0];\n        if (pageUrl && urls.indexOf(pageUrl) === -1) urls.push(pageUrl);\n        if (window.tiAjaxUrl && urls.indexOf(window.tiAjaxUrl) === -1) urls.push(window.tiAjaxUrl);\n        var lastErr = null;\n        function tryAt(i){\n            if (i >= urls.length) return Promise.reject(lastErr || new Error('Endpoint acquisti non disponibile.'));\n            var fd = buildForm(actionName, extra);\n            return fetch(urls[i], {method:'POST', body:fd, credentials:'same-origin', cache:'no-store', headers:{'X-Requested-With':'XMLHttpRequest'}}).then(function(resp){\n                return resp.text().then(function(raw){\n                    var txt = String(raw || '').trim();\n                    if (!resp.ok) throw new Error('HTTP ' + resp.status + ': ' + txt.slice(0, 180));\n                    if (txt === '0' || txt === '-1') throw new Error('Endpoint non autorizzato su ' + urls[i]);\n                    try { return JSON.parse(txt); }\n                    catch(e) { throw new Error('Risposta non JSON da ' + urls[i] + ': ' + txt.slice(0, 220)); }\n                });\n            }).catch(function(err){ lastErr = err; return tryAt(i + 1); });\n        }\n        return tryAt(0);\n    }\n    function closePurchase(){\n        try { if (typeof window.closePurchaseManualFlow === 'function') window.closePurchaseManualFlow(); } catch(ignore) {}\n        var ov = byId('ti-purchase-ov');\n        if (ov) { ov.style.setProperty('display','none','important'); ov.style.setProperty('pointer-events','none','important'); ov.classList.remove('ti-purchase-rescue-open','ti-modal-open','open','show','active'); }\n        ['ti-purchase-row-pop-184','ti-purchase-row-search-popover'].forEach(function(id){ var p = byId(id); if (p && p.parentNode) p.parentNode.removeChild(p); });\n    }\n    function confirmRows188(ev){\n        if (ev) { ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); }\n        var btn = byId('ti-purchase-confirm-btn');\n        var errors = validateRows();\n        if (errors.length) { alertUser('Correggi le righe acquisto:\\n' + errors.slice(0, 8).join('\\n')); return false; }\n        var rows = collectRows();\n        if (!rows.length) { alertUser('Inserisci almeno una riga acquisto.'); return false; }\n        var db = selectedDb();\n        if (!db || db === 'NEW_DB') { alertUser('Seleziona prima una ditta.'); return false; }\n        if (window.__tiPurchaseSaving188) return false;\n        window.__tiPurchaseSaving188 = true;\n        if (btn) { btn.disabled = true; btn.dataset.oldText188 = btn.innerHTML; btn.innerHTML = 'Salvataggio...'; }\n        postAction('ti_ai_confirm_purchase_rows', {db:db, rows:b64(JSON.stringify(rows))}).then(function(res){\n            if (!res || !res.success) throw new Error((res && res.data && res.data.message) || 'Salvataggio righe acquisto non riuscito.');\n            return postAction('ti_ai_get_db_data', {db:db}).catch(function(){ return null; }).then(function(dbRes){\n                if (dbRes && dbRes.success && dbRes.data) window.currentDbData = dbRes.data;\n                closePurchase();\n                alertUser('\u2705 Righe acquisto confermate e salvate nel database.');\n            });\n        }).catch(function(err){\n            alertUser('Errore conferma acquisto: ' + ((err && err.message) || err));\n        }).finally(function(){\n            window.__tiPurchaseSaving188 = false;\n            if (btn) { btn.disabled = false; btn.innerHTML = btn.dataset.oldText188 || 'Conferma righe acquisto'; }\n        });\n        return false;\n    }\n\n    window.confirmPurchaseRows = confirmRows188;\n    window.purchaseSendToChat = confirmRows188;\n\n    function bind(){\n        var btn = byId('ti-purchase-confirm-btn');\n        if (btn) {\n            btn.onclick = confirmRows188;\n            btn.dataset.tiConfirmFix188 = '1';\n        }\n    }\n    function capture(ev){\n        var t = ev.target;\n        if (!t || !t.closest) return;\n        var btn = t.closest('#ti-purchase-confirm-btn,button');\n        if (!btn) return;\n        var label = String(btn.textContent || '').toLowerCase();\n        if (btn.id === 'ti-purchase-confirm-btn' || label.indexOf('conferma righe acquisto') !== -1) confirmRows188(ev);\n    }\n    window.addEventListener('click', capture, true);\n    window.addEventListener('touchend', capture, {passive:false, capture:true});\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ bind(); setTimeout(bind,250); setTimeout(bind,1000); });\n    else { bind(); setTimeout(bind,250); setTimeout(bind,1000); }\n    try { new MutationObserver(bind).observe(document.documentElement, {childList:true, subtree:true}); } catch(ignore) {}\n})();\n<\/script>\n\n\n<script id=\"ti-help-company-main-193\">\n(function(){\n    if (window.__tiHelpCompanyMain193) return;\n    window.__tiHelpCompanyMain193 = true;\n    function byId(id){ return document.getElementById(id); }\n    function esc(v){\n        if (window.tiCompanyHelpEscape) return window.tiCompanyHelpEscape(v);\n        return String(v == null ? '' : v).replace(\/[&<>\"']\/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c]; });\n    }\n    function norm(v){\n        return String(v == null ? '' : v).toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g,'').replace(\/[_\\-]+\/g,' ').replace(\/\\s+\/g,' ').trim();\n    }\n    function getTables(data){ return data && data.Tabelle && typeof data.Tabelle === 'object' ? data.Tabelle : {}; }\n    function getRows(data, names){\n        var tables = getTables(data);\n        var keys = Object.keys(tables || {});\n        for (var i=0; i<names.length; i++) {\n            var wanted = norm(names[i]);\n            var key = keys.find(function(k){ return norm(k) === wanted; });\n            if (key) return Array.isArray(tables[key]) ? tables[key] : Object.values(tables[key] || {});\n        }\n        return [];\n    }\n    function pick(row, names){\n        if (window.tiPickCompanyVal) return window.tiPickCompanyVal(row || {}, names || []);\n        row = row || {};\n        var keys = Object.keys(row);\n        for (var i=0; i<names.length; i++) {\n            var nm = names[i];\n            if (Object.prototype.hasOwnProperty.call(row, nm) && String(row[nm] || '').trim() !== '') return String(row[nm]).trim();\n            var wanted = norm(nm);\n            var found = keys.find(function(k){ return norm(k) === wanted; });\n            if (found && String(row[found] || '').trim() !== '') return String(row[found]).trim();\n        }\n        return '';\n    }\n    function currentDb(){ return (byId('ti-ditta') && byId('ti-ditta').value) ? byId('ti-ditta').value : ''; }\n    function firstUrl(txt){\n        var m = String(txt || '').match(\/https?:\\\/\\\/[^\\s<>'\")]+\/i);\n        return m ? m[0].replace(\/[\\],.;]+$\/,'') : '';\n    }\n    function encodePath(path){ return String(path || '').split('\/').map(function(x){ return encodeURIComponent(x); }).join('\/'); }\n    function basenamePath(v){\n        var s = String(v || '').trim().replace(\/\\\\\/g,'\/');\n        s = s.split(\/[?#]\/)[0];\n        var p = s.split('\/');\n        return (p[p.length - 1] || '').trim();\n    }\n    function slugRecordLabel(row){\n        row = row || {};\n        var names = ['Descrizione','Titolo','Nome','Informazione','Documento','Doc','URL','Note'];\n        var keys = Object.keys(row || {});\n        var val = '';\n        for (var i=0; i<names.length && !val; i++) {\n            var wanted = norm(names[i]);\n            var hit = keys.find(function(k){ return norm(k) === wanted; });\n            if (hit && String(row[hit] || '').trim()) val = String(row[hit]).trim();\n        }\n        val = val || 'informazioni';\n        return val.toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g,'').replace(\/\\.[a-z0-9]{2,5}$\/i,'').replace(\/[^a-z0-9]+\/g,'-').replace(\/^-+|-+$\/g,'') || 'informazioni';\n    }\n    function docFileNameFromText(txt){\n        var s = String(txt || '');\n        try { s = decodeURIComponent(s); } catch(e) {}\n        var m = s.match(\/([A-Za-z0-9][A-Za-z0-9._ -]*\\.(?:pdf|docx?|xlsx?|txt|csv|html?|jpg|jpeg|png|webp))\/i);\n        if (m) return basenamePath(m[1]);\n        return '';\n    }\n    function isSharedDocsUrl(v){\n        var s = String(v || '');\n        try { s = decodeURIComponent(s); } catch(e) {}\n        return \/AI-data\\\/AI-docs\\\/Shop-service\/i.test(s) || \/AI-docs\\\/Shop-service\/i.test(s);\n    }\n    function sharedDocsManualUrl(){\n        var setup = window.tiSharedSetup || {};\n        var u = String(setup.manual_url || '').trim();\n        if (u) return u;\n        var base = String(setup.docs_base_url || 'https:\/\/www.targetinformatica.it\/wp-content\/uploads\/AI-data\/AI-docs\/Shop-service').replace(\/\\\/+$\/, '');\n        return base + '\/Shop-Service%20Manual.pdf';\n    }\n    function buildLocalDocRoute(fileName, row){\n        var file = basenamePath(fileName);\n        if (!file) return '';\n        var db = currentDb();\n        if (!db || db === 'NEW_DB') return '';\n        var base = String(window.tiUrl || window.location.href).split('#')[0];\n        var sep = base.indexOf('?') >= 0 ? '&' : '?';\n        return base + sep + 'ti_action=ti_ai_get_image&db=' + encodeURIComponent(db.replace(\/\\.json$\/i,'')) + '&tbl=Informazioni&file=' + encodeURIComponent(file) + '&record=' + encodeURIComponent(slugRecordLabel(row));\n    }\n    function docToUrl(val, row){\n        var v = String(val || '').trim();\n        if (!v) return '';\n        if (isSharedDocsUrl(v)) return v;\n        var direct = firstUrl(v);\n        if (direct) return direct;\n        var file = docFileNameFromText(v) || (\/\\.(pdf|docx?|xlsx?|txt|csv|html?|jpg|jpeg|png|webp)(\\?.*)?$\/i.test(v) ? basenamePath(v) : '');\n        if (file) return buildLocalDocRoute(file, row) || '';\n        if (\/^\\\/[^\/]\/.test(v)) return v;\n        if (\/^(wp-content\\\/uploads\\\/|uploads\\\/|AI-data\\\/)\/i.test(v)) {\n            var f = basenamePath(v);\n            return f ? buildLocalDocRoute(f, row) : ('\/' + v.replace(\/^\\\/+\/, ''));\n        }\n        return '';\n    }\n    function infoLinksHtml(data){\n        var rows = getRows(data, ['Informazioni','Informazioni ditta']);\n        var wanted = [\n            {key:'commerciali', label:'Condizioni commerciali', re:\/commercial|vendita|pagament|offert|listin|prezz|recess|reso|spedizion|fattur|ordine\/i},\n            {key:'contrattuali', label:'Condizioni contrattuali', re:\/contratt|termini|accord|fornitura|garanzi|clausol|condizion\/i},\n            {key:'privacy', label:'Privacy', re:\/privacy|gdpr|dati personal|informativa|cookie|trattamento dati\/i}\n        ];\n        var found = {};\n        rows.forEach(function(row){\n            if (!row || typeof row !== 'object') return;\n            var flat = Object.keys(row).map(function(k){ return k + ' ' + String(row[k] == null ? '' : row[k]); }).join(' ');\n            var tipo = String(pick(row, ['Tipo','type']) || '').toUpperCase();\n            var ref = pick(row, ['URL','Url','Link','Doc','Docs','File','Files','PDF','Pdf','Documento','Documenti','Allegato','Allegati','File associato','File associati','Nome file','Path','Percorso','Descrizione','Istruzioni','Note']);\n            var url = docToUrl(ref, row) || docToUrl(flat, row);\n            wanted.forEach(function(w){\n                if (found[w.key]) return;\n                if (w.re.test(flat) && (\/^(DOC|URL|WEB|RIC)?$\/i.test(tipo) || !tipo)) {\n                    if (url) found[w.key] = '<a href=\"' + esc(url) + '\" target=\"_blank\" rel=\"noopener\" style=\"color:#93c5fd;text-decoration:underline;\">' + esc(w.label) + '<\/a>';\n                    else found[w.key] = '<span style=\"color:#cbd5e1;\">' + esc(w.label) + '<\/span>';\n                }\n            });\n        });\n        var links = wanted.map(function(w){ return found[w.key]; }).filter(Boolean);\n        return links.length ? '<div style=\"margin-top:4px;\">' + links.join(' &nbsp;|&nbsp; ') + '<\/div>' : '';\n    }\n    function render(data){\n        var box = byId('ti-help-company');\n        if (!box) return;\n        data = data || window.currentDbData || window.currentDbViewData || null;\n        var cfgRows = getRows(data, ['Config','Configurazione']);\n        var cfg = cfgRows && cfgRows[0] && typeof cfgRows[0] === 'object' ? cfgRows[0] : {};\n        var lines = [];\n        var rag = pick(cfg, ['Ragione sociale','Ragione Sociale','Ditta','Nome ditta','Azienda']);\n        var indirizzo = pick(cfg, ['Indirizzo','Sede','Indirizzo ditta','Indirizzo sede']);\n        var piva = pick(cfg, ['PIVA_VAT','PIVA VAT','Partita IVA','Partita iva','P. IVA','PIva','PIVA','VAT']);\n        var email = pick(cfg, ['Email principale','Email ditta','Email','E-mail','Mail']);\n        var tel = pick(cfg, ['Tel','Telefono','Telefono ditta','Cellulare','Phone']);\n        if (rag) lines.push('<div><b>' + esc(rag) + '<\/b><\/div>');\n        if (indirizzo) lines.push('<div>' + esc(indirizzo) + '<\/div>');\n        if (piva) lines.push('<div>Partita IVA: ' + esc(piva) + '<\/div>');\n        if (email) lines.push('<div>Email: <a href=\"mailto:' + esc(email) + '\" style=\"color:#93c5fd;\">' + esc(email) + '<\/a><\/div>');\n        if (tel) lines.push('<div>Telefono: <a href=\"tel:' + esc(tel) + '\" style=\"color:#93c5fd;\">' + esc(tel) + '<\/a><\/div>');\n        var links = infoLinksHtml(data);\n        if (links) lines.push(links);\n        box.innerHTML = lines.join('');\n        box.style.display = lines.length ? 'block' : 'none';\n        try { rewriteLocalDocAnchors(document.getElementById('ti-help-ov') || document); } catch(e) {}\n    }\n    function loadDataAndRender(){\n        render(window.currentDbData || window.currentDbViewData || null);\n        var db = currentDb();\n        if (!db || db === 'NEW_DB') return Promise.resolve(null);\n        if (window.currentDbData && window.currentDbData.Tabelle) return Promise.resolve(window.currentDbData);\n        if (!window.fetchJsonSafe) return Promise.resolve(null);\n        var fd = new FormData();\n        fd.append('ti_action','ti_ai_get_db_data');\n        fd.append('db', db);\n        return window.fetchJsonSafe((window.tiAjaxUrl || window.tiUrl || window.location.href), {method:'POST', body:fd, credentials:'same-origin'}).then(function(res){\n            if (res && res.success && res.data) { window.currentDbData = res.data; render(res.data); }\n            return res && res.data ? res.data : null;\n        }).catch(function(){ return null; });\n    }\n    function rewriteLocalDocAnchors(root){\n        root = root || document;\n        if (!root.querySelectorAll) return;\n        root.querySelectorAll('a[href]').forEach(function(a){\n            var raw = a.getAttribute('href') || '';\n            if (a.id === 'btn-manual') { a.setAttribute('href', sharedDocsManualUrl()); return; }\n            if (!raw || isSharedDocsUrl(raw) || \/ti_action=ti_ai_get_image\/i.test(raw) || \/^mailto:|^tel:|^javascript:\/i.test(raw)) return;\n            var file = docFileNameFromText(raw);\n            if (!file) return;\n            var abs = a.href || '';\n            var isBare = raw === file || raw.indexOf('\/') === -1;\n            var isSamePageRelative = abs && abs.indexOf(window.location.origin) === 0 && abs.indexOf('\/wp-content\/uploads\/') === -1;\n            if (isBare || isSamePageRelative || \/AI-data|wp-content\\\/uploads\/i.test(raw)) {\n                var fixed = buildLocalDocRoute(file, null);\n                if (fixed) a.setAttribute('href', fixed);\n            }\n        });\n    }\n    window.tiUpdateMainHelpCompanyHeader = loadDataAndRender;\n    var prevOpen = window.openHelpModal;\n    window.openHelpModal = function(){\n        try { loadDataAndRender(); } catch(e) {}\n        if (typeof prevOpen === 'function') return prevOpen.apply(this, arguments);\n        if (window.openModal) window.openModal('ti-help-ov');\n    };\n    document.addEventListener('change', function(ev){ if (ev.target && ev.target.id === 'ti-ditta') setTimeout(loadDataAndRender, 80); }, true);\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ render(); setTimeout(loadDataAndRender, 300); try { rewriteLocalDocAnchors(document); } catch(e) {} });\n    else { render(); setTimeout(loadDataAndRender, 300); try { rewriteLocalDocAnchors(document); } catch(e) {} }\n    try { new MutationObserver(function(muts){ muts.forEach(function(m){ (m.addedNodes || []).forEach(function(n){ if (n && n.nodeType === 1) rewriteLocalDocAnchors(n); }); }); }).observe(document.documentElement, {childList:true, subtree:true}); } catch(e) {}\n})();\n<\/script>\n\n\n\n<script id=\"ti-activity-qrcode-direct-299\">\n(function(){\n    \"use strict\";\n    if (window.__tiActivityQrDirect299) return;\n    window.__tiActivityQrDirect299 = true;\n    function tryShow(){ try { if (window.showActivityFromQrIfRequested) window.showActivityFromQrIfRequested(); } catch(e) {} }\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ setTimeout(tryShow, 600); setTimeout(tryShow, 1600); });\n    else { setTimeout(tryShow, 600); setTimeout(tryShow, 1600); }\n    document.addEventListener('change', function(ev){ if (ev && ev.target && ev.target.id === 'ti-ditta') setTimeout(tryShow, 700); }, true);\n})();\n<\/script>\n\n\n<script id=\"ti-config-open-rescue-218\">\n(function(){\n    \"use strict\";\n    if (window.__tiConfigOpenRescue218) return;\n    window.__tiConfigOpenRescue218 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function esc(v){\n        return String(v == null ? '' : v).replace(\/[&<>\"']\/g, function(c){\n            return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#039;'}[c];\n        });\n    }\n    function selectedDb(){\n        var sel = byId('ti-ditta');\n        return sel ? String(sel.value || '').trim() : '';\n    }\n    function openConfigOverlay(){\n        var ov = byId('ti-conf-ov');\n        try {\n            if (window.showPluginModal) window.showPluginModal(ov || 'ti-conf-ov');\n            else if (window.openModal) window.openModal('ti-conf-ov');\n            else if (ov) { ov.style.display = 'flex'; ov.style.position = 'fixed'; ov.style.inset = '0'; ov.style.zIndex = '2147483000'; }\n        } catch(e) {\n            if (ov) { ov.style.display = 'flex'; ov.style.position = 'fixed'; ov.style.inset = '0'; ov.style.zIndex = '2147483000'; }\n        }\n    }\n    function setConfigMessage(html, type){\n        var cnt = byId('ti-conf-cnt');\n        if (!cnt) return;\n        var border = type === 'error' ? '#ef4444' : '#22c55e';\n        var bg = type === 'error' ? '#450a0a' : '#052e16';\n        var color = type === 'error' ? '#fecaca' : '#bbf7d0';\n        cnt.innerHTML = '<div style=\"margin:14px;padding:14px;border-radius:12px;border:1px solid ' + border + ';background:' + bg + ';color:' + color + ';font-size:14px;line-height:1.45;\">' + html + '<\/div>';\n    }\n    function buildFd(action, extra){\n        var fd;\n        if (typeof window.buildAjaxFormData === 'function') fd = window.buildAjaxFormData(action, extra || {});\n        else {\n            fd = new FormData();\n            fd.append('action', action);\n            fd.append('ti_action', action);\n            Object.keys(extra || {}).forEach(function(k){ if (extra[k] !== undefined && extra[k] !== null) fd.append(k, extra[k]); });\n        }\n        if (fd instanceof FormData) {\n            if (!fd.has('action')) fd.append('action', action);\n            if (!fd.has('ti_action')) fd.append('ti_action', action);\n        }\n        return fd;\n    }\n    function postJson(fd, db){\n        var url = window.tiAjaxUrl || window.tiUrl || window.ajaxurl || window.location.href;\n        var opts = {\n            method: 'POST',\n            body: (window.ensureAjaxActionField ? window.ensureAjaxActionField(fd) : fd),\n            credentials: 'same-origin',\n            tiNoLongProcessSignal: true,\n            tiShowTableReadWait: true,\n            tiTableReadTitle: '\u23f3 Lettura tabelle configurazione',\n            tiTableReadDetail: 'Sto leggendo tutte le tabelle della ditta prima di aprire la configurazione.',\n            tiTableReadItem: db || ''\n        };\n        if (typeof window.postFormDataJsonSafe === 'function') return window.postFormDataJsonSafe(url, fd, opts);\n        if (typeof window.fetchJsonSafe === 'function') return window.fetchJsonSafe(url, opts);\n        return fetch(url, opts).then(function(r){ return r.text().then(function(t){ if(!r.ok) throw new Error('HTTP ' + r.status + ': ' + t.slice(0,180)); try { return JSON.parse(t); } catch(e) { throw new Error('Risposta server non valida: ' + t.slice(0,180)); } }); });\n    }\n\n    window.openConfigSafe218 = function(ev){\n        if (ev && ev.preventDefault) ev.preventDefault();\n        var db = selectedDb();\n        if (!db || db === 'NEW_DB') {\n            var msg = 'Seleziona prima una ditta valida per aprire la configurazione.';\n            if (typeof window.tiAlert === 'function') window.tiAlert(msg); else alert(msg);\n            return Promise.resolve(false);\n        }\n        if (!window.__tiConfigOpenProtectionBypass392 && typeof window.requestMaintenanceStatus === 'function') {\n            var reopenAfterProtection = function(){\n                window.__tiConfigOpenProtectionBypass392 = true;\n                var p;\n                try { p = Promise.resolve(window.openConfigSafe218(ev)); }\n                catch(ex) { window.__tiConfigOpenProtectionBypass392 = false; throw ex; }\n                return p.then(function(v){ window.__tiConfigOpenProtectionBypass392 = false; return v; }, function(e){ window.__tiConfigOpenProtectionBypass392 = false; throw e; });\n            };\n            return window.requestMaintenanceStatus({db: db}).then(function(ms){\n                var data = (ms && ms.data) ? ms.data : {};\n                if (data && data.database_protected && typeof window.showConfigProtectedPopup === 'function') {\n                    return new Promise(function(resolve){\n                        window.showConfigProtectedPopup(data, function(){ reopenAfterProtection().then(resolve, resolve); }, function(){ reopenAfterProtection().then(resolve, resolve); });\n                    });\n                }\n                return reopenAfterProtection();\n            }).catch(function(){ return reopenAfterProtection(); });\n        }\n        openConfigOverlay();\n        setConfigMessage('<b>Caricamento configurazione in corso...<\/b>', 'ok');\n        var fd = buildFd('ti_ai_get_db_data', {db: db});\n        return postJson(fd, db).then(function(res){\n            if (!res || !res.success || !res.data) {\n                var serverMsg = res && res.data && res.data.message ? res.data.message : (res && res.message ? res.message : 'lettura database non riuscita');\n                throw new Error(serverMsg);\n            }\n            window.currentDbData = res.data;\n            try { window.configOriginalDbData = JSON.parse(JSON.stringify(res.data || {})); } catch(e) { window.configOriginalDbData = res.data || {}; }\n            window.configWasSaved = false;\n            window.tiConfigDeletedRowsManifest = [];\n            if (window.modifiedFields && typeof window.modifiedFields.clear === 'function') window.modifiedFields.clear();\n            if (typeof window.renderConfig !== 'function') throw new Error('Funzione renderConfig non disponibile.');\n            try {\n                window.renderConfig();\n            } catch(renderErr) {\n                setConfigMessage('<b>Errore apertura form configurazione.<\/b><br>' + esc(renderErr && renderErr.message ? renderErr.message : renderErr), 'error');\n                throw renderErr;\n            }\n            openConfigOverlay();\n            setTimeout(function(){\n                var cnt = byId('ti-conf-cnt');\n                if (cnt && !String(cnt.innerHTML || '').trim()) {\n                    setConfigMessage('<b>Form configurazione aperto, ma nessuna tabella renderizzata.<\/b><br>Verifica che il database contenga la sezione Tabelle.', 'error');\n                }\n                if (typeof window.updateConfigSaveButtonState === 'function') window.updateConfigSaveButtonState();\n            }, 80);\n            return true;\n        }).catch(function(err){\n            openConfigOverlay();\n            var m = err && err.message ? err.message : String(err || 'errore sconosciuto');\n            setConfigMessage('<b>Configurazione non caricata.<\/b><br>' + esc(m) + '<br><br>La finestra resta aperta per mostrare il problema invece di chiudersi senza messaggi.', 'error');\n            try { console.error('Shop & Service CONFIGURA open error', err); } catch(e) {}\n            return false;\n        });\n    };\n\n    function bindConfigButton(){\n        var btn = byId('ti-conf-toggle');\n        if (!btn) return;\n        btn.onclick = window.openConfigSafe218;\n        btn.setAttribute('data-ti-config-open-rescue', '218');\n        btn.style.display = 'inline-block';\n    }\n\n    document.addEventListener('click', function(ev){\n        var target = ev.target;\n        if (!target || !target.closest) return;\n        var btn = target.closest('#ti-conf-toggle');\n        if (!btn) return;\n        ev.preventDefault();\n        if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();\n        else ev.stopPropagation();\n        window.openConfigSafe218(ev);\n    }, true);\n\n    var prevSetAdminUI = window.setAdminUI;\n    if (typeof prevSetAdminUI === 'function' && !prevSetAdminUI.__tiConfigOpenRescue218) {\n        window.setAdminUI = function(){\n            var ret = prevSetAdminUI.apply(this, arguments);\n            try { bindConfigButton(); } catch(e) {}\n            return ret;\n        };\n        window.setAdminUI.__tiConfigOpenRescue218 = true;\n    }\n\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bindConfigButton);\n    else bindConfigButton();\n    var tries = 0;\n    var timer = setInterval(function(){\n        tries += 1;\n        bindConfigButton();\n        if (tries > 20 || byId('ti-conf-toggle')) clearInterval(timer);\n    }, 500);\n})();\n<\/script>\n\n\n\n<script id=\"ti-login-memory-fix-312\">\n(function(){\n    \"use strict\";\n    if (window.__tiLoginMemoryFix312) return;\n    window.__tiLoginMemoryFix312 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function safeGet(storage, key){ try { return storage.getItem(key) || ''; } catch(e) { return ''; } }\n    function safeSet(storage, key, val){ try { storage.setItem(key, String(val || '')); } catch(e) {} }\n    function safeRemove(storage, key){ try { storage.removeItem(key); } catch(e) {} }\n    function hasOption(sel, value){\n        if (!sel || !value) return false;\n        for (var i = 0; i < sel.options.length; i++) if (sel.options[i].value === value) return true;\n        return false;\n    }\n    function isBadCredentialClearActive(){\n        return safeGet(sessionStorage, 'ti_login_failed_clear') === '1';\n    }\n    function clearStableUsername(userEl){\n        try { if (userEl && userEl.dataset) delete userEl.dataset.tiStableUsername; } catch(e) {}\n    }\n\n    window.tiRememberSuccessfulLogin = function(user, db){\n        try {\n            var u = String(user || '').trim();\n            var d = String(db || '').trim();\n            if (u) safeSet(localStorage, 'ti_last_login_user', u);\n            if (d) {\n                safeSet(localStorage, 'ti_last_login_db', d);\n                safeSet(localStorage, 'ti_saved_db', d);\n            }\n            safeRemove(sessionStorage, 'ti_login_failed_clear');\n        } catch(e) {}\n    };\n\n    window.tiClearLoginFieldsAfterFailure = function(clearUserToo){\n        try {\n            \/\/ 30.9.404: non svuotare piu i campi login dopo errore.\n            \/\/ In manutenzione e con password manager, lo svuotamento ripetuto rendeva il campo password non scrivibile.\n            safeRemove(sessionStorage, 'ti_login_failed_clear');\n            var target = clearUserToo ? (byId('l-user') || byId('ti-direct-login-user')) : (byId('l-pass') || byId('ti-direct-login-pass'));\n            if (target && typeof target.focus === 'function') {\n                setTimeout(function(){ try { target.focus({preventScroll:true}); } catch(e) { try { target.focus(); } catch(ignore) {} } }, 30);\n            }\n        } catch(e) {}\n    };\n\n    window.tiApplyLastSuccessfulLoginToForm = function(force){\n        try {\n            var userEl = byId('l-user');\n            var passEl = byId('l-pass');\n            var dbEl = byId('l-db');\n            var hiddenDb = byId('l-db-hidden');\n            if (isBadCredentialClearActive() && !force) {\n                \/\/ 30.9.404: preserva username\/password digitati o riempiti dal browser.\n                safeRemove(sessionStorage, 'ti_login_failed_clear');\n            }\n            var lastUser = safeGet(localStorage, 'ti_last_login_user');\n            var lastDb = safeGet(localStorage, 'ti_last_login_db') || safeGet(localStorage, 'ti_saved_db');\n            if (userEl && lastUser && (!userEl.value || force)) {\n                userEl.value = lastUser;\n                try { userEl.dataset.tiStableUsername = lastUser; } catch(e) {}\n            }\n            if (dbEl && lastDb && hasOption(dbEl, lastDb) && (!dbEl.value || force)) {\n                dbEl.value = lastDb;\n                if (hiddenDb) hiddenDb.value = lastDb;\n                try { dbEl.dispatchEvent(new Event('change', {bubbles:true})); } catch(e) {}\n            } else if (hiddenDb && lastDb && (!hiddenDb.value || force)) {\n                hiddenDb.value = lastDb;\n            }\n            if (passEl && force && document.activeElement !== passEl) passEl.value = '';\n        } catch(e) {}\n    };\n\n    function bindManualTypingReset(){\n        try {\n            ['l-user','l-pass','ti-direct-login-user','ti-direct-login-pass'].forEach(function(id){\n                var el = byId(id);\n                if (!el || el.dataset.tiLoginMemoryResetBound312) return;\n                el.dataset.tiLoginMemoryResetBound312 = '1';\n                el.addEventListener('input', function(){ safeRemove(sessionStorage, 'ti_login_failed_clear'); }, false);\n            });\n        } catch(e) {}\n    }\n\n    function patchOpenLogin(){\n        try {\n            if (typeof window.openLoginModalSafe === 'function' && !window.openLoginModalSafe.__tiLoginMemory312) {\n                var prevOpen = window.openLoginModalSafe;\n                window.openLoginModalSafe = function(){\n                    var ret = prevOpen.apply(this, arguments);\n                    setTimeout(function(){ try { window.tiApplyLastSuccessfulLoginToForm(false); bindManualTypingReset(); } catch(e) {} }, 90);\n                    setTimeout(function(){ try { window.tiApplyLastSuccessfulLoginToForm(false); bindManualTypingReset(); } catch(e) {} }, 260);\n                    return ret;\n                };\n                window.openLoginModalSafe.__tiLoginMemory312 = true;\n            }\n            if (typeof window.syncLoginDbSelect === 'function' && !window.syncLoginDbSelect.__tiLoginMemory312) {\n                var prevSync = window.syncLoginDbSelect;\n                window.syncLoginDbSelect = function(){\n                    var ret = prevSync.apply(this, arguments);\n                    try { window.tiApplyLastSuccessfulLoginToForm(false); } catch(e) {}\n                    return ret;\n                };\n                window.syncLoginDbSelect.__tiLoginMemory312 = true;\n            }\n        } catch(e) {}\n    }\n\n    function boot(){\n        try {\n            if (String(window.tiUser || '').trim() && window.tiRememberSuccessfulLogin) {\n                window.tiRememberSuccessfulLogin(window.tiUser, window.tiSessionDb || window.tiForced || '');\n            }\n        } catch(e) {}\n        bindManualTypingReset();\n        patchOpenLogin();\n        try { window.tiApplyLastSuccessfulLoginToForm(false); } catch(e) {}\n    }\n\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ boot(); setTimeout(boot, 300); setTimeout(boot, 1200); });\n    else { boot(); setTimeout(boot, 300); setTimeout(boot, 1200); }\n    try { new MutationObserver(function(){ bindManualTypingReset(); patchOpenLogin(); }).observe(document.documentElement, {childList:true, subtree:true}); } catch(e) {}\n})();\n<\/script>\n\n\n<script id=\"ti-plugin-version-access-memory-314\">\n(function(){\n    \"use strict\";\n    if (window.__tiPluginVersionAccessMemory314) return;\n    window.__tiPluginVersionAccessMemory314 = true;\n\n    function safeGet(storage, key){ try { return storage.getItem(key) || ''; } catch(e) { return ''; } }\n    function safeSet(storage, key, val){ try { storage.setItem(key, String(val || '')); } catch(e) {} }\n    function esc(s){\n        return String(s == null ? '' : s).replace(\/[&<>\"']\/g, function(c){\n            return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c] || c;\n        });\n    }\n    function isPrivilegedVersionUser(){\n        var role = String(window.tiRole || '').toLowerCase();\n        var user = String(window.tiUser || '').toLowerCase();\n        return !!String(window.tiUser || '').trim() && (user === 'ssglobaladmin' || role.indexOf('configuratore') >= 0 || role.indexOf('configurator') >= 0 || role.indexOf('amministratore') >= 0 || role.indexOf('administrator') >= 0);\n    }\n    function selectedDbKey(){\n        var db = String(window.tiSessionDb || window.tiForced || window.tiCurrentSelectedDb || '').trim();\n        if (!db) { try { db = String((document.getElementById('ti-ditta') || {}).value || '').trim(); } catch(e) {} }\n        if (!db) { try { db = String(localStorage.getItem('ti_saved_db') || '').trim(); } catch(e) {} }\n        return db || 'no_db';\n    }\n    function storageKey(){\n        var user = String(window.tiUser || 'guest').trim() || 'guest';\n        var db = selectedDbKey();\n        return 'ti_plugin_last_version_access_' + location.host + '_' + db + '_' + user;\n    }\n    function readPrevious(key){\n        var raw = safeGet(localStorage, key);\n        if (!raw) return null;\n        try {\n            var obj = JSON.parse(raw);\n            if (obj && typeof obj === 'object') return obj;\n        } catch(e) {}\n        return {version: raw, datetime: '', ts: ''};\n    }\n    function currentPayload(){\n        return {\n            version: String(window.tiVersion || '').trim(),\n            datetime: String(window.tiVersionDateTime || '').trim(),\n            header: String(window.tiVersion || '').trim() + (window.tiVersionDateTime ? ' - ' + String(window.tiVersionDateTime).trim() : ''),\n            user: String(window.tiUser || '').trim(),\n            role: String(window.tiRole || '').trim(),\n            db: selectedDbKey(),\n            ts: new Date().toISOString()\n        };\n    }\n    window.tiCheckPluginVersionOnAccess = function(){\n        try {\n            if (!String(window.tiUser || '').trim()) return;\n            var key = storageKey();\n            var cur = currentPayload();\n            if (!cur.version) return;\n            var prev = readPrevious(key);\n            var changed = !!(prev && String(prev.version || '').trim() && String(prev.version || '').trim() !== cur.version);\n            safeSet(localStorage, key, JSON.stringify(cur));\n            safeSet(localStorage, 'ti_plugin_last_version_access_global_' + location.host, JSON.stringify(cur));\n            if (!changed || !isPrivilegedVersionUser()) return;\n            var prevHeader = String(prev.header || '').trim() || (String(prev.version || '').trim() + (prev.datetime ? ' - ' + String(prev.datetime).trim() : ''));\n            var html = '<div style=\"text-align:left;line-height:1.45;\">' +\n                '<b>Aggiornamento versione Shop &amp; Service rilevato<\/b><br><br>' +\n                'La versione del plugin e diversa da quella memorizzata al precedente accesso.<br><br>' +\n                '<b>Versione precedente:<\/b><br>' + esc(prevHeader || 'non disponibile') + '<br><br>' +\n                '<b>Versione attuale:<\/b><br>' + esc(cur.header || cur.version) + '<br><br>' +\n                '<span style=\"font-size:12px;color:#ccc;\">La nuova versione e stata ora memorizzata per i prossimi accessi.<\/span>' +\n                '<\/div>';\n            setTimeout(function(){\n                try {\n                    if (typeof window.tiAlert === 'function') window.tiAlert(html);\n                    else alert('Aggiornamento versione Shop & Service rilevato\\nVersione precedente: ' + (prevHeader || 'non disponibile') + '\\nVersione attuale: ' + (cur.header || cur.version));\n                } catch(e) {}\n            }, 900);\n        } catch(e) {}\n    };\n    function boot(){ try { window.tiCheckPluginVersionOnAccess(); } catch(e) {} }\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ setTimeout(boot, 250); setTimeout(boot, 1400); });\n    else { setTimeout(boot, 250); setTimeout(boot, 1400); }\n})();\n<\/script>\n\n\n<script id=\"ti-login-singleton-fix-315\">\n(function(){\n    \"use strict\";\n    if (window.__tiLoginSingletonFix315) return;\n    window.__tiLoginSingletonFix315 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function all(sel){ try { return Array.prototype.slice.call(document.querySelectorAll(sel)); } catch(e) { return []; } }\n    function isVisible(el){\n        if (!el) return false;\n        try {\n            var cs = window.getComputedStyle ? window.getComputedStyle(el) : null;\n            return el.style.display !== 'none' && (!cs || cs.display !== 'none') && el.getAttribute('aria-hidden') !== 'true';\n        } catch(e) { return el.style.display !== 'none'; }\n    }\n    function hideOverlay(el){\n        if (!el) return;\n        try {\n            el.classList.remove('ti-modal-open','ti-login-open-186','ti-login-open-315','open','active','show');\n            el.style.setProperty('display','none','important');\n            el.setAttribute('aria-hidden','true');\n        } catch(e) {}\n    }\n    function preferredLoginOverlay(){\n        var list = all('#ti-login-ov');\n        if (!list.length) return null;\n        var visible = list.filter(isVisible);\n        if (visible.length) return visible[0];\n        var inOuter = list.filter(function(el){ return !!(el.closest && el.closest('#ti-ai-outer')); });\n        return inOuter[0] || list[0];\n    }\n    function dedupeLoginDom(){\n        try {\n            var keep = preferredLoginOverlay();\n            all('#ti-login-ov').forEach(function(el){ if (el !== keep) hideOverlay(el); });\n            var keepForm = keep ? keep.querySelector('#ti-login-form') : null;\n            all('#ti-login-form').forEach(function(form){ if (form !== keepForm && form.closest && form.closest('#ti-login-ov')) hideOverlay(form.closest('#ti-login-ov')); });\n            var keepDirect = byId('ti-mobile-direct-login');\n            all('#ti-mobile-direct-login').forEach(function(el, idx){ if (idx > 0 || el !== keepDirect) { try { el.style.display='none'; el.setAttribute('aria-hidden','true'); } catch(e) {} } });\n            return keep;\n        } catch(e) { return byId('ti-login-ov'); }\n    }\n    function closeDirectFallbackUnlessManual(){\n        try {\n            var dl = byId('ti-mobile-direct-login');\n            if (!dl) return;\n            if (!dl.classList.contains('ti-direct-login-manual-open')) {\n                dl.classList.remove('ti-direct-login-visible');\n                dl.style.display = 'none';\n                dl.setAttribute('aria-hidden','true');\n            }\n        } catch(e) {}\n    }\n    function openLoginSingleton(ev){\n        if (ev) { ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); }\n        var now = Date.now();\n        if (window.__tiLoginOpenTs315 && now - window.__tiLoginOpenTs315 < 450) return false;\n        window.__tiLoginOpenTs315 = now;\n        var ov = dedupeLoginDom();\n        if (!ov) return false;\n        closeDirectFallbackUnlessManual();\n        try { if (window.syncLoginDbSelect) window.syncLoginDbSelect(); } catch(e) {}\n        try {\n            ov.style.setProperty('display','flex','important');\n            ov.style.setProperty('align-items','center','important');\n            ov.style.setProperty('justify-content','center','important');\n            ov.style.setProperty('pointer-events','auto','important');\n            ov.style.setProperty('z-index','2147483000','important');\n            ov.classList.add('ti-modal-open','ti-login-open-315');\n            ov.removeAttribute('aria-hidden');\n            var modal = ov.querySelector('.ti-modal');\n            if (modal) modal.style.setProperty('pointer-events','auto','important');\n            if (window.applyLayoutLanguage) window.applyLayoutLanguage(true);\n            if (window.tiApplyLastSuccessfulLoginToForm) window.tiApplyLastSuccessfulLoginToForm(false);\n            if (window.bindMobileLoginButton) window.bindMobileLoginButton();\n        } catch(e) {}\n        setTimeout(function(){\n            try {\n                dedupeLoginDom();\n                if (!(window.tiIsMobileLike && window.tiIsMobileLike())) {\n                    var u = byId('l-user'); if (u && typeof u.focus === 'function') u.focus({preventScroll:true});\n                }\n            } catch(e) {}\n        }, 80);\n        return false;\n    }\n    function closeLoginSingleton(ev){\n        if (ev) { ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); }\n        all('#ti-login-ov').forEach(hideOverlay);\n        try {\n            document.documentElement.classList.remove('ti-no-scroll');\n            document.body.classList.remove('ti-no-scroll');\n            if (window.updatePluginScrollLock) window.updatePluginScrollLock();\n        } catch(e) {}\n        return false;\n    }\n    function markManualDirectOpen(){\n        try {\n            var dl = byId('ti-mobile-direct-login');\n            if (dl) dl.classList.add('ti-direct-login-manual-open');\n        } catch(e) {}\n    }\n    function bindLoginSingleton(){\n        try {\n            dedupeLoginDom();\n            var openBtn = byId('ti-login-btn');\n            if (openBtn && !openBtn.dataset.tiLoginSingleton315) {\n                openBtn.dataset.tiLoginSingleton315 = '1';\n                openBtn.onclick = openLoginSingleton;\n                openBtn.addEventListener('click', openLoginSingleton, true);\n                openBtn.addEventListener('touchend', openLoginSingleton, {passive:false, capture:true});\n            }\n            all('#ti-login-ov .btn-close, #ti-login-ov button[onclick*=\"closeModal\"]').forEach(function(btn){\n                if (btn.dataset.tiLoginSingletonClose315) return;\n                btn.dataset.tiLoginSingletonClose315 = '1';\n                btn.onclick = closeLoginSingleton;\n                btn.addEventListener('click', closeLoginSingleton, true);\n            });\n            var manual = byId('ti-show-direct-login-fallback');\n            if (manual && !manual.dataset.tiDirectManual315) {\n                manual.dataset.tiDirectManual315 = '1';\n                manual.addEventListener('click', markManualDirectOpen, true);\n            }\n        } catch(e) {}\n    }\n\n    var previousOpenLogin = window.openLoginModalSafe;\n    window.openLoginModalSafe = function(){ return openLoginSingleton(); };\n    window.openLoginModalSafe.__tiLoginSingleton315 = true;\n\n    if (typeof window.openModal === 'function' && !window.openModal.__tiLoginSingleton315) {\n        var prevOpenModal = window.openModal;\n        window.openModal = function(id){\n            if (id === 'ti-login-ov') return openLoginSingleton();\n            return prevOpenModal.apply(this, arguments);\n        };\n        window.openModal.__tiLoginSingleton315 = true;\n    }\n    if (typeof window.closeModal === 'function' && !window.closeModal.__tiLoginSingleton315) {\n        var prevCloseModal = window.closeModal;\n        window.closeModal = function(id){\n            if (id === 'ti-login-ov') return closeLoginSingleton();\n            return prevCloseModal.apply(this, arguments);\n        };\n        window.closeModal.__tiLoginSingleton315 = true;\n    }\n\n    function suppressRepeatedLoginErrorOpen(){\n        try {\n            var params = new URLSearchParams(window.location.search || '');\n            if (!params.has('ti_login_error')) return;\n            var key = 'ti_login_error_open_seen_' + location.pathname + '_' + params.get('ti_login_error');\n            if (sessionStorage.getItem(key) === '1') return;\n            sessionStorage.setItem(key, '1');\n            setTimeout(function(){\n                try {\n                    var detail = params.get('ti_login_error') || 'login non riuscito';\n                    if (window.revealDirectLoginFallback) window.revealDirectLoginFallback('login-error-url', decodeURIComponent(detail));\n                    else openLoginSingleton();\n                } catch(e) { openLoginSingleton(); }\n            }, 260);\n        } catch(e) {}\n    }\n\n    function bindDelegatedLoginInterceptors(){\n        if (window.__tiLoginDelegatedInterceptors315) return;\n        window.__tiLoginDelegatedInterceptors315 = true;\n        document.addEventListener('click', function(ev){\n            var t = ev.target;\n            if (!t || !t.closest) return;\n            if (t.closest('#ti-login-btn')) { openLoginSingleton(ev); return; }\n            if (t.closest('#ti-login-ov .btn-close, #ti-login-ov button[onclick*=\\\"closeModal\\\"]')) { closeLoginSingleton(ev); return; }\n        }, true);\n        document.addEventListener('touchend', function(ev){\n            var t = ev.target;\n            if (!t || !t.closest) return;\n            if (t.closest('#ti-login-btn')) { openLoginSingleton(ev); return; }\n        }, {passive:false, capture:true});\n    }\n\n    function boot(){\n        bindDelegatedLoginInterceptors();\n        bindLoginSingleton();\n        dedupeLoginDom();\n        suppressRepeatedLoginErrorOpen();\n    }\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ boot(); setTimeout(boot, 400); setTimeout(boot, 1200); });\n    else { boot(); setTimeout(boot, 400); setTimeout(boot, 1200); }\n    try { new MutationObserver(function(){ bindLoginSingleton(); dedupeLoginDom(); }).observe(document.documentElement, {childList:true, subtree:true}); } catch(e) {}\n})();\n<\/script>\n\n\n<script id=\"ti-login-success-nonblocking-fix-318\">\n(function(){\n    \"use strict\";\n    if (window.__tiLoginSuccessNonBlocking318) return;\n    window.__tiLoginSuccessNonBlocking318 = true;\n\n    function hasLoginParam(){\n        try {\n            var p = new URLSearchParams(window.location.search || '');\n            return p.has('ti_login_error') || p.has('ti_login_ok') || p.has('ti_action') || p.has('action') || p.has('redirect') || p.has('ti_mobile_login_redirect');\n        } catch(e) { return false; }\n    }\n    function cleanLoginUrl(){\n        try {\n            var url = new URL(window.location.href);\n            ['ti_login_error','ti_login_ok','ti_action','action','redirect','ti_mobile_login_redirect','_wpnonce'].forEach(function(k){ url.searchParams.delete(k); });\n            var clean = url.pathname + (url.search ? url.search : '') + (url.hash || '');\n            if (clean !== (window.location.pathname + window.location.search + window.location.hash)) {\n                window.history.replaceState({}, document.title, clean);\n            }\n            return clean || window.location.pathname || '\/';\n        } catch(e) { return window.location.href.split('#')[0]; }\n    }\n    function hideLoginIfVisible(){\n        try {\n            document.querySelectorAll('#ti-login-ov').forEach(function(ov){\n                var cs = window.getComputedStyle ? window.getComputedStyle(ov) : null;\n                var visible = ov && ov.getAttribute('aria-hidden') !== 'true' && (!cs || cs.display !== 'none') && ov.style.display !== 'none';\n                if (!visible) return;\n                ov.classList.remove('ti-modal-open','ti-login-open-186','ti-login-open-315','open','active','show');\n                ov.style.setProperty('display','none','important');\n                ov.setAttribute('aria-hidden','true');\n            });\n            var dl = document.getElementById('ti-mobile-direct-login');\n            if (dl && !dl.classList.contains('ti-direct-login-manual-open')) {\n                dl.classList.remove('ti-direct-login-visible');\n                dl.style.display = 'none';\n                dl.setAttribute('aria-hidden','true');\n            }\n            if (window.updatePluginScrollLock) window.updatePluginScrollLock();\n        } catch(e) {}\n    }\n    function markSuccess(){\n        try {\n            sessionStorage.setItem('ti_login_success_no_autoopen_318', String(Date.now()));\n            sessionStorage.removeItem('ti_login_failed_clear');\n        } catch(e) {}\n    }\n    function recentSuccess(){\n        try {\n            var ts = parseInt(sessionStorage.getItem('ti_login_success_no_autoopen_318') || '0', 10);\n            return ts && (Date.now() - ts < 15000);\n        } catch(e) { return false; }\n    }\n\n    window.tiCleanLoginUrl = cleanLoginUrl;\n    window.tiReloadAfterSuccessfulLogin = function(){\n        markSuccess();\n        var clean = cleanLoginUrl();\n        try {\n            var target = new URL(clean, window.location.origin).href;\n            window.location.replace(target);\n        } catch(e) {\n            window.location.href = clean || window.location.href.split('#')[0];\n        }\n    };\n\n    function boot(){\n        try {\n            var logged = !!String(window.tiUser || '').trim();\n            if (logged || hasLoginParam() || recentSuccess()) cleanLoginUrl();\n            if (logged || recentSuccess()) {\n                hideLoginIfVisible();\n                setTimeout(hideLoginIfVisible, 180);\n            }\n        } catch(e) {}\n    }\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, {once:true});\n    else boot();\n})();\n<\/script>\n\n\n<script id=\"ti-login-direct-proposal-336\">\n(function(){\n    \"use strict\";\n    if (window.__tiLoginDirectProposal336) return;\n    window.__tiLoginDirectProposal336 = true;\n    function propose(detail){\n        try {\n            if (String(window.tiUser || '').trim()) return;\n            if (window.revealDirectLoginFallback) {\n                window.revealDirectLoginFallback('login-failed-336', detail || 'login non riuscito');\n            }\n        } catch(e) {}\n    }\n    function boot(){\n        try {\n            var params = new URLSearchParams(window.location.search || '');\n            if (params.has('ti_login_error')) {\n                var detail = params.get('ti_login_error') || 'login non riuscito';\n                setTimeout(function(){ propose(detail); }, 360);\n            }\n        } catch(e) {}\n    }\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, {once:true});\n    else boot();\n})();\n<\/script>\n\n\n<script id=\"ti-config-stay-open-324\">\n(function(){\n    \"use strict\";\n    if (window.__tiConfigStayOpen324Installed) return;\n    window.__tiConfigStayOpen324Installed = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function getConfigOverlay(){ return byId('ti-conf-ov'); }\n    function targetId(x){\n        if (typeof x === 'string') return x;\n        if (x && x.id) return x.id;\n        return '';\n    }\n    function isVisible(el){\n        if (!el) return false;\n        try {\n            var cs = window.getComputedStyle ? window.getComputedStyle(el) : null;\n            return el.style.display !== 'none' && (!cs || (cs.display !== 'none' && cs.visibility !== 'hidden' && cs.opacity !== '0'));\n        } catch(e) { return el.style.display !== 'none'; }\n    }\n    function markConfigOpen(reason){\n        window.__tiConfigStayOpen324 = true;\n        window.__tiConfigExplicitClose324 = false;\n        window.__tiConfigStayOpenReason324 = reason || 'config-open';\n    }\n    function allowExplicitConfigClose(reason){\n        window.__tiConfigExplicitClose324 = true;\n        window.__tiConfigStayOpen324 = false;\n        window.__tiConfigStayOpenReason324 = reason || 'explicit-close';\n    }\n    function shouldKeepConfigOpen(){\n        var ov = getConfigOverlay();\n        if (!ov) return false;\n        if (window.__tiConfigExplicitClose324) return false;\n        if (!window.__tiConfigStayOpen324) return false;\n        return true;\n    }\n    function showConfigOverlay(reason){\n        var ov = getConfigOverlay();\n        if (!ov || !shouldKeepConfigOpen()) return false;\n        try {\n            if (window.preparePluginModal) window.preparePluginModal(ov);\n            if (window.showPluginModal && !window.__tiConfigStayOpenShowing324) {\n                window.__tiConfigStayOpenShowing324 = true;\n                try { window.showPluginModal(ov); } finally { window.__tiConfigStayOpenShowing324 = false; }\n            } else {\n                ov.style.display = 'flex';\n                ov.removeAttribute('aria-hidden');\n            }\n            if (window.keepPluginModalInFront) window.keepPluginModalInFront(ov);\n            if (window.updatePluginScrollLock) window.updatePluginScrollLock();\n            window.__tiConfigStayOpenReason324 = reason || window.__tiConfigStayOpenReason324 || 'ensure';\n            return true;\n        } catch(e) {\n            try { ov.style.display = 'flex'; } catch(ignore) {}\n            return false;\n        }\n    }\n    function ensureConfigOpen(reason){\n        if (!shouldKeepConfigOpen()) return;\n        setTimeout(function(){ if (shouldKeepConfigOpen()) showConfigOverlay(reason || 'after-process'); }, 80);\n        setTimeout(function(){ if (shouldKeepConfigOpen()) showConfigOverlay(reason || 'after-process'); }, 350);\n    }\n\n    function wrapOpenModal(){\n        if (typeof window.openModal === 'function' && !window.openModal.__tiConfigStayOpen324) {\n            var prevOpen = window.openModal;\n            window.openModal = function(id){\n                if (targetId(id) === 'ti-conf-ov') markConfigOpen('openModal');\n                var ret = prevOpen.apply(this, arguments);\n                if (targetId(id) === 'ti-conf-ov') ensureConfigOpen('openModal');\n                return ret;\n            };\n            window.openModal.__tiConfigStayOpen324 = true;\n        }\n    }\n    function wrapShowPluginModal(){\n        if (typeof window.showPluginModal === 'function' && !window.showPluginModal.__tiConfigStayOpen324) {\n            var prevShow = window.showPluginModal;\n            window.showPluginModal = function(elOrId){\n                if (targetId(elOrId) === 'ti-conf-ov') markConfigOpen('showPluginModal');\n                var ret = prevShow.apply(this, arguments);\n                if (targetId(elOrId) === 'ti-conf-ov') ensureConfigOpen('showPluginModal');\n                return ret;\n            };\n            window.showPluginModal.__tiConfigStayOpen324 = true;\n        }\n    }\n    function wrapCloseModal(){\n        if (typeof window.closeModal === 'function' && !window.closeModal.__tiConfigStayOpen324) {\n            var prevClose = window.closeModal;\n            window.closeModal = function(id){\n                var tid = targetId(id);\n                if (tid === 'ti-conf-ov' && shouldKeepConfigOpen() && !window.__tiConfigExplicitClose324) {\n                    ensureConfigOpen('blocked-closeModal');\n                    return false;\n                }\n                var ret = prevClose.apply(this, arguments);\n                if (tid && tid !== 'ti-conf-ov') ensureConfigOpen('closed-' + tid);\n                return ret;\n            };\n            window.closeModal.__tiConfigStayOpen324 = true;\n        }\n    }\n    function wrapCloseConfigModal(){\n        if (typeof window.closeConfigModal === 'function' && !window.closeConfigModal.__tiConfigStayOpen324) {\n            var prevCloseConfig = window.closeConfigModal;\n            window.closeConfigModal = function(){\n                allowExplicitConfigClose('closeConfigModal');\n                return prevCloseConfig.apply(this, arguments);\n            };\n            window.closeConfigModal.__tiConfigStayOpen324 = true;\n        }\n    }\n    function wrapProcessFunctions(){\n        if (typeof window.hideProgressPopup === 'function' && !window.hideProgressPopup.__tiConfigStayOpen324) {\n            var prevHideProgress = window.hideProgressPopup;\n            window.hideProgressPopup = function(){\n                var ret = prevHideProgress.apply(this, arguments);\n                ensureConfigOpen('hideProgressPopup');\n                return ret;\n            };\n            window.hideProgressPopup.__tiConfigStayOpen324 = true;\n        }\n        if (typeof window.tiAlert === 'function' && !window.tiAlert.__tiConfigStayOpen324) {\n            var prevAlert = window.tiAlert;\n            window.tiAlert = function(){\n                var ret = prevAlert.apply(this, arguments);\n                ensureConfigOpen('tiAlert');\n                return ret;\n            };\n            window.tiAlert.__tiConfigStayOpen324 = true;\n        }\n        if (typeof window.closeConfigImportPreview === 'function' && !window.closeConfigImportPreview.__tiConfigStayOpen324) {\n            var prevCloseImport = window.closeConfigImportPreview;\n            window.closeConfigImportPreview = function(){\n                var ret = prevCloseImport.apply(this, arguments);\n                ensureConfigOpen('closeConfigImportPreview');\n                return ret;\n            };\n            window.closeConfigImportPreview.__tiConfigStayOpen324 = true;\n        }\n    }\n\n    function installExplicitCloseCapture(){\n        if (window.__tiConfigExplicitCloseCapture324) return;\n        window.__tiConfigExplicitCloseCapture324 = true;\n        document.addEventListener('click', function(ev){\n            var t = ev.target;\n            if (!t || !t.closest) return;\n            var ov = t.closest('#ti-conf-ov');\n            if (!ov) return;\n            var btn = t.closest('button, a');\n            if (!btn) return;\n            var txt = String(btn.textContent || btn.value || '').trim().toLowerCase();\n            var isClose = btn.classList.contains('ti-modal-x-red') || btn.classList.contains('btn-close') || \/^(x|\u00d7|chiudi|close)$\/i.test(txt) || \/\\bchiudi\\b\/i.test(txt);\n            if (isClose) allowExplicitConfigClose('user-button');\n        }, true);\n    }\n\n    function installObserver(){\n        if (window.__tiConfigStayObserver324) return;\n        var ov = getConfigOverlay();\n        if (!ov || typeof MutationObserver === 'undefined') return;\n        window.__tiConfigStayObserver324 = new MutationObserver(function(){\n            if (shouldKeepConfigOpen() && !isVisible(ov)) ensureConfigOpen('observer');\n        });\n        try { window.__tiConfigStayObserver324.observe(ov, {attributes:true, attributeFilter:['style','class','aria-hidden']}); } catch(e) {}\n    }\n\n    function boot(){\n        wrapShowPluginModal();\n        wrapOpenModal();\n        wrapCloseModal();\n        wrapCloseConfigModal();\n        wrapProcessFunctions();\n        installExplicitCloseCapture();\n        installObserver();\n        var ov = getConfigOverlay();\n        if (ov && isVisible(ov)) markConfigOpen('boot-visible');\n    }\n\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, {once:true});\n    else boot();\n    setTimeout(boot, 300);\n    setTimeout(boot, 1200);\n})();\n<\/script>\n<script id=\"ti-file-manager-ateco-front-347\">\n(function(){\n    \/\/ Disattivato dalla 30.9.353: sostituito da ti-modal-stack-353.\n})();\n<\/script>\n\n<script id=\"ti-file-manager-ateco-native-fallback-346\">\n(function(){\n    \"use strict\";\n    if (window.__tiFileManagerAtecoNativeFallback346) return;\n    window.__tiFileManagerAtecoNativeFallback346 = true;\n    function byId(id){ return document.getElementById(id); }\n    function isVisible(el){\n        if (!el) return false;\n        try { var cs = window.getComputedStyle ? window.getComputedStyle(el) : null; return !!(cs && cs.display !== 'none' && cs.visibility !== 'hidden'); }\n        catch(e) { return el.style && el.style.display !== 'none'; }\n    }\n    document.addEventListener('click', function(ev){\n        var t = ev && ev.target;\n        if (!t || !t.closest) return;\n        var atecoBtn = t.closest('button[onclick*=\"openAtecoSearch\"]');\n        if (atecoBtn) {\n            setTimeout(function(){\n                if (isVisible(byId('ti-ateco-ov'))) return;\n                var oc = String(atecoBtn.getAttribute('onclick') || '');\n                var m = oc.match(\/openAtecoSearch\\((['\"])(.*?)\\1\\)\/);\n                if (m && typeof window.openAtecoSearch === 'function') window.openAtecoSearch(m[2] || '');\n            }, 80);\n            return;\n        }\n        var fmBtn = t.closest('#ti-file-manager-ov button');\n        if (!fmBtn) return;\n        var oc = String(fmBtn.getAttribute('onclick') || '');\n        if (oc.indexOf('chooseAIManager') !== -1) {\n            setTimeout(function(){ if (!isVisible(byId('ti-ai-gen-ov')) && typeof window.chooseAIManager === 'function') window.chooseAIManager(); }, 120);\n        }\n    }, false);\n})();\n<\/script>\n<script id=\"ti-operational-popups-front-350\">\n(function(){\n    \/\/ Disattivato dalla 30.9.353: sostituito da ti-modal-stack-353.\n})();\n<\/script>\n<script id=\"ti-operational-popups-front-persistent-351\">\n(function(){\n    \/\/ Disattivato dalla 30.9.353: sostituito da ti-modal-stack-353.\n})();\n<\/script>\n<script id=\"ti-ai-gen-interactive-front-352\">\n(function(){\n    \/\/ Disattivato dalla 30.9.353: sostituito da ti-modal-stack-353.\n})();\n<\/script>\n\n\n<script id=\"ti-modal-front-354\">\n(function(){\n    \/\/ Disattivato dalla 30.9.355: il gestore con MutationObserver poteva causare flash\/loop e perdita del contesto help.\n})();\n<\/script>\n\n<script id=\"ti-modal-front-355\">\n(function(){\n    \"use strict\";\n    if (window.__tiModalFront355) return;\n    window.__tiModalFront355 = true;\n    var MAIN_Z = 2147480800;\n    var CHILD_BASE_Z = 2147483000;\n    var ALERT_Z = 2147483647;\n    var LOADER_Z = 2147483646;\n    var seq = 0;\n    var order = [];\n    var mainIds = {'ti-conf-ov':true,'ti-login-ov':true,'ti-direct-login-ov':true,'ti-purchase-ov':true};\n    function byId(id){ return typeof id === 'string' ? document.getElementById(id) : id; }\n    function isEl(el){ return !!(el && el.nodeType === 1); }\n    function isOverlay(el){ return isEl(el) && ((el.classList && el.classList.contains('ti-ov')) || !!(el.id && \/^ti-.*-ov$\/.test(el.id))); }\n    function closestOverlay(node){ try { return node && node.closest ? node.closest('.ti-ov, [id^=\"ti-\"][id$=\"-ov\"]') : null; } catch(e) { return null; } }\n    function visible(el){\n        if (!isOverlay(el)) return false;\n        try {\n            if (el.hasAttribute && el.hasAttribute('hidden')) return false;\n            var cs = window.getComputedStyle ? window.getComputedStyle(el) : null;\n            if (cs && (cs.display === 'none' || cs.visibility === 'hidden')) return false;\n            return true;\n        } catch(e) { return !!(el.style && el.style.display !== 'none'); }\n    }\n    function setZ(el, z){\n        if (!isEl(el) || !el.style) return;\n        try { el.style.setProperty('z-index', String(z), 'important'); } catch(e) { try { el.style.zIndex = String(z); } catch(e2) {} }\n        try { el.style.setProperty('pointer-events', 'auto', 'important'); } catch(e) {}\n    }\n    function removeOrder(el){ order = order.filter(function(x){ return x !== el && visible(x); }); }\n    function promote(el){\n        el = byId(el);\n        if (!isOverlay(el)) return null;\n        if (el.id === 'ti-alert-ov') { setZ(el, ALERT_Z); return el; }\n        if (el.id === 'ti-ai-loader-ov') { setZ(el, LOADER_Z); return el; }\n        removeOrder(el);\n        order.push(el);\n        seq++;\n        if (seq > 20000) seq = 1;\n        if (mainIds[el.id]) {\n            setZ(el, MAIN_Z);\n        } else {\n            setZ(el, CHILD_BASE_Z + seq);\n            var conf = document.getElementById('ti-conf-ov');\n            if (visible(conf)) setZ(conf, MAIN_Z);\n        }\n        return el;\n    }\n    function promoteVisibleTop(){ order = order.filter(visible); if (order.length) promote(order[order.length - 1]); }\n    function afterOpen(id){ setTimeout(function(){ promote(id); }, 0); setTimeout(function(){ promote(id); }, 80); setTimeout(function(){ promote(id); }, 240); }\n    window.tiPromoteModalFront355 = function(id){ return promote(id); };\n    window.tiPromoteLastVisibleModal355 = promoteVisibleTop;\n    if (typeof window.openModal === 'function' && !window.openModal.__tiFront355) {\n        var prevOpen = window.openModal;\n        window.openModal = function(id){ var ret = prevOpen.apply(this, arguments); afterOpen(id); return ret; };\n        window.openModal.__tiFront355 = true;\n    }\n    if (typeof window.showPluginModal === 'function' && !window.showPluginModal.__tiFront355) {\n        var prevShow = window.showPluginModal;\n        window.showPluginModal = function(id){ var ret = prevShow.apply(this, arguments); afterOpen(id); return ret; };\n        window.showPluginModal.__tiFront355 = true;\n    }\n    if (typeof window.closeModal === 'function' && !window.closeModal.__tiFront355) {\n        var prevClose = window.closeModal;\n        window.closeModal = function(id){ var el = byId(id); var ret = prevClose.apply(this, arguments); if (el) removeOrder(el); setTimeout(promoteVisibleTop, 40); return ret; };\n        window.closeModal.__tiFront355 = true;\n    }\n    ['mousedown','focusin','touchstart'].forEach(function(evt){ document.addEventListener(evt, function(ev){ var ov = closestOverlay(ev && ev.target); if (ov) promote(ov); }, false); });\n    setTimeout(function(){ ['ti-col-help-ov','ti-ateco-ov','ti-file-manager-ov','ti-ai-gen-ov','ti-help-ov','ti-report-ov','ti-global-action-ov'].forEach(function(id){ var el=byId(id); if (visible(el)) promote(el); }); }, 300);\n})();\n<\/script>\n\n\n<script id=\"ti-modal-context-front-356\">\n(function(){\n    \"use strict\";\n    if (window.__tiModalContextFront356) return;\n    window.__tiModalContextFront356 = true;\n\n    var MAIN_Z = 2147480900;\n    var CHILD_BASE_Z = 2147483200;\n    var ALERT_Z = 2147483647;\n    var LOADER_Z = 2147483646;\n    var mainIds = {\n        'ti-conf-ov': true,\n        'ti-login-ov': true,\n        'ti-direct-login-ov': true,\n        'ti-purchase-ov': true\n    };\n    var topSystemIds = {\n        'ti-alert-ov': ALERT_Z,\n        'ti-ai-loader-ov': LOADER_Z\n    };\n    var order = [];\n    var seq = 0;\n\n    function byId(id){ return (typeof id === 'string') ? document.getElementById(id) : id; }\n    function isEl(el){ return !!(el && el.nodeType === 1); }\n    function overlayOf(el){\n        if (!el) return null;\n        if (typeof el === 'string') return document.getElementById(el);\n        try {\n            if (el.classList && el.classList.contains('ti-ov')) return el;\n            return el.closest ? el.closest('.ti-ov, [id^=\"ti-\"][id$=\"-ov\"]') : null;\n        } catch(e) { return null; }\n    }\n    function overlayId(idOrEl){ var el = byId(idOrEl); return el && el.id ? el.id : String(idOrEl || ''); }\n    function isOverlay(el){ return isEl(el) && ((el.classList && el.classList.contains('ti-ov')) || (el.id && \/^ti-.*-ov$\/.test(el.id))); }\n    function isVisible(el){\n        if (!isOverlay(el)) return false;\n        try {\n            if (el.hasAttribute && el.hasAttribute('hidden')) return false;\n            var cs = window.getComputedStyle ? window.getComputedStyle(el) : null;\n            if (cs && (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0')) return false;\n            if (el.style && (el.style.display === 'none' || el.style.visibility === 'hidden')) return false;\n            return true;\n        } catch(e) { return !!(el.style && el.style.display !== 'none'); }\n    }\n    function setZ(el, z){\n        if (!isEl(el) || !el.style) return;\n        try { el.style.setProperty('z-index', String(z), 'important'); } catch(e) { try { el.style.zIndex = String(z); } catch(e2) {} }\n        try { el.style.setProperty('pointer-events', 'auto', 'important'); } catch(e) {}\n        if (isVisible(el)) {\n            try { el.removeAttribute('aria-hidden'); } catch(e) {}\n        }\n    }\n    function visibleChildren(){\n        var nodes = Array.prototype.slice.call(document.querySelectorAll('.ti-ov, [id^=\"ti-\"][id$=\"-ov\"]'));\n        return nodes.filter(function(el){\n            if (!isVisible(el) || !el.id) return false;\n            if (mainIds[el.id] || topSystemIds[el.id]) return false;\n            return true;\n        });\n    }\n    function visibleMain(){\n        return Object.keys(mainIds).map(function(id){ return document.getElementById(id); }).filter(isVisible);\n    }\n    function normalizeOrder(){\n        var children = visibleChildren();\n        order = order.filter(function(id){ return children.some(function(el){ return el.id === id; }); });\n        children.forEach(function(el){ if (order.indexOf(el.id) < 0) order.push(el.id); });\n    }\n    function lowerMainIfNeeded(){\n        var children = visibleChildren();\n        if (!children.length) return;\n        visibleMain().forEach(function(el){ setZ(el, MAIN_Z); });\n    }\n    function topChild(){\n        normalizeOrder();\n        for (var i = order.length - 1; i >= 0; i--) {\n            var el = document.getElementById(order[i]);\n            if (isVisible(el) && !mainIds[el.id] && !topSystemIds[el.id]) return el;\n        }\n        var children = visibleChildren();\n        return children.length ? children[children.length - 1] : null;\n    }\n    function applyStack(){\n        normalizeOrder();\n        lowerMainIfNeeded();\n        order.forEach(function(id, idx){\n            var el = document.getElementById(id);\n            if (isVisible(el)) setZ(el, CHILD_BASE_Z + idx + 1);\n        });\n        Object.keys(topSystemIds).forEach(function(id){ var el = document.getElementById(id); if (isVisible(el)) setZ(el, topSystemIds[id]); });\n    }\n    function promote(idOrEl){\n        var el = overlayOf(idOrEl) || byId(idOrEl);\n        if (!isOverlay(el)) return null;\n        if (topSystemIds[el.id]) { setZ(el, topSystemIds[el.id]); return el; }\n        if (mainIds[el.id]) {\n            var child = topChild();\n            setZ(el, MAIN_Z);\n            if (child) promote(child);\n            else setZ(el, CHILD_BASE_Z);\n            return el;\n        }\n        order = order.filter(function(id){ return id !== el.id; });\n        order.push(el.id);\n        seq++;\n        applyStack();\n        return el;\n    }\n    function promoteSoon(idOrEl){\n        var delays = [0, 60, 180, 420, 900, 1600];\n        delays.forEach(function(ms){ setTimeout(function(){ promote(idOrEl); applyStack(); }, ms); });\n    }\n\n    \/\/ Esponi un solo punto leggero di promozione. Mantiene compatibilita con il codice gia esistente.\n    window.tiPromoteModalFront356 = function(idOrEl){ return promote(idOrEl); };\n    window.tiPromoteLastVisibleModal356 = function(){ var el = topChild(); if (el) promote(el); else applyStack(); };\n    window.tiPromoteModalFront355 = window.tiPromoteModalFront356;\n    window.tiPromoteLastVisibleModal355 = window.tiPromoteLastVisibleModal356;\n\n    if (typeof window.bringPluginModalToFront === 'function' && !window.bringPluginModalToFront.__tiFront356) {\n        var prevBring = window.bringPluginModalToFront;\n        window.bringPluginModalToFront = function(elOrId){\n            var el = overlayOf(elOrId) || byId(elOrId);\n            if (el && mainIds[el.id] && topChild()) { setZ(el, MAIN_Z); promote(topChild()); return el; }\n            var ret = prevBring.apply(this, arguments);\n            var target = overlayOf(ret) || el;\n            if (target) promote(target);\n            return ret;\n        };\n        window.bringPluginModalToFront.__tiFront356 = true;\n    }\n    if (typeof window.keepPluginModalInFront === 'function' && !window.keepPluginModalInFront.__tiFront356) {\n        var prevKeep = window.keepPluginModalInFront;\n        window.keepPluginModalInFront = function(idOrEl){\n            var el = overlayOf(idOrEl) || byId(idOrEl);\n            var ret = prevKeep.apply(this, arguments);\n            if (el && mainIds[el.id]) {\n                setZ(el, MAIN_Z);\n                var child = topChild();\n                if (child) promoteSoon(child);\n            } else if (el) {\n                promoteSoon(el);\n            }\n            return ret;\n        };\n        window.keepPluginModalInFront.__tiFront356 = true;\n    }\n    if (typeof window.showPluginModal === 'function' && !window.showPluginModal.__tiFront356) {\n        var prevShow = window.showPluginModal;\n        window.showPluginModal = function(idOrEl){\n            var ret = prevShow.apply(this, arguments);\n            var el = overlayOf(idOrEl) || overlayOf(ret) || byId(idOrEl);\n            if (el) promoteSoon(el);\n            return ret;\n        };\n        window.showPluginModal.__tiFront356 = true;\n    }\n    if (typeof window.openModal === 'function' && !window.openModal.__tiFront356) {\n        var prevOpen = window.openModal;\n        window.openModal = function(id){\n            var ret = prevOpen.apply(this, arguments);\n            var el = byId(id);\n            if (el) promoteSoon(el);\n            return ret;\n        };\n        window.openModal.__tiFront356 = true;\n    }\n    if (typeof window.closeModal === 'function' && !window.closeModal.__tiFront356) {\n        var prevClose = window.closeModal;\n        window.closeModal = function(id){\n            var tid = overlayId(id);\n            var ret = prevClose.apply(this, arguments);\n            if (tid) order = order.filter(function(x){ return x !== tid; });\n            setTimeout(function(){ var child = topChild(); if (child) promote(child); else applyStack(); }, 60);\n            return ret;\n        };\n        window.closeModal.__tiFront356 = true;\n    }\n    ['mousedown','mouseup','click','focusin','touchstart'].forEach(function(evt){\n        document.addEventListener(evt, function(ev){\n            var ov = overlayOf(ev && ev.target);\n            if (!ov) return;\n            if (mainIds[ov.id] && topChild()) { setTimeout(function(){ var child = topChild(); if (child) promote(child); }, 0); return; }\n            if (!mainIds[ov.id]) promote(ov);\n        }, false);\n    });\n\n    function cloneCtx(ctx){\n        if (!ctx || typeof ctx !== 'object') return null;\n        return {tbl: ctx.tbl, idx: ctx.idx, key: ctx.key, val: ctx.val};\n    }\n    function validCtx(ctx){ return !!(ctx && typeof ctx === 'object' && ctx.tbl && ctx.idx !== undefined && ctx.idx !== null && ctx.key); }\n    function saveUploadCtx(ctx){\n        if (!validCtx(ctx)) return null;\n        var c = cloneCtx(ctx);\n        window.tiLastUploadContext356 = c;\n        try { sessionStorage.setItem('tiLastUploadContext356', JSON.stringify(c)); } catch(e) {}\n        return c;\n    }\n    function restoreUploadCtx(){\n        if (validCtx(window.targetUploadContext)) return window.targetUploadContext;\n        if (validCtx(window.tiLastUploadContext356)) { window.targetUploadContext = cloneCtx(window.tiLastUploadContext356); return window.targetUploadContext; }\n        try {\n            var raw = sessionStorage.getItem('tiLastUploadContext356');\n            if (raw) {\n                var parsed = JSON.parse(raw);\n                if (validCtx(parsed)) { window.targetUploadContext = cloneCtx(parsed); window.tiLastUploadContext356 = cloneCtx(parsed); return window.targetUploadContext; }\n            }\n        } catch(e) {}\n        return window.targetUploadContext;\n    }\n    function stampOverlayCtx(id, ctx){\n        var el = document.getElementById(id);\n        if (!el || !validCtx(ctx)) return;\n        try {\n            el.setAttribute('data-ti-context-table', ctx.tbl || '');\n            el.setAttribute('data-ti-context-row', String(ctx.idx));\n            el.setAttribute('data-ti-context-field', ctx.key || '');\n        } catch(e) {}\n    }\n\n    if (typeof window.openFileManager === 'function' && !window.openFileManager.__tiCtx356) {\n        var prevOpenFM = window.openFileManager;\n        window.openFileManager = function(tbl, idx, key){\n            var ret = prevOpenFM.apply(this, arguments);\n            var ctx = saveUploadCtx(window.targetUploadContext || {tbl:tbl, idx:idx, key:key});\n            stampOverlayCtx('ti-file-manager-ov', ctx);\n            promoteSoon('ti-file-manager-ov');\n            return ret;\n        };\n        window.openFileManager.__tiCtx356 = true;\n    }\n    if (typeof window.chooseAIManager === 'function' && !window.chooseAIManager.__tiCtx356) {\n        var prevChooseAIManager = window.chooseAIManager;\n        window.chooseAIManager = function(){\n            var ctx = restoreUploadCtx();\n            if (validCtx(ctx)) saveUploadCtx(ctx);\n            var ret = prevChooseAIManager.apply(this, arguments);\n            ctx = restoreUploadCtx();\n            if (validCtx(ctx)) {\n                window.targetUploadContext = cloneCtx(ctx);\n                stampOverlayCtx('ti-ai-gen-ov', ctx);\n                stampOverlayCtx('ti-file-manager-ov', ctx);\n            }\n            \/\/ La form AI e l'ultima operativa: deve stare sopra Gestione File Associati.\n            promoteSoon('ti-ai-gen-ov');\n            return ret;\n        };\n        window.chooseAIManager.__tiCtx356 = true;\n    }\n    if (typeof window.chooseAI === 'function' && !window.chooseAI.__tiCtx356) {\n        var prevChooseAI = window.chooseAI;\n        window.chooseAI = function(){\n            if (validCtx(window.targetUploadContext)) saveUploadCtx(window.targetUploadContext);\n            var ret = prevChooseAI.apply(this, arguments);\n            var ctx = restoreUploadCtx();\n            if (validCtx(ctx)) stampOverlayCtx('ti-ai-gen-ov', ctx);\n            promoteSoon('ti-ai-gen-ov');\n            return ret;\n        };\n        window.chooseAI.__tiCtx356 = true;\n    }\n    if (typeof window.useAIGen === 'function' && !window.useAIGen.__tiCtx356) {\n        var prevUseAI = window.useAIGen;\n        window.useAIGen = function(){\n            var ctx = restoreUploadCtx();\n            if (validCtx(ctx)) window.targetUploadContext = cloneCtx(ctx);\n            var ret = prevUseAI.apply(this, arguments);\n            setTimeout(function(){ if (validCtx(ctx)) { window.targetUploadContext = cloneCtx(ctx); promote('ti-file-manager-ov'); } }, 160);\n            return ret;\n        };\n        window.useAIGen.__tiCtx356 = true;\n    }\n    if (typeof window.handleFileSelect === 'function' && !window.handleFileSelect.__tiCtx356) {\n        var prevHandleFileSelect = window.handleFileSelect;\n        window.handleFileSelect = function(e){\n            try {\n                if (e && e.target && e.target.dataset && e.target.dataset.target === 'manager') {\n                    var ctx = restoreUploadCtx();\n                    if (validCtx(ctx)) window.targetUploadContext = cloneCtx(ctx);\n                }\n            } catch(ignore) {}\n            return prevHandleFileSelect.apply(this, arguments);\n        };\n        window.handleFileSelect.__tiCtx356 = true;\n    }\n\n    if (typeof window.openAtecoSearch === 'function' && !window.openAtecoSearch.__tiCtx356) {\n        var prevAteco = window.openAtecoSearch;\n        window.openAtecoSearch = function(targetId){\n            window.tiAtecoTargetId356 = targetId || window.tiAtecoTargetId356 || '';\n            var ret = prevAteco.apply(this, arguments);\n            try { var hidden = document.getElementById('ateco-target-id'); if (hidden && targetId) hidden.value = targetId; } catch(e) {}\n            promoteSoon('ti-ateco-ov');\n            return ret;\n        };\n        window.openAtecoSearch.__tiCtx356 = true;\n    }\n    if (typeof window.searchAteco === 'function' && !window.searchAteco.__tiFront356) {\n        var prevSearchAteco = window.searchAteco;\n        window.searchAteco = function(){ var ret = prevSearchAteco.apply(this, arguments); promoteSoon('ti-ateco-ov'); return ret; };\n        window.searchAteco.__tiFront356 = true;\n    }\n    if (typeof window.selectAteco === 'function' && !window.selectAteco.__tiCtx356) {\n        var prevSelectAteco = window.selectAteco;\n        window.selectAteco = function(codice, desc){\n            try { var hidden = document.getElementById('ateco-target-id'); if (hidden && !hidden.value && window.tiAtecoTargetId356) hidden.value = window.tiAtecoTargetId356; } catch(e) {}\n            return prevSelectAteco.apply(this, arguments);\n        };\n        window.selectAteco.__tiCtx356 = true;\n    }\n\n    if (typeof window.askColumnHelp === 'function' && !window.askColumnHelp.__tiCtx356) {\n        var prevAskColumnHelp = window.askColumnHelp;\n        window.askColumnHelp = function(tbl, col){\n            var ctx = {tbl:String(tbl || ''), col:String(col || ''), ts:Date.now()};\n            window.tiColumnHelpContext356 = ctx;\n            var ret = prevAskColumnHelp.apply(this, arguments);\n            var ov = document.getElementById('ti-col-help-ov');\n            if (ov) {\n                try {\n                    ov.setAttribute('data-ti-help-table', ctx.tbl);\n                    ov.setAttribute('data-ti-help-column', ctx.col);\n                    ov.setAttribute('data-ti-help-context-ts', String(ctx.ts));\n                } catch(e) {}\n            }\n            promoteSoon('ti-col-help-ov');\n            return ret;\n        };\n        window.askColumnHelp.__tiCtx356 = true;\n    }\n\n    \/\/ Primo riallineamento non invasivo per form gia aperte al caricamento dello script.\n    setTimeout(function(){ var child = topChild(); if (child) promote(child); else applyStack(); }, 300);\n})();\n<\/script>\n\n\n\n<script id=\"ti-help-copy-loader-close-357\">\n(function(){\n    \"use strict\";\n    if (window.__tiHelpCopyLoaderClose357Installed) return;\n    window.__tiHelpCopyLoaderClose357Installed = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function textOf(el){ return el ? String(el.innerText || el.textContent || '') : ''; }\n    function clearLoaderTimers(){\n        try {\n            var st = window.tiLoaderState || (window.tiLoaderState = {});\n            st.active = false;\n            ['timer','watchdog327','stallGuard338'].forEach(function(k){ if (st[k]) { clearInterval(st[k]); st[k] = null; } });\n            ['closeTimer338','closeTimer357'].forEach(function(k){ if (st[k]) { clearTimeout(st[k]); st[k] = null; } });\n        } catch(e) {}\n    }\n    function hardCloseLoader(reason){\n        clearLoaderTimers();\n        try { if (typeof window.clearLongAIProcess === 'function') window.clearLongAIProcess(); } catch(e) {}\n        var ov = byId('ti-ai-loader-ov');\n        if (ov) {\n            try {\n                ov.style.setProperty('display','none','important');\n                ov.style.setProperty('visibility','hidden','important');\n                ov.style.setProperty('opacity','0','important');\n                ov.style.setProperty('pointer-events','none','important');\n                ov.setAttribute('aria-hidden','true');\n            } catch(e) {}\n        }\n        try { if (typeof window.updatePluginScrollLock === 'function') window.updatePluginScrollLock(); } catch(e) {}\n        return true;\n    }\n    function looksCompleted(title, sub){\n        var s = String(title || '') + ' ' + String(sub || '') + ' ' + textOf(byId('ti-loader-title')) + ' ' + textOf(byId('ti-loader-sub')) + ' ' + textOf(byId('ti-loader-percent'));\n        s = s.toLowerCase();\n        if (\/errore|fallito|interrotto|annulla|chiarimento|specifiche|record aggiornati\\s*0\/.test(s)) return false;\n        return \/100\\s*%|completamento confermato|configurazione salvata|immagine generata|attivit[a\u00e0] ai conclusa|operazione completata\/.test(s);\n    }\n    function scheduleHardClose(reason, delay){\n        try {\n            var st = window.tiLoaderState || (window.tiLoaderState = {});\n            if (st.closeTimer357) clearTimeout(st.closeTimer357);\n            st.closeTimer357 = setTimeout(function(){ hardCloseLoader(reason); }, Math.max(80, delay || 700));\n        } catch(e) { setTimeout(function(){ hardCloseLoader(reason); }, Math.max(80, delay || 700)); }\n    }\n    window.tiHardCloseCompletedLoader357 = function(reason){\n        if (looksCompleted('', '')) return hardCloseLoader(reason || 'manual');\n        return false;\n    };\n\n    if (typeof window.hideProgressPopup === 'function' && !window.hideProgressPopup.__tiClose357) {\n        var prevHide357 = window.hideProgressPopup;\n        window.hideProgressPopup = function(success, finalText, opts){\n            var ret = prevHide357.apply(this, arguments);\n            if (success !== false) {\n                scheduleHardClose('hideProgressPopup-success', 650);\n                scheduleHardClose('hideProgressPopup-success-late', 1600);\n            }\n            return ret;\n        };\n        window.hideProgressPopup.__tiClose357 = true;\n    }\n    if (typeof window.updateProgressPopup === 'function' && !window.updateProgressPopup.__tiClose357) {\n        var prevUpdate357 = window.updateProgressPopup;\n        window.updateProgressPopup = function(percent, title, subtitle, currentItem){\n            var ret = prevUpdate357.apply(this, arguments);\n            if ((parseInt(percent,10) || 0) >= 100 && looksCompleted(title, subtitle)) {\n                scheduleHardClose('updateProgressPopup-100', 650);\n                scheduleHardClose('updateProgressPopup-100-late', 1600);\n            }\n            return ret;\n        };\n        window.updateProgressPopup.__tiClose357 = true;\n    }\n\n    \/\/ Garanzia non invasiva: se la risposta help viene aggiornata da vecchi handler, ripulisci solo # e * e mantieni il pulsante copia.\n    function sanitizeHelpNow(){\n        try {\n            var title = byId('col-help-title');\n            var box = byId('col-help-content');\n            if (title && \/[#*]\/.test(title.innerText || title.textContent || '')) title.innerText = String(title.innerText || title.textContent || '').replace(\/[#*]+\/g,'').trim();\n            if (box && \/[#*]\/.test(box.innerHTML || '')) {\n                box.innerHTML = String(box.innerHTML || '').replace(\/[#*]+\/g,'').replace(\/(<br\\s*\\\/?>(\\s*)){3,}\/gi,'<br><br>');\n            }\n        } catch(e) {}\n    }\n    if (typeof MutationObserver !== 'undefined') {\n        var box = byId('col-help-content');\n        if (box) {\n            try {\n                var busy = false;\n                var mo = new MutationObserver(function(){ if (busy) return; busy = true; setTimeout(function(){ sanitizeHelpNow(); busy = false; }, 20); });\n                mo.observe(box, {childList:true, subtree:true, characterData:true});\n            } catch(e) {}\n        }\n    }\n    setTimeout(sanitizeHelpNow, 300);\n})();\n<\/script>\n\n<script id=\"ti-ai-file-manager-stability-358\">\n(function(){\n    \"use strict\";\n    if (window.__tiAIFileManagerStability358Installed) return;\n    window.__tiAIFileManagerStability358Installed = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function visible(el){\n        if (!el) return false;\n        try {\n            var st = window.getComputedStyle ? getComputedStyle(el) : null;\n            if (st && (st.display === 'none' || st.visibility === 'hidden' || st.opacity === '0')) return false;\n            return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);\n        } catch(e) { return false; }\n    }\n    function setImp(el, prop, val){ try { if (el) el.style.setProperty(prop, val, 'important'); } catch(e) {} }\n    function cloneCtx(ctx){ return (ctx && typeof ctx === 'object') ? {tbl:ctx.tbl, idx:ctx.idx, key:ctx.key} : null; }\n    function validCtx(ctx){ return !!(ctx && ctx.tbl && ctx.key && ctx.idx !== undefined && ctx.idx !== null); }\n    function saveCtx(ctx){\n        var c = cloneCtx(ctx || window.targetUploadContext);\n        if (!validCtx(c)) return null;\n        window.__tiLastFileAiCtx358 = c;\n        try { sessionStorage.setItem('tiLastFileAiCtx358', JSON.stringify(c)); } catch(e) {}\n        return c;\n    }\n    function restoreCtx(){\n        if (validCtx(window.targetUploadContext)) return window.targetUploadContext;\n        if (validCtx(window.__tiLastFileAiCtx358)) { window.targetUploadContext = cloneCtx(window.__tiLastFileAiCtx358); return window.targetUploadContext; }\n        try {\n            var raw = sessionStorage.getItem('tiLastFileAiCtx358');\n            if (raw) {\n                var parsed = JSON.parse(raw);\n                if (validCtx(parsed)) { window.__tiLastFileAiCtx358 = cloneCtx(parsed); window.targetUploadContext = cloneCtx(parsed); return window.targetUploadContext; }\n            }\n        } catch(e) {}\n        return null;\n    }\n    function promoteFront(id, z){\n        var el = typeof id === 'string' ? byId(id) : id;\n        if (!el) return null;\n        setImp(el,'display','flex');\n        setImp(el,'visibility','visible');\n        setImp(el,'opacity','1');\n        setImp(el,'pointer-events','auto');\n        setImp(el,'z-index', String(z || 2147483646));\n        try { el.removeAttribute('aria-hidden'); el.setAttribute('data-ti-force-front','1'); } catch(e) {}\n        try { if (window.preparePluginModal) window.preparePluginModal(el); } catch(e) {}\n        try { if (window.updatePluginScrollLock) window.updatePluginScrollLock(); } catch(e) {}\n        return el;\n    }\n    function hideBehindAIGen(){\n        var fm = byId('ti-file-manager-ov');\n        if (!fm) return;\n        fm.setAttribute('data-ti-hidden-by-aigen358','1');\n        setImp(fm,'visibility','hidden');\n        setImp(fm,'opacity','0');\n        setImp(fm,'pointer-events','none');\n        setImp(fm,'z-index','2147483644');\n    }\n    function restoreFileManagerFront(){\n        var fm = byId('ti-file-manager-ov');\n        if (!fm) return;\n        try {\n            fm.removeAttribute('data-ti-hidden-by-aigen358');\n            promoteFront(fm, 2147483646);\n        } catch(e) {}\n    }\n    function keepAIGenStable(){\n        var ai = byId('ti-ai-gen-ov');\n        if (!visible(ai)) return;\n        promoteFront(ai, 2147483647);\n        hideBehindAIGen();\n    }\n    function hideCompletedLoaderNow(){\n        var ov = byId('ti-ai-loader-ov');\n        if (!ov) return false;\n        var all = '';\n        try {\n            all = [byId('ti-loader-title'), byId('ti-loader-sub'), byId('ti-loader-percent'), ov]\n                .map(function(el){ return el ? String(el.innerText || el.textContent || '') : ''; }).join(' ').toLowerCase();\n        } catch(e) {}\n        if (!\/100\\s*%|immagine generata|attivit[a\u00e0] ai conclusa|completamento confermato|configurazione salvata|operazione completata\/.test(all)) return false;\n        setImp(ov,'display','none');\n        setImp(ov,'visibility','hidden');\n        setImp(ov,'opacity','0');\n        setImp(ov,'pointer-events','none');\n        try { ov.setAttribute('aria-hidden','true'); } catch(e) {}\n        try { if (typeof window.clearLongAIProcess === 'function') window.clearLongAIProcess(); } catch(e) {}\n        try {\n            var st = window.tiLoaderState || (window.tiLoaderState = {});\n            ['timer','watchdog327','stallGuard338'].forEach(function(k){ if (st[k]) { clearInterval(st[k]); st[k]=null; } });\n            ['closeTimer338','closeTimer357'].forEach(function(k){ if (st[k]) { clearTimeout(st[k]); st[k]=null; } });\n            st.active = false;\n        } catch(e) {}\n        try { if (window.updatePluginScrollLock) window.updatePluginScrollLock(); } catch(e) {}\n        return true;\n    }\n    function schedulePostGenerateReturn(){\n        [120, 320, 700, 1400].forEach(function(ms){\n            setTimeout(function(){\n                hideCompletedLoaderNow();\n                restoreCtx();\n                var ai = byId('ti-ai-gen-ov');\n                if (!visible(ai)) restoreFileManagerFront();\n            }, ms);\n        });\n    }\n\n    if (typeof window.openFileManager === 'function' && !window.openFileManager.__ti358) {\n        var prevOpenFM358 = window.openFileManager;\n        window.openFileManager = function(tbl, idx, key){\n            saveCtx({tbl:tbl, idx:idx, key:key});\n            var ret = prevOpenFM358.apply(this, arguments);\n            [30, 150, 450].forEach(function(ms){ setTimeout(function(){ if (!visible(byId('ti-ai-gen-ov'))) restoreFileManagerFront(); }, ms); });\n            return ret;\n        };\n        window.openFileManager.__ti358 = true;\n    }\n    if (typeof window.chooseAIManager === 'function' && !window.chooseAIManager.__ti358) {\n        var prevChooseAIManager358 = window.chooseAIManager;\n        window.chooseAIManager = function(){\n            saveCtx(window.targetUploadContext);\n            window.__tiAIGenReturnToFileManager358 = true;\n            var ret = prevChooseAIManager358.apply(this, arguments);\n            [20, 120, 350].forEach(function(ms){ setTimeout(keepAIGenStable, ms); });\n            return ret;\n        };\n        window.chooseAIManager.__ti358 = true;\n    }\n    if (typeof window.chooseAI === 'function' && !window.chooseAI.__ti358) {\n        var prevChooseAI358 = window.chooseAI;\n        window.chooseAI = function(){\n            saveCtx(window.targetUploadContext);\n            window.__tiAIGenReturnToFileManager358 = false;\n            var ret = prevChooseAI358.apply(this, arguments);\n            [20, 120, 350].forEach(function(ms){ setTimeout(function(){ promoteFront('ti-ai-gen-ov', 2147483647); }, ms); });\n            return ret;\n        };\n        window.chooseAI.__ti358 = true;\n    }\n    if (typeof window.doAIGen === 'function' && !window.doAIGen.__ti358) {\n        var prevDoAIGen358 = window.doAIGen;\n        window.doAIGen = function(){\n            saveCtx(window.targetUploadContext);\n            keepAIGenStable();\n            var ret = prevDoAIGen358.apply(this, arguments);\n            setTimeout(keepAIGenStable, 80);\n            return ret;\n        };\n        window.doAIGen.__ti358 = true;\n    }\n    if (typeof window.useAIGen === 'function' && !window.useAIGen.__ti358) {\n        var prevUseAIGen358 = window.useAIGen;\n        window.useAIGen = function(){\n            saveCtx(window.targetUploadContext);\n            var ret = prevUseAIGen358.apply(this, arguments);\n            schedulePostGenerateReturn();\n            return ret;\n        };\n        window.useAIGen.__ti358 = true;\n    }\n    if (typeof window.closeModal === 'function' && !window.closeModal.__ti358AIGen) {\n        var prevClose358 = window.closeModal;\n        window.closeModal = function(id){\n            var ret = prevClose358.apply(this, arguments);\n            if (id === 'ti-ai-gen-ov') {\n                setTimeout(function(){ hideCompletedLoaderNow(); if (window.__tiAIGenReturnToFileManager358) restoreFileManagerFront(); }, 80);\n            }\n            return ret;\n        };\n        window.closeModal.__ti358AIGen = true;\n    }\n\n    if (typeof MutationObserver !== 'undefined') {\n        try {\n            var aiRes = byId('ai-gen-res');\n            if (aiRes) {\n                var moRes = new MutationObserver(function(){\n                    if (visible(aiRes)) {\n                        hideCompletedLoaderNow();\n                        promoteFront('ti-ai-gen-ov', 2147483647);\n                        hideBehindAIGen();\n                    }\n                });\n                moRes.observe(aiRes, {attributes:true, attributeFilter:['style','class'], childList:true, subtree:true});\n            }\n        } catch(e) {}\n        try {\n            var aiOv = byId('ti-ai-gen-ov');\n            if (aiOv) {\n                var moAi = new MutationObserver(function(){ if (visible(aiOv)) keepAIGenStable(); else if (window.__tiAIGenReturnToFileManager358) restoreFileManagerFront(); });\n                moAi.observe(aiOv, {attributes:true, attributeFilter:['style','class','aria-hidden']});\n            }\n        } catch(e) {}\n        try {\n            var fmOv = byId('ti-file-manager-ov');\n            if (fmOv) {\n                var moFm = new MutationObserver(function(){\n                    if (fmOv.getAttribute('data-ti-hidden-by-aigen358') === '1' && visible(byId('ti-ai-gen-ov'))) hideBehindAIGen();\n                });\n                moFm.observe(fmOv, {attributes:true, attributeFilter:['style','class','aria-hidden']});\n            }\n        } catch(e) {}\n    }\n\n    setTimeout(function(){\n        restoreCtx();\n        if (visible(byId('ti-ai-gen-ov'))) keepAIGenStable();\n        else if (visible(byId('ti-file-manager-ov'))) restoreFileManagerFront();\n        hideCompletedLoaderNow();\n    }, 250);\n})();\n<\/script>\n\n<script id=\"ti-file-manager-genera-ai-click-360\">\n(function(){\n    \"use strict\";\n    if (window.__tiFileManagerGeneraAiClick360Installed) return;\n    window.__tiFileManagerGeneraAiClick360Installed = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function isObj(v){ return !!(v && typeof v === 'object'); }\n    function validCtx(ctx){ return !!(isObj(ctx) && ctx.tbl && ctx.key && ctx.idx !== undefined && ctx.idx !== null && ctx.idx !== ''); }\n    function cloneCtx(ctx){ return validCtx(ctx) ? {tbl:String(ctx.tbl), idx:ctx.idx, key:String(ctx.key)} : null; }\n    function saveCtx(ctx){\n        var c = cloneCtx(ctx);\n        if (!c) return null;\n        window.targetUploadContext = cloneCtx(c);\n        window.__tiLastFileManagerCtx360 = cloneCtx(c);\n        try { sessionStorage.setItem('tiLastFileManagerCtx360', JSON.stringify(c)); } catch(e) {}\n        return c;\n    }\n    function ctxFromFileManager(){\n        var ctx = cloneCtx(window.targetUploadContext);\n        if (ctx) return ctx;\n        var ov = byId('ti-file-manager-ov');\n        if (ov) {\n            ctx = cloneCtx({tbl:ov.dataset ? ov.dataset.tbl : '', idx:ov.dataset ? ov.dataset.idx : '', key:ov.dataset ? ov.dataset.key : ''});\n            if (ctx) return saveCtx(ctx);\n            ctx = cloneCtx({\n                tbl:ov.getAttribute('data-ti-context-table') || '',\n                idx:ov.getAttribute('data-ti-context-row') || '',\n                key:ov.getAttribute('data-ti-context-field') || ''\n            });\n            if (ctx) return saveCtx(ctx);\n        }\n        ctx = cloneCtx(window.__tiLastFileManagerCtx360);\n        if (ctx) return saveCtx(ctx);\n        try {\n            var raw = sessionStorage.getItem('tiLastFileManagerCtx360') || sessionStorage.getItem('tiLastFileAiCtx358') || sessionStorage.getItem('tiLastUploadContext356');\n            if (raw) {\n                var parsed = JSON.parse(raw);\n                ctx = cloneCtx(parsed);\n                if (ctx) return saveCtx(ctx);\n            }\n        } catch(e) {}\n        return null;\n    }\n    function setImp(el, prop, val){ try { if (el) el.style.setProperty(prop, val, 'important'); } catch(e) {} }\n    function showFront(id, z){\n        var el = typeof id === 'string' ? byId(id) : id;\n        if (!el) return null;\n        setImp(el,'display','flex');\n        setImp(el,'visibility','visible');\n        setImp(el,'opacity','1');\n        setImp(el,'pointer-events','auto');\n        setImp(el,'z-index', String(z || 2147483647));\n        try { el.removeAttribute('aria-hidden'); el.setAttribute('data-ti-force-front','1'); } catch(e) {}\n        try { if (window.preparePluginModal) window.preparePluginModal(el); } catch(e) {}\n        try { if (window.updatePluginScrollLock) window.updatePluginScrollLock(); } catch(e) {}\n        return el;\n    }\n    function quietFileManagerBehind(){\n        var fm = byId('ti-file-manager-ov');\n        if (!fm) return;\n        try { fm.setAttribute('data-ti-hidden-by-aigen360','1'); } catch(e) {}\n        setImp(fm,'display','flex');\n        setImp(fm,'visibility','hidden');\n        setImp(fm,'opacity','0');\n        setImp(fm,'pointer-events','none');\n        setImp(fm,'z-index','2147483644');\n    }\n    function rowForCtx(ctx){\n        try {\n            if (window.currentDbData && window.currentDbData.Tabelle && window.currentDbData.Tabelle[ctx.tbl]) {\n                return window.currentDbData.Tabelle[ctx.tbl][ctx.idx] || null;\n            }\n        } catch(e) {}\n        return null;\n    }\n    function resetAIGenUi(row){\n        try {\n            if (typeof window.populateAIGenPrompt === 'function') window.populateAIGenPrompt(row);\n            else if (byId('ai-gen-prompt')) {\n                var label = row ? (row.Descrizione || row.Prodotto || row.Servizio || row.Nome || '') : '';\n                byId('ai-gen-prompt').value = (typeof window.buildAIGenPrompt === 'function') ? window.buildAIGenPrompt(label) : String(label || '');\n            }\n        } catch(e) {}\n        try { if (byId('ai-gen-detail')) byId('ai-gen-detail').value = ''; } catch(e) {}\n        try { if (byId('ai-gen-res')) byId('ai-gen-res').style.display = 'none'; } catch(e) {}\n        try { if (byId('ai-gen-img')) byId('ai-gen-img').src = ''; } catch(e) {}\n        try {\n            var btn = byId('ai-gen-btn');\n            if (btn) {\n                btn.disabled = false;\n                btn.innerText = 'Genera Immagine';\n                btn.style.display = 'block';\n            }\n        } catch(e) {}\n        window.currentGenFilename = null;\n    }\n    function openAIGeneratorFromFileManager(){\n        var ctx = ctxFromFileManager();\n        if (!ctx) {\n            if (window.tiAlert) window.tiAlert('Gestione file associati: record\/campo non disponibile. Riapri la form dalla tabella.');\n            return false;\n        }\n        saveCtx(ctx);\n        var row = rowForCtx(ctx);\n        resetAIGenUi(row);\n        window.__tiAIGenReturnToFileManager358 = true;\n        window.__tiAIGenReturnToFileManager360 = true;\n        try { if (typeof window.openModal === 'function') window.openModal('ti-ai-gen-ov'); } catch(e) {}\n        quietFileManagerBehind();\n        showFront('ti-ai-gen-ov', 2147483647);\n        [40, 160, 420, 900].forEach(function(ms){\n            setTimeout(function(){\n                saveCtx(ctx);\n                quietFileManagerBehind();\n                showFront('ti-ai-gen-ov', 2147483647);\n                try { var prompt = byId('ai-gen-prompt'); if (prompt && ms === 160) prompt.focus({preventScroll:true}); } catch(e) {}\n            }, ms);\n        });\n        return false;\n    }\n    window.tiOpenAIGeneratorFromFileManager360 = openAIGeneratorFromFileManager;\n\n    document.addEventListener('click', function(ev){\n        var t = ev && ev.target;\n        if (!t || !t.closest) return;\n        var btn = t.closest('#ti-file-manager-ov button');\n        if (!btn) return;\n        var oc = String(btn.getAttribute('onclick') || '');\n        var txt = String(btn.textContent || btn.value || '').toLowerCase();\n        if (oc.indexOf('chooseAIManager') === -1 && txt.indexOf('genera con ai') === -1) return;\n        try { ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); } catch(e) {}\n        openAIGeneratorFromFileManager();\n    }, true);\n\n    function bindButton(){\n        try {\n            var btns = document.querySelectorAll('#ti-file-manager-ov button');\n            Array.prototype.forEach.call(btns, function(btn){\n                var oc = String(btn.getAttribute('onclick') || '');\n                var txt = String(btn.textContent || '').toLowerCase();\n                if (oc.indexOf('chooseAIManager') !== -1 || txt.indexOf('genera con ai') !== -1) {\n                    btn.setAttribute('data-ti-genera-ai-360','1');\n                    btn.onclick = function(e){\n                        if (e) { try { e.preventDefault(); e.stopPropagation(); } catch(ignore) {} }\n                        return openAIGeneratorFromFileManager();\n                    };\n                }\n            });\n        } catch(e) {}\n    }\n    setTimeout(bindButton, 200);\n    setTimeout(bindButton, 1000);\n    if (typeof MutationObserver !== 'undefined') {\n        try {\n            var fm = byId('ti-file-manager-ov');\n            if (fm) new MutationObserver(bindButton).observe(fm, {childList:true, subtree:true});\n        } catch(e) {}\n    }\n})();\n<\/script>\n\n\n\n<script id=\"ti-plugin-audio-mic-internal-363\">\n(function(){\n    \"use strict\";\n    if (window.__tiPluginAudioMic363Installed) return;\n    window.__tiPluginAudioMic363Installed = true;\n\n    var audioCtx = null;\n    var audioUnlocked = false;\n    var speechPrimed = false;\n    var lastUnlockError = '';\n    var micRecognition = null;\n    var micStoppingTimer = null;\n\n    function byId(id){ return document.getElementById(id); }\n    function root(){ return byId('ti-ai-outer') || document.querySelector('.ti-ai-shop-service,[data-ti-shop-service]'); }\n    function isMuted(){ return !!(window.isMuted || window.muteState === 1 || window.muteState === 2); }\n    function safeAlert(msg){ try { if (window.tiAlert) window.tiAlert(msg); else alert(msg); } catch(e) { try { alert(msg); } catch(ignore) {} } }\n    function setMicUi(active){\n        var btn = byId('ti-mic');\n        if (!btn) return;\n        try {\n            btn.style.background = active ? '#b91c1c' : '#222';\n            btn.style.color = active ? '#fff' : '#ccc';\n            btn.textContent = active ? 'Mic...' : ((window.currLang === 'en') ? 'Mic' : 'Mic');\n            btn.disabled = false;\n        } catch(e) {}\n    }\n    function secureContextOk(){\n        try { return window.isSecureContext || location.protocol === 'https:' || location.hostname === 'localhost' || location.hostname === '127.0.0.1'; } catch(e) { return false; }\n    }\n    function unlockAudioInternal(){\n        var ok = false;\n        lastUnlockError = '';\n        try {\n            var AC = window.AudioContext || window.webkitAudioContext;\n            if (AC) {\n                audioCtx = audioCtx || new AC();\n                if (audioCtx.state === 'suspended' && audioCtx.resume) {\n                    var r = audioCtx.resume();\n                    if (r && typeof r.catch === 'function') r.catch(function(e){ lastUnlockError = e && e.message ? e.message : String(e || ''); });\n                }\n                try {\n                    var osc = audioCtx.createOscillator();\n                    var gain = audioCtx.createGain();\n                    gain.gain.value = 0.00001;\n                    osc.connect(gain);\n                    gain.connect(audioCtx.destination);\n                    osc.start(0);\n                    osc.stop(audioCtx.currentTime + 0.04);\n                } catch(ignore) {}\n                ok = true;\n            }\n        } catch(e) {\n            lastUnlockError = e && e.message ? e.message : String(e || 'AudioContext non disponibile');\n        }\n        try {\n            if ('speechSynthesis' in window) {\n                window.speechSynthesis.resume();\n                if (!speechPrimed) {\n                    var u = new SpeechSynthesisUtterance(' ');\n                    u.volume = 0;\n                    u.rate = 1;\n                    u.pitch = 1;\n                    u.lang = (window.currLang === 'en') ? 'en-US' : 'it-IT';\n                    window.speechSynthesis.speak(u);\n                    speechPrimed = true;\n                }\n                ok = true;\n            }\n        } catch(e) {\n            lastUnlockError = e && e.message ? e.message : String(e || 'speechSynthesis non disponibile');\n        }\n        try {\n            var r = root();\n            (r ? r.querySelectorAll('audio,video') : document.querySelectorAll('#ti-ai-outer audio,#ti-ai-outer video')).forEach(function(el){\n                try { el.muted = false; el.volume = 1; } catch(ignore) {}\n            });\n        } catch(e) {}\n        if (ok) audioUnlocked = true;\n        window.tiPluginAudioUnlocked363 = audioUnlocked;\n        return ok;\n    }\n    window.tiEnsurePluginAudioUnlocked = function(){ return unlockAudioInternal(); };\n    window.tiPluginAudioStatus = function(){\n        return {\n            audioUnlocked: audioUnlocked,\n            audioContextState: audioCtx ? audioCtx.state : 'not-created',\n            speechSynthesis: ('speechSynthesis' in window),\n            speechPrimed: speechPrimed,\n            muted: isMuted(),\n            secureContext: secureContextOk(),\n            mediaDevices: !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),\n            speechRecognition: !!(window.SpeechRecognition || window.webkitSpeechRecognition),\n            rootFound: !!root(),\n            lastUnlockError: lastUnlockError\n        };\n    };\n\n    var oldPrime = window.primeSpeech;\n    window.primeSpeech = function(){\n        if (audioUnlocked && speechPrimed) return true;\n        return unlockAudioInternal() || (typeof oldPrime === 'function' ? oldPrime.apply(this, arguments) : false);\n    };\n\n    if (typeof window.speakText === 'function' && !window.speakText.__tiAudio363) {\n        var oldSpeakText = window.speakText;\n        window.speakText = function(text){\n            if (!isMuted()) unlockAudioInternal();\n            return oldSpeakText.apply(this, arguments);\n        };\n        window.speakText.__tiAudio363 = true;\n    }\n\n    function startMic(){\n        unlockAudioInternal();\n        var btn = byId('ti-mic');\n        var msg = byId('ti-msg');\n        var send = byId('ti-send');\n        if (!secureContextOk()) {\n            safeAlert('Microfono non disponibile. Apri la pagina in HTTPS e autorizza il microfono per questo sito.');\n            return false;\n        }\n        if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {\n            safeAlert('Microfono non disponibile nel browser. Verifica permessi sito oppure usa Chrome, Edge o Safari aggiornato.');\n            return false;\n        }\n        var SR = window.SpeechRecognition || window.webkitSpeechRecognition;\n        if (!SR) {\n            safeAlert('Riconoscimento vocale non supportato dal browser. Il microfono esiste, ma questo browser non supporta SpeechRecognition. Usa Chrome o Edge quando possibile.');\n            return false;\n        }\n        if (micRecognition) {\n            try { micRecognition.stop(); } catch(e) {}\n            micRecognition = null;\n            setMicUi(false);\n            return false;\n        }\n        setMicUi(true);\n        navigator.mediaDevices.getUserMedia({audio:true}).then(function(stream){\n            try { stream.getTracks().forEach(function(t){ t.stop(); }); } catch(e) {}\n            var r = new SR();\n            micRecognition = r;\n            r.lang = (window.currLang === 'en') ? 'en-US' : 'it-IT';\n            r.continuous = false;\n            r.interimResults = false;\n            r.maxAlternatives = 1;\n            r.onresult = function(e){\n                try {\n                    var transcript = e && e.results && e.results[0] && e.results[0][0] ? e.results[0][0].transcript : '';\n                    if (transcript && msg) {\n                        msg.value = transcript;\n                        msg.dispatchEvent(new Event('input', {bubbles:true}));\n                        msg.dispatchEvent(new Event('change', {bubbles:true}));\n                    }\n                    if (transcript && send) setTimeout(function(){ try { send.click(); } catch(ignore) {} }, 80);\n                } catch(err) {}\n            };\n            r.onerror = function(e){\n                var code = e && e.error ? e.error : 'errore';\n                var detail = 'Microfono non disponibile o non autorizzato.';\n                if (code === 'not-allowed' || code === 'service-not-allowed') detail = 'Microfono non autorizzato. Abilita il permesso microfono nelle impostazioni del browser per questo sito.';\n                else if (code === 'no-speech') detail = 'Non ho rilevato voce. Riprova parlando dopo aver premuto Mic.';\n                else if (code === 'audio-capture') detail = 'Nessun microfono rilevato dal browser.';\n                safeAlert(detail + ' Codice: ' + code);\n            };\n            r.onend = function(){\n                micRecognition = null;\n                if (micStoppingTimer) { clearTimeout(micStoppingTimer); micStoppingTimer = null; }\n                setMicUi(false);\n            };\n            try { r.start(); } catch(e) { micRecognition = null; setMicUi(false); safeAlert('Impossibile avviare il microfono: ' + (e && e.message ? e.message : 'errore browser')); return; }\n            micStoppingTimer = setTimeout(function(){\n                try { if (micRecognition) micRecognition.stop(); } catch(e) {}\n                setMicUi(false);\n            }, 10000);\n        }).catch(function(e){\n            setMicUi(false);\n            var msgErr = e && e.name ? e.name : '';\n            safeAlert('Microfono non autorizzato o non disponibile. Verifica i permessi del browser per questo sito. ' + (msgErr ? 'Dettaglio: ' + msgErr : ''));\n        });\n        return false;\n    }\n    window.tiStartPluginMic = startMic;\n\n    function installMicButton(){\n        var btn = byId('ti-mic');\n        if (!btn || btn.__tiMic363) return;\n        btn.__tiMic363 = true;\n        btn.onclick = function(ev){\n            if (ev) { try { ev.preventDefault(); ev.stopPropagation(); } catch(e) {} }\n            return startMic();\n        };\n    }\n    function installGestureUnlock(){\n        var r = root();\n        if (!r || r.__tiAudioGesture363) return;\n        r.__tiAudioGesture363 = true;\n        ['pointerdown','touchstart','mousedown','click','keydown'].forEach(function(evt){\n            r.addEventListener(evt, function(){ unlockAudioInternal(); }, {capture:true, passive:true});\n        });\n    }\n    function installAll(){ installGestureUnlock(); installMicButton(); }\n\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', installAll);\n    installAll();\n    setTimeout(installAll, 300);\n    setTimeout(installAll, 1200);\n})();\n<\/script>\n\n\n\n<script id=\"ti-ateco-config-field-restore-364\">\n(function(){\n    \"use strict\";\n    if (window.__tiAtecoConfigFieldRestore364Installed) return;\n    window.__tiAtecoConfigFieldRestore364Installed = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function norm(s){\n        try { s = String(s || '').normalize('NFD').replace(\/[\\u0300-\\u036f]\/g,''); } catch(e) { s = String(s || ''); }\n        return s.toLowerCase().replace(\/[^a-z0-9]+\/g,'');\n    }\n    function isAtecoFieldName(key){\n        var n = norm(key);\n        return n === 'attivita' || n === 'attivitaditta' || n === 'codiceateco' || n === 'codateco' || n.indexOf('ateco') !== -1;\n    }\n    function visible(el){\n        if (!el) return false;\n        try {\n            var st = window.getComputedStyle ? getComputedStyle(el) : null;\n            if (st && (st.display === 'none' || st.visibility === 'hidden' || st.opacity === '0')) return false;\n            return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);\n        } catch(e) { return false; }\n    }\n    function css(el, prop, val){ try { if (el) el.style.setProperty(prop, val, 'important'); } catch(e) {} }\n    function ensureId(el){\n        if (!el) return '';\n        if (el.id) return el.id;\n        var tbl = el.getAttribute('data-tbl') || 'Config';\n        var idx = el.getAttribute('data-idx') || '0';\n        var key = el.getAttribute('data-key') || 'Attivita';\n        var id = 'ti-ateco-target-' + norm(tbl) + '-' + String(idx).replace(\/[^0-9a-z_-]\/gi,'') + '-' + norm(key) + '-' + Math.random().toString(36).slice(2,7);\n        el.id = id;\n        return id;\n    }\n    function frontAteco(){\n        var ov = byId('ti-ateco-ov');\n        if (!ov) return;\n        try { if (window.preparePluginModal) window.preparePluginModal(ov); } catch(e) {}\n        css(ov, 'display', 'flex');\n        css(ov, 'visibility', 'visible');\n        css(ov, 'opacity', '1');\n        css(ov, 'pointer-events', 'auto');\n        css(ov, 'z-index', '2147483647');\n        try { ov.removeAttribute('aria-hidden'); ov.setAttribute('data-ti-config-child-modal','1'); ov.setAttribute('data-ti-force-front','1'); } catch(e) {}\n        try { if (window.updatePluginScrollLock) window.updatePluginScrollLock(); } catch(e) {}\n        setTimeout(function(){\n            try {\n                var input = byId('ateco-in');\n                if (input) input.focus({preventScroll:true});\n            } catch(e) {}\n        }, 80);\n    }\n    function openForTarget(el){\n        if (!el) return false;\n        var id = ensureId(el);\n        window.__tiAtecoTargetElement364 = el;\n        window.__tiAtecoTargetId364 = id;\n        try {\n            var h = byId('ateco-target-id');\n            if (h) h.value = id;\n        } catch(e) {}\n        try {\n            if (typeof window.__tiNativeOpenAtecoSearch364 === 'function') {\n                window.__tiNativeOpenAtecoSearch364(id);\n            } else if (typeof window.openAtecoSearch === 'function') {\n                window.openAtecoSearch(id);\n            }\n        } catch(e) {\n            try { if (window.tiAlert) window.tiAlert('Ricerca ATECO non disponibile: ' + (e && e.message ? e.message : 'errore')); } catch(ignore) {}\n        }\n        try {\n            var h2 = byId('ateco-target-id');\n            if (h2) h2.value = id;\n            var q = byId('ateco-in');\n            if (q && !q.value && el.value) q.value = String(el.value || '').trim();\n            var res = byId('ateco-res');\n            if (res && !String(res.innerHTML || '').trim()) res.innerHTML = '<i>Scrivi l\\'attivit\u00e0 da cercare...<\/i>';\n        } catch(e) {}\n        frontAteco();\n        return false;\n    }\n    function findFieldFromButton(btn){\n        if (!btn) return null;\n        var wrap = btn.parentElement || btn.closest('td');\n        var field = wrap ? wrap.querySelector('input[data-key], textarea[data-key], select[data-key]') : null;\n        if (field) return field;\n        var oc = String(btn.getAttribute('onclick') || '');\n        var m = oc.match(\/openAtecoSearch\\((['\"])(.*?)\\1\\)\/);\n        if (m && m[2]) return byId(m[2]);\n        var targetId = btn.getAttribute('data-ti-ateco-target');\n        return targetId ? byId(targetId) : null;\n    }\n    function addButtonForField(el){\n        if (!el || !isAtecoFieldName(el.getAttribute('data-key') || '')) return;\n        var parent = el.parentElement;\n        if (!parent) return;\n        if (parent.querySelector('[data-ti-ateco-btn=\"364\"], button[onclick*=\"openAtecoSearch\"]')) return;\n        var btn = document.createElement('button');\n        btn.type = 'button';\n        btn.className = 'ti-btn ti-ateco-restore-btn';\n        btn.setAttribute('data-ti-ateco-btn', '364');\n        btn.setAttribute('data-ti-ateco-target', ensureId(el));\n        btn.title = 'Ricerca ATECO';\n        btn.textContent = '\ud83d\udd0d';\n        btn.style.cssText = 'padding:4px 6px;font-size:10px;background:#2563eb;color:#fff;white-space:nowrap;';\n        parent.appendChild(btn);\n    }\n    function ensureAtecoButtons(){\n        try {\n            var scope = byId('ti-conf-ov') || document;\n            var fields = scope.querySelectorAll('input[data-key], textarea[data-key], select[data-key]');\n            Array.prototype.forEach.call(fields, addButtonForField);\n        } catch(e) {}\n    }\n\n    if (typeof window.openAtecoSearch === 'function' && !window.openAtecoSearch.__tiAteco364) {\n        window.__tiNativeOpenAtecoSearch364 = window.openAtecoSearch;\n        window.openAtecoSearch = function(targetId){\n            var el = targetId ? byId(targetId) : null;\n            if (el) {\n                window.__tiAtecoTargetElement364 = el;\n                window.__tiAtecoTargetId364 = targetId;\n            }\n            var ret = window.__tiNativeOpenAtecoSearch364.apply(this, arguments);\n            try { var h = byId('ateco-target-id'); if (h && targetId) h.value = targetId; } catch(e) {}\n            frontAteco();\n            return ret;\n        };\n        window.openAtecoSearch.__tiAteco364 = true;\n    }\n    if (typeof window.selectAteco === 'function' && !window.selectAteco.__tiAteco364) {\n        var prevSelect364 = window.selectAteco;\n        window.selectAteco = function(codice, desc){\n            try {\n                var h = byId('ateco-target-id');\n                var id = (h && h.value) || window.__tiAtecoTargetId364 || '';\n                var el = id ? byId(id) : null;\n                if (!el && window.__tiAtecoTargetElement364) el = window.__tiAtecoTargetElement364;\n                if (el) {\n                    el.value = desc || codice || '';\n                    try { el.dispatchEvent(new Event('input', {bubbles:true})); } catch(e) {}\n                    try { el.dispatchEvent(new Event('change', {bubbles:true})); } catch(e) {}\n                }\n            } catch(e) {}\n            return prevSelect364.apply(this, arguments);\n        };\n        window.selectAteco.__tiAteco364 = true;\n    }\n    document.addEventListener('click', function(ev){\n        var btn = ev.target && ev.target.closest ? ev.target.closest('[data-ti-ateco-btn=\"364\"], button[onclick*=\"openAtecoSearch\"]') : null;\n        if (!btn) return;\n        var field = findFieldFromButton(btn);\n        if (!field) return;\n        if (!isAtecoFieldName(field.getAttribute('data-key') || '')) return;\n        ev.preventDefault();\n        ev.stopPropagation();\n        openForTarget(field);\n        return false;\n    }, true);\n\n    if (typeof window.renderConfig === 'function' && !window.renderConfig.__tiAteco364) {\n        var prevRender364 = window.renderConfig;\n        window.renderConfig = function(){\n            var ret = prevRender364.apply(this, arguments);\n            setTimeout(ensureAtecoButtons, 80);\n            setTimeout(ensureAtecoButtons, 350);\n            return ret;\n        };\n        window.renderConfig.__tiAteco364 = true;\n    }\n    if (typeof MutationObserver !== 'undefined') {\n        try {\n            var cfg = byId('ti-conf-ov');\n            if (cfg) {\n                var mo = new MutationObserver(function(){ setTimeout(ensureAtecoButtons, 60); });\n                mo.observe(cfg, {childList:true, subtree:true});\n            }\n        } catch(e) {}\n    }\n    setTimeout(ensureAtecoButtons, 400);\n})();\n<\/script>\n\n\n<script id=\"ti-global-template-quick-button-368\">\n(function(){\n    \"use strict\";\n    if (window.__tiGlobalTemplateQuickButton368Installed) return;\n    window.__tiGlobalTemplateQuickButton368Installed = true;\n\n    var CMD = \"Adegua struttura database a template shopservicemain.json\";\n\n    function byId(id){ return document.getElementById(id); }\n\n    function fillGlobalTemplateCommand(){\n        var prompt = byId('global-action-prompt');\n        if (!prompt) return false;\n        prompt.value = CMD;\n        try { prompt.dispatchEvent(new Event('input', {bubbles:true})); } catch(e) {}\n        try { prompt.dispatchEvent(new Event('change', {bubbles:true})); } catch(e) {}\n        try { prompt.focus({preventScroll:true}); } catch(e) { try { prompt.focus(); } catch(ignore) {} }\n        return false;\n    }\n\n    window.tiFillGlobalTemplateCommand368 = fillGlobalTemplateCommand;\n\n    function ensureButton(){\n        var prompt = byId('global-action-prompt');\n        if (!prompt) return;\n\n        var btn = byId('global-action-template-btn');\n        if (!btn) {\n            btn = document.createElement('button');\n            btn.type = 'button';\n            btn.id = 'global-action-template-btn';\n            btn.className = 'ti-btn';\n            btn.textContent = 'Template';\n            btn.title = 'Scrivi comando: ' + CMD;\n            btn.onclick = fillGlobalTemplateCommand;\n\n            var parent = prompt.parentNode;\n            if (parent) {\n                try {\n                    parent.style.display = 'flex';\n                    parent.style.gap = parent.style.gap || '6px';\n                    parent.style.alignItems = 'stretch';\n                } catch(e) {}\n                parent.appendChild(btn);\n            }\n        } else {\n            btn.onclick = fillGlobalTemplateCommand;\n            btn.textContent = 'Template';\n            btn.title = 'Scrivi comando: ' + CMD;\n        }\n\n        try {\n            btn.style.setProperty('display', 'inline-flex', 'important');\n            btn.style.setProperty('align-items', 'center', 'important');\n            btn.style.setProperty('justify-content', 'center', 'important');\n            btn.style.setProperty('width', '58px', 'important');\n            btn.style.setProperty('min-width', '58px', 'important');\n            btn.style.setProperty('padding', '6px 4px', 'important');\n            btn.style.setProperty('background', '#0f766e', 'important');\n            btn.style.setProperty('color', '#fff', 'important');\n            btn.style.setProperty('font-size', '10px', 'important');\n            btn.style.setProperty('line-height', '1.1', 'important');\n            btn.style.setProperty('font-weight', '800', 'important');\n            btn.style.setProperty('white-space', 'normal', 'important');\n            btn.style.setProperty('cursor', 'pointer', 'important');\n            prompt.style.setProperty('flex', '1 1 auto', 'important');\n            prompt.style.setProperty('min-width', '0', 'important');\n        } catch(e) {}\n    }\n\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', ensureButton);\n    else ensureButton();\n\n    if (typeof window.openModal === 'function' && !window.openModal.__tiGlobalTpl368) {\n        var prevOpen = window.openModal;\n        window.openModal = function(id){\n            var ret = prevOpen.apply(this, arguments);\n            if (id === 'ti-global-action-ov') setTimeout(ensureButton, 40);\n            return ret;\n        };\n        window.openModal.__tiGlobalTpl368 = true;\n    }\n\n    document.addEventListener('click', function(ev){\n        var t = ev && ev.target;\n        if (!t) return;\n        if (t.id === 'global-action-template-btn' || (t.closest && t.closest('#global-action-template-btn'))) {\n            ev.preventDefault();\n            ev.stopPropagation();\n            fillGlobalTemplateCommand();\n        }\n    }, true);\n\n    setTimeout(ensureButton, 300);\n})();\n<\/script>\n\n\n<script id=\"ti-contact-num-readonly-416\">\n(function(){\n    \"use strict\";\n    if (window.__tiContactNumReadonly416Installed) return;\n    window.__tiContactNumReadonly416Installed = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function selectedDb(){\n        try {\n            var d = byId('ti-ditta');\n            var v = d && d.value ? String(d.value).trim() : '';\n            if (!v || v === 'NEW_DB') {\n                try { v = String(window.tiForced || localStorage.getItem('ti_saved_db') || '').trim(); } catch(e) {}\n            }\n            return (!v || v === 'NEW_DB') ? '' : v;\n        } catch(e) { return ''; }\n    }\n    function fmt(n){\n        n = parseInt(n, 10) || 0;\n        try { return n.toLocaleString('it-IT'); } catch(e) { return String(n).replace(\/\\B(?=(\\d{3})+(?!\\d))\/g, '.'); }\n    }\n    function getConfigRow(){\n        try {\n            var data = window.currentDbData || {};\n            var t = data && data.Tabelle ? data.Tabelle : null;\n            if (!t) return null;\n            var cfgName = Object.keys(t).find(function(k){ return String(k).toLowerCase() === 'config'; }) || 'Config';\n            var rows = t[cfgName];\n            if (Array.isArray(rows)) return rows[0] || null;\n            if (rows && typeof rows === 'object') return Object.values(rows)[0] || null;\n        } catch(e) {}\n        return null;\n    }\n    function setLocalContactsNum(n, field){\n        n = parseInt(n, 10) || 0;\n        try {\n            if (!window.currentDbData) window.currentDbData = {};\n            window.currentDbData.__contacts_num = n;\n            var row = getConfigRow();\n            if (row) {\n                var k = Object.keys(row).find(function(x){\n                    var norm = String(x || '').toLowerCase().replace(\/[^a-z0-9]\/g,'');\n                    return norm === 'contactsnum' || norm === 'contactnum';\n                }) || field || 'Contacts_num';\n                row[k] = n;\n            }\n        } catch(e) {}\n        return n;\n    }\n    function currentContactsNum(){\n        try {\n            if (window.currentDbData && typeof window.currentDbData.__contacts_num !== 'undefined') return parseInt(window.currentDbData.__contacts_num, 10) || 0;\n            var row = getConfigRow();\n            if (!row) return 0;\n            var k = Object.keys(row).find(function(x){\n                var norm = String(x || '').toLowerCase().replace(\/[^a-z0-9]\/g,'');\n                return norm === 'contactsnum' || norm === 'contactnum';\n            });\n            return k ? (parseInt(String(row[k] || '0').replace(\/[^0-9\\-]\/g,''), 10) || 0) : 0;\n        } catch(e) { return 0; }\n    }\n    function updateReportTitle(n){\n        try {\n            var title = byId('ti-report-title');\n            if (!title) return;\n            if (typeof n === 'undefined' || n === null) n = currentContactsNum();\n            var txt = String(title.innerText || title.textContent || '')\n                .replace(\/\\s*[\\-\u2013\u2014]?\\s*(?:Contatti(?: main form)?|Totale contatti)\\s*:\\s*[0-9.]+\\s*$\/i, '')\n                .trim();\n            title.innerText = txt + ' - Totale contatti : ' + fmt(n || 0);\n        } catch(e) {}\n    }\n    function requestContactsRead(db, opts){\n        opts = opts || {};\n        db = String(db || selectedDb() || '').trim();\n        if (!db || db === 'NEW_DB') return Promise.resolve({contacts_num: currentContactsNum(), skipped:true});\n        try {\n            var fd = new FormData();\n            fd.append('action', 'ti_ai_track_contact_access');\n            fd.append('ti_action', 'ti_ai_track_contact_access');\n            fd.append('db', db);\n            fd.append('mode', 'read');\n            var url = window.tiAjaxUrl || window.tiUrl || window.location.href;\n            var p = window.fetchJsonSafe\n                ? window.fetchJsonSafe(url, {method:'POST', body:fd, credentials:'same-origin', tiNoLongProcessSignal:true})\n                : fetch(url, {method:'POST', body:fd, credentials:'same-origin'}).then(function(r){ return r.json(); });\n            return p.then(function(res){\n                var payload = res && res.success ? (res.data || {}) : {};\n                var n = typeof payload.contacts_num !== 'undefined' ? (parseInt(payload.contacts_num, 10) || 0) : currentContactsNum();\n                setLocalContactsNum(n, payload.field || 'Contacts_num');\n                if (opts.updateReport) updateReportTitle(n);\n                return payload;\n            }).catch(function(){\n                if (opts.updateReport) updateReportTitle(currentContactsNum());\n                return {contacts_num: currentContactsNum(), error:true};\n            });\n        } catch(e) {\n            if (opts.updateReport) updateReportTitle(currentContactsNum());\n            return Promise.resolve({contacts_num: currentContactsNum(), error:true});\n        }\n    }\n    function requestRoleLoginCompensate(db){\n        db = String(db || selectedDb() || '').trim();\n        if (!db || db === 'NEW_DB') return Promise.resolve(false);\n        var flagKey = 'ti_contact_counted_before_role_login_' + db;\n        var hadCount = false;\n        try { hadCount = sessionStorage.getItem(flagKey) === '1'; } catch(e) {}\n        if (!hadCount) return Promise.resolve(false);\n        try {\n            var fd = new FormData();\n            fd.append('action', 'ti_ai_track_contact_access');\n            fd.append('ti_action', 'ti_ai_track_contact_access');\n            fd.append('db', db);\n            fd.append('mode', 'role_login_compensate');\n            var url = window.tiAjaxUrl || window.tiUrl || window.location.href;\n            var fetcher = window.fetchJsonSafe ? window.fetchJsonSafe : function(u, init){ return fetch(u, init).then(function(r){ return r.json(); }); };\n            return fetcher(url, {method:'POST', body:fd, credentials:'same-origin', tiNoLongProcessSignal:true}).then(function(res){\n                var payload = res && res.success ? (res.data || {}) : {};\n                var n = typeof payload.contacts_num !== 'undefined' ? (parseInt(payload.contacts_num, 10) || 0) : currentContactsNum();\n                setLocalContactsNum(n, payload.field || 'Contacts_num');\n                updateReportTitle(n);\n                try { if (payload.decremented) sessionStorage.removeItem(flagKey); } catch(ignore) {}\n                return !!payload.decremented;\n            }).catch(function(){ return false; });\n        } catch(e) { return Promise.resolve(false); }\n    }\n\n    function refreshForReport(){\n        requestContactsRead(selectedDb(), {updateReport:true});\n        setTimeout(function(){ requestContactsRead(selectedDb(), {updateReport:true}); }, 600);\n    }\n    function removeStaleZeroMessages(){\n        try {\n            var roots = [];\n            ['ti-msgs','ti-chat','ti-ai-messages','ti-chat-body'].forEach(function(id){ var el = byId(id); if (el) roots.push(el); });\n            var root = roots.length ? roots[0] : (byId('ti-ai-outer') || document.body);\n            if (!root) return;\n            var nodes = Array.prototype.slice.call(root.querySelectorAll('div,li,p,span'));\n            var totalNodes = nodes.filter(function(el){ return \/Totale\\s+contatti\\s*:\\s*\/i.test(String(el.textContent || '')); });\n            var hasNonZero = totalNodes.some(function(el){ return \/Totale\\s+contatti\\s*:\\s*(?!0(?:\\D|$))[0-9.]+\/i.test(String(el.textContent || '')); });\n            totalNodes.forEach(function(el){\n                var txt = String(el.textContent || '').replace(\/\\s+\/g,' ').trim();\n                if (\/^Totale\\s+contatti\\s*:\\s*0\\s*$\/i.test(txt) || (hasNonZero && \/Totale\\s+contatti\\s*:\\s*0(?:\\D|$)\/i.test(txt))) {\n                    var box = el.closest && el.closest('.ti-msg,.msg,.message,.chat-message,li,div');\n                    try { (box || el).remove(); } catch(ignore) { if (box || el) (box || el).style.display = 'none'; }\n                }\n            });\n        } catch(e) {}\n    }\n    function install(){\n        if (typeof window.updCtx === 'function' && !window.updCtx.__tiContact416ReadOnly) {\n            var oldUpd = window.updCtx;\n            window.updCtx = function(){\n                var ret = oldUpd.apply(this, arguments);\n                setTimeout(function(){ requestContactsRead(selectedDb(), {updateReport:true}); removeStaleZeroMessages(); }, 900);\n                return ret;\n            };\n            window.updCtx.__tiContact416ReadOnly = true;\n        }\n        if (typeof window.renderActivityReport === 'function' && !window.renderActivityReport.__tiContact416ReadOnly) {\n            var oldRender = window.renderActivityReport;\n            window.renderActivityReport = function(){ var ret = oldRender.apply(this, arguments); updateReportTitle(); return ret; };\n            window.renderActivityReport.__tiContact416ReadOnly = true;\n        }\n        if (typeof window.openActivityReport === 'function' && !window.openActivityReport.__tiContact416ReadOnly) {\n            var oldOpen = window.openActivityReport;\n            window.openActivityReport = function(){ var ret = oldOpen.apply(this, arguments); refreshForReport(); return ret; };\n            window.openActivityReport.__tiContact416ReadOnly = true;\n        }\n        try {\n            var d = byId('ti-ditta');\n            if (d && !d.__tiContact416ReadOnlyChange) {\n                d.__tiContact416ReadOnlyChange = true;\n                d.addEventListener('change', function(){ setTimeout(function(){ requestContactsRead(selectedDb(), {updateReport:true}); removeStaleZeroMessages(); }, 900); }, false);\n            }\n        } catch(e) {}\n    }\n\n    document.addEventListener('DOMContentLoaded', function(){ install(); setTimeout(function(){ requestContactsRead(selectedDb(), {updateReport:true}); removeStaleZeroMessages(); }, 1200); }, false);\n    setTimeout(function(){ install(); requestContactsRead(selectedDb(), {updateReport:true}); removeStaleZeroMessages(); }, 1000);\n    setTimeout(removeStaleZeroMessages, 1800);\n\n    \/\/ Compatibilita con chiamate storiche: da 30.9.416 queste funzioni leggono soltanto il dato reale.\n    window.tiTrackCompanyContactAccess375 = function(db){ return requestContactsRead(db || selectedDb(), {updateReport:true}); };\n    window.tiReadCompanyContactAccess375 = function(db){ return requestContactsRead(db || selectedDb(), {updateReport:true}); };\n    window.tiCompensateContactAfterRoleLogin377 = requestRoleLoginCompensate;\n    window.tiUpdateActivityReportContactsTitle375 = function(){ refreshForReport(); };\n    window.tiCleanupDuplicateContactsMessages416 = removeStaleZeroMessages;\n    try {\n        if (!window.__tiContactLoginCompensate416Installed) {\n            window.__tiContactLoginCompensate416Installed = true;\n            var prevReload = window.tiReloadAfterSuccessfulLogin;\n            window.tiReloadAfterSuccessfulLogin = function(){\n                var db = selectedDb();\n                var done = false;\n                function go(){ if (done) return; done = true; if (typeof prevReload === 'function') prevReload(); else window.location.reload(); }\n                try { requestRoleLoginCompensate(db).then(go).catch(go); setTimeout(go, 900); } catch(e) { go(); }\n            };\n        }\n    } catch(e) {}\n})();\n<\/script>\n\n\n\n\n\n<script id=\"ti-plugin-audio-mic-fix-380\">\n(function(){\n    \"use strict\";\n    if (window.__tiPluginAudioMicFix380Installed) return;\n    window.__tiPluginAudioMicFix380Installed = true;\n\n    var audioCtx380 = null;\n    var audioUnlocked380 = false;\n    var speechPrimed380 = false;\n    var micRecognition380 = null;\n    var micTimer380 = null;\n    var lastError380 = '';\n\n    function byId(id){ return document.getElementById(id); }\n    function root(){ return byId('ti-ai-outer') || document.querySelector('.ti-ai-shop-service,[data-ti-shop-service]'); }\n    function muted(){ return !!(window.isMuted || window.muteState === 1 || window.muteState === 2); }\n    function alertSafe(msg){ try { if (window.tiAlert) window.tiAlert(msg); else alert(msg); } catch(e) { try { alert(msg); } catch(ignore){} } }\n    function secureOk(){ try { return !!(window.isSecureContext || location.protocol === 'https:' || location.hostname === 'localhost' || location.hostname === '127.0.0.1'); } catch(e) { return false; } }\n\n    function unlockAudio380(){\n        var ok = false;\n        lastError380 = '';\n        try {\n            var AC = window.AudioContext || window.webkitAudioContext;\n            if (AC) {\n                audioCtx380 = audioCtx380 || new AC();\n                if (audioCtx380.state === 'suspended' && audioCtx380.resume) {\n                    try {\n                        var pr = audioCtx380.resume();\n                        if (pr && typeof pr.catch === 'function') pr.catch(function(e){ lastError380 = e && e.message ? e.message : String(e || ''); });\n                    } catch(e) { lastError380 = e && e.message ? e.message : String(e || ''); }\n                }\n                try {\n                    var osc = audioCtx380.createOscillator();\n                    var gain = audioCtx380.createGain();\n                    gain.gain.value = 0.00001;\n                    osc.frequency.value = 880;\n                    osc.connect(gain);\n                    gain.connect(audioCtx380.destination);\n                    osc.start(0);\n                    osc.stop(audioCtx380.currentTime + 0.04);\n                } catch(ignore) {}\n                ok = true;\n            }\n        } catch(e) { lastError380 = e && e.message ? e.message : String(e || 'AudioContext non disponibile'); }\n        try {\n            if ('speechSynthesis' in window) {\n                window.speechSynthesis.resume();\n                if (!speechPrimed380) {\n                    var u = new SpeechSynthesisUtterance(' ');\n                    u.lang = (window.currLang === 'en') ? 'en-US' : 'it-IT';\n                    u.volume = 0.01;\n                    u.rate = 1;\n                    u.pitch = 1;\n                    window.speechSynthesis.speak(u);\n                    speechPrimed380 = true;\n                }\n                ok = true;\n            }\n        } catch(e) { lastError380 = e && e.message ? e.message : String(e || 'speechSynthesis non disponibile'); }\n        try {\n            var rr = root();\n            (rr ? rr.querySelectorAll('audio,video') : document.querySelectorAll('#ti-ai-outer audio,#ti-ai-outer video')).forEach(function(el){\n                try { el.muted = false; el.volume = 1; } catch(ignore) {}\n            });\n        } catch(e) {}\n        if (ok) audioUnlocked380 = true;\n        window.tiPluginAudioUnlocked380 = audioUnlocked380;\n        return ok;\n    }\n\n    window.tiEnsurePluginAudioUnlocked = unlockAudio380;\n    window.tiPluginAudioStatus = function(){\n        return {\n            version: '30.9.381',\n            audioUnlocked: audioUnlocked380,\n            audioContextState: audioCtx380 ? audioCtx380.state : 'not-created',\n            speechSynthesis: ('speechSynthesis' in window),\n            speechPrimed: speechPrimed380,\n            muted: muted(),\n            secureContext: secureOk(),\n            mediaDevices: !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),\n            speechRecognition: !!(window.SpeechRecognition || window.webkitSpeechRecognition),\n            rootFound: !!root(),\n            micBound: !!(byId('ti-mic') && byId('ti-mic').__tiMic380),\n            lastUnlockError: lastError380\n        };\n    };\n\n    if (typeof window.speakText === 'function' && !window.speakText.__tiAudio380) {\n        var prevSpeakText380 = window.speakText;\n        window.speakText = function(text){\n            if (!muted()) unlockAudio380();\n            return prevSpeakText380.apply(this, arguments);\n        };\n        window.speakText.__tiAudio380 = true;\n    }\n    var prevPrime380 = window.primeSpeech;\n    window.primeSpeech = function(){\n        return unlockAudio380() || (typeof prevPrime380 === 'function' ? prevPrime380.apply(this, arguments) : false);\n    };\n\n    function setMicUi(active){\n        var btn = byId('ti-mic');\n        if (!btn) return;\n        try {\n            btn.disabled = false;\n            btn.style.background = active ? '#b91c1c' : '#222';\n            btn.style.color = active ? '#fff' : '#ccc';\n            btn.textContent = active ? 'Mic...' : 'Mic';\n        } catch(e) {}\n    }\n    function startMic380(ev){\n        if (ev) { try { ev.preventDefault(); ev.stopPropagation(); } catch(e) {} }\n        unlockAudio380();\n        var msg = byId('ti-msg');\n        var send = byId('ti-send');\n        if (!secureOk()) { alertSafe('Microfono non disponibile. Apri la pagina in HTTPS e autorizza il microfono per questo sito.'); return false; }\n        if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) { alertSafe('Microfono non disponibile nel browser. Verifica i permessi del sito oppure usa Chrome, Edge o Safari aggiornato.'); return false; }\n        var SR = window.SpeechRecognition || window.webkitSpeechRecognition;\n        if (!SR) { alertSafe('Riconoscimento vocale non supportato dal browser. Usa Chrome o Edge quando possibile.'); return false; }\n        if (micRecognition380) {\n            try { micRecognition380.stop(); } catch(e) {}\n            micRecognition380 = null;\n            if (micTimer380) { clearTimeout(micTimer380); micTimer380 = null; }\n            setMicUi(false);\n            return false;\n        }\n        setMicUi(true);\n        navigator.mediaDevices.getUserMedia({audio:true}).then(function(stream){\n            try { stream.getTracks().forEach(function(t){ t.stop(); }); } catch(e) {}\n            var r = new SR();\n            micRecognition380 = r;\n            r.lang = (window.currLang === 'en') ? 'en-US' : 'it-IT';\n            r.continuous = false;\n            r.interimResults = false;\n            r.maxAlternatives = 1;\n            r.onresult = function(e){\n                try {\n                    var transcript = e && e.results && e.results[0] && e.results[0][0] ? e.results[0][0].transcript : '';\n                    if (transcript && msg) {\n                        msg.value = transcript;\n                        msg.dispatchEvent(new Event('input', {bubbles:true}));\n                        msg.dispatchEvent(new Event('change', {bubbles:true}));\n                    }\n                    if (transcript && send) setTimeout(function(){ try { send.click(); } catch(ignore){} }, 80);\n                } catch(ignore) {}\n            };\n            r.onerror = function(e){\n                var code = e && e.error ? e.error : 'errore';\n                var detail = 'Microfono non disponibile o non autorizzato.';\n                if (code === 'not-allowed' || code === 'service-not-allowed') detail = 'Microfono non autorizzato. Abilita il permesso microfono nelle impostazioni del browser per questo sito.';\n                else if (code === 'no-speech') detail = 'Non ho rilevato voce. Riprova parlando dopo aver premuto Mic.';\n                else if (code === 'audio-capture') detail = 'Nessun microfono rilevato dal browser.';\n                alertSafe(detail + ' Codice: ' + code);\n            };\n            r.onend = function(){ micRecognition380 = null; if (micTimer380) { clearTimeout(micTimer380); micTimer380 = null; } setMicUi(false); };\n            try { r.start(); } catch(e) { micRecognition380 = null; setMicUi(false); alertSafe('Impossibile avviare il microfono: ' + (e && e.message ? e.message : 'errore browser')); }\n            micTimer380 = setTimeout(function(){ try { if (micRecognition380) micRecognition380.stop(); } catch(e) {} setMicUi(false); }, 10000);\n        }).catch(function(e){\n            setMicUi(false);\n            alertSafe('Microfono non autorizzato o non disponibile. Verifica i permessi del browser per questo sito.' + (e && e.name ? ' Dettaglio: ' + e.name : ''));\n        });\n        return false;\n    }\n    window.tiStartPluginMic = startMic380;\n\n    function bindMic380(){\n        var btn = byId('ti-mic');\n        if (!btn) return;\n        btn.__tiMic380 = true;\n        btn.disabled = false;\n        btn.onclick = startMic380;\n    }\n    function bindMute380(){\n        var btn = byId('ti-mute-btn');\n        if (!btn || btn.__tiMuteUnlock380) return;\n        btn.__tiMuteUnlock380 = true;\n        btn.addEventListener('click', function(){ setTimeout(function(){ if (!muted()) unlockAudio380(); }, 30); }, false);\n    }\n    function bindGestures380(){\n        var rr = root();\n        if (!rr || rr.__tiAudioGesture380) return;\n        rr.__tiAudioGesture380 = true;\n        ['pointerdown','touchstart','mousedown','click','keydown'].forEach(function(evt){\n            rr.addEventListener(evt, function(){ if (!muted()) unlockAudio380(); }, {capture:true, passive:true});\n        });\n    }\n    function install380(){ bindGestures380(); bindMic380(); bindMute380(); }\n\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', install380);\n    install380();\n    setTimeout(install380, 100);\n    setTimeout(install380, 400);\n    setTimeout(install380, 1200);\n    setInterval(bindMic380, 2500);\n})();\n<\/script>\n\n\n<script id=\"ti-profile-batch-ai-fix-381\">\n(function(){\n    \"use strict\";\n    if (window.__tiProfileBatchAiFix381Installed) return;\n    window.__tiProfileBatchAiFix381Installed = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function esc(v){ return (window.escapeHtml ? window.escapeHtml(String(v == null ? '' : v)) : String(v == null ? '' : v).replace(\/[&<>'\"]\/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;',\"'\":'&#39;','\"':'&quot;'}[c];})); }\n    function resetNewProfileOnce(){\n        var ids = ['p-utente','p-user','p-pass','p-ragione_sociale','p-email','p-tel','p-ind','p-piva_vat','p-cod_fisc','p-ade','p-sesso','p-dato_1','p-dato_2','p-dato_3','p-nota','p-foto'];\n        ids.forEach(function(id){\n            var el = byId(id); if (!el) return;\n            try {\n                if (el.type === 'checkbox' || el.type === 'radio') el.checked = false;\n                else el.value = '';\n                el.disabled = false;\n                el.readOnly = false;\n                el.removeAttribute('readonly');\n                el.removeAttribute('disabled');\n                el.setAttribute('autocomplete', id === 'p-pass' ? 'new-password' : 'off');\n                if (window.forceWhiteEditStyle) window.forceWhiteEditStyle(el);\n            } catch(e) {}\n        });\n        ['p-com_mail','p-com_email','p-com_sms','p-com_tel'].forEach(function(id){ var el = byId(id); if (el) el.checked = true; });\n        var eta = byId('p-eta'); if (eta) { eta.value = ''; eta.disabled = false; eta.readOnly = false; }\n        var pass = byId('p-pass'); if (pass) { pass.required = true; pass.disabled = false; pass.readOnly = false; }\n        var user = byId('p-user'); if (user) {\n            user.required = true;\n            user.disabled = false;\n            user.readOnly = false;\n            user.removeAttribute('readonly');\n            user.removeAttribute('disabled');\n            user.style.setProperty('pointer-events','auto','important');\n            user.style.setProperty('user-select','text','important');\n            user.style.setProperty('-webkit-user-select','text','important');\n        }\n        var prev = byId('p-foto-preview'); if (prev) { prev.removeAttribute('src'); prev.style.display = 'none'; }\n    }\n\n    var legacyOpenProfile381 = window.openProfileModal;\n    window.openProfileModal = function(isNew){\n        var ditta = byId('ti-ditta');\n        if (!ditta || !ditta.value || ditta.value === 'NEW_DB') { if (window.tiAlert) window.tiAlert('Seleziona una ditta prima di registrarti.'); return false; }\n        if (isNew === true) {\n            var title = byId('lbl-profile-title'); if (title) title.innerText = 'Crea Profilo';\n            var lbl = byId('lbl-p-pass'); if (lbl) lbl.innerText = 'Password*';\n            var cancelBtn = byId('ti-profile-cancel-btn'); if (cancelBtn) cancelBtn.style.display = 'none';\n            resetNewProfileOnce();\n            if (window.openModal) window.openModal('ti-profile-ov');\n            setTimeout(function(){\n                var u = byId('p-user');\n                if (u) { u.readOnly = false; u.disabled = false; try { u.focus({preventScroll:true}); } catch(e) { try { u.focus(); } catch(e2) {} } }\n                if (window.bindProfileUsernameDuplicatePopup) window.bindProfileUsernameDuplicatePopup();\n            }, 80);\n            return false;\n        }\n        if (!window.tiUser) {\n            if (window.openLoginModalSafe) window.openLoginModalSafe();\n            else if (window.openModal) window.openModal('ti-login-ov');\n            return false;\n        }\n        if (typeof legacyOpenProfile381 === 'function') return legacyOpenProfile381.apply(this, arguments);\n        return false;\n    };\n\n    function bindUserArea381(){\n        var area = byId('ti-user-area');\n        if (!area || area.__tiUserArea381) return;\n        area.__tiUserArea381 = true;\n        area.onclick = function(ev){\n            if (ev) { ev.preventDefault(); ev.stopPropagation(); }\n            if (window.tiUser) window.openProfileModal(false);\n            else if (window.openLoginModalSafe) window.openLoginModalSafe();\n            else if (window.openModal) window.openModal('ti-login-ov');\n            return false;\n        };\n    }\n    bindUserArea381();\n    setTimeout(bindUserArea381, 500);\n\n    document.addEventListener('focusin', function(ev){\n        var el = ev.target;\n        if (!el || el.id !== 'p-user') return;\n        if (byId('ti-profile-ov') && !window.tiUser) {\n            try { el.readOnly = false; el.disabled = false; el.removeAttribute('readonly'); el.removeAttribute('disabled'); } catch(e) {}\n        }\n    }, true);\n\n    function ajaxConfig381(fd){\n        if (window.postFormDataJsonSafe) return window.postFormDataJsonSafe(window.tiUrl, fd, {tiNoLongProcessSignal:true, cache:'no-store'});\n        if (window.fetchJsonSafe) return window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd, tiNoLongProcessSignal:true, cache:'no-store'});\n        return fetch(window.tiUrl, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store'}).then(function(r){ return r.json(); });\n    }\n    function withTimeout381(promise, ms, label){\n        var tid;\n        var timeout = new Promise(function(_, reject){ tid = setTimeout(function(){ reject(new Error(label || 'Richiesta non completata nei tempi previsti.')); }, ms || 45000); });\n        return Promise.race([promise, timeout]).finally(function(){ clearTimeout(tid); });\n    }\n\n    window.runBatchAIGen = function(tableName) {\n        var dbSel = byId('ti-ditta');\n        var replyBox = byId('ti-conf-ai-reply');\n        if (!dbSel || !dbSel.value || dbSel.value === 'NEW_DB') { if (window.tiAlert) window.tiAlert('Seleziona prima una ditta.'); return; }\n        if (!replyBox) { if (window.tiAlert) window.tiAlert('Area risposta configurazione non disponibile.'); return; }\n        var photoDetail = ((byId('ti-conf-ai-photo-detail') && byId('ti-conf-ai-photo-detail').value) ? byId('ti-conf-ai-photo-detail').value : '').trim();\n        replyBox.style.display = 'block';\n        replyBox.innerHTML = 'Preparazione elenco record. Priorita a prodotti e servizi senza immagini specifiche.<br>Prompt base: attivita ditta + descrizione prodotto\/servizio.';\n        var initFd = window.buildAjaxFormData ? window.buildAjaxFormData('ti_ai_config_action', {db: dbSel.value, mode: 'batch_ai_images', tables: tableName, detail_prompt: photoDetail}) : new FormData();\n        if (!initFd.has('ti_action')) initFd.append('ti_action','ti_ai_config_action');\n        if (!initFd.has('action')) initFd.append('action','ti_ai_config_action');\n        initFd.set('db', dbSel.value); initFd.set('mode','batch_ai_images'); initFd.set('tables', tableName); initFd.set('detail_prompt', photoDetail);\n        if (window.clearLongAIProcess) window.clearLongAIProcess();\n        if (window.showProgressPopup) window.showProgressPopup('Generazione foto AI', 'Sto preparando l elenco dei record. Prompt base: attivita ditta e descrizione prodotto o servizio.', {startPercent:2, targetPercent:18, stepMs:350, cancellable:false});\n        withTimeout381(ajaxConfig381(initFd), 45000, 'Preparazione elenco record non completata: verifica connessione o riprova.').then(function(d){\n            if (!d || !d.success) {\n                if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                if (window.hideProgressPopup) window.hideProgressPopup(false, 'Errore preparazione batch');\n                replyBox.innerHTML = 'Errore: ' + esc(d && d.data && d.data.message ? d.data.message : 'Errore sconosciuto');\n                return;\n            }\n            var targets = (d.data && Array.isArray(d.data.targets)) ? d.data.targets : [];\n            if (!targets.length) {\n                if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                if (window.hideProgressPopup) window.hideProgressPopup(true, 'Nessun elemento da elaborare', {notifyUser:false, remindSave:false});\n                replyBox.innerHTML = 'Nessun record trovato nella tabella selezionata.';\n                return;\n            }\n            var index = 0, okCount = 0, logs = [], total = targets.length;\n            window.tiBatchAIGenState = {active:true, pendingConfirm:false, generated:[], db:dbSel.value, tableName:tableName, total:total, startedAt:Date.now()};\n            var withoutAssoc = (d.data && typeof d.data.without_associations !== 'undefined') ? parseInt(d.data.without_associations,10) : targets.filter(function(x){return !x.has_associations;}).length;\n            var withAssoc = (d.data && typeof d.data.with_associations !== 'undefined') ? parseInt(d.data.with_associations,10) : Math.max(0,total-withoutAssoc);\n            var detailMode = (d.data && d.data.detail_prompt_mode) ? String(d.data.detail_prompt_mode) : '';\n            var detailInfo = (photoDetail && detailMode === 'leaflet') ? '<br>Istruzione aggiuntiva riconosciuta: <b>foglietto illustrativo \/ materiale informativo<\/b>.' : (photoDetail ? '<br>Istruzione aggiuntiva: <b>' + esc(photoDetail) + '<\/b>.' : '');\n            replyBox.innerHTML = 'Generazione immagini AI pronta.<br>Prompt base: <b>attivita ditta + descrizione prodotto\/servizio<\/b>.' + detailInfo + '<br>Priorita: <b>' + withoutAssoc + '<\/b> prodotti\/servizi senza associazioni.<br>Successivi: <b>' + withAssoc + '<\/b> prodotti\/servizi gia con associazioni.';\n            var refreshDb = function(){\n                if (window.refreshCurrentConfigDb) return window.refreshCurrentConfigDb(dbSel.value);\n                var fdDb = window.buildAjaxFormData ? window.buildAjaxFormData('ti_ai_get_db_data', {db: dbSel.value}) : new FormData();\n                if (!fdDb.has('ti_action')) fdDb.append('ti_action','ti_ai_get_db_data');\n                if (!fdDb.has('action')) fdDb.append('action','ti_ai_get_db_data');\n                fdDb.set('db', dbSel.value);\n                return ajaxConfig381(fdDb).then(function(dbRes){ if (dbRes && dbRes.success && dbRes.data) { window.currentDbData = dbRes.data; if (window.renderConfig) window.renderConfig(); } }).catch(function(){});\n            };\n            var runNext = function(){\n                if (index >= total) {\n                    if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                    if (window.updateProgressPopup) window.updateProgressPopup(100, 'Generazione foto AI', 'Completamento e verifica salvataggio associazioni.');\n                    Promise.resolve(refreshDb()).finally(function(){\n                        if (window.hideProgressPopup) window.hideProgressPopup(true, 'Generazione foto AI completata', {remindSave:false, successMessage:'Attivita AI completata. Immagini associate e verificate: ' + okCount + ' su ' + total + '.'});\n                        replyBox.innerHTML = '\u2705 Generazione immagini AI completata.<br>Immagini associate e verificate: <b>' + okCount + '<\/b> \/ ' + total + '<br><br>' + logs.slice(0,40).join('<br>');\n                    });\n                    return;\n                }\n                var item = targets[index];\n                var currentStep = index + 1;\n                var pct = Math.max(5, Math.min(98, Math.round((index \/ total) * 100)));\n                var priorityText = item.has_associations ? 'Elemento gia con associazioni' : 'Priorita senza associazioni';\n                if (window.updateProgressPopup) window.updateProgressPopup(pct, 'Generazione foto AI', priorityText + '. Elaborazione ' + currentStep + ' \/ ' + total + ': ' + (item.name || 'Elemento'), (item.name || 'Elemento'));\n                replyBox.innerHTML = 'Generazione immagini AI in corso...<br>' + priorityText + '<br>Elaborazione ' + currentStep + ' \/ ' + total + ': <b>' + esc(item.name || 'Elemento') + '<\/b>';\n                var fd = window.buildAjaxFormData ? window.buildAjaxFormData('ti_ai_config_action', {db: dbSel.value, mode:'batch_ai_image_one', table:item.table || tableName, row_index:item.row_index, detail_prompt:photoDetail}) : new FormData();\n                if (!fd.has('ti_action')) fd.append('ti_action','ti_ai_config_action');\n                if (!fd.has('action')) fd.append('action','ti_ai_config_action');\n                fd.set('db', dbSel.value); fd.set('mode','batch_ai_image_one'); fd.set('table', item.table || tableName); fd.set('row_index', item.row_index); fd.set('detail_prompt', photoDetail);\n                ajaxConfig381(fd).then(function(one){\n                    var data = one && one.data ? one.data : {};\n                    if (one && one.success && data.generated) {\n                        okCount++;\n                        if (window.tiBatchAIGenState && Array.isArray(window.tiBatchAIGenState.generated) && (data.added_value || data.filename || data.url)) {\n                            window.tiBatchAIGenState.generated.push({table:data.table || item.table || tableName, row_index:(typeof data.row_index !== 'undefined') ? data.row_index : item.row_index, field:data.image_field || 'Immagine', value:data.added_value || data.filename || data.url, name:data.name || item.name || 'Elemento'});\n                        }\n                        logs.push('\u2705 ' + esc(data.name || item.name || 'Elemento') + ': ' + (data.fallback_logo ? 'foto non disponibile, usato logo ditta' : 'immagine generata e associazione salvata'));\n                    } else if (one && one.success && data.skipped) {\n                        logs.push('\u23ed\ufe0f ' + esc(data.name || item.name || 'Elemento') + ': immagine gia presente');\n                    } else {\n                        logs.push('\u274c ' + esc(data.name || item.name || 'Elemento') + ': ' + esc(data.message || 'errore'));\n                    }\n                    index++;\n                    setTimeout(runNext, 350);\n                }).catch(function(err){\n                    logs.push('\u274c ' + esc(item.name || 'Elemento') + ': ' + esc(err && err.message ? err.message : 'errore rete'));\n                    index++;\n                    setTimeout(runNext, 700);\n                });\n            };\n            runNext();\n        }).catch(function(err){\n            if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n            if (window.hideProgressPopup) window.hideProgressPopup(false, 'Errore batch immagini');\n            replyBox.innerHTML = 'Errore preparazione generazione foto AI: ' + esc(err && err.message ? err.message : 'errore rete');\n        });\n    };\n})();\n<\/script>\n\n\n<script id=\"ti-report-controls-charts-fields-382\">\n(function(){\n    \"use strict\";\n    if (window.__tiReportControlsChartsFields382Installed) return;\n    window.__tiReportControlsChartsFields382Installed = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function esc(v){\n        return String(v == null ? '' : v).replace(\/[&<>\"']\/g, function(c){\n            return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c];\n        });\n    }\n    function safeCols(){\n        var cols = Array.isArray(window.activityReportCols) ? window.activityReportCols.slice() : [];\n        if (!cols.length) cols = ['Username','Descrizione','Prezzo','Quantit\u00e0','Esito','Data','Ora','Stato','Totale'];\n        return cols;\n    }\n    function ensureStyle(){\n        if (byId('ti-report-controls-charts-fields-382-css')) return;\n        var st = document.createElement('style');\n        st.id = 'ti-report-controls-charts-fields-382-css';\n        st.textContent = ''+\n        '#ti-report-cnt .ti-report-toolbar{display:flex!important;visibility:visible!important;opacity:1!important;gap:8px!important;align-items:center!important;flex-wrap:wrap!important;overflow-x:auto!important;margin-bottom:8px!important;}'+\n        '#ti-report-cnt .ti-report-382-panel{display:block!important;visibility:visible!important;opacity:1!important;background:#0f172a!important;border:1px solid #334155!important;border-radius:10px!important;padding:8px!important;margin:8px 0!important;color:#e5e7eb!important;}'+\n        '#ti-report-cnt .ti-report-382-row{display:flex!important;gap:8px!important;align-items:center!important;flex-wrap:wrap!important;margin:5px 0!important;}'+\n        '#ti-report-cnt .ti-report-382-row label{display:inline-flex!important;gap:5px!important;align-items:center!important;font-size:12px!important;background:#111827!important;border:1px solid #334155!important;border-radius:999px!important;padding:4px 8px!important;}'+\n        '#ti-report-cnt .ti-report-382-btn{border:1px solid #475569!important;border-radius:8px!important;background:#1f2937!important;color:#fff!important;padding:6px 9px!important;font-weight:800!important;cursor:pointer!important;}'+\n        '#ti-report-cnt .ti-report-382-btn.is-active{background:#1d4ed8!important;border-color:#60a5fa!important;}'+\n        '#ti-report-cnt .ti-report-382-select{background:#111827!important;color:#fff!important;border:1px solid #475569!important;border-radius:8px!important;padding:5px 8px!important;}'+\n        '#ti-report-cnt .ti-chart-wrap{display:block!important;visibility:visible!important;opacity:1!important;min-height:360px!important;}'+\n        '#ti-report-cnt .ti-chart-wrap canvas{display:block!important;visibility:visible!important;opacity:1!important;background:#020617!important;max-width:100%!important;}';\n        document.head.appendChild(st);\n    }\n    function ensureState(){\n        if (!window.activityReportState) window.activityReportState = {};\n        if (!window.activityReportState.colFlags) window.activityReportState.colFlags = {};\n        if (!window.activityReportState.metricFlags) {\n            window.activityReportState.metricFlags = (typeof window.getDefaultActivityMetricFlags === 'function') ? window.getDefaultActivityMetricFlags() : {Prezzo:false, Quantit\u00e0:false, Totale:true};\n        }\n        if (!window.activityReportState.view) window.activityReportState.view = 'table';\n        if (!window.activityReportState.groupField) window.activityReportState.groupField = 'Username';\n    }\n    function setView(view){\n        ensureState();\n        window.activityReportState.view = view || 'table';\n        if (typeof window.saveActivityReportPrefs === 'function') window.saveActivityReportPrefs();\n        if (typeof window.renderActivityReport === 'function') window.renderActivityReport();\n    }\n    function setColumnVisible(col, checked){\n        ensureState();\n        window.activityReportState.colFlags[col] = !checked;\n        if (typeof window.saveActivityReportPrefs === 'function') window.saveActivityReportPrefs();\n        if (typeof window.renderActivityReport === 'function') window.renderActivityReport();\n    }\n    function setMetric(metric, checked){\n        ensureState();\n        window.activityReportState.metricFlags[metric] = !!checked;\n        var metrics = ['Prezzo','Quantit\u00e0','Totale'];\n        var has = metrics.some(function(m){ return !!window.activityReportState.metricFlags[m]; });\n        if (!has) window.activityReportState.metricFlags[metric] = true;\n        if (typeof window.saveActivityReportPrefs === 'function') window.saveActivityReportPrefs();\n        if (typeof window.renderActivityReport === 'function') window.renderActivityReport();\n    }\n    function setGroup(field){\n        ensureState();\n        window.activityReportState.groupField = field || 'Username';\n        if (typeof window.saveActivityReportPrefs === 'function') window.saveActivityReportPrefs();\n        if (typeof window.renderActivityReport === 'function') window.renderActivityReport();\n    }\n    window.tiReport382SetView = setView;\n    window.tiReport382SetColumnVisible = setColumnVisible;\n    window.tiReport382SetMetric = setMetric;\n    window.tiReport382SetGroup = setGroup;\n\n    function addControls(){\n        var box = byId('ti-report-cnt');\n        if (!box) return;\n        ensureStyle();\n        ensureState();\n        var old = byId('ti-report-controls-382');\n        if (old) old.remove();\n\n        var view = window.activityReportState.view || 'table';\n        var cols = safeCols();\n        var colFlags = window.activityReportState.colFlags || {};\n        var metricFlags = window.activityReportState.metricFlags || {};\n        var groupFields = (typeof window.getActivityAvailableGroupFields === 'function') ? window.getActivityAvailableGroupFields() : cols;\n        if (!Array.isArray(groupFields) || !groupFields.length) groupFields = cols;\n        var group = (typeof window.getActivitySelectedGroupField === 'function') ? window.getActivitySelectedGroupField() : (window.activityReportState.groupField || 'Username');\n        var html = '<div id=\"ti-report-controls-382\" class=\"ti-report-382-panel\">';\n        html += '<div class=\"ti-report-382-row\"><b>Vista report<\/b>';\n        [['table','Elenco dati'],['pie','Grafico torte'],['trend','Grafico andamento']].forEach(function(v){\n            html += '<button type=\"button\" class=\"ti-report-382-btn '+(view===v[0]?'is-active':'')+'\" onclick=\"window.tiReport382SetView(\\''+v[0]+'\\')\">'+esc(v[1])+'<\/button>';\n        });\n        html += '<\/div>';\n        html += '<div class=\"ti-report-382-row\"><b>Campi<\/b>';\n        cols.forEach(function(c){\n            var checked = !colFlags[c];\n            html += '<label><input type=\"checkbox\" '+(checked?'checked':'')+' onchange=\"window.tiReport382SetColumnVisible('+JSON.stringify(c).replace(\/\"\/g,'&quot;')+', this.checked)\"><span>'+esc(c)+'<\/span><\/label>';\n        });\n        html += '<\/div>';\n        if (view === 'pie' || view === 'trend') {\n            html += '<div class=\"ti-report-382-row\"><b>Grafico<\/b><span>Raggruppa per<\/span><select class=\"ti-report-382-select\" onchange=\"window.tiReport382SetGroup(this.value)\">';\n            groupFields.forEach(function(g){ html += '<option value=\"'+esc(g)+'\" '+(g===group?'selected':'')+'>'+esc(g)+'<\/option>'; });\n            html += '<\/select>';\n            ['Prezzo','Quantit\u00e0','Totale'].forEach(function(m){\n                html += '<label><input type=\"checkbox\" '+(metricFlags[m]?'checked':'')+' onchange=\"window.tiReport382SetMetric(\\''+m+'\\', this.checked)\"><span>'+esc(m)+'<\/span><\/label>';\n            });\n            html += '<\/div>';\n        }\n        html += '<\/div>';\n\n        var toolbar = box.querySelector('.ti-report-toolbar');\n        if (toolbar && toolbar.parentNode) toolbar.insertAdjacentHTML('afterend', html);\n        else box.insertAdjacentHTML('afterbegin', html);\n    }\n    function ensureChart(){\n        var box = byId('ti-report-cnt');\n        if (!box || !window.activityReportState) return;\n        var view = window.activityReportState.view || 'table';\n        if (view !== 'pie' && view !== 'trend') return;\n        var id = view === 'pie' ? 'ti-report-pie' : 'ti-report-trend';\n        if (!byId(id)) {\n            box.insertAdjacentHTML('beforeend', '<div class=\"ti-chart-wrap\"><canvas id=\"'+id+'\" width=\"980\" height=\"380\"><\/canvas><\/div>');\n        }\n        var rows = (typeof window.getFilteredActivityRows === 'function') ? window.getFilteredActivityRows(false) : [];\n        setTimeout(function(){\n            try {\n                if (view === 'pie' && typeof window.drawPie3D === 'function') window.drawPie3D(id, rows);\n                if (view === 'trend' && typeof window.drawTrend3D === 'function') window.drawTrend3D(id, rows);\n            } catch(e) {\n                try {\n                    var cv = byId(id), ctx = cv && cv.getContext ? cv.getContext('2d') : null;\n                    if (ctx) { ctx.fillStyle='#fff'; ctx.font='14px Arial'; ctx.fillText('Grafico non disponibile: '+(e && e.message ? e.message : 'errore'), 20, 30); }\n                } catch(ignore) {}\n            }\n        }, 80);\n    }\n    function enhance(){ addControls(); ensureChart(); }\n\n    if (typeof window.renderActivityReport === 'function' && !window.renderActivityReport.__ti382) {\n        var oldRender = window.renderActivityReport;\n        window.renderActivityReport = function(){\n            var ret = oldRender.apply(this, arguments);\n            setTimeout(enhance, 0);\n            setTimeout(enhance, 160);\n            return ret;\n        };\n        window.renderActivityReport.__ti382 = true;\n    }\n    if (typeof window.openActivityReport === 'function' && !window.openActivityReport.__ti382) {\n        var oldOpen = window.openActivityReport;\n        window.openActivityReport = function(){\n            var ret = oldOpen.apply(this, arguments);\n            setTimeout(enhance, 120);\n            setTimeout(enhance, 450);\n            return ret;\n        };\n        window.openActivityReport.__ti382 = true;\n    }\n    setTimeout(enhance, 500);\n})();\n<\/script>\n\n\n\n\n<script id=\"ti-auto-audio-unlock-383\">\n(function(){\n    \"use strict\";\n    if (window.__tiAutoAudioUnlock383Installed) return;\n    window.__tiAutoAudioUnlock383Installed = true;\n\n    var audioCtx = null;\n    var speechPrimed = false;\n    var unlocked = false;\n    var lastError = \"\";\n    var gestureCount = 0;\n\n    function byId(id){ return document.getElementById(id); }\n    function hasPluginRoot(){ return !!(byId('ti-ai-outer') || document.querySelector('.ti-ai-shop-service,[data-ti-shop-service]')); }\n    function isPluginPage(){\n        try {\n            return hasPluginRoot() || (document.body && document.body.classList && document.body.classList.contains('page-id-1165'));\n        } catch(e) { return false; }\n    }\n    function isMuted(){ return !!(window.isMuted || window.muteState === 1 || window.muteState === 2); }\n    function mediaEls(){\n        var root = byId('ti-ai-outer');\n        try { return root ? root.querySelectorAll('audio,video') : document.querySelectorAll('#ti-ai-outer audio,#ti-ai-outer video'); }\n        catch(e) { return []; }\n    }\n    function unlockAudio383(force){\n        if (!force && !isPluginPage()) return false;\n        var ok = false;\n        lastError = \"\";\n        try {\n            var AC = window.AudioContext || window.webkitAudioContext;\n            if (AC) {\n                audioCtx = audioCtx || new AC();\n                if (audioCtx.state === 'suspended' && audioCtx.resume) {\n                    try {\n                        var pr = audioCtx.resume();\n                        if (pr && typeof pr.catch === 'function') pr.catch(function(e){ lastError = e && e.message ? e.message : String(e || ''); });\n                    } catch(e) { lastError = e && e.message ? e.message : String(e || ''); }\n                }\n                try {\n                    var osc = audioCtx.createOscillator();\n                    var gain = audioCtx.createGain();\n                    gain.gain.value = 0.00001;\n                    osc.frequency.value = 880;\n                    osc.connect(gain);\n                    gain.connect(audioCtx.destination);\n                    osc.start(0);\n                    osc.stop(audioCtx.currentTime + 0.04);\n                } catch(ignore) {}\n                ok = true;\n            }\n        } catch(e) { lastError = e && e.message ? e.message : String(e || 'AudioContext non disponibile'); }\n        try {\n            if ('speechSynthesis' in window) {\n                window.speechSynthesis.resume();\n                if (!speechPrimed && !isMuted()) {\n                    var u = new SpeechSynthesisUtterance(' ');\n                    u.lang = (window.currLang === 'en') ? 'en-US' : 'it-IT';\n                    u.volume = 0;\n                    u.rate = 1;\n                    u.pitch = 1;\n                    window.speechSynthesis.speak(u);\n                    speechPrimed = true;\n                }\n                ok = true;\n            }\n        } catch(e) { lastError = e && e.message ? e.message : String(e || 'speechSynthesis non disponibile'); }\n        try { mediaEls().forEach(function(el){ try { el.muted = false; el.volume = 1; } catch(ignore) {} }); } catch(e) {}\n        if (ok) unlocked = true;\n        window.tiAudioAutoUnlocked383 = unlocked;\n        return ok;\n    }\n\n    window.tiEnsurePluginAudioUnlocked = function(){ return unlockAudio383(true); };\n    window.tiPluginAudioStatus = function(){\n        var prev = {};\n        try { if (typeof window.__tiPreviousPluginAudioStatus383 === 'function') prev = window.__tiPreviousPluginAudioStatus383() || {}; } catch(e) {}\n        return Object.assign({}, prev, {\n            version: '30.9.383',\n            audioUnlocked: unlocked,\n            audioAutoUnlock383: unlocked,\n            gestureCount383: gestureCount,\n            audioContextState383: audioCtx ? audioCtx.state : 'not-created',\n            speechSynthesis: ('speechSynthesis' in window),\n            speechPrimed383: speechPrimed,\n            muted: isMuted(),\n            pluginRootFound: hasPluginRoot(),\n            pageId1165: !!(document.body && document.body.classList && document.body.classList.contains('page-id-1165')),\n            lastUnlockError383: lastError\n        });\n    };\n\n    \/\/ Conserva eventuale diagnostica esistente, ma sovrascrive lo sblocco con il nuovo fallback.\n    try {\n        if (typeof window.tiPluginAudioStatus === 'function' && !window.__tiPreviousPluginAudioStatus383) {\n            window.__tiPreviousPluginAudioStatus383 = window.tiPluginAudioStatus;\n        }\n    } catch(e) {}\n\n    function onRealGesture(){\n        if (!isPluginPage()) return;\n        gestureCount++;\n        unlockAudio383(true);\n    }\n    function bindDocumentGestures(){\n        if (document.__tiAudioAutoGestures383) return;\n        document.__tiAudioAutoGestures383 = true;\n        ['pointerdown','touchstart','mousedown','click','keydown'].forEach(function(evt){\n            document.addEventListener(evt, onRealGesture, {capture:true, passive:true});\n        });\n    }\n    function wrapSpeakText(){\n        if (typeof window.speakText === 'function' && !window.speakText.__tiAutoAudio383) {\n            var prevSpeakText = window.speakText;\n            window.speakText = function(text){\n                if (!isMuted()) unlockAudio383(true);\n                return prevSpeakText.apply(this, arguments);\n            };\n            window.speakText.__tiAutoAudio383 = true;\n        }\n        if (typeof window.primeSpeech === 'function' && !window.primeSpeech.__tiAutoAudio383) {\n            var prevPrime = window.primeSpeech;\n            window.primeSpeech = function(){\n                return unlockAudio383(true) || prevPrime.apply(this, arguments);\n            };\n            window.primeSpeech.__tiAutoAudio383 = true;\n        }\n    }\n    function wrapNativeSpeak(){\n        try {\n            if (!('speechSynthesis' in window) || !window.speechSynthesis || !window.speechSynthesis.speak || window.speechSynthesis.speak.__tiAutoAudio383) return;\n            var nativeSpeak = window.speechSynthesis.speak.bind(window.speechSynthesis);\n            var wrappedSpeak = function(utterance){\n                if (!isMuted()) unlockAudio383(true);\n                return nativeSpeak(utterance);\n            };\n            wrappedSpeak.__tiAutoAudio383 = true;\n            window.speechSynthesis.speak = wrappedSpeak;\n        } catch(e) {}\n    }\n    function wrapMediaPlay(){\n        try {\n            if (!window.HTMLMediaElement || !HTMLMediaElement.prototype || !HTMLMediaElement.prototype.play || HTMLMediaElement.prototype.play.__tiAutoAudio383) return;\n            var nativePlay = HTMLMediaElement.prototype.play;\n            var wrappedPlay = function(){\n                unlockAudio383(true);\n                try { this.muted = false; this.volume = 1; } catch(e) {}\n                return nativePlay.apply(this, arguments);\n            };\n            wrappedPlay.__tiAutoAudio383 = true;\n            HTMLMediaElement.prototype.play = wrappedPlay;\n        } catch(e) {}\n    }\n    function bindMute(){\n        var btn = byId('ti-mute-btn');\n        if (!btn || btn.__tiAutoAudio383) return;\n        btn.__tiAutoAudio383 = true;\n        btn.addEventListener('click', function(){ setTimeout(function(){ if (!isMuted()) unlockAudio383(true); }, 40); }, false);\n    }\n    function install(){\n        if (!isPluginPage()) return;\n        bindDocumentGestures();\n        wrapSpeakText();\n        wrapNativeSpeak();\n        wrapMediaPlay();\n        bindMute();\n        \/\/ Tentativo non sonoro: se il browser lo permette, lo sblocco avviene subito; altrimenti avverra al primo gesto reale.\n        if (!isMuted()) unlockAudio383(false);\n    }\n\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', install);\n    install();\n    setTimeout(install, 250);\n    setTimeout(install, 900);\n    setTimeout(install, 1800);\n    if (typeof MutationObserver !== 'undefined') {\n        try { new MutationObserver(function(){ install(); }).observe(document.documentElement, {childList:true, subtree:true}); } catch(e) {}\n    }\n})();\n<\/script>\n\n\n\n<script id=\"ti-profile-registration-384\">\n(function(){\n    \"use strict\";\n    if (window.__tiProfileRegistration384Installed) return;\n    window.__tiProfileRegistration384Installed = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function visible(el){\n        if (!el) return false;\n        try {\n            var st = window.getComputedStyle ? getComputedStyle(el) : null;\n            return !(st && (st.display === 'none' || st.visibility === 'hidden' || st.opacity === '0')) && !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);\n        } catch(e) { return false; }\n    }\n    function inRegistration(){ return window.__tiProfileRegistrationMode384 === true; }\n    function setUserAreaVisibility384(){\n        var area = byId('ti-user-area');\n        if (!area) return;\n        var allow = !!window.tiUser || inRegistration();\n        area.style.setProperty('display', allow ? 'flex' : 'none', 'important');\n        if (!window.tiUser && !inRegistration()) {\n            var name = byId('ti-user-name');\n            var role = byId('ti-user-role');\n            if (name) name.textContent = 'Cliente\/Utente';\n            if (role) role.textContent = '';\n        }\n    }\n    function setEditableRegistrationFields384(){\n        ['p-utente','p-user','p-pass','p-email','p-ragione_sociale','p-ind','p-piva_vat','p-cod_fisc','p-ade','p-tel','p-sesso','p-eta','p-dato_1','p-dato_2','p-dato_3','p-nota'].forEach(function(id){\n            var el = byId(id); if (!el) return;\n            try {\n                el.disabled = false;\n                el.readOnly = false;\n                el.removeAttribute('disabled');\n                el.removeAttribute('readonly');\n                el.style.setProperty('pointer-events','auto','important');\n                el.style.setProperty('user-select','text','important');\n                el.style.setProperty('-webkit-user-select','text','important');\n                el.setAttribute('autocomplete', id === 'p-pass' ? 'new-password' : 'off');\n                if (id === 'p-user') {\n                    el.required = true;\n                    el.setAttribute('inputmode','text');\n                    el.setAttribute('autocapitalize','none');\n                    el.setAttribute('autocorrect','off');\n                    el.setAttribute('spellcheck','false');\n                }\n            } catch(e) {}\n        });\n        var pass = byId('p-pass'); if (pass) pass.required = true;\n        var title = byId('lbl-profile-title'); if (title) title.innerText = 'Crea Profilo Cliente\/Utente';\n        var lbl = byId('lbl-p-pass'); if (lbl) lbl.innerText = 'Password*';\n        var cancelBtn = byId('ti-profile-cancel-btn'); if (cancelBtn) cancelBtn.style.display = 'none';\n    }\n    function clearRegistrationFields384(){\n        ['p-utente','p-user','p-pass','p-email','p-ragione_sociale','p-ind','p-piva_vat','p-cod_fisc','p-ade','p-tel','p-sesso','p-eta','p-dato_1','p-dato_2','p-dato_3','p-nota','p-foto'].forEach(function(id){\n            var el = byId(id); if (!el) return;\n            try { if (el.type === 'checkbox' || el.type === 'radio') el.checked = false; else el.value = ''; } catch(e) {}\n        });\n        ['p-com_mail','p-com_email','p-com_sms','p-com_tel'].forEach(function(id){ var el = byId(id); if (el) el.checked = true; });\n        var prev = byId('p-foto-preview'); if (prev) { prev.removeAttribute('src'); prev.style.display = 'none'; }\n        setEditableRegistrationFields384();\n    }\n\n    var prevOpen384 = window.openProfileModal;\n    window.openProfileModal = function(isNew){\n        if (isNew === true) {\n            var ditta = byId('ti-ditta');\n            if (!ditta || !ditta.value || ditta.value === 'NEW_DB' || ditta.value === '') { if (window.tiAlert) window.tiAlert('Seleziona una ditta prima di registrarti.'); return false; }\n            window.__tiProfileRegistrationMode384 = true;\n            clearRegistrationFields384();\n            if (typeof prevOpen384 === 'function') prevOpen384.call(this, true);\n            else if (window.openModal) window.openModal('ti-profile-ov');\n            setTimeout(function(){ window.__tiProfileRegistrationMode384 = true; setEditableRegistrationFields384(); setUserAreaVisibility384(); var u = byId('p-user'); if (u) { try { u.focus({preventScroll:true}); } catch(e) { try { u.focus(); } catch(e2) {} } } }, 60);\n            setTimeout(function(){ setEditableRegistrationFields384(); setUserAreaVisibility384(); }, 250);\n            return false;\n        }\n        if (!window.tiUser) {\n            window.__tiProfileRegistrationMode384 = false;\n            setUserAreaVisibility384();\n            if (window.openLoginModalSafe) window.openLoginModalSafe();\n            else if (window.openModal) window.openModal('ti-login-ov');\n            return false;\n        }\n        window.__tiProfileRegistrationMode384 = false;\n        return (typeof prevOpen384 === 'function') ? prevOpen384.apply(this, arguments) : false;\n    };\n\n    var prevClose384 = window.closeModal;\n    if (typeof prevClose384 === 'function' && !prevClose384.__tiProfile384) {\n        window.closeModal = function(id){\n            var ret = prevClose384.apply(this, arguments);\n            if (id === 'ti-profile-ov') {\n                window.__tiProfileRegistrationMode384 = false;\n                setTimeout(setUserAreaVisibility384, 40);\n            }\n            return ret;\n        };\n        window.closeModal.__tiProfile384 = true;\n    }\n\n    function bindRegistrationButton384(){\n        try {\n            document.querySelectorAll('button').forEach(function(btn){\n                var txt = String(btn.textContent || '').trim().toLowerCase();\n                if (txt.indexOf('registrati') === -1) return;\n                if (btn.__tiReg384) return;\n                btn.__tiReg384 = true;\n                btn.addEventListener('click', function(ev){\n                    window.__tiProfileRegistrationMode384 = true;\n                    setTimeout(function(){ setEditableRegistrationFields384(); setUserAreaVisibility384(); }, 80);\n                }, true);\n            });\n        } catch(e) {}\n    }\n    function bindUserArea384(){\n        var area = byId('ti-user-area');\n        if (!area) return;\n        area.onclick = function(ev){\n            if (ev) { ev.preventDefault(); ev.stopPropagation(); }\n            if (window.tiUser) return window.openProfileModal(false);\n            if (inRegistration() && visible(byId('ti-profile-ov'))) return window.openProfileModal(true);\n            return false;\n        };\n    }\n    document.addEventListener('focusin', function(ev){\n        var el = ev && ev.target;\n        if (!el) return;\n        if (inRegistration() && \/^p-\/.test(el.id || '')) setEditableRegistrationFields384();\n    }, true);\n    document.addEventListener('input', function(ev){\n        var el = ev && ev.target;\n        if (el && el.id === 'p-user' && inRegistration()) setEditableRegistrationFields384();\n    }, true);\n\n    function tick384(){ bindRegistrationButton384(); bindUserArea384(); setUserAreaVisibility384(); }\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', tick384);\n    tick384();\n    setTimeout(tick384, 200);\n    setTimeout(tick384, 800);\n    setTimeout(tick384, 1600);\n})();\n<\/script>\n\n\n\n<script id=\"ti-registration-start-385\">\n(function(){\n    \"use strict\";\n    if (window.__tiRegistrationStart385Installed) return;\n    window.__tiRegistrationStart385Installed = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function setImp(el, prop, val){ try { if (el) el.style.setProperty(prop, val, 'important'); } catch(e) {} }\n    function selectedDb(){ var d = byId('ti-ditta') || byId('l-db') || byId('l-db-hidden'); return d ? String(d.value || '').trim() : ''; }\n    function hideLogin(){\n        var ov = byId('ti-login-ov');\n        if (!ov) return;\n        setImp(ov, 'display', 'none');\n        setImp(ov, 'visibility', 'hidden');\n        setImp(ov, 'opacity', '0');\n        setImp(ov, 'pointer-events', 'none');\n        try { ov.setAttribute('aria-hidden','true'); } catch(e) {}\n    }\n    function showProfile(){\n        var ov = byId('ti-profile-ov');\n        if (!ov) return;\n        setImp(ov, 'display', 'flex');\n        setImp(ov, 'visibility', 'visible');\n        setImp(ov, 'opacity', '1');\n        setImp(ov, 'pointer-events', 'auto');\n        setImp(ov, 'z-index', '2147483647');\n        try { ov.removeAttribute('aria-hidden'); ov.classList.add('ti-modal-active','ti-ov-active','is-active','show'); } catch(e) {}\n        try { if (window.preparePluginModal) window.preparePluginModal(ov); } catch(e) {}\n        try { if (window.updatePluginScrollLock) window.updatePluginScrollLock(); } catch(e) {}\n    }\n    function enableProfileInputs(){\n        var ids = ['p-utente','p-user','p-pass','p-email','p-ragione_sociale','p-ind','p-piva_vat','p-cod_fisc','p-ade','p-tel','p-sesso','p-eta','p-dato_1','p-dato_2','p-dato_3','p-nota','p-foto'];\n        ids.forEach(function(id){\n            var el = byId(id); if (!el) return;\n            try {\n                el.disabled = false;\n                el.readOnly = false;\n                el.removeAttribute('disabled');\n                el.removeAttribute('readonly');\n                el.style.setProperty('pointer-events','auto','important');\n                el.style.setProperty('user-select','text','important');\n                el.style.setProperty('-webkit-user-select','text','important');\n                el.setAttribute('autocomplete', id === 'p-pass' ? 'new-password' : 'off');\n                el.setAttribute('autocorrect','off');\n                el.setAttribute('spellcheck','false');\n                if (id === 'p-user') {\n                    el.required = true;\n                    el.setAttribute('inputmode','text');\n                    el.setAttribute('autocapitalize','none');\n                }\n                if (window.forceWhiteEditStyle) window.forceWhiteEditStyle(el);\n            } catch(e) {}\n        });\n        var pass = byId('p-pass'); if (pass) pass.required = true;\n        var user = byId('p-user'); if (user) user.required = true;\n    }\n    function clearProfileFields(){\n        var ids = ['p-utente','p-user','p-pass','p-email','p-ragione_sociale','p-ind','p-piva_vat','p-cod_fisc','p-ade','p-tel','p-sesso','p-eta','p-dato_1','p-dato_2','p-dato_3','p-nota','p-foto'];\n        ids.forEach(function(id){\n            var el = byId(id); if (!el) return;\n            try { if (el.type === 'checkbox' || el.type === 'radio') el.checked = false; else el.value = ''; } catch(e) {}\n        });\n        ['p-com_mail','p-com_email','p-com_sms','p-com_tel'].forEach(function(id){ var el = byId(id); if (el) el.checked = true; });\n        var prev = byId('p-foto-preview'); if (prev) { try { prev.removeAttribute('src'); prev.style.display = 'none'; } catch(e) {} }\n    }\n    function setProfileRegistrationLayout(){\n        window.__tiProfileRegistrationMode384 = true;\n        window.__tiProfileRegistrationMode385 = true;\n        var title = byId('lbl-profile-title'); if (title) title.innerText = 'Crea Profilo Cliente\/Utente';\n        var passLbl = byId('lbl-p-pass'); if (passLbl) passLbl.innerText = 'Password*';\n        var cancelBtn = byId('ti-profile-cancel-btn'); if (cancelBtn) cancelBtn.style.display = 'none';\n        var area = byId('ti-user-area'); if (area) area.style.setProperty('display','flex','important');\n        var name = byId('ti-user-name'); if (name) name.textContent = 'Cliente\/Utente';\n        var role = byId('ti-user-role'); if (role) role.textContent = 'Registrazione';\n        enableProfileInputs();\n    }\n\n    window.tiStartUserRegistration385 = function(ev){\n        if (ev) {\n            try { ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); } catch(e) {}\n        }\n        var db = selectedDb();\n        if (!db || db === 'NEW_DB') { if (window.tiAlert) window.tiAlert('Seleziona una ditta prima di registrarti.'); return false; }\n        window.__tiProfileRegistrationMode384 = true;\n        window.__tiProfileRegistrationMode385 = true;\n        hideLogin();\n        clearProfileFields();\n        setProfileRegistrationLayout();\n        try { if (window.openModal) window.openModal('ti-profile-ov'); } catch(e) {}\n        showProfile();\n        setTimeout(function(){ setProfileRegistrationLayout(); showProfile(); var u = byId('p-user'); if (u) { try { u.focus({preventScroll:true}); } catch(e) { try { u.focus(); } catch(e2) {} } } }, 40);\n        setTimeout(function(){ setProfileRegistrationLayout(); showProfile(); }, 180);\n        setTimeout(function(){ setProfileRegistrationLayout(); showProfile(); }, 500);\n        try { if (window.bindProfileUsernameDuplicatePopup) window.bindProfileUsernameDuplicatePopup(); } catch(e) {}\n        return false;\n    };\n\n    function isRegisterButton(el){\n        if (!el) return false;\n        var btn = el.closest ? el.closest('button,a,input[type=\"button\"],input[type=\"submit\"]') : null;\n        if (!btn) return false;\n        var txt = String(btn.textContent || btn.value || '').toLowerCase();\n        return txt.indexOf('registrati') !== -1 || txt.indexOf('register') !== -1 || btn.id === 'ti-register-user-btn';\n    }\n    document.addEventListener('click', function(ev){\n        if (!isRegisterButton(ev.target)) return;\n        window.tiStartUserRegistration385(ev);\n    }, true);\n\n    function bindButtons(){\n        try {\n            document.querySelectorAll('button,a,input[type=\"button\"],input[type=\"submit\"]').forEach(function(btn){\n                var txt = String(btn.textContent || btn.value || '').toLowerCase();\n                if (txt.indexOf('registrati') === -1 && txt.indexOf('register') === -1 && btn.id !== 'ti-register-user-btn') return;\n                btn.setAttribute('data-ti-registration-button','1');\n                btn.onclick = function(ev){ return window.tiStartUserRegistration385(ev || window.event); };\n            });\n        } catch(e) {}\n    }\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bindButtons);\n    bindButtons();\n    setTimeout(bindButtons, 250);\n    setTimeout(bindButtons, 900);\n    setTimeout(bindButtons, 1800);\n    if (typeof MutationObserver !== 'undefined') {\n        try { new MutationObserver(function(){ bindButtons(); }).observe(document.documentElement, {childList:true, subtree:true}); } catch(e) {}\n    }\n})();\n<\/script>\n\n\n<script id=\"ti-batch-ai-timeout-386\">\n(function(){\n    \"use strict\";\n    if (window.__tiBatchAiTimeout386Installed) return;\n    window.__tiBatchAiTimeout386Installed = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function esc(v){\n        return (window.escapeHtml ? window.escapeHtml(String(v == null ? '' : v)) : String(v == null ? '' : v).replace(\/[&<>'\"]\/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;',\"'\":'&#39;','\"':'&quot;'}[c];}));\n    }\n    function buildFd(action, data){\n        var fd = window.buildAjaxFormData ? window.buildAjaxFormData(action, data || {}) : new FormData();\n        if (!fd.has('ti_action')) fd.append('ti_action', action);\n        if (!fd.has('action')) fd.append('action', action);\n        Object.keys(data || {}).forEach(function(k){ fd.set(k, data[k]); });\n        return fd;\n    }\n    function ajaxWithTimeout(fd, ms, label){\n        ms = ms || 90000;\n        var controller = null;\n        var timer = null;\n        var opts = {method:'POST', body:fd, credentials:'same-origin', cache:'no-store'};\n        try {\n            if (window.AbortController) {\n                controller = new AbortController();\n                opts.signal = controller.signal;\n            }\n        } catch(e) {}\n        return new Promise(function(resolve, reject){\n            timer = setTimeout(function(){\n                try { if (controller) controller.abort(); } catch(e) {}\n                reject(new Error(label || 'Richiesta non completata nei tempi previsti.'));\n            }, ms);\n            fetch(window.tiUrl, opts).then(function(r){\n                if (!r.ok) throw new Error('HTTP ' + r.status);\n                return r.json();\n            }).then(resolve).catch(function(err){\n                if (err && err.name === 'AbortError') reject(new Error(label || 'Richiesta interrotta per timeout.'));\n                else reject(err);\n            }).finally(function(){ clearTimeout(timer); });\n        });\n    }\n    function shouldCancel(){\n        try { return !!(window.isLongAIProcessCancelled && window.isLongAIProcessCancelled()); } catch(e) { return false; }\n    }\n    function clearCancel(){ try { if (window.clearLongAIProcess) window.clearLongAIProcess(); } catch(e) {} }\n\n    window.runBatchAIGen = function(tableName) {\n        var dbSel = byId('ti-ditta');\n        var replyBox = byId('ti-conf-ai-reply');\n        if (!dbSel || !dbSel.value || dbSel.value === 'NEW_DB') { if (window.tiAlert) window.tiAlert('Seleziona prima una ditta.'); return; }\n        if (!replyBox) { if (window.tiAlert) window.tiAlert('Area risposta configurazione non disponibile.'); return; }\n        var photoDetail = ((byId('ti-conf-ai-photo-detail') && byId('ti-conf-ai-photo-detail').value) ? byId('ti-conf-ai-photo-detail').value : '').trim();\n        replyBox.style.display = 'block';\n        replyBox.innerHTML = 'Preparazione elenco record. Priorita a prodotti e servizi senza immagini specifiche.<br>Prompt base: attivita ditta + descrizione prodotto\/servizio.';\n        clearCancel();\n        if (window.showProgressPopup) window.showProgressPopup('Generazione foto AI', 'Sto preparando l elenco dei record. Prompt base: attivita ditta e descrizione prodotto o servizio.', {startPercent:2, targetPercent:18, stepMs:350, cancellable:true});\n\n        var initFd = buildFd('ti_ai_config_action', {db:dbSel.value, mode:'batch_ai_images', tables:tableName, detail_prompt:photoDetail});\n        ajaxWithTimeout(initFd, 45000, 'Preparazione elenco record non completata entro 45 secondi. Riprova o riduci il numero di record.').then(function(d){\n            if (!d || !d.success) {\n                if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                if (window.hideProgressPopup) window.hideProgressPopup(false, 'Errore preparazione batch');\n                replyBox.innerHTML = 'Errore preparazione generazione foto AI: ' + esc(d && d.data && d.data.message ? d.data.message : 'Errore sconosciuto');\n                return;\n            }\n            var targets = (d.data && Array.isArray(d.data.targets)) ? d.data.targets : [];\n            if (!targets.length) {\n                if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                if (window.hideProgressPopup) window.hideProgressPopup(true, 'Nessun elemento da elaborare', {notifyUser:false, remindSave:false});\n                replyBox.innerHTML = 'Nessun record trovato nella tabella selezionata.';\n                return;\n            }\n            var index = 0;\n            var okCount = 0;\n            var skipCount = 0;\n            var failCount = 0;\n            var logs = [];\n            var total = targets.length;\n            var itemTimeoutMs = 90000;\n            window.tiBatchAIGenState = {active:true, pendingConfirm:false, generated:[], db:dbSel.value, tableName:tableName, total:total, startedAt:Date.now(), timeout_ms:itemTimeoutMs};\n            var withoutAssoc = (d.data && typeof d.data.without_associations !== 'undefined') ? parseInt(d.data.without_associations,10) : targets.filter(function(x){return !x.has_associations;}).length;\n            var withAssoc = (d.data && typeof d.data.with_associations !== 'undefined') ? parseInt(d.data.with_associations,10) : Math.max(0,total-withoutAssoc);\n            var detailMode = (d.data && d.data.detail_prompt_mode) ? String(d.data.detail_prompt_mode) : '';\n            var detailInfo = (photoDetail && detailMode === 'leaflet') ? '<br>Istruzione aggiuntiva riconosciuta: <b>foglietto illustrativo \/ materiale informativo<\/b>.' : (photoDetail ? '<br>Istruzione aggiuntiva: <b>' + esc(photoDetail) + '<\/b>.' : '');\n            replyBox.innerHTML = 'Generazione immagini AI pronta.<br>Prompt base: <b>attivita ditta + descrizione prodotto\/servizio<\/b>.' + detailInfo + '<br>Priorita: <b>' + withoutAssoc + '<\/b> prodotti\/servizi senza associazioni.<br>Successivi: <b>' + withAssoc + '<\/b> prodotti\/servizi gia con associazioni.<br><span style=\"color:#facc15;\">Ogni record ha timeout controllato: se un elemento si blocca viene saltato e il batch prosegue.<\/span>';\n\n            function refreshDb(){\n                if (window.refreshCurrentConfigDb) return Promise.resolve(window.refreshCurrentConfigDb(dbSel.value)).catch(function(){});\n                var fdDb = buildFd('ti_ai_get_db_data', {db:dbSel.value});\n                return ajaxWithTimeout(fdDb, 30000, 'Rilettura database non completata').then(function(dbRes){\n                    if (dbRes && dbRes.success && dbRes.data) { window.currentDbData = dbRes.data; if (window.renderConfig) window.renderConfig(); }\n                }).catch(function(){});\n            }\n            function finish(){\n                if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                if (window.updateProgressPopup) window.updateProgressPopup(100, 'Generazione foto AI', 'Completamento e verifica salvataggio associazioni.');\n                Promise.resolve(refreshDb()).finally(function(){\n                    if (window.hideProgressPopup) window.hideProgressPopup(true, 'Generazione foto AI completata', {remindSave:false, successMessage:'Attivita AI completata. Immagini associate e verificate: ' + okCount + ' su ' + total + '. Errori\/saltati: ' + failCount + '.'});\n                    replyBox.innerHTML = '\u2705 Generazione immagini AI completata.<br>Immagini associate e verificate: <b>' + okCount + '<\/b> \/ ' + total + '<br>Elementi gia presenti\/saltati: <b>' + skipCount + '<\/b><br>Errori o timeout: <b>' + failCount + '<\/b><br><br>' + logs.slice(0,80).join('<br>');\n                });\n            }\n            function runNext(){\n                if (shouldCancel()) {\n                    if (window.tiBatchAIGenState && window.tiBatchAIGenState.pendingConfirm) return;\n                    replyBox.innerHTML = 'Generazione foto AI interrotta dall utente. Immagini gia generate mantenute: ' + okCount + '. Errori\/saltati: ' + failCount + '.';\n                    if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                    clearCancel();\n                    return;\n                }\n                if (index >= total) { finish(); return; }\n                var item = targets[index] || {};\n                var currentStep = index + 1;\n                var pct = Math.max(5, Math.min(98, Math.round(((index + 0.1) \/ total) * 100)));\n                var itemName = item.name || 'Elemento';\n                var priorityText = item.has_associations ? 'Elemento gia con associazioni' : 'Priorita senza associazioni';\n                if (window.updateProgressPopup) window.updateProgressPopup(pct, 'Generazione foto AI', priorityText + '. Elaborazione ' + currentStep + ' \/ ' + total + ': ' + itemName, itemName);\n                replyBox.innerHTML = 'Generazione immagini AI in corso...<br>' + priorityText + '<br>Elaborazione ' + currentStep + ' \/ ' + total + ': <b>' + esc(itemName) + '<\/b><br><span style=\"color:#94a3b8;\">Timeout massimo per questo record: ' + Math.round(itemTimeoutMs\/1000) + ' secondi.<\/span>';\n                var fd = buildFd('ti_ai_config_action', {db:dbSel.value, mode:'batch_ai_image_one', table:item.table || tableName, row_index:item.row_index, detail_prompt:photoDetail});\n                ajaxWithTimeout(fd, itemTimeoutMs, 'Timeout su record: ' + itemName).then(function(one){\n                    var data = one && one.data ? one.data : {};\n                    if (one && one.success && data.generated) {\n                        okCount++;\n                        if (window.tiBatchAIGenState && Array.isArray(window.tiBatchAIGenState.generated) && (data.added_value || data.filename || data.url)) {\n                            window.tiBatchAIGenState.generated.push({table:data.table || item.table || tableName, row_index:(typeof data.row_index !== 'undefined') ? data.row_index : item.row_index, field:data.image_field || 'Immagine', value:data.added_value || data.filename || data.url, name:data.name || itemName});\n                        }\n                        logs.push('\u2705 ' + esc(data.name || itemName) + ': ' + (data.fallback_logo ? 'foto non disponibile, usato logo ditta' : 'immagine generata e associazione salvata'));\n                    } else if (one && one.success && data.skipped) {\n                        skipCount++;\n                        logs.push('\u23ed\ufe0f ' + esc(data.name || itemName) + ': immagine gia presente');\n                    } else {\n                        failCount++;\n                        logs.push('\u274c ' + esc(data.name || itemName) + ': ' + esc(data.message || 'errore generazione'));\n                    }\n                }).catch(function(err){\n                    failCount++;\n                    logs.push('\u23f1\ufe0f ' + esc(itemName) + ': ' + esc(err && err.message ? err.message : 'timeout o errore rete') + '. Record saltato, proseguo con il successivo.');\n                }).finally(function(){\n                    index++;\n                    setTimeout(runNext, 250);\n                });\n            }\n            runNext();\n        }).catch(function(err){\n            if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n            if (window.hideProgressPopup) window.hideProgressPopup(false, 'Errore batch immagini');\n            replyBox.innerHTML = 'Errore preparazione generazione foto AI: ' + esc(err && err.message ? err.message : 'errore rete');\n        });\n    };\n})();\n<\/script>\n\n\n<script id=\"ti-batch-ai-stable-393\">\n(function(){\n    \"use strict\";\n    window.__tiBatchAiStable393Installed = true;\n    function byId(id){ return document.getElementById(id); }\n    function esc(v){\n        return (window.escapeHtml ? window.escapeHtml(String(v == null ? '' : v)) : String(v == null ? '' : v).replace(\/[&<>'\"]\/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;',\"'\":'&#39;','\"':'&quot;'}[c];}));\n    }\n    function buildFd(action, data){\n        var fd = window.buildAjaxFormData ? window.buildAjaxFormData(action, data || {}) : new FormData();\n        if (!fd.has('ti_action')) fd.append('ti_action', action);\n        if (!fd.has('action')) fd.append('action', action);\n        Object.keys(data || {}).forEach(function(k){ fd.set(k, data[k]); });\n        return fd;\n    }\n    function postJson(fd, ms, label){\n        ms = ms || 180000;\n        var work = function(){\n            if (window.postFormDataJsonSafe) {\n                return window.postFormDataJsonSafe(window.tiUrl || window.location.href, fd, {tiNoLongProcessSignal:true, cache:'no-store'});\n            }\n            if (window.fetchJsonSafe) {\n                return window.fetchJsonSafe(window.tiUrl || window.location.href, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store', tiNoLongProcessSignal:true});\n            }\n            return fetch(window.tiUrl || window.location.href, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store'}).then(function(r){ return r.text().then(function(raw){ if(!r.ok) throw new Error('HTTP ' + r.status + ': ' + raw.slice(0,180)); try { return JSON.parse(raw); } catch(e){ throw new Error('Risposta server non valida: ' + raw.slice(0,180)); } }); });\n        };\n        return new Promise(function(resolve, reject){\n            var done = false;\n            var timer = setTimeout(function(){ if(done) return; done = true; reject(new Error(label || 'Richiesta non completata nei tempi previsti.')); }, ms);\n            work().then(function(data){ if(done) return; done = true; clearTimeout(timer); resolve(data); }).catch(function(err){ if(done) return; done = true; clearTimeout(timer); reject(err); });\n        });\n    }\n    function shouldCancel(){ try { return !!(window.isLongAIProcessCancelled && window.isLongAIProcessCancelled()); } catch(e) { return false; } }\n    function clearCancel(){ try { if (window.clearLongAIProcess) window.clearLongAIProcess(); } catch(e) {} }\n    function errText(res, fallback){\n        if (!res) return fallback || 'Errore sconosciuto';\n        if (res.data && res.data.message) return String(res.data.message);\n        if (res.message) return String(res.message);\n        return fallback || 'Errore sconosciuto';\n    }\n\n    window.runBatchAIGen = function(tableName) {\n        var dbSel = byId('ti-ditta');\n        var replyBox = byId('ti-conf-ai-reply');\n        if (!dbSel || !dbSel.value || dbSel.value === 'NEW_DB') { if (window.tiAlert) window.tiAlert('Seleziona prima una ditta.'); return; }\n        if (!replyBox) { if (window.tiAlert) window.tiAlert('Area risposta configurazione non disponibile.'); return; }\n        var photoDetail = ((byId('ti-conf-ai-photo-detail') && byId('ti-conf-ai-photo-detail').value) ? byId('ti-conf-ai-photo-detail').value : '').trim();\n        var initTimeoutMs = 90000;\n        var itemTimeoutMs = 240000;\n        replyBox.style.display = 'block';\n        replyBox.innerHTML = 'Preparazione elenco record. Verranno elaborate le righe senza immagini specifiche.<br>Prompt base: attivit\u00e0 ditta + descrizione prodotto\/servizio.';\n        clearCancel();\n        if (window.showProgressPopup) window.showProgressPopup('Generazione foto AI', 'Sto preparando l elenco dei record senza immagini.', {startPercent:2, targetPercent:18, stepMs:350, cancellable:true});\n\n        var initFd = buildFd('ti_ai_config_action', {db:dbSel.value, mode:'batch_ai_images', tables:tableName, detail_prompt:photoDetail, only_missing:'1'});\n        postJson(initFd, initTimeoutMs, 'Preparazione elenco record non completata entro 90 secondi.').then(function(d){\n            if (!d || !d.success) {\n                if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                var msg = errText(d, 'Errore preparazione generazione foto AI.');\n                if (window.hideProgressPopup) window.hideProgressPopup(false, 'Errore preparazione foto AI');\n                replyBox.innerHTML = '<b>Generazione foto AI non avviata.<\/b><br>' + esc(msg);\n                if (window.tiAlert) window.tiAlert('Generazione foto AI non avviata: ' + msg);\n                return;\n            }\n            var targets = (d.data && Array.isArray(d.data.targets)) ? d.data.targets : [];\n            var withoutAssoc = (d.data && typeof d.data.without_associations !== 'undefined') ? parseInt(d.data.without_associations,10) : targets.length;\n            var withAssoc = (d.data && typeof d.data.with_associations !== 'undefined') ? parseInt(d.data.with_associations,10) : 0;\n            if (!targets.length) {\n                if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                if (window.hideProgressPopup) window.hideProgressPopup(true, 'Nessun elemento da elaborare', {notifyUser:false, remindSave:false});\n                replyBox.innerHTML = 'Nessun record senza immagine trovato nella tabella selezionata.<br>Record gi\u00e0 con associazioni: <b>' + withAssoc + '<\/b>.';\n                return;\n            }\n            var index = 0, okCount = 0, skipCount = 0, failCount = 0;\n            var logs = [];\n            var total = targets.length;\n            window.tiBatchAIGenState = {active:true, pendingConfirm:false, generated:[], db:dbSel.value, tableName:tableName, total:total, startedAt:Date.now(), timeout_ms:itemTimeoutMs, only_missing:true};\n            var detailMode = (d.data && d.data.detail_prompt_mode) ? String(d.data.detail_prompt_mode) : '';\n            var detailInfo = (photoDetail && detailMode === 'leaflet') ? '<br>Istruzione aggiuntiva riconosciuta: <b>foglietto illustrativo \/ materiale informativo<\/b>.' : (photoDetail ? '<br>Istruzione aggiuntiva: <b>' + esc(photoDetail) + '<\/b>.' : '');\n            replyBox.innerHTML = 'Generazione immagini AI pronta.<br>Da elaborare senza immagine: <b>' + total + '<\/b>.' + detailInfo + '<br>Gi\u00e0 con immagini specifiche e saltati: <b>' + withAssoc + '<\/b>.<br><span style=\"color:#facc15;\">Timeout per record aumentato a ' + Math.round(itemTimeoutMs\/1000) + ' secondi; in caso di errore il batch prosegue.<\/span>';\n\n            function refreshDb(){\n                if (window.refreshCurrentConfigDb) return Promise.resolve(window.refreshCurrentConfigDb(dbSel.value)).catch(function(){});\n                var fdDb = buildFd('ti_ai_get_db_data', {db:dbSel.value});\n                return postJson(fdDb, 45000, 'Rilettura database non completata').then(function(dbRes){\n                    if (dbRes && dbRes.success && dbRes.data) { window.currentDbData = dbRes.data; if (window.renderConfig) window.renderConfig(); }\n                }).catch(function(){});\n            }\n            function finish(){\n                if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                if (window.updateProgressPopup) window.updateProgressPopup(100, 'Generazione foto AI', 'Completamento e verifica salvataggio associazioni.');\n                Promise.resolve(refreshDb()).finally(function(){\n                    if (window.hideProgressPopup) window.hideProgressPopup(true, 'Generazione foto AI completata', {remindSave:false, successMessage:'Immagini generate e associate: ' + okCount + ' su ' + total + '. Errori\/saltati: ' + (failCount + skipCount) + '.'});\n                    replyBox.innerHTML = '\u2705 Generazione immagini AI completata.<br>Immagini generate e verificate: <b>' + okCount + '<\/b> \/ ' + total + '<br>Saltati: <b>' + skipCount + '<\/b><br>Errori: <b>' + failCount + '<\/b><br><br>' + logs.slice(0,100).join('<br>');\n                });\n            }\n            function runNext(){\n                if (shouldCancel()) {\n                    replyBox.innerHTML = 'Generazione foto AI interrotta dall utente. Immagini gi\u00e0 generate mantenute: ' + okCount + '. Errori\/saltati: ' + (failCount + skipCount) + '.';\n                    if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n                    clearCancel();\n                    return;\n                }\n                if (index >= total) { finish(); return; }\n                var item = targets[index] || {};\n                var currentStep = index + 1;\n                var pct = Math.max(5, Math.min(98, Math.round(((index + 0.2) \/ total) * 100)));\n                var itemName = item.name || 'Elemento';\n                if (window.updateProgressPopup) window.updateProgressPopup(pct, 'Generazione foto AI', 'Elaborazione ' + currentStep + ' \/ ' + total + ': ' + itemName, itemName);\n                replyBox.innerHTML = 'Generazione immagini AI in corso...<br>Elaborazione ' + currentStep + ' \/ ' + total + ': <b>' + esc(itemName) + '<\/b><br><span style=\"color:#94a3b8;\">Timeout massimo record: ' + Math.round(itemTimeoutMs\/1000) + ' secondi.<\/span>';\n                var fd = buildFd('ti_ai_config_action', {db:dbSel.value, mode:'batch_ai_image_one', table:item.table || tableName, row_index:item.row_index, detail_prompt:photoDetail, skip_existing:'1', allow_fallback_logo:'0'});\n                postJson(fd, itemTimeoutMs, 'Timeout su record: ' + itemName).then(function(one){\n                    var data = one && one.data ? one.data : {};\n                    if (one && one.success && data.generated) {\n                        okCount++;\n                        if (window.tiBatchAIGenState && Array.isArray(window.tiBatchAIGenState.generated) && (data.added_value || data.filename || data.url)) {\n                            window.tiBatchAIGenState.generated.push({table:data.table || item.table || tableName, row_index:(typeof data.row_index !== 'undefined') ? data.row_index : item.row_index, field:data.image_field || 'Immagine', value:data.added_value || data.filename || data.url, name:data.name || itemName});\n                        }\n                        logs.push('\u2705 ' + esc(data.name || itemName) + ': immagine generata e associazione salvata');\n                    } else if (one && one.success && data.skipped) {\n                        skipCount++;\n                        logs.push('\u23ed\ufe0f ' + esc(data.name || itemName) + ': ' + esc(data.message || 'record saltato'));\n                    } else {\n                        failCount++;\n                        logs.push('\u274c ' + esc(data.name || itemName) + ': ' + esc(data.message || errText(one, 'errore generazione')));\n                    }\n                }).catch(function(err){\n                    failCount++;\n                    logs.push('\u23f1\ufe0f ' + esc(itemName) + ': ' + esc(err && err.message ? err.message : 'timeout o errore rete') + '. Record saltato, proseguo con il successivo.');\n                }).finally(function(){\n                    index++;\n                    setTimeout(runNext, 300);\n                });\n            }\n            runNext();\n        }).catch(function(err){\n            if (window.tiBatchAIGenState) window.tiBatchAIGenState.active = false;\n            if (window.hideProgressPopup) window.hideProgressPopup(false, 'Errore preparazione foto AI');\n            var msg = err && err.message ? err.message : 'errore rete';\n            replyBox.innerHTML = '<b>Generazione foto AI non avviata.<\/b><br>' + esc(msg) + '<br><br>Verificare chiave OpenAI, permessi cartella AI-data e sessione configuratore.';\n            if (window.tiAlert) window.tiAlert('Generazione foto AI non avviata: ' + msg);\n        });\n    };\n})();\n<\/script>\n\n\n\n<script id=\"ti-batch-ai-interrupt-fix-394\">\n(function(){\n    \"use strict\";\n    if (window.__tiBatchAiInterruptFix394Installed) return;\n    window.__tiBatchAiInterruptFix394Installed = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function esc(v){\n        return (window.escapeHtml ? window.escapeHtml(String(v == null ? '' : v)) : String(v == null ? '' : v).replace(\/[&<>'\"]\/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;',\"'\":'&#39;','\"':'&quot;'}[c];}));\n    }\n    function buildFd(action, data){\n        var fd = window.buildAjaxFormData ? window.buildAjaxFormData(action, data || {}) : new FormData();\n        if (!fd.has('ti_action')) fd.append('ti_action', action);\n        if (!fd.has('action')) fd.append('action', action);\n        Object.keys(data || {}).forEach(function(k){ fd.set(k, data[k]); });\n        return fd;\n    }\n    function postJson(fd, ms, label){\n        ms = ms || 180000;\n        var work = function(){\n            if (window.postFormDataJsonSafe) {\n                return window.postFormDataJsonSafe(window.tiUrl || window.location.href, fd, {tiNoLongProcessSignal:true, cache:'no-store'});\n            }\n            if (window.fetchJsonSafe) {\n                return window.fetchJsonSafe(window.tiUrl || window.location.href, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store', tiNoLongProcessSignal:true});\n            }\n            return fetch(window.tiUrl || window.location.href, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store'}).then(function(r){\n                return r.text().then(function(raw){\n                    if(!r.ok) throw new Error('HTTP ' + r.status + ': ' + raw.slice(0,180));\n                    try { return JSON.parse(raw); } catch(e){ throw new Error('Risposta server non valida: ' + raw.slice(0,180)); }\n                });\n            });\n        };\n        return new Promise(function(resolve, reject){\n            var done = false;\n            var timer = setTimeout(function(){ if(done) return; done = true; reject(new Error(label || 'Richiesta non completata nei tempi previsti.')); }, ms);\n            work().then(function(data){ if(done) return; done = true; clearTimeout(timer); resolve(data); }).catch(function(err){ if(done) return; done = true; clearTimeout(timer); reject(err); });\n        });\n    }\n    function errText(res, fallback){\n        if (!res) return fallback || 'Errore sconosciuto';\n        if (res.data && res.data.message) return String(res.data.message);\n        if (res.message) return String(res.message);\n        return fallback || 'Errore sconosciuto';\n    }\n    function getBatchState(){\n        if (!window.tiBatchAIGenState || typeof window.tiBatchAIGenState !== 'object') window.tiBatchAIGenState = {active:false, generated:[]};\n        return window.tiBatchAIGenState;\n    }\n    function batchActive(){\n        var st = getBatchState();\n        return !!(st && st.active);\n    }\n    function setLoaderCancelLabel(txt){\n        try {\n            var btn = byId('ti-loader-close');\n            if (btn) btn.innerText = txt || 'Interrompi generazione';\n        } catch(e) {}\n    }\n    function reopenLoader(){\n        try {\n            setLoaderCancelLabel('Interrompi generazione');\n            if (window.showPluginModal) window.showPluginModal('ti-ai-loader-ov');\n            else if (window.openModal) window.openModal('ti-ai-loader-ov');\n        } catch(e) {}\n    }\n\n    var originalCancelLongAIProcess394 = window.cancelLongAIProcess;\n    window.cancelLongAIProcess = function(){\n        if (batchActive() && typeof window.confirmCancelBatchAIGen === 'function') {\n            window.confirmCancelBatchAIGen();\n            return;\n        }\n        if (typeof originalCancelLongAIProcess394 === 'function') return originalCancelLongAIProcess394.apply(this, arguments);\n        if (window.closeModal) window.closeModal('ti-ai-loader-ov');\n    };\n\n    window.confirmCancelBatchAIGen = function(){\n        var st = getBatchState();\n        if (!st.active) return false;\n        if (st.pendingConfirm) return true;\n        st.pendingConfirm = true;\n        st.paused = true;\n        try {\n            if (window.tiLongProcessState) window.tiLongProcessState.cancelled = false;\n        } catch(e) {}\n        if (window.closeModal) window.closeModal('ti-ai-loader-ov');\n        var generatedCount = Array.isArray(st.generated) ? st.generated.length : 0;\n        var phase = st.phase ? String(st.phase) : 'batch';\n        var rb = byId('ti-conf-ai-reply');\n        if (rb) {\n            rb.style.display = 'block';\n            rb.innerHTML = 'Generazione foto AI in pausa: conferma se vuoi interrompere oppure continua il batch.';\n        }\n        function resumeBatch(){\n            st.pendingConfirm = false;\n            st.paused = false;\n            st.cancelRequested = false;\n            st.stopMode = '';\n            try { if (window.tiLongProcessState) window.tiLongProcessState.cancelled = false; } catch(e) {}\n            if (rb) rb.innerHTML = 'Generazione foto AI ripresa. Il batch continua dal punto corrente.';\n            reopenLoader();\n        }\n        function requestStop(mode){\n            st.pendingConfirm = false;\n            st.paused = false;\n            st.cancelRequested = true;\n            st.stopMode = mode || 'keep';\n            try { if (window.tiLongProcessState) window.tiLongProcessState.cancelled = false; } catch(e) {}\n            if (rb) {\n                rb.style.display = 'block';\n                rb.innerHTML = 'Interruzione richiesta. Il record eventualmente in corso viene chiuso in modo sicuro, poi il batch si ferma.';\n            }\n            reopenLoader();\n        }\n        function askKeepOrRevert(){\n            if (generatedCount > 0 && typeof window.tiConfirmYesNoAction === 'function') {\n                window.tiConfirmYesNoAction(\n                    'Sono gi\u00e0 state generate <b>' + generatedCount + '<\/b> associazioni immagine.<br><br>Vuoi mantenerle oppure annullarle quando il batch si ferma?',\n                    function(){ requestStop('keep'); },\n                    function(){ requestStop('revert'); },\n                    'INTERROMPI E MANTIENI',\n                    'INTERROMPI E ANNULLA'\n                );\n            } else {\n                requestStop('keep');\n            }\n        }\n        if (typeof window.tiConfirmYesNoAction === 'function') {\n            window.tiConfirmYesNoAction(\n                'Vuoi davvero interrompere la generazione foto AI?<br><br>Fase corrente: <b>' + esc(phase) + '<\/b>.<br>Immagini gi\u00e0 associate: <b>' + generatedCount + '<\/b>.<br><br>Premi <b>CONTINUA<\/b> per proseguire senza perdere il batch.',\n                askKeepOrRevert,\n                resumeBatch,\n                'INTERROMPI',\n                'CONTINUA'\n            );\n        } else {\n            requestStop('keep');\n        }\n        return true;\n    };\n\n    window.runBatchAIGen = function(tableName) {\n        var dbSel = byId('ti-ditta');\n        var replyBox = byId('ti-conf-ai-reply');\n        if (!dbSel || !dbSel.value || dbSel.value === 'NEW_DB') { if (window.tiAlert) window.tiAlert('Seleziona prima una ditta.'); return; }\n        if (!replyBox) { if (window.tiAlert) window.tiAlert('Area risposta configurazione non disponibile.'); return; }\n        var photoDetail = ((byId('ti-conf-ai-photo-detail') && byId('ti-conf-ai-photo-detail').value) ? byId('ti-conf-ai-photo-detail').value : '').trim();\n        var initTimeoutMs = 120000;\n        var itemTimeoutMs = 240000;\n        var logs = [];\n        var okCount = 0, skipCount = 0, failCount = 0, total = 0, withAssoc = 0;\n        var st = window.tiBatchAIGenState = {\n            active:true,\n            pendingConfirm:false,\n            paused:false,\n            cancelRequested:false,\n            stopMode:'',\n            generated:[],\n            db:dbSel.value,\n            tableName:tableName,\n            total:0,\n            phase:'preparazione elenco',\n            startedAt:Date.now(),\n            timeout_ms:itemTimeoutMs,\n            only_missing:true\n        };\n        replyBox.style.display = 'block';\n        replyBox.innerHTML = 'Preparazione elenco record. Verranno elaborate solo le righe senza immagini specifiche.<br>Prompt base: attivit\u00e0 ditta + descrizione prodotto\/servizio.';\n        try { if (window.clearLongAIProcess) window.clearLongAIProcess(); } catch(e) {}\n        if (window.showProgressPopup) window.showProgressPopup('Generazione foto AI', 'Sto preparando l elenco dei record senza immagini.', {startPercent:2, targetPercent:18, stepMs:350, cancellable:true});\n        setLoaderCancelLabel('Interrompi generazione');\n\n        function isStopped(){\n            var cur = getBatchState();\n            return !!(cur.cancelRequested || !cur.active);\n        }\n        function isPaused(){\n            var cur = getBatchState();\n            return !!(cur.paused || cur.pendingConfirm);\n        }\n        function refreshDb(){\n            if (window.refreshCurrentConfigDb) return Promise.resolve(window.refreshCurrentConfigDb(dbSel.value)).catch(function(){});\n            var fdDb = buildFd('ti_ai_get_db_data', {db:dbSel.value});\n            return postJson(fdDb, 45000, 'Rilettura database non completata').then(function(dbRes){\n                if (dbRes && dbRes.success && dbRes.data) { window.currentDbData = dbRes.data; if (window.renderConfig) window.renderConfig(); }\n            }).catch(function(){});\n        }\n        function resetState(){\n            try { if (window.clearLongAIProcess) window.clearLongAIProcess(); } catch(e) {}\n            var cur = getBatchState();\n            cur.active = false;\n            cur.pendingConfirm = false;\n            cur.paused = false;\n        }\n        function finishComplete(){\n            st.phase = 'completamento';\n            resetState();\n            if (window.updateProgressPopup) window.updateProgressPopup(100, 'Generazione foto AI', 'Completamento e verifica salvataggio associazioni.');\n            Promise.resolve(refreshDb()).finally(function(){\n                if (window.hideProgressPopup) window.hideProgressPopup(true, 'Generazione foto AI completata', {remindSave:false, successMessage:'Immagini generate e associate: ' + okCount + ' su ' + total + '. Errori\/saltati: ' + (failCount + skipCount) + '.'});\n                replyBox.innerHTML = '\u2705 Generazione immagini AI completata.<br>Immagini generate e verificate: <b>' + okCount + '<\/b> \/ ' + total + '<br>Saltati: <b>' + skipCount + '<\/b><br>Errori: <b>' + failCount + '<\/b><br><br>' + logs.slice(0,100).join('<br>');\n            });\n        }\n        function revertGeneratedThenReport(doneText){\n            var generated = Array.isArray(st.generated) ? st.generated.slice() : [];\n            if (!generated.length) return Promise.resolve(0);\n            var fd = buildFd('ti_ai_config_action', {db:st.db || dbSel.value, mode:'batch_ai_images_revert', items_json:JSON.stringify(generated)});\n            replyBox.innerHTML = 'Annullamento associazioni generate in corso...';\n            return postJson(fd, 90000, 'Annullamento associazioni non completato').then(function(res){\n                var removed = res && res.data && typeof res.data.removed !== 'undefined' ? parseInt(res.data.removed,10) || 0 : 0;\n                return removed;\n            }).catch(function(err){\n                logs.push('\u26a0\ufe0f Annullamento associazioni non completato: ' + esc(err && err.message ? err.message : 'errore sconosciuto'));\n                return -1;\n            });\n        }\n        function finishInterrupted(){\n            st.phase = 'interruzione confermata';\n            var mode = st.stopMode || 'keep';\n            if (window.updateProgressPopup) window.updateProgressPopup(Math.min(100, Math.max(5, Math.round(((okCount + failCount + skipCount) \/ Math.max(1,total)) * 100))), 'Generazione foto AI interrotta', 'Chiusura sicura del batch.');\n            var finalize = function(extra){\n                resetState();\n                Promise.resolve(refreshDb()).finally(function(){\n                    if (window.hideProgressPopup) window.hideProgressPopup(false, 'Generazione foto AI interrotta', {notifyUser:false});\n                    var msg = 'Generazione foto AI interrotta su richiesta.<br>Immagini generate prima dello stop: <b>' + okCount + '<\/b> \/ ' + total + '.<br>Saltati: <b>' + skipCount + '<\/b>. Errori: <b>' + failCount + '<\/b>.' + (extra ? '<br>' + extra : '') + '<br><br>Puoi riprendere l operazione: verranno rielaborate solo le righe ancora senza immagine.';\n                    replyBox.innerHTML = msg + '<br><br>' + logs.slice(0,80).join('<br>');\n                    if (window.tiAlert) window.tiAlert('Generazione foto AI interrotta su richiesta. Puoi ripetere l operazione: saranno elaborate solo le righe ancora senza immagine.');\n                });\n            };\n            if (mode === 'revert') {\n                revertGeneratedThenReport().then(function(removed){\n                    if (removed >= 0) finalize('Associazioni generate annullate: <b>' + removed + '<\/b>.');\n                    else finalize('Non sono riuscito ad annullare automaticamente tutte le associazioni generate: verifica la configurazione.');\n                });\n            } else {\n                finalize('Associazioni generate mantenute.');\n            }\n        }\n        function waitWhilePaused(next){\n            if (!isPaused()) { next(); return; }\n            setTimeout(function(){ waitWhilePaused(next); }, 500);\n        }\n\n        var initFd = buildFd('ti_ai_config_action', {db:dbSel.value, mode:'batch_ai_images', tables:tableName, detail_prompt:photoDetail, only_missing:'1'});\n        postJson(initFd, initTimeoutMs, 'Preparazione elenco record non completata entro 120 secondi.').then(function(d){\n            waitWhilePaused(function(){\n                if (st.cancelRequested) { finishInterrupted(); return; }\n                if (!d || !d.success) {\n                    resetState();\n                    var msg = errText(d, 'Errore preparazione generazione foto AI.');\n                    if (window.hideProgressPopup) window.hideProgressPopup(false, 'Errore preparazione foto AI');\n                    replyBox.innerHTML = '<b>Generazione foto AI non avviata.<\/b><br>' + esc(msg);\n                    if (window.tiAlert) window.tiAlert('Generazione foto AI non avviata: ' + msg);\n                    return;\n                }\n                var targets = (d.data && Array.isArray(d.data.targets)) ? d.data.targets : [];\n                var withoutAssoc = (d.data && typeof d.data.without_associations !== 'undefined') ? parseInt(d.data.without_associations,10) : targets.length;\n                withAssoc = (d.data && typeof d.data.with_associations !== 'undefined') ? parseInt(d.data.with_associations,10) : 0;\n                total = targets.length;\n                st.total = total;\n                st.phase = 'generazione immagini';\n                if (!targets.length) {\n                    resetState();\n                    if (window.hideProgressPopup) window.hideProgressPopup(true, 'Nessun elemento da elaborare', {notifyUser:false, remindSave:false});\n                    replyBox.innerHTML = 'Nessun record senza immagine trovato nella tabella selezionata.<br>Record gi\u00e0 con associazioni: <b>' + withAssoc + '<\/b>.';\n                    return;\n                }\n                var detailMode = (d.data && d.data.detail_prompt_mode) ? String(d.data.detail_prompt_mode) : '';\n                var detailInfo = (photoDetail && detailMode === 'leaflet') ? '<br>Istruzione aggiuntiva riconosciuta: <b>foglietto illustrativo \/ materiale informativo<\/b>.' : (photoDetail ? '<br>Istruzione aggiuntiva: <b>' + esc(photoDetail) + '<\/b>.' : '');\n                replyBox.innerHTML = 'Generazione immagini AI pronta.<br>Da elaborare senza immagine: <b>' + total + '<\/b>.' + detailInfo + '<br>Gi\u00e0 con immagini specifiche e saltati: <b>' + withAssoc + '<\/b>.<br><span style=\"color:#facc15;\">Il pulsante Interrompi ora chiede conferma e CONTINUA riprende il batch.<\/span>';\n\n                function runNext(){\n                    waitWhilePaused(function(){\n                        if (st.cancelRequested) { finishInterrupted(); return; }\n                        if (index >= total) { finishComplete(); return; }\n                        var item = targets[index] || {};\n                        var currentStep = index + 1;\n                        var pct = Math.max(5, Math.min(98, Math.round(((index + 0.2) \/ total) * 100)));\n                        var itemName = item.name || 'Elemento';\n                        st.phase = 'record ' + currentStep + ' \/ ' + total;\n                        if (window.updateProgressPopup) window.updateProgressPopup(pct, 'Generazione foto AI', 'Elaborazione ' + currentStep + ' \/ ' + total + ': ' + itemName, itemName);\n                        setLoaderCancelLabel('Interrompi generazione');\n                        replyBox.innerHTML = 'Generazione immagini AI in corso...<br>Elaborazione ' + currentStep + ' \/ ' + total + ': <b>' + esc(itemName) + '<\/b><br><span style=\"color:#94a3b8;\">Timeout massimo record: ' + Math.round(itemTimeoutMs\/1000) + ' secondi.<\/span>';\n                        var fd = buildFd('ti_ai_config_action', {db:dbSel.value, mode:'batch_ai_image_one', table:item.table || tableName, row_index:item.row_index, detail_prompt:photoDetail, skip_existing:'1', allow_fallback_logo:'0'});\n                        postJson(fd, itemTimeoutMs, 'Timeout su record: ' + itemName).then(function(one){\n                            var data = one && one.data ? one.data : {};\n                            if (one && one.success && data.generated) {\n                                okCount++;\n                                if (Array.isArray(st.generated) && (data.added_value || data.filename || data.url)) {\n                                    st.generated.push({table:data.table || item.table || tableName, row_index:(typeof data.row_index !== 'undefined') ? data.row_index : item.row_index, field:data.image_field || 'Immagine', value:data.added_value || data.filename || data.url, name:data.name || itemName});\n                                }\n                                logs.push('\u2705 ' + esc(data.name || itemName) + ': immagine generata e associazione salvata');\n                            } else if (one && one.success && data.skipped) {\n                                skipCount++;\n                                logs.push('\u23ed\ufe0f ' + esc(data.name || itemName) + ': ' + esc(data.message || 'record saltato'));\n                            } else {\n                                failCount++;\n                                logs.push('\u274c ' + esc(data.name || itemName) + ': ' + esc(data.message || errText(one, 'errore generazione')));\n                            }\n                        }).catch(function(err){\n                            failCount++;\n                            logs.push('\u23f1\ufe0f ' + esc(itemName) + ': ' + esc(err && err.message ? err.message : 'timeout o errore rete') + '. Record saltato, proseguo con il successivo.');\n                        }).finally(function(){\n                            index++;\n                            setTimeout(runNext, 300);\n                        });\n                    });\n                }\n                var index = 0;\n                runNext();\n            });\n        }).catch(function(err){\n            if (st.cancelRequested) { finishInterrupted(); return; }\n            resetState();\n            if (window.hideProgressPopup) window.hideProgressPopup(false, 'Errore preparazione foto AI');\n            var msg = err && err.message ? err.message : 'errore rete';\n            replyBox.innerHTML = '<b>Generazione foto AI non avviata.<\/b><br>' + esc(msg) + '<br><br>Verificare chiave OpenAI, permessi cartella AI-data e sessione configuratore.';\n            if (window.tiAlert) window.tiAlert('Generazione foto AI non avviata: ' + msg);\n        });\n    };\n})();\n<\/script>\n\n<script id=\"ti-ai-batch-400-fix\">\n(function(){\n    if (window.__tiBatchAI400FixApplied) return;\n    window.__tiBatchAI400FixApplied = true;\n    function byId(id){ return document.getElementById(id); }\n    function esc(s){ return String(s == null ? '' : s).replace(\/[&<>\"']\/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c];}); }\n    function buildFd(action, data){\n        var fd = window.buildAjaxFormData ? window.buildAjaxFormData(action, data || {}) : new FormData();\n        if (!fd.has('ti_action')) fd.append('ti_action', action);\n        if (!fd.has('action')) fd.append('action', action);\n        Object.keys(data || {}).forEach(function(k){ fd.set(k, data[k]); });\n        return fd;\n    }\n    function postJson(fd, ms, label){\n        ms = ms || 60000;\n        var work = function(){\n            if (window.postFormDataJsonSafe) return window.postFormDataJsonSafe(window.tiUrl || window.location.href, fd, {tiNoLongProcessSignal:true, cache:'no-store'});\n            if (window.fetchJsonSafe) return window.fetchJsonSafe(window.tiUrl || window.location.href, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store', tiNoLongProcessSignal:true});\n            return fetch(window.tiUrl || window.location.href, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store'}).then(function(r){ return r.text().then(function(raw){ if(!r.ok) throw new Error('HTTP ' + r.status + ': ' + raw.slice(0,180)); try { return JSON.parse(raw); } catch(e){ throw new Error('Risposta server non valida: ' + raw.slice(0,180)); } }); });\n        };\n        return new Promise(function(resolve, reject){\n            var done = false;\n            var timer = setTimeout(function(){ if(done) return; done = true; reject(new Error(label || 'Richiesta non completata nei tempi previsti.')); }, ms);\n            work().then(function(data){ if(done) return; done = true; clearTimeout(timer); resolve(data); }).catch(function(err){ if(done) return; done = true; clearTimeout(timer); reject(err); });\n        });\n    }\n    function getBatchState(){ if (!window.tiBatchAIGenState || typeof window.tiBatchAIGenState !== 'object') window.tiBatchAIGenState = {active:false, generated:[]}; return window.tiBatchAIGenState; }\n    function setLoaderCancelLabel(txt){ try { var b=byId('ti-loader-close'); if (b) b.innerText = txt || 'Interrompi generazione'; } catch(e) {} }\n    function realTable(data, name){\n        var tabs = data && data.Tabelle ? data.Tabelle : null;\n        if (!tabs) return '';\n        if (tabs[name]) return name;\n        var want = String(name || '').toLowerCase().replace(\/[\\s_\\-]\/g,'');\n        var keys = Object.keys(tabs);\n        for (var i=0;i<keys.length;i++){ if (String(keys[i]).toLowerCase().replace(\/[\\s_\\-]\/g,'') === want) return keys[i]; }\n        return '';\n    }\n    function pick(row, keys){\n        for (var i=0;i<keys.length;i++){\n            var k = keys[i];\n            if (Object.prototype.hasOwnProperty.call(row,k) && String(row[k] == null ? '' : row[k]).trim() !== '') return String(row[k]).trim();\n            var want = String(k).toLowerCase().replace(\/[^a-z0-9]\/g,'');\n            var rk = Object.keys(row).find(function(x){ return String(x).toLowerCase().replace(\/[^a-z0-9]\/g,'') === want; });\n            if (rk && String(row[rk] == null ? '' : row[rk]).trim() !== '') return String(row[rk]).trim();\n        }\n        return '';\n    }\n    function mediaCount(row){\n        if (!row || typeof row !== 'object') return 0;\n        var n = 0;\n        Object.keys(row).forEach(function(k){\n            var key = String(k || '').toLowerCase();\n            if (!\/(immagine|img|foto|logo|doc|document|file|files|allegat)\/.test(key)) return;\n            var raw = String(row[k] == null ? '' : row[k]).trim();\n            if (!raw) return;\n            raw.split(\/[,;\\r\\n]+\/).forEach(function(tok){\n                tok = String(tok || '').trim();\n                if (!tok) return;\n                var tl = tok.toLowerCase();\n                if (\/^(0|no|nessuno|nessuna|--|-|null|undefined)$\/.test(tl)) return;\n                n++;\n            });\n        });\n        return n;\n    }\n    function localPrepare(tableName){\n        var data = window.currentDbData || window.tiCurrentDbData || window.tiDbData || null;\n        if (!data || !data.Tabelle) return null;\n        var reqTables = String(tableName || 'Prodotti').split(',').map(function(x){return x.trim();}).filter(Boolean);\n        if (!reqTables.length) reqTables = ['Prodotti'];\n        var without = [], withAssoc = [];\n        reqTables.forEach(function(tReq){\n            var rt = realTable(data, tReq);\n            if (!rt) return;\n            var rows = data.Tabelle[rt];\n            if (!Array.isArray(rows)) rows = rows && typeof rows === 'object' ? Object.keys(rows).map(function(k){return rows[k];}) : [];\n            rows.forEach(function(row, idx){\n                if (!row || typeof row !== 'object') return;\n                var assoc = mediaCount(row);\n                var name = pick(row, ['Prodotto','Servizio','Nome','Titolo','Articolo']) || pick(row, ['Descrizione','Dettaglio','Note']) || ('Elemento riga ' + (idx+1));\n                if (name.length > 180) name = name.slice(0,177) + '...';\n                var item = {table:rt, row_index:idx, name:name, has_associations:assoc>0, association_count:assoc, priority_label:assoc>0?'gia con associazioni':'senza associazioni'};\n                if (assoc > 0) withAssoc.push(item); else without.push(item);\n            });\n        });\n        return {success:true, data:{targets:without, total:without.length, without_associations:without.length, with_associations:withAssoc.length, only_missing:true, prepare_mode:'local_current_db', ti_skip_company_notice:true}};\n    }\n    function errText(res, fallback){\n        if (!res) return fallback || 'Errore sconosciuto';\n        if (res.data && res.data.message) return String(res.data.message);\n        if (res.message) return String(res.message);\n        return fallback || 'Errore sconosciuto';\n    }\n    function missingKey(data){\n        var msg = String((data && data.message) || '');\n        var code = String((data && data.code) || '');\n        return code === 'missing_openai_key' || \/chiave\\s+openai\\s+mancante\/i.test(msg);\n    }\n\n    window.runBatchAIGen = function(tableName){\n        var dbSel = byId('ti-ditta');\n        var replyBox = byId('ti-conf-ai-reply');\n        if (!dbSel || !dbSel.value || dbSel.value === 'NEW_DB') { if (window.tiAlert) window.tiAlert('Seleziona prima una ditta.'); return; }\n        if (!replyBox) { if (window.tiAlert) window.tiAlert('Area risposta configurazione non disponibile.'); return; }\n        var photoBox = byId('ti-conf-ai-photo-detail');\n        var photoDetail = photoBox && photoBox.value ? String(photoBox.value).trim() : '';\n        var itemTimeoutMs = 240000;\n        var logs = [];\n        var okCount = 0, skipCount = 0, failCount = 0, total = 0, withAssoc = 0, index = 0;\n        var st = window.tiBatchAIGenState = {active:true, pendingConfirm:false, paused:false, cancelRequested:false, stopMode:'', generated:[], db:dbSel.value, tableName:tableName, total:0, phase:'preparazione elenco', startedAt:Date.now(), timeout_ms:itemTimeoutMs, only_missing:true, fix400:true};\n        replyBox.style.display = 'block';\n        replyBox.innerHTML = 'Preparazione elenco record. Verranno elaborate solo le righe senza immagini specifiche.<br>Se il server non risponde subito, uso l elenco gi\u00e0 caricato in configurazione.';\n        try { if (window.clearLongAIProcess) window.clearLongAIProcess(); } catch(e) {}\n        if (window.showProgressPopup) window.showProgressPopup('Generazione foto AI', 'Sto preparando l elenco dei record senza immagini.', {startPercent:2, targetPercent:18, stepMs:350, cancellable:true});\n        setLoaderCancelLabel('Interrompi generazione');\n\n        function isPaused(){ var cur = getBatchState(); return !!(cur.paused || cur.pendingConfirm); }\n        function resetState(){ try { if (window.clearLongAIProcess) window.clearLongAIProcess(); } catch(e) {} var cur=getBatchState(); cur.active=false; cur.pendingConfirm=false; cur.paused=false; }\n        function refreshDb(){\n            if (window.refreshCurrentConfigDb) return Promise.resolve(window.refreshCurrentConfigDb(dbSel.value)).catch(function(){});\n            var fdDb = buildFd('ti_ai_get_db_data', {db:dbSel.value});\n            return postJson(fdDb, 45000, 'Rilettura database non completata').then(function(dbRes){ if (dbRes && dbRes.success && dbRes.data) { window.currentDbData=dbRes.data; if (window.renderConfig) window.renderConfig(); } }).catch(function(){});\n        }\n        function finishComplete(){\n            st.phase='completamento'; resetState();\n            if (window.updateProgressPopup) window.updateProgressPopup(100, 'Generazione foto AI', 'Completamento e verifica salvataggio associazioni.');\n            Promise.resolve(refreshDb()).finally(function(){\n                if (window.hideProgressPopup) window.hideProgressPopup(true, 'Generazione foto AI completata', {remindSave:false, successMessage:'Immagini generate e associate: ' + okCount + ' su ' + total + '. Errori\/saltati: ' + (failCount + skipCount) + '.'});\n                replyBox.innerHTML = '\u2705 Generazione immagini AI completata.<br>Immagini generate e verificate: <b>' + okCount + '<\/b> \/ ' + total + '<br>Saltati: <b>' + skipCount + '<\/b><br>Errori: <b>' + failCount + '<\/b><br><br>' + logs.slice(0,120).join('<br>');\n            });\n        }\n        function revertGeneratedThenReport(){\n            var generated = Array.isArray(st.generated) ? st.generated.slice() : [];\n            if (!generated.length) return Promise.resolve(0);\n            var fd = buildFd('ti_ai_config_action', {db:st.db || dbSel.value, mode:'batch_ai_images_revert', items_json:JSON.stringify(generated)});\n            replyBox.innerHTML = 'Annullamento associazioni generate in corso...';\n            return postJson(fd, 90000, 'Annullamento associazioni non completato').then(function(res){ return res && res.data && typeof res.data.removed !== 'undefined' ? (parseInt(res.data.removed,10)||0) : 0; }).catch(function(err){ logs.push('\u26a0\ufe0f Annullamento associazioni non completato: ' + esc(err && err.message ? err.message : 'errore sconosciuto')); return -1; });\n        }\n        function finishInterrupted(){\n            st.phase='interruzione confermata';\n            var mode = st.stopMode || 'keep';\n            var finalize = function(extra){ resetState(); Promise.resolve(refreshDb()).finally(function(){ if (window.hideProgressPopup) window.hideProgressPopup(false, 'Generazione foto AI interrotta', {notifyUser:false}); replyBox.innerHTML = 'Generazione foto AI interrotta su richiesta.<br>Immagini generate prima dello stop: <b>' + okCount + '<\/b> \/ ' + total + '.<br>Saltati: <b>' + skipCount + '<\/b>. Errori: <b>' + failCount + '<\/b>.' + (extra ? '<br>' + extra : '') + '<br><br>Puoi riprendere l operazione: verranno rielaborate solo le righe ancora senza immagine.<br><br>' + logs.slice(0,80).join('<br>'); }); };\n            if (mode === 'revert') revertGeneratedThenReport().then(function(removed){ finalize(removed >= 0 ? 'Associazioni generate annullate: <b>' + removed + '<\/b>.' : 'Non sono riuscito ad annullare automaticamente tutte le associazioni generate.'); });\n            else finalize('Associazioni generate mantenute.');\n        }\n        function waitWhilePaused(next){ if (!isPaused()) { next(); return; } setTimeout(function(){ waitWhilePaused(next); }, 500); }\n        function stopForMissingKey(msg){\n            failCount++;\n            logs.push('\u274c Generazione non avviata: ' + esc(msg));\n            resetState();\n            if (window.hideProgressPopup) window.hideProgressPopup(false, 'Generazione foto AI non avviata');\n            replyBox.innerHTML = '<b>Generazione foto AI non avviata.<\/b><br>' + esc(msg) + '<br><br>Configurare la chiave OpenAI nella ditta o nelle opzioni plugin, poi ripetere l operazione.';\n            if (window.tiAlert) window.tiAlert('Generazione foto AI non avviata: ' + msg);\n        }\n        function runNext(targets){\n            waitWhilePaused(function(){\n                if (st.cancelRequested) { finishInterrupted(); return; }\n                if (index >= total) { finishComplete(); return; }\n                var item = targets[index] || {};\n                var currentStep = index + 1;\n                var pct = Math.max(5, Math.min(98, Math.round(((index + 0.2) \/ Math.max(1,total)) * 100)));\n                var itemName = item.name || 'Elemento';\n                st.phase = 'record ' + currentStep + ' \/ ' + total;\n                if (window.updateProgressPopup) window.updateProgressPopup(pct, 'Generazione foto AI', 'Elaborazione ' + currentStep + ' \/ ' + total + ': ' + itemName, itemName);\n                setLoaderCancelLabel('Interrompi generazione');\n                replyBox.innerHTML = 'Generazione immagini AI in corso...<br>Elaborazione ' + currentStep + ' \/ ' + total + ': <b>' + esc(itemName) + '<\/b><br><span style=\"color:#94a3b8;\">Timeout massimo record: ' + Math.round(itemTimeoutMs\/1000) + ' secondi.<\/span>';\n                var fd = buildFd('ti_ai_config_action', {db:dbSel.value, mode:'batch_ai_image_one', table:item.table || tableName, row_index:item.row_index, detail_prompt:photoDetail, skip_existing:'1', allow_fallback_logo:'0'});\n                postJson(fd, itemTimeoutMs, 'Timeout su record: ' + itemName).then(function(one){\n                    var data = one && one.data ? one.data : {};\n                    if (missingKey(data)) { stopForMissingKey(data.message || 'Chiave OpenAI mancante.'); index = total; return; }\n                    if (one && one.success && data.generated) {\n                        okCount++;\n                        if (Array.isArray(st.generated) && (data.added_value || data.filename || data.url)) st.generated.push({table:data.table || item.table || tableName, row_index:(typeof data.row_index !== 'undefined') ? data.row_index : item.row_index, field:data.image_field || 'Immagine', value:data.added_value || data.filename || data.url, name:data.name || itemName});\n                        logs.push('\u2705 ' + esc(data.name || itemName) + ': immagine generata e associazione salvata');\n                    } else if (one && one.success && data.skipped) {\n                        skipCount++; logs.push('\u23ed\ufe0f ' + esc(data.name || itemName) + ': ' + esc(data.message || 'record saltato'));\n                    } else {\n                        failCount++; logs.push('\u274c ' + esc(data.name || itemName) + ': ' + esc(data.message || errText(one, 'errore generazione')));\n                    }\n                }).catch(function(err){ failCount++; logs.push('\u23f1\ufe0f ' + esc(itemName) + ': ' + esc(err && err.message ? err.message : 'timeout o errore rete') + '. Record saltato, proseguo con il successivo.'); }).finally(function(){\n                    if (!getBatchState().active || index >= total) return;\n                    index++;\n                    setTimeout(function(){ runNext(targets); }, 300);\n                });\n            });\n        }\n        function startFromPrepare(d, source){\n            waitWhilePaused(function(){\n                if (st.cancelRequested) { finishInterrupted(); return; }\n                if (!d || !d.success) {\n                    resetState();\n                    var msg = errText(d, 'Errore preparazione generazione foto AI.');\n                    if (window.hideProgressPopup) window.hideProgressPopup(false, 'Errore preparazione foto AI');\n                    replyBox.innerHTML = '<b>Generazione foto AI non avviata.<\/b><br>' + esc(msg);\n                    if (window.tiAlert) window.tiAlert('Generazione foto AI non avviata: ' + msg);\n                    return;\n                }\n                var targets = d.data && Array.isArray(d.data.targets) ? d.data.targets : [];\n                withAssoc = d.data && typeof d.data.with_associations !== 'undefined' ? parseInt(d.data.with_associations,10) || 0 : 0;\n                total = targets.length;\n                st.total = total; st.phase = 'generazione immagini'; st.prepareSource = source || (d.data && d.data.prepare_mode) || 'server';\n                if (!targets.length) {\n                    resetState();\n                    if (window.hideProgressPopup) window.hideProgressPopup(true, 'Nessun elemento da elaborare', {notifyUser:false, remindSave:false});\n                    replyBox.innerHTML = 'Nessun record senza immagine trovato nella tabella selezionata.<br>Record gi\u00e0 con associazioni: <b>' + withAssoc + '<\/b>.';\n                    return;\n                }\n                var detailMode = d.data && d.data.detail_prompt_mode ? String(d.data.detail_prompt_mode) : '';\n                var detailInfo = (photoDetail && detailMode === 'leaflet') ? '<br>Istruzione aggiuntiva riconosciuta: <b>foglietto illustrativo \/ materiale informativo<\/b>.' : (photoDetail ? '<br>Istruzione aggiuntiva: <b>' + esc(photoDetail) + '<\/b>.' : '');\n                var srcInfo = source === 'local_current_db' ? '<br><span style=\"color:#38bdf8;\">Elenco record preparato localmente dai dati gi\u00e0 caricati: nessun blocco al 2%.<\/span>' : '';\n                replyBox.innerHTML = 'Generazione immagini AI pronta.<br>Da elaborare senza immagine: <b>' + total + '<\/b>.' + detailInfo + '<br>Gi\u00e0 con immagini specifiche e saltati: <b>' + withAssoc + '<\/b>.' + srcInfo + '<br><span style=\"color:#facc15;\">Il pulsante Interrompi chiede conferma e CONTINUA riprende il batch.<\/span>';\n                if (window.updateProgressPopup) window.updateProgressPopup(5, 'Generazione foto AI', 'Elenco record pronto. Avvio generazione immagini.');\n                index = 0;\n                runNext(targets);\n            });\n        }\n\n        var local = localPrepare(tableName);\n        if (local && local.data && Array.isArray(local.data.targets) && local.data.targets.length) {\n            startFromPrepare(local, 'local_current_db');\n            var pre = buildFd('ti_ai_batch_ai_images_prepare', {db:dbSel.value, tables:tableName, detail_prompt:photoDetail, only_missing:'1'});\n            postJson(pre, 12000, 'Preflight chiave OpenAI non completato').then(function(res){\n                if (res && !res.success && res.data && missingKey(res.data) && getBatchState().active) {\n                    st.cancelRequested = true;\n                    stopForMissingKey(res.data.message || 'Chiave OpenAI mancante.');\n                }\n            }).catch(function(){ });\n            return;\n        }\n\n        var initFd = buildFd('ti_ai_batch_ai_images_prepare', {db:dbSel.value, tables:tableName, detail_prompt:photoDetail, only_missing:'1'});\n        postJson(initFd, 30000, 'Preparazione elenco record non completata entro 30 secondi.').then(function(d){ startFromPrepare(d, d && d.data && d.data.prepare_mode ? d.data.prepare_mode : 'fast_endpoint'); }).catch(function(err){\n            var local2 = localPrepare(tableName);\n            if (local2 && local2.data && Array.isArray(local2.data.targets) && local2.data.targets.length) {\n                logs.push('\u26a0\ufe0f Preparazione server non completata: ' + esc(err && err.message ? err.message : 'timeout') + '. Uso elenco locale.');\n                startFromPrepare(local2, 'local_current_db');\n                return;\n            }\n            resetState();\n            if (window.hideProgressPopup) window.hideProgressPopup(false, 'Errore preparazione foto AI');\n            var msg = err && err.message ? err.message : 'errore rete';\n            replyBox.innerHTML = '<b>Generazione foto AI non avviata.<\/b><br>' + esc(msg) + '<br><br>Non trovo un elenco locale gi\u00e0 caricato. Riaprire la configurazione o ricaricare la ditta e ripetere.';\n            if (window.tiAlert) window.tiAlert('Generazione foto AI non avviata: ' + msg);\n        });\n    };\n})();\n<\/script>\n<script id=\"ti-contact-useful-interaction-399\">\n(function(){\n    \"use strict\";\n    if (window.__tiContactUsefulInteraction399) return;\n    window.__tiContactUsefulInteraction399 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function selectedDb(){\n        var d = byId('ti-ditta');\n        var v = d && d.value ? String(d.value).trim() : '';\n        if (!v || v === 'NEW_DB') { try { v = String(window.tiForced || localStorage.getItem('ti_saved_db') || '').trim(); } catch(e) {} }\n        return (!v || v === 'NEW_DB') ? '' : v;\n    }\n    function fmt(n){ n = parseInt(n,10)||0; try { return n.toLocaleString('it-IT'); } catch(e) { return String(n); } }\n    function uid(){\n        var k = 'ti_contact_uid_v399';\n        try {\n            var v = localStorage.getItem(k);\n            if (!v) { v = 'c' + Date.now().toString(36) + Math.random().toString(36).slice(2,12); localStorage.setItem(k, v); }\n            try { document.cookie = 'ti_ai_ss_contact_uid=' + encodeURIComponent(v) + '; path=\/; max-age=31536000; SameSite=Lax'; } catch(ignore) {}\n            return v;\n        } catch(e) { return 's' + Date.now().toString(36) + Math.random().toString(36).slice(2,10); }\n    }\n    function getConfigRow(){\n        try { var data = window.currentDbData || {}; var t = data && data.Tabelle ? data.Tabelle : null; if (!t) return null; var cfgName = Object.keys(t).find(function(k){ return String(k).toLowerCase() === 'config'; }) || 'Config'; var rows = t[cfgName]; if (Array.isArray(rows)) return rows[0] || null; if (rows && typeof rows === 'object') return Object.values(rows)[0] || null; } catch(e) {}\n        return null;\n    }\n    function setLocal(n, field){\n        n = parseInt(n,10)||0;\n        try { if (!window.currentDbData) window.currentDbData = {}; window.currentDbData.__contacts_num = n; var row = getConfigRow(); if (row) { var k = Object.keys(row).find(function(x){ var s=String(x||'').toLowerCase().replace(\/[^a-z0-9]\/g,''); return s === 'contactsnum' || s === 'contactnum'; }) || field || 'Contacts_num'; row[k] = n; } } catch(e) {}\n        return n;\n    }\n    function current(){ try { if (window.currentDbData && typeof window.currentDbData.__contacts_num !== 'undefined') return parseInt(window.currentDbData.__contacts_num,10)||0; var row = getConfigRow(); if (!row) return 0; var k = Object.keys(row).find(function(x){ var s=String(x||'').toLowerCase().replace(\/[^a-z0-9]\/g,''); return s === 'contactsnum' || s === 'contactnum'; }); return k ? (parseInt(String(row[k]||'0').replace(\/[^0-9\\-]\/g,''),10)||0) : 0; } catch(e) { return 0; } }\n    function updateReport(n){ try { var title = byId('ti-report-title'); if (!title) return; var txt = String(title.innerText || title.textContent || '').replace(\/\\s*[\\-\u2013\u2014]?\\s*(?:Contatti(?: main form)?|Totale contatti)\\s*:\\s*[0-9.]+\\s*$\/i,'').trim(); title.innerText = txt + ' - Totale contatti : ' + fmt(n || 0); } catch(e) {} }\n    function request(db, mode, eventName){\n        db = String(db || selectedDb() || '').trim();\n        if (!db || db === 'NEW_DB') return Promise.resolve({contacts_num:current(), skipped:true});\n        var fd = new FormData();\n        fd.append('action','ti_ai_track_contact_access');\n        fd.append('ti_action','ti_ai_track_contact_access');\n        fd.append('db', db);\n        fd.append('mode', mode || 'read');\n        fd.append('contact_uid', uid());\n        if (mode === 'useful_interaction') { fd.append('contact_interaction','1'); fd.append('contact_event', eventName || 'interface'); }\n        var fetcher = window.fetchJsonSafe ? window.fetchJsonSafe : function(url, init){ return fetch(url, init).then(function(r){ return r.json(); }); };\n        return fetcher(window.tiAjaxUrl || window.tiUrl || window.location.href, {method:'POST', body:fd, credentials:'same-origin', tiNoLongProcessSignal:true}).then(function(res){\n            var payload = res && res.success ? (res.data || {}) : {};\n            var n = typeof payload.contacts_num !== 'undefined' ? (parseInt(payload.contacts_num,10)||0) : current();\n            setLocal(n, payload.field || 'Contacts_num');\n            updateReport(n);\n            if (payload.incremented || payload.duplicate) { try { sessionStorage.setItem('ti_contact_useful_registered_399_' + db, '1'); } catch(e) {} }\n            if (payload.incremented) { try { sessionStorage.setItem('ti_contact_counted_before_role_login_' + db, '1'); } catch(e) {} }\n            return payload;\n        }).catch(function(){ updateReport(current()); return {contacts_num:current(), error:true}; });\n    }\n    function already(db){ try { return sessionStorage.getItem('ti_contact_useful_registered_399_' + db) === '1'; } catch(e) { return false; } }\n    var pending = {};\n    function mark(eventName){\n        var db = selectedDb();\n        if (!db || db === 'NEW_DB' || already(db) || pending[db]) return;\n        pending[db] = true;\n        request(db, 'useful_interaction', eventName || 'interface').then(function(){ pending[db] = false; }).catch(function(){ pending[db] = false; });\n    }\n    function meaningfulClick(t){\n        if (!t || !t.closest) return '';\n        if (t.closest('#ti-maintenance-ov,#ti-login-ov,#ti-ditta,#ti-help-btn')) return '';\n        if (t.closest('#ti-send')) return 'chat_send';\n        if (t.closest('#ti-order-confirm-host,#ti-order-confirm-actions')) return 'order_confirmation';\n        var btn = t.closest('button,.ti-btn,[role=\"button\"],a,[data-ti-action],.ti-card,.ti-product-card,.ti-service-card,.ti-order-card');\n        if (!btn) return '';\n        var txt = String(btn.textContent || btn.value || btn.title || '').toLowerCase().trim();\n        if (\/^(accedi|login|registrati|chiudi|annulla|manuale|aiuto|help|config|manutenzione|accesso configuratore|accesso amministratore)\/.test(txt)) return '';\n        if (t.closest('#ti-chat,#ti-msgs,#ti-table,#ti-order-table,#ti-ai-outer')) return 'interface_click';\n        return '';\n    }\n    document.addEventListener('click', function(ev){ var r = meaningfulClick(ev.target); if (r) mark(r); }, true);\n    document.addEventListener('keydown', function(ev){ var t = ev.target; if (!t) return; if (ev.key === 'Enter' && t.id === 'ti-msg' && String(t.value || '').trim().length > 1) mark('chat_enter'); }, true);\n    document.addEventListener('submit', function(ev){ if (ev.target && ev.target.closest && ev.target.closest('#ti-ai-outer')) mark('form_submit'); }, true);\n\n    window.tiTrackCompanyContactAccess375 = function(db){ return request(db || selectedDb(), 'read', 'read'); };\n    window.tiReadCompanyContactAccess375 = function(db){ return request(db || selectedDb(), 'read', 'read'); };\n    window.tiTrackUsefulContactInteraction399 = mark;\n    setTimeout(function(){ request(selectedDb(), 'read', 'read'); }, 900);\n})();\n<\/script>\n\n\n<script id=\"ti-ai-batch-410-fix\">\n(function(){\n    \"use strict\";\n    if (window.__tiBatchAI410FixApplied) return;\n    window.__tiBatchAI410FixApplied = true;\n    function byId(id){ return document.getElementById(id); }\n    function esc(s){ return String(s == null ? '' : s).replace(\/[&<>\"']\/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c];}); }\n    function buildFd(action, data){\n        var fd = new FormData();\n        fd.append('ti_action', action);\n        fd.append('action', action);\n        Object.keys(data || {}).forEach(function(k){ fd.set(k, data[k]); });\n        return fd;\n    }\n    function postJsonAbortive(fd, ms, label, progressCb){\n        ms = ms || 90000;\n        var ctrl = (typeof AbortController !== 'undefined') ? new AbortController() : null;\n        var start = Date.now();\n        var beat = null;\n        var done = false;\n        return new Promise(function(resolve, reject){\n            var to = setTimeout(function(){\n                if (done) return;\n                done = true;\n                try { if (ctrl) ctrl.abort(); } catch(e) {}\n                if (beat) clearInterval(beat);\n                reject(new Error(label || 'Richiesta AI non completata entro il tempo massimo.'));\n            }, ms);\n            if (progressCb) {\n                beat = setInterval(function(){\n                    if (done) return;\n                    try { progressCb(Math.max(1, Math.round((Date.now()-start)\/1000)), Math.max(0, Math.ceil((ms-(Date.now()-start))\/1000))); } catch(e) {}\n                }, 4000);\n            }\n            fetch(window.tiUrl || window.tiAjaxUrl || window.location.href, {\n                method:'POST', body:fd, credentials:'same-origin', cache:'no-store', signal: ctrl ? ctrl.signal : undefined,\n                headers: {'X-TI-No-Long-Process':'1'}\n            }).then(function(r){\n                return r.text().then(function(raw){\n                    if (!r.ok) throw new Error('HTTP ' + r.status + ': ' + raw.slice(0,240));\n                    try { return JSON.parse(raw); } catch(e) { throw new Error('Risposta server non valida: ' + raw.slice(0,240)); }\n                });\n            }).then(function(data){\n                if (done) return;\n                done = true; clearTimeout(to); if (beat) clearInterval(beat);\n                resolve(data);\n            }).catch(function(err){\n                if (done) return;\n                done = true; clearTimeout(to); if (beat) clearInterval(beat);\n                if (err && err.name === 'AbortError') reject(new Error(label || 'Timeout record AI: passo al successivo.'));\n                else reject(err || new Error(label || 'Errore richiesta AI'));\n            });\n        });\n    }\n    function getData(){ return window.currentDbData || window.tiCurrentDbData || window.tiDbData || null; }\n    function realTable(data, name){\n        var tabs = data && data.Tabelle ? data.Tabelle : null; if (!tabs) return '';\n        if (tabs[name]) return name;\n        var want = String(name||'').toLowerCase().replace(\/[^a-z0-9]\/g,'');\n        var keys = Object.keys(tabs);\n        for (var i=0;i<keys.length;i++){ if (String(keys[i]).toLowerCase().replace(\/[^a-z0-9]\/g,'') === want) return keys[i]; }\n        return '';\n    }\n    function pick(row, keys){\n        for (var i=0;i<keys.length;i++){\n            var k=keys[i];\n            if (Object.prototype.hasOwnProperty.call(row,k) && String(row[k] == null ? '' : row[k]).trim() !== '') return String(row[k]).trim();\n            var want=String(k).toLowerCase().replace(\/[^a-z0-9]\/g,'');\n            var rk=Object.keys(row).find(function(x){ return String(x).toLowerCase().replace(\/[^a-z0-9]\/g,'') === want; });\n            if (rk && String(row[rk] == null ? '' : row[rk]).trim() !== '') return String(row[rk]).trim();\n        }\n        return '';\n    }\n    function mediaCount(row){\n        var n=0;\n        Object.keys(row||{}).forEach(function(k){\n            var key=String(k||'').toLowerCase();\n            if (!\/(immagine|img|foto|logo|doc|document|file|files|allegat)\/.test(key)) return;\n            String(row[k] == null ? '' : row[k]).split(\/[,;\\r\\n]+\/).forEach(function(tok){\n                tok=String(tok||'').trim(); if(!tok) return;\n                if (\/^(0|no|nessuno|nessuna|--|-|null|undefined)$\/i.test(tok)) return;\n                n++;\n            });\n        });\n        return n;\n    }\n    function localTargets(tableName){\n        var data=getData(); if (!data || !data.Tabelle) return null;\n        var tables=String(tableName||'Prodotti').split(',').map(function(x){return x.trim();}).filter(Boolean);\n        var targets=[], withAssoc=0;\n        tables.forEach(function(t){\n            var rt=realTable(data,t); if(!rt) return;\n            var rows=data.Tabelle[rt]; if(!Array.isArray(rows)) return;\n            rows.forEach(function(row,idx){\n                if(!row || typeof row !== 'object') return;\n                var mc=mediaCount(row);\n                if (mc>0) { withAssoc++; return; }\n                var name=pick(row,['Prodotto','Servizio','Nome','Titolo','Articolo']) || pick(row,['Descrizione','Note']) || ('Riga ' + (idx+1));\n                name=String(name).replace(\/\\s+\/g,' ').trim();\n                if (!name || \/^nome\\s+prodotto$\/i.test(name) || \/^descrizione$\/i.test(name)) return;\n                if (name.length>180) name=name.slice(0,177)+'...';\n                targets.push({table:rt,row_index:idx,name:name,has_associations:false,association_count:0});\n            });\n        });\n        return {success:true,data:{targets:targets,total:targets.length,without_associations:targets.length,with_associations:withAssoc,prepare_mode:'local_410',ti_skip_company_notice:true}};\n    }\n    function state(){ if (!window.tiBatchAIGenState || typeof window.tiBatchAIGenState !== 'object') window.tiBatchAIGenState={}; return window.tiBatchAIGenState; }\n    function setCancelText(){ try { var b=byId('ti-loader-close'); if(b) b.innerText='Interrompi generazione'; } catch(e){} }\n    function missingKey(d){ var msg=String((d&&d.message)||''); var code=String((d&&d.code)||''); return code==='missing_openai_key' || \/chiave\\s+openai\\s+mancante\/i.test(msg); }\n    function refreshDb(db){\n        var fd=buildFd('ti_ai_get_db_data',{db:db});\n        return postJsonAbortive(fd,30000,'Rilettura database non completata').then(function(r){ if(r&&r.success&&r.data){ window.currentDbData=r.data; if(window.renderConfig) window.renderConfig(); } }).catch(function(){});\n    }\n    window.runBatchAIGen = function(tableName){\n        var dbSel=byId('ti-ditta'), reply=byId('ti-conf-ai-reply');\n        if(!dbSel || !dbSel.value || dbSel.value==='NEW_DB'){ if(window.tiAlert) window.tiAlert('Seleziona prima una ditta.'); return; }\n        if(!reply){ if(window.tiAlert) window.tiAlert('Area risposta configurazione non disponibile.'); return; }\n        var detailBox=byId('ti-conf-ai-photo-detail');\n        var detail=detailBox && detailBox.value ? String(detailBox.value).trim() : '';\n        var itemTimeout=105000;\n        var targetsPack=localTargets(tableName);\n        var targets=(targetsPack && targetsPack.data && targetsPack.data.targets) ? targetsPack.data.targets : [];\n        var withAssoc=(targetsPack && targetsPack.data ? parseInt(targetsPack.data.with_associations,10)||0 : 0);\n        var logs=[], generated=[], ok=0, fail=0, skip=0, idx=0, total=targets.length;\n        var st=window.tiBatchAIGenState={active:true,cancelRequested:false,pendingConfirm:false,paused:false,generated:generated,db:dbSel.value,tableName:tableName,fix410:true};\n        reply.style.display='block';\n        if(window.showProgressPopup) window.showProgressPopup('Generazione foto AI','Preparazione elenco completata. Avvio generazione immagini senza blocco sul primo prodotto.',{startPercent:3,targetPercent:10,stepMs:250,cancellable:true});\n        setCancelText();\n        if(!total){\n            if(window.hideProgressPopup) window.hideProgressPopup(true,'Nessun elemento da elaborare',{notifyUser:false,remindSave:false});\n            reply.innerHTML='Nessun record senza immagine trovato nella tabella selezionata.<br>Record gi\u00e0 con associazioni: <b>'+withAssoc+'<\/b>.';\n            st.active=false; return;\n        }\n        reply.innerHTML='Generazione immagini AI pronta.<br>Da elaborare senza immagine: <b>'+total+'<\/b>.<br>Gi\u00e0 con immagini specifiche e saltati: <b>'+withAssoc+'<\/b>.<br><span style=\"color:#facc15;\">Se un prodotto resta bloccato oltre '+Math.round(itemTimeout\/1000)+' secondi viene saltato e il batch prosegue.<\/span>';\n        function end(interrupted){\n            st.active=false;\n            Promise.resolve(refreshDb(dbSel.value)).finally(function(){\n                if(window.hideProgressPopup) window.hideProgressPopup(!interrupted,'Generazione foto AI '+(interrupted?'interrotta':'completata'),{remindSave:false,successMessage:'Generate: '+ok+' \/ '+total+'. Errori: '+fail+'. Saltati: '+skip+'.'});\n                reply.innerHTML=(interrupted?'\u26a0\ufe0f Generazione immagini AI interrotta.':'\u2705 Generazione immagini AI completata.')+'<br>Generate e associate: <b>'+ok+'<\/b> \/ '+total+'<br>Saltati: <b>'+skip+'<\/b><br>Errori: <b>'+fail+'<\/b><br><br>'+logs.slice(-160).join('<br>');\n            });\n        }\n        function next(){\n            if(!state().active) return;\n            if(state().cancelRequested){ end(true); return; }\n            if(idx>=total){ end(false); return; }\n            var it=targets[idx]||{};\n            var name=it.name||('Record '+(idx+1));\n            var pct=Math.max(5,Math.min(97,Math.round(((idx+0.15)\/Math.max(1,total))*100)));\n            if(window.updateProgressPopup) window.updateProgressPopup(pct,'Generazione foto AI','Elaborazione '+(idx+1)+' \/ '+total+': '+name,name);\n            setCancelText();\n            reply.innerHTML='Generazione immagini AI in corso...<br>Elaborazione <b>'+(idx+1)+' \/ '+total+'<\/b>: '+esc(name)+'<br><span style=\"color:#94a3b8;\">Timeout record: '+Math.round(itemTimeout\/1000)+' secondi. Se non risponde, passo al successivo.<\/span>';\n            var fd=buildFd('ti_ai_config_action',{db:dbSel.value,mode:'batch_ai_image_one',table:it.table||tableName,row_index:it.row_index,detail_prompt:detail,skip_existing:'1',allow_fallback_logo:'0'});\n            postJsonAbortive(fd,itemTimeout,'Timeout su record: '+name,function(elapsed,left){\n                var sub=Math.min(96, pct + Math.min(5, Math.floor(elapsed\/15)));\n                if(window.updateProgressPopup) window.updateProgressPopup(sub,'Generazione foto AI','Attendo risposta AI per '+(idx+1)+' \/ '+total+': '+name+' ('+elapsed+'s trascorsi, max '+Math.round(itemTimeout\/1000)+'s)',name);\n            }).then(function(res){\n                var d=res&&res.data?res.data:{};\n                if(missingKey(d)){ fail++; logs.push('\u274c Generazione non avviata: '+esc(d.message||'Chiave OpenAI mancante')); state().cancelRequested=true; return; }\n                if(res&&res.success&&d.generated){ ok++; generated.push({table:d.table||it.table||tableName,row_index:(typeof d.row_index!=='undefined'?d.row_index:it.row_index),field:d.image_field||'Immagine',value:d.added_value||d.filename||d.url,name:d.name||name}); logs.push('\u2705 '+esc(d.name||name)+': immagine generata'); }\n                else if(res&&res.success&&d.skipped){ skip++; logs.push('\u23ed\ufe0f '+esc(d.name||name)+': '+esc(d.message||'record saltato')); }\n                else { fail++; logs.push('\u274c '+esc(d.name||name)+': '+esc(d.message||'errore generazione')); }\n            }).catch(function(err){\n                fail++; logs.push('\u23f1\ufe0f '+esc(name)+': '+esc(err&&err.message?err.message:'timeout o errore rete')+'. Record saltato, proseguo.');\n            }).finally(function(){\n                idx++;\n                setTimeout(next, 500);\n            });\n        }\n        \/\/ Preflight leggero: se la chiave manca, stoppa subito prima di restare sul primo prodotto.\n        var pre=buildFd('ti_ai_batch_ai_images_prepare',{db:dbSel.value,tables:tableName,detail_prompt:detail,only_missing:'1'});\n        postJsonAbortive(pre,12000,'Preflight chiave OpenAI non completato').then(function(r){\n            if(r && !r.success && r.data && missingKey(r.data)){ state().active=false; if(window.hideProgressPopup) window.hideProgressPopup(false,'Generazione foto AI non avviata'); reply.innerHTML='<b>Generazione foto AI non avviata.<\/b><br>'+esc(r.data.message||'Chiave OpenAI mancante.'); if(window.tiAlert) window.tiAlert('Generazione foto AI non avviata: '+(r.data.message||'Chiave OpenAI mancante.')); return; }\n            next();\n        }).catch(function(){ next(); });\n    };\n})();\n<\/script>\n\n<script id=\"ti-ai-batch-411-interrupt-fix\">\n(function(){\n    \"use strict\";\n    if (window.__tiBatchAI411FixApplied) return;\n    window.__tiBatchAI411FixApplied = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function esc(s){ return String(s == null ? '' : s).replace(\/[&<>\"']\/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c];}); }\n    function buildFd(action, data){\n        var fd = new FormData();\n        fd.append('ti_action', action);\n        fd.append('action', action);\n        Object.keys(data || {}).forEach(function(k){ fd.set(k, data[k]); });\n        return fd;\n    }\n    function getData(){ return window.currentDbData || window.tiCurrentDbData || window.tiDbData || null; }\n    function getState(){\n        if (!window.tiBatchAIGenState || typeof window.tiBatchAIGenState !== 'object') window.tiBatchAIGenState = {};\n        return window.tiBatchAIGenState;\n    }\n    function isBatchActive(){ var st=getState(); return !!(st && st.active && (st.fix411 || st.fix410 || st.phase === 'batch_ai_images')); }\n    function setCancelText(txt){\n        try {\n            var b = byId('ti-loader-close');\n            if (b) {\n                b.innerText = txt || 'Interrompi generazione';\n                b.style.display = 'inline-block';\n                b.disabled = false;\n                b.removeAttribute('disabled');\n                b.style.width = '100%';\n                b.style.background = '#b91c1c';\n                b.style.color = '#fff';\n            }\n        } catch(e) {}\n    }\n    function showLoader(){ try { if (window.showPluginModal) window.showPluginModal('ti-ai-loader-ov'); else if (window.openModal) window.openModal('ti-ai-loader-ov'); } catch(e){} }\n    function setLoaderMessage(title, sub, item, pct){\n        try {\n            var ov=byId('ti-ai-loader-ov'); if(ov) ov.style.display='flex';\n            var t=byId('ti-loader-title'), s=byId('ti-loader-sub'), i=byId('ti-loader-current-item'), p=byId('ti-loader-percent'), bar=byId('ti-loader-bar'), actions=byId('ti-loader-actions');\n            if (t && title) t.innerText=title;\n            if (s && sub) s.innerText=sub;\n            if (i) { i.style.display = item ? 'block' : 'none'; i.innerText = item ? ('Elemento in lavorazione: ' + item) : ''; }\n            if (typeof pct === 'number') { if(p) p.innerText=Math.max(0,Math.min(100,Math.round(pct)))+'%'; if(bar) bar.style.width=Math.max(0,Math.min(100,Math.round(pct)))+'%'; }\n            if (actions) actions.style.display='block';\n            setCancelText('Interrompi generazione');\n            showLoader();\n        } catch(e) {}\n    }\n    function markReply(html){ var r=byId('ti-conf-ai-reply'); if(r){ r.style.display='block'; r.innerHTML=html; } }\n    function realTable(data, name){\n        var tabs = data && data.Tabelle ? data.Tabelle : null; if (!tabs) return '';\n        if (tabs[name]) return name;\n        var want = String(name||'').toLowerCase().replace(\/[^a-z0-9]\/g,'');\n        var keys = Object.keys(tabs);\n        for (var i=0;i<keys.length;i++){ if (String(keys[i]).toLowerCase().replace(\/[^a-z0-9]\/g,'') === want) return keys[i]; }\n        return '';\n    }\n    function pick(row, keys){\n        for (var i=0;i<keys.length;i++){\n            var k=keys[i];\n            if (Object.prototype.hasOwnProperty.call(row,k) && String(row[k] == null ? '' : row[k]).trim() !== '') return String(row[k]).trim();\n            var want=String(k).toLowerCase().replace(\/[^a-z0-9]\/g,'');\n            var rk=Object.keys(row).find(function(x){ return String(x).toLowerCase().replace(\/[^a-z0-9]\/g,'') === want; });\n            if (rk && String(row[rk] == null ? '' : row[rk]).trim() !== '') return String(row[rk]).trim();\n        }\n        return '';\n    }\n    function mediaCount(row){\n        var n=0;\n        Object.keys(row||{}).forEach(function(k){\n            var key=String(k||'').toLowerCase();\n            if (!\/(immagine|img|foto|logo|doc|document|file|files|allegat)\/.test(key)) return;\n            String(row[k] == null ? '' : row[k]).split(\/[,;\\r\\n]+\/).forEach(function(tok){\n                tok=String(tok||'').trim(); if(!tok) return;\n                if (\/^(0|no|nessuno|nessuna|--|-|null|undefined)$\/i.test(tok)) return;\n                n++;\n            });\n        });\n        return n;\n    }\n    function localTargets(tableName){\n        var data=getData(); if (!data || !data.Tabelle) return {success:false,data:{targets:[],total:0,with_associations:0}};\n        var tables=String(tableName||'Prodotti').split(',').map(function(x){return x.trim();}).filter(Boolean);\n        var targets=[], withAssoc=0;\n        tables.forEach(function(t){\n            var rt=realTable(data,t); if(!rt) return;\n            var rows=data.Tabelle[rt]; if(!Array.isArray(rows)) return;\n            rows.forEach(function(row,idx){\n                if(!row || typeof row !== 'object') return;\n                var mc=mediaCount(row);\n                if (mc>0) { withAssoc++; return; }\n                var name=pick(row,['Prodotto','Servizio','Nome','Titolo','Articolo']) || pick(row,['Descrizione','Note']) || ('Riga ' + (idx+1));\n                name=String(name).replace(\/\\s+\/g,' ').trim();\n                if (!name || \/^nome\\s+prodotto$\/i.test(name) || \/^descrizione$\/i.test(name)) return;\n                if (name.length>180) name=name.slice(0,177)+'...';\n                targets.push({table:rt,row_index:idx,name:name,has_associations:false,association_count:0});\n            });\n        });\n        return {success:true,data:{targets:targets,total:targets.length,without_associations:targets.length,with_associations:withAssoc,prepare_mode:'local_411'}};\n    }\n    function missingKey(d){ var msg=String((d&&d.message)||''); var code=String((d&&d.code)||''); return code==='missing_openai_key' || \/chiave\\s+openai\\s+mancante\/i.test(msg); }\n    function postJsonAbortive(fd, ms, label, progressCb){\n        ms = ms || 90000;\n        var st = getState();\n        var ctrl = (typeof AbortController !== 'undefined') ? new AbortController() : null;\n        st.currentController = ctrl;\n        var start=Date.now(), beat=null, done=false;\n        return new Promise(function(resolve,reject){\n            function finish(fn, val){ if(done) return; done=true; try{ clearTimeout(to); }catch(e){} if(beat) clearInterval(beat); if(st.currentController===ctrl) st.currentController=null; fn(val); }\n            var to=setTimeout(function(){ try{ if(ctrl) ctrl.abort(); }catch(e){} finish(reject,new Error(label || 'Richiesta AI non completata entro il tempo massimo.')); }, ms);\n            if(progressCb){ beat=setInterval(function(){ if(done) return; try{ progressCb(Math.round((Date.now()-start)\/1000), Math.max(0,Math.ceil((ms-(Date.now()-start))\/1000))); }catch(e){} }, 3500); }\n            fetch(window.tiUrl || window.tiAjaxUrl || window.location.href, {method:'POST',body:fd,credentials:'same-origin',cache:'no-store',signal:ctrl?ctrl.signal:undefined,headers:{'X-TI-No-Long-Process':'1'}})\n            .then(function(r){ return r.text().then(function(raw){ if(!r.ok) throw new Error('HTTP '+r.status+': '+raw.slice(0,240)); try{return JSON.parse(raw);}catch(e){throw new Error('Risposta server non valida: '+raw.slice(0,240));} }); })\n            .then(function(data){ finish(resolve,data); })\n            .catch(function(err){\n                if (err && err.name === 'AbortError') finish(reject,new Error(getState().cancelRequested ? 'Interruzione richiesta dall utente.' : (label || 'Timeout record AI: passo al successivo.')));\n                else finish(reject,err || new Error(label || 'Errore richiesta AI'));\n            });\n        });\n    }\n    function refreshDb(db){\n        var fd=buildFd('ti_ai_get_db_data',{db:db});\n        return postJsonAbortive(fd,30000,'Rilettura database non completata').then(function(r){ if(r&&r.success&&r.data){ window.currentDbData=r.data; if(window.renderConfig) window.renderConfig(); } }).catch(function(){});\n    }\n    function requestBatchStop(){\n        var st=getState();\n        if (!st || !st.active) return false;\n        st.cancelRequested = true;\n        st.paused = false;\n        st.pendingConfirm = false;\n        st.stopMode = 'keep';\n        try { if (st.currentController) st.currentController.abort(); } catch(e) {}\n        try { if (window.tiLongProcessState) window.tiLongProcessState.cancelled = false; } catch(e) {}\n        setLoaderMessage('Interruzione generazione foto AI','Interruzione richiesta. Sto fermando il record in corso e chiudo il batch in modo controllato. Le immagini gi\u00e0 generate restano associate.', st.currentName || '', st.lastPercent || 0);\n        markReply('Interruzione richiesta. Il batch viene fermato senza uscire dalla form di progressione. Le immagini gi\u00e0 generate restano associate.');\n        return true;\n    }\n    window.confirmCancelBatchAIGen = requestBatchStop;\n    window.cancelLongAIProcess = function(){\n        if (isBatchActive()) { requestBatchStop(); return; }\n        try {\n            var state = window.tiLongProcessState || {};\n            if (!state.controller || !state.cancellable) { if(window.closeModal) window.closeModal('ti-ai-loader-ov'); return; }\n            state.cancelled = true;\n            try { state.controller.abort(); } catch(e) {}\n            if(window.closeModal) window.closeModal('ti-ai-loader-ov');\n            if(window.tiAlert) window.tiAlert('Processo AI interrotto. Per completarlo dovrai ripetere l operazione.');\n        } catch(e) {}\n    };\n\n    document.addEventListener('click', function(ev){\n        var btn = ev.target && ev.target.closest ? ev.target.closest('#ti-loader-close') : null;\n        if (btn && isBatchActive()) {\n            ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();\n            requestBatchStop();\n        }\n    }, true);\n\n    window.runBatchAIGen = function(tableName){\n        var dbSel=byId('ti-ditta'), reply=byId('ti-conf-ai-reply');\n        if(!dbSel || !dbSel.value || dbSel.value==='NEW_DB'){ if(window.tiAlert) window.tiAlert('Seleziona prima una ditta.'); return; }\n        if(!reply){ if(window.tiAlert) window.tiAlert('Area risposta configurazione non disponibile.'); return; }\n        var detailBox=byId('ti-conf-ai-photo-detail');\n        var detail=detailBox && detailBox.value ? String(detailBox.value).trim() : '';\n        var itemTimeout=105000;\n        var targetsPack=localTargets(tableName);\n        var targets=(targetsPack && targetsPack.data && targetsPack.data.targets) ? targetsPack.data.targets : [];\n        var withAssoc=(targetsPack && targetsPack.data ? parseInt(targetsPack.data.with_associations,10)||0 : 0);\n        var logs=[], generated=[], ok=0, fail=0, skip=0, idx=0, total=targets.length;\n        var st=window.tiBatchAIGenState={active:true,cancelRequested:false,pendingConfirm:false,paused:false,generated:generated,db:dbSel.value,tableName:tableName,fix411:true,phase:'batch_ai_images',currentController:null,lastPercent:3,currentName:''};\n        reply.style.display='block';\n        if(window.showProgressPopup) window.showProgressPopup('Generazione foto AI','Preparazione elenco completata. Avvio generazione immagini.',{startPercent:3,targetPercent:10,stepMs:250,cancellable:true});\n        setCancelText('Interrompi generazione');\n        if(!total){\n            if(window.hideProgressPopup) window.hideProgressPopup(true,'Nessun elemento da elaborare',{notifyUser:false,remindSave:false});\n            reply.innerHTML='Nessun record senza immagine trovato nella tabella selezionata.<br>Record gi\u00e0 con associazioni: <b>'+withAssoc+'<\/b>.';\n            st.active=false; return;\n        }\n        reply.innerHTML='Generazione immagini AI pronta.<br>Da elaborare senza immagine: <b>'+total+'<\/b>.<br>Gi\u00e0 con immagini specifiche e saltati: <b>'+withAssoc+'<\/b>.<br><span style=\"color:#facc15;\">Interrompi generazione ora abortisce il record in corso e resta nella form di avanzamento.<\/span>';\n        function showEnd(interrupted){\n            st.active=false;\n            st.cancelRequested=false;\n            try { if (st.currentController) st.currentController.abort(); } catch(e) {}\n            st.currentController=null;\n            Promise.resolve(refreshDb(dbSel.value)).finally(function(){\n                var title = interrupted ? 'Generazione foto AI interrotta' : 'Generazione foto AI completata';\n                var sub = interrupted ? 'Batch interrotto correttamente. Le immagini gi\u00e0 generate restano associate.' : 'Batch completato.';\n                setLoaderMessage(title, sub, '', 100);\n                var actions=byId('ti-loader-actions');\n                if(actions){\n                    actions.style.display='block';\n                    actions.innerHTML='<button type=\"button\" id=\"ti-loader-close-final\" class=\"ti-btn\" style=\"width:100%;background:#2563eb;color:#fff;\">Chiudi<\/button>';\n                    var fb=byId('ti-loader-close-final');\n                    if(fb) fb.onclick=function(){ try{ if(window.closeModal) window.closeModal('ti-ai-loader-ov'); }catch(e){} try{ if(window.clearLongAIProcess) window.clearLongAIProcess(); }catch(e){} };\n                }\n                reply.innerHTML=(interrupted?'\u26a0\ufe0f Generazione immagini AI interrotta.':'\u2705 Generazione immagini AI completata.')+'<br>Generate e associate: <b>'+ok+'<\/b> \/ '+total+'<br>Saltati: <b>'+skip+'<\/b><br>Errori: <b>'+fail+'<\/b><br><br>'+logs.slice(-160).join('<br>');\n            });\n        }\n        function next(){\n            st=getState();\n            if(!st.active) return;\n            if(st.cancelRequested){ showEnd(true); return; }\n            if(idx>=total){ showEnd(false); return; }\n            var it=targets[idx]||{};\n            var name=it.name||('Record '+(idx+1));\n            var pct=Math.max(5,Math.min(97,Math.round(((idx+0.15)\/Math.max(1,total))*100)));\n            st.currentName=name; st.lastPercent=pct;\n            if(window.updateProgressPopup) window.updateProgressPopup(pct,'Generazione foto AI','Elaborazione '+(idx+1)+' \/ '+total+': '+name,name);\n            setCancelText('Interrompi generazione');\n            reply.innerHTML='Generazione immagini AI in corso...<br>Elaborazione <b>'+(idx+1)+' \/ '+total+'<\/b>: '+esc(name)+'<br><span style=\"color:#94a3b8;\">Premendo Interrompi generazione il record corrente viene abortito e il batch si ferma.<\/span>';\n            var fd=buildFd('ti_ai_config_action',{db:dbSel.value,mode:'batch_ai_image_one',table:it.table||tableName,row_index:it.row_index,detail_prompt:detail,skip_existing:'1',allow_fallback_logo:'0'});\n            postJsonAbortive(fd,itemTimeout,'Timeout su record: '+name,function(elapsed,left){\n                var sub=Math.min(96, pct + Math.min(5, Math.floor(elapsed\/15))); st.lastPercent=sub;\n                if(window.updateProgressPopup) window.updateProgressPopup(sub,'Generazione foto AI','Attendo risposta AI per '+(idx+1)+' \/ '+total+': '+name+' ('+elapsed+'s trascorsi, max '+Math.round(itemTimeout\/1000)+'s)',name);\n                setCancelText('Interrompi generazione');\n            }).then(function(res){\n                var d=res&&res.data?res.data:{};\n                if(missingKey(d)){ fail++; logs.push('\u274c Generazione non avviata: '+esc(d.message||'Chiave OpenAI mancante')); getState().cancelRequested=true; return; }\n                if(res&&res.success&&d.generated){ ok++; generated.push({table:d.table||it.table||tableName,row_index:(typeof d.row_index!=='undefined'?d.row_index:it.row_index),field:d.image_field||'Immagine',value:d.added_value||d.filename||d.url,name:d.name||name}); logs.push('\u2705 '+esc(d.name||name)+': immagine generata'); }\n                else if(res&&res.success&&d.skipped){ skip++; logs.push('\u23ed\ufe0f '+esc(d.name||name)+': '+esc(d.message||'record saltato')); }\n                else { fail++; logs.push('\u274c '+esc(d.name||name)+': '+esc(d.message||'errore generazione')); }\n            }).catch(function(err){\n                if(getState().cancelRequested){ logs.push('\u23f9\ufe0f '+esc(name)+': interruzione richiesta dall utente.'); }\n                else { fail++; logs.push('\u23f1\ufe0f '+esc(name)+': '+esc(err&&err.message?err.message:'timeout o errore rete')+'. Record saltato, proseguo.'); }\n            }).finally(function(){\n                if(getState().cancelRequested){ showEnd(true); return; }\n                idx++;\n                setTimeout(next, 500);\n            });\n        }\n        var pre=buildFd('ti_ai_batch_ai_images_prepare',{db:dbSel.value,tables:tableName,detail_prompt:detail,only_missing:'1'});\n        postJsonAbortive(pre,12000,'Preflight chiave OpenAI non completato').then(function(r){\n            if(r && !r.success && r.data && missingKey(r.data)){ getState().active=false; setLoaderMessage('Generazione foto AI non avviata', r.data.message||'Chiave OpenAI mancante.', '', 100); reply.innerHTML='<b>Generazione foto AI non avviata.<\/b><br>'+esc(r.data.message||'Chiave OpenAI mancante.'); return; }\n            next();\n        }).catch(function(){ next(); });\n    };\n})();\n<\/script>\n\n\n\n\n\n<style id=\"ti-maintenance-final-415-css\">\n#ti-maintenance-ov.ti-maintenance-active-415{\n    position:fixed!important;\n    inset:0!important;\n    display:flex!important;\n    visibility:visible!important;\n    opacity:1!important;\n    pointer-events:auto!important;\n    align-items:center!important;\n    justify-content:center!important;\n    z-index:2147483645!important;\n    background:rgba(0,0,0,.72)!important;\n}\n#ti-maintenance-ov.ti-maintenance-active-415 .ti-maintenance-modal,\n#ti-maintenance-ov.ti-maintenance-active-415 .ti-modal{\n    position:relative!important;\n    width:min(92vw,620px)!important;\n    max-width:620px!important;\n    padding:48px 22px 22px 22px!important;\n    text-align:left!important;\n    z-index:2147483646!important;\n    pointer-events:auto!important;\n}\n#ti-maintenance-ov.ti-maintenance-active-415,\n#ti-maintenance-ov.ti-maintenance-active-415 *,\n#ti-maintenance-ov.ti-maintenance-active-415 button{\n    pointer-events:auto!important;\n}\n#ti-maintenance-close-x-415{\n    position:absolute!important;\n    top:10px!important;\n    right:10px!important;\n    width:34px!important;\n    height:34px!important;\n    min-width:34px!important;\n    min-height:34px!important;\n    border-radius:999px!important;\n    border:1px solid #991b1b!important;\n    background:#dc2626!important;\n    color:#fff!important;\n    font-size:24px!important;\n    font-weight:900!important;\n    line-height:1!important;\n    display:flex!important;\n    align-items:center!important;\n    justify-content:center!important;\n    cursor:pointer!important;\n    z-index:2147483647!important;\n    box-shadow:0 6px 18px rgba(0,0,0,.45)!important;\n}\n#ti-maintenance-close-x-415:hover{background:#b91c1c!important;}\n#ti-maintenance-login-row{\n    display:flex!important;\n    justify-content:center!important;\n    align-items:center!important;\n    text-align:center!important;\n    width:100%!important;\n    margin-top:16px!important;\n}\n#ti-maintenance-login-btn{\n    display:inline-flex!important;\n    visibility:visible!important;\n    opacity:1!important;\n    align-items:center!important;\n    justify-content:center!important;\n    width:auto!important;\n    min-width:min(100%,340px)!important;\n    max-width:100%!important;\n    margin:0 auto!important;\n    padding:11px 20px!important;\n    background:#2563eb!important;\n    color:#fff!important;\n    border:1px solid #1d4ed8!important;\n    border-radius:10px!important;\n    font-weight:900!important;\n    font-size:14px!important;\n    line-height:1.2!important;\n    text-align:center!important;\n    cursor:pointer!important;\n    user-select:none!important;\n    touch-action:manipulation!important;\n    position:relative!important;\n    z-index:2147483647!important;\n    box-shadow:0 8px 22px rgba(37,99,235,.35)!important;\n    pointer-events:auto!important;\n}\n#ti-maintenance-login-btn:hover{background:#1d4ed8!important;}\n#ti-maintenance-close-row-415{\n    display:flex!important;\n    justify-content:center!important;\n    align-items:center!important;\n    width:100%!important;\n    margin-top:14px!important;\n    padding-top:12px!important;\n    border-top:1px solid rgba(148,163,184,.25)!important;\n}\n#ti-maintenance-close-btn-415{\n    display:inline-flex!important;\n    align-items:center!important;\n    justify-content:center!important;\n    min-width:min(100%,180px)!important;\n    margin:0 auto!important;\n    padding:10px 20px!important;\n    background:#4b5563!important;\n    color:#fff!important;\n    border:1px solid #64748b!important;\n    border-radius:9px!important;\n    font-weight:900!important;\n    cursor:pointer!important;\n    pointer-events:auto!important;\n    z-index:2147483647!important;\n}\n#ti-maintenance-close-btn-415:hover{background:#374151!important;}\n#ti-maintenance-ov.ti-maintenance-hidden-415,\nbody.ti-maintenance-login-open-415 #ti-maintenance-ov{\n    display:none!important;\n    visibility:hidden!important;\n    opacity:0!important;\n    pointer-events:none!important;\n}\n#ti-login-ov.ti-maintenance-login-front-415{\n    position:fixed!important;\n    inset:0!important;\n    display:flex!important;\n    visibility:visible!important;\n    opacity:1!important;\n    align-items:center!important;\n    justify-content:center!important;\n    pointer-events:auto!important;\n    z-index:2147483647!important;\n}\n#ti-login-ov.ti-maintenance-login-front-415,\n#ti-login-ov.ti-maintenance-login-front-415 *,\n#ti-login-ov.ti-maintenance-login-front-415 input,\n#ti-login-ov.ti-maintenance-login-front-415 select,\n#ti-login-ov.ti-maintenance-login-front-415 button{\n    pointer-events:auto!important;\n}\n#ti-login-ov.ti-maintenance-login-front-415 .ti-modal{\n    position:relative!important;\n    pointer-events:auto!important;\n    z-index:2147483647!important;\n}\n#ti-login-ov.ti-maintenance-login-front-415 #l-user,\n#ti-login-ov.ti-maintenance-login-front-415 #l-pass,\n#ti-login-ov.ti-maintenance-login-front-415 #ti-direct-login-user,\n#ti-login-ov.ti-maintenance-login-front-415 #ti-direct-login-pass{\n    user-select:text!important;\n    -webkit-user-select:text!important;\n    caret-color:#ffffff!important;\n    cursor:text!important;\n    pointer-events:auto!important;\n    touch-action:auto!important;\n}\n#ti-login-close-x-415{\n    position:absolute!important;\n    top:10px!important;\n    right:10px!important;\n    width:32px!important;\n    height:32px!important;\n    border-radius:999px!important;\n    border:1px solid #991b1b!important;\n    background:#dc2626!important;\n    color:#fff!important;\n    font-size:22px!important;\n    font-weight:900!important;\n    line-height:1!important;\n    display:flex!important;\n    align-items:center!important;\n    justify-content:center!important;\n    z-index:2147483647!important;\n    cursor:pointer!important;\n}\n<\/style>\n<script id=\"ti-maintenance-final-415\">\n(function(){\n    \"use strict\";\n    if (window.__tiMaintenanceFinal415) return;\n    window.__tiMaintenanceFinal415 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function trim(v){ return String(v == null ? '' : v).trim(); }\n    function lower(v){ return trim(v).toLowerCase(); }\n    function stop(ev){\n        if (!ev) return;\n        try { ev.preventDefault(); } catch(e) {}\n        try { ev.stopPropagation(); } catch(e) {}\n        try { if (ev.stopImmediatePropagation) ev.stopImmediatePropagation(); } catch(e) {}\n    }\n    function validDb(v){\n        v = trim(v);\n        if (!v || v === 'NEW' || v === 'NEW_DB' || v === 'Global' || v === 'shopservicemain.json') return '';\n        return v;\n    }\n    function esc(v){\n        v = trim(v);\n        if (window.escapeHtml) return window.escapeHtml(v);\n        return v.replace(\/[&<>\"']\/g, function(ch){ return ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'})[ch] || ch; });\n    }\n    function selectedDbFromFields(){\n        var ids = ['ti-ditta','l-db','l-db-hidden','ti-direct-login-db'];\n        for (var i = 0; i < ids.length; i++) {\n            var el = byId(ids[i]);\n            if (!el) continue;\n            var db = validDb(el.value || el.getAttribute('value') || '');\n            if (db) return db;\n            try {\n                if (el.tagName && el.tagName.toLowerCase() === 'select' && el.selectedIndex >= 0) {\n                    var opt = el.options[el.selectedIndex];\n                    db = validDb(opt && (opt.value || opt.getAttribute('value') || ''));\n                    if (db) return db;\n                }\n            } catch(e) {}\n        }\n        return '';\n    }\n    function rememberedDb(){\n        var vals = [];\n        try {\n            var st = window.tiMaintenanceState || {}, m = st.maintenance || {};\n            vals = [window.__tiMaintenanceDb415, st.db, st.database, st.ditta, m.db, m.database, window.tiCurrentSelectedDb, window.tiSessionDb, window.tiForced, localStorage.getItem('ti_maintenance_db_415'), localStorage.getItem('ti_saved_db'), localStorage.getItem('ti_last_login_db')];\n        } catch(e) {}\n        for (var i = 0; i < vals.length; i++) {\n            var db = validDb(vals[i]);\n            if (db) return db;\n        }\n        return '';\n    }\n    function currentDb(allowRemembered){\n        var db = selectedDbFromFields();\n        if (db) return db;\n        if (allowRemembered) return rememberedDb();\n        return '';\n    }\n    function rememberDb(data){\n        data = data || {};\n        var m = data.maintenance || {};\n        var vals = [data.db, data.database, data.ditta, m.db, m.database, selectedDbFromFields(), window.tiCurrentSelectedDb, window.tiSessionDb, window.tiForced];\n        for (var i = 0; i < vals.length; i++) {\n            var db = validDb(vals[i]);\n            if (!db) continue;\n            window.__tiMaintenanceDb415 = db;\n            try { localStorage.setItem('ti_maintenance_db_415', db); localStorage.setItem('ti_saved_db', db); localStorage.setItem('ti_last_login_db', db); } catch(e) {}\n            try {\n                window.tiMaintenanceState = Object.assign({}, window.tiMaintenanceState || {}, {db:db, database:db});\n                if (!window.tiMaintenanceState.maintenance) window.tiMaintenanceState.maintenance = {};\n                window.tiMaintenanceState.maintenance.db = db;\n            } catch(e) {}\n            return db;\n        }\n        return '';\n    }\n    function state(){ return window.tiMaintenanceState || {}; }\n    function isActive(st){\n        st = st || state();\n        var m = st.maintenance || {};\n        return !!(st.active || st.maintenance_active || st.maintenance_locked || st.force_maintenance_popup || st.show_maintenance_popup || m.active);\n    }\n    function canConfigure(st){ st = st || state(); return !!(st.can_configure || st.canConfigure); }\n    function locked(st){ return isActive(st) && !canConfigure(st); }\n    function normalizePayload(data){\n        data = data || {};\n        rememberDb(data);\n        var maint = data.maintenance || {};\n        var connected = data.connected || {};\n        var prev = window.tiMaintenanceState || {};\n        window.tiMaintenanceState = Object.assign({}, prev, {\n            active: !!(maint.active || data.maintenance_active || data.maintenance_locked),\n            can_configure: !!(data.can_configure || data.canConfigure),\n            connected_count: parseInt(connected.count || data.connected_count || 0, 10) || 0,\n            maintenance: Object.assign({}, maint),\n            connected: connected,\n            database_protected: !!data.database_protected,\n            message: data.message || maint.message || prev.message || ''\n        });\n        rememberDb(window.tiMaintenanceState);\n        return window.tiMaintenanceState;\n    }\n    function messageHtml(st){\n        st = st || state();\n        var m = st.maintenance || {};\n        var message = trim(m.message || st.message || 'Servizio temporaneamente in manutenzione. Il gestore si scusa per il disagio: il servizio sar\u00e0 nuovamente disponibile in tempi brevi.');\n        var by = trim(m.started_by) ? '<br><br><b>Manutenzione avviata da:<\/b> ' + esc(m.started_by) : '';\n        var at = trim(m.started_at) ? '<br><b>Avvio:<\/b> ' + esc(m.started_at) : '';\n        return '<div style=\"text-align:left;line-height:1.55;\">' +\n            '<b>\ud83d\udd27 Servizio temporaneamente in manutenzione.<\/b><br><br>' + esc(message) +\n            '<br><br><span style=\"color:#fde68a;font-weight:900;\">Durante la manutenzione gli utenti ordinari non possono consultare prodotti, servizi, ordini o prenotazioni.<\/span>' +\n            '<br><br><span style=\"color:#bfdbfe;font-weight:700;\">Configuratori e amministratori possono accedere dalla form di configurazione.<\/span>' +\n            by + at + '<\/div>';\n    }\n    function removeConflictingMaintenanceControls(ov){\n        if (!ov) return;\n        try {\n            ov.querySelectorAll('#ti-maintenance-admin-login-row-412,#ti-maintenance-admin-login-412,#ti-maintenance-close-x-409,#ti-maintenance-close-row-409,#ti-maintenance-close-btn-409,#ti-maintenance-close-x-407,#ti-maintenance-close-row-407,#ti-maintenance-admin-open-408,#ti-maintenance-login-row-408,#ti-maintenance-final-close-x-408,#ti-maintenance-final-close-row-408,.ti-maintenance-close-control-407,.ti-maintenance-close-control-408').forEach(function(n){ if (n && n.parentNode) n.parentNode.removeChild(n); });\n        } catch(e) {}\n    }\n    function ensureMaintenanceControls(){\n        var ov = byId('ti-maintenance-ov');\n        if (!ov) return null;\n        var modal = ov.querySelector('.ti-maintenance-modal,.ti-modal') || ov;\n        modal.style.setProperty('position','relative','important');\n        removeConflictingMaintenanceControls(ov);\n        var x = byId('ti-maintenance-close-x-415');\n        if (!x) {\n            x = document.createElement('button');\n            x.type = 'button';\n            x.id = 'ti-maintenance-close-x-415';\n            x.title = 'Chiudi avviso manutenzione';\n            x.setAttribute('aria-label','Chiudi avviso manutenzione');\n            x.textContent = '\u00d7';\n            modal.insertBefore(x, modal.firstChild || null);\n        }\n        var row = byId('ti-maintenance-login-row');\n        if (!row) {\n            row = document.createElement('div');\n            row.id = 'ti-maintenance-login-row';\n            modal.appendChild(row);\n        }\n        if (row.parentNode !== modal) modal.appendChild(row);\n        var login = byId('ti-maintenance-login-btn');\n        if (!login) {\n            login = document.createElement('button');\n            login.id = 'ti-maintenance-login-btn';\n            login.className = 'ti-btn';\n            row.appendChild(login);\n        }\n        if (login.parentNode !== row) row.appendChild(login);\n        login.type = 'button';\n        login.className = (login.className || 'ti-btn').replace(\/\\bti-maintenance-login-\\d+\\b\/g,'').trim() + ' ti-maintenance-login-415';\n        login.textContent = 'Accesso configuratore \/ amministratore';\n        login.removeAttribute('disabled');\n        login.removeAttribute('aria-disabled');\n        login.removeAttribute('onclick');\n        login.setAttribute('data-ti-maintenance-login-415','1');\n        login.onclick = openMaintenanceLogin415;\n        var closeRow = byId('ti-maintenance-close-row-415');\n        if (!closeRow) {\n            closeRow = document.createElement('div');\n            closeRow.id = 'ti-maintenance-close-row-415';\n            var close = document.createElement('button');\n            close.type = 'button';\n            close.id = 'ti-maintenance-close-btn-415';\n            close.className = 'ti-btn';\n            close.textContent = 'Chiudi';\n            closeRow.appendChild(close);\n        }\n        if (closeRow.parentNode !== modal) modal.appendChild(closeRow);\n        var closeBtn = byId('ti-maintenance-close-btn-415');\n        [x, closeBtn, login].forEach(function(btn){\n            if (!btn) return;\n            btn.removeAttribute('disabled');\n            btn.style.setProperty('pointer-events','auto','important');\n            btn.style.setProperty('cursor','pointer','important');\n        });\n        x.onclick = closeMaintenanceToCompanyChoice415;\n        if (closeBtn) closeBtn.onclick = closeMaintenanceToCompanyChoice415;\n        return ov;\n    }\n    function showMaintenance415(st){\n        st = st || state();\n        if (window.__tiMaintenanceLoginOpen415) return false;\n        var db = currentDb(false);\n        if (!db) { hideMaintenance415(true); return false; }\n        if (!locked(st)) { hideMaintenance415(false); return false; }\n        rememberDb({db:db, maintenance:(st.maintenance || {})});\n        var ov = ensureMaintenanceControls();\n        if (!ov) return false;\n        var msg = byId('ti-maintenance-msg');\n        var html = messageHtml(st);\n        if (msg && msg.getAttribute('data-ti-maintenance-html-415') !== html) {\n            msg.innerHTML = html;\n            msg.setAttribute('data-ti-maintenance-html-415', html);\n        }\n        try { document.body.classList.remove('ti-maintenance-login-open-409','ti-maintenance-login-open-412','ti-maintenance-login-open-414','ti-maintenance-login-open-415'); } catch(e) {}\n        ov.className = String(ov.className || '').replace(\/\\bti-maintenance-[^\\s]+\\b\/g, '').trim();\n        ov.classList.add('ti-ov','ti-maintenance-active-415');\n        ov.setAttribute('data-ti-non-closable','0');\n        ov.setAttribute('aria-hidden','false');\n        ov.removeAttribute('inert');\n        ov.style.setProperty('display','flex','important');\n        ov.style.setProperty('visibility','visible','important');\n        ov.style.setProperty('opacity','1','important');\n        ov.style.setProperty('pointer-events','auto','important');\n        ov.style.setProperty('z-index','2147483645','important');\n        try { if (window.updateLoginMaintenanceNotice) window.updateLoginMaintenanceNotice(true); } catch(e) {}\n        try { if (window.updatePluginScrollLock) window.updatePluginScrollLock(); } catch(e) {}\n        return true;\n    }\n    function hideMaintenance415(markHidden){\n        var ov = byId('ti-maintenance-ov');\n        if (!ov) return;\n        ov.classList.remove('ti-maintenance-active-415');\n        if (markHidden) ov.classList.add('ti-maintenance-hidden-415'); else ov.classList.remove('ti-maintenance-hidden-415');\n        ov.style.setProperty('display','none','important');\n        ov.style.setProperty('visibility','hidden','important');\n        ov.style.setProperty('opacity','0','important');\n        ov.style.setProperty('pointer-events','none','important');\n        ov.setAttribute('aria-hidden','true');\n        try { if (window.updatePluginScrollLock) window.updatePluginScrollLock(); } catch(e) {}\n    }\n    function setFieldValue(id, db){\n        var el = byId(id);\n        if (!el || !db) return;\n        try {\n            if (el.tagName && el.tagName.toLowerCase() === 'select') {\n                var found = false;\n                Array.prototype.slice.call(el.options || []).forEach(function(o){ if (o.value === db) found = true; });\n                if (!found) {\n                    var opt = document.createElement('option');\n                    opt.value = db;\n                    opt.textContent = db.replace(\/\\.json$\/i, '');\n                    el.appendChild(opt);\n                }\n            }\n            el.value = db;\n            el.setAttribute('value', db);\n        } catch(e) {}\n    }\n    function syncLoginDb(db){\n        db = validDb(db || currentDb(true));\n        if (!db) return '';\n        ['ti-ditta','l-db','l-db-hidden','ti-direct-login-db'].forEach(function(id){ setFieldValue(id, db); });\n        try { window.__tiMaintenanceDb415 = db; window.tiCurrentSelectedDb = db; window.tiSessionDb = db; localStorage.setItem('ti_maintenance_db_415', db); localStorage.setItem('ti_saved_db', db); localStorage.setItem('ti_last_login_db', db); } catch(e) {}\n        return db;\n    }\n    function writable(el){\n        if (!el) return;\n        try {\n            el.removeAttribute('readonly');\n            el.removeAttribute('disabled');\n            el.removeAttribute('inert');\n            el.readOnly = false;\n            el.disabled = false;\n            if (el.id && lower(el.id).indexOf('pass') >= 0) {\n                el.type = 'password';\n                el.setAttribute('autocomplete','current-password');\n            } else if (el.id && lower(el.id).indexOf('user') >= 0) {\n                el.setAttribute('autocomplete','username');\n            }\n            el.style.setProperty('pointer-events','auto','important');\n            el.style.setProperty('user-select','text','important');\n            el.style.setProperty('-webkit-user-select','text','important');\n            el.style.setProperty('caret-color','#ffffff','important');\n            el.style.setProperty('cursor','text','important');\n            el.style.setProperty('touch-action','auto','important');\n        } catch(e) {}\n    }\n    function ensureLoginClose(){\n        var ov = byId('ti-login-ov');\n        if (!ov) return;\n        var modal = ov.querySelector('.ti-modal') || ov;\n        ['ti-login-close-x-409','ti-login-close-x-412','ti-login-close-x-414'].forEach(function(id){ var old = byId(id); if (old && old.parentNode) old.parentNode.removeChild(old); });\n        var x = byId('ti-login-close-x-415');\n        if (!x) {\n            x = document.createElement('button');\n            x.type = 'button';\n            x.id = 'ti-login-close-x-415';\n            x.textContent = '\u00d7';\n            x.title = 'Chiudi';\n            x.setAttribute('aria-label','Chiudi');\n            modal.appendChild(x);\n        }\n        x.onclick = closeMaintenanceLogin415;\n        try {\n            ov.querySelectorAll('.btn-close,button[onclick*=\"closeModal\"],[data-ti-login-close-409],[data-ti-login-close-412],[data-ti-login-close-414],.ti-modal-x-red').forEach(function(btn){\n                if (!btn || btn.id === 'l-do' || btn.id === 'ti-forgot-password-btn' || btn.id === 'ti-show-direct-login-fallback') return;\n                var text = lower(btn.textContent || btn.value || '');\n                if (text === 'entra' || text.indexOf('registrati') >= 0 || text.indexOf('password') >= 0 || text.indexOf('accesso diretto') >= 0) return;\n                btn.setAttribute('data-ti-login-close-415','1');\n                btn.onclick = closeMaintenanceLogin415;\n            });\n        } catch(e) {}\n    }\n    function openMaintenanceLogin415(ev){\n        stop(ev);\n        var db = currentDb(true);\n        if (db) syncLoginDb(db);\n        var ov = byId('ti-login-ov');\n        if (!ov) {\n            try { if (window.showPluginModal) window.showPluginModal('ti-login-ov'); } catch(e) {}\n            ov = byId('ti-login-ov');\n        }\n        if (!ov) {\n            try { if (window.tiAlert) window.tiAlert('Form login non disponibile nella pagina. Ricaricare la pagina e riprovare.'); } catch(e) {}\n            return false;\n        }\n        window.__tiMaintenanceLoginOpen415 = true;\n        try { document.body.classList.add('ti-maintenance-login-open-415'); } catch(e) {}\n        hideMaintenance415(true);\n        ov.classList.remove('ti-maintenance-login-front-409','ti-maintenance-login-front-412','ti-maintenance-login-front-414');\n        ov.classList.add('ti-modal-open','ti-maintenance-login-front-415');\n        ov.removeAttribute('aria-hidden');\n        ov.removeAttribute('inert');\n        ov.style.setProperty('position','fixed','important');\n        ov.style.setProperty('inset','0','important');\n        ov.style.setProperty('display','flex','important');\n        ov.style.setProperty('align-items','center','important');\n        ov.style.setProperty('justify-content','center','important');\n        ov.style.setProperty('visibility','visible','important');\n        ov.style.setProperty('opacity','1','important');\n        ov.style.setProperty('pointer-events','auto','important');\n        ov.style.setProperty('z-index','2147483647','important');\n        var modal = ov.querySelector('.ti-modal');\n        if (modal) {\n            modal.style.setProperty('position','relative','important');\n            modal.style.setProperty('pointer-events','auto','important');\n            modal.style.setProperty('z-index','2147483647','important');\n        }\n        var note = byId('ti-login-maintenance-note');\n        if (note) {\n            note.innerHTML = 'Servizio in manutenzione: accesso consentito solo a configuratori e amministratori. Chiudi questa finestra per tornare all avviso di manutenzione.';\n            note.style.setProperty('display','block','important');\n            note.style.setProperty('text-align','left','important');\n        }\n        ['l-user','l-pass','ti-direct-login-user','ti-direct-login-pass'].forEach(function(id){ writable(byId(id)); });\n        ensureLoginClose();\n        try { if (window.bindMobileLoginButton) window.bindMobileLoginButton(); } catch(e) {}\n        try { if (window.updatePluginScrollLock) window.updatePluginScrollLock(); } catch(e) {}\n        [60,180,420,900,1600].forEach(function(ms){ setTimeout(function(){ if (window.__tiMaintenanceLoginOpen415) { if (db) syncLoginDb(db); ['l-user','l-pass','ti-direct-login-user','ti-direct-login-pass'].forEach(function(id){ writable(byId(id)); }); ensureLoginClose(); } }, ms); });\n        setTimeout(function(){\n            try {\n                var u = byId('l-user'), p = byId('l-pass');\n                var target = (u && !trim(u.value)) ? u : p;\n                if (target && !(window.tiIsMobileLike && window.tiIsMobileLike())) target.focus({preventScroll:true});\n            } catch(e) {}\n        }, 200);\n        return false;\n    }\n    function closeMaintenanceLogin415(ev){\n        stop(ev);\n        window.__tiMaintenanceLoginOpen415 = false;\n        try { document.body.classList.remove('ti-maintenance-login-open-415'); } catch(e) {}\n        var ov = byId('ti-login-ov');\n        if (ov) {\n            ov.classList.remove('ti-maintenance-login-front-409','ti-maintenance-login-front-412','ti-maintenance-login-front-414','ti-maintenance-login-front-415','ti-modal-open');\n            ov.style.setProperty('display','none','important');\n            ov.style.setProperty('visibility','hidden','important');\n            ov.style.setProperty('opacity','0','important');\n            ov.setAttribute('aria-hidden','true');\n        }\n        try { if (window.updatePluginScrollLock) window.updatePluginScrollLock(); } catch(e) {}\n        setTimeout(function(){\n            var db = currentDb(true);\n            if (db && window.requestMaintenanceStatus) window.requestMaintenanceStatus({silent:true, db:db});\n            else if (db && locked(state())) showMaintenance415(state());\n        }, 100);\n        return false;\n    }\n    function clearDbSelection(){\n        var db = currentDb(true);\n        window.__tiMaintenanceDismissedDb415 = db;\n        window.__tiMaintenanceLoginOpen415 = false;\n        try { localStorage.removeItem('ti_saved_db'); localStorage.removeItem('ti_last_login_db'); localStorage.removeItem('ti_maintenance_db_415'); } catch(e) {}\n        try { window.__tiMaintenanceDb415 = ''; window.tiCurrentSelectedDb = ''; window.tiSessionDb = ''; } catch(e) {}\n        ['ti-ditta','l-db','l-db-hidden','ti-direct-login-db'].forEach(function(id){ var el = byId(id); if (el) { try { el.value = ''; el.setAttribute('value',''); } catch(e) {} } });\n        try { window.tiMaintenanceState = Object.assign({}, state(), {active:false, maintenance:{active:false}, db:'', database:'', dismissed_to_company_choice:true}); } catch(e) {}\n    }\n    function closeMaintenanceToCompanyChoice415(ev){\n        stop(ev);\n        hideMaintenance415(true);\n        clearDbSelection();\n        ['ti-login-ov','ti-conf-ov','ti-profile-ov','ti-new-db-ov','ti-newdb-ov','ti-global-action-ov','ti-purchase-ov','ti-delete-company-ov','ti-mobile-direct-login'].forEach(function(id){\n            var el = byId(id);\n            if (!el) return;\n            try { el.classList.remove('ti-modal-open','ti-maintenance-login-front-415','ti-direct-login-visible','ti-direct-login-manual-open'); } catch(e) {}\n            el.style.setProperty('display','none','important');\n            el.style.setProperty('visibility','hidden','important');\n            el.style.setProperty('opacity','0','important');\n            el.setAttribute('aria-hidden','true');\n        });\n        try { if (window.applyDittaSelectVisual) window.applyDittaSelectVisual(); } catch(e) {}\n        try { if (window.syncDittaTrigger) window.syncDittaTrigger(); } catch(e) {}\n        try { if (window.hideOrderConfirmPanel) window.hideOrderConfirmPanel(); } catch(e) {}\n        try { var msg = byId('ti-msg'); if (msg) msg.value = ''; } catch(e) {}\n        setTimeout(function(){\n            try { if (window.renderDittePopup) window.renderDittePopup(); } catch(e) {}\n            var trigger = byId('ti-ditta-trigger');\n            var pop = byId('ti-ditta-popup');\n            if (trigger) {\n                trigger.textContent = '-- Scegli una ditta --';\n                trigger.style.setProperty('color','#fff','important');\n                trigger.style.setProperty('border-color','#444','important');\n                try { trigger.focus({preventScroll:false}); } catch(e) {}\n            }\n            if (pop) pop.style.setProperty('display','block','important');\n        }, 120);\n        return false;\n    }\n    function isLoginTarget(t){\n        if (!t || !t.closest) return false;\n        if (t.closest('#ti-maintenance-login-btn,[data-ti-maintenance-login-415]')) return true;\n        var ov = t.closest('#ti-maintenance-ov');\n        if (!ov) return false;\n        var btn = t.closest('button,a,[role=\"button\"],input[type=\"button\"],input[type=\"submit\"]');\n        if (!btn) return false;\n        var text = lower(btn.textContent || btn.value || btn.getAttribute('aria-label') || btn.getAttribute('title') || '');\n        return text.indexOf('configuratore') >= 0 || text.indexOf('amministratore') >= 0 || text.indexOf('accesso configurazione') >= 0 || text.indexOf('accedi alla configurazione') >= 0;\n    }\n    function route(ev){\n        var t = ev && ev.target;\n        if (!t || !t.closest) return;\n        if (t.closest('#ti-maintenance-close-x-415,#ti-maintenance-close-btn-415')) return closeMaintenanceToCompanyChoice415(ev);\n        if (isLoginTarget(t)) return openMaintenanceLogin415(ev);\n        if (t.closest('#ti-login-close-x-415,#ti-login-ov [data-ti-login-close-415]')) return closeMaintenanceLogin415(ev);\n        if (locked(state()) && currentDb(false) && !window.__tiMaintenanceLoginOpen415) {\n            if (t.closest('#ti-maintenance-ov,#ti-login-ov,#ti-ditta,#ti-ditta-trigger,#ti-ditta-popup')) return;\n            stop(ev);\n            showMaintenance415(state());\n        }\n    }\n    ['pointerdown','mousedown','touchstart','click'].forEach(function(name){\n        window.addEventListener(name, route, {capture:true, passive:false});\n        document.addEventListener(name, route, {capture:true, passive:false});\n    });\n    document.addEventListener('keydown', function(ev){\n        var t = ev.target;\n        if (ev.key === 'Escape' && window.__tiMaintenanceLoginOpen415) return closeMaintenanceLogin415(ev);\n        if (ev.key === 'Escape' && byId('ti-maintenance-ov') && byId('ti-maintenance-ov').classList.contains('ti-maintenance-active-415')) return closeMaintenanceToCompanyChoice415(ev);\n        if ((ev.key === 'Enter' || ev.key === ' ') && isLoginTarget(t)) return openMaintenanceLogin415(ev);\n        if ((ev.key === 'Enter' || ev.key === ' ') && t && t.closest && t.closest('#ti-maintenance-close-x-415,#ti-maintenance-close-btn-415')) return closeMaintenanceToCompanyChoice415(ev);\n    }, true);\n\n    window.applyMaintenanceStatus = function(data){\n        var st = normalizePayload(data || {});\n        try { if (window.refreshMaintenanceButton) window.refreshMaintenanceButton(); } catch(e) {}\n        try { if (window.updateLoginMaintenanceNotice) window.updateLoginMaintenanceNotice(isActive(st) && !canConfigure(st)); } catch(e) {}\n        if (locked(st)) showMaintenance415(st); else hideMaintenance415(false);\n        return false;\n    };\n    window.showMandatoryMaintenancePopup = function(st){\n        st = st || {};\n        var merged = Object.assign({}, state(), st);\n        if (st.maintenance) merged.maintenance = Object.assign({}, (state().maintenance || {}), st.maintenance);\n        window.tiMaintenanceState = merged;\n        rememberDb(merged);\n        if (locked(merged)) showMaintenance415(merged);\n        return false;\n    };\n    window.requestMaintenanceStatus = function(opts){\n        opts = opts || {};\n        var db = validDb(opts.db || currentDb(false));\n        if (!db) { hideMaintenance415(true); return Promise.resolve({success:true, data:{maintenance:{active:false}, connected:{count:0, users:[]}, can_configure:false}}); }\n        rememberDb({db:db});\n        var fd = new FormData();\n        fd.append('ti_action','ti_ai_maintenance_status');\n        fd.append('action','ti_ai_maintenance_status');\n        fd.append('db', db);\n        var fetcher = window.fetchJsonSafe ? window.fetchJsonSafe : function(url, init){ return fetch(url, init).then(function(r){ return r.json(); }); };\n        return fetcher(window.tiUrl || window.tiAjaxUrl || window.location.href, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store', tiNoLongProcessSignal:true}).then(function(res){\n            if (res && res.success && res.data) window.applyMaintenanceStatus(res.data);\n            return res;\n        }).catch(function(){ return {success:false}; });\n    };\n    window.openMaintenanceLogin = openMaintenanceLogin415;\n    window.openMaintenanceLoginAccess = openMaintenanceLogin415;\n    window.openMaintenanceLogin415 = openMaintenanceLogin415;\n    window.closeMaintenanceLogin415 = closeMaintenanceLogin415;\n    window.closeMaintenanceToCompanyChoice = closeMaintenanceToCompanyChoice415;\n\n    var ditta = byId('ti-ditta');\n    if (ditta && !ditta.dataset.tiMaintenance415Bound) {\n        ditta.dataset.tiMaintenance415Bound = '1';\n        ditta.addEventListener('change', function(){\n            var db = selectedDbFromFields();\n            if (db) rememberDb({db:db});\n            setTimeout(function(){ if (window.requestMaintenanceStatus) window.requestMaintenanceStatus({silent:true}); }, 150);\n        }, false);\n    }\n    function boot(){\n        ensureMaintenanceControls();\n        var db = selectedDbFromFields();\n        if (db && window.requestMaintenanceStatus) window.requestMaintenanceStatus({silent:true, db:db});\n    }\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ boot(); setTimeout(boot, 500); setTimeout(boot, 1600); });\n    else { boot(); setTimeout(boot, 500); setTimeout(boot, 1600); }\n    setInterval(function(){\n        ensureMaintenanceControls();\n        if (window.__tiMaintenanceLoginOpen415) {\n            var db = currentDb(true); if (db) syncLoginDb(db);\n            ['l-user','l-pass','ti-direct-login-user','ti-direct-login-pass'].forEach(function(id){ writable(byId(id)); });\n            ensureLoginClose();\n            return;\n        }\n        if (currentDb(false) && locked(state())) showMaintenance415(state());\n    }, 2500);\n})();\n<\/script>\n\n\n<style id=\"ti-maintenance-toggle-color-417\">\n#ti-ai-outer #ti-maint-toggle-btn,\n#ti-conf-ov #ti-maint-toggle-btn{\n    color:#fff!important;\n    font-weight:900!important;\n    text-shadow:none!important;\n    opacity:1!important;\n}\n#ti-ai-outer #ti-maint-toggle-btn.ti-maint-on,\n#ti-ai-outer #ti-maint-toggle-btn[data-ti-maint-state=\"on\"],\n#ti-conf-ov #ti-maint-toggle-btn.ti-maint-on,\n#ti-conf-ov #ti-maint-toggle-btn[data-ti-maint-state=\"on\"]{\n    background:#dc2626!important;\n    background-color:#dc2626!important;\n    border:1px solid #991b1b!important;\n    color:#fff!important;\n    box-shadow:0 0 0 2px rgba(220,38,38,.20)!important;\n}\n#ti-ai-outer #ti-maint-toggle-btn.ti-maint-off,\n#ti-ai-outer #ti-maint-toggle-btn[data-ti-maint-state=\"off\"],\n#ti-conf-ov #ti-maint-toggle-btn.ti-maint-off,\n#ti-conf-ov #ti-maint-toggle-btn[data-ti-maint-state=\"off\"]{\n    background:#16a34a!important;\n    background-color:#16a34a!important;\n    border:1px solid #15803d!important;\n    color:#fff!important;\n    box-shadow:0 0 0 2px rgba(22,163,74,.18)!important;\n}\n#ti-ai-outer #ti-maint-toggle-btn:hover,\n#ti-conf-ov #ti-maint-toggle-btn:hover{\n    filter:brightness(1.06)!important;\n}\n<\/style>\n<script id=\"ti-maintenance-toggle-color-417\">\n(function(){\n    \"use strict\";\n    if (window.__tiMaintenanceToggleColor417) return;\n    window.__tiMaintenanceToggleColor417 = true;\n    function activeFromState(){\n        var st = window.tiMaintenanceState || {};\n        var m = st.maintenance || {};\n        return !!(st.active || st.maintenance_active || st.maintenance_locked || m.active);\n    }\n    function paintMaintenanceButton(active){\n        var btn = document.getElementById('ti-maint-toggle-btn');\n        if (!btn) return;\n        active = !!active;\n        var bg = active ? '#dc2626' : '#16a34a';\n        var border = active ? '#991b1b' : '#15803d';\n        btn.textContent = active ? 'Manutenzione ON' : 'Manutenzione OFF';\n        btn.title = active ? 'Manutenzione attiva: premi per disattivarla dopo conferma.' : 'Manutenzione disattivata: premi per attivarla dopo conferma.';\n        btn.setAttribute('data-ti-maint-state', active ? 'on' : 'off');\n        btn.setAttribute('aria-pressed', active ? 'true' : 'false');\n        btn.classList.remove('ti-maint-on','ti-maint-off');\n        btn.classList.add(active ? 'ti-maint-on' : 'ti-maint-off');\n        btn.style.setProperty('background', bg, 'important');\n        btn.style.setProperty('background-color', bg, 'important');\n        btn.style.setProperty('border', '1px solid ' + border, 'important');\n        btn.style.setProperty('color', '#fff', 'important');\n        btn.style.setProperty('box-shadow', active ? '0 0 0 2px rgba(220,38,38,.20)' : '0 0 0 2px rgba(22,163,74,.18)', 'important');\n        btn.style.setProperty('opacity', '1', 'important');\n    }\n    var previousRefresh = window.refreshMaintenanceButton;\n    window.refreshMaintenanceButton = function(){\n        try { if (typeof previousRefresh === 'function') previousRefresh.apply(this, arguments); } catch(e) {}\n        paintMaintenanceButton(activeFromState());\n    };\n    var previousApply = window.applyMaintenanceStatus;\n    window.applyMaintenanceStatus = function(data){\n        var ret;\n        try { ret = (typeof previousApply === 'function') ? previousApply.apply(this, arguments) : undefined; } catch(e) { ret = undefined; }\n        setTimeout(function(){ paintMaintenanceButton(activeFromState()); }, 0);\n        setTimeout(function(){ paintMaintenanceButton(activeFromState()); }, 120);\n        return ret;\n    };\n    function boot(){ paintMaintenanceButton(activeFromState()); }\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ boot(); setTimeout(boot, 250); setTimeout(boot, 1200); });\n    else { boot(); setTimeout(boot, 250); setTimeout(boot, 1200); }\n})();\n<\/script>\n\n\n<script id=\"ti-final-manual-images-activity-420\">\n(function(){\n    \"use strict\";\n    if (window.__tiFinalManualImagesActivity420) return;\n    window.__tiFinalManualImagesActivity420 = true;\n    function byId(id){ return document.getElementById(id); }\n    function esc(v){ return String(v == null ? \"\" : v).replace(\/[&<>\"']\/g,function(c){return {\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",\"\\\"\":\"&quot;\",\"'\":\"&#39;\"}[c];}); }\n    function fd(action, data){ var f=new FormData(); f.append(\"ti_action\",action); f.append(\"action\",action); Object.keys(data||{}).forEach(function(k){ f.set(k,data[k]); }); return f; }\n    function ajax(form, ms, label, progressCb){\n        ms=ms||90000;\n        var st=window.tiBatchAIGenState||{};\n        var ctrl=(typeof AbortController!==\"undefined\")?new AbortController():null;\n        if(st && st.active) st.currentController=ctrl;\n        var start=Date.now(), done=false, timer=null, beat=null;\n        return new Promise(function(resolve,reject){\n            function finish(fn,val){ if(done) return; done=true; try{clearTimeout(timer);}catch(e){} try{if(beat) clearInterval(beat);}catch(e){} if(st && st.currentController===ctrl) st.currentController=null; fn(val); }\n            timer=setTimeout(function(){ try{if(ctrl) ctrl.abort();}catch(e){} finish(reject,new Error(label||\"Richiesta non completata.\")); }, ms);\n            if(progressCb) beat=setInterval(function(){ if(done) return; try{progressCb(Math.max(1,Math.round((Date.now()-start)\/1000)));}catch(e){} }, 3500);\n            fetch(window.tiUrl||window.tiAjaxUrl||window.location.href,{method:\"POST\",body:form,credentials:\"same-origin\",cache:\"no-store\",signal:ctrl?ctrl.signal:undefined,headers:{\"X-TI-No-Long-Process\":\"1\"}})\n            .then(function(r){return r.text().then(function(raw){ if(!r.ok) throw new Error(\"HTTP \"+r.status+\": \"+raw.slice(0,240)); try{return JSON.parse(raw);}catch(e){throw new Error(\"Risposta server non valida: \"+raw.slice(0,240));} });})\n            .then(function(json){ finish(resolve,json); })\n            .catch(function(err){ if(err && err.name===\"AbortError\") finish(reject,new Error((window.tiBatchAIGenState&&window.tiBatchAIGenState.cancelRequested)?\"Interruzione richiesta dall utente.\":(label||\"Timeout richiesta.\"))); else finish(reject,err||new Error(label||\"Errore richiesta.\")); });\n        });\n    }\n    function confirmBox(html, yes, no, y, n){\n        if(typeof window.tiConfirmYesNoAction===\"function\") { window.tiConfirmYesNoAction(html, yes, no||function(){}, y||\"SI\", n||\"NO\"); return; }\n        if(window.confirm(String(html||\"\").replace(\/<br\\s*\\\/?>\/gi,\"\\n\").replace(\/<[^>]+>\/g,\"\"))) yes(); else if(no) no();\n    }\n    function scrubMainFormText(root){\n        try{\n            var walker=document.createTreeWalker(root||document.body, NodeFilter.SHOW_TEXT);\n            var n; while((n=walker.nextNode())){ if(n.nodeValue && \/main form\/i.test(n.nodeValue)) n.nodeValue=n.nodeValue.replace(\/main form\/gi,\"servizio Shop & Service\"); }\n        }catch(e){}\n    }\n    if(document.body){ scrubMainFormText(document.body); try{ new MutationObserver(function(muts){ muts.forEach(function(m){ (m.addedNodes||[]).forEach(function(n){ if(n.nodeType===1) scrubMainFormText(n); else if(n.nodeType===3 && \/main form\/i.test(n.nodeValue||\"\")) n.nodeValue=n.nodeValue.replace(\/main form\/gi,\"servizio Shop & Service\"); }); }); }).observe(document.body,{childList:true,subtree:true}); }catch(e){} }\n\n    window.askColumnHelp = function(tbl,col){\n        var dbEl=byId(\"ti-ditta\"), db=dbEl&&dbEl.value?dbEl.value:\"NEW_DB\";\n        var table=String(tbl||\"\").replace(\/[\\\"\u201c\u201d]\/g,\"\").trim();\n        var field=String(col||\"\").replace(\/\\\\'\/g,\"'\").replace(\/[\\\"\u201c\u201d]\/g,\"\").trim();\n        var en=String(window.currLang||document.documentElement.lang||\"it\").toLowerCase().indexOf(\"en\")===0;\n        var ov=byId(\"ti-col-help-ov\"), title=byId(\"col-help-title\"), box=byId(\"col-help-content\");\n        var req=Date.now()+\"-\"+Math.random().toString(36).slice(2);\n        window.tiColumnHelpActiveRequestId=req;\n        if(ov){ov.setAttribute(\"data-ti-help-request-id\",req);ov.setAttribute(\"data-ti-help-table\",table);ov.setAttribute(\"data-ti-help-column\",field);} \n        if(title) title.innerText=field?(en?\"Field info: \":\"Info campo: \")+field+\" [\"+table+\"]\":(en?\"Table info: \":\"Info tabella: \")+table;\n        if(window.tiUpdateColumnHelpCompanyHeader) window.tiUpdateColumnHelpCompanyHeader();\n        if(box) box.innerHTML=en?\"<i>\u23f3 Searching manual, schema and company data...<\/i>\":\"<i>\u23f3 Consulto manuale, schema, dati ditta e documenti collegati...<\/i>\";\n        try{ if(window.openModal) window.openModal(\"ti-col-help-ov\"); if(window.tiPromoteModalFront355) window.tiPromoteModalFront355(\"ti-col-help-ov\"); }catch(e){}\n        ajax(fd(\"ti_ai_config_action\",{db:db,mode:\"manual_help\",table:table,field:field,lang:en?\"en\":\"it\"}),45000,en?\"Manual search timed out.\":\"Ricerca manuale non completata.\").then(function(res){\n            if(window.tiColumnHelpActiveRequestId!==req) return;\n            if(ov && ov.getAttribute(\"data-ti-help-request-id\")!==req) return;\n            if(box) box.innerHTML=(res&&res.success&&res.data&&res.data.reply)?String(res.data.reply):(en?\"No operational information found.\":\"Non ho trovato informazioni operative puntuali.\");\n            try{ if(window.applyLayoutLanguage) window.applyLayoutLanguage(false); if(window.tiPromoteModalFront355) window.tiPromoteModalFront355(\"ti-col-help-ov\"); }catch(e){}\n        }).catch(function(err){ if(box) box.innerHTML=\"<span style='color:#ef4444;'>\"+esc(en?\"Manual\/table search did not respond: \":\"La ricerca info tabelle\/manuale non ha risposto: \")+esc(err&&err.message?err.message:\"errore\")+\"<\/span>\"; });\n    };\n\n    function specificKey(k){ var key=String(k||\"\").toLowerCase(); if(\/\\b(doc|docs|document|documento|documenti|file|files|allegat|pdf|xls|word|excel)\\b\/.test(key)) return false; return \/\\b(immagine|immagini|img|image|images|foto|photo|picture|gallery|galleria|logo)\\b\/.test(key); }\n    function tokenImage(t){ t=String(t||\"\").trim(); if(!t || \/^(0|no|nessuno|nessuna|--|-|null|undefined)$\/i.test(t)) return false; if(\/ti_action=ti_ai_get_image\/i.test(t)) return true; return \/\\.(jpg|jpeg|png|webp|gif|bmp|avif)(?:[?#].*)?$\/i.test(t.split(\"?\")[0].split(\"#\")[0]); }\n    function specCount(row){ var n=0; Object.keys(row||{}).forEach(function(k){ if(!specificKey(k)) return; String(row[k]==null?\"\":row[k]).split(\/[,;\\r\\n]+\/).forEach(function(x){ if(tokenImage(x)) n++; }); }); return n; }\n    function anyMedia(row){ var n=0; Object.keys(row||{}).forEach(function(k){ if(!\/(immagine|img|foto|logo|doc|document|file|files|allegat)\/i.test(k)) return; String(row[k]==null?\"\":row[k]).split(\/[,;\\r\\n]+\/).forEach(function(x){ x=String(x||\"\").trim(); if(x && !\/^(0|no|nessuno|nessuna|--|-|null|undefined)$\/i.test(x)) n++; }); }); return n; }\n    function getDb(){ return window.currentDbData||window.tiCurrentDbData||window.tiDbData||null; }\n    function realTable(data,name){ var t=data&&data.Tabelle?data.Tabelle:null; if(!t) return \"\"; if(t[name]) return name; var want=String(name||\"\").toLowerCase().replace(\/[^a-z0-9]\/g,\"\"); return Object.keys(t).find(function(k){return String(k).toLowerCase().replace(\/[^a-z0-9]\/g,\"\")===want;})||\"\"; }\n    function pick(row,ks){ for(var i=0;i<ks.length;i++){ var want=String(ks[i]).toLowerCase().replace(\/[^a-z0-9]\/g,\"\"); var k=Object.keys(row||{}).find(function(x){return String(x).toLowerCase().replace(\/[^a-z0-9]\/g,\"\")===want;}); if(k && String(row[k]||\"\").trim()) return String(row[k]).trim(); } return \"\"; }\n    function targetsFor(tableName, includeExisting){\n        var data=getDb(), out=[], no=0, yes=0; if(!data||!data.Tabelle) return {targets:[],no:0,yes:0};\n        String(tableName||\"Prodotti\").split(\",\").map(function(x){return x.trim();}).filter(Boolean).forEach(function(t){ var rt=realTable(data,t); var rows=rt&&Array.isArray(data.Tabelle[rt])?data.Tabelle[rt]:[]; rows.forEach(function(row,idx){ if(!row||typeof row!==\"object\") return; var name=pick(row,[\"Prodotto\",\"Servizio\",\"Nome\",\"Titolo\",\"Articolo\"])||pick(row,[\"Descrizione\",\"Dettaglio\",\"Note\"])||(\"Riga \"+(idx+1)); name=name.replace(\/\\s+\/g,\" \").trim(); if(!name || \/^nome\\s+prodotto$\/i.test(name) || \/^descrizione$\/i.test(name)) return; if(name.length>180) name=name.slice(0,177)+\"...\"; var sc=specCount(row), ac=anyMedia(row); var item={table:rt,row_index:idx,name:name,specific_image_count:sc,association_count:ac}; if(sc>0){ yes++; if(includeExisting) out.push(item); } else { no++; out.push(item); } }); });\n        return {targets:out,no:no,yes:yes};\n    }\n    function batchState(){ if(!window.tiBatchAIGenState||typeof window.tiBatchAIGenState!==\"object\") window.tiBatchAIGenState={}; return window.tiBatchAIGenState; }\n    function setLoader(title,sub,item,pct){ try{ var ov=byId(\"ti-ai-loader-ov\"); if(ov) ov.style.display=\"flex\"; var t=byId(\"ti-loader-title\"),s=byId(\"ti-loader-sub\"),i=byId(\"ti-loader-current-item\"),p=byId(\"ti-loader-percent\"),b=byId(\"ti-loader-bar\"),a=byId(\"ti-loader-actions\"); if(t&&title)t.innerText=title; if(s&&sub)s.innerText=sub; if(i){i.style.display=item?\"block\":\"none\";i.innerText=item?\"Elemento in lavorazione: \"+item:\"\";} if(typeof pct===\"number\"){var pc=Math.max(0,Math.min(100,Math.round(pct))); if(p)p.innerText=pc+\"%\"; if(b)b.style.width=pc+\"%\";} if(a)a.style.display=\"block\"; var c=byId(\"ti-loader-close\"); if(c){c.innerText=\"Interrompi generazione\";c.disabled=false;c.style.display=\"inline-block\";c.style.background=\"#b91c1c\";c.style.color=\"#fff\";} if(window.showPluginModal) window.showPluginModal(\"ti-ai-loader-ov\"); else if(window.openModal) window.openModal(\"ti-ai-loader-ov\"); }catch(e){} }\n    function stopBatch(){ var st=batchState(); if(!st.active||!st.fix420) return false; st.cancelRequested=true; try{if(st.currentController) st.currentController.abort();}catch(e){} setLoader(\"Interruzione generazione foto AI\",\"Sto fermando il record in corso senza chiudere la form di avanzamento.\",st.currentName||\"\",st.lastPercent||0); return true; }\n    window.cancelLongAIProcess=function(){ if(stopBatch()) return; if(window.closeModal) window.closeModal(\"ti-ai-loader-ov\"); };\n    window.confirmCancelBatchAIGen=stopBatch;\n    function refreshDb(db){ return ajax(fd(\"ti_ai_get_db_data\",{db:db}),35000,\"Rilettura database non completata\").then(function(r){ if(r&&r.success&&r.data){ window.currentDbData=r.data; if(window.renderConfig) window.renderConfig(); } }).catch(function(){}); }\n    window.runBatchAIGen=function(tableName){ startBatch(tableName,{confirmed:false,includeExisting:false,generic:false}); };\n    function startBatch(tableName,opt){\n        opt=opt||{}; var dbEl=byId(\"ti-ditta\"), reply=byId(\"ti-conf-ai-reply\"); if(!dbEl||!dbEl.value||dbEl.value===\"NEW_DB\"){ if(window.tiAlert) window.tiAlert(\"Seleziona prima una ditta.\"); return; } if(!reply){ if(window.tiAlert) window.tiAlert(\"Area risposta configurazione non disponibile.\"); return; }\n        var detailEl=byId(\"ti-conf-ai-photo-detail\"), detail=detailEl&&detailEl.value?String(detailEl.value).trim():\"\";\n        var pack=targetsFor(tableName,!!opt.includeExisting);\n        if(!opt.confirmed && !opt.generic && pack.yes>0){ confirmBox(\"La generazione immagini AI pu\u00f2 creare immagini aggiuntive rispetto a quelle gi\u00e0 presenti.<br><br>Voci senza immagine specifica: <b>\"+pack.no+\"<\/b><br>Voci gi\u00e0 con immagine specifica: <b>\"+pack.yes+\"<\/b><br><br>Confermi la generazione aggiuntiva?\",function(){startBatch(tableName,{confirmed:true,includeExisting:true,generic:false});},function(){startBatch(tableName,{confirmed:true,includeExisting:false,generic:false});},\"GENERA ANCHE AGGIUNTIVE\",\"SOLO MANCANTI\"); return; }\n        var targets=pack.targets,total=targets.length,idx=0,ok=0,fail=0,skip=0,logs=[],generated=[]; var st=window.tiBatchAIGenState={active:true,fix420:true,cancelRequested:false,currentController:null,currentName:\"\",lastPercent:3,generated:generated,db:dbEl.value,tableName:tableName}; reply.style.display=\"block\";\n        if(window.showProgressPopup) window.showProgressPopup(\"Generazione foto AI\", opt.generic?\"Retry con informazioni pi\u00f9 generiche tramite AI.\":\"Preparazione elenco record per immagini AI.\",{startPercent:3,targetPercent:10,stepMs:250,cancellable:true});\n        if(!total){ st.active=false; if(window.hideProgressPopup) window.hideProgressPopup(true,\"Nessun elemento da elaborare\",{notifyUser:false,remindSave:false}); reply.innerHTML=\"Nessun record senza immagine specifica trovato.<br>Record gi\u00e0 con immagine specifica: <b>\"+pack.yes+\"<\/b>.\"; return; }\n        reply.innerHTML=\"Generazione immagini AI pronta.<br>Da elaborare: <b>\"+total+\"<\/b>.<br>Voci senza immagine specifica: <b>\"+pack.no+\"<\/b>.<br>Voci gi\u00e0 con immagine specifica: <b>\"+pack.yes+\"<\/b>.\"+(opt.includeExisting?\"<br><span style='color:#facc15;'>Generazione aggiuntiva confermata.<\/span>\":\"<br><span style='color:#94a3b8;'>Saranno elaborate solo le voci senza immagine specifica.<\/span>\")+(opt.generic?\"<br><b>Modalit\u00e0 retry AI generica attiva.<\/b>\":\"\");\n        function end(interrupted){ st.active=false; st.cancelRequested=false; try{if(st.currentController) st.currentController.abort();}catch(e){} refreshDb(dbEl.value).finally(function(){ var rem=targetsFor(tableName,false).no; setLoader(interrupted?\"Generazione foto AI interrotta\":\"Generazione foto AI completata\", interrupted?\"Batch interrotto correttamente.\":(rem>0?\"Conclusa con immagini ancora mancanti.\":\"Batch completato.\"),\"\",100); var a=byId(\"ti-loader-actions\"); if(a){a.innerHTML=\"<button type='button' id='ti-loader-close-final-420' class='ti-btn' style='width:100%;background:#2563eb;color:#fff;'>Chiudi<\/button>\"; var b=byId(\"ti-loader-close-final-420\"); if(b)b.onclick=function(){ if(window.closeModal) window.closeModal(\"ti-ai-loader-ov\"); };} reply.innerHTML=(interrupted?\"\u26a0\ufe0f Generazione immagini AI interrotta.\":\"\u2705 Generazione immagini AI completata.\")+\"<br>Generate e associate: <b>\"+ok+\"<\/b> \/ \"+total+\"<br>Saltati: <b>\"+skip+\"<\/b><br>Errori: <b>\"+fail+\"<\/b><br>Ancora senza immagine specifica: <b>\"+rem+\"<\/b><br><br>\"+logs.slice(-160).join(\"<br>\"); if(!interrupted && !opt.generic && rem>0){ confirmBox(\"La procedura \u00e8 conclusa, ma alcune immagini non sono state generate.<br><br>Immagini ancora mancanti: <b>\"+rem+\"<\/b><br><br>Vuoi provare ad utilizzare informazioni pi\u00f9 generiche tramite AI, come una chat AI generica, usando tutte le informazioni disponibili?\",function(){startBatch(tableName,{confirmed:true,includeExisting:false,generic:true});},function(){},\"USA AI GENERICA\",\"NON ORA\"); } }); }\n        function next(){ st=batchState(); if(!st.active) return; if(st.cancelRequested){end(true);return;} if(idx>=total){end(false);return;} var it=targets[idx]||{}, name=it.name||(\"Record \"+(idx+1)), pct=Math.max(5,Math.min(97,Math.round(((idx+0.15)\/Math.max(1,total))*100))); st.currentName=name; st.lastPercent=pct; if(window.updateProgressPopup) window.updateProgressPopup(pct,\"Generazione foto AI\",(opt.generic?\"Retry AI generica - \":\"\")+\"Elaborazione \"+(idx+1)+\" \/ \"+total+\": \"+name,name); reply.innerHTML=\"Generazione immagini AI in corso...<br>Elaborazione <b>\"+(idx+1)+\" \/ \"+total+\"<\/b>: \"+esc(name); var form=fd(\"ti_ai_config_action\",{db:dbEl.value,mode:\"batch_ai_image_one\",table:it.table||tableName,row_index:it.row_index,detail_prompt:detail,skip_existing:opt.includeExisting?\"0\":\"1\",allow_fallback_logo:\"0\",prompt_mode:opt.generic?\"generic_ai\":\"record\"}); ajax(form,130000,\"Timeout su record: \"+name,function(sec){ if(window.updateProgressPopup) window.updateProgressPopup(Math.min(96,pct+Math.min(5,Math.floor(sec\/18))),\"Generazione foto AI\",\"Attendo risposta AI per \"+(idx+1)+\" \/ \"+total+\": \"+name+\" (\"+sec+\"s)\",name); }).then(function(res){ var d=res&&res.data?res.data:{}; if(d.code===\"missing_openai_key\"||\/chiave\\s+openai\/i.test(String(d.message||\"\"))){fail++; logs.push(\"\u274c \"+esc(d.message||\"Chiave OpenAI mancante\")); st.cancelRequested=true; return;} if(res&&res.success&&d.generated){ok++; generated.push({table:d.table||it.table||tableName,row_index:d.row_index!==undefined?d.row_index:it.row_index,field:d.image_field||\"Immagine\",value:d.added_value||d.filename||d.url,name:d.name||name}); logs.push(\"\u2705 \"+esc(d.name||name)+\": immagine generata\");} else if(res&&res.success&&d.skipped){skip++; logs.push(\"\u23ed\ufe0f \"+esc(d.name||name)+\": \"+esc(d.message||\"record saltato\"));} else {fail++; logs.push(\"\u274c \"+esc(d.name||name)+\": \"+esc(d.message||\"errore generazione\"));} }).catch(function(err){ if(st.cancelRequested) logs.push(\"\u23f9\ufe0f \"+esc(name)+\": interruzione richiesta.\"); else {fail++; logs.push(\"\u23f1\ufe0f \"+esc(name)+\": \"+esc(err&&err.message?err.message:\"timeout o errore rete\")+\". Record saltato.\");} }).finally(function(){ if(batchState().cancelRequested){end(true);return;} idx++; setTimeout(next,450); }); }\n        ajax(fd(\"ti_ai_batch_ai_images_prepare\",{db:dbEl.value,tables:tableName,detail_prompt:detail,only_missing:opt.includeExisting?\"0\":\"1\"}),18000,\"Preflight chiave OpenAI non completato\").then(function(r){ if(r&&!r.success&&r.data&&(r.data.code===\"missing_openai_key\"||\/chiave\\s+openai\/i.test(String(r.data.message||\"\")))){st.active=false; setLoader(\"Generazione foto AI non avviata\",r.data.message||\"Chiave OpenAI mancante\",\"\",100); reply.innerHTML=\"<b>Generazione foto AI non avviata.<\/b><br>\"+esc(r.data.message||\"Chiave OpenAI mancante.\"); return;} next(); }).catch(function(){ next(); });\n    }\n\n    \/* Report Attivit\u00e0 charts: dati, raggruppa per e popup attesa *\/\n    function actRows(){ try{ return (typeof window.getFilteredActivityRows===\"function\"?window.getFilteredActivityRows(false):[]).filter(Boolean); }catch(e){ return []; } }\n    function val(row,metric){ if(metric===\"Conteggio\") return 1; var n=window.parseReportNum?window.parseReportNum(row&&row[metric],0):parseFloat(String(row&&row[metric]||\"0\").replace(\",\",\".\")); return isNaN(n)?0:n; }\n    function groupLabel(row,field){ var v=row&&row[field]; if(v===undefined||v===null||String(v).trim()===\"\") return \"N\/D\"; return String(v).trim(); }\n    function rowsKeys(rows){ var pref=[\"Username\",\"Descrizione\",\"Esito\",\"Operatore\",\"Dispositivo\",\"Sito\",\"Prodotto\",\"Servizio\",\"Data\",\"Ora\",\"Stato\",\"Nota\",\"ID QRcode\"]; var out=[]; pref.forEach(function(k){ if(out.indexOf(k)<0) out.push(k); }); rows.forEach(function(r){ Object.keys(r||{}).forEach(function(k){ if(k.indexOf(\"__\")===0) return; if(out.indexOf(k)<0 && String(k).trim()) out.push(k); }); }); return out.filter(function(k){ return rows.some(function(r){ return r&&r[k]!==undefined&&r[k]!==null&&String(r[k]).trim()!==\"\"; }) || pref.indexOf(k)>=0; }); }\n    window.activityReportMetricOptions=[\"Conteggio\",\"Prezzo\",\"Quantit\u00e0\",\"Totale\"];\n    window.getDefaultActivityMetricFlags=function(){ return {Conteggio:true,Prezzo:false,\"Quantit\u00e0\":false,Totale:true}; };\n    window.getActivityAvailableMetrics=function(){ return [\"Conteggio\",\"Prezzo\",\"Quantit\u00e0\",\"Totale\"]; };\n    window.getActivitySelectedMetrics=function(){ var flags=(window.activityReportState&&window.activityReportState.metricFlags)||window.getDefaultActivityMetricFlags(); var sel=window.getActivityAvailableMetrics().filter(function(k){return !!flags[k];}); return sel.length?sel:[\"Conteggio\"]; };\n    window.getActivityAvailableGroupFields=function(){ var r=actRows(); var keys=rowsKeys(r); return keys.length?keys:[\"Username\",\"Descrizione\",\"Data\",\"Stato\"]; };\n    window.getActivitySelectedGroupField=function(){ var a=window.getActivityAvailableGroupFields(); var c=(window.activityReportState&&window.activityReportState.groupField)||\"Username\"; return a.indexOf(c)>=0?c:(a[0]||\"Username\"); };\n    function showChartWait(msg){ try{ if(window.showProgressPopup) window.showProgressPopup(\"Grafici attivit\u00e0\", msg||\"Sto preparando i dati per grafici e torte.\",{startPercent:5,targetPercent:92,stepMs:350,cancellable:false}); }catch(e){} }\n    function hideChartWait(){ setTimeout(function(){ try{ if(window.hideProgressPopup) window.hideProgressPopup(true,\"Grafici attivit\u00e0 pronti\",{notifyUser:false,remindSave:false}); }catch(e){} },550); }\n    window.setActivityGroupField=function(field){ if(!window.activityReportState) window.activityReportState={}; window.activityReportState.groupField=field||\"Username\"; if(window.saveActivityReportPrefs) window.saveActivityReportPrefs(); showChartWait(\"Aggiorno raggruppamento grafici attivit\u00e0.\"); if(window.renderActivityReport) window.renderActivityReport(); hideChartWait(); };\n    window.setActivityMetricFlag=function(metric,checked){ if(!window.activityReportState) window.activityReportState={}; if(!window.activityReportState.metricFlags) window.activityReportState.metricFlags=window.getDefaultActivityMetricFlags(); window.activityReportState.metricFlags[metric]=!!checked; if(!window.getActivitySelectedMetrics().length) window.activityReportState.metricFlags.Conteggio=true; if(window.saveActivityReportPrefs) window.saveActivityReportPrefs(); showChartWait(\"Aggiorno metriche grafici attivit\u00e0.\"); if(window.renderActivityReport) window.renderActivityReport(); hideChartWait(); };\n    function drawNo(ctx,text){ ctx.fillStyle=\"#fff\"; ctx.font=\"14px Arial\"; ctx.fillText(text,20,28); }\n    window.drawPie3D=function(canvasId,rows){ var cv=byId(canvasId); if(!cv||!cv.getContext)return; var ctx=cv.getContext(\"2d\"); rows=rows||actRows(); var metrics=window.getActivitySelectedMetrics(); var gf=window.getActivitySelectedGroupField(); var cols=metrics.length>1?2:1, bw=420,bh=285; cv.width=Math.max(860,cols*bw+40); cv.height=Math.max(340,Math.ceil(metrics.length\/cols)*bh+40); ctx.clearRect(0,0,cv.width,cv.height); var colors=[\"#38bdf8\",\"#22c55e\",\"#f59e0b\",\"#ef4444\",\"#a855f7\",\"#14b8a6\",\"#fb7185\",\"#84cc16\"]; if(!rows.length){drawNo(ctx,\"Nessun dato attivit\u00e0 disponibile\");return;} metrics.forEach(function(m,mi){ var groups={}; rows.forEach(function(r){ var v=val(r,m); groups[groupLabel(r,gf)]=(groups[groupLabel(r,gf)]||0)+v; }); var entries=Object.entries(groups).filter(function(e){return e[1]>0;}).sort(function(a,b){return b[1]-a[1];}).slice(0,10); if(!entries.length&&rows.length){ groups={}; rows.forEach(function(r){groups[groupLabel(r,gf)]=(groups[groupLabel(r,gf)]||0)+1;}); entries=Object.entries(groups).sort(function(a,b){return b[1]-a[1];}).slice(0,10); m=\"Conteggio\"; } var ox=20+(mi%cols)*bw, oy=20+Math.floor(mi\/cols)*bh, cx=ox+120, cy=oy+122, rad=72, total=entries.reduce(function(s,e){return s+e[1];},0), start=-Math.PI\/2; ctx.fillStyle=\"#fff\"; ctx.font=\"bold 13px Arial\"; ctx.fillText(m,ox+8,oy+18); if(!entries.length){ctx.fillText(\"Nessun dato\",ox+8,oy+42);return;} entries.forEach(function(e,i){ var ang=(e[1]\/total)*Math.PI*2; ctx.fillStyle=\"rgba(0,0,0,.35)\"; ctx.beginPath(); ctx.moveTo(cx,cy+14); ctx.arc(cx,cy+14,rad,start,start+ang); ctx.closePath(); ctx.fill(); ctx.fillStyle=colors[i%colors.length]; ctx.beginPath(); ctx.moveTo(cx,cy); ctx.arc(cx,cy,rad,start,start+ang); ctx.closePath(); ctx.fill(); var mid=start+ang\/2, tx=cx+Math.cos(mid)*(rad+28)+(Math.cos(mid)>=0?8:-150), ty=cy+Math.sin(mid)*(rad+28); ctx.fillStyle=\"#fff\"; ctx.font=\"11px Arial\"; ctx.fillText(String(e[0]).slice(0,24),tx,ty-8); ctx.fillStyle=\"#cbd5e1\"; ctx.fillText((window.fmtNum?window.fmtNum(e[1]):e[1]),tx,ty+6); start+=ang; }); ctx.fillStyle=\"#94a3b8\"; ctx.font=\"11px Arial\"; ctx.fillText(\"Raggruppa per: \"+gf,ox+8,oy+bh-12); }); };\n    window.drawTrend3D=function(canvasId,rows){ var cv=byId(canvasId); if(!cv||!cv.getContext)return; var ctx=cv.getContext(\"2d\"); rows=rows||actRows(); var metrics=window.getActivitySelectedMetrics(); var gf=window.getActivitySelectedGroupField(); var cols=metrics.length>1?2:1,bw=470,bh=320; cv.width=Math.max(980,cols*bw+40); cv.height=Math.max(380,Math.ceil(metrics.length\/cols)*bh+40); ctx.clearRect(0,0,cv.width,cv.height); var colors=[\"#38bdf8\",\"#22c55e\",\"#f59e0b\",\"#ef4444\",\"#a855f7\",\"#14b8a6\"]; if(!rows.length){drawNo(ctx,\"Nessun dato attivit\u00e0 disponibile\");return;} metrics.forEach(function(m,mi){ var timeline={}, totals={}; rows.forEach(function(r,i){ var key=(r.Data||\"\")+(r.Ora?\" \"+r.Ora:\"\"); if(!key.trim()) key=\"Riga \"+(i+1); var g=groupLabel(r,gf), v=val(r,m); if(v<=0&&m!==\"Conteggio\") return; if(!timeline[key]) timeline[key]={}; timeline[key][g]=(timeline[key][g]||0)+v; totals[g]=(totals[g]||0)+v; }); if(!Object.keys(timeline).length&&rows.length){ m=\"Conteggio\"; rows.forEach(function(r,i){ var key=(r.Data||\"\")+(r.Ora?\" \"+r.Ora:\"\"); if(!key.trim()) key=\"Riga \"+(i+1); var g=groupLabel(r,gf); if(!timeline[key]) timeline[key]={}; timeline[key][g]=(timeline[key][g]||0)+1; totals[g]=(totals[g]||0)+1; }); } var entries=Object.entries(timeline).sort(function(a,b){return a[0].localeCompare(b[0]);}).slice(-12); var groups=Object.entries(totals).sort(function(a,b){return b[1]-a[1];}).slice(0,4).map(function(e){return e[0];}); var ox=20+(mi%cols)*bw, oy=20+Math.floor(mi\/cols)*bh, base=oy+245,left=ox+54,step=34,barW=Math.max(8,Math.floor(34\/Math.max(1,groups.length))), max=Math.max(1,...entries.flatMap(function(e){return groups.map(function(g){return e[1][g]||0;});})); ctx.fillStyle=\"#fff\"; ctx.font=\"bold 13px Arial\"; ctx.fillText(m,ox+8,oy+18); if(!entries.length||!groups.length){ctx.fillText(\"Nessun dato\",ox+8,oy+42);return;} ctx.strokeStyle=\"#94a3b8\"; ctx.beginPath(); ctx.moveTo(left,oy+34); ctx.lineTo(left,base); ctx.lineTo(left+step*entries.length,base); ctx.stroke(); entries.forEach(function(e,i){ var gx=left+10+i*step; groups.forEach(function(g,gi){ var vv=e[1][g]||0,h=(vv\/max)*160,x=gx+gi*(barW+3); ctx.fillStyle=\"rgba(0,0,0,.25)\"; ctx.fillRect(x+4,base-h+4,barW,h); ctx.fillStyle=colors[gi%colors.length]; ctx.fillRect(x,base-h,barW,h); if(h>0){ctx.fillStyle=\"#fff\";ctx.font=\"10px Arial\";ctx.fillText(window.fmtNum?window.fmtNum(vv):vv,x-2,base-h-6);} }); ctx.save(); ctx.fillStyle=\"#fff\"; ctx.font=\"10px Arial\"; ctx.translate(gx,base+14); ctx.rotate(-0.6); ctx.fillText(String(e[0]).slice(0,16),0,0); ctx.restore(); }); groups.forEach(function(g,gi){ var lx=ox+8+gi*110,ly=oy+34; ctx.fillStyle=colors[gi%colors.length]; ctx.fillRect(lx,ly,12,12); ctx.fillStyle=\"#fff\"; ctx.font=\"11px Arial\"; ctx.fillText(String(g).slice(0,16),lx+18,ly+10); }); ctx.fillStyle=\"#cbd5e1\"; ctx.font=\"11px Arial\"; ctx.fillText(\"Raggruppa per: \"+gf,ox+8,oy+bh-12); }); };\n    if(typeof window.renderActivityReport===\"function\" && !window.renderActivityReport.__ti420){ var oldRender=window.renderActivityReport; window.renderActivityReport=function(){ var view=(window.activityReportState&&window.activityReportState.view)||\"table\"; if(view===\"pie\"||view===\"trend\") showChartWait(\"Sto preparando dati e grafici attivit\u00e0.\"); var ret=oldRender.apply(this,arguments); if(view===\"pie\"||view===\"trend\") hideChartWait(); return ret; }; window.renderActivityReport.__ti420=true; }\n    if(typeof window.setActivityReportView===\"function\" && !window.setActivityReportView.__ti420){ var oldSetView=window.setActivityReportView; window.setActivityReportView=function(view){ if(view===\"pie\"||view===\"trend\") showChartWait(\"Sto preparando il grafico attivit\u00e0.\"); var ret=oldSetView.apply(this,arguments); if(view===\"pie\"||view===\"trend\") hideChartWait(); return ret; }; window.setActivityReportView.__ti420=true; }\n})();\n<\/script>\n\n\n<script id=\"ti-final-manual-idle-421\">\n(function(){\n    \"use strict\";\n    if (window.__tiFinalManualIdle421) return;\n    window.__tiFinalManualIdle421 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function esc(v){ return String(v == null ? \"\" : v).replace(\/[&<>\"']\/g, function(c){ return {\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",\"\\\"\":\"&quot;\",\"'\":\"&#39;\"}[c]; }); }\n    function stripTags(v){ return String(v || \"\").replace(\/<[^>]+>\/g, \" \").replace(\/\\s+\/g, \" \").trim(); }\n    function norm(v){ return String(v || \"\").toLowerCase().normalize ? String(v || \"\").toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g,'').replace(\/[^a-z0-9]\/g,'') : String(v || \"\").toLowerCase().replace(\/[^a-z0-9]\/g,''); }\n    function getDb(){ return window.currentDbData || window.tiCurrentDbData || window.tiDbData || null; }\n    function getTables(){ var d = getDb(); return d && d.Tabelle && typeof d.Tabelle === \"object\" ? d.Tabelle : {}; }\n    function realTable(name){ var tables = getTables(), want = norm(name); if (tables[name]) return name; return Object.keys(tables).find(function(k){ return norm(k) === want; }) || String(name || \"\"); }\n    function rowsFor(table){ var rt = realTable(table), rows = getTables()[rt]; return Array.isArray(rows) ? rows : []; }\n    function keysForRows(rows){ var out = []; rows.slice(0, 30).forEach(function(r){ Object.keys(r || {}).forEach(function(k){ if (String(k).trim() && out.indexOf(k) < 0) out.push(k); }); }); return out; }\n    function sampleValues(rows, field, limit){ var want = norm(field), vals = []; rows.forEach(function(r){ if (!r || typeof r !== \"object\") return; var key = Object.keys(r).find(function(k){ return norm(k) === want; }) || field; var v = r[key]; if (v === undefined || v === null) return; v = String(v).replace(\/\\s+\/g, \" \").trim(); if (!v || \/^(--|null|undefined)$\/i.test(v)) return; if (v.length > 90) v = v.slice(0, 87) + \"...\"; if (vals.indexOf(v) < 0) vals.push(v); }); return vals.slice(0, limit || 4); }\n    function guessFieldUse(table, field){\n        var f = String(field || \"\"), k = norm(f);\n        if (!f) return \"\";\n        if (\/^(id|idriga|recordid)$\/.test(k)) return \"identifica la riga\/record e di solito non va modificato manualmente.\";\n        if (\/stato|status\/.test(k)) return \"indica se il record \u00e8 Attivo, Sospeso, Cancellato o in altra condizione operativa.\";\n        if (\/data\/.test(k)) return \"contiene una data; usare preferibilmente il formato GG\/MM\/AAAA.\";\n        if (\/ora|orario\/.test(k)) return \"contiene un orario; usare preferibilmente il formato HH:MM. Vuoto o --:-- indica orario standard quando previsto.\";\n        if (\/prezzo|costo|iva|vat|totale|importo\/.test(k)) return \"contiene un valore economico o percentuale; usare numeri coerenti, con virgola o punto decimale.\";\n        if (\/quantita|disponibilita|riordino\/.test(k)) return \"contiene quantit\u00e0, disponibilit\u00e0 o dato di riordino; usare numeri o indicazioni operative chiare.\";\n        if (\/email|mail\/.test(k)) return \"contiene un indirizzo email usato per comunicazioni, notifiche o contatti.\";\n        if (\/tel|telefono|cell|sms\/.test(k)) return \"contiene un numero telefonico usato per contatti o SMS, quando il servizio lo consente.\";\n        if (\/immagine|foto|image|logo|doc|file|allegat\/.test(k)) return \"contiene nomi file o collegamenti ad allegati\/immagini associati al record.\";\n        if (\/istruzion|indicazion|nota|note\/.test(k)) return \"contiene istruzioni operative o note usate dall'interfaccia e dalla AI quando pertinenti.\";\n        if (\/username|utente|ruolo|password\/.test(k)) return \"\u00e8 collegato alla gestione utenti, accessi, ruoli o credenziali.\";\n        if (\/descrizion|prodotto|servizio|attivita|sito|operatore|dispositivo\/.test(k)) return \"descrive o collega l'elemento operativo usato da ricerca, ordini, servizi o prenotazioni.\";\n        return \"va compilato in modo coerente con la tabella e con gli altri campi della stessa riga.\";\n    }\n    function localHelpHtml(table, field, en){\n        var rt = realTable(table), rows = rowsFor(rt), keys = keysForRows(rows), exists = !!(getTables()[rt]);\n        var html = [];\n        if (field) {\n            var samples = sampleValues(rows, field, 5);\n            html.push(\"<div style='text-align:left;line-height:1.45;'>\");\n            html.push(\"<b>Info campo:<\/b> \" + esc(field) + \"<br>\");\n            html.push(\"<b>Tabella:<\/b> \" + esc(rt || table || \"-\") + \"<br>\");\n            html.push(\"<b>Uso operativo:<\/b> \" + esc(guessFieldUse(rt, field)) + \"<br>\");\n            html.push(\"<b>Righe tabella disponibili:<\/b> \" + esc(rows.length) + \"<br>\");\n            if (samples.length) html.push(\"<b>Esempi valori presenti:<\/b><br><ul style='margin:6px 0 8px 18px;padding:0;'>\" + samples.map(function(v){ return \"<li>\" + esc(v) + \"<\/li>\"; }).join(\"\") + \"<\/ul>\");\n            else html.push(\"<b>Esempi valori presenti:<\/b> nessun valore compilato trovato per questo campo.<br>\");\n            html.push(\"<b>Controllo consigliato:<\/b> dopo la modifica premere Salva Modifiche e verificare che il record sia coerente con Stato, Data\/Ora e campi collegati.<br>\");\n            if (!exists) html.push(\"<span style='color:#fbbf24;'>Nota: la tabella non \u00e8 stata trovata nei dati locali caricati; verificare nome tabella o ricaricare la configurazione.<\/span><br>\");\n            html.push(\"<div id='ti-help-server-421' style='margin-top:10px;color:#93c5fd;'>Cerco anche riferimenti da manuale\/documenti...<\/div>\");\n            html.push(\"<\/div>\");\n        } else {\n            html.push(\"<div style='text-align:left;line-height:1.45;'>\");\n            html.push(\"<b>Info tabella:<\/b> \" + esc(rt || table || \"-\") + \"<br>\");\n            html.push(\"<b>Righe disponibili:<\/b> \" + esc(rows.length) + \"<br>\");\n            html.push(\"<b>Campi rilevati:<\/b><br>\");\n            if (keys.length) html.push(\"<div style='margin:6px 0 8px 0;display:flex;flex-wrap:wrap;gap:5px;'>\" + keys.slice(0, 80).map(function(k){ return \"<span style='border:1px solid #475569;border-radius:999px;padding:2px 7px;background:#0f172a;'>\" + esc(k) + \"<\/span>\"; }).join(\"\") + \"<\/div>\");\n            else html.push(\"nessun campo rilevato nei dati locali.<br>\");\n            html.push(\"<b>Uso operativo:<\/b> aprire la tabella in Configurazione, compilare i campi principali e salvare. Le istruzioni e gli allegati vengono usati anche dalla AI quando pertinenti.<br>\");\n            if (!exists) html.push(\"<span style='color:#fbbf24;'>Nota: tabella non trovata nei dati locali caricati; verificare nome o ricaricare la configurazione.<\/span><br>\");\n            html.push(\"<div id='ti-help-server-421' style='margin-top:10px;color:#93c5fd;'>Cerco anche riferimenti da manuale\/documenti...<\/div>\");\n            html.push(\"<\/div>\");\n        }\n        var out = html.join(\"\");\n        if (en && window.tiTranslateHtmlStaticToEn) {\n            try { out = window.tiTranslateHtmlStaticToEn(out); } catch(e) {}\n        }\n        return out;\n    }\n    function fd(action, data){ var f = new FormData(); f.append(\"ti_action\", action); f.append(\"action\", action); Object.keys(data || {}).forEach(function(k){ f.set(k, data[k]); }); return f; }\n    function ajax(form, ms){\n        var url = window.tiUrl || window.tiAjaxUrl || window.ajaxurl || window.location.href;\n        var ctrl = typeof AbortController !== \"undefined\" ? new AbortController() : null;\n        var timer;\n        return new Promise(function(resolve, reject){\n            timer = setTimeout(function(){ try { if (ctrl) ctrl.abort(); } catch(e) {} reject(new Error(\"timeout\")); }, ms || 18000);\n            var init = {method:\"POST\", body:form, credentials:\"same-origin\", cache:\"no-store\"};\n            if (ctrl) init.signal = ctrl.signal;\n            fetch(url, init).then(function(r){ return r.text(); }).then(function(t){\n                clearTimeout(timer);\n                try { resolve(JSON.parse(t)); } catch(e) { reject(new Error(\"risposta non valida\")); }\n            }).catch(function(e){ clearTimeout(timer); reject(e); });\n        });\n    }\n    window.tiBuildLocalTableHelp421 = localHelpHtml;\n    window.askColumnHelp = function(tbl, col){\n        var dbEl = byId(\"ti-ditta\"), db = dbEl && dbEl.value ? dbEl.value : \"NEW_DB\";\n        var table = String(tbl || \"\").replace(\/[\\\"\u201c\u201d]\/g, \"\").trim();\n        var field = String(col || \"\").replace(\/\\\\'\/g, \"'\").replace(\/[\\\"\u201c\u201d]\/g, \"\").trim();\n        var en = String(window.currLang || document.documentElement.lang || \"it\").toLowerCase().indexOf(\"en\") === 0;\n        var ov = byId(\"ti-col-help-ov\"), title = byId(\"col-help-title\"), box = byId(\"col-help-content\");\n        var req = Date.now() + \"-\" + Math.random().toString(36).slice(2);\n        window.tiColumnHelpActiveRequestId = req;\n        if (ov) { ov.setAttribute(\"data-ti-help-request-id\", req); ov.setAttribute(\"data-ti-help-table\", table); ov.setAttribute(\"data-ti-help-column\", field); }\n        if (title) title.innerText = field ? ((en ? \"Field info: \" : \"Info campo: \") + field + \" [\" + table + \"]\") : ((en ? \"Table info: \" : \"Info tabella: \") + table);\n        if (window.tiUpdateColumnHelpCompanyHeader) window.tiUpdateColumnHelpCompanyHeader();\n        if (box) box.innerHTML = localHelpHtml(table, field, en);\n        try { if (window.openModal) window.openModal(\"ti-col-help-ov\"); if (window.tiPromoteModalFront355) window.tiPromoteModalFront355(\"ti-col-help-ov\"); } catch(e) {}\n        ajax(fd(\"ti_ai_config_action\", {db:db, mode:\"manual_help\", table:table, field:field, lang:en ? \"en\" : \"it\"}), 16000).then(function(res){\n            if (window.tiColumnHelpActiveRequestId !== req) return;\n            if (ov && ov.getAttribute(\"data-ti-help-request-id\") !== req) return;\n            var reply = res && res.success && res.data ? String(res.data.reply || \"\") : \"\";\n            if (!reply || \/Informazione operativa non trovata|No operational information\/i.test(stripTags(reply))) {\n                var srv = byId(\"ti-help-server-421\");\n                if (srv) srv.innerHTML = \"<span style='color:#fbbf24;'>Manuale\/documenti non hanno aggiunto altri dettagli. \u00c8 visibile la risposta locale da schema e dati caricati.<\/span>\";\n                return;\n            }\n            if (box) box.innerHTML = \"<div style='text-align:left;line-height:1.45;'>\" + reply + \"<\/div>\";\n            try { if (window.applyLayoutLanguage) window.applyLayoutLanguage(false); if (window.tiPromoteModalFront355) window.tiPromoteModalFront355(\"ti-col-help-ov\"); } catch(e) {}\n        }).catch(function(err){\n            if (window.tiColumnHelpActiveRequestId !== req) return;\n            var srv = byId(\"ti-help-server-421\");\n            if (srv) srv.innerHTML = \"<span style='color:#fbbf24;'>Risposta locale mostrata. Ricerca manuale\/documenti non disponibile ora.<\/span>\";\n        });\n        return false;\n    };\n\n    function visible(el){ if (!el) return false; var st = window.getComputedStyle ? getComputedStyle(el) : el.style; var hasBox = !!(el.offsetWidth || el.offsetHeight || (el.getClientRects && el.getClientRects().length)); return !!(st && st.display !== \"none\" && st.visibility !== \"hidden\" && hasBox); }\n    window.tiIsConfigContextActive421 = function(){\n        var conf = byId(\"ti-conf-ov\") || byId(\"ti-config-ov\") || byId(\"ti-configurator-ov\");\n        if (visible(conf)) return true;\n        if (document.body && \/\\bconfig\/i.test(document.body.className || \"\")) return true;\n        return !!(byId(\"ti-conf-cnt\") && visible(byId(\"ti-conf-cnt\")));\n    };\n    window.tiIsProcessActiveForIdle421 = function(){\n        var b = window.tiBatchAIGenState || {};\n        if (b.active || b.pendingConfirm || b.paused || b.currentController) return true;\n        var g = window.globalActionProgressState || {};\n        if (g.active) return true;\n        var oc = window.tiOrphanCleanupState || window.orphanCleanupState || {};\n        if (oc.active || oc.running || oc.inProgress) return true;\n        var imp = window.tiImportPreviewState || window.tiPurchaseImportState || window.tiGlobalActionState || {};\n        if (imp.active || imp.running || imp.inProgress || imp.saving) return true;\n        var loader = byId(\"ti-ai-loader-ov\");\n        if (visible(loader)) {\n            var txt = stripTags(loader.textContent || \"\").toLowerCase();\n            if (!\/(completat|interrott|non avviat|chiudi)\/.test(txt) || \/(in corso|prepar|elabor|generaz|attendo|salvat|import|grafici|cancellazione)\/.test(txt)) return true;\n        }\n        return false;\n    };\n    window.tiShouldHoldSessionForConfigProcess421 = function(){ return window.tiIsConfigContextActive421() && window.tiIsProcessActiveForIdle421(); };\n    function holdSession(reason){\n        window.idleLastUserActivityAt = Date.now();\n        window.idleFirstWarningAt = 0;\n        window.idleFinalWarningAt = 0;\n        window.idleCloseCheckAt = 0;\n        window.idleAwaitingChoice = false;\n        try { clearTimeout(window.idleTimer1); clearTimeout(window.idleTimer2); clearTimeout(window.idleTimer3); } catch(e) {}\n        window.idleTimer1 = setTimeout(function(){ if (window.scheduleIdleFirstWarning) window.scheduleIdleFirstWarning(); }, 60000);\n        var now = Date.now();\n        if (!window.__tiLastHoldNotice421 || now - window.__tiLastHoldNotice421 > 120000) {\n            window.__tiLastHoldNotice421 = now;\n            var msg = \"Processo in corso in Configurazione: la sessione non viene chiusa automaticamente. Deciderai tu se interrompere o continuare.\";\n            try { if (window.addMsg) window.addMsg(\"ai\", msg, msg); } catch(e) {}\n        }\n        return false;\n    }\n    var oldScheduleIdle = window.scheduleIdleFirstWarning;\n    window.scheduleIdleFirstWarning = function(){ if (window.tiShouldHoldSessionForConfigProcess421 && window.tiShouldHoldSessionForConfigProcess421()) return holdSession(\"schedule\"); return oldScheduleIdle ? oldScheduleIdle.apply(this, arguments) : undefined; };\n    var oldFirst = window.showIdleFirstWarning;\n    window.showIdleFirstWarning = function(){ if (window.tiShouldHoldSessionForConfigProcess421 && window.tiShouldHoldSessionForConfigProcess421()) return holdSession(\"first\"); return oldFirst ? oldFirst.apply(this, arguments) : undefined; };\n    var oldFinal = window.showIdleFinalWarning;\n    window.showIdleFinalWarning = function(){ if (window.tiShouldHoldSessionForConfigProcess421 && window.tiShouldHoldSessionForConfigProcess421()) return holdSession(\"final\"); return oldFinal ? oldFinal.apply(this, arguments) : undefined; };\n    var oldCloseIdle = window.closeIdleSessionIfStillInactive;\n    window.closeIdleSessionIfStillInactive = function(){ if (window.tiShouldHoldSessionForConfigProcess421 && window.tiShouldHoldSessionForConfigProcess421()) return holdSession(\"close\"); return oldCloseIdle ? oldCloseIdle.apply(this, arguments) : undefined; };\n    var oldScheduleClarify = window.scheduleClarifyClosure;\n    window.scheduleClarifyClosure = function(){ if (window.tiShouldHoldSessionForConfigProcess421 && window.tiShouldHoldSessionForConfigProcess421()) return holdSession(\"clarify-schedule\"); return oldScheduleClarify ? oldScheduleClarify.apply(this, arguments) : undefined; };\n    var oldFresh = window.startFreshChatAfterClarifyLimit;\n    window.startFreshChatAfterClarifyLimit = function(){ if (window.tiShouldHoldSessionForConfigProcess421 && window.tiShouldHoldSessionForConfigProcess421()) return holdSession(\"clarify-close\"); return oldFresh ? oldFresh.apply(this, arguments) : undefined; };\n    setInterval(function(){ if (window.tiShouldHoldSessionForConfigProcess421 && window.tiShouldHoldSessionForConfigProcess421()) holdSession(\"heartbeat\"); }, 30000);\n})();\n<\/script>\n\n\n<script id=\"ti-json-html-safe-423\">\n(function(){\n    \"use strict\";\n    if (window.__tiJsonHtmlSafe423) return;\n    window.__tiJsonHtmlSafe423 = true;\n\n    function isFormData(v){ return (typeof FormData !== \"undefined\" && v instanceof FormData); }\n    function getAction(body){\n        try {\n            if (!isFormData(body)) return \"\";\n            return String(body.get(\"ti_action\") || body.get(\"action\") || \"\").trim();\n        } catch(e) { return \"\"; }\n    }\n    function ensureAction(body){\n        try {\n            if (!isFormData(body)) return body;\n            if (body.has(\"ti_action\") && !body.has(\"action\")) body.append(\"action\", body.get(\"ti_action\"));\n            if (body.has(\"action\") && !body.has(\"ti_action\")) body.append(\"ti_action\", body.get(\"action\"));\n            if (window.tiAppendCurrentLanguageToFormData) window.tiAppendCurrentLanguageToFormData(body);\n        } catch(e) {}\n        return body;\n    }\n    function trimText(v){ return String(v == null ? \"\" : v).replace(\/^\\uFEFF\/, \"\").trim(); }\n    function htmlLike(raw){\n        var t = trimText(raw).slice(0, 300).toLowerCase();\n        return t.indexOf(\"<\") === 0 || t.indexOf(\"<!doctype\") === 0 || t.indexOf(\"<html\") >= 0 || t.indexOf(\"<br\") === 0 || t.indexOf(\"<b>\") >= 0 || t.indexOf(\"<script\") >= 0;\n    }\n    function preview(raw){\n        var t = trimText(raw).replace(\/<script[\\s\\S]*?<\\\/script>\/gi, \"\");\n        t = t.replace(\/<style[\\s\\S]*?<\\\/style>\/gi, \"\");\n        t = t.replace(\/<[^>]+>\/g, \" \").replace(\/\\s+\/g, \" \").trim();\n        if (!t) t = trimText(raw).slice(0, 240);\n        return t.slice(0, 260);\n    }\n    function userMessage(err){\n        var m = String((err && err.message) || err || \"\");\n        if (\/unexpected token\\s*['\\\"]?<['\\\"]?\/i.test(m) || \/risposta html\/i.test(m) || \/risposta server non valida\/i.test(m)) {\n            return \"Il server ha restituito HTML invece di JSON. Ho bloccato l'errore tecnico e mantengo la risposta locale; ricarica la configurazione o ripeti l'azione se serve.\";\n        }\n        return m || \"Risposta server non valida.\";\n    }\n    function showJsonNotice(err){\n        var msg = userMessage(err);\n        try {\n            if (window.tiAlert) { window.tiAlert(msg); return; }\n        } catch(e) {}\n        try {\n            if (window.addMsg) { window.addMsg(\"ai\", \"\u26a0\ufe0f \" + msg, \"\u26a0\ufe0f \" + msg); return; }\n        } catch(e) {}\n        try { console.warn(\"Shop-Service JSON\", msg, err); } catch(e) {}\n    }\n    function candidateUrls(primary, action){\n        var list = [];\n        function add(u){\n            u = String(u || \"\").trim();\n            if (!u) return;\n            if (list.indexOf(u) < 0) list.push(u);\n        }\n        add(primary);\n        \/\/ La configurazione usa spesso l'interceptor pagina; admin-ajax \u00e8 un fallback quando WordPress accetta la sessione.\n        if (action === \"ti_ai_config_action\" || action === \"ti_ai_batch_ai_images_prepare\" || action === \"ti_ai_save_full_db\") {\n            add(window.tiUrl || \"\");\n            add(window.tiAjaxUrl || window.ajaxurl || \"\");\n        } else {\n            add(window.tiAjaxUrl || window.ajaxurl || \"\");\n            add(window.tiUrl || \"\");\n        }\n        add(window.location && window.location.href ? window.location.href.split(\"?\")[0] : \"\");\n        return list;\n    }\n    function readJsonResponse(resp, raw, url, action, allowRetry){\n        var txt = trimText(raw);\n        if (!resp || !resp.ok) {\n            var httpMsg = \"HTTP \" + (resp ? resp.status : \"?\") + \": \" + (preview(txt) || \"risposta server vuota\");\n            var he = new Error(htmlLike(txt) ? (\"Risposta HTML dal server su \" + action + \": \" + httpMsg) : httpMsg);\n            he.httpStatus = resp ? resp.status : 0;\n            he.rawPreview = preview(txt);\n            he.url = url;\n            he.action = action;\n            he.retryableJson422 = !!allowRetry;\n            throw he;\n        }\n        if (txt === \"0\" || txt === \"-1\") {\n            var ze = new Error(\"Endpoint AJAX non disponibile o sessione non autorizzata per \" + action + \": \" + txt);\n            ze.rawPreview = txt;\n            ze.url = url;\n            ze.action = action;\n            ze.retryableJson422 = !!allowRetry;\n            throw ze;\n        }\n        try { return JSON.parse(txt); }\n        catch(e) {\n            var je = new Error((htmlLike(txt) ? \"Risposta HTML dal server\" : \"Risposta server non valida\") + \" per \" + (action || \"richiesta\") + \": \" + (preview(txt) || \"vuota\"));\n            je.rawPreview = preview(txt);\n            je.url = url;\n            je.action = action;\n            je.retryableJson422 = !!allowRetry;\n            throw je;\n        }\n    }\n    function fetchJson422(url, options){\n        var opts = Object.assign({}, options || {});\n        var body = ensureAction(opts.body);\n        opts.body = body;\n        var action = getAction(body);\n        var tableReadWait = false, tableReadShown = false, tableReadOk = false;\n        var tableReadTitle = \"\u23f3 Lettura tabelle in corso\";\n        var tableReadDetail = \"Sto leggendo le tabelle della ditta. Attendi la fine del caricamento.\";\n        var tableReadItem = \"\";\n        try {\n            tableReadWait = !!opts.tiShowTableReadWait;\n            if (opts.tiTableReadTitle) tableReadTitle = String(opts.tiTableReadTitle);\n            if (opts.tiTableReadDetail) tableReadDetail = String(opts.tiTableReadDetail);\n            if (opts.tiTableReadItem) tableReadItem = String(opts.tiTableReadItem);\n            delete opts.tiShowTableReadWait; delete opts.tiTableReadTitle; delete opts.tiTableReadDetail; delete opts.tiTableReadItem;\n        } catch(e) {}\n        var noLong = !!opts.tiNoLongProcessSignal;\n        if (Object.prototype.hasOwnProperty.call(opts, \"tiNoLongProcessSignal\")) delete opts.tiNoLongProcessSignal;\n        if (!opts.credentials) opts.credentials = \"same-origin\";\n        if (!opts.cache) opts.cache = \"no-store\";\n        if (!opts.method) opts.method = body ? \"POST\" : \"GET\";\n        if (!noLong && !opts.signal && window.tiLongProcessState && window.tiLongProcessState.controller) {\n            opts.signal = window.tiLongProcessState.controller.signal;\n        }\n        if (tableReadWait && typeof window.showTableReadWait === \"function\") {\n            tableReadShown = window.showTableReadWait(tableReadTitle, tableReadDetail, tableReadItem);\n        }\n        var urls = candidateUrls(url || window.tiUrl || window.tiAjaxUrl || window.location.href, action);\n        var lastErr = null;\n        function attempt(i){\n            var u = urls[i] || url || window.location.href;\n            var thisOpts = Object.assign({}, opts);\n            \/\/ FormData pu\u00f2 essere riusato; eventuali campi lingua\/action sono gi\u00e0 applicati.\n            return fetch(u, thisOpts).then(function(resp){\n                return resp.text().then(function(raw){ return readJsonResponse(resp, raw, u, action, i < urls.length - 1); });\n            }).catch(function(err){\n                lastErr = err;\n                if (err && err.name === \"AbortError\") {\n                    var abortErr = new Error(\"Processo interrotto dall utente.\");\n                    abortErr.userCancelled = true;\n                    throw abortErr;\n                }\n                if (i < urls.length - 1 && (err && (err.retryableJson422 || \/html|endpoint ajax|sessione|http 400|http 403|http 404\/i.test(String(err.message || \"\"))))) {\n                    return attempt(i + 1);\n                }\n                throw err;\n            });\n        }\n        var p = attempt(0).then(function(data){ tableReadOk = true; return data; });\n        if (tableReadShown && typeof p.finally === \"function\") {\n            p = p.finally(function(){ try { if (window.hideTableReadWait) window.hideTableReadWait(tableReadOk, tableReadOk ? \"Tabelle lette\" : \"Errore lettura tabelle\"); } catch(e) {} });\n        }\n        return p.catch(function(err){\n            \/\/ Non mostrare sempre popup durante fallback informativi silenziosi: l'handler specifico pu\u00f2 gestire il messaggio.\n            if (!(opts && opts.tiSilentJsonError)) {\n                try { console.warn(\"Shop-Service richiesta JSON non valida\", err, {action:action, urls:urls}); } catch(e) {}\n            }\n            throw err;\n        });\n    }\n    if (window.fetchJsonSafe && window.fetchJsonSafe.__tiVersion423) {\n        window.fetchJsonSafe423 = window.fetchJsonSafe;\n        window.postFormDataJsonSafe = function(url, fd, options){\n            ensureAction(fd);\n            var opts = Object.assign({method:\"POST\", body:fd, credentials:\"same-origin\", cache:\"no-store\", tiNoLongProcessSignal:true}, options || {});\n            return window.fetchJsonSafe(url || window.tiUrl || window.tiAjaxUrl || window.location.href, opts);\n        };\n    } else {\n        window.fetchJsonSafe423 = fetchJson422;\n        window.fetchJsonSafe = fetchJson422;\n        window.postFormDataJsonSafe = function(url, fd, options){\n            ensureAction(fd);\n            var opts = Object.assign({method:\"POST\", body:fd, credentials:\"same-origin\", cache:\"no-store\", tiNoLongProcessSignal:true}, options || {});\n            return fetchJson422(url || window.tiUrl || window.tiAjaxUrl || window.location.href, opts);\n        };\n    }\n\n    if (typeof Response !== \"undefined\" && Response.prototype && !Response.prototype.__tiJsonSafe422) {\n        try {\n            Object.defineProperty(Response.prototype, \"__tiJsonSafe422\", {value:true, configurable:true});\n            var originalJson = Response.prototype.json;\n            Response.prototype.json = function(){\n                var resp = this;\n                var cloned = null;\n                try { cloned = resp.clone(); } catch(e) {}\n                return originalJson.call(resp).catch(function(err){\n                    if (!\/unexpected token\\s*['\\\"]?<['\\\"]?\/i.test(String(err && err.message || \"\"))) throw err;\n                    if (!cloned) {\n                        var e0 = new Error(\"Risposta HTML dal server invece di JSON.\");\n                        e0.originalError = err;\n                        throw e0;\n                    }\n                    return cloned.text().then(function(raw){\n                        var e = new Error(\"Risposta HTML dal server invece di JSON: \" + (preview(raw) || \"contenuto HTML\"));\n                        e.originalError = err;\n                        e.rawPreview = preview(raw);\n                        throw e;\n                    });\n                });\n            };\n        } catch(e) {}\n    }\n\n    var oldAsk = window.askColumnHelp;\n    window.askColumnHelp = function(tbl, col){\n        var table = String(tbl || \"\").replace(\/[\\\"\u201c\u201d]\/g, \"\").trim();\n        var field = String(col || \"\").replace(\/\\\\'\/g, \"'\").replace(\/[\\\"\u201c\u201d]\/g, \"\").trim();\n        var en = String(window.currLang || document.documentElement.lang || \"it\").toLowerCase().indexOf(\"en\") === 0;\n        var ov = document.getElementById(\"ti-col-help-ov\");\n        var title = document.getElementById(\"col-help-title\");\n        var box = document.getElementById(\"col-help-content\");\n        var req = Date.now() + \"-\" + Math.random().toString(36).slice(2);\n        window.tiColumnHelpActiveRequestId = req;\n        if (ov) { ov.setAttribute(\"data-ti-help-request-id\", req); ov.setAttribute(\"data-ti-help-table\", table); ov.setAttribute(\"data-ti-help-column\", field); }\n        if (title) title.innerText = field ? ((en ? \"Field info: \" : \"Info campo: \") + field + \" [\" + table + \"]\") : ((en ? \"Table info: \" : \"Info tabella: \") + table);\n        var local = \"\";\n        try { if (window.tiBuildLocalTableHelp421) local = window.tiBuildLocalTableHelp421(table, field, en); } catch(e) {}\n        if (!local && typeof oldAsk === \"function\") {\n            try { return oldAsk.apply(this, arguments); } catch(e) {}\n        }\n        if (box) box.innerHTML = local || \"<div style='text-align:left;color:#fbbf24;'>Informazione locale non disponibile. Ricaricare la configurazione e riprovare.<\/div>\";\n        try { if (window.openModal) window.openModal(\"ti-col-help-ov\"); if (window.tiPromoteModalFront355) window.tiPromoteModalFront355(\"ti-col-help-ov\"); } catch(e) {}\n        var fd = new FormData();\n        fd.append(\"ti_action\", \"ti_ai_config_action\");\n        fd.append(\"action\", \"ti_ai_config_action\");\n        fd.append(\"mode\", \"manual_help\");\n        var dbEl = document.getElementById(\"ti-ditta\");\n        fd.append(\"db\", dbEl && dbEl.value ? dbEl.value : \"NEW_DB\");\n        fd.append(\"table\", table);\n        fd.append(\"field\", field);\n        fd.append(\"lang\", en ? \"en\" : \"it\");\n        fetchJson422(window.tiUrl || window.tiAjaxUrl || window.location.href, {method:\"POST\", body:fd, credentials:\"same-origin\", cache:\"no-store\", tiNoLongProcessSignal:true, tiSilentJsonError:true}).then(function(res){\n            if (window.tiColumnHelpActiveRequestId !== req) return;\n            if (ov && ov.getAttribute(\"data-ti-help-request-id\") !== req) return;\n            var reply = res && res.success && res.data ? String(res.data.reply || \"\") : \"\";\n            if (!reply || \/Informazione operativa non trovata|No operational information|Accesso negato\/i.test(reply)) {\n                var srv = document.getElementById(\"ti-help-server-421\");\n                if (srv) srv.innerHTML = \"<span style='color:#fbbf24;'>Risposta locale mostrata. Manuale\/documenti non hanno aggiunto dettagli disponibili.<\/span>\";\n                return;\n            }\n            if (box) box.innerHTML = \"<div style='text-align:left;line-height:1.45;'>\" + reply + \"<\/div>\";\n            try { if (window.applyLayoutLanguage) window.applyLayoutLanguage(false); if (window.tiPromoteModalFront355) window.tiPromoteModalFront355(\"ti-col-help-ov\"); } catch(e) {}\n        }).catch(function(err){\n            if (window.tiColumnHelpActiveRequestId !== req) return;\n            var srv = document.getElementById(\"ti-help-server-421\");\n            if (srv) srv.innerHTML = \"<span style='color:#fbbf24;'>Risposta locale mostrata. Ricerca manuale\/documenti non disponibile ora: \" + String(userMessage(err)).replace(\/[&<>\\\"']\/g,function(c){return {\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",\"\\\"\":\"&quot;\",\"'\":\"&#39;\"}[c]||c;}) + \"<\/span>\";\n        });\n        return false;\n    };\n\n    window.addEventListener(\"unhandledrejection\", function(evt){\n        var reason = evt && evt.reason;\n        var msg = String((reason && reason.message) || reason || \"\");\n        if (\/unexpected token\\s*['\\\"]?<['\\\"]?|risposta html dal server|risposta server non valida\/i.test(msg)) {\n            if (evt && evt.preventDefault) evt.preventDefault();\n            showJsonNotice(reason || msg);\n            return true;\n        }\n    }, true);\n    window.addEventListener(\"error\", function(evt){\n        var msg = String((evt && (evt.message || (evt.error && evt.error.message))) || \"\");\n        if (\/unexpected token\\s*['\\\"]?<['\\\"]?\/i.test(msg)) {\n            if (evt && evt.preventDefault) evt.preventDefault();\n            showJsonNotice(evt.error || msg);\n            return true;\n        }\n    }, true);\n})();\n<\/script>\n\n\n<script id=\"ti-json-early-final-423\">\n(function(){\n    \"use strict\";\n    if (window.__tiResponseJsonFinal423 || !window.Response || !window.Response.prototype) return;\n    window.__tiResponseJsonFinal423 = true;\n    try {\n        var oldJson = window.Response.prototype.json;\n        window.Response.prototype.json = function(){\n            var resp = this;\n            return resp.text().then(function(raw){\n                var txt = String(raw == null ? '' : raw).replace(\/^\\uFEFF\/, '').trim();\n                try { return JSON.parse(txt); }\n                catch(e) {\n                    var clean = txt.replace(\/<script[\\s\\S]*?<\\\/script>\/gi,' ').replace(\/<style[\\s\\S]*?<\\\/style>\/gi,' ').replace(\/<[^>]+>\/g,' ').replace(\/\\s+\/g,' ').trim().slice(0,260);\n                    return {success:false, data:{message:'Risposta server non valida o HTML ricevuto invece di JSON. ' + (clean ? 'Dettaglio: ' + clean : ''), code:'response_json_html_423'}};\n                }\n            }).catch(function(e){\n                try { return oldJson.call(resp); } catch(e2) { return Promise.resolve({success:false,data:{message:(e2 && e2.message) || 'Errore JSON', code:'response_json_error_423'}}); }\n            });\n        };\n    } catch(e) {}\n})();\n<\/script>\n\n\n<script id=\"ti-ai-batch-close-424\">\n(function(){\n    \"use strict\";\n    if (window.__tiAIBatchClose424) return;\n    window.__tiAIBatchClose424 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function txt(el){ return String(el ? (el.innerText || el.textContent || el.value || '') : '').trim(); }\n    function lower(s){ return String(s || '').toLowerCase(); }\n    function visible(el){\n        if (!el) return false;\n        var cs = null;\n        try { cs = window.getComputedStyle(el); } catch(e) {}\n        return !!(el.offsetWidth || el.offsetHeight || (cs && cs.display !== 'none' && cs.visibility !== 'hidden' && cs.opacity !== '0'));\n    }\n    function isImageBatchText(s){\n        s = lower(s);\n        return \/generazione\\s+(foto|immagini)\\s+ai|immagini\\s+ai\/.test(s);\n    }\n    function isFinalText(s){\n        s = lower(s);\n        return \/interrott|completat|conclus|terminat|100\\s*%\/.test(s);\n    }\n    function currentLoaderText(){\n        return [txt(byId('ti-loader-title')), txt(byId('ti-loader-sub')), txt(byId('ti-loader-current-item')), txt(byId('ti-loader-percent'))].join(' ');\n    }\n    function resetLongProcessFlags(){\n        try {\n            var st = window.tiBatchAIGenState;\n            if (st && typeof st === 'object') {\n                st.active = false;\n                st.cancelRequested = false;\n                st.pendingConfirm = false;\n                st.paused = false;\n                st.closing424 = true;\n                try { if (st.currentController) st.currentController.abort(); } catch(e) {}\n                st.currentController = null;\n            }\n        } catch(e) {}\n        try {\n            var ls = window.tiLoaderState || (window.tiLoaderState = {});\n            ls.active = false;\n            if (ls.timer) clearInterval(ls.timer);\n            if (ls.closeTimer357) clearTimeout(ls.closeTimer357);\n            ls.timer = null;\n        } catch(e) {}\n        try {\n            if (window.tiLongProcessState && typeof window.tiLongProcessState === 'object') {\n                window.tiLongProcessState.active = false;\n                window.tiLongProcessState.cancelled = false;\n                window.tiLongProcessState.cancellable = false;\n                try { if (window.tiLongProcessState.controller) window.tiLongProcessState.controller.abort(); } catch(e) {}\n                window.tiLongProcessState.controller = null;\n            }\n        } catch(e) {}\n        try { if (typeof window.clearLongAIProcess === 'function') window.clearLongAIProcess(); } catch(e) {}\n    }\n    function forceHideLoader(){\n        var ov = byId('ti-ai-loader-ov');\n        if (!ov) return true;\n        try {\n            ov.style.setProperty('display', 'none', 'important');\n            ov.style.setProperty('visibility', 'hidden', 'important');\n            ov.style.setProperty('opacity', '0', 'important');\n            ov.style.setProperty('pointer-events', 'none', 'important');\n            ov.setAttribute('aria-hidden', 'true');\n            ov.classList.remove('ti-modal-open','ti-front-modal','ti-plugin-modal-front','ti-plugin-modal','ti-open','open','show','active');\n        } catch(e) {}\n        try { if (typeof window.closeModal === 'function') window.closeModal('ti-ai-loader-ov'); } catch(e) {}\n        try {\n            if (typeof window.updatePluginScrollLock === 'function') window.updatePluginScrollLock();\n            else {\n                var stillOpen = Array.prototype.some.call(document.querySelectorAll('[id$=\"-ov\"],.ti-modal'), function(x){ return x !== ov && visible(x); });\n                if (!stillOpen) {\n                    document.documentElement.classList.remove('ti-no-scroll');\n                    document.body.classList.remove('ti-no-scroll');\n                }\n            }\n        } catch(e) {}\n        return true;\n    }\n    function closeImageBatchLoader424(){\n        resetLongProcessFlags();\n        forceHideLoader();\n        setTimeout(forceHideLoader, 40);\n        setTimeout(forceHideLoader, 180);\n        return false;\n    }\n    window.tiCloseImageBatchProgress424 = closeImageBatchLoader424;\n\n    function finalMode(){\n        var text = currentLoaderText();\n        var st = window.tiBatchAIGenState || {};\n        if (isImageBatchText(text) && isFinalText(text)) return true;\n        if (st && st.fix420 && st.active === false && isImageBatchText(text) && isFinalText(text)) return true;\n        if (st && st.fix411 && st.active === false && isImageBatchText(text) && isFinalText(text)) return true;\n        return false;\n    }\n    function installFinalButton(){\n        var ov = byId('ti-ai-loader-ov');\n        if (!ov || !visible(ov) || !finalMode()) return;\n        var actions = byId('ti-loader-actions');\n        if (!actions) return;\n        actions.style.display = 'block';\n        var btn = byId('ti-loader-close-final-424');\n        if (!btn) {\n            actions.innerHTML = '<button type=\"button\" id=\"ti-loader-close-final-424\" class=\"ti-btn\" style=\"width:100%;max-width:260px;margin:0 auto;display:block;background:#2563eb;color:#fff;\">Chiudi<\/button>';\n            btn = byId('ti-loader-close-final-424');\n        }\n        if (btn && !btn.__tiClose424) {\n            btn.__tiClose424 = true;\n            btn.addEventListener('click', function(ev){\n                ev.preventDefault(); ev.stopPropagation(); if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();\n                closeImageBatchLoader424();\n            }, true);\n        }\n    }\n    function isCloseClick(btn){\n        if (!btn) return false;\n        var ov = btn.closest ? btn.closest('#ti-ai-loader-ov') : null;\n        if (!ov) return false;\n        var id = String(btn.id || '');\n        var label = lower(txt(btn));\n        if (\/^ti-loader-close-final\/.test(id)) return true;\n        if (id === 'ti-loader-close' && \/chiudi|close\/.test(label) && finalMode()) return true;\n        if (\/chiudi|close\/.test(label) && finalMode()) return true;\n        return false;\n    }\n    document.addEventListener('click', function(ev){\n        var btn = ev.target && ev.target.closest ? ev.target.closest('button,a,[role=\"button\"],.ti-btn,input[type=\"button\"]') : null;\n        if (!isCloseClick(btn)) return;\n        ev.preventDefault();\n        ev.stopPropagation();\n        if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();\n        closeImageBatchLoader424();\n    }, true);\n\n    try {\n        var oldUpdate = window.updateProgressPopup;\n        if (typeof oldUpdate === 'function' && !oldUpdate.__tiClose424) {\n            window.updateProgressPopup = function(){\n                var ret = oldUpdate.apply(this, arguments);\n                setTimeout(installFinalButton, 0);\n                setTimeout(installFinalButton, 120);\n                return ret;\n            };\n            window.updateProgressPopup.__tiClose424 = true;\n        }\n    } catch(e) {}\n    try {\n        var oldCancel = window.cancelLongAIProcess;\n        if (typeof oldCancel === 'function' && !oldCancel.__tiClose424) {\n            window.cancelLongAIProcess = function(){\n                if (finalMode()) return closeImageBatchLoader424();\n                return oldCancel.apply(this, arguments);\n            };\n            window.cancelLongAIProcess.__tiClose424 = true;\n        }\n    } catch(e) {}\n\n    if (typeof MutationObserver !== 'undefined') {\n        try {\n            var ov = byId('ti-ai-loader-ov');\n            if (ov) {\n                var mo = new MutationObserver(function(){ installFinalButton(); });\n                mo.observe(ov, {childList:true, subtree:true, attributes:true, attributeFilter:['style','class']});\n            }\n        } catch(e) {}\n    }\n    setInterval(installFinalButton, 800);\n})();\n\n\/* Shop-Service 30.9.425 - mobile Configurazione e fallback preview immagini *\/\n(function(){\n    if (window.__tiMobileConfig425) return; window.__tiMobileConfig425 = true;\n    function addCss(){\n        if (document.getElementById('ti-mobile-config-425-css')) return;\n        var css = `\n        @media (max-width: 768px) {\n            #ti-ai-outer #ti-conf-cnt,\n            #ti-ai-outer .ti-conf-body,\n            #ti-ai-outer .ti-cfg-details,\n            #ti-ai-outer .ti-cfg-table-container,\n            #ti-ai-outer .ti-sync-hscroll-wrap,\n            #ti-ai-outer .ti-table-wrap {\n                max-width: 100vw !important;\n                min-width: 0 !important;\n                box-sizing: border-box !important;\n            }\n            #ti-ai-outer .ti-conf-body,\n            #ti-ai-outer #ti-conf-cnt {\n                overflow-x: hidden !important;\n                overflow-y: auto !important;\n                -webkit-overflow-scrolling: touch !important;\n                touch-action: pan-y !important;\n                padding-left: 6px !important;\n                padding-right: 6px !important;\n            }\n            #ti-ai-outer .ti-cfg-table-container,\n            #ti-ai-outer .ti-sync-hscroll-wrap,\n            #ti-ai-outer .ti-cfg-top-scroll,\n            #ti-ai-outer .ti-cfg-bottom-scroll {\n                overflow-x: auto !important;\n                overflow-y: auto !important;\n                -webkit-overflow-scrolling: touch !important;\n                touch-action: pan-x pan-y !important;\n                width: 100% !important;\n                max-width: 100% !important;\n            }\n            #ti-ai-outer .ti-cfg-table {\n                table-layout: auto !important;\n                width: max-content !important;\n                min-width: 980px !important;\n                max-width: none !important;\n                font-size: 11px !important;\n            }\n            #ti-ai-outer .ti-cfg-table th,\n            #ti-ai-outer .ti-cfg-table td {\n                min-width: 92px !important;\n                max-width: 180px !important;\n                padding: 4px 5px !important;\n                white-space: nowrap !important;\n            }\n            #ti-ai-outer .ti-cfg-table input,\n            #ti-ai-outer .ti-cfg-table select,\n            #ti-ai-outer .ti-cfg-table textarea {\n                min-width: 86px !important;\n                max-width: 170px !important;\n                font-size: 11px !important;\n                padding: 5px 6px !important;\n            }\n            #ti-ai-outer .ti-cfg-table textarea { min-height: 36px !important; white-space: normal !important; }\n            #ti-ai-outer .ti-cfg-rownum,\n            #ti-ai-outer .ti-sticky-col {\n                min-width: 44px !important;\n                width: 44px !important;\n                max-width: 44px !important;\n            }\n            #ti-ai-outer .ti-config-actions,\n            #ti-ai-outer .ti-config-buttons,\n            #ti-ai-outer .ti-config-toolbar,\n            #ti-ai-outer .ti-cfg-actions,\n            #ti-ai-outer .ti-conf-actions,\n            #ti-ai-outer [data-ti-config-actions=\"1\"] {\n                display: flex !important;\n                flex-wrap: nowrap !important;\n                overflow-x: auto !important;\n                overflow-y: hidden !important;\n                gap: 6px !important;\n                justify-content: flex-start !important;\n                align-items: center !important;\n                -webkit-overflow-scrolling: touch !important;\n                touch-action: pan-x !important;\n                padding-bottom: 6px !important;\n                max-width: 100% !important;\n            }\n            #ti-ai-outer .ti-config-actions .ti-btn,\n            #ti-ai-outer .ti-config-buttons .ti-btn,\n            #ti-ai-outer .ti-config-toolbar .ti-btn,\n            #ti-ai-outer .ti-cfg-actions .ti-btn,\n            #ti-ai-outer .ti-conf-actions .ti-btn,\n            #ti-ai-outer [data-ti-config-actions=\"1\"] .ti-btn {\n                flex: 0 0 auto !important;\n                width: auto !important;\n                min-width: 92px !important;\n                max-width: 150px !important;\n                padding: 7px 9px !important;\n                font-size: 11px !important;\n                line-height: 1.15 !important;\n                white-space: normal !important;\n            }\n            #ti-ai-outer details.ti-cfg-details > summary {\n                position: sticky !important;\n                left: 0 !important;\n                z-index: 5 !important;\n                overflow-x: auto !important;\n                white-space: nowrap !important;\n                -webkit-overflow-scrolling: touch !important;\n            }\n            #ti-ai-outer img.ti-img-broken-425 { object-fit: contain !important; background:#111827 !important; border:1px dashed #64748b !important; }\n        }`;\n        var st = document.createElement('style'); st.id = 'ti-mobile-config-425-css'; st.appendChild(document.createTextNode(css)); document.head.appendChild(st);\n    }\n    function ensureScrollbars(root){\n        root = root || document;\n        var wraps = root.querySelectorAll ? root.querySelectorAll('.ti-cfg-table-container, .ti-sync-hscroll-wrap') : [];\n        Array.prototype.forEach.call(wraps, function(wrap){\n            if (!wrap || wrap.__tiMobile425) return; wrap.__tiMobile425 = true;\n            wrap.style.overflowX = 'auto'; wrap.style.webkitOverflowScrolling = 'touch'; wrap.style.touchAction = 'pan-x pan-y';\n            var table = wrap.querySelector('table');\n            if (table) { table.style.width = 'max-content'; table.style.maxWidth = 'none'; if (!table.style.minWidth) table.style.minWidth = '980px'; }\n        });\n    }\n    function tokenImage(val){\n        val = String(val || '').trim();\n        if (!val) return '';\n        if (val.indexOf(',') >= 0) val = val.split(',')[0].trim();\n        if (val.indexOf(';') >= 0) val = val.split(';')[0].trim();\n        return val;\n    }\n    function fallbackUrl(img){\n        try {\n            var src = img.getAttribute('src') || '';\n            if (!src || img.__tiImgFallback425) return '';\n            if (src.indexOf('ti_action=ti_ai_get_image') < 0) return '';\n            var u = new URL(src, window.location.href);\n            if (u.searchParams.has('record')) { u.searchParams.delete('record'); u.searchParams.set('ti_cleanup_missing', '1'); img.__tiImgFallback425 = true; return u.toString(); }\n        } catch(e) {}\n        return '';\n    }\n    function bindImageFallbacks(root){\n        root = root || document;\n        var imgs = root.querySelectorAll ? root.querySelectorAll('img') : [];\n        Array.prototype.forEach.call(imgs, function(img){\n            if (!img || img.__tiImgErr425) return; img.__tiImgErr425 = true;\n            img.addEventListener('error', function(){\n                var fb = fallbackUrl(img);\n                if (fb) { img.src = fb; return; }\n                if (window.handleConfigImageError426) { window.handleConfigImageError426(img); return; }\n                img.style.display = 'none';\n                img.title = 'Immagine non visualizzabile: scollegamento richiesto.';\n            }, true);\n        });\n    }\n    function run(){ addCss(); ensureScrollbars(document); bindImageFallbacks(document); }\n    var oldRender = window.renderConfig;\n    if (typeof oldRender === 'function' && !oldRender.__tiMobile425) {\n        window.renderConfig = function(){ var ret = oldRender.apply(this, arguments); setTimeout(run, 0); setTimeout(run, 250); return ret; };\n        window.renderConfig.__tiMobile425 = true;\n    }\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', run); else run();\n    if (typeof MutationObserver !== 'undefined') {\n        try { new MutationObserver(function(){ run(); }).observe(document.body, {childList:true, subtree:true}); } catch(e) {}\n    }\n})();\n\n\n\/* Shop-Service 30.9.426 - rimozione automatica immagini Prodotti\/Servizi non visualizzabili *\/\n(function(){\n    if (window.__tiUnviewableImageCleanup426) return; window.__tiUnviewableImageCleanup426 = true;\n    var pending = Object.create(null);\n    function lower(s){ return String(s || '').toLowerCase(); }\n    function getUrl(img){ return String((img && (img.currentSrc || img.src || img.getAttribute('src'))) || ''); }\n    function parseImgUrl(img){\n        var src = getUrl(img);\n        if (!src || src.indexOf('ti_action=ti_ai_get_image') < 0) return null;\n        try {\n            var u = new URL(src, window.location.href);\n            var tbl = u.searchParams.get('tbl') || '';\n            var file = u.searchParams.get('file') || '';\n            var db = u.searchParams.get('db') || (document.getElementById('ti-ditta') ? document.getElementById('ti-ditta').value : '');\n            var record = u.searchParams.get('record') || '';\n            if (!db || !tbl || !file) return null;\n            var tl = lower(tbl);\n            if (tl.indexOf('prodott') < 0 && tl.indexOf('serviz') < 0) return null;\n            return {src:src, db:db, tbl:tbl, file:file, record:record};\n        } catch(e) { return null; }\n    }\n    function contextFromDom(img){\n        var out = {idx:'', key:''};\n        try {\n            var td = img && img.closest ? img.closest('td') : null;\n            var input = td ? td.querySelector('input[data-tbl][data-idx][data-key],textarea[data-tbl][data-idx][data-key]') : null;\n            if (input) {\n                out.idx = input.getAttribute('data-idx') || '';\n                out.key = input.getAttribute('data-key') || '';\n            }\n        } catch(e) {}\n        return out;\n    }\n    function markRemoved(img, message){\n        try {\n            img.classList.add('ti-img-broken-425');\n            img.alt = 'Immagine non visualizzabile rimossa';\n            img.title = message || 'Immagine non visualizzabile scollegata e rimossa dal database.';\n            img.style.objectFit = 'contain';\n            img.style.opacity = '0.45';\n            img.style.filter = 'grayscale(1)';\n        } catch(e) {}\n    }\n    function refreshConfigFromDb(dbData){\n        if (!dbData || !dbData.Tabelle) return;\n        try { window.currentDbData = dbData; } catch(e) {}\n        try {\n            if (typeof window.renderConfig === 'function') {\n                clearTimeout(window.__tiRenderAfterImageCleanup426);\n                window.__tiRenderAfterImageCleanup426 = setTimeout(function(){ try { window.renderConfig(); } catch(e) {} }, 500);\n            }\n        } catch(e) {}\n    }\n    function cleanup(img, reason){\n        var meta = parseImgUrl(img);\n        if (!meta) return false;\n        var key = [meta.db, meta.tbl, meta.file, meta.record].join('|').toLowerCase();\n        if (pending[key]) return true;\n        pending[key] = true;\n        var ctx = contextFromDom(img);\n        markRemoved(img, 'Rimozione immagine non visualizzabile in corso...');\n        var fd = new FormData();\n        fd.append('ti_action','ti_ai_cleanup_unviewable_image');\n        fd.append('action','ti_ai_cleanup_unviewable_image');\n        fd.append('db', meta.db);\n        fd.append('tbl', meta.tbl);\n        fd.append('file', meta.file);\n        fd.append('record', meta.record || '');\n        fd.append('idx', ctx.idx || '');\n        fd.append('key', ctx.key || '');\n        fd.append('browser_error','1');\n        fd.append('reason', reason || 'browser image error');\n        var url = window.tiUrl || window.tiAjaxUrl || window.location.href;\n        var p = window.fetchJsonSafe ? window.fetchJsonSafe(url, {method:'POST', body:fd, credentials:'same-origin', tiNoLongProcessSignal:true}) : fetch(url, {method:'POST', body:fd, credentials:'same-origin'}).then(function(r){ return r.json(); });\n        Promise.resolve(p).then(function(res){\n            var data = res && res.data ? res.data : {};\n            markRemoved(img, data.message || 'Immagine non visualizzabile scollegata e rimossa.');\n            if (data.db) refreshConfigFromDb(data.db);\n        }).catch(function(err){\n            pending[key] = false;\n            markRemoved(img, 'Immagine non visualizzabile. Rimozione automatica non riuscita: ' + (err && err.message ? err.message : 'errore sconosciuto'));\n        });\n        return true;\n    }\n    window.tiCleanupUnviewableImage426 = cleanup;\n\n    var oldApply = window.applyFallbackImage;\n    window.applyFallbackImage = function(img){\n        var meta = parseImgUrl(img);\n        if (meta) {\n            if (img && img.__tiImgFallback425 && !img.__tiCleanup426FallbackSeen) {\n                img.__tiCleanup426FallbackSeen = true;\n                return false;\n            }\n            return cleanup(img, 'applyFallbackImage');\n        }\n        if (typeof oldApply === 'function') return oldApply.apply(this, arguments);\n        if (img) img.style.display = 'none';\n        return false;\n    };\n\n    function bind(root){\n        root = root || document;\n        var imgs = root.querySelectorAll ? root.querySelectorAll('img') : [];\n        Array.prototype.forEach.call(imgs, function(img){\n            if (!img || img.__tiCleanup426Bound) return;\n            img.__tiCleanup426Bound = true;\n            img.addEventListener('error', function(){\n                var srcAtError = getUrl(img);\n                setTimeout(function(){\n                    if (!img) return;\n                    if (getUrl(img) !== srcAtError) return;\n                    cleanup(img, 'image error after fallback');\n                }, 260);\n            }, true);\n        });\n    }\n    var oldRender = window.renderConfig;\n    if (typeof oldRender === 'function' && !oldRender.__tiCleanup426) {\n        window.renderConfig = function(){ var ret = oldRender.apply(this, arguments); setTimeout(function(){ bind(document); }, 0); setTimeout(function(){ bind(document); }, 300); return ret; };\n        window.renderConfig.__tiCleanup426 = true;\n    }\n    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ bind(document); }); else bind(document);\n    if (typeof MutationObserver !== 'undefined') {\n        try { new MutationObserver(function(muts){ muts.forEach(function(m){ if (m && m.addedNodes) Array.prototype.forEach.call(m.addedNodes, function(n){ if (n && n.nodeType === 1) bind(n); }); }); }).observe(document.body, {childList:true, subtree:true}); } catch(e) {}\n    }\n})();\n<\/script>\n\n\n<script id=\"ti-activity-chart-loader-close-427\">\n(function(){\n    \"use strict\";\n    if (window.__tiActivityChartLoaderClose427) return;\n    window.__tiActivityChartLoaderClose427 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function text(el){ return el ? String(el.innerText || el.textContent || '') : ''; }\n    function norm(s){ return String(s || '').toLowerCase().replace(\/\u00e0\/g,'a').replace(\/\u00e8\/g,'e').replace(\/\u00e9\/g,'e').replace(\/\u00ec\/g,'i').replace(\/\u00f2\/g,'o').replace(\/\u00f9\/g,'u').replace(\/\\s+\/g,' ').trim(); }\n    function visible(el){\n        if (!el) return false;\n        try {\n            var cs = window.getComputedStyle ? window.getComputedStyle(el) : el.style;\n            if (!cs || cs.display === 'none' || cs.visibility === 'hidden' || parseFloat(cs.opacity || '1') === 0) return false;\n            var r = el.getBoundingClientRect ? el.getBoundingClientRect() : {width:1,height:1};\n            return r.width > 0 && r.height > 0;\n        } catch(e) { return !!(el && el.style && el.style.display !== 'none'); }\n    }\n    function loader(){ return byId('ti-ai-loader-ov'); }\n    function loaderText(){\n        return norm(text(byId('ti-loader-title')) + ' ' + text(byId('ti-loader-sub')) + ' ' + text(byId('ti-loader-current-item')) + ' ' + text(loader()));\n    }\n    function isActivityChartLoader(){\n        var ov = loader();\n        if (!visible(ov)) return false;\n        var s = loaderText();\n        return \/grafici attivita|grafico attivita|report attivita|prepar.*grafic.*attivita|aggiorno.*grafic.*attivita\/.test(s);\n    }\n    function pctVal(){\n        var raw = text(byId('ti-loader-percent')).replace(\/[^0-9]\/g,'');\n        var n = parseInt(raw || '0', 10);\n        return isNaN(n) ? 0 : Math.max(0, Math.min(100, n));\n    }\n    function hasBlockingText(){\n        var s = loaderText();\n        return \/errore|fallito|interrott|annulla|conferma|interrompi generazione\/.test(s);\n    }\n    function clearLoaderTimers(){\n        try {\n            var st = window.tiLoaderState || (window.tiLoaderState = {});\n            st.active = false;\n            ['timer','watchdog327','stallGuard338'].forEach(function(k){ if (st[k]) { clearInterval(st[k]); st[k] = null; } });\n            ['closeTimer338','closeTimer357','activityCloseTimer427','activityMaxTimer427'].forEach(function(k){ if (st[k]) { clearTimeout(st[k]); st[k] = null; } });\n        } catch(e) {}\n    }\n    function clearGraphLongProcess(){\n        try {\n            var lp = window.tiLongProcessState || {};\n            var label = norm(lp.label || text(byId('ti-loader-title')) || '');\n            if (!label || \/grafici attivita|grafico attivita|report attivita\/.test(label)) {\n                if (typeof window.clearLongAIProcess === 'function') window.clearLongAIProcess();\n            }\n        } catch(e) {}\n    }\n    function hardCloseActivityLoader(reason){\n        var ov = loader();\n        if (!ov) return false;\n        if (!isActivityChartLoader() && !\/activity|grafici\/.test(String(reason || '').toLowerCase())) return false;\n        clearLoaderTimers();\n        clearGraphLongProcess();\n        try {\n            ov.style.setProperty('display','none','important');\n            ov.style.setProperty('visibility','hidden','important');\n            ov.style.setProperty('opacity','0','important');\n            ov.style.setProperty('pointer-events','none','important');\n            ov.setAttribute('aria-hidden','true');\n        } catch(e) {}\n        try { if (typeof window.updatePluginScrollLock === 'function') window.updatePluginScrollLock(); } catch(e) {}\n        return true;\n    }\n    function markShown(){\n        try {\n            var st = window.tiLoaderState || (window.tiLoaderState = {});\n            st.activityChartStarted427 = Date.now();\n            st.activityChartDoneSince427 = 0;\n        } catch(e) {}\n    }\n    function scheduleClose(reason, delay){\n        try {\n            var st = window.tiLoaderState || (window.tiLoaderState = {});\n            if (st.activityCloseTimer427) clearTimeout(st.activityCloseTimer427);\n            st.activityCloseTimer427 = setTimeout(function(){\n                try {\n                    if (!isActivityChartLoader()) return;\n                    if (hasBlockingText()) return;\n                    hardCloseActivityLoader(reason || 'scheduled');\n                } catch(e) {}\n            }, Math.max(80, delay || 500));\n        } catch(e) {\n            setTimeout(function(){ if (isActivityChartLoader() && !hasBlockingText()) hardCloseActivityLoader(reason || 'fallback'); }, Math.max(80, delay || 500));\n        }\n    }\n    function scheduleCloseSeries(reason){\n        [220, 650, 1200, 2500, 5000].forEach(function(ms){ setTimeout(function(){\n            try { if (isActivityChartLoader() && !hasBlockingText() && (pctVal() >= 90 || \/pront|complet|100\/.test(loaderText()))) hardCloseActivityLoader(reason + '-' + ms); } catch(e) {}\n        }, ms); });\n    }\n    window.tiCloseActivityChartLoader427 = function(reason){ return hardCloseActivityLoader(reason || 'manual'); };\n\n    if (typeof window.showProgressPopup === 'function' && !window.showProgressPopup.__tiActivity427) {\n        var oldShow = window.showProgressPopup;\n        window.showProgressPopup = function(title, subtitle, opts){\n            var isAct = \/grafici attivit|grafico attivit|report attivit\/i.test(String(title || '') + ' ' + String(subtitle || ''));\n            var ret = oldShow.apply(this, arguments);\n            if (isAct) {\n                markShown();\n                try {\n                    var st = window.tiLoaderState || (window.tiLoaderState = {});\n                    if (st.activityMaxTimer427) clearTimeout(st.activityMaxTimer427);\n                    st.activityMaxTimer427 = setTimeout(function(){ if (isActivityChartLoader() && !hasBlockingText()) hardCloseActivityLoader('activity-chart-max'); }, 12000);\n                } catch(e) {}\n            }\n            return ret;\n        };\n        window.showProgressPopup.__tiActivity427 = true;\n    }\n    if (typeof window.updateProgressPopup === 'function' && !window.updateProgressPopup.__tiActivity427) {\n        var oldUpdate = window.updateProgressPopup;\n        window.updateProgressPopup = function(percent, title, subtitle, currentItem){\n            var ret = oldUpdate.apply(this, arguments);\n            if (isActivityChartLoader() && ((parseInt(percent,10) || 0) >= 100 || \/pront|complet\/.test(norm(String(title || '') + ' ' + String(subtitle || ''))))) {\n                scheduleClose('activity-update-100', 350);\n                scheduleCloseSeries('activity-update');\n            }\n            return ret;\n        };\n        window.updateProgressPopup.__tiActivity427 = true;\n    }\n    if (typeof window.hideProgressPopup === 'function' && !window.hideProgressPopup.__tiActivity427) {\n        var oldHide = window.hideProgressPopup;\n        window.hideProgressPopup = function(success, finalText, opts){\n            var wasActivity = isActivityChartLoader() || \/grafici attivit|grafico attivit|report attivit\/i.test(String(finalText || ''));\n            var ret = oldHide.apply(this, arguments);\n            if (wasActivity && success !== false) {\n                scheduleClose('activity-hide', 220);\n                scheduleCloseSeries('activity-hide');\n            }\n            return ret;\n        };\n        window.hideProgressPopup.__tiActivity427 = true;\n    }\n    if (typeof window.renderActivityReport === 'function' && !window.renderActivityReport.__tiActivityClose427) {\n        var oldRender = window.renderActivityReport;\n        window.renderActivityReport = function(){\n            var ret = oldRender.apply(this, arguments);\n            try {\n                var view = (window.activityReportState && window.activityReportState.view) || '';\n                if (view === 'pie' || view === 'trend') {\n                    setTimeout(function(){ if (typeof window.updateProgressPopup === 'function' && isActivityChartLoader()) window.updateProgressPopup(100, 'Grafici attivit\u00e0 pronti', 'Report attivit\u00e0 aggiornato.', ''); }, 420);\n                    scheduleClose('renderActivityReport', 900);\n                    scheduleCloseSeries('renderActivityReport');\n                }\n            } catch(e) {}\n            return ret;\n        };\n        window.renderActivityReport.__tiActivityClose427 = true;\n    }\n    if (typeof window.tiIsProcessActiveForIdle421 === 'function' && !window.tiIsProcessActiveForIdle421.__tiActivity427) {\n        var oldIdle = window.tiIsProcessActiveForIdle421;\n        window.tiIsProcessActiveForIdle421 = function(){\n            if (isActivityChartLoader() && !hasBlockingText()) return false;\n            return oldIdle.apply(this, arguments);\n        };\n        window.tiIsProcessActiveForIdle421.__tiActivity427 = true;\n    }\n    setInterval(function(){\n        try {\n            if (!isActivityChartLoader() || hasBlockingText()) return;\n            var st = window.tiLoaderState || {};\n            if (pctVal() >= 100 || \/pront|complet\/.test(loaderText())) {\n                if (!st.activityChartDoneSince427) st.activityChartDoneSince427 = Date.now();\n                if (Date.now() - st.activityChartDoneSince427 > 900) hardCloseActivityLoader('activity-100-watchdog');\n            } else if (st.activityChartStarted427 && Date.now() - st.activityChartStarted427 > 12000) {\n                hardCloseActivityLoader('activity-timeout-watchdog');\n            }\n        } catch(e) {}\n    }, 500);\n})();\n<\/script>\n\n\n<script id=\"ti-final-ai-image-confirm-428\">\n(function(){\n    \"use strict\";\n    if (window.__tiFinalAIImageConfirm428) return;\n    window.__tiFinalAIImageConfirm428 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function esc(v){ return String(v == null ? \"\" : v).replace(\/[&<>\"']\/g,function(c){return {\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",\"\\\"\":\"&quot;\",\"'\":\"&#39;\"}[c];}); }\n    function strip(v){ return String(v || \"\").replace(\/<br\\s*\\\/?\\s*>\/gi,\"\\n\").replace(\/<[^>]*>\/g,\" \").replace(\/\\s+\/g,\" \").trim(); }\n    function fd(action, data){\n        var f = window.buildAjaxFormData ? window.buildAjaxFormData(action, data || {}) : new FormData();\n        if (!f.has(\"ti_action\")) f.append(\"ti_action\", action);\n        if (!f.has(\"action\")) f.append(\"action\", action);\n        Object.keys(data || {}).forEach(function(k){ f.set(k, data[k]); });\n        return f;\n    }\n    function ajax(form, ms, label, progressCb){\n        ms = ms || 90000;\n        var st = window.tiBatchAIGenState || {};\n        var ctrl = (typeof AbortController !== \"undefined\") ? new AbortController() : null;\n        if (st && st.active) st.currentController = ctrl;\n        var start = Date.now(), done = false, timer = null, beat = null;\n        return new Promise(function(resolve, reject){\n            function finish(fn, val){\n                if (done) return;\n                done = true;\n                try { clearTimeout(timer); } catch(e) {}\n                try { if (beat) clearInterval(beat); } catch(e) {}\n                if (st && st.currentController === ctrl) st.currentController = null;\n                fn(val);\n            }\n            timer = setTimeout(function(){ try { if (ctrl) ctrl.abort(); } catch(e) {} finish(reject, new Error(label || \"Richiesta non completata.\")); }, ms);\n            if (progressCb) beat = setInterval(function(){ if (!done) { try { progressCb(Math.max(1, Math.round((Date.now() - start) \/ 1000))); } catch(e) {} } }, 3500);\n            var url = window.tiUrl || window.tiAjaxUrl || window.location.href;\n            var opts = {method:\"POST\", body:form, credentials:\"same-origin\", cache:\"no-store\", signal:ctrl ? ctrl.signal : undefined, headers:{\"X-TI-No-Long-Process\":\"1\"}, tiNoLongProcessSignal:true};\n            var req = window.fetchJsonSafe ? window.fetchJsonSafe(url, opts) : fetch(url, opts).then(function(r){ return r.text().then(function(raw){ if (!r.ok) throw new Error(\"HTTP \" + r.status + \": \" + raw.slice(0,240)); try { return JSON.parse(raw); } catch(e) { throw new Error(\"Risposta server non valida: \" + raw.slice(0,240)); } }); });\n            Promise.resolve(req).then(function(json){ finish(resolve, json); }).catch(function(err){\n                if (err && err.name === \"AbortError\") finish(reject, new Error((window.tiBatchAIGenState && window.tiBatchAIGenState.cancelRequested) ? \"Interruzione richiesta dall utente.\" : (label || \"Timeout richiesta.\")));\n                else finish(reject, err || new Error(label || \"Errore richiesta.\"));\n            });\n        });\n    }\n    function getDb(){ return window.currentDbData || window.tiCurrentDbData || window.tiDbData || null; }\n    function realTable(data, name){\n        var t = data && data.Tabelle ? data.Tabelle : null;\n        if (!t) return \"\";\n        if (t[name]) return name;\n        var want = String(name || \"\").toLowerCase().replace(\/[^a-z0-9]\/g,\"\");\n        return Object.keys(t).find(function(k){ return String(k).toLowerCase().replace(\/[^a-z0-9]\/g,\"\") === want; }) || \"\";\n    }\n    function pick(row, keys){\n        for (var i=0;i<keys.length;i++){\n            var want = String(keys[i]).toLowerCase().replace(\/[^a-z0-9]\/g,\"\");\n            var k = Object.keys(row || {}).find(function(x){ return String(x).toLowerCase().replace(\/[^a-z0-9]\/g,\"\") === want; });\n            if (k && String(row[k] || \"\").trim()) return String(row[k]).trim();\n        }\n        return \"\";\n    }\n    function specificKey(k){\n        var key = String(k || \"\").toLowerCase();\n        if (\/\\b(doc|docs|document|documento|documenti|file|files|allegat|pdf|xls|word|excel)\\b\/.test(key)) return false;\n        return \/\\b(immagine|immagini|img|image|images|foto|photo|picture|gallery|galleria|logo)\\b\/.test(key);\n    }\n    function tokenImage(t){\n        t = String(t || \"\").trim();\n        if (!t || \/^(0|no|nessuno|nessuna|--|-|null|undefined)$\/i.test(t)) return false;\n        if (\/ti_action=ti_ai_get_image\/i.test(t)) return true;\n        return \/\\.(jpg|jpeg|png|webp|gif|bmp|avif)(?:[?#].*)?$\/i.test(t.split(\"?\")[0].split(\"#\")[0]);\n    }\n    function specCount(row){\n        var n = 0;\n        Object.keys(row || {}).forEach(function(k){\n            if (!specificKey(k)) return;\n            String(row[k] == null ? \"\" : row[k]).split(\/[,;\\r\\n]+\/).forEach(function(x){ if (tokenImage(x)) n++; });\n        });\n        return n;\n    }\n    function anyMedia(row){\n        var n = 0;\n        Object.keys(row || {}).forEach(function(k){\n            if (!\/(immagine|img|foto|logo|doc|document|file|files|allegat)\/i.test(k)) return;\n            String(row[k] == null ? \"\" : row[k]).split(\/[,;\\r\\n]+\/).forEach(function(x){ x = String(x || \"\").trim(); if (x && !\/^(0|no|nessuno|nessuna|--|-|null|undefined)$\/i.test(x)) n++; });\n        });\n        return n;\n    }\n    function targetsFor(tableName, includeExisting){\n        var data = getDb(), out = [], no = 0, yes = 0;\n        if (!data || !data.Tabelle) return {targets:[], no:0, yes:0};\n        String(tableName || \"Prodotti\").split(\",\").map(function(x){ return x.trim(); }).filter(Boolean).forEach(function(t){\n            var rt = realTable(data, t);\n            var rows = rt && Array.isArray(data.Tabelle[rt]) ? data.Tabelle[rt] : [];\n            rows.forEach(function(row, idx){\n                if (!row || typeof row !== \"object\") return;\n                var name = pick(row,[\"Prodotto\",\"Servizio\",\"Nome\",\"Titolo\",\"Articolo\"]) || pick(row,[\"Descrizione\",\"Dettaglio\",\"Note\"]) || (\"Riga \" + (idx+1));\n                name = name.replace(\/\\s+\/g,\" \").trim();\n                if (!name || \/^nome\\s+prodotto$\/i.test(name) || \/^descrizione$\/i.test(name)) return;\n                if (name.length > 180) name = name.slice(0,177) + \"...\";\n                var sc = specCount(row), ac = anyMedia(row);\n                var item = {table:rt, row_index:idx, name:name, specific_image_count:sc, association_count:ac};\n                if (sc > 0) { yes++; if (includeExisting) out.push(item); }\n                else { no++; out.push(item); }\n            });\n        });\n        return {targets:out, no:no, yes:yes};\n    }\n    function hardCloseAlert(){\n        var ov = byId(\"ti-alert-ov\");\n        try { if (window.closeModal) window.closeModal(\"ti-alert-ov\"); } catch(e) {}\n        if (ov) {\n            try { ov.style.setProperty(\"display\",\"none\",\"important\"); ov.style.setProperty(\"visibility\",\"hidden\",\"important\"); ov.style.setProperty(\"opacity\",\"0\",\"important\"); ov.style.setProperty(\"pointer-events\",\"none\",\"important\"); ov.removeAttribute(\"data-ti-imggen-confirm-428\"); } catch(e) {}\n        }\n        var x = byId(\"ti-alert-x-428\"), c = byId(\"ti-alert-close-extra-428\"), no = byId(\"ti-alert-no\"), yes = byId(\"ti-alert-yes\"), copy = byId(\"ti-alert-copy\");\n        try { if (x) x.style.display = \"none\"; if (c) c.style.display = \"none\"; if (no) no.style.display = \"\"; if (yes) yes.disabled = false; if (no) no.disabled = false; if (copy) copy.style.display = \"block\"; } catch(e) {}\n        try { if (window.updatePluginScrollLock) window.updatePluginScrollLock(); } catch(e) {}\n    }\n    function ensureConfirmChrome(){\n        var ov = byId(\"ti-alert-ov\"), modal = ov ? ov.querySelector(\".ti-modal\") : null;\n        if (!ov || !modal) return {};\n        modal.style.position = \"relative\";\n        modal.style.maxWidth = \"min(92vw, 440px)\";\n        modal.style.width = \"min(92vw, 420px)\";\n        var x = byId(\"ti-alert-x-428\");\n        if (!x) {\n            x = document.createElement(\"button\");\n            x.type = \"button\";\n            x.id = \"ti-alert-x-428\";\n            x.innerHTML = \"&times;\";\n            x.className = \"ti-btn\";\n            x.style.cssText = \"position:absolute;right:8px;top:8px;width:30px;height:30px;line-height:22px;padding:0;border-radius:999px;background:#b91c1c;color:#fff;font-size:22px;font-weight:bold;z-index:3;\";\n            modal.insertBefore(x, modal.firstChild);\n        }\n        var close = byId(\"ti-alert-close-extra-428\");\n        if (!close) {\n            close = document.createElement(\"button\");\n            close.type = \"button\";\n            close.id = \"ti-alert-close-extra-428\";\n            close.className = \"ti-btn\";\n            close.textContent = \"Chiudi\";\n            close.style.cssText = \"display:none;margin:12px auto 0 auto;min-width:120px;background:#4b5563;color:#fff;\";\n            modal.appendChild(close);\n        }\n        return {ov:ov, modal:modal, x:x, close:close};\n    }\n    function openImageChoice(html, actions){\n        actions = actions || [];\n        var msg = byId(\"ti-alert-msg\"), yes = byId(\"ti-alert-yes\"), no = byId(\"ti-alert-no\"), copy = byId(\"ti-alert-copy\");\n        var ch = ensureConfirmChrome();\n        if (!ch.ov || !msg || !yes || !no) { if (actions[0] && actions[0].fn) actions[0].fn(); return; }\n        ch.ov.setAttribute(\"data-ti-imggen-confirm-428\", \"1\");\n        msg.innerHTML = html;\n        msg.style.textAlign = \"left\";\n        msg.style.paddingRight = \"24px\";\n        if (copy) copy.style.display = \"none\";\n        function runAction(fn){\n            var started = false;\n            return function(ev){\n                try { if (ev) { ev.preventDefault(); ev.stopPropagation(); } } catch(e) {}\n                if (started) return false;\n                started = true;\n                try { yes.disabled = true; no.disabled = true; if (ch.close) ch.close.disabled = true; if (ch.x) ch.x.disabled = true; } catch(e) {}\n                hardCloseAlert();\n                if (typeof fn === \"function\") {\n                    var rb = byId(\"ti-conf-ai-reply\");\n                    if (rb) { rb.style.display = \"block\"; rb.innerHTML = \"Opzione confermata. Avvio generazione foto AI...\"; }\n                    try { if (window.showProgressPopup) window.showProgressPopup(\"Generazione foto AI\", \"Opzione confermata. Sto preparando l elenco record.\", {startPercent:3, targetPercent:12, stepMs:250, cancellable:true}); } catch(e) {}\n                    setTimeout(function(){ try { fn(); } catch(err) { if (window.tiAlert) window.tiAlert(\"Generazione foto AI non avviata: \" + (err && err.message ? err.message : err)); } }, 90);\n                }\n                return false;\n            };\n        }\n        yes.style.display = \"inline-block\";\n        yes.style.flex = \"1 1 160px\";\n        yes.style.minWidth = \"150px\";\n        yes.style.background = actions[0] && actions[0].background ? actions[0].background : \"#059669\";\n        yes.style.color = \"#fff\";\n        yes.innerText = (actions[0] && actions[0].label) || \"AVVIA\";\n        yes.onclick = runAction(actions[0] && actions[0].fn);\n        if (actions[1]) {\n            no.style.display = \"inline-block\";\n            no.style.flex = \"1 1 150px\";\n            no.style.minWidth = \"140px\";\n            no.style.background = actions[1].background || \"#2563eb\";\n            no.style.color = \"#fff\";\n            no.innerText = actions[1].label || \"SOLO MANCANTI\";\n            no.onclick = runAction(actions[1].fn);\n        } else {\n            no.style.display = \"none\";\n        }\n        if (ch.x) { ch.x.style.display = \"inline-block\"; ch.x.disabled = false; ch.x.onclick = function(ev){ try { if (ev) { ev.preventDefault(); ev.stopPropagation(); } } catch(e) {} hardCloseAlert(); return false; }; }\n        if (ch.close) { ch.close.style.display = \"block\"; ch.close.disabled = false; ch.close.onclick = function(ev){ try { if (ev) { ev.preventDefault(); ev.stopPropagation(); } } catch(e) {} hardCloseAlert(); return false; }; }\n        try { if (window.showPluginModal) window.showPluginModal(\"ti-alert-ov\"); else ch.ov.style.display = \"flex\"; } catch(e) { ch.ov.style.display = \"flex\"; }\n        try { if (window.keepUserCommunicationPopupsInFront) { window.keepUserCommunicationPopupsInFront(); setTimeout(window.keepUserCommunicationPopupsInFront, 80); } } catch(e) {}\n    }\n    window.tiOpenImageGenerationConfirm428 = openImageChoice;\n\n    var previousConfirm = window.tiConfirmYesNoAction;\n    if (typeof previousConfirm === \"function\" && !previousConfirm.__tiImgGen428) {\n        window.tiConfirmYesNoAction = function(msg, yesCb, noCb, yesLabel, noLabel){\n            var all = String(msg || \"\") + \" \" + String(yesLabel || \"\") + \" \" + String(noLabel || \"\");\n            if (\/generazione\\s+immagini|generazione\\s+foto|immagini\\s+aggiuntive|immagini\\s+ancora\\s+mancanti|usa\\s+ai\\s+generica|genera\\s+anche\\s+aggiuntive|solo\\s+mancanti\/i.test(strip(all))) {\n                openImageChoice(msg, [\n                    {label: yesLabel || \"AVVIA\", fn: yesCb, background:\"#059669\"},\n                    {label: noLabel || \"SOLO MANCANTI\", fn: noCb, background:\"#2563eb\"}\n                ]);\n                return;\n            }\n            return previousConfirm.apply(this, arguments);\n        };\n        window.tiConfirmYesNoAction.__tiImgGen428 = true;\n    }\n\n    function setLoader(title, sub, item, pct){\n        try {\n            var ov = byId(\"ti-ai-loader-ov\"); if (ov) { ov.style.display = \"flex\"; ov.style.visibility = \"visible\"; ov.style.opacity = \"1\"; ov.style.pointerEvents = \"auto\"; }\n            var t=byId(\"ti-loader-title\"), s=byId(\"ti-loader-sub\"), i=byId(\"ti-loader-current-item\"), p=byId(\"ti-loader-percent\"), b=byId(\"ti-loader-bar\"), a=byId(\"ti-loader-actions\"), c=byId(\"ti-loader-close\");\n            if (t && title) t.innerText = title;\n            if (s && sub) s.innerText = sub;\n            if (i) { i.style.display = item ? \"block\" : \"none\"; i.innerText = item ? (\"Elemento in lavorazione: \" + item) : \"\"; }\n            if (typeof pct === \"number\") { var pc = Math.max(0, Math.min(100, Math.round(pct))); if (p) p.innerText = pc + \"%\"; if (b) b.style.width = pc + \"%\"; }\n            if (a) a.style.display = \"block\";\n            if (c) { c.innerText = \"Interrompi generazione\"; c.disabled = false; c.style.display = \"inline-block\"; c.style.background = \"#b91c1c\"; c.style.color = \"#fff\"; }\n            if (window.showPluginModal) window.showPluginModal(\"ti-ai-loader-ov\"); else if (window.openModal) window.openModal(\"ti-ai-loader-ov\");\n        } catch(e) {}\n    }\n    function refreshDb(db){\n        if (window.refreshCurrentConfigDb) return Promise.resolve(window.refreshCurrentConfigDb(db)).catch(function(){});\n        return ajax(fd(\"ti_ai_get_db_data\", {db:db}), 45000, \"Rilettura database non completata\").then(function(r){ if (r && r.success && r.data) { window.currentDbData = r.data; if (window.renderConfig) window.renderConfig(); } }).catch(function(){});\n    }\n    function missingKey(d){ d = d || {}; return d.code === \"missing_openai_key\" || \/chiave\\s+openai\/i.test(String(d.message || \"\")); }\n\n    window.runBatchAIGen = function(tableName){\n        startImageBatch428(tableName, {confirmed:false, includeExisting:false, generic:false});\n    };\n    function startImageBatch428(tableName, opt){\n        opt = opt || {};\n        var dbEl = byId(\"ti-ditta\"), reply = byId(\"ti-conf-ai-reply\");\n        if (!dbEl || !dbEl.value || dbEl.value === \"NEW_DB\") { if (window.tiAlert) window.tiAlert(\"Seleziona prima una ditta.\"); return; }\n        if (!reply) { if (window.tiAlert) window.tiAlert(\"Area risposta configurazione non disponibile.\"); return; }\n        var detailEl = byId(\"ti-conf-ai-photo-detail\"), detail = detailEl && detailEl.value ? String(detailEl.value).trim() : \"\";\n        var pack = targetsFor(tableName, !!opt.includeExisting);\n        if (!opt.confirmed && !opt.generic && pack.yes > 0) {\n            var html = \"<b>Conferma generazione immagini AI<\/b><br><br>\" +\n                \"La generazione pu\u00f2 creare immagini aggiuntive rispetto a quelle gi\u00e0 presenti.<br><br>\" +\n                \"Voci senza immagine specifica: <b>\" + pack.no + \"<\/b><br>\" +\n                \"Voci gi\u00e0 con immagine specifica: <b>\" + pack.yes + \"<\/b><br><br>\" +\n                (pack.no > 0 ? \"Scegli se avviare solo le immagini mancanti oppure anche immagini aggiuntive.\" : \"Non risultano immagini mancanti. Puoi avviare solo la generazione aggiuntiva oppure chiudere.\");\n            var actions = [];\n            actions.push({label:\"AVVIA ANCHE AGGIUNTIVE\", background:\"#059669\", fn:function(){ startImageBatch428(tableName, {confirmed:true, includeExisting:true, generic:false}); }});\n            if (pack.no > 0) actions.push({label:\"AVVIA SOLO MANCANTI\", background:\"#2563eb\", fn:function(){ startImageBatch428(tableName, {confirmed:true, includeExisting:false, generic:false}); }});\n            openImageChoice(html, actions);\n            return;\n        }\n        var targets = pack.targets, total = targets.length, idx = 0, ok = 0, fail = 0, skip = 0, logs = [], generated = [];\n        var st = window.tiBatchAIGenState = {active:true, fix420:true, fix428:true, cancelRequested:false, currentController:null, currentName:\"\", lastPercent:3, generated:generated, db:dbEl.value, tableName:tableName, phase:\"preparazione\"};\n        reply.style.display = \"block\";\n        reply.innerHTML = \"Avvio generazione immagini AI...<br>Da elaborare: <b>\" + total + \"<\/b>.\" + (opt.includeExisting ? \"<br><span style='color:#facc15;'>Generazione aggiuntiva confermata.<\/span>\" : \"<br><span style='color:#94a3b8;'>Saranno elaborate solo le voci senza immagine specifica.<\/span>\") + (opt.generic ? \"<br><b>Modalit\u00e0 AI generica attiva.<\/b>\" : \"\");\n        try { if (window.clearLongAIProcess) window.clearLongAIProcess(); } catch(e) {}\n        if (window.showProgressPopup) window.showProgressPopup(\"Generazione foto AI\", opt.generic ? \"Retry con informazioni pi\u00f9 generiche tramite AI.\" : \"Preparazione elenco record per immagini AI.\", {startPercent:3, targetPercent:12, stepMs:250, cancellable:true});\n        if (!total) {\n            st.active = false;\n            setLoader(\"Generazione foto AI\", \"Nessun elemento da elaborare.\", \"\", 100);\n            if (window.hideProgressPopup) window.hideProgressPopup(true, \"Nessun elemento da elaborare\", {notifyUser:false, remindSave:false});\n            reply.innerHTML = \"Nessun record senza immagine specifica trovato.<br>Record gi\u00e0 con immagine specifica: <b>\" + pack.yes + \"<\/b>.\";\n            return;\n        }\n        function end(interrupted){\n            st.active = false; st.cancelRequested = false;\n            try { if (st.currentController) st.currentController.abort(); } catch(e) {}\n            refreshDb(dbEl.value).finally(function(){\n                var rem = targetsFor(tableName, false).no;\n                setLoader(interrupted ? \"Generazione foto AI interrotta\" : \"Generazione foto AI completata\", interrupted ? \"Batch interrotto correttamente.\" : (rem > 0 ? \"Conclusa con immagini ancora mancanti.\" : \"Batch completato.\"), \"\", 100);\n                var a = byId(\"ti-loader-actions\");\n                if (a) {\n                    a.innerHTML = \"<button type='button' id='ti-loader-close-final-428' class='ti-btn' style='width:100%;background:#2563eb;color:#fff;'>Chiudi<\/button>\";\n                    var b = byId(\"ti-loader-close-final-428\");\n                    if (b) b.onclick = function(){ try { if (window.closeModal) window.closeModal(\"ti-ai-loader-ov\"); } catch(e) {} try { if (window.clearLongAIProcess) window.clearLongAIProcess(); } catch(e) {} };\n                }\n                reply.innerHTML = (interrupted ? \"\u26a0\ufe0f Generazione immagini AI interrotta.\" : \"\u2705 Generazione immagini AI completata.\") + \"<br>Generate e associate: <b>\" + ok + \"<\/b> \/ \" + total + \"<br>Saltati: <b>\" + skip + \"<\/b><br>Errori: <b>\" + fail + \"<\/b><br>Ancora senza immagine specifica: <b>\" + rem + \"<\/b><br><br>\" + logs.slice(-160).join(\"<br>\");\n                if (!interrupted && !opt.generic && rem > 0) {\n                    openImageChoice(\"<b>Generazione immagini AI conclusa<\/b><br><br>Alcune immagini non sono state generate.<br><br>Immagini ancora mancanti: <b>\" + rem + \"<\/b><br><br>Vuoi provare a usare informazioni pi\u00f9 generiche tramite AI, come una chat AI generica, usando tutte le informazioni disponibili?\", [\n                        {label:\"AVVIA RETRY AI GENERICA\", background:\"#059669\", fn:function(){ startImageBatch428(tableName, {confirmed:true, includeExisting:false, generic:true}); }}\n                    ]);\n                }\n            });\n        }\n        function next(){\n            st = window.tiBatchAIGenState || st;\n            if (!st.active) return;\n            if (st.cancelRequested) { end(true); return; }\n            if (idx >= total) { end(false); return; }\n            var it = targets[idx] || {}, name = it.name || (\"Record \" + (idx+1));\n            var pct = Math.max(5, Math.min(97, Math.round(((idx + 0.15) \/ Math.max(1,total)) * 100)));\n            st.currentName = name; st.lastPercent = pct; st.phase = \"record \" + (idx+1) + \" \/ \" + total;\n            if (window.updateProgressPopup) window.updateProgressPopup(pct, \"Generazione foto AI\", (opt.generic ? \"Retry AI generica - \" : \"\") + \"Elaborazione \" + (idx+1) + \" \/ \" + total + \": \" + name, name);\n            setLoader(\"Generazione foto AI\", (opt.generic ? \"Retry AI generica - \" : \"\") + \"Elaborazione \" + (idx+1) + \" \/ \" + total + \": \" + name, name, pct);\n            reply.innerHTML = \"Generazione immagini AI in corso...<br>Elaborazione <b>\" + (idx+1) + \" \/ \" + total + \"<\/b>: \" + esc(name);\n            var form = fd(\"ti_ai_config_action\", {db:dbEl.value, mode:\"batch_ai_image_one\", table:it.table || tableName, row_index:it.row_index, detail_prompt:detail, skip_existing:opt.includeExisting ? \"0\" : \"1\", allow_fallback_logo:\"0\", prompt_mode:opt.generic ? \"generic_ai\" : \"record\"});\n            ajax(form, 130000, \"Timeout su record: \" + name, function(sec){ if (window.updateProgressPopup) window.updateProgressPopup(Math.min(96, pct + Math.min(5, Math.floor(sec\/18))), \"Generazione foto AI\", \"Attendo risposta AI per \" + (idx+1) + \" \/ \" + total + \": \" + name + \" (\" + sec + \"s)\", name); }).then(function(res){\n                var d = res && res.data ? res.data : {};\n                if (!res || !res.success) {\n                    if (missingKey(d)) { fail++; logs.push(\"\u274c \" + esc(d.message || \"Chiave OpenAI mancante\")); st.cancelRequested = true; return; }\n                    fail++; logs.push(\"\u274c \" + esc(name) + \": \" + esc(d.message || \"errore generazione\")); return;\n                }\n                if (d.generated) { ok++; generated.push({table:d.table || it.table || tableName, row_index:d.row_index !== undefined ? d.row_index : it.row_index, field:d.image_field || \"Immagine\", value:d.added_value || d.filename || d.url, name:d.name || name}); logs.push(\"\u2705 \" + esc(d.name || name) + \": immagine generata\"); }\n                else if (d.skipped) { skip++; logs.push(\"\u23ed\ufe0f \" + esc(d.name || name) + \": \" + esc(d.message || \"record saltato\")); }\n                else { fail++; logs.push(\"\u274c \" + esc(d.name || name) + \": \" + esc(d.message || \"errore generazione\")); }\n            }).catch(function(err){\n                if (st.cancelRequested) logs.push(\"\u23f9\ufe0f \" + esc(name) + \": interruzione richiesta.\");\n                else { fail++; logs.push(\"\u23f1\ufe0f \" + esc(name) + \": \" + esc(err && err.message ? err.message : \"timeout o errore rete\") + \". Record saltato.\"); }\n            }).finally(function(){\n                if ((window.tiBatchAIGenState || st).cancelRequested) { end(true); return; }\n                idx++;\n                setTimeout(next, 350);\n            });\n        }\n        ajax(fd(\"ti_ai_batch_ai_images_prepare\", {db:dbEl.value, tables:tableName, detail_prompt:detail, only_missing:opt.includeExisting ? \"0\" : \"1\"}), 18000, \"Preflight chiave OpenAI non completato\").then(function(r){\n            if (r && !r.success && r.data && missingKey(r.data)) {\n                st.active = false;\n                setLoader(\"Generazione foto AI non avviata\", r.data.message || \"Chiave OpenAI mancante\", \"\", 100);\n                reply.innerHTML = \"<b>Generazione foto AI non avviata.<\/b><br>\" + esc(r.data.message || \"Chiave OpenAI mancante.\");\n                if (window.tiAlert) window.tiAlert(\"Generazione foto AI non avviata: \" + (r.data.message || \"Chiave OpenAI mancante.\"));\n                return;\n            }\n            next();\n        }).catch(function(){ next(); });\n    }\n})();\n<\/script>\n\n\n<script id=\"ti-save-info-final-429\">\n(function(){\n    \"use strict\";\n    if (window.__tiSaveInfoFinal429) return;\n    window.__tiSaveInfoFinal429 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function text(el){ return String(el ? (el.innerText || el.textContent || el.value || '') : ''); }\n    function low(v){ return String(v || '').toLowerCase(); }\n    function esc(v){ return String(v == null ? '' : v).replace(\/[&<>\"']\/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c];}); }\n    function isSaveTitle(title){ return \/salvataggio\\s+configurazione|cancellazione\\s+definitiva\\s+configurazione|salvataggio\\s+finale\\s+configurazione\/i.test(String(title || '')); }\n    function loaderLooksLikeSave(){\n        var t = text(byId('ti-loader-title')) + ' ' + text(byId('ti-loader-sub'));\n        return isSaveTitle(t) || \/sto preparando e salvando|salvo ora il database|errore salvataggio|configurazione salvata\/i.test(t);\n    }\n    function hardCloseLoaderIfSave(){\n        var ov = byId('ti-ai-loader-ov');\n        if (ov && loaderLooksLikeSave()) {\n            try { ov.style.setProperty('display','none','important'); ov.style.setProperty('visibility','hidden','important'); ov.style.setProperty('pointer-events','none','important'); } catch(e) {}\n            try { if (window.closeModal) window.closeModal('ti-ai-loader-ov'); } catch(e) {}\n        }\n    }\n    function clearLongProcessSafe(){\n        try {\n            if (window.tiLongProcessState && typeof window.tiLongProcessState === 'object') {\n                window.tiLongProcessState.cancelled = false;\n                window.tiLongProcessState.cancellable = false;\n                window.tiLongProcessState.controller = null;\n                window.tiLongProcessState.label = '';\n                window.tiLongProcessState.active = false;\n            }\n        } catch(e) {}\n    }\n    function resetSaveUi(reason, notify){\n        window.__saveConfigInFlight = false;\n        window.__tiSaveStartedAt429 = 0;\n        try { if (window.tiLoaderState) { window.tiLoaderState.active = false; clearInterval(window.tiLoaderState.timer); } } catch(e) {}\n        clearLongProcessSafe();\n        hardCloseLoaderIfSave();\n        var btn = byId('lbl-conf-save');\n        if (btn) {\n            btn.disabled = false;\n            btn.style.pointerEvents = '';\n            if (\/salvataggio\/i.test(String(btn.innerText || ''))) btn.innerText = 'Salva Modifiche';\n            if (!btn.style.background || btn.style.background === 'rgb(217, 119, 6)' || btn.style.background === '#d97706') btn.style.background = '#16a34a';\n        }\n        if (notify && window.tiAlert) window.tiAlert(reason || 'Salvataggio configurazione sbloccato. Puoi correggere eventuali dati e riprovare.');\n    }\n    window.tiResetSaveConfig429 = resetSaveUi;\n\n    var oldShow = window.showProgressPopup;\n    window.showProgressPopup = function(title, subtitle, opts){\n        if (isSaveTitle(title)) {\n            opts = opts || {};\n            var ov = byId('ti-ai-loader-ov');\n            if (!ov || !oldShow) return oldShow ? oldShow.apply(this, arguments) : undefined;\n            var titleEl = byId('ti-loader-title'), subEl = byId('ti-loader-sub'), itemEl = byId('ti-loader-current-item');\n            var wrap = byId('ti-loader-progress-wrap'), pct = byId('ti-loader-percent'), bar = byId('ti-loader-bar'), actions = byId('ti-loader-actions');\n            if (titleEl) titleEl.innerText = title || 'Salvataggio configurazione';\n            if (subEl) subEl.innerText = subtitle || 'Sto salvando le modifiche al database.';\n            if (itemEl) { itemEl.style.display = 'none'; itemEl.innerText = ''; }\n            if (wrap) wrap.style.display = 'block';\n            if (actions) actions.style.display = 'none';\n            window.tiLoaderState = window.tiLoaderState || {};\n            try { clearInterval(window.tiLoaderState.timer); } catch(e) {}\n            window.tiLoaderState.active = true;\n            window.tiLoaderState.percent = Math.max(0, Math.min(100, parseInt(opts.startPercent || 5, 10) || 5));\n            window.tiLoaderState.target = Math.max(window.tiLoaderState.percent, Math.min(99, parseInt(opts.targetPercent || 95, 10) || 95));\n            if (pct) pct.innerText = window.tiLoaderState.percent + '%';\n            if (bar) bar.style.width = window.tiLoaderState.percent + '%';\n            try { if (window.showPluginModal) window.showPluginModal('ti-ai-loader-ov'); else ov.style.display = 'flex'; } catch(e) { ov.style.display = 'flex'; }\n            var stepMs = Math.max(180, parseInt(opts.stepMs || 500, 10) || 500);\n            window.tiLoaderState.timer = setInterval(function(){\n                if (!window.tiLoaderState.active) return;\n                window.tiLoaderState.percent = Math.min(window.tiLoaderState.target, (window.tiLoaderState.percent || 0) + 2);\n                if (pct) pct.innerText = window.tiLoaderState.percent + '%';\n                if (bar) bar.style.width = window.tiLoaderState.percent + '%';\n                if (window.tiLoaderState.percent >= window.tiLoaderState.target) clearInterval(window.tiLoaderState.timer);\n            }, stepMs);\n            \/\/ Salvataggio configurazione: il loader e' solo informativo e non deve creare un AbortController globale\n            \/\/ che poi blocca lettura dati o altre richieste AJAX.\n            clearLongProcessSafe();\n            return;\n        }\n        return oldShow ? oldShow.apply(this, arguments) : undefined;\n    };\n\n    var oldHide = window.hideProgressPopup;\n    window.hideProgressPopup = function(success, finalText, opts){\n        var ret;\n        try { ret = oldHide ? oldHide.apply(this, arguments) : undefined; }\n        finally {\n            var finalTxt = String(finalText || '') + ' ' + text(byId('ti-loader-title')) + ' ' + text(byId('ti-loader-sub'));\n            if (isSaveTitle(finalTxt) || \/errore\\s+salvataggio|configurazione\\s+salvata|salvataggio\/i.test(finalTxt)) {\n                setTimeout(function(){ resetSaveUi('', false); }, success ? 900 : 350);\n            } else if (success === false) {\n                setTimeout(function(){ clearLongProcessSafe(); }, 350);\n            }\n        }\n        return ret;\n    };\n\n    var oldFetch = window.fetch;\n    if (oldFetch && !oldFetch.__tiSave429) {\n        var wrapped = function(input, init){\n            try {\n                var body = init && init.body;\n                if (body instanceof FormData) {\n                    var action = String(body.get('ti_action') || body.get('action') || '');\n                    if (action === 'ti_ai_save_full_db') {\n                        \/\/ Il salvataggio non deve ereditare AbortController globali di processi precedenti.\n                        if (init && init.signal && window.tiLongProcessState && init.signal === (window.tiLongProcessState.controller && window.tiLongProcessState.controller.signal)) {\n                            init = Object.assign({}, init);\n                            delete init.signal;\n                        }\n                    }\n                }\n            } catch(e) {}\n            return oldFetch.call(this, input, init);\n        };\n        wrapped.__tiSave429 = true;\n        window.fetch = wrapped;\n    }\n\n    var oldSave = window.saveConfig;\n    if (typeof oldSave === 'function' && !oldSave.__tiSave429) {\n        window.saveConfig = function(){\n            if (window.__saveConfigInFlight && window.__tiSaveStartedAt429 && Date.now() - window.__tiSaveStartedAt429 > 45000) {\n                resetSaveUi('Il precedente salvataggio risultava ancora bloccato: stato sbloccato automaticamente.', false);\n            }\n            if (window.__saveConfigInFlight) return;\n            window.__tiSaveStartedAt429 = Date.now();\n            clearLongProcessSafe();\n            try {\n                var ret = oldSave.apply(this, arguments);\n                setTimeout(function(){\n                    if (window.__saveConfigInFlight && window.__tiSaveStartedAt429 && Date.now() - window.__tiSaveStartedAt429 > 240000) {\n                        resetSaveUi('Salvataggio configurazione non completato entro il tempo massimo. La UI e stata sbloccata: verifica la connessione\/server e riprova.', true);\n                    }\n                }, 240000);\n                return ret;\n            } catch(err) {\n                resetSaveUi('Errore avvio salvataggio configurazione: ' + (err && err.message ? err.message : err), true);\n                throw err;\n            }\n        };\n        window.saveConfig.__tiSave429 = true;\n    }\n\n    \/\/ Help ? finale: invia anche un testo-query al server, cosi la ricerca manuale usa tabella\/campo come termini\n    \/\/ e non resta limitata alla sola risposta locale.\n    var oldAsk = window.askColumnHelp;\n    if (typeof oldAsk === 'function' && !oldAsk.__tiManual429) {\n        window.askColumnHelp = function(tbl, col){\n            var table = String(tbl || '').replace(\/[\\\"\u201c\u201d]\/g, '').trim();\n            var field = String(col || '').replace(\/\\\\'\/g, \"'\").replace(\/[\\\"\u201c\u201d]\/g, '').trim();\n            var box = byId('col-help-content');\n            var serverNoteId = 'ti-help-server-429';\n            try { oldAsk.apply(this, arguments); } catch(e) {}\n            setTimeout(function(){\n                try {\n                    var srv = byId('ti-help-server-421');\n                    if (srv && !byId(serverNoteId)) srv.insertAdjacentHTML('afterend', '<div id=\"' + serverNoteId + '\" style=\"margin-top:8px;color:#93c5fd;\">Consulto manuale operativo, AI-docs e documenti collegati con ricerca estesa...<\/div>');\n                } catch(e) {}\n            }, 50);\n            var fd = new FormData();\n            fd.append('ti_action', 'ti_ai_config_action');\n            fd.append('action', 'ti_ai_config_action');\n            fd.append('mode', 'manual_help');\n            var dbEl = byId('ti-ditta');\n            fd.append('db', dbEl && dbEl.value ? dbEl.value : 'NEW_DB');\n            fd.append('table', table);\n            fd.append('field', field);\n            fd.append('text', (table + ' ' + field + ' manuale operativo documentazione AI Informazioni Istruzioni').trim());\n            fd.append('lang', String(window.currLang || document.documentElement.lang || 'it').toLowerCase().indexOf('en') === 0 ? 'en' : 'it');\n            var fetcher = window.fetchJsonSafe || function(url, opts){ return fetch(url, opts).then(function(r){ return r.json(); }); };\n            fetcher(window.tiUrl || window.tiAjaxUrl || window.location.href, {method:'POST', body:fd, credentials:'same-origin', cache:'no-store', tiNoLongProcessSignal:true, tiSilentJsonError:true}).then(function(res){\n                var reply = res && res.success && res.data ? String(res.data.reply || '') : '';\n                if (!reply) return;\n                if (box) box.innerHTML = '<div style=\"text-align:left;line-height:1.45;\">' + reply + '<\/div>';\n                try { if (window.tiPromoteModalFront355) window.tiPromoteModalFront355('ti-col-help-ov'); } catch(e) {}\n            }).catch(function(err){\n                var note = byId(serverNoteId) || byId('ti-help-server-421');\n                if (note) note.innerHTML = '<span style=\"color:#fbbf24;\">Risposta locale mostrata. Manuale\/documenti non raggiungibili: ' + esc(err && err.message ? err.message : 'errore') + '<\/span>';\n            });\n            return false;\n        };\n        window.askColumnHelp.__tiManual429 = true;\n    }\n})();\n<\/script>\n\n\n<script id=\"ti-manual-help-save-final-430\">\n(function(){\n    \"use strict\";\n    if (window.__tiManualHelpSaveFinal430) return;\n    window.__tiManualHelpSaveFinal430 = true;\n\n    function byId(id){ return document.getElementById(id); }\n    function esc(v){ return String(v == null ? \"\" : v).replace(\/[&<>\"']\/g,function(c){return {\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",\"\\\"\":\"&quot;\",\"'\":\"&#39;\"}[c];}); }\n    function visible(el){ if(!el) return false; try{ var cs=window.getComputedStyle?window.getComputedStyle(el):el.style; return cs && cs.display!=='none' && cs.visibility!=='hidden' && parseFloat(cs.opacity||'1')>0; }catch(e){ return !!el; } }\n    function actionFromBody(body){ try { return body instanceof FormData ? String(body.get('ti_action') || body.get('action') || '') : ''; } catch(e){ return ''; } }\n    function isSaveActive(){\n        try { return !!(window.__saveConfigInFlight || window.__tiSaveConfigInFlight429 || window.__tiSaveConfigInFlight430 || \/salvataggio configurazione|cancellazione definitiva configurazione\/i.test(String(window.tiLongProcessState && window.tiLongProcessState.label || ''))); } catch(e) { return false; }\n    }\n    function isolateSaveLongProcess(){\n        try {\n            if (!isSaveActive() || !window.tiLongProcessState) return;\n            window.tiLongProcessState.cancellable = false;\n            window.tiLongProcessState.cancelled = false;\n            window.tiLongProcessState.controller = null;\n        } catch(e) {}\n    }\n\n    if (typeof window.fetchJsonSafe === 'function' && !window.fetchJsonSafe.__tiSaveInfo430) {\n        var oldFetchJsonSafe430 = window.fetchJsonSafe;\n        window.fetchJsonSafe = function(url, options){\n            var opts = Object.assign({}, options || {});\n            var action = actionFromBody(opts.body);\n            if (isSaveActive() && action !== 'ti_ai_save_full_db' && action !== 'ti_ai_config_delete_rows_definitive') {\n                opts.tiNoLongProcessSignal = true;\n                if (opts.signal) delete opts.signal;\n            }\n            if (action === 'ti_ai_manual_help_430' || (action === 'ti_ai_config_action' && opts.body instanceof FormData && String(opts.body.get('mode') || '') === 'manual_help')) {\n                opts.tiNoLongProcessSignal = true;\n                if (opts.signal) delete opts.signal;\n                opts.tiSilentJsonError = true;\n            }\n            isolateSaveLongProcess();\n            return oldFetchJsonSafe430.call(this, url, opts);\n        };\n        window.fetchJsonSafe.__tiSaveInfo430 = true;\n    }\n\n    if (typeof window.postFormDataJsonSafe === 'function' && !window.postFormDataJsonSafe.__tiSaveInfo430) {\n        var oldPostForm430 = window.postFormDataJsonSafe;\n        window.postFormDataJsonSafe = function(url, fd, options){\n            var opts = Object.assign({}, options || {});\n            var action = actionFromBody(fd);\n            if (isSaveActive() && action !== 'ti_ai_save_full_db' && action !== 'ti_ai_config_delete_rows_definitive') {\n                opts.tiNoLongProcessSignal = true;\n                if (opts.signal) delete opts.signal;\n            }\n            if (action === 'ti_ai_manual_help_430') opts.tiNoLongProcessSignal = true;\n            isolateSaveLongProcess();\n            return oldPostForm430.call(this, url, fd, opts);\n        };\n        window.postFormDataJsonSafe.__tiSaveInfo430 = true;\n    }\n\n    if (typeof window.isLongAIProcessCancelled === 'function' && !window.isLongAIProcessCancelled.__tiSaveInfo430) {\n        var oldIsCancelled430 = window.isLongAIProcessCancelled;\n        window.isLongAIProcessCancelled = function(){\n            if (isSaveActive()) return false;\n            return oldIsCancelled430.apply(this, arguments);\n        };\n        window.isLongAIProcessCancelled.__tiSaveInfo430 = true;\n    }\n\n    if (typeof window.beginLongAIProcess === 'function' && !window.beginLongAIProcess.__tiSaveInfo430) {\n        var oldBegin430 = window.beginLongAIProcess;\n        window.beginLongAIProcess = function(label, cancellable){\n            var s = String(label || '');\n            if (\/salvataggio configurazione|cancellazione definitiva configurazione\/i.test(s)) {\n                window.__tiSaveConfigInFlight430 = true;\n                var ctrl = oldBegin430.call(this, label, false);\n                isolateSaveLongProcess();\n                return ctrl;\n            }\n            return oldBegin430.apply(this, arguments);\n        };\n        window.beginLongAIProcess.__tiSaveInfo430 = true;\n    }\n\n    function buildLocalHelp(table, field, en){\n        var out = '';\n        try { if (typeof window.tiBuildLocalTableHelp421 === 'function') out = window.tiBuildLocalTableHelp421(table, field, en); } catch(e) {}\n        if (!out) {\n            out = '<div style=\"text-align:left;line-height:1.45;color:#e5e7eb;\"><b>' + esc(en ? 'Local data' : 'Dati locali') + '<\/b><br>' + esc(en ? 'Local schema not available yet. I am querying the operating manual and company documents.' : 'Schema locale non ancora disponibile. Sto interrogando manuale operativo e documenti ditta.') + '<\/div>';\n        }\n        return out;\n    }\n    function getDbName(){ var dbEl=byId('ti-ditta'); return dbEl && dbEl.value ? dbEl.value : 'NEW_DB'; }\n    function postManualHelp430(table, field, en){\n        var fd = new FormData();\n        fd.append('ti_action','ti_ai_manual_help_430');\n        fd.append('action','ti_ai_manual_help_430');\n        fd.append('db', getDbName());\n        fd.append('table', table);\n        fd.append('field', field);\n        fd.append('lang', en ? 'en' : 'it');\n        var opts = {method:'POST', body:fd, credentials:'same-origin', cache:'no-store', tiNoLongProcessSignal:true, tiSilentJsonError:true, headers:{'X-TI-No-Long-Process':'1','X-TI-Manual-Help':'1'}};\n        if (typeof window.fetchJsonSafe === 'function') return window.fetchJsonSafe(window.tiUrl || window.tiAjaxUrl || window.location.href, opts);\n        return fetch(window.tiUrl || window.tiAjaxUrl || window.location.href, opts).then(function(r){ return r.text().then(function(raw){ try { return JSON.parse(String(raw||'').replace(\/^\\uFEFF\/,'').trim()); } catch(e) { throw new Error('Risposta server non valida: ' + String(raw||'').replace(\/<[^>]+>\/g,' ').replace(\/\\s+\/g,' ').slice(0,240)); } }); });\n    }\n\n    window.askColumnHelp = function(tbl, col){\n        var table = String(tbl || '').replace(\/[\\\"\u201c\u201d]\/g,'').trim();\n        var field = String(col || '').replace(\/\\\\'\/g,\"'\").replace(\/[\\\"\u201c\u201d]\/g,'').trim();\n        var en = String(window.currLang || document.documentElement.lang || 'it').toLowerCase().indexOf('en') === 0;\n        var ov = byId('ti-col-help-ov'), title = byId('col-help-title'), box = byId('col-help-content');\n        var req = Date.now() + '-' + Math.random().toString(36).slice(2);\n        window.tiColumnHelpActiveRequestId = req;\n        if (ov) { ov.setAttribute('data-ti-help-request-id', req); ov.setAttribute('data-ti-help-table', table); ov.setAttribute('data-ti-help-column', field); }\n        if (title) title.innerText = field ? ((en ? 'Field info: ' : 'Info campo: ') + field + ' [' + table + ']') : ((en ? 'Table info: ' : 'Info tabella: ') + table);\n        if (window.tiUpdateColumnHelpCompanyHeader) window.tiUpdateColumnHelpCompanyHeader();\n        var local = buildLocalHelp(table, field, en);\n        if (box) {\n            box.innerHTML = '<div style=\"text-align:left;line-height:1.45;\">' + local + '<\/div>' +\n                '<div id=\"ti-help-server-430\" style=\"margin-top:12px;padding-top:10px;border-top:1px solid #334155;color:#bfdbfe;text-align:left;\">\u23f3 ' + esc(en ? 'Searching operating manual, company documents and technical sources...' : 'Cerco manuale operativo, documenti ditta e fonti tecniche...') + '<\/div>';\n        }\n        try { if (window.openModal) window.openModal('ti-col-help-ov'); if (window.tiPromoteModalFront355) window.tiPromoteModalFront355('ti-col-help-ov'); } catch(e) {}\n        postManualHelp430(table, field, en).then(function(res){\n            if (window.tiColumnHelpActiveRequestId !== req) return;\n            if (ov && ov.getAttribute('data-ti-help-request-id') !== req) return;\n            var reply = res && res.success && res.data ? String(res.data.reply || '') : '';\n            if (!reply) {\n                var msg = res && res.data && res.data.message ? res.data.message : (en ? 'No manual\/document response available.' : 'Nessuna risposta da manuale\/documenti disponibile.');\n                var srv = byId('ti-help-server-430');\n                if (srv) srv.innerHTML = '<span style=\"color:#fbbf24;\">' + esc(msg) + '<\/span>';\n                return;\n            }\n            if (box) box.innerHTML = '<div style=\"text-align:left;line-height:1.45;\">' + reply + '<\/div>';\n            try { if (window.applyLayoutLanguage) window.applyLayoutLanguage(false); if (window.tiPromoteModalFront355) window.tiPromoteModalFront355('ti-col-help-ov'); } catch(e) {}\n        }).catch(function(err){\n            if (window.tiColumnHelpActiveRequestId !== req) return;\n            var srv = byId('ti-help-server-430');\n            var msg = err && err.message ? err.message : String(err || 'errore');\n            if (srv) srv.innerHTML = '<span style=\"color:#fbbf24;\">' + esc(en ? 'Local information shown. Manual\/document search unavailable now: ' : 'Informazione locale mostrata. Ricerca manuale\/documenti non disponibile ora: ') + esc(msg) + '<\/span>';\n        });\n        return false;\n    };\n\n\n    \/\/ 30.9.431: filtri tabelle Configurazione robusti e finale pulizia file non associati con zero file.\n    (function installConfigFiltersAndOrphanClose431(){\n        if (window.__tiConfigFiltersAndOrphanClose431Installed) return;\n        window.__tiConfigFiltersAndOrphanClose431Installed = true;\n\n        function byId(id){ return document.getElementById(id); }\n        function visible(el){ if(!el) return false; var st = window.getComputedStyle ? window.getComputedStyle(el) : null; return !!(el.offsetParent || (st && st.position === 'fixed')) && (!st || st.display !== 'none') && (!st || st.visibility !== 'hidden'); }\n        function escCss(v){\n            v = String(v == null ? '' : v);\n            if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(v);\n            return v.replace(\/[^a-zA-Z0-9_-]\/g, function(ch){ return '\\\\' + ch; });\n        }\n        function normText431(v){\n            return String(v == null ? '' : v).toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').replace(\/\\s+\/g, ' ').trim();\n        }\n        var previousConfigFilterMatches431 = typeof window.configFilterMatches === 'function' ? window.configFilterMatches : null;\n        window.configFilterMatches = function(cellVal, filterVal){\n            var f = String(filterVal == null ? '' : filterVal).trim();\n            if (!f) return true;\n            if (previousConfigFilterMatches431) {\n                try { if (previousConfigFilterMatches431(cellVal, filterVal)) return true; } catch(e) {}\n            }\n            var raw = String(cellVal == null ? '' : cellVal);\n            return normText431(raw).indexOf(normText431(f)) !== -1;\n        };\n\n        function getTableFromInput(inp){\n            if (!inp) return '';\n            var t = inp.getAttribute('data-tbl') || '';\n            if (!t) {\n                var det = inp.closest ? inp.closest('details.ti-cfg-details') : null;\n                if (det) t = det.getAttribute('data-tbl') || '';\n            }\n            return String(t || '');\n        }\n        function ensureState(tbl){\n            if (typeof window.ensureConfigTableState === 'function') return window.ensureConfigTableState(tbl);\n            window.configTableState = window.configTableState || {};\n            if (!window.configTableState[tbl]) window.configTableState[tbl] = {filters:{}, filterDrafts:{}, sortKey:'', sortDir:'asc'};\n            var st = window.configTableState[tbl];\n            st.filters = st.filters || {}; st.filterDrafts = st.filterDrafts || {};\n            return st;\n        }\n        function rowValue(tbl, idx, key, cellIndex, tr){\n            try {\n                var cells = tr ? tr.children : [];\n                var td = cells && cells[cellIndex + 1] ? cells[cellIndex + 1] : null; \/\/ +1 per colonna Riga rif.\n                if (td) {\n                    var controls = Array.prototype.slice.call(td.querySelectorAll('input,textarea,select')).filter(function(el){\n                        var typ = String(el.type || '').toLowerCase();\n                        return typ !== 'checkbox' && typ !== 'radio' && typ !== 'button' && typ !== 'submit' && typ !== 'hidden';\n                    });\n                    if (controls.length) {\n                        var vals = controls.map(function(el){\n                            if (el.tagName === 'SELECT' && el.multiple) return Array.prototype.slice.call(el.selectedOptions || []).map(function(o){ return o.textContent || o.value || ''; }).join(' ');\n                            if (el.tagName === 'SELECT') return (el.options && el.selectedIndex >= 0) ? (el.options[el.selectedIndex].textContent || el.value || '') : (el.value || '');\n                            return el.value || '';\n                        }).join(' ').trim();\n                        if (vals) return vals;\n                    }\n                }\n            } catch(e0) {}\n            try {\n                var data = window.currentDbData || window.currentDbViewData || null;\n                var rows = data && data.Tabelle ? data.Tabelle[tbl] : null;\n                if (rows && !Array.isArray(rows)) rows = Object.values(rows || {});\n                var row = rows && rows[idx] ? rows[idx] : null;\n                if (row && Object.prototype.hasOwnProperty.call(row, key)) return row[key];\n            } catch(e) {}\n            try {\n                var cells2 = tr ? tr.children : [];\n                var td2 = cells2 && cells2[cellIndex + 1] ? cells2[cellIndex + 1] : null;\n                if (td2) return td2.innerText || td2.textContent || '';\n            } catch(e2) {}\n            return '';\n        }\n        window.tiApplyConfigTableFilters431 = function(tbl){\n            tbl = String(tbl || '');\n            if (!tbl) return;\n            var det = document.querySelector('details.ti-cfg-details[data-tbl=\"' + escCss(tbl) + '\"]');\n            if (!det) return;\n            var table = det.querySelector('table.ti-cfg-table');\n            if (!table) return;\n            var st = ensureState(tbl);\n            var inputs = Array.prototype.slice.call(table.querySelectorAll('thead .ti-cfg-filter'));\n            var filters = [];\n            inputs.forEach(function(inp, i){\n                var key = String(inp.getAttribute('data-key') || '').trim();\n                var val = String(inp.value == null ? '' : inp.value).trim();\n                if (key) {\n                    st.filterDrafts[key] = val;\n                    st.filters[key] = val;\n                    if (val) filters.push({key:key, value:val, cellIndex:i});\n                }\n            });\n            var shown = 0, hidden = 0;\n            Array.prototype.slice.call(table.querySelectorAll('tbody tr')).forEach(function(tr){\n                var rawIdx = tr.getAttribute('data-row-idx');\n                var idx = parseInt(rawIdx, 10);\n                var ok = filters.every(function(f){\n                    return window.configFilterMatches(rowValue(tbl, isNaN(idx) ? -1 : idx, f.key, f.cellIndex, tr), f.value);\n                });\n                tr.style.display = ok ? '' : 'none';\n                if (ok) shown++; else hidden++;\n            });\n            var summary = det.querySelector('.ti-cfg-filter-summary-431');\n            if (!summary) {\n                summary = document.createElement('div');\n                summary.className = 'ti-cfg-filter-summary-431';\n                summary.style.cssText = 'font-size:11px;color:#93c5fd;padding:4px 10px;text-align:left;display:none;';\n                var cont = det.querySelector('.ti-cfg-table-container');\n                if (cont && cont.parentNode) cont.parentNode.insertBefore(summary, cont);\n            }\n            var hasFilter = filters.length > 0;\n            if (summary) {\n                summary.style.display = hasFilter ? 'block' : 'none';\n                if (hasFilter) summary.textContent = 'Filtro attivo: ' + shown + ' righe visibili' + (hidden ? ', ' + hidden + ' nascoste' : '') + '.';\n            }\n        };\n        function rememberFocus(tbl, key, value, inputEl){\n            try {\n                window.__configFilterFocus = {\n                    tbl:String(tbl||''), key:String(key||''), value:String(value||''),\n                    start: inputEl && typeof inputEl.selectionStart === 'number' ? inputEl.selectionStart : String(value||'').length,\n                    end: inputEl && typeof inputEl.selectionEnd === 'number' ? inputEl.selectionEnd : String(value||'').length,\n                    pending:true\n                };\n            } catch(e) {}\n        }\n        var filterTimers431 = {};\n        window.scheduleConfigTableFilter = function(tbl, key, value, inputEl){\n            tbl = String(tbl || getTableFromInput(inputEl) || '');\n            key = String(key || (inputEl ? inputEl.getAttribute('data-key') : '') || '');\n            value = String(value == null ? (inputEl ? inputEl.value : '') : value);\n            if (!tbl || !key) return;\n            var st = ensureState(tbl);\n            st.filterDrafts[key] = value;\n            st.filters[key] = value;\n            rememberFocus(tbl, key, value, inputEl);\n            window.tiApplyConfigTableFilters431(tbl);\n            var timerKey = encodeURIComponent(tbl) + '|' + encodeURIComponent(key);\n            if (filterTimers431[timerKey]) clearTimeout(filterTimers431[timerKey]);\n            filterTimers431[timerKey] = setTimeout(function(){\n                try {\n                    var active = document.activeElement;\n                    if (active && active.classList && active.classList.contains('ti-cfg-filter')) {\n                        window.tiApplyConfigTableFilters431(tbl);\n                    } else if (typeof window.renderConfig === 'function') {\n                        window.renderConfig();\n                    }\n                } catch(e) {}\n                delete filterTimers431[timerKey];\n            }, 350);\n        };\n        window.filterConfigTable = function(tbl, key, value, inputEl){ return window.scheduleConfigTableFilter(tbl, key, value, inputEl); };\n        document.addEventListener('input', function(ev){\n            var inp = ev.target && ev.target.closest ? ev.target.closest('.ti-cfg-filter') : null;\n            if (!inp) return;\n            var tbl = getTableFromInput(inp);\n            var key = inp.getAttribute('data-key') || '';\n            window.scheduleConfigTableFilter(tbl, key, inp.value, inp);\n        }, true);\n        document.addEventListener('change', function(ev){\n            var inp = ev.target && ev.target.closest ? ev.target.closest('.ti-cfg-filter') : null;\n            if (!inp) return;\n            var tbl = getTableFromInput(inp);\n            var key = inp.getAttribute('data-key') || '';\n            window.scheduleConfigTableFilter(tbl, key, inp.value, inp);\n        }, true);\n        var previousRenderConfig431 = typeof window.renderConfig === 'function' ? window.renderConfig : null;\n        if (previousRenderConfig431 && !previousRenderConfig431.__tiConfigFilterWrap431) {\n            window.renderConfig = function(){\n                var ret = previousRenderConfig431.apply(this, arguments);\n                setTimeout(function(){\n                    try {\n                        Object.keys(window.configTableState || {}).forEach(function(t){\n                            var st = window.configTableState[t] || {};\n                            var has = Object.keys(st.filters || {}).some(function(k){ return String(st.filters[k] || '').trim() !== ''; });\n                            if (has) window.tiApplyConfigTableFilters431(t);\n                        });\n                        if (typeof window.restoreConfigFilterFocus === 'function') window.restoreConfigFilterFocus();\n                    } catch(e) {}\n                }, 0);\n                return ret;\n            };\n            window.renderConfig.__tiConfigFilterWrap431 = true;\n        }\n\n        function isNoOrphanText(txt){\n            return \/nessun\\s+file\\s+non\\s+associato\\s+trovato|nessun\\s+file\\s+non\\s+associato\\s+da\\s+eliminare|pulizia\\s+globale\\s+completata\\.\\s+nessun\\s+file\\s+non\\s+associato\/i.test(String(txt || ''));\n        }\n        function closeLoader431(afterClose){\n            try { if (window.tiLoaderState && window.tiLoaderState.timer) clearInterval(window.tiLoaderState.timer); } catch(e) {}\n            try { if (window.tiLoaderState) window.tiLoaderState.active = false; } catch(e2) {}\n            try { if (window.orphanCleanupState) { window.orphanCleanupState.active = false; window.orphanCleanupState.stopRequested = false; window.orphanCleanupState.processed = window.orphanCleanupState.total || 0; } } catch(e3) {}\n            try { if (window.clearLongAIProcess) window.clearLongAIProcess(); } catch(e4) {}\n            try { if (window.closeModal) window.closeModal('ti-ai-loader-ov'); } catch(e5) {}\n            var ov = byId('ti-ai-loader-ov');\n            if (ov) { ov.style.display = 'none'; ov.classList && ov.classList.remove('show','active','ti-modal-active'); }\n            try { if (typeof window.restoreDefaultLoaderActions === 'function') window.restoreDefaultLoaderActions(); } catch(e6) {}\n            if (typeof afterClose === 'function') setTimeout(afterClose, 60);\n        }\n        window.tiFinishNoOrphanCleanup431 = function(message, opts){\n            opts = opts || {};\n            var msg = String(message || (opts.globalMode ? 'Pulizia globale completata. Nessun file non associato trovato.' : 'Pulizia completata. Nessun file non associato trovato.'));\n            try { if (window.orphanCleanupState) { window.orphanCleanupState.active = false; window.orphanCleanupState.stopRequested = false; window.orphanCleanupState.total = 0; window.orphanCleanupState.processed = 0; window.orphanCleanupState.deleted = 0; } } catch(e) {}\n            try { if (window.clearLongAIProcess) window.clearLongAIProcess(); } catch(e2) {}\n            var ov = byId('ti-ai-loader-ov');\n            var title = byId('ti-loader-title'), sub = byId('ti-loader-sub'), pct = byId('ti-loader-percent'), bar = byId('ti-loader-bar'), item = byId('ti-loader-current-item'), actions = byId('ti-loader-actions');\n            if (ov) {\n                if (title) title.innerText = opts.globalMode ? 'Pulizia globale file non associati' : 'Pulizia file non associati';\n                if (sub) sub.innerText = msg;\n                if (pct) pct.innerText = '100%';\n                if (bar) bar.style.width = '100%';\n                if (item) { item.style.display = 'none'; item.innerText = ''; }\n                if (actions) {\n                    actions.style.display = 'block';\n                    actions.innerHTML = '<button type=\"button\" id=\"ti-orphan-empty-close-431\" class=\"ti-btn\" style=\"display:block;margin:8px auto 0 auto;min-width:150px;background:#16a34a;color:#fff;font-weight:800;\">Chiudi<\/button>';\n                    var b = byId('ti-orphan-empty-close-431');\n                    if (b) b.onclick = function(){ closeLoader431(opts.afterClose); return false; };\n                }\n                try { if (window.showPluginModal) window.showPluginModal('ti-ai-loader-ov'); else ov.style.display = 'flex'; } catch(e3) { ov.style.display = 'flex'; }\n            } else if (typeof window.tiAlert === 'function') {\n                window.tiAlert(msg);\n            }\n        };\n        var prevHide431 = typeof window.hideProgressPopup === 'function' ? window.hideProgressPopup : null;\n        if (prevHide431 && !prevHide431.__tiNoOrphanClose431) {\n            window.hideProgressPopup = function(success, finalText, opts){\n                opts = opts || {};\n                var combined = String(finalText || '') + ' ' + String(opts.successMessage || '') + ' ' + String(opts.errorMessage || '');\n                if (success !== false && isNoOrphanText(combined)) {\n                    window.tiFinishNoOrphanCleanup431(finalText || opts.successMessage || 'Pulizia completata. Nessun file non associato trovato.', opts);\n                    return;\n                }\n                return prevHide431.apply(this, arguments);\n            };\n            window.hideProgressPopup.__tiNoOrphanClose431 = true;\n        }\n        document.addEventListener('click', function(ev){\n            var btn = ev.target && ev.target.closest ? ev.target.closest('#ti-orphan-empty-close-431') : null;\n            if (!btn) return;\n            ev.preventDefault(); ev.stopPropagation();\n            closeLoader431();\n        }, true);\n        setInterval(function(){\n            try {\n                var ov = byId('ti-ai-loader-ov');\n                if (!ov || !visible(ov)) return;\n                var txt = String(ov.innerText || ov.textContent || '');\n                if (isNoOrphanText(txt) && \/100\\s*%\/.test(txt) && !byId('ti-orphan-empty-close-431')) {\n                    window.tiFinishNoOrphanCleanup431(\/globale\/i.test(txt) ? 'Pulizia globale completata. Nessun file non associato trovato.' : 'Pulizia completata. Nessun file non associato trovato.', {globalMode:\/globale\/i.test(txt)});\n                }\n            } catch(e) {}\n        }, 900);\n    })();\n\n    setInterval(function(){\n        try {\n            if (isSaveActive()) isolateSaveLongProcess();\n            if (!window.__saveConfigInFlight && !window.__tiSaveConfigInFlight429) window.__tiSaveConfigInFlight430 = false;\n            var ov = byId('ti-ai-loader-ov');\n            var txt = ov ? String(ov.innerText || ov.textContent || '') : '';\n            if (visible(ov) && \/salvataggio configurazione|cancellazione definitiva configurazione\/i.test(txt) && !window.__saveConfigInFlight && !window.__tiSaveConfigInFlight429) {\n                if (window.closeModal) window.closeModal('ti-ai-loader-ov');\n                if (window.clearLongAIProcess) window.clearLongAIProcess();\n            }\n        } catch(e) {}\n    }, 1200);\n})();\n<\/script>\n\n\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"Pagina_base_vuota.php","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_kadence_starter_templates_imported_post":false,"_kad_post_transparent":"default","_kad_post_title":"default","_kad_post_layout":"default","_kad_post_sidebar_id":"","_kad_post_content_style":"default","_kad_post_vertical_padding":"default","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"class_list":["post-1165","page","type-page","status-publish","hentry"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tisoft.it\/en\/wp-json\/wp\/v2\/pages\/1165","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tisoft.it\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.tisoft.it\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.tisoft.it\/en\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tisoft.it\/en\/wp-json\/wp\/v2\/comments?post=1165"}],"version-history":[{"count":4,"href":"https:\/\/www.tisoft.it\/en\/wp-json\/wp\/v2\/pages\/1165\/revisions"}],"predecessor-version":[{"id":1202,"href":"https:\/\/www.tisoft.it\/en\/wp-json\/wp\/v2\/pages\/1165\/revisions\/1202"}],"wp:attachment":[{"href":"https:\/\/www.tisoft.it\/en\/wp-json\/wp\/v2\/media?parent=1165"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}