Skip to main content
This document provides a comprehensive overview of the TradeMaster Transactions (TMT) application architecture, including the React structure, Redux state management, routing patterns, authentication flow, and key architectural decisions.

Technology Stack

React 18

Modern React with hooks and concurrent features

Redux Toolkit

State management with Redux Persist

Firebase

Authentication, Firestore, Cloud Functions, Storage

Material-UI 5

Component library and theming

Vite

Fast build tool and dev server

React Router 6

Client-side routing

Core Dependencies

package.json
{
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "@reduxjs/toolkit": "1.8.3",
    "redux-persist": "^6.0.0",
    "firebase": "^9.5.0",
    "@mui/material": "5.10.16",
    "react-router-dom": "6.3.0",
    "formik": "2.2.9",
    "yup": "^0.32.11",
    "axios": "0.27.2",
    "@casl/ability": "^6.7.1"
  }
}

Application Entry Point

The application bootstraps through a clear initialization chain:
src/main.jsx:18-42
import { Provider } from "react-redux";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { PersistGate } from 'redux-persist/integration/react';
import { AuthProvider } from "src/guards/firebase/FirebaseContext";
import store, { persistor } from "./store/Store";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")).render(
  <Provider store={store}>
    <Suspense fallback={<Spinner />}>
      <PersistGate loading={null} persistor={persistor}>
        <BrowserRouter>
          <ToastContainer />
          <AuthProvider>
            <App />
          </AuthProvider>
        </BrowserRouter>
      </PersistGate>
    </Suspense>
  </Provider>
);
Initialization Flow:
1

Redux Provider

Wraps the entire app with Redux store access
2

PersistGate

Rehydrates persisted state from localStorage before rendering
3

BrowserRouter

Enables client-side routing
4

AuthProvider

Initializes Firebase authentication listener
5

App Component

Renders the main application with routing and theme

Project Structure

src/
├── App.jsx                    # Root component with routing
├── main.jsx                   # Application entry point
├── components/               
│   ├── apps/                  # Feature-specific components
│   │   ├── events/           # Event management components
│   │   ├── Portals/          # Portal configuration
│   │   ├── contracts/        # Contract forms and displays
│   │   ├── bank-documents/   # Financial documents
│   │   └── ...               # Other feature modules
│   ├── dashboards/           # Dashboard widgets
│   ├── forms/                # Reusable form elements
│   └── shared/               # Shared UI components
├── views/                    
│   ├── authentication/       # Login, register, forgot password
│   ├── dashboard/            # Dashboard pages
│   ├── events/              # Event management views
│   ├── Users/               # User management (staff, clients)
│   ├── Tickets/             # Ticket views and search
│   ├── offices/             # Box office management
│   └── ...                  # Other view pages
├── store/                   
│   ├── Store.js             # Redux store configuration
│   ├── apps/                # Feature slices
│   │   ├── auth/           # Authentication slice
│   │   ├── events/         # Events slice
│   │   ├── tickets/        # Tickets slice
│   │   ├── Users/          # User management slices
│   │   └── ...             # 26+ feature slices
│   ├── customizer/         # UI customization state
│   └── setup/              # Platform setup configuration
├── guards/                  
│   ├── firebase/           # Firebase configuration & exports
│   ├── authGuard/          # Route protection HOCs
│   └── contexts/           # CASL ability definitions
├── routes/                 
│   └── Router.js           # Route configuration
├── layouts/                
│   ├── full/              # Main layout with sidebar
│   └── blank/             # Minimal layout for auth
├── theme/                  # MUI theme configuration
└── utils/                  # Utility functions and hooks

Redux State Management

Store Configuration

The Redux store is configured with Redux Toolkit and Redux Persist:
src/store/Store.js:45-95
import { configureStore } from '@reduxjs/toolkit';
import { persistReducer, persistStore } from 'redux-persist';
import storage from 'redux-persist/lib/storage';

const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['auth', 'customizer', 'setup'] // Only persist these slices
};

