Forráskód Böngészése

splitting the monoliths 🗣

Matthew Trejo 2 hónapja
szülő
commit
927cceca73

+ 261 - 0
src/app/api/dashboard/stats/route.ts

@@ -0,0 +1,261 @@
+import { NextRequest, NextResponse } from "next/server"
+import { getServerSession } from "next-auth"
+import { authOptions } from "@/lib/auth"
+import { prisma } from "@/lib/prisma"
+
+export async function GET(request: NextRequest) {
+  try {
+    const session = await getServerSession(authOptions)
+
+    if (!session?.user) {
+      return NextResponse.json(
+        { error: "No autorizado" },
+        { status: 401 }
+      )
+    }
+
+    const userRole = session.user.role
+    const userId = session.user.id
+
+    // Obtener fecha de hoy y hace 30 días para comparaciones
+    const today = new Date()
+    today.setHours(0, 0, 0, 0)
+    
+    const thirtyDaysAgo = new Date()
+    thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
+    
+    const sixtyDaysAgo = new Date()
+    sixtyDaysAgo.setDate(sixtyDaysAgo.getDate() - 60)
+
+    const sevenDaysAgo = new Date()
+    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
+
+    let stats = {
+      totalConsults: 0,
+      totalReports: 0,
+      totalPatients: 0,
+      totalAppointments: 0,
+      consultsToday: 0,
+      reportsThisWeek: 0,
+      activePatients: 0,
+      consultsTrend: "+0%",
+      reportsTrend: "+0%",
+    }
+
+    if (userRole === "PATIENT") {
+      // Estadísticas para pacientes
+      
+      // Total de reportes del paciente
+      const totalReports = await prisma.record.count({
+        where: { userId }
+      })
+
+      // Reportes de los últimos 7 días
+      const reportsThisWeek = await prisma.record.count({
+        where: {
+          userId,
+          createdAt: { gte: sevenDaysAgo }
+        }
+      })
+
+      // Reportes del mes anterior (para tendencia)
+      const reportsLastMonth = await prisma.record.count({
+        where: {
+          userId,
+          createdAt: { gte: sixtyDaysAgo, lt: thirtyDaysAgo }
+        }
+      })
+
+      const reportsThisMonth = await prisma.record.count({
+        where: {
+          userId,
+          createdAt: { gte: thirtyDaysAgo }
+        }
+      })
+
+      // Citas del paciente
+      const totalAppointments = await prisma.appointment.count({
+        where: {
+          pacienteId: userId,
+          estado: { in: ["PENDIENTE", "APROBADA"] }
+        }
+      })
+
+      // Calcular tendencia de reportes
+      let reportsTrend = "+0%"
+      if (reportsLastMonth > 0) {
+        const change = ((reportsThisMonth - reportsLastMonth) / reportsLastMonth) * 100
+        reportsTrend = `${change >= 0 ? '+' : ''}${Math.round(change)}%`
+      } else if (reportsThisMonth > 0) {
+        reportsTrend = "+100%"
+      }
+
+      stats = {
+        totalConsults: 0, // Los pacientes no tienen esta métrica específica
+        totalReports,
+        totalPatients: 0, // No aplica para pacientes
+        totalAppointments,
+        consultsToday: 0,
+        reportsThisWeek,
+        activePatients: 0,
+        consultsTrend: "+0%",
+        reportsTrend,
+      }
+
+    } else if (userRole === "DOCTOR") {
+      // Estadísticas para doctores
+
+      // Pacientes asignados al doctor
+      const assignedPatients = await prisma.patientAssignment.findMany({
+        where: { doctorId: userId },
+        select: { patientId: true }
+      })
+      const patientIds = assignedPatients.map((a: { patientId: string }) => a.patientId)
+
+      // Total de pacientes asignados
+      const totalPatients = patientIds.length
+
+      // Total de reportes de los pacientes asignados
+      const totalReports = await prisma.record.count({
+        where: { userId: { in: patientIds } }
+      })
+
+      // Reportes de hoy
+      const consultsToday = await prisma.record.count({
+        where: {
+          userId: { in: patientIds },
+          createdAt: { gte: today }
+        }
+      })
+
+      // Reportes de esta semana
+      const reportsThisWeek = await prisma.record.count({
+        where: {
+          userId: { in: patientIds },
+          createdAt: { gte: sevenDaysAgo }
+        }
+      })
+
+      // Reportes del mes actual vs mes anterior
+      const reportsThisMonth = await prisma.record.count({
+        where: {
+          userId: { in: patientIds },
+          createdAt: { gte: thirtyDaysAgo }
+        }
+      })
+
+      const reportsLastMonth = await prisma.record.count({
+        where: {
+          userId: { in: patientIds },
+          createdAt: { gte: sixtyDaysAgo, lt: thirtyDaysAgo }
+        }
+      })
+
+      // Pacientes activos (que han generado reportes recientemente)
+      const activePatients = await prisma.record.groupBy({
+        by: ['userId'],
+        where: {
+          userId: { in: patientIds },
+          createdAt: { gte: thirtyDaysAgo }
+        }
+      })
+
+      // Tendencias
+      let consultsTrend = "+0%"
+      let reportsTrend = "+0%"
+      
+      if (reportsLastMonth > 0) {
+        const change = ((reportsThisMonth - reportsLastMonth) / reportsLastMonth) * 100
+        consultsTrend = `${change >= 0 ? '+' : ''}${Math.round(change)}%`
+        reportsTrend = consultsTrend
+      } else if (reportsThisMonth > 0) {
+        consultsTrend = "+100%"
+        reportsTrend = "+100%"
+      }
+
+      stats = {
+        totalConsults: totalReports,
+        totalReports,
+        totalPatients,
+        totalAppointments: 0, // Por ahora
+        consultsToday,
+        reportsThisWeek,
+        activePatients: activePatients.length,
+        consultsTrend,
+        reportsTrend,
+      }
+
+    } else if (userRole === "ADMIN") {
+      // Estadísticas para administradores (vista completa del sistema)
+
+      // Total de usuarios por tipo
+      const totalPatients = await prisma.user.count({
+        where: { role: "PATIENT" }
+      })
+
+      // Total de reportes en el sistema
+      const totalReports = await prisma.record.count()
+
+      // Reportes de hoy
+      const consultsToday = await prisma.record.count({
+        where: { createdAt: { gte: today } }
+      })
+
+      // Reportes de esta semana
+      const reportsThisWeek = await prisma.record.count({
+        where: { createdAt: { gte: sevenDaysAgo } }
+      })
+
+      // Reportes del mes actual vs mes anterior
+      const reportsThisMonth = await prisma.record.count({
+        where: { createdAt: { gte: thirtyDaysAgo } }
+      })
+
+      const reportsLastMonth = await prisma.record.count({
+        where: {
+          createdAt: { gte: sixtyDaysAgo, lt: thirtyDaysAgo }
+        }
+      })
+
+      // Pacientes activos (que han generado reportes recientemente)
+      const activePatients = await prisma.record.groupBy({
+        by: ['userId'],
+        where: { createdAt: { gte: thirtyDaysAgo } }
+      })
+
+      // Tendencias
+      let consultsTrend = "+0%"
+      let reportsTrend = "+0%"
+      
+      if (reportsLastMonth > 0) {
+        const change = ((reportsThisMonth - reportsLastMonth) / reportsLastMonth) * 100
+        consultsTrend = `${change >= 0 ? '+' : ''}${Math.round(change)}%`
+        reportsTrend = consultsTrend
+      } else if (reportsThisMonth > 0) {
+        consultsTrend = "+100%"
+        reportsTrend = "+100%"
+      }
+
+      stats = {
+        totalConsults: totalReports,
+        totalReports,
+        totalPatients,
+        totalAppointments: 0, // Por ahora
+        consultsToday,
+        reportsThisWeek,
+        activePatients: activePatients.length,
+        consultsTrend,
+        reportsTrend,
+      }
+    }
+
+    return NextResponse.json(stats)
+
+  } catch (error) {
+    console.error("Error in dashboard stats API:", error)
+    return NextResponse.json(
+      { error: "Error al obtener estadísticas del dashboard" },
+      { status: 500 }
+    )
+  }
+}

