erp-core/docs/01-analisis-referencias/gamilit/frontend-patterns.md

18 KiB

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

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:

{
  "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:

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

// ❌ 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)

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

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:

// 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:

// 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:

// 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

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

// 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

// 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

// 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