
End-to-End PropTech Platform (Auge Urbano)
Real-estate portal deployed on a VPS: 159 Flask routes over MySQL 8 with 45 tables, an in-house CRM, partner and street-scout portals, embedded AI agents and a SEO/GEO/AEO stack with IndexNow. ~43,000 lines of Python that run the whole business, not a landing page with a form.
Auge Urbano sells residential property in Medellín and the Aburrá Valley. The platform is not a brochure: it is the system the business runs on — a listing catalogue with ~900 photos, a lead and closing CRM, a portal for partner agents, a portal for street scouts, blog, finance and a ranking engine built for Google and for LLMs alike.
Real screenshots of the system in operation.
The challenge
- 1One system, three audiences with their own session: buyer, partner agent and street scout.
- 2Run the business without an external CRM: leads, contacts, viewings, closings, income and expenses in the same admin.
- 3Rank a small catalogue in a saturated market and make it citable by LLMs, not just indexable by Google.
- 4Serve ~25 photos per listing without wrecking LCP on mid-range phones.
- 5Normalise Aburrá Valley geography (municipality → commune/rural district → neighbourhood) for filters, URLs and GEO signals.
- 6Expose AI agents to the internet without opening the door to prompt injection or leaking integration keys.
The approach
Modularised Flask monolith
159 routes in a single process, with the domain split across 33 Python modules (geo, amenities, SEO, AI, finance, payments, mail) imported as packages instead of a new framework per feature.
Switchable dual persistence
MySQL 8 InnoDB utf8mb4 in production (45 tables, 440 columns, 21 foreign keys, 115 indexes) with SQLite as a rescue mode via AUGE_FORCE_SQLITE; the same code creates and backfills the schema on both engines.
Portals with their own auth
Unlinked admin, a /colega portal (signup, listings, agenda, orders, email password reset) and a scout portal with phone + municipality dedup and a bonus settled when the sale closes.
In-house SEO/GEO/AEO stack
14 intent landings plus neighbourhood-driven dynamic landings, RealEstateListing/FAQPage JSON-LD, a sitemap index with 99 URLs and 800 images, and automatic pings to IndexNow and the Google Indexing API on publish.
Embedded AI, not a demo
Commercial advisor on landings, a business copilot that answers on KPIs with Chart.js charts, and bulk SEO/AEO generation per listing; OpenAI or Gemini with provider fallback and prompt presets versioned in the database.
Audited WebP pipeline
Automatic conversion on upload, per-file log in the database and an optimisation modal in the admin: 1,338 recorded conversions, from 459 MB down to 53 MB of source images.

Nginx (TLS, canonical apex, 301 from www) → Gunicorn 3 workers x 2 threads → modular Flask monolith with 159 routes → MySQL 8 InnoDB (45 tables) plus an uploads volume; on top, AI agents (public advisor, business copilot, listing SEO) and the discovery pack of sitemaps + llms.txt + ai.txt.
System layers
- 01Nginx: Let's Encrypt TLS, 301 www → apex, body up to 300 MB
- 02Gunicorn: 3 workers x 2 threads, 120s timeout
- 03Flask: 159 routes (public, admin, /colega, /captador, JSON API)
- 04Domain: 33 Python modules (geo, amenities, SEO, AI, finance, payments)
- 05MySQL 8 InnoDB utf8mb4: 45 tables with idempotent self-creating schema
- 06/app/instance volume: rescue database and lead/scout uploads
- 07AI: OpenAI/Gemini with fallback, guards and keys in integration_settings
- 08Discovery: robots, sitemap index, image sitemap, llms.txt, ai.txt, JSON-LD catalogue
Applied stack
- Python 3.12
- Flask
- Gunicorn
- MySQL 8 / InnoDB
- PyMySQL
- Jinja2
- Pillow
- OpenAI + Gemini
- Bold Link API
- GA4 + Search Console
- Docker
- Nginx
Design decisions
MySQL 8 in production with SQLite as switchable fallback
A catalogue with ~900 media items, business aggregates and the geo master needs real InnoDB indexes; SQLite stays as a recovery path if MySQL or its credentials fail, running the very same schema code.
Modular monolith instead of microservices
A business run by a small team does not need network boundaries: boundaries live in Python modules and operating cost stays at one container plus a proxy.
Idempotent schema on boot instead of migrations
ensure_*_schema functions plus seeds create and backfill tables on every boot; a reproducible deploy without maintaining a migration runner for a single operator.
AEO and GEO as data, not as template
Every listing stores its primary question, answer snippet, extra FAQ and coordinates: what an LLM can cite is edited in the admin, not improvised in the HTML.
Own guards before exposing AI to the public
Internal markers, prompt-injection and SQLi detection, honeypot and in-memory rate limiting: enough protection without adding dependencies or a paid WAF.
Outcomes
- 159
- Flask routes in production
- 45
- InnoDB tables (440 columns, 21 FKs)
- -88%
- Image weight after WebP (459 → 53 MB)
- 597
- Normalised geo nodes (22 municipalities · 232 zones · 343 neighbourhoods)
- 99
- Automated tests with pytest
- 20
- Event types in first-party telemetry
Key points
- Real deployment: a Gunicorn container behind Nginx with Let's Encrypt TLS and a canonical apex — not a local demo.
- Full real-estate CRM (leads, contacts, viewings, closings, referrals, income and expenses) with no per-seat fee.
- AI agents exposed to the internet behind prompt-injection and SQLi guards, honeypot and rate limiting on public forms.
- Colombian closing costs encoded in the simulator: notary 0.54%, charity levy 1%, registry 0.67% and 1% / 2.5% withholding by threshold.
- First-party funnel telemetry (form_start → form_change → form_submit) so the business does not depend on GA4 alone.
Fit / how to hire
Hire this if you need a full-stack who understands the real-estate vertical end to end — catalogue, CRM, third-party portals, payments, SEO/AEO and AI agents — and leaves it running on your own infrastructure instead of spread across five SaaS tools. Discovery → working product → automation and AI once volume justifies it.
Book a technical call