+ 2 - 323
src/app/dashboard/page.tsx

@@ -2,24 +2,7 @@
 
 import { useSession } from "next-auth/react"
 import AuthenticatedLayout from "@/components/AuthenticatedLayout"
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
-import { Button } from "@/components/ui/button"
-import { Badge } from "@/components/ui/badge"
-import { Separator } from "@/components/ui/separator"
-import { 
-  MessageSquare, 
-  FileText, 
-  Users, 
-  Activity, 
-  Calendar,
-  Shield,
-  TrendingUp,
-  ArrowRight,
-  Clock,
-  ChevronRight
-} from "lucide-react"
-import Link from "next/link"
-import { cn } from "@/lib/utils"
+import DashboardContent from "@/components/dashboard/DashboardContent"
 
 export default function DashboardPage() {
   const { data: session } = useSession()
@@ -28,313 +11,9 @@ export default function DashboardPage() {
     return null
   }
 
-  const isDoctor = session.user.role === "DOCTOR"
-  const isAdmin = session.user.role === "ADMIN"
-  const isPatient = session.user.role === "PATIENT"
-
-  const roleConfig = {
-    ADMIN: {
-      badge: "Admin",
-      badgeVariant: "default" as const,
-      description: "Gestión completa del sistema"
-    },
-    DOCTOR: {
-      badge: "Doctor",
-      badgeVariant: "secondary" as const,
-      description: "Panel de gestión médica"
-    },
-    PATIENT: {
-      badge: "Paciente",
-      badgeVariant: "outline" as const,
-      description: "Tu asistente médico personal"
-    }
-  }
-
-  const config = roleConfig[session.user.role as keyof typeof roleConfig]
-
   return (
     <AuthenticatedLayout>
-      <div className="flex-1 space-y-6 p-8 pt-6">
-        {/* Header Section */}
-        <Card>
-          <CardContent className="p-6">
-            <div className="flex items-center justify-between">
-              <div className="flex items-center gap-4">
-                <div className="w-12 h-12 bg-primary rounded-lg flex items-center justify-center shadow-sm">
-                  <Activity className="w-6 h-6 text-primary-foreground" />
-                </div>
-                <div>
-                  <div className="flex items-center gap-3">
-                    <h2 className="text-2xl font-bold tracking-tight">Dashboard</h2>
-                    <Badge variant={config.badgeVariant}>{config.badge}</Badge>
-                  </div>
-                  <p className="text-sm text-muted-foreground mt-1">
-                    {config.description}
-                  </p>
-                </div>
-              </div>
-            </div>
-          </CardContent>
-        </Card>
-
-        {/* Stats Grid */}
-        <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
-          <Card>
-            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
-              <CardTitle className="text-sm font-medium">
-                Consultas
-              </CardTitle>
-              <MessageSquare className="h-4 w-4 text-muted-foreground" />
-            </CardHeader>
-            <CardContent>
-              <div className="text-2xl font-bold">
-                {isPatient ? "Disponible" : "24"}
-              </div>
-              <p className="text-xs text-muted-foreground">
-                {isPatient ? "3 consultas por sesión" : "+12% desde el mes pasado"}
-              </p>
-            </CardContent>
-          </Card>
-
-          <Card>
-            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
-              <CardTitle className="text-sm font-medium">
-                Reportes
-              </CardTitle>
-              <FileText className="h-4 w-4 text-muted-foreground" />
-            </CardHeader>
-            <CardContent>
-              <div className="text-2xl font-bold">
-                {isPatient ? "15" : "128"}
-              </div>
-              <p className="text-xs text-muted-foreground">
-                {isPatient ? "Reportes generados" : "+8 esta semana"}
-              </p>
-            </CardContent>
-          </Card>
-
-          <Card>
-            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
-              <CardTitle className="text-sm font-medium">
-                {isDoctor || isAdmin ? "Pacientes" : "Citas"}
-              </CardTitle>
-              {isDoctor || isAdmin ? (
-                <Users className="h-4 w-4 text-muted-foreground" />
-              ) : (
-                <Calendar className="h-4 w-4 text-muted-foreground" />
-              )}
-            </CardHeader>
-            <CardContent>
-              <div className="text-2xl font-bold">
-                {isDoctor || isAdmin ? "45" : "2"}
-              </div>
-              <p className="text-xs text-muted-foreground">
-                {isDoctor || isAdmin ? "Activos en el sistema" : "Próximas citas"}
-              </p>
-            </CardContent>
-          </Card>
-        </div>
-
-        {/* Main Content Grid */}
-        <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
-          {/* Quick Actions */}
-          <Card className="col-span-4">
-            <CardHeader>
-              <CardTitle>Acciones Rápidas</CardTitle>
-              <CardDescription>
-                Accede a las funciones principales del sistema
-              </CardDescription>
-            </CardHeader>
-            <CardContent className="space-y-4">
-              {isAdmin ? (
-                <>
-                  <QuickActionItem
-                    icon={Shield}
-                    title="Panel de Administración"
-                    description="Gestiona usuarios, doctores y configuración"
-                    href="/admin"
-                    iconBg="bg-violet-100 dark:bg-violet-900/20"
-                    iconColor="text-violet-600 dark:text-violet-400"
-                  />
-                  <Separator />
-                  <QuickActionItem
-                    icon={FileText}
-                    title="Todos los Reportes"
-                    description="Accede al historial completo del sistema"
-                    href="/records"
-                    iconBg="bg-emerald-100 dark:bg-emerald-900/20"
-                    iconColor="text-emerald-600 dark:text-emerald-400"
-                  />
-                  <Separator />
-                  <QuickActionItem
-                    icon={Users}
-                    title="Gestión de Usuarios"
-                    description="Administra pacientes y personal médico"
-                    href="/admin/patients"
-                    iconBg="bg-blue-100 dark:bg-blue-900/20"
-                    iconColor="text-blue-600 dark:text-blue-400"
-                  />
-                </>
-              ) : isDoctor ? (
-                <>
-                  <QuickActionItem
-                    icon={Users}
-                    title="Mis Pacientes"
-                    description="Revisa y gestiona tus pacientes asignados"
-                    href="/admin"
-                    iconBg="bg-blue-100 dark:bg-blue-900/20"
-                    iconColor="text-blue-600 dark:text-blue-400"
-                  />
-                  <Separator />
-                  <QuickActionItem
-                    icon={FileText}
-                    title="Reportes Médicos"
-                    description="Historial de consultas y diagnósticos"
-                    href="/records"
-                    iconBg="bg-emerald-100 dark:bg-emerald-900/20"
-                    iconColor="text-emerald-600 dark:text-emerald-400"
-                  />
-                  <Separator />
-                  <QuickActionItem
-                    icon={Calendar}
-                    title="Agenda de Citas"
-                    description="Visualiza tu calendario de consultas"
-                    href="/appointments/doctor"
-                    iconBg="bg-amber-100 dark:bg-amber-900/20"
-                    iconColor="text-amber-600 dark:text-amber-400"
-                  />
-                </>
-              ) : (
-                <>
-                  <QuickActionItem
-                    icon={MessageSquare}
-                    title="Chat Médico"
-                    description="Consulta con el asistente virtual"
-                    href="/chat"
-                    iconBg="bg-blue-100 dark:bg-blue-900/20"
-                    iconColor="text-blue-600 dark:text-blue-400"
-                    badge="3 disponibles"
-                  />
-                  <Separator />
-                  <QuickActionItem
-                    icon={FileText}
-                    title="Mis Reportes"
-                    description="Historial de reportes médicos"
-                    href="/records"
-                    iconBg="bg-emerald-100 dark:bg-emerald-900/20"
-                    iconColor="text-emerald-600 dark:text-emerald-400"
-                  />
-                  <Separator />
-                  <QuickActionItem
-                    icon={Calendar}
-                    title="Mis Citas"
-                    description="Próximas consultas programadas"
-                    href="/appointments"
-                    iconBg="bg-amber-100 dark:bg-amber-900/20"
-                    iconColor="text-amber-600 dark:text-amber-400"
-                  />
-                </>
-              )}
-            </CardContent>
-          </Card>
-        </div>
-
-        {/* Additional Information */}
-        {(isDoctor || isAdmin) && (
-          <Card>
-            <CardHeader>
-              <CardTitle>Resumen de Actividad</CardTitle>
-              <CardDescription>
-                Estadísticas del sistema en tiempo real
-              </CardDescription>
-            </CardHeader>
-            <CardContent>
-              <div className="grid gap-4 md:grid-cols-3">
-                <div className="flex items-center gap-4">
-                  <div className="rounded-full bg-blue-100 dark:bg-blue-900/20 p-3">
-                    <TrendingUp className="h-5 w-5 text-blue-600 dark:text-blue-400" />
-                  </div>
-                  <div>
-                    <p className="text-sm font-medium text-muted-foreground">
-                      Consultas Hoy
-                    </p>
-                    <p className="text-2xl font-bold">12</p>
-                  </div>
-                </div>
-                <div className="flex items-center gap-4">
-                  <div className="rounded-full bg-emerald-100 dark:bg-emerald-900/20 p-3">
-                    <FileText className="h-5 w-5 text-emerald-600 dark:text-emerald-400" />
-                  </div>
-                  <div>
-                    <p className="text-sm font-medium text-muted-foreground">
-                      Reportes Generados
-                    </p>
-                    <p className="text-2xl font-bold">8</p>
-                  </div>
-                </div>
-                <div className="flex items-center gap-4">
-                  <div className="rounded-full bg-amber-100 dark:bg-amber-900/20 p-3">
-                    <Users className="h-5 w-5 text-amber-600 dark:text-amber-400" />
-                  </div>
-                  <div>
-                    <p className="text-sm font-medium text-muted-foreground">
-                      Pacientes Activos
-                    </p>
-                    <p className="text-2xl font-bold">45</p>
-                  </div>
-                </div>
-              </div>
-            </CardContent>
-          </Card>
-        )}
-      </div>
+      <DashboardContent role={session.user.role} />
     </AuthenticatedLayout>
   )
-}
-
-interface QuickActionItemProps {
-  icon: React.ElementType
-  title: string
-  description: string
-  href: string
-  iconBg: string
-  iconColor: string
-  badge?: string
-}
-
-function QuickActionItem({
-  icon: Icon,
-  title,
-  description,
-  href,
-  iconBg,
-  iconColor,
-  badge
-}: QuickActionItemProps) {
-  return (
-    <Link 
-      href={href} 
-      className="group flex items-center justify-between rounded-lg transition-colors hover:bg-accent"
-    >
-      <div className="flex items-center gap-4 flex-1 py-2">
-        <div className={cn("rounded-lg p-2.5", iconBg)}>
-          <Icon className={cn("h-5 w-5", iconColor)} />
-        </div>
-        <div className="flex-1 space-y-1">
-          <div className="flex items-center gap-2">
-            <p className="text-sm font-medium leading-none">{title}</p>
-            {badge && (
-              <Badge variant="secondary" className="text-xs">
-                {badge}
-              </Badge>
-            )}
-          </div>
-          <p className="text-sm text-muted-foreground line-clamp-1">
-            {description}
-          </p>
-        </div>
-      </div>
-      <ChevronRight className="h-5 w-5 text-muted-foreground transition-transform group-hover:translate-x-1" />
-    </Link>
-  )
 } 

