Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/davidmpizarro/QuipuEco-Hackaton/llms.txt

Use this file to discover all available pages before exploring further.

The ImpactDashboard component gives users a personalised view of the environmental impact they have accumulated through every waste classification. All data is sourced from the useHistorial hook, which maintains a persistent record in localStorage under the key "quipueco_historial" — meaning the dashboard survives page refreshes, browser restarts, and even offline use. Three summary metrics are displayed at the top (CO₂ avoided, total weight, item count), followed by a gamified rank card showing the user’s current eco-tier and progress towards the next one. Below that, three tabs break the data down further: a per-category breakdown, a cumulative CO₂ area chart rendered with Recharts, and a scrollable log of the last ten classified items. This combination of real environmental data and game mechanics is designed to make recycling feel rewarding and measurable.

Accessing the Dashboard

The dashboard is accessible from two places in the UI:
  • The “Mi impacto” tab in the bottom navigation bar (BarChart3 icon)
  • The Trophy button in the app header, which also displays the current point total (e.g. 42 pts) — tapping it toggles directly to the dashboard from any view and back again

Summary Metrics

The three top-level stat cards pull directly from the stats object returned by useHistorial:
MetricSource fieldDescription
kg CO₂ Evitadostats.co2TotalSum of co2_evitado_kg across all history entries
kg Peso Recicladostats.pesoTotalSum of peso_estimado_kg across all history entries
Items Clasificadosstats.totalItemsTotal count of classification records

Eco-Rank System

Classification activity earns Eco Points that unlock five rank tiers. The current rank and the progress bar towards the next tier are displayed in a gradient card at the top of the dashboard. Thresholds are defined in the NIVELES array in ImpactDashboard.jsx.

🌱 Semilla Eco

Threshold: 0 points
Starting rank for all users. Every classification, no matter how small, pushes you forward.

🌳 Árbol Joven

Threshold: 30 points
Reached after classifying roughly 3 recyclable items.

🌲 Bosque Verde

Threshold: 100 points
A dedicated recycler who has built a real habit.

🦅 Guardián Eco

Threshold: 250 points
An active guardian of Lima’s environment.

🏆 Héroe del Planeta

Threshold: 500 points
Maximum rank. The progress bar is replaced with a “¡Has alcanzado el rango máximo de Guardián Eco!” badge.

Points Calculation

Points are assigned per classification record by the useHistorial hook. The logic rewards engagement with every item, including non-recyclable and hazardous waste — recognising that correctly identifying a hazardous material is itself an environmentally responsible action.
// From useHistorial.js — stats.puntos reducer
puntos: historial.reduce((acc, r) => {
  if (r.tipo === "no_reciclable") return acc + 2;
  if (r.tipo === "peligroso")     return acc + 5;
  return acc + 10;   // all other recyclable categories
}, 0),
Item typePoints
Any recyclable category (plastico, papel, vidrio, organico, metal, electronico)+10 pts
Peligroso (hazardous waste)+5 pts
No reciclable+2 pts
The progress bar towards the next rank is calculated as:
const progreso =
  siguiente
    ? ((stats.puntos - nivel.min) / (siguiente.min - nivel.min)) * 100
    : 100;

The useHistorial Hook

useHistorial is the single source of truth for all dashboard data. It is imported in App.jsx and its return values are passed down to both ImpactDashboard and used to seed new records via agregarRegistro.
import { useHistorial } from "./hooks/useHistorial";

const { historial, agregarRegistro, limpiarHistorial, stats } = useHistorial();

API

