/* tablero-acks.jsx — seguimiento real de notificaciones y confirmaciones WhatsApp. */
(function () {
  var React = window.React, h = React.createElement, ui = window.CR.ui;
  var useState = React.useState, useEffect = React.useEffect;
  function valor(r, k, alt) { return (r.datos && r.datos[k]) || r[k] || alt || '—'; }


  function conVuelta(r, texto) {
    var c = r && r.datos && r.datos.cubierta;
    if (!c || typeof c !== 'object') return texto;
    return h('span', null, texto, ' ', h('span', { className: 'badge badge--info', style: { fontSize: 11 }, 'data-cubierta-ack': r.rutaId,
      title: 'Fuerza mayor: esta vuelta la hace ' + (c.patente || 'otra unidad') + '. Motivo: ' + (c.motivo || 'sin motivo') },
      '⇄ cubre ' + (c.patente || 'otra unidad') + (c.titular && c.titular.patente ? ' (titular ' + c.titular.patente + ')' : '')));
  }
  function horaArt(r) {
    var value = r.recibido_en || r.recibidoEn || r.ts || r.created_at || r.createdAt;
    if (!value) return '—';
    var d = new Date(value);
    if (isNaN(d.getTime())) return '—'; // valor truthy pero no-fecha (ej. {} que manda el backend) NO debe romper la pantalla
    return new Intl.DateTimeFormat('es-AR', {
      timeZone: 'America/Argentina/Buenos_Aires', hour: '2-digit', minute: '2-digit', hour12: false
    }).format(d);
  }
  function resumenAprobacion(r) {
    var aprobadas = Number(r && r.rutasAprobadas) || 0;
    var yaAprobadas = Number(r && r.rutasYaAprobadas) || 0;
    var motivo = r && r.motivo ? String(r.motivo) : 'sin_motivo_informado';
    if (aprobadas > 0) return 'Éxito: ' + aprobadas + ' ruta(s) recién aprobada(s); ' + yaAprobadas + ' ya estaba(n) aprobada(s). Motivo: ' + motivo + '.';
    if (yaAprobadas > 0) return 'Sin cambios: 0 rutas recién aprobadas; ' + yaAprobadas + ' ya estaba(n) aprobada(s). Motivo: ' + motivo + '.';
    return 'Sin cambios: no se auto-aprobaron rutas (0 recién aprobadas; 0 ya aprobadas). Motivo: ' + motivo + '.';
  }


  /* Seccion plegable con buscador. Reemplaza el patron Card + DataTable que se repetia en 5 bloques.
     Tres decisiones:
     · Arranca ABIERTA solo si tiene filas. Una seccion vacia plegada no esconde nada — el contador
       del encabezado sigue diciendo 0 — y deja de robar pantalla en una jornada de 75 rutas.
     · El buscador filtra sobre TODA la fila, no sobre una columna: se busca una patente, un nombre o
       un pedazo del mensaje sin pensar en que columna cae.
     · El encabezado dice cuantas hay y cuantas se ven. Si el filtro deja 3 de 40, se ve que hay 40. */
  /* Como se lee el filtro activo en el aviso. Tiene que decir lo MISMO que la tarjeta que lo puso;
     si no, el operador ve "hay filas ocultas" y no sabe por que. */
  var ETIQUETA_FOCO = {
    notificadas: 'Rutas notificadas', conOk: 'Con ok', sinOk: 'Sin ok',
    fallidas: 'Envíos fallidos', sinNotificar: 'Asignadas sin notificar',
    atribuidosSinEnvio: 'ACK atribuido sin envío',
    noReconocidos: 'Mensajes no reconocidos', pendientes: 'Sin atribuir / ambiguos',
    desestimadas: 'Desestimadas'
  };

  /* El motivo no es una etiqueta: es la accion. Cada uno se arregla en una pantalla distinta, y sin
     eso el operador aprieta "Notificar" toda la mañana sobre una ruta que nunca va a salir. */
  var MOTIVO_SIN_NOTIFICAR = {
    chofer_sin_grupo_vinculado: {
      texto: 'el chofer no tiene grupo de WhatsApp vinculado',
      como: 'Se vincula en Onboarding de grupos.'
    },
    zona_sin_destino: {
      texto: 'la ruta no tiene chofer y la zona no tiene grupo de destino',
      como: 'Se asigna un chofer, o se configura el destino de esa zona.'
    },
    no_se_notifico: {
      texto: 'había destino y el aviso no salió',
      como: 'Suele pasar cuando se asignó después de notificar: alcanza con volver a notificar.'
    }
  };

  function SeccionTabla(props) {
    var abs = useState(props.abiertaSiempre === true || (props.rows || []).length > 0);
    var abierta = abs[0], setAbierta = abs[1];
    /* Abrir por senal externa va en un EFECTO, no en el inicializador de arriba: ese corre una sola
       vez, en el primer montaje, y no se entera de nada que pase despues. Es exactamente el bug de
       v0.42.4 — la cola arrancaba plegada con 17 pendientes porque la condicion miraba datos que
       todavia no habian llegado. */
    useEffect(function () { if (props.abrir) setAbierta(true); }, [props.abrir]);
    var qs2 = useState(''); var q = qs2[0], setQ = qs2[1];
    var todas = props.rows || [];
    var texto = q.trim().toLowerCase();
    var filas = texto
      ? todas.filter(function (r) {
          try { return JSON.stringify(r).toLowerCase().indexOf(texto) >= 0; } catch (_) { return true; }
        })
      : todas;
    // `data-novedad`: donde apunta el «Mostrame dónde» del popup de novedades (sólo el atributo)



    return h('div', { className: 'card', 'data-novedad': props.novedad || undefined },
      h('button', {
        type: 'button', className: 'seccion-plegable',
        'aria-expanded': abierta ? 'true' : 'false',
        style: { display: 'flex', alignItems: 'center', gap: 8, width: '100%', background: 'none',
                 border: 0, padding: '2px 0 8px', cursor: 'pointer', textAlign: 'left', font: 'inherit', color: 'inherit' },
        onClick: function () { setAbierta(!abierta); }
      },
        h('span', { style: { fontSize: 12, opacity: 0.7 } }, abierta ? '\u25be' : '\u25b8'),
        h('b', null, props.title),
        h('span', { className: 'muted', style: { marginLeft: 'auto', fontSize: 12 } },
          texto ? (filas.length + ' de ' + todas.length) : String(todas.length))),
      abierta ? h('div', null,
        todas.length > 4 ? h('input', {
          className: 'input', type: 'search', value: q,
          placeholder: 'Buscar en ' + props.title.toLowerCase() + '\u2026',
          'aria-label': 'Buscar en ' + props.title,
          style: { marginBottom: 8 },
          onChange: function (e) { setQ(e.target.value); }
        }) : null,
        h(ui.DataTable, {
          rows: filas, rowKey: props.rowKey, columns: props.columns,
          empty: texto && !filas.length ? 'Ninguna coincide con la búsqueda' : props.empty
        })) : null);
  }

  function TableroAcks(props) {




  function puedeConfigMatcher() {
    var secs = (props.session && props.session.secciones) || [];
    return secs.indexOf('ack-matcher-config') !== -1;
  }
    /* Arranca SIN fecha: el servidor resuelve la jornada OPERATIVA (la que se esta trabajando) y la
       devuelve; recien ahi se fija el selector. Con hoy() y la planilla de manana cargada, este
       tablero mostraba un dia ya cerrado y avisado (v0.68.1). */
    var fs = useState(''), fecha = fs[0], setFecha = fs[1];
    var ds = useState(null), data = ds[0], setData = ds[1];
    var bs = useState(false), busy = bs[0], setBusy = bs[1];
    var es = useState(null), err = es[0], setErr = es[1];
    var ms = useState(null), msg = ms[0], setMsg = ms[1];
    var mans = useState(null), manual = mans[0], setManual = mans[1];
    var ns = useState('Confirmación telefónica'), nota = ns[0], setNota = ns[1];
    var gs = useState(false), grupo = gs[0], setGrupo = gs[1];
    var gis = useState(''), grupoId = gis[0], setGrupoId = gis[1];
    var zs = useState(''), zona = zs[0], setZona = zs[1];
    var as = useState(''), actor = as[0], setActor = as[1];
    var ws = useState(null), wa = ws[0], setWa = ws[1];
    var cs = useState([]), choferes = cs[0], setChoferes = cs[1];
    var qs = useState({}), buscas = qs[0], setBuscas = qs[1];
    var ss = useState({}), selecciones = ss[0], setSelecciones = ss[1];
    var dus = useState(null), altaDueno = dus[0], setAltaDueno = dus[1];
    var mts = useState(null), matcher = mts[0], setMatcher = mts[1];
    var dfs = useState(null), defaultsMatcher = dfs[0], setDefaultsMatcher = dfs[1];
    var ats = useState(null), agregarToken = ats[0], setAgregarToken = ats[1];
    var ets = useState(''), tokenEditado = ets[0], setTokenEditado = ets[1];
    /* Que tarjeta del resumen esta actuando como filtro. '' = ninguna, se ve todo. */
    var fcs = useState(''), foco = fcs[0], setFoco = fcs[1];
    var mtts = useState(''), matcherTokensText = mtts[0], setMatcherTokensText = mtts[1];
    var mnts = useState(''), matcherNegacionesText = mnts[0], setMatcherNegacionesText = mnts[1];

    function cargar() {
      setBusy(true); setErr(null);
      return Promise.all([
        window.CR.backend.tableroAcks(fecha),
        window.CR.backend.flotaReal(),
        // ya no se pide /agencias: el dueño se registra contra el grupo del que vino el mensaje
        puedeConfigMatcher() ? window.CR.backend.ackMatcher() : Promise.resolve(null)
      ]).then(function (xs) {
        var tablero = xs[0], flota = xs[1] || [], porId = {};
        if (!fecha && tablero && tablero.fecha) setFecha(tablero.fecha);
        (tablero.rutas || []).forEach(function (r) {
          if (r.choferId) porId[r.choferId] = { id: r.choferId, nombre: valor(r, 'chofer', r.choferId), patente: valor(r, 'patente', '') };
        });
        flota.forEach(function (v) {
          var id = v.choferId || v.chofer_id;
          if (id) porId[id] = { id: id, nombre: v.nombre || id, patente: v.patente || '' };
        });
        setChoferes(Object.keys(porId).map(function (id) { return porId[id]; }).sort(function (a, b) { return a.nombre.localeCompare(b.nombre); }));
        if (xs[2]) {
          setMatcher(xs[2].matcher || { tokens: [], negaciones: [] });
          setDefaultsMatcher(xs[2].defaults || { tokens: [], negaciones: [] });
          setMatcherTokensText((xs[2].matcher && xs[2].matcher.tokens || []).join('\n'));
          setMatcherNegacionesText((xs[2].matcher && xs[2].matcher.negaciones || []).join('\n'));
        }
        setData(tablero);
      }).catch(function (e) { setData(null); setErr(e.message); }).then(function () { setBusy(false); });
    }
    function cargarWa() { window.CR.backend.waStatus().then(setWa); }
    useEffect(function () { cargar(); cargarWa(); }, [fecha]);
    function reconsultar() {
      setBusy(true); setErr(null); setMsg(null);
      window.CR.backend.notificar(data.jornadaId, true).then(function (r) {
        setMsg('Reconsulta procesada: ' + (r.enviados || 0) + ' enviadas, ' + (r.omitidos || 0) + ' omitidas y ' + (r.fallidos || 0) + ' fallidas.');
        return cargar();
      }).catch(function (e) { setErr(e.message); setBusy(false); });
    }


    function notificarUna(r) {
      if (!data || !data.jornadaId || !r || !r.rutaId) return;
      var nombre = r.idRuta || r.rutaId;
      if (!window.confirm('Vas a enviar el WhatsApp REAL de la ruta ' + nombre + ' al grupo de ' + (r.chofer || 'su chofer') + '. No se puede deshacer. ¿Confirmás?')) return;
      setBusy(true); setErr(null); setMsg(null);
      window.CR.backend.notificarJornada(data.jornadaId, false, [r.rutaId]).then(function (res) {
        var fila = (res.porRuta || []).filter(function (x) { return x.rutaId === r.rutaId; })[0];
        var estado = fila ? fila.estado : ((res.noEncontradas || []).length ? 'no_encontrada' : 'sin_resultado');
        setMsg(estado === 'ok' ? 'Aviso enviado para ' + nombre + '.'
          : estado === 'skip' ? 'La ruta ' + nombre + ' ya tenía su aviso para este chofer: no se repitió.'
          : estado === 'pendiente_grupo' ? 'No salió: el chofer de ' + nombre + ' no tiene grupo de WhatsApp vinculado (se vincula en Onboarding).'
          : estado === 'error' ? 'El envío de ' + nombre + ' falló: revisá la conexión de WhatsApp y reintentá.'
          : 'No pude notificar ' + nombre + ' (' + estado + ').');
        return cargar();
      }).catch(function (e) { setErr('No pude notificar la ruta: ' + e.message); setBusy(false); });
    }
    function guardarManual() {
      setBusy(true); setErr(null);
      window.CR.backend.marcarAckManual({ jornadaId: data.jornadaId, rutaId: manual.rutaId, nota: nota.trim(), actor: actor.trim() || undefined })
        .then(function () { setManual(null); setMsg('ACK manual auditado.'); return cargar(); })
        .catch(function (e) { setErr(e.message); setBusy(false); });
    }
    function activarGrupo() {
      setBusy(true); setErr(null);
      window.CR.backend.activarGrupo({ grupoId: grupoId.trim(), macroZona: zona.trim(), actor: actor.trim() || undefined, confirmado: true })
        .then(function (r) { setGrupo(false); setGrupoId(''); setZona(''); setMsg('Grupo real activado: ' + ((r.grupo && r.grupo.nombre) || 'confirmado') + '.'); setBusy(false); })
        .catch(function (e) { setErr(e.message); setBusy(false); });
    }
    function agruparPendientes(xs) {
      var porSender = {};
      (xs || []).forEach(function (a) {
        var sender = a.sender_jid || 'sin-remitente';
        if (!porSender[sender]) porSender[sender] = Object.assign({}, a, { cantidad: 0 });
        porSender[sender].cantidad++;
      });
      return Object.keys(porSender).map(function (k) { return porSender[k]; });
    }
    /* UNA sola fuente para "a quién se le va a atribuir": lo que muestra el select, lo que habilita
       el botón y lo que se manda al servidor. Si el sugerido se pintara sólo en el select, el
       operador vería un chofer elegido con el botón apagado — la pantalla diciendo algo que el
       estado no sostiene, que es la familia de bugs que este sistema viene arrastrando.
       Precedencia: lo que eligió la persona SIEMPRE gana sobre la sugerencia. */
    function elegido(a) { return selecciones[a.sender_jid] || a.sugerido_id || ''; }

    function atribuir(a) {
      var choferId = elegido(a);
      if (!choferId) return;
      if (a.nota && (a.nota === 'jornada_ambigua' || a.nota === 'cita_no_encontrada')
          && !window.confirm('Este OK no identifica un día con seguridad. ¿Confirmás atribuirlo al chofer elegido en la jornada ' + (data.fecha || fecha) + '?')) return;
      setBusy(true); setErr(null); setMsg(null);
      window.CR.backend.marcarAckManual({ ackId: a.id, choferId: choferId, jornadaId: data.jornadaId }).then(function () {
        setMsg('Respuesta atribuida al chofer sin salir del tablero.');
        return cargar();
      }).catch(function (e) { setErr(e.message); setBusy(false); });
    }
    function abrirAgregarToken(a) {
      var texto = String(a.mensaje_raw || '').trim();
      setAgregarToken(a); setTokenEditado(texto);
    }
    function guardarToken() {
      var token = tokenEditado.trim();
      if (!token) return;
      setBusy(true); setErr(null); setMsg(null);
      window.CR.backend.agregarTokenAckMatcher(token).then(function (r) {
        setAgregarToken(null);
        setMatcher((r && r.matcher) || matcher);
        if (r && r.matcher) {
          setMatcherTokensText((r.matcher.tokens || []).join('\n'));
          setMatcherNegacionesText((r.matcher.negaciones || []).join('\n'));
        }
        setMsg(r && r.agregado === false ? 'La palabra ya estaba aceptada.' : 'Palabra agregada. Los próximos mensajes podrán reconocerse como OK.');
        setBusy(false);
      }).catch(function (e) { setErr(e.message); setBusy(false); });
    }
    function lineas(value) {
      return String(value || '').split(/\r?\n/).map(function (x) { return x.trim(); }).filter(Boolean);
    }
    function guardarMatcher() {
      var actualizado = { tokens: lineas(matcherTokensText), negaciones: lineas(matcherNegacionesText) };
      setBusy(true); setErr(null); setMsg(null);
      window.CR.backend.guardarAckMatcher(actualizado).then(function (r) {
        setMatcher(r.matcher); setMsg('Configuración del matcher guardada y auditada.'); setBusy(false);
      }).catch(function (e) { setErr(e.message); setBusy(false); });
    }
    function registrarDueno() {
      var lid = altaDueno.senderLid;
      setBusy(true); setErr(null); setMsg(null);
      var grupo = altaDueno.grupo;
      window.CR.backend.agregarGrupoDueno({ grupoId: altaDueno.grupoId, senderLid: lid, nombre: altaDueno.nombre.trim() })
        .then(function (r) {
          setAltaDueno(null);
          setMsg(lid + ' quedó como dueño de ' + grupo + '. ' + resumenAprobacion(r));
          return cargar();
        }).catch(function (e) { setErr(e.message); setBusy(false); });
    }
    function estadoAck(r) {
      var estado = r.estadoAck || (r.conOk ? 'ok_del_chofer' : 'sin_ok');
      if (estado === 'aprobada_por_agencia' || estado === 'aprobado_por_agencia') {
        return h(ui.Badge, { color: 'ok' }, 'Aprobada por agencia' + (r.ackDueno ? ' (dueño ' + r.ackDueno + ')' : ''));
      }
      if (estado === 'ok_del_chofer') {




        if (r.ackMotivo === 'nombre_en_mensaje') return h(ui.Badge, { color: 'ok',
          title: 'Este teléfono lo comparten dos choferes. El OK se atribuyó por el nombre escrito en el mensaje, no por el teléfono.' },
          'OK del chofer (por el nombre en el mensaje: «' + (r.ackToken || '?') + '»)');
        return h(ui.Badge, { color: 'ok' }, 'OK del chofer');
      }
      return h(ui.Badge, { color: 'warn' }, 'Sin OK');
    }
    function contadoresAgencia(xs) {
      var grupos = {};
      xs.forEach(function (r) {
        var grupo = r.agencia || r.grupoNombre || r.grupo || r.grupoId;
        if (!grupo || !((r.estadoAck || '').indexOf('agencia') >= 0 || r.ackOrigen === 'agencia')) return;
        if (!grupos[grupo]) grupos[grupo] = { nombre: grupo, total: 0, aprobadas: 0 };
        grupos[grupo].total++;
        if (r.estadoAck === 'aprobada_por_agencia' || r.estadoAck === 'aprobado_por_agencia') grupos[grupo].aprobadas++;
      });
      return Object.keys(grupos).map(function (k) { return grupos[k]; });
    }
    var rutas = (data && data.rutas) || [], pendientes = agruparPendientes((data && data.sinAtribuir) || []);
    var fallidas = (data && data.fallidas) || [];
    var atribuidosSinEnvio = (data && data.atribuidosSinEnvio) || [];
    var sinNotificar = (data && data.sinNotificar) || [];
    var asignadas = (data && data.asignadas) || 0;
    var noReconocidos = (data && data.noReconocidos) || [];


    var desestimadas = (data && data.desestimadas) || [];
    /* Toggle: tocar la tarjeta activa la saca. */
    function tocarTarjeta(clave) {
      return function () { setFoco(function (actual) { return actual === clave ? '' : clave; }); }
    }
    /* Las tres primeras tarjetas apuntan a la MISMA seccion con distinto recorte. Las otras cuatro
       son secciones propias: ahi la tarjeta solo abre, no filtra. */
    var rutasVisibles = foco === 'conOk' ? rutas.filter(function (r) { return !!r.conOk; })
      : foco === 'sinOk' ? rutas.filter(function (r) { return !r.conOk; })
      : rutas;
    var gruposAgencia = contadoresAgencia(rutas);
    return h('div', null,
      h(ui.Page, { title: 'Tablero de ACKs', subtitle: 'Datos reales derivados de sends y acks para la jornada elegida.' }),
      h(ui.Toolbar, null,
        h(ui.Field, { label: 'Jornada' }, h(ui.Input, { type: 'date', value: fecha, onChange: setFecha, disabled: busy })),
        h(ui.Badge, { color: wa && wa.ready ? 'ok' : 'danger', dot: true }, wa && wa.ready ? 'WhatsApp conectado' : 'WhatsApp sin conexión'),
        h('div', { className: 'spacer' }),
        h(ui.Button, { variant: 'ghost', onClick: cargarWa }, 'Actualizar WA'),
        h(ui.Button, { onClick: function () { setGrupo(true); } }, 'Activar grupo real'),
        h(ui.Button, { variant: 'primary', onClick: reconsultar, disabled: busy || !data || !data.sinOk }, busy ? 'Procesando…' : 'Re-consultar pendientes')),
      err ? h('div', { className: 'alerta alerta--alta', role: 'alert' }, err) : null,
      msg ? h('div', { className: 'alerta alerta--baja', role: 'status' }, msg) : null,









      data ? h('div', { className: 'grid grid--stats' },



        h(ui.StatCard, { label: 'Rutas asignadas', value: asignadas,
          titulo: 'Rutas con patente asignada en esta jornada' }),
        h(ui.StatCard, { label: 'Rutas notificadas', value: data.notificados || 0,
          activo: foco === 'notificadas', onClick: tocarTarjeta('notificadas'),
          titulo: 'Ver todas las rutas notificadas' }),
        h(ui.StatCard, { label: 'Asignadas sin notificar', value: sinNotificar.length,
          tone: sinNotificar.length ? 'danger' : 'ok',
          activo: foco === 'sinNotificar', onClick: tocarTarjeta('sinNotificar'),
          titulo: 'Ver qué ruta quedó sin aviso y por qué' }),
        h(ui.StatCard, { label: 'Con ok', value: data.conOk || 0, tone: 'ok',
          activo: foco === 'conOk', onClick: tocarTarjeta('conOk'),
          titulo: 'Ver solo las rutas que ya confirmaron' }),
        h(ui.StatCard, { label: 'Sin ok', value: data.sinOk || 0, tone: data.sinOk ? 'danger' : 'ok',
          activo: foco === 'sinOk', onClick: tocarTarjeta('sinOk'),
          titulo: 'Ver solo las rutas que todavia no confirmaron' }),
        h(ui.StatCard, { label: 'Envíos fallidos', value: fallidas.length, tone: fallidas.length ? 'danger' : 'ok',
          activo: foco === 'fallidas', onClick: tocarTarjeta('fallidas') }),
        h(ui.StatCard, { label: 'ACK atribuido sin envío', value: atribuidosSinEnvio.length, tone: atribuidosSinEnvio.length ? 'danger' : 'ok',
          activo: foco === 'atribuidosSinEnvio', onClick: tocarTarjeta('atribuidosSinEnvio') }),
        h(ui.StatCard, { label: 'Mensajes no reconocidos', value: noReconocidos.length, tone: noReconocidos.length ? 'warn' : 'ok',
          activo: foco === 'noReconocidos', onClick: tocarTarjeta('noReconocidos') }),
        h(ui.StatCard, { label: 'Sin atribuir / ambiguos', value: pendientes.length, tone: pendientes.length ? 'danger' : 'ok',
          activo: foco === 'pendientes', onClick: tocarTarjeta('pendientes') }),
        h(ui.StatCard, { label: 'Desestimadas', value: desestimadas.length,
          activo: foco === 'desestimadas', onClick: tocarTarjeta('desestimadas'),
          titulo: 'Rutas que no hacemos nosotros (MELI las cubrió con otra empresa): no se avisan ni cuentan como asignadas' })) : null,
      foco ? h('div', { className: 'alerta alerta--baja', role: 'status',
        style: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' } },
        h('span', null, 'Filtrando por ', h('b', null, ETIQUETA_FOCO[foco] || foco), ' — hay filas ocultas.'),
        h(ui.Button, { variant: 'ghost', size: 'sm', onClick: function () { setFoco(''); } }, 'Ver todo')) : null,
      gruposAgencia.length ? h(ui.Card, { title: 'Aprobación por agencia' }, h('div', { className: 'grid grid--stats' },
        gruposAgencia.map(function (g) { return h(ui.StatCard, { key: g.nombre, label: g.nombre, value: g.aprobadas + '/' + g.total + ' aprobadas', tone: g.aprobadas === g.total ? 'ok' : 'danger' }); }))) : null,
      h(SeccionTabla, { title: 'Rutas notificadas', rows: rutasVisibles, abrir: (foco === 'notificadas' || foco === 'conOk' || foco === 'sinOk'), rowKey: 'rutaId', empty: busy ? 'Cargando…' : 'No hay rutas notificadas para esta jornada', columns: [
        { key: 'idRuta', label: 'Ruta', render: function (r) { return conVuelta(r, valor(r, 'idRuta')); } },
        { key: 'chofer', label: 'Chofer', render: function (r) { return valor(r, 'chofer', r.choferId || 'Sin chofer'); } },





        { key: 'aviso', label: 'Aviso', render: function (r) {
          if (r.notificadaPorAgencia) return h(ui.Badge, { color: 'info',
            title: 'El aviso llegó al grupo de la agencia. La ruta cambió de chofer después, dentro de la misma agencia.' },
            'Avisada a la agencia (cambió el chofer después del aviso: ' + (r.choferAvisado || 'sin nombre') + ')');
          return h('span', { className: 'muted' }, 'al chofer');
        } },
        { key: 'estado', label: 'Estado', render: estadoAck },
        { key: 'acciones', label: 'Acciones', render: function (r) { return h('div', { style: { display: 'flex', gap: 6 } },
          !r.conOk ? h(ui.Button, { size: 'sm', onClick: function () { setManual(r); } }, 'Marcar manual') : null,
          h(ui.Button, { variant: 'ghost', size: 'sm', onClick: function () { window.location.hash = '#/asignacion-real'; } }, 'Reasignar chofer')); } }
      ] }),
      h(SeccionTabla, { title: 'Rutas asignadas que no se notificaron', rows: sinNotificar,
        abrir: foco === 'sinNotificar', rowKey: 'rutaId',
        empty: busy ? 'Cargando…' : 'Todas las rutas asignadas tienen su aviso', columns: [
        { key: 'idRuta', label: 'Ruta', render: function (r) { return conVuelta(r, r.idRuta || r.rutaId); } },
        { key: 'chofer', label: 'Chofer', render: function (r) { return r.chofer || r.choferId || 'Sin chofer'; } },
        { key: 'patente', label: 'Patente', render: function (r) { return r.patente || '—'; } },
        { key: 'zona', label: 'Zona', render: function (r) { return r.macroZona || '—'; } },
        { key: 'motivo', label: 'Por qué no salió', render: function (r) {
          var m = MOTIVO_SIN_NOTIFICAR[r.motivo];
          /* Un motivo que la pantalla no conoce se muestra CRUDO en vez de esconderse: si mañana el
             servidor agrega uno nuevo, es mejor leer la clave que no ver la fila. */
          return m ? m.texto : (r.motivo || 'sin motivo');
        } },
        { key: 'como', label: 'Dónde se arregla', render: function (r) {
          var m = MOTIVO_SIN_NOTIFICAR[r.motivo];
          return m ? m.como : '—';
        } },
        /* El boton va en TODAS las filas y no solo en `no_se_notifico`: si el operador acaba de
           vincular el grupo en Onboarding, el motivo que ve es el de la carga anterior. El servidor
           resuelve el destino de nuevo y contesta lo que pasa. */
        { key: 'accion', label: 'Acción', render: function (r) {
          return h(ui.Button, { size: 'sm', variant: 'primary', disabled: busy,
            'aria-label': 'Notificar esta ruta ' + (r.idRuta || r.rutaId),
            onClick: function () { notificarUna(r); } }, 'Notificar esta ruta');
        } }
      ] }),

      h(SeccionTabla, { title: 'Rutas con envío fallido', rows: fallidas, abrir: foco === 'fallidas', rowKey: 'rutaId', empty: busy ? 'Cargando…' : 'No hay fallos de notificación para esta jornada', columns: [
        { key: 'idRuta', label: 'Ruta', render: function (r) { return valor(r, 'idRuta', r.rutaId); } },
        { key: 'chofer', label: 'Chofer', render: function (r) { return valor(r, 'chofer', r.choferId || 'Sin chofer'); } },
        { key: 'destino', label: 'Destino', render: function (r) { return valor(r, 'destino', r.destinoJid); } },
        { key: 'error', label: 'Fallo', render: function (r) { return valor(r, 'error', r.mensaje || r.estado || 'Envío fallido'); } },
        { key: 'accion', label: 'Acción', render: function () { return h(ui.Button, { variant: 'ghost', size: 'sm', onClick: reconsultar, disabled: busy }, 'Reintentar'); } }
      ] }),
      h(SeccionTabla, { title: 'ACKs atribuidos sin envío exitoso', rows: atribuidosSinEnvio, abrir: foco === 'atribuidosSinEnvio', rowKey: 'id', empty: busy ? 'Cargando…' : 'No hay confirmaciones atribuidas sin envío', columns: [
        { key: 'idRuta', label: 'Ruta', render: function (a) { return valor(a, 'idRuta', a.rutaId || 'Sin ruta'); } },
        { key: 'chofer', label: 'Chofer', render: function (a) { return valor(a, 'chofer', a.choferId || 'Sin chofer'); } },
        { key: 'sender_jid', label: 'Remitente' },
        { key: 'mensaje_raw', label: 'Mensaje' },
        { key: 'recibido_en', label: 'Hora (ART)', render: horaArt },
        { key: 'estado', label: 'Estado', render: function () { return h(ui.Badge, { color: 'warn' }, 'Atribuido sin envío'); } }
      ] }),

      h(SeccionTabla, { title: 'Rutas desestimadas (no las hacemos nosotros)', rows: desestimadas, abrir: foco === 'desestimadas', rowKey: 'rutaId',
        empty: busy ? 'Cargando…' : 'No hay rutas desestimadas en esta jornada', columns: [
        { key: 'idRuta', label: 'Ruta', render: function (r) { return r.idRuta || r.rutaId; } },
        { key: 'tenia', label: 'Tenía', render: function (r) { return r.patente ? (r.patente + (r.chofer ? ' · ' + r.chofer : '')) : h('span', { className: 'muted' }, 'sin unidad'); } },
        { key: 'motivo', label: 'Motivo', render: function (r) { return r.motivo || h('span', { className: 'muted' }, 'sin motivo'); } },
        { key: 'avisoPrevio', label: 'Aviso al chofer', render: function (r) {
          return r.avisoPrevio
            ? h('span', { title: 'El chofer ya había recibido esta ruta por WhatsApp. El sistema no manda ningún mensaje nuevo: hay que avisarle.' },
                h(ui.Badge, { color: 'warn' }, 'Ya avisado: avisale que no sale'))
            : h('span', { className: 'muted' }, 'no se le había avisado');
        } },
        { key: 'cuando', label: 'Cuándo (ART)', render: function (r) { return horaArt({ ts: r.cuando }); } },
        { key: 'accion', label: 'Acción', render: function () {
          return h(ui.Button, { variant: 'ghost', size: 'sm', onClick: function () { window.location.hash = '#/asignacion-real'; } }, 'Restituir desde Asignación');
        } }
      ] }),
      h(SeccionTabla, { title: 'Mensajes recibidos (no reconocidos como OK)', abrir: foco === 'noReconocidos',
        novedad: 'acks-mensajes-recibidos',
        rows: noReconocidos, rowKey: 'id', empty: busy ? 'Cargando…' : 'No hay mensajes pendientes de interpretar', columns: [
          { key: 'sender_jid', label: 'Remitente', render: function (a) { return a.sender_jid || '—'; } },
          { key: 'mensaje_raw', label: 'Texto', render: function (a) { return h('div', null, a.mensaje_raw || '—',
            a.nota === 'jornada_ambigua' || a.nota === 'cita_no_encontrada' ? h('p', { className: 'muted' },
              'Día sin confirmar. Pedí que use Responder sobre el aviso original, o seleccioná la jornada correcta en el tablero y atribuí el OK. Avisos: ' +
              (a.jornadas_candidatas || []).map(function (j) { return j.fecha; }).join(', ')) : null); } },
          { key: 'grupo', label: 'Grupo', render: function (a) { return a.grupo || a.chat_jid || '—'; } },
          { key: 'recibido_en', label: 'Hora (ART)', render: horaArt },
          { key: 'accion', label: 'Acciones', render: function (a) {
            return h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', minWidth: 220 } },
              h(ui.Select, { value: selecciones[a.sender_jid] || '', placeholder: choferes.length ? 'Elegir chofer…' : 'Sin choferes disponibles', disabled: busy || !choferes.length,
                options: choferes.map(function (c) { return { value: c.id, label: c.nombre + (c.patente ? ' · ' + c.patente : '') }; }),
                onChange: function (v) { var n = Object.assign({}, selecciones); n[a.sender_jid] = v; setSelecciones(n); } }),
              h(ui.Button, { variant: 'primary', size: 'sm', disabled: busy || !selecciones[a.sender_jid], onClick: function () { atribuir(a); } }, 'Tomar como OK'),
              puedeConfigMatcher() ? h(ui.Button, { size: 'sm', disabled: busy, onClick: function () { abrirAgregarToken(a); } }, 'Agregar esta palabra') : null);
          } }
        ]
      }),
      puedeConfigMatcher() && matcher ? h(ui.Card, { title: 'Configuración del matcher de ACKs' },
        h('p', { className: 'muted' }, 'Una entrada por línea. Los valores por defecto siguen visibles como referencia y el guardado queda auditado.'),
        h('div', { className: 'grid grid--2' },
          h(ui.Field, { label: 'Palabras aceptadas' }, h('textarea', { className: 'input', rows: 8, value: matcherTokensText, disabled: busy,
            onChange: function (e) { setMatcherTokensText(e.target.value); } })),
          h(ui.Field, { label: 'Negaciones' }, h('textarea', { className: 'input', rows: 8, value: matcherNegacionesText, disabled: busy,
            onChange: function (e) { setMatcherNegacionesText(e.target.value); } }))),
        h('p', { className: 'muted' }, 'Defaults: ', (defaultsMatcher && defaultsMatcher.tokens || []).join(', ')),
        h(ui.Button, { variant: 'primary', disabled: busy || !lineas(matcherTokensText).length, onClick: guardarMatcher }, 'Guardar configuración')) : null,
      /* `abiertaSiempre` porque esta es la cola de trabajo: es donde se atribuyen los OK que el
         matcher no pudo resolver. Plegada, lo unico visible es el numero del encabezado y las filas
         NO existen en el DOM. El `rows.length > 0` de SeccionTabla no alcanza: el inicializador de
         `useState` corre en el PRIMER montaje, cuando `data` todavia es null y `pendientes` es [],
         asi que esa condicion nunca llega a ser true en produccion — arranca cerrada siempre. */
      h(SeccionTabla, { title: 'ACKs sin atribuir o ambiguos', rows: pendientes, rowKey: 'id', abiertaSiempre: true, abrir: foco === 'pendientes', empty: 'No hay respuestas pendientes de atribución', columns: [
        { key: 'estado', label: 'Estado', render: function (a) { return h(ui.Badge, { color: a.estado === 'ambiguo' ? 'warn' : 'danger' }, a.estado === 'ambiguo' ? 'Ambiguo' : 'Sin atribuir'); } },
        /* El backend ya manda el nombre del grupo (`g.nombre` del left join) y la tabla no lo
           mostraba: quien atribuye veia `15119006326827@lid` y nada mas, que no identifica a nadie.
           El grupo es el dato con el que se reconoce al chofer. Puede venir null —el left join no
           encuentra grupo registrado para ese chat—, y ahi el @lid de al lado es lo unico que hay. */
        { key: 'grupo', label: 'Grupo', render: function (a) {
          return a.grupo ? h('b', null, a.grupo) : h('span', { className: 'muted' }, 'sin grupo registrado'); } },
        { key: 'sender_jid', label: 'Remitente', render: function (a) { return h('span', null, a.sender_jid, a.cantidad > 1 ? h(ui.Badge, { color: 'warn' }, a.cantidad + ' respuestas') : null); } },
        { key: 'mensaje_raw', label: 'Último mensaje' },
        /* Cuando dos choferes comparten el teléfono, el sistema ya no elige por su cuenta — pero
           dice cuál de los dos había sido notificado. Es una recomendación, no un hecho: por eso
           aparece acá y no atribuida sola. El caso que lo originó: JARRYS MAURO y MAZZA LEANDRO
           con el mismo celular, y el 10/08 el OK de uno se le acreditó al otro. */
        { key: 'sugerido_nombre', label: 'Sugerido', render: function (a) {
          if (!a.sugerido_nombre) return h('span', { className: 'muted' }, '—');
          return h(ui.Badge, { color: 'warn', title: 'Fue el notificado en esta jornada. Confirmalo vos: el sistema no lo da por hecho.' },
            a.sugerido_nombre);
        } },
        { key: 'recibido_en', label: 'Hora (ART)', render: horaArt },
        { key: 'accion', label: 'Atribuir', render: function (a) {
          var q = buscas[a.sender_jid] || '';
          var opciones = choferes.filter(function (c) { return !q || (c.nombre + ' ' + c.patente).toLowerCase().indexOf(q.toLowerCase()) >= 0; });
          return h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', minWidth: 310 } },
            h(ui.Input, { value: q, placeholder: 'Buscar chofer…', disabled: busy, onChange: function (v) { var n = Object.assign({}, buscas); n[a.sender_jid] = v; setBuscas(n); } }),
            h(ui.Select, { value: elegido(a), placeholder: opciones.length ? 'Elegir chofer…' : 'Sin coincidencias', disabled: busy || !opciones.length,
              options: opciones.map(function (c) { return { value: c.id, label: c.nombre + (c.patente ? ' · ' + c.patente : '') }; }),
              onChange: function (v) { var n = Object.assign({}, selecciones); n[a.sender_jid] = v; setSelecciones(n); } }),
            h(ui.Button, { variant: 'primary', size: 'sm', disabled: busy || !elegido(a), onClick: function () { atribuir(a); } }, 'Atribuir a chofer'),
            /* El dueño se registra contra EL GRUPO del que vino el mensaje, no contra una agencia
               elegida a mano. Antes pedía agencia y la lista excluye a los particulares, así que un
               grupo de un solo chofer (MLP LEDESMA VICTOR) no se podía atribuir a nadie. */
            puedeConfigMatcher() ? h(ui.Button, {
              size: 'sm', disabled: busy || !a.grupo_id,
              title: a.grupo_id ? 'Registrar a quien mandó este mensaje como dueño de ' + a.grupo
                : 'Este mensaje no vino de un grupo vinculado, así que no hay grupo al que atribuirlo',
              onClick: function () {
                setAltaDueno({ senderLid: a.sender_lid || a.senderLid || a.sender_jid,
                  nombre: a.sender_nombre || a.nombre || '', grupoId: a.grupo_id, grupo: a.grupo,
                  tipo: a.grupo_tipo });
              }
            }, 'Registrar como DUEÑO') : null);
        } }
      ] }),
      h(ui.Modal, { open: !!altaDueno, title: 'Registrar dueño del grupo', onClose: function () { setAltaDueno(null); }, footer: h('div', null,
        h(ui.Button, { variant: 'ghost', onClick: function () { setAltaDueno(null); } }, 'Cancelar'),
        h(ui.Button, { variant: 'primary', disabled: busy || !altaDueno || !altaDueno.grupoId || !altaDueno.nombre.trim(), onClick: registrarDueno }, 'Registrar dueño')) },
        h('p', null, 'Queda como dueño de ', h('b', null, altaDueno && altaDueno.grupo),
          altaDueno && altaDueno.tipo === 'particular'
            ? h('span', { className: 'muted' }, ' (grupo de un solo chofer)')
            : h('span', { className: 'muted' }, ' — su OK va a aprobar todas las rutas de ese grupo')),
        h('p', { className: 'muted' }, 'El LID se toma de este ACK real: ', h('b', null, altaDueno && altaDueno.senderLid)),
        h(ui.Field, { label: 'Nombre del dueño' }, h(ui.Input, { value: altaDueno && altaDueno.nombre || '', placeholder: 'Nombre y apellido', onChange: function (v) { setAltaDueno(Object.assign({}, altaDueno, { nombre: v })); } }))),
      h(ui.Modal, { open: !!agregarToken, title: 'Agregar palabra al matcher', onClose: function () { setAgregarToken(null); }, footer: h('div', null,
        h(ui.Button, { variant: 'ghost', onClick: function () { setAgregarToken(null); } }, 'Cancelar'),
        h(ui.Button, { variant: 'primary', disabled: busy || !tokenEditado.trim(), onClick: guardarToken }, 'Agregar palabra')) },
        h('p', { className: 'muted' }, 'Revisá qué palabra o frase debe tomarse como OK en los próximos mensajes. El mensaje actual no cambia hasta que uses “Tomar como OK”.'),
        h(ui.Field, { label: 'Palabra o frase' }, h(ui.Input, { value: tokenEditado, disabled: busy, onChange: setTokenEditado }))),
      h(ui.Modal, { open: !!manual, title: 'Registrar confirmación manual', onClose: function () { setManual(null); }, footer: h('div', null,
        h(ui.Button, { variant: 'ghost', onClick: function () { setManual(null); } }, 'Cancelar'),
        h(ui.Button, { variant: 'primary', onClick: guardarManual, disabled: busy || !nota.trim() }, 'Guardar ACK')) },
        h('p', null, 'La confirmación telefónica quedará auditada para la ruta ', h('b', null, manual && valor(manual, 'idRuta')), '.'),
        h(ui.Field, { label: 'Nota obligatoria' }, h(ui.Input, { value: nota, onChange: setNota })),
        h(ui.Field, { label: 'Operador (opcional)' }, h(ui.Input, { value: actor, onChange: setActor }))),
      h(ui.Modal, { open: grupo, title: 'Activar grupo real', onClose: function () { setGrupo(false); }, footer: h('div', null,
        h(ui.Button, { variant: 'ghost', onClick: function () { setGrupo(false); } }, 'Cancelar'),
        h(ui.Button, { variant: 'danger', onClick: activarGrupo, disabled: busy || !grupoId.trim() || !zona.trim() }, 'Sí, activar grupo real')) },
        h('div', { className: 'alerta alerta--alta' }, h('b', null, 'Esta acción va a enviar mensajes REALES a este grupo.')),
        h('p', { className: 'muted' }, 'Ingresá los identificadores provistos por el backend. Se habilitarán las dos llaves de seguridad y quedará auditado.'),
        h(ui.Field, { label: 'ID del grupo' }, h(ui.Input, { value: grupoId, onChange: setGrupoId, placeholder: 'UUID del grupo' })),
        h(ui.Field, { label: 'Macro-zona' }, h(ui.Input, { value: zona, onChange: setZona, placeholder: 'Macro-zona exacta' })),
        h(ui.Field, { label: 'Operador (opcional)' }, h(ui.Input, { value: actor, onChange: setActor }))));
  }
  window.CR.screens = window.CR.screens || {};
  window.CR.screens.TableroAcks = TableroAcks;
})();
