feat: seguimiento auxiliares UI con tabs Asignadas/Sin asignar

- Componente seguimiento-auxiliares.tsx con tabs Asignadas/Sin asignar
- Tabs internos Obligaciones/Tareas en cada vista
- API client y hooks para asignaciones
- Fix: invalidar query sin-asignar al asignar/desasignar
This commit is contained in:
Horux Dev
2026-05-23 23:40:39 +00:00
parent f43cb165c6
commit e8b0733304
4 changed files with 546 additions and 33 deletions

View File

@@ -5,12 +5,13 @@ import {
Button, Card, CardContent, CardHeader, CardTitle, Input, Label,
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
Tabs, TabsList, TabsTrigger, TabsContent,
cn,
} from '@horux/shared-ui';
import { useQueryClient } from '@tanstack/react-query';
import {
FolderOpen, Plus, Trash2, ChevronDown, ChevronUp, X,
Users, Building2, FolderPlus, UserCog,
Users, Building2, FolderPlus, UserCog, ClipboardList,
} from 'lucide-react';
import {
useCarteras, useCreateCartera, useDeleteCartera,
@@ -25,14 +26,16 @@ import { useUsuarios } from '@/lib/hooks/use-usuarios';
import { useAuthStore } from '@/stores/auth-store';
import { DashboardShell } from '@/components/layouts/dashboard-shell';
import type { Cartera } from '@/lib/api/carteras';
import SeguimientoAuxiliares from './seguimiento-auxiliares';
/* ------------------------------------------------------------------ */
/* SubcarteraCard */
/* ------------------------------------------------------------------ */
function SubcarteraCard({ sub, usuarios, contribuyentes, onDelete }: {
function SubcarteraCard({ sub, usuarios, contribuyentes, parentEntidadIds, onDelete }: {
sub: Cartera;
usuarios: any[];
contribuyentes: any[];
parentEntidadIds: string[];
onDelete: () => void;
}) {
const [expanded, setExpanded] = useState(false);
@@ -47,7 +50,7 @@ function SubcarteraCard({ sub, usuarios, contribuyentes, onDelete }: {
);
const available = (contribuyentes ?? []).filter(
(c: any) => !(entidadIds ?? []).includes(c.id)
(c: any) => (parentEntidadIds ?? []).includes(c.id) && !(entidadIds ?? []).includes(c.id)
);
const auxiliarUser = usuarios?.find((u: any) => u.id === sub.auxiliarUserId);
@@ -319,6 +322,7 @@ function CarteraDetail({ cartera, canEdit = true, canManageSubcarteras = true }:
sub={sub}
usuarios={usuarios ?? []}
contribuyentes={contribuyentes ?? []}
parentEntidadIds={entidadIds ?? []}
onDelete={() => handleDeleteSubcartera(sub.id)}
/>
))}
@@ -396,6 +400,10 @@ export default function CarterasPage() {
const canEditCartera = userRole === 'owner'; // Edit/delete top-level carteras + add/remove RFCs
const canManageSubcarteras = userRole === 'owner' || userRole === 'supervisor'; // Create subcarteras
const isAuxiliar = userRole === 'auxiliar';
const isSupervisor = userRole === 'supervisor';
const isOwner = userRole === 'owner';
const puedeVerSeguimiento = isOwner || isSupervisor;
const [activeTab, setActiveTab] = useState('carteras');
const { data: carteras, isLoading } = useCarteras();
const { data: supervisores } = useSupervisores();
const { data: usuarios } = useUsuarios();
@@ -440,9 +448,43 @@ export default function CarterasPage() {
}
};
const CarterasList = () => (
<>
{isLoading ? (
<p className="text-muted-foreground">Cargando...</p>
) : !carteras || carteras.length === 0 ? (
<Card>
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
<FolderOpen className="h-12 w-12 text-muted-foreground mb-4" />
<h3 className="text-lg font-semibold">Sin carteras</h3>
<p className="text-sm text-muted-foreground mt-1 mb-4">
Crea la primera cartera para organizar tus contribuyentes.
</p>
<Button onClick={() => setShowCreate(true)}>Crear primera cartera</Button>
</CardContent>
</Card>
) : (
<div className="space-y-3">
{carteras.map(cartera => (
<CarteraCard
key={cartera.id}
cartera={cartera}
expanded={expandedId === cartera.id}
onToggle={() => setExpandedId(expandedId === cartera.id ? null : cartera.id)}
onDelete={() => handleDelete(cartera)}
usuarios={usuarios ?? []}
canEdit={canEditCartera}
canManageSubcarteras={canManageSubcarteras}
/>
))}
</div>
)}
</>
);
return (
<DashboardShell title="Carteras">
<div className="max-w-3xl mx-auto space-y-6">
<div className="max-w-4xl mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
@@ -450,42 +492,34 @@ export default function CarterasPage() {
{isAuxiliar ? 'Carteras asignadas a ti' : 'Organiza contribuyentes en carteras y asigna subcarteras a cada auxiliar'}
</p>
</div>
{canCreate && (
{canCreate && activeTab === 'carteras' && (
<Button onClick={() => setShowCreate(true)} className="flex items-center gap-2">
<Plus className="h-4 w-4" /> Nueva cartera
</Button>
)}
</div>
{/* List */}
{isLoading ? (
<p className="text-muted-foreground">Cargando...</p>
) : !carteras || carteras.length === 0 ? (
<Card>
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
<FolderOpen className="h-12 w-12 text-muted-foreground mb-4" />
<h3 className="text-lg font-semibold">Sin carteras</h3>
<p className="text-sm text-muted-foreground mt-1 mb-4">
Crea la primera cartera para organizar tus contribuyentes.
</p>
<Button onClick={() => setShowCreate(true)}>Crear primera cartera</Button>
</CardContent>
</Card>
{puedeVerSeguimiento ? (
<Tabs value={activeTab} onValueChange={setActiveTab} defaultValue="carteras" className="space-y-4">
<TabsList>
<TabsTrigger value="carteras">
<FolderOpen className="h-4 w-4 mr-1.5" /> Carteras
</TabsTrigger>
<TabsTrigger value="seguimiento">
<ClipboardList className="h-4 w-4 mr-1.5" /> Seguimiento de Auxiliares
</TabsTrigger>
</TabsList>
<TabsContent value="carteras">
<CarterasList />
</TabsContent>
<TabsContent value="seguimiento">
<SeguimientoAuxiliares />
</TabsContent>
</Tabs>
) : (
<div className="space-y-3">
{carteras.map(cartera => (
<CarteraCard
key={cartera.id}
cartera={cartera}
expanded={expandedId === cartera.id}
onToggle={() => setExpandedId(expandedId === cartera.id ? null : cartera.id)}
onDelete={() => handleDelete(cartera)}
usuarios={usuarios ?? []}
canEdit={canEditCartera}
canManageSubcarteras={canManageSubcarteras}
/>
))}
</div>
<CarterasList />
)}
{/* Create dialog */}