🎨 TutorConnect UC - Design System & Style Guide

📐 Principios de Diseño

Core Values

  1. Universitario & Profesional: Refleja la seriedad académica de la UC
  2. Accesible & Inclusivo: Usable para todos los estudiantes
  3. Eficiente: Mínimos clicks para completar acciones
  4. Confiable: Transmite seguridad y verificación
  5. Mobile-First: Optimizado para estudiantes en movimiento

🎨 Paleta de Colores

Colores Primarios (UC Brand)

/* Azul UC - Color principal */
--uc-blue: #002D5E;
--uc-blue-light: #1E4A7A;
--uc-blue-dark: #001B3D;
 
/* Dorado UC - Acentos */
--uc-gold: #D4AF37;
--uc-gold-light: #E5C766;
--uc-gold-dark: #B89520;

Colores Secundarios (Aplicación)

/* Grises (Neutrales) */
--gray-50: #F9FAFB;
--gray-100: #F3F4F6;
--gray-200: #E5E7EB;
--gray-300: #D1D5DB;
--gray-400: #9CA3AF;
--gray-500: #6B7280;
--gray-600: #4B5563;
--gray-700: #374151;
--gray-800: #1F2937;
--gray-900: #111827;
 
/* Colores Funcionales */
--success: #10B981;   /* Verde - confirmado, completado */
--warning: #F59E0B;   /* Amarillo - pendiente, advertencia */
--error: #EF4444;     /* Rojo - urgente, error */
--info: #3B82F6;      /* Azul claro - información */

Estados de Tutorías (Semánticos)

--status-pending: #F59E0B;    /* 🟡 Pendiente */
--status-confirmed: #10B981;   /* 🟢 Confirmada */
--status-completed: #6B7280;   /* ✅ Completada */
--status-cancelled: #EF4444;   /* ❌ Cancelada */

Uso de Colores

ElementoColorCuándo Usar
Botones primariosuc-blueAcciones principales (Agendar, Enviar)
Botones secundariosgray-600Acciones secundarias (Cancelar, Ver más)
Badges destacadosuc-goldPremium, destacado, verificado
Linksuc-blue-lightEnlaces de texto
Backgroundsgray-50Fondos de secciones alternadas
Bordersgray-200Bordes de cards, inputs

📝 Tipografía

Font Stack

/* Sans-serif (Principal) */
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 
             'Roboto', sans-serif;
 
/* Monospace (Código de ramos) */
font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;

Escala Tipográfica

/* Headings */
--text-h1: 2.5rem;      /* 40px - Títulos principales */
--text-h2: 2rem;        /* 32px - Secciones */
--text-h3: 1.5rem;      /* 24px - Subsecciones */
--text-h4: 1.25rem;     /* 20px - Cards headers */
--text-h5: 1.125rem;    /* 18px - Pequeños headers */
 
/* Body */
--text-base: 1rem;      /* 16px - Texto normal */
--text-sm: 0.875rem;    /* 14px - Texto secundario */
--text-xs: 0.75rem;     /* 12px - Metadata, badges */
 
/* Font Weights */
--font-light: 300;
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;

Jerarquía Visual

// Ejemplo en componentes
<h1 className="text-4xl font-bold text-gray-900">
  Encuentra tu tutor UC
</h1>
 
<h2 className="text-3xl font-bold text-gray-800">
  Tutores Destacados
</h2>
 
<p className="text-base text-gray-600">
  Descripción normal del contenido
</p>
 
<span className="text-sm text-gray-500">
  Metadata o información secundaria
</span>
 
<code className="text-sm font-mono text-uc-blue bg-gray-100 px-2 py-1 rounded">
  IIC2233
</code>

📏 Espaciado (Spacing Scale)

Sistema de 8pt Grid

