ci-validation
Version:
🇺🇾 Complete TypeScript/JavaScript library for validating Uruguayan CI (Cédula de Identidad) with official algorithm and government service integration
825 lines • 37.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SmiService = void 0;
const axios_1 = __importDefault(require("axios"));
const cheerio = __importStar(require("cheerio"));
const crypto = __importStar(require("crypto"));
const dotenv_1 = __importDefault(require("dotenv"));
// Load environment variables
dotenv_1.default.config();
class SmiService {
constructor() {
this.loginUrl = "https://reservasweb.smi.com.uy/Autogestion/servlet/com.mgkwebfrontend.autogestion.wpauglogin";
this.baseUrl = "https://reservasweb.smi.com.uy/Autogestion/servlet/com.mgkwebfrontend.autogestion.wpaugrecuperarpassword";
// Session data storage
this.sessionData = {};
this.headers = {
Accept: "*/*",
"Accept-Language": "es-ES,es;q=0.9,bg;q=0.8",
"Cache-Control": "no-cache",
Connection: "keep-alive",
"Content-Type": "application/json",
GxAjaxRequest: "1",
Origin: "https://reservasweb.smi.com.uy",
Pragma: "no-cache",
Referer: "https://reservasweb.smi.com.uy/Autogestion/servlet/com.mgkwebfrontend.autogestion.wpaugrecuperarpassword",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
"sec-ch-ua": '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
};
}
/**
* Create axios instance with proxy configuration if available
* @returns Configured axios instance
*/
createAxiosInstance() {
const config = {
timeout: 40000,
};
// Support for simple PROXY URL format (e.g., http://179.27.158.18:80)
if (process.env.PROXY) {
try {
const proxyUrl = new URL(process.env.PROXY);
config.proxy = {
protocol: proxyUrl.protocol.replace(":", ""),
host: proxyUrl.hostname,
port: parseInt(proxyUrl.port, 10),
};
// Add proxy authentication if provided in URL
if (proxyUrl.username && proxyUrl.password) {
config.proxy.auth = {
username: proxyUrl.username,
password: proxyUrl.password,
};
}
}
catch (error) {
console.error("Invalid PROXY URL format:", process.env.PROXY, error);
}
}
// Fallback to individual environment variables for backward compatibility
else if (process.env.PROXY_HOST && process.env.PROXY_PORT) {
config.proxy = {
host: process.env.PROXY_HOST,
port: parseInt(process.env.PROXY_PORT, 10),
};
// Add proxy authentication if provided
if (process.env.PROXY_USERNAME && process.env.PROXY_PASSWORD) {
config.proxy.auth = {
username: process.env.PROXY_USERNAME,
password: process.env.PROXY_PASSWORD,
};
}
// Support for proxy protocol (http/https)
if (process.env.PROXY_PROTOCOL) {
config.proxy.protocol = process.env.PROXY_PROTOCOL;
}
}
return axios_1.default.create(config);
}
/**
* Initialize session by making a GET request to the login page
* @returns Promise with session initialization result
*/
async initializeSession() {
try {
const axiosInstance = this.createAxiosInstance();
// First request to get initial cookies
const initialResponse = await axiosInstance.get(this.loginUrl, {
headers: {
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "es-ES,es;q=0.9",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
},
});
// Extract session cookies from first response
const setCookieHeader = initialResponse.headers["set-cookie"];
if (setCookieHeader) {
setCookieHeader.forEach((cookie) => {
if (cookie.includes("JSESSIONID=")) {
this.sessionData.jsessionId = cookie.split("JSESSIONID=")[1].split(";")[0];
}
if (cookie.includes("GX_SESSION_ID=")) {
this.sessionData.gxSessionId = cookie.split("GX_SESSION_ID=")[1].split(";")[0];
}
});
}
// Second request using the obtained cookies to get proper session state
const sessionResponse = await axiosInstance.get(this.loginUrl, {
headers: {
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Language": "es-ES,es;q=0.9",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
"sec-ch-ua": '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Dest": "document",
Referer: "https://reservasweb.smi.com.uy/Autogestion/servlet/com.mgkwebfrontend.autogestion.wpauglogin",
Cookie: this.buildCookieHeader(),
},
timeout: 10000,
});
// Extract tokens from the second response HTML
this.extractTokensFromHTML(sessionResponse.data);
const cacheBuster = Date.now();
const recoveryHash = this.sessionData.recoveryPageHash;
const url = `${this.baseUrl}?${this.sessionData.recoveryPageHash},gx-no-cache=${cacheBuster}`;
const newSession = await axiosInstance.get(url, {
headers: {
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Language": "es-ES,es;q=0.9",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
"sec-ch-ua": '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Dest": "document",
Referer: "https://reservasweb.smi.com.uy/Autogestion/servlet/com.mgkwebfrontend.autogestion.wpauglogin",
},
timeout: 10000,
});
// Extract tokens from the second response HTML
this.extractTokensFromHTML(newSession.data);
this.sessionData.recoveryPageHash = recoveryHash;
return !!(this.sessionData.jsessionId && this.sessionData.ajaxSecurityToken);
}
catch (error) {
console.error("Failed to initialize SMI session:", error);
return false;
}
}
/**
* Extract security tokens from HTML content
* @param html - The HTML content from the login page
*/
extractTokensFromHTML(html) {
try {
// Extract AJAX_SECURITY_TOKEN
const ajaxTokenMatch = html.match(/"AJAX_SECURITY_TOKEN":"([^"]+)"/);
if (ajaxTokenMatch) {
this.sessionData.ajaxSecurityToken = ajaxTokenMatch[1];
}
// Extract GX_AUTH token
const authTokenMatch = html.match(/"GX_AUTH_AUTOGESTION\.WPAUGRECUPERARPASSWORD":"([^"]+)"/);
if (authTokenMatch) {
this.sessionData.gxAuthToken = authTokenMatch[1];
}
// Extract GX_AJAX_KEY
const ajaxKeyMatch = html.match(/"GX_AJAX_KEY":"([^"]+)"/);
if (ajaxKeyMatch) {
this.sessionData.gxAjaxKey = ajaxKeyMatch[1];
}
// Generate recovery page hash using session data
this.sessionData.recoveryPageHash = this.generateRecoveryPageHash();
}
catch (error) {
console.error("Failed to extract tokens from HTML:", error);
}
}
/**
* Generate recovery page hash based on GeneXus session and security patterns
* @returns MD5 hash for the recovery page URL
*/
generateRecoveryPageHash() {
try {
// Based on GeneXus documentation and patterns observed in the JavaScript file,
// the hash is typically generated from session data + object metadata + security tokens
// GeneXus uses object-specific information for hash generation
const recoveryObjectData = [
"autogestion.wpaugrecuperarpassword", // Target object class
"com.mgkwebfrontend", // Package name
this.sessionData.jsessionId || "",
this.sessionData.gxSessionId || "",
this.sessionData.ajaxSecurityToken || "",
// Add timestamp component for cache busting (similar to gx-no-cache parameter)
Math.floor(Date.now() / 1000).toString(),
].join("|");
// Generate MD5 hash as used by GeneXus framework
const hash = crypto.createHash("md5").update(recoveryObjectData).digest("hex");
return hash;
}
catch (error) {
console.error("Failed to generate recovery page hash:", error);
// Fallback to a default hash pattern if generation fails
return "6cee17c9c6b0d3535d1e9001474b1f4c";
}
}
/**
* Generate login page hash for forgot password requests
* @returns MD5 hash for the login page URL
*/
generateLoginPageHash() {
try {
// Generate hash specific to the login object
const loginObjectData = [
"autogestion.wpauglogin", // Login object class
"com.mgkwebfrontend", // Package name
this.sessionData.jsessionId || "",
this.sessionData.gxSessionId || "",
this.sessionData.ajaxSecurityToken || "",
Math.floor(Date.now() / 1000).toString(),
].join("|");
const hash = crypto.createHash("md5").update(loginObjectData).digest("hex");
return hash;
}
catch (error) {
console.error("Failed to generate login page hash:", error);
// Fallback to a default hash pattern if generation fails
return "303618c36883921c9a81d9b113d82232";
}
}
/**
* Execute forgot password action on login page
* @returns Promise with success status
*/
async executeForgotPassword() {
try {
const axiosInstance = this.createAxiosInstance();
const cacheBuster = Date.now();
const hash = this.generateLoginPageHash(); // Use login page hash for forgot password action
const url = `${this.loginUrl}?${hash},gx-no-cache=${cacheBuster}`;
const payload = {
MPage: false,
cmpCtx: "",
parms: [],
hsh: [],
objClass: "autogestion.wpauglogin",
pkgName: "com.mgkwebfrontend",
events: ["'FORGOTPASSWORD'"],
grids: {},
};
const requestHeaders = {
...this.headers,
Cookie: this.buildCookieHeader(),
AJAX_SECURITY_TOKEN: this.sessionData.ajaxSecurityToken,
"X-GXAUTH-TOKEN": this.sessionData.gxAuthToken,
};
const response = await axiosInstance.post(url, payload, {
headers: requestHeaders,
timeout: 10000,
});
return response.status === 200;
}
catch (error) {
console.error("Failed to execute forgot password:", error);
return false;
}
}
/**
* Initialize recovery page by making a GET request
* @returns Promise with initialization result
*/
async initializeRecoveryPage() {
try {
const axiosInstance = this.createAxiosInstance();
const getHeaders = {
Accept: "*/*",
"Accept-Language": "es-ES,es;q=0.9",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
"sec-ch-ua": '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"X-SPA-REQUEST": "1",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
Referer: "https://reservasweb.smi.com.uy/Autogestion/servlet/com.mgkwebfrontend.autogestion.wpauglogin",
Cookie: this.buildCookieHeader(),
AJAX_SECURITY_TOKEN: this.sessionData.ajaxSecurityToken,
};
const response = await axiosInstance.get(this.baseUrl, {
headers: getHeaders,
timeout: 10000,
});
return response.status === 200;
}
catch (error) {
console.error("Failed to initialize recovery page:", error);
return false;
}
}
/**
* Perform final validation request to validate account
* @param finalUrl - The complete URL for the validation request
* @returns Promise with validation result
*/
async performFinalValidation(finalUrl) {
try {
const axiosInstance = this.createAxiosInstance();
const cacheBuster = Date.now();
const refererUrl = `${this.baseUrl}?${this.sessionData.recoveryPageHash},gx-no-cache=${cacheBuster}`;
const validationHeaders = {
Accept: "*/*",
"Accept-Language": "es-ES,es;q=0.9",
"Accept-Encoding": "gzip, deflate, br, zstd",
Connection: "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
"sec-ch-ua": '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"X-SPA-REQUEST": "1",
"X-SPA-MP": "rwdnologueado",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
Referer: refererUrl,
Cookie: this.buildCookieHeader(),
AJAX_SECURITY_TOKEN: this.sessionData.ajaxSecurityToken,
};
const response = await axiosInstance.get(finalUrl, {
headers: validationHeaders,
timeout: 10000,
});
return response.data;
}
catch (error) {
console.error("Failed to perform final validation:", error);
throw error;
}
}
/**
* Check if user is registered in SMI system
* @param request - Object containing the CI
* @returns Promise with SMI response data
*/
async checkUser(request) {
const startTime = Date.now();
try {
// Check if SMI_PROXY is configured to use external proxy
if (process.env.SMI_PROXY) {
return await this.checkUserViaProxy(request, startTime);
}
// Original direct SMI implementation
return await this.checkUserDirect(request, startTime);
}
catch (error) {
// Handle different types of errors
if (axios_1.default.isAxiosError(error)) {
if (error.response?.status === 403 || error.response?.status === 401) {
return {
success: false,
hasUser: false,
error: "Acceso denegado: API requiere autenticación válida",
};
}
else if (error.response && error.response.status >= 500) {
return {
success: false,
hasUser: false,
error: "Error del servidor SMI",
};
}
}
return {
success: error instanceof Error && error.message === "not_registered",
hasUser: false,
error: error instanceof Error ? error.message : "Error desconocido al consultar SMI",
};
}
}
/**
* Check user via external proxy (when SMI_PROXY is set)
* @param request - Object containing the CI
* @param startTime - Start time for execution timing
* @returns Promise with SMI response data
*/
async checkUserViaProxy(request, startTime) {
try {
const axiosInstance = this.createAxiosInstance();
const proxyUrl = process.env.SMI_PROXY;
// Ensure proxy URL ends with slash for proper path joining
const baseUrl = proxyUrl.endsWith("/") ? proxyUrl.slice(0, -1) : proxyUrl;
const url = `${baseUrl}/api/ci/smi?ci=${encodeURIComponent(request.ci)}`;
console.log(`🔄 Using SMI proxy: ${url}`);
const response = await axiosInstance.get(url, {
timeout: 30000, // 30 second timeout for proxy requests
headers: {
Accept: "application/json",
"User-Agent": "ci-validation-service/1.0",
},
});
// The proxy should return our standard API response format
if (response.data && response.data.success !== undefined) {
const proxyResponse = response.data;
// If the proxy response has data.success, extract the SMI response from data.data
if (proxyResponse.data && typeof proxyResponse.data === "object") {
const smiResponse = proxyResponse.data;
// Update execution time to include our processing time
const totalTime = Date.now() - startTime;
if (smiResponse.member) {
smiResponse.member.executionTime = totalTime;
}
return smiResponse;
}
// If it's already a direct SMI response
return proxyResponse;
}
// Fallback: treat response as direct SMI response
return response.data;
}
catch (error) {
console.error(`❌ Error using SMI proxy (${process.env.SMI_PROXY}):`, error);
if (axios_1.default.isAxiosError(error)) {
if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND") {
return {
success: false,
hasUser: false,
error: `No se pudo conectar al proxy SMI: ${process.env.SMI_PROXY}`,
};
}
if (error.response?.status === 404) {
return {
success: false,
hasUser: false,
error: `Endpoint SMI no encontrado en el proxy: ${process.env.SMI_PROXY}/api/smi`,
};
}
}
throw error; // Re-throw to be handled by the main catch block
}
}
/**
* Direct SMI check (original implementation)
* @param request - Object containing the CI
* @param startTime - Start time for execution timing
* @returns Promise with SMI response data
*/
async checkUserDirect(request, startTime) {
try {
// Initialize session first
const sessionInitialized = await this.initializeSession();
if (!sessionInitialized) {
return {
success: false,
hasUser: false,
error: "No se pudo inicializar la sesión con SMI",
};
}
// Generate unique cache buster
const cacheBuster = Date.now();
// Use the login page hash for the recovery request as shown in the example
const url = `${this.baseUrl}?${this.sessionData.recoveryPageHash},gx-no-cache=${cacheBuster}`;
const payload = {
MPage: false,
cmpCtx: "",
parms: [false, request.ci.slice(0, -1), "", "", "", ""],
hsh: [],
objClass: "autogestion.wpaugrecuperarpassword",
pkgName: "com.mgkwebfrontend",
events: ["'CONFIRM'"],
grids: {},
};
// Build headers with session data - match exact order and headers from example
const requestHeaders = {
Accept: "*/*",
"Accept-Language": "es-ES,es;q=0.9",
"Cache-Control": "no-cache",
Connection: "keep-alive",
"Content-Type": "application/json",
GxAjaxRequest: "1",
Origin: "https://reservasweb.smi.com.uy",
Pragma: "no-cache",
Referer: "https://reservasweb.smi.com.uy/Autogestion/servlet/com.mgkwebfrontend.autogestion.wpaugrecuperarpassword",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
"sec-ch-ua": '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"Accept-Encoding": "gzip, deflate, br, zstd",
Cookie: this.buildCookieHeader(),
AJAX_SECURITY_TOKEN: this.sessionData.ajaxSecurityToken,
"X-GXAUTH-TOKEN": this.sessionData.gxAuthToken,
};
const response = await this.createAxiosInstance()
.post(url, payload, {
headers: requestHeaders,
timeout: 10000, // 10 second timeout
})
.catch((e) => {
return {
data: e.response?.data || {},
status: e.response?.status || 500,
};
});
// Check if we have a redirect command
if (response.data && response.data.gxCommands && response.data.gxCommands[0] && response.data.gxCommands[0].redirect) {
const toRedirect = response.data.gxCommands[0].redirect.url;
const finalUrl = `https://reservasweb.smi.com.uy/Autogestion/servlet/${toRedirect}`;
// Perform the final validation request
const finalResponse = await this.performFinalValidation(finalUrl);
return this.parseResponse(finalResponse, request.ci, Date.now() - startTime);
}
else {
throw new Error("not_registered");
}
}
catch (error) {
throw error; // Re-throw to be handled by the main catch block
}
}
/**
* Build cookie header from session data
* @returns Cookie header string
*/
buildCookieHeader() {
const cookies = [];
if (this.sessionData.jsessionId) {
cookies.push(`JSESSIONID=${this.sessionData.jsessionId}`);
}
if (this.sessionData.gxSessionId) {
cookies.push(`GX_SESSION_ID=${this.sessionData.gxSessionId}`);
}
// Add timezone cookie as seen in the original request
cookies.push("GxTZOffset=America/Montevideo");
return cookies.join("; ");
}
/**
* Extract user data from the final HTML response
* @param htmlContent - The HTML content from the final validation response
* @returns Extracted user data
*/
extractUserDataFromHTML(htmlContent) {
try {
const $ = cheerio.load(htmlContent);
// Check if this is the recovery page with user data
const hasRecoveryContent = $('span:contains("Recuperar contraseña")').length > 0;
if (!hasRecoveryContent) {
return {
isValidUser: false,
mensaje: "No se encontró contenido de recuperación de contraseña",
};
}
// Extract data from the script tag that contains gx.ajax.saveJsonResponse
const scriptTags = $("script").toArray();
let gxData = null;
for (const script of scriptTags) {
const scriptContent = $(script).html();
if (scriptContent && scriptContent.includes("gx.ajax.saveJsonResponse")) {
try {
// Find the start of the object after gx.ajax.saveJsonResponse(
const startIndex = scriptContent.indexOf("gx.ajax.saveJsonResponse(") + "gx.ajax.saveJsonResponse(".length;
if (startIndex > "gx.ajax.saveJsonResponse(".length - 1) {
// Find the matching closing parenthesis by counting braces
let braceCount = 0;
let inString = false;
let escapeNext = false;
let endIndex = -1;
for (let i = startIndex; i < scriptContent.length; i++) {
const char = scriptContent[i];
if (escapeNext) {
escapeNext = false;
continue;
}
if (char === "\\") {
escapeNext = true;
continue;
}
if (char === '"' || char === "'") {
if (!escapeNext) {
inString = !inString;
}
continue;
}
if (!inString) {
if (char === "{") {
braceCount++;
}
else if (char === "}") {
braceCount--;
if (braceCount === 0) {
endIndex = i;
break;
}
}
}
}
if (endIndex > startIndex) {
const jsObjectString = scriptContent.substring(startIndex, endIndex + 1);
// Convert JavaScript object notation to JSON by using eval in a safe context
// This is necessary because GeneXus generates JavaScript objects, not JSON
const safeEval = new Function("return " + jsObjectString);
gxData = safeEval();
break;
}
}
}
catch (parseError) {
console.error("Error parsing GeneXus data:", parseError);
}
}
}
// Extract user data from the gxHiddens object
const userData = {
isValidUser: false,
};
if (gxData && gxData.gxHiddens) {
const hiddens = gxData.gxHiddens;
// Extract user ID and CI
userData.perID = hiddens.vPERID;
userData.perCI = hiddens.vPERCI;
// Extract masked email and phone
userData.perMail = hiddens.vPERMAIL;
userData.domicTel = hiddens.vDOMICTEL;
// Extract recovery type
userData.tipoCodigoRecuperacion = hiddens.vTIPOCODIGO;
// Extract available recovery methods
if (hiddens.vMETODOS && Array.isArray(hiddens.vMETODOS)) {
userData.metodos = hiddens.vMETODOS.map((metodo) => ({
tipo: metodo.Tipo,
descripcion: metodo.Descripcion,
}));
}
// User is valid if we have both ID and CI
userData.isValidUser = !!(userData.perID && userData.perCI);
}
// Check for selection dropdown which indicates successful user validation
const hasMethodSelection = $("#vMETODOENVIO").length > 0;
if (hasMethodSelection && userData.perID) {
userData.isValidUser = true;
}
return userData;
}
catch (error) {
console.error("Error extracting user data from HTML:", error);
return {
isValidUser: false,
mensaje: "Error al procesar la respuesta HTML",
};
}
}
/**
* Parse the raw API response into our standardized format
* @param apiResponse - Raw response from SMI API
* @param ci - The CI number that was queried
* @param executionTime - Time taken for the request
* @returns Parsed SmiResponse
*/
parseResponse(apiResponse, ci, executionTime) {
try {
// Handle HTML response (form response)
if (typeof apiResponse === "string" && apiResponse.includes("<html")) {
// Use the new extraction function for HTML content
const userData = this.extractUserDataFromHTML(apiResponse);
if (userData.isValidUser) {
return {
success: true,
hasUser: true,
member: {
userData,
ci,
status: "registered",
executionTime,
},
};
}
else {
return {
success: true,
hasUser: false,
error: userData.mensaje || "Usuario no registrado en SMI",
};
}
}
// Handle JSON response from GeneXus
if (typeof apiResponse === "object") {
// Check for success indicators in GeneXus response
if (apiResponse.vISOK === true || apiResponse.vSUCCESS === true) {
return {
success: true,
hasUser: true,
member: {
ci,
status: "registered",
executionTime,
},
};
}
// Check for messages indicating user status
if (apiResponse.vMSG || apiResponse.messages) {
const message = (apiResponse.vMSG || (apiResponse.messages && apiResponse.messages[0]?.text) || "").toLowerCase();
if (message.includes("no encontrado") || message.includes("no existe") || message.includes("inválido") || message.includes("no registrado")) {
return {
success: true,
hasUser: false,
error: "Usuario no registrado en SMI",
};
}
else if (message.includes("encontrado") || message.includes("existe") || message.includes("válido")) {
return {
success: true,
hasUser: true,
member: {
ci,
status: "registered",
executionTime,
},
};
}
}
// Check for variables in GeneXus response that might indicate user status
if (apiResponse.variables) {
const vars = apiResponse.variables;
if (vars.vUSERFOUND === true || vars.vEXISTS === true) {
return {
success: true,
hasUser: true,
member: {
ci,
status: "registered",
executionTime,
},
};
}
}
}
// Default case - assume not found if we can't determine status
return {
success: true,
hasUser: false,
error: "No se pudo determinar el estado del usuario en SMI",
};
}
catch (parseError) {
return {
success: false,
hasUser: false,
error: "Error al procesar la respuesta de SMI",
};
}
}
/**
* Check if user exists in the SMI system
* @param request - Object containing the CI
* @returns Promise with boolean indicating if user exists
*/
async hasUser(request) {
const response = await this.checkUser(request);
return response.hasUser;
}
/**
* Get member information
* @param request - Object containing the CI
* @returns Promise with member data or null if not found
*/
async getMember(request) {
const response = await this.checkUser(request);
return response.member || null;
}
/**
* Extract detailed user data from HTML response
* @param htmlContent - HTML content from SMI response
* @returns Extracted user data with detailed information
*/
extractUserData(htmlContent) {
return this.extractUserDataFromHTML(htmlContent);
}
}
exports.SmiService = SmiService;
//# sourceMappingURL=Smi.js.map