🧾 Partes de Orden de Compra (updatePartes)
🧠 Concepto general
El endpoint updatePartes permite sincronizar el estado completo de las partes de una orden de compra.
👉 No existen endpoints separados para:
- crear parte
- editar parte
- eliminar parte
Todo se resuelve enviando el array completo de partes.
📌 Endpoint
POST /ordenes_de_compra/{id}/updatePartes
| Parámetro | Tipo | Descripción |
|---|---|---|
id |
number | ID de la orden de compra |
⚠️ Funcionamiento general
El frontend siempre debe enviar el snapshot completo actual de partes.
| Caso | Resultado |
|---|---|
Parte con id |
Se actualiza |
Parte sin id |
Se crea |
| Parte existente que no vino en el array | Se elimina |
partes: [] |
La OC queda sin partes |
🔑 Campo key
Cada parte debe tener un campo key.
Este campo debe generarse en frontend y sirve para identificar filas nuevas antes de tener un id real de base de datos.
Se recomienda utilizar UUID.
Ejemplo:
{
"key": "f8b94a91-8a3f-4e8a-a66f-9c1b2d4a1234"
}
📥 Payload
{
"partes": [
{
"id": 15,
"key": "f8b94a91-8a3f-4e8a-a66f-9c1b2d4a1234",
"numeroParte": 1,
"descripcion": "Primera entrega",
"montoTotal": 50000,
"moneda": "Peso Argentino",
"estado": "FACTURADO",
"remitoNumero": "R-0001",
"remitoFecha": "2026-05-13 10:00:00",
"facturado": true,
"facturaNumero": "FC-0001-00001234",
"facturaFecha": "2026-05-14 10:00:00",
"pagado": false,
"fechaPago": null,
"montoPagado": null,
"observaciones": "Factura presentada"
}
]
}
📤 Respuesta
La respuesta devuelve el estado oficial final persistido por el backend.
👉 El frontend debe utilizar esta respuesta como fuente de verdad.
{
"ok": true,
"idOrdenDeCompra": 123,
"ordenDeCompra": {
"id": 123,
"dividida": true,
"estado": "FACTURADO",
"compraEstado": "TOTAL",
"entregaEstado": "PENDIENTE",
"facturado": true,
"pagado": false,
"montoPagado": 0
},
"partes": [
{
"id": 15,
"key": "f8b94a91-8a3f-4e8a-a66f-9c1b2d4a1234"
}
]
}
🧩 Uso recomendado en React
setOrden((current) => ({
...current,
...response.ordenDeCompra,
}));
setPartes((current) =>
current.map((parte) => {
const updated = response.partes.find(
(p) => p.key === parte.key
);
return updated
? {
...parte,
id: updated.id,
}
: parte;
})
);
➕ Crear partes
Para crear partes nuevas, enviar objetos sin id.
{
"partes": [
{
"key": "a1",
"numeroParte": 1,
"descripcion": "Parte 1",
"montoTotal": 50000,
"estado": "PENDIENTE",
"facturado": false,
"pagado": false
}
]
}
✏️ Actualizar partes
Para actualizar una parte, enviar su id.
{
"partes": [
{
"id": 15,
"key": "a1",
"numeroParte": 1,
"descripcion": "Parte actualizada",
"montoTotal": 75000,
"estado": "FACTURADO",
"facturado": true,
"pagado": false
}
]
}
🗑️ Eliminar partes
Para eliminar una parte, simplemente no incluirla en el array enviado.
🚫 Eliminar todas las partes
{
"partes": []
}
⚠️ Importante sobre montos
La suma de partes no necesita coincidir con el monto total original de la orden de compra.
❌ Posibles errores
Orden no encontrada
{
"error": "Orden de compra no encontrada"
}
ID inválido
{
"error": "id inválido"
}
Error por numeroParte duplicado
{
"error": "Duplicate entry '123-1' for key 'uq_oc_parte'"
}
Error por key duplicada
{
"error": "Duplicate entry 'uuid' for key 'uq_oc_partes_key'"
}
📘 Tipos TypeScript
export type OrdenDeCompraParteEstado =
| "PENDIENTE"
| "ENTREGADO"
| "FACTURADO"
| "PAGADO"
| "CANCELADO";
export interface OrdenDeCompraParteInput {
id?: number;
key: string;
numeroParte: number;
descripcion?: string | null;
montoTotal: number;
moneda?: string | null;
estado: OrdenDeCompraParteEstado;
remitoNumero?: string | null;
remitoFecha?: string | null;
facturado: boolean;
facturaNumero?: string | null;
facturaFecha?: string | null;
pagado: boolean;
fechaPago?: string | null;
montoPagado?: number | null;
observaciones?: string | null;
}
export interface OrdenDeCompraResumenResponse {
id: number;
dividida: boolean;
estado: string;
compraEstado: string;
entregaEstado: string;
facturado: boolean;
pagado: boolean;
montoPagado: number | null;
}
export interface UpdatePartesRequest {
partes: OrdenDeCompraParteInput[];
}
export interface UpdatePartesResponseParte {
id: number;
key: string;
}
export interface UpdatePartesResponse {
ok: boolean;
idOrdenDeCompra: number;
ordenDeCompra: OrdenDeCompraResumenResponse;
partes: UpdatePartesResponseParte[];
}
🧑💻 Helper createGuid
export function createGuid(): string {
if (
typeof crypto !== "undefined"
&& crypto.randomUUID
) {
return crypto.randomUUID();
}
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
(c) => {
const r = Math.random() * 16 | 0;
const v = c === "x"
? r
: (r & 0x3 | 0x8);
return v.toString(16);
}
);
}
➕ Helper para crear una parte vacía
export function createEmptyParte(
numeroParte: number
): OrdenDeCompraParteInput {
return {
key: createGuid(),
numeroParte,
descripcion: null,
montoTotal: 0,
moneda: null,
estado: "PENDIENTE",
remitoNumero: null,
remitoFecha: null,
facturado: false,
facturaNumero: null,
facturaFecha: null,
pagado: false,
fechaPago: null,
montoPagado: null,
observaciones: null,
};
}
🔄 Ejemplo con Axios
import axios from "axios";
export async function updateOrdenDeCompraPartes(
idOrdenDeCompra: number,
partes: OrdenDeCompraParteInput[]
): Promise<UpdatePartesResponse> {
const response = await axios.post<UpdatePartesResponse>(
`/ordenes_de_compra/${idOrdenDeCompra}/updatePartes`,
{ partes }
);
return response.data;
}
✅ Resumen rápido
Crear:
- enviar sin id
Actualizar:
- enviar con id
Eliminar:
- no enviar en el array
Eliminar todas:
- enviar partes: []
El backend devuelve:
- estado actualizado de la OC
- partes finales persistidas