--space-1: 0.25rem;   /* 4px */
--space-2: 0.5rem;    /* 8px */
--space-3: 0.75rem;   /* 12px */
--space-4: 1rem;      /* 16px */
--space-5: 1.25rem;   /* 20px */
--space-6: 1.5rem;    /* 24px */
--space-8: 2rem;      /* 32px */
--space-10: 2.5rem;   /* 40px */
--space-12: 3rem;     /* 48px */
--space-16: 4rem;     /* 64px */
--space-20: 5rem;     /* 80px */

Reglas de Espaciado

/* Entre elementos relacionados */
gap: 1rem;  /* 16px */
 
/* Entre secciones */
margin-bottom: 2rem;  /* 32px */
 
/* Padding de containers */
padding: 1.5rem;  /* 24px desktop */
padding: 1rem;    /* 16px mobile */
 
/* Padding de cards */
padding: 1.5rem;  /* 24px */

🔲 Componentes Base

Buttons

// Primary Button
<button className="
  px-6 py-3 
  bg-uc-blue hover:bg-uc-blue-light 
  text-white font-medium 
  rounded-lg 
  transition-colors duration-200
  focus:outline-none focus:ring-2 focus:ring-uc-blue focus:ring-offset-2
">
  Agendar Tutoría →
</button>
 
// Secondary Button
<button className="
  px-6 py-3 
  bg-gray-100 hover:bg-gray-200 
  text-gray-700 font-medium 
  rounded-lg 
  transition-colors duration-200
">
  Cancelar
</button>
 
// Ghost Button
<button className="
  px-4 py-2 
  text-uc-blue hover:bg-blue-50 
  font-medium 
  rounded-lg 
  transition-colors duration-200
">
  Ver más
</button>
 
// Destructive Button
<button className="
  px-6 py-3 
  bg-red-500 hover:bg-red-600 
  text-white font-medium 
  rounded-lg 
  transition-colors duration-200
">
  Eliminar
</button>

Cards

// TutorCard
<div className="
  bg-white 
  border border-gray-200 
  rounded-xl 
  p-6 
  shadow-sm hover:shadow-md 
  transition-shadow duration-200
">
  {/* Content */}
</div>
 
// OfertaCard (con badge)
<div className="
  bg-white 
  border-l-4 border-l-green-500 
  border border-gray-200 
  rounded-lg 
  p-4
">
  <span className="
    inline-block 
    px-2 py-1 
    bg-green-100 text-green-700 
    text-xs font-semibold 
    rounded-full
  ">
    🟢 OFERTA
  </span>
</div>

Inputs

// Text Input
<input 
  type="text"
  className="
    w-full 
    px-4 py-2 
    border border-gray-300 
    rounded-lg 
    focus:outline-none focus:ring-2 focus:ring-uc-blue focus:border-transparent
    placeholder:text-gray-400
  "
  placeholder="Buscar ramo..."
/>
 
// Select
<select className="
  w-full 
  px-4 py-2 
  border border-gray-300 
  rounded-lg 
  bg-white
  focus:outline-none focus:ring-2 focus:ring-uc-blue
">
  <option>Selecciona un campus</option>
</select>

Badges

// Status Badge
<span className="
  inline-flex items-center 
  px-2.5 py-0.5 
  rounded-full 
  text-xs font-medium 
  bg-yellow-100 text-yellow-800
">
  🟡 Pendiente
</span>
 
// Rating Badge
<span className="
  inline-flex items-center 
  gap-1 
  text-sm font-medium
">
  ⭐ 4.9
</span>
 
// Campus Badge
<span className="
  inline-block 
  px-2 py-1 
  bg-uc-blue text-white 
  text-xs font-medium 
  rounded
">
  📍 San Joaquín
</span>

🧩 Componentes Compuestos

TutorCard (Reutilizable)

// components/tutor-card.tsx
interface TutorCardProps {
  tutor: {
    id: string
    full_name: string
    avatar_url?: string
    avg_rating: number
    total_reviews: number
    campus: string
    tarifa: number
    ramos: { codigo: string; nota: number }[]
  }
}
 