Appends a new classification record to the history array and persists it to localStorage. The record merges all fields from the classification JSON with a generated id (timestamp), an ISO fecha, and the imagen data URL for the thumbnail.
const agregarRegistro = (resultado, imagen) => {
  const nuevo = {
    id: Date.now(),
    fecha: new Date().toISOString(),
    imagen,
    ...resultado,   // nombre, tipo, co2_evitado_kg, peso_estimado_kg, ...
  };
  setHistorial((prev) => [nuevo, ...prev]);
  return nuevo;
};
Called immediately after a successful /clasificar response in App.jsx, before the result view is rendered.
Resets the history array to [], which in turn clears localStorage via the useEffect sync. All stats drop to zero, the rank resets to Semilla Eco, and the empty-state screen is shown.
const limpiarHistorial = () => setHistorial([]);
This is a destructive, irreversible action. There is no confirmation dialog — the button in the Historial tab triggers it directly.
A computed object derived from the full history array on every render:
const stats = {
  totalItems: historial.length,
  co2Total:   historial.reduce((acc, r) => acc + (r.co2_evitado_kg  || 0), 0),
  pesoTotal:  historial.reduce((acc, r) => acc + (r.peso_estimado_kg || 0), 0),
  porTipo:    historial.reduce((acc, r) => {
                acc[r.tipo] = (acc[r.tipo] || 0) + 1;
                return acc;
              }, {}),
  puntos:     historial.reduce((acc, r) => {
                if (r.tipo === "no_reciclable") return acc + 2;
                if (r.tipo === "peligroso")     return acc + 5;
                return acc + 10;
              }, 0),
};
FieldTypeDescription
totalItemsnumberCount of all records
co2TotalnumberSum of CO₂ saved (kg)
pesoTotalnumberSum of estimated weights (kg)
porTipoRecord<string, number>Item count grouped by category key
puntosnumberTotal eco points
The hook reads from and writes to localStorage under the key quipueco_historial:
const STORAGE_KEY = "quipueco_historial";

// Hydration on mount
const [historial, setHistorial] = useState(() => {
  try {
    const guardado = localStorage.getItem(STORAGE_KEY);
    return guardado ? JSON.parse(guardado) : [];
  } catch {
    return [];
  }
});

// Sync on every change
useEffect(() => {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(historial));
}, [historial]);
Each record in the stored array includes the base64 image data URL in the imagen field. On devices with very limited localStorage quota, a large number of high-resolution images may cause a QuotaExceededError. For production use, consider storing images separately or as references to IndexedDB.

Dashboard Tabs

1

Por tipo — category breakdown

Iterates over stats.porTipo sorted descending by count. Each category renders a label badge (with emoji and colour from TIPO_CONFIG) and a relative-width progress bar showing that category’s share of total items.
const porcentaje = Math.round((cantidad / stats.totalItems) * 100);
2

Tendencia — cumulative CO₂ area chart

Renders a Recharts AreaChart with cumulative CO₂ data. The history array is reversed (oldest first), then reduced to produce a running total:
const chartData = [...historial]
  .reverse()
  .reduce((acc, item, index) => {
    const prevVal = index > 0 ? acc[index - 1].co2 : 0;
    const fecha = new Date(item.fecha).toLocaleDateString("es-PE", {
      day: "2-digit", month: "short",
    });
    acc.push({
      name: fecha,
      co2: Number((prevVal + (item.co2_evitado_kg || 0)).toFixed(2)),
    });
    return acc;
  }, []);
The chart uses an emerald gradient fill (#10b981 at 40% opacity → 0%) and a 2 px stroke. The custom Tooltip is styled to match the dark app theme.
3

Historial — last 10 entries

A scrollable max-h-64 list showing the 10 most recent records (historial.slice(0, 10)). Each row displays:
  • Thumbnail image (w-11 h-11 rounded square, source from the imagen data URL)
  • Item name and category badge
  • CO₂ saved in kg and classification date in es-PE locale
Below the list, a destructive “Limpiar Historial” button calls onLimpiar (which maps to limpiarHistorial()).
<button onClick={onLimpiar}>
  <Trash2 /> Limpiar Historial
</button>

Empty State

When stats.totalItems === 0, the component renders an empty-state card instead of any metrics or tabs:
if (stats.totalItems === 0) {
  return (
    <div>
      <Leaf /> {/* icon */}
      <h3>Tu impacto empieza aquí</h3>
      <p>
        Clasifica tu primer residuo con la cámara IA para comenzar a
        registrar tus métricas de reciclaje y acumular puntos.
      </p>
    </div>
  );
}
This state is shown to every new user and after limpiarHistorial() is called.
The “Limpiar Historial” button in the Historial tab permanently deletes all classification records from localStorage. There is no undo or confirmation prompt. Rank, points, CO₂ totals, and all history thumbnails are immediately lost. Warn users before they tap this button if building a production UI.

Build docs developers (and LLMs) love