🚀 P02: Despliegue exitoso del sistema DASUTEN en pcv-dasu0

Infraestructura y Red:
- Tailscale v1.94.2 instalado en srv-dasu (IP: 100.116.210.36)
- Configurado como subnet router para 10.0.100.0/24
- Fix: Repositorios enterprise de Proxmox deshabilitados (.sources → .bak)

Despliegue del Sistema (pcv-dasu0):
- Transferencia SCP de runSysDasuten.zip via SSH puerto 7022
- Extracción en C:\Dasuten\runSysDasuten- Kermet.ini configurado: SERVER=sql-dasuten
- InstaladorComponentesKermet.exe (silent install)
- Fuentes TrueType registradas en Windows (Code39, I2OF5)
- Firewall TCP 1433 abierto en sql-dasuten
- Test-NetConnection: TcpTestSucceeded=True
- Login exitoso con usuario aalmiron 

ADN y Contexto:
- 05_ia.md: Regla 13 Auto-Contextualización Topológica
- 07_proyectos.md: Sección 'Topología y Red' obligatoria en manifiestos
- P2601_dasuten.md: Contexto de red documentado
- nodos/pcv-dasu0.md: Puerto SSH actualizado a 7022

Dashboard P2601:
- Hitos completados: 8 → 11 (P02.APP, NET01, TEST03)
- Progreso: 67% → 79%
- pcv-dasu0: Testing → Online
- Nuevo hito activo: UAT (verificación presencial)

Bitácoras y Documentación:
- Bitácora 04/03 y 05/03 completas
- Plan de despliegue en docs/plan/260305-1017
- Investigación de ingeniería inversa documentada
- ns8-candados: Instrucciones de uso para IA agregadas
- dtic-BITACORAs: CRUD completo, Dashboard P2601 integrado

