cambios finales
This commit is contained in:
parent
d2b8aba6a0
commit
2d29bd88a1
98
README.md
98
README.md
@ -23,9 +23,9 @@ psql -U postgres -d postgres -c "ALTER ROLE postgres WITH PASSWORD 'JKHUG9876hBh
|
|||||||
HBA=$(psql -U postgres -d postgres -Atc "show hba_file"); cp "$HBA" "$HBA.bak"; sed -i 's/\btrust\b/scram-sha-256/g' "$HBA"; rc-service postgresql reload
|
HBA=$(psql -U postgres -d postgres -Atc "show hba_file"); cp "$HBA" "$HBA.bak"; sed -i 's/\btrust\b/scram-sha-256/g' "$HBA"; rc-service postgresql reload
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3) Subir codigo
|
### 3) Subir codigo (desde tu maquina)
|
||||||
```bash
|
```bash
|
||||||
mkdir -p /opt/saludut/backend
|
mkdir -p /opt/saludut/backend
|
||||||
tar -czf backend.tar.gz \
|
tar -czf backend.tar.gz \
|
||||||
--exclude="node_modules" \
|
--exclude="node_modules" \
|
||||||
--exclude=".env" \
|
--exclude=".env" \
|
||||||
@ -82,17 +82,14 @@ ADMIN_EMAIL=admin@saludut.com
|
|||||||
ADMIN_NAME=Administrador Sistema
|
ADMIN_NAME=Administrador Sistema
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6) Crear usuario admin (automatico)
|
### 6) Backend (instalar y correr)
|
||||||
Si defines `ADMIN_USER` y `ADMIN_PASS` en `.env`, el backend crea/actualiza el admin automaticamente al iniciar.
|
|
||||||
|
|
||||||
### 7) Backend (instalar y correr)
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/saludut/backend
|
cd /opt/saludut/backend
|
||||||
npm ci --omit=dev
|
npm ci --omit=dev
|
||||||
node src/server.js
|
node src/server.js
|
||||||
```
|
```
|
||||||
|
|
||||||
#### OpenRC (servicio backend)
|
#### 6.1) OpenRC (servicio backend)
|
||||||
```bash
|
```bash
|
||||||
cat <<'EOF' > /etc/init.d/saludut-backend
|
cat <<'EOF' > /etc/init.d/saludut-backend
|
||||||
#!/sbin/openrc-run
|
#!/sbin/openrc-run
|
||||||
@ -117,15 +114,19 @@ rc-update add saludut-backend default
|
|||||||
rc-service saludut-backend start
|
rc-service saludut-backend start
|
||||||
```
|
```
|
||||||
|
|
||||||
### Frontend
|
### 7) Frontend + Nginx (deploy)
|
||||||
|
|
||||||
|
#### 7.1) Acceso SSH y paquetes base
|
||||||
```bash
|
```bash
|
||||||
sed -i 's/#Port 22/Port 48952/g' /etc/ssh/sshd_config
|
sed -i 's/#Port 22/Port 48952/g' /etc/ssh/sshd_config
|
||||||
rc-service sshd restart
|
rc-service sshd restart
|
||||||
ssh -p 48952 root@autorizacion.saludut.com
|
ssh -p 48952 root@autorizacion.saludut.com
|
||||||
apk add nginx certbot certbot-nginx nftables rsync
|
apk add nginx certbot certbot-nginx nftables rsync
|
||||||
|
```
|
||||||
|
|
||||||
#Ejecutar publishNftables.sh
|
#### 7.2) Publicar scripts (nftables + frontend)
|
||||||
|
```bash
|
||||||
|
# Ejecutar publishNftables.sh
|
||||||
head -n1 ./scripts/publishNftables.sh | cat -A
|
head -n1 ./scripts/publishNftables.sh | cat -A
|
||||||
sed -i 's/\r$//' ./scripts/publishNftables.sh
|
sed -i 's/\r$//' ./scripts/publishNftables.sh
|
||||||
sed -i '1s|^.*$|#!/usr/bin/env bash|' ./scripts/publishNftables.sh
|
sed -i '1s|^.*$|#!/usr/bin/env bash|' ./scripts/publishNftables.sh
|
||||||
@ -133,15 +134,18 @@ chmod +x ./scripts/publishNftables.sh
|
|||||||
file ./scripts/publishNftables.sh
|
file ./scripts/publishNftables.sh
|
||||||
bash ./scripts/publishNftables.sh
|
bash ./scripts/publishNftables.sh
|
||||||
|
|
||||||
#Ejecutar publish.sh
|
# Ejecutar publish.sh
|
||||||
head -n1 ./scripts/publish.sh | cat -A
|
head -n1 ./scripts/publish.sh | cat -A
|
||||||
sed -i 's/\r$//' ./scripts/publish.sh
|
sed -i 's/\r$//' ./scripts/publish.sh
|
||||||
sed -i '1s|^.*$|#!/usr/bin/env bash|' ./scripts/publish.sh
|
sed -i '1s|^.*$|#!/usr/bin/env bash|' ./scripts/publish.sh
|
||||||
chmod +x ./scripts/publish.sh
|
chmod +x ./scripts/publish.sh
|
||||||
file ./scripts/publish.sh
|
file ./scripts/publish.sh
|
||||||
bash ./scripts/publish.sh
|
bash ./scripts/publish.sh
|
||||||
|
```
|
||||||
|
|
||||||
#Ejecutar el nginx de autorizacion.saludut.com
|
#### 7.3) Nginx HTTP + certbot
|
||||||
|
```bash
|
||||||
|
# Ejecutar el nginx de autorizacion.saludut.com
|
||||||
scp -P 48952 scripts/vhost/autorizacion.saludut.com.conf root@autorizacion.saludut.com:/etc/nginx/http.d/
|
scp -P 48952 scripts/vhost/autorizacion.saludut.com.conf root@autorizacion.saludut.com:/etc/nginx/http.d/
|
||||||
|
|
||||||
server {
|
server {
|
||||||
@ -177,7 +181,10 @@ server {
|
|||||||
|
|
||||||
rc-service nginx start
|
rc-service nginx start
|
||||||
sudo certbot --nginx -d autorizacion.saludut.com
|
sudo certbot --nginx -d autorizacion.saludut.com
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7.4) Nginx HTTPS (redirect + SSL)
|
||||||
|
```bash
|
||||||
scp -P 48952 scripts/vhost/autorizacion.saludut.com.conf root@autorizacion.saludut.com:/etc/nginx/http.d/
|
scp -P 48952 scripts/vhost/autorizacion.saludut.com.conf root@autorizacion.saludut.com:/etc/nginx/http.d/
|
||||||
|
|
||||||
server {
|
server {
|
||||||
@ -197,7 +204,7 @@ server {
|
|||||||
|
|
||||||
client_max_body_size 60m;
|
client_max_body_size 60m;
|
||||||
|
|
||||||
# ✅ Angular está dentro de /browser
|
# Ojo: Angular esta dentro de /browser
|
||||||
root /var/www/autorizacion.saludut.com/htdocs/browser;
|
root /var/www/autorizacion.saludut.com/htdocs/browser;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
@ -225,5 +232,70 @@ server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nginx -t && rc-service nginx restart
|
nginx -t && rc-service nginx restart
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7.5) Python venv (scripts)
|
||||||
|
```bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 1) Paquetes base para Python + compilacion (Alpine)
|
||||||
|
apk add --no-cache \
|
||||||
|
python3 py3-pip py3-virtualenv \
|
||||||
|
build-base python3-dev musl-dev \
|
||||||
|
libffi-dev openssl-dev \
|
||||||
|
openblas-dev lapack-dev gfortran \
|
||||||
|
postgresql-dev
|
||||||
|
|
||||||
|
# 2) Crear entorno virtual "general" para SaludUT
|
||||||
|
mkdir -p /opt/saludut
|
||||||
|
python3 -m venv /opt/saludut/venv
|
||||||
|
. /opt/saludut/venv/bin/activate
|
||||||
|
|
||||||
|
# 3) Actualizar pip y herramientas
|
||||||
|
python -m pip install --upgrade pip setuptools wheel
|
||||||
|
|
||||||
|
# 4) Librerias recomendadas (Excel + pandas + postgres + .env + utilidades comunes)
|
||||||
|
pip install pandas numpy openpyxl xlsxwriter xlrd python-dotenv psycopg2-binary requests pytz python-dateutil tqdm
|
||||||
|
|
||||||
|
# (Opcional util si en algun punto generas PDF con python)
|
||||||
|
pip install reportlab || true
|
||||||
|
|
||||||
|
# 5) Probar que todo importa bien
|
||||||
|
/opt/saludut/venv/bin/python -c "import pandas as pd; import openpyxl; import psycopg2; import dotenv; print('OK: pandas/openpyxl/psycopg2/dotenv')"
|
||||||
|
|
||||||
|
# 6) Actualizar .env para que Node use el Python del venv
|
||||||
|
# (cambia PYTHON_PATH=python3 -> PYTHON_PATH=/opt/saludut/venv/bin/python)
|
||||||
|
sed -i 's|^PYTHON_PATH=.*|PYTHON_PATH=/opt/saludut/venv/bin/python|g' /opt/saludut/backend/.env
|
||||||
|
|
||||||
|
# 7) Reiniciar backend y ver errores
|
||||||
|
rc-service saludut-backend restart || true
|
||||||
|
sleep 1
|
||||||
|
tail -n 80 /var/log/saludut-backend.err || true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7.6) LibreOffice + fuentes (opcional)
|
||||||
|
```bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
apk update
|
||||||
|
apk add --no-cache \
|
||||||
|
libreoffice \
|
||||||
|
fontconfig \
|
||||||
|
ttf-dejavu \
|
||||||
|
ttf-liberation \
|
||||||
|
font-noto \
|
||||||
|
font-noto-cjk \
|
||||||
|
poppler-utils \
|
||||||
|
python3 py3-pip \
|
||||||
|
py3-pandas py3-openpyxl py3-psycopg2 py3-dateutil py3-dotenv
|
||||||
|
|
||||||
|
# cache de fuentes (clave para que no salgan cuadritos)
|
||||||
|
fc-cache -f -v
|
||||||
|
|
||||||
|
# por si tu codigo usa librerias extra (opcional)
|
||||||
|
pip3 install --break-system-packages --upgrade pip
|
||||||
|
pip3 install --break-system-packages pdfplumber pypdf reportlab pillow
|
||||||
|
|
||||||
|
# verificacion
|
||||||
|
python3 -c "import pandas, openpyxl, dotenv; print('OK', pandas.__version__, openpyxl.__version__, dotenv.__version__)"
|
||||||
```
|
```
|
||||||
|
|||||||
@ -96,6 +96,26 @@ CREATE TABLE IF NOT EXISTS usuario (
|
|||||||
token_version INTEGER NOT NULL DEFAULT 1
|
token_version INTEGER NOT NULL DEFAULT 1
|
||||||
);
|
);
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS validar_contrasena(text);
|
||||||
|
CREATE OR REPLACE FUNCTION validar_contrasena(contrasena TEXT)
|
||||||
|
RETURNS BOOLEAN AS $$
|
||||||
|
BEGIN
|
||||||
|
IF contrasena IS NULL THEN
|
||||||
|
RETURN false;
|
||||||
|
END IF;
|
||||||
|
IF length(contrasena) < 8 THEN
|
||||||
|
RETURN false;
|
||||||
|
END IF;
|
||||||
|
IF contrasena !~ '[A-Za-z]' THEN
|
||||||
|
RETURN false;
|
||||||
|
END IF;
|
||||||
|
IF contrasena !~ '[0-9]' THEN
|
||||||
|
RETURN false;
|
||||||
|
END IF;
|
||||||
|
RETURN true;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS usuario_sede (
|
CREATE TABLE IF NOT EXISTS usuario_sede (
|
||||||
id_usuario_sede SERIAL PRIMARY KEY,
|
id_usuario_sede SERIAL PRIMARY KEY,
|
||||||
id_usuario INTEGER NOT NULL REFERENCES usuario(id_usuario),
|
id_usuario INTEGER NOT NULL REFERENCES usuario(id_usuario),
|
||||||
|
|||||||
@ -335,6 +335,60 @@ const ensureUsuarioTokenVersion = async () => {
|
|||||||
`);
|
`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ensurePasswordValidationFunction = async () => {
|
||||||
|
await pool.query(`
|
||||||
|
DROP FUNCTION IF EXISTS validar_contrasena(text);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
CREATE OR REPLACE FUNCTION validar_contrasena(contrasena TEXT)
|
||||||
|
RETURNS BOOLEAN AS $$
|
||||||
|
BEGIN
|
||||||
|
IF contrasena IS NULL THEN
|
||||||
|
RETURN false;
|
||||||
|
END IF;
|
||||||
|
IF length(contrasena) < 8 THEN
|
||||||
|
RETURN false;
|
||||||
|
END IF;
|
||||||
|
IF contrasena !~ '[A-Za-z]' THEN
|
||||||
|
RETURN false;
|
||||||
|
END IF;
|
||||||
|
IF contrasena !~ '[0-9]' THEN
|
||||||
|
RETURN false;
|
||||||
|
END IF;
|
||||||
|
RETURN true;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validarContrasenaLocal = (password) => {
|
||||||
|
if (!password || password.length < 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!/[A-Za-z]/.test(password)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!/[0-9]/.test(password)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validarContrasena = async (password) => {
|
||||||
|
try {
|
||||||
|
const sqlValidar = 'SELECT validar_contrasena($1) as valida';
|
||||||
|
const { rows } = await pool.query(sqlValidar, [password]);
|
||||||
|
return rows[0]?.valida === true;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
'validar_contrasena no disponible, usando validacion local:',
|
||||||
|
error?.message || error
|
||||||
|
);
|
||||||
|
return validarContrasenaLocal(password);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const ensureAdminFromEnv = async () => {
|
const ensureAdminFromEnv = async () => {
|
||||||
const adminUser = process.env.ADMIN_USER;
|
const adminUser = process.env.ADMIN_USER;
|
||||||
const adminPass = process.env.ADMIN_PASS;
|
const adminPass = process.env.ADMIN_PASS;
|
||||||
@ -3270,16 +3324,19 @@ app.post('/api/auth/register', verificarToken, esAdministrador, async (req, res)
|
|||||||
|
|
||||||
console.log('Solicitud de registro:', req.body);
|
console.log('Solicitud de registro:', req.body);
|
||||||
|
|
||||||
if (!username || !email || !password || !nombre_completo || !id_rol) {
|
if (!username || !email || !password || !nombre_completo || id_rol === undefined || id_rol === null) {
|
||||||
return res.status(400).json({ error: 'Todos los campos son requeridos' });
|
return res.status(400).json({ error: 'Todos los campos son requeridos' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const idRol = Number(id_rol);
|
||||||
|
if (!Number.isFinite(idRol)) {
|
||||||
|
return res.status(400).json({ error: 'id_rol invalido' });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Validar contraseña con la función de Postgres
|
// Validar contraseña con la función de Postgres
|
||||||
const sqlValidar = 'SELECT validar_contrasena($1) as valida';
|
const valida = await validarContrasena(password);
|
||||||
const { rows } = await pool.query(sqlValidar, [password]);
|
if (!valida) {
|
||||||
|
|
||||||
if (!rows[0].valida) {
|
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'La contraseña debe tener al menos 8 caracteres y contener letras y números'
|
error: 'La contraseña debe tener al menos 8 caracteres y contener letras y números'
|
||||||
});
|
});
|
||||||
@ -3293,6 +3350,11 @@ app.post('/api/auth/register', verificarToken, esAdministrador, async (req, res)
|
|||||||
return res.status(400).json({ error: 'El usuario o email ya están registrados' });
|
return res.status(400).json({ error: 'El usuario o email ya están registrados' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rolCheck = await pool.query('SELECT id_rol FROM rol WHERE id_rol = $1', [idRol]);
|
||||||
|
if (rolCheck.rows.length === 0) {
|
||||||
|
return res.status(400).json({ error: 'El rol seleccionado no existe' });
|
||||||
|
}
|
||||||
|
|
||||||
// Encriptar contraseña
|
// Encriptar contraseña
|
||||||
const passwordHash = await bcrypt.hash(password, 10);
|
const passwordHash = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
@ -3304,13 +3366,13 @@ app.post('/api/auth/register', verificarToken, esAdministrador, async (req, res)
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const { rows: newUsuario } = await pool.query(insertSql, [
|
const { rows: newUsuario } = await pool.query(insertSql, [
|
||||||
username, email, passwordHash, nombre_completo, id_rol
|
username, email, passwordHash, nombre_completo, idRol
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const usuarioCreado = newUsuario[0];
|
const usuarioCreado = newUsuario[0];
|
||||||
|
|
||||||
// Si es administrativo_sede, asignar sedes
|
// Si es administrativo_sede, asignar sedes
|
||||||
if (id_rol == 2) {
|
if (idRol === 2) {
|
||||||
let sedesAsignadas = Array.isArray(sedes) ? sedes : [];
|
let sedesAsignadas = Array.isArray(sedes) ? sedes : [];
|
||||||
sedesAsignadas = sedesAsignadas
|
sedesAsignadas = sedesAsignadas
|
||||||
.map((s) => String(s || '').trim())
|
.map((s) => String(s || '').trim())
|
||||||
@ -3505,9 +3567,8 @@ app.patch('/api/usuarios/:id', verificarToken, esAdministrador, async (req, res)
|
|||||||
return res.status(400).json({ error: 'password no puede estar vacia' });
|
return res.status(400).json({ error: 'password no puede estar vacia' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const sqlValidar = 'SELECT validar_contrasena($1) as valida';
|
const valida = await validarContrasena(passwordValue);
|
||||||
const { rows: validacion } = await pool.query(sqlValidar, [passwordValue]);
|
if (!valida) {
|
||||||
if (!validacion[0]?.valida) {
|
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'La contrasena debe tener al menos 8 caracteres y contener letras y numeros'
|
error: 'La contrasena debe tener al menos 8 caracteres y contener letras y numeros'
|
||||||
});
|
});
|
||||||
@ -3837,6 +3898,10 @@ ensureUsuarioTokenVersion().catch((error) => {
|
|||||||
console.error('Error inicializando token_version de usuario:', error.message);
|
console.error('Error inicializando token_version de usuario:', error.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ensurePasswordValidationFunction().catch((error) => {
|
||||||
|
console.error('Error inicializando validar_contrasena:', error.message);
|
||||||
|
});
|
||||||
|
|
||||||
ensureAdminFromEnv().catch((error) => {
|
ensureAdminFromEnv().catch((error) => {
|
||||||
console.warn('Error asegurando admin desde .env:', error.message);
|
console.warn('Error asegurando admin desde .env:', error.message);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -205,7 +205,7 @@ export class EstadisticasAutorizacionesComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
formatDateLabel(fecha: string): string {
|
formatDateLabel(fecha: string): string {
|
||||||
const date = new Date(fecha);
|
const date = this.parseDate(fecha);
|
||||||
if (Number.isNaN(date.getTime())) return fecha;
|
if (Number.isNaN(date.getTime())) return fecha;
|
||||||
return date.toLocaleDateString('es-CO', {
|
return date.toLocaleDateString('es-CO', {
|
||||||
weekday: 'short',
|
weekday: 'short',
|
||||||
@ -296,7 +296,7 @@ export class EstadisticasAutorizacionesComponent implements OnInit {
|
|||||||
|
|
||||||
if (this.periodo === 'dia') {
|
if (this.periodo === 'dia') {
|
||||||
return dias.map((dia) => {
|
return dias.map((dia) => {
|
||||||
const date = new Date(dia.fecha);
|
const date = this.parseDate(dia.fecha);
|
||||||
const label = Number.isNaN(date.getTime())
|
const label = Number.isNaN(date.getTime())
|
||||||
? ''
|
? ''
|
||||||
: String(date.getDate());
|
: String(date.getDate());
|
||||||
@ -313,13 +313,13 @@ export class EstadisticasAutorizacionesComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const rangoInicio = new Date(rango.inicio);
|
const rangoInicio = this.parseDate(rango.inicio);
|
||||||
const rangoFin = new Date(rango.fin);
|
const rangoFin = this.parseDate(rango.fin);
|
||||||
|
|
||||||
if (this.periodo === 'semana') {
|
if (this.periodo === 'semana') {
|
||||||
const buckets = new Map<string, ChartBucket>();
|
const buckets = new Map<string, ChartBucket>();
|
||||||
dias.forEach((dia) => {
|
dias.forEach((dia) => {
|
||||||
const date = new Date(dia.fecha);
|
const date = this.parseDate(dia.fecha);
|
||||||
if (Number.isNaN(date.getTime())) return;
|
if (Number.isNaN(date.getTime())) return;
|
||||||
const weekStart = this.getWeekStart(date);
|
const weekStart = this.getWeekStart(date);
|
||||||
const weekEnd = this.addDays(weekStart, 6);
|
const weekEnd = this.addDays(weekStart, 6);
|
||||||
@ -352,7 +352,7 @@ export class EstadisticasAutorizacionesComponent implements OnInit {
|
|||||||
if (this.periodo === 'mes') {
|
if (this.periodo === 'mes') {
|
||||||
const buckets = new Map<string, ChartBucket>();
|
const buckets = new Map<string, ChartBucket>();
|
||||||
dias.forEach((dia) => {
|
dias.forEach((dia) => {
|
||||||
const date = new Date(dia.fecha);
|
const date = this.parseDate(dia.fecha);
|
||||||
if (Number.isNaN(date.getTime())) return;
|
if (Number.isNaN(date.getTime())) return;
|
||||||
const key = `${date.getFullYear()}-${date.getMonth()}`;
|
const key = `${date.getFullYear()}-${date.getMonth()}`;
|
||||||
if (!buckets.has(key)) {
|
if (!buckets.has(key)) {
|
||||||
@ -384,7 +384,7 @@ export class EstadisticasAutorizacionesComponent implements OnInit {
|
|||||||
|
|
||||||
const buckets = new Map<string, ChartBucket>();
|
const buckets = new Map<string, ChartBucket>();
|
||||||
dias.forEach((dia) => {
|
dias.forEach((dia) => {
|
||||||
const date = new Date(dia.fecha);
|
const date = this.parseDate(dia.fecha);
|
||||||
if (Number.isNaN(date.getTime())) return;
|
if (Number.isNaN(date.getTime())) return;
|
||||||
const year = date.getFullYear();
|
const year = date.getFullYear();
|
||||||
const key = String(year);
|
const key = String(year);
|
||||||
@ -438,6 +438,24 @@ export class EstadisticasAutorizacionesComponent implements OnInit {
|
|||||||
return a < b ? a : b;
|
return a < b ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private parseDate(value: string): Date {
|
||||||
|
if (!value) {
|
||||||
|
return new Date('invalid');
|
||||||
|
}
|
||||||
|
const clean = value.slice(0, 10);
|
||||||
|
const parts = clean.split('-');
|
||||||
|
if (parts.length !== 3) {
|
||||||
|
return new Date(value);
|
||||||
|
}
|
||||||
|
const year = Number(parts[0]);
|
||||||
|
const month = Number(parts[1]);
|
||||||
|
const day = Number(parts[2]);
|
||||||
|
if (!year || !month || !day) {
|
||||||
|
return new Date(value);
|
||||||
|
}
|
||||||
|
return new Date(year, month - 1, day);
|
||||||
|
}
|
||||||
|
|
||||||
private formatWeekLabel(inicio: Date, fin: Date): string {
|
private formatWeekLabel(inicio: Date, fin: Date): string {
|
||||||
const inicioDia = inicio.getDate();
|
const inicioDia = inicio.getDate();
|
||||||
const finDia = fin.getDate();
|
const finDia = fin.getDate();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user