#49 feat: CRUD admin de organizaciones, proveedores y geografía

This commit is contained in:
2026-08-15 17:40:34 -05:00
parent 4ecfb64118
commit 760b3031b2
12 changed files with 1739 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
import { describe, expect, it, vi } from 'vitest'
import { flushPromises, mount } from '@vue/test-utils'
import GeographyManagement from '@/components/provenance/admin/GeographyManagement.vue'
import vuetify from '@/plugins/vuetify'
import { clickBody, setBodyInput } from './helpers'
const countries = [
{ id: 1, name: 'Colombia', code: 'CO' },
{ id: 2, name: 'Venezuela', code: 'VE' },
]
const departments = [
{ id: 2, name: 'Cundinamarca', country: 1, country_detail: { id: 1, name: 'Colombia', code: 'CO' } },
{ id: 3, name: 'Antioquia', country: 1, country_detail: { id: 1, name: 'Colombia', code: 'CO' } },
]
const municipalities = [
{ id: 7, name: 'La Mesa', department: 2, country: 1, department_detail: { id: 2, name: 'Cundinamarca' } },
{ id: 8, name: 'San Antonio', department: 3, country: 1, department_detail: { id: 3, name: 'Antioquia' } },
]
function mockApi () {
return {
getCountries: vi.fn().mockResolvedValue(countries),
createCountry: vi.fn().mockResolvedValue({}),
updateCountry: vi.fn().mockResolvedValue({}),
deleteCountry: vi.fn().mockResolvedValue({}),
getDepartments: vi.fn().mockResolvedValue(departments),
createDepartment: vi.fn().mockResolvedValue({}),
updateDepartment: vi.fn().mockResolvedValue({}),
deleteDepartment: vi.fn().mockResolvedValue({}),
getMunicipalities: vi.fn().mockResolvedValue(municipalities),
createMunicipality: vi.fn().mockResolvedValue({}),
updateMunicipality: vi.fn().mockResolvedValue({}),
deleteMunicipality: vi.fn().mockResolvedValue({}),
}
}
function mountComponent (api) {
return mount(GeographyManagement, {
global: { plugins: [vuetify], provide: { api } },
})
}
describe('GeographyManagement', () => {
it('muestra los países por defecto y permite crear uno', async () => {
const api = mockApi()
const wrapper = mountComponent(api)
await flushPromises()
expect(api.getCountries).toHaveBeenCalled()
expect(wrapper.text()).toContain('Colombia')
await wrapper.find('[data-testid="geo-country-create"]').trigger('click')
setBodyInput('[data-testid="geo-country-form-name"] input', 'Ecuador')
setBodyInput('[data-testid="geo-country-form-code"] input', 'EC')
clickBody('[data-testid="geo-country-save"]')
await flushPromises()
expect(api.createCountry).toHaveBeenCalledWith({ name: 'Ecuador', code: 'EC' })
expect(api.getCountries).toHaveBeenCalledTimes(2)
})
it('cambia a la pestaña de departamentos y crea uno', async () => {
const api = mockApi()
const wrapper = mountComponent(api)
await flushPromises()
await wrapper.find('[data-testid="geo-tab-departments"]').trigger('click')
await flushPromises()
expect(api.getDepartments).toHaveBeenCalled()
expect(wrapper.text()).toContain('Cundinamarca')
await wrapper.find('[data-testid="geo-department-create"]').trigger('click')
setBodyInput('[data-testid="geo-department-form-name"] input', 'Risaralda')
const countrySelect = wrapper.findAllComponents({ name: 'VSelect' })
.find(select => select.props('items').some(item => item.id === 2))
await countrySelect.vm.$emit('update:modelValue', 2)
clickBody('[data-testid="geo-department-save"]')
await flushPromises()
expect(api.createDepartment).toHaveBeenCalledWith(expect.objectContaining({
name: 'Risaralda',
country: 2,
}))
expect(api.getDepartments).toHaveBeenCalledTimes(2)
})
it('cambia a la pestaña de municipios y crea uno con departamento y país', async () => {
const api = mockApi()
const wrapper = mountComponent(api)
await flushPromises()
await wrapper.find('[data-testid="geo-tab-municipalities"]').trigger('click')
await flushPromises()
expect(api.getMunicipalities).toHaveBeenCalled()
expect(wrapper.text()).toContain('San Antonio')
await wrapper.find('[data-testid="geo-municipality-create"]').trigger('click')
setBodyInput('[data-testid="geo-municipality-form-name"] input', 'Pereira')
await wrapper.findAllComponents({ name: 'VAutocomplete' })[0].vm.$emit('update:modelValue', 3)
clickBody('[data-testid="geo-municipality-save"]')
await flushPromises()
expect(api.createMunicipality).toHaveBeenCalledWith(expect.objectContaining({
name: 'Pereira',
department: 3,
country: 1,
}))
expect(api.getMunicipalities).toHaveBeenCalledTimes(2)
})
})

