@davidbc01/telar 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -110,7 +110,7 @@ Telar compila a HTML + CSS + JavaScript optimizados. El desarrollador nunca toca
110
110
  | CLI — compilar, servir, verificar | ✅ Completo |
111
111
  | Publicado en npm | ✅ Completo |
112
112
  | Live reload en telar servir | ✅ Completo |
113
- | Extensión VS Code | 🔄 En desarrollo |
113
+ | Extensión VS Code | Completo |
114
114
  | Lanzamiento público | 🟪 Pendiente |
115
115
 
116
116
  ## Hoja de ruta
@@ -130,7 +130,7 @@ Telar compila a HTML + CSS + JavaScript optimizados. El desarrollador nunca toca
130
130
 
131
131
  ### v0.3 - Experiencia de desarrollo
132
132
  - [x] Live reload en `telar servir`
133
- - [ ] Extensión para VS Code
133
+ - [x] Extensión para VS Code
134
134
  - [ ] Mensajes de error mejorados con contexto visual
135
135
 
136
136
  ### v1.0 - Lanzamiento público
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davidbc01/telar",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Lenguaje de programación declarativo para la web, escrito en español",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/dist/carrito.html DELETED
@@ -1,31 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="es">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Tu carrito</title>
7
-
8
-
9
-
10
- <link rel="stylesheet" href="telar.css">
11
- </head>
12
- <body>
13
- <main role="main">
14
- <h1>Tu carrito</h1>
15
- <div data-si="hay-resultados">
16
-
17
- </div>
18
- <p class="campo" data-campo="pedido.productos">{pedido.productos}</p>
19
- <p class="campo" data-campo="Total: (pedido.total) €">{Total: (pedido.total) €}</p>
20
- <a href="/pago" class="boton" role="button">Finalizar compra</a>
21
- <div data-si="hay-resultados">
22
-
23
- </div>
24
- <section class="lista" data-modelo="Tu carrito está vacío">
25
- <div class="cargando" aria-live="polite">Cargando...</div>
26
- </section>
27
- <a href="/inicio" class="boton" role="button">Ver productos</a>
28
- </main>
29
- <script src="telar.js" defer></script>
30
- </body>
31
- </html>
package/dist/entrar.html DELETED
@@ -1,36 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="es">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Entrar</title>
7
-
8
-
9
-
10
- <link rel="stylesheet" href="telar.css">
11
- </head>
12
- <body>
13
- <main role="main">
14
- <h1>Entrar</h1>
15
- <div class="campo-grupo">
16
- <label for="correo-electrnico">Correo electrónico</label>
17
- <input type="email" id="correo-electrnico" name="correo-electrnico" autocomplete="email">
18
- </div>
19
- <div class="campo-grupo">
20
- <label for="contrasea">Contraseña</label>
21
- <input type="contraseña" id="contrasea" name="contrasea" >
22
- </div>
23
- <button class="boton" data-accion="iniciarSesion" type="button">
24
- Entrar
25
- </button>
26
- <div class="error" role="alert" hidden>
27
-
28
- </div>
29
- <section class="lista" data-modelo="Correo o contraseña incorrectos">
30
- <div class="cargando" aria-live="polite">Cargando...</div>
31
- </section>
32
- <a href="/recuperarContraseña" class="boton" role="button">¿Olvidaste tu contraseña?</a>
33
- </main>
34
- <script src="telar.js" defer></script>
35
- </body>
36
- </html>
package/dist/index.html DELETED
@@ -1,42 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="es">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Bienvenido a MiTienda</title>
7
- <meta name="description" content="Los mejores productos al mejor precio">
8
- <meta http-equiv="Cache-Control" content="max-age=600">
9
- <meta name="mobile-web-app-capable" content="yes">
10
- <link rel="stylesheet" href="telar.css">
11
- </head>
12
- <body>
13
- <main role="main">
14
- <h1>Bienvenido a MiTienda</h1>
15
- <p class="descripcion">Los mejores productos al mejor precio</p>
16
- <section class="lista" data-modelo="Producto" data-maximo="8" data-ordenar="fecha" data-recientes="true">
17
- <div class="cargando" aria-live="polite">Cargando...</div>
18
- <div class="error" role="alert" hidden>
19
-
20
- </div>
21
- </section>
22
- <p class="campo" data-campo="No se pudieron cargar los productos.">{No se pudieron cargar los productos.}</p>
23
- <button class="reintentar" data-reintentar="10" type="button">
24
- Reintentar
25
- </button>
26
- <div data-si="usuario-conectado">
27
-
28
- </div>
29
- <p class="campo" data-campo="Hola, (usuario.nombre)">{Hola, (usuario.nombre)}</p>
30
- <a href="/cuenta" class="boton" role="button">Mi cuenta</a>
31
- <a href="/carrito" class="boton" role="button">Mi carrito</a>
32
- <div data-si="hay-resultados">
33
-
34
- </div>
35
- <a href="/login" class="boton" role="button">Entrar</a>
36
- <a href="/registro" class="boton" role="button">Registrarse</a>
37
-
38
-
39
- </main>
40
- <script src="telar.js" defer></script>
41
- </body>
42
- </html>
@@ -1,38 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="es">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>(producto.nombre)</title>
7
-
8
-
9
- <meta name="mobile-web-app-capable" content="yes">
10
- <link rel="stylesheet" href="telar.css">
11
- </head>
12
- <body>
13
- <main role="main">
14
- <h1>(producto.nombre)</h1>
15
- <p class="campo" data-campo="producto.imagen">{producto.imagen}</p>
16
- <p class="campo" data-campo="producto.nombre">{producto.nombre}</p>
17
- <p class="campo" data-campo="producto.precio">{producto.precio}</p>
18
- <p class="campo" data-campo="producto.descripción">{producto.descripción}</p>
19
- <div data-si="producto.stock-mayor-0">
20
-
21
- </div>
22
- <button class="boton" data-accion="añadirAlCarrito" type="button">
23
- Añadir al carrito
24
- </button>
25
- <div data-si="hay-resultados">
26
-
27
- </div>
28
- <section class="lista" data-modelo="Sin stock — avísame cuando llegue">
29
- <div class="cargando" aria-live="polite">Cargando...</div>
30
- </section>
31
- <button class="boton" data-accion="suscribirStock" type="button">
32
- Avisarme
33
- </button>
34
-
35
- </main>
36
- <script src="telar.js" defer></script>
37
- </body>
38
- </html>
package/dist/telar.css DELETED
@@ -1,118 +0,0 @@
1
- /* Telar — estilos base generados automáticamente */
2
-
3
- *, *::before, *::after {
4
- box-sizing: border-box;
5
- margin: 0;
6
- padding: 0;
7
- }
8
-
9
- :root {
10
- --color-fondo: #ffffff;
11
- --color-texto: #1a1a1a;
12
- --color-primario: #5B4AB7;
13
- --color-borde: #e0e0e0;
14
- --color-error: #dc2626;
15
- --radio: 8px;
16
- --espacio: 1rem;
17
- font-size: 16px;
18
- }
19
-
20
- body {
21
- font-family: system-ui, -apple-system, sans-serif;
22
- background: var(--color-fondo);
23
- color: var(--color-texto);
24
- line-height: 1.6;
25
- padding: var(--espacio);
26
- }
27
-
28
- main {
29
- max-width: 680px;
30
- margin: 0 auto;
31
- padding: var(--espacio);
32
- }
33
-
34
- h1 {
35
- font-size: 2rem;
36
- font-weight: 700;
37
- margin-bottom: var(--espacio);
38
- }
39
-
40
- .descripcion {
41
- color: #555;
42
- margin-bottom: var(--espacio);
43
- }
44
-
45
- .boton {
46
- display: inline-block;
47
- padding: 0.6rem 1.2rem;
48
- background: var(--color-primario);
49
- color: white;
50
- border: none;
51
- border-radius: var(--radio);
52
- font-size: 1rem;
53
- cursor: pointer;
54
- text-decoration: none;
55
- margin: 0.25rem 0;
56
- }
57
-
58
- .boton:hover {
59
-
60
- }
61
-
62
- .campo-grupo {
63
- display: flex;
64
- flex-direction: column;
65
- gap: 0.4rem;
66
- margin-bottom: var(--espacio);
67
- }
68
-
69
- label {
70
- font-weight: 500;
71
- font-size: 0.9rem;
72
- }
73
-
74
- input, textarea {
75
- padding: 0.6rem 0.8rem;
76
- border: 1px solid var(--color-borde);
77
- border-radius: var(--radio);
78
- font-size: 1rem;
79
- width: 100%;
80
- }
81
-
82
- input:focus, textarea:focus {
83
- outline: 2px solid var(--color-primario);
84
- border-color: transparent;
85
- }
86
-
87
- .lista {
88
- margin: var(--espacio) 0;
89
- }
90
-
91
- .cargando {
92
- color: #888;
93
- font-size: 0.9rem;
94
- }
95
-
96
- .error {
97
- color: var(--color-error);
98
- font-size: 0.9rem;
99
- padding: 0.5rem;
100
- border: 1px solid var(--color-error);
101
- border-radius: var(--radio);
102
- margin: 0.5rem 0;
103
- }
104
-
105
- .reintentar {
106
- background: none;
107
- border: 1px solid var(--color-borde);
108
- border-radius: var(--radio);
109
- padding: 0.4rem 0.8rem;
110
- cursor: pointer;
111
- font-size: 0.85rem;
112
- }
113
-
114
- /* Responsive — optimizar para móvil */
115
- @media (max-width: 600px) {
116
- h1 { font-size: 1.5rem; }
117
- main { padding: 0.5rem; }
118
- }
package/dist/telar.js DELETED
@@ -1,251 +0,0 @@
1
- // Telar runtime — generado automáticamente
2
- 'use strict';
3
-
4
- const Telar = {
5
- // Estado de la sesión
6
- usuario: null,
7
-
8
- // Inicializar sesión desde localStorage
9
- iniciarSesion() {
10
- try {
11
- const datos = localStorage.getItem('telar_usuario')
12
- if (datos) this.usuario = JSON.parse(datos)
13
- } catch (e) {
14
- this.usuario = null
15
- }
16
- },
17
-
18
- // Comprobar condiciones
19
- evaluar(condicion) {
20
- switch (condicion) {
21
- case 'usuario-conectado': return this.usuario !== null
22
- case 'usuario-admin': return this.usuario?.rol === 'admin'
23
- case 'hay-resultados': return true // se actualiza dinámicamente
24
- default: return false
25
- }
26
- },
27
-
28
- // Mostrar u ocultar elementos según condición
29
- aplicarCondicion(condicion) {
30
- const elementos = document.querySelectorAll(`[data-si="${condicion}"]`)
31
- const elementosNo = document.querySelectorAll(`[data-si-no="${condicion}"]`)
32
- const valor = this.evaluar(condicion)
33
-
34
- elementos.forEach(el => {
35
- el.style.display = valor ? '' : 'none'
36
- })
37
- elementosNo.forEach(el => {
38
- el.style.display = valor ? 'none' : ''
39
- })
40
- },
41
-
42
- // Cargar datos desde la API
43
- async cargar(modelo, opciones = {}) {
44
- const params = new URLSearchParams()
45
- if (opciones.maximo) params.set('limit', opciones.maximo)
46
- if (opciones.ordenar) params.set('sort', opciones.ordenar)
47
- if (opciones.recientes) params.set('recientes', 'true')
48
-
49
- const url = `/api/${modelo.toLowerCase()}?${params}`
50
-
51
- try {
52
- const res = await fetch(url)
53
- if (!res.ok) throw new Error(`Error ${res.status}`)
54
- return await res.json()
55
- } catch (error) {
56
- throw error
57
- }
58
- },
59
-
60
- // Mostrar error en un contenedor
61
- mostrarError(contenedor, mensaje) {
62
- const errorEl = contenedor.querySelector('.error')
63
- const cargandoEl = contenedor.querySelector('.cargando')
64
- if (cargandoEl) cargandoEl.style.display = 'none'
65
- if (errorEl) {
66
- errorEl.textContent = mensaje
67
- errorEl.removeAttribute('hidden')
68
- }
69
- },
70
-
71
- // Renderizar lista de items
72
- renderizarLista(contenedor, items, modelo) {
73
- const cargandoEl = contenedor.querySelector('.cargando')
74
- const errorEl = contenedor.querySelector('.error')
75
- if (cargandoEl) cargandoEl.style.display = 'none'
76
- if (errorEl) errorEl.setAttribute('hidden', '')
77
-
78
- if (!items || items.length === 0) {
79
- contenedor.setAttribute('data-vacio', 'true')
80
- this.aplicarCondicion('hay-resultados')
81
- return
82
- }
83
-
84
- // Renderizar cada item
85
- const lista = document.createElement('ul')
86
- lista.className = 'telar-lista'
87
- items.forEach(item => {
88
- const li = document.createElement('li')
89
- li.className = 'telar-item'
90
- li.innerHTML = this.renderizarItem(item, modelo)
91
- lista.appendChild(li)
92
- })
93
-
94
- contenedor.appendChild(lista)
95
- this.aplicarCondicion('hay-resultados')
96
- },
97
-
98
- // Renderizar un item individual
99
- renderizarItem(item, modelo) {
100
- return Object.entries(item)
101
- .map(([clave, valor]) => `<p><strong>${clave}:</strong> ${valor}</p>`)
102
- .join('')
103
- },
104
-
105
- // Reintentar una operación después de N segundos
106
- reintentar(fn, segundos) {
107
- setTimeout(fn, segundos * 1000)
108
- }
109
- };
110
-
111
- // Aplicar condiciones dinámicas
112
- function aplicarCondiciones() {
113
- Telar.aplicarCondicion('usuario-conectado');
114
- Telar.aplicarCondicion('hay-resultados');
115
- Telar.aplicarCondicion('producto.stock-mayor-0');
116
- }
117
-
118
- // Cargar Producto
119
- async function cargarProducto() {
120
- const contenedor = document.querySelector('[data-modelo="Producto"]')
121
- if (!contenedor) return
122
-
123
- try {
124
- const datos = await Telar.cargar('Producto', { recientes: true, maximo: '8', ordenar: 'fecha' })
125
- Telar.renderizarLista(contenedor, datos, 'Producto')
126
- } catch (error) {
127
- Telar.mostrarError(contenedor, 'Error al cargar los datos')
128
-
129
- }
130
- }
131
-
132
- // Cargar Sin stock — avísame cuando llegue
133
- async function cargarSin stock — avísame cuando llegue() {
134
- const contenedor = document.querySelector('[data-modelo="Sin stock — avísame cuando llegue"]')
135
- if (!contenedor) return
136
-
137
- try {
138
- const datos = await Telar.cargar('Sin stock — avísame cuando llegue', { })
139
- Telar.renderizarLista(contenedor, datos, 'Sin stock — avísame cuando llegue')
140
- } catch (error) {
141
- Telar.mostrarError(contenedor, 'Error al cargar los datos')
142
-
143
- }
144
- }
145
-
146
- // Cargar Tu carrito está vacío
147
- async function cargarTu carrito está vacío() {
148
- const contenedor = document.querySelector('[data-modelo="Tu carrito está vacío"]')
149
- if (!contenedor) return
150
-
151
- try {
152
- const datos = await Telar.cargar('Tu carrito está vacío', { })
153
- Telar.renderizarLista(contenedor, datos, 'Tu carrito está vacío')
154
- } catch (error) {
155
- Telar.mostrarError(contenedor, 'Error al cargar los datos')
156
-
157
- }
158
- }
159
-
160
- // Cargar Correo o contraseña incorrectos
161
- async function cargarCorreo o contraseña incorrectos() {
162
- const contenedor = document.querySelector('[data-modelo="Correo o contraseña incorrectos"]')
163
- if (!contenedor) return
164
-
165
- try {
166
- const datos = await Telar.cargar('Correo o contraseña incorrectos', { })
167
- Telar.renderizarLista(contenedor, datos, 'Correo o contraseña incorrectos')
168
- } catch (error) {
169
- Telar.mostrarError(contenedor, 'Error al cargar los datos')
170
-
171
- }
172
- }
173
-
174
-
175
- // Acción: añadirAlCarrito
176
- async function añadirAlCarrito() {
177
- const boton = document.querySelector('[data-accion="añadirAlCarrito"]')
178
- const errorEl = boton?.nextElementSibling
179
-
180
- try {
181
- if (boton) boton.disabled = true
182
- const res = await fetch('/api/accion/añadirAlCarrito', { method: 'POST' })
183
- if (!res.ok) throw new Error()
184
- // Acción completada — redirigir o actualizar según contexto
185
- } catch (error) {
186
- if (errorEl && errorEl.classList.contains('error')) {
187
- errorEl.removeAttribute('hidden')
188
- }
189
- } finally {
190
- if (boton) boton.disabled = false
191
- }
192
- }
193
-
194
- // Acción: suscribirStock
195
- async function suscribirStock() {
196
- const boton = document.querySelector('[data-accion="suscribirStock"]')
197
- const errorEl = boton?.nextElementSibling
198
-
199
- try {
200
- if (boton) boton.disabled = true
201
- const res = await fetch('/api/accion/suscribirStock', { method: 'POST' })
202
- if (!res.ok) throw new Error()
203
- // Acción completada — redirigir o actualizar según contexto
204
- } catch (error) {
205
- if (errorEl && errorEl.classList.contains('error')) {
206
- errorEl.removeAttribute('hidden')
207
- }
208
- } finally {
209
- if (boton) boton.disabled = false
210
- }
211
- }
212
-
213
- // Acción: iniciarSesion
214
- async function iniciarSesion() {
215
- const boton = document.querySelector('[data-accion="iniciarSesion"]')
216
- const errorEl = boton?.nextElementSibling
217
-
218
- try {
219
- if (boton) boton.disabled = true
220
- const res = await fetch('/api/accion/iniciarSesion', { method: 'POST' })
221
- if (!res.ok) throw new Error()
222
- // Acción completada — redirigir o actualizar según contexto
223
- } catch (error) {
224
- if (errorEl && errorEl.classList.contains('error')) {
225
- errorEl.removeAttribute('hidden')
226
- }
227
- } finally {
228
- if (boton) boton.disabled = false
229
- }
230
- }
231
-
232
- // Registrar listeners de acciones
233
- function registrarAcciones() {
234
- document.querySelector('[data-accion="añadirAlCarrito"]')
235
- ?.addEventListener('click', añadirAlCarrito);
236
- document.querySelector('[data-accion="suscribirStock"]')
237
- ?.addEventListener('click', suscribirStock);
238
- document.querySelector('[data-accion="iniciarSesion"]')
239
- ?.addEventListener('click', iniciarSesion);
240
- }
241
-
242
- // Inicializar cuando el DOM esté listo
243
- document.addEventListener('DOMContentLoaded', function() {
244
- Telar.iniciarSesion();
245
- aplicarCondiciones();
246
- registrarAcciones();
247
- cargarProducto();
248
- cargarSin stock — avísame cuando llegue();
249
- cargarTu carrito está vacío();
250
- cargarCorreo o contraseña incorrectos();
251
- });