// ============================================================
// Módulo Digitalización
// ============================================================
const DIGITALIZATION_UPLOAD_BATCH_SIZE = 50;

const DIGITALIZATION_ASSIGNMENT_STATUS = Object.freeze({
  pending: 'nuevo',
  in_progress: 'en_digitalizacion',
  completed: 'digitalizado',
  cancelled: 'rechazado',
  reassigned: 'rechazado'
});

const toIsoDate = (value) => {
  if (!value) return '';
  if (/^\d{4}-\d{2}-\d{2}$/.test(String(value))) return String(value);
  const normalized = String(value).replace(' ', 'T');
  const date = new Date(normalized);
  if (Number.isNaN(date.getTime())) return '';
  return date.toISOString().slice(0, 10);
};

const formatShortDate = (value) => {
  const iso = toIsoDate(value);
  if (!iso) return '—';
  const [year, month, day] = iso.split('-');
  return `${day}/${month}/${year}`;
};

const mapCurrentBookStatus = (stage, status) => {
  switch (stage) {
    case 'digitization':
      return status === 'completed' ? 'digitalizado' : 'en_digitalizacion';
    case 'editing':
      return status === 'completed' ? 'editado' : 'en_edicion';
    case 'quality_control':
      if (status === 'rejected') return 'rechazado';
      if (status === 'accepted' || status === 'completed') return 'aceptado';
      return 'en_cc';
    case 'conversion':
      return status === 'completed' ? 'finalizado' : 'en_conversion';
    case 'publication':
    case 'published':
      return 'finalizado';
    default:
      if (status === 'completed') return 'digitalizado';
      if (status === 'in_progress') return 'en_digitalizacion';
      return 'nuevo';
  }
};

const ServerDigitalizationDetailModal = ({ item, currentUser, onClose, onSessionExpired }) => {
  const [state, setState] = useState({ status: item ? 'loading' : 'idle', book: null, error: '' });

  useEffect(() => {
    if (!item?.book?.id) return undefined;

    let active = true;
    const controller = new AbortController();

    const loadBook = async () => {
      setState({ status: 'loading', book: null, error: '' });
      try {
        const response = await window.apiRequest(`books/show.php?id=${encodeURIComponent(item.book.id)}`, {
          signal: controller.signal
        });
        if (!active) return;
        setState({
          status: 'loaded',
          book: response && response.data ? response.data.book : null,
          error: ''
        });
      } catch (error) {
        if (!active) return;
        if (error && error.status === 401) {
          onClose();
          onSessionExpired();
          return;
        }
        setState({
          status: 'error',
          book: null,
          error: error && error.message ? error.message : 'No fue posible cargar el detalle del libro.'
        });
      }
    };

    loadBook();

    return () => {
      active = false;
      controller.abort();
    };
  }, [item?.book?.id, onClose, onSessionExpired]);

  if (!item) return null;

  const book = state.book || {};
  const title = book.title || item.book?.title || 'Libro sin título';
  const inventoryNumber = book.inventory_number || item.book?.inventory_number || item.book?.id || item.assignment_id;
  const series = book.publisher || item.book?.series_collection || 'Sin serie';
  const totalPages = book.total_pages || item.book?.total_pages || 0;
  const barcode = book.barcode || '—';
  const statusKey = mapCurrentBookStatus(item.book?.current_stage, item.book?.status);

  return (
    <Modal open={!!item} onClose={onClose} title={`Ficha del libro #${inventoryNumber}`} maxWidth={780}>
      {state.status === 'loading' && (
        <Empty icon="library" title="Cargando ficha del libro" subtitle="Obteniendo los datos del servidor…"/>
      )}
      {state.status === 'error' && (
        <div role="alert" style={{ color: 'var(--danger)' }}>{state.error}</div>
      )}
      {state.status !== 'loading' && state.status !== 'error' && (
        <div className="book-detail">
          <div className="detail-section">
            <div className="detail-section-title">Datos del libro</div>
            <div className="stack" style={{ gap: 10 }}>
              <div><div className="detail-label">Núm. Consecutivo</div><div className="detail-value text-mono">#{inventoryNumber}</div></div>
              <div><div className="detail-label">Serie o Colección</div><div className="detail-value">{item.book?.series_collection || '—'}</div></div>
              <div><div className="detail-label">Título</div><div className="detail-value fw-600 text-serif" style={{ fontSize: 16 }}>{title}</div></div>
              <div><div className="detail-label">Autor</div><div className="detail-value">{book.author || item.book?.author || '—'}</div></div>
              <div><div className="detail-label">Pie de imprenta</div><div className="detail-value text-sm">{book.publisher ? `${book.publisher}${book.publication_year ? `, ${book.publication_year}` : ''}` : '—'}</div></div>
              <div className="detail-grid">
                <div><div className="detail-label">Páginas</div><div className="detail-value">{fmtNum(totalPages)}</div></div>
                <div><div className="detail-label">Código de barras</div><div className="detail-value text-mono text-sm">{barcode}</div></div>
              </div>
            </div>
          </div>

          <div className="stack" style={{ gap: 16 }}>
            <div className="detail-section">
              <div className="detail-section-title">Proceso de digitalización</div>
              <div className="detail-grid">
                <div><div className="detail-label">Fecha</div><div className="detail-value text-sm">{formatShortDate(item.completed_at)}</div></div>
                <div><div className="detail-label">Digitalizado por</div><div className="detail-value text-sm">{currentUser.nombre || currentUser.name || '—'}</div></div>
                <div><div className="detail-label">Imágenes</div><div className="detail-value text-mono">{fmtNum(item.resulting_images || 0)}</div></div>
                <div><div className="detail-label">Acumulado</div><div className="detail-value text-mono">{fmtNum(item.cumulative_images || 0)}</div></div>
              </div>
            </div>

            <div className="detail-section">
              <div className="detail-section-title">Proceso de edición</div>
              <div className="detail-grid">
                <div><div className="detail-label">Fecha</div><div className="detail-value text-sm">—</div></div>
                <div><div className="detail-label">Editado por</div><div className="detail-value text-sm">—</div></div>
              </div>
            </div>

            <div className="detail-section">
              <div className="detail-section-title">Control de calidad</div>
              <div className="detail-grid">
                <div><div className="detail-label">Fecha</div><div className="detail-value text-sm">—</div></div>
                <div><div className="detail-label">Revisado por</div><div className="detail-value text-sm">—</div></div>
              </div>
              <div className="mt-12"><div className="detail-label">Estatus</div><div className="mt-4"><StatusPill estatus={statusKey}/></div></div>
            </div>

            <div className="detail-section">
              <div className="detail-section-title">Conversión</div>
              <div className="detail-grid">
                <div><div className="detail-label">Fecha</div><div className="detail-value text-sm">—</div></div>
                <div><div className="detail-label">Status final</div><div className="detail-value text-sm">—</div></div>
              </div>
            </div>
          </div>
        </div>
      )}
    </Modal>
  );
};

