Skip to content

BryanStrk/drive-arena-frontend

Repository files navigation

🏁 Drive Arena — Frontend

Interfaz de operación del resort experiencial · React 19 + Vite + Tailwind v4

React Vite Tailwind CSS React Router Framer Motion Vercel Version


📋 Tabla de contenidos


🎯 Visión general

Drive Arena Frontend es la SPA que da forma a la operación diaria del resort: gestiona el turno de taquilla, el panel de control del administrador, y el flujo Kanban del técnico de mantenimiento. Consume la API REST del backend mediante Axios autenticado con JWT, y materializa un sistema de diseño propio inspirado en la estética de circuitos nocturnos: dark + rojo de marca + tipografía condensada.

La aplicación está construida con React 19, Vite 7, Tailwind CSS v4 y React Router 7, organizada por rol con tres layouts independientes (DashboardLayout para escritorio, TecnicoLayout para tablet, PublicLayout para landing y reserva pública).


🛠 Stack tecnológico

Categoría Tecnología Versión
Framework React 19
Build tool Vite 7
Estilos Tailwind CSS v4 (CSS variables, sin config JS)
Routing React Router 7
Formularios react-hook-form + zod + @hookform/resolvers 7 / 3
HTTP Axios 1.x
Animaciones framer-motion 12
Iconografía lucide-react 0.x
Notificaciones react-hot-toast 2.x
Fechas react-day-picker + date-fns 9
Linting ESLint 9

Cero dependencias UI heavyweight: ni Material UI, ni Chakra, ni Headless UI. Todo el sistema visual está construido sobre Tailwind v4 + tokens CSS personalizados.


🎨 Design System

Paleta

Token Tailwind Color Uso
bg-primary #FF2D2D (Drive Arena Red) CTAs, KPI signature, badges activos, marca
bg-bg #0A0A0F Fondo global
bg-surface-1 #13131A Cards, modales, headers
bg-surface-2 #1A1A22 Hover states, sub-superficies
border-border-strong #2A2A35 Bordes y divisores
text-text #F5F5F7 Texto principal
text-text-muted #A0A0AB Texto secundario
text-text-dim #6B6B75 Texto terciario

Tipografía

Familia Token Uso
Bebas Neue font-display Headings, KPIs, números grandes
Roboto Mono font-mono Etiquetas, metadata, micro-copy técnico
Inter font-sans Cuerpo de texto, formularios

Signature effects

  • KPI glow: valores numéricos principales con text-primary + drop-shadow-[0_0_8px_rgba(255,45,45,0.3)].
  • CTA glow: botones size="lg" con hover:shadow-xl hover:shadow-primary/50.
  • Micro-interactions: cards con hover:bg-surface-2/50, botones con active:scale-[0.98].
  • Stagger animations: listas con framer-motion entrando escalonadas (50 ms).
  • Skeleton loaders: componente <SkeletonCard /> reutilizable con animate-pulse.

⚡ Features

Operativa por rol

  • ADMIN: panel de control con KPIs en tiempo real, dashboard con widgets editables (ingresos mensuales, top lodges, ventas por edad, mantenimientos pendientes), gestión completa de clientes, ventas, lodges, circuitos, mantenimientos y usuarios sistema.
  • TAQUILLA: flujo de venta optimizado (/taquilla/nueva-compra), gestión de clientes, listado de "mis ventas" y "todas las ventas".
  • TÉCNICO: vista Kanban a 3 columnas (Pendiente / En curso / Resuelto), asignación dinámica de mantenimientos, reportes de finalización.

UX detallada

  • Sidebar drawer con hamburger menu en mobile (<md), comportamiento idéntico al sidebar fijo en desktop (md:).
  • Tablas con reflow tabla→cards en mobile (las páginas de listado mantienen <table> en desktop pero muestran cards apiladas en mobile, sin scroll horizontal).
  • Modales bottom-sheet en mobile (entran desde abajo) y centrados en desktop, todo con framer-motion.
  • Filtros con wrap automático (flex-wrap) para evitar overflow horizontal.
  • Formularios con validación zod y feedback de errores inline.
  • Notificaciones toast para todas las acciones CRUD.
  • Skeleton states durante carga para evitar layout shift.
  • Imágenes desde Cloudinary con componente <ImageWithFallback />.

