- README.md con guía completa de instalación y arquitectura - docs/BACKEND.md — guía detallada del API - docs/FRONTEND.md — guía del SPA React - docs/DATABASE.md — funciones SQL y patrones de datos - docs/API.md — referencia completa de endpoints REST - docs/DEPLOYMENT.md — guía de despliegue en producción - docs/TROUBLESHOOTING.md — solución de problemas comunes - CHANGELOG.md — historial de cambios - CONTRIBUTING.md — guía de contribución
432 lines
14 KiB
Markdown
432 lines
14 KiB
Markdown
# 🎨 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(
|
|
<AuthProvider>
|
|
<LangProvider>
|
|
<BrowserRouter>
|
|
<App />
|
|
</BrowserRouter>
|
|
</LangProvider>
|
|
</AuthProvider>
|
|
);
|
|
```
|
|
|
|
**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 `<Layout />` (que es `Layout2.jsx`).
|
|
|
|
```jsx
|
|
<Route path="/" element={<Login />} />
|
|
<Route path="/app" element={<Layout />}>
|
|
<Route path="income" element={<Income/>} />
|
|
<Route path="employees" element={<Employees />} />
|
|
<Route path="contracts" element={<Contracts />} />
|
|
<Route path="hotelpl" element={<HotelPL />} />
|
|
<Route path="restaurantpl" element={<RestaurantPL />} />
|
|
<Route path="expenses" element={<Expenses />} />
|
|
<Route path="new-expense" element={<NewExpense />} />
|
|
<Route path="products" element={<Products />} />
|
|
<Route path="payroll" element={<Payroll />} />
|
|
{/* ... ~40 rutas más */}
|
|
</Route>
|
|
```
|
|
|
|
**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 (
|
|
<AuthContext.Provider value={{ user, login, logout, userData }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
```
|
|
|
|
**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 (
|
|
<langContext.Provider value={{ lang, toggleLang }}>
|
|
{children}
|
|
</langContext.Provider>
|
|
);
|
|
};
|
|
|
|
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
|
|
<Table
|
|
columns={[
|
|
{ header: "Nombre", key: "name" },
|
|
{ header: "Acciones", key: "id", render: (id) => <button>Editar</button> }
|
|
]}
|
|
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 `<Route path="/app" element={<Layout />}>`.
|
|
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.
|