← Home

Projects Deep — Interview Reference

Mock Q →

// 2026-05-19 — interview hôm nay 15:00 với Leaders Chuyên môn Base.vn

Phong's Project Portfolio

5 featured projects + AI Hackathon + Work Experience — based on REAL codebase scan

Summary Shiru TWD Agents Hub Twendee ERP MoneyFi Decot VCCI Go Claw CKForensics 🔥 Pain Points Talking Points Q to Ask

⚡ Quick Profile Snapshot

Experience: 2 years (Front-end + Full-stack)

Current: Twendee Limited Software (Feb 2025 - Now)

Background: 7+ FE projects · 5+ Web3 products (~$20M TX volume) · AI Agents Engineering

Education: PTIT, Electronics & Telecom, GPA 3.45 (Distinction)

Stack mạnh:

TypeScriptNext.jsReactNestJSPrismaPostgreSQLRedisBullMQTanStackWeb3Claude CodeMCP

"Em là Front-end Developer 2 năm kinh nghiệm, hiện tại đang shift sang Full-stack và AI Agentic Engineering tại Twendee. Em đã ship 5+ Web3 products với tổng transaction volume ~$20M, build full-stack platforms với NestJS + Prisma + PostgreSQL, và đang lead 1 AI Agent platform multi-tenant cho ERP."

🌟 Shiru — AI Advisor Web3 Application

Fullstack · Jan 2026-Now

Pitch 1 câu: AI-powered crypto portfolio platform với dual auth (SIWE + Email/PIN), KYC/KYB workflow, AI investment recommendations, portfolio tracking trên 6 EVM chains.

FE Stack

Next.jsTailwindCSSShadcn UITanStack Query

BE Stack (REAL từ package.json)

NestJS 9Prisma 7PostgreSQL@prisma/adapter-pgRedis (ioredis)BullMQJWT + PassportSendGrid@nestjs/throttler@nestjs/scheduleSwaggerMoralis SDKSumsubPM2Docker

🏗️ Architecture (REAL từ source scan):

🎯 Key Backend Decisions (Talk about these):

  1. Tại sao Repository pattern? → Tách Prisma logic khỏi service. Service không phụ thuộc ORM cụ thể. Dễ test (mock repository).
  2. Tại sao JWT refresh-rotation với Token table? → Refresh token reuse detection. Mỗi lần refresh, mark old token usedAt; nếu thấy reuse → revoke entire family (security).
  3. Tại sao BullMQ thay vì sync? → AI recommendation generation chậm (~5s/call), không block API. Portfolio sync chạy background. Retry built-in cho external API failures.
  4. Tại sao custom cache thay vì Redis trực tiếp? → Encapsulate TTL conventions, key naming, fallback to DB nếu Redis down.
  5. Tại sao separate cron-job process? → Crash isolation. Cron failure không kill main API. PM2 manage cả 2 process riêng.
  6. Tại sao Merkle tree cho whitelist? → Lưu 1 root hash on-chain (~32 bytes) thay vì N addresses (gas saving). User submit proof khi claim.

🔐 Auth Flow Deep (REAL từ src/modules/auth/auth.service.ts):

🤖 AI Integration Module (REAL submodules):

⏰ Cron Jobs (3 specific jobs):

🔥 Pain Points (REAL từ codebase scan):

🚀 Improvement ideas (nếu interviewer hỏi):

  1. OpenTelemetry tracing across NestJS → BullMQ jobs → external APIs để debug slow flows
  2. Prisma slow query logging + Datadog/Sentry integration cho N+1 detection
  3. Cursor pagination cho TransactionHistory thay offset (table sẽ lớn nhanh)
  4. Event sourcing cho KYB audit log — immutable history với optimistic concurrency
  5. Tag-based cache invalidation với meta convention (đã learn TanStack Query meta pattern — apply tương tự server side)
  6. Helmet + CSP headers nếu chưa có, plus signed cookies cho refresh token
  7. Database connection pool tuning — Prisma adapter-pg cho phép control pool size theo memory budget
  8. Health endpoint deeper checks — không chỉ DB/Redis mà còn Moralis quota, Sumsub responsive, SendGrid status

🤖 TWDAgentsHub — Multi-tenant AI Orchestrator

AI Agentic · 2026

Pitch 1 câu: Multi-tenant AI Orchestrator SaaS cho Vietnamese SMEs — 5 domain agents (Finance, HR, Sales, Supply, Compliance), multi-channel input (REST, Web Chat, Telegram), pluggable ERP connectors, proactive intelligence.

⚠️ CỰC kỳ relevant cho Base.vn — multi-tenant SaaS, Vietnamese market, HR agent, ERP-style. Có thể là bài tủ trong interview.

Stack

NestJSTypeScriptPostgreSQL 16 (RLS!)Redis 7Vercel AI SDKReactshadcn/uiTurborepopnpm workspacesBiomeDocker

Monorepo Layout

apps/
  api/         NestJS modular monolith
  admin-ui/    Vite + React + shadcn
  chat-widget/ Embeddable Vite lib
packages/
  shared-types/    TS contracts
  erp-adapters/    Pluggable ERP connectors
tooling/
  tsconfig/        Shared TS configs
  tailwind-config/ Shared Tailwind preset

📦 API Submodules (REAL từ apps/api/src/):

agents/ — 5 domain agents (Finance, HR, Sales, Supply, Compliance)
orchestrator/ — Agent orchestration layer
dynamic-agents/ — Runtime-created agents
mcp/ — Model Context Protocol servers
llm/ — Multi-provider LLM abstraction
memory/ — Agent memory (conversation context)
conversations/ — Chat conversations CRUD
messages/ — Message persistence
chat/ — Chat session orchestration
channels/ — Multi-channel routing
channel-bots/ — Telegram/Slack/Discord bots
lead-notifications/ — Proactive intelligence alerts
auth/ — Multi-tenant auth
database/ — Prisma + RLS setup
redis/ — Redis client wrapper
health/ — Health checks (db, redis)
common/ — Shared utilities
config/ — Environment config

→ 18 submodules. Modular monolith done right — clear bounded contexts trong cùng app.

🏗️ Key Architecture Decisions:

  1. Multi-tenant với PostgreSQL RLS (Row-Level Security) → từng tenant chỉ thấy data của mình, enforced tại DB layer (không phụ thuộc app code). Defense-in-depth.
  2. Modular monolith (không microservices) → 5 devs, 8-week MVP. Microservices premature cho scale này.
  3. Turborepo + pnpm workspaces → share TS types qua shared-types package, cache builds, parallel tasks.
  4. Pluggable ERP adapters → strategy pattern. Mỗi ERP (vd SAP, Odoo, custom) có adapter riêng implement common interface.
  5. Vercel AI SDK → unified abstraction cho multiple LLM providers (OpenAI, Anthropic, GLM). Streaming + tool calling built-in.
  6. Multi-channel input → REST API + Web Chat (embedded widget) + Telegram bot. Tất cả route vào cùng agent engine.
  7. Biome thay ESLint+Prettier → 1 tool, 10× faster, simpler config.
  8. Health endpoint pattern: {status, uptime, checks: {db, redis}} — Kubernetes/load balancer friendly.

🚀 Improvement ideas:

  1. Prompt evaluation harness — golden dataset, regression test khi đổi prompt
  2. Prompt caching (Anthropic) — cache system prompts, reduce 90% input tokens
  3. Cost dashboard per tenant — track LLM spend, enforce quotas
  4. Human-in-the-loop approval gates cho actions sensitive (finance/HR writes) — đã có trong CV
  5. Structured logging cho agent steps (LangFuse hoặc tự build) — debug AI decisions
  6. Async streaming responses cho long-running agents qua SSE
  7. Multi-tenant rate limiting — fair share resources, prevent noisy neighbor

🏢 Twendee ERP — Internal Business Platform

Fullstack · 2025

Pitch: Internal ERP cho Twendee gồm hr-system (backend NestJS + Prisma + Cerbos RBAC), new-erp-ui (frontend), erp-agents-presentation (AI agents UI). Multi-environment (dev/staging/prod) với Docker.

⚠️ Đặc biệt relevant Base.vn — đây là tương tự Base HRM (HR management).

🏗️ Architecture Highlights:

🎯 Key Decisions to discuss:

  1. Tại sao Cerbos thay vì hardcoded RBAC? → Policy as code. Non-dev (PM, HR) có thể đề xuất policy thay đổi. Audit trail của policy changes. Decouple authz logic khỏi business code.
  2. Tại sao tách hr-system khỏi new-erp-ui? → Separation FE/BE concern, độc lập deployment.
  3. Multi-env yml configs: 12-factor app principle — config in env, không hardcode.

🔥 Pain Points (REAL từ codebase scan):

🔄 Flow/UX Pain Points (Process Design Issues):

1. Onboarding Flow — Multi-Channel Chaos

HR tạo quy trình (Web) → Nhận link Telegram → Gửi link khác cho nhân viên mới
    ↓                         ↓                        ↓
Fill metadata         Bot gửi /profile#config    Fill onboarding form (link riêng)
    ↓                         ↓                        ↓
Assign responsible    User phải vào web link    Submit → HR nhận status update
        
  • Problem: 3 links khác nhau, user không biết link nào làm gì
  • Problem: Telegram chỉ thông báo, phải quay về web để action
  • Solution: Single onboarding link + Telegram inline buttons + progress checklist

2. Leave Request Flow — Approval Visibility Gap

Employee submit (Web) → Manager nhận email+Telegram → Phải vào web approve
    ↓                              ↓                         ↓
No in-app notification    "Click link to approve"    No timestamp ai approved khi nào
    ↓                              ↓                         ↓
Employee refresh page      No inline approve btn     No escalation nếu pending >3 ngày
        
  • Problem: Employee không biết "đang pending ai" trên dashboard
  • Problem: Approver nhận Telegram nhưng phải ra web để approve
  • Solution: Telegram /approve_[id] callback + approval timeline với timestamp

3. Payroll Flow — No Calculation Transparency

  • Problem: UI shows final amount, not breakdown (gross → deductions → net)
  • Problem: Approval status unclear ("CEO rejected" but no corrective action shown)
  • Problem: Manual export → email to bank → no audit trail
  • Solution: Payroll builder UI + reject with comments + one-click export với audit log

4. Cross-Channel Friction Matrix

StepWebTelegramEmail
Request createdForm submit ✓User notified (link)Approver notified
Approval neededView page → click"Approve via link" 🔴Link in email
Status updateRefresh page 🔴Message in chatNo push 🔴

→ Thiếu: Notification Hub (in-app bell), Telegram inline actions, auto-escalation

📈 Scaling Bottlenecks (1K → 100K users):

Area 1K 10K 100K Solution
Database ⚠️ 🔴 💀 Connection pool + .select() thay .include()
State (2,247 useState) ⚠️ 🔴 💀 TanStack Query + virtualization
Notifications ⚠️ 🔴 💀 Bull queue + exponential backoff
Caching ✓ ⚠️ 🔴 Redis cho roles/permissions/config
File Storage ✓ ⚠️ ⚠️ Streaming upload + presigned URLs
Multi-tenancy 🔴 💀 💀 tenantId + PostgreSQL RLS (rewrite)

🔥 Critical Findings:

  • User model có 80+ relations — all fetched via .include()
  • No connection pooling configured
  • Notifications sent synchronously trong request cycle
  • In-memory cache only (no Redis) — OOM risk
  • No tenantId — multi-org impossible

⏱️ Priority Fix Timeline:

  1. Hour 1: DB connection pooling
  2. Hour 2: Replace .include() → .select()
  3. Hour 4: Bull queues for notifications
  4. Hour 6: Redis caching layer
  5. Day 2: React state optimization

🔧 Process Extensibility (Adding New Workflow Types):

Current process types: ONBOARDING, OFFBOARDING, PURCHASE, CHANGE_SOCIAL_INSURANCE

Component Extensibility Current State What Breaks
Process Types 1/5 Hardcoded enum New type = enum + Prisma model + service + migration
Step Definitions 2/5 Helper functions per type New steps = new helper + hardcoded order
State Machine 1/5 Switch-case per type Transitions scattered, not pluggable
Forms 2/5 DTO per type New type = new DTO + new FE component
Notifications 2/5 Hardcoded in updateStatus() New type = new case statement
Templates (Non-dev) 0/5 Not supported PM/HR không thể tạo process type mới

🚨 Adding New Process Type Requires:

  1. Add enum value to ProcessType
  2. Create new Prisma model + migration
  3. Create new service + helper functions
  4. Add switch-case for transitions
  5. Create new DTO + FE form component
  6. Add notification rules manually

→ ~3-5 days per new process type

✅ True Extensibility Solution:

  • Move ProcessType to DB table (config-driven)
  • Generic step engine (store steps in DB)
  • BPMN-like rule engine for transitions
  • JSON schema for forms (dynamic rendering)
  • Event-driven notifications with templates
  • Process builder UI for PM/HR

→ ~2-3 weeks refactor

✅ Flexible Workflow Engine Solution:

Database-driven workflow definitions — PM/HR can create process types via UI

📊 CONFIG LAYER — Table Fields (click to expand)

1. ProcessDefinition — Workflow Templates

FieldTypePurpose
idString (CUID)Primary key
codeString UNIQUE"ONBOARDING", "LEAVE_REQUEST"
nameString"Employee Onboarding"
descriptionString?Mô tả cho PM/HR
categoryString"HR", "FINANCE", "IT"
iconString?"user-plus", "calendar"
formSchemaJsonJSON Schema cho dynamic form
isActiveBooleanEnable/disable
versionIntSchema versioning
createdBy, createdAt, updatedAtAuditTrack changes

2. StepDefinition — Generic Steps

FieldTypePurpose
idString (CUID)Primary key
processDefinitionIdFKBelongs to which process
codeString"DEPT_APPROVAL", "CEO_SIGN"
nameString"Department Approval"
orderIntExecution order (1, 2, 3...)
typeEnumACTION | APPROVAL | NOTIFICATION | CONDITION
assigneeTypeEnumROLE | USER | DEPT_HEAD | MANAGER | DYNAMIC
assigneeConfigJson?{"roleId": "...", "expression": "..."}
formSchemaJson?Step-specific form fields
timeoutHoursInt?Auto-escalate after N hours
escalationRoleIdFK?Escalate to this role
conditionExpressionString?"data.amount > 10000000"
isRequired, allowReject, allowDelegateBooleanStep behaviors

3. TransitionRule — State Machine

FieldTypePurpose
idString (CUID)Primary key
processDefinitionIdFKBelongs to which process
fromStepIdFK?null = START state
toStepIdFK?null = END state
triggerActionEnumAPPROVE | REJECT | COMPLETE | TIMEOUT
conditionExpressionString?Additional condition check
sideEffectsJson?[{type: "SEND_NOTIFICATION", ...}]

Example: Step1 --APPROVE--> Step2 --REJECT--> END

4. NotificationTemplate — Event Notifications

FieldTypePurpose
idString (CUID)Primary key
processDefinitionIdFKBelongs to which process
codeString"STEP_ASSIGNED", "COMPLETED"
triggerEventEnumSTEP_ASSIGNED | COMPLETED | REJECTED | TIMEOUT
triggerStepCodeString?Specific step or null = process-level
channelsEnum[][EMAIL, TELEGRAM, IN_APP]
recipientTypeEnumREQUESTER | ASSIGNEE | ROLE
subjectTemplateString"Leave #{{processId}} - Action Required"
bodyTemplateString"Hi {{recipientName}}, ..."
🏃 RUNTIME LAYER — Table Fields (click to expand)

5. ProcessInstance — Running Processes

FieldTypePurpose
idString (CUID)Primary key
processDefinitionIdFKWhich process template
referenceNumberString UNIQUE"ONB-2026-0001" (human-readable)
requesterIdFK UserWho created this request
statusEnumDRAFT | SUBMITTED | IN_PROGRESS | COMPLETED | CANCELLED
dataJsonProcess form data (validated by formSchema)
currentStepIdFK?Current active step
submittedAt, completedAt, cancelledAtDateTime?Lifecycle timestamps

6. StepInstance — Step Executions

FieldTypePurpose
idString (CUID)Primary key
processInstanceIdFKBelongs to which process run
stepDefinitionIdFKWhich step template
assigneeIdFK User?Resolved at runtime
statusEnumPENDING | ASSIGNED | IN_PROGRESS | COMPLETED | REJECTED | TIMEOUT
dataJson?Step-specific form data
decisionEnum?APPROVED | REJECTED | DELEGATED
decisionCommentString?"Approved, looks good"
dueAtDateTime?Calculated from timeoutHours
assignedAt, startedAt, completedAtDateTime?Step lifecycle

7. ProcessHistory — Audit Trail (Immutable)

FieldTypePurpose
idString (CUID)Primary key
processInstanceIdFKWhich process run
actorIdFK UserWho performed action
actionEnumCREATED | SUBMITTED | STEP_COMPLETED | REJECTED | CANCELLED
stepCodeString?Which step (if step-related)
fromStatus, toStatusString?Status transition
commentString?User comment
metadata, createdAtJson?, DateTimeExtra context + timestamp

Insert-only table — never update/delete for compliance

📋 ENUMS — All Enum Values (click to expand)

StepType

ACTION | APPROVAL | NOTIFICATION | CONDITION | PARALLEL

AssigneeType

ROLE | USER | DEPT_HEAD | REQUESTER_MANAGER | DYNAMIC

TriggerAction

APPROVE | REJECT | COMPLETE | TIMEOUT | CONDITION_TRUE

ProcessStatus

DRAFT | SUBMITTED | IN_PROGRESS | COMPLETED | CANCELLED | REJECTED

StepStatus

PENDING | ASSIGNED | IN_PROGRESS | COMPLETED | REJECTED | SKIPPED | TIMEOUT

NotificationChannel

EMAIL | TELEGRAM | IN_APP | SMS

🎯 USER FLOWS — Cách tạo và chạy quy trình (click to expand)

FLOW 1: PM/HR Tạo Workflow Template (Process Builder UI)

📝 Step 1: PM mở Process Builder, điền thông tin cơ bản:

// UI Form Input
{
  code: "LEAVE_REQUEST",
  name: "Đơn xin nghỉ phép",
  category: "HR",
  icon: "calendar",
  description: "Quy trình xin nghỉ phép..."
}

💾 API Call: POST /api/process-definitions

// INSERT INTO ProcessDefinition
{
  id: "cuid_abc123",
  code: "LEAVE_REQUEST",
  name: "Đơn xin nghỉ phép",
  category: "HR",
  formSchema: {}, // empty, fill later
  isActive: false, // draft mode
  version: 1,
  createdBy: "pm_user_id"
}

📋 Step 2: PM thiết kế Form Schema (drag-drop form builder):

// formSchema được update via PUT /api/process-definitions/:id
{
  "type": "object",
  "required": ["leaveType", "startDate", "endDate", "reason"],
  "properties": {
    "leaveType": {
      "type": "string",
      "enum": ["ANNUAL", "SICK", "UNPAID", "MATERNITY"],
      "title": "Loại nghỉ phép"
    },
    "startDate": {
      "type": "string",
      "format": "date",
      "title": "Ngày bắt đầu"
    },
    "endDate": {
      "type": "string",
      "format": "date",
      "title": "Ngày kết thúc"
    },
    "reason": {
      "type": "string",
      "maxLength": 500,
      "title": "Lý do"
    },
    "attachments": {
      "type": "array",
      "items": { "type": "string", "format": "uri" },
      "title": "Đính kèm (nếu có)"
    }
  }
}

🔄 Step 3: PM kéo thả Steps trong React Flow canvas:

// Step 1: Manager Approval
POST /api/step-definitions
{
  processDefinitionId: "cuid_abc123",
  code: "MANAGER_APPROVAL",
  name: "Quản lý duyệt",
  order: 1,
  type: "APPROVAL",
  assigneeType: "REQUESTER_MANAGER",
  timeoutHours: 48,
  escalationRoleId: "hr_admin_role"
}
// Step 2: HR Confirm
POST /api/step-definitions
{
  processDefinitionId: "cuid_abc123",
  code: "HR_CONFIRM",
  name: "HR xác nhận",
  order: 2,
  type: "APPROVAL",
  assigneeType: "ROLE",
  assigneeConfig: {
    "roleId": "hr_staff_role"
  },
  timeoutHours: 24
}
// Step 3: Notify Employee
POST /api/step-definitions
{
  processDefinitionId: "cuid_abc123",
  code: "NOTIFY_RESULT",
  name: "Thông báo kết quả",
  order: 3,
  type: "NOTIFICATION",
  assigneeType: "DYNAMIC",
  assigneeConfig: {
    "expression": "requester"
  }
}

➡️ Step 4: PM nối các Steps bằng Transitions:

// POST /api/transition-rules (batch)
[
  { fromStepId: null, toStepId: "step1_id", triggerAction: "COMPLETE" },  // START → Manager
  { fromStepId: "step1_id", toStepId: "step2_id", triggerAction: "APPROVE" },  // Manager ✓ → HR
  { fromStepId: "step1_id", toStepId: null, triggerAction: "REJECT" },  // Manager ✗ → END (rejected)
  { fromStepId: "step2_id", toStepId: "step3_id", triggerAction: "APPROVE" },  // HR ✓ → Notify
  { fromStepId: "step2_id", toStepId: null, triggerAction: "REJECT" },  // HR ✗ → END (rejected)
  { fromStepId: "step3_id", toStepId: null, triggerAction: "COMPLETE" }  // Notify → END (completed)
]

🔔 Step 5: PM cấu hình Notifications:

// POST /api/notification-templates
{
  processDefinitionId: "cuid_abc123",
  code: "STEP_ASSIGNED_MANAGER",
  triggerEvent: "STEP_ASSIGNED",
  triggerStepCode: "MANAGER_APPROVAL",
  channels: ["EMAIL", "TELEGRAM", "IN_APP"],
  recipientType: "ASSIGNEE",
  subjectTemplate: "🗓️ Đơn nghỉ phép #{{referenceNumber}} cần duyệt",
  bodyTemplate: "Xin chào {{recipientName}},\n\n{{requesterName}} đã gửi đơn xin nghỉ phép:\n- Loại: {{data.leaveType}}\n- Từ: {{data.startDate}} đến {{data.endDate}}\n- Lý do: {{data.reason}}\n\nVui lòng duyệt tại: {{approvalLink}}",
  telegramTemplate: "📋 Đơn nghỉ #{{referenceNumber}} từ {{requesterName}}. Duyệt: {{approvalLink}}"
}

✅ Step 6: PM click "Publish" → isActive: true → Workflow sẵn sàng sử dụng!

FLOW 2: Nhân viên Tạo Đơn (Process Instance)

📝 Step 1: Nhân viên mở trang "Tạo đơn mới", chọn loại quy trình:

// GET /api/process-definitions?isActive=true&category=HR
// Response: list of available process types
[
  { id: "cuid_abc123", code: "LEAVE_REQUEST", name: "Đơn xin nghỉ phép", icon: "calendar" },
  { id: "cuid_def456", code: "EXPENSE_CLAIM", name: "Đơn thanh toán chi phí", icon: "receipt" }
]

📋 Step 2: Nhân viên click "Đơn xin nghỉ phép" → FE fetch formSchema → render dynamic form:

// GET /api/process-definitions/cuid_abc123
// Response includes formSchema → FE renders:
// - Select: Loại nghỉ phép (ANNUAL, SICK, UNPAID...)
// - DatePicker: Ngày bắt đầu
// - DatePicker: Ngày kết thúc
// - Textarea: Lý do
// - FileUpload: Đính kèm

💾 Step 3: Nhân viên điền form và click "Lưu nháp":

// POST /api/process-instances
{
  processDefinitionId: "cuid_abc123",
  data: {
    leaveType: "ANNUAL",
    startDate: "2026-06-01",
    endDate: "2026-06-03",
    reason: "Nghỉ phép năm, đi du lịch với gia đình",
    attachments: []
  }
}

// Response: ProcessInstance created
{
  id: "inst_xyz789",
  referenceNumber: "LV-2026-0042",  // auto-generated
  status: "DRAFT",
  requesterId: "employee_user_id",
  data: { ... },
  createdAt: "2026-05-22T10:30:00Z"
}

🚀 Step 4: Nhân viên click "Gửi đơn" → Workflow Engine bắt đầu:

// POST /api/process-instances/inst_xyz789/submit

// Backend WorkflowEngine.submitProcess() does:
// 1. Validate form data against formSchema (Zod)
// 2. Find first step (MANAGER_APPROVAL)
// 3. Resolve assignee (requester's manager → "manager_user_id")
// 4. Create StepInstance
// 5. Update ProcessInstance status
// 6. Trigger notifications

// INSERT INTO StepInstance
{
  id: "step_inst_001",
  processInstanceId: "inst_xyz789",
  stepDefinitionId: "step1_id",
  assigneeId: "manager_user_id",  // resolved from REQUESTER_MANAGER
  status: "ASSIGNED",
  assignedAt: "2026-05-22T10:31:00Z",
  dueAt: "2026-05-24T10:31:00Z"  // +48 hours
}

// UPDATE ProcessInstance
{
  status: "SUBMITTED" → "IN_PROGRESS",
  currentStepId: "step_inst_001",
  submittedAt: "2026-05-22T10:31:00Z"
}

// INSERT INTO ProcessHistory
{
  processInstanceId: "inst_xyz789",
  actorId: "employee_user_id",
  action: "SUBMITTED",
  fromStatus: "DRAFT",
  toStatus: "IN_PROGRESS"
}

📧 Step 5: Hệ thống gửi thông báo cho Manager:

// NotificationService triggered by STEP_ASSIGNED event
// Interpolate template với data:

Email to: manager@company.com
Subject: 🗓️ Đơn nghỉ phép #LV-2026-0042 cần duyệt
Body: Xin chào Nguyễn Văn A,

Trần Thị B đã gửi đơn xin nghỉ phép:
- Loại: Nghỉ phép năm
- Từ: 01/06/2026 đến 03/06/2026
- Lý do: Nghỉ phép năm, đi du lịch với gia đình

Vui lòng duyệt tại: https://erp.company.com/approvals/step_inst_001

// Telegram message also sent with inline button

FLOW 3: Manager Duyệt Đơn

👀 Step 1: Manager mở link từ email/Telegram → xem chi tiết đơn:

// GET /api/step-instances/step_inst_001
{
  id: "step_inst_001",
  status: "ASSIGNED",
  stepDefinition: { code: "MANAGER_APPROVAL", name: "Quản lý duyệt", allowReject: true },
  processInstance: {
    referenceNumber: "LV-2026-0042",
    requester: { name: "Trần Thị B", department: "Marketing" },
    data: { leaveType: "ANNUAL", startDate: "2026-06-01", ... }
  }
}

✅ Step 2: Manager click "Duyệt" với comment:

// POST /api/step-instances/step_inst_001/complete
{
  decision: "APPROVED",
  comment: "Đồng ý, chúc em đi chơi vui vẻ!"
}

// Backend WorkflowEngine.completeStep() does:
// 1. Validate assignee matches current user
// 2. Validate step status is ASSIGNED or IN_PROGRESS
// 3. Update StepInstance
// 4. Find next transition (APPROVE → HR_CONFIRM)
// 5. Create next StepInstance
// 6. Trigger notifications

// UPDATE StepInstance (current)
{
  status: "COMPLETED",
  decision: "APPROVED",
  decisionComment: "Đồng ý, chúc em đi chơi vui vẻ!",
  decisionAt: "2026-05-22T14:00:00Z",
  completedAt: "2026-05-22T14:00:00Z"
}

// INSERT INTO StepInstance (next step)
{
  id: "step_inst_002",
  processInstanceId: "inst_xyz789",
  stepDefinitionId: "step2_id",  // HR_CONFIRM
  assigneeId: "hr_staff_user_id",  // resolved from ROLE
  status: "ASSIGNED",
  assignedAt: "2026-05-22T14:00:01Z",
  dueAt: "2026-05-23T14:00:01Z"  // +24 hours
}

// UPDATE ProcessInstance
{
  currentStepId: "step_inst_002"
}

// INSERT INTO ProcessHistory
{
  action: "STEP_COMPLETED",
  stepCode: "MANAGER_APPROVAL",
  comment: "Đồng ý, chúc em đi chơi vui vẻ!"
}

FLOW 4: Quy trình Hoàn thành

// Sau khi HR duyệt → Step NOTIFY_RESULT được tạo (type: NOTIFICATION)
// WorkflowEngine tự động complete notification step

// INSERT INTO StepInstance
{
  id: "step_inst_003",
  stepDefinitionId: "step3_id",  // NOTIFY_RESULT
  assigneeId: "employee_user_id",  // requester
  status: "COMPLETED",  // auto-completed for NOTIFICATION type
  completedAt: "2026-05-22T16:00:00Z"
}

// Transition COMPLETE → toStepId: null (END)
// WorkflowEngine.completeProcess()

// UPDATE ProcessInstance
{
  status: "COMPLETED",
  currentStepId: null,
  completedAt: "2026-05-22T16:00:00Z"
}

// INSERT INTO ProcessHistory
{
  action: "COMPLETED",
  fromStatus: "IN_PROGRESS",
  toStatus: "COMPLETED"
}

// Trigger PROCESS_COMPLETED notification to requester
// "🎉 Đơn nghỉ phép #LV-2026-0042 đã được duyệt!"

FLOW 5: Manager Từ chối Đơn (REJECT)

❌ Step 1: Manager click "Từ chối" với lý do:

// POST /api/step-instances/step_inst_001/complete
{
  decision: "REJECTED",
  comment: "Không đủ nhân sự trong thời gian này. Đề nghị chọn ngày khác."
}

// Backend WorkflowEngine.completeStep() does:
// 1. Update StepInstance với decision: REJECTED
// 2. Find transition với triggerAction: REJECT
// 3. Execute transition (toStepId: null = END)

💾 Step 2: Database Updates:

// UPDATE StepInstance
{
  id: "step_inst_001",
  status: "REJECTED",           // ← not COMPLETED
  decision: "REJECTED",
  decisionComment: "Không đủ nhân sự trong thời gian này...",
  decisionAt: "2026-05-22T15:00:00Z",
  completedAt: "2026-05-22T15:00:00Z"
}

// Find TransitionRule
{
  fromStepId: "step1_id",       // MANAGER_APPROVAL
  toStepId: null,               // → END (no next step)
  triggerAction: "REJECT"
}

// UPDATE ProcessInstance
{
  status: "REJECTED",           // ← Process ends with REJECTED status
  currentStepId: null,
  completedAt: "2026-05-22T15:00:00Z"
}

// INSERT INTO ProcessHistory
{
  processInstanceId: "inst_xyz789",
  actorId: "manager_user_id",
  action: "STEP_REJECTED",
  stepCode: "MANAGER_APPROVAL",
  fromStatus: "IN_PROGRESS",
  toStatus: "REJECTED",
  comment: "Không đủ nhân sự trong thời gian này..."
}

📧 Step 3: Thông báo cho nhân viên:

// NotificationService triggered by STEP_REJECTED event
// Template: NotificationTemplate với triggerEvent: "STEP_REJECTED"

Email to: employee@company.com
Subject: ❌ Đơn nghỉ phép #LV-2026-0042 bị từ chối
Body: Xin chào Trần Thị B,

Đơn nghỉ phép của bạn đã bị từ chối bởi Nguyễn Văn A (Quản lý).

Lý do: Không đủ nhân sự trong thời gian này. Đề nghị chọn ngày khác.

Bạn có thể tạo đơn mới tại: https://erp.company.com/leave-request/new

// Telegram: "❌ Đơn #LV-2026-0042 bị từ chối. Lý do: ..."

🔄 Option: Cho phép tạo đơn mới (Resubmit):

// Nhân viên có thể:
// 1. Tạo đơn MỚI với ngày khác (recommended)
// 2. Hoặc nếu system cho phép REVISION:

// POST /api/process-instances/inst_xyz789/revise
{
  data: {
    leaveType: "ANNUAL",
    startDate: "2026-06-15",    // ← changed dates
    endDate: "2026-06-17",
    reason: "Nghỉ phép năm (đã đổi ngày theo góp ý của quản lý)"
  }
}

// This creates NEW ProcessInstance linked to original
{
  id: "inst_revision_001",
  referenceNumber: "LV-2026-0042-R1",  // revision suffix
  previousVersionId: "inst_xyz789",     // link to rejected version
  status: "DRAFT"
}

FLOW 6: Timeout & Auto-Escalation

⏰ Background: Cron job check timeout mỗi 5 phút:

// TimeoutCheckerCronJob runs every 5 minutes
// SELECT * FROM StepInstance
// WHERE status IN ('ASSIGNED', 'IN_PROGRESS')
//   AND dueAt < NOW()
//   AND dueAt IS NOT NULL

// Found: step_inst_001 with dueAt: "2026-05-24T10:31:00Z"
// Current time: "2026-05-24T12:00:00Z" (past due)

// StepDefinition has:
{
  timeoutHours: 48,
  escalationRoleId: "hr_admin_role"
}

🔔 Step 1: Send Reminder (nếu chưa escalate):

// First timeout: send reminder to original assignee
// Check: Is this first timeout or repeat?

// If first timeout (within grace period, e.g., +4 hours):
// NotificationService sends REMINDER

Email to: manager@company.com
Subject: ⚠️ NHẮC NHỞ: Đơn #LV-2026-0042 cần duyệt GẤP
Body: Xin chào Nguyễn Văn A,

Đơn nghỉ phép #LV-2026-0042 đã QUÁ HẠN duyệt.
- Đã chờ: 50 giờ (hạn: 48 giờ)
- Nhân viên: Trần Thị B
- Ngày nghỉ: 01/06 - 03/06/2026

Vui lòng duyệt NGAY: https://erp.company.com/approvals/step_inst_001

Nếu không có phản hồi trong 4 giờ, đơn sẽ được chuyển lên HR Admin.

// INSERT ProcessHistory
{
  action: "STEP_TIMEOUT",
  stepCode: "MANAGER_APPROVAL",
  metadata: { reminderSent: true, reminderAt: "2026-05-24T12:00:00Z" }
}

⬆️ Step 2: Auto-Escalate (nếu vẫn không response):

// After grace period (e.g., +4 hours), still no action
// TimeoutCheckerCronJob escalates

// Option A: Reassign to escalation role
// UPDATE StepInstance
{
  id: "step_inst_001",
  assigneeId: "hr_admin_user_id",  // ← reassigned from manager
  status: "ASSIGNED",               // ← reset to ASSIGNED
  assignedAt: "2026-05-24T16:00:00Z",
  dueAt: "2026-05-25T16:00:00Z",   // ← new deadline (+24h)
  metadata: {
    escalatedFrom: "manager_user_id",
    escalatedAt: "2026-05-24T16:00:00Z",
    escalationReason: "TIMEOUT"
  }
}

// INSERT ProcessHistory
{
  action: "STEP_ESCALATED",
  stepCode: "MANAGER_APPROVAL",
  metadata: {
    fromAssignee: "manager_user_id",
    toAssignee: "hr_admin_user_id",
    reason: "TIMEOUT after 52 hours"
  }
}

// Notify original assignee (manager)
Email: "⚠️ Đơn #LV-2026-0042 đã được chuyển lên HR Admin do quá hạn."

// Notify new assignee (HR Admin)
Email: "📋 Đơn #LV-2026-0042 cần xử lý GẤP (escalated từ Nguyễn Văn A)"

🚫 Option B: Auto-Reject (nếu config):

// Alternative: StepDefinition có timeoutAction: "AUTO_REJECT"
// Instead of escalating, system auto-rejects

// Find TransitionRule
{
  fromStepId: "step1_id",
  toStepId: null,                    // → END
  triggerAction: "TIMEOUT"           // ← special trigger
}

// UPDATE StepInstance
{
  status: "TIMEOUT",
  decision: null,                    // no human decision
  decisionComment: "Tự động từ chối do quá hạn duyệt",
  completedAt: "2026-05-24T16:00:00Z"
}

// UPDATE ProcessInstance
{
  status: "CANCELLED",               // or "TIMEOUT"
  completedAt: "2026-05-24T16:00:00Z"
}

// Notify requester
Email: "⏰ Đơn #LV-2026-0042 đã hết hạn do không được duyệt trong 48h.
        Vui lòng tạo đơn mới."

📊 Timeout Configuration Summary:

ConfigFieldExample
Timeout durationStepDefinition.timeoutHours48 (hours)
Grace periodStepDefinition.gracePeriodHours4 (hours before escalate)
Escalation targetStepDefinition.escalationRoleIdFK to Role table
Timeout actionStepDefinition.timeoutActionESCALATE | AUTO_REJECT | REMIND_ONLY
Max escalationsStepDefinition.maxEscalations2 (then auto-reject)

FLOW 7: Delegation (Ủy quyền)

🔄 Use case: Manager đi công tác, delegate cho Deputy:

// POST /api/step-instances/step_inst_001/delegate
{
  delegateToUserId: "deputy_manager_id",
  reason: "Đi công tác đến 25/05, ủy quyền cho Phó phòng"
}

// Validate: StepDefinition.allowDelegate == true

// UPDATE StepInstance
{
  assigneeId: "deputy_manager_id",    // ← new assignee
  delegatedFromId: "manager_user_id", // ← track original
  status: "ASSIGNED",
  assignedAt: "2026-05-22T11:00:00Z"  // ← reset assignment time
}

// INSERT ProcessHistory
{
  action: "STEP_DELEGATED",
  stepCode: "MANAGER_APPROVAL",
  actorId: "manager_user_id",
  metadata: {
    delegatedTo: "deputy_manager_id",
    reason: "Đi công tác đến 25/05..."
  }
}

// Notify deputy
Email: "📋 Nguyễn Văn A đã ủy quyền đơn #LV-2026-0042 cho bạn xử lý."

// Notify requester (optional)
Email: "📌 Đơn của bạn đã được chuyển từ Nguyễn Văn A sang Trần Văn C."

📊 Visual Flow Summary:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   DRAFT     │────▶│  SUBMITTED  │────▶│ IN_PROGRESS │────▶│  COMPLETED  │
│             │     │             │     │             │     │             │
│ Employee    │     │ Create      │     │ Steps       │     │ All steps   │
│ fills form  │     │ Step 1      │     │ executing   │     │ done        │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
                           │                   │
                           ▼                   ▼
                    ┌─────────────┐     ┌─────────────┐
                    │ StepInstance│     │ StepInstance│
                    │ MANAGER     │────▶│ HR_CONFIRM  │────▶ ... ────▶ END
                    │ APPROVAL    │     │             │
                    │ assignee:   │     │ assignee:   │
                    │ manager_id  │     │ hr_staff_id │
                    └─────────────┘     └─────────────┘
                           │
                           ▼ (if REJECT)
                    ┌─────────────┐
                    │  REJECTED   │
                    │             │
                    │ Process ends│
                    │ with reject │
                    └─────────────┘
            

Dynamic Forms

formSchema: Json field → JSON Schema → Zod validation → render động

Assignee Resolution

AssigneeType: ROLE, USER, DEPT_HEAD, MANAGER, DYNAMIC (expression)

Process Builder UI

React Flow drag-drop → PM define steps, transitions, notifications

Migration Strategy (5-6 weeks):

  1. Week 1-2: Create parallel tables, seed existing 4 types
  2. Week 3: Dual-write (old primary, new shadow)
  3. Week 4: Read from new, validate matches old
  4. Week 5-6: Deprecate old code, keep tables for rollback

🚀 Improvement ideas:

  1. Migration tooling consolidation — fix-*.js scripts → unified migration framework (Prisma Migrate flow)
  2. Cerbos policy versioning + audit log mỗi policy change
  3. API Gateway layer nếu có nhiều internal services (rate limit, auth aggregation)
  4. Observability stack — OpenTelemetry + Grafana cho HR workflow latency tracking

💰 MoneyFi — Multi-chain DeFi (Universal/Aptos)

FE · Feb 2025-Now

Pitch: Multi-chain DeFi automation platform — 3 sub-projects: moneyFi-dapp (user dapp), moneyFi-admin (admin), moneyfi-SDK-example (published SDK demo). Deposit/withdraw/earn yield across Aptos + 5 EVM networks via cross-chain.

ReactJSTypeScriptMaterial UITanStack QueryAptos Web3web3.jsWagmiPrivyD3Chart.jsSentryGA4OpenAPI codegenReact Hook Form + Zod

🏗️ Architecture:

🎯 Key Decisions:

  1. Adapter pattern cho chain abstraction → component code không biết Aptos vs EVM, chỉ work với common interface
  2. OpenAPI codegen → BE thay schema → FE auto-regenerate types → compile error cảnh báo break change
  3. Privy cho EVM social login → bypass MetaMask UX friction cho non-crypto users
  4. CommonJS + ESM dual build cho SDK → support cả Node.js (CJS) và modern bundlers (ESM)

🔥 Pain Points (REAL từ codebase scan):

🚀 Improvement ideas:

  1. Code-split 45+ vault components với React.lazy + Suspense — reduce initial bundle
  2. Web Workers cho heavy D3 computations — không block main thread
  3. WebSocket cho real-time portfolio thay vì TanStack Query polling
  4. Storybook cho 45+ components — design system documentation + visual regression
  5. E2E tests Playwright cho critical wallet flows (login, deposit, withdraw)
  6. SDK examples ecosystem — Next.js example, Vue example, Vanilla JS example

📝 Decot — Privacy-Preserving CLM on Sui

FE Maintainer · Oct 2025-Feb 2026

Pitch: Contract Lifecycle Management (CLM) platform với e-signatures + on-chain verification trên Sui blockchain, ZKLogin để privacy-preserving auth.

Next.jsReactRedux ToolkitStyled ComponentsAnt DesignSui BlockchainZKLoginMysten Dapp-kitAmazon Cognito OAuthSocket.IORedux PersistCypressDocker (dev/staging/prod)

🏗️ Highlights:

🎯 Discuss khi được hỏi:

  1. Tại sao ZKLogin? → User auth qua OAuth (Google/Cognito) NHƯNG vẫn có on-chain identity, không expose private key. Zero-knowledge proof của OAuth credential.
  2. Tại sao Redux Toolkit (project legacy)? → State global cho document state + collaboration. Persist cho draft.
  3. DocumentViewer 40KB — bundle implication: nên code-split.

🚀 Improvement ideas:

  1. Migrate Redux Toolkit → TanStack Query + Zustand — server state vs client state separation
  2. Code-split DocumentViewer 40KB với dynamic import (only mount khi user opens contract)
  3. Virtual scrolling cho long PDFs — react-window/react-virtuoso
  4. Document version control + diff visualization — feature giá trị
  5. Operational Transform (OT) hoặc CRDT cho real-time collaboration thay Socket.IO broadcasting

🏛️ VCCI Member Management Platform

FE · Jul-Dec 2025

Pitch: Membership registration + lifecycle management cho Vietnam Chamber of Commerce and Industry. 40+ form fields, two-tier registration (Company vs Association), full Vietnamese localization.

Vite 7ReactJSTanStack RouterTanStack QueryHeroUIRadix UITailwind CSSReact Hook Form + ZodFramer Motioni18nextinput-otp

🏗️ Highlights:

🎯 Talking points:

  1. Form 40+ fields perf: RHF uncontrolled → field-level re-render → form vẫn smooth
  2. Zod discriminated union cho conditional schemas (Company schema vs Association schema)
  3. TanStack Router — type-safe routes, code-splitting

🏆 Go Claw — AI Agentic for HR Management

3rd Prize · Twendee Hackathon

Pitch: AI Agentic application cho HR Management. Team "Claude Code Never Sleep" — 3rd Prize. Reference code trong Twd/goclaw-ref.

⚠️ PERFECT alignment với Base.vn HRM. Mention story này trong "tại sao Base.vn?".

Story key:

→ Base.vn pitch angle: "Em build Go Claw cho HR Management trong hackathon → win Top 3. Đó là lý do em hứng thú với Base.vn — Base HRM là exactly cùng problem space em đã solve ở scale prototype. Em muốn áp dụng experience đó vào production scale tại Base."

🔍 CKForensics — Open Source CLI Tool

Side Project · Open Source

Pitch: Forensic analysis tool cho Claude Code sessions — token cost, context map, audit manifest, skill recommendations. Phân tích "Claude Code đã touch những file gì, tokens đâu đi, session cost bao nhiêu".

Tech:

TypeScriptBunCLIOpen Source (MIT)gitleakseslintConventional Commits

Why mention this:

🔥 Pain Points Summary — Interview Cheat Sheet

Cross-project patterns để articulate trong interview — based on REAL codebase scan 2026-05-22

📊 Quick Stats

ProjectCodeFlowCriticalTop Issue
Shiru10-364 as any, monolithic services
Twendee ERP843Multi-channel chaos, no notification hub
MoneyFi8-24 state systems, chain duplication

🚨 Code Red Flags

  • • No centralized tokens → rebranding impossible
  • • 2,000+ LOC services → untestable, unscalable
  • • as any everywhere → TypeScript useless
  • • No tests → refactoring blocked

🔄 Flow Red Flags

  • • Multi-channel confusion → 3 links cho 1 flow
  • • Telegram chỉ notify → phải về web approve
  • • No notification hub → phải refresh page

📈 Scale Red Flags

  • • 80+ relations .include() → N+1
  • • Sync notifications → blocked
  • • No tenantId → single-tenant

🔧 Extend Red Flags

  • • Hardcoded ProcessType enum
  • • Service-per-type → 3-5 days/type
  • • No process builder UI

🌟 Shiru Pain → Answer

Q: "Technical challenge khó nhất?"

A: "KYC Sumsub ↔ Auth circular dependency. Auth cần Sumsub verify, Sumsub cần Auth get user. Dùng forwardRef nhưng đó là workaround. Nếu làm lại: KYC module riêng, communicate qua events/queue."

🏢 ERP Pain → Answer

Q: "Handle legacy code thế nào?"

A: "Nhận codebase 2,247 useState + components 3,000 LOC. Không thể rewrite. Approach: identify highest-risk areas (payroll), extract domain logic vào hooks, add tests incrementally."

💰 MoneyFi Pain → Answer

Q: "State management philosophy?"

A: "4 systems cạnh tranh — debug phải check cả 4. Solution: Server state = React Query (caching, refetch). UI state = Zustand (simple, granular). Kill Redux."

🔄 ERP Flow Pain → Answer

Q: "Thách thức UX/process design?"

A: "Onboarding flow có 3 links khác nhau: HR tạo process → nhận Telegram link → gửi link khác cho nhân viên mới. User không biết link nào làm gì. Solution: single onboarding link + Telegram inline approve buttons + unified notification hub."

🔄 Cross-Channel Pain → Answer

Q: "Sao approval flow chậm?"

A: "Telegram chỉ notify 'có request mới' nhưng phải về web approve. No inline action buttons. Request pending 3 ngày không có escalation. Solution: Telegram /approve_[id] callback + auto-escalate rule."

📈 ERP Scale 10K → Answer

Q: "Scale ERP lên 10K users thì sao?"

A: "3 bottlenecks chính: (1) User model có 80+ relations fetched via .include() — N+1 queries. (2) Notifications gửi sync trong request — block 30s+. (3) No Redis — cache trong memory sẽ OOM. Fix: connection pool + .select() + Bull queue + Redis layer."

📈 Multi-tenant → Answer

Q: "Multi-org support thế nào?"

A: "Hiện tại không có tenantId — single-tenant design. Scale 100K+ cần: (1) Add tenantId FK to all models (migration). (2) PostgreSQL Row-Level Security policies. (3) Tenant middleware filter mọi query. Đây là rewrite, không phải refactor."

🔧 New Process Type → Answer

Q: "Thêm workflow type mới thế nào?"

A: "Hiện tại ProcessType là hardcoded enum. Thêm type mới cần: enum + Prisma model + service + helper + DTO + FE form + notification cases. ~3-5 days per type. Solution: database-driven process definitions + generic step engine + JSON form schema. Refactor ~2-3 weeks."

🔧 PM/HR Create Process → Answer

Q: "Non-dev có thể tạo process type?"

A: "Hiện tại: không. Zero end-user flexibility. Cần: (1) Process builder UI. (2) Step definitions stored in DB. (3) BPMN-like rule engine cho transitions. (4) Event-driven notifications với templates. Đây là feature lớn, không quick fix."

✅ Schema Design → Answer

Q: "Schema design cho flexible workflow?"

A: "3 layers: (1) Config tables — ProcessDefinition, StepDefinition, TransitionRule. (2) Runtime tables — ProcessInstance, StepInstance, ProcessHistory. (3) Dynamic forms — JSON Schema trong formSchema field, validate bằng Zod, render động."

✅ Migration Strategy → Answer

Q: "Migrate từ hardcoded → flexible?"

A: "4 phases, 5-6 weeks: (1) Parallel tables + seed existing types. (2) Dual-write — old primary, new shadow. (3) Read from new, validate matches old. (4) Deprecate old, keep for rollback 30 days. Zero downtime."

⏱️ Improvement Timeline I Would Propose

WeekFocusImpact
1-2Design tokens + strict tsconfigFoundation — stop bleeding
3-4Extract hooks from giant componentsTestability — unblock refactor
5-6Add critical path tests (>60%)Confidence — ship with safety
7-8State management consolidationDebug speed — single source of truth
9-10API layer standardizationReliability — retry, error handling

🎤 Master Talking Points (memorize)

"Giới thiệu bản thân"

"Em là Phong, Front-end Developer 2 năm kinh nghiệm, hiện đang shift sang Full-stack và AI Agentic Engineering tại Twendee. Em đã ship 7+ FE projects, 5+ Web3 products với tổng transaction ~$20M, và đang lead 1 AI Orchestrator platform multi-tenant. Top 3 Twendee AI Hackathon với idea Go Claw cho HR Management — em rất hứng thú với hệ sản phẩm của Base, đặc biệt Base HRM."

"Dự án ấn tượng nhất?"

"Em mention 2: Shiru — fullstack AI advisor với NestJS+Prisma+PostgreSQL+BullMQ+Redis, em ownership JWT refresh-rotation và BullMQ async architecture. TWDAgentsHub — multi-tenant AI Orchestrator với PostgreSQL RLS, Turborepo, 5 domain agents. Đây là evolution từ hackathon Go Claw."

"Thách thức kỹ thuật khó nhất?"

"Em chọn multi-tenancy với RLS trong TWDAgentsHub. Trước em chỉ biết tenant_id check ở app level. Học PostgreSQL Row-Level Security → defense-in-depth, enforced at DB. Trade-off: complexity setup nhưng security guarantee mạnh hơn nhiều. Apply pattern này khi build cho Vietnamese SMEs vì compliance/data isolation critical."

"Đề xuất cải tiến dự án?"

"Shiru: thêm OpenTelemetry tracing — debug BullMQ jobs đang gặp khó. Migrate transaction history pagination offset → cursor (table sắp lớn). TWDAgentsHub: prompt evaluation harness — golden dataset regression test. Anthropic prompt caching giảm 90% input tokens. MoneyFi: code-split 45+ components giảm initial bundle."

"Tại sao Base.vn?"

"3 lý do. (1) Base HRM — em đã build Go Claw cho HR ở hackathon scale, muốn áp dụng vào production multi-tenant của Base. (2) Hệ sản phẩm 31 products phục vụ 11k+ doanh nghiệp VN — challenge multi-tenant + multi-product compelling. (3) Engineering culture — Base focus product, UX clean, engineering quality cao — match expectation của em về team."

"Mindset làm việc?"

"3 nguyên tắc. (1) Bug-first thinking — pattern nào prevent bug nào, không cargo-cult. (2) AI Agentic workflow — Claude Code orchestrate review/test/refactor để focus vào decisions, không chỉ typing. (3) Continuous learning — bài Async event loop em mới deep-dive tuần trước, useMemo+React Compiler là next exploration."

❓ 12 Smart Questions to Ask Base.vn

Cuối interview, interviewer sẽ hỏi "Em có câu hỏi gì cho bên anh/chị không?". KHÔNG bỏ qua — đây là cơ hội (1) show engagement + research, (2) extract info để decide nếu được offer, (3) close interview mạnh. Hỏi 3-4 câu thôi, pick từ list theo flow.

🚀 Role & Growth (chọn 1-2)

Q1 · Signal cao — onboarding pattern

"Anh/chị thấy engineer mới gia nhập Base làm tốt trong 3 tháng đầu thường có pattern gì? Ngược lại, ai struggle thì lý do thường là gì?"

→ Show: bro nghĩ proactively về success. Extract: expectation thực tế, common pitfalls.

Q2 · Specific product interest

"Em đang quan tâm tới Base HRM. Engineer mới có cơ hội ownership feature gì trong 6 tháng đầu không, hay sẽ rotation qua nhiều product?"

→ Show: research kỹ, biết Base có nhiều products. Extract: ownership structure, career path.

Q3 · Career path

"Path development cho engineer tại Base ra sao? Có level rõ ràng (Junior → Mid → Senior → Staff) hay flat structure?"

→ Senior signal: thinking about progression. Important info cho offer evaluation.

🏗️ Engineering Culture (chọn 1-2)

Q4 · AI workflow (Base có Vibe Code Engineer)

"Em nghe Base có Vibe Code Engineer role. Engineer ở Base dùng AI tools (Claude Code, Cursor) trong daily workflow ra sao? Có guideline nào về responsible AI usage không?"

→ ⭐ Highly relevant — bro đang là AI Agentic Engineer. Show alignment với culture. Extract: actual AI maturity.

Q5 · Code review process

"Code review process tại Base như thế nào? Trung bình 1 PR mất bao lâu từ open → merge? Có pair programming hay async review?"

→ Show: care about quality + collaboration. Extract: PR velocity (signal về team health).

Q6 · Tech debt vs new feature

"Engineer ở Base làm sao balance giữa shipping feature mới và technical debt? Có dedicated time cho refactor/improvement không?"

→ Mature signal. Extract: real culture (most companies SAY they value tech debt but don't budget time).

Q7 · Testing & deployment

"Testing strategy của Base ra sao — unit/integration/E2E proportion? Cadence release thế nào, có CI/CD pipeline cho mỗi product?"

→ Show production mindset. Extract: engineering maturity.

👥 Team & Product (chọn 1)

Q8 · Team structure

"Team Engineering của product anh/chị đang lead có bao nhiêu người? Cấu trúc — squad theo product hay function?"

→ Personal touch (asking về interviewer's team). Extract: team size + structure.

Q9 · Tech challenge lớn nhất

"Phía Base đang đối mặt với technical challenge lớn nhất hiện tại là gì? Multi-tenancy, scale, integration giữa 31 products, hay AI adoption?"

→ ⭐ Senior signal — thinking about real problems. Show breadth + curiosity. Họ thường share genuine.

Q10 · Cross-product platform

"Base có 31+ products. Engineering team handle scale này như thế nào — shared platform team hay mỗi product team độc lập tech stack?"

→ Show: understand business complexity. Extract: architecture decisions.

💖 Personal + Next Steps (chọn 1, ALWAYS hỏi)

Q11 · Personal story

"Anh/chị thấy điều gì khiến anh/chị work ở Base lâu nhất? Hoặc khoảnh khắc nào anh/chị thấy proud về Base nhất?"

→ Họ kể story → bro listen → build rapport. Họ remember bro = senior emotional intelligence.

Q12 · ⭐ MUST ASK · Next steps

"Dạ sau buổi này, next steps là gì? Timeline kết quả khoảng bao lâu? Có gì em có thể chuẩn bị thêm cho round tiếp theo (nếu có)?"

→ Luôn hỏi cuối cùng. Show interest in moving forward. Set expectation về timeline.

🚫 KHÔNG hỏi những câu này với Tech Lead

🎯 Suggested Flow — 3 câu hỏi

  1. OPENER (technical/cultural): Q4 (AI workflow) HOẶC Q9 (tech challenge) — chọn 1 dựa vào flow interview
  2. MIDDLE (role/team): Q1 (onboarding) HOẶC Q8 (team structure) — depend on rapport
  3. CLOSER (always): Q12 (next steps) — bắt buộc

→ 3 câu là đủ. Quá nhiều = drag interview. Quá ít (0-1) = không engaged. Sweet spot 3-4.

📧 Sau khi xong interview — 24h follow-up

Gửi thank-you email tới HR (cc tech lead nếu có email) trong vòng 24h:

Subject: Cảm ơn buổi PV Base.vn ngày 19/05 — Trần Nam Phong

Dạ chị [HR name],

Em cảm ơn anh/chị [tech lead names] đã dành thời gian trao đổi với em
chiều nay. Em đặc biệt thấy hứng thú với phần thảo luận về [1 SPECIFIC
TOPIC discussed, vd: multi-tenancy approach cho Base HRM / engineering
culture với AI tools / tech challenge X].

Em đã đọc thêm về [related topic / link] sau buổi PV, em có thêm 1
suggestion là [brief idea — show continued thinking].

Em mong nhận được phản hồi từ phía Base. Có gì em hỗ trợ thêm,
chị cứ ping em ạ.

Em chân thành cảm ơn,
Phong
[phone] · [email] · [linkedin]

→ 1 specific reference từ buổi PV (không generic) + 1 idea bro nghĩ sau = show genuine engagement. Senior signal.

✅ Interview Day Checklist