Performance y DX

  • Code splitting automático por ruta vía React Router.
  • HMR instantáneo con Vite.
  • ESLint con reglas de hooks y react-refresh.
  • Build producción ~460 ms, gzip ~540 KB total.

🚀 Quick Start

Prerequisitos

  • Node.js ≥ 20
  • npm ≥ 10
  • Backend corriendo en http://localhost:8080 (ver backend repo)

Pasos

# 1. Clonar
git clone https://github.com/BryanStrk/drive-arena-frontend.git
cd drive-arena-frontend

# 2. Instalar dependencias
npm install

# 3. Configurar variables (ver siguiente sección)
cp .env.example .env

# 4. Arrancar dev server
npm run dev

La app quedará disponible en http://localhost:5173.

Scripts disponibles

npm run dev       # Dev server con HMR (Vite)
npm run build     # Build producción → dist/
npm run preview   # Preview del build de producción
npm run lint      # ESLint sobre src/

🔐 Variables de entorno

# URL del backend
VITE_API_BASE_URL=http://localhost:8080/api

# Cloudinary (solo si hay upload directo desde el cliente)
VITE_CLOUDINARY_CLOUD_NAME=tu_cloud
VITE_CLOUDINARY_UPLOAD_PRESET=tu_preset

# Entorno
VITE_APP_ENV=development

Importante: Vite expone al cliente solo las variables prefijadas con VITE_. Nunca pongas secretos como API keys de servidor en estas variables.

Proxy en desarrollo

vite.config.js incluye un proxy para evitar problemas de CORS en local:

server: {
  proxy: {
    '/api': {
      target: 'http://localhost:8080',
      changeOrigin: true,
      rewrite: (path) => path.replace(/^\/api/, ''),
    },
  },
}

📁 Estructura del proyecto

drive-arena-frontend/
├── public/
│   └── favicon.svg
├── src/
│   ├── components/
│   │   ├── Button.jsx, Input.jsx, Card.jsx, KpiCard.jsx,
│   │   ├── Badge.jsx, SkeletonCard.jsx, ImageWithFallback.jsx
│   │   ├── circuitos/         ← Modales y forms específicos de circuitos
│   │   ├── clientes/
│   │   ├── compras/
│   │   ├── dashboard/         ← Widgets del panel
│   │   │   ├── MonthlyRevenueWidget.jsx
│   │   │   ├── TopLodgesWidget.jsx
│   │   │   ├── AgeRangeSalesWidget.jsx
│   │   │   └── MantenimientosWidget.jsx
│   │   ├── layout/            ← Sidebar y elementos de chrome
│   │   ├── lodges/
│   │   ├── mantenimiento/     ← MantenimientoCard, modales
│   │   ├── taquilla/          ← Modales detalle venta, ticket, cliente
│   │   ├── tecnico/           ← Kanban, reporte de mantenimiento
│   │   └── usuarios/          ← Modales de gestión usuario sistema
│   ├── layouts/
│   │   ├── DashboardLayout.jsx  ← ADMIN + TAQUILLA (sidebar + breadcrumbs)
│   │   ├── TecnicoLayout.jsx    ← TÉCNICO (header simple, optimizado tablet)
│   │   └── PublicLayout.jsx     ← Landing y reserva pública
│   ├── lib/
│   │   ├── api.js              ← Axios instance con interceptors JWT
│   │   ├── motion.js           ← Variantes framer-motion reutilizables
│   │   └── utils.js            ← Formatters (fechas, moneda, etc)
│   ├── pages/
│   │   ├── Login.jsx, Landing.jsx, NotFound.jsx
│   │   ├── Dashboard.jsx
│   │   ├── ClientesPage.jsx, UsuariosPage.jsx,
│   │   ├── ComprasPage.jsx, LodgesPage.jsx, CircuitosPage.jsx,
│   │   ├── MantenimientoPage.jsx, RankingPage.jsx
│   │   ├── taquilla/
│   │   │   ├── NuevaCompraPage.jsx
│   │   │   ├── MisComprasPage.jsx
│   │   │   ├── TodasLasVentasPage.jsx
│   │   │   └── TaquillaClientesPage.jsx
│   │   └── tecnico/
│   │       └── KanbanPage.jsx
│   ├── hooks/
│   │   ├── useAuth.js          ← Context auth + login/logout
│   │   ├── useApi.js           ← Wrapper sobre Axios con loading/error
│   │   └── useDebounce.js
│   ├── router/
│   │   └── index.jsx           ← Definición de rutas + ProtectedRoute por rol
│   ├── styles/
│   │   └── globals.css         ← Tokens CSS + @import tailwindcss
│   ├── App.jsx
│   └── main.jsx
├── vercel.json                 ← Rewrite SPA + headers
├── vite.config.js              ← Proxy /api + alias @/
├── eslint.config.js
├── package.json
└── README.md