const rootReducer = combineReducers({
  customizer: CustomizerReducer,
  setup: SetupReducer,
  staff: StaffReducer,
  collaborators: CollaboratorsReducer,
  clients: ClientsReducer,
  customers: CustomerReducer,
  contracts: ContractReducer,
  addendums: AddendumReducer,
  tickets: TicketReducer,
  queryTickets: QueryTicketReducer,
  auth: authReducer,
  eventVenues: EventsVenuesReduces,
  events: EventsReduces,
  credentials: CredentialsReduces,
  Transactions: TransactionsReducer,
  offices: OfficesReducer,
  officeEvents: OfficeEventReducer,
  officeTransactions: OfficeTransactionReducer,
  paymentsMethod: PaymentsMethodsReducer,
  payouts: PayoutsReducer,
  custodyAccounts: CustodyAccountsReducer,
  orders: OrdersReducer,
  officeCollaborators: ActiveOfficeCollaboratorsReducer,
  contractsLegal: ContractsLegalReducer,
  ordersPayout: OrdersPayoutReducer,
  portals: PortalsReducer,
  documents: DocumentsReducer,
  marketing: MarketingReducer,
});

const persistedReducer = persistReducer(persistConfig, rootReducer);

export const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    }),
});

export const persistor = persistStore(store);

State Structure

The application manages 26+ Redux slices, each handling a specific domain:
interface RootState {
  // UI State (Persisted)
  customizer: {
    activeMode: 'light' | 'dark';
    activeTheme: string;
    isCollapse: boolean;
    isMobileSidebar: boolean;
    isHorizontal: boolean;
  };
  
  // Authentication (Persisted)
  auth: {
    isAuthenticated: boolean;
    user: {
      uid: string;
      email: string;
      name: string;
      account_type: 'Administrador' | 'Cliente' | 'Coordinador' | 'Contador' | 'Soporte';
      avatar: string | null;
      phone: string;
      address: object;
    } | null;
  };
  
  // Platform Configuration (Persisted)
  setup: {
    Setup: {
      event_type: { data: string[] };
      // Other platform settings
    };
  };
  
  // Feature States (Not Persisted)
  events: {
    Events: Event[];
    ActiveEvents: Event[];
    ClientEvents: Event[];
    EventsSearch: string;
  };
  
  tickets: {
    Tickets: Ticket[];
    // Ticket state
  };
  
  // ... 20+ other feature slices
}

Example Slice: Events

src/store/apps/events/EventsSlice.js:1-43
import { createSlice } from '@reduxjs/toolkit';
import { Firestore } from '../../../guards/firebase/Firebase';
import moment from 'moment';

const initialState = {
  Events: [],
  ActiveEvents: [],
  ClientEvents: [],
  EventsSearch: '',
  error: ''
};

export const EventsSlice = createSlice({
  name: 'Events',
  initialState,
  reducers: {
    hasError(state, action) {
      state.error = action.payload;
    },
    getEvents: (state, action) => {
      state.Events = action.payload;
    },
    getActiveEvents: (state, action) => {
      state.ActiveEvents = action.payload;
    },
    getClientEvents: (state, action) => {
      state.ClientEvents = action.payload;
    },
    SearchEvents: (state, action) => {
      state.EventsSearch = action.payload;
    }
  },
});

export const {
  hasError,
  getEvents,
  getActiveEvents,
  getClientEvents,
  SearchEvents,
} = EventsSlice.actions;
Async Thunk for Data Fetching:
src/store/apps/events/EventsSlice.js:45-81
export const fetchEvents = (id) => async (dispatch) => {
  try {
    const querySnapshot = id 
      ? await Firestore.collection('events').where("venue_id", "==", id).get() 
      : await Firestore.collection('events').get();

    const EventsArray = querySnapshot.docs.map((doc) => {
      const data = doc.data();

      const convertTimestamp = (timestamp) => {
        if (timestamp && timestamp.seconds) {
          const date = new Date(timestamp.seconds * 1000 + timestamp.nanoseconds / 1000000);
          return moment(date).format('DD/MM/YYYY - hh:mm:ss a');
        }
        return null;
      };

      const date = {
        created: convertTimestamp(data.date?.created),
        updated: convertTimestamp(data.date?.updated),
      };

      const date_end = convertTimestamp(data.date_end);
      const date_start = convertTimestamp(data.date_start);

      return {
        id: doc.id,
        ...data,
        date,
        date_end,
        date_start
      };
    });

    dispatch(getEvents(EventsArray));
  } catch (error) {
    dispatch(hasError(error));
  }
};
All slices follow a similar pattern: initial state, synchronous reducers, and async thunks for Firestore operations.