+ 91 - 0
src/components/dashboard/ActivitySummary.tsx

@@ -0,0 +1,91 @@
+"use client"
+
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { TrendingUp, FileText, Users } from "lucide-react"
+import { Skeleton } from "@/components/ui/skeleton"
+
+interface ActivitySummaryProps {
+  stats: {
+    consultsToday: number
+    reportsThisWeek: number
+    activePatients: number
+    loading: boolean
+    error: string | null
+  }
+}
+
+export default function ActivitySummary({ stats }: ActivitySummaryProps) {
+  if (stats.loading) {
+    return (
+      <Card>
+        <CardHeader>
+          <CardTitle>Resumen de Actividad</CardTitle>
+          <CardDescription>
+            Estadísticas del sistema en tiempo real
+          </CardDescription>
+        </CardHeader>
+        <CardContent>
+          <div className="grid gap-4 md:grid-cols-3">
+            {[1, 2, 3].map((i) => (
+              <div key={i} className="flex items-center gap-4">
+                <Skeleton className="h-12 w-12 rounded-full" />
+                <div className="space-y-2">
+                  <Skeleton className="h-4 w-24" />
+                  <Skeleton className="h-6 w-12" />
+                </div>
+              </div>
+            ))}
+          </div>
+        </CardContent>
+      </Card>
+    )
+  }
+
+  return (
+    <Card>
+      <CardHeader>
+        <CardTitle>Resumen de Actividad</CardTitle>
+        <CardDescription>
+          Estadísticas del sistema en tiempo real
+        </CardDescription>
+      </CardHeader>
+      <CardContent>
+        <div className="grid gap-4 md:grid-cols-3">
+          <div className="flex items-center gap-4">
+            <div className="rounded-full bg-blue-100 dark:bg-blue-900/20 p-3">
+              <TrendingUp className="h-5 w-5 text-blue-600 dark:text-blue-400" />
+            </div>
+            <div>
+              <p className="text-sm font-medium text-muted-foreground">
+                Consultas Hoy
+              </p>
+              <p className="text-2xl font-bold">{stats.consultsToday}</p>
+            </div>
+          </div>
+          <div className="flex items-center gap-4">
+            <div className="rounded-full bg-emerald-100 dark:bg-emerald-900/20 p-3">
+              <FileText className="h-5 w-5 text-emerald-600 dark:text-emerald-400" />
+            </div>
+            <div>
+              <p className="text-sm font-medium text-muted-foreground">
+                Reportes Esta Semana
+              </p>
+              <p className="text-2xl font-bold">{stats.reportsThisWeek}</p>
+            </div>
+          </div>
+          <div className="flex items-center gap-4">
+            <div className="rounded-full bg-amber-100 dark:bg-amber-900/20 p-3">
+              <Users className="h-5 w-5 text-amber-600 dark:text-amber-400" />
+            </div>
+            <div>
+              <p className="text-sm font-medium text-muted-foreground">
+                Pacientes Activos
+              </p>
+              <p className="text-2xl font-bold">{stats.activePatients}</p>
+            </div>
+          </div>
+        </div>
+      </CardContent>
+    </Card>
+  )
+}

