// ============================================================ // Auditoría de Digitalización // Optimizada: Finalizados paginados + hojas PDF cargadas por libro // ============================================================ const AuditoriaDigitalizacion = ({ onSessionExpired }) => { const [activeTab, setActiveTab] = useState('digitalized'); const [digitalized, setDigitalized] = useState({ items: [], summary: { total_books: 0, mismatch_books: 0, database_images: 0, filesystem_images: 0, difference: 0, }, loading: true, error: '', }); const [finalized, setFinalized] = useState({ items: [], summary: { total_books: 0, database_images: 0, jpg_count: 0, tiff_count: 0, pdf_color_files: 0, pdf_bw_files: 0, pdf_optimized_files: 0, }, pagination: { page: 1, per_page: 20, total: 0, total_pages: 1, }, loading: false, error: '', }); const [searchInput, setSearchInput] = useState(''); const [search, setSearch] = useState(''); const [onlyMismatch, setOnlyMismatch] = useState(false); const formatNumber = (value) => new Intl.NumberFormat('es-MX').format(Number(value || 0)); const formatDate = (value) => { if (!value) return '—'; const date = new Date(String(value).replace(' ', 'T')); if (Number.isNaN(date.getTime())) { return value; } return date.toLocaleString('es-MX', { dateStyle: 'short', timeStyle: 'short', }); }; const loadDigitalized = async () => { setDigitalized((previous) => ({ ...previous, loading: true, error: '', })); try { const params = new URLSearchParams(); params.set('view', 'digitalized'); if (search) params.set('search', search); if (onlyMismatch) params.set('only_mismatch', '1'); const response = await window.apiRequest( `digitalization/image-audit.php?${params.toString()}` ); const data = response?.data || {}; setDigitalized({ items: Array.isArray(data.items) ? data.items : [], summary: data.summary || {}, loading: false, error: '', }); } catch (err) { if (err?.status === 401) { onSessionExpired(); return; } setDigitalized((previous) => ({ ...previous, loading: false, error: err?.message || 'No fue posible cargar la auditoría.', })); } }; const loadPdfPagesProgressively = async (items) => { for (const item of items) { try { const response = await window.apiRequest( `digitalization/image-audit.php?view=finalized_pdf&book_id=${encodeURIComponent(item.book_id)}` ); const detail = response?.data || {}; setFinalized((previous) => ({ ...previous, items: previous.items.map((row) => Number(row.book_id) === Number(item.book_id) ? { ...row, pdf_color_pages: detail.pdf_color_pages, pdf_bw_pages: detail.pdf_bw_pages, pdf_optimized_pages: detail.pdf_optimized_pages, pdf_pages_status: 'loaded', } : row ), })); } catch (err) { if (err?.status === 401) { onSessionExpired(); return; } setFinalized((previous) => ({ ...previous, items: previous.items.map((row) => Number(row.book_id) === Number(item.book_id) ? { ...row, pdf_pages_status: 'error', } : row ), })); } } }; const loadFinalized = async (page = finalized.pagination.page) => { setFinalized((previous) => ({ ...previous, loading: true, error: '', })); try { const params = new URLSearchParams(); params.set('view', 'finalized'); params.set('page', String(page)); params.set('per_page', '20'); if (search) params.set('search', search); const response = await window.apiRequest( `digitalization/image-audit.php?${params.toString()}` ); const data = response?.data || {}; const items = Array.isArray(data.items) ? data.items : []; setFinalized({ items, summary: data.summary || {}, pagination: data.pagination || { page: 1, per_page: 20, total: items.length, total_pages: 1, }, loading: false, error: '', }); window.setTimeout(() => { loadPdfPagesProgressively(items); }, 50); } catch (err) { if (err?.status === 401) { onSessionExpired(); return; } setFinalized((previous) => ({ ...previous, loading: false, error: err?.message || 'No fue posible cargar los libros finalizados.', })); } }; useEffect(() => { if (activeTab === 'finalized') { loadFinalized(1); } else { loadDigitalized(); } }, [activeTab, search, onlyMismatch]); const submitSearch = (event) => { event.preventDefault(); setSearch(searchInput.trim()); }; const clearSearch = () => { setSearchInput(''); setSearch(''); }; const changeTab = (tab) => { setActiveTab(tab); setSearchInput(''); setSearch(''); setOnlyMismatch(false); }; const escapeHtml = (value) => String(value ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); const excelText = (value) => `${escapeHtml(value)}`; const downloadExcelHtml = (filename, headers, rows) => { const html = ` ${headers.map((h) => ``).join('')}${rows}
${escapeHtml(h)}
`; const blob = new Blob( ['\ufeff', html], { type: 'application/vnd.ms-excel;charset=utf-8;' } ); const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = filename; document.body.appendChild(anchor); anchor.click(); anchor.remove(); URL.revokeObjectURL(url); }; const exportCurrent = () => { const source = activeTab === 'finalized' ? finalized.items : digitalized.items; if (!source.length) { alert('No hay registros para exportar.'); return; } if (activeTab === 'finalized') { const rows = source.map((item) => ` ${excelText(item.inventory_number)} ${excelText(item.barcode)} ${escapeHtml(item.series_collection)} ${escapeHtml(item.title)} ${escapeHtml(item.converted_by)} ${escapeHtml(item.completed_at)} ${Number(item.database_image_count || 0)} ${Number(item.jpg_count || 0)} ${Number(item.tiff_count || 0)} ${Number(item.pdf_color_count || 0)} ${item.pdf_color_pages ?? ''} ${Number(item.pdf_bw_count || 0)} ${item.pdf_bw_pages ?? ''} ${Number(item.pdf_optimized_count || 0)} ${item.pdf_optimized_pages ?? ''} ${escapeHtml(item.relative_path)} `).join(''); downloadExcelHtml( `auditoria_finalizados_${new Date().toISOString().slice(0, 10)}.xls`, [ 'Inventario', 'Código de barras', 'Serie / colección', 'Título', 'Convertido por', 'Fecha de finalización', 'Imágenes registradas BD', 'Cantidad JPG', 'Cantidad TIFF', 'Cantidad PDF Color', 'Hojas PDF Color', 'Cantidad PDF B&N', 'Hojas PDF B&N', 'Cantidad PDF Optimizado', 'Hojas PDF Optimizado', 'Ruta FINALIZADO', ], rows ); return; } const rows = source.map((item) => ` ${excelText(item.inventory_number)} ${excelText(item.barcode)} ${escapeHtml(item.series_collection)} ${escapeHtml(item.title)} ${escapeHtml(item.digitalized_by)} ${escapeHtml(item.completed_at)} ${Number(item.database_image_count || 0)} ${Number(item.filesystem_image_count || 0)} ${Number(item.difference || 0)} ${item.images_match ? 'Correcto' : 'Diferencia'} `).join(''); downloadExcelHtml( `auditoria_digitalizacion_${new Date().toISOString().slice(0, 10)}.xls`, [ 'Inventario', 'Código de barras', 'Serie / colección', 'Título', 'Digitalizado por', 'Fecha', 'Imágenes registradas BD', 'Imágenes reales', 'Diferencia', 'Estado', ], rows ); }; const activeState = activeTab === 'finalized' ? finalized : digitalized; const tabStyle = (tab) => ({ border: 'none', borderBottom: activeTab === tab ? '3px solid var(--primary)' : '3px solid transparent', background: 'transparent', color: activeTab === tab ? 'var(--primary)' : 'var(--text-2)', padding: '12px 16px 10px', fontWeight: activeTab === tab ? 800 : 650, cursor: 'pointer', }); return (
Auditoría de Digitalización
Digitalizados y productos finales.
setSearchInput(event.target.value)} placeholder="Buscar por inventario, código, título, autor, serie o usuario…" style={{ flex: '1 1 380px', minWidth: 260, }} /> {(search || searchInput) && ( )} {activeTab === 'digitalized' && ( )}
{activeState.error && (
{activeState.error}
)}
{activeState.loading ? (
Cargando información…
) : activeTab === 'finalized' ? ( ) : ( )}
{activeTab === 'finalized' && !finalized.loading && (
Página {finalized.pagination.page} de{' '} {finalized.pagination.total_pages} ·{' '} {formatNumber(finalized.pagination.total)} libro(s)
)}
); }; const auditCell = { padding: '11px 12px', borderBottom: '1px solid var(--border)', verticalAlign: 'top', fontSize: 13, }; const auditNumberCell = { ...auditCell, textAlign: 'right', fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap', }; const PdfPagesValue = ({ item, field }) => { if (item.pdf_pages_status === 'pending') { return Calculando…; } if (item.pdf_pages_status === 'error') { return No leído; } const value = item[field]; return ( {value === null || value === undefined ? 'No leído' : value} ); }; const FinalizedAuditTable = ({ items, formatNumber, formatDate }) => (
{[ 'Inventario', 'Título', 'Convertido por', 'Finalizado', 'Registradas BD', 'JPG', 'TIFF', 'PDF Color', 'Hojas Color', 'PDF B&N', 'Hojas B&N', 'PDF Optimizado', 'Hojas Optimizado', 'Ruta', ].map((label) => ( ))} {items.map((item) => ( ))}
{label}
{item.inventory_number || item.barcode || '—'}
{item.title || 'Sin título'}
{item.series_collection || 'Sin serie'}
{item.converted_by || '—'} {formatDate(item.completed_at)} {formatNumber(item.database_image_count)} {formatNumber(item.jpg_count)} {formatNumber(item.tiff_count)} {formatNumber(item.pdf_color_count)} {formatNumber(item.pdf_bw_count)} {formatNumber(item.pdf_optimized_count)} {item.folder_exists ? item.relative_path : 'Carpeta no encontrada'}
); const DigitalizedAuditTable = ({ items, formatNumber, formatDate }) => (
{[ 'Inventario', 'Título', 'Digitalizado por', 'Fecha', 'Registradas BD', 'Reales carpeta', 'Diferencia', 'Estado', ].map((label) => ( ))} {items.map((item) => { const mismatch = !item.images_match; return ( ); })}
{label}
{item.inventory_number || item.barcode || '—'} {item.title || 'Sin título'} {item.digitalized_by || '—'} {formatDate(item.completed_at)} {formatNumber(item.database_image_count)} {formatNumber(item.filesystem_image_count)} {item.difference > 0 ? '+' : ''} {formatNumber(item.difference)} {mismatch ? 'Diferencia' : 'Correcto'}
);