// ============================================================
// Módulo Control de Calidad
// ============================================================
const ControlCalidadLocal = ({ libros, currentUser, onUpdate, onSelectBook }) => {
  const [revisando, setRevisando] = useState(null);
  const [decision, setDecision] = useState(null); // 'aceptado' | 'rechazado'
  const [observaciones, setObservaciones] = useState('');

  // Cola: libros editados esperando CC
  const cola = libros.filter(l => l.estatus === 'editado')
    .sort((a, b) => a.fechaEdicion.localeCompare(b.fechaEdicion));

  const revisadosHoy = libros.filter(l =>
    l.ccPor === currentUser.id &&
    l.fechaControlCalidad === '2026-08-02'
  );

  const aceptadosHoy = revisadosHoy.filter(l => ['aceptado','en_conversion','finalizado'].includes(l.estatus)).length;
  const rechazadosHoy = revisadosHoy.filter(l => l.estatus === 'rechazado').length;

  const totalAceptados = libros.filter(l => ['aceptado','en_conversion','finalizado'].includes(l.estatus)).length;
  const totalRechazados = libros.filter(l => l.estatus === 'rechazado').length;
  const tasaAcept = totalAceptados + totalRechazados > 0 ? (totalAceptados / (totalAceptados + totalRechazados)) * 100 : 0;

  const abrirRevision = (libro) => {
    setRevisando(libro);
    setDecision(null);
    setObservaciones('');
  };

  const guardarDecision = () => {
    if (!revisando || !decision) return;
    if (decision === 'rechazado' && !observaciones.trim()) return;
    onUpdate(revisando.cons, {
      estatus: decision,
      fechaControlCalidad: '2026-08-02',
      ccPor: currentUser.id,
      observaciones: decision === 'rechazado' ? observaciones : null,
    });
    setRevisando(null);
  };

  return (
    <div className="stack">
      <div className="kpi-grid" style={{ gridTemplateColumns: 'repeat(4, 1fr)' }}>
        <KPI label="En cola" value={cola.length} sub="Libros por revisar" tone="primary"/>
        <KPI label="Revisados hoy" value={revisadosHoy.length} sub={`${aceptadosHoy} aceptados · ${rechazadosHoy} rechazados`} tone="amber"/>
        <KPI label="Tasa de aceptación" value={`${tasaAcept.toFixed(0)}%`} sub="Global del proyecto" progress={tasaAcept} tone="success"/>
        <KPI label="Rechazados totales" value={totalRechazados} sub="Requieren reproceso" tone="danger"/>
      </div>

      <div className="card">
        <div className="card-header">
          <Icon name="check_shield" size={16} style={{ color: 'var(--text-2)' }}/>
          <div>
            <div className="card-title">Cola de control de calidad (FIFO)</div>
            <div className="card-subtitle">Libros editados esperando revisión · ordenados por fecha de edición</div>
          </div>
        </div>
        <div className="table-wrap" style={{ border: 'none', borderRadius: 0 }}>
          <table className="data">
            <thead>
              <tr>
                <th style={{ width: 40 }}>#</th>
                <th>Núm.</th>
                <th>Título</th>
                <th>Editor</th>
                <th>Fecha edición</th>
                <th style={{ textAlign: 'right' }}>Imágs.</th>
                <th style={{ textAlign: 'right' }}>Acción</th>
              </tr>
            </thead>
            <tbody>
              {cola.length === 0 && (
                <tr><td colSpan="7"><Empty icon="check_shield" title="Cola vacía" subtitle="No hay libros esperando revisión de calidad"/></td></tr>
              )}
              {cola.map((l, i) => {
                const editor = getUser(l.editadoPor);
                return (
                  <tr key={l.cons}>
                    <td className="text-serif" style={{ fontSize: 18, fontWeight: 600, color: 'var(--primary)' }}>{i + 1}</td>
                    <td><span className="cons">#{l.cons}</span></td>
                    <td className="title-cell">
                      <div className="title-line">{l.titulo}</div>
                      <div className="author-line">{l.autor}</div>
                    </td>
                    <td>
                      {editor && <div className="row"><Avatar user={editor} size="sm"/><span className="text-sm">{editor.nombre.split(' ')[0]}</span></div>}
                    </td>
                    <td className="text-sm text-2">{fmtDate(l.fechaEdicion)}</td>
                    <td style={{ textAlign: 'right' }} className="text-mono">{fmtNum(l.imagenesEditadas)}</td>
                    <td style={{ textAlign: 'right' }}>
                      <button className="btn btn-primary btn-sm" onClick={() => abrirRevision(l)}>Revisar</button>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>

      {/* Historial reciente */}
      <div className="stat-grid grid-2">
        <div className="card">
          <div className="card-header">
            <Icon name="check" size={16} style={{ color: 'var(--success)' }}/>
            <div>
              <div className="card-title">Últimos aceptados</div>
              <div className="card-subtitle">Pasan a conversión</div>
            </div>
          </div>
          <div className="table-wrap" style={{ border: 'none', borderRadius: 0 }}>
            <table className="data">
              <tbody>
                {libros.filter(l => ['aceptado','en_conversion','finalizado'].includes(l.estatus))
                  .slice(-5).reverse().map(l => (
                  <tr key={l.cons} onClick={() => onSelectBook(l)} style={{ cursor: 'pointer' }}>
                    <td><span className="cons">#{l.cons}</span></td>
                    <td className="title-cell"><div className="title-line">{l.titulo}</div></td>
                    <td className="text-sm text-2">{fmtDate(l.fechaControlCalidad)}</td>
                    <td><StatusPill estatus={l.estatus}/></td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>

        <div className="card">
          <div className="card-header">
            <Icon name="alert" size={16} style={{ color: 'var(--danger)' }}/>
            <div>
              <div className="card-title">Rechazados con observaciones</div>
              <div className="card-subtitle">Requieren reproceso</div>
            </div>
          </div>
          <div style={{ padding: '8px 20px 20px' }}>
            {libros.filter(l => l.estatus === 'rechazado').length === 0 && (
              <Empty icon="check" title="Sin rechazos" subtitle="Todos los libros han sido aceptados"/>
            )}
            {libros.filter(l => l.estatus === 'rechazado').slice(0, 5).map(l => (
              <div key={l.cons} style={{ padding: 12, background: 'var(--danger-soft)', borderRadius: 6, marginBottom: 8, borderLeft: '3px solid var(--danger)' }} onClick={() => onSelectBook(l)}>
                <div className="row">
                  <span className="cons">#{l.cons}</span>
                  <div className="spacer"/>
                  <span className="text-xs text-2">{fmtDate(l.fechaControlCalidad)}</span>
                </div>
                <div style={{ fontWeight: 500, marginTop: 2 }}>{l.titulo}</div>
                <div className="text-sm mt-4" style={{ color: 'var(--danger)' }}>
                  <strong>Obs:</strong> {l.observaciones}
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>

      {/* Modal de revisión */}
      <Modal
        open={!!revisando}
        onClose={() => setRevisando(null)}
        title="Revisión de Control de Calidad"
        maxWidth={560}
        footer={<>
          <button className="btn btn-ghost" onClick={() => setRevisando(null)}>Cancelar</button>
          <button
            className="btn btn-primary"
            onClick={guardarDecision}
            disabled={!decision || (decision === 'rechazado' && !observaciones.trim())}
          >
            Guardar decisión
          </button>
        </>}
      >
        {revisando && (
          <div>
            <div style={{ padding: 12, background: 'var(--surface-2)', borderRadius: 6, marginBottom: 16 }}>
              <div className="text-xs text-3">#{revisando.cons} · {revisando.serie}</div>
              <div style={{ fontWeight: 500, fontSize: 15, marginTop: 2 }}>{revisando.titulo}</div>
              <div className="text-sm text-2 mt-4">{revisando.autor}</div>
              <div className="detail-grid" style={{ marginTop: 12 }}>
                <div className="detail-item">
                  <div className="detail-label">Digitalizado</div>
                  <div className="detail-value text-sm">{fmtDate(revisando.fechaDigitalizacion)} · {getUser(revisando.digitalizadoPor)?.nombre}</div>
                </div>
                <div className="detail-item">
                  <div className="detail-label">Editado</div>
                  <div className="detail-value text-sm">{fmtDate(revisando.fechaEdicion)} · {getUser(revisando.editadoPor)?.nombre}</div>
                </div>
                <div className="detail-item">
                  <div className="detail-label">Imágenes originales</div>
                  <div className="detail-value text-sm">{fmtNum(revisando.imagenesResultantes)}</div>
                </div>
                <div className="detail-item">
                  <div className="detail-label">Imágenes editadas</div>
                  <div className="detail-value text-sm">{fmtNum(revisando.imagenesEditadas)}</div>
                </div>
              </div>
            </div>

            <div className="field-label mb-8">Decisión</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginBottom: 16 }}>
              <button
                type="button"
                className={`check ${decision === 'aceptado' ? 'checked' : ''}`}
                style={{ justifyContent: 'center', padding: '14px 8px', fontSize: 14 }}
                onClick={() => setDecision('aceptado')}
              >
                <Icon name="check" size={16}/>
                Aceptar
              </button>
              <button
                type="button"
                className="check"
                style={{
                  justifyContent: 'center', padding: '14px 8px', fontSize: 14,
                  ...(decision === 'rechazado' ? { background: 'var(--danger-soft)', borderColor: 'var(--danger)', color: 'var(--danger)' } : {})
                }}
                onClick={() => setDecision('rechazado')}
              >
                <Icon name="x" size={16}/>
                Rechazar
              </button>
            </div>

            {decision === 'rechazado' && (
              <div className="field">
                <label className="field-label">Observaciones (obligatorio)</label>
                <textarea
                  className="textarea"
                  placeholder="Ej. Páginas 45-48 fuera de foco, reprocesar. Alineación incorrecta en portada..."
                  value={observaciones}
                  onChange={e => setObservaciones(e.target.value)}
                  autoFocus
                />
              </div>
            )}
            {decision === 'aceptado' && (
              <div style={{ padding: 12, borderRadius: 6, background: 'var(--success-soft)', color: 'var(--success)', fontSize: 13 }}>
                ✓ El libro pasará automáticamente a la etapa de <strong>Conversión</strong> con todos los formatos pendientes.
              </div>
            )}
          </div>
        )}
      </Modal>
    </div>
  );
};



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

const qcCorrectionStatusLabel = (status) => ({
  pending_digitizer: 'En proceso',
  pending_editor: 'En proceso',
  digitizer_in_progress: 'En proceso',
  editor_in_progress: 'En proceso',
  digitizer_submitted: 'Corregido',
  editor_submitted: 'Corregido',
  quality_review: 'Corregido',
  accepted: 'Libros corregidos'
}[status] || status || 'En proceso');

const qcCorrectionCanReview = (status) => ['digitizer_submitted','editor_submitted','quality_review'].includes(status);

const QualityControlServer = ({ currentUser, onSessionExpired }) => {
  const [data, setData] = useState({ status: 'loading', queue: [], active: [], corrections: [], accepted: [], today: {}, totals: {}, error: '' });
  const [reviewId, setReviewId] = useState(null);
  const [detail, setDetail] = useState(null);
  const [tab, setTab] = useState('editing');
  const [selected, setSelected] = useState(null);
  const [issue, setIssue] = useState('');
  const [observation, setObservation] = useState('');
  const [responsibleStage, setResponsibleStage] = useState('editing');
  const [saving, setSaving] = useState(false);
  const [message, setMessage] = useState(null);
  const [returnModal, setReturnModal] = useState({ open: false, stage: 'editing', scope: 'selected_images', reason: '' });
  const [zoom, setZoom] = useState(1);
  const [pan, setPan] = useState({ x: 0, y: 0 });
  const [dragging, setDragging] = useState(false);
  const dragOrigin = useRef({ x: 0, y: 0, panX: 0, panY: 0 });
  const viewerRef = useRef(null);


  const reload = async () => {
    setData(previous => ({ ...previous, status: 'loading', error: '' }));
    try {
      const response = await apiRequest('quality-control/dashboard.php');
      setData({
        status: 'loaded',
        queue: response.data.queue || [],
        active: response.data.active || [],
        corrections: response.data.corrections || [],
        accepted: response.data.accepted || [],
        today: response.data.today || {},
        totals: response.data.totals || {},
        error: ''
      });
    } catch (error) {
      if (error.status === 401) return onSessionExpired();
      setData(previous => ({ ...previous, status: 'error', error: error.message }));
    }
  };

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

  const setSelectedImage = (image) => {
    setSelected(image || null);
    setIssue(image?.issue_type || '');
    setObservation(image?.observation || '');
    setResponsibleStage(image?.responsible_stage || 'editing');
    setZoom(1);
    setPan({ x: 0, y: 0 });
    setDragging(false);
  };

  const clampZoom = value => Math.min(5, Math.max(0.5, Number(value) || 1));
  const changeZoom = nextValue => {
    const next = clampZoom(nextValue);
    setZoom(next);
    if (next <= 1) setPan({ x: 0, y: 0 });
  };
  const handleViewerWheel = event => {
    event.preventDefault();
    event.stopPropagation();
    if (typeof event.stopImmediatePropagation === 'function') {
      event.stopImmediatePropagation();
    }

    const direction = event.deltaY < 0 ? 0.2 : -0.2;
    setZoom(currentZoom => {
      const nextZoom = clampZoom(currentZoom + direction);
      if (nextZoom <= 1) {
        setPan({ x: 0, y: 0 });
      }
      return nextZoom;
    });
  };

  useEffect(() => {
    const viewer = viewerRef.current;
    if (!viewer) return undefined;

    const blockPageScroll = event => handleViewerWheel(event);
    viewer.addEventListener('wheel', blockPageScroll, { passive: false });

    return () => {
      viewer.removeEventListener('wheel', blockPageScroll);
    };
  }, [reviewId, tab]);
  const beginPan = event => {
    if (zoom <= 1) return;
    event.preventDefault();
    event.stopPropagation();
    setDragging(true);
    dragOrigin.current = {
      x: event.clientX,
      y: event.clientY,
      panX: pan.x,
      panY: pan.y
    };
  };
  const movePan = event => {
    if (!dragging) return;
    event.preventDefault();
    event.stopPropagation();
    setPan({
      x: dragOrigin.current.panX + event.clientX - dragOrigin.current.x,
      y: dragOrigin.current.panY + event.clientY - dragOrigin.current.y
    });
  };
  const endPan = () => setDragging(false);

  const openReview = async (id) => {
    setReviewId(id);
    setDetail(null);
    setTab('editing');
    try {
      const response = await apiRequest(`quality-control/detail.php?review_id=${id}`);
      setDetail(response.data);
      const correctionStatus = response.data?.active_correction?.status;
      if (correctionStatus === 'digitizer_submitted') {
        setTab('digitization');
        setSelectedImage((response.data.digitization_images || [])[0] || null);
      } else {
        setTab('editing');
        setSelectedImage((response.data.editing_images || [])[0] || null);
      }
    } catch (error) {
      if (error.status === 401) return onSessionExpired();
      setMessage({ tone: 'error', text: error.message });
      setReviewId(null);
    }
  };

  const claim = async (bookId) => {
    setSaving(true);
    try {
      const response = await apiRequest('quality-control/claim.php', { method: 'POST', body: { book_id: bookId } });
      await reload();
      await openReview(response.data.review_id);
    } catch (error) {
      setMessage({ tone: 'error', text: error.message });
    } finally {
      setSaving(false);
    }
  };

  const images = detail
    ? (tab === 'digitization' ? detail.digitization_images : detail.editing_images)
    : [];
  const selectedIndex = selected ? images.findIndex(image => image.file_name === selected.file_name) : -1;
  const imageBaseUrl = APP_CONFIG.API_BASE_URL.replace(/\/$/, '');
  const imgUrl = selected && reviewId && tab !== 'compare'
    ? `${imageBaseUrl}/quality-control/image.php?review_id=${reviewId}&source=${tab}&file=${encodeURIComponent(selected.file_name)}`
    : '';
  const compareEditingUrl = selected && reviewId
    ? `${imageBaseUrl}/quality-control/image.php?review_id=${reviewId}&source=editing&file=${encodeURIComponent(selected.file_name)}`
    : '';
  const compareOriginalName = selected && detail
    ? ((detail.digitization_images || []).find(image => image.file_name === selected.file_name)?.file_name
      || (detail.digitization_images || [])[selectedIndex]?.file_name
      || selected.file_name)
    : '';
  const compareDigitizationUrl = compareOriginalName && reviewId
    ? `${imageBaseUrl}/quality-control/image.php?review_id=${reviewId}&source=digitization&file=${encodeURIComponent(compareOriginalName)}`
    : '';

  const nextPendingImage = (updatedImages, currentIndex) => {
    for (let index = currentIndex + 1; index < updatedImages.length; index += 1) {
      if ((updatedImages[index].status || 'pending') === 'pending') return updatedImages[index];
    }
    for (let index = 0; index < updatedImages.length; index += 1) {
      if ((updatedImages[index].status || 'pending') === 'pending') return updatedImages[index];
    }
    return updatedImages[Math.min(currentIndex + 1, updatedImages.length - 1)] || null;
  };

  const saveImage = async (status) => {
    if (!selected || tab === 'digitization') return;
    if (status === 'observed' && (!issue || !observation.trim())) {
      setMessage({ tone: 'error', text: 'Selecciona el tipo de incidencia y escribe la observación.' });
      return;
    }
    setSaving(true);
    try {
      const saveResponse = await apiRequest('quality-control/save-image.php', {
        method: 'POST',
        body: {
          image_id: selected.id,
          status,
          issue_type: status === 'observed' ? issue : null,
          observation: status === 'observed' ? observation.trim() : null,
          responsible_stage: status === 'observed' ? responsibleStage : null
        }
      });
      if (saveResponse?.data?.correction_created) {
        setReviewId(null);
        setDetail(null);
        await reload();
        setMessage({
          tone: 'success',
          text: 'La nueva observación se guardó y el libro regresó a En proceso de corrección.'
        });
        return;
      }
      const response = await apiRequest(`quality-control/detail.php?review_id=${reviewId}`);
      const refreshed = response.data;
      setDetail(refreshed);
      const refreshedImages = refreshed.editing_images || [];
      const currentIndex = refreshedImages.findIndex(image => image.file_name === selected.file_name);
      setSelectedImage(nextPendingImage(refreshedImages, currentIndex));
      setMessage({ tone: 'success', text: saveResponse?.data?.changed === false
        ? 'No se detectaron cambios; el libro conserva su estado actual.'
        : (status === 'correct' ? 'Imagen correcta. Se abrió la siguiente pendiente.' : 'Observación guardada. Se abrió la siguiente pendiente.') });
    } catch (error) {
      setMessage({ tone: 'error', text: error.message });
    } finally {
      setSaving(false);
    }
  };

  const decide = async () => {
    setSaving(true);
    try {
      await apiRequest('quality-control/decision.php', { method: 'POST', body: { review_id: reviewId, decision: 'approved', general_observations: null } });
      setReviewId(null);
      setDetail(null);
      await reload();
      setMessage({ tone: 'success', text: 'Libro aprobado y enviado a Conversión.' });
    } catch (error) {
      setMessage({ tone: 'error', text: error.message });
    } finally {
      setSaving(false);
    }
  };


  const dispatchCorrections = async () => {
    if (!detail || Number(detail.review.reviewed_images) < Number(detail.review.total_images)) {
      setMessage({ tone: 'error', text: 'Debes revisar todas las imágenes antes de enviar correcciones.' });
      return;
    }
    if (Number(detail.review.observed_images) < 1) {
      setMessage({ tone: 'error', text: 'No existen imágenes con observación.' });
      return;
    }
    setSaving(true);
    try {
      const response = await apiRequest('quality-control/dispatch-corrections.php', {
        method: 'POST',
        body: { review_id: reviewId }
      });
      setReviewId(null);
      setDetail(null);
      await reload();
      setMessage({
        tone: 'success',
        text: `Las observaciones se enviaron al Buzón de correcciones. Solicitud #${response.data.correction_id}.`
      });
    } catch (error) {
      setMessage({ tone: 'error', text: error.message });
    } finally {
      setSaving(false);
    }
  };


  const validateActiveCorrection = async (decision) => {
    const correction = detail?.active_correction;
    if (!correction?.id) return;
    const reason = decision === 'rejected'
      ? window.prompt('Motivo del rechazo:', '')
      : '';
    if (decision === 'rejected' && !reason) return;

    setSaving(true);
    setMessage(null);
    try {
      const response = await apiRequest('corrections/validate.php', {
        method: 'POST',
        body: {
          correction_id: Number(correction.id),
          decision,
          reason
        }
      });

      setReviewId(null);
      setDetail(null);
      await reload();

      const nextStatus = response?.data?.status;
      setMessage({
        tone: 'success',
        text: nextStatus === 'pending_editor'
          ? 'La corrección de Digitalización fue validada. Ahora se envió una nueva solicitud al editor para actualizar las imágenes editadas.'
          : decision === 'approved'
            ? 'La corrección fue validada correctamente.'
            : 'La corrección fue rechazada y regresó al responsable.'
      });
    } catch (error) {
      if (error?.status === 401) {
        onSessionExpired();
        return;
      }
      setMessage({ tone: 'error', text: error?.message || 'No fue posible validar la corrección.' });
    } finally {
      setSaving(false);
    }
  };

  const submitReturn = async () => {
    if (!returnModal.reason.trim()) return;
    setSaving(true);
    try {
      const response = await apiRequest('quality-control/return-book.php', {
        method: 'POST',
        body: {
          review_id: reviewId,
          responsible_stage: returnModal.stage,
          scope: returnModal.scope,
          reason: returnModal.reason.trim()
        }
      });
      setReturnModal({ open: false, stage: 'editing', scope: 'selected_images', reason: '' });
      setReviewId(null);
      setDetail(null);
      await reload();
      setMessage({ tone: 'success', text: `Libro regresado. Se creó la corrección #${response.data.correction_id}.` });
    } catch (error) {
      setMessage({ tone: 'error', text: error.message });
    } finally {
      setSaving(false);
    }
  };

  if (reviewId) {
    return <div className="stack quality-workspace">
      <div className="row" style={{ justifyContent: 'space-between' }}>
        <button className="btn btn-ghost" onClick={() => { setReviewId(null); setDetail(null); reload(); }}>← Regresar a Control de calidad</button>
        <div className="row" style={{ gap: 8 }}>
          {detail?.review?.status !== 'approved' && (
            <button className="btn" style={{ color: 'var(--danger)' }} onClick={() => setReturnModal(previous => ({ ...previous, open: true }))}>Regresar todo el libro</button>
          )}
          {detail && detail.review.status !== 'approved' && Number(detail.review.reviewed_images) >= Number(detail.review.total_images) && Number(detail.review.observed_images) > 0 && (
            <button className="btn btn-primary" disabled={saving} onClick={dispatchCorrections}>Enviar observaciones a corrección</button>
          )}
          {detail?.review?.status === 'approved' ? (
            <span className="status-pill" style={{ background: '#dcfce7', color: '#166534' }}><span className="badge-dot" style={{ background: '#166534' }}/>Libro aceptado</span>
          ) : (
            <button className="btn btn-primary" disabled={saving || !detail || Number(detail.review.reviewed_images) < Number(detail.review.total_images) || Number(detail.review.observed_images) > 0} onClick={decide}>Aprobar y enviar a Conversión</button>
          )}
        </div>
      </div>

      {message && <div role="alert" style={{ padding: 12, borderRadius: 6, background: message.tone === 'success' ? '#f0fdf4' : '#fef2f2', color: message.tone === 'success' ? '#166534' : '#991b1b' }}>{message.text}</div>}

      {!detail ? <div className="card"><Empty icon="check_shield" title="Cargando revisión" subtitle="Preparando las imágenes…"/></div> : <>
        <div className="card"><div className="card-header"><Icon name="book"/><div><div className="card-title">{detail.review.title}</div><div className="card-subtitle">#{detail.review.inventory_number} · {detail.review.series_collection}</div></div></div><div style={{ padding: 16 }} className="detail-grid"><div><div className="detail-label">Digitalizado por</div><div>{detail.actors.digitization?.name || '—'}</div></div><div><div className="detail-label">Editado por</div><div>{detail.actors.editing?.name || '—'}</div></div><div><div className="detail-label">Imágenes digitalizadas</div><div>{detail.digitization_images.length}</div></div><div><div className="detail-label">Imágenes editadas</div><div>{detail.editing_images.length}</div></div></div></div>
        <div className="row" style={{ gap: 8 }}>
          <button className={`btn ${tab === 'editing' ? 'btn-primary' : 'btn-ghost'}`} onClick={() => { setTab('editing'); setSelectedImage(detail.editing_images[0] || null); }}>Edición ({detail.editing_images.length})</button>
          <button className={`btn ${tab === 'digitization' ? 'btn-primary' : 'btn-ghost'}`} onClick={() => { setTab('digitization'); setSelectedImage(detail.digitization_images[0] || null); }}>Digitalización ({detail.digitization_images.length})</button>
          <button className={`btn ${tab === 'compare' ? 'btn-primary' : 'btn-ghost'}`} onClick={() => { setTab('compare'); setSelectedImage(detail.editing_images[0] || null); }}>Comparar</button>
        </div>
        <div className="quality-review-grid">
          <div className="card quality-image-list">{images.map((image, index) => <button key={`${tab}-${image.file_name}`} onClick={() => setSelectedImage(image)} className={selected?.file_name === image.file_name ? 'quality-image-row active' : 'quality-image-row'}><span>{index + 1}. {image.file_name}</span><span className={`quality-image-status ${image.status || 'pending'}`}>{image.status === 'correct' ? 'Correcta' : image.status === 'observed' ? 'Observada' : image.status === 'corrected' ? 'Corregida' : 'Pendiente'}</span></button>)}</div>
          <div className={`card quality-viewer ${tab === 'compare' ? 'quality-compare-viewer' : ''}`} style={{minHeight:'72vh',overflow:'hidden',position:'relative'}}>
            <div style={{position:'absolute',top:10,left:10,right:10,zIndex:4,display:'flex',justifyContent:'space-between',alignItems:'center',gap:8,pointerEvents:'none'}}>
              <div className="row" style={{gap:6,pointerEvents:'auto',padding:6,borderRadius:8,background:'rgba(255,255,255,.94)',boxShadow:'0 2px 10px rgba(15,23,42,.14)'}}>
                <button className="btn btn-ghost btn-sm" type="button" onClick={()=>changeZoom(zoom-0.2)} disabled={zoom<=0.5}>−</button>
                <strong className="text-mono" style={{minWidth:58,textAlign:'center'}}>{Math.round(zoom*100)}%</strong>
                <button className="btn btn-ghost btn-sm" type="button" onClick={()=>changeZoom(zoom+0.2)} disabled={zoom>=5}>+</button>
                <button className="btn btn-ghost btn-sm" type="button" onClick={()=>{setZoom(1);setPan({x:0,y:0});}}>Ajustar</button>
                <button className="btn btn-ghost btn-sm" type="button" onClick={()=>changeZoom(2)}>200%</button>
              </div>
              <div className="text-xs text-2" style={{pointerEvents:'auto',padding:'7px 10px',borderRadius:8,background:'rgba(255,255,255,.94)'}}>
                Rueda para zoom · Arrastra al ampliar
              </div>
            </div>
            <div
              ref={viewerRef}
              className="quality-viewer-interaction"
              onMouseDown={beginPan}
              onMouseMove={movePan}
              onMouseUp={endPan}
              onMouseLeave={endPan}
              style={{
                position:'absolute',
                inset:0,
                padding:'64px 18px 42px',
                overflow:'hidden',
                cursor:zoom>1?(dragging?'grabbing':'grab'):'default',
                display:'flex',
                alignItems:'center',
                justifyContent:'center',
                background:'var(--surface-2)'
              }}
            >
              <div style={{
                width:'100%',
                height:'100%',
                display:'flex',
                alignItems:'center',
                justifyContent:'center',
                transform:`translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
                transformOrigin:'center center',
                transition:dragging?'none':'transform .12s ease'
              }}>
                {tab === 'compare' ? (
                  selected ? <div className="quality-compare-grid" style={{width:'100%',height:'100%',gap:12}}>
                    <div className="quality-compare-pane" style={{minHeight:0}}><div className="quality-compare-title">Digitalización</div><img draggable="false" style={{maxWidth:'100%',maxHeight:'calc(72vh - 120px)',objectFit:'contain',userSelect:'none'}} src={compareDigitizationUrl} alt={`Original ${compareOriginalName}`} /></div>
                    <div className="quality-compare-pane" style={{minHeight:0}}><div className="quality-compare-title">Edición</div><img draggable="false" style={{maxWidth:'100%',maxHeight:'calc(72vh - 120px)',objectFit:'contain',userSelect:'none'}} src={compareEditingUrl} alt={`Editada ${selected.file_name}`} /></div>
                  </div> : <div>Selecciona una imagen</div>
                ) : (imgUrl ? <img draggable="false" style={{maxWidth:'100%',maxHeight:'100%',objectFit:'contain',userSelect:'none'}} src={imgUrl} alt={selected?.file_name} /> : <div>Selecciona una imagen</div>)}
              </div>
            </div>
            <div className="quality-counter" style={{zIndex:5}}>Imagen {selectedIndex + 1} de {images.length}</div>
          </div>
          <div className="detail-section"><div className="detail-section-title">Resultado de la imagen</div>{tab === 'digitization' ? (
            detail?.active_correction && detail.active_correction.status === 'digitizer_submitted' ? <div className="stack" style={{gap:10}}>
              <div style={{padding:12,borderRadius:6,background:'#eff6ff',color:'#1e40af'}}>
                El digitalizador sustituyó la imagen en <strong>DIGITALIZACION</strong>. Revisa esta pestaña antes de continuar.
              </div>
              <button className="btn btn-primary" type="button" disabled={saving} onClick={()=>validateActiveCorrection('approved')}>
                Validar Digitalización y enviar al editor
              </button>
              <button className="btn" type="button" style={{color:'var(--danger)'}} disabled={saving} onClick={()=>validateActiveCorrection('rejected')}>
                Rechazar corrección de Digitalización
              </button>
            </div> : <div className="text-sm text-2">La imagen original se muestra como referencia. Cuando exista una corrección enviada por Digitalización, aquí aparecerán las opciones para validarla antes de notificar al editor.</div>
          ) : <><button className="btn btn-primary" style={{ width: '100%', marginBottom: 12 }} disabled={saving} onClick={() => saveImage('correct')}>Marcar como correcta y continuar</button><select className="input" value={issue} onChange={event => setIssue(event.target.value)}><option value="">Tipo de incidencia</option>{['Imagen borrosa','Recorte incorrecto','Página incompleta','Orientación incorrecta','Mancha o sombra','Orden incorrecto','Imagen duplicada','Imagen faltante','Color incorrecto','Problema de digitalización','Problema de edición','Otra'].map(value => <option key={value}>{value}</option>)}</select><select className="input" value={responsibleStage} onChange={event => setResponsibleStage(event.target.value)} style={{ marginTop: 10 }}><option value="editing">Responsable: Edición</option><option value="digitization">Responsable: Digitalización</option></select><textarea className="input" rows="6" placeholder="Observación de la imagen" value={observation} onChange={event => setObservation(event.target.value)} style={{ marginTop: 10 }}/><button className="btn" style={{ width: '100%', marginTop: 10, color: 'var(--danger)' }} disabled={saving} onClick={() => saveImage('observed')}>Guardar observación y continuar</button></>}</div>
        </div>
        <div className="card" style={{ padding: 16 }}><strong>Avance:</strong> {detail.review.reviewed_images}/{detail.review.total_images} · Correctas: {detail.review.correct_images} · Con observación: {detail.review.observed_images}{Number(detail.review.reviewed_images) >= Number(detail.review.total_images) && Number(detail.review.observed_images) > 0 && <div style={{ marginTop: 10, padding: 10, borderRadius: 6, background: '#fffbeb', color: '#92400e' }}>La revisión terminó con observaciones. Presiona <strong>Enviar observaciones a corrección</strong> para que aparezcan en el buzón del responsable.</div>}</div>
      </>}

      <Modal open={returnModal.open} onClose={() => setReturnModal(previous => ({ ...previous, open: false }))} title="Regresar todo el libro" maxWidth={620} footer={<><button className="btn btn-ghost" onClick={() => setReturnModal(previous => ({ ...previous, open: false }))}>Cancelar</button><button className="btn" style={{ color: 'var(--danger)' }} disabled={saving || !returnModal.reason.trim()} onClick={submitReturn}>Respaldar y regresar el libro</button></>}>
        <div className="stack"><div style={{ padding: 12, borderRadius: 6, background: '#fffbeb', color: '#92400e' }}>Antes de limpiar archivos se crea un respaldo con fecha y hora. La operación queda registrada para evitar duplicaciones.</div><div className="field"><label className="field-label">Responsable de la corrección</label><select className="input" value={returnModal.stage} onChange={event => setReturnModal(previous => ({ ...previous, stage: event.target.value }))}><option value="editing">Edición</option><option value="digitization">Digitalización</option><option value="both">Digitalización y después Edición</option></select></div><div className="field"><label className="field-label">Alcance</label><select className="input" value={returnModal.scope} onChange={event => setReturnModal(previous => ({ ...previous, scope: event.target.value }))}><option value="selected_images">Solo imágenes observadas</option><option value="whole_book">Todo el libro</option></select></div><div className="field"><label className="field-label">Motivo</label><textarea className="input" rows="5" value={returnModal.reason} onChange={event => setReturnModal(previous => ({ ...previous, reason: event.target.value }))} placeholder="Explica por qué debe corregirse el libro."/></div></div>
      </Modal>
    </div>;
  }

  const approved = Number(data.totals.approved || 0), rejected = Number(data.totals.rejected || 0), rate = approved + rejected ? Math.round(approved * 100 / (approved + rejected)) : 0;
  return <div className="stack">
    {message && <div role="alert" style={{ padding: 12, borderRadius: 6, background: message.tone === 'success' ? '#f0fdf4' : '#fef2f2', color: message.tone === 'success' ? '#166534' : '#991b1b' }}>{message.text}</div>}
    <div className="kpi-grid" style={{ gridTemplateColumns: 'repeat(4,1fr)' }}><KPI label="En cola" value={data.queue.length} sub="Libros por revisar" tone="primary"/><KPI label="Revisados hoy" value={Number(data.today.total || 0)} sub={`${Number(data.today.approved || 0)} aceptados · ${Number(data.today.rejected || 0)} rechazados`} tone="amber"/><KPI label="Tasa de aceptación" value={`${rate}%`} sub="Global del proyecto" progress={rate} tone="success"/><KPI label="Rechazados totales" value={rejected} sub="Requieren corrección" tone="danger"/></div>
    <div className="card"><div className="card-header"><Icon name="check_shield"/><div><div className="card-title">Cola de control de calidad (FIFO)</div><div className="card-subtitle">Libros editados con imágenes disponibles</div></div></div><div className="table-wrap" style={{ border: 'none' }}><table className="data"><thead><tr><th>#</th><th>Núm.</th><th>Título</th><th>Editor</th><th>Fecha edición</th><th>Imágs.</th><th>Acción</th></tr></thead><tbody>{data.queue.map((book, index) => <tr key={book.id}><td>{index + 1}</td><td>#{book.inventory_number}</td><td><strong>{book.title}</strong><div className="text-xs text-3">{book.author}</div></td><td>{book.editor_name}</td><td>{qcFormatDate(book.edited_at)}</td><td>{Number(book.edited_images || 0)}</td><td><button className="btn btn-primary btn-sm" disabled={saving} onClick={() => claim(book.id)}>Revisar</button></td></tr>)}{!data.queue.length && <tr><td colSpan="7"><Empty icon="check_shield" title="Cola vacía" subtitle="No hay libros esperando revisión"/></td></tr>}</tbody></table></div></div>
    <div className="card"><div className="card-header"><Icon name="alert"/><div><div className="card-title">En proceso de corrección</div><div className="card-subtitle">Libros enviados al responsable y correcciones pendientes de validar</div></div></div><div className="table-wrap" style={{ border: 'none' }}><table className="data"><thead><tr><th>Libro</th><th>Serie / Título</th><th>Responsable</th><th>Imágenes</th><th>Estado</th><th>Actualización</th><th>Acción</th></tr></thead><tbody>{data.corrections.map(item => <tr key={item.correction_id}><td>#{item.inventory_number}</td><td><strong>{item.title}</strong><div className="text-xs text-3">{item.series_collection}</div></td><td>{item.assigned_user_name || 'Sin asignar'}</td><td>{Number(item.image_count || 0)}</td><td><span className={`status-pill ${item.status}`}><span className="badge-dot"/>{qcCorrectionStatusLabel(item.status)}</span></td><td>{qcFormatDate(item.updated_at)}</td><td>{qcCorrectionCanReview(item.status) ? <button className="btn btn-primary btn-sm" onClick={() => openReview(item.quality_review_id)}>Revisar corrección</button> : <span className="text-sm text-2">En proceso</span>}</td></tr>)}{!data.corrections.length && <tr><td colSpan="7"><Empty icon="check" title="Sin correcciones activas" subtitle="No hay libros en proceso de corrección"/></td></tr>}</tbody></table></div></div>
    <div className="card"><div className="card-header"><Icon name="check"/><div><div className="card-title">Libros aceptados</div><div className="card-subtitle">Revisiones aprobadas; abrir no cambia el estado mientras no registres una nueva observación</div></div></div><div className="table-wrap" style={{ border: 'none' }}><table className="data"><thead><tr><th>Libro</th><th>Serie / Título</th><th>Revisó</th><th>Imágenes</th><th>Fecha de aceptación</th><th>Estado</th><th>Acción</th></tr></thead><tbody>{data.accepted.map(item => <tr key={item.review_id}><td>#{item.inventory_number}</td><td><strong>{item.title}</strong><div className="text-xs text-3">{item.series_collection}</div></td><td>{item.reviewer_name}</td><td>{Number(item.total_images || 0)}</td><td>{qcFormatDate(item.completed_at)}</td><td><span className="status-pill" style={{ background: '#dcfce7', color: '#166534' }}><span className="badge-dot" style={{ background: '#166534' }}/>Aceptado</span></td><td><button className="btn btn-ghost btn-sm" onClick={() => openReview(item.review_id)}>Revisar</button></td></tr>)}{!data.accepted.length && <tr><td colSpan="7"><Empty icon="check" title="Sin libros aceptados" subtitle="Los libros aprobados aparecerán en esta sección"/></td></tr>}</tbody></table></div></div>
    {!!data.active.length && <div className="card"><div className="card-header"><div><div className="card-title">Revisiones en proceso</div></div></div><div style={{ padding: 16, display: 'flex', gap: 10, flexWrap: 'wrap' }}>{data.active.map(review => <button key={review.review_id} className="btn btn-ghost" onClick={() => openReview(review.review_id)}>{review.title} · {review.reviewed_images}/{review.total_images}</button>)}</div></div>}
  </div>;
};

const ControlCalidad = (props) => window.APP_CONFIG.AUTH.MODE === 'server'
  ? <QualityControlServer currentUser={props.currentUser} onSessionExpired={props.onSessionExpired}/>
  : <ControlCalidadLocal {...props}/>;
Object.assign(window, { ControlCalidad });
