'use client'; import { useState } from 'react'; import { DashboardShell } from '@/components/layouts/dashboard-shell'; import { Card, CardContent, CardHeader, CardTitle, Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@horux/shared-ui'; import { useUsuarios, useInviteUsuario, useUpdateUsuario, useDeleteUsuario } from '@/lib/hooks/use-usuarios'; import { useContribuyentes } from '@/lib/hooks/use-contribuyentes'; import { addClienteAcceso } from '@/lib/api/contribuyentes'; import { useQuery } from '@tanstack/react-query'; import { apiClient } from '@/lib/api/client'; import { useAuthStore } from '@/stores/auth-store'; import { Users, UserPlus, Trash2, Shield, Eye, Calculator, UserCheck, UserCog, Building2, FolderOpen, KeyRound } from 'lucide-react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@horux/shared-ui'; import Link from 'next/link'; import { cn } from '@horux/shared-ui'; import { isDespachoTenant } from '@horux/shared'; import type { Role, UserInvite } from '@horux/shared'; // ── Horux360 legacy roles ───────────────────────────────────────────────────── const legacyRoleLabels: Record = { owner: { label: 'Dueño', icon: Shield, color: 'text-primary' }, cfo: { label: 'CFO', icon: Shield, color: 'text-primary' }, contador: { label: 'Contador', icon: Calculator, color: 'text-success' }, auxiliar: { label: 'Auxiliar', icon: UserCog, color: 'text-success' }, visor: { label: 'Visor', icon: Eye, color: 'text-muted-foreground' }, }; const legacyInviteRoles: { value: string; label: string }[] = [ { value: 'contador', label: 'Contador' }, { value: 'visor', label: 'Visor' }, { value: 'auxiliar', label: 'Auxiliar' }, ]; // ── Despacho roles ──────────────────────────────────────────────────────────── const despachoRoleLabels: Record = { owner: { label: 'Owner', icon: Shield, color: 'text-primary' }, supervisor: { label: 'Supervisor', icon: UserCheck, color: 'text-success' }, auxiliar: { label: 'Auxiliar', icon: UserCog, color: 'text-success' }, cliente: { label: 'Cliente', icon: Building2, color: 'text-muted-foreground' }, }; const despachoInviteRoles: { value: string; label: string; description: string }[] = [ { value: 'supervisor', label: 'Supervisor', description: 'Titular de RFCs, crea carteras y gestiona auxiliares', }, { value: 'auxiliar', label: 'Auxiliar', description: 'Accede a RFCs asignados vía carteras', }, { value: 'cliente', label: 'Cliente', description: 'Visor externo — acceso read-only a sus RFCs', }, ]; // ── Fallback for unknown roles ───────────────────────────────────────────────── function getRoleInfo( role: string, isDespacho: boolean, ): { label: string; icon: React.ElementType; color: string } { if (isDespacho) { return despachoRoleLabels[role] ?? { label: role, icon: Eye, color: 'text-muted-foreground' }; } return legacyRoleLabels[role] ?? { label: role, icon: Eye, color: 'text-muted-foreground' }; } export default function UsuariosPage() { const { user: currentUser } = useAuthStore(); const { data: usuarios, isLoading } = useUsuarios(); const { data: contribuyentes } = useContribuyentes(); const inviteUsuario = useInviteUsuario(); const updateUsuario = useUpdateUsuario(); const deleteUsuario = useDeleteUsuario(); const isDespacho = isDespachoTenant(currentUser?.tenantRfc); const inviteRoles = isDespacho ? despachoInviteRoles : legacyInviteRoles; const defaultInviteRole = isDespacho ? 'auxiliar' : 'visor'; const isAdmin = currentUser?.role === 'owner' || currentUser?.role === 'cfo'; const [showInvite, setShowInvite] = useState(false); const [inviteForm, setInviteForm] = useState<{ email: string; nombre: string; role: UserInvite['role']; supervisorUserId?: string }>({ email: '', nombre: '', role: defaultInviteRole as UserInvite['role'], }); const [selectedRfcIds, setSelectedRfcIds] = useState([]); // Edit accesos modal const [editingAccesosUser, setEditingAccesosUser] = useState<{ id: string; nombre: string } | null>(null); const [accesosRfcIds, setAccesosRfcIds] = useState([]); const [savingAccesos, setSavingAccesos] = useState(false); // Edit supervisor modal (para auxiliares) const [editingSupervisorUser, setEditingSupervisorUser] = useState<{ id: string; nombre: string } | null>(null); const [selectedSupervisorId, setSelectedSupervisorId] = useState(''); const [savingSupervisor, setSavingSupervisor] = useState(false); const openEditSupervisor = async (userId: string, nombre: string) => { try { const res = await apiClient.get<{ supervisorUserId: string | null }>(`/usuarios/${userId}/supervisor`); setSelectedSupervisorId(res.data.supervisorUserId ?? ''); setEditingSupervisorUser({ id: userId, nombre }); } catch { alert('Error al cargar supervisor'); } }; const handleSaveSupervisor = async () => { if (!editingSupervisorUser) return; setSavingSupervisor(true); try { await apiClient.put(`/usuarios/${editingSupervisorUser.id}/supervisor`, { supervisorUserId: selectedSupervisorId || null, }); setEditingSupervisorUser(null); } catch { alert('Error al guardar supervisor'); } finally { setSavingSupervisor(false); } }; const openEditAccesos = async (userId: string, nombre: string) => { try { const res = await apiClient.get<{ data: string[] }>(`/usuarios/${userId}/accesos`); setAccesosRfcIds(res.data.data); setEditingAccesosUser({ id: userId, nombre }); } catch { alert('Error al cargar accesos'); } }; const handleSaveAccesos = async () => { if (!editingAccesosUser) return; setSavingAccesos(true); try { await apiClient.post(`/usuarios/${editingAccesosUser.id}/accesos`, { entidadIds: accesosRfcIds }); setEditingAccesosUser(null); } catch { alert('Error al guardar accesos'); } finally { setSavingAccesos(false); } }; const toggleAccesoRfc = (id: string) => { setAccesosRfcIds(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]); }; // Fetch supervisors for auxiliar invite dropdown const { data: supervisores } = useQuery({ queryKey: ['cartera-supervisores'], queryFn: async () => { const res = await apiClient.get<{ data: Array<{ userId: string; nombre: string; email: string }> }>('/carteras/supervisores'); return res.data.data; }, enabled: isDespacho && isAdmin, }); const toggleRfc = (id: string) => { setSelectedRfcIds((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id], ); }; const handleInvite = async (e: React.FormEvent) => { e.preventDefault(); if (inviteForm.role === 'auxiliar' && !inviteForm.supervisorUserId) { alert('Debes asignar un supervisor al auxiliar'); return; } try { const newUser = await inviteUsuario.mutateAsync(inviteForm); // If role is 'cliente' and RFCs were selected, grant access to each if (inviteForm.role === 'cliente' && selectedRfcIds.length > 0) { await Promise.all( selectedRfcIds.map((rfcId) => addClienteAcceso(rfcId, newUser.id)), ); } setShowInvite(false); setInviteForm({ email: '', nombre: '', role: defaultInviteRole as UserInvite['role'], supervisorUserId: undefined }); setSelectedRfcIds([]); } catch (error: any) { alert(error.response?.data?.message || 'Error al invitar usuario'); } }; const handleToggleActive = (id: string, active: boolean) => { updateUsuario.mutate({ id, data: { active: !active } }); }; const handleDelete = (id: string) => { if (confirm('¿Eliminar este usuario?')) { deleteUsuario.mutate(id); } }; return (
{/* Header */}

Usuarios

Gestiona tu equipo

{isAdmin && isDespacho && ( )} {isAdmin && ( )}
{/* User count */}
{usuarios?.length || 0} usuarios
{/* Invite Form */} {showInvite && ( Invitar Nuevo Usuario
setInviteForm({ ...inviteForm, email: e.target.value })} required />
setInviteForm({ ...inviteForm, nombre: e.target.value })} required />
{/* Supervisor selector for auxiliar role */} {isDespacho && inviteForm.role === 'auxiliar' && (
{supervisores && supervisores.length > 0 ? ( ) : (

No hay supervisores registrados. Crea uno primero para poder asignar auxiliares.

)}
)} {/* RFC access for cliente role */} {isDespacho && inviteForm.role === 'cliente' && contribuyentes && contribuyentes.length > 0 && (
{contribuyentes.map((c) => (
toggleRfc(c.id)} className="h-4 w-4" />
))}
)}
)} {/* Users List */} {isLoading ? (
Cargando...
) : (
{usuarios?.map(usuario => { const roleInfo = getRoleInfo(usuario.role, isDespacho); const RoleIcon = roleInfo.icon; const isCurrentUser = usuario.id === currentUser?.id; return (
{usuario.nombre.charAt(0).toUpperCase()}
{usuario.nombre} {isCurrentUser && ( )} {!usuario.active && ( Inactivo )}
{usuario.email}
{roleInfo.label}
{isAdmin && !isCurrentUser && (
{isDespacho && usuario.role === 'cliente' && ( )} {isDespacho && usuario.role === 'auxiliar' && ( )}
)}
); })}
)}
{/* Edit Accesos Modal */} {editingAccesosUser && ( { if (!open) setEditingAccesosUser(null); }}> Editar accesos — {editingAccesosUser.nombre}
{contribuyentes && contribuyentes.length > 0 ? ( contribuyentes.map((c) => (
toggleAccesoRfc(c.id)} className="h-4 w-4" />
)) ) : (

No hay contribuyentes registrados.

)}
)} {/* Edit Supervisor Modal (auxiliares) */} {editingSupervisorUser && ( { if (!open) setEditingSupervisorUser(null); }}> Asignar supervisor — {editingSupervisorUser.nombre}
{supervisores && supervisores.length > 0 ? ( ) : (

No hay supervisores registrados todavía.

)}
)}
); }