22 Commits

Author SHA1 Message Date
d66b1f3ece feat: a day of the activity map opens what happened, ticket by ticket
On the project dashboard, a day with activity can be picked on the map:
a side panel lists that day's moves grouped by ticket (each key opens
it), with the time of each, and the project's own (sprints, releases,
members) apart.

The activity feed takes an `after` date (GraphQL, REST), so with
`before` it reads a window such as a day; the MCP's
get_project_activity takes a `day`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:42:30 +02:00
8fe581ff3e 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>
2026-10-16 10:39:26 +02:00
1101b6e926 feat: dashboard maps the activity, follows a ticket's way and the proposals
Three more blocks on the project dashboard, also by REST, GraphQL and
MCP:
- Activity: what moved each day of the last 12 weeks, in a heatmap,
  with the busiest weekday and the longest lull.
- A ticket's way: for the tickets done in the last 90 days, where the
  time from created to done went, status by status (waiting included),
  and how much of it was work (flow efficiency).
- The public's proposals: received, waiting, under way, done and
  discarded, and the most voted still unanswered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 09:29:38 +02:00
0946e9517d feat: a home page with life
Greeting and summary of the day, the coming week, the latest across my
projects, who is online now, the week in numbers (mine and the team's,
with a streak), sprints under way, «Retomar», project cards with their
activity, what to pick up when nothing is mine and first steps for
newcomers. REST (GET /api/me/home), GraphQL (myHome) and MCP
(get_my_week).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:20:41 +02:00
affce615a7 feat: portfolio of all my projects
/cartera (and GraphQL portfolio, REST /api/me/portfolio, MCP
get_portfolio) shows every active project I belong to (not archived, not
as a guest) at a glance: open, overdue, blocked and stuck tickets, the
sprint under way and whether it gets there, planned releases with their
forecast, and the open epics with their progress. It reuses the
dashboard's computations. A release with no tickets now reads «Sin
tickets» instead of «Todo hecho», here and on the dashboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:27:46 +02:00
8bf7d47b2e feat: guest role for clients and outsiders
A project member can be a GUEST. In a private project a guest only sees
the tickets they take part in (opened, assigned or followed): every other
ticket answers 404, also in my work, realtime changes, blockers and
linked tickets, and project-wide views (activity, statistics, dashboard,
releases, sprints, epics, trash) answer 403. Guests open tickets without
assigning or planning them, and comment, attach, follow and vote on their
own; they change nothing else. In a public project they see what anyone
does. The web gives them only Tablero, Lista and Miembros and loads none
of the project-wide data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:02:36 +02:00
f60d4c8346 feat: presence on tickets
A ticket's page beats every 10 s (touchPresence / PUT
/issues/{issue}/presence, with whether I am editing) and gets back who
else is there, editing ones first. Entries live in Redis, one hash per
ticket, and fade after 30 s without a beat; leaving the page takes me out
at once. Only for people who can read the ticket; never in the event
store. The ticket shows «Bob está editando · Ana también lo está viendo»
above its title; editing means a ticket or comment form is open or a
comment is being written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:53:47 +02:00
869acd3f1f feat: teams within a project
Projects keep teams of members in their settings (settings.teams; people
outside the project are dropped, teams are archived, not removed; absent
when saving means unchanged). Tickets go to a team (`team`, also filtered
in the list and in MCP search_issues), and a comment mentioning
@[Frontend](team-FRONT) tells each of its members. Settings has the teams
with their members, the ticket a «Equipo» select, the list a team filter,
and «@» offers the teams after the people.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:20:35 +02:00
4acf629106 feat: archive a project instead of deleting it
The owner archives a finished project (archiveProject, POST
/projects/{project}/archive) and brings it back (unarchive). While
archived it stays readable, managers still read its rules and webhooks,
but no command changes it: contributing and managing checks refuse with
422, votes and public proposals too, and automations do not run. The web
shows a read-only notice on every page of it (with «Desarchivar» for the
owner), hides what edits, marks it on the home page and lists it last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:46:22 +02:00
e598f76e1c feat: definition of done per project
A project can ask that tickets reach a DONE status only with their
checklist ticked (settings.checklistBeforeDone, off by default; absent
when saving means unchanged). Members get a 422 telling how many steps are
left; owners and admins may still close it. Settings has the switch and
the ticket's checklist says when the project asks for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:39:24 +02:00
5b0396bc10 feat: saved views of my work, across my projects
A person saves named views of «my work»: projects, assigned to me, that I
follow, status categories, priorities, bugs only, overdue and text. They
are private (Mongo my_views, no history), listed and deleted by their
owner (GraphQL myViews / saveMyView / deleteMyView, REST /api/me/views),
and run as the cross-project search, which gains project, priority,
bugsOnly and overdue filters (also in MCP my_issues). The home page shows
them as a bar above «my work», with a drawer to save a new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:22:20 +02:00
76312c079d feat: dashboard weighs quality and splits work by a list field
The insights reading adds quality (open bugs per priority, bugs created
and closed per week for 8 weeks, QA returns and passes per area over 90
days, bugs reported in the 14 days after each of the last three
releases) and, for each list custom field, open tickets per option. The
dashboard shows both; the field to split by can be picked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 13:06:11 +02:00
0d158a2ed7 feat: dashboard forecasts releases, tells what changed and weighs the load
The insights reading grows (GraphQL insights(since:), REST ?since=, MCP):
- planned releases with a likely (p50) and safe (p85) finish date from a
  Monte Carlo over the last six weeks of finished work, one after another,
  and a verdict against their target;
