feat: a dashboard for clients, behind a secret link

Owners and admins turn it on in Configuración → General, pick what it
shows (tickets per status, the sprint under way, release forecasts,
open epics and activity per day) and can renew or drop the link. Whoever
has it sees /share/{token} without an account: only those blocks, no
people nor single tickets but the epics, no links into the app, read
again every minute. A renewed or dropped link says it no longer works.

REST (GET/PUT/DELETE /api/projects/{project}/share and the public
GET /api/shared/dashboards/{token}) and GraphQL (dashboardShare,
shareDashboard, unshareDashboard, sharedDashboard). The token is kept
in dashboard_shares and goes with the project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 10:39:26 +02:00
parent 4dc673997d
commit 8fe581ff3e
44 changed files with 3299 additions and 12 deletions

View File

@@ -339,6 +339,25 @@ Cada evento enlaza a la web (`WEB_URL`). El token (32 bytes aleatorios) se crea,
servicio la URL anterior. Se guarda tal cual en `calendar_feeds` para poder volver a copiar la URL: solo da lectura de
fechas y títulos que ya están en el read model.
### Panel para clientes
Cada proyecto puede enseñar su estado a sus clientes con un enlace de solo lectura, sin cuenta: `/share/{token}` en la
web, que lee `GET /api/shared/dashboards/{token}` (GraphQL: `sharedDashboard(token)`), ambos sin sesión. Solo trae los
bloques elegidos, y los demás llegan a `null`:
| Bloque | Qué da |
| ---------- | -------------------------------------------------------------------------- |
| `flow` | Tickets en cada estado activo, con su nombre y color |
| `sprint` | El sprint en curso, como en el panel (¿llega?) |
| `releases` | Las releases planificadas con su previsión al ritmo real |
| `epics` | Las épicas abiertas (8, por fecha): título, fecha, terminados y porcentaje |
| `activity` | Movimientos por día de las últimas 12 semanas |
No enseña personas ni tickets sueltos (salvo las épicas). Propietarios y admins lo activan, cambian sus bloques o lo
renuevan con `GET`, `PUT` y `DELETE /api/projects/{project}/share` (`{ blocks, renew }`; GraphQL: `dashboardShare`,
`shareDashboard`, `unshareDashboard`). Renovarlo deja sin servicio el enlace anterior. El token (32 bytes aleatorios)
se guarda tal cual en `dashboard_shares` para poder volver a copiar el enlace, y se borra con el proyecto.
### Notificaciones
Se derivan de los eventos (`NotificationsEventHandler`) y se guardan **por persona** en MongoDB: no son estado de
@@ -707,6 +726,7 @@ Proyectos y tickets se pueden indicar por **id o por clave** (`WEB`, `WEB-12`).
| Adjuntos | `POST /api/issues/{issue}/attachments` (multipart: `file` y opcional `commentId`), `GET/DELETE /api/issues/{issue}/attachments/{id}` |
| Releases | `GET /api/me/releases` (próximas), `GET/POST /api/projects/{project}/releases`, `GET/PATCH /api/releases/{id}`, `PUT …/notes`, `GET …/notes/draft`, `…/progress`, `…/burnup`, `…/issues`, `POST …/publish`, `POST …/archive` |
| Explorar | `GET /api/explore/projects` (proyectos públicos, sin token) |
| Compartir | `GET/PUT/DELETE /api/projects/{project}/share` (enlace para clientes), `GET /api/shared/dashboards/{token}` (lo que enseña, sin token) |
| Importar | `GET /api/projects/{project}/export.csv`, `POST /api/projects/{project}/import?dryRun=` (multipart `file`) |
| Webhooks | `GET /api/webhook-events`, `GET/POST /api/projects/{project}/webhooks`, `PATCH/DELETE /api/outgoing-webhooks/{id}`, `POST …/test` |
| Git | `GET /api/projects/{project}/git-integration`, `POST …/git-integration/repositories`, `POST …/repositories/{id}/secret`, `DELETE …/repositories/{id}`, `POST /api/webhooks/git/{project}` (lo llama el host de Git) |

View File

