Documentación interna
Partes de Orden de Compra

🧾 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