---
url: https://docs.nemo12.com/architecture/sdd-038/nemo-walk.md
description: >-
  NEMO WALK: khoá đi bộ luyện nói, đổi tên ba khoá Speaking, trang tối giản, hồ
  sơ một câu một màn và quiz luật 10/10
---

# SDD-038 · NEMO WALK

> Một phần của [SDD-038 Cổng IELTS](index.md). Phần đầu là đặc tả đang áp dụng, mỗi mục giữ số § cũ; diễn biến và lý do các vòng quyết định nằm ở mục **Lịch sử quyết định** cuối trang.

## 146. NEMO WALK và đổi tên ba khoá Speaking (SRC-1136, 29.09.2026)

Chủ dự án 29.09.2026: thêm chuỗi hoạt động **NEMO WALK** bên cạnh các khoá, và đổi tên
NEMO SPEAK 1 → **NEMO START**, NEMO SPEAK 2 → **NEMO SPEAK 1**, NEMO SPEAK 3 → **NEMO SPEAK 2**.

**Đổi tên: tên và URL đổi, id nội bộ giữ.** `course_id` trong D1 (`speak-2`, `speak-3`, migration 0312
đã áp), đơn và mã kích hoạt đã phát đều giữ nguyên. Bảng nối duy nhất slug ↔ id ↔ tên:
`apps/learn/src/ielts/speakSlugs.ts` (SpeakPair.tsx re-export). Bảng không nằm thẳng trong
SpeakPair.tsx vì router.ts cần nó, còn SpeakPair.tsx import Shell, Shell import router: đặt ở đó là
vòng import. Phía worker, `COURSES[id].slug` trong `speakCourses/cohort.ts` là bản sao; test
`cohortAgrees.test.ts` giữ tên và slug hai bên khớp.

| Tên mới | Id nội bộ | `/speaking/...` | `/ielts/speaking/pair/...` |
| --- | --- | --- | --- |
| NEMO START | `speak-1` | `start` | `start` |
| NEMO SPEAK 1 | `speak-2` | `speak-1`, `speak-1/register` | như cột trái |
| NEMO SPEAK 2 | `speak-3` | `speak-2`, `speak-2/register` | như cột trái |
| NEMO WALK | (không có) | `walk` | `walk` |

* URL cũ mới sống một ngày: `/speak-3` và `/speak-3/register` chuyển sang `/speak-2...` bằng
  `replaceState` (không thêm mục lịch sử). `/speak-1`, `/speak-2` cũ nay mang nghĩa MỚI: chủ dự án chấp
  nhận.
* Thư đơn và thư mã kích hoạt trỏ link theo slug mới (`courseSlug`), không theo id nội bộ.
* Tiền tố nội dung chuyển khoản và mã (`SPK2-`, `SPK3-`, `SPEAK2-`, `SPEAK3-`) **không đổi** cho đơn mới:
  chúng là chuỗi mờ, đổi thì phải đổi cả phép kiểm mã và hai kiểu mã cùng sống song song. Hệ quả là
  một đơn NEMO SPEAK 1 mang mã `SPK2-...`: chấp nhận được, ghi ra đây để không ai "sửa cho khớp".
* Admin (tab Đơn SPEAK) hiện tên mới theo id.

**NEMO WALK.** Hoạt động luyện Speaking miễn phí, **Chủ nhật hằng tuần 15:00-16:30** giờ Việt Nam (90
phút), ở khu Phố Cổ Hà Nội. Lịch qua ba lượt chỉ đạo cùng ngày 29.09.2026: 09:00 thứ Bảy 60 phút →
15:00-16:30 thứ Bảy → **15:00-16:30 Chủ nhật** (bản hiện hành). Không trùng lớp nào: NEMO SPEAK 1 (id
`speak-2`) học Chủ nhật 10:00-12:00, xong trước ba tiếng.