@@ -129,8 +129,9 @@ quieta con la misma caja, así que nada se mueve al llegar. Lo demás es de su p
- Las de una página que se vuelve a visitar a menudo (portada, Explorar, el formulario de propuestas) van en los
`providers` de su ruta (`provideHomeFacade()`, `provideMyHomeFacade()`, `provideMyViewsFacade()`,
`provideCalendarFacade()`, `provideExploreFacade()`, `provideProposeFacade()`): cargan con la página y el inyector de
`provideCalendarFacade()`, `provideExploreFacade()`, `provideProposeFacade()`, `provideSharedDashboardFacade()`): cargan con la página y el inyector de
la ruta las conserva, así que al volver se ve lo último al instante mientras se recarga.
- `SharingFacade` (el enlace para clientes en Configuración) va en los `providers` de su propio componente.
- Las de un proyecto o un ticket se registran con `provideProjectFacade()`, `provideBoardFacade()` o
`provideIssueFacade()` en los `providers` del componente de la página (store + facade) y desaparecen al salir (con sus
subscriptions). Las páginas hijas de `/p/:key` reciben la misma `ProjectFacade`.
@@ -310,9 +311,10 @@ visualización de la plataforma:
| `/p/:key/roadmap` | Hoja de ruta: épicas en el tiempo, con sprints y releases; flechas «bloquea» / «depende de» entre épicas y aviso si una empieza antes de que acabe la que la bloquea |
| `/p/:key/dashboard` | Seguimiento: cifras, flujo por estado, lo que ha cambiado desde tu última visita (guardada en este navegador), sprint en curso, tickets en riesgo, previsión de las próximas releases, antigüedad de lo que está en curso, cuellos de botella, salud del backlog (cada número abre la lista filtrada: `?missing=estimate`, `area`, `assignee` o `?staleDays=90`), calidad (bugs por prioridad y por semana, QA por área, bugs tras cada release), abiertos por un campo de lista a elegir, el recorrido de un ticket (dónde se va el tiempo de creado a hecho y qué parte es trabajo), la actividad de 12 semanas en un mapa de calor, el embudo de propuestas del público (si las hay), equipo con sus puntos abiertos y tipos. Se recarga solo con cada cambio. «Modo pantalla» lo pone a pantalla completa para una tele del equipo: un bloque cada vez, ampliado, que cambia cada 15 s (← → para moverse, espacio para pausar, Esc para salir), con la hora y en directo |
| `/p/:key/analytics` | Estadísticas: creados vs. resueltos, flujo acumulado, burnup de una release, tiempos de ciclo y entrega, QA y resueltos por persona (30 días a 1 año) |
| `/p/:key/settings` | Configuración (propietario y admins; pestaña oculta para el resto), con un menú de secciones (`?section=`): General (visibilidad, propuestas), Flujo de trabajo (estados, transiciones, definición de «Hecho»), Tickets (tipos, áreas, campos), Equipos, Automatizaciones, Integraciones (Git, webhooks), Importar y exportar y Zona peligrosa; archivar en lugar de borrar, y un punto en el menú donde hay cambios sin guardar |
| `/p/:key/settings` | Configuración (propietario y admins; pestaña oculta para el resto), con un menú de secciones (`?section=`): General (visibilidad, propuestas, panel para clientes), Flujo de trabajo (estados, transiciones, definición de «Hecho»), Tickets (tipos, áreas, campos), Equipos, Automatizaciones, Integraciones (Git, webhooks), Importar y exportar y Zona peligrosa; archivar en lugar de borrar, y un punto en el menú donde hay cambios sin guardar |
| `/p/:key/activity` | Actividad en vivo |
| `/p/:key/propose` | Formulario público para reportar un error o proponer una idea sin cuenta (si el proyecto lo tiene abierto); llegan como tickets con la etiqueta «propuesta» |
| `/share/:token` | Panel para clientes, sin sesión: los bloques que eligió el proyecto (flujo, sprint, releases, épicas, actividad), sin enlaces hacia dentro de la aplicación; se vuelve a leer cada minuto. Si el enlace se renovó o retiró, «Este enlace ya no funciona» |
| `/p/:key/members` | Miembros, con su rol (propietario, admin, miembro, lector o invitado). Un invitado de un proyecto privado solo tiene Tablero, Lista y Miembros, ve solo sus tickets y abre tickets sin asignarlos ni planificarlos |
Solo `/` está protegida por `authGuard` (redirige a `/login?returnUrl=…`); `/login` y `/auth/callback` son para quien

View File

@@ -14,7 +14,7 @@ cuando hay pistas, por dónde empezar.
| Limpiar los datos de prueba de producción | DEMO, TEST y los tickets de las pruebas del MCP | Borrar los proyectos desde Configuración (borrado definitivo con su clave) |
| Registrar el webhook del cliente de contraseña en Pireon | Sin él, revocar un token de quien entra con contraseña no se nota hasta que caduca la caché | Solo si `PIREON_PASSWORD_CLIENT_ID` tiene valor: webhook en ese cliente y su secret en `PIREON_PASSWORD_WEBHOOK_SECRET` (ver [Autenticación](05-autenticacion.md)) |
| Una versión 1.0 | Un punto fijo al que volver | Desplegar todo, etiquetar `v1.0.0` y redactar la lista de cambios (las notas de versión de TaskStream dan el borrador) |
| Revisión de seguridad | Hay rutas sin sesión (proyectos públicos, escaparate, propuestas) y un MCP abierto a Internet | Permisos por rol, qué ve un anónimo, límites de peticiones (solo las propuestas tienen), CORS, cabeceras |
| Revisión de seguridad | Hay rutas sin sesión (proyectos públicos, escaparate, propuestas, panel para clientes) y un MCP abierto a Internet | Permisos por rol, qué ve un anónimo, límites de peticiones (solo las propuestas tienen), CORS, cabeceras |
| Registro de errores en producción | Hoy un fallo solo se conoce si alguien avisa | Sentry (o similar) en la API, la web y el MCP |
## 10.2 Fallos y deuda conocidos
@@ -130,3 +130,5 @@ Mucho trabajo para lo que aportan hoy; se pueden recuperar si alguien los necesi
con las más votadas sin respuesta.
- Modo pantalla del panel: a pantalla completa para una tele del equipo, un bloque cada vez que cambia solo, en
directo y con la hora.
- Panel para clientes: un enlace de solo lectura, sin cuenta, con los bloques que elijan el propietario y los admins
(flujo, sprint, releases, épicas, actividad); se renueva o se retira desde Configuración.

Binary file not shown.

Binary file not shown.