feat(captura): fechas de Obra bloqueadas tras establecerse, solo admin las cambia
This commit is contained in:
@@ -148,7 +148,7 @@ describe('guardarEndpoint (POST /api/captura)', () => {
|
||||
res,
|
||||
)
|
||||
expect(status).toHaveBeenCalledWith(204)
|
||||
expect(capturaService.guardarCampo).toHaveBeenCalledWith('SENDA', 'U-101', 'VENTAS', 'estatus', 'VENDIDO', 'u1')
|
||||
expect(capturaService.guardarCampo).toHaveBeenCalledWith('SENDA', 'U-101', 'VENTAS', 'estatus', 'VENDIDO', 'u1', 'admin')
|
||||
})
|
||||
|
||||
it('rechaza departamento inválido', async () => {
|
||||
@@ -190,7 +190,7 @@ describe('bulkEndpoint (POST /api/captura/bulk)', () => {
|
||||
expect(capturaService.guardarBulk).toHaveBeenCalledWith('SENDA', 'VENTAS', [
|
||||
{ unidad: 'U-101', campo: 'estatus', valor: 'VENDIDO' },
|
||||
{ unidad: 'U-102', campo: 'estatus', valor: 'DISPONIBLE' },
|
||||
], 'u1')
|
||||
], 'u1', 'admin')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ export async function guardarEndpoint(req: Request, res: Response): Promise<void
|
||||
body.campo,
|
||||
body.valor ?? null,
|
||||
usuarioId,
|
||||
req.usuarioToken?.rol ?? '',
|
||||
)
|
||||
res.status(204).end()
|
||||
}
|
||||
@@ -106,6 +107,7 @@ export async function bulkEndpoint(req: Request, res: Response): Promise<void> {
|
||||
body.departamento,
|
||||
body.registros.map((r) => ({ unidad: r.unidad, campo: r.campo, valor: r.valor ?? null })),
|
||||
usuarioId,
|
||||
req.usuarioToken?.rol ?? '',
|
||||
)
|
||||
res.status(204).end()
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('obtenerCapturaUnidad', () => {
|
||||
describe('guardarCampo', () => {
|
||||
it('hace upsert del campo', async () => {
|
||||
prismaMock.capturaDepartamento.upsert.mockResolvedValue({})
|
||||
await guardarCampo('SENDA', 'U-101', 'VENTAS', 'estatus', 'VENDIDO', 'user-1')
|
||||
await guardarCampo('SENDA', 'U-101', 'VENTAS', 'estatus', 'VENDIDO', 'user-1', 'admin')
|
||||
expect(prismaMock.capturaDepartamento.upsert).toHaveBeenCalledWith({
|
||||
where: {
|
||||
desarrollo_unidadCodigo_departamento_campo: {
|
||||
@@ -122,7 +122,7 @@ describe('guardarCampo', () => {
|
||||
|
||||
it('permite valor null', async () => {
|
||||
prismaMock.capturaDepartamento.upsert.mockResolvedValue({})
|
||||
await guardarCampo('SENDA', 'U-101', 'OBRA', 'comentarios_obra', null, 'user-1')
|
||||
await guardarCampo('SENDA', 'U-101', 'OBRA', 'comentarios_obra', null, 'user-1', 'admin')
|
||||
expect(prismaMock.capturaDepartamento.upsert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
update: expect.objectContaining({ valor: null }),
|
||||
@@ -137,7 +137,7 @@ describe('guardarBulk', () => {
|
||||
await guardarBulk('SENDA', 'VENTAS', [
|
||||
{ unidad: 'U-101', campo: 'estatus', valor: 'VENDIDO' },
|
||||
{ unidad: 'U-101', campo: 'modelo', valor: 'Casa A' },
|
||||
], 'user-1')
|
||||
], 'user-1', 'admin')
|
||||
expect(prismaMock.$transaction).toHaveBeenCalledTimes(1)
|
||||
expect(prismaMock.$transaction).toHaveBeenCalledWith(expect.arrayContaining([
|
||||
expect.any(Promise),
|
||||
@@ -145,6 +145,55 @@ describe('guardarBulk', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('fechas de Obra bloqueadas', () => {
|
||||
const CAMPO_FECHA = 'certificacion_gas_fecha'
|
||||
|
||||
it('admin puede modificar una fecha ya establecida', async () => {
|
||||
prismaMock.capturaDepartamento.findMany.mockResolvedValue([
|
||||
{ unidadCodigo: 'U-101', campo: CAMPO_FECHA, valor: '2026-01-01' },
|
||||
])
|
||||
prismaMock.capturaDepartamento.upsert.mockResolvedValue({})
|
||||
await guardarCampo('SENDA', 'U-101', 'OBRA', CAMPO_FECHA, '2026-02-02', 'user-1', 'admin')
|
||||
expect(prismaMock.capturaDepartamento.upsert).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('no-admin no puede modificar una fecha ya establecida', async () => {
|
||||
prismaMock.capturaDepartamento.findMany.mockResolvedValue([
|
||||
{ unidadCodigo: 'U-101', campo: CAMPO_FECHA, valor: '2026-01-01' },
|
||||
])
|
||||
await expect(
|
||||
guardarCampo('SENDA', 'U-101', 'OBRA', CAMPO_FECHA, '2026-02-02', 'user-1', 'obra'),
|
||||
).rejects.toThrow('solo puede modificarlas un administrador')
|
||||
expect(prismaMock.capturaDepartamento.upsert).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('no-admin puede establecer una fecha por primera vez', async () => {
|
||||
prismaMock.capturaDepartamento.findMany.mockResolvedValue([])
|
||||
prismaMock.capturaDepartamento.upsert.mockResolvedValue({})
|
||||
await guardarCampo('SENDA', 'U-101', 'OBRA', CAMPO_FECHA, '2026-03-03', 'user-1', 'obra')
|
||||
expect(prismaMock.capturaDepartamento.upsert).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('bulk: rechaza completo si algún registro cambia una fecha establecida (no-admin)', async () => {
|
||||
prismaMock.capturaDepartamento.findMany.mockResolvedValue([
|
||||
{ unidadCodigo: 'U-101', campo: CAMPO_FECHA, valor: '2026-01-01' },
|
||||
])
|
||||
await expect(
|
||||
guardarBulk(
|
||||
'SENDA',
|
||||
'OBRA',
|
||||
[
|
||||
{ unidad: 'U-101', campo: 'comentarios_obra', valor: 'ok' },
|
||||
{ unidad: 'U-101', campo: CAMPO_FECHA, valor: '2026-05-05' },
|
||||
],
|
||||
'user-1',
|
||||
'obra',
|
||||
),
|
||||
).rejects.toThrow('solo puede modificarlas un administrador')
|
||||
expect(prismaMock.$transaction).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('resumenAvance', () => {
|
||||
it('calcula porcentaje de avance por departamento', async () => {
|
||||
// Mock para count (campos llenos)
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
import { prisma } from './prisma.js'
|
||||
import { CAMPOS_POR_DEPARTAMENTO, DEPARTAMENTOS } from '../config/campos-departamento.js'
|
||||
import { AppError } from '../utils/errors.js'
|
||||
|
||||
/** Fechas de Obra emparejadas a checkboxes: tras establecerse solo las cambia un admin */
|
||||
const CAMPOS_FECHA_OBRA = new Set(
|
||||
(CAMPOS_POR_DEPARTAMENTO.OBRA ?? [])
|
||||
.filter((c) => c.conFecha)
|
||||
.map((c) => c.conFecha as string),
|
||||
)
|
||||
|
||||
/** Bloquear modificación de fechas de Obra ya establecidas (solo admin) */
|
||||
async function validarFechasObra(
|
||||
desarrollo: string,
|
||||
departamento: string,
|
||||
registros: { unidad: string; campo: string; valor: string | null }[],
|
||||
rol: string,
|
||||
): Promise<void> {
|
||||
if (rol === 'admin' || departamento !== 'OBRA') return
|
||||
const candidatos = registros.filter((r) => CAMPOS_FECHA_OBRA.has(r.campo))
|
||||
if (candidatos.length === 0) return
|
||||
|
||||
const existentes = await prisma.capturaDepartamento.findMany({
|
||||
where: {
|
||||
desarrollo,
|
||||
departamento,
|
||||
unidadCodigo: { in: [...new Set(candidatos.map((r) => r.unidad))] },
|
||||
campo: { in: candidatos.map((r) => r.campo) },
|
||||
},
|
||||
})
|
||||
const previos = new Map(existentes.map((e) => [`${e.unidadCodigo}|${e.campo}`, e.valor]))
|
||||
|
||||
for (const r of candidatos) {
|
||||
const previo = previos.get(`${r.unidad}|${r.campo}`)
|
||||
if (previo != null && previo !== r.valor) {
|
||||
throw new AppError(403, 'Las fechas ya establecidas solo puede modificarlas un administrador')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Obtener unidades únicas de un desarrollo ──────────────────────────
|
||||
|
||||
@@ -67,7 +104,10 @@ export async function guardarCampo(
|
||||
campo: string,
|
||||
valor: string | null,
|
||||
usuarioId: string,
|
||||
rol: string,
|
||||
): Promise<void> {
|
||||
await validarFechasObra(desarrollo, departamento, [{ unidad: unidadCodigo, campo, valor }], rol)
|
||||
|
||||
const tipo = CAMPOS_POR_DEPARTAMENTO[departamento]?.find((c) => c.campo === campo)?.tipo ?? 'text'
|
||||
|
||||
await prisma.capturaDepartamento.upsert({
|
||||
@@ -91,7 +131,10 @@ export async function guardarBulk(
|
||||
departamento: string,
|
||||
registros: { unidad: string; campo: string; valor: string | null }[],
|
||||
usuarioId: string,
|
||||
rol: string,
|
||||
): Promise<void> {
|
||||
await validarFechasObra(desarrollo, departamento, registros, rol)
|
||||
|
||||
const tipoPorCampo = new Map(
|
||||
(CAMPOS_POR_DEPARTAMENTO[departamento] ?? []).map((c) => [c.campo, c.tipo]),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user