* **Trang** `/speaking/walk` (và `/ielts/speaking/pair/walk`): chữ giới thiệu nguyên văn của chủ dự án (em
  dash đổi thành dấu hai chấm), dòng "Every Sunday 15:00 · Hanoi Old Quarter · Free", danh sách "What you
  will do", thẻ "Next walk", rồi lịch các tuần sau. Mỗi tuần: tên khu, ngày giờ, điểm hẹn, chủ đề,
  Speaking Mission, "n going" và một trong Join / Cancel / You are in / Closed.
* **Thẻ thứ tư ở `/speaking`** vẫn chỉ xem, không nút: ảnh (cảnh `walk` trong PairScene: hồ, cầu đỏ, cây),
  tên, "Every Sunday 15:00 · For SPEAK 1 and SPEAK 2 learners".
* **Bốn khu luân phiên** theo tuần: Hoan Kiem Lake, Old Quarter (Phố Cổ), Food Tour (ăn vặt Phố Cổ),
  Hanoi Cathedral (Nhà Thờ Lớn). Tám tuần đầu là 8 Chủ nhật 04.10 đến 22.11.2026, mỗi tuần một điểm hẹn
  cụ thể, một chủ đề hợp khu, một nhiệm vụ tiếng Anh đơn giản. Điểm hẹn, chủ đề, nhiệm vụ và dòng
  "Hanoi Old Quarter": **chủ dự án giao điều phối viên quyết, 29.09.2026**. Câu giới thiệu của chủ dự án
  vẫn nói "đi bộ quanh Hồ Hoàn Kiếm" dù ba trong bốn khu không phải hồ; giữ nguyên văn theo cùng lượt giao.
* **Dữ liệu:** `apps/learn/src/ielts/walk/weeks.json`, mỗi dòng `{date, area, place, topic, mission}`;
  worker giữ bản sao ở `workers/api/src/modules/walks/weeks.ts` (chặn ngày lạ, viết thư).
  `walkWeeks.test.ts` giữ hai bản khớp từng chữ; thêm tuần thì thêm ở cả hai. Tuần tự ẩn khi buổi KẾT
  THÚC (16:30 giờ Việt Nam).

**Đăng ký WALK (migration 0314, `walks/routes.ts`).** Miễn phí, cần đăng nhập, mỗi learner một lượt mỗi
tuần (khoá chính `walk_date, learner_id` của bảng `walk_registrations`).

* `GET /v1/learners/{id}/walks`: các tuần chưa kết thúc với `signups`, `registered`, `can_register`, cùng
  `eligible` và `slots: {total, used, left, by_course: [{course_id, name, total, used}]}`.
* `POST /v1/learners/{id}/walks/{date}` đăng ký, gọi lại vẫn 200 và không lấy suất thứ hai;
  `DELETE` huỷ. Cả hai đóng **2 giờ trước 15:00** (409 `closed`); ngày không có buổi là 404.
* **Suất:** chủ dự án viết "NEMO TALK 1/2", điều phối viên đọc là NEMO SPEAK 1 (`speak-2`) và NEMO
  SPEAK 2 (`speak-3`). Chỉ learner có dòng trong `speak_course_enrollments` của hai khoá ấy mới đăng ký
  được (không có thì 403 `not_enrolled`, câu "NEMO WALK is for NEMO SPEAK 1 and NEMO SPEAK 2 learners.").
  Mỗi lượt ghi danh cho **2 suất** (khoá = 4 buổi online + 2 buổi WALK, xem dưới); học cả hai khoá là 4, 2 của mỗi khoá. Cột `course_id` ghi suất của khoá
  nào: đăng ký lấy khoá ghi danh SỚM NHẤT còn suất. Hết suất là 409 `no_slots`. Huỷ trước giờ đóng trả
  suất (xoá dòng); buổi đã qua thì dòng còn đó, tức là suất đã dùng.
* Trang WALK: chưa ghi danh thì hiện "NEMO WALK is for NEMO SPEAK 1 and NEMO SPEAK 2 learners" kèm link
  hai trang khoá, không nút Join; đã ghi danh thì hiện "Walk slots: n of 2 left" cho từng khoá, hết suất
  thì Join bị khoá.