+ 59 - 0
src/components/dashboard/DashboardContent.tsx

@@ -0,0 +1,59 @@
+"use client"
+
+import { useDashboard } from "@/hooks/useDashboard"
+import DashboardHeader from "./DashboardHeader"
+import DashboardStats from "./DashboardStats"
+import QuickActions from "./QuickActions"
+import ActivitySummary from "./ActivitySummary"
+
+interface DashboardContentProps {
+  role: string
+}
+
+export default function DashboardContent({ role }: DashboardContentProps) {
+  const stats = useDashboard()
+  const isDoctor = role === "DOCTOR"
+  const isAdmin = role === "ADMIN"
+  const isPatient = role === "PATIENT"
+
+  const roleConfig = {
+    ADMIN: {
+      badge: "Admin",
+      badgeVariant: "default" as const,
+      description: "Gestión completa del sistema"
+    },
+    DOCTOR: {
+      badge: "Doctor",
+      badgeVariant: "secondary" as const,
+      description: "Panel de gestión médica"
+    },
+    PATIENT: {
+      badge: "Paciente",
+      badgeVariant: "outline" as const,
+      description: "Tu asistente médico personal"
+    }
+  }
+
+  const config = roleConfig[role as keyof typeof roleConfig]
+
+  return (
+    <div className="space-y-6 p-8 pl-12 pt-6">
+      <DashboardHeader role={role} roleConfig={config} />
+      
+      <DashboardStats 
+        isPatient={isPatient}
+        isDoctor={isDoctor}
+        isAdmin={isAdmin}
+        stats={stats}
+      />
+
+      <QuickActions 
+        isAdmin={isAdmin}
+        isDoctor={isDoctor}
+        isPatient={isPatient}
+      />
+
+      {(isDoctor || isAdmin) && <ActivitySummary stats={stats} />}
+    </div>
+  )
+}

