// ROCA GOLF — Vista de Gestión de Usuarios (solo administrador) const ROLES_RG = { administrador: 'Administrador', contralor: 'Contralor', auxiliarcontable: 'Auxiliar Contable', directorproyectos: 'Director de Proyectos', tesorero: 'Tesorero', supervisor: 'Supervisor', proyectos: 'Proyectos', }; const ROL_COLORS = { administrador: '#b22222', contralor: '#1a4d2e', auxiliarcontable: '#2563eb', directorproyectos: '#7c3aed', tesorero: '#b45309', supervisor: '#64748b', proyectos: '#64748b', }; function UsuariosView({ onNav, user, onLogout }) { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [err, setErr] = useState(''); // Modal: nueva contraseña generada const [pwdModal, setPwdModal] = useState(false); const [pwdNombre, setPwdNombre] = useState(''); const [pwdUsuario, setPwdUsuario] = useState(''); const [pwdPlain, setPwdPlain] = useState(''); const [copied, setCopied] = useState(false); // Modal: crear usuario const [createModal, setCreateModal] = useState(false); const [createLoading, setCreateLoading] = useState(false); const [createForm, setCreateForm] = useState({ nombre: '', usuario: '', rol: 'proyectos' }); // Modal: editar usuario const [editModal, setEditModal] = useState(false); const [editLoading, setEditLoading] = useState(false); const [editForm, setEditForm] = useState({ uid: 0, nombre: '', rol: 'proyectos' }); // ── Helpers ─────────────────────────────────────────────────────────────── const token = () => sessionStorage.getItem('rg_token') || ''; function apiPost(body) { return fetch('api/usuarios.php', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token() }, body: JSON.stringify(body), }).then(r => r.json()); } function loadUsers() { setLoading(true); setErr(''); fetch('api/usuarios.php', { headers: { 'Authorization': 'Bearer ' + token() }, }) .then(r => r.json()) .then(d => { if (d.ok) setUsers(d.data); else setErr(d.error || 'Error cargando usuarios'); }) .catch(() => setErr('Error de conexión')) .finally(() => setLoading(false)); } useEffect(() => { loadUsers(); }, []); // Muestra modal con la contraseña generada function mostrarPassword(nombre, usuario, plain) { setPwdNombre(nombre); setPwdUsuario(usuario); setPwdPlain(plain); setCopied(false); setPwdModal(true); } function copiarPassword() { navigator.clipboard.writeText(pwdPlain).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2500); }); } // ── Acciones ───────────────────────────────────────────────────────────── async function handleGenerar(u) { const ok = window.confirm( `¿${u.tiene_acceso_rg ? 'Resetear' : 'Generar'} contraseña de ROCA GOLF para ${u.nombre}?\n` + (u.tiene_acceso_rg ? 'La contraseña anterior quedará inválida.' : '') ); if (!ok) return; const d = await apiPost({ accion: 'generar', uid: u.id }); if (d.ok) { mostrarPassword(u.nombre, u.usuario, d.data.password); loadUsers(); } else { toast.error(d.error || 'Error generando acceso'); } } async function handleRevocar(u) { if (!window.confirm(`¿Revocar el acceso de ${u.nombre} a ROCA GOLF? No podrá iniciar sesión.`)) return; const d = await apiPost({ accion: 'revocar', uid: u.id }); if (d.ok) { toast('Acceso revocado'); loadUsers(); } else toast.error(d.error || 'Error'); } async function handleToggle(u) { if (!window.confirm(`¿${u.activo ? 'Desactivar' : 'Activar'} a ${u.nombre}?`)) return; const d = await apiPost({ accion: 'toggle', uid: u.id }); if (d.ok) { toast(u.activo ? 'Usuario desactivado' : 'Usuario activado'); loadUsers(); } else toast.error(d.error || 'Error'); } async function handleCreate(e) { e.preventDefault(); setCreateLoading(true); const d = await apiPost({ accion: 'crear', ...createForm }); setCreateLoading(false); if (d.ok) { setCreateModal(false); setCreateForm({ nombre: '', usuario: '', rol: 'proyectos' }); mostrarPassword(createForm.nombre, createForm.usuario, d.data.password); loadUsers(); } else { toast.error(d.error || 'Error creando usuario'); } } async function handleEdit(e) { e.preventDefault(); setEditLoading(true); const d = await apiPost({ accion: 'update', uid: editForm.uid, nombre: editForm.nombre, rol: editForm.rol }); setEditLoading(false); if (d.ok) { toast('Usuario actualizado'); setEditModal(false); loadUsers(); } else toast.error(d.error || 'Error actualizando'); } function abrirEditar(u) { setEditForm({ uid: u.id, nombre: u.nombre, rol: u.rol }); setEditModal(true); } // ── Solo administradores ───────────────────────────────────────────────── if (user?.rol !== 'administrador') { return (
Acceso restringido
Solo los administradores pueden gestionar usuarios.
); } // ── Render ──────────────────────────────────────────────────────────────── const sinAcceso = users.filter(u => !u.tiene_acceso_rg).length; const conAcceso = users.filter(u => u.tiene_acceso_rg).length; return (
setCreateModal(true)}> {Icon.plus(13)} Nuevo Usuario } />
{/* KPIs rápidos */}
{[ { label: 'Total usuarios', val: users.length, color: '#1a4d2e' }, { label: 'Con acceso RG', val: conAcceso, color: '#2563eb' }, { label: 'Sin acceso RG', val: sinAcceso, color: '#b45309', warn: sinAcceso > 0 }, { label: 'Inactivos', val: users.filter(u => !u.activo).length, color: '#64748b' }, ].map(k => (
{k.label}
{k.val}
))}
{/* Error global */} {err && (
{err}
)} {/* Tabla */}
Usuarios del sistema Tabla compartida con Contraloría · Las contraseñas son independientes por sistema
{loading ? (
Cargando usuarios…
) : ( {['ID', 'Nombre', 'Usuario', 'Rol', 'Acceso RG', 'Estado', 'Acciones'].map(h => ( ))} {users.length === 0 ? ( ) : users.map(u => ( e.currentTarget.style.background = '#faf9f6'} onMouseLeave={e => e.currentTarget.style.background = ''}> {/* ID */} {/* Nombre */} {/* Usuario */} {/* Rol */} {/* Acceso RG */} {/* Estado */} {/* Acciones */} ))}
{h}
No hay usuarios registrados.
{u.id}
{u.nombre}
{u.id === user.id && ( )}
{u.usuario} {ROLES_RG[u.rol] || u.rol} {u.tiene_acceso_rg ? ( Habilitado ) : ( Sin acceso )} {u.activo ? 'Activo' : 'Inactivo'}
{/* Editar nombre/rol */} {/* Generar / resetear contraseña */} {/* Revocar acceso */} {u.tiene_acceso_rg && u.id !== user.id && ( )} {/* Toggle activo */} {u.id !== user.id && ( )}
)}
{/* Nota informativa */}
Contraseñas independientes: Cada usuario tiene una contraseña para Contraloría y otra para ROCA GOLF. Al crear o resetear una contraseña de ROCA GOLF, la contraseña de Contraloría no se modifica. Los usuarios creados aquí aparecerán en Contraloría sin contraseña activa hasta que el admin de ese sistema la configure.
{/* ── Modal: Contraseña generada ── */} {pwdModal && (
setPwdModal(false)}>
e.stopPropagation()}> {/* Header */}
Contraseña generada
{pwdNombre} · {pwdUsuario}
{/* Body */}
Contraseña ROCA GOLF
{pwdPlain}
⚠ Esta contraseña se muestra solo una vez.
Cópiala y entrégala al usuario. Si se pierde, usa "Resetear contraseña" para generar una nueva.
)} {/* ── Modal: Crear usuario ── */} {createModal && (
setCreateModal(false)}>
e.stopPropagation()}>
Nuevo Usuario
{/* Nombre */}
setCreateForm(f => ({ ...f, nombre: e.target.value }))} placeholder="Ej. Juan Pérez García" style={{ width: '100%', padding: '10px 13px', border: '1.5px solid #e0dbd2', borderRadius: 8, fontSize: 13.5, fontFamily: 'var(--rg-font-body)', outline: 'none', boxSizing: 'border-box' }} onFocus={e => e.target.style.borderColor = '#1a4d2e'} onBlur={e => e.target.style.borderColor = '#e0dbd2'} />
{/* Usuario */}
setCreateForm(f => ({ ...f, usuario: e.target.value.toLowerCase().replace(/[^a-z0-9_\-]/g, '') }))} placeholder="Ej. jperez" style={{ width: '100%', padding: '10px 13px', border: '1.5px solid #e0dbd2', borderRadius: 8, fontSize: 13.5, fontFamily: 'var(--rg-font-mono)', outline: 'none', boxSizing: 'border-box' }} onFocus={e => e.target.style.borderColor = '#1a4d2e'} onBlur={e => e.target.style.borderColor = '#e0dbd2'} />
Solo letras minúsculas, números y guiones. La contraseña se genera automáticamente.
{/* Rol */}
{/* Aviso */}
La contraseña se generará automáticamente y se mostrará una sola vez al crear el usuario.
{/* Footer */}
)} {/* ── Modal: Editar usuario ── */} {editModal && (
setEditModal(false)}>
e.stopPropagation()}>
Editar Usuario
setEditForm(f => ({ ...f, nombre: e.target.value }))} style={{ width: '100%', padding: '10px 13px', border: '1.5px solid #e0dbd2', borderRadius: 8, fontSize: 13.5, fontFamily: 'var(--rg-font-body)', outline: 'none', boxSizing: 'border-box' }} onFocus={e => e.target.style.borderColor = '#1a4d2e'} onBlur={e => e.target.style.borderColor = '#e0dbd2'} />
)}
); } Object.assign(window, { UsuariosView });