// ============================================================
// Panel Supervisor - Asignación FIFO
// ============================================================
const SupervisorLocal = ({ libros, onUpdate, onSelectBook }) => {
  const [asignando, setAsignando] = useState(null);
  const [editorElegido, setEditorElegido] = useState(null);

  // Cola FIFO de digitalizados
  const digitalizados = libros.filter(l => l.estatus === 'digitalizado')
    .sort((a, b) => a.fechaDigitalizacion.localeCompare(b.fechaDigitalizacion));

  const editores = APP_DATA.USUARIOS.filter(u => u.roles.includes('edicion'));

  // Cargas de trabajo por editor
  const cargas = {};
  editores.forEach(e => {
    cargas[e.id] = {
      user: e,
      enCurso: libros.filter(l => l.estatus === 'en_edicion' && l.editadoPor === e.id).length,
      totalEditados: libros.filter(l =>
        l.editadoPor === e.id &&
        ['editado','en_cc','aceptado','rechazado','en_conversion','finalizado'].includes(l.estatus)
      ).length,
    };
  });

  const editorMenosCargado = () => {
    return Object.values(cargas).sort((a, b) => a.enCurso - b.enCurso)[0]?.user;
  };

  const asignarSiguiente = () => {
    const primero = digitalizados[0];
    const editor = editorMenosCargado();
    if (primero && editor) {
      onUpdate(primero.cons, { estatus: 'en_edicion', editadoPor: editor.id });
    }
  };

  const abrirAsignacion = (libro) => {
    setAsignando(libro);
    setEditorElegido(editorMenosCargado()?.id || null);
  };

  const confirmarAsignacion = () => {
    if (!asignando || !editorElegido) return;
    onUpdate(asignando.cons, { estatus: 'en_edicion', editadoPor: editorElegido });
    setAsignando(null);
  };

  // KPIs globales
  const totalPendientes = libros.filter(l => ['nuevo','en_digitalizacion','digitalizado','en_edicion','editado','en_cc','en_conversion'].includes(l.estatus)).length;
  const finalizados = libros.filter(l => l.estatus === 'finalizado').length;
  const rechazados = libros.filter(l => l.estatus === 'rechazado').length;

  return (
    <div className="stack">
      <div className="kpi-grid" style={{ gridTemplateColumns: 'repeat(4, 1fr)' }}>
        <KPI label="En cola de edición" value={digitalizados.length} sub="Digitalizados esperando asignación" tone="primary"/>
        <KPI label="En proceso" value={totalPendientes} sub="Libros activos en el pipeline" tone="amber"/>
        <KPI label="Finalizados" value={finalizados} sub={`Meta: ${APP_DATA.PROYECTO.metaLibros}`} progress={(finalizados/APP_DATA.PROYECTO.metaLibros)*100} tone="success"/>
        <KPI label="Rechazados por CC" value={rechazados} sub="Requieren reproceso" tone="danger"/>
      </div>

      {/* Asignación FIFO */}
      <div className="card">
        <div className="card-header">
          <Icon name="fifo" size={16} style={{ color: 'var(--text-2)' }}/>
          <div>
            <div className="card-title">Cola FIFO · Libros digitalizados esperando asignación</div>
            <div className="card-subtitle">Primeras entradas, primeras salidas · asigna en orden para respetar el flujo</div>
          </div>
          <div className="spacer"/>
          <button className="btn btn-primary" onClick={asignarSiguiente} disabled={digitalizados.length === 0}>
            <Icon name="arrow_right" size={14}/>
            Asignar el siguiente al editor menos cargado
          </button>
        </div>
        <div className="card-body" style={{ padding: 16 }}>
          {digitalizados.length === 0 && <Empty icon="fifo" title="Cola vacía" subtitle="No hay libros digitalizados esperando asignación"/>}
          <div className="stack" style={{ gap: 6 }}>
            {digitalizados.map((l, i) => {
              const dias = daysBetween(l.fechaDigitalizacion, '2026-08-02');
              const digitalizador = getUser(l.digitalizadoPor);
              return (
                <div key={l.cons} className="fifo-item" style={{ gridTemplateColumns: '40px 1fr auto auto auto' }}>
                  <div className={`fifo-order ${dias > 5 ? 'urgent' : ''}`}>{i + 1}</div>
                  <div style={{ minWidth: 0 }}>
                    <div className="text-xs text-3">#{l.cons} · {l.serie}</div>
                    <div className="title-line" style={{ fontWeight: 500 }}>{l.titulo}</div>
                    <div className="text-xs text-2 mt-4 row" style={{ gap: 6 }}>
                      <span>{fmtNum(l.imagenesResultantes)} imág</span>
                      <span>·</span>
                      <span>Digitalizó {digitalizador?.nombre.split(' ')[0]}</span>
                      <span>·</span>
                      <span>Hace {dias} día{dias === 1 ? '' : 's'}</span>
                    </div>
                  </div>
                  <div>
                    {dias > 5 && <span className="badge" style={{ background: 'var(--danger-soft)', color: 'var(--danger)' }}>Urgente</span>}
                    {dias >= 3 && dias <= 5 && <span className="badge" style={{ background: 'var(--warn-soft)', color: 'var(--warn)' }}>Atención</span>}
                  </div>
                  <div className="text-xs text-3" style={{ textAlign: 'right', minWidth: 90 }}>
                    {fmtDate(l.fechaDigitalizacion)}
                  </div>
                  <button className="btn btn-primary btn-sm" onClick={() => abrirAsignacion(l)}>Asignar</button>
                </div>
              );
            })}
          </div>
        </div>
      </div>

      {/* Cargas por editor */}
      <div className="card">
        <div className="card-header">
          <Icon name="users" size={16} style={{ color: 'var(--text-2)' }}/>
          <div>
            <div className="card-title">Cargas de trabajo por editor</div>
            <div className="card-subtitle">Balance actual · el menor primero</div>
          </div>
        </div>
        <div className="card-body">
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: 12 }}>
            {Object.values(cargas).sort((a, b) => a.enCurso - b.enCurso).map(c => (
              <div key={c.user.id} style={{ padding: 14, background: 'var(--surface-2)', borderRadius: 8, border: '1px solid var(--border)' }}>
                <div className="row">
                  <Avatar user={c.user}/>
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontWeight: 500, fontSize: 13.5 }}>{c.user.nombre}</div>
                    <div className="text-xs text-3">{c.user.roles.includes('supervisor') ? 'Supervisor·Editor' : 'Editor'}</div>
                  </div>
                </div>
                <div style={{ marginTop: 12, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
                  <div>
                    <div className="text-xs text-3" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}>En curso</div>
                    <div className="text-serif" style={{ fontSize: 22, fontWeight: 600 }}>{c.enCurso}</div>
                  </div>
                  <div>
                    <div className="text-xs text-3" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}>Total</div>
                    <div className="text-serif" style={{ fontSize: 22, fontWeight: 600 }}>{c.totalEditados}</div>
                  </div>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>

      {/* Kanban global */}
      <div className="card">
        <div className="card-header">
          <Icon name="dashboard" size={16} style={{ color: 'var(--text-2)' }}/>
          <div>
            <div className="card-title">Vista global del pipeline</div>
            <div className="card-subtitle">Estado de todos los libros en cada etapa</div>
          </div>
        </div>
        <div className="card-body">
          <div className="kanban">
            {[
              { title: 'Digitalización', keys: ['nuevo','en_digitalizacion','digitalizado'] },
              { title: 'Edición',       keys: ['en_edicion','editado'] },
              { title: 'Control Calidad', keys: ['en_cc','aceptado','rechazado'] },
              { title: 'Conversión / Final', keys: ['en_conversion','finalizado'] },
            ].map(col => {
              const items = libros.filter(l => col.keys.includes(l.estatus));
              return (
                <div key={col.title} className="kanban-col">
                  <div className="kanban-col-header">
                    <span>{col.title}</span>
                    <span className="kanban-count">{items.length}</span>
                  </div>
                  <div style={{ maxHeight: 400, overflow: 'auto' }}>
                    {items.slice(0, 8).map(l => (
                      <div key={l.cons} className="kanban-card" onClick={() => onSelectBook(l)}>
                        <div className="kanban-card-cons">#{l.cons}</div>
                        <div className="kanban-card-title">{l.titulo}</div>
                        <div className="kanban-card-meta">
                          <StatusPill estatus={l.estatus}/>
                        </div>
                      </div>
                    ))}
                    {items.length > 8 && (
                      <div className="text-xs text-3" style={{ textAlign: 'center', padding: 8 }}>
                        + {items.length - 8} más
                      </div>
                    )}
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      </div>

      {/* Modal asignación */}
      <Modal
        open={!!asignando}
        onClose={() => setAsignando(null)}
        title="Asignar libro a editor"
        maxWidth={480}
        footer={<>
          <button className="btn btn-ghost" onClick={() => setAsignando(null)}>Cancelar</button>
          <button className="btn btn-primary" onClick={confirmarAsignacion} disabled={!editorElegido}>Asignar</button>
        </>}
      >
        {asignando && (
          <div>
            <div style={{ padding: 12, background: 'var(--surface-2)', borderRadius: 6, marginBottom: 16 }}>
              <div className="text-xs text-3">#{asignando.cons} · {asignando.serie}</div>
              <div style={{ fontWeight: 500, fontSize: 15 }}>{asignando.titulo}</div>
              <div className="text-sm text-2 mt-4">{fmtNum(asignando.imagenesResultantes)} imágenes</div>
            </div>

            <div className="field-label mb-8">Selecciona editor</div>
            <div className="stack" style={{ gap: 6 }}>
              {editores.map(e => {
                const c = cargas[e.id];
                return (
                  <div
                    key={e.id}
                    onClick={() => setEditorElegido(e.id)}
                    style={{
                      padding: 10,
                      border: `2px solid ${editorElegido === e.id ? 'var(--primary)' : 'var(--border)'}`,
                      background: editorElegido === e.id ? 'var(--primary-soft)' : 'var(--surface)',
                      borderRadius: 6,
                      cursor: 'pointer',
                    }}
                  >
                    <div className="row">
                      <Avatar user={e} size="sm"/>
                      <div style={{ minWidth: 0, flex: 1 }}>
                        <div style={{ fontWeight: 500, fontSize: 13 }}>{e.nombre}</div>
                        <div className="text-xs text-3">
                          {c.enCurso} en curso · {c.totalEditados} total
                        </div>
                      </div>
                      {c.enCurso === Math.min(...Object.values(cargas).map(x => x.enCurso)) && (
                        <span className="badge" style={{ background: 'var(--success-soft)', color: 'var(--success)' }}>
                          Menos cargado
                        </span>
                      )}
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        )}
      </Modal>
    </div>
  );
};

const SUPERVISOR_STAGES = Object.freeze({
  catalog: 'Catálogo',
  digitization: 'Digitalización',
  editing: 'Edición',
  quality_control: 'Control de calidad',
  conversion: 'Conversión',
  publication: 'Publicación',
  published: 'Publicado'
});

const SUPERVISOR_ASSIGNABLE_STAGES = Object.freeze(
  Object.keys(SUPERVISOR_STAGES).filter(stage => stage !== 'published')
);

const SUPERVISOR_STATUS_LABELS = Object.freeze({
  pending: 'Pendiente',
  in_progress: 'En proceso',
  completed: 'Completada',
  cancelled: 'Cancelada',
  reassigned: 'Reasignada'
});

const SUPERVISOR_HISTORY_LABELS = Object.freeze({
  assignment_created: 'Asignación creada',
  assignment_cancelled: 'Asignación cancelada',
  assignment_reassigned: 'Asignación reasignada',
  assignment_created_from_reassignment: 'Nueva asignación por reasignación'
});

const SupervisorFifoOverviewServer = ({ refreshToken = 0, onChanged, onSessionExpired }) => {
  const [state, setState] = useState('loading');
  const [books, setBooks] = useState([]);
  const [queue, setQueue] = useState([]);
  const [workload, setWorkload] = useState([]);
  const [editors, setEditors] = useState([]);
  const [error, setError] = useState('');
  const [assigning, setAssigning] = useState(null);
  const [selectedEditorId, setSelectedEditorId] = useState('');
  const [submitting, setSubmitting] = useState(false);
  const [notice, setNotice] = useState(null);

  const loadAllBooks = async (filters = {}, signal) => {
    const first = await window.booksService.listBooks({
      ...filters, page: 1, per_page: 100, signal
    });
    const items = [...first.items];
    for (let page = 2; page <= first.pagination.total_pages; page += 1) {
      const result = await window.booksService.listBooks({
        ...filters, page, per_page: 100, signal
      });
      items.push(...result.items);
    }
    return items;
  };

  const loadActiveEditingAssignments = async signal => {
    const statuses = ['pending', 'in_progress'];
    const all = [];
    for (const status of statuses) {
      const first = await window.assignmentsService.listAssignments({
        stage: 'editing', status, page: 1, per_page: 100, signal
      });
      all.push(...first.items);
      for (let page = 2; page <= first.pagination.total_pages; page += 1) {
        const result = await window.assignmentsService.listAssignments({
          stage: 'editing', status, page, per_page: 100, signal
        });
        all.push(...result.items);
      }
    }
    return all;
  };

  const reload = React.useCallback(async () => {
    const controller = new AbortController();
    setState('loading');
    setError('');
    try {
      const [allBooks, completedDigitization, activeEditing, workloadItems, activeUsers] = await Promise.all([
        loadAllBooks({ sort: 'updated_at', direction: 'asc' }, controller.signal),
        loadAllBooks({ current_stage: 'digitization', status: 'completed', sort: 'updated_at', direction: 'asc' }, controller.signal),
        loadActiveEditingAssignments(controller.signal),
        window.assignmentsService.listWorkload({ stage: 'editing', signal: controller.signal }),
        window.usersService.listUsers({ status: 'active', signal: controller.signal })
      ]);

      const activeBookIds = new Set(activeEditing.map(item => item.bookId));
      const availableQueue = completedDigitization.filter(book => !activeBookIds.has(book.serverId));
      const compatibleEditors = window.usersService.getCompatibleUsersForStage(activeUsers, 'editing');

      setBooks(allBooks);
      setQueue(availableQueue);
      setWorkload(workloadItems);
      setEditors(compatibleEditors);
      setState('loaded');
    } catch (requestError) {
      if (controller.signal.aborted) return;
      if (Number(requestError && requestError.status) === 401) {
        if (onSessionExpired) onSessionExpired();
        return;
      }
      setState('error');
      setError((requestError && requestError.message) || 'No fue posible cargar la cola FIFO.');
    }
    return () => controller.abort();
  }, [refreshToken]);

  useEffect(() => {
    reload();
  }, [reload]);

  const workloadFor = userId => workload.find(item => item.user.id === userId) || {
    pending: 0, inProgress: 0, activeTotal: 0, completed: 0
  };

  const leastLoadedEditor = () => [...editors].sort((a, b) => {
    const aLoad = workloadFor(a.id).activeTotal;
    const bLoad = workloadFor(b.id).activeTotal;
    if (aLoad !== bLoad) return aLoad - bLoad;
    return a.name.localeCompare(b.name, 'es');
  })[0] || null;

  const openAssignment = book => {
    const editor = leastLoadedEditor();
    setAssigning(book);
    setSelectedEditorId(editor ? String(editor.id) : '');
    setNotice(null);
  };

  const assignBook = async (book, userId) => {
    if (!book || !userId || submitting) return;
    setSubmitting(true);
    setNotice(null);
    try {
      await window.assignmentsService.createAssignment({
        book_id: book.serverId,
        user_id: Number(userId),
        stage: 'editing',
        observations: 'Asignación creada desde la cola FIFO.'
      });
      setAssigning(null);
      setSelectedEditorId('');
      setNotice({ tone: 'success', text: 'El libro se asignó correctamente a edición.' });
      await reload();
      if (onChanged) onChanged();
    } catch (requestError) {
      if (Number(requestError && requestError.status) === 401) {
        if (onSessionExpired) onSessionExpired();
        return;
      }
      const message = Number(requestError && requestError.status) === 409
        ? 'El libro ya tiene una asignación activa de edición.'
        : ((requestError && requestError.message) || 'No fue posible asignar el libro.');
      setNotice({ tone: 'danger', text: message });
      await reload();
    } finally {
      setSubmitting(false);
    }
  };

  const assignNext = () => {
    const book = queue[0];
    const editor = leastLoadedEditor();
    if (book && editor) assignBook(book, editor.id);
  };

  const totalInProcess = books.filter(book => ['pending', 'assigned', 'in_progress', 'correction_required'].includes(book.status)).length;
  const completedBooks = books.filter(book => book.currentStage === 'published' || (book.currentStage === 'publication' && book.status === 'completed')).length;
  const rejectedBooks = books.filter(book => book.status === 'rejected' || book.status === 'correction_required').length;
  const projectGoal = Number(window.APP_DATA && window.APP_DATA.PROYECTO && window.APP_DATA.PROYECTO.metaLibros) || books.length || 1;

  const pipelineColumns = [
    { title: 'Digitalización', stages: ['catalog', 'digitization'] },
    { title: 'Edición', stages: ['editing'] },
    { title: 'Control de calidad', stages: ['quality_control'] },
    { title: 'Conversión / Final', stages: ['conversion', 'publication', 'published'] }
  ];

  const formatDate = value => {
    if (!value) return '—';
    const parsed = new Date(String(value).replace(' ', 'T'));
    return Number.isNaN(parsed.getTime()) ? '—' : parsed.toLocaleDateString('es-MX');
  };

  const statusLabel = book => {
    const labels = window.bookCatalogs && window.bookCatalogs.statuses;
    return (labels && labels[book.status]) || book.status || 'Pendiente';
  };

  return (
    <div className="stack" data-fifo-overview="server">
      {notice && <div style={{ padding: '10px 12px', borderRadius: 6, background: notice.tone === 'success' ? 'var(--success-soft)' : 'var(--danger-soft)', color: notice.tone === 'success' ? 'var(--success)' : 'var(--danger)', fontSize: 13 }} className="row">
        <span style={{ flex: 1 }}>{notice.text}</span>
        <button className="btn btn-ghost btn-sm" onClick={() => setNotice(null)}>Cerrar</button>
      </div>}

      <div className="kpi-grid" style={{ gridTemplateColumns: 'repeat(4, 1fr)' }}>
        <KPI label="En cola de edición" value={queue.length} sub="Digitalizados esperando asignación" tone="primary"/>
        <KPI label="En proceso" value={totalInProcess} sub="Libros activos en el pipeline" tone="amber"/>
        <KPI label="Finalizados" value={completedBooks} sub={`Meta: ${projectGoal}`} progress={(completedBooks / projectGoal) * 100} tone="success"/>
        <KPI label="Rechazados por CC" value={rejectedBooks} sub="Requieren reproceso" tone="danger"/>
      </div>

      <div className="card">
        <div className="card-header">
          <Icon name="fifo" size={16} style={{ color: 'var(--text-2)' }}/>
          <div>
            <div className="card-title">Cola FIFO · Libros digitalizados esperando asignación</div>
            <div className="card-subtitle">Primero digitalizado, primero asignado · datos reales del servidor</div>
          </div>
          <div className="spacer"/>
          <button className="btn btn-primary" onClick={assignNext} disabled={state === 'loading' || submitting || queue.length === 0 || editors.length === 0}>
            <Icon name="arrow_right" size={14}/>
            {submitting ? 'Asignando…' : 'Asignar el siguiente al editor menos cargado'}
          </button>
        </div>
        <div className="card-body" style={{ padding: 16 }}>
          {state === 'loading' && <Empty icon="fifo" title="Cargando cola FIFO…"/>}
          {state === 'error' && <div style={{ padding: 12, background: 'var(--danger-soft)', color: 'var(--danger)', borderRadius: 6 }}>{error}<button className="btn btn-ghost btn-sm ml-8" onClick={reload}>Reintentar</button></div>}
          {state === 'loaded' && queue.length === 0 && <Empty icon="fifo" title="Cola vacía" subtitle="No hay libros con digitalización terminada esperando asignación de edición."/>}
          {state === 'loaded' && <div className="stack" style={{ gap: 6 }}>
            {queue.slice(0, 20).map((book, index) => (
              <div key={book.serverId} className="fifo-item" style={{ gridTemplateColumns: '40px 1fr auto auto' }}>
                <div className="fifo-order">{index + 1}</div>
                <div style={{ minWidth: 0 }}>
                  <div className="text-xs text-3">#{book.inventoryNumber || book.cons || book.serverId} · {book.serie || 'Sin serie'}</div>
                  <div className="title-line" style={{ fontWeight: 500 }}>{book.title || book.titulo}</div>
                  <div className="text-xs text-2 mt-4 row" style={{ gap: 6 }}>
                    <span>{book.author || 'Autor no registrado'}</span><span>·</span>
                    <span>{book.totalPages || 0} páginas</span><span>·</span>
                    <span>Terminó digitalización: {formatDate(book.updatedAt)}</span>
                  </div>
                </div>
                <div className="text-xs text-3" style={{ textAlign: 'right', minWidth: 90 }}>{formatDate(book.updatedAt)}</div>
                <button className="btn btn-primary btn-sm" onClick={() => openAssignment(book)}>Asignar</button>
              </div>
            ))}
            {queue.length > 20 && <div className="text-xs text-3" style={{ textAlign: 'center', padding: 8 }}>+ {queue.length - 20} libros más en cola</div>}
          </div>}
        </div>
      </div>

      <div className="card">
        <div className="card-header">
          <Icon name="users" size={16} style={{ color: 'var(--text-2)' }}/>
          <div><div className="card-title">Cargas de trabajo por editor</div><div className="card-subtitle">Asignaciones activas de edición · menor carga primero</div></div>
        </div>
        <div className="card-body">
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(190px, 1fr))', gap: 12 }}>
            {[...editors].sort((a, b) => workloadFor(a.id).activeTotal - workloadFor(b.id).activeTotal).map(editor => {
              const load = workloadFor(editor.id);
              return <div key={editor.id} className="fifo-editor-card">
                <div className="row"><Avatar user={editor}/><div style={{ minWidth: 0 }}><div style={{ fontWeight: 500, fontSize: 13.5 }}>{editor.name}</div><div className="text-xs text-3">{editor.roles.includes('supervisor') ? 'Supervisor · Editor' : 'Editor'}</div></div></div>
                <div style={{ marginTop: 12, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
                  <div><div className="text-xs text-3">EN CURSO</div><div className="text-serif" style={{ fontSize: 22, fontWeight: 600 }}>{load.activeTotal}</div></div>
                  <div><div className="text-xs text-3">COMPLETADAS</div><div className="text-serif" style={{ fontSize: 22, fontWeight: 600 }}>{load.completed}</div></div>
                </div>
              </div>;
            })}
          </div>
        </div>
      </div>

      <div className="card">
        <div className="card-header"><Icon name="dashboard" size={16} style={{ color: 'var(--text-2)' }}/><div><div className="card-title">Vista global del pipeline</div><div className="card-subtitle">Estado real de los libros en cada etapa</div></div></div>
        <div className="card-body"><div className="kanban">
          {pipelineColumns.map(column => {
            const items = books.filter(book => column.stages.includes(book.currentStage));
            return <div key={column.title} className="kanban-col">
              <div className="kanban-col-header"><span>{column.title}</span><span className="kanban-count">{items.length}</span></div>
              <div style={{ maxHeight: 400, overflow: 'auto' }}>
                {items.slice(0, 8).map(book => <div key={book.serverId} className="kanban-card">
                  <div className="kanban-card-cons">#{book.inventoryNumber || book.cons || book.serverId}</div>
                  <div className="kanban-card-title">{book.title || book.titulo}</div>
                  <div className="kanban-card-meta"><span className="badge" style={{ background: 'var(--surface-3)', color: 'var(--text-2)' }}>{statusLabel(book)}</span></div>
                </div>)}
                {items.length > 8 && <div className="text-xs text-3" style={{ textAlign: 'center', padding: 8 }}>+ {items.length - 8} más</div>}
              </div>
            </div>;
          })}
        </div></div>
      </div>

      <Modal open={!!assigning} onClose={() => !submitting && setAssigning(null)} title="Asignar libro a editor" maxWidth={520}
        footer={<><button className="btn btn-ghost" disabled={submitting} onClick={() => setAssigning(null)}>Cancelar</button><button className="btn btn-primary" disabled={submitting || !selectedEditorId} onClick={() => assignBook(assigning, selectedEditorId)}>{submitting ? 'Asignando…' : 'Asignar'}</button></>}>
        {assigning && <div>
          <div style={{ padding: 12, background: 'var(--surface-2)', borderRadius: 6, marginBottom: 16 }}><div className="text-xs text-3">#{assigning.inventoryNumber || assigning.cons || assigning.serverId} · {assigning.serie || 'Sin serie'}</div><div style={{ fontWeight: 500, fontSize: 15 }}>{assigning.title}</div><div className="text-sm text-2 mt-4">{assigning.author || 'Autor no registrado'} · {assigning.totalPages || 0} páginas</div></div>
          <div className="field-label mb-8">Selecciona editor</div>
          <div className="stack" style={{ gap: 6 }}>{[...editors].sort((a, b) => workloadFor(a.id).activeTotal - workloadFor(b.id).activeTotal).map(editor => {
            const load = workloadFor(editor.id);
            const least = leastLoadedEditor();
            return <div key={editor.id} onClick={() => setSelectedEditorId(String(editor.id))} style={{ padding: 10, border: `2px solid ${String(editor.id) === selectedEditorId ? 'var(--primary)' : 'var(--border)'}`, background: String(editor.id) === selectedEditorId ? 'var(--primary-soft)' : 'var(--surface)', borderRadius: 6, cursor: 'pointer' }}>
              <div className="row"><Avatar user={editor} size="sm"/><div style={{ minWidth: 0, flex: 1 }}><div style={{ fontWeight: 500, fontSize: 13 }}>{editor.name}</div><div className="text-xs text-3">{load.activeTotal} activas · {load.completed} completadas</div></div>{least && least.id === editor.id && <span className="badge" style={{ background: 'var(--success-soft)', color: 'var(--success)' }}>Menos cargado</span>}</div>
            </div>;
          })}</div>
        </div>}
      </Modal>
    </div>
  );
};

const SupervisorServer = ({ currentUser, onSessionExpired }) => {
  const canManage = window.authMapper.hasAnyRole(currentUser, ['administrator', 'supervisor']);
  const [filters, setFilters] = useState({
    book_id: '', user_id: '', stage: '', status: '', assigned_from: '', assigned_to: '',
    page: 1, per_page: 10
  });
  const [assignments, setAssignments] = useState([]);
  const [pagination, setPagination] = useState({ page: 1, per_page: 10, total: 0, total_pages: 0 });
  const [listState, setListState] = useState('loading');
  const [listError, setListError] = useState('');
  const [users, setUsers] = useState([]);
  const [workload, setWorkload] = useState([]);
  const [workloadState, setWorkloadState] = useState('loading');
  const [refreshKey, setRefreshKey] = useState(0);
  const [notice, setNotice] = useState(null);

  const [filterBookSearch, setFilterBookSearch] = useState('');
  const [filterBookResults, setFilterBookResults] = useState([]);
  const [filterBookLoading, setFilterBookLoading] = useState(false);

  const [detail, setDetail] = useState(null);
  const [detailState, setDetailState] = useState('idle');
  const [history, setHistory] = useState([]);
  const [historyState, setHistoryState] = useState('idle');

  const [createOpen, setCreateOpen] = useState(false);
  const [createForm, setCreateForm] = useState({ book: null, stage: '', user_id: '', observations: '' });
  const [createBookSearch, setCreateBookSearch] = useState('');
  const [createBookResults, setCreateBookResults] = useState([]);
  const [createBookLoading, setCreateBookLoading] = useState(false);
  const [createError, setCreateError] = useState('');
  const [submittingCreate, setSubmittingCreate] = useState(false);

  const [cancelTarget, setCancelTarget] = useState(null);
  const [cancelReason, setCancelReason] = useState('');
  const [cancelError, setCancelError] = useState('');
  const [submittingCancel, setSubmittingCancel] = useState(false);

  const [reassignTarget, setReassignTarget] = useState(null);
  const [reassignForm, setReassignForm] = useState({ user_id: '', reason: '' });
  const [reassignError, setReassignError] = useState('');
  const [submittingReassign, setSubmittingReassign] = useState(false);
  const operationLocks = React.useRef({ create: false, cancel: false, reassign: false });

  const formatDateTime = value => value
    ? new Date(String(value).replace(' ', 'T')).toLocaleString('es-MX')
    : '—';

  const closeAllModals = () => {
    setCreateOpen(false);
    setCancelTarget(null);
    setReassignTarget(null);
    setDetail(null);
  };

  const handleSessionExpired = () => {
    closeAllModals();
    if (onSessionExpired) onSessionExpired();
  };

  const errorMessage = (error, operation = '') => {
    const status = Number(error && error.status) || 0;
    if (status === 401) {
      handleSessionExpired();
      return '';
    }
    if (status === 403) return 'No tienes permiso para realizar esta operación.';
    if (status === 404) return 'La asignación, el libro o el usuario ya no está disponible.';
    if (status === 409 && operation === 'create') {
      return 'Este libro ya tiene una asignación activa para la etapa seleccionada.';
    }
    if (status === 409) return (error && error.message) || 'La asignación cambió y la operación ya no es válida.';
    if (status === 422) return (error && error.message) || 'Verifica los datos capturados.';
    if (status === 400) return 'No fue posible procesar la solicitud. Verifica los datos e inténtalo nuevamente.';
    return 'No fue posible completar la operación. Verifica el estado del servidor e inténtalo nuevamente.';
  };

  const updateFilter = (name, value) => {
    setFilters(previous => ({ ...previous, [name]: value, page: name === 'page' ? value : 1 }));
  };

  useEffect(() => {
    if (!canManage) return undefined;
    const controller = new AbortController();
    window.usersService.listUsers({ status: 'active', signal: controller.signal })
      .then(setUsers)
      .catch(error => {
        if (error && error.status === 401) handleSessionExpired();
        else setNotice({ tone: 'danger', text: errorMessage(error) });
      });
    return () => controller.abort();
  }, [canManage]);

  useEffect(() => {
    if (!canManage) return undefined;
    const controller = new AbortController();
    setListState('loading');
    setListError('');
    window.assignmentsService.listAssignments({ ...filters, signal: controller.signal })
      .then(result => {
        setAssignments(result.items);
        setPagination(result.pagination);
        setListState(result.items.length ? 'loaded' : 'empty');
      })
      .catch(error => {
        if (controller.signal.aborted) return;
        if (error && error.status === 401) {
          handleSessionExpired();
          return;
        }
        setAssignments([]);
        setListState(Number(error && error.status) === 0 ? 'unavailable' : 'error');
        setListError(errorMessage(error));
      });
    return () => controller.abort();
  }, [canManage, filters.book_id, filters.user_id, filters.stage, filters.status,
    filters.assigned_from, filters.assigned_to, filters.page, filters.per_page, refreshKey]);

  useEffect(() => {
    if (!canManage) return undefined;
    const controller = new AbortController();
    setWorkloadState('loading');
    window.assignmentsService.listWorkload({
      stage: filters.stage,
      user_id: filters.user_id,
      signal: controller.signal
    }).then(items => {
      setWorkload(items);
      setWorkloadState('loaded');
    }).catch(error => {
      if (controller.signal.aborted) return;
      if (error && error.status === 401) handleSessionExpired();
      else setWorkloadState('error');
    });
    return () => controller.abort();
  }, [canManage, filters.stage, filters.user_id, refreshKey]);

  useEffect(() => {
    if (!canManage || filterBookSearch.trim().length < 2) {
      setFilterBookResults([]);
      return undefined;
    }
    const controller = new AbortController();
    const timer = window.setTimeout(() => {
      setFilterBookLoading(true);
      window.booksService.listBooks({ search: filterBookSearch, page: 1, per_page: 8, signal: controller.signal })
        .then(result => setFilterBookResults(result.items))
        .catch(error => {
          if (error && error.status === 401) handleSessionExpired();
          else setFilterBookResults([]);
        })
        .finally(() => setFilterBookLoading(false));
    }, 250);
    return () => { window.clearTimeout(timer); controller.abort(); };
  }, [canManage, filterBookSearch]);

  useEffect(() => {
    if (!createOpen || createBookSearch.trim().length < 2) {
      setCreateBookResults([]);
      return undefined;
    }
    const controller = new AbortController();
    const timer = window.setTimeout(() => {
      setCreateBookLoading(true);
      window.booksService.listBooks({ search: createBookSearch, page: 1, per_page: 10, signal: controller.signal })
        .then(result => setCreateBookResults(result.items))
        .catch(error => {
          if (error && error.status === 401) handleSessionExpired();
          else setCreateBookResults([]);
        })
        .finally(() => setCreateBookLoading(false));
    }, 250);
    return () => { window.clearTimeout(timer); controller.abort(); };
  }, [createOpen, createBookSearch]);

  const loadDetail = async (assignmentId, knownBookId = null) => {
    setDetailState('loading');
    setHistoryState('loading');
    setDetail(null);
    setHistory([]);
    try {
      const assignment = await window.assignmentsService.getAssignmentById(assignmentId);
      setDetail(assignment);
      setDetailState('loaded');
      const historyResult = await window.historyService.getBookHistory(knownBookId || assignment.bookId, {
        page: 1, per_page: 50
      });
      setHistory(historyResult.items);
      setHistoryState('loaded');
    } catch (error) {
      if (error && error.status === 401) {
        handleSessionExpired();
        return;
      }
      if (error && error.status === 404) setDetail(null);
      setDetailState('error');
      setHistoryState('error');
      setNotice({ tone: 'danger', text: errorMessage(error) });
    }
  };

  const refreshServerState = (assignmentId = null, bookId = null) => {
    setRefreshKey(value => value + 1);
    if (assignmentId) loadDetail(assignmentId, bookId);
  };

  const openCreate = () => {
    setCreateForm({ book: null, stage: '', user_id: '', observations: '' });
    setCreateBookSearch('');
    setCreateBookResults([]);
    setCreateError('');
    setCreateOpen(true);
  };

  const submitCreate = async event => {
    event.preventDefault();
    if (operationLocks.current.create || submittingCreate) return;
    const bookId = createForm.book && createForm.book.serverId;
    const userId = Number(createForm.user_id);
    if (!bookId || !createForm.stage || !Number.isInteger(userId) || userId < 1) {
      setCreateError('Selecciona un libro, una etapa y un usuario compatible.');
      return;
    }
    operationLocks.current.create = true;
    setSubmittingCreate(true);
    setCreateError('');
    try {
      const result = await window.assignmentsService.createAssignment({
        book_id: bookId,
        user_id: userId,
        stage: createForm.stage,
        observations: createForm.observations.trim() || null
      });
      setCreateOpen(false);
      setNotice({ tone: 'success', text: 'La asignación se creó correctamente.' });
      refreshServerState(result.assignment && result.assignment.id, bookId);
    } catch (error) {
      const message = errorMessage(error, 'create');
      setCreateError(message);
      if ([404, 409, 500, 0].includes(Number(error && error.status) || 0)) refreshServerState();
    } finally {
      operationLocks.current.create = false;
      setSubmittingCreate(false);
    }
  };

  const submitCancel = async event => {
    event.preventDefault();
    if (operationLocks.current.cancel || submittingCancel || !cancelTarget) return;
    if (!cancelReason.trim()) {
      setCancelError('El motivo es obligatorio.');
      return;
    }
    operationLocks.current.cancel = true;
    setSubmittingCancel(true);
    setCancelError('');
    try {
      const result = await window.assignmentsService.cancelAssignment(cancelTarget.id, cancelReason.trim());
      setCancelTarget(null);
      setNotice({ tone: 'success', text: 'La asignación se canceló correctamente.' });
      refreshServerState(result.assignment && result.assignment.id, cancelTarget.bookId);
    } catch (error) {
      setCancelError(errorMessage(error, 'cancel'));
      if ([404, 409, 500, 0].includes(Number(error && error.status) || 0)) refreshServerState();
    } finally {
      operationLocks.current.cancel = false;
      setSubmittingCancel(false);
    }
  };

  const submitReassign = async event => {
    event.preventDefault();
    if (operationLocks.current.reassign || submittingReassign || !reassignTarget) return;
    const userId = Number(reassignForm.user_id);
    if (!Number.isInteger(userId) || userId < 1 || userId === reassignTarget.userId) {
      setReassignError('Selecciona un usuario compatible distinto del usuario actual.');
      return;
    }
    if (!reassignForm.reason.trim()) {
      setReassignError('El motivo es obligatorio.');
      return;
    }
    operationLocks.current.reassign = true;
    setSubmittingReassign(true);
    setReassignError('');
    try {
      const result = await window.assignmentsService.reassignAssignment(
        reassignTarget.id, userId, reassignForm.reason.trim()
      );
      setReassignTarget(null);
      setNotice({ tone: 'success', text: 'La asignación se reasignó correctamente.' });
      refreshServerState(result.assignment && result.assignment.id, reassignTarget.bookId);
    } catch (error) {
      setReassignError(errorMessage(error, 'reassign'));
      if ([404, 409, 500, 0].includes(Number(error && error.status) || 0)) refreshServerState();
    } finally {
      operationLocks.current.reassign = false;
      setSubmittingReassign(false);
    }
  };

  if (!canManage) {
    return <Empty icon="check_shield" title="Acceso denegado" subtitle="No tienes permiso para consultar la supervisión global."/>;
  }

  const compatibleCreateUsers = createForm.stage
    ? window.usersService.getCompatibleUsersForStage(users, createForm.stage)
    : [];
  const compatibleReassignUsers = reassignTarget
    ? window.usersService.getCompatibleUsersForStage(users, reassignTarget.stage)
      .filter(user => user.id !== reassignTarget.userId)
    : [];
  const activeAssignment = assignment => ['pending', 'in_progress'].includes(assignment.status);
  const alertStyle = tone => ({
    padding: '10px 12px', borderRadius: 6,
    background: tone === 'success' ? 'var(--success-soft)' : 'var(--danger-soft)',
    color: tone === 'success' ? 'var(--success)' : 'var(--danger)', fontSize: 13
  });

  return (
    <div className="stack" data-supervisor-mode="server">
      <SupervisorFifoOverviewServer
        refreshToken={refreshKey}
        onChanged={() => refreshServerState()}
        onSessionExpired={handleSessionExpired}
      />

      {notice && (
        <div style={alertStyle(notice.tone)} role="status" className="row">
          <span style={{ flex: 1 }}>{notice.text}</span>
          <button className="btn btn-ghost btn-sm" onClick={() => setNotice(null)}>Cerrar</button>
        </div>
      )}

      <Modal open={createOpen} onClose={() => !submittingCreate && setCreateOpen(false)} title="Nueva asignación" maxWidth={620}
        footer={<>
          <button type="button" className="btn btn-ghost" disabled={submittingCreate} onClick={() => setCreateOpen(false)}>Cancelar</button>
          <button type="submit" form="supervisor-create-form" className="btn btn-primary" disabled={submittingCreate}>
            {submittingCreate ? 'Creando…' : 'Crear asignación'}
          </button>
        </>}>
        <form id="supervisor-create-form" onSubmit={submitCreate}>
          {createError && <div style={alertStyle('danger')} className="mb-12">{createError}</div>}
          <div className="field">
            <label className="field-label">1. Libro</label>
            {createForm.book ? (
              <div style={{ padding: 10, background: 'var(--surface-2)', borderRadius: 6 }}>
                <div className="text-xs text-3">#{createForm.book.inventoryNumber || '—'}</div>
                <div style={{ fontWeight: 500 }}>{createForm.book.title}</div>
                <div className="text-xs text-2">{createForm.book.author || 'Autor no registrado'} · {SUPERVISOR_STAGES[createForm.book.currentStage] || createForm.book.currentStage || '—'} · {createForm.book.status || '—'}</div>
                <button type="button" className="btn btn-ghost btn-sm mt-8" onClick={() => setCreateForm(previous => ({ ...previous, book: null }))}>Cambiar libro</button>
              </div>
            ) : (
              <>
                <input className="input" value={createBookSearch} onChange={event => setCreateBookSearch(event.target.value)} placeholder="Buscar por título o inventario"/>
                {createBookLoading && <div className="field-help">Buscando…</div>}
                {createBookSearch.trim().length >= 2 && !createBookLoading && createBookResults.length === 0 && <div className="field-help">No se encontraron libros con ese criterio.</div>}
                <div className="stack mt-8" style={{ gap: 4, maxHeight: 190, overflow: 'auto' }}>
                  {createBookResults.map(book => <button key={book.serverId} type="button" className="btn btn-ghost" style={{ justifyContent: 'flex-start', textAlign: 'left' }} onClick={() => {
                    setCreateForm(previous => ({ ...previous, book })); setCreateBookResults([]);
                  }}>
                    <span><strong>{book.inventoryNumber || '—'}</strong> · {book.title}<br/><span className="text-xs text-3">{book.author || 'Autor no registrado'} · {SUPERVISOR_STAGES[book.currentStage] || book.currentStage || '—'} · {book.status || '—'}</span></span>
                  </button>)}
                </div>
              </>
            )}
          </div>
          <div className="field">
            <label className="field-label">2. Etapa</label>
            <select className="select" value={createForm.stage} onChange={event => setCreateForm(previous => ({ ...previous, stage: event.target.value, user_id: '' }))}>
              <option value="">Selecciona una etapa</option>
              {SUPERVISOR_ASSIGNABLE_STAGES.map(stage => <option key={stage} value={stage}>{SUPERVISOR_STAGES[stage]}</option>)}
            </select>
          </div>
          <div className="field">
            <label className="field-label">3. Usuario asignado</label>
            <select className="select" value={createForm.user_id} disabled={!createForm.stage} onChange={event => setCreateForm(previous => ({ ...previous, user_id: event.target.value }))}>
              <option value="">Selecciona un usuario</option>
              {compatibleCreateUsers.map(user => <option key={user.id} value={user.id}>{user.name} ({user.username})</option>)}
            </select>
            {createForm.stage && compatibleCreateUsers.length === 0 && <div className="field-help">No existen usuarios activos compatibles con esta etapa.</div>}
          </div>
          <div className="field">
            <label className="field-label">4. Observaciones opcionales</label>
            <textarea className="textarea" maxLength={2000} value={createForm.observations} onChange={event => setCreateForm(previous => ({ ...previous, observations: event.target.value }))}/>
          </div>
        </form>
      </Modal>

      <Modal open={!!cancelTarget} onClose={() => !submittingCancel && setCancelTarget(null)} title="Cancelar asignación" maxWidth={520}
        footer={<>
          <button type="button" className="btn btn-ghost" disabled={submittingCancel} onClick={() => setCancelTarget(null)}>Volver</button>
          <button type="submit" form="supervisor-cancel-form" className="btn btn-primary" disabled={submittingCancel}>{submittingCancel ? 'Cancelando…' : 'Confirmar cancelación'}</button>
        </>}>
        {cancelTarget && <form id="supervisor-cancel-form" onSubmit={submitCancel}>
          {cancelError && <div style={alertStyle('danger')} className="mb-12">{cancelError}</div>}
          <div style={{ padding: 10, background: 'var(--surface-2)', borderRadius: 6 }}>
            <strong>{cancelTarget.book && cancelTarget.book.title}</strong><br/>
            <span className="text-xs text-2">{cancelTarget.user && cancelTarget.user.name} · {SUPERVISOR_STAGES[cancelTarget.stage]} · {SUPERVISOR_STATUS_LABELS[cancelTarget.status]}</span>
          </div>
          <div className="field mt-12"><label className="field-label">Motivo obligatorio</label><textarea className="textarea" maxLength={2000} value={cancelReason} onChange={event => setCancelReason(event.target.value)}/></div>
          <div className="text-sm text-2">La asignación no se eliminará; quedará registrada como cancelada.</div>
        </form>}
      </Modal>

      <Modal open={!!reassignTarget} onClose={() => !submittingReassign && setReassignTarget(null)} title="Reasignar libro" maxWidth={520}
        footer={<>
          <button type="button" className="btn btn-ghost" disabled={submittingReassign} onClick={() => setReassignTarget(null)}>Volver</button>
          <button type="submit" form="supervisor-reassign-form" className="btn btn-primary" disabled={submittingReassign}>{submittingReassign ? 'Reasignando…' : 'Confirmar reasignación'}</button>
        </>}>
        {reassignTarget && <form id="supervisor-reassign-form" onSubmit={submitReassign}>
          {reassignError && <div style={alertStyle('danger')} className="mb-12">{reassignError}</div>}
          <div style={{ padding: 10, background: 'var(--surface-2)', borderRadius: 6 }}>
            <strong>{reassignTarget.book && reassignTarget.book.title}</strong><br/>
            <span className="text-xs text-2">Usuario actual: {reassignTarget.user && reassignTarget.user.name} · {SUPERVISOR_STAGES[reassignTarget.stage]}</span>
          </div>
          <div className="field mt-12"><label className="field-label">Nuevo usuario</label>
            <select className="select" value={reassignForm.user_id} onChange={event => setReassignForm(previous => ({ ...previous, user_id: event.target.value }))}>
              <option value="">Selecciona un usuario</option>
              {compatibleReassignUsers.map(user => <option key={user.id} value={user.id}>{user.name} ({user.username})</option>)}
            </select>
            {compatibleReassignUsers.length === 0 && <div className="field-help">No existen usuarios activos compatibles con esta etapa.</div>}
          </div>
          <div className="field"><label className="field-label">Motivo obligatorio</label><textarea className="textarea" maxLength={2000} value={reassignForm.reason} onChange={event => setReassignForm(previous => ({ ...previous, reason: event.target.value }))}/></div>
        </form>}
      </Modal>

      <Modal open={detailState !== 'idle'} onClose={() => { setDetail(null); setDetailState('idle'); setHistory([]); }} title="Detalle de asignación" maxWidth={760}>
        {detailState === 'loading' && <Empty icon="book" title="Cargando detalle…"/>}
        {detailState === 'error' && <div style={alertStyle('danger')}>No fue posible cargar el detalle.</div>}
        {detailState === 'loaded' && detail && <div className="stack">
          <div className="table-wrap"><table className="data"><tbody>
            <tr><th>Libro</th><td>#{detail.book && detail.book.inventory_number} · {detail.book && detail.book.title}</td></tr>
            <tr><th>Etapa / estado</th><td>{SUPERVISOR_STAGES[detail.stage]} · {SUPERVISOR_STATUS_LABELS[detail.status]}</td></tr>
            <tr><th>Usuario</th><td>{detail.user && `${detail.user.name} (${detail.user.username})`}</td></tr>
            <tr><th>Asignó</th><td>{detail.assignedByUser ? `${detail.assignedByUser.name} (${detail.assignedByUser.username})` : '—'}</td></tr>
            <tr><th>Fechas</th><td>Asignada: {formatDateTime(detail.assignedAt)} · Inicio: {formatDateTime(detail.startedAt)} · Fin: {formatDateTime(detail.completedAt || detail.cancelledAt)}</td></tr>
            <tr><th>Observaciones</th><td>{detail.observations || '—'}</td></tr>
          </tbody></table></div>
          <div><div className="card-title mb-8">Historial del libro</div>
            {historyState === 'loading' && <div className="text-sm text-2">Cargando historial…</div>}
            {historyState === 'error' && <div style={alertStyle('danger')}>No fue posible cargar el historial.</div>}
            {historyState === 'loaded' && history.length === 0 && <div className="text-sm text-2">No hay eventos registrados.</div>}
            {historyState === 'loaded' && history.map(event => <div key={event.id} style={{ padding: 10, borderBottom: '1px solid var(--border)' }}>
              <div className="row" style={{ justifyContent: 'space-between' }}><strong>{SUPERVISOR_HISTORY_LABELS[event.action] || event.action}</strong><span className="text-xs text-3">{formatDateTime(event.effectiveAt || event.createdAt)}</span></div>
              <div className="text-xs text-2">{event.user ? event.user.name : 'Sistema'}{event.observations ? ` · ${event.observations}` : ''}</div>
            </div>)}
          </div>
        </div>}
      </Modal>
    </div>
  );
};

const Supervisor = props => window.APP_CONFIG.AUTH.MODE === 'server'
  ? <SupervisorServer {...props}/>
  : <SupervisorLocal {...props}/>;

Object.assign(window, { Supervisor });
