#1140 – Luis perez

<?php

<section id="gx-prod-panel">
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

  <style>
    /* --- CONFIGURACIÓN VISUAL (RESTAURADO) --- */
    #gx-prod-panel {
      --bg-app: #f8fafc; --bg-panel: #ffffff;
      --color-text: #334155; --border: #e2e8f0;
      
      /* Colores Vivos */
      --c-teal: #0f766e; --bg-teal-light: #f0fdfa;
      --c-blue: #2563eb; --c-green: #16a34a; 
      --c-red: #dc2626; --c-hold: #d97706; --c-purple: #7c3aed;

      width: 100% !important; max-width: 100% !important; margin: 0 auto !important;
      background: var(--bg-app); color: var(--color-text);
      font-family: 'Inter', sans-serif; font-size: 12px;
      box-sizing: border-box;
    }
    #gx-prod-panel * { box-sizing: border-box; outline: none; }

    #gx-prod-panel .gx-main-card {
      width: 98%; margin: 10px auto; background: var(--bg-panel);
      border: 1px solid var(--border); border-radius: 6px;
      display: flex; flex-direction: column; height: 92vh; overflow: hidden;
      box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);
    }

    /* HEADER COMPACTO */
    .gx-header { 
      padding: 8px 15px; border-bottom: 1px solid var(--border); background: #fff; 
      display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; 
      height: 40px;
    }
    .gx-header h1 { font-size: 1.1rem; margin: 0; color: #0f172a; font-weight: 700; }
    .gx-kpi-row { display: flex; gap: 15px; font-size: 0.8rem; font-weight: 600; }

    /* --- TOOLBAR HORIZONTAL --- */
    .gx-toolbar { 
      padding: 8px 15px; background: #f1f5f9; border-bottom: 1px solid var(--border); 
      display: flex; flex-direction: row; align-items: center; gap: 10px; 
      flex-shrink: 0; overflow-x: auto; flex-wrap: nowrap; white-space: nowrap;
    }
    
    .gx-input { 
      height: 28px; padding: 0 8px; border: 1px solid #cbd5e1; border-radius: 4px; 
      font-size: 0.75rem; background: #fff; color: #334155;
      border-left: 3px solid var(--c-teal);
    }
    .gx-input:focus { border-color: var(--c-teal); outline: none; }

    #gx-search { width: 200px; flex-shrink: 0; }
    #gx-filter-status { width: 130px; flex-shrink: 0; }
    #gx-filter-date { width: 130px; flex-shrink: 0; }
    #gx-filter-sort { width: 130px; flex-shrink: 0; }

    .gx-btn-refresh { 
      height: 28px; padding: 0 15px; background: var(--c-teal); color: #fff; 
      border: none; border-radius: 4px; cursor: pointer; font-weight: 600; 
      white-space: nowrap; flex-shrink: 0; margin-left: auto; 
    }

    /* --- TABLA --- */
    .gx-table-wrap { flex: 1; overflow: auto; background: #fff; }
    table { width: 100%; border-collapse: separate; border-spacing: 0; table-layout: fixed; min-width: 1850px; }

    thead th {
      position: sticky; top: 0; z-index: 20; background: #f8fafc; color: #475569;
      font-weight: 700; font-size: 0.65rem; text-transform: uppercase; padding: 6px 4px;
      border-bottom: 2px solid #cbd5e1; text-align: left; height: 28px;
    }

    tbody td {
      padding: 4px 6px; 
      border-bottom: 1px solid #f1f5f9; vertical-align: middle;
      font-size: 0.75rem; color: #334155; line-height: 1.2;
    }
    tbody tr:hover { background: #f0f9ff !important; }

    .col-trunc { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; max-width: 100%; }
    
    .date-box { font-size: 0.65rem; color: #64748b; line-height: 1.1; white-space: nowrap; }
    .date-val { font-weight: 500; color: #334155; }
    .date-highlight { color: var(--c-teal); font-weight: 700; }

    .prod-cell-flex { display: flex; justify-content: space-between; align-items: center; width: 100%; }
    .prod-info { display: flex; flex-direction: column; overflow: hidden; margin-right: 8px; }
    .prod-main { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 0.75rem; color: #0f172a; }
    .prod-sub { font-size: 0.65rem; color: #64748b; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    
    .btn-ver-mini { 
      background: #f1f5f9; color: var(--c-blue); border: 1px solid #e2e8f0; 
      font-weight: 700; font-size: 0.6rem; padding: 2px 6px; border-radius: 4px;
      cursor: pointer; white-space: nowrap; flex-shrink: 0; text-transform: uppercase;
    }
    .btn-ver-mini:hover { background: var(--c-blue); color: #fff; border-color: var(--c-blue); }

    /* TOGGLES */
    .gx-toggle {
      width: 100%; height: 20px; border: 1px solid #e2e8f0; background: #fff; border-radius: 3px;
      font-size: 0.6rem; color: #cbd5e1; font-weight: 700; text-transform: uppercase;
      cursor: pointer; display: flex; align-items: center; justify-content: center;
      transition: all 0.1s;
    }
    .gx-toggle:hover { border-color: #94a3b8; color: #64748b; }
    
    .gx-toggle.active { color: #fff !important; border-color: transparent !important; }
    .t-blue.active { background: var(--c-blue) !important; }
    .t-purple.active { background: var(--c-purple) !important; }
    .t-green.active { background: var(--c-green) !important; }
    .t-hold.active { background: var(--c-hold) !important; }
    .t-red.active { background: var(--c-red) !important; }

    .lbl-grp { display: flex; gap: 2px; }
    .lbl-btn { 
      font-size: 8px; font-weight: 700; padding: 2px 4px; border-radius: 3px;
      border: 1px solid #cbd5e1; background: #fff; color: #64748b; 
      cursor: pointer; text-transform: uppercase; min-width: 28px; text-align: center;
    }
    .lbl-btn:hover { color: var(--c-teal); border-color: var(--c-teal); background: #f0fdfa; }

    .actions-cell { display: flex; gap: 4px; justify-content: flex-end; align-items: center; }
    .icon-clean {
      width: 24px; height: 24px; border-radius: 4px; border: none; background: transparent; 
      color: #94a3b8; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center;
    }
    .icon-clean:hover { background: #f1f5f9; color: var(--c-teal); }
    .icon-clean.del:hover { background: #fef2f2; color: var(--c-red); }

    .gx-pill { display: inline-block; padding: 2px 6px; border-radius: 3px; font-size: 0.6rem; font-weight: 800; text-transform: uppercase; }
    .st-ok { background: #dcfce7; color: #15803d; }
    .st-hold { background: #fef9c3; color: #a16207; }
    .st-cancel { background: #fee2e2; color: #b91c1c; }
    .st-proc { background: #e0f2fe; color: #0369a1; }
    
    /* ETIQUETA APROBADO POR DOCTOR */
    .gx-pill-doctor { 
      background: #10b981; color: white; border-radius: 4px; 
      font-size: 0.55rem; padding: 2px 5px; font-weight: 800; 
      margin-left: 5px; text-transform: uppercase; vertical-align: middle;
      box-shadow: 0 2px 4px rgba(16, 185, 129, 0.3);
    }
    
    .gx-link-sala { color: var(--c-teal); font-weight: 700; text-decoration: none; font-size: 0.7rem; cursor: pointer; padding: 2px 6px; border-radius: 4px; background: #f0fdfa; border: 1px solid #ccfbf1; }
    .gx-link-sala:hover { background: var(--c-teal); color: #fff; }

    .gx-pg { padding: 5px 20px; background: #fff; border-top: 1px solid #e2e8f0; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; height: 40px; }
    .gx-pg-btn { background: #fff; border: 1px solid #cbd5e1; padding: 3px 10px; border-radius: 4px; font-size: 0.75rem; cursor: pointer; color: #334155; }

    .gx-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: none; align-items: center; justify-content: center; z-index: 1000; }
    .gx-card-modal { background: #fff; width: 450px; border-radius: 8px; overflow: hidden; box-shadow: 0 10px 25px rgba(0,0,0,0.2); }
    .gx-c-body { padding: 20px; max-height: 50vh; overflow-y: auto; }
    .gx-c-foot { padding: 10px 20px; background: #f8fafc; text-align: right; border-top: 1px solid #e2e8f0; }
    .gx-item-list li { border-bottom: 1px dashed #e2e8f0; padding: 8px 0; font-size: 0.8rem; list-style: none; }
  </style>

  <div class="gx-main-card">
    <div class="gx-header">
      <h1>Gxpert Producción <span style="font-weight:400;color:#64748b;font-size:0.8em">v18.0 (Detalle Clínico)</span></h1>
      <div class="gx-kpi-row">
        <span style="color:#2563eb">Proc: <span id="kpi-proc">0</span></span>
        <span style="color:#d97706">Hold: <span id="kpi-hold">0</span></span>
        <span style="color:#dc2626">Cancel: <span id="kpi-cancel">0</span></span>
      </div>
    </div>

    <div class="gx-toolbar">
      <input type="text" id="gx-search" class="gx-input" placeholder="Buscar...">
      <select id="gx-filter-status" class="gx-input"><option value="TODOS">Todos</option><option value="Produccion">Producción</option><option value="Entrega">Entrega</option><option value="Ok">OK</option><option value="Hold">Hold</option><option value="Cancel">Cancel</option></select>
      <select id="gx-filter-date" class="gx-input"><option value="ALL">Todo</option><option value="TODAY">Hoy</option><option value="7">7 días</option><option value="30">30 días</option></select>
      <select id="gx-filter-sort" class="gx-input"><option value="NEWEST">Recientes</option><option value="OLDEST">Antiguos</option></select>
      <button class="gx-btn-refresh" id="gx-btn-refresh">↻ Refrescar</button>
    </div>

    <div class="gx-table-wrap">
      <table>
        <thead>
          <tr>
            <th style="width:60px">Orden</th>
            <th style="width:80px">Estado</th>
            <th style="width:160px">Paciente / Doctor</th>
            <th style="width:80px">Fechas</th>
            <th style="width:200px">Producto</th>
            
            <th style="width:40px;text-align:center">Apr</th>
            <th style="width:40px;text-align:center">Dis</th>
            <th style="width:40px;text-align:center">DrA</th>
            <th style="width:40px;text-align:center">Prd</th>
            <th style="width:40px;text-align:center">Emp</th>
            <th style="width:40px;text-align:center">Ent</th>
            <th style="width:40px;text-align:center">OK</th>
            <th style="width:40px;text-align:center">Hld</th>
            <th style="width:40px;text-align:center">Urg</th>
            <th style="width:40px;text-align:center">Cnl</th>
            
            <th style="width:100px">Etiquetas</th>
            <th style="width:45px;text-align:center">Sala</th>
            <th style="width:40px;text-align:center">Nota</th>
            <th style="width:100px;text-align:right">Acciones</th>
          </tr>
        </thead>
        <tbody id="gx-tbody">
          <tr><td colspan="20" style="text-align:center;padding:20px;color:#94a3b8">Cargando...</td></tr>
        </tbody>
      </table>
    </div>

    <div class="gx-pg">
      <button class="gx-pg-btn" id="gx-pg-prev">Anterior</button>
      <span style="color:#64748b;font-size:0.8rem" id="gx-pg-info">1</span>
      <button class="gx-pg-btn" id="gx-pg-next">Siguiente</button>
    </div>
  </div>

  <div class="gx-overlay" id="gx-modal-items">
    <div class="gx-card-modal">
      <div style="padding:15px;border-bottom:1px solid #eee;font-weight:700">Detalle Completo</div>
      <div class="gx-c-body"><ul id="gx-items-list" class="gx-item-list" style="padding-left:0"></ul></div>
      <div class="gx-c-foot"><button class="gx-pg-btn" id="gx-items-close">Cerrar</button></div>
    </div>
  </div>

  <div class="gx-overlay" id="gx-modal-notes">
    <div class="gx-card-modal">
      <div style="padding:15px;border-bottom:1px solid #eee;font-weight:700">Nota</div>
      <div class="gx-c-body"><textarea id="gx-notes-input" style="width:100%;height:100px;border:1px solid #ccc;padding:8px;font-family:inherit"></textarea></div>
      <div class="gx-c-foot">
        <button id="gx-notes-close" style="border:none;background:none;cursor:pointer;margin-right:10px;color:#666">Cancelar</button>
        <button class="gx-btn-refresh" id="gx-notes-save" style="font-size:0.8rem;padding:0 10px;height:26px">Guardar</button>
      </div>
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>

  <script>
    (function(){
      const API_KEY = "AIzaSyClk6fa-1Pl9lGDfk4itUhxAmr67b2Y0wk";
      const PROJECT_ID = "gx-orders";
      const BASE_URL = `https://firestore.googleapis.com/v1/projects/${PROJECT_ID}/databases/(default)/documents/gx_orders`;
      const SALA_BASE_URL = "https://sistema.grad.com.mx/sala-test/";

      let allOrders = [], filteredOrders = [], currentPage = 1, currentEditingId = null;
      const itemsPerPage = 15;

      const val = (v) => v ? (v.stringValue || v.booleanValue || v.integerValue || v.timestampValue || "") : "";
      const mapVal = (m) => {
         let obj = {};
         if(m && m.mapValue && m.mapValue.fields) for(let k in m.mapValue.fields) obj[k] = val(m.mapValue.fields[k]);
         return obj;
      };
      const fmtDate = (iso) => {
         if(!iso) return "-";
         let d = new Date(iso);
         return isNaN(d) ? "-" : d.toLocaleDateString('es-MX', {day:'2-digit', month:'2-digit'});
      };

      // --- FUNCIÓN QUE EXTRAE EL DETALLE CLÍNICO PARA LA TABLA ---
      function formatDetailedItems(item) {
          // Recibe el objeto del array 'items' de Firestore
          let rawSpecs = [];
          
          // Recopilamos todas las especificaciones complejas en un array
          if(item.teeth) rawSpecs.push(`Dte: ${item.teeth}`);
          if(item.material) rawSpecs.push(`Mat: ${item.material}`);
          if(item.color) rawSpecs.push(`Color: ${item.color}`);
          if(item.piezas && item.piezas !== '1') rawSpecs.push(`Piezas: ${item.piezas}`);
          if(item.implante) rawSpecs.push(`Implante: ${item.implante}`);
          if(item.ausencia) rawSpecs.push(`Ausencia: ${item.ausencia}`);
          
          if (rawSpecs.length === 0) return { main: `${item.qty}x ${item.name}`, sub: '' };

          // Unimos las especificaciones con el separador que ya estaba en el CSS original (.prod-sub)
          let subDetails = rawSpecs.join(' • ');
          
          return { 
              main: `${item.qty}x ${item.name}`, 
              sub: subDetails 
          };
      }


      // --- URLS Y JSON ---
      const buildItemsString = (items) => {
         return items.map(i => {
            let props = [];
            if(i.teeth) props.push(`Teeth=${i.teeth}`);
            if(i.piezas) props.push(`Pieces=${i.piezas}`);
            if(i.implante) props.push(`implante=${i.implante}`);
            if(i.ausencia) props.push(`ausencia=${i.ausencia}`);
            let fullTitle = i.name || "";
            if(i.color) fullTitle += " - " + i.color;
            if(i.material) fullTitle += " / " + i.material;
            return `${fullTitle}~~${i.qty||1}~${props.join(';')}`;
         }).join('||');
      };

      const buildSalaUrl = (o) => {
         let cleanOrder = o.orderId.replace(/#/g, ''); 
         const gxData = {
            case: o.caseId, pn: o.patient, dob: o.dob, del: o.entregaFecha, time: o.entregaHora,
            sit: "", links: "", folder: o.folder, note: o.notes,
            doc: o.doctor.nombre, mail: o.doctor.email, phone: o.doctor.telefono,
            addr: o.address, order: "#" + cleanOrder, items: buildItemsString(o.items)
         };
         return `${SALA_BASE_URL}?gx=${encodeURIComponent(JSON.stringify(gxData)).replace(/%20/g, '+')}`;
      };

      async function loadOrders(){
         try {
            let res = await fetch(`${BASE_URL}?pageSize=300&key=${API_KEY}`);
            let data = await res.json();
            if(data.documents) {
               allOrders = data.documents.map(doc => {
                  let f = doc.fields;
                  let id = doc.name.split('/').pop();
                  let items = [];
                  
                  // --- LECTURA DE ARRAY DE ITEMS ---
                  if(f.items && f.items.arrayValue && f.items.arrayValue.values){
                     items = f.items.arrayValue.values.map(iv => {
                        let obj = mapVal(iv);
                        return {
                           qty: obj.qty || 1,
                           name: obj.nombre || obj.name || "Item",
                           material: obj.variante || obj.material || "",
                           color: obj.color || "",
                           teeth: obj.teeth || "",
                           implante: obj.implante || "",
                           ausencia: obj.ausencia || "",
                           piezas: obj.piezas || ""
                        };
                     });
                  }
                  
                  // --- LECTURA DE ITEM COMPLEJO DESDE GxRaw (BUSCA DETALLE PERDIDO) ---
                  if (items.length === 0 && f.gxRaw && f.gxRaw.stringValue) {
                      try {
                          const gxRawData = JSON.parse(f.gxRaw.stringValue);
                          if (gxRawData.items) {
                              // Esto intentará parsear la cadena compleja de ítems
                              const rawItemString = gxRawData.items; 
                              const simpleProducts = rawItemString.split('||');
                              
                              simpleProducts.forEach(rawItem => {
                                  const parts = rawItem.split('~~');
                                  if (parts.length >= 2) {
                                      const title = parts[0];
                                      const qtyAndProps = parts[1].split('~');
                                      const rawProps = qtyAndProps[1] || '';
                                      const parsedProps = {};
                                      
                                      rawProps.split(';').forEach(prop => {
                                          const [key, value] = prop.split('=');
                                          if (key && value) parsedProps[key.trim().toLowerCase()] = value.trim();
                                      });

                                      // Reconstruir el formato de Array
                                      items.push({
                                          qty: qtyAndProps[0] || 1,
                                          name: title,
                                          teeth: parsedProps.teeth || '',
                                          implante: parsedProps.implante || '',
                                          ausencia: parsedProps.ausencia || '',
                                          material: parsedProps.material || parsedProps.variante || '',
                                          color: parsedProps.color || parsedProps.color || '',
                                          piezas: parsedProps.pieces || ''
                                      });
                                  }
                              });
                          }
                      } catch (e) { console.error("Error parseando gxRaw.items:", e); }
                  }
                  // --- FIN LECTURA ITEMS ---
                  
                  // Resto de lógica de lectura de mapas...
                  let states = f.states ? mapVal(f.states) : {};
                  let tstamps = f.timestamps ? mapVal(f.timestamps) : {};
                  let entrega = f.entrega ? mapVal(f.entrega) : {};
                  let paciente = f.paciente ? mapVal(f.paciente) : {};
                  let docObj = f.doctor ? mapVal(f.doctor) : {};
                  
                  let doctor = {
                     nombre: val(f.doc) || docObj.nombre || "",
                     email: val(f.mail) || docObj.email || "",
                     telefono: val(f.phone) || docObj.telefono || "",
                     clinica: val(f.addr) || docObj.clinica || ""
                  };

                  let isDrApproved = (f.approved && f.approved.booleanValue) || (f.status && f.status.stringValue && f.status.stringValue.includes("Aprobado"));

                  return {
                     id: id, orderId: val(f.orderId) || id, caseId: val(f.case) || id,
                     patient: val(f.pn) || paciente.nombre || "Sin Nombre",
                     doctor: doctor,
                     address: val(f.addr) || doctor.clinica || "",
                     folder: val(f.folder), created: val(f.createdAt) || tstamps.entrada || doc.createTime,
                     entregaFecha: val(f.del) || entrega.fecha || "",
                     entregaHora: val(f.time) || entrega.hora || "",
                     items: items, states: states, notes: val(f.note) || "",
                     drApproved: isDrApproved
                  };
               });
            }
            applyFilters();
         } catch(e) { console.error(e); }
      }

      function applyFilters(){
         let term = document.getElementById('gx-search').value.toLowerCase();
         let fStatus = document.getElementById('gx-filter-status').value;
         let fDate = document.getElementById('gx-filter-date').value;
         
         filteredOrders = allOrders.filter(o => {
            let text = (o.orderId + " " + o.patient + " " + o.doctor.nombre).toLowerCase();
            if(term && !text.includes(term)) return false;
            if(fStatus !== "TODOS"){
               if(fStatus === "Hold" && !o.states.hold) return false;
               if(fStatus === "Cancel" && !o.states.cancel) return false;
               if(fStatus === "Ok" && !o.states.ok) return false;
               if(fStatus === "Produccion" && !o.states.produccion) return false;
               if(fStatus === "Entrega" && !o.states.entrega) return false;
            }
            if(fDate !== "ALL"){
               let d = new Date(o.created);
               let limit = new Date();
               if(fDate === "TODAY") limit.setHours(0,0,0,0);
               else limit.setDate(limit.getDate() - parseInt(fDate));
               if(d < limit) return false;
            }
            return true;
         });
         filteredOrders.sort((a,b) => {
            let da = new Date(a.created), db = new Date(b.created);
            return db - da; 
         });
         
         let kpi = { proc:0, draut:0, hold:0, cancel:0 };
         filteredOrders.forEach(o => {
            if(o.states.hold) kpi.hold++; else if(o.states.cancel) kpi.cancel++; else {
               if(!o.states.ok) kpi.proc++;
               if(!o.states.draut && !o.states.ok) kpi.draut++;
            }
         });
         document.getElementById('kpi-proc').innerText = kpi.proc;
         document.getElementById('kpi-hold').innerText = kpi.hold;
         document.getElementById('kpi-cancel').innerText = kpi.cancel;
         
         currentPage = 1; renderTable();
      }

      window.changePage = (dir) => { currentPage += dir; renderTable(); };

      function renderTable(){
         let tbody = document.getElementById('gx-tbody');
         tbody.innerHTML = '';
         const start = (currentPage - 1) * itemsPerPage;
         const paginatedItems = filteredOrders.slice(start, start + itemsPerPage);
         
         document.getElementById('gx-pg-info').textContent = `Pág ${currentPage}`;
         document.getElementById('gx-pg-prev').disabled = currentPage === 1;
         document.getElementById('gx-pg-next').disabled = paginatedItems.length < itemsPerPage;

         if(paginatedItems.length === 0) {
            tbody.innerHTML = `<tr><td colspan="20" style="text-align:center;padding:20px;color:#94a3b8;">Sin datos.</td></tr>`;
            return;
         }

         paginatedItems.forEach(o => {
            let tr = document.createElement('tr');
            if(o.states.hold) tr.style.backgroundColor = "#fffbeb";
            if(o.states.cancel) tr.style.backgroundColor = "#fef2f2";
            if(o.states.urgente) tr.style.backgroundColor = "#eff6ff";
            
            if(o.drApproved) tr.style.boxShadow = "inset 4px 0 0 #16a34a";

            let itemsHtml = '<span style="color:#ccc">-</span>';
            if(o.items.length > 0) {
               let i = o.items[0];
                // --- APLICAR FORMATO DETALLADO ---
                let formattedItem = formatDetailedItems(i);

               let extraText = o.items.length > 1 ? ` (+${o.items.length-1})` : '';
               itemsHtml = `
                 <div class="prod-cell-flex">
                   <div class="prod-info">
                     <div class="prod-main" title="${i.name}">${formattedItem.main}${extraText}</div>
                     <div class="prod-sub">${formattedItem.sub}</div>
                   </div>
                   <button class="btn-ver-mini" onclick="openItems('${o.id}')">Ver</button>
                 </div>`;
            }

            const tg = (key, txt, colorClass) => {
               let activeClass = o.states[key] ? `active ${colorClass}` : '';
               return `<td style="padding:4px 2px; text-align:center"><div class="gx-toggle ${activeClass}" onclick="toggleState('${o.id}', '${key}')">${txt}</div></td>`;
            };

            let patientHtml = o.patient;
            if(o.drApproved) {
                patientHtml += ' <span class="gx-pill-doctor">✅ APROBADO POR DR</span>';
            }

            tr.innerHTML = `
               <td><span style="font-family:monospace;font-weight:700">${o.orderId}</span></td>
               <td><span class="gx-pill ${o.states.hold?'st-hold':(o.states.cancel?'st-cancel':(o.states.ok?'st-ok':'st-proc'))}">${o.states.hold?'HOLD':(o.states.ok?'OK':'PROD')}</span></td>
               
               <td>
                  <div class="col-trunc" style="font-weight:600">${patientHtml}</div>
                  <div class="col-trunc" style="color:#64748b;font-size:0.7rem">Dr. ${o.doctor.nombre}</div>
               </td>
               
               <td>
                  <div class="date-box">
                     <div>In: <span class="date-val">${fmtDate(o.created).split(' ')[0]}</span></div>
                     <div>Out: <span class="date-val date-highlight">${o.entregaFecha||'-'}</span></div>
                  </div>
               </td>
               
               <td>${itemsHtml}</td>
               
               ${tg('aprobado','APR','t-blue')} ${tg('diseno','DIS','t-blue')} ${tg('draut','DRA','t-blue')} ${tg('produccion','PRD','t-blue')} 
               ${tg('empaque','EMP','t-purple')} ${tg('entrega','ENT','t-purple')} ${tg('ok','OK','t-green')} ${tg('hold','HLD','t-hold')} 
               ${tg('urgente','URG','t-red')} ${tg('cancel','CNL','t-red')}

               <td>
                  <div class="lbl-grp">
                     <button class="lbl-btn" onclick="printLabel('${o.id}','prod')">PRD</button>
                     <button class="lbl-btn" onclick="printLabel('${o.id}','box')">EMP</button>
                     <button class="lbl-btn" onclick="printLabel('${o.id}','acuse')">ACU</button>
                  </div>
               </td>
               
               <td style="text-align:center"><a onclick="openSala('${o.id}')" class="gx-link-sala">Sala</a></td>
               <td style="text-align:center"><button class="icon-clean" onclick="openNotes('${o.id}')" style="margin:0 auto;${o.notes?'color:#d97706':''}">✎</button></td>
               
               <td style="text-align:right">
                  <div class="actions-cell">
                     <button class="icon-clean" onclick="sendMail('${o.id}')">✉</button>
                     <button class="icon-clean" onclick="sendWa('${o.id}')">✆</button>
                     <button class="icon-clean del" onclick="deleteOrder('${o.id}')">✕</button>
                  </div>
               </td>
            `;
            tbody.appendChild(tr);
         });
      }

      window.openSala = (id) => {
         let o = allOrders.find(x => x.id === id);
         let url = buildSalaUrl(o);
         window.open(url, '_blank');
      };

      /* --- IMPRESIÓN REPARADA --- */
      window.printLabel = (id, type) => {
         let o = allOrders.find(x => x.id === id);
         let w = window.open("","","width=500,height=600");
         let html = "", style = "";

         if(type === 'prod') {
            style = "@page { size: 100mm 150mm; margin: 0; } body { margin: 0; font-family: sans-serif; } ul {padding-left:15px; margin:0;} li {margin-bottom:4px;}";
            let itemsFull = o.items.map(i => {
               let d = []; if(i.color) d.push(`Col:${i.color}`); if(i.teeth) d.push(`Dtes:${i.teeth}`); if(i.implante) d.push(`Imp:${i.implante}`);
               return `<li style="font-size:11px;"><strong>${i.qty}x ${i.name}</strong> (${i.material}) ${d.join(' ')}</li>`;
            }).join('');
            html = `<div style="width:100mm;height:150mm;padding:5mm;box-sizing:border-box;"><div style="text-align:center;margin-bottom:10px;"><svg id="bc"></svg></div><div style="border-bottom:2px solid #000;padding-bottom:5px;margin-bottom:10px;"><h1 style="margin:0;font-size:22px;">#${o.orderId.replace('#','')}</h1><div style="font-size:10px;">Ent: ${fmtDate(o.created)}</div></div><div style="font-size:12px;margin-bottom:10px;line-height:1.5;"><div><strong>PACIENTE:</strong> ${o.patient}</div><div><strong>DOCTOR:</strong> ${o.doctor.nombre}</div><div><strong>ENTREGA:</strong> <b>${o.entregaFecha || "PENDIENTE"}</b></div></div>${o.notes ? `<div style="border:1px dashed #000;padding:5px;font-size:11px;margin-bottom:10px;"><strong>NOTA:</strong> ${o.notes}</div>` : ''}<div style="flex:1;border-top:1px solid #000;padding-top:5px;"><ul>${itemsFull}</ul></div></div>`;
         } else if (type === 'box') {
            style = "@page { size: 102mm 38mm; margin: 0; } body { margin: 0; font-family: sans-serif; }";
            html = `<div style="width:102mm;height:38mm;padding:3mm;box-sizing:border-box;display:flex;justify-content:space-between;align-items:center;"><div style="line-height:1.2;"><strong style="font-size:14px;">GXPERT LAB</strong><br><span style="font-size:11px">${o.patient.substring(0,25)}</span><br><span style="font-size:10px">Dr. ${o.doctor.nombre.substring(0,25)}</span></div><div style="text-align:right"><h2 style="margin:0;font-size:24px;">#${o.orderId.replace('#','')}</h2></div></div>`;
         } else {
            style = "@page { size: 4in 6in; margin: 0; } body { margin: 0; font-family: sans-serif; }";
            let itemsAcuse = o.items.map(i => `${i.qty}x ${i.name}`).join(', ');
            html = `<div style="width:4in;height:6in;padding:0.5in;box-sizing:border-box;"><h2 style="text-align:center;margin:0">ACUSE RECIBO</h2><h1 style="text-align:center;margin:10px 0;font-size:32px;">#${o.orderId.replace('#','')}</h1><div style="line-height:1.6;font-size:12px;"><p><strong>Doctor:</strong> ${o.doctor.nombre}</p><p><strong>Paciente:</strong> ${o.patient}</p></div><hr><div style="font-size:11px;min-height:50px;"><strong>Items:</strong> ${itemsAcuse}</div><div style="margin-top:2in;border-top:1px solid #000;text-align:center;padding-top:5px;">Firma de Recibido</div></div>`;
         }

         w.document.write(`<html><head><style>${style}</style></head><body>${html}<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"><\/script><script>if(document.getElementById('bc')) JsBarcode('#bc','${o.orderId.replace('#','')}',{height:35,displayValue:true,fontSize:14,margin:0}); window.print();<\/script></body></html>`);
         w.document.close();
      };

      // --- CORRECCIÓN CRÍTICA DE BORRADO ---
      window.deleteOrder = async (id) => {
         if(prompt("Código seguridad borrar (1235):") !== "1235") return;
         allOrders = allOrders.filter(o => o.id !== id); applyFilters();
         
         let safeId = encodeURIComponent(id);
         
         await fetch(`${BASE_URL}/${safeId}?key=${API_KEY}`, { method: 'DELETE' });
      };

      // --- CORRECCIÓN CRÍTICA DE ESTADOS ---
      window.toggleState = async (id, key) => {
         let o = allOrders.find(x => x.id === id);
         let newState = !o.states[key];
         if((key === 'aprobado' || key === 'cancel') && newState) {
            if(prompt("Clave Supervisor (9095):") !== "9095") return;
         }
         o.states[key] = newState; renderTable();
         let body = { fields: { states: { mapValue: { fields: {} } } } };
         for(let k in o.states) body.fields.states.mapValue.fields[k] = { booleanValue: !!o.states[k] };
         
         let safeId = encodeURIComponent(id);
         await fetch(`${BASE_URL}/${safeId}?updateMask.fieldPaths=states&key=${API_KEY}`, { method: 'PATCH', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(body) });
      };

      window.openItems = (id) => {
         let o = allOrders.find(x => x.id === id);
         document.getElementById('gx-items-list').innerHTML = o.items.map(i => {
            let d = []; if(i.material) d.push("Mat: "+i.material); if(i.color) d.push("Col: "+i.color); if(i.teeth) d.push("Dtes: "+i.teeth); if(i.implante) d.push("Imp: "+i.implante);
            return `<li style="list-style:none;border-bottom:1px dashed #eee;padding:5px 0"><b>${i.qty}x ${i.name}</b><br><span style="font-size:0.8rem;color:#666">${d.join(' | ')}</span></li>`;
         }).join('');
         document.getElementById('gx-modal-items').style.display = 'flex';
      };
      
      document.getElementById('gx-items-close').onclick = () => document.getElementById('gx-modal-items').style.display = 'none';

      window.openNotes = (id) => {
         currentEditingId = id;
         document.getElementById('gx-notes-input').value = allOrders.find(x => x.id === id).notes;
         document.getElementById('gx-modal-notes').style.display = 'flex';
      };
      
      document.getElementById('gx-notes-save').onclick = async () => {
         let txt = document.getElementById('gx-notes-input').value;
         let o = allOrders.find(x => x.id === currentEditingId);
         if(o) {
            o.notes = txt;
            let safeId = encodeURIComponent(currentEditingId); // Fix: Codificar ID
            await fetch(`${BASE_URL}/${safeId}?updateMask.fieldPaths=note&key=${API_KEY}`, { method: 'PATCH', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({fields:{note:{stringValue:txt}}}) });
            document.getElementById('gx-modal-notes').style.display = 'none';
            renderTable();
         }
      };
      document.getElementById('gx-notes-close').onclick = () => document.getElementById('gx-modal-notes').style.display = 'none';

      window.sendMail = (id) => { 
         let o = allOrders.find(x=>x.id===id); 
         if(!o.doctor.email) { alert("Sin correo registrado"); return; }
         let link = buildSalaUrl(o);
         let body = `Hola Dr(a). ${o.doctor.nombre},\n\nLe enviamos la orden #${o.orderId.replace('#','')} del paciente ${o.patient}.\nPuede ver el detalle aquí: ${link}`;
         window.open(`mailto:${o.doctor.email}?subject=Orden #${o.orderId.replace('#','')}&body=${encodeURIComponent(body)}`);
      };
      
      window.sendWa = (id) => { 
         let o = allOrders.find(x=>x.id===id); 
         if(!o.doctor.telefono) { alert("Sin WhatsApp registrado"); return; }
         let num = o.doctor.telefono.replace(/\D/g,'');
         let link = buildSalaUrl(o);
         let msg = `Hola Dr(a). ${o.doctor.nombre}, orden #${o.orderId.replace('#','')} del paciente ${o.patient}. Ver detalle: ${link}`;
         window.open(`https://wa.me/52${num}?text=${encodeURIComponent(msg)}`, '_blank');
      };

      document.getElementById('gx-btn-refresh').onclick = loadOrders;
      document.getElementById('gx-search').onkeyup = applyFilters;
      document.getElementById('gx-filter-status').onchange = applyFilters;
      document.getElementById('gx-filter-date').onchange = applyFilters;
      document.getElementById('gx-pg-prev').onclick = () => changePage(-1);
      document.getElementById('gx-pg-next').onclick = () => changePage(1);

      loadOrders();
    })();
  </script>
</section>
ORDEN SHOPIFY

#1140

PENDING

04/Dec/2025 – 05:40 AM

Cliente

Luis perez
📞 +526863768951

Dirección de Envío

Avenida sebastian Lerdo de Tejada 1210
Mexicali, Baja California
CP: 21100
Mexico
Producto Cant. Total
Carilla 1 $450.00
TOTAL PEDIDO: $450.00 MXN
👇 CONTROL INTERNO: Usa los campos de abajo para actualizar el estado y la guía.
Scroll al inicio