export function TutorCard({ tutor }: TutorCardProps) {
  return (
    <Link href={`/tutor/${tutor.id}`}>
      <div className="
        bg-white 
        border border-gray-200 
        rounded-xl 
        p-6 
        hover:shadow-lg 
        transition-all duration-200 
        cursor-pointer
        hover:border-uc-blue
      ">
        {/* Avatar */}
        <div className="flex items-center gap-4 mb-4">
          <Avatar className="h-16 w-16">
            <AvatarImage src={tutor.avatar_url} />
            <AvatarFallback className="bg-uc-blue text-white">
              {tutor.full_name[0]}
            </AvatarFallback>
          </Avatar>
          
          <div>
            <h3 className="font-semibold text-gray-900">
              {tutor.full_name}
            </h3>
            <div className="flex items-center gap-1 text-sm">
              <span className="text-yellow-500">⭐</span>
              <span className="font-medium">{tutor.avg_rating}</span>
              <span className="text-gray-500">({tutor.total_reviews})</span>
            </div>
          </div>
        </div>
 
        {/* Ramo principal */}
        {tutor.ramos[0] && (
          <div className="mb-3">
            <code className="
              text-sm font-mono 
              bg-gray-100 text-uc-blue 
              px-2 py-1 rounded
            ">
              {tutor.ramos[0].codigo}
            </code>
            <span className="ml-2 text-sm text-gray-600">
              Nota: {tutor.ramos[0].nota}
            </span>
          </div>
        )}
 
        {/* Footer */}
        <div className="flex items-center justify-between pt-4 border-t">
          <span className="text-lg font-bold text-gray-900">
            ${tutor.tarifa.toLocaleString()}
            <span className="text-sm font-normal text-gray-500">/módulo</span>
          </span>
          <Badge variant="outline">{tutor.campus}</Badge>
        </div>
      </div>
    </Link>
  )
}

SessionCard (Estado de tutoría)

interface SessionCardProps {
  session: {
    tutor_name: string
    ramo: string
    fecha_hora: string
    modalidad: 'presencial' | 'online'
    ubicacion: string
    precio_total: number
    estado: 'pendiente' | 'confirmada' | 'completada' | 'cancelada'
  }
}
 
export function SessionCard({ session }: SessionCardProps) {
  const statusConfig = {
    pendiente: {
      badge: '🟡 PENDIENTE',
      bgColor: 'bg-yellow-50',
      borderColor: 'border-yellow-200'
    },
    confirmada: {
      badge: '🟢 CONFIRMADA',
      bgColor: 'bg-green-50',
      borderColor: 'border-green-200'
    },
    completada: {
      badge: '✅ COMPLETADA',
      bgColor: 'bg-gray-50',
      borderColor: 'border-gray-200'
    },
    cancelada: {
      badge: '❌ CANCELADA',
      bgColor: 'bg-red-50',
      borderColor: 'border-red-200'
    }
  }
 
  const config = statusConfig[session.estado]
 
  return (
    <div className={`
      ${config.bgColor} 
      border ${config.borderColor} 
      rounded-lg 
      p-6
    `}>
      <div className="flex items-start justify-between mb-4">
        <Badge variant="secondary">{config.badge}</Badge>
        <span className="text-lg font-bold">
          ${session.precio_total.toLocaleString()}
        </span>
      </div>
 
      <h3 className="font-semibold text-gray-900 mb-2">
        {session.ramo} con {session.tutor_name}
      </h3>
 
      <div className="space-y-2 text-sm text-gray-600">
        <p>📅 {new Date(session.fecha_hora).toLocaleString('es-CL')}</p>
        <p>
          {session.modalidad === 'presencial' ? '📍' : '🌐'} 
          {' '}
          {session.ubicacion}
        </p>
      </div>
 
      {/* Actions basadas en estado */}
      {session.estado === 'pendiente' && (
        <div className="flex gap-2 mt-4">
          <Button variant="outline" size="sm">Ver detalles</Button>
          <Button variant="destructive" size="sm">Cancelar</Button>
        </div>
      )}
    </div>
  )
}

