{"id":6505,"date":"2026-03-30T12:17:46","date_gmt":"2026-03-30T10:17:46","guid":{"rendered":"https:\/\/www.targetinformatica.it\/?p=6505"},"modified":"2026-03-30T12:18:23","modified_gmt":"2026-03-30T10:18:23","slug":"6505-2","status":"publish","type":"post","link":"https:\/\/www.targetinformatica.it\/en\/6505-2\/","title":{"rendered":""},"content":{"rendered":"    <style>\n        #ti-ai-outer { width: 100%; max-width: 980px; margin: 0 auto; padding: 12px; font-family: system-ui, sans-serif; color: #e5e7eb; box-sizing: border-box; overflow-x: hidden; }\n        #ti-ai-outer * { box-sizing: border-box; }\n        .ti-header { display: flex; align-items: center; justify-content: space-between; padding: 15px; min-height: 85px; border-radius: 14px; background: #111827; border: 1px solid rgba(255,255,255,0.08); margin-bottom: 10px; position: relative; }\n        .ti-hl { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0; }\n        .ti-left-stack { display:flex; flex-direction:column; align-items:flex-start; gap:6px; }\n        .ti-hr-right { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }\n        .ti-hr-bottom { display: flex; align-items: center; gap: 8px; }\n        .ti-lang-wrap { display: flex; gap: 8px; font-size: 11px; font-weight: bold; z-index: 10; }\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: 3px 7px; border-radius: 18px; border: 1px solid rgba(255,255,255,0.12); min-height: 24px; max-width: 240px; width: fit-content; }\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; }\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.4; white-space: pre-wrap; word-wrap: break-word; font-size: 15px !important; }\n        .ti-bubble p { font-size: 15px !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: 60px; resize: vertical; }\n        .ti-send-wrap { display: flex; flex-direction: column; gap: 6px; }\n        .ti-send { height: 38px; width: 90px; 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: 90px; border-radius: 8px; background: #374151; color: #aaa; font-weight: bold; cursor: pointer; border: 1px solid #4b5563; font-size: 14px; }\n        .ti-actions-row { margin-top: 10px; display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }\n        .ti-btn { padding: 8px 12px; border-radius: 8px; background: #222; color: #ccc; border: 1px solid #444; cursor: pointer; font-weight: 600; font-size: 13px; display: flex; align-items: center; justify-content: center; gap: 5px; }\n        .ti-btn-ok { background: #065f46; border-color: #059669; color: #fff; }\n        .ti-btn-x { background: #7f1d1d; border-color: #b91c1c; color: #fff; }\n        html.ti-no-scroll, body.ti-no-scroll { overflow: hidden !important; height: 100% !important; touch-action: none !important; }\n        \n        #ti-ai-outer { position: relative; z-index: 1; isolation: isolate; }\n        #ti-plugin-modal-root { position: fixed !important; inset: 0 !important; z-index: 2147483600 !important; pointer-events: none !important; }\n        #ti-plugin-modal-root > .ti-ov { pointer-events: auto !important; }\n        .ti-ov { position: fixed !important; inset: 0 !important; width: 100vw !important; height: 100vh !important; min-width: 100vw !important; min-height: 100vh !important; display: none; align-items: center !important; justify-content: center !important; background: rgba(0,0,0,0.85) !important; z-index: 2147483647 !important; isolation: isolate !important; pointer-events: auto !important; overflow: hidden !important; }\n        .ti-modal, .ti-conf-window { position: relative !important; pointer-events: auto !important; z-index: 2 !important; box-sizing: border-box !important; }\n        .ti-modal { width: 350px; max-width: min(96vw, 900px) !important; background: #1f2937; border-radius: 16px; padding: 25px; border: 1px solid #374151; text-align: center; display: flex; flex-direction: column; max-height: 92vh !important; overflow-y: auto !important; -webkit-overflow-scrolling: touch !important; }\n        .ti-conf-window { z-index: 2 !important; width: auto !important; max-width: 1000px !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: 5vh !important; bottom: 5vh !important; left: 2vw !important; right: 2vw !important; max-width: 1000px !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        \n        .ti-in { width: 100%; padding: 10px; margin: 10px 0; background: #111827; border: 1px solid #4b5563; color: #fff; border-radius: 8px; }\n        #ti-filter-ditte { width: 35px; 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-table-container.ti-sync-hscroll-wrap { overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch; }\n        .ti-cfg-top-scroll, .ti-cfg-bottom-scroll, .ti-order-top-scroll, .ti-order-bottom-scroll { scrollbar-gutter: stable; }\n        .ti-rel-editor { display:flex; flex-direction:column; gap:6px; min-width:240px; }\n        .ti-rel-open-btn { display:inline-flex; align-items:center; justify-content:center; gap:6px; min-height:32px; padding:6px 10px; border-radius:8px; border:1px solid #475569; background:#1f2937; color:#fff; cursor:pointer; font-size:12px; font-weight:600; }\n        .ti-rel-open-btn:hover { background:#2563eb; border-color:#2563eb; }\n        .ti-rel-open-btn[disabled] { opacity:.55; cursor:not-allowed; }\n        .ti-rel-summary { font-size:11px; color:#cbd5e1; line-height:1.35; white-space:normal; word-break:break-word; background:#0f172a; border:1px solid #334155; border-radius:8px; padding:6px 8px; }\n        .ti-rel-popup-toolbar { display:grid; grid-template-columns:minmax(180px,1fr) auto auto; gap:8px; align-items:center; margin-bottom:10px; }\n        .ti-rel-popup-search { width:100%; min-width:0; }\n        .ti-rel-popup-list { max-height:320px; overflow:auto; background:#000; border:1px solid #374151; border-radius:10px; padding:8px; }\n        .ti-rel-popup-item { display:flex; align-items:flex-start; gap:8px; padding:7px 8px; border-radius:8px; cursor:pointer; color:#e5e7eb; }\n        .ti-rel-popup-item:hover { background:#111827; }\n        .ti-rel-popup-item input { margin-top:2px; }\n        .ti-rel-popup-free { width:100%; min-height:88px; resize:vertical; margin-top:8px; }\n        .ti-rel-popup-extrafilters { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin-top:8px; margin-bottom:8px; }\n        .ti-rel-popup-meta { font-size:11px; color:#94a3b8; line-height:1.35; margin-top:2px; }\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-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        @media (max-width: 768px) { .ti-order-modal-grid { grid-template-columns:1fr; } }\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 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-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; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); display:none; align-items:center; justify-content:center; z-index:9999; }\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-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 { border:1px solid #444; border-top:none; border-radius:0 0 8px 8px; background:#111827; padding:10px; }\n        .ti-order-footer-grid { display:grid; grid-template-columns:1fr auto auto; gap:12px; align-items:center; }\n        .ti-order-footer-total { color:#e2e8f0; font-size:13px; text-align:right; }\n        .ti-order-footer-total b { color:#38bdf8; }\n        .ti-order-footer-btn { background:#10b981; color:#fff; width:100%; font-weight:bold; }\n\n        @media (max-width: 768px) { \n            .ti-header { flex-direction: column; align-items: flex-start; padding: 12px; } \n            .ti-hr-right { width: 100%; align-items: flex-start; margin-top: 10px; } \n            .ti-hr-bottom { flex-wrap: wrap; width: 100%; justify-content: space-between; } \n            #ti-ditta { max-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-height: 88vh; overflow-y: auto; }\n            .ti-order-footer-grid { grid-template-columns: 1fr; }\n            .ti-order-footer-total { text-align:left; }\n            .ti-order-toolbar-right { width:100%; }\n            .ti-order-hint { display:block; }\n        }\n\n        @media (max-width: 640px) {\n            .ti-header { 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    <\/style>\n\n    <div id=\"ti-ai-outer\" class=\"ti-plugin-scroll-lock\">\n        <div id=\"ti-ai-loader-ov\" class=\"ti-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-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()\">Chiudi<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-alert-ov\" class=\"ti-ov\" style=\"z-index: 999999 !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;\">\n                    <button type=\"button\" id=\"ti-alert-yes\" class=\"ti-btn\" style=\"background:#10b981; color:#fff; display:none; flex:1;\">SI<\/button>\n                    <button type=\"button\" id=\"ti-alert-no\" class=\"ti-btn\" style=\"background:#4b5563; color:#fff; flex:1;\">OK<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div class=\"ti-header\">\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 id=\"ti-company-sub\" style=\"display:none;\"><\/div>\n                 <\/div>\n            <\/div>\n            \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 class=\"ti-hr-bottom\">\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                     <select id=\"ti-ditta\" style=\"padding:6px;border-radius:8px;background:#000;color:#fff;border:1px solid #444;max-width:200px;\"><\/select>\n                     \n                     <button type=\"button\" id=\"ti-restart-btn\" class=\"ti-btn\" style=\"background:#b91c1c; color:#fff; display:flex;\" onclick=\"window.doRestart()\">\ud83d\udd04 <span class=\"txt-riavvia\">Riavvia<\/span><\/button>\n                     <button type=\"button\" id=\"ti-login-btn\" class=\"ti-btn\" onclick=\"window.openModal('ti-login-ov')\">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\"><\/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        \n        <div id=\"ti-chat-box\" class=\"ti-chat-box ti-scrollable-plugin\"><\/div>\n        \n        <div class=\"ti-input-wrap\">\n            <div class=\"ti-input-main\">\n                <textarea id=\"ti-msg\" class=\"ti-msg\" rows=\"1\" 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-help-btn\" class=\"ti-help\">?<\/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\">Copia<\/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-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-ok\" class=\"ti-btn ti-btn-ok\">OK<\/button>\n                <button type=\"button\" id=\"ti-x\" class=\"ti-btn ti-btn-x\">X<\/button>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-conf-ov\" class=\"ti-ov ti-closable-ov\">\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\">\n                    <button type=\"button\" id=\"lbl-conf-save\" class=\"ti-btn\" style=\"flex:1; background:#10b981 !important; color:#fff !important; cursor:pointer !important; font-size:14px !important; border:none !important;\" onclick=\"window.saveConfig()\">Salva Modifiche<\/button>\n                    <button type=\"button\" class=\"ti-btn btn-close\" style=\"flex:1; background:#b91c1c !important; color:#fff !important; cursor:pointer !important; font-size:14px !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 id=\"ti-conf-import-preview-title\" style=\"color:#fff;margin-top:0;border-bottom:1px solid #444;padding-bottom:10px;\">Anteprima modifiche import AI<\/h3>\n                <div id=\"ti-conf-import-preview-subtitle\" 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-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:65vh;overflow:auto;\"><\/div>\n                <div style=\"display:flex;gap:8px;flex-wrap:wrap;margin-top:12px;\">\n                    <button type=\"button\" id=\"ti-conf-import-preview-confirm\" class=\"ti-btn ti-btn-ok\" onclick=\"window.confirmConfigImportPreview()\">OK Conferma e salva<\/button>\n                    <button type=\"button\" id=\"ti-conf-import-preview-cancel\" class=\"ti-btn ti-btn-x\" onclick=\"window.cancelConfigImportPreview()\">X Annulla import<\/button>\n                    <button type=\"button\" id=\"ti-conf-import-preview-close\" 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\">\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-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                <form id=\"ti-login-form\" autocomplete=\"on\" onsubmit=\"event.preventDefault(); window.doLogin();\">\n                    <input id=\"l-user\" name=\"username\" class=\"ti-in\" placeholder=\"Username\" autocomplete=\"username\" 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\" autocapitalize=\"none\" autocorrect=\"off\" spellcheck=\"false\">\n                    <button type=\"submit\" id=\"l-do\" class=\"ti-btn\" style=\"width:100%; background:#3b82f6; margin-top:10px;\">Entra<\/button>\n                <\/form>\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                <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                    <p style=\"font-size:11px;color:#10b981;text-align:center;\">Verr\u00e0 creato il database e farai l'accesso automatico.<\/p>\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\" 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><\/div>\n                        <div><label>Username (Accesso)*<\/label><input id=\"p-user\" class=\"ti-in\" required><\/div>\n                        \n                        <div><label id=\"lbl-p-pass\">Password*<\/label><input id=\"p-pass\" type=\"password\" class=\"ti-in\" placeholder=\"***\"><\/div>\n                        <div><label>Email<\/label><input id=\"p-email\" type=\"email\" class=\"ti-in\"><\/div>\n                        \n                        <div><label>Ragione Sociale<\/label><input id=\"p-ragione_sociale\" class=\"ti-in\"><\/div>\n                        <div><label>Indirizzo<\/label><input id=\"p-ind\" class=\"ti-in\"><\/div>\n                        \n                        <div><label>P.IVA \/ VAT<\/label><input id=\"p-piva_vat\" class=\"ti-in\"><\/div>\n                        <div><label>Codice Fiscale<\/label><input id=\"p-cod_fisc\" class=\"ti-in\"><\/div>\n                        \n                        <div><label>Codice ADE \/ SDI<\/label><input id=\"p-ade\" class=\"ti-in\"><\/div>\n                        <div><label>Telefono<\/label><input id=\"p-tel\" class=\"ti-in\"><\/div>\n                        \n                        <div><label>Sesso<\/label><input id=\"p-sesso\" class=\"ti-in\" placeholder=\"M\/F\"><\/div>\n                        <div><label>Et\u00e0<\/label><input id=\"p-eta\" type=\"number\" class=\"ti-in\" value=\"0\" min=\"0\"><\/div>\n\n                        <div><label>Dato 1<\/label><input id=\"p-dato_1\" class=\"ti-in\"><\/div>\n                        <div><label>Dato 2<\/label><input id=\"p-dato_2\" class=\"ti-in\"><\/div>\n                        \n                        <div style=\"grid-column: span 2;\"><label>Dato 3<\/label><input id=\"p-dato_3\" class=\"ti-in\"><\/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\"><\/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\">\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                <textarea id=\"global-action-prompt\" class=\"ti-in\" rows=\"3\" placeholder=\"Es: Elimina la parola Terni dai nomi delle ditte...\"><\/textarea>\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                <div style=\"display:flex;gap:8px;flex-wrap:wrap;\">\n                    <button type=\"button\" class=\"ti-btn\" style=\"flex:1;background:#a16207;color:#fff;\" onclick=\"window.openSharedSetupModal()\">Setup storage centralizzato<\/button>\n                    <button type=\"button\" class=\"ti-btn btn-close\" style=\"flex:1;background:#4b5563;\" onclick=\"window.closeModal('ti-global-action-ov')\">Chiudi<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div id=\"ti-up-choice-ov\" class=\"ti-ov ti-closable-ov\">\n            <div class=\"ti-modal\">\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\">\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                <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:10px;\">Aiuto Colonna<\/h3>\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                <button type=\"button\" onclick=\"window.closeModal('ti-col-help-ov')\" class=\"ti-btn btn-close\" style=\"width:100%; margin-top:15px; background:#4b5563;\">Chiudi<\/button>\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\" style=\"width:100%; max-width:600px;\"><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            <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 style=\"color:#fff;margin-top:0;margin-bottom:14px;\">Dettaglio articolo<\/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-note\" style=\"font-size:12px;color:#9ca3af;margin-top:8px;white-space:pre-wrap;\"><\/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    \/\/ Inizializzazione variabili globali sicura\n    window.gE = function(id) { return document.getElementById(id); };\n    const gE = window.gE;\n\n    window.tiUrl = window.location.href.split('?')[0]; \n    window.tiAjaxUrl = \"https:\/\/www.targetinformatica.it\/wp-admin\/admin-ajax.php\";\n    window.tiForced = \"\"; \n    window.tiUser = \"\"; \n    window.tiRole = \"\"; \n    window.tiUserFoto = \"\"; window.tiUserFotoDb = \"\"; window.tiSessionDb = \"\"; \n    window.tiUserDisplay = \"\"; \n    window.tiVersion = \"14-04-2026 12:40\";\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\",\"default_base_path\":\"\\\/home\\\/lqlo5s240ldu\\\/public_html\\\/wp-content\\\/uploads\\\/AI-data\\\/\",\"default_base_url\":\"https:\\\/\\\/www.targetinformatica.it\\\/wp-content\\\/uploads\\\/AI-data\",\"target_url_example\":\"https:\\\/\\\/www.targetinformatica.it\\\/wp-content\\\/uploads\\\/AI-data\"};\n\n    window.currentDbData = 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.targetUploadContext = null; window.currentGenFilename = null; window.configWasSaved = false;\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\", file: \"File\", products: \"Prodotti\", services: \"Servizi\", 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\", file: \"File\", products: \"Products\", services: \"Services\", kbd: \"Keyboard\", mic: \"Mic\", cam: \"Cam\", login: \"Login\" } \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 byTable = {\n            'utenti': ['Username','Utente','Nome','Titolo','Descrizione'],\n            'operatori': ['Utente','Username','Nome','Titolo','Descrizione'],\n            'prodotti': ['Prodotto','Descrizione','Nome','Titolo'],\n            'servizi': ['Servizio','Descrizione','Nome','Titolo'],\n            'siti': ['Sito','Descrizione','Nome','Titolo'],\n            'dispositivi': ['Dispositivo','Nome','Titolo','Descrizione']\n        };\n        const picks = byTable[tbl] || ['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(function(x){ return 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\n        let dbRaw = dbName ? String(dbName).replace('.json', '') : '';\n        const tableName = String(tName || '').trim();\n        const recLabel = String(recordLabel || '').trim();\n\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\n        const isHttp = \/^https?:\\\/\\\/\/i.test(val);\n        const isUploadsHttp = isHttp && \/\\\/wp-content\\\/uploads\\\/\/i.test(val);\n        const isUploadsRel = \/^\\\/?wp-content\\\/uploads\\\/\/i.test(val) || \/^uploads\\\/\/i.test(val);\n        const isAiDataPath = \/AI-data\\\/\/i.test(val);\n\n        if (isUploadsHttp || isUploadsRel || isAiDataPath) {\n            const pts = val.split('\/');\n            const fileOnly = String(pts[pts.length - 1] || '').trim();\n            if (!fileOnly) return '';\n            return window.tiUrl\n                + '?ti_action=ti_ai_get_image'\n                + '&db=' + encodeURIComponent(dbRaw)\n                + '&tbl=' + encodeURIComponent(tableName)\n                + '&file=' + encodeURIComponent(fileOnly)\n                + (recLabel ? '&record=' + encodeURIComponent(recLabel) : '');\n        }\n\n        if (\/^\\\/\/.test(val)) return window.location.origin + val;\n        if (isHttp) return val;\n\n        if (val.indexOf('\/') !== -1) {\n            const pts = val.split('\/');\n            val = pts[pts.length - 1];\n        }\n\n        return window.tiUrl\n            + '?ti_action=ti_ai_get_image'\n            + '&db=' + encodeURIComponent(dbRaw)\n            + '&tbl=' + encodeURIComponent(tableName)\n            + '&file=' + encodeURIComponent(val)\n            + (recLabel ? '&record=' + encodeURIComponent(recLabel) : '');\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        if (!opts.signal && window.tiLongProcessState && window.tiLongProcessState.controller) {\n            opts.signal = window.tiLongProcessState.controller.signal;\n        }\n        return 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        }).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    };\n\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 fd;\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        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        window.hidePluginTopModal('ti-ai-loader-ov');\n        window.tiAlert('Processo AI interrotto. Per completarlo dovrai ripetere l operazione.');\n    };\n\n    window.tiModalBaseZ = 2147483600;\n    window.tiModalStack = 0;\n    window.ensurePluginModalRoot = function() {\n        let root = document.getElementById('ti-plugin-modal-root');\n        if (!root) { root = document.createElement('div'); root.id = 'ti-plugin-modal-root'; document.body.appendChild(root); }\n        return root;\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.setProperty('position', 'fixed', 'important');\n            el.style.setProperty('inset', '0', 'important');\n            el.style.setProperty('width', '100vw', 'important');\n            el.style.setProperty('height', '100vh', 'important');\n            el.style.setProperty('z-index', String(window.tiModalBaseZ), 'important');\n            el.style.setProperty('align-items', 'center', 'important');\n            el.style.setProperty('justify-content', 'center', 'important');\n        });\n    };\n    window.bringPluginModalToFront = function(el) {\n        if (!el) return;\n        const root = window.ensurePluginModalRoot();\n        if (el.parentNode !== root) root.appendChild(el);\n        window.tiModalStack = (window.tiModalStack || 0) + 1;\n        el.style.setProperty('position', 'fixed', 'important');\n        el.style.setProperty('inset', '0', 'important');\n        el.style.setProperty('width', '100vw', 'important');\n        el.style.setProperty('height', '100vh', 'important');\n        el.style.setProperty('z-index', String(window.tiModalBaseZ + window.tiModalStack), 'important');\n    };\n    window.updatePluginScrollLock = function() {\n        const hasOpen = Array.from(document.querySelectorAll('.ti-ov')).some(function(el){\n            return window.getComputedStyle(el).display !== 'none';\n        });\n        document.documentElement.classList.toggle('ti-no-scroll', hasOpen);\n        document.body.classList.toggle('ti-no-scroll', hasOpen);\n    };\n    window.showPluginTopModal = function(idOrEl) {\n        try {\n            window.portalizePluginModals();\n            const el = (typeof idOrEl === 'string') ? gE(idOrEl) : idOrEl;\n            if (!el) return null;\n            window.bringPluginModalToFront(el);\n            el.style.setProperty('display', 'flex');\n            window.updatePluginScrollLock();\n            return el;\n        } catch(e) { return null; }\n    };\n    window.hidePluginTopModal = function(idOrEl) {\n        try {\n            const el = (typeof idOrEl === 'string') ? gE(idOrEl) : idOrEl;\n            if (!el) return;\n            el.style.setProperty('display', 'none');\n            window.updatePluginScrollLock();\n        } catch(e) {}\n    };\n    window.openModal = function(id) { return window.showPluginTopModal(id); };\n    window.closeModal = function(id) { return window.hidePluginTopModal(id); };\n    try { window.portalizePluginModals(); } 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        window.closeModal('ti-conf-ov');\n        if (window.configWasSaved) {\n            window.configWasSaved = false;\n            const fd = window.buildAjaxFormData('ti_ai_reset_flow');\n            fetch(window.tiAjaxUrl, {method:'POST', body:fd}).then(() => location.reload());\n        }\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');\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 (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.showPluginTopModal('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) {\n        const ov = gE('ti-ai-loader-ov'); if(!ov) return;\n        const titleEl = gE('ti-loader-title'); const subEl = gE('ti-loader-sub');\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 (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.showPluginTopModal('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 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        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.hidePluginTopModal('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.hidePluginTopModal('ti-ai-loader-ov');\n                if (opts.notifyUser !== false) { window.tiAlert(errorMsg); }\n            }, 250);\n        }\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            if (!window.currentDbData || !window.currentDbData.Tabelle) return null;\n            const tables = window.currentDbData.Tabelle || {};\n            const cfgKey = Object.keys(tables).find(function(k){ return String(k || '').toLowerCase() === 'config'; });\n            if (!cfgKey || !Array.isArray(tables[cfgKey]) || !tables[cfgKey][0] || typeof tables[cfgKey][0] !== 'object') return null;\n            return tables[cfgKey][0];\n        } catch(e) { return null; }\n    };\n    window.getOrderConfigFieldValue = function(fieldName) {\n        const row = window.getOrderConfigRow();\n        if (!row) return '';\n        const realKey = Object.keys(row).find(function(k){ return String(k || '').toLowerCase() === String(fieldName || '').toLowerCase(); });\n        return realKey ? row[realKey] : '';\n    };\n    window.getOrderViewAllowedModes = function() {\n        const map = [\n            { field:'Tab_tab', mode:'table' },\n            { field:'Tab_sma', mode:'small' },\n            { field:'Tab_med', mode:'medium' },\n            { field:'Tab_lar', mode:'large' }\n        ];\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();\n        if (!wrap) return;\n        if (!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(); fd.append('ti_action', 'ti_ai_save_ui_pref'); fd.append('pref_key', 'order_view_mode'); fd.append('pref_value', mode); 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\n        let s = String(val == null ? '' : val).trim();\n        if (!s) return 0;\n        s = s.replace(\/\\s+\/g, '').replace(\/[^0-9,.-]\/g, '');\n        if (!s) return 0;\n        const hasComma = s.indexOf(',') !== -1;\n        const hasDot = s.indexOf('.') !== -1;\n        if (hasComma && hasDot) {\n            s = s.replace(\/\\.\/g, '').replace(',', '.');\n        } else if (hasComma) {\n            s = s.replace(',', '.');\n        }\n        const n = parseFloat(s);\n        return Number.isFinite(n) ? n : 0;\n    };\n\n    window.formatQtyInput = function(val) {\n        return window.fmtNum(window.parseQtyInput(val));\n    };\n    window.stepQtyInput = function(btn, delta) {\n        try {\n            const wrap = btn && btn.closest ? btn.closest('.ti-qty-stepper') : null;\n            const inp = wrap ? wrap.querySelector('input') : null;\n            if (!inp) return;\n            let qty = window.parseQtyInput(inp.value);\n            qty = Math.max(0, qty + (parseFloat(delta) || 0));\n            inp.value = window.formatQtyInput(qty);\n            if (inp.id === 'ti-order-item-qty') {\n                const st = window.orderItemPopupState || {};\n                const tot = gE('ti-order-item-total');\n                if (tot) tot.textContent = '\u20ac ' + window.fmtNum((st.price || 0) * qty);\n            }\n            if (inp.classList.contains('ti-q')) window.syncOrderQty(inp);\n        } catch(e) {}\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    };\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 (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.addTableRow = 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        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        window.configForceTop = true;\n        window.renderConfig();\n    };\n\n    window.duplicateTableRow = function(tName, idx) {\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        rows.splice(idx + 1, 0, clone);\n        window.renderConfig();\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.delTableRow = function(tName, idx) {\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\n        if (rows.length <= 1) {\n            window.tiConfirm('Esiste una sola riga: verranno svuotati tutti i campi della riga.', function() {\n                window.clearSingleRemainingRow(tName, idx);\n                window.renderConfig();\n            });\n            return;\n        }\n\n        window.tiConfirm('Confermi l\\'eliminazione della riga?', function() {\n            rows.splice(idx, 1);\n            window.currentDbData.Tabelle[tName] = rows;\n            if (!window.modifiedFields) window.modifiedFields = new Set();\n            rows.forEach(function(rowObj, rowIdx){\n                Object.keys(rowObj || {}).forEach(function(k){\n                    window.modifiedFields.add(`${tName}-${rowIdx}-${k}`);\n                });\n            });\n            window.renderConfig();\n        });\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        return 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) {\n            e.preventDefault();\n            return;\n        }\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 isModalOpen = Array.from(document.querySelectorAll('.ti-ov')).find(function(el){ return window.getComputedStyle(el).display !== 'none'; }); \n        if (isModalOpen) { \n            const scrollable = e.target.closest('.ti-conf-body') || e.target.closest('.ti-table-wrap') || e.target.closest('.ti-chat-box') || e.target.closest('.ti-modal') || e.target.closest('#help-bot-reply') || e.target.closest('#col-help-content') || e.target.closest('#ateco-res') || e.target.closest('#global-action-res') || e.target.closest('#fm-grid'); \n            if (!scrollable) e.preventDefault(); \n        } \n    }, { passive: false });\n\n    window.tiAlert = function(msg) {\n        gE('ti-alert-msg').innerHTML = msg;\n        gE('ti-alert-yes').style.display = 'none';\n        gE('ti-alert-no').innerText = 'OK';\n        gE('ti-alert-no').onclick = function() { window.hidePluginTopModal('ti-alert-ov'); };\n        window.showPluginTopModal('ti-alert-ov');\n    };\n\n    window.tiConfirm = function(msg, callback) {\n        gE('ti-alert-msg').innerHTML = msg;\n        gE('ti-alert-yes').style.display = 'block';\n        gE('ti-alert-yes').innerText = 'SI';\n        gE('ti-alert-no').innerText = 'ANNULLA';\n        gE('ti-alert-yes').onclick = function() { window.hidePluginTopModal('ti-alert-ov'); if (typeof callback === 'function') callback(); };\n        gE('ti-alert-no').onclick = function() { window.hidePluginTopModal('ti-alert-ov'); };\n        window.showPluginTopModal('ti-alert-ov');\n    };\n\n\n    window.tiConfirmYesNoAction = function(msg, yesCb, noCb, yesLabel = 'OK', noLabel = 'ANNULLA') {\n        gE('ti-alert-msg').innerHTML = msg;\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 = function() { window.hidePluginTopModal('ti-alert-ov'); if (typeof yesCb === 'function') yesCb(); };\n        gE('ti-alert-no').onclick = function() { window.hidePluginTopModal('ti-alert-ov'); if (typeof noCb === 'function') noCb(); };\n        window.showPluginTopModal('ti-alert-ov');\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        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}\">` : `<div class=\"file-icon\">\ud83d\udcc4<\/div>`;\n            html += `<div class=\"ti-file-item\">\n                        ${preview}\n                        <div class=\"ti-file-name\" title=\"${f}\">${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        gE('fm-grid').innerHTML = html;\n        window.openModal('ti-file-manager-ov');\n    };\n\n    window.removeFileFromManager = function(fileIndex) {\n        window.tiConfirm(\"Eliminare questo file dal record?\", function() {\n            let ctx = window.targetUploadContext;\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            files.splice(fileIndex, 1);\n            let newVal = files.join(', ');\n            \n            window.currentDbData.Tabelle[ctx.tbl][ctx.idx][ctx.key] = newVal;\n            if(!window.modifiedFields) window.modifiedFields = new Set();\n            window.modifiedFields.add(`${ctx.tbl}-${ctx.idx}-${ctx.key}`);\n            \n            window.renderConfig();\n            window.openFileManager(ctx.tbl, ctx.idx, ctx.key);\n        });\n    };\n\n    window.chooseLocalManager = function() {\n        window.closeModal('ti-file-manager-ov');\n        const fileIn = gE('ti-file-in'); fileIn.dataset.target = 'manager'; fileIn.value = ''; setTimeout(() => fileIn.click(), 100);\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        window.closeModal('ti-file-manager-ov');\n        let row = null;\n        if (window.targetUploadContext && window.targetUploadContext.tbl) {\n            row = window.currentDbData.Tabelle[window.targetUploadContext.tbl][window.targetUploadContext.idx] || null;\n        }\n        window.populateAIGenPrompt(row);\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.doLogin = function() {\n        const userEl = gE('l-user');\n        const passEl = gE('l-pass');\n        const loginBtn = gE('l-do');\n        const ditta = gE('ti-ditta');\n        const u = userEl ? userEl.value.trim() : '';\n        const dbVal = (ditta && ditta.value && ditta.value !== 'NEW_DB') ? ditta.value : (window.tiForced || localStorage.getItem('ti_saved_db') || '');\n        if (!u) { window.tiAlert('Inserisci username'); if (userEl) userEl.focus(); return; }\n        if (u !== 'SSGlobalAdmin' && !dbVal) { window.tiAlert('Seleziona una ditta'); return; }\n        if (loginBtn) loginBtn.disabled = true;\n        const fd = new FormData();\n        fd.append('ti_action','ti_ai_login');\n        fd.append('user',u);\n        fd.append('pass', passEl ? passEl.value : '');\n        fd.append('db', dbVal);\n        window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d=>{\n            if(d.success) { location.reload(); return; }\n            window.tiAlert('Errore: ' + (((d||{}).data||{}).message || 'Login non riuscito'));\n        }).catch(()=>{\n            window.tiAlert('Errore di connessione durante il login.');\n        }).finally(()=>{\n            if (loginBtn) loginBtn.disabled = false;\n        });\n    };\n\n    window.doLogout = function(silentReset = false) {\n        const fd = new FormData(); fd.append('ti_action','ti_ai_logout'); \n        fetch(window.tiUrl,{method:'POST',body:fd}).then(()=> {\n            if(silentReset) { const fdR = new FormData(); fdR.append('ti_action','ti_ai_reset_flow'); fetch(window.tiUrl,{method:'POST',body:fdR}).then(()=>location.reload()); } \n            else location.reload();\n        });\n    };\n\n    window.doRestart = function() { window.tiConfirm(dict[window.currLang].restartConfirm, function(){ window.doLogout(true); }); };\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            gE('p-utente').value = ''; gE('p-user').value = ''; gE('p-user').readOnly = false; gE('p-ragione_sociale').value = ''; gE('p-email').value = ''; gE('p-tel').value = ''; gE('p-ind').value = ''; gE('p-piva_vat').value = ''; gE('p-cod_fisc').value=''; gE('p-ade').value=''; gE('p-sesso').value=''; gE('p-eta').value='0'; gE('p-dato_1').value=''; gE('p-dato_2').value=''; gE('p-dato_3').value=''; gE('p-nota').value='';\n            gE('p-foto').value = ''; gE('p-foto-preview').style.display = 'none';\n            window.openModal('ti-profile-ov');\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            });\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 = new FormData(); fd.append('ti_action', 'ti_ai_create_db'); fd.append('name', dbName); fd.append('username', uName); fd.append('password', uPass); fd.append('nome', uName); fd.append('email', uEmail);\n        window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d=>{ if(d.success) { localStorage.setItem('ti_saved_db', d.data.db); location.href = window.tiUrl; } else window.tiAlert(d.data.message); });\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=>{ if(d.success) { window.tiAlert(d.data.msg); location.reload(); } else { if (submitBtn) { submitBtn.disabled = false; submitBtn.innerText = 'Salva'; } window.tiAlert(d.data.message); } }).catch(e=>{ if (submitBtn) { submitBtn.disabled = false; submitBtn.innerText = 'Salva'; } window.tiAlert('Errore di rete durante il salvataggio profilo.'); });\n    };\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.askColumnHelp = function(tbl, col) {\n        const dbVal = gE('ti-ditta').value || 'NEW_DB'; gE('col-help-title').innerText = `Info colonna: ${col}`;\n        const contentBox = gE('col-help-content'); contentBox.innerHTML = `<i>\u23f3 Consulto il manuale per la colonna <b>${col}<\/b>...<\/i>`; window.openModal('ti-col-help-ov');\n        const fd = new FormData(); fd.append('ti_action', 'ti_ai_chat_start'); fd.append('db', dbVal); const cleanCol = col.replace(\/\\\\'\/g, \"'\");\n        fd.append('text', `AI_MANUAL_HELP: Spiega nel dettaglio a cosa serve la colonna \"${cleanCol}\" nella tabella \"${tbl}\".`);\n        fetch(window.tiUrl, {method: 'POST', body: fd}).then(r => r.json()).then(res => { if(res.success) contentBox.innerHTML = res.data.reply.replace(\/\\n\/g, '<br>'); else contentBox.innerHTML = \"<span style='color:#ef4444;'>Errore durante la ricerca nel manuale.<\/span>\"; }).catch(e => { contentBox.innerHTML = \"<span style='color:#ef4444;'>Errore di rete.<\/span>\"; });\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    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 btn = gE('global-action-btn'); const resBox = gE('global-action-res');\n        btn.innerText = \"\u23f3 Esecuzione in corso su tutti i DB...\"; btn.disabled = true; resBox.style.display = 'block'; resBox.innerHTML = \"Elaborazione AI... non chiudere la pagina.\";\n        window.startGlobalActionProgress('Avvio modifica globale database...');\n        const fd = new FormData(); fd.append('ti_action', 'ti_ai_global_action'); fd.append('prompt', prompt);\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                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.loadActivityReportPrefs = function() {\n        try {\n            const raw = localStorage.getItem(window.activityReportStorageKey());\n            if (!raw) return {colFlags:{}, rowFlags:{}, metricFlags: window.getDefaultActivityMetricFlags(), groupField: 'Username'};\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') };\n        } catch(e) { return {colFlags:{}, rowFlags:{}, metricFlags: window.getDefaultActivityMetricFlags(), groupField: 'Username'}; }\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            }));\n        } catch(e) {}\n    };\n    window.activityReportState = { sortCol: 'DataOra', sortDir: 'desc', filters: {}, view: 'table', colFlags: {}, rowFlags: {}, metricFlags: window.getDefaultActivityMetricFlags(), groupField: 'Username' };\n    window.activityReportCols = ['Username','Descrizione','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.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        return rows.map((r, idx) => {\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            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(r.Descrizione || r.Dettaglio || r.Note || '') + '|' + idx;\n            return {\n                Username: r.Username || r.Utente || r.user || '',\n                Descrizione: r.Descrizione || r.Dettaglio || r.Note || '',\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                __dataora: dataora,\n                __rowKey: rowKey\n            };\n        });\n    };\n    window.getActivityIncludedRows = function() {\n        const rowFlags = (window.activityReportState && window.activityReportState.rowFlags) || {};\n        return window.getActivityRowsNormalized().filter(r => !rowFlags[r.__rowKey]);\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        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.drawPie3D = function(canvasId, rows) {\n        const cv = gE(canvasId); if(!cv) return; const ctx = cv.getContext('2d');\n        ctx.clearRect(0,0,cv.width,cv.height);\n        const selectedMetrics = window.getActivitySelectedMetrics();\n        const groupField = window.getActivitySelectedGroupField();\n        const groups = {};\n        rows.forEach(r => {\n            const label = String(r[groupField] || 'N\/D').trim() || 'N\/D';\n            const value = selectedMetrics.reduce((s, metric) => s + (window.parseReportNum(r[metric], 0) || 0), 0);\n            groups[label] = (groups[label] || 0) + value;\n        });\n        let entries = Object.entries(groups).filter(e => e[1] > 0).sort((a,b) => b[1]-a[1]).slice(0, 12);\n        if(!entries.length){ ctx.fillStyle='#fff'; ctx.font='14px Arial'; ctx.fillText('Nessun dato',20,20); return; }\n        const total = entries.reduce((a,b)=>a+b[1],0);\n        const colors=['#38bdf8','#22c55e','#f59e0b','#ef4444','#a855f7','#14b8a6','#f97316','#06b6d4','#84cc16','#fb7185','#f97316','#60a5fa'];\n        let start=-Math.PI\/2; const cx=220, cy=140, rad=95, depth=18;\n        entries.forEach(([label,val],i) => {\n            const ang=(val\/total)*Math.PI*2;\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            const mid=start+ang\/2; ctx.fillStyle='#fff'; ctx.font='12px Arial'; ctx.fillText(label+' ('+window.fmtNum(val)+')', cx+Math.cos(mid)*(rad+24)-42, cy+Math.sin(mid)*(rad+24));\n            start+=ang;\n        });\n        ctx.fillStyle = '#cbd5e1'; ctx.font = '12px Arial';\n        ctx.fillText('Raggruppa per: ' + groupField + ' | Valori: ' + selectedMetrics.join(', '), 12, cv.height - 12);\n    };\n    window.drawTrend3D = function(canvasId, rows) {\n        const cv = gE(canvasId); if(!cv) return; const ctx=cv.getContext('2d'); ctx.clearRect(0,0,cv.width,cv.height);\n        const selectedMetrics = window.getActivitySelectedMetrics();\n        const groupField = window.getActivitySelectedGroupField();\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 = selectedMetrics.reduce((s, metric) => s + (window.parseReportNum(r[metric], 0) || 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        if(!entries.length){ ctx.fillStyle='#fff'; ctx.font='14px Arial'; ctx.fillText('Nessun dato',20,20); return; }\n        const groups = Object.entries(groupTotals).sort((a,b)=>b[1]-a[1]).slice(0,4).map(e => e[0]);\n        if(!groups.length){ ctx.fillStyle='#fff'; ctx.font='14px Arial'; ctx.fillText('Nessun dato',20,20); return; }\n        const max=Math.max(1, ...entries.flatMap(e => groups.map(g => e[1][g] || 0)));\n        const baseY=260; const left=60; const step=68; const barW=Math.max(10, Math.floor(40 \/ Math.max(1, groups.length))); const colors=['#38bdf8','#22c55e','#f59e0b','#ef4444','#a855f7','#14b8a6'];\n        ctx.strokeStyle='#94a3b8'; ctx.beginPath(); ctx.moveTo(left,20); ctx.lineTo(left,baseY); ctx.lineTo(left+step*(entries.length),baseY); ctx.stroke();\n        entries.forEach(([label, metrics], i)=>{\n            const groupX=left+18+i*step;\n            groups.forEach((grp, gi) => {\n                const val = metrics[grp] || 0;\n                const h=(val\/max)*170;\n                const x=groupX + gi*(barW+4);\n                ctx.fillStyle='rgba(0,0,0,0.25)'; ctx.fillRect(x+6, baseY-h+6, barW, h);\n                ctx.fillStyle=colors[gi%colors.length]; ctx.fillRect(x, baseY-h, barW, h);\n                if (h > 0) {\n                    ctx.fillStyle='#fff'; ctx.font='10px Arial'; ctx.fillText(window.fmtNum(val), x-2, baseY-h-6);\n                }\n            });\n            ctx.save(); ctx.fillStyle='#fff'; ctx.font='10px Arial'; ctx.translate(groupX, baseY+14); ctx.rotate(-0.55); ctx.fillText(label.slice(0,16),0,0); ctx.restore();\n        });\n        groups.forEach((grp, gi) => {\n            const lx = 18 + gi*180; const ly = 18;\n            ctx.fillStyle = colors[gi%colors.length]; ctx.fillRect(lx, ly, 14, 14);\n            ctx.fillStyle = '#fff'; ctx.font = '12px Arial'; ctx.fillText(grp, lx + 20, ly + 12);\n        });\n        ctx.fillStyle = '#cbd5e1'; ctx.font = '12px Arial';\n        ctx.fillText('Raggruppa per: ' + groupField + ' | Valori: ' + selectedMetrics.join(', '), 12, cv.height - 12);\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            + '<\/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=\"760\" height=\"300\"><\/canvas><\/div>';\n        } else {\n            html += '<div class=\"ti-chart-wrap\"><canvas id=\"ti-report-trend\" width=\"860\" height=\"340\"><\/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.openActivityReport = function() {\n        const prefs = window.loadActivityReportPrefs();\n        window.activityReportState = { sortCol: 'DataOra', sortDir: 'desc', filters: {}, view: 'table', colFlags: prefs.colFlags || {}, rowFlags: prefs.rowFlags || {}, metricFlags: prefs.metricFlags || window.getDefaultActivityMetricFlags(), groupField: prefs.groupField || 'Username' };\n        window.renderActivityReport();\n        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    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        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    };\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.makeConfigSafeId = function(v) { return String(v == null ? '' : v).replace(\/[^a-zA-Z0-9_-]+\/g, '_'); };\n    window.getConfigRelationDomId = function(tbl, idx, key) { return `${window.makeConfigSafeId(tbl)}-${idx}-${window.makeConfigSafeId(key)}`; };\n    window.uniqueConfigRelationValues = function(values) { const seen = new Set(); const out = []; (Array.isArray(values) ? values : []).forEach(function(v){ const s = String(v == null ? '' : v).trim(); if (!s) return; const k = s.toLowerCase(); if (seen.has(k)) return; seen.add(k); out.push(s); }); return out; };\n    window.splitConfigRelationValues = function(raw) { return window.uniqueConfigRelationValues(String(raw == null ? '' : raw).split(\/[;,\\n]+\/).map(function(v){ return String(v || '').trim(); }).filter(Boolean)); };\n    window.initConfigTopScrollbars = function(root) { try { const scope = root || document; scope.querySelectorAll('.ti-sync-hscroll-wrap').forEach(function(wrap){ if (!wrap || !wrap.querySelector) return; const table = wrap.querySelector('table'); if (!table) return; 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; const bars = [topBar, bottomBar].filter(Boolean); if (!bars.length) return; 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); const canScroll = width > ((wrap.clientWidth || 0) + 4); bars.forEach(function(bar){ const inner = bar.querySelector('.ti-cfg-top-scroll-inner, .ti-sync-hscroll-inner'); if (!inner) return; inner.style.width = width + 'px'; bar.style.display = 'block'; bar.style.visibility = canScroll ? 'visible' : 'hidden'; }); if (wrap.dataset.syncBound !== '1') { const syncBars = function(source){ const left = source && typeof source.scrollLeft === 'number' ? source.scrollLeft : wrap.scrollLeft; if (wrap.scrollLeft !== left) wrap.scrollLeft = left; bars.forEach(function(bar){ if (bar && bar.scrollLeft !== left) bar.scrollLeft = left; }); }; wrap.addEventListener('scroll', function(){ syncBars(wrap); }, { passive:true }); bars.forEach(function(bar){ bar.addEventListener('scroll', function(){ syncBars(bar); }, { passive:true }); }); wrap.dataset.syncBound = '1'; } bars.forEach(function(bar){ if (bar) bar.scrollLeft = wrap.scrollLeft || 0; }); }); } catch(e) {} };\n    window.bindConfigTableUX = function(root) { try { const scope = root || document; scope.querySelectorAll('details.ti-cfg-details').forEach(function(det){ if (det.dataset.uxBound === '1') return; det.addEventListener('toggle', function(){ requestAnimationFrame(function(){ window.initConfigTopScrollbars(scope); setTimeout(function(){ window.initConfigTopScrollbars(scope); }, 120); }); }); det.dataset.uxBound = '1'; }); scope.querySelectorAll('.ti-cfg-table-container').forEach(function(wrap){ if (wrap.dataset.scrollUxBound === '1') return; wrap.addEventListener('mouseenter', function(){ window.initConfigTopScrollbars(scope); }, { passive:true }); wrap.addEventListener('touchstart', function(){ window.initConfigTopScrollbars(scope); }, { passive:true }); wrap.dataset.scrollUxBound = '1'; }); if (!window.__cfgResizeBound) { let resizeTimer = null; window.addEventListener('resize', function(){ clearTimeout(resizeTimer); resizeTimer = setTimeout(function(){ window.initConfigTopScrollbars(document); }, 60); }, { passive:true }); window.__cfgResizeBound = true; } } catch(e) {} };\n    window.getConfigRelationMeta = function(tbl, key) { const tableName = String(tbl || '').toLowerCase(); const fieldName = String(key || '').toLowerCase(); if (tableName !== 'servizi') return null; const map = {'sito': {table: 'Siti', picks: ['Descrizione','Sito','Nome','Titolo']}, 'dispositivo': {table: 'Dispositivi', picks: ['Dispositivo','Nome','Titolo']}, 'prodotto': {table: 'Prodotti', picks: ['Prodotto','Nome','Titolo']}, 'operatore': {table: 'Operatori', picks: ['Operatore','Utente','Username','Nome','Titolo']}}; return map[fieldName] || null; };\n    window.getConfigRelationRecords = function(tbl, key) { const meta = window.getConfigRelationMeta(tbl, key); const dbData = window.currentDbData; if (!meta || !dbData || !dbData.Tabelle || !Array.isArray(dbData.Tabelle[meta.table])) return []; const out = []; const seen = new Set(); const isOperator = String(key || '').toLowerCase() === 'operatore'; dbData.Tabelle[meta.table].forEach(function(row){ if (!row || typeof row !== 'object') return; const getField = function(names) { for (let i = 0; i < names.length; i++) { const foundKey = Object.keys(row).find(function(rk){ return String(rk || '').toLowerCase() === String(names[i] || '').toLowerCase(); }); const foundVal = foundKey ? String(row[foundKey] == null ? '' : row[foundKey]).trim() : ''; if (foundVal) return foundVal; } return ''; }; const label = getField(meta.picks || []); if (!label) return; const utente = isOperator ? getField(['Utente','Username','Operatore','Nome','Titolo']) : ''; const competenze = isOperator ? getField(['Competenze','Competenza','Skills','Skill','Abilitazioni']) : ''; const keyLower = label.toLowerCase(); if (seen.has(keyLower)) return; seen.add(keyLower); out.push({ label: label, utente: utente, competenze: competenze, searchText: [label, utente, competenze].join(' ').toLowerCase() }); }); return out.sort(function(a, b){ return String(a.label).localeCompare(String(b.label), 'it'); }); };\n    window.getConfigRelationOptions = function(tbl, key) { return window.getConfigRelationRecords(tbl, key).map(function(r){ return r.label; }); };\n    window.setConfigRelationFieldValue = function(tbl, idx, key, values, markerEl) { const cleanValues = window.uniqueConfigRelationValues(values); const joined = cleanValues.join(', '); if (window.currentDbData && window.currentDbData.Tabelle[tbl] && window.currentDbData.Tabelle[tbl][idx]) { window.currentDbData.Tabelle[tbl][idx][key] = joined; if (!window.modifiedFields) window.modifiedFields = new Set(); window.modifiedFields.add(`${tbl}-${idx}-${key}`); } if (markerEl && markerEl.style) markerEl.style.setProperty('border-color', '#fbbf24', 'important'); const domId = window.getConfigRelationDomId(tbl, idx, key); const summary = document.getElementById(`cfg-rel-summary-${domId}`); if (summary) summary.textContent = joined || 'Nessun record associato'; const btn = document.getElementById(`cfg-rel-btn-${domId}`); if (btn) { btn.textContent = joined ? 'Modifica selezione' : 'Seleziona'; btn.style.setProperty('border-color', '#fbbf24', 'important'); } };\n    window.updateMemFieldFromMultiSelect = function(tbl, idx, key, sel) { if (!sel) return; const values = Array.from(sel.selectedOptions || []).map(function(opt){ return String(opt.value || '').trim(); }).filter(Boolean); window.setConfigRelationFieldValue(tbl, idx, key, values, sel); };\n    window.ensureConfigRelationPopup = function() { if (document.getElementById('ti-rel-popup-ov')) return; const wrap = document.createElement('div'); wrap.id = 'ti-rel-popup-ov'; wrap.className = 'ti-ov'; wrap.innerHTML = `<div class=\"ti-modal\" style=\"width:96%;max-width:980px;text-align:left;\"><h3 id=\"ti-rel-popup-title\" style=\"margin-top:0;color:#fff;\">Selezione collegamenti<\/h3><div id=\"ti-rel-popup-subtitle\" style=\"font-size:12px;color:#93c5fd;margin-bottom:10px;\"><\/div><div class=\"ti-rel-popup-toolbar\"><input type=\"text\" id=\"ti-rel-popup-search\" class=\"ti-in ti-rel-popup-search\" placeholder=\"Cerca tra i record disponibili\"><button type=\"button\" class=\"ti-btn\" style=\"background:#1d4ed8;color:#fff;\" onclick=\"window.selectAllConfigRelationPopupVisible(true)\">Tutti<\/button><button type=\"button\" class=\"ti-btn\" style=\"background:#374151;color:#fff;\" onclick=\"window.selectAllConfigRelationPopupVisible(false)\">Nessuno<\/button><\/div><div id=\"ti-rel-popup-extrafilters\" class=\"ti-rel-popup-extrafilters\" style=\"display:none;\"><input type=\"text\" id=\"ti-rel-popup-user\" class=\"ti-in\" placeholder=\"Filtra per Utente\"><input type=\"text\" id=\"ti-rel-popup-skill\" class=\"ti-in\" placeholder=\"Filtra per Competenze\"><\/div><div id=\"ti-rel-popup-list\" class=\"ti-rel-popup-list\"><\/div><div style=\"margin-top:12px;font-size:12px;color:#93c5fd;\">Testo libero aggiuntivo<\/div><textarea id=\"ti-rel-popup-free\" class=\"ti-in ti-rel-popup-free\" placeholder=\"Puoi scrivere uno o pi\u00f9 valori manuali, separati da virgola o invio\"><\/textarea><div style=\"display:flex;gap:8px;justify-content:flex-end;flex-wrap:wrap;margin-top:14px;\"><button type=\"button\" class=\"ti-btn\" onclick=\"window.closeConfigRelationPopup()\">Annulla<\/button><button type=\"button\" class=\"ti-btn\" style=\"background:#10b981;color:#fff;\" onclick=\"window.applyConfigRelationPopup()\">Conferma<\/button><\/div><\/div>`; document.body.appendChild(wrap); wrap.addEventListener('click', function(ev){ if (ev.target === wrap) window.closeConfigRelationPopup(); }); ['ti-rel-popup-search', 'ti-rel-popup-user', 'ti-rel-popup-skill'].forEach(function(id){ const el = wrap.querySelector('#' + id); if (el) el.addEventListener('input', function(){ window.renderConfigRelationPopupOptions(); }); }); };\n    window.configRelationArrayHasValue = function(arr, value) { const needle = String(value || '').trim().toLowerCase(); return (Array.isArray(arr) ? arr : []).some(function(v){ return String(v || '').trim().toLowerCase() === needle; }); };\n    window.configRelationArrayAddValue = function(arr, value) { const out = Array.isArray(arr) ? arr.slice() : []; if (!window.configRelationArrayHasValue(out, value)) out.push(String(value || '').trim()); return window.uniqueConfigRelationValues(out); };\n    window.configRelationArrayRemoveValue = function(arr, value) { const needle = String(value || '').trim().toLowerCase(); return (Array.isArray(arr) ? arr : []).filter(function(v){ return String(v || '').trim().toLowerCase() !== needle; }); };\n    window.renderConfigRelationPopupOptions = function() { const st = window.configRelationPopupState || null; const list = document.getElementById('ti-rel-popup-list'); if (!st || !list) return; const q = String((document.getElementById('ti-rel-popup-search') || {}).value || '').trim().toLowerCase(); const qUser = String((document.getElementById('ti-rel-popup-user') || {}).value || '').trim().toLowerCase(); const qSkill = String((document.getElementById('ti-rel-popup-skill') || {}).value || '').trim().toLowerCase(); const visibleRecords = (st.baseRecords || []).filter(function(rec){ const okText = !q || String(rec.searchText || '').includes(q); const okUser = !qUser || String(rec.utente || '').toLowerCase().includes(qUser); const okSkill = !qSkill || String(rec.competenze || '').toLowerCase().includes(qSkill); return okText && okUser && okSkill; }); if (!visibleRecords.length) { list.innerHTML = `<div style=\"padding:8px;color:#9ca3af;\">Nessun record trovato.<\/div>`; return; } const isOperator = st.keyLower === 'operatore'; list.innerHTML = visibleRecords.map(function(rec){ const safeLabel = window.escapeHtml(rec.label); const checked = window.configRelationArrayHasValue(st.selectedBase, rec.label) ? ' checked' : ''; const metaHtml = isOperator ? `<div class=\"ti-rel-popup-meta\">Utente: ${window.escapeHtml(rec.utente || '-')} \u00b7 Competenze: ${window.escapeHtml(rec.competenze || '-')}<\/div>` : ''; return `<label class=\"ti-rel-popup-item\"><input type=\"checkbox\" value=\"${safeLabel}\"${checked} onchange=\"window.onConfigRelationPopupToggleValue(this)\"><span><div>${safeLabel}<\/div>${metaHtml}<\/span><\/label>`; }).join(''); };\n    window.onConfigRelationPopupToggleValue = function(el) { const st = window.configRelationPopupState || null; if (!st || !el) return; const value = String(el.value || '').trim(); if (!value) return; if (el.checked) st.selectedBase = window.configRelationArrayAddValue(st.selectedBase, value); else st.selectedBase = window.configRelationArrayRemoveValue(st.selectedBase, value); };\n    window.selectAllConfigRelationPopupVisible = function(flag) { const st = window.configRelationPopupState || null; const list = document.getElementById('ti-rel-popup-list'); if (!st || !list) return; list.querySelectorAll('input[type=\"checkbox\"]').forEach(function(cb){ cb.checked = !!flag; const value = String(cb.value || '').trim(); if (!value) return; if (flag) st.selectedBase = window.configRelationArrayAddValue(st.selectedBase, value); else st.selectedBase = window.configRelationArrayRemoveValue(st.selectedBase, value); }); };\n    window.openConfigRelationPopup = function(tbl, idx, key) { window.ensureConfigRelationPopup(); const meta = window.getConfigRelationMeta(tbl, key); if (!meta) return; const currentRaw = (window.currentDbData && window.currentDbData.Tabelle && window.currentDbData.Tabelle[tbl] && window.currentDbData.Tabelle[tbl][idx]) ? String(window.currentDbData.Tabelle[tbl][idx][key] == null ? '' : window.currentDbData.Tabelle[tbl][idx][key]).trim() : ''; const baseRecords = window.getConfigRelationRecords(tbl, key); const currentValues = window.splitConfigRelationValues(currentRaw); const selectedBase = []; const customValues = []; currentValues.forEach(function(v){ const existsInBase = baseRecords.some(function(rec){ return String(rec.label).trim().toLowerCase() === String(v).trim().toLowerCase(); }); if (existsInBase) selectedBase.push(v); else customValues.push(v); }); window.configRelationPopupState = { tbl: tbl, idx: idx, key: key, keyLower: String(key || '').toLowerCase(), relationTable: meta.table, baseRecords: baseRecords.slice(), selectedBase: window.uniqueConfigRelationValues(selectedBase) }; const title = document.getElementById('ti-rel-popup-title'); const subtitle = document.getElementById('ti-rel-popup-subtitle'); const search = document.getElementById('ti-rel-popup-search'); const free = document.getElementById('ti-rel-popup-free'); const fUser = document.getElementById('ti-rel-popup-user'); const fSkill = document.getElementById('ti-rel-popup-skill'); const extra = document.getElementById('ti-rel-popup-extrafilters'); const isOperator = String(key || '').toLowerCase() === 'operatore'; if (title) title.textContent = `Selezione ${key}`; if (subtitle) subtitle.textContent = `Tabella collegata: ${meta.table} \u2022 Riga ${idx + 1} \u2022 Tabella ${tbl}`; if (search) search.value = ''; if (free) free.value = customValues.join(', '); if (fUser) fUser.value = ''; if (fSkill) fSkill.value = ''; if (extra) extra.style.display = isOperator ? 'grid' : 'none'; window.renderConfigRelationPopupOptions(); window.openModal('ti-rel-popup-ov'); setTimeout(function(){ const inp = document.getElementById('ti-rel-popup-search'); if (inp) inp.focus(); }, 30); };\n    window.closeConfigRelationPopup = function() { window.closeModal('ti-rel-popup-ov'); };\n    window.applyConfigRelationPopup = function() { const st = window.configRelationPopupState || null; if (!st) return; const free = document.getElementById('ti-rel-popup-free'); const freeValues = window.splitConfigRelationValues(free ? free.value : ''); const finalValues = window.uniqueConfigRelationValues((st.selectedBase || []).concat(freeValues)); window.setConfigRelationFieldValue(st.tbl, st.idx, st.key, finalValues); window.closeConfigRelationPopup(); };\n    window.renderConfig = function() {\n        window.__ensurePluginDateInputs();\n        if(!window.currentDbData) return; 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        \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        \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');\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=\"${tName}\" style=\"background:#111; margin-bottom:8px; border-radius:8px; border:1px solid #333;\" ${isOpen}><summary class=\"ti-cfg-summary\">${tName}<\/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                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\" onclick=\"window.delBulk(decodeURIComponent('${jsEsc(tName)}'), '${safeCls}')\" 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:{}, sortKey:'', sortDir:'asc'};\n                const tableState = window.configTableState[tName];\n                let rowEntries = rows.map((row, idx) => ({row, idx}));\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                        const av = window.getComparableCellValue({innerText: String(a.row && a.row[sortKey] != null ? a.row[sortKey] : '')});\n                        const bv = window.getComparableCellValue({innerText: String(b.row && b.row[sortKey] != null ? b.row[sortKey] : '')});\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                html += `<thead><tr>`;\n                keys.forEach((k, i) => {\n                    let stickyClass = (i===0) ? 'ti-sticky-col' : '';\n                    let safeK = jsEsc(k);\n                    let dir = (tableState.sortKey === k ? (tableState.sortDir === 'desc' ? '\u25bc' : '\u25b2') : '\u2195');\n                    let fVal = (tableState.filters && tableState.filters[k]) ? String(tableState.filters[k]).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\" value=\"${fVal}\" oninput=\"window.filterConfigTable(decodeURIComponent('${jsEsc(tName)}'),decodeURIComponent('${safeK}'), this.value)\" 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}')\"><\/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>`;\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 = (i===0) ? 'ti-sticky-col' : ''; let isMod = window.modifiedFields && window.modifiedFields.has(`${tName}-${idx}-${key}`); let bStyle = isMod ? 'border-color:#fbbf24 !important;' : '';\n                        html += `<td class=\"${stickyClass}\">`;\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 readOnlyAttr = (isDataCreazione && !isSuperAdmin) ? 'readonly title=\"Modificabile solo da Amministratore Globale\" style=\"color:#888;\"' : '';\n                        if (isInstructionsTable && !canEditRows) readOnlyAttr = 'readonly title=\"Tabella modificabile solo da amministratore, configuratore o responsabile\" style=\"color:#888;\"';\n                        const isConfigStateField = (String(tName || '').toLowerCase() === 'config' && String(key || '').toLowerCase() === 'stato');\n                        const tiRoleLower = String(window.tiRole || '').toLowerCase();\n                        const tiUserLower = String(window.tiUser || '').toLowerCase();\n                        const canManageCompanyState = (tiUserLower === 'ssglobaladmin' || tiRoleLower.includes('amministratore'));\n                        let fieldReadOnlyAttr = readOnlyAttr;\n                        if (isConfigStateField && canManageCompanyState) fieldReadOnlyAttr = '';\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 = safeVal.split(',').map(f => f.trim()).filter(f=>f);\n                            let firstImg = fileList.find(f => f.match(\/\\.(jpg|jpeg|png|webp|gif)\/i));\n                            let thumbHtml = '';\n                            if (firstImg) {\n                                let viewUrl = window.getSafePreviewUrl(firstImg, gE('ti-ditta').value, tName, key, window.getRecordLabelForUpload(tName, row, tName));\n                                const fbUrl = window.getCompanyFallbackMediaUrl(gE('ti-ditta').value);\n                                thumbHtml = `<img decoding=\"async\" src=\"${viewUrl}\" data-fallback=\"${window.escapeHtml(fbUrl)}\" onerror=\"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\">`;\n                            } else if (fileList.length > 0) {\n                                thumbHtml = `<span style=\"font-size:18px; cursor:pointer;\" onclick=\"window.openFileManager(decodeURIComponent('${jsEsc(tName)}'), ${idx}, decodeURIComponent('${jsEsc(key)}'))\" title=\"Apri Galleria\">\ud83d\udcc4<\/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);\n                            if (isConfigStateField) {\n                                const currentStateRaw = String(val == null ? '' : val).trim();\n                                const currentStateNorm = \/configuraz\/i.test(currentStateRaw) ? 'Configurazione' : (\/sospes\/i.test(currentStateRaw) ? 'Sospesa' : (\/attiv\/i.test(currentStateRaw) ? 'Attiva' : 'Configurazione'));\n                                const stateDisabledAttr = fieldReadOnlyAttr ? 'disabled title=\"Modificabile solo da amministratore\"' : '';\n                                html += `<select class=\"ti-in\" style=\"min-width:120px; background:#000; color:#fff; border:1px solid #444; ${bStyle}\" ${stateDisabledAttr} onchange=\"window.currentDbData.Tabelle[decodeURIComponent('${jsEsc(tName)}')][${idx}][decodeURIComponent('${jsEsc(key)}')] = this.value; if(!window.modifiedFields) window.modifiedFields = new Set(); window.modifiedFields.add('${tName}-${idx}-${key}'); this.style.setProperty('border-color','#fbbf24','important'); this.style.setProperty('background','#000','important'); this.style.setProperty('color','#fff','important');\">\n                                    <option value=\"Configurazione\"${currentStateNorm === 'Configurazione' ? ' selected' : ''}>Configurazione<\/option>\n                                    <option value=\"Attiva\"${currentStateNorm === 'Attiva' ? ' selected' : ''}>Attiva<\/option>\n                                    <option value=\"Sospesa\"${currentStateNorm === 'Sospesa' ? ' selected' : ''}>Sospesa<\/option>\n                                <\/select>`;\n                            } else if (relationMeta) {\n                                const selectedVals = window.splitConfigRelationValues(val);\n                                const relSummary = selectedVals.join(', ');\n                                const relDomId = window.getConfigRelationDomId(tName, idx, key);\n                                html += `<div class=\"ti-rel-editor\">\n                                    <div style=\"font-size:11px; color:#93c5fd;\">Apri popup collegamenti da ${relationMeta.table}<\/div>\n                                    <button type=\"button\" id=\"cfg-rel-btn-${relDomId}\" class=\"ti-rel-open-btn\" onclick=\"window.openConfigRelationPopup(decodeURIComponent('${jsEsc(tName)}'), ${idx}, decodeURIComponent('${jsEsc(key)}'))\" ${fieldReadOnlyAttr}>${relSummary ? 'Modifica selezione' : 'Seleziona'}<\/button>\n                                    <div id=\"cfg-rel-summary-${relDomId}\" class=\"ti-rel-summary\">${window.escapeHtml(relSummary || 'Nessun record associato')}<\/div>\n                                <\/div>`;\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\" 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\" style=\"min-width:200px; height:40px; resize:vertical; ${bStyle}\" ${fieldReadOnlyAttr}>${safeVal}<\/textarea><\/div>`; } else {\n                                    let inpType = isPassword ? 'text' : (window.isDateFieldName(key) ? 'date' : (window.isTimeFieldName(key) ? 'time' : 'text')); \n                                    let inputVal = safeVal;\n                                    if (inpType === 'date') inputVal = window.ddmmyyyyToInputDate(val) || ''; if (inpType === 'time') inputVal = window.hhmmToInputTime(val) || ''; if ((inpType === 'date' || inpType === 'time') && (\/^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\" style=\"min-width:100px; ${bStyle}\" value=\"${String(inputVal).replace(\/\"\/g,'&quot;')}\" ${fieldReadOnlyAttr}>`;\n                                    if (inpType === 'date') html += `<button type=\"button\" class=\"ti-btn\" title=\"Oggi\" onclick=\"this.previousElementSibling.value=(new Date()).toISOString().slice(0,10); this.previousElementSibling.dispatchEvent(new Event('change')); this.previousElementSibling.showPicker && this.previousElementSibling.showPicker();\" 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-cb-${safeCls}\" 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                            <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\" onclick=\"window.delTableRow(decodeURIComponent('${jsEsc(tName)}'), ${idx})\" 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;\">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:12px;border:1px solid #374151;border-radius:10px;background:#111827;\">\n            <h4 style=\"margin:0 0 10px 0;color:#fff;\">\ud83e\udd16 Chat bot configurazione AI<\/h4>\n            <div style=\"font-size:12px;color:#9ca3af;margin-bottom:8px;\">Fornisci istruzioni di configurazione tramite AI. Esempio: <i>Aggiungere foto AI a tutti i prodotti<\/i><\/div>\n            <textarea id=\"ti-conf-ai-prompt\" class=\"ti-in\" rows=\"4\" placeholder=\"Fornisci istruzioni di configurazione tramite AI. Esempio: Aggiungere foto AI a tutti i prodotti\"><\/textarea>\n            <div style=\"font-size:12px;color:#9ca3af;margin:12px 0 8px 0;\">Campo dedicato per chiedere consigli operativi su come configurare il plugin. La AI verifica funzioni disponibili, manuale operativo, campi database e codice plugin rilevante.<\/div>\n            <textarea id=\"ti-conf-ai-advice\" class=\"ti-in\" rows=\"4\" placeholder=\"Esempio: Quando un utente prenota invia una email della prenotazione a tutti gli operatori. Come configuro?\"><\/textarea>\n            <div style=\"font-size:12px;color:#9ca3af;margin:8px 0 6px 0;\">Questa area funziona come una vera chat di approfondimento: puoi continuare a fare domande all AI, copiare i messaggi e, se il suggerimento e applicabile ai dati esistenti, decidere poi se farlo applicare automaticamente.<\/div>\n            <div id=\"ti-conf-ai-advice-chat\" style=\"display:none;margin-top:8px;max-height:280px;overflow:auto;background:#020617;padding:10px;border-radius:10px;border:1px solid #334155;\"><\/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 style=\"display:flex;gap:8px;flex-wrap:wrap;\">\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\" id=\"ti-conf-ai-advice-send\" class=\"ti-btn\" style=\"background:#0ea5e9;color:#fff;\" onclick=\"window.runConfigAdviceBot()\">Chiedi consiglio AI<\/button>\n                <button type=\"button\" class=\"ti-btn\" style=\"background:#334155;color:#fff;\" onclick=\"window.copyConfigAdviceChat()\">Copia chat AI<\/button>\n                <button type=\"button\" class=\"ti-btn\" style=\"background:#475569;color:#fff;\" onclick=\"window.clearConfigAdviceChat()\">Pulisci chat AI<\/button>\n                ${window.tiUser === 'SSGlobalAdmin' ? '<button type=\"button\" class=\"ti-btn\" style=\"background:#a16207;color:#fff;\" onclick=\"window.openSharedSetupModal()\">Setup storage centralizzato<\/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:#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                <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-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>`;\n        const confCnt = gE('ti-conf-cnt');\n        if (confCnt) {\n            confCnt.innerHTML = html;\n            window.__ensurePluginDateInputs();\n            window.initPluginDateInputs(confCnt);\n            window.bindConfigTableUX(confCnt);\n            window.initConfigTopScrollbars(confCnt);\n            requestAnimationFrame(function(){ window.initConfigTopScrollbars(confCnt); setTimeout(function(){ window.initConfigTopScrollbars(confCnt); }, 120); });\n            if (!window.__configRenderedOnce || window.configForceTop) {\n                confCnt.scrollTop = 0;\n                document.querySelectorAll('.ti-cfg-table-container').forEach(function(el){ el.scrollTop = 0; el.scrollLeft = 0; });\n                document.querySelectorAll('.ti-cfg-top-scroll, .ti-cfg-bottom-scroll').forEach(function(el){ el.scrollLeft = 0; });\n                window.__configRenderedOnce = true;\n                window.configForceTop = false;\n            }\n        }\n    };\n\n    window.saveConfig = function() {\n        if (window.__saveConfigInFlight) return;\n        if(!window.currentDbData) return;\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        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) window.renderConfig();\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        const jsonStr = JSON.stringify(window.currentDbData); const b64Payload = 'FULL_DB_UPDATE_B64|||' + utoa(jsonStr);\n        const fd = new FormData(); fd.append('ti_action', 'ti_ai_chat_start'); fd.append('db', gE('ti-ditta').value); fd.append('text', b64Payload);\n\n        window.showProgressPopup('Salvataggio configurazione', 'Sto preparando e salvando le modifiche al database.', {startPercent: 5, targetPercent: 95, stepMs: 500});\n\n        const saveSignal = window.tiLongProcessState && window.tiLongProcessState.controller ? window.tiLongProcessState.controller.signal : undefined;\n        fetch(window.tiUrl, {method:'POST', body:fd, signal: saveSignal}).then(r => { if (r.status === 413) throw new Error('Errore 413: Limite superato.'); if (!r.ok) throw new Error('Errore Server ' + r.status); return r.json(); }).then(d => {\n            if(d.success) {\n                window.hideProgressPopup(true, 'Configurazione salvata', {notifyUser:true, remindSave:false, successMessage:'Attivita AI completata e configurazione salvata con successo.'});\n                window.configWasSaved = true;\n                if(window.modifiedFields) window.modifiedFields.clear();\n                setTimeout(() => { restoreBtn(true); }, 900);\n            } else {\n                window.hideProgressPopup(false, 'Errore salvataggio');\n                window.tiAlert('Errore: ' + ((d && d.data && d.data.message) ? d.data.message : 'salvataggio non riuscito'));\n                restoreBtn(false);\n            }\n        }).catch(e => {\n            if (e && (e.name === 'AbortError' || e.userCancelled || window.isLongAIProcessCancelled())) { restoreBtn(false); window.clearLongAIProcess(); return; }\n            window.hideProgressPopup(false, 'Errore salvataggio');\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        if(window.currentDbData && window.currentDbData.Tabelle[tbl] && window.currentDbData.Tabelle[tbl][idx]) { \n            window.currentDbData.Tabelle[tbl][idx][key] = window.normalizeInputValueForKey(key, el.value); el.style.setProperty('border-color', '#fbbf24', 'important'); if(!window.modifiedFields) window.modifiedFields = new Set(); window.modifiedFields.add(`${tbl}-${idx}-${key}`); \n        } \n    };\n\n    window.configAdviceChatHistory = [];\n\n    window.stripHtmlToText = function(html) {\n        const div = document.createElement('div');\n        div.innerHTML = String(html || '');\n        return (div.textContent || div.innerText || '').replace(\/\\n{3,}\/g, '\\n\\n').trim();\n    };\n\n    window.copyTextQuick = function(text) {\n        const raw = String(text || '');\n        if (!raw) return;\n        if (navigator.clipboard && navigator.clipboard.writeText) {\n            navigator.clipboard.writeText(raw).then(function(){ window.tiAlert('Testo copiato.'); }).catch(function(){\n                const ta = document.createElement('textarea');\n                ta.value = raw; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); window.tiAlert('Testo copiato.');\n            });\n            return;\n        }\n        const ta = document.createElement('textarea');\n        ta.value = raw; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); window.tiAlert('Testo copiato.');\n    };\n\n    window.renderConfigAdviceChat = function() {\n        const box = gE('ti-conf-ai-advice-chat');\n        if (!box) return;\n        const items = Array.isArray(window.configAdviceChatHistory) ? window.configAdviceChatHistory : [];\n        if (!items.length) {\n            box.style.display = 'none';\n            box.innerHTML = '';\n            return;\n        }\n        box.style.display = 'block';\n        box.innerHTML = items.map(function(msg, idx){\n            const isUser = msg.role === 'user';\n            const bg = isUser ? '#0f766e' : '#111827';\n            const border = isUser ? '#14b8a6' : '#334155';\n            const label = isUser ? 'Utente' : 'AI';\n            return `<div style=\"margin:0 0 10px 0;padding:10px;border-radius:10px;background:${bg};border:1px solid ${border};color:#e5e7eb;\">\n                <div style=\"display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px;\">\n                    <strong style=\"font-size:12px;color:${isUser ? '#ccfbf1' : '#bfdbfe'};\">${label}<\/strong>\n                    <button type=\"button\" class=\"ti-btn\" style=\"background:#1e293b;color:#fff;padding:4px 8px;font-size:11px;min-height:auto;\" onclick=\"window.copyConfigAdviceMessage(${idx})\">Copia<\/button>\n                <\/div>\n                <div style=\"white-space:pre-wrap;line-height:1.45;\">${window.escapeHtml(String(msg.content || ''))}<\/div>\n            <\/div>`;\n        }).join('');\n        box.scrollTop = box.scrollHeight;\n    };\n\n    window.addConfigAdviceChatMessage = function(role, content) {\n        const clean = String(content || '').trim();\n        if (!clean) return;\n        if (!Array.isArray(window.configAdviceChatHistory)) window.configAdviceChatHistory = [];\n        window.configAdviceChatHistory.push({role: role === 'assistant' ? 'assistant' : 'user', content: clean});\n        if (window.configAdviceChatHistory.length > 24) window.configAdviceChatHistory = window.configAdviceChatHistory.slice(-24);\n        window.renderConfigAdviceChat();\n    };\n\n    window.copyConfigAdviceMessage = function(index) {\n        const items = Array.isArray(window.configAdviceChatHistory) ? window.configAdviceChatHistory : [];\n        const hit = items[index] || null;\n        if (!hit) return;\n        window.copyTextQuick(hit.content || '');\n    };\n\n    window.copyConfigAdviceChat = function() {\n        const items = Array.isArray(window.configAdviceChatHistory) ? window.configAdviceChatHistory : [];\n        if (!items.length) { window.tiAlert('Nessuna chat AI da copiare.'); return; }\n        const raw = items.map(function(msg){ return (msg.role === 'user' ? 'UTENTE' : 'AI') + ':\\n' + String(msg.content || ''); }).join('\\n\\n');\n        window.copyTextQuick(raw);\n    };\n\n    window.clearConfigAdviceChat = function() {\n        window.configAdviceChatHistory = [];\n        window.renderConfigAdviceChat();\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: <b>' + window.escapeHtml(payload.shared_base_path || '(non impostato)') + '<\/b><br>URL condiviso salvato: <b>' + window.escapeHtml(payload.shared_base_url || '(non impostato)') + '<\/b><br><br>Percorso effettivo in uso: <b>' + window.escapeHtml(payload.effective_base_path || '') + '<\/b><br>URL effettivo in uso: <b>' + window.escapeHtml(payload.effective_base_url || '') + '<\/b><br><br>Valori default disponibili: <b>' + window.escapeHtml(payload.default_base_path || '') + '<\/b><br><b>' + window.escapeHtml(payload.default_base_url || '') + '<\/b><br><br>Esempio URL centralizzato Target Informatica: <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.tiUrl, {method:'POST', body:fd}).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            if (manualBtn && window.tiUploadBase) manualBtn.href = window.tiUploadBase + '\/AI-docs\/Shop-service\/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    window.clearConfigBot = function() {\n        const p = gE('ti-conf-ai-prompt'); const a = gE('ti-conf-ai-advice'); const r = gE('ti-conf-ai-reply'); const f = gE('ti-conf-ai-file');\n        if (p) p.value = '';\n        if (a) a.value = '';\n        if (r) { r.style.display = 'none'; r.innerHTML = ''; }\n        window.configBotFile = null;\n        window.clearConfigAdviceChat();\n        if (f) { f.style.display = 'none'; f.innerHTML = ''; }\n    };\n\n    window.currentConfigPreviewKind = 'import';\n\n    window.showConfigImportPreview = function(previewText, options) {\n        options = options || {};\n        window.currentConfigPreviewKind = options.kind || 'import';\n        const titleEl = gE('ti-conf-import-preview-title');\n        const subEl = gE('ti-conf-import-preview-subtitle');\n        const confirmBtn = gE('ti-conf-import-preview-confirm');\n        const cancelBtn = gE('ti-conf-import-preview-cancel');\n        const body = gE('ti-conf-import-preview-body');\n        if (titleEl) titleEl.textContent = options.title || (window.currentConfigPreviewKind === 'suggestion' ? 'Suggerimento AI pronto' : 'Anteprima modifiche import AI');\n        if (subEl) subEl.textContent = options.subtitle || (window.currentConfigPreviewKind === 'suggestion' ? 'Controlla il suggerimento. Conferma solo se vuoi applicare modifiche ai dati esistenti.' : 'Controlla l anteprima delle modifiche. L elenco non verr\u00e0 letto vocalmente. Conferma solo se vuoi procedere al salvataggio nel database.');\n        if (confirmBtn) {\n            confirmBtn.textContent = options.confirmLabel || (window.currentConfigPreviewKind === 'suggestion' ? 'Applica suggerimento AI' : 'OK Conferma e salva');\n            confirmBtn.style.display = options.showConfirm === false ? 'none' : 'inline-flex';\n        }\n        if (cancelBtn) {\n            cancelBtn.textContent = options.cancelLabel || (window.currentConfigPreviewKind === 'suggestion' ? 'Lascio manuale' : 'X Annulla import');\n            cancelBtn.style.display = options.showCancel === false ? 'none' : 'inline-flex';\n        }\n        if (body) {\n            body.innerHTML = (previewText || '').replace(\/\\[NO_VOICE\\]\/g, '').trim();\n            if (window.initConfigTopScrollbars) setTimeout(function(){ window.initConfigTopScrollbars(body); }, 40);\n        }\n        window.openModal('ti-conf-import-preview-ov');\n    };\n\n    window.closeConfigImportPreview = function() {\n        window.closeModal('ti-conf-import-preview-ov');\n    };\n\n    window.confirmConfigImportPreview = function() {\n        const dbSel = gE('ti-ditta');\n        if (!dbSel || !dbSel.value || dbSel.value === 'NEW_DB') { window.tiAlert('Seleziona una ditta.'); return; }\n        const isSuggestion = window.currentConfigPreviewKind === 'suggestion';\n        const mapping = {};\n        if (!isSuggestion) {\n            document.querySelectorAll('#ti-conf-import-preview-body .ti-import-map-select').forEach(function(sel){\n                const hdr = sel.getAttribute('data-source-header') || '';\n                if (hdr) mapping[hdr] = sel.value || '--';\n            });\n            const formulaInp = gE('ti-import-price-formula');\n            if (formulaInp && String(formulaInp.value || '').trim() !== '') mapping['__price_formula'] = String(formulaInp.value || '').trim();\n        }\n        const fd = new FormData();\n        fd.append('ti_action', 'ti_ai_config_action');\n        fd.append('db', dbSel.value);\n        fd.append('mode', isSuggestion ? 'apply_suggestion' : 'confirm_import_preview');\n        if (!isSuggestion) fd.append('mapping_json', JSON.stringify(mapping));\n        window.closeConfigImportPreview();\n        window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(function(res){\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 || (isSuggestion ? 'Suggerimento applicato.' : 'Import completato.')));\n                if (res.data && res.data.needs_reload) setTimeout(function(){ location.reload(); }, 1200);\n            } else {\n                window.tiAlert((res && res.data && res.data.message) ? res.data.message : (isSuggestion ? 'Errore applicazione suggerimento' : 'Errore conferma import'));\n            }\n        }).catch(function(err){\n            window.tiAlert((isSuggestion ? 'Errore applicazione suggerimento: ' : 'Errore conferma import: ') + ((err && err.message) ? err.message : 'errore sconosciuto'));\n        });\n    };\n\n    window.cancelConfigImportPreview = function() {\n        if (window.currentConfigPreviewKind === 'suggestion') {\n            window.closeConfigImportPreview();\n            return;\n        }\n        const box = gE('ti-conf-ai-prompt');\n        if (box) box.value = 'ANNULLA';\n        window.closeConfigImportPreview();\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        replyBox.style.display = 'block';\n        replyBox.innerHTML = 'Preparazione elenco record senza immagine...';\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 da elaborare.', {startPercent: 2, targetPercent: 18, stepMs: 350});\n        window.fetchJsonSafe(window.tiAjaxUrl, {method:'POST', body:initFd}).then(d => {\n            if (!d.success) {\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                window.hideProgressPopup(true, 'Nessun elemento da elaborare', {notifyUser:false, remindSave:false});\n                replyBox.innerHTML = 'Nessun record senza immagine trovato nella tabella selezionata.';\n                return;\n            }\n            let index = 0;\n            let okCount = 0;\n            const logs = [];\n            const total = targets.length;\n            const refreshDb = () => {\n                const fdDb = window.buildAjaxFormData('ti_ai_get_db_data', {db: dbSel.value});\n                window.fetchJsonSafe(window.tiAjaxUrl, {method:'POST', body:fdDb}).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                    replyBox.innerHTML = 'Operazione interrotta dall utente. Per completarla dovrai ripetere la generazione.';\n                    window.clearLongAIProcess();\n                    return;\n                }\n                if (index >= total) {\n                    window.updateProgressPopup(100, 'Generazione foto AI', 'Completamento e aggiornamento configurazione.');\n                    refreshDb();\n                    window.hideProgressPopup(true, 'Generazione foto AI completata', {\n                        remindSave: false,\n                        successMessage: 'Attivita AI completata. Immagini generate: ' + okCount + ' su ' + total + '.'\n                    });\n                    replyBox.innerHTML = '\u2705 Generazione immagini AI completata.<br>Immagini generate: <b>' + okCount + '<\/b> \/ ' + total + '<br><br>' + logs.slice(0, 40).join('<br>');\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                window.updateProgressPopup(pct, 'Generazione foto AI', 'Elaborazione ' + currentStep + ' \/ ' + total + ': ' + (item.name || 'Elemento'));\n                replyBox.innerHTML = 'Generazione immagini AI in corso...<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                });\n                window.fetchJsonSafe(window.tiAjaxUrl, {method:'POST', body:fd}).then(one => {\n                    const data = one && one.data ? one.data : {};\n                    if (one.success && data.generated) {\n                        okCount++;\n                        logs.push('\u2705 ' + (data.name || item.name || 'Elemento') + ': immagine generata\/aggiornata');\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                        replyBox.innerHTML = 'Operazione interrotta dall utente. Per completarla dovrai ripetere la generazione.';\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())) { replyBox.innerHTML = 'Operazione interrotta dall utente. Per completarla dovrai ripetere la generazione.'; window.clearLongAIProcess(); return; }\n            window.hideProgressPopup(false, 'Errore batch immagini');\n            replyBox.innerHTML = 'Errore di rete: ' + err.message;\n        });\n    };\n\n    window.runConfigBotRequest = function(options) {\n        options = options || {};\n        const boxId = options.boxId || 'ti-conf-ai-prompt';\n        const buttonId = options.buttonId || 'ti-conf-ai-send';\n        const requestKind = options.requestKind || 'config_instruction';\n        const progressTitle = options.progressTitle || 'Configurazione AI';\n        const emptyMessage = options.emptyMessage || 'Scrivi una istruzione di configurazione.';\n        const box = gE(boxId); const replyBox = gE('ti-conf-ai-reply'); const btn = gE(buttonId);\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(emptyMessage); return; }\n        const roleTxt = String(window.tiRole || '').toLowerCase();\n        const isDbAdmin = roleTxt.includes('amministratore') || roleTxt.includes('configuratore') || 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. Se necessario invia una richiesta a comm@targetinformatica.it.';\n            return;\n        }\n        btn.disabled = true; const oldText = btn.innerText; btn.innerText = '\u23f3 Elaborazione...';\n        replyBox.style.display = 'block'; replyBox.innerHTML = 'Elaborazione AI in corso...';\n        window.showProgressPopup(progressTitle, '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_suggest',\n            request_kind: requestKind,\n            prompt: prompt\n        });\n        if (window.configBotFile) fd.append('file_url', window.configBotFile);\n        window.fetchJsonSafe(window.tiAjaxUrl, {method:'POST', body:fd}).then(d => {\n            btn.disabled = false; btn.innerText = oldText;\n            if (!d.success) { window.hideProgressPopup(false, 'Errore configurazione AI'); replyBox.innerHTML = 'Errore: ' + (d.data && d.data.message ? d.data.message : 'Errore sconosciuto'); return; }\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 pronta', {notifyUser:false, remindSave:false, successMessage:'Anteprima pronta.'});\n                replyBox.style.display = 'none';\n                window.showConfigImportPreview(replyText, {\n                    kind: (d.data.preview_kind || 'import'),\n                    title: d.data.preview_title || '',\n                    subtitle: d.data.preview_subtitle || '',\n                    confirmLabel: d.data.confirm_label || '',\n                    cancelLabel: d.data.cancel_label || ''\n                });\n                return;\n            }\n            window.hideProgressPopup(true, 'Configurazione AI completata', {remindSave:true, successMessage:'Attivita AI di configurazione completata.'});\n            replyBox.style.display = 'block';\n            replyBox.innerHTML = replyText.replace(\/\\[NO_VOICE\\]\/g, '').trim();\n            if (d.data && d.data.needs_reload) {\n                const fdDb = window.buildAjaxFormData('ti_ai_get_db_data', {db: dbSel.value});\n                fetch(window.tiAjaxUrl, {method:'POST', body: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'; replyBox.innerHTML = 'Operazione interrotta dall utente. Per completarla dovrai ripetere la richiesta.'; window.clearLongAIProcess(); return; }\n            window.hideProgressPopup(false, 'Errore di rete');\n            replyBox.style.display = 'block'; replyBox.innerHTML = 'Errore di rete: ' + err.message;\n        });\n    };\n\n    window.runConfigBot = function() {\n        window.runConfigBotRequest({\n            boxId: 'ti-conf-ai-prompt',\n            buttonId: 'ti-conf-ai-send',\n            requestKind: 'config_instruction',\n            progressTitle: 'Configurazione AI',\n            emptyMessage: 'Scrivi una istruzione di configurazione.'\n        });\n    };\n\n    window.runConfigAdviceBot = function() {\n        window.runConfigBotRequest({\n            boxId: 'ti-conf-ai-advice',\n            buttonId: 'ti-conf-ai-advice-send',\n            requestKind: 'config_advice',\n            progressTitle: 'Consiglio AI configurazione',\n            emptyMessage: 'Scrivi una richiesta di consiglio su come configurare il plugin.'\n        });\n    };\n\n    window.openAtecoSearch = function(targetId) { gE('ateco-target-id').value = targetId; gE('ateco-in').value = ''; gE('ateco-res').innerHTML = '<i>Scrivi l\\'attivit\u00e0 da cercare...<\/i>'; window.openModal('ti-ateco-ov'); };\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        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(); if(!prompt) { window.tiAlert(\"Inserisci una descrizione!\"); return; } if (!\/^Attivit\u00e0 ditta:\/i.test(prompt) && !\/^Verifica anzitutto\/i.test(prompt)) { prompt = window.buildAIGenPrompt(prompt); gE('ai-gen-prompt').value = prompt; }\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.data.message); } \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 = ''; 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}`);\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}`);\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 oppure chiedi un consiglio operativo di configurazione.<\/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                    const adviceBox = gE('ti-conf-ai-advice');\n                    if (adviceBox && !adviceBox.value.trim()) {\n                        adviceBox.value = 'Ho caricato un file per la AI. Analizza il contenuto del file e spiegami come configurare correttamente il plugin in base al file allegato.';\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 && 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 fd = new FormData(); fd.append('ti_action', 'ti_ai_media_upload'); fd.append('db', dbVal); fd.append('file', fileObj); 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                    let rRole = window.tiRole ? window.tiRole.toLowerCase() : '';\n                    let isAdm = rRole.includes('amministratore') || rRole.includes('configuratore') || rRole.includes('responsabile') || window.tiUser === 'SSGlobalAdmin';\n                    let pmt = isAdm ? `Importa il file allegato nel database della ditta. Se il contenuto \u00e8 un listino complesso txt, csv o xlsx, avvia il processo di import dei dati dal file associato al database ditta. Usa Prodotti come destinazione predefinita per listini tecnici\/commerciali strutturati e mostra l'anteprima di import con conferma finale prima del salvataggio.` : `Ho allegato un documento\/immagine. Leggi ed estrai i dati scritti per aiutarmi.`;\n                    if (isAdm) {\n                        window.askAdminUploadedFileAction(window.lastUploadedFile, fileObj.name || '');\n                    } else {\n                        window.rememberLastFileContext(window.lastUploadedFile, pmt, isAdm);\n                        gE('ti-msg').value = pmt;\n                        gE('ti-send').click();\n                    }\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    };\n\n    window.toggleAllTI = function(ck, sC) { document.querySelectorAll('.ti-cb-'+sC).forEach(c=>c.checked=ck.checked); };\n    \n    window.delBulk = function(tN, sC) { \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        let c=document.querySelectorAll('.ti-cb-'+sC+':checked'); \n        if(c.length===0) { window.tiAlert('Seleziona almeno una riga da eliminare.'); return; }\n        window.tiConfirm(\"Eliminare \"+c.length+\" righe?\", function(){ \n            if(!Array.isArray(window.currentDbData.Tabelle[tN])) window.currentDbData.Tabelle[tN]=Object.values(window.currentDbData.Tabelle[tN]); \n            if (window.currentDbData.Tabelle[tN].length <= 1 && c.length === 1) {\n                window.clearSingleRemainingRow(tN, parseInt(c[0].value, 10) || 0);\n            } else {\n                let ixs=Array.from(c).map(x=>parseInt(x.value,10)).filter(n=>!isNaN(n)).sort((a,b)=>b-a); \n                ixs.forEach(i=>{ window.currentDbData.Tabelle[tN].splice(i,1); }); \n            }\n            window.renderConfig(); \n        }); \n    };\n\n    window.addMsg = function(r,h) { const chat = gE('ti-chat-box'); if(!chat) return; const d = document.createElement('div'); d.className = 'ti-bubble ' + r; d.innerHTML = h; chat.appendChild(d); chat.scrollTop = chat.scrollHeight; const msgInp = gE('ti-msg'); if(r === 'ai' && msgInp) msgInp.focus(); };\n\n    window.collectImportMappingFromUI = function(root) {\n        const box = root || document;\n        const 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                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        } 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        } 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 state = { bubbleScrollTop: bubble ? bubble.scrollTop : 0 };\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            }\n        } catch(e) {}\n    };\n\n    window.refreshImportPreviewFromUI = function(sourceEl) {\n        const bubble = sourceEl && sourceEl.closest ? sourceEl.closest('.ti-bubble.ai') : null;\n        const root = bubble || document;\n        const dbVal = gE('ti-ditta') ? gE('ti-ditta').value : '';\n        if (!dbVal) return;\n        const mapping = window.collectImportMappingFromUI(root);\n        const focusState = window.captureImportFocusState(sourceEl);\n        if (bubble) {\n            try { bubble.dataset.importMapping = encodeURIComponent(JSON.stringify(mapping)); } 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', 'refresh_import_preview');\n        fd.append('prompt', 'refresh import preview');\n        fd.append('mapping_json', JSON.stringify(mapping));\n        if (bubble) bubble.style.opacity = '0.75';\n        window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(function(res){\n            if (bubble) bubble.style.opacity = '';\n            if (res && res.success && bubble) {\n                const rawReply = ((res.data || {}).reply || '').replace(\/\\[NO_VOICE\\]\/g, '').trim();\n                if (rawReply) {\n                    bubble.innerHTML = rawReply;\n                    let applyMap = mapping;\n                    try {\n                        if (bubble.dataset.importMapping) applyMap = JSON.parse(decodeURIComponent(bubble.dataset.importMapping));\n                    } catch(e) {}\n                    window.reapplyImportMappingToUI(bubble, applyMap);\n                    if (window.initConfigTopScrollbars) window.initConfigTopScrollbars(bubble);\n                    window.restoreImportFocusState(bubble, focusState);\n                }\n            }\n        }).catch(function(){ if (bubble) bubble.style.opacity = ''; });\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.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 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        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.extractInstructionPriceAdjustments = function(instr, basePrice) {\n        const txt = String(instr || ''); const low = txt.toLowerCase(); let finalPrice = parseFloat(basePrice) || 0; const listPrice = finalPrice; 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        const mDiscPct = low.match(\/sconto\\s*(?:del|di)?\\s*(\\d+[\\.,]?\\d*)\\s*%\/i) || low.match(\/-(\\d+[\\.,]?\\d*)\\s*%\/i);\n        if (mDiscPct) { const pct = parseFloat(String(mDiscPct[1]).replace(',', '.')) || 0; finalPrice = listPrice * (1 - pct \/ 100); }\n        const mAddPct = low.match(\/(?:rincaro|aumento|maggiorazione)\\s*(?:del|di)?\\s*(\\d+[\\.,]?\\d*)\\s*%\/i) || low.match(\/\\+(\\d+[\\.,]?\\d*)\\s*%\/i);\n        if (mAddPct) { const pct = parseFloat(String(mAddPct[1]).replace(',', '.')) || 0; finalPrice = listPrice * (1 + pct \/ 100); }\n        const mDiscEuro = low.match(\/sconto\\s*(?:di)?\\s*\u20ac?\\s*(\\d+[\\.,]?\\d*)\/i);\n        if (mDiscEuro) { const val = parseFloat(String(mDiscEuro[1]).replace(',', '.')) || 0; finalPrice = Math.max(0, listPrice - val); }\n        const mSetPrice = low.match(\/prezzo\\s*(?:applicato|finale|vendita)?\\s*[:=]?\\s*\u20ac?\\s*(\\d+[\\.,]?\\d*)\/i);\n        if (mSetPrice) { finalPrice = parseFloat(String(mSetPrice[1]).replace(',', '.')) || finalPrice; }\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.sortConfigTable = function(tbl, key) {\n        if (!window.configTableState[tbl]) window.configTableState[tbl] = {filters:{}, sortKey:'', sortDir:'asc'};\n        const st = window.configTableState[tbl];\n        st.sortDir = (st.sortKey === key && st.sortDir === 'asc') ? 'desc' : 'asc';\n        st.sortKey = key;\n        window.renderConfig();\n    };\n    window.filterConfigTable = function(tbl, key, value) {\n        if (!window.configTableState[tbl]) window.configTableState[tbl] = {filters:{}, sortKey:'', sortDir:'asc'};\n        window.configTableState[tbl].filters[key] = value || '';\n        window.renderConfig();\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        let h = t.split('[TABLE]')[0];\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();\n        let preferredView = window.getOrderViewPreference();\n        if (!allowedViews.includes(preferredView)) preferredView = allowedViews[0] || 'table';\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')\">Tabella<\/button>` : '',\n            allowedViews.includes('small')  ? `<button type=\"button\" class=\"ti-order-view-btn ${preferredView === 'small' ? 'active' : ''}\" data-view=\"small\" onclick=\"window.setOrderView(this, 'small')\">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')\">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')\">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            const desc = p[4] || '';\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            let firstImg = String(imgUrl).split(',')[0].trim();\n            let fullImgUrl = firstImg ? window.getFullImgUrl(firstImg, gE('ti-ditta').value, 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 cleanIstr = istr ? String(istr).replace(\/\"\/g, '&quot;') : '';\n            const localAdj = window.extractInstructionPriceAdjustments(cleanIstr, pFinal);\n            const hasPriceRule = !!(cleanIstr && localAdj && localAdj.isModified && \/(sconto|rincaro|aumento|maggiorazione|prezzo\\s*(applicato|finale|vendita)?)\/i.test(cleanIstr));\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${cleanIstr}`);\n            const isImgFile = firstImg && \/\\.(jpg|jpeg|png|webp|gif)$\/i.test(firstImg);\n            const orderId = 'ord-' + idx;\n            let qtyDefault = (localAdj && localAdj.defaultQty != null) ? localAdj.defaultQty : 0;\n            const istrLower = String(cleanIstr || '').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, desc, tblName, firstImg, fullImgUrl, cleanIstr, encodedIstr, isImgFile, pFinal, pOrig, isMod, hasPriceRule, priceHtml, qtyDefault, ivaRate,\n                popupJson: encodeURIComponent(JSON.stringify({orderId: orderId, name: n, desc: (desc || n), img: fullImgUrl, note: cleanIstr, price: pFinal, priceHtml: priceHtml, qty: qtyDefault || 0, ivaRate: ivaRate})),\n                nameFilter: window.escapeHtml(n),\n                descFilter: window.escapeHtml(desc || ''),\n                priceFilter: String(pFinal)\n            };\n        }).filter(item => (parseFloat(item && item.pFinal) || 0) > 0);\n\n        const tableRows = items.map(item => {\n            const fbUrl = window.getCompanyFallbackMediaUrl(gE('ti-ditta').value);\n            const warnIcon = item.hasPriceRule ? `<span class=\"ti-order-warn\" data-istr=\"${window.escapeHtml(item.encodedIstr)}\" style='color:#22c55e;font-weight:900;cursor:pointer;font-size:16px;margin-right:5px;'>\u2757<\/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)}\" onerror=\"window.applyFallbackImage(this)\" data-no-lazy=\"1\">`\n                : (item.hasPriceRule ? warnIcon + '\ud83d\udcc4' : '\ud83d\udcc4');\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.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\" data-price=\"${item.pFinal}\">${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 mediaHtml = item.fullImgUrl && item.isImgFile\n                ? `<img decoding=\"async\" src=\"${item.fullImgUrl}\" alt=\"${window.escapeHtml(item.n)}\" class=\"ti-order-popup-trigger\" data-fallback=\"${window.escapeHtml(fbUrl)}\" data-popup=\"${window.escapeHtml(item.popupJson)}\" onerror=\"window.applyFallbackImage(this)\">`\n                : `<div class=\"ti-order-file-icon\">\ud83d\udcc4<\/div>`;\n            const warnBtn = item.hasPriceRule ? `<button type=\"button\" class=\"ti-btn ti-order-warn\" data-istr=\"${window.escapeHtml(item.encodedIstr)}\" style=\"padding:4px 8px;font-size:10px;background:#14532d;\">\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-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\">${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 ? '' : 'style=\"display:none;\"'}>${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\">Prodotto<\/span><input type=\"text\" class=\"ti-filter-input\" oninput=\"window.filterOrderWrap(this,'name')\" placeholder=\"Filtra prodotto\"><\/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\">Prodotto A-Z<\/option><option value=\"name_desc\">Prodotto 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\">Img<\/th><th onclick=\"window.sortTI(this,1)\">Prodotto<\/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 class=\"ti-order-footer\">\n                <div class=\"ti-order-footer-grid\">\n                    <button type=\"button\" class=\"ti-btn ti-order-footer-btn\" onclick=\"window.sendOrderInChat(this)\">${d.confirm}<\/button>\n                    <div class=\"ti-order-footer-total\">Qt\u00e0 tot.<br><b class=\"ti-order-total-qty\">0,00<\/b><\/div>\n                    <div class=\"ti-order-footer-total\">Importo totale<br><b class=\"ti-order-total-value\">\u20ac 0,00<\/b><\/div>\n                <\/div>\n            <\/div>\n        <\/div>`;\n        return h;\n    };\n\n    window.sendOrderInChat = function(btn) { \n        const wrap = btn.closest('.ti-order-wrap') || btn.closest('.ti-table-wrap'); if(!wrap) return; const table = wrap.querySelector('table'); if(!table) return;\n        let orderText = \"RIEPILOGO ORDINE:\\n\"; let totalGross = 0; let totalVat = 0; let hasItems = false; \n        table.querySelectorAll('tbody tr').forEach(tr => { \n            if(tr.style.display === 'none') return; \n            const qtyInput = tr.querySelector('.ti-q'); if(!qtyInput) return; \n            let qty = window.parseQtyInput(qtyInput.value); \n            if(qty > 0) { \n                let name = tr.querySelector('.ti-item-name').innerText.trim(); \n                let price = parseFloat(tr.getAttribute('data-price') || tr.querySelector('.ti-col-eur').getAttribute('data-price')) || 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; totalVat += rowVat; hasItems = true; \n            } \n        }); \n        if(!hasItems) { window.tiAlert(\"Nessun articolo selezionato.\"); return; } \n        const totalNet = totalGross - totalVat;\n        orderText += `\\nIMPONIBILE: \u20ac ${window.fmtNum(totalNet)}\\nIVA SCORPORATA: \u20ac ${window.fmtNum(totalVat)}\\nTOTALE LORDO: \u20ac ${window.fmtNum(totalGross)}`; \n        gE('ti-msg').value = orderText; gE('ti-send').click();\n    };\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.showPluginTopModal('ti-cam-ov'); navigator.mediaDevices.getUserMedia({ video: { facingMode: \"environment\" } }).then(s => { window.stream = s; video.srcObject = s; }).catch(e => { window.hidePluginTopModal('ti-cam-ov'); gE('ti-cam-in').click(); }); } else { gE('ti-cam-in').click(); } };\n    window.closeCamModal = function() { window.hidePluginTopModal('ti-cam-ov'); 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    window.speakText = function(text) {\n        if (!('speechSynthesis' in window) || window.isMuted) return; \n        window.speechSynthesis.cancel();\n        \n        let clean = text.replace(\/\\[TABLE\\][\\s\\S]*?\\[\\\/TABLE\\]\/g, '').replace(\/\\[P\\][\\s\\S]*?\\[\\\/P\\]\/g, '').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        \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.tiUrl, {method:'POST', body:fd}).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.tiUrl, {method:'POST', body:fd}).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: \" + 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    document.querySelectorAll('.ti-lang-opt').forEach(el => {\n        el.onclick = function() {\n            document.querySelectorAll('.ti-lang-opt').forEach(e => e.classList.remove('active')); this.classList.add('active'); window.currLang = this.dataset.lang; const d = dict[window.currLang] || dict['it'];\n            localStorage.setItem('ti_lang_' + window.tiUser, window.currLang);\n            \n            if(gE('ti-msg')) gE('ti-msg').placeholder = d.ph; if(gE('ti-send')) gE('ti-send').innerText = d.send; if(gE('ti-new')) gE('ti-new').innerText = d.new; if(gE('ti-copy')) gE('ti-copy').innerText = d.copy; if(gE('ti-file')) gE('ti-file').innerText = d.file; if(gE('ti-products-btn')) gE('ti-products-btn').innerText = d.products || 'Prodotti'; if(gE('ti-services-btn')) gE('ti-services-btn').innerText = d.services || 'Servizi'; if(gE('ti-kbd-btn')) gE('ti-kbd-btn').innerText = d.kbd; if(gE('ti-mic')) gE('ti-mic').innerText = d.mic; if(gE('ti-cam')) gE('ti-cam').innerText = d.cam; if(gE('ti-login-btn') && gE('ti-login-btn').style.display !== 'none') gE('ti-login-btn').innerText = d.login;\n            window.applyMuteState();\n            \n            let lastAIBubble = Array.from(document.querySelectorAll('.ti-bubble.ai')).pop();\n            if (lastAIBubble) {\n                let htmlToTranslate = lastAIBubble.innerHTML;\n                lastAIBubble.innerHTML = \"<i>Translating... \/ Traduzione in corso... \u23f3<\/i>\";\n                const fd = new FormData();\n                fd.append('ti_action', 'ti_ai_chat_start');\n                fd.append('db', gE('ti-ditta').value || '');\n                fd.append('text', `TRADUCI_ESATTAMENTE_QUESTO_HTML_IN_${window.currLang.toUpperCase()}: ${htmlToTranslate}`);\n                fetch(window.tiUrl, {method: 'POST', body: fd}).then(r=>r.json()).then(res => {\n                    if (res.success) {\n                        lastAIBubble.innerHTML = res.data.reply;\n                        window.speakText(res.data.reply);\n                    } else { lastAIBubble.innerHTML = htmlToTranslate; }\n                }).catch(() => { lastAIBubble.innerHTML = htmlToTranslate; });\n            }\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;\n        window.muteState = localStorage.getItem('ti_mute_state_' + window.tiUser) ? parseInt(localStorage.getItem('ti_mute_state_' + window.tiUser)) : 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('ti_mute_state_' + window.tiUser, '2'); } \n                else { localStorage.removeItem('ti_mute_state_' + window.tiUser); }\n                window.applyMuteState();\n                if(window.isMuted && window.speechSynthesis) window.speechSynthesis.cancel();\n            };\n        }\n        \n        window.showSuspendedDitte = localStorage.getItem('ti_show_suspended_ditte') === '1';\n        window.renderSuspendedToggle = function() {\n            const b = gE('ti-show-sospese-btn'); if(!b) return;\n            b.textContent = window.showSuspendedDitte ? 'Sosp ON' : 'Sosp';\n            b.style.background = window.showSuspendedDitte ? '#b91c1c' : '#374151';\n            b.title = window.showSuspendedDitte ? 'Nascondi le ditte sospese' : 'Mostra anche le ditte sospese';\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        window.renderDitteDropdown = function() {\n            const dittaInput = gE('ti-ditta'); if(!dittaInput) return;\n            const filterInp = gE('ti-filter-ditte');\n            const q = filterInp ? filterInp.value : '';\n            let options = `<option value=\"\">-- Scegli una ditta --<\/option><option value=\"NEW_DB\" style=\"color:#10b981;font-weight:bold;\">+ Nuova ditta ...<\/option>`;\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            options += sorted.map(d => {\n                const safeLogo = d.logo ? d.logo.replace(\/\"\/g, '&quot;') : ''; const safeAcc = d.accoglienza ? d.accoglienza.replace(\/\"\/g, '&quot;') : '';\n                let colorStyle = d.is_sosp ? 'color:#ef4444;font-style:italic;' : (d.is_conf ? 'color:#fbbf24;font-weight:bold' : '');\n                const safeQr = d.qr ? d.qr.replace(\/\"\/g, '&quot;') : '';\n                return `<option value=\"${d.db}\" data-acc=\"${safeAcc}\" data-logo=\"${safeLogo}\" data-qr=\"${safeQr}\" data-name=\"${d.nome_pura}\" style=\"${colorStyle}\">${d.nome_pura}${d.settore ? ' - ' + d.settore : ''}${d.is_conf ? ' - Conf' : ''}${d.is_sosp ? ' - Sosp' : ''}<\/option>`;\n            }).join('');\n            if (!sorted.length && q) {\n                options += `<option value=\"\" disabled style=\"color:#9ca3af;\">Nessuna ditta trovata<\/option>`;\n            }\n            dittaInput.innerHTML = options;\n            if(window.tiForced) { dittaInput.style.display = 'none'; const sBtn = gE('ti-sort-btn'); if(sBtn) sBtn.style.display = 'none'; const fInp = gE('ti-filter-ditte'); if(fInp) fInp.style.display = 'none'; }\n        };\n\n        window.fetchDitteList = function(opts){\n            opts = opts || {};\n            const silent = !!opts.silent;\n            const prevVal = dom.ditta ? dom.ditta.value : '';\n            return fetch(window.tiUrl + '?ti_action=ti_ai_list_ditte&current_db=' + encodeURIComponent(window.tiForced || '') + '&show_suspended=' + (window.showSuspendedDitte ? '1' : '0')).then(r=>r.json()).then(res=>{\n             if(res.success && dom.ditta) { \n                 window.ditteData = res.data.ditte; 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}); } \n                 } else {\n                     let targetDb = prevVal || localStorage.getItem('ti_saved_db');\n                     if (targetDb && window.ditteData.find(d => d.db === targetDb)) { dom.ditta.value = targetDb; }\n                 }\n                 window.updCtx(silent); \n             }\n        }); };\n        \/\/ Al primo accesso o al refresh dell'interfaccia mostra sempre il messaggio di accoglienza\n        \/\/ della ditta selezionata (o il welcome generale se nessuna ditta \u00e8 selezionata).\n        window.fetchDitteList({silent:false});\n\n        const sospBtn = gE('ti-show-sospese-btn');\n        if(sospBtn) {\n            window.renderSuspendedToggle();\n            sospBtn.onclick = () => {\n                window.showSuspendedDitte = !window.showSuspendedDitte;\n                localStorage.setItem('ti_show_suspended_ditte', window.showSuspendedDitte ? '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            };\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); } else { localStorage.removeItem('ti_saved_db'); }\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.updCtx(false);\n                 const fd = new FormData(); fd.append('ti_action', 'ti_ai_reset_flow'); fetch(window.tiUrl, {method:'POST', body:fd});\n            };\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 htmlMsg = `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                     if(window.addMsg) window.addMsg('ai', htmlMsg);\n                     window.speakText(`Sono l'assistente virtuale di Shop-Service. Ciao ${window.tiUserDisplay || ''}! Scegli una ditta dal menu.`);\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; const companySub = gE('ti-company-sub'); if (companySub) { companySub.innerHTML = ''; companySub.style.display = 'none'; } \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 acc = s.dataset.acc && s.dataset.acc !== 'undefined' && s.dataset.acc !== '' ? `<br><br><b>${s.dataset.acc}<\/b>` : '';\n                     let welcomeName = window.tiUserDisplay ? \" Ciao <b>\" + window.tiUserDisplay + \"<\/b>!\" : \"\";\n                     let plainAcc = s.dataset.acc && s.dataset.acc !== 'undefined' ? s.dataset.acc : '';\n                     let plainWelcomeName = window.tiUserDisplay ? \" Ciao \" + window.tiUserDisplay + \"!\" : \"\";\n\n                     let htmlMsg = `Sono l'assistente virtuale di <b>${s.dataset.name}<\/b>.${welcomeName}${acc}<br><br>Come posso aiutarti oggi?`;\n                     let voiceMsg = `Sono l'assistente virtuale di ${s.dataset.name}. ${plainWelcomeName} ${plainAcc} Come posso aiutarti oggi?`;\n\n                     if(window.addMsg) window.addMsg('ai', htmlMsg);\n                     window.speakText(voiceMsg);\n                     window.resetIdleTimers(); \n                 }\n             }\n        };\n\nwindow.idleTimer1 = null; window.idleTimer2 = null; window.idleTimer3 = null;\n        window.resetIdleTimers = function() {\n            clearTimeout(window.idleTimer1); clearTimeout(window.idleTimer2); clearTimeout(window.idleTimer3);\n            if(dom.ditta && dom.ditta.value && dom.ditta.value !== 'NEW_DB') {\n                window.idleTimer1 = setTimeout(() => {\n                    if(window.addMsg) window.addMsg('ai', \"\u23f3 Non rilevo attivit\u00e0 da 3 minuti. Vuoi continuare o chiudere la sessione?\"); window.speakText(\"Non rilevo attivit\u00e0 da 3 minuti. Vuoi continuare o chiudere la sessione?\");\n                    window.idleTimer2 = setTimeout(() => {\n                        if(window.addMsg) window.addMsg('ai', \"\u23f3 Se non ricevo istruzioni chiuder\u00f2 la sessione a breve.\"); window.speakText(\"Se non ricevo istruzioni, chiuder\u00f2 la sessione a breve per proteggere i tuoi dati.\");\n                        window.idleTimer3 = setTimeout(() => { window.doLogout(true); }, 60000); \n                    }, 120000); \n                }, 180000); \n            }\n        };\n        ['mousemove', 'keydown', 'scroll', 'touchstart', 'click'].forEach(evt => { document.addEventListener(evt, window.resetIdleTimers, {passive: true}); });\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            gE('ti-login-btn').style.display = 'none'; gE('ti-logout-btn').style.display = 'inline-block';\n            if(role.toLowerCase().includes('amministratore') || role.toLowerCase().includes('configuratore') || role.toLowerCase().includes('responsabile')) {\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'; document.querySelector('.ti-actions-row').appendChild(confBtn); \n                }\n                confBtn.style.display = 'inline-block';\n                confBtn.onclick = () => {\n                    const fd = new FormData(); fd.append('ti_action', 'ti_ai_get_db_data'); fd.append('db', gE('ti-ditta').value);\n                    window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).then(d=>{ if(d.success) { window.currentDbData = d.data; window.renderConfig(); window.openModal('ti-conf-ov'); } });\n                };\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'; document.querySelector('.ti-actions-row').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'; document.querySelector('.ti-actions-row').appendChild(sb);\n                }\n                sb.style.display = 'inline-block';\n                sb.onclick = () => window.openSharedSetupModal();\n            }\n        };\n\n        if(window.tiUser) { window.setAdminUI(window.tiUserDisplay || window.tiUser, window.tiRole); }\n\n        window.isStructuredListinoFileName = function(fileName) {\n            const nm = String(fileName || '').toLowerCase().trim();\n            return \/\\.(txt|csv|tsv|xlsx)$\/i.test(nm);\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 = '<i>Analisi file in corso...<\/i>';\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('text', prompt + ` [FILE_ALLEGATO: ${fileUrl}]`);\n            fd.append('history_context', JSON.stringify(chatHist));\n\n            window.fetchJsonSafe(window.tiUrl, {method:'POST', body:fd}).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.'));\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. Interpreta al meglio il contenuto, struttura una tabella coerente di preview e 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 = '<i>Avvio import listino in corso...<\/i>';\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.tiUrl, {method:'POST', body:fd}).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.'));\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        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            const fd=new FormData(); fd.append('ti_action','ti_ai_media_upload'); fd.append('db',dbVal); fd.append('file',file); 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                    let rRole = window.tiRole ? window.tiRole.toLowerCase() : '';\n                    let isAdm = rRole.includes('amministratore') || rRole.includes('configuratore') || rRole.includes('responsabile') || window.tiUser === 'SSGlobalAdmin';\n                    let pmt = isAdm ? `Importa il file allegato nel database della ditta. Se il contenuto \u00e8 un listino complesso txt, csv o xlsx, avvia il processo di import dei dati dal file associato al database ditta. Usa Prodotti come destinazione predefinita per listini tecnici\/commerciali strutturati e mostra l'anteprima di import con conferma finale prima del salvataggio.` : `Ho allegato un documento\/immagine. Leggi ed estrai i dati scritti per aiutarmi.`;\n                    if (isAdm) {\n                        window.askAdminUploadedFileAction(window.lastUploadedFile, file.name || '');\n                    } else {\n                        window.rememberLastFileContext(window.lastUploadedFile, pmt, isAdm);\n                        gE('ti-msg').value = pmt; \n                        gE('ti-send').click();\n                    } \n                } else { if(window.addMsg) window.addMsg('ai',`\u26a0\ufe0f Errore caricamento.`); }\n            });\n        };\n\n        if(dom.btnNew) dom.btnNew.onclick = () => { window.clearLastFileContext(); 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 = '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 = 'Mostra tutti i servizi disponibili in tabella ordine'; gE('ti-send').click(); } };\n        \n        const fileIn = gE('ti-file-in');\n        if(fileIn) fileIn.onchange = window.handleFileSelect;\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        if(dom.send) dom.send.onclick = () => {\n            if(!dom.msg) return; let txt=dom.msg.value.trim(); if(!txt) return;\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')) : null;\n            if (lastImportBubble && \/^(ok|conferma|procedi|salva|importa|vai)$\/i.test(txt)) {\n                const mapping = window.collectImportMappingFromUI(lastImportBubble);\n                window.addMsg('user', txt); dom.msg.value='';\n                let b = document.createElement('div'); b.className='ti-bubble ai'; b.innerHTML=\"<i>Importazione in corso...<\/i>\"; if(dom.chat){ dom.chat.appendChild(b); dom.chat.scrollTop=dom.chat.scrollHeight; }\n                const fdImp = new FormData();\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                fdImp.append('mapping_json', JSON.stringify(mapping));\n                fetch(window.tiUrl,{method:'POST',body:fdImp}).then(r=>{ if(!r.ok) throw new Error('Errore Server ' + r.status); return r.json(); }).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                        if (!noVoice) window.speakText(cleanReply);\n                        if (res.data.needs_reload) 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); 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            let textToSend = txt;\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.addMsg('user',txt); 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            let b = document.createElement('div'); b.className='ti-bubble ai'; b.innerHTML=\"<i>Fammi pensare...<\/i>\"; 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.clear_chat && dom.chat) dom.chat.innerHTML = '';\n                    if(res.data.needs_reload) {\n                        window.clearLastFileContext();\n                        setTimeout(() => { location.reload(); }, 2500);\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                                    fetch(window.tiUrl, {method:'POST', body:fdUpdate}).then(ru=>ru.json()).then(du=>{\n                                        if(du.success) {\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); 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(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_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:fd2}).then(rr=>rr.json()).then(rr=>{\n                            window.addMsg('ai',rr.data.reply); window.speakText(rr.data.reply);\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:fd3}).then(r3=>r3.json()).then(r3=>{ window.addMsg('ai',r3.data.reply); window.speakText(r3.data.reply); }); }\n                        });\n                    } else { const rawReply = res.data.reply || ''; const noVoice = !!(res.data && res.data.no_voice) || rawReply.includes('[NO_VOICE]'); const cleanReply = rawReply.replace(\/\\[NO_VOICE\\]\/g, '').trim(); window.addMsg('ai', window.formatTable(cleanReply)); if (!noVoice) window.speakText(cleanReply); }\n                } else { window.addMsg('ai', \"\u26a0\ufe0f Errore AI: \" + (res.data && res.data.message ? res.data.message : 'Sconosciuto')); }\n            }).catch((e)=>{ \n                window.hideProgressPopup(false, 'Errore richiesta AI'); \n                window.addMsg('ai', \"\u26a0\ufe0f \" + e.message); \n            });\n        };\n\n        if(gE('ti-ok')) { gE('ti-ok').onclick=()=>{ const curr = dom.msg ? (dom.msg.value || '').trim() : ''; if (curr && \/^\\d+$\/.test(curr)) { if(dom.send) dom.send.click(); return; } if(dom.msg) dom.msg.value=\"OK\"; if(dom.send) dom.send.click(); }; }\n        if(gE('ti-x')) { gE('ti-x').onclick=()=>{ if(dom.msg) dom.msg.value=\"ANNULLA\"; if(dom.send) dom.send.click(); }; }\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    <\/script>\n\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p class=\"has-text-align-center\">AI Care \u00e8 un servizio di intelligenza artificiale di <strong>Target Informatica S.r.l.<\/strong>&nbsp;<br>Info: <a href=\"mailto: comm@targetinformatica.it\" target=\"_blank\" rel=\"noopener\" title=\"\">comm@targetinformatica.it<\/a> <br>Via Filippo Turati, 16 05100 Terni \u2013 Italy &#8211; Tel.&nbsp;<a href=\"tel:+390744288409\">+39 0744 288409<\/a>\u2013<a href=\"http:\/\/+390744288410\/\">10<\/a> &#8211; P.IVA\/VAT IT00664210556<br>\u00a9 Copyright 2025 Target Informatica S.r.l.<br><a href=\"https:\/\/www.targetinformatica.it\/privacy-policy\/\" target=\"_blank\" rel=\"noopener\" title=\"\">privacy policy<\/a>&nbsp;\u2013&nbsp;<a href=\"https:\/\/www.targetinformatica.it\/cookie-policy\/\" target=\"_blank\" rel=\"noopener\" title=\"\">cookie policy<\/a><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>AI Care is an artificial intelligence service from Target Informatica S.r.l. Info: comm@targetinformatica.it Via Filippo Turati, 16 05100 Terni \u2013 Italy \u2013 Tel. +39 0744 288409\u201310 \u2013 P.IVA\/VAT IT00664210556\u00a9 Copyright 2025 Target Informatica S.r.l. privacy policy \u2013 cookie policy<\/p>","protected":false},"author":25,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-6505","post","type-post","status-publish","format-standard","hentry","category-senza-categoria"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.targetinformatica.it\/en\/wp-json\/wp\/v2\/posts\/6505","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.targetinformatica.it\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.targetinformatica.it\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.targetinformatica.it\/en\/wp-json\/wp\/v2\/users\/25"}],"replies":[{"embeddable":true,"href":"https:\/\/www.targetinformatica.it\/en\/wp-json\/wp\/v2\/comments?post=6505"}],"version-history":[{"count":1,"href":"https:\/\/www.targetinformatica.it\/en\/wp-json\/wp\/v2\/posts\/6505\/revisions"}],"predecessor-version":[{"id":6532,"href":"https:\/\/www.targetinformatica.it\/en\/wp-json\/wp\/v2\/posts\/6505\/revisions\/6532"}],"wp:attachment":[{"href":"https:\/\/www.targetinformatica.it\/en\/wp-json\/wp\/v2\/media?parent=6505"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.targetinformatica.it\/en\/wp-json\/wp\/v2\/categories?post=6505"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.targetinformatica.it\/en\/wp-json\/wp\/v2\/tags?post=6505"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}