const { useState, useMemo } = React; window.AdminAuditoria = function({ logs = [], globalUsers = [], absences = [], actions = [], setMapLogs, setPreviewImage, refreshData }) { const [searchName, setSearchName] = useState(''); const [searchDate, setSearchDate] = useState(window.getNICDate()); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const [selectedActionType, setSelectedActionType] = useState('ALL'); const [editingLog, setEditingLog] = useState(null); const [showNewMarkModal, setShowNewMarkModal] = useState(false); const [formUser, setFormUser] = useState(''); const [formType, setFormType] = useState('entrada'); const [formDate, setFormDate] = useState(window.getNICDate()); const [formTime, setFormTime] = useState('08:00'); const [formJustification, setFormJustification] = useState(''); const applyQuickFilter = (type) => { const today = new Date(); const y = today.getFullYear(); const m = String(today.getMonth() + 1).padStart(2, '0'); const d = String(today.getDate()).padStart(2, '0'); if (type === 'today') { setSearchDate(`${y}-${m}-${d}`); setStartDate(''); setEndDate(''); } else if (type === 'this_week') { const firstDay = new Date(today.setDate(today.getDate() - today.getDay() + 1)); const lastDay = new Date(today.setDate(today.getDate() - today.getDay() + 7)); setSearchDate(''); setStartDate(window.getNICDateFromObj(firstDay)); setEndDate(window.getNICDateFromObj(lastDay)); } else if (type === 'this_month') { const firstDay = new Date(y, today.getMonth(), 1); const lastDay = new Date(y, today.getMonth() + 1, 0); setSearchDate(''); setStartDate(window.getNICDateFromObj(firstDay)); setEndDate(window.getNICDateFromObj(lastDay)); } }; const formatLogType = (typeStr) => { if (!typeStr) return { label: 'MARCA', isStart: false, isEnd: false }; if (typeStr === 'entrada') return { label: 'Entrada Trabajo', isStart: true, isEnd: false }; if (typeStr === 'salida') return { label: 'Cierre de Día (Salida)', isStart: false, isEnd: true }; const isStart = typeStr.endsWith('_in'); const isEnd = typeStr.endsWith('_out') || typeStr.startsWith('vuelta_'); let cleanId = typeStr.replace('_in', '').replace('_out', '').replace('vuelta_', '').toLowerCase(); const matchedAction = (actions || []).find(a => (a.id || '').toLowerCase() === cleanId); let baseName = matchedAction ? matchedAction.label : cleanId.replace(/_\d+/g, '').replace(/_/g, ' ').toUpperCase(); let finalLabel = baseName; if (isStart) finalLabel = `Inicio ${baseName}`; else if (isEnd) finalLabel = `Fin ${baseName}`; return { label: finalLabel, isStart, isEnd }; }; const combinedAuditList = useMemo(() => { const mappedLogs = (logs || []).map(l => ({ ...l, isAbsenceRecord: false })); const mappedAbsences = (absences || []).map(a => ({ id: `abs_${a.id}`, realAbsenceId: a.id, userId: a.userId, userName: a.userName, type: a.type === 'injustificada' ? 'Falta Injustificada' : 'Falta Justificada', date: a.date, timestamp: a.createdAt || Date.now(), justification: a.notes ? `Observación: ${a.notes}` : 'Sin nota', isAbsenceRecord: true, absenceType: a.type })); return [...mappedLogs, ...mappedAbsences]; }, [logs, absences]); const filteredLogs = useMemo(() => { const result = combinedAuditList.filter(l => { const matchName = (l.userName || '').toLowerCase().includes(searchName.toLowerCase()); let matchDate = true; if (searchDate) { matchDate = l.date === searchDate; } else if (startDate && endDate) { matchDate = l.date >= startDate && l.date <= endDate; } let matchType = true; if (selectedActionType === 'ENTRADA_SALIDA') { matchType = l.type === 'entrada' || l.type === 'salida'; } else if (selectedActionType === 'ALMUERZO') { matchType = String(l.type).includes('almuerzo'); } else if (selectedActionType === 'AUSENCIAS') { matchType = l.isAbsenceRecord; } else if (selectedActionType === 'ALERTAS') { matchType = (l.justification && l.justification.startsWith('ALERTA:')) || window.isUserOffDay(globalUsers.find(u=>u.id===l.userId)?.scheduleDays, l.date); } else if (selectedActionType !== 'ALL') { matchType = String(l.type).includes(selectedActionType); } return matchName && matchDate && matchType; }); return result.sort((a,b) => { if (a.date !== b.date) return a.date > b.date ? -1 : 1; if (a.userName !== b.userName) return a.userName.localeCompare(b.userName); return a.timestamp - b.timestamp; }); }, [combinedAuditList, searchName, searchDate, startDate, endDate, selectedActionType, globalUsers]); // MONITOREO GPS EN VIVO ESTRICTO (SOLO POSICIONES REALES DE HOY) const handleOpenLiveTrackingMap = () => { const todayNIC = window.getNICDate(); // Filtrar colaboradores con GPS activo que hayan emitido señal real hoy const activeTrackers = (globalUsers || []).filter(u => { if (!u.gpsTrackingEnabled || !u.lastLat || !u.lastLng || !u.lastGpsTime) return false; // Validar que la última señal emitida pertenezca a la fecha de hoy const signalDate = window.getNICDateFromObj(new Date(u.lastGpsTime)); return signalDate === todayNIC; }); if (activeTrackers.length === 0) { alert("No hay vendedores o colaboradores transmitiendo posición GPS en tiempo real en este momento."); return; } const mapItems = activeTrackers.map(u => ({ id: `live_${u.id}`, userName: `${u.name} (@${u.alias})`, type: 'visita_in', timestamp: u.lastGpsTime, location: { lat: u.lastLat, lng: u.lastLng }, justification: `Última transmisión en vivo: ${window.formatNICTimeFull(u.lastGpsTime)}` })); setMapLogs(mapItems); }; const handleOpenEdit = (log) => { setEditingLog(log); setFormUser(log.userId); setFormType(log.type); setFormDate(log.date); const dateObj = new Date(log.timestamp); const hours = String(dateObj.getHours()).padStart(2, '0'); const mins = String(dateObj.getMinutes()).padStart(2, '0'); setFormTime(`${hours}:${mins}`); setFormJustification(log.justification || ''); }; const handleSaveLogAudit = async (e) => { e.preventDefault(); const [h, m] = formTime.split(':').map(Number); const [y, mon, d] = formDate.split('-').map(Number); const targetTimestamp = new Date(y, mon - 1, d, h, m, 0).getTime(); const userObj = globalUsers.find(u => Number(u.id) === Number(formUser)); if (!userObj) return alert("Selecciona un colaborador."); const payload = { id: editingLog ? editingLog.id : null, userId: userObj.id, userName: userObj.name, type: formType, date: formDate, timestamp: targetTimestamp, justification: `${formJustification ? formJustification + ' | ' : ''}Ajuste Manual por Administrador (${new Date().toLocaleDateString()})` }; const res = await window.apiCall(editingLog ? 'update_log' : 'save_log', payload); if (res && res.success) { setEditingLog(null); setShowNewMarkModal(false); if (refreshData) await refreshData(); } else { alert("Error al procesar marca."); } }; const handleDeleteLog = async (logId) => { if (!confirm("¿Eliminar este registro de marcaje de la auditoría?")) return; const res = await window.apiCall('delete_log', { id: logId }); if (res && res.success && refreshData) await refreshData(); }; const printReport = () => window.print(); return (
{(editingLog || showNewMarkModal) && (
{ setEditingLog(null); setShowNewMarkModal(false); }}>
e.stopPropagation()}>

{editingLog ? 'Corregir Registro de Marcaje' : 'Registrar Marca Manual (Olvido)'}

Quedará asentado en auditoría quién realizó el ajuste

setFormTime(e.target.value)} className="w-full p-3.5 bg-slate-50 rounded-2xl font-bold text-xs outline-none border focus:ring-2 focus:ring-indigo-200" required />
setFormDate(e.target.value)} className="w-full p-3.5 bg-slate-50 rounded-2xl font-bold text-xs outline-none border focus:ring-2 focus:ring-indigo-200" required />
setFormJustification(e.target.value)} placeholder="Ej. Olvidó marcar entrada por corte de luz..." className="w-full p-3.5 bg-slate-50 rounded-2xl font-bold text-xs outline-none border focus:ring-2 focus:ring-indigo-200" required />
)}