📱 Responsive Design

Breakpoints

/* Mobile First */
--screen-sm: 640px;   /* Tablet portrait */
--screen-md: 768px;   /* Tablet landscape */
--screen-lg: 1024px;  /* Desktop */
--screen-xl: 1280px;  /* Large desktop */

Grid System

// Landing Grid
<div className="
  grid 
  grid-cols-1           /* Mobile: 1 columna */
  md:grid-cols-2        /* Tablet: 2 columnas */
  lg:grid-cols-3        /* Desktop: 3 columnas */
  gap-6
">
  {tutores.map(tutor => <TutorCard key={tutor.id} tutor={tutor} />)}
</div>
 
// Sidebar Layout (Búsqueda)
<div className="
  grid 
  grid-cols-1           /* Mobile: stack */
  lg:grid-cols-4        /* Desktop: sidebar + content */
  gap-8
">
  <aside className="lg:col-span-1">
    <Filters />
  </aside>
  <main className="lg:col-span-3">
    <Results />
  </main>
</div>

Mobile Patterns

// Mobile: Drawer para filtros
// Desktop: Sidebar fijo
{isMobile ? (
  <Sheet>
    <SheetTrigger asChild>
      <Button>Filtros</Button>
    </SheetTrigger>
    <SheetContent side="left">
      <Filters />
    </SheetContent>
  </Sheet>
) : (
  <aside className="sticky top-20">
    <Filters />
  </aside>
)}
 
// Mobile: Bottom Navigation
<nav className="
  lg:hidden 
  fixed bottom-0 inset-x-0 
  bg-white border-t 
  flex justify-around 
  py-2
">
  <NavItem icon="🏠" label="Inicio" />
  <NavItem icon="🔍" label="Buscar" />
  <NavItem icon="💼" label="Ofertas" />
  <NavItem icon="👤" label="Perfil" />
</nav>

⚡ Interacciones & Animaciones

Transiciones

/* Hover states */
transition: all 200ms ease-in-out;
 
/* Color changes */
transition-property: background-color, border-color;
transition-duration: 200ms;
 
/* Transform (scale, translate) */
transition: transform 150ms ease-out;

Hover Effects

// Cards
<div className="
  hover:shadow-lg 
  hover:-translate-y-1 
  transition-all duration-200
">
 
// Buttons
<button className="
  hover:scale-105 
  active:scale-95 
  transition-transform
">
 
// Images
<img className="
  hover:opacity-80 
  transition-opacity
">

Loading States

// Skeleton Loader
<div className="animate-pulse space-y-4">
  <div className="h-4 bg-gray-200 rounded w-3/4"></div>
  <div className="h-4 bg-gray-200 rounded"></div>
  <div className="h-4 bg-gray-200 rounded w-5/6"></div>
</div>
 
// Spinner
<div className="
  animate-spin 
  rounded-full 
  h-8 w-8 
  border-b-2 border-uc-blue
"></div>

♿ Accesibilidad

Contraste de Colores

  • WCAG AA: Mínimo 4.5:1 para texto normal
  • WCAG AAA: Mínimo 7:1 para texto normal
// ✅ Buen contraste
<p className="text-gray-900 bg-white">  {/* 21:1 */}
<p className="text-white bg-uc-blue">   {/* 12:1 */}
 
// ❌ Mal contraste
<p className="text-gray-400 bg-white">  {/* 2.5:1 - ❌ */}

Focus States

// Todos los elementos interactivos
<button className="
  focus:outline-none 
  focus:ring-2 
  focus:ring-uc-blue 
  focus:ring-offset-2
">

Semantic HTML

// ✅ Usar elementos semánticos
<nav>
<main>
<article>
<section>
<aside>
 