+ 39 - 0
src/components/dashboard/DashboardHeader.tsx

@@ -0,0 +1,39 @@
+"use client"
+
+import { Card, CardContent } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Activity } from "lucide-react"
+
+interface DashboardHeaderProps {
+  role: string
+  roleConfig: {
+    badge: string
+    badgeVariant: "default" | "secondary" | "outline"
+    description: string
+  }
+}
+
+export default function DashboardHeader({ role, roleConfig }: DashboardHeaderProps) {
+  return (
+    <Card>
+      <CardContent className="p-6">
+        <div className="flex items-center justify-between">
+          <div className="flex items-center gap-4">
+            <div className="w-12 h-12 bg-primary rounded-lg flex items-center justify-center shadow-sm">
+              <Activity className="w-6 h-6 text-primary-foreground" />
+            </div>
+            <div>
+              <div className="flex items-center gap-3">
+                <h2 className="text-2xl font-bold tracking-tight">Dashboard</h2>
+                <Badge variant={roleConfig.badgeVariant}>{roleConfig.badge}</Badge>
+              </div>
+              <p className="text-sm text-muted-foreground mt-1">
+                {roleConfig.description}
+              </p>
+            </div>
+          </div>
+        </div>
+      </CardContent>
+    </Card>
+  )
+}

