// ============================================================ // Módulo de Gestión de Usuarios // ============================================================ const AREAS = ['Digitalización', 'Edición', 'Control de Calidad', 'Conversión', 'Supervisión', 'Administración']; const PUESTOS = ['Operador', 'Analista', 'Coordinador', 'Supervisor', 'Jefe de área', 'Director']; const GestionUsuariosLocal = ({ usuarios, onUpdate, onCreate, onDelete }) => { const [editando, setEditando] = useState(null); const [creando, setCreando] = useState(false); const [q, setQ] = useState(''); const filtrados = usuarios.filter(u => !q || u.nombre.toLowerCase().includes(q.toLowerCase()) || u.id.toLowerCase().includes(q.toLowerCase()) ); return (
Gestión de usuarios y perfiles
Administra nombres, áreas, puestos, credenciales y roles del sistema
setQ(e.target.value)} style={{ paddingLeft: 32 }} />
{filtrados.map(u => ( ))}
Usuario Nombre completo Área Puesto Correo Roles asignados Password Acciones
{u.id}
{u.nombre} {u.area || '—'} {u.puesto || '—'} {u.email || '—'}
{u.roles.map(r => ( {APP_DATA.ROLES[r]?.label} ))}
{u.password ? '••••••••' : 'Sin definir'}
{ setEditando(null); setCreando(false); }} onSave={(data) => { if (editando) onUpdate(editando.id, data); else onCreate(data); setEditando(null); setCreando(false); }} onDelete={editando ? () => { onDelete(editando.id); setEditando(null); } : null} />
); }; const UsuarioModal = ({ open, usuario, onClose, onSave, onDelete }) => { const [form, setForm] = useState({ id: '', nombre: '', area: '', puesto: '', email: '', telefono: '', color: '#1e40af', iniciales: '', roles: [], password: '', confirmPassword: '', activo: true, }); const [showPass, setShowPass] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); useEffect(() => { if (usuario) { setForm({ id: usuario.id, nombre: usuario.nombre, area: usuario.area || '', puesto: usuario.puesto || '', email: usuario.email || '', telefono: usuario.telefono || '', color: usuario.color, iniciales: usuario.iniciales, roles: [...usuario.roles], password: usuario.password || '', confirmPassword: usuario.password || '', activo: usuario.activo !== false, }); } else { setForm({ id: '', nombre: '', area: '', puesto: '', email: '', telefono: '', color: '#1e40af', iniciales: '', roles: [], password: '', confirmPassword: '', activo: true, }); } setConfirmDelete(false); setShowPass(false); }, [usuario, open]); const set = (k, v) => setForm(f => ({ ...f, [k]: v })); const toggleRol = (r) => { setForm(f => ({ ...f, roles: f.roles.includes(r) ? f.roles.filter(x => x !== r) : [...f.roles, r], })); }; const generarIniciales = (nombre) => { const parts = nombre.trim().split(/\s+/); return ((parts[0]?.[0] || '') + (parts[1]?.[0] || '')).toUpperCase(); }; const generarUsuario = (nombre) => { return nombre.toLowerCase().split(/\s+/)[0].normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^a-z]/g, ''); }; const handleNombreChange = (v) => { setForm(f => ({ ...f, nombre: v, iniciales: usuario ? f.iniciales : generarIniciales(v), id: usuario ? f.id : generarUsuario(v), })); }; const puedeGuardar = form.nombre && form.id && form.roles.length > 0 && form.password && form.password === form.confirmPassword; const guardar = () => { if (!puedeGuardar) return; const { confirmPassword, ...data } = form; onSave(data); }; const COLORES = ['#1e40af','#0f766e','#b45309','#4338ca','#be185d','#7e22ce','#c2410c','#15803d','#7c2d12','#0891b2']; return ( {usuario && onDelete && ( confirmDelete ? ( <> ¿Confirmar eliminación? ) : ( <> ) )} {!usuario && ( <> )} } > {/* Avatar preview */}
{form.iniciales || '??'}
{form.nombre || 'Nombre completo'}
{form.puesto || 'Puesto'} · {form.area || 'Área'}
@{form.id || 'usuario'}
Color
{COLORES.map(c => (
set('color', c)} style={{ width: 20, height: 20, borderRadius: '50%', background: c, cursor: 'pointer', border: form.color === c ? '2px solid var(--text)' : '2px solid transparent', boxShadow: form.color === c ? '0 0 0 1px white inset' : 'none', }} /> ))}
Datos personales
handleNombreChange(e.target.value)} placeholder="Ej. Juan Pérez López"/>
set('iniciales', e.target.value.toUpperCase().slice(0,3))} maxLength="3"/>
set('email', e.target.value)} placeholder="usuario@dominio.com"/>
set('telefono', e.target.value)} placeholder="55 1234 5678"/>
Credenciales de acceso
set('id', e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, ''))} disabled={!!usuario}/> {usuario &&
El usuario no se puede modificar
}
set('password', e.target.value)} placeholder="Mín. 6 caracteres" style={{ paddingRight: 60 }} />
set('confirmPassword', e.target.value)} /> {form.password && form.confirmPassword && form.password !== form.confirmPassword && (
Las contraseñas no coinciden
)}
Roles y permisos *
Selecciona los procesos a los que este usuario podrá acceder
{Object.entries(APP_DATA.ROLES).map(([key, r]) => (
toggleRol(key)} style={{ justifyContent: 'flex-start' }} >
{form.roles.includes(key) && }
{r.label}
))}
💡 Al iniciar sesión, este usuario verá únicamente los módulos correspondientes a los roles marcados arriba.
); }; const UsuariosServer = ({ onSessionExpired }) => { const [state,setState]=useState({status:'loading',users:[],roles:[],error:''}); const [editing,setEditing]=useState(null); const [creating,setCreating]=useState(false); const [query,setQuery]=useState(''); const [message,setMessage]=useState(null); const [saving,setSaving]=useState(false); const load=async()=>{ setState(previous=>({...previous,status:'loading',error:''})); try{ const response=await window.apiRequest('users/index.php'); setState({status:'loaded',users:response?.data?.users||[],roles:response?.data?.roles||[],error:''}); }catch(error){ if(error?.status===401){onSessionExpired?.();return;} setState(previous=>({...previous,status:'error',error:error?.message||'No fue posible cargar usuarios.'})); } }; useEffect(()=>{load();},[]); const filtered=state.users.filter(user=>{ const value=query.trim().toLowerCase(); return !value || [user.name,user.username,user.email,(user.roles||[]).join(' ')].some(item=>String(item||'').toLowerCase().includes(value)); }); const save=async(form)=>{ setSaving(true);setMessage(null); try{ await window.apiRequest('users/save.php',{method:'POST',body:form}); setEditing(null);setCreating(false); setMessage({tone:'success',text:form.id?'Usuario actualizado correctamente.':'Usuario creado correctamente.'}); await load(); }catch(error){ if(error?.status===401){onSessionExpired?.();return;} setMessage({tone:'error',text:error?.message||'No fue posible guardar el usuario.'}); }finally{setSaving(false);} }; const deactivate=async(user)=>{ if(!window.confirm(`¿Desactivar a ${user.name}? Ya no podrá iniciar sesión.`))return; setSaving(true);setMessage(null); try{ await window.apiRequest('users/deactivate.php',{method:'POST',body:{id:Number(user.id)}}); setEditing(null); setMessage({tone:'success',text:'Usuario desactivado correctamente.'}); await load(); }catch(error){ if(error?.status===401){onSessionExpired?.();return;} setMessage({tone:'error',text:error?.message||'No fue posible desactivar el usuario.'}); }finally{setSaving(false);} }; if(state.status==='error')return
; return
{message&&
{message.text}
}
Gestión de usuarios y perfiles
Datos reales de MySQL · usuarios, estado, contraseña y perfiles
setQuery(event.target.value)} placeholder="Buscar por nombre, usuario, correo o perfil..."/>
{filtered.map(user=>)} {!filtered.length&&}
UsuarioNombreCorreoPerfil principalPerfiles asignadosEstadoActualizadoAcción
@{user.username}{user.name}{user.email||'—'}{user.role_name||'—'}
{(user.roles||[]).map(code=>{state.roles.find(role=>role.code===code)?.name||code})}
{user.status==='active'?'Activo':'Inactivo'}{user.updated_at||'—'}
{setCreating(false);setEditing(null);}} onSave={save} onDeactivate={editing?()=>deactivate(editing):null}/>
; }; const UsuarioServerModal=({open,user,roles,saving,onClose,onSave,onDeactivate})=>{ const empty={id:0,name:'',username:'',email:'',status:'active',password:'',confirmPassword:'',role_ids:[],primary_role_id:0}; const [form,setForm]=useState(empty); const [showPassword,setShowPassword]=useState(false); const [error,setError]=useState(''); useEffect(()=>{ if(user){ const ids=(user.roles||[]).map(code=>Number(roles.find(role=>role.code===code)?.id||0)).filter(Boolean); const primary=Number(roles.find(role=>role.code===user.role)?.id||ids[0]||0); setForm({id:Number(user.id),name:user.name||'',username:user.username||'',email:user.email||'',status:user.status||'active',password:'',confirmPassword:'',role_ids:ids,primary_role_id:primary}); }else setForm(empty); setError('');setShowPassword(false); },[user,open,roles]); const set=(key,value)=>setForm(previous=>({...previous,[key]:value})); const toggleRole=id=>setForm(previous=>{ const exists=previous.role_ids.includes(id); const role_ids=exists?previous.role_ids.filter(value=>value!==id):[...previous.role_ids,id]; const primary_role_id=role_ids.includes(previous.primary_role_id)?previous.primary_role_id:(role_ids[0]||0); return {...previous,role_ids,primary_role_id}; }); const submit=()=>{ if(!form.name.trim()||!form.username.trim()){setError('Nombre y usuario son obligatorios.');return;} if(!form.role_ids.length){setError('Selecciona al menos un perfil.');return;} if(!user&&form.password.length<6){setError('La contraseña debe tener al menos 6 caracteres.');return;} if(form.password&&form.password.length<6){setError('La nueva contraseña debe tener al menos 6 caracteres.');return;} if(form.password!==form.confirmPassword){setError('Las contraseñas no coinciden.');return;} const {confirmPassword,...payload}=form; onSave(payload); }; return {user&&}}>
{error&&
{error}
}
set('name',event.target.value)}/>
set('username',event.target.value.toLowerCase().replace(/[^a-z0-9._-]/g,''))}/>
set('email',event.target.value)}/>
set('password',event.target.value)} placeholder={user?'Dejar vacío para conservarla':'Mínimo 6 caracteres'}/>
set('confirmPassword',event.target.value)}/>
Perfiles asignados *
Marca los perfiles y selecciona cuál será el principal.
{roles.map(role=>
toggleRole(Number(role.id))}>
{form.role_ids.includes(Number(role.id))&&}
{role.name}
{role.description}
{form.role_ids.includes(Number(role.id))&&event.stopPropagation()} onChange={()=>set('primary_role_id',Number(role.id))} title="Perfil principal"/>}
)}
; }; const GestionUsuarios=(props)=>window.APP_CONFIG?.AUTH?.MODE==='server' ? : ; Object.assign(window,{GestionUsuarios});