arca-siradig 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +311 -0
- package/dist/src/arca/deductions.d.ts +71 -0
- package/dist/src/arca/deductions.js +636 -0
- package/dist/src/arca/employees.d.ts +10 -0
- package/dist/src/arca/employees.js +80 -0
- package/dist/src/arca/form.d.ts +39 -0
- package/dist/src/arca/form.js +143 -0
- package/dist/src/arca/http.d.ts +14 -0
- package/dist/src/arca/http.js +108 -0
- package/dist/src/arca/menu.d.ts +6 -0
- package/dist/src/arca/menu.js +36 -0
- package/dist/src/arca/personal-data.d.ts +4 -0
- package/dist/src/arca/personal-data.js +62 -0
- package/dist/src/arca/persons.d.ts +11 -0
- package/dist/src/arca/persons.js +115 -0
- package/dist/src/arca/receipt-types.d.ts +4 -0
- package/dist/src/arca/receipt-types.js +39 -0
- package/dist/src/arca/session.d.ts +27 -0
- package/dist/src/arca/session.js +165 -0
- package/dist/src/arca/submission.d.ts +22 -0
- package/dist/src/arca/submission.js +140 -0
- package/dist/src/arca/submitted-forms.d.ts +12 -0
- package/dist/src/arca/submitted-forms.js +54 -0
- package/dist/src/cli/errors.d.ts +1 -0
- package/dist/src/cli/errors.js +11 -0
- package/dist/src/cli/help.d.ts +6 -0
- package/dist/src/cli/help.js +301 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/cli/index.js +810 -0
- package/dist/src/cli/options.d.ts +120 -0
- package/dist/src/cli/options.js +419 -0
- package/dist/src/cli/prompts.d.ts +9 -0
- package/dist/src/cli/prompts.js +99 -0
- package/dist/src/cli/render.d.ts +13 -0
- package/dist/src/cli/render.js +262 -0
- package/dist/src/cli/structured-output.d.ts +4 -0
- package/dist/src/cli/structured-output.js +92 -0
- package/dist/src/files/pdf.d.ts +3 -0
- package/dist/src/files/pdf.js +43 -0
- package/dist/src/files/private.d.ts +4 -0
- package/dist/src/files/private.js +21 -0
- package/dist/src/security/errors.d.ts +2 -0
- package/dist/src/security/errors.js +22 -0
- package/dist/src/session/store.d.ts +15 -0
- package/dist/src/session/store.js +35 -0
- package/dist/src/types.d.ts +106 -0
- package/dist/src/types.js +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { addDeductionMonthlyItem, addDeductionReceiptItem, extractDeductionDetail, findDeductionById, openDeductionDetail, openDeductionsMenu, readDeductions, removeMedicalExpenseReceiptItem, } from "../arca/deductions.js";
|
|
4
|
+
import { extractEmployeeData, findEmployeeByCuit, openEmployeeDetails, readEmployees, } from "../arca/employees.js";
|
|
5
|
+
import { openFormPreview, printFormDraft, readFormSummary, selectFormPeriod, verifyFormContext, } from "../arca/form.js";
|
|
6
|
+
import { listMenuItems, returnToMainMenu, selectMenuItem } from "../arca/menu.js";
|
|
7
|
+
import { extractPersonalData } from "../arca/personal-data.js";
|
|
8
|
+
import { acceptReminderModal, acceptReminderModalIfPresent, listPersons, selectPerson, waitForPersonSelection, } from "../arca/persons.js";
|
|
9
|
+
import { listReceiptTypes } from "../arca/receipt-types.js";
|
|
10
|
+
import { ARCA_LOGIN_URL, ARCA_PORTAL_URL, ArcaLoginError, closeBrowserSession, createBrowserSession, isExpiredPortalSessionUrl, loginToArca, openSiradigFromPortal, } from "../arca/session.js";
|
|
11
|
+
import { submitForm } from "../arca/submission.js";
|
|
12
|
+
import { createSessionStore } from "../session/store.js";
|
|
13
|
+
import { formatCliError } from "./errors.js";
|
|
14
|
+
import { renderHelp } from "./help.js";
|
|
15
|
+
import { parseCliOptions } from "./options.js";
|
|
16
|
+
import { promptCredentials, promptFormSubmission, promptMenuItem, promptMissingCredentials, promptPerson, promptPersonalDataAction, } from "./prompts.js";
|
|
17
|
+
import { printDeductionDetail, printDeductions, printEmployeeData, printEmployees, printFormSummary, printPersonalData, printReceiptTypes, renderFormSummary, } from "./render.js";
|
|
18
|
+
import { toStructuredDeductionDetail, toStructuredEmployeeData, toStructuredPersonalData, } from "./structured-output.js";
|
|
19
|
+
const PERSON_SELECTION_URL = "https://serviciosjava2.afip.gob.ar/radig/jsp/menu_sel_empresa.jsp";
|
|
20
|
+
async function main() {
|
|
21
|
+
const command = parseCliOptions(process.argv.slice(2));
|
|
22
|
+
const store = createSessionStore();
|
|
23
|
+
switch (command.kind) {
|
|
24
|
+
case "help":
|
|
25
|
+
console.log(renderHelp(command.topic, {
|
|
26
|
+
width: process.stdout.columns,
|
|
27
|
+
color: Boolean(process.stdout.isTTY) && !process.env.NO_COLOR,
|
|
28
|
+
}));
|
|
29
|
+
return;
|
|
30
|
+
case "interactive":
|
|
31
|
+
await runInteractive(command);
|
|
32
|
+
return;
|
|
33
|
+
case "login":
|
|
34
|
+
await runLogin(command, store);
|
|
35
|
+
return;
|
|
36
|
+
case "logout":
|
|
37
|
+
await runLogout(store);
|
|
38
|
+
return;
|
|
39
|
+
case "persons-list":
|
|
40
|
+
await runPersonsList(command, store);
|
|
41
|
+
return;
|
|
42
|
+
case "persons-select":
|
|
43
|
+
await runPersonsSelect(command, store);
|
|
44
|
+
return;
|
|
45
|
+
case "persons-personal-data-show":
|
|
46
|
+
await runPersonPersonalDataShow(command, store);
|
|
47
|
+
return;
|
|
48
|
+
case "persons-employees-list":
|
|
49
|
+
await runPersonEmployeesList(command, store);
|
|
50
|
+
return;
|
|
51
|
+
case "persons-employees-show":
|
|
52
|
+
await runPersonEmployeesShow(command, store);
|
|
53
|
+
return;
|
|
54
|
+
case "form-summary":
|
|
55
|
+
case "form-show":
|
|
56
|
+
case "form-print":
|
|
57
|
+
await runFormPreview(command, store);
|
|
58
|
+
return;
|
|
59
|
+
case "form-submit":
|
|
60
|
+
await runFormSubmit(command, store);
|
|
61
|
+
return;
|
|
62
|
+
case "form-deductions-list":
|
|
63
|
+
await runFormDeductionsList(command, store);
|
|
64
|
+
return;
|
|
65
|
+
case "form-deductions-list-receipt-types":
|
|
66
|
+
await runFormDeductionsListReceiptTypes(command);
|
|
67
|
+
return;
|
|
68
|
+
case "form-deductions-show":
|
|
69
|
+
await runFormDeductionsShow(command, store);
|
|
70
|
+
return;
|
|
71
|
+
case "form-deductions-add-item":
|
|
72
|
+
await runFormDeductionsAddItem(command, store);
|
|
73
|
+
return;
|
|
74
|
+
case "form-deductions-add-item-receipt":
|
|
75
|
+
await runFormDeductionsAddItemReceipt(command, store);
|
|
76
|
+
return;
|
|
77
|
+
case "form-deductions-remove-item-receipt":
|
|
78
|
+
await runFormDeductionsRemoveItemReceipt(command, store);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function runLogin(command, store) {
|
|
83
|
+
if (!command.hasExplicitCredentials) {
|
|
84
|
+
const currentSession = await store.read();
|
|
85
|
+
if (currentSession && (await canListPersons(command.headless, currentSession.storageState))) {
|
|
86
|
+
console.log(`Ya estás logueado con CUIT/CUIL ${currentSession.cuit}.`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const credentials = await promptMissingCredentials({
|
|
91
|
+
cuit: command.cuit,
|
|
92
|
+
password: command.password,
|
|
93
|
+
}, {
|
|
94
|
+
cuit: process.env.ARCA_CUIT,
|
|
95
|
+
password: process.env.ARCA_PASSWORD,
|
|
96
|
+
});
|
|
97
|
+
const session = await createBrowserSession({ headless: command.headless });
|
|
98
|
+
try {
|
|
99
|
+
console.log("\nIngresando a ARCA...");
|
|
100
|
+
await loginToArca(session.page, credentials);
|
|
101
|
+
console.log("Abriendo SiRADIG - Trabajador...");
|
|
102
|
+
session.page = await openSiradigFromPortal(session.page);
|
|
103
|
+
const selectedPerson = await autoSelectFirstPersonAfterLogin(session);
|
|
104
|
+
if (selectedPerson) {
|
|
105
|
+
await saveSelectedPersonSession(store, credentials.cuit, session, selectedPerson);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
await store.write({
|
|
109
|
+
cuit: credentials.cuit,
|
|
110
|
+
createdAt: new Date().toISOString(),
|
|
111
|
+
storageState: await session.context.storageState(),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
console.log(`Sesión guardada para CUIT/CUIL ${credentials.cuit}.`);
|
|
115
|
+
if (selectedPerson) {
|
|
116
|
+
console.log(`Persona seleccionada: ${selectedPerson.name}${selectedPerson.id ? ` (ID de selección: ${selectedPerson.id})` : ""}.`);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log("No se encontraron personas en la pantalla de selección de SiRADIG.");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
await closeBrowserSession(session);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function runLogout(store) {
|
|
127
|
+
const currentSession = await store.read();
|
|
128
|
+
await store.clear();
|
|
129
|
+
if (!currentSession) {
|
|
130
|
+
console.log("No había una sesión guardada.");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
console.log(`Sesión eliminada para CUIT/CUIL ${currentSession.cuit}.`);
|
|
134
|
+
}
|
|
135
|
+
async function autoSelectFirstPersonAfterLogin(session) {
|
|
136
|
+
await waitForPersonSelection(session.page);
|
|
137
|
+
await acceptReminderModalIfPresent(session.page);
|
|
138
|
+
const persons = await listPersons(session.page);
|
|
139
|
+
const selectedPerson = persons[0] ?? null;
|
|
140
|
+
if (!selectedPerson) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
await selectPerson(session.page, selectedPerson.id ?? selectedPerson.name);
|
|
144
|
+
await acceptReminderModal(session.page, { timeoutMs: 5_000 });
|
|
145
|
+
return selectedPerson;
|
|
146
|
+
}
|
|
147
|
+
async function runPersonsList(command, store) {
|
|
148
|
+
const activeSession = await openStoredSession(command.headless, store);
|
|
149
|
+
if (!activeSession) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const { session, cuit } = activeSession;
|
|
153
|
+
try {
|
|
154
|
+
if (!(await openPersonSelectionPage(session, store, cuit))) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
await acceptReminderModalIfPresent(session.page);
|
|
158
|
+
const persons = await listPersons(session.page);
|
|
159
|
+
if (persons.length === 0) {
|
|
160
|
+
console.error("No se pudieron listar personas. Ejecutá `arca-siradig login`.");
|
|
161
|
+
process.exitCode = 1;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (command.format === "json") {
|
|
165
|
+
console.log(JSON.stringify(persons));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
console.log("Personas disponibles:");
|
|
169
|
+
for (const person of persons) {
|
|
170
|
+
console.log(formatPersonText(person.id, person.name));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
await closeBrowserSession(session);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function runPersonsSelect(command, store) {
|
|
178
|
+
const activeSession = await openStoredSession(command.headless, store);
|
|
179
|
+
if (!activeSession) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const { session, cuit } = activeSession;
|
|
183
|
+
try {
|
|
184
|
+
if (!(await openPersonSelectionPage(session, store, cuit))) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
await acceptReminderModalIfPresent(session.page);
|
|
188
|
+
const persons = await listPersons(session.page);
|
|
189
|
+
if (persons.length === 0) {
|
|
190
|
+
console.error("No se pudieron listar personas. Ejecutá `arca-siradig login`.");
|
|
191
|
+
process.exitCode = 1;
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const selectedPerson = persons.find((person) => person.id === command.person || person.name === command.person);
|
|
195
|
+
if (!selectedPerson) {
|
|
196
|
+
console.error(`No se encontró la persona "${command.person}".`);
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
await selectPerson(session.page, command.person);
|
|
201
|
+
await acceptReminderModal(session.page, { timeoutMs: 5_000 });
|
|
202
|
+
await saveSelectedPersonSession(store, cuit, session, selectedPerson);
|
|
203
|
+
console.log(`Persona seleccionada: ${selectedPerson.name}${selectedPerson.id ? ` (ID de selección: ${selectedPerson.id})` : ""}.`);
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
await closeBrowserSession(session);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function runPersonPersonalDataShow(command, store) {
|
|
210
|
+
const activeSession = await openStoredSession(command.headless, store);
|
|
211
|
+
if (!activeSession) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const { session, cuit } = activeSession;
|
|
215
|
+
try {
|
|
216
|
+
const personReady = await ensurePersonSelected(session, store, cuit, command.person, activeSession.selectedPerson);
|
|
217
|
+
if (!personReady) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (!(await navigateWithSessionRefresh(session, store, cuit, (page) => selectMenuItem(page, "Datos Personales")))) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const personalData = await extractPersonalData(session.page);
|
|
224
|
+
if (command.format === "json") {
|
|
225
|
+
console.log(JSON.stringify(toStructuredPersonalData(personalData)));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
printPersonalData(personalData);
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
await closeBrowserSession(session);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function runPersonEmployeesList(command, store) {
|
|
235
|
+
const activeSession = await openStoredSession(command.headless, store);
|
|
236
|
+
if (!activeSession) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const { session, cuit } = activeSession;
|
|
240
|
+
try {
|
|
241
|
+
const personReady = await ensurePersonSelected(session, store, cuit, command.person, activeSession.selectedPerson);
|
|
242
|
+
if (!personReady) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const employees = await readWithSessionRefresh(session, store, cuit, readEmployees);
|
|
246
|
+
if (employees === null)
|
|
247
|
+
return;
|
|
248
|
+
if (command.format === "json") {
|
|
249
|
+
console.log(JSON.stringify(employees));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
printEmployees(employees);
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
await closeBrowserSession(session);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function runPersonEmployeesShow(command, store) {
|
|
259
|
+
const activeSession = await openStoredSession(command.headless, store);
|
|
260
|
+
if (!activeSession) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const { session, cuit } = activeSession;
|
|
264
|
+
try {
|
|
265
|
+
const personReady = await ensurePersonSelected(session, store, cuit, command.person, activeSession.selectedPerson);
|
|
266
|
+
if (!personReady) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const employees = await readWithSessionRefresh(session, store, cuit, readEmployees);
|
|
270
|
+
if (employees === null)
|
|
271
|
+
return;
|
|
272
|
+
const employee = findEmployeeByCuit(employees, command.employeeCuit);
|
|
273
|
+
if (!employee) {
|
|
274
|
+
console.error(`No se encontró un empleador con CUIT ${command.employeeCuit}.`);
|
|
275
|
+
process.exitCode = 1;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (!(await navigateWithSessionRefresh(session, store, cuit, (page) => openEmployeeDetails(page, employee.id)))) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const employeeData = await extractEmployeeData(session.page);
|
|
282
|
+
if (command.format === "json") {
|
|
283
|
+
console.log(JSON.stringify(toStructuredEmployeeData(employeeData)));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
printEmployeeData(employeeData);
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
await closeBrowserSession(session);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function printFormContext(context) {
|
|
293
|
+
console.log(`Persona: ${context.person.id} - ${context.person.name}\nPeríodo fiscal: ${context.period}`);
|
|
294
|
+
}
|
|
295
|
+
async function runFormPreview(command, store) {
|
|
296
|
+
const active = await openStoredSession(command.headless, store);
|
|
297
|
+
if (!active)
|
|
298
|
+
return;
|
|
299
|
+
const { session, cuit } = active;
|
|
300
|
+
try {
|
|
301
|
+
const personReady = await ensurePersonSelected(session, store, cuit, command.person, active.selectedPerson);
|
|
302
|
+
if (!personReady)
|
|
303
|
+
return;
|
|
304
|
+
const context = await selectFormPeriod(session.page, {
|
|
305
|
+
period: command.period,
|
|
306
|
+
person: personReady.name,
|
|
307
|
+
selector: personReady.id ?? personReady.name,
|
|
308
|
+
});
|
|
309
|
+
if (command.kind === "form-show") {
|
|
310
|
+
if (command.format === "json")
|
|
311
|
+
console.log(JSON.stringify(context));
|
|
312
|
+
else
|
|
313
|
+
printFormContext(context);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (!(await navigateWithSessionRefresh(session, store, cuit, openFormPreview, context)))
|
|
317
|
+
return;
|
|
318
|
+
await verifyFormContext(session.page, { period: context.period, person: context.person.id });
|
|
319
|
+
if (command.kind === "form-print") {
|
|
320
|
+
if (!command.path)
|
|
321
|
+
throw new Error("--path is required.");
|
|
322
|
+
const savedPath = await printFormDraft(session.page, command.path, context.period);
|
|
323
|
+
printFormContext(context);
|
|
324
|
+
console.log(`Borrador guardado en: ${savedPath}`);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const summary = await readFormSummary(session.page);
|
|
328
|
+
if (command.format === "json") {
|
|
329
|
+
console.log(JSON.stringify(summary));
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
printFormSummary(summary);
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
await closeBrowserSession(session);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function runFormSubmit(command, store) {
|
|
339
|
+
if (!command.accept && !process.stdin.isTTY) {
|
|
340
|
+
throw new Error("El envío requiere aceptar o cancelar en una terminal interactiva. Para confirmar directamente usá --aceptar.");
|
|
341
|
+
}
|
|
342
|
+
const active = await openStoredSession(command.headless, store);
|
|
343
|
+
if (!active)
|
|
344
|
+
return;
|
|
345
|
+
const { session, cuit } = active;
|
|
346
|
+
try {
|
|
347
|
+
const selectedPerson = await ensurePersonSelected(session, store, cuit, command.person, active.selectedPerson);
|
|
348
|
+
if (!selectedPerson)
|
|
349
|
+
return;
|
|
350
|
+
const context = await selectFormPeriod(session.page, {
|
|
351
|
+
period: command.period,
|
|
352
|
+
person: selectedPerson.name,
|
|
353
|
+
selector: selectedPerson.id ?? selectedPerson.name,
|
|
354
|
+
});
|
|
355
|
+
if (!(await navigateWithSessionRefresh(session, store, cuit, openFormPreview, context)))
|
|
356
|
+
return;
|
|
357
|
+
await verifyFormContext(session.page, { period: context.period, person: context.person.id });
|
|
358
|
+
const summary = await readFormSummary(session.page);
|
|
359
|
+
console.error(renderFormSummary(summary, {
|
|
360
|
+
color: Boolean(process.stderr.isTTY) && !process.env.NO_COLOR,
|
|
361
|
+
width: process.stderr.columns,
|
|
362
|
+
}));
|
|
363
|
+
const result = await submitForm(session.page, context, {
|
|
364
|
+
accept: command.accept,
|
|
365
|
+
confirm: async (message) => {
|
|
366
|
+
console.error(`\n${message}\n`);
|
|
367
|
+
return promptFormSubmission();
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
if (command.format === "json")
|
|
371
|
+
console.log(JSON.stringify(result));
|
|
372
|
+
else {
|
|
373
|
+
console.log(`\n${result.message}`);
|
|
374
|
+
if (result.reference)
|
|
375
|
+
console.log(`Número de presentación: ${result.reference}`);
|
|
376
|
+
}
|
|
377
|
+
if (result.status === "rejected" || result.status === "unconfirmed")
|
|
378
|
+
process.exitCode = 1;
|
|
379
|
+
}
|
|
380
|
+
finally {
|
|
381
|
+
await closeBrowserSession(session);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async function runFormDeductionsList(command, store) {
|
|
385
|
+
const activeSession = await openStoredSession(command.headless, store);
|
|
386
|
+
if (!activeSession) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const { session, cuit } = activeSession;
|
|
390
|
+
try {
|
|
391
|
+
const personReady = await ensurePersonSelected(session, store, cuit, command.person, activeSession.selectedPerson);
|
|
392
|
+
if (!personReady) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const formContext = await selectFormPeriod(session.page, {
|
|
396
|
+
period: command.period,
|
|
397
|
+
person: personReady.name,
|
|
398
|
+
selector: personReady.id ?? personReady.name,
|
|
399
|
+
});
|
|
400
|
+
const deductions = await readWithSessionRefresh(session, store, cuit, readDeductions, formContext);
|
|
401
|
+
if (deductions === null)
|
|
402
|
+
return;
|
|
403
|
+
if (command.format === "json") {
|
|
404
|
+
console.log(JSON.stringify({ ...formContext, deductions }));
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
printFormContext(formContext);
|
|
408
|
+
printDeductions(deductions);
|
|
409
|
+
}
|
|
410
|
+
finally {
|
|
411
|
+
await closeBrowserSession(session);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
async function runFormDeductionsListReceiptTypes(command) {
|
|
415
|
+
const receiptTypes = listReceiptTypes();
|
|
416
|
+
if (command.format === "json") {
|
|
417
|
+
console.log(JSON.stringify(receiptTypes));
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
printReceiptTypes(receiptTypes);
|
|
421
|
+
}
|
|
422
|
+
async function runFormDeductionsShow(command, store) {
|
|
423
|
+
const activeSession = await openStoredSession(command.headless, store);
|
|
424
|
+
if (!activeSession) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const { session, cuit } = activeSession;
|
|
428
|
+
try {
|
|
429
|
+
const personReady = await ensurePersonSelected(session, store, cuit, command.person, activeSession.selectedPerson);
|
|
430
|
+
if (!personReady) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const formContext = await selectFormPeriod(session.page, {
|
|
434
|
+
period: command.period,
|
|
435
|
+
person: personReady.name,
|
|
436
|
+
selector: personReady.id ?? personReady.name,
|
|
437
|
+
});
|
|
438
|
+
if (!(await navigateWithSessionRefresh(session, store, cuit, openDeductionsMenu, formContext))) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const deduction = await findDeductionById(session.page, command.deductionId);
|
|
442
|
+
if (!deduction) {
|
|
443
|
+
console.error(`No se encontró una deducción con ID ${command.deductionId}.`);
|
|
444
|
+
process.exitCode = 1;
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (!(await navigateWithSessionRefresh(session, store, cuit, (page) => openDeductionDetail(page, deduction.editUrl, deduction.id), formContext))) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const deductionDetail = await extractDeductionDetail(session.page);
|
|
451
|
+
if (command.format === "json") {
|
|
452
|
+
console.log(JSON.stringify({ ...formContext, deduction: toStructuredDeductionDetail(deductionDetail) }));
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
printFormContext(formContext);
|
|
456
|
+
printDeductionDetail(deductionDetail);
|
|
457
|
+
}
|
|
458
|
+
finally {
|
|
459
|
+
await closeBrowserSession(session);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async function runFormDeductionsAddItem(command, store) {
|
|
463
|
+
const activeSession = await openStoredSession(command.headless, store);
|
|
464
|
+
if (!activeSession) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const { session, cuit } = activeSession;
|
|
468
|
+
try {
|
|
469
|
+
const personReady = await ensurePersonSelected(session, store, cuit, command.person, activeSession.selectedPerson);
|
|
470
|
+
if (!personReady) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const formContext = await selectFormPeriod(session.page, {
|
|
474
|
+
period: command.period,
|
|
475
|
+
person: personReady.name,
|
|
476
|
+
selector: personReady.id ?? personReady.name,
|
|
477
|
+
});
|
|
478
|
+
await verifyFormContext(session.page, {
|
|
479
|
+
period: formContext.period,
|
|
480
|
+
person: formContext.person.id,
|
|
481
|
+
});
|
|
482
|
+
printFormContext(formContext);
|
|
483
|
+
await addDeductionMonthlyItem(session.page, {
|
|
484
|
+
deductionType: command.deductionType,
|
|
485
|
+
cuit: command.cuit,
|
|
486
|
+
month: command.month,
|
|
487
|
+
amount: command.amount,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
handleCommandError(error);
|
|
492
|
+
}
|
|
493
|
+
finally {
|
|
494
|
+
await closeBrowserSession(session);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
async function runFormDeductionsAddItemReceipt(command, store) {
|
|
498
|
+
const activeSession = await openStoredSession(command.headless, store);
|
|
499
|
+
if (!activeSession) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const { session, cuit } = activeSession;
|
|
503
|
+
try {
|
|
504
|
+
const personReady = await ensurePersonSelected(session, store, cuit, command.person, activeSession.selectedPerson);
|
|
505
|
+
if (!personReady) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const formContext = await selectFormPeriod(session.page, {
|
|
509
|
+
period: command.period,
|
|
510
|
+
person: personReady.name,
|
|
511
|
+
selector: personReady.id ?? personReady.name,
|
|
512
|
+
});
|
|
513
|
+
await verifyFormContext(session.page, {
|
|
514
|
+
period: formContext.period,
|
|
515
|
+
person: formContext.person.id,
|
|
516
|
+
});
|
|
517
|
+
printFormContext(formContext);
|
|
518
|
+
await addDeductionReceiptItem(session.page, {
|
|
519
|
+
deductionType: command.deductionType,
|
|
520
|
+
cuit: command.cuit,
|
|
521
|
+
month: command.month,
|
|
522
|
+
date: command.date,
|
|
523
|
+
receiptType: command.receiptType,
|
|
524
|
+
number: command.number,
|
|
525
|
+
amount: command.amount,
|
|
526
|
+
reimbursedAmount: command.reimbursedAmount,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
handleCommandError(error);
|
|
531
|
+
}
|
|
532
|
+
finally {
|
|
533
|
+
await closeBrowserSession(session);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
async function runFormDeductionsRemoveItemReceipt(command, store) {
|
|
537
|
+
const activeSession = await openStoredSession(command.headless, store);
|
|
538
|
+
if (!activeSession) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const { session, cuit } = activeSession;
|
|
542
|
+
try {
|
|
543
|
+
const personReady = await ensurePersonSelected(session, store, cuit, command.person, activeSession.selectedPerson);
|
|
544
|
+
if (!personReady) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const formContext = await selectFormPeriod(session.page, {
|
|
548
|
+
period: command.period,
|
|
549
|
+
person: personReady.name,
|
|
550
|
+
selector: personReady.id ?? personReady.name,
|
|
551
|
+
});
|
|
552
|
+
if (command.deductionType !== "gastos-medicos") {
|
|
553
|
+
throw new Error(`remove-item-receipt does not support type ${command.deductionType}.`);
|
|
554
|
+
}
|
|
555
|
+
await verifyFormContext(session.page, {
|
|
556
|
+
period: formContext.period,
|
|
557
|
+
person: formContext.person.id,
|
|
558
|
+
});
|
|
559
|
+
printFormContext(formContext);
|
|
560
|
+
await removeMedicalExpenseReceiptItem(session.page, {
|
|
561
|
+
cuit: command.cuit,
|
|
562
|
+
number: command.number,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
handleCommandError(error);
|
|
567
|
+
}
|
|
568
|
+
finally {
|
|
569
|
+
await closeBrowserSession(session);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
async function canListPersons(headless, storageState) {
|
|
573
|
+
const session = await createBrowserSession({ headless, storageState });
|
|
574
|
+
try {
|
|
575
|
+
await session.page.goto(PERSON_SELECTION_URL, { waitUntil: "domcontentloaded" });
|
|
576
|
+
if (isExpiredPortalSessionUrl(session.page.url())) {
|
|
577
|
+
await session.page.goto(ARCA_PORTAL_URL, { waitUntil: "domcontentloaded" });
|
|
578
|
+
if (await isLoginPage(session.page)) {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
session.page = await openSiradigFromPortal(session.page);
|
|
582
|
+
await session.page.goto(PERSON_SELECTION_URL, { waitUntil: "domcontentloaded" });
|
|
583
|
+
}
|
|
584
|
+
if (await isLoginPage(session.page)) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
return (await listPersons(session.page)).length > 0;
|
|
588
|
+
}
|
|
589
|
+
catch {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
finally {
|
|
593
|
+
await closeBrowserSession(session);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
async function openStoredSession(headless, store) {
|
|
597
|
+
const storedSession = await store.read();
|
|
598
|
+
if (!storedSession) {
|
|
599
|
+
console.error("No hay una sesión guardada. Ejecutá `arca-siradig login`.");
|
|
600
|
+
process.exitCode = 1;
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
cuit: storedSession.cuit,
|
|
605
|
+
selectedPerson: storedSession.selectedPerson ?? null,
|
|
606
|
+
session: await createBrowserSession({
|
|
607
|
+
headless,
|
|
608
|
+
storageState: storedSession.storageState,
|
|
609
|
+
}),
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
async function openPersonSelectionPage(session, store, cuit) {
|
|
613
|
+
if (!(await navigateWithSessionRefresh(session, store, cuit, (page) => page.goto(PERSON_SELECTION_URL, { waitUntil: "domcontentloaded" })))) {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
if (await isLoginPage(session.page)) {
|
|
617
|
+
console.error("La sesión expiró. Ejecutá `arca-siradig login`.");
|
|
618
|
+
process.exitCode = 1;
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
async function ensurePersonSelected(session, store, cuit, personIdentifier, storedSelectedPerson) {
|
|
624
|
+
const targetPerson = await resolveTargetPerson(personIdentifier, storedSelectedPerson);
|
|
625
|
+
if (!targetPerson) {
|
|
626
|
+
console.error(formatNoSelectedPersonMessage());
|
|
627
|
+
process.exitCode = 1;
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
if (!(await openPersonSelectionPage(session, store, cuit))) {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
await acceptReminderModalIfPresent(session.page);
|
|
634
|
+
const persons = await listPersons(session.page);
|
|
635
|
+
if (persons.length === 0) {
|
|
636
|
+
console.error("No se pudieron listar personas. Ejecutá `arca-siradig login`.");
|
|
637
|
+
process.exitCode = 1;
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
const selectedPerson = persons.find((person) => person.id === targetPerson || person.name === targetPerson);
|
|
641
|
+
if (!selectedPerson) {
|
|
642
|
+
console.error(`No se encontró la persona "${targetPerson}".`);
|
|
643
|
+
process.exitCode = 1;
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
await selectPerson(session.page, selectedPerson.id ?? selectedPerson.name);
|
|
647
|
+
await acceptReminderModal(session.page, { timeoutMs: 5_000 });
|
|
648
|
+
await saveSelectedPersonSession(store, cuit, session, selectedPerson);
|
|
649
|
+
return selectedPerson;
|
|
650
|
+
}
|
|
651
|
+
async function resolveTargetPerson(personIdentifier, storedSelectedPerson) {
|
|
652
|
+
if (personIdentifier) {
|
|
653
|
+
return personIdentifier;
|
|
654
|
+
}
|
|
655
|
+
if (storedSelectedPerson?.id) {
|
|
656
|
+
return storedSelectedPerson.id;
|
|
657
|
+
}
|
|
658
|
+
if (storedSelectedPerson?.name) {
|
|
659
|
+
return storedSelectedPerson.name;
|
|
660
|
+
}
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
function formatPersonText(id, name) {
|
|
664
|
+
return `ID de selección: ${id ?? ""}\nNombre Completo: ${name}`;
|
|
665
|
+
}
|
|
666
|
+
function formatNoSelectedPersonMessage() {
|
|
667
|
+
return [
|
|
668
|
+
"No hay una persona seleccionada.",
|
|
669
|
+
"Ejecutá:",
|
|
670
|
+
"- `arca-siradig persons list` para ver las personas disponibles y luego",
|
|
671
|
+
"- `arca-siradig persons select <id|nombre>`.",
|
|
672
|
+
].join("\n");
|
|
673
|
+
}
|
|
674
|
+
async function saveSelectedPersonSession(store, cuit, session, selectedPerson) {
|
|
675
|
+
await store.write({
|
|
676
|
+
cuit,
|
|
677
|
+
createdAt: new Date().toISOString(),
|
|
678
|
+
selectedPerson,
|
|
679
|
+
storageState: await session.context.storageState(),
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
async function readWithSessionRefresh(session, store, cuit, read, formContext) {
|
|
683
|
+
let result = null;
|
|
684
|
+
const ready = await navigateWithSessionRefresh(session, store, cuit, async (page) => {
|
|
685
|
+
result = await read(page);
|
|
686
|
+
}, formContext);
|
|
687
|
+
return ready ? result : null;
|
|
688
|
+
}
|
|
689
|
+
async function navigateWithSessionRefresh(session, store, cuit, navigate, formContext) {
|
|
690
|
+
const navigatedPage = await navigate(session.page);
|
|
691
|
+
if (isBrowserPage(navigatedPage)) {
|
|
692
|
+
session.page = navigatedPage;
|
|
693
|
+
}
|
|
694
|
+
if (!isExpiredPortalSessionUrl(session.page.url())) {
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
const selectedPerson = formContext ? (await store.read())?.selectedPerson : null;
|
|
698
|
+
if (!(await refreshSiradigSession(session, store, cuit))) {
|
|
699
|
+
console.error("La sesión expiró. Ejecutá `arca-siradig login`.");
|
|
700
|
+
process.exitCode = 1;
|
|
701
|
+
return false;
|
|
702
|
+
}
|
|
703
|
+
if (formContext) {
|
|
704
|
+
const restoredPerson = await ensurePersonSelected(session, store, cuit, undefined, selectedPerson ?? { id: null, name: formContext.person.name });
|
|
705
|
+
if (!restoredPerson)
|
|
706
|
+
return false;
|
|
707
|
+
await selectFormPeriod(session.page, {
|
|
708
|
+
period: formContext.period,
|
|
709
|
+
person: formContext.person.id,
|
|
710
|
+
selector: restoredPerson.id ?? restoredPerson.name,
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
const retriedPage = await navigate(session.page);
|
|
714
|
+
if (isBrowserPage(retriedPage)) {
|
|
715
|
+
session.page = retriedPage;
|
|
716
|
+
}
|
|
717
|
+
if (isExpiredPortalSessionUrl(session.page.url())) {
|
|
718
|
+
console.error("La sesión expiró. Ejecutá `arca-siradig login`.");
|
|
719
|
+
process.exitCode = 1;
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
function isBrowserPage(value) {
|
|
725
|
+
if (!value || typeof value !== "object") {
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
return "url" in value && "goto" in value;
|
|
729
|
+
}
|
|
730
|
+
async function refreshSiradigSession(session, store, cuit) {
|
|
731
|
+
await session.page.goto(ARCA_PORTAL_URL, { waitUntil: "domcontentloaded" });
|
|
732
|
+
if (await isLoginPage(session.page)) {
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
session.page = await openSiradigFromPortal(session.page);
|
|
736
|
+
await store.write({
|
|
737
|
+
cuit,
|
|
738
|
+
createdAt: new Date().toISOString(),
|
|
739
|
+
storageState: await session.context.storageState(),
|
|
740
|
+
});
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
async function runInteractive(command) {
|
|
744
|
+
const session = await createBrowserSession({ headless: command.headless });
|
|
745
|
+
try {
|
|
746
|
+
await promptAndLogin(session.page);
|
|
747
|
+
console.log("Abriendo SiRADIG - Trabajador...");
|
|
748
|
+
const siradigPage = await openSiradigFromPortal(session.page);
|
|
749
|
+
await acceptReminderModalIfPresent(siradigPage);
|
|
750
|
+
const persons = await listPersons(siradigPage);
|
|
751
|
+
if (persons.length === 0) {
|
|
752
|
+
throw new Error("No people were found to manage.");
|
|
753
|
+
}
|
|
754
|
+
const selectedPerson = await promptPerson(persons);
|
|
755
|
+
await selectPerson(siradigPage, selectedPerson.id ?? selectedPerson.name);
|
|
756
|
+
await acceptReminderModal(siradigPage, { timeoutMs: 5_000 });
|
|
757
|
+
while (true) {
|
|
758
|
+
const menuItems = await listMenuItems(siradigPage);
|
|
759
|
+
if (menuItems.length === 0) {
|
|
760
|
+
throw new Error("No actions were found in the main menu.");
|
|
761
|
+
}
|
|
762
|
+
const selectedMenuItem = await promptMenuItem(menuItems);
|
|
763
|
+
await selectMenuItem(siradigPage, selectedMenuItem.label);
|
|
764
|
+
const personalData = await extractPersonalData(siradigPage);
|
|
765
|
+
printPersonalData(personalData);
|
|
766
|
+
await promptPersonalDataAction();
|
|
767
|
+
await returnToMainMenu(siradigPage);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
finally {
|
|
771
|
+
await closeBrowserSession(session);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
async function promptAndLogin(page) {
|
|
775
|
+
let cuitDefault = process.env.ARCA_CUIT;
|
|
776
|
+
let usePasswordDefault = true;
|
|
777
|
+
while (true) {
|
|
778
|
+
const credentials = await promptCredentials({
|
|
779
|
+
cuit: cuitDefault,
|
|
780
|
+
password: process.env.ARCA_PASSWORD,
|
|
781
|
+
}, { usePasswordDefault });
|
|
782
|
+
cuitDefault = credentials.cuit;
|
|
783
|
+
try {
|
|
784
|
+
console.log("\nIngresando a ARCA...");
|
|
785
|
+
await loginToArca(page, credentials);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
catch (error) {
|
|
789
|
+
if (!(error instanceof ArcaLoginError)) {
|
|
790
|
+
throw error;
|
|
791
|
+
}
|
|
792
|
+
console.error(`\nARCA rechazó las credenciales: ${error.message}`);
|
|
793
|
+
console.error("Intentá nuevamente.\n");
|
|
794
|
+
usePasswordDefault = false;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
async function isLoginPage(page) {
|
|
799
|
+
return page.url().startsWith(ARCA_LOGIN_URL) || page.getByRole("spinbutton").isVisible();
|
|
800
|
+
}
|
|
801
|
+
function handleCommandError(error) {
|
|
802
|
+
const message = formatCliError(error);
|
|
803
|
+
console.error(message);
|
|
804
|
+
process.exitCode = 1;
|
|
805
|
+
}
|
|
806
|
+
main().catch((error) => {
|
|
807
|
+
const message = formatCliError(error);
|
|
808
|
+
console.error(`\nError: ${message}`);
|
|
809
|
+
process.exitCode = 1;
|
|
810
|
+
});
|