// ✅ ARIA labels cuando sea necesario
<button aria-label="Cerrar modal">
  <X className="h-4 w-4" />
</button>
 
// ✅ Alt text en imágenes
<img src="..." alt="Foto de perfil de María González" />

Keyboard Navigation

// Tab order lógico
tabIndex={0}  // Elemento focusable
tabIndex={-1} // Elemento no focusable pero accesible programáticamente
 
// Atajos de teclado
onKeyDown={(e) => {
  if (e.key === 'Enter' || e.key === ' ') {
    handleClick()
  }
}}

🎯 Iconografía

Icons Library

Usar Lucide React (ya instalado con shadcn/ui):

import { 
  Search, 
  Calendar, 
  MapPin, 
  Star, 
  User, 
  Bell,
  CheckCircle,
  XCircle,
  Clock
} from 'lucide-react'
 
// Uso
<Search className="h-5 w-5 text-gray-400" />
<Star className="h-4 w-4 text-yellow-500 fill-current" />

Tamaños Consistentes

// Small (16px)
<Icon className="h-4 w-4" />
 
// Medium (20px)
<Icon className="h-5 w-5" />
 
// Large (24px)
<Icon className="h-6 w-6" />
 
// Extra Large (32px)
<Icon className="h-8 w-8" />

Emojis como Complemento

// Estados de tutoría
🟡 Pendiente
🟢 Confirmada
✅ Completada
❌ Cancelada
 
// Modalidad
📍 Presencial
🌐 Online
 
// Otros
⭐ Rating
💰 Precio
📅 Fecha
🎓 Ramo

📐 Layout Patterns

Container

<div className="
  container 
  mx-auto 
  px-4 sm:px-6 lg:px-8 
  max-w-7xl
">
  {/* Content */}
</div>

Section Spacing

<section className="py-16 lg:py-24">
  {/* Content */}
</section>
<nav className="
  sticky top-0 z-50 
  bg-white border-b 
  backdrop-blur-sm bg-white/95
">
  {/* Nav content */}
</nav>

🧪 Testing Checklist

Visual Testing

  • Todas las páginas se ven bien en 375px (iPhone)
  • Todas las páginas se ven bien en 768px (Tablet)
  • Todas las páginas se ven bien en 1440px (Desktop)
  • Todos los estados hover funcionan
  • Todos los focus states son visibles
  • Contraste WCAG AA cumplido

Functional Testing

  • Todos los botones hacen algo
  • Todos los links van a algún lado
  • Todos los forms se pueden enviar
  • Todos los modals se pueden cerrar
  • Navigation keyboard funciona

✅ Component Checklist

Antes de crear un componente nuevo, preguntate:

  1. ¿Ya existe? - Revisa shadcn/ui primero
  2. ¿Es reutilizable? - Si solo se usa 1 vez, no lo components
  3. ¿Tiene props claras? - Define TypeScript interface
  4. ¿Es accesible? - Focus, ARIA, semantic HTML
  5. ¿Es responsive? - Funciona en mobile y desktop

🎨 Brand Assets

// Logo completo (desktop)
<div className="flex items-center gap-2">
  <span className="text-2xl font-bold text-uc-blue">
    TutorConnect
  </span>
  <span className="text-sm font-medium text-uc-gold">
    UC
  </span>
</div>
 
// Logo reducido (mobile)
<span className="text-xl font-bold text-uc-blue">TC</span>

Favicon

  • Color: UC Blue (#002D5E)
  • Icon: “T” + “C” monogram
  • Sizes: 16x16, 32x32, 180x180 (Apple), 192x192 (Android)

📚 Resources

Design Tools

Inspiration

  • Airbnb (marketplace)
  • Calendly (booking)
  • LinkedIn (profiles)
  • Notion (clean UI)

Este design system es tu única fuente de verdad. Síguelo religiosamente durante el hackathon para mantener consistencia visual. 🎨