Routing Architecture

React Router 6 provides declarative routing with nested routes and route guards.

Route Configuration

src/routes/Router.js:116-246
import { Navigate } from 'react-router-dom';
import AuthGuard from 'src/guards/authGuard/AuthGuard';
import GuestGuard from 'src/guards/authGuard/GuestGaurd';
import PermissionGuard from '../guards/authGuard/PermissionGuard';

const Router = [
  {
    path: '/',
    element: (
      <AuthGuard>
        <FullLayout />
      </AuthGuard>
    ),
    children: [
      { path: '/', element: <Navigate to="/dashboards/modern" /> },
      { path: '/dashboards/modern', element: <ModernDash /> },
      
      // Events with permission guards
      { 
        path: '/eventos', 
        element: (
          <PermissionGuard action="view" subject="ViewEvents">
            <EventsList />
          </PermissionGuard>
        ) 
      },
      { 
        path: '/eventos-crear', 
        element: (
          <PermissionGuard action="view" subject="ViewEventsCreate">
            <NewEvents />
          </PermissionGuard>
        ) 
      },
      
      // Tickets
      { 
        path: '/tickets', 
        element: (
          <PermissionGuard action="view" subject="ViewTickets">
            <TicketTable />
          </PermissionGuard>
        ) 
      },
      
      // ... 50+ more routes
    ],
  },
  {
    path: '/auth',
    element: (
      <GuestGuard>
        <BlankLayout />
      </GuestGuard>
    ),
    children: [
      { path: '/auth/login', element: <Login /> },
      { path: '/auth/forgot-password', element: <ForgotPassword /> },
      { path: '/auth/register', element: <Register /> },
    ],
  },
  {
    path: '/auth',
    element: <BlankLayout />,
    children: [
      { path: '404', element: <Error /> },
      { path: 'permissions', element: <PermissionsValidator /> },
    ],
  },
];

export default Router;

Route Guards

1. AuthGuard - Protects authenticated routes:
src/guards/authGuard/AuthGuard.js:5-19
import { useNavigate } from 'react-router-dom';
import useAuth from './UseAuth';
import { useEffect } from 'react';

const AuthGuard = ({ children }) => {
  const { isAuthenticated } = useAuth();
  const navigate = useNavigate();

  useEffect(() => {
    if (!isAuthenticated) {
      navigate('/auth/login', { replace: true });
    }
  }, [isAuthenticated, navigate]);

  return children;
};
2. PermissionGuard - Enforces CASL ability checks:
src/guards/authGuard/PermissionGuard.js:1-18
import React from "react";
import { useNavigate } from 'react-router-dom';
import { AbilityContext } from "../contexts/AbilityContext";

const PermissionGuard = ({ children, action, subject }) => {
    const ability = React.useContext(AbilityContext);
    const navigate = useNavigate();

    React.useEffect(() => {
        if (!ability.can(action, subject)) {
            navigate("/auth/permissions", { replace: true });
        }
    }, [navigate]);

    return children;
};
Always wrap protected routes with PermissionGuard to prevent unauthorized access to features.

Authentication & Authorization

Firebase Authentication

Authentication is managed through Firebase with custom authorization logic:
src/guards/firebase/Firebase.js:10-17
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
import 'firebase/compat/storage';
import { getFunctions, httpsCallable } from 'firebase/functions';

const firebaseConfig = {
  apiKey: "-",
  authDomain: "-",
  projectId: "-",
  storageBucket: "-",
  messagingSenderId: "-",
  appId: "-"
};

const app = firebase.initializeApp(firebaseConfig);
const Firestore = firebase.firestore();
const Storage = getStorage(app);
const functions = getFunctions(app);

Auth Context and State Management

