From 322f1d3f1510dc310667df8dcbcb2ebaca6f335b Mon Sep 17 00:00:00 2001 From: Horacio Daniel Ros Date: Tue, 15 Sep 2026 11:32:11 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20agregar=20validaciones=20para=20el=20m?= =?UTF-8?q?=C3=B3dulo=20ARCA=20seg=C3=BAn=20el=20plan=20de=20la=20organiza?= =?UTF-8?q?ci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Models/ArcaCredentials/ArcaCredentials.ts | 34 +++++++++- .../components/StepEngine/StepEngine.tsx | 65 ++++++++++++++++++- 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/server/src/Models/ArcaCredentials/ArcaCredentials.ts b/server/src/Models/ArcaCredentials/ArcaCredentials.ts index 738edbc..fca0090 100644 --- a/server/src/Models/ArcaCredentials/ArcaCredentials.ts +++ b/server/src/Models/ArcaCredentials/ArcaCredentials.ts @@ -2,6 +2,9 @@ import crypto from "crypto"; import axios from "axios"; import forge from "node-forge"; import { validatePermissionsByCompany, validateSessionUser } from "../../helpers/check"; +import CompaniesList from "../Companies/Companies"; +import PlanSubscriptionsList from "../PlanSubscriptions/PlanSubscriptons"; +import { PlanFeatures } from "../Plans/Plans.interface"; import ArcaWsaaTokens from "../ArcaWsaaTokens/ArcaWsaaTokens"; import { ARCA_WSAA_ENVIRONMENT, @@ -123,6 +126,23 @@ class ArcaCredentialsManager implements IArcaCredentialsManager { await validatePermissionsByCompany({ companyId, sessionUser }); } + private async requirePaymentsFeature(companyId: string): Promise { + const company = await CompaniesList.companies.findOne({ _id: companyId }); + + if (!company) { + throw new Error("No se ha encontrado la organización"); + } + + const canAccessPayments = await PlanSubscriptionsList.checkFeature({ + userId: String(company.ownerId), + feature: PlanFeatures.PAYMENTS, + }); + + if (!canAccessPayments) { + throw new Error("La organización no tiene un plan que soporte el módulo de pagos"); + } + } + private async requireActiveFiscalProfile( companyId: string, sessionUser: string @@ -730,6 +750,7 @@ class ArcaCredentialsManager implements IArcaCredentialsManager { public async generateCsr(data: GenerateArcaCredentialCsrParams): Promise { await this.validateCompanyAdmin(data.companyId, data.sessionUser); + await this.requirePaymentsFeature(data.companyId); const fiscalProfile = await this.requireActiveFiscalProfile(data.companyId, data.sessionUser); const keys = forge.pki.rsa.generateKeyPair(2048); @@ -778,6 +799,7 @@ class ArcaCredentialsManager implements IArcaCredentialsManager { public async uploadCertificate(data: UploadArcaCredentialCertificateParams): Promise { await this.validateCompanyAdmin(data.companyId, data.sessionUser); + await this.requirePaymentsFeature(data.companyId); if (!data.certificatePem || !data.certificatePem.trim()) { throw new Error("Se requiere el certificado ARCA"); @@ -836,6 +858,7 @@ class ArcaCredentialsManager implements IArcaCredentialsManager { public async testWsaaLogin(data: TestArcaWsaaLoginParams): Promise { await this.validateCompanyAdmin(data.companyId, data.sessionUser); + await this.requirePaymentsFeature(data.companyId); const service = ARCA_WSAA_SERVICE.WSFE; const environment = this.getArcaEnvironment(); @@ -853,6 +876,7 @@ class ArcaCredentialsManager implements IArcaCredentialsManager { public async getWsfeLastVoucher(data: ArcaWsfeLastVoucherParams): Promise { await this.validateCompanyAdmin(data.companyId, data.sessionUser); + await this.requirePaymentsFeature(data.companyId); const fiscalProfile = await this.requireActiveFiscalProfile(data.companyId, data.sessionUser); const loginTicket = await this.getWsaaLoginTicket(data.companyId); const environment = this.getArcaEnvironment(); @@ -886,6 +910,7 @@ class ArcaCredentialsManager implements IArcaCredentialsManager { public async issueFacturaC(data: ArcaWsfeIssueFacturaCParams): Promise { await this.validateCompanyAdmin(data.companyId, data.sessionUser); + await this.requirePaymentsFeature(data.companyId); if (data.amount <= 0) { throw new Error("El importe de la factura debe ser mayor a cero"); @@ -961,6 +986,7 @@ class ArcaCredentialsManager implements IArcaCredentialsManager { public async getWsfeDiagnostics(data: ArcaWsfeDiagnosticsParams): Promise { await this.validateCompanyAdmin(data.companyId, data.sessionUser); + await this.requirePaymentsFeature(data.companyId); const fiscalProfile = await this.requireActiveFiscalProfile(data.companyId, data.sessionUser); const loginTicket = await this.getWsaaLoginTicket(data.companyId); const environment = this.getArcaEnvironment(); @@ -1054,8 +1080,11 @@ class ArcaCredentialsManager implements IArcaCredentialsManager { await validateSessionUser({ sessionUser: data.sessionUser }); + let companyId: string | undefined; + if (data.companyId) { await validatePermissionsByCompany({ companyId: data.companyId, sessionUser: data.sessionUser }); + companyId = data.companyId; } else if (data.id) { const credential = await this.arcaCredentials.findOne(data); @@ -1067,12 +1096,13 @@ class ArcaCredentialsManager implements IArcaCredentialsManager { companyId: String(credential.companyId), sessionUser: data.sessionUser, }); - - return [this.sanitizeCredential(credential)]; + companyId = String(credential.companyId); } else { throw new Error("Se requiere una organización para buscar credenciales ARCA"); } + await this.requirePaymentsFeature(companyId); + const credentials = await this.arcaCredentials.find(data); return credentials.map((credential) => this.sanitizeCredential(credential)); } diff --git a/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx b/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx index 940fc23..5f47821 100644 --- a/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx +++ b/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx @@ -206,6 +206,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm const [wapError, setWapError] = useState(null); // State for ARCA onboarding flow + const [arcaIsBlocked, setArcaIsBlocked] = useState(false); const [arcaCuit, setArcaCuit] = useState(""); const [arcaLegalName, setArcaLegalName] = useState(""); const [arcaTaxCondition, setArcaTaxCondition] = useState<"MONOTRIBUTO" | "EXENTO">("MONOTRIBUTO"); @@ -277,6 +278,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm }; const loadOrganizationContext = (org: any) => { + setArcaIsBlocked(false); setCreatedCompanyId(org.id || org._id); setSelectedOrganization(org); setOrgName(org.name || ""); @@ -499,6 +501,18 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm setWapQr(null); setWapError(null); } else if (flowId === "arca-onboarding") { + const orgSub = SessionInfo.organizationSubscriptions[org.id || org._id]; + const planSupportsPayments = orgSub ? orgSub.plan?.payments : false; + + if (!planSupportsPayments) { + setArcaIsBlocked(true); + setCurrentStepIndex(0); + setArcaError(null); + setArcaLoading(false); + return; + } + + setArcaIsBlocked(false); setCurrentStepIndex(0); setArcaError(null); setArcaLoading(false); @@ -527,6 +541,19 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm setArcaStatusLabel(getArcaStatusLabel(credential.status)); }).catch(console.error); } else if (flowId === "arca-test-connection") { + const orgSub = SessionInfo.organizationSubscriptions[org.id || org._id]; + const planSupportsPayments = orgSub ? orgSub.plan?.payments : false; + + if (!planSupportsPayments) { + setArcaIsBlocked(true); + setCurrentStepIndex(0); + setArcaError(null); + setArcaLoading(false); + setArcaTestResult(null); + return; + } + + setArcaIsBlocked(false); setCurrentStepIndex(0); setArcaError(null); setArcaLoading(false); @@ -2415,8 +2442,42 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm if (flowId === "public-link") return renderPublicLinkFlow(); if (flowId === "notifications") return renderNotificationsFlow(); if (flowId === "whatsapp-bot") return renderWhatsAppBotFlow(); - if (flowId === "arca-onboarding") return renderArcaOnboardingFlow(); - if (flowId === "arca-test-connection") return renderArcaTestConnectionFlow(); + if (flowId === "arca-onboarding") { + if (arcaIsBlocked) { + return ( + router.push("/landing/pricing")} + nextLabel="Ver planes" + > +
+ La organización seleccionada necesita un plan con el módulo de pagos activo para usar ARCA. Actualizá tu plan o volvé al asistente y elegí otra opción. +
+
+ ); + } + return renderArcaOnboardingFlow(); + } + if (flowId === "arca-test-connection") { + if (arcaIsBlocked) { + return ( + router.push("/landing/pricing")} + nextLabel="Ver planes" + > +
+ La organización seleccionada necesita un plan con el módulo de pagos activo para usar ARCA. Actualizá tu plan o volvé al asistente y elegí otra opción. +
+
+ ); + } + return renderArcaTestConnectionFlow(); + } if (flowId === "disable-schedule") return renderDisableScheduleFlow(); if (flowId === "open-reservation-periods") return renderReservationPeriodFlow(); if (flowId === "override-schedule") return renderOverrideScheduleFlow();