View File

@@ -0,0 +1,117 @@
import { describe, expect, it, vi } from 'vitest'
import { flushPromises, mount } from '@vue/test-utils'
import OrganizationsManagement from '@/components/provenance/admin/OrganizationsManagement.vue'
import vuetify from '@/plugins/vuetify'
import { clickBody, setBodyInput } from './helpers'
const organizations = [
{
id: 1,
name: 'Red de Economía Solidaria',
description: 'Red de organizaciones',
website: 'https://red.example.org',
contact_email: 'contacto@red.example.org',
contact_phone: '3001112233',
},
{
id: 2,
name: 'Cooperativa El Campo',
description: null,
website: null,
contact_email: null,
contact_phone: null,
},
]
function mockApi () {
return {
getOrganizations: vi.fn().mockResolvedValue(organizations),
createOrganization: vi.fn().mockResolvedValue({}),
updateOrganization: vi.fn().mockResolvedValue({}),
deleteOrganization: vi.fn().mockResolvedValue({}),
}
}
function mountComponent (api) {
return mount(OrganizationsManagement, {
global: { plugins: [vuetify], provide: { api } },
})
}
function fillForm () {
setBodyInput('[data-testid="org-form-name"] input', 'Fundación Montaña')
setBodyInput('[data-testid="org-form-description"] textarea', 'Fundación')
setBodyInput('[data-testid="org-form-website"] input', 'https://montana.example.org')
setBodyInput('[data-testid="org-form-email"] input', 'info@montana.example.org')
setBodyInput('[data-testid="org-form-phone"] input', '3109876543')
}
describe('OrganizationsManagement', () => {
it('carga y muestra las organizaciones', async () => {
const api = mockApi()
const wrapper = mountComponent(api)
await flushPromises()
expect(api.getOrganizations).toHaveBeenCalled()
expect(wrapper.text()).toContain('Red de Economía Solidaria')
expect(wrapper.text()).toContain('Cooperativa El Campo')
})
it('filtra las organizaciones por búsqueda', async () => {
const wrapper = mountComponent(mockApi())
await flushPromises()
await wrapper.find('[data-testid="org-search"] input').setValue('Cooperativa')
expect(wrapper.text()).toContain('Cooperativa El Campo')
expect(wrapper.text()).not.toContain('Red de Economía Solidaria')
})
it('crea una organización', async () => {
const api = mockApi()
const wrapper = mountComponent(api)
await flushPromises()
await wrapper.find('[data-testid="org-create"]').trigger('click')
fillForm()
clickBody('[data-testid="org-save"]')
await flushPromises()
expect(api.createOrganization).toHaveBeenCalledWith({
name: 'Fundación Montaña',
description: 'Fundación',
website: 'https://montana.example.org',
contact_email: 'info@montana.example.org',
contact_phone: '3109876543',
})
expect(api.getOrganizations).toHaveBeenCalledTimes(2)
})
it('edita una organización', async () => {
const api = mockApi()
const wrapper = mountComponent(api)
await flushPromises()
await wrapper.find('[data-testid="org-edit-1"]').trigger('click')
setBodyInput('[data-testid="org-form-name"] input', 'Red de Economía Solidaria Colombia')
clickBody('[data-testid="org-save"]')
await flushPromises()
expect(api.updateOrganization).toHaveBeenCalledWith(1, expect.objectContaining({
name: 'Red de Economía Solidaria Colombia',
}))
})
it('elimina una organización', async () => {
const api = mockApi()
const wrapper = mountComponent(api)
await flushPromises()
await wrapper.find('[data-testid="org-delete-2"]').trigger('click')
clickBody('[data-testid="org-confirm-delete"]')
await flushPromises()
expect(api.deleteOrganization).toHaveBeenCalledWith(2)
expect(api.getOrganizations).toHaveBeenCalledTimes(2)
})
})

View File

