ADD new frontend

This commit is contained in:
Urtzi Alfaro
2025-08-28 10:41:04 +02:00
parent 9c247a5f99
commit 0fd273cfce
492 changed files with 114979 additions and 1632 deletions

View File

@@ -0,0 +1,340 @@
import React, { Suspense } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import { ProtectedRoute } from './ProtectedRoute';
import { LoadingSpinner } from '../components/shared/LoadingSpinner';
import { AppShell } from '../components/layout';
// Lazy load the pages we actually have
const LandingPage = React.lazy(() => import('../pages/public/LandingPage'));
const LoginPage = React.lazy(() => import('../pages/public/LoginPage'));
const RegisterPage = React.lazy(() => import('../pages/public/RegisterPage'));
const DashboardPage = React.lazy(() => import('../pages/app/DashboardPage'));
// Operations pages
const InventoryPage = React.lazy(() => import('../pages/app/operations/inventory/InventoryPage'));
const ProductionPage = React.lazy(() => import('../pages/app/operations/production/ProductionPage'));
const RecipesPage = React.lazy(() => import('../pages/app/operations/recipes/RecipesPage'));
const ProcurementPage = React.lazy(() => import('../pages/app/operations/procurement/ProcurementPage'));
const OrdersPage = React.lazy(() => import('../pages/app/operations/orders/OrdersPage'));
const POSPage = React.lazy(() => import('../pages/app/operations/pos/POSPage'));
// Analytics pages
const ForecastingPage = React.lazy(() => import('../pages/app/analytics/forecasting/ForecastingPage'));
const SalesAnalyticsPage = React.lazy(() => import('../pages/app/analytics/sales-analytics/SalesAnalyticsPage'));
const AIInsightsPage = React.lazy(() => import('../pages/app/analytics/ai-insights/AIInsightsPage'));
const PerformanceAnalyticsPage = React.lazy(() => import('../pages/app/analytics/performance/PerformanceAnalyticsPage'));
// Communications pages
const AlertsPage = React.lazy(() => import('../pages/app/communications/alerts/AlertsPage'));
const NotificationsPage = React.lazy(() => import('../pages/app/communications/notifications/NotificationsPage'));
const PreferencesPage = React.lazy(() => import('../pages/app/communications/preferences/PreferencesPage'));
// Settings pages
const BakeryConfigPage = React.lazy(() => import('../pages/app/settings/bakery-config/BakeryConfigPage'));
const SystemSettingsPage = React.lazy(() => import('../pages/app/settings/system/SystemSettingsPage'));
const TeamPage = React.lazy(() => import('../pages/app/settings/team/TeamPage'));
const TrainingPage = React.lazy(() => import('../pages/app/settings/training/TrainingPage'));
// Data pages
const WeatherPage = React.lazy(() => import('../pages/app/data/weather/WeatherPage'));
const TrafficPage = React.lazy(() => import('../pages/app/data/traffic/TrafficPage'));
const EventsPage = React.lazy(() => import('../pages/app/data/events/EventsPage'));
// Onboarding pages
const OnboardingSetupPage = React.lazy(() => import('../pages/app/onboarding/setup/OnboardingSetupPage'));
const OnboardingUploadPage = React.lazy(() => import('../pages/app/onboarding/upload/OnboardingUploadPage'));
const OnboardingAnalysisPage = React.lazy(() => import('../pages/app/onboarding/analysis/OnboardingAnalysisPage'));
const OnboardingReviewPage = React.lazy(() => import('../pages/app/onboarding/review/OnboardingReviewPage'));
export const AppRouter: React.FC = () => {
return (
<Suspense fallback={<LoadingSpinner overlay text="Cargando aplicación..." />}>
<Routes>
{/* Public Routes */}
<Route path="/" element={<LandingPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
{/* Protected Routes with AppShell Layout */}
<Route
path="/app"
element={
<ProtectedRoute>
<AppShell>
<DashboardPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/dashboard"
element={
<ProtectedRoute>
<AppShell>
<DashboardPage />
</AppShell>
</ProtectedRoute>
}
/>
{/* Operations Routes */}
<Route
path="/app/operations/inventory"
element={
<ProtectedRoute>
<AppShell>
<InventoryPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/operations/production"
element={
<ProtectedRoute>
<AppShell>
<ProductionPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/operations/recipes"
element={
<ProtectedRoute>
<AppShell>
<RecipesPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/operations/procurement"
element={
<ProtectedRoute>
<AppShell>
<ProcurementPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/operations/orders"
element={
<ProtectedRoute>
<AppShell>
<OrdersPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/operations/pos"
element={
<ProtectedRoute>
<AppShell>
<POSPage />
</AppShell>
</ProtectedRoute>
}
/>
{/* Analytics Routes */}
<Route
path="/app/analytics/forecasting"
element={
<ProtectedRoute>
<AppShell>
<ForecastingPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/analytics/sales"
element={
<ProtectedRoute>
<AppShell>
<SalesAnalyticsPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/analytics/ai-insights"
element={
<ProtectedRoute>
<AppShell>
<AIInsightsPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/analytics/performance"
element={
<ProtectedRoute>
<AppShell>
<PerformanceAnalyticsPage />
</AppShell>
</ProtectedRoute>
}
/>
{/* Communications Routes */}
<Route
path="/app/communications/alerts"
element={
<ProtectedRoute>
<AppShell>
<AlertsPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/communications/notifications"
element={
<ProtectedRoute>
<AppShell>
<NotificationsPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/communications/preferences"
element={
<ProtectedRoute>
<AppShell>
<PreferencesPage />
</AppShell>
</ProtectedRoute>
}
/>
{/* Settings Routes */}
<Route
path="/app/settings/bakery-config"
element={
<ProtectedRoute>
<AppShell>
<BakeryConfigPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/settings/system"
element={
<ProtectedRoute>
<AppShell>
<SystemSettingsPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/settings/team"
element={
<ProtectedRoute>
<AppShell>
<TeamPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/settings/training"
element={
<ProtectedRoute>
<AppShell>
<TrainingPage />
</AppShell>
</ProtectedRoute>
}
/>
{/* Data Routes */}
<Route
path="/app/data/weather"
element={
<ProtectedRoute>
<AppShell>
<WeatherPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/data/traffic"
element={
<ProtectedRoute>
<AppShell>
<TrafficPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/data/events"
element={
<ProtectedRoute>
<AppShell>
<EventsPage />
</AppShell>
</ProtectedRoute>
}
/>
{/* Onboarding Routes */}
<Route
path="/app/onboarding/setup"
element={
<ProtectedRoute>
<AppShell>
<OnboardingSetupPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/onboarding/upload"
element={
<ProtectedRoute>
<AppShell>
<OnboardingUploadPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/onboarding/analysis"
element={
<ProtectedRoute>
<AppShell>
<OnboardingAnalysisPage />
</AppShell>
</ProtectedRoute>
}
/>
<Route
path="/app/onboarding/review"
element={
<ProtectedRoute>
<AppShell>
<OnboardingReviewPage />
</AppShell>
</ProtectedRoute>
}
/>
{/* Default redirects */}
<Route path="/app/*" element={<Navigate to="/app/dashboard" replace />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</Suspense>
);
};
export default AppRouter;

View File

@@ -0,0 +1,307 @@
/**
* Protected Route component for handling authentication and authorization
*/
import React from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { useAuthUser, useIsAuthenticated, useAuthLoading } from '../stores';
import { RouteConfig, canAccessRoute, ROUTES } from './routes.config';
interface ProtectedRouteProps {
children: React.ReactNode;
route?: RouteConfig;
fallback?: React.ReactNode;
redirectTo?: string;
}
interface LoadingSpinnerProps {
message?: string;
}
const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({ message = 'Cargando...' }) => (
<div className="flex items-center justify-center min-h-screen bg-bg-primary">
<div className="flex flex-col items-center gap-4">
<div className="spinner spinner-lg"></div>
<p className="text-text-secondary text-sm">{message}</p>
</div>
</div>
);
const UnauthorizedPage: React.FC = () => (
<div className="flex items-center justify-center min-h-screen bg-bg-primary">
<div className="text-center max-w-md mx-auto px-6">
<div className="mb-6">
<div className="w-16 h-16 mx-auto mb-4 bg-color-error rounded-full flex items-center justify-center">
<svg
className="w-8 h-8 text-text-inverse"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.732 19.5c-.77.833.192 2.5 1.732 2.5z"
/>
</svg>
</div>
<h1 className="text-2xl font-bold text-text-primary mb-2">
Acceso no autorizado
</h1>
<p className="text-text-secondary mb-6">
No tienes permisos para acceder a esta página. Contacta con tu administrador si crees que esto es un error.
</p>
</div>
<div className="flex flex-col gap-3">
<button
onClick={() => window.history.back()}
className="btn btn-primary"
>
Volver atrás
</button>
<button
onClick={() => window.location.href = ROUTES.DASHBOARD}
className="btn btn-outline"
>
Ir al Panel de Control
</button>
</div>
</div>
</div>
);
const ForbiddenPage: React.FC = () => (
<div className="flex items-center justify-center min-h-screen bg-bg-primary">
<div className="text-center max-w-md mx-auto px-6">
<div className="mb-6">
<div className="w-16 h-16 mx-auto mb-4 bg-color-warning rounded-full flex items-center justify-center">
<svg
className="w-8 h-8 text-text-inverse"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L5.636 5.636"
/>
</svg>
</div>
<h1 className="text-2xl font-bold text-text-primary mb-2">
Acceso restringido
</h1>
<p className="text-text-secondary mb-6">
Tu cuenta no tiene los permisos necesarios para ver esta sección. Contacta con tu administrador para solicitar acceso.
</p>
</div>
<div className="flex flex-col gap-3">
<button
onClick={() => window.history.back()}
className="btn btn-primary"
>
Volver atrás
</button>
<button
onClick={() => window.location.href = ROUTES.DASHBOARD}
className="btn btn-outline"
>
Ir al Panel de Control
</button>
</div>
</div>
</div>
);
export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
children,
route,
fallback,
redirectTo,
}) => {
const user = useAuthUser();
const isAuthenticated = useIsAuthenticated();
const isLoading = useAuthLoading();
const location = useLocation();
// Show loading spinner while checking authentication
if (isLoading) {
return fallback || <LoadingSpinner message="Verificando autenticación..." />;
}
// If route doesn't require auth, render children
if (route && !route.requiresAuth) {
return <>{children}</>;
}
// If not authenticated and route requires auth, redirect to login
if (!isAuthenticated) {
const redirectPath = redirectTo || ROUTES.LOGIN;
const returnUrl = location.pathname + location.search;
return (
<Navigate
to={`${redirectPath}?returnUrl=${encodeURIComponent(returnUrl)}`}
replace
/>
);
}
// If no route config is provided, just check authentication
if (!route) {
return <>{children}</>;
}
// Get user roles and permissions
const userRoles = user?.role ? [user.role] : [];
const userPermissions: string[] = [];
// Check if user can access this route
const canAccess = canAccessRoute(route, isAuthenticated, userRoles, userPermissions);
if (!canAccess) {
// Check if it's a permission issue or role issue
const hasRequiredRoles = !route.requiredRoles ||
route.requiredRoles.some(role => userRoles.includes(role));
if (!hasRequiredRoles) {
return <UnauthorizedPage />;
} else {
return <ForbiddenPage />;
}
}
// User has access, render the protected content
return <>{children}</>;
};
// Higher-order component for route protection
export const withProtectedRoute = <P extends object>(
Component: React.ComponentType<P>,
route: RouteConfig,
options?: {
fallback?: React.ReactNode;
redirectTo?: string;
}
) => {
const ProtectedComponent = (props: P) => (
<ProtectedRoute
route={route}
fallback={options?.fallback}
redirectTo={options?.redirectTo}
>
<Component {...props} />
</ProtectedRoute>
);
ProtectedComponent.displayName = `withProtectedRoute(${Component.displayName || Component.name})`;
return ProtectedComponent;
};
// Hook for checking route access
export const useRouteAccess = (route: RouteConfig) => {
const user = useAuthUser();
const isAuthenticated = useIsAuthenticated();
const isLoading = useAuthLoading();
const userRoles = user?.role ? [user.role] : [];
const userPermissions: string[] = [];
const canAccess = canAccessRoute(route, isAuthenticated, userRoles, userPermissions);
return {
canAccess,
isLoading,
isAuthenticated,
userRoles,
userPermissions,
hasRequiredRoles: !route.requiredRoles ||
route.requiredRoles.some(role => userRoles.includes(role)),
hasRequiredPermissions: !route.requiredPermissions ||
route.requiredPermissions.every(permission => userPermissions.includes(permission)),
};
};
// Component for conditionally rendering content based on permissions
interface ConditionalRenderProps {
children: React.ReactNode;
requiredPermissions?: string[];
requiredRoles?: string[];
requireAll?: boolean; // If true, requires ALL permissions/roles, otherwise ANY
fallback?: React.ReactNode;
}
export const ConditionalRender: React.FC<ConditionalRenderProps> = ({
children,
requiredPermissions = [],
requiredRoles = [],
requireAll = true,
fallback = null,
}) => {
const user = useAuthUser();
const isAuthenticated = useIsAuthenticated();
if (!isAuthenticated || !user) {
return <>{fallback}</>;
}
const userRoles = user.role ? [user.role] : [];
const userPermissions: string[] = [];
// Check roles
let hasRoles = true;
if (requiredRoles.length > 0) {
if (requireAll) {
hasRoles = requiredRoles.every(role => userRoles.includes(role));
} else {
hasRoles = requiredRoles.some(role => userRoles.includes(role));
}
}
// Check permissions
let hasPermissions = true;
if (requiredPermissions.length > 0) {
if (requireAll) {
hasPermissions = requiredPermissions.every(permission => userPermissions.includes(permission));
} else {
hasPermissions = requiredPermissions.some(permission => userPermissions.includes(permission));
}
}
if (hasRoles && hasPermissions) {
return <>{children}</>;
}
return <>{fallback}</>;
};
// Route guard for admin-only routes
export const AdminRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<ConditionalRender
requiredRoles={['admin', 'super_admin']}
requireAll={false}
fallback={<UnauthorizedPage />}
>
{children}
</ConditionalRender>
);
};
// Route guard for manager-level routes
export const ManagerRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<ConditionalRender
requiredRoles={['admin', 'super_admin', 'manager']}
requireAll={false}
fallback={<UnauthorizedPage />}
>
{children}
</ConditionalRender>
);
};

View File

@@ -0,0 +1,22 @@
export { AppRouter, default as Router } from './AppRouter';
export { ProtectedRoute } from './ProtectedRoute';
export {
ROUTES,
ROUTE_CONFIGS,
canAccessRoute,
getRouteByPath,
getRoutesForRole,
getRoutesWithPermission,
isPublicRoute,
isProtectedRoute,
type RouteConfig
} from './routes.config';
// Additional utility exports for route components
export {
ConditionalRender,
AdminRoute,
ManagerRoute,
useRouteAccess,
withProtectedRoute
} from './ProtectedRoute';

View File

@@ -1,320 +0,0 @@
import React from 'react';
import { createBrowserRouter, Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
// Layout components
import Layout from '../components/layout/Layout';
import AuthLayout from '../components/layout/AuthLayout';
// Pages
import LandingPage from '../pages/landing/LandingPage';
import LoginPage from '../pages/auth/LoginPage';
import RegisterPage from '../pages/auth/SimpleRegisterPage';
import OnboardingPage from '../pages/onboarding/OnboardingPage';
import DashboardPage from '../pages/dashboard/DashboardPage';
// Operations Hub Pages
import OperationsLayout from '../components/layout/OperationsLayout';
import ProductionPage from '../pages/production/ProductionPage';
import OrdersPage from '../pages/orders/OrdersPage';
import InventoryPage from '../pages/inventory/InventoryPage';
import SalesPage from '../pages/sales/SalesPage';
import RecipesPage from '../pages/recipes/RecipesPage';
import POSPage from '../pages/pos/POSPage';
// Analytics Hub Pages
import AnalyticsLayout from '../components/layout/AnalyticsLayout';
import ForecastPage from '../pages/forecast/ForecastPage';
import SalesAnalyticsPage from '../pages/analytics/SalesAnalyticsPage';
import ProductionReportsPage from '../pages/analytics/ProductionReportsPage';
import FinancialReportsPage from '../pages/analytics/FinancialReportsPage';
import PerformanceKPIsPage from '../pages/analytics/PerformanceKPIsPage';
import AIInsightsPage from '../pages/analytics/AIInsightsPage';
// Settings Pages
import SettingsLayout from '../components/layout/SettingsLayout';
import SettingsPage from '../pages/settings/SettingsPage';
import UsersManagementPage from '../pages/settings/UsersManagementPage';
import BakeriesManagementPage from '../pages/settings/BakeriesManagementPage';
import GeneralSettingsPage from '../pages/settings/GeneralSettingsPage';
import AccountSettingsPage from '../pages/settings/AccountSettingsPage';
// Route Guards
import ProtectedRoute from '../components/auth/ProtectedRoute';
import RoleBasedRoute from '../components/auth/RoleBasedRoute';
// Add RootState import for type checking
import type { RootState } from '../store';
export const router = createBrowserRouter([
// Public Routes
{
path: '/',
element: <AuthLayout />,
children: [
{
index: true,
element: <LandingPage />
},
{
path: 'login',
element: <LoginPage />
},
{
path: 'register',
element: <RegisterPage />
}
]
},
// Protected Routes
{
path: '/app',
element: (
<ProtectedRoute>
<Layout />
</ProtectedRoute>
),
children: [
// Dashboard
{
index: true,
element: <Navigate to="/app/dashboard" replace />
},
{
path: 'dashboard',
element: <DashboardPage />
},
// Onboarding (conditional)
{
path: 'onboarding',
element: <OnboardingPage />
},
// Operations Hub
{
path: 'operations',
element: <OperationsLayout />,
children: [
{
index: true,
element: <Navigate to="/app/operations/production" replace />
},
{
path: 'production',
children: [
{
index: true,
element: <ProductionPage />
},
{
path: 'schedule',
element: <ProductionPage view="schedule" />
},
{
path: 'active-batches',
element: <ProductionPage view="active-batches" />
},
{
path: 'equipment',
element: <ProductionPage view="equipment" />
}
]
},
{
path: 'orders',
children: [
{
index: true,
element: <OrdersPage />
},
{
path: 'incoming',
element: <OrdersPage view="incoming" />
},
{
path: 'in-progress',
element: <OrdersPage view="in-progress" />
},
{
path: 'supplier-orders',
element: <OrdersPage view="supplier-orders" />
}
]
},
{
path: 'inventory',
children: [
{
index: true,
element: <InventoryPage />
},
{
path: 'stock-levels',
element: <InventoryPage view="stock-levels" />
},
{
path: 'movements',
element: <InventoryPage view="movements" />
},
{
path: 'alerts',
element: <InventoryPage view="alerts" />
}
]
},
{
path: 'sales',
children: [
{
index: true,
element: <SalesPage />
},
{
path: 'daily-sales',
element: <SalesPage view="daily-sales" />
},
{
path: 'customer-orders',
element: <SalesPage view="customer-orders" />
}
]
},
{
path: 'pos',
children: [
{
index: true,
element: <POSPage />
},
{
path: 'integrations',
element: <POSPage view="integrations" />
},
{
path: 'sync-status',
element: <POSPage view="sync-status" />
},
{
path: 'transactions',
element: <POSPage view="transactions" />
}
]
},
{
path: 'recipes',
children: [
{
index: true,
element: <RecipesPage />
},
{
path: 'active-recipes',
element: <RecipesPage view="active-recipes" />
},
{
path: 'development',
element: (
<RoleBasedRoute requiredRoles={['admin', 'manager']}>
<RecipesPage view="development" />
</RoleBasedRoute>
)
},
{
path: 'costing',
element: (
<RoleBasedRoute requiredRoles={['admin', 'manager']}>
<RecipesPage view="costing" />
</RoleBasedRoute>
)
}
]
}
]
},
// Analytics Hub (Admin/Manager only)
{
path: 'analytics',
element: (
<RoleBasedRoute requiredRoles={['admin', 'manager']}>
<AnalyticsLayout />
</RoleBasedRoute>
),
children: [
{
index: true,
element: <Navigate to="/app/analytics/forecasting" replace />
},
{
path: 'forecasting',
element: <ForecastPage />
},
{
path: 'sales-analytics',
element: <SalesAnalyticsPage />
},
{
path: 'production-reports',
element: <ProductionReportsPage />
},
{
path: 'financial-reports',
element: <FinancialReportsPage />
},
{
path: 'performance-kpis',
element: <PerformanceKPIsPage />
},
{
path: 'ai-insights',
element: <AIInsightsPage />
}
]
},
// Settings Hub
{
path: 'settings',
element: <SettingsLayout />,
children: [
{
index: true,
element: <Navigate to="/app/settings/general" replace />
},
{
path: 'general',
element: <GeneralSettingsPage />
},
{
path: 'users',
element: (
<RoleBasedRoute requiredRoles={['admin']}>
<UsersManagementPage />
</RoleBasedRoute>
)
},
{
path: 'bakeries',
element: (
<RoleBasedRoute requiredRoles={['admin']}>
<BakeriesManagementPage />
</RoleBasedRoute>
)
},
{
path: 'account',
element: <AccountSettingsPage />
}
]
}
]
},
// Catch all route
{
path: '*',
element: <Navigate to="/" replace />
}
]);

View File

@@ -0,0 +1,652 @@
/**
* Route configuration for the bakery management application
*/
export interface RouteConfig {
path: string;
name: string;
component: string;
title: string;
description?: string;
icon?: string;
requiresAuth: boolean;
requiredRoles?: string[];
requiredPermissions?: string[];
showInNavigation?: boolean;
showInBreadcrumbs?: boolean;
children?: RouteConfig[];
meta?: {
layout?: 'default' | 'auth' | 'minimal';
headerTitle?: string;
breadcrumbTitle?: string;
hideHeader?: boolean;
hideSidebar?: boolean;
fullScreen?: boolean;
scrollToTop?: boolean;
preload?: boolean;
cache?: boolean;
};
}
// Route paths as constants
export const ROUTES = {
// Public routes
HOME: '/',
LOGIN: '/login',
REGISTER: '/register',
FORGOT_PASSWORD: '/forgot-password',
RESET_PASSWORD: '/reset-password',
VERIFY_EMAIL: '/verify-email',
// Dashboard
DASHBOARD: '/app/dashboard',
// Inventory Management
INVENTORY: '/app/operations/inventory',
INVENTORY_INGREDIENTS: '/inventory/ingredients',
INVENTORY_STOCK: '/inventory/stock',
INVENTORY_MOVEMENTS: '/inventory/movements',
INVENTORY_ALERTS: '/inventory/alerts',
INVENTORY_QUALITY: '/inventory/quality',
INVENTORY_REPORTS: '/inventory/reports',
// Production Management
PRODUCTION: '/app/operations/production',
PRODUCTION_BATCHES: '/production/batches',
PRODUCTION_RECIPES: '/production/recipes',
PRODUCTION_SCHEDULE: '/production/schedule',
PRODUCTION_QUALITY: '/production/quality',
PRODUCTION_REPORTS: '/production/reports',
// Sales & Analytics
SALES: '/sales',
SALES_DATA: '/sales/data',
SALES_ANALYTICS: '/sales/analytics',
SALES_REPORTS: '/sales/reports',
SALES_FORECASTING: '/sales/forecasting',
// Forecasting & ML
FORECASTING: '/forecasting',
FORECASTING_MODELS: '/forecasting/models',
FORECASTING_PREDICTIONS: '/forecasting/predictions',
FORECASTING_TRAINING: '/forecasting/training',
FORECASTING_ANALYTICS: '/forecasting/analytics',
// Orders Management
ORDERS: '/orders',
ORDERS_LIST: '/orders/list',
ORDERS_QUEUE: '/orders/queue',
ORDERS_HISTORY: '/orders/history',
ORDERS_CUSTOMERS: '/orders/customers',
// Procurement
PROCUREMENT: '/procurement',
PROCUREMENT_ORDERS: '/procurement/orders',
PROCUREMENT_SUPPLIERS: '/procurement/suppliers',
PROCUREMENT_DELIVERIES: '/procurement/deliveries',
PROCUREMENT_ANALYTICS: '/procurement/analytics',
// Point of Sale
POS: '/pos',
POS_INTEGRATION: '/pos/integration',
POS_TRANSACTIONS: '/pos/transactions',
POS_WEBHOOKS: '/pos/webhooks',
POS_SETTINGS: '/pos/settings',
// Data Management
DATA: '/data',
DATA_IMPORT: '/data/import',
DATA_EXPORT: '/data/export',
DATA_EXTERNAL: '/data/external',
DATA_WEATHER: '/data/weather',
DATA_EVENTS: '/data/events',
// Training & ML
TRAINING: '/training',
TRAINING_MODELS: '/training/models',
TRAINING_JOBS: '/training/jobs',
TRAINING_EVALUATION: '/training/evaluation',
TRAINING_DATASETS: '/training/datasets',
// Notifications
NOTIFICATIONS: '/notifications',
NOTIFICATIONS_LIST: '/notifications/list',
NOTIFICATIONS_TEMPLATES: '/notifications/templates',
NOTIFICATIONS_SETTINGS: '/notifications/settings',
// Settings
SETTINGS: '/settings',
SETTINGS_PROFILE: '/settings/profile',
SETTINGS_TENANT: '/settings/tenant',
SETTINGS_USERS: '/settings/users',
SETTINGS_PERMISSIONS: '/settings/permissions',
SETTINGS_INTEGRATIONS: '/settings/integrations',
SETTINGS_PREFERENCES: '/settings/preferences',
SETTINGS_BILLING: '/settings/billing',
// Reports
REPORTS: '/reports',
REPORTS_PRODUCTION: '/reports/production',
REPORTS_INVENTORY: '/reports/inventory',
REPORTS_SALES: '/reports/sales',
REPORTS_FINANCIAL: '/reports/financial',
REPORTS_QUALITY: '/reports/quality',
// Help & Support
HELP: '/help',
HELP_DOCUMENTATION: '/help/docs',
HELP_TUTORIALS: '/help/tutorials',
HELP_SUPPORT: '/help/support',
HELP_FEEDBACK: '/help/feedback',
// Error pages
NOT_FOUND: '/404',
UNAUTHORIZED: '/401',
FORBIDDEN: '/403',
SERVER_ERROR: '/500',
} as const;
// Route configurations
export const routesConfig: RouteConfig[] = [
// Public routes
{
path: ROUTES.HOME,
name: 'Home',
component: 'HomePage',
title: 'Panadería IA - Gestión Inteligente',
requiresAuth: false,
showInNavigation: false,
meta: {
layout: 'minimal',
scrollToTop: true,
},
},
{
path: ROUTES.LOGIN,
name: 'Login',
component: 'LoginPage',
title: 'Iniciar Sesión',
requiresAuth: false,
showInNavigation: false,
meta: {
layout: 'auth',
hideHeader: true,
hideSidebar: true,
},
},
{
path: ROUTES.REGISTER,
name: 'Register',
component: 'RegisterPage',
title: 'Crear Cuenta',
requiresAuth: false,
showInNavigation: false,
meta: {
layout: 'auth',
hideHeader: true,
hideSidebar: true,
},
},
// Dashboard
{
path: ROUTES.DASHBOARD,
name: 'Dashboard',
component: 'DashboardPage',
title: 'Panel de Control',
icon: 'dashboard',
requiresAuth: true,
showInNavigation: true,
meta: {
headerTitle: 'Panel de Control',
preload: true,
cache: true,
},
},
// Inventory Management
{
path: '/app/operations/inventory',
name: 'Inventory',
component: 'InventoryPage',
title: 'Inventario',
icon: 'inventory',
requiresAuth: true,
showInNavigation: true,
},
// Production Management
{
path: '/app/operations/production',
name: 'Production',
component: 'ProductionPage',
title: 'Producción',
icon: 'production',
requiresAuth: true,
showInNavigation: true,
},
// Settings
{
path: ROUTES.SETTINGS,
name: 'Settings',
component: 'SettingsPage',
title: 'Configuración',
icon: 'settings',
requiresAuth: true,
showInNavigation: true,
children: [
{
path: '/app/settings/bakery-config',
name: 'BakeryConfig',
component: 'BakeryConfigPage',
title: 'Configuración de Panadería',
icon: 'settings',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
{
path: '/app/settings/system',
name: 'SystemSettings',
component: 'SystemSettingsPage',
title: 'Configuración del Sistema',
icon: 'settings',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
{
path: '/app/settings/team',
name: 'Team',
component: 'TeamPage',
title: 'Gestión de Equipo',
icon: 'settings',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
{
path: '/app/settings/training',
name: 'Training',
component: 'TrainingPage',
title: 'Entrenamiento',
icon: 'training',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
],
},
// Individual Operations Pages
{
path: '/app/operations/recipes',
name: 'Recipes',
component: 'RecipesPage',
title: 'Recetas',
icon: 'production',
requiresAuth: true,
showInNavigation: true,
},
{
path: '/app/operations/orders',
name: 'Orders',
component: 'OrdersPage',
title: 'Pedidos',
icon: 'orders',
requiresAuth: true,
showInNavigation: true,
},
{
path: '/app/operations/procurement',
name: 'Procurement',
component: 'ProcurementPage',
title: 'Compras',
icon: 'procurement',
requiresAuth: true,
showInNavigation: true,
},
{
path: '/app/operations/pos',
name: 'POS',
component: 'POSPage',
title: 'Punto de Venta',
icon: 'pos',
requiresAuth: true,
showInNavigation: true,
},
// Individual Analytics Pages
{
path: '/app/analytics/forecasting',
name: 'Forecasting',
component: 'ForecastingPage',
title: 'Pronósticos',
icon: 'forecasting',
requiresAuth: true,
showInNavigation: true,
},
{
path: '/app/analytics/sales',
name: 'SalesAnalytics',
component: 'SalesAnalyticsPage',
title: 'Análisis de Ventas',
icon: 'sales',
requiresAuth: true,
showInNavigation: true,
},
{
path: '/app/analytics/ai-insights',
name: 'AIInsights',
component: 'AIInsightsPage',
title: 'Insights de IA',
icon: 'forecasting',
requiresAuth: true,
showInNavigation: true,
},
{
path: '/app/analytics/performance',
name: 'PerformanceAnalytics',
component: 'PerformanceAnalyticsPage',
title: 'Análisis de Rendimiento',
icon: 'sales',
requiresAuth: true,
showInNavigation: true,
},
// Communications Section
{
path: '/app/communications',
name: 'Communications',
component: 'CommunicationsPage',
title: 'Comunicaciones',
icon: 'notifications',
requiresAuth: true,
showInNavigation: true,
children: [
{
path: '/app/communications/alerts',
name: 'Alerts',
component: 'AlertsPage',
title: 'Alertas',
icon: 'notifications',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
{
path: '/app/communications/notifications',
name: 'Notifications',
component: 'NotificationsPage',
title: 'Notificaciones',
icon: 'notifications',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
{
path: '/app/communications/preferences',
name: 'Preferences',
component: 'PreferencesPage',
title: 'Preferencias',
icon: 'settings',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
],
},
// Data Management Section
{
path: '/app/data',
name: 'Data',
component: 'DataPage',
title: 'Gestión de Datos',
icon: 'data',
requiresAuth: true,
showInNavigation: true,
children: [
{
path: '/app/data/weather',
name: 'Weather',
component: 'WeatherPage',
title: 'Datos Meteorológicos',
icon: 'data',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
{
path: '/app/data/traffic',
name: 'Traffic',
component: 'TrafficPage',
title: 'Datos de Tráfico',
icon: 'data',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
{
path: '/app/data/events',
name: 'Events',
component: 'EventsPage',
title: 'Eventos y Festivales',
icon: 'data',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
],
},
// Onboarding Section
{
path: '/app/onboarding',
name: 'Onboarding',
component: 'OnboardingPage',
title: 'Configuración Inicial',
icon: 'settings',
requiresAuth: true,
showInNavigation: true,
children: [
{
path: '/app/onboarding/setup',
name: 'OnboardingSetup',
component: 'OnboardingSetupPage',
title: 'Configuración Básica',
icon: 'settings',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
{
path: '/app/onboarding/upload',
name: 'OnboardingUpload',
component: 'OnboardingUploadPage',
title: 'Carga de Datos',
icon: 'data',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
{
path: '/app/onboarding/analysis',
name: 'OnboardingAnalysis',
component: 'OnboardingAnalysisPage',
title: 'Análisis de Datos',
icon: 'forecasting',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
{
path: '/app/onboarding/review',
name: 'OnboardingReview',
component: 'OnboardingReviewPage',
title: 'Revisión Final',
icon: 'settings',
requiresAuth: true,
showInNavigation: true,
showInBreadcrumbs: true,
},
],
},
// Error pages
{
path: ROUTES.NOT_FOUND,
name: 'NotFound',
component: 'NotFoundPage',
title: 'Página no encontrada',
requiresAuth: false,
showInNavigation: false,
meta: {
layout: 'minimal',
},
},
{
path: ROUTES.UNAUTHORIZED,
name: 'Unauthorized',
component: 'UnauthorizedPage',
title: 'No autorizado',
requiresAuth: false,
showInNavigation: false,
meta: {
layout: 'minimal',
},
},
{
path: ROUTES.FORBIDDEN,
name: 'Forbidden',
component: 'ForbiddenPage',
title: 'Acceso prohibido',
requiresAuth: false,
showInNavigation: false,
meta: {
layout: 'minimal',
},
},
];
// Helper functions
export const getRouteByPath = (path: string): RouteConfig | undefined => {
const findRoute = (routes: RouteConfig[], targetPath: string): RouteConfig | undefined => {
for (const route of routes) {
if (route.path === targetPath) {
return route;
}
if (route.children) {
const childRoute = findRoute(route.children, targetPath);
if (childRoute) {
return childRoute;
}
}
}
return undefined;
};
return findRoute(routesConfig, path);
};
export const getRouteByName = (name: string): RouteConfig | undefined => {
const findRoute = (routes: RouteConfig[], targetName: string): RouteConfig | undefined => {
for (const route of routes) {
if (route.name === targetName) {
return route;
}
if (route.children) {
const childRoute = findRoute(route.children, targetName);
if (childRoute) {
return childRoute;
}
}
}
return undefined;
};
return findRoute(routesConfig, name);
};
export const getNavigationRoutes = (): RouteConfig[] => {
const filterNavRoutes = (routes: RouteConfig[]): RouteConfig[] => {
return routes
.filter(route => route.showInNavigation)
.map(route => ({
...route,
children: route.children ? filterNavRoutes(route.children) : undefined,
}));
};
return filterNavRoutes(routesConfig);
};
export const getBreadcrumbs = (path: string): RouteConfig[] => {
const breadcrumbs: RouteConfig[] = [];
const pathSegments = path.split('/').filter(segment => segment);
let currentPath = '';
for (const segment of pathSegments) {
currentPath += `/${segment}`;
const route = getRouteByPath(currentPath);
if (route && route.showInBreadcrumbs !== false) {
breadcrumbs.push(route);
}
}
return breadcrumbs;
};
export const hasPermission = (route: RouteConfig, userPermissions: string[]): boolean => {
if (!route.requiredPermissions || route.requiredPermissions.length === 0) {
return true;
}
// Check for wildcard permission
if (userPermissions.includes('*')) {
return true;
}
return route.requiredPermissions.every(permission =>
userPermissions.includes(permission)
);
};
export const hasRole = (route: RouteConfig, userRoles: string[]): boolean => {
if (!route.requiredRoles || route.requiredRoles.length === 0) {
return true;
}
return route.requiredRoles.some(role =>
userRoles.includes(role)
);
};
export const canAccessRoute = (
route: RouteConfig,
isAuthenticated: boolean,
userRoles: string[] = [],
userPermissions: string[] = []
): boolean => {
// Check authentication requirement
if (route.requiresAuth && !isAuthenticated) {
return false;
}
// Check role requirements
if (!hasRole(route, userRoles)) {
return false;
}
// Check permission requirements
if (!hasPermission(route, userPermissions)) {
return false;
}
return true;
};