* **Thư xác nhận** `walk-registered`: ngày, giờ, khu, điểm hẹn, chủ đề, nhiệm vụ. Thư giao dịch, miễn
  trần; khoá chống trùng theo tuần + người + email, nên huỷ rồi đăng ký lại không gửi lần hai. Không mang
  nhận diện NEMO IELTS.
* **Admin:** tab "NEMO WALK" liệt kê người đăng ký theo từng tuần (tên, email, khoá dùng suất).

**Cấu trúc khoá mới (chủ dự án 29.09.2026, cùng SRC):** NEMO SPEAK 1 (`speak-2`) và NEMO SPEAK 2
(`speak-3`) mỗi khoá = **4 buổi online + 2 buổi NEMO WALK**, học phí giữ 1.980.000đ.

* Plan (learn `SPEAK_COURSES` và bản sao worker `cohort.ts`, test `cohortAgrees` giữ khớp):
  SPEAK 1: 8 bài, \[1,2] \[3,4] \[5,6] \[7,8]. Buổi 5 cũ (tổng kết, demo trước lớp, SRC-1133) **bỏ**; phần
  demo và chia sẻ chuyển sang các buổi WALK (điều phối viên quyết).
  SPEAK 2: 10 bài trong 4 buổi, \[1,2,3] \[4,5,6] \[7,8] \[9,10] (✍️ điều phối viên quyết: hai buổi đầu 3 bài,
  chờ chủ dự án nếu muốn chia khác).
* Lịch cuốn chiếu nay xoay 4 vị trí plan (chỉ số buổi mod 4); kích hoạt xong học 4 buổi online kế tiếp,
  vẫn cắt 2 giờ (`SESSIONS_PER_ENROLLMENT = 4` ở cả hai phía). `first_session_index` đã lưu vẫn đúng vì nó
  là chỉ số trong dòng buổi liên tục, không phụ thuộc số vị trí plan.
* Chữ: dòng lịch "... · join any time · 4 online sessions × 120 min + 2 NEMO WALK"; nhãn "4 online sessions",
  "2 walks"; thẻ học phí "4 online sessions × 120 min + 2 NEMO WALK sessions"; thẻ ở `/speaking`
  "x topics · 4 sessions + 2 walks"; trang đăng ký và thư "4 buổi". `SHOWCASE_LABEL` (buổi không bài) giữ
  làm nhánh an toàn, hiện không plan nào dùng.

**Trang bài Speak together chỉ xem (chủ dự án 29.09.2026, cùng SRC).** `SpeakPairTopicPage` (vd
`/speaking/family`) bỏ nút Start a room, ô nhập mã, nút Join và cả luồng phòng cặp trên giao diện; ai mở
cũng đọc được bối cảnh, vai, mọi lượt hỏi (3 cách hỏi `a_variants`) và đáp ("Answer 1/2/3" từ `b_variants`,
không có thì lấy `b`, nên gộp trước hay sau nhánh thêm `b_variants` đều được), gợi ý, cụm từ. Client
`speakPairApi` và các component phòng đã xoá; API `speak-pairs` ở worker giữ nguyên, không ai gọi. Câu
giới thiệu ở `/speaking` và ở thẻ Speak together của trang Nói: "Pick a topic. Read the questions and
answers. Practise them with a partner in class." Trang hướng dẫn một buổi nói về breakout room của lớp,
không nói về phòng mã, nên giữ nguyên.

**Còn để ngỏ:** thư nhắc trước buổi, giới hạn số người mỗi buổi, số liên lạc tại điểm hẹn.

