Documentation Index
Fetch the complete documentation index at: https://mintlify.com/JoseOlivares19/Proyecto-PC3-JavaScript-Avanzado/llms.txt
Use this file to discover all available pages before exploring further.
SmartStock360’s frontend follows a deliberately lean single-file architecture: the entire UI lives inside src/App.tsx. This makes the codebase easy to read end-to-end, with no prop-drilling across multiple files and no additional component library beyond Bootstrap 5 (loaded via CDN in index.html) and Bootstrap Icons. The three logical UI sections — the product card, the prediction results panel, and the inventory table — are rendered as sibling columns and card elements within a single Bootstrap container.
Single-Component Architecture
All state, event handlers, data-fetching logic, and JSX markup are co-located in the App function component exported from src/App.tsx. The component:
- Declares two pieces of React state
- Runs a side-effect on mount to pre-load the product list
- Exposes a click handler that dispatches the prediction request
- Returns a single JSX fragment containing all three UI sections
No child components, no React context, no routing — everything renders in one pass.
React State
The component uses two useState hooks:
const [productos, setProductos] = useState<any[]>([]);
const [resultado, setResultado] = useState<any>(null);
| State variable | Type | Purpose |
|---|
productos | any[] | Holds the array of product objects fetched from GET /api/productos |
resultado | any | Holds the AI prediction response returned from POST /api/productos/predict; starts as null |
The useEffect hook calls cargarProductos() immediately on component mount with an empty dependency array ([]), so the inventory table is populated as soon as the page loads — no user interaction required.useEffect(() => {
cargarProductos();
}, []);
Because this is a demo application, the prediction input is a hardcoded object defined directly inside the component body:
const formulario = {
precio: 129.90,
stock_actual: 80,
ventas_7d: 420,
descuento_pct: 20,
temporada: 2,
dias_sin_reabastecer: 18,
rating_producto: 4.6
};
This object is sent verbatim to the backend when the user clicks “Ejecutar Predicción IA”. The values represent a specific Zapatillas Deportivas product (SKU: PROD-004) with high recent sales and a holiday/high-demand season flag (temporada: 2).
UI Section 1 — Product Card (col-lg-5)
The left column renders a saas-card that displays all formulario fields as labelled parameter badges, plus the action button:
| Field displayed | Value source |
|---|
| Product name | Hardcoded — “Zapatillas Deportivas” |
| SKU | Hardcoded — “PROD-004” |
| Price | formulario.precio |
| Stock disponible | formulario.stock_actual |
| Ventas últimos 7 días | formulario.ventas_7d |
| Descuento activo | formulario.descuento_pct |
| Temporada | formulario.temporada (displayed as “Nivel 2”) |
The “Ejecutar Predicción IA” button calls manejarPrediccion() on click:
const manejarPrediccion = async () => {
try {
const data = await predecirDemanda(formulario);
setResultado(data);
} catch (error) {
console.error("Error al predecir", error);
}
};
UI Section 2 — Prediction Results Panel (col-lg-7)
The right column renders conditionally based on whether resultado is null:
- Before prediction (
resultado === null): shows an idle placeholder with a CPU icon and the instruction to click the button.
- After prediction (
resultado is set): renders the full results card.
The results card displays three key pieces of data from the API response:
| Response field | Rendered as |
|---|
resultado.prediccion | Raw model label in a <code> block (e.g., DEMANDA_ALTA_REABASTECER) |
obtenerDecision(resultado.prediccion) | Human-readable strategic decision in a colour-coded banner |
resultado.recomendaciones | A mapped list of recommendation strings, one card per item |
The banner colour is driven by whether the prediction label contains the substring 'ALTA':
- Contains
'ALTA' → red banner (urgent restock)
- Does not contain
'ALTA' → green banner (monitor or reduce)
The obtenerDecision() Helper
This utility function translates raw ML class labels into business-friendly action strings:
const obtenerDecision = (prediccion: string) => {
if (prediccion === "DEMANDA_ALTA_REABASTECER") return "Reabastecer urgentemente";
if (prediccion === "DEMANDA_MEDIA_MONITOREAR") return "Monitorear stock";
return "Reducir compra";
};
| AI label | Human-readable decision |
|---|
DEMANDA_ALTA_REABASTECER | Reabastecer urgentemente |
DEMANDA_MEDIA_MONITOREAR | Monitorear stock |
| Any other value | Reducir compra |
UI Section 3 — Inventory Table
The bottom section renders a full-width saas-card containing a responsive Bootstrap table. It iterates over the productos state array:
{productos.length > 0 ? (
productos.map(p => (
<tr key={p.id}>
<td className="fw-semibold text-muted">#{p.id}</td>
<td className="fw-bold text-dark">{p.nombre}</td>
<td className="fw-semibold text-primary">${p.precio}</td>
<td>
<span className={`badge ${p.stockActual < 50
? 'bg-danger bg-opacity-10 text-danger'
: 'bg-success bg-opacity-10 text-success'
} px-2 py-1 rounded-pill`}>
{p.stockActual} uds
</span>
</td>
</tr>
))
) : (
<tr>
<td colSpan={4} className="text-center py-4">
No se encontraron registros en MySQL.
</td>
</tr>
)}
The table renders four columns sourced directly from each product object:
| Column | Product field | Notes |
|---|
| ID | p.id | Prefixed with # |
| Producto | p.nombre | Product name |
| Precio Unitario | p.precio | Prefixed with $ |
| Stock Actual | p.stockActual | Badge colour: red if < 50, green otherwise |
The low-stock threshold of 50 units controls the badge colour: products with fewer than 50 units in stock get a red danger badge, all others get a green success badge.
If the backend is unreachable or returns an empty array, the table shows a friendly empty-state row reading “No se encontraron registros en MySQL.” The cargarProductos function logs any fetch errors to the browser console without surfacing them to the UI.