Auditoría Operativa

Busca, filtra, exporta y corrige marcajes del personal

setSearchName(e.target.value)} placeholder="Buscar nombre..." className="w-full p-4 bg-slate-50 border rounded-2xl font-bold outline-none focus:border-indigo-500 transition-colors text-xs" />
{setSearchDate(e.target.value); setStartDate(''); setEndDate('');}} className="w-full p-4 bg-slate-50 border rounded-2xl font-bold outline-none focus:border-indigo-500 transition-colors text-xs" />
Rango Opcional
{setStartDate(e.target.value); setSearchDate('');}} className="w-full p-2 bg-white rounded-xl font-bold outline-none border text-xs" /> {setEndDate(e.target.value); setSearchDate('');}} className="w-full p-2 bg-white rounded-xl font-bold outline-none border text-xs" />
{/* TABLA DE AUDITORÍA */}

{window.SHIFT_CONFIG?.companyName || 'Shift'} - Reporte de Asistencia y Auditoría

Impreso el: {new Date().toLocaleDateString()} a las {new Date().toLocaleTimeString()} | Filtro: {selectedActionType} | Mostrando {filteredLogs.length} registros.

{filteredLogs.length === 0 ? ( ) : ( filteredLogs.map((l) => { if (l.isAbsenceRecord) { return ( ); } const { label: displayType, isStart, isEnd } = formatLogType(l.type); const userObj = globalUsers.find(u => u.id === l.userId); const isOffDay = userObj ? window.isUserOffDay(userObj.scheduleDays, l.date) : false; const isAlert = l.justification && l.justification.startsWith('ALERTA:'); const isMissedOut = l.justification === 'SALIDA NO REGISTRADA (OLVIDO)'; return ( ); }) )}
Colaborador / Usuario Actividad Registrada Fecha y Hora Observación de Auditoría Pruebas Físicas
No se encontraron registros en la fecha seleccionada.
{l.userName} {l.type} {window.formatDateToNIC(l.date)} {l.justification} {l.absenceType === 'injustificada' ? '(Deduce Séptimo Día en Nómina)' : ''} Ausencia
{l.userName || 'Usuario'} {l.client && ({l.client})} {displayType} {window.formatNICTimeFull ? window.formatNICTimeFull(l.timestamp) : new Date(l.timestamp).toLocaleString()} {isOffDay && ( ★ Marca en Día Libre )} {isAlert || isMissedOut ? (
{l.justification}
) : l.justification ? ( {l.justification} ) : ( Normal )}
{l.location && l.location.lat ? ( ) : N/A} {l.photo ? ( setPreviewImage(l.photo)} onError={(e) => { e.target.style.display = 'none'; }} className="w-8 h-8 rounded-xl object-cover cursor-pointer hover:scale-110 transition-transform border border-slate-200 shadow-sm" /> ) :
X
}
); };