From de07975d4a8a1ac99af3502f6c5b07bfc1e925c3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 19:27:24 +0000 Subject: [PATCH] fix: abrir documentos de proyecto enviando doc_id a la RPC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La API pedía el PDF con { id } y fn_project_document_get leía payload->>'doc_id', así que el id llegaba vacío aunque el archivo ya estuviera en el bucket. Mismo desajuste en empresa y trabajador. Co-authored-by: alberto.martinez --- api/document_rpc.ts | 10 ++ api/document_rpc_test.ts | 13 +++ api/main.ts | 14 ++- db/core/changelog-master.xml | 1 + .../changesets/032-rpc-document-get-id.sql | 99 +++++++++++++++++++ 5 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 api/document_rpc.ts create mode 100644 api/document_rpc_test.ts create mode 100644 db/core/changesets/032-rpc-document-get-id.sql diff --git a/api/document_rpc.ts b/api/document_rpc.ts new file mode 100644 index 0000000..7634a94 --- /dev/null +++ b/api/document_rpc.ts @@ -0,0 +1,10 @@ +/** + * Payload para core.fn_*_document_get. + * Las RPC leen `doc_id`; historicamente la API enviaba `id` y el documento + * nunca se encontraba (mensaje "documento id= no encontrado"). + */ +export function normalizeDocumentGetPayload( + payload: Record, +): Record { + return { ...payload, doc_id: payload.doc_id ?? payload.id }; +} diff --git a/api/document_rpc_test.ts b/api/document_rpc_test.ts new file mode 100644 index 0000000..8bcec8d --- /dev/null +++ b/api/document_rpc_test.ts @@ -0,0 +1,13 @@ +import { assertEquals } from "jsr:@std/assert@1"; +import { normalizeDocumentGetPayload } from "./document_rpc.ts"; + +Deno.test("document get payload usa doc_id cuando la API envía id", () => { + const payload = normalizeDocumentGetPayload({ project_id: 1, id: 42 }); + assertEquals(payload.doc_id, 42); + assertEquals(payload.project_id, 1); +}); + +Deno.test("document get payload respeta doc_id si ya viene", () => { + const payload = normalizeDocumentGetPayload({ company_id: 3, doc_id: 7, id: 99 }); + assertEquals(payload.doc_id, 7); +}); diff --git a/api/main.ts b/api/main.ts index b400b4f..4617023 100644 --- a/api/main.ts +++ b/api/main.ts @@ -16,6 +16,7 @@ import { type Db, } from "./db.ts"; import { callCoreFn } from "./rpc.ts"; +import { normalizeDocumentGetPayload } from "./document_rpc.ts"; import { respondRpc, respondApiError, @@ -100,7 +101,12 @@ async function rpcDocumentForDownload( payload: Record, route: string, ): Promise<{ doc?: StorageDoc; envelope: RpcEnvelope }> { - const envelope = await callCoreFn<{ document: StorageDoc }>(db, fn, payload, { route }); + const envelope = await callCoreFn<{ document: StorageDoc }>( + db, + fn, + normalizeDocumentGetPayload(payload), + { route }, + ); if (!envelope.ok) return { envelope }; const doc = envelope.data?.document; if (!doc?.storage_name) { @@ -447,7 +453,7 @@ app.get("/v1/companies/:id/documents/:docId", ...requireCoreAuth, requirePermiss const { doc, envelope } = await rpcDocumentForDownload( db, "core.fn_company_document_get", - { company_id: companyId, id: docId }, + { company_id: companyId, doc_id: docId }, route, ); if (!doc) return respondRpc(c, envelope); @@ -689,7 +695,7 @@ app.get("/v1/projects/:id/documents/:docId", ...requireCoreAuth, requirePermissi const { doc, envelope } = await rpcDocumentForDownload( db, "core.fn_project_document_get", - { project_id: projectId, id: docId }, + { project_id: projectId, doc_id: docId }, route, ); if (!doc) return respondRpc(c, envelope); @@ -1237,7 +1243,7 @@ app.get("/v1/workers/:id/documents/:docId", ...requireCoreAuth, requirePermissio const { doc, envelope } = await rpcDocumentForDownload( db, "core.fn_worker_document_get", - { worker_id: workerId, id: docId }, + { worker_id: workerId, doc_id: docId }, route, ); if (!doc) return respondRpc(c, envelope); diff --git a/db/core/changelog-master.xml b/db/core/changelog-master.xml index ecb60c8..175385f 100644 --- a/db/core/changelog-master.xml +++ b/db/core/changelog-master.xml @@ -38,5 +38,6 @@ + diff --git a/db/core/changesets/032-rpc-document-get-id.sql b/db/core/changesets/032-rpc-document-get-id.sql new file mode 100644 index 0000000..e7e394f --- /dev/null +++ b/db/core/changesets/032-rpc-document-get-id.sql @@ -0,0 +1,99 @@ +--liquibase formatted sql +-- PANELS · core · fn_*_document_get acepta doc_id o id +-- La API HTTP enviaba { id } y las RPC leían payload->>'doc_id', dejando el id vacío. + +--changeset panel:core-032a-fn-project-document-get splitStatements:false +CREATE OR REPLACE FUNCTION core.fn_project_document_get(payload jsonb) +RETURNS jsonb +LANGUAGE plpgsql +SECURITY INVOKER +SET search_path = core +AS $$ +DECLARE + v_project_id bigint := NULLIF(payload->>'project_id', '')::bigint; + v_doc_id bigint := NULLIF(COALESCE(payload->>'doc_id', payload->>'id'), '')::bigint; + v_doc jsonb; +BEGIN + IF v_project_id IS NULL OR v_doc_id IS NULL THEN + RETURN core.rpc_err('VALIDATION', + 'fn_project_document_get: project_id y doc_id son obligatorios', + jsonb_build_object('fn', 'fn_project_document_get', 'project_id', v_project_id, 'doc_id', v_doc_id)); + END IF; + SELECT to_jsonb(d) INTO v_doc FROM project_documents d + WHERE d.id = v_doc_id AND d.project_id = v_project_id; + IF v_doc IS NULL THEN + RETURN core.rpc_err('NOT_FOUND', + format('fn_project_document_get: documento id=%s no encontrado en proyecto %s', v_doc_id, v_project_id), + jsonb_build_object('fn', 'fn_project_document_get', 'doc_id', v_doc_id, 'project_id', v_project_id)); + END IF; + RETURN core.rpc_ok(jsonb_build_object('document', v_doc), + format('Documento id=%s del proyecto %s obtenido', v_doc_id, v_project_id), + jsonb_build_object('fn', 'fn_project_document_get', 'doc_id', v_doc_id)); +EXCEPTION WHEN OTHERS THEN + RETURN core.rpc_from_exception('fn_project_document_get', SQLSTATE, SQLERRM); +END; +$$; + +--changeset panel:core-032b-fn-company-document-get splitStatements:false +CREATE OR REPLACE FUNCTION core.fn_company_document_get(payload jsonb) +RETURNS jsonb +LANGUAGE plpgsql +SECURITY INVOKER +SET search_path = core +AS $$ +DECLARE + v_company_id bigint := NULLIF(payload->>'company_id', '')::bigint; + v_doc_id bigint := NULLIF(COALESCE(payload->>'doc_id', payload->>'id'), '')::bigint; + v_doc jsonb; +BEGIN + IF v_company_id IS NULL OR v_doc_id IS NULL THEN + RETURN core.rpc_err('VALIDATION', + 'fn_company_document_get: company_id y doc_id son obligatorios', + jsonb_build_object('fn', 'fn_company_document_get', 'company_id', v_company_id, 'doc_id', v_doc_id)); + END IF; + SELECT to_jsonb(d) INTO v_doc FROM company_documents d + WHERE d.id = v_doc_id AND d.company_id = v_company_id; + IF v_doc IS NULL THEN + RETURN core.rpc_err('NOT_FOUND', + format('fn_company_document_get: documento id=%s no encontrado en empresa %s', v_doc_id, v_company_id), + jsonb_build_object('fn', 'fn_company_document_get', 'doc_id', v_doc_id, 'company_id', v_company_id)); + END IF; + RETURN core.rpc_ok(jsonb_build_object('document', v_doc), + format('Documento id=%s de la empresa %s obtenido', v_doc_id, v_company_id), + jsonb_build_object('fn', 'fn_company_document_get', 'doc_id', v_doc_id)); +EXCEPTION WHEN OTHERS THEN + RETURN core.rpc_from_exception('fn_company_document_get', SQLSTATE, SQLERRM); +END; +$$; + +--changeset panel:core-032c-fn-worker-document-get splitStatements:false +CREATE OR REPLACE FUNCTION core.fn_worker_document_get(payload jsonb) +RETURNS jsonb +LANGUAGE plpgsql +SECURITY INVOKER +SET search_path = core +AS $$ +DECLARE + v_worker_id bigint := NULLIF(payload->>'worker_id', '')::bigint; + v_doc_id bigint := NULLIF(COALESCE(payload->>'doc_id', payload->>'id'), '')::bigint; + v_doc jsonb; +BEGIN + IF v_worker_id IS NULL OR v_doc_id IS NULL THEN + RETURN core.rpc_err('VALIDATION', + 'fn_worker_document_get: worker_id y doc_id son obligatorios', + jsonb_build_object('fn', 'fn_worker_document_get', 'worker_id', v_worker_id, 'doc_id', v_doc_id)); + END IF; + SELECT to_jsonb(d) INTO v_doc FROM documents d + WHERE d.id = v_doc_id AND d.worker_id = v_worker_id; + IF v_doc IS NULL THEN + RETURN core.rpc_err('NOT_FOUND', + format('fn_worker_document_get: documento id=%s no encontrado para trabajador %s', v_doc_id, v_worker_id), + jsonb_build_object('fn', 'fn_worker_document_get', 'doc_id', v_doc_id, 'worker_id', v_worker_id)); + END IF; + RETURN core.rpc_ok(jsonb_build_object('document', v_doc), + format('Documento id=%s del trabajador %s obtenido', v_doc_id, v_worker_id), + jsonb_build_object('fn', 'fn_worker_document_get', 'doc_id', v_doc_id)); +EXCEPTION WHEN OTHERS THEN + RETURN core.rpc_from_exception('fn_worker_document_get', SQLSTATE, SQLERRM); +END; +$$;