Sistema de Propostas - Portal do Produtor AgrSis
Sistema de Propostas - Portal do Produtor AgrSis
📋 Visão Geral
Este documento detalha a implementação do sistema de propostas no frontend do produtor, permitindo que produtores rurais visualizem, comparem e gerenciem propostas recebidas para suas licitações de insumos agrícolas.
🎯 Objetivos
- Visualização Organizada: Listar propostas agrupadas por licitação
- Análise Comparativa: Permitir comparação detalhada de até 3 propostas
- Anonimato: Nunca identificar fornecedores para garantir imparcialidade
- Escolha Flexível: Permitir escolher vencedor independente do menor preço
- Profissionalismo: Interface intuitiva e completa para tomada de decisão
📊 Estrutura de Dados
Models Existentes na API
Proposal (Proposta)
interface Proposal {
id: number
uuid: string
order_id: number
supplier_id: number // OCULTAR DO PRODUTOR
total_amount: number
notes?: string
validity_date?: Date
status: 'draft' | 'submitted' | 'accepted' | 'rejected' | 'cancelled' | 'expired' | 'lost'
submitted_at?: Date
accepted_at?: Date
rejected_at?: Date
rejection_reason?: string
items: ProposalItem[]
}
ProposalItem (Item da Proposta)
interface ProposalItem {
id: number
proposal_id: number
order_item_id: number
product_id: number
unit_price?: number
quantity: number
total_price?: number
offers_similar: boolean
similar_product_name?: string
similar_product_description?: string
justification?: string
justification_type?: 'unavailable' | 'out_of_stock' | 'logistics' | 'other'
notes?: string
}
🔌 Endpoints da API
Endpoints Disponíveis para Produtor
# Listar propostas recebidas para uma licitação
GET /api/v1/orders/{order}/proposals/received
# Detalhes de uma proposta específica
GET /api/v1/proposals/{proposal}
# Aceitar proposta (escolher vencedor)
POST /api/v1/proposals/{proposal}/accept
# Rejeitar proposta
POST /api/v1/proposals/{proposal}/reject
{
"reason": "string opcional"
}
🏗️ Estrutura de Componentes Vue
1. Página Principal de Propostas (pages/proposals.vue)
Funcionalidades:
- Lista todas as licitações com propostas recebidas
- Agrupamento por código/número da licitação
- Cards informativos com:
- Número da licitação
- Data de abertura
- Prazo final
- Quantidade de propostas recebidas
- Status (aberta/fechada/finalizada)
- Valor da menor proposta (sem identificar fornecedor)
- Indicador de novas propostas
Layout Sugerido:
<template>
<div class="proposals-page">
<!-- Header com estatísticas -->
<div class="stats-grid">
<StatCard title="Licitações Ativas" :value="activeCount" />
<StatCard title="Propostas Recebidas" :value="totalProposals" />
<StatCard title="Aguardando Análise" :value="pendingAnalysis" />
<StatCard title="Economia Potencial" :value="potentialSavings" />
</div>
<!-- Filtros -->
<div class="filters-bar">
<StatusFilter v-model="statusFilter" />
<DateRangeFilter v-model="dateRange" />
<SearchInput v-model="searchTerm" />
</div>
<!-- Lista de Licitações -->
<div class="biddings-grid">
<BiddingCard
v-for="bidding in filteredBiddings"
:key="bidding.id"
:bidding="bidding"
@click="navigateToBidding(bidding.id)"
/>
</div>
</div>
</template>
2. Página de Detalhes da Licitação (pages/proposals/[uuid].vue)
Funcionalidades:
- Informações completas da licitação
- Lista de propostas ordenada por valor total
- Identificação anônima (Proposta A, B, C...)
- Filtros e ordenação (por valor, data, completude)
- Ações rápidas (comparar, aceitar, rejeitar)
Layout Sugerido:
<template>
<div class="bidding-detail">
<!-- Header com info da licitação -->
<BiddingHeader :order="order" />
<!-- Barra de ações -->
<div class="actions-bar">
<button @click="openComparison" :disabled="selectedProposals.length < 2">
Comparar Selecionadas ({{ selectedProposals.length }}/3)
</button>
<div class="sort-controls">
<label>Ordenar por:</label>
<select v-model="sortBy">
<option value="total_amount_asc">Menor Valor</option>
<option value="total_amount_desc">Maior Valor</option>
<option value="submitted_at">Data de Envio</option>
<option value="completeness">Completude</option>
</select>
</div>
</div>
<!-- Lista de Propostas -->
<div class="proposals-list">
<ProposalCard
v-for="(proposal, index) in sortedProposals"
:key="proposal.uuid"
:proposal="proposal"
:label="getProposalLabel(index)"
:is-selected="isSelected(proposal)"
:is-best-price="isBestPrice(proposal)"
@select="toggleSelection(proposal)"
@view-details="viewDetails(proposal)"
/>
</div>
</div>
</template>
3. Modal de Comparação (components/ProposalComparison.vue)
Funcionalidades:
- Comparação lado a lado de até 3 propostas
- Tabela comparativa com:
- Cabeçalho fixo com identificação anônima
- Valor total destacado
- Lista de produtos com:
- Nome do produto
- Quantidade solicitada
- Preço unitário de cada proposta
- Total por item
- Indicador de produto similar/substituto
- Justificativas quando não cotado
- Destacar visualmente:
- Menor preço por item (verde)
- Maior preço por item (vermelho)
- Itens não cotados (cinza)
- Totalizadores e diferenças percentuais
- Botão para escolher vencedor direto da comparação
Layout Sugerido:
<template>
<div class="comparison-modal">
<div class="comparison-header">
<h2>Comparação de Propostas</h2>
<button @click="close" class="close-btn">×</button>
</div>
<div class="comparison-table">
<!-- Cabeçalho Fixo -->
<div class="table-header">
<div class="product-column">Produto</div>
<div v-for="(proposal, index) in proposals" :key="proposal.uuid" class="proposal-column">
<div class="proposal-label">{{ getLabel(index) }}</div>
<div class="proposal-total">R$ {{ formatCurrency(proposal.total_amount) }}</div>
</div>
</div>
<!-- Corpo da Tabela -->
<div class="table-body">
<div v-for="item in comparisonItems" :key="item.id" class="comparison-row">
<div class="product-info">
<span class="product-name">{{ item.name }}</span>
<span class="product-qty">{{ item.quantity }} {{ item.unit }}</span>
</div>
<div v-for="proposal in proposals" :key="proposal.uuid" class="proposal-item">
<ProposalItemCell
:item="getItemForProposal(item, proposal)"
:is-best="isBestPrice(item, proposal)"
:is-worst="isWorstPrice(item, proposal)"
/>
</div>
</div>
</div>
<!-- Rodapé com Totais -->
<div class="table-footer">
<div class="total-label">Total Geral</div>
<div v-for="proposal in proposals" :key="proposal.uuid" class="proposal-total">
<div class="total-value">R$ {{ formatCurrency(proposal.total_amount) }}</div>
<div class="difference" v-if="!isCheapest(proposal)">
+{{ calculateDifference(proposal) }}% em relação ao menor
</div>
<button @click="selectWinner(proposal)" class="select-btn">
Escolher como Vencedor
</button>
</div>
</div>
</div>
</div>
</template>
4. Card de Proposta (components/ProposalCard.vue)
Características:
- Identificação anônima (Proposta A, B, C...)
- Informações principais:
- Valor total em destaque
- Data/hora de envio
- Prazo de validade
- Quantidade de itens cotados/total
- Indicadores visuais (melhor preço, itens faltantes)
- Checkbox para seleção (comparação)
- Botões de ação contextual
<template>
<div class="proposal-card" :class="cardClasses">
<div class="card-header">
<div class="proposal-label">
<input type="checkbox" v-model="selected" @change="$emit('select')" />
<span class="label">{{ label }}</span>
<span v-if="isBestPrice" class="badge badge-success">Menor Valor</span>
</div>
<div class="proposal-value">
R$ {{ formatCurrency(proposal.total_amount) }}
</div>
</div>
<div class="card-body">
<div class="info-grid">
<div class="info-item">
<span class="label">Enviado em:</span>
<span class="value">{{ formatDate(proposal.submitted_at) }}</span>
</div>
<div class="info-item">
<span class="label">Validade:</span>
<span class="value">{{ formatDate(proposal.validity_date) }}</span>
</div>
<div class="info-item">
<span class="label">Itens cotados:</span>
<span class="value">{{ quotedItems }}/{{ totalItems }}</span>
</div>
<div class="info-item">
<span class="label">Completude:</span>
<ProgressBar :value="completeness" />
</div>
</div>
<div v-if="proposal.notes" class="proposal-notes">
<strong>Observações:</strong>
<p>{{ proposal.notes }}</p>
</div>
</div>
<div class="card-footer">
<button @click="$emit('view-details')" class="btn-secondary">
Ver Detalhes
</button>
<button @click="openQuickView" class="btn-secondary">
Visualização Rápida
</button>
<button v-if="canAccept" @click="acceptProposal" class="btn-primary">
Escolher Vencedor
</button>
</div>
</div>
</template>
🎨 Diretrizes de UX/UI
Visualização Dual: Cards e Lista
A interface oferece duas formas de visualização que o usuário pode alternar:
- Modo Cards (Padrão em Mobile/Tablet)
- Visual mais moderno e amigável
- Ideal para análise rápida e toque
- Melhor para poucos itens
- Informações resumidas e visuais
- Modo Lista/Tabela (Padrão em Desktop)
- Mais informações visíveis simultaneamente
- Comparação lado a lado facilitada
- Ideal para muitas propostas
- Ordenação e filtros mais eficientes
Implementação:
<!-- Toggle de Visualização -->
<div class="view-toggle">
<button @click="viewMode = 'cards'" :class="{ active: viewMode === 'cards' }">
<IconCards /> Cards
</button>
<button @click="viewMode = 'list'" :class="{ active: viewMode === 'list' }">
<IconList /> Lista
</button>
</div>
<!-- Renderização Condicional -->
<ProposalCards v-if="viewMode === 'cards'" :proposals="proposals" />
<ProposalTable v-else :proposals="proposals" />
Agrupamento por Licitação
Todas as propostas são agrupadas por licitação com cabeçalhos expansíveis:
<div class="bidding-group" v-for="bidding in groupedProposals">
<div class="group-header" @click="toggle(bidding.id)">
<IconChevron :rotated="expanded[bidding.id]" />
<span class="bidding-code">{{ bidding.code }}</span>
<span class="bidding-title">{{ bidding.title }}</span>
<span class="proposal-count">{{ bidding.proposals.length }} propostas</span>
<span class="value-range">
R$ {{ formatCurrency(bidding.minValue) }} - {{ formatCurrency(bidding.maxValue) }}
</span>
</div>
<div v-show="expanded[bidding.id]" class="group-content">
<!-- Cards ou Lista aqui -->
<div class="group-actions">
<button @click="compareSelected(bidding.id)">
Comparar Selecionadas (max 3)
</button>
</div>
<ProposalCards v-if="viewMode === 'cards'" :proposals="bidding.proposals" />
<ProposalTable v-else :proposals="bidding.proposals" />
</div>
</div>
Princípios de Design
- Anonimato Visual
- Usar cores e letras para identificar propostas
- Nunca mostrar nome, CNPJ ou qualquer dado do fornecedor
- Gerar IDs sequenciais aleatórios para cada sessão
- Hierarquia de Informação
- Valor total sempre em destaque
- Indicadores visuais para facilitar análise rápida
- Agrupamento lógico por licitação
- Preferência de visualização salva no localStorage
- Feedback Visual
- Cores para indicar status:
- Verde: Melhor preço/opção
- Amarelo: Atenção/pendente
- Vermelho: Problema/rejeitado
- Azul: Selecionado/ativo
- Animações suaves para transições
- Loading states para operações assíncronas
- Cores para indicar status:
- Responsividade
- Layout adaptável para desktop e tablet
- Tabela de comparação com scroll horizontal em telas menores
- Cards empilhados em mobile
- Toggle de visualização baseado no dispositivo
Estados e Interações
// Estados da proposta no contexto do produtor
enum ProposalState {
NEW = 'new', // Recém chegada, não visualizada
VIEWED = 'viewed', // Visualizada pelo produtor
ANALYZING = 'analyzing', // Em análise/comparação
SHORTLISTED = 'shortlisted', // Pré-selecionada
WINNER = 'winner', // Escolhida como vencedora
REJECTED = 'rejected' // Rejeitada
}
// Indicadores visuais
interface VisualIndicators {
bestPrice: boolean // Menor valor total
complete: boolean // Todos os itens cotados
hasAlternatives: boolean // Oferece produtos similares
nearDeadline: boolean // Próximo do vencimento
hasNotes: boolean // Contém observações
}
🔧 Funcionalidades Técnicas
1. Sistema de Identificação Anônima
// composables/useAnonymousLabels.js
export const useAnonymousLabels = () => {
const labelMap = ref(new Map())
const labelCounter = ref(0)
const getLabel = (proposalId) => {
if (!labelMap.value.has(proposalId)) {
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
const label = letters[labelCounter.value % 26]
labelMap.value.set(proposalId, `Proposta ${label}`)
labelCounter.value++
}
return labelMap.value.get(proposalId)
}
const resetLabels = () => {
labelMap.value.clear()
labelCounter.value = 0
}
return { getLabel, resetLabels }
}
2. Cálculo de Métricas
// composables/useProposalMetrics.js
export const useProposalMetrics = (proposals) => {
const metrics = computed(() => {
if (!proposals.value.length) return null
const amounts = proposals.value.map(p => p.total_amount)
return {
lowest: Math.min(...amounts),
highest: Math.max(...amounts),
average: amounts.reduce((a, b) => a + b, 0) / amounts.length,
spread: Math.max(...amounts) - Math.min(...amounts),
potentialSavings: calculateSavings(proposals.value)
}
})
const calculateCompleteness = (proposal, totalItems) => {
const quotedItems = proposal.items.filter(i => i.unit_price > 0).length
return (quotedItems / totalItems) * 100
}
return { metrics, calculateCompleteness }
}
3. Filtros e Ordenação
// composables/useProposalFilters.js
export const useProposalFilters = (proposals) => {
const sortBy = ref('total_amount_asc')
const filterStatus = ref('all')
const filterCompleteness = ref(0)
const filtered = computed(() => {
let result = [...proposals.value]
// Aplicar filtros
if (filterStatus.value !== 'all') {
result = result.filter(p => p.status === filterStatus.value)
}
if (filterCompleteness.value > 0) {
result = result.filter(p => {
const completeness = calculateCompleteness(p)
return completeness >= filterCompleteness.value
})
}
// Aplicar ordenação
switch(sortBy.value) {
case 'total_amount_asc':
result.sort((a, b) => a.total_amount - b.total_amount)
break
case 'total_amount_desc':
result.sort((a, b) => b.total_amount - a.total_amount)
break
case 'submitted_at':
result.sort((a, b) => new Date(b.submitted_at) - new Date(a.submitted_at))
break
case 'completeness':
result.sort((a, b) => calculateCompleteness(b) - calculateCompleteness(a))
break
}
return result
})
return { filtered, sortBy, filterStatus, filterCompleteness }
}
🚀 Implementação por Fases
Fase 1: Estrutura Base (2-3 dias)
- Criar página de listagem de propostas
- Implementar cards de licitação com contador
- Sistema de roteamento para detalhes
- Integração com API para buscar dados
Fase 2: Visualização de Propostas (2-3 dias)
- Página de detalhes da licitação
- Cards de propostas com anonimização
- Ordenação e filtros básicos
- Visualização rápida de proposta
Fase 3: Sistema de Comparação (3-4 dias)
- Seleção múltipla de propostas
- Modal de comparação
- Tabela comparativa com highlights
- Cálculos de diferenças e métricas
Fase 4: Gestão de Vencedor (2 dias)
- Fluxo de escolha de vencedor
- Modal de confirmação com justificativa
- Rejeição de propostas com motivo
- Histórico de decisões
Fase 5: Refinamentos (2 dias)
- Dashboard com estatísticas
- Exportação de comparações (PDF/Excel)
- Notificações de novas propostas
- Melhorias de performance
📋 TASKS LINEAR - Divisão Detalhada
🏗️ EPIC: Sistema de Propostas para Produtores
Objetivo: Implementar sistema completo de visualização, comparação e seleção de propostas Estimativa Total: 11-14 dias úteis Prioridade: Alta
✅ Task 1: Infraestrutura e Setup Inicial
Prioridade: 🔴 Alta | Estimativa: 0.5 dia | Dependências: Nenhuma
Subtasks:
- Criar estrutura de pastas para propostas no frontend
pages/proposals/index.vuepages/proposals/[uuid].vue⚠️ IMPORTANTE: Usar UUID, nunca IDcomponents/proposals/
- Configurar rotas no Nuxt com UUID
- Criar store Pinia para propostas (
stores/proposals.js) - Criar composable base (
composables/useProposals.js) - Configurar serviço de API (
services/proposalService.js)
Critérios de Aceitação:
- Rotas
/proposalse/proposals/:uuidfuncionando - Store Pinia criada e configurada
- Estrutura de pastas organizada
- NUNCA expor IDs do banco, sempre usar UUID
✅ Task 2: Sistema de Anonimização
Prioridade: 🔴 Alta | Estimativa: 0.5 dia | Dependências: Task 1
Subtasks:
- Implementar
composables/useAnonymousLabels.js- Função para gerar labels (Proposta A, B, C...)
- Mapeamento persistente por sessão
- Reset de labels quando necessário
- Criar componente
AnonymousLabel.vue - Adicionar testes unitários para o sistema de labels
- Documentar regras de anonimização
Critérios de Aceitação:
- Labels gerados sequencialmente e consistentes
- Nunca expor supplier_id no frontend
- Testes passando com 100% de cobertura
✅ Task 3: Página de Listagem de Licitações com Propostas
Prioridade: 🔴 Alta | Estimativa: 1 dia | Dependências: Task 2
Subtasks:
- Criar
pages/proposals/index.vue - Implementar componente
BiddingCard.vue- Mostrar contador de propostas
- Indicador de novas propostas
- Status da licitação
- Adicionar grid responsivo de cards
- Implementar filtros:
- Por status (ativas/fechadas)
- Por período
- Com/sem propostas
- Adicionar barra de estatísticas no topo
- Implementar busca por código/título
Critérios de Aceitação:
- Lista mostrando todas as licitações com propostas
- Filtros funcionando corretamente
- Layout responsivo em todas as resoluções
✅ Task 4: Visualização Dual (Cards/Lista)
Prioridade: 🟡 Média | Estimativa: 1.5 dias | Dependências: Task 3
Subtasks:
- Criar toggle de visualização
- Implementar
ProposalCards.vue- Layout em grid
- Cards com informações resumidas
- Implementar
ProposalTable.vue- Tabela com colunas ordenáveis
- Mais informações visíveis
- Salvar preferência no localStorage
- Adaptar visualização padrão por dispositivo
Critérios de Aceitação:
- Toggle funcionando suavemente
- Preferência salva e restaurada
- Mobile defaulta para cards, desktop para lista
✅ Task 5: Agrupamento por Licitação
Prioridade: 🔴 Alta | Estimativa: 1 dia | Dependências: Task 4
Subtasks:
- Implementar cabeçalhos de grupo expansíveis
- Adicionar contadores por grupo
- Mostrar range de valores (min-max)
- Implementar expand/collapse com animação
- Adicionar botão "expandir/recolher todos"
- Persistir estado de expansão no sessionStorage
Critérios de Aceitação:
- Grupos claramente separados visualmente
- Animações suaves de expand/collapse
- Estado mantido durante a sessão
✅ Task 6: Página de Detalhes da Licitação
Prioridade: 🔴 Alta | Estimativa: 1.5 dias | Dependências: Task 5
Subtasks:
- Criar
pages/proposals/[uuid].vue⚠️ Usar UUID da Order - Implementar
BiddingHeader.vuecom info completa - Criar lista de propostas com labels anônimos
- Adicionar ordenação:
- Por valor (crescente/decrescente)
- Por data de envio
- Por completude
- Implementar indicadores visuais:
- Badge "Menor Valor"
- Barra de completude
- Status da proposta
- Adicionar breadcrumb de navegação
Critérios de Aceitação:
- Todas as propostas da licitação visíveis
- Ordenação funcionando corretamente
- Labels anônimos aplicados consistentemente
- URL usando UUID da Order, nunca ID
✅ Task 7: Card de Proposta Individual
Prioridade: 🔴 Alta | Estimativa: 1 dia | Dependências: Task 6
Subtasks:
- Criar
ProposalCard.vuecom:- Checkbox para seleção
- Label anônimo destacado
- Valor total em destaque
- Data de envio e validade
- Contador de itens cotados
- Barra de progresso de completude
- Adicionar estados visuais (hover, selected, disabled)
- Implementar ações:
- Ver detalhes
- Visualização rápida
- Selecionar para comparação
- Adicionar tooltips informativos
Critérios de Aceitação:
- Card visualmente atrativo e informativo
- Interações responsivas
- Informações organizadas hierarquicamente
✅ Task 8: Sistema de Seleção para Comparação
Prioridade: 🔴 Alta | Estimativa: 1 dia | Dependências: Task 7
Subtasks:
- Implementar
composables/useProposalSelection.js - Limitar seleção a 3 propostas por licitação
- Criar contador visual de selecionados (X/3)
- Desabilitar checkboxes quando limite atingido
- Adicionar botão "Limpar Seleção"
- Mostrar feedback quando tentar selecionar mais de 3
Critérios de Aceitação:
- Máximo 3 propostas selecionáveis
- Feedback claro do limite
- Seleção apenas dentro da mesma licitação
✅ Task 9: Modal de Comparação - Estrutura
Prioridade: 🔴 Alta | Estimativa: 2 dias | Dependências: Task 8
Subtasks:
- Criar
ProposalComparison.vue - Implementar layout de tabela comparativa:
- Cabeçalho fixo com labels e totais
- Linhas de produtos
- Rodapé com totalizadores
- Adicionar scroll horizontal para mobile
- Implementar fechamento do modal
- Adicionar loading state enquanto carrega dados
Critérios de Aceitação:
- Modal abre com propostas selecionadas
- Layout responsivo e usável
- Cabeçalho e rodapé fixos ao scrollar
✅ Task 10: Modal de Comparação - Funcionalidades
Prioridade: 🔴 Alta | Estimativa: 1.5 dias | Dependências: Task 9
Subtasks:
- Implementar highlights de preços:
- Verde para menor preço por item
- Vermelho para maior preço
- Cinza para item não cotado
- Adicionar cálculo de diferenças percentuais
- Criar
ProposalItemCell.vuepara células - Mostrar produtos similares/substitutos
- Adicionar tooltips com justificativas
- Implementar totalizadores e métricas
Critérios de Aceitação:
- Comparação visual clara e intuitiva
- Cálculos corretos de diferenças
- Indicações visuais funcionando
✅ Task 11: Escolha de Vencedor
Prioridade: 🔴 Alta | Estimativa: 1 dia | Dependências: Task 10
Subtasks:
- Criar botão "Escolher Vencedor" em cada proposta
- Implementar modal de confirmação com:
- Resumo da proposta escolhida
- Campo opcional para justificativa
- Aviso se não for o menor preço
- Integrar com API
POST /proposals/{id}/accept - Atualizar status visual após aceite
- Desabilitar outras propostas após escolha
Critérios de Aceitação:
- Fluxo de aceite funcionando end-to-end
- Confirmação antes de finalizar
- Estado atualizado em toda interface
✅ Task 12: Rejeição de Propostas
Prioridade: 🟡 Média | Estimativa: 0.5 dia | Dependências: Task 11
Subtasks:
- Adicionar botão "Rejeitar" nas propostas
- Criar modal para motivo de rejeição
- Integrar com API
POST /proposals/{id}/reject - Atualizar status visual
- Adicionar confirmação antes de rejeitar
Critérios de Aceitação:
- Rejeição com motivo obrigatório
- Visual indicando proposta rejeitada
- Não permitir rejeitar proposta aceita
✅ Task 13: Visualização Rápida (Quick View)
Prioridade: 🟡 Média | Estimativa: 1 dia | Dependências: Task 7
Subtasks:
- Criar
ProposalQuickView.vue - Mostrar lista completa de itens
- Indicar produtos não cotados
- Mostrar observações do fornecedor
- Adicionar navegação entre propostas
- Implementar como modal ou drawer
Critérios de Aceitação:
- Abertura rápida sem carregar nova página
- Todas informações relevantes visíveis
- Navegação fluida entre propostas
✅ Task 14: Dashboard e Estatísticas
Prioridade: 🟢 Baixa | Estimativa: 1 dia | Dependências: Task 6
Subtasks:
- Criar cards de estatísticas:
- Total de licitações ativas
- Propostas aguardando análise
- Economia potencial
- Taxa de participação
- Implementar
composables/useProposalMetrics.js - Adicionar gráficos simples (opcional)
- Criar widget de atividade recente
Critérios de Aceitação:
- Métricas calculadas corretamente
- Dashboard informativo e visual
- Performance otimizada
✅ Task 15: Testes e Otimizações
Prioridade: 🔴 Alta | Estimativa: 1 dia | Dependências: Tasks 1-14
Subtasks:
- Escrever testes E2E para fluxos principais
- Otimizar queries e requisições
- Implementar cache onde apropriado
- Testar em diferentes resoluções
- Validar acessibilidade (A11y)
- Revisar e corrigir bugs encontrados
Critérios de Aceitação:
- Cobertura de testes > 80%
- Performance < 2s para carregar
- Zero erros de console
- Acessível via teclado
📈 KPIs e Métricas
Métricas de Negócio
- Tempo médio de análise de propostas
- Taxa de propostas analisadas vs recebidas
- Economia realizada vs menor preço
- Propostas por licitação (média)
Métricas de UX
- Tempo para primeira decisão
- Taxa de uso da comparação
- Cliques até escolha do vencedor
- Taxa de abandono no fluxo
🔒 Considerações de Segurança
- Proteção de Dados
- Nunca expor supplier_id no frontend
- Validar permissões em cada endpoint
- Logs de auditoria para decisões
- Integridade do Processo
- Timestamps imutáveis
- Histórico de todas as ações
- Justificativas obrigatórias para rejeições
- Performance
- Paginação de propostas
- Lazy loading de detalhes
- Cache de cálculos complexos
📝 Notas de Implementação
Composables Necessários
composables/
├── useProposals.js # CRUD de propostas
├── useAnonymousLabels.js # Sistema de anonimização
├── useProposalMetrics.js # Cálculos e métricas
├── useProposalFilters.js # Filtros e ordenação
├── useProposalComparison.js # Lógica de comparação
└── useProposalSelection.js # Gestão de seleção
Stores Pinia
stores/
├── proposals.js # Estado global de propostas
├── comparisons.js # Estado de comparações
└── selections.js # Propostas selecionadas
Componentes Reutilizáveis
components/
├── proposals/
│ ├── ProposalCard.vue
│ ├── ProposalList.vue
│ ├── ProposalDetails.vue
│ ├── ProposalComparison.vue
│ ├── ProposalQuickView.vue
│ └── ProposalItemCell.vue
├── biddings/
│ ├── BiddingCard.vue
│ ├── BiddingHeader.vue
│ └── BiddingStats.vue
└── common/
├── AnonymousLabel.vue
├── PriceIndicator.vue
└── CompletenessBar.vue
🎯 Critérios de Sucesso
- Funcional
- ✅ Produtor consegue ver todas as propostas recebidas
- ✅ Comparação eficiente de até 3 propostas
- ✅ Escolha de vencedor independente do preço
- ✅ Anonimato mantido em todo o fluxo
- Usabilidade
- ✅ Interface intuitiva sem necessidade de treinamento
- ✅ Tempo de decisão reduzido em 50%
- ✅ Zero reclamações sobre identificação de fornecedores
- Técnico
- ✅ Performance < 2s para carregar propostas
- ✅ Responsivo em todas as resoluções
- ✅ Zero erros críticos em produção
📅 Cronograma Estimado
Total: 11-14 dias úteis
- Semana 1: Fases 1 e 2 (estrutura e visualização)
- Semana 2: Fases 3 e 4 (comparação e gestão)
- Semana 3: Fase 5 e testes (refinamentos)
🔗 Referências
Documento criado em: 12/01/2025Última atualização: 12/01/2025Versão: 2.0
📌 Resumo das Alterações v2.0
Principais Mudanças:
- ✅ Visualização Dual implementada: Cards e Lista com toggle
- ✅ Agrupamento por Licitação: Com headers expansíveis
- ✅ Remoção de ações em lote: Simplificação conforme solicitado
- ✅ Tasks Linear detalhadas: 15 tasks com subtasks e critérios de aceitação
- ✅ Estimativas refinadas: Total de 11-14 dias úteis
- ✅ Priorização clara: Alta 🔴, Média 🟡, Baixa 🟢
- ✅ Dependências mapeadas: Ordem lógica de implementação
- ⚠️ Segurança: Sempre usar UUID nas URLs, nunca expor IDs do banco