@@ -0,0 +1,125 @@
import { describe, expect, it, vi } from 'vitest'
import { flushPromises, mount } from '@vue/test-utils'
import SuppliersManagement from '@/components/provenance/admin/SuppliersManagement.vue'
import vuetify from '@/plugins/vuetify'
import { clickBody, setBodyInput } from './helpers'
const organizations = [
{ id: 3, name: 'Red de Economía Solidaria' },
{ id: 4, name: 'Cooperativa El Campo' },
]
const municipalities = [
{ id: 7, name: 'La Mesa' },
{ id: 8, name: 'San Antonio' },
]
const suppliers = [
{
id: 5,
name: 'Asociación Agropecuaria La Mesa',
description: 'Cooperativa de campesinos',
organization: 3,
organization_detail: { id: 3, name: 'Red de Economía Solidaria' },
municipality: 7,
municipality_detail: { id: 7, name: 'La Mesa' },
contact_email: 'contacto@agro.example.org',
contact_phone: '300 123 4567',
},
{
id: 6,
name: 'Finca El Paraíso',
description: null,
organization: null,
organization_detail: null,
municipality: null,
municipality_detail: null,
contact_email: null,
contact_phone: null,
},
]
function mockApi () {
return {
getSuppliers: vi.fn().mockResolvedValue(suppliers),
getOrganizations: vi.fn().mockResolvedValue(organizations),
getMunicipalities: vi.fn().mockResolvedValue(municipalities),
createSupplier: vi.fn().mockResolvedValue({}),
updateSupplier: vi.fn().mockResolvedValue({}),
deleteSupplier: vi.fn().mockResolvedValue({}),
}
}
function mountComponent (api) {
return mount(SuppliersManagement, {
global: { plugins: [vuetify], provide: { api } },
})
}
describe('SuppliersManagement', () => {
it('carga y muestra los proveedores con su organización y municipio', async () => {
const api = mockApi()
const wrapper = mountComponent(api)
await flushPromises()
expect(api.getSuppliers).toHaveBeenCalled()
expect(wrapper.text()).toContain('Asociación Agropecuaria La Mesa')
expect(wrapper.text()).toContain('Red de Economía Solidaria')
expect(wrapper.text()).toContain('La Mesa')
})
it('crea un proveedor con organización y municipio', async () => {
const api = mockApi()
const wrapper = mountComponent(api)
await flushPromises()
await wrapper.find('[data-testid="supplier-create"]').trigger('click')
setBodyInput('[data-testid="supplier-form-name"] input', 'Asociación El Cedro')
setBodyInput('[data-testid="supplier-form-description"] textarea', 'Caficultores')
const orgSelect = wrapper.findAllComponents({ name: 'VSelect' })
.find(select => select.props('items').some(item => item.id === 4))
await orgSelect.vm.$emit('update:modelValue', 4)
await wrapper.findAllComponents({ name: 'VAutocomplete' })[0].vm.$emit('update:modelValue', 8)
clickBody('[data-testid="supplier-save"]')
await flushPromises()
expect(api.createSupplier).toHaveBeenCalledWith(expect.objectContaining({
name: 'Asociación El Cedro',
description: 'Caficultores',
organization: 4,
municipality: 8,
}))
expect(api.getSuppliers).toHaveBeenCalledTimes(2)
})
it('edita un proveedor conservando la organización seleccionada', async () => {
const api = mockApi()
const wrapper = mountComponent(api)
await flushPromises()
await wrapper.find('[data-testid="supplier-edit-5"]').trigger('click')
setBodyInput('[data-testid="supplier-form-name"] input', 'Asociación Agropecuaria La Mesa Renovada')
clickBody('[data-testid="supplier-save"]')
await flushPromises()
expect(api.updateSupplier).toHaveBeenCalledWith(5, expect.objectContaining({
name: 'Asociación Agropecuaria La Mesa Renovada',
organization: 3,
municipality: 7,
}))
})
it('elimina un proveedor', async () => {
const api = mockApi()
const wrapper = mountComponent(api)
await flushPromises()
await wrapper.find('[data-testid="supplier-delete-6"]').trigger('click')
clickBody('[data-testid="supplier-confirm-delete"]')
await flushPromises()
expect(api.deleteSupplier).toHaveBeenCalledWith(6)
expect(api.getSuppliers).toHaveBeenCalledTimes(2)
})
})

View File

@@ -0,0 +1,23 @@
// Helpers para interactuar con el contenido de diálogos, que Vuetify
// teleporta a document.body fuera del DOM del wrapper.
function setNativeValue (el, value) {
const proto = el.tagName === 'TEXTAREA'
? window.HTMLTextAreaElement.prototype
: window.HTMLInputElement.prototype
const setter = Object.getOwnPropertyDescriptor(proto, 'value').set
setter.call(el, value)
el.dispatchEvent(new Event('input', { bubbles: true }))
}
export function setBodyInput (selector, value) {
const el = document.body.querySelector(selector)
if (!el) throw new Error(`Input no encontrado en body: ${selector}`)
setNativeValue(el, value)
}
export function clickBody (selector) {
const el = document.body.querySelector(selector)
if (!el) throw new Error(`Elemento no encontrado en body: ${selector}`)
el.click()
}