- with `since` (the viewer's last visit, kept in the browser): tickets
  created, finished and sent back from QA, and their mentions;
- each person's open points against what they closed per sprint lately;
- open tickets without estimate, area or assignee, or untouched for 90
  days, each count opening the list filtered (new noEstimate, noArea and
  updatedBefore filters).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:50:24 +02:00
fe03c5bda8 feat: dashboard reads the sprint, the risks, aging and bottlenecks
A new projectInsights reading (GraphQL Project.insights, REST
/projects/{project}/insights, MCP get_project_insights), computed by pure
functions over the read model and the timeline:
- the sprint under way: days left, points done, pace against what is
  needed and whether it gets there;
- tickets at risk: overdue, due within a week, blocked by an open ticket
  and stuck (7 days or more in an in-progress or QA status);
- aging of the work under way against the p85 cycle time;
- average days in each in-progress and QA status, with WIP limits.
The dashboard shows them in four new panels, reloaded on every change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:55:52 +02:00
e5f4e6c683 feat: start a project from another one's configuration
Creating a project can name one the creator owns or administers: its
statuses, transitions, types, areas, custom fields and automation rules
are copied once (paused rules stay paused; rules assigning to someone who
is not in the new project are left out). GraphQL templateProjectId, REST
template (id or key), MCP create_project template, and a select in the
web's new-project form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 10:25:00 +02:00
f7e850068b feat: custom fields per project
Text, number, date or option-list fields defined in the project settings
(archived, never removed; their type is fixed once saved). Tickets hold
them, edited in the side panel, listed under the title and filtered by
option in the list view; also over GraphQL, REST (?field=ID:value) and MCP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:31:12 +02:00
71dc2d6f2d fix(api): typed OpenAPI query params and not-found messages that hide private projects
- Query DTO properties with a default value (limit, offset, days, perColumn,
  assignedToMe, search, archived, requiresReason, force) were documented as
  `object` in openapi.json, which broke generated clients. They now declare
  their type explicitly.
- Reading a private project, ticket, release or its comments without access
  answered "Project <internal id> not found", confirming the project exists.
  ReadAccess now reports what the caller asked for (e2e covered).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 18:59:38 +02:00
e656222b54 feat: public projects, project deletion and sagas
Public projects:
- Project.isPublic (create/update, owners and admins). Anyone can read a
  public project without a token: project, board, tickets, comments,
  history, releases, activity, tracking and statistics, over GraphQL and
  REST. Writes stay for members (401 anonymous, 403 outsiders).
- @OptionalAuth() / @ApiOptionalPireonAuth(): anonymous callers allowed,
  invalid tokens still rejected. ReadAccess lets anonymous viewers read
  public projects only; linked tickets are filtered by visibility.
- publicProjects (GraphQL) and GET /api/explore/projects (REST).
- Web: «Explorar» page, project pages open without a session (read-only
  notice, sign-in hints), public badge, visibility switch in settings,
  no realtime subscriptions without a session.

Project deletion:
- Owner only, confirming the key (deleteProject / DELETE
  /api/projects/{project}?confirm=KEY). The key is freed; the read model
  drops the project and members are notified (web goes home).

Sagas (run by the EventsBridge after projections, durable and ordered):
- ProjectDeletionSaga: project deleted → delete its tickets and releases;
  ticket deleted → purge its comments and drop links from other tickets.
- ReleaseArchivedSaga: unfinished tickets leave an archived release.
- UserDeactivatedSaga: the Pireon webhook records UserDeactivatedEvent in
  the event store (EventRecorder); the tracker unassigns that user's open
  tickets as the system actor.
- Internal idempotent commands; domain refusals are logged and skipped,
  concurrency conflicts retried. Affected streams come from the event store
  (TrackerStreamsRepository).

Tests: domain lifecycle specs, saga specs run like the bridge, facade
tests, e2e for public access, cascade deletion, archived releases and
deactivation webhook. Docs, READMEs, schema.gql/openapi.json and Word/PDF
updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 07:04:20 +02:00
0e301ad545 feat: per-project workflow settings, statistics and "my work" home
Per-project settings (owners/admins, «Configuración» tab):
- Statuses with category (TODO, IN_PROGRESS, QA, DONE, CANCELLED), colour and
  "requires reason"; configurable transitions; ticket types (icon, colour) and
  areas. Defaults are the previous fixed values; projects created before keep
  them without migration.
- Values are archived, never deleted: tickets, history and statistics keep
  referring to them. Validated in the domain (ProjectSettingsChangedEvent).
- Releases, board, tracking and "my work" use categories, so custom statuses
  keep working. Status changes carry name/category metadata for history.
- GraphQL: status/type/area are strings (breaking for clients sending enum
  literals); Project.settings, updateProjectSettings, statusCategory,
  myIssues(categories). REST: PUT /projects/{project}/settings, category filters.
- Read model: statusCategory on issues (backfilled at startup).

Statistics («Estadísticas» tab):
- New issue_timeline projection; pure functions compute created vs resolved,
  cumulative flow, cycle/lead time (avg + p85), throughput per person, QA
  pass/fail and release burnup. GraphQL Project.analytics(days),
  Release.burnup; REST /projects/{project}/analytics, /releases/{id}/burnup.
- Web: SVG charts with validated light/dark palette, crosshair tooltip,
  keyboard navigation, legend and data table.

Home "Mi trabajo": my open tickets across projects grouped by category (QA
failures first), tickets waiting for QA, upcoming releases and "Ir a…".

Tests: domain/application specs, e2e scenario for settings and analytics,
facade tests. Docs, READMEs, schema.gql/openapi.json and Word/PDF updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 20:53:45 +02:00
341241a52f feat(api): REST API with OpenAPI for other apps, behind Pireon OAuth
A REST adapter next to GraphQL (presenters/http) that calls the same
application services, so validation, permissions and domain errors are the
same: projects (members, board, stats, activity), issues (search, workflow,
assignee, release, links, comments, history), comments, releases and users.
Projects and issues can be addressed by id or key (WEB, WEB-12).

- Swagger UI at /api/docs, document at /api/docs-json and apps/api/openapi.json
  (rewritten by `nx serve api`, like schema.gql) for client generation.
- Every route but health/auth/webhook needs a Pireon token: the document
  declares Pireon OAuth2 (authorization code + PKCE, password) through the
  /api/auth/token proxy, and a bearer scheme for apps with their own client.
- configureApp() shares main.ts setup with the e2e, which now also covers REST.
- SWAGGER_ENABLED / OPENAPI_FILE config; docs 03/05 and library READMEs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 15:26:47 +02:00
bf0294a188 fix(docker): read the Nexus token from NEXUS_TOKEN instead of ~/.npmrc
Dokploy builds failed with "failed to stat /root/.npmrc": the compose build
secret pointed at ${HOME}/.npmrc, which only exists on a developer machine.
The token now comes from the NEXUS_TOKEN environment variable (Dokploy's
Environment tab or .env), is written to a temporary npmrc and removed in the
same RUN, so it never lands in an image layer.

Also removes a temporary UI dev-server spec that was committed by mistake,
and documents the variable (.env.example, docs 06/07, Word/PDF export).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 14:11:01 +02:00
566f415fa7 first commit 2026-10-03 13:40:26 +02:00