These routes power the visitor-facing side of BioScan Museo. They render HTML pages rather than JSON, but their behaviour — including which data is recorded, when scan events are logged, and how visit milestones are tracked — is relevant for integrators building kiosk frontends, QR-scanning apps, or custom analytics on top of the platform. All four routes are publicly accessible without authentication, though authenticated visitors receive a richer experience.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/GustavoNightmare/InformacionMuseo/llms.txt
Use this file to discover all available pages before exploring further.
GET /especies
Displays the full species catalogue as an HTML page. Supports a text search that filters across the QR identifier, common name, and scientific name fields. Authentication: None required. When the visitor is authenticated, the page additionally shows which species they have already scanned (scanned_ids) and the total count of their unique scans (scanned_count).
Query parameters
Optional free-text search string. The filter is applied case-insensitively against
qr_id, nombre_comun, and nombre_cientifico using SQL ILIKE (%q%). If omitted, all species are returned in ascending alphabetical order by common name.Template context
The following values are passed to theespecies.html template:
| Variable | Type | Description |
|---|---|---|
items | list[Species] | Filtered list of species matching q, ordered by nombre_comun ascending. |
q | string | The current search string (empty if not provided). |
total_count | int | Total number of species in the database, regardless of the search filter. |
scanned_count | int | Number of distinct species the authenticated user has visited. 0 for unauthenticated visitors. |
scanned_ids | set[str] | Set of species_id values the authenticated user has scanned. Empty for unauthenticated visitors. |
scan_counts | dict[str, int] | Maps each species_id to the number of distinct users who have visited it (sourced from the Visit table). |
Example
GET /especie/<qr_id>
Renders the full detail page for a single species identified by its QR slug. This is the primary landing page for visitors who scan a physical QR code or navigate the catalogue manually. Authentication: None required. Authenticated visitors see additional UI elements (e.g., “already scanned” indicators, a link to the AI chat). Admins who navigate withorigin=manual have an viewed_admin audit event recorded in SpeciesAuditLog (deduplicated within a 5-minute window).
Path parameter
The species’ QR identifier. Normalised server-side by
sanitize_id() and validated against ^[a-z0-9-_]+$. Returns 404 if no species matches.Query parameters
Optional scan origin hint. Accepted values are
web and manual. When provided, the server calls record_scan_event() and creates a ScanEvent record with the given origin. Has no effect if omitted or set to qr (QR scans are recorded by the /scan/<qr_id> route instead). Defaults to no scan event being recorded when visiting the detail page directly.When set to
"1", the template sets show_celebration=True and displays a milestone animation. This parameter is injected automatically by the /scan/<qr_id> redirect when the visitor has just completed their tour of all species.Behaviour notes
qr_idis lowercased and stripped of any characters not in[a-z0-9-_]before the database lookup.- When
originiswebormanual,record_scan_event()is called regardless of authentication status. The resultingScanEventrow stores the visitor’suser_idif they are logged in, orNULLif anonymous. - The
is_scannedflag passed to the template isTrueonly when the authenticated user has aVisitrecord for this species.
Example
GET /scan/<qr_id>
The canonical QR-scan entry point. Physical QR codes in the museum encode the species’qr_id as the URL path. This route records the scan event, optionally records a Visit for authenticated users, and then redirects to the species detail page.
Authentication: None required. Works for anonymous visitors.
Path parameter
The species’ QR identifier. Normalised and validated identically to
/especie/<qr_id>. Returns 404 if the species does not exist.Query parameters
Optional scan origin. Normalised by
normalize_scan_origin(). Accepted values: qr, web, manual. Any other value (including absent) defaults to qr. This value is stored in the ScanEvent.origin column.Behaviour
- Always:
record_scan_event()is called with the resolved species, the caller’suser_id(orNoneif anonymous), and the normalisedorigin. AScanEventrow is inserted immediately. - Authenticated only: If no
Visitrecord exists for this user+species pair, a newVisitis inserted. - Milestone check: After inserting the
Visit, the server compares the user’s unique visit count against the total species count. If they match and both are greater than zero,just_completedis set toTrue. - Redirect: The visitor is redirected to
/especie/<qr_id>. Ifjust_completedisTrue, the redirect includes?celebration=1.
Example
The redirect destination is always
/especie/<qr_id>, using the qr_id stored in the database (not the raw path segment), so canonicalisation is applied even if the QR code encodes an older slug.GET /especies/comparar
Renders an HTML side-by-side comparison page for two species. Both species must be taxonomically related — sharing the samefamilia or the same orden — or the request is rejected with a flash error and a redirect to /especies.
Authentication: None required.
Query parameters
The
qr_id of the first species. Sanitised and validated against ^[a-z0-9-_]+$.The
qr_id of the second species. Must differ from a.Validation
The helperget_species_pair_for_comparison() applies the following checks in order. On the first failing check, a flash error message is set and the visitor is redirected to /especies:
- Both
aandbmust be non-empty and match^[a-z0-9-_]+$. aandbmust be different values.- Both species must exist in the database.
- The species must share
familiaororden(normalised to lowercase for comparison vianormalize_taxonomy()). If neither matches, the error is “Solo puedes comparar especies de la misma familia u orden.”
Template context
| Variable | Type | Description |
|---|---|---|
item_a | Species | First species object. |
item_b | Species | Second species object. |
same_family | bool | True if both species share the same familia. |
same_order | bool | True if both species share the same orden. |
comparison_rows | list[dict] | Side-by-side field comparison built by build_species_comparison_rows(). Each row has label, a (value for species A), and b (value for species B). Fields covered: QR ID, common name, scientific name, family, order, habitat, diet, zones, description, and curiosities. |
Example
This HTML page loads the AI-powered analysis asynchronously via
GET /api/especies/comparar/analysis. The two routes share the same validation logic (get_species_pair_for_comparison()), so any pair that passes the HTML route’s validation will also be accepted by the JSON analysis endpoint.