Herramientas:
- w-zombi: Mejora anti-loop (max 5 ciclos)
- Payloads de despliegue SQL y sistema generados
This commit is contained in:
Ricardo Monla
2026-03-05 13:15:31 -03:00
parent 680dbe6f8d
commit 15725e0b3c
24826 changed files with 3499 additions and 3325954 deletions
+13
View File
@@ -27,3 +27,16 @@ tmp/
tools/ns8-candados/.boveda.json
.agent_token
apps/
tools/w-zombi/payloads/runSysDasuten.zip
# Node
node_modules/
dist/
build/
.pnp
.pnp.js
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.yarn/berry.cjs
+4
View File
@@ -44,3 +44,7 @@ Cualquier script efímero (Python, Bash, PowerShell) creado por la IA para asist
### 12. Principio Bitácora-First (Anticipación)
Todo hito mayor, prueba de concepto técnica o interacción de red iniciada por la IA debe ser documentado en un borrador `⏳` dentro de la Bitácora **ANTES** de ejecutar los comandos en la Terminal. Una vez que la validación finalice, actualizar dicha fila al estado `✅` o `❌`.
### 13. Auto-Contextualización Geográfica y Topológica
Antes de iniciar escaneos, pruebas de red, o conexiones SSH a un nodo de un proyecto, la IA **DEBE** investigar autónomamente su ubicación en la topología (ej. leyendo los encuadres en `nodos/` y `proyectos/`).
- **Objetivo:** Identificar si el nodo objetivo está en una subred aislada, detrás de un NAT, o si requiere un salto de red (ProxyJump) a través de un hypervisor anfitrión, previniendo errores ciegos y garantizando trazabilidad geográfica.
+1
View File
@@ -30,6 +30,7 @@ Cada proyecto debe tener un archivo manifiesto con las siguientes secciones:
| **Nombre** | Nombre descriptivo del proyecto. |
| **Objetivo** | Para qué existe, qué problema resuelve. |
| **Stakeholders** | Quién consulta/supervisa el avance (cargo, relación). |
| **Topología y Red** | Contexto espacial: subredes en uso, hypervisors anfitriones, aislamiento NAT y reglas de ruteo/VPN necesarias para acceder al entorno. |
| **Nodos Involucrados** | Qué infraestructura participa (links a `nodos/`). Debe categorizarse en **Nodos Exclusivos** (Core) y **Nodos de Soporte/Infraestructura**. |
| **Herramientas** | Software, scripts y servicios utilizados. |
| **Hitos Clave** | Timeline de alto nivel con estados y fechas. |
+98
View File
@@ -0,0 +1,98 @@
# Bitácora de Operaciones - 04/03/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [➡️ - P02 - **Sistema DASUTEN**](2026-03-05.md) | sql-dasuten | ➡️ **[Continúa el 05/03](2026-03-05.md)**<br>Despliegue e instalación del software del sistema DASUTEN en la VM con la base de datos operativa. |
| [➡️ - TEST03 - **Conectividad SQL**](2026-03-05.md) | pcv-dasu0 | ➡️ **[Continúa el 05/03](2026-03-05.md)**<br>Pruebas de acceso al motor SQL Server desde la VM de pruebas. |
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [➡️ - P2603 - **Sistema de Bitácoras**](2026-03-05.md) | srv-ns8 | ➡️ **[Continúa el 05/03](2026-03-05.md)**<br>Evolución de `dtic-BITACORAs`: CRUD completo implementado. |
| [➡️ - P2602 - **Proyecto Backups DTIC**](2026-03-05.md) | srv-ns8 | ➡️ **[Continúa el 05/03](2026-03-05.md)**<br>Fase 1: Reconocimiento. SSH 12/15 ✅. Pendiente: pcv-dasu1/2, pcv-serviio (Windows pmox2). |
### ✅ Resueltos
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [✅ - INC01 - **Sin Red srvv-maurik**](#srvv-maurik) | srvv-maurik | 🟢 Resuelto. Reasignación de VIF erróneo en XenServer. |
| [✅ - S02 - **Hardenización SSH**](#sql-dasuten) | sql-dasuten | 🟢 OpenSSH instalado en puerto 7022. Conexión SSH directa verificada. |
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| :--- | :--- |
| srv-ns8 | Jornada remota (10:45) y presencial (16:12). CRUD dtic-BITACORAs implementado. Mejora anti-loop en w-zombi. Conectividad red 10.0.100.x restaurada. |
| pc-dasu0 | Reporte de incidente (08:24) por Andrea Almiron. Pérdida de acceso directo por caída de red de servidores. |
| srvv-maurik | Incidente de red resuelto (12:00). Reasignación de VIF erróneo en XenServer. |
| sql-dasuten | SSH habilitado (puerto 7022) vía w-zombi. Conexión directa desde srv-ns8 verificada. |
---
## 📂 Actividades Detalladas
### srv-ns8
#### ⏳ - SINC01 - Inicio de Jornada Remota
🚀 Inicio de jornada remota (10:45). Tareas administrativas y continuación de desarrollo.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 10:45 | 10:48 | (IA) **Rollover 2026-03-04**: Creación de bitácora diaria y migración de pendientes del 03/03. | [R] ✅ |
| 10:48 | 10:53 | (IA) **P2603 - CRUD Completo**: Implementación de CRUD completo para nodos, bitácoras y entradas con confirmación de borrado y edición inline. Contenedores reconstruidos. | [R] ✅ |
| 11:25 | 11:25 | (IA) **ns8-candados**: Almacenamiento de credenciales SSH de srv-xen1 (`ssh-xen1`). | [R] ✅ |
| 12:39 | 16:12 | (IA) **SINC01 - Pausa**: El usuario cierra sesión remota y la IA continúa operando en P2603 de forma asíncrona. | [R] ✅ |
| 16:12 | 16:27 | **SINC02 - Inicio de Jornada Presencial**: Retoma de actividades en sitio. Conectividad con red 10.0.100.x restaurada (ruta estática + fix typo en srv-dasu). VMs DASUTEN alcanzables. | [P] ✅ |
| 16:33 | 17:04 | (IA) **w-zombi - Mejora anti-loop**: Implementación de mecanismo de seguridad: máximo 5 ciclos totales antes de auto-detención. Servidor reiniciado con nuevo código. | [R] ✅ |
| 17:39 | 18:03 | **INC03 - Sin acceso a /apps**: Causa raíz: DNS de `srvv-maurik` no resuelve `ns8.frlr.utn.edu.ar` (NXDOMAIN). Registro A agregado pero aún falla. Pausado para continuar después. | [P] ⚠️ |
| 18:03 | 18:09 | (IA) **P02 - Ingeniería Inversa**: Análisis del ejecutable `DasutenSQL.exe` (VFP9/Kermet). Identificada cadena de conexión en `Kermet.ini` (`SERVER=srvFENIX`, `DATABASE=sysdasuten`). Documentado en [`P2601_dasuten_ingenieria_inversa.md`](../proyectos/P2601_dasuten_ingenieria_inversa.md). | [R] ✅ |
| 18:44 | 18:50 | (IA) **P02 - Preparación de Despliegue**: Empaquetado de `runSysDasuten/` (268MB zip) y creación de payload w-zombi para despliegue automático en `pcv-dasu0`. | [R] ✅ |
| 19:25 | 19:33 | (IA) **Dashboard P2601 - Restauración**: El directorio `dashboard/` no existía (nunca fue commiteado a git). Recreado con panel estático. Fix de permisos Nginx (`chmod o+x`). | [R] ✅ |
| 19:46 | 19:59 | (IA) **Dashboard P2601 - Plan de Integración**: Diseño de integración del dashboard como parte de dtic-BITACORAs. Nuevas tablas (proyectos, fases, hitos), API REST y componente React. 6 fases definidas. Plan en [`docs/260304-1959_plan_dashboard_P2601_en_bitacoras.md`](../docs/260304-1959_plan_dashboard_P2601_en_bitacoras.md). | [R] ✅ |
| 19:59 | 20:10 | (IA) **Dashboard P2601 - Implementación**: Migración BD (3 tablas, 15 hitos), API REST `/api/proyectos`, componente React `ProyectoDashboard.tsx`. Fix columna ambigua. Docker rebuild. Verificado OK. Plan en [`docs/260304-1959_plan_dashboard_P2601_en_bitacoras.md`](../docs/260304-1959_plan_dashboard_P2601_en_bitacoras.md). | [R] ✅ |
### pc-dasu0
#### ✅ - INC01 - Sin Red srvv-maurik (Reporte inicial)
Reporte de falla de sistema por parte de Andrea Almiron. Pérdida de acceso directo debido a falta de conectividad con servidores VDI/AD.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:24 | 11:08 | **Reporte de Incidente**: Usuario reporta que no puede ingresar al sistema porque se borró el acceso directo del escritorio. Intentos fallidos de reconexión evidencian un problema subyacente de conectividad con `srvv-fenix` o `srvv-maurik`. Se escala a infraestructura y se convoca agente IA. | [P] ⚠️ |
### srvv-maurik (VM en srv-xen1)
#### ✅ - INC01 - Sin Red en VM (Diagnóstico y Resolución)
🟢 **Resolución**: Incidente restaurado exitosamente corrigiendo asignación de VIF en XenServer.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 11:08 | 11:11 | (IA) **Análisis de Red (XenCenter)**: `ipconfig /all` muestra IP 10.0.10.10, ping localhost OK, ping a 10.0.10.23 falla. Problema en **capa 2** (VIF/vSwitch). VM reiniciada y AV desactivado sin mejoras. | [R] ✅ |
| 11:11 | 11:30 | (IA) **Diagnóstico desde srv-xen1**: Conexión SSH como `rmonla@10.0.10.23`. Hallazgos: VIF device=1 está attached en red equivocada (`xenbr1/eth1`), VIF device=0 **NOT ATTACHED**. | [R] ✅ |
| 11:30 | 11:38 | (IA) **Reparación de VIF**: Se descubrió VIF activa en Virtual Switch erróneo. Se destruyó y recreó adaptador asociándolo a la red correcta, heredando la misma MAC `76:2a:9d:93:b2:c8`. Restauración completa en capa L2. | [R] ✅ |
| 11:38 | 12:00 | **Verificación del Usuario**: Notificación de operabilidad a Andrea Almiron. Confirmación de acceso al sistema exitoso. Incidente finalizado y cerrado. | [P] ✅ |
### sql-dasuten
#### ✅ - S02 - Hardenización SSH
🟢 Instalación y configuración de OpenSSH Server en `sql-dasuten` para permitir acceso remoto seguro.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 16:27 | 16:30 | **Instalación SSH**: Ejecución de `setup.ps1` vía w-zombi desde la consola del servidor. OpenSSH instalado, puerto 7022, firewall abierto, servicio reiniciado. | [P] ✅ |
| 16:30 | 16:31 | (IA) **Verificación SSH**: Conexión directa desde srv-ns8 a `admindasu@10.0.100.11:7022` exitosa. Hostname confirmado. | [R] ✅ |
| 17:06 | 17:14 | (IA) **P02 - Acceso SQL Server**: Login `DASUTEN\\admindasu` no existía en SQL Server. Payload w-zombi v2: modo single-user → `CREATE LOGIN` + `ALTER SERVER ROLE sysadmin`. Fix exitoso. | [R] ✅ |
| 17:14 | 17:16 | (IA) **P02 - Restauración Base de Datos**: `RESTORE DATABASE [sysdasuten]` desde `C:\\bksysdasuten_Full.bak` (~9GB). Datos movidos a `S:\\Data\\`. 1.132.497 páginas procesadas en 46s (189 MB/s). | [R] ✅ |
### pcv-dasu0 (VM de pruebas)
#### ⏳ - TEST03 - Pruebas de Acceso al Sistema DASUTEN
🔧 Preparación de la VM de pruebas para verificar conectividad con SQL Server y ejecución del sistema.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 17:25 | 17:31 | **Inicio de VM y SSH**: VM iniciada por el usuario. Auto-start habilitado en las 3 VMs. SSH instalado vía w-zombi (puerto 7022). Conexión verificada desde srv-ns8. | [P] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+81
View File
@@ -0,0 +1,81 @@
# Bitácora de Operaciones - 05/03/2026
## Control de Gestión
### 🚩 Pendientes
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [📍 - INC03 - **Sin acceso a /apps**](#srv-ns8) | srv-ns8 | ➡️ **[Viene del 04/03](2026-03-04.md)**<br>Problema de resolución DNS en `srvv-maurik` para `ns8.frlr.utn.edu.ar`. |
### ⏳ En Proceso
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [⏳ - P02 - **Sistema DASUTEN**](#pcv-dasu0) | pcv-dasu0 | Despliegue exitoso. Login verificado con `aalmiron`. Pendiente: verificación presencial. |
| [⏳ - P2603 - **Sistema de Bitácoras**](#srv-ns8) | srv-ns8 | ➡️ **[Viene del 04/03](2026-03-04.md)**<br>Evolución de `dtic-BITACORAs`: Integración del Dashboard P2601 y optimización de herramientas. |
| [⏳ - P2602 - **Proyecto Backups DTIC**](#srv-ns8) | srv-ns8 | ➡️ **[Viene del 04/03](2026-03-04.md)**<br>Fase 1: Reconocimiento. SSH 12/15 ✅. Pendiente: pcv-dasu1/2, pcv-serviio (Windows pmox2). |
### ✅ Resueltos
| ID | NODO | DETALLE |
| :--- | :--- | :--- |
| [✅ - TEST03 - **Conectividad SQL**](#pcv-dasu0) | pcv-dasu0 | 🟢 Resuelto. Firewall TCP 1433 abierto. `TcpTestSucceeded: True`. Login exitoso con usuario `aalmiron`. |
| [✅ - NET01 - **Tailscale srv-dasu**](#srv-dasu) | srv-dasu | 🟢 Resuelto. Tailscale v1.94.2 instalado. IP: `100.116.210.36`. Subnet router `10.0.100.0/24` habilitado. |
### 📝 Resumen de Actividades
| NODO | RESUMEN INTEGRAL |
| :--- | :--- |
| srv-ns8 | Inicio de jornada presencial (08:00). Análisis de alineación con el ADN y bitácora anterior. Rollover de tareas completado. Configuración técnica para optimización de IA en el editor. |
| srv-dasu | Instalación de Tailscale (v1.94.2) y configuración como subnet router para alcanzar la red aislada 10.0.100.0/24 desde Tailscale. |
| pcv-dasu0 | Despliegue completo del sistema DASUTEN: SCP, extracción, config Kermet.ini, componentes, fuentes, firewall SQL, login exitoso con `aalmiron`. Pendiente verificación presencial. |
---
## 📂 Actividades Detalladas
### srv-dasu
#### ✅ - NET01 - Despliegue de Tailscale
🚀 Instalación de Tailscale en el hypervisor anfitrión (10.0.10.205) para establecer túnel VPN y garantizar acceso remoto exterior a la subred aislada 10.0.100.x de las VMs.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 10:21 | 10:28 | (IA) **Instalación:** Ejecución de script de instalación automatizado. Tailscale v1.94.2 instalado, autenticado (IP: `100.116.210.36`) y configurado como subnet router para `10.0.100.0/24`. | [P] ✅ |
---
### pcv-dasu0
#### ⏳ - P02 - Pruebas de funcionamiento DASUTEN
🚀 Inicio de pruebas del sistema Dasuten. Fase 1: Verificación de acceso SSH. Fase 2: Despliegue de aplicación e ingeniería.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 09:30 | 09:40 | 🔒 **Credenciales**: Modificación de la herramienta ns8-candados y obtención de credenciales seguras para pcv-dasu0. | [P] ✅ |
| 10:17 | 10:20 | (IA) **P02 - Plan de Despliegue**: Creación del documento de paso a paso para transferencia y configuración de `runSysDasuten.zip` en `docs/plan`. | [P] ✅ |
| 11:01 | 11:03 | (IA) **P02 - Corrección Contextual y SSH**: Actualización de ADN (`nodos/pcv-dasu0.md`) denotando el uso del puerto `7022` por hardering. Transferencia SCP del payload `runSysDasuten.zip` al anfitrión. | [P] ✅ |
| 11:03 | 11:05 | (IA) **P02 - Extracción de Payload**: Descompresión del sistema Dasuten vía PowerShell remoting hacia `C:\Dasuten`. | [P] ✅ |
| 11:05 | 11:07 | (IA) **P02 - Configuración Kermet**: Inyección de cadenas de conexión en `Kermet.ini` (SERVER=sql-dasuten) en el binario desplegado. | [P] ✅ |
| 11:07 | 11:09 | (IA) **P02 - Dependencias y Fuentes**: Instalación de InstaladorComponentesKermet.exe y carga de fuentes TrueType en el registro de Windows. | [P] ✅ |
| 11:09 | 11:31 | (IA) **P02 - Conectividad SQL**: Se abrió regla de firewall TCP 1433 en sql-dasuten. Test-NetConnection confirmó `TcpTestSucceeded: True`. | [P] ✅ |
| 11:31 | 11:35 | (IA) **P02 - Verificación GUI**: Lanzamiento de `DasutenSQL.exe`. Pantalla de login confirmó el correcto arranque del sistema. | [P] ✅ |
| 11:40 | 11:42 | (IA) **P02 - Indagación de Usuarios**: Consulta de la tabla `Usuarios` en `sysdasuten`. Se identificaron 13 registros. | [P] ✅ |
| 11:48 | 11:55 | ✅ **P02 - Login Exitoso**: Prueba de ingreso al sistema con credenciales de `aalmiron` (proporcionadas por Andrea Almirón). Login y arranque inicial del sistema exitoso. | [P] ✅ |
| 11:55 | 12:57 | 👤 **P02 - Verificación Presencial**: Andrea Almirón (usuario final) validó in-situ el estado funcional y operativo del sistema. Resultado **EXITOSO**. | [P] ✅ |
| 12:08 | 12:15 | (IA) **Dashboard P2601**: Cierre de TEST03 y NET01. Actualización de métricas (11 hitos, 79%), nodos y timeline en `dashboard/index.html`. | [P] ✅ |
---
### srv-ns8
#### ⏳ - SINC01 - Inicio de Jornada Presencial
🚀 Inicio de jornada (08:00). Sincronización de contexto técnico y operativo basado en las hebras del ADN.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 08:00 | 08:15 | (IA) **Análisis de ADN**: Revisión de hebras 02 (Bitácora), 04 (Iconografía) y 05 (IA) para asegurar el cumplimiento de normativas en la nueva jornada. | [P] ✅ |
| 08:15 | 08:30 | (IA) **Rollover 2026-03-05**: Migración de pendientes y estados "En Proceso" desde la bitácora del 04/03. | [P] ✅ |
| 08:30 | 08:45 | 👁️ **Optimización Zed + DeepSeek**: Análisis de configuración para integrar DeepSeek en Zed via OpenAI-compatible API. Se requiere editar `settings.json` con el `base_url: "https://api.deepseek.com"` y el modelo `deepseek-chat`. | [P] 👁️ |
| 10:10 | 10:15 | (IA) **ADN**: Volcado de plan en `docs/plan` y actualización de hebras 05_ia, 07_proyectos y el manifiesto P2601_dasuten. | [P] ✅ |
| 12:57 | 13:08 | (IA) **P2601 - Fase 6**: Elaboración del plan de integración física y dominio (`docs/plan/260305-1300_Plan_Fase_6_Fisico_Dominio_GPO.md`). Actualización de contexto operativo en ADN (Tailscale router). | [P] ✅ |
---
<!-- 🤖 PREMISAS DE TRABAJO (IA): Fuente de Verdad → adn/05_ia.md -->
+708
View File
@@ -0,0 +1,708 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>P2601 — Proyecto DASUTEN | Dashboard de Seguimiento</title>
<meta name="description"
content="Panel de seguimiento del proyecto de migración del sistema DASUTEN a infraestructura independiente.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap"
rel="stylesheet">
<style>
:root {
--bg-primary: #0a0e1a;
--bg-secondary: #111827;
--bg-card: rgba(30, 41, 59, 0.65);
--border-glass: rgba(255, 255, 255, 0.08);
--accent: #38bdf8;
--accent-glow: rgba(56, 189, 248, 0.15);
--green: #22c55e;
--yellow: #eab308;
--red: #ef4444;
--orange: #f97316;
--text: #e2e8f0;
--text-muted: #94a3b8;
--text-dim: #64748b;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', system-ui, sans-serif;
background: var(--bg-primary);
color: var(--text);
min-height: 100vh;
background-image:
radial-gradient(ellipse at 20% 50%, rgba(56, 189, 248, 0.04) 0%, transparent 50%),
radial-gradient(ellipse at 80% 20%, rgba(139, 92, 246, 0.03) 0%, transparent 50%);
}
.container {
max-width: 1100px;
margin: 0 auto;
padding: 2rem 1.5rem;
}
/* Header */
.header {
text-align: center;
margin-bottom: 2.5rem;
}
.header-badge {
display: inline-block;
padding: 4px 14px;
border-radius: 999px;
background: var(--accent-glow);
color: var(--accent);
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
margin-bottom: 0.75rem;
}
.header h1 {
font-size: 2.2rem;
font-weight: 800;
margin-bottom: 0.3rem;
}
.header h1 span {
background: linear-gradient(135deg, var(--accent), #a78bfa);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.header .subtitle {
color: var(--text-muted);
font-size: 0.95rem;
font-weight: 300;
}
/* Metrics bar */
.metrics-bar {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.metric {
background: var(--bg-card);
backdrop-filter: blur(12px);
border: 1px solid var(--border-glass);
border-radius: 12px;
padding: 1.2rem;
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
}
.metric:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}
.metric .value {
font-size: 1.8rem;
font-weight: 700;
line-height: 1;
}
.metric .label {
font-size: 0.72rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.06em;
margin-top: 0.4rem;
}
.metric .value.accent {
color: var(--accent);
}
.metric .value.green {
color: var(--green);
}
.metric .value.yellow {
color: var(--yellow);
}
/* Progress bar */
.progress-section {
margin-bottom: 2rem;
}
.progress-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.progress-header h3 {
font-size: 0.9rem;
font-weight: 600;
}
.progress-header span {
font-size: 0.85rem;
color: var(--accent);
font-weight: 600;
}
.progress-track {
width: 100%;
height: 10px;
background: rgba(255, 255, 255, 0.06);
border-radius: 999px;
overflow: hidden;
}
.progress-fill {
height: 100%;
border-radius: 999px;
background: linear-gradient(90deg, var(--accent), #818cf8);
transition: width 0.8s ease;
}
/* Sections */
.section-title {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-dim);
font-weight: 600;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border-glass);
}
/* Timeline */
.timeline {
position: relative;
margin-bottom: 2.5rem;
}
.timeline::before {
content: '';
position: absolute;
left: 18px;
top: 0;
bottom: 0;
width: 2px;
background: linear-gradient(180deg, var(--accent), var(--border-glass));
}
.tl-item {
position: relative;
padding-left: 48px;
margin-bottom: 1.2rem;
}
.tl-dot {
position: absolute;
left: 10px;
top: 4px;
width: 18px;
height: 18px;
border-radius: 50%;
border: 2px solid var(--accent);
background: var(--bg-primary);
z-index: 1;
}
.tl-dot.done {
background: var(--green);
border-color: var(--green);
}
.tl-dot.active {
background: var(--accent);
border-color: var(--accent);
box-shadow: 0 0 8px var(--accent);
animation: pulse 2s infinite;
}
.tl-dot.pending {
background: var(--bg-primary);
border-color: var(--text-dim);
}
@keyframes pulse {
0%,
100% {
box-shadow: 0 0 4px var(--accent);
}
50% {
box-shadow: 0 0 12px var(--accent);
}
}
.tl-content {
background: var(--bg-card);
backdrop-filter: blur(12px);
border: 1px solid var(--border-glass);
border-radius: 10px;
padding: 0.9rem 1.1rem;
}
.tl-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.3rem;
}
.tl-id {
font-size: 0.7rem;
font-weight: 700;
color: var(--accent);
letter-spacing: 0.04em;
}
.tl-date {
font-size: 0.7rem;
color: var(--text-dim);
}
.tl-title {
font-size: 0.9rem;
font-weight: 600;
margin-bottom: 0.2rem;
}
.tl-desc {
font-size: 0.78rem;
color: var(--text-muted);
line-height: 1.4;
}
.tl-tags {
display: flex;
gap: 0.4rem;
margin-top: 0.5rem;
flex-wrap: wrap;
}
.tag {
font-size: 0.65rem;
padding: 2px 8px;
border-radius: 999px;
font-weight: 600;
letter-spacing: 0.03em;
}
.tag-phys {
background: rgba(249, 115, 22, 0.15);
color: var(--orange);
}
.tag-remote {
background: rgba(139, 92, 246, 0.15);
color: #a78bfa;
}
.tag-done {
background: rgba(34, 197, 94, 0.12);
color: var(--green);
}
/* Nodes grid */
.nodes-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.node-card {
background: var(--bg-card);
backdrop-filter: blur(12px);
border: 1px solid var(--border-glass);
border-radius: 10px;
padding: 1rem;
transition: transform 0.2s;
}
.node-card:hover {
transform: translateY(-2px);
}
.node-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.node-name {
font-weight: 700;
font-size: 0.9rem;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
}
.status-dot.online {
background: var(--green);
box-shadow: 0 0 6px var(--green);
}
.status-dot.testing {
background: var(--yellow);
box-shadow: 0 0 6px var(--yellow);
}
.status-dot.offline {
background: var(--text-dim);
}
.node-detail {
font-size: 0.75rem;
color: var(--text-muted);
line-height: 1.6;
}
.node-detail code {
background: rgba(255, 255, 255, 0.06);
padding: 1px 5px;
border-radius: 4px;
font-size: 0.72rem;
}
.footer {
text-align: center;
color: var(--text-dim);
font-size: 0.72rem;
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid var(--border-glass);
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="header-badge">Proyecto P2601</div>
<h1>Infraestructura <span>DASUTEN</span></h1>
<p class="subtitle">Migración del sistema de gestión a infraestructura independiente — UTN FRLR</p>
</div>
<!-- Métricas Operativas -->
<div class="metrics-bar">
<div class="metric">
<div class="value green">12</div>
<div class="label">Hitos Completados</div>
</div>
<div class="metric">
<div class="value yellow">6</div>
<div class="label">Hitos Pendientes</div>
</div>
<div class="metric">
<div class="value accent">14</div>
<div class="label">Bitácoras Generadas</div>
</div>
<div class="metric">
<div class="value" style="color:#a78bfa;">~53h</div>
<div class="label">Esfuerzo Total</div>
</div>
<div class="metric">
<div class="value" style="color:#f97316;">~8h</div>
<div class="label">Horas Físicas</div>
</div>
</div>
<!-- Progreso General -->
<div class="progress-section">
<div class="progress-header">
<h3>Progreso General del Proyecto</h3>
<span id="progressPct">79%</span>
</div>
<div class="progress-track">
<div class="progress-fill" style="width: 79%;"></div>
</div>
</div>
<!-- Nodos -->
<div class="section-title">🖧 Nodos del Ecosistema</div>
<div class="nodes-grid">
<div class="node-card">
<div class="node-header">
<span class="node-name">🖥️ srv-dasu</span>
<span class="status-dot online" title="Online"></span>
</div>
<div class="node-detail">
IP: <code>10.0.10.205</code><br>
Proxmox VE 9.1<br>
Hipervisor — 3 VMs
</div>
</div>
<div class="node-card">
<div class="node-header">
<span class="node-name">🏛️ dc-dasuten</span>
<span class="status-dot online" title="Online"></span>
</div>
<div class="node-detail">
IP: <code>10.0.100.10</code><br>
WinSrv 2022 Core<br>
AD DS + DNS
</div>
</div>
<div class="node-card">
<div class="node-header">
<span class="node-name">🗄️ sql-dasuten</span>
<span class="status-dot online" title="Online"></span>
</div>
<div class="node-detail">
IP: <code>10.0.100.11</code><br>
SQL Server 2019<br>
DB: <code>sysdasuten</code><br>
SSH: Puerto 7022 ✅
</div>
</div>
<div class="node-card">
<div class="node-header">
<span class="node-name">💻 pcv-dasu0</span>
<span class="status-dot online" title="Online"></span>
</div>
<div class="node-detail">
IP: <code>10.0.100.12</code><br>
Windows 10 LTSC<br>
VM de Pruebas<br>
Deploy: ✅ Login OK<br>
SSH: Puerto 7022 ✅
</div>
</div>
<div class="node-card">
<div class="node-header">
<span class="node-name">🖱️ pc-dasu0</span>
<span class="status-dot offline" title="Pendiente"></span>
</div>
<div class="node-detail">
Oficina DASUTeN<br>
PC física cliente<br>
Tailscale: <code>100.65.62.44</code>
</div>
</div>
</div>
<!-- Timeline de Hitos -->
<div class="section-title">📅 Timeline de Hitos</div>
<div class="timeline">
<div class="tl-item">
<div class="tl-dot done"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">A00-A01</span><span class="tl-date">23/02</span></div>
<div class="tl-title">Acondicionamiento Hardware + Proxmox VE</div>
<div class="tl-desc">Instalación física del servidor <code>srv-dasu</code> y despliegue de Proxmox
VE 9.1.</div>
<div class="tl-tags"><span class="tag tag-phys">Físico: 2:30h</span><span class="tag tag-done">
Completado</span></div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot done"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">A05</span><span class="tl-date">23-24/02</span></div>
<div class="tl-title">VM Windows Server Core → DC</div>
<div class="tl-desc">Creación de VM, instalación de OS, habilitación SVM en BIOS, promoción a
Controlador de Dominio (<code>dc-dasuten</code>).</div>
<div class="tl-tags"><span class="tag tag-phys">Físico: 0:30h</span><span
class="tag tag-remote">Remoto: ~14h</span><span class="tag tag-done">✅ Completado</span>
</div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot done"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">A06</span><span class="tl-date">23-24/02</span></div>
<div class="tl-title">Dashboard P2601</div>
<div class="tl-desc">Despliegue del panel web con telemetría operativa, glassmorphism y métricas de
justificación.</div>
<div class="tl-tags"><span class="tag tag-remote">Remoto: ~2h</span><span class="tag tag-done">
Completado</span></div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot done"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">DB01</span><span class="tl-date">25/02</span></div>
<div class="tl-title">SQL Server 2019 en sql-dasuten</div>
<div class="tl-desc">Creación de VM, instalación de OS, integración al dominio, instalación de SQL
Server 2019 con collation <code>Modern_Spanish_CI_AS</code>.</div>
<div class="tl-tags"><span class="tag tag-remote">Remoto: ~8h</span><span class="tag tag-done">
Completado</span></div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot done"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">SSH</span><span class="tl-date">26/02</span></div>
<div class="tl-title">OpenSSH Server en VMs Windows</div>
<div class="tl-desc">Despliegue de SSH nativo en <code>dc-dasuten</code> y <code>sql-dasuten</code>.
Puerto 7022, firewall configurado.</div>
<div class="tl-tags"><span class="tag tag-remote">Remoto: ~3h</span><span class="tag tag-done">
Completado</span></div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot done"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">BKP</span><span class="tl-date">26/02</span></div>
<div class="tl-title">Resguardo VZDump</div>
<div class="tl-desc">Backup offline (stop mode) de las VMs DC y SQL vía VZDump.</div>
<div class="tl-tags"><span class="tag tag-remote">Remoto: ~1h</span><span class="tag tag-done">
Completado</span></div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot done"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">TEST</span><span class="tl-date">27/02</span></div>
<div class="tl-title">VM de Pruebas pcv-dasu0</div>
<div class="tl-desc">Despliegue de Win 10 LTSC, SSH, toolkit W-Zombi. Creación de usuario
<code>admindasu</code> (Domain Admin), unión al dominio.
</div>
<div class="tl-tags"><span class="tag tag-remote">Remoto: ~6h</span><span class="tag tag-done">
Completado</span></div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot done"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">P02.DB</span><span class="tl-date">04/03</span></div>
<div class="tl-title">Restauración Base de Datos</div>
<div class="tl-desc">Backup <code>bksysdasuten_Full.bak</code> (~9GB) restaurado en
<code>S:\Data\</code>. Login sysadmin creado vía w-zombi. 189 MB/s.
</div>
<div class="tl-tags"><span class="tag tag-remote">Remoto: ~1h</span><span class="tag tag-done">
Completado</span></div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot done"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">P02.APP</span><span class="tl-date">05/03</span></div>
<div class="tl-title">Despliegue del Sistema DASUTEN</div>
<div class="tl-desc">Transferencia SCP, extracción, configuración de <code>Kermet.ini</code>
<code>SERVER=sql-dasuten</code>,
instalación de componentes Kermet y fuentes TrueType. Login exitoso con usuario
<code>aalmiron</code>.
</div>
<div class="tl-tags"><span class="tag tag-phys">Físico: 1h</span><span
class="tag tag-remote">Remoto: ~2h</span><span class="tag tag-done">
Completado</span></div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot done"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">NET01</span><span class="tl-date">05/03</span></div>
<div class="tl-title">Tailscale VPN en srv-dasu</div>
<div class="tl-desc">Instalación de Tailscale v1.94.2 en el hipervisor. Configurado como subnet
router para <code>10.0.100.0/24</code>.
IP Tailscale: <code>100.116.210.36</code>. Acceso remoto exterior garantizado.</div>
<div class="tl-tags"><span class="tag tag-remote">Remoto: ~0.5h</span><span class="tag tag-done">
Completado</span></div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot done"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">TEST03</span><span class="tl-date">04-05/03</span></div>
<div class="tl-title">Conectividad SQL desde Cliente</div>
<div class="tl-desc">Apertura de firewall TCP 1433 en <code>sql-dasuten</code>.
<code>Test-NetConnection</code> confirmó
<code>TcpTestSucceeded: True</code>. Conexión ODBC validada vía login al sistema.
</div>
<div class="tl-tags"><span class="tag tag-remote">Remoto: ~1h</span><span class="tag tag-done">
Completado</span></div>
</div>
</div>
<!-- Pendientes (Fase 6) -->
<div class="tl-item">
<div class="tl-dot done"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">UAT</span><span class="tl-date">05/03</span></div>
<div class="tl-title">Verificación Presencial por Usuario</div>
<div class="tl-desc">Validación funcional del sistema por Andrea Almirón in-situ. Se confirmó la
operatividad completa de los módulos y generación de reportes correctos.</div>
<div class="tl-tags"><span class="tag tag-phys">Físico: 1h</span><span class="tag tag-done">
Completado</span></div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot active"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">FÍSICO</span><span class="tl-date">Pendiente</span></div>
<div class="tl-title">Instalación Física de srv-dasu</div>
<div class="tl-desc">Traslado e instalación definitiva del servidor físico Proxmox en los gabinetes
/ racks de la oficina DASUTeN.</div>
<div class="tl-tags"><span class="tag tag-phys">Requiere trabajo físico</span></div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot pending"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">ADDOM</span><span class="tl-date">Pendiente</span></div>
<div class="tl-title">Unión de Cliente pc-dasu0 al Dominio</div>
<div class="tl-desc">Unir la PC física del cliente local al directorio activo (dc-dasuten) y
configurar DNS primarios apuntando al controlador 10.0.100.10.</div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot pending"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">GPO</span><span class="tl-date">Pendiente</span></div>
<div class="tl-title">Políticas de Seguridad Local</div>
<div class="tl-desc">Configuración de GPOs de dominio para restringir acceso de usuarios regulares,
perfiles locales, mapeo de unidades y carpetas del sistema DASUTEN.</div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot pending"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">S02</span><span class="tl-date">Pendiente</span></div>
<div class="tl-title">Hardenización SSH</div>
<div class="tl-desc">Llaves SSH, passphrases, auditoría. Elevación de seguridad del acceso remoto.
</div>
</div>
</div>
<div class="tl-item">
<div class="tl-dot pending"></div>
<div class="tl-content">
<div class="tl-header"><span class="tl-id">CLI</span><span class="tl-date">Pendiente</span></div>
<div class="tl-title">Configuración pc-dasu0</div>
<div class="tl-desc">PC física de la oficina DASUTeN como cliente del dominio
<code>dasuten.utnlr</code> y del sistema.
</div>
</div>
</div>
</div>
<div class="footer">
P2601 — Infraestructura DASUTEN · UTN FRLR · Responsable Técnico: Lic. Ricardo Monla<br>
Última actualización: <span id="ts"></span>
</div>
</div>
<script>document.getElementById('ts').textContent = new Date().toLocaleString('es-AR');</script>
</body>
</html>
@@ -0,0 +1,150 @@
# P2601 - Ingeniería Inversa: Sistema DASUTEN
> Documento técnico generado el 04/03/2026 a partir del análisis del directorio `dtic-APPs/temp/runSysDasuten/`.
## Resumen Ejecutivo
El **Sistema DASUTEN** es una aplicación de escritorio Windows desarrollada en **Visual FoxPro 9** sobre el framework propietario **"Kermet"**. Se conecta a **SQL Server** vía ODBC usando autenticación Windows. El ejecutable principal es `DasutenSQL.exe` y la configuración de conexión reside en `Kermet.ini`.
---
## Tecnología
| Componente | Detalle |
| :--- | :--- |
| **Lenguaje** | Visual FoxPro 9 (compilado a PE32 GUI Win32) |
| **Framework** | Kermet (propietario, `Kernel.dll` + `libgen.prg` + `libclas.prg`) |
| **Base de Datos** | SQL Server (instancia por defecto `MSSQLSERVER`) |
| **Conexión** | ODBC Driver `SQL Server` — Autenticación Windows Integrada |
| **Base** | `sysdasuten` |
| **Sincronización** | FTP + archivos DBF/ZIP empaquetados con PKZIP25 |
## Cadena de Conexión (`Kermet.ini`)
```ini
TIPOBASEDEDATOS=SQLServer
[INFORMIX]
DRIVER=INFORMIX 2.80 32 BIT
DATABASE=Sysacad
UID=informix
SERVER=utn
HOST=localhost
SERVICE=turbo
PROTOCOL=olsoctcp
[SQLSERVER]
DRIVER=SQL Server
UID=
ADDRESS=0
LOCKEO=0
SERVER=srvFENIX
DATABASE=sysdasuten
PWD=(cifrado binario)
```
### Campos Clave
| Campo | Valor Original | Valor Migración |
| :--- | :--- | :--- |
| `SERVER` | `srvFENIX` | `sql-dasuten`**Cambiar** |
| `DATABASE` | `sysdasuten` | `sysdasuten` (sin cambios) |
| `DRIVER` | `SQL Server` | `SQL Server` (sin cambios) |
| `UID` | *(vacío = Windows Auth)* | *(vacío = Windows Auth)* |
> [!IMPORTANT]
> El campo `SERVER` debe apuntar al hostname o IP del nuevo servidor SQL. Para la migración: `sql-dasuten` o `10.0.100.11`.
## Estructura del Directorio de Distribución
```
runSysDasuten/
├── Kermet.ini ← Configuración de conexión SQL (MODIFICAR)
├── registro.RKI ← Config sincronización (Instancia=30)
├── SysDasuten_Sincro.lnk ← Acceso directo de sincronización
├── khr / khw ← Archivos de control (vacíos)
├── Fonts/
│ └── Fonts/
│ ├── code39.ttf ← Fuente código de barras Code39
│ ├── I2OF5.TTF ← Fuente código de barras Interleaved 2 of 5
│ ├── I2OF5NT.TTF ← Variante I2OF5
│ └── PF_I2OF5_W.ttf ← Variante I2OF5
├── Sistema/
│ ├── DasutenSQL.exe ← Ejecutable principal (PE32 GUI)
│ ├── Kernel.dll ← DLL principal del framework Kermet
│ ├── eztw32.dll ← DLL de escaneo TWAIN
│ ├── gdiplus.dll ← GDI+ para gráficos
│ ├── Kermet.ini ← Copia de config (puede ser la efectiva)
│ ├── registro.RKI ← Config de sincronización
│ ├── PKZIP25.EXE ← Compresor para sincronización
│ ├── SysDasuten_Sincro.bat ← Bat de sincro FTP
│ ├── InstaladorComponentesKermet.exe ← Instalador dependencias
│ ├── VFP9Rerr.log ← Log de errores VFP
│ ├── logdasuten2.txt ← Log de sincronización FTP
│ ├── Manuales de Uso/ ← Documentación del sistema
│ │ └── Instructivo-Instalacion del sistema.doc
│ ├── dasutensql.1292.exe ← Versión más reciente del ejecutable
│ ├── dasutensql.1270.exe
│ ├── dasutensql.1264.exe
│ ├── dasutensql.1247.exe
│ └── _gsdata_/ ← Metadata de sincronización GoodSync
├── Actus/ ← Actualizaciones históricas
│ └── Hist/ ← Paquetes .zip y .rar de versiones previas
└── error sincro/ ← Capturas de errores de sincronización
```
## Versiones del Ejecutable
| Archivo | Observaciones |
| :--- | :--- |
| `DasutenSQL.exe` | Versión base (sin strings de conexión SQL visibles) |
| `dasutensql.1247.exe` | Contiene referencias a `sysdasuten` |
| `dasutensql.1264.exe` | Contiene referencias a `sysdasuten` |
| `dasutensql.1270.exe` | Contiene referencias a `sysdasuten` |
| `dasutensql.1292.exe` | **Más reciente**. Apunta a `SERVER=172.16.9.204\SQL2017`, `DATABASE=SYSDASUTEN`. Contiene referencia a `SYSDASUTEN_PRODUCCION`. |
> [!WARNING]
> La versión 1292 conecta a una instancia nombrada `SQL2017` en la IP `172.16.9.204`. Si la nueva infra usa instancia por defecto (`MSSQLSERVER`), el `.exe` base o una versión anterior puede ser más compatible. **Verificar cuál es la versión activa en producción consultando `srvv-fenix`**.
## Modo de Ejecución Histórico
Según los logs de errores y el bat de sincronización:
- **Share UNC original**: `\\Quetzal\Sysdasuten\sistema\``\\srvFENIX\Sysdasuten\Sistema\`
- **Ruta local alternativa**: `D:\SysDasuten\Sistema\dasutensql.exe`
- **Sincronización**: `dasutensql.exe /RUN=SINCRONIZAR:FTP /usuario=sincro /password=sincroftp`
- **Kermet.ini efectivo**: Se lee desde `\\servidor\Sysdasuten\kermet.ini` (raíz del share)
## Dependencias para Despliegue
### En el cliente Windows (pcv-dasu0)
1. **Fuentes**: Instalar `code39.ttf`, `I2OF5.TTF`, `I2OF5NT.TTF`, `PF_I2OF5_W.ttf`
2. **ODBC Driver**: `SQL Server` (incluido en Windows)
3. **VFP Runtime**: Ya embebido en el `.exe` (no requiere instalación separada)
4. **Componentes Kermet**: Ejecutar `InstaladorComponentesKermet.exe` si es necesario
5. **Conectividad SQL**: El cliente debe resolver `sql-dasuten` por DNS o hosts
### En el servidor SQL (sql-dasuten)
1. **Base de datos**: `sysdasuten` ✅ (ya restaurada en `S:\Data\`)
2. **Puerto SQL**: TCP 1433 (instancia por defecto) — verificar firewall
3. **Autenticación**: Windows Integrada (el usuario del dominio `DASUTEN\admindasu` ya es sysadmin)
4. **SQL Browser**: UDP 1434 — habilitar si se usan instancias nombradas
## Plan de Despliegue en pcv-dasu0
```
1. Transferir directorio runSysDasuten/ a pcv-dasu0 vía SCP
2. Modificar Kermet.ini → SERVER=sql-dasuten (o 10.0.100.11)
3. Instalar fuentes (Fonts/)
4. Ejecutar InstaladorComponentesKermet.exe
5. Verificar firewall SQL 1433 en sql-dasuten
6. Probar conexión: sqlcmd -S sql-dasuten -E -Q "SELECT DB_NAME()"
7. Lanzar DasutenSQL.exe y verificar login
```
## Referencias
- **Proyecto**: [P2601_dasuten.md](P2601_dasuten.md)
- **Nodo SQL**: [sql-dasuten.md](../nodos/sql-dasuten.md)
- **Directorio fuente**: `dtic-APPs/temp/runSysDasuten/`
- **Bitácora**: [04/03/2026](../bitacoras/2026-03-04.md)
@@ -0,0 +1,64 @@
# Investigación: Uso de Agentes de IA Externos en el IDE ZED (Modo Gratuito)
## 1. Introducción
La integración de asistentes de Inteligencia Artificial (IA) en los entornos de desarrollo (IDE) ha transformado la forma de programar. El IDE ZED, conocido por su velocidad extrema (escrito en Rust), destaca por su enfoque flexible hacia la IA: en lugar de atarte a un solo proveedor, te permite conectar múltiples agentes y elegir el mejor para cada tarea mediante su panel de asistente y la generación en línea (inline).
En esta investigación analizaremos cómo sacar el máximo provecho de **Gemini**, **Copilot**, **DeepSeek** y **Claude** dentro de ZED, enfocándonos estrictamente en **planes gratuitos (Free Tiers)**.
---
## 2. Agentes de IA en ZED: Configuración y Uso Gratuito
### A. Google Gemini
Google ofrece una capa gratuita muy generosa a través de **Google AI Studio**.
* **Modo FREE/Gratuito**: Puedes generar una API Key en AI Studio sin costo. Esto te da acceso a modelos avanzados como **Gemini 1.5 Flash** y **Gemini 1.5 Pro**. Los límites gratuitos suelen ser muy amplios para un desarrollador individual (ej. 15 peticiones por minuto, 1 millón de tokens por minuto).
* **Integración en ZED**: ZED tiene soporte nativo. Solo necesitas abrir el panel del Asistente, seleccionar el proveedor `google` y configurar tu API Key obtenida en AI Studio.
* **Uso Ideal**: Análisis de grandes bloques de código o proyectos completos (gracias a su inmensa ventana de contexto de 1M-2M tokens) y tareas generales de programación.
### B. Anthropic Claude
Claude (especialmente el modelo Claude 3.5 Sonnet) es actualmente considerado por muchos como el mejor modelo para programación.
* **Modo FREE/Gratuito**: Anthropic no ofrece una API gratuita permanente directamente. **Sin embargo**, ZED incluye un servicio integrado llamado **ZED AI** que permite usar modelos como Claude 3.5 Sonnet de manera gratuita (con un sistema de limits de uso justo).
* **Integración en ZED**: Solo debes iniciar sesión con tu cuenta gratuita de ZED en el IDE y seleccionar "Zed AI" como proveedor en el panel del asistente.
* **Uso Ideal**: Tareas complejas de lógica, refactorización profunda, depuración intrincada y creación de arquitecturas desde cero. Su precisión en sintaxis es superior.
### C. GitHub Copilot
Copilot es el pionero. Recientemente, Microsoft introdujo versiones más accesibles.
* **Modo FREE/Gratuito**: GitHub ha habilitado un plan **Copilot Free** que ofrece un número limitado de autocompletados al mes y 50 mensajes de chat. (Además, es 100% ilimitado y gratuito si eres estudiante verificado, profesor, o mantenedor clave de un proyecto Open Source).
* **Integración en ZED**: ZED tiene integración oficial. Puedes iniciar sesión y vincular tu cuenta usando el Command Palette (`cmd/ctrl + shift + p`) > `copilot: sign in`.
* **Uso Ideal**: Autocompletado de código silente o "fantasma" a medida que escribes en el editor. Es excepcional prediciendo el bloque que necesitas línea a línea.
### D. DeepSeek (DeepSeek Coder / V3 / R1)
DeepSeek ha revolucionado el mercado por ser extremadamente competente y radicalmente barato u Open Source.
* **Modo FREE/Gratuito**: La API oficial es de pago (aunque a fracciones de centavo). Para un uso 100% gratuito, lo ideal es correrlo **localmente**. Sus modelos destilados (ej. DeepSeek-R1-Distill) pueden correr en una PC estándar sin problemas.
* **Integración en ZED**: ZED soporta la herramienta **Ollama** nativamente. Una vez que instalas Ollama (gratis) en tu sistema y descargas un modelo de DeepSeek (ej. ejecutando `ollama run deepseek-coder`), puedes seleccionar el proveedor `ollama` en el asistente de ZED, apuntando a tu `localhost`.
* **Uso Ideal**: Programación general y consultas sin requerir conexión a internet, garantizando **100% de privacidad** de tu código.
---
## 3. Ventajas y Desventajas Generales
| Aspecto | Ventajas (Pros) | Desventajas (Contras) |
| :--- | :--- | :--- |
| **Productividad** | Acelera radicalmente la escritura de código "boilerplate" (repetitivo), unit tests y redacción de documentación. | **Alucinaciones**: El código generado a veces incluye librerías inexistentes o comete errores lógicos sutiles que parecen correctos. |
| **Aprendizaje y Soporte** | Excelente para explicar código "legacy" (heredado), entender bugs crípticos o lenguajes que no dominas. | **Dependencia y Atrofia**: Un uso ciego puede atrofiar la capacidad del desarrollador para resolver problemas por sí mismo o diseñar arquitecturas. |
| **Contexto Integrado** | ZED permite agregar archivos específicos al contexto del asistente (usando comandos como `/file` o `/tabs`) sin salir de la IDE. | **Riesgo de Privacidad**: Al enviar código a APIs públicas (Google, Anthropic o GitHub), expones propiedad intelectual. No debes enviar secretos corporativos o claves. (Excepto si usas DeepSeek local vía Ollama). |
| **Costo** | Las capas gratuitas mencionadas intercaladas (ZED AI + Gemini + Local) cubren más del 90% de un flujo de trabajo intenso. | Los límites gratuitos (rate limits) pueden cortarte el flujo si confías exclusivamente en el tier gratuito de un solo proveedor en un día intenso. |
---
## 4. Estrategia: "Cuánto y Cómo Usar" en Modo Free
Para armar el flujo de trabajo ideal en ZED gastando 0 USD, la clave es la **diversificación y la alternancia**:
1. **Autocompletado Rápido (Mientras escribes):**
* **Cómo:** Habilita GitHub Copilot Free o usa DeepSeek Coder corriendo localmente con Ollama.
* **Cuánto:** Actívalo de forma permanente, pero sé crítico. Si tras dos segundos de pausa la sugerencia "fantasma" no es lo que necesitas, ignórala y escribe tú.
2. **Tareas Complejas y Diseño (Asistente Chat):**
* **Cómo:** Abre el Asistente lateral de ZED (`Cmd/Ctrl + b`). Usa **Zed AI (Claude 3.5 Sonnet)** como proveedor primario. Usa los comandos `/file`, `/tabs`, o `/selection` para incluir el código EXACTO que necesitas que analice.
* **Cuánto:** Úsalo cuando te "atasques" por más de 5 a 10 minutos, o para diseñar el esqueleto limpio de una función, una migración SQL, o expresiones regulares (Regex). Si Zed AI te advierte que has alcanzado el límite gratuito diario, cambia inmediatamente al proveedor de **Google (Gemini)**.
3. **Edición Rápida (Inline Generation):**
* **Cómo:** Selecciona un bloque de código que escribiste y presiona el atajo de generación en línea (`Cmd/Ctrl + enter`). Escribe un prompt rápido como: `"agrega manejo de errores (try/catch)"` o `"traduce estos comentarios al español"`.
* **Cuánto:** Úsalo libremente para refactorizaciones menores y mejoras de seguridad.
## Conclusión
La filosofía "trae tu propio modelo" de ZED es su mayor fortaleza. Combinando inteligentemente **Zed AI (Claude 3.5)** para lógica pesada diaria, **Gemini (AI Studio)** de respaldo por su gran contexto, y **Ollama (DeepSeek)** para tareas privadas/locales, obtienes un entorno de desarrollo de última generación totalmente gratuito. El límite, con este setup, será tu propia revisión del código.
@@ -0,0 +1,80 @@
# Plan: Integración Dashboard P2601 en dtic-BITACORAs
> Fecha: 04/03/2026 19:59hs
> Proyecto: P2601 - DASUTEN
> Estado: **Aprobado — En ejecución**
## Objetivo
Integrar el panel de seguimiento del proyecto P2601 dentro del sistema dtic-BITACORAs:
- Accesible en `ns8.frlr.utn.edu.ar/bitacoras/p2601`
- Datos almacenados en PostgreSQL (mismo stack que dtic-BITACORAs)
- Discriminación de horas presenciales vs remotas por hito
- Identificación de fases del proyecto
## Esquema de Datos
### Nuevas tablas (esquema `bitacoras`)
```sql
CREATE TABLE bitacoras.proyectos (
id SERIAL PRIMARY KEY,
codigo VARCHAR(10) NOT NULL UNIQUE, -- 'P2601'
nombre VARCHAR(200) NOT NULL,
estado VARCHAR(10) DEFAULT '',
descripcion TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE bitacoras.fases (
id SERIAL PRIMARY KEY,
proyecto_id INTEGER REFERENCES bitacoras.proyectos(id),
numero INTEGER NOT NULL,
nombre VARCHAR(200) NOT NULL,
estado VARCHAR(10) DEFAULT '',
descripcion TEXT,
fecha_inicio DATE,
fecha_fin DATE,
UNIQUE(proyecto_id, numero)
);
CREATE TABLE bitacoras.hitos (
id SERIAL PRIMARY KEY,
fase_id INTEGER REFERENCES bitacoras.fases(id),
id_hito VARCHAR(20) NOT NULL, -- 'A00', 'SSH', 'P02.DB'
titulo VARCHAR(200) NOT NULL,
descripcion TEXT,
estado VARCHAR(10) DEFAULT '',
fecha DATE,
horas_presencial NUMERIC(5,2) DEFAULT 0,
horas_remoto NUMERIC(5,2) DEFAULT 0,
nodo_id INTEGER REFERENCES bitacoras.nodos(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
## Fases del proyecto P2601
| # | Fase | Descripción | Nodos | Estado |
|:--|:--|:--|:--|:--|
| 1 | Infraestructura Física | Preparación hardware srv-dasu + Proxmox VE 9.1 | srv-dasu | ✅ |
| 2 | Dominio y Directorio Activo | VM dc-dasuten, AD DS, DNS, dominio `dasuten.utnlr` | dc-dasuten | ✅ |
| 3 | Motor de Base de Datos | VM sql-dasuten, SQL Server 2019, backup y restore sysdasuten | sql-dasuten | ✅ |
| 4 | Acceso Remoto y Seguridad | OpenSSH 7022, w-zombi, firewall en todas las VMs | dc/sql/pcv-dasuten | ✅ |
| 5 | Sistema DASUTEN | Ingeniería inversa, deploy DasutenSQL.exe, Kermet.ini | pcv-dasu0, sql-dasuten | ⏳ |
| 6 | Cliente Final | Config pc-dasu0 en dominio, sistema productivo | pc-dasu0 | 📋 |
## Archivos a modificar/crear
| Acción | Archivo | Descripción |
|:--|:--|:--|
| MODIFY | `docker/init.sql` | Tablas proyectos, fases, hitos + datos semilla P2601 |
| NEW | `backend/src/routes/proyectos.js` | API CRUD /api/proyectos con métricas |
| MODIFY | `backend/src/server.js` | Registrar ruta proyectos |
| NEW | `frontend/src/pages/ProyectoDashboard.tsx` | Página React con timeline, fases y métricas |
| MODIFY | `frontend/src/App.tsx` | Ruta /bitacoras/p2601 |
## Verificación
- `curl` al endpoint `/api/proyectos/P2601`
- Navegador a `https://ns8.frlr.utn.edu.ar/bitacoras/p2601`
+21
View File
@@ -0,0 +1,21 @@
# Plan de Pruebas: Sistema Dasuten (P02)
**Fecha/Hora:** 260305-0930
**Objetivo:** Confirmar el correcto funcionamiento y conectividad del entorno Dasuten.
## Fases de la Prueba
1. **Uso de Herramienta de Credenciales Secretas:**
- Modificar la herramienta `tools/ns8-candados` para que incluya instrucciones y explicación de uso en la cabecera.
- Ejecutar la herramienta `tools/ns8-candados` de manera que capture las credenciales requeridas en el entorno (o en una variable segura) sin exponer la salida (stdout) al flujo visible del chat, preservando la confidencialidad.
2. **Verificación de Acceso Base:**
- Confirmar acceso SSH al servidor principal `pcv-dasu0` utilizando las credenciales obtenidas.
- Validar estado general del sistema (uptime, uso de recursos básicos).
3. **Pruebas Funcionales Adicionales (A definir iterativamente):**
- *Se irán agregando puntos de prueba según el progreso y los resultados del acceso base.*
## Protocolo de Registro (Bitácora First)
- Todo paso debe ser previamente registrado en la bitácora del día correspondiente como iniciado.
- Tras concluir cada paso o iteración, actualizar el registro en la bitácora con los resultados (éxito/fallo y observaciones).
@@ -0,0 +1,22 @@
# Plan de Implementación: Contexto en el ADN (Caso DASUTEN)
**Fecha**: 2026-03-05
**Objetivo**: Dotar a la IA y al proyecto de un protocolo estandarizado de "Auto-contextualización" sobre topología de redes, evitando operaciones ciegas (ej. Timeouts SSH en subredes NAT).
## Propuesta de Cambios
### Hebra: Directivas de la Inteligencia Artificial (`adn/05_ia.md`)
Agregaremos una nueva directiva `13. Auto-Contextualización`.
- **Regla:** Antes de iniciar pruebas de red o conexiones a nodos, la IA DEBE consultar el archivo del nodo (en la carpeta `nodos/`) y el proyecto asociado para comprender su ubicación en la topología (ej. si está detrás de un NAT, si requiere salto SSH, qué IP tiene).
- **Ejecución:** Evitar asunciones de visibilidad directa; mapear el contexto siempre.
### Hebra: Proyectos (`adn/07_proyectos.md`)
Añadir la sección obligatoria de "Topología / Contexto de Red" a los manifiestos de proyecto.
- **Topología Lógica / Contexto de Red:** Exige describir la ubicación de los nodos (ej: Red aislada, host físico, dependencias de NAT) para documentar y estandarizar el **CÓMO** llegar a ellos de forma técnica.
### Manifiesto: P2601 Dasuten (`proyectos/P2601_dasuten.md`)
Actualizar el manifiesto actual del proyecto Dasuten para reflejar esta nueva arquitectura de topología explícita.
- Añadir una sección `## 🌐 Topología y Contexto de Red` explicando que:
1. Todo el ecosistema Dasuten corre bajo `srv-dasu` (Standalone - 10.0.10.205).
2. Las VMs periféricas (pcv-dasu0, dc-dasuten, sql-dasuten) están en una subred aislada `10.0.100.0/24`.
3. **Regla de Acceso:** Para la intervención, es necesario un ruteo o ProxyJump a través de `10.0.10.205`.
@@ -0,0 +1,29 @@
# Plan de Acción: Despliegue y Prueba de DASUTEN en pcv-dasu0
**Fecha/Hora:** 260305-1017
**Artefacto:** `tools/w-zombi/payloads/runSysDasuten.zip`
**Objetivo:** Desplegar los binarios del sistema DASUTEN en la VM de pruebas (cliente Windows 10 LTSC) y verificar su conexión contra el motor SQL recién instalado en la fase DB01.
## Fases del Despliegue
### 1. Preparación y Transferencia
1. Obtener credenciales de `admindasu` desde `ns8-candados`.
2. Establecer túnel o salto SSH válido hacia la red `10.0.100.x` a través de `srv-dasu` (10.0.10.205).
3. Transferir el archivo `tools/w-zombi/payloads/runSysDasuten.zip` vía SCP al disco virtual de `pcv-dasu0` apuntando al **puerto SSH 7022** (ej. en `C:\Dasuten\`).
### 2. Descompresión y Configuración
1. Descomprimir el archivo ZIP en el cliente usando PowerShell remoting / SSH.
2. Localizar y editar el archivo de configuración `Kermet.ini`:
- Modificar la cadena de conexión de ODBC para `[SQLSERVER]`.
- Reemplazar `SERVER=srvFENIX` por el endpoint actual: `SERVER=sql-dasuten` (o la IP `10.0.100.11`).
### 3. Instalación de Dependencias
1. Instalar de forma automatizada las fuentes tipográficas necesarias de la carpeta `Fonts/` (códigos de barras Code39, I2OF5).
2. Ejecutar silenciosa/desatendidamente `InstaladorComponentesKermet.exe` para registrar librerías VFP/Kermet requeridas por el sistema en el cliente.
### 4. Pruebas de Verificación
1. **Red**: Realizar un `Test-NetConnection` o ping desde `pcv-dasu0` hacia el puerto TCP 1433 de `sql-dasuten` para garantizar que el firewall permite el tráfico SQL.
2. **Sistema GUI**: Invocar la apertura de `DasutenSQL.exe` (puede requerir verificación visual del usuario vía consola NoVNC de Proxmox o Anydesk/RDP). Se debe comprobar que se abre la pantalla de logon sin errores de conexión a la base de datos `sysdasuten`.
## Registro y Trazabilidad
- Todo evento de éxito/falla de red se registrará en la bitácora bajo la etiqueta `[P02]`.
@@ -0,0 +1,31 @@
# Fase 6 - DASUTEN: Despliegue Físico, Dominio y Seguridad Local
**Fecha:** 05/03/2026 - P2601 Proyecto Dasuten
## Objetivo
Habiendo superado la prueba de concepto (UAT) y verificado el funcionamiento del sistema en la red aislada `10.0.100.x` virtualizada bajo Proxmox, la siguiente fase consiste en **entregar la infraestructura a producción**.
Esto implica el traslado físico del equipamiento, la adhesión de las estaciones de trabajo de los usuarios al nuevo Active Directory (`dc-dasuten`), y la configuración de las políticas organizativas.
## Tareas a Realizar
### 1. Instalación Física (Hardware)
* **Servidor Proxmox (`srv-dasu`)**: Trasladar el host físico a la oficina de DASUTeN e instalarlo en su locación definitiva (gabinete/rack), asegurando energía ininterrumpida (UPS) y conectividad de red adecuada.
* **Estaciones de Trabajo (`pc-dasu0` y otras si las hay)**: Reconectar y asegurar la conectividad L2 (física/switch) hacia el puerto de red que ofrece acceso al servidor `srv-dasu`.
### 2. Integración de Red y Dominio
* **Configuración de Red Local**: Asegurar que la PC física tenga acceso a la red donde opera el controlador de dominio. Confirmar que su **DNS Primario** sea la IP de `dc-dasuten` (`10.0.100.10`).
* **Unión al Dominio**: Ingresar la PC `pc-dasu0` al dominio `dasuten.utnlr`.
* **Reasignación de Usuarios**: Mapear/Migrar los perfiles y datos del usuario local que usaba Andrea Almirón hacia su nueva cuenta de dominio.
### 3. Seguridad y GPOs (Group Policy Objects)
* **Restricción de Acceso**: Configurar GPOs para prohibir la instalación de software no autorizado por los usuarios estándar de DASUTeN.
* **Unidades de Red**: Despliegue automático de mapeos de red necesarios (si la aplicación requiere una letra de unidad específica o para almacenamiento compartido) mediante GPO.
* **Restricciones de Explorador**: Ocultar u restringir acceso a la unidad C:\ y recursos sensibles.
* **Políticas de Contraseñas**: Asegurar rotación o longitud mínima para los usuarios del dominio.
### 4. Revisión Final de Aplicaciones
* Distribuir el conjunto de accesos directos del sistema Dasuten (`DasutenSQL.exe`) al escritorio de todos los usuarios mediante GPO.
* Verificar que la conexión ODBC/Kermet funciona bajo el contexto de seguridad del usuario de dominio sin requerir elevación de privilegios.
## Siguientes Pasos (IA)
Una vez que el usuario informe que el equipo fue trasladado y encendido en su nueva locación, la IA asistirá vía Tailscale / AnyDesk (o conexiones remotas establecidas) con la configuración de las Políticas de Seguridad (GPOs) y las uniones de dominio.
@@ -98,5 +98,25 @@ module.exports = (pool) => {
res.send(md);
});
// PUT /api/bitacoras/:id — Actualizar resumen de bitácora
router.put('/:id', async (req, res) => {
const { id } = req.params;
const { resumen } = req.body;
const { rows } = await pool.query(
'UPDATE bitacoras.bitacoras SET resumen=$1 WHERE id=$2 RETURNING *',
[resumen, id]
);
if (rows.length === 0) return res.status(404).json({ error: 'Bitácora no encontrada' });
res.json(rows[0]);
});
// DELETE /api/bitacoras/:id — Eliminar bitácora y sus entradas
router.delete('/:id', async (req, res) => {
await pool.query('DELETE FROM bitacoras.entradas WHERE bitacora_id = $1', [req.params.id]);
const { rowCount } = await pool.query('DELETE FROM bitacoras.bitacoras WHERE id = $1', [req.params.id]);
if (rowCount === 0) return res.status(404).json({ error: 'Bitácora no encontrada' });
res.json({ message: 'Bitácora eliminada' });
});
return router;
};
@@ -17,5 +17,33 @@ module.exports = (pool) => {
res.status(201).json(rows[0]);
});
// PUT /api/nodos/:id — Actualizar nodo
router.put('/:id', async (req, res) => {
const { id } = req.params;
const { nombre, ip, tipo, descripcion, activo } = req.body;
const { rows } = await pool.query(
'UPDATE bitacoras.nodos SET nombre=$1, ip=$2, tipo=$3, descripcion=$4, activo=$5 WHERE id=$6 RETURNING *',
[nombre, ip, tipo, descripcion, activo, id]
);
if (rows.length === 0) return res.status(404).json({ error: 'Nodo no encontrado' });
res.json(rows[0]);
});
// DELETE /api/nodos/:id — Eliminar nodo (lógico o físico si no tiene entradas asociadas)
router.delete('/:id', async (req, res) => {
try {
const { rowCount } = await pool.query('DELETE FROM bitacoras.nodos WHERE id = $1', [req.params.id]);
if (rowCount === 0) return res.status(404).json({ error: 'Nodo no encontrado' });
res.json({ message: 'Nodo eliminado' });
} catch (err) {
// Si hay foreign key violation, podemos marcarlo como inactivo
if (err.code === '23503') {
await pool.query('UPDATE bitacoras.nodos SET activo=false WHERE id = $1', [req.params.id]);
return res.json({ message: 'Nodo marcado como inactivo por tener entradas asociadas' });
}
res.status(500).json({ error: err.message });
}
});
return router;
};
@@ -0,0 +1,118 @@
module.exports = (pool) => {
const router = require('express').Router();
// GET /api/proyectos — Listar proyectos
router.get('/', async (req, res) => {
const { rows } = await pool.query('SELECT * FROM bitacoras.proyectos ORDER BY codigo');
res.json(rows);
});
// GET /api/proyectos/:codigo — Detalle con fases, hitos y métricas
router.get('/:codigo', async (req, res) => {
const { codigo } = req.params;
const pResult = await pool.query('SELECT * FROM bitacoras.proyectos WHERE codigo = $1', [codigo]);
if (pResult.rows.length === 0) return res.status(404).json({ error: 'Proyecto no encontrado' });
const proyecto = pResult.rows[0];
// Fases con hitos y nodo
const { rows: fases } = await pool.query(`
SELECT f.*, json_agg(
json_build_object(
'id', h.id,
'id_hito', h.id_hito,
'titulo', h.titulo,
'descripcion', h.descripcion,
'estado', h.estado,
'fecha', h.fecha,
'horas_presencial', h.horas_presencial,
'horas_remoto', h.horas_remoto,
'nodo_nombre', n.nombre,
'nodo_ip', n.ip
) ORDER BY h.fecha NULLS LAST, h.id
) FILTER (WHERE h.id IS NOT NULL) AS hitos
FROM bitacoras.fases f
LEFT JOIN bitacoras.hitos h ON h.fase_id = f.id
LEFT JOIN bitacoras.nodos n ON h.nodo_id = n.id
WHERE f.proyecto_id = $1
GROUP BY f.id
ORDER BY f.numero
`, [proyecto.id]);
// Métricas globales
const { rows: [metricas] } = await pool.query(`
SELECT
COUNT(*) FILTER (WHERE h.estado = '✅') AS hitos_completados,
COUNT(*) FILTER (WHERE h.estado != '✅') AS hitos_pendientes,
COUNT(*) AS hitos_total,
COALESCE(SUM(h.horas_presencial), 0) AS total_presencial,
COALESCE(SUM(h.horas_remoto), 0) AS total_remoto,
COALESCE(SUM(h.horas_presencial + h.horas_remoto), 0) AS total_horas
FROM bitacoras.hitos h
JOIN bitacoras.fases f ON h.fase_id = f.id
WHERE f.proyecto_id = $1
`, [proyecto.id]);
// Nodos únicos involucrados
const { rows: nodos } = await pool.query(`
SELECT DISTINCT n.id, n.nombre, n.ip, n.tipo, n.descripcion
FROM bitacoras.hitos h
JOIN bitacoras.fases f ON h.fase_id = f.id
JOIN bitacoras.nodos n ON h.nodo_id = n.id
WHERE f.proyecto_id = $1
ORDER BY n.nombre
`, [proyecto.id]);
res.json({ ...proyecto, fases, metricas, nodos });
});
// POST /api/proyectos/:codigo/hitos — Crear hito
router.post('/:codigo/hitos', async (req, res) => {
const { codigo } = req.params;
const { fase_numero, id_hito, titulo, descripcion, estado, fecha, horas_presencial, horas_remoto, nodo_nombre } = req.body;
const pResult = await pool.query('SELECT id FROM bitacoras.proyectos WHERE codigo = $1', [codigo]);
if (pResult.rows.length === 0) return res.status(404).json({ error: 'Proyecto no encontrado' });
const fResult = await pool.query(
'SELECT id FROM bitacoras.fases WHERE proyecto_id = $1 AND numero = $2',
[pResult.rows[0].id, fase_numero]
);
if (fResult.rows.length === 0) return res.status(404).json({ error: 'Fase no encontrada' });
let nodo_id = null;
if (nodo_nombre) {
const nResult = await pool.query('SELECT id FROM bitacoras.nodos WHERE nombre = $1', [nodo_nombre]);
if (nResult.rows.length > 0) nodo_id = nResult.rows[0].id;
}
const { rows } = await pool.query(
`INSERT INTO bitacoras.hitos (fase_id, id_hito, titulo, descripcion, estado, fecha, horas_presencial, horas_remoto, nodo_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *`,
[fResult.rows[0].id, id_hito, titulo, descripcion || '', estado || '⏳', fecha || null,
horas_presencial || 0, horas_remoto || 0, nodo_id]
);
res.status(201).json(rows[0]);
});
// PUT /api/proyectos/hitos/:id — Actualizar hito
router.put('/hitos/:id', async (req, res) => {
const { id } = req.params;
const { titulo, descripcion, estado, fecha, horas_presencial, horas_remoto } = req.body;
const { rows } = await pool.query(
`UPDATE bitacoras.hitos SET
titulo = COALESCE($1, titulo),
descripcion = COALESCE($2, descripcion),
estado = COALESCE($3, estado),
fecha = COALESCE($4, fecha),
horas_presencial = COALESCE($5, horas_presencial),
horas_remoto = COALESCE($6, horas_remoto)
WHERE id = $7 RETURNING *`,
[titulo, descripcion, estado, fecha, horas_presencial, horas_remoto, id]
);
if (rows.length === 0) return res.status(404).json({ error: 'Hito no encontrado' });
res.json(rows[0]);
});
return router;
};
+1
View File
@@ -28,6 +28,7 @@ app.get('/health', async (req, res) => {
app.use('/api/nodos', require('./routes/nodos')(pool));
app.use('/api/bitacoras', require('./routes/bitacoras')(pool));
app.use('/api/entradas', require('./routes/entradas')(pool));
app.use('/api/proyectos', require('./routes/proyectos')(pool));
// Error handler
app.use((err, req, res, next) => {
+1 -1
View File
@@ -37,7 +37,7 @@ services:
ports:
- "5174:5173"
environment:
VITE_API_URL: http://localhost:3002
VITE_API_URL: /bitacoras/api
depends_on:
- api
volumes:
+414 -115
View File
@@ -1,6 +1,7 @@
import React, { useState, useEffect, useCallback } from 'react';
import ProyectoDashboard from './pages/ProyectoDashboard';
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3002';
const API_URL = import.meta.env.VITE_API_URL || '/bitacoras/api';
interface Entrada {
id: number;
@@ -31,15 +32,56 @@ interface Nodo {
id: number;
nombre: string;
ip: string;
tipo: string;
descripcion: string;
activo: boolean;
}
// ===== MODAL DE CONFIRMACIÓN =====
function ConfirmModal({ show, title, message, onConfirm, onCancel }: {
show: boolean; title: string; message: string;
onConfirm: () => void; onCancel: () => void;
}) {
if (!show) return null;
return (
<div className="modal-overlay" onClick={onCancel}>
<div className="modal-box" onClick={e => e.stopPropagation()}>
<div className="modal-header"><i className="fas fa-exclamation-triangle"></i> {title}</div>
<p className="modal-body">{message}</p>
<div className="modal-actions">
<button className="btn btn-sm" onClick={onCancel} style={{ background: 'var(--bg-card)', border: '1px solid var(--border)', color: 'var(--text)' }}>Cancelar</button>
<button className="btn btn-sm btn-danger" onClick={onConfirm}><i className="fas fa-trash-alt"></i> Eliminar</button>
</div>
</div>
</div>
);
}
// ===== TABS =====
type Tab = 'bitacora' | 'nodos' | 'proyecto';
function getInitialTab(): Tab {
const path = window.location.pathname;
if (path.includes('/p2601') || path.includes('/P2601')) return 'proyecto';
if (path.includes('/nodos')) return 'nodos';
return 'bitacora';
}
function App() {
const [tab, setTab] = useState<Tab>(getInitialTab());
const changeTab = (t: Tab) => {
setTab(t);
const base = '/bitacoras/';
const paths: Record<Tab, string> = { bitacora: base, nodos: base + 'nodos', proyecto: base + 'p2601' };
window.history.replaceState(null, '', paths[t]);
};
const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]);
const [bitacora, setBitacora] = useState<Bitacora | null>(null);
const [nodos, setNodos] = useState<Nodo[]>([]);
const [loading, setLoading] = useState(true);
// Form state
// Form: nueva entrada
const [newInicio, setNewInicio] = useState('');
const [newFin, setNewFin] = useState('');
const [newDesc, setNewDesc] = useState('');
@@ -48,10 +90,31 @@ function App() {
const [newNodoId, setNewNodoId] = useState('');
const [newEsIa, setNewEsIa] = useState(false);
// Edit: entrada en edición
const [editId, setEditId] = useState<number | null>(null);
const [editInicio, setEditInicio] = useState('');
const [editFin, setEditFin] = useState('');
const [editDesc, setEditDesc] = useState('');
const [editEstado, setEditEstado] = useState('');
const [editModo, setEditModo] = useState('');
const [editEsIa, setEditEsIa] = useState(false);
// Confirm delete
const [confirmDelete, setConfirmDelete] = useState<{ type: string; id: number; name: string } | null>(null);
// Nodo form
const [nodoFormVisible, setNodoFormVisible] = useState(false);
const [editNodoId, setEditNodoId] = useState<number | null>(null);
const [nodoNombre, setNodoNombre] = useState('');
const [nodoIp, setNodoIp] = useState('');
const [nodoTipo, setNodoTipo] = useState('servidor');
const [nodoDesc, setNodoDesc] = useState('');
// === FETCH ===
const fetchBitacora = useCallback(async () => {
setLoading(true);
try {
const res = await fetch(`${API_URL}/api/bitacoras/${fecha}/completa`);
const res = await fetch(`${API_URL}/bitacoras/${fecha}/completa`);
const data = await res.json();
setBitacora(data);
} catch (err) { console.error('Error:', err); }
@@ -60,7 +123,7 @@ function App() {
const fetchNodos = async () => {
try {
const res = await fetch(`${API_URL}/api/nodos`);
const res = await fetch(`${API_URL}/nodos`);
setNodos(await res.json());
} catch (err) { console.error('Error:', err); }
};
@@ -68,11 +131,12 @@ function App() {
useEffect(() => { fetchBitacora(); }, [fetchBitacora]);
useEffect(() => { fetchNodos(); }, []);
// === ENTRADAS CRUD ===
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!bitacora || !newDesc || !newInicio || !newNodoId) return;
try {
await fetch(`${API_URL}/api/entradas`, {
await fetch(`${API_URL}/entradas`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -86,134 +150,369 @@ function App() {
} catch (err) { console.error('Error:', err); }
};
const handleDelete = async (id: number) => {
await fetch(`${API_URL}/api/entradas/${id}`, { method: 'DELETE' });
const startEdit = (e: Entrada) => {
setEditId(e.id);
setEditInicio(e.inicio?.substring(0, 5) || '');
setEditFin(e.fin ? e.fin.substring(0, 5) : '');
setEditDesc(e.descripcion);
setEditEstado(e.estado);
setEditModo(e.modo);
setEditEsIa(e.es_ia);
};
const cancelEdit = () => setEditId(null);
const saveEdit = async () => {
if (!editId) return;
await fetch(`${API_URL}/entradas/${editId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
inicio: editInicio, fin: editFin || null, descripcion: editDesc,
estado: editEstado, modo: editModo, es_ia: editEsIa,
})
});
setEditId(null);
fetchBitacora();
};
const requestDeleteEntrada = (id: number, desc: string) => {
setConfirmDelete({ type: 'entrada', id, name: desc.substring(0, 50) + '...' });
};
const executeDelete = async () => {
if (!confirmDelete) return;
const { type, id } = confirmDelete;
if (type === 'entrada') {
await fetch(`${API_URL}/entradas/${id}`, { method: 'DELETE' });
fetchBitacora();
} else if (type === 'nodo') {
await fetch(`${API_URL}/nodos/${id}`, { method: 'DELETE' });
fetchNodos();
}
setConfirmDelete(null);
};
// === NODOS CRUD ===
const resetNodoForm = () => {
setEditNodoId(null); setNodoNombre(''); setNodoIp(''); setNodoTipo('servidor'); setNodoDesc('');
setNodoFormVisible(false);
};
const openNodoForm = (nodo?: Nodo) => {
if (nodo) {
setEditNodoId(nodo.id); setNodoNombre(nodo.nombre); setNodoIp(nodo.ip || '');
setNodoTipo(nodo.tipo || 'servidor'); setNodoDesc(nodo.descripcion || '');
} else {
setEditNodoId(null); setNodoNombre(''); setNodoIp(''); setNodoTipo('servidor'); setNodoDesc('');
}
setNodoFormVisible(true);
};
const saveNodo = async (e: React.FormEvent) => {
e.preventDefault();
if (!nodoNombre) return;
if (editNodoId) {
const existing = nodos.find(n => n.id === editNodoId);
await fetch(`${API_URL}/nodos/${editNodoId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nombre: nodoNombre, ip: nodoIp, tipo: nodoTipo, descripcion: nodoDesc, activo: existing?.activo ?? true })
});
} else {
await fetch(`${API_URL}/nodos`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nombre: nodoNombre, ip: nodoIp, tipo: nodoTipo, descripcion: nodoDesc })
});
}
resetNodoForm();
fetchNodos();
};
const requestDeleteNodo = (id: number, nombre: string) => {
setConfirmDelete({ type: 'nodo', id, name: nombre });
};
// === STATS ===
const totalEntradas = bitacora?.nodos?.reduce((sum, n) => sum + n.entradas.length, 0) || 0;
const totalNodos = bitacora?.nodos?.length || 0;
return (
<div className="app">
{/* Modal de confirmación */}
<ConfirmModal
show={!!confirmDelete}
title="Confirmar eliminación"
message={`¿Estás seguro que querés eliminar "${confirmDelete?.name}"? Esta acción no se puede deshacer.`}
onConfirm={executeDelete}
onCancel={() => setConfirmDelete(null)}
/>
<nav className="navbar">
<div className="navbar-brand">
<i className="fas fa-book-open"></i>
dtic-BITACORAs
<span className="badge badge-gradient">v1.0</span>
<span className="badge badge-gradient">v1.1</span>
</div>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<button
className={`btn btn-sm ${tab === 'bitacora' ? 'btn-primary' : ''}`}
onClick={() => changeTab('bitacora')}
style={tab !== 'bitacora' ? { background: 'var(--bg-card)', color: 'var(--text)', border: '1px solid var(--border)' } : {}}
><i className="fas fa-book"></i> Bitácora</button>
<button
className={`btn btn-sm ${tab === 'nodos' ? 'btn-primary' : ''}`}
onClick={() => changeTab('nodos')}
style={tab !== 'nodos' ? { background: 'var(--bg-card)', color: 'var(--text)', border: '1px solid var(--border)' } : {}}
><i className="fas fa-server"></i> Nodos</button>
<button
className={`btn btn-sm ${tab === 'proyecto' ? 'btn-primary' : ''}`}
onClick={() => changeTab('proyecto')}
style={tab !== 'proyecto' ? { background: 'var(--bg-card)', color: 'var(--text)', border: '1px solid var(--border)' } : {}}
><i className="fas fa-project-diagram"></i> P2601</button>
{tab === 'bitacora' && (
<a href={`${API_URL}/bitacoras/${fecha}/export`} target="_blank" rel="noopener"
className="btn btn-sm btn-primary">
<i className="fas fa-file-export"></i> Exportar MD
</a>
)}
</div>
<a href={`${API_URL}/api/bitacoras/${fecha}/export`} target="_blank" rel="noopener"
className="btn btn-sm btn-primary">
<i className="fas fa-file-export"></i> Exportar MD
</a>
</nav>
<main className="main">
{/* Date Selector */}
<div className="date-selector">
<button className="btn btn-sm btn-primary" onClick={() => {
const d = new Date(fecha); d.setDate(d.getDate() - 1); setFecha(d.toISOString().split('T')[0]);
}}><i className="fas fa-chevron-left"></i></button>
<input type="date" value={fecha} onChange={e => setFecha(e.target.value)} />
<button className="btn btn-sm btn-primary" onClick={() => {
const d = new Date(fecha); d.setDate(d.getDate() + 1); setFecha(d.toISOString().split('T')[0]);
}}><i className="fas fa-chevron-right"></i></button>
</div>
{/* Stats */}
<div className="stats-bar">
<div className="stat-card">
<div className="stat-value">{totalEntradas}</div>
<div className="stat-label">Entradas</div>
</div>
<div className="stat-card">
<div className="stat-value">{totalNodos}</div>
<div className="stat-label">Nodos Activos</div>
</div>
<div className="stat-card">
<div className="stat-value">{bitacora?.nodos?.reduce((s, n) => s + n.entradas.filter(e => e.estado === '✅').length, 0) || 0}</div>
<div className="stat-label">Completadas</div>
</div>
<div className="stat-card">
<div className="stat-value">{bitacora?.nodos?.reduce((s, n) => s + n.entradas.filter(e => e.estado === '⏳').length, 0) || 0}</div>
<div className="stat-label">En Proceso</div>
</div>
</div>
{/* New Entry Form */}
<div className="card" style={{ marginBottom: '2rem' }}>
<div className="card-header"><i className="fas fa-plus-circle"></i> Nueva Entrada</div>
<form className="form-inline" onSubmit={handleSubmit}>
<input type="time" className="input-time" value={newInicio} onChange={e => setNewInicio(e.target.value)} placeholder="I" required />
<input type="time" className="input-time" value={newFin} onChange={e => setNewFin(e.target.value)} placeholder="F" />
<input type="text" className="input-desc" value={newDesc} onChange={e => setNewDesc(e.target.value)} placeholder="Descripción..." required />
<select value={newNodoId} onChange={e => setNewNodoId(e.target.value)} required>
<option value="">Nodo...</option>
{nodos.map(n => <option key={n.id} value={n.id}>{n.nombre}</option>)}
</select>
<select value={newEstado} onChange={e => setNewEstado(e.target.value)}>
<option value="⏳"></option><option value="✅"></option>
<option value="⚠️"></option><option value="❌"></option><option value="👁️">👁</option>
</select>
<select value={newModo} onChange={e => setNewModo(e.target.value)}>
<option value="P">[P] Presencial</option><option value="R">[R] Remoto</option>
</select>
<label style={{ display: 'flex', alignItems: 'center', gap: '0.3rem', color: 'var(--text-secondary)', fontSize: '0.85rem' }}>
<input type="checkbox" checked={newEsIa} onChange={e => setNewEsIa(e.target.checked)} /> IA
</label>
<button type="submit" className="btn btn-primary"><i className="fas fa-save"></i> Guardar</button>
</form>
</div>
{/* Loading */}
{loading && <div style={{ textAlign: 'center', padding: '2rem', color: 'var(--text-muted)' }}><i className="fas fa-spinner fa-spin fa-2x"></i></div>}
{/* Entries by Node */}
{bitacora?.nodos?.map((nodoGroup, idx) => (
<div key={idx} className="nodo-section card" style={{ marginBottom: '1.5rem' }}>
<div className="nodo-title">
<i className="fas fa-server"></i>
{nodoGroup.nodo}
{nodoGroup.ip && <span className="nodo-ip">({nodoGroup.ip})</span>}
<span className="badge badge-gradient" style={{ marginLeft: 'auto' }}>{nodoGroup.entradas.length}</span>
{/* =================== TAB BITÁCORA =================== */}
{tab === 'bitacora' && (
<>
{/* Date Selector */}
<div className="date-selector">
<button className="btn btn-sm btn-primary" onClick={() => {
const d = new Date(fecha); d.setDate(d.getDate() - 1); setFecha(d.toISOString().split('T')[0]);
}}><i className="fas fa-chevron-left"></i></button>
<input type="date" value={fecha} onChange={e => setFecha(e.target.value)} />
<button className="btn btn-sm btn-primary" onClick={() => {
const d = new Date(fecha); d.setDate(d.getDate() + 1); setFecha(d.toISOString().split('T')[0]);
}}><i className="fas fa-chevron-right"></i></button>
<button className="btn btn-sm" onClick={() => setFecha(new Date().toISOString().split('T')[0])}
style={{ background: 'var(--bg-card)', color: 'var(--text-secondary)', border: '1px solid var(--border)' }}>
<i className="fas fa-calendar-day"></i> Hoy
</button>
</div>
<table className="table-ifde">
<thead>
<tr>
<th className="col-time">I</th>
<th className="col-time">F</th>
<th>Descripción</th>
<th className="col-status">E</th>
<th style={{ width: '40px' }}></th>
</tr>
</thead>
<tbody>
{nodoGroup.entradas.map(e => (
<tr key={e.id}>
<td className="col-time">{e.inicio?.substring(0, 5)}</td>
<td className="col-time">{e.fin ? e.fin.substring(0, 5) : '—'}</td>
<td className="col-desc">
{e.es_ia && <span className="ia-tag">IA</span>}
{e.descripcion}
</td>
<td className="col-status">[{e.modo}] {e.estado}</td>
<td>
<button className="btn btn-sm btn-danger" onClick={() => handleDelete(e.id)} title="Eliminar">
<i className="fas fa-trash-alt"></i>
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
))}
{!loading && totalEntradas === 0 && (
<div style={{ textAlign: 'center', padding: '3rem', color: 'var(--text-muted)' }}>
<i className="fas fa-inbox fa-3x" style={{ marginBottom: '1rem', display: 'block' }}></i>
Sin entradas para esta fecha. ¡Creá la primera!
</div>
{/* Stats */}
<div className="stats-bar">
<div className="stat-card">
<div className="stat-value">{totalEntradas}</div>
<div className="stat-label">Entradas</div>
</div>
<div className="stat-card">
<div className="stat-value">{totalNodos}</div>
<div className="stat-label">Nodos Activos</div>
</div>
<div className="stat-card">
<div className="stat-value">{bitacora?.nodos?.reduce((s, n) => s + n.entradas.filter(e => e.estado === '✅').length, 0) || 0}</div>
<div className="stat-label">Completadas</div>
</div>
<div className="stat-card">
<div className="stat-value">{bitacora?.nodos?.reduce((s, n) => s + n.entradas.filter(e => e.estado === '⏳').length, 0) || 0}</div>
<div className="stat-label">En Proceso</div>
</div>
</div>
{/* New Entry Form */}
<div className="card" style={{ marginBottom: '2rem' }}>
<div className="card-header"><i className="fas fa-plus-circle"></i> Nueva Entrada</div>
<form className="form-inline" onSubmit={handleSubmit}>
<input type="time" className="input-time" value={newInicio} onChange={e => setNewInicio(e.target.value)} placeholder="I" required />
<input type="time" className="input-time" value={newFin} onChange={e => setNewFin(e.target.value)} placeholder="F" />
<input type="text" className="input-desc" value={newDesc} onChange={e => setNewDesc(e.target.value)} placeholder="Descripción..." required />
<select value={newNodoId} onChange={e => setNewNodoId(e.target.value)} required>
<option value="">Nodo...</option>
{nodos.map(n => <option key={n.id} value={n.id}>{n.nombre}</option>)}
</select>
<select value={newEstado} onChange={e => setNewEstado(e.target.value)}>
<option value="⏳"></option><option value="✅"></option>
<option value="⚠️"></option><option value="❌"></option><option value="👁️">👁</option>
</select>
<select value={newModo} onChange={e => setNewModo(e.target.value)}>
<option value="P">[P] Presencial</option><option value="R">[R] Remoto</option>
</select>
<label style={{ display: 'flex', alignItems: 'center', gap: '0.3rem', color: 'var(--text-secondary)', fontSize: '0.85rem' }}>
<input type="checkbox" checked={newEsIa} onChange={e => setNewEsIa(e.target.checked)} /> IA
</label>
<button type="submit" className="btn btn-primary"><i className="fas fa-save"></i> Guardar</button>
</form>
</div>
{/* Loading */}
{loading && <div style={{ textAlign: 'center', padding: '2rem', color: 'var(--text-muted)' }}><i className="fas fa-spinner fa-spin fa-2x"></i></div>}
{/* Entries by Node */}
{bitacora?.nodos?.map((nodoGroup, idx) => (
<div key={idx} className="nodo-section card" style={{ marginBottom: '1.5rem' }}>
<div className="nodo-title">
<i className="fas fa-server"></i>
{nodoGroup.nodo}
{nodoGroup.ip && <span className="nodo-ip">({nodoGroup.ip})</span>}
<span className="badge badge-gradient" style={{ marginLeft: 'auto' }}>{nodoGroup.entradas.length}</span>
</div>
<table className="table-ifde">
<thead>
<tr>
<th className="col-time">I</th>
<th className="col-time">F</th>
<th>Descripción</th>
<th className="col-status">E</th>
<th style={{ width: '80px' }}>Acc.</th>
</tr>
</thead>
<tbody>
{nodoGroup.entradas.map(e => (
editId === e.id ? (
/* ==== FILA EN EDICIÓN ==== */
<tr key={e.id} style={{ background: 'rgba(102, 126, 234, 0.1)' }}>
<td><input type="time" value={editInicio} onChange={ev => setEditInicio(ev.target.value)} className="edit-input" /></td>
<td><input type="time" value={editFin} onChange={ev => setEditFin(ev.target.value)} className="edit-input" /></td>
<td>
<input type="text" value={editDesc} onChange={ev => setEditDesc(ev.target.value)} className="edit-input" style={{ width: '100%' }} />
<div style={{ display: 'flex', gap: '0.3rem', marginTop: '0.3rem', alignItems: 'center' }}>
<select value={editEstado} onChange={ev => setEditEstado(ev.target.value)} className="edit-input">
<option value="⏳"></option><option value="✅"></option>
<option value="⚠️"></option><option value="❌"></option><option value="👁️">👁</option>
</select>
<select value={editModo} onChange={ev => setEditModo(ev.target.value)} className="edit-input">
<option value="P">[P]</option><option value="R">[R]</option>
</select>
<label style={{ fontSize: '0.75rem', color: 'var(--text-secondary)', display: 'flex', alignItems: 'center', gap: '0.2rem' }}>
<input type="checkbox" checked={editEsIa} onChange={ev => setEditEsIa(ev.target.checked)} /> IA
</label>
</div>
</td>
<td></td>
<td>
<div style={{ display: 'flex', gap: '0.2rem' }}>
<button className="btn btn-sm btn-success" onClick={saveEdit} title="Guardar"><i className="fas fa-check"></i></button>
<button className="btn btn-sm" onClick={cancelEdit} title="Cancelar" style={{ background: 'var(--bg-card)', color: 'var(--text-muted)' }}><i className="fas fa-times"></i></button>
</div>
</td>
</tr>
) : (
/* ==== FILA NORMAL ==== */
<tr key={e.id}>
<td className="col-time">{e.inicio?.substring(0, 5)}</td>
<td className="col-time">{e.fin ? e.fin.substring(0, 5) : '—'}</td>
<td className="col-desc">
{e.es_ia && <span className="ia-tag">IA</span>}
{e.descripcion}
</td>
<td className="col-status">[{e.modo}] {e.estado}</td>
<td>
<div style={{ display: 'flex', gap: '0.2rem' }}>
<button className="btn btn-sm" onClick={() => startEdit(e)} title="Editar"
style={{ background: 'var(--info)', color: 'white' }}>
<i className="fas fa-pen"></i>
</button>
<button className="btn btn-sm btn-danger" onClick={() => requestDeleteEntrada(e.id, e.descripcion)} title="Eliminar">
<i className="fas fa-trash-alt"></i>
</button>
</div>
</td>
</tr>
)
))}
</tbody>
</table>
</div>
))}
{!loading && totalEntradas === 0 && (
<div style={{ textAlign: 'center', padding: '3rem', color: 'var(--text-muted)' }}>
<i className="fas fa-inbox fa-3x" style={{ marginBottom: '1rem', display: 'block' }}></i>
Sin entradas para esta fecha. ¡Creá la primera!
</div>
)}
</>
)}
{/* =================== TAB NODOS =================== */}
{tab === 'nodos' && (
<>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1.5rem' }}>
<h2 style={{ fontSize: '1.3rem', fontWeight: 600 }}><i className="fas fa-server" style={{ color: 'var(--primary)', marginRight: '0.5rem' }}></i>Gestión de Nodos</h2>
<button className="btn btn-primary" onClick={() => openNodoForm()}>
<i className="fas fa-plus"></i> Nuevo Nodo
</button>
</div>
{/* Nodo Form */}
{nodoFormVisible && (
<div className="card" style={{ marginBottom: '1.5rem' }}>
<div className="card-header">
<i className={editNodoId ? 'fas fa-pen' : 'fas fa-plus-circle'}></i>
{editNodoId ? 'Editar Nodo' : 'Nuevo Nodo'}
</div>
<form className="form-inline" onSubmit={saveNodo}>
<input type="text" value={nodoNombre} onChange={e => setNodoNombre(e.target.value)} placeholder="Nombre (ej: srv-ns8)" required style={{ minWidth: '150px' }} />
<input type="text" value={nodoIp} onChange={e => setNodoIp(e.target.value)} placeholder="IP (ej: 10.0.10.200)" style={{ minWidth: '130px' }} />
<select value={nodoTipo} onChange={e => setNodoTipo(e.target.value)}>
<option value="servidor">Servidor</option>
<option value="vm">VM</option>
<option value="pc">PC</option>
<option value="switch">Switch</option>
<option value="otro">Otro</option>
</select>
<input type="text" className="input-desc" value={nodoDesc} onChange={e => setNodoDesc(e.target.value)} placeholder="Descripción..." />
<button type="submit" className="btn btn-success"><i className="fas fa-save"></i> {editNodoId ? 'Actualizar' : 'Crear'}</button>
<button type="button" className="btn btn-sm" onClick={resetNodoForm} style={{ background: 'var(--bg-card)', color: 'var(--text-muted)', border: '1px solid var(--border)' }}>
<i className="fas fa-times"></i> Cancelar
</button>
</form>
</div>
)}
{/* Nodos Table */}
<div className="card">
<table className="table-ifde">
<thead>
<tr>
<th>ID</th>
<th>Nombre</th>
<th>IP</th>
<th>Tipo</th>
<th>Descripción</th>
<th style={{ width: '80px' }}>Acc.</th>
</tr>
</thead>
<tbody>
{nodos.map(n => (
<tr key={n.id}>
<td style={{ color: 'var(--text-muted)', fontSize: '0.8rem' }}>{n.id}</td>
<td style={{ fontWeight: 600 }}>{n.nombre}</td>
<td style={{ fontFamily: 'monospace', color: 'var(--text-secondary)' }}>{n.ip || '—'}</td>
<td><span className="badge" style={{ background: 'rgba(102, 126, 234, 0.15)', color: 'var(--primary)' }}>{n.tipo || 'N/A'}</span></td>
<td style={{ color: 'var(--text-secondary)', fontSize: '0.85rem' }}>{n.descripcion || '—'}</td>
<td>
<div style={{ display: 'flex', gap: '0.2rem' }}>
<button className="btn btn-sm" onClick={() => openNodoForm(n)} title="Editar"
style={{ background: 'var(--info)', color: 'white' }}>
<i className="fas fa-pen"></i>
</button>
<button className="btn btn-sm btn-danger" onClick={() => requestDeleteNodo(n.id, n.nombre)} title="Eliminar">
<i className="fas fa-trash-alt"></i>
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</>
)}
{/* =================== TAB PROYECTO =================== */}
{tab === 'proyecto' && (
<ProyectoDashboard codigo="P2601" />
)}
</main>
</div>
+265 -39
View File
@@ -23,7 +23,11 @@
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
@@ -33,7 +37,11 @@ body {
}
/* Layout */
.app { min-height: 100vh; display: flex; flex-direction: column; }
.app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.navbar {
background: rgba(15, 23, 42, 0.8);
@@ -59,7 +67,10 @@ body {
gap: 0.5rem;
}
.navbar-brand i { -webkit-text-fill-color: initial; color: var(--primary); }
.navbar-brand i {
-webkit-text-fill-color: initial;
color: var(--primary);
}
.badge {
display: inline-block;
@@ -70,10 +81,19 @@ body {
letter-spacing: 0.05em;
}
.badge-gradient { background: var(--gradient); color: white; }
.badge-gradient {
background: var(--gradient);
color: white;
}
/* Main content */
.main { flex: 1; padding: 2rem; max-width: 1400px; margin: 0 auto; width: 100%; }
.main {
flex: 1;
padding: 2rem;
max-width: 1400px;
margin: 0 auto;
width: 100%;
}
/* Cards */
.card {
@@ -85,7 +105,11 @@ body {
animation: fadeIn 0.5s ease-out;
}
.card:hover { transform: translateY(-2px); box-shadow: var(--shadow); border-color: var(--primary); }
.card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow);
border-color: var(--primary);
}
.card-header {
background: var(--gradient);
@@ -100,7 +124,12 @@ body {
}
/* Table I-F-D-E */
.table-ifde { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.table-ifde {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.table-ifde th {
background: rgba(102, 126, 234, 0.15);
color: var(--primary);
@@ -112,15 +141,33 @@ body {
letter-spacing: 0.05em;
border-bottom: 2px solid var(--border);
}
.table-ifde td {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border);
vertical-align: top;
}
.table-ifde tr:hover td { background: rgba(102, 126, 234, 0.05); }
.table-ifde .col-time { width: 60px; font-family: 'JetBrains Mono', monospace; color: var(--text-secondary); font-size: 0.85rem; }
.table-ifde .col-status { width: 80px; text-align: center; }
.table-ifde .col-desc { color: var(--text); }
.table-ifde tr:hover td {
background: rgba(102, 126, 234, 0.05);
}
.table-ifde .col-time {
width: 60px;
font-family: 'JetBrains Mono', monospace;
color: var(--text-secondary);
font-size: 0.85rem;
}
.table-ifde .col-status {
width: 80px;
text-align: center;
}
.table-ifde .col-desc {
color: var(--text);
}
.table-ifde .ia-tag {
background: rgba(102, 126, 234, 0.2);
color: var(--primary);
@@ -132,7 +179,10 @@ body {
}
/* Nodo section */
.nodo-section { margin-bottom: 2rem; }
.nodo-section {
margin-bottom: 2rem;
}
.nodo-title {
display: flex;
align-items: center;
@@ -143,12 +193,31 @@ body {
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--border);
}
.nodo-title i { color: var(--primary); }
.nodo-ip { color: var(--text-muted); font-size: 0.8rem; font-weight: 400; }
.nodo-title i {
color: var(--primary);
}
.nodo-ip {
color: var(--text-muted);
font-size: 0.8rem;
font-weight: 400;
}
/* Form inline */
.form-inline { display: flex; gap: 0.5rem; align-items: center; padding: 1rem; background: rgba(102, 126, 234, 0.05); border-radius: var(--radius); margin-top: 1rem; flex-wrap: wrap; }
.form-inline input, .form-inline select {
.form-inline {
display: flex;
gap: 0.5rem;
align-items: center;
padding: 1rem;
background: rgba(102, 126, 234, 0.05);
border-radius: var(--radius);
margin-top: 1rem;
flex-wrap: wrap;
}
.form-inline input,
.form-inline select {
background: var(--bg-input);
border: 1px solid var(--border);
color: var(--text);
@@ -158,13 +227,22 @@ body {
font-family: inherit;
transition: var(--transition);
}
.form-inline input:focus, .form-inline select:focus {
.form-inline input:focus,
.form-inline select:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}
.form-inline .input-time { width: 80px; }
.form-inline .input-desc { flex: 1; min-width: 200px; }
.form-inline .input-time {
width: 80px;
}
.form-inline .input-desc {
flex: 1;
min-width: 200px;
}
/* Buttons */
.btn {
@@ -179,12 +257,34 @@ body {
align-items: center;
gap: 0.3rem;
}
.btn:hover { transform: translateY(-1px); }
.btn-primary { background: var(--gradient); color: white; }
.btn-primary:hover { box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); }
.btn-sm { padding: 0.3rem 0.6rem; font-size: 0.75rem; }
.btn-danger { background: var(--danger); color: white; }
.btn-success { background: var(--success); color: white; }
.btn:hover {
transform: translateY(-1px);
}
.btn-primary {
background: var(--gradient);
color: white;
}
.btn-primary:hover {
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
}
.btn-sm {
padding: 0.3rem 0.6rem;
font-size: 0.75rem;
}
.btn-danger {
background: var(--danger);
color: white;
}
.btn-success {
background: var(--success);
color: white;
}
/* Date selector */
.date-selector {
@@ -193,6 +293,7 @@ body {
gap: 1rem;
margin-bottom: 2rem;
}
.date-selector input[type="date"] {
background: var(--bg-card);
border: 1px solid var(--border);
@@ -202,13 +303,20 @@ body {
font-size: 1rem;
font-family: inherit;
}
.date-selector input[type="date"]:focus {
outline: none;
border-color: var(--primary);
}
/* Stats bar */
.stats-bar { display: flex; gap: 1rem; margin-bottom: 2rem; flex-wrap: wrap; }
.stats-bar {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.stat-card {
background: var(--bg-card);
border: 1px solid var(--border);
@@ -219,24 +327,142 @@ body {
text-align: center;
transition: var(--transition);
}
.stat-card:hover { border-color: var(--primary); }
.stat-value { font-size: 2rem; font-weight: 700; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.stat-label { font-size: 0.8rem; color: var(--text-muted); margin-top: 0.3rem; text-transform: uppercase; letter-spacing: 0.05em; }
.stat-card:hover {
border-color: var(--primary);
}
.stat-value {
font-size: 2rem;
font-weight: 700;
background: var(--gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat-label {
font-size: 0.8rem;
color: var(--text-muted);
margin-top: 0.3rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Animations */
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Scrollbar */
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg);
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
/* Responsive */
@media (max-width: 768px) {
.main { padding: 1rem; }
.navbar { padding: 0.75rem 1rem; }
.stats-bar { flex-direction: column; }
.form-inline { flex-direction: column; }
.form-inline .input-desc { min-width: 100%; }
.main {
padding: 1rem;
}
.navbar {
padding: 0.75rem 1rem;
}
.stats-bar {
flex-direction: column;
}
.form-inline {
flex-direction: column;
}
.form-inline .input-desc {
min-width: 100%;
}
}
/* Modal de confirmación */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.2s ease-out;
}
.modal-box {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0;
max-width: 420px;
width: 90%;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
animation: fadeIn 0.3s ease-out;
}
.modal-header {
background: var(--danger);
color: white;
padding: 1rem 1.5rem;
border-radius: var(--radius) var(--radius) 0 0;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.5rem;
}
.modal-body {
padding: 1.5rem;
color: var(--text-secondary);
line-height: 1.5;
}
.modal-actions {
padding: 0 1.5rem 1.5rem;
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
/* Campos de edición inline */
.edit-input {
background: var(--bg-input);
border: 1px solid var(--primary);
color: var(--text);
padding: 0.35rem 0.5rem;
border-radius: 6px;
font-size: 0.8rem;
font-family: inherit;
transition: var(--transition);
}
.edit-input:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}
@@ -0,0 +1,645 @@
import React, { useState, useEffect } from 'react';
const API_URL = import.meta.env.VITE_API_URL || '/bitacoras/api';
interface Hito {
id: number;
id_hito: string;
titulo: string;
descripcion: string;
estado: string;
fecha: string | null;
horas_presencial: number;
horas_remoto: number;
nodo_nombre: string;
nodo_ip: string;
}
interface Fase {
id: number;
numero: number;
nombre: string;
estado: string;
descripcion: string;
fecha_inicio: string | null;
fecha_fin: string | null;
hitos: Hito[] | null;
}
interface NodoInfo {
id: number;
nombre: string;
ip: string;
tipo: string;
descripcion: string;
}
interface Metricas {
hitos_completados: string;
hitos_pendientes: string;
hitos_total: string;
total_presencial: string;
total_remoto: string;
total_horas: string;
}
interface Proyecto {
id: number;
codigo: string;
nombre: string;
estado: string;
descripcion: string;
fases: Fase[];
metricas: Metricas;
nodos: NodoInfo[];
}
type Vista = 'gerencial' | 'tecnica';
/* ═══════════ DONUT CHART SVG ═══════════ */
function DonutChart({ segments, size = 140, strokeWidth = 18, centerLabel, centerValue, centerColor }: {
segments: { value: number; color: string; label: string }[];
size?: number;
strokeWidth?: number;
centerLabel?: string;
centerValue?: string;
centerColor?: string;
}) {
const radius = (size - strokeWidth) / 2;
const circumference = 2 * Math.PI * radius;
const total = segments.reduce((s, seg) => s + seg.value, 0);
let offset = 0;
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.5rem' }}>
<svg width={size} height={size} style={{ transform: 'rotate(-90deg)' }}>
{/* Fondo gris */}
<circle cx={size / 2} cy={size / 2} r={radius}
fill="none" stroke="rgba(255,255,255,0.05)" strokeWidth={strokeWidth} />
{/* Segmentos */}
{total > 0 && segments.map((seg, i) => {
const segLen = (seg.value / total) * circumference;
const el = (
<circle key={i} cx={size / 2} cy={size / 2} r={radius}
fill="none" stroke={seg.color} strokeWidth={strokeWidth}
strokeDasharray={`${segLen} ${circumference - segLen}`}
strokeDashoffset={-offset}
strokeLinecap="round"
style={{ transition: 'stroke-dasharray 0.6s ease, stroke-dashoffset 0.6s ease' }}
/>
);
offset += segLen;
return el;
})}
{/* Texto central */}
{centerValue && (
<g style={{ transform: 'rotate(90deg)', transformOrigin: 'center' }}>
<text x={size / 2} y={size / 2 - 4} textAnchor="middle" dominantBaseline="middle"
fill={centerColor || '#fff'} fontSize="1.6rem" fontWeight="700">{centerValue}</text>
{centerLabel && (
<text x={size / 2} y={size / 2 + 16} textAnchor="middle" dominantBaseline="middle"
fill="var(--text-muted)" fontSize="0.6rem" textTransform="uppercase" letterSpacing="0.05em">{centerLabel}</text>
)}
</g>
)}
</svg>
{/* Leyenda */}
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem 1rem', justifyContent: 'center' }}>
{segments.filter(s => s.value > 0).map((seg, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: '4px', fontSize: '0.7rem', color: 'var(--text-muted)' }}>
<span style={{ width: 8, height: 8, borderRadius: '50%', background: seg.color, flexShrink: 0 }}></span>
{seg.label}
</div>
))}
</div>
</div>
);
}
/* ═══════════ CONTENIDO ENRIQUECIDO POR FASE ═══════════ */
const faseDetalles: Record<number, { objetivo: string; desarrollo: string; logros: string }> = {
1: {
objetivo: 'Preparar el equipo central que alojará todos los servicios de DASUTeN, garantizando que opere de forma independiente a los sistemas de la Facultad.',
desarrollo: 'Se instaló y configuró el servidor dedicado en la oficina de DASUTeN, creando un entorno de trabajo completamente separado de la red universitaria para mayor seguridad y control.',
logros: 'Servidor propio operativo y listo para funcionar. Entorno aislado que brinda independencia total respecto a los sistemas de la Facultad.'
},
2: {
objetivo: 'Establecer un sistema centralizado de identificación de usuarios y permisos de acceso, propio de la oficina DASUTeN.',
desarrollo: 'Se configuró un servicio de gestión de identidades que permite controlar quién accede a cada equipo y recurso dentro de la red de DASUTeN.',
logros: 'Sistema de usuarios operativo. Cada persona accede con credenciales propias. Se simplifica la administración de permisos y la seguridad de acceso.'
},
3: {
objetivo: 'Instalar el sistema de base de datos necesario y recuperar toda la información del sistema DASUTeN desde el respaldo existente.',
desarrollo: 'Se preparó un entorno dedicado para la base de datos, se transfirió el respaldo completo (~9 GB de información) y se restauró verificando su integridad.',
logros: 'Toda la información de gestión de DASUTeN fue recuperada exitosamente. La base de datos está operativa y accesible desde la red interna.'
},
4: {
objetivo: 'Habilitar la administración remota segura de todos los equipos y generar respaldos de seguridad como punto de recuperación.',
desarrollo: 'Se activó el acceso remoto seguro en todos los equipos y se generaron copias de respaldo completas para poder restaurar el sistema ante cualquier eventualidad.',
logros: 'Posibilidad de gestionar todos los equipos sin necesidad de presencia física. Respaldos de seguridad disponibles ante cualquier imprevisto.'
},
5: {
objetivo: 'Analizar la aplicación de gestión DASUTeN y ponerla en funcionamiento sobre la nueva infraestructura virtual.',
desarrollo: 'Configuración de Kermet.ini, instalación de componentes de sistema, fuentes y apertura de firewall SQL. Pruebas de conectividad exitosas.',
logros: 'Despliegue operativo verificado con login exitoso. Validación presencial (UAT) realizada con Andrea Almirón satisfactoriamente.'
},
6: {
objetivo: 'Poner en funcionamiento el servidor físico en la oficina, integrar clientes al dominio y configurar políticas de seguridad.',
desarrollo: 'Traslado físico del servidor Proxmox, unión de la PC pc-dasu0 al dominio dasuten.utnlr y despliegue de GPOs de seguridad.',
logros: 'Hitos pendientes: Instalación física, AD-Join, GPO de seguridad y mapeo de unidades de red.'
}
};
/* ═══════════ MAIN COMPONENT ═══════════ */
export default function ProyectoDashboard({ codigo = 'P2601' }: { codigo?: string }) {
const [proyecto, setProyecto] = useState<Proyecto | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [vista, setVista] = useState<Vista>('gerencial');
const [expandedFases, setExpandedFases] = useState<Set<number>>(new Set());
const [expandedGerFases, setExpandedGerFases] = useState<Set<number>>(new Set());
const [descExpanded, setDescExpanded] = useState(false);
useEffect(() => { fetchProyecto(); }, [codigo]);
const fetchProyecto = async () => {
setLoading(true);
try {
const res = await fetch(`${API_URL}/proyectos/${codigo}`);
if (!res.ok) throw new Error('Proyecto no encontrado');
const data = await res.json();
setProyecto(data);
setExpandedFases(new Set(data.fases.map((f: Fase) => f.id)));
} catch (e: any) {
setError(e.message);
} finally {
setLoading(false);
}
};
const toggleFase = (id: number) => {
setExpandedFases(prev => {
const next = new Set(prev);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
};
const toggleGerFase = (num: number) => {
setExpandedGerFases(prev => {
const next = new Set(prev);
next.has(num) ? next.delete(num) : next.add(num);
return next;
});
};
if (loading) return <div style={{ textAlign: 'center', padding: '3rem', color: 'var(--text-muted)' }}>Cargando proyecto...</div>;
if (error || !proyecto) return <div style={{ textAlign: 'center', padding: '3rem', color: '#ef4444' }}>Error: {error}</div>;
const m = proyecto.metricas;
const totalH = parseFloat(m.total_horas);
const completados = parseInt(m.hitos_completados);
const pendientes = parseInt(m.hitos_pendientes);
const totalHitos = parseInt(m.hitos_total);
const progreso = totalHitos > 0 ? Math.round((completados / totalHitos) * 100) : 0;
const hrsP = parseFloat(m.total_presencial);
const hrsR = parseFloat(m.total_remoto);
const fasesCompletas = proyecto.fases.filter(f => f.estado === '✅').length;
const fasesEnCurso = proyecto.fases.filter(f => f.estado === '⏳').length;
const fasesPendientes = proyecto.fases.filter(f => f.estado !== '✅' && f.estado !== '⏳').length;
const estadoIcon = (estado: string) => {
if (estado === '✅') return '✅';
if (estado === '⏳') return '⏳';
if (estado === '📋') return '📋';
return estado;
};
return (
<div className="proyecto-dashboard">
{/* Header */}
<div style={{ textAlign: 'center', marginBottom: '1.5rem' }}>
<div style={{
display: 'inline-block', padding: '3px 12px', borderRadius: '999px',
background: 'rgba(56,189,248,0.12)', color: '#38bdf8',
fontSize: '0.72rem', fontWeight: 600, letterSpacing: '0.05em', textTransform: 'uppercase',
marginBottom: '0.5rem'
}}>{proyecto.codigo}</div>
<h2 style={{ fontSize: '1.5rem', fontWeight: 700, margin: '0.3rem 0' }}>
{proyecto.nombre}
</h2>
<p style={{ color: 'var(--text-muted)', fontSize: '0.85rem', maxWidth: '600px', margin: '0 auto 0.8rem' }}>
{proyecto.descripcion}
</p>
{/* Toggle vista */}
<div style={{
display: 'inline-flex', borderRadius: '8px', overflow: 'hidden',
border: '1px solid var(--border)', background: 'rgba(255,255,255,0.03)'
}}>
<button
onClick={() => setVista('gerencial')}
style={{
padding: '6px 16px', fontSize: '0.75rem', fontWeight: 600, border: 'none', cursor: 'pointer',
background: vista === 'gerencial' ? 'linear-gradient(135deg, #38bdf8, #818cf8)' : 'transparent',
color: vista === 'gerencial' ? '#fff' : 'var(--text-muted)', transition: 'all 0.2s ease'
}}
><i className="fas fa-chart-pie" style={{ marginRight: '5px' }}></i>Resumen</button>
<button
onClick={() => setVista('tecnica')}
style={{
padding: '6px 16px', fontSize: '0.75rem', fontWeight: 600, border: 'none', cursor: 'pointer',
background: vista === 'tecnica' ? 'linear-gradient(135deg, #38bdf8, #818cf8)' : 'transparent',
color: vista === 'tecnica' ? '#fff' : 'var(--text-muted)', transition: 'all 0.2s ease'
}}
><i className="fas fa-list-alt" style={{ marginRight: '5px' }}></i>Detalle Técnico</button>
</div>
</div>
{/* ═══════════ VISTA GERENCIAL ═══════════ */}
{vista === 'gerencial' && (
<>
{/* Descripción del Proyecto — desplegable */}
<div className="card" style={{ padding: 0, overflow: 'hidden', marginBottom: '1.5rem' }}>
<div
onClick={() => setDescExpanded(prev => !prev)}
style={{
padding: '0.8rem 1rem', cursor: 'pointer', display: 'flex',
alignItems: 'center', justifyContent: 'space-between',
userSelect: 'none'
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.6rem' }}>
<span style={{ fontSize: '1.1rem' }}>📋</span>
<span style={{ fontWeight: 600, fontSize: '0.88rem' }}>Descripción del Proyecto</span>
</div>
<span style={{ fontSize: '0.8rem', color: 'var(--text-muted)', transition: 'transform 0.2s' }}>
{descExpanded ? '▾' : '▸'}
</span>
</div>
{descExpanded && (
<div style={{ padding: '0 1rem 1rem', lineHeight: 1.6, fontSize: '0.82rem', color: 'var(--text)' }}>
<p style={{ margin: '0 0 0.8rem' }}>
<strong>DASUTeN</strong> es la obra social que brinda cobertura a empleados, docentes y alumnos de la
Universidad Tecnológica Nacional. Su oficina en la Facultad Regional La Rioja depende
operativamente de la <strong>sede central en Buenos Aires</strong>.
</p>
<p style={{ margin: '0 0 0.8rem' }}>
Este proyecto tiene como objetivo <strong>dotar a la oficina DASUTeN de su propia infraestructura
tecnológica independiente</strong>, operando bajo un entorno virtualizado (Proxmox) con acceso remoto seguro vía Tailscale.
</p>
<div style={{
display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)',
gap: '1.2rem', marginTop: '1rem'
}}>
<div>
<div style={subheadStyle}>🎯 Objetivos y Alcance</div>
<ul style={listStyle}>
<li>Sistema de gestión operando desde servidor propio</li>
<li>Independencia total de los equipos de la Facultad</li>
<li>Oficina autosuficiente con su propia red y respaldos</li>
<li>Servidor dedicado en oficina DASUTeN</li>
<li>Red interna propia y segura</li>
<li>Computadora de escritorio como acceso al sistema</li>
</ul>
</div>
<div>
<div style={subheadStyle}>👥 Responsables y Beneficiarios</div>
<ul style={listStyle}>
<li><strong>Ejecución:</strong> Lic. Ricardo Monla</li>
<li><strong>Supervisión:</strong> Dirección de TIC</li>
</ul>
<div style={{ fontSize: '0.7rem', color: '#38bdf8', fontWeight: 600, marginTop: '0.4rem', marginBottom: '0.2rem' }}>Usuarios directos:</div>
<ul style={listStyle}>
<li>Andrea Almirón Administrativa DASUTeN</li>
<li>Romina Molina Administrativa</li>
<li>Dra. Eugenia Riveros Auditora Médica</li>
</ul>
<div style={{ fontSize: '0.7rem', color: '#38bdf8', fontWeight: 600, marginTop: '0.4rem', marginBottom: '0.2rem' }}>Beneficiarios indirectos:</div>
<ul style={listStyle}>
<li>Personal Docente</li>
<li>Personal No Docente</li>
<li>Alumnos</li>
</ul>
</div>
</div>
</div>
)}
</div>
{/* Gráficos de torta */}
<div style={{
display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))',
gap: '1.2rem', marginBottom: '1.8rem'
}}>
{/* Donut: Avance Global */}
<div className="card" style={{ padding: '1.2rem', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div style={{ fontSize: '0.72rem', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '0.6rem', fontWeight: 600 }}>
Avance de Hitos
</div>
<DonutChart
segments={[
{ value: completados, color: '#22c55e', label: `${completados} Completados` },
{ value: pendientes, color: '#eab308', label: `${pendientes} Pendientes` },
]}
centerValue={`${progreso}%`}
centerLabel="avance"
centerColor="#22c55e"
/>
</div>
{/* Donut: Distribución de Horas */}
<div className="card" style={{ padding: '1.2rem', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div style={{ fontSize: '0.72rem', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '0.6rem', fontWeight: 600 }}>
Distribución de Horas
</div>
<DonutChart
segments={[
{ value: hrsP, color: '#f97316', label: `${hrsP.toFixed(1)}h Presencial` },
{ value: hrsR, color: '#a78bfa', label: `${hrsR.toFixed(1)}h Remoto` },
]}
centerValue={`${totalH.toFixed(0)}h`}
centerLabel="total"
centerColor="#38bdf8"
/>
</div>
{/* Donut: Estado de Fases */}
<div className="card" style={{ padding: '1.2rem', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div style={{ fontSize: '0.72rem', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '0.6rem', fontWeight: 600 }}>
Estado de Fases
</div>
<DonutChart
segments={[
{ value: fasesCompletas, color: '#22c55e', label: `${fasesCompletas} Completas` },
{ value: fasesEnCurso, color: '#eab308', label: `${fasesEnCurso} En curso` },
{ value: fasesPendientes, color: '#64748b', label: `${fasesPendientes} Pendientes` },
]}
centerValue={`${fasesCompletas}/${proyecto.fases.length}`}
centerLabel="fases"
centerColor="#22c55e"
/>
</div>
</div>
{/* Fases con descripciones expandibles */}
<SectionTitle title="📊 Fases del Proyecto" />
{proyecto.fases.map(fase => {
const hitos = fase.hitos || [];
const faseCompletos = hitos.filter(h => h.estado === '✅').length;
const faseProgreso = hitos.length > 0 ? Math.round((faseCompletos / hitos.length) * 100) : 0;
const hrsTotal = hitos.reduce((s, h) => s + h.horas_presencial + h.horas_remoto, 0);
const isExpanded = expandedGerFases.has(fase.numero);
const detalle = faseDetalles[fase.numero];
return (
<div key={fase.id} className="card" style={{
marginBottom: '0.6rem', padding: 0, overflow: 'hidden', position: 'relative'
}}>
{/* Barra de progreso de fondo */}
<div style={{
position: 'absolute', top: 0, left: 0, bottom: 0, width: `${faseProgreso}%`,
background: fase.estado === '✅' ? 'rgba(34,197,94,0.06)' : 'rgba(56,189,248,0.04)',
transition: 'width 0.4s', pointerEvents: 'none'
}} />
{/* Header clickeable */}
<div
onClick={() => toggleGerFase(fase.numero)}
style={{
position: 'relative', display: 'flex', justifyContent: 'space-between',
alignItems: 'center', padding: '0.8rem 1rem', cursor: 'pointer'
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.7rem' }}>
<span style={{
fontSize: '1.3rem', width: '36px', height: '36px', display: 'flex',
alignItems: 'center', justifyContent: 'center', borderRadius: '8px',
background: fase.estado === '✅' ? 'rgba(34,197,94,0.12)' :
fase.estado === '⏳' ? 'rgba(234,179,8,0.12)' : 'rgba(255,255,255,0.05)'
}}>{estadoIcon(fase.estado)}</span>
<div>
<div style={{ fontWeight: 600, fontSize: '0.88rem' }}>
F{fase.numero} · {fase.nombre}
</div>
{!isExpanded && fase.descripcion && (
<div style={{ fontSize: '0.72rem', color: 'var(--text-muted)', marginTop: '2px' }}>
{fase.descripcion}
</div>
)}
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem', flexShrink: 0 }}>
{hrsTotal > 0 && (
<span style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>{hrsTotal.toFixed(1)}h</span>
)}
<div style={{
minWidth: '52px', textAlign: 'right', fontWeight: 700, fontSize: '0.85rem',
color: faseProgreso === 100 ? '#22c55e' : faseProgreso > 0 ? '#38bdf8' : 'var(--text-muted)'
}}>{faseProgreso}%</div>
<span style={{ color: 'var(--text-muted)', fontSize: '0.8rem' }}>{isExpanded ? '▾' : '▸'}</span>
</div>
</div>
{/* Detalle expandible */}
{isExpanded && detalle && (
<div style={{
position: 'relative', borderTop: '1px solid var(--border)',
padding: '0.8rem 1rem',
display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: '0.8rem'
}}>
<FaseDetailCol icon="🎯" title="Objetivo" text={detalle.objetivo} />
<FaseDetailCol icon="⚙️" title="Desarrollo" text={detalle.desarrollo} />
<FaseDetailCol icon="✨" title="Logros" text={detalle.logros} />
</div>
)}
</div>
);
})}
</>
)}
{/* ═══════════ VISTA TÉCNICA ═══════════ */}
{vista === 'tecnica' && (
<>
{/* Métricas numéricas */}
<div style={{
display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(130px, 1fr))',
gap: '0.8rem', marginBottom: '1.5rem'
}}>
<MetricCard label="Completados" value={m.hitos_completados} color="#22c55e" />
<MetricCard label="Pendientes" value={m.hitos_pendientes} color="#eab308" />
<MetricCard label="Hrs Presencial" value={`${hrsP.toFixed(1)}h`} color="#f97316" />
<MetricCard label="Hrs Remoto" value={`${hrsR.toFixed(1)}h`} color="#a78bfa" />
<MetricCard label="Total Horas" value={`${totalH.toFixed(1)}h`} color="#38bdf8" />
</div>
{/* Barra progreso */}
<div style={{ marginBottom: '1.5rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem', marginBottom: '0.3rem' }}>
<span style={{ fontWeight: 600 }}>Avance Global</span>
<span style={{ color: '#38bdf8', fontWeight: 600 }}>{progreso}%</span>
</div>
<div style={{
width: '100%', height: '10px', background: 'rgba(255,255,255,0.06)',
borderRadius: '999px', overflow: 'hidden'
}}>
<div style={{
width: `${progreso}%`, height: '100%', borderRadius: '999px',
background: 'linear-gradient(90deg, #38bdf8, #818cf8)', transition: 'width 0.6s ease'
}} />
</div>
</div>
{/* Nodos */}
<SectionTitle title="🖧 Nodos del Proyecto" />
<div style={{
display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))',
gap: '0.7rem', marginBottom: '1.5rem'
}}>
{proyecto.nodos.map(n => (
<div key={n.id} className="card" style={{ padding: '0.8rem' }}>
<div style={{ fontWeight: 700, fontSize: '0.85rem', marginBottom: '0.3rem' }}>{n.nombre}</div>
<div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>
{n.ip && <><code style={codeStyle}>{n.ip}</code><br /></>}
{n.descripcion}
</div>
</div>
))}
</div>
{/* Fases detalladas */}
<SectionTitle title="📋 Fases del Proyecto" />
{proyecto.fases.map(fase => {
const hitos = fase.hitos || [];
const faseCompletos = hitos.filter(h => h.estado === '✅').length;
const faseProgreso = hitos.length > 0 ? Math.round((faseCompletos / hitos.length) * 100) : 0;
const fasHrsP = hitos.reduce((s, h) => s + h.horas_presencial, 0);
const fasHrsR = hitos.reduce((s, h) => s + h.horas_remoto, 0);
const expanded = expandedFases.has(fase.id);
return (
<div key={fase.id} className="card" style={{ marginBottom: '0.8rem', padding: 0, overflow: 'hidden' }}>
<div onClick={() => toggleFase(fase.id)} style={{
padding: '0.8rem 1rem', cursor: 'pointer', display: 'flex',
justifyContent: 'space-between', alignItems: 'center',
borderBottom: expanded ? '1px solid var(--border)' : 'none'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.6rem' }}>
<span style={{ fontSize: '0.7rem', color: '#38bdf8', fontWeight: 700 }}>F{fase.numero}</span>
<span style={{ fontWeight: 600, fontSize: '0.9rem' }}>{fase.nombre}</span>
<span style={{ fontSize: '0.8rem' }}>{estadoIcon(fase.estado)}</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.8rem', fontSize: '0.72rem', color: 'var(--text-muted)' }}>
{fasHrsP > 0 && <span style={{ color: '#f97316' }}>🏢 {fasHrsP.toFixed(1)}h</span>}
{fasHrsR > 0 && <span style={{ color: '#a78bfa' }}>💻 {fasHrsR.toFixed(1)}h</span>}
<span style={{ color: '#38bdf8' }}>{faseProgreso}%</span>
<span>{expanded ? '▾' : '▸'}</span>
</div>
</div>
<div style={{ height: '3px', background: 'rgba(255,255,255,0.03)' }}>
<div style={{
width: `${faseProgreso}%`, height: '100%',
background: fase.estado === '✅' ? '#22c55e' : 'linear-gradient(90deg, #38bdf8, #818cf8)',
transition: 'width 0.4s'
}} />
</div>
{expanded && hitos.length > 0 && (
<div style={{ padding: '0.5rem 1rem 0.8rem' }}>
<table className="table-ifde" style={{ fontSize: '0.8rem' }}>
<thead>
<tr>
<th style={{ width: '55px' }}>ID</th>
<th>Hito</th>
<th style={{ width: '75px' }}>Fecha</th>
<th style={{ width: '60px' }}>🏢</th>
<th style={{ width: '60px' }}>💻</th>
<th style={{ width: '80px' }}>Nodo</th>
<th style={{ width: '35px' }}>E</th>
</tr>
</thead>
<tbody>
{hitos.map(h => (
<tr key={h.id}>
<td><code style={{ fontSize: '0.72rem', color: '#38bdf8' }}>{h.id_hito}</code></td>
<td>
<strong>{h.titulo}</strong>
{h.descripcion && <div style={{ fontSize: '0.72rem', color: 'var(--text-muted)', marginTop: '2px' }}>{h.descripcion}</div>}
</td>
<td style={{ fontSize: '0.72rem', color: 'var(--text-muted)' }}>
{h.fecha ? new Date(h.fecha + 'T12:00:00').toLocaleDateString('es-AR', { day: '2-digit', month: '2-digit' }) : '—'}
</td>
<td style={{ textAlign: 'center', color: h.horas_presencial > 0 ? '#f97316' : 'var(--text-muted)' }}>
{h.horas_presencial > 0 ? `${h.horas_presencial}h` : '—'}
</td>
<td style={{ textAlign: 'center', color: h.horas_remoto > 0 ? '#a78bfa' : 'var(--text-muted)' }}>
{h.horas_remoto > 0 ? `${h.horas_remoto}h` : '—'}
</td>
<td style={{ fontSize: '0.72rem' }}>{h.nodo_nombre || '—'}</td>
<td style={{ textAlign: 'center' }}>{estadoIcon(h.estado)}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
})}
</>
)}
<div style={{ textAlign: 'center', color: 'var(--text-muted)', fontSize: '0.7rem', marginTop: '1.5rem', paddingTop: '0.8rem', borderTop: '1px solid var(--border)' }}>
P2601 Infraestructura DASUTEN · UTN FRLR · Dashboard integrado en dtic-BITACORAs
</div>
</div>
);
}
/* ═══ Estilos compartidos ═══ */
const codeStyle: React.CSSProperties = {
background: 'rgba(255,255,255,0.06)', padding: '1px 4px', borderRadius: '3px', fontSize: '0.72rem'
};
const subheadStyle: React.CSSProperties = {
fontSize: '0.75rem', fontWeight: 600, marginBottom: '0.4rem', color: '#38bdf8'
};
const listStyle: React.CSSProperties = {
margin: 0, paddingLeft: '1.1rem', fontSize: '0.78rem', color: 'var(--text-muted)', lineHeight: 1.7
};
/* ═══ Sub-componentes ═══ */
function MetricCard({ label, value, color }: { label: string; value: string; color: string }) {
return (
<div className="card" style={{ padding: '0.9rem', textAlign: 'center' }}>
<div style={{ fontSize: '1.5rem', fontWeight: 700, color, lineHeight: 1 }}>{value}</div>
<div style={{ fontSize: '0.68rem', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', marginTop: '0.3rem' }}>{label}</div>
</div>
);
}
function SectionTitle({ title }: { title: string }) {
return (
<div style={{
fontSize: '0.78rem', textTransform: 'uppercase', letterSpacing: '0.06em',
color: 'var(--text-muted)', fontWeight: 600, marginBottom: '0.8rem',
paddingBottom: '0.4rem', borderBottom: '1px solid var(--border)'
}}>{title}</div>
);
}
function FaseDetailCol({ icon, title, text }: { icon: string; title: string; text: string }) {
return (
<div>
<div style={{ fontSize: '0.75rem', fontWeight: 600, color: '#38bdf8', marginBottom: '0.3rem' }}>
{icon} {title}
</div>
<div style={{ fontSize: '0.76rem', color: 'var(--text-muted)', lineHeight: 1.5 }}>
{text}
</div>
</div>
);
}
+16 -2
View File
@@ -3,6 +3,20 @@ import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: { host: true, port: 5173 },
define: { 'import.meta.env.VERSION': JSON.stringify(require('./package.json').version) }
base: '/bitacoras/',
server: {
host: true,
port: 5173,
strictPort: true,
cors: true,
allowedHosts: ['ns8.frlr.utn.edu.ar', 'localhost'],
proxy: {
'/bitacoras/api': {
target: 'http://api:3001',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/bitacoras\/api/, '/api')
}
}
},
define: { 'import.meta.env.VERSION': JSON.stringify('1.0.0') }
});
+158
View File
@@ -0,0 +1,158 @@
{
"name": "tools",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"pg": "^8.19.0"
}
},
"node_modules/pg": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.19.0.tgz",
"integrity": "sha512-QIcLGi508BAHkQ3pJNptsFz5WQMlpGbuBGBaIaXsWK8mel2kQ/rThYI+DbgjUvZrIr7MiuEuc9LcChJoEZK1xQ==",
"license": "MIT",
"dependencies": {
"pg-connection-string": "^2.11.0",
"pg-pool": "^3.12.0",
"pg-protocol": "^1.12.0",
"pg-types": "2.2.0",
"pgpass": "1.0.5"
},
"engines": {
"node": ">= 16.0.0"
},
"optionalDependencies": {
"pg-cloudflare": "^1.3.0"
},
"peerDependencies": {
"pg-native": ">=3.0.1"
},
"peerDependenciesMeta": {
"pg-native": {
"optional": true
}
}
},
"node_modules/pg-cloudflare": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
"integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
"license": "MIT",
"optional": true
},
"node_modules/pg-connection-string": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz",
"integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==",
"license": "MIT"
},
"node_modules/pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"license": "ISC",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/pg-pool": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.12.0.tgz",
"integrity": "sha512-eIJ0DES8BLaziFHW7VgJEBPi5hg3Nyng5iKpYtj3wbcAUV9A1wLgWiY7ajf/f/oO1wfxt83phXPY8Emztg7ITg==",
"license": "MIT",
"peerDependencies": {
"pg": ">=8.0"
}
},
"node_modules/pg-protocol": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.12.0.tgz",
"integrity": "sha512-uOANXNRACNdElMXJ0tPz6RBM0XQ61nONGAwlt8da5zs/iUOOCLBQOHSXnrC6fMsvtjxbOJrZZl5IScGv+7mpbg==",
"license": "MIT"
},
"node_modules/pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"license": "MIT",
"dependencies": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/pgpass": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"license": "MIT",
"dependencies": {
"split2": "^4.1.0"
}
},
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/postgres-bytea": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
"integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"license": "MIT",
"dependencies": {
"xtend": "^4.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": {
"node": ">=0.4"
}
}
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"dependencies": {
"pg": "^8.19.0"
}
}
+94
View File
@@ -0,0 +1,94 @@
const fs = require('fs');
const { Pool } = require('pg');
const pool = new Pool({
connectionString: "postgres://dtic_user:dtic_pass_2026@localhost:5433/dtic_bitacoras"
});
async function syncFile(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
const dateStr = filePath.match(/(\d{4}-\d{2}-\d{2})\.md/)[1];
console.log(`Sincronizando bitácora: ${dateStr}`);
// Asegurar bitácora
const bResult = await pool.query(
'INSERT INTO bitacoras.bitacoras (fecha) VALUES ($1) ON CONFLICT (fecha) DO UPDATE SET fecha = EXCLUDED.fecha RETURNING id',
[dateStr]
);
const bitacoraId = bResult.rows[0].id;
// Obtener mapeo de nodos
const { rows: nodos } = await pool.query('SELECT id, nombre FROM bitacoras.nodos');
const nodoMap = {};
nodos.forEach(n => { nodoMap[n.nombre] = n.id; });
// Limpiar entradas previas
await pool.query('DELETE FROM bitacoras.entradas WHERE bitacora_id = $1', [bitacoraId]);
let currentNodoId = null;
let inDetailedActivities = false;
let entriesFound = 0;
for (let line of lines) {
// Detectar inicio de sección de actividades detalladas
if (line.includes('Actividades Detalladas')) {
inDetailedActivities = true;
continue;
}
if (!inDetailedActivities) continue;
// Detectar cambio de nodo
if (line.startsWith('### ')) {
const nodoName = line.replace('### ', '').trim().split(' ')[0];
currentNodoId = nodoMap[nodoName] || null;
if (currentNodoId) console.log(`Cambiando a nodo: ${nodoName} (ID: ${currentNodoId})`);
continue;
}
if (!currentNodoId) continue;
// Detectar entrada de tabla
const regex = /\|\s*(\d{2}:\d{2})\s*\|\s*(\d{2}:\d{2})\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|/;
const match = line.match(regex);
if (match) {
let inicio = match[1];
let fin = match[2];
let descRaw = match[3];
let estadoRaw = match[4];
const esIA = descRaw.includes('(IA)');
let descripcion = descRaw.replace('(IA)', '').replace(/\*\*/g, '').replace(/__/g, '').trim();
// A veces el MD tiene ** al principio
if (descripcion.startsWith('**')) descripcion = descripcion.substring(2);
const statusMatch = estadoRaw.match(/\[([A-Z])\] (.*)/);
const modo = statusMatch ? statusMatch[1] : 'P';
const estado = statusMatch ? statusMatch[2] : estadoRaw;
await pool.query(
`INSERT INTO bitacoras.entradas (inicio, fin, descripcion, estado, modo, es_ia, bitacora_id, nodo_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
[inicio, fin, descripcion, estado, modo, esIA, bitacoraId, currentNodoId]
);
entriesFound++;
}
}
console.log(`Sincronización finalizada. Entradas cargadas: ${entriesFound}`);
await pool.end();
}
const file = process.argv[2];
if (!file) {
console.error('Uso: node sync_bitacora.js <path_to_md>');
process.exit(1);
}
syncFile(file).catch(err => {
console.error(err);
process.exit(1);
});
@@ -0,0 +1,77 @@
const { Client } = require('pg');
const client = new Client({
user: 'dtic_user',
host: '127.0.0.1',
database: 'dtic_bitacoras',
password: 'dtic_pass_2026',
port: 5433,
});
async function run() {
await client.connect();
console.log("Connected to DB");
const codigo = 'P2601';
// Get Project ID
const pRes = await client.query("SELECT id FROM bitacoras.proyectos WHERE codigo = $1", [codigo]);
if (pRes.rowCount === 0) {
console.error("Proyecto no encontrado");
process.exit(1);
}
const pid = pRes.rows[0].id;
// Fases IDs
const fRes = await client.query("SELECT id, numero FROM bitacoras.fases WHERE proyecto_id = $1", [pid]);
const fases = {};
fRes.rows.forEach(f => fases[f.numero] = f.id);
// Nodos IDs
const nRes = await client.query("SELECT id, nombre FROM bitacoras.nodos");
const nodos = {};
nRes.rows.forEach(n => nodos[n.nombre] = n.id);
// Updates a realizar según el HTML
const hitos = [
{ fase: 5, id_hito: 'P02.APP', titulo: 'Despliegue del Sistema DASUTEN', desc: 'Transferencia SCP, extracción, configuración de Kermet.ini → SERVER=sql-dasuten, instalación de componentes Kermet y fuentes TrueType. Login exitoso con usuario aalmiron.', est: '✅', fec: '2026-03-05', h_p: 1, h_r: 2, nodo: 'pcv-dasu0' },
{ fase: 5, id_hito: 'NET01', titulo: 'Tailscale VPN en srv-dasu', desc: 'Instalación de Tailscale v1.94.2 en el hipervisor. Configurado como subnet router para 10.0.100.0/24. IP Tailscale: 100.116.210.36. Acceso remoto exterior garantizado.', est: '✅', fec: '2026-03-05', h_p: 0, h_r: 0.5, nodo: 'srv-dasu' },
{ fase: 5, id_hito: 'TEST03', titulo: 'Conectividad SQL desde Cliente', desc: 'Apertura de firewall TCP 1433 en sql-dasuten. Test-NetConnection confirmó TcpTestSucceeded: True. Conexión ODBC validada vía login al sistema.', est: '✅', fec: '2026-03-05', h_p: 0, h_r: 1, nodo: 'pcv-dasu0' },
{ fase: 6, id_hito: 'UAT', titulo: 'Verificación Presencial por Usuario', desc: 'Validación funcional del sistema por Andrea Almirón in-situ. Se confirmó la operatividad completa de los módulos y generación de reportes correctos.', est: '✅', fec: '2026-03-05', h_p: 1, h_r: 0, nodo: 'pc-dasu0' },
{ fase: 6, id_hito: 'FÍSICO', titulo: 'Instalación Física de srv-dasu', desc: 'Traslado e instalación definitiva del servidor físico Proxmox en los gabinetes / racks de la oficina DASUTeN.', est: '⏳', fec: null, h_p: 0, h_r: 0, nodo: 'srv-dasu' },
{ fase: 6, id_hito: 'ADDOM', titulo: 'Unión de Cliente pc-dasu0 al Dominio', desc: 'Unir la PC física del cliente local al directorio activo (dc-dasuten) y configurar DNS primarios apuntando al controlador 10.0.100.10.', est: '⏳', fec: null, h_p: 0, h_r: 0, nodo: 'pc-dasu0' },
{ fase: 6, id_hito: 'GPO', titulo: 'Políticas de Seguridad Local', desc: 'Configuración de GPOs de dominio para restringir acceso de usuarios regulares, perfiles locales, mapeo de unidades y carpetas del sistema DASUTEN.', est: '⏳', fec: null, h_p: 0, h_r: 0, nodo: 'dc-dasuten' }
];
// Primero marcamos P02.DB, P02.APP viejos u otros como completados/borrados si es necesario
await client.query("UPDATE bitacoras.hitos SET estado = '✅', fecha = '2026-03-04' WHERE id_hito = 'P02.DB'");
await client.query("DELETE FROM bitacoras.hitos WHERE id_hito IN ('MIG', 'P02.APP')");
for (const h of hitos) {
if (!fases[h.fase]) {
console.log(`Fase ${h.fase} no existe, creándola...`);
const faseI = await client.query("INSERT INTO bitacoras.fases (proyecto_id, numero, titulo) VALUES ($1, $2, $3) RETURNING id", [pid, h.fase, `Fase ${h.fase}`]);
fases[h.fase] = faseI.rows[0].id;
}
const nodo_id = nodos[h.nodo] || null;
// Verificar si existe el hito
const exRes = await client.query("SELECT id FROM bitacoras.hitos WHERE id_hito = $1 AND fase_id = $2", [h.id_hito, fases[h.fase]]);
if (exRes.rowCount > 0) {
console.log(`Updating ${h.id_hito}...`);
await client.query(`UPDATE bitacoras.hitos SET titulo=$1, descripcion=$2, estado=$3, fecha=$4, horas_presencial=$5, horas_remoto=$6, nodo_id=$7 WHERE id=$8`,
[h.titulo, h.desc, h.est, h.fec, h.h_p, h.h_r, nodo_id, exRes.rows[0].id]);
} else {
console.log(`Inserting ${h.id_hito}...`);
await client.query(`INSERT INTO bitacoras.hitos (fase_id, id_hito, titulo, descripcion, estado, fecha, horas_presencial, horas_remoto, nodo_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
[fases[h.fase], h.id_hito, h.titulo, h.desc, h.est, h.fec, h.h_p, h.h_r, nodo_id]);
}
}
console.log("Done");
await client.end();
}
run().catch(console.error);
+16
View File
@@ -0,0 +1,16 @@
const section = `srv-ns8
#### ⏳ - SINC01 - Inicio de Jornada Presencial
🚀 Inicio de jornada presencial (08:00 - 13:00). Sincronización de contexto y tareas administrativas.
| I | F | Descripción | E |
| :--- | :--- | :--- | :--- |
| 12:35 | 12:50 | (IA) **P2603 - Sistema de Bitácoras**: Creación completa de \`dtic-BITACORAs/\`. Backend API REST (3 rutas CRUD), esquema SQL I-F-D-E, frontend React 18 dark mode, Docker Compose. 3 contenedores operativos. | [P] ✅ |
| 11:15 | 11:37 | (IA) **Categorización de Nodos**: Separación de nodos exclusivos vs. soporte en P2601 y P2602. ADN 07 actualizado con el nuevo estándar. | [P] ✅ |`;
const regex = /\|\s*(\d{2}:\d{2})\s*\|\s*(\d{2}:\d{2})\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|/g;
const matches = [...section.matchAll(regex)];
console.log(`Encontradas: ${matches.length}`);
matches.forEach((m, i) => {
console.log(`Match ${i}: ${m[1]} - ${m[2]} - ${m[3].substring(0, 20)}...`);
});
+1 -1
View File
@@ -38,7 +38,7 @@ Esta VM replica la configuración prevista para la PC física [`pc-dasu0`](pc-da
- **Fecha de Unión**: 27/02/2026
## Servicios
- **OpenSSH Server**: Puerto 22 (inicio automático)
- **OpenSSH Server**: Puerto 7022 (inicio automático, hardeado)
## Dependencias
- Servidor dependiente del host físico `srv-dasu`.
+31 -22
View File
@@ -80,31 +80,40 @@ Actualmente, el sistema de gestión administrativa de DASUTeN funciona dentro de
| — | MIG | 📍 | Migración de datos del sistema actual a la nueva infraestructura. |
| — | CLI | 📍 | Configuración final de `pc-dasu0` como cliente del dominio `dasuten.utnlr`. |
## 🌐 Topología y Contexto de Red
**Regla de Acceso (Agentes e IA):** Todo el ecosistema central (dc-dasuten, sql-dasuten, pcv-dasu0) transcurre en la **subred aislada `10.0.100.0/24`**. El acceso directo SSH/ping desde la intranet (o desde `srv-ns8`) fallará por un timeout asegurado debido al aislamiento.
**Vía Tailscale (Nuevo Estándar):** El hipervisor `srv-dasu` (`10.0.10.205`) tiene instalado Tailscale y está configurado como **Subnet Router** para la red `10.0.100.0/24`. Desde cualquier nodo o IA en la Tailnet (ej. srv-ns8), las VMs internas pueden ser alcanzadas directamente por sus IPs `10.0.100.x` a través de la VPN sin necesidad de ProxyJump.
**Vía SSH Clásico:** En caso de no contar con Tailscale, es imperativo configurar un salto (ProxyJump) a través de `10.0.10.205`.
## Arquitectura de Red
```
┌─────────────────────────────────────────┐
│ Red Facultad (10.0.10.x) │
│ │
│ srv-ns8 (.8) srv-dasu (.205) │
│ [Dashboard] [Proxmox VE]
│ NAT/MASQUERADE
┌──────────────┐
│ 10.0.100.x
│ (Aislada)
│ │
│ dc-dasuten │ │
│ (.10) [AD DS]│
│ │
│ sql-dasuten │
│ (.11) [SQL] │
│ │
│ pcv-dasu0 │ pc-dasu0
│ (.12) [Test] │ [Cliente]
└──────────────┘
└─────────────────────────────────────────┘
┌────────────────────────────────────────────────────────
│ Red Facultad (10.0.10.x)
│ srv-ns8 (.8) srv-dasu (.205)
│ [Tailscale VPN] [Tailscale Subnet Router]
└─────────────────────────┘
│ NAT/VPN Routing
┌──────────────┐
│ 10.0.100.x │
│ │ (Aislada) │
│ dc-dasuten │
│ │ (.10) [AD DS]│
│ │
│ sql-dasuten │
│ │ (.11) [SQL] │
│ │
│ pcv-dasu0 │
│ (.12) [Test] │ pc-dasu0
│ └──────────────┘ [Cliente] │
└────────────────────────────────────────────────────────┘
```
## Referencias
-14
View File
@@ -1,14 +0,0 @@
services:
apps:
image: tinyfilemanager/tinyfilemanager:master
container_name: srvv-apps
restart: always
ports:
- "8082:80"
volumes:
- /mnt/ns8Disco2/DTIC_APPs/:/var/www/html/data
- ./index.php:/var/www/html/index.php
- ./translation.json:/var/www/html/translation.json
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
# Persist config if needed, or mount specific config.php
# - ./config.php:/var/www/html/config.php
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
-5
View File
@@ -1,5 +0,0 @@
upload_max_filesize = 10G
post_max_size = 10G
memory_limit = 1024M
max_execution_time = 3600
max_input_time = 3600
@@ -1 +0,0 @@
../baseline-browser-mapping/dist/cli.js
-1
View File
@@ -1 +0,0 @@
../browserslist/cli.js
-1
View File
@@ -1 +0,0 @@
../create-jest/bin/create-jest.js
-1
View File
@@ -1 +0,0 @@
../esprima/bin/esparse.js
-1
View File
@@ -1 +0,0 @@
../esprima/bin/esvalidate.js
-1
View File
@@ -1 +0,0 @@
../import-local/fixtures/cli.js
-1
View File
@@ -1 +0,0 @@
../jest/bin/jest.js
-1
View File
@@ -1 +0,0 @@
../js-yaml/bin/js-yaml.js
-1
View File
@@ -1 +0,0 @@
../jsesc/bin/jsesc
-1
View File
@@ -1 +0,0 @@
../json5/lib/cli.js
-1
View File
@@ -1 +0,0 @@
../mime/cli.js
-1
View File
@@ -1 +0,0 @@
../mkdirp/bin/cmd.js
-1
View File
@@ -1 +0,0 @@
../which/bin/node-which
-1
View File
@@ -1 +0,0 @@
../nodemon/bin/nodemon.js
-1
View File
@@ -1 +0,0 @@
../touch/bin/nodetouch.js
-1
View File
@@ -1 +0,0 @@
../@babel/parser/bin/babel-parser.js
-1
View File
@@ -1 +0,0 @@
../resolve/bin/resolve
-1
View File
@@ -1 +0,0 @@
../semver/bin/semver.js
-1
View File
@@ -1 +0,0 @@
../update-browserslist-db/cli.js
File diff suppressed because it is too large Load Diff
-22
View File
@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-19
View File
@@ -1,19 +0,0 @@
# @babel/code-frame
> Generate errors that contain a code frame that point to source locations.
See our website [@babel/code-frame](https://babeljs.io/docs/babel-code-frame) for more information.
## Install
Using npm:
```sh
npm install --save-dev @babel/code-frame
```
or using yarn:
```sh
yarn add @babel/code-frame --dev
```
-216
View File
@@ -1,216 +0,0 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var picocolors = require('picocolors');
var jsTokens = require('js-tokens');
var helperValidatorIdentifier = require('@babel/helper-validator-identifier');
function isColorSupported() {
return (typeof process === "object" && (process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false") ? false : picocolors.isColorSupported
);
}
const compose = (f, g) => v => f(g(v));
function buildDefs(colors) {
return {
keyword: colors.cyan,
capitalized: colors.yellow,
jsxIdentifier: colors.yellow,
punctuator: colors.yellow,
number: colors.magenta,
string: colors.green,
regex: colors.magenta,
comment: colors.gray,
invalid: compose(compose(colors.white, colors.bgRed), colors.bold),
gutter: colors.gray,
marker: compose(colors.red, colors.bold),
message: compose(colors.red, colors.bold),
reset: colors.reset
};
}
const defsOn = buildDefs(picocolors.createColors(true));
const defsOff = buildDefs(picocolors.createColors(false));
function getDefs(enabled) {
return enabled ? defsOn : defsOff;
}
const sometimesKeywords = new Set(["as", "async", "from", "get", "of", "set"]);
const NEWLINE$1 = /\r\n|[\n\r\u2028\u2029]/;
const BRACKET = /^[()[\]{}]$/;
let tokenize;
{
const JSX_TAG = /^[a-z][\w-]*$/i;
const getTokenType = function (token, offset, text) {
if (token.type === "name") {
if (helperValidatorIdentifier.isKeyword(token.value) || helperValidatorIdentifier.isStrictReservedWord(token.value, true) || sometimesKeywords.has(token.value)) {
return "keyword";
}
if (JSX_TAG.test(token.value) && (text[offset - 1] === "<" || text.slice(offset - 2, offset) === "</")) {
return "jsxIdentifier";
}
if (token.value[0] !== token.value[0].toLowerCase()) {
return "capitalized";
}
}
if (token.type === "punctuator" && BRACKET.test(token.value)) {
return "bracket";
}
if (token.type === "invalid" && (token.value === "@" || token.value === "#")) {
return "punctuator";
}
return token.type;
};
tokenize = function* (text) {
let match;
while (match = jsTokens.default.exec(text)) {
const token = jsTokens.matchToToken(match);
yield {
type: getTokenType(token, match.index, text),
value: token.value
};
}
};
}
function highlight(text) {
if (text === "") return "";
const defs = getDefs(true);
let highlighted = "";
for (const {
type,
value
} of tokenize(text)) {
if (type in defs) {
highlighted += value.split(NEWLINE$1).map(str => defs[type](str)).join("\n");
} else {
highlighted += value;
}
}
return highlighted;
}
let deprecationWarningShown = false;
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
function getMarkerLines(loc, source, opts) {
const startLoc = Object.assign({
column: 0,
line: -1
}, loc.start);
const endLoc = Object.assign({}, startLoc, loc.end);
const {
linesAbove = 2,
linesBelow = 3
} = opts || {};
const startLine = startLoc.line;
const startColumn = startLoc.column;
const endLine = endLoc.line;
const endColumn = endLoc.column;
let start = Math.max(startLine - (linesAbove + 1), 0);
let end = Math.min(source.length, endLine + linesBelow);
if (startLine === -1) {
start = 0;
}
if (endLine === -1) {
end = source.length;
}
const lineDiff = endLine - startLine;
const markerLines = {};
if (lineDiff) {
for (let i = 0; i <= lineDiff; i++) {
const lineNumber = i + startLine;
if (!startColumn) {
markerLines[lineNumber] = true;
} else if (i === 0) {
const sourceLength = source[lineNumber - 1].length;
markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1];
} else if (i === lineDiff) {
markerLines[lineNumber] = [0, endColumn];
} else {
const sourceLength = source[lineNumber - i].length;
markerLines[lineNumber] = [0, sourceLength];
}
}
} else {
if (startColumn === endColumn) {
if (startColumn) {
markerLines[startLine] = [startColumn, 0];
} else {
markerLines[startLine] = true;
}
} else {
markerLines[startLine] = [startColumn, endColumn - startColumn];
}
}
return {
start,
end,
markerLines
};
}
function codeFrameColumns(rawLines, loc, opts = {}) {
const shouldHighlight = opts.forceColor || isColorSupported() && opts.highlightCode;
const defs = getDefs(shouldHighlight);
const lines = rawLines.split(NEWLINE);
const {
start,
end,
markerLines
} = getMarkerLines(loc, lines, opts);
const hasColumns = loc.start && typeof loc.start.column === "number";
const numberMaxWidth = String(end).length;
const highlightedLines = shouldHighlight ? highlight(rawLines) : rawLines;
let frame = highlightedLines.split(NEWLINE, end).slice(start, end).map((line, index) => {
const number = start + 1 + index;
const paddedNumber = ` ${number}`.slice(-numberMaxWidth);
const gutter = ` ${paddedNumber} |`;
const hasMarker = markerLines[number];
const lastMarkerLine = !markerLines[number + 1];
if (hasMarker) {
let markerLine = "";
if (Array.isArray(hasMarker)) {
const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
const numberOfMarkers = hasMarker[1] || 1;
markerLine = ["\n ", defs.gutter(gutter.replace(/\d/g, " ")), " ", markerSpacing, defs.marker("^").repeat(numberOfMarkers)].join("");
if (lastMarkerLine && opts.message) {
markerLine += " " + defs.message(opts.message);
}
}
return [defs.marker(">"), defs.gutter(gutter), line.length > 0 ? ` ${line}` : "", markerLine].join("");
} else {
return ` ${defs.gutter(gutter)}${line.length > 0 ? ` ${line}` : ""}`;
}
}).join("\n");
if (opts.message && !hasColumns) {
frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`;
}
if (shouldHighlight) {
return defs.reset(frame);
} else {
return frame;
}
}
function index (rawLines, lineNumber, colNumber, opts = {}) {
if (!deprecationWarningShown) {
deprecationWarningShown = true;
const message = "Passing lineNumber and colNumber is deprecated to @babel/code-frame. Please use `codeFrameColumns`.";
if (process.emitWarning) {
process.emitWarning(message, "DeprecationWarning");
} else {
const deprecationError = new Error(message);
deprecationError.name = "DeprecationWarning";
console.warn(new Error(message));
}
}
colNumber = Math.max(colNumber, 0);
const location = {
start: {
column: colNumber,
line: lineNumber
}
};
return codeFrameColumns(rawLines, location, opts);
}
exports.codeFrameColumns = codeFrameColumns;
exports.default = index;
exports.highlight = highlight;
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
@@ -1,31 +0,0 @@
{
"name": "@babel/code-frame",
"version": "7.27.1",
"description": "Generate errors that contain a code frame that point to source locations.",
"author": "The Babel Team (https://babel.dev/team)",
"homepage": "https://babel.dev/docs/en/next/babel-code-frame",
"bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-code-frame"
},
"main": "./lib/index.js",
"dependencies": {
"@babel/helper-validator-identifier": "^7.27.1",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"devDependencies": {
"import-meta-resolve": "^4.1.0",
"strip-ansi": "^4.0.0"
},
"engines": {
"node": ">=6.9.0"
},
"type": "commonjs"
}
-22
View File
@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-19
View File
@@ -1,19 +0,0 @@
# @babel/compat-data
> The compat-data to determine required Babel plugins
See our website [@babel/compat-data](https://babeljs.io/docs/babel-compat-data) for more information.
## Install
Using npm:
```sh
npm install --save @babel/compat-data
```
or using yarn:
```sh
yarn add @babel/compat-data
```
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file as Babel 8 drop support of core-js 2
module.exports = require("./data/corejs2-built-ins.json");
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file now that it is included in babel-plugin-polyfill-corejs3
module.exports = require("./data/corejs3-shipped-proposals.json");
File diff suppressed because it is too large Load Diff
@@ -1,5 +0,0 @@
[
"esnext.promise.all-settled",
"esnext.string.match-all",
"esnext.global-this"
]
@@ -1,18 +0,0 @@
{
"es6.module": {
"chrome": "61",
"and_chr": "61",
"edge": "16",
"firefox": "60",
"and_ff": "60",
"node": "13.2.0",
"opera": "48",
"op_mob": "45",
"safari": "10.1",
"ios": "10.3",
"samsung": "8.2",
"android": "61",
"electron": "2.0",
"ios_saf": "10.3"
}
}
@@ -1,35 +0,0 @@
{
"transform-async-to-generator": [
"bugfix/transform-async-arrows-in-class"
],
"transform-parameters": [
"bugfix/transform-edge-default-parameters",
"bugfix/transform-safari-id-destructuring-collision-in-function-expression"
],
"transform-function-name": [
"bugfix/transform-edge-function-name"
],
"transform-block-scoping": [
"bugfix/transform-safari-block-shadowing",
"bugfix/transform-safari-for-shadowing"
],
"transform-template-literals": [
"bugfix/transform-tagged-template-caching"
],
"transform-optional-chaining": [
"bugfix/transform-v8-spread-parameters-in-optional-chaining"
],
"proposal-optional-chaining": [
"bugfix/transform-v8-spread-parameters-in-optional-chaining"
],
"transform-class-properties": [
"bugfix/transform-v8-static-class-fields-redefine-readonly",
"bugfix/transform-firefox-class-in-computed-class-key",
"bugfix/transform-safari-class-field-initializer-scope"
],
"proposal-class-properties": [
"bugfix/transform-v8-static-class-fields-redefine-readonly",
"bugfix/transform-firefox-class-in-computed-class-key",
"bugfix/transform-safari-class-field-initializer-scope"
]
}
@@ -1,203 +0,0 @@
{
"bugfix/transform-async-arrows-in-class": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "11",
"node": "7.6",
"deno": "1",
"ios": "11",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"bugfix/transform-edge-default-parameters": {
"chrome": "49",
"opera": "36",
"edge": "18",
"firefox": "52",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-edge-function-name": {
"chrome": "51",
"opera": "38",
"edge": "79",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"bugfix/transform-safari-block-shadowing": {
"chrome": "49",
"opera": "36",
"edge": "12",
"firefox": "44",
"safari": "11",
"node": "6",
"deno": "1",
"ie": "11",
"ios": "11",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-safari-for-shadowing": {
"chrome": "49",
"opera": "36",
"edge": "12",
"firefox": "4",
"safari": "11",
"node": "6",
"deno": "1",
"ie": "11",
"ios": "11",
"samsung": "5",
"rhino": "1.7.13",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-safari-id-destructuring-collision-in-function-expression": {
"chrome": "49",
"opera": "36",
"edge": "14",
"firefox": "2",
"safari": "16.3",
"node": "6",
"deno": "1",
"ios": "16.3",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-tagged-template-caching": {
"chrome": "41",
"opera": "28",
"edge": "12",
"firefox": "34",
"safari": "13",
"node": "4",
"deno": "1",
"ios": "13",
"samsung": "3.4",
"rhino": "1.7.14",
"opera_mobile": "28",
"electron": "0.21"
},
"bugfix/transform-v8-spread-parameters-in-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-optional-chaining": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "74",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"proposal-optional-chaining": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "74",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"transform-parameters": {
"chrome": "49",
"opera": "36",
"edge": "15",
"firefox": "52",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-async-to-generator": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "10.1",
"node": "7.6",
"deno": "1",
"ios": "10.3",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"transform-template-literals": {
"chrome": "41",
"opera": "28",
"edge": "13",
"firefox": "34",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "3.4",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-function-name": {
"chrome": "51",
"opera": "38",
"edge": "14",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-block-scoping": {
"chrome": "50",
"opera": "37",
"edge": "14",
"firefox": "53",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
}
}
@@ -1,838 +0,0 @@
{
"transform-explicit-resource-management": {
"chrome": "134",
"edge": "134",
"firefox": "141",
"node": "24",
"electron": "35.0"
},
"transform-duplicate-named-capturing-groups-regex": {
"chrome": "126",
"opera": "112",
"edge": "126",
"firefox": "129",
"safari": "17.4",
"node": "23",
"ios": "17.4",
"electron": "31.0"
},
"transform-regexp-modifiers": {
"chrome": "125",
"opera": "111",
"edge": "125",
"firefox": "132",
"node": "23",
"samsung": "27",
"electron": "31.0"
},
"transform-unicode-sets-regex": {
"chrome": "112",
"opera": "98",
"edge": "112",
"firefox": "116",
"safari": "17",
"node": "20",
"deno": "1.32",
"ios": "17",
"samsung": "23",
"opera_mobile": "75",
"electron": "24.0"
},
"bugfix/transform-v8-static-class-fields-redefine-readonly": {
"chrome": "98",
"opera": "84",
"edge": "98",
"firefox": "75",
"safari": "15",
"node": "12",
"deno": "1.18",
"ios": "15",
"samsung": "11",
"opera_mobile": "52",
"electron": "17.0"
},
"bugfix/transform-firefox-class-in-computed-class-key": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "126",
"safari": "16",
"node": "12",
"deno": "1",
"ios": "16",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"bugfix/transform-safari-class-field-initializer-scope": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "69",
"safari": "16",
"node": "12",
"deno": "1",
"ios": "16",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"transform-class-static-block": {
"chrome": "94",
"opera": "80",
"edge": "94",
"firefox": "93",
"safari": "16.4",
"node": "16.11",
"deno": "1.14",
"ios": "16.4",
"samsung": "17",
"opera_mobile": "66",
"electron": "15.0"
},
"proposal-class-static-block": {
"chrome": "94",
"opera": "80",
"edge": "94",
"firefox": "93",
"safari": "16.4",
"node": "16.11",
"deno": "1.14",
"ios": "16.4",
"samsung": "17",
"opera_mobile": "66",
"electron": "15.0"
},
"transform-private-property-in-object": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "90",
"safari": "15",
"node": "16.9",
"deno": "1.9",
"ios": "15",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"proposal-private-property-in-object": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "90",
"safari": "15",
"node": "16.9",
"deno": "1.9",
"ios": "15",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-class-properties": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "90",
"safari": "14.1",
"node": "12",
"deno": "1",
"ios": "14.5",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"proposal-class-properties": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "90",
"safari": "14.1",
"node": "12",
"deno": "1",
"ios": "14.5",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"transform-private-methods": {
"chrome": "84",
"opera": "70",
"edge": "84",
"firefox": "90",
"safari": "15",
"node": "14.6",
"deno": "1",
"ios": "15",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"proposal-private-methods": {
"chrome": "84",
"opera": "70",
"edge": "84",
"firefox": "90",
"safari": "15",
"node": "14.6",
"deno": "1",
"ios": "15",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"transform-numeric-separator": {
"chrome": "75",
"opera": "62",
"edge": "79",
"firefox": "70",
"safari": "13",
"node": "12.5",
"deno": "1",
"ios": "13",
"samsung": "11",
"rhino": "1.7.14",
"opera_mobile": "54",
"electron": "6.0"
},
"proposal-numeric-separator": {
"chrome": "75",
"opera": "62",
"edge": "79",
"firefox": "70",
"safari": "13",
"node": "12.5",
"deno": "1",
"ios": "13",
"samsung": "11",
"rhino": "1.7.14",
"opera_mobile": "54",
"electron": "6.0"
},
"transform-logical-assignment-operators": {
"chrome": "85",
"opera": "71",
"edge": "85",
"firefox": "79",
"safari": "14",
"node": "15",
"deno": "1.2",
"ios": "14",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"proposal-logical-assignment-operators": {
"chrome": "85",
"opera": "71",
"edge": "85",
"firefox": "79",
"safari": "14",
"node": "15",
"deno": "1.2",
"ios": "14",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"transform-nullish-coalescing-operator": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "72",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"proposal-nullish-coalescing-operator": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "72",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"transform-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"proposal-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-json-strings": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "62",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "9",
"rhino": "1.7.14",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-json-strings": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "62",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "9",
"rhino": "1.7.14",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-optional-catch-binding": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "58",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-optional-catch-binding": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "58",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-parameters": {
"chrome": "49",
"opera": "36",
"edge": "18",
"firefox": "52",
"safari": "16.3",
"node": "6",
"deno": "1",
"ios": "16.3",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-async-generator-functions": {
"chrome": "63",
"opera": "50",
"edge": "79",
"firefox": "57",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "8",
"opera_mobile": "46",
"electron": "3.0"
},
"proposal-async-generator-functions": {
"chrome": "63",
"opera": "50",
"edge": "79",
"firefox": "57",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "8",
"opera_mobile": "46",
"electron": "3.0"
},
"transform-object-rest-spread": {
"chrome": "60",
"opera": "47",
"edge": "79",
"firefox": "55",
"safari": "11.1",
"node": "8.3",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"opera_mobile": "44",
"electron": "2.0"
},
"proposal-object-rest-spread": {
"chrome": "60",
"opera": "47",
"edge": "79",
"firefox": "55",
"safari": "11.1",
"node": "8.3",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"opera_mobile": "44",
"electron": "2.0"
},
"transform-dotall-regex": {
"chrome": "62",
"opera": "49",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "8.10",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"rhino": "1.7.15",
"opera_mobile": "46",
"electron": "3.0"
},
"transform-unicode-property-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-unicode-property-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-named-capturing-groups-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-async-to-generator": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "11",
"node": "7.6",
"deno": "1",
"ios": "11",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"transform-exponentiation-operator": {
"chrome": "52",
"opera": "39",
"edge": "14",
"firefox": "52",
"safari": "10.1",
"node": "7",
"deno": "1",
"ios": "10.3",
"samsung": "6",
"rhino": "1.7.14",
"opera_mobile": "41",
"electron": "1.3"
},
"transform-template-literals": {
"chrome": "41",
"opera": "28",
"edge": "13",
"firefox": "34",
"safari": "13",
"node": "4",
"deno": "1",
"ios": "13",
"samsung": "3.4",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-literals": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "53",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.15",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-function-name": {
"chrome": "51",
"opera": "38",
"edge": "79",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-arrow-functions": {
"chrome": "47",
"opera": "34",
"edge": "13",
"firefox": "43",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"rhino": "1.7.13",
"opera_mobile": "34",
"electron": "0.36"
},
"transform-block-scoped-functions": {
"chrome": "41",
"opera": "28",
"edge": "12",
"firefox": "46",
"safari": "10",
"node": "4",
"deno": "1",
"ie": "11",
"ios": "10",
"samsung": "3.4",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-classes": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-object-super": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-shorthand-properties": {
"chrome": "43",
"opera": "30",
"edge": "12",
"firefox": "33",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.14",
"opera_mobile": "30",
"electron": "0.27"
},
"transform-duplicate-keys": {
"chrome": "42",
"opera": "29",
"edge": "12",
"firefox": "34",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "3.4",
"opera_mobile": "29",
"electron": "0.25"
},
"transform-computed-properties": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "34",
"safari": "7.1",
"node": "4",
"deno": "1",
"ios": "8",
"samsung": "4",
"rhino": "1.8",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-for-of": {
"chrome": "51",
"opera": "38",
"edge": "15",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-sticky-regex": {
"chrome": "49",
"opera": "36",
"edge": "13",
"firefox": "3",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"rhino": "1.7.15",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-unicode-escapes": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "53",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.15",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-unicode-regex": {
"chrome": "50",
"opera": "37",
"edge": "13",
"firefox": "46",
"safari": "12",
"node": "6",
"deno": "1",
"ios": "12",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-spread": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-destructuring": {
"chrome": "51",
"opera": "38",
"edge": "15",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-block-scoping": {
"chrome": "50",
"opera": "37",
"edge": "14",
"firefox": "53",
"safari": "11",
"node": "6",
"deno": "1",
"ios": "11",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-typeof-symbol": {
"chrome": "48",
"opera": "35",
"edge": "12",
"firefox": "36",
"safari": "9",
"node": "6",
"deno": "1",
"ios": "9",
"samsung": "5",
"rhino": "1.8",
"opera_mobile": "35",
"electron": "0.37"
},
"transform-new-target": {
"chrome": "46",
"opera": "33",
"edge": "14",
"firefox": "41",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-regenerator": {
"chrome": "50",
"opera": "37",
"edge": "13",
"firefox": "53",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-member-expression-literals": {
"chrome": "7",
"opera": "12",
"edge": "12",
"firefox": "2",
"safari": "5.1",
"node": "0.4",
"deno": "1",
"ie": "9",
"android": "4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "12",
"electron": "0.20"
},
"transform-property-literals": {
"chrome": "7",
"opera": "12",
"edge": "12",
"firefox": "2",
"safari": "5.1",
"node": "0.4",
"deno": "1",
"ie": "9",
"android": "4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "12",
"electron": "0.20"
},
"transform-reserved-words": {
"chrome": "13",
"opera": "10.50",
"edge": "12",
"firefox": "2",
"safari": "3.1",
"node": "0.6",
"deno": "1",
"ie": "9",
"android": "4.4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "10.1",
"electron": "0.20"
},
"transform-export-namespace-from": {
"chrome": "72",
"deno": "1.0",
"edge": "79",
"firefox": "80",
"node": "13.2.0",
"opera": "60",
"opera_mobile": "51",
"safari": "14.1",
"ios": "14.5",
"samsung": "11.0",
"android": "72",
"electron": "5.0"
},
"proposal-export-namespace-from": {
"chrome": "72",
"deno": "1.0",
"edge": "79",
"firefox": "80",
"node": "13.2.0",
"opera": "60",
"opera_mobile": "51",
"safari": "14.1",
"ios": "14.5",
"samsung": "11.0",
"android": "72",
"electron": "5.0"
}
}
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/native-modules.json");
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/overlapping-plugins.json");
@@ -1,40 +0,0 @@
{
"name": "@babel/compat-data",
"version": "7.28.5",
"author": "The Babel Team (https://babel.dev/team)",
"license": "MIT",
"description": "The compat-data to determine required Babel plugins",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-compat-data"
},
"publishConfig": {
"access": "public"
},
"exports": {
"./plugins": "./plugins.js",
"./native-modules": "./native-modules.js",
"./corejs2-built-ins": "./corejs2-built-ins.js",
"./corejs3-shipped-proposals": "./corejs3-shipped-proposals.js",
"./overlapping-plugins": "./overlapping-plugins.js",
"./plugin-bugfixes": "./plugin-bugfixes.js"
},
"scripts": {
"build-data": "./scripts/download-compat-table.sh && node ./scripts/build-data.mjs && node ./scripts/build-modules-support.mjs && node ./scripts/build-bugfixes-targets.mjs"
},
"keywords": [
"babel",
"compat-table",
"compat-data"
],
"devDependencies": {
"@mdn/browser-compat-data": "^6.0.8",
"core-js-compat": "^3.43.0",
"electron-to-chromium": "^1.5.140"
},
"engines": {
"node": ">=6.9.0"
},
"type": "commonjs"
}
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/plugin-bugfixes.json");
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/plugins.json");
-22
View File
@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-19
View File
@@ -1,19 +0,0 @@
# @babel/core
> Babel compiler core.
See our website [@babel/core](https://babeljs.io/docs/babel-core) for more information or the [issues](https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A%20core%22+is%3Aopen) associated with this package.
## Install
Using npm:
```sh
npm install --save-dev @babel/core
```
or using yarn:
```sh
yarn add @babel/core --dev
```
@@ -1,5 +0,0 @@
"use strict";
0 && 0;
//# sourceMappingURL=cache-contexts.js.map
@@ -1 +0,0 @@
{"version":3,"names":[],"sources":["../../src/config/cache-contexts.ts"],"sourcesContent":["import type { ConfigContext } from \"./config-chain.ts\";\nimport type {\n CallerMetadata,\n TargetsListOrObject,\n} from \"./validation/options.ts\";\n\nexport type { ConfigContext as FullConfig };\n\nexport type FullPreset = {\n targets: TargetsListOrObject;\n} & ConfigContext;\nexport type FullPlugin = {\n assumptions: { [name: string]: boolean };\n} & FullPreset;\n\n// Context not including filename since it is used in places that cannot\n// process 'ignore'/'only' and other filename-based logic.\nexport type SimpleConfig = {\n envName: string;\n caller: CallerMetadata | undefined;\n};\nexport type SimplePreset = {\n targets: TargetsListOrObject;\n} & SimpleConfig;\nexport type SimplePlugin = {\n assumptions: {\n [name: string]: boolean;\n };\n} & SimplePreset;\n"],"mappings":"","ignoreList":[]}
@@ -1,261 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.assertSimpleType = assertSimpleType;
exports.makeStrongCache = makeStrongCache;
exports.makeStrongCacheSync = makeStrongCacheSync;
exports.makeWeakCache = makeWeakCache;
exports.makeWeakCacheSync = makeWeakCacheSync;
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _async = require("../gensync-utils/async.js");
var _util = require("./util.js");
const synchronize = gen => {
return _gensync()(gen).sync;
};
function* genTrue() {
return true;
}
function makeWeakCache(handler) {
return makeCachedFunction(WeakMap, handler);
}
function makeWeakCacheSync(handler) {
return synchronize(makeWeakCache(handler));
}
function makeStrongCache(handler) {
return makeCachedFunction(Map, handler);
}
function makeStrongCacheSync(handler) {
return synchronize(makeStrongCache(handler));
}
function makeCachedFunction(CallCache, handler) {
const callCacheSync = new CallCache();
const callCacheAsync = new CallCache();
const futureCache = new CallCache();
return function* cachedFunction(arg, data) {
const asyncContext = yield* (0, _async.isAsync)();
const callCache = asyncContext ? callCacheAsync : callCacheSync;
const cached = yield* getCachedValueOrWait(asyncContext, callCache, futureCache, arg, data);
if (cached.valid) return cached.value;
const cache = new CacheConfigurator(data);
const handlerResult = handler(arg, cache);
let finishLock;
let value;
if ((0, _util.isIterableIterator)(handlerResult)) {
value = yield* (0, _async.onFirstPause)(handlerResult, () => {
finishLock = setupAsyncLocks(cache, futureCache, arg);
});
} else {
value = handlerResult;
}
updateFunctionCache(callCache, cache, arg, value);
if (finishLock) {
futureCache.delete(arg);
finishLock.release(value);
}
return value;
};
}
function* getCachedValue(cache, arg, data) {
const cachedValue = cache.get(arg);
if (cachedValue) {
for (const {
value,
valid
} of cachedValue) {
if (yield* valid(data)) return {
valid: true,
value
};
}
}
return {
valid: false,
value: null
};
}
function* getCachedValueOrWait(asyncContext, callCache, futureCache, arg, data) {
const cached = yield* getCachedValue(callCache, arg, data);
if (cached.valid) {
return cached;
}
if (asyncContext) {
const cached = yield* getCachedValue(futureCache, arg, data);
if (cached.valid) {
const value = yield* (0, _async.waitFor)(cached.value.promise);
return {
valid: true,
value
};
}
}
return {
valid: false,
value: null
};
}
function setupAsyncLocks(config, futureCache, arg) {
const finishLock = new Lock();
updateFunctionCache(futureCache, config, arg, finishLock);
return finishLock;
}
function updateFunctionCache(cache, config, arg, value) {
if (!config.configured()) config.forever();
let cachedValue = cache.get(arg);
config.deactivate();
switch (config.mode()) {
case "forever":
cachedValue = [{
value,
valid: genTrue
}];
cache.set(arg, cachedValue);
break;
case "invalidate":
cachedValue = [{
value,
valid: config.validator()
}];
cache.set(arg, cachedValue);
break;
case "valid":
if (cachedValue) {
cachedValue.push({
value,
valid: config.validator()
});
} else {
cachedValue = [{
value,
valid: config.validator()
}];
cache.set(arg, cachedValue);
}
}
}
class CacheConfigurator {
constructor(data) {
this._active = true;
this._never = false;
this._forever = false;
this._invalidate = false;
this._configured = false;
this._pairs = [];
this._data = void 0;
this._data = data;
}
simple() {
return makeSimpleConfigurator(this);
}
mode() {
if (this._never) return "never";
if (this._forever) return "forever";
if (this._invalidate) return "invalidate";
return "valid";
}
forever() {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._never) {
throw new Error("Caching has already been configured with .never()");
}
this._forever = true;
this._configured = true;
}
never() {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._forever) {
throw new Error("Caching has already been configured with .forever()");
}
this._never = true;
this._configured = true;
}
using(handler) {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._never || this._forever) {
throw new Error("Caching has already been configured with .never or .forever()");
}
this._configured = true;
const key = handler(this._data);
const fn = (0, _async.maybeAsync)(handler, `You appear to be using an async cache handler, but Babel has been called synchronously`);
if ((0, _async.isThenable)(key)) {
return key.then(key => {
this._pairs.push([key, fn]);
return key;
});
}
this._pairs.push([key, fn]);
return key;
}
invalidate(handler) {
this._invalidate = true;
return this.using(handler);
}
validator() {
const pairs = this._pairs;
return function* (data) {
for (const [key, fn] of pairs) {
if (key !== (yield* fn(data))) return false;
}
return true;
};
}
deactivate() {
this._active = false;
}
configured() {
return this._configured;
}
}
function makeSimpleConfigurator(cache) {
function cacheFn(val) {
if (typeof val === "boolean") {
if (val) cache.forever();else cache.never();
return;
}
return cache.using(() => assertSimpleType(val()));
}
cacheFn.forever = () => cache.forever();
cacheFn.never = () => cache.never();
cacheFn.using = cb => cache.using(() => assertSimpleType(cb()));
cacheFn.invalidate = cb => cache.invalidate(() => assertSimpleType(cb()));
return cacheFn;
}
function assertSimpleType(value) {
if ((0, _async.isThenable)(value)) {
throw new Error(`You appear to be using an async cache handler, ` + `which your current version of Babel does not support. ` + `We may add support for this in the future, ` + `but if you're on the most recent version of @babel/core and still ` + `seeing this error, then you'll need to synchronously handle your caching logic.`);
}
if (value != null && typeof value !== "string" && typeof value !== "boolean" && typeof value !== "number") {
throw new Error("Cache keys must be either string, boolean, number, null, or undefined.");
}
return value;
}
class Lock {
constructor() {
this.released = false;
this.promise = void 0;
this._resolve = void 0;
this.promise = new Promise(resolve => {
this._resolve = resolve;
});
}
release(value) {
this.released = true;
this._resolve(value);
}
}
0 && 0;
//# sourceMappingURL=caching.js.map
File diff suppressed because one or more lines are too long
@@ -1,469 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.buildPresetChain = buildPresetChain;
exports.buildPresetChainWalker = void 0;
exports.buildRootChain = buildRootChain;
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
var _options = require("./validation/options.js");
var _patternToRegex = require("./pattern-to-regex.js");
var _printer = require("./printer.js");
var _rewriteStackTrace = require("../errors/rewrite-stack-trace.js");
var _configError = require("../errors/config-error.js");
var _index = require("./files/index.js");
var _caching = require("./caching.js");
var _configDescriptors = require("./config-descriptors.js");
const debug = _debug()("babel:config:config-chain");
function* buildPresetChain(arg, context) {
const chain = yield* buildPresetChainWalker(arg, context);
if (!chain) return null;
return {
plugins: dedupDescriptors(chain.plugins),
presets: dedupDescriptors(chain.presets),
options: chain.options.map(o => createConfigChainOptions(o)),
files: new Set()
};
}
const buildPresetChainWalker = exports.buildPresetChainWalker = makeChainWalker({
root: preset => loadPresetDescriptors(preset),
env: (preset, envName) => loadPresetEnvDescriptors(preset)(envName),
overrides: (preset, index) => loadPresetOverridesDescriptors(preset)(index),
overridesEnv: (preset, index, envName) => loadPresetOverridesEnvDescriptors(preset)(index)(envName),
createLogger: () => () => {}
});
const loadPresetDescriptors = (0, _caching.makeWeakCacheSync)(preset => buildRootDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors));
const loadPresetEnvDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(envName => buildEnvDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, envName)));
const loadPresetOverridesDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(index => buildOverrideDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, index)));
const loadPresetOverridesEnvDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(index => (0, _caching.makeStrongCacheSync)(envName => buildOverrideEnvDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, index, envName))));
function* buildRootChain(opts, context) {
let configReport, babelRcReport;
const programmaticLogger = new _printer.ConfigPrinter();
const programmaticChain = yield* loadProgrammaticChain({
options: opts,
dirname: context.cwd
}, context, undefined, programmaticLogger);
if (!programmaticChain) return null;
const programmaticReport = yield* programmaticLogger.output();
let configFile;
if (typeof opts.configFile === "string") {
configFile = yield* (0, _index.loadConfig)(opts.configFile, context.cwd, context.envName, context.caller);
} else if (opts.configFile !== false) {
configFile = yield* (0, _index.findRootConfig)(context.root, context.envName, context.caller);
}
let {
babelrc,
babelrcRoots
} = opts;
let babelrcRootsDirectory = context.cwd;
const configFileChain = emptyChain();
const configFileLogger = new _printer.ConfigPrinter();
if (configFile) {
const validatedFile = validateConfigFile(configFile);
const result = yield* loadFileChain(validatedFile, context, undefined, configFileLogger);
if (!result) return null;
configReport = yield* configFileLogger.output();
if (babelrc === undefined) {
babelrc = validatedFile.options.babelrc;
}
if (babelrcRoots === undefined) {
babelrcRootsDirectory = validatedFile.dirname;
babelrcRoots = validatedFile.options.babelrcRoots;
}
mergeChain(configFileChain, result);
}
let ignoreFile, babelrcFile;
let isIgnored = false;
const fileChain = emptyChain();
if ((babelrc === true || babelrc === undefined) && typeof context.filename === "string") {
const pkgData = yield* (0, _index.findPackageData)(context.filename);
if (pkgData && babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory)) {
({
ignore: ignoreFile,
config: babelrcFile
} = yield* (0, _index.findRelativeConfig)(pkgData, context.envName, context.caller));
if (ignoreFile) {
fileChain.files.add(ignoreFile.filepath);
}
if (ignoreFile && shouldIgnore(context, ignoreFile.ignore, null, ignoreFile.dirname)) {
isIgnored = true;
}
if (babelrcFile && !isIgnored) {
const validatedFile = validateBabelrcFile(babelrcFile);
const babelrcLogger = new _printer.ConfigPrinter();
const result = yield* loadFileChain(validatedFile, context, undefined, babelrcLogger);
if (!result) {
isIgnored = true;
} else {
babelRcReport = yield* babelrcLogger.output();
mergeChain(fileChain, result);
}
}
if (babelrcFile && isIgnored) {
fileChain.files.add(babelrcFile.filepath);
}
}
}
if (context.showConfig) {
console.log(`Babel configs on "${context.filename}" (ascending priority):\n` + [configReport, babelRcReport, programmaticReport].filter(x => !!x).join("\n\n") + "\n-----End Babel configs-----");
}
const chain = mergeChain(mergeChain(mergeChain(emptyChain(), configFileChain), fileChain), programmaticChain);
return {
plugins: isIgnored ? [] : dedupDescriptors(chain.plugins),
presets: isIgnored ? [] : dedupDescriptors(chain.presets),
options: isIgnored ? [] : chain.options.map(o => createConfigChainOptions(o)),
fileHandling: isIgnored ? "ignored" : "transpile",
ignore: ignoreFile || undefined,
babelrc: babelrcFile || undefined,
config: configFile || undefined,
files: chain.files
};
}
function babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory) {
if (typeof babelrcRoots === "boolean") return babelrcRoots;
const absoluteRoot = context.root;
if (babelrcRoots === undefined) {
return pkgData.directories.includes(absoluteRoot);
}
let babelrcPatterns = babelrcRoots;
if (!Array.isArray(babelrcPatterns)) {
babelrcPatterns = [babelrcPatterns];
}
babelrcPatterns = babelrcPatterns.map(pat => {
return typeof pat === "string" ? _path().resolve(babelrcRootsDirectory, pat) : pat;
});
if (babelrcPatterns.length === 1 && babelrcPatterns[0] === absoluteRoot) {
return pkgData.directories.includes(absoluteRoot);
}
return babelrcPatterns.some(pat => {
if (typeof pat === "string") {
pat = (0, _patternToRegex.default)(pat, babelrcRootsDirectory);
}
return pkgData.directories.some(directory => {
return matchPattern(pat, babelrcRootsDirectory, directory, context);
});
});
}
const validateConfigFile = (0, _caching.makeWeakCacheSync)(file => ({
filepath: file.filepath,
dirname: file.dirname,
options: (0, _options.validate)("configfile", file.options, file.filepath)
}));
const validateBabelrcFile = (0, _caching.makeWeakCacheSync)(file => ({
filepath: file.filepath,
dirname: file.dirname,
options: (0, _options.validate)("babelrcfile", file.options, file.filepath)
}));
const validateExtendFile = (0, _caching.makeWeakCacheSync)(file => ({
filepath: file.filepath,
dirname: file.dirname,
options: (0, _options.validate)("extendsfile", file.options, file.filepath)
}));
const loadProgrammaticChain = makeChainWalker({
root: input => buildRootDescriptors(input, "base", _configDescriptors.createCachedDescriptors),
env: (input, envName) => buildEnvDescriptors(input, "base", _configDescriptors.createCachedDescriptors, envName),
overrides: (input, index) => buildOverrideDescriptors(input, "base", _configDescriptors.createCachedDescriptors, index),
overridesEnv: (input, index, envName) => buildOverrideEnvDescriptors(input, "base", _configDescriptors.createCachedDescriptors, index, envName),
createLogger: (input, context, baseLogger) => buildProgrammaticLogger(input, context, baseLogger)
});
const loadFileChainWalker = makeChainWalker({
root: file => loadFileDescriptors(file),
env: (file, envName) => loadFileEnvDescriptors(file)(envName),
overrides: (file, index) => loadFileOverridesDescriptors(file)(index),
overridesEnv: (file, index, envName) => loadFileOverridesEnvDescriptors(file)(index)(envName),
createLogger: (file, context, baseLogger) => buildFileLogger(file.filepath, context, baseLogger)
});
function* loadFileChain(input, context, files, baseLogger) {
const chain = yield* loadFileChainWalker(input, context, files, baseLogger);
chain == null || chain.files.add(input.filepath);
return chain;
}
const loadFileDescriptors = (0, _caching.makeWeakCacheSync)(file => buildRootDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors));
const loadFileEnvDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(envName => buildEnvDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, envName)));
const loadFileOverridesDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(index => buildOverrideDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, index)));
const loadFileOverridesEnvDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(index => (0, _caching.makeStrongCacheSync)(envName => buildOverrideEnvDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, index, envName))));
function buildFileLogger(filepath, context, baseLogger) {
if (!baseLogger) {
return () => {};
}
return baseLogger.configure(context.showConfig, _printer.ChainFormatter.Config, {
filepath
});
}
function buildRootDescriptors({
dirname,
options
}, alias, descriptors) {
return descriptors(dirname, options, alias);
}
function buildProgrammaticLogger(_, context, baseLogger) {
var _context$caller;
if (!baseLogger) {
return () => {};
}
return baseLogger.configure(context.showConfig, _printer.ChainFormatter.Programmatic, {
callerName: (_context$caller = context.caller) == null ? void 0 : _context$caller.name
});
}
function buildEnvDescriptors({
dirname,
options
}, alias, descriptors, envName) {
var _options$env;
const opts = (_options$env = options.env) == null ? void 0 : _options$env[envName];
return opts ? descriptors(dirname, opts, `${alias}.env["${envName}"]`) : null;
}
function buildOverrideDescriptors({
dirname,
options
}, alias, descriptors, index) {
var _options$overrides;
const opts = (_options$overrides = options.overrides) == null ? void 0 : _options$overrides[index];
if (!opts) throw new Error("Assertion failure - missing override");
return descriptors(dirname, opts, `${alias}.overrides[${index}]`);
}
function buildOverrideEnvDescriptors({
dirname,
options
}, alias, descriptors, index, envName) {
var _options$overrides2, _override$env;
const override = (_options$overrides2 = options.overrides) == null ? void 0 : _options$overrides2[index];
if (!override) throw new Error("Assertion failure - missing override");
const opts = (_override$env = override.env) == null ? void 0 : _override$env[envName];
return opts ? descriptors(dirname, opts, `${alias}.overrides[${index}].env["${envName}"]`) : null;
}
function makeChainWalker({
root,
env,
overrides,
overridesEnv,
createLogger
}) {
return function* chainWalker(input, context, files = new Set(), baseLogger) {
const {
dirname
} = input;
const flattenedConfigs = [];
const rootOpts = root(input);
if (configIsApplicable(rootOpts, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: rootOpts,
envName: undefined,
index: undefined
});
const envOpts = env(input, context.envName);
if (envOpts && configIsApplicable(envOpts, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: envOpts,
envName: context.envName,
index: undefined
});
}
(rootOpts.options.overrides || []).forEach((_, index) => {
const overrideOps = overrides(input, index);
if (configIsApplicable(overrideOps, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: overrideOps,
index,
envName: undefined
});
const overrideEnvOpts = overridesEnv(input, index, context.envName);
if (overrideEnvOpts && configIsApplicable(overrideEnvOpts, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: overrideEnvOpts,
index,
envName: context.envName
});
}
}
});
}
if (flattenedConfigs.some(({
config: {
options: {
ignore,
only
}
}
}) => shouldIgnore(context, ignore, only, dirname))) {
return null;
}
const chain = emptyChain();
const logger = createLogger(input, context, baseLogger);
for (const {
config,
index,
envName
} of flattenedConfigs) {
if (!(yield* mergeExtendsChain(chain, config.options, dirname, context, files, baseLogger))) {
return null;
}
logger(config, index, envName);
yield* mergeChainOpts(chain, config);
}
return chain;
};
}
function* mergeExtendsChain(chain, opts, dirname, context, files, baseLogger) {
if (opts.extends === undefined) return true;
const file = yield* (0, _index.loadConfig)(opts.extends, dirname, context.envName, context.caller);
if (files.has(file)) {
throw new Error(`Configuration cycle detected loading ${file.filepath}.\n` + `File already loaded following the config chain:\n` + Array.from(files, file => ` - ${file.filepath}`).join("\n"));
}
files.add(file);
const fileChain = yield* loadFileChain(validateExtendFile(file), context, files, baseLogger);
files.delete(file);
if (!fileChain) return false;
mergeChain(chain, fileChain);
return true;
}
function mergeChain(target, source) {
target.options.push(...source.options);
target.plugins.push(...source.plugins);
target.presets.push(...source.presets);
for (const file of source.files) {
target.files.add(file);
}
return target;
}
function* mergeChainOpts(target, {
options,
plugins,
presets
}) {
target.options.push(options);
target.plugins.push(...(yield* plugins()));
target.presets.push(...(yield* presets()));
return target;
}
function emptyChain() {
return {
options: [],
presets: [],
plugins: [],
files: new Set()
};
}
function createConfigChainOptions(opts) {
const options = Object.assign({}, opts);
delete options.extends;
delete options.env;
delete options.overrides;
delete options.plugins;
delete options.presets;
delete options.passPerPreset;
delete options.ignore;
delete options.only;
delete options.test;
delete options.include;
delete options.exclude;
if (hasOwnProperty.call(options, "sourceMap")) {
options.sourceMaps = options.sourceMap;
delete options.sourceMap;
}
return options;
}
function dedupDescriptors(items) {
const map = new Map();
const descriptors = [];
for (const item of items) {
if (typeof item.value === "function") {
const fnKey = item.value;
let nameMap = map.get(fnKey);
if (!nameMap) {
nameMap = new Map();
map.set(fnKey, nameMap);
}
let desc = nameMap.get(item.name);
if (!desc) {
desc = {
value: item
};
descriptors.push(desc);
if (!item.ownPass) nameMap.set(item.name, desc);
} else {
desc.value = item;
}
} else {
descriptors.push({
value: item
});
}
}
return descriptors.reduce((acc, desc) => {
acc.push(desc.value);
return acc;
}, []);
}
function configIsApplicable({
options
}, dirname, context, configName) {
return (options.test === undefined || configFieldIsApplicable(context, options.test, dirname, configName)) && (options.include === undefined || configFieldIsApplicable(context, options.include, dirname, configName)) && (options.exclude === undefined || !configFieldIsApplicable(context, options.exclude, dirname, configName));
}
function configFieldIsApplicable(context, test, dirname, configName) {
const patterns = Array.isArray(test) ? test : [test];
return matchesPatterns(context, patterns, dirname, configName);
}
function ignoreListReplacer(_key, value) {
if (value instanceof RegExp) {
return String(value);
}
return value;
}
function shouldIgnore(context, ignore, only, dirname) {
if (ignore && matchesPatterns(context, ignore, dirname)) {
var _context$filename;
const message = `No config is applied to "${(_context$filename = context.filename) != null ? _context$filename : "(unknown)"}" because it matches one of \`ignore: ${JSON.stringify(ignore, ignoreListReplacer)}\` from "${dirname}"`;
debug(message);
if (context.showConfig) {
console.log(message);
}
return true;
}
if (only && !matchesPatterns(context, only, dirname)) {
var _context$filename2;
const message = `No config is applied to "${(_context$filename2 = context.filename) != null ? _context$filename2 : "(unknown)"}" because it fails to match one of \`only: ${JSON.stringify(only, ignoreListReplacer)}\` from "${dirname}"`;
debug(message);
if (context.showConfig) {
console.log(message);
}
return true;
}
return false;
}
function matchesPatterns(context, patterns, dirname, configName) {
return patterns.some(pattern => matchPattern(pattern, dirname, context.filename, context, configName));
}
function matchPattern(pattern, dirname, pathToTest, context, configName) {
if (typeof pattern === "function") {
return !!(0, _rewriteStackTrace.endHiddenCallStack)(pattern)(pathToTest, {
dirname,
envName: context.envName,
caller: context.caller
});
}
if (typeof pathToTest !== "string") {
throw new _configError.default(`Configuration contains string/RegExp pattern, but no filename was passed to Babel`, configName);
}
if (typeof pattern === "string") {
pattern = (0, _patternToRegex.default)(pattern, dirname);
}
return pattern.test(pathToTest);
}
0 && 0;
//# sourceMappingURL=config-chain.js.map
File diff suppressed because one or more lines are too long
@@ -1,190 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createCachedDescriptors = createCachedDescriptors;
exports.createDescriptor = createDescriptor;
exports.createUncachedDescriptors = createUncachedDescriptors;
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _functional = require("../gensync-utils/functional.js");
var _index = require("./files/index.js");
var _item = require("./item.js");
var _caching = require("./caching.js");
var _resolveTargets = require("./resolve-targets.js");
function isEqualDescriptor(a, b) {
var _a$file, _b$file, _a$file2, _b$file2;
return a.name === b.name && a.value === b.value && a.options === b.options && a.dirname === b.dirname && a.alias === b.alias && a.ownPass === b.ownPass && ((_a$file = a.file) == null ? void 0 : _a$file.request) === ((_b$file = b.file) == null ? void 0 : _b$file.request) && ((_a$file2 = a.file) == null ? void 0 : _a$file2.resolved) === ((_b$file2 = b.file) == null ? void 0 : _b$file2.resolved);
}
function* handlerOf(value) {
return value;
}
function optionsWithResolvedBrowserslistConfigFile(options, dirname) {
if (typeof options.browserslistConfigFile === "string") {
options.browserslistConfigFile = (0, _resolveTargets.resolveBrowserslistConfigFile)(options.browserslistConfigFile, dirname);
}
return options;
}
function createCachedDescriptors(dirname, options, alias) {
const {
plugins,
presets,
passPerPreset
} = options;
return {
options: optionsWithResolvedBrowserslistConfigFile(options, dirname),
plugins: plugins ? () => createCachedPluginDescriptors(plugins, dirname)(alias) : () => handlerOf([]),
presets: presets ? () => createCachedPresetDescriptors(presets, dirname)(alias)(!!passPerPreset) : () => handlerOf([])
};
}
function createUncachedDescriptors(dirname, options, alias) {
return {
options: optionsWithResolvedBrowserslistConfigFile(options, dirname),
plugins: (0, _functional.once)(() => createPluginDescriptors(options.plugins || [], dirname, alias)),
presets: (0, _functional.once)(() => createPresetDescriptors(options.presets || [], dirname, alias, !!options.passPerPreset))
};
}
const PRESET_DESCRIPTOR_CACHE = new WeakMap();
const createCachedPresetDescriptors = (0, _caching.makeWeakCacheSync)((items, cache) => {
const dirname = cache.using(dir => dir);
return (0, _caching.makeStrongCacheSync)(alias => (0, _caching.makeStrongCache)(function* (passPerPreset) {
const descriptors = yield* createPresetDescriptors(items, dirname, alias, passPerPreset);
return descriptors.map(desc => loadCachedDescriptor(PRESET_DESCRIPTOR_CACHE, desc));
}));
});
const PLUGIN_DESCRIPTOR_CACHE = new WeakMap();
const createCachedPluginDescriptors = (0, _caching.makeWeakCacheSync)((items, cache) => {
const dirname = cache.using(dir => dir);
return (0, _caching.makeStrongCache)(function* (alias) {
const descriptors = yield* createPluginDescriptors(items, dirname, alias);
return descriptors.map(desc => loadCachedDescriptor(PLUGIN_DESCRIPTOR_CACHE, desc));
});
});
const DEFAULT_OPTIONS = {};
function loadCachedDescriptor(cache, desc) {
const {
value,
options = DEFAULT_OPTIONS
} = desc;
if (options === false) return desc;
let cacheByOptions = cache.get(value);
if (!cacheByOptions) {
cacheByOptions = new WeakMap();
cache.set(value, cacheByOptions);
}
let possibilities = cacheByOptions.get(options);
if (!possibilities) {
possibilities = [];
cacheByOptions.set(options, possibilities);
}
if (!possibilities.includes(desc)) {
const matches = possibilities.filter(possibility => isEqualDescriptor(possibility, desc));
if (matches.length > 0) {
return matches[0];
}
possibilities.push(desc);
}
return desc;
}
function* createPresetDescriptors(items, dirname, alias, passPerPreset) {
return yield* createDescriptors("preset", items, dirname, alias, passPerPreset);
}
function* createPluginDescriptors(items, dirname, alias) {
return yield* createDescriptors("plugin", items, dirname, alias);
}
function* createDescriptors(type, items, dirname, alias, ownPass) {
const descriptors = yield* _gensync().all(items.map((item, index) => createDescriptor(item, dirname, {
type,
alias: `${alias}$${index}`,
ownPass: !!ownPass
})));
assertNoDuplicates(descriptors);
return descriptors;
}
function* createDescriptor(pair, dirname, {
type,
alias,
ownPass
}) {
const desc = (0, _item.getItemDescriptor)(pair);
if (desc) {
return desc;
}
let name;
let options;
let value = pair;
if (Array.isArray(value)) {
if (value.length === 3) {
[value, options, name] = value;
} else {
[value, options] = value;
}
}
let file = undefined;
let filepath = null;
if (typeof value === "string") {
if (typeof type !== "string") {
throw new Error("To resolve a string-based item, the type of item must be given");
}
const resolver = type === "plugin" ? _index.loadPlugin : _index.loadPreset;
const request = value;
({
filepath,
value
} = yield* resolver(value, dirname));
file = {
request,
resolved: filepath
};
}
if (!value) {
throw new Error(`Unexpected falsy value: ${String(value)}`);
}
if (typeof value === "object" && value.__esModule) {
if (value.default) {
value = value.default;
} else {
throw new Error("Must export a default export when using ES6 modules.");
}
}
if (typeof value !== "object" && typeof value !== "function") {
throw new Error(`Unsupported format: ${typeof value}. Expected an object or a function.`);
}
if (filepath !== null && typeof value === "object" && value) {
throw new Error(`Plugin/Preset files are not allowed to export objects, only functions. In ${filepath}`);
}
return {
name,
alias: filepath || alias,
value,
options,
dirname,
ownPass,
file
};
}
function assertNoDuplicates(items) {
const map = new Map();
for (const item of items) {
if (typeof item.value !== "function") continue;
let nameMap = map.get(item.value);
if (!nameMap) {
nameMap = new Set();
map.set(item.value, nameMap);
}
if (nameMap.has(item.name)) {
const conflicts = items.filter(i => i.value === item.value);
throw new Error([`Duplicate plugin/preset detected.`, `If you'd like to use two separate instances of a plugin,`, `they need separate names, e.g.`, ``, ` plugins: [`, ` ['some-plugin', {}],`, ` ['some-plugin', {}, 'some unique name'],`, ` ]`, ``, `Duplicates detected are:`, `${JSON.stringify(conflicts, null, 2)}`].join("\n"));
}
nameMap.add(item.name);
}
}
0 && 0;
//# sourceMappingURL=config-descriptors.js.map
File diff suppressed because one or more lines are too long
@@ -1,290 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ROOT_CONFIG_FILENAMES = void 0;
exports.findConfigUpwards = findConfigUpwards;
exports.findRelativeConfig = findRelativeConfig;
exports.findRootConfig = findRootConfig;
exports.loadConfig = loadConfig;
exports.resolveShowConfigPath = resolveShowConfigPath;
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
function _fs() {
const data = require("fs");
_fs = function () {
return data;
};
return data;
}
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _json() {
const data = require("json5");
_json = function () {
return data;
};
return data;
}
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _caching = require("../caching.js");
var _configApi = require("../helpers/config-api.js");
var _utils = require("./utils.js");
var _moduleTypes = require("./module-types.js");
var _patternToRegex = require("../pattern-to-regex.js");
var _configError = require("../../errors/config-error.js");
var fs = require("../../gensync-utils/fs.js");
require("module");
var _rewriteStackTrace = require("../../errors/rewrite-stack-trace.js");
var _async = require("../../gensync-utils/async.js");
const debug = _debug()("babel:config:loading:files:configuration");
const ROOT_CONFIG_FILENAMES = exports.ROOT_CONFIG_FILENAMES = ["babel.config.js", "babel.config.cjs", "babel.config.mjs", "babel.config.json", "babel.config.cts", "babel.config.ts", "babel.config.mts"];
const RELATIVE_CONFIG_FILENAMES = [".babelrc", ".babelrc.js", ".babelrc.cjs", ".babelrc.mjs", ".babelrc.json", ".babelrc.cts"];
const BABELIGNORE_FILENAME = ".babelignore";
const runConfig = (0, _caching.makeWeakCache)(function* runConfig(options, cache) {
yield* [];
return {
options: (0, _rewriteStackTrace.endHiddenCallStack)(options)((0, _configApi.makeConfigAPI)(cache)),
cacheNeedsConfiguration: !cache.configured()
};
});
function* readConfigCode(filepath, data) {
if (!_fs().existsSync(filepath)) return null;
let options = yield* (0, _moduleTypes.default)(filepath, (yield* (0, _async.isAsync)()) ? "auto" : "require", "You appear to be using a native ECMAScript module configuration " + "file, which is only supported when running Babel asynchronously " + "or when using the Node.js `--experimental-require-module` flag.", "You appear to be using a configuration file that contains top-level " + "await, which is only supported when running Babel asynchronously.");
let cacheNeedsConfiguration = false;
if (typeof options === "function") {
({
options,
cacheNeedsConfiguration
} = yield* runConfig(options, data));
}
if (!options || typeof options !== "object" || Array.isArray(options)) {
throw new _configError.default(`Configuration should be an exported JavaScript object.`, filepath);
}
if (typeof options.then === "function") {
options.catch == null || options.catch(() => {});
throw new _configError.default(`You appear to be using an async configuration, ` + `which your current version of Babel does not support. ` + `We may add support for this in the future, ` + `but if you're on the most recent version of @babel/core and still ` + `seeing this error, then you'll need to synchronously return your config.`, filepath);
}
if (cacheNeedsConfiguration) throwConfigError(filepath);
return buildConfigFileObject(options, filepath);
}
const cfboaf = new WeakMap();
function buildConfigFileObject(options, filepath) {
let configFilesByFilepath = cfboaf.get(options);
if (!configFilesByFilepath) {
cfboaf.set(options, configFilesByFilepath = new Map());
}
let configFile = configFilesByFilepath.get(filepath);
if (!configFile) {
configFile = {
filepath,
dirname: _path().dirname(filepath),
options
};
configFilesByFilepath.set(filepath, configFile);
}
return configFile;
}
const packageToBabelConfig = (0, _caching.makeWeakCacheSync)(file => {
const babel = file.options.babel;
if (babel === undefined) return null;
if (typeof babel !== "object" || Array.isArray(babel) || babel === null) {
throw new _configError.default(`.babel property must be an object`, file.filepath);
}
return {
filepath: file.filepath,
dirname: file.dirname,
options: babel
};
});
const readConfigJSON5 = (0, _utils.makeStaticFileCache)((filepath, content) => {
let options;
try {
options = _json().parse(content);
} catch (err) {
throw new _configError.default(`Error while parsing config - ${err.message}`, filepath);
}
if (!options) throw new _configError.default(`No config detected`, filepath);
if (typeof options !== "object") {
throw new _configError.default(`Config returned typeof ${typeof options}`, filepath);
}
if (Array.isArray(options)) {
throw new _configError.default(`Expected config object but found array`, filepath);
}
delete options.$schema;
return {
filepath,
dirname: _path().dirname(filepath),
options
};
});
const readIgnoreConfig = (0, _utils.makeStaticFileCache)((filepath, content) => {
const ignoreDir = _path().dirname(filepath);
const ignorePatterns = content.split("\n").map(line => line.replace(/#.*$/, "").trim()).filter(Boolean);
for (const pattern of ignorePatterns) {
if (pattern[0] === "!") {
throw new _configError.default(`Negation of file paths is not supported.`, filepath);
}
}
return {
filepath,
dirname: _path().dirname(filepath),
ignore: ignorePatterns.map(pattern => (0, _patternToRegex.default)(pattern, ignoreDir))
};
});
function findConfigUpwards(rootDir) {
let dirname = rootDir;
for (;;) {
for (const filename of ROOT_CONFIG_FILENAMES) {
if (_fs().existsSync(_path().join(dirname, filename))) {
return dirname;
}
}
const nextDir = _path().dirname(dirname);
if (dirname === nextDir) break;
dirname = nextDir;
}
return null;
}
function* findRelativeConfig(packageData, envName, caller) {
let config = null;
let ignore = null;
const dirname = _path().dirname(packageData.filepath);
for (const loc of packageData.directories) {
if (!config) {
var _packageData$pkg;
config = yield* loadOneConfig(RELATIVE_CONFIG_FILENAMES, loc, envName, caller, ((_packageData$pkg = packageData.pkg) == null ? void 0 : _packageData$pkg.dirname) === loc ? packageToBabelConfig(packageData.pkg) : null);
}
if (!ignore) {
const ignoreLoc = _path().join(loc, BABELIGNORE_FILENAME);
ignore = yield* readIgnoreConfig(ignoreLoc);
if (ignore) {
debug("Found ignore %o from %o.", ignore.filepath, dirname);
}
}
}
return {
config,
ignore
};
}
function findRootConfig(dirname, envName, caller) {
return loadOneConfig(ROOT_CONFIG_FILENAMES, dirname, envName, caller);
}
function* loadOneConfig(names, dirname, envName, caller, previousConfig = null) {
const configs = yield* _gensync().all(names.map(filename => readConfig(_path().join(dirname, filename), envName, caller)));
const config = configs.reduce((previousConfig, config) => {
if (config && previousConfig) {
throw new _configError.default(`Multiple configuration files found. Please remove one:\n` + ` - ${_path().basename(previousConfig.filepath)}\n` + ` - ${config.filepath}\n` + `from ${dirname}`);
}
return config || previousConfig;
}, previousConfig);
if (config) {
debug("Found configuration %o from %o.", config.filepath, dirname);
}
return config;
}
function* loadConfig(name, dirname, envName, caller) {
const filepath = (((v, w) => (v = v.split("."), w = w.split("."), +v[0] > +w[0] || v[0] == w[0] && +v[1] >= +w[1]))(process.versions.node, "8.9") ? require.resolve : (r, {
paths: [b]
}, M = require("module")) => {
let f = M._findPath(r, M._nodeModulePaths(b).concat(b));
if (f) return f;
f = new Error(`Cannot resolve module '${r}'`);
f.code = "MODULE_NOT_FOUND";
throw f;
})(name, {
paths: [dirname]
});
const conf = yield* readConfig(filepath, envName, caller);
if (!conf) {
throw new _configError.default(`Config file contains no configuration data`, filepath);
}
debug("Loaded config %o from %o.", name, dirname);
return conf;
}
function readConfig(filepath, envName, caller) {
const ext = _path().extname(filepath);
switch (ext) {
case ".js":
case ".cjs":
case ".mjs":
case ".ts":
case ".cts":
case ".mts":
return readConfigCode(filepath, {
envName,
caller
});
default:
return readConfigJSON5(filepath);
}
}
function* resolveShowConfigPath(dirname) {
const targetPath = process.env.BABEL_SHOW_CONFIG_FOR;
if (targetPath != null) {
const absolutePath = _path().resolve(dirname, targetPath);
const stats = yield* fs.stat(absolutePath);
if (!stats.isFile()) {
throw new Error(`${absolutePath}: BABEL_SHOW_CONFIG_FOR must refer to a regular file, directories are not supported.`);
}
return absolutePath;
}
return null;
}
function throwConfigError(filepath) {
throw new _configError.default(`\
Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured
for various types of caching, using the first param of their handler functions:
module.exports = function(api) {
// The API exposes the following:
// Cache the returned value forever and don't call this function again.
api.cache(true);
// Don't cache at all. Not recommended because it will be very slow.
api.cache(false);
// Cached based on the value of some function. If this function returns a value different from
// a previously-encountered value, the plugins will re-evaluate.
var env = api.cache(() => process.env.NODE_ENV);
// If testing for a specific env, we recommend specifics to avoid instantiating a plugin for
// any possible NODE_ENV value that might come up during plugin execution.
var isProd = api.cache(() => process.env.NODE_ENV === "production");
// .cache(fn) will perform a linear search though instances to find the matching plugin based
// based on previous instantiated plugins. If you want to recreate the plugin and discard the
// previous instance whenever something changes, you may use:
var isProd = api.cache.invalidate(() => process.env.NODE_ENV === "production");
// Note, we also expose the following more-verbose versions of the above examples:
api.cache.forever(); // api.cache(true)
api.cache.never(); // api.cache(false)
api.cache.using(fn); // api.cache(fn)
// Return the value that will be cached.
return { };
};`, filepath);
}
0 && 0;
//# sourceMappingURL=configuration.js.map
File diff suppressed because one or more lines are too long
@@ -1,6 +0,0 @@
module.exports = function import_(filepath) {
return import(filepath);
};
0 && 0;
//# sourceMappingURL=import.cjs.map
@@ -1 +0,0 @@
{"version":3,"names":["module","exports","import_","filepath"],"sources":["../../../src/config/files/import.cjs"],"sourcesContent":["// We keep this in a separate file so that in older node versions, where\n// import() isn't supported, we can try/catch around the require() call\n// when loading this file.\n\nmodule.exports = function import_(filepath) {\n return import(filepath);\n};\n"],"mappings":"AAIAA,MAAM,CAACC,OAAO,GAAG,SAASC,OAAOA,CAACC,QAAQ,EAAE;EAC1C,OAAO,OAAOA,QAAQ,CAAC;AACzB,CAAC;AAAC","ignoreList":[]}
@@ -1,58 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ROOT_CONFIG_FILENAMES = void 0;
exports.findConfigUpwards = findConfigUpwards;
exports.findPackageData = findPackageData;
exports.findRelativeConfig = findRelativeConfig;
exports.findRootConfig = findRootConfig;
exports.loadConfig = loadConfig;
exports.loadPlugin = loadPlugin;
exports.loadPreset = loadPreset;
exports.resolvePlugin = resolvePlugin;
exports.resolvePreset = resolvePreset;
exports.resolveShowConfigPath = resolveShowConfigPath;
function findConfigUpwards(rootDir) {
return null;
}
function* findPackageData(filepath) {
return {
filepath,
directories: [],
pkg: null,
isPackage: false
};
}
function* findRelativeConfig(pkgData, envName, caller) {
return {
config: null,
ignore: null
};
}
function* findRootConfig(dirname, envName, caller) {
return null;
}
function* loadConfig(name, dirname, envName, caller) {
throw new Error(`Cannot load ${name} relative to ${dirname} in a browser`);
}
function* resolveShowConfigPath(dirname) {
return null;
}
const ROOT_CONFIG_FILENAMES = exports.ROOT_CONFIG_FILENAMES = [];
function resolvePlugin(name, dirname) {
return null;
}
function resolvePreset(name, dirname) {
return null;
}
function loadPlugin(name, dirname) {
throw new Error(`Cannot load plugin ${name} relative to ${dirname} in a browser`);
}
function loadPreset(name, dirname) {
throw new Error(`Cannot load preset ${name} relative to ${dirname} in a browser`);
}
0 && 0;
//# sourceMappingURL=index-browser.js.map
@@ -1 +0,0 @@
{"version":3,"names":["findConfigUpwards","rootDir","findPackageData","filepath","directories","pkg","isPackage","findRelativeConfig","pkgData","envName","caller","config","ignore","findRootConfig","dirname","loadConfig","name","Error","resolveShowConfigPath","ROOT_CONFIG_FILENAMES","exports","resolvePlugin","resolvePreset","loadPlugin","loadPreset"],"sources":["../../../src/config/files/index-browser.ts"],"sourcesContent":["/* c8 ignore start */\n\nimport type { Handler } from \"gensync\";\n\nimport type {\n ConfigFile,\n IgnoreFile,\n RelativeConfig,\n FilePackageData,\n} from \"./types.ts\";\n\nimport type { CallerMetadata } from \"../validation/options.ts\";\n\nexport type { ConfigFile, IgnoreFile, RelativeConfig, FilePackageData };\n\nexport function findConfigUpwards(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n rootDir: string,\n): string | null {\n return null;\n}\n\n// eslint-disable-next-line require-yield\nexport function* findPackageData(filepath: string): Handler<FilePackageData> {\n return {\n filepath,\n directories: [],\n pkg: null,\n isPackage: false,\n };\n}\n\n// eslint-disable-next-line require-yield\nexport function* findRelativeConfig(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n pkgData: FilePackageData,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n envName: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n caller: CallerMetadata | undefined,\n): Handler<RelativeConfig> {\n return { config: null, ignore: null };\n}\n\n// eslint-disable-next-line require-yield\nexport function* findRootConfig(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n dirname: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n envName: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n caller: CallerMetadata | undefined,\n): Handler<ConfigFile | null> {\n return null;\n}\n\n// eslint-disable-next-line require-yield\nexport function* loadConfig(\n name: string,\n dirname: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n envName: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n caller: CallerMetadata | undefined,\n): Handler<ConfigFile> {\n throw new Error(`Cannot load ${name} relative to ${dirname} in a browser`);\n}\n\n// eslint-disable-next-line require-yield\nexport function* resolveShowConfigPath(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n dirname: string,\n): Handler<string | null> {\n return null;\n}\n\nexport const ROOT_CONFIG_FILENAMES: string[] = [];\n\ntype Resolved =\n | { loader: \"require\"; filepath: string }\n | { loader: \"import\"; filepath: string };\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function resolvePlugin(name: string, dirname: string): Resolved | null {\n return null;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function resolvePreset(name: string, dirname: string): Resolved | null {\n return null;\n}\n\nexport function loadPlugin(\n name: string,\n dirname: string,\n): Handler<{\n filepath: string;\n value: unknown;\n}> {\n throw new Error(\n `Cannot load plugin ${name} relative to ${dirname} in a browser`,\n );\n}\n\nexport function loadPreset(\n name: string,\n dirname: string,\n): Handler<{\n filepath: string;\n value: unknown;\n}> {\n throw new Error(\n `Cannot load preset ${name} relative to ${dirname} in a browser`,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAeO,SAASA,iBAAiBA,CAE/BC,OAAe,EACA;EACf,OAAO,IAAI;AACb;AAGO,UAAUC,eAAeA,CAACC,QAAgB,EAA4B;EAC3E,OAAO;IACLA,QAAQ;IACRC,WAAW,EAAE,EAAE;IACfC,GAAG,EAAE,IAAI;IACTC,SAAS,EAAE;EACb,CAAC;AACH;AAGO,UAAUC,kBAAkBA,CAEjCC,OAAwB,EAExBC,OAAe,EAEfC,MAAkC,EACT;EACzB,OAAO;IAAEC,MAAM,EAAE,IAAI;IAAEC,MAAM,EAAE;EAAK,CAAC;AACvC;AAGO,UAAUC,cAAcA,CAE7BC,OAAe,EAEfL,OAAe,EAEfC,MAAkC,EACN;EAC5B,OAAO,IAAI;AACb;AAGO,UAAUK,UAAUA,CACzBC,IAAY,EACZF,OAAe,EAEfL,OAAe,EAEfC,MAAkC,EACb;EACrB,MAAM,IAAIO,KAAK,CAAC,eAAeD,IAAI,gBAAgBF,OAAO,eAAe,CAAC;AAC5E;AAGO,UAAUI,qBAAqBA,CAEpCJ,OAAe,EACS;EACxB,OAAO,IAAI;AACb;AAEO,MAAMK,qBAA+B,GAAAC,OAAA,CAAAD,qBAAA,GAAG,EAAE;AAO1C,SAASE,aAAaA,CAACL,IAAY,EAAEF,OAAe,EAAmB;EAC5E,OAAO,IAAI;AACb;AAGO,SAASQ,aAAaA,CAACN,IAAY,EAAEF,OAAe,EAAmB;EAC5E,OAAO,IAAI;AACb;AAEO,SAASS,UAAUA,CACxBP,IAAY,EACZF,OAAe,EAId;EACD,MAAM,IAAIG,KAAK,CACb,sBAAsBD,IAAI,gBAAgBF,OAAO,eACnD,CAAC;AACH;AAEO,SAASU,UAAUA,CACxBR,IAAY,EACZF,OAAe,EAId;EACD,MAAM,IAAIG,KAAK,CACb,sBAAsBD,IAAI,gBAAgBF,OAAO,eACnD,CAAC;AACH;AAAC","ignoreList":[]}
@@ -1,78 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "ROOT_CONFIG_FILENAMES", {
enumerable: true,
get: function () {
return _configuration.ROOT_CONFIG_FILENAMES;
}
});
Object.defineProperty(exports, "findConfigUpwards", {
enumerable: true,
get: function () {
return _configuration.findConfigUpwards;
}
});
Object.defineProperty(exports, "findPackageData", {
enumerable: true,
get: function () {
return _package.findPackageData;
}
});
Object.defineProperty(exports, "findRelativeConfig", {
enumerable: true,
get: function () {
return _configuration.findRelativeConfig;
}
});
Object.defineProperty(exports, "findRootConfig", {
enumerable: true,
get: function () {
return _configuration.findRootConfig;
}
});
Object.defineProperty(exports, "loadConfig", {
enumerable: true,
get: function () {
return _configuration.loadConfig;
}
});
Object.defineProperty(exports, "loadPlugin", {
enumerable: true,
get: function () {
return _plugins.loadPlugin;
}
});
Object.defineProperty(exports, "loadPreset", {
enumerable: true,
get: function () {
return _plugins.loadPreset;
}
});
Object.defineProperty(exports, "resolvePlugin", {
enumerable: true,
get: function () {
return _plugins.resolvePlugin;
}
});
Object.defineProperty(exports, "resolvePreset", {
enumerable: true,
get: function () {
return _plugins.resolvePreset;
}
});
Object.defineProperty(exports, "resolveShowConfigPath", {
enumerable: true,
get: function () {
return _configuration.resolveShowConfigPath;
}
});
var _package = require("./package.js");
var _configuration = require("./configuration.js");
var _plugins = require("./plugins.js");
({});
0 && 0;
//# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
{"version":3,"names":["_package","require","_configuration","_plugins"],"sources":["../../../src/config/files/index.ts"],"sourcesContent":["type indexBrowserType = typeof import(\"./index-browser\");\ntype indexType = typeof import(\"./index\");\n\n// Kind of gross, but essentially asserting that the exports of this module are the same as the\n// exports of index-browser, since this file may be replaced at bundle time with index-browser.\n({}) as any as indexBrowserType as indexType;\n\nexport { findPackageData } from \"./package.ts\";\n\nexport {\n findConfigUpwards,\n findRelativeConfig,\n findRootConfig,\n loadConfig,\n resolveShowConfigPath,\n ROOT_CONFIG_FILENAMES,\n} from \"./configuration.ts\";\nexport type {\n ConfigFile,\n IgnoreFile,\n RelativeConfig,\n FilePackageData,\n} from \"./types.ts\";\nexport {\n loadPlugin,\n loadPreset,\n resolvePlugin,\n resolvePreset,\n} from \"./plugins.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,IAAAA,QAAA,GAAAC,OAAA;AAEA,IAAAC,cAAA,GAAAD,OAAA;AAcA,IAAAE,QAAA,GAAAF,OAAA;AAlBA,CAAC,CAAC,CAAC;AAA0C","ignoreList":[]}
@@ -1,211 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = loadCodeDefault;
exports.supportsESM = void 0;
var _async = require("../../gensync-utils/async.js");
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _url() {
const data = require("url");
_url = function () {
return data;
};
return data;
}
require("module");
function _semver() {
const data = require("semver");
_semver = function () {
return data;
};
return data;
}
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
var _rewriteStackTrace = require("../../errors/rewrite-stack-trace.js");
var _configError = require("../../errors/config-error.js");
var _transformFile = require("../../transform-file.js");
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
const debug = _debug()("babel:config:loading:files:module-types");
{
try {
var import_ = require("./import.cjs");
} catch (_unused) {}
}
const supportsESM = exports.supportsESM = _semver().satisfies(process.versions.node, "^12.17 || >=13.2");
const LOADING_CJS_FILES = new Set();
function loadCjsDefault(filepath) {
if (LOADING_CJS_FILES.has(filepath)) {
debug("Auto-ignoring usage of config %o.", filepath);
return {};
}
let module;
try {
LOADING_CJS_FILES.add(filepath);
module = (0, _rewriteStackTrace.endHiddenCallStack)(require)(filepath);
} finally {
LOADING_CJS_FILES.delete(filepath);
}
{
return module != null && (module.__esModule || module[Symbol.toStringTag] === "Module") ? module.default || (arguments[1] ? module : undefined) : module;
}
}
const loadMjsFromPath = (0, _rewriteStackTrace.endHiddenCallStack)(function () {
var _loadMjsFromPath = _asyncToGenerator(function* (filepath) {
const url = (0, _url().pathToFileURL)(filepath).toString() + "?import";
{
if (!import_) {
throw new _configError.default("Internal error: Native ECMAScript modules aren't supported by this platform.\n", filepath);
}
return yield import_(url);
}
});
function loadMjsFromPath(_x) {
return _loadMjsFromPath.apply(this, arguments);
}
return loadMjsFromPath;
}());
const tsNotSupportedError = ext => `\
You are using a ${ext} config file, but Babel only supports transpiling .cts configs. Either:
- Use a .cts config file
- Update to Node.js 23.6.0, which has native TypeScript support
- Install tsx to transpile ${ext} files on the fly\
`;
const SUPPORTED_EXTENSIONS = {
".js": "unknown",
".mjs": "esm",
".cjs": "cjs",
".ts": "unknown",
".mts": "esm",
".cts": "cjs"
};
const asyncModules = new Set();
function* loadCodeDefault(filepath, loader, esmError, tlaError) {
let async;
const ext = _path().extname(filepath);
const isTS = ext === ".ts" || ext === ".cts" || ext === ".mts";
const type = SUPPORTED_EXTENSIONS[hasOwnProperty.call(SUPPORTED_EXTENSIONS, ext) ? ext : ".js"];
const pattern = `${loader} ${type}`;
switch (pattern) {
case "require cjs":
case "auto cjs":
if (isTS) {
return ensureTsSupport(filepath, ext, () => loadCjsDefault(filepath));
} else {
return loadCjsDefault(filepath, arguments[2]);
}
case "auto unknown":
case "require unknown":
case "require esm":
try {
if (isTS) {
return ensureTsSupport(filepath, ext, () => loadCjsDefault(filepath));
} else {
return loadCjsDefault(filepath, arguments[2]);
}
} catch (e) {
if (e.code === "ERR_REQUIRE_ASYNC_MODULE" || e.code === "ERR_REQUIRE_CYCLE_MODULE" && asyncModules.has(filepath)) {
asyncModules.add(filepath);
if (!(async != null ? async : async = yield* (0, _async.isAsync)())) {
throw new _configError.default(tlaError, filepath);
}
} else if (e.code === "ERR_REQUIRE_ESM" || type === "esm") {} else {
throw e;
}
}
case "auto esm":
if (async != null ? async : async = yield* (0, _async.isAsync)()) {
const promise = isTS ? ensureTsSupport(filepath, ext, () => loadMjsFromPath(filepath)) : loadMjsFromPath(filepath);
return (yield* (0, _async.waitFor)(promise)).default;
}
if (isTS) {
throw new _configError.default(tsNotSupportedError(ext), filepath);
} else {
throw new _configError.default(esmError, filepath);
}
default:
throw new Error("Internal Babel error: unreachable code.");
}
}
function ensureTsSupport(filepath, ext, callback) {
if (process.features.typescript || require.extensions[".ts"] || require.extensions[".cts"] || require.extensions[".mts"]) {
return callback();
}
if (ext !== ".cts") {
throw new _configError.default(tsNotSupportedError(ext), filepath);
}
const opts = {
babelrc: false,
configFile: false,
sourceType: "unambiguous",
sourceMaps: "inline",
sourceFileName: _path().basename(filepath),
presets: [[getTSPreset(filepath), Object.assign({
onlyRemoveTypeImports: true,
optimizeConstEnums: true
}, {
allowDeclareFields: true
})]]
};
let handler = function (m, filename) {
if (handler && filename.endsWith(".cts")) {
try {
return m._compile((0, _transformFile.transformFileSync)(filename, Object.assign({}, opts, {
filename
})).code, filename);
} catch (error) {
const packageJson = require("@babel/preset-typescript/package.json");
if (_semver().lt(packageJson.version, "7.21.4")) {
console.error("`.cts` configuration file failed to load, please try to update `@babel/preset-typescript`.");
}
throw error;
}
}
return require.extensions[".js"](m, filename);
};
require.extensions[ext] = handler;
try {
return callback();
} finally {
if (require.extensions[ext] === handler) delete require.extensions[ext];
handler = undefined;
}
}
function getTSPreset(filepath) {
try {
return require("@babel/preset-typescript");
} catch (error) {
if (error.code !== "MODULE_NOT_FOUND") throw error;
let message = "You appear to be using a .cts file as Babel configuration, but the `@babel/preset-typescript` package was not found: please install it!";
{
if (process.versions.pnp) {
message += `
If you are using Yarn Plug'n'Play, you may also need to add the following configuration to your .yarnrc.yml file:
packageExtensions:
\t"@babel/core@*":
\t\tpeerDependencies:
\t\t\t"@babel/preset-typescript": "*"
`;
}
}
throw new _configError.default(message, filepath);
}
}
0 && 0;
//# sourceMappingURL=module-types.js.map
File diff suppressed because one or more lines are too long
@@ -1,61 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.findPackageData = findPackageData;
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
var _utils = require("./utils.js");
var _configError = require("../../errors/config-error.js");
const PACKAGE_FILENAME = "package.json";
const readConfigPackage = (0, _utils.makeStaticFileCache)((filepath, content) => {
let options;
try {
options = JSON.parse(content);
} catch (err) {
throw new _configError.default(`Error while parsing JSON - ${err.message}`, filepath);
}
if (!options) throw new Error(`${filepath}: No config detected`);
if (typeof options !== "object") {
throw new _configError.default(`Config returned typeof ${typeof options}`, filepath);
}
if (Array.isArray(options)) {
throw new _configError.default(`Expected config object but found array`, filepath);
}
return {
filepath,
dirname: _path().dirname(filepath),
options
};
});
function* findPackageData(filepath) {
let pkg = null;
const directories = [];
let isPackage = true;
let dirname = _path().dirname(filepath);
while (!pkg && _path().basename(dirname) !== "node_modules") {
directories.push(dirname);
pkg = yield* readConfigPackage(_path().join(dirname, PACKAGE_FILENAME));
const nextLoc = _path().dirname(dirname);
if (dirname === nextLoc) {
isPackage = false;
break;
}
dirname = nextLoc;
}
return {
filepath,
directories,
pkg,
isPackage
};
}
0 && 0;
//# sourceMappingURL=package.js.map
@@ -1 +0,0 @@
{"version":3,"names":["_path","data","require","_utils","_configError","PACKAGE_FILENAME","readConfigPackage","makeStaticFileCache","filepath","content","options","JSON","parse","err","ConfigError","message","Error","Array","isArray","dirname","path","findPackageData","pkg","directories","isPackage","basename","push","join","nextLoc"],"sources":["../../../src/config/files/package.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { Handler } from \"gensync\";\nimport { makeStaticFileCache } from \"./utils.ts\";\n\nimport type { ConfigFile, FilePackageData } from \"./types.ts\";\n\nimport ConfigError from \"../../errors/config-error.ts\";\n\nconst PACKAGE_FILENAME = \"package.json\";\n\nconst readConfigPackage = makeStaticFileCache(\n (filepath, content): ConfigFile => {\n let options;\n try {\n options = JSON.parse(content) as unknown;\n } catch (err) {\n throw new ConfigError(\n `Error while parsing JSON - ${err.message}`,\n filepath,\n );\n }\n\n if (!options) throw new Error(`${filepath}: No config detected`);\n\n if (typeof options !== \"object\") {\n throw new ConfigError(\n `Config returned typeof ${typeof options}`,\n filepath,\n );\n }\n if (Array.isArray(options)) {\n throw new ConfigError(`Expected config object but found array`, filepath);\n }\n\n return {\n filepath,\n dirname: path.dirname(filepath),\n options,\n };\n },\n);\n\n/**\n * Find metadata about the package that this file is inside of. Resolution\n * of Babel's config requires general package information to decide when to\n * search for .babelrc files\n */\nexport function* findPackageData(filepath: string): Handler<FilePackageData> {\n let pkg = null;\n const directories = [];\n let isPackage = true;\n\n let dirname = path.dirname(filepath);\n while (!pkg && path.basename(dirname) !== \"node_modules\") {\n directories.push(dirname);\n\n pkg = yield* readConfigPackage(path.join(dirname, PACKAGE_FILENAME));\n\n const nextLoc = path.dirname(dirname);\n if (dirname === nextLoc) {\n isPackage = false;\n break;\n }\n dirname = nextLoc;\n }\n\n return { filepath, directories, pkg, isPackage };\n}\n"],"mappings":";;;;;;AAAA,SAAAA,MAAA;EAAA,MAAAC,IAAA,GAAAC,OAAA;EAAAF,KAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAEA,IAAAE,MAAA,GAAAD,OAAA;AAIA,IAAAE,YAAA,GAAAF,OAAA;AAEA,MAAMG,gBAAgB,GAAG,cAAc;AAEvC,MAAMC,iBAAiB,GAAG,IAAAC,0BAAmB,EAC3C,CAACC,QAAQ,EAAEC,OAAO,KAAiB;EACjC,IAAIC,OAAO;EACX,IAAI;IACFA,OAAO,GAAGC,IAAI,CAACC,KAAK,CAACH,OAAO,CAAY;EAC1C,CAAC,CAAC,OAAOI,GAAG,EAAE;IACZ,MAAM,IAAIC,oBAAW,CACnB,8BAA8BD,GAAG,CAACE,OAAO,EAAE,EAC3CP,QACF,CAAC;EACH;EAEA,IAAI,CAACE,OAAO,EAAE,MAAM,IAAIM,KAAK,CAAC,GAAGR,QAAQ,sBAAsB,CAAC;EAEhE,IAAI,OAAOE,OAAO,KAAK,QAAQ,EAAE;IAC/B,MAAM,IAAII,oBAAW,CACnB,0BAA0B,OAAOJ,OAAO,EAAE,EAC1CF,QACF,CAAC;EACH;EACA,IAAIS,KAAK,CAACC,OAAO,CAACR,OAAO,CAAC,EAAE;IAC1B,MAAM,IAAII,oBAAW,CAAC,wCAAwC,EAAEN,QAAQ,CAAC;EAC3E;EAEA,OAAO;IACLA,QAAQ;IACRW,OAAO,EAAEC,MAAGA,CAAC,CAACD,OAAO,CAACX,QAAQ,CAAC;IAC/BE;EACF,CAAC;AACH,CACF,CAAC;AAOM,UAAUW,eAAeA,CAACb,QAAgB,EAA4B;EAC3E,IAAIc,GAAG,GAAG,IAAI;EACd,MAAMC,WAAW,GAAG,EAAE;EACtB,IAAIC,SAAS,GAAG,IAAI;EAEpB,IAAIL,OAAO,GAAGC,MAAGA,CAAC,CAACD,OAAO,CAACX,QAAQ,CAAC;EACpC,OAAO,CAACc,GAAG,IAAIF,MAAGA,CAAC,CAACK,QAAQ,CAACN,OAAO,CAAC,KAAK,cAAc,EAAE;IACxDI,WAAW,CAACG,IAAI,CAACP,OAAO,CAAC;IAEzBG,GAAG,GAAG,OAAOhB,iBAAiB,CAACc,MAAGA,CAAC,CAACO,IAAI,CAACR,OAAO,EAAEd,gBAAgB,CAAC,CAAC;IAEpE,MAAMuB,OAAO,GAAGR,MAAGA,CAAC,CAACD,OAAO,CAACA,OAAO,CAAC;IACrC,IAAIA,OAAO,KAAKS,OAAO,EAAE;MACvBJ,SAAS,GAAG,KAAK;MACjB;IACF;IACAL,OAAO,GAAGS,OAAO;EACnB;EAEA,OAAO;IAAEpB,QAAQ;IAAEe,WAAW;IAAED,GAAG;IAAEE;EAAU,CAAC;AAClD;AAAC","ignoreList":[]}
@@ -1,230 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.loadPlugin = loadPlugin;
exports.loadPreset = loadPreset;
exports.resolvePreset = exports.resolvePlugin = void 0;
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
var _async = require("../../gensync-utils/async.js");
var _moduleTypes = require("./module-types.js");
function _url() {
const data = require("url");
_url = function () {
return data;
};
return data;
}
var _importMetaResolve = require("../../vendor/import-meta-resolve.js");
require("module");
function _fs() {
const data = require("fs");
_fs = function () {
return data;
};
return data;
}
const debug = _debug()("babel:config:loading:files:plugins");
const EXACT_RE = /^module:/;
const BABEL_PLUGIN_PREFIX_RE = /^(?!@|module:|[^/]+\/|babel-plugin-)/;
const BABEL_PRESET_PREFIX_RE = /^(?!@|module:|[^/]+\/|babel-preset-)/;
const BABEL_PLUGIN_ORG_RE = /^(@babel\/)(?!plugin-|[^/]+\/)/;
const BABEL_PRESET_ORG_RE = /^(@babel\/)(?!preset-|[^/]+\/)/;
const OTHER_PLUGIN_ORG_RE = /^(@(?!babel\/)[^/]+\/)(?![^/]*babel-plugin(?:-|\/|$)|[^/]+\/)/;
const OTHER_PRESET_ORG_RE = /^(@(?!babel\/)[^/]+\/)(?![^/]*babel-preset(?:-|\/|$)|[^/]+\/)/;
const OTHER_ORG_DEFAULT_RE = /^(@(?!babel$)[^/]+)$/;
const resolvePlugin = exports.resolvePlugin = resolveStandardizedName.bind(null, "plugin");
const resolvePreset = exports.resolvePreset = resolveStandardizedName.bind(null, "preset");
function* loadPlugin(name, dirname) {
const {
filepath,
loader
} = resolvePlugin(name, dirname, yield* (0, _async.isAsync)());
const value = yield* requireModule("plugin", loader, filepath);
debug("Loaded plugin %o from %o.", name, dirname);
return {
filepath,
value
};
}
function* loadPreset(name, dirname) {
const {
filepath,
loader
} = resolvePreset(name, dirname, yield* (0, _async.isAsync)());
const value = yield* requireModule("preset", loader, filepath);
debug("Loaded preset %o from %o.", name, dirname);
return {
filepath,
value
};
}
function standardizeName(type, name) {
if (_path().isAbsolute(name)) return name;
const isPreset = type === "preset";
return name.replace(isPreset ? BABEL_PRESET_PREFIX_RE : BABEL_PLUGIN_PREFIX_RE, `babel-${type}-`).replace(isPreset ? BABEL_PRESET_ORG_RE : BABEL_PLUGIN_ORG_RE, `$1${type}-`).replace(isPreset ? OTHER_PRESET_ORG_RE : OTHER_PLUGIN_ORG_RE, `$1babel-${type}-`).replace(OTHER_ORG_DEFAULT_RE, `$1/babel-${type}`).replace(EXACT_RE, "");
}
function* resolveAlternativesHelper(type, name) {
const standardizedName = standardizeName(type, name);
const {
error,
value
} = yield standardizedName;
if (!error) return value;
if (error.code !== "MODULE_NOT_FOUND") throw error;
if (standardizedName !== name && !(yield name).error) {
error.message += `\n- If you want to resolve "${name}", use "module:${name}"`;
}
if (!(yield standardizeName(type, "@babel/" + name)).error) {
error.message += `\n- Did you mean "@babel/${name}"?`;
}
const oppositeType = type === "preset" ? "plugin" : "preset";
if (!(yield standardizeName(oppositeType, name)).error) {
error.message += `\n- Did you accidentally pass a ${oppositeType} as a ${type}?`;
}
if (type === "plugin") {
const transformName = standardizedName.replace("-proposal-", "-transform-");
if (transformName !== standardizedName && !(yield transformName).error) {
error.message += `\n- Did you mean "${transformName}"?`;
}
}
error.message += `\n
Make sure that all the Babel plugins and presets you are using
are defined as dependencies or devDependencies in your package.json
file. It's possible that the missing plugin is loaded by a preset
you are using that forgot to add the plugin to its dependencies: you
can workaround this problem by explicitly adding the missing package
to your top-level package.json.
`;
throw error;
}
function tryRequireResolve(id, dirname) {
try {
if (dirname) {
return {
error: null,
value: (((v, w) => (v = v.split("."), w = w.split("."), +v[0] > +w[0] || v[0] == w[0] && +v[1] >= +w[1]))(process.versions.node, "8.9") ? require.resolve : (r, {
paths: [b]
}, M = require("module")) => {
let f = M._findPath(r, M._nodeModulePaths(b).concat(b));
if (f) return f;
f = new Error(`Cannot resolve module '${r}'`);
f.code = "MODULE_NOT_FOUND";
throw f;
})(id, {
paths: [dirname]
})
};
} else {
return {
error: null,
value: require.resolve(id)
};
}
} catch (error) {
return {
error,
value: null
};
}
}
function tryImportMetaResolve(id, options) {
try {
return {
error: null,
value: (0, _importMetaResolve.resolve)(id, options)
};
} catch (error) {
return {
error,
value: null
};
}
}
function resolveStandardizedNameForRequire(type, name, dirname) {
const it = resolveAlternativesHelper(type, name);
let res = it.next();
while (!res.done) {
res = it.next(tryRequireResolve(res.value, dirname));
}
return {
loader: "require",
filepath: res.value
};
}
function resolveStandardizedNameForImport(type, name, dirname) {
const parentUrl = (0, _url().pathToFileURL)(_path().join(dirname, "./babel-virtual-resolve-base.js")).href;
const it = resolveAlternativesHelper(type, name);
let res = it.next();
while (!res.done) {
res = it.next(tryImportMetaResolve(res.value, parentUrl));
}
return {
loader: "auto",
filepath: (0, _url().fileURLToPath)(res.value)
};
}
function resolveStandardizedName(type, name, dirname, allowAsync) {
if (!_moduleTypes.supportsESM || !allowAsync) {
return resolveStandardizedNameForRequire(type, name, dirname);
}
try {
const resolved = resolveStandardizedNameForImport(type, name, dirname);
if (!(0, _fs().existsSync)(resolved.filepath)) {
throw Object.assign(new Error(`Could not resolve "${name}" in file ${dirname}.`), {
type: "MODULE_NOT_FOUND"
});
}
return resolved;
} catch (e) {
try {
return resolveStandardizedNameForRequire(type, name, dirname);
} catch (e2) {
if (e.type === "MODULE_NOT_FOUND") throw e;
if (e2.type === "MODULE_NOT_FOUND") throw e2;
throw e;
}
}
}
{
var LOADING_MODULES = new Set();
}
function* requireModule(type, loader, name) {
{
if (!(yield* (0, _async.isAsync)()) && LOADING_MODULES.has(name)) {
throw new Error(`Reentrant ${type} detected trying to load "${name}". This module is not ignored ` + "and is trying to load itself while compiling itself, leading to a dependency cycle. " + 'We recommend adding it to your "ignore" list in your babelrc, or to a .babelignore.');
}
}
try {
{
LOADING_MODULES.add(name);
}
{
return yield* (0, _moduleTypes.default)(name, loader, `You appear to be using a native ECMAScript module ${type}, ` + "which is only supported when running Babel asynchronously " + "or when using the Node.js `--experimental-require-module` flag.", `You appear to be using a ${type} that contains top-level await, ` + "which is only supported when running Babel asynchronously.", true);
}
} catch (err) {
err.message = `[BABEL]: ${err.message} (While processing: ${name})`;
throw err;
} finally {
{
LOADING_MODULES.delete(name);
}
}
}
0 && 0;
//# sourceMappingURL=plugins.js.map
File diff suppressed because one or more lines are too long
@@ -1,5 +0,0 @@
"use strict";
0 && 0;
//# sourceMappingURL=types.js.map
@@ -1 +0,0 @@
{"version":3,"names":[],"sources":["../../../src/config/files/types.ts"],"sourcesContent":["import type { InputOptions } from \"../index.ts\";\n\nexport type ConfigFile = {\n filepath: string;\n dirname: string;\n options: InputOptions & { babel?: unknown };\n};\n\nexport type IgnoreFile = {\n filepath: string;\n dirname: string;\n ignore: Array<RegExp>;\n};\n\nexport type RelativeConfig = {\n // The actual config, either from package.json#babel, .babelrc, or\n // .babelrc.js, if there was one.\n config: ConfigFile | null;\n // The .babelignore, if there was one.\n ignore: IgnoreFile | null;\n};\n\nexport type FilePackageData = {\n // The file in the package.\n filepath: string;\n // Any ancestor directories of the file that are within the package.\n directories: Array<string>;\n // The contents of the package.json. May not be found if the package just\n // terminated at a node_modules folder without finding one.\n pkg: ConfigFile | null;\n // True if a package.json or node_modules folder was found while traversing\n // the directory structure.\n isPackage: boolean;\n};\n"],"mappings":"","ignoreList":[]}

Some files were not shown because too many files have changed in this diff Show More