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
This commit is contained in:
431
docs/FRONTEND.md
Normal file
431
docs/FRONTEND.md
Normal file
@@ -0,0 +1,431 @@
|
||||
# 🎨 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.
|
||||
Reference in New Issue
Block a user