src/guards/firebase/FirebaseContext.js:22-72
export const AuthProvider = ({ children }) => {
  const dispatch = useDispatch();
  const auth = useSelector((state) => state.auth);

  useEffect(() => {
    const unsubscribe = firebase.auth().onAuthStateChanged(async (user) => {
      if (user) {
        // Fetch user data from Firestore
        const querySnapshot = await Firestore
          .collection('u_clients')
          .doc(user.uid)
          .get();

        // Check if user is active
        if (querySnapshot.data()?.status !== true) {
          logout();
        } else {
          // Get user's IP address
          const queryIP = await fetch('https://ipapi.co/json/');
          const data = await queryIP.json();

          // Update last access timestamp
          await Firestore.collection('u_clients').doc(user.uid).update({
            "date.last_access": firebase.firestore.FieldValue.serverTimestamp(),
            last_IP: data.ip
          });

          // Fetch platform setup configuration
          dispatch(fetchSetup());
          
          // Update auth state
          dispatch(authStateChanged({
            isAuthenticated: true,
            user: {
              uid: user.uid,
              avatar: querySnapshot.data()?.image ?? null,
              account_type: querySnapshot.data()?.account_type,
              phone: querySnapshot.data()?.phone ?? "",
              address: querySnapshot.data()?.address ?? {},
              email: user.email,
              country_calling_code: data.country_calling_code,
              name: querySnapshot.data()?.name,
              ...querySnapshot.data()
            },
          }));
        }
      } else {
        dispatch(authStateChanged({
          isAuthenticated: false,
          user: null,
        }));
      }
    });

    return () => unsubscribe();
  }, [dispatch]);

  // Auth methods
  const signin = (email, password) =>
    firebase.auth().signInWithEmailAndPassword(email, password);

  const logout = () => firebase.auth().signOut();

  return (
    <AuthContext.Provider value={{ ...auth, signin, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

Permission System (CASL)

Permissions are defined using CASL (Code Access Security Layer):
src/guards/contexts/DefineAbilities.js:1-237
import { AbilityBuilder, Ability } from '@casl/ability';

export function defineAbilitiesFor(user) {
    const { can, cannot, build } = new AbilityBuilder(Ability);

    if (!user) return build();

    const commonPermissions = {
        'Coordinador': {
            can: [
                ['create', 'usersClients'],
                ['edit', 'usersClients'],
                ['create', 'events'],
                ['view', 'ViewEvents'],
                ['view', 'ViewEventsCreate'],
                ['view', 'ViewTickets'],
                // ... more permissions
            ],
            cannot: [
                ['create', 'usersStaff'],
                ['view', 'ViewPayouts'],
                ['change', 'eventsStatus'],
                // ... restricted actions
            ]
        },
        'Contador': {
            can: [
                ['view', 'ViewClients'],
                ['view', 'ViewContracts'],
                ['view', 'ViewEvents'],
                // ... accountant permissions
            ],
            cannot: [
                ['create', 'events'],
                ['view', 'ViewEventsCreate'],
                // ... restricted actions
            ]
        },
        'Soporte': {
            can: [
                ['view', 'ViewEvents'],
                ['view', 'ViewTicketsDetail'],
                // ... support permissions (mostly read-only)
            ],
            cannot: [
                ['create', 'events'],
                ['edit', 'events'],
                // ... restricted actions
            ]
        }
    };

    if (user.account_type === 'Administrador' || user.account_type === 'Cliente') {
        can('manage', 'all'); // Full access
    } else if (commonPermissions[user.account_type]) {
        const permissions = commonPermissions[user.account_type];
        permissions.can?.forEach(([action, subject]) => can(action, subject));
        permissions.cannot?.forEach(([action, subject]) => cannot(action, subject));
    }

    return build();
};
Usage in App Component:
src/App.jsx:13-34
import { AbilityContext } from './guards/contexts/AbilityContext';
import { defineAbilitiesFor } from './guards/contexts/DefineAbilities';

function App() {
  const { user } = useAuth();
  const ability = defineAbilitiesFor(user);
  const routing = useRoutes(Router);
  const theme = ThemeSettings();

  React.useEffect(() => {
    ability.update(defineAbilitiesFor(user).rules);
  }, [user]);

  return (
    <ThemeProvider theme={theme}>
      <AbilityContext.Provider value={ability}>
        <RTL direction={customizer.activeDir}>
          <CssBaseline />
          <ScrollToTop>{routing}</ScrollToTop>
        </RTL>
      </AbilityContext.Provider>
    </ThemeProvider>
  );
}
CASL abilities are updated dynamically when the user object changes, ensuring permissions are always in sync.

Firebase Integration

Cloud Functions

The application leverages Firebase Cloud Functions for serverless operations:
src/guards/firebase/Firebase.js:54-85
const functions = getFunctions(app);

// User Management
export const validateUserPlatform = httpsCallable(functions, 'validate_user_platform');
export const create_staff = httpsCallable(functions, 'create_staff');
export const create_client = httpsCallable(functions, 'create_client');
export const create_collaborator = httpsCallable(functions, 'create_collaborator');

// Ticket Operations
export const tickets_generate = httpsCallable(functions, 'tickets_generate');
export const tickets_list_event = httpsCallable(functions, 'tickets_list_event');
export const tickets_individual = httpsCallable(functions, 'tickets_individual');

// Office Management
export const office_pin_set = httpsCallable(functions, 'office_pin_set');

// Analytics & Reporting
export const sold_by_office = httpsCallable(functions, 'sold_by_office');
export const number_tickets_vs_sold = httpsCallable(functions, 'number_tickets_vs_sold');
export const money_distribution = httpsCallable(functions, 'money_distribution');
export const offices_vs_offices_active = httpsCallable(functions, 'offices_vs_offices_active');
export const general_platform_data = httpsCallable(functions, 'general_platform_data');
export const types_of_collaborators = httpsCallable(functions, 'types_of_collaborators');
export const sold_by_usbs = httpsCallable(functions, 'sold_by_usbs');
export const total_sales = httpsCallable(functions, 'total_sales');

// Offline Tickets
export const offline_tickets_list_event_sales = httpsCallable(functions, 'offline_tickets_list_event_sales');
export const offline_office_tickets_list_sales = httpsCallable(functions, 'offline_office_tickets_list_sales');
export const offline_office_tickets = httpsCallable(functions, 'offline_office_tickets');
export const offline_office_tickets_unassign = httpsCallable(functions, 'offline_office_tickets_unassign');

// Reconciliation
export const conciliation_data_banco = httpsCallable(functions, 'conciliation_data_banco');
export const conciliation_data_cia = httpsCallable(functions, 'conciliation_data_cia');
export const conciliation_data = httpsCallable(functions, 'conciliation_data');
export const noconciliation_data_b = httpsCallable(functions, 'noconciliation_data_b');
export const noconciliation_data_cia = httpsCallable(functions, 'noconciliation_data_cia');
export const conciliation_load_bank_manual = httpsCallable(functions, 'conciliation_load_bank_manual');
export const conciliation_load_cia_manual = httpsCallable(functions, 'conciliation_load_cia_manual');

// Notifications
export const send_email = httpsCallable(functions, 'send_email');

Firestore Collections

Key Firestore collections used throughout the application:
// User Management
u_clients          // Client accounts (authenticated users)
u_staff           // Staff members
u_collaborators   // Event collaborators
u_customers       // Ticket purchasers

// Event Management
events            // Event records
event_venues      // Venue information
credentials       // Event credentials/access passes

// Sales & Transactions
tickets           // Individual tickets
orders            // Customer orders
transactions      // Payment transactions
offices           // Box office locations
portals           // Online sales portals

// Financial
contracts         // Client contracts
contracts_legal   // Legal contracts
payouts           // Payment distributions
custody_accounts  // Custody account records
payment_methods   // Payment method configurations
orders_payout     // Payout orders

// Documents & Reconciliation
documents         // Financial documents
bank_documents    // Bank transaction records
company_documents // Company transaction records

// Marketing
marketing         // Marketing campaigns

Firebase Storage

File uploads are handled through Firebase Storage:
import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
import { Storage } from 'src/guards/firebase/Firebase';

// Upload event image
const uploadEventImage = async (file, eventId) => {
  const storageRef = ref(Storage, `events/${eventId}/image.jpg`);
  await uploadBytes(storageRef, file);
  const downloadURL = await getDownloadURL(storageRef);
  return downloadURL;
};

Component Architecture

Layout System

The application uses a two-layout system: 1. Full Layout - Main authenticated layout with sidebar:
src/layouts/full/FullLayout.js:26-78
const FullLayout = () => {
  const customizer = useSelector((state) => state.customizer);
  const theme = useTheme();

  return (
    <MainWrapper>
      {/* Sidebar (vertical or hidden for horizontal) */}
      {customizer.isHorizontal ? '' : <Sidebar />}
      
      <PageWrapper>
        {/* Header */}
        {customizer.isHorizontal ? <HorizontalHeader /> : <Header />}
        
        {/* Navigation (horizontal mode) */}
        {customizer.isHorizontal ? <Navigation /> : ''}
        
        {/* Page Content */}
        <Container
          sx={{
            maxWidth: customizer.isLayout === 'boxed' ? 'lg' : '100%!important',
          }}
        >
          <Box sx={{ minHeight: 'calc(100vh - 170px)' }}>
            <Outlet /> {/* Nested routes render here */}
          </Box>
        </Container>
      </PageWrapper>
    </MainWrapper>
  );
};
2. Blank Layout - Minimal layout for authentication pages:
const BlankLayout = () => {
  return (
    <Box sx={{ minHeight: '100vh', display: 'flex' }}>
      <Outlet />
    </Box>
  );
};

View → Component Pattern

The application follows a clear separation between views (page containers) and components (reusable logic): View Layer (src/views/events/new-events/NewEvents.js):
src/views/events/new-events/NewEvents.js:20-39
const NewEventVenues = () => {
  return (
    <PageContainer title="Crear Evento" description="this is Crear Evento page">
      <Breadcrumb title="Crear Evento" items={BCrumb} />
      
      <ParentCard title='Crear Evento'>
        <Grid container spacing={3}>
          <Grid item xs={12}>
            <NewEventVenueForm />
          </Grid>
        </Grid>
      </ParentCard>
    </PageContainer>
  );
};
Component Layer (src/components/apps/events/NewEventForm.js):
const NewEventForm = () => {
  const [clients, setClients] = useState([]);
  const [contracts, setContracts] = useState([]);
  const [eventsVenues, setEventsVenues] = useState([]);
  
  const formik = useFormik({
    initialValues: { /* ... */ },
    validationSchema: EventSchema,
    onSubmit: async (values) => {
      // Handle form submission
      await Firestore.collection('events').add(values);
    }
  });
  
  return (
    <Form>
      {/* Form fields */}
    </Form>
  );
};
This pattern keeps views focused on layout and navigation, while components handle business logic and data operations.

Form Management

Forms are built using Formik with Yup validation:
import { useFormik, FormikProvider } from 'formik';
import * as Yup from 'yup';

const ValidationSchema = Yup.object().shape({
  email: Yup.string()
    .email('El correo electrónico es invalido')
    .required('Correo electrónico es requerido'),
  password: Yup.string()
    .min(6, 'La contraseña debe tener al menos 6 caracteres')
    .required('Se requiere contraseña'),
});

const formik = useFormik({
  initialValues: {
    email: '',
    password: '',
  },
  validationSchema: ValidationSchema,
  onSubmit: async (values) => {
    // Handle submission
  },
});

return (
  <FormikProvider value={formik}>
    <Form onSubmit={formik.handleSubmit}>
      <CustomTextField
        id="email"
        {...formik.getFieldProps('email')}
        error={Boolean(formik.touched.email && formik.errors.email)}
        helperText={formik.touched.email && formik.errors.email}
      />
    </Form>
  </FormikProvider>
);

Theme Customization

Material-UI theming is managed through Redux:
src/store/customizer/CustomizerSlice.js:4-19
const initialState = {
  activeDir: 'ltr',
  activeMode: 'light',
  activeTheme: 'BLUE_THEME',
  SidebarWidth: 270,
  MiniSidebarWidth: 87,
  TopbarHeight: 70,
  isLayout: 'boxed',
  isCollapse: false,
  isSidebarHover: false,
  isMobileSidebar: false,
  isHorizontal: false,
  isLanguage: 'en',
  isCardShadow: true,
  borderRadius: 7,
};

Performance Considerations

Code Splitting

Routes use React.lazy for code splitting:
src/routes/Router.js:1-14
import React, { lazy } from 'react';
import Loadable from '../layouts/full/shared/loadable/Loadable';

const ModernDash = Loadable(lazy(() => import('../views/dashboard/Modern')));
const EventsList = Loadable(lazy(() => import('../views/events/list-events/EventsList')));
const NewEvents = Loadable(lazy(() => import('../views/events/new-events/NewEvents')));
// ... all routes are lazy loaded

Redux Persist

Only essential state is persisted:
const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['auth', 'customizer', 'setup'] // Selective persistence
};

Firestore Queries

Queries use specific indexes and filters:
// Good: Filtered query
const activeEvents = await Firestore
  .collection('events')
  .where('status', '==', 'Activo')
  .where('client_id', '==', userId)
  .get();

// Avoid: Fetching all documents
// const allEvents = await Firestore.collection('events').get();

Error Handling

Toast Notifications

Errors are displayed using react-toastify:
import { toast } from 'react-toastify';

try {
  await createEvent(data);
  toast.success('Evento creado exitosamente');
} catch (error) {
  toast.error('Error al crear evento: ' + error.message, {
    position: "bottom-center",
    autoClose: 7000,
    hideProgressBar: false,
    closeOnClick: true,
    pauseOnHover: true,
    draggable: true,
    theme: "light",
  });
}

Authentication Errors

src/views/authentication/authForms/AuthLogin.js:98-111
const getErrorMessage = (errorCode) => {
  switch (errorCode) {
    case "auth/invalid-email":
      return "La dirección de correo electrónico no es válida.";
    case "auth/invalid-login-credentials":
      return "La contraseña es incorrecta";
    case "auth/invalid-credential":
      return "La contraseña o el correo es incorrecto.";
    case "auth/too-many-requests":
      return "Has hecho muchas solicitudes, espera un momento.";
    default:
      return "Ocurrió un error al intentar iniciar sesión.";
  }
};

Build & Deployment

Vite Configuration

vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      'src': '/src'
    }
  },
  server: {
    port: 3000
  },
  build: {
    outDir: 'dist',
    sourcemap: true
  }
});

