From 564b666fcc6448ab41c61e11fa2c299db2d748c9 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 9 Oct 2026 23:11:31 +0200 Subject: [PATCH] =?UTF-8?q?A=C3=B1adir=20funcionalidad=20para=20eliminar?= =?UTF-8?q?=20procesos=20y=20mejorar=20la=20gesti=C3=B3n=20de=20procesos?= =?UTF-8?q?=20en=20la=20aplicaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/api/application/process.repository.ts | 1 + .../src/api/application/process.use-cases.ts | 6 ++++++ .../persistence/sequelize-process.repository.ts | 9 +++++++++ modules/companies/src/common/locales/en.json | 4 +++- modules/companies/src/common/locales/es.json | 4 +++- .../detail/types/process-detail-props.ts | 2 ++ .../web/processes/detail/ui/process-detail.tsx | 16 +++++++++++++++- .../web/processes/list/api/get-processes.api.ts | 4 ++++ .../src/web/processes/list/api/index.ts | 2 +- .../use-processes-page-controller.ts | 7 +++++-- .../web/processes/list/ui/processes-page.tsx | 17 +++++++++++++---- .../web/processes/shared/api/process-request.ts | 2 +- 12 files changed, 63 insertions(+), 11 deletions(-) diff --git a/modules/companies/src/api/application/process.repository.ts b/modules/companies/src/api/application/process.repository.ts index e469a11..9db1b9a 100644 --- a/modules/companies/src/api/application/process.repository.ts +++ b/modules/companies/src/api/application/process.repository.ts @@ -19,6 +19,7 @@ export interface IProcessRepository { action: (context: ProcessCatalogContext) => Result ): Promise>; get(company: UniqueID): Promise>; + remove(company: UniqueID, process: UniqueID): Promise>; save( company: UniqueID, action: (context: ProcessContext) => Result diff --git a/modules/companies/src/api/application/process.use-cases.ts b/modules/companies/src/api/application/process.use-cases.ts index be730a5..0f9d38f 100644 --- a/modules/companies/src/api/application/process.use-cases.ts +++ b/modules/companies/src/api/application/process.use-cases.ts @@ -105,6 +105,12 @@ export class ProcessUseCases { : values; }); } + remove(companyId: string, id: string) { + const company = UniqueID.create(companyId); + const process = UniqueID.create(id); + if (!(company.isSuccess && process.isSuccess)) return Promise.resolve(Result.fail(new ProcessError("INVALID_REQUEST"))); + return this.repository.remove(company.data, process.data); + } update(companyId: string, id: string, input: UpdateProcessInput, actorId: string) { const company = UniqueID.create(companyId); const actor = UniqueID.create(actorId); diff --git a/modules/companies/src/api/infrastructure/persistence/sequelize-process.repository.ts b/modules/companies/src/api/infrastructure/persistence/sequelize-process.repository.ts index cbd1b5c..edecc5d 100644 --- a/modules/companies/src/api/infrastructure/persistence/sequelize-process.repository.ts +++ b/modules/companies/src/api/infrastructure/persistence/sequelize-process.repository.ts @@ -146,6 +146,15 @@ export class SequelizeProcessRepository implements IProcessRepository { return Result.ok({ template: candidate.data, context }); }); } + remove(company: UniqueID, process: UniqueID) { + return this.database.transaction(async (transaction) => { + const rows = await this.database.query<{ id: string }>("SELECT id FROM person_processes WHERE id=:id AND companyId=:companyId FOR UPDATE", { replacements: { id: process.toPrimitive(), companyId: company.toPrimitive() }, transaction, type: QueryTypes.SELECT }); + if (!rows.length) return Result.fail(new ProcessError("PROCESS_NOT_FOUND")); + await this.database.query("DELETE FROM person_process_revisions WHERE itemId=:id", { replacements: { id: process.toPrimitive() }, transaction }); + await this.database.query("DELETE FROM person_processes WHERE id=:id AND companyId=:companyId", { replacements: { id: process.toPrimitive(), companyId: company.toPrimitive() }, transaction }); + return Result.ok(undefined); + }); + } save(company: UniqueID, action: Parameters[1]) { return this.database.transaction(async (transaction) => { const loaded = await this.load(company, transaction, true); diff --git a/modules/companies/src/common/locales/en.json b/modules/companies/src/common/locales/en.json index 7773226..e7254a0 100644 --- a/modules/companies/src/common/locales/en.json +++ b/modules/companies/src/common/locales/en.json @@ -327,7 +327,9 @@ "INTERNAL_ERROR": "The operation could not be completed. Try again.", "FORBIDDEN": "Sign in again.", "UNAUTHENTICATED": "Sign in again." - } + }, + "delete": "Delete process", + "deleteConfirm": "Do you want to delete the process “{{name}}”? This also deletes its history." }, "remote": { "title": "Remote access", diff --git a/modules/companies/src/common/locales/es.json b/modules/companies/src/common/locales/es.json index a836bf6..c412ebd 100644 --- a/modules/companies/src/common/locales/es.json +++ b/modules/companies/src/common/locales/es.json @@ -327,7 +327,9 @@ "INTERNAL_ERROR": "No se ha podido completar la operación. Inténtalo de nuevo.", "FORBIDDEN": "Vuelve a iniciar sesión.", "UNAUTHENTICATED": "Vuelve a iniciar sesión." - } + }, + "delete": "Eliminar proceso", + "deleteConfirm": "¿Quieres eliminar el proceso «{{name}}»? Esta acción también elimina su historial." }, "remote": { "title": "Accesos remotos", diff --git a/modules/companies/src/web/processes/detail/types/process-detail-props.ts b/modules/companies/src/web/processes/detail/types/process-detail-props.ts index b8d4cfc..ee26dab 100644 --- a/modules/companies/src/web/processes/detail/types/process-detail-props.ts +++ b/modules/companies/src/web/processes/detail/types/process-detail-props.ts @@ -4,4 +4,6 @@ export type ProcessDetailProps = { process: PersonProcessDTO; person: string; csrfToken: string; + onDelete?: (id: string) => void; + onDeleted?: () => void; }; diff --git a/modules/companies/src/web/processes/detail/ui/process-detail.tsx b/modules/companies/src/web/processes/detail/ui/process-detail.tsx index d43136c..7b8bb48 100644 --- a/modules/companies/src/web/processes/detail/ui/process-detail.tsx +++ b/modules/companies/src/web/processes/detail/ui/process-detail.tsx @@ -1,4 +1,5 @@ import { Button } from "@repo/shadcn-ui/components/button"; +import { useState } from "react"; import { Card, CardContent } from "@repo/shadcn-ui/components/card"; import { useTranslation } from "react-i18next"; @@ -9,9 +10,10 @@ import { useProcessDetailController } from "../controllers"; import type { ProcessDetailProps } from "../types"; export function ProcessDetail(props: ProcessDetailProps) { - const { process, person, csrfToken } = props; + const { process, person, csrfToken, onDelete, onDeleted } = props; const { edit, setEdit, refresh, open, resolved } = useProcessDetailController(props); const { t, i18n } = useTranslation("companies"); + const [deleting, setDeleting] = useState(false); return (
@@ -39,6 +41,18 @@ export function ProcessDetail(props: ProcessDetailProps) { +
)} diff --git a/modules/companies/src/web/processes/list/api/get-processes.api.ts b/modules/companies/src/web/processes/list/api/get-processes.api.ts index bce34fe..e9e7a4a 100644 --- a/modules/companies/src/web/processes/list/api/get-processes.api.ts +++ b/modules/companies/src/web/processes/list/api/get-processes.api.ts @@ -4,3 +4,7 @@ import { processRequest } from "../../shared/api/process-request"; export async function getProcesses(company: string) { return ProcessesResponseSchema.parse(await processRequest(company)); } + +export async function deleteProcess(company: string, id: string, token: string) { + await processRequest(`${company}/${id}/delete`, {}, token); +} diff --git a/modules/companies/src/web/processes/list/api/index.ts b/modules/companies/src/web/processes/list/api/index.ts index 41a353b..abdcdf5 100644 --- a/modules/companies/src/web/processes/list/api/index.ts +++ b/modules/companies/src/web/processes/list/api/index.ts @@ -1 +1 @@ -export { getProcesses } from "./get-processes.api"; +export { deleteProcess, getProcesses } from "./get-processes.api"; diff --git a/modules/companies/src/web/processes/list/controllers/use-processes-page-controller.ts b/modules/companies/src/web/processes/list/controllers/use-processes-page-controller.ts index 00c9dac..6afdd92 100644 --- a/modules/companies/src/web/processes/list/controllers/use-processes-page-controller.ts +++ b/modules/companies/src/web/processes/list/controllers/use-processes-page-controller.ts @@ -1,14 +1,15 @@ import { useBrowserSearchParams, useEntityNavigation } from "@metis/core/client"; -import { useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { processKeys } from "../../shared/queries"; -import { getProcesses } from "../api"; +import { deleteProcess, getProcesses } from "../api"; import type { ProcessesPageProps } from "../types"; export function useProcessesPageController({ company }: ProcessesPageProps) { const { t } = useTranslation("companies"); + const client = useQueryClient(); const [create, setCreate] = useState(false); const { selectedId: selected, selectEntity: setSelected } = useEntityNavigation( `/companies/${company.id}/processes` @@ -32,6 +33,7 @@ export function useProcessesPageController({ company }: ProcessesPageProps) { params.delete("closed"); } }); + const remove = useMutation({ mutationFn: ({ id, token }: { id: string; token: string }) => deleteProcess(company.id, id, token), onSuccess: async () => { await client.invalidateQueries({ queryKey: processKeys.company(company.id) }); setSelected(undefined); } }); const query = useQuery({ queryKey: processKeys.company(company.id), queryFn: () => getProcesses(company.id), @@ -56,6 +58,7 @@ export function useProcessesPageController({ company }: ProcessesPageProps) { closed, setClosed, query, + remove, person, current, missingView: Boolean(selected && query.isSuccess && !current), diff --git a/modules/companies/src/web/processes/list/ui/processes-page.tsx b/modules/companies/src/web/processes/list/ui/processes-page.tsx index a5b36c4..b5a52d1 100644 --- a/modules/companies/src/web/processes/list/ui/processes-page.tsx +++ b/modules/companies/src/web/processes/list/ui/processes-page.tsx @@ -28,6 +28,7 @@ export function ProcessesPage(props: ProcessesPageProps) { closed, setClosed, query, + remove, person, current, missingView, @@ -66,6 +67,8 @@ export function ProcessesPage(props: ProcessesPageProps) { csrfToken={csrfToken} person={person(current.values.personId)} process={current} + onDeleted={() => setSelected(undefined)} + onDelete={(id) => remove.mutate({ id, token: csrfToken })} /> ) : ( !selected && ( @@ -150,10 +153,16 @@ export function ProcessesPage(props: ProcessesPageProps) { hideable: false, render: (p) => ( - {" "} - setSelected(p.id)}> - {t("processes.openDetail")} - {" "} + setSelected(p.id)}>{t("processes.openDetail")} + { + if (window.confirm(t("processes.deleteConfirm", { name: p.values.name }))) { + remove.mutate({ id: p.id, token: csrfToken }); + } + }} + > + {t("processes.delete")} + ), }, diff --git a/modules/companies/src/web/processes/shared/api/process-request.ts b/modules/companies/src/web/processes/shared/api/process-request.ts index bef8dbc..f212d6e 100644 --- a/modules/companies/src/web/processes/shared/api/process-request.ts +++ b/modules/companies/src/web/processes/shared/api/process-request.ts @@ -10,7 +10,7 @@ export async function processRequest(path: string, body?: unknown, token?: strin body === undefined ? {} : { "content-type": "application/json", "x-csrf-token": token ?? "" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); - const data: unknown = await response.json(); + const data: unknown = response.status === 204 ? undefined : await response.json(); if (!response.ok) { const error = z.object({ code: z.string() }).safeParse(data); throw new ProcessApiError(error.success ? error.data.code : "INTERNAL_ERROR");