@ramiidv/arca-empleados 0.2.0 → 0.4.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/CHANGELOG.md +109 -0
- package/README.md +149 -357
- package/dist/constants.d.ts +55 -313
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +54 -510
- package/dist/constants.js.map +1 -1
- package/dist/errors.d.ts +30 -7
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +21 -10
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -20
- package/dist/index.js.map +1 -1
- package/dist/ingreso-masivo.d.ts +63 -0
- package/dist/ingreso-masivo.d.ts.map +1 -0
- package/dist/ingreso-masivo.js +234 -0
- package/dist/ingreso-masivo.js.map +1 -0
- package/dist/serializer.d.ts +51 -57
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +301 -269
- package/dist/serializer.js.map +1 -1
- package/dist/types.d.ts +127 -193
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +1 -1
- package/dist/validators.d.ts +22 -31
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +85 -229
- package/dist/validators.js.map +1 -1
- package/llms.txt +49 -249
- package/package.json +24 -23
- package/dist/f935-builder.d.ts +0 -91
- package/dist/f935-builder.d.ts.map +0 -1
- package/dist/f935-builder.js +0 -246
- package/dist/f935-builder.js.map +0 -1
package/dist/serializer.js
CHANGED
|
@@ -1,304 +1,336 @@
|
|
|
1
|
-
import { ArcaSerializationError } from './errors.js';
|
|
2
|
-
import { TipoOperacion } from './constants.js';
|
|
3
|
-
import { normalizeCuil } from './validators.js';
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
// Formatting helpers
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
1
|
/**
|
|
8
|
-
*
|
|
2
|
+
* Serializador del archivo TXT de ingreso masivo de relaciones laborales de
|
|
3
|
+
* Simplificación Registral: registros de ancho fijo de 130 posiciones, tipo `01`.
|
|
4
|
+
*
|
|
5
|
+
* Diseño de registro: Manual de Ayuda de Simplificación Registral, ARCA, feb. 2025, pág. 24.
|
|
6
|
+
* https://www.arca.gob.ar/simplificacionregistral/ayuda/documentos/SR_Manual-de-Ayuda_20250218_definitivo.pdf
|
|
7
|
+
*
|
|
8
|
+
* Lo que el manual NO define (relleno, decimales de la retribución, codificación, fin
|
|
9
|
+
* de línea, valor de los campos vacíos) sigue la convención habitual de los diseños
|
|
10
|
+
* de registro de ARCA y es configurable con {@link OpcionesSerializacion}; ver README.
|
|
9
11
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return
|
|
12
|
+
import { ArcaValidationError, formatDate, normalizeCuit } from '@ramiidv/arca-common';
|
|
13
|
+
import { ArcaSerializationError } from './errors.js';
|
|
14
|
+
import { CODIGOS_MOVIMIENTO, LONGITUD_REGISTRO, TIPO_REGISTRO } from './constants.js';
|
|
15
|
+
function campo(campo, descripcion, desde, hasta, tipo) {
|
|
16
|
+
return Object.freeze({ campo, descripcion, desde, hasta, longitud: hasta - desde + 1, tipo });
|
|
15
17
|
}
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
/** Diseño de registro `01` (manual de Simplificación Registral, pág. 24). */
|
|
19
|
+
export const DISENO_REGISTRO = Object.freeze([
|
|
20
|
+
campo('tipoRegistro', 'Tipo de registro', 1, 2, 'N'),
|
|
21
|
+
campo('movimiento', 'Código de movimiento', 3, 4, 'A'),
|
|
22
|
+
campo('cuil', 'Cuil del empleado', 5, 15, 'N'),
|
|
23
|
+
campo('trabajadorAgropecuario', 'Marca de trabajador agropecuario', 16, 16, 'A'),
|
|
24
|
+
campo('modalidadContrato', 'Modalidad de contrato', 17, 19, 'N'),
|
|
25
|
+
campo('fechaInicio', 'Fecha de inicio de rel. laboral', 20, 29, 'F'),
|
|
26
|
+
campo('fechaFin', 'Fecha de fin de rel. laboral', 30, 39, 'F'),
|
|
27
|
+
campo('obraSocial', 'Código obra social', 40, 45, 'N'),
|
|
28
|
+
campo('situacionRevista', 'Código de situación de revista', 46, 47, 'N'),
|
|
29
|
+
campo('fechaTelegramaRenuncia', 'Fecha recepción telegrama de renuncia', 48, 57, 'F'),
|
|
30
|
+
campo('retribucion', 'Retribución pactada', 58, 72, 'N'),
|
|
31
|
+
campo('modalidadLiquidacion', 'Modalidad de liquidación', 73, 73, 'N'),
|
|
32
|
+
campo('sucursal', 'Sucursal - Domicilio de desempeño', 74, 78, 'N'),
|
|
33
|
+
campo('actividad', 'Actividad en el domicilio de desempeño', 79, 84, 'N'),
|
|
34
|
+
campo('puesto', 'Puesto desempeñado', 85, 88, 'A'),
|
|
35
|
+
campo('rectificacion', 'Rectificación', 89, 90, 'N'),
|
|
36
|
+
campo('cct', 'Código de Convenio Colectivo de Trabajo', 91, 100, 'A'),
|
|
37
|
+
campo('categoriaProfesional', 'Categoría profesional', 101, 106, 'N'),
|
|
38
|
+
campo('tipoServicio', 'Tipo de servicio', 107, 109, 'N'),
|
|
39
|
+
campo('fechaSuspensionServiciosTemporarios', 'Fecha de suspensión servicios temporarios', 110, 119, 'F'),
|
|
40
|
+
campo('numeroFormularioAgropecuario', 'Número formulario agropecuario', 120, 129, 'N'),
|
|
41
|
+
campo('marcaCovidTipoContratoCCG', 'Marca de trabajador licenciado por COVID / Tipo de contrato CCG', 130, 130, 'N'),
|
|
42
|
+
]);
|
|
43
|
+
/** Campos numéricos con default `0` según el manual (en lugar de "vacío"). */
|
|
44
|
+
const DEFAULT_CERO = new Set(['rectificacion', 'marcaCovidTipoContratoCCG']);
|
|
45
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: detecta caracteres de control a propósito
|
|
46
|
+
const CONTROL_CHARS = /[\u0000-\u001F\u007F-\u009F]/;
|
|
47
|
+
function resolverOpciones(opts = {}) {
|
|
48
|
+
const r = {
|
|
49
|
+
eol: opts.eol ?? '\r\n',
|
|
50
|
+
eolFinal: opts.eolFinal ?? true,
|
|
51
|
+
encoding: opts.encoding ?? 'latin1',
|
|
52
|
+
noRepresentables: opts.noRepresentables ?? 'error',
|
|
53
|
+
numericoVacio: opts.numericoVacio ?? 'ceros',
|
|
54
|
+
};
|
|
55
|
+
if (r.eol !== '\r\n' && r.eol !== '\n')
|
|
56
|
+
throw new TypeError(`eol inválido: ${JSON.stringify(opts.eol)}`);
|
|
57
|
+
if (r.encoding !== 'latin1' && r.encoding !== 'ascii') {
|
|
58
|
+
throw new TypeError(`encoding inválido: ${JSON.stringify(opts.encoding)} (usar 'latin1' o 'ascii')`);
|
|
59
|
+
}
|
|
60
|
+
if (r.noRepresentables !== 'error' && r.noRepresentables !== 'transliterar') {
|
|
61
|
+
throw new TypeError(`noRepresentables inválido: ${JSON.stringify(opts.noRepresentables)}`);
|
|
62
|
+
}
|
|
63
|
+
if (r.numericoVacio !== 'ceros' && r.numericoVacio !== 'espacios') {
|
|
64
|
+
throw new TypeError(`numericoVacio inválido: ${JSON.stringify(opts.numericoVacio)}`);
|
|
65
|
+
}
|
|
66
|
+
return r;
|
|
21
67
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*/
|
|
25
|
-
function formatMonto(val) {
|
|
26
|
-
if (val === undefined || val === null)
|
|
27
|
-
return '';
|
|
28
|
-
return val.toFixed(2);
|
|
68
|
+
function maxCodePoint(encoding) {
|
|
69
|
+
return encoding === 'ascii' ? 0x7f : 0xff;
|
|
29
70
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*/
|
|
33
|
-
function str(val) {
|
|
34
|
-
if (val === undefined || val === null)
|
|
35
|
-
return '';
|
|
36
|
-
return String(val);
|
|
71
|
+
function fail(c, value, motivo, cause) {
|
|
72
|
+
throw new ArcaSerializationError(`Campo "${c.campo}" (posiciones ${c.desde}-${c.hasta}, ${c.descripcion}): ${motivo}`, { field: c.campo, desde: c.desde, hasta: c.hasta, value, cause });
|
|
37
73
|
}
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Fechas
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
const FECHA_STRING_RE = /^(\d{4})([-/]?)(\d{2})\2(\d{2})$/;
|
|
38
78
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
79
|
+
* Formatea una fecha como `AAAA/MM/DD` (formato de las fechas del archivo de
|
|
80
|
+
* Simplificación Registral). Distinto del `formatDate` de common (`AAAAMMDD`).
|
|
81
|
+
*
|
|
82
|
+
* - `Date`: fecha calendario en Argentina (`America/Argentina/Buenos_Aires`),
|
|
83
|
+
* independiente de la zona horaria del proceso.
|
|
84
|
+
* - string `AAAA-MM-DD`, `AAAAMMDD` o `AAAA/MM/DD`: se toma literal.
|
|
85
|
+
*
|
|
86
|
+
* @throws ArcaValidationError si el formato es otro, la fecha no existe o el `Date` es inválido.
|
|
42
87
|
*/
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
88
|
+
export function formatFechaSR(fecha) {
|
|
89
|
+
let input;
|
|
90
|
+
if (typeof fecha === 'string') {
|
|
91
|
+
const m = FECHA_STRING_RE.exec(fecha);
|
|
92
|
+
if (!m) {
|
|
93
|
+
throw new ArcaValidationError(`Fecha inválida: "${fecha}". Formato esperado: AAAA-MM-DD, AAAAMMDD o AAAA/MM/DD.`, [{ field: 'fecha', message: 'Formato esperado: AAAA-MM-DD, AAAAMMDD o AAAA/MM/DD', value: fecha }], 'fecha');
|
|
94
|
+
}
|
|
95
|
+
input = `${m[1]}-${m[3]}-${m[4]}`;
|
|
96
|
+
}
|
|
97
|
+
else if (fecha instanceof Date) {
|
|
98
|
+
input = fecha;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
throw new ArcaValidationError(`Fecha inválida: se esperaba Date o string, se recibió ${tipoDe(fecha)}.`, [{ field: 'fecha', message: 'Se esperaba Date o string', value: fecha }], 'fecha');
|
|
102
|
+
}
|
|
103
|
+
const yyyymmdd = formatDate(input);
|
|
104
|
+
return `${yyyymmdd.slice(0, 4)}/${yyyymmdd.slice(4, 6)}/${yyyymmdd.slice(6, 8)}`;
|
|
51
105
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
return '';
|
|
59
|
-
return
|
|
106
|
+
function tipoDe(v) {
|
|
107
|
+
if (v === null)
|
|
108
|
+
return 'null';
|
|
109
|
+
if (Array.isArray(v))
|
|
110
|
+
return 'array';
|
|
111
|
+
if (typeof v === 'number' && Number.isNaN(v))
|
|
112
|
+
return 'NaN';
|
|
113
|
+
return typeof v;
|
|
60
114
|
}
|
|
61
115
|
// ---------------------------------------------------------------------------
|
|
62
|
-
//
|
|
116
|
+
// Campos
|
|
63
117
|
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
* Format: 00|CUIT|RAZON_SOCIAL|FECHA_GENERACION|CANTIDAD_REGISTROS|
|
|
67
|
-
*/
|
|
68
|
-
export function serializeHeader(archivo) {
|
|
69
|
-
const cuit = normalizeCuil(archivo.cuitEmpleador);
|
|
70
|
-
const razon = strEsc(archivo.razonSocial ?? '');
|
|
71
|
-
const fecha = formatDate(archivo.fechaGeneracion);
|
|
72
|
-
const cantidad = String(archivo.cantidadRegistros);
|
|
73
|
-
return `00|${cuit}|${razon}|${fecha}|${cantidad}|`;
|
|
118
|
+
function vacio(v) {
|
|
119
|
+
return v === undefined || v === null || v === '';
|
|
74
120
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
String(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
String(dom.provincia),
|
|
101
|
-
strEsc(dom.telefono),
|
|
102
|
-
];
|
|
103
|
-
return fields.join('|') + '|';
|
|
121
|
+
function formatearNumerico(c, value, o) {
|
|
122
|
+
if (vacio(value)) {
|
|
123
|
+
if (DEFAULT_CERO.has(c.campo))
|
|
124
|
+
return '0'.repeat(c.longitud);
|
|
125
|
+
return (o.numericoVacio === 'ceros' ? '0' : ' ').repeat(c.longitud);
|
|
126
|
+
}
|
|
127
|
+
let digits;
|
|
128
|
+
if (typeof value === 'number') {
|
|
129
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
130
|
+
fail(c, value, `debe ser un entero no negativo (se recibió ${Number.isNaN(value) ? 'NaN' : value}).`);
|
|
131
|
+
}
|
|
132
|
+
digits = String(value);
|
|
133
|
+
}
|
|
134
|
+
else if (typeof value === 'string') {
|
|
135
|
+
if (!/^\d+$/.test(value))
|
|
136
|
+
fail(c, value, `debe contener solo dígitos (se recibió "${value}").`);
|
|
137
|
+
digits = value;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
fail(c, value, `se esperaba number o string de dígitos, se recibió ${tipoDe(value)}.`);
|
|
141
|
+
}
|
|
142
|
+
if (digits.length > c.longitud) {
|
|
143
|
+
fail(c, value, `"${digits}" tiene ${digits.length} dígitos y el campo admite ${c.longitud}.`);
|
|
144
|
+
}
|
|
145
|
+
return digits.padStart(c.longitud, '0');
|
|
104
146
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
function formatearRetribucion(c, value, o) {
|
|
148
|
+
if (vacio(value))
|
|
149
|
+
return formatearNumerico(c, undefined, o);
|
|
150
|
+
if (typeof value !== 'number')
|
|
151
|
+
fail(c, value, `se esperaba number, se recibió ${tipoDe(value)}.`);
|
|
152
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
153
|
+
fail(c, value, `debe ser un importe finito y no negativo (se recibió ${value}).`);
|
|
154
|
+
}
|
|
155
|
+
const centavosExactos = value * 100;
|
|
156
|
+
const centavos = Math.round(centavosExactos);
|
|
157
|
+
if (Math.abs(centavosExactos - centavos) > 1e-6 * Math.max(1, Math.abs(centavosExactos))) {
|
|
158
|
+
fail(c, value, `admite como máximo 2 decimales (se recibió ${value}).`);
|
|
159
|
+
}
|
|
160
|
+
if (!Number.isSafeInteger(centavos))
|
|
161
|
+
fail(c, value, 'importe demasiado grande.');
|
|
162
|
+
const digits = String(centavos);
|
|
163
|
+
if (digits.length > c.longitud) {
|
|
164
|
+
fail(c, value, `${value} ocupa ${digits.length} posiciones (2 decimales implícitos) y el campo admite ${c.longitud}.`);
|
|
165
|
+
}
|
|
166
|
+
return digits.padStart(c.longitud, '0');
|
|
121
167
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
* Format: 01|CUIT_EMPLEADOR|TIPO_OP|CUIL|FECHA_INICIO|MODALIDAD|OBRA_SOCIAL|ACTIVIDAD|AGROPECUARIO|FECHA_FIN|CALLE|NUMERO|PISO|DEPTO|LOCALIDAD|CP|PROVINCIA|TELEFONO|
|
|
125
|
-
*/
|
|
126
|
-
export function serializeModificacion(cuitEmpleador, mod) {
|
|
127
|
-
const cuit = normalizeCuil(cuitEmpleador);
|
|
128
|
-
const cuil = normalizeCuil(mod.cuil);
|
|
129
|
-
const dom = mod.domicilioExplotacion;
|
|
130
|
-
const fields = [
|
|
131
|
-
'01',
|
|
132
|
-
cuit,
|
|
133
|
-
String(TipoOperacion.MODIFICACION),
|
|
134
|
-
cuil,
|
|
135
|
-
formatDate(mod.fechaInicio),
|
|
136
|
-
str(mod.modalidadContratacion),
|
|
137
|
-
str(mod.obraSocial),
|
|
138
|
-
str(mod.actividadEconomica),
|
|
139
|
-
mod.trabajadorAgropecuario !== undefined ? boolToStr(mod.trabajadorAgropecuario) : '',
|
|
140
|
-
mod.fechaFin ? formatDate(mod.fechaFin) : '',
|
|
141
|
-
dom ? strEsc(dom.calle) : '',
|
|
142
|
-
dom ? strEsc(dom.numero) : '',
|
|
143
|
-
dom ? strEsc(dom.piso) : '',
|
|
144
|
-
dom ? strEsc(dom.depto) : '',
|
|
145
|
-
dom ? strEsc(dom.localidad) : '',
|
|
146
|
-
dom ? dom.codigoPostal : '',
|
|
147
|
-
dom ? String(dom.provincia) : '',
|
|
148
|
-
dom ? strEsc(dom.telefono) : '',
|
|
149
|
-
];
|
|
150
|
-
return fields.join('|') + '|';
|
|
168
|
+
function transliterar(s) {
|
|
169
|
+
return s.normalize('NFD').replace(/[̀-ͯ]/g, '').normalize('NFC');
|
|
151
170
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
str(datos.cantidadHorasTrabajadas),
|
|
173
|
-
formatMonto(datos.porcentajeAporteAdicionalSS),
|
|
174
|
-
str(datos.contribucionTareasDiferenciales),
|
|
175
|
-
str(datos.codigoSiniestrado),
|
|
176
|
-
datos.marcaPreaviso !== undefined ? boolToStr(datos.marcaPreaviso) : '',
|
|
177
|
-
datos.fechaPreaviso ? formatDate(datos.fechaPreaviso) : '',
|
|
178
|
-
str(datos.conceptoLiquidacion),
|
|
179
|
-
];
|
|
180
|
-
return fields.join('|') + '|';
|
|
171
|
+
function formatearAlfanumerico(c, value, o) {
|
|
172
|
+
if (vacio(value))
|
|
173
|
+
return ' '.repeat(c.longitud);
|
|
174
|
+
if (typeof value !== 'string')
|
|
175
|
+
fail(c, value, `se esperaba string, se recibió ${tipoDe(value)}.`);
|
|
176
|
+
if (CONTROL_CHARS.test(value))
|
|
177
|
+
fail(c, value, 'contiene caracteres de control (saltos de línea, tabulaciones, etc.).');
|
|
178
|
+
const max = maxCodePoint(o.encoding);
|
|
179
|
+
let s = value.normalize('NFC');
|
|
180
|
+
const noRepresentable = (t) => [...t].find((ch) => ch.codePointAt(0) > max);
|
|
181
|
+
if (noRepresentable(s) !== undefined && o.noRepresentables === 'transliterar')
|
|
182
|
+
s = transliterar(s);
|
|
183
|
+
const bad = noRepresentable(s);
|
|
184
|
+
if (bad !== undefined) {
|
|
185
|
+
fail(c, value, `el caracter "${bad}" no es representable en ${o.encoding === 'ascii' ? 'ASCII' : 'ISO-8859-1'}.`);
|
|
186
|
+
}
|
|
187
|
+
if (s.length > c.longitud) {
|
|
188
|
+
fail(c, value, `"${s}" tiene ${s.length} caracteres y el campo admite ${c.longitud}.`);
|
|
189
|
+
}
|
|
190
|
+
return s.padEnd(c.longitud, ' ');
|
|
181
191
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
formatDate(cbuData.fechaInicio),
|
|
194
|
-
cbuData.cbu,
|
|
195
|
-
str(cbuData.tipoCuenta),
|
|
196
|
-
];
|
|
197
|
-
return fields.join('|') + '|';
|
|
192
|
+
function formatearFecha(c, value) {
|
|
193
|
+
if (vacio(value))
|
|
194
|
+
return ' '.repeat(c.longitud);
|
|
195
|
+
try {
|
|
196
|
+
return formatFechaSR(value);
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
if (e instanceof ArcaValidationError)
|
|
200
|
+
fail(c, value, e.message, e);
|
|
201
|
+
throw e;
|
|
202
|
+
}
|
|
198
203
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
'04',
|
|
208
|
-
cuit,
|
|
209
|
-
cuil,
|
|
210
|
-
formatDate(rel.fechaInicio),
|
|
211
|
-
String(rel.tipoRelacion),
|
|
212
|
-
rel.cuilFamiliar ? normalizeCuil(rel.cuilFamiliar) : '',
|
|
213
|
-
str(rel.dniFamiliar),
|
|
214
|
-
strEsc(rel.nombreFamiliar),
|
|
215
|
-
strEsc(rel.apellidoFamiliar),
|
|
216
|
-
formatDate(rel.fechaNacimiento),
|
|
217
|
-
rel.discapacitado !== undefined ? boolToStr(rel.discapacitado) : '',
|
|
218
|
-
str(rel.escolaridad),
|
|
219
|
-
str(rel.obraSocialFamiliar),
|
|
220
|
-
rel.cuilTitularObraSocial ? normalizeCuil(rel.cuilTitularObraSocial) : '',
|
|
221
|
-
];
|
|
222
|
-
return fields.join('|') + '|';
|
|
204
|
+
function formatearCuil(c, value) {
|
|
205
|
+
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
206
|
+
fail(c, value, `es obligatorio (string o number de 11 dígitos), se recibió ${tipoDe(value)}.`);
|
|
207
|
+
}
|
|
208
|
+
const n = normalizeCuit(value);
|
|
209
|
+
if (!/^\d{11}$/.test(n))
|
|
210
|
+
fail(c, value, `debe tener 11 dígitos (se recibió "${String(value)}").`);
|
|
211
|
+
return n;
|
|
223
212
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
cuil
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
213
|
+
function formatearCampo(c, registro, o) {
|
|
214
|
+
if (c.campo === 'tipoRegistro')
|
|
215
|
+
return TIPO_REGISTRO;
|
|
216
|
+
const value = registro[c.campo];
|
|
217
|
+
switch (c.campo) {
|
|
218
|
+
case 'movimiento':
|
|
219
|
+
if (typeof value !== 'string' || !CODIGOS_MOVIMIENTO.includes(value)) {
|
|
220
|
+
fail(c, value, `debe ser uno de ${CODIGOS_MOVIMIENTO.join(', ')} (se recibió ${JSON.stringify(value) ?? tipoDe(value)}).`);
|
|
221
|
+
}
|
|
222
|
+
return value;
|
|
223
|
+
case 'cuil':
|
|
224
|
+
return formatearCuil(c, value);
|
|
225
|
+
case 'retribucion':
|
|
226
|
+
return formatearRetribucion(c, value, o);
|
|
227
|
+
}
|
|
228
|
+
switch (c.tipo) {
|
|
229
|
+
case 'N':
|
|
230
|
+
return formatearNumerico(c, value, o);
|
|
231
|
+
case 'A':
|
|
232
|
+
return formatearAlfanumerico(c, value, o);
|
|
233
|
+
case 'F':
|
|
234
|
+
return formatearFecha(c, value);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function assertObjeto(registro) {
|
|
238
|
+
if (typeof registro !== 'object' || registro === null || Array.isArray(registro)) {
|
|
239
|
+
throw new ArcaSerializationError(`El registro debe ser un objeto con los datos de la relación laboral, se recibió ${tipoDe(registro)}.`, { value: registro });
|
|
240
|
+
}
|
|
246
241
|
}
|
|
247
242
|
// ---------------------------------------------------------------------------
|
|
248
|
-
//
|
|
243
|
+
// API pública
|
|
249
244
|
// ---------------------------------------------------------------------------
|
|
250
245
|
/**
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
|
|
246
|
+
* Serializa un registro `01` a una línea de exactamente 130 caracteres (sin fin de línea).
|
|
247
|
+
* @throws ArcaSerializationError si algún campo no se puede escribir (nunca trunca).
|
|
248
|
+
*/
|
|
249
|
+
export function serializarRegistro(registro, opciones) {
|
|
250
|
+
const o = resolverOpciones(opciones);
|
|
251
|
+
assertObjeto(registro);
|
|
252
|
+
const linea = DISENO_REGISTRO.map((c) => formatearCampo(c, registro, o)).join('');
|
|
253
|
+
if (linea.length !== LONGITUD_REGISTRO) {
|
|
254
|
+
// Invariante: cada formateador devuelve exactamente `longitud` caracteres.
|
|
255
|
+
throw new ArcaSerializationError(`Registro de ${linea.length} caracteres (se esperaban ${LONGITUD_REGISTRO}).`);
|
|
256
|
+
}
|
|
257
|
+
return linea;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Devuelve todos los errores de formato de un registro (sin lanzar), uno por campo.
|
|
261
|
+
* @internal Lo usa `validateRegistro`.
|
|
254
262
|
*/
|
|
255
|
-
export function
|
|
263
|
+
export function erroresDeFormato(registro, opciones) {
|
|
264
|
+
const o = resolverOpciones(opciones);
|
|
256
265
|
try {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
for (const registro of archivo.registros) {
|
|
262
|
-
lines.push(serializeRegistro(archivo.cuitEmpleador, registro));
|
|
263
|
-
}
|
|
264
|
-
return lines.join('\n');
|
|
266
|
+
assertObjeto(registro);
|
|
267
|
+
}
|
|
268
|
+
catch (e) {
|
|
269
|
+
return [{ field: 'registro', message: e.message, value: registro }];
|
|
265
270
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
271
|
+
const errores = [];
|
|
272
|
+
for (const c of DISENO_REGISTRO) {
|
|
273
|
+
try {
|
|
274
|
+
formatearCampo(c, registro, o);
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
if (!(e instanceof ArcaSerializationError))
|
|
278
|
+
throw e;
|
|
279
|
+
errores.push({ field: c.campo, message: e.message, value: e.value });
|
|
269
280
|
}
|
|
270
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
271
|
-
throw new ArcaSerializationError(`Error al serializar el archivo F935: ${message}`);
|
|
272
281
|
}
|
|
282
|
+
return errores;
|
|
273
283
|
}
|
|
274
284
|
/**
|
|
275
|
-
*
|
|
285
|
+
* Serializa varios registros al contenido completo del archivo (string).
|
|
286
|
+
* Por defecto: líneas separadas por CRLF y CRLF final.
|
|
287
|
+
* @throws ArcaSerializationError con `indice` del registro que falló y `cause` = error original.
|
|
276
288
|
*/
|
|
277
|
-
function
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
289
|
+
export function serializarArchivo(registros, opciones) {
|
|
290
|
+
const o = resolverOpciones(opciones);
|
|
291
|
+
if (!Array.isArray(registros)) {
|
|
292
|
+
throw new ArcaSerializationError(`Se esperaba un array de registros, se recibió ${tipoDe(registros)}.`);
|
|
293
|
+
}
|
|
294
|
+
const lineas = registros.map((r, i) => {
|
|
295
|
+
try {
|
|
296
|
+
return serializarRegistro(r, o);
|
|
297
|
+
}
|
|
298
|
+
catch (e) {
|
|
299
|
+
if (!(e instanceof ArcaSerializationError))
|
|
300
|
+
throw e;
|
|
301
|
+
throw new ArcaSerializationError(`Registro ${i}: ${e.message}`, {
|
|
302
|
+
field: e.field,
|
|
303
|
+
desde: e.desde,
|
|
304
|
+
hasta: e.hasta,
|
|
305
|
+
value: e.value,
|
|
306
|
+
indice: i,
|
|
307
|
+
cause: e,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
return unirLineas(lineas, o);
|
|
312
|
+
}
|
|
313
|
+
/** @internal */
|
|
314
|
+
export function unirLineas(lineas, opciones) {
|
|
315
|
+
const o = resolverOpciones(opciones);
|
|
316
|
+
if (lineas.length === 0)
|
|
317
|
+
return '';
|
|
318
|
+
return lineas.join(o.eol) + (o.eolFinal ? o.eol : '');
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Codifica el contenido del archivo a `Buffer` (ISO-8859-1 por defecto, un byte por caracter).
|
|
322
|
+
* @throws ArcaSerializationError si hay un caracter no representable en la codificación.
|
|
323
|
+
*/
|
|
324
|
+
export function codificarArchivo(contenido, encoding = 'latin1') {
|
|
325
|
+
if (encoding !== 'latin1' && encoding !== 'ascii') {
|
|
326
|
+
throw new TypeError(`encoding inválido: ${JSON.stringify(encoding)} (usar 'latin1' o 'ascii')`);
|
|
327
|
+
}
|
|
328
|
+
const max = maxCodePoint(encoding);
|
|
329
|
+
for (let i = 0; i < contenido.length; i++) {
|
|
330
|
+
if (contenido.charCodeAt(i) > max) {
|
|
331
|
+
throw new ArcaSerializationError(`El caracter "${contenido[i]}" (posición ${i} del contenido) no es representable en ${encoding}.`, { value: contenido[i] });
|
|
301
332
|
}
|
|
302
333
|
}
|
|
334
|
+
return Buffer.from(contenido, 'latin1');
|
|
303
335
|
}
|
|
304
336
|
//# sourceMappingURL=serializer.js.map
|