Kiểm chứng: `e2e/speakWalk.spec.ts` (đồng hồ ghim; bốn khu; Join/Cancel/You are in/Closed; chưa ghi danh
thì không Join; hết suất thì Join khoá; thẻ học phí), `e2e/speakRegister.spec.ts` (bốn thẻ, tên mới,
chuyển hướng `/speak-3`), `mobileAudit.spec.ts` thêm `start` và `walk`, `walkWeeks.test.ts` (Chủ nhật,
bốn khu luân phiên, hai bản lịch khớp), `walks/routes.test.ts` (đăng ký, gọi lại, huỷ, đóng sau giờ, quyền,
403 chưa ghi danh, 2 suất, 4 suất khi học cả hai khoá, trả suất, lượt thứ ba 409, buổi đã qua vẫn tính), `cohortAgrees.test.ts`,
và test worker kiểm link trong thư NEMO SPEAK.

## 150. Trang NEMO WALK tối giản, mặc định giấu gần hết (SRC-1148, 30.09.2026)

Chủ dự án yêu cầu trang `/speaking/walk` (và bí danh `/ielts/speaking/pair/walk`) cực kỳ tối giản, gọn và dễ hiểu:
mặc định giấu gần hết, learner bấm vào chỗ nào cần thì mới mở. Đây là luật tối giản SRC-048 áp lên §146. Chỉ đổi
cách trình bày; API, Join, Cancel, suất và thông báo 403/409 giữ nguyên.

Mặc định chỉ còn:

1. Tiêu đề `NEMO WALK`, câu `Walk. Talk. Speak English.` và một dòng mờ `Every Sunday 15:00 · Free`. Hình lớn đổi
   thành một hình nhỏ đứng cạnh tiêu đề.
2. Một thẻ `Next walk`: ngày giờ, khu và MỘT nút. Đủ điều kiện: `Join`, hoặc `You are in` + `Cancel`, hoặc `Closed`.
   Không ghi danh NEMO SPEAK 1/2: một link nhỏ `For NEMO SPEAK 1 and 2 learners` (tới trang NEMO SPEAK 1); thẻ
   "Who can join" riêng đã gỡ. Bấm thân thẻ mở chi tiết: điểm hẹn, chủ đề, Speaking Mission, số người đi, thời
   lượng và giờ đóng đăng ký.
3. Bốn hàng đóng sẵn, mỗi hàng một nút có `aria-expanded` + `aria-controls`, mở bằng preset Motion `Reveal`:
   `Upcoming walks` (các tuần sau, mỗi tuần lại mở được để xem chi tiết và nút), `Your walk slots` (chỉ khi đủ điều
   kiện, ghi "n of m left" ngay trên hàng), `What you will do` (5 gạch đầu dòng của chủ dự án) và `About NEMO WALK`
   (chữ giới thiệu nguyên văn của chủ dự án, kể cả câu kết).

Hàng bấm cao tối thiểu 44px, nút hành động 40px; ở 375px không cuộn ngang.

Kiểm chứng: `apps/learn/e2e/speakWalk.spec.ts` (mặc định chỉ có tiêu đề, thẻ Next walk và bốn hàng đóng; mở thẻ và
từng hàng; Join/Cancel gọi đúng API; không đủ điều kiện chỉ có link nhỏ; hết suất thì Join bị khoá; tuần đã qua tự
ẩn; 375px không cuộn ngang, hàng bấm >= 40px; ảnh chụp mặc định và mở ra ở desktop và 375px).
`mobileAudit.spec.ts` đã phủ `/speaking/walk`.

## 161. NEMO WALK: hồ sơ một-câu-một-màn và quiz luật 10/10 trước buổi đầu (SRC-1193, 03.10.2026)

Chủ dự án 03.10.2026: trang `learn.nemo12.com/walk` dẫn tới một quiz 10 câu kiểm learner hiểu luật chung của
NEMO WALK; phải đúng 10/10 mới qua, qua thì sang trang chúc mừng và server GHI LẠI là người này đã qua bài
kiểm tra để bắt đầu tham gia. Trước quiz là một hồ sơ cá nhân hỏi từng câu một. Toàn bộ chữ là tiếng Anh rất
đơn giản như trang Walk.