+ 108 - 0
src/components/dashboard/DashboardStats.tsx

@@ -0,0 +1,108 @@
+"use client"
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { MessageSquare, FileText, Users, Calendar } from "lucide-react"
+import { Skeleton } from "@/components/ui/skeleton"
+
+interface DashboardStatsProps {
+  isPatient: boolean
+  isDoctor: boolean
+  isAdmin: boolean
+  stats: {
+    totalConsults: number
+    totalReports: number
+    totalPatients: number
+    totalAppointments: number
+    consultsToday: number
+    reportsThisWeek: number
+    activePatients: number
+    consultsTrend: string
+    reportsTrend: string
+    loading: boolean
+    error: string | null
+  }
+}
+
+export default function DashboardStats({ isPatient, isDoctor, isAdmin, stats }: DashboardStatsProps) {
+  if (stats.loading) {
+    return (
+      <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
+        {[1, 2, 3].map((i) => (
+          <Card key={i}>
+            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+              <Skeleton className="h-4 w-20" />
+              <Skeleton className="h-4 w-4" />
+            </CardHeader>
+            <CardContent>
+              <Skeleton className="h-8 w-16 mb-2" />
+              <Skeleton className="h-3 w-32" />
+            </CardContent>
+          </Card>
+        ))}
+      </div>
+    )
+  }
+
+  return (
+    <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
+      <Card>
+        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+          <CardTitle className="text-sm font-medium">
+            Consultas
+          </CardTitle>
+          <MessageSquare className="h-4 w-4 text-muted-foreground" />
+        </CardHeader>
+        <CardContent>
+          <div className="text-2xl font-bold">
+            {isPatient ? "Disponible" : stats.totalConsults}
+          </div>
+          <p className="text-xs text-muted-foreground">
+            {isPatient ? "3 consultas por sesión" : stats.consultsTrend + " desde el mes pasado"}
+          </p>
+        </CardContent>
+      </Card>
+
+      <Card>
+        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+          <CardTitle className="text-sm font-medium">
+            Reportes
+          </CardTitle>
+          <FileText className="h-4 w-4 text-muted-foreground" />
+        </CardHeader>
+        <CardContent>
+          <div className="text-2xl font-bold">
+            {stats.totalReports}
+          </div>
+          <p className="text-xs text-muted-foreground">
+            {isPatient 
+              ? `${stats.reportsThisWeek} esta semana` 
+              : stats.reportsTrend + " desde el mes pasado"}
+          </p>
+        </CardContent>
+      </Card>
+
+      <Card>
+        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+          <CardTitle className="text-sm font-medium">
+            {isDoctor || isAdmin ? "Pacientes" : "Citas"}
+          </CardTitle>
+          {isDoctor || isAdmin ? (
+            <Users className="h-4 w-4 text-muted-foreground" />
+          ) : (
+            <Calendar className="h-4 w-4 text-muted-foreground" />
+          )}
+        </CardHeader>
+        <CardContent>
+          <div className="text-2xl font-bold">
+            {isDoctor || isAdmin ? stats.totalPatients : stats.totalAppointments}
+          </div>
+          <p className="text-xs text-muted-foreground">
+            {isDoctor || isAdmin 
+              ? `${stats.activePatients} activos` 
+              : "Próximas citas"}
+          </p>
+        </CardContent>
+      </Card>
+    </div>
+  )
+}

