QuipuEco follows a clean two-tier architecture: a stateful React SPA handles all user interaction, routing, and media capture in the browser, while a thin FastAPI service owns all AI inference and collection-point data. There is no shared database, no authentication layer, and no WebSocket connection — every interaction is a discrete HTTP POST that resolves to a JSON payload the frontend immediately renders. This keeps the system auditable, easy to deploy, and straightforward to extend with new waste categories or collection networks.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.
System Data Flow
Frontend Architecture
The entire frontend lives in a singleApp.jsx component that uses React’s useState to drive routing between four named views. There is no React Router — navigation is purely state-based.
State-Driven Routing
Thevista state variable is the single source of truth for which view is rendered:
vista value | Component rendered | Triggered by |
|---|---|---|
"captura" | <ImageCapture> | App load, reset button, Clasificar nav tab |
"resultado" | <ClassificationResult> | Successful /clasificar API response |
"mapa" | <VistaMapaPuntos> (fullscreen overlay) | “Ver mapa” button, voice agent mapa action |
"dashboard" | <ImpactDashboard> | Mi Impacto nav tab, voice agent dashboard action |
AgenteVoz) is rendered as an absolute-positioned overlay on top of the active view. It is controlled by a separate agenteAbierto boolean rather than the vista state, allowing it to float over both the "resultado" and "captura" views simultaneously.
Component Responsibilities
- ImageCapture
- ClassificationResult
- AgenteVoz
- VistaMapaPuntos
- ImpactDashboard
File:
src/components/ImageCapture.jsxHandles the two image input modes — file upload (with drag-and-drop) and live camera capture. When a file is selected or a frame is captured from the getUserMedia video stream, the component renders a preview and exposes a Clasificar Residuo button.On submit, it constructs a FormData object and calls POST http://localhost:8000/clasificar. On success, it passes the API response and the image preview URL to the onResult callback in App.jsx, which transitions vista to "resultado".The useHistorial Hook
File: src/hooks/useHistorial.js
This custom hook provides localStorage persistence for all classification records. It is the only form of data persistence in the application — there is no user account, no server-side database, and no session storage.
stats object is computed on every render as a derived value from historial:
GSAP Animations
On desktop viewports,App.jsx uses GSAP to animate the hero mascot, title, subtitle, feature cards, and footer on first render. All animations are scoped with gsap.context() and reverted on unmount. The mascot also has interactive hover (wiggle) and click (360° spin) animations. These animations are guarded by a ref check — if the mascot element does not exist in the DOM (i.e., on mobile), no animation is attempted.
Backend Architecture
API Endpoints
The FastAPI backend exposes two POST endpoints. Both use thegemini-3.1-flash-lite model, accessed via the Google Generative AI Python SDK.
- POST /clasificar
- POST /chat
Accepts a multipart form upload with a single Response fields:
file field containing an image (JPEG, PNG, WEBP, max ~10 MB in practice).The endpoint:- Reads the image bytes and encodes them for the Gemini multimodal API
- Sends the image with a structured classification prompt from
prompts.py - Parses the Gemini response into the Pydantic response model (
models.py) - Returns a JSON object with all fields needed by
ClassificationResult.jsx
| Field | Type | Description |
|---|---|---|
nombre | string | Human-readable item name in Spanish |
tipo | string | Material category (plastico, papel, vidrio, organico, metal, electronico, peligroso, no_reciclable) |
instrucciones | string | Preparation instructions for Lima collection points |
punto_acopio | string | Collection network name (Tiendas Tambo or Centro Verde Municipal) |
co2_evitado_kg | float | Estimated CO₂ savings in kilograms |
peso_estimado_kg | float | Estimated item weight in kilograms |
consejo | string | Eco tip related to the classified material |
respuesta_voz | string | Short TTS-optimised announcement for the voice agent |
Haversine Distance Calculation
The backend’sutils.py implements the Haversine formula to compute great-circle distances between the user’s GPS coordinates and every collection point in data/puntos.py. The same formula is also implemented in the frontend’s VistaMapaPuntos.jsx to sort the visible card list by proximity after geolocation is acquired.
Collection Point Data (data/puntos.py)
All collection point coordinates are stored as static Python data structures in data/puntos.py. The data is partitioned by waste category, and the plastico, papel, and metal categories all point to the same TAMBO_REAL_LIMA_ESTE list. This partitioning is enforced both in the backend (for nearest-point calculation) and in the frontend VistaMapaPuntos.jsx (for map rendering).
The Two Collection Networks
The category-to-network routing is a hard constraint in both frontend and backend code, not a runtime preference.- Tambo Network
- Centro Verde Network
Categories:
plastico, papel, metalTambo convenience stores accept dry, clean recyclables that are safe to handle in a retail environment. No appointment is required — items are handed directly to store staff at the counter.The frontend uses the Tambo logo image as a custom Mapbox marker icon for these categories. The RED_POR_CATEGORIA mapping in VistaMapaPuntos.jsx controls which badge and marker style is displayed.Participating stores (Lima Este pilot):| Store | District | Coordinates |
|---|---|---|
| Tambo Corregidor | La Molina | -12.0815, -76.9398 |
| Tambo Alondras | Santa Anita | -12.0445, -76.9678 |
| Tambo Paracas | Ate | -12.0398, -76.9156 |
| Tambo Ayllón | Chaclacayo | -11.9821, -76.7701 |
| Tambo Riva Agüero | El Agustino | -12.0478, -76.9967 |
| Tambo 28 de Julio | Chosica | -11.9356, -76.6945 |
Environment Variables
QuipuEco requires two environment variables, one per service. Neither variable is committed to version control.| Variable | Service | File | Description |
|---|---|---|---|
VITE_MAPBOX_TOKEN | Frontend | quipueco-frontend/.env | Mapbox public access token. Exposed to the browser bundle via Vite’s import.meta.env. Used in VistaMapaPuntos.jsx for map tiles and the Directions API. |
GEMINI_API_KEY | Backend | quipueco-backend/.env | Google AI Studio API key. Read server-side only via config.py. Never sent to the browser. |
Environment Variable Configuration
See the full environment variable reference, including how to obtain each credential, set up local
.env files, and configure the app for production deployment.