Empresa (Company)
├── Portfolio "Safra 2024/2025"
│ ├── Carteira "Analista A — Região Sul"
│ │ ├── Contrato CPR-001
│ │ └── Contrato CPR-002
│ └── Carteira "Analista B — Região Norte"
│ └── Contrato CPR-003
├── Portfolio "Duplicatas — SP"
│ └── Carteira "Padrão"
│ ├── Contrato CT-001
│ └── Contrato CT-002
└── Portfolio "CCBs — Crédito Rural"
└── Carteira "Padrão"
└── Contrato CCB-001portfolio:read, contract:read, etc. Define o que o usuário pode fazer.portfolio:assign_user.| Nível | Entidade | Efeito |
|---|---|---|
| Portfolio | PortfolioUser | Usuário enxerga todos os contratos do portfolio, em qualquer carteira filha. |
| Carteira | SubPortfolioUser | Usuário enxerga apenas os contratos da(s) carteira(s) à(s) qual(is) está atribuído (escopo restrito dentro do portfolio). |
Recurso visível ⇔ (usuário tem permissão RBAC sobre o resource) AND (usuário está atribuído ao portfolio do recurso OU a alguma carteira que contenha o recurso).
UNIT_RESTRICTED. O escopo de unit usado em outros módulos da plataforma (separação matriz/filial) não se aplica ao módulo de Portfolio Management. A visibilidade aqui é independente da estrutura organizacional de units.| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| id | UUID | Sim | Identificador único do portfolio. |
| companyId | UUID | Sim | Empresa à qual o portfolio pertence. |
| name | String | Sim | Nome do portfolio (ex: "Safra 2024/2025", "Duplicatas — Filial SP"). Único por empresa. |
| description | String | Não | Descrição do propósito ou critério de agrupamento do portfolio. |
| status | Enum | Sim | Estado: ACTIVE, INACTIVE. |
| createdBy | Object | Sim | Usuário que criou o portfolio, hidratado: { id, name }. No armazenamento permanece como UUID flat. |
| createdAt | DateTime | Sim | Data de criação. |
| updatedAt | DateTime | Sim | Data da última atualização. |
startDate / endDate). Quando omitido, o sistema assume todas as informações do portfolio.| Campo | Tipo | Descrição |
|---|---|---|
| totalReceivable | Decimal | Soma do valor em aberto de todos os pagamentos (parcelas) com status PENDING ou PARTIALLY_PAID no período. |
| totalReceivableDelta | Decimal | Variação percentual em relação ao período anterior. |
| totalOverdue | Decimal | Soma do valor em aberto de todos os pagamentos (parcelas) com status OVERDUE. |
| totalOverdueDelta | Decimal | Variação percentual em relação ao período anterior. |
| defaulters | Integer | Número de devedores únicos com ao menos um pagamento (parcela) OVERDUE. |
| defaultersDelta | Integer | Variação absoluta. |
| activeContracts | Integer | Contratos com status = ACTIVE. |
| activeContractsDelta | Integer | Variação absoluta. |
| overdueContracts | Integer | Contratos com ao menos um pagamento (parcela) OVERDUE. |
| overdueContractsDelta | Integer | Variação absoluta. |
| overduePayments | Integer | Total de pagamentos (parcelas) com status OVERDUE. |
| overduePaymentsDelta | Integer | Variação absoluta. |
| Campo | Tipo | Descrição |
|---|---|---|
| recoveryRate | Decimal | Percentual do valor vencido efetivamente recebido no período. |
| recoveryRateDelta | Decimal | Variação em pontos percentuais. |
| averageReceivingDays | Integer | Tempo médio em dias entre vencimento e liquidação. |
| averageReceivingDaysDelta | Integer | Variação absoluta em dias. |
subPortfolioId). O nome técnico da entidade no código permanece SubPortfolio (refletindo a hierarquia: portfolio é a carteira-pai); o rótulo PT-BR exibido na UI e na documentação de negócio é "Carteira".| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| id | UUID | Sim | Identificador único da carteira. |
| companyId | UUID | Sim | Empresa à qual a carteira pertence. |
| portfolioId | UUID | Sim | Portfolio ao qual a carteira pertence. |
| name | String | Sim | Nome da carteira. Único dentro do portfolio. |
| description | String | Não | Descrição opcional do critério de agrupamento. |
| user | Object | Não | Usuário responsável (gestor) pela carteira, hidratado: { id, name }. No armazenamento permanece como UUID flat. |
| status | Enum | Sim | Estado: ACTIVE, INACTIVE. |
| createdBy | Object | Sim | Usuário que criou a carteira, hidratado: { id, name }. No armazenamento permanece como UUID flat. |
| createdAt | DateTime (ISO 8601 UTC) | Sim | Data de criação. |
| updatedAt | DateTime (ISO 8601 UTC) | Sim | Data da última atualização. |
PortfolioSummary (seção 2.2), mas no escopo de uma única carteira. Acessado via GET /v2/companies/:companyId/portfolios/:portfolioId/sub-portfolios/:subPortfolioId no campo summary da resposta.PortfolioSummary é a agregação dos SubPortfolioSummary das carteiras filhas do portfolio — todos os indicadores numéricos do portfolio são a soma dos respectivos indicadores das suas carteiras.subPortfolioId obrigatório na entidade Contract (ver Contratos.md). Não existe entidade de vínculo separada. O contrato é criado já indicando a carteira de destino, e o campo pode ser alterado posteriormente para mover o contrato entre carteiras do mesmo portfolio via PATCH .../contracts/:contractId.subPortfolioId = null em um contrato — todo contrato precisa estar atribuído a exatamente uma carteira do seu portfolio.| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| id | UUID | Sim | Identificador único do vínculo. |
| portfolioId | UUID | Sim | Referência ao portfolio. |
| user | Object | Sim | Usuário tenant vinculado, hidratado: { id, name }. No armazenamento permanece como UUID flat. |
| assignedBy | Object | Sim | Usuário que realizou a atribuição, hidratado: { id, name }. No armazenamento permanece como UUID flat. |
| assignedAt | DateTime | Sim | Data e hora da atribuição. |
| createdAt | DateTime | Sim | Data de criação do registro. |
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| id | UUID | Sim | Identificador único do vínculo. |
| subPortfolioId | UUID | Sim | Referência à carteira. |
| user | Object | Sim | Usuário tenant vinculado, hidratado: { id, name }. No armazenamento permanece como UUID flat. |
| assignedBy | Object | Sim | Usuário que realizou a atribuição, hidratado: { id, name }. No armazenamento permanece como UUID flat. |
| assignedAt | DateTime (ISO 8601 UTC) | Sim | Data e hora da atribuição. |
| createdAt | DateTime (ISO 8601 UTC) | Sim | Data de criação do registro. |
name deve ser único dentro da empresa. Erro: PORTFOLIO_NAME_ALREADY_EXISTS.DRAFT, ACTIVE, OVERDUE, DEFAULTED). Todos os contratos devem estar em estados terminais (CLOSED, CANCELLED ou RENEGOTIATED) antes da desativação. Erro: PORTFOLIO_HAS_ACTIVE_CONTRACTS.INACTIVE, todos os seus portfolios são automaticamente desativados. Todas as carteiras e vínculos de usuários (tanto PortfolioUser quanto SubPortfolioUser) são removidos em cascata.status = INACTIVE.portfolioId. Não é possível criar carteiras em portfolios com status = INACTIVE. Erro: PORTFOLIO_INACTIVE.PORTFOLIO_HAS_NO_SUB_PORTFOLIO.name deve ser único dentro do portfolio (não da empresa). Carteiras de portfolios diferentes podem ter o mesmo nome. Erro: SUB_PORTFOLIO_NAME_ALREADY_EXISTS.DRAFT, ACTIVE, OVERDUE, DEFAULTED). O usuário deve mover ou encerrar todos os contratos ativos antes da desativação. Erro: SUB_PORTFOLIO_HAS_CONTRACTS.SubPortfolioUser são removidos automaticamente. Os contratos permanecem no portfolio.subPortfolioId obrigatório na entidade Contract (não há entidade de vínculo separada).subPortfolioId preenchido — não é permitido null. O contrato é criado já indicando a carteira de destino. Erro: SUB_PORTFOLIO_REQUIRED.subPortfolioId é um campo único, não uma lista). Mover entre carteiras é feito sobrescrevendo o subPortfolioId com o ID da nova carteira via PATCH .../contracts/:contractId.subPortfolioId deve pertencer ao mesmo portfolio do contrato (portfolioId). Erro: CONTRACT_PORTFOLIO_MISMATCH.PATCH .../contracts/:contractId informando o novo subPortfolioId. Permissão requerida: portfolio:assign_contract.subPortfolioId. O contrato permanece atribuído à carteira até ser explicitamente movido.USER_COMPANY_MISMATCH.USER_COMPANY_MISMATCH.{
"error": "CODIGO_DO_ERRO",
"message": "Descrição legível do problema.",
"details": {}
}| Status | Código | Descrição |
|---|---|---|
| 401 Unauthorized | UNAUTHORIZED | Token JWT ausente, expirado ou inválido. |
| 403 Forbidden | FORBIDDEN | Permissão insuficiente. |
| 404 Not Found | PORTFOLIO_NOT_FOUND | Portfolio não encontrado ou não pertence à empresa. |
| 404 Not Found | SUB_PORTFOLIO_NOT_FOUND | Carteira não encontrada. |
| 404 Not Found | CONTRACT_NOT_FOUND | Contrato não encontrado. |
| 404 Not Found | USER_NOT_FOUND | Usuário não encontrado. |
| 500 Internal Server Error | INTERNAL_SERVER_ERROR | Erro inesperado. |
| Status | Código | Descrição |
|---|---|---|
| 409 Conflict | PORTFOLIO_NAME_ALREADY_EXISTS | Nome de portfolio duplicado na empresa. |
| 422 Unprocessable Entity | PORTFOLIO_INACTIVE | Portfolio inativo — operação não permitida. |
| 422 Unprocessable Entity | PORTFOLIO_HAS_ACTIVE_CONTRACTS | Portfolio possui contratos ativos e não pode ser desativado. |
| 422 Unprocessable Entity | PORTFOLIO_HAS_NO_SUB_PORTFOLIO | Tentativa de criar contrato em portfolio sem carteira ativa. Criar uma carteira primeiro. |
| 409 Conflict | SUB_PORTFOLIO_NAME_ALREADY_EXISTS | Nome de carteira duplicado no portfolio. |
| 422 Unprocessable Entity | SUB_PORTFOLIO_HAS_CONTRACTS | Carteira possui contratos em estados não-terminais e não pode ser desativada. |
| 422 Unprocessable Entity | SUB_PORTFOLIO_INACTIVE | Carteira inativa. |
| 422 Unprocessable Entity | SUB_PORTFOLIO_REQUIRED | Contrato sendo criado sem subPortfolioId. O campo é obrigatório. |
| 422 Unprocessable Entity | CONTRACT_PORTFOLIO_MISMATCH | Carteira informada não pertence ao portfolio do contrato. |
| 422 Unprocessable Entity | USER_COMPANY_MISMATCH | Usuário não pertence à empresa. |
| 422 Unprocessable Entity | INTERNAL_USER_NOT_ALLOWED | Usuários internos não podem ser vinculados. |
| 409 Conflict | USER_ALREADY_ASSIGNED | Usuário já vinculado ao portfolio ou à carteira. |
| 404 Not Found | USER_ASSIGNMENT_NOT_FOUND | Vínculo usuário-portfolio ou usuário-carteira não encontrado. |
| Parâmetro | Tipo | Default | Descrição |
|---|---|---|---|
offset | Integer | 0 | Quantidade de registros a pular antes de começar a retornar. |
limit | Integer | 20 | Quantidade máxima de registros por página. Máximo: 100. |
{
"items": [ ... ],
"nextPage": true
}items: array com os registros da página atual.nextPage: indica se existe uma próxima página. true quando há mais registros após os retornados; false quando a página atual é a última.offset e limit enviados na request não são retornados no response (o cliente já os conhece). Não há campo total no response da v1 — listagens não dependem de "página X de Y"./v2/companies/:companyId/portfoliosportfolio:create{
"name": "Safra 2024/2025",
"description": "Contratos de crédito rural vinculados à safra 24/25."
}No request, apenas user.idé necessário (e aceito).
{
"id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
"companyId": "a1b2c3d4-0000-0000-0000-000000000001",
"name": "Safra 2024/2025",
"description": "Contratos de crédito rural vinculados à safra 24/25.",
"status": "ACTIVE",
"createdBy": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"name": "João Silva"
},
"createdAt": "2025-03-17T09:00:00.000+00:00",
"updatedAt": "2025-03-17T09:00:00.000+00:00"
}PORTFOLIO_NAME_ALREADY_EXISTS, USER_COMPANY_MISMATCH (quando user.id informado é inválido).portfolio:read| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
status | Enum | Não | Filtrar por ACTIVE ou INACTIVE. |
orderBy | Enum | Não | Ordenação: name, createdAt. Default: name. |
order | Enum | Não | asc ou desc. Default: asc. |
offset | Integer | Não | Default: 0. |
limit | Integer | Não | Default: 20. Máximo: 100. |
{
"items": [
{
"id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
"companyId": "a1b2c3d4-0000-0000-0000-000000000001",
"name": "Safra 2024/2025",
"description": "Contratos de crédito rural vinculados à safra 24/25.",
"status": "ACTIVE",
"summary": {
"subPortfolios": 45,
"value": 12500000.00
},
"createdBy": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"name": "João Silva"
},
"createdAt": "2025-03-17T09:00:00.000+00:00",
"updatedAt": "2025-03-17T09:00:00.000+00:00"
}
],
"summary": {
"activeContracts": 45,
"value": 12500000.00
},
"nextPage": false
}summary.activeContracts e summary.value como campos resumidos para contexto — sem o cálculo completo do PortfolioSummary (que vai no detalhe).portfolio:read| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| startDate | Datetime (UTC) | Não | Início do período para summary. Default: primeiro dia do ano corrente. |
| endDate | Datetime (UTC) | Não | Fim do período. Default: último dia do ano corrente. |
{
"id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
"companyId": "a1b2c3d4-0000-0000-0000-000000000001",
"name": "Safra 2024/2025",
"description": "Contratos de crédito rural vinculados à safra 24/25.",
"status": "ACTIVE",
"createdBy": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"name": "João Silva"
},
"createdAt": "2025-03-17T13:32:32.209+00:00",
"updatedAt": "2025-03-17T13:32:32.209+00:00",
"summary": {
/** "period": {
"startDate": "2025-01-01T13:32:32.209+00:00",
"endDate": "2025-12-31T13:32:32.209+00:00"
},**/
"Receivable": 2847350.00,
"ReceivableDelta": 5.2,
"Overdue": 892150.00,
"OverdueDelta": 2.1,
"defaulters": 127,
"defaultersDelta": -3,
"activeContracts": 45,
"activeContractsDelta": 12,
"overdueContracts": 8,
"overdueContractsDelta": 2,
"overduePayments": 23,
"overduePaymentsDelta": 3,
"recoveryRate": 68.5,
"recoveryRateDelta": 2.3,
"averageReceivingDays": 18,
"averageReceivingDaysDelta": -3
}
}summary é a agregação dos SubPortfolioSummary das carteiras filhas. O portfolio não devolve a lista de contratos — a UI obtém essa informação via GET .../sub-portfolios/:subPortfolioId/contracts ou GET .../portfolios/:portfolioId/contracts?subPortfolioId=....PORTFOLIO_NOT_FOUND, INVALID_DATE_RANGE.name, descriptionportfolio:update{
"name": "Safra 2024/2025 — Soja e Milho",
"description": "Contratos de crédito rural para soja e milho, safra 24/25."
}PORTFOLIO_NAME_ALREADY_EXISTS, PORTFOLIO_INACTIVE, USER_COMPANY_MISMATCH (quando user.id é informado e o usuário não pertence à empresa).portfolio:delete{
"status": "INACTIVE"
}{
"id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
"status": "INACTIVE",
"updatedAt": "2025-03-17T11:00:00.000+00:00"
}PORTFOLIO_HAS_ACTIVE_CONTRACTS.portfolio:create{
"name": "Analista João — Região Sul",
"description": "Contratos da região Sul sob responsabilidade do analista João.",
"user": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851"
}
}{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"companyId": "a1b2c3d4-0000-0000-0000-000000000001",
"portfolioId": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
"name": "Analista João — Região Sul",
"description": "Contratos da região Sul sob responsabilidade do analista João.",
"user": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"name": "João Silva"
},
"status": "ACTIVE",
"createdBy": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"name": "João Silva"
},
"createdAt": "2025-03-17T13:32:32.209+00:00",
"updatedAt": "2025-03-17T13:32:32.209+00:00"
}SUB_PORTFOLIO_NAME_ALREADY_EXISTS, PORTFOLIO_INACTIVE, USER_COMPANY_MISMATCH (quando user.id informado é inválido).portfolio:read| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
status | Enum | Não | Filtrar por ACTIVE ou INACTIVE. |
orderBy | Enum | Não | Ordenação: name, createdAt. Default: name. |
order | Enum | Não | asc ou desc. Default: asc. |
offset | Integer | Não | Default: 0. |
limit | Integer | Não | Default: 20. Máximo: 100. |
{
"items": [
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"portfolioId": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
"name": "Analista João — Região Sul",
"description": "Contratos da região Sul sob responsabilidade do analista João.",
"user": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"name": "João Silva"
},
"status": "ACTIVE",
"summary": {
"activeContracts": 12,
"value": 3500000.00,
"overdue": 100000.00
},
"createdAt": "2025-03-17T13:32:32.209+00:00"
}
],
"nextPage": false
}SubPortfolioSummary completo (mesma estrutura do PortfolioSummary, ver seção 2.2/2.3.1, mas no escopo desta carteira).portfolio:read| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
startDate | Datetime (UTC) | Não | Início do período para summary. Default: primeiro dia do ano corrente. |
endDate | Datetime (UTC) | Não | Fim do período. Default: último dia do ano corrente. |
GET /portfolios/:portfolioId, mas restrita aos contratos desta carteira (ver PortfolioSummary em 2.2 para os campos do summary).name, description, user (envia apenas { "user": { "id": "..." } }).SUB_PORTFOLIO_NAME_ALREADY_EXISTS, SUB_PORTFOLIO_INACTIVE, USER_COMPANY_MISMATCH.SUB_PORTFOLIO_HAS_CONTRACTS.subPortfolioId (obrigatório) na entidade Contract e definida no POST do contrato (ver Contratos.md). Para mover um contrato entre carteiras do mesmo portfolio, atualiza-se o campo via PATCH .../portfolios/:portfolioId/contracts/:contractId informando o novo subPortfolioId.subPortfolioId é obrigatório no POST do contrato (POST .../portfolios/:portfolioId/contracts).PATCH .../contracts/:contractId com novo subPortfolioId no body.GET .../portfolios/:portfolioId/contracts?subPortfolioId=... ou via endpoint dedicado da carteira (ver Contratos.md).subPortfolioId = null — todo contrato sempre pertence a uma carteira.portfolio:assign_contract.portfolio:assign_user{
"user": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"role": "d290f1..."
}
}{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"portfolioId": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
"user": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"name": "João Silva"
},
"assignedBy": {
"id": "a1b2c3d4-1111-2222-3333-444455556666",
"name": "Maria Santos"
},
"assignedAt": "2025-03-17T13:32:32.209+00:00",
"createdAt": "2025-03-17T13:32:32.209+00:00"
}USER_ALREADY_ASSIGNED, USER_COMPANY_MISMATCH, INTERNAL_USER_NOT_ALLOWED.:userId na rota é o user.id.USER_ASSIGNMENT_NOT_FOUND.portfolio:assign_user{
"user": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"role": "d290f1..."
}
}{
"id": "b9d3f1aa-22cc-4e72-b430-1e45a3d6e001",
"subPortfolioId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"user":[
{
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"name": "João Silva",
"role": "d290f1..."
},
{
"id": "d2901...",
"name": "Pedro Henrique",
"role": "d290f1..."
}
],
"assignedBy": {
"id": "a1b2c3d4-1111-2222-3333-444455556666",
"name": "Maria Santos"
},
"assignedAt": "2025-03-17T13:32:32.209+00:00",
"createdAt": "2025-03-17T13:32:32.209+00:00"
}USER_ALREADY_ASSIGNED, USER_COMPANY_MISMATCH, INTERNAL_USER_NOT_ALLOWED.:userId na rota é o user.id.USER_ASSIGNMENT_NOT_FOUND.