feat: support ARCA production environment with dynamic endpoint configuration and environment selection

This commit is contained in:
2026-08-12 09:04:06 -03:00
parent 6e7bf6e835
commit 4ffb6a281f
7 changed files with 82 additions and 18 deletions
+1
View File
@@ -69,6 +69,7 @@ FILE_SERVER_URL = http://localhost:4000/
FILE_SERVER_URL_prod = https://files.turnosxpress.com.ar/
SYS_ADMIN_API_PUBLIC_KEY = keys/tx-sysadmin-public.key
ARCA_ENVIRONMENT=homologation
ARCA_WSAA_URL=https://wsaahomo.afip.gov.ar/ws/services/LoginCms
ARCA_WSFE_URL=https://wswhomo.afip.gov.ar/wsfev1/service.asmx
+1
View File
@@ -67,6 +67,7 @@ SYSTEM_KEY = ad7c956a-76bf-45hdr60-8a50-2ca7b2180997
FILE_SERVER_URL = https://files.turnosxpress.com.ar/
SYS_ADMIN_API_PUBLIC_KEY = keys/tx-sysadmin-public.key
ARCA_ENVIRONMENT=production
ARCA_WSAA_URL=https://wsaa.afip.gov.ar/ws/services/LoginCms
ARCA_WSFE_URL=https://servicios1.afip.gov.ar/wsfev1/service.asmx
@@ -1,4 +1,5 @@
import { IArcaCredentialDocument } from "./ArcaCredentials.Adapter.Mongoose";
import { ARCA_WSAA_ENVIRONMENT, ARCA_WSAA_SERVICE } from "../ArcaWsaaTokens/ArcaWsaaTokens.Interface";
export enum ARCA_CREDENTIAL_STATUS {
CSR_GENERATED = "CSR_GENERATED",
@@ -26,8 +27,9 @@ export type TestArcaWsaaLoginParams = {
export type ArcaWsaaLoginTestResult = {
status: "cached" | "created";
environment: "homologation";
service: "wsfe";
environment: ARCA_WSAA_ENVIRONMENT;
service: ARCA_WSAA_SERVICE;
endpoints: ArcaEndpointMetadata;
expirationTime: Date;
cached: boolean;
};
@@ -38,8 +40,9 @@ export type ArcaWsfeLastVoucherParams = {
};
export type ArcaWsfeLastVoucherResult = {
environment: "homologation";
service: "wsfe";
environment: ARCA_WSAA_ENVIRONMENT;
service: ARCA_WSAA_SERVICE;
endpoints: ArcaEndpointMetadata;
pointOfSale: number;
voucherType: 11;
lastVoucherNumber: number;
@@ -58,8 +61,9 @@ export type ArcaWsfeIssueFacturaCParams = {
};
export type ArcaWsfeIssueFacturaCResult = {
environment: "homologation";
service: "wsfe";
environment: ARCA_WSAA_ENVIRONMENT;
service: ARCA_WSAA_SERVICE;
endpoints: ArcaEndpointMetadata;
pointOfSale: number;
voucherType: 11;
voucherNumber: number;
@@ -78,9 +82,15 @@ export type ArcaWsfeDiagnosticsParams = {
sessionUser: string;
};
export type ArcaEndpointMetadata = {
wsaaHost?: string;
wsfeHost?: string;
};
export type ArcaWsfeDiagnosticsResult = {
environment: "homologation";
service: "wsfe";
environment: ARCA_WSAA_ENVIRONMENT;
service: ARCA_WSAA_SERVICE;
endpoints: ArcaEndpointMetadata;
dummy: {
ok: boolean;
appServer?: string;
@@ -200,6 +200,35 @@ class ArcaCredentialsManager implements IArcaCredentialsManager {
return parsedValue;
}
private getArcaEnvironment(): ARCA_WSAA_ENVIRONMENT {
const environment = (process.env.ARCA_ENVIRONMENT || ARCA_WSAA_ENVIRONMENT.HOMOLOGATION).trim().toLowerCase();
if (!Object.values(ARCA_WSAA_ENVIRONMENT).includes(environment as ARCA_WSAA_ENVIRONMENT)) {
throw new Error("ARCA_ENVIRONMENT debe ser 'homologation' o 'production'");
}
return environment as ARCA_WSAA_ENVIRONMENT;
}
private getEndpointHost(value?: string): string | undefined {
if (!value) {
return undefined;
}
try {
return new URL(value).host;
} catch {
return undefined;
}
}
private getArcaEndpointMetadata() {
return {
wsaaHost: this.getEndpointHost(process.env.ARCA_WSAA_URL),
wsfeHost: this.getEndpointHost(process.env.ARCA_WSFE_URL),
};
}
private signTraCms(traXml: string, certificatePem: string, privateKeyPem: string): string {
const p7 = forge.pkcs7.createSignedData();
p7.content = forge.util.createBuffer(traXml, "utf8");
@@ -270,7 +299,7 @@ class ArcaCredentialsManager implements IArcaCredentialsManager {
cached: boolean;
}> {
const service = ARCA_WSAA_SERVICE.WSFE;
const environment = ARCA_WSAA_ENVIRONMENT.HOMOLOGATION;
const environment = this.getArcaEnvironment();
const cachedToken = await ArcaWsaaTokens.getValidToken({ companyId, service, environment });
if (cachedToken) {
@@ -582,7 +611,8 @@ class ArcaCredentialsManager implements IArcaCredentialsManager {
private parseWsfeIssueFacturaCReturn(
xml: string,
pointOfSale: number,
voucherNumber: number
voucherNumber: number,
environment: ARCA_WSAA_ENVIRONMENT
): ArcaWsfeIssueFacturaCResult {
const faultString = this.extractXmlValue(xml, "faultstring");
@@ -600,8 +630,9 @@ class ArcaCredentialsManager implements IArcaCredentialsManager {
}
return {
environment: ARCA_WSAA_ENVIRONMENT.HOMOLOGATION,
environment,
service: ARCA_WSAA_SERVICE.WSFE,
endpoints: this.getArcaEndpointMetadata(),
pointOfSale,
voucherType: FACTURA_C_VOUCHER_TYPE,
voucherNumber,
@@ -807,13 +838,14 @@ class ArcaCredentialsManager implements IArcaCredentialsManager {
await this.validateCompanyAdmin(data.companyId, data.sessionUser);
const service = ARCA_WSAA_SERVICE.WSFE;
const environment = ARCA_WSAA_ENVIRONMENT.HOMOLOGATION;
const environment = this.getArcaEnvironment();
const loginTicket = await this.getWsaaLoginTicket(data.companyId);
return {
status: loginTicket.cached ? "cached" : "created",
environment,
service,
endpoints: this.getArcaEndpointMetadata(),
expirationTime: loginTicket.expirationTime,
cached: loginTicket.cached,
};
@@ -823,6 +855,7 @@ class ArcaCredentialsManager implements IArcaCredentialsManager {
await this.validateCompanyAdmin(data.companyId, data.sessionUser);
const fiscalProfile = await this.requireActiveFiscalProfile(data.companyId, data.sessionUser);
const loginTicket = await this.getWsaaLoginTicket(data.companyId);
const environment = this.getArcaEnvironment();
const voucherType = FACTURA_C_VOUCHER_TYPE;
let responseXml: string;
@@ -842,8 +875,9 @@ class ArcaCredentialsManager implements IArcaCredentialsManager {
}
return {
environment: ARCA_WSAA_ENVIRONMENT.HOMOLOGATION,
environment,
service: ARCA_WSAA_SERVICE.WSFE,
endpoints: this.getArcaEndpointMetadata(),
pointOfSale: fiscalProfile.pointOfSale,
voucherType,
lastVoucherNumber: this.parseWsfeLastVoucherReturn(responseXml),
@@ -859,6 +893,7 @@ class ArcaCredentialsManager implements IArcaCredentialsManager {
const fiscalProfile = await this.requireActiveFiscalProfile(data.companyId, data.sessionUser);
const loginTicket = await this.getWsaaLoginTicket(data.companyId);
const environment = this.getArcaEnvironment();
const voucherType = 11;
const receiverDocumentType = this.parseXmlInteger(
data.receiverDocumentType ?? FINAL_CONSUMER_DOCUMENT_TYPE,
@@ -908,7 +943,8 @@ class ArcaCredentialsManager implements IArcaCredentialsManager {
"FECAESolicitar"
),
fiscalProfile.pointOfSale,
voucherNumber
voucherNumber,
environment
);
} catch (error) {
throw new ArcaWsfeIssueFacturaCError(
@@ -927,11 +963,13 @@ class ArcaCredentialsManager implements IArcaCredentialsManager {
await this.validateCompanyAdmin(data.companyId, data.sessionUser);
const fiscalProfile = await this.requireActiveFiscalProfile(data.companyId, data.sessionUser);
const loginTicket = await this.getWsaaLoginTicket(data.companyId);
const environment = this.getArcaEnvironment();
const voucherType = 11;
const diagnostics: ArcaWsfeDiagnosticsResult = {
environment: ARCA_WSAA_ENVIRONMENT.HOMOLOGATION,
environment,
service: ARCA_WSAA_SERVICE.WSFE,
endpoints: this.getArcaEndpointMetadata(),
dummy: { ok: false },
voucherTypes: { ok: false },
pointsOfSale: {
@@ -6,6 +6,7 @@ export enum ARCA_WSAA_SERVICE {
export enum ARCA_WSAA_ENVIRONMENT {
HOMOLOGATION = "homologation",
PRODUCTION = "production",
}
export type ArcaWsaaTokenLookupParams = {
@@ -19,8 +19,12 @@ export type ArcaWsfeLastVoucherParams = {
};
export type ArcaWsfeLastVoucherResult = {
environment: "homologation";
environment: "homologation" | "production";
service: "wsfe";
endpoints?: {
wsaaHost?: string;
wsfeHost?: string;
};
pointOfSale: number;
voucherType: 11;
lastVoucherNumber: number;
@@ -4,15 +4,22 @@ import QuestionCard from "../../QuestionCard/QuestionCard";
export type ArcaWsaaTestResult = {
status?: string;
environment?: string;
environment?: "homologation" | "production";
service?: string;
endpoints?: ArcaEndpointMetadata;
expirationTime?: string;
cached?: boolean;
};
type ArcaEndpointMetadata = {
wsaaHost?: string;
wsfeHost?: string;
};
export type ArcaWsfeDiagnosticsResult = {
environment?: string;
environment?: "homologation" | "production";
service?: string;
endpoints?: ArcaEndpointMetadata;
dummy?: {
ok: boolean;
appServer?: string;
@@ -84,6 +91,7 @@ export default function ArcaTestConnectionFlow({ currentStepIndex, loading, diag
<InfoPanel>WSAA está OK. El diagnóstico WSFE no emite comprobantes: solo consulta salud, puntos de venta, tipos de comprobante y último número autorizado.</InfoPanel>
<InfoRow label="Ambiente" value={result?.environment || "homologation"} />
<InfoRow label="Servicio" value={result?.service || "wsfe"} />
{result?.endpoints?.wsaaHost ? <InfoRow label="WSAA host" value={result.endpoints.wsaaHost} /> : null}
<InfoRow label="Vencimiento" value={formatExpiration(result?.expirationTime)} />
<InfoRow label="Token" value={result?.cached ? "Reutilizado desde caché" : "Creado en esta prueba"} />
{diagnosticsLoading && <InfoPanel>Consultando FEDummy, puntos de venta, tipos de comprobante y último comprobante autorizado...</InfoPanel>}
@@ -125,6 +133,7 @@ function WsfeDiagnosticsPanel({ diagnostics }: { diagnostics: ArcaWsfeDiagnostic
return (
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
<InfoRow label="WSFE FEDummy" value={diagnostics.dummy?.ok ? `OK (${[diagnostics.dummy.appServer, diagnostics.dummy.dbServer, diagnostics.dummy.authServer].filter(Boolean).join(" / ") || "sin detalle"})` : `Error: ${diagnostics.dummy?.error || "sin detalle"}`} />
{diagnostics.endpoints?.wsfeHost ? <InfoRow label="WSFE host" value={diagnostics.endpoints.wsfeHost} /> : null}
<InfoRow label="Punto de venta configurado" value={diagnostics.pointsOfSale?.includesConfiguredPointOfSale ? `OK (${diagnostics.pointsOfSale.configuredPointOfSale})` : `No aparece (${diagnostics.pointsOfSale?.configuredPointOfSale || "sin configurar"})`} />
{diagnostics.pointsOfSale?.points?.length ? <InfoRow label="Puntos de venta ARCA" value={diagnostics.pointsOfSale.points.join(", ")} /> : null}
{diagnostics.pointsOfSale?.error ? <StatusMessage error={diagnostics.pointsOfSale.error} /> : null}