ESPECIFICAÇÃO - PERFIL DO FORNECEDOR (SUPPLIER)
ESPECIFICAÇÃO - PERFIL DO FORNECEDOR (SUPPLIER)
Data: 2026-01-14 Hora: 13:38 Status: ✅ Aprovado para Implementação Epic: AGR-152 (Validação de Perfil)
📋 VISÃO GERAL
Implementação completa da página /profile do fornecedor, incluindo validação de e-mail, gestão de endereços, seleção de tipos de produtos e configuração de área de atuação geográfica.
🎯 OBJETIVOS
Objetivo Principal
Permitir que fornecedores completem seu cadastro de forma simples e intuitiva, garantindo informações mínimas necessárias para participar de licitações.
Critérios de Sucesso
- ✅ 80%+ dos fornecedores completam o perfil
- ✅ Tempo médio < 5 minutos para completar
- ✅ Taxa de abandono < 15% na seção geográfica
- ✅ Taxa de erros de validação < 5%
📐 ARQUITETURA DA SOLUÇÃO
Estrutura de Componentes
apps/supplier/pages/profile.vue
├── Header com Avatar e Informações Básicas
├── Barra de Progresso (AGR-152)
├── Card 1: Validação de E-mail (reutilizar producer)
├── Card 2: Endereços (reutilizar producer)
├── Card 3: Tipos de Produtos (NOVO)
│ └── components/profile/ProductTypesSelector.vue
└── Card 4: Área de Atuação Geográfica (NOVO)
├── components/profile/GeographicAreaSelector.vue
├── components/profile/GeographicStatesSelector.vue
└── components/profile/GeographicCitiesAutocomplete.vue
🗄️ ESTRUTURA DE BANCO DE DADOS
Tabela Nova: supplier_product_types (Pivot)
CREATE TABLE supplier_product_types (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
supplier_id BIGINT UNSIGNED NOT NULL,
product_type_id BIGINT UNSIGNED NOT NULL,
status BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
UNIQUE KEY unique_supplier_product (supplier_id, product_type_id),
INDEX idx_supplier_id (supplier_id),
INDEX idx_product_type_id (product_type_id),
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE CASCADE,
FOREIGN KEY (product_type_id) REFERENCES product_types(id) ON DELETE CASCADE
);
Tabelas Existentes (Já Criadas)
-- Tipos de produtos (5 tipos cadastrados)
product_types
├── id
├── description (varchar)
├── status (boolean)
└── timestamps
-- Estados atendidos pelo fornecedor
supplier_states
├── id
├── supplier_id
├── state_id
├── status
└── timestamps
-- Cidades atendidas pelo fornecedor
supplier_state_cities
├── id
├── supplier_id
├── state_city_id
├── status
└── timestamps
-- Estados brasileiros (27 estados)
states
├── id
├── sigla (varchar 2)
├── nome (varchar)
└── timestamps
-- Cidades (5.570 cidades)
state_cities
├── id
├── state_id
├── nome (varchar)
└── timestamps
🔌 ESPECIFICAÇÃO DE APIs
1. Tipos de Produtos
GET /api/v1/product-types
Lista todos os tipos de produtos disponíveis (5 tipos).
Response:
{
"data": [
{
"id": 1,
"description": "Defensivos Agrícolas",
"status": true
},
{
"id": 2,
"description": "Fertilizantes & Nutrição",
"status": true
},
{
"id": 3,
"description": "Sementes",
"status": true
},
{
"id": 4,
"description": "Adjuvantes & Modificadores de Calda",
"status": true
},
{
"id": 5,
"description": "Bioinsumos",
"status": true
}
]
}
GET /api/v1/supplier/product-types
Retorna os tipos de produtos que o fornecedor selecionou.
Response:
{
"data": [
{
"id": 1,
"description": "Defensivos Agrícolas",
"selected_at": "2026-01-14T13:30:00Z"
},
{
"id": 3,
"description": "Sementes",
"selected_at": "2026-01-14T13:30:00Z"
}
]
}
POST /api/v1/supplier/product-types
Salva os tipos de produtos selecionados pelo fornecedor.
Request:
{
"product_type_ids": [1, 3, 5]
}
Response:
{
"message": "Tipos de produtos atualizados com sucesso",
"data": {
"product_types": [
{ "id": 1, "description": "Defensivos Agrícolas" },
{ "id": 3, "description": "Sementes" },
{ "id": 5, "description": "Bioinsumos" }
]
}
}
Validação:
- Mínimo 1 tipo de produto deve ser selecionado
- IDs devem existir na tabela
product_types
2. Geografia (Estados e Cidades)
GET /api/v1/geography/states
Lista todos os estados brasileiros (27 estados).
Response:
{
"data": [
{ "id": 1, "sigla": "AC", "nome": "Acre" },
{ "id": 2, "sigla": "AL", "nome": "Alagoas" },
{ "id": 25, "sigla": "SP", "nome": "São Paulo" },
// ... 27 estados
]
}
GET /api/v1/geography/cities?state_id=25
Lista cidades de um estado específico.
Query Parameters:
state_id(required): ID do estado
Response:
{
"data": [
{ "id": 1234, "nome": "São Paulo", "state_id": 25 },
{ "id": 1235, "nome": "Campinas", "state_id": 25 },
{ "id": 1236, "nome": "Santos", "state_id": 25 },
// ... todas cidades do estado
],
"meta": {
"total": 645,
"state": { "id": 25, "sigla": "SP", "nome": "São Paulo" }
}
}
GET /api/v1/geography/cities/search?q=Uber
Busca cidades por nome (autocomplete).
Query Parameters:
q(required): Termo de busca (mínimo 3 caracteres)limit(optional): Limite de resultados (default: 10)
Response:
{
"data": [
{
"id": 1234,
"nome": "Uberlândia",
"state_id": 31,
"state": { "sigla": "MG", "nome": "Minas Gerais" }
},
{
"id": 1235,
"nome": "Uberaba",
"state_id": 31,
"state": { "sigla": "MG", "nome": "Minas Gerais" }
},
{
"id": 1236,
"nome": "Ubatuba",
"state_id": 25,
"state": { "sigla": "SP", "nome": "São Paulo" }
}
],
"meta": {
"total": 3,
"query": "Uber"
}
}
Otimizações:
- Cache de 1 hora para lista de estados (são fixos)
- Debounce de 300ms no frontend para autocomplete
- Limite padrão de 10 resultados para performance
3. Área de Atuação Geográfica
GET /api/v1/supplier/geographic-area
Retorna a área de atuação configurada pelo fornecedor.
Response:
{
"data": {
"scope": "states",
"states": [
{ "id": 25, "sigla": "SP", "nome": "São Paulo" },
{ "id": 31, "sigla": "MG", "nome": "Minas Gerais" },
{ "id": 33, "sigla": "RJ", "nome": "Rio de Janeiro" }
],
"cities": [
{
"id": 2345,
"nome": "Goiânia",
"state": { "id": 52, "sigla": "GO", "nome": "Goiás" }
},
{
"id": 3456,
"nome": "Brasília",
"state": { "id": 53, "sigla": "DF", "nome": "Distrito Federal" }
}
],
"summary": {
"is_national": false,
"states_count": 3,
"cities_count": 2,
"total_coverage": "3 estados + 2 cidades adicionais"
}
}
}
Caso Nacional:
{
"data": {
"scope": "national",
"states": [],
"cities": [],
"summary": {
"is_national": true,
"states_count": 0,
"cities_count": 0,
"total_coverage": "Todo o Brasil"
}
}
}
POST /api/v1/supplier/geographic-area
Salva a área de atuação do fornecedor.
Request (Nacional):
{
"scope": "national"
}
Request (Por Estados):
{
"scope": "states",
"state_ids": [25, 31, 33],
"city_ids": [2345, 3456]
}
Request (Por Cidades):
{
"scope": "cities",
"city_ids": [1234, 1235, 1236, 1237, 1238]
}
Response:
{
"message": "Área de atuação atualizada com sucesso",
"data": {
"scope": "states",
"summary": {
"is_national": false,
"states_count": 3,
"cities_count": 2,
"total_coverage": "3 estados + 2 cidades adicionais"
}
}
}
Validação:
- Se
scope = "national": não precisa state_ids nem city_ids - Se
scope = "states": state_ids obrigatório com mínimo 1 item - Se
scope = "cities": city_ids obrigatório com mínimo 1 item - IDs devem existir nas tabelas correspondentes
Lógica de Salvamento:
- Limpar registros antigos de
supplier_statesesupplier_state_cities - Se nacional: não inserir nenhum registro (flag é a ausência de registros + scope)
- Se estados: inserir em
supplier_states - Se cidades: inserir em
supplier_state_cities - Permitir combinação: estados + cidades adicionais
4. Validação de Perfil
GET /api/v1/supplier/profile/validation
Retorna o status de validação do perfil do fornecedor.
Response:
{
"data": {
"is_complete": true,
"progress": 100,
"validation": {
"email_verified": {
"status": true,
"verified_at": "2026-01-14T10:30:00Z"
},
"has_address": {
"status": true,
"count": 2
},
"has_product_types": {
"status": true,
"count": 3,
"types": ["Defensivos Agrícolas", "Sementes", "Bioinsumos"]
},
"has_geographic_area": {
"status": true,
"scope": "states",
"summary": "3 estados + 2 cidades adicionais"
}
},
"missing_items": [],
"next_steps": []
}
}
Response (Perfil Incompleto):
{
"data": {
"is_complete": false,
"progress": 50,
"validation": {
"email_verified": {
"status": true,
"verified_at": "2026-01-14T10:30:00Z"
},
"has_address": {
"status": true,
"count": 1
},
"has_product_types": {
"status": false,
"count": 0,
"types": []
},
"has_geographic_area": {
"status": false,
"scope": null,
"summary": null
}
},
"missing_items": [
"Selecione pelo menos 1 tipo de produto",
"Configure sua área de atuação geográfica"
],
"next_steps": [
"Acesse a aba 'Tipos de Produtos' e selecione os insumos que você fornece",
"Configure em qual região você atende (Nacional, Estados ou Cidades)"
]
}
}
Cálculo de Progresso:
// 4 itens obrigatórios (25% cada)
const progress =
(emailVerified ? 25 : 0) +
(hasAddress ? 25 : 0) +
(hasProductTypes ? 25 : 0) +
(hasGeographicArea ? 25 : 0)
🎨 SOLUÇÃO DE UX/UI
Princípios de Design Aplicados
1. Lei de Miller (Chunking Information)
- Máximo 7±2 itens visíveis por vez
- Selecionados separados visualmente
- Lista completa com scroll
2. Lei de Hick (Reduzir Decisões)
- Fluxo guiado: uma decisão por vez
- 3 opções claras iniciais (Nacional/Estados/Cidades)
- Progressive disclosure
3. Efeito do Gradiente de Objetivo
- Barra de progresso sempre visível
- Checklist visual (✅/⚠️)
- Feedback imediato
4. Lei da Conectividade Uniforme
- Cards separados por seção
- Status visual claro (cor verde = completo, amarelo = pendente)
- Agrupamento hierárquico
5. Lei de Prägnanz (Simplicidade)
- Design minimalista
- Uma ação por vez
- Botões grandes e labels claras
Fluxo de Área de Atuação Geográfica
Passo 1: Escolha do Escopo
┌─────────────────────────────────────────────────────────────┐
│ 📍 Onde você atende? │
│ │
│ [ ] 🇧🇷 NACIONAL - Atendo todo o Brasil │
│ [✓] 🗺️ POR ESTADOS - Escolho estados específicos │
│ [ ] 📍 POR CIDADES - Escolho cidades específicas │
│ │
│ [Continuar] │
└─────────────────────────────────────────────────────────────┘
Passo 2A: Nacional (Fim)
┌─────────────────────────────────────────────────────────────┐
│ ✅ Área de Atuação: NACIONAL │
│ │
│ 🇧🇷 Você atende todo o Brasil │
│ │
│ [Editar] │
└─────────────────────────────────────────────────────────────┘
Passo 2B: Por Estados
┌─────────────────────────────────────────────────────────────┐
│ 🗺️ Selecione os estados onde você atende: │
│ │
│ 🔍 [Buscar estado... ] [Limpar] │
│ │
│ ✅ Selecionados (3): │
│ ┌─────────────┬─────────────┬─────────────┐ │
│ │ ✅ SP │ ✅ MG │ ✅ RJ │ │
│ │ São Paulo │ Minas Gerais│ Rio Janeiro │ │
│ │ [×] │ [×] │ [×] │ │
│ └─────────────┴─────────────┴─────────────┘ │
│ │
│ 📋 Todos os estados: │
│ □ AC □ AL □ AM □ AP □ BA □ CE □ DF □ ES │
│ □ GO □ MA □ MT □ MS □ PA □ PB □ PE □ PI │
│ □ PR □ RN □ RO □ RR □ RS □ SC □ SE □ TO │
│ │
│ 💡 [+ Adicionar cidades de outros estados] │
│ │
│ [Salvar Seleção] │
└─────────────────────────────────────────────────────────────┘
Passo 2C: Por Cidades (Autocomplete)
┌─────────────────────────────────────────────────────────────┐
│ 📍 Digite o nome da cidade: │
│ │
│ 🔍 [Uber___________________________] │
│ │ │
│ │ Sugestões: │
│ │ • Uberlândia - MG │
│ │ • Uberaba - MG │
│ │ • Ubatuba - SP │
│ └─────────────────────────────────────────────────────│
│ │
│ ✅ Cidades Selecionadas (5): │
│ • Uberlândia - MG [×] │
│ • Goiânia - GO [×] │
│ • São Paulo - SP [×] │
│ • Campinas - SP [×] │
│ • Brasília - DF [×] │
│ │
│ 💡 Atendendo muitas cidades de um estado? │
│ [← Voltar e selecionar estados inteiros] │
│ │
│ [Salvar Seleção] │
└─────────────────────────────────────────────────────────────┘
Passo 3: Resumo Visual
┌─────────────────────────────────────────────────────────────┐
│ ✅ Área de Atuação Configurada │
│ │
│ 🗺️ Você atende: │
│ │
│ 📌 ESTADOS (3): │
│ • São Paulo (SP) - todas cidades │
│ • Minas Gerais (MG) - todas cidades │
│ • Rio de Janeiro (RJ) - todas cidades │
│ │
│ 📍 CIDADES ADICIONAIS (2): │
│ • Goiânia - GO │
│ • Brasília - DF │
│ │
│ 💡 Você receberá licitações destas regiões │
│ │
│ [Editar Área de Atuação] │
└─────────────────────────────────────────────────────────────┘
💻 ESTRUTURA DE CÓDIGO
Composables
composables/useSupplierProfile.ts
export interface ProductType {
id: number
description: string
status: boolean
}
export interface GeographicArea {
scope: 'national' | 'states' | 'cities'
states: Array<{ id: number; sigla: string; nome: string }>
cities: Array<{
id: number
nome: string
state: { id: number; sigla: string; nome: string }
}>
summary: {
is_national: boolean
states_count: number
cities_count: number
total_coverage: string
}
}
export interface ProfileValidation {
is_complete: boolean
progress: number
validation: {
email_verified: { status: boolean; verified_at?: string }
has_address: { status: boolean; count: number }
has_product_types: { status: boolean; count: number; types: string[] }
has_geographic_area: { status: boolean; scope?: string; summary?: string }
}
missing_items: string[]
next_steps: string[]
}
export const useSupplierProfile = () => {
const { user } = useAuth()
const config = useRuntimeConfig()
// Estados
const productTypes = ref<ProductType[]>([])
const selectedProductTypes = ref<number[]>([])
const geographicArea = ref<GeographicArea | null>(null)
const profileValidation = ref<ProfileValidation | null>(null)
const loading = ref(false)
const error = ref<string | null>(null)
// Computeds
const isProfileComplete = computed(() =>
profileValidation.value?.is_complete || false
)
const profileProgress = computed(() =>
profileValidation.value?.progress || 0
)
// Métodos
const loadProductTypes = async () => {
try {
loading.value = true
const response = await $fetch<{ data: ProductType[] }>(
'/api/v1/product-types',
{ baseURL: config.public.apiBase }
)
productTypes.value = response.data
} catch (e) {
error.value = 'Erro ao carregar tipos de produtos'
console.error(e)
} finally {
loading.value = false
}
}
const loadSelectedProductTypes = async () => {
try {
const response = await $fetch<{ data: ProductType[] }>(
'/api/v1/supplier/product-types',
{ baseURL: config.public.apiBase }
)
selectedProductTypes.value = response.data.map(pt => pt.id)
} catch (e) {
console.error(e)
}
}
const saveProductTypes = async (typeIds: number[]) => {
try {
loading.value = true
await $fetch('/api/v1/supplier/product-types', {
method: 'POST',
baseURL: config.public.apiBase,
body: { product_type_ids: typeIds }
})
selectedProductTypes.value = typeIds
await validateProfile()
} catch (e) {
error.value = 'Erro ao salvar tipos de produtos'
console.error(e)
throw e
} finally {
loading.value = false
}
}
const loadGeographicArea = async () => {
try {
const response = await $fetch<{ data: GeographicArea }>(
'/api/v1/supplier/geographic-area',
{ baseURL: config.public.apiBase }
)
geographicArea.value = response.data
} catch (e) {
console.error(e)
}
}
const saveGeographicArea = async (area: Partial<GeographicArea>) => {
try {
loading.value = true
const payload: any = { scope: area.scope }
if (area.scope === 'states' && area.states) {
payload.state_ids = area.states.map(s => s.id)
}
if (area.cities && area.cities.length > 0) {
payload.city_ids = area.cities.map(c => c.id)
}
const response = await $fetch<{ data: GeographicArea }>(
'/api/v1/supplier/geographic-area',
{
method: 'POST',
baseURL: config.public.apiBase,
body: payload
}
)
geographicArea.value = response.data
await validateProfile()
} catch (e) {
error.value = 'Erro ao salvar área de atuação'
console.error(e)
throw e
} finally {
loading.value = false
}
}
const validateProfile = async () => {
try {
const response = await $fetch<{ data: ProfileValidation }>(
'/api/v1/supplier/profile/validation',
{ baseURL: config.public.apiBase }
)
profileValidation.value = response.data
} catch (e) {
console.error(e)
}
}
// Inicialização
onMounted(() => {
loadProductTypes()
loadSelectedProductTypes()
loadGeographicArea()
validateProfile()
})
return {
productTypes,
selectedProductTypes,
geographicArea,
profileValidation,
isProfileComplete,
profileProgress,
loading,
error,
saveProductTypes,
saveGeographicArea,
validateProfile
}
}
composables/useGeographySearch.ts
export interface State {
id: number
sigla: string
nome: string
}
export interface City {
id: number
nome: string
state_id: number
state: State
}
export const useGeographySearch = () => {
const config = useRuntimeConfig()
const states = ref<State[]>([])
const cities = ref<City[]>([])
const loading = ref(false)
const loadStates = async () => {
try {
loading.value = true
const response = await $fetch<{ data: State[] }>(
'/api/v1/geography/states',
{ baseURL: config.public.apiBase }
)
states.value = response.data
} catch (e) {
console.error('Erro ao carregar estados:', e)
} finally {
loading.value = false
}
}
const searchCities = useDebounceFn(async (query: string) => {
if (query.length < 3) {
cities.value = []
return []
}
try {
loading.value = true
const response = await $fetch<{ data: City[] }>(
`/api/v1/geography/cities/search?q=${encodeURIComponent(query)}`,
{ baseURL: config.public.apiBase }
)
cities.value = response.data
return response.data
} catch (e) {
console.error('Erro ao buscar cidades:', e)
return []
} finally {
loading.value = false
}
}, 300)
return {
states,
cities,
loading,
loadStates,
searchCities
}
}
✅ CRITÉRIOS DE VALIDAÇÃO
Perfil Completo = TRUE quando:
- ✅ E-mail Validado
- Código de verificação enviado e confirmado
- Campo
email_verified_atnão nulo na tabelauser_accesses
- ✅ Pelo Menos 1 Endereço
- Tabela
addressescomaddressable_type = 'App\Models\Supplier' addressable_id = supplier.idstatus = true
- Tabela
- ✅ Pelo Menos 1 Tipo de Produto
- Tabela
supplier_product_typescomsupplier_id = supplier.id status = true- Mínimo 1 registro
- Tabela
- ✅ Pelo Menos 1 Item na Área de Atuação
- OPÇÃO A: Scope nacional (flag no sistema, sem registros específicos)
- OPÇÃO B: Mínimo 1 registro em
supplier_states - OPÇÃO C: Mínimo 1 registro em
supplier_state_cities
🎯 MÉTRICAS DE SUCESSO
Métricas a Acompanhar
- Taxa de Conclusão de Perfil
- Fórmula:
completed_profiles / total_signups * 100 - Meta: > 80%
- Fórmula:
- Tempo Médio para Completar
- Fórmula:
AVG(completed_at - signup_at) - Meta: < 5 minutos
- Fórmula:
- Taxa de Abandono por Seção
- Email:
abandoned_at_email / started_email * 100 - Endereços:
abandoned_at_address / started_address * 100 - Produtos:
abandoned_at_products / started_products * 100 - Geografia:
abandoned_at_geography / started_geography * 100 - Meta: < 15% em cada seção
- Email:
- Taxa de Erros de Validação
- Fórmula:
validation_errors / save_attempts * 100 - Meta: < 5%
- Fórmula:
- NPS de Usabilidade
- Pergunta: "Foi fácil completar seu perfil?"
- Meta: > 85% positivo
♿ ACESSIBILIDADE (WCAG 2.1 AA)
Checklist
- ✅ Contraste de Cores: Mínimo 4.5:1 para textos, 3:1 para elementos interativos
- ✅ Navegação por Teclado: Todos os elementos focáveis com Tab, ativação com Space/Enter
- ✅ Screen Readers: Labels descritivos, aria-label em ícones, estados anunciados
- ✅ Feedback Visual: Estados de hover, focus, error sempre visíveis
- ✅ Touch Targets: Mínimo 44x44px em mobile
- ✅ Responsividade: Layout adapta de 320px (mobile) até 1920px (desktop)
- ✅ Semântica HTML: Tags corretas (button, input, label, section)
🚀 PLANO DE IMPLEMENTAÇÃO
Fase 1: Backend - Banco de Dados (1 dia)
- Criar migration
create_supplier_product_types_table - Criar model
SupplierProductType - Adicionar relacionamento no model
Supplier - Executar migration e testar relacionamentos
Fase 2: Backend - Controllers e APIs (2 dias)
- Criar
GeographyControllercom métodos:getStates()getCitiesByState()searchCities()
- Adicionar métodos no
SupplierController:getProductTypes()updateProductTypes()getGeographicArea()updateGeographicArea()validateProfile()
- Criar rotas no
routes/api.php - Testar todas as APIs via Postman/Insomnia
Fase 3: Frontend - Base da Página (1 dia)
- Criar estrutura da página
apps/supplier/pages/profile.vue - Implementar header com avatar e informações
- Implementar barra de progresso (AGR-152)
- Reutilizar componente de validação de e-mail do producer
- Reutilizar componente de endereços do producer
Fase 4: Frontend - Tipos de Produtos (1 dia)
- Criar componente
components/profile/ProductTypesSelector.vue - Implementar seleção visual (checkboxes estilizados)
- Integrar com API
/api/v1/supplier/product-types - Validar mínimo 1 selecionado
Fase 5: Frontend - Área Geográfica (3 dias)
- Criar composable
composables/useGeographySearch.ts - Criar componente
components/profile/GeographicAreaSelector.vue - Criar sub-componente
GeographicStatesSelector.vue - Criar sub-componente
GeographicCitiesAutocomplete.vue - Implementar fluxo guiado (Nacional → Estados → Cidades)
- Implementar resumo visual
- Integrar com APIs de geografia
Fase 6: Validação e Testes (1-2 dias)
- Implementar validação completa de perfil
- Testar fluxo completo de preenchimento
- Testar navegação por teclado
- Testar com screen reader (NVDA/VoiceOver)
- Testar responsividade (mobile/tablet/desktop)
- Ajustes finais de UX
Fase 7: Documentação (meio dia)
- Documentar APIs no Scribe
- Atualizar README com fluxo de perfil
- Documentar componentes criados
📦 ENTREGÁVEIS
- ✅ Migration e Model
SupplierProductType - ✅ Controller
GeographyControllercompleto - ✅ Métodos adicionados no
SupplierController - ✅ Rotas de API documentadas no Scribe
- ✅ Página
/profilecompleta e funcional - ✅ Composables
useSupplierProfileeuseGeographySearch - ✅ Componentes de seleção de produtos e área geográfica
- ✅ Testes de usabilidade realizados
- ✅ Documentação atualizada
🔍 DEPENDÊNCIAS
Bibliotecas Necessárias (já instaladas)
- ✅ Vue 3 / Nuxt 3
- ✅ Tailwind CSS
- ✅ Laravel 11
- ✅ PostgreSQL 16
Novos Composables VueUse (se necessário)
useDebounceFn(para autocomplete) - já disponível no VueUse
📝 NOTAS TÉCNICAS
Performance
- Cache de Estados
- Estados são fixos (27), cachear por 1 hora
- Usar
Cache::remember('states', 3600, fn() => State::all())
- Autocomplete de Cidades
- Mínimo 3 caracteres para buscar
- Debounce de 300ms no frontend
- Limitar a 10 resultados por query
- Index em
state_cities.nomepara busca rápida
- Validação de Perfil
- Cachear resultado por 5 minutos
- Invalidar cache ao salvar qualquer seção
- Usar jobs em background para recalcular se necessário
Segurança
- Autorização
- Apenas o próprio fornecedor pode editar seu perfil
- Middleware
auth:sanctumem todas as rotas - Validar
supplier_id = auth()->user()->supplier_id
- Validação de Dados
- Validar IDs de estados/cidades existem no banco
- Validar mínimo 1 item em cada seção obrigatória
- Sanitizar queries de busca (SQL injection)
📅 CRONOGRAMA ESTIMADO
| Fase | Descrição | Dias | Responsável |
|---|---|---|---|
| 1 | Backend - Banco de Dados | 1 | Dev |
| 2 | Backend - APIs | 2 | Dev |
| 3 | Frontend - Base | 1 | Dev |
| 4 | Frontend - Produtos | 1 | Dev |
| 5 | Frontend - Geografia | 3 | Dev |
| 6 | Testes | 1-2 | Dev + QA |
| 7 | Documentação | 0.5 | Dev |
| TOTAL | 9-10 dias |
✅ APROVAÇÃO
- Proposta de UX aprovada
- Fluxo de área geográfica validado
- Estrutura de APIs definida
- Cronograma aceito
Aprovado por: Gustavo Carneiro Data: 2026-01-14 Status: ✅ Pronto para Desenvolvimento