Initial commit: Proyecto portal-requisicion-vacantes

This commit is contained in:
Isaac_Aracena
2026-06-12 16:08:49 -04:00
commit b5a16d8daf
112 changed files with 18334 additions and 0 deletions
+528
View File
@@ -0,0 +1,528 @@
"use client"
import { useState } from "react"
import { mockRequisitions, mockCandidates, mockDepartments } from "@/lib/mock-data"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import {
BarChart3,
TrendingUp,
TrendingDown,
Download,
Calendar,
Users,
Clock,
Target,
Building2,
Briefcase,
CheckCircle2,
XCircle,
PieChart,
Activity,
ArrowUpRight,
ArrowDownRight,
FileText,
Filter
} from "lucide-react"
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
LineChart,
Line,
PieChart as RechartsPieChart,
Pie,
Cell,
AreaChart,
Area
} from "recharts"
// Sample data for charts
const requisitionsByMonth = [
{ month: "Ene", creadas: 12, aprobadas: 10, cubiertas: 8 },
{ month: "Feb", creadas: 15, aprobadas: 13, cubiertas: 11 },
{ month: "Mar", creadas: 18, aprobadas: 16, cubiertas: 14 },
{ month: "Abr", creadas: 14, aprobadas: 12, cubiertas: 10 },
{ month: "May", creadas: 20, aprobadas: 18, cubiertas: 15 },
{ month: "Jun", creadas: 16, aprobadas: 14, cubiertas: 12 }
]
const candidatesBySource = [
{ name: "LinkedIn", value: 45, color: "#0077B5" },
{ name: "Referidos", value: 25, color: "#61ce70" },
{ name: "Portal Empleo", value: 18, color: "#6ec1e4" },
{ name: "Website", value: 8, color: "#4054b2" },
{ name: "Agencias", value: 4, color: "#54595f" }
]
const timeToHire = [
{ month: "Ene", dias: 35 },
{ month: "Feb", dias: 32 },
{ month: "Mar", dias: 28 },
{ month: "Abr", dias: 30 },
{ month: "May", dias: 25 },
{ month: "Jun", dias: 22 }
]
const requisitionsByDepartment = [
{ name: "Tecnologia", abiertas: 8, cubiertas: 15 },
{ name: "Ventas", abiertas: 5, cubiertas: 12 },
{ name: "Marketing", abiertas: 3, cubiertas: 8 },
{ name: "Operaciones", abiertas: 4, cubiertas: 10 },
{ name: "Finanzas", abiertas: 2, cubiertas: 6 },
{ name: "RR.HH.", abiertas: 1, cubiertas: 4 }
]
const pipelineConversion = [
{ stage: "Aplicados", count: 500, percentage: 100 },
{ stage: "Screening", count: 250, percentage: 50 },
{ stage: "Entrevista RH", count: 120, percentage: 24 },
{ stage: "Entrevista Tec", count: 60, percentage: 12 },
{ stage: "Entrevista Ger", count: 30, percentage: 6 },
{ stage: "Oferta", count: 15, percentage: 3 },
{ stage: "Contratados", count: 12, percentage: 2.4 }
]
export function ReportsPage() {
const [dateRange, setDateRange] = useState("6m")
const [selectedDepartment, setSelectedDepartment] = useState("all")
// Calculate stats
const totalRequisitions = mockRequisitions.length
const openRequisitions = mockRequisitions.filter(r =>
["draft", "pending", "approved", "published"].includes(r.status)
).length
const closedRequisitions = mockRequisitions.filter(r =>
["filled", "cancelled"].includes(r.status)
).length
const totalCandidates = mockCandidates.length
const hiredCandidates = mockCandidates.filter(c => c.currentStage === "hired").length
const avgTimeToHire = 27 // days
return (
<div className="space-y-6">
{/* Header */}
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<h1 className="text-3xl font-bold tracking-tight">Reportes y Analitica</h1>
<p className="text-muted-foreground">
Metricas y estadisticas del proceso de reclutamiento
</p>
</div>
<div className="flex items-center gap-3">
<Select value={dateRange} onValueChange={setDateRange}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Periodo" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1m">Ultimo mes</SelectItem>
<SelectItem value="3m">Ultimos 3 meses</SelectItem>
<SelectItem value="6m">Ultimos 6 meses</SelectItem>
<SelectItem value="1y">Ultimo ano</SelectItem>
</SelectContent>
</Select>
<Button variant="outline">
<Download className="mr-2 h-4 w-4" />
Exportar
</Button>
</div>
</div>
{/* KPIs */}
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between mb-2">
<Briefcase className="h-5 w-5 text-primary" />
<Badge variant="secondary" className="text-xs">
<ArrowUpRight className="h-3 w-3 mr-1" />
12%
</Badge>
</div>
<div className="text-2xl font-bold">{totalRequisitions}</div>
<div className="text-xs text-muted-foreground">Total Requisiciones</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between mb-2">
<Target className="h-5 w-5 text-accent" />
<Badge className="bg-accent/10 text-accent text-xs">Activas</Badge>
</div>
<div className="text-2xl font-bold">{openRequisitions}</div>
<div className="text-xs text-muted-foreground">Vacantes Abiertas</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between mb-2">
<Users className="h-5 w-5 text-blue-500" />
<Badge variant="secondary" className="text-xs">
<ArrowUpRight className="h-3 w-3 mr-1" />
8%
</Badge>
</div>
<div className="text-2xl font-bold">{totalCandidates}</div>
<div className="text-xs text-muted-foreground">Total Candidatos</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between mb-2">
<CheckCircle2 className="h-5 w-5 text-accent" />
<Badge className="bg-accent/10 text-accent text-xs">
<ArrowUpRight className="h-3 w-3 mr-1" />
15%
</Badge>
</div>
<div className="text-2xl font-bold">{hiredCandidates}</div>
<div className="text-xs text-muted-foreground">Contratados</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between mb-2">
<Clock className="h-5 w-5 text-secondary" />
<Badge variant="secondary" className="text-xs">
<ArrowDownRight className="h-3 w-3 mr-1 text-accent" />
-5 dias
</Badge>
</div>
<div className="text-2xl font-bold">{avgTimeToHire}d</div>
<div className="text-xs text-muted-foreground">Tiempo Promedio</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between mb-2">
<Activity className="h-5 w-5 text-purple-500" />
</div>
<div className="text-2xl font-bold">2.4%</div>
<div className="text-xs text-muted-foreground">Tasa Conversion</div>
</CardContent>
</Card>
</div>
<Tabs defaultValue="overview" className="space-y-6">
<TabsList>
<TabsTrigger value="overview">Vision General</TabsTrigger>
<TabsTrigger value="requisitions">Requisiciones</TabsTrigger>
<TabsTrigger value="candidates">Candidatos</TabsTrigger>
<TabsTrigger value="pipeline">Pipeline</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Requisitions Trend */}
<Card>
<CardHeader>
<CardTitle className="text-base">Tendencia de Requisiciones</CardTitle>
<CardDescription>Requisiciones creadas, aprobadas y cubiertas</CardDescription>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={requisitionsByMonth}>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis dataKey="month" className="text-xs" />
<YAxis className="text-xs" />
<Tooltip
contentStyle={{
backgroundColor: "hsl(var(--card))",
border: "1px solid hsl(var(--border))",
borderRadius: "8px"
}}
/>
<Legend />
<Bar dataKey="creadas" fill="#6ec1e4" name="Creadas" radius={[4, 4, 0, 0]} />
<Bar dataKey="aprobadas" fill="#4054b2" name="Aprobadas" radius={[4, 4, 0, 0]} />
<Bar dataKey="cubiertas" fill="#61ce70" name="Cubiertas" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
{/* Candidates by Source */}
<Card>
<CardHeader>
<CardTitle className="text-base">Candidatos por Fuente</CardTitle>
<CardDescription>Distribucion de fuentes de reclutamiento</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center">
<ResponsiveContainer width="100%" height={300}>
<RechartsPieChart>
<Pie
data={candidatesBySource}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={100}
paddingAngle={2}
dataKey="value"
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
>
{candidatesBySource.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip />
</RechartsPieChart>
</ResponsiveContainer>
</div>
<div className="flex flex-wrap justify-center gap-4 mt-4">
{candidatesBySource.map((source) => (
<div key={source.name} className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: source.color }} />
<span className="text-sm">{source.name}</span>
</div>
))}
</div>
</CardContent>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Time to Hire */}
<Card>
<CardHeader>
<CardTitle className="text-base">Tiempo Promedio de Contratacion</CardTitle>
<CardDescription>Dias promedio desde aplicacion hasta contratacion</CardDescription>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={250}>
<AreaChart data={timeToHire}>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis dataKey="month" className="text-xs" />
<YAxis className="text-xs" />
<Tooltip
contentStyle={{
backgroundColor: "hsl(var(--card))",
border: "1px solid hsl(var(--border))",
borderRadius: "8px"
}}
/>
<Area
type="monotone"
dataKey="dias"
stroke="#6ec1e4"
fill="#6ec1e4"
fillOpacity={0.3}
name="Dias"
/>
</AreaChart>
</ResponsiveContainer>
</CardContent>
</Card>
{/* Requisitions by Department */}
<Card>
<CardHeader>
<CardTitle className="text-base">Requisiciones por Departamento</CardTitle>
<CardDescription>Vacantes abiertas vs cubiertas por area</CardDescription>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={250}>
<BarChart data={requisitionsByDepartment} layout="vertical">
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis type="number" className="text-xs" />
<YAxis dataKey="name" type="category" className="text-xs" width={80} />
<Tooltip
contentStyle={{
backgroundColor: "hsl(var(--card))",
border: "1px solid hsl(var(--border))",
borderRadius: "8px"
}}
/>
<Legend />
<Bar dataKey="abiertas" fill="#6ec1e4" name="Abiertas" radius={[0, 4, 4, 0]} />
<Bar dataKey="cubiertas" fill="#61ce70" name="Cubiertas" radius={[0, 4, 4, 0]} />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="requisitions" className="space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<Card className="lg:col-span-2">
<CardHeader>
<CardTitle className="text-base">Estado de Requisiciones</CardTitle>
<CardDescription>Distribucion por estado actual</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{[
{ status: "Borrador", count: 3, color: "bg-muted" },
{ status: "Pendiente Aprobacion", count: 5, color: "bg-yellow-500" },
{ status: "Aprobada", count: 8, color: "bg-blue-500" },
{ status: "Publicada", count: 12, color: "bg-primary" },
{ status: "En Proceso", count: 15, color: "bg-purple-500" },
{ status: "Cubierta", count: 25, color: "bg-accent" },
{ status: "Cancelada", count: 2, color: "bg-destructive" }
].map(item => (
<div key={item.status} className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">{item.status}</span>
<span className="text-sm text-muted-foreground">{item.count}</span>
</div>
<Progress
value={(item.count / 70) * 100}
className="h-2"
/>
</div>
))}
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">Prioridad de Vacantes</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between p-3 bg-destructive/10 rounded-lg">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-destructive" />
<span className="text-sm font-medium">Urgente</span>
</div>
<span className="font-bold">5</span>
</div>
<div className="flex items-center justify-between p-3 bg-yellow-500/10 rounded-lg">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-yellow-500" />
<span className="text-sm font-medium">Alta</span>
</div>
<span className="font-bold">12</span>
</div>
<div className="flex items-center justify-between p-3 bg-primary/10 rounded-lg">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-primary" />
<span className="text-sm font-medium">Media</span>
</div>
<span className="font-bold">18</span>
</div>
<div className="flex items-center justify-between p-3 bg-muted rounded-lg">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-muted-foreground" />
<span className="text-sm font-medium">Baja</span>
</div>
<span className="font-bold">8</span>
</div>
</div>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="candidates" className="space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle className="text-base">Candidatos por Etapa</CardTitle>
<CardDescription>Distribucion actual en el pipeline</CardDescription>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={pipelineConversion}>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis dataKey="stage" className="text-xs" angle={-45} textAnchor="end" height={80} />
<YAxis className="text-xs" />
<Tooltip
contentStyle={{
backgroundColor: "hsl(var(--card))",
border: "1px solid hsl(var(--border))",
borderRadius: "8px"
}}
/>
<Bar dataKey="count" fill="#6ec1e4" name="Candidatos" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">Metricas de Candidatos</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm">Tasa de Respuesta</span>
<span className="font-bold">78%</span>
</div>
<Progress value={78} className="h-2" />
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm">Tasa de Entrevista</span>
<span className="font-bold">45%</span>
</div>
<Progress value={45} className="h-2" />
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm">Tasa de Oferta</span>
<span className="font-bold">12%</span>
</div>
<Progress value={12} className="h-2" />
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm">Tasa de Aceptacion</span>
<span className="font-bold">85%</span>
</div>
<Progress value={85} className="h-2" />
</div>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="pipeline" className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="text-base">Funnel de Conversion</CardTitle>
<CardDescription>Conversion de candidatos a traves del pipeline</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{pipelineConversion.map((stage, index) => (
<div key={stage.stage} className="relative">
<div className="flex items-center gap-4">
<div className="w-24 text-sm font-medium">{stage.stage}</div>
<div className="flex-1">
<div
className="h-10 bg-primary/20 rounded-lg flex items-center px-4 transition-all"
style={{ width: `${stage.percentage}%`, minWidth: "80px" }}
>
<span className="font-bold text-primary">{stage.count}</span>
</div>
</div>
<div className="w-16 text-right text-sm text-muted-foreground">
{stage.percentage}%
</div>
</div>
{index < pipelineConversion.length - 1 && (
<div className="ml-12 pl-4 border-l-2 border-dashed border-muted h-4" />
)}
</div>
))}
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
)
}