Files
hotel-hacienda/docs/FRONTEND.md
Consultoria AS 411d517ba4 docs: documentación extensiva del proyecto
- 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
2026-06-09 16:26:09 -07:00

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.