🛣 Routing y roles

El routing usa React Router 7 con un wrapper <ProtectedRoute> que valida el rol antes de renderizar.

Mapa de rutas

/                              → PublicLayout (Landing)
/login                         → Login (sin layout)
/reservar                      → PublicLayout (Reserva pública)

/dashboard                     → DashboardLayout · ADMIN
  ├── /clientes
  ├── /lodges
  ├── /circuitos
  ├── /usuarios
  ├── /mantenimiento
  └── /compras

/taquilla                      → DashboardLayout · TAQUILLA
  ├── /nueva-compra
  ├── /mis-compras
  ├── /todas-las-ventas
  └── /clientes

/tecnico                       → TecnicoLayout · TÉCNICO
  └── /kanban

ProtectedRoute

<ProtectedRoute allowedRoles={['ADMIN']}>
  <DashboardLayout>
    <UsuariosPage />
  </DashboardLayout>
</ProtectedRoute>

Si el usuario no tiene rol, redirección a /login. Si tiene un rol que no está en allowedRoles, redirección a su home por rol.


🧩 Arquitectura de componentes

Componentes atómicos reutilizables

  • <Button variant size> — variants: primary (rojo), secondary (outline), ghost, danger. Sizes: sm, md, lg.
  • <Input> — wrapper sobre <input> con focus ring de marca, error state, label embebido.
  • <Card> — superficie con bg-surface-1, border-border-strong, rounded-card.
  • <Badge variant dot> — pills de estado con punto de color opcional.
  • <KpiCard> — número grande con drop-shadow rojo signature.
  • <SkeletonCard count> — loader con animate-pulse.

Patrón de modales

Todos los modales del proyecto siguen el mismo patrón con framer-motion:

<AnimatePresence>
  {isOpen && (
    <motion.div
      className="fixed inset-0 z-50 flex items-end sm:items-center justify-center"
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    >
      <motion.div
        className="bg-surface-1 rounded-t-card sm:rounded-card max-h-[90vh] overflow-y-auto"
        initial={{ y: '100%' }}
        animate={{ y: 0 }}
        exit={{ y: '100%' }}
        transition={{ type: 'spring', damping: 25, stiffness: 300 }}
      >
        {/* contenido */}
      </motion.div>
    </motion.div>
  )}
</AnimatePresence>

Bottom-sheet en mobile, centrado en desktop.

Patrón de páginas de listado

<PageHeader title="Clientes" cta={<Button>Nuevo Cliente</Button>} />
<SearchAndFilters />
{isLoading
  ? <SkeletonList count={5} />
  : <>
      <table className="hidden md:block">...</table>   {/* desktop */}
      <motion.ul className="md:hidden">...</motion.ul>  {/* mobile cards con stagger */}
    </>
}
<Pagination />

