const { useState, useEffect, useMemo, useRef } = React; const ALL_DAYS = [ { code: 'lun', label: 'Lunes' }, { code: 'mar', label: 'Martes' }, { code: 'mie', label: 'Miércoles' }, { code: 'jue', label: 'Jueves' }, { code: 'vie', label: 'Viernes' }, { code: 'sab', label: 'Sábado' }, { code: 'dom', label: 'Domingo' } ]; // Iconos vectoriales minimalistas const IconPersonal = ({ name, className = "w-4 h-4" }) => { const icons = { 'users': , 'calendar': , 'clock': , 'search': , 'plus': , 'edit': , 'trash': , 'lock': , 'unlock': , 'camera': , 'shield': , 'x': }; return icons[name] || null; }; window.AdminPersonal = function({ globalUsers = [], roles = [], logs = [], handleUserSubmit, editingUser, setEditingUser, deleteUser, toggleLock, confirmAction, isReadOnly = false, showToast, refreshData }) { const isFacialModuleActive = window.SHIFT_CONFIG?.modules?.facial; // Sub-pestañas: Directorio vs Matriz Semanal de Horarios vs Organigrama const [activeSubTab, setActiveSubTab] = useState(() => { const saved = localStorage.getItem('shift_personal_subtab'); return ['directory', 'schedule_matrix', 'organigram'].includes(saved) ? saved : 'directory'; }); useEffect(() => { try { localStorage.setItem('shift_personal_subtab', activeSubTab); } catch (e) {} }, [activeSubTab]); // Filtros de búsqueda const [searchTerm, setSearchTerm] = useState(''); const [filterRole, setFilterRole] = useState('ALL'); const [filterStatus, setFilterStatus] = useState('ALL'); // 'ALL' | 'activo' | 'baja' const [selectedOrgUser, setSelectedOrgUser] = useState(null); // Info rápida interactiva del organigrama // Modal de edición / alta const [showModal, setShowModal] = useState(false); const [modalTab, setModalTab] = useState('identity'); // 'identity' | 'job_salary' | 'schedules' | 'vault' const [isSubmittingUser, setIsSubmittingUser] = useState(false); // Horarios e Identidad en formulario const [selectedScheduleDays, setSelectedScheduleDays] = useState(['lun', 'mar', 'mie', 'jue', 'vie']); const [scheduleCustom, setScheduleCustom] = useState({}); const [baseStart, setBaseStart] = useState('08:00'); const [baseEnd, setBaseEnd] = useState('17:00'); // INSS, Retenciones y GPS const [hasInss, setHasInss] = useState(false); const [inssNumber, setInssNumber] = useState(''); const [appliesInss, setAppliesInss] = useState(true); const [appliesIr, setAppliesIr] = useState(true); const [gpsTracking, setGpsTracking] = useState(false); const [avatarBase64, setAvatarBase64] = useState(''); const [avatarPreview, setAvatarPreview] = useState(''); // Bóveda Digital de Expedientes const [userDocs, setUserDocs] = useState([]); const [uploadModalDoc, setUploadModalDoc] = useState(null); const [isUploadingDoc, setIsUploadingDoc] = useState(false); const [docFileBase64, setDocFileBase64] = useState(''); const [docFileName, setDocFileName] = useState(''); const [docIssueDate, setDocIssueDate] = useState(''); const [docExpiryDate, setDocExpiryDate] = useState(''); const [docUploadFeedback, setDocUploadFeedback] = useState(''); // Escaneo Biométrico Facial const [showFaceModal, setShowFaceModal] = useState(false); const [targetBioUser, setTargetBioUser] = useState(null); const [isCapturingBio, setIsCapturingBio] = useState(false); const [bioStatusMsg, setBioStatusMsg] = useState(''); const bioVideoRef = useRef(null); const bioStreamRef = useRef(null); const REQUIRED_DOC_TYPES = [ { key: 'cedula', label: 'Cédula de Identidad', desc: 'Identificación oficial vigente (anverso y reverso)', maxYears: 10 }, { key: 'record_policia', label: 'Récord de Policía Nacional', desc: 'Certificado de conducta emitido por la Policía Nacional (vigencia 12 meses)', maxYears: 1 }, { key: 'salud', label: 'Certificado de Salud / Carnet MINSA', desc: 'Certificado médico o carnet de manipulación de alimentos', maxYears: 1 }, { key: 'titulo', label: 'Título Académico / Certificación', desc: 'Diploma universitario, bachillerato o certificación técnica oficial', maxYears: 0 }, { key: 'otro', label: 'Otros Documentos Contractuales', desc: 'Contrato físico firmado, constancias salariales o cartas de recomendación', maxYears: 0 } ]; const getDocumentStatusInfo = (doc) => { if (!doc || !doc.expiryDate) { return { status: 'valid', label: 'Vigente', color: 'emerald' }; } const today = new Date(window.getNICDate()); const exp = new Date(doc.expiryDate); const diffTime = exp - today; const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); if (diffDays < 0) { return { status: 'expired', label: 'Vencido', color: 'rose', diffDays }; } if (diffDays <= 30) { return { status: 'expiring', label: `Por vencer (${diffDays}d)`, color: 'amber', diffDays }; } return { status: 'valid', label: `Vigente`, color: 'emerald', diffDays }; }; const getUserDocumentAlert = (user) => { const docs = Array.isArray(user.digitalDocuments) ? user.digitalDocuments : []; let hasExpired = false; let hasExpiring = false; let alertText = ''; docs.forEach(d => { const info = getDocumentStatusInfo(d); if (info.status === 'expired' && !hasExpired) { hasExpired = true; alertText = `${d.name || 'Documento'} vencido`; } else if (info.status === 'expiring' && !hasExpired && !hasExpiring) { hasExpiring = true; alertText = `${d.name || 'Documento'} vence en ${info.diffDays}d`; } }); if (hasExpired) return { label: alertText, text: alertText, type: 'expired' }; if (hasExpiring) return { label: alertText, text: alertText, type: 'expiring' }; return null; }; const handleDocFileSelect = (e) => { const file = e.target.files[0]; if (!file) return; setDocFileName(file.name); const reader = new FileReader(); reader.onload = (ev) => { setDocFileBase64(ev.target.result); }; reader.readAsDataURL(file); }; const handleSaveDocUpload = async () => { if (!editingUser || !editingUser.id) return; if (!docFileBase64) { setDocUploadFeedback('Selecciona un archivo PDF o imagen.'); return; } setIsUploadingDoc(true); setDocUploadFeedback(''); try { const res = await window.apiCall('upload_user_document', { userId: editingUser.id, docType: uploadModalDoc.key, docName: uploadModalDoc.label, issueDate: docIssueDate || null, expiryDate: docExpiryDate || null, fileBase64: docFileBase64, fileName: docFileName }); if (res && res.success) { setUserDocs(res.documents || []); editingUser.digitalDocuments = res.documents || []; if (typeof showToast === 'function') showToast('Documento guardado en el expediente.'); setUploadModalDoc(null); setDocFileBase64(''); setDocFileName(''); setDocIssueDate(''); setDocExpiryDate(''); if (typeof refreshData === 'function') await refreshData(); } else { setDocUploadFeedback('Error: ' + (res?.error || 'No se pudo subir')); } } catch (err) { setDocUploadFeedback('Error al subir el archivo.'); } finally { setIsUploadingDoc(false); } }; const handleDeleteDocument = (docId) => { if (!editingUser || !editingUser.id) return; confirmAction('¿Eliminar este documento del expediente digital del colaborador?', async () => { const res = await window.apiCall('delete_user_document', { userId: editingUser.id, docId }); if (res && res.success) { setUserDocs(res.documents || []); editingUser.digitalDocuments = res.documents || []; if (typeof showToast === 'function') showToast('Documento eliminado.'); if (typeof refreshData === 'function') await refreshData(); } }); }; const isUserLockedToday = (user) => { const todayLogs = (logs || []).filter(l => Number(l.userId) === Number(user.id) && l.date === window.getNICDate()); const hasSalida = todayLogs.some(l => l.type === 'salida'); return Boolean(user.manualLock) || (hasSalida && !user.overrideCheckOut); }; useEffect(() => { if (editingUser) { setSelectedScheduleDays(editingUser.scheduleDays || ['lun', 'mar', 'mie', 'jue', 'vie']); setScheduleCustom(editingUser.scheduleCustom || {}); setBaseStart(editingUser.scheduleStart || '08:00'); setBaseEnd(editingUser.scheduleEnd || '17:00'); setHasInss(Boolean(editingUser.hasInss)); setInssNumber(editingUser.inssNumber || ''); setAppliesInss(editingUser.appliesInss !== undefined ? Boolean(editingUser.appliesInss) : true); setAppliesIr(editingUser.appliesIr !== undefined ? Boolean(editingUser.appliesIr) : true); setGpsTracking(Boolean(editingUser.gpsTrackingEnabled)); setAvatarPreview(editingUser.avatarUrl || ''); setAvatarBase64(''); setUserDocs(Array.isArray(editingUser.digitalDocuments) ? editingUser.digitalDocuments : []); setIsSubmittingUser(false); setModalTab('identity'); setShowModal(true); } else { setSelectedScheduleDays(['lun', 'mar', 'mie', 'jue', 'vie']); setScheduleCustom({}); setBaseStart('08:00'); setBaseEnd('17:00'); setHasInss(false); setInssNumber(''); setAppliesInss(true); setAppliesIr(true); setGpsTracking(false); setAvatarPreview(''); setAvatarBase64(''); setIsSubmittingUser(false); setModalTab('identity'); } }, [editingUser]); const openNewUserModal = () => { if (isReadOnly) return alert("Modo supervisión: no tienes permisos para crear colaboradores."); setEditingUser(null); setSelectedScheduleDays(['lun', 'mar', 'mie', 'jue', 'vie']); setScheduleCustom({}); setBaseStart('08:00'); setBaseEnd('17:00'); setHasInss(false); setInssNumber(''); setAppliesInss(true); setAppliesIr(true); setGpsTracking(false); setAvatarPreview(''); setAvatarBase64(''); setIsSubmittingUser(false); setModalTab('identity'); setShowModal(true); }; const closeModal = () => { if (isSubmittingUser) return; setEditingUser(null); setShowModal(false); }; const handleAvatarFile = (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (ev) => { setAvatarBase64(ev.target.result); setAvatarPreview(ev.target.result); }; reader.readAsDataURL(file); }; const handleToggleDay = (dayCode) => { setSelectedScheduleDays(prev => prev.includes(dayCode) ? prev.filter(d => d !== dayCode) : [...prev, dayCode] ); }; const handleToggleCustomDay = (dayCode) => { setScheduleCustom(prev => { const copy = { ...prev }; if (copy[dayCode]) { delete copy[dayCode]; } else { copy[dayCode] = { start: '08:00', end: '12:00', isHalfDay: true }; } return copy; }); }; const handleCustomDayChange = (dayCode, field, val) => { setScheduleCustom(prev => ({ ...prev, [dayCode]: { ...prev[dayCode], [field]: val } })); }; // Escáner facial biométrico const openFaceScanner = async (user) => { setTargetBioUser(user); setShowFaceModal(true); setBioStatusMsg(''); setIsCapturingBio(false); try { const stream = await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 640 }, height: { ideal: 480 }, facingMode: "user" } }); bioStreamRef.current = stream; if (bioVideoRef.current) bioVideoRef.current.srcObject = stream; } catch (e) { setBioStatusMsg("No se pudo iniciar la cámara."); } }; const closeFaceScanner = () => { if (bioStreamRef.current) { bioStreamRef.current.getTracks().forEach(t => t.stop()); bioStreamRef.current = null; } setShowFaceModal(false); }; const handleSaveFaceDescriptor = async () => { if (!targetBioUser || !bioVideoRef.current) return; setIsCapturingBio(true); setBioStatusMsg("Cargando modelo neuronal..."); try { const MODEL_URL = '/models'; if (window.faceapi && !window.faceapi.nets.ssdMobilenetv1.params) { await Promise.all([ window.faceapi.nets.ssdMobilenetv1.loadFromUri(MODEL_URL), window.faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL), window.faceapi.nets.faceRecognitionNet.loadFromUri(MODEL_URL) ]); } if (bioVideoRef.current.readyState < 2) { setBioStatusMsg("Esperando señal de video..."); setIsCapturingBio(false); return; } setBioStatusMsg("Analizando rostro frente a la cámara..."); const detection = await window.faceapi.detectSingleFace(bioVideoRef.current) .withFaceLandmarks() .withFaceDescriptor(); if (!detection) { setBioStatusMsg("No se detectó un rostro claro. Centra tu cara con buena iluminación."); setIsCapturingBio(false); return; } const descriptorArray = Array.from(detection.descriptor); const res = await window.apiCall('save_face_biometrics', { userId: targetBioUser.id, face_descriptor: JSON.stringify(descriptorArray) }); if (res && res.success) { setBioStatusMsg("Rostro biométrico registrado correctamente."); targetBioUser.face_descriptor = JSON.stringify(descriptorArray); targetBioUser.hasFace = true; setTimeout(() => closeFaceScanner(), 1500); } else { setBioStatusMsg("Error al guardar: " + (res?.error || "")); } } catch (err) { setBioStatusMsg("Error: " + err.message); } finally { setIsCapturingBio(false); } }; // Lista de roles limpia const dbRolesList = useMemo(() => { const roleSet = new Map(); roleSet.set('propietario', 'Propietario / Admin'); roleSet.set('empleado', 'Empleado'); (roles || []).forEach(r => { const rawName = (typeof r === 'object' ? r.name : r) || ''; const key = rawName.toLowerCase().trim(); if (key) { if (key.includes('propietario') || key.includes('admin') || key.includes('dueño')) { roleSet.set('propietario', 'Propietario / Admin'); } else if (key === 'empleado') { roleSet.set('empleado', 'Empleado'); } else { roleSet.set(key, rawName); } } }); return Array.from(roleSet.entries()).map(([key, label]) => ({ key, label })); }, [roles]); // Filtrado de usuarios const filteredUsers = useMemo(() => { return (globalUsers || []).filter(u => { const matchText = (u.name || '').toLowerCase().includes(searchTerm.toLowerCase()) || (u.email || '').toLowerCase().includes(searchTerm.toLowerCase()) || (u.alias || '').toLowerCase().includes(searchTerm.toLowerCase()) || (u.idCard || u.id_card || '').toLowerCase().includes(searchTerm.toLowerCase()); const userRoleKey = (u.role || u.role_name || '').toLowerCase().trim(); const matchRole = filterRole === 'ALL' || userRoleKey === filterRole.toLowerCase().trim(); const matchStatus = filterStatus === 'ALL' || (u.status || 'activo') === filterStatus; return matchText && matchRole && matchStatus; }); }, [globalUsers, searchTerm, filterRole, filterStatus]); // Métricas para cabecera const metrics = useMemo(() => { const total = globalUsers.length; const active = globalUsers.filter(u => (u.status || 'activo') === 'activo').length; const insured = globalUsers.filter(u => u.appliesInss !== false).length; const biometric = globalUsers.filter(u => u.face_descriptor || u.hasFace).length; return { total, active, insured, biometric }; }, [globalUsers]); // Árbol Jerárquico de Organigrama (Art. 6 CT) const organigramTree = useMemo(() => { const usersMap = new Map(); (globalUsers || []).forEach(u => { usersMap.set(Number(u.id), { ...u, reports: [] }); }); usersMap.forEach(u => { const supId = Number(u.supervisorId || u.supervisor_id || 0); if (supId > 0 && usersMap.has(supId) && supId !== Number(u.id)) { usersMap.get(supId).reports.push(u); } }); const cards = []; usersMap.forEach(u => { const supId = Number(u.supervisorId || u.supervisor_id || 0); if (u.reports.length > 0 || supId === 0) { cards.push(u); } }); return cards.sort((a, b) => b.reports.length - a.reports.length); }, [globalUsers]); const onFormSubmit = async (e) => { e.preventDefault(); if (isSubmittingUser) return; const f = new FormData(e.target); const nameVal = (f.get('name') || editingUser?.name || '').trim(); const aliasVal = (f.get('alias') || editingUser?.alias || '').trim(); const emailVal = (f.get('email') || editingUser?.email || '').trim(); if (!nameVal) { setModalTab('identity'); if (typeof showToast === 'function') showToast("Por favor ingresa el Nombre Completo."); else alert("Por favor ingresa el Nombre Completo."); return; } if (!aliasVal) { setModalTab('identity'); if (typeof showToast === 'function') showToast("Por favor ingresa el Usuario / Alias de acceso."); else alert("Por favor ingresa el Usuario / Alias de acceso."); return; } if (!emailVal) { setModalTab('identity'); if (typeof showToast === 'function') showToast("Por favor ingresa el Correo Electrónico."); else alert("Por favor ingresa el Correo Electrónico."); return; } if (!editingUser && !f.get('pass')) { setModalTab('identity'); if (typeof showToast === 'function') showToast("Ingresa una contraseña o PIN inicial para el colaborador."); else alert("Ingresa una contraseña o PIN inicial para el colaborador."); return; } setIsSubmittingUser(true); try { await handleUserSubmit(e); closeModal(); } catch (err) { console.error("Error al procesar colaborador:", err); if (typeof showToast === 'function') showToast("Error al guardar: " + (err.message || "Fallo inesperado")); } finally { setIsSubmittingUser(false); } }; return (
{/* KPI METRIC CARDS (ESTILO ENTERPRISE) */}
Total Personal

