---
name: frontend-conventions
description: Convenciones y patrones para el desarrollo frontend con React + Mantine
---
# Frontend Conventions
## Stack
- React 19 + TypeScript
- Vite 6.x
- Mantine v7 + TailwindCSS
- TanStack Query v5
- Zustand v5
- React Hook Form + Zod
## Estructura de Carpetas
```
frontend/src/
├── app/ # Configuración global
│ ├── providers.tsx # MantineProvider, QueryProvider, etc.
│ ├── router.tsx # React Router config
│ └── App.tsx
│
├── modules/ # Módulos de negocio
│ ├── core/ # Auth, Layout, Config
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── services/
│ │ └── stores/
│ │
│ └── delivery/ # Módulo principal
│ ├── components/ # Componentes del módulo
│ ├── hooks/ # Custom hooks (useViajes, etc.)
│ ├── services/ # API calls
│ ├── stores/ # Zustand stores
│ ├── types/ # TypeScript types
│ └── pages/ # Páginas/rutas
│
├── shared/ # Compartido entre módulos
│ ├── components/ # Componentes genéricos
│ ├── hooks/ # Hooks reutilizables
│ └── utils/ # Utilidades
│
└── lib/ # Configuraciones
├── api.ts # Cliente HTTP base
├── queryClient.ts # TanStack Query config
└── auth.ts # Better-Auth client
```
## Convenciones de Código
### Componentes
```tsx
// ✅ Correcto: Functional component con TypeScript
interface ViajeCardProps {
viaje: Viaje;
onEdit?: (id: string) => void;
}
export function ViajeCard({ viaje, onEdit }: ViajeCardProps) {
return (
{/* ... */}
);
}
```
### Hooks de TanStack Query
```tsx
// hooks/useViajes.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { viajesApi } from '../services/viajes.api';
export function useViajes(filters?: ViajesFilters) {
return useQuery({
queryKey: ['viajes', filters],
queryFn: () => viajesApi.getAll(filters),
});
}
export function useCreateViaje() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: viajesApi.create,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['viajes'] });
},
});
}
```
### Stores (Zustand)
```tsx
// stores/delivery.store.ts
import { create } from 'zustand';
interface DeliveryState {
selectedTurno: 'DIA' | 'NOCHE' | null;
setTurno: (turno: 'DIA' | 'NOCHE') => void;
}
export const useDeliveryStore = create((set) => ({
selectedTurno: null,
setTurno: (turno) => set({ selectedTurno: turno }),
}));
```
### API Services
```tsx
// services/viajes.api.ts
import { api } from '@/lib/api';
import type { Viaje, CreateViajeDto } from '../types/viaje.types';
export const viajesApi = {
getAll: (filters?: ViajesFilters) =>
api.get('/api/delivery/viajes', { params: filters }),
getById: (id: string) =>
api.get(`/api/delivery/viajes/${id}`),
create: (data: CreateViajeDto) =>
api.post('/api/delivery/viajes', data),
update: (id: string, data: Partial) =>
api.patch(`/api/delivery/viajes/${id}`, data),
delete: (id: string) =>
api.delete(`/api/delivery/viajes/${id}`),
};
```
## Mantine UI Patterns
### Usar componentes de Mantine, no HTML nativo
```tsx
// ❌ Incorrecto
// ✅ Correcto
```
### Tables con @mantine/core
```tsx
import { Table } from '@mantine/core';
Fecha
Motoquero
{viajes.map((viaje) => (
{viaje.fecha}
{viaje.motoquero.nombre}
))}
```
### Modales y Drawers
```tsx
import { useDisclosure } from '@mantine/hooks';
import { Modal, Drawer } from '@mantine/core';
function ViajesPage() {
const [opened, { open, close }] = useDisclosure(false);
return (
<>
>
);
}
```
## Formularios
Usar React Hook Form + Zod + Mantine:
```tsx
import { useForm, zodResolver } from '@mantine/form';
import { z } from 'zod';
const viajeSchema = z.object({
motoqueroId: z.string().min(1, 'Seleccione un motoquero'),
direcciones: z.array(z.string()).min(1, 'Ingrese al menos una dirección'),
});
function ViajeForm() {
const form = useForm({
initialValues: { motoqueroId: '', direcciones: [''] },
validate: zodResolver(viajeSchema),
});
return (
);
}
```
## Rutas
```tsx
// app/router.tsx
import { createBrowserRouter } from 'react-router-dom';
export const router = createBrowserRouter([
{
path: '/',
element: ,
children: [
{ index: true, element: },
{
path: 'delivery',
children: [
{ path: 'viajes', element: },
{ path: 'motoqueros', element: },
{ path: 'liquidaciones', element: },
],
},
{
path: 'config',
children: [
{ path: 'parametros', element: },
{ path: 'usuarios', element: },
],
},
],
},
{ path: '/login', element: },
]);
```