const DigitalizacionLocal = ({ libros, currentUser, onUpdate, onSelectBook }) => {
  const [formState, setFormState] = useState({
    open: false,
    mode: 'complete',
    book: null,
    images: '',
    date: '2026-08-02',
    error: ''
  });

  const misLibros = libros.filter(l =>
    (l.estatus === 'en_digitalizacion' && l.digitalizadoPor === currentUser.id) || l.estatus === 'nuevo'
  );

  const misFinalizados = libros.filter(l =>
    l.digitalizadoPor === currentUser.id &&
    ['digitalizado', 'en_edicion', 'editado', 'en_cc', 'aceptado', 'rechazado', 'en_conversion', 'finalizado'].includes(l.estatus)
  );

  const totalImagenesHoy = misFinalizados
    .filter(l => l.fechaDigitalizacion === '2026-08-02')
    .reduce((sum, l) => sum + (l.imagenesResultantes || 0), 0);

  const iniciarDigitalizacion = (libro) => {
    onUpdate(libro.cons, { estatus: 'en_digitalizacion', digitalizadoPor: currentUser.id });
  };

  const openCompleteModal = (libro) => {
    setFormState({ open: true, mode: 'complete', book: libro, images: '', date: '2026-08-02', error: '' });
  };

  const openEditModal = (libro) => {
    setFormState({
      open: true,
      mode: 'edit',
      book: libro,
      images: libro.imagenesResultantes ? String(libro.imagenesResultantes) : '',
      date: libro.fechaDigitalizacion || '2026-08-02',
      error: ''
    });
  };

  const closeFormModal = () => {
    setFormState({ open: false, mode: 'complete', book: null, images: '', date: '2026-08-02', error: '' });
  };

  const submitLocalForm = (event) => {
    event.preventDefault();
    if (!formState.book) return;
    const images = parseInt(formState.images, 10);
    if (isNaN(images) || images <= 0) {
      setFormState(previous => ({ ...previous, error: 'Ingresa una cantidad válida de imágenes.' }));
      return;
    }
    onUpdate(formState.book.cons, {
      estatus: formState.mode === 'complete' ? 'digitalizado' : formState.book.estatus,
      fechaDigitalizacion: formState.date,
      digitalizadoPor: currentUser.id,
      imagenesResultantes: images,
      acumuladoImagenes: formState.mode === 'edit'
        ? (formState.book.acumuladoImagenes ? formState.book.acumuladoImagenes - (formState.book.imagenesResultantes || 0) + images : images)
        : images,
    });
    closeFormModal();
  };

  return (
    <div className="stack">
      <div className="kpi-grid" style={{ gridTemplateColumns: 'repeat(3, 1fr)' }}>
        <KPI label="En mi cola" value={misLibros.length} sub="Disponibles o tomados por mí" tone="primary"/>
        <KPI label="Digitalizados por mí" value={misFinalizados.length} sub="Acumulado en el proyecto" tone="success"/>
        <KPI label="Imágenes hoy" value={fmtNum(totalImagenesHoy)} sub="Producción del día" tone="amber"/>
      </div>

      <div className="card">
        <div className="card-header">
          <Icon name="camera" size={16} style={{ color: 'var(--text-2)' }}/>
          <div>
            <div className="card-title">Mi cola de digitalización</div>
            <div className="card-subtitle">Obras no digitalizadas disponibles y trabajos tomados por ti · Los libros de otros usuarios no aparecen</div>
          </div>
        </div>
        <div className="table-wrap" style={{ border: 'none', borderRadius: 0, maxHeight: 620 }}>
          <table className="data">
            <thead>
              <tr>
                <th>Núm.</th>
                <th>Serie / Título</th>
                <th>Autor</th>
                <th style={{ textAlign: 'right' }}>Págs.</th>
                <th>Estatus</th>
                <th style={{ textAlign: 'right' }}>Acción</th>
              </tr>
            </thead>
            <tbody>
              {misLibros.length === 0 && (
                <tr><td colSpan="6"><Empty icon="camera" title="Sin libros en cola" subtitle="No hay obras disponibles sin digitalizar ni trabajos tomados por ti"/></td></tr>
              )}
              {misLibros.map(libro => (
                <tr key={libro.cons}>
                  <td><span className="cons">#{libro.cons}</span></td>
                  <td className="title-cell">
                    <div className="text-xs text-3" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}>{libro.serie}</div>
                    <div className="title-line">{libro.titulo}</div>
                  </td>
                  <td className="text-sm text-2">{libro.autor}</td>
                  <td style={{ textAlign: 'right' }} className="text-mono text-sm">{libro.paginas}</td>
                  <td><StatusPill estatus={libro.estatus}/></td>
                  <td style={{ textAlign: 'right' }}>
                    {libro.estatus === 'nuevo' ? (
                      <button className="btn btn-ghost btn-sm" onClick={() => iniciarDigitalizacion(libro)}>Iniciar</button>
                    ) : (
                      <button className="btn btn-primary btn-sm" onClick={() => openCompleteModal(libro)}>Finalizar</button>
                    )}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>

      <div className="card">
        <div className="card-header">
          <Icon name="check" size={16} style={{ color: 'var(--text-2)' }}/>
          <div>
            <div className="card-title">Mis libros digitalizados</div>
            <div className="card-subtitle">Últimos {Math.min(10, misFinalizados.length)} libros procesados</div>
          </div>
        </div>
        <div className="table-wrap" style={{ border: 'none', borderRadius: 0 }}>
          <table className="data">
            <thead>
              <tr>
                <th>Núm.</th>
                <th>Título</th>
                <th>Fecha</th>
                <th style={{ textAlign: 'right' }}>Imágenes</th>
                <th style={{ textAlign: 'right' }}>Acumulado</th>
                <th>Estatus actual</th>
                <th style={{ textAlign: 'right' }}>Acciones</th>
              </tr>
            </thead>
            <tbody>
              {misFinalizados.length === 0 && (
                <tr><td colSpan="7"><Empty icon="check" title="Sin libros digitalizados" subtitle="Aún no tienes libros finalizados en digitalización"/></td></tr>
              )}
              {misFinalizados.slice(-10).reverse().map(libro => (
                <tr key={libro.cons}>
                  <td><span className="cons">#{libro.cons}</span></td>
                  <td className="title-cell"><div className="title-line">{libro.titulo}</div></td>
                  <td className="text-sm text-2">{fmtDate(libro.fechaDigitalizacion)}</td>
                  <td style={{ textAlign: 'right' }} className="text-mono">{fmtNum(libro.imagenesResultantes)}</td>
                  <td style={{ textAlign: 'right' }} className="text-mono text-2">{fmtNum(libro.acumuladoImagenes)}</td>
                  <td><StatusPill estatus={libro.estatus}/></td>
                  <td style={{ textAlign: 'right' }}>
                    <div className="row" style={{ justifyContent: 'flex-end', gap: 8 }}>
                      <button className="btn btn-ghost btn-sm" onClick={() => onSelectBook(libro)}>Detalles</button>
                      <button className="btn btn-primary btn-sm" onClick={() => openEditModal(libro)}>Editar</button>
                    </div>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>

      <Modal
        open={formState.open}
        onClose={closeFormModal}
        title={formState.mode === 'edit' ? 'Editar digitalización' : 'Finalizar digitalización'}
        maxWidth={520}
        footer={<>
          <button className="btn btn-ghost" type="button" onClick={closeFormModal}>Cancelar</button>
          <button className="btn btn-primary" type="submit" form="local-digitization-form">
            {formState.mode === 'edit' ? 'Guardar cambios' : 'Registrar y enviar a cola'}
          </button>
        </>}
      >
        {formState.book && (
          <form id="local-digitization-form" onSubmit={submitLocalForm}>
            <div style={{ padding: 12, background: 'var(--surface-2)', borderRadius: 6, marginBottom: 16 }}>
              <div className="text-xs text-3">#{formState.book.cons}</div>
              <div style={{ fontWeight: 600, fontSize: 18, marginTop: 2 }}>{formState.book.titulo}</div>
              <div className="text-sm text-2 mt-4">{formState.book.autor} · {formState.book.paginas} páginas</div>
            </div>
            <div className="field">
              <label className="field-label">Cantidad de imágenes resultantes</label>
              <input
                type="number"
                className="input"
                placeholder="Ej. 245"
                min="1"
                value={formState.images}
                onChange={event => setFormState(previous => ({ ...previous, images: event.target.value, error: '' }))}
                autoFocus
              />
              <div className="field-help">Debe coincidir con las páginas escaneadas más portadas y guardas</div>
            </div>
            <div className="field">
              <label className="field-label">Fecha de digitalización</label>
              <input
                type="text"
                className="input"
                value={formatShortDate(formState.date)}
                readOnly
              />
            </div>
            <div className="field">
              <label className="field-label">Digitalizado por</label>
              <div className="row" style={{ padding: 8, background: 'var(--surface-2)', borderRadius: 6 }}>
                <Avatar user={currentUser} size="sm"/>
                <span>{currentUser.nombre}</span>
              </div>
            </div>
            {formState.error && <div role="alert" style={{ color: 'var(--danger)', marginTop: 12 }}>{formState.error}</div>}
          </form>
        )}
      </Modal>
    </div>
  );
};

const DigitalizacionServer = ({ currentUser, onSessionExpired }) => {
  const [state, setState] = useState({
    status: 'loading',
    error: '',
    stats: { queue_count: 0, completed_total: 0, images_today: 0 },
    queue: [],
    queuePagination: { page: 1, per_page: 10, total: 0, total_pages: 0 },
    recent: []
  });
  const [reloadToken, setReloadToken] = useState(0);
  const [queueSearch, setQueueSearch] = useState('');
  const [queueSearchInput, setQueueSearchInput] = useState('');
  const [queuePage, setQueuePage] = useState(1);
  const [queuePerPage, setQueuePerPage] = useState(10);
  const [feedback, setFeedback] = useState(null);
  const [operationById, setOperationById] = useState({});
  const [detailItem, setDetailItem] = useState(null);
  const [successModal, setSuccessModal] = useState({ open: false, title: '', message: '', path: '', count: 0 });
  const [uploadProgress, setUploadProgress] = useState({
    active: false,
    uploaded: 0,
    total: 0,
    batch: 0,
    totalBatches: 0,
    percent: 0,
    message: ''
  });
  const [formState, setFormState] = useState({
    open: false,
    mode: 'complete',
    assignment: null,
    images: '',
    files: [],
    imageOperation: 'append',
    imageStatus: null,
    statusLoading: false,
    date: new Date().toISOString().slice(0, 10),
    error: ''
  });

  useEffect(() => {
    let active = true;
    const controller = new AbortController();

    const loadDashboard = async () => {
      setState(previous => ({ ...previous, status: 'loading', error: '' }));
      try {
        const query = new URLSearchParams({
          page: String(queuePage),
          per_page: String(queuePerPage)
        });
        if (queueSearch) query.set('search', queueSearch);
        const response = await window.apiRequest(`digitalization/dashboard.php?${query.toString()}`, { signal: controller.signal });
        if (!active) return;
        const data = response && response.data ? response.data : {};
        setState({
          status: 'loaded',
          error: '',
          stats: data.stats || { queue_count: 0, completed_total: 0, images_today: 0 },
          queue: Array.isArray(data.queue_items) ? data.queue_items : [],
          queuePagination: data.queue_pagination || { page: queuePage, per_page: queuePerPage, total: 0, total_pages: 0 },
          recent: Array.isArray(data.recent_completed) ? data.recent_completed : []
        });
      } catch (error) {
        if (!active) return;
        if (error && error.status === 401) {
          onSessionExpired();
          return;
        }
        setState(previous => ({
          ...previous,
          status: 'error',
          error: error && error.message ? error.message : 'No fue posible obtener los datos de digitalización.'
        }));
      }
    };

    loadDashboard();
    return () => {
      active = false;
      controller.abort();
    };
  }, [reloadToken, queueSearch, queuePage, queuePerPage, onSessionExpired]);

  const submitQueueSearch = (event) => {
    event.preventDefault();
    setQueuePage(1);
    setQueueSearch(queueSearchInput.trim());
  };

  const clearQueueSearch = () => {
    setQueueSearchInput('');
    setQueueSearch('');
    setQueuePage(1);
  };

  const changeQueuePerPage = (value) => {
    setQueuePerPage(Number(value));
    setQueuePage(1);
  };

  const refreshData = () => setReloadToken(value => value + 1);

  const setOperation = (assignmentId, value) => {
    setOperationById(previous => ({ ...previous, [assignmentId]: value }));
  };

  const clearOperation = (assignmentId) => {
    setOperationById(previous => {
      const next = { ...previous };
      delete next[assignmentId];
      return next;
    });
  };

  const startAssignment = async (assignment) => {
    const assignmentId = assignment.assignment_id;
    if (!assignmentId) return;
    setFeedback(null);
    setOperation(assignmentId, 'starting');
    try {
      await window.assignmentsService.startAssignment(assignmentId);
      setFeedback({ tone: 'success', message: 'La digitalización se inició correctamente.' });
      refreshData();
    } catch (error) {
      if (error && error.status === 401) {
        onSessionExpired();
        return;
      }
      setFeedback({ tone: 'error', message: error && error.message ? error.message : 'No fue posible iniciar la digitalización.' });
    } finally {
      clearOperation(assignmentId);
    }
  };

  const claimAvailableBook = async (item) => {
    const bookId = item?.book?.id;
    if (!bookId) return;
    const operationKey = `book-${bookId}`;
    setFeedback(null);
    setOperation(operationKey, 'claiming');
    try {
      await window.apiRequest('digitalization/claim.php', {
        method: 'POST',
        body: { book_id: bookId }
      });
      setFeedback({ tone: 'success', message: 'El libro se agregó a tu cola y la digitalización quedó iniciada.' });
      refreshData();
    } catch (error) {
      if (error && error.status === 401) {
        onSessionExpired();
        return;
      }
      if (error && error.status === 409) {
        setFeedback({ tone: 'warning', message: error.message || 'Otro usuario ya tomó este libro.' });
        refreshData();
        return;
      }
      setFeedback({ tone: 'error', message: error && error.message ? error.message : 'No fue posible tomar el libro.' });
    } finally {
      clearOperation(operationKey);
    }
  };

  const openCompleteModal = (assignment) => {
    setFeedback(null);
    setUploadProgress({ active: false, uploaded: 0, total: 0, batch: 0, totalBatches: 0, percent: 0, message: '' });
    setFormState({
      open: true,
      mode: 'complete',
      assignment,
      images: '',
      files: [],
      imageOperation: 'append',
      imageStatus: null,
      statusLoading: false,
      date: new Date().toISOString().slice(0, 10),
      error: ''
    });
  };

  const openEditModal = async (assignment) => {
    setFeedback(null);
    setUploadProgress({ active: false, uploaded: 0, total: 0, batch: 0, totalBatches: 0, percent: 0, message: '' });
    setFormState({
      open: true,
      mode: 'edit',
      assignment,
      images: assignment.resulting_images ? String(assignment.resulting_images) : '',
      files: [],
      imageOperation: 'append',
      imageStatus: null,
      statusLoading: true,
      date: toIsoDate(assignment.completed_at) || new Date().toISOString().slice(0, 10),
      error: ''
    });

    try {
      const response = await window.apiRequest(
        `digitalization/edit-status.php?assignment_id=${encodeURIComponent(assignment.assignment_id)}`
      );
      const status = response && response.data ? response.data : null;
      setFormState(previous => {
        if (!previous.open || previous.assignment?.assignment_id !== assignment.assignment_id) return previous;
        return {
          ...previous,
          imageStatus: status,
          statusLoading: false,
          images: String(status?.database_image_count ?? assignment.resulting_images ?? 0),
          date: toIsoDate(status?.effective_at) || previous.date,
          error: ''
        };
      });
    } catch (error) {
      if (error && error.status === 401) {
        onSessionExpired();
        return;
      }
      setFormState(previous => ({
        ...previous,
        statusLoading: false,
        error: error && error.message ? error.message : 'No fue posible consultar las imágenes actuales.'
      }));
    }
  };

  const closeFormModal = () => {
    const assignmentId = formState.assignment?.assignment_id;
    if (assignmentId && operationById[assignmentId]) return;
    setUploadProgress({ active: false, uploaded: 0, total: 0, batch: 0, totalBatches: 0, percent: 0, message: '' });
    setFormState({
      open: false,
      mode: 'complete',
      assignment: null,
      images: '',
      files: [],
      imageOperation: 'append',
      imageStatus: null,
      statusLoading: false,
      date: new Date().toISOString().slice(0, 10),
      error: ''
    });
  };

  const uploadImagesInBatches = async (assignmentId, operation, selectedFiles) => {
    const total = selectedFiles.length;
    const totalBatches = Math.ceil(total / DIGITALIZATION_UPLOAD_BATCH_SIZE);
    const uploadId = `${assignmentId}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
    let finalData = null;

    setUploadProgress({
      active: true,
      uploaded: 0,
      total,
      batch: 0,
      totalBatches,
      percent: 0,
      message: `Preparando ${total} imágenes…`
    });

    for (let index = 0; index < totalBatches; index += 1) {
      const start = index * DIGITALIZATION_UPLOAD_BATCH_SIZE;
      const batchFiles = selectedFiles.slice(start, start + DIGITALIZATION_UPLOAD_BATCH_SIZE);
      const currentBatch = index + 1;

      setUploadProgress(previous => ({
        ...previous,
        active: true,
        batch: currentBatch,
        totalBatches,
        message: `Subiendo lote ${currentBatch} de ${totalBatches}…`
      }));

      const uploadBody = new FormData();
      uploadBody.append('assignment_id', String(assignmentId));
      uploadBody.append('mode', operation);
      uploadBody.append('upload_id', uploadId);
      uploadBody.append('batch_index', String(currentBatch));
      uploadBody.append('total_batches', String(totalBatches));
      uploadBody.append('total_files', String(total));
      batchFiles.forEach(file => uploadBody.append('images[]', file, file.name));

      const uploadResponse = await window.apiRequest('digitalization/upload-images.php', {
        method: 'POST',
        body: uploadBody,
        timeout: 180000
      });
      finalData = uploadResponse && uploadResponse.data ? uploadResponse.data : {};

      const uploaded = Math.min(total, start + batchFiles.length);
      const percent = total > 0 ? Math.round((uploaded / total) * 100) : 100;
      setUploadProgress({
        active: currentBatch < totalBatches,
        uploaded,
        total,
        batch: currentBatch,
        totalBatches,
        percent,
        message: currentBatch < totalBatches
          ? `${uploaded} de ${total} imágenes cargadas`
          : `Carga completa: ${uploaded} de ${total} imágenes`
      });
    }

    return finalData || {};
  };

  const submitServerForm = async (event) => {
    event.preventDefault();
    const assignment = formState.assignment;
    const assignmentId = assignment?.assignment_id;
    let images = parseInt(formState.images, 10);

    if (!assignmentId) {
      setFormState(previous => ({ ...previous, error: 'La asignación ya no está disponible.' }));
      return;
    }
    if (!formState.date) {
      setFormState(previous => ({ ...previous, error: 'La fecha de digitalización es obligatoria.' }));
      return;
    }

    const isComplete = formState.mode === 'complete';
    const operation = isComplete ? 'replace' : formState.imageOperation;
    const requiresFiles = isComplete || ['append', 'replace'].includes(operation);

    if (requiresFiles && formState.files.length === 0) {
      setFormState(previous => ({ ...previous, error: 'Selecciona las imágenes JPG que se van a subir.' }));
      return;
    }

    if (operation === 'database_only') {
      if (isNaN(images) || images < 0) {
        setFormState(previous => ({ ...previous, error: 'Ingresa una cantidad válida para la base de datos.' }));
        return;
      }
    } else if (isComplete || operation === 'replace') {
      images = formState.files.length;
    } else if (operation === 'append') {
      images = Number(formState.imageStatus?.filesystem_image_count || 0) + formState.files.length;
    }

    const action = formState.mode === 'edit' ? 'editing-completion' : 'completing';
    setOperation(assignmentId, action);
    setFeedback(null);
    setFormState(previous => ({ ...previous, error: '' }));

    try {
      let uploadData = null;
      if (operation !== 'database_only') {
        if (operation === 'replace' && formState.mode === 'edit') {
          const confirmed = window.confirm(
            `Se reemplazarán las ${formState.imageStatus?.filesystem_image_count || 0} imágenes actuales. `
            + 'Los archivos existentes se moverán a Backup antes de continuar. ¿Deseas continuar?'
          );
          if (!confirmed) {
            clearOperation(assignmentId);
            return;
          }
        }

        uploadData = await uploadImagesInBatches(assignmentId, operation, formState.files);
        images = Number(uploadData.final_count ?? images);
      }

      if (formState.mode === 'edit') {
        await window.apiRequest('digitalization/update-completion.php', {
          method: 'POST',
          body: {
            assignment_id: assignmentId,
            resulting_images: images,
            effective_at: formState.date
          }
        });
        const successMessage = operation === 'database_only'
          ? 'La cantidad registrada en la base de datos se actualizó sin modificar archivos.'
          : `La digitalización se actualizó correctamente. La carpeta contiene ${images} imágenes.`;
        setFeedback({ tone: 'success', message: successMessage });
        setSuccessModal({
          open: true,
          title: 'Digitalización actualizada',
          message: successMessage,
          path: uploadData?.relative_path || formState.imageStatus?.relative_path || '',
          count: images
        });
      } else {
        await window.assignmentsService.completeAssignment(assignmentId, {
          resulting_images: images,
          effective_at: formState.date,
          upload_relative_path: uploadData?.relative_path || null,
          uploaded_files: Array.isArray(uploadData?.files) ? uploadData.files : []
        });
        const successMessage = `La digitalización se registró correctamente. Se guardaron ${images} imágenes.`;
        setFeedback({ tone: 'success', message: successMessage });
        setSuccessModal({
          open: true,
          title: 'Carga completada',
          message: successMessage,
          path: uploadData?.relative_path || 'INPI 2026/DIGITALIZACION',
          count: images
        });
      }

      setFormState({
        open: false,
        mode: 'complete',
        assignment: null,
        images: '',
        files: [],
        imageOperation: 'append',
        imageStatus: null,
        statusLoading: false,
        date: new Date().toISOString().slice(0, 10),
        error: ''
      });
      setUploadProgress(previous => ({ ...previous, active: false, percent: 100, message: 'Carga completada.' }));
      refreshData();
    } catch (error) {
      setUploadProgress(previous => ({
        ...previous,
        active: false,
        message: previous.total > 0
          ? `La carga se detuvo después de ${previous.uploaded} de ${previous.total} imágenes.`
          : previous.message
      }));
      if (error && error.status === 401) {
        onSessionExpired();
        return;
      }
      setFormState(previous => ({
        ...previous,
        error: error && error.message ? error.message : (formState.mode === 'edit'
          ? 'No fue posible editar la digitalización.'
          : 'No fue posible finalizar la digitalización.')
      }));
    } finally {
      clearOperation(assignmentId);
    }
  };

  const queueItems = state.queue;
  const queuePagination = state.queuePagination || { page: 1, per_page: queuePerPage, total: 0, total_pages: 0 };
  const recentItems = state.recent;
  const isSavingForm = formState.assignment ? Boolean(operationById[formState.assignment.assignment_id]) : false;

  if (state.status === 'error') {
    return (
      <div className="card" style={{ paddingBottom: 24 }}>
        <Empty icon="alert" title="No se pudo cargar digitalización" subtitle={state.error || 'No fue posible obtener los datos del módulo.'}/>
        <div style={{ display: 'flex', justifyContent: 'center' }}>
          <button className="btn btn-primary" onClick={refreshData}>Reintentar</button>
        </div>
      </div>
    );
  }

  return (
    <div className="stack" data-source="server-digitalization">
      {feedback && (
        <div
          role={feedback.tone === 'success' ? 'status' : 'alert'}
          style={{
            padding: '12px 14px',
            borderRadius: 6,
            border: `1px solid ${feedback.tone === 'success' ? '#86efac' : '#fecaca'}`,
            background: feedback.tone === 'success' ? '#f0fdf4' : '#fef2f2',
            color: feedback.tone === 'success' ? '#166534' : '#991b1b'
          }}
        >
          {feedback.message}
        </div>
      )}

      <div className="kpi-grid" style={{ gridTemplateColumns: 'repeat(3, 1fr)' }}>
        <KPI label="En mi cola" value={state.status === 'loading' ? '…' : state.stats.queue_count} sub="Disponibles o tomados por mí" tone="primary"/>
        <KPI label="Digitalizados por mí" value={state.status === 'loading' ? '…' : state.stats.completed_total} sub="Acumulado en el proyecto" tone="success"/>
        <KPI label="Imágenes hoy" value={state.status === 'loading' ? '…' : fmtNum(state.stats.images_today)} sub="Producción del día" tone="amber"/>
      </div>

      <div className="card">
        <div className="card-header">
          <Icon name="camera" size={16} style={{ color: 'var(--text-2)' }}/>
          <div>
            <div className="card-title">Mi cola de digitalización</div>
            <div className="card-subtitle">Obras no digitalizadas disponibles y trabajos tomados por ti · Los libros tomados por otros usuarios no aparecen</div>
          </div>
        </div>
        <form onSubmit={submitQueueSearch} style={{ padding: '14px 16px', borderBottom: '1px solid var(--border)', background: 'var(--surface-2)' }}>
          <div style={{ display: 'grid', gridTemplateColumns: 'minmax(280px, 1fr) auto', gap: 12, alignItems: 'end' }}>
            <div className="field" style={{ marginBottom: 0 }}>
              <label className="field-label" htmlFor="digitization-book-search">Buscar libro físico</label>
              <div className="row" style={{ gap: 8 }}>
                <input
                  id="digitization-book-search"
                  className="input"
                  type="search"
                  placeholder="Título, autor, serie, número, código de barras…"
                  value={queueSearchInput}
                  onChange={(event) => setQueueSearchInput(event.target.value)}
                />
                <button className="btn btn-primary" type="submit">Buscar</button>
                {(queueSearch || queueSearchInput) && (
                  <button className="btn btn-ghost" type="button" onClick={clearQueueSearch}>Limpiar</button>
                )}
              </div>
            </div>
            <div className="field" style={{ marginBottom: 0, minWidth: 150 }}>
              <label className="field-label" htmlFor="digitization-per-page">Mostrar</label>
              <select
                id="digitization-per-page"
                className="input"
                value={queuePerPage}
                onChange={(event) => changeQueuePerPage(event.target.value)}
              >
                {[10, 25, 50, 75, 100, 750].map(value => (
                  <option key={value} value={value}>{value} libros</option>
                ))}
              </select>
            </div>
          </div>
          {queueSearch && (
            <div className="text-sm text-2" style={{ marginTop: 10 }}>
              Resultados para: <strong>{queueSearch}</strong>
            </div>
          )}
        </form>
        <div className="table-wrap" style={{ border: 'none', borderRadius: 0 }}>
          <table className="data">
            <thead>
              <tr>
                <th>Núm.</th>
                <th>Serie / Título</th>
                <th>Autor</th>
                <th style={{ textAlign: 'right' }}>Págs.</th>
                <th>Estatus</th>
                <th style={{ textAlign: 'right' }}>Acción</th>
              </tr>
            </thead>
            <tbody>
              {state.status === 'loading' && (
                <tr><td colSpan="6"><Empty icon="camera" title="Cargando cola" subtitle="Consultando las asignaciones de digitalización…"/></td></tr>
              )}
              {state.status === 'loaded' && queueItems.length === 0 && (
                <tr><td colSpan="6"><Empty icon="camera" title="Sin libros en cola" subtitle="No hay obras disponibles sin digitalizar ni trabajos tomados por ti"/></td></tr>
              )}
              {queueItems.map(item => {
                const assignmentStatus = item.status;
                const isAvailable = item.queue_type === 'available';
                const localStatusKey = DIGITALIZATION_ASSIGNMENT_STATUS[assignmentStatus] || 'nuevo';
                const operationKey = isAvailable ? `book-${item.book?.id}` : item.assignment_id;
                const operation = operationById[operationKey];
                return (
                  <tr key={isAvailable ? `available-${item.book?.id}` : `assignment-${item.assignment_id}`}>
                    <td><span className="cons">#{item.book?.inventory_number || item.book?.id || item.assignment_id}</span></td>
                    <td className="title-cell">
                      <div className="text-xs text-3" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}>
                        {item.book?.series_collection || 'Sin serie'}
                      </div>
                      <div className="title-line">{item.book?.title || 'Libro sin título'}</div>
                    </td>
                    <td className="text-sm text-2">{item.book?.author || 'Sin autor'}</td>
                    <td style={{ textAlign: 'right' }} className="text-mono text-sm">{fmtNum(item.book?.total_pages || 0)}</td>
                    <td>
                      {isAvailable ? (
                        <span className="status-pill" style={{ background: '#eef2ff', color: '#3730a3' }}>
                          <span className="badge-dot" style={{ background: '#4f46e5' }}/>
                          Disponible
                        </span>
                      ) : <StatusPill estatus={localStatusKey}/>} 
                    </td>
                    <td style={{ textAlign: 'right' }}>
                      {isAvailable ? (
                        <button className="btn btn-primary btn-sm" onClick={() => claimAvailableBook(item)} disabled={Boolean(operation)}>
                          {operation === 'claiming' ? 'Tomando…' : 'Tomar e iniciar'}
                        </button>
                      ) : assignmentStatus === 'pending' ? (
                        <button className="btn btn-ghost btn-sm" onClick={() => startAssignment(item)} disabled={Boolean(operation)}>
                          {operation === 'starting' ? 'Iniciando…' : 'Iniciar'}
                        </button>
                      ) : (
                        <button className="btn btn-primary btn-sm" onClick={() => openCompleteModal(item)} disabled={Boolean(operation)}>
                          Finalizar
                        </button>
                      )}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
        <div style={{ padding: '12px 16px', borderTop: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
          <div className="text-sm text-2">
            {queuePagination.total === 0
              ? 'Sin resultados'
              : `Mostrando ${(queuePagination.page - 1) * queuePagination.per_page + 1}–${Math.min(queuePagination.page * queuePagination.per_page, queuePagination.total)} de ${queuePagination.total} obras`}
          </div>
          <div className="row" style={{ gap: 8 }}>
            <button
              className="btn btn-ghost btn-sm"
              type="button"
              disabled={state.status === 'loading' || queuePagination.page <= 1}
              onClick={() => setQueuePage(value => Math.max(1, value - 1))}
            >
              Anterior
            </button>
            <span className="text-sm text-2">
              Página {queuePagination.total_pages ? queuePagination.page : 0} de {queuePagination.total_pages || 0}
            </span>
            <button
              className="btn btn-ghost btn-sm"
              type="button"
              disabled={state.status === 'loading' || queuePagination.page >= queuePagination.total_pages}
              onClick={() => setQueuePage(value => value + 1)}
            >
              Siguiente
            </button>
          </div>
        </div>
      </div>

      <div className="card">
        <div className="card-header">
          <Icon name="check" size={16} style={{ color: 'var(--text-2)' }}/>
          <div>
            <div className="card-title">Mis libros digitalizados</div>
            <div className="card-subtitle">Últimos 6 libros procesados</div>
          </div>
        </div>
        <div className="table-wrap" style={{ border: 'none', borderRadius: 0 }}>
          <table className="data">
            <thead>
              <tr>
                <th>Núm.</th>
                <th>Título</th>
                <th>Fecha</th>
                <th style={{ textAlign: 'right' }}>Imágenes</th>
                <th style={{ textAlign: 'right' }}>Acumulado</th>
                <th>Estatus actual</th>
                <th style={{ textAlign: 'right' }}>Acciones</th>
              </tr>
            </thead>
            <tbody>
              {state.status === 'loading' && (
                <tr><td colSpan="7"><Empty icon="check" title="Cargando historial" subtitle="Consultando tus libros digitalizados…"/></td></tr>
              )}
              {state.status === 'loaded' && recentItems.length === 0 && (
                <tr><td colSpan="7"><Empty icon="check" title="Sin libros digitalizados" subtitle="Aún no tienes libros finalizados en digitalización"/></td></tr>
              )}
              {recentItems.map(item => {
                const currentStatusKey = mapCurrentBookStatus(item.book?.current_stage, item.book?.status);
                const operation = operationById[item.assignment_id];
                return (
                  <tr key={`${item.assignment_id}-${item.book?.id || 'book'}`}>
                    <td><span className="cons">#{item.book?.inventory_number || item.book?.id || item.assignment_id}</span></td>
                    <td className="title-cell"><div className="title-line">{item.book?.title || 'Libro sin título'}</div></td>
                    <td className="text-sm text-2">{formatShortDate(item.completed_at)}</td>
                    <td style={{ textAlign: 'right' }} className="text-mono">{fmtNum(item.resulting_images || 0)}</td>
                    <td style={{ textAlign: 'right' }} className="text-mono text-2">{fmtNum(item.cumulative_images || 0)}</td>
                    <td><StatusPill estatus={currentStatusKey}/></td>
                    <td style={{ textAlign: 'right' }}>
                      <div className="row" style={{ justifyContent: 'flex-end', gap: 8 }}>
                        <button className="btn btn-ghost btn-sm" onClick={() => setDetailItem(item)}>Detalles</button>
                        <button className="btn btn-primary btn-sm" onClick={() => openEditModal(item)} disabled={Boolean(operation)}>
                          {operation === 'editing-completion' ? 'Guardando…' : 'Editar'}
                        </button>
                      </div>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>

      <Modal
        open={formState.open}
        onClose={closeFormModal}
        title={formState.mode === 'edit' ? 'Editar digitalización' : 'Finalizar digitalización'}
        maxWidth={520}
        footer={<>
          <button className="btn btn-ghost" type="button" onClick={closeFormModal} disabled={isSavingForm}>Cancelar</button>
          <button className="btn btn-primary" type="submit" form="server-digitization-form" disabled={isSavingForm}>
            {isSavingForm ? 'Guardando…' : (formState.mode === 'edit' ? 'Guardar cambios' : 'Registrar y enviar a cola')}
          </button>
        </>}
      >
        {formState.assignment && (
          <form id="server-digitization-form" onSubmit={submitServerForm}>
            <div style={{ padding: 12, background: 'var(--surface-2)', borderRadius: 6, marginBottom: 16 }}>
              <div className="text-xs text-3">#{formState.assignment.book?.inventory_number || formState.assignment.book?.id}</div>
              <div style={{ fontWeight: 600, fontSize: 18, marginTop: 2 }}>{formState.assignment.book?.title || 'Libro sin título'}</div>
              <div className="text-sm text-2 mt-4">
                {formState.assignment.book?.author || 'Sin autor'} · {fmtNum(formState.assignment.book?.total_pages || 0)} páginas
              </div>
            </div>

            <div className="field">
              <label className="field-label">Cantidad de imágenes resultantes</label>
              <input
                type="number"
                className="input"
                placeholder="Ej. 245"
                min="1"
                value={formState.images}
                readOnly={formState.mode === 'edit' && formState.imageOperation !== 'database_only'}
                onChange={(event) => setFormState(previous => ({ ...previous, images: event.target.value, error: '' }))}
                autoFocus
              />
              <div className="field-help">Debe coincidir con las páginas escaneadas más portadas y guardas</div>
            </div>

            {formState.mode === 'edit' && (
              <div style={{ marginBottom: 16 }}>
                <div className="field-label" style={{ marginBottom: 8 }}>Estado actual de las imágenes</div>
                {formState.statusLoading ? (
                  <div style={{ padding: 12, background: 'var(--surface-2)', borderRadius: 6 }}>
                    Consultando la base de datos y la carpeta del libro…
                  </div>
                ) : formState.imageStatus ? (
                  <div style={{ padding: 12, background: 'var(--surface-2)', borderRadius: 6 }}>
                    <div className="row" style={{ justifyContent: 'space-between', gap: 12 }}>
                      <span className="text-sm text-2">Cantidad registrada en la base de datos</span>
                      <strong className="text-mono">{fmtNum(formState.imageStatus.database_image_count)}</strong>
                    </div>
                    <div className="row" style={{ justifyContent: 'space-between', gap: 12, marginTop: 8 }}>
                      <span className="text-sm text-2">Imágenes encontradas en la carpeta</span>
                      <strong className="text-mono">{fmtNum(formState.imageStatus.filesystem_image_count)}</strong>
                    </div>
                    <div className="text-xs text-3" style={{ marginTop: 10, wordBreak: 'break-word' }}>
                      Carpeta: {formState.imageStatus.relative_path}
                    </div>
                    <div className="text-xs text-3" style={{ marginTop: 4, wordBreak: 'break-word' }}>
                      Primer archivo: {formState.imageStatus.relative_path}\1.jpg
                    </div>
                    <div style={{
                      marginTop: 10,
                      padding: '8px 10px',
                      borderRadius: 5,
                      background: formState.imageStatus.images_match ? '#dcfce7' : '#fef3c7',
                      color: formState.imageStatus.images_match ? '#166534' : '#92400e'
                    }}>
                      {formState.imageStatus.images_match
                        ? '✓ La cantidad registrada coincide con los archivos almacenados.'
                        : `⚠ La base de datos registra ${formState.imageStatus.database_image_count}, pero se encontraron ${formState.imageStatus.filesystem_image_count} archivos.`}
                    </div>
                  </div>
                ) : null}
              </div>
            )}

            {formState.mode === 'edit' && (
              <div className="field">
                <label className="field-label">Gestión de imágenes</label>
                <label className="row" style={{ alignItems: 'flex-start', gap: 10, padding: '8px 0', cursor: 'pointer' }}>
                  <input
                    type="radio"
                    name="imageOperation"
                    value="append"
                    checked={formState.imageOperation === 'append'}
                    onChange={() => setFormState(previous => ({ ...previous, imageOperation: 'append', files: [], error: '' }))}
                  />
                  <span>
                    <strong>Conservar actuales y agregar imágenes</strong>
                    <div className="text-xs text-3" style={{ marginTop: 3 }}>Las nuevas imágenes conservarán el mismo nombre que tienen en el origen.</div>
                  </span>
                </label>
                <label className="row" style={{ alignItems: 'flex-start', gap: 10, padding: '8px 0', cursor: 'pointer' }}>
                  <input
                    type="radio"
                    name="imageOperation"
                    value="replace"
                    checked={formState.imageOperation === 'replace'}
                    onChange={() => setFormState(previous => ({ ...previous, imageOperation: 'replace', files: [], error: '' }))}
                  />
                  <span>
                    <strong>Reemplazar todas las imágenes</strong>
                    <div className="text-xs text-3" style={{ marginTop: 3 }}>Los archivos actuales se moverán a Backup antes del reemplazo.</div>
                  </span>
                </label>
                <label className="row" style={{ alignItems: 'flex-start', gap: 10, padding: '8px 0', cursor: 'pointer' }}>
                  <input
                    type="radio"
                    name="imageOperation"
                    value="database_only"
                    checked={formState.imageOperation === 'database_only'}
                    onChange={() => setFormState(previous => ({ ...previous, imageOperation: 'database_only', files: [], error: '' }))}
                  />
                  <span>
                    <strong>Corregir solo la cantidad registrada</strong>
                    <div className="text-xs text-3" style={{ marginTop: 3 }}>No agrega, elimina ni reemplaza archivos físicos.</div>
                  </span>
                </label>
              </div>
            )}

            {(formState.mode === 'complete' || formState.imageOperation !== 'database_only') && (
              <div className="field">
                <label className="field-label">
                  {formState.mode === 'edit' && formState.imageOperation === 'append'
                    ? 'Agregar imágenes JPG'
                    : formState.mode === 'edit' && formState.imageOperation === 'replace'
                      ? 'Seleccionar nuevo lote JPG'
                      : 'Subir imágenes JPG'}
                </label>
                <input
                  type="file"
                  className="input"
                  accept=".jpg,.jpeg,image/jpeg"
                  multiple
                  onChange={(event) => {
                    const selectedFiles = Array.from(event.target.files || []);
                    setFormState(previous => {
                      let nextImages = previous.images;
                      if (previous.mode === 'complete' || previous.imageOperation === 'replace') {
                        nextImages = selectedFiles.length ? String(selectedFiles.length) : previous.images;
                      } else if (previous.imageOperation === 'append') {
                        nextImages = String(Number(previous.imageStatus?.filesystem_image_count || 0) + selectedFiles.length);
                      }
                      return { ...previous, files: selectedFiles, images: nextImages, error: '' };
                    });
                  }}
                />
                <div className="field-help">
                  Se conservan correctamente los nombres originales, incluidos acentos y ñ. Si un nombre ya existe, el sistema evitará sobrescribirlo.
                </div>
                {formState.files.length > 0 && (
                  <div style={{ marginTop: 8, padding: 10, background: 'var(--surface-2)', borderRadius: 6 }}>
                    <div className="text-sm fw-600">{formState.files.length} imagen(es) seleccionada(s)</div>
                    <div className="text-xs text-3" style={{ marginTop: 4 }}>
                      Cantidad final estimada: {formState.mode === 'edit' && formState.imageOperation === 'append'
                        ? Number(formState.imageStatus?.filesystem_image_count || 0) + formState.files.length
                        : formState.files.length}
                    </div>
                  </div>
                )}
              </div>
            )}

            <div className="field">
              <label className="field-label">Fecha de digitalización</label>
              <input
                type="text"
                className="input"
                value={formatShortDate(formState.date)}
                readOnly
              />
            </div>

            <div className="field">
              <label className="field-label">Digitalizado por</label>
              <div className="row" style={{ padding: 8, background: 'var(--surface-2)', borderRadius: 6 }}>
                <Avatar user={currentUser} size="sm"/>
                <span>{currentUser.nombre || currentUser.name || 'Usuario'}</span>
              </div>
            </div>

            {(uploadProgress.total > 0 || uploadProgress.active) && (
              <div role="status" style={{
                marginTop: 12,
                padding: '12px 14px',
                borderRadius: 6,
                background: '#eff6ff',
                color: '#1e40af',
                border: '1px solid #bfdbfe'
              }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, marginBottom: 8 }}>
                  <strong>{uploadProgress.message || 'Preparando carga…'}</strong>
                  <span className="text-mono">{uploadProgress.percent}%</span>
                </div>
                <div style={{
                  height: 12,
                  background: '#dbeafe',
                  borderRadius: 999,
                  overflow: 'hidden'
                }}>
                  <div style={{
                    width: `${Math.max(0, Math.min(100, uploadProgress.percent))}%`,
                    height: '100%',
                    background: '#2563eb',
                    transition: 'width 180ms ease'
                  }}/>
                </div>
                <div className="text-xs" style={{ marginTop: 8 }}>
                  Lote {uploadProgress.batch || 0} de {uploadProgress.totalBatches || 0}
                  {' · '}
                  {uploadProgress.uploaded || 0} de {uploadProgress.total || 0} imágenes
                  {' · '}
                  Máximo {DIGITALIZATION_UPLOAD_BATCH_SIZE} archivos por solicitud
                </div>
              </div>
            )}

            {isSavingForm && (
              <div role="status" style={{
                marginTop: 12,
                padding: '12px 14px',
                borderRadius: 6,
                background: '#eff6ff',
                color: '#1e40af',
                border: '1px solid #bfdbfe'
              }}>
                {uploadProgress.active ? uploadProgress.message : (formState.mode === 'edit' ? 'Guardando cambios y verificando las imágenes…' : 'Registrando la digitalización…')}
              </div>
            )}
            {formState.error && (
              <div role="alert" style={{ color: 'var(--danger)', marginTop: 12 }}>
                {formState.error}
              </div>
            )}
          </form>
        )}
      </Modal>

      <ServerDigitalizationDetailModal
        item={detailItem}
        currentUser={currentUser}
        onClose={() => setDetailItem(null)}
        onSessionExpired={onSessionExpired}
      />

      <Modal
        open={successModal.open}
        onClose={() => setSuccessModal({ open: false, title: '', message: '', path: '', count: 0 })}
        title={successModal.title || 'Operación completada'}
        maxWidth={520}
        footer={
          <button className="btn btn-primary" type="button" onClick={() => setSuccessModal({ open: false, title: '', message: '', path: '', count: 0 })}>
            Aceptar
          </button>
        }
      >
        <div style={{ padding: 14, background: '#f0fdf4', color: '#166534', borderRadius: 6, border: '1px solid #86efac' }}>
          <div style={{ fontWeight: 700 }}>{successModal.message}</div>
          {successModal.path && (
            <div className="text-sm" style={{ marginTop: 10, wordBreak: 'break-word' }}>
              Carpeta: {successModal.path}
              <br/>
              Primer archivo: {successModal.path}\1.jpg
            </div>
          )}
        </div>
      </Modal>
    </div>
  );
};

const Digitalizacion = (props) => {
  const serverMode = window.APP_CONFIG.AUTH.MODE === 'server';
  if (serverMode) {
    return <DigitalizacionServer currentUser={props.currentUser} onSessionExpired={props.onSessionExpired}/>;
  }
  return <DigitalizacionLocal {...props}/>;
};

Object.assign(window, { Digitalizacion });
