|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 95 KiB |
@ -0,0 +1,576 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"background": "oklch(0.52 0.125 74)",
|
||||||
|
"clase": "saltar svelte-12qhfyh",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 42.390625,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "8px 16px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Saltar al contenido",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 163.3125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "marca svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Newsreader, \"Iowan Old Style\", Georgia, serif",
|
||||||
|
"height": 38,
|
||||||
|
"line": "39.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "34px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Senza Paura",
|
||||||
|
"weight": "600",
|
||||||
|
"width": 226.3125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 26,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "4px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Música",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 51.328125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 26,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "4px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Músicos y grupos",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 124.203125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 26,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "4px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Taller de letra",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 93.625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.52 0.125 74)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 26,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "4px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Blog",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 32.796875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "accion-icono jukebox svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Juke-box",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 44
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 35.390625,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "4px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Entrar",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 43.78125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "accion-icono buscar-contenido svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Buscar en Senza Paura",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 44
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "accion-icono carrito svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Carrito",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 44
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "boton svelte-10d17h5",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "1px 6px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"text": "Cambiar al tema oscuro",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 44
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1971vt9",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Newsreader, \"Iowan Old Style\", Georgia, serif",
|
||||||
|
"height": 52.796875,
|
||||||
|
"line": "52.8px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "48px",
|
||||||
|
"tag": "H1",
|
||||||
|
"text": "Blog",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 568.40625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1971vt9",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 15,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Seguirlo por RSS",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 106.84375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "visually-hidden svelte-egko5e",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 1,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "LABEL",
|
||||||
|
"text": "Buscar una entrada",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-egko5e",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 48.75,
|
||||||
|
"line": "24.75px",
|
||||||
|
"padding": "12px 24px",
|
||||||
|
"size": "15px",
|
||||||
|
"tag": "INPUT",
|
||||||
|
"text": "Buscar por título o por lo que cuenta",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 962.984375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "vista svelte-egko5e oro",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 48.75,
|
||||||
|
"line": "24.75px",
|
||||||
|
"padding": "12px 24px",
|
||||||
|
"size": "15px",
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"text": "Tarjetas",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 100.890625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "vista svelte-egko5e",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 48.75,
|
||||||
|
"line": "24.75px",
|
||||||
|
"padding": "12px 24px",
|
||||||
|
"size": "15px",
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"text": "Tabla",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 85.125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "etiqueta svelte-q0tptl",
|
||||||
|
"color": "oklch(0.45 0.13 290)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 28,
|
||||||
|
"line": "16.2px",
|
||||||
|
"padding": "4px 12px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"text": "Oficio 2",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 72.328125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "etiqueta svelte-q0tptl",
|
||||||
|
"color": "oklch(0.45 0.13 20)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 28,
|
||||||
|
"line": "16.2px",
|
||||||
|
"padding": "4px 12px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"text": "Desamor 1",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 90.234375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "etiqueta svelte-q0tptl",
|
||||||
|
"color": "oklch(0.45 0.13 20)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 28,
|
||||||
|
"line": "16.2px",
|
||||||
|
"padding": "4px 12px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"text": "Estilos 1",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 77.203125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "oklch(0.915 0.016 264)",
|
||||||
|
"clase": "portada svelte-1971vt9",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 318.375,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 566
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1971vt9",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Newsreader, \"Iowan Old Style\", Georgia, serif",
|
||||||
|
"height": 26.390625,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "24px",
|
||||||
|
"tag": "H2",
|
||||||
|
"text": "Escribir en un estilo que no es el tuyo",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 534
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1971vt9",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Newsreader, \"Iowan Old Style\", Georgia, serif",
|
||||||
|
"height": 24,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "24px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Escribir en un estilo que no es el tuyo",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 349.09375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "etiqueta svelte-q0tptl",
|
||||||
|
"color": "oklch(0.45 0.13 20)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 28,
|
||||||
|
"line": "16.2px",
|
||||||
|
"padding": "4px 12px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Estilos",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 62.171875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "etiqueta svelte-q0tptl",
|
||||||
|
"color": "oklch(0.45 0.13 290)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 28,
|
||||||
|
"line": "16.2px",
|
||||||
|
"padding": "4px 12px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Oficio",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 57.296875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "oklch(0.915 0.016 264)",
|
||||||
|
"clase": "portada svelte-1971vt9",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 318.375,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 566
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1971vt9",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Newsreader, \"Iowan Old Style\", Georgia, serif",
|
||||||
|
"height": 26.390625,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "24px",
|
||||||
|
"tag": "H2",
|
||||||
|
"text": "Lo que no cabe en una canción",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 534
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1971vt9",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Newsreader, \"Iowan Old Style\", Georgia, serif",
|
||||||
|
"height": 24,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "24px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Lo que no cabe en una canción",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 289.71875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "etiqueta svelte-q0tptl",
|
||||||
|
"color": "oklch(0.45 0.13 20)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 28,
|
||||||
|
"line": "16.2px",
|
||||||
|
"padding": "4px 12px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Desamor",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 75.203125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "etiqueta svelte-q0tptl",
|
||||||
|
"color": "oklch(0.45 0.13 290)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 28,
|
||||||
|
"line": "16.2px",
|
||||||
|
"padding": "4px 12px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Oficio",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 57.296875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "emblema svelte-34w38f",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 52,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Senza Paura",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 52
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 15,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Sobre mí",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 55.640625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 15,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "hola@senzapaura.es",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 132
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 15,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "booking@senzapaura.es",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 155.265625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 15,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Formulario de contacto",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 143.09375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 15,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Suscripciones",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 88.53125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 14,
|
||||||
|
"line": "19.8px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Información legal",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 91.171875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 14,
|
||||||
|
"line": "19.8px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Condiciones de uso",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 105.15625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 14,
|
||||||
|
"line": "19.8px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Política de privacidad",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 112.578125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 14,
|
||||||
|
"line": "19.8px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Política de cookies",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 99
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 14,
|
||||||
|
"line": "19.8px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Condiciones de venta",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 114.890625
|
||||||
|
}
|
||||||
|
]
|
||||||
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 44 KiB |
@ -0,0 +1,744 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"background": "oklch(0.52 0.125 74)",
|
||||||
|
"clase": "saltar svelte-12qhfyh",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 42.390625,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "8px 16px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Saltar al contenido",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 163.3125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "marca svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Newsreader, \"Iowan Old Style\", Georgia, serif",
|
||||||
|
"height": 38,
|
||||||
|
"line": "39.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "34px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Senza Paura",
|
||||||
|
"weight": "600",
|
||||||
|
"width": 226.3125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 26,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "4px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Música",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 51.328125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 26,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "4px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Músicos y grupos",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 124.203125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 26,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "4px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Taller de letra",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 93.625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 26,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "4px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Blog",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 32.796875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "accion-icono jukebox svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Juke-box",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 44
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 35.390625,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "4px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Entrar",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 43.78125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "accion-icono buscar-contenido svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Buscar en Senza Paura",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 44
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "accion-icono carrito svelte-1ss0jhs",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Carrito",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 44
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "boton svelte-10d17h5",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "1px 6px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"text": "Cambiar al tema oscuro",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 44
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-92u4vw",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 54.1875,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "4px 0px 4px 12px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "01 Lo que hace que una letra funcione",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-92u4vw",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 31.09375,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "4px 0px 4px 12px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "02 La imagen concreta",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-92u4vw",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 31.09375,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "4px 0px 4px 12px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "03 Métrica y acento",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-92u4vw",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 31.09375,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "4px 0px 4px 12px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "04 La rima como consecuencia",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-92u4vw",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 31.09375,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "4px 0px 4px 12px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "05 Estructura y giro",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-92u4vw",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 31.09375,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "4px 0px 4px 12px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "06 El estribillo",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-92u4vw",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 31.09375,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "4px 0px 4px 12px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Fichas de género",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-92u4vw",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 31.09375,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "4px 0px 4px 12px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Glosario",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-qodzbh",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Newsreader, \"Iowan Old Style\", Georgia, serif",
|
||||||
|
"height": 52.796875,
|
||||||
|
"line": "52.8px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "48px",
|
||||||
|
"tag": "H1",
|
||||||
|
"text": "Taller de letras",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 568.40625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-qodzbh",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Newsreader, \"Iowan Old Style\", Georgia, serif",
|
||||||
|
"height": 37.390625,
|
||||||
|
"line": "37.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "34px",
|
||||||
|
"tag": "H2",
|
||||||
|
"text": "Los principios",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 912
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-qodzbh",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 99.6875,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "16px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "01 Lo que hace que una letra funcione Una letra funciona cuando dice una sola co",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 912
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-qodzbh",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 99.6875,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "16px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "02 La imagen concreta Lo concreto emociona; lo abstracto solo informa de que hay",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 912
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-qodzbh",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 122.78125,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "16px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "03 Métrica y acento Escribes para una boca, no para una página: lo que no se pue",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 912
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-qodzbh",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 99.6875,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "16px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "04 La rima como consecuencia La rima confirma un verso que ya era bueno; no pued",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 912
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-qodzbh",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 99.6875,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "16px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "05 Estructura y giro Una canción es una promesa que se cumple de otra manera.",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 912
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-qodzbh",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 99.6875,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "16px 0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "06 El estribillo El estribillo es la frase que sobrevive cuando se olvida el res",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 912
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-qodzbh",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Newsreader, \"Iowan Old Style\", Georgia, serif",
|
||||||
|
"height": 37.390625,
|
||||||
|
"line": "37.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "34px",
|
||||||
|
"tag": "H2",
|
||||||
|
"text": "Fichas de género",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 912
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "oklch(0.76 0.13 82)",
|
||||||
|
"clase": "btn btn--primary svelte-qodzbh",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 46.25,
|
||||||
|
"line": "20.25px",
|
||||||
|
"padding": "12px 24px",
|
||||||
|
"size": "15px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Ver los géneros",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 154.296875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "emblema svelte-34w38f",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 52,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Senza Paura",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 52
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 15,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Sobre mí",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 55.640625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 15,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "hola@senzapaura.es",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 132
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 15,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "booking@senzapaura.es",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 155.265625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 15,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Formulario de contacto",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 143.09375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 15,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Suscripciones",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 88.53125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 14,
|
||||||
|
"line": "19.8px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Información legal",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 91.171875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 14,
|
||||||
|
"line": "19.8px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Condiciones de uso",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 105.15625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 14,
|
||||||
|
"line": "19.8px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Política de privacidad",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 112.578125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 14,
|
||||||
|
"line": "19.8px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Política de cookies",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 99
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-34w38f",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 14,
|
||||||
|
"line": "19.8px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "12px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "Condiciones de venta",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 114.890625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-amof2w",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Newsreader, \"Iowan Old Style\", Georgia, serif",
|
||||||
|
"height": 26.390625,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "24px",
|
||||||
|
"tag": "H2",
|
||||||
|
"text": "Contacto",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 416
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-amof2w",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 15,
|
||||||
|
"line": "21px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "A",
|
||||||
|
"text": "hola@senzapaura.es",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 132
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-14m83vr",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 94.171875,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "LABEL",
|
||||||
|
"text": "No rellenes este campo",
|
||||||
|
"weight": "600",
|
||||||
|
"width": 51.296875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "oklch(0.98 0.008 264)",
|
||||||
|
"clase": "svelte-14m83vr",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44.390625,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "8px 12px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "INPUT",
|
||||||
|
"text": "",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 26
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-14m83vr",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 23.09375,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "LABEL",
|
||||||
|
"text": "Nombre",
|
||||||
|
"weight": "600",
|
||||||
|
"width": 416
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "oklch(0.98 0.008 264)",
|
||||||
|
"clase": "svelte-14m83vr",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44.390625,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "8px 12px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "INPUT",
|
||||||
|
"text": "",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 416
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-14m83vr",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 23.09375,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "LABEL",
|
||||||
|
"text": "Correo electrónico",
|
||||||
|
"weight": "600",
|
||||||
|
"width": 416
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "oklch(0.98 0.008 264)",
|
||||||
|
"clase": "svelte-14m83vr",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44.390625,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "8px 12px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "INPUT",
|
||||||
|
"text": "",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 416
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-14m83vr",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 23.09375,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "LABEL",
|
||||||
|
"text": "Motivo",
|
||||||
|
"weight": "600",
|
||||||
|
"width": 416
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "oklch(0.98 0.008 264)",
|
||||||
|
"clase": "svelte-14m83vr",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"line": "normal",
|
||||||
|
"padding": "8px 12px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "SELECT",
|
||||||
|
"text": "Elige un motivoColaboración o encargoLicencia de una obraContratación de un dire",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 416
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "svelte-14m83vr",
|
||||||
|
"color": "oklch(0.24 0.055 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 23.09375,
|
||||||
|
"line": "23.1px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "14px",
|
||||||
|
"tag": "LABEL",
|
||||||
|
"text": "Mensaje",
|
||||||
|
"weight": "600",
|
||||||
|
"width": 416
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "oklch(0.3 0.075 255)",
|
||||||
|
"clase": "btn btn--primary svelte-14m83vr",
|
||||||
|
"color": "oklch(0.98 0.005 255)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 46.25,
|
||||||
|
"line": "20.25px",
|
||||||
|
"padding": "12px 24px",
|
||||||
|
"size": "15px",
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"text": "Enviar mensaje",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 154.59375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "rgba(0, 0, 0, 0)",
|
||||||
|
"clase": "cerrar modal-close svelte-amof2w",
|
||||||
|
"color": "oklch(0.48 0.038 264)",
|
||||||
|
"font": "Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"line": "26.4px",
|
||||||
|
"padding": "0px",
|
||||||
|
"size": "16px",
|
||||||
|
"tag": "BUTTON",
|
||||||
|
"text": "Cerrar contacto",
|
||||||
|
"weight": "400",
|
||||||
|
"width": 44
|
||||||
|
}
|
||||||
|
]
|
||||||
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 40 KiB |
@ -0,0 +1,336 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"buttons": [
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"padding": "11px 16px",
|
||||||
|
"text": "Acción principal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"padding": "11px 16px",
|
||||||
|
"text": "Acción secundaria"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"padding": "11px 16px",
|
||||||
|
"text": "No disponible"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 36,
|
||||||
|
"padding": "7px 12px",
|
||||||
|
"text": "Compacto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"padding": "11px 16px",
|
||||||
|
"text": "Acción con una etiqueta extensa que debe adaptarse al espacio disponible"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"page": "/__diseno",
|
||||||
|
"w": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buttons": [
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"text": "Acción principal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"text": "Acción secundaria"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"text": "No disponible"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 36,
|
||||||
|
"text": "Compacto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 64,
|
||||||
|
"text": "Acción con una etiqueta extensa que debe adaptarse al espacio disponible"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"overflow": false,
|
||||||
|
"page": "/__diseno",
|
||||||
|
"w": 390
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"brokenImages": 0,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"height": 44,
|
||||||
|
"weight": "400"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"height": 44,
|
||||||
|
"weight": "400"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"overflow": false,
|
||||||
|
"page": "/medios",
|
||||||
|
"w": 390
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buttons": [
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"text": "Acción principal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"text": "Acción secundaria"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"text": "No disponible"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 36,
|
||||||
|
"text": "Compacto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"text": "Acción con una etiqueta extensa que debe adaptarse al espacio disponible"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headings": [
|
||||||
|
{
|
||||||
|
"font": "600 28px / 33.6px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"t": "Controles y jerarquía"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "600 20px / 26px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"t": "Acciones"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "600 20px / 26px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"t": "Campos y mensajes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "600 20px / 26px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"t": "Sección"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "600 18px / 24.3px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"t": "Panel secundario"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "600 20px / 26px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"t": "Comprobación en el navegador"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"page": "estudio/__diseno",
|
||||||
|
"w": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buttons": [
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 44,
|
||||||
|
"text": "Acción principal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 44,
|
||||||
|
"text": "Acción secundaria"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 44,
|
||||||
|
"text": "No disponible"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 36,
|
||||||
|
"text": "Compacto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 64,
|
||||||
|
"text": "Acción con una etiqueta extensa que debe adaptarse al espacio disponible"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"page": "estudio/__diseno",
|
||||||
|
"status": "Sin desviaciones de tipografía, altura ni desbordamiento en las muestras.",
|
||||||
|
"w": 390
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 44,
|
||||||
|
"text": "Guardar borrador"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 44,
|
||||||
|
"text": "Vista previa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 44,
|
||||||
|
"text": "Revisar y publicar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldY": 547.140625,
|
||||||
|
"overflow": false,
|
||||||
|
"page": "estudio-editor",
|
||||||
|
"w": 768
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"text": "Guardar borrador"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"text": "Vista previa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"text": "Revisar y publicar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldY": 500.75,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"font": "16px / 24px Archivo, ui-sans-serif, system-ui, sans-serif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "16px Archivo, ui-sans-serif, system-ui, sans-serif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "16px Archivo, ui-sans-serif, system-ui, sans-serif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "16px Archivo, ui-sans-serif, system-ui, sans-serif"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"page": "estudio-editor",
|
||||||
|
"w": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"covers": [
|
||||||
|
{
|
||||||
|
"h": 56,
|
||||||
|
"plays": 1,
|
||||||
|
"w": 56
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 56,
|
||||||
|
"plays": 1,
|
||||||
|
"w": 56
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 56,
|
||||||
|
"plays": 1,
|
||||||
|
"w": 56
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"heading": "34px / 37.4px Newsreader, \"Iowan Old Style\", Georgia, serif",
|
||||||
|
"overflow": false,
|
||||||
|
"page": "catalogo-tablas",
|
||||||
|
"w": 390
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buttons": [
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 44,
|
||||||
|
"text": "Guardar borrador"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 44,
|
||||||
|
"text": "Revisar y publicar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 36,
|
||||||
|
"text": "Texto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 36,
|
||||||
|
"text": "Código"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 36,
|
||||||
|
"text": "Vídeo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 36,
|
||||||
|
"text": "Imagen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 36,
|
||||||
|
"text": "Sonido"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"h": 44,
|
||||||
|
"text": "Retirar publicación"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldY": 605.34375,
|
||||||
|
"overflow": false,
|
||||||
|
"page": "estudio-editor-final",
|
||||||
|
"w": 390
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"controls": [
|
||||||
|
{
|
||||||
|
"font": "16px / 24px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"tag": "INPUT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "16px / 24px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"tag": "INPUT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "16px / 24px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"tag": "INPUT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "16px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"tag": "SELECT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font": "500 14px / 20px Archivo, ui-sans-serif, system-ui, sans-serif",
|
||||||
|
"height": 44,
|
||||||
|
"tag": "BUTTON"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"page": "/contacto",
|
||||||
|
"w": 1280
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -0,0 +1,170 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"ancho": 3801,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Reproducción normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 1920,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Reproducción normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 1440,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Reproducción normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 1280,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Reproducción normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 1024,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Reproducción normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 838,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Reproducción normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 768,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Reproducción normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 390,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Reproducción normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 3801,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Iniciar programación de prueba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 1920,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Iniciar programación de prueba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 1440,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Iniciar programación de prueba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 1280,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Iniciar programación de prueba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 1024,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Iniciar programación de prueba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 838,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Iniciar programación de prueba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 768,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Iniciar programación de prueba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 390,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Iniciar programación de prueba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 3801,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Elegir canción manual"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 1920,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Elegir canción manual"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 1440,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Elegir canción manual"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 1280,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Elegir canción manual"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 1024,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Elegir canción manual"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 838,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Elegir canción manual"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 768,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Elegir canción manual"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ancho": 390,
|
||||||
|
"controlesFuera": 0,
|
||||||
|
"desborde": 0,
|
||||||
|
"desvio": 0,
|
||||||
|
"estado": "Elegir canción manual"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 91 KiB |
@ -0,0 +1,86 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"alturasEtiquetas": [
|
||||||
|
44
|
||||||
|
],
|
||||||
|
"ancho": 1440,
|
||||||
|
"desborde": 0,
|
||||||
|
"origen": [
|
||||||
|
{
|
||||||
|
"alto": 36,
|
||||||
|
"texto": "Todo el catálogo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alto": 36,
|
||||||
|
"texto": "Emociones y etiquetas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alto": 36,
|
||||||
|
"texto": "Listas y canciones"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alturasEtiquetas": [
|
||||||
|
44
|
||||||
|
],
|
||||||
|
"ancho": 1024,
|
||||||
|
"desborde": 0,
|
||||||
|
"origen": [
|
||||||
|
{
|
||||||
|
"alto": 36,
|
||||||
|
"texto": "Todo el catálogo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alto": 36,
|
||||||
|
"texto": "Emociones y etiquetas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alto": 36,
|
||||||
|
"texto": "Listas y canciones"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alturasEtiquetas": [
|
||||||
|
44
|
||||||
|
],
|
||||||
|
"ancho": 768,
|
||||||
|
"desborde": 0,
|
||||||
|
"origen": [
|
||||||
|
{
|
||||||
|
"alto": 36,
|
||||||
|
"texto": "Todo el catálogo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alto": 36,
|
||||||
|
"texto": "Emociones y etiquetas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alto": 36,
|
||||||
|
"texto": "Listas y canciones"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alturasEtiquetas": [
|
||||||
|
44
|
||||||
|
],
|
||||||
|
"ancho": 390,
|
||||||
|
"desborde": 0,
|
||||||
|
"origen": [
|
||||||
|
{
|
||||||
|
"alto": 48.5,
|
||||||
|
"texto": "Todo el catálogo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alto": 48.5,
|
||||||
|
"texto": "Emociones y etiquetas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alto": 48.5,
|
||||||
|
"texto": "Listas y canciones"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 30 KiB |
@ -0,0 +1,21 @@
|
|||||||
|
# Correcciones del reproductor y Jukebox
|
||||||
|
|
||||||
|
El reparto desigual de columnas desplazaba el transporte al activar el Jukebox. Los laterales ahora tienen el mismo ancho; cuando falta espacio, el estado del Jukebox ocupa otra fila. En móvil el transporte ocupa una fila centrada. Los botones conservan su tamaño sin comprimirse.
|
||||||
|
|
||||||
|
El botón principal del hero reproduce el Jukebox elegido (por enlace, selección actual o único disponible), lo pausa y lo continúa. La selección se abre con un botón independiente, «Elegir Jukebox». Antes «Play JukeBox» únicamente desplegaba la selección. En espera, el botón informa «Jukebox programado».
|
||||||
|
|
||||||
|
Las acciones de la barra y del hero corresponden al estado: «Detener Jukebox» durante su reproducción, «Cancelar reanudación» durante una selección manual con retorno pendiente y «Desactivar Jukebox» en pausa o espera. Cancelar el retorno conserva la canción manual. El acceso anónimo al Jukebox también está disponible en el menú móvil.
|
||||||
|
|
||||||
|
## Comprobaciones
|
||||||
|
|
||||||
|
- 24 combinaciones: reproducción normal, programación activa y canción manual, cada una a 3801, 1920, 1440, 1280, 1024, 838, 768 y 390 píxeles. Desviación del transporte respecto al centro de la barra: 0 px. Sin desbordamientos ni controles fuera de la barra. [Medidas](assets/jukebox-corregido-2026-09-14/centrado.json).
|
||||||
|
- Navegador: audio público real, Play con una pulsación, pausa, continuación, interrupción manual, retorno a la pista y posición anteriores, cancelación sin cortar la canción manual y reinicio. La última carga completa no produjo errores de JavaScript.
|
||||||
|
- Galería de desarrollo `/__diseno/reproductor`: instancia del reproductor en memoria y los mismos componentes de la página, sin modificar cuentas ni programaciones guardadas. La ruta devuelve 404 fuera de desarrollo.
|
||||||
|
- 31 pruebas unitarias de cola, selección y programación superadas. Regresiones de interfaz añadidas en `e2e/jukebox-controles.e2e.ts`; sus escenarios se comprobaron mediante el navegador integrado. No se ejecutó el conjunto E2E autenticado con escrituras en la base de datos.
|
||||||
|
- Servidor local: `/juke-box` responde HTTP 200.
|
||||||
|
|
||||||
|
## Límites de la verificación
|
||||||
|
|
||||||
|
Se verificaron los componentes y el motor con canciones públicas y una programación efímera. No se comprobó la programación guardada de la cuenta del usuario. La revisión automática rechazó crear una cuenta y suscripción de prueba; esa acción no se ejecutó y no se modificaron cuentas ni suscripciones.
|
||||||
|
|
||||||
|
[Escritorio](assets/jukebox-corregido-2026-09-14/escritorio.jpg) · [Móvil](assets/jukebox-corregido-2026-09-14/movil.jpg)
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
# Jukebox por emociones y etiquetas
|
||||||
|
|
||||||
|
Cada franja permite elegir «Emociones y etiquetas» en Contenido. Usa las etiquetas editoriales del catálogo, incluidas las emociones, con búsqueda, selección múltiple, recuentos y vista previa. Coincidir con cualquiera de las etiquetas basta; una canción no se duplica por coincidir con varias.
|
||||||
|
|
||||||
|
La franja guarda las etiquetas, no una lista fija de canciones. La selección se resuelve con el catálogo disponible al iniciar el Jukebox, tras aplicar los permisos de escucha existentes. Las programaciones anteriores mantienen su funcionamiento. El campo opcional se guarda dentro del JSON de franjas existente; no requiere migración.
|
||||||
|
|
||||||
|
Una selección vacía desactiva el guardado y la prueba de reproducción. El servidor rechaza filtros malformados y filtros sin canciones coincidentes. Una selección sin coincidencias nunca se sustituye por el catálogo completo. El resumen de configuración muestra las etiquetas elegidas y el número de canciones.
|
||||||
|
|
||||||
|
## Verificación
|
||||||
|
|
||||||
|
- 39 pruebas automatizadas superadas: cola, programación, selección y endpoint de guardado. Cubren normalización de tildes y mayúsculas, unión sin duplicados, incorporación de canciones nuevas al resolver, compatibilidad con franjas anteriores, serialización y datos inválidos. Las pruebas del endpoint usan almacenamiento simulado; no escriben en cuentas reales.
|
||||||
|
- Comprobación manual en navegador con los componentes reales: «Deseo» y «Despedidas» producen tres canciones sin duplicar «Quédate»; «Melancolía» reproduce «Para qué recordar» y el audio avanza.
|
||||||
|
- Revisión visual a 1440, 1024, 768 y 390 píxeles, sin desbordamientos. Opciones de etiquetas de 44 px y alturas iguales entre los controles de origen. Temas claro y oscuro comprobados.
|
||||||
|
- `check:sitio`: sin errores ni avisos. ESLint en los archivos afectados y `git diff --check`: sin incidencias.
|
||||||
|
- Regresión de navegador añadida en `e2e/jukebox-etiquetas.e2e.ts`. La galería `/__diseno/jukebox` es exclusiva de desarrollo y permite probar el editor y audio sin modificar una cuenta. Sus escenarios se verificaron con el navegador integrado; no se ejecutó el conjunto E2E autenticado contra la base de datos.
|
||||||
|
|
||||||
|
[Escritorio](assets/jukebox-etiquetas-2026-09-14/escritorio.jpg) · [Móvil](assets/jukebox-etiquetas-2026-09-14/movil.jpg) · [Tema claro](assets/jukebox-etiquetas-2026-09-14/claro.jpg) · [Medidas](assets/jukebox-etiquetas-2026-09-14/medidas.json)
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
# Likes públicos y valoraciones conservadas
|
||||||
|
|
||||||
|
Las fichas y tarjetas de canciones muestran un pulgar y el número entero de likes. Las estrellas y las medias ya no se muestran en la web pública. Favoritos conserva su función personal y su corazón.
|
||||||
|
|
||||||
|
La tabla existente `valoracion` reúne ambos datos, con una fila por `cancion_id` y `usuario_id`:
|
||||||
|
|
||||||
|
- `date`: fecha original, conservada.
|
||||||
|
- `like`: booleano independiente; solo los valores `true` suman al contador.
|
||||||
|
- `value`: puntuación anterior de 1 a 5, o NULL si la cuenta solo ha dado like.
|
||||||
|
- `actualizada_en`: fecha histórica adicional conservada.
|
||||||
|
|
||||||
|
Dar o quitar like nunca modifica `value` ni `date`, y quitarlo no borra la fila. Una fila creada para un like puede recibir su primera puntuación posteriormente mediante el servicio de valoraciones; una puntuación existente no se sobrescribe. No se ha habilitado una interfaz profesional nueva.
|
||||||
|
|
||||||
|
La migración 0041 incorpora los likes de la tabla provisional 0040 y retira esa tabla tras verificar la copia. En la misma transacción compara todas las valoraciones antiguas, incluyendo sus fechas, y revierte si falta o cambia alguna. No convierte puntuaciones en likes.
|
||||||
|
|
||||||
|
Validación:
|
||||||
|
|
||||||
|
- 23 pruebas de servidor superadas, incluidas pruebas contra tablas temporales de PostgreSQL, sin cuentas nuevas ni cambios persistentes en los datos de prueba.
|
||||||
|
- Svelte: cero errores y cero avisos. ESLint y `git diff --check` correctos.
|
||||||
|
- Navegador con sesión: 0 → 1 en ficha, persistencia al abrir catálogo y 1 → 0 desde tarjeta. Estado final de prueba restaurado a 0.
|
||||||
|
- Tarjeta móvil de 390 px y escritorio de 1440 px sin desbordamiento; botón de 44 px. Sin errores de consola en la pestaña de prueba.
|
||||||
|
- Los E2E existentes se adaptaron a los likes; no se ejecutó la suite E2E completa.
|
||||||
|
|
||||||
|
Evidencia: [tarjeta móvil](assets/likes-2026-09-14/tarjeta-movil.jpg).
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
-- Likes independientes de las antiguas puntuaciones. Un like por cuenta y canción.
|
||||||
|
CREATE TABLE "me_gusta" (
|
||||||
|
"usuario_id" text NOT NULL REFERENCES "usuario"("id") ON DELETE CASCADE,
|
||||||
|
"cancion_id" text NOT NULL REFERENCES "cancion"("id") ON DELETE CASCADE,
|
||||||
|
"creado_en" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
PRIMARY KEY ("usuario_id", "cancion_id")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX "idx_me_gusta_cancion" ON "me_gusta" ("cancion_id");
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
-- Conserva cada puntuación y su fecha. El like es independiente y empieza desmarcado.
|
||||||
|
CREATE TEMP TABLE valoraciones_antes_de_likes ON COMMIT DROP AS
|
||||||
|
SELECT usuario_id, cancion_id, puntuacion, creada_en, actualizada_en FROM valoracion;
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "valoracion" ADD COLUMN "like" boolean DEFAULT false NOT NULL;
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "valoracion" ALTER COLUMN "puntuacion" DROP NOT NULL;
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "valoracion" RENAME COLUMN "puntuacion" TO "value";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "valoracion" RENAME COLUMN "creada_en" TO "date";
|
||||||
|
--> statement-breakpoint
|
||||||
|
-- Recoge cualquier like recibido durante la transición sin sobrescribir valoraciones.
|
||||||
|
INSERT INTO "valoracion" ("usuario_id", "cancion_id", "date", "like", "value")
|
||||||
|
SELECT "usuario_id", "cancion_id", "creado_en", true, NULL FROM "me_gusta"
|
||||||
|
ON CONFLICT ("usuario_id", "cancion_id") DO UPDATE SET "like" = true;
|
||||||
|
--> statement-breakpoint
|
||||||
|
-- Verifica la copia antes de retirar la tabla provisional. Toda la migración es transaccional.
|
||||||
|
DO $$ BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT usuario_id, cancion_id, puntuacion, creada_en, actualizada_en FROM valoraciones_antes_de_likes
|
||||||
|
EXCEPT
|
||||||
|
SELECT usuario_id, cancion_id, "value", "date", actualizada_en FROM valoracion
|
||||||
|
) THEN RAISE EXCEPTION 'Las valoraciones históricas deben permanecer intactas'; END IF;
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM "me_gusta" m LEFT JOIN "valoracion" v
|
||||||
|
ON v.usuario_id = m.usuario_id AND v.cancion_id = m.cancion_id
|
||||||
|
WHERE v."like" IS DISTINCT FROM true
|
||||||
|
) THEN RAISE EXCEPTION 'Faltan likes por trasladar'; END IF;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DROP TABLE "me_gusta";
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
-- Cuatro identidades ficticias para acreditar las voces de «Cupido sin flechas».
|
||||||
|
-- Los retratos son recursos editoriales originales guardados junto al sitio.
|
||||||
|
INSERT INTO medio (id, clase, clave, publico, mime, bytes, ancho, alto, sha256, alt)
|
||||||
|
VALUES
|
||||||
|
('d35863e8-7ae5-4835-b3f5-a2753a9a75ef', 'imagen', 'images/artistas/cupido-sin-flechas/lia-valdes.webp', true, 'image/webp', 84798, 1448, 1086, '2f695eab09337e1e65a8d8fc8b59808204bc5bdb64514491250a797d7ef5d422', 'Retrato editorial de Lía Valdés'),
|
||||||
|
('e2ab35f0-f965-47f2-afef-15eb63046ac1', 'imagen', 'images/artistas/cupido-sin-flechas/adrian-vega.webp', true, 'image/webp', 110522, 1448, 1086, '8dba9aa86a758d58b0515010bc41820d38524c8946c182c8543ccda4995f4e4d', 'Retrato editorial de Adrián Vega'),
|
||||||
|
('c73a6532-35bc-4bf7-9f37-57061fffb1ac', 'imagen', 'images/artistas/cupido-sin-flechas/mara-santel.webp', true, 'image/webp', 82632, 1448, 1086, '14bba4065581d72acfbf684235870cd0e6c24ef2b7ab50a8c28b43de29ba1776', 'Retrato editorial en blanco y negro de Mara Santel'),
|
||||||
|
('52cd559f-30f5-4b5f-8207-e70aab24f044', 'imagen', 'images/artistas/cupido-sin-flechas/bruno-leal.webp', true, 'image/webp', 104022, 1448, 1086, '51b6b1d24c964b085752a8418e582c60ade084519a04229194348584647e0a38', 'Retrato editorial de Bruno Leal')
|
||||||
|
ON CONFLICT (clave) DO UPDATE SET
|
||||||
|
publico = EXCLUDED.publico,
|
||||||
|
mime = EXCLUDED.mime,
|
||||||
|
bytes = EXCLUDED.bytes,
|
||||||
|
ancho = EXCLUDED.ancho,
|
||||||
|
alto = EXCLUDED.alto,
|
||||||
|
sha256 = EXCLUDED.sha256,
|
||||||
|
alt = EXCLUDED.alt;
|
||||||
|
--> statement-breakpoint
|
||||||
|
INSERT INTO artista (
|
||||||
|
id, codigo, slug, tipo, es_virtual, nombre, descripcion_corta, biografia,
|
||||||
|
imagen_id, pais_codigo, lugar_origen, inicio, estado, origen, modelo_origen, importado_en
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
'53f546da-378b-47ac-be14-b0f3e13a9496', 'ART-CF-001', 'lia-valdes', 'persona', false,
|
||||||
|
'Lía Valdés',
|
||||||
|
'Una voz soul de pulso íntimo, capaz de convertir el deseo y la pérdida en grandes baladas contemporáneas.',
|
||||||
|
'Lía Valdés creció entre el piano familiar y los discos de soul que sonaban en casa. En Cupido sin flechas interpreta las canciones más abiertas y emocionales del álbum, con una voz cálida que pasa de la confidencia al desgarro sin perder elegancia.',
|
||||||
|
(SELECT id FROM medio WHERE clave = 'images/artistas/cupido-sin-flechas/lia-valdes.webp'),
|
||||||
|
CASE WHEN EXISTS (SELECT 1 FROM pais WHERE codigo = 'ES') THEN 'ES' END,
|
||||||
|
'Valencia', 2012, 'publicado', 'importado', 'gpt-image-2 / perfil ficticio', now()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'f50f8e16-c1ae-4326-a303-c8ba3b30ea58', 'ART-CF-002', 'adrian-vega', 'persona', false,
|
||||||
|
'Adrián Vega',
|
||||||
|
'Baladista de voz grave y vivida, con una interpretación serena marcada por la memoria y los años.',
|
||||||
|
'Adrián Vega llegó a la canción después de una larga etapa como músico de directo. Su timbre áspero, su fraseo lento y una presencia de otra época dan peso a las promesas, las ausencias y los regresos de Cupido sin flechas.',
|
||||||
|
(SELECT id FROM medio WHERE clave = 'images/artistas/cupido-sin-flechas/adrian-vega.webp'),
|
||||||
|
CASE WHEN EXISTS (SELECT 1 FROM pais WHERE codigo = 'ES') THEN 'ES' END,
|
||||||
|
'Madrid', 1996, 'publicado', 'importado', 'gpt-image-2 / perfil ficticio', now()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'87387a05-9058-492e-8263-24549fced35b', 'ART-CF-003', 'mara-santel', 'persona', false,
|
||||||
|
'Mara Santel',
|
||||||
|
'Una intérprete de pop nocturno y emoción contenida, reconocible por su dicción precisa y su tensión dramática.',
|
||||||
|
'Mara Santel construye cada canción desde el silencio y la palabra. Su lectura sobria, casi cinematográfica, sostiene los pasajes más frágiles de Cupido sin flechas y deja que cada ruptura respire antes del siguiente verso.',
|
||||||
|
(SELECT id FROM medio WHERE clave = 'images/artistas/cupido-sin-flechas/mara-santel.webp'),
|
||||||
|
CASE WHEN EXISTS (SELECT 1 FROM pais WHERE codigo = 'ES') THEN 'ES' END,
|
||||||
|
'Barcelona', 2008, 'publicado', 'importado', 'gpt-image-2 / perfil ficticio', now()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'e81d9231-b0d1-4bc0-8c4a-8e2229bef15d', 'ART-CF-004', 'bruno-leal', 'persona', false,
|
||||||
|
'Bruno Leal',
|
||||||
|
'Voz fronteriza entre la balada y el rock, directa, madura y sin miedo a mostrar las grietas.',
|
||||||
|
'Bruno Leal aporta el reverso más áspero del álbum. Su trayectoria entre pequeñas salas y bandas de carretera se oye en una voz sin adornos, elegida para las canciones donde el amor se mira con distancia, ironía y dignidad.',
|
||||||
|
(SELECT id FROM medio WHERE clave = 'images/artistas/cupido-sin-flechas/bruno-leal.webp'),
|
||||||
|
CASE WHEN EXISTS (SELECT 1 FROM pais WHERE codigo = 'ES') THEN 'ES' END,
|
||||||
|
'Sevilla', 2001, 'publicado', 'importado', 'gpt-image-2 / perfil ficticio', now()
|
||||||
|
)
|
||||||
|
ON CONFLICT (slug) DO UPDATE SET
|
||||||
|
codigo = EXCLUDED.codigo,
|
||||||
|
tipo = EXCLUDED.tipo,
|
||||||
|
es_virtual = EXCLUDED.es_virtual,
|
||||||
|
nombre = EXCLUDED.nombre,
|
||||||
|
descripcion_corta = EXCLUDED.descripcion_corta,
|
||||||
|
biografia = EXCLUDED.biografia,
|
||||||
|
imagen_id = EXCLUDED.imagen_id,
|
||||||
|
pais_codigo = EXCLUDED.pais_codigo,
|
||||||
|
lugar_origen = EXCLUDED.lugar_origen,
|
||||||
|
inicio = EXCLUDED.inicio,
|
||||||
|
estado = EXCLUDED.estado,
|
||||||
|
origen = EXCLUDED.origen,
|
||||||
|
modelo_origen = EXCLUDED.modelo_origen,
|
||||||
|
importado_en = EXCLUDED.importado_en,
|
||||||
|
actualizado_en = now();
|
||||||
|
--> statement-breakpoint
|
||||||
|
-- Sustituye cualquier crédito principal provisional del disco por el reparto editorial definitivo.
|
||||||
|
DELETE FROM cancion_interprete
|
||||||
|
WHERE rol = 'principal'
|
||||||
|
AND cancion_id IN (
|
||||||
|
SELECT c.id FROM cancion c
|
||||||
|
INNER JOIN album a ON a.id = c.album_id
|
||||||
|
WHERE a.slug = 'cupido-sin-flechas' AND c.version_de_id IS NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
INSERT INTO cancion_interprete (cancion_id, artista_id, rol, orden)
|
||||||
|
SELECT c.id, a.id, 'principal', 0
|
||||||
|
FROM (
|
||||||
|
VALUES
|
||||||
|
('amame-esta-noche-nada-mas', 'lia-valdes'),
|
||||||
|
('cuando-tus-manos-me-tocan', 'adrian-vega'),
|
||||||
|
('inventario-a-los-56', 'bruno-leal'),
|
||||||
|
('no-me-mientas', 'mara-santel'),
|
||||||
|
('pague-por-una-noche-de-amor', 'lia-valdes'),
|
||||||
|
('para-que-recordar', 'mara-santel'),
|
||||||
|
('prometiste', 'adrian-vega'),
|
||||||
|
('quedate', 'lia-valdes'),
|
||||||
|
('ton-silence-me-libere', 'mara-santel'),
|
||||||
|
('tus-malas-maneras', 'bruno-leal')
|
||||||
|
) AS reparto(cancion_slug, artista_slug)
|
||||||
|
INNER JOIN cancion c ON c.slug = reparto.cancion_slug
|
||||||
|
INNER JOIN artista a ON a.slug = reparto.artista_slug
|
||||||
|
ON CONFLICT (cancion_id, artista_id, rol) DO UPDATE SET orden = EXCLUDED.orden;
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
CREATE TABLE "artista_seguido" (
|
||||||
|
"usuario_id" text NOT NULL,
|
||||||
|
"artista_id" text NOT NULL,
|
||||||
|
"seguido_en" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "artista_seguido_usuario_id_artista_id_pk" PRIMARY KEY("usuario_id", "artista_id"),
|
||||||
|
CONSTRAINT "artista_seguido_usuario_fk" FOREIGN KEY ("usuario_id") REFERENCES "usuario"("id") ON DELETE cascade,
|
||||||
|
CONSTRAINT "artista_seguido_artista_fk" FOREIGN KEY ("artista_id") REFERENCES "artista"("id") ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX "artista_seguido_usuario" ON "artista_seguido" USING btree ("usuario_id");
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX "artista_seguido_artista" ON "artista_seguido" USING btree ("artista_id");
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
-- Los retratos pasan a un encuadre panorámico preparado para la cabecera pública.
|
||||||
|
UPDATE medio SET
|
||||||
|
bytes = 83386,
|
||||||
|
ancho = 1672,
|
||||||
|
alto = 941,
|
||||||
|
sha256 = '8cb093e8dd2c775815ee7af232c9829598049d3f00753329d6064c95cde4eb08'
|
||||||
|
WHERE clave = 'images/artistas/cupido-sin-flechas/lia-valdes.webp';
|
||||||
|
--> statement-breakpoint
|
||||||
|
UPDATE medio SET
|
||||||
|
bytes = 118842,
|
||||||
|
ancho = 1672,
|
||||||
|
alto = 941,
|
||||||
|
sha256 = '7cbdc2b9489c586cf8b8f18b56cc0621c59cda9f75ad2d25d9a97a206e89d1a0'
|
||||||
|
WHERE clave = 'images/artistas/cupido-sin-flechas/adrian-vega.webp';
|
||||||
|
--> statement-breakpoint
|
||||||
|
UPDATE medio SET
|
||||||
|
bytes = 69182,
|
||||||
|
ancho = 1672,
|
||||||
|
alto = 941,
|
||||||
|
sha256 = '1eff6b1c802d397d3aeb310df610d6f1827305d44a8428112dce15d25eee2944'
|
||||||
|
WHERE clave = 'images/artistas/cupido-sin-flechas/mara-santel.webp';
|
||||||
|
--> statement-breakpoint
|
||||||
|
UPDATE medio SET
|
||||||
|
bytes = 102606,
|
||||||
|
ancho = 1672,
|
||||||
|
alto = 941,
|
||||||
|
sha256 = '74cb868aeffb3c1b7cac6dab79dc72afc775dfd3dfabeb1bd96cec10a98fc31f'
|
||||||
|
WHERE clave = 'images/artistas/cupido-sin-flechas/bruno-leal.webp';
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
-- Sincroniza los metadatos con los cuatro retratos definitivos incluidos en static.
|
||||||
|
-- Actualiza solo los archivos conocidos; repetir estas sentencias conserva el resultado.
|
||||||
|
UPDATE medio SET
|
||||||
|
bytes = 113578,
|
||||||
|
ancho = 1672,
|
||||||
|
alto = 941,
|
||||||
|
sha256 = '88b20050c4d4257187ad9eaebc8fe4b4ea76446370698796186f3aff1dbcaa55'
|
||||||
|
WHERE clave = 'images/artistas/cupido-sin-flechas/lia-valdes.webp';
|
||||||
|
--> statement-breakpoint
|
||||||
|
UPDATE medio SET
|
||||||
|
bytes = 140926,
|
||||||
|
ancho = 1672,
|
||||||
|
alto = 941,
|
||||||
|
sha256 = '3fcea5a11ccf1471929786e343b48751f1a95bbbfb59d24810f4e638a1067b76'
|
||||||
|
WHERE clave = 'images/artistas/cupido-sin-flechas/adrian-vega.webp';
|
||||||
|
--> statement-breakpoint
|
||||||
|
UPDATE medio SET
|
||||||
|
bytes = 73984,
|
||||||
|
ancho = 1672,
|
||||||
|
alto = 941,
|
||||||
|
sha256 = '674d596109d9088eca36df8f6326104af621abc97037764bfe3a316f98b50919'
|
||||||
|
WHERE clave = 'images/artistas/cupido-sin-flechas/mara-santel.webp';
|
||||||
|
--> statement-breakpoint
|
||||||
|
UPDATE medio SET
|
||||||
|
bytes = 110558,
|
||||||
|
ancho = 1672,
|
||||||
|
alto = 941,
|
||||||
|
sha256 = '18a62844dc4348444520559b77e7b736acdc7f9a61477d6ca041aaa802e5d645'
|
||||||
|
WHERE clave = 'images/artistas/cupido-sin-flechas/bruno-leal.webp';
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
test.describe('Controles del Jukebox sin cambios en la base de datos', () => {
|
||||||
|
test.skip(process.env.E2E_JUKEBOX !== '1', 'La galería solo existe en desarrollo.');
|
||||||
|
test('Play inicia, pausa y recupera la programación con acciones coherentes', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
await page.goto('/__diseno/reproductor');
|
||||||
|
await page.locator('html[data-hidratado]').waitFor();
|
||||||
|
const barra = page.getByRole('region', { name: 'Reproductor', exact: true });
|
||||||
|
const posicion = barra.getByRole('slider', { name: 'Posición de reproducción' });
|
||||||
|
await page.getByRole('button', { name: 'Play JukeBox', exact: true }).click();
|
||||||
|
await expect.poll(() => posicion.inputValue().then(Number)).toBeGreaterThan(0);
|
||||||
|
const titulo = await barra.getByRole('group', { name: 'Sonando ahora' }).innerText();
|
||||||
|
await page.getByRole('button', { name: 'Pausar Jukebox', exact: true }).click();
|
||||||
|
await expect(barra).toContainText('En pausa');
|
||||||
|
await expect(page.getByRole('button', { name: 'Detener Jukebox', exact: true })).toHaveCount(0);
|
||||||
|
await page.getByRole('button', { name: 'Continuar Jukebox', exact: true }).click();
|
||||||
|
await page.getByRole('button', { name: 'Elegir canción manual', exact: true }).click();
|
||||||
|
await expect(barra).toContainText('Vuelve al terminar');
|
||||||
|
await expect(page.getByRole('button', { name: 'Detener Jukebox', exact: true })).toHaveCount(0);
|
||||||
|
await expect(barra.getByRole('button', { name: 'Cancelar reanudación' })).toBeVisible();
|
||||||
|
await page.getByRole('button', { name: 'Continuar Jukebox', exact: true }).click();
|
||||||
|
await expect(barra.getByRole('group', { name: 'Sonando ahora' })).toHaveText(titulo);
|
||||||
|
await page.getByRole('button', { name: 'Limpiar prueba', exact: true }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mantiene el transporte centrado con y sin Jukebox, también en escucha manual', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
await page.goto('/__diseno/reproductor');
|
||||||
|
await page.locator('html[data-hidratado]').waitFor();
|
||||||
|
for (const estado of [
|
||||||
|
'Reproducción normal',
|
||||||
|
'Iniciar programación de prueba',
|
||||||
|
'Elegir canción manual'
|
||||||
|
]) {
|
||||||
|
await page.getByRole('button', { name: estado, exact: true }).click();
|
||||||
|
for (const width of [3801, 1920, 1440, 1280, 1024, 838, 768, 390]) {
|
||||||
|
await page.setViewportSize({ width, height: 1000 });
|
||||||
|
const geometria = await page
|
||||||
|
.getByRole('region', { name: 'Reproductor' })
|
||||||
|
.evaluate((barra) => {
|
||||||
|
const r = barra.getBoundingClientRect();
|
||||||
|
const b = barra.querySelector('.principal')!.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
desvio: Math.abs(b.x + b.width / 2 - r.x - r.width / 2),
|
||||||
|
desborde: document.documentElement.scrollWidth - document.documentElement.clientWidth
|
||||||
|
};
|
||||||
|
});
|
||||||
|
expect(geometria.desvio, `${estado} a ${width}px`).toBeLessThanOrEqual(1);
|
||||||
|
expect(geometria.desborde).toBe(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await page.getByRole('button', { name: 'Limpiar prueba', exact: true }).click();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
test.describe('Franjas por emociones y etiquetas', () => {
|
||||||
|
test.skip(process.env.E2E_JUKEBOX !== '1', 'La galería solo existe en desarrollo.');
|
||||||
|
test('selecciona etiquetas, muestra la cola filtrada y bloquea una selección vacía', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
await page.goto('/__diseno/jukebox');
|
||||||
|
await page.locator('html[data-hidratado]').waitFor();
|
||||||
|
await page.getByRole('button', { name: 'Emociones y etiquetas', exact: true }).click();
|
||||||
|
await expect(page.getByRole('button', { name: 'Guardar jukebox', exact: true })).toBeDisabled();
|
||||||
|
await expect(page.getByRole('button', { name: 'Guardar franja', exact: true })).toBeDisabled();
|
||||||
|
await page.getByRole('checkbox', { name: 'Deseo', exact: true }).check();
|
||||||
|
await page.getByRole('checkbox', { name: 'Despedidas', exact: true }).check();
|
||||||
|
await page.getByText('Ver canciones de la selección', { exact: true }).click();
|
||||||
|
const selector = page.getByRole('region', { name: 'Selección de emociones y etiquetas' });
|
||||||
|
const titulos = await selector.locator('details li').allTextContents();
|
||||||
|
expect(titulos.length).toBeGreaterThan(0);
|
||||||
|
expect(new Set(titulos).size).toBe(titulos.length);
|
||||||
|
await expect(page.getByRole('button', { name: 'Guardar jukebox', exact: true })).toBeEnabled();
|
||||||
|
await page.getByRole('button', { name: 'Probar selección', exact: true }).click();
|
||||||
|
await page.getByRole('button', { name: 'Ver la cola de reproducción', exact: true }).click();
|
||||||
|
const cola = page.getByRole('complementary', { name: 'Cola de reproducción' });
|
||||||
|
for (const titulo of titulos) await expect(cola).toContainText(titulo);
|
||||||
|
await expect
|
||||||
|
.poll(() =>
|
||||||
|
page.getByRole('slider', { name: 'Posición de reproducción' }).inputValue().then(Number)
|
||||||
|
)
|
||||||
|
.toBeGreaterThan(0);
|
||||||
|
await page.getByRole('button', { name: 'Detener prueba', exact: true }).click();
|
||||||
|
for (const width of [1440, 1024, 768, 390]) {
|
||||||
|
await page.setViewportSize({ width, height: 1000 });
|
||||||
|
expect(
|
||||||
|
await page.evaluate(
|
||||||
|
() => document.documentElement.scrollWidth - document.documentElement.clientWidth
|
||||||
|
)
|
||||||
|
).toBe(0);
|
||||||
|
}
|
||||||
|
await page.getByRole('button', { name: 'Quitar selección', exact: true }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Probar selección', exact: true })
|
||||||
|
).toBeDisabled();
|
||||||
|
await expect(page.getByRole('button', { name: 'Guardar franja', exact: true })).toBeDisabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,417 @@
|
|||||||
|
/** Genera el diccionario del modelo desde el esquema PostgreSQL realmente aplicado. */
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import pg from 'pg';
|
||||||
|
import { format } from 'prettier';
|
||||||
|
|
||||||
|
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||||
|
|
||||||
|
const salida = process.argv[2] ?? 'docs/modelo-datos.md';
|
||||||
|
const cliente = new pg.Client({
|
||||||
|
connectionString: process.env.DATABASE_URL,
|
||||||
|
query_timeout: 120_000
|
||||||
|
});
|
||||||
|
|
||||||
|
const dominios = [
|
||||||
|
[
|
||||||
|
'Base y artistas',
|
||||||
|
['pais', 'medio', 'persona', 'artista', 'artista_persona', 'artista_identificador']
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Catálogo musical',
|
||||||
|
[
|
||||||
|
'estilo',
|
||||||
|
'album',
|
||||||
|
'cancion',
|
||||||
|
'cancion_interprete',
|
||||||
|
'cancion_audio',
|
||||||
|
'traduccion',
|
||||||
|
'cancion_persona',
|
||||||
|
'registro_obra',
|
||||||
|
'autoria',
|
||||||
|
'video',
|
||||||
|
'cancion_enlace',
|
||||||
|
'album_enlace',
|
||||||
|
'credito_album',
|
||||||
|
'etiqueta',
|
||||||
|
'cancion_etiqueta'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Cuentas y suscripciones',
|
||||||
|
['usuario', 'plan_suscripcion', 'suscripcion', 'identidad', 'codigo_acceso', 'sesion', 'limite']
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Interacción y escucha',
|
||||||
|
[
|
||||||
|
'favorito',
|
||||||
|
'lista_reproduccion',
|
||||||
|
'lista_reproduccion_cancion',
|
||||||
|
'acceso_cancion_usuario',
|
||||||
|
'valoracion',
|
||||||
|
'reproduccion'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Comercio',
|
||||||
|
[
|
||||||
|
'carrito',
|
||||||
|
'carrito_item',
|
||||||
|
'producto',
|
||||||
|
'carrito_producto',
|
||||||
|
'pedido',
|
||||||
|
'pedido_item',
|
||||||
|
'pedido_linea',
|
||||||
|
'compra',
|
||||||
|
'intencion_pago'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
['Facturación', ['datos_facturacion', 'contador_factura', 'factura', 'descarga']],
|
||||||
|
[
|
||||||
|
'Editorial y legal',
|
||||||
|
[
|
||||||
|
'entrada',
|
||||||
|
'entrada_cancion',
|
||||||
|
'entrada_etiqueta',
|
||||||
|
'pagina',
|
||||||
|
'documento_legal',
|
||||||
|
'version_legal',
|
||||||
|
'redireccion'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Taller de letras',
|
||||||
|
[
|
||||||
|
'genero',
|
||||||
|
'genero_origen',
|
||||||
|
'genero_pais',
|
||||||
|
'genero_seccion',
|
||||||
|
'genero_alias',
|
||||||
|
'genero_tema',
|
||||||
|
'cancion_genero',
|
||||||
|
'metro',
|
||||||
|
'genero_metro',
|
||||||
|
'compas',
|
||||||
|
'genero_compas',
|
||||||
|
'instrumento',
|
||||||
|
'genero_instrumento',
|
||||||
|
'genero_persona',
|
||||||
|
'obra_referencia',
|
||||||
|
'obra_referencia_persona',
|
||||||
|
'obra_referencia_enlace',
|
||||||
|
'verso',
|
||||||
|
'verso_linea',
|
||||||
|
'capitulo',
|
||||||
|
'termino',
|
||||||
|
'acepcion',
|
||||||
|
'termino_variante',
|
||||||
|
'capitulo_acepcion',
|
||||||
|
'modelo_ia',
|
||||||
|
'animo',
|
||||||
|
'prompt'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
['Jukebox', ['programacion_jukebox', 'jukebox']]
|
||||||
|
];
|
||||||
|
|
||||||
|
const explicaciones = {
|
||||||
|
valoracion:
|
||||||
|
'Interacción unificada por cuenta y canción. `like` alimenta el contador público; `value` conserva la puntuación de 1 a 5 para un uso profesional posterior. Son independientes.',
|
||||||
|
cancion:
|
||||||
|
'Tema principal o versión musical. `version_de_id` apunta a otra canción y separa obra principal de arreglo o grabación.',
|
||||||
|
medio:
|
||||||
|
'Archivo administrado: imagen, audio, muestra o documento, con metadatos técnicos e integridad SHA-256.',
|
||||||
|
jukebox: 'Programación independiente y versionada de una cuenta.',
|
||||||
|
programacion_jukebox:
|
||||||
|
'Programación histórica por cuenta, conservada por compatibilidad con el modelo anterior de un único Jukebox.',
|
||||||
|
compra: 'Permiso materializado de descarga de una canción adquirida.',
|
||||||
|
factura: 'Documento fiscal inmutable; una rectificativa referencia otra factura.',
|
||||||
|
producto: 'Oferta comercial genérica; sus datos específicos se conservan en JSON.',
|
||||||
|
prompt:
|
||||||
|
'Prompt probado para un modelo de IA, género, ánimo y, opcionalmente, una canción resultante.'
|
||||||
|
};
|
||||||
|
|
||||||
|
const celda = (valor) =>
|
||||||
|
String(valor ?? '')
|
||||||
|
.replaceAll('|', '\\|')
|
||||||
|
.replaceAll('\n', ' ');
|
||||||
|
const codigo = (valor) => `\`${celda(valor).replaceAll('`', '\\`')}\``;
|
||||||
|
const nombreDominio = (tabla) =>
|
||||||
|
dominios.find(([, tablas]) => tablas.includes(tabla))?.[0] ?? 'Otras estructuras';
|
||||||
|
|
||||||
|
await cliente.connect();
|
||||||
|
await cliente.query('BEGIN READ ONLY');
|
||||||
|
try {
|
||||||
|
// Una transacción pertenece a una sola conexión: las consultas se ejecutan en
|
||||||
|
// orden para no solaparlas sobre el mismo cliente de PostgreSQL.
|
||||||
|
const { rows: objetos } =
|
||||||
|
await cliente.query(`select c.relname as nombre, c.relkind, obj_description(c.oid, 'pg_class') as comentario,
|
||||||
|
case when c.relkind in ('v','m') then pg_get_viewdef(c.oid,true) end as definicion
|
||||||
|
from pg_class c join pg_namespace n on n.oid=c.relnamespace
|
||||||
|
where n.nspname='public' and c.relkind in ('r','p','v','m') order by c.relkind, c.relname`);
|
||||||
|
const { rows: columnas } =
|
||||||
|
await cliente.query(`select c.relname as tabla, a.attnum as posicion, a.attname as nombre,
|
||||||
|
pg_catalog.format_type(a.atttypid,a.atttypmod) as tipo, not a.attnotnull as nullable,
|
||||||
|
pg_get_expr(ad.adbin,ad.adrelid) as defecto, col_description(c.oid,a.attnum) as comentario
|
||||||
|
from pg_attribute a join pg_class c on c.oid=a.attrelid join pg_namespace n on n.oid=c.relnamespace
|
||||||
|
left join pg_attrdef ad on ad.adrelid=a.attrelid and ad.adnum=a.attnum
|
||||||
|
where n.nspname='public' and c.relkind in ('r','p','v','m') and a.attnum>0 and not a.attisdropped
|
||||||
|
order by c.relname,a.attnum`);
|
||||||
|
const { rows: restricciones } =
|
||||||
|
await cliente.query(`select c.relname as tabla, con.conname as nombre, con.contype as tipo,
|
||||||
|
pg_get_constraintdef(con.oid,true) as definicion
|
||||||
|
from pg_constraint con join pg_class c on c.oid=con.conrelid join pg_namespace n on n.oid=c.relnamespace
|
||||||
|
where n.nspname='public' order by c.relname, con.contype, con.conname`);
|
||||||
|
const { rows: indices } =
|
||||||
|
await cliente.query(`select tablename as tabla,indexname as nombre,indexdef as definicion
|
||||||
|
from pg_indexes where schemaname='public' order by tablename,indexname`);
|
||||||
|
const { rows: disparadores } =
|
||||||
|
await cliente.query(`select c.relname as tabla,t.tgname as nombre,pg_get_triggerdef(t.oid,true) as definicion
|
||||||
|
from pg_trigger t join pg_class c on c.oid=t.tgrelid join pg_namespace n on n.oid=c.relnamespace
|
||||||
|
where n.nspname='public' and not t.tgisinternal order by c.relname,t.tgname`);
|
||||||
|
const { rows: secuencias } =
|
||||||
|
await cliente.query(`select sequencename as nombre,data_type,start_value,increment_by,min_value,max_value,cycle
|
||||||
|
from pg_sequences where schemaname='public' order by sequencename`);
|
||||||
|
const { rows: enumeraciones } =
|
||||||
|
await cliente.query(`select t.typname as tipo,e.enumlabel as valor,e.enumsortorder as orden
|
||||||
|
from pg_type t join pg_enum e on e.enumtypid=t.oid join pg_namespace n on n.oid=t.typnamespace
|
||||||
|
where n.nspname='public' order by t.typname,e.enumsortorder`);
|
||||||
|
const { rows: funciones } = await cliente.query(`select p.proname as nombre,
|
||||||
|
pg_get_function_identity_arguments(p.oid) as argumentos,
|
||||||
|
pg_get_function_result(p.oid) as resultado,l.lanname as lenguaje
|
||||||
|
from pg_proc p join pg_namespace n on n.oid=p.pronamespace join pg_language l on l.oid=p.prolang
|
||||||
|
where n.nspname='public' and p.prokind='f' order by p.proname,argumentos`);
|
||||||
|
const { rows: migraciones } = await cliente.query(
|
||||||
|
`select count(*)::int as total,max(created_at)::text as ultima from drizzle.__drizzle_migrations`
|
||||||
|
);
|
||||||
|
|
||||||
|
const tablas = objetos.filter((o) => ['r', 'p'].includes(o.relkind));
|
||||||
|
const vistas = objetos.filter((o) => ['v', 'm'].includes(o.relkind));
|
||||||
|
const lineas = [];
|
||||||
|
lineas.push('# Modelo de datos completo de Senza Paura', '');
|
||||||
|
lineas.push(
|
||||||
|
`Generado el ${new Date().toISOString()} desde el esquema PostgreSQL aplicado y contrastado con los contratos TypeScript de la aplicación.`,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
lineas.push(
|
||||||
|
`**Alcance:** ${tablas.length} tablas, ${vistas.length} ${vistas.length === 1 ? 'vista' : 'vistas'}, ${secuencias.length} secuencias, ${funciones.length} funciones de base y ${migraciones[0]?.total ?? 0} migraciones aplicadas.`,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
lineas.push(
|
||||||
|
'## Fuentes y convenciones',
|
||||||
|
'',
|
||||||
|
'- Esquema declarativo: `src/lib/server/db/schema/`.',
|
||||||
|
'- Historial físico: `drizzle/`.',
|
||||||
|
'- Campos TypeScript en `camelCase`; nombres PostgreSQL en `snake_case`.',
|
||||||
|
'- `text` es el identificador opaco habitual. `pais.codigo` es la excepción: usa ISO 3166-1 alfa-2.',
|
||||||
|
'- Los importes monetarios son enteros en céntimos y la moneda usa código ISO en minúsculas.',
|
||||||
|
'- Las marcas de tiempo son `timestamp with time zone`.',
|
||||||
|
'- Los estados editoriales comunes son `borrador`, `publicado` y `retirado`.',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
lineas.push(
|
||||||
|
'## Vista conceptual',
|
||||||
|
'',
|
||||||
|
'```mermaid',
|
||||||
|
'flowchart LR',
|
||||||
|
' BASE[Base y artistas] --> CAT[Catálogo musical]',
|
||||||
|
' CAT --> INT[Interacción y escucha]',
|
||||||
|
' CTA[Cuentas y suscripciones] --> INT',
|
||||||
|
' CTA --> COM[Comercio]',
|
||||||
|
' CAT --> COM',
|
||||||
|
' COM --> FAC[Facturación]',
|
||||||
|
' CAT --> EDI[Editorial y legal]',
|
||||||
|
' BASE --> TALLER[Taller de letras]',
|
||||||
|
' CAT --> TALLER',
|
||||||
|
' CTA --> JUKE[Jukebox]',
|
||||||
|
' CAT --> JUKE',
|
||||||
|
'```',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
lineas.push(
|
||||||
|
'La entidad central es `cancion`. Se relaciona con álbumes, estilos, intérpretes, audios, autoría, etiquetas, ventas, descargas, listas, reproducciones, likes/valoraciones, entradas editoriales, géneros del taller y prompts.',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
lineas.push(
|
||||||
|
'## Reglas que definen el modelo',
|
||||||
|
'',
|
||||||
|
'- Una versión musical es otra fila de `cancion`; `version_de_id` apunta a la canción principal. Una versión no puede venderse por separado.',
|
||||||
|
'- `valoracion` tiene una sola fila por `(usuario_id, cancion_id)`: `like` y `value` no se pisan. Quitar un like conserva `value` y `date`.',
|
||||||
|
'- `artista` es la identidad pública; `persona` es la persona física o acreditada. `artista_persona` permite solistas, grupos y proyectos.',
|
||||||
|
'- `compra` materializa el permiso de descarga. El pedido conserva el hecho comercial; la factura conserva el hecho fiscal.',
|
||||||
|
'- Una factura emitida no se reescribe: las correcciones se modelan como facturas rectificativas.',
|
||||||
|
'- La programación del Jukebox vive como franjas JSON versionadas mediante `revision` para control de concurrencia.',
|
||||||
|
'- El taller usa vocabularios normalizados y tablas puente para géneros, países, metros, compases, instrumentos, figuras, obras, versos y glosario.',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
lineas.push(
|
||||||
|
'## Relaciones principales',
|
||||||
|
'',
|
||||||
|
'```mermaid',
|
||||||
|
'erDiagram',
|
||||||
|
' USUARIO ||--o{ VALORACION : emite',
|
||||||
|
' CANCION ||--o{ VALORACION : recibe',
|
||||||
|
' USUARIO ||--o{ FAVORITO : guarda',
|
||||||
|
' USUARIO ||--o{ LISTA_REPRODUCCION : crea',
|
||||||
|
' LISTA_REPRODUCCION ||--o{ LISTA_REPRODUCCION_CANCION : contiene',
|
||||||
|
' CANCION ||--o{ LISTA_REPRODUCCION_CANCION : aparece',
|
||||||
|
' ESTILO ||--o{ ALBUM : agrupa',
|
||||||
|
' ESTILO ||--o{ CANCION : clasifica',
|
||||||
|
' ALBUM ||--o{ CANCION : publica',
|
||||||
|
' CANCION ||--o{ CANCION_AUDIO : dispone',
|
||||||
|
' MEDIO ||--o{ CANCION_AUDIO : almacena',
|
||||||
|
' ARTISTA ||--o{ CANCION_INTERPRETE : interpreta',
|
||||||
|
' CANCION ||--o{ CANCION_INTERPRETE : acredita',
|
||||||
|
' USUARIO ||--o{ PEDIDO : realiza',
|
||||||
|
' PEDIDO ||--o{ PEDIDO_LINEA : contiene',
|
||||||
|
' PEDIDO ||--o{ FACTURA : documenta',
|
||||||
|
' USUARIO ||--o{ JUKEBOX : configura',
|
||||||
|
'```',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
lineas.push(
|
||||||
|
'## Contratos JSON de la aplicación',
|
||||||
|
'',
|
||||||
|
'### Contenido editorial por bloques',
|
||||||
|
'',
|
||||||
|
'`artista.secciones`, `album.secciones`, `cancion.inspiracion_secciones` y `entrada.secciones` contienen una lista ordenada de bloques:',
|
||||||
|
'',
|
||||||
|
'- Base: `{ id, tipo, titulo? }`.',
|
||||||
|
'- Texto: `{ tipo: "texto", texto }`.',
|
||||||
|
'- Código: `{ tipo: "codigo", codigo, lenguaje? }`.',
|
||||||
|
'- Medio: `{ tipo: "video" | "imagen" | "audio", url, pie?, subtitulos?, alt? }`.',
|
||||||
|
'',
|
||||||
|
'### Presentación editorial',
|
||||||
|
'',
|
||||||
|
'`artista.presentacion`, `album.presentacion`, `cancion.presentacion` y `entrada.presentacion` usan `{ plantilla, heroe }`, donde `heroe` es `automatico`, `visible` u `oculto`.',
|
||||||
|
'',
|
||||||
|
'### Jukebox',
|
||||||
|
'',
|
||||||
|
'`jukebox.franjas` y `programacion_jukebox.franjas` contienen objetos `{ id, inicio, fin, orden, catalogo, etiquetas?, contenidos }`. `orden` es `secuencial` o `aleatoria`; `contenidos` referencia listas o canciones mediante `{ tipo, id }`; varias etiquetas se combinan como unión.',
|
||||||
|
'',
|
||||||
|
'### Comercio abierto',
|
||||||
|
'',
|
||||||
|
'`producto.datos`, `pedido_linea.datos`, `intencion_pago.datos` y `factura.datos_congelados` conservan atributos variables o una instantánea del momento comercial. Los campos consultables y las cantidades monetarias permanecen normalizados en columnas.',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const [dominio] of dominios.concat([['Otras estructuras', []]])) {
|
||||||
|
const delDominio = tablas.filter((t) => nombreDominio(t.nombre) === dominio);
|
||||||
|
if (!delDominio.length) continue;
|
||||||
|
lineas.push(`## ${dominio}`, '');
|
||||||
|
for (const tabla of delDominio) {
|
||||||
|
lineas.push(`### ${codigo(tabla.nombre)}`, '');
|
||||||
|
if (explicaciones[tabla.nombre]) lineas.push(explicaciones[tabla.nombre], '');
|
||||||
|
lineas.push(
|
||||||
|
'| Columna | Tipo PostgreSQL | Nulo | Valor por defecto |',
|
||||||
|
'|---|---|:---:|---|'
|
||||||
|
);
|
||||||
|
for (const col of columnas.filter((c) => c.tabla === tabla.nombre)) {
|
||||||
|
lineas.push(
|
||||||
|
`| ${codigo(col.nombre)} | ${codigo(col.tipo)} | ${col.nullable ? 'sí' : 'no'} | ${col.defecto ? codigo(col.defecto) : '—'} |`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lineas.push('');
|
||||||
|
const cons = restricciones.filter((c) => c.tabla === tabla.nombre);
|
||||||
|
if (cons.length) {
|
||||||
|
lineas.push('Claves y restricciones:', '');
|
||||||
|
for (const con of cons) {
|
||||||
|
const clase =
|
||||||
|
{ p: 'PK', f: 'FK', u: 'UNIQUE', c: 'CHECK', x: 'EXCLUDE' }[con.tipo] ?? con.tipo;
|
||||||
|
lineas.push(`- **${clase} ${celda(con.nombre)}:** ${codigo(con.definicion)}`);
|
||||||
|
}
|
||||||
|
lineas.push('');
|
||||||
|
}
|
||||||
|
const idx = indices.filter(
|
||||||
|
(i) => i.tabla === tabla.nombre && !cons.some((c) => c.nombre === i.nombre)
|
||||||
|
);
|
||||||
|
if (idx.length)
|
||||||
|
lineas.push(`Índices adicionales: ${idx.map((i) => codigo(i.nombre)).join(', ')}.`, '');
|
||||||
|
const trg = disparadores.filter((t) => t.tabla === tabla.nombre);
|
||||||
|
if (trg.length) {
|
||||||
|
lineas.push('Disparadores:', '');
|
||||||
|
for (const t of trg) lineas.push(`- ${codigo(t.nombre)}: ${codigo(t.definicion)}`);
|
||||||
|
lineas.push('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lineas.push('## Vistas', '');
|
||||||
|
if (!vistas.length) lineas.push('No hay vistas en el esquema público.', '');
|
||||||
|
for (const vista of vistas) {
|
||||||
|
lineas.push(
|
||||||
|
`### ${codigo(vista.nombre)}`,
|
||||||
|
'',
|
||||||
|
'| Columna | Tipo PostgreSQL | Nulo |',
|
||||||
|
'|---|---|:---:|'
|
||||||
|
);
|
||||||
|
for (const col of columnas.filter((c) => c.tabla === vista.nombre))
|
||||||
|
lineas.push(
|
||||||
|
`| ${codigo(col.nombre)} | ${codigo(col.tipo)} | ${col.nullable ? 'sí' : 'no'} |`
|
||||||
|
);
|
||||||
|
lineas.push('', 'Definición:', '', '```sql', vista.definicion, '```', '');
|
||||||
|
}
|
||||||
|
lineas.push('## Enumeraciones PostgreSQL', '');
|
||||||
|
if (!enumeraciones.length) lineas.push('No hay tipos enumerados en el esquema público.', '');
|
||||||
|
for (const tipo of [...new Set(enumeraciones.map((e) => e.tipo))]) {
|
||||||
|
lineas.push(
|
||||||
|
`- ${codigo(tipo)}: ${enumeraciones
|
||||||
|
.filter((e) => e.tipo === tipo)
|
||||||
|
.map((e) => codigo(e.valor))
|
||||||
|
.join(', ')}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lineas.push(
|
||||||
|
'',
|
||||||
|
'Los demás vocabularios cerrados se almacenan como `text` y aparecen en las restricciones `CHECK` de cada tabla.',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
lineas.push(
|
||||||
|
'## Funciones de base de datos',
|
||||||
|
'',
|
||||||
|
'| Función | Argumentos | Resultado | Lenguaje |',
|
||||||
|
'|---|---|---|---|'
|
||||||
|
);
|
||||||
|
for (const f of funciones)
|
||||||
|
lineas.push(
|
||||||
|
`| ${codigo(f.nombre)} | ${f.argumentos ? codigo(f.argumentos) : '—'} | ${codigo(f.resultado)} | ${codigo(f.lenguaje)} |`
|
||||||
|
);
|
||||||
|
lineas.push(
|
||||||
|
'',
|
||||||
|
'Las funciones asociadas a disparadores se localizan además bajo la tabla afectada, en el apartado “Disparadores”.',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
lineas.push(
|
||||||
|
'## Secuencias',
|
||||||
|
'',
|
||||||
|
'| Secuencia | Tipo | Inicio | Incremento | Ciclo |',
|
||||||
|
'|---|---|---:|---:|:---:|'
|
||||||
|
);
|
||||||
|
for (const s of secuencias)
|
||||||
|
lineas.push(
|
||||||
|
`| ${codigo(s.nombre)} | ${codigo(s.data_type)} | ${s.start_value} | ${s.increment_by} | ${s.cycle ? 'sí' : 'no'} |`
|
||||||
|
);
|
||||||
|
lineas.push(
|
||||||
|
'',
|
||||||
|
'## Mantenimiento',
|
||||||
|
'',
|
||||||
|
'Este documento se regenera desde la base aplicada:',
|
||||||
|
'',
|
||||||
|
'```powershell',
|
||||||
|
'node --env-file=.env scripts/db/documentar-modelo.mjs',
|
||||||
|
'```',
|
||||||
|
'',
|
||||||
|
'Después de una migración, regénéralo y revisa también los contratos JSON si cambió su estructura TypeScript.',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
const markdown = await format(`${lineas.join('\n')}\n`, { parser: 'markdown' });
|
||||||
|
await fs.writeFile(salida, markdown, 'utf8');
|
||||||
|
console.log(
|
||||||
|
`${salida}: ${tablas.length} tablas, ${vistas.length} ${vistas.length === 1 ? 'vista' : 'vistas'}, ${columnas.length} columnas.`
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
await cliente.query('ROLLBACK').catch(() => {});
|
||||||
|
await cliente.end();
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
/* Geometría compartida por enlaces de acción, formularios y controles musicales.
|
||||||
|
* La altura es mínima: el texto ampliado puede crecer sin quedar recortado. */
|
||||||
|
.btn,
|
||||||
|
.ui-button {
|
||||||
|
--ui-control-height: var(--control-height);
|
||||||
|
--ui-control-inset: var(--sp-4);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--sp-2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: var(--ui-control-height);
|
||||||
|
padding: calc((var(--ui-control-height) - var(--ui-control-line) - 2px) / 2)
|
||||||
|
var(--ui-control-inset);
|
||||||
|
border: 1px solid var(--c-border);
|
||||||
|
border-radius: var(--radius-boton);
|
||||||
|
font: var(--ui-control-font);
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.ui-button.pequeno,
|
||||||
|
.ui-button.compacto,
|
||||||
|
.btn--compact {
|
||||||
|
--ui-control-height: var(--control-height-compact);
|
||||||
|
--ui-control-inset: var(--sp-3);
|
||||||
|
}
|
||||||
|
.ui-button svg,
|
||||||
|
.btn svg {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
.ui-button:disabled,
|
||||||
|
.btn:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.ui-button:focus-visible,
|
||||||
|
.btn:focus-visible {
|
||||||
|
outline: 2px solid var(--c-focus);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
@media (pointer: coarse) {
|
||||||
|
.ui-button.pequeno,
|
||||||
|
.ui-button.compacto,
|
||||||
|
.btn--compact {
|
||||||
|
--ui-control-height: var(--control-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Icono from '$lib/components/Icono.svelte';
|
||||||
|
let { src, nombre, clase }: { src: string; nombre: string; clase: string } = $props();
|
||||||
|
let fallida = $state(false);
|
||||||
|
$effect(() => {
|
||||||
|
if (src) fallida = false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if fallida}
|
||||||
|
<span class="no-disponible" title={nombre}
|
||||||
|
><Icono nombre="documento" tamano={20} /><small>Archivo no disponible</small></span
|
||||||
|
>
|
||||||
|
{:else if clase === 'imagen'}
|
||||||
|
<img {src} alt={nombre} width="48" height="48" loading="lazy" onerror={() => (fallida = true)} />
|
||||||
|
{:else}
|
||||||
|
<audio controls preload="none" {src} aria-label={nombre} onerror={() => (fallida = true)}></audio>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
img {
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--radius-boton);
|
||||||
|
}
|
||||||
|
audio {
|
||||||
|
display: block;
|
||||||
|
width: 230px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.no-disponible {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--sp-2);
|
||||||
|
padding: var(--sp-2);
|
||||||
|
border: 1px dashed var(--borde);
|
||||||
|
border-radius: var(--radius-boton);
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { dev } from '$app/environment';
|
||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
export const load = () => {
|
||||||
|
if (!dev) error(404, 'No encontrado');
|
||||||
|
return {};
|
||||||
|
};
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import GaleriaControles from '$lib/components/GaleriaControles.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<GaleriaControles administracion />
|
||||||