Negócio

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

  1. Visualização Organizada: Listar propostas agrupadas por licitação
  2. Análise Comparativa: Permitir comparação detalhada de até 3 propostas
  3. Anonimato: Nunca identificar fornecedores para garantir imparcialidade
  4. Escolha Flexível: Permitir escolher vencedor independente do menor preço
  5. 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:

  1. 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
  2. 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

  1. 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
  2. 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
  3. 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
  4. 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.vue
    • pages/proposals/[uuid].vue ⚠️ IMPORTANTE: Usar UUID, nunca ID
    • components/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 /proposals e /proposals/:uuid funcionando
  • 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.vue com 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.vue com:
    • 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.vue para 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

  1. Proteção de Dados
    • Nunca expor supplier_id no frontend
    • Validar permissões em cada endpoint
    • Logs de auditoria para decisões
  2. Integridade do Processo
    • Timestamps imutáveis
    • Histórico de todas as ações
    • Justificativas obrigatórias para rejeições
  3. 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

  1. 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
  2. Usabilidade
    • ✅ Interface intuitiva sem necessidade de treinamento
    • ✅ Tempo de decisão reduzido em 50%
    • ✅ Zero reclamações sobre identificação de fornecedores
  3. 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:

  1. Visualização Dual implementada: Cards e Lista com toggle
  2. Agrupamento por Licitação: Com headers expansíveis
  3. Remoção de ações em lote: Simplificação conforme solicitado
  4. Tasks Linear detalhadas: 15 tasks com subtasks e critérios de aceitação
  5. Estimativas refinadas: Total de 11-14 dias úteis
  6. Priorização clara: Alta 🔴, Média 🟡, Baixa 🟢
  7. Dependências mapeadas: Ordem lógica de implementação
  8. ⚠️ Segurança: Sempre usar UUID nas URLs, nunca expor IDs do banco
Copyright © 2026