feat(captura): fechas de Obra bloqueadas tras establecerse, solo admin las cambia

This commit is contained in:
2026-09-12 07:52:40 +00:00
parent 1c81331614
commit d2e97f33f5
8 changed files with 175 additions and 10 deletions

View File

@@ -277,4 +277,46 @@ describe('TablaCaptura', () => {
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(
<TablaCaptura
unidades={UNIDADES}
campos={camposEmparejados}
datos={datosConFecha}
onGuardar={vi.fn()}
guardando={false}
puedeEditarFechas={false}
/>,
)
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(
<TablaCaptura
unidades={UNIDADES}
campos={camposEmparejados}
datos={datosConFecha}
onGuardar={vi.fn()}
guardando={false}
puedeEditarFechas
/>,
)
const fechas2 = r2.container.querySelectorAll('input[type="date"]')
expect((fechas2[0] as HTMLInputElement).disabled).toBe(false)
expect((fechas2[1] as HTMLInputElement).disabled).toBe(false)
})
})

View File

@@ -13,6 +13,8 @@ interface Props {
guardando: boolean
/** Filtro de estatus de unidad (columna estestatus en VENTAS) */
filtroEstatus?: string
/** Si es false, las fechas de Obra ya guardadas no se pueden editar (solo admin) */
puedeEditarFechas?: boolean
}
const POR_PAGINA = 50
@@ -57,6 +59,8 @@ interface CeldaProps {
valorFecha?: string | null
dirtyFecha?: boolean
onFechaChange?: (nuevo: string | null) => void
/** Fecha ya establecida y usuario sin permiso de edición */
fechaBloqueada?: boolean
}
function CeldaEditable({
@@ -69,6 +73,7 @@ function CeldaEditable({
valorFecha,
dirtyFecha,
onFechaChange,
fechaBloqueada,
}: CeldaProps) {
const bordeDirty = dirty ? 'ring-2 ring-accent/40' : ''
@@ -88,8 +93,14 @@ function CeldaEditable({
<input
type="date"
value={valorFecha ?? ''}
disabled={fechaBloqueada}
onChange={(e) => onFechaChange?.(e.target.value || null)}
className={`w-28 border-0 bg-transparent px-1 py-0.5 text-xs focus:outline-none [color-scheme:light] dark:[color-scheme:dark] ${colorFecha} ${bordeFecha}`}
title={
fechaBloqueada
? 'Fecha ya establecida — solo un administrador puede modificarla'
: undefined
}
className={`w-28 border-0 bg-transparent px-1 py-0.5 text-xs focus:outline-none [color-scheme:light] dark:[color-scheme:dark] disabled:cursor-not-allowed ${colorFecha} ${bordeFecha}`}
/>
)}
<input
@@ -159,6 +170,7 @@ export function TablaCaptura({
onGuardar,
guardando,
filtroEstatus,
puedeEditarFechas = true,
}: Props) {
const [dirty, setDirty] = useState<DirtyMap>({})
const [pagina, setPagina] = useState(0)
@@ -314,6 +326,12 @@ export function TablaCaptura({
campoFecha ? valorEfectivo(dirty, datos, unidad, campoFecha) : undefined
}
dirtyFecha={campoFecha ? dirty[unidad]?.[campoFecha] !== undefined : false}
fechaBloqueada={
campoFecha
? !puedeEditarFechas &&
valorOriginal(datos, unidad, campoFecha) != null
: false
}
onFechaChange={
campoFecha
? (nuevo) => handleChange(unidad, campoFecha, nuevo)

View File

@@ -87,8 +87,12 @@ export function CapturaPage({ onVolver }: Props) {
await guardarBulk({ desarrollo, departamento, registros })
toast.success('Cambios guardados')
await cargar()
} catch {
toast.error('No se pudieron guardar los cambios')
} catch (e) {
toast.error(
e instanceof Error && e.message && !e.message.startsWith('HTTP')
? e.message
: 'No se pudieron guardar los cambios',
)
} finally {
setGuardando(false)
}
@@ -215,6 +219,7 @@ export function CapturaPage({ onVolver }: Props) {
onGuardar={handleGuardar}
guardando={guardando}
filtroEstatus={filtroEstatus}
puedeEditarFechas={rol === 'admin'}
/>
)}
</section>

View File

@@ -161,7 +161,10 @@ export async function guardarCampo(data: {
}),
)
manejar401(resp)
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
if (!resp.ok) {
const cuerpo = (await resp.json().catch(() => null)) as { error?: string } | null
throw new Error(cuerpo?.error ?? `HTTP ${resp.status}`)
}
}
/** POST /api/captura/bulk — guardar múltiples campos */
@@ -183,5 +186,8 @@ export async function guardarBulk(data: {
}),
)
manejar401(resp)
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
if (!resp.ok) {
const cuerpo = (await resp.json().catch(() => null)) as { error?: string } | null
throw new Error(cuerpo?.error ?? `HTTP ${resp.status}`)
}
}