760 lines
18 KiB
Markdown
760 lines
18 KiB
Markdown
# Patrones de Frontend - GAMILIT
|
|
|
|
**Documento:** Analisis de Patrones de Frontend React + TypeScript
|
|
**Proyecto de Referencia:** GAMILIT
|
|
**Fecha:** 2025-11-23
|
|
**Analista:** Architecture-Analyst Agent
|
|
|
|
---
|
|
|
|
## 1. VISION GENERAL
|
|
|
|
GAMILIT implementa un **frontend moderno con React 18+, Vite 5+ y TypeScript 5+** siguiendo la arquitectura Feature-Sliced Design (FSD) con 180+ componentes reutilizables.
|
|
|
|
**Stack tecnologico:**
|
|
- **Framework:** React 18+ con hooks
|
|
- **Build Tool:** Vite 5+ (HMR rapido)
|
|
- **Lenguaje:** TypeScript 5+ (strict mode)
|
|
- **Styling:** Tailwind CSS 3+ (utility-first)
|
|
- **Router:** React Router v6
|
|
- **State:** Zustand (8 stores especializados)
|
|
- **Forms:** React Hook Form + Zod validation
|
|
- **HTTP:** Axios con interceptors
|
|
- **Animations:** Framer Motion
|
|
- **Testing:** Vitest + React Testing Library (13% coverage - GAP)
|
|
- **Docs:** Storybook 7+
|
|
|
|
**Metricas:**
|
|
- **LOC:** ~85,000 lineas (65% del proyecto)
|
|
- **Componentes:** 180+ componentes reutilizables
|
|
- **Paginas:** ~50 vistas
|
|
- **Hooks personalizados:** ~30
|
|
- **Zustand Stores:** 8 stores
|
|
- **Tests:** 15 tests (13% coverage - GAP CRITICO)
|
|
|
|
---
|
|
|
|
## 2. FEATURE-SLICED DESIGN (FSD)
|
|
|
|
### 2.1 Arquitectura de Capas
|
|
|
|
```
|
|
frontend/src/
|
|
├── shared/ # Capa compartida (180+ componentes)
|
|
│ ├── components/ # UI components genericos
|
|
│ ├── hooks/ # Custom React hooks
|
|
│ ├── utils/ # Utilidades generales
|
|
│ ├── types/ # Tipos TypeScript compartidos
|
|
│ └── constants/ # Constantes (sincronizadas con backend)
|
|
│
|
|
├── features/ # Features de negocio por dominio/rol
|
|
│ ├── student/ # Portal estudiante
|
|
│ ├── teacher/ # Portal profesor
|
|
│ └── admin/ # Portal administrador
|
|
│
|
|
├── pages/ # Paginas/Vistas (composicion de features)
|
|
│ ├── student/
|
|
│ ├── teacher/
|
|
│ └── admin/
|
|
│
|
|
├── services/ # Servicios externos
|
|
│ ├── api/ # API clients (Axios)
|
|
│ └── websocket/ # Socket.IO client
|
|
│
|
|
└── app/ # Capa de aplicacion
|
|
├── providers/ # Context providers
|
|
├── layouts/ # Layouts principales
|
|
└── router/ # Configuracion de rutas
|
|
```
|
|
|
|
### 2.2 Principios de FSD Aplicados
|
|
|
|
1. **Layered Architecture:**
|
|
- `shared` - Codigo reutilizable sin dependencias de negocio
|
|
- `features` - Logica de negocio por dominio
|
|
- `pages` - Composicion de features
|
|
- `app` - Configuracion global
|
|
|
|
2. **Public API:** Cada feature expone API publica via `index.ts`
|
|
|
|
3. **Low Coupling:** Features no dependen entre si
|
|
|
|
4. **High Cohesion:** Todo relacionado a una feature esta junto
|
|
|
|
### 2.3 Aplicabilidad a ERP Generico
|
|
|
|
⭐⭐⭐⭐ (ALTA)
|
|
|
|
**Decision:** ✅ **ADOPTAR Y ADAPTAR**
|
|
|
|
**Propuesta para ERP:**
|
|
```
|
|
frontend/src/
|
|
├── shared/ # Componentes reutilizables
|
|
├── features/
|
|
│ ├── administrator/ # Portal administrador
|
|
│ ├── accountant/ # Portal contador
|
|
│ ├── supervisor/ # Portal supervisor de obra
|
|
│ ├── purchaser/ # Portal comprador
|
|
│ └── hr/ # Portal RRHH
|
|
├── pages/
|
|
├── services/
|
|
└── app/
|
|
```
|
|
|
|
---
|
|
|
|
## 3. SHARED COMPONENTS (180+)
|
|
|
|
### 3.1 Categorias de Componentes
|
|
|
|
**1. Atomos (Elementos basicos):**
|
|
```
|
|
shared/components/atoms/
|
|
├── Button/
|
|
│ ├── Button.tsx
|
|
│ ├── Button.stories.tsx
|
|
│ ├── Button.test.tsx
|
|
│ └── index.ts
|
|
├── Input/
|
|
├── Label/
|
|
├── Badge/
|
|
├── Avatar/
|
|
└── Icon/
|
|
```
|
|
|
|
**2. Moleculas (Combinaciones de atomos):**
|
|
```
|
|
shared/components/molecules/
|
|
├── FormField/ # Label + Input + Error
|
|
├── SearchBar/ # Input + Icon + Button
|
|
├── UserCard/ # Avatar + Name + Badge
|
|
└── Notification/ # Icon + Title + Message
|
|
```
|
|
|
|
**3. Organismos (Componentes complejos):**
|
|
```
|
|
shared/components/organisms/
|
|
├── Navbar/
|
|
├── Sidebar/
|
|
├── DataTable/
|
|
├── Modal/
|
|
├── Dropdown/
|
|
└── Pagination/
|
|
```
|
|
|
|
**4. Templates (Layouts):**
|
|
```
|
|
shared/components/templates/
|
|
├── DashboardLayout/
|
|
├── AuthLayout/
|
|
├── SettingsLayout/
|
|
└── EmptyState/
|
|
```
|
|
|
|
### 3.2 Patron de Componente
|
|
|
|
**Ejemplo:** `shared/components/atoms/Button/Button.tsx`
|
|
|
|
```typescript
|
|
import { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
|
|
// Variants con Tailwind CSS
|
|
const buttonVariants = cva(
|
|
'inline-flex items-center justify-center rounded-md font-medium transition-colors',
|
|
{
|
|
variants: {
|
|
variant: {
|
|
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
|
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
|
|
outline: 'border border-gray-300 hover:bg-gray-100',
|
|
ghost: 'hover:bg-gray-100',
|
|
danger: 'bg-red-600 text-white hover:bg-red-700',
|
|
},
|
|
size: {
|
|
sm: 'h-8 px-3 text-sm',
|
|
md: 'h-10 px-4',
|
|
lg: 'h-12 px-6 text-lg',
|
|
},
|
|
fullWidth: {
|
|
true: 'w-full',
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
variant: 'primary',
|
|
size: 'md',
|
|
},
|
|
}
|
|
);
|
|
|
|
export interface ButtonProps
|
|
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
|
VariantProps<typeof buttonVariants> {
|
|
children: ReactNode;
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
export function Button({
|
|
children,
|
|
variant,
|
|
size,
|
|
fullWidth,
|
|
isLoading,
|
|
disabled,
|
|
className,
|
|
...props
|
|
}: ButtonProps) {
|
|
return (
|
|
<button
|
|
className={buttonVariants({ variant, size, fullWidth, className })}
|
|
disabled={disabled || isLoading}
|
|
{...props}
|
|
>
|
|
{isLoading ? <Spinner className="mr-2" /> : null}
|
|
{children}
|
|
</button>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 3.3 Aplicabilidad a ERP Generico
|
|
|
|
⭐⭐⭐⭐⭐ (MAXIMA)
|
|
|
|
**Decision:** ✅ **ADOPTAR COMPLETAMENTE**
|
|
|
|
**Componentes criticos para ERP:**
|
|
- Button, Input, Select, Checkbox (atomos)
|
|
- FormField, SearchBar, DateRangePicker (moleculas)
|
|
- DataTable, Modal, Sidebar, Navbar (organismos)
|
|
- DashboardLayout, ReportLayout (templates)
|
|
|
|
---
|
|
|
|
## 4. PATH ALIASES FRONTEND
|
|
|
|
### 4.1 Configuracion
|
|
|
|
**tsconfig.json:**
|
|
```json
|
|
{
|
|
"compilerOptions": {
|
|
"baseUrl": "./src",
|
|
"paths": {
|
|
"@/*": ["./*"],
|
|
"@shared/*": ["shared/*"],
|
|
"@components/*": ["shared/components/*"],
|
|
"@hooks/*": ["shared/hooks/*"],
|
|
"@utils/*": ["shared/utils/*"],
|
|
"@types/*": ["shared/types/*"],
|
|
"@services/*": ["services/*"],
|
|
"@app/*": ["app/*"],
|
|
"@features/*": ["features/*"],
|
|
"@pages/*": ["pages/*"]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**vite.config.ts:**
|
|
```typescript
|
|
import { defineConfig } from 'vite';
|
|
import react from '@vitejs/plugin-react';
|
|
import path from 'path';
|
|
|
|
export default defineConfig({
|
|
plugins: [react()],
|
|
resolve: {
|
|
alias: {
|
|
'@': path.resolve(__dirname, './src'),
|
|
'@shared': path.resolve(__dirname, './src/shared'),
|
|
'@components': path.resolve(__dirname, './src/shared/components'),
|
|
'@hooks': path.resolve(__dirname, './src/shared/hooks'),
|
|
'@utils': path.resolve(__dirname, './src/shared/utils'),
|
|
'@types': path.resolve(__dirname, './src/shared/types'),
|
|
'@services': path.resolve(__dirname, './src/services'),
|
|
'@app': path.resolve(__dirname, './src/app'),
|
|
'@features': path.resolve(__dirname, './src/features'),
|
|
'@pages': path.resolve(__dirname, './src/pages'),
|
|
},
|
|
},
|
|
});
|
|
```
|
|
|
|
### 4.2 Uso
|
|
|
|
```typescript
|
|
// ❌ Sin aliases
|
|
import { Button } from '../../../shared/components/atoms/Button';
|
|
import { useAuth } from '../../../shared/hooks/useAuth';
|
|
|
|
// ✅ Con aliases
|
|
import { Button } from '@components/atoms/Button';
|
|
import { useAuth } from '@hooks/useAuth';
|
|
```
|
|
|
|
### 4.3 Aplicabilidad a ERP Generico
|
|
|
|
⭐⭐⭐⭐⭐ (MAXIMA)
|
|
|
|
**Decision:** ✅ **ADOPTAR COMPLETAMENTE**
|
|
|
|
---
|
|
|
|
## 5. STATE MANAGEMENT CON ZUSTAND
|
|
|
|
### 5.1 Los 8 Stores de GAMILIT
|
|
|
|
```
|
|
stores/
|
|
├── useAuthStore.ts # Autenticacion (user, token, logout)
|
|
├── useGamificationStore.ts # Gamificacion (XP, coins, logros)
|
|
├── useProgressStore.ts # Progreso de aprendizaje
|
|
├── useExerciseStore.ts # Estado de ejercicios actuales
|
|
├── useNotificationStore.ts # Notificaciones en tiempo real
|
|
├── useSocialStore.ts # Features sociales
|
|
├── useTenantStore.ts # Multi-tenancy
|
|
└── useUIStore.ts # Estado UI (modals, sidebar, theme)
|
|
```
|
|
|
|
### 5.2 Patron de Store (ejemplo: useAuthStore)
|
|
|
|
```typescript
|
|
import { create } from 'zustand';
|
|
import { persist } from 'zustand/middleware';
|
|
|
|
interface AuthState {
|
|
// Estado
|
|
user: User | null;
|
|
token: string | null;
|
|
isAuthenticated: boolean;
|
|
|
|
// Acciones
|
|
login: (email: string, password: string) => Promise<void>;
|
|
logout: () => void;
|
|
refreshToken: () => Promise<void>;
|
|
updateProfile: (data: Partial<User>) => Promise<void>;
|
|
}
|
|
|
|
export const useAuthStore = create<AuthState>()(
|
|
persist(
|
|
(set, get) => ({
|
|
// Estado inicial
|
|
user: null,
|
|
token: null,
|
|
isAuthenticated: false,
|
|
|
|
// Acciones
|
|
login: async (email, password) => {
|
|
const response = await authApi.login({ email, password });
|
|
set({
|
|
user: response.user,
|
|
token: response.token,
|
|
isAuthenticated: true,
|
|
});
|
|
},
|
|
|
|
logout: () => {
|
|
set({
|
|
user: null,
|
|
token: null,
|
|
isAuthenticated: false,
|
|
});
|
|
},
|
|
|
|
refreshToken: async () => {
|
|
const { token } = get();
|
|
const response = await authApi.refresh(token);
|
|
set({ token: response.token });
|
|
},
|
|
|
|
updateProfile: async (data) => {
|
|
const { user } = get();
|
|
const updated = await userApi.update(user!.id, data);
|
|
set({ user: updated });
|
|
},
|
|
}),
|
|
{
|
|
name: 'auth-storage', // Key en localStorage
|
|
partialize: (state) => ({
|
|
token: state.token,
|
|
user: state.user,
|
|
}),
|
|
}
|
|
)
|
|
);
|
|
```
|
|
|
|
### 5.3 Uso en Componentes
|
|
|
|
```typescript
|
|
function UserProfile() {
|
|
const { user, logout, updateProfile } = useAuthStore();
|
|
|
|
if (!user) return <LoginPrompt />;
|
|
|
|
return (
|
|
<div>
|
|
<h1>Welcome, {user.name}!</h1>
|
|
<button onClick={logout}>Logout</button>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 5.4 Aplicabilidad a ERP Generico
|
|
|
|
⭐⭐⭐⭐⭐ (MAXIMA)
|
|
|
|
**Decision:** ✅ **ADOPTAR COMPLETAMENTE**
|
|
|
|
**Stores propuestos para ERP:**
|
|
```
|
|
stores/
|
|
├── useAuthStore.ts # Autenticacion
|
|
├── useCompanyStore.ts # Empresa actual (multi-tenant)
|
|
├── useProjectStore.ts # Proyecto actual
|
|
├── useBudgetStore.ts # Presupuesto actual
|
|
├── useNotificationStore.ts # Notificaciones
|
|
├── useUIStore.ts # Estado UI
|
|
└── usePermissionsStore.ts # Permisos del usuario
|
|
```
|
|
|
|
---
|
|
|
|
## 6. CUSTOM HOOKS (~30)
|
|
|
|
### 6.1 Categorias de Hooks
|
|
|
|
**1. Hooks de Estado:**
|
|
```typescript
|
|
// hooks/useLocalStorage.ts
|
|
export function useLocalStorage<T>(key: string, initialValue: T) {
|
|
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
try {
|
|
const item = window.localStorage.getItem(key);
|
|
return item ? JSON.parse(item) : initialValue;
|
|
} catch {
|
|
return initialValue;
|
|
}
|
|
});
|
|
|
|
const setValue = (value: T | ((val: T) => T)) => {
|
|
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
setStoredValue(valueToStore);
|
|
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
};
|
|
|
|
return [storedValue, setValue] as const;
|
|
}
|
|
```
|
|
|
|
**2. Hooks de Fetch:**
|
|
```typescript
|
|
// hooks/useQuery.ts
|
|
export function useQuery<T>(
|
|
queryFn: () => Promise<T>,
|
|
deps: any[] = []
|
|
) {
|
|
const [data, setData] = useState<T | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<Error | null>(null);
|
|
|
|
useEffect(() => {
|
|
setLoading(true);
|
|
queryFn()
|
|
.then(setData)
|
|
.catch(setError)
|
|
.finally(() => setLoading(false));
|
|
}, deps);
|
|
|
|
return { data, loading, error, refetch: () => queryFn().then(setData) };
|
|
}
|
|
```
|
|
|
|
**3. Hooks de Utilidad:**
|
|
```typescript
|
|
// hooks/useDebounce.ts
|
|
export function useDebounce<T>(value: T, delay: number): T {
|
|
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
|
|
useEffect(() => {
|
|
const handler = setTimeout(() => setDebouncedValue(value), delay);
|
|
return () => clearTimeout(handler);
|
|
}, [value, delay]);
|
|
|
|
return debouncedValue;
|
|
}
|
|
```
|
|
|
|
### 6.2 Aplicabilidad a ERP Generico
|
|
|
|
⭐⭐⭐⭐⭐ (MAXIMA)
|
|
|
|
**Decision:** ✅ **ADOPTAR COMPLETAMENTE**
|
|
|
|
**Hooks criticos para ERP:**
|
|
- `useQuery` - Fetching de datos
|
|
- `useMutation` - Operaciones CRUD
|
|
- `useDebounce` - Busquedas optimizadas
|
|
- `useLocalStorage` - Persistencia local
|
|
- `usePermissions` - Verificacion de permisos
|
|
|
|
---
|
|
|
|
## 7. FORMS CON REACT HOOK FORM + ZOD
|
|
|
|
### 7.1 Patron de Form
|
|
|
|
```typescript
|
|
import { useForm } from 'react-hook-form';
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { z } from 'zod';
|
|
|
|
// Schema de validacion con Zod
|
|
const loginSchema = z.object({
|
|
email: z.string().email('Email invalido'),
|
|
password: z.string().min(8, 'Minimo 8 caracteres'),
|
|
});
|
|
|
|
type LoginFormData = z.infer<typeof loginSchema>;
|
|
|
|
export function LoginForm() {
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
formState: { errors, isSubmitting },
|
|
} = useForm<LoginFormData>({
|
|
resolver: zodResolver(loginSchema),
|
|
});
|
|
|
|
const onSubmit = async (data: LoginFormData) => {
|
|
await authApi.login(data);
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit(onSubmit)}>
|
|
<FormField
|
|
label="Email"
|
|
error={errors.email?.message}
|
|
{...register('email')}
|
|
/>
|
|
|
|
<FormField
|
|
label="Password"
|
|
type="password"
|
|
error={errors.password?.message}
|
|
{...register('password')}
|
|
/>
|
|
|
|
<Button type="submit" isLoading={isSubmitting}>
|
|
Login
|
|
</Button>
|
|
</form>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 7.2 Aplicabilidad a ERP Generico
|
|
|
|
⭐⭐⭐⭐⭐ (MAXIMA)
|
|
|
|
**Decision:** ✅ **ADOPTAR COMPLETAMENTE**
|
|
|
|
---
|
|
|
|
## 8. API CLIENTS CON AXIOS
|
|
|
|
### 8.1 Configuracion de Axios Instance
|
|
|
|
```typescript
|
|
// services/api/axios-instance.ts
|
|
import axios from 'axios';
|
|
import { useAuthStore } from '@stores/useAuthStore';
|
|
|
|
const api = axios.create({
|
|
baseURL: import.meta.env.VITE_API_URL,
|
|
timeout: 10000,
|
|
});
|
|
|
|
// Interceptor de request (agregar token)
|
|
api.interceptors.request.use((config) => {
|
|
const { token } = useAuthStore.getState();
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`;
|
|
}
|
|
return config;
|
|
});
|
|
|
|
// Interceptor de response (refresh token)
|
|
api.interceptors.response.use(
|
|
(response) => response,
|
|
async (error) => {
|
|
if (error.response?.status === 401) {
|
|
const { refreshToken, logout } = useAuthStore.getState();
|
|
try {
|
|
await refreshToken();
|
|
return api.request(error.config);
|
|
} catch {
|
|
logout();
|
|
}
|
|
}
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
export default api;
|
|
```
|
|
|
|
### 8.2 API Clients por Modulo
|
|
|
|
```typescript
|
|
// services/api/auth.api.ts
|
|
import api from './axios-instance';
|
|
import { API_ENDPOINTS } from '@shared/constants';
|
|
|
|
export const authApi = {
|
|
login: (data: LoginDto) =>
|
|
api.post(API_ENDPOINTS.AUTH.LOGIN, data).then((r) => r.data),
|
|
|
|
register: (data: RegisterDto) =>
|
|
api.post(API_ENDPOINTS.AUTH.REGISTER, data).then((r) => r.data),
|
|
|
|
logout: () =>
|
|
api.post(API_ENDPOINTS.AUTH.LOGOUT).then((r) => r.data),
|
|
|
|
refresh: (token: string) =>
|
|
api.post(API_ENDPOINTS.AUTH.REFRESH, { token }).then((r) => r.data),
|
|
};
|
|
```
|
|
|
|
### 8.3 Aplicabilidad a ERP Generico
|
|
|
|
⭐⭐⭐⭐⭐ (MAXIMA)
|
|
|
|
**Decision:** ✅ **ADOPTAR COMPLETAMENTE**
|
|
|
|
---
|
|
|
|
## 9. TESTING PATTERNS (GAP CRITICO)
|
|
|
|
### 9.1 Metricas de Testing
|
|
|
|
| Metrica | Actual | Objetivo | Gap |
|
|
|---------|--------|----------|-----|
|
|
| **Tests Frontend** | 15 | 60 | **-45 (75%)** |
|
|
| **Coverage** | 13% | 70% | **-57pp** |
|
|
|
|
**Critica:** Coverage extremadamente bajo. NO copiar este anti-patron.
|
|
|
|
### 9.2 Ejemplo de Test
|
|
|
|
```typescript
|
|
// components/Button/Button.test.tsx
|
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
import { Button } from './Button';
|
|
|
|
describe('Button', () => {
|
|
it('renders with children', () => {
|
|
render(<Button>Click me</Button>);
|
|
expect(screen.getByText('Click me')).toBeInTheDocument();
|
|
});
|
|
|
|
it('calls onClick when clicked', () => {
|
|
const onClick = vi.fn();
|
|
render(<Button onClick={onClick}>Click</Button>);
|
|
fireEvent.click(screen.getByText('Click'));
|
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('shows loading state', () => {
|
|
render(<Button isLoading>Loading</Button>);
|
|
expect(screen.getByRole('button')).toBeDisabled();
|
|
});
|
|
});
|
|
```
|
|
|
|
### 9.3 Recomendacion para ERP Generico
|
|
|
|
⭐⭐⭐⭐⭐ (MAXIMA - CRITICO)
|
|
|
|
**Decision:** ✅ **IMPLEMENTAR TESTING DESDE EL INICIO**
|
|
|
|
**Objetivos:**
|
|
- **Coverage:** 70%+ desde el inicio
|
|
- **Tests por componente:** Unit tests
|
|
- **Integration tests:** Features completas
|
|
- **E2E tests:** Flujos criticos (Playwright/Cypress)
|
|
|
|
---
|
|
|
|
## 10. MATRIZ DE DECISION
|
|
|
|
### 10.1 ADOPTAR COMPLETAMENTE ✅
|
|
|
|
| Patron | Prioridad |
|
|
|--------|-----------|
|
|
| Feature-Sliced Design (FSD) | P0 |
|
|
| Shared Components (180+) | P0 |
|
|
| Path Aliases | P0 |
|
|
| Zustand State Management | P0 |
|
|
| Custom Hooks | P0 |
|
|
| React Hook Form + Zod | P0 |
|
|
| Axios Interceptors | P0 |
|
|
| Tailwind CSS | P1 |
|
|
| Storybook | P1 |
|
|
|
|
### 10.2 MEJORAR 🔧
|
|
|
|
| Patron | Estado Actual | Mejora | Prioridad |
|
|
|--------|---------------|--------|-----------|
|
|
| Testing | 13% coverage | 70%+ coverage | P0 CRITICO |
|
|
| Type Safety | Parcial | Completa | P1 |
|
|
| E2E Tests | Inexistentes | Playwright/Cypress | P1 |
|
|
|
|
### 10.3 EVITAR ❌
|
|
|
|
| Patron | Razon |
|
|
|--------|-------|
|
|
| Coverage bajo (13%) | Inaceptable para produccion |
|
|
| Sin E2E tests | Flujos criticos sin validar |
|
|
|
|
---
|
|
|
|
## 11. PROPUESTA PARA ERP GENERICO
|
|
|
|
```
|
|
frontend/src/
|
|
├── shared/
|
|
│ ├── components/ # 100+ componentes reutilizables
|
|
│ ├── hooks/ # Custom hooks
|
|
│ └── constants/ # Constantes (sync con backend)
|
|
│
|
|
├── features/
|
|
│ ├── administrator/ # Portal admin
|
|
│ ├── accountant/ # Portal contador
|
|
│ ├── supervisor/ # Portal supervisor
|
|
│ ├── purchaser/ # Portal compras
|
|
│ └── hr/ # Portal RRHH
|
|
│
|
|
├── pages/
|
|
├── services/
|
|
│ └── api/
|
|
│ ├── budgets.api.ts
|
|
│ ├── purchasing.api.ts
|
|
│ └── projects.api.ts
|
|
│
|
|
└── app/
|
|
├── providers/
|
|
├── layouts/
|
|
└── router/
|
|
```
|
|
|
|
---
|
|
|
|
**Documento creado:** 2025-11-23
|
|
**Version:** 1.0
|
|
**Estado:** Completado
|
|
**Proximo documento:** `ssot-system.md`
|