🔌 Gestión de estado y data fetching

Estado global

  • Auth context (useAuth) — usuario actual, token JWT, login, logout, refresh.
  • Sin Redux ni Zustand — el estado global se reduce a auth; el resto se gestiona localmente con useState / useReducer.

Data fetching

  • Axios instance (lib/api.js) con interceptor que inyecta el JWT en cada request y maneja 401 (logout automático + redirect a /login).
  • Patrón ad-hoc con useEffect y useState({ data, loading, error }).
  • No se usa SWR ni React Query: el alcance del proyecto no lo justifica y mantiene la curva de aprendizaje baja para el tribunal.

Ejemplo

const [clientes, setClientes] = useState([]);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
  api.get('/clientes', { params: { page, search } })
    .then(res => setClientes(res.data.content))
    .catch(err => toast.error('Error al cargar clientes'))
    .finally(() => setIsLoading(false));
}, [page, search]);

📱 Responsive design

Breakpoints

Token Pixels Uso
sm: ≥ 640px Ajustes de spacing y tipografía
md: ≥ 768px Breakpoint principal mobile/desktop
lg: ≥ 1024px Grids multi-columna
xl: ≥ 1280px Dashboard a 3 columnas

Estrategia mobile-first

A partir de la versión v2.1.0, todo el código sigue una estrategia mobile-first: clases base aplican al mobile, los modificadores sm: / md: aplican a desktop. Esto evita el anti-patrón de "desktop primero con max-md: para mobile".

Patrones aplicados

  • Sidebar: drawer overlay con backdrop en mobile (<md), sidebar fijo en desktop (md:).
  • Tablas: <table className="hidden md:block"> + <ul className="md:hidden"> para reflow a cards.
  • Modales: items-end sm:items-center (bottom-sheet vs centrado).
  • Filtros: flex-wrap para wrap automático sin overflow.
  • Headers: flex-col sm:flex-row para stack vertical en mobile.

🚢 Despliegue

Vercel

El frontend se despliega automáticamente en Vercel con auto-deploy desde la rama main.

// vercel.json
{
  "rewrites": [{ "source": "/(.*)", "destination": "/" }]
}

El rewrite es necesario para que React Router gestione las rutas SPA sin que Vercel devuelva 404 en refresh.

Build

npm run build         # → dist/

El output (dist/) es estático y puede servirse desde cualquier CDN o servidor estático.


🏷 Versionado

Tag Hito
v1.0.0 Primera versión funcional con CRUDs básicos
v1.5.0 Sistema de auth + rutas protegidas por rol
v1.8.0 Dashboard con widgets y métricas
v2.0.0 Multi-técnico, roles TÉCNICO + TAQUILLA, paginación
v2.1.0 Responsive mobile completo + visual polish (micro-interactions, stagger animations, signature effects)

📝 Convenciones de Git

Mismas convenciones que el backend: Conventional Commits + GitFlow simplificado + merges --no-ff.

Tipo Uso
feat Nueva feature de UI
fix Bug visual o funcional
refactor Reorganización de componentes
chore Dependencias, config
style Cambios CSS sin lógica
docs README, comentarios

Workflow

git checkout -b feature/nombre-acotado
# trabajo + commits temáticos por área
git checkout main
git merge feature/nombre-acotado --no-ff
git tag -a vX.Y.Z -m "..."
git push origin main --tags

👤 Autor

Bryan Alejandro Paico AlbinesDesarrollo de Aplicaciones Web (DAW) · 2026


📄 Licencia

Proyecto académico desarrollado como Trabajo academico de Desarrollo de Aplicaciones Web. Uso no comercial.


Drive Arena · Conduce · Compite · Domina

About

Frontend for Drive Arena, a futuristic racing-themed park and resort web application. Includes activities, user profiles, rankings, and booking system.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors