Skip to main content

Comprender MVP — Effort E1 (foundation + identity)

canonical · pm · updated 2026-09-05 · source

What the product side of TED OS IS today, after effort E1 shipped (deployed). This is the client-facing System of Record (tenant data) — separate from the dev-workflow Plan Engine graph. It describes the running system, not the build process; for "what happened" see decisions/log.md.

Note (ADR-017, 2026-08-31): the two-level apps/admin shell this doc describes is now FROZEN — apps/portal is the primary tenant surface, and Comprender itself is retiring/ migrating into portal. The System of Record + contract described below (@tedos/domain, @tedos/shared) remain canonical; the admin-shell delivery detail is historical.

Comprender = tenant #1. The first paying client (an education provider) is the first project on the platform. E1 is "MVP slice 1": identity-first — a single, unified person deduped across Comprender's channels, with fiscal data, on real infra with server-side tenancy + roles.

What E1 delivered

  • A product System of Record for tenant/client data — package @tedos/domain (Drizzle/Postgres), in a dedicated product Postgres schema.
  • A frozen shared contract in @tedos/shared (/domain + /api entry points): zod-as-source
    • inferred TypeScript types for project / person / person_identity, plus the 8 endpoint request/response shapes. Both the API and the admin import it — consistency by construction.
  • Auth + tenancy in apps/api (Clerk): a requireProject guard maps a Clerk org → projectId
    • role; superadmin is the operator (OPERATOR_USER_IDS). Every /p/:id/* route is authorized server-side.
  • Projects + Person API in apps/api (Fastify) — see the surface below.
  • A two-level admin shell in apps/admin: Level 1 (platform) + Level 2 (project context).
  • Match-key dedupe (email / phone / RFC): the same person arriving from two channels unifies into one record, with an append-only merge audit.
  • Deployed to real infra (Fly + Vercel) — see deploy topology.

The product SoR is isolated from the dev-workflow graph (hard rule)

Per ADR-006, tenant/client data must never enter the dev-workflow graph. The product SoR uses its own Drizzle journal/config in a dedicated product Postgres schema. The dev-workflow Plan Engine graph (@tedos/db, effort #560, live) uses a dedicated plan schema. The two never share tables. As built, both schemas currently live on the same Fly MPG (managed Postgres) cluster — a documented compromise (no real tenant data exists yet) with a tracked upgrade path: move the plan graph to a dedicated cluster before real tenant-PII volume or any external training-data export. See plan-engine.md §7 for the full decision + upgrade path. This boundary is the product/workflow split applied at the data layer.

Data model (@tedos/domain, Drizzle — product schema)

TablePurposeKey fields
projectThe tenant (= a Clerk org = a client)id · clerk_org_id · name · status · domain · created_at
personA unified person inside a projectid · project_id · full_name · email · phone · rfc (fiscal, PII-masked) · created_at · updated_at
person_identityOne source/channel record for a personid · person_id · channel (live | recorded | posgrado) · external_ref · raw (jsonb)
person_mergeAppend-only merge audit logmerge source/target + reason

Match keys — unique-ish on (project_id, email), (project_id, phone), (project_id, rfc); the merge/dedupe service links/dedupes on these. Tenant scoping (project_id) is mandatory on every product row.

API surface (apps/api, Fastify v5)

All routes behind the auth/tenancy guard (Clerk org + role; superadmin = any project). Tenant scoping is server-side, never trusted from the client.

MethodRouteWhat
GET / POST/projectsList / create the operator's projects
GET/projects/:idOne project
GET/p/:id/studentsList + search the project's people
GET/p/:id/students/:sidOne person (single-view)
POST / PATCH/p/:id/studentsCreate / update a person
POST/p/:id/students/mergeMerge two people (dedupe)
GET/p/:id/summaryProject summary (basic)

The admin reaches these through a proxy at /api/e1/* (env ENGINE_API_URL), so Clerk session + tenant context inject server-side.

Two-level admin shell (apps/admin)

The same chrome wraps both levels; the level swaps only the sidebar + topbar slots (composition, not a boolean variant).

  • Level 1 — platform: /projects switcher (pick / create a project). Root route group.
  • Level 2 — project context: /p/[projectId]/… swaps in the project sidebar, sets org context (ProjectProvider), and renders role-scoped nav (Owner / Sales / Finanzas / Soporte / Alumno). Screens: Alumnos list, person single-view (channels, fiscal, enrollments placeholder), Resumen (basic).

Each screen ships the 4 mandatory states and follows the admin conventions (semantic tokens, HugeIcons, Grid v2, components/ui/ reuse, one Cian accent per screen). Paper design was deferred for E1 — the screens use the existing design system and are flagged for later Designer review (see below).

Roles

Clerk org membership maps to a ProjectRole: Owner · Sales · Finanzas · Soporte · Alumno. The operator is superadmin (OPERATOR_USER_IDS) — any project. The role gates Level-2 nav (admin) and authorization on every /p/:id/* route (API).

Deploy topology

  • API — Fly app tedos-api (region gru) as two process groups: web (Fastify) + worker (pg-boss). Backed by Fly Managed Postgres (Basic, gru). Live at https://tedos-api.fly.dev (custom domain api.tuempresa.digital pending DNS).
  • Admin — Vercel (tedos-admin).
  • The product SoR (product schema) and the dev-workflow Plan Engine graph (plan schema) currently share this Fly MPG cluster, isolated by Postgres schema — a documented compromise with a tracked upgrade path to a dedicated cluster (see the hard rule above and plan-engine.md §7).

Screens flagged for Designer review (Paper deferred)

E1 shipped these on the existing design system without a Paper spec; each needs a later Designer pass (per design-routing.md):

  • /projects — Level-1 platform project switcher
  • /p/[projectId] — Level-2 project shell + role-scoped nav
  • Alumnos — people list
  • Person single-view — channels, fiscal, enrollments placeholder
  • Resumen — basic project summary

The Comprender hand-authored theme (.tenant-comprender navy+gold scope, custom fonts, section bands — effort #1376) was removed by effort #1461: the tenant portal renders only on the neutral design system + runtime brandingTokens; the single-rail chrome stays as the generic tenant-chrome. The old spec (comprender-tenant-design.md) is deleted — git is the archive.