@ramiidv/arca-empleados 0.2.0 → 0.3.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/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 +215 -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 +292 -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 +77 -229
- package/dist/validators.js.map +1 -1
- package/llms.txt +49 -249
- package/package.json +12 -9
- 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,327 @@
|
|
|
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
|
+
const CONTROL_CHARS = /[\u0000-\u001F\u007F-\u009F]/;
|
|
46
|
+
function resolverOpciones(opts = {}) {
|
|
47
|
+
const r = {
|
|
48
|
+
eol: opts.eol ?? '\r\n',
|
|
49
|
+
eolFinal: opts.eolFinal ?? true,
|
|
50
|
+
encoding: opts.encoding ?? 'latin1',
|
|
51
|
+
noRepresentables: opts.noRepresentables ?? 'error',
|
|
52
|
+
numericoVacio: opts.numericoVacio ?? 'ceros',
|
|
53
|
+
};
|
|
54
|
+
if (r.eol !== '\r\n' && r.eol !== '\n')
|
|
55
|
+
throw new TypeError(`eol inválido: ${JSON.stringify(opts.eol)}`);
|
|
56
|
+
if (r.encoding !== 'latin1' && r.encoding !== 'ascii') {
|
|
57
|
+
throw new TypeError(`encoding inválido: ${JSON.stringify(opts.encoding)} (usar 'latin1' o 'ascii')`);
|
|
58
|
+
}
|
|
59
|
+
if (r.noRepresentables !== 'error' && r.noRepresentables !== 'transliterar') {
|
|
60
|
+
throw new TypeError(`noRepresentables inválido: ${JSON.stringify(opts.noRepresentables)}`);
|
|
61
|
+
}
|
|
62
|
+
if (r.numericoVacio !== 'ceros' && r.numericoVacio !== 'espacios') {
|
|
63
|
+
throw new TypeError(`numericoVacio inválido: ${JSON.stringify(opts.numericoVacio)}`);
|
|
64
|
+
}
|
|
65
|
+
return r;
|
|
21
66
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*/
|
|
25
|
-
function formatMonto(val) {
|
|
26
|
-
if (val === undefined || val === null)
|
|
27
|
-
return '';
|
|
28
|
-
return val.toFixed(2);
|
|
67
|
+
function maxCodePoint(encoding) {
|
|
68
|
+
return encoding === 'ascii' ? 0x7f : 0xff;
|
|
29
69
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*/
|
|
33
|
-
function str(val) {
|
|
34
|
-
if (val === undefined || val === null)
|
|
35
|
-
return '';
|
|
36
|
-
return String(val);
|
|
70
|
+
function fail(c, value, motivo, cause) {
|
|
71
|
+
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
72
|
}
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Fechas
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
const FECHA_STRING_RE = /^(\d{4})([-/]?)(\d{2})\2(\d{2})$/;
|
|
38
77
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
78
|
+
* Formatea una fecha como `AAAA/MM/DD` (formato de las fechas del archivo de
|
|
79
|
+
* Simplificación Registral). Distinto del `formatDate` de common (`AAAAMMDD`).
|
|
80
|
+
*
|
|
81
|
+
* - `Date`: fecha calendario en Argentina (`America/Argentina/Buenos_Aires`),
|
|
82
|
+
* independiente de la zona horaria del proceso.
|
|
83
|
+
* - string `AAAA-MM-DD`, `AAAAMMDD` o `AAAA/MM/DD`: se toma literal.
|
|
84
|
+
*
|
|
85
|
+
* @throws ArcaValidationError si el formato es otro, la fecha no existe o el `Date` es inválido.
|
|
42
86
|
*/
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
87
|
+
export function formatFechaSR(fecha) {
|
|
88
|
+
let input;
|
|
89
|
+
if (typeof fecha === 'string') {
|
|
90
|
+
const m = FECHA_STRING_RE.exec(fecha);
|
|
91
|
+
if (!m) {
|
|
92
|
+
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');
|
|
93
|
+
}
|
|
94
|
+
input = `${m[1]}-${m[3]}-${m[4]}`;
|
|
95
|
+
}
|
|
96
|
+
else if (fecha instanceof Date) {
|
|
97
|
+
input = fecha;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
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');
|
|
101
|
+
}
|
|
102
|
+
const yyyymmdd = formatDate(input);
|
|
103
|
+
return `${yyyymmdd.slice(0, 4)}/${yyyymmdd.slice(4, 6)}/${yyyymmdd.slice(6, 8)}`;
|
|
51
104
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
return '';
|
|
59
|
-
return
|
|
105
|
+
function tipoDe(v) {
|
|
106
|
+
if (v === null)
|
|
107
|
+
return 'null';
|
|
108
|
+
if (Array.isArray(v))
|
|
109
|
+
return 'array';
|
|
110
|
+
if (typeof v === 'number' && Number.isNaN(v))
|
|
111
|
+
return 'NaN';
|
|
112
|
+
return typeof v;
|
|
60
113
|
}
|
|
61
114
|
// ---------------------------------------------------------------------------
|
|
62
|
-
//
|
|
115
|
+
// Campos
|
|
63
116
|
// ---------------------------------------------------------------------------
|
|
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}|`;
|
|
117
|
+
function vacio(v) {
|
|
118
|
+
return v === undefined || v === null || v === '';
|
|
74
119
|
}
|
|
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('|') + '|';
|
|
120
|
+
function formatearNumerico(c, value, o) {
|
|
121
|
+
if (vacio(value)) {
|
|
122
|
+
if (DEFAULT_CERO.has(c.campo))
|
|
123
|
+
return '0'.repeat(c.longitud);
|
|
124
|
+
return (o.numericoVacio === 'ceros' ? '0' : ' ').repeat(c.longitud);
|
|
125
|
+
}
|
|
126
|
+
let digits;
|
|
127
|
+
if (typeof value === 'number') {
|
|
128
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
129
|
+
fail(c, value, `debe ser un entero no negativo (se recibió ${Number.isNaN(value) ? 'NaN' : value}).`);
|
|
130
|
+
}
|
|
131
|
+
digits = String(value);
|
|
132
|
+
}
|
|
133
|
+
else if (typeof value === 'string') {
|
|
134
|
+
if (!/^\d+$/.test(value))
|
|
135
|
+
fail(c, value, `debe contener solo dígitos (se recibió "${value}").`);
|
|
136
|
+
digits = value;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
fail(c, value, `se esperaba number o string de dígitos, se recibió ${tipoDe(value)}.`);
|
|
140
|
+
}
|
|
141
|
+
if (digits.length > c.longitud) {
|
|
142
|
+
fail(c, value, `"${digits}" tiene ${digits.length} dígitos y el campo admite ${c.longitud}.`);
|
|
143
|
+
}
|
|
144
|
+
return digits.padStart(c.longitud, '0');
|
|
104
145
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
146
|
+
function formatearRetribucion(c, value, o) {
|
|
147
|
+
if (vacio(value))
|
|
148
|
+
return formatearNumerico(c, undefined, o);
|
|
149
|
+
if (typeof value !== 'number')
|
|
150
|
+
fail(c, value, `se esperaba number, se recibió ${tipoDe(value)}.`);
|
|
151
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
152
|
+
fail(c, value, `debe ser un importe finito y no negativo (se recibió ${value}).`);
|
|
153
|
+
}
|
|
154
|
+
const centavosExactos = value * 100;
|
|
155
|
+
const centavos = Math.round(centavosExactos);
|
|
156
|
+
if (Math.abs(centavosExactos - centavos) > 1e-6 * Math.max(1, Math.abs(centavosExactos))) {
|
|
157
|
+
fail(c, value, `admite como máximo 2 decimales (se recibió ${value}).`);
|
|
158
|
+
}
|
|
159
|
+
if (!Number.isSafeInteger(centavos))
|
|
160
|
+
fail(c, value, 'importe demasiado grande.');
|
|
161
|
+
const digits = String(centavos);
|
|
162
|
+
if (digits.length > c.longitud) {
|
|
163
|
+
fail(c, value, `${value} ocupa ${digits.length} posiciones (2 decimales implícitos) y el campo admite ${c.longitud}.`);
|
|
164
|
+
}
|
|
165
|
+
return digits.padStart(c.longitud, '0');
|
|
121
166
|
}
|
|
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('|') + '|';
|
|
167
|
+
function transliterar(s) {
|
|
168
|
+
return s.normalize('NFD').replace(/[̀-ͯ]/g, '').normalize('NFC');
|
|
151
169
|
}
|
|
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('|') + '|';
|
|
170
|
+
function formatearAlfanumerico(c, value, o) {
|
|
171
|
+
if (vacio(value))
|
|
172
|
+
return ' '.repeat(c.longitud);
|
|
173
|
+
if (typeof value !== 'string')
|
|
174
|
+
fail(c, value, `se esperaba string, se recibió ${tipoDe(value)}.`);
|
|
175
|
+
if (CONTROL_CHARS.test(value))
|
|
176
|
+
fail(c, value, 'contiene caracteres de control (saltos de línea, tabulaciones, etc.).');
|
|
177
|
+
const max = maxCodePoint(o.encoding);
|
|
178
|
+
let s = value.normalize('NFC');
|
|
179
|
+
const noRepresentable = (t) => [...t].find((ch) => ch.codePointAt(0) > max);
|
|
180
|
+
if (noRepresentable(s) !== undefined && o.noRepresentables === 'transliterar')
|
|
181
|
+
s = transliterar(s);
|
|
182
|
+
const bad = noRepresentable(s);
|
|
183
|
+
if (bad !== undefined) {
|
|
184
|
+
fail(c, value, `el caracter "${bad}" no es representable en ${o.encoding === 'ascii' ? 'ASCII' : 'ISO-8859-1'}.`);
|
|
185
|
+
}
|
|
186
|
+
if (s.length > c.longitud) {
|
|
187
|
+
fail(c, value, `"${s}" tiene ${s.length} caracteres y el campo admite ${c.longitud}.`);
|
|
188
|
+
}
|
|
189
|
+
return s.padEnd(c.longitud, ' ');
|
|
181
190
|
}
|
|
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('|') + '|';
|
|
191
|
+
function formatearFecha(c, value) {
|
|
192
|
+
if (vacio(value))
|
|
193
|
+
return ' '.repeat(c.longitud);
|
|
194
|
+
try {
|
|
195
|
+
return formatFechaSR(value);
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
if (e instanceof ArcaValidationError)
|
|
199
|
+
fail(c, value, e.message, e);
|
|
200
|
+
throw e;
|
|
201
|
+
}
|
|
198
202
|
}
|
|
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('|') + '|';
|
|
203
|
+
function formatearCuil(c, value) {
|
|
204
|
+
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
205
|
+
fail(c, value, `es obligatorio (string o number de 11 dígitos), se recibió ${tipoDe(value)}.`);
|
|
206
|
+
}
|
|
207
|
+
const n = normalizeCuit(value);
|
|
208
|
+
if (!/^\d{11}$/.test(n))
|
|
209
|
+
fail(c, value, `debe tener 11 dígitos (se recibió "${String(value)}").`);
|
|
210
|
+
return n;
|
|
223
211
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
cuil
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
212
|
+
function formatearCampo(c, registro, o) {
|
|
213
|
+
if (c.campo === 'tipoRegistro')
|
|
214
|
+
return TIPO_REGISTRO;
|
|
215
|
+
const value = registro[c.campo];
|
|
216
|
+
switch (c.campo) {
|
|
217
|
+
case 'movimiento':
|
|
218
|
+
if (typeof value !== 'string' || !CODIGOS_MOVIMIENTO.includes(value)) {
|
|
219
|
+
fail(c, value, `debe ser uno de ${CODIGOS_MOVIMIENTO.join(', ')} (se recibió ${JSON.stringify(value) ?? tipoDe(value)}).`);
|
|
220
|
+
}
|
|
221
|
+
return value;
|
|
222
|
+
case 'cuil':
|
|
223
|
+
return formatearCuil(c, value);
|
|
224
|
+
case 'retribucion':
|
|
225
|
+
return formatearRetribucion(c, value, o);
|
|
226
|
+
}
|
|
227
|
+
switch (c.tipo) {
|
|
228
|
+
case 'N': return formatearNumerico(c, value, o);
|
|
229
|
+
case 'A': return formatearAlfanumerico(c, value, o);
|
|
230
|
+
case 'F': return formatearFecha(c, value);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function assertObjeto(registro) {
|
|
234
|
+
if (typeof registro !== 'object' || registro === null || Array.isArray(registro)) {
|
|
235
|
+
throw new ArcaSerializationError(`El registro debe ser un objeto con los datos de la relación laboral, se recibió ${tipoDe(registro)}.`, { value: registro });
|
|
236
|
+
}
|
|
246
237
|
}
|
|
247
238
|
// ---------------------------------------------------------------------------
|
|
248
|
-
//
|
|
239
|
+
// API pública
|
|
249
240
|
// ---------------------------------------------------------------------------
|
|
250
241
|
/**
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
|
|
242
|
+
* Serializa un registro `01` a una línea de exactamente 130 caracteres (sin fin de línea).
|
|
243
|
+
* @throws ArcaSerializationError si algún campo no se puede escribir (nunca trunca).
|
|
244
|
+
*/
|
|
245
|
+
export function serializarRegistro(registro, opciones) {
|
|
246
|
+
const o = resolverOpciones(opciones);
|
|
247
|
+
assertObjeto(registro);
|
|
248
|
+
const linea = DISENO_REGISTRO.map((c) => formatearCampo(c, registro, o)).join('');
|
|
249
|
+
if (linea.length !== LONGITUD_REGISTRO) {
|
|
250
|
+
// Invariante: cada formateador devuelve exactamente `longitud` caracteres.
|
|
251
|
+
throw new ArcaSerializationError(`Registro de ${linea.length} caracteres (se esperaban ${LONGITUD_REGISTRO}).`);
|
|
252
|
+
}
|
|
253
|
+
return linea;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Devuelve todos los errores de formato de un registro (sin lanzar), uno por campo.
|
|
257
|
+
* @internal Lo usa `validateRegistro`.
|
|
254
258
|
*/
|
|
255
|
-
export function
|
|
259
|
+
export function erroresDeFormato(registro, opciones) {
|
|
260
|
+
const o = resolverOpciones(opciones);
|
|
256
261
|
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');
|
|
262
|
+
assertObjeto(registro);
|
|
263
|
+
}
|
|
264
|
+
catch (e) {
|
|
265
|
+
return [{ field: 'registro', message: e.message, value: registro }];
|
|
265
266
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
267
|
+
const errores = [];
|
|
268
|
+
for (const c of DISENO_REGISTRO) {
|
|
269
|
+
try {
|
|
270
|
+
formatearCampo(c, registro, o);
|
|
271
|
+
}
|
|
272
|
+
catch (e) {
|
|
273
|
+
if (!(e instanceof ArcaSerializationError))
|
|
274
|
+
throw e;
|
|
275
|
+
errores.push({ field: c.campo, message: e.message, value: e.value });
|
|
269
276
|
}
|
|
270
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
271
|
-
throw new ArcaSerializationError(`Error al serializar el archivo F935: ${message}`);
|
|
272
277
|
}
|
|
278
|
+
return errores;
|
|
273
279
|
}
|
|
274
280
|
/**
|
|
275
|
-
*
|
|
281
|
+
* Serializa varios registros al contenido completo del archivo (string).
|
|
282
|
+
* Por defecto: líneas separadas por CRLF y CRLF final.
|
|
283
|
+
* @throws ArcaSerializationError con `indice` del registro que falló y `cause` = error original.
|
|
276
284
|
*/
|
|
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
|
-
|
|
285
|
+
export function serializarArchivo(registros, opciones) {
|
|
286
|
+
const o = resolverOpciones(opciones);
|
|
287
|
+
if (!Array.isArray(registros)) {
|
|
288
|
+
throw new ArcaSerializationError(`Se esperaba un array de registros, se recibió ${tipoDe(registros)}.`);
|
|
289
|
+
}
|
|
290
|
+
const lineas = registros.map((r, i) => {
|
|
291
|
+
try {
|
|
292
|
+
return serializarRegistro(r, o);
|
|
293
|
+
}
|
|
294
|
+
catch (e) {
|
|
295
|
+
if (!(e instanceof ArcaSerializationError))
|
|
296
|
+
throw e;
|
|
297
|
+
throw new ArcaSerializationError(`Registro ${i}: ${e.message}`, {
|
|
298
|
+
field: e.field, desde: e.desde, hasta: e.hasta, value: e.value, indice: i, cause: e,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
return unirLineas(lineas, o);
|
|
303
|
+
}
|
|
304
|
+
/** @internal */
|
|
305
|
+
export function unirLineas(lineas, opciones) {
|
|
306
|
+
const o = resolverOpciones(opciones);
|
|
307
|
+
if (lineas.length === 0)
|
|
308
|
+
return '';
|
|
309
|
+
return lineas.join(o.eol) + (o.eolFinal ? o.eol : '');
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Codifica el contenido del archivo a `Buffer` (ISO-8859-1 por defecto, un byte por caracter).
|
|
313
|
+
* @throws ArcaSerializationError si hay un caracter no representable en la codificación.
|
|
314
|
+
*/
|
|
315
|
+
export function codificarArchivo(contenido, encoding = 'latin1') {
|
|
316
|
+
if (encoding !== 'latin1' && encoding !== 'ascii') {
|
|
317
|
+
throw new TypeError(`encoding inválido: ${JSON.stringify(encoding)} (usar 'latin1' o 'ascii')`);
|
|
318
|
+
}
|
|
319
|
+
const max = maxCodePoint(encoding);
|
|
320
|
+
for (let i = 0; i < contenido.length; i++) {
|
|
321
|
+
if (contenido.charCodeAt(i) > max) {
|
|
322
|
+
throw new ArcaSerializationError(`El caracter "${contenido[i]}" (posición ${i} del contenido) no es representable en ${encoding}.`, { value: contenido[i] });
|
|
301
323
|
}
|
|
302
324
|
}
|
|
325
|
+
return Buffer.from(contenido, 'latin1');
|
|
303
326
|
}
|
|
304
327
|
//# sourceMappingURL=serializer.js.map
|