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>
This commit is contained in:
2026-10-15 18:27:46 +02:00
parent 8bf7d47b2e
commit affce615a7
35 changed files with 1603 additions and 80 deletions

View File

@@ -397,6 +397,26 @@ export interface paths {
patch: operations["ProjectsController_changeMemberRole"];
trace?: never;
};
"/api/me/portfolio": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* My projects at a glance
* @description Every active project I am a member of (not archived, not as a guest): open and at-risk tickets, the sprint under way, release forecasts and open epics.
*/
get: operations["IssuesController_portfolio"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/issues/{issue}/presence": {
parameters: {
query?: never;
@@ -2440,6 +2460,36 @@ export interface components {
ChangeMemberRoleDto: {
role: components["schemas"]["ProjectRole"];
};
ChildProgressDto: {
total: number;
/** @description Done or cancelled */
finished: number;
percent: number;
storyPoints: number;
finishedStoryPoints: number;
};
EpicDto: {
issue: components["schemas"]["IssueDto"];
progress: components["schemas"]["ChildProgressDto"];
};
PortfolioProjectDto: {
/** Format: uuid */
projectId: string;
/** @example WEB */
key: string;
name: string;
isPublic: boolean;
total: number;
open: number;
overdue: number;
blocked: number;
stuck: number;
sprint: components["schemas"]["SprintPulseDto"] | null;
releases: components["schemas"]["ReleaseForecastDto"][];
/** @description Open epics, nearest due date first (six at most) */
epics: components["schemas"]["EpicDto"][];
updatedAt: string;
};
PresenceBodyDto: {
/**
* @description Editing the ticket or writing a comment
@@ -2514,18 +2564,6 @@ export interface components {
*/
parent?: string | null;
};
ChildProgressDto: {
total: number;
/** @description Done or cancelled */
finished: number;
percent: number;
storyPoints: number;
finishedStoryPoints: number;
};
EpicDto: {
issue: components["schemas"]["IssueDto"];
progress: components["schemas"]["ChildProgressDto"];
};
UpdateIssueDto: {
description?: string | null;
/**
@@ -4778,6 +4816,61 @@ export interface operations {
};
};
};
IssuesController_portfolio: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PortfolioProjectDto"][];
};
};
/** @description Invalid input */
400: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Missing or rejected Pireon token */
401: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Not a member of the project (or not allowed) */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
};
};
IssuesController_touchPresence: {
parameters: {
query?: never;