{metrics.total}

Colaboradores registrados

Personal Activo

{metrics.active}

En plantilla vigente

Cotizantes INSS

{metrics.insured}

Aporte legal activo

Biometría Facial

{metrics.biometric}

Rostros vinculados

{/* BARRA DE HERRAMIENTAS: PESTAÑAS, BÚSQUEDA Y ACCIÓN NUEVO */}
{/* Sub-pestañas de vista */}
{!isReadOnly && ( )}
{/* Filtros */}
setSearchTerm(e.target.value)} placeholder="Buscar por nombre, cédula o correo..." className="w-full pl-10 pr-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-xs font-medium outline-none focus:border-slate-400 transition-colors" />
{/* VISTA 1: DIRECTORIO DE PERSONAL */} {activeSubTab === 'directory' && (
{/* TABLA EN DESKTOP */}
{filteredUsers.length > 0 ? ( filteredUsers.map(u => { const isLocked = isUserLockedToday(u); const activeDaysStr = (u.scheduleDays || []).join(', ').toUpperCase(); const hasCustom = u.scheduleCustom && Object.keys(u.scheduleCustom).length > 0; const hasFace = Boolean(u.face_descriptor || u.hasFace); const isInactive = (u.status || 'activo') !== 'activo'; return ( {/* Columna Colaborador */} {/* Columna Jornada & Horario */} {/* Columna Compensación & Ley */} {/* Columna Seguridad & Biometría */} {/* Acciones */} ); }) ) : ( )}
Colaborador Jornada & Turno Compensación & Ley Seguridad & Biometría Acciones
{u.avatarUrl ? ( {u.name} ) : (
{u.name ? u.name[0].toUpperCase() : 'U'}
)}
{u.name} {u.gpsTrackingEnabled && ( )}
{u.positionTitle || u.role_name || u.role || 'Colaborador'} • {u.email}
{(u.idCard || u.id_card) && Céd: {u.idCard || u.id_card}} {(u.phoneNumber || u.phone_number) && ( +{u.phoneNumber || u.phone_number} )}
{window.formatScheduleRange12h ? window.formatScheduleRange12h(u.scheduleStart, u.scheduleEnd) : `${u.scheduleStart || '08:00'} - ${u.scheduleEnd || '17:00'}`} {activeDaysStr || 'Sin días asignados'} {hasCustom && ( Horario Especial )}
C$ {parseFloat(u.baseSalary || 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
{u.appliesInss !== false ? ( INSS {u.inssNumber ? u.inssNumber : 'Activo'} ) : ( Sin INSS )} {u.appliesIr === false && ( Exento IR )}
{(u.bank_account_number || u.bankAccountNumber) ? (
{u.bank_name || u.bankName || 'Banco'}: {u.bank_account_number || u.bankAccountNumber}
) : ( Sin cuenta bancaria )}
{isFacialModuleActive && ( hasFace ? ( Biometría OK ) : ( Sin Rostro ) )}
{isInactive ? ( Inactivo / Baja ) : isLocked ? ( Acceso Bloqueado ) : ( Acceso Habilitado )}
{(() => { const docAlert = getUserDocumentAlert(u); if (!docAlert) return null; const alertMsg = docAlert.text || docAlert.label; if (!alertMsg) return null; return (
{alertMsg}
); })()}
{!isReadOnly && (
{isFacialModuleActive && ( )} {isLocked ? ( ) : ( )}
)}
No se encontraron colaboradores con los filtros seleccionados.
{/* VISTA MÓVIL: TARJETAS COMPACTAS (TOUCH FRIENDLY) */}
{filteredUsers.length > 0 ? ( filteredUsers.map(u => { const isLocked = isUserLockedToday(u); const activeDaysStr = (u.scheduleDays || []).join(', ').toUpperCase(); const hasFace = Boolean(u.face_descriptor || u.hasFace); const isInactive = (u.status || 'activo') !== 'activo'; return (
{u.avatarUrl ? ( {u.name} ) : (
{u.name ? u.name[0].toUpperCase() : 'U'}
)}

{u.name}

{u.positionTitle || u.role || 'Colaborador'}

{u.email} {(u.phoneNumber || u.phone_number) ? ` • +${u.phoneNumber || u.phone_number}` : ''}

{isInactive ? 'Inactivo' : 'Activo'}
Jornada {u.scheduleStart || '08:00'} - {u.scheduleEnd || '17:00'}
Salario Base C$ {parseFloat(u.baseSalary || 0).toLocaleString('en-US', { minimumFractionDigits: 2 })}
{u.appliesInss !== false ? ( INSS {u.inssNumber ? u.inssNumber : 'Activo'} ) : ( Sin INSS )} {u.appliesIr === false && ( Exento IR )} {isFacialModuleActive && ( hasFace ? ( Biometría OK ) : ( Sin Rostro ) )}
{(u.bank_account_number || u.bankAccountNumber) ? (
{u.bank_name || u.bankName || 'Banco'} {u.bank_account_number || u.bankAccountNumber}
) : null} {(() => { const docAlert = getUserDocumentAlert(u); if (!docAlert) return null; const alertMsg = docAlert.text || docAlert.label; if (!alertMsg) return null; return (
Expediente: {alertMsg}
); })()} {/* Botones de acción móvil (mínimo 44px de alto) */} {!isReadOnly && (
{isFacialModuleActive && ( )} {isLocked ? ( ) : ( )}
)}
); }) ) : (
No se encontraron colaboradores.
)}
)} {/* VISTA 2: MATRIZ SEMANAL DE TURNOS (CUADRÍCULA LUN A DOM) */} {activeSubTab === 'schedule_matrix' && (
{ALL_DAYS.map(d => ( ))} {filteredUsers.map(u => { const activeDays = u.scheduleDays || []; const customs = u.scheduleCustom || {}; const baseStartHour = u.scheduleStart || '08:00'; const baseEndHour = u.scheduleEnd || '17:00'; return ( {ALL_DAYS.map(d => { const isActive = activeDays.includes(d.code); const isCustom = Boolean(customs[d.code]); const dayStart = isCustom ? customs[d.code].start : baseStartHour; const dayEnd = isCustom ? customs[d.code].end : baseEndHour; return ( ); })} ); })}
Colaborador{d.label}
{u.name}
{u.positionTitle || u.role}
{isActive ? (
{dayStart} {dayEnd}
) : ( Descanso )}
)} {/* VISTA 3: ORGANIGRAMA INTERACTIVO & JERARQUÍAS (ART. 6 CT - TENDENCIA 2026) */} {activeSubTab === 'organigram' && (

Estructura Organizacional & Jerarquías de Mando

Árbol Interactivo

Haz clic en cualquier colaborador o líder de equipo para desplegar su ficha rápida interactiva con detalles de puesto, contacto y salario.

{globalUsers.length} Colaboradores en Estructura
{organigramTree.map(rootUser => (
{/* NODO PRINCIPAL / SUPERVISOR / DIRECCIÓN (INTERACTIVO 2026) */}
setSelectedOrgUser(rootUser)} className="flex flex-col sm:flex-row items-start sm:items-center justify-between p-4 sm:p-5 bg-slate-900 hover:bg-black text-white rounded-2xl shadow-md cursor-pointer transition-all hover:scale-[1.005] hover:shadow-xl group border border-slate-800 relative overflow-hidden" title="Haz clic para abrir la ficha rápida de información del supervisor" >
{rootUser.avatarUrl ? ( {rootUser.name} ) : (
{rootUser.name ? rootUser.name[0].toUpperCase() : 'U'}
)}

{rootUser.name}

{rootUser.reports.length > 0 ? 'Líder / Jefe Inmediato' : 'Dirección General'}

{rootUser.positionTitle || rootUser.role || 'Puesto no asignado'} • @{rootUser.alias}

{rootUser.reports.length} Reporte(s) Directo(s) Ver Ficha
{/* LISTA DE REPORTES DIRECTOS DE ESTE SUPERVISOR (RAMA ÁRBOL 2026) */} {rootUser.reports.length > 0 ? (
Colaboradores bajo supervisión directa ({rootUser.reports.length}):
{rootUser.reports.map(rep => (
setSelectedOrgUser(rep)} className="p-4 bg-slate-50 hover:bg-white border border-slate-200 hover:border-indigo-300 rounded-2xl flex items-center justify-between gap-3 shadow-2xs hover:shadow-md transition-all cursor-pointer group active:scale-95" title="Haz clic para desplegar ficha rápida interactiva" >
{rep.avatarUrl ? ( {rep.name} ) : (
{rep.name ? rep.name[0].toUpperCase() : 'U'}
)}
{rep.name}

{rep.positionTitle || rep.role}

{rep.status || 'activo'}
))}
) : (

Sin personal subordinado asignado a esta jefatura.

)}
))}
{/* MODAL / DRAWER FLOTANTE: FICHA RÁPIDA ORGANIGRAMA 2026 */} {selectedOrgUser && (
setSelectedOrgUser(null)}>
e.stopPropagation()}>
{/* Header Ficha */}
{selectedOrgUser.avatarUrl ? ( {selectedOrgUser.name} ) : (
{selectedOrgUser.name ? selectedOrgUser.name[0].toUpperCase() : 'U'}
)}

{selectedOrgUser.name}

{selectedOrgUser.positionTitle || selectedOrgUser.role || 'Puesto no especificado'}

@{selectedOrgUser.alias || 'usuario'}
{/* Información Detallada del Puesto y Contacto */}
Estructura & Jerarquía
Supervisor Directo: {(() => { const supId = Number(selectedOrgUser.supervisorId || selectedOrgUser.supervisor_id || 0); const sup = globalUsers.find(u => Number(u.id) === supId); return sup ? sup.name : 'Dirección General / N/A'; })()}
Personal a Cargo: {(() => { const count = globalUsers.filter(u => Number(u.supervisorId || u.supervisor_id || 0) === Number(selectedOrgUser.id)).length; return `${count} colaborador(es)`; })()}
Correo Electrónico: {selectedOrgUser.email || 'No registrado'}
Teléfono / WhatsApp: {selectedOrgUser.phone || 'No registrado'}
Cédula Identidad: {selectedOrgUser.idCard || selectedOrgUser.id_card || 'N/A'}
Estado Laboral: {selectedOrgUser.status || 'activo'}
{selectedOrgUser.baseSalary > 0 && (
Salario Base Configurado: C$ {parseFloat(selectedOrgUser.baseSalary).toLocaleString('en-US', { minimumFractionDigits: 2 })}
)}
{/* Acciones Rápidas */}
)}
)} {/* MODAL / DRAWER: FICHA DEL COLABORADOR (TABULADO ENTERPRISE) */} {showModal && (
e.stopPropagation()} > {/* Cabecera del modal */}

{editingUser ? "Editar Ficha de Colaborador" : "Alta de Nuevo Colaborador"}

{editingUser ? editingUser.name : "Completa los datos de identidad, puesto y asignación de jornada"}

{/* Barra de pestañas */}
{/* Formulario */}
{/* PESTAÑA 1: IDENTIDAD & ACCESO */}
{avatarPreview ? ( ) : (
{editingUser?.name ? editingUser.name[0] : '+'}
)}
{/* PESTAÑA 2: PUESTO, SALARIO & LEY */}
{/* SECCIÓN SEGURIDAD SOCIAL Y RETENCIONES */}
Retenciones y Seguridad Social (Nicaragua)
{appliesInss && (
{hasInss && ( setInssNumber(e.target.value)} placeholder="Número de asegurado INSS" className="w-full p-2.5 bg-white rounded-xl border border-slate-200 text-xs font-mono font-medium outline-none focus:border-slate-400" /> )}
)}
{/* SECCIÓN DISPERSIÓN BANCARIA / PAGO DE NÓMINA */}
Datos Bancarios para Pago de Nómina (Dispersión) Opcional para transferencias masivas
{/* PESTAÑA 3: JORNADA & HORARIOS */}
Horario Estándar de Entrada y Salida
setBaseStart(e.target.value)} className="w-full p-2.5 bg-white rounded-xl border border-slate-200 text-xs font-bold font-mono outline-none" />
setBaseEnd(e.target.value)} className="w-full p-2.5 bg-white rounded-xl border border-slate-200 text-xs font-bold font-mono outline-none" />
{ALL_DAYS.map(d => { const isActive = selectedScheduleDays.includes(d.code); const custom = scheduleCustom[d.code]; return (
{isActive && ( )}
{isActive && custom && (
Horario de {d.label}: handleCustomDayChange(d.code, 'start', e.target.value)} className="p-1.5 bg-white rounded-lg text-xs font-mono font-bold border border-slate-200" /> a handleCustomDayChange(d.code, 'end', e.target.value)} className="p-1.5 bg-white rounded-lg text-xs font-mono font-bold border border-slate-200" />
)}
); })}
{/* PESTAÑA 4: BÓVEDA DIGITAL DE EXPEDIENTES */}

