@runnerpro/backend 1.21.16 → 1.22.1
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/lib/cjs/sendMail/index.js +36 -59
- package/lib/cjs/types/sendMail/index.d.ts +8 -1
- package/lib/cjs/types/sendMail/index.d.ts.map +1 -1
- package/lib/cjs/types/workout/planificacionPrueba7dias/empezarCorrer/getPlanificacionPrueba7diasEmpezarCorrer.d.ts.map +1 -1
- package/lib/cjs/types/workout/planificacionPrueba7dias/index.d.ts.map +1 -1
- package/lib/cjs/types/workout/saveWorkoutAplication.d.ts.map +1 -1
- package/lib/cjs/workout/planificacionPrueba7dias/empezarCorrer/getPlanificacionPrueba7diasEmpezarCorrer.js +24 -5
- package/lib/cjs/workout/planificacionPrueba7dias/index.js +17 -3
- package/lib/cjs/workout/saveWorkoutAplication.js +16 -0
- package/package.json +1 -1
|
@@ -24,12 +24,12 @@ const transporter = nodemailer_1.default.createTransport({
|
|
|
24
24
|
greetingTimeout: 10000,
|
|
25
25
|
socketTimeout: 20000,
|
|
26
26
|
});
|
|
27
|
-
const sendMail = ({ subject, title, body, to, link, attachments, bcc }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
27
|
+
const sendMail = ({ subject, title, body, to, link, attachments, bcc, signoff }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
28
28
|
var _a;
|
|
29
29
|
const toMapped = process.env.NODE_ENV === 'PROD' ? to.map((correo) => correo).join(',') : process.env.GMAIL_EMAIL_USER;
|
|
30
30
|
if ((toMapped === null || toMapped === void 0 ? void 0 : toMapped.length) === 0)
|
|
31
31
|
return;
|
|
32
|
-
const bodyHTML = getBodyHTML(title, body, link);
|
|
32
|
+
const bodyHTML = getBodyHTML(title, body, link, signoff);
|
|
33
33
|
const sendPromise = transporter.sendMail({
|
|
34
34
|
from: `"RunnerPro" <${process.env.GMAIL_EMAIL_USER}>`,
|
|
35
35
|
to: toMapped,
|
|
@@ -37,14 +37,7 @@ const sendMail = ({ subject, title, body, to, link, attachments, bcc }) => __awa
|
|
|
37
37
|
subject,
|
|
38
38
|
text: body + (link ? link.url : ''),
|
|
39
39
|
html: bodyHTML,
|
|
40
|
-
attachments: [
|
|
41
|
-
...(attachments || []),
|
|
42
|
-
{
|
|
43
|
-
filename: 'logo-large.png',
|
|
44
|
-
path: 'https://storage.googleapis.com/assets_runnerpro/logo-large-nuevo.png',
|
|
45
|
-
cid: 'logo',
|
|
46
|
-
},
|
|
47
|
-
],
|
|
40
|
+
attachments: attachments || [],
|
|
48
41
|
});
|
|
49
42
|
const transport = (_a = transporter.transporter) !== null && _a !== void 0 ? _a : transporter;
|
|
50
43
|
const errorPromise = new Promise((_, reject) => {
|
|
@@ -71,63 +64,47 @@ const sendMail = ({ subject, title, body, to, link, attachments, bcc }) => __awa
|
|
|
71
64
|
return Promise.race([sendPromise, errorPromise, timeoutPromise]);
|
|
72
65
|
});
|
|
73
66
|
exports.sendMail = sendMail;
|
|
74
|
-
function getBodyHTML(title, body, link) {
|
|
67
|
+
function getBodyHTML(title, body, link, signoff) {
|
|
68
|
+
// Estilo: correo personal, de founder a runner. Fondo blanco, sin cabecera
|
|
69
|
+
// negra ni tarjeta; Helvetica, texto a la izquierda, mucho aire, CTA como
|
|
70
|
+
// enlace de texto y firma personal al pie (como las newsletters de Cristóbal).
|
|
71
|
+
const FONT = 'Helvetica, Arial, sans-serif';
|
|
72
|
+
const INK = '#2c2c2b';
|
|
73
|
+
const MUTED = '#8a8a86';
|
|
74
|
+
const firma = signoff || '';
|
|
75
|
+
const greetingHTML = title
|
|
76
|
+
? `<p style="margin:0 0 20px 0;font-family:${FONT};font-size:16px;line-height:26px;color:${INK};">${title}</p>`
|
|
77
|
+
: '';
|
|
78
|
+
const bodyHTML = `<div style="font-family:${FONT};font-size:16px;line-height:26px;color:${INK};">${body
|
|
79
|
+
.split('\n')
|
|
80
|
+
.join('<br>')}</div>`;
|
|
81
|
+
const linkHTML = link
|
|
82
|
+
? `<p style="margin:26px 0 0 0;font-family:${FONT};font-size:16px;line-height:26px;">
|
|
83
|
+
<a href="${link.external ? '' : process.env.FRONTEND_URL}${link.url}" style="color:${INK};font-weight:bold;text-decoration:underline;">${link.text} →</a>
|
|
84
|
+
</p>`
|
|
85
|
+
: '';
|
|
86
|
+
const signoffHTML = firma
|
|
87
|
+
? `<p style="margin:26px 0 0 0;font-family:${FONT};font-size:16px;line-height:26px;font-style:italic;color:${MUTED};">${firma}</p>`
|
|
88
|
+
: '';
|
|
75
89
|
return `
|
|
76
90
|
<html lang="es">
|
|
77
91
|
<head>
|
|
78
92
|
<meta charset="UTF-8">
|
|
79
93
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
80
|
-
<title>
|
|
81
|
-
<style>
|
|
82
|
-
body {
|
|
83
|
-
margin: 0;
|
|
84
|
-
padding: 0;
|
|
85
|
-
}
|
|
86
|
-
.button {
|
|
87
|
-
display: inline-block;
|
|
88
|
-
padding: 12px 26px;
|
|
89
|
-
font-size: 17px;
|
|
90
|
-
text-align: center;
|
|
91
|
-
text-decoration: none;
|
|
92
|
-
background-color: #000000;
|
|
93
|
-
color: #ffffff !important;
|
|
94
|
-
margin: 40px 0 20px 0;
|
|
95
|
-
font-family: 'Sofia Sans', 'Roboto', sans-serif;
|
|
96
|
-
font-weight: bold;
|
|
97
|
-
border-radius: 4px;
|
|
98
|
-
border: 2px solid #000000;
|
|
99
|
-
}
|
|
100
|
-
</style>
|
|
94
|
+
<title>RunnerPro</title>
|
|
101
95
|
</head>
|
|
102
|
-
<body style="
|
|
103
|
-
<table role="presentation" style="
|
|
96
|
+
<body style="margin:0;padding:0;background:#ffffff;">
|
|
97
|
+
<table role="presentation" width="100%" style="border-collapse:collapse;border:0;border-spacing:0;background:#ffffff;">
|
|
104
98
|
<tr>
|
|
105
|
-
<td align="center" style="padding:
|
|
106
|
-
<table role="presentation" style="width:100%;border-collapse:collapse;border-spacing:0;text-align:left;">
|
|
99
|
+
<td align="center" style="padding:32px 16px 48px 16px;">
|
|
100
|
+
<table role="presentation" width="560" style="width:560px;max-width:100%;border-collapse:collapse;border:0;border-spacing:0;text-align:left;">
|
|
107
101
|
<tr>
|
|
108
|
-
<td
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<h1 style="font-size:22px;text-align:center;margin:16px 0 6px 0;font-family:'Sofia Sans', 'Roboto', sans-serif;font-style: italic">
|
|
115
|
-
${title || ''}
|
|
116
|
-
</h1>
|
|
117
|
-
<p style="text-align:left;margin:0 0 12px 0;font-size:15px;line-height:24px;font-family:'Sofia Sans', 'Roboto', sans-serif;">
|
|
118
|
-
${body.split('\n').join('<br>')}
|
|
119
|
-
</p>
|
|
120
|
-
${link
|
|
121
|
-
? `
|
|
122
|
-
<table role="presentation" style="width:100%;border-collapse:collapse;border:0;border-spacing:0;background:#f0f0f0;">
|
|
123
|
-
<tr>
|
|
124
|
-
<td align="center" style="padding:0;">
|
|
125
|
-
<a href="${link.external ? '' : process.env.FRONTEND_URL}${link.url}" class="button" style="color:white">${link.text}</a>
|
|
126
|
-
</td>
|
|
127
|
-
</tr>
|
|
128
|
-
</table>
|
|
129
|
-
`
|
|
130
|
-
: ''}
|
|
102
|
+
<td style="padding:0;">
|
|
103
|
+
${greetingHTML}
|
|
104
|
+
${bodyHTML}
|
|
105
|
+
${linkHTML}
|
|
106
|
+
${signoffHTML}
|
|
107
|
+
<p style="margin:40px 0 0 0;font-family:${FONT};font-size:14px;color:${MUTED};">RunnerPro</p>
|
|
131
108
|
</td>
|
|
132
109
|
</tr>
|
|
133
110
|
</table>
|
|
@@ -6,6 +6,7 @@ interface Mail {
|
|
|
6
6
|
link?: {
|
|
7
7
|
url: string;
|
|
8
8
|
text: string;
|
|
9
|
+
external?: boolean;
|
|
9
10
|
};
|
|
10
11
|
attachments?: {
|
|
11
12
|
filename: string;
|
|
@@ -13,7 +14,13 @@ interface Mail {
|
|
|
13
14
|
cid?: string;
|
|
14
15
|
}[];
|
|
15
16
|
bcc?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Firma personal al pie (estilo newsletter de Cristóbal), p. ej.
|
|
19
|
+
* "Cristóbal, CEO de RunnerPro". Por defecto no se firma. Solo se muestra
|
|
20
|
+
* si el correo pasa explícitamente este campo (correos de cliente).
|
|
21
|
+
*/
|
|
22
|
+
signoff?: string;
|
|
16
23
|
}
|
|
17
|
-
declare const sendMail: ({ subject, title, body, to, link, attachments, bcc }: Mail) => Promise<import("nodemailer/lib/smtp-transport").SentMessageInfo>;
|
|
24
|
+
declare const sendMail: ({ subject, title, body, to, link, attachments, bcc, signoff }: Mail) => Promise<import("nodemailer/lib/smtp-transport").SentMessageInfo>;
|
|
18
25
|
export { sendMail };
|
|
19
26
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/sendMail/index.ts"],"names":[],"mappings":"AAaA,UAAU,IAAI;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/sendMail/index.ts"],"names":[],"mappings":"AAaA,UAAU,IAAI;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACzD,WAAW,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjE,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,QAAA,MAAM,QAAQ,kEAAyE,IAAI,qEAsC1F,CAAC;AAiEF,OAAO,EAAE,QAAQ,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getPlanificacionPrueba7diasEmpezarCorrer.d.ts","sourceRoot":"","sources":["../../../../../../src/workout/planificacionPrueba7dias/empezarCorrer/getPlanificacionPrueba7diasEmpezarCorrer.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,wCAAwC;;;;;;;;;;;;;GAK7C,CAAC;AAEF,QAAA,MAAM,4CAA4C,
|
|
1
|
+
{"version":3,"file":"getPlanificacionPrueba7diasEmpezarCorrer.d.ts","sourceRoot":"","sources":["../../../../../../src/workout/planificacionPrueba7dias/empezarCorrer/getPlanificacionPrueba7diasEmpezarCorrer.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,wCAAwC;;;;;;;;;;;;;GAK7C,CAAC;AAEF,QAAA,MAAM,4CAA4C,kBAgCjD,CAAC;AAEF,OAAO,EAAE,wCAAwC,EAAE,4CAA4C,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/workout/planificacionPrueba7dias/index.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;GAkBG;AACH,QAAA,MAAM,2BAA2B,qBAAgB,GAAG,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/workout/planificacionPrueba7dias/index.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;GAkBG;AACH,QAAA,MAAM,2BAA2B,qBAAgB,GAAG,UA+EnD,CAAC;AAmIF,OAAO,EAAE,2BAA2B,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"saveWorkoutAplication.d.ts","sourceRoot":"","sources":["../../../../src/workout/saveWorkoutAplication.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"saveWorkoutAplication.d.ts","sourceRoot":"","sources":["../../../../src/workout/saveWorkoutAplication.ts"],"names":[],"mappings":"AAuTA;;;;;;;;;;GAUG;AACH,QAAA,MAAM,qBAAqB,SACnB,GAAG,UAED,GAAG,KACV,QAAQ,MAAM,GAAG,IAAI,CA6EvB,CAAC;AAuEF,OAAO,EAAE,qBAAqB,EAAE,CAAC"}
|
|
@@ -15,14 +15,33 @@ const getPlanificacionPrueba7diasEmpezarCorrer = (c) => {
|
|
|
15
15
|
};
|
|
16
16
|
exports.getPlanificacionPrueba7diasEmpezarCorrer = getPlanificacionPrueba7diasEmpezarCorrer;
|
|
17
17
|
const changeGoalDistanceByNivelActualEmpezarCorrer = (c) => {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// A5 — El mapeo estaba invertido y con un typo: la vuelta de lesión (el perfil más frágil)
|
|
19
|
+
// caía en el nivel MÁS duro (2x8'), porque se comparaba contra 'Correr después de lesión'
|
|
20
|
+
// (sin "una", valor que nunca casaba con el del formulario) y, vía -2, caía en type '3'.
|
|
21
|
+
// Además el nivel más suave (3x1') era inalcanzable.
|
|
22
|
+
//
|
|
23
|
+
// Capa 3 — Si el formulario web aportó el AGUANTE corriendo (F3), la progresión CaCos se
|
|
24
|
+
// gradúa con él; y el ESTADO de la lesión (F7) pone techo (con dolor nunca por encima del
|
|
25
|
+
// nivel suave). Sin esos datos (cohorte app): el frágil y el principiante absoluto arrancan
|
|
26
|
+
// en el nivel suave (type '1'); el resto en el intermedio (type '2').
|
|
27
|
+
const NIVEL_POR_AGUANTE = { lt5: 1, '5-20': 2, '20-45': 2, gt45: 3 };
|
|
28
|
+
const TECHO_POR_LESION = { duele: 1, leve: 2, prudencia: 2, recuperado: 3 };
|
|
29
|
+
let nivel;
|
|
30
|
+
if (NIVEL_POR_AGUANTE[c.aguanteCorriendo] !== undefined) {
|
|
31
|
+
nivel = NIVEL_POR_AGUANTE[c.aguanteCorriendo];
|
|
20
32
|
}
|
|
21
|
-
else if (
|
|
22
|
-
c.
|
|
33
|
+
else if (c.objetivoPrincipal === 'Correr después de una lesión' ||
|
|
34
|
+
c.objetivoPrincipal === 'Comenzar a correr' ||
|
|
35
|
+
Number(c.goalDistance) === -2) {
|
|
36
|
+
nivel = 1;
|
|
23
37
|
}
|
|
24
38
|
else {
|
|
25
|
-
|
|
39
|
+
nivel = 2;
|
|
26
40
|
}
|
|
41
|
+
if (c.objetivoPrincipal === 'Correr después de una lesión') {
|
|
42
|
+
const techo = TECHO_POR_LESION[c.estadoLesion] !== undefined ? TECHO_POR_LESION[c.estadoLesion] : 1;
|
|
43
|
+
nivel = Math.min(nivel, techo);
|
|
44
|
+
}
|
|
45
|
+
c.type = String(nivel);
|
|
27
46
|
};
|
|
28
47
|
exports.changeGoalDistanceByNivelActualEmpezarCorrer = changeGoalDistanceByNivelActualEmpezarCorrer;
|
|
@@ -40,14 +40,28 @@ const getPlanificacionPrueba7dias = (c, options = {}) => {
|
|
|
40
40
|
// @ts-ignore
|
|
41
41
|
workoutsBase = getWorkoutsByGoalDistance(c);
|
|
42
42
|
if (!workoutsBase || workoutsBase.length === 0) {
|
|
43
|
-
|
|
43
|
+
// El aviso se mantiene como señal de que un objetivo se ha quedado sin plantilla propia.
|
|
44
44
|
(0, sendMail_1.sendMail)({
|
|
45
45
|
to: ['david.jimenez@runnerpro.app'],
|
|
46
46
|
subject: 'Planificación de prueba 7 días - No hay entrenamientos',
|
|
47
47
|
body: JSON.stringify(c),
|
|
48
48
|
title: '',
|
|
49
49
|
});
|
|
50
|
+
// Fallback — nunca dejar al cliente sin plantilla base ('Correr una distancia/carrera'
|
|
51
|
+
// sin distancia elegida, 'Ciclismo', textos de objetivo futuros…): vuelta de lesión (o
|
|
52
|
+
// similar) → progresión CaCos; resto → plan 10k por nivel, el mismo default que
|
|
53
|
+
// 'Entrenamiento general'.
|
|
54
|
+
c.objetivoPrincipal = /lesi/i.test(String(c.objetivoPrincipal || '')) ? 'Correr después de una lesión' : 'Entrenamiento general';
|
|
55
|
+
c.goalDistance = -1;
|
|
56
|
+
changeGoalDistanceByNivelActual(c);
|
|
57
|
+
// @ts-ignore
|
|
58
|
+
workoutsBase = getWorkoutsByGoalDistance(c) || [];
|
|
50
59
|
}
|
|
60
|
+
// Sin plantilla no hay nada que repartir: mejor semana vacía explícita que rellenar los
|
|
61
|
+
// días disponibles con stubs { date } sin contenido (aguas abajo acababan siendo filas
|
|
62
|
+
// WORKOUT sin título ni duración) o solo con la sesión de fuerza genérica.
|
|
63
|
+
if (workoutsBase.length === 0)
|
|
64
|
+
return [];
|
|
51
65
|
if ((Number(c.nivelEntrenamientoFuerza) === 1 || Number(c.incluirEntrenamientoFuerza) === 1) && nDiasDisponibles >= 4) {
|
|
52
66
|
// @ts-ignore
|
|
53
67
|
workoutsBase.splice(1, 0, addWorkoutFuerza());
|
|
@@ -98,7 +112,7 @@ const getPlanificacionPrueba7dias = (c, options = {}) => {
|
|
|
98
112
|
};
|
|
99
113
|
exports.getPlanificacionPrueba7dias = getPlanificacionPrueba7dias;
|
|
100
114
|
const changeGoalDistanceByNivelActual = (c) => {
|
|
101
|
-
if (['Comenzar a correr', 'Correr después de lesión'].includes(c.objetivoPrincipal) || [-2].includes(Number(c.goalDistance)))
|
|
115
|
+
if (['Comenzar a correr', 'Correr después de una lesión'].includes(c.objetivoPrincipal) || [-2].includes(Number(c.goalDistance)))
|
|
102
116
|
(0, getPlanificacionPrueba7diasEmpezarCorrer_1.changeGoalDistanceByNivelActualEmpezarCorrer)(c);
|
|
103
117
|
else if (Number(c.goalDistance) > 1 && Number(c.goalDistance) < 8000)
|
|
104
118
|
(0, getPlanificacionPrueba7dias5k_1.changeGoalDistanceByNivelActual5k)(c);
|
|
@@ -113,7 +127,7 @@ const changeGoalDistanceByNivelActual = (c) => {
|
|
|
113
127
|
(0, getPlanificacionPrueba7dias42k_1.changeGoalDistanceByNivelActual42k)(c);
|
|
114
128
|
};
|
|
115
129
|
const getWorkoutsByGoalDistance = (c) => {
|
|
116
|
-
if (['Comenzar a correr', 'Correr después de lesión'].includes(c.objetivoPrincipal) || Number(c.goalDistance) === -2)
|
|
130
|
+
if (['Comenzar a correr', 'Correr después de una lesión'].includes(c.objetivoPrincipal) || Number(c.goalDistance) === -2)
|
|
117
131
|
return (0, getPlanificacionPrueba7diasEmpezarCorrer_1.getPlanificacionPrueba7diasEmpezarCorrer)(c);
|
|
118
132
|
if (Number(c.goalDistance) === 5000)
|
|
119
133
|
return (0, getPlanificacionPrueba7dias5k_1.getPlanificacionPrueba7dias5k)(c);
|
|
@@ -160,6 +160,16 @@ const VENTANA_INICIO_MS = 30 * 60 * 1000;
|
|
|
160
160
|
// terminar, o redondea la duración). 2h la absorben; si aun así no solapan, son
|
|
161
161
|
// dos entrenos distintos y se respetan como dobles.
|
|
162
162
|
const VENTANA_INICIO_MANUAL_MS = 2 * 60 * 60 * 1000;
|
|
163
|
+
// Duración mínima de un entreno importado (anti "entrenos residuales"). Un
|
|
164
|
+
// arranque accidental del reloj/app (p.ej. re-lanzar la rutina al acabar y
|
|
165
|
+
// pararla al minuto) genera en el proveedor una actividad REAL de segundos con
|
|
166
|
+
// UUID propio: el dedup no puede fusionarla (es otra actividad, no solapa) y
|
|
167
|
+
// acababa como "hecho" duplicado del día con el mismo título de rutina, o
|
|
168
|
+
// completando un planificado con 60 s. Por debajo de 2 min no existe una
|
|
169
|
+
// sesión de entrenamiento legítima; se descarta en este embudo común para
|
|
170
|
+
// cubrir TODAS las fuentes (Apple/Strava/Garmin). El completado MANUAL de la
|
|
171
|
+
// app NO pasa por aquí, así que no se ve afectado.
|
|
172
|
+
const DURACION_MINIMA_INGESTA_S = 120;
|
|
163
173
|
const inicioSolapa = (a, b, ventanaMs = VENTANA_INICIO_MS) => {
|
|
164
174
|
if (!a || !b)
|
|
165
175
|
return false;
|
|
@@ -298,6 +308,12 @@ const aplicarMerge = (q, idWorkout, opts, esFilaNueva) => __awaiter(void 0, void
|
|
|
298
308
|
const saveWorkoutAplication = (opts,
|
|
299
309
|
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
|
300
310
|
_data) => __awaiter(void 0, void 0, void 0, function* () {
|
|
311
|
+
// Entreno residual: se ignora ANTES de traducir y de abrir la tx. Sólo aplica
|
|
312
|
+
// cuando la fuente informa duración; sin ella no se puede juzgar y se deja pasar.
|
|
313
|
+
const duracionIngesta = Number(opts.duration);
|
|
314
|
+
if (Number.isFinite(duracionIngesta) && duracionIngesta > 0 && duracionIngesta < DURACION_MINIMA_INGESTA_S) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
301
317
|
const dateStr = (0, moment_1.default)(opts.date).format('YYYY-MM-DD');
|
|
302
318
|
const pt = predicadoTipo(opts);
|
|
303
319
|
// Traducción FUERA de la sección crítica. getTitleDescriptionTranslated hace
|