## 🎨 Technical Specification: Issue #9 - Property Navigation & Site Architecture ### Architecture Overview ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ FRONTEND β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ index.html │──▢│ property.htmlβ”‚ β”‚ admin.html β”‚ β”‚ β”‚ β”‚ (Catalog) β”‚ β”‚ (Detail) β”‚ β”‚ (Dashboard) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ app.js β”‚ β”‚ property.js β”‚ β”‚ β”‚ β”‚ - renderCard β”‚ β”‚ - loadProp β”‚ β”‚ β”‚ β”‚ - filters β”‚ β”‚ - gallery β”‚ β”‚ β”‚ β”‚ - map β”‚ β”‚ - similar β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ api.js β”‚ β”‚ β”‚ β”‚ getProperties, getProperty β”‚ β”‚ β”‚ β”‚ getFeatured, trackEvent β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ HTTP/JSON β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ BACKEND β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Bun + Hono (src/server/index.ts) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ GET /api/properties β†’ list + filter β”‚ β”‚ β”‚ β”‚ GET /api/properties/:slug β†’ single + increment β”‚ β”‚ β”‚ β”‚ GET /api/properties/featured β†’ featured list β”‚ β”‚ β”‚ β”‚ GET /api/cities β†’ distinct cities β”‚ β”‚ β”‚ β”‚ POST /api/analytics/event β†’ track β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ SQLite (data/tenerifeprop.db) β”‚ β”‚ β”‚ β”‚ tables: properties, leads, testimonials, faq, β”‚ β”‚ β”‚ β”‚ services, settings, users, sessions β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### Component Changes #### 1. **public/index.html** β€” Property Card Navigation & Site Map **Current State**: Cards already link to `/property/${prop.slug}` via `` in `renderPropertyCard()`. Map popup lacks click-through navigation. **Changes Required**: - Add `data-property-id` and `data-property-slug` attributes to each property card wrapper for analytics tracking - Wire map marker popup click β†’ `window.location.href = '/property/' + slug` - Add `rel="canonical"` meta tag to `` - Add `` to `` - Footer property links (lines 1886-1890) need href wired to `#catalog` with type filter - Hero floating cards need href to specific properties **Specific changes**: ```html ... ...
  • Terrenos agrΓ­colas
  • Terrenos urbanos
  • ... ``` #### 2. **public/js/app.js** β€” Card Click Tracking & Map Navigation **Current State**: `renderPropertyCard()` generates cards with `` links already. Map markers show popup but don't navigate. **Changes Required**: - Modify `updateMapMarkers()` to add click handler on popup that navigates to property detail - Add `data-property-slug` attribute to card root element for analytics - Add smooth scroll handler for footer filter links - Add `navigateToProperty(slug)` helper method ```javascript // In updateMapMarkers(): marker.on('popupopen', () => { const popupEl = marker.getPopup().getElement() if (popupEl) { popupEl.style.cursor = 'pointer' popupEl.addEventListener('click', () => { window.location.href = `/property/${prop.slug}` }) } }) // In renderPropertyCard(), add to root div:
    // New method: navigateToProperty(slug) { window.location.href = `/property/${slug}` } // Footer filter link handler: document.querySelectorAll('.footer-links a[data-filter]').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault() const filter = e.target.dataset.filter window.location.href = `/#catalog` sessionStorage.setItem('pendingFilter', filter) }) }) // In init(), check for pending filter: const pendingFilter = sessionStorage.getItem('pendingFilter') if (pendingFilter) { sessionStorage.removeItem('pendingFilter') setTimeout(() => this.setFilter(pendingFilter), 500) } ``` #### 3. **public/js/property.js** β€” Breadcrumb & Similar Properties Navigation **Current State**: `property.js` loads property by slug, renders gallery, features, utilities, similar properties. Similar properties link to `property.html` (static). Breadcrumb is hardcoded. **Changes Required**: - Update `renderSimilarProperties()` to use dynamic slugs (already uses `prop.slug`) - Add `updateBreadcrumb()` method to set dynamic breadcrumb from property data - Add "Back to catalog" button with scroll-to-catalog behavior - Wire share buttons with dynamic URLs ```javascript // New method: updateBreadcrumb() { const items = document.querySelectorAll('.breadcrumb-item a, .breadcrumb-item.active') if (items.length >= 4 && this.property) { items[2].textContent = this.getTypeLabel(this.property.type) items[3].textContent = this.property.title } } // Call in updatePage(): this.updateBreadcrumb() // Update share methods to use dynamic data: shareProperty() { const url = window.location.href const title = this.property?.title || 'Property' if (navigator.share) { navigator.share({ title, url }) } else { navigator.clipboard.writeText(url) this.showNotification('Enlace copiado', 'success') } } ``` #### 4. **public/js/api.js** β€” Add Missing Endpoints **Current State**: Has `getProperties`, `getProperty`, `getFeaturedProperties`, `createLead`, `getLeads`, `getTestimonials`, `getFAQ`, `getServices`, `getSettings`, `trackEvent`, `getCities`. **Changes Required**: - Add `getSitemap()` method for sitemap generation - Add `getNavigationData()` method for consistent nav across pages ```javascript // New methods: static async getSitemap() { const response = await fetch('/api/sitemap') return response.json() } static async getNavigationData(lang = 'es') { const [cities, featured] = await Promise.all([ this.getCities(), this.getFeaturedProperties(lang) ]) return { cities: cities.data, featured: featured.data } } ``` #### 5. **public/property.html** β€” Consistent Navigation & Breadcrumb **Current State**: Navbar links point to `index.html` and `index.html#section`. Breadcrumb is static. Similar property cards link to `property.html` (no slug). **Changes Required**: - Update all navbar links to use proper paths - Make breadcrumb dynamic via JS (see property.js changes) - Update similar property card links to use `prop.slug` - Add `data-property-id` meta tag for analytics ```html ``` #### 6. **src/server/index.ts** β€” Sitemap & Navigation API **Current State**: Has all property/lead/content endpoints. No sitemap endpoint. **Changes Required**: - Add `GET /api/sitemap` endpoint returning XML sitemap - Add `GET /sitemap.xml` static route or dynamic generation ```typescript // New endpoint: app.get('/api/sitemap', (c) => { const properties = db.query('SELECT slug, updated_at FROM properties WHERE published_at IS NOT NULL AND status = ?').all('active') let xml = ` https://tenerifeprop.com/ ${new Date().toISOString()} 1.0 https://tenerifeprop.com/#catalog 0.8 ` properties.forEach((p: any) => { xml += ` https://tenerifeprop.com/property/${p.slug} ${p.updated_at} 0.7 ` }) xml += '\n' c.header('Content-Type', 'application/xml') return c.body(xml) }) ``` #### 7. **Mobile Navigation** β€” Responsive Improvements **Current State**: Bootstrap navbar with collapse. Language switcher in navbar. No mobile-specific optimizations for property cards. **Changes Required**: - Add mobile bottom navigation bar for property detail page - Ensure map is usable on mobile (touch-friendly) - Add swipe navigation for property gallery (already exists in property.js) - Add "sticky CTA" on mobile property pages ```html
    Llamar WhatsApp
    ``` ### Data Flow ``` User clicks property card (index.html) β†’ window.location.href = '/property/{slug}' β†’ property.html loads β†’ property.js init() β†’ getPropertySlug() from URL β†’ API.getProperty(slug, lang) β†’ Backend: SELECT * FROM properties WHERE slug = ? β†’ Backend: views_count++ β†’ Response: { success: true, data: { ...property, title, description } } β†’ updatePage() renders all sections β†’ loadSimilarProperties() β†’ API.getProperties({ type, city, limit: 3 }) β†’ renderSimilarProperties() with links back to /property/{slug} ``` ### API Contracts | Method | Endpoint | Input | Output | |--------|----------|-------|--------| | GET | `/api/properties` | `?type=&city=&minPrice=&maxPrice=&lang=&limit=&offset=` | `{ success, data: Property[], total }` | | GET | `/api/properties/:slug` | `?lang=` | `{ success, data: Property }` | | GET | `/api/properties/featured` | `?lang=` | `{ success, data: Property[] }` | | GET | `/api/sitemap` | - | XML sitemap | | GET | `/api/cities` | - | `{ success, data: string[] }` | | POST | `/api/analytics/event` | `{ type, session_id, ...data }` | 200 OK | **Property Response Shape** (from `/api/properties/:slug`): ```json { "success": true, "data": { "id": "prop-001", "slug": "terreno-urbano-adeje", "title": "Terreno Urbano en Adeje", "description": "...", "city": "Adeje", "zone": "Costa Adeje", "address": "Avda. de la ConstituciΓ³n", "price": 385000, "area": 2500, "type": "urban", "images": "[{\"url\":\"...\",\"alt\":\"...\"}]", "lat": 28.1227, "lng": -16.6942, "water": "available", "electricity": "available", "road": "asphalt", "is_featured": 1, "is_exclusive": 1, "views_count": 42 } } ``` ### Implementation Checklist - [ ] **1. Property Card Navigation** - [ ] Add `data-property-slug` to card root in `renderPropertyCard()` (app.js) - [ ] Add click handler on map popup β†’ navigate to property (app.js) - [ ] Wire hero floating cards to specific property slugs (index.html) - [ ] Wire footer property links to catalog with type filter (index.html + app.js) - [ ] **2. API Data Integration** - [ ] Add `getSitemap()` method (api.js) - [ ] Add `getNavigationData()` method (api.js) - [ ] Add `/api/sitemap` endpoint returning XML (server/index.ts) - [ ] **3. Consistent Site Navigation** - [ ] Update breadcrumb to be dynamic on property page (property.js) - [ ] Ensure navbar links are consistent across all pages (index.html, property.html) - [ ] Add "Back to catalog" link on property page - [ ] **4. User Workflow (B2B Client Journey)** - [ ] Add pending filter persistence via sessionStorage (app.js) - [ ] Track property view events with `trackEvent()` (already exists, verify) - [ ] Add lead form pre-fill with property context (property.js) - [ ] **5. Site Map Implementation** - [ ] Create `/api/sitemap` XML endpoint (server/index.ts) - [ ] Add `` to index.html `` - [ ] Include all published properties + main pages - [ ] **6. Data Connectivity** - [ ] Verify similar properties use correct slugs (property.js) - [ ] Verify share buttons use dynamic URLs (property.js) - [ ] Add `data-property-id` meta tag to property.html - [ ] **7. Site Consistency** - [ ] Unify CSS variables across index.html and property.html (already consistent) - [ ] Ensure footer is identical across pages - [ ] Verify language switcher works on all pages - [ ] **8. Mobile Navigation** - [ ] Add mobile bottom navigation bar to property.html - [ ] Verify map is touch-friendly on mobile - [ ] Verify gallery swipe works (already exists) - [ ] Add responsive CSS for mobile bottom nav ### Technical Debt Notes 1. **Duplicate HTML**: `index.html` contains a nested/wrapper HTML structure (lines 1-15 are a wrapper DOCTYPE). Should be cleaned up but not blocking. 2. **Inline translations**: Both `index.html` and `property.html` embed full translation objects inline. Should be consolidated into external JSON files loaded via `api.js` or `i18n.js`. 3. **Static property.html**: The property page HTML contains hardcoded content that gets overwritten by JS. Consider using a minimal template skeleton. 4. **No server-side routing**: Property pages use client-side URL matching (`/property/:slug`). The server serves `property.html` for all paths. Consider adding proper routing if SEO becomes critical. 5. **Mixed JS patterns**: `app.js` uses vanilla class pattern, `property.html` inline script uses jQuery. Should standardize on one approach. 6. **Missing error states**: No 404 page for invalid property slugs. `property.js` calls `showError()` but replaces entire `
    ` content. --- Status: designed @SDETEngineer ready for test creation @lead-developer ready for implementation