index

Pasted Nov 10, 2026, 5:49 PM1 file2,015 lines

Scan to open on your phone

Point a camera at the QR code - it opens this exact paste, no app needed.

https://codepastes.com/zzgs86nj40skweyz
index
<!DOCTYPE html>
<html lang="es">
<head>
  <base target="_top">
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Chechare Wash | Administración</title>
  <style>
    *{box-sizing:border-box}
    :root{--navy:#101827;--bg:#f3f5f9;--text:#172033;--muted:#64748b;--line:#e2e8f0;--green:#166534}
    body{margin:0;font-family:Arial,sans-serif;background:var(--bg);color:var(--text)}
    .app{display:flex;height:100vh;min-height:100vh;overflow:hidden}
    .sidebar{width:250px;flex-shrink:0;height:100vh;position:sticky;top:0;overflow-y:auto;background:#101827;color:#fff;padding:22px 14px;display:flex;flex-direction:column;gap:22px}
    .brand{padding:0 10px}.brand h1{font-size:19px;margin:0;letter-spacing:.4px}.brand p{font-size:12px;color:#cbd5e1;margin:7px 0 0}
    .nav-label{font-size:10px;color:#94a3b8;font-weight:bold;letter-spacing:1.2px;padding:0 10px;margin-bottom:7px}
    .nav-btn,.subnav-btn{width:100%;display:flex;align-items:center;gap:10px;text-align:left;border:0;background:transparent;color:#dbe4f0;padding:11px 12px;border-radius:8px;font-size:14px;font-weight:500}
    .nav-btn:hover,.subnav-btn:hover{background:#253247;color:white}
    .nav-btn.active,.subnav-btn.active{background:#dcece5;color:#183e32;font-weight:bold}
    .nav-btn .chevron{margin-left:auto}
    .submenu{padding:5px 0 7px 13px}
    .subnav-btn{font-size:13px;padding:10px 12px}
    .sidebar-foot{margin-top:auto;padding:12px 10px;color:#94a3b8;font-size:11px;line-height:1.5}
    .main-area{flex:1;min-width:0;height:100vh;overflow-y:auto;overflow-x:hidden}
    header{background:#fff;padding:20px 30px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;gap:16px}
    header h2{margin:0;font-size:21px}header p{margin:5px 0 0;color:var(--muted);font-size:13px}
    .status-pill{background:#dcfce7;color:#166534;padding:6px 10px;border-radius:20px;font-size:11px;font-weight:bold}
    main{max-width:1400px;margin:0 auto;padding:28px}
    .welcome{margin-bottom:22px}.welcome h2{margin:0 0 8px;font-size:25px}.welcome p,.description{color:var(--muted);line-height:1.5}
    .welcome p{margin:0}.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:16px;margin-bottom:22px}
    .summary,.card{background:#fff;padding:22px;border-radius:14px;border:1px solid var(--line);box-shadow:0 3px 12px rgba(15,23,42,.04);margin-bottom:20px}
    .summary{margin:0}.summary h3{margin:0 0 12px;font-size:13px;color:var(--muted);font-weight:600}.number{font-size:30px;font-weight:bold}
    .summary-link{display:block;width:100%;text-align:left;color:inherit;background:white;border:1px solid var(--line);border-radius:14px;padding:22px}
    .summary-link:hover{border-color:#94b8a9;transform:translateY(-1px)}
    .summary-link h3{margin:0 0 12px;font-size:13px;color:var(--muted)}.summary-link .number{font-size:30px}
    .card h2{margin:0 0 10px;font-size:21px}.description{margin-top:0}
    .form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px}
    label{display:block;font-size:14px;font-weight:600}
    input,select,textarea{display:block;width:100%;padding:12px;margin-top:6px;border:1px solid #cbd5e1;border-radius:8px;background:#fff;font:inherit;color:var(--text)}
    textarea{min-height:85px;resize:vertical}
    button{border:0;border-radius:8px;padding:12px 18px;cursor:pointer;font-size:14px;font-weight:600}
    button:disabled{opacity:.6;cursor:wait}
    .primary{background:var(--navy);color:white}.secondary{background:#e2e8f0;color:var(--text)}.entry{background:#166534;color:white}.consume{background:#b91c1c;color:white}.attended{background:#166534;color:white}.pending-btn{background:#b45309;color:white}.incentive-actions{display:flex;gap:8px;align-items:center}.incentive-choice{padding:8px 16px;border:1px solid #cbd5e1;background:#fff;color:#334155;min-width:68px}.incentive-choice.is-si{background:#166534;border-color:#166534;color:#fff}.incentive-choice.is-no{background:#b91c1c;border-color:#b91c1c;color:#fff}.incentive-choice:focus-visible{outline:3px solid #93c5fd;outline-offset:2px}.incentive-bulk{display:flex;gap:10px;flex-wrap:wrap;margin-top:16px}.incentive-na{color:#64748b;font-size:13px;font-weight:600}
    .actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:20px}
    .table-wrap{overflow-x:auto;margin-top:18px}
    table{width:100%;border-collapse:collapse;text-align:left;font-size:14px}
    th,td{padding:12px;border-bottom:1px solid var(--line);white-space:nowrap}th{background:#f1f5f9}
    .notes-table td.note-content{white-space:pre-wrap;overflow-wrap:anywhere;min-width:220px;max-width:400px}
    .notes-table td.note-actions{white-space:normal;min-width:170px}
    .state-label{display:inline-block;padding:5px 9px;border-radius:20px;font-size:12px;font-weight:bold}.pending{color:#92400e;background:#fef3c7}.done{color:#166534;background:#dcfce7}
    .stock-low{color:#b91c1c;font-weight:bold}.stock-ok{color:#166534;font-weight:bold}
    .machine-status{display:inline-block;padding:5px 9px;border-radius:20px;font-size:12px;font-weight:bold;background:#e2e8f0;color:#334155}
    .machine-status.operativa{background:#dcfce7;color:#166534}.machine-status.respaldo{background:#dbeafe;color:#1d4ed8}
    .machine-status.danada{background:#fee2e2;color:#991b1b}.machine-status.reparacion{background:#fef3c7;color:#92400e}
    .document-status{display:inline-block;padding:5px 8px;border-radius:16px;font-size:12px;font-weight:bold;background:#dcfce7;color:#166534}.document-status.faltante{background:#fef3c7;color:#92400e}.document-status.vencido{background:#fee2e2;color:#991b1b}.document-status.no-aplica{background:#e2e8f0;color:#475569}
    .message{margin-bottom:0;overflow-wrap:anywhere;line-height:1.5}.help{font-size:13px;color:var(--muted);margin-top:8px}
    footer{text-align:center;color:var(--muted);font-size:12px;padding:20px 15px 30px}
    .view-section[hidden],.submenu[hidden]{display:none!important}
    .mobile-menu{display:none;background:var(--navy);color:#fff}
    @media(max-width:800px){.sidebar{width:220px}main{padding:20px}header{padding:18px 20px}}
    @media(max-width:650px){.app{display:block;height:auto;min-height:100vh;overflow:visible}.sidebar{width:100%;height:auto;position:relative;top:auto;overflow:visible;padding:14px;gap:14px}.main-area{height:auto;overflow:visible}.brand{display:flex;align-items:center;justify-content:space-between}.sidebar nav{display:grid;grid-template-columns:1fr 1fr;gap:3px}.nav-label{grid-column:1/-1}.submenu{grid-column:1/-1;padding-left:10px}.sidebar-foot{display:none}header{padding:16px 18px}main{padding:16px}.card,.summary{padding:17px}.summary-link{padding:17px}th,td{padding:9px}}
  </style>
</head>
<body>
<div class="app">
  <aside class="sidebar">
    <div class="brand"><h1>CHECHARE WASH</h1><p>Panel de administración</p></div>
    <nav aria-label="Menú principal">
      <div class="nav-label">MENÚ PRINCIPAL</div>
      <button class="nav-btn active" type="button" data-view="inicio"><span>⌂</span> Inicio</button>
      <button class="nav-btn" id="toggleInventario" type="button" aria-expanded="true"><span>▣</span> Inventario <span class="chevron" id="chevronInventario">⌄</span></button>
      <div class="submenu" id="submenuInventario">
        <button class="subnav-btn" type="button" data-view="productos">Registrar producto</button>
        <button class="subnav-btn" type="button" data-view="consulta">Consultar productos</button>
        <button class="subnav-btn" type="button" data-view="movimientos">Movimientos de inventario</button>
        <button class="subnav-btn" type="button" data-view="entrada">Registrar entrada</button>
        <button class="subnav-btn" type="button" data-view="consumo">Registrar consumo</button>
      </div>
      <button class="nav-btn" id="togglePersonal" type="button" aria-expanded="true"><span>♙</span> Personal / Lavadores <span class="chevron" id="chevronPersonal">⌄</span></button>
      <div class="submenu" id="submenuPersonal">
        <button class="subnav-btn" type="button" data-personal-panel="registrar">Registrar lavador</button>
        <button class="subnav-btn" type="button" data-personal-panel="consultar">Consultar lavadores</button>
        <button class="subnav-btn" type="button" data-personal-panel="documentos">Documentación</button>
        <button class="subnav-btn" type="button" data-personal-panel="estadoDocumentacion">Estado de documentación</button>
        <button class="subnav-btn" type="button" data-personal-panel="semanal">Registro semanal</button>
        <button class="subnav-btn" type="button" data-personal-panel="incentivo">Incentivo</button>
        <button class="subnav-btn" type="button" data-personal-panel="bonos">Bonos semestrales</button>
        <button class="subnav-btn" type="button" data-personal-panel="historial">Historial individual</button>
      </div>
      <button class="nav-btn" id="toggleMaquinas" type="button" aria-expanded="true"><span>⚙</span> Máquinas <span class="chevron" id="chevronMaquinas">⌄</span></button>
      <div class="submenu" id="submenuMaquinas">
        <button class="subnav-btn" type="button" data-maquina-panel="consulta">Consultar máquinas</button>
        <button class="subnav-btn" type="button" data-maquina-panel="registrar">Registrar máquina</button>
        <button class="subnav-btn" type="button" data-maquina-panel="asignar">Asignar máquina</button>
        <button class="subnav-btn" type="button" data-maquina-panel="devolver">Registrar devolución</button>
        <button class="subnav-btn" type="button" data-maquina-panel="estado">Cambiar estado</button>
        <button class="subnav-btn" type="button" data-maquina-panel="historial">Historial de movimientos</button>
      </div>
      <button class="nav-btn" type="button" data-view="enviarNota"><span>✉</span> Enviar nota</button>
      <button class="nav-btn" type="button" data-view="notas"><span>☷</span> Bandeja de notas</button>
      <button class="nav-btn" type="button" data-view="configuracion"><span>⚙</span> Configuración</button>
    </nav>
    <div class="sidebar-foot">Chechare Wash<br>Sistema interno de gestión</div>
  </aside>

  <div class="main-area">
    <header><div><h2 id="pageTitle">Inicio</h2><p id="pageSubtitle">Resumen general del inventario y las solicitudes.</p></div><span class="status-pill">SISTEMA INTERNO</span></header>
    <main>
      <section class="view-section" id="view-inicio">
        <div class="welcome"><h2>Resumen general</h2><p>Consulta rápidamente el estado del inventario, las notas y el personal.</p></div>
        <div class="cards">
          <button class="summary-link" type="button" data-view="consulta"><h3>Productos registrados</h3><div class="number" id="totalProductos">—</div></button>
          <button class="summary-link" type="button" data-view="consulta"><h3>Stock bajo</h3><div class="number" id="totalBajos">—</div></button>
          <button class="summary-link" type="button" data-view="notas"><h3>Notas pendientes</h3><div class="number" id="totalNotasDashboard">—</div></button>
          <button class="summary-link" type="button" data-view="personal"><h3>Lavadores activos</h3><div class="number" id="totalLavadoresDashboard">—</div></button>
          <button class="summary-link" type="button" data-alerta="faltantes"><h3>Documentos faltantes</h3><div class="number" id="totalFaltantesDashboard">—</div></button>
          <button class="summary-link" type="button" data-alerta="porVencer"><h3>Por vencer en 30 días</h3><div class="number" id="totalPorVencerDashboard">—</div></button>
          <button class="summary-link" type="button" data-alerta="vencidos"><h3>Documentos vencidos</h3><div class="number" id="totalVencidosDashboard">—</div></button>
        </div>
        <section class="card">
          <h2>Estado de las máquinas</h2>
          <p class="description">Resumen del equipamiento y de las máquinas asignadas actualmente.</p>
          <div class="cards">
            <button class="summary-link" type="button" data-view="maquinas"><h3>Total de máquinas</h3><div class="number" id="totalMaquinasDashboard">—</div></button>
            <button class="summary-link" type="button" data-view="maquinas"><h3>Operativas</h3><div class="number" id="maquinasOperativasDashboard">—</div></button>
            <button class="summary-link" type="button" data-view="maquinas"><h3>De respaldo</h3><div class="number" id="maquinasRespaldoDashboard">—</div></button>
            <button class="summary-link" type="button" data-view="maquinas"><h3>Con problemas</h3><div class="number" id="maquinasProblemasDashboard">—</div></button>
          </div>
          <div class="table-wrap">
            <table><thead><tr><th>Código</th><th>Máquina</th><th>Estado</th><th>Lavador asignado</th></tr></thead>
              <tbody id="tablaResumenMaquinasDashboard"><tr><td colspan="4">Cargando máquinas...</td></tr></tbody>
            </table>
          </div>
          <div class="actions"><button class="secondary" type="button" data-view="maquinas">Abrir módulo de máquinas</button></div>
          <p id="mensajeResumenMaquinasDashboard" class="message" role="status" aria-live="polite"></p>
        </section>
        <section class="card">
          <h2>Documentos faltantes por tipo</h2>
          <p class="description">Cantidad de lavadores activos a los que les falta cada documento obligatorio.</p>
          <div class="table-wrap"><table><thead><tr><th>Documento</th><th>Faltantes</th></tr></thead><tbody id="tablaResumenDocumentos"><tr><td colspan="2">Cargando resumen...</td></tr></tbody></table></div>
          <p id="mensajeResumenPersonal" class="message" role="status" aria-live="polite"></p>
        </section>
        <section class="card">
          <h2>Accesos rápidos</h2><p class="description">Entra directamente a las tareas más habituales.</p>
          <div class="actions">
            <button class="primary" type="button" data-view="productos">Registrar producto</button>
            <button class="entry" type="button" data-view="entrada">Registrar entrada</button>
            <button class="consume" type="button" data-view="consumo">Registrar consumo</button>
            <button class="secondary" type="button" data-view="notas">Ver notas pendientes</button>
          </div>
        </section>
      </section>

      <section class="view-section" id="view-productos" hidden>
        <section class="card">
          <h2>Registrar producto</h2><p class="description">Define la unidad en la que controlarás este producto. Todas las entradas y consumos posteriores usarán esa misma unidad.</p>
          <form id="formProducto">
            <div class="form-grid">
              <label>Código del producto *<input name="codigo" required maxlength="50" placeholder="Ej.: PROD-001"></label>
              <label>Nombre del producto *<input name="nombre" required maxlength="150" placeholder="Ej.: Shampoo para autos"></label>
              <label>Categoría *<input name="categoria" required maxlength="100" placeholder="Ej.: Limpieza"></label>
              <label>Unidad de control *
                <select name="unidad" id="unidadProducto" required>
                  <option value="">Seleccionar unidad</option><option value="Unidad">Unidad</option><option value="Litro">Litro</option><option value="Mililitro">Mililitro</option><option value="Kilogramo">Kilogramo</option><option value="Gramo">Gramo</option><option value="Metro">Metro</option><option value="Centímetro">Centímetro</option><option value="Par">Par</option><option value="Rollo">Rollo</option><option value="Pack">Pack</option><option value="Caja">Caja</option><option value="Bolsa">Bolsa</option><option value="Bidón">Bidón</option><option value="Otro">Otra unidad...</option>
                </select>
              </label>
              <label id="otraUnidadContenedor" hidden>Escribe la unidad *<input id="otraUnidad" maxlength="30" placeholder="Ej.: Paquete"></label>
              <label>Stock inicial<input name="stockInicial" type="number" min="0" step="any" value="0" required></label>
              <label>Stock mínimo *<input name="stockMinimo" type="number" min="0" step="any" value="0" required></label>
            </div>
            <p class="help">Ejemplo: shampoo en litros, paños en unidades y bolsas en unidades o bolsas, según cómo quieras controlarlas.</p>
            <div class="actions"><button id="btnRegistrar" class="primary" type="submit">Registrar producto</button></div>
            <p id="mensajeProducto" class="message" role="status" aria-live="polite"></p>
          </form>
        </section>
      </section>

      <section class="view-section" id="view-consulta" hidden>
        <section class="card">
          <h2>Consultar productos</h2><p class="description">Consulta las existencias actuales y las alertas de reposición.</p>
          <div class="actions" style="align-items:end">
            <button id="btnActualizar" type="button" class="primary" onclick="cargarInventario()">Actualizar inventario</button>
            <label style="min-width:220px;flex:1">Buscar producto<input id="buscarProductoConsulta" type="search" autocomplete="off" placeholder="Código, nombre, categoría o unidad"></label>
            <label style="min-width:190px;flex:1">Ordenar por
              <select id="ordenProductos">
                <option value="codigo">Código</option>
                <option value="producto" selected>Nombre del producto</option>
                <option value="categoria">Categoría</option>
                <option value="unidad">Unidad</option>
                <option value="stockActual">Stock actual</option>
                <option value="stockMinimo">Stock mínimo</option>
                <option value="estado">Estado del stock</option>
              </select>
            </label>
            <label style="min-width:160px">Orden
              <select id="direccionOrdenProductos">
                <option value="asc" selected>Ascendente (A-Z / menor a mayor)</option>
                <option value="desc">Descendente (Z-A / mayor a menor)</option>
              </select>
            </label>
          </div>
          <div class="table-wrap"><table><thead><tr><th>Código</th><th>Producto</th><th>Categoría</th><th>Unidad</th><th>Stock actual</th><th>Stock mínimo</th><th>Estado</th></tr></thead><tbody id="tablaProductos"><tr><td colspan="7">Cargando inventario...</td></tr></tbody></table></div>
          <p id="estadoInventario" class="message" role="status" aria-live="polite"></p>
        </section>
      </section>

      <section class="view-section" id="view-movimientos" hidden>
        <section class="card">
          <h2>Movimientos de inventario</h2><p class="description">Selecciona el producto y escribe directamente la cantidad que entra o que consumes. No se realizan conversiones por cajas, bolsas o bidones.</p>
          <form id="formMovimiento">
            <div class="form-grid">
              <label>Buscar producto *<input id="buscarProductoMovimiento" type="search" autocomplete="off" placeholder="Código, nombre, categoría o unidad"></label>
              <label>Producto *<select id="productoMovimiento" required><option value="">Cargando productos...</option></select></label>
              <label>Tipo de movimiento *<select id="tipoMovimiento" required><option value="Entrada">Entrada: sumar stock</option><option value="Consumo">Consumo: descontar stock</option></select></label>
              <label id="etiquetaCantidad">Cantidad que entra *<input id="cantidadMovimiento" type="number" min="0.000001" step="any" required></label>
            </div>
            <p id="ayudaUnidad" class="help">Selecciona un producto para ver su unidad y stock actual.</p>
            <label style="margin-top:16px">Motivo *<input id="motivoMovimiento" maxlength="250" required placeholder="Ej.: Compra, reposición o uso en un vehículo"></label>
            <div class="actions"><button id="btnMovimiento" class="entry" type="submit">Registrar entrada</button></div>
            <p id="mensajeMovimiento" class="message" role="status" aria-live="polite"></p>
          </form>
        </section>

        <section class="card">
          <h2>Historial de movimientos</h2>
          <p class="description">Consulta todas las entradas y salidas registradas, con el stock antes y después de cada operación.</p>
          <div class="form-grid">
            <label>Filtrar por tipo
              <select id="filtroTipoMovimiento">
                <option value="Todos">Todos los movimientos</option>
                <option value="Entrada">Entradas</option>
                <option value="Consumo">Consumos</option>
                <option value="Stock inicial">Stock inicial</option>
              </select>
            </label>
            <label>Buscar producto
              <input id="buscarHistorialMovimiento" type="search" placeholder="Código o nombre del producto">
            </label>
            <div class="actions" style="align-items:end;margin-top:0">
              <button id="btnActualizarHistorialMovimientos" type="button" class="secondary">Actualizar historial</button>
            </div>
          </div>
          <p id="mensajeHistorialMovimientos" class="message" role="status" aria-live="polite"></p>
          <div class="table-wrap">
            <table>
              <thead><tr><th>Fecha y hora</th><th>Tipo</th><th>Código</th><th>Producto</th><th>Cantidad</th><th>Stock anterior</th><th>Stock resultante</th><th>Motivo</th></tr></thead>
              <tbody id="tablaHistorialMovimientos"><tr><td colspan="8">Cargando historial...</td></tr></tbody>
            </table>
          </div>
        </section>
      </section>

      <section class="view-section" id="view-entrada" hidden>
        <section class="card"><h2>Registrar entrada</h2><p class="description">Registra una compra o reposición para sumar existencias.</p><button type="button" class="entry" onclick="abrirMovimiento('Entrada')">Abrir formulario de entrada</button></section>
      </section>
      <section class="view-section" id="view-consumo" hidden>
        <section class="card"><h2>Registrar consumo</h2><p class="description">Registra el uso de un producto para descontarlo del stock.</p><button type="button" class="consume" onclick="abrirMovimiento('Consumo')">Abrir formulario de consumo</button></section>
      </section>

      <section class="view-section" id="view-personal" hidden>
        <div class="personal-panel" id="panel-personal-registrar">
        <section class="card">
          <h2>Registrar lavador</h2>
          <p class="description">Registra los datos de la persona y la fecha real en que comenzó a trabajar.</p>
          <form id="formLavador">
            <div class="form-grid">
              <label>Nombre completo del lavador *<input id="nombreLavador" maxlength="150" required placeholder="Nombre y apellido"></label>
              <label>Teléfono (opcional)<input id="telefonoLavador" maxlength="50" placeholder="Teléfono de contacto"></label>
              <label>Fecha de ingreso laboral *<input id="fechaIngresoLavador" type="date" required></label>
            </div>
            <div class="actions"><button class="primary" id="btnRegistrarLavador" type="submit">Registrar lavador</button><button class="secondary" id="btnActualizarLavadores" type="button">Actualizar lista</button></div>
            <p id="mensajeLavador" class="message" role="status" aria-live="polite"></p>
          </form>
        </section>
        </div>
        <div class="personal-panel" id="panel-personal-consultar" hidden>
          <section class="card">
            <h2>Consultar lavadores</h2>
            <p class="description">Consulta el personal registrado, su antigüedad y estado. Desde aquí puedes actualizar la fecha real de ingreso.</p>
            <div class="actions"><button class="primary" type="button" id="btnActualizarListaPersonal">Actualizar lista</button></div>
            <div class="table-wrap"><table><thead><tr><th>Lavador</th><th>Teléfono</th><th>Fecha de ingreso</th><th>Antigüedad</th><th>Estado</th><th>Evaluación semanal</th><th>Motivo</th><th>Acción</th></tr></thead><tbody id="tablaPersonal"><tr><td colspan="8">Cargando personal...</td></tr></tbody></table></div>
            <p id="mensajeListaPersonal" class="message" role="status" aria-live="polite"></p>
          </section>
        </div>
        <div class="personal-panel" id="panel-personal-documentos" hidden>
        <section class="card">
          <h2>Documentación del lavador</h2>
          <p class="description">Selecciona una persona para consultar sus documentos y cargar archivos o renovaciones.</p>
          <label>Lavador *
            <select id="selectorLavador">
              <option value="">Cargando lavadores...</option>
            </select>
          </label>
          <div class="actions" style="margin-top:12px">
            <button class="secondary" id="btnEliminarLavador" type="button">Desactivar lavador seleccionado</button><button class="consume" id="btnEliminarLavadorDefinitivo" type="button">Eliminar definitivamente</button>
          </div>
          <div id="panelDocumentosLavador" hidden>
            <div class="table-wrap"><table><thead><tr><th>Documento</th><th>Requisito</th><th>Estado</th><th>Emisión</th><th>Vencimiento</th><th>Archivo</th></tr></thead><tbody id="tablaDocumentosLavador"></tbody></table></div>
            <h3 style="margin-top:26px">Cargar o renovar documento</h3>
            <form id="formDocumentoLavador">
              <div class="form-grid">
                <label>Tipo de documento *<select id="tipoDocumentoLavador" required>
                  <option value="Licencia de conducir">Licencia de conducir</option>
                  <option value="Seguro">Seguro</option>
                  <option value="Certificado de antecedentes">Certificado de antecedentes</option>
                  <option value="Carnet de lavador">Carnet de lavador</option>
                </select></label>
                <label>Fecha de emisión (opcional)<input id="fechaEmisionDocumento" type="date"></label>
                <label>Fecha de vencimiento<input id="fechaVencimientoDocumento" type="date"></label>
                <label>Foto o archivo del documento *<input id="archivoDocumentoLavador" type="file" accept="image/*,.pdf" required></label>
              </div>
              <label style="margin-top:14px"><input id="documentoNoVence" type="checkbox" style="display:inline-block;width:auto;margin-right:8px">No vence</label>
              <p class="help">La carga del archivo es obligatoria, incluso cuando marcas «No vence». Formatos recomendados: imagen o PDF, hasta 6 MB.</p>
              <div class="actions"><button class="primary" id="btnGuardarDocumentoLavador" type="submit">Guardar documento</button></div>
              <p id="mensajeDocumentoLavador" class="message" role="status" aria-live="polite"></p>
            </form>
          </div>
        </section>
        <section class="card">
          <h2>Alertas de documentación</h2>
          <p class="description">El correo diario agrupado se enviará a turnoschechare@gmail.com. Incluirá documentos faltantes, vencidos y avisos de vencimiento a 30, 15 y 7 días.</p>
          <div class="actions"><button class="secondary" id="btnProbarAvisoPersonal" type="button">Ejecutar revisión ahora</button></div>
          <p id="mensajeAvisoPersonal" class="message" role="status" aria-live="polite"></p>
        </section>
        </div>
        <div class="personal-panel" id="panel-personal-estadoDocumentacion" hidden>
        <section class="card">
          <h2>Estado de documentación</h2>
          <p class="description">Consulta los cuatro documentos de cada lavador activo. Los documentos marcados como «No aplica» no se cuentan como pendientes.</p>
          <div class="form-grid">
            <label>Buscar lavador<input id="buscarEstadoDocumentacion" type="search" placeholder="Escribe el nombre del lavador"></label>
            <label>Documento
              <select id="filtroDocumentoEstado">
                <option value="Todos">Todos los documentos</option>
                <option value="Carnet de lavador">Carnet de lavador</option>
                <option value="Licencia de conducir">Licencia de conducir</option>
                <option value="Seguro">Seguro</option>
                <option value="Certificado de antecedentes">Certificado de antecedentes</option>
              </select>
            </label>
            <label>Estado
              <select id="filtroEstadoDocumento">
                <option value="Todos">Todos los estados</option>
                <option value="Faltante">Faltante</option>
                <option value="Vencido">Vencido</option>
                <option value="Completo">Completo</option>
                <option value="No aplica">No aplica</option>
                <option value="Sin datos">Sin datos</option>
              </select>
            </label>
            <div class="actions" style="align-items:end;margin-top:0"><button class="secondary" id="btnActualizarEstadoDocumentacion" type="button">Actualizar estado</button></div>
          </div>
          <p id="mensajeEstadoDocumentacion" class="message" role="status" aria-live="polite">Cargando estado de documentación...</p>
          <div class="table-wrap"><table>
            <thead><tr><th>Lavador</th><th>Carnet de lavador</th><th>Licencia de conducir</th><th>Seguro</th><th>Certificado de antecedentes</th><th>Estado general</th></tr></thead>
            <tbody id="tablaEstadoDocumentacion"><tr><td colspan="6">Cargando...</td></tr></tbody>
          </table></div>
        </section>
        </div>
        <div class="personal-panel" id="panel-personal-semanal" hidden>
          <section class="card">
            <h2>Registro semanal de incentivos</h2>
            <p class="description">Selecciona el lunes de la semana que deseas registrar. Los lavadores excluidos por función y quienes aún no habían ingresado aparecerán automáticamente como No aplica.</p>
            <div class="form-grid">
              <label>Semana (lunes) *<input type="date" id="fechaSemanaIncentivo"></label>
            </div>
            <p id="rangoSemanaIncentivo" class="help">Selecciona una fecha para consultar la semana.</p>
            <div class="incentive-bulk">
              <button class="entry" type="button" id="btnTodosIncentivoSi">Marcar todos Sí</button>
              <button class="consume" type="button" id="btnTodosIncentivoNo">Marcar todos No</button>
            </div>
            <div class="table-wrap"><table><thead><tr><th>Lavador</th><th>Resultado semanal</th></tr></thead><tbody id="tablaIncentivosSemana"><tr><td colspan="2">Selecciona una semana.</td></tr></tbody></table></div>
            <div class="actions"><button class="primary" type="button" id="btnGuardarIncentivos">Guardar semana</button></div>
            <p id="mensajeIncentivos" class="message" role="status" aria-live="polite"></p>
          </section>
        </div>
        <div class="personal-panel" id="panel-personal-incentivo" hidden>
          <section class="card">
            <h2>Estado de incentivos</h2>
            <p class="description">Consulta quién tiene derecho al 60 % durante una semana determinada. El estado se obtiene del resultado Sí o No de la semana inmediatamente anterior.</p>
            <div class="form-grid">
              <label>Semana en la que aplica el incentivo *<input type="date" id="fechaConsultaIncentivo"></label>
              <label>Buscar lavador<input type="search" id="buscarLavadorIncentivo" placeholder="Escribe parte del nombre, por ejemplo: ale"></label>
              <label>Mostrar<select id="filtroEstadoIncentivo"><option value="Todos">Todos</option><option value="Sí">Tiene incentivo (60 %)</option><option value="No">No tiene incentivo (50 %)</option><option value="Sin registrar">Sin resultado registrado</option><option value="No aplica">No aplica</option></select></label>
            </div>
            <p id="rangoConsultaIncentivo" class="help">Selecciona la semana que deseas consultar.</p>
            <p id="resumenConsultaIncentivo" class="help"></p>
            <div class="table-wrap"><table><thead><tr><th>Lavador</th><th>Estado del incentivo</th><th>Resultado registrado</th></tr></thead><tbody id="tablaEstadoIncentivo"><tr><td colspan="3">Selecciona una semana.</td></tr></tbody></table></div>
            <p id="mensajeConsultaIncentivo" class="message" role="status" aria-live="polite"></p>
          </section>
        </div>
        <div class="personal-panel" id="panel-personal-bonos" hidden>
          <section class="card">
            <h2>Bonos semestrales</h2>
            <p class="description">Cada lavador tiene su propio período de seis meses. Se cuentan las semanas completas registradas con «No». Con 9 o más faltas, el bono no corresponde. Los lavadores configurados como No aplica quedan excluidos de esta evaluación.</p>
            <div class="actions"><button class="primary" type="button" id="btnActualizarBonos">Actualizar evaluación</button></div>
            <div class="table-wrap"><table><thead><tr><th>Lavador</th><th>Antigüedad</th><th>Inicio del ciclo</th><th>Evaluación</th><th>Último pago</th><th>Semanas sin incentivo</th><th>Estado</th><th>Acción</th></tr></thead><tbody id="tablaBonos"><tr><td colspan="8">Cargando bonos...</td></tr></tbody></table></div>
            <p id="mensajeBonos" class="message" role="status" aria-live="polite"></p>
          </section>
        </div>
        <div class="personal-panel" id="panel-personal-historial" hidden>
          <section class="card">
            <h2>Historial individual</h2>
            <p class="description">Consulta los registros semanales y los movimientos de bonos de una persona.</p>
            <label>Lavador *<select id="selectorHistorialPersonal"><option value="">Seleccionar lavador</option></select></label>
            <div class="table-wrap"><table><thead><tr><th>Semana desde</th><th>Semana hasta</th><th>Incentivo</th><th>Registrado</th></tr></thead><tbody id="tablaHistorialIncentivos"><tr><td colspan="4">Selecciona un lavador.</td></tr></tbody></table></div>
            <div class="table-wrap"><table><thead><tr><th>Movimiento</th><th>Fecha</th><th>Período</th><th>Faltas</th><th>Estado</th><th>Observaciones</th></tr></thead><tbody id="tablaHistorialBonos"><tr><td colspan="6">Selecciona un lavador.</td></tr></tbody></table></div>
            <p id="mensajeHistorialPersonal" class="message" role="status" aria-live="polite"></p>
          </section>
        </div>
      </section>

      <section class="view-section" id="view-enviarNota" hidden>
        <section class="card"><h2>Enviar una nota al administrador</h2><p class="description">Comunica una incidencia o una solicitud relacionada con el inventario.</p>
          <form id="formSolicitud"><div class="form-grid">
            <label>Tu nombre<input id="nombreSolicitud" maxlength="100" placeholder="Nombre"></label>
            <label>Categoría *<select id="categoriaSolicitud" required><option value="">Seleccionar categoría</option><option value="Stock">Stock</option><option value="Información">Información</option><option value="Máquina">Máquina</option><option value="Personal">Personal</option><option value="Incentivos / bonos">Incentivos / bonos</option><option value="Otro">Otro</option></select></label>
            <label>Nota *<textarea id="textoSolicitud" maxlength="2000" required placeholder="Escribe tu mensaje..."></textarea></label>
          </div><div class="actions"><button id="btnSolicitud" class="secondary" type="submit">Enviar nota</button></div><p id="mensajeSolicitud" class="message" role="status" aria-live="polite"></p></form>
        </section>
      </section>

      <section class="view-section" id="view-notas" hidden>
        <section class="card"><h2>Bandeja de notas</h2><p class="description">Consulta las notas recibidas y marca cada una como pendiente o atendida.</p>
          <div class="cards">
            <article class="summary"><h3>Notas pendientes</h3><div class="number" id="totalNotasPendientes">—</div></article>
            <article class="summary"><h3>Notas atendidas</h3><div class="number" id="totalNotasAtendidas">—</div></article>
          </div>
          <div class="actions"><button id="btnActualizarNotas" type="button" class="primary" onclick="cargarSolicitudes()">Actualizar bandeja</button>
            <label style="min-width:200px;flex:1">Filtrar notas<select id="filtroNotas"><option value="Todas">Todas las notas</option><option value="Pendiente">Solo pendientes</option><option value="Atendida">Solo atendidas</option></select></label>
          </div>
          <p id="mensajeBandeja" class="message" role="status" aria-live="polite"></p>
          <div class="table-wrap"><table class="notes-table"><thead><tr><th>Fecha</th><th>Persona</th><th>Categoría</th><th>Nota</th><th>Estado</th><th>Acciones</th></tr></thead><tbody id="tablaSolicitudes"><tr><td colspan="5">Cargando notas...</td></tr></tbody></table></div>
        </section>
      </section>

      <section class="view-section" id="view-maquinas" hidden>
        <div class="machine-panel" id="panel-maquinas-consulta">
        <div class="welcome"><h2>Gestión de máquinas</h2><p>Registra equipos, controla las asignaciones, devoluciones, estados y movimientos históricos.</p></div>
        <div class="cards">
          <article class="summary"><h3>Total de máquinas</h3><div class="number" id="totalMaquinasModulo">—</div></article>
          <article class="summary"><h3>Operativas</h3><div class="number" id="maquinasOperativasModulo">—</div></article>
          <article class="summary"><h3>De respaldo</h3><div class="number" id="maquinasRespaldoModulo">—</div></article>
          <article class="summary"><h3>Dañadas / En reparación</h3><div class="number" id="maquinasProblemasModulo">—</div></article>
        </div>

        <section class="card">
          <h2>Máquinas registradas</h2>
          <p class="description">Consulta el estado actual y quién tiene cada equipo asignado.</p>
          <div class="actions"><button class="primary" id="btnActualizarMaquinas" type="button">Actualizar lista</button></div>
          <div class="table-wrap"><table><thead><tr><th>Código</th><th>Tipo</th><th>Estado</th><th>Lavador asignado</th><th>Fecha de registro</th></tr></thead><tbody id="tablaMaquinas"><tr><td colspan="5">Cargando máquinas...</td></tr></tbody></table></div>
          <p id="mensajeListaMaquinas" class="message" role="status" aria-live="polite"></p>
        </section>

        </div>

        <div class="machine-panel" id="panel-maquinas-registrar" hidden>
        <section class="card">
          <h2>Registrar máquina</h2><p class="description">El equipo nuevo se registra inicialmente como De respaldo.</p>
          <form id="formRegistrarMaquina">
            <div class="form-grid">
              <label>Código único *<input name="codigo" required maxlength="30" placeholder="Ej.: H2 o A3"></label>
              <label>Tipo de máquina *<select name="tipo" required><option value="">Seleccionar tipo</option><option value="Hidrolavadora">Hidrolavadora</option><option value="Aspiradora">Aspiradora</option><option value="Otra">Otra</option></select></label>
            </div>
            <label style="margin-top:14px">Observaciones<textarea name="observaciones" maxlength="1000" placeholder="Detalles del equipo, accesorios o condición al registrarlo"></textarea></label>
            <div class="actions"><button class="primary" id="btnRegistrarMaquina" type="submit">Registrar máquina</button></div>
            <p id="mensajeRegistrarMaquina" class="message" role="status" aria-live="polite"></p>
          </form>
        </section>

        </div>

        <div class="machine-panel" id="panel-maquinas-asignar" hidden>
        <section class="card">
          <h2>Asignar máquina a un lavador</h2><p class="description">Solo aparecen lavadores activos y máquinas disponibles para asignación.</p>
          <form id="formAsignarMaquina">
            <div class="form-grid">
              <label>Máquina disponible *<select id="maquinaParaAsignar" required><option value="">Cargando máquinas...</option></select></label>
              <label>Lavador activo *<select id="lavadorParaAsignar" required><option value="">Cargando lavadores...</option></select></label>
            </div>
            <label style="margin-top:14px">Observaciones<textarea id="observacionesAsignacion" maxlength="1000" placeholder="Condición de entrega, accesorios, etc."></textarea></label>
            <div class="actions"><button class="primary" id="btnAsignarMaquina" type="submit">Registrar asignación</button></div>
            <p id="mensajeAsignarMaquina" class="message" role="status" aria-live="polite"></p>
          </form>
        </section>

        </div>

        <div class="machine-panel" id="panel-maquinas-devolver" hidden>
        <section class="card">
          <h2>Registrar devolución</h2><p class="description">Selecciona una máquina que tenga una asignación activa. Si está dañada o en reparación, conservará ese estado al devolverla.</p>
          <form id="formDevolverMaquina">
            <div class="form-grid">
              <label>Máquina asignada *<select id="maquinaParaDevolver" required><option value="">Cargando máquinas...</option></select></label>
              <label>Motivo de devolución *<input id="motivoDevolucionMaquina" required maxlength="250" placeholder="Ej.: Cambio de equipo o fin de asignación"></label>
            </div>
            <label style="margin-top:14px">Observaciones<textarea id="observacionesDevolucionMaquina" maxlength="1000"></textarea></label>
            <div class="actions"><button class="secondary" id="btnDevolverMaquina" type="submit">Registrar devolución</button></div>
            <p id="mensajeDevolverMaquina" class="message" role="status" aria-live="polite"></p>
          </form>
        </section>

        </div>

        <div class="machine-panel" id="panel-maquinas-estado" hidden>
        <section class="card">
          <h2>Cambiar estado de una máquina</h2><p class="description">Registra daños, envíos a reparación o la confirmación de un equipo reparado.</p>
          <form id="formEstadoMaquina">
            <div class="form-grid">
              <label>Máquina *<select id="maquinaParaEstado" required><option value="">Seleccionar máquina</option></select></label>
              <label>Nuevo estado *<select id="nuevoEstadoMaquina" required><option value="">Seleccionar estado</option><option value="Dañada">Dañada</option><option value="En reparación">En reparación</option><option value="Operativa">Operativa</option><option value="De respaldo">De respaldo</option></select></label>
              <label>Motivo *<input id="motivoEstadoMaquina" required maxlength="250" placeholder="Ej.: Equipo dañado / Reparación finalizada"></label>
            </div>
            <label style="margin-top:14px">Observaciones<textarea id="observacionesEstadoMaquina" maxlength="1000"></textarea></label>
            <div class="actions"><button class="secondary" id="btnCambiarEstadoMaquina" type="submit">Actualizar estado</button></div>
            <p id="mensajeEstadoMaquina" class="message" role="status" aria-live="polite"></p>
          </form>
        </section>

        </div>

        <div class="machine-panel" id="panel-maquinas-historial" hidden>
        <section class="card">
          <h2>Historial de movimientos</h2><p class="description">Consulta los movimientos por código de máquina o por nombre de lavador. Puedes dejar ambos filtros vacíos para ver todo el historial.</p>
          <form id="formHistorialMaquinas">
            <div class="form-grid">
              <label>Código de máquina<select id="filtroCodigoHistorialMaquinas"><option value="">Todas las máquinas</option></select></label>
              <label>Nombre del lavador<select id="filtroLavadorHistorialMaquinas"><option value="">Todos los lavadores</option></select></label>
            </div>
            <div class="actions"><button class="primary" id="btnBuscarHistorialMaquinas" type="submit">Consultar historial</button></div>
          </form>
          <p id="mensajeHistorialMaquinas" class="message" role="status" aria-live="polite"></p>
          <div class="table-wrap"><table><thead><tr><th>Fecha y hora</th><th>Código</th><th>Movimiento</th><th>Lavador</th><th>Estado anterior</th><th>Estado nuevo</th><th>Motivo</th><th>Observaciones</th></tr></thead><tbody id="tablaHistorialMaquinas"><tr><td colspan="8">Aplica un filtro o consulta todo el historial.</td></tr></tbody></table></div>
        </section>
        </div>
      </section>

      <section class="view-section" id="view-configuracion" hidden>
        <section class="card"><h2>Configuración</h2><p class="description">En esta etapa, las categorías se escriben al registrar cada producto. Más adelante podemos crear aquí un administrador de categorías y otros ajustes.</p></section>
      </section>

      <section class="view-section" id="view-detalleDocumentos" hidden>
        <section class="card">
          <h2 id="tituloDetalleDocumentos">Detalle de documentación</h2>
          <p id="descripcionDetalleDocumentos" class="description">Consulta el estado de los documentos del personal.</p>
          <div class="actions"><button class="secondary" type="button" data-view="inicio">Volver al resumen</button><button class="primary" type="button" id="btnActualizarDetalleDocumentos">Actualizar lista</button></div>
          <p id="mensajeDetalleDocumentos" class="message" role="status" aria-live="polite"></p>
          <div class="table-wrap"><table><thead><tr><th>Lavador</th><th>Documento</th><th>Estado</th><th>Fecha de vencimiento</th><th>Acción</th></tr></thead><tbody id="tablaDetalleDocumentos"><tr><td colspan="5">Selecciona una tarjeta del resumen.</td></tr></tbody></table></div>
        </section>
      </section>
    </main>
    <footer>Chechare Wash · Sistema interno de gestión</footer>
  </div>
</div>

<script>
  let productosCache = [];
  let solicitudesCache = [];
  let tipoAlertaDocumentosActual = 'faltantes';
  let lavadorParaSeleccionar = '';

  const titulosVista = {
    inicio:['Inicio','Resumen general del inventario y las solicitudes.'],
    productos:['Registrar producto','Da de alta un producto y define su stock mínimo.'],
    consulta:['Consultar productos','Revisa existencias y alertas de reposición.'],
    movimientos:['Movimientos de inventario','Registra entradas o consumos y consulta el historial de movimientos.'],
    entrada:['Registrar entrada','Suma existencias al inventario.'],
    consumo:['Registrar consumo','Descuenta existencias del inventario.'],
    personal:['Personal / Lavadores','Administra el personal, los incentivos y los bonos semestrales.'],
    maquinas:['Máquinas','Registra equipos, asignaciones, devoluciones y estados.'],
    enviarNota:['Enviar nota','Envía una incidencia o solicitud al administrador.'],
    notas:['Bandeja de notas','Consulta las notas pendientes y atendidas.'],
    configuracion:['Configuración','Ajustes generales del sistema.'],
    detalleDocumentos:['Detalle de documentación','Consulta los documentos faltantes y los vencimientos del personal.']
  };

  function mostrarError(error) {
    return error && error.message ? error.message : 'Ocurrió un error desconocido.';
  }

  function mostrarVista(nombre) {
    const vista = titulosVista[nombre] ? nombre : 'inicio';
    document.querySelectorAll('.view-section').forEach(function(seccion) {
      seccion.hidden = seccion.id !== 'view-' + vista;
    });
    document.querySelectorAll('[data-view]').forEach(function(boton) {
      boton.classList.toggle('active', boton.dataset.view === vista);
    });
    document.getElementById('toggleMaquinas').classList.toggle('active', vista === 'maquinas');
    document.getElementById('togglePersonal').classList.toggle('active', vista === 'personal');
    const info = titulosVista[vista];
    document.getElementById('pageTitle').textContent = info[0];
    document.getElementById('pageSubtitle').textContent = info[1];
    if (['productos','consulta','movimientos','entrada','consumo'].includes(vista)) {
      document.getElementById('submenuInventario').hidden = false;
      document.getElementById('toggleInventario').setAttribute('aria-expanded','true');
      document.getElementById('chevronInventario').textContent = '⌄';
    }
    if (vista === 'consulta') cargarInventario();
    if (vista === 'movimientos') { cargarInventario(); cargarHistorialMovimientos(); }
    if (vista === 'personal') {
      document.getElementById('submenuPersonal').hidden = false;
      document.getElementById('togglePersonal').setAttribute('aria-expanded','true');
      document.getElementById('chevronPersonal').textContent = '⌄';
      abrirPanelPersonal('registrar');
      cargarLavadores();
    }
    if (vista === 'maquinas') {
      document.getElementById('submenuMaquinas').hidden = false;
      document.getElementById('toggleMaquinas').setAttribute('aria-expanded','true');
      document.getElementById('chevronMaquinas').textContent = '⌄';
      abrirPanelMaquinas('consulta');
      cargarModuloMaquinas();
    }
    if (vista === 'inicio') cargarResumenMaquinas();
    if (vista === 'inicio') cargarResumenPersonal();
    if (vista === 'notas') cargarSolicitudes();
    if (vista === 'detalleDocumentos') cargarDetalleDocumentos(tipoAlertaDocumentosActual);
    if (vista === 'entrada') prepararTipoMovimiento('Entrada');
    if (vista === 'consumo') prepararTipoMovimiento('Consumo');
  }

  document.querySelectorAll('[data-view]').forEach(function(boton) {
    boton.addEventListener('click', function() { mostrarVista(boton.dataset.view); });
  });

  document.querySelectorAll('[data-alerta]').forEach(function(boton) {
    boton.addEventListener('click', function() { abrirDetalleDocumentos(boton.dataset.alerta); });
  });
  document.getElementById('btnActualizarDetalleDocumentos').addEventListener('click', function() {
    cargarDetalleDocumentos(tipoAlertaDocumentosActual);
  });

  document.getElementById('toggleInventario').addEventListener('click', function() {
    const submenu = document.getElementById('submenuInventario');
    submenu.hidden = !submenu.hidden;
    this.setAttribute('aria-expanded', String(!submenu.hidden));
    document.getElementById('chevronInventario').textContent = submenu.hidden ? '›' : '⌄';
  });

  document.getElementById('toggleMaquinas').addEventListener('click', function() {
    const submenu = document.getElementById('submenuMaquinas');
    submenu.hidden = !submenu.hidden;
    this.setAttribute('aria-expanded', String(!submenu.hidden));
    document.getElementById('chevronMaquinas').textContent = submenu.hidden ? '›' : '⌄';
    if (!submenu.hidden && !document.querySelector('#view-maquinas:not([hidden])')) mostrarVista('maquinas');
  });

  document.getElementById('togglePersonal').addEventListener('click', function() {
    const submenu = document.getElementById('submenuPersonal');
    submenu.hidden = !submenu.hidden;
    this.setAttribute('aria-expanded', String(!submenu.hidden));
    document.getElementById('chevronPersonal').textContent = submenu.hidden ? '›' : '⌄';
    if (!submenu.hidden && document.getElementById('view-personal').hidden) mostrarVista('personal');
  });

  document.querySelectorAll('[data-personal-panel]').forEach(function(boton) {
    boton.addEventListener('click', function() {
      if (document.getElementById('view-personal').hidden) mostrarVista('personal');
      abrirPanelPersonal(boton.dataset.personalPanel);
    });
  });

  function abrirPanelPersonal(nombre) {
    const panel = ['registrar','consultar','documentos','estadoDocumentacion','semanal','incentivo','bonos','historial'].includes(nombre) ? nombre : 'registrar';
    document.querySelectorAll('.personal-panel').forEach(function(elemento) {
      elemento.hidden = elemento.id !== 'panel-personal-' + panel;
    });
    document.querySelectorAll('[data-personal-panel]').forEach(function(boton) {
      boton.classList.toggle('active', boton.dataset.personalPanel === panel);
    });
    const titulos = {
      registrar:['Registrar lavador','Registra los datos personales y la fecha real de ingreso laboral.'],
      consultar:['Consultar lavadores','Consulta el personal registrado y su antigüedad.'],
      documentos:['Documentación','Carga y consulta los documentos obligatorios de cada lavador.'],
      estadoDocumentacion:['Estado de documentación','Consulta y filtra los documentos faltantes, vencidos y completos de cada lavador.'],
      semanal:['Registro semanal','Registra el resultado Sí o No de cada semana completa.'],
      incentivo:['Estado de incentivos','Consulta quién tiene o no tiene incentivo durante una semana.'],
      bonos:['Bonos semestrales','Consulta la evaluación y registra los pagos individuales.'],
      historial:['Historial individual','Consulta los incentivos semanales y movimientos de bonos por persona.']
    };
    document.getElementById('pageTitle').textContent = titulos[panel][0];
    document.getElementById('pageSubtitle').textContent = titulos[panel][1];
    if (panel === 'consultar') cargarTablaPersonal();
    if (panel === 'estadoDocumentacion') cargarEstadoDocumentacion();
    if (panel === 'semanal' && document.getElementById('fechaSemanaIncentivo').value) cargarSemanaIncentivos();
    if (panel === 'incentivo') prepararConsultaEstadoIncentivo();
    if (panel === 'bonos') cargarBonosSemestrales();
    if (panel === 'historial') cargarSelectorHistorialPersonal();
  }

  document.querySelectorAll('[data-maquina-panel]').forEach(function(boton) {
    boton.addEventListener('click', function() {
      if (document.getElementById('view-maquinas').hidden) mostrarVista('maquinas');
      abrirPanelMaquinas(boton.dataset.maquinaPanel);
    });
  });

  function abrirPanelMaquinas(nombre) {
    const panel = ['consulta','registrar','asignar','devolver','estado','historial'].includes(nombre) ? nombre : 'consulta';
    document.querySelectorAll('.machine-panel').forEach(function(elemento) {
      elemento.hidden = elemento.id !== 'panel-maquinas-' + panel;
    });
    document.querySelectorAll('[data-maquina-panel]').forEach(function(boton) {
      boton.classList.toggle('active', boton.dataset.maquinaPanel === panel);
    });
    const titulos = {
      consulta:['Consultar máquinas','Consulta el estado actual y quién tiene cada equipo asignado.'],
      registrar:['Registrar máquina','Registra un equipo nuevo en el inventario de máquinas.'],
      asignar:['Asignar máquina','Asigna una máquina disponible a un lavador activo.'],
      devolver:['Registrar devolución','Registra la devolución de una máquina asignada.'],
      estado:['Cambiar estado','Registra daños, reparaciones y cambios de disponibilidad.'],
      historial:['Historial de movimientos','Consulta el historial por máquina o por lavador.']
    };
    document.getElementById('pageTitle').textContent = titulos[panel][0];
    document.getElementById('pageSubtitle').textContent = titulos[panel][1];
  }

  function abrirMovimiento(tipo) {
    prepararTipoMovimiento(tipo);
    mostrarVista('movimientos');
  }
  function prepararTipoMovimiento(tipo) {
    document.getElementById('tipoMovimiento').value = tipo;
    actualizarTipoMovimiento();
  }

  function cargarInventario() {
    const tabla = document.getElementById('tablaProductos');
    const estado = document.getElementById('estadoInventario');
    const boton = document.getElementById('btnActualizar');

    if (tabla) {
      tabla.replaceChildren();
      const fila = document.createElement('tr');
      const celda = document.createElement('td');
      celda.colSpan = 7;
      celda.textContent = 'Cargando inventario...';
      fila.appendChild(celda);
      tabla.appendChild(fila);
    }
    if (boton) boton.disabled = true;
    if (estado) estado.textContent = '';

    google.script.run.withSuccessHandler(function(productos) {
      productosCache = (productos || []).filter(function(p) {
        return String(p.activo).toLowerCase() !== 'false';
      });
      if (boton) boton.disabled = false;

      const bajos = productosCache.filter(function(p) {
        return (Number(p.stockActual) || 0) <= (Number(p.stockMinimo) || 0);
      }).length;

      document.getElementById('totalProductos').textContent = productosCache.length;
      document.getElementById('totalBajos').textContent = bajos;
      if (estado) estado.textContent = 'Productos activos: ' + productosCache.length;

      renderizarInventario();
      actualizarSelectorProductos();
    }).withFailureHandler(function(error) {
      if (boton) boton.disabled = false;
      if (tabla) {
        tabla.replaceChildren();
        const tr = document.createElement('tr');
        const td = document.createElement('td');
        td.colSpan = 7;
        td.textContent = 'Error al cargar: ' + mostrarError(error);
        tr.appendChild(td);
        tabla.appendChild(tr);
      }
      if (estado) estado.textContent = 'No se pudo consultar el inventario.';
    }).obtenerProductos();
  }

  function renderizarInventario() {
    const tabla = document.getElementById('tablaProductos');
    if (!tabla) return;

    tabla.replaceChildren();

    if (productosCache.length === 0) {
      const tr = document.createElement('tr');
      const td = document.createElement('td');
      td.colSpan = 7;
      td.textContent = 'No hay productos activos registrados.';
      tr.appendChild(td);
      tabla.appendChild(tr);
      return;
    }

    const busqueda = normalizarBusquedaInventario(document.getElementById('buscarProductoConsulta').value);
    const productosFiltrados = productosCache.filter(function(p) {
      const texto = normalizarBusquedaInventario([p.codigo, p.producto, p.categoria, p.unidad].join(' '));
      return texto.includes(busqueda);
    });
    if (!productosFiltrados.length) {
      const tr = document.createElement('tr'); const td = document.createElement('td'); td.colSpan = 7;
      td.textContent = 'No se encontraron productos que coincidan con la búsqueda.'; tr.appendChild(td); tabla.appendChild(tr); return;
    }

    const campo = document.getElementById('ordenProductos').value;
    const direccion = document.getElementById('direccionOrdenProductos').value;
    const factor = direccion === 'desc' ? -1 : 1;

    const productosOrdenados = productosFiltrados.slice().sort(function(a, b) {
      let valorA;
      let valorB;

      if (campo === 'stockActual' || campo === 'stockMinimo') {
        valorA = Number(a[campo]) || 0;
        valorB = Number(b[campo]) || 0;
        return (valorA - valorB) * factor;
      }

      if (campo === 'estado') {
        valorA = (Number(a.stockActual) || 0) <= (Number(a.stockMinimo) || 0) ? 'Reponer' : 'Disponible';
        valorB = (Number(b.stockActual) || 0) <= (Number(b.stockMinimo) || 0) ? 'Reponer' : 'Disponible';
      } else {
        valorA = String(a[campo] || '');
        valorB = String(b[campo] || '');
      }

      return valorA.localeCompare(valorB, 'es', { numeric: true, sensitivity: 'base' }) * factor;
    });

    productosOrdenados.forEach(function(p) {
      const stock = Number(p.stockActual) || 0;
      const minimo = Number(p.stockMinimo) || 0;
      const bajo = stock <= minimo;
      const tr = document.createElement('tr');

      [p.codigo, p.producto, p.categoria, p.unidad, p.stockActual, p.stockMinimo, bajo ? 'Reponer' : 'Disponible'].forEach(function(valor, indice) {
        const td = document.createElement('td');
        td.textContent = valor == null ? '' : String(valor);
        if (indice === 6) td.className = bajo ? 'stock-low' : 'stock-ok';
        tr.appendChild(td);
      });

      tabla.appendChild(tr);
    });
  }

  function normalizarBusquedaInventario(valor) {
    return String(valor || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim();
  }

  function actualizarSelectorProductos() {
    const selector = document.getElementById('productoMovimiento');
    const buscador = document.getElementById('buscarProductoMovimiento');
    const anterior = selector.value;
    const termino = normalizarBusquedaInventario(buscador ? buscador.value : '');
    const coincidencias = productosCache.filter(function(p) {
      const texto = normalizarBusquedaInventario([p.codigo, p.producto, p.categoria, p.unidad].join(' '));
      return !termino || texto.includes(termino);
    });
    selector.replaceChildren();
    const inicial = document.createElement('option');
    inicial.value = '';
    inicial.textContent = coincidencias.length
      ? 'Selecciona un producto (' + coincidencias.length + ' coincidencia(s))'
      : 'No se encontraron productos';
    selector.appendChild(inicial);
    coincidencias.forEach(function(p) {
      const opcion = document.createElement('option');
      opcion.value = p.codigo;
      opcion.textContent = p.codigo + ' — ' + p.producto + ' (' + p.stockActual + ' ' + p.unidad + ')';
      selector.appendChild(opcion);
    });
    if (coincidencias.some(p => p.codigo === anterior)) selector.value = anterior;
    else if (coincidencias.length === 1) selector.value = coincidencias[0].codigo;
    actualizarAyudaProducto();
  }

  function actualizarAyudaProducto() {
    const codigo = document.getElementById('productoMovimiento').value;
    const p = productosCache.find(item => item.codigo === codigo);
    const ayuda = document.getElementById('ayudaUnidad');
    const cantidad = document.getElementById('cantidadMovimiento');
    if (!p) { ayuda.textContent = 'Selecciona un producto para ver su unidad y stock actual.'; cantidad.removeAttribute('max'); return; }
    ayuda.textContent = 'Unidad: ' + p.unidad + '. Stock actual: ' + p.stockActual + ' ' + p.unidad + '.';
    actualizarTipoMovimiento();
  }

  function actualizarTipoMovimiento() {
    const tipo = document.getElementById('tipoMovimiento').value;
    document.getElementById('etiquetaCantidad').firstChild.textContent =
      tipo === 'Entrada' ? 'Cantidad que entra *' : 'Cantidad que consumes *';
    const boton = document.getElementById('btnMovimiento');
    boton.textContent = tipo === 'Entrada' ? 'Registrar entrada' : 'Registrar consumo';
    boton.className = tipo === 'Entrada' ? 'entry' : 'consume';
    actualizarAyudaProductoSinRecursion();
  }

  function actualizarAyudaProductoSinRecursion() {
    const codigo = document.getElementById('productoMovimiento').value;
    const p = productosCache.find(item => item.codigo === codigo);
    const cantidad = document.getElementById('cantidadMovimiento');
    if (p && document.getElementById('tipoMovimiento').value === 'Consumo') cantidad.max = Math.max(0, Number(p.stockActual) || 0);
    else cantidad.removeAttribute('max');
  }

  document.getElementById('buscarProductoConsulta').addEventListener('input', renderizarInventario);
  document.getElementById('buscarProductoMovimiento').addEventListener('input', actualizarSelectorProductos);
  document.getElementById('productoMovimiento').addEventListener('change', actualizarAyudaProducto);
  document.getElementById('tipoMovimiento').addEventListener('change', actualizarTipoMovimiento);
  document.getElementById('unidadProducto').addEventListener('change', function() {
    const otra = this.value === 'Otro';
    document.getElementById('otraUnidadContenedor').hidden = !otra;
    document.getElementById('otraUnidad').required = otra;
  });

  document.getElementById('formProducto').addEventListener('submit', function(event) {
    event.preventDefault();
    const formulario = event.currentTarget;
    const boton = document.getElementById('btnRegistrar');
    const mensaje = document.getElementById('mensajeProducto');
    const datos = Object.fromEntries(new FormData(formulario).entries());
    if (datos.unidad === 'Otro') datos.unidad = document.getElementById('otraUnidad').value.trim();
    datos.stockInicial = Number(datos.stockInicial); datos.stockMinimo = Number(datos.stockMinimo);
    boton.disabled = true; mensaje.textContent = 'Registrando producto...';
    google.script.run.withSuccessHandler(function(resultado) {
      mensaje.textContent = resultado.mensaje || 'Producto registrado correctamente.';
      formulario.reset(); document.getElementById('otraUnidadContenedor').hidden = true;
      document.getElementById('otraUnidad').required = false; boton.disabled = false; cargarInventario();
    }).withFailureHandler(function(error) {
      mensaje.textContent = 'No se pudo registrar: ' + mostrarError(error); boton.disabled = false;
    }).registrarProducto(datos);
  });

  document.getElementById('formMovimiento').addEventListener('submit', function(event) {
    event.preventDefault();
    const codigo = document.getElementById('productoMovimiento').value;
    const cantidad = Number(document.getElementById('cantidadMovimiento').value);
    const tipo = document.getElementById('tipoMovimiento').value;
    const motivo = document.getElementById('motivoMovimiento').value.trim();
    const boton = document.getElementById('btnMovimiento');
    const mensaje = document.getElementById('mensajeMovimiento');
    if (!codigo || !Number.isFinite(cantidad) || cantidad <= 0 || !motivo) {
      mensaje.textContent = 'Selecciona un producto e indica una cantidad y un motivo válidos.'; return;
    }
    boton.disabled = true; mensaje.textContent = 'Registrando movimiento...';
    const runner = google.script.run.withSuccessHandler(function(resultado) {
      mensaje.textContent = (resultado.mensaje || 'Movimiento registrado.') + ' Stock resultante: ' + resultado.stockResultante + ' ' + resultado.unidad + '.';
      document.getElementById('cantidadMovimiento').value = ''; document.getElementById('motivoMovimiento').value = '';
      boton.disabled = false; cargarInventario(); cargarHistorialMovimientos();
    }).withFailureHandler(function(error) {
      mensaje.textContent = 'No se pudo registrar: ' + mostrarError(error); boton.disabled = false;
    });
    if (tipo === 'Entrada') runner.registrarEntrada(codigo,cantidad,motivo);
    else runner.registrarConsumo(codigo,cantidad,motivo);
  });

  document.getElementById('formSolicitud').addEventListener('submit', function(event) {
    event.preventDefault();
    const boton = document.getElementById('btnSolicitud');
    const mensaje = document.getElementById('mensajeSolicitud');
    const nombre = document.getElementById('nombreSolicitud').value.trim();
    const nota = document.getElementById('textoSolicitud').value.trim();
    const categoria = document.getElementById('categoriaSolicitud').value;
    if (!categoria) { mensaje.textContent = 'Selecciona una categoría.'; return; }
    boton.disabled = true; mensaje.textContent = 'Enviando nota...';
    google.script.run.withSuccessHandler(function(resultado) {
      mensaje.textContent = resultado.mensaje || 'Nota enviada.';
      document.getElementById('textoSolicitud').value = ''; document.getElementById('categoriaSolicitud').value = ''; boton.disabled = false; cargarSolicitudes();
    }).withFailureHandler(function(error) {
      mensaje.textContent = 'No se pudo enviar: ' + mostrarError(error); boton.disabled = false;
    }).registrarSolicitud(nota,nombre,categoria);
  });

  function cargarSolicitudes() {
    const tabla = document.getElementById('tablaSolicitudes');
    const boton = document.getElementById('btnActualizarNotas');
    const mensaje = document.getElementById('mensajeBandeja');
    if (boton) boton.disabled = true;
    if (mensaje) mensaje.textContent = 'Cargando notas...';
    google.script.run.withSuccessHandler(function(notas) {
      solicitudesCache = Array.isArray(notas) ? notas : [];
      if (boton) boton.disabled = false;
      if (mensaje) mensaje.textContent = '';
      renderizarSolicitudes();
    }).withFailureHandler(function(error) {
      if (boton) boton.disabled = false;
      if (mensaje) mensaje.textContent = 'No se pudieron cargar las notas: ' + mostrarError(error);
      tabla.replaceChildren(); const tr = document.createElement('tr'); const td = document.createElement('td');
      td.colSpan = 6; td.textContent = 'Error al consultar la bandeja.'; tr.appendChild(td); tabla.appendChild(tr);
    }).obtenerSolicitudes();
  }

  function renderizarSolicitudes() {
    const tabla = document.getElementById('tablaSolicitudes');
    const filtro = document.getElementById('filtroNotas').value;
    tabla.replaceChildren();
    const pendientes = solicitudesCache.filter(nota => nota.estado !== 'Atendida').length;
    const atendidas = solicitudesCache.filter(nota => nota.estado === 'Atendida').length;
    document.getElementById('totalNotasPendientes').textContent = pendientes;
    document.getElementById('totalNotasAtendidas').textContent = atendidas;
    document.getElementById('totalNotasDashboard').textContent = pendientes;
    const notas = solicitudesCache.filter(nota => filtro === 'Todas' || nota.estado === filtro);
    if (notas.length === 0) {
      const tr = document.createElement('tr'); const td = document.createElement('td'); td.colSpan = 6;
      td.textContent = solicitudesCache.length === 0 ? 'Todavía no hay notas registradas.' : 'No hay notas para el filtro seleccionado.';
      tr.appendChild(td); tabla.appendChild(tr); return;
    }
    notas.forEach(function(nota) {
      const tr = document.createElement('tr');
      const tdFecha = document.createElement('td'); tdFecha.textContent = nota.fecha || ''; tr.appendChild(tdFecha);
      const tdPersona = document.createElement('td'); tdPersona.textContent = nota.persona || 'Sin identificar'; tr.appendChild(tdPersona);
      const tdCategoria = document.createElement('td'); tdCategoria.textContent = nota.categoria || '—'; tr.appendChild(tdCategoria);
      const tdNota = document.createElement('td'); tdNota.className = 'note-content'; tdNota.textContent = nota.nota || ''; tr.appendChild(tdNota);
      const tdEstado = document.createElement('td'); const etiqueta = document.createElement('span');
      const estaAtendida = nota.estado === 'Atendida'; etiqueta.className = 'state-label ' + (estaAtendida ? 'done' : 'pending');
      etiqueta.textContent = estaAtendida ? 'Atendida' : 'Pendiente'; tdEstado.appendChild(etiqueta); tr.appendChild(tdEstado);
      const tdAcciones = document.createElement('td'); tdAcciones.className = 'note-actions';
      const boton = document.createElement('button'); boton.type = 'button'; boton.className = estaAtendida ? 'pending-btn' : 'attended';
      boton.textContent = estaAtendida ? 'Marcar pendiente' : 'Marcar atendida';
      boton.addEventListener('click', function() { cambiarEstadoSolicitud(nota.fila,estaAtendida?'Pendiente':'Atendida',boton); });
      tdAcciones.appendChild(boton); tr.appendChild(tdAcciones); tabla.appendChild(tr);
    });
  }

  function cambiarEstadoSolicitud(fila,nuevoEstado,boton) {
    const mensaje = document.getElementById('mensajeBandeja');
    boton.disabled = true; mensaje.textContent = 'Actualizando estado de la nota...';
    google.script.run.withSuccessHandler(function(resultado) {
      mensaje.textContent = resultado.mensaje || 'Estado actualizado.'; cargarSolicitudes();
    }).withFailureHandler(function(error) {
      boton.disabled = false; mensaje.textContent = 'No se pudo actualizar el estado: ' + mostrarError(error);
    }).actualizarEstadoSolicitud(fila,nuevoEstado);
  }

  document.getElementById('filtroNotas').addEventListener('change',renderizarSolicitudes);
  document.getElementById('ordenProductos').addEventListener('change', renderizarInventario);
  document.getElementById('direccionOrdenProductos').addEventListener('change', renderizarInventario);


  let maquinasCache = [];
  let lavadoresMaquinasCache = [];

  function claseEstadoMaquina(estado) {
    if (estado === 'Operativa') return 'operativa';
    if (estado === 'De respaldo') return 'respaldo';
    if (estado === 'Dañada') return 'danada';
    if (estado === 'En reparación') return 'reparacion';
    return '';
  }

  function actualizarIndicadoresMaquinas(lista, sufijo) {
    const operativas = lista.filter(m => m.estado === 'Operativa').length;
    const respaldo = lista.filter(m => m.estado === 'De respaldo').length;
    const problemas = lista.filter(m => ['Dañada','En reparación'].includes(m.estado)).length;
    const ids = {
      total: ['totalMaquinasDashboard','totalMaquinasModulo'],
      operativas: ['maquinasOperativasDashboard','maquinasOperativasModulo'],
      respaldo: ['maquinasRespaldoDashboard','maquinasRespaldoModulo'],
      problemas: ['maquinasProblemasDashboard','maquinasProblemasModulo']
    };
    const valores = {total:lista.length,operativas:operativas,respaldo:respaldo,problemas:problemas};
    Object.keys(ids).forEach(function(tipo) {
      ids[tipo].forEach(function(id) {
        const elemento = document.getElementById(id);
        if (elemento) elemento.textContent = valores[tipo];
      });
    });
  }

  function agregarFilaVacia(tabla, columnas, mensaje) {
    tabla.replaceChildren();
    const tr = document.createElement('tr');
    const td = document.createElement('td');
    td.colSpan = columnas;
    td.textContent = mensaje;
    tr.appendChild(td);
    tabla.appendChild(tr);
  }

  function renderizarResumenMaquinasDashboard(lista) {
    const tabla = document.getElementById('tablaResumenMaquinasDashboard');
    if (!tabla) return;
    tabla.replaceChildren();
    if (!lista.length) {
      agregarFilaVacia(tabla,4,'Todavía no hay máquinas registradas.');
      return;
    }
    lista.forEach(function(maquina) {
      const tr = document.createElement('tr');
      const valores = [maquina.codigo,maquina.tipo,maquina.estado,maquina.asignacionActiva ? maquina.asignacionActiva.nombreLavador : 'Sin asignar'];
      valores.forEach(function(valor,index) {
        const td = document.createElement('td');
        if (index === 2) {
          const etiqueta = document.createElement('span');
          etiqueta.className = 'machine-status ' + claseEstadoMaquina(maquina.estado);
          etiqueta.textContent = maquina.estado || 'Sin estado';
          td.appendChild(etiqueta);
        } else td.textContent = valor || '—';
        tr.appendChild(td);
      });
      tabla.appendChild(tr);
    });
  }

  function cargarResumenMaquinas() {
    const mensaje = document.getElementById('mensajeResumenMaquinasDashboard');
    google.script.run.withSuccessHandler(function(lista) {
      maquinasCache = Array.isArray(lista) ? lista : [];
      actualizarIndicadoresMaquinas(maquinasCache);
      renderizarResumenMaquinasDashboard(maquinasCache);
      if (mensaje) mensaje.textContent = '';
    }).withFailureHandler(function(error) {
      if (mensaje) mensaje.textContent = 'No se pudo cargar el resumen de máquinas: ' + mostrarError(error);
      const tabla = document.getElementById('tablaResumenMaquinasDashboard');
      if (tabla) agregarFilaVacia(tabla,4,'No se pudo consultar el equipamiento.');
    }).obtenerResumenMaquinas();
  }

  function renderizarTablaMaquinas(lista) {
    const tabla = document.getElementById('tablaMaquinas');
    if (!tabla) return;
    tabla.replaceChildren();
    if (!lista.length) {
      agregarFilaVacia(tabla,5,'Todavía no hay máquinas registradas.');
      return;
    }
    lista.forEach(function(maquina) {
      const tr = document.createElement('tr');
      const valores = [maquina.codigo,maquina.tipo,maquina.estado,maquina.asignacionActiva ? maquina.asignacionActiva.nombreLavador : 'Sin asignar',maquina.fechaRegistro];
      valores.forEach(function(valor,index) {
        const td = document.createElement('td');
        if (index === 2) {
          const etiqueta = document.createElement('span');
          etiqueta.className = 'machine-status ' + claseEstadoMaquina(maquina.estado);
          etiqueta.textContent = maquina.estado || 'Sin estado';
          td.appendChild(etiqueta);
        } else td.textContent = valor || '—';
        tr.appendChild(td);
      });
      tabla.appendChild(tr);
    });
  }

  function llenarSelectorMaquinas(id, lista, filtro) {
    const selector = document.getElementById(id);
    if (!selector) return;
    const anterior = selector.value;
    selector.replaceChildren();
    const inicial = document.createElement('option');
    inicial.value = '';
    inicial.textContent = 'Seleccionar máquina';
    selector.appendChild(inicial);
    lista.filter(filtro).forEach(function(maquina) {
      const opcion = document.createElement('option');
      opcion.value = maquina.codigo;
      opcion.textContent = maquina.codigo + ' — ' + maquina.tipo + ' (' + maquina.estado + ')';
      selector.appendChild(opcion);
    });
    if (lista.some(m => m.codigo === anterior && filtro(m))) selector.value = anterior;
  }

  function actualizarSelectoresModuloMaquinas() {
    llenarSelectorMaquinas('maquinaParaAsignar',maquinasCache,m => !m.asignacionActiva && !m.lavadorAsignado && !['Dañada','En reparación'].includes(m.estado));
    llenarSelectorMaquinas('maquinaParaDevolver',maquinasCache,m => Boolean(m.asignacionActiva || m.lavadorAsignado));
    llenarSelectorMaquinas('maquinaParaEstado',maquinasCache,() => true);
    const selectorLavador = document.getElementById('lavadorParaAsignar');
    if (selectorLavador) {
      const anterior = selectorLavador.value;
      selectorLavador.replaceChildren();
      const inicial = document.createElement('option'); inicial.value = ''; inicial.textContent = 'Seleccionar lavador activo'; selectorLavador.appendChild(inicial);
      lavadoresMaquinasCache.filter(l => l.activo).forEach(function(lavador) {
        const opcion = document.createElement('option'); opcion.value = lavador.id; opcion.textContent = lavador.nombre; selectorLavador.appendChild(opcion);
      });
      if (lavadoresMaquinasCache.some(l => l.id === anterior && l.activo)) selectorLavador.value = anterior;
    }

    // Filtro de historial: códigos de todas las máquinas registradas.
    const filtroCodigo = document.getElementById('filtroCodigoHistorialMaquinas');
    if (filtroCodigo) {
      const anterior = filtroCodigo.value;
      filtroCodigo.replaceChildren();
      const todas = document.createElement('option');
      todas.value = '';
      todas.textContent = 'Todas las máquinas';
      filtroCodigo.appendChild(todas);
      maquinasCache.forEach(function(maquina) {
        const opcion = document.createElement('option');
        opcion.value = maquina.codigo;
        opcion.textContent = maquina.codigo + ' — ' + maquina.tipo;
        filtroCodigo.appendChild(opcion);
      });
      if (maquinasCache.some(m => m.codigo === anterior)) filtroCodigo.value = anterior;
    }

    // Filtro de historial: lavadores activos e inactivos para incluir movimientos antiguos.
    const filtroLavador = document.getElementById('filtroLavadorHistorialMaquinas');
    if (filtroLavador) {
      const anterior = filtroLavador.value;
      filtroLavador.replaceChildren();
      const todos = document.createElement('option');
      todos.value = '';
      todos.textContent = 'Todos los lavadores';
      filtroLavador.appendChild(todos);
      const nombres = [...new Set(lavadoresMaquinasCache.map(l => l.nombre).filter(Boolean))]
        .sort((a, b) => a.localeCompare(b));
      nombres.forEach(function(nombre) {
        const opcion = document.createElement('option');
        opcion.value = nombre;
        opcion.textContent = nombre;
        filtroLavador.appendChild(opcion);
      });
      if (nombres.includes(anterior)) filtroLavador.value = anterior;
    }
  }

  function cargarModuloMaquinas() {
    const mensaje = document.getElementById('mensajeListaMaquinas');
    if (mensaje) mensaje.textContent = 'Cargando máquinas y lavadores...';
    google.script.run.withSuccessHandler(function(lista) {
      maquinasCache = Array.isArray(lista) ? lista : [];
      actualizarIndicadoresMaquinas(maquinasCache);
      renderizarTablaMaquinas(maquinasCache);
      actualizarSelectoresModuloMaquinas();
      if (mensaje) mensaje.textContent = maquinasCache.length + ' máquina(s) registradas.';
    }).withFailureHandler(function(error) {
      if (mensaje) mensaje.textContent = 'No se pudo cargar la lista: ' + mostrarError(error);
      agregarFilaVacia(document.getElementById('tablaMaquinas'),5,'Error al consultar las máquinas.');
    }).obtenerResumenMaquinas();

    google.script.run.withSuccessHandler(function(lavadores) {
      lavadoresMaquinasCache = Array.isArray(lavadores) ? lavadores : [];
      actualizarSelectoresModuloMaquinas();
    }).withFailureHandler(function(error) {
      const aviso = document.getElementById('mensajeAsignarMaquina');
      if (aviso) aviso.textContent = 'No se pudo cargar la lista de lavadores: ' + mostrarError(error);
    }).obtenerLavadores();
  }

  function ejecutarAccionMaquina(config) {
    const boton = document.getElementById(config.boton);
    const mensaje = document.getElementById(config.mensaje);
    boton.disabled = true;
    mensaje.textContent = config.cargando;
    config.ejecutar(
      function(resultado) {
        mensaje.textContent = resultado.mensaje || config.exito;
        boton.disabled = false;
        if (config.formulario) document.getElementById(config.formulario).reset();
        cargarModuloMaquinas();
        cargarResumenMaquinas();
      },
      function(error) {
        mensaje.textContent = 'No se pudo completar la operación: ' + mostrarError(error);
        boton.disabled = false;
      }
    );
  }

  document.getElementById('btnActualizarMaquinas').addEventListener('click',function() {
    cargarModuloMaquinas();
    cargarResumenMaquinas();
  });

  document.getElementById('formRegistrarMaquina').addEventListener('submit',function(event) {
    event.preventDefault();
    const datos = Object.fromEntries(new FormData(event.currentTarget).entries());
    ejecutarAccionMaquina({boton:'btnRegistrarMaquina',mensaje:'mensajeRegistrarMaquina',cargando:'Registrando máquina...',exito:'Máquina registrada.',formulario:'formRegistrarMaquina',ejecutar:function(ok,fail) {
      google.script.run.withSuccessHandler(ok).withFailureHandler(fail).registrarMaquina(datos);
    }});
  });

  document.getElementById('formAsignarMaquina').addEventListener('submit',function(event) {
    event.preventDefault();
    const codigo = document.getElementById('maquinaParaAsignar').value;
    const idLavador = document.getElementById('lavadorParaAsignar').value;
    const observaciones = document.getElementById('observacionesAsignacion').value.trim();
    ejecutarAccionMaquina({boton:'btnAsignarMaquina',mensaje:'mensajeAsignarMaquina',cargando:'Registrando asignación...',exito:'Máquina asignada.',formulario:'formAsignarMaquina',ejecutar:function(ok,fail) {
      google.script.run.withSuccessHandler(ok).withFailureHandler(fail).asignarMaquina(codigo,idLavador,observaciones);
    }});
  });

  document.getElementById('formDevolverMaquina').addEventListener('submit',function(event) {
    event.preventDefault();
    const codigo = document.getElementById('maquinaParaDevolver').value;
    const motivo = document.getElementById('motivoDevolucionMaquina').value.trim();
    const observaciones = document.getElementById('observacionesDevolucionMaquina').value.trim();
    ejecutarAccionMaquina({boton:'btnDevolverMaquina',mensaje:'mensajeDevolverMaquina',cargando:'Registrando devolución...',exito:'Devolución registrada.',formulario:'formDevolverMaquina',ejecutar:function(ok,fail) {
      google.script.run.withSuccessHandler(ok).withFailureHandler(fail).devolverMaquina(codigo,motivo,observaciones);
    }});
  });

  document.getElementById('formEstadoMaquina').addEventListener('submit',function(event) {
    event.preventDefault();
    const codigo = document.getElementById('maquinaParaEstado').value;
    const estado = document.getElementById('nuevoEstadoMaquina').value;
    const motivo = document.getElementById('motivoEstadoMaquina').value.trim();
    const observaciones = document.getElementById('observacionesEstadoMaquina').value.trim();
    ejecutarAccionMaquina({boton:'btnCambiarEstadoMaquina',mensaje:'mensajeEstadoMaquina',cargando:'Actualizando estado...',exito:'Estado actualizado.',formulario:'formEstadoMaquina',ejecutar:function(ok,fail) {
      google.script.run.withSuccessHandler(ok).withFailureHandler(fail).cambiarEstadoMaquina(codigo,estado,motivo,observaciones);
    }});
  });

  document.getElementById('formHistorialMaquinas').addEventListener('submit',function(event) {
    event.preventDefault();
    const codigo = document.getElementById('filtroCodigoHistorialMaquinas').value;
    const lavador = document.getElementById('filtroLavadorHistorialMaquinas').value;
    const boton = document.getElementById('btnBuscarHistorialMaquinas');
    const mensaje = document.getElementById('mensajeHistorialMaquinas');
    const tabla = document.getElementById('tablaHistorialMaquinas');
    boton.disabled = true; mensaje.textContent = 'Consultando historial...';
    google.script.run.withSuccessHandler(function(lista) {
      boton.disabled = false; tabla.replaceChildren();
      const movimientos = Array.isArray(lista) ? lista : [];
      if (!movimientos.length) {
        agregarFilaVacia(tabla,8,'No se encontraron movimientos con esos filtros.');
        mensaje.textContent = '0 movimientos encontrados.'; return;
      }
      movimientos.forEach(function(item) {
        const tr = document.createElement('tr');
        [item.fecha,item.codigo,item.tipoMovimiento,item.lavador,item.estadoAnterior,item.estadoNuevo,item.motivo,item.observaciones].forEach(function(valor) {
          const td = document.createElement('td'); td.textContent = valor || '—'; tr.appendChild(td);
        });
        tabla.appendChild(tr);
      });
      mensaje.textContent = movimientos.length + ' movimiento(s) encontrados.';
    }).withFailureHandler(function(error) {
      boton.disabled = false; mensaje.textContent = 'No se pudo consultar el historial: ' + mostrarError(error);
      agregarFilaVacia(tabla,8,'Error al consultar el historial.');
    }).obtenerHistorialMaquinas(codigo,lavador);
  });

  let lavadoresPersonalCache = [];

  function cargarTablaPersonal() {
    const tabla = document.getElementById('tablaPersonal');
    tabla.innerHTML = '<tr><td colspan="8">Cargando personal...</td></tr>';
    google.script.run.withSuccessHandler(function(lista) {
      lavadoresPersonalCache = Array.isArray(lista) ? lista : [];
      tabla.replaceChildren();
      if (!lavadoresPersonalCache.length) { tabla.innerHTML = '<tr><td colspan="8">No hay lavadores registrados.</td></tr>'; return; }
      lavadoresPersonalCache.forEach(function(l) {
        const tr = document.createElement('tr');
        [l.nombre, l.telefono || '—', l.fechaIngreso || 'Sin fecha', l.antiguedad || '—', l.activo ? 'Activo' : 'Inactivo'].forEach(function(v) {
          const td = document.createElement('td'); td.textContent = v; tr.appendChild(td);
        });
        const celdaAplica = document.createElement('td'), selectorAplica = document.createElement('select');
        [['true','Sí aplica'],['false','No aplica']].forEach(function(par){const op=document.createElement('option');op.value=par[0];op.textContent=par[1];selectorAplica.appendChild(op);});
        selectorAplica.value = l.aplicaIncentivo === false ? 'false' : 'true'; celdaAplica.appendChild(selectorAplica); tr.appendChild(celdaAplica);
        const celdaMotivo = document.createElement('td'), selectorMotivo = document.createElement('select');
        [['','Seleccionar'],['Jefe','Jefe'],['Encargado','Encargado'],['Otro','Otro']].forEach(function(par){const op=document.createElement('option');op.value=par[0];op.textContent=par[1];selectorMotivo.appendChild(op);});
        selectorMotivo.value = l.motivoNoAplica || ''; selectorMotivo.disabled = selectorAplica.value === 'true';
        selectorAplica.addEventListener('change',function(){selectorMotivo.disabled=selectorAplica.value==='true';if(selectorAplica.value==='true')selectorMotivo.value='';});
        celdaMotivo.appendChild(selectorMotivo); tr.appendChild(celdaMotivo);
        const accion = document.createElement('td');
        const guardar = document.createElement('button'); guardar.className='primary'; guardar.textContent='Guardar evaluación';
        guardar.addEventListener('click',function(){
          guardar.disabled=true;
          google.script.run.withSuccessHandler(function(r){document.getElementById('mensajeListaPersonal').textContent=r.mensaje || 'Configuración actualizada.';guardar.disabled=false;cargarTablaPersonal();})
            .withFailureHandler(function(e){document.getElementById('mensajeListaPersonal').textContent='No se pudo actualizar: '+mostrarError(e);guardar.disabled=false;})
            .actualizarAplicabilidadIncentivoLavador(l.id,selectorAplica.value,selectorMotivo.value);
        });
        const editar = document.createElement('button'); editar.className='secondary'; editar.textContent='Editar ingreso'; editar.style.marginTop='6px'; editar.addEventListener('click',function(){
          const fecha = prompt('Fecha real de ingreso laboral de ' + l.nombre + ' (AAAA-MM-DD):', l.fechaIngreso || '');
          if (!fecha) return;
          google.script.run.withSuccessHandler(function(r){document.getElementById('mensajeListaPersonal').textContent=r.mensaje || 'Fecha actualizada.';cargarTablaPersonal();})
            .withFailureHandler(function(e){document.getElementById('mensajeListaPersonal').textContent='No se pudo actualizar: '+mostrarError(e);})
            .actualizarFechaIngresoLavador(l.id,fecha);
        });
        accion.append(guardar,editar); tr.appendChild(accion); tabla.appendChild(tr);
      });
      document.getElementById('mensajeListaPersonal').textContent = lavadoresPersonalCache.length + ' lavador(es) encontrado(s).';
    }).withFailureHandler(function(error) {
      tabla.innerHTML = '<tr><td colspan="8">No se pudo cargar el personal.</td></tr>';
      document.getElementById('mensajeListaPersonal').textContent = mostrarError(error);
    }).obtenerLavadores();
  }

  document.getElementById('btnActualizarListaPersonal').addEventListener('click', cargarTablaPersonal);

  function fechaLocalISO(fecha) {
    const y = fecha.getFullYear(), m = String(fecha.getMonth()+1).padStart(2,'0'), d = String(fecha.getDate()).padStart(2,'0');
    return y + '-' + m + '-' + d;
  }
  function ajustarFechaLunes(valor) {
    if (!valor) return '';
    const f = new Date(valor + 'T12:00:00');
    f.setDate(f.getDate() - ((f.getDay()+6)%7));
    return fechaLocalISO(f);
  }
  function seleccionarResultadoIncentivo(fila, resultado) {
    const botones = fila.querySelectorAll('button[data-resultado-incentivo]');
    botones.forEach(function(boton) {
      const activo = boton.dataset.resultadoIncentivo === resultado;
      boton.classList.toggle('is-si', activo && resultado === 'Sí');
      boton.classList.toggle('is-no', activo && resultado === 'No');
      boton.setAttribute('aria-pressed', activo ? 'true' : 'false');
    });
    fila.dataset.incentivo = resultado;
  }

  function cargarSemanaIncentivos() {
    const input = document.getElementById('fechaSemanaIncentivo');
    if (!input.value) return;
    const lunes = ajustarFechaLunes(input.value);
    input.value = lunes;
    const domingo = new Date(lunes + 'T12:00:00'); domingo.setDate(domingo.getDate()+6);
    document.getElementById('rangoSemanaIncentivo').textContent = 'Semana seleccionada: ' + lunes + ' al ' + fechaLocalISO(domingo) + ' (lunes a domingo).';
    const tabla = document.getElementById('tablaIncentivosSemana');
    tabla.innerHTML = '<tr><td colspan="2">Cargando semana...</td></tr>';
    google.script.run.withSuccessHandler(function(r) {
      tabla.replaceChildren();
      (r.registros || []).forEach(function(item) {
        const tr = document.createElement('tr'), nombre = document.createElement('td'), celda = document.createElement('td');
        nombre.textContent = item.nombre;
        tr.dataset.idLavador = item.idLavador;
        if (item.incentivo === 'No aplica') {
          const estado = document.createElement('span');
          estado.className = 'incentive-na';
          estado.textContent = 'No aplica' + (item.motivoNoAplica ? ' — ' + item.motivoNoAplica : '');
          celda.appendChild(estado);
          tr.dataset.incentivo = 'No aplica';
        } else {
          const grupo = document.createElement('div');
          grupo.className = 'incentive-actions';
          [['Sí','Sí'],['No','No']].forEach(function(par) {
            const boton = document.createElement('button');
            boton.type = 'button';
            boton.className = 'incentive-choice';
            boton.textContent = par[1];
            boton.dataset.resultadoIncentivo = par[0];
            boton.setAttribute('aria-pressed', 'false');
            boton.addEventListener('click', function() {
              seleccionarResultadoIncentivo(tr, par[0]);
            });
            grupo.appendChild(boton);
          });
          celda.appendChild(grupo);
          if (item.incentivo === 'Sí' || item.incentivo === 'No') seleccionarResultadoIncentivo(tr, item.incentivo);
          else tr.dataset.incentivo = '';
        }
        tr.append(nombre,celda); tabla.appendChild(tr);
      });
      if (!r.registros || !r.registros.length) tabla.innerHTML = '<tr><td colspan="2">No hay lavadores activos.</td></tr>';
    }).withFailureHandler(function(error) {
      tabla.innerHTML = '<tr><td colspan="2">No se pudo cargar la semana: ' + mostrarError(error) + '</td></tr>';
    }).obtenerIncentivosSemana(lunes);
  }

  function marcarTodosIncentivos(resultado) {
    document.querySelectorAll('#tablaIncentivosSemana tr[data-id-lavador]').forEach(function(fila) {
      if (fila.dataset.incentivo !== 'No aplica' && fila.querySelector('button[data-resultado-incentivo]')) {
        seleccionarResultadoIncentivo(fila, resultado);
      }
    });
    document.getElementById('mensajeIncentivos').textContent = 'Se marcaron los lavadores correspondientes como "' + resultado + '". Puedes modificar cualquier resultado antes de guardar.';
  }

  document.getElementById('btnTodosIncentivoSi').addEventListener('click', function() { marcarTodosIncentivos('Sí'); });
  document.getElementById('btnTodosIncentivoNo').addEventListener('click', function() { marcarTodosIncentivos('No'); });
  document.getElementById('fechaSemanaIncentivo').addEventListener('change', cargarSemanaIncentivos);
  document.getElementById('btnGuardarIncentivos').addEventListener('click', function() {
    const boton = this, mensaje = document.getElementById('mensajeIncentivos'), fecha = document.getElementById('fechaSemanaIncentivo').value;
    const registros = Array.from(document.querySelectorAll('#tablaIncentivosSemana tr[data-id-lavador]')).map(fila => ({idLavador:fila.dataset.idLavador,incentivo:fila.dataset.incentivo || ''}));
    if (!fecha) { mensaje.textContent = 'Selecciona una semana.'; return; }
    if (registros.some(r => !r.incentivo)) { mensaje.textContent = 'Selecciona Sí o No para todos los lavadores que corresponda evaluar.'; return; }
    boton.disabled = true; mensaje.textContent = 'Guardando resultados...';
    google.script.run.withSuccessHandler(function(r) { mensaje.textContent = r.mensaje || 'Semana guardada.'; boton.disabled = false; })
      .withFailureHandler(function(error) { mensaje.textContent = 'No se pudo guardar: ' + mostrarError(error); boton.disabled = false; })
      .guardarIncentivosSemana(fecha, registros);
  });

  let estadoIncentivosCache = [];
  let semanaEstadoIncentivoCargada = '';

  function prepararConsultaEstadoIncentivo() {
    const input = document.getElementById('fechaConsultaIncentivo');
    if (!input.value) input.value = ajustarFechaLunes(fechaLocalISO(new Date()));
    if (input.value !== semanaEstadoIncentivoCargada) cargarEstadoIncentivo();
  }


  let historialMovimientosCache = [];

  function renderizarHistorialMovimientos() {
    const tabla = document.getElementById('tablaHistorialMovimientos');
    if (!tabla) return;
    const tipo = document.getElementById('filtroTipoMovimiento').value;
    const busqueda = normalizarTextoBusqueda(document.getElementById('buscarHistorialMovimiento').value);
    const filtrados = historialMovimientosCache.filter(function(movimiento) {
      const coincideTipo = tipo === 'Todos' || movimiento.tipo === tipo;
      const texto = normalizarTextoBusqueda([movimiento.codigo, movimiento.producto, movimiento.motivo].join(' '));
      return coincideTipo && (!busqueda || texto.includes(busqueda));
    });
    tabla.replaceChildren();
    if (!filtrados.length) {
      const tr = document.createElement('tr'); const td = document.createElement('td'); td.colSpan = 8;
      td.textContent = historialMovimientosCache.length ? 'No hay movimientos que coincidan con los filtros.' : 'Todavía no hay movimientos registrados.';
      tr.appendChild(td); tabla.appendChild(tr); return;
    }
    filtrados.forEach(function(movimiento) {
      const tr = document.createElement('tr');
      [movimiento.fechaHora,movimiento.tipo,movimiento.codigo,movimiento.producto,movimiento.cantidad,movimiento.stockAnterior,movimiento.stockResultante,movimiento.motivo].forEach(function(valor,indice) {
        const td=document.createElement('td'); td.textContent=valor == null ? '' : String(valor);
        if (indice===1) td.className = movimiento.tipo==='Entrada' || movimiento.tipo==='Stock inicial' ? 'stock-ok' : 'stock-low';
        if (indice===7) td.className='note-content';
        tr.appendChild(td);
      });
      tabla.appendChild(tr);
    });
  }

  function cargarHistorialMovimientos() {
    const tabla=document.getElementById('tablaHistorialMovimientos');
    const mensaje=document.getElementById('mensajeHistorialMovimientos');
    const boton=document.getElementById('btnActualizarHistorialMovimientos');
    if (!tabla) return;
    if (boton) boton.disabled=true;
    if (mensaje) mensaje.textContent='Cargando historial...';
    google.script.run.withSuccessHandler(function(movimientos) {
      historialMovimientosCache=Array.isArray(movimientos)?movimientos:[];
      if (boton) boton.disabled=false;
      if (mensaje) mensaje.textContent=historialMovimientosCache.length+' movimiento(s) registrado(s).';
      renderizarHistorialMovimientos();
    }).withFailureHandler(function(error) {
      if (boton) boton.disabled=false;
      if (mensaje) mensaje.textContent='No se pudo cargar el historial: '+mostrarError(error);
      tabla.replaceChildren(); const tr=document.createElement('tr'); const td=document.createElement('td'); td.colSpan=8; td.textContent='Error al consultar el historial.'; tr.appendChild(td); tabla.appendChild(tr);
    }).obtenerHistorialMovimientos();
  }

  document.getElementById('filtroTipoMovimiento').addEventListener('change',renderizarHistorialMovimientos);
  document.getElementById('buscarHistorialMovimiento').addEventListener('input',renderizarHistorialMovimientos);
  document.getElementById('btnActualizarHistorialMovimientos').addEventListener('click',cargarHistorialMovimientos);

  function normalizarTextoBusqueda(valor) {
    return String(valor || '').toLocaleLowerCase('es').normalize('NFD').replace(/[\u0300-\u036f]/g, '');
  }

  function cargarEstadoIncentivo() {
    const input = document.getElementById('fechaConsultaIncentivo');
    if (!input.value) return;
    const lunesObjetivo = ajustarFechaLunes(input.value);
    input.value = lunesObjetivo;
    const lunesResultado = new Date(lunesObjetivo + 'T12:00:00');
    lunesResultado.setDate(lunesResultado.getDate() - 7);
    const fechaResultado = fechaLocalISO(lunesResultado);
    const domingoResultado = new Date(lunesResultado); domingoResultado.setDate(domingoResultado.getDate() + 6);
    const domingoObjetivo = new Date(lunesObjetivo + 'T12:00:00'); domingoObjetivo.setDate(domingoObjetivo.getDate() + 6);
    document.getElementById('rangoConsultaIncentivo').textContent = 'Incentivo aplicable del ' + lunesObjetivo + ' al ' + fechaLocalISO(domingoObjetivo) + '. Se toma el resultado de la semana anterior: ' + fechaResultado + ' al ' + fechaLocalISO(domingoResultado) + '.';
    const tabla = document.getElementById('tablaEstadoIncentivo');
    tabla.innerHTML = '<tr><td colspan="3">Consultando incentivos...</td></tr>';
    document.getElementById('mensajeConsultaIncentivo').textContent = '';
    semanaEstadoIncentivoCargada = lunesObjetivo;
    google.script.run.withSuccessHandler(function(r) {
      estadoIncentivosCache = (r && r.registros ? r.registros : []).map(function(item) {
        return { idLavador:item.idLavador, nombre:item.nombre, resultado:item.incentivo || '', motivoNoAplica:item.motivoNoAplica || '' };
      });
      renderizarEstadoIncentivo();
    }).withFailureHandler(function(error) {
      estadoIncentivosCache = [];
      tabla.innerHTML = '<tr><td colspan="3">No se pudo consultar: ' + mostrarError(error) + '</td></tr>';
      document.getElementById('resumenConsultaIncentivo').textContent = '';
    }).obtenerIncentivosSemana(fechaResultado);
  }

  function renderizarEstadoIncentivo() {
    const tabla = document.getElementById('tablaEstadoIncentivo');
    const busqueda = normalizarTextoBusqueda(document.getElementById('buscarLavadorIncentivo').value.trim());
    const filtro = document.getElementById('filtroEstadoIncentivo').value;
    const orden = { 'Sí':0, 'No':1, 'No aplica':2, '':3 };
    const filtrados = estadoIncentivosCache.filter(function(item) {
      const coincideNombre = normalizarTextoBusqueda(item.nombre).includes(busqueda);
      const estado = item.resultado || 'Sin registrar';
      const coincideEstado = filtro === 'Todos' || estado === filtro;
      return coincideNombre && coincideEstado;
    }).sort(function(a,b) {
      return (orden[a.resultado] - orden[b.resultado]) || a.nombre.localeCompare(b.nombre, 'es');
    });
    tabla.replaceChildren();
    filtrados.forEach(function(item) {
      const tr = document.createElement('tr');
      const nombre = document.createElement('td'); nombre.textContent = item.nombre;
      const estado = document.createElement('td');
      const resultado = document.createElement('td'); resultado.textContent = item.resultado || 'Sin registrar';
      if (item.resultado === 'Sí') estado.textContent = 'Tiene incentivo — 60 %';
      else if (item.resultado === 'No') estado.textContent = 'No tiene incentivo — 50 %';
      else if (item.resultado === 'No aplica') estado.textContent = 'No aplica' + (item.motivoNoAplica ? ' — ' + item.motivoNoAplica : '');
      else estado.textContent = 'Pendiente de registrar';
      tr.append(nombre, estado, resultado); tabla.appendChild(tr);
    });
    if (!filtrados.length) tabla.innerHTML = '<tr><td colspan="3">No se encontraron lavadores con esos criterios.</td></tr>';
    const si = estadoIncentivosCache.filter(x => x.resultado === 'Sí').length;
    const no = estadoIncentivosCache.filter(x => x.resultado === 'No').length;
    const pendientes = estadoIncentivosCache.filter(x => !x.resultado).length;
    const noAplica = estadoIncentivosCache.filter(x => x.resultado === 'No aplica').length;
    document.getElementById('resumenConsultaIncentivo').textContent = 'Total: ' + estadoIncentivosCache.length + ' | Con incentivo: ' + si + ' | Sin incentivo: ' + no + ' | No aplica: ' + noAplica + ' | Sin resultado: ' + pendientes + '. Mostrando ' + filtrados.length + '.';
  }

  document.getElementById('fechaConsultaIncentivo').addEventListener('change', cargarEstadoIncentivo);
  document.getElementById('buscarLavadorIncentivo').addEventListener('input', renderizarEstadoIncentivo);
  document.getElementById('filtroEstadoIncentivo').addEventListener('change', renderizarEstadoIncentivo);

  function cargarBonosSemestrales() {
    const tabla = document.getElementById('tablaBonos'); tabla.innerHTML = '<tr><td colspan="8">Cargando evaluación...</td></tr>';
    google.script.run.withSuccessHandler(function(lista) {
      tabla.replaceChildren();
      (lista || []).forEach(function(b) {
        const tr = document.createElement('tr');
        [b.nombre,b.antiguedad || '—',b.fechaInicio || '—',b.fechaEvaluacion || '—',b.ultimoPago || 'Nunca',String(b.faltas)].forEach(v => { const td=document.createElement('td'); td.textContent=v; tr.appendChild(td); });
        const estado=document.createElement('td'); estado.textContent=b.estado; tr.appendChild(estado);
        const accion=document.createElement('td');
        if (b.estado === 'Bono pendiente') { const btn=document.createElement('button'); btn.className='entry'; btn.textContent='Registrar pago'; btn.addEventListener('click',()=>registrarPagoBonoUI(b)); accion.appendChild(btn); }
        else if (b.estado === 'No corresponde') { const btn=document.createElement('button'); btn.className='secondary'; btn.textContent='Modificar con autorización'; btn.addEventListener('click',()=>desbloquearBonoUI(b)); accion.appendChild(btn); }
        else accion.textContent = b.estado === 'Falta fecha de ingreso' ? 'Completar fecha laboral' : '—';
        tr.appendChild(accion); tabla.appendChild(tr);
      });
      if (!lista || !lista.length) tabla.innerHTML='<tr><td colspan="8">No hay lavadores registrados.</td></tr>';
    }).withFailureHandler(function(error) { tabla.innerHTML='<tr><td colspan="8">Error: '+mostrarError(error)+'</td></tr>'; }).obtenerResumenBonosSemestrales();
  }
  document.getElementById('btnActualizarBonos').addEventListener('click', cargarBonosSemestrales);
  function registrarPagoBonoUI(b) {
    const fecha = prompt('Fecha real de pago del bono (AAAA-MM-DD):', fechaLocalISO(new Date()));
    if (!fecha) return;
    const obs = prompt('Observaciones (opcional):','') || '';
    if (!confirm('¿Confirmas el pago del bono a ' + b.nombre + ' con fecha ' + fecha + '?')) return;
    google.script.run.withSuccessHandler(function(r) { document.getElementById('mensajeBonos').textContent=r.mensaje; cargarBonosSemestrales(); })
      .withFailureHandler(function(e) { document.getElementById('mensajeBonos').textContent='No se pudo registrar el pago: '+mostrarError(e); })
      .registrarPagoBonoSemestral(b.idLavador,fecha,obs);
  }
  function desbloquearBonoUI(b) {
    const codigo=prompt('Código de autorización para modificar el estado de ' + b.nombre + ':'); if (!codigo) return;
    const nuevo=prompt('Escribe el nuevo estado exactamente:\nBono pendiente\nEn evaluación'); if (!nuevo) return;
    const obs=prompt('Motivo de la modificación (obligatorio):',''); if (!obs) return;
    google.script.run.withSuccessHandler(function(r) { document.getElementById('mensajeBonos').textContent=r.mensaje; cargarBonosSemestrales(); })
      .withFailureHandler(function(e) { document.getElementById('mensajeBonos').textContent='No se pudo modificar: '+mostrarError(e); })
      .desbloquearEstadoBono(b.idLavador,nuevo,codigo,obs);
  }

  function cargarSelectorHistorialPersonal() {
    const selector=document.getElementById('selectorHistorialPersonal');
    google.script.run.withSuccessHandler(function(lista) {
      lavadoresPersonalCache=lista || []; selector.replaceChildren();
      const op=document.createElement('option'); op.value=''; op.textContent='Seleccionar lavador'; selector.appendChild(op);
      lavadoresPersonalCache.forEach(l=>{const o=document.createElement('option');o.value=l.id;o.textContent=l.nombre;selector.appendChild(o);});
    }).withFailureHandler(function(e){document.getElementById('mensajeHistorialPersonal').textContent=mostrarError(e);}).obtenerLavadores();
  }
  document.getElementById('selectorHistorialPersonal').addEventListener('change',function(){
    const id=this.value, ti=document.getElementById('tablaHistorialIncentivos'), tb=document.getElementById('tablaHistorialBonos');
    if(!id){ti.innerHTML='<tr><td colspan="4">Selecciona un lavador.</td></tr>';tb.innerHTML='<tr><td colspan="6">Selecciona un lavador.</td></tr>';return;}
    ti.innerHTML='<tr><td colspan="4">Cargando incentivos...</td></tr>'; tb.innerHTML='<tr><td colspan="6">Cargando movimientos...</td></tr>';
    google.script.run.withSuccessHandler(function(r){
      ti.replaceChildren(); (r.incentivos || []).forEach(function(x){const tr=document.createElement('tr');[x.semanaDesde,x.semanaHasta,x.incentivo,x.fechaRegistro].forEach(v=>{const td=document.createElement('td');td.textContent=v||'—';tr.appendChild(td);});ti.appendChild(tr);});
      if(!r.incentivos || !r.incentivos.length) ti.innerHTML='<tr><td colspan="4">Sin registros semanales.</td></tr>';
      tb.replaceChildren(); (r.bonos || []).forEach(function(x){const tr=document.createElement('tr');[x.movimiento,x.fecha,x.periodo,x.faltas,x.estado,x.observaciones].forEach(v=>{const td=document.createElement('td');td.textContent=v||'—';tr.appendChild(td);});tb.appendChild(tr);});
      if(!r.bonos || !r.bonos.length) tb.innerHTML='<tr><td colspan="6">Sin movimientos de bonos.</td></tr>';
      document.getElementById('mensajeHistorialPersonal').textContent='Historial actualizado.';
    }).withFailureHandler(function(e){document.getElementById('mensajeHistorialPersonal').textContent='No se pudo cargar el historial: '+mostrarError(e);})
      .obtenerHistorialIndividualLavador(id);
  });


  let estadoDocumentacionCache = [];

  function renderizarEstadoDocumentacion() {
    const tabla=document.getElementById('tablaEstadoDocumentacion');
    if (!tabla) return;
    const termino=normalizarTextoBusqueda(document.getElementById('buscarEstadoDocumentacion').value);
    const documentoSeleccionado=document.getElementById('filtroDocumentoEstado').value;
    const estadoSeleccionado=document.getElementById('filtroEstadoDocumento').value;
    const lista=estadoDocumentacionCache.filter(function(item) {
      if (!normalizarTextoBusqueda(item.nombre).includes(termino)) return false;
      const documentos=Array.isArray(item.documentos)?item.documentos:[];
      const documentosFiltrados=documentoSeleccionado==='Todos'
        ? documentos
        : documentos.filter(function(doc){ return doc.tipo===documentoSeleccionado; });
      if (estadoSeleccionado==='Todos') return true;
      if (!documentosFiltrados.length) return estadoSeleccionado==='Sin datos';
      return documentosFiltrados.some(function(doc) {
        const estado=doc && doc.estado ? doc.estado : 'Sin datos';
        return estado===estadoSeleccionado;
      });
    });
    tabla.replaceChildren();
    if (!lista.length) {
      const tr=document.createElement('tr'); const td=document.createElement('td'); td.colSpan=6;
      td.textContent=estadoDocumentacionCache.length?'No hay lavadores que coincidan con los filtros seleccionados.':'No hay lavadores activos.';
      tr.appendChild(td); tabla.appendChild(tr); return;
    }
    lista.forEach(function(item) {
      const tr=document.createElement('tr');
      const nombre=document.createElement('td'); nombre.textContent=item.nombre; tr.appendChild(nombre);
      // El backend entrega los documentos en el orden de DOCUMENTOS_OBLIGATORIOS,
      // pero la tabla tiene su propio orden de columnas. Asociarlos por tipo evita
      // que los estados aparezcan bajo el encabezado equivocado.
      const documentosPorTipo = new Map(
        (Array.isArray(item.documentos) ? item.documentos : []).map(function(doc) {
          return [doc.tipo, doc];
        })
      );
      const ordenColumnasDocumentos = [
        'Carnet de lavador',
        'Licencia de conducir',
        'Seguro',
        'Certificado de antecedentes'
      ];
      ordenColumnasDocumentos.forEach(function(tipo) {
        const doc = documentosPorTipo.get(tipo);
        const td=document.createElement('td');
        const estado=document.createElement('span');
        const valorEstado = doc && doc.estado ? doc.estado : 'Sin datos';
        estado.className='document-status '+(valorEstado==='Faltante'?'faltante':valorEstado==='Vencido'?'vencido':valorEstado==='No aplica'?'no-aplica':'');
        estado.textContent=valorEstado;
        td.appendChild(estado);
        if (doc && doc.fechaVencimiento) { const detalle=document.createElement('div'); detalle.className='help'; detalle.textContent=doc.fechaVencimiento; td.appendChild(detalle); }
        tr.appendChild(td);
      });
      const tdGeneral=document.createElement('td');
      const badge=document.createElement('span');
      badge.className='document-status '+(item.estadoGeneral==='Faltante'?'faltante':item.estadoGeneral==='Vencido'?'vencido':'');
      badge.textContent=item.estadoGeneral; tdGeneral.appendChild(badge); tr.appendChild(tdGeneral);
      tabla.appendChild(tr);
    });
    const mensaje=document.getElementById('mensajeEstadoDocumentacion');
    if (mensaje) mensaje.textContent='Lavadores activos: '+estadoDocumentacionCache.length+'. Mostrando: '+lista.length+'.';
  }

  function cargarEstadoDocumentacion() {
    const tabla=document.getElementById('tablaEstadoDocumentacion');
    const mensaje=document.getElementById('mensajeEstadoDocumentacion');
    if (!tabla) return;
    if (mensaje) mensaje.textContent='Consultando documentación...';
    tabla.innerHTML='<tr><td colspan="6">Cargando estado de documentación...</td></tr>';
    google.script.run.withSuccessHandler(function(lista) {
      estadoDocumentacionCache=Array.isArray(lista)?lista:[];
      renderizarEstadoDocumentacion();
    }).withFailureHandler(function(error) {
      estadoDocumentacionCache=[];
      tabla.innerHTML='<tr><td colspan="6">No se pudo consultar: '+mostrarError(error)+'</td></tr>';
      if (mensaje) mensaje.textContent='Error al consultar el estado de documentación.';
    }).obtenerEstadoDocumentacionLavadores('');
  }

  document.getElementById('buscarEstadoDocumentacion').addEventListener('input',renderizarEstadoDocumentacion);
  document.getElementById('filtroDocumentoEstado').addEventListener('change',renderizarEstadoDocumentacion);
  document.getElementById('filtroEstadoDocumento').addEventListener('change',renderizarEstadoDocumentacion);
  document.getElementById('btnActualizarEstadoDocumentacion').addEventListener('click',cargarEstadoDocumentacion);

  function cargarLavadores() {
    const selector = document.getElementById('selectorLavador');
    const boton = document.getElementById('btnActualizarLavadores');
    if (boton) boton.disabled = true;
    google.script.run.withSuccessHandler(function(lavadores) {
      const lista = (lavadores || []).filter(l => l.activo);
      const anterior = selector.value;
      selector.replaceChildren();
      const inicial = document.createElement('option'); inicial.value = ''; inicial.textContent = 'Seleccionar lavador'; selector.appendChild(inicial);
      lista.forEach(function(l) { const op = document.createElement('option'); op.value = l.id; op.textContent = l.nombre + (l.telefono ? ' — ' + l.telefono : ''); selector.appendChild(op); });
      if (lavadorParaSeleccionar && lista.some(l => l.id === lavadorParaSeleccionar)) {
        selector.value = lavadorParaSeleccionar;
        lavadorParaSeleccionar = '';
      } else if (lista.some(l => l.id === anterior)) selector.value = anterior;
      if (boton) boton.disabled = false;
      cargarDocumentosLavador();
    }).withFailureHandler(function(error) {
      if (boton) boton.disabled = false;
      document.getElementById('mensajeLavador').textContent = 'No se pudo cargar la lista: ' + mostrarError(error);
    }).obtenerLavadores();
  }

  document.getElementById('formLavador').addEventListener('submit', function(event) {
    event.preventDefault();
    const boton = document.getElementById('btnRegistrarLavador'), mensaje = document.getElementById('mensajeLavador');
    boton.disabled = true; mensaje.textContent = 'Registrando lavador...';
    google.script.run.withSuccessHandler(function(r) {
      mensaje.textContent = r.mensaje || 'Lavador registrado.';
      document.getElementById('formLavador').reset(); boton.disabled = false; cargarLavadores();
      setTimeout(function(){ document.getElementById('selectorLavador').value = r.id; cargarDocumentosLavador(); }, 500);
    }).withFailureHandler(function(error) { mensaje.textContent = 'No se pudo registrar: ' + mostrarError(error); boton.disabled = false; })
      .registrarLavador({nombre:document.getElementById('nombreLavador').value.trim(), telefono:document.getElementById('telefonoLavador').value.trim(), fechaIngreso:document.getElementById('fechaIngresoLavador').value});
  });

  document.getElementById('btnActualizarLavadores').addEventListener('click', cargarLavadores);
  document.getElementById('selectorLavador').addEventListener('change', cargarDocumentosLavador);
  const btnEliminarLavador = document.getElementById('btnEliminarLavador');
  if (btnEliminarLavador) btnEliminarLavador.addEventListener('click', function() {
  const selector = document.getElementById('selectorLavador');
  const idLavador = selector.value;
  const nombreLavador = selector.options[selector.selectedIndex]?.text || '';
  const boton = this;
  const mensaje = document.getElementById('mensajeLavador');

  if (!idLavador) {
    mensaje.textContent = 'Selecciona un lavador para eliminar.';
    return;
  }

  if (!confirm('¿Seguro que deseas desactivar a "' + nombreLavador + '"? Dejará de aparecer en la lista activa, pero se conservará su historial.')) {
    return;
  }

  boton.disabled = true;
  mensaje.textContent = 'Eliminando lavador...';

  google.script.run
    .withSuccessHandler(function(resultado) {
      mensaje.textContent = resultado.mensaje || 'Lavador desactivado.';
      document.getElementById('panelDocumentosLavador').hidden = true;
      document.getElementById('selectorLavador').value = '';
      cargarLavadores();
      boton.disabled = false;
    })
    .withFailureHandler(function(error) {
      mensaje.textContent = 'No se pudo eliminar: ' + mostrarError(error);
      boton.disabled = false;
    })
    .eliminarLavador(idLavador);
});

  const btnEliminarLavadorDefinitivo = document.getElementById('btnEliminarLavadorDefinitivo');
  if (btnEliminarLavadorDefinitivo) btnEliminarLavadorDefinitivo.addEventListener('click', function() {
    const selector = document.getElementById('selectorLavador');
    const idLavador = selector.value;
    const nombreLavador = selector.options[selector.selectedIndex]?.text || '';
    const boton = this;
    const mensaje = document.getElementById('mensajeLavador');

    if (!idLavador) {
      mensaje.textContent = 'Selecciona un lavador para eliminar definitivamente.';
      return;
    }

    const confirmacion = confirm(
      'ATENCIÓN: vas a eliminar definitivamente a "' + nombreLavador + '". ' +
      'Se borrará su registro y sus documentos de la hoja de cálculo; los archivos asociados se enviarán a la papelera de Google Drive. ' +
      'Esta acción no se puede deshacer desde la aplicación. ¿Deseas continuar?'
    );
    if (!confirmacion) return;

    const segundaConfirmacion = prompt('Para confirmar, escribe ELIMINAR');
    if (segundaConfirmacion !== 'ELIMINAR') {
      mensaje.textContent = 'Eliminación cancelada. Debes escribir ELIMINAR exactamente para continuar.';
      return;
    }

    boton.disabled = true;
    mensaje.textContent = 'Eliminando lavador y enviando sus archivos a la papelera...';
    google.script.run
      .withSuccessHandler(function(resultado) {
        mensaje.textContent = resultado.mensaje || 'Lavador eliminado definitivamente.';
        document.getElementById('panelDocumentosLavador').hidden = true;
        selector.value = '';
        cargarLavadores();
        boton.disabled = false;
      })
      .withFailureHandler(function(error) {
        mensaje.textContent = 'No se pudo eliminar definitivamente: ' + mostrarError(error);
        boton.disabled = false;
      })
      .eliminarLavadorDefinitivo(idLavador);
  });

  function cargarDocumentosLavador() {
    const id = document.getElementById('selectorLavador').value, panel = document.getElementById('panelDocumentosLavador'), tabla = document.getElementById('tablaDocumentosLavador');
    tabla.replaceChildren(); panel.hidden = !id; if (!id) return;
    const trCarga = document.createElement('tr'), tdCarga = document.createElement('td'); tdCarga.colSpan = 6; tdCarga.textContent = 'Cargando documentos...'; trCarga.appendChild(tdCarga); tabla.appendChild(trCarga);
    google.script.run.withSuccessHandler(function(docs) {
      tabla.replaceChildren();
      (docs || []).forEach(function(doc) {
        const tr = document.createElement('tr');
        const tdTipo = document.createElement('td'); tdTipo.textContent = doc.tipo; tr.appendChild(tdTipo);
        const tdRequisito = document.createElement('td');
        const selector = document.createElement('select');
        const opObligatorio = document.createElement('option'); opObligatorio.value = 'true'; opObligatorio.textContent = 'Obligatorio'; selector.appendChild(opObligatorio);
        const opNoAplica = document.createElement('option'); opNoAplica.value = 'false'; opNoAplica.textContent = 'No aplica'; selector.appendChild(opNoAplica);
        selector.value = doc.requerido === false ? 'false' : 'true';
        selector.addEventListener('change', function() {
          selector.disabled = true;
          google.script.run.withSuccessHandler(function(r) {
            cargarDocumentosLavador();
            cargarResumenPersonal();
            cargarEstadoDocumentacion();
            const aviso = document.getElementById('mensajeDocumentoLavador'); if (aviso) aviso.textContent = r.mensaje;
          }).withFailureHandler(function(error) {
            selector.disabled = false; selector.value = doc.requerido === false ? 'false' : 'true';
            const aviso = document.getElementById('mensajeDocumentoLavador'); if (aviso) aviso.textContent = 'No se pudo actualizar el requisito: ' + mostrarError(error);
          }).guardarAplicabilidadDocumento(id, doc.tipo, selector.value === 'true');
        });
        tdRequisito.appendChild(selector); tr.appendChild(tdRequisito);
        const estado = doc.requerido === false ? 'No aplica' : doc.estado;
        [estado, doc.fechaEmision || '—', doc.noVence ? 'No vence' : (doc.fechaVencimiento || '—')].forEach(function(valor) { const td = document.createElement('td'); td.textContent = valor; tr.appendChild(td); });
        const tdArchivo = document.createElement('td');
        if (doc.url) { const a = document.createElement('a'); a.href = doc.url; a.target = '_blank'; a.rel = 'noopener'; a.textContent = doc.nombreArchivo || 'Abrir archivo'; tdArchivo.appendChild(a); } else tdArchivo.textContent = 'Sin archivo';
        tr.appendChild(tdArchivo); tabla.appendChild(tr);
      });
    }).withFailureHandler(function(error) { tabla.replaceChildren(); const tr = document.createElement('tr'), td = document.createElement('td'); td.colSpan = 6; td.textContent = 'No se pudieron cargar los documentos: ' + mostrarError(error); tr.appendChild(td); tabla.appendChild(tr); }).obtenerDocumentosLavador(id);
  }

  document.getElementById('documentoNoVence').addEventListener('change', function() {
    const campo = document.getElementById('fechaVencimientoDocumento'); campo.disabled = this.checked; if (this.checked) campo.value = '';
  });

  document.getElementById('formDocumentoLavador').addEventListener('submit', function(event) {
    event.preventDefault();
    const idLavador = document.getElementById('selectorLavador').value, tipo = document.getElementById('tipoDocumentoLavador').value;
    const fechaEmision = document.getElementById('fechaEmisionDocumento').value, noVence = document.getElementById('documentoNoVence').checked;
    const fechaVencimiento = document.getElementById('fechaVencimientoDocumento').value, input = document.getElementById('archivoDocumentoLavador'), archivo = input.files && input.files[0];
    const boton = document.getElementById('btnGuardarDocumentoLavador'), mensaje = document.getElementById('mensajeDocumentoLavador');
    if (!idLavador) { mensaje.textContent = 'Selecciona un lavador.'; return; }
    if (!archivo) { mensaje.textContent = 'Selecciona el archivo del documento.'; return; }
    if (archivo.size > 6 * 1024 * 1024) { mensaje.textContent = 'El archivo supera los 6 MB. Reduce su tamaño antes de subirlo.'; return; }
    if (!noVence && !fechaVencimiento) { mensaje.textContent = 'Indica la fecha de vencimiento o marca «No vence».'; return; }
    boton.disabled = true; mensaje.textContent = 'Subiendo documento a Google Drive...';
    const reader = new FileReader();
    reader.onload = function() {
      const base64 = String(reader.result).split(',')[1];
      google.script.run.withSuccessHandler(function(r) {
        mensaje.textContent = r.mensaje || 'Documento guardado.';
        document.getElementById('formDocumentoLavador').reset(); document.getElementById('fechaVencimientoDocumento').disabled = false;
        boton.disabled = false; cargarDocumentosLavador(); cargarEstadoDocumentacion();
      }).withFailureHandler(function(error) { mensaje.textContent = 'No se pudo guardar el documento: ' + mostrarError(error); boton.disabled = false; })
        .guardarDocumentoLavador({idLavador:idLavador, tipo:tipo, fechaEmision:fechaEmision, fechaVencimiento:fechaVencimiento, noVence:noVence,
          archivo:{nombre:archivo.name,mimeType:archivo.type || 'application/octet-stream',base64:base64}});
    };
    reader.onerror = function() { mensaje.textContent = 'No se pudo leer el archivo.'; boton.disabled = false; };
    reader.readAsDataURL(archivo);
  });

  document.getElementById('btnProbarAvisoPersonal').addEventListener('click', function() {
    const boton = this, mensaje = document.getElementById('mensajeAvisoPersonal'); boton.disabled = true; mensaje.textContent = 'Revisando documentación...';
    google.script.run.withSuccessHandler(function(r) { mensaje.textContent = r.mensaje || 'Revisión completada.'; boton.disabled = false; })
      .withFailureHandler(function(error) { mensaje.textContent = 'Falló la revisión: ' + mostrarError(error); boton.disabled = false; }).probarAvisoPersonal();
  });

  function abrirDetalleDocumentos(tipo) {
    tipoAlertaDocumentosActual = tipo || 'faltantes';
    mostrarVista('detalleDocumentos');
  }

  function cargarDetalleDocumentos(tipo) {
    tipoAlertaDocumentosActual = tipo || tipoAlertaDocumentosActual;
    const nombres = {
      faltantes: ['Documentos faltantes', 'Lavadores activos que todavía no tienen cargado el archivo obligatorio.'],
      porVencer: ['Documentos por vencer en 30 días', 'Documentos con vencimiento desde hoy hasta los próximos 30 días.'],
      vencidos: ['Documentos vencidos', 'Documentos cuya fecha de vencimiento ya pasó.']
    };
    const info = nombres[tipoAlertaDocumentosActual] || nombres.faltantes;
    document.getElementById('tituloDetalleDocumentos').textContent = info[0];
    document.getElementById('descripcionDetalleDocumentos').textContent = info[1];
    const tabla = document.getElementById('tablaDetalleDocumentos');
    const mensaje = document.getElementById('mensajeDetalleDocumentos');
    tabla.replaceChildren(); mensaje.textContent = 'Cargando documentación...';
    const carga = document.createElement('tr'), celda = document.createElement('td');
    celda.colSpan = 5; celda.textContent = 'Cargando lista...'; carga.appendChild(celda); tabla.appendChild(carga);
    google.script.run.withSuccessHandler(function(items) {
      tabla.replaceChildren(); mensaje.textContent = (items || []).length + ' registro(s).';
      if (!(items || []).length) {
        const tr = document.createElement('tr'), td = document.createElement('td');
        td.colSpan = 5; td.textContent = 'No hay documentos en esta categoría.'; tr.appendChild(td); tabla.appendChild(tr); return;
      }
      items.forEach(function(item) {
        const tr = document.createElement('tr');
        [item.nombreLavador, item.tipo, item.estado, item.fechaVencimiento || '—'].forEach(function(valor) {
          const td = document.createElement('td'); td.textContent = valor; tr.appendChild(td);
        });
        const tdAccion = document.createElement('td');
        if (item.url) {
          const a = document.createElement('a'); a.href = item.url; a.target = '_blank'; a.rel = 'noopener'; a.textContent = 'Abrir archivo'; tdAccion.appendChild(a);
          tdAccion.appendChild(document.createTextNode(' · '));
        }
        const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'secondary'; btn.textContent = 'Abrir ficha';
        btn.addEventListener('click', function() { abrirFichaLavador(item.idLavador); });
        tdAccion.appendChild(btn); tr.appendChild(tdAccion); tabla.appendChild(tr);
      });
    }).withFailureHandler(function(error) {
      tabla.replaceChildren(); mensaje.textContent = 'No se pudo cargar el detalle: ' + mostrarError(error);
      const tr = document.createElement('tr'), td = document.createElement('td'); td.colSpan = 5; td.textContent = 'Error al consultar los documentos.'; tr.appendChild(td); tabla.appendChild(tr);
    }).obtenerDetalleDocumentos(tipoAlertaDocumentosActual);
  }

  function abrirFichaLavador(idLavador) {
    lavadorParaSeleccionar = idLavador;
    mostrarVista('personal');
  }

  function cargarResumenPersonal() {
    const tabla = document.getElementById('tablaResumenDocumentos');
    const mensaje = document.getElementById('mensajeResumenPersonal');
    if (mensaje) mensaje.textContent = '';
    google.script.run.withSuccessHandler(function(resumen) {
      document.getElementById('totalLavadoresDashboard').textContent = resumen.totalLavadores;
      document.getElementById('totalFaltantesDashboard').textContent = resumen.totalFaltantes;
      document.getElementById('totalPorVencerDashboard').textContent = resumen.totalPorVencer;
      document.getElementById('totalVencidosDashboard').textContent = resumen.totalVencidos;
      tabla.replaceChildren();
      (resumen.porTipo || []).forEach(function(item) {
        const tr = document.createElement('tr');
        const tdTipo = document.createElement('td'); tdTipo.textContent = item.tipo; tr.appendChild(tdTipo);
        const tdFaltantes = document.createElement('td'); tdFaltantes.textContent = item.faltantes; tr.appendChild(tdFaltantes);
        tabla.appendChild(tr);
      });
      if (!(resumen.porTipo || []).length) {
        const tr = document.createElement('tr'); const td = document.createElement('td'); td.colSpan = 2; td.textContent = 'No hay documentos obligatorios configurados.'; tr.appendChild(td); tabla.appendChild(tr);
      }
    }).withFailureHandler(function(error) {
      tabla.replaceChildren(); const tr = document.createElement('tr'); const td = document.createElement('td'); td.colSpan = 2; td.textContent = 'No se pudo cargar el resumen de lavadores.'; tr.appendChild(td); tabla.appendChild(tr);
      if (mensaje) mensaje.textContent = mostrarError(error);
    }).obtenerResumenPersonal();
  }

  window.addEventListener('load',function() {
    mostrarVista('inicio');
    cargarInventario();
    cargarSolicitudes();
    cargarResumenPersonal();
  });
</script>
</body>
</html>

131,582 chars · codepastes.com