Registro de horas: estado de pago por partida (por pagar / pagada)
Permite separar las horas ya cobradas de las pendientes para que los totales no se
acumulen y poder emitir la nota por pagar al cliente y conservar el recibo de lo pagado.
- Modelo: RegistroHoras.estadoPago ("por_pagar" default | "pagada") + fechaPago
(se sella al marcar pagada, se limpia al revertir). Migración idempotente.
- API: PATCH /api/cotizaciones/[id]/horas/[registroId] para marcar por pagar/pagada.
- calculators: ESTADOS_PAGO_HORAS + resumenPagoHoras() que suma por separado
pendiente vs pagado.
- Panel: resumen "Por pagar" vs "Pagado" siempre visible (con IVA si aplica),
badge/toggle de estado por fila, filtro de estado (Por pagar/Pagadas/Todas) que
además define el documento: "Nota de horas por pagar", "Recibo de horas pagadas"
o "Estado de cuenta de horas".
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
8b1e420d4a
commit
d83a80c85a
@@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { registroHorasSchema } from "@/lib/schemas";
|
||||
import { registroHorasSchema, registroHorasEstadoSchema } from "@/lib/schemas";
|
||||
import { calcularHorasRango, fechaRegistroDesdeISO } from "@/lib/calculators";
|
||||
|
||||
// PUT /api/cotizaciones/:id/horas/:registroId
|
||||
@@ -54,6 +54,46 @@ export async function PUT(
|
||||
}
|
||||
}
|
||||
|
||||
// PATCH /api/cotizaciones/:id/horas/:registroId
|
||||
// Marca la partida como "por_pagar" o "pagada" sin tocar el resto. Al marcarla pagada
|
||||
// se sella la fechaPago; al regresarla a por_pagar se limpia.
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; registroId: string }> }
|
||||
) {
|
||||
try {
|
||||
const { id, registroId } = await params;
|
||||
const body = await request.json();
|
||||
|
||||
const parsed = registroHorasEstadoSchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json(
|
||||
{ error: parsed.error.issues.map((i) => i.message).join(", ") },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
const { estadoPago } = parsed.data;
|
||||
|
||||
const existing = await prisma.registroHoras.findUnique({ where: { id: registroId } });
|
||||
if (!existing || existing.cotizacionId !== id) {
|
||||
return NextResponse.json({ error: "Registro no encontrado" }, { status: 404 });
|
||||
}
|
||||
|
||||
const registro = await prisma.registroHoras.update({
|
||||
where: { id: registroId },
|
||||
data: {
|
||||
estadoPago,
|
||||
fechaPago: estadoPago === "pagada" ? (existing.fechaPago ?? new Date()) : null,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(registro);
|
||||
} catch (error: unknown) {
|
||||
const msg = error instanceof Error ? error.message : "Error interno";
|
||||
return NextResponse.json({ error: msg }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/cotizaciones/:id/horas/:registroId
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
|
||||
Reference in New Issue
Block a user