NPM Scripts

package.json:6-10
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  }
}

Key Architectural Decisions

1

Firebase as Backend

Using Firebase eliminates the need for custom backend infrastructure. Firestore provides real-time data sync, Cloud Functions handle serverless logic, and Authentication manages user sessions.
2

Redux with Persistence

Redux Toolkit simplifies state management, while Redux Persist maintains authentication and UI preferences across sessions. Only critical state is persisted to optimize performance.
3

CASL for Permissions

CASL provides declarative, role-based access control that’s easy to maintain and audit. Permissions are defined centrally and enforced at both route and component levels.
4

Material-UI Components

MUI provides a consistent, accessible design system. Custom theme wrappers maintain brand consistency while leveraging MUI’s extensive component library.
5

Formik + Yup Validation

Formik handles complex form state, while Yup provides schema-based validation. This combination reduces boilerplate and ensures consistent validation across the app.
6

Code Splitting by Route

Lazy loading routes reduces initial bundle size. Each major feature loads on-demand, improving first contentful paint and time-to-interactive metrics.

Development Workflow

Start Dev Server

npm run dev
# Vite dev server on http://localhost:3000

Build for Production

npm run build
# Output to /dist directory

Lint Code

npm run lint
# ESLint checks with React rules

Preview Build

npm run preview
# Test production build locally

Next Steps

Quickstart Guide

Learn how to create your first event

API Reference

Explore Firebase Cloud Functions API

State Management

Deep dive into Redux slices

Permission System

Configure CASL abilities for custom roles

Build docs developers (and LLMs) love