# Demo Email Domain Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Standardize demo accounts on `@agendamax.demo`, make every demo login explicit in the README, and migrate local and Coolify data without deleting records. **Architecture:** Keep the current simple demo authentication and replace only the two platform-owned demo email constants plus their test fixtures. Apply one targeted, idempotent SQLite `UPDATE` to the exact existing admin and owner rows; appointments, businesses, and related records remain unchanged. Deploy the source through the existing GitHub mirror and verify the live app through health and login endpoints. **Tech Stack:** React/Vite, Express, TypeScript, Node 22 `node:sqlite`, SQLite, PowerShell/OpenSSH, Coolify v4.1.2. ## Global Constraints - Keep the demo password exactly `demo1234`. - Replace active `@agendapro.demo` references with `@agendamax.demo`; retain the old domain only in explicit negative-login assertions or migration-history documentation. - Do not reset or delete local or production business, appointment, client, employee, ticket, or review data. - Never commit `.env.local.ps1` or print token values. - Production deployment must finish before the production data migration is run. - If production deployment fails, stop before changing the production SQLite database and use the documented rollback path. --- ### Task 1: Update Demo Identity References and Access Documentation **Files:** - Modify: `README.md:66-82` — show email and password for every demo account. - Modify: `server/index.ts:38-43` — use the new default owner email. - Modify: `server/scripts/seed.ts:315-334` — use the new admin and owner emails for fresh databases. - Modify: `src/pages/LoginPage.tsx:8-15` — use the new default development login. - Modify: `admin-test.mjs`, `e2e-test.mjs`, `e2e-full.mjs`, `server/scripts/booking-e2e.mjs`, `visual-audit.mjs`, and any other active file returned by `rg -l 'agendapro\.demo'` — keep automated login fixtures aligned. - Modify: active plan/report documents containing executable login examples; keep old-domain examples only as migration-history documentation or explicit negative-login assertions. **Interfaces:** - Fresh seed continues to create users with the existing schema and password. - The login page continues to load `/api/auth/demo-users` and quick-login users; only the initial email constant changes. - [ ] **Step 1: Record the current active references and credentials** Run: ```powershell rg -n --glob '!node_modules' --glob '!data/**' 'agendapro\.demo|demo1234' . ``` Expected: references are limited to the known seed, login defaults, tests, README, explicit negative-login assertions, and migration-history documentation; no secret token values are printed. - [ ] **Step 2: Update the README credentials table first** The resulting table must explicitly show: ```markdown | **Admin**| `admin@agendamax.demo` | `demo1234` | Administrador | | Dueño | `owner@agendamax.demo` | `demo1234` | Daniela Reyes | ``` Every employee row must also include the same password in its password column. Keep the note that the login screen offers quick access. - [ ] **Step 3: Update fresh-seed and UI constants** Use these exact values: ```ts // server/index.ts and server/scripts/seed.ts ownerEmail: "owner@agendamax.demo" // server/scripts/seed.ts VALUES (NULL, 'admin@agendamax.demo', 'demo1234', 'Administrador', 'admin', '#0f172a') // src/pages/LoginPage.tsx useState(DEMO ? "owner@agendamax.demo" : "") ``` - [ ] **Step 4: Update all active automated fixtures** Replace only the email domain in existing login assertions and fixtures. Do not change employee domains or passwords. - [ ] **Step 5: Verify the source tree has no stale active domain** Run: ```powershell rg -n --glob '!node_modules' --glob '!data/**' --glob '!docs/superpowers/specs/**' 'agendapro\.demo' . ``` Expected: no output from application code or positive fixtures; the intentional old-domain negative-login assertions and migration-history documents are the only allowed matches. ### Task 2: Migrate the Local SQLite Demo Users **Files:** - Modify: local ignored database `data/agendapro.db` only; no tracked database files. **Interfaces:** - Uses the existing `server/db.ts` connection and `users` table. - Produces updated exact admin/owner email rows while preserving all row counts outside `users.email`. - [ ] **Step 1: Confirm the local database and take a count snapshot** Run from the repository root: ```powershell Test-Path -LiteralPath .\data\agendapro.db node --import tsx --input-type=module -e "import { db } from './server/db.ts'; console.log(JSON.stringify({users: db.prepare('SELECT id,email,role FROM users ORDER BY id').all(), appointments: db.prepare('SELECT COUNT(*) AS c FROM appointments').get(), clients: db.prepare('SELECT COUNT(*) AS c FROM clients').get()}));" ``` Expected: the local database exists or the command reports that a fresh seed is required; existing appointment/client counts are recorded before mutation. Before updating, inspect the exact old/new admin and owner rows from the `users` snapshot. For either role, if its target email already exists while its matching old-domain row also exists, stop and resolve that unique-email collision instead of running the update. If no old-domain rows exist and the new rows are present, record a safe no-op and skip the update. - [ ] **Step 2: Apply the idempotent local email update** Run: ```powershell node --% --import tsx --input-type=module -e "import { db } from './server/db.ts'; const r = db.prepare(\"UPDATE users SET email = CASE email WHEN 'admin@agendapro.demo' THEN 'admin@agendamax.demo' WHEN 'owner@agendapro.demo' THEN 'owner@agendamax.demo' END WHERE (email = 'admin@agendapro.demo' AND role = 'admin') OR (email = 'owner@agendapro.demo' AND role = 'owner')\").run(); console.log(JSON.stringify(r));" ``` Expected: only the exact email/role pairs are changed; an old-domain employee or other role is untouched. Running the same command again reports zero additional changes. - [ ] **Step 3: Verify local credentials and preservation** Run: ```powershell node --% --import tsx --input-type=module -e "import { db } from './server/db.ts'; console.log(JSON.stringify({demo: db.prepare(\"SELECT email,role,password FROM users WHERE email IN ('admin@agendamax.demo','owner@agendamax.demo') ORDER BY role\").all(), appointments: db.prepare('SELECT COUNT(*) AS c FROM appointments').get(), clients: db.prepare('SELECT COUNT(*) AS c FROM clients').get()}));" ``` Expected: `admin@agendamax.demo` and `owner@agendamax.demo` use `demo1234`; appointment and client counts match the snapshot. ### Task 3: Run Local Regression Checks **Files:** - No additional files; validate Task 1 and Task 2 together. **Interfaces:** - Existing package scripts are the regression contract: typecheck, build, unit tests, and API tests. - [ ] **Step 1: Run typecheck and build** ```powershell npm run typecheck npm run build ``` Expected: both commands exit with code 0. - [ ] **Step 2: Run unit and admin/API tests** ```powershell npm run test:unit npm run test:admin npm run test:e2e ``` Expected: typecheck, build, unit, admin, and non-slot authentication checks pass. On the already-migrated local database, `businesses.working_hours` and employee `working_hours` are null, so the slot-dependent e2e/booking assertions may fail before booking (including their existing downstream dereference); record this pre-existing limitation as unrelated to the email change and do not change scheduling data for this task. - [ ] **Step 3: Inspect the final diff** ```powershell git diff --check git status --short ``` Expected: only intended source, test, README, and specification/plan files are changed; `.env.local.ps1` and `data/agendapro.db` remain ignored. ### Task 4: Deploy AgendaMax Through Coolify **Files:** - Modify: the tracked AgendaPro source files from Tasks 1-3 through a normal commit. - Do not modify: `.env.local.ps1`, local SQLite files, or Coolify credentials in Git. **Interfaces:** - Source mirror: the existing GitHub remote used by Coolify (`urieljarethbusiness-cpu/agendamax`). - Coolify application: UUID `s30f7egdlkx4wyjp59o1iunc`, FQDN `https://agendamax.urieljareth.org`. - [ ] **Step 1: Commit only the intended tracked changes** ```powershell git add README.md server/index.ts server/scripts/seed.ts src/pages/LoginPage.tsx admin-test.mjs e2e-test.mjs e2e-full.mjs server/scripts/booking-e2e.mjs visual-audit.mjs docs/superpowers/specs/2026-07-27-demo-email-domain-design.md docs/superpowers/plans/2026-07-27-demo-email-domain.md git diff --cached --check git commit -m "docs: standardize AgendaMax demo access" ``` Add any additional active fixture files found by Task 1 explicitly; never use `git add .`. - [ ] **Step 2: Push the source mirror used by Coolify** ```powershell git push github main ``` Expected: the new commit is available in the GitHub mirror without exposing credentials in output. - [ ] **Step 3: Trigger and monitor the Coolify deploy** ```powershell . .\.env.local.ps1 $h = @{ Authorization = "Bearer $env:COOLIFY_TOKEN" } $result = Invoke-RestMethod "$env:COOLIFY_API_URL/deploy?uuid=s30f7egdlkx4wyjp59o1iunc&force=true" -Headers $h $deployment = $result.deployments[0].deployment_uuid Invoke-RestMethod "$env:COOLIFY_API_URL/deployments/$deployment" -Headers $h ``` Poll until the status is `finished`; do not run the production SQL migration while it is queued, in progress, or failed. ### Task 5: Migrate and Verify Production Data **Files:** - Modify: persistent SQLite database inside the running AgendaMax Coolify app container only. - Do not modify: source files or unrelated production tables. **Interfaces:** - Production app data path: `/app/data/agendapro.db`. - Proxmox access: host `192.168.0.200`, LXC `102`; use the existing `scripts/Invoke-ProxmoxSsh.ps1`/`ProxmoxAgent.ps1` helpers and environment variables. - [ ] **Step 1: Identify the running AgendaMax app container and verify the old rows** Use the existing Proxmox SSH chain to run inside LXC 102: ```sh container=$(docker ps --format '{{.ID}} {{.Names}}' | awk '$2 ~ /agendamax|s30f7/ {print $1; exit}') test -n "$container" || { echo 'AgendaMax container not found' >&2; exit 1; } docker exec "$container" node -e "const {DatabaseSync}=require('node:sqlite'); const db=new DatabaseSync('/app/data/agendapro.db'); console.log(db.prepare(\"SELECT id,email,role FROM users WHERE email IN ('admin@agendapro.demo','owner@agendapro.demo','admin@agendamax.demo','owner@agendamax.demo') ORDER BY id\").all())" ``` Expected: the app is running the new image and the current exact admin/owner rows are visible before mutation. If an old row and its matching new row both exist, stop and resolve the unique-email collision before updating. - [ ] **Step 2: Apply the production email update** ```sh docker exec "$container" node -e "const {DatabaseSync}=require('node:sqlite'); const db=new DatabaseSync('/app/data/agendapro.db'); const r=db.prepare(\"UPDATE users SET email=CASE email WHEN 'admin@agendapro.demo' THEN 'admin@agendamax.demo' WHEN 'owner@agendapro.demo' THEN 'owner@agendamax.demo' END WHERE (email='admin@agendapro.demo' AND role='admin') OR (email='owner@agendapro.demo' AND role='owner')\").run(); console.log(r)" ``` Expected: only the matching admin and owner rows move to `@agendamax.demo`; no old-domain employee or other role is rewritten, and no business or appointment rows are deleted. If the preflight query shows an old row alongside its matching new row, stop and resolve the unique-email collision. If it shows no old-domain rows because the deployment seeded a fresh database with the new domain, record the update as a safe no-op and skip it. - [ ] **Step 3: Verify live health and credentials** ```powershell $health = Invoke-RestMethod https://agendamax.urieljareth.org/api/health $admin = Invoke-RestMethod https://agendamax.urieljareth.org/api/auth/login -Method Post -ContentType 'application/json' -Body '{"email":"admin@agendamax.demo","password":"demo1234"}' $owner = Invoke-RestMethod https://agendamax.urieljareth.org/api/auth/login -Method Post -ContentType 'application/json' -Body '{"email":"owner@agendamax.demo","password":"demo1234"}' Write-Host "health_ok=$($health.ok) admin_role=$($admin.user.role) admin_token_present=$([bool]$admin.token) owner_role=$($owner.user.role) owner_token_present=$([bool]$owner.token)" ``` Expected: health returns `ok: true`; both logins return the correct roles. Only boolean token-presence values are printed, never token contents. - [ ] **Step 4: Verify the deployed page and repository state** Run the existing browser/HTTP post-deploy check if available, then: ```powershell git status --short ``` Expected: the deployed AgendaMax page loads, no uncaught frontend errors are reported, and only intentional local follow-up changes remain.