Bóveda de Documentos Legales & Expediente

Resguardo digital de soportes requeridos por el Código del Trabajo y autoridades laborales de Nicaragua.

{!editingUser ? (

Guarda primero los datos básicos del colaborador para poder gestionar su expediente digital.

) : (
{REQUIRED_DOC_TYPES.map(docType => { const currentDoc = (userDocs || []).find(d => d.type === docType.key); const statusInfo = currentDoc ? getDocumentStatusInfo(currentDoc) : null; return (
{docType.label}
{currentDoc ? ( {statusInfo?.label} ) : ( Pendiente )}

{docType.desc}

{currentDoc && (
{currentDoc.fileName || currentDoc.name} {currentDoc.expiryDate && ( Vence: {currentDoc.expiryDate} )} {currentDoc.uploadedAt && ( Subido: {currentDoc.uploadedAt} )}
)}
{currentDoc ? ( Ver ) : ( )}
); })}
)}
{/* BARRA INFERIOR DE ACCIONES (STICKY EN PANTALLA) */}
)} {/* SUB-MODAL CARGA DE DOCUMENTO EXPEDIENTE */} {uploadModalDoc && (
setUploadModalDoc(null)}>
e.stopPropagation()}>

Adjuntar {uploadModalDoc.label}

{editingUser?.name}

{uploadModalDoc.desc}

{docFileName && (

Seleccionado: {docFileName}

)}
setDocIssueDate(e.target.value)} className="w-full p-2.5 bg-slate-50 rounded-xl border border-slate-200 text-xs font-medium outline-none" />
setDocExpiryDate(e.target.value)} className="w-full p-2.5 bg-slate-50 rounded-xl border border-slate-200 text-xs font-medium outline-none" />
{uploadModalDoc.maxYears > 0 && (
)} {docUploadFeedback && (

{docUploadFeedback}

)}
)} {/* MODAL ESCANEO BIOMÉTRICO FACIAL */} {showFaceModal && targetBioUser && (
e.stopPropagation()}>

Registro Facial Biométrico

{targetBioUser.name}

{bioStatusMsg && (

{bioStatusMsg}

)}
)}
); };