Files
RPAInmigracion/uscis.mjs
T
2025-06-11 19:41:29 -05:00

423 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import path from 'path';
import { exec } from 'child_process';
import puppeteer from 'puppeteer-core';
import fetch from 'node-fetch';
import { google } from 'googleapis';
import { createWriteStream } from 'fs';
import fs from 'fs';
import { app } from 'electron';
console.log(app.getAppPath());
let url_cred = app.getAppPath().replace('app.asar', '');
url_cred = url_cred + '\\credenciales.json';
console.log(url_cred);
// Configuración
const CONFIG = {
PATHS: {
BASE_DIR: 'C:\\Users\\Drackxus\\Documents\\NODE\\migracion',
ARCHIVOS: 'C:\\Users\\Drackxus\\Documents\\NODE\\migracion\\archivos',
CREDENTIALS: url_cred
},
DRIVE: {
FOLDER_ID: '1bjEaxLFkv4toUt0SeUz_HqrvIWAI2Xv5',
SCOPES: [
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/spreadsheets'
]
},
PLATFORMS: {
USCIS: {
URL: 'https://egov.uscis.gov/es',
TYPE: 'uscis'
}
}
};
// Inicialización de Google APIs
const auth = new google.auth.GoogleAuth({
keyFile: CONFIG.PATHS.CREDENTIALS,
scopes: CONFIG.DRIVE.SCOPES
});
const sheets = google.sheets({ version: 'v4', auth });
const drive = google.drive({ version: 'v3', auth });
let browser; // Variable global para el navegador
// Funciones de Google Sheets
async function listarArchivosEnCarpeta(folderId) {
const res = await drive.files.list({
q: `'${folderId}' in parents and mimeType='application/vnd.google-apps.spreadsheet' and name='base_uscis'`,
fields: '*'
});
console.log(`${res.data.files.length} archivo(s) encontrados con el nombre "base_uscis"`);
return res.data.files;
}
async function obtenerDatosHoja(spreadsheetId) {
const response = await sheets.spreadsheets.values.get({
spreadsheetId,
range: 'A:H', // Ajusta el rango según tus necesidades
});
return response.data.values;
}
async function actualizarCelda(spreadsheetId, range, values) {
try {
await sheets.spreadsheets.values.update({
spreadsheetId,
range,
valueInputOption: 'USER_ENTERED',
requestBody: {
values: [values]
}
});
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) {
console.error('Error al actualizar celda:', error.message);
throw error;
}
}
// Funciones de manejo del navegador
async function cerrarChromeDebugging() {
return new Promise((resolve) => {
if (process.platform === 'win32') {
exec('wmic process where "commandline like \'%--remote-debugging-port=9223%\'" call terminate', (error) => {
if (error) {
console.log('⚠️ No se encontraron instancias de Chrome debugging');
} else {
console.log('🔒 Chrome debugging cerrado correctamente');
}
resolve();
});
} else {
exec("pkill -f 'chrome.*--remote-debugging-port=9223'", () => {
resolve();
});
}
});
}
async function iniciarChrome(url) {
return new Promise((resolve) => {
const chromeProcess = exec(
`start chrome `
+ `--remote-debugging-port=9223 `
+ `--no-sandbox `
+ `--disable-setuid-sandbox `
+ `--disable-background-timer-throttling `
+ `--disable-renderer-backgrounding `
+ `--disable-backgrounding-occluded-windows `
+ `--user-data-dir="C:\\temp\\chrome_debug_temp_uscis" `
+ `"${url}"`,
(error) => {
if (error) {
console.log('⚠️ Error al iniciar Chrome:', error);
}
resolve();
}
);
});
}
async function initBrowser(url) {
try {
await cerrarChromeDebugging();
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('🌐 Iniciando Chrome con debugging...');
await iniciarChrome(url);
await new Promise(resolve => setTimeout(resolve, 8000)); // Aumentado a 8 segundos
console.log('🔍 Intentando conectar con Chrome...');
const webSocketDebuggerUrl = await getWebSocketUrl();
console.log('✅ Conexión establecida con Chrome');
return await puppeteer.connect({
browserWSEndpoint: webSocketDebuggerUrl,
defaultViewport: null
});
} catch (error) {
console.error('❌ Error al inicializar el navegador:', error);
throw error;
}
}
async function getWebSocketUrl() {
let retries = 10; // Aumentado a 15 intentos
while (retries > 0) {
try {
const response = await fetch('http://localhost:9223/json/version');
if (!response.ok) throw new Error('No se pudo obtener la versión del navegador');
const json = await response.json();
if (json.webSocketDebuggerUrl) {
return json.webSocketDebuggerUrl;
}
throw new Error('URL de WebSocket no encontrada');
} catch (err) {
console.log(`🔄 Reintentando conexión a Chrome... (intentos restantes: ${retries})`);
await new Promise(resolve => setTimeout(resolve, 2000)); // Aumentado a 4 segundos
retries--;
}
}
throw new Error('No se pudo conectar a Chrome después de varios intentos');
}
// Funciones de utilidad
async function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Funciones de procesamiento USCIS
async function procesarCasoUSCIS(page, element, spreadsheetId, intentos = 1) {
const MAX_INTENTOS = 3;
try {
console.log(`⌛ Consultando caso USCIS... (Intento ${intentos}/${MAX_INTENTOS})`);
try {
await page.goto(CONFIG.PLATFORMS.USCIS.URL, {
waitUntil: 'domcontentloaded',
timeout: 20000
});
await page.waitForSelector('#receipt_number', { timeout: 10000 });
} catch (navigationError) {
console.log(' ⚠️ Error de navegación o campo no encontrado, reintentando...');
if (intentos < MAX_INTENTOS) {
return await procesarCasoUSCIS(page, element, spreadsheetId, intentos + 1);
} else {
throw new Error('No se pudo acceder a la página después de varios intentos');
}
}
// Intentar llenar el campo y hacer clic
await page.type('#receipt_number', element.valor);
await delay(1000);
await page.click('::-p-text(Verifique Estatus)');
await delay(2000);
// Esperar a que se complete la acción
// page.waitForSelector('.errorMessage', { timeout: 5000 }),
// page.waitForSelector('.conditionalLanding', { timeout: 5000 })
// Verificar el resultado
const errorMessage = await page.$('.errorMessage', { timeout: 5000 });
if (errorMessage) {
console.log(' ⚠️ Caso no válido');
await actualizarCelda(
spreadsheetId,
`G${element.fila}`,
['El número de recibo ingresado no es válido, intente nuevamente.']
);
} else {
try {
const [titulo, descripcion] = await Promise.all([
page.$eval('.conditionalLanding h2', el => el.innerText),
page.$eval('.conditionalLanding p', el => el.innerText)
]);
console.log(' ✅ Información obtenida');
console.log(` 📌 Estado: ${titulo}`);
await actualizarCelda(
spreadsheetId,
`G${element.fila}:H${element.fila}`,
[titulo, descripcion]
);
} catch (dataError) {
console.error(' ❌ Error al obtener datos de la página:', dataError);
throw dataError;
}
}
await delay(1000);
return true; // Indica que el proceso fue exitoso
} catch (error) {
console.error(' ❌ Error procesando caso USCIS:', error);
if (intentos < MAX_INTENTOS) {
console.log(` 🔄 Reintentando... (${intentos}/${MAX_INTENTOS})`);
return await procesarCasoUSCIS(page, element, spreadsheetId, intentos + 1);
} else {
console.log(' ⚠️ Se agotaron los intentos o error no recuperable');
await actualizarCelda(
spreadsheetId,
`G${element.fila}`,
['Error al procesar el caso. Por favor, intente más tarde.']
);
return false; // Indica que el proceso falló
}
}
}
// Modificar la función procesarArchivo para manejar mejor la reconexión de Chrome
async function procesarArchivo(archivo) {
console.log('\n🔍 Analizando archivo:', archivo.name);
const datos = await obtenerDatosHoja(archivo.id);
const platform_type = 'uscis';
const platform = CONFIG.PLATFORMS[platform_type.toUpperCase()].URL;
// const filasAProcesar = datos.slice(1).map((row, index) => ({
// valor: row[4],
// fila: index + 2,
// tieneValor: !row[5],
// ultimaConsulta: row[5] || ''
// })).filter(row => {
// if (!row.ultimaConsulta) return true;
// const fechaUltimaConsulta = new Date(row.ultimaConsulta);
// const ahora = new Date();
// const diferenciaHoras = (ahora - fechaUltimaConsulta) / (1000 * 60 * 60);
// return diferenciaHoras > 1;
// });
const filasAProcesar = datos.slice(1)
.map((row, index) => ({
valor: row[4], // Columna 5 (Valor a consultar)
fila: index + 2, // Número de fila real
ultimaConsulta: row[5], // Columna 6 (Fecha de última consulta)
resultado: row[6] // Columna 7 (Resultado)
}))
.filter(row => {
// Priorizar si la columna G (resultado) está vacía
if (!row.resultado) return true;
// Si la columna G no está vacía, verificar la fecha en la columna F
if (!row.ultimaConsulta) return true; // Si no hay fecha en F, también se procesa (aunque G no esté vacía)
// Parse date in Spanish format DD/MM/YYYY
const [date, time] = row.ultimaConsulta.split(', ');
const [day, month, year] = date.split('/');
const [hours, minutes, seconds] = time.split(':');
const fechaUltimaConsulta = new Date(year, month - 1, day, hours, minutes, seconds);
const ahora = new Date();
// Calculate hours difference
const diferenciaHoras = (ahora - fechaUltimaConsulta) / (1000 * 60 * 60);
// For future dates, return false to skip processing
if (fechaUltimaConsulta > ahora) return false;
return diferenciaHoras > 1; // Process only if more than 1 hour has passed
});
console.log(filasAProcesar);
console.log(`📊 Total de filas a procesar: ${filasAProcesar.length}`);
if (filasAProcesar.length > 0) {
console.log('🌐 Iniciando navegador...');
let browser = await initBrowser(platform);
let page = (await browser.pages())[0];
let fallosConsecutivos = 0;
const MAX_FALLOS_CONSECUTIVOS = 3;
let procesadas = 0;
for (const element of filasAProcesar) {
procesadas++;
console.log(`\n⏳ Procesando fila ${element.fila} (${procesadas}/${filasAProcesar.length})`);
console.log(`📝 Valor a consultar: ${element.valor}`);
// Guardar la fecha y hora actual
const fechaHoraActual = new Date().toLocaleString('es-ES', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
// Actualizar la marca de tiempo en la columna F y limpiar las columnas G y H
await Promise.all([
actualizarCelda(
archivo.id,
`F${element.fila}`,
[fechaHoraActual]
),
actualizarCelda(
archivo.id,
`G${element.fila}:H${element.fila}`,
['', '']
)
]);
const resultado = await procesarCasoUSCIS(page, element, archivo.id);
if (!resultado) {
fallosConsecutivos++;
if (fallosConsecutivos >= MAX_FALLOS_CONSECUTIVOS) {
console.log('🔄 Reiniciando navegador debido a fallos consecutivos...');
await browser.close();
await cerrarChromeDebugging();
await delay(2000);
browser = await initBrowser(platform);
page = (await browser.pages())[0];
fallosConsecutivos = 0;
}
} else {
fallosConsecutivos = 0;
}
}
console.log('\n✅ Proceso completado para el archivo:', archivo.name);
await browser.close();
} else {
console.log('️ No hay filas para procesar en este archivo');
}
}
// Función para cerrar el navegador y la aplicación
async function cerrarTodo() {
if (browser) {
await browser.close(); // Cerrar el navegador si está abierto
}
console.log('🔄 Cerrando Chrome debugging y finalizando...');
await cerrarChromeDebugging();
process.exit();
app.quit(); // Cerrar la aplicación Electron
}
// Manejar la señal SIGINT (Ctrl + C)
process.on('SIGINT', async () => {
console.log('Recibida señal de interrupción (Ctrl + C)');
await cerrarTodo();
});
// Función principal
async function procesarArchivos() {
try {
console.log('🚀 Iniciando proceso...');
const archivos = await listarArchivosEnCarpeta(CONFIG.DRIVE.FOLDER_ID);
console.log(`📁 Total de archivos encontrados: ${archivos.length}`);
let procesados = 0;
for (const archivo of archivos) {
procesados++;
console.log(`\n📌 Procesando archivo ${procesados}/${archivos.length}: ${archivo.name}`);
await procesarArchivo(archivo);
}
console.log('\n🎉 Proceso completado exitosamente!');
} catch (error) {
console.error('❌ Error en el proceso:', error);
} finally {
await cerrarTodo(); // Asegúrate de cerrar todo al final
}
}
// Ejecutar el proceso
app.on('ready', () => {
console.log('Aplicación Electron está lista y en ejecución sin ventana.');
procesarArchivos();
});