H1Trip Planner — Current Pattern (Canonical)
H2Identity
Trip Planner = deterministic form → normalized stops → pricing engine → persisted Trip entity
H2Flow (locked)
Client → TripPlannerContext → normalizeStops → setForm → (create|update)TripFromPlanner → Prisma → revalidate → Trip Engine → UI
H2Core Concepts
H3Stops (single source of truth)
form.stops[]= canonical route- Each stop:
- cityId
- nights
- computed role
- services (transport, guide, tours)
H3Roles (derived, never user-controlled)
- index 0 → arrival
- last index → departure
- Córdoba + Granada → core
- else → extension
H3Determinism Layer
normalizeStops()computeDisplayRole()businessRank()
Guarantees:
- No duplicate cities
- Core cities always present
- Stable ordering
- Valid nights constraints
H2Steps (UI abstraction, not domain)
- basics
- route
- services
- hotels
- options
⚠️ Steps are UI only — state is continuous
H2Services Model
- defaults at form level
- per-stop override
- optional "apply to all"
H2Hotels Model
- derived from
numPax - override allowed
H2Persistence
- create →
createTripFromPlanner - edit →
updateTripFromPlanner - autosave in edit mode
H2Assistant
- prompt → AI intent → merge → complete → normalized form → persist
H2Invariants
- no Prisma outside actions
- no mutation in pages
- form is always valid after normalize
- engine never trusts UI