* **Đường đi:** `/walk` (thẻ "Before your first walk") → đăng nhập Google nếu chưa → `/walk/join` (stepper) →
  `/walk/quiz` → `/walk/passed`. Mỗi bước lưu server ngay, mở lại thì về bước còn thiếu đầu tiên. Đã qua thì
  `/walk` hiện "You passed the NEMO WALK test" + link `/speak/walk`; vào `/walk/quiz` bị đưa sang `/walk/passed`;
  vào quiz khi hồ sơ chưa xong bị đưa về `/walk/join`.
* **Stepper (9 câu, chi nhánh "Something else" còn 8):** số điện thoại (di động VN, lưu E.164) · họ tên · ngày sinh
  (ba ô số, hiện DD.MM.YYYY, 8 đến 100 tuổi) · can-do Speaking A1..C1 kèm tình huống (chọn nhiều) · nói liền
  được bao lâu về gia đình · gặp du khách hỏi đường thấy thế nào · điểm thi 2 năm gần đây (IELTS Speaking / thi
  khác / chưa) · học sinh hay đi làm · trường + lớp hoặc nơi làm + nghề. Màn cuối xem lại, có nút Change.
* **Ước lượng Speaking** (`packages/walk/profile.ts`): can-do chỉ đếm chuỗi liên tục từ A1; tự đánh giá =
  0,6 can-do + 0,2 thời lượng + 0,2 độ thoải mái, không vượt quá chuỗi can-do một bậc; có IELTS Speaking thì
  0,6 band (bảng IELTS-CEFR) + 0,4 tự đánh giá, độ tin "medium", không có thì "low". Thi khác chỉ lưu cho staff đọc.
* **Quiz** (`packages/walk/quiz.ts`, phiên bản `walk-rules-v1`): điểm hẹn cổng Nhà Thờ Lớn, check-in, ghép cặp
  bởi app/host, vai guide/tourist, vòng 2 đổi vai đổi chỗ, điện thoại chỉ xem nhanh, ghi chú sau mỗi vòng, chỉ
  tiếng Anh trong vòng, ở cạnh bạn cặp và tử tế, kết thúc bánh rán Lý Quốc Sư rồi trà chanh. Sai thì hiện câu
  nào sai kèm luật cần đọc lại (không đánh dấu đáp án), "Try again" chỉ hỏi lại các câu sai.
* **Trang Walk thành 5 thẻ gập** (chủ dự án 03.10.2026, 5 thẻ là được): What you will do · The rules (luật
  điện thoại + 5 luật quiz hỏi, link "Know the rules? Take the quiz") · How it works (6 bước) · After the walk
  (bánh rán + trà chanh) · 5 places to visit. Mọi đáp án quiz đều đọc được trên trang.
* **Server** (`workers/api/src/modules/walks/entry.ts`, migration 0325): `GET /v1/learners/{id}/walk-entry`,
  `PUT .../walk-entry/profile` (từng phần, gộp), `POST .../walk-entry/quiz` (chấm bằng cùng mã với UI; lưu mọi
  lượt với điểm, câu trả lời, phiên bản; lượt qua duy nhất mỗi phiên bản nhờ index UNIQUE có điều kiện; đề đổi
  phiên bản thì 409 `quiz_changed`; hồ sơ chưa xong thì 409 `profile_incomplete`), `GET /v1/admin/walk-entries`
  cho staff/admin. **Riêng tư:** chỉ chính learner ghi; đọc là learner, staff, admin; phụ huynh và mentor bị
  chặn dù `canAccessLearner` thường cho qua. Không PII trong URL, log hay localStorage.
* **Một nguồn:** quiz, luật trường và ước lượng nằm ở gói `packages/walk` (`@nemo12/walk`), learn và api cùng
  import; `.github/app-paths.json` khai `walk` là nguồn chung để đổi gói thì deploy lại cả hai.

Kiểm: `workers/api/src/modules/walks/entry.test.ts` (quyền, từng nhánh hỏng, luật 10/10), `apps/learn/src/walk/walkEntry.test.ts`
(chấm quiz, số điện thoại, ngày sinh, ước lượng Speaking), `apps/learn/e2e/walkEntry.spec.ts` (cả mạch ở 375px).
