phase-6: the staff console, and a log that says who did what

Flow H, three screens behind a role check: find an account, work the
ingestion error queue, read and export the audit log. Superadmins can
change a role, never their own.

The error queue merges ingest errors and dead jobs into one table with a
cursor that pages both sources; only a job can be retried and only an
ingest row resolved, with a note that migration 003 gives it somewhere
to live.

writeAudit no longer defaults a missing subject to the actor, which had
been recording a user search as staff looking themselves up. Omitting
the subject still means acting on yourself; null now means the action
has no subject, which is what a search, a retry and an export are.

Reading the log is not audited. Exporting it is: a copy leaving the
building is a different act from looking.

e2e/global-setup.ts asks for every screen once before the suite starts,
so a dev server's first-request compile is paid before the first test
rather than by it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Michilis
2026-09-04 21:55:59 +00:00
co-authored by Claude Opus 5
parent 6620650e9e
commit 4c39926483
39 changed files with 2767 additions and 11 deletions
+69
View File
@@ -421,4 +421,73 @@ export const en: Record<MessageKey, string> = {
"admin.errors.resolve": "Mark resolved",
"admin.audit.title": "Audit",
"admin.audit.export": "Export CSV",
// Admin console (phase 6)
"common.loadMore": "Show more",
"common.none": "No data",
"admin.nav.console": "Console",
"admin.forbidden": "This section is for the team only.",
"admin.users.searchLabel": "Find an account",
"admin.users.searchHint": "Email, RUC, CI or name. Type at least 2 characters.",
"admin.users.empty": "No account matches that.",
"admin.users.start": "Search for an account to begin.",
"admin.users.col.email": "Email",
"admin.users.col.name": "Name",
"admin.users.col.doc": "RUC or CI",
"admin.users.col.role": "Role",
"admin.users.col.created": "Joined",
"admin.users.counts.documents": "Comprobantes",
"admin.users.counts.needsReview": "To review",
"admin.users.counts.declarations": "Declarations",
"admin.users.lastActivity": "Last activity: {date}",
"admin.users.noActivity": "No activity yet",
"admin.users.noProfile": "This account has not finished its profile.",
"admin.users.openErrors": "Open errors on this account",
"admin.users.noErrors": "No open errors.",
"admin.users.obligations": "Obligations",
"admin.role.title": "Role",
"admin.role.change": "Change role",
"admin.role.confirm": "You are changing {email} to {role}. This is logged under your name.",
"admin.role.saved": "Role updated.",
"admin.role.user": "User",
"admin.role.accountant": "Accountant",
"admin.role.staff": "Staff",
"admin.role.superadmin": "Superadmin",
"admin.errors.empty": "The queue is empty.",
"admin.errors.col.when": "When",
"admin.errors.col.stage": "Stage",
"admin.errors.col.user": "Account",
"admin.errors.col.message": "Message",
"admin.errors.col.status": "Status",
"admin.errors.filterStage": "Stage",
"admin.errors.filterStatus": "Status",
"admin.errors.all": "All",
"admin.errors.allStatus": "All",
"admin.errors.stage.qr_parse": "QR",
"admin.errors.stage.ocr": "OCR",
"admin.errors.stage.dedupe": "Duplicates",
"admin.errors.stage.verify": "Verification",
"admin.errors.stage.job": "Job",
"admin.errors.stage.other": "Other",
"admin.errors.status.open": "Open",
"admin.errors.status.resolved": "Resolved",
"admin.errors.retry": "Retry the job",
"admin.errors.note": "Resolution note",
"admin.errors.notePlaceholder": "What happened and what you did",
"admin.errors.attempts": "{count} attempts",
"admin.errors.goToQueue": "Open the full error queue",
"admin.audit.empty": "Nothing matches those filters.",
"admin.audit.col.when": "When",
"admin.audit.col.actor": "Who",
"admin.audit.col.action": "Action",
"admin.audit.col.subject": "About whom",
"admin.audit.col.resource": "Resource",
"admin.audit.col.ip": "IP",
"admin.audit.filterActor": "Who (email)",
"admin.audit.filterAction": "Action",
"admin.audit.filterSubject": "About whom (email)",
"admin.audit.filterFrom": "From",
"admin.audit.filterTo": "To",
"admin.audit.apply": "Filter",
"admin.audit.clear": "Clear",
"admin.audit.total": "{count} entries",
};
+70
View File
@@ -210,6 +210,76 @@ export const esExtra = {
"decl.approvedAt": "Aprobada el {date}",
"decl.filedAt": "Presentada el {date}",
// Admin console (phase 6). Plain and dense: this is a tool, not a product surface.
"common.loadMore": "Ver mas",
"common.none": "Sin datos",
"admin.nav.console": "Consola",
"admin.forbidden": "Esta seccion es solo para el equipo.",
"admin.users.searchLabel": "Buscar una cuenta",
"admin.users.searchHint": "Email, RUC, CI o nombre. Escribí al menos 2 caracteres.",
"admin.users.empty": "No encontramos ninguna cuenta con ese dato.",
"admin.users.start": "Buscá una cuenta para empezar.",
"admin.users.col.email": "Email",
"admin.users.col.name": "Nombre",
"admin.users.col.doc": "RUC o CI",
"admin.users.col.role": "Rol",
"admin.users.col.created": "Alta",
"admin.users.counts.documents": "Comprobantes",
"admin.users.counts.needsReview": "Para revisar",
"admin.users.counts.declarations": "Declaraciones",
"admin.users.lastActivity": "Ultima actividad: {date}",
"admin.users.noActivity": "Sin actividad todavia",
"admin.users.noProfile": "Esta cuenta todavia no completo el perfil.",
"admin.users.openErrors": "Errores abiertos de esta cuenta",
"admin.users.noErrors": "Sin errores abiertos.",
"admin.users.obligations": "Obligaciones",
"admin.role.title": "Rol",
"admin.role.change": "Cambiar rol",
"admin.role.confirm": "Vas a cambiar el rol de {email} a {role}. Queda registrado con tu nombre.",
"admin.role.saved": "Rol actualizado.",
"admin.role.user": "Usuario",
"admin.role.accountant": "Contador",
"admin.role.staff": "Staff",
"admin.role.superadmin": "Superadmin",
"admin.errors.empty": "No hay errores en la cola.",
"admin.errors.col.when": "Cuando",
"admin.errors.col.stage": "Etapa",
"admin.errors.col.user": "Cuenta",
"admin.errors.col.message": "Mensaje",
"admin.errors.col.status": "Estado",
"admin.errors.filterStage": "Etapa",
"admin.errors.filterStatus": "Estado",
"admin.errors.all": "Todas",
"admin.errors.allStatus": "Todos",
"admin.errors.stage.qr_parse": "QR",
"admin.errors.stage.ocr": "OCR",
"admin.errors.stage.dedupe": "Duplicados",
"admin.errors.stage.verify": "Verificacion",
"admin.errors.stage.job": "Job",
"admin.errors.stage.other": "Otro",
"admin.errors.status.open": "Abierto",
"admin.errors.status.resolved": "Resuelto",
"admin.errors.retry": "Reintentar el job",
"admin.errors.note": "Nota de resolucion",
"admin.errors.notePlaceholder": "Que paso y que hiciste",
"admin.errors.attempts": "{count} intentos",
"admin.errors.goToQueue": "Ver toda la cola de errores",
"admin.audit.empty": "Ningun movimiento con esos filtros.",
"admin.audit.col.when": "Cuando",
"admin.audit.col.actor": "Quien",
"admin.audit.col.action": "Accion",
"admin.audit.col.subject": "Sobre quien",
"admin.audit.col.resource": "Recurso",
"admin.audit.col.ip": "IP",
"admin.audit.filterActor": "Quien (email)",
"admin.audit.filterAction": "Accion",
"admin.audit.filterSubject": "Sobre quien (email)",
"admin.audit.filterFrom": "Desde",
"admin.audit.filterTo": "Hasta",
"admin.audit.apply": "Filtrar",
"admin.audit.clear": "Limpiar",
"admin.audit.total": "{count} movimientos",
// Chrome
"common.language": "Idioma",
"common.languageEs": "Español",