# 🎨 Documentación del Frontend — Frontend-Hotel ## Índice 1. [Introducción](#introducción) 2. [Estructura de Carpetas](#estructura-de-carpetas) 3. [Entry Points](#entry-points) 4. [Enrutamiento](#enrutamiento) 5. [Manejo de Estado Global](#manejo-de-estado-global) 6. [Sistema de Permisos (Roles)](#sistema-de-permisos-roles) 7. [Comunicación con el Backend](#comunicación-con-el-backend) 8. [Componentes Reutilizables](#componentes-reutilizables) 9. [Estilos](#estilos) 10. [Páginas por Dominio](#páginas-por-dominio) 11. [Guía para Agregar una Nueva Página](#guía-para-agregar-una-nueva-página) 12. [Bugs y Limitaciones Conocidas](#bugs-y-limitaciones-conocidas) --- ## Introducción El frontend es una **Single Page Application (SPA)** construida con **React 19**, **Vite** y **React Router DOM**. Es una aplicación cliente que consume la API REST del backend. **Tecnologías clave:** - React 19.1.1 (JSX, JavaScript puro, sin TypeScript) - Vite 7.1.2 con SWC para compilación rápida - React Router DOM 7.8.2 (BrowserRouter) - Estado global: React Context API - HTTP: mezcla de `fetch` nativo y `axios` - Estilos: Tailwind CSS + Bootstrap 5 + CSS puro --- ## Estructura de Carpetas ``` Frontend-Hotel/ ├── index.html # Entry HTML (título: "Hacienda San Angel") ├── vite.config.js # Config Vite (puerto 5172) ├── tailwind.config.cjs # Config Tailwind ├── postcss-config.js # PostCSS con Tailwind + Autoprefixer ├── eslint.config.js # ESLint 9 flat config ├── public/ │ ├── logoHotel.png │ ├── IconoHotel.svg │ └── ... ├── src/ │ ├── main.jsx # Entry point React │ ├── App.jsx # Definición central de rutas │ ├── index.css # Tailwind directives + estilos base │ ├── App.css │ ├── constants/ │ │ └── menuconfig.js # Configuración del menú de navegación │ ├── context/ │ │ ├── AuthContext.jsx # Estado de autenticación │ │ └── LenguageContext.jsx # Estado de idioma (EN/ES) │ ├── routes/ │ │ └── ProtectedRoute.jsx # Ruta protegida (NO usada en App.jsx) │ ├── services/ │ │ ├── api.js # Instancia axios (baseURL desde env) │ │ ├── employeeService.js │ │ ├── incomeService.js │ │ ├── userService.js │ │ └── contractService.js │ ├── styles/ │ │ ├── global.css │ │ ├── Dashboard.css │ │ ├── Login.css │ │ ├── Layout.css │ │ └── Sidebar.css │ ├── assets/ │ │ └── pages/login.jsx │ ├── components/ │ │ ├── Layout2.jsx # Layout activo (Sidebar + Topbar + Outlet) │ │ ├── Layout.jsx # Layout antiguo (comentado) │ │ ├── Sidebar.jsx │ │ ├── topbar/Topbar.jsx │ │ ├── Navbar/Navbar.jsx │ │ ├── Table/HotelTable.jsx │ │ ├── ExcelExportButton.jsx │ │ ├── SummaryCard.jsx │ │ ├── FormInput.jsx │ │ ├── FormSelect.jsx │ │ ├── Switch.jsx │ │ ├── Modals/ │ │ ├── Buttons/ │ │ ├── Filters/ │ │ └── Inputs/ │ └── pages/ # ~50+ páginas organizadas por dominio │ ├── Login.jsx │ ├── Dashboard.jsx │ ├── Dashboard/ │ ├── Expenses/ │ ├── ExpensesToBeApproval/ │ ├── Inventory/ │ ├── Hotel/ │ ├── Income/ │ ├── Payroll/ │ ├── Settings/ │ └── ... ``` --- ## Entry Points ### `index.html` Carga la fuente Roboto desde Google Fonts y el script `src/main.jsx`. ### `src/main.jsx` ```jsx import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; import { AuthProvider } from "./context/AuthContext"; import { LangProvider } from "./context/LenguageContext"; import App from "./App.jsx"; ReactDOM.createRoot(document.getElementById("root")).render( ); ``` **Jerarquía de providers:** 1. `AuthProvider` — disponible en toda la app 2. `LangProvider` — depende de `AuthContext` (lee el rol para forzar español a housekeepers) 3. `BrowserRouter` — enrutamiento --- ## Enrutamiento ### `src/App.jsx` Define todas las rutas de la aplicación. No usa `ProtectedRoute`. Todas las rutas internas están bajo `/app` y renderizan `` (que es `Layout2.jsx`). ```jsx } /> }> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* ... ~40 rutas más */} ``` **Convenciones de rutas:** - Páginas de listado: plural simple (`/products`, `/payroll`) - Páginas de creación: prefijo `new-` (`/new-product`, `/new-expense`) - Páginas de edición: `:id` como parámetro (`/expenses/edit/:id`, `/payroll/employee/:id`) - Detalles: segmento fijo + `:id` (`/expenses/:id`, `/properties/:id`) --- ## Manejo de Estado Global ### AuthContext (`src/context/AuthContext.jsx`) ```jsx export const AuthContext = createContext(); function AuthProvider({ children }) { const [user, setUser] = useState(null); // rol numérico const [userData, setUserData] = useState(null); useEffect(() => { const savedUser = localStorage.getItem("rol"); if (savedUser) setUser(JSON.parse(savedUser)); }, []); const login = (userData, data) => { setUserData(data); setUser(userData); localStorage.setItem("rol", JSON.stringify(userData)); }; const logout = () => { setUser(null); localStorage.removeItem("rol"); }; return ( {children} ); } ``` **Observaciones:** - Guarda el rol como número en `localStorage` bajo la clave `"rol"`. - **NO exporta `useAuth`**, lo cual es un bug conocido. - No usa JWT ni tokens. ### LangContext (`src/context/LenguageContext.jsx`) ```jsx export const langContext = createContext(); export const LangProvider = ({ children }) => { const authContext = useContext(AuthContext); const user = authContext?.user || null; const [lang, setLang] = useState(user === 6 ? "es" : "en"); const toggleLang = (event) => setLang(event.target.value); return ( {children} ); }; export const useLang = () => useContext(langContext); ``` **Observaciones:** - Forza español si `user === 6` (Housekeeper). - No persiste en localStorage. - Se accede con `useContext(langContext)` o `useLang()`. --- ## Sistema de Permisos (Roles) Los permisos son **numéricos y hardcodeados** en `Layout2.jsx`. No hay backend de ACL. ### Roles Definidos | Rol | Acceso | |-----|--------| | 1 | Admin — Todo | | 2 | Supervisor limitado — Dashboards, Expenses (Report/Monthly Report), Payroll (Report/Attendance/Employees/Contracts), Expenses to be approved | | 3-4 | Rangos intermedios (según lógica de rangos) | | 5 | Compras — Solo: New Expense, Purchase Entries, New Suppliers | | 6 | Housekeeper — Solo Outcomes, forzado a español | ### Lógica de Permisos en `Layout2.jsx` ```javascript const menuConfigWithPermissions = Object.values(menuConfig).map(section => ({ ...section, hidden: section.label === "Dashboards" ? (user >= 1 && user <= 2 ? false : true) : section.label === "Expenses to be approved" ? (user === 1 || user === 2 ? false : true) : section.label === "Expenses" ? (user >= 1 && user <= 5 ? false : true) : section.label === "Inventory" ? (user >= 1 && user <= 5 ? false : true) : section.label === "Payroll" ? (user >= 1 && user <= 4 ? false : true) : section.label === "Hotel" ? (user === 1 ? false : true) : section.label === "Income" ? (user >= 1 && user <= 4 ? false : true) : section.label === "Housekeeper" ? (user === 6 ? false : true) : false, // ... lógica de submenús })); ``` **Regla:** Si `hidden === true`, la sección no aparece en el sidebar. --- ## Comunicación con el Backend ### Variable de Entorno ```env VITE_API_BASE_URL=http://localhost:4000/api ``` Se accede en el frontend como: ```javascript import.meta.env.VITE_API_BASE_URL ``` ### Instancia Axios (`src/services/api.js`) ```javascript import axios from "axios"; const api = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000, }); export default api; ``` **Problema:** Esta instancia está infrautilizada. Muchas páginas usan `fetch` directo o axios sin la instancia base. ### Patrón Fetch (más común) ```javascript fetch(import.meta.env.VITE_API_BASE_URL + '/purchases/getpurchases') .then(res => res.json()) .then(data => setState(data)) .catch(err => console.error(err)); ``` ### Patrón Axios ```javascript axios.put(`${import.meta.env.VITE_API_BASE_URL}/purchases/entry/${id}`, { checking: parseInt(checking) }) .then(res => { ... }) .catch(err => { ... }); ``` **Problemas conocidos:** - Varios archivos en `services/` tienen URLs hardcodeadas a `localhost:3000` o `localhost:4000`. - No hay interceptores para manejo centralizado de errores. - No se envían headers de autorización. --- ## Componentes Reutilizables ### `HotelTable.jsx` (`src/components/Table/`) Tabla genérica que recibe: - `columns`: array de `{ header, key, render?, headerStyle? }` - `data`: array de objetos ```jsx } ]} data={products} /> ``` ### `ExcelExportButton.jsx` Exporta datos a `.xlsx` usando la librería `xlsx`. ### `SummaryCard.jsx` Card de resumen con indicador de loading. ### `FormInput.jsx` / `FormSelect.jsx` Wrappers básicos de inputs HTML con estilos consistentes. ### Modales (`src/components/Modals/`) - `ConfirmationModal.jsx` — Confirmación genérica - `DiscardConfirmModal.jsx` — Confirmación de descarte - `ConfirmationMontlyPay.jsx` — Confirmación de pago mensual - `ConfirmationOutcome.jsx` — Confirmación de salida --- ## Estilos ### Mezcla de Tecnologías El proyecto usa tres sistemas de estilos simultáneamente: 1. **Tailwind CSS** — Configurado pero usado esporádicamente. 2. **Bootstrap 5** — Clases como `btn btn-primary`, `btn btn-secondary`. 3. **CSS Puro** — Un archivo `.css` por página/componente. ### Convención Actual Cada página tiene su propio archivo CSS: ``` src/pages/Expenses/NewExpense.css src/pages/Inventory/Products.css src/components/Table/Table.css ``` ### Fuente Roboto cargada desde Google Fonts en `index.html`. --- ## Páginas por Dominio | Dominio | Páginas | Ruta Base | |---------|---------|-----------| | **Dashboards** | Income, HotelPL, RestaurantPL, RoomAnalysis, RestaurantAnalysis, Budget, CostPerRoom, Expenses | `/app/income`, `/app/hotelpl`... | | **Income** | NewIncome, IncomeReport | `/app/new-income-form`, `/app/new-income-report` | | **Expenses to Approve** | PendingApproval, Approved, Rejected | `/app/pending-approval`... | | **Expenses** | NewExpense, EditExpense, ExpenseDetail, ReportExpense, Payments, MonthlyPayments, MonthlyReport, NewMonthlyPayment, NewSuppliers, PurchaseEntries | `/app/new-expense`... | | **Inventory** | Products, NewProduct, AlterProduct, InventoryReport, Adjustments, Outcomes, HousekeeperOutcomes, DiscardProduct | `/app/products`... | | **Hotel** | Properties, PropertiesId | `/app/properties`... | | **Payroll** | Payroll, Plantillapayroll, EditPayroll, PayrollEmployees, NewEmployee, PayrollAttendance, PayrollContract, ContractsDetail | `/app/payroll`... | | **Settings** | Settings, SettingsId, RoomsManagement | `/app/settings`... | --- ## Guía para Agregar una Nueva Página 1. **Crear el componente** en `src/pages/[Dominio]/NombrePagina.jsx`. 2. **Crear los estilos** opcionales en `src/pages/[Dominio]/NombrePagina.css`. 3. **Agregar la ruta** en `src/App.jsx` dentro de `}>`. 4. **Agregar al menú** (si aplica): - Editar `src/constants/menuconfig.js` - Agregar la ruta a la sección correspondiente 5. **Actualizar permisos** en `src/components/Layout2.jsx`: - Agregar lógica de `hidden` para la sección/submenú - Agregar detección de ruta en `activeSection` si la ruta no sigue el patrón estándar 6. **Agregar traducciones** EN/ES si es texto visible. 7. **Consumir la API** usando `import.meta.env.VITE_API_BASE_URL`. --- ## Bugs y Limitaciones Conocidas 1. **`useAuth` no existe** — `AuthContext.jsx` no exporta `useAuth`, pero `ProtectedRoute.jsx` y `Navbar.jsx` lo intentan importar. 2. **No hay rutas protegidas** — `App.jsx` no usa `ProtectedRoute`. Cualquiera puede acceder a `/app/*`. 3. **URLs hardcodeadas** — Varios `*Service.js` apuntan a `localhost` en vez de usar `VITE_API_BASE_URL`. 4. **Código comentado masivo** — Especialmente en `Layout.jsx` y páginas grandes. 5. **`multer` en frontend** — Es un middleware de Node.js, inapropiado para React. 6. **Sin tests** — No hay Jest, Vitest, ni Playwright configurados.