// @vitest-environment jsdom import { afterEach, describe, expect, it, vi } from 'vitest' import { cleanup, fireEvent, render, screen } from '@testing-library/react' import { TablaCaptura } from './TablaCaptura' import type { CampoDef, DatosCaptura } from '../services/captura' const CAMPOS_OBRA: CampoDef[] = [ { campo: 'certificacion_gas', tipo: 'boolean', label: 'Certificación Gas' }, { campo: 'comentarios_obra', tipo: 'text', label: 'Comentarios' }, ] const CAMPOS_VENTAS: CampoDef[] = [ { campo: 'estatus', tipo: 'select', label: 'Estatus', options: ['DISPONIBLE', 'APARTADO', 'VENDIDO'] }, { campo: 'precio_venta', tipo: 'number', label: 'Precio Venta' }, ] const DATOS: DatosCaptura = { '409-1': { certificacion_gas: 'true', comentarios_obra: 'Listo' }, '409-2': { certificacion_gas: null, comentarios_obra: null }, } const UNIDADES = ['409-1', '409-2'] afterEach(() => { cleanup() }) describe('TablaCaptura', () => { it('renderiza la tabla con unidades y campos', () => { const onGuardar = vi.fn() render( , ) expect(screen.getByText('409-1')).toBeTruthy() expect(screen.getByText('409-2')).toBeTruthy() expect(screen.getByText('Certificación Gas')).toBeTruthy() expect(screen.getByText('Comentarios')).toBeTruthy() }) it('muestra checkboxes para campos boolean', () => { const onGuardar = vi.fn() render( , ) const checkboxes = screen.getAllByRole('checkbox') expect(checkboxes.length).toBe(2) // 2 unidades * 1 boolean campo // Primera unidad tiene certificacion_gas = 'true' expect((checkboxes[0] as HTMLInputElement).checked).toBe(true) // Segunda unidad tiene null expect((checkboxes[1] as HTMLInputElement).checked).toBe(false) }) it('muestra select para campos select', () => { const onGuardar = vi.fn() render( , ) const selects = screen.getAllByRole('combobox') expect(selects.length).toBe(2) // 2 unidades * 1 select campo }) it('muestra "sin unidades" cuando la lista está vacía', () => { const onGuardar = vi.fn() render( , ) expect(screen.getByText(/No hay unidades/)).toBeTruthy() }) it('edita una celda y muestra el botón de guardar', () => { const onGuardar = vi.fn() render( , ) // Click en el checkbox de la segunda unidad (cambiar de unchecked a checked) const checkboxes = screen.getAllByRole('checkbox') fireEvent.click(checkboxes[1]) // Debería aparecer "Hay cambios sin guardar" expect(screen.getByText('Hay cambios sin guardar')).toBeTruthy() expect(screen.getByText('Guardar cambios')).toBeTruthy() expect(screen.getByText('Descartar')).toBeTruthy() }) it('click en Guardar llama a onGuardar con los cambios dirty', () => { const onGuardar = vi.fn() render( , ) // Cambiar checkbox de la segunda unidad const checkboxes = screen.getAllByRole('checkbox') fireEvent.click(checkboxes[1]) // Click en Guardar fireEvent.click(screen.getByText('Guardar cambios')) expect(onGuardar).toHaveBeenCalledTimes(1) const dirty = onGuardar.mock.calls[0][0] expect(dirty['409-2']).toEqual({ certificacion_gas: 'true' }) }) it('click en Descartar elimina los cambios', () => { const onGuardar = vi.fn() render( , ) // Cambiar checkbox const checkboxes = screen.getAllByRole('checkbox') fireEvent.click(checkboxes[1]) // Descartar fireEvent.click(screen.getByText('Descartar')) // Ya no debería mostrar "Hay cambios sin guardar" expect(screen.queryByText('Hay cambios sin guardar')).toBeNull() }) it('filtra por estatus', () => { const onGuardar = vi.fn() const datosConEstatus: DatosCaptura = { '409-1': { estatus: 'VENDIDO' }, '409-2': { estatus: 'DISPONIBLE' }, } render( , ) expect(screen.getByText('409-1')).toBeTruthy() expect(screen.queryByText('409-2')).toBeNull() }) it('paginación: muestra paginación cuando hay >50 unidades', () => { const onGuardar = vi.fn() const unidadesGrandes = Array.from({ length: 60 }, (_, i) => `U-${String(i + 1).padStart(3, '0')}`) render( , ) expect(screen.getByText(/Mostrando 1–50 de 60/)).toBeTruthy() expect(screen.getByText('Anterior')).toBeTruthy() expect(screen.getByText('Siguiente')).toBeTruthy() // Navigate to page 2 fireEvent.click(screen.getByText('Siguiente')) expect(screen.getByText(/Mostrando 51–60 de 60/)).toBeTruthy() }) it('boolean con conFecha: fecha + checkbox en una celda, semáforo y dirty del campo fecha', () => { const onGuardar = vi.fn() const camposEmparejados: CampoDef[] = [ { campo: 'certificacion_gas', tipo: 'boolean', label: 'Certificación Gas', conFecha: 'certificacion_gas_fecha' }, { campo: 'certificacion_gas_fecha', tipo: 'date', label: 'Fecha Certificación Gas' }, ] const datosEmparejados: DatosCaptura = { // Check activo → fecha en verde '409-1': { certificacion_gas: 'true', certificacion_gas_fecha: '2026-01-01' }, // Fecha pasada sin check → fecha en rojo '409-2': { certificacion_gas: null, certificacion_gas_fecha: '2026-01-01' }, } const { container } = render( , ) // La columna standalone de fecha se oculta: solo Unidad + Certificación Gas expect(screen.getAllByRole('columnheader')).toHaveLength(2) expect(screen.queryByText('Fecha Certificación Gas')).toBeNull() // Un date input y un checkbox por unidad const fechas = container.querySelectorAll('input[type="date"]') expect(fechas).toHaveLength(2) expect((fechas[0] as HTMLInputElement).value).toBe('2026-01-01') // Semáforo: check activo → verde; fecha pasada sin check → rojo expect(fechas[0].className).toContain('text-green-600') expect(fechas[1].className).toContain('text-red-600') // Cambiar la fecha marca dirty en el campo _fecha y se guarda en el bulk fireEvent.change(fechas[1], { target: { value: '2026-12-01' } }) // El input muestra el valor recién elegido (no se resetea al guardado) expect((fechas[1] as HTMLInputElement).value).toBe('2026-12-01') fireEvent.click(screen.getByText('Guardar cambios')) expect(onGuardar).toHaveBeenCalledTimes(1) const dirty = onGuardar.mock.calls[0][0] expect(dirty['409-2']).toEqual({ certificacion_gas_fecha: '2026-12-01' }) }) it('regresión: escribir en un campo text no se borra (valor viene de dirty)', () => { const onGuardar = vi.fn() const { container } = render( , ) // Teclear en comentarios de la primera unidad (original: 'Listo') const textos = container.querySelectorAll('input[type="text"]') expect(textos.length).toBe(2) // 1 campo text * 2 unidades fireEvent.change(textos[0], { target: { value: 'Nota X' } }) expect((textos[0] as HTMLInputElement).value).toBe('Nota X') // Guardar persiste el texto tecleado fireEvent.click(screen.getByText('Guardar cambios')) expect(onGuardar).toHaveBeenCalledTimes(1) expect(onGuardar.mock.calls[0][0]['409-1']).toEqual({ comentarios_obra: 'Nota X' }) }) it('fechas de Obra guardadas: disabled para no-admin, editables para admin', () => { const camposEmparejados: CampoDef[] = [ { campo: 'certificacion_gas', tipo: 'boolean', label: 'Certificación Gas', conFecha: 'certificacion_gas_fecha' }, { campo: 'certificacion_gas_fecha', tipo: 'date', label: 'Fecha Certificación Gas' }, ] const datosConFecha: DatosCaptura = { '409-1': { certificacion_gas: null, certificacion_gas_fecha: '2026-01-01' }, '409-2': { certificacion_gas: null, certificacion_gas_fecha: null }, } // No-admin: la fecha guardada se deshabilita, la vacía sigue editable const r1 = render( , ) const fechas1 = r1.container.querySelectorAll('input[type="date"]') expect((fechas1[0] as HTMLInputElement).disabled).toBe(true) expect((fechas1[1] as HTMLInputElement).disabled).toBe(false) r1.unmount() // Admin: todas editables const r2 = render( , ) const fechas2 = r2.container.querySelectorAll('input[type="date"]') expect((fechas2[0] as HTMLInputElement).disabled).toBe(false) expect((fechas2[1] as HTMLInputElement).disabled).toBe(false) }) })