+ 55 - 0
src/components/dashboard/QuickActionButton.tsx

@@ -0,0 +1,55 @@
+"use client"
+
+import Link from "next/link"
+import { Card, CardContent } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { ArrowRight } from "lucide-react"
+import { cn } from "@/lib/utils"
+
+interface QuickActionButtonProps {
+  icon: React.ElementType
+  title: string
+  description: string
+  href: string
+  iconBg: string
+  iconColor: string
+  badge?: string
+}
+
+export default function QuickActionButton({
+  icon: Icon,
+  title,
+  description,
+  href,
+  iconBg,
+  iconColor,
+  badge
+}: QuickActionButtonProps) {
+  return (
+    <Link href={href}>
+      <Card className="group cursor-pointer transition-all hover:shadow-lg hover:scale-[1.02] hover:border-primary/50">
+        <CardContent className="p-6">
+          <div className="flex flex-col items-center text-center space-y-4">
+            <div className={cn("rounded-xl p-4 transition-transform group-hover:scale-110", iconBg)}>
+              <Icon className={cn("h-8 w-8", iconColor)} />
+            </div>
+            <div className="space-y-2">
+              <div className="flex items-center justify-center gap-2">
+                <h4 className="font-semibold text-base">{title}</h4>
+                {badge && (
+                  <Badge variant="secondary" className="text-xs">
+                    {badge}
+                  </Badge>
+                )}
+              </div>
+              <p className="text-sm text-muted-foreground">
+                {description}
+              </p>
+            </div>
+            <ArrowRight className="h-5 w-5 text-muted-foreground transition-transform group-hover:translate-x-1" />
+          </div>
+        </CardContent>
+      </Card>
+    </Link>
+  )
+}

+ 108 - 0
src/components/dashboard/QuickActions.tsx

@@ -0,0 +1,108 @@
+"use client"
+
+import { MessageSquare, FileText, Users, Calendar, Shield } from "lucide-react"
+import QuickActionButton from "./QuickActionButton"
+
+interface QuickActionsProps {
+  isAdmin: boolean
+  isDoctor: boolean
+  isPatient: boolean
+}
+
+export default function QuickActions({ isAdmin, isDoctor, isPatient }: QuickActionsProps) {
+  return (
+    <div>
+      <div className="mb-4">
+        <h3 className="text-lg font-semibold">Acciones Rápidas</h3>
+        <p className="text-sm text-muted-foreground">
+          Accede a las funciones principales del sistema
+        </p>
+      </div>
+      <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
+        {isAdmin ? (
+          <>
+            <QuickActionButton
+              icon={Shield}
+              title="Panel de Administración"
+              description="Gestiona usuarios, doctores y configuración"
+              href="/admin"
+              iconBg="bg-violet-100 dark:bg-violet-900/20"
+              iconColor="text-violet-600 dark:text-violet-400"
+            />
+            <QuickActionButton
+              icon={FileText}
+              title="Todos los Reportes"
+              description="Accede al historial completo del sistema"
+              href="/records"
+              iconBg="bg-emerald-100 dark:bg-emerald-900/20"
+              iconColor="text-emerald-600 dark:text-emerald-400"
+            />
+            <QuickActionButton
+              icon={Users}
+              title="Gestión de Usuarios"
+              description="Administra pacientes y personal médico"
+              href="/admin/patients"
+              iconBg="bg-blue-100 dark:bg-blue-900/20"
+              iconColor="text-blue-600 dark:text-blue-400"
+            />
+          </>
+        ) : isDoctor ? (
+          <>
+            <QuickActionButton
+              icon={Users}
+              title="Mis Pacientes"
+              description="Revisa y gestiona tus pacientes asignados"
+              href="/admin"
+              iconBg="bg-blue-100 dark:bg-blue-900/20"
+              iconColor="text-blue-600 dark:text-blue-400"
+            />
+            <QuickActionButton
+              icon={FileText}
+              title="Reportes Médicos"
+              description="Historial de consultas y diagnósticos"
+              href="/records"
+              iconBg="bg-emerald-100 dark:bg-emerald-900/20"
+              iconColor="text-emerald-600 dark:text-emerald-400"
+            />
+            <QuickActionButton
+              icon={Calendar}
+              title="Agenda de Citas"
+              description="Visualiza tu calendario de consultas"
+              href="/appointments/doctor"
+              iconBg="bg-amber-100 dark:bg-amber-900/20"
+              iconColor="text-amber-600 dark:text-amber-400"
+            />
+          </>
+        ) : (
+          <>
+            <QuickActionButton
+              icon={MessageSquare}
+              title="Chat Médico"
+              description="Consulta con el asistente virtual"
+              href="/chat"
+              iconBg="bg-blue-100 dark:bg-blue-900/20"
+              iconColor="text-blue-600 dark:text-blue-400"
+              badge="3 disponibles"
+            />
+            <QuickActionButton
+              icon={FileText}
+              title="Mis Reportes"
+              description="Historial de reportes médicos"
+              href="/records"
+              iconBg="bg-emerald-100 dark:bg-emerald-900/20"
+              iconColor="text-emerald-600 dark:text-emerald-400"
+            />
+            <QuickActionButton
+              icon={Calendar}
+              title="Mis Citas"
+              description="Próximas consultas programadas"
+              href="/appointments"
+              iconBg="bg-amber-100 dark:bg-amber-900/20"
+              iconColor="text-amber-600 dark:text-amber-400"
+            />
+          </>
+        )}
+      </div>
+    </div>
+  )
+}

+ 7 - 0
src/components/dashboard/index.ts

@@ -0,0 +1,7 @@
+// Dashboard Components
+export { default as DashboardContent } from "./DashboardContent"
+export { default as DashboardHeader } from "./DashboardHeader"
+export { default as DashboardStats } from "./DashboardStats"
+export { default as QuickActions } from "./QuickActions"
+export { default as QuickActionButton } from "./QuickActionButton"
+export { default as ActivitySummary } from "./ActivitySummary"

+ 76 - 0
src/hooks/useDashboard.ts

@@ -0,0 +1,76 @@
+import { useState, useEffect } from "react"
+import { useSession } from "next-auth/react"
+
+interface DashboardStats {
+  // Estadísticas generales
+  totalConsults: number
+  totalReports: number
+  totalPatients: number
+  totalAppointments: number
+  
+  // Estadísticas de actividad (para doctores/admins)
+  consultsToday: number
+  reportsThisWeek: number
+  activePatients: number
+  
+  // Tendencias
+  consultsTrend: string
+  reportsTrend: string
+  
+  loading: boolean
+  error: string | null
+}
+
+export function useDashboard() {
+  const { data: session, status } = useSession()
+  const [stats, setStats] = useState<DashboardStats>({
+    totalConsults: 0,
+    totalReports: 0,
+    totalPatients: 0,
+    totalAppointments: 0,
+    consultsToday: 0,
+    reportsThisWeek: 0,
+    activePatients: 0,
+    consultsTrend: "+0%",
+    reportsTrend: "+0%",
+    loading: true,
+    error: null,
+  })
+
+  useEffect(() => {
+    if (status !== "authenticated" || !session?.user) {
+      return
+    }
+
+    const fetchDashboardStats = async () => {
+      try {
+        setStats(prev => ({ ...prev, loading: true, error: null }))
+
+        const response = await fetch("/api/dashboard/stats")
+        
+        if (!response.ok) {
+          throw new Error("Error al cargar estadísticas")
+        }
+
+        const data = await response.json()
+        
+        setStats({
+          ...data,
+          loading: false,
+          error: null,
+        })
+      } catch (error) {
+        console.error("Error fetching dashboard stats:", error)
+        setStats(prev => ({
+          ...prev,
+          loading: false,
+          error: error instanceof Error ? error.message : "Error desconocido",
+        }))
+      }
+    }
+
+    fetchDashboardStats